js-cloudimage-hotspot 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +294 -0
- package/dist/a11y/aria.d.ts +7 -0
- package/dist/a11y/aria.d.ts.map +1 -0
- package/dist/a11y/focus.d.ts +9 -0
- package/dist/a11y/focus.d.ts.map +1 -0
- package/dist/a11y/keyboard.d.ts +14 -0
- package/dist/a11y/keyboard.d.ts.map +1 -0
- package/dist/core/CIHotspot.d.ts +48 -0
- package/dist/core/CIHotspot.d.ts.map +1 -0
- package/dist/core/ci-hotspot.d.ts +95 -0
- package/dist/core/ci-hotspot.d.ts.map +1 -0
- package/dist/core/config.d.ts +15 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/types.d.ts +227 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/editor/ci-hotspot-editor.d.ts +58 -0
- package/dist/editor/ci-hotspot-editor.d.ts.map +1 -0
- package/dist/editor/drag-manager.d.ts +18 -0
- package/dist/editor/drag-manager.d.ts.map +1 -0
- package/dist/editor/editor-toolbar.d.ts +26 -0
- package/dist/editor/editor-toolbar.d.ts.map +1 -0
- package/dist/editor/index.d.ts +3 -0
- package/dist/editor/index.d.ts.map +1 -0
- package/dist/editor/js-cloudimage-hotspot-editor.cjs.js +4 -0
- package/dist/editor/js-cloudimage-hotspot-editor.cjs.js.map +1 -0
- package/dist/editor/js-cloudimage-hotspot-editor.esm.js +1981 -0
- package/dist/editor/js-cloudimage-hotspot-editor.esm.js.map +1 -0
- package/dist/editor/js-cloudimage-hotspot-editor.min.js +4 -0
- package/dist/editor/js-cloudimage-hotspot-editor.min.js.map +1 -0
- package/dist/editor/property-panel.d.ts +17 -0
- package/dist/editor/property-panel.d.ts.map +1 -0
- package/dist/editor/selection-manager.d.ts +15 -0
- package/dist/editor/selection-manager.d.ts.map +1 -0
- package/dist/editor/types.d.ts +32 -0
- package/dist/editor/types.d.ts.map +1 -0
- package/dist/editor/undo-manager.d.ts +16 -0
- package/dist/editor/undo-manager.d.ts.map +1 -0
- package/dist/fullscreen/fullscreen.d.ts +14 -0
- package/dist/fullscreen/fullscreen.d.ts.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/js-cloudimage-hotspot.cjs.js +2 -0
- package/dist/js-cloudimage-hotspot.cjs.js.map +1 -0
- package/dist/js-cloudimage-hotspot.esm.js +1408 -0
- package/dist/js-cloudimage-hotspot.esm.js.map +1 -0
- package/dist/js-cloudimage-hotspot.min.js +2 -0
- package/dist/js-cloudimage-hotspot.min.js.map +1 -0
- package/dist/markers/Marker.d.ts +10 -0
- package/dist/markers/Marker.d.ts.map +1 -0
- package/dist/markers/pulse.d.ts +9 -0
- package/dist/markers/pulse.d.ts.map +1 -0
- package/dist/popover/Popover.d.ts +41 -0
- package/dist/popover/Popover.d.ts.map +1 -0
- package/dist/popover/position.d.ts +6 -0
- package/dist/popover/position.d.ts.map +1 -0
- package/dist/popover/sanitize.d.ts +6 -0
- package/dist/popover/sanitize.d.ts.map +1 -0
- package/dist/popover/template.d.ts +9 -0
- package/dist/popover/template.d.ts.map +1 -0
- package/dist/react/index.cjs +2 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.js +1617 -0
- package/dist/react/index.js.map +1 -0
- package/dist/utils/cloudimage.d.ts +16 -0
- package/dist/utils/cloudimage.d.ts.map +1 -0
- package/dist/utils/coordinates.d.ts +17 -0
- package/dist/utils/coordinates.d.ts.map +1 -0
- package/dist/utils/dom.d.ts +13 -0
- package/dist/utils/dom.d.ts.map +1 -0
- package/dist/utils/events.d.ts +15 -0
- package/dist/utils/events.d.ts.map +1 -0
- package/dist/zoom/ScrollHint.d.ts +8 -0
- package/dist/zoom/ScrollHint.d.ts.map +1 -0
- package/dist/zoom/ZoomPan.d.ts +51 -0
- package/dist/zoom/ZoomPan.d.ts.map +1 -0
- package/dist/zoom/controls.d.ts +14 -0
- package/dist/zoom/controls.d.ts.map +1 -0
- package/dist/zoom/gestures.d.ts +28 -0
- package/dist/zoom/gestures.d.ts.map +1 -0
- package/dist/zoom/scroll-hint.d.ts +8 -0
- package/dist/zoom/scroll-hint.d.ts.map +1 -0
- package/dist/zoom/zoom-pan.d.ts +53 -0
- package/dist/zoom/zoom-pan.d.ts.map +1 -0
- package/package.json +97 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const S=require("react/jsx-runtime"),x=require("react"),nt=require("react-dom"),rt={alt:"",trigger:"hover",zoom:!1,zoomMax:4,zoomMin:1,theme:"light",pulse:!0,zoomControls:!0,placement:"top",lazyLoad:!0,sceneTransition:"fade",scrollHint:!0,invertMarkerTheme:!1,fullscreenButton:!0,zoomControlsPosition:"bottom-right"},at={"data-ci-hotspot-src":{key:"src",type:"string"},"data-ci-hotspot-alt":{key:"alt",type:"string"},"data-ci-hotspot-items":{key:"hotspots",type:"json"},"data-ci-hotspot-trigger":{key:"trigger",type:"string"},"data-ci-hotspot-zoom":{key:"zoom",type:"boolean"},"data-ci-hotspot-zoom-max":{key:"zoomMax",type:"number"},"data-ci-hotspot-zoom-min":{key:"zoomMin",type:"number"},"data-ci-hotspot-theme":{key:"theme",type:"string"},"data-ci-hotspot-pulse":{key:"pulse",type:"boolean"},"data-ci-hotspot-placement":{key:"placement",type:"string"},"data-ci-hotspot-lazy-load":{key:"lazyLoad",type:"boolean"},"data-ci-hotspot-zoom-controls":{key:"zoomControls",type:"boolean"},"data-ci-hotspot-scroll-hint":{key:"scrollHint",type:"boolean"},"data-ci-hotspot-ci-token":{key:"token",type:"string",nested:"cloudimage"},"data-ci-hotspot-ci-api-version":{key:"apiVersion",type:"string",nested:"cloudimage"},"data-ci-hotspot-ci-domain":{key:"domain",type:"string",nested:"cloudimage"},"data-ci-hotspot-ci-limit-factor":{key:"limitFactor",type:"number",nested:"cloudimage"},"data-ci-hotspot-ci-params":{key:"params",type:"string",nested:"cloudimage"},"data-ci-hotspot-scenes":{key:"scenes",type:"json"},"data-ci-hotspot-initial-scene":{key:"initialScene",type:"string"},"data-ci-hotspot-scene-transition":{key:"sceneTransition",type:"string"},"data-ci-hotspot-scene-aspect-ratio":{key:"sceneAspectRatio",type:"string"},"data-ci-hotspot-invert-marker-theme":{key:"invertMarkerTheme",type:"boolean"},"data-ci-hotspot-fullscreen-button":{key:"fullscreenButton",type:"boolean"},"data-ci-hotspot-zoom-controls-position":{key:"zoomControlsPosition",type:"string"}};function ct(i){const t={},e={};for(const[o,s]of Object.entries(at)){const c=i.getAttribute(o);if(c===null)continue;const n=lt(c,s.type);s.nested==="cloudimage"?e[s.key]=n:t[s.key]=n}return Object.keys(e).length>0&&(t.cloudimage=e),t}function lt(i,t){switch(t){case"boolean":return i==="true";case"number":{const e=parseFloat(i);if(isNaN(e)){console.warn(`CIHotspot: invalid number value "${i}"`);return}return e}case"json":try{return JSON.parse(i)}catch{console.warn(`CIHotspot: failed to parse JSON value "${i}"`);return}default:return i}}function $(i){return{...rt,...i,src:i.src||"",hotspots:i.hotspots||[]}}function W(i){if(i.scenes&&i.scenes.length>0){const t=new Set;for(const e of i.scenes){if(!e.id)throw new Error('CIHotspot: each scene must have an "id"');if(t.has(e.id))throw new Error(`CIHotspot: duplicate scene ID "${e.id}"`);if(t.add(e.id),!e.src)throw new Error(`CIHotspot: scene "${e.id}" must have a "src"`)}for(const e of i.scenes)for(const o of e.hotspots||[])if(o.navigateTo&&!t.has(o.navigateTo))throw new Error(`CIHotspot: hotspot "${o.id}" navigateTo "${o.navigateTo}" is not a valid scene ID`);if(i.initialScene&&!t.has(i.initialScene))throw new Error(`CIHotspot: initialScene "${i.initialScene}" not found in scenes`)}else if(!i.src)throw new Error('CIHotspot: "src" is required')}const j="ci-hotspot-styles";function tt(){return typeof window<"u"&&typeof document<"u"}function ht(i){if(typeof i=="string"){const t=document.querySelector(i);if(!t)throw new Error(`CIHotspot: element "${i}" not found`);return t}return i}function b(i,t,e){const o=document.createElement(i);if(t&&(o.className=t),e)for(const[s,c]of Object.entries(e))o.setAttribute(s,c);return o}function g(i,...t){i.classList.add(...t)}function w(i,...t){i.classList.remove(...t)}function pt(i){if(!tt()||document.getElementById(j))return;const t=document.createElement("style");t.id=j,t.textContent=i,document.head.appendChild(t)}function V(i){if(typeof i=="string"){const t=i.trim();return t.endsWith("%")?{value:parseFloat(t),isPercent:!0}:{value:parseFloat(t),isPercent:!1}}return{value:i,isPercent:!1}}function B(i,t,e,o){const s=V(i),c=V(t);return{x:s.isPercent?s.value:s.value/e*100,y:c.isPercent?c.value:c.value/o*100}}function m(i,t,e,o){return i.addEventListener(t,e,o),()=>i.removeEventListener(t,e,o)}function dt(i,t){const e=b("button","ci-hotspot-marker",{"aria-label":i.label,"aria-expanded":"false","data-hotspot-id":i.id,tabindex:"0"});if(e.style.left=`${i.x}%`,e.style.top=`${i.y}%`,i.className){const o=i.className.trim().split(/\s+/).filter(Boolean);o.length&&g(e,...o)}return i.hidden&&g(e,"ci-hotspot-marker--hidden"),t&&g(e,"ci-hotspot-marker--pulse"),i.icon&&mt(e,i.icon),e}function ut(i){if(typeof DOMParser>"u")return"";const e=new DOMParser().parseFromString(i,"image/svg+xml").documentElement;return e.querySelector("parsererror")?"":(et(e),new XMLSerializer().serializeToString(e))}const ft=new Set(["script","foreignobject","iframe","object","embed","animate","animatetransform","animatemotion","set","style","a","use","image","feimage"]);function et(i){for(const t of Array.from(i.attributes)){const e=t.name.toLowerCase();(e.startsWith("on")||e==="style"||(e==="href"||e==="xlink:href")&&/^\s*javascript\s*:/i.test(t.value))&&i.removeAttribute(t.name)}for(const t of Array.from(i.children)){if(ft.has(t.tagName.toLowerCase())){t.remove();continue}et(t)}}function mt(i,t){const e=t.trim();if(/^<svg[\s>]/i.test(e)||/^<\?xml/i.test(e))i.innerHTML=ut(t);else if(t.match(/\.(png|jpg|jpeg|gif|svg|webp)$/i)||t.startsWith("http")||t.startsWith("/")){const o=b("img",void 0,{src:t,alt:"","aria-hidden":"true"});o.style.width="100%",o.style.height="100%",o.style.objectFit="contain",i.appendChild(o)}else{const o=b("span",t,{"aria-hidden":"true"});i.appendChild(o)}}function f(i,t){t?(g(i,"ci-hotspot-marker--active"),i.setAttribute("aria-expanded","true")):(w(i,"ci-hotspot-marker--active"),i.setAttribute("aria-expanded","false"))}function gt(i,t){t?g(i,"ci-hotspot-marker--hidden"):w(i,"ci-hotspot-marker--hidden")}function A(i){i.remove()}function vt(i,t,e,o){const s=i.getBoundingClientRect(),c=e.getBoundingClientRect(),n=t.offsetWidth,r=t.offsetHeight,a=s.left+s.width/2-c.left,l=s.top+s.height/2-c.top,d=s.top-c.top,h=s.bottom-c.top,p=s.left-c.left,u=s.right-c.left,k=e.offsetWidth,v=e.offsetHeight,y=8,M={top:d-y,bottom:v-h-y,left:p-y,right:k-u-y};let z=o.placement;z==="auto"&&(z=bt(M)),z=yt(z,n,r,M);let T,C,N=0;switch(z){case"top":T=a-n/2,C=d-y-r;break;case"bottom":T=a-n/2,C=h+y;break;case"left":T=p-y-n,C=l-r/2;break;case"right":T=u+y,C=l-r/2;break;default:T=a-n/2,C=d-y-r}const L=wt(T,C,n,r,k,v);return N=z==="top"||z==="bottom"?T-L.x:C-L.y,{x:L.x,y:L.y,placement:z,arrowOffset:N}}function bt(i){const t=Math.max(i.top,i.bottom,i.left,i.right);return t===i.top?"top":t===i.bottom?"bottom":t===i.right?"right":"left"}function yt(i,t,e,o){const s=i;switch(s){case"top":if(o.top<e&&o.bottom>o.top)return"bottom";break;case"bottom":if(o.bottom<e&&o.top>o.bottom)return"top";break;case"left":if(o.left<t&&o.right>o.left)return"right";break;case"right":if(o.right<t&&o.left>o.right)return"left";break}return s}function wt(i,t,e,o,s,c){let r=i,a=t;return e>s-2*4?r=(s-e)/2:(r<4&&(r=4),r+e>s-4&&(r=s-4-e)),o>c-2*4?a=(c-o)/2:(a<4&&(a=4),a+o>c-4&&(a=c-4-o)),{x:r,y:a}}const kt=new Set(["a","b","br","div","em","h1","h2","h3","h4","h5","h6","i","img","li","ol","p","span","strong","ul"]),xt=new Set(["class","href","src","alt","title","target","rel"]),Et=/^(?:https?:|mailto:)/i,zt=/^(?:https?:|data:image\/(?!svg[+%]))/i,Tt=new Set(["noopener","noreferrer","nofollow","external","author","help","license","next","prev","search","tag","bookmark"]);function Ct(i){const o=new DOMParser().parseFromString(`<body>${i}</body>`,"text/html").body;return ot(o),o.innerHTML}function ot(i){const t=Array.from(i.childNodes);for(const e of t)if(e.nodeType!==Node.TEXT_NODE)if(e.nodeType===Node.ELEMENT_NODE){const o=e,s=o.tagName.toLowerCase();if(!kt.has(s)){o.remove();continue}const c=Array.from(o.attributes);for(const n of c){const r=n.name.toLowerCase();if(r.startsWith("on")){o.removeAttribute(n.name);continue}if(!xt.has(r)){o.removeAttribute(n.name);continue}if(r==="href"&&!Et.test(n.value.trim()))o.removeAttribute(n.name);else if(r==="src"&&!zt.test(n.value.trim()))o.removeAttribute(n.name);else if(r==="rel"){const a=n.value.trim().toLowerCase().split(/\s+/).filter(l=>Tt.has(l));a.length===0?o.removeAttribute(n.name):o.setAttribute(n.name,a.join(" "))}}ot(o)}else e.remove()}function St(i){const t=[];i.image&&t.push(`<img class="ci-hotspot-popover-image" src="${Z(i.image)}" alt="${Z(i.title||"")}">`);const e=[];if(i.title&&e.push(`<h3 class="ci-hotspot-popover-title">${F(i.title)}</h3>`),i.price&&e.push(`<span class="ci-hotspot-popover-price">${F(i.price)}</span>`),i.description&&e.push(`<p class="ci-hotspot-popover-description">${F(i.description)}</p>`),i.url&&Pt(i.url)){const o=i.ctaText||"View details";e.push(`<a class="ci-hotspot-popover-cta" href="${Z(i.url)}">${F(String(o))}</a>`)}return e.length>0&&t.push(`<div class="ci-hotspot-popover-body">${e.join("")}</div>`),t.join("")}function Ht(i,t){return t?t(i):i.content?Ct(i.content):i.data?St(i.data):""}function F(i){return i.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function Pt(i){const t=i.replace(/[\s\x00-\x1f]/g,"");return/^https?:\/\//i.test(t)||/^\/(?!\/)/.test(t)||/^#/.test(t)}function Z(i){return i.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">")}class _{constructor(t,e){this.visible=!1,this.markerEl=null,this.containerEl=null,this.hoverCleanups=[],this.hotspot=t,this.options=e;const o=e.triggerMode==="click"||e.triggerMode==="load";this.element=b("div","ci-hotspot-popover",{role:o?"dialog":"tooltip",id:`ci-hotspot-popover-${t.id}`,"aria-hidden":"true","data-placement":e.placement==="auto"?"top":e.placement,...o&&t.label?{"aria-label":t.label}:{}}),this.arrowEl=b("div","ci-hotspot-popover-arrow"),this.contentEl=b("div","ci-hotspot-popover-content"),this.element.appendChild(this.arrowEl),this.element.appendChild(this.contentEl);const s=Ht(t,e.renderFn);if(typeof s=="string"?this.contentEl.innerHTML=s:s instanceof HTMLElement&&this.contentEl.appendChild(s),e.triggerMode==="hover"){const c=()=>this.clearHideTimer(),n=()=>this.scheduleHide();this.element.addEventListener("mouseenter",c),this.element.addEventListener("mouseleave",n),this.hoverCleanups.push(()=>this.element.removeEventListener("mouseenter",c),()=>this.element.removeEventListener("mouseleave",n))}}mount(t,e){this.containerEl=t,this.markerEl=e,t.appendChild(this.element),this.options.triggerMode==="click"||this.options.triggerMode==="load"?(e.setAttribute("aria-haspopup","dialog"),e.setAttribute("aria-controls",this.element.id)):e.setAttribute("aria-describedby",this.element.id)}show(){var t,e;this.clearHideTimer(),!this.visible&&(this.visible=!0,g(this.element,"ci-hotspot-popover--visible"),this.element.setAttribute("aria-hidden","false"),this.updatePosition(),(e=(t=this.options).onOpen)==null||e.call(t,this.hotspot))}scheduleHide(t=200){this.clearHideTimer(),this.hideTimer=setTimeout(()=>{this.hide()},t)}hide(){var t,e;this.clearHideTimer(),this.visible&&(this.visible=!1,w(this.element,"ci-hotspot-popover--visible"),this.element.setAttribute("aria-hidden","true"),(e=(t=this.options).onClose)==null||e.call(t,this.hotspot))}clearHideTimer(){this.hideTimer!==void 0&&(clearTimeout(this.hideTimer),this.hideTimer=void 0)}updatePosition(){if(!this.markerEl||!this.containerEl||!this.visible)return;const t=vt(this.markerEl,this.element,this.containerEl,{placement:this.options.placement});this.element.style.left=`${t.x}px`,this.element.style.top=`${t.y}px`,this.element.setAttribute("data-placement",t.placement),this.positionArrow(t.placement,t.arrowOffset)}positionArrow(t,e){const o=this.arrowEl;o.style.left="",o.style.top="",t==="top"||t==="bottom"?o.style.left=`calc(50% - var(--ci-hotspot-arrow-size) + ${e}px)`:o.style.top=`calc(50% - var(--ci-hotspot-arrow-size) + ${e}px)`}isVisible(){return this.visible}getHotspot(){return this.hotspot}destroy(){var t,e,o;this.clearHideTimer(),this.hoverCleanups.forEach(s=>s()),this.hoverCleanups=[],(t=this.markerEl)==null||t.removeAttribute("aria-describedby"),(e=this.markerEl)==null||e.removeAttribute("aria-controls"),(o=this.markerEl)==null||o.removeAttribute("aria-haspopup"),this.element.remove(),this.markerEl=null,this.containerEl=null}}class Mt{constructor(t,e,o){this.lastTouchEnd=0,this.initialPinchDistance=0,this.initialPinchScale=1,this.panStartX=0,this.panStartY=0,this.isPinching=!1,this.isPanning=!1,this.wasPinching=!1,this.cleanups=[],this.el=t,this.callbacks=e,this.initialPinchScale=o();const s=r=>this.handleTouchStart(r,o),c=r=>this.handleTouchMove(r),n=r=>this.handleTouchEnd(r);t.addEventListener("touchstart",s,{passive:!1}),t.addEventListener("touchmove",c,{passive:!1}),t.addEventListener("touchend",n,{passive:!0}),this.cleanups.push(()=>t.removeEventListener("touchstart",s),()=>t.removeEventListener("touchmove",c),()=>t.removeEventListener("touchend",n))}handleTouchStart(t,e){t.touches.length===2?(t.preventDefault(),this.isPinching=!0,this.wasPinching=!1,this.initialPinchDistance=this.getTouchDistance(t.touches),this.initialPinchScale=e()):t.touches.length===1&&(this.panStartX=t.touches[0].clientX,this.panStartY=t.touches[0].clientY,this.wasPinching=!1)}handleTouchMove(t){var e,o,s,c,n,r;if(t.touches.length===2&&this.isPinching){t.preventDefault();const a=this.getTouchDistance(t.touches),l=this.initialPinchScale*(a/this.initialPinchDistance),d=(t.touches[0].clientX+t.touches[1].clientX)/2,h=(t.touches[0].clientY+t.touches[1].clientY)/2;(o=(e=this.callbacks).onPinch)==null||o.call(e,l,d,h)}else if(t.touches.length===1&&!this.isPinching&&!this.wasPinching){this.isPanning||(this.isPanning=!0,(c=(s=this.callbacks).onPanStart)==null||c.call(s));const a=t.touches[0].clientX-this.panStartX,l=t.touches[0].clientY-this.panStartY;(r=(n=this.callbacks).onPan)==null||r.call(n,a,l)}}handleTouchEnd(t){var e,o,s,c;if(this.isPinching&&t.touches.length<2&&(this.isPinching=!1,this.wasPinching=!0,t.touches.length===1&&(this.panStartX=t.touches[0].clientX,this.panStartY=t.touches[0].clientY)),this.isPanning&&(this.isPanning=!1,(o=(e=this.callbacks).onPanEnd)==null||o.call(e)),t.changedTouches.length===1&&t.touches.length===0){if(this.wasPinching){this.wasPinching=!1,this.lastTouchEnd=0;return}const n=Date.now();if(n-this.lastTouchEnd<300){const r=t.changedTouches[0];(c=(s=this.callbacks).onDoubleTap)==null||c.call(s,r.clientX,r.clientY)}this.lastTouchEnd=n}}getTouchDistance(t){const e=t[0].clientX-t[1].clientX,o=t[0].clientY-t[1].clientY;return Math.sqrt(e*e+o*o)}destroy(){this.cleanups.forEach(t=>t()),this.cleanups=[]}}class Lt{constructor(t,e,o){this.zoom=1,this.panX=0,this.panY=0,this.enabled=!0,this.isDragging=!1,this.dragStartX=0,this.dragStartY=0,this.lastPanX=0,this.lastPanY=0,this.gestures=null,this.isGesturing=!1,this.gestureStartZoom=1,this.touchStartPanX=0,this.touchStartPanY=0,this.cleanups=[],this.viewport=t,this.container=e,this.options=o,this.bindEvents()}bindEvents(){const t=n=>{var p,u;if(!this.enabled)return;if(this.isGesturing){n.preventDefault();return}if(!n.ctrlKey){(u=(p=this.options).onScrollWithoutZoom)==null||u.call(p);return}n.preventDefault();const r=this.container.getBoundingClientRect(),a=n.clientX-r.left,l=n.clientY-r.top;let d=n.deltaY;n.deltaMode===1&&(d*=20);const h=-d*.01;this.setZoom(this.zoom+h,a,l)};this.container.addEventListener("wheel",t,{passive:!1}),this.cleanups.push(()=>this.container.removeEventListener("wheel",t)),this.bindSafariGestures();const e=n=>{if(!this.enabled)return;const r=this.container.getBoundingClientRect(),a=n.clientX-r.left,l=n.clientY-r.top;this.zoom>1?this.resetZoom():this.setZoom(2,a,l)};this.container.addEventListener("dblclick",e),this.cleanups.push(()=>this.container.removeEventListener("dblclick",e));const o=n=>{!this.enabled||this.zoom<=1||n.button===0&&(this.isDragging=!0,this.dragStartX=n.clientX,this.dragStartY=n.clientY,this.lastPanX=this.panX,this.lastPanY=this.panY,g(this.viewport,"ci-hotspot-viewport--dragging"),this.container.style.cursor="grabbing",n.preventDefault())},s=n=>{if(!this.isDragging)return;const r=(n.clientX-this.dragStartX)/this.zoom,a=(n.clientY-this.dragStartY)/this.zoom;this.panX=this.lastPanX+r,this.panY=this.lastPanY+a,this.clampPan(),this.applyTransform()},c=()=>{this.isDragging&&(this.isDragging=!1,w(this.viewport,"ci-hotspot-viewport--dragging"),this.container.style.cursor=this.zoom>1?"grab":"")};this.container.addEventListener("mousedown",o),document.addEventListener("mousemove",s),document.addEventListener("mouseup",c),this.cleanups.push(()=>this.container.removeEventListener("mousedown",o),()=>document.removeEventListener("mousemove",s),()=>document.removeEventListener("mouseup",c)),this.gestures=new Mt(this.container,{onPinch:(n,r,a)=>{const l=this.container.getBoundingClientRect();this.setZoom(n,r-l.left,a-l.top)},onPanStart:()=>{this.touchStartPanX=this.panX,this.touchStartPanY=this.panY},onPan:(n,r)=>{this.zoom<=1||(this.panX=this.touchStartPanX+n/this.zoom,this.panY=this.touchStartPanY+r/this.zoom,this.clampPan(),this.applyTransform())},onDoubleTap:(n,r)=>{const a=this.container.getBoundingClientRect();this.zoom>1?this.resetZoom():this.setZoom(2,n-a.left,r-a.top)}},()=>this.zoom)}bindSafariGestures(){if(typeof window>"u"||!("GestureEvent"in window))return;const t=s=>{s.preventDefault(),this.isGesturing=!0,this.gestureStartZoom=this.zoom},e=s=>{if(s.preventDefault(),!this.enabled)return;const c=s,n=this.container.getBoundingClientRect(),r=s,a=r.clientX!=null?r.clientX-n.left:n.width/2,l=r.clientY!=null?r.clientY-n.top:n.height/2;this.setZoom(this.gestureStartZoom*c.scale,a,l)},o=s=>{s.preventDefault(),this.isGesturing=!1};this.container.addEventListener("gesturestart",t),this.container.addEventListener("gesturechange",e),this.container.addEventListener("gestureend",o),this.cleanups.push(()=>this.container.removeEventListener("gesturestart",t),()=>this.container.removeEventListener("gesturechange",e),()=>this.container.removeEventListener("gestureend",o))}setZoom(t,e,o){var c,n;const s=this.zoom;if(this.zoom=Math.max(this.options.zoomMin,Math.min(this.options.zoomMax,t)),e!==void 0&&o!==void 0&&s!==this.zoom){const r=this.container.offsetWidth,a=this.container.offsetHeight,l=this.zoom/s,d=e/r,h=o/a;this.panX=this.panX-r*d*(l-1)/this.zoom,this.panY=this.panY-a*h*(l-1)/this.zoom}this.clampPan(),this.applyTransform(),this.updateCursor(),(n=(c=this.options).onZoom)==null||n.call(c,this.zoom)}getZoom(){return this.zoom}resetZoom(){var t,e;this.zoom=1,this.panX=0,this.panY=0,this.applyTransform(),this.updateCursor(),(e=(t=this.options).onZoom)==null||e.call(t,1)}pan(t,e){this.zoom<=1||(this.panX+=t/this.zoom,this.panY+=e/this.zoom,this.clampPan(),this.applyTransform())}enable(){this.enabled=!0}disable(){this.enabled=!1}clampPan(){const t=this.container.offsetWidth,e=this.container.offsetHeight,o=t*(this.zoom-1)/this.zoom,s=e*(this.zoom-1)/this.zoom;this.panX=Math.max(-o,Math.min(0,this.panX)),this.panY=Math.max(-s,Math.min(0,this.panY)),this.zoom<=1&&(this.panX=0,this.panY=0)}applyTransform(){this.viewport.style.transform=`scale(${this.zoom}) translate(${this.panX}px, ${this.panY}px)`,this.viewport.style.setProperty("--zoom",String(this.zoom))}updateCursor(){this.isDragging||(this.container.style.cursor=this.zoom>1?"grab":"")}destroy(){var t;this.cleanups.forEach(e=>e()),this.cleanups=[],(t=this.gestures)==null||t.destroy(),this.gestures=null,this.viewport.style.transform="",this.viewport.style.removeProperty("--zoom"),this.container.style.cursor=""}}function At(i,t,e){const o=e.zoomStep||.5,s=b("div","ci-hotspot-zoom-controls");s.dataset.position=e.position||"bottom-right";const c=b("button","ci-hotspot-zoom-in",{"aria-label":"Zoom in",type:"button"});c.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" x2="16.65" y1="21" y2="16.65"/><line x1="11" x2="11" y1="8" y2="14"/><line x1="8" x2="14" y1="11" y2="11"/></svg>';const n=b("button","ci-hotspot-zoom-out",{"aria-label":"Zoom out",type:"button"});n.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" x2="16.65" y1="21" y2="16.65"/><line x1="8" x2="14" y1="11" y2="11"/></svg>';const r=b("button","ci-hotspot-zoom-reset",{"aria-label":"Reset zoom",type:"button"});r.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>',s.appendChild(c),s.appendChild(n),s.appendChild(r),c.addEventListener("click",l=>{l.stopPropagation(),t.setZoom(t.getZoom()+o),a()}),n.addEventListener("click",l=>{l.stopPropagation(),t.setZoom(t.getZoom()-o),a()}),r.addEventListener("click",l=>{l.stopPropagation(),t.resetZoom(),a()});function a(){const l=t.getZoom();c.disabled=l>=e.zoomMax,n.disabled=l<=e.zoomMin,r.disabled=Math.abs(l-1)<.001}return i.appendChild(s),a(),{element:s,update:a,destroy:()=>s.remove()}}const Ft=typeof navigator<"u"&&/Mac|iPhone|iPad|iPod/i.test(navigator.userAgent),Rt=Ft?"⌘ Scroll or pinch to zoom":"Ctrl + scroll to zoom",Dt=1500;class It{constructor(t){this.hideTimer=null,this.el=document.createElement("div"),this.el.className="ci-hotspot-scroll-hint",this.el.textContent=Rt,this.el.setAttribute("aria-hidden","true"),t.appendChild(this.el)}show(){this.hideTimer!==null&&clearTimeout(this.hideTimer),this.el.classList.add("ci-hotspot-scroll-hint--visible"),this.hideTimer=setTimeout(()=>{this.el.classList.remove("ci-hotspot-scroll-hint--visible"),this.hideTimer=null},Dt)}destroy(){this.hideTimer!==null&&(clearTimeout(this.hideTimer),this.hideTimer=null),this.el.remove()}}const Zt="cloudimg.io",Ot="v7",I=100;function Yt(i,t=I){return Math.ceil(i/t)*t}function it(i,t=1,e=1,o=I){const s=i*t*e;return Yt(s,o)}function H(i,t,e,o=1,s=1){const c=t.domain||Zt,n=t.apiVersion||Ot,r=t.limitFactor||I,a=it(e,s,o,r),l=encodeURI(i);let d=`https://${t.token}.${c}/${n}/${l}?width=${a}`;return t.params&&(d+=`&${t.params}`),d}function Xt(i,t,e,o){const s=e.limitFactor||I;let c=0;const n=new ResizeObserver(r=>{for(const a of r){const l=a.contentRect.width;if(l===0)continue;const d=typeof window<"u"&&window.devicePixelRatio||1,h=it(l,d,o(),s);h!==c&&(c=h,i.src=H(t,e,l,o(),d))}});return{observer:n,destroy:()=>n.disconnect()}}const R=50,q=.5;class Nt{constructor(t){this.cleanups=[];const{container:e,getZoomPan:o,onEscape:s,onFullscreenToggle:c}=t,n=m(e,"keydown",r=>{const a=r.target;if(a.tagName==="INPUT"||a.tagName==="TEXTAREA"||a.tagName==="SELECT")return;const l=o();switch(r.key){case"Escape":s==null||s();break;case"ArrowUp":l&&l.getZoom()>1&&(r.preventDefault(),l.pan(0,R));break;case"ArrowDown":l&&l.getZoom()>1&&(r.preventDefault(),l.pan(0,-R));break;case"ArrowLeft":l&&l.getZoom()>1&&(r.preventDefault(),l.pan(R,0));break;case"ArrowRight":l&&l.getZoom()>1&&(r.preventDefault(),l.pan(-R,0));break;case"+":case"=":l&&(r.preventDefault(),l.setZoom(l.getZoom()+q));break;case"-":l&&(r.preventDefault(),l.setZoom(l.getZoom()-q));break;case"0":l&&(r.preventDefault(),l.resetZoom());break;case"f":c&&(r.preventDefault(),c());break}});this.cleanups.push(n)}destroy(){this.cleanups.forEach(t=>t()),this.cleanups=[]}}const $t='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function G(i){return Array.from(i.querySelectorAll($t))}function K(i,t){let e=!1,o=null;function s(){if(e)return;e=!0;const r=G(i);r.length!==0&&(r[0].focus(),o=m(i,"keydown",a=>{if(a.key!=="Tab")return;const l=G(i);if(l.length===0)return;const d=l[0],h=l[l.length-1];a.shiftKey&&document.activeElement===d?(a.preventDefault(),h.focus()):!a.shiftKey&&document.activeElement===h&&(a.preventDefault(),d.focus())}))}function c(){e&&(e=!1,o==null||o(),o=null,t.focus())}function n(){c()}return{activate:s,deactivate:c,destroy:n}}let E=null,D=0;function Wt(i){tt()&&(E||(E=b("div",void 0,{"aria-live":"polite","aria-atomic":"true",role:"status"}),E.style.cssText="position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0",document.body.appendChild(E)),E.textContent="",requestAnimationFrame(()=>{E&&(E.textContent=i)}))}function U(){D++}function jt(){D=Math.max(0,D-1),D===0&&E&&(E.remove(),E=null)}const J='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" x2="14" y1="3" y2="10"/><line x1="3" x2="10" y1="21" y2="14"/></svg>',Vt='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/><line x1="14" x2="21" y1="10" y2="3"/><line x1="3" x2="10" y1="21" y2="14"/></svg>';function Bt(){return!!(document.fullscreenEnabled||document.webkitFullscreenEnabled)}function _t(){return document.fullscreenElement||document.webkitFullscreenElement||null}function Q(i){return i.requestFullscreen?i.requestFullscreen():i.webkitRequestFullscreen?(i.webkitRequestFullscreen(),Promise.resolve()):Promise.reject(new Error("Fullscreen API not supported"))}function O(){return document.exitFullscreen?document.exitFullscreen():document.webkitExitFullscreen?(document.webkitExitFullscreen(),Promise.resolve()):Promise.reject(new Error("Fullscreen API not supported"))}function qt(i,t={}){if(!Bt())return null;const e=b("button","ci-hotspot-fullscreen-btn",{"aria-label":"Enter fullscreen","aria-pressed":"false",type:"button"});e.innerHTML=J;const o=[];function s(){return _t()===i}function c(){var u;const p=s();e.innerHTML=p?Vt:J,e.setAttribute("aria-label",p?"Exit fullscreen":"Enter fullscreen"),e.setAttribute("aria-pressed",String(p)),p?g(i,"ci-hotspot-container--fullscreen"):w(i,"ci-hotspot-container--fullscreen"),(u=t.onChange)==null||u.call(t,p)}function n(){s()?O().catch(()=>{}):Q(i).catch(()=>{})}function r(){s()||Q(i).catch(()=>{})}function a(){s()&&O().catch(()=>{})}const l=m(document,"fullscreenchange",c);o.push(l),document.addEventListener("webkitfullscreenchange",c),o.push(()=>document.removeEventListener("webkitfullscreenchange",c));const d=m(e,"click",p=>{p.stopPropagation(),n()});o.push(d),i.appendChild(e);function h(){s()&&O().catch(()=>{}),w(i,"ci-hotspot-container--fullscreen"),o.forEach(p=>p()),o.length=0,e.remove()}return{element:e,isFullscreen:s,toggle:n,enter:r,exit:a,destroy:h}}const Gt='.ci-hotspot-container{--ci-hotspot-marker-size: 24px;--ci-hotspot-marker-color: #ffffff;--ci-hotspot-marker-bg: rgba(0, 0, 0, .6);--ci-hotspot-marker-border-width: 2px;--ci-hotspot-marker-border-color: rgba(255, 255, 255, .8);--ci-hotspot-marker-border: var(--ci-hotspot-marker-border-width) solid var(--ci-hotspot-marker-border-color);--ci-hotspot-marker-border-radius: 50%;--ci-hotspot-marker-shadow: 0 2px 8px rgba(0, 0, 0, .3);--ci-hotspot-pulse-color: rgba(0, 0, 0, .2);--ci-hotspot-pulse-size: 40px;--ci-hotspot-pulse-duration: 1.8s;--ci-hotspot-popover-bg: #ffffff;--ci-hotspot-popover-color: #1a1a1a;--ci-hotspot-popover-border: 1px solid rgba(0, 0, 0, .1);--ci-hotspot-popover-border-radius: 12px;--ci-hotspot-popover-shadow: 0 8px 32px rgba(0, 0, 0, .12);--ci-hotspot-popover-padding: 16px;--ci-hotspot-popover-max-width: 320px;--ci-hotspot-popover-max-height: 400px;--ci-hotspot-popover-font-family: inherit;--ci-hotspot-popover-font-size: 14px;--ci-hotspot-popover-line-height: 1.5;--ci-hotspot-popover-z-index: 1000;--ci-hotspot-arrow-size: 8px;--ci-hotspot-arrow-color: var(--ci-hotspot-popover-bg);--ci-hotspot-title-font-size: 16px;--ci-hotspot-title-font-weight: 600;--ci-hotspot-title-color: #1a1a1a;--ci-hotspot-price-color: #2d8c3c;--ci-hotspot-price-font-size: 18px;--ci-hotspot-price-font-weight: 700;--ci-hotspot-description-color: #666666;--ci-hotspot-cta-bg: #0058a3;--ci-hotspot-cta-color: #ffffff;--ci-hotspot-cta-border-radius: 8px;--ci-hotspot-cta-padding: 8px 16px;--ci-hotspot-hover-transition: .2s ease;--ci-hotspot-popover-transition: .3s ease;--ci-hotspot-scene-transition-duration: .4s;--ci-hotspot-zoom-controls-bg: rgba(255, 255, 255, .9);--ci-hotspot-zoom-controls-color: #333333;--ci-hotspot-zoom-controls-border-radius: 8px;--ci-hotspot-zoom-controls-shadow: 0 2px 8px rgba(0, 0, 0, .15)}.ci-hotspot-container *,.ci-hotspot-container *:before,.ci-hotspot-container *:after{box-sizing:border-box}.ci-hotspot-container{position:relative;overflow:hidden;width:100%;line-height:0;user-select:none;-webkit-user-select:none}.ci-hotspot-viewport{position:relative;width:100%;transform-origin:0 0;will-change:transform;transition:transform .3s ease}.ci-hotspot-viewport--dragging{transition:none}.ci-hotspot-image{display:block;width:100%;height:auto;pointer-events:none}.ci-hotspot-markers{position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none}.ci-hotspot-marker{position:absolute;width:var(--ci-hotspot-marker-size);height:var(--ci-hotspot-marker-size);padding:0;border:var(--ci-hotspot-marker-border);border-radius:var(--ci-hotspot-marker-border-radius);background:var(--ci-hotspot-marker-bg);color:var(--ci-hotspot-marker-color);box-shadow:var(--ci-hotspot-marker-shadow);cursor:pointer;pointer-events:auto;transform:translate(-50%,-50%) scale(calc(1 / var(--zoom, 1)));transition:transform var(--ci-hotspot-hover-transition),box-shadow var(--ci-hotspot-hover-transition);z-index:1;display:flex;align-items:center;justify-content:center;font-size:12px;line-height:1;outline:none}.ci-hotspot-marker:hover{transform:translate(-50%,-50%) scale(calc(1.2 / var(--zoom, 1)));box-shadow:0 4px 12px #0006}.ci-hotspot-marker:focus-visible{outline:3px solid var(--ci-hotspot-focus-ring, #4A90D9);outline-offset:2px}.ci-hotspot-marker--active{transform:translate(-50%,-50%) scale(calc(1.2 / var(--zoom, 1)));z-index:2}.ci-hotspot-marker--hidden{display:none}.ci-hotspot-marker--pulse:before{content:"";position:absolute;top:50%;left:50%;width:var(--ci-hotspot-pulse-size);height:var(--ci-hotspot-pulse-size);border-radius:50%;background:var(--ci-hotspot-pulse-color);transform:translate(-50%,-50%) scale(1);animation:ci-hotspot-pulse var(--ci-hotspot-pulse-duration) ease-out infinite;pointer-events:none}@keyframes ci-hotspot-pulse{0%{transform:translate(-50%,-50%) scale(1);opacity:1}to{transform:translate(-50%,-50%) scale(1.8);opacity:0}}.ci-hotspot-marker--pulse{animation:ci-hotspot-breathe 2.4s ease-in-out infinite}.ci-hotspot-marker--pulse:hover,.ci-hotspot-marker--pulse.ci-hotspot-marker--active{animation:none}@keyframes ci-hotspot-breathe{0%,to{transform:translate(-50%,-50%) scale(calc(1 / var(--zoom, 1)))}50%{transform:translate(-50%,-50%) scale(calc(1.15 / var(--zoom, 1)))}}.ci-hotspot-popover{position:absolute;z-index:var(--ci-hotspot-popover-z-index);max-width:var(--ci-hotspot-popover-max-width);background:var(--ci-hotspot-popover-bg);color:var(--ci-hotspot-popover-color);border:var(--ci-hotspot-popover-border);border-radius:var(--ci-hotspot-popover-border-radius);box-shadow:var(--ci-hotspot-popover-shadow);font-family:var(--ci-hotspot-popover-font-family);font-size:var(--ci-hotspot-popover-font-size);line-height:var(--ci-hotspot-popover-line-height);opacity:0;pointer-events:none;transform:translateY(4px);transition:opacity var(--ci-hotspot-popover-transition),transform var(--ci-hotspot-popover-transition)}.ci-hotspot-popover--visible{opacity:1;pointer-events:auto;transform:translateY(0);animation:ci-hotspot-popover-in var(--ci-hotspot-popover-transition) forwards}@keyframes ci-hotspot-popover-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.ci-hotspot-popover-arrow{position:absolute;width:calc(var(--ci-hotspot-arrow-size) * 2);height:calc(var(--ci-hotspot-arrow-size) * 2);background:var(--ci-hotspot-arrow-color);transform:rotate(45deg);pointer-events:none}.ci-hotspot-popover[data-placement=top] .ci-hotspot-popover-arrow{bottom:calc(var(--ci-hotspot-arrow-size) * -1)}.ci-hotspot-popover[data-placement=bottom] .ci-hotspot-popover-arrow{top:calc(var(--ci-hotspot-arrow-size) * -1)}.ci-hotspot-popover[data-placement=left] .ci-hotspot-popover-arrow{right:calc(var(--ci-hotspot-arrow-size) * -1)}.ci-hotspot-popover[data-placement=right] .ci-hotspot-popover-arrow{left:calc(var(--ci-hotspot-arrow-size) * -1)}.ci-hotspot-popover-content{padding:var(--ci-hotspot-popover-padding);max-height:var(--ci-hotspot-popover-max-height);overflow-y:auto;overflow-x:hidden;border-radius:var(--ci-hotspot-popover-border-radius)}.ci-hotspot-popover-image{display:block;width:100%;height:auto;border-radius:calc(var(--ci-hotspot-popover-border-radius) - 4px) calc(var(--ci-hotspot-popover-border-radius) - 4px) 0 0;margin:calc(var(--ci-hotspot-popover-padding) * -1);margin-bottom:12px;width:calc(100% + var(--ci-hotspot-popover-padding) * 2)}.ci-hotspot-popover-body{line-height:1.5}.ci-hotspot-popover-title{margin:0 0 4px;font-size:var(--ci-hotspot-title-font-size);font-weight:var(--ci-hotspot-title-font-weight);color:var(--ci-hotspot-title-color)}.ci-hotspot-popover-price{display:inline-block;margin-bottom:8px;font-size:var(--ci-hotspot-price-font-size);font-weight:var(--ci-hotspot-price-font-weight);color:var(--ci-hotspot-price-color)}.ci-hotspot-popover-description{margin:0 0 12px;color:var(--ci-hotspot-description-color)}.ci-hotspot-popover-cta{display:inline-block;padding:var(--ci-hotspot-cta-padding);background:var(--ci-hotspot-cta-bg);color:var(--ci-hotspot-cta-color);border-radius:var(--ci-hotspot-cta-border-radius);text-decoration:none;font-weight:600;font-size:14px;transition:opacity .2s ease}.ci-hotspot-popover-cta:hover{opacity:.9}.ci-hotspot-popover-cta:focus-visible{outline:3px solid var(--ci-hotspot-focus-ring, #4A90D9);outline-offset:2px}.ci-hotspot-zoom-controls{position:absolute;display:flex;gap:2px;background:var(--ci-hotspot-zoom-controls-bg);border-radius:var(--ci-hotspot-zoom-controls-border-radius);box-shadow:var(--ci-hotspot-zoom-controls-shadow);z-index:10;overflow:hidden}.ci-hotspot-zoom-controls button{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;background:transparent;color:var(--ci-hotspot-zoom-controls-color);cursor:pointer;font-size:18px;line-height:1;padding:0;transition:background .15s ease}.ci-hotspot-zoom-controls button:hover{background:#0000000d}.ci-hotspot-zoom-controls button:disabled{opacity:.3;cursor:default}.ci-hotspot-zoom-controls button:disabled:hover{background:transparent}.ci-hotspot-zoom-controls button svg{width:18px;height:18px}.ci-hotspot-zoom-controls button:focus-visible{outline:3px solid var(--ci-hotspot-focus-ring, #4A90D9);outline-offset:-3px}.ci-hotspot-zoom-controls[data-position=bottom-right]{bottom:16px;right:16px}.ci-hotspot-zoom-controls[data-position=bottom-left]{bottom:16px;left:16px}.ci-hotspot-zoom-controls[data-position=bottom-center]{bottom:16px;left:50%;transform:translate(-50%)}.ci-hotspot-zoom-controls[data-position=top-right]{top:16px;right:16px}.ci-hotspot-zoom-controls[data-position=top-left]{top:16px;left:16px}.ci-hotspot-zoom-controls[data-position=top-center]{top:16px;left:50%;transform:translate(-50%)}.ci-hotspot-cluster{position:absolute;display:flex;align-items:center;justify-content:center;min-width:32px;height:32px;padding:0 8px;border-radius:16px;border:2px solid rgba(255,255,255,.8);background:var(--ci-hotspot-marker-bg);color:var(--ci-hotspot-marker-color);font-size:13px;font-weight:700;cursor:pointer;pointer-events:auto;transform:translate(-50%,-50%) scale(calc(1 / var(--zoom, 1)));box-shadow:var(--ci-hotspot-marker-shadow)}.ci-hotspot-loading .ci-hotspot-image{opacity:0;transition:opacity .3s ease}.ci-hotspot-loading .ci-hotspot-markers{display:none}.ci-hotspot-theme-dark{--ci-hotspot-marker-bg: rgba(255, 255, 255, .8);--ci-hotspot-marker-color: #1a1a1a;--ci-hotspot-marker-border-color: rgba(255, 255, 255, .4);--ci-hotspot-pulse-color: rgba(255, 255, 255, .2);--ci-hotspot-popover-bg: #1a1a1a;--ci-hotspot-popover-color: #f0f0f0;--ci-hotspot-popover-border: 1px solid rgba(255, 255, 255, .1);--ci-hotspot-popover-shadow: 0 8px 32px rgba(0, 0, 0, .4);--ci-hotspot-title-color: #f0f0f0;--ci-hotspot-description-color: #aaaaaa;--ci-hotspot-zoom-controls-bg: rgba(30, 30, 30, .9);--ci-hotspot-zoom-controls-color: #f0f0f0}.ci-hotspot-marker-inverted{--ci-hotspot-marker-bg: rgba(255, 255, 255, .8);--ci-hotspot-marker-color: #1a1a1a;--ci-hotspot-marker-border-color: rgba(0, 0, 0, .3);--ci-hotspot-marker-shadow: 0 2px 8px rgba(0, 0, 0, .15);--ci-hotspot-pulse-color: rgba(0, 0, 0, .15)}.ci-hotspot-theme-dark.ci-hotspot-marker-inverted{--ci-hotspot-marker-bg: rgba(0, 0, 0, .6);--ci-hotspot-marker-color: #ffffff;--ci-hotspot-marker-border-color: rgba(255, 255, 255, .3);--ci-hotspot-pulse-color: rgba(255, 255, 255, .15)}.ci-hotspot-scroll-hint{position:absolute;bottom:16px;left:50%;transform:translate(-50%) translateY(4px);padding:8px 16px;border-radius:20px;background:#000000b3;color:#fff;font-size:13px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;line-height:1;white-space:nowrap;z-index:100;opacity:0;pointer-events:none;transition:opacity .3s ease,transform .3s ease}.ci-hotspot-scroll-hint--visible{opacity:1;transform:translate(-50%) translateY(0)}.ci-hotspot-container--fixed-ratio .ci-hotspot-viewport{overflow:hidden}.ci-hotspot-container--fixed-ratio .ci-hotspot-image{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:contain}.ci-hotspot-container--fixed-ratio .ci-hotspot-markers{z-index:1}.ci-hotspot-scene-incoming{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:contain;pointer-events:none;z-index:0}.ci-hotspot-scene-fade-in{animation:ci-hotspot-scene-fade-in var(--ci-hotspot-scene-transition-duration) ease forwards;z-index:1}.ci-hotspot-scene-fade-out{animation:ci-hotspot-scene-fade-out var(--ci-hotspot-scene-transition-duration) ease forwards}@keyframes ci-hotspot-scene-fade-in{0%{opacity:0}to{opacity:1}}@keyframes ci-hotspot-scene-fade-out{0%{opacity:1}to{opacity:0}}.ci-hotspot-scene-slide-in{animation:ci-hotspot-scene-slide-in var(--ci-hotspot-scene-transition-duration) ease forwards;z-index:1}.ci-hotspot-scene-slide-out{animation:ci-hotspot-scene-slide-out var(--ci-hotspot-scene-transition-duration) ease forwards}@keyframes ci-hotspot-scene-slide-in{0%{transform:translate(100%)}to{transform:translate(0)}}@keyframes ci-hotspot-scene-slide-out{0%{transform:translate(0)}to{transform:translate(-100%)}}.ci-hotspot-scene-slide-in-reverse{animation:ci-hotspot-scene-slide-in-reverse var(--ci-hotspot-scene-transition-duration) ease forwards;z-index:1}.ci-hotspot-scene-slide-out-reverse{animation:ci-hotspot-scene-slide-out-reverse var(--ci-hotspot-scene-transition-duration) ease forwards}@keyframes ci-hotspot-scene-slide-in-reverse{0%{transform:translate(-100%)}to{transform:translate(0)}}@keyframes ci-hotspot-scene-slide-out-reverse{0%{transform:translate(0)}to{transform:translate(100%)}}.ci-hotspot-marker--navigate{--ci-hotspot-marker-bg: rgba(0, 88, 163, .7)}.ci-hotspot-navigate-icon{width:75%;height:75%;display:block}.ci-hotspot-scene-transitioning .ci-hotspot-markers{opacity:0;pointer-events:none;transition:opacity .1s ease}.ci-hotspot-scene-loading:after{content:"";position:absolute;top:50%;left:50%;width:32px;height:32px;margin:-16px 0 0 -16px;border:3px solid rgba(0,0,0,.1);border-top-color:#00000080;border-radius:50%;animation:ci-hotspot-spin .6s linear infinite;z-index:10;pointer-events:none}.ci-hotspot-theme-dark .ci-hotspot-scene-loading:after,.ci-hotspot-scene-loading.ci-hotspot-theme-dark:after{border-color:#ffffff1a;border-top-color:#ffffff80}@keyframes ci-hotspot-spin{to{transform:rotate(360deg)}}.ci-hotspot-fullscreen-btn{position:absolute;top:16px;right:16px;display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;border-radius:var(--ci-hotspot-zoom-controls-border-radius);background:var(--ci-hotspot-zoom-controls-bg);color:var(--ci-hotspot-zoom-controls-color);box-shadow:var(--ci-hotspot-zoom-controls-shadow);cursor:pointer;padding:0;z-index:10;transition:background .15s ease}.ci-hotspot-fullscreen-btn:hover{background:#0000000d}.ci-hotspot-fullscreen-btn svg{width:18px;height:18px}.ci-hotspot-fullscreen-btn:focus-visible{outline:3px solid var(--ci-hotspot-focus-ring, #4A90D9);outline-offset:2px}.ci-hotspot-container--fullscreen{background:#000;width:100vw;height:100vh;display:flex;align-items:center;justify-content:center}.ci-hotspot-container--fullscreen .ci-hotspot-viewport{width:fit-content;max-width:100%;max-height:100%}.ci-hotspot-container--fullscreen .ci-hotspot-image{max-height:100vh;width:auto;max-width:100%;object-fit:contain}.ci-hotspot-container--fullscreen.ci-hotspot-container--fixed-ratio .ci-hotspot-viewport{width:100%;height:100%}.ci-hotspot-container--fullscreen.ci-hotspot-container--fixed-ratio .ci-hotspot-image{width:100%;height:100%;max-height:none}.ci-hotspot-theme-dark .ci-hotspot-fullscreen-btn,.ci-hotspot-container--fullscreen .ci-hotspot-fullscreen-btn{background:#1e1e1ee6;color:#f0f0f0}.ci-hotspot-theme-dark .ci-hotspot-fullscreen-btn:hover,.ci-hotspot-container--fullscreen .ci-hotspot-fullscreen-btn:hover{background:#ffffff1a}@media (prefers-reduced-motion: reduce){.ci-hotspot-marker,.ci-hotspot-marker:before,.ci-hotspot-popover{animation:none!important;transition-duration:.01ms!important}.ci-hotspot-viewport,.ci-hotspot-scroll-hint{transition-duration:.01ms!important}.ci-hotspot-scene-fade-in,.ci-hotspot-scene-fade-out,.ci-hotspot-scene-slide-in,.ci-hotspot-scene-slide-out,.ci-hotspot-scene-slide-in-reverse,.ci-hotspot-scene-slide-out-reverse{animation-duration:.01ms!important}.ci-hotspot-scene-transitioning .ci-hotspot-markers{transition-duration:.01ms!important}.ci-hotspot-scene-loading:after{animation-duration:.01ms!important}.ci-hotspot-fullscreen-btn{transition-duration:.01ms!important}}',P=class P{constructor(t,e){this.markers=new Map,this.popovers=new Map,this.normalizedHotspots=new Map,this.scrollHint=null,this.zoomPan=null,this.zoomControls=null,this.cloudimageHandler=null,this.resizeObserver=null,this.keyboardHandler=null,this.fullscreenControl=null,this.focusTraps=new Map,this.cleanups=[],this.hotspotCleanups=new Map,this.imageLoaded=!1,this.destroyed=!1,this.scenesMap=new Map,this.isTransitioning=!1,this.activeTimers=new Set,this.sceneHotspotOverrides=new Map,this.preloadedScenes=new Set,this.rootEl=ht(t),this.config=$(e),W(this.config),this.config.scenes&&this.config.scenes.length>0&&this.initScenes(),U(),pt(Gt),this.buildDOM(),this.applyTheme(),this.setupImage(),this.initHotspots(),this.config.zoom&&this.initZoom(),this.initKeyboard(),this.initFullscreen(),this.setupResponsive()}static autoInit(t){const o=(t||document).querySelectorAll("[data-ci-hotspot-src], [data-ci-hotspot-scenes]"),s=[];return o.forEach(c=>{const n=ct(c);(n.src||n.scenes)&&s.push(new P(c,n))}),s}buildDOM(){this.containerEl=b("div","ci-hotspot-container"),this.viewportEl=b("div","ci-hotspot-viewport"),this.imgEl=b("img","ci-hotspot-image",{alt:this.config.alt||"",draggable:"false"}),this.markersEl=b("div","ci-hotspot-markers"),this.viewportEl.appendChild(this.imgEl),this.viewportEl.appendChild(this.markersEl),this.containerEl.appendChild(this.viewportEl),this.containerEl.setAttribute("role","group"),this.containerEl.setAttribute("aria-label",this.config.alt||"Image with hotspots"),this.config.sceneAspectRatio&&(g(this.containerEl,"ci-hotspot-container--fixed-ratio"),this.viewportEl.style.aspectRatio=this.config.sceneAspectRatio),this.rootEl.innerHTML="",this.rootEl.appendChild(this.containerEl),this.config.lazyLoad&&g(this.containerEl,"ci-hotspot-loading")}applyTheme(){this.config.theme==="dark"&&g(this.containerEl,"ci-hotspot-theme-dark"),this.config.invertMarkerTheme&&g(this.containerEl,"ci-hotspot-marker-inverted")}setupImage(){const t=()=>{w(this.containerEl,"ci-hotspot-loading"),this.imageLoaded=!0,this.renormalizePixelCoordinates(),this.syncMarkersToImage(),this.showLoadTriggerPopovers()};if(this.imgEl.addEventListener("load",t),this.cleanups.push(()=>this.imgEl.removeEventListener("load",t)),this.config.lazyLoad&&typeof IntersectionObserver<"u"){const e=new IntersectionObserver(o=>{var s;(s=o[0])!=null&&s.isIntersecting&&(this.loadImage(),e.disconnect())},{threshold:.1});e.observe(this.containerEl),this.cleanups.push(()=>e.disconnect())}else this.loadImage()}loadImage(){var t,e;if((t=this.config.cloudimage)!=null&&t.token){const o=this.containerEl.offsetWidth||300,s=window.devicePixelRatio||1,c=((e=this.zoomPan)==null?void 0:e.getZoom())||1;this.imgEl.src=H(this.config.src,this.config.cloudimage,o,c,s),this.cloudimageHandler=Xt(this.imgEl,this.config.src,this.config.cloudimage,()=>{var n;return((n=this.zoomPan)==null?void 0:n.getZoom())||1}),this.cloudimageHandler.observer.observe(this.containerEl),this.cleanups.push(()=>{var n;return(n=this.cloudimageHandler)==null?void 0:n.destroy()})}else this.imgEl.src=this.config.src}initHotspots(){for(const t of this.config.hotspots)this.addHotspotInternal(t)}addHotspotInternal(t){var d,h;if(this.markers.has(t.id)){const p=this.markers.get(t.id);A(p),this.markers.delete(t.id),(d=this.popovers.get(t.id))==null||d.destroy(),this.popovers.delete(t.id);const u=this.hotspotCleanups.get(t.id);u&&(u.forEach(v=>v()),this.hotspotCleanups.delete(t.id));const k=this.focusTraps.get(t.id);k&&(k.destroy(),this.focusTraps.delete(t.id))}const{x:e,y:o}=B(t.x,t.y,this.imgEl.naturalWidth||1e3,this.imgEl.naturalHeight||1e3),s={...t,x:e,y:o};this.normalizedHotspots.set(t.id,s);const c=this.config.pulse!==!1,n=dt(s,c);this.markers.set(t.id,n),this.markersEl.appendChild(n);const r=t.trigger||this.config.trigger||"hover";if(t.navigateTo){const p=this.enrichNavigateHotspot(t);if(!!(p.data||p.content||this.config.renderPopover)){const k=t.placement||this.config.placement||"top",v=new _(p,{placement:k,triggerMode:"hover",renderFn:this.config.renderPopover,onOpen:this.config.onOpen,onClose:this.config.onClose});this.popovers.set(t.id,v),v.mount(this.containerEl,n),this.bindNavigateTrigger(t,n,v)}else this.bindNavigateTrigger(t,n);return}const a=t.placement||this.config.placement||"top",l=new _(t,{placement:a,triggerMode:r,renderFn:this.config.renderPopover,onOpen:this.config.onOpen,onClose:this.config.onClose});this.popovers.set(t.id,l),l.mount(this.containerEl,n),this.bindTrigger(t,n,l,r),r==="load"&&this.imageLoaded&&(this.closeAll(),l.show(),f(n,!0),this.ensureFocusTrap(t.id,l.element,n),(h=this.focusTraps.get(t.id))==null||h.activate())}enrichNavigateHotspot(t){if(t.data||t.content)return t;const e=this.scenesMap.get(t.navigateTo);return e?{...t,data:{title:t.label||e.alt||e.id}}:t}bindNavigateTrigger(t,e,o){if(g(e,"ci-hotspot-marker--navigate"),e.innerHTML=P.NAVIGATE_ARROW_SVG,t.arrowDirection!=null){const r=e.querySelector("svg");r&&(r.style.transform=`rotate(${t.arrowDirection}deg)`)}const s=t.label||t.navigateTo;if(e.setAttribute("aria-label",`Navigate to ${s}`),e.setAttribute("aria-roledescription","navigation hotspot"),o){const r=m(e,"mouseenter",()=>{this.preloadSceneImage(t.navigateTo),o.clearHideTimer(),o.show(),f(e,!0)}),a=m(e,"mouseleave",()=>{o.scheduleHide(200),this.trackedTimeout(()=>{o.isVisible()||f(e,!1)},250)}),l=m(e,"focus",()=>{this.preloadSceneImage(t.navigateTo),o.clearHideTimer(),o.show(),f(e,!0)}),d=m(e,"blur",()=>{o.scheduleHide(200),this.trackedTimeout(()=>{o.isVisible()||f(e,!1)},250)});this.addHotspotCleanups(t.id,r,a,l,d)}else{const r=m(e,"mouseenter",()=>{this.preloadSceneImage(t.navigateTo)}),a=m(e,"focus",()=>{this.preloadSceneImage(t.navigateTo)});this.addHotspotCleanups(t.id,r,a)}const c=m(e,"click",r=>{var a,l,d;r.stopPropagation(),o==null||o.hide(),f(e,!1),(l=(a=this.config).onClick)==null||l.call(a,r,t),(d=t.onClick)==null||d.call(t,r,t),this.goToScene(t.navigateTo)}),n=m(e,"keydown",r=>{var a,l,d;(r.key==="Enter"||r.key===" ")&&(r.preventDefault(),o==null||o.hide(),f(e,!1),(l=(a=this.config).onClick)==null||l.call(a,r,t),(d=t.onClick)==null||d.call(t,r,t),this.goToScene(t.navigateTo))});this.addHotspotCleanups(t.id,c,n)}bindTrigger(t,e,o,s){s==="hover"?this.bindHoverTrigger(t,e,o):(s==="click"||s==="load")&&this.bindClickTrigger(t,e,o),this.bindKeyboardTrigger(t,e,o,s)}bindHoverTrigger(t,e,o){const s=m(e,"mouseenter",()=>{o.clearHideTimer(),o.show(),f(e,!0)}),c=m(e,"mouseleave",()=>{o.scheduleHide(200),this.trackedTimeout(()=>{o.isVisible()||f(e,!1)},250)});this.addHotspotCleanups(t.id,s,c)}bindClickTrigger(t,e,o){const s=K(o.element,e);this.focusTraps.set(t.id,s);const c=m(e,"click",r=>{var a,l,d;r.stopPropagation(),(l=(a=this.config).onClick)==null||l.call(a,r,t),(d=t.onClick)==null||d.call(t,r,t),o.isVisible()?(o.hide(),f(e,!1),s.deactivate()):(this.closeAll(),o.show(),f(e,!0),s.activate())}),n=m(document,"click",r=>{o.isVisible()&&!t.keepOpen&&!o.element.contains(r.target)&&!e.contains(r.target)&&(o.hide(),f(e,!1),s.deactivate())});this.addHotspotCleanups(t.id,c,n)}bindKeyboardTrigger(t,e,o,s){const c=m(e,"focus",()=>{s==="hover"&&(o.clearHideTimer(),o.show(),f(e,!0))}),n=m(e,"blur",()=>{s==="hover"&&(o.scheduleHide(200),this.trackedTimeout(()=>{o.isVisible()||f(e,!1)},250))}),r=m(e,"keydown",a=>{var l,d,h,p,u;a.key==="Enter"||a.key===" "?(a.preventDefault(),(d=(l=this.config).onClick)==null||d.call(l,a,t),o.isVisible()?(o.hide(),f(e,!1),(h=this.focusTraps.get(t.id))==null||h.deactivate()):(this.closeAll(),o.show(),f(e,!0),this.ensureFocusTrap(t.id,o.element,e),(p=this.focusTraps.get(t.id))==null||p.activate())):a.key==="Escape"&&o.isVisible()&&(o.hide(),f(e,!1),(u=this.focusTraps.get(t.id))==null||u.deactivate(),e.focus())});this.addHotspotCleanups(t.id,c,n,r)}renormalizePixelCoordinates(){const t=this.imgEl.naturalWidth,e=this.imgEl.naturalHeight;if(!(!t||!e)){for(const o of this.config.hotspots)if(typeof o.x=="number"||typeof o.y=="number"){const{x:s,y:c}=B(o.x,o.y,t,e),n=this.markers.get(o.id);n&&(n.style.left=`${s}%`,n.style.top=`${c}%`);const r=this.normalizedHotspots.get(o.id);r&&(r.x=s,r.y=c)}}}showLoadTriggerPopovers(){var t;for(const[e,o]of this.popovers){const s=this.normalizedHotspots.get(e);if(((s==null?void 0:s.trigger)||this.config.trigger||"hover")==="load"&&!o.isVisible()){o.show();const n=this.markers.get(e);n&&(f(n,!0),this.ensureFocusTrap(e,o.element,n),(t=this.focusTraps.get(e))==null||t.activate());break}}}initZoom(){this.config.scrollHint!==!1&&(this.scrollHint=new It(this.containerEl)),this.zoomPan=new Lt(this.viewportEl,this.containerEl,{zoomMin:this.config.zoomMin||1,zoomMax:this.config.zoomMax||4,onZoom:t=>{var e,o,s;(o=(e=this.config).onZoom)==null||o.call(e,t),(s=this.zoomControls)==null||s.update();for(const[,c]of this.popovers)c.isVisible()&&c.updatePosition()},onScrollWithoutZoom:()=>{var t;return(t=this.scrollHint)==null?void 0:t.show()}}),this.config.zoomControls!==!1&&(this.zoomControls=At(this.containerEl,this.zoomPan,{zoomMin:this.config.zoomMin||1,zoomMax:this.config.zoomMax||4,position:this.config.zoomControlsPosition}))}setupResponsive(){if(typeof ResizeObserver>"u")return;let t=0;this.resizeObserver=new ResizeObserver(()=>{t||(t=requestAnimationFrame(()=>{if(t=0,this.destroyed)return;this.syncMarkersToImage();const e=this.containerEl.offsetWidth;for(const[o,s]of this.normalizedHotspots)if(s.responsive){const c=this.markers.get(o);if(!c)continue;const n=s.responsive.maxWidth&&e>s.responsive.maxWidth||s.responsive.minWidth&&e<s.responsive.minWidth;gt(c,!!n)}}))}),this.cleanups.push(()=>{t&&cancelAnimationFrame(t)}),this.resizeObserver.observe(this.containerEl),this.cleanups.push(()=>{var e;return(e=this.resizeObserver)==null?void 0:e.disconnect()})}syncMarkersToImage(){if(!this.config.sceneAspectRatio)return;const t=this.viewportEl.offsetWidth,e=this.viewportEl.offsetHeight,o=this.imgEl.naturalWidth,s=this.imgEl.naturalHeight;if(!t||!e||!o||!s)return;const c=t/e,n=o/s;let r,a,l,d;n>c?(r=t,a=t/n,l=0,d=(e-a)/2):(a=e,r=e*n,l=(t-r)/2,d=0),this.markersEl.style.left=`${l}px`,this.markersEl.style.top=`${d}px`,this.markersEl.style.width=`${r}px`,this.markersEl.style.height=`${a}px`,this.markersEl.style.right="auto",this.markersEl.style.bottom="auto"}initScenes(){for(const o of this.config.scenes)this.scenesMap.set(o.id,o);const t=this.config.initialScene||this.config.scenes[0].id,e=this.scenesMap.get(t);this.config.src=e.src,this.config.alt=e.alt||"",this.config.hotspots=[...e.hotspots],this.currentSceneId=t}initKeyboard(){this.keyboardHandler=new Nt({container:this.containerEl,getZoomPan:()=>this.zoomPan,onEscape:()=>{var t;if((t=this.fullscreenControl)!=null&&t.isFullscreen()){this.fullscreenControl.exit();return}this.closeAll()},onFullscreenToggle:()=>{var t;(t=this.fullscreenControl)==null||t.toggle()}})}initFullscreen(){this.config.fullscreenButton!==!1&&(this.fullscreenControl=qt(this.containerEl,{onChange:t=>{var e,o;requestAnimationFrame(()=>{for(const[,s]of this.popovers)s.isVisible()&&s.updatePosition()}),(o=(e=this.config).onFullscreenChange)==null||o.call(e,t)}}))}preloadSceneImage(t){var s;if(this.preloadedScenes.has(t))return;const e=this.scenesMap.get(t);if(!e)return;this.preloadedScenes.add(t);const o=new Image;if((s=this.config.cloudimage)!=null&&s.token){const c=this.containerEl.offsetWidth||300,n=typeof window<"u"&&window.devicePixelRatio||1;o.src=H(e.src,this.config.cloudimage,c,1,n)}else o.src=e.src}ensureFocusTrap(t,e,o){this.focusTraps.has(t)||this.focusTraps.set(t,K(e,o))}trackedTimeout(t,e){const o=setTimeout(()=>{this.activeTimers.delete(o),t()},e);this.activeTimers.add(o)}syncCurrentSceneHotspots(){!this.currentSceneId||this.isTransitioning||this.sceneHotspotOverrides.set(this.currentSceneId,[...this.config.hotspots])}addHotspotCleanups(t,...e){let o=this.hotspotCleanups.get(t);o||(o=[],this.hotspotCleanups.set(t,o)),o.push(...e)}clearHotspots(){for(const t of this.hotspotCleanups.values())t.forEach(e=>e());this.hotspotCleanups.clear();for(const[,t]of this.popovers)t.destroy();this.popovers.clear();for(const[,t]of this.markers)A(t);this.markers.clear(),this.normalizedHotspots.clear();for(const[,t]of this.focusTraps)t.destroy();this.focusTraps.clear()}getSceneTransitionDuration(){if(typeof getComputedStyle>"u")return 400;const t=getComputedStyle(this.containerEl).getPropertyValue("--ci-hotspot-scene-transition-duration").trim(),e=parseFloat(t);return isNaN(e)?400:t.endsWith("s")&&!t.endsWith("ms")?e*1e3:e}performSceneTransition(t,e,o,s){var a;if(e==="none"){this.clearHotspots(),this.switchToScene(t),s();return}const c=this.getSceneTransitionDuration();g(this.containerEl,"ci-hotspot-scene-transitioning");const n=b("img","ci-hotspot-scene-incoming",{alt:t.alt||"",draggable:"false"});if((a=this.config.cloudimage)!=null&&a.token){const l=this.containerEl.offsetWidth||300,d=typeof window<"u"&&window.devicePixelRatio||1;n.src=H(t.src,this.config.cloudimage,l,1,d)}else n.src=t.src;const r=()=>{if(!this.destroyed){if(e==="fade")g(n,"ci-hotspot-scene-fade-in"),g(this.imgEl,"ci-hotspot-scene-fade-out");else if(e==="slide"){const l=o?"-reverse":"";g(n,`ci-hotspot-scene-slide-in${l}`),g(this.imgEl,`ci-hotspot-scene-slide-out${l}`)}this.viewportEl.insertBefore(n,this.markersEl),this.transitionTimer=setTimeout(()=>{if(this.transitionTimer=void 0,this.destroyed)return;this.clearHotspots(),this.switchToScene(t);const l=()=>{n.remove(),w(this.imgEl,"ci-hotspot-scene-fade-out"),w(this.imgEl,"ci-hotspot-scene-slide-out"),w(this.imgEl,"ci-hotspot-scene-slide-out-reverse"),w(this.containerEl,"ci-hotspot-scene-transitioning"),s()};this.imgEl.complete&&this.imgEl.naturalWidth>0?l():(this.imgEl.addEventListener("load",l,{once:!0}),this.imgEl.addEventListener("error",l,{once:!0}))},c)}};n.complete?r():(g(this.containerEl,"ci-hotspot-scene-loading"),n.onload=()=>{w(this.containerEl,"ci-hotspot-scene-loading"),r()},n.onerror=()=>{this.destroyed||(w(this.containerEl,"ci-hotspot-scene-loading"),n.remove(),w(this.containerEl,"ci-hotspot-scene-transitioning"),this.clearHotspots(),this.switchToScene(t),s())})}switchToScene(t){var o;this.config.src=t.src,this.config.alt=t.alt||"",this.config.hotspots=this.sceneHotspotOverrides.get(t.id)??[...t.hotspots],this.imgEl.alt=t.alt||"",this.containerEl.setAttribute("aria-label",t.alt||"Image with hotspots"),this.imageLoaded=!1;const e=()=>{this.imageLoaded=!0,this.renormalizePixelCoordinates(),this.syncMarkersToImage(),this.showLoadTriggerPopovers()};if(this.imgEl.addEventListener("load",e,{once:!0}),(o=this.config.cloudimage)!=null&&o.token){const s=this.containerEl.offsetWidth||300,c=typeof window<"u"&&window.devicePixelRatio||1;this.imgEl.src=H(t.src,this.config.cloudimage,s,1,c)}else this.imgEl.src=t.src;this.imgEl.complete&&this.imgEl.naturalWidth>0&&!this.imageLoaded&&(this.imgEl.removeEventListener("load",e),e()),this.initHotspots()}getElements(){return{container:this.containerEl,viewport:this.viewportEl,image:this.imgEl,markers:this.markersEl}}open(t){var s;if(this.destroyed)return;const e=this.popovers.get(t),o=this.markers.get(t);!e||!o||e.isVisible()||(this.closeAll(),e.show(),f(o,!0),(s=this.focusTraps.get(t))==null||s.activate())}close(t){var s;if(this.destroyed)return;const e=this.popovers.get(t),o=this.markers.get(t);e&&o&&(e.hide(),f(o,!1),(s=this.focusTraps.get(t))==null||s.deactivate())}closeAll(){var t;if(!this.destroyed){for(const[e,o]of this.popovers)if(o.isVisible()){o.hide();const s=this.markers.get(e);s&&f(s,!1),(t=this.focusTraps.get(e))==null||t.deactivate()}}}setZoom(t){var e;this.destroyed||(e=this.zoomPan)==null||e.setZoom(t)}getZoom(){var t;return((t=this.zoomPan)==null?void 0:t.getZoom())||1}resetZoom(){var t;this.destroyed||(t=this.zoomPan)==null||t.resetZoom()}goToScene(t){if(this.destroyed||this.isTransitioning||!this.scenesMap.size||t===this.currentSceneId)return;const e=this.scenesMap.get(t);if(!e)return;const o=this.config.sceneTransition||"fade";this.isTransitioning=!0,this.zoomPan&&this.zoomPan.getZoom()>1&&this.zoomPan.resetZoom();let s=!1;if(o==="slide"){for(const c of this.config.hotspots)if(c.navigateTo===t){const n=this.normalizedHotspots.get(c.id);n&&n.x<=50&&(s=!0);break}}this.syncCurrentSceneHotspots(),this.currentSceneId=t,this.performSceneTransition(e,o,s,()=>{var n,r;this.isTransitioning=!1,Wt(`Navigated to ${e.alt||t}`),(r=(n=this.config).onSceneChange)==null||r.call(n,t,e);const c=e.hotspots[0];if(c){const a=this.markers.get(c.id);a&&document.activeElement&&this.containerEl.contains(document.activeElement)&&a.focus()}})}getCurrentScene(){return this.currentSceneId}getScenes(){return Array.from(this.scenesMap.keys())}enterFullscreen(){var t;this.destroyed||(t=this.fullscreenControl)==null||t.enter()}exitFullscreen(){var t;this.destroyed||(t=this.fullscreenControl)==null||t.exit()}isFullscreen(){var t;return((t=this.fullscreenControl)==null?void 0:t.isFullscreen())??!1}addHotspot(t){this.destroyed||(this.config.hotspots.push(t),this.addHotspotInternal(t),this.syncCurrentSceneHotspots())}removeHotspot(t){if(this.destroyed)return;const e=this.hotspotCleanups.get(t);e&&(e.forEach(n=>n()),this.hotspotCleanups.delete(t));const o=this.focusTraps.get(t);o&&(o.destroy(),this.focusTraps.delete(t));const s=this.markers.get(t),c=this.popovers.get(t);c&&(c.destroy(),this.popovers.delete(t)),s&&(A(s),this.markers.delete(t)),this.normalizedHotspots.delete(t),this.config.hotspots=this.config.hotspots.filter(n=>n.id!==t),this.syncCurrentSceneHotspots()}updateHotspot(t,e){if(this.destroyed)return;const o=this.config.hotspots.findIndex(l=>l.id===t);if(o===-1)return;const s=this.markers.get(t),c=(s==null?void 0:s.nextElementSibling)||null,r={...this.config.hotspots[o],...e};this.removeHotspot(t),this.config.hotspots.splice(o,0,r),this.addHotspotInternal(r);const a=this.markers.get(t);a&&c&&this.markersEl.contains(c)&&this.markersEl.insertBefore(a,c),this.syncCurrentSceneHotspots()}update(t){this.destroyed||(this.destroyInternal(),U(),this.config=$({...this.config,...t}),W(this.config),this.config.scenes&&this.config.scenes.length>0&&this.initScenes(),this.buildDOM(),this.applyTheme(),this.setupImage(),this.initHotspots(),this.config.zoom&&this.initZoom(),this.initKeyboard(),this.initFullscreen(),this.setupResponsive())}destroy(){this.destroyed||(this.destroyed=!0,this.destroyInternal(),this.rootEl.innerHTML="")}destroyInternal(){var t,e,o,s,c,n,r;this.imageLoaded=!1;for(const a of this.activeTimers)clearTimeout(a);this.activeTimers.clear();for(const a of this.hotspotCleanups.values())a.forEach(l=>l());this.hotspotCleanups.clear(),this.cleanups.forEach(a=>a()),this.cleanups=[];for(const[,a]of this.popovers)a.destroy();this.popovers.clear();for(const[,a]of this.markers)A(a);this.markers.clear(),this.normalizedHotspots.clear();for(const[,a]of this.focusTraps)a.destroy();this.focusTraps.clear(),this.scenesMap.clear(),this.preloadedScenes.clear(),this.sceneHotspotOverrides.clear(),this.currentSceneId=void 0,this.isTransitioning=!1,this.transitionTimer!==void 0&&(clearTimeout(this.transitionTimer),this.transitionTimer=void 0),(t=this.fullscreenControl)==null||t.destroy(),this.fullscreenControl=null,(e=this.keyboardHandler)==null||e.destroy(),this.keyboardHandler=null,(o=this.zoomPan)==null||o.destroy(),this.zoomPan=null,(s=this.zoomControls)==null||s.destroy(),this.zoomControls=null,(c=this.scrollHint)==null||c.destroy(),this.scrollHint=null,(n=this.cloudimageHandler)==null||n.destroy(),this.cloudimageHandler=null,(r=this.resizeObserver)==null||r.disconnect(),this.resizeObserver=null,jt()}};P.NAVIGATE_ARROW_SVG='<svg class="ci-hotspot-navigate-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>';let X=P;function Y(i){const t=x.useRef({value:i,key:JSON.stringify(i)});if(i!==t.current.value){const e=JSON.stringify(i);e!==t.current.key?t.current={value:i,key:e}:t.current.value=i}return t.current.key}function st(i){const t=x.useRef(null),e=x.useRef(null),o=x.useRef(!1),s=x.useRef(i);s.current=i,x.useEffect(()=>{const a=t.current;if(!a)return;const l={src:s.current.src||"",alt:s.current.alt,hotspots:s.current.hotspots||[],trigger:s.current.trigger,zoom:s.current.zoom,zoomMax:s.current.zoomMax,zoomMin:s.current.zoomMin,theme:s.current.theme,pulse:s.current.pulse,placement:s.current.placement,zoomControls:s.current.zoomControls,zoomControlsPosition:s.current.zoomControlsPosition,lazyLoad:s.current.lazyLoad,scrollHint:s.current.scrollHint,cloudimage:s.current.cloudimage,onOpen:s.current.onOpen,onClose:s.current.onClose,onZoom:s.current.onZoom,onClick:s.current.onClick,scenes:s.current.scenes,initialScene:s.current.initialScene,sceneTransition:s.current.sceneTransition,sceneAspectRatio:s.current.sceneAspectRatio,onSceneChange:s.current.onSceneChange,fullscreenButton:s.current.fullscreenButton,onFullscreenChange:s.current.onFullscreenChange};s.current.renderPopover&&(l.renderPopover=h=>{const p=document.createElement("div");return p.dataset.reactPortal=h.id,p});const d=new X(a,l);return e.current=d,()=>{d.destroy(),e.current=null}},[]);const c=Y(i.scenes),n=Y(i.hotspots),r=Y(i.cloudimage);return x.useEffect(()=>{if(!o.current){o.current=!0;return}e.current&&e.current.update({src:i.src||"",alt:i.alt,hotspots:i.hotspots||[],trigger:i.trigger,zoom:i.zoom,zoomMax:i.zoomMax,zoomMin:i.zoomMin,theme:i.theme,pulse:i.pulse,placement:i.placement,zoomControls:i.zoomControls,zoomControlsPosition:i.zoomControlsPosition,lazyLoad:i.lazyLoad,scrollHint:i.scrollHint,cloudimage:i.cloudimage,onOpen:i.onOpen,onClose:i.onClose,onZoom:i.onZoom,onClick:i.onClick,scenes:i.scenes,initialScene:i.initialScene,sceneTransition:i.sceneTransition,sceneAspectRatio:i.sceneAspectRatio,onSceneChange:i.onSceneChange,fullscreenButton:i.fullscreenButton,onFullscreenChange:i.onFullscreenChange})},[i.src,i.alt,n,i.trigger,i.zoom,i.zoomMax,i.zoomMin,i.theme,i.pulse,i.placement,i.zoomControls,i.zoomControlsPosition,i.lazyLoad,i.scrollHint,r,c,i.initialScene,i.sceneTransition,i.sceneAspectRatio,i.fullscreenButton]),{containerRef:t,instance:e}}const Kt=x.forwardRef(function(t,e){const{className:o,style:s,renderPopover:c,...n}=t,{containerRef:r,instance:a}=st(n),[l,d]=x.useState(new Map);return x.useImperativeHandle(e,()=>({open:h=>{var p;return(p=a.current)==null?void 0:p.open(h)},close:h=>{var p;return(p=a.current)==null?void 0:p.close(h)},closeAll:()=>{var h;return(h=a.current)==null?void 0:h.closeAll()},setZoom:h=>{var p;return(p=a.current)==null?void 0:p.setZoom(h)},getZoom:()=>{var h;return((h=a.current)==null?void 0:h.getZoom())??1},resetZoom:()=>{var h;return(h=a.current)==null?void 0:h.resetZoom()},addHotspot:h=>{var p;return(p=a.current)==null?void 0:p.addHotspot(h)},removeHotspot:h=>{var p;return(p=a.current)==null?void 0:p.removeHotspot(h)},updateHotspot:(h,p)=>{var u;return(u=a.current)==null?void 0:u.updateHotspot(h,p)},goToScene:h=>{var p;return(p=a.current)==null?void 0:p.goToScene(h)},getCurrentScene:()=>{var h;return(h=a.current)==null?void 0:h.getCurrentScene()},getScenes:()=>{var h;return((h=a.current)==null?void 0:h.getScenes())??[]},enterFullscreen:()=>{var h;return(h=a.current)==null?void 0:h.enterFullscreen()},exitFullscreen:()=>{var h;return(h=a.current)==null?void 0:h.exitFullscreen()},isFullscreen:()=>{var h;return((h=a.current)==null?void 0:h.isFullscreen())??!1}})),x.useEffect(()=>{if(!r.current||!c)return;const h=new MutationObserver(()=>{var k;const p=(k=r.current)==null?void 0:k.querySelectorAll("[data-react-portal]");if(!p)return;const u=new Map;p.forEach(v=>{const y=v.dataset.reactPortal;y&&u.set(y,v)}),d(v=>{if(v.size!==u.size)return u;for(const[y,M]of u)if(v.get(y)!==M)return u;return v})});return h.observe(r.current,{childList:!0,subtree:!0}),()=>h.disconnect()},[c]),S.jsxs(S.Fragment,{children:[S.jsx("div",{ref:r,className:o,style:s}),c&&Array.from(l.entries()).map(([h,p])=>{var k;const u=(k=t.hotspots)==null?void 0:k.find(v=>v.id===h);return u?nt.createPortal(S.jsx(S.Fragment,{children:c(u)}),p,h):null})]})});exports.CIHotspotViewer=Kt;exports.useCIHotspot=st;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../src/core/config.ts","../../src/utils/dom.ts","../../src/utils/coordinates.ts","../../src/utils/events.ts","../../src/markers/marker.ts","../../src/popover/position.ts","../../src/popover/sanitize.ts","../../src/popover/template.ts","../../src/popover/popover.ts","../../src/zoom/gestures.ts","../../src/zoom/zoom-pan.ts","../../src/zoom/controls.ts","../../src/zoom/scroll-hint.ts","../../src/utils/cloudimage.ts","../../src/a11y/keyboard.ts","../../src/a11y/focus.ts","../../src/a11y/aria.ts","../../src/fullscreen/fullscreen.ts","../../src/core/ci-hotspot.ts","../../src/react/use-ci-hotspot.ts","../../src/react/ci-hotspot-viewer.tsx"],"sourcesContent":["import type { CIHotspotConfig, CloudimageConfig, ResolvedCIHotspotConfig } from './types';\n\nexport const DEFAULT_CONFIG: Omit<Required<CIHotspotConfig>, 'src' | 'hotspots' | 'cloudimage' | 'renderPopover' | 'onOpen' | 'onClose' | 'onZoom' | 'onClick' | 'scenes' | 'initialScene' | 'onSceneChange' | 'sceneAspectRatio' | 'onFullscreenChange'> = {\n alt: '',\n trigger: 'hover',\n zoom: false,\n zoomMax: 4,\n zoomMin: 1,\n theme: 'light',\n pulse: true,\n zoomControls: true,\n placement: 'top',\n lazyLoad: true,\n sceneTransition: 'fade',\n scrollHint: true,\n invertMarkerTheme: false,\n fullscreenButton: true,\n zoomControlsPosition: 'bottom-right',\n};\n\n/** Data attribute to config property mapping */\nexport const DATA_ATTR_MAP: Record<string, { key: string; type: 'string' | 'boolean' | 'number' | 'json'; nested?: string }> = {\n 'data-ci-hotspot-src': { key: 'src', type: 'string' },\n 'data-ci-hotspot-alt': { key: 'alt', type: 'string' },\n 'data-ci-hotspot-items': { key: 'hotspots', type: 'json' },\n 'data-ci-hotspot-trigger': { key: 'trigger', type: 'string' },\n 'data-ci-hotspot-zoom': { key: 'zoom', type: 'boolean' },\n 'data-ci-hotspot-zoom-max': { key: 'zoomMax', type: 'number' },\n 'data-ci-hotspot-zoom-min': { key: 'zoomMin', type: 'number' },\n 'data-ci-hotspot-theme': { key: 'theme', type: 'string' },\n 'data-ci-hotspot-pulse': { key: 'pulse', type: 'boolean' },\n 'data-ci-hotspot-placement': { key: 'placement', type: 'string' },\n 'data-ci-hotspot-lazy-load': { key: 'lazyLoad', type: 'boolean' },\n 'data-ci-hotspot-zoom-controls': { key: 'zoomControls', type: 'boolean' },\n 'data-ci-hotspot-scroll-hint': { key: 'scrollHint', type: 'boolean' },\n 'data-ci-hotspot-ci-token': { key: 'token', type: 'string', nested: 'cloudimage' },\n 'data-ci-hotspot-ci-api-version': { key: 'apiVersion', type: 'string', nested: 'cloudimage' },\n 'data-ci-hotspot-ci-domain': { key: 'domain', type: 'string', nested: 'cloudimage' },\n 'data-ci-hotspot-ci-limit-factor': { key: 'limitFactor', type: 'number', nested: 'cloudimage' },\n 'data-ci-hotspot-ci-params': { key: 'params', type: 'string', nested: 'cloudimage' },\n 'data-ci-hotspot-scenes': { key: 'scenes', type: 'json' },\n 'data-ci-hotspot-initial-scene': { key: 'initialScene', type: 'string' },\n 'data-ci-hotspot-scene-transition': { key: 'sceneTransition', type: 'string' },\n 'data-ci-hotspot-scene-aspect-ratio': { key: 'sceneAspectRatio', type: 'string' },\n 'data-ci-hotspot-invert-marker-theme': { key: 'invertMarkerTheme', type: 'boolean' },\n 'data-ci-hotspot-fullscreen-button': { key: 'fullscreenButton', type: 'boolean' },\n 'data-ci-hotspot-zoom-controls-position': { key: 'zoomControlsPosition', type: 'string' },\n};\n\n/** Parse data attributes from an element into a config object */\nexport function parseDataAttributes(element: HTMLElement): Partial<CIHotspotConfig> {\n const config: Record<string, unknown> = {};\n const cloudimage: Record<string, unknown> = {};\n\n for (const [attr, mapping] of Object.entries(DATA_ATTR_MAP)) {\n const value = element.getAttribute(attr);\n if (value === null) continue;\n\n const parsed = coerceValue(value, mapping.type);\n\n if (mapping.nested === 'cloudimage') {\n cloudimage[mapping.key] = parsed;\n } else {\n config[mapping.key] = parsed;\n }\n }\n\n if (Object.keys(cloudimage).length > 0) {\n config.cloudimage = cloudimage as unknown as CloudimageConfig;\n }\n\n return config as Partial<CIHotspotConfig>;\n}\n\nfunction coerceValue(value: string, type: string): unknown {\n switch (type) {\n case 'boolean':\n return value === 'true';\n case 'number': {\n const num = parseFloat(value);\n if (isNaN(num)) {\n console.warn(`CIHotspot: invalid number value \"${value}\"`);\n return undefined;\n }\n return num;\n }\n case 'json':\n try {\n return JSON.parse(value);\n } catch {\n console.warn(`CIHotspot: failed to parse JSON value \"${value}\"`);\n return undefined;\n }\n default:\n return value;\n }\n}\n\n/** Merge user config with defaults */\nexport function mergeConfig(userConfig: Partial<CIHotspotConfig>): ResolvedCIHotspotConfig {\n return {\n ...DEFAULT_CONFIG,\n ...userConfig,\n src: userConfig.src || '',\n hotspots: userConfig.hotspots || [],\n } as ResolvedCIHotspotConfig;\n}\n\n/** Validate config — throws on critical issues */\nexport function validateConfig(config: ResolvedCIHotspotConfig): void {\n if (config.scenes && config.scenes.length > 0) {\n const sceneIds = new Set<string>();\n for (const scene of config.scenes) {\n if (!scene.id) throw new Error('CIHotspot: each scene must have an \"id\"');\n if (sceneIds.has(scene.id)) {\n throw new Error(`CIHotspot: duplicate scene ID \"${scene.id}\"`);\n }\n sceneIds.add(scene.id);\n if (!scene.src) throw new Error(`CIHotspot: scene \"${scene.id}\" must have a \"src\"`);\n }\n for (const scene of config.scenes) {\n for (const hotspot of scene.hotspots || []) {\n if (hotspot.navigateTo && !sceneIds.has(hotspot.navigateTo)) {\n throw new Error(`CIHotspot: hotspot \"${hotspot.id}\" navigateTo \"${hotspot.navigateTo}\" is not a valid scene ID`);\n }\n }\n }\n if (config.initialScene) {\n if (!sceneIds.has(config.initialScene)) {\n throw new Error(`CIHotspot: initialScene \"${config.initialScene}\" not found in scenes`);\n }\n }\n } else if (!config.src) {\n throw new Error('CIHotspot: \"src\" is required');\n }\n}\n","const STYLE_ID = 'ci-hotspot-styles';\n\n/** Check if code is running in browser environment */\nexport function isBrowser(): boolean {\n return typeof window !== 'undefined' && typeof document !== 'undefined';\n}\n\n/** Resolve element from selector string or HTMLElement */\nexport function getElement(el: HTMLElement | string): HTMLElement {\n if (typeof el === 'string') {\n const found = document.querySelector<HTMLElement>(el);\n if (!found) throw new Error(`CIHotspot: element \"${el}\" not found`);\n return found;\n }\n return el;\n}\n\n/** Create an HTML element with optional class and attributes */\nexport function createElement<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n className?: string,\n attrs?: Record<string, string>,\n): HTMLElementTagNameMap[K] {\n const el = document.createElement(tag);\n if (className) el.className = className;\n if (attrs) {\n for (const [key, value] of Object.entries(attrs)) {\n el.setAttribute(key, value);\n }\n }\n return el;\n}\n\n/** Add CSS class to element */\nexport function addClass(el: HTMLElement, ...classNames: string[]): void {\n el.classList.add(...classNames);\n}\n\n/** Remove CSS class from element */\nexport function removeClass(el: HTMLElement, ...classNames: string[]): void {\n el.classList.remove(...classNames);\n}\n\n/** Idempotent CSS style injection — only injects once per page */\nexport function injectStyles(css: string): void {\n if (!isBrowser()) return;\n if (document.getElementById(STYLE_ID)) return;\n const style = document.createElement('style');\n style.id = STYLE_ID;\n style.textContent = css;\n document.head.appendChild(style);\n}\n","import type { Point } from '../core/types';\n\n/**\n * Parse a coordinate value.\n * - String ending in '%': return the numeric percent value\n * - Number: return as-is (pixel value)\n */\nexport function parseCoordinate(value: string | number): { value: number; isPercent: boolean } {\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (trimmed.endsWith('%')) {\n return { value: parseFloat(trimmed), isPercent: true };\n }\n return { value: parseFloat(trimmed), isPercent: false };\n }\n return { value, isPercent: false };\n}\n\n/**\n * Normalize x/y coordinates to percentages.\n * If already percentages, returns as-is.\n * If pixel values, converts using natural image dimensions.\n */\nexport function normalizeToPercent(\n x: string | number,\n y: string | number,\n naturalWidth: number,\n naturalHeight: number,\n): Point {\n const px = parseCoordinate(x);\n const py = parseCoordinate(y);\n\n return {\n x: px.isPercent ? px.value : (px.value / naturalWidth) * 100,\n y: py.isPercent ? py.value : (py.value / naturalHeight) * 100,\n };\n}\n\n","type EventHandler = (...args: unknown[]) => void;\n\n/** Minimal typed event emitter */\nexport class EventEmitter {\n private listeners = new Map<string, Set<EventHandler>>();\n\n on(event: string, handler: EventHandler): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(handler);\n }\n\n off(event: string, handler: EventHandler): void {\n this.listeners.get(event)?.delete(handler);\n }\n\n emit(event: string, ...args: unknown[]): void {\n this.listeners.get(event)?.forEach((handler) => handler(...args));\n }\n\n removeAll(): void {\n this.listeners.clear();\n }\n}\n\n/**\n * Add a DOM event listener and return a cleanup function.\n */\nexport function addListener<K extends keyof HTMLElementEventMap>(\n el: EventTarget,\n event: K,\n handler: (e: HTMLElementEventMap[K]) => void,\n options?: boolean | AddEventListenerOptions,\n): () => void {\n el.addEventListener(event, handler as EventListener, options);\n return () => el.removeEventListener(event, handler as EventListener, options);\n}\n\n","import type { NormalizedHotspot } from '../core/types';\nimport { createElement, addClass, removeClass } from '../utils/dom';\n\n/** Create a marker button element for a hotspot */\nexport function createMarker(hotspot: NormalizedHotspot, pulse: boolean): HTMLButtonElement {\n const marker = createElement('button', 'ci-hotspot-marker', {\n 'aria-label': hotspot.label,\n 'aria-expanded': 'false',\n 'data-hotspot-id': hotspot.id,\n 'tabindex': '0',\n });\n\n marker.style.left = `${hotspot.x}%`;\n marker.style.top = `${hotspot.y}%`;\n\n if (hotspot.className) {\n const classes = hotspot.className.trim().split(/\\s+/).filter(Boolean);\n if (classes.length) addClass(marker, ...classes);\n }\n\n if (hotspot.hidden) {\n addClass(marker, 'ci-hotspot-marker--hidden');\n }\n\n if (pulse) {\n addClass(marker, 'ci-hotspot-marker--pulse');\n }\n\n if (hotspot.icon) {\n setMarkerIcon(marker, hotspot.icon);\n }\n\n return marker;\n}\n\n/** Sanitize SVG string to prevent XSS via injected scripts or event handlers */\nfunction sanitizeSVG(svg: string): string {\n if (typeof DOMParser === 'undefined') return '';\n const doc = new DOMParser().parseFromString(svg, 'image/svg+xml');\n const root = doc.documentElement;\n // If parsing failed, return empty\n if (root.querySelector('parsererror')) return '';\n cleanSVGNode(root);\n return new XMLSerializer().serializeToString(root);\n}\n\nconst SVG_BLOCKED_ELEMENTS = new Set([\n 'script', 'foreignobject', 'iframe', 'object', 'embed',\n 'animate', 'animatetransform', 'animatemotion', 'set',\n 'style', 'a', 'use', 'image', 'feimage',\n]);\n\nfunction cleanSVGNode(node: Element): void {\n for (const attr of Array.from(node.attributes)) {\n const name = attr.name.toLowerCase();\n if (name.startsWith('on') || name === 'style') {\n node.removeAttribute(attr.name);\n } else if (\n (name === 'href' || name === 'xlink:href') &&\n /^\\s*javascript\\s*:/i.test(attr.value)\n ) {\n node.removeAttribute(attr.name);\n }\n }\n for (const child of Array.from(node.children)) {\n if (SVG_BLOCKED_ELEMENTS.has(child.tagName.toLowerCase())) {\n child.remove();\n continue;\n }\n cleanSVGNode(child);\n }\n}\n\n/** Set marker icon content */\nfunction setMarkerIcon(marker: HTMLButtonElement, icon: string): void {\n const trimmed = icon.trim();\n if (/^<svg[\\s>]/i.test(trimmed) || /^<\\?xml/i.test(trimmed)) {\n marker.innerHTML = sanitizeSVG(icon);\n } else if (icon.match(/\\.(png|jpg|jpeg|gif|svg|webp)$/i) || icon.startsWith('http') || icon.startsWith('/')) {\n const img = createElement('img', undefined, {\n src: icon,\n alt: '',\n 'aria-hidden': 'true',\n });\n img.style.width = '100%';\n img.style.height = '100%';\n img.style.objectFit = 'contain';\n marker.appendChild(img);\n } else {\n // CSS class name\n const iconEl = createElement('span', icon, { 'aria-hidden': 'true' });\n marker.appendChild(iconEl);\n }\n}\n\n/** Set marker as active (popover open) */\nexport function setMarkerActive(marker: HTMLButtonElement, active: boolean): void {\n if (active) {\n addClass(marker, 'ci-hotspot-marker--active');\n marker.setAttribute('aria-expanded', 'true');\n } else {\n removeClass(marker, 'ci-hotspot-marker--active');\n marker.setAttribute('aria-expanded', 'false');\n }\n}\n\n/** Set marker as hidden */\nexport function setMarkerHidden(marker: HTMLButtonElement, hidden: boolean): void {\n if (hidden) {\n addClass(marker, 'ci-hotspot-marker--hidden');\n } else {\n removeClass(marker, 'ci-hotspot-marker--hidden');\n }\n}\n\n/** Destroy a marker element */\nexport function destroyMarker(marker: HTMLButtonElement): void {\n marker.remove();\n}\n","import type { Placement, PositionResult } from '../core/types';\n\ninterface AvailableSpace {\n top: number;\n bottom: number;\n left: number;\n right: number;\n}\n\n/** Compute the best position for a popover relative to a marker within a container */\nexport function computePosition(\n markerEl: HTMLElement,\n popoverEl: HTMLElement,\n containerEl: HTMLElement,\n options: { placement: Placement },\n): PositionResult {\n const markerRect = markerEl.getBoundingClientRect();\n const containerRect = containerEl.getBoundingClientRect();\n const popoverWidth = popoverEl.offsetWidth;\n const popoverHeight = popoverEl.offsetHeight;\n\n // Marker position relative to container\n const markerCenterX = markerRect.left + markerRect.width / 2 - containerRect.left;\n const markerCenterY = markerRect.top + markerRect.height / 2 - containerRect.top;\n const markerTop = markerRect.top - containerRect.top;\n const markerBottom = markerRect.bottom - containerRect.top;\n const markerLeft = markerRect.left - containerRect.left;\n const markerRight = markerRect.right - containerRect.left;\n\n const containerWidth = containerEl.offsetWidth;\n const containerHeight = containerEl.offsetHeight;\n const gap = 8; // gap between marker and popover\n\n const space: AvailableSpace = {\n top: markerTop - gap,\n bottom: containerHeight - markerBottom - gap,\n left: markerLeft - gap,\n right: containerWidth - markerRight - gap,\n };\n\n let placement = options.placement;\n if (placement === 'auto') {\n placement = getAutoPlacement(space);\n }\n\n // Flip if not enough space\n placement = flip(placement, popoverWidth, popoverHeight, space);\n\n // Calculate position\n let x: number;\n let y: number;\n let arrowOffset = 0;\n\n switch (placement) {\n case 'top':\n x = markerCenterX - popoverWidth / 2;\n y = markerTop - gap - popoverHeight;\n break;\n case 'bottom':\n x = markerCenterX - popoverWidth / 2;\n y = markerBottom + gap;\n break;\n case 'left':\n x = markerLeft - gap - popoverWidth;\n y = markerCenterY - popoverHeight / 2;\n break;\n case 'right':\n x = markerRight + gap;\n y = markerCenterY - popoverHeight / 2;\n break;\n default:\n x = markerCenterX - popoverWidth / 2;\n y = markerTop - gap - popoverHeight;\n }\n\n // Shift to stay within container\n const shifted = shift(x, y, popoverWidth, popoverHeight, containerWidth, containerHeight);\n arrowOffset = (placement === 'top' || placement === 'bottom')\n ? x - shifted.x\n : y - shifted.y;\n\n return {\n x: shifted.x,\n y: shifted.y,\n placement,\n arrowOffset,\n };\n}\n\nfunction getAutoPlacement(space: AvailableSpace): Exclude<Placement, 'auto'> {\n const max = Math.max(space.top, space.bottom, space.left, space.right);\n if (max === space.top) return 'top';\n if (max === space.bottom) return 'bottom';\n if (max === space.right) return 'right';\n return 'left';\n}\n\nfunction flip(\n placement: Placement,\n popoverWidth: number,\n popoverHeight: number,\n space: AvailableSpace,\n): Exclude<Placement, 'auto'> {\n const p = placement as Exclude<Placement, 'auto'>;\n switch (p) {\n case 'top':\n if (space.top < popoverHeight && space.bottom > space.top) return 'bottom';\n break;\n case 'bottom':\n if (space.bottom < popoverHeight && space.top > space.bottom) return 'top';\n break;\n case 'left':\n if (space.left < popoverWidth && space.right > space.left) return 'right';\n break;\n case 'right':\n if (space.right < popoverWidth && space.left > space.right) return 'left';\n break;\n }\n return p;\n}\n\nfunction shift(\n x: number,\n y: number,\n width: number,\n height: number,\n containerWidth: number,\n containerHeight: number,\n): { x: number; y: number } {\n const padding = 4;\n let sx = x;\n let sy = y;\n\n // Horizontal: center if popover wider than container, otherwise clamp\n if (width > containerWidth - 2 * padding) {\n sx = (containerWidth - width) / 2;\n } else {\n if (sx < padding) sx = padding;\n if (sx + width > containerWidth - padding) sx = containerWidth - padding - width;\n }\n\n // Vertical: center if popover taller than container, otherwise clamp\n if (height > containerHeight - 2 * padding) {\n sy = (containerHeight - height) / 2;\n } else {\n if (sy < padding) sy = padding;\n if (sy + height > containerHeight - padding) sy = containerHeight - padding - height;\n }\n\n return { x: sx, y: sy };\n}\n","const ALLOWED_TAGS = new Set([\n 'a', 'b', 'br', 'div', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n 'i', 'img', 'li', 'ol', 'p', 'span', 'strong', 'ul',\n]);\n\nconst ALLOWED_ATTRS = new Set([\n 'class', 'href', 'src', 'alt', 'title', 'target', 'rel',\n]);\n\nconst SAFE_HREF_PATTERN = /^(?:https?:|mailto:)/i;\nconst SAFE_SRC_PATTERN = /^(?:https?:|data:image\\/(?!svg[+%]))/i;\nconst SAFE_REL_VALUES = new Set([\n 'noopener', 'noreferrer', 'nofollow', 'external', 'author', 'help',\n 'license', 'next', 'prev', 'search', 'tag', 'bookmark',\n]);\n\n/**\n * Sanitize HTML string to prevent XSS.\n * Uses DOMParser for robust parsing.\n */\nexport function sanitizeHTML(html: string): string {\n const parser = new DOMParser();\n const doc = parser.parseFromString(`<body>${html}</body>`, 'text/html');\n const body = doc.body;\n\n sanitizeNode(body);\n\n return body.innerHTML;\n}\n\nfunction sanitizeNode(node: Node): void {\n const children = Array.from(node.childNodes);\n\n for (const child of children) {\n if (child.nodeType === Node.TEXT_NODE) {\n continue;\n }\n\n if (child.nodeType === Node.ELEMENT_NODE) {\n const el = child as Element;\n const tagName = el.tagName.toLowerCase();\n\n if (!ALLOWED_TAGS.has(tagName)) {\n // Remove disallowed element entirely (including its children)\n el.remove();\n continue;\n }\n\n // Remove disallowed attributes\n const attrs = Array.from(el.attributes);\n for (const attr of attrs) {\n const name = attr.name.toLowerCase();\n\n // Block event handlers\n if (name.startsWith('on')) {\n el.removeAttribute(attr.name);\n continue;\n }\n\n if (!ALLOWED_ATTRS.has(name)) {\n el.removeAttribute(attr.name);\n continue;\n }\n\n // Block unsafe URLs per attribute type\n if (name === 'href' && !SAFE_HREF_PATTERN.test(attr.value.trim())) {\n el.removeAttribute(attr.name);\n } else if (name === 'src' && !SAFE_SRC_PATTERN.test(attr.value.trim())) {\n el.removeAttribute(attr.name);\n } else if (name === 'rel') {\n const safeTokens = attr.value.trim().toLowerCase().split(/\\s+/).filter((t) => SAFE_REL_VALUES.has(t));\n if (safeTokens.length === 0) {\n el.removeAttribute(attr.name);\n } else {\n el.setAttribute(attr.name, safeTokens.join(' '));\n }\n }\n }\n\n // Recursively sanitize children\n sanitizeNode(el);\n } else {\n // Remove comment nodes and other non-element/text nodes\n child.remove();\n }\n }\n}\n","import type { HotspotItem, PopoverData } from '../core/types';\nimport { sanitizeHTML } from './sanitize';\n\n/** Render the built-in product template from data fields */\nexport function renderBuiltInTemplate(data: PopoverData): string {\n const parts: string[] = [];\n\n if (data.image) {\n parts.push(`<img class=\"ci-hotspot-popover-image\" src=\"${escapeAttr(data.image)}\" alt=\"${escapeAttr(data.title || '')}\">`);\n }\n\n const bodyParts: string[] = [];\n\n if (data.title) {\n bodyParts.push(`<h3 class=\"ci-hotspot-popover-title\">${escapeHtml(data.title)}</h3>`);\n }\n\n if (data.price) {\n bodyParts.push(`<span class=\"ci-hotspot-popover-price\">${escapeHtml(data.price)}</span>`);\n }\n\n if (data.description) {\n bodyParts.push(`<p class=\"ci-hotspot-popover-description\">${escapeHtml(data.description)}</p>`);\n }\n\n if (data.url && isSafeUrl(data.url)) {\n const ctaText = data.ctaText || 'View details';\n bodyParts.push(\n `<a class=\"ci-hotspot-popover-cta\" href=\"${escapeAttr(data.url)}\">${escapeHtml(String(ctaText))}</a>`,\n );\n }\n\n if (bodyParts.length > 0) {\n parts.push(`<div class=\"ci-hotspot-popover-body\">${bodyParts.join('')}</div>`);\n }\n\n return parts.join('');\n}\n\n/**\n * Render popover content with priority: renderFn > content string > data template.\n * Returns HTML string or DOM element.\n */\nexport function renderPopoverContent(\n hotspot: HotspotItem,\n renderFn?: (hotspot: HotspotItem) => string | HTMLElement,\n): string | HTMLElement {\n // Priority 1: Custom render function (bypasses sanitization)\n if (renderFn) {\n return renderFn(hotspot);\n }\n\n // Priority 2: HTML content string (sanitized)\n if (hotspot.content) {\n return sanitizeHTML(hotspot.content);\n }\n\n // Priority 3: Built-in template from data\n if (hotspot.data) {\n return renderBuiltInTemplate(hotspot.data);\n }\n\n return '';\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\n/** Check that a URL uses a safe protocol (allowlist approach) */\nfunction isSafeUrl(url: string): boolean {\n const normalized = url.replace(/[\\s\\x00-\\x1f]/g, '');\n return /^https?:\\/\\//i.test(normalized) || /^\\/(?!\\/)/.test(normalized) || /^#/.test(normalized);\n}\n\nfunction escapeAttr(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/</g, '<')\n .replace(/>/g, '>');\n}\n","import type { HotspotItem, Placement } from '../core/types';\nimport { createElement, addClass, removeClass } from '../utils/dom';\nimport { computePosition } from './position';\nimport { renderPopoverContent } from './template';\n\nexport interface PopoverOptions {\n placement: Placement;\n triggerMode: 'hover' | 'click' | 'load';\n renderFn?: (hotspot: HotspotItem) => string | HTMLElement;\n onOpen?: (hotspot: HotspotItem) => void;\n onClose?: (hotspot: HotspotItem) => void;\n}\n\nexport class Popover {\n readonly element: HTMLElement;\n private arrowEl: HTMLElement;\n private contentEl: HTMLElement;\n private visible = false;\n private hideTimer: ReturnType<typeof setTimeout> | undefined;\n private hotspot: HotspotItem;\n private markerEl: HTMLElement | null = null;\n private containerEl: HTMLElement | null = null;\n private options: PopoverOptions;\n private hoverCleanups: (() => void)[] = [];\n\n constructor(hotspot: HotspotItem, options: PopoverOptions) {\n this.hotspot = hotspot;\n this.options = options;\n\n const isDialog = options.triggerMode === 'click' || options.triggerMode === 'load';\n this.element = createElement('div', 'ci-hotspot-popover', {\n 'role': isDialog ? 'dialog' : 'tooltip',\n 'id': `ci-hotspot-popover-${hotspot.id}`,\n 'aria-hidden': 'true',\n 'data-placement': options.placement === 'auto' ? 'top' : options.placement,\n ...(isDialog && hotspot.label ? { 'aria-label': hotspot.label } : {}),\n });\n\n this.arrowEl = createElement('div', 'ci-hotspot-popover-arrow');\n this.contentEl = createElement('div', 'ci-hotspot-popover-content');\n\n this.element.appendChild(this.arrowEl);\n this.element.appendChild(this.contentEl);\n\n // Render content\n const content = renderPopoverContent(hotspot, options.renderFn);\n if (typeof content === 'string') {\n this.contentEl.innerHTML = content;\n } else if (content instanceof HTMLElement) {\n this.contentEl.appendChild(content);\n }\n\n // Hover delay: clear hide timer when mouse enters popover\n if (options.triggerMode === 'hover') {\n const onEnter = () => this.clearHideTimer();\n const onLeave = () => this.scheduleHide();\n this.element.addEventListener('mouseenter', onEnter);\n this.element.addEventListener('mouseleave', onLeave);\n this.hoverCleanups.push(\n () => this.element.removeEventListener('mouseenter', onEnter),\n () => this.element.removeEventListener('mouseleave', onLeave),\n );\n }\n }\n\n /** Mount the popover to a container, associating it with a marker */\n mount(containerEl: HTMLElement, markerEl: HTMLElement): void {\n this.containerEl = containerEl;\n this.markerEl = markerEl;\n containerEl.appendChild(this.element);\n\n // Set appropriate ARIA relationship on marker\n if (this.options.triggerMode === 'click' || this.options.triggerMode === 'load') {\n markerEl.setAttribute('aria-haspopup', 'dialog');\n markerEl.setAttribute('aria-controls', this.element.id);\n } else {\n markerEl.setAttribute('aria-describedby', this.element.id);\n }\n }\n\n /** Show the popover */\n show(): void {\n this.clearHideTimer();\n if (this.visible) return;\n this.visible = true;\n\n addClass(this.element, 'ci-hotspot-popover--visible');\n this.element.setAttribute('aria-hidden', 'false');\n\n this.updatePosition();\n this.options.onOpen?.(this.hotspot);\n }\n\n /** Schedule hide with delay (for hover mode) */\n scheduleHide(delay: number = 200): void {\n this.clearHideTimer();\n this.hideTimer = setTimeout(() => {\n this.hide();\n }, delay);\n }\n\n /** Hide the popover immediately */\n hide(): void {\n this.clearHideTimer();\n if (!this.visible) return;\n this.visible = false;\n\n removeClass(this.element, 'ci-hotspot-popover--visible');\n this.element.setAttribute('aria-hidden', 'true');\n\n this.options.onClose?.(this.hotspot);\n }\n\n /** Clear any pending hide timer */\n clearHideTimer(): void {\n if (this.hideTimer !== undefined) {\n clearTimeout(this.hideTimer);\n this.hideTimer = undefined;\n }\n }\n\n /** Update popover position relative to marker */\n updatePosition(): void {\n if (!this.markerEl || !this.containerEl || !this.visible) return;\n\n const result = computePosition(\n this.markerEl,\n this.element,\n this.containerEl,\n { placement: this.options.placement },\n );\n\n this.element.style.left = `${result.x}px`;\n this.element.style.top = `${result.y}px`;\n this.element.setAttribute('data-placement', result.placement);\n\n // Position arrow\n this.positionArrow(result.placement, result.arrowOffset);\n }\n\n private positionArrow(placement: Placement, offset: number): void {\n const arrow = this.arrowEl;\n // Reset\n arrow.style.left = '';\n arrow.style.top = '';\n\n if (placement === 'top' || placement === 'bottom') {\n arrow.style.left = `calc(50% - var(--ci-hotspot-arrow-size) + ${offset}px)`;\n } else {\n arrow.style.top = `calc(50% - var(--ci-hotspot-arrow-size) + ${offset}px)`;\n }\n }\n\n /** Check if popover is currently visible */\n isVisible(): boolean {\n return this.visible;\n }\n\n /** Get the hotspot associated with this popover */\n getHotspot(): HotspotItem {\n return this.hotspot;\n }\n\n /** Destroy the popover and clean up */\n destroy(): void {\n this.clearHideTimer();\n this.hoverCleanups.forEach((fn) => fn());\n this.hoverCleanups = [];\n this.markerEl?.removeAttribute('aria-describedby');\n this.markerEl?.removeAttribute('aria-controls');\n this.markerEl?.removeAttribute('aria-haspopup');\n this.element.remove();\n this.markerEl = null;\n this.containerEl = null;\n }\n}\n","export interface GestureCallbacks {\n onPinch?: (scale: number, centerX: number, centerY: number) => void;\n onPanStart?: () => void;\n onPan?: (totalDx: number, totalDy: number) => void;\n onPanEnd?: () => void;\n onDoubleTap?: (x: number, y: number) => void;\n}\n\n/** Recognizes touch gestures: pinch, pan, double-tap */\nexport class GestureRecognizer {\n private el: HTMLElement;\n private callbacks: GestureCallbacks;\n private lastTouchEnd = 0;\n private initialPinchDistance = 0;\n private initialPinchScale = 1;\n private panStartX = 0;\n private panStartY = 0;\n private isPinching = false;\n private isPanning = false;\n private wasPinching = false;\n private cleanups: (() => void)[] = [];\n\n constructor(el: HTMLElement, callbacks: GestureCallbacks, getZoom: () => number) {\n this.el = el;\n this.callbacks = callbacks;\n this.initialPinchScale = getZoom();\n\n const onTouchStart = (e: TouchEvent) => this.handleTouchStart(e, getZoom);\n const onTouchMove = (e: TouchEvent) => this.handleTouchMove(e);\n const onTouchEnd = (e: TouchEvent) => this.handleTouchEnd(e);\n\n el.addEventListener('touchstart', onTouchStart, { passive: false });\n el.addEventListener('touchmove', onTouchMove, { passive: false });\n el.addEventListener('touchend', onTouchEnd, { passive: true });\n\n this.cleanups.push(\n () => el.removeEventListener('touchstart', onTouchStart),\n () => el.removeEventListener('touchmove', onTouchMove),\n () => el.removeEventListener('touchend', onTouchEnd),\n );\n }\n\n private handleTouchStart(e: TouchEvent, getZoom: () => number): void {\n if (e.touches.length === 2) {\n e.preventDefault();\n this.isPinching = true;\n this.wasPinching = false;\n this.initialPinchDistance = this.getTouchDistance(e.touches);\n this.initialPinchScale = getZoom();\n } else if (e.touches.length === 1) {\n this.panStartX = e.touches[0].clientX;\n this.panStartY = e.touches[0].clientY;\n this.wasPinching = false;\n }\n }\n\n private handleTouchMove(e: TouchEvent): void {\n if (e.touches.length === 2 && this.isPinching) {\n e.preventDefault();\n const currentDistance = this.getTouchDistance(e.touches);\n const scale = this.initialPinchScale * (currentDistance / this.initialPinchDistance);\n const centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2;\n const centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2;\n this.callbacks.onPinch?.(scale, centerX, centerY);\n } else if (e.touches.length === 1 && !this.isPinching && !this.wasPinching) {\n if (!this.isPanning) {\n this.isPanning = true;\n this.callbacks.onPanStart?.();\n }\n const totalDx = e.touches[0].clientX - this.panStartX;\n const totalDy = e.touches[0].clientY - this.panStartY;\n this.callbacks.onPan?.(totalDx, totalDy);\n }\n }\n\n private handleTouchEnd(e: TouchEvent): void {\n if (this.isPinching && e.touches.length < 2) {\n this.isPinching = false;\n this.wasPinching = true;\n // Update pan start to remaining finger position\n if (e.touches.length === 1) {\n this.panStartX = e.touches[0].clientX;\n this.panStartY = e.touches[0].clientY;\n }\n }\n\n if (this.isPanning) {\n this.isPanning = false;\n this.callbacks.onPanEnd?.();\n }\n\n // Double-tap detection (suppressed after pinch)\n if (e.changedTouches.length === 1 && e.touches.length === 0) {\n if (this.wasPinching) {\n this.wasPinching = false;\n this.lastTouchEnd = 0;\n return;\n }\n const now = Date.now();\n if (now - this.lastTouchEnd < 300) {\n const touch = e.changedTouches[0];\n this.callbacks.onDoubleTap?.(touch.clientX, touch.clientY);\n }\n this.lastTouchEnd = now;\n }\n }\n\n private getTouchDistance(touches: TouchList): number {\n const dx = touches[0].clientX - touches[1].clientX;\n const dy = touches[0].clientY - touches[1].clientY;\n return Math.sqrt(dx * dx + dy * dy);\n }\n\n destroy(): void {\n this.cleanups.forEach((fn) => fn());\n this.cleanups = [];\n }\n}\n","import { addClass, removeClass } from '../utils/dom';\nimport { GestureRecognizer } from './gestures';\n\n/** Safari's proprietary GestureEvent (not in standard TypeScript lib) */\ninterface SafariGestureEvent extends UIEvent {\n readonly scale: number;\n readonly rotation: number;\n}\n\ndeclare global {\n interface WindowEventMap {\n gesturestart: SafariGestureEvent;\n gesturechange: SafariGestureEvent;\n gestureend: SafariGestureEvent;\n }\n}\n\nexport interface ZoomPanOptions {\n zoomMin: number;\n zoomMax: number;\n onZoom?: (level: number) => void;\n onScrollWithoutZoom?: () => void;\n}\n\nexport class ZoomPan {\n private viewport: HTMLElement;\n private container: HTMLElement;\n private options: ZoomPanOptions;\n private zoom = 1;\n private panX = 0;\n private panY = 0;\n private enabled = true;\n private isDragging = false;\n private dragStartX = 0;\n private dragStartY = 0;\n private lastPanX = 0;\n private lastPanY = 0;\n private gestures: GestureRecognizer | null = null;\n private isGesturing = false;\n private gestureStartZoom = 1;\n private touchStartPanX = 0;\n private touchStartPanY = 0;\n private cleanups: (() => void)[] = [];\n\n constructor(viewport: HTMLElement, container: HTMLElement, options: ZoomPanOptions) {\n this.viewport = viewport;\n this.container = container;\n this.options = options;\n\n this.bindEvents();\n }\n\n private bindEvents(): void {\n // Mouse wheel zoom — only on pinch (ctrlKey) or Ctrl+scroll\n const onWheel = (e: WheelEvent) => {\n if (!this.enabled) return;\n\n // Safari fires ctrlKey wheel events during a gesture — skip if already handled\n if (this.isGesturing) {\n e.preventDefault();\n return;\n }\n\n if (!e.ctrlKey) {\n // Regular scroll — let it pass through to the page\n this.options.onScrollWithoutZoom?.();\n return;\n }\n\n e.preventDefault();\n\n const rect = this.container.getBoundingClientRect();\n const originX = e.clientX - rect.left;\n const originY = e.clientY - rect.top;\n\n // Normalize deltaY: Firefox line-mode (deltaMode=1) uses ~20px per line\n let deltaY = e.deltaY;\n if (e.deltaMode === 1) deltaY *= 20;\n\n const delta = -deltaY * 0.01;\n this.setZoom(this.zoom + delta, originX, originY);\n };\n this.container.addEventListener('wheel', onWheel, { passive: false });\n this.cleanups.push(() => this.container.removeEventListener('wheel', onWheel));\n\n // Safari GestureEvent support (proprietary pinch-to-zoom)\n this.bindSafariGestures();\n\n // Double-click toggle\n const onDblClick = (e: MouseEvent) => {\n if (!this.enabled) return;\n const rect = this.container.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n if (this.zoom > 1) {\n this.resetZoom();\n } else {\n this.setZoom(2, x, y);\n }\n };\n this.container.addEventListener('dblclick', onDblClick);\n this.cleanups.push(() => this.container.removeEventListener('dblclick', onDblClick));\n\n // Mouse drag for panning\n const onMouseDown = (e: MouseEvent) => {\n if (!this.enabled || this.zoom <= 1) return;\n // Only pan with left mouse button\n if (e.button !== 0) return;\n this.isDragging = true;\n this.dragStartX = e.clientX;\n this.dragStartY = e.clientY;\n this.lastPanX = this.panX;\n this.lastPanY = this.panY;\n addClass(this.viewport, 'ci-hotspot-viewport--dragging');\n this.container.style.cursor = 'grabbing';\n e.preventDefault();\n };\n\n const onMouseMove = (e: MouseEvent) => {\n if (!this.isDragging) return;\n const dx = (e.clientX - this.dragStartX) / this.zoom;\n const dy = (e.clientY - this.dragStartY) / this.zoom;\n this.panX = this.lastPanX + dx;\n this.panY = this.lastPanY + dy;\n this.clampPan();\n this.applyTransform();\n };\n\n const onMouseUp = () => {\n if (!this.isDragging) return;\n this.isDragging = false;\n removeClass(this.viewport, 'ci-hotspot-viewport--dragging');\n this.container.style.cursor = this.zoom > 1 ? 'grab' : '';\n };\n\n this.container.addEventListener('mousedown', onMouseDown);\n document.addEventListener('mousemove', onMouseMove);\n document.addEventListener('mouseup', onMouseUp);\n this.cleanups.push(\n () => this.container.removeEventListener('mousedown', onMouseDown),\n () => document.removeEventListener('mousemove', onMouseMove),\n () => document.removeEventListener('mouseup', onMouseUp),\n );\n\n // Touch gestures\n this.gestures = new GestureRecognizer(\n this.container,\n {\n onPinch: (scale, cx, cy) => {\n const rect = this.container.getBoundingClientRect();\n this.setZoom(scale, cx - rect.left, cy - rect.top);\n },\n onPanStart: () => {\n this.touchStartPanX = this.panX;\n this.touchStartPanY = this.panY;\n },\n onPan: (totalDx, totalDy) => {\n if (this.zoom <= 1) return;\n this.panX = this.touchStartPanX + totalDx / this.zoom;\n this.panY = this.touchStartPanY + totalDy / this.zoom;\n this.clampPan();\n this.applyTransform();\n },\n onDoubleTap: (cx, cy) => {\n const rect = this.container.getBoundingClientRect();\n if (this.zoom > 1) {\n this.resetZoom();\n } else {\n this.setZoom(2, cx - rect.left, cy - rect.top);\n }\n },\n },\n () => this.zoom,\n );\n }\n\n private bindSafariGestures(): void {\n // Feature-detect Safari's proprietary GestureEvent\n if (typeof window === 'undefined' || !('GestureEvent' in window)) return;\n\n const onGestureStart = (e: Event) => {\n e.preventDefault();\n this.isGesturing = true;\n this.gestureStartZoom = this.zoom;\n };\n\n const onGestureChange = (e: Event) => {\n e.preventDefault();\n if (!this.enabled) return;\n const ge = e as unknown as SafariGestureEvent;\n const rect = this.container.getBoundingClientRect();\n // Safari GestureEvent may carry cursor coordinates; fall back to center\n const ev = e as unknown as { clientX?: number; clientY?: number };\n const originX = ev.clientX != null ? ev.clientX - rect.left : rect.width / 2;\n const originY = ev.clientY != null ? ev.clientY - rect.top : rect.height / 2;\n this.setZoom(this.gestureStartZoom * ge.scale, originX, originY);\n };\n\n const onGestureEnd = (e: Event) => {\n e.preventDefault();\n this.isGesturing = false;\n };\n\n this.container.addEventListener('gesturestart', onGestureStart as EventListener);\n this.container.addEventListener('gesturechange', onGestureChange as EventListener);\n this.container.addEventListener('gestureend', onGestureEnd as EventListener);\n this.cleanups.push(\n () => this.container.removeEventListener('gesturestart', onGestureStart as EventListener),\n () => this.container.removeEventListener('gesturechange', onGestureChange as EventListener),\n () => this.container.removeEventListener('gestureend', onGestureEnd as EventListener),\n );\n }\n\n setZoom(level: number, originX?: number, originY?: number): void {\n const oldZoom = this.zoom;\n this.zoom = Math.max(this.options.zoomMin, Math.min(this.options.zoomMax, level));\n\n if (originX !== undefined && originY !== undefined && oldZoom !== this.zoom) {\n // Adjust pan to zoom toward the origin point\n const containerWidth = this.container.offsetWidth;\n const containerHeight = this.container.offsetHeight;\n const scaleChange = this.zoom / oldZoom;\n const ox = originX / containerWidth;\n const oy = originY / containerHeight;\n this.panX = this.panX - (containerWidth * ox * (scaleChange - 1)) / this.zoom;\n this.panY = this.panY - (containerHeight * oy * (scaleChange - 1)) / this.zoom;\n }\n\n this.clampPan();\n this.applyTransform();\n this.updateCursor();\n this.options.onZoom?.(this.zoom);\n }\n\n getZoom(): number {\n return this.zoom;\n }\n\n resetZoom(): void {\n this.zoom = 1;\n this.panX = 0;\n this.panY = 0;\n this.applyTransform();\n this.updateCursor();\n this.options.onZoom?.(1);\n }\n\n pan(dx: number, dy: number): void {\n if (this.zoom <= 1) return;\n this.panX += dx / this.zoom;\n this.panY += dy / this.zoom;\n this.clampPan();\n this.applyTransform();\n }\n\n enable(): void {\n this.enabled = true;\n }\n\n disable(): void {\n this.enabled = false;\n }\n\n private clampPan(): void {\n const containerWidth = this.container.offsetWidth;\n const containerHeight = this.container.offsetHeight;\n\n // Maximum pan so image edge stays at container edge.\n // With transform-origin 0 0 and scale() before translate(), valid range is [-max, 0]:\n // pan=0 → top-left visible, pan=-max → bottom-right visible.\n const maxPanX = (containerWidth * (this.zoom - 1)) / this.zoom;\n const maxPanY = (containerHeight * (this.zoom - 1)) / this.zoom;\n\n this.panX = Math.max(-maxPanX, Math.min(0, this.panX));\n this.panY = Math.max(-maxPanY, Math.min(0, this.panY));\n\n if (this.zoom <= 1) {\n this.panX = 0;\n this.panY = 0;\n }\n }\n\n private applyTransform(): void {\n this.viewport.style.transform = `scale(${this.zoom}) translate(${this.panX}px, ${this.panY}px)`;\n this.viewport.style.setProperty('--zoom', String(this.zoom));\n }\n\n private updateCursor(): void {\n if (!this.isDragging) {\n this.container.style.cursor = this.zoom > 1 ? 'grab' : '';\n }\n }\n\n destroy(): void {\n this.cleanups.forEach((fn) => fn());\n this.cleanups = [];\n this.gestures?.destroy();\n this.gestures = null;\n this.viewport.style.transform = '';\n this.viewport.style.removeProperty('--zoom');\n this.container.style.cursor = '';\n }\n}\n","import { createElement } from '../utils/dom';\nimport type { ZoomPan } from './zoom-pan';\n\nexport interface ZoomControlsOptions {\n zoomMin: number;\n zoomMax: number;\n zoomStep?: number;\n position?: string;\n}\n\n/** Create zoom controls UI (+/−/reset) */\nexport function createZoomControls(\n container: HTMLElement,\n zoomPan: ZoomPan,\n options: ZoomControlsOptions,\n): { element: HTMLElement; update: () => void; destroy: () => void } {\n const step = options.zoomStep || 0.5;\n const controls = createElement('div', 'ci-hotspot-zoom-controls');\n controls.dataset.position = options.position || 'bottom-right';\n\n const btnIn = createElement('button', 'ci-hotspot-zoom-in', {\n 'aria-label': 'Zoom in',\n 'type': 'button',\n });\n btnIn.innerHTML = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" x2=\"16.65\" y1=\"21\" y2=\"16.65\"/><line x1=\"11\" x2=\"11\" y1=\"8\" y2=\"14\"/><line x1=\"8\" x2=\"14\" y1=\"11\" y2=\"11\"/></svg>';\n\n const btnOut = createElement('button', 'ci-hotspot-zoom-out', {\n 'aria-label': 'Zoom out',\n 'type': 'button',\n });\n btnOut.innerHTML = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" x2=\"16.65\" y1=\"21\" y2=\"16.65\"/><line x1=\"8\" x2=\"14\" y1=\"11\" y2=\"11\"/></svg>';\n\n const btnReset = createElement('button', 'ci-hotspot-zoom-reset', {\n 'aria-label': 'Reset zoom',\n 'type': 'button',\n });\n btnReset.innerHTML = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\"/><path d=\"M3 3v5h5\"/></svg>';\n\n controls.appendChild(btnIn);\n controls.appendChild(btnOut);\n controls.appendChild(btnReset);\n\n btnIn.addEventListener('click', (e) => {\n e.stopPropagation();\n zoomPan.setZoom(zoomPan.getZoom() + step);\n updateState();\n });\n\n btnOut.addEventListener('click', (e) => {\n e.stopPropagation();\n zoomPan.setZoom(zoomPan.getZoom() - step);\n updateState();\n });\n\n btnReset.addEventListener('click', (e) => {\n e.stopPropagation();\n zoomPan.resetZoom();\n updateState();\n });\n\n function updateState(): void {\n const zoom = zoomPan.getZoom();\n btnIn.disabled = zoom >= options.zoomMax;\n btnOut.disabled = zoom <= options.zoomMin;\n btnReset.disabled = Math.abs(zoom - 1) < 0.001;\n }\n\n container.appendChild(controls);\n updateState();\n\n return {\n element: controls,\n update: updateState,\n destroy: () => controls.remove(),\n };\n}\n","const isMac =\n typeof navigator !== 'undefined' && /Mac|iPhone|iPad|iPod/i.test(navigator.userAgent);\n\nconst HINT_TEXT = isMac\n ? '\\u2318 Scroll or pinch to zoom'\n : 'Ctrl + scroll to zoom';\n\nconst HIDE_DELAY = 1500;\n\nexport class ScrollHint {\n private el: HTMLElement;\n private hideTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(parent: HTMLElement) {\n this.el = document.createElement('div');\n this.el.className = 'ci-hotspot-scroll-hint';\n this.el.textContent = HINT_TEXT;\n this.el.setAttribute('aria-hidden', 'true');\n parent.appendChild(this.el);\n }\n\n show(): void {\n // Reset timer on repeated scrolls\n if (this.hideTimer !== null) {\n clearTimeout(this.hideTimer);\n }\n\n this.el.classList.add('ci-hotspot-scroll-hint--visible');\n\n this.hideTimer = setTimeout(() => {\n this.el.classList.remove('ci-hotspot-scroll-hint--visible');\n this.hideTimer = null;\n }, HIDE_DELAY);\n }\n\n destroy(): void {\n if (this.hideTimer !== null) {\n clearTimeout(this.hideTimer);\n this.hideTimer = null;\n }\n this.el.remove();\n }\n}\n","import type { CloudimageConfig } from '../core/types';\n\nconst DEFAULT_DOMAIN = 'cloudimg.io';\nconst DEFAULT_API_VERSION = 'v7';\nconst DEFAULT_LIMIT_FACTOR = 100;\n\n/** Round a width up to the nearest limitFactor */\nexport function roundToLimitFactor(width: number, limitFactor: number = DEFAULT_LIMIT_FACTOR): number {\n return Math.ceil(width / limitFactor) * limitFactor;\n}\n\n/** Get the optimal image width for the container */\nexport function getOptimalWidth(\n containerWidth: number,\n dpr: number = 1,\n zoomLevel: number = 1,\n limitFactor: number = DEFAULT_LIMIT_FACTOR,\n): number {\n const raw = containerWidth * dpr * zoomLevel;\n return roundToLimitFactor(raw, limitFactor);\n}\n\n/** Build a Cloudimage CDN URL */\nexport function buildCloudimageUrl(\n src: string,\n config: CloudimageConfig,\n containerWidth: number,\n zoomLevel: number = 1,\n dpr: number = 1,\n): string {\n const domain = config.domain || DEFAULT_DOMAIN;\n const apiVersion = config.apiVersion || DEFAULT_API_VERSION;\n const limitFactor = config.limitFactor || DEFAULT_LIMIT_FACTOR;\n\n const width = getOptimalWidth(containerWidth, dpr, zoomLevel, limitFactor);\n\n const encodedSrc = encodeURI(src);\n let url = `https://${config.token}.${domain}/${apiVersion}/${encodedSrc}?width=${width}`;\n if (config.params) {\n url += `&${config.params}`;\n }\n return url;\n}\n\n/**\n * Create a ResizeObserver-based handler that updates img src when\n * the container width crosses a limitFactor boundary.\n */\nexport function createResizeHandler(\n img: HTMLImageElement,\n src: string,\n config: CloudimageConfig,\n getZoomLevel: () => number,\n): { observer: ResizeObserver; destroy: () => void } {\n const limitFactor = config.limitFactor || DEFAULT_LIMIT_FACTOR;\n let lastRequestedWidth = 0;\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const containerWidth = entry.contentRect.width;\n if (containerWidth === 0) continue;\n\n const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n const newWidth = getOptimalWidth(containerWidth, dpr, getZoomLevel(), limitFactor);\n\n if (newWidth !== lastRequestedWidth) {\n lastRequestedWidth = newWidth;\n img.src = buildCloudimageUrl(src, config, containerWidth, getZoomLevel(), dpr);\n }\n }\n });\n\n return {\n observer,\n destroy: () => observer.disconnect(),\n };\n}\n","import type { ZoomPan } from '../zoom/zoom-pan';\nimport { addListener } from '../utils/events';\n\nexport interface KeyboardHandlerOptions {\n container: HTMLElement;\n getZoomPan: () => ZoomPan | null;\n onEscape?: () => void;\n onFullscreenToggle?: () => void;\n}\n\nconst PAN_STEP = 50;\nconst ZOOM_STEP = 0.5;\n\n/** Handles keyboard navigation within the hotspot container */\nexport class KeyboardHandler {\n private cleanups: (() => void)[] = [];\n\n constructor(options: KeyboardHandlerOptions) {\n const { container, getZoomPan, onEscape, onFullscreenToggle } = options;\n\n const cleanup = addListener(container, 'keydown', (e: KeyboardEvent) => {\n const target = e.target as HTMLElement;\n\n // Don't handle keys typed into form elements inside popovers\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT') {\n return;\n }\n\n const zoomPan = getZoomPan();\n\n switch (e.key) {\n case 'Escape':\n onEscape?.();\n break;\n\n case 'ArrowUp':\n if (zoomPan && zoomPan.getZoom() > 1) {\n e.preventDefault();\n zoomPan.pan(0, PAN_STEP);\n }\n break;\n case 'ArrowDown':\n if (zoomPan && zoomPan.getZoom() > 1) {\n e.preventDefault();\n zoomPan.pan(0, -PAN_STEP);\n }\n break;\n case 'ArrowLeft':\n if (zoomPan && zoomPan.getZoom() > 1) {\n e.preventDefault();\n zoomPan.pan(PAN_STEP, 0);\n }\n break;\n case 'ArrowRight':\n if (zoomPan && zoomPan.getZoom() > 1) {\n e.preventDefault();\n zoomPan.pan(-PAN_STEP, 0);\n }\n break;\n\n case '+':\n case '=':\n if (zoomPan) {\n e.preventDefault();\n zoomPan.setZoom(zoomPan.getZoom() + ZOOM_STEP);\n }\n break;\n case '-':\n if (zoomPan) {\n e.preventDefault();\n zoomPan.setZoom(zoomPan.getZoom() - ZOOM_STEP);\n }\n break;\n case '0':\n if (zoomPan) {\n e.preventDefault();\n zoomPan.resetZoom();\n }\n break;\n\n case 'f':\n if (onFullscreenToggle) {\n e.preventDefault();\n onFullscreenToggle();\n }\n break;\n }\n });\n\n this.cleanups.push(cleanup);\n }\n\n destroy(): void {\n this.cleanups.forEach((fn) => fn());\n this.cleanups = [];\n }\n}\n","import { addListener } from '../utils/events';\n\nconst FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\n/** Get all focusable elements within a container */\nexport function getFocusableElements(container: HTMLElement): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));\n}\n\n/** Create a focus trap within a popover element */\nexport function createFocusTrap(\n popover: HTMLElement,\n returnFocusTo: HTMLElement,\n): { activate: () => void; deactivate: () => void; destroy: () => void } {\n let active = false;\n let cleanup: (() => void) | null = null;\n\n function activate(): void {\n if (active) return;\n active = true;\n\n const focusable = getFocusableElements(popover);\n if (focusable.length === 0) return;\n\n // Focus first element\n focusable[0].focus();\n\n cleanup = addListener(popover, 'keydown', (e: KeyboardEvent) => {\n if (e.key !== 'Tab') return;\n\n const currentFocusable = getFocusableElements(popover);\n if (currentFocusable.length === 0) return;\n\n const first = currentFocusable[0];\n const last = currentFocusable[currentFocusable.length - 1];\n\n if (e.shiftKey && document.activeElement === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n });\n }\n\n function deactivate(): void {\n if (!active) return;\n active = false;\n cleanup?.();\n cleanup = null;\n returnFocusTo.focus();\n }\n\n function destroy(): void {\n deactivate();\n }\n\n return { activate, deactivate, destroy };\n}\n\n","import { isBrowser, createElement } from '../utils/dom';\n\nlet liveRegion: HTMLElement | null = null;\nlet liveRegionRefCount = 0;\n\n/** Announce a message to screen readers via a live region */\nexport function announceToScreenReader(message: string): void {\n if (!isBrowser()) return;\n\n if (!liveRegion) {\n liveRegion = createElement('div', undefined, {\n 'aria-live': 'polite',\n 'aria-atomic': 'true',\n 'role': 'status',\n });\n liveRegion.style.cssText =\n 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0';\n document.body.appendChild(liveRegion);\n }\n\n // Clear then set to trigger announcement\n liveRegion.textContent = '';\n requestAnimationFrame(() => {\n if (liveRegion) liveRegion.textContent = message;\n });\n}\n\n/** Register an instance that uses the live region */\nexport function acquireLiveRegion(): void {\n liveRegionRefCount++;\n}\n\n/** Release an instance; removes the live region when the last one is released */\nexport function releaseLiveRegion(): void {\n liveRegionRefCount = Math.max(0, liveRegionRefCount - 1);\n if (liveRegionRefCount === 0 && liveRegion) {\n liveRegion.remove();\n liveRegion = null;\n }\n}\n","import { createElement, addClass, removeClass } from '../utils/dom';\nimport { addListener } from '../utils/events';\n\nexport interface FullscreenControlOptions {\n onChange?: (isFullscreen: boolean) => void;\n}\n\nexport interface FullscreenControl {\n element: HTMLElement;\n isFullscreen: () => boolean;\n toggle: () => void;\n enter: () => void;\n exit: () => void;\n destroy: () => void;\n}\n\n// Lucide Maximize2 SVG\nconst MAXIMIZE_SVG =\n '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"15 3 21 3 21 9\"/><polyline points=\"9 21 3 21 3 15\"/><line x1=\"21\" x2=\"14\" y1=\"3\" y2=\"10\"/><line x1=\"3\" x2=\"10\" y1=\"21\" y2=\"14\"/></svg>';\n\n// Lucide Minimize2 SVG\nconst MINIMIZE_SVG =\n '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"4 14 10 14 10 20\"/><polyline points=\"20 10 14 10 14 4\"/><line x1=\"14\" x2=\"21\" y1=\"10\" y2=\"3\"/><line x1=\"3\" x2=\"10\" y1=\"21\" y2=\"14\"/></svg>';\n\nfunction isFullscreenEnabled(): boolean {\n return !!(\n document.fullscreenEnabled ||\n (document as any).webkitFullscreenEnabled\n );\n}\n\nfunction getFullscreenElement(): Element | null {\n return (\n document.fullscreenElement ||\n (document as any).webkitFullscreenElement ||\n null\n );\n}\n\nfunction requestFullscreen(el: HTMLElement): Promise<void> {\n if (el.requestFullscreen) return el.requestFullscreen();\n if ((el as any).webkitRequestFullscreen) {\n (el as any).webkitRequestFullscreen();\n return Promise.resolve();\n }\n return Promise.reject(new Error('Fullscreen API not supported'));\n}\n\nfunction exitFullscreen(): Promise<void> {\n if (document.exitFullscreen) return document.exitFullscreen();\n if ((document as any).webkitExitFullscreen) {\n (document as any).webkitExitFullscreen();\n return Promise.resolve();\n }\n return Promise.reject(new Error('Fullscreen API not supported'));\n}\n\n/** Create a fullscreen toggle button for the container */\nexport function createFullscreenControl(\n container: HTMLElement,\n options: FullscreenControlOptions = {},\n): FullscreenControl | null {\n if (!isFullscreenEnabled()) return null;\n\n const btn = createElement('button', 'ci-hotspot-fullscreen-btn', {\n 'aria-label': 'Enter fullscreen',\n 'aria-pressed': 'false',\n 'type': 'button',\n });\n btn.innerHTML = MAXIMIZE_SVG;\n\n const cleanups: (() => void)[] = [];\n\n function isActive(): boolean {\n return getFullscreenElement() === container;\n }\n\n function syncState(): void {\n const fs = isActive();\n btn.innerHTML = fs ? MINIMIZE_SVG : MAXIMIZE_SVG;\n btn.setAttribute('aria-label', fs ? 'Exit fullscreen' : 'Enter fullscreen');\n btn.setAttribute('aria-pressed', String(fs));\n if (fs) {\n addClass(container, 'ci-hotspot-container--fullscreen');\n } else {\n removeClass(container, 'ci-hotspot-container--fullscreen');\n }\n options.onChange?.(fs);\n }\n\n function toggle(): void {\n if (isActive()) {\n exitFullscreen().catch(() => {});\n } else {\n requestFullscreen(container).catch(() => {});\n }\n }\n\n function enter(): void {\n if (!isActive()) {\n requestFullscreen(container).catch(() => {});\n }\n }\n\n function exit(): void {\n if (isActive()) {\n exitFullscreen().catch(() => {});\n }\n }\n\n // Listen to fullscreenchange (handles both standard and webkit)\n const changeCleanup = addListener(document, 'fullscreenchange', syncState);\n cleanups.push(changeCleanup);\n\n document.addEventListener('webkitfullscreenchange', syncState);\n cleanups.push(() => document.removeEventListener('webkitfullscreenchange', syncState));\n\n // Button click\n const clickCleanup = addListener(btn, 'click', (e) => {\n e.stopPropagation();\n toggle();\n });\n cleanups.push(clickCleanup);\n\n container.appendChild(btn);\n\n function destroy(): void {\n // Exit fullscreen if active before cleanup\n if (isActive()) {\n exitFullscreen().catch(() => {});\n }\n removeClass(container, 'ci-hotspot-container--fullscreen');\n cleanups.forEach((fn) => fn());\n cleanups.length = 0;\n btn.remove();\n }\n\n return {\n element: btn,\n isFullscreen: isActive,\n toggle,\n enter,\n exit,\n destroy,\n };\n}\n","import type { CIHotspotConfig, CIHotspotInstance, HotspotItem, NormalizedHotspot, ResolvedCIHotspotConfig, Scene, SceneTransition, TriggerMode } from './types';\nimport { mergeConfig, parseDataAttributes, validateConfig } from './config';\nimport { getElement, createElement, addClass, removeClass, injectStyles } from '../utils/dom';\nimport { normalizeToPercent } from '../utils/coordinates';\nimport { addListener } from '../utils/events';\nimport { createMarker, setMarkerActive, setMarkerHidden, destroyMarker } from '../markers/marker';\nimport { Popover } from '../popover/popover';\nimport { ZoomPan } from '../zoom/zoom-pan';\nimport { createZoomControls } from '../zoom/controls';\nimport { ScrollHint } from '../zoom/scroll-hint';\nimport { buildCloudimageUrl, createResizeHandler } from '../utils/cloudimage';\nimport { KeyboardHandler } from '../a11y/keyboard';\nimport { createFocusTrap } from '../a11y/focus';\nimport { announceToScreenReader, acquireLiveRegion, releaseLiveRegion } from '../a11y/aria';\nimport { createFullscreenControl, type FullscreenControl } from '../fullscreen/fullscreen';\nimport cssText from '../styles/index.css?inline';\n\nexport class CIHotspot implements CIHotspotInstance {\n private config: ResolvedCIHotspotConfig;\n private rootEl: HTMLElement;\n private containerEl!: HTMLElement;\n private viewportEl!: HTMLElement;\n private imgEl!: HTMLImageElement;\n private markersEl!: HTMLElement;\n private markers = new Map<string, HTMLButtonElement>();\n private popovers = new Map<string, Popover>();\n private normalizedHotspots = new Map<string, NormalizedHotspot>();\n private scrollHint: ScrollHint | null = null;\n private zoomPan: ZoomPan | null = null;\n private zoomControls: { element: HTMLElement; update: () => void; destroy: () => void } | null = null;\n private cloudimageHandler: { observer: ResizeObserver; destroy: () => void } | null = null;\n private resizeObserver: ResizeObserver | null = null;\n private keyboardHandler: KeyboardHandler | null = null;\n private fullscreenControl: FullscreenControl | null = null;\n private focusTraps = new Map<string, ReturnType<typeof createFocusTrap>>();\n private cleanups: (() => void)[] = [];\n private hotspotCleanups = new Map<string, (() => void)[]>();\n private imageLoaded = false;\n private destroyed = false;\n private currentSceneId: string | undefined;\n private scenesMap = new Map<string, Scene>();\n private isTransitioning = false;\n private transitionTimer: ReturnType<typeof setTimeout> | undefined;\n private activeTimers = new Set<ReturnType<typeof setTimeout>>();\n private sceneHotspotOverrides = new Map<string, HotspotItem[]>();\n\n constructor(element: HTMLElement | string, config: CIHotspotConfig) {\n this.rootEl = getElement(element);\n this.config = mergeConfig(config);\n validateConfig(this.config);\n\n if (this.config.scenes && this.config.scenes.length > 0) {\n this.initScenes();\n }\n\n acquireLiveRegion();\n injectStyles(cssText);\n this.buildDOM();\n this.applyTheme();\n this.setupImage();\n this.initHotspots();\n\n if (this.config.zoom) {\n this.initZoom();\n }\n\n this.initKeyboard();\n this.initFullscreen();\n this.setupResponsive();\n }\n\n /** Auto-initialize all elements with data-ci-hotspot-src or data-ci-hotspot-scenes attribute */\n static autoInit(root?: HTMLElement): CIHotspotInstance[] {\n const container = root || document;\n const elements = container.querySelectorAll<HTMLElement>(\n '[data-ci-hotspot-src], [data-ci-hotspot-scenes]',\n );\n const instances: CIHotspotInstance[] = [];\n\n elements.forEach((el) => {\n const config = parseDataAttributes(el);\n if (config.src || config.scenes) {\n instances.push(new CIHotspot(el, config as CIHotspotConfig));\n }\n });\n\n return instances;\n }\n\n private buildDOM(): void {\n this.containerEl = createElement('div', 'ci-hotspot-container');\n this.viewportEl = createElement('div', 'ci-hotspot-viewport');\n this.imgEl = createElement('img', 'ci-hotspot-image', {\n alt: this.config.alt || '',\n draggable: 'false',\n });\n this.markersEl = createElement('div', 'ci-hotspot-markers');\n\n this.viewportEl.appendChild(this.imgEl);\n this.viewportEl.appendChild(this.markersEl);\n this.containerEl.appendChild(this.viewportEl);\n\n // Set container role and aria-label for accessibility\n this.containerEl.setAttribute('role', 'group');\n this.containerEl.setAttribute('aria-label', this.config.alt || 'Image with hotspots');\n\n // Fixed aspect-ratio for scenes mode\n if (this.config.sceneAspectRatio) {\n addClass(this.containerEl, 'ci-hotspot-container--fixed-ratio');\n this.viewportEl.style.aspectRatio = this.config.sceneAspectRatio;\n }\n\n // Replace root element content\n this.rootEl.innerHTML = '';\n this.rootEl.appendChild(this.containerEl);\n\n // Loading state\n if (this.config.lazyLoad) {\n addClass(this.containerEl, 'ci-hotspot-loading');\n }\n }\n\n private applyTheme(): void {\n if (this.config.theme === 'dark') {\n addClass(this.containerEl, 'ci-hotspot-theme-dark');\n }\n if (this.config.invertMarkerTheme) {\n addClass(this.containerEl, 'ci-hotspot-marker-inverted');\n }\n }\n\n private setupImage(): void {\n const onLoad = () => {\n removeClass(this.containerEl, 'ci-hotspot-loading');\n this.imageLoaded = true;\n // Normalize pixel coordinates now that we know natural dimensions\n this.renormalizePixelCoordinates();\n // Position markers to match rendered image in fixed-ratio mode\n this.syncMarkersToImage();\n // Now that the image defines the container size, show load-trigger popovers\n this.showLoadTriggerPopovers();\n };\n\n this.imgEl.addEventListener('load', onLoad);\n this.cleanups.push(() => this.imgEl.removeEventListener('load', onLoad));\n\n if (this.config.lazyLoad && typeof IntersectionObserver !== 'undefined') {\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries[0]?.isIntersecting) {\n this.loadImage();\n observer.disconnect();\n }\n },\n { threshold: 0.1 },\n );\n observer.observe(this.containerEl);\n this.cleanups.push(() => observer.disconnect());\n } else {\n this.loadImage();\n }\n }\n\n private loadImage(): void {\n if (this.config.cloudimage?.token) {\n const containerWidth = this.containerEl.offsetWidth || 300;\n const dpr = window.devicePixelRatio || 1;\n const zoomLevel = this.zoomPan?.getZoom() || 1;\n this.imgEl.src = buildCloudimageUrl(\n this.config.src,\n this.config.cloudimage,\n containerWidth,\n zoomLevel,\n dpr,\n );\n\n this.cloudimageHandler = createResizeHandler(\n this.imgEl,\n this.config.src,\n this.config.cloudimage,\n () => this.zoomPan?.getZoom() || 1,\n );\n this.cloudimageHandler.observer.observe(this.containerEl);\n this.cleanups.push(() => this.cloudimageHandler?.destroy());\n } else {\n this.imgEl.src = this.config.src;\n }\n }\n\n private initHotspots(): void {\n for (const hotspot of this.config.hotspots) {\n this.addHotspotInternal(hotspot);\n }\n }\n\n private addHotspotInternal(hotspot: HotspotItem): void {\n // Remove existing hotspot with same ID to prevent orphaned DOM\n if (this.markers.has(hotspot.id)) {\n const oldMarker = this.markers.get(hotspot.id)!;\n destroyMarker(oldMarker);\n this.markers.delete(hotspot.id);\n this.popovers.get(hotspot.id)?.destroy();\n this.popovers.delete(hotspot.id);\n const fns = this.hotspotCleanups.get(hotspot.id);\n if (fns) { fns.forEach((fn) => fn()); this.hotspotCleanups.delete(hotspot.id); }\n const trap = this.focusTraps.get(hotspot.id);\n if (trap) { trap.destroy(); this.focusTraps.delete(hotspot.id); }\n }\n\n // Normalize coordinates\n const { x, y } = normalizeToPercent(\n hotspot.x,\n hotspot.y,\n this.imgEl.naturalWidth || 1000,\n this.imgEl.naturalHeight || 1000,\n );\n\n const normalized: NormalizedHotspot = { ...hotspot, x, y };\n this.normalizedHotspots.set(hotspot.id, normalized);\n\n // Create marker\n const pulse = this.config.pulse !== false;\n const marker = createMarker(normalized, pulse);\n this.markers.set(hotspot.id, marker);\n this.markersEl.appendChild(marker);\n\n const triggerMode = hotspot.trigger || this.config.trigger || 'hover';\n\n // navigateTo hotspots: hover shows destination info, click navigates\n if (hotspot.navigateTo) {\n const popoverHotspot = this.enrichNavigateHotspot(hotspot);\n const hasContent = !!(popoverHotspot.data || popoverHotspot.content || this.config.renderPopover);\n\n if (hasContent) {\n const placement = hotspot.placement || this.config.placement || 'top';\n const popover = new Popover(popoverHotspot, {\n placement,\n triggerMode: 'hover',\n renderFn: this.config.renderPopover,\n onOpen: this.config.onOpen,\n onClose: this.config.onClose,\n });\n this.popovers.set(hotspot.id, popover);\n popover.mount(this.containerEl, marker);\n this.bindNavigateTrigger(hotspot, marker, popover);\n } else {\n this.bindNavigateTrigger(hotspot, marker);\n }\n return;\n }\n\n // Create popover for regular hotspots\n const placement = hotspot.placement || this.config.placement || 'top';\n\n const popover = new Popover(hotspot, {\n placement,\n triggerMode,\n renderFn: this.config.renderPopover,\n onOpen: this.config.onOpen,\n onClose: this.config.onClose,\n });\n this.popovers.set(hotspot.id, popover);\n popover.mount(this.containerEl, marker);\n\n // Bind triggers\n this.bindTrigger(hotspot, marker, popover, triggerMode);\n\n // Handle per-hotspot trigger override for 'load'\n if (triggerMode === 'load' && this.imageLoaded) {\n this.closeAll();\n popover.show();\n setMarkerActive(marker, true);\n this.ensureFocusTrap(hotspot.id, popover.element, marker);\n this.focusTraps.get(hotspot.id)?.activate();\n }\n }\n\n /** For navigateTo hotspots without explicit data/content, generate popover content from the destination scene */\n private enrichNavigateHotspot(hotspot: HotspotItem): HotspotItem {\n if (hotspot.data || hotspot.content) return hotspot;\n const destScene = this.scenesMap.get(hotspot.navigateTo!);\n if (!destScene) return hotspot;\n return {\n ...hotspot,\n data: { title: hotspot.label || destScene.alt || destScene.id },\n };\n }\n\n private static readonly NAVIGATE_ARROW_SVG =\n '<svg class=\"ci-hotspot-navigate-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m9 18 6-6-6-6\"/></svg>';\n\n private bindNavigateTrigger(hotspot: HotspotItem, marker: HTMLButtonElement, popover?: Popover): void {\n addClass(marker, 'ci-hotspot-marker--navigate');\n marker.innerHTML = CIHotspot.NAVIGATE_ARROW_SVG;\n if (hotspot.arrowDirection != null) {\n const svg = marker.querySelector('svg');\n if (svg) svg.style.transform = `rotate(${hotspot.arrowDirection}deg)`;\n }\n const sceneLabel = hotspot.label || hotspot.navigateTo!;\n marker.setAttribute('aria-label', `Navigate to ${sceneLabel}`);\n marker.setAttribute('aria-roledescription', 'navigation hotspot');\n\n // Hover: show/hide popover (only if popover exists) + preload target scene\n if (popover) {\n const enterCleanup = addListener(marker, 'mouseenter', () => {\n this.preloadSceneImage(hotspot.navigateTo!);\n popover.clearHideTimer();\n popover.show();\n setMarkerActive(marker, true);\n });\n\n const leaveCleanup = addListener(marker, 'mouseleave', () => {\n popover.scheduleHide(200);\n this.trackedTimeout(() => {\n if (!popover.isVisible()) {\n setMarkerActive(marker, false);\n }\n }, 250);\n });\n\n const focusCleanup = addListener(marker, 'focus', () => {\n this.preloadSceneImage(hotspot.navigateTo!);\n popover.clearHideTimer();\n popover.show();\n setMarkerActive(marker, true);\n });\n\n const blurCleanup = addListener(marker, 'blur', () => {\n popover.scheduleHide(200);\n this.trackedTimeout(() => {\n if (!popover.isVisible()) setMarkerActive(marker, false);\n }, 250);\n });\n\n this.addHotspotCleanups(hotspot.id, enterCleanup, leaveCleanup, focusCleanup, blurCleanup);\n } else {\n // No popover — still preload on hover/focus\n const preloadEnter = addListener(marker, 'mouseenter', () => {\n this.preloadSceneImage(hotspot.navigateTo!);\n });\n const preloadFocus = addListener(marker, 'focus', () => {\n this.preloadSceneImage(hotspot.navigateTo!);\n });\n this.addHotspotCleanups(hotspot.id, preloadEnter, preloadFocus);\n }\n\n // Click: hide popover and navigate\n const clickCleanup = addListener(marker, 'click', (e) => {\n e.stopPropagation();\n popover?.hide();\n setMarkerActive(marker, false);\n this.config.onClick?.(e, hotspot);\n hotspot.onClick?.(e, hotspot);\n this.goToScene(hotspot.navigateTo!);\n });\n\n // Keyboard: Enter/Space navigates (fire onClick for parity with click handler)\n const keyCleanup = addListener(marker, 'keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n popover?.hide();\n setMarkerActive(marker, false);\n this.config.onClick?.(e, hotspot);\n hotspot.onClick?.(e, hotspot);\n this.goToScene(hotspot.navigateTo!);\n }\n });\n\n this.addHotspotCleanups(hotspot.id, clickCleanup, keyCleanup);\n }\n\n private bindTrigger(\n hotspot: HotspotItem,\n marker: HTMLButtonElement,\n popover: Popover,\n triggerMode: TriggerMode,\n ): void {\n if (triggerMode === 'hover') {\n this.bindHoverTrigger(hotspot, marker, popover);\n } else if (triggerMode === 'click' || triggerMode === 'load') {\n this.bindClickTrigger(hotspot, marker, popover);\n }\n this.bindKeyboardTrigger(hotspot, marker, popover, triggerMode);\n }\n\n private bindHoverTrigger(hotspot: HotspotItem, marker: HTMLButtonElement, popover: Popover): void {\n const enterCleanup = addListener(marker, 'mouseenter', () => {\n popover.clearHideTimer();\n popover.show();\n setMarkerActive(marker, true);\n });\n\n const leaveCleanup = addListener(marker, 'mouseleave', () => {\n popover.scheduleHide(200);\n this.trackedTimeout(() => {\n if (!popover.isVisible()) setMarkerActive(marker, false);\n }, 250);\n });\n\n this.addHotspotCleanups(hotspot.id, enterCleanup, leaveCleanup);\n }\n\n private bindClickTrigger(hotspot: HotspotItem, marker: HTMLButtonElement, popover: Popover): void {\n const trap = createFocusTrap(popover.element, marker);\n this.focusTraps.set(hotspot.id, trap);\n\n const clickCleanup = addListener(marker, 'click', (e) => {\n e.stopPropagation();\n this.config.onClick?.(e, hotspot);\n hotspot.onClick?.(e, hotspot);\n\n if (popover.isVisible()) {\n popover.hide();\n setMarkerActive(marker, false);\n trap.deactivate();\n } else {\n this.closeAll();\n popover.show();\n setMarkerActive(marker, true);\n trap.activate();\n }\n });\n\n const outsideCleanup = addListener(document, 'click', (e) => {\n if (\n popover.isVisible() &&\n !hotspot.keepOpen &&\n !popover.element.contains(e.target as Node) &&\n !marker.contains(e.target as Node)\n ) {\n popover.hide();\n setMarkerActive(marker, false);\n trap.deactivate();\n }\n });\n\n this.addHotspotCleanups(hotspot.id, clickCleanup, outsideCleanup);\n }\n\n private bindKeyboardTrigger(\n hotspot: HotspotItem,\n marker: HTMLButtonElement,\n popover: Popover,\n triggerMode: TriggerMode,\n ): void {\n const focusCleanup = addListener(marker, 'focus', () => {\n if (triggerMode === 'hover') {\n popover.clearHideTimer();\n popover.show();\n setMarkerActive(marker, true);\n }\n });\n\n const blurCleanup = addListener(marker, 'blur', () => {\n if (triggerMode === 'hover') {\n popover.scheduleHide(200);\n this.trackedTimeout(() => {\n if (!popover.isVisible()) setMarkerActive(marker, false);\n }, 250);\n }\n });\n\n const keyCleanup = addListener(marker, 'keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n this.config.onClick?.(e, hotspot);\n if (popover.isVisible()) {\n popover.hide();\n setMarkerActive(marker, false);\n this.focusTraps.get(hotspot.id)?.deactivate();\n } else {\n this.closeAll();\n popover.show();\n setMarkerActive(marker, true);\n this.ensureFocusTrap(hotspot.id, popover.element, marker);\n this.focusTraps.get(hotspot.id)?.activate();\n }\n } else if (e.key === 'Escape') {\n if (popover.isVisible()) {\n popover.hide();\n setMarkerActive(marker, false);\n this.focusTraps.get(hotspot.id)?.deactivate();\n marker.focus();\n }\n }\n });\n\n this.addHotspotCleanups(hotspot.id, focusCleanup, blurCleanup, keyCleanup);\n }\n\n private renormalizePixelCoordinates(): void {\n const naturalWidth = this.imgEl.naturalWidth;\n const naturalHeight = this.imgEl.naturalHeight;\n if (!naturalWidth || !naturalHeight) return;\n\n for (const hotspot of this.config.hotspots) {\n if (typeof hotspot.x === 'number' || typeof hotspot.y === 'number') {\n const { x, y } = normalizeToPercent(hotspot.x, hotspot.y, naturalWidth, naturalHeight);\n const marker = this.markers.get(hotspot.id);\n if (marker) {\n marker.style.left = `${x}%`;\n marker.style.top = `${y}%`;\n }\n const normalized = this.normalizedHotspots.get(hotspot.id);\n if (normalized) {\n normalized.x = x;\n normalized.y = y;\n }\n }\n }\n }\n\n private showLoadTriggerPopovers(): void {\n for (const [id, popover] of this.popovers) {\n const hotspot = this.normalizedHotspots.get(id);\n const triggerMode = hotspot?.trigger || this.config.trigger || 'hover';\n if (triggerMode === 'load' && !popover.isVisible()) {\n popover.show();\n const marker = this.markers.get(id);\n if (marker) {\n setMarkerActive(marker, true);\n this.ensureFocusTrap(id, popover.element, marker);\n this.focusTraps.get(id)?.activate();\n }\n break;\n }\n }\n }\n\n private initZoom(): void {\n if (this.config.scrollHint !== false) {\n this.scrollHint = new ScrollHint(this.containerEl);\n }\n\n this.zoomPan = new ZoomPan(this.viewportEl, this.containerEl, {\n zoomMin: this.config.zoomMin || 1,\n zoomMax: this.config.zoomMax || 4,\n onZoom: (level) => {\n this.config.onZoom?.(level);\n this.zoomControls?.update();\n // Update all popover positions\n for (const [, popover] of this.popovers) {\n if (popover.isVisible()) {\n popover.updatePosition();\n }\n }\n },\n onScrollWithoutZoom: () => this.scrollHint?.show(),\n });\n\n if (this.config.zoomControls !== false) {\n this.zoomControls = createZoomControls(this.containerEl, this.zoomPan, {\n zoomMin: this.config.zoomMin || 1,\n zoomMax: this.config.zoomMax || 4,\n position: this.config.zoomControlsPosition,\n });\n }\n }\n\n private setupResponsive(): void {\n if (typeof ResizeObserver === 'undefined') return;\n\n let rafId = 0;\n this.resizeObserver = new ResizeObserver(() => {\n if (rafId) return;\n rafId = requestAnimationFrame(() => {\n rafId = 0;\n if (this.destroyed) return;\n // Re-sync markers position in fixed-ratio mode\n this.syncMarkersToImage();\n // Check responsive hotspot visibility\n const containerWidth = this.containerEl.offsetWidth;\n for (const [id, hotspot] of this.normalizedHotspots) {\n if (hotspot.responsive) {\n const marker = this.markers.get(id);\n if (!marker) continue;\n const shouldHide =\n (hotspot.responsive.maxWidth && containerWidth > hotspot.responsive.maxWidth) ||\n (hotspot.responsive.minWidth && containerWidth < hotspot.responsive.minWidth);\n setMarkerHidden(marker, !!shouldHide);\n }\n }\n });\n });\n this.cleanups.push(() => { if (rafId) cancelAnimationFrame(rafId); });\n\n this.resizeObserver.observe(this.containerEl);\n this.cleanups.push(() => this.resizeObserver?.disconnect());\n }\n\n /** Position the markers layer to match the rendered image area within a fixed-ratio viewport */\n private syncMarkersToImage(): void {\n if (!this.config.sceneAspectRatio) return;\n\n const vw = this.viewportEl.offsetWidth;\n const vh = this.viewportEl.offsetHeight;\n const nw = this.imgEl.naturalWidth;\n const nh = this.imgEl.naturalHeight;\n if (!vw || !vh || !nw || !nh) return;\n\n const viewportRatio = vw / vh;\n const imageRatio = nw / nh;\n\n let renderedWidth: number;\n let renderedHeight: number;\n let offsetX: number;\n let offsetY: number;\n\n if (imageRatio > viewportRatio) {\n // Image is wider than viewport — fills width, vertical padding\n renderedWidth = vw;\n renderedHeight = vw / imageRatio;\n offsetX = 0;\n offsetY = (vh - renderedHeight) / 2;\n } else {\n // Image is taller than viewport — fills height, horizontal padding\n renderedHeight = vh;\n renderedWidth = vh * imageRatio;\n offsetX = (vw - renderedWidth) / 2;\n offsetY = 0;\n }\n\n this.markersEl.style.left = `${offsetX}px`;\n this.markersEl.style.top = `${offsetY}px`;\n this.markersEl.style.width = `${renderedWidth}px`;\n this.markersEl.style.height = `${renderedHeight}px`;\n // Override inset:0 from base CSS\n this.markersEl.style.right = 'auto';\n this.markersEl.style.bottom = 'auto';\n }\n\n private initScenes(): void {\n for (const scene of this.config.scenes!) {\n this.scenesMap.set(scene.id, scene);\n }\n const initialId = this.config.initialScene || this.config.scenes![0].id;\n const initialScene = this.scenesMap.get(initialId)!;\n this.config.src = initialScene.src;\n this.config.alt = initialScene.alt || '';\n this.config.hotspots = [...initialScene.hotspots];\n this.currentSceneId = initialId;\n }\n\n private initKeyboard(): void {\n this.keyboardHandler = new KeyboardHandler({\n container: this.containerEl,\n getZoomPan: () => this.zoomPan,\n onEscape: () => {\n // If in fullscreen, exit fullscreen first; otherwise close popovers\n if (this.fullscreenControl?.isFullscreen()) {\n this.fullscreenControl.exit();\n return;\n }\n this.closeAll();\n },\n onFullscreenToggle: () => {\n this.fullscreenControl?.toggle();\n },\n });\n }\n\n private initFullscreen(): void {\n if (this.config.fullscreenButton === false) return;\n\n this.fullscreenControl = createFullscreenControl(this.containerEl, {\n onChange: (isFullscreen) => {\n // Reposition visible popovers after the layout shift\n requestAnimationFrame(() => {\n for (const [, popover] of this.popovers) {\n if (popover.isVisible()) {\n popover.updatePosition();\n }\n }\n });\n this.config.onFullscreenChange?.(isFullscreen);\n },\n });\n }\n\n private preloadedScenes = new Set<string>();\n\n private preloadSceneImage(sceneId: string): void {\n if (this.preloadedScenes.has(sceneId)) return;\n const scene = this.scenesMap.get(sceneId);\n if (!scene) return;\n this.preloadedScenes.add(sceneId);\n const img = new Image();\n if (this.config.cloudimage?.token) {\n const containerWidth = this.containerEl.offsetWidth || 300;\n const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n img.src = buildCloudimageUrl(scene.src, this.config.cloudimage, containerWidth, 1, dpr);\n } else {\n img.src = scene.src;\n }\n }\n\n private ensureFocusTrap(id: string, popoverEl: HTMLElement, markerEl: HTMLButtonElement): void {\n if (!this.focusTraps.has(id)) {\n this.focusTraps.set(id, createFocusTrap(popoverEl, markerEl));\n }\n }\n\n /** Create a setTimeout that is automatically cleared on destroy */\n private trackedTimeout(fn: () => void, delay: number): void {\n const id = setTimeout(() => {\n this.activeTimers.delete(id);\n fn();\n }, delay);\n this.activeTimers.add(id);\n }\n\n /** Sync current hotspots back to scene override map so navigating away and back preserves changes */\n private syncCurrentSceneHotspots(): void {\n if (!this.currentSceneId || this.isTransitioning) return;\n this.sceneHotspotOverrides.set(this.currentSceneId, [...this.config.hotspots]);\n }\n\n private addHotspotCleanups(id: string, ...fns: (() => void)[]): void {\n let arr = this.hotspotCleanups.get(id);\n if (!arr) {\n arr = [];\n this.hotspotCleanups.set(id, arr);\n }\n arr.push(...fns);\n }\n\n private clearHotspots(): void {\n for (const fns of this.hotspotCleanups.values()) {\n fns.forEach((fn) => fn());\n }\n this.hotspotCleanups.clear();\n\n for (const [, popover] of this.popovers) {\n popover.destroy();\n }\n this.popovers.clear();\n\n for (const [, marker] of this.markers) {\n destroyMarker(marker);\n }\n this.markers.clear();\n this.normalizedHotspots.clear();\n\n for (const [, trap] of this.focusTraps) {\n trap.destroy();\n }\n this.focusTraps.clear();\n }\n\n /** Read scene transition duration from CSS variable (handles both ms and s units) */\n private getSceneTransitionDuration(): number {\n if (typeof getComputedStyle === 'undefined') return 400;\n const val = getComputedStyle(this.containerEl).getPropertyValue('--ci-hotspot-scene-transition-duration').trim();\n const num = parseFloat(val);\n if (isNaN(num)) return 400;\n // If the value ends with 's' but not 'ms', it's in seconds\n if (val.endsWith('s') && !val.endsWith('ms')) return num * 1000;\n return num;\n }\n\n private performSceneTransition(\n scene: Scene,\n transition: SceneTransition,\n slideReverse: boolean,\n onComplete: () => void,\n ): void {\n if (transition === 'none') {\n this.clearHotspots();\n this.switchToScene(scene);\n onComplete();\n return;\n }\n\n const transitionDuration = this.getSceneTransitionDuration();\n\n addClass(this.containerEl, 'ci-hotspot-scene-transitioning');\n\n const incomingImg = createElement('img', 'ci-hotspot-scene-incoming', {\n alt: scene.alt || '',\n draggable: 'false',\n }) as HTMLImageElement;\n\n if (this.config.cloudimage?.token) {\n const containerWidth = this.containerEl.offsetWidth || 300;\n const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n incomingImg.src = buildCloudimageUrl(scene.src, this.config.cloudimage, containerWidth, 1, dpr);\n } else {\n incomingImg.src = scene.src;\n }\n\n const doTransition = () => {\n if (this.destroyed) return;\n\n if (transition === 'fade') {\n addClass(incomingImg, 'ci-hotspot-scene-fade-in');\n addClass(this.imgEl, 'ci-hotspot-scene-fade-out');\n } else if (transition === 'slide') {\n const suffix = slideReverse ? '-reverse' : '';\n addClass(incomingImg, `ci-hotspot-scene-slide-in${suffix}`);\n addClass(this.imgEl, `ci-hotspot-scene-slide-out${suffix}`);\n }\n\n this.viewportEl.insertBefore(incomingImg, this.markersEl);\n\n this.transitionTimer = setTimeout(() => {\n this.transitionTimer = undefined;\n if (this.destroyed) return;\n\n this.clearHotspots();\n this.switchToScene(scene);\n\n const finish = () => {\n incomingImg.remove();\n removeClass(this.imgEl, 'ci-hotspot-scene-fade-out');\n removeClass(this.imgEl, 'ci-hotspot-scene-slide-out');\n removeClass(this.imgEl, 'ci-hotspot-scene-slide-out-reverse');\n removeClass(this.containerEl, 'ci-hotspot-scene-transitioning');\n onComplete();\n };\n\n if (this.imgEl.complete && this.imgEl.naturalWidth > 0) {\n finish();\n } else {\n this.imgEl.addEventListener('load', finish, { once: true });\n this.imgEl.addEventListener('error', finish, { once: true });\n }\n }, transitionDuration);\n };\n\n if (incomingImg.complete) {\n doTransition();\n } else {\n addClass(this.containerEl, 'ci-hotspot-scene-loading');\n incomingImg.onload = () => {\n removeClass(this.containerEl, 'ci-hotspot-scene-loading');\n doTransition();\n };\n incomingImg.onerror = () => {\n if (this.destroyed) return;\n removeClass(this.containerEl, 'ci-hotspot-scene-loading');\n incomingImg.remove();\n removeClass(this.containerEl, 'ci-hotspot-scene-transitioning');\n this.clearHotspots();\n this.switchToScene(scene);\n onComplete();\n };\n }\n }\n\n private switchToScene(scene: Scene): void {\n this.config.src = scene.src;\n this.config.alt = scene.alt || '';\n this.config.hotspots = this.sceneHotspotOverrides.get(scene.id) ?? [...scene.hotspots];\n\n this.imgEl.alt = scene.alt || '';\n\n this.containerEl.setAttribute('aria-label', scene.alt || 'Image with hotspots');\n\n this.imageLoaded = false;\n const onLoad = () => {\n this.imageLoaded = true;\n this.renormalizePixelCoordinates();\n this.syncMarkersToImage();\n this.showLoadTriggerPopovers();\n };\n // Attach listener BEFORE setting src to avoid missing synchronous cache loads\n this.imgEl.addEventListener('load', onLoad, { once: true });\n\n if (this.config.cloudimage?.token) {\n const containerWidth = this.containerEl.offsetWidth || 300;\n const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n this.imgEl.src = buildCloudimageUrl(scene.src, this.config.cloudimage, containerWidth, 1, dpr);\n } else {\n this.imgEl.src = scene.src;\n }\n\n // If image loaded synchronously from cache, the listener may have already fired.\n // If not, trigger the handler manually.\n if (this.imgEl.complete && this.imgEl.naturalWidth > 0 && !this.imageLoaded) {\n this.imgEl.removeEventListener('load', onLoad);\n onLoad();\n }\n\n this.initHotspots();\n }\n\n // === Public API ===\n\n /** Get references to the internal DOM elements */\n getElements(): {\n container: HTMLElement;\n viewport: HTMLElement;\n image: HTMLImageElement;\n markers: HTMLElement;\n } {\n return {\n container: this.containerEl,\n viewport: this.viewportEl,\n image: this.imgEl,\n markers: this.markersEl,\n };\n }\n\n open(id: string): void {\n if (this.destroyed) return;\n const popover = this.popovers.get(id);\n const marker = this.markers.get(id);\n if (!popover || !marker) return;\n if (popover.isVisible()) return;\n this.closeAll();\n popover.show();\n setMarkerActive(marker, true);\n this.focusTraps.get(id)?.activate();\n }\n\n close(id: string): void {\n if (this.destroyed) return;\n const popover = this.popovers.get(id);\n const marker = this.markers.get(id);\n if (popover && marker) {\n popover.hide();\n setMarkerActive(marker, false);\n this.focusTraps.get(id)?.deactivate();\n }\n }\n\n closeAll(): void {\n if (this.destroyed) return;\n for (const [id, popover] of this.popovers) {\n if (popover.isVisible()) {\n popover.hide();\n const marker = this.markers.get(id);\n if (marker) setMarkerActive(marker, false);\n this.focusTraps.get(id)?.deactivate();\n }\n }\n }\n\n setZoom(level: number): void {\n if (this.destroyed) return;\n this.zoomPan?.setZoom(level);\n }\n\n getZoom(): number {\n return this.zoomPan?.getZoom() || 1;\n }\n\n resetZoom(): void {\n if (this.destroyed) return;\n this.zoomPan?.resetZoom();\n }\n\n goToScene(sceneId: string): void {\n if (this.destroyed) return;\n if (this.isTransitioning) return;\n if (!this.scenesMap.size) return;\n if (sceneId === this.currentSceneId) return;\n\n const scene = this.scenesMap.get(sceneId);\n if (!scene) return;\n\n const transition = this.config.sceneTransition || 'fade';\n this.isTransitioning = true;\n\n if (this.zoomPan && this.zoomPan.getZoom() > 1) {\n this.zoomPan.resetZoom();\n }\n\n // Determine slide direction from the triggering hotspot's position\n let slideReverse = false;\n if (transition === 'slide') {\n for (const hotspot of this.config.hotspots) {\n if (hotspot.navigateTo === sceneId) {\n const normalized = this.normalizedHotspots.get(hotspot.id);\n if (normalized && normalized.x <= 50) {\n slideReverse = true;\n }\n break;\n }\n }\n }\n\n // Sync old scene's hotspots before updating currentSceneId\n this.syncCurrentSceneHotspots();\n this.currentSceneId = sceneId;\n\n this.performSceneTransition(scene, transition, slideReverse, () => {\n this.isTransitioning = false;\n\n announceToScreenReader(`Navigated to ${scene.alt || sceneId}`);\n this.config.onSceneChange?.(sceneId, scene);\n\n // Focus first marker if keyboard user\n const firstHotspot = scene.hotspots[0];\n if (firstHotspot) {\n const firstMarker = this.markers.get(firstHotspot.id);\n if (firstMarker && document.activeElement && this.containerEl.contains(document.activeElement)) {\n firstMarker.focus();\n }\n }\n });\n }\n\n getCurrentScene(): string | undefined {\n return this.currentSceneId;\n }\n\n getScenes(): string[] {\n return Array.from(this.scenesMap.keys());\n }\n\n enterFullscreen(): void {\n if (this.destroyed) return;\n this.fullscreenControl?.enter();\n }\n\n exitFullscreen(): void {\n if (this.destroyed) return;\n this.fullscreenControl?.exit();\n }\n\n isFullscreen(): boolean {\n return this.fullscreenControl?.isFullscreen() ?? false;\n }\n\n addHotspot(hotspot: HotspotItem): void {\n if (this.destroyed) return;\n this.config.hotspots.push(hotspot);\n this.addHotspotInternal(hotspot);\n this.syncCurrentSceneHotspots();\n }\n\n removeHotspot(id: string): void {\n if (this.destroyed) return;\n\n // Clean up per-hotspot listeners\n const fns = this.hotspotCleanups.get(id);\n if (fns) {\n fns.forEach((fn) => fn());\n this.hotspotCleanups.delete(id);\n }\n\n // Clean up focus trap\n const trap = this.focusTraps.get(id);\n if (trap) {\n trap.destroy();\n this.focusTraps.delete(id);\n }\n\n const marker = this.markers.get(id);\n const popover = this.popovers.get(id);\n\n if (popover) {\n popover.destroy();\n this.popovers.delete(id);\n }\n if (marker) {\n destroyMarker(marker);\n this.markers.delete(id);\n }\n this.normalizedHotspots.delete(id);\n this.config.hotspots = this.config.hotspots.filter((h) => h.id !== id);\n this.syncCurrentSceneHotspots();\n }\n\n updateHotspot(id: string, updates: Partial<HotspotItem>): void {\n if (this.destroyed) return;\n const idx = this.config.hotspots.findIndex((h) => h.id === id);\n if (idx === -1) return;\n\n // Track marker's DOM position before removal\n const oldMarker = this.markers.get(id);\n const nextSibling = oldMarker?.nextElementSibling || null;\n\n // Remove and re-add with updated config\n const current = this.config.hotspots[idx];\n const updated = { ...current, ...updates };\n this.removeHotspot(id);\n this.config.hotspots.splice(idx, 0, updated);\n this.addHotspotInternal(updated);\n\n // Restore DOM order\n const newMarker = this.markers.get(id);\n if (newMarker && nextSibling && this.markersEl.contains(nextSibling)) {\n this.markersEl.insertBefore(newMarker, nextSibling);\n }\n this.syncCurrentSceneHotspots();\n }\n\n update(config: Partial<CIHotspotConfig>): void {\n if (this.destroyed) return;\n // Destroy current state and rebuild\n this.destroyInternal();\n acquireLiveRegion();\n this.config = mergeConfig({ ...this.config, ...config });\n validateConfig(this.config);\n\n if (this.config.scenes && this.config.scenes.length > 0) {\n this.initScenes();\n }\n\n this.buildDOM();\n this.applyTheme();\n this.setupImage();\n this.initHotspots();\n if (this.config.zoom) {\n this.initZoom();\n }\n this.initKeyboard();\n this.initFullscreen();\n this.setupResponsive();\n }\n\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n this.destroyInternal();\n this.rootEl.innerHTML = '';\n }\n\n private destroyInternal(): void {\n this.imageLoaded = false;\n\n // Clear all tracked timers\n for (const id of this.activeTimers) clearTimeout(id);\n this.activeTimers.clear();\n\n // Run all per-hotspot cleanup functions\n for (const fns of this.hotspotCleanups.values()) {\n fns.forEach((fn) => fn());\n }\n this.hotspotCleanups.clear();\n this.cleanups.forEach((fn) => fn());\n this.cleanups = [];\n\n // Destroy popovers\n for (const [, popover] of this.popovers) {\n popover.destroy();\n }\n this.popovers.clear();\n\n // Destroy markers\n for (const [, marker] of this.markers) {\n destroyMarker(marker);\n }\n this.markers.clear();\n this.normalizedHotspots.clear();\n\n // Destroy focus traps\n for (const [, trap] of this.focusTraps) {\n trap.destroy();\n }\n this.focusTraps.clear();\n\n // Clear scenes state\n this.scenesMap.clear();\n this.preloadedScenes.clear();\n this.sceneHotspotOverrides.clear();\n this.currentSceneId = undefined;\n this.isTransitioning = false;\n if (this.transitionTimer !== undefined) {\n clearTimeout(this.transitionTimer);\n this.transitionTimer = undefined;\n }\n\n // Destroy fullscreen control\n this.fullscreenControl?.destroy();\n this.fullscreenControl = null;\n\n // Destroy keyboard handler\n this.keyboardHandler?.destroy();\n this.keyboardHandler = null;\n\n // Destroy zoom\n this.zoomPan?.destroy();\n this.zoomPan = null;\n this.zoomControls?.destroy();\n this.zoomControls = null;\n this.scrollHint?.destroy();\n this.scrollHint = null;\n\n // Destroy cloudimage handler\n this.cloudimageHandler?.destroy();\n this.cloudimageHandler = null;\n\n // Destroy resize observer\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n\n // Release shared live region\n releaseLiveRegion();\n }\n}\n","import { useRef, useEffect } from 'react';\nimport type { CIHotspotInstance, CIHotspotConfig, HotspotItem } from '../core/types';\nimport { CIHotspot } from '../core/ci-hotspot';\nimport type { UseCIHotspotOptions, UseCIHotspotReturn } from './types';\n\n/** Returns a stable string key for a value, only re-stringifying when the reference changes */\nfunction useStableKey(value: unknown): string {\n const ref = useRef({ value, key: JSON.stringify(value) });\n if (value !== ref.current.value) {\n const key = JSON.stringify(value);\n if (key !== ref.current.key) {\n ref.current = { value, key };\n } else {\n ref.current.value = value;\n }\n }\n return ref.current.key;\n}\n\nexport function useCIHotspot(options: UseCIHotspotOptions): UseCIHotspotReturn {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const instanceRef = useRef<CIHotspotInstance | null>(null);\n const initializedRef = useRef(false);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n\n const config: CIHotspotConfig = {\n src: optionsRef.current.src || '',\n alt: optionsRef.current.alt,\n hotspots: optionsRef.current.hotspots || [],\n trigger: optionsRef.current.trigger,\n zoom: optionsRef.current.zoom,\n zoomMax: optionsRef.current.zoomMax,\n zoomMin: optionsRef.current.zoomMin,\n theme: optionsRef.current.theme,\n pulse: optionsRef.current.pulse,\n placement: optionsRef.current.placement,\n zoomControls: optionsRef.current.zoomControls,\n zoomControlsPosition: optionsRef.current.zoomControlsPosition,\n lazyLoad: optionsRef.current.lazyLoad,\n scrollHint: optionsRef.current.scrollHint,\n cloudimage: optionsRef.current.cloudimage,\n onOpen: optionsRef.current.onOpen,\n onClose: optionsRef.current.onClose,\n onZoom: optionsRef.current.onZoom,\n onClick: optionsRef.current.onClick,\n scenes: optionsRef.current.scenes,\n initialScene: optionsRef.current.initialScene,\n sceneTransition: optionsRef.current.sceneTransition,\n sceneAspectRatio: optionsRef.current.sceneAspectRatio,\n onSceneChange: optionsRef.current.onSceneChange,\n fullscreenButton: optionsRef.current.fullscreenButton,\n onFullscreenChange: optionsRef.current.onFullscreenChange,\n };\n\n // If renderPopover is provided, adapt ReactNode -> HTMLElement\n if (optionsRef.current.renderPopover) {\n config.renderPopover = (hotspot: HotspotItem) => {\n // Return an empty div that React can portal into\n const portalTarget = document.createElement('div');\n portalTarget.dataset.reactPortal = hotspot.id;\n return portalTarget;\n };\n }\n\n const instance = new CIHotspot(el, config);\n instanceRef.current = instance;\n\n return () => {\n instance.destroy();\n instanceRef.current = null;\n };\n }, []);\n\n // Stabilize object/array references for dependency comparison\n const scenesKey = useStableKey(options.scenes);\n const hotspotsKey = useStableKey(options.hotspots);\n const cloudimageKey = useStableKey(options.cloudimage);\n\n // Update on options change (skip initial mount — instance was just created)\n useEffect(() => {\n if (!initializedRef.current) {\n initializedRef.current = true;\n return;\n }\n if (!instanceRef.current) return;\n\n instanceRef.current.update({\n src: options.src || '',\n alt: options.alt,\n hotspots: options.hotspots || [],\n trigger: options.trigger,\n zoom: options.zoom,\n zoomMax: options.zoomMax,\n zoomMin: options.zoomMin,\n theme: options.theme,\n pulse: options.pulse,\n placement: options.placement,\n zoomControls: options.zoomControls,\n zoomControlsPosition: options.zoomControlsPosition,\n lazyLoad: options.lazyLoad,\n scrollHint: options.scrollHint,\n cloudimage: options.cloudimage,\n onOpen: options.onOpen,\n onClose: options.onClose,\n onZoom: options.onZoom,\n onClick: options.onClick,\n scenes: options.scenes,\n initialScene: options.initialScene,\n sceneTransition: options.sceneTransition,\n sceneAspectRatio: options.sceneAspectRatio,\n onSceneChange: options.onSceneChange,\n fullscreenButton: options.fullscreenButton,\n onFullscreenChange: options.onFullscreenChange,\n });\n }, [\n options.src,\n options.alt,\n hotspotsKey,\n options.trigger,\n options.zoom,\n options.zoomMax,\n options.zoomMin,\n options.theme,\n options.pulse,\n options.placement,\n options.zoomControls,\n options.zoomControlsPosition,\n options.lazyLoad,\n options.scrollHint,\n cloudimageKey,\n scenesKey,\n options.initialScene,\n options.sceneTransition,\n options.sceneAspectRatio,\n options.fullscreenButton,\n // Note: Callback props (onOpen, onClose, onZoom, onClick, onSceneChange, onFullscreenChange) are\n // intentionally excluded from this dependency array. They are read from optionsRef\n // at call time, so they always reflect the latest value without triggering re-init.\n ]);\n\n return { containerRef, instance: instanceRef };\n}\n","import React, { forwardRef, useImperativeHandle, useRef, useEffect } from 'react';\nimport { createPortal } from 'react-dom';\nimport { useCIHotspot } from './use-ci-hotspot';\nimport type { CIHotspotViewerProps, CIHotspotViewerRef } from './types';\n\nexport const CIHotspotViewer = forwardRef<CIHotspotViewerRef, CIHotspotViewerProps>(\n function CIHotspotViewer(props, ref) {\n const { className, style, renderPopover, ...options } = props;\n const { containerRef, instance } = useCIHotspot(options);\n const [portalTargets, setPortalTargets] = React.useState<Map<string, HTMLElement>>(new Map());\n\n useImperativeHandle(ref, () => ({\n open: (id: string) => instance.current?.open(id),\n close: (id: string) => instance.current?.close(id),\n closeAll: () => instance.current?.closeAll(),\n setZoom: (level: number) => instance.current?.setZoom(level),\n getZoom: () => instance.current?.getZoom() ?? 1,\n resetZoom: () => instance.current?.resetZoom(),\n addHotspot: (hotspot) => instance.current?.addHotspot(hotspot),\n removeHotspot: (id: string) => instance.current?.removeHotspot(id),\n updateHotspot: (id, updates) => instance.current?.updateHotspot(id, updates),\n goToScene: (sceneId: string) => instance.current?.goToScene(sceneId),\n getCurrentScene: () => instance.current?.getCurrentScene(),\n getScenes: () => instance.current?.getScenes() ?? [],\n enterFullscreen: () => instance.current?.enterFullscreen(),\n exitFullscreen: () => instance.current?.exitFullscreen(),\n isFullscreen: () => instance.current?.isFullscreen() ?? false,\n }));\n\n // Find portal targets for React rendering\n useEffect(() => {\n if (!containerRef.current || !renderPopover) return;\n\n const observer = new MutationObserver(() => {\n const targets = containerRef.current?.querySelectorAll<HTMLElement>('[data-react-portal]');\n if (!targets) return;\n\n const newMap = new Map<string, HTMLElement>();\n targets.forEach((el) => {\n const id = el.dataset.reactPortal;\n if (id) newMap.set(id, el);\n });\n\n // Only update state if the portal targets actually changed\n setPortalTargets((prev) => {\n if (prev.size !== newMap.size) return newMap;\n for (const [id, el] of newMap) {\n if (prev.get(id) !== el) return newMap;\n }\n return prev;\n });\n });\n\n observer.observe(containerRef.current, { childList: true, subtree: true });\n return () => observer.disconnect();\n }, [renderPopover]);\n\n return (\n <>\n <div ref={containerRef} className={className} style={style} />\n {renderPopover && Array.from(portalTargets.entries()).map(([id, target]) => {\n const hotspot = props.hotspots?.find((h) => h.id === id);\n if (!hotspot) return null;\n return createPortal(\n <>{renderPopover(hotspot)}</>,\n target,\n id,\n );\n })}\n </>\n );\n },\n);\n"],"names":["DEFAULT_CONFIG","DATA_ATTR_MAP","parseDataAttributes","element","config","cloudimage","attr","mapping","value","parsed","coerceValue","type","num","mergeConfig","userConfig","validateConfig","sceneIds","scene","hotspot","STYLE_ID","isBrowser","getElement","el","found","createElement","tag","className","attrs","key","addClass","classNames","removeClass","injectStyles","css","style","parseCoordinate","trimmed","normalizeToPercent","x","y","naturalWidth","naturalHeight","px","py","addListener","event","handler","options","createMarker","pulse","marker","classes","setMarkerIcon","sanitizeSVG","svg","root","cleanSVGNode","SVG_BLOCKED_ELEMENTS","node","name","child","icon","img","iconEl","setMarkerActive","active","setMarkerHidden","hidden","destroyMarker","computePosition","markerEl","popoverEl","containerEl","markerRect","containerRect","popoverWidth","popoverHeight","markerCenterX","markerCenterY","markerTop","markerBottom","markerLeft","markerRight","containerWidth","containerHeight","gap","space","placement","getAutoPlacement","flip","arrowOffset","shifted","shift","max","p","width","height","sx","sy","ALLOWED_TAGS","ALLOWED_ATTRS","SAFE_HREF_PATTERN","SAFE_SRC_PATTERN","SAFE_REL_VALUES","sanitizeHTML","html","body","sanitizeNode","children","tagName","safeTokens","t","renderBuiltInTemplate","data","parts","escapeAttr","bodyParts","escapeHtml","isSafeUrl","ctaText","renderPopoverContent","renderFn","str","url","normalized","Popover","isDialog","content","onEnter","onLeave","_b","_a","delay","result","offset","arrow","fn","_c","GestureRecognizer","callbacks","getZoom","onTouchStart","e","onTouchMove","onTouchEnd","currentDistance","scale","centerX","centerY","_d","totalDx","totalDy","_f","_e","now","touch","touches","dx","dy","ZoomPan","viewport","container","onWheel","rect","originX","originY","deltaY","delta","onDblClick","onMouseDown","onMouseMove","onMouseUp","cx","cy","onGestureStart","onGestureChange","ge","ev","onGestureEnd","level","oldZoom","scaleChange","ox","oy","maxPanX","maxPanY","createZoomControls","zoomPan","step","controls","btnIn","btnOut","btnReset","updateState","zoom","isMac","HINT_TEXT","HIDE_DELAY","ScrollHint","parent","DEFAULT_DOMAIN","DEFAULT_API_VERSION","DEFAULT_LIMIT_FACTOR","roundToLimitFactor","limitFactor","getOptimalWidth","dpr","zoomLevel","raw","buildCloudimageUrl","src","domain","apiVersion","encodedSrc","createResizeHandler","getZoomLevel","lastRequestedWidth","observer","entries","entry","newWidth","PAN_STEP","ZOOM_STEP","KeyboardHandler","getZoomPan","onEscape","onFullscreenToggle","cleanup","target","FOCUSABLE_SELECTOR","getFocusableElements","createFocusTrap","popover","returnFocusTo","activate","focusable","currentFocusable","first","last","deactivate","destroy","liveRegion","liveRegionRefCount","announceToScreenReader","message","acquireLiveRegion","releaseLiveRegion","MAXIMIZE_SVG","MINIMIZE_SVG","isFullscreenEnabled","getFullscreenElement","requestFullscreen","exitFullscreen","createFullscreenControl","btn","cleanups","isActive","syncState","fs","toggle","enter","exit","changeCleanup","clickCleanup","_CIHotspot","cssText","elements","instances","onLoad","oldMarker","fns","trap","triggerMode","popoverHotspot","destScene","sceneLabel","enterCleanup","leaveCleanup","focusCleanup","blurCleanup","preloadEnter","preloadFocus","keyCleanup","outsideCleanup","id","rafId","shouldHide","vw","vh","nw","nh","viewportRatio","imageRatio","renderedWidth","renderedHeight","offsetX","offsetY","initialId","initialScene","isFullscreen","sceneId","arr","val","transition","slideReverse","onComplete","transitionDuration","incomingImg","doTransition","suffix","finish","firstHotspot","firstMarker","h","updates","idx","nextSibling","updated","newMarker","_g","CIHotspot","useStableKey","ref","useRef","useCIHotspot","containerRef","instanceRef","initializedRef","optionsRef","useEffect","portalTarget","instance","scenesKey","hotspotsKey","cloudimageKey","CIHotspotViewer","forwardRef","props","renderPopover","portalTargets","setPortalTargets","React","useImperativeHandle","targets","newMap","prev","jsxs","Fragment","jsx","createPortal"],"mappings":"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"}
|