{"id":13445631,"url":"https://github.com/scaleflex/js-cloudimage-360-view","last_synced_at":"2026-01-25T18:15:41.480Z","repository":{"id":34582762,"uuid":"179676647","full_name":"scaleflex/js-cloudimage-360-view","owner":"scaleflex","description":"Engage your customers with a stunning 360 view of your products. Any questions or issues, please report to https://github.com/scaleflex/js-cloudimage-360-view/issues","archived":false,"fork":false,"pushed_at":"2026-01-22T18:25:03.000Z","size":4696,"stargazers_count":2096,"open_issues_count":29,"forks_count":246,"subscribers_count":28,"default_branch":"master","last_synced_at":"2026-01-23T02:56:19.264Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scaleflex.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-04-05T12:23:47.000Z","updated_at":"2026-01-22T16:54:51.000Z","dependencies_parsed_at":"2022-08-14T03:30:22.764Z","dependency_job_id":"b9d74bac-2ffa-46fe-95c2-b4938d80439d","html_url":"https://github.com/scaleflex/js-cloudimage-360-view","commit_stats":{"total_commits":332,"total_committers":19,"mean_commits":"17.473684210526315","dds":0.6927710843373494,"last_synced_commit":"39469ed58823ebe45388a5a885d880e288c99366"},"previous_names":[],"tags_count":37,"template":false,"template_full_name":null,"purl":"pkg:github/scaleflex/js-cloudimage-360-view","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scaleflex%2Fjs-cloudimage-360-view","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scaleflex%2Fjs-cloudimage-360-view/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scaleflex%2Fjs-cloudimage-360-view/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scaleflex%2Fjs-cloudimage-360-view/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scaleflex","download_url":"https://codeload.github.com/scaleflex/js-cloudimage-360-view/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scaleflex%2Fjs-cloudimage-360-view/sbom","scorecard":{"id":803336,"data":{"date":"2025-08-11","repo":{"name":"github.com/scaleflex/js-cloudimage-360-view","commit":"39469ed58823ebe45388a5a885d880e288c99366"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/29 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 2 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-23T11:06:12.029Z","repository_id":34582762,"created_at":"2025-08-23T11:06:12.029Z","updated_at":"2025-08-23T11:06:12.029Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28756433,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T16:32:25.380Z","status":"ssl_error","status_checked_at":"2026-01-25T16:32:09.189Z","response_time":113,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T05:00:36.895Z","updated_at":"2026-01-25T18:15:41.474Z","avatar_url":"https://github.com/scaleflex.png","language":"JavaScript","funding_links":[],"categories":["Libraries","JavaScript"],"sub_categories":["VR/AR"],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.scaleflex.com/en/home\"\u003e\n    \u003cimg width=\"350\" src=\"https://scaleflex.cloudimg.io/v7/plugins/js-cloudimage-360-view/logo_scaleflex_on_white_bg.jpg?vh=91b12d\u0026w=700\" alt=\"Cloudimage logo\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eJS Cloudimage 360 View\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eA powerful JavaScript library for creating interactive 360-degree product views\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/scaleflex/js-cloudimage-360-view/releases\"\u003e\n    \u003cimg src=\"https://img.shields.io/github/v/release/scaleflex/js-cloudimage-360-view\" alt=\"Release\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://img.shields.io/bundlephobia/min/js-cloudimage-360-view\"\u003e\n    \u003cimg src=\"https://img.shields.io/bundlephobia/min/js-cloudimage-360-view\" alt=\"Size\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://img.shields.io/npm/dt/js-cloudimage-360-view?logoColor=orange\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/dt/js-cloudimage-360-view?logoColor=orange\" alt=\"Downloads\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://opensource.org/licenses/MIT\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/license-MIT-blue.svg\" alt=\"License\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://www.cloudimage.io/en/home\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Powered%20by-Cloudimage-blue\" alt=\"Cloudimage\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://scaleflex.github.io/js-cloudimage-360-view/\"\u003eView Demo\u003c/a\u003e ·\n  \u003ca href=\"https://codesandbox.io/p/sandbox/github/scaleflex/js-cloudimage-360-view/tree/master/codesandbox/react\"\u003eReact CodeSandbox\u003c/a\u003e ·\n  \u003ca href=\"https://codesandbox.io/p/sandbox/github/scaleflex/js-cloudimage-360-view/tree/master/codesandbox/vanilla\"\u003eVanilla CodeSandbox\u003c/a\u003e ·\n  \u003ca href=\"https://github.com/scaleflex/js-cloudimage-360-view/issues\"\u003eReport Bug\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Features](#features)\n- [Quick Start](#quick-start)\n- [Installation](#installation)\n- [Usage](#usage)\n- [React / Next.js](#react--nextjs)\n- [Configuration Options](#configuration-options)\n- [Event Callbacks](#event-callbacks)\n- [Hotspots](#hotspots)\n- [Interaction Hints](#interaction-hints)\n- [Styling \u0026 Theming](#styling--theming)\n- [Methods](#methods)\n- [Cloudimage Integration](#cloudimage-integration)\n- [Browser Support](#browser-support)\n- [Migration Guide (v3 → v4)](#migration-guide-v3--v4)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Overview\n\nJS Cloudimage 360 View enables you to create stunning, interactive 360-degree product views for your website. Perfect for e-commerce platforms, virtual tours, and product showcases, it provides an immersive viewing experience that lets users explore products from every angle.\n\n### Why Choose This Library?\n\n- **Easy Integration** - Get started in minutes with CDN or npm\n- **Fully Customizable** - CSS variables, callbacks, and extensive configuration options\n- **Mobile-Friendly** - Touch and swipe support out of the box\n- **Performance Optimized** - Lazy loading, responsive images, and efficient rendering\n- **Feature Rich** - Hotspots, zoom, fullscreen, autoplay, and more\n\n---\n\n## Features\n\n| Feature | Description |\n|---------|-------------|\n| **360° Rotation** | Smooth horizontal and vertical rotation with customizable speed |\n| **Touch \u0026 Drag** | Intuitive mouse and touch controls with inertia/momentum |\n| **Pinch-to-Zoom** | Natural pinch gesture zooming on mobile devices |\n| **Autoplay** | Automatic rotation with configurable behavior and direction |\n| **Zoom** | Pointer zoom and magnifier glass for detailed views |\n| **Fullscreen** | Immersive fullscreen mode with ESC key support |\n| **Hotspots** | Interactive markers with tooltips for highlighting features |\n| **Keyboard Navigation** | Arrow key support for accessibility |\n| **Lazy Loading** | Optimized loading for better performance |\n| **Responsive** | Works on all screen sizes with Cloudimage CDN integration |\n| **Theming** | CSS variables for easy customization |\n| **Event Callbacks** | Hook into viewer lifecycle and user interactions |\n\n---\n\n## Quick Start\n\nAdd the library via CDN and create your first 360 viewer in seconds:\n\n```html\n\u003c!-- Add the library (CSS is auto-injected) --\u003e\n\u003cscript src=\"https://scaleflex.cloudimg.io/v7/plugins/js-cloudimage-360-view/latest/js-cloudimage-360-view.min.js\"\u003e\u003c/script\u003e\n\n\u003c!-- Create a container with data attributes --\u003e\n\u003cdiv\n  class=\"cloudimage-360\"\n  data-folder=\"https://scaleflex.cloudimg.io/v7/demo/360-car/\"\n  data-filename-x=\"car-{index}.jpg\"\n  data-amount-x=\"36\"\n\u003e\u003c/div\u003e\n\n\u003c!-- Initialize --\u003e\n\u003cscript\u003e\n  const viewer = new window.CI360();\n  viewer.initAll();\n\u003c/script\u003e\n```\n\n---\n\n## Installation\n\n### Option 1: CDN (Recommended for Quick Setup)\n\n```html\n\u003cscript src=\"https://scaleflex.cloudimg.io/v7/plugins/js-cloudimage-360-view/latest/js-cloudimage-360-view.min.js\"\u003e\u003c/script\u003e\n```\n\n\u003e **Note:** CSS is automatically injected by the script - no separate stylesheet needed.\n\n### Option 2: Package Manager\n\n```bash\n# npm\nnpm install js-cloudimage-360-view\n\n# yarn\nyarn add js-cloudimage-360-view\n\n# pnpm\npnpm add js-cloudimage-360-view\n```\n\nThen import in your JavaScript:\n\n```javascript\nimport CI360 from 'js-cloudimage-360-view';\nimport 'js-cloudimage-360-view/css';\n```\n\n---\n\n## Usage\n\n### Method 1: Data Attributes (Declarative)\n\nThe simplest way to create a 360 viewer using HTML data attributes:\n\n```html\n\u003cdiv\n  id=\"my-360-viewer\"\n  class=\"cloudimage-360\"\n  data-folder=\"https://your-domain.com/images/\"\n  data-filename-x=\"{index}.jpg\"\n  data-amount-x=\"36\"\n  data-autoplay\n  data-fullscreen\n  data-magnifier=\"2\"\n\u003e\u003c/div\u003e\n\n\u003cscript\u003e\n  const viewer = new CI360();\n  viewer.initAll(); // Initializes all elements with class \"cloudimage-360\"\n\u003c/script\u003e\n```\n\n### Method 2: JavaScript Configuration (Programmatic)\n\nFor more control, initialize with a JavaScript configuration object:\n\n```javascript\nconst viewer = new CI360();\n\nconst container = document.getElementById('product-viewer');\n\nconst config = {\n  folder: 'https://your-domain.com/images/',\n  filenameX: 'product-{index}.jpg',\n  amountX: 36,\n  autoplay: true,\n  speed: 100,\n  dragSpeed: 150,\n  fullscreen: true,\n  magnifier: 2,\n  pointerZoom: 2,\n  inertia: true,\n\n  // Event callbacks\n  onReady: () =\u003e console.log('Viewer ready!'),\n  onSpin: (e) =\u003e console.log(`Frame: ${e.activeImageX + 1}/${e.amountX}`),\n};\n\nviewer.init(container, config);\n```\n\n### X and Y Axis Rotation\n\nSupport 360° rotation on both axes for full product exploration:\n\n```javascript\nconst config = {\n  folder: 'https://your-domain.com/images/',\n  filenameX: 'product-x-{index}.jpg',\n  filenameY: 'product-y-{index}.jpg',\n  amountX: 36,\n  amountY: 18,\n  autoplayBehavior: 'spin-xy', // Options: 'spin-x', 'spin-y', 'spin-xy', 'spin-yx'\n};\n```\n\n---\n\n## React / Next.js\n\nThe library provides a React wrapper for seamless integration with React and Next.js applications.\n\n### Installation\n\n```bash\nnpm install js-cloudimage-360-view\n```\n\n### Basic Usage\n\n```tsx\nimport { CI360Viewer } from 'js-cloudimage-360-view/react';\nimport 'js-cloudimage-360-view/css';\n\nfunction ProductView() {\n  return (\n    \u003cCI360Viewer\n      folder=\"https://example.com/images/\"\n      filenameX=\"product-{index}.jpg\"\n      amountX={36}\n      autoplay\n      fullscreen\n      aspectRatio=\"16/9\"\n      style={{ width: '100%', maxWidth: 800 }}\n    /\u003e\n  );\n}\n```\n\n### Imperative Control with Ref\n\nUse a ref to control the viewer programmatically:\n\n```tsx\nimport { useRef } from 'react';\nimport { CI360Viewer, CI360ViewerRef } from 'js-cloudimage-360-view/react';\nimport 'js-cloudimage-360-view/css';\n\nfunction ProductView() {\n  const viewerRef = useRef\u003cCI360ViewerRef\u003e(null);\n\n  return (\n    \u003c\u003e\n      \u003cCI360Viewer\n        ref={viewerRef}\n        folder=\"https://example.com/images/\"\n        filenameX=\"{index}.jpg\"\n        amountX={36}\n        onSpin={(e) =\u003e console.log(`Frame: ${e.activeImageX}`)}\n      /\u003e\n      \u003cbutton onClick={() =\u003e viewerRef.current?.play()}\u003ePlay\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e viewerRef.current?.stop()}\u003eStop\u003c/button\u003e\n      \u003cbutton onClick={() =\u003e viewerRef.current?.goToFrame(17)}\u003eGo to Frame 17\u003c/button\u003e\n    \u003c/\u003e\n  );\n}\n```\n\n### Available Ref Methods\n\n| Method | Description |\n|--------|-------------|\n| `play()` | Start autoplay |\n| `stop()` | Stop autoplay |\n| `moveLeft(steps?)` | Move left by specified frames (default: 1) |\n| `moveRight(steps?)` | Move right by specified frames (default: 1) |\n| `moveTop(steps?)` | Move up on Y-axis (default: 1) |\n| `moveBottom(steps?)` | Move down on Y-axis (default: 1) |\n| `zoomIn()` | Toggle zoom in |\n| `zoomOut()` | Zoom out |\n| `goToFrame(frame, hotspotId?)` | Animate to specific frame |\n| `getViewer()` | Get underlying viewer instance |\n\n### With Hotspots\n\n```tsx\nimport { CI360Viewer, Hotspot } from 'js-cloudimage-360-view/react';\n\nconst hotspots: Hotspot[] = [\n  {\n    id: 'feature-1',\n    label: 'Engine',\n    orientation: 'x',\n    containerSize: [1200, 800],\n    positions: { 0: { x: 500, y: 300 } },\n    content: '\u003cdiv\u003eEngine details\u003c/div\u003e',\n  },\n];\n\nfunction ProductView() {\n  return (\n    \u003cCI360Viewer\n      folder=\"https://example.com/images/\"\n      filenameX=\"{index}.jpg\"\n      amountX={36}\n      hotspots={hotspots}\n    /\u003e\n  );\n}\n```\n\n### Next.js (SSR)\n\nFor Next.js applications, use dynamic import to disable server-side rendering:\n\n```tsx\nimport dynamic from 'next/dynamic';\nimport 'js-cloudimage-360-view/css';\n\nconst CI360Viewer = dynamic(\n  () =\u003e import('js-cloudimage-360-view/react').then(mod =\u003e mod.CI360Viewer),\n  { ssr: false }\n);\n\nexport default function ProductPage() {\n  return (\n    \u003cCI360Viewer\n      folder=\"https://example.com/images/\"\n      filenameX=\"{index}.jpg\"\n      amountX={36}\n    /\u003e\n  );\n}\n```\n\n### useCI360 Hook\n\nFor advanced use cases, you can use the `useCI360` hook directly:\n\n```tsx\nimport { useRef } from 'react';\nimport { useCI360 } from 'js-cloudimage-360-view/react';\n\nfunction CustomViewer() {\n  const containerRef = useRef\u003cHTMLDivElement\u003e(null);\n  const { viewer, isReady } = useCI360(containerRef, {\n    folder: 'https://example.com/images/',\n    filenameX: '{index}.jpg',\n    amountX: 36,\n    onReady: () =\u003e console.log('Viewer ready!'),\n  });\n\n  return (\n    \u003cdiv\u003e\n      \u003cdiv ref={containerRef} style={{ width: '100%', maxWidth: 800, aspectRatio: '16/9' }} /\u003e\n      {isReady \u0026\u0026 \u003cp\u003eViewer is ready!\u003c/p\u003e}\n    \u003c/div\u003e\n  );\n}\n```\n\n### TypeScript Support\n\nThe React wrapper is fully typed. Import types as needed:\n\n```tsx\nimport type {\n  CI360ViewerProps,\n  CI360ViewerRef,\n  CI360Config,\n  SpinEventData,\n  Hotspot,\n} from 'js-cloudimage-360-view/react';\n```\n\n---\n\n## Configuration Options\n\nAll options can be set via JavaScript config or HTML data attributes.\n\n### Image Source Options\n\n| Option | Data Attribute | Default | Description |\n|--------|----------------|---------|-------------|\n| `folder` | `data-folder` | `'/'` | Path to the folder containing images |\n| `filenameX` | `data-filename-x` | `'image-{index}.jpg'` | Filename pattern for X-axis images. Use `{index}` as placeholder |\n| `filenameY` | `data-filename-y` | `null` | Filename pattern for Y-axis images |\n| `imageListX` | `data-image-list-x` | `null` | Array of image URLs for X-axis (alternative to folder/filename) |\n| `imageListY` | `data-image-list-y` | `null` | Array of image URLs for Y-axis |\n| `amountX` | `data-amount-x` | `0` | Total number of X-axis images |\n| `amountY` | `data-amount-y` | `0` | Total number of Y-axis images |\n| `indexZeroBase` | `data-index-zero-base` | `0` | Starting index for image filenames |\n\n### Behavior Options\n\n| Option | Data Attribute | Default | Description |\n|--------|----------------|---------|-------------|\n| `autoplay` | `data-autoplay` | `false` | Enable automatic rotation |\n| `autoplayBehavior` | `data-autoplay-behavior` | `'spin-x'` | Autoplay pattern: `'spin-x'`, `'spin-y'`, `'spin-xy'`, `'spin-yx'` |\n| `autoplayReverse` | `data-autoplay-reverse` | `false` | Reverse autoplay direction |\n| `playOnce` | `data-play-once` | `false` | Stop after one complete rotation |\n| `speed` | `data-speed` | `80` | Autoplay speed (ms between frames) |\n| `inertia` | `data-inertia` | `false` | Enable momentum after drag release |\n\n### Control Options\n\n| Option | Data Attribute | Default | Description |\n|--------|----------------|---------|-------------|\n| `draggable` | `data-draggable` | `true` | Enable mouse drag rotation |\n| `swipeable` | `data-swipeable` | `true` | Enable touch swipe rotation |\n| `dragSpeed` | `data-drag-speed` | `150` | Drag sensitivity |\n| `dragReverse` | `data-drag-reverse` | `false` | Reverse drag direction |\n| `keys` | `data-keys` | `false` | Enable keyboard arrow navigation |\n| `keysReverse` | `data-keys-reverse` | `false` | Reverse keyboard direction |\n| `stopAtEdges` | `data-stop-at-edges` | `false` | Stop rotation at first/last frame |\n| `pinchZoom` | `data-pinch-zoom` | `true` | Enable pinch-to-zoom on touch devices |\n\n### Display Options\n\n| Option | Data Attribute | Default | Description |\n|--------|----------------|---------|-------------|\n| `aspectRatio` | `data-aspect-ratio` | `null` | Aspect ratio for the container (e.g., `\"16/9\"`, `\"4/3\"`, `\"1/1\"`) |\n| `fullscreen` | `data-fullscreen` | `false` | Show fullscreen button |\n| `magnifier` | `data-magnifier` | `null` | Magnifier zoom level (1-5) |\n| `pointerZoom` | `data-pointer-zoom` | `0` | Pointer zoom level on click (1-5) |\n| `bottomCircle` | `data-bottom-circle` | `true` | Show 360° progress indicator |\n| `bottomCircleOffset` | `data-bottom-circle-offset` | `5` | Progress indicator offset (px) |\n| `initialIconShown` | `data-initial-icon` | `true` | Show 360° icon on load |\n| `lazyload` | `data-lazyload` | `true` | Enable lazy loading |\n| `hints` | `data-hints` | `true` | Show interaction hints on load |\n| `theme` | `data-theme` | `null` | Color theme: `'light'` or `'dark'` |\n| `hotspotTrigger` | `data-hotspot-trigger` | `'hover'` | Hotspot trigger mode: `'hover'` or `'click'` |\n| `hotspotTimelineOnClick` | `data-hotspot-timeline-on-click` | `true` | Show hotspot popup when clicking timeline dot |\n\n### Cloudimage CDN Options\n\n| Option | Data Attribute | Default | Description |\n|--------|----------------|---------|-------------|\n| `ciToken` | `data-responsive` | `null` | Cloudimage token for responsive images |\n| `ciFilters` | `data-filters` | `null` | Cloudimage filters |\n| `ciTransformation` | `data-transformation` | `null` | Cloudimage transformations |\n\n---\n\n## Event Callbacks\n\nHook into viewer events for custom functionality. Callbacks are only available via JavaScript configuration.\n\n| Callback | Event Data | Description |\n|----------|------------|-------------|\n| `onReady` | `{ viewerId }` | Viewer initialized and ready |\n| `onLoad` | `{ viewerId, imagesX, imagesY }` | All images loaded |\n| `onSpin` | `{ viewerId, direction, activeImageX, activeImageY, amountX, amountY }` | Each rotation frame |\n| `onAutoplayStart` | `{ viewerId }` | Autoplay started |\n| `onAutoplayStop` | `{ viewerId }` | Autoplay stopped |\n| `onDragStart` | `{ viewerId }` | User started dragging |\n| `onDragEnd` | `{ viewerId }` | User stopped dragging |\n| `onZoomIn` | `{ viewerId, zoomLevel }` | Pointer zoom activated |\n| `onZoomOut` | `{ viewerId }` | Pointer zoom deactivated |\n| `onFullscreenOpen` | `{ viewerId }` | Fullscreen mode opened |\n| `onFullscreenClose` | `{ viewerId }` | Fullscreen mode closed |\n\n### Example\n\n```javascript\nconst config = {\n  folder: 'https://example.com/images/',\n  filenameX: '{index}.jpg',\n  amountX: 36,\n\n  onReady: (e) =\u003e {\n    console.log(`Viewer ${e.viewerId} is ready`);\n  },\n\n  onSpin: (e) =\u003e {\n    // Update custom progress indicator\n    const progress = ((e.activeImageX + 1) / e.amountX * 100).toFixed(0);\n    document.getElementById('progress').textContent = `${progress}%`;\n  },\n\n  onFullscreenOpen: () =\u003e {\n    // Pause background video when entering fullscreen\n    document.getElementById('bg-video')?.pause();\n  },\n};\n```\n\n---\n\n## Hotspots\n\nAdd interactive markers to highlight product features.\n\n### Configuration\n\n```javascript\nconst hotspots = [\n  {\n    id: 'feature-1',\n    orientation: 'x',\n    containerSize: [1200, 800], // Reference container size for positioning\n    positions: {\n      0: { x: 500, y: 300 },\n      1: { x: 520, y: 300 },\n      2: { x: 540, y: null }, // null inherits from previous frame\n      3: { x: 560, y: null },\n      // ... positions for frames where hotspot is visible\n    },\n    content: '\u003cdiv class=\"tooltip\"\u003e\u003cstrong\u003ePremium Feature\u003c/strong\u003e\u003cp\u003eDescription here\u003c/p\u003e\u003c/div\u003e',\n    onClick: () =\u003e {\n      console.log('Hotspot clicked!');\n    },\n  },\n];\n\nconst config = {\n  folder: 'https://example.com/images/',\n  filenameX: '{index}.jpg',\n  amountX: 36,\n  hotspots: hotspots,\n};\n```\n\n### Hotspot Properties\n\n| Property | Required | Description |\n|----------|----------|-------------|\n| `id` | Yes | Unique identifier |\n| `orientation` | Yes | `'x'` or `'y'` axis |\n| `containerSize` | Yes | `[width, height]` reference dimensions |\n| `positions` | Yes | Object mapping frame index to `{ x, y }` coordinates |\n| `content` | Yes | HTML content for the tooltip |\n| `label` | No | Short label for the hotspot (used in timeline tooltips) |\n| `onClick` | No | Click handler function |\n\n### Hotspot Timeline\n\nWhen hotspots are configured, a timeline navigation bar automatically appears below the viewer. This timeline shows:\n\n- **Position indicator** - Shows current rotation position\n- **Hotspot dots** - One dot per hotspot at its center frame position\n- **Hover tooltips** - If a hotspot has a `label`, hovering over its dot shows a tooltip\n\nClicking a dot animates the viewer to that hotspot's position and optionally shows its popup.\n\n#### Timeline Tooltips\n\nTooltips display the hotspot's `label` property when hovering over a timeline dot:\n\n```javascript\nconst hotspots = [\n  {\n    id: 'engine',\n    label: 'Engine Bay',  // This text appears in the tooltip\n    orientation: 'x',\n    containerSize: [1200, 800],\n    positions: { 0: { x: 500, y: 300 }, /* ... */ },\n    content: '\u003cdiv\u003eFull hotspot content here\u003c/div\u003e',\n  },\n];\n```\n\n**Tooltip behavior:**\n- Appears after a **400ms hover delay** to prevent accidental triggers\n- Positioned above the dot with an arrow pointer\n- Hidden on mouse leave or click (navigation)\n\n#### Timeline Configuration\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `hotspotTimelineOnClick` | `true` | Show hotspot popup when clicking a timeline dot |\n\n```javascript\nconst config = {\n  hotspots: [...],\n  hotspotTimelineOnClick: true,  // Show popup on click (default)\n  // or\n  hotspotTimelineOnClick: false, // Only navigate, don't show popup\n};\n```\n\n#### Timeline CSS Variables\n\nCustomize the timeline appearance with CSS variables:\n\n```css\n:root {\n  /* Timeline track */\n  --ci360-timeline-height: 6px;\n  --ci360-timeline-track-bg: rgba(0, 0, 0, 0.12);\n\n  /* Hotspot dots */\n  --ci360-timeline-dot-size: 18px;\n  --ci360-timeline-dot-color: var(--ci360-hotspot-color);\n  --ci360-timeline-dot-border: 2px solid #fff;\n\n  /* Position indicator */\n  --ci360-timeline-indicator-size: 12px;\n  --ci360-timeline-indicator-color: #333333;\n\n  /* Tooltip styling (matches theme) */\n  --ci360-timeline-tooltip-bg: rgba(255, 255, 255, 0.95);\n  --ci360-timeline-tooltip-color: #333333;\n}\n\n/* Dark theme uses dark tooltip */\n.ci360-theme-dark {\n  --ci360-timeline-tooltip-bg: rgba(40, 40, 45, 0.95);\n  --ci360-timeline-tooltip-color: #e0e0e0;\n}\n```\n\n**Custom tooltip styling example:**\n\n```css\n/* Increase tooltip font size */\n.cloudimage-360-hotspot-timeline-tooltip {\n  font-size: 14px;\n  padding: 8px 16px;\n}\n\n/* Brand-colored tooltip */\n.my-viewer {\n  --ci360-timeline-tooltip-bg: #2563eb;\n  --ci360-timeline-tooltip-color: #ffffff;\n}\n```\n\n---\n\n## Interaction Hints\n\nThe viewer displays helpful hints at the bottom showing users how to interact with the 360° view. Hints are automatically generated based on enabled features and hide after the first interaction.\n\n### Configuration\n\n```javascript\nconst config = {\n  // Auto-detect hints based on enabled features (default)\n  hints: true,\n\n  // Disable hints\n  hints: false,\n\n  // Custom hints array\n  hints: ['drag', 'click', 'keys'],\n};\n```\n\n### Available Hint Types\n\n| Type | Desktop | Mobile | Description |\n|------|---------|--------|-------------|\n| `drag` | ✓ | - | \"Drag to rotate\" |\n| `swipe` | - | ✓ | \"Swipe to rotate\" |\n| `click` | ✓ | - | \"Click to zoom\" (when pointerZoom enabled) |\n| `pinch` | - | ✓ | \"Pinch to zoom\" (when pinchZoom enabled) |\n| `keys` | ✓ | - | \"Use arrow keys\" (when keys enabled) |\n\n---\n\n## Styling \u0026 Theming\n\n### Built-in Themes\n\nApply a theme by setting the `theme` option or using the `ci360-theme-dark` class:\n\n```javascript\n// Via config\nconst config = {\n  theme: 'dark', // or 'light'\n  // ...other options\n};\n\n// Or via HTML\n\u003cdiv class=\"cloudimage-360 ci360-theme-dark\" ...\u003e\u003c/div\u003e\n```\n\n### CSS Variables (Recommended)\n\nThe easiest way to customize the viewer appearance:\n\n```css\n:root {\n  /* Buttons */\n  --ci360-button-bg: #f0f0f0;\n  --ci360-button-bg-hover: #e0e0e0;\n  --ci360-button-size: 40px;\n  --ci360-button-border-radius: 6px;\n  --ci360-button-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n\n  /* Icons */\n  --ci360-icon-color: #37414b;\n  --ci360-icon-color-hover: #1a1f24;\n  --ci360-icon-size: 20px;\n\n  /* 360° Indicator */\n  --ci360-initial-icon-bg: rgba(255, 255, 255, 0.9);\n  --ci360-initial-icon-color: #505050;\n  --ci360-initial-icon-size: 80px;\n  --ci360-initial-icon-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);\n\n  /* Loading Spinner */\n  --ci360-spinner-color: #fff;\n  --ci360-spinner-accent: #a3a3a3;\n  --ci360-spinner-size: 30px;\n\n  /* Fullscreen */\n  --ci360-fullscreen-bg: #fff;\n\n  /* Magnifier */\n  --ci360-magnifier-size: 250px;\n  --ci360-magnifier-border: 2px solid rgba(0, 0, 0, 0.3);\n  --ci360-magnifier-shadow: 0 8px 16px rgba(0, 0, 0, 0.4);\n\n  /* Hotspots */\n  --ci360-hotspot-color: #00aaff;\n  --ci360-hotspot-border: 1px solid #fff;\n  --ci360-hotspot-size: 18px;\n\n  /* Tooltips */\n  --ci360-popper-bg: rgba(255, 255, 255, 0.95);\n  --ci360-popper-color: #333;\n  --ci360-popper-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);\n  --ci360-popper-border-radius: 6px;\n\n  /* Hints Overlay */\n  --ci360-hints-bg: rgba(0, 0, 0, 0.75);\n  --ci360-hints-color: #ffffff;\n  --ci360-hints-font-size: 14px;\n  --ci360-hints-border-radius: 12px;\n\n  /* Bottom Circle Indicator */\n  --ci360-circle-color-start: rgba(0, 0, 0, 0.05);\n  --ci360-circle-color-mid: rgba(0, 0, 0, 0.3);\n  --ci360-circle-color-end: rgba(0, 0, 0, 0.05);\n  --ci360-circle-dot-color: rgba(0, 0, 0, 0.4);\n\n  /* Other */\n  --ci360-focus-color: #0066cc;\n  --ci360-overlay-bg: rgba(255, 255, 255, 1);\n}\n```\n\n### Custom Dark Theme Example\n\nIf you prefer to customize beyond the built-in dark theme:\n\n```css\n.my-dark-viewer {\n  --ci360-button-bg: rgba(30, 30, 35, 0.9);\n  --ci360-button-bg-hover: rgba(45, 45, 50, 0.95);\n  --ci360-icon-color: #e0e0e0;\n  --ci360-icon-color-hover: #ffffff;\n  --ci360-fullscreen-bg: #1a1a1f;\n  --ci360-initial-icon-bg: rgba(30, 30, 35, 0.9);\n  --ci360-initial-icon-color: #e0e0e0;\n  --ci360-popper-bg: rgba(40, 40, 45, 0.95);\n  --ci360-popper-color: #e0e0e0;\n  --ci360-hints-bg: rgba(255, 255, 255, 0.12);\n  --ci360-circle-color-mid: rgba(255, 255, 255, 0.25);\n  --ci360-circle-dot-color: rgba(255, 255, 255, 0.4);\n  --ci360-overlay-bg: rgba(26, 26, 31, 1);\n}\n```\n\n### Scope to Specific Viewer\n\n```css\n#my-special-viewer {\n  --ci360-button-bg: #4a90d9;\n  --ci360-icon-color: #ffffff;\n  --ci360-hotspot-color: #ff6b6b;\n}\n```\n\n### CSS Classes Reference\n\n| Class | Description |\n|-------|-------------|\n| `.cloudimage-360` | Main container |\n| `.cloudimage-360-inner-box` | Inner container |\n| `.cloudimage-360-button` | Control buttons |\n| `.cloudimage-360-icons-container` | Button container |\n| `.cloudimage-initial-icon` | 360° indicator icon |\n| `.cloudimage-360-view-360-circle` | Bottom progress indicator |\n| `.cloudimage-loading-spinner` | Loading spinner |\n| `.cloudimage-360-fullscreen-modal` | Fullscreen container |\n| `.cloudimage-360-img-magnifier-glass` | Magnifier element |\n| `.cloudimage-360-hotspot` | Hotspot marker |\n| `.cloudimage-360-popper` | Hotspot tooltip |\n| `.cloudimage-360-hints-overlay` | Hints overlay container |\n| `.cloudimage-360-hints-container` | Hints content box |\n| `.cloudimage-360-hotspot-timeline` | Hotspot timeline container |\n| `.cloudimage-360-hotspot-timeline-track` | Timeline track |\n| `.cloudimage-360-hotspot-timeline-dot` | Timeline hotspot dot |\n| `.cloudimage-360-hotspot-timeline-indicator` | Timeline position indicator |\n| `.cloudimage-360-hotspot-timeline-tooltip` | Timeline dot tooltip (appears on hover) |\n| `.ci360-theme-dark` | Dark theme class |\n\n---\n\n## Methods\n\n### Instance Methods\n\n```javascript\nconst viewer = new CI360();\n\n// Initialize all viewers with class \"cloudimage-360\"\nviewer.initAll();\n\n// Initialize a specific container\nviewer.init(containerElement, config);\n\n// Get a viewer by its container ID\nconst view = viewer.getViewById('my-viewer');\n\n// Get all viewer instances\nconst allViews = viewer.getViews();\n\n// Update a viewer's configuration\nviewer.updateView('my-viewer', { speed: 50, autoplay: true });\n```\n\n### View Methods\n\n```javascript\nconst view = viewer.getViewById('my-viewer');\n\n// Playback control\nview.play();                    // Start autoplay\nview.stopAutoplay();            // Stop autoplay\n\n// Rotation (stopAtEdges: boolean, steps: number)\nview.moveLeft(false, 5);        // Rotate left by 5 frames\nview.moveRight(false, 5);       // Rotate right by 5 frames\nview.moveTop(false, 1);         // Rotate up by 1 frame (Y-axis)\nview.moveBottom(false, 1);      // Rotate down by 1 frame (Y-axis)\n\n// Navigation\nview.animateToFrame(36);        // Animate to frame 36\nview.animateToFrame(10, 'hotspot-1'); // Go to frame and show hotspot\n\n// UI control\nview.hideAllIcons();            // Hide all overlay icons\n\n// State\nview.activeImageX;              // Current X-axis frame (0-indexed)\nview.activeImageY;              // Current Y-axis frame (0-indexed)\nview.amountX;                   // Total X-axis frames\nview.amountY;                   // Total Y-axis frames\n\n// Cleanup\nview.destroy();                 // Destroy the viewer\n```\n\n---\n\n## Cloudimage Integration\n\nEnhance performance with [Cloudimage](https://cloudimage.io) CDN for responsive, optimized images.\n\n### Setup\n\n1. Register at [cloudimage.io](https://cloudimage.io) to get your token\n2. Add the token to your viewer configuration:\n\n```javascript\nconst config = {\n  folder: 'https://your-domain.com/images/',\n  filenameX: '{index}.jpg',\n  amountX: 36,\n  ciToken: 'your-cloudimage-token', // or use data-responsive attribute\n};\n```\n\n### Benefits\n\n- **25GB free CDN traffic** per month\n- **Automatic optimization** - WebP, AVIF conversion\n- **Responsive images** - Serve the right size for each device\n- **Global CDN** - Fast delivery worldwide\n- **Image transformations** - Resize, crop, filters on-the-fly\n\n---\n\n## Browser Support\n\n| Browser | Version |\n|---------|---------|\n| Chrome | 69+ |\n| Firefox | 105+ |\n| Safari | 16.4+ |\n| Edge | 79+ |\n| iOS Safari | 16.4+ |\n| Android Chrome | 69+ |\n\n\u003e **Note:** This library uses OffscreenCanvas for optimal performance, which requires the browser versions listed above.\n\n---\n\n## Mobile Considerations\n\n### Memory Limitations\n\nMobile browsers (especially Safari) have strict memory limits that can cause tab crashes when loading many high-resolution images. The library includes built-in optimizations for mobile that are **automatically enabled**:\n\n- **Sequential image loading** (3 concurrent on mobile vs 6 on desktop)\n- **Main-thread canvas rendering** (avoids OffscreenCanvas memory issues on Safari)\n- **Reduced touch event rate** (30fps vs 100fps on desktop)\n- **Capped device pixel ratio** (max 2x on mobile)\n- **Automatic memory management** (releases off-screen viewers, frees memory when page backgrounded)\n\n### Recommended Settings for Mobile\n\n| Setting | Desktop | Mobile | Notes |\n|---------|---------|--------|-------|\n| `amountX` | 60-100+ | 30-40 max | Each image uses ~4MB GPU memory |\n| `pointerZoom` | ✅ | ❌ | Loads higher-res images |\n| `magnifier` | ✅ | ❌ | Loads higher-res images |\n\n### Detecting Mobile Devices\n\nThe library automatically detects mobile devices, but you can also adjust your configuration:\n\n```javascript\nconst isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(\n  navigator.userAgent\n);\n\nconst viewer = new CI360();\nviewer.init(container, {\n  folder: 'https://example.com/images/',\n  filenameX: '{index}.jpg',\n  amountX: isMobile ? 36 : 72,           // Fewer images on mobile\n  pointerZoom: isMobile ? false : 2,     // Disable zoom on mobile\n  magnifier: isMobile ? false : 3,       // Disable magnifier on mobile\n});\n```\n\n### Memory Management API\n\nMemory management is **automatically enabled on mobile**. For desktop or manual control:\n\n```javascript\nconst viewer = new CI360();\nviewer.initAll();\n\n// Manually enable (already auto-enabled on mobile)\nviewer.enableMemoryManagement();\n\n// Disable if needed\nviewer.disableMemoryManagement();\n```\n\nThis uses IntersectionObserver to:\n- Release memory when viewers scroll off-screen\n- Reload images when viewers become visible again\n- Release all viewer memory when the page is backgrounded\n\n---\n\n## Migration Guide (v3 → v4)\n\nVersion 4 introduces significant improvements in performance, customization, and developer experience. This guide helps you upgrade from v3.\n\n### Breaking Changes\n\n#### 1. CSS Handling\n\nFor CDN users, CSS is now auto-injected (same as v3):\n\n```html\n\u003c!-- v4: Just include the script --\u003e\n\u003cscript src=\".../js-cloudimage-360-view.min.js\"\u003e\u003c/script\u003e\n```\n\nFor npm/bundler users, import CSS separately:\n\n```javascript\nimport CI360 from 'js-cloudimage-360-view';\nimport 'js-cloudimage-360-view/css';\n```\n\n#### 2. Initialization API Changed\n\n```javascript\n// v3\nwindow.CI360.init();\nwindow.CI360.add('my-viewer');\nwindow.CI360.update('my-viewer', true);\nwindow.CI360.destroy();\n\n// v4\nconst viewer = new CI360();\nviewer.initAll();                           // Initialize all\nviewer.init(container, config);             // Initialize specific container\nviewer.updateView('my-viewer', newConfig);  // Update with new config\nviewer.getViewById('my-viewer').destroy();  // Destroy specific viewer\n```\n\n#### 3. Browser Requirements Changed\n\nv4 uses OffscreenCanvas for performance, requiring newer browsers:\n\n| Browser | v3 | v4 |\n|---------|-----|-----|\n| Safari | 12+ | **16.4+** |\n| iOS Safari | 12+ | **16.4+** |\n| Firefox | 55+ | **105+** |\n| Chrome | 60+ | 69+ |\n\n### Deprecated Configuration Options\n\nThe following options have been removed in v4:\n\n| v3 Option | v4 Alternative |\n|-----------|----------------|\n| `data-box-shadow` | Use CSS: `.cloudimage-360 { box-shadow: ... }` |\n| `data-ratio` | Container automatically maintains aspect ratio |\n| `data-lazy-selector` | Use `data-lazyload` (boolean) |\n| `data-hide-360-logo` | Use `data-initial-icon` (boolean, inverted) |\n| `data-logo-src` | Custom logos not supported; use CSS to hide |\n| `data-image-info` | Removed |\n| `data-request-responsive-images` | Removed |\n| `data-disable-drag` | Use `data-draggable` (inverted: `draggable=\"false\"`) |\n| `data-spin-reverse` | Use `data-drag-reverse` and `data-autoplay-reverse` |\n\n### Hotspot Configuration Changes\n\nHotspot properties have been simplified:\n\n```javascript\n// v3 - Multiple specific properties\nconst hotspot = {\n  id: 'feature-1',\n  title: 'Feature Title',\n  description: 'Description text',\n  url: 'https://example.com',\n  newTab: true,\n  moreDetailsUrl: 'https://example.com/details',\n  moreDetailsTitle: 'Learn More',\n  popupSelector: '#custom-popup',\n  arrow: true,\n  placement: 'top',\n  offset: [0, 10],\n  positions: { 0: { x: 100, y: 200 } },\n};\n\n// v4 - Flexible HTML content\nconst hotspot = {\n  id: 'feature-1',\n  orientation: 'x',\n  containerSize: [1200, 800],\n  positions: { 0: { x: 100, y: 200 } },\n  content: `\n    \u003cdiv class=\"my-tooltip\"\u003e\n      \u003ch3\u003eFeature Title\u003c/h3\u003e\n      \u003cp\u003eDescription text\u003c/p\u003e\n      \u003ca href=\"https://example.com\" target=\"_blank\"\u003eLearn More\u003c/a\u003e\n    \u003c/div\u003e\n  `,\n  onClick: () =\u003e console.log('Clicked!'),\n};\n```\n\n| v3 Property | v4 Alternative |\n|-------------|----------------|\n| `title`, `description` | Use `content` with HTML |\n| `url`, `newTab` | Include `\u003ca\u003e` tag in `content` |\n| `moreDetailsUrl`, `moreDetailsTitle` | Include in `content` HTML |\n| `popupSelector` | Use `content` with your HTML |\n| `arrow`, `placement`, `offset` | Popper.js handles positioning automatically |\n| `open` | Removed; hotspots open on click/hover |\n\n### New Features in v4\n\nTake advantage of these new capabilities:\n\n#### CSS Variables for Theming\n\n```css\n:root {\n  --ci360-button-bg: #f0f0f0;\n  --ci360-icon-color: #333;\n  --ci360-hotspot-color: #00aaff;\n}\n```\n\n#### Built-in Themes\n\n```html\n\u003cdiv class=\"cloudimage-360 ci360-theme-dark\" ...\u003e\u003c/div\u003e\n```\n\n#### Event Callbacks\n\n```javascript\nconst config = {\n  onReady: (e) =\u003e console.log('Ready'),\n  onSpin: (e) =\u003e console.log(`Frame: ${e.activeImageX}`),\n  onFullscreenOpen: () =\u003e console.log('Fullscreen'),\n};\n```\n\n#### Interaction Hints\n\n```javascript\nconst config = {\n  hints: true,  // Auto-detect hints\n  // or\n  hints: ['drag', 'click', 'keys'],  // Custom hints\n};\n```\n\n#### Pinch-to-Zoom (Mobile)\n\n```javascript\nconst config = {\n  pinchZoom: true,  // Enabled by default\n};\n```\n\n### Quick Migration Checklist\n\n- [ ] Add CSS file import alongside JS\n- [ ] Update initialization code to use `new CI360()`\n- [ ] Replace `data-disable-drag` with `data-draggable=\"false\"`\n- [ ] Replace `data-spin-reverse` with `data-drag-reverse`\n- [ ] Replace `data-hide-360-logo` with `data-initial-icon=\"false\"`\n- [ ] Update hotspot configs to use `content` instead of individual properties\n- [ ] Test on Safari 16.4+ (older versions not supported)\n- [ ] Consider adding CSS variables for customization\n- [ ] Consider adding event callbacks for analytics/tracking\n\n---\n\n## Contributing\n\nWe welcome contributions! Here's how you can help:\n\n- **[Report bugs](https://github.com/Scaleflex/js-cloudimage-360-view/issues)** - Found a bug? Let us know!\n- **[Request features](https://github.com/Scaleflex/js-cloudimage-360-view/issues)** - Have an idea? Share it!\n- **[Submit PRs](https://github.com/Scaleflex/js-cloudimage-360-view/pulls)** - Code contributions are welcome!\n- **[Join discussions](https://github.com/Scaleflex/js-cloudimage-360-view/discussions)** - Ask questions, share insights\n\n### Development Setup\n\n```bash\ngit clone https://github.com/Scaleflex/js-cloudimage-360-view.git\ncd js-cloudimage-360-view\nnpm install\nnpm run dev\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eContributors\u003c/strong\u003e\u003c/summary\u003e\n\u003cbr\u003e\n\u003ca href=\"https://github.com/Scaleflex/js-cloudimage-360-view/graphs/contributors\"\u003e\n  \u003cimg src=\"https://contrib.rocks/image?repo=Scaleflex/js-cloudimage-360-view\" alt=\"Contributors\"\u003e\n\u003c/a\u003e\n\u003c/details\u003e\n\n---\n\n## License\n\nThis project is licensed under the [MIT License](https://opensource.org/licenses/MIT).\n\n---\n\n\u003cp align=\"center\"\u003e\n  Made with care by the \u003ca href=\"https://www.scaleflex.com\"\u003eScaleflex\u003c/a\u003e team\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscaleflex%2Fjs-cloudimage-360-view","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscaleflex%2Fjs-cloudimage-360-view","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscaleflex%2Fjs-cloudimage-360-view/lists"}