{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dome-gallery",
  "type": "registry:block",
  "title": "Dome gallery",
  "description": "Dome gallery",
  "files": [
    {
      "path": "components/usages/domegalleryusage.tsx",
      "content": "import { useState } from \"react\";\n\nimport DomeGallery from \"@/registry/open-source/dome-gallery\";\n\nimport { Slider } from \"../ui/slider\";\nimport { Switch } from \"../ui/switch\";\n\nexport default function Usage() {\n\tconst [fit, setFit] = useState(0.5);\n\tconst [minRadius, setMinRadius] = useState(600);\n\tconst [maxVerticalRotationDeg, setMaxVerticalRotationDeg] = useState(0);\n\tconst [segments, setSegments] = useState(34);\n\tconst [dragDampening, setDragDampening] = useState(2);\n\tconst [grayscale, setGrayscale] = useState(true);\n\n\treturn (\n\t\t<div className=\"h-screen w-screen overflow-auto\">\n\t\t\t<DomeGallery\n\t\t\t\tfit={fit}\n\t\t\t\tminRadius={minRadius}\n\t\t\t\tmaxVerticalRotationDeg={maxVerticalRotationDeg}\n\t\t\t\tsegments={segments}\n\t\t\t\tdragDampening={dragDampening}\n\t\t\t\tgrayscale={grayscale}\n\t\t\t/>\n\t\t\t<div className=\"h-screen flex flex-col gap-7\">\n\t\t\t\t<label>fit</label>\n\t\t\t\t<Slider\n\t\t\t\t\ttitle=\"Fit\"\n\t\t\t\t\tmin={0.5}\n\t\t\t\t\tmax={1}\n\t\t\t\t\tstep={0.05}\n\t\t\t\t\tdefaultValue={[fit]}\n\t\t\t\t\tonValueChange={(value) => {\n\t\t\t\t\t\tsetFit(value);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\n\t\t\t\t<label>min radius</label>\n\t\t\t\t<Slider\n\t\t\t\t\ttitle=\"Min Radius\"\n\t\t\t\t\tmin={300}\n\t\t\t\t\tmax={1000}\n\t\t\t\t\tstep={50}\n\t\t\t\t\tdefaultValue={[minRadius]}\n\t\t\t\t\tvalueUnit=\"px\"\n\t\t\t\t\tonValueChange={(value) => {\n\t\t\t\t\t\tsetMinRadius(value);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\n\t\t\t\t<label>max vertical rotation</label>\n\t\t\t\t<Slider\n\t\t\t\t\ttitle=\"Max Vertical Rotation\"\n\t\t\t\t\tmin={0}\n\t\t\t\t\tmax={20}\n\t\t\t\t\tstep={1}\n\t\t\t\t\tdefaultValue={[maxVerticalRotationDeg]}\n\t\t\t\t\tvalueUnit=\"°\"\n\t\t\t\t\tonValueChange={(value) => {\n\t\t\t\t\t\tsetMaxVerticalRotationDeg(value);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\n\t\t\t\t<label>segments</label>\n\t\t\t\t<Slider\n\t\t\t\t\ttitle=\"Segments\"\n\t\t\t\t\tmin={20}\n\t\t\t\t\tmax={34}\n\t\t\t\t\tstep={2}\n\t\t\t\t\tdefaultValue={[segments]}\n\t\t\t\t\tonValueChange={(value) => {\n\t\t\t\t\t\tsetSegments(value);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\n\t\t\t\t<label>drag dampening</label>\n\t\t\t\t<Slider\n\t\t\t\t\ttitle=\"Drag Dampening\"\n\t\t\t\t\tmin={0}\n\t\t\t\t\tmax={5}\n\t\t\t\t\tstep={0.2}\n\t\t\t\t\tdefaultValue={[dragDampening]}\n\t\t\t\t\tonValueChange={(value) => {\n\t\t\t\t\t\tsetDragDampening(value);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\n\t\t\t\t<label>grayscale</label>\n\t\t\t\t<Switch\n\t\t\t\t\ttitle=\"Grayscale\"\n\t\t\t\t\tchecked={grayscale}\n\t\t\t\t\tonCheckedChange={(checked) => {\n\t\t\t\t\t\tsetGrayscale(checked);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n",
      "type": "registry:block",
      "target": "~/example.tsx"
    },
    {
      "path": "components/usages/domegalleryusage.tsx",
      "content": "import { useState } from \"react\";\n\nimport DomeGallery from \"@/registry/open-source/dome-gallery\";\n\nimport { Slider } from \"../ui/slider\";\nimport { Switch } from \"../ui/switch\";\n\nexport default function Usage() {\n\tconst [fit, setFit] = useState(0.5);\n\tconst [minRadius, setMinRadius] = useState(600);\n\tconst [maxVerticalRotationDeg, setMaxVerticalRotationDeg] = useState(0);\n\tconst [segments, setSegments] = useState(34);\n\tconst [dragDampening, setDragDampening] = useState(2);\n\tconst [grayscale, setGrayscale] = useState(true);\n\n\treturn (\n\t\t<div className=\"h-screen w-screen overflow-auto\">\n\t\t\t<DomeGallery\n\t\t\t\tfit={fit}\n\t\t\t\tminRadius={minRadius}\n\t\t\t\tmaxVerticalRotationDeg={maxVerticalRotationDeg}\n\t\t\t\tsegments={segments}\n\t\t\t\tdragDampening={dragDampening}\n\t\t\t\tgrayscale={grayscale}\n\t\t\t/>\n\t\t\t<div className=\"h-screen flex flex-col gap-7\">\n\t\t\t\t<label>fit</label>\n\t\t\t\t<Slider\n\t\t\t\t\ttitle=\"Fit\"\n\t\t\t\t\tmin={0.5}\n\t\t\t\t\tmax={1}\n\t\t\t\t\tstep={0.05}\n\t\t\t\t\tdefaultValue={[fit]}\n\t\t\t\t\tonValueChange={(value) => {\n\t\t\t\t\t\tsetFit(value);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\n\t\t\t\t<label>min radius</label>\n\t\t\t\t<Slider\n\t\t\t\t\ttitle=\"Min Radius\"\n\t\t\t\t\tmin={300}\n\t\t\t\t\tmax={1000}\n\t\t\t\t\tstep={50}\n\t\t\t\t\tdefaultValue={[minRadius]}\n\t\t\t\t\tvalueUnit=\"px\"\n\t\t\t\t\tonValueChange={(value) => {\n\t\t\t\t\t\tsetMinRadius(value);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\n\t\t\t\t<label>max vertical rotation</label>\n\t\t\t\t<Slider\n\t\t\t\t\ttitle=\"Max Vertical Rotation\"\n\t\t\t\t\tmin={0}\n\t\t\t\t\tmax={20}\n\t\t\t\t\tstep={1}\n\t\t\t\t\tdefaultValue={[maxVerticalRotationDeg]}\n\t\t\t\t\tvalueUnit=\"°\"\n\t\t\t\t\tonValueChange={(value) => {\n\t\t\t\t\t\tsetMaxVerticalRotationDeg(value);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\n\t\t\t\t<label>segments</label>\n\t\t\t\t<Slider\n\t\t\t\t\ttitle=\"Segments\"\n\t\t\t\t\tmin={20}\n\t\t\t\t\tmax={34}\n\t\t\t\t\tstep={2}\n\t\t\t\t\tdefaultValue={[segments]}\n\t\t\t\t\tonValueChange={(value) => {\n\t\t\t\t\t\tsetSegments(value);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\n\t\t\t\t<label>drag dampening</label>\n\t\t\t\t<Slider\n\t\t\t\t\ttitle=\"Drag Dampening\"\n\t\t\t\t\tmin={0}\n\t\t\t\t\tmax={5}\n\t\t\t\t\tstep={0.2}\n\t\t\t\t\tdefaultValue={[dragDampening]}\n\t\t\t\t\tonValueChange={(value) => {\n\t\t\t\t\t\tsetDragDampening(value);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\n\t\t\t\t<label>grayscale</label>\n\t\t\t\t<Switch\n\t\t\t\t\ttitle=\"Grayscale\"\n\t\t\t\t\tchecked={grayscale}\n\t\t\t\t\tonCheckedChange={(checked) => {\n\t\t\t\t\t\tsetGrayscale(checked);\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/open-source/dome-gallery.tsx",
      "content": "import { useCallback, useEffect, useMemo, useRef } from \"react\";\r\n\r\nimport { useGesture } from \"@use-gesture/react\";\r\n\r\ntype ImageItem = string | { src: string; alt?: string };\r\n\r\ntype DomeGalleryProps = {\r\n\timages?: ImageItem[];\r\n\tfit?: number;\r\n\tfitBasis?: \"auto\" | \"min\" | \"max\" | \"width\" | \"height\";\r\n\tminRadius?: number;\r\n\tmaxRadius?: number;\r\n\tpadFactor?: number;\r\n\toverlayBlurColor?: string;\r\n\tmaxVerticalRotationDeg?: number;\r\n\tdragSensitivity?: number;\r\n\tenlargeTransitionMs?: number;\r\n\tsegments?: number;\r\n\tdragDampening?: number;\r\n\topenedImageWidth?: string;\r\n\topenedImageHeight?: string;\r\n\timageBorderRadius?: string;\r\n\topenedImageBorderRadius?: string;\r\n\tgrayscale?: boolean;\r\n};\r\n\r\ntype ItemDef = {\r\n\tsrc: string;\r\n\talt: string;\r\n\tx: number;\r\n\ty: number;\r\n\tsizeX: number;\r\n\tsizeY: number;\r\n};\r\n\r\nconst DEFAULT_IMAGES: ImageItem[] = [\r\n\t{\r\n\t\tsrc: \"https://images.unsplash.com/photo-1755331039789-7e5680e26e8f?q=80&w=774&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\",\r\n\t\talt: \"Abstract art\",\r\n\t},\r\n\t{\r\n\t\tsrc: \"https://images.unsplash.com/photo-1755569309049-98410b94f66d?q=80&w=772&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\",\r\n\t\talt: \"Modern sculpture\",\r\n\t},\r\n\t{\r\n\t\tsrc: \"https://images.unsplash.com/photo-1755497595318-7e5e3523854f?q=80&w=774&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\",\r\n\t\talt: \"Digital artwork\",\r\n\t},\r\n\t{\r\n\t\tsrc: \"https://images.unsplash.com/photo-1755353985163-c2a0fe5ac3d8?q=80&w=774&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\",\r\n\t\talt: \"Contemporary art\",\r\n\t},\r\n\t{\r\n\t\tsrc: \"https://images.unsplash.com/photo-1745965976680-d00be7dc0377?q=80&w=774&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\",\r\n\t\talt: \"Geometric pattern\",\r\n\t},\r\n\t{\r\n\t\tsrc: \"https://images.unsplash.com/photo-1752588975228-21f44630bb3c?q=80&w=774&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D\",\r\n\t\talt: \"Textured surface\",\r\n\t},\r\n\t{\r\n\t\tsrc: \"https://pbs.twimg.com/media/Gyla7NnXMAAXSo_?format=jpg&name=large\",\r\n\t\talt: \"Social media image\",\r\n\t},\r\n];\r\n\r\nconst DEFAULTS = {\r\n\tmaxVerticalRotationDeg: 5,\r\n\tdragSensitivity: 20,\r\n\tenlargeTransitionMs: 300,\r\n\tsegments: 35,\r\n};\r\n\r\nconst clamp = (v: number, min: number, max: number) =>\r\n\tMath.min(Math.max(v, min), max);\r\nconst normalizeAngle = (d: number) => ((d % 360) + 360) % 360;\r\nconst getDataNumber = (el: HTMLElement, name: string, fallback: number) => {\r\n\tconst attr = el.dataset[name] ?? el.getAttribute(`data-${name}`);\r\n\tconst n = attr == null ? NaN : parseFloat(attr);\r\n\treturn Number.isFinite(n) ? n : fallback;\r\n};\r\n\r\nfunction buildItems(pool: ImageItem[], seg: number): ItemDef[] {\r\n\tconst xCols = Array.from({ length: seg }, (_, i) => -37 + i * 2);\r\n\tconst evenYs = [-4, -2, 0, 2, 4];\r\n\tconst oddYs = [-3, -1, 1, 3, 5];\r\n\r\n\tconst coords = xCols.flatMap((x, c) => {\r\n\t\tconst ys = c % 2 === 0 ? evenYs : oddYs;\r\n\t\treturn ys.map((y) => ({ x, y, sizeX: 2, sizeY: 2 }));\r\n\t});\r\n\r\n\tconst totalSlots = coords.length;\r\n\tif (pool.length === 0) {\r\n\t\treturn coords.map((c) => ({ ...c, src: \"\", alt: \"\" }));\r\n\t}\r\n\tif (pool.length > totalSlots) {\r\n\t\tconsole.warn(\r\n\t\t\t`[DomeGallery] Provided image count (${pool.length}) exceeds available tiles (${totalSlots}). Some images will not be shown.`\r\n\t\t);\r\n\t}\r\n\r\n\tconst normalizedImages = pool.map((image) => {\r\n\t\tif (typeof image === \"string\") {\r\n\t\t\treturn { src: image, alt: \"\" };\r\n\t\t}\r\n\t\treturn { src: image.src || \"\", alt: image.alt || \"\" };\r\n\t});\r\n\r\n\tconst usedImages = Array.from(\r\n\t\t{ length: totalSlots },\r\n\t\t(_, i) => normalizedImages[i % normalizedImages.length]\r\n\t);\r\n\r\n\tfor (let i = 1; i < usedImages.length; i++) {\r\n\t\tif (usedImages[i].src === usedImages[i - 1].src) {\r\n\t\t\tfor (let j = i + 1; j < usedImages.length; j++) {\r\n\t\t\t\tif (usedImages[j].src !== usedImages[i].src) {\r\n\t\t\t\t\tconst tmp = usedImages[i];\r\n\t\t\t\t\tusedImages[i] = usedImages[j];\r\n\t\t\t\t\tusedImages[j] = tmp;\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn coords.map((c, i) => ({\r\n\t\t...c,\r\n\t\tsrc: usedImages[i].src,\r\n\t\talt: usedImages[i].alt,\r\n\t}));\r\n}\r\n\r\nfunction computeItemBaseRotation(\r\n\toffsetX: number,\r\n\toffsetY: number,\r\n\tsizeX: number,\r\n\tsizeY: number,\r\n\tsegments: number\r\n) {\r\n\tconst unit = 360 / segments / 2;\r\n\tconst rotateY = unit * (offsetX + (sizeX - 1) / 2);\r\n\tconst rotateX = unit * (offsetY - (sizeY - 1) / 2);\r\n\treturn { rotateX, rotateY };\r\n}\r\n\r\nexport default function DomeGallery({\r\n\timages = DEFAULT_IMAGES,\r\n\tfit = 0.5,\r\n\tfitBasis = \"auto\",\r\n\tminRadius = 600,\r\n\tmaxRadius = Infinity,\r\n\tpadFactor = 0.25,\r\n\toverlayBlurColor = \"#060010\",\r\n\tmaxVerticalRotationDeg = DEFAULTS.maxVerticalRotationDeg,\r\n\tdragSensitivity = DEFAULTS.dragSensitivity,\r\n\tenlargeTransitionMs = DEFAULTS.enlargeTransitionMs,\r\n\tsegments = DEFAULTS.segments,\r\n\tdragDampening = 2,\r\n\topenedImageWidth = \"400px\",\r\n\topenedImageHeight = \"400px\",\r\n\timageBorderRadius = \"30px\",\r\n\topenedImageBorderRadius = \"30px\",\r\n\tgrayscale = true,\r\n}: DomeGalleryProps) {\r\n\tconst rootRef = useRef<HTMLDivElement>(null);\r\n\tconst mainRef = useRef<HTMLDivElement>(null);\r\n\tconst sphereRef = useRef<HTMLDivElement>(null);\r\n\tconst frameRef = useRef<HTMLDivElement>(null);\r\n\tconst viewerRef = useRef<HTMLDivElement>(null);\r\n\tconst scrimRef = useRef<HTMLDivElement>(null);\r\n\tconst focusedElRef = useRef<HTMLElement | null>(null);\r\n\tconst originalTilePositionRef = useRef<{\r\n\t\tleft: number;\r\n\t\ttop: number;\r\n\t\twidth: number;\r\n\t\theight: number;\r\n\t} | null>(null);\r\n\r\n\tconst rotationRef = useRef({ x: 0, y: 0 });\r\n\tconst startRotRef = useRef({ x: 0, y: 0 });\r\n\tconst startPosRef = useRef<{ x: number; y: number } | null>(null);\r\n\tconst draggingRef = useRef(false);\r\n\tconst cancelTapRef = useRef(false);\r\n\tconst movedRef = useRef(false);\r\n\tconst inertiaRAF = useRef<number | null>(null);\r\n\tconst pointerTypeRef = useRef<\"mouse\" | \"pen\" | \"touch\">(\"mouse\");\r\n\tconst tapTargetRef = useRef<HTMLElement | null>(null);\r\n\r\n\tconst scrollLockedRef = useRef(false);\r\n\tconst lockScroll = useCallback(() => {\r\n\t\tif (scrollLockedRef.current) return;\r\n\t\tscrollLockedRef.current = true;\r\n\t\tdocument.body.classList.add(\"dg-scroll-lock\");\r\n\t}, []);\r\n\tconst unlockScroll = useCallback(() => {\r\n\t\tif (!scrollLockedRef.current) return;\r\n\t\tif (rootRef.current?.getAttribute(\"data-enlarging\") === \"true\") return; // keep locked while enlarged\r\n\t\tscrollLockedRef.current = false;\r\n\t\tdocument.body.classList.remove(\"dg-scroll-lock\");\r\n\t}, []);\r\n\r\n\tconst items = useMemo(\r\n\t\t() => buildItems(images, segments),\r\n\t\t[images, segments]\r\n\t);\r\n\r\n\tconst applyTransform = (xDeg: number, yDeg: number) => {\r\n\t\tconst el = sphereRef.current;\r\n\t\tif (el) {\r\n\t\t\tel.style.transform = `translateZ(calc(var(--radius) * -1)) rotateX(${xDeg}deg) rotateY(${yDeg}deg)`;\r\n\t\t}\r\n\t};\r\n\r\n\tconst lockedRadiusRef = useRef<number | null>(null);\r\n\r\n\tuseEffect(() => {\r\n\t\tconst root = rootRef.current;\r\n\t\tif (!root) return;\r\n\t\tconst ro = new ResizeObserver((entries) => {\r\n\t\t\tconst cr = entries[0].contentRect;\r\n\t\t\tconst w = Math.max(1, cr.width),\r\n\t\t\t\th = Math.max(1, cr.height);\r\n\t\t\tconst minDim = Math.min(w, h),\r\n\t\t\t\tmaxDim = Math.max(w, h),\r\n\t\t\t\taspect = w / h;\r\n\t\t\tlet basis: number;\r\n\t\t\tswitch (fitBasis) {\r\n\t\t\t\tcase \"min\":\r\n\t\t\t\t\tbasis = minDim;\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"max\":\r\n\t\t\t\t\tbasis = maxDim;\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"width\":\r\n\t\t\t\t\tbasis = w;\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tcase \"height\":\r\n\t\t\t\t\tbasis = h;\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tdefault:\r\n\t\t\t\t\tbasis = aspect >= 1.3 ? w : minDim;\r\n\t\t\t}\r\n\t\t\tlet radius = basis * fit;\r\n\t\t\tconst heightGuard = h * 1.35;\r\n\t\t\tradius = Math.min(radius, heightGuard);\r\n\t\t\tradius = clamp(radius, minRadius, maxRadius);\r\n\t\t\tlockedRadiusRef.current = Math.round(radius);\r\n\r\n\t\t\tconst viewerPad = Math.max(8, Math.round(minDim * padFactor));\r\n\t\t\troot.style.setProperty(\"--radius\", `${lockedRadiusRef.current}px`);\r\n\t\t\troot.style.setProperty(\"--viewer-pad\", `${viewerPad}px`);\r\n\t\t\troot.style.setProperty(\"--overlay-blur-color\", overlayBlurColor);\r\n\t\t\troot.style.setProperty(\"--tile-radius\", imageBorderRadius);\r\n\t\t\troot.style.setProperty(\"--enlarge-radius\", openedImageBorderRadius);\r\n\t\t\troot.style.setProperty(\r\n\t\t\t\t\"--image-filter\",\r\n\t\t\t\tgrayscale ? \"grayscale(1)\" : \"none\"\r\n\t\t\t);\r\n\t\t\tapplyTransform(rotationRef.current.x, rotationRef.current.y);\r\n\r\n\t\t\tconst enlargedOverlay = viewerRef.current?.querySelector(\r\n\t\t\t\t\".enlarge\"\r\n\t\t\t) as HTMLElement;\r\n\t\t\tif (enlargedOverlay && frameRef.current && mainRef.current) {\r\n\t\t\t\tconst frameR = frameRef.current.getBoundingClientRect();\r\n\t\t\t\tconst mainR = mainRef.current.getBoundingClientRect();\r\n\r\n\t\t\t\tconst hasCustomSize = openedImageWidth && openedImageHeight;\r\n\t\t\t\tif (hasCustomSize) {\r\n\t\t\t\t\tconst tempDiv = document.createElement(\"div\");\r\n\t\t\t\t\ttempDiv.style.cssText = `position: absolute; width: ${openedImageWidth}; height: ${openedImageHeight}; visibility: hidden;`;\r\n\t\t\t\t\tdocument.body.appendChild(tempDiv);\r\n\t\t\t\t\tconst tempRect = tempDiv.getBoundingClientRect();\r\n\t\t\t\t\tdocument.body.removeChild(tempDiv);\r\n\r\n\t\t\t\t\tconst centeredLeft =\r\n\t\t\t\t\t\tframeR.left -\r\n\t\t\t\t\t\tmainR.left +\r\n\t\t\t\t\t\t(frameR.width - tempRect.width) / 2;\r\n\t\t\t\t\tconst centeredTop =\r\n\t\t\t\t\t\tframeR.top -\r\n\t\t\t\t\t\tmainR.top +\r\n\t\t\t\t\t\t(frameR.height - tempRect.height) / 2;\r\n\r\n\t\t\t\t\tenlargedOverlay.style.left = `${centeredLeft}px`;\r\n\t\t\t\t\tenlargedOverlay.style.top = `${centeredTop}px`;\r\n\t\t\t\t} else {\r\n\t\t\t\t\tenlargedOverlay.style.left = `${frameR.left - mainR.left}px`;\r\n\t\t\t\t\tenlargedOverlay.style.top = `${frameR.top - mainR.top}px`;\r\n\t\t\t\t\tenlargedOverlay.style.width = `${frameR.width}px`;\r\n\t\t\t\t\tenlargedOverlay.style.height = `${frameR.height}px`;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\t\tro.observe(root);\r\n\t\treturn () => ro.disconnect();\r\n\t}, [\r\n\t\tfit,\r\n\t\tfitBasis,\r\n\t\tminRadius,\r\n\t\tmaxRadius,\r\n\t\tpadFactor,\r\n\t\toverlayBlurColor,\r\n\t\tgrayscale,\r\n\t\timageBorderRadius,\r\n\t\topenedImageBorderRadius,\r\n\t\topenedImageWidth,\r\n\t\topenedImageHeight,\r\n\t]);\r\n\r\n\tuseEffect(() => {\r\n\t\tapplyTransform(rotationRef.current.x, rotationRef.current.y);\r\n\t}, []);\r\n\r\n\tconst stopInertia = useCallback(() => {\r\n\t\tif (inertiaRAF.current) {\r\n\t\t\tcancelAnimationFrame(inertiaRAF.current);\r\n\t\t\tinertiaRAF.current = null;\r\n\t\t}\r\n\t}, []);\r\n\r\n\tconst startInertia = useCallback(\r\n\t\t(vx: number, vy: number) => {\r\n\t\t\tlet vX = vx * 100;\r\n\t\t\tlet vY = vy * 100;\r\n\t\t\tlet frames = 0;\r\n\t\t\tconst d = clamp(dragDampening ?? 0.6, 0, 1);\r\n\t\t\tconst frictionMul = 0.94 + 0.055 * d;\r\n\t\t\tconst stopThreshold = 0.015 - 0.01 * d;\r\n\t\t\tconst maxFrames = Math.round(90 + 270 * d);\r\n\t\t\tconst step = () => {\r\n\t\t\t\tvX *= frictionMul;\r\n\t\t\t\tvY *= frictionMul;\r\n\t\t\t\tif (Math.abs(vX) < stopThreshold && Math.abs(vY) < stopThreshold) {\r\n\t\t\t\t\tinertiaRAF.current = null;\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\tif (++frames > maxFrames) {\r\n\t\t\t\t\tinertiaRAF.current = null;\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\tconst nextX = clamp(\r\n\t\t\t\t\trotationRef.current.x - vY / 200,\r\n\t\t\t\t\t-maxVerticalRotationDeg,\r\n\t\t\t\t\tmaxVerticalRotationDeg\r\n\t\t\t\t);\r\n\t\t\t\tconst nextY = rotationRef.current.y + vX / 200;\r\n\t\t\t\trotationRef.current = { x: nextX, y: nextY };\r\n\t\t\t\tapplyTransform(nextX, nextY);\r\n\t\t\t\tinertiaRAF.current = requestAnimationFrame(step);\r\n\t\t\t};\r\n\t\t\tstopInertia();\r\n\t\t\tinertiaRAF.current = requestAnimationFrame(step);\r\n\t\t},\r\n\t\t[dragDampening, maxVerticalRotationDeg, stopInertia]\r\n\t);\r\n\r\n\tuseGesture(\r\n\t\t{\r\n\t\t\tonDragStart: ({ event }) => {\r\n\t\t\t\tif (focusedElRef.current) return;\r\n\t\t\t\tstopInertia();\r\n\r\n\t\t\t\tconst evt = event as PointerEvent;\r\n\t\t\t\tpointerTypeRef.current = (evt.pointerType as any) || \"mouse\";\r\n\t\t\t\tif (pointerTypeRef.current === \"touch\") evt.preventDefault();\r\n\t\t\t\tif (pointerTypeRef.current === \"touch\") lockScroll();\r\n\t\t\t\tdraggingRef.current = true;\r\n\t\t\t\tcancelTapRef.current = false;\r\n\t\t\t\tmovedRef.current = false;\r\n\t\t\t\tstartRotRef.current = { ...rotationRef.current };\r\n\t\t\t\tstartPosRef.current = { x: evt.clientX, y: evt.clientY };\r\n\t\t\t\tconst potential = (evt.target as Element).closest?.(\r\n\t\t\t\t\t\".item__image\"\r\n\t\t\t\t) as HTMLElement | null;\r\n\t\t\t\ttapTargetRef.current = potential || null;\r\n\t\t\t},\r\n\t\t\tonDrag: ({\r\n\t\t\t\tevent,\r\n\t\t\t\tlast,\r\n\t\t\t\tvelocity: velArr = [0, 0],\r\n\t\t\t\tdirection: dirArr = [0, 0],\r\n\t\t\t\tmovement,\r\n\t\t\t}) => {\r\n\t\t\t\tif (\r\n\t\t\t\t\tfocusedElRef.current ||\r\n\t\t\t\t\t!draggingRef.current ||\r\n\t\t\t\t\t!startPosRef.current\r\n\t\t\t\t)\r\n\t\t\t\t\treturn;\r\n\r\n\t\t\t\tconst evt = event as PointerEvent;\r\n\t\t\t\tif (pointerTypeRef.current === \"touch\") evt.preventDefault();\r\n\r\n\t\t\t\tconst dxTotal = evt.clientX - startPosRef.current.x;\r\n\t\t\t\tconst dyTotal = evt.clientY - startPosRef.current.y;\r\n\r\n\t\t\t\tif (!movedRef.current) {\r\n\t\t\t\t\tconst dist2 = dxTotal * dxTotal + dyTotal * dyTotal;\r\n\t\t\t\t\tif (dist2 > 16) movedRef.current = true;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tconst nextX = clamp(\r\n\t\t\t\t\tstartRotRef.current.x - dyTotal / dragSensitivity,\r\n\t\t\t\t\t-maxVerticalRotationDeg,\r\n\t\t\t\t\tmaxVerticalRotationDeg\r\n\t\t\t\t);\r\n\t\t\t\tconst nextY = startRotRef.current.y + dxTotal / dragSensitivity;\r\n\r\n\t\t\t\tconst cur = rotationRef.current;\r\n\t\t\t\tif (cur.x !== nextX || cur.y !== nextY) {\r\n\t\t\t\t\trotationRef.current = { x: nextX, y: nextY };\r\n\t\t\t\t\tapplyTransform(nextX, nextY);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (last) {\r\n\t\t\t\t\tdraggingRef.current = false;\r\n\t\t\t\t\tlet isTap = false;\r\n\r\n\t\t\t\t\tif (startPosRef.current) {\r\n\t\t\t\t\t\tconst dx = evt.clientX - startPosRef.current.x;\r\n\t\t\t\t\t\tconst dy = evt.clientY - startPosRef.current.y;\r\n\t\t\t\t\t\tconst dist2 = dx * dx + dy * dy;\r\n\t\t\t\t\t\tconst TAP_THRESH_PX =\r\n\t\t\t\t\t\t\tpointerTypeRef.current === \"touch\" ? 10 : 6;\r\n\t\t\t\t\t\tif (dist2 <= TAP_THRESH_PX * TAP_THRESH_PX) {\r\n\t\t\t\t\t\t\tisTap = true;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tlet [vMagX, vMagY] = velArr;\r\n\t\t\t\t\tconst [dirX, dirY] = dirArr;\r\n\t\t\t\t\tlet vx = vMagX * dirX;\r\n\t\t\t\t\tlet vy = vMagY * dirY;\r\n\r\n\t\t\t\t\tif (\r\n\t\t\t\t\t\t!isTap &&\r\n\t\t\t\t\t\tMath.abs(vx) < 0.001 &&\r\n\t\t\t\t\t\tMath.abs(vy) < 0.001 &&\r\n\t\t\t\t\t\tArray.isArray(movement)\r\n\t\t\t\t\t) {\r\n\t\t\t\t\t\tconst [mx, my] = movement;\r\n\t\t\t\t\t\tvx = (mx / dragSensitivity) * 0.02;\r\n\t\t\t\t\t\tvy = (my / dragSensitivity) * 0.02;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (!isTap && (Math.abs(vx) > 0.005 || Math.abs(vy) > 0.005)) {\r\n\t\t\t\t\t\tstartInertia(vx, vy);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tstartPosRef.current = null;\r\n\t\t\t\t\tcancelTapRef.current = !isTap;\r\n\r\n\t\t\t\t\tif (isTap && tapTargetRef.current && !focusedElRef.current) {\r\n\t\t\t\t\t\topenItemFromElement(tapTargetRef.current);\r\n\t\t\t\t\t}\r\n\t\t\t\t\ttapTargetRef.current = null;\r\n\r\n\t\t\t\t\tif (cancelTapRef.current)\r\n\t\t\t\t\t\tsetTimeout(() => (cancelTapRef.current = false), 120);\r\n\t\t\t\t\tif (pointerTypeRef.current === \"touch\") unlockScroll();\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t},\r\n\t\t{ target: mainRef, eventOptions: { passive: false } }\r\n\t);\r\n\r\n\tuseEffect(() => {\r\n\t\tconst scrim = scrimRef.current;\r\n\t\tif (!scrim) return;\r\n\r\n\t\tconst close = () => {\r\n\t\t\tconst el = focusedElRef.current;\r\n\t\t\tif (!el) return;\r\n\t\t\tconst parent = el.parentElement as HTMLElement;\r\n\t\t\tconst overlay = viewerRef.current?.querySelector(\r\n\t\t\t\t\".enlarge\"\r\n\t\t\t) as HTMLElement | null;\r\n\t\t\tif (!overlay) return;\r\n\r\n\t\t\tconst refDiv = parent.querySelector(\r\n\t\t\t\t\".item__image--reference\"\r\n\t\t\t) as HTMLElement | null;\r\n\r\n\t\t\tconst originalPos = originalTilePositionRef.current;\r\n\t\t\tif (!originalPos) {\r\n\t\t\t\toverlay.remove();\r\n\t\t\t\tif (refDiv) refDiv.remove();\r\n\t\t\t\tparent.style.setProperty(\"--rot-y-delta\", `0deg`);\r\n\t\t\t\tparent.style.setProperty(\"--rot-x-delta\", `0deg`);\r\n\t\t\t\tel.style.visibility = \"\";\r\n\t\t\t\t(el.style as any).zIndex = 0;\r\n\t\t\t\tfocusedElRef.current = null;\r\n\t\t\t\trootRef.current?.removeAttribute(\"data-enlarging\");\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tconst currentRect = overlay.getBoundingClientRect();\r\n\t\t\tconst rootRect = rootRef.current!.getBoundingClientRect();\r\n\r\n\t\t\tconst originalPosRelativeToRoot = {\r\n\t\t\t\tleft: originalPos.left - rootRect.left,\r\n\t\t\t\ttop: originalPos.top - rootRect.top,\r\n\t\t\t\twidth: originalPos.width,\r\n\t\t\t\theight: originalPos.height,\r\n\t\t\t};\r\n\r\n\t\t\tconst overlayRelativeToRoot = {\r\n\t\t\t\tleft: currentRect.left - rootRect.left,\r\n\t\t\t\ttop: currentRect.top - rootRect.top,\r\n\t\t\t\twidth: currentRect.width,\r\n\t\t\t\theight: currentRect.height,\r\n\t\t\t};\r\n\r\n\t\t\tconst animatingOverlay = document.createElement(\"div\");\r\n\t\t\tanimatingOverlay.className = \"enlarge-closing\";\r\n\t\t\tanimatingOverlay.style.cssText = `\r\n        position: absolute;\r\n        left: ${overlayRelativeToRoot.left}px;\r\n        top: ${overlayRelativeToRoot.top}px;\r\n        width: ${overlayRelativeToRoot.width}px;\r\n        height: ${overlayRelativeToRoot.height}px;\r\n        z-index: 9999;\r\n        border-radius: ${openedImageBorderRadius};\r\n        overflow: hidden;\r\n        box-shadow: 0 10px 30px rgba(0,0,0,.35);\r\n        transition: all ${enlargeTransitionMs}ms ease-out;\r\n        pointer-events: none;\r\n        margin: 0;\r\n        transform: none;\r\n        filter: ${grayscale ? \"grayscale(1)\" : \"none\"};\r\n      `;\r\n\r\n\t\t\tconst originalImg = overlay.querySelector(\"img\");\r\n\t\t\tif (originalImg) {\r\n\t\t\t\tconst img = originalImg.cloneNode() as HTMLImageElement;\r\n\t\t\t\timg.style.cssText = \"width: 100%; height: 100%; object-fit: cover;\";\r\n\t\t\t\tanimatingOverlay.appendChild(img);\r\n\t\t\t}\r\n\r\n\t\t\toverlay.remove();\r\n\t\t\trootRef.current!.appendChild(animatingOverlay);\r\n\r\n\t\t\tvoid animatingOverlay.getBoundingClientRect();\r\n\r\n\t\t\trequestAnimationFrame(() => {\r\n\t\t\t\tanimatingOverlay.style.left = originalPosRelativeToRoot.left + \"px\";\r\n\t\t\t\tanimatingOverlay.style.top = originalPosRelativeToRoot.top + \"px\";\r\n\t\t\t\tanimatingOverlay.style.width =\r\n\t\t\t\t\toriginalPosRelativeToRoot.width + \"px\";\r\n\t\t\t\tanimatingOverlay.style.height =\r\n\t\t\t\t\toriginalPosRelativeToRoot.height + \"px\";\r\n\t\t\t\tanimatingOverlay.style.opacity = \"0\";\r\n\t\t\t});\r\n\r\n\t\t\tconst cleanup = () => {\r\n\t\t\t\tanimatingOverlay.remove();\r\n\t\t\t\toriginalTilePositionRef.current = null;\r\n\r\n\t\t\t\tif (refDiv) refDiv.remove();\r\n\t\t\t\tparent.style.transition = \"none\";\r\n\t\t\t\tel.style.transition = \"none\";\r\n\r\n\t\t\t\tparent.style.setProperty(\"--rot-y-delta\", `0deg`);\r\n\t\t\t\tparent.style.setProperty(\"--rot-x-delta\", `0deg`);\r\n\r\n\t\t\t\trequestAnimationFrame(() => {\r\n\t\t\t\t\tel.style.visibility = \"\";\r\n\t\t\t\t\tel.style.opacity = \"0\";\r\n\t\t\t\t\t(el.style as any).zIndex = 0;\r\n\t\t\t\t\tfocusedElRef.current = null;\r\n\t\t\t\t\trootRef.current?.removeAttribute(\"data-enlarging\");\r\n\r\n\t\t\t\t\trequestAnimationFrame(() => {\r\n\t\t\t\t\t\tparent.style.transition = \"\";\r\n\t\t\t\t\t\tel.style.transition = \"opacity 300ms ease-out\";\r\n\r\n\t\t\t\t\t\trequestAnimationFrame(() => {\r\n\t\t\t\t\t\t\tel.style.opacity = \"1\";\r\n\t\t\t\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\t\t\t\tel.style.transition = \"\";\r\n\t\t\t\t\t\t\t\tel.style.opacity = \"\";\r\n\t\t\t\t\t\t\t\tif (\r\n\t\t\t\t\t\t\t\t\t!draggingRef.current &&\r\n\t\t\t\t\t\t\t\t\trootRef.current?.getAttribute(\"data-enlarging\") !==\r\n\t\t\t\t\t\t\t\t\t\t\"true\"\r\n\t\t\t\t\t\t\t\t) {\r\n\t\t\t\t\t\t\t\t\tdocument.body.classList.remove(\"dg-scroll-lock\");\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}, 300);\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t});\r\n\t\t\t\t});\r\n\t\t\t};\r\n\r\n\t\t\tanimatingOverlay.addEventListener(\"transitionend\", cleanup, {\r\n\t\t\t\tonce: true,\r\n\t\t\t});\r\n\t\t};\r\n\r\n\t\tscrim.addEventListener(\"click\", close);\r\n\t\tconst onKey = (e: KeyboardEvent) => {\r\n\t\t\tif (e.key === \"Escape\") close();\r\n\t\t};\r\n\t\twindow.addEventListener(\"keydown\", onKey);\r\n\r\n\t\treturn () => {\r\n\t\t\tscrim.removeEventListener(\"click\", close);\r\n\t\t\twindow.removeEventListener(\"keydown\", onKey);\r\n\t\t};\r\n\t}, [enlargeTransitionMs, openedImageBorderRadius, grayscale]);\r\n\r\n\tconst openItemFromElement = (el: HTMLElement) => {\r\n\t\tif (cancelTapRef.current) return;\r\n\t\tlockScroll();\r\n\t\tconst parent = el.parentElement as HTMLElement;\r\n\t\tfocusedElRef.current = el;\r\n\t\tel.setAttribute(\"data-focused\", \"true\");\r\n\t\tconst offsetX = getDataNumber(parent, \"offsetX\", 0);\r\n\t\tconst offsetY = getDataNumber(parent, \"offsetY\", 0);\r\n\t\tconst sizeX = getDataNumber(parent, \"sizeX\", 2);\r\n\t\tconst sizeY = getDataNumber(parent, \"sizeY\", 2);\r\n\t\tconst parentRot = computeItemBaseRotation(\r\n\t\t\toffsetX,\r\n\t\t\toffsetY,\r\n\t\t\tsizeX,\r\n\t\t\tsizeY,\r\n\t\t\tsegments\r\n\t\t);\r\n\t\tconst parentY = normalizeAngle(parentRot.rotateY);\r\n\t\tconst globalY = normalizeAngle(rotationRef.current.y);\r\n\t\tlet rotY = -(parentY + globalY) % 360;\r\n\t\tif (rotY < -180) rotY += 360;\r\n\t\tconst rotX = -parentRot.rotateX - rotationRef.current.x;\r\n\t\tparent.style.setProperty(\"--rot-y-delta\", `${rotY}deg`);\r\n\t\tparent.style.setProperty(\"--rot-x-delta\", `${rotX}deg`);\r\n\t\tconst refDiv = document.createElement(\"div\");\r\n\t\trefDiv.className = \"item__image item__image--reference opacity-0\";\r\n\t\trefDiv.style.transform = `rotateX(${-parentRot.rotateX}deg) rotateY(${-parentRot.rotateY}deg)`;\r\n\t\tparent.appendChild(refDiv);\r\n\t\tconst tileR = refDiv.getBoundingClientRect();\r\n\t\tconst mainR = mainRef.current!.getBoundingClientRect();\r\n\t\tconst frameR = frameRef.current!.getBoundingClientRect();\r\n\t\toriginalTilePositionRef.current = {\r\n\t\t\tleft: tileR.left,\r\n\t\t\ttop: tileR.top,\r\n\t\t\twidth: tileR.width,\r\n\t\t\theight: tileR.height,\r\n\t\t};\r\n\t\tel.style.visibility = \"hidden\";\r\n\t\t(el.style as any).zIndex = 0;\r\n\t\tconst overlay = document.createElement(\"div\");\r\n\t\toverlay.className = \"enlarge\";\r\n\t\toverlay.style.cssText = `position:absolute; left:${frameR.left - mainR.left}px; top:${frameR.top - mainR.top}px; width:${frameR.width}px; height:${frameR.height}px; opacity:0; z-index:30; will-change:transform,opacity; transform-origin:top left; transition:transform ${enlargeTransitionMs}ms ease, opacity ${enlargeTransitionMs}ms ease; border-radius:${openedImageBorderRadius}; overflow:hidden; box-shadow:0 10px 30px rgba(0,0,0,.35);`;\r\n\t\tconst rawSrc =\r\n\t\t\tparent.dataset.src ||\r\n\t\t\t(el.querySelector(\"img\") as HTMLImageElement)?.src ||\r\n\t\t\t\"\";\r\n\t\tconst rawAlt =\r\n\t\t\tparent.dataset.alt ||\r\n\t\t\t(el.querySelector(\"img\") as HTMLImageElement)?.alt ||\r\n\t\t\t\"\";\r\n\t\tconst img = document.createElement(\"img\");\r\n\t\timg.src = rawSrc;\r\n\t\timg.alt = rawAlt;\r\n\t\timg.style.cssText = `width:100%; height:100%; object-fit:cover; filter:${grayscale ? \"grayscale(1)\" : \"none\"};`;\r\n\t\toverlay.appendChild(img);\r\n\t\tviewerRef.current!.appendChild(overlay);\r\n\t\tconst tx0 = tileR.left - frameR.left;\r\n\t\tconst ty0 = tileR.top - frameR.top;\r\n\t\tconst sx0 = tileR.width / frameR.width;\r\n\t\tconst sy0 = tileR.height / frameR.height;\r\n\t\toverlay.style.transform = `translate(${tx0}px, ${ty0}px) scale(${sx0}, ${sy0})`;\r\n\t\trequestAnimationFrame(() => {\r\n\t\t\toverlay.style.opacity = \"1\";\r\n\t\t\toverlay.style.transform = \"translate(0px, 0px) scale(1, 1)\";\r\n\t\t\trootRef.current?.setAttribute(\"data-enlarging\", \"true\");\r\n\t\t});\r\n\t\tconst wantsResize = openedImageWidth || openedImageHeight;\r\n\t\tif (wantsResize) {\r\n\t\t\tconst onFirstEnd = (ev: TransitionEvent) => {\r\n\t\t\t\tif (ev.propertyName !== \"transform\") return;\r\n\t\t\t\toverlay.removeEventListener(\"transitionend\", onFirstEnd);\r\n\t\t\t\tconst prevTransition = overlay.style.transition;\r\n\t\t\t\toverlay.style.transition = \"none\";\r\n\t\t\t\tconst tempWidth = openedImageWidth || `${frameR.width}px`;\r\n\t\t\t\tconst tempHeight = openedImageHeight || `${frameR.height}px`;\r\n\t\t\t\toverlay.style.width = tempWidth;\r\n\t\t\t\toverlay.style.height = tempHeight;\r\n\t\t\t\tconst newRect = overlay.getBoundingClientRect();\r\n\t\t\t\toverlay.style.width = frameR.width + \"px\";\r\n\t\t\t\toverlay.style.height = frameR.height + \"px\";\r\n\t\t\t\tvoid overlay.offsetWidth;\r\n\t\t\t\toverlay.style.transition = `left ${enlargeTransitionMs}ms ease, top ${enlargeTransitionMs}ms ease, width ${enlargeTransitionMs}ms ease, height ${enlargeTransitionMs}ms ease`;\r\n\t\t\t\tconst centeredLeft =\r\n\t\t\t\t\tframeR.left - mainR.left + (frameR.width - newRect.width) / 2;\r\n\t\t\t\tconst centeredTop =\r\n\t\t\t\t\tframeR.top - mainR.top + (frameR.height - newRect.height) / 2;\r\n\t\t\t\trequestAnimationFrame(() => {\r\n\t\t\t\t\toverlay.style.left = `${centeredLeft}px`;\r\n\t\t\t\t\toverlay.style.top = `${centeredTop}px`;\r\n\t\t\t\t\toverlay.style.width = tempWidth;\r\n\t\t\t\t\toverlay.style.height = tempHeight;\r\n\t\t\t\t});\r\n\t\t\t\tconst cleanupSecond = () => {\r\n\t\t\t\t\toverlay.removeEventListener(\"transitionend\", cleanupSecond);\r\n\t\t\t\t\toverlay.style.transition = prevTransition;\r\n\t\t\t\t};\r\n\t\t\t\toverlay.addEventListener(\"transitionend\", cleanupSecond, {\r\n\t\t\t\t\tonce: true,\r\n\t\t\t\t});\r\n\t\t\t};\r\n\t\t\toverlay.addEventListener(\"transitionend\", onFirstEnd);\r\n\t\t}\r\n\t};\r\n\r\n\tuseEffect(() => {\r\n\t\treturn () => {\r\n\t\t\tdocument.body.classList.remove(\"dg-scroll-lock\");\r\n\t\t};\r\n\t}, []);\r\n\r\n\tconst cssStyles = `\r\n    .sphere-root {\r\n      --radius: 520px;\r\n      --viewer-pad: 72px;\r\n      --circ: calc(var(--radius) * 3.14);\r\n      --rot-y: calc((360deg / var(--segments-x)) / 2);\r\n      --rot-x: calc((360deg / var(--segments-y)) / 2);\r\n      --item-width: calc(var(--circ) / var(--segments-x));\r\n      --item-height: calc(var(--circ) / var(--segments-y));\r\n    }\r\n    \r\n    .sphere-root * {\r\n      box-sizing: border-box;\r\n      transform-style: preserve-3d;\r\n    }\r\n    \r\n    .stage {\r\n      width: 100%;\r\n      height: 100%;\r\n      display: grid;\r\n      place-items: center;\r\n      position: absolute;\r\n      inset: 0;\r\n      margin: auto;\r\n      perspective: calc(var(--radius) * 2);\r\n      perspective-origin: 50% 50%;\r\n    }\r\n    \r\n    .sphere {\r\n      transform: translateZ(calc(var(--radius) * -1));\r\n      will-change: transform;\r\n      position: absolute;\r\n    }\r\n    \r\n    .sphere-item {\r\n      width: calc(var(--item-width) * var(--item-size-x));\r\n      height: calc(var(--item-height) * var(--item-size-y));\r\n      position: absolute;\r\n      top: -999px;\r\n      bottom: -999px;\r\n      left: -999px;\r\n      right: -999px;\r\n      margin: auto;\r\n      transform-origin: 50% 50%;\r\n      backface-visibility: hidden;\r\n      transition: transform 300ms;\r\n      transform: rotateY(calc(var(--rot-y) * (var(--offset-x) + ((var(--item-size-x) - 1) / 2)) + var(--rot-y-delta, 0deg))) \r\n                 rotateX(calc(var(--rot-x) * (var(--offset-y) - ((var(--item-size-y) - 1) / 2)) + var(--rot-x-delta, 0deg))) \r\n                 translateZ(var(--radius));\r\n    }\r\n    \r\n    .sphere-root[data-enlarging=\"true\"] .scrim {\r\n      opacity: 1 !important;\r\n      pointer-events: all !important;\r\n    }\r\n    \r\n    @media (max-aspect-ratio: 1/1) {\r\n      .viewer-frame {\r\n        height: auto !important;\r\n        width: 100% !important;\r\n      }\r\n    }\r\n    \r\n    // body.dg-scroll-lock {\r\n    //   position: fixed !important;\r\n    //   top: 0;\r\n    //   left: 0;\r\n    //   width: 100% !important;\r\n    //   height: 100% !important;\r\n    //   overflow: hidden !important;\r\n    //   touch-action: none !important;\r\n    //   overscroll-behavior: contain !important;\r\n    // }\r\n    .item__image {\r\n      position: absolute;\r\n      inset: 10px;\r\n      border-radius: var(--tile-radius, 12px);\r\n      overflow: hidden;\r\n      cursor: pointer;\r\n      backface-visibility: hidden;\r\n      -webkit-backface-visibility: hidden;\r\n      transition: transform 300ms;\r\n    }\r\n    .item__image--reference {\r\n      position: absolute;\r\n      inset: 10px;\r\n      pointer-events: none;\r\n    }\r\n  `;\r\n\r\n\treturn (\r\n\t\t<>\r\n\t\t\t<style dangerouslySetInnerHTML={{ __html: cssStyles }} />\r\n\t\t\t<div\r\n\t\t\t\tref={rootRef}\r\n\t\t\t\tclassName=\"sphere-root relative w-full h-full\"\r\n\t\t\t\tstyle={\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t[\"--segments-x\" as any]: segments,\r\n\t\t\t\t\t\t[\"--segments-y\" as any]: segments,\r\n\t\t\t\t\t\t[\"--overlay-blur-color\" as any]: overlayBlurColor,\r\n\t\t\t\t\t\t[\"--tile-radius\" as any]: imageBorderRadius,\r\n\t\t\t\t\t\t[\"--enlarge-radius\" as any]: openedImageBorderRadius,\r\n\t\t\t\t\t\t[\"--image-filter\" as any]: grayscale\r\n\t\t\t\t\t\t\t? \"grayscale(1)\"\r\n\t\t\t\t\t\t\t: \"none\",\r\n\t\t\t\t\t} as React.CSSProperties\r\n\t\t\t\t}\r\n\t\t\t>\r\n\t\t\t\t<main\r\n\t\t\t\t\tref={mainRef}\r\n\t\t\t\t\tclassName=\"absolute inset-0 grid place-items-center overflow-hidden select-none bg-transparent\"\r\n\t\t\t\t\tstyle={{\r\n\t\t\t\t\t\ttouchAction: \"none\",\r\n\t\t\t\t\t\tWebkitUserSelect: \"none\",\r\n\t\t\t\t\t}}\r\n\t\t\t\t>\r\n\t\t\t\t\t<div className=\"stage\">\r\n\t\t\t\t\t\t<div ref={sphereRef} className=\"sphere\">\r\n\t\t\t\t\t\t\t{items.map((it, i) => (\r\n\t\t\t\t\t\t\t\t<div\r\n\t\t\t\t\t\t\t\t\tkey={`${it.x},${it.y},${i}`}\r\n\t\t\t\t\t\t\t\t\tclassName=\"sphere-item absolute m-auto\"\r\n\t\t\t\t\t\t\t\t\tdata-src={it.src}\r\n\t\t\t\t\t\t\t\t\tdata-alt={it.alt}\r\n\t\t\t\t\t\t\t\t\tdata-offset-x={it.x}\r\n\t\t\t\t\t\t\t\t\tdata-offset-y={it.y}\r\n\t\t\t\t\t\t\t\t\tdata-size-x={it.sizeX}\r\n\t\t\t\t\t\t\t\t\tdata-size-y={it.sizeY}\r\n\t\t\t\t\t\t\t\t\tstyle={\r\n\t\t\t\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\t\t\t\t[\"--offset-x\" as any]: it.x,\r\n\t\t\t\t\t\t\t\t\t\t\t[\"--offset-y\" as any]: it.y,\r\n\t\t\t\t\t\t\t\t\t\t\t[\"--item-size-x\" as any]: it.sizeX,\r\n\t\t\t\t\t\t\t\t\t\t\t[\"--item-size-y\" as any]: it.sizeY,\r\n\t\t\t\t\t\t\t\t\t\t\ttop: \"-999px\",\r\n\t\t\t\t\t\t\t\t\t\t\tbottom: \"-999px\",\r\n\t\t\t\t\t\t\t\t\t\t\tleft: \"-999px\",\r\n\t\t\t\t\t\t\t\t\t\t\tright: \"-999px\",\r\n\t\t\t\t\t\t\t\t\t\t} as React.CSSProperties\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t\t<div\r\n\t\t\t\t\t\t\t\t\t\tclassName=\"item__image absolute block overflow-hidden cursor-pointer bg-gray-200 transition-transform duration-300\"\r\n\t\t\t\t\t\t\t\t\t\tstyle={{\r\n\t\t\t\t\t\t\t\t\t\t\tinset: \"10px\",\r\n\t\t\t\t\t\t\t\t\t\t\tborderRadius: `var(--tile-radius, ${imageBorderRadius})`,\r\n\t\t\t\t\t\t\t\t\t\t\tbackfaceVisibility: \"hidden\",\r\n\t\t\t\t\t\t\t\t\t\t}}\r\n\t\t\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t\t\t<img\r\n\t\t\t\t\t\t\t\t\t\t\tsrc={it.src}\r\n\t\t\t\t\t\t\t\t\t\t\tdraggable={false}\r\n\t\t\t\t\t\t\t\t\t\t\talt={it.alt}\r\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"w-full h-full object-cover pointer-events-none\"\r\n\t\t\t\t\t\t\t\t\t\t\tstyle={{\r\n\t\t\t\t\t\t\t\t\t\t\t\tbackfaceVisibility: \"hidden\",\r\n\t\t\t\t\t\t\t\t\t\t\t\tfilter: `var(--image-filter, ${grayscale ? \"grayscale(1)\" : \"none\"})`,\r\n\t\t\t\t\t\t\t\t\t\t\t}}\r\n\t\t\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t\t))}\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\r\n\t\t\t\t\t<div\r\n\t\t\t\t\t\tclassName=\"absolute inset-0 m-auto z-3 pointer-events-none\"\r\n\t\t\t\t\t\tstyle={{\r\n\t\t\t\t\t\t\tbackgroundImage: `radial-gradient(rgba(235, 235, 235, 0) 65%, var(--overlay-blur-color, ${overlayBlurColor}) 100%)`,\r\n\t\t\t\t\t\t}}\r\n\t\t\t\t\t/>\r\n\r\n\t\t\t\t\t<div\r\n\t\t\t\t\t\tclassName=\"absolute inset-0 m-auto z-3 pointer-events-none\"\r\n\t\t\t\t\t\tstyle={{\r\n\t\t\t\t\t\t\tWebkitMaskImage: `radial-gradient(rgba(235, 235, 235, 0) 70%, var(--overlay-blur-color, ${overlayBlurColor}) 90%)`,\r\n\t\t\t\t\t\t\tmaskImage: `radial-gradient(rgba(235, 235, 235, 0) 70%, var(--overlay-blur-color, ${overlayBlurColor}) 90%)`,\r\n\t\t\t\t\t\t\tbackdropFilter: \"blur(3px)\",\r\n\t\t\t\t\t\t}}\r\n\t\t\t\t\t/>\r\n\r\n\t\t\t\t\t<div\r\n\t\t\t\t\t\tclassName=\"absolute left-0 right-0 top-0 h-[120px] z-5 pointer-events-none rotate-180\"\r\n\t\t\t\t\t\tstyle={{\r\n\t\t\t\t\t\t\tbackground: `linear-gradient(to bottom, transparent, var(--overlay-blur-color, ${overlayBlurColor}))`,\r\n\t\t\t\t\t\t}}\r\n\t\t\t\t\t/>\r\n\t\t\t\t\t<div\r\n\t\t\t\t\t\tclassName=\"absolute left-0 right-0 bottom-0 h-[120px] z-5 pointer-events-none\"\r\n\t\t\t\t\t\tstyle={{\r\n\t\t\t\t\t\t\tbackground: `linear-gradient(to bottom, transparent, var(--overlay-blur-color, ${overlayBlurColor}))`,\r\n\t\t\t\t\t\t}}\r\n\t\t\t\t\t/>\r\n\r\n\t\t\t\t\t<div\r\n\t\t\t\t\t\tref={viewerRef}\r\n\t\t\t\t\t\tclassName=\"absolute inset-0 z-20 pointer-events-none flex items-center justify-center\"\r\n\t\t\t\t\t\tstyle={{ padding: \"var(--viewer-pad)\" }}\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t<div\r\n\t\t\t\t\t\t\tref={scrimRef}\r\n\t\t\t\t\t\t\tclassName=\"scrim absolute inset-0 z-10 pointer-events-none opacity-0 transition-opacity duration-500\"\r\n\t\t\t\t\t\t\tstyle={{\r\n\t\t\t\t\t\t\t\tbackground: \"rgba(0, 0, 0, 0.4)\",\r\n\t\t\t\t\t\t\t\tbackdropFilter: \"blur(3px)\",\r\n\t\t\t\t\t\t\t}}\r\n\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t<div\r\n\t\t\t\t\t\t\tref={frameRef}\r\n\t\t\t\t\t\t\tclassName=\"viewer-frame h-full aspect-square flex\"\r\n\t\t\t\t\t\t\tstyle={{\r\n\t\t\t\t\t\t\t\tborderRadius: `var(--enlarge-radius, ${openedImageBorderRadius})`,\r\n\t\t\t\t\t\t\t}}\r\n\t\t\t\t\t\t/>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</main>\r\n\t\t\t</div>\r\n\t\t</>\r\n\t);\r\n}\r\n",
      "type": "registry:ui"
    },
    {
      "path": "components/ui/slider.tsx",
      "content": "\"use client\";\r\n\r\nimport React from \"react\";\r\n\r\nimport { cn } from \"@/registry/utilities/cn\";\r\nimport * as SliderPrimitive from \"@radix-ui/react-slider\";\r\n\r\nconst Slider = React.forwardRef<\r\n\tReact.ElementRef<typeof SliderPrimitive.Root>,\r\n\tReact.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>\r\n>(({ className, ...props }, ref) => (\r\n\t<SliderPrimitive.Root\r\n\t\tref={ref}\r\n\t\tclassName={cn(\r\n\t\t\t\"relative flex w-full touch-none select-none items-center\",\r\n\t\t\tclassName\r\n\t\t)}\r\n\t\t{...props}\r\n\t>\r\n\t\t<SliderPrimitive.Track className=\"relative h-2 w-full grow overflow-hidden rounded-full bg-secondary\">\r\n\t\t\t<SliderPrimitive.Range className=\"absolute h-full bg-primary\" />\r\n\t\t</SliderPrimitive.Track>\r\n\t\t<SliderPrimitive.Thumb className=\"block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\" />\r\n\t\t<SliderPrimitive.Thumb className=\"block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50\" />\r\n\t</SliderPrimitive.Root>\r\n));\r\nSlider.displayName = SliderPrimitive.Root.displayName;\r\n\r\nexport { Slider };\r\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/utilities/cn.ts",
      "content": "import { ClassValue, clsx } from \"clsx\";\r\nimport { twMerge } from \"tailwind-merge\";\r\n\r\nexport function cn(...inputs: ClassValue[]) {\r\n\treturn twMerge(clsx(inputs));\r\n}\r\n",
      "type": "registry:ui"
    },
    {
      "path": "components/ui/switch.tsx",
      "content": "\"use client\";\r\n\r\nimport React from \"react\";\r\n\r\nimport { cn } from \"@/registry/utilities/cn\";\r\nimport * as SwitchPrimitives from \"@radix-ui/react-switch\";\r\n\r\nconst Switch = React.forwardRef<\r\n\tReact.ElementRef<typeof SwitchPrimitives.Root>,\r\n\tReact.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>\r\n>(({ className, ...props }, ref) => (\r\n\t<SwitchPrimitives.Root\r\n\t\tclassName={cn(\r\n\t\t\t\"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input\",\r\n\t\t\tclassName\r\n\t\t)}\r\n\t\t{...props}\r\n\t\tref={ref}\r\n\t>\r\n\t\t<SwitchPrimitives.Thumb\r\n\t\t\tclassName={cn(\r\n\t\t\t\t\"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0\"\r\n\t\t\t)}\r\n\t\t/>\r\n\t</SwitchPrimitives.Root>\r\n));\r\nSwitch.displayName = SwitchPrimitives.Root.displayName;\r\n\r\nexport { Switch };\r\n",
      "type": "registry:ui"
    }
  ]
}