@rr0/ufoathome 0.8.0 → 0.8.1

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.
@@ -194,11 +194,23 @@ button.preset[aria-pressed="true"] {
194
194
  .frame's content, unchanged) — but it matters when this element is embedded with a definite
195
195
  host size from outside (e.g. <rr0-scene>'s .ufo-overlay sizing this element to fill its own
196
196
  #stage while THAT is fullscreen): it lets .toolbar/.fullscreen-btn, anchored to .stage below,
197
- actually reach that outer element's true edges instead of only .frame's letterboxed ones. */
197
+ actually reach that outer element's true edges instead of only .frame's letterboxed ones.
198
+ display:flex + centering is unconditional (not just under :fullscreen below) for the same
199
+ nested-embedding case: this .stage is never itself the real fullscreen element when nested
200
+ inside <rr0-scene>'s .ufo-overlay (only the *outer* stage is, so :fullscreen never matches
201
+ here even while genuinely full-viewport-sized) — without this, .frame just sat at .stage's
202
+ top-left in that oversized box instead of centered, misaligning every shape's canvas
203
+ coordinates against the outer 3D scene's own (correctly centered) letterboxed content the
204
+ instant .stage's height actually exceeds .frame's. Harmless in the normal standalone case:
205
+ .stage's height already matches .frame's exactly there (see above), so there's no extra
206
+ space to center within regardless. */
198
207
  .stage {
199
208
  position: relative;
200
209
  width: 100%;
201
210
  height: 100%;
211
+ display: flex;
212
+ align-items: center;
213
+ justify-content: center;
202
214
  }
203
215
  /* The browser's own fullscreen UA styles force the fullscreened element (.stage) to fill the
204
216
  whole viewport (100vw/100vh) regardless of its content's aspect ratio. .toolbar/.fullscreen-btn
@@ -206,9 +218,6 @@ button.preset[aria-pressed="true"] {
206
218
  edges, full width, like a normal video player's controls — not stuck to the letterboxed
207
219
  content's own (possibly smaller, centered) box above/around them. */
208
220
  .stage:fullscreen {
209
- display: flex;
210
- align-items: center;
211
- justify-content: center;
212
221
  width: 100vw;
213
222
  height: 100vh;
214
223
  background: #000;
@@ -357,11 +366,16 @@ input[type=range] {
357
366
  always fills .stage's *current* box exactly: .frame's own letterboxed box in normal mode
358
367
  (where .stage's box equals .frame's, same as before), but the *full* fullscreen viewport when
359
368
  .stage is fullscreen. That's what lets the nested <rr0-ufo>'s own toolbar (anchored to *its*
360
- own .stage, which now spans this full box) reach the true screen edges too — its :fullscreen
361
- CSS never actually applies here (only this outer #stage is ever the real browser-fullscreen
362
- element), but it doesn't need to: its .stage{height:100%} plus .frame's own aspect-ratio/max-*
363
- constraints do the same "contain, centered" fit unconditionally, driven purely by this host's
364
- now-definite size, not by :fullscreen matching inside its own shadow root. */
369
+ own .stage, which now spans this full box) reach the true screen edges too — its own
370
+ :fullscreen CSS never actually applies here (only this outer #stage is ever the real
371
+ browser-fullscreen element). Its .frame still needs to end up centered within that now
372
+ oversized box, matching *this* outer .frame's own centered position exactly, or its canvas
373
+ coordinates visibly misalign against this backdrop the instant .stage's height exceeds
374
+ .frame's (every shape shifted by however far off-center .frame landed). aspect-ratio/max-*
375
+ alone does NOT do that — they size .frame correctly but leave it at .stage's default
376
+ top-left, block-flow position; ufoTemplate.ts's own .stage makes display:flex + centering
377
+ unconditional (not gated on :fullscreen matching) specifically so this nested case gets the
378
+ same centering this outer .stage only gets from :fullscreen. */
365
379
  .ufo-overlay {
366
380
  position: absolute;
367
381
  inset: 0;
@@ -184,11 +184,16 @@ const Ku=`
184
184
  always fills .stage's *current* box exactly: .frame's own letterboxed box in normal mode
185
185
  (where .stage's box equals .frame's, same as before), but the *full* fullscreen viewport when
186
186
  .stage is fullscreen. That's what lets the nested <rr0-ufo>'s own toolbar (anchored to *its*
187
- own .stage, which now spans this full box) reach the true screen edges too — its :fullscreen
188
- CSS never actually applies here (only this outer #stage is ever the real browser-fullscreen
189
- element), but it doesn't need to: its .stage{height:100%} plus .frame's own aspect-ratio/max-*
190
- constraints do the same "contain, centered" fit unconditionally, driven purely by this host's
191
- now-definite size, not by :fullscreen matching inside its own shadow root. */
187
+ own .stage, which now spans this full box) reach the true screen edges too — its own
188
+ :fullscreen CSS never actually applies here (only this outer #stage is ever the real
189
+ browser-fullscreen element). Its .frame still needs to end up centered within that now
190
+ oversized box, matching *this* outer .frame's own centered position exactly, or its canvas
191
+ coordinates visibly misalign against this backdrop the instant .stage's height exceeds
192
+ .frame's (every shape shifted by however far off-center .frame landed). aspect-ratio/max-*
193
+ alone does NOT do that — they size .frame correctly but leave it at .stage's default
194
+ top-left, block-flow position; ufoTemplate.ts's own .stage makes display:flex + centering
195
+ unconditional (not gated on :fullscreen matching) specifically so this nested case gets the
196
+ same centering this outer .stage only gets from :fullscreen. */
192
197
  .ufo-overlay {
193
198
  position: absolute;
194
199
  inset: 0;
@@ -232,11 +237,23 @@ const Ku=`
232
237
  .frame's content, unchanged) — but it matters when this element is embedded with a definite
233
238
  host size from outside (e.g. <rr0-scene>'s .ufo-overlay sizing this element to fill its own
234
239
  #stage while THAT is fullscreen): it lets .toolbar/.fullscreen-btn, anchored to .stage below,
235
- actually reach that outer element's true edges instead of only .frame's letterboxed ones. */
240
+ actually reach that outer element's true edges instead of only .frame's letterboxed ones.
241
+ display:flex + centering is unconditional (not just under :fullscreen below) for the same
242
+ nested-embedding case: this .stage is never itself the real fullscreen element when nested
243
+ inside <rr0-scene>'s .ufo-overlay (only the *outer* stage is, so :fullscreen never matches
244
+ here even while genuinely full-viewport-sized) — without this, .frame just sat at .stage's
245
+ top-left in that oversized box instead of centered, misaligning every shape's canvas
246
+ coordinates against the outer 3D scene's own (correctly centered) letterboxed content the
247
+ instant .stage's height actually exceeds .frame's. Harmless in the normal standalone case:
248
+ .stage's height already matches .frame's exactly there (see above), so there's no extra
249
+ space to center within regardless. */
236
250
  .stage {
237
251
  position: relative;
238
252
  width: 100%;
239
253
  height: 100%;
254
+ display: flex;
255
+ align-items: center;
256
+ justify-content: center;
240
257
  }
241
258
  /* The browser's own fullscreen UA styles force the fullscreened element (.stage) to fill the
242
259
  whole viewport (100vw/100vh) regardless of its content's aspect ratio. .toolbar/.fullscreen-btn
@@ -244,9 +261,6 @@ const Ku=`
244
261
  edges, full width, like a normal video player's controls — not stuck to the letterboxed
245
262
  content's own (possibly smaller, centered) box above/around them. */
246
263
  .stage:fullscreen {
247
- display: flex;
248
- align-items: center;
249
- justify-content: center;
250
264
  width: 100vw;
251
265
  height: 100vh;
252
266
  background: #000;
@@ -4758,4 +4772,4 @@ void main() {
4758
4772
  float blur = max(vNearBlur, vHaze);
4759
4773
  gl_FragColor = vec4(color, tex.a * uOpacity * (1.0 - blur * 0.5));
4760
4774
  }
4761
- `;let xr;function wx(){if(xr)return xr;const i=128,e=document.createElement("canvas");e.width=i,e.height=i;const t=e.getContext("2d"),n=t.createRadialGradient(i/2,i/2,0,i/2,i/2,i/2);return n.addColorStop(0,"rgba(255,255,255,1)"),n.addColorStop(.4,"rgba(255,255,255,0.35)"),n.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=n,t.fillRect(0,0,i,i),xr=new Zn(e),xr}function Rx(i){const t=document.createElement("canvas");t.width=128,t.height=128;const n=t.getContext("2d"),s=128/2-2,r=128/2,a=128/2,o="#2e2b26",l="#f4f1e2";n.fillStyle=o,n.beginPath(),n.arc(r,a,s,0,Math.PI*2),n.fill();const c=Gu(i.illuminatedFraction,0,1),h=i.phaseFraction<.5,d=s*Math.abs(1-2*c),u=c<.5;return n.save(),n.beginPath(),n.arc(r,a,s,0,Math.PI*2),n.clip(),n.fillStyle=l,n.beginPath(),h?(n.arc(r,a,s,-Math.PI/2,Math.PI/2,!1),n.ellipse(r,a,d,s,0,Math.PI/2,-Math.PI/2,u)):(n.arc(r,a,s,Math.PI/2,-Math.PI/2,!1),n.ellipse(r,a,d,s,0,-Math.PI/2,Math.PI/2,u)),n.fill(),n.restore(),new Zn(t)}function Cx(i){const e=document.createElement("canvas");e.width=128,e.height=128;const t=e.getContext("2d");return t.fillStyle="rgba(0, 0, 0, 0.55)",t.beginPath(),t.arc(64,64,60,0,Math.PI*2),t.fill(),t.fillStyle="#ffffff",t.font="bold 52px sans-serif",t.textAlign="center",t.textBaseline="middle",t.fillText(i,64,68),new Zn(e)}function Va(i){let e=i;return function(){e|=0,e=e+1831565813|0;let n=Math.imul(e^e>>>15,1|e);return n=n+Math.imul(n^n>>>7,61|n)^n,((n^n>>>14)>>>0)/4294967296}}const Th=new Map;function Px(i){const e=i.byteLength/(4*Float32Array.BYTES_PER_ELEMENT),t=e,n=t*Float32Array.BYTES_PER_ELEMENT;return{count:e,ra:new Float32Array(i,0*n,t),dec:new Float32Array(i,1*n,t),mag:new Float32Array(i,2*n,t),ci:new Float32Array(i,3*n,t)}}function Ix(i){let e=Th.get(i);return e||(e=fetch(i).then(t=>t.arrayBuffer()).then(Px),Th.set(i,e)),e}const Dx=new URL(""+new URL("rain-BvWZrB9h.ogg",import.meta.url).href,import.meta.url).href,Lx=new URL(""+new URL("wind-BjklexY7.ogg",import.meta.url).href,import.meta.url).href,Ux=new URL(""+new URL("thunder-DA48qs_v.wav",import.meta.url).href,import.meta.url).href;class Nx{context;buffers=new Map;ambientSource;ambientGain;ambientKey="none";ambientToken=0;ambientVolume=0;windSource;windGain;windActive=!1;windToken=0;windVolume=0;resume(){if(!this.context){if(typeof AudioContext>"u")return;try{this.context=new AudioContext}catch(e){console.warn("WeatherAudio: Web Audio unavailable, weather sounds disabled:",e);return}}this.context.state==="suspended"&&this.context.resume()}setAmbient(e,t,n){if(!this.context)return;const s=e==="rain"||e==="hail"?e:"none";if(this.ambientVolume=s==="hail"?Math.min(1,t*1.3+.2):s==="rain"?t:0,s!==this.ambientKey){this.ambientKey=s;const a=++this.ambientToken;this.stopSource(this.ambientSource,this.ambientGain),this.ambientSource=void 0,this.ambientGain=void 0,s!=="none"&&this.startLoop(Dx,this.ambientVolume).then(o=>this.applyIfCurrent(o,a,()=>this.ambientToken,l=>{this.ambientSource=l?.source,this.ambientGain=l?.gain,l&&(l.gain.gain.value=this.ambientVolume)}))}else this.ambientGain&&(this.ambientGain.gain.value=this.ambientVolume);this.windVolume=n;const r=n>.03;if(r!==this.windActive){this.windActive=r;const a=++this.windToken;this.stopSource(this.windSource,this.windGain),this.windSource=void 0,this.windGain=void 0,r&&this.startLoop(Lx,this.windVolume).then(o=>this.applyIfCurrent(o,a,()=>this.windToken,l=>{this.windSource=l?.source,this.windGain=l?.gain,l&&(l.gain.gain.value=this.windVolume)}))}else this.windGain&&(this.windGain.gain.value=this.windVolume)}playThunder(){this.context&&this.playOneShot(Ux,.9)}dispose(){this.ambientToken++,this.windToken++,this.stopSource(this.ambientSource,this.ambientGain),this.stopSource(this.windSource,this.windGain),this.ambientSource=void 0,this.ambientGain=void 0,this.windSource=void 0,this.windGain=void 0,this.context?.close(),this.context=void 0,this.buffers.clear()}applyIfCurrent(e,t,n,s){if(t!==n()){e?.source.stop();return}s(e)}async startLoop(e,t){const n=this.context;if(n)try{const s=await this.getBuffer(e),r=n.createBufferSource();r.buffer=s,r.loop=!0;const a=n.createGain();return a.gain.value=t,r.connect(a).connect(n.destination),r.start(),{source:r,gain:a}}catch(s){console.warn("Weather ambient sound failed to load, staying silent:",s);return}}async playOneShot(e,t){const n=this.context;if(n)try{const s=await this.getBuffer(e),r=n.createBufferSource();r.buffer=s;const a=n.createGain();a.gain.value=t,r.connect(a).connect(n.destination),r.start()}catch(s){console.warn("Thunder sound failed to load:",s)}}async getBuffer(e){const t=this.context;if(!t)throw new Error("WeatherAudio: AudioContext not ready — resume() must be called first");let n=this.buffers.get(e);return n||(n=fetch(e).then(s=>{if(!s.ok)throw new Error(`Audio fetch failed (${s.status}): ${e}`);return s.arrayBuffer()}).then(s=>t.decodeAudioData(s)),this.buffers.set(e,n)),n}stopSource(e,t){try{e?.stop()}catch{}e?.disconnect(),t?.disconnect()}}Ch();const Fx={sun:{en:"Sun",fr:"Soleil"},moon:{en:"Moon",fr:"Lune"},Venus:{en:"Venus",fr:"Vénus"},Mars:{en:"Mars",fr:"Mars"},Jupiter:{en:"Jupiter",fr:"Jupiter"},Saturn:{en:"Saturn",fr:"Saturne"}},Ox=["en","fr"],Bx=new URL(""+new URL("stars-mag7.5-DDr9QasD.bin",import.meta.url).href,import.meta.url).href,zx={sun:{altitudeDeg:-3,azimuthDeg:180,magnitude:-26.7},moon:{altitudeDeg:-90,azimuthDeg:0,phase:{phaseFraction:0,illuminatedFraction:0},magnitude:-12.7},planets:[]},Wa={lat:0,lng:0,elevationM:0,headingDeg:void 0,pitchDeg:0,fovDeg:60};class Gx extends HTMLElement{static get observedAttributes(){return["src","star-catalog-src","show-compass"]}shadow;stageElement;frameElement;sceneCanvas;ufoElement;sceneRenderer;bodyTooltip;resizeObserver;lastTimeMs=0;starCatalog;weatherAudio=new Nx;thunderTimeoutId;handleFullscreenChange=()=>this.resizeToStage();handlePointerMove=e=>{this.sceneRenderer.setCompassHovered(!0);const n=this.ufoElement.canvasElement.getBoundingClientRect();if(n.width===0||n.height===0)return;const s=(e.clientX-n.left)/n.width*2-1,r=-((e.clientY-n.top)/n.height*2-1),a=this.sceneRenderer.pickBodyAt(s,r);if(!a){this.bodyTooltip.hidden=!0;return}const o=ll(navigator.languages,Ox);this.bodyTooltip.textContent=Fx[a]?.[o]??a,this.bodyTooltip.hidden=!1;const l=this.stageElement.getBoundingClientRect();this.bodyTooltip.style.left=`${e.clientX-l.left+12}px`,this.bodyTooltip.style.top=`${e.clientY-l.top+12}px`};handlePointerLeave=()=>{this.bodyTooltip.hidden=!0,this.sceneRenderer.setCompassHovered(!1)};handleLightningFlash=()=>{clearTimeout(this.thunderTimeoutId);const e=(.5+Math.random()*3.5)*1e3;this.thunderTimeoutId=window.setTimeout(()=>this.weatherAudio.playThunder(),e)};handleFirstInteraction=()=>{this.weatherAudio.resume(),this.setWeather(this.ufoElement.sighting.weather)};handleTimeUpdate=e=>{this.lastTimeMs=e.detail.time,this.updateAstronomy(this.lastTimeMs)};constructor(){super(),this.shadow=this.attachShadow({mode:"open"});const e=document.createElement("template");e.innerHTML=`<style>${ju}</style>${Qu}`,this.shadow.appendChild(e.content.cloneNode(!0)),this.stageElement=this.shadow.getElementById("stage"),this.frameElement=this.shadow.getElementById("frame"),this.sceneCanvas=this.shadow.getElementById("scene-canvas"),this.sceneRenderer=new vx(this.sceneCanvas,void 0,this.handleLightningFlash),this.bodyTooltip=this.shadow.getElementById("body-tooltip"),this.ufoElement=document.createElement(Ya),this.ufoElement.classList.add("ufo-overlay"),this.ufoElement.style.setProperty("--ufo-canvas-background","transparent"),this.ufoElement.style.setProperty("--ufo-canvas-border","none"),this.ufoElement.fullscreenTarget=this.stageElement,this.shadow.getElementById("ufo-slot").replaceWith(this.ufoElement),this.ufoElement.addEventListener("timeupdate",this.handleTimeUpdate),this.ufoElement.canvasElement.addEventListener("pointermove",this.handlePointerMove),this.ufoElement.canvasElement.addEventListener("pointerleave",this.handlePointerLeave),this.ufoElement.canvasElement.addEventListener("pointerdown",this.handleFirstInteraction,{once:!0})}connectedCallback(){this.resizeToStage(),this.updateAstronomy(this.lastTimeMs),this.loadStars(),this.resizeObserver=new ResizeObserver(()=>this.resizeToStage()),this.resizeObserver.observe(this.frameElement),document.addEventListener("fullscreenchange",this.handleFullscreenChange);const e=this.getAttribute("src");e&&this.loadFromSrc(e)}disconnectedCallback(){this.resizeObserver?.disconnect(),document.removeEventListener("fullscreenchange",this.handleFullscreenChange),this.sceneRenderer.stopTwinkle(),clearTimeout(this.thunderTimeoutId),this.weatherAudio.dispose()}attributeChangedCallback(e,t,n){e==="src"&&n&&n!==t&&this.isConnected&&this.loadFromSrc(n),e==="star-catalog-src"&&n!==t&&this.isConnected&&this.loadStars(),e==="show-compass"&&n!==t&&this.sceneRenderer.setShowCompass(this.hasAttribute("show-compass"))}setCompassForced(e){this.sceneRenderer.setCompassForced(e)}setWeather(e){const t=e??zu;this.sceneRenderer.setWeather(t),this.weatherAudio.setAmbient(t.precipitationType,t.precipitationIntensity,t.windSpeed)}resumeWeatherAudio(){this.weatherAudio.resume()}async loadFromSrc(e){const t=await fetch(e);this.sightingData=await t.json()}get sightingData(){return this.ufoElement.sightingData}set sightingData(e){this.ufoElement.sightingData=e,this.lastTimeMs=0,this.updateAstronomy(0),this.setWeather(this.ufoElement.sighting.weather)}get currentTerrainAttribution(){return this.sceneRenderer.currentTerrainAttribution}resizeToStage(){const e=this.frameElement.getBoundingClientRect(),t=Math.max(1,Math.round(e.width)),n=Math.max(1,Math.round(e.height));this.sceneCanvas.width=t,this.sceneCanvas.height=n,this.sceneRenderer.resize(t,n)}async loadStars(){const e=this.getAttribute("star-catalog-src")??Bx;this.starCatalog=await Ix(e),this.updateAstronomy(this.lastTimeMs)}updateAstronomy(e){const t=this.ufoElement.sighting,n=f0(t,e);this.sceneRenderer.setObserverPose(n??Wa),this.sceneRenderer.setTerrainOrigin(n?.lat,n?.lng);const s=n?.lat??Wa.lat,r=n?.lng??Wa.lng,a=Lu(t.event.time??{},r);if(!a){this.sceneRenderer.setAstronomy(zx);return}const o=new Date(a.getTime()+e),l={lat:s,lng:r,elevationM:n?.elevationM??0},c={...Oa("Sun",o,l),magnitude:Ba("Sun",o)},h={...Oa("Moon",o,l),phase:cv(o),magnitude:Ba("Moon",o)},d=ov.map(u=>({body:u,position:Oa(u,o,l),magnitude:Ba(u,o)}));this.sceneRenderer.setAstronomy({sun:c,moon:h,planets:d,stars:this.starCatalog?{catalog:this.starCatalog,date:o,observer:l}:void 0})}}const ol="rr0-scene";function ku(){Ch(),customElements.get(ol)||customElements.define(ol,Gx)}const Hu={testimonyBy:"Testimony by",about:"About",close:"Close",observation:"Observation",date:"Date",location:"Location",case:"Case",credits:"Credits"},kx=Object.freeze(Object.defineProperty({__proto__:null,eyewitnessMessages_en:Hu},Symbol.toStringTag,{value:"Module"}));ku();const Hx="“Thunder” by Jerimee",Vx="https://creativecommons.org/licenses/by/3.0/",Wx="CC BY 3.0",Xx="https://ufoathome.org";class qx extends HTMLElement{static get observedAttributes(){return["src"]}shadow;sceneElement;toolbarElement;testimonyPrefix;witnessText;witnessSelect;infoButton;infoPanel;infoAppLink;infoObservationHeading;infoObservationList;infoCreditsToggle;infoCreditsList;infoCloseButton;entries=[];currentSrc;infoOpen=!1;creditsOpen=!1;language="en";messages=Hu;constructor(){super(),this.shadow=this.attachShadow({mode:"open"});const e=document.createElement("template");e.innerHTML=`<style>${Ju}</style>${Ku}`,this.shadow.appendChild(e.content.cloneNode(!0)),this.sceneElement=document.createElement(ol),this.shadow.getElementById("ufo-slot").replaceWith(this.sceneElement),this.toolbarElement=this.shadow.getElementById("toolbar"),this.testimonyPrefix=this.shadow.getElementById("testimony-prefix"),this.witnessText=this.shadow.getElementById("witness-text"),this.witnessSelect=this.shadow.getElementById("witness"),this.infoButton=this.shadow.getElementById("info-button"),this.infoPanel=this.shadow.getElementById("info-panel"),this.infoAppLink=this.shadow.getElementById("info-app-link"),this.infoObservationHeading=this.shadow.getElementById("info-observation-heading"),this.infoObservationList=this.shadow.getElementById("info-observation-list"),this.infoCreditsToggle=this.shadow.getElementById("info-credits-toggle"),this.infoCreditsList=this.shadow.getElementById("info-credits-list"),this.infoCloseButton=this.shadow.getElementById("info-close"),this.witnessSelect.addEventListener("change",()=>this.selectWitness(this.witnessSelect.value)),this.infoButton.addEventListener("click",()=>this.toggleInfoPanel()),this.infoCloseButton.addEventListener("click",()=>this.toggleInfoPanel()),this.infoCreditsToggle.addEventListener("click",()=>this.toggleCredits()),this.loadLocaleMessages()}async loadLocaleMessages(){this.language=ll(navigator.languages,wh),this.language!=="en"&&(this.messages=await C0(this.language),this.testimonyPrefix.textContent=this.messages.testimonyBy,this.infoButton.title=this.messages.about,this.infoButton.setAttribute("aria-label",this.messages.about),this.infoCloseButton.setAttribute("aria-label",this.messages.close),this.infoObservationHeading.textContent=this.messages.observation,this.infoCreditsToggle.textContent=this.messages.credits,this.infoOpen&&this.populateInfoPanel(),this.updateTestimonyLine())}connectedCallback(){const e=this.getAttribute("src");e&&this.loadFromSrc(e)}disconnectedCallback(){document.removeEventListener("click",this.handleOutsideClick)}attributeChangedCallback(e,t,n){e==="src"&&n&&n!==t&&this.isConnected&&this.loadFromSrc(n)}async loadFromSrc(e){const n=await(await fetch(e)).json();Array.isArray(n)?await this.loadWitnessUrls(n):this.setEntries([{src:e,sighting:n}])}get witnessUrls(){return this.entries.map(e=>e.src)}set witnessUrls(e){this.loadWitnessUrls(e)}async loadWitnessUrls(e){const t=await Promise.all(e.map(async n=>{const s=await fetch(n);return{src:n,sighting:await s.json()}}));this.setEntries(t)}setEntries(e){this.entries=e,this.warnOnMismatchedCaseIds(e),this.toolbarElement.hidden=e.length===0;const t=e.length>1;this.witnessSelect.hidden=!t,this.witnessText.hidden=t,this.witnessSelect.innerHTML="";for(const s of e){const r=document.createElement("option");r.value=s.src,r.textContent=s.sighting.witnessName??s.sighting.witnessId??s.src,this.witnessSelect.appendChild(r)}const n=e.find(s=>s.src===this.currentSrc)??e[0];n?this.selectWitness(n.src):this.updateTestimonyLine()}warnOnMismatchedCaseIds(e){const t=new Set(e.map(n=>n.sighting.caseId).filter(n=>n!==void 0));t.size>1&&console.warn(`<rr0-eyewitness>: witnesses declare different case ids (${[...t].join(", ")}) — they may not belong to the same case.`)}selectWitness(e){const t=this.entries.find(n=>n.src===e);t&&(this.currentSrc=e,this.witnessSelect.value=e,this.sceneElement.sightingData=t.sighting,this.updateTestimonyLine(),this.infoOpen&&this.populateInfoPanel())}updateTestimonyLine(){const e=this.entries.find(t=>t.src===this.currentSrc);e&&(this.witnessText.textContent=e.sighting.witnessName??e.sighting.witnessId??e.src)}formatDate(e){const t=e.place?.[0],n=Lu(e.time??{},t?.lng??0);if(n)return new Intl.DateTimeFormat(this.language==="fr"?"fr-FR":"en-US",{dateStyle:"long",timeStyle:"short"}).format(n)}formatLocation(e){const t=e.place?.[0];if(t)return`${t.lat.toFixed(4)}, ${t.lng.toFixed(4)}`}toggleInfoPanel(){this.infoOpen=!this.infoOpen,this.infoPanel.hidden=!this.infoOpen,this.infoButton.setAttribute("aria-expanded",String(this.infoOpen)),this.infoOpen?(this.populateInfoPanel(),document.addEventListener("click",this.handleOutsideClick)):(this.creditsOpen=!1,this.infoCreditsList.hidden=!0,this.infoCreditsToggle.setAttribute("aria-expanded","false"),document.removeEventListener("click",this.handleOutsideClick))}handleOutsideClick=e=>{const t=e.composedPath();!t.includes(this.infoPanel)&&!t.includes(this.infoButton)&&this.toggleInfoPanel()};toggleCredits(){this.creditsOpen=!this.creditsOpen,this.infoCreditsList.hidden=!this.creditsOpen,this.infoCreditsToggle.setAttribute("aria-expanded",String(this.creditsOpen))}populateInfoPanel(){const e=this.entries.find(r=>r.src===this.currentSrc);if(this.infoObservationList.innerHTML="",e){const r=this.formatDate(e.sighting);r&&this.appendInfoRow(this.infoObservationList,this.messages.date,r);const a=this.formatLocation(e.sighting);a&&this.appendInfoRow(this.infoObservationList,this.messages.location,a),e.sighting.caseId&&this.appendInfoRow(this.infoObservationList,this.messages.case,e.sighting.caseId)}this.infoAppLink.href=Xx,this.infoAppLink.textContent="UFO@home v0.8.0",this.infoCreditsList.innerHTML="";const t=this.sceneElement.currentTerrainAttribution;if(t){const r=document.createElement("li");r.textContent=t,this.infoCreditsList.appendChild(r)}const n=document.createElement("li");n.textContent=`${Hx} (`;const s=document.createElement("a");s.href=Vx,s.target="_blank",s.rel="noopener",s.textContent=Wx,n.appendChild(s),n.appendChild(document.createTextNode(")")),this.infoCreditsList.appendChild(n)}appendInfoRow(e,t,n){const s=document.createElement("dt");s.textContent=t;const r=document.createElement("dd");r.textContent=n,e.appendChild(s),e.appendChild(r)}}const Ah="rr0-eyewitness";function Yx(){ku(),customElements.get(Ah)||customElements.define(Ah,qx)}Yx();
4775
+ `;let xr;function wx(){if(xr)return xr;const i=128,e=document.createElement("canvas");e.width=i,e.height=i;const t=e.getContext("2d"),n=t.createRadialGradient(i/2,i/2,0,i/2,i/2,i/2);return n.addColorStop(0,"rgba(255,255,255,1)"),n.addColorStop(.4,"rgba(255,255,255,0.35)"),n.addColorStop(1,"rgba(255,255,255,0)"),t.fillStyle=n,t.fillRect(0,0,i,i),xr=new Zn(e),xr}function Rx(i){const t=document.createElement("canvas");t.width=128,t.height=128;const n=t.getContext("2d"),s=128/2-2,r=128/2,a=128/2,o="#2e2b26",l="#f4f1e2";n.fillStyle=o,n.beginPath(),n.arc(r,a,s,0,Math.PI*2),n.fill();const c=Gu(i.illuminatedFraction,0,1),h=i.phaseFraction<.5,d=s*Math.abs(1-2*c),u=c<.5;return n.save(),n.beginPath(),n.arc(r,a,s,0,Math.PI*2),n.clip(),n.fillStyle=l,n.beginPath(),h?(n.arc(r,a,s,-Math.PI/2,Math.PI/2,!1),n.ellipse(r,a,d,s,0,Math.PI/2,-Math.PI/2,u)):(n.arc(r,a,s,Math.PI/2,-Math.PI/2,!1),n.ellipse(r,a,d,s,0,-Math.PI/2,Math.PI/2,u)),n.fill(),n.restore(),new Zn(t)}function Cx(i){const e=document.createElement("canvas");e.width=128,e.height=128;const t=e.getContext("2d");return t.fillStyle="rgba(0, 0, 0, 0.55)",t.beginPath(),t.arc(64,64,60,0,Math.PI*2),t.fill(),t.fillStyle="#ffffff",t.font="bold 52px sans-serif",t.textAlign="center",t.textBaseline="middle",t.fillText(i,64,68),new Zn(e)}function Va(i){let e=i;return function(){e|=0,e=e+1831565813|0;let n=Math.imul(e^e>>>15,1|e);return n=n+Math.imul(n^n>>>7,61|n)^n,((n^n>>>14)>>>0)/4294967296}}const Th=new Map;function Px(i){const e=i.byteLength/(4*Float32Array.BYTES_PER_ELEMENT),t=e,n=t*Float32Array.BYTES_PER_ELEMENT;return{count:e,ra:new Float32Array(i,0*n,t),dec:new Float32Array(i,1*n,t),mag:new Float32Array(i,2*n,t),ci:new Float32Array(i,3*n,t)}}function Ix(i){let e=Th.get(i);return e||(e=fetch(i).then(t=>t.arrayBuffer()).then(Px),Th.set(i,e)),e}const Dx=new URL(""+new URL("rain-BvWZrB9h.ogg",import.meta.url).href,import.meta.url).href,Lx=new URL(""+new URL("wind-BjklexY7.ogg",import.meta.url).href,import.meta.url).href,Ux=new URL(""+new URL("thunder-DA48qs_v.wav",import.meta.url).href,import.meta.url).href;class Nx{context;buffers=new Map;ambientSource;ambientGain;ambientKey="none";ambientToken=0;ambientVolume=0;windSource;windGain;windActive=!1;windToken=0;windVolume=0;resume(){if(!this.context){if(typeof AudioContext>"u")return;try{this.context=new AudioContext}catch(e){console.warn("WeatherAudio: Web Audio unavailable, weather sounds disabled:",e);return}}this.context.state==="suspended"&&this.context.resume()}setAmbient(e,t,n){if(!this.context)return;const s=e==="rain"||e==="hail"?e:"none";if(this.ambientVolume=s==="hail"?Math.min(1,t*1.3+.2):s==="rain"?t:0,s!==this.ambientKey){this.ambientKey=s;const a=++this.ambientToken;this.stopSource(this.ambientSource,this.ambientGain),this.ambientSource=void 0,this.ambientGain=void 0,s!=="none"&&this.startLoop(Dx,this.ambientVolume).then(o=>this.applyIfCurrent(o,a,()=>this.ambientToken,l=>{this.ambientSource=l?.source,this.ambientGain=l?.gain,l&&(l.gain.gain.value=this.ambientVolume)}))}else this.ambientGain&&(this.ambientGain.gain.value=this.ambientVolume);this.windVolume=n;const r=n>.03;if(r!==this.windActive){this.windActive=r;const a=++this.windToken;this.stopSource(this.windSource,this.windGain),this.windSource=void 0,this.windGain=void 0,r&&this.startLoop(Lx,this.windVolume).then(o=>this.applyIfCurrent(o,a,()=>this.windToken,l=>{this.windSource=l?.source,this.windGain=l?.gain,l&&(l.gain.gain.value=this.windVolume)}))}else this.windGain&&(this.windGain.gain.value=this.windVolume)}playThunder(){this.context&&this.playOneShot(Ux,.9)}dispose(){this.ambientToken++,this.windToken++,this.stopSource(this.ambientSource,this.ambientGain),this.stopSource(this.windSource,this.windGain),this.ambientSource=void 0,this.ambientGain=void 0,this.windSource=void 0,this.windGain=void 0,this.context?.close(),this.context=void 0,this.buffers.clear()}applyIfCurrent(e,t,n,s){if(t!==n()){e?.source.stop();return}s(e)}async startLoop(e,t){const n=this.context;if(n)try{const s=await this.getBuffer(e),r=n.createBufferSource();r.buffer=s,r.loop=!0;const a=n.createGain();return a.gain.value=t,r.connect(a).connect(n.destination),r.start(),{source:r,gain:a}}catch(s){console.warn("Weather ambient sound failed to load, staying silent:",s);return}}async playOneShot(e,t){const n=this.context;if(n)try{const s=await this.getBuffer(e),r=n.createBufferSource();r.buffer=s;const a=n.createGain();a.gain.value=t,r.connect(a).connect(n.destination),r.start()}catch(s){console.warn("Thunder sound failed to load:",s)}}async getBuffer(e){const t=this.context;if(!t)throw new Error("WeatherAudio: AudioContext not ready — resume() must be called first");let n=this.buffers.get(e);return n||(n=fetch(e).then(s=>{if(!s.ok)throw new Error(`Audio fetch failed (${s.status}): ${e}`);return s.arrayBuffer()}).then(s=>t.decodeAudioData(s)),this.buffers.set(e,n)),n}stopSource(e,t){try{e?.stop()}catch{}e?.disconnect(),t?.disconnect()}}Ch();const Fx={sun:{en:"Sun",fr:"Soleil"},moon:{en:"Moon",fr:"Lune"},Venus:{en:"Venus",fr:"Vénus"},Mars:{en:"Mars",fr:"Mars"},Jupiter:{en:"Jupiter",fr:"Jupiter"},Saturn:{en:"Saturn",fr:"Saturne"}},Ox=["en","fr"],Bx=new URL(""+new URL("stars-mag7.5-DDr9QasD.bin",import.meta.url).href,import.meta.url).href,zx={sun:{altitudeDeg:-3,azimuthDeg:180,magnitude:-26.7},moon:{altitudeDeg:-90,azimuthDeg:0,phase:{phaseFraction:0,illuminatedFraction:0},magnitude:-12.7},planets:[]},Wa={lat:0,lng:0,elevationM:0,headingDeg:void 0,pitchDeg:0,fovDeg:60};class Gx extends HTMLElement{static get observedAttributes(){return["src","star-catalog-src","show-compass"]}shadow;stageElement;frameElement;sceneCanvas;ufoElement;sceneRenderer;bodyTooltip;resizeObserver;lastTimeMs=0;starCatalog;weatherAudio=new Nx;thunderTimeoutId;handleFullscreenChange=()=>this.resizeToStage();handlePointerMove=e=>{this.sceneRenderer.setCompassHovered(!0);const n=this.ufoElement.canvasElement.getBoundingClientRect();if(n.width===0||n.height===0)return;const s=(e.clientX-n.left)/n.width*2-1,r=-((e.clientY-n.top)/n.height*2-1),a=this.sceneRenderer.pickBodyAt(s,r);if(!a){this.bodyTooltip.hidden=!0;return}const o=ll(navigator.languages,Ox);this.bodyTooltip.textContent=Fx[a]?.[o]??a,this.bodyTooltip.hidden=!1;const l=this.stageElement.getBoundingClientRect();this.bodyTooltip.style.left=`${e.clientX-l.left+12}px`,this.bodyTooltip.style.top=`${e.clientY-l.top+12}px`};handlePointerLeave=()=>{this.bodyTooltip.hidden=!0,this.sceneRenderer.setCompassHovered(!1)};handleLightningFlash=()=>{clearTimeout(this.thunderTimeoutId);const e=(.5+Math.random()*3.5)*1e3;this.thunderTimeoutId=window.setTimeout(()=>this.weatherAudio.playThunder(),e)};handleFirstInteraction=()=>{this.weatherAudio.resume(),this.setWeather(this.ufoElement.sighting.weather)};handleTimeUpdate=e=>{this.lastTimeMs=e.detail.time,this.updateAstronomy(this.lastTimeMs)};constructor(){super(),this.shadow=this.attachShadow({mode:"open"});const e=document.createElement("template");e.innerHTML=`<style>${ju}</style>${Qu}`,this.shadow.appendChild(e.content.cloneNode(!0)),this.stageElement=this.shadow.getElementById("stage"),this.frameElement=this.shadow.getElementById("frame"),this.sceneCanvas=this.shadow.getElementById("scene-canvas"),this.sceneRenderer=new vx(this.sceneCanvas,void 0,this.handleLightningFlash),this.bodyTooltip=this.shadow.getElementById("body-tooltip"),this.ufoElement=document.createElement(Ya),this.ufoElement.classList.add("ufo-overlay"),this.ufoElement.style.setProperty("--ufo-canvas-background","transparent"),this.ufoElement.style.setProperty("--ufo-canvas-border","none"),this.ufoElement.fullscreenTarget=this.stageElement,this.shadow.getElementById("ufo-slot").replaceWith(this.ufoElement),this.ufoElement.addEventListener("timeupdate",this.handleTimeUpdate),this.ufoElement.canvasElement.addEventListener("pointermove",this.handlePointerMove),this.ufoElement.canvasElement.addEventListener("pointerleave",this.handlePointerLeave),this.ufoElement.canvasElement.addEventListener("pointerdown",this.handleFirstInteraction,{once:!0})}connectedCallback(){this.resizeToStage(),this.updateAstronomy(this.lastTimeMs),this.loadStars(),this.resizeObserver=new ResizeObserver(()=>this.resizeToStage()),this.resizeObserver.observe(this.frameElement),document.addEventListener("fullscreenchange",this.handleFullscreenChange);const e=this.getAttribute("src");e&&this.loadFromSrc(e)}disconnectedCallback(){this.resizeObserver?.disconnect(),document.removeEventListener("fullscreenchange",this.handleFullscreenChange),this.sceneRenderer.stopTwinkle(),clearTimeout(this.thunderTimeoutId),this.weatherAudio.dispose()}attributeChangedCallback(e,t,n){e==="src"&&n&&n!==t&&this.isConnected&&this.loadFromSrc(n),e==="star-catalog-src"&&n!==t&&this.isConnected&&this.loadStars(),e==="show-compass"&&n!==t&&this.sceneRenderer.setShowCompass(this.hasAttribute("show-compass"))}setCompassForced(e){this.sceneRenderer.setCompassForced(e)}setWeather(e){const t=e??zu;this.sceneRenderer.setWeather(t),this.weatherAudio.setAmbient(t.precipitationType,t.precipitationIntensity,t.windSpeed)}resumeWeatherAudio(){this.weatherAudio.resume()}async loadFromSrc(e){const t=await fetch(e);this.sightingData=await t.json()}get sightingData(){return this.ufoElement.sightingData}set sightingData(e){this.ufoElement.sightingData=e,this.lastTimeMs=0,this.updateAstronomy(0),this.setWeather(this.ufoElement.sighting.weather)}get currentTerrainAttribution(){return this.sceneRenderer.currentTerrainAttribution}resizeToStage(){const e=this.frameElement.getBoundingClientRect(),t=Math.max(1,Math.round(e.width)),n=Math.max(1,Math.round(e.height));this.sceneCanvas.width=t,this.sceneCanvas.height=n,this.sceneRenderer.resize(t,n)}async loadStars(){const e=this.getAttribute("star-catalog-src")??Bx;this.starCatalog=await Ix(e),this.updateAstronomy(this.lastTimeMs)}updateAstronomy(e){const t=this.ufoElement.sighting,n=f0(t,e);this.sceneRenderer.setObserverPose(n??Wa),this.sceneRenderer.setTerrainOrigin(n?.lat,n?.lng);const s=n?.lat??Wa.lat,r=n?.lng??Wa.lng,a=Lu(t.event.time??{},r);if(!a){this.sceneRenderer.setAstronomy(zx);return}const o=new Date(a.getTime()+e),l={lat:s,lng:r,elevationM:n?.elevationM??0},c={...Oa("Sun",o,l),magnitude:Ba("Sun",o)},h={...Oa("Moon",o,l),phase:cv(o),magnitude:Ba("Moon",o)},d=ov.map(u=>({body:u,position:Oa(u,o,l),magnitude:Ba(u,o)}));this.sceneRenderer.setAstronomy({sun:c,moon:h,planets:d,stars:this.starCatalog?{catalog:this.starCatalog,date:o,observer:l}:void 0})}}const ol="rr0-scene";function ku(){Ch(),customElements.get(ol)||customElements.define(ol,Gx)}const Hu={testimonyBy:"Testimony by",about:"About",close:"Close",observation:"Observation",date:"Date",location:"Location",case:"Case",credits:"Credits"},kx=Object.freeze(Object.defineProperty({__proto__:null,eyewitnessMessages_en:Hu},Symbol.toStringTag,{value:"Module"}));ku();const Hx="“Thunder” by Jerimee",Vx="https://creativecommons.org/licenses/by/3.0/",Wx="CC BY 3.0",Xx="https://ufoathome.org";class qx extends HTMLElement{static get observedAttributes(){return["src"]}shadow;sceneElement;toolbarElement;testimonyPrefix;witnessText;witnessSelect;infoButton;infoPanel;infoAppLink;infoObservationHeading;infoObservationList;infoCreditsToggle;infoCreditsList;infoCloseButton;entries=[];currentSrc;infoOpen=!1;creditsOpen=!1;language="en";messages=Hu;constructor(){super(),this.shadow=this.attachShadow({mode:"open"});const e=document.createElement("template");e.innerHTML=`<style>${Ju}</style>${Ku}`,this.shadow.appendChild(e.content.cloneNode(!0)),this.sceneElement=document.createElement(ol),this.shadow.getElementById("ufo-slot").replaceWith(this.sceneElement),this.toolbarElement=this.shadow.getElementById("toolbar"),this.testimonyPrefix=this.shadow.getElementById("testimony-prefix"),this.witnessText=this.shadow.getElementById("witness-text"),this.witnessSelect=this.shadow.getElementById("witness"),this.infoButton=this.shadow.getElementById("info-button"),this.infoPanel=this.shadow.getElementById("info-panel"),this.infoAppLink=this.shadow.getElementById("info-app-link"),this.infoObservationHeading=this.shadow.getElementById("info-observation-heading"),this.infoObservationList=this.shadow.getElementById("info-observation-list"),this.infoCreditsToggle=this.shadow.getElementById("info-credits-toggle"),this.infoCreditsList=this.shadow.getElementById("info-credits-list"),this.infoCloseButton=this.shadow.getElementById("info-close"),this.witnessSelect.addEventListener("change",()=>this.selectWitness(this.witnessSelect.value)),this.infoButton.addEventListener("click",()=>this.toggleInfoPanel()),this.infoCloseButton.addEventListener("click",()=>this.toggleInfoPanel()),this.infoCreditsToggle.addEventListener("click",()=>this.toggleCredits()),this.loadLocaleMessages()}async loadLocaleMessages(){this.language=ll(navigator.languages,wh),this.language!=="en"&&(this.messages=await C0(this.language),this.testimonyPrefix.textContent=this.messages.testimonyBy,this.infoButton.title=this.messages.about,this.infoButton.setAttribute("aria-label",this.messages.about),this.infoCloseButton.setAttribute("aria-label",this.messages.close),this.infoObservationHeading.textContent=this.messages.observation,this.infoCreditsToggle.textContent=this.messages.credits,this.infoOpen&&this.populateInfoPanel(),this.updateTestimonyLine())}connectedCallback(){const e=this.getAttribute("src");e&&this.loadFromSrc(e)}disconnectedCallback(){document.removeEventListener("click",this.handleOutsideClick)}attributeChangedCallback(e,t,n){e==="src"&&n&&n!==t&&this.isConnected&&this.loadFromSrc(n)}async loadFromSrc(e){const n=await(await fetch(e)).json();Array.isArray(n)?await this.loadWitnessUrls(n):this.setEntries([{src:e,sighting:n}])}get witnessUrls(){return this.entries.map(e=>e.src)}set witnessUrls(e){this.loadWitnessUrls(e)}async loadWitnessUrls(e){const t=await Promise.all(e.map(async n=>{const s=await fetch(n);return{src:n,sighting:await s.json()}}));this.setEntries(t)}setEntries(e){this.entries=e,this.warnOnMismatchedCaseIds(e),this.toolbarElement.hidden=e.length===0;const t=e.length>1;this.witnessSelect.hidden=!t,this.witnessText.hidden=t,this.witnessSelect.innerHTML="";for(const s of e){const r=document.createElement("option");r.value=s.src,r.textContent=s.sighting.witnessName??s.sighting.witnessId??s.src,this.witnessSelect.appendChild(r)}const n=e.find(s=>s.src===this.currentSrc)??e[0];n?this.selectWitness(n.src):this.updateTestimonyLine()}warnOnMismatchedCaseIds(e){const t=new Set(e.map(n=>n.sighting.caseId).filter(n=>n!==void 0));t.size>1&&console.warn(`<rr0-eyewitness>: witnesses declare different case ids (${[...t].join(", ")}) — they may not belong to the same case.`)}selectWitness(e){const t=this.entries.find(n=>n.src===e);t&&(this.currentSrc=e,this.witnessSelect.value=e,this.sceneElement.sightingData=t.sighting,this.updateTestimonyLine(),this.infoOpen&&this.populateInfoPanel())}updateTestimonyLine(){const e=this.entries.find(t=>t.src===this.currentSrc);e&&(this.witnessText.textContent=e.sighting.witnessName??e.sighting.witnessId??e.src)}formatDate(e){const t=e.place?.[0],n=Lu(e.time??{},t?.lng??0);if(n)return new Intl.DateTimeFormat(this.language==="fr"?"fr-FR":"en-US",{dateStyle:"long",timeStyle:"short"}).format(n)}formatLocation(e){const t=e.place?.[0];if(t)return`${t.lat.toFixed(4)}, ${t.lng.toFixed(4)}`}toggleInfoPanel(){this.infoOpen=!this.infoOpen,this.infoPanel.hidden=!this.infoOpen,this.infoButton.setAttribute("aria-expanded",String(this.infoOpen)),this.infoOpen?(this.populateInfoPanel(),document.addEventListener("click",this.handleOutsideClick)):(this.creditsOpen=!1,this.infoCreditsList.hidden=!0,this.infoCreditsToggle.setAttribute("aria-expanded","false"),document.removeEventListener("click",this.handleOutsideClick))}handleOutsideClick=e=>{const t=e.composedPath();!t.includes(this.infoPanel)&&!t.includes(this.infoButton)&&this.toggleInfoPanel()};toggleCredits(){this.creditsOpen=!this.creditsOpen,this.infoCreditsList.hidden=!this.creditsOpen,this.infoCreditsToggle.setAttribute("aria-expanded",String(this.creditsOpen))}populateInfoPanel(){const e=this.entries.find(r=>r.src===this.currentSrc);if(this.infoObservationList.innerHTML="",e){const r=this.formatDate(e.sighting);r&&this.appendInfoRow(this.infoObservationList,this.messages.date,r);const a=this.formatLocation(e.sighting);a&&this.appendInfoRow(this.infoObservationList,this.messages.location,a),e.sighting.caseId&&this.appendInfoRow(this.infoObservationList,this.messages.case,e.sighting.caseId)}this.infoAppLink.href=Xx,this.infoAppLink.textContent="UFO@home v0.8.1",this.infoCreditsList.innerHTML="";const t=this.sceneElement.currentTerrainAttribution;if(t){const r=document.createElement("li");r.textContent=t,this.infoCreditsList.appendChild(r)}const n=document.createElement("li");n.textContent=`${Hx} (`;const s=document.createElement("a");s.href=Vx,s.target="_blank",s.rel="noopener",s.textContent=Wx,n.appendChild(s),n.appendChild(document.createTextNode(")")),this.infoCreditsList.appendChild(n)}appendInfoRow(e,t,n){const s=document.createElement("dt");s.textContent=t;const r=document.createElement("dd");r.textContent=n,e.appendChild(s),e.appendChild(r)}}const Ah="rr0-eyewitness";function Yx(){ku(),customElements.get(Ah)||customElements.define(Ah,qx)}Yx();
@@ -49,11 +49,16 @@ const Xu=`
49
49
  always fills .stage's *current* box exactly: .frame's own letterboxed box in normal mode
50
50
  (where .stage's box equals .frame's, same as before), but the *full* fullscreen viewport when
51
51
  .stage is fullscreen. That's what lets the nested <rr0-ufo>'s own toolbar (anchored to *its*
52
- own .stage, which now spans this full box) reach the true screen edges too — its :fullscreen
53
- CSS never actually applies here (only this outer #stage is ever the real browser-fullscreen
54
- element), but it doesn't need to: its .stage{height:100%} plus .frame's own aspect-ratio/max-*
55
- constraints do the same "contain, centered" fit unconditionally, driven purely by this host's
56
- now-definite size, not by :fullscreen matching inside its own shadow root. */
52
+ own .stage, which now spans this full box) reach the true screen edges too — its own
53
+ :fullscreen CSS never actually applies here (only this outer #stage is ever the real
54
+ browser-fullscreen element). Its .frame still needs to end up centered within that now
55
+ oversized box, matching *this* outer .frame's own centered position exactly, or its canvas
56
+ coordinates visibly misalign against this backdrop the instant .stage's height exceeds
57
+ .frame's (every shape shifted by however far off-center .frame landed). aspect-ratio/max-*
58
+ alone does NOT do that — they size .frame correctly but leave it at .stage's default
59
+ top-left, block-flow position; ufoTemplate.ts's own .stage makes display:flex + centering
60
+ unconditional (not gated on :fullscreen matching) specifically so this nested case gets the
61
+ same centering this outer .stage only gets from :fullscreen. */
57
62
  .ufo-overlay {
58
63
  position: absolute;
59
64
  inset: 0;
@@ -97,11 +102,23 @@ const Xu=`
97
102
  .frame's content, unchanged) — but it matters when this element is embedded with a definite
98
103
  host size from outside (e.g. <rr0-scene>'s .ufo-overlay sizing this element to fill its own
99
104
  #stage while THAT is fullscreen): it lets .toolbar/.fullscreen-btn, anchored to .stage below,
100
- actually reach that outer element's true edges instead of only .frame's letterboxed ones. */
105
+ actually reach that outer element's true edges instead of only .frame's letterboxed ones.
106
+ display:flex + centering is unconditional (not just under :fullscreen below) for the same
107
+ nested-embedding case: this .stage is never itself the real fullscreen element when nested
108
+ inside <rr0-scene>'s .ufo-overlay (only the *outer* stage is, so :fullscreen never matches
109
+ here even while genuinely full-viewport-sized) — without this, .frame just sat at .stage's
110
+ top-left in that oversized box instead of centered, misaligning every shape's canvas
111
+ coordinates against the outer 3D scene's own (correctly centered) letterboxed content the
112
+ instant .stage's height actually exceeds .frame's. Harmless in the normal standalone case:
113
+ .stage's height already matches .frame's exactly there (see above), so there's no extra
114
+ space to center within regardless. */
101
115
  .stage {
102
116
  position: relative;
103
117
  width: 100%;
104
118
  height: 100%;
119
+ display: flex;
120
+ align-items: center;
121
+ justify-content: center;
105
122
  }
106
123
  /* The browser's own fullscreen UA styles force the fullscreened element (.stage) to fill the
107
124
  whole viewport (100vw/100vh) regardless of its content's aspect ratio. .toolbar/.fullscreen-btn
@@ -109,9 +126,6 @@ const Xu=`
109
126
  edges, full width, like a normal video player's controls — not stuck to the letterboxed
110
127
  content's own (possibly smaller, centered) box above/around them. */
111
128
  .stage:fullscreen {
112
- display: flex;
113
- align-items: center;
114
- justify-content: center;
115
129
  width: 100vw;
116
130
  height: 100vh;
117
131
  background: #000;
@@ -22,11 +22,23 @@ const L=`
22
22
  .frame's content, unchanged) — but it matters when this element is embedded with a definite
23
23
  host size from outside (e.g. <rr0-scene>'s .ufo-overlay sizing this element to fill its own
24
24
  #stage while THAT is fullscreen): it lets .toolbar/.fullscreen-btn, anchored to .stage below,
25
- actually reach that outer element's true edges instead of only .frame's letterboxed ones. */
25
+ actually reach that outer element's true edges instead of only .frame's letterboxed ones.
26
+ display:flex + centering is unconditional (not just under :fullscreen below) for the same
27
+ nested-embedding case: this .stage is never itself the real fullscreen element when nested
28
+ inside <rr0-scene>'s .ufo-overlay (only the *outer* stage is, so :fullscreen never matches
29
+ here even while genuinely full-viewport-sized) — without this, .frame just sat at .stage's
30
+ top-left in that oversized box instead of centered, misaligning every shape's canvas
31
+ coordinates against the outer 3D scene's own (correctly centered) letterboxed content the
32
+ instant .stage's height actually exceeds .frame's. Harmless in the normal standalone case:
33
+ .stage's height already matches .frame's exactly there (see above), so there's no extra
34
+ space to center within regardless. */
26
35
  .stage {
27
36
  position: relative;
28
37
  width: 100%;
29
38
  height: 100%;
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
30
42
  }
31
43
  /* The browser's own fullscreen UA styles force the fullscreened element (.stage) to fill the
32
44
  whole viewport (100vw/100vh) regardless of its content's aspect ratio. .toolbar/.fullscreen-btn
@@ -34,9 +46,6 @@ const L=`
34
46
  edges, full width, like a normal video player's controls — not stuck to the letterboxed
35
47
  content's own (possibly smaller, centered) box above/around them. */
36
48
  .stage:fullscreen {
37
- display: flex;
38
- align-items: center;
39
- justify-content: center;
40
49
  width: 100vw;
41
50
  height: 100vh;
42
51
  background: #000;
@@ -134,4 +143,4 @@ input[type=range] {
134
143
  min-width: 3em;
135
144
  text-align: center;
136
145
  }
137
- `;function C(i,e,t){const{bounds:s}=i;return e>=s.x&&e<=s.x+s.width&&t>=s.y&&t<=s.y+s.height}function l(i,e,t){return i+(e-i)*t}function T(i){const e=/^#([0-9a-f]{6})$/i.exec(i);if(!e)return;const t=parseInt(e[1],16);return[t>>16&255,t>>8&255,t&255]}function O(i){return`#${i.map(e=>Math.round(e).toString(16).padStart(2,"0")).join("")}`}function _(i,e,t){const s=T(i),n=T(e);return!s||!n?t<1?i:e:O([l(s[0],n[0],t),l(s[1],n[1],t),l(s[2],n[2],t)])}function R(i,e,t){const s={x:l(i.bounds.x,e.bounds.x,t),y:l(i.bounds.y,e.bounds.y,t),width:l(i.bounds.width,e.bounds.width,t),height:l(i.bounds.height,e.bounds.height,t)},n=l(i.angle,e.angle,t),r=l(i.transparency,e.transparency,t),a=l(i.haloScale,e.haloScale,t),o=_(i.color,e.color,t);return i.kind==="polygon"&&e.kind==="polygon"&&i.points.length===e.points.length?{...i,bounds:s,angle:n,transparency:r,haloScale:a,color:o,points:i.points.map((c,f)=>({x:l(c.x,e.points[f].x,t),y:l(c.y,e.points[f].y,t)}))}:{...t<1?i:e,bounds:s,angle:n,transparency:r,haloScale:a,color:o}}class k{keyframes=[];order=[];addKeyframe(e,t){const s=this.findInsertIndex(e);if(this.keyframes[s]?.t===e){const n=new Set(t.map(r=>r.sourceId));this.keyframes[s]={t:e,shapes:[...this.keyframes[s].shapes.filter(r=>!n.has(r.sourceId)),...t]}}else this.keyframes.splice(s,0,{t:e,shapes:[...t]});for(const n of t)this.order.includes(n.sourceId)||this.order.push(n.sourceId)}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const n=t+s>>>1;this.keyframes[n].t<e?t=n+1:s=n}return t}getKeyframeAt(e){const t=this.findInsertIndex(e),s=this.keyframes[t];return s?.t===e?s:void 0}getShapeAt(e,t){return this.getKeyframeAt(e)?.shapes.find(s=>s.sourceId===t)?.shape}getLatestShapeAt(e,t){let s=this.findInsertIndex(e);for(this.keyframes[s]?.t!==e&&(s-=1);s>=0;s--){const n=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(n)return n.shape}}getInterpolatedShapeAt(e,t){const s=this.findShapeAtOrBefore(e,t);if(s?.t===e)return s.shape;const n=this.findShapeAtOrAfter(e,t);return s?n?R(s.shape,n.shape,(e-s.t)/(n.t-s.t)):s.shape:n?.shape}findShapeAtOrBefore(e,t){let s=this.findInsertIndex(e);for(this.keyframes[s]?.t!==e&&(s-=1);s>=0;s--){const n=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(n)return{t:this.keyframes[s].t,shape:n.shape}}}findShapeAtOrAfter(e,t){for(let s=this.findInsertIndex(e);s<this.keyframes.length;s++){const n=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(n)return{t:this.keyframes[s].t,shape:n.shape}}}hitTest(e,t,s){const n=this.sourceIds;for(let r=n.length-1;r>=0;r--){const a=this.getInterpolatedShapeAt(e,n[r]);if(a&&C(a,t,s))return{sourceId:n[r],shape:a}}}removeSource(e){for(let t=this.keyframes.length-1;t>=0;t--){const s=this.keyframes[t].shapes.filter(n=>n.sourceId!==e);s.length===0?this.keyframes.splice(t,1):s.length!==this.keyframes[t].shapes.length&&(this.keyframes[t]={t:this.keyframes[t].t,shapes:s})}this.order=this.order.filter(t=>t!==e)}bringToFront(e){const t=this.order.indexOf(e);t!==-1&&(this.order.splice(t,1),this.order.push(e))}sendToBack(e){const t=this.order.indexOf(e);t!==-1&&(this.order.splice(t,1),this.order.unshift(e))}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get sourceIds(){return[...this.order]}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes,order:this.order}}static fromJSON(e){const t=new k;for(const s of e.keyframes)t.addKeyframe(s.t,s.shapes);if(e.order){const s=t.order.filter(n=>!e.order.includes(n));t.order=[...e.order,...s]}return t}}function N(i,e,t){return Math.max(e,Math.min(t,i))}function U(i,e,t){const s=((e-i)%360+540)%360-180;return((i+s*t)%360+360)%360}function g(i,e,t){return i+(e-i)*t}function H(i,e,t){return{lat:i.lat===void 0||e.lat===void 0?void 0:g(i.lat,e.lat,t),lng:i.lng===void 0||e.lng===void 0?void 0:g(i.lng,e.lng,t),elevationM:g(i.elevationM,e.elevationM,t),headingDeg:i.headingDeg===void 0||e.headingDeg===void 0?void 0:U(i.headingDeg,e.headingDeg,t),pitchDeg:g(i.pitchDeg,e.pitchDeg,t),fovDeg:g(i.fovDeg,e.fovDeg,t)}}class y{keyframes=[];addKeyframe(e,t){const s=this.findInsertIndex(e);this.keyframes[s]?.t===e?this.keyframes[s]={t:e,pose:t}:this.keyframes.splice(s,0,{t:e,pose:t})}clear(){this.keyframes.length=0}removeKeyframeAt(e){const t=this.findInsertIndex(e);this.keyframes[t]?.t===e&&this.keyframes.splice(t,1)}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const n=t+s>>>1;this.keyframes[n].t<e?t=n+1:s=n}return t}getLatestPoseAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].pose:void 0}getInterpolatedPoseAt(e){const t=this.findInsertIndex(e),s=this.keyframes[t]?.t===e?this.keyframes[t]:this.keyframes[t-1];if(s?.t===e)return s.pose;const n=this.keyframes[t]?.t===e?void 0:this.keyframes[t];return s?n?H(s.pose,n.pose,N((e-s.t)/(n.t-s.t),0,1)):s.pose:n?.pose}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes}}static fromJSON(e){const t=new y;for(const s of e.keyframes)t.addKeyframe(s.t,s.pose);return t}}function S(i){if(i.year!==void 0)return Date.UTC(i.year,(i.month??1)-1,i.day??1,i.hour??0,i.minute??0,i.second??0)}function $(i){if(i.durationSeconds!==void 0)return i.durationSeconds*1e3;const e=i.time?S(i.time):void 0,t=i.endTime?S(i.endTime):void 0;return e!==void 0&&t!==void 0?t-e:void 0}class w{constructor(e,t,s,n,r,a,o){this.event=e,this.timeline=t,this.observerTrack=s,this.witnessId=n,this.witnessName=r,this.caseId=a,this.weather=o}static create(e,t,s){return new w({eventType:"sighting",time:e,place:t},new k,new y,s)}}class J{constructor(e,t){this.timeline=e,this.onFrame=t}rafId=null;state="stopped";currentT=0;lastWallTime=0;playbackRate=1;loop=!1;durationOverrideMs=0;get seekableDuration(){return Math.max(this.timeline.duration,this.durationOverrideMs)}play(){if(this.state==="playing")return;this.currentT>=this.seekableDuration&&(this.currentT=0,this.resolveFrame(0)),this.state="playing",this.lastWallTime=performance.now();const e=()=>{if(this.state!=="playing")return;const t=performance.now();if(this.currentT+=(t-this.lastWallTime)*this.playbackRate,this.lastWallTime=t,this.currentT>=this.seekableDuration){if(this.loop&&this.seekableDuration>0){this.currentT%=this.seekableDuration,this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(e);return}this.currentT=this.seekableDuration,this.stop(),this.resolveFrame(this.currentT);return}this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(e)};this.rafId=requestAnimationFrame(e)}pause(){this.state==="playing"&&(this.state="paused",this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null))}stop(){this.state="stopped",this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null)}seek(e){this.currentT=Math.max(0,Math.min(e,this.seekableDuration)),this.resolveFrame(this.currentT)}get playbackState(){return this.state}get time(){return this.currentT}resolveFrame(e){const t=new Map;for(const s of this.timeline.sourceIds){const n=this.timeline.getInterpolatedShapeAt(e,s);n&&t.set(s,n)}this.onFrame(e,t)}}const K=["nw","n","ne","e","se","s","sw","w"],q=24;function z(i,e,t){const s=i.x-e.x,n=i.y-e.y,r=Math.cos(t),a=Math.sin(t);return{x:e.x+s*r-n*a,y:e.y+s*a+n*r}}function W(i){return{x:i.x+i.width/2,y:i.y+i.height/2}}function Z(i){const{x:e,y:t,width:s,height:n}=i.bounds,r={nw:{x:e,y:t},n:{x:e+s/2,y:t},ne:{x:e+s,y:t},e:{x:e+s,y:t+n/2},se:{x:e+s,y:t+n},s:{x:e+s/2,y:t+n},sw:{x:e,y:t+n},w:{x:e,y:t+n/2},rotate:{x:e+s/2,y:t-q}},a=W(i.bounds),o={};for(const c of Object.keys(r))o[c]=z(r[c],a,i.angle);return o}const I=6,b=I/2,G=20;class Q{constructor(e){this.ctx=e}clear(e,t){this.ctx.clearRect(0,0,e,t)}paintShape(e){this.ctx.save(),this.ctx.globalAlpha=1-e.transparency,e.haloScale>0&&this.paintHalo(e),this.paintBase(e),this.ctx.restore(),e.selected&&this.paintSelectionHandles(e)}paintBase(e){if(this.ctx.fillStyle=e.color,this.ctx.beginPath(),e.kind==="oval"){const{x:t,y:s,width:n,height:r}=e.bounds,a=n/2,o=r/2;this.ctx.ellipse(t+a,s+o,a,o,e.angle,0,2*Math.PI)}else{const{x:t,y:s,width:n,height:r}=e.bounds;this.ctx.save(),this.ctx.translate(t+n/2,s+r/2),this.ctx.rotate(e.angle),this.ctx.translate(-n/2,-r/2),e.points.forEach((a,o)=>{o===0?this.ctx.moveTo(a.x,a.y):this.ctx.lineTo(a.x,a.y)}),this.ctx.closePath(),this.ctx.restore()}this.ctx.fill()}paintHalo(e){this.ctx.save(),this.ctx.shadowColor=e.color,this.ctx.shadowBlur=G*e.haloScale,this.paintBase(e),this.ctx.restore()}paintSelectionHandles(e){const t=Z(e);this.ctx.strokeStyle="lightgray",this.ctx.beginPath(),this.ctx.moveTo(t.nw.x,t.nw.y),this.ctx.lineTo(t.ne.x,t.ne.y),this.ctx.lineTo(t.se.x,t.se.y),this.ctx.lineTo(t.sw.x,t.sw.y),this.ctx.closePath(),this.ctx.stroke(),this.ctx.fillStyle="lightgray";for(const s of K){const n=t[s];this.ctx.fillRect(n.x-b,n.y-b,I,I)}this.ctx.beginPath(),this.ctx.moveTo(t.n.x,t.n.y),this.ctx.lineTo(t.rotate.x,t.rotate.y),this.ctx.stroke(),this.ctx.beginPath(),this.ctx.ellipse(t.rotate.x,t.rotate.y,b+1,b+1,0,0,2*Math.PI),this.ctx.fill()}}function V(i){return{version:1,time:i.event.time,endTime:i.event.endTime,durationSeconds:i.event.durationSeconds,place:i.event.place,witnessId:i.witnessId,witnessName:i.witnessName,caseId:i.caseId,timeline:i.timeline.toJSON(),observerTrack:i.observerTrack.toJSON(),weather:i.weather}}function X(i){return new w({eventType:"sighting",time:i.time,endTime:i.endTime,durationSeconds:i.durationSeconds,place:i.place},k.fromJSON(i.timeline),i.observerTrack?y.fromJSON(i.observerTrack):new y,i.witnessId,i.witnessName,i.caseId,i.weather)}function Y(i,e){for(const t of i){const s=t.toLowerCase().split("-")[0];if(e.includes(s))return s}return"en"}const j="modulepreload",ee=function(i,e){return new URL(i,e).href},E={},P=function(e,t,s){let n=Promise.resolve();if(t&&t.length>0){let a=function(h){return Promise.all(h.map(d=>Promise.resolve(d).then(p=>({status:"fulfilled",value:p}),p=>({status:"rejected",reason:p}))))};const o=document.getElementsByTagName("link"),c=document.querySelector("meta[property=csp-nonce]"),f=c?.nonce||c?.getAttribute("nonce");n=a(t.map(h=>{if(h=ee(h,s),h in E)return;E[h]=!0;const d=h.endsWith(".css"),p=d?'[rel="stylesheet"]':"";if(!!s)for(let m=o.length-1;m>=0;m--){const v=o[m];if(v.href===h&&(!d||v.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${h}"]${p}`))return;const u=document.createElement("link");if(u.rel=d?"stylesheet":j,d||(u.as="script"),u.crossOrigin="",u.href=h,f&&u.setAttribute("nonce",f),document.head.appendChild(u),d)return new Promise((m,v)=>{u.addEventListener("load",m),u.addEventListener("error",()=>v(new Error(`Unable to preload CSS for ${h}`)))})}))}function r(a){const o=new Event("vite:preloadError",{cancelable:!0});if(o.payload=a,window.dispatchEvent(o),!o.defaultPrevented)throw a}return n.then(a=>{for(const o of a||[])o.status==="rejected"&&r(o.reason);return e().catch(r)})},te=["en","fr"],se={en:()=>P(()=>Promise.resolve().then(()=>ne),void 0,import.meta.url).then(i=>i.ufoMessages_en),fr:()=>P(()=>import("./UfoMessages_fr-NZrwNFCQ.js"),[],import.meta.url).then(i=>i.ufoMessages_fr)};function ie(i){return se[i]()}const A={play:"Play",pause:"Pause",noDuration:"No observation duration",autoReplay:"Auto-replay",currentPosition:"Current position",duration:"Duration",fullscreen:"Fullscreen",exitFullscreen:"Exit fullscreen"},ne=Object.freeze(Object.defineProperty({__proto__:null,ufoMessages_en:A},Symbol.toStringTag,{value:"Module"}));class re extends HTMLElement{static get observedAttributes(){return["src"]}shadow;stageElement;canvas;canvasRenderer;toolbar;playPauseButton;loopButton;fullscreenButton;seekInput;timeStartLabel;timeEndLabel;currentSighting=w.create();player;loopEnabled=!0;highlightedSourceId;enableClickToPlay=!0;fullscreenTarget;messages=A;realDurationMs;realStartMs;handleFullscreenChange=()=>this.updateFullscreenButton();constructor(){super(),this.shadow=this.attachShadow({mode:"open"});const e=document.createElement("template");e.innerHTML=`<style>${F}</style>${L}`,this.shadow.appendChild(e.content.cloneNode(!0)),this.stageElement=this.shadow.getElementById("stage"),this.canvas=this.shadow.getElementById("canvas"),this.canvasRenderer=new Q(this.canvas.getContext("2d")),this.toolbar=this.shadow.getElementById("toolbar"),this.playPauseButton=this.shadow.getElementById("play-pause"),this.loopButton=this.shadow.getElementById("loop"),this.fullscreenButton=this.shadow.getElementById("fullscreen"),this.seekInput=this.shadow.getElementById("seek"),this.timeStartLabel=this.shadow.getElementById("time-start"),this.timeEndLabel=this.shadow.getElementById("time-end"),this.fullscreenTarget=this.stageElement,this.playPauseButton.addEventListener("click",()=>this.togglePlayPause()),this.loopButton.addEventListener("click",()=>this.toggleLoop()),this.fullscreenButton.addEventListener("click",()=>this.toggleFullscreen()),this.seekInput.addEventListener("input",()=>this.player.seek(Number(this.seekInput.value))),this.canvas.addEventListener("click",()=>{this.enableClickToPlay&&this.togglePlayPause()}),document.addEventListener("fullscreenchange",this.handleFullscreenChange),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.updateFullscreenButton(),this.refresh(),this.loadLocaleMessages()}connectedCallback(){const e=this.getAttribute("src");e&&this.loadFromSrc(e)}disconnectedCallback(){document.removeEventListener("fullscreenchange",this.handleFullscreenChange)}attributeChangedCallback(e,t,s){e==="src"&&s&&s!==t&&this.isConnected&&this.loadFromSrc(s)}async loadFromSrc(e){const t=await fetch(e);this.sightingData=await t.json()}get sightingData(){return V(this.currentSighting)}set sightingData(e){this.player.stop(),this.currentSighting=X(e),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.refresh()}get sighting(){return this.currentSighting}get canvasElement(){return this.canvas}get renderer(){return this.canvasRenderer}get currentTime(){return this.player.time}get playbackState(){return this.player.playbackState}get selectedSourceId(){return this.highlightedSourceId}get durationSeconds(){return this.currentSighting.event.durationSeconds}set durationSeconds(e){this.currentSighting.event.durationSeconds=e,this.updateTimeLabels(),this.refresh(),this.updatePlayPauseButton()}set selectedSourceId(e){e!==this.highlightedSourceId&&(this.highlightedSourceId=e,this.refresh())}refresh(){this.seekInput.max=String(this.player.seekableDuration),this.player.seek(this.player.time)}onFrame(e,t){this.canvasRenderer.clear(this.canvas.width,this.canvas.height);const s=this.playbackState!=="playing";for(const[n,r]of t){const a=s&&n===this.highlightedSourceId;this.canvasRenderer.paintShape(a?{...r,selected:!0}:r)}this.seekInput.value=String(e),this.timeStartLabel.textContent=this.formatPosition(e),this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{time:e}}))}createPlayer(){const e=new J(this.currentSighting.timeline,(t,s)=>this.onFrame(t,s));return e.loop=this.loopEnabled,e}togglePlayPause(){this.player.seekableDuration<=0||(this.player.playbackState==="playing"?(this.player.pause(),this.refresh()):this.player.play(),this.updatePlayPauseButton())}updatePlayPauseButton(){const e=this.player.playbackState==="playing";this.playPauseButton.textContent=e?"⏸":"▶";const t=this.player.seekableDuration>0;this.playPauseButton.disabled=!t;const s=t?e?this.messages.pause:this.messages.play:this.messages.noDuration;this.playPauseButton.title=s,this.playPauseButton.setAttribute("aria-label",s),this.toolbar.classList.toggle("auto-hide",e),this.fullscreenButton.classList.toggle("auto-hide",e)}toggleLoop(){this.loopEnabled=!this.loopEnabled,this.loopButton.setAttribute("aria-pressed",String(this.loopEnabled)),this.player.loop=this.loopEnabled}toggleFullscreen(){document.fullscreenElement?document.exitFullscreen():this.fullscreenTarget.requestFullscreen().catch(e=>{console.error("<rr0-ufo>: requestFullscreen() failed —",e)})}updateFullscreenButton(){const e=document.fullscreenElement===this.fullscreenTarget;this.fullscreenButton.title=e?this.messages.exitFullscreen:this.messages.fullscreen,this.fullscreenButton.setAttribute("aria-label",this.fullscreenButton.title)}async loadLocaleMessages(){const e=Y(navigator.languages,te);e!=="en"&&this.applyMessages(await ie(e))}applyMessages(e){this.messages=e,this.timeStartLabel.title=e.currentPosition,this.timeEndLabel.title=e.duration,this.loopButton.title=e.autoReplay,this.loopButton.setAttribute("aria-label",e.autoReplay),this.updatePlayPauseButton(),this.updateFullscreenButton()}updateTimeLabels(){const e=this.currentSighting.event,t=$(e);this.realDurationMs=t!==void 0&&t>0?t:void 0,this.realStartMs=e.time?S(e.time):void 0;const s=this.currentSighting.timeline.duration;this.player.playbackRate=this.realDurationMs!==void 0&&s>0?s/this.realDurationMs:1,this.player.durationOverrideMs=this.realDurationMs??0,this.timeEndLabel.textContent=this.formatEndOfTimeline(),this.timeStartLabel.textContent=this.formatPosition(this.player.time)}formatPosition(e){if(this.realDurationMs===void 0)return x(e);const t=this.currentSighting.timeline.duration,s=t>0&&e<=t?e/t*this.realDurationMs:e;return this.realStartMs!==void 0?B(M(this.realStartMs+s)):x(s)}formatEndOfTimeline(){return this.realDurationMs===void 0?x(this.currentSighting.timeline.duration):this.realStartMs!==void 0?B(M(this.realStartMs+this.realDurationMs)):x(this.realDurationMs)}}function M(i){const e=new Date(i);return{hour:e.getUTCHours(),minute:e.getUTCMinutes(),second:e.getUTCSeconds()}}function B(i){if(i.hour===void 0)return"0:00";const e=t=>String(t).padStart(2,"0");return i.second?`${e(i.hour)}:${e(i.minute??0)}:${e(i.second)}`:`${e(i.hour)}:${e(i.minute??0)}`}function x(i){const e=Math.round(i/1e3),t=Math.floor(e/60),s=e%60;return`${t}:${String(s).padStart(2,"0")}`}const D="rr0-ufo";function ae(){customElements.get(D)||customElements.define(D,re)}ae();
146
+ `;function C(i,e,t){const{bounds:s}=i;return e>=s.x&&e<=s.x+s.width&&t>=s.y&&t<=s.y+s.height}function l(i,e,t){return i+(e-i)*t}function T(i){const e=/^#([0-9a-f]{6})$/i.exec(i);if(!e)return;const t=parseInt(e[1],16);return[t>>16&255,t>>8&255,t&255]}function O(i){return`#${i.map(e=>Math.round(e).toString(16).padStart(2,"0")).join("")}`}function _(i,e,t){const s=T(i),n=T(e);return!s||!n?t<1?i:e:O([l(s[0],n[0],t),l(s[1],n[1],t),l(s[2],n[2],t)])}function R(i,e,t){const s={x:l(i.bounds.x,e.bounds.x,t),y:l(i.bounds.y,e.bounds.y,t),width:l(i.bounds.width,e.bounds.width,t),height:l(i.bounds.height,e.bounds.height,t)},n=l(i.angle,e.angle,t),r=l(i.transparency,e.transparency,t),a=l(i.haloScale,e.haloScale,t),o=_(i.color,e.color,t);return i.kind==="polygon"&&e.kind==="polygon"&&i.points.length===e.points.length?{...i,bounds:s,angle:n,transparency:r,haloScale:a,color:o,points:i.points.map((c,f)=>({x:l(c.x,e.points[f].x,t),y:l(c.y,e.points[f].y,t)}))}:{...t<1?i:e,bounds:s,angle:n,transparency:r,haloScale:a,color:o}}class k{keyframes=[];order=[];addKeyframe(e,t){const s=this.findInsertIndex(e);if(this.keyframes[s]?.t===e){const n=new Set(t.map(r=>r.sourceId));this.keyframes[s]={t:e,shapes:[...this.keyframes[s].shapes.filter(r=>!n.has(r.sourceId)),...t]}}else this.keyframes.splice(s,0,{t:e,shapes:[...t]});for(const n of t)this.order.includes(n.sourceId)||this.order.push(n.sourceId)}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const n=t+s>>>1;this.keyframes[n].t<e?t=n+1:s=n}return t}getKeyframeAt(e){const t=this.findInsertIndex(e),s=this.keyframes[t];return s?.t===e?s:void 0}getShapeAt(e,t){return this.getKeyframeAt(e)?.shapes.find(s=>s.sourceId===t)?.shape}getLatestShapeAt(e,t){let s=this.findInsertIndex(e);for(this.keyframes[s]?.t!==e&&(s-=1);s>=0;s--){const n=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(n)return n.shape}}getInterpolatedShapeAt(e,t){const s=this.findShapeAtOrBefore(e,t);if(s?.t===e)return s.shape;const n=this.findShapeAtOrAfter(e,t);return s?n?R(s.shape,n.shape,(e-s.t)/(n.t-s.t)):s.shape:n?.shape}findShapeAtOrBefore(e,t){let s=this.findInsertIndex(e);for(this.keyframes[s]?.t!==e&&(s-=1);s>=0;s--){const n=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(n)return{t:this.keyframes[s].t,shape:n.shape}}}findShapeAtOrAfter(e,t){for(let s=this.findInsertIndex(e);s<this.keyframes.length;s++){const n=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(n)return{t:this.keyframes[s].t,shape:n.shape}}}hitTest(e,t,s){const n=this.sourceIds;for(let r=n.length-1;r>=0;r--){const a=this.getInterpolatedShapeAt(e,n[r]);if(a&&C(a,t,s))return{sourceId:n[r],shape:a}}}removeSource(e){for(let t=this.keyframes.length-1;t>=0;t--){const s=this.keyframes[t].shapes.filter(n=>n.sourceId!==e);s.length===0?this.keyframes.splice(t,1):s.length!==this.keyframes[t].shapes.length&&(this.keyframes[t]={t:this.keyframes[t].t,shapes:s})}this.order=this.order.filter(t=>t!==e)}bringToFront(e){const t=this.order.indexOf(e);t!==-1&&(this.order.splice(t,1),this.order.push(e))}sendToBack(e){const t=this.order.indexOf(e);t!==-1&&(this.order.splice(t,1),this.order.unshift(e))}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get sourceIds(){return[...this.order]}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes,order:this.order}}static fromJSON(e){const t=new k;for(const s of e.keyframes)t.addKeyframe(s.t,s.shapes);if(e.order){const s=t.order.filter(n=>!e.order.includes(n));t.order=[...e.order,...s]}return t}}function N(i,e,t){return Math.max(e,Math.min(t,i))}function U(i,e,t){const s=((e-i)%360+540)%360-180;return((i+s*t)%360+360)%360}function m(i,e,t){return i+(e-i)*t}function H(i,e,t){return{lat:i.lat===void 0||e.lat===void 0?void 0:m(i.lat,e.lat,t),lng:i.lng===void 0||e.lng===void 0?void 0:m(i.lng,e.lng,t),elevationM:m(i.elevationM,e.elevationM,t),headingDeg:i.headingDeg===void 0||e.headingDeg===void 0?void 0:U(i.headingDeg,e.headingDeg,t),pitchDeg:m(i.pitchDeg,e.pitchDeg,t),fovDeg:m(i.fovDeg,e.fovDeg,t)}}class y{keyframes=[];addKeyframe(e,t){const s=this.findInsertIndex(e);this.keyframes[s]?.t===e?this.keyframes[s]={t:e,pose:t}:this.keyframes.splice(s,0,{t:e,pose:t})}clear(){this.keyframes.length=0}removeKeyframeAt(e){const t=this.findInsertIndex(e);this.keyframes[t]?.t===e&&this.keyframes.splice(t,1)}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const n=t+s>>>1;this.keyframes[n].t<e?t=n+1:s=n}return t}getLatestPoseAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].pose:void 0}getInterpolatedPoseAt(e){const t=this.findInsertIndex(e),s=this.keyframes[t]?.t===e?this.keyframes[t]:this.keyframes[t-1];if(s?.t===e)return s.pose;const n=this.keyframes[t]?.t===e?void 0:this.keyframes[t];return s?n?H(s.pose,n.pose,N((e-s.t)/(n.t-s.t),0,1)):s.pose:n?.pose}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes}}static fromJSON(e){const t=new y;for(const s of e.keyframes)t.addKeyframe(s.t,s.pose);return t}}function S(i){if(i.year!==void 0)return Date.UTC(i.year,(i.month??1)-1,i.day??1,i.hour??0,i.minute??0,i.second??0)}function $(i){if(i.durationSeconds!==void 0)return i.durationSeconds*1e3;const e=i.time?S(i.time):void 0,t=i.endTime?S(i.endTime):void 0;return e!==void 0&&t!==void 0?t-e:void 0}class w{constructor(e,t,s,n,r,a,o){this.event=e,this.timeline=t,this.observerTrack=s,this.witnessId=n,this.witnessName=r,this.caseId=a,this.weather=o}static create(e,t,s){return new w({eventType:"sighting",time:e,place:t},new k,new y,s)}}class J{constructor(e,t){this.timeline=e,this.onFrame=t}rafId=null;state="stopped";currentT=0;lastWallTime=0;playbackRate=1;loop=!1;durationOverrideMs=0;get seekableDuration(){return Math.max(this.timeline.duration,this.durationOverrideMs)}play(){if(this.state==="playing")return;this.currentT>=this.seekableDuration&&(this.currentT=0,this.resolveFrame(0)),this.state="playing",this.lastWallTime=performance.now();const e=()=>{if(this.state!=="playing")return;const t=performance.now();if(this.currentT+=(t-this.lastWallTime)*this.playbackRate,this.lastWallTime=t,this.currentT>=this.seekableDuration){if(this.loop&&this.seekableDuration>0){this.currentT%=this.seekableDuration,this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(e);return}this.currentT=this.seekableDuration,this.stop(),this.resolveFrame(this.currentT);return}this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(e)};this.rafId=requestAnimationFrame(e)}pause(){this.state==="playing"&&(this.state="paused",this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null))}stop(){this.state="stopped",this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null)}seek(e){this.currentT=Math.max(0,Math.min(e,this.seekableDuration)),this.resolveFrame(this.currentT)}get playbackState(){return this.state}get time(){return this.currentT}resolveFrame(e){const t=new Map;for(const s of this.timeline.sourceIds){const n=this.timeline.getInterpolatedShapeAt(e,s);n&&t.set(s,n)}this.onFrame(e,t)}}const z=["nw","n","ne","e","se","s","sw","w"],K=24;function q(i,e,t){const s=i.x-e.x,n=i.y-e.y,r=Math.cos(t),a=Math.sin(t);return{x:e.x+s*r-n*a,y:e.y+s*a+n*r}}function W(i){return{x:i.x+i.width/2,y:i.y+i.height/2}}function Z(i){const{x:e,y:t,width:s,height:n}=i.bounds,r={nw:{x:e,y:t},n:{x:e+s/2,y:t},ne:{x:e+s,y:t},e:{x:e+s,y:t+n/2},se:{x:e+s,y:t+n},s:{x:e+s/2,y:t+n},sw:{x:e,y:t+n},w:{x:e,y:t+n/2},rotate:{x:e+s/2,y:t-K}},a=W(i.bounds),o={};for(const c of Object.keys(r))o[c]=q(r[c],a,i.angle);return o}const I=6,b=I/2,G=20;class j{constructor(e){this.ctx=e}clear(e,t){this.ctx.clearRect(0,0,e,t)}paintShape(e){this.ctx.save(),this.ctx.globalAlpha=1-e.transparency,e.haloScale>0&&this.paintHalo(e),this.paintBase(e),this.ctx.restore(),e.selected&&this.paintSelectionHandles(e)}paintBase(e){if(this.ctx.fillStyle=e.color,this.ctx.beginPath(),e.kind==="oval"){const{x:t,y:s,width:n,height:r}=e.bounds,a=n/2,o=r/2;this.ctx.ellipse(t+a,s+o,a,o,e.angle,0,2*Math.PI)}else{const{x:t,y:s,width:n,height:r}=e.bounds;this.ctx.save(),this.ctx.translate(t+n/2,s+r/2),this.ctx.rotate(e.angle),this.ctx.translate(-n/2,-r/2),e.points.forEach((a,o)=>{o===0?this.ctx.moveTo(a.x,a.y):this.ctx.lineTo(a.x,a.y)}),this.ctx.closePath(),this.ctx.restore()}this.ctx.fill()}paintHalo(e){this.ctx.save(),this.ctx.shadowColor=e.color,this.ctx.shadowBlur=G*e.haloScale,this.paintBase(e),this.ctx.restore()}paintSelectionHandles(e){const t=Z(e);this.ctx.strokeStyle="lightgray",this.ctx.beginPath(),this.ctx.moveTo(t.nw.x,t.nw.y),this.ctx.lineTo(t.ne.x,t.ne.y),this.ctx.lineTo(t.se.x,t.se.y),this.ctx.lineTo(t.sw.x,t.sw.y),this.ctx.closePath(),this.ctx.stroke(),this.ctx.fillStyle="lightgray";for(const s of z){const n=t[s];this.ctx.fillRect(n.x-b,n.y-b,I,I)}this.ctx.beginPath(),this.ctx.moveTo(t.n.x,t.n.y),this.ctx.lineTo(t.rotate.x,t.rotate.y),this.ctx.stroke(),this.ctx.beginPath(),this.ctx.ellipse(t.rotate.x,t.rotate.y,b+1,b+1,0,0,2*Math.PI),this.ctx.fill()}}function Q(i){return{version:1,time:i.event.time,endTime:i.event.endTime,durationSeconds:i.event.durationSeconds,place:i.event.place,witnessId:i.witnessId,witnessName:i.witnessName,caseId:i.caseId,timeline:i.timeline.toJSON(),observerTrack:i.observerTrack.toJSON(),weather:i.weather}}function V(i){return new w({eventType:"sighting",time:i.time,endTime:i.endTime,durationSeconds:i.durationSeconds,place:i.place},k.fromJSON(i.timeline),i.observerTrack?y.fromJSON(i.observerTrack):new y,i.witnessId,i.witnessName,i.caseId,i.weather)}function X(i,e){for(const t of i){const s=t.toLowerCase().split("-")[0];if(e.includes(s))return s}return"en"}const Y="modulepreload",ee=function(i,e){return new URL(i,e).href},E={},P=function(e,t,s){let n=Promise.resolve();if(t&&t.length>0){let a=function(h){return Promise.all(h.map(d=>Promise.resolve(d).then(p=>({status:"fulfilled",value:p}),p=>({status:"rejected",reason:p}))))};const o=document.getElementsByTagName("link"),c=document.querySelector("meta[property=csp-nonce]"),f=c?.nonce||c?.getAttribute("nonce");n=a(t.map(h=>{if(h=ee(h,s),h in E)return;E[h]=!0;const d=h.endsWith(".css"),p=d?'[rel="stylesheet"]':"";if(!!s)for(let g=o.length-1;g>=0;g--){const v=o[g];if(v.href===h&&(!d||v.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${h}"]${p}`))return;const u=document.createElement("link");if(u.rel=d?"stylesheet":Y,d||(u.as="script"),u.crossOrigin="",u.href=h,f&&u.setAttribute("nonce",f),document.head.appendChild(u),d)return new Promise((g,v)=>{u.addEventListener("load",g),u.addEventListener("error",()=>v(new Error(`Unable to preload CSS for ${h}`)))})}))}function r(a){const o=new Event("vite:preloadError",{cancelable:!0});if(o.payload=a,window.dispatchEvent(o),!o.defaultPrevented)throw a}return n.then(a=>{for(const o of a||[])o.status==="rejected"&&r(o.reason);return e().catch(r)})},te=["en","fr"],se={en:()=>P(()=>Promise.resolve().then(()=>ne),void 0,import.meta.url).then(i=>i.ufoMessages_en),fr:()=>P(()=>import("./UfoMessages_fr-NZrwNFCQ.js"),[],import.meta.url).then(i=>i.ufoMessages_fr)};function ie(i){return se[i]()}const A={play:"Play",pause:"Pause",noDuration:"No observation duration",autoReplay:"Auto-replay",currentPosition:"Current position",duration:"Duration",fullscreen:"Fullscreen",exitFullscreen:"Exit fullscreen"},ne=Object.freeze(Object.defineProperty({__proto__:null,ufoMessages_en:A},Symbol.toStringTag,{value:"Module"}));class re extends HTMLElement{static get observedAttributes(){return["src"]}shadow;stageElement;canvas;canvasRenderer;toolbar;playPauseButton;loopButton;fullscreenButton;seekInput;timeStartLabel;timeEndLabel;currentSighting=w.create();player;loopEnabled=!0;highlightedSourceId;enableClickToPlay=!0;fullscreenTarget;messages=A;realDurationMs;realStartMs;handleFullscreenChange=()=>this.updateFullscreenButton();constructor(){super(),this.shadow=this.attachShadow({mode:"open"});const e=document.createElement("template");e.innerHTML=`<style>${F}</style>${L}`,this.shadow.appendChild(e.content.cloneNode(!0)),this.stageElement=this.shadow.getElementById("stage"),this.canvas=this.shadow.getElementById("canvas"),this.canvasRenderer=new j(this.canvas.getContext("2d")),this.toolbar=this.shadow.getElementById("toolbar"),this.playPauseButton=this.shadow.getElementById("play-pause"),this.loopButton=this.shadow.getElementById("loop"),this.fullscreenButton=this.shadow.getElementById("fullscreen"),this.seekInput=this.shadow.getElementById("seek"),this.timeStartLabel=this.shadow.getElementById("time-start"),this.timeEndLabel=this.shadow.getElementById("time-end"),this.fullscreenTarget=this.stageElement,this.playPauseButton.addEventListener("click",()=>this.togglePlayPause()),this.loopButton.addEventListener("click",()=>this.toggleLoop()),this.fullscreenButton.addEventListener("click",()=>this.toggleFullscreen()),this.seekInput.addEventListener("input",()=>this.player.seek(Number(this.seekInput.value))),this.canvas.addEventListener("click",()=>{this.enableClickToPlay&&this.togglePlayPause()}),document.addEventListener("fullscreenchange",this.handleFullscreenChange),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.updateFullscreenButton(),this.refresh(),this.loadLocaleMessages()}connectedCallback(){const e=this.getAttribute("src");e&&this.loadFromSrc(e)}disconnectedCallback(){document.removeEventListener("fullscreenchange",this.handleFullscreenChange)}attributeChangedCallback(e,t,s){e==="src"&&s&&s!==t&&this.isConnected&&this.loadFromSrc(s)}async loadFromSrc(e){const t=await fetch(e);this.sightingData=await t.json()}get sightingData(){return Q(this.currentSighting)}set sightingData(e){this.player.stop(),this.currentSighting=V(e),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.refresh()}get sighting(){return this.currentSighting}get canvasElement(){return this.canvas}get renderer(){return this.canvasRenderer}get currentTime(){return this.player.time}get playbackState(){return this.player.playbackState}get selectedSourceId(){return this.highlightedSourceId}get durationSeconds(){return this.currentSighting.event.durationSeconds}set durationSeconds(e){this.currentSighting.event.durationSeconds=e,this.updateTimeLabels(),this.refresh(),this.updatePlayPauseButton()}set selectedSourceId(e){e!==this.highlightedSourceId&&(this.highlightedSourceId=e,this.refresh())}refresh(){this.seekInput.max=String(this.player.seekableDuration),this.player.seek(this.player.time)}onFrame(e,t){this.canvasRenderer.clear(this.canvas.width,this.canvas.height);const s=this.playbackState!=="playing";for(const[n,r]of t){const a=s&&n===this.highlightedSourceId;this.canvasRenderer.paintShape(a?{...r,selected:!0}:r)}this.seekInput.value=String(e),this.timeStartLabel.textContent=this.formatPosition(e),this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{time:e}}))}createPlayer(){const e=new J(this.currentSighting.timeline,(t,s)=>this.onFrame(t,s));return e.loop=this.loopEnabled,e}togglePlayPause(){this.player.seekableDuration<=0||(this.player.playbackState==="playing"?(this.player.pause(),this.refresh()):this.player.play(),this.updatePlayPauseButton())}updatePlayPauseButton(){const e=this.player.playbackState==="playing";this.playPauseButton.textContent=e?"⏸":"▶";const t=this.player.seekableDuration>0;this.playPauseButton.disabled=!t;const s=t?e?this.messages.pause:this.messages.play:this.messages.noDuration;this.playPauseButton.title=s,this.playPauseButton.setAttribute("aria-label",s),this.toolbar.classList.toggle("auto-hide",e),this.fullscreenButton.classList.toggle("auto-hide",e)}toggleLoop(){this.loopEnabled=!this.loopEnabled,this.loopButton.setAttribute("aria-pressed",String(this.loopEnabled)),this.player.loop=this.loopEnabled}toggleFullscreen(){document.fullscreenElement?document.exitFullscreen():this.fullscreenTarget.requestFullscreen().catch(e=>{console.error("<rr0-ufo>: requestFullscreen() failed —",e)})}updateFullscreenButton(){const e=document.fullscreenElement===this.fullscreenTarget;this.fullscreenButton.title=e?this.messages.exitFullscreen:this.messages.fullscreen,this.fullscreenButton.setAttribute("aria-label",this.fullscreenButton.title)}async loadLocaleMessages(){const e=X(navigator.languages,te);e!=="en"&&this.applyMessages(await ie(e))}applyMessages(e){this.messages=e,this.timeStartLabel.title=e.currentPosition,this.timeEndLabel.title=e.duration,this.loopButton.title=e.autoReplay,this.loopButton.setAttribute("aria-label",e.autoReplay),this.updatePlayPauseButton(),this.updateFullscreenButton()}updateTimeLabels(){const e=this.currentSighting.event,t=$(e);this.realDurationMs=t!==void 0&&t>0?t:void 0,this.realStartMs=e.time?S(e.time):void 0;const s=this.currentSighting.timeline.duration;this.player.playbackRate=this.realDurationMs!==void 0&&s>0?s/this.realDurationMs:1,this.player.durationOverrideMs=this.realDurationMs??0,this.timeEndLabel.textContent=this.formatEndOfTimeline(),this.timeStartLabel.textContent=this.formatPosition(this.player.time)}formatPosition(e){if(this.realDurationMs===void 0)return x(e);const t=this.currentSighting.timeline.duration,s=t>0&&e<=t?e/t*this.realDurationMs:e;return this.realStartMs!==void 0?D(M(this.realStartMs+s)):x(s)}formatEndOfTimeline(){return this.realDurationMs===void 0?x(this.currentSighting.timeline.duration):this.realStartMs!==void 0?D(M(this.realStartMs+this.realDurationMs)):x(this.realDurationMs)}}function M(i){const e=new Date(i);return{hour:e.getUTCHours(),minute:e.getUTCMinutes(),second:e.getUTCSeconds()}}function D(i){if(i.hour===void 0)return"0:00";const e=t=>String(t).padStart(2,"0");return i.second?`${e(i.hour)}:${e(i.minute??0)}:${e(i.second)}`:`${e(i.hour)}:${e(i.minute??0)}`}function x(i){const e=Math.round(i/1e3),t=Math.floor(e/60),s=e%60;return`${t}:${String(s).padStart(2,"0")}`}const B="rr0-ufo";function ae(){customElements.get(B)||customElements.define(B,re)}ae();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rr0/ufoathome",
3
3
  "type": "module",
4
- "version": "0.8.0",
4
+ "version": "0.8.1",
5
5
  "description": "UFO@home — record and replay a UFO sighting's shape, movement and appearance",
6
6
  "author": "Jérôme Beau <rr0@rr0.org> (https://rr0.org)",
7
7
  "license": "MIT",