@rr0/ufoathome 0.6.0 → 0.6.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.
@@ -2,18 +2,18 @@ const Ku=`
2
2
  <div class="toolbar" id="toolbar" hidden>
3
3
  <label id="witness-picker"><span id="label-witness">Witness</span> <select id="witness"></select></label>
4
4
  <button id="info-button" class="info-btn" type="button" title="About" aria-label="About" aria-expanded="false">?</button>
5
- </div>
6
- <div id="info-panel" class="info-panel" hidden>
7
- <button id="info-close" class="info-close" type="button" aria-label="Close">×</button>
8
- <p class="info-app"><a id="info-app-link" href="https://ufoathome.org" target="_blank" rel="noopener"></a></p>
9
- <section>
10
- <h3 id="info-observation-heading">Observation</h3>
11
- <dl id="info-observation-list" class="info-dl"></dl>
12
- </section>
13
- <section>
14
- <h3 id="info-credits-heading">Credits</h3>
15
- <ul id="info-credits-list" class="info-ul"></ul>
16
- </section>
5
+ <div id="info-panel" class="info-panel" hidden>
6
+ <button id="info-close" class="info-close" type="button" aria-label="Close">×</button>
7
+ <p class="info-app"><a id="info-app-link" href="https://ufoathome.org" target="_blank" rel="noopener"></a></p>
8
+ <section>
9
+ <h3 id="info-observation-heading">Observation</h3>
10
+ <dl id="info-observation-list" class="info-dl"></dl>
11
+ </section>
12
+ <section>
13
+ <h3 id="info-credits-heading">Credits</h3>
14
+ <ul id="info-credits-list" class="info-ul"></ul>
15
+ </section>
16
+ </div>
17
17
  </div>
18
18
  <div id="ufo-slot"></div>
19
19
  `,Ju=`
@@ -22,16 +22,19 @@ const Ku=`
22
22
  font-family: sans-serif;
23
23
  }
24
24
  /* Normal document flow, above the canvas — unlike the video player's own toolbar (ufoTemplate.ts),
25
- this one never overlays the scene, so it needs none of that toolbar's hover/auto-hide dance:
26
- once there's something to show, it just stays visible like any other page content. */
25
+ this row itself never overlays the scene, so it needs none of that toolbar's hover/auto-hide
26
+ dance: once there's something to show, it just stays visible like any other page content. Its
27
+ own info panel (below) is the one thing that overlays — anchored to this row via
28
+ position:relative — so opening it never shifts the canvas or the rest of the page. */
27
29
  .toolbar {
30
+ position: relative;
28
31
  display: flex;
29
32
  align-items: center;
30
- justify-content: space-between;
31
33
  gap: 0.5em;
32
34
  margin-bottom: 0.5em;
33
35
  }
34
36
  .info-btn {
37
+ margin-left: auto;
35
38
  flex-shrink: 0;
36
39
  width: 1.6em;
37
40
  height: 1.6em;
@@ -45,8 +48,11 @@ const Ku=`
45
48
  padding: 0;
46
49
  }
47
50
  .info-panel {
48
- position: relative;
49
- margin-bottom: 0.5em;
51
+ position: absolute;
52
+ top: 100%;
53
+ right: 0;
54
+ z-index: 2;
55
+ margin-top: 0.3em;
50
56
  padding: 0.6em 0.8em;
51
57
  border: 1px solid #ccc;
52
58
  border-radius: 4px;
@@ -54,6 +60,7 @@ const Ku=`
54
60
  color: #222;
55
61
  max-width: 28em;
56
62
  font-size: 0.9em;
63
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
57
64
  }
58
65
  .info-close {
59
66
  position: absolute;
@@ -4714,4 +4721,4 @@ void main() {
4714
4721
  float blur = max(vNearBlur, vHaze);
4715
4722
  gl_FragColor = vec4(color, tex.a * uOpacity * (1.0 - blur * 0.5));
4716
4723
  }
4717
- `;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 Hu(){Ch(),customElements.get(ol)||customElements.define(ol,Gx)}const ku={witness:"Witness",about:"About",close:"Close",observation:"Observation",date:"Date",location:"Location",case:"Case",credits:"Credits"},Hx=Object.freeze(Object.defineProperty({__proto__:null,eyewitnessMessages_en:ku},Symbol.toStringTag,{value:"Module"}));Hu();const kx="“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;witnessPicker;witnessSelect;labelWitness;infoButton;infoPanel;infoAppLink;infoObservationHeading;infoObservationList;infoCreditsHeading;infoCreditsList;infoCloseButton;entries=[];currentSrc;infoOpen=!1;language="en";messages=ku;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.witnessPicker=this.shadow.getElementById("witness-picker"),this.witnessSelect=this.shadow.getElementById("witness"),this.labelWitness=this.shadow.getElementById("label-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.infoCreditsHeading=this.shadow.getElementById("info-credits-heading"),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.loadLocaleMessages()}async loadLocaleMessages(){this.language=ll(navigator.languages,wh),this.language!=="en"&&(this.messages=await C0(this.language),this.labelWitness.textContent=this.messages.witness,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.infoCreditsHeading.textContent=this.messages.credits,this.infoOpen&&this.populateInfoPanel())}connectedCallback(){const e=this.getAttribute("src");e&&this.loadFromSrc(e)}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.witnessPicker.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)}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.infoOpen&&this.populateInfoPanel())}toggleInfoPanel(){this.infoOpen=!this.infoOpen,this.infoPanel.hidden=!this.infoOpen,this.infoButton.setAttribute("aria-expanded",String(this.infoOpen)),this.infoOpen&&this.populateInfoPanel()}populateInfoPanel(){this.infoAppLink.href=Xx,this.infoAppLink.textContent="UFO@home v0.6.0";const e=this.entries.find(r=>r.src===this.currentSrc);if(this.infoObservationList.innerHTML="",e){const r=e.sighting.place?.[0],a=Lu(e.sighting.time??{},r?.lng??0);a&&this.appendInfoRow(this.infoObservationList,this.messages.date,new Intl.DateTimeFormat(this.language==="fr"?"fr-FR":"en-US",{dateStyle:"long",timeStyle:"short"}).format(a)),r&&this.appendInfoRow(this.infoObservationList,this.messages.location,`${r.lat.toFixed(4)}, ${r.lng.toFixed(4)}`),e.sighting.witnessName&&this.appendInfoRow(this.infoObservationList,this.messages.witness,e.sighting.witnessName),e.sighting.caseId&&this.appendInfoRow(this.infoObservationList,this.messages.case,e.sighting.caseId)}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=`${kx} (`;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(){Hu(),customElements.get(Ah)||customElements.define(Ah,qx)}Yx();
4724
+ `;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 Hu(){Ch(),customElements.get(ol)||customElements.define(ol,Gx)}const ku={witness:"Witness",about:"About",close:"Close",observation:"Observation",date:"Date",location:"Location",case:"Case",credits:"Credits"},Hx=Object.freeze(Object.defineProperty({__proto__:null,eyewitnessMessages_en:ku},Symbol.toStringTag,{value:"Module"}));Hu();const kx="“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;witnessPicker;witnessSelect;labelWitness;infoButton;infoPanel;infoAppLink;infoObservationHeading;infoObservationList;infoCreditsHeading;infoCreditsList;infoCloseButton;entries=[];currentSrc;infoOpen=!1;language="en";messages=ku;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.witnessPicker=this.shadow.getElementById("witness-picker"),this.witnessSelect=this.shadow.getElementById("witness"),this.labelWitness=this.shadow.getElementById("label-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.infoCreditsHeading=this.shadow.getElementById("info-credits-heading"),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.loadLocaleMessages()}async loadLocaleMessages(){this.language=ll(navigator.languages,wh),this.language!=="en"&&(this.messages=await C0(this.language),this.labelWitness.textContent=this.messages.witness,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.infoCreditsHeading.textContent=this.messages.credits,this.infoOpen&&this.populateInfoPanel())}connectedCallback(){const e=this.getAttribute("src");e&&this.loadFromSrc(e)}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.witnessPicker.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)}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.infoOpen&&this.populateInfoPanel())}toggleInfoPanel(){this.infoOpen=!this.infoOpen,this.infoPanel.hidden=!this.infoOpen,this.infoButton.setAttribute("aria-expanded",String(this.infoOpen)),this.infoOpen&&this.populateInfoPanel()}populateInfoPanel(){this.infoAppLink.href=Xx,this.infoAppLink.textContent="UFO@home v0.6.1";const e=this.entries.find(r=>r.src===this.currentSrc);if(this.infoObservationList.innerHTML="",e){const r=e.sighting.place?.[0],a=Lu(e.sighting.time??{},r?.lng??0);a&&this.appendInfoRow(this.infoObservationList,this.messages.date,new Intl.DateTimeFormat(this.language==="fr"?"fr-FR":"en-US",{dateStyle:"long",timeStyle:"short"}).format(a)),r&&this.appendInfoRow(this.infoObservationList,this.messages.location,`${r.lat.toFixed(4)}, ${r.lng.toFixed(4)}`),e.sighting.witnessName&&this.appendInfoRow(this.infoObservationList,this.messages.witness,e.sighting.witnessName),e.sighting.caseId&&this.appendInfoRow(this.infoObservationList,this.messages.case,e.sighting.caseId)}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=`${kx} (`;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(){Hu(),customElements.get(Ah)||customElements.define(Ah,qx)}Yx();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rr0/ufoathome",
3
3
  "type": "module",
4
- "version": "0.6.0",
4
+ "version": "0.6.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",