@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
|
|
361
|
-
CSS never actually applies here (only this outer #stage is ever the real
|
|
362
|
-
element)
|
|
363
|
-
|
|
364
|
-
|
|
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
|
|
188
|
-
CSS never actually applies here (only this outer #stage is ever the real
|
|
189
|
-
element)
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
53
|
-
CSS never actually applies here (only this outer #stage is ever the real
|
|
54
|
-
element)
|
|
55
|
-
|
|
56
|
-
|
|
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.
|
|
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",
|