@rr0/ufoathome 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,9 @@
1
- const L=`
1
+ const z=`
2
2
  <div class="stage" id="stage">
3
3
  <div class="frame" id="frame">
4
4
  <canvas id="canvas" width="640" height="360"></canvas>
5
5
  </div>
6
+ <div id="tooltip" class="tooltip" hidden></div>
6
7
  <button id="fullscreen" class="fullscreen-btn" type="button" title="Fullscreen" aria-label="Fullscreen">⛶</button>
7
8
  <div class="toolbar" id="toolbar">
8
9
  <button id="play-pause" type="button" title="Play" aria-label="Play">▶</button>
@@ -12,7 +13,7 @@ const L=`
12
13
  <button id="loop" type="button" title="Auto-replay" aria-label="Auto-replay" aria-pressed="true">↻</button>
13
14
  </div>
14
15
  </div>
15
- `,F=`
16
+ `,U=`
16
17
  :host {
17
18
  display: block;
18
19
  font-family: sans-serif;
@@ -22,11 +23,23 @@ const L=`
22
23
  .frame's content, unchanged) — but it matters when this element is embedded with a definite
23
24
  host size from outside (e.g. <rr0-scene>'s .ufo-overlay sizing this element to fill its own
24
25
  #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. */
26
+ actually reach that outer element's true edges instead of only .frame's letterboxed ones.
27
+ display:flex + centering is unconditional (not just under :fullscreen below) for the same
28
+ nested-embedding case: this .stage is never itself the real fullscreen element when nested
29
+ inside <rr0-scene>'s .ufo-overlay (only the *outer* stage is, so :fullscreen never matches
30
+ here even while genuinely full-viewport-sized) — without this, .frame just sat at .stage's
31
+ top-left in that oversized box instead of centered, misaligning every shape's canvas
32
+ coordinates against the outer 3D scene's own (correctly centered) letterboxed content the
33
+ instant .stage's height actually exceeds .frame's. Harmless in the normal standalone case:
34
+ .stage's height already matches .frame's exactly there (see above), so there's no extra
35
+ space to center within regardless. */
26
36
  .stage {
27
37
  position: relative;
28
38
  width: 100%;
29
39
  height: 100%;
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: center;
30
43
  }
31
44
  /* The browser's own fullscreen UA styles force the fullscreened element (.stage) to fill the
32
45
  whole viewport (100vw/100vh) regardless of its content's aspect ratio. .toolbar/.fullscreen-btn
@@ -34,9 +47,6 @@ const L=`
34
47
  edges, full width, like a normal video player's controls — not stuck to the letterboxed
35
48
  content's own (possibly smaller, centered) box above/around them. */
36
49
  .stage:fullscreen {
37
- display: flex;
38
- align-items: center;
39
- justify-content: center;
40
50
  width: 100vw;
41
51
  height: 100vh;
42
52
  background: #000;
@@ -82,6 +92,14 @@ canvas {
82
92
  opacity: 0;
83
93
  pointer-events: none;
84
94
  }
95
+ /* A compound class selector (0,2,0) so this reliably beats the plain .toolbar rule above (0,1,0)
96
+ regardless of declaration order — set via UfoElement's showToolbar setter by a composing
97
+ element (see UfoRecorderElement) that drives its own external playback controls instead, since
98
+ this overlay's flex:1 seek bar would otherwise intercept nearly the full width of the canvas's
99
+ bottom edge, blocking shape drag/resize there. */
100
+ .toolbar.hidden {
101
+ display: none;
102
+ }
85
103
  .stage:hover .auto-hide {
86
104
  opacity: 1;
87
105
  pointer-events: auto;
@@ -134,4 +152,16 @@ input[type=range] {
134
152
  min-width: 3em;
135
153
  text-align: center;
136
154
  }
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();
155
+ .tooltip {
156
+ position: absolute;
157
+ z-index: 1;
158
+ padding: 0.2em 0.5em;
159
+ background: rgba(0, 0, 0, 0.7);
160
+ color: #fff;
161
+ font-family: sans-serif;
162
+ font-size: 0.85em;
163
+ border-radius: 3px;
164
+ pointer-events: none;
165
+ white-space: nowrap;
166
+ }
167
+ `;function $(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 f(i,e,t){return i+(e-i)*t}function D(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 J(i){return`#${i.map(e=>Math.round(e).toString(16).padStart(2,"0")).join("")}`}function K(i,e,t){const s=D(i),n=D(e);return!s||!n?t<1?i:e:J([f(s[0],n[0],t),f(s[1],n[1],t),f(s[2],n[2],t)])}function G(i,e,t){const s={x:f(i.bounds.x,e.bounds.x,t),y:f(i.bounds.y,e.bounds.y,t),width:f(i.bounds.width,e.bounds.width,t),height:f(i.bounds.height,e.bounds.height,t)},n=f(i.angle,e.angle,t),a=f(i.transparency,e.transparency,t),r=f(i.haloScale,e.haloScale,t),o=K(i.color,e.color,t);return i.kind==="polygon"&&e.kind==="polygon"&&i.points.length===e.points.length?{...i,bounds:s,angle:n,transparency:a,haloScale:r,color:o,points:i.points.map((l,h)=>({x:f(l.x,e.points[h].x,t),y:f(l.y,e.points[h].y,t)}))}:{...t<1?i:e,bounds:s,angle:n,transparency:a,haloScale:r,color:o}}class E{keyframes=[];order=[];groups=[];addKeyframe(e,t){const s=this.findInsertIndex(e);if(this.keyframes[s]?.t===e){const n=new Set(t.map(a=>a.sourceId));this.keyframes[s]={t:e,shapes:[...this.keyframes[s].shapes.filter(a=>!n.has(a.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(a=>a.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?G(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(a=>a.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(a=>a.sourceId===t);if(n)return{t:this.keyframes[s].t,shape:n.shape}}}hitTest(e,t,s){const n=this.sourceIds;for(let a=n.length-1;a>=0;a--){const r=this.getInterpolatedShapeAt(e,n[a]);if(r&&$(r,t,s))return{sourceId:n[a],shape:r}}}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),this.removeFromGroup(e)}group(e){if(!(e.length<2)){for(const t of e)this.removeFromGroup(t);this.groups.push([...e])}}ungroup(e){const t=this.groups.findIndex(s=>s.includes(e));t!==-1&&this.groups.splice(t,1)}groupMembers(e){return this.groups.find(t=>t.includes(e))}removeFromGroup(e){const t=this.groups.findIndex(n=>n.includes(e));if(t===-1)return;const s=this.groups[t].filter(n=>n!==e);s.length<2?this.groups.splice(t,1):this.groups[t]=s}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,groups:this.groups}}static fromJSON(e){const t=new E;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 e.groups&&(t.groups=e.groups.map(s=>s.filter(n=>t.order.includes(n))).filter(s=>s.length>=2)),t}}function q(i,e,t){return Math.max(e,Math.min(t,i))}function W(i,e,t){const s=((e-i)%360+540)%360-180;return((i+s*t)%360+360)%360}function v(i,e,t){return i+(e-i)*t}function Y(i,e,t){return{lat:i.lat===void 0||e.lat===void 0?void 0:v(i.lat,e.lat,t),lng:i.lng===void 0||e.lng===void 0?void 0:v(i.lng,e.lng,t),elevationM:v(i.elevationM,e.elevationM,t),headingDeg:i.headingDeg===void 0||e.headingDeg===void 0?void 0:W(i.headingDeg,e.headingDeg,t),pitchDeg:v(i.pitchDeg,e.pitchDeg,t),fovDeg:v(i.fovDeg,e.fovDeg,t)}}class b{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?Y(s.pose,n.pose,q((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 b;for(const s of e.keyframes)t.addKeyframe(s.t,s.pose);return t}}function P(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 F(i){return(((i.day??0)*24+(i.hour??0))*60+(i.minute??0))*6e4+(i.second??0)*1e3}const Z=["year","month","day","hour","minute"],X=["day","hour","minute"];function B(i,e){return e.filter(t=>i[t]!==void 0).join(",")}function Q(i){if(i.durationSeconds!==void 0)return i.durationSeconds*1e3;if(!i.time||!i.endTime)return;const e=i.time.year!==void 0&&i.endTime.year!==void 0,t=e?Z:X,s=B(i.time,t);if(!(s===""||s!==B(i.endTime,t)))return e?P(i.endTime)-P(i.time):F(i.endTime)-F(i.time)}class T{constructor(e,t,s,n,a,r){this.event=e,this.timeline=t,this.witnessTrack=s,this.witness=n,this.caseId=a,this.weather=r}static create(e,t,s){return new T({eventType:"sighting",time:e,place:t},new E,new b,s)}}class V{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 N=["nw","n","ne","e","se","s","sw","w"],j=24,w=8;function L(i,e,t){const s=i.x-e.x,n=i.y-e.y,a=Math.cos(t),r=Math.sin(t);return{x:e.x+s*a-n*r,y:e.y+s*r+n*a}}function I(i){return{x:i.x+i.width/2,y:i.y+i.height/2}}const ee={nw:{left:!0,top:!0},n:{top:!0},ne:{right:!0,top:!0},e:{right:!0},se:{right:!0,bottom:!0},s:{bottom:!0},sw:{left:!0,bottom:!0},w:{left:!0}};class m{static handlePointsFor(e){const{x:t,y:s,width:n,height:a}=e.bounds,r={nw:{x:t,y:s},n:{x:t+n/2,y:s},ne:{x:t+n,y:s},e:{x:t+n,y:s+a/2},se:{x:t+n,y:s+a},s:{x:t+n/2,y:s+a},sw:{x:t,y:s+a},w:{x:t,y:s+a/2},rotate:{x:t+n/2,y:s-j}},o=I(e.bounds),l={};for(const h of Object.keys(r))l[h]=L(r[h],o,e.angle);return l}static hitTestHandle(e,t,s=8,n=[...N,"rotate"]){const a=m.handlePointsFor(e);for(const r of n)if(Math.hypot(t.x-a[r].x,t.y-a[r].y)<=s)return r}static resizeBounds(e,t,s,n){const a=I(e),r=L(n,a,-t),{x:o,y:l,width:h,height:c}=e,u=ee[s];let p=o,y=l,d=o+h,g=l+c;return u.left&&(p=Math.min(r.x,d-w)),u.right&&(d=Math.max(r.x,p+w)),u.top&&(y=Math.min(r.y,g-w)),u.bottom&&(g=Math.max(r.y,y+w)),{x:p,y,width:d-p,height:g-y}}static resizeShape(e,t,s){if(t==="rotate")throw new Error("resizeShape does not accept the rotate handle");const{width:n,height:a}=e.bounds,r=m.resizeBounds(e.bounds,e.angle,t,s);if(e.kind==="oval")return{...e,bounds:r};const o=n===0?1:r.width/n,l=a===0?1:r.height/a;return{...e,bounds:r,points:e.points.map(h=>({x:h.x*o,y:h.y*l}))}}static rotateShape(e,t){const s=I(e.bounds),n=Math.atan2(t.y-s.y,t.x-s.x)+Math.PI/2;return{...e,angle:n}}static groupBoundsFor(e){const t=Math.min(...e.map(r=>r.x)),s=Math.min(...e.map(r=>r.y)),n=Math.max(...e.map(r=>r.x+r.width)),a=Math.max(...e.map(r=>r.y+r.height));return{x:t,y:s,width:n-t,height:a-s}}}const M=6,k=M/2,te=20;class se{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:a}=e.bounds,r=n/2,o=a/2;this.ctx.ellipse(t+r,s+o,r,o,e.angle,0,2*Math.PI)}else{const{x:t,y:s,width:n,height:a}=e.bounds;this.ctx.save(),this.ctx.translate(t+n/2,s+a/2),this.ctx.rotate(e.angle),this.ctx.translate(-n/2,-a/2),e.points.forEach((r,o)=>{o===0?this.ctx.moveTo(r.x,r.y):this.ctx.lineTo(r.x,r.y)}),this.ctx.closePath(),this.ctx.restore()}this.ctx.fill()}paintHalo(e){this.ctx.save(),this.ctx.shadowColor=e.color,this.ctx.shadowBlur=te*e.haloScale,this.paintBase(e),this.ctx.restore()}paintSelectionHandles(e){this.paintHandleFrame(m.handlePointsFor(e),{includeRotate:!0})}paintMemberOutline(e){this.paintOutline(m.handlePointsFor(e))}paintGroupHandles(e){this.paintHandleFrame(m.handlePointsFor({bounds:e,angle:0}),{includeRotate:!0})}paintOutline(e){this.ctx.strokeStyle="lightgray",this.ctx.beginPath(),this.ctx.moveTo(e.nw.x,e.nw.y),this.ctx.lineTo(e.ne.x,e.ne.y),this.ctx.lineTo(e.se.x,e.se.y),this.ctx.lineTo(e.sw.x,e.sw.y),this.ctx.closePath(),this.ctx.stroke()}paintHandleFrame(e,{includeRotate:t}){this.paintOutline(e),this.ctx.fillStyle="lightgray";for(const s of N){const n=e[s];this.ctx.fillRect(n.x-k,n.y-k,M,M)}t&&(this.ctx.beginPath(),this.ctx.moveTo(e.n.x,e.n.y),this.ctx.lineTo(e.rotate.x,e.rotate.y),this.ctx.stroke(),this.ctx.beginPath(),this.ctx.ellipse(e.rotate.x,e.rotate.y,k+1,k+1,0,0,2*Math.PI),this.ctx.fill())}}function ie(i){return{version:1,time:i.event.time,endTime:i.event.endTime,durationSeconds:i.event.durationSeconds,place:i.event.place,witness:i.witness,caseId:i.caseId,description:i.event.description,tags:i.event.tags,timeline:i.timeline.toJSON(),witnessTrack:i.witnessTrack.toJSON(),weather:i.weather}}function ne(i){return new T({eventType:"sighting",time:i.time,endTime:i.endTime,durationSeconds:i.durationSeconds,place:i.place,description:i.description,tags:i.tags},E.fromJSON(i.timeline),i.witnessTrack?b.fromJSON(i.witnessTrack):new b,i.witness,i.caseId,i.weather)}function re(i,e){for(const t of i){const s=t.toLowerCase().split("-")[0];if(e.includes(s))return s}return"en"}const ae="modulepreload",oe=function(i,e){return new URL(i,e).href},A={},C=function(e,t,s){let n=Promise.resolve();if(t&&t.length>0){let r=function(c){return Promise.all(c.map(u=>Promise.resolve(u).then(p=>({status:"fulfilled",value:p}),p=>({status:"rejected",reason:p}))))};const o=document.getElementsByTagName("link"),l=document.querySelector("meta[property=csp-nonce]"),h=l?.nonce||l?.getAttribute("nonce");n=r(t.map(c=>{if(c=oe(c,s),c in A)return;A[c]=!0;const u=c.endsWith(".css"),p=u?'[rel="stylesheet"]':"";if(!!s)for(let g=o.length-1;g>=0;g--){const x=o[g];if(x.href===c&&(!u||x.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${c}"]${p}`))return;const d=document.createElement("link");if(d.rel=u?"stylesheet":ae,u||(d.as="script"),d.crossOrigin="",d.href=c,h&&d.setAttribute("nonce",h),document.head.appendChild(d),u)return new Promise((g,x)=>{d.addEventListener("load",g),d.addEventListener("error",()=>x(new Error(`Unable to preload CSS for ${c}`)))})}))}function a(r){const o=new Event("vite:preloadError",{cancelable:!0});if(o.payload=r,window.dispatchEvent(o),!o.defaultPrevented)throw r}return n.then(r=>{for(const o of r||[])o.status==="rejected"&&a(o.reason);return e().catch(a)})},le=["en","fr"],he={en:()=>C(()=>Promise.resolve().then(()=>ue),void 0,import.meta.url).then(i=>i.ufoMessages_en),fr:()=>C(()=>import("./UfoMessages_fr-NZrwNFCQ.js"),[],import.meta.url).then(i=>i.ufoMessages_fr)};function ce(i){return he[i]()}const H={play:"Play",pause:"Pause",noDuration:"No observation duration",autoReplay:"Auto-replay",currentPosition:"Current position",duration:"Duration",fullscreen:"Fullscreen",exitFullscreen:"Exit fullscreen"},ue=Object.freeze(Object.defineProperty({__proto__:null,ufoMessages_en:H},Symbol.toStringTag,{value:"Module"})),de=new Set;class fe extends HTMLElement{static get observedAttributes(){return["src"]}shadow;stageElement;canvas;canvasRenderer;tooltip;toolbar;playPauseButton;loopButton;fullscreenButton;seekInput;timeStartLabel;timeEndLabel;currentSighting=T.create();player;loopEnabled=!0;highlightedSourceIds=new Set;enableClickToPlay=!0;fullscreenTarget;messages=H;realDurationMs;realStartMs;handleFullscreenChange=()=>this.updateFullscreenButton();handlePointerMove=e=>{const t=this.canvasPointFromEvent(e),s=t&&this.currentSighting.timeline.hitTest(this.currentTime,t.x,t.y);if(!s?.shape.title){this.tooltip.hidden=!0;return}this.tooltip.textContent=s.shape.title,this.tooltip.hidden=!1;const n=this.stageElement.getBoundingClientRect();this.tooltip.style.left=`${e.clientX-n.left+12}px`,this.tooltip.style.top=`${e.clientY-n.top+12}px`};handlePointerLeave=()=>{this.tooltip.hidden=!0};constructor(){super(),this.shadow=this.attachShadow({mode:"open"});const e=document.createElement("template");e.innerHTML=`<style>${U}</style>${z}`,this.shadow.appendChild(e.content.cloneNode(!0)),this.stageElement=this.shadow.getElementById("stage"),this.canvas=this.shadow.getElementById("canvas"),this.canvasRenderer=new se(this.canvas.getContext("2d")),this.tooltip=this.shadow.getElementById("tooltip"),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()}),this.canvas.addEventListener("pointermove",this.handlePointerMove),this.canvas.addEventListener("pointerleave",this.handlePointerLeave),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 ie(this.currentSighting)}set sightingData(e){this.player.stop(),this.currentSighting=ne(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}set currentTime(e){this.player.seek(e)}get seekableDuration(){return this.player.seekableDuration}get autoReplayEnabled(){return this.loopEnabled}get positionLabel(){return this.timeStartLabel.textContent??""}get durationLabel(){return this.timeEndLabel.textContent??""}set showToolbar(e){this.toolbar.classList.toggle("hidden",!e)}get playbackState(){return this.player.playbackState}get selectedSourceIds(){return this.highlightedSourceIds}get durationSeconds(){return this.currentSighting.event.durationSeconds}set durationSeconds(e){this.currentSighting.event.durationSeconds=e,this.updateTimeLabels(),this.refresh(),this.updatePlayPauseButton()}set selectedSourceIds(e){const t=new Set(e);t.size===this.highlightedSourceIds.size&&[...t].every(n=>this.highlightedSourceIds.has(n))||(this.highlightedSourceIds=t,this.refresh())}refresh(){this.seekInput.max=String(this.player.seekableDuration),this.player.seek(this.player.time)}canvasPointFromEvent(e){const t=this.canvas.getBoundingClientRect();if(!(t.width===0||t.height===0))return{x:(e.clientX-t.left)/t.width*this.canvas.width,y:(e.clientY-t.top)/t.height*this.canvas.height}}onFrame(e,t){this.canvasRenderer.clear(this.canvas.width,this.canvas.height);const s=this.playbackState!=="playing"?this.highlightedSourceIds:de;for(const[n,a]of t){const r=s.has(n);r&&s.size===1?this.canvasRenderer.paintShape({...a,selected:!0}):(this.canvasRenderer.paintShape(a),r&&this.canvasRenderer.paintMemberOutline(a))}if(s.size>1){const n=m.groupBoundsFor([...t].filter(([a])=>s.has(a)).map(([,a])=>a.bounds));this.canvasRenderer.paintGroupHandles(n)}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 V(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=re(navigator.languages,le);e!=="en"&&this.applyMessages(await ce(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=Q(e);this.realDurationMs=t!==void 0&&t>0?t:void 0,this.realStartMs=e.time?P(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 S(e);const t=this.currentSighting.timeline.duration,s=t>0&&e<=t?e/t*this.realDurationMs:e;return this.realStartMs!==void 0?R(O(this.realStartMs+s)):S(s)}formatEndOfTimeline(){return this.realDurationMs===void 0?S(this.currentSighting.timeline.duration):this.realStartMs!==void 0?R(O(this.realStartMs+this.realDurationMs)):S(this.realDurationMs)}}function O(i){const e=new Date(i);return{hour:e.getUTCHours(),minute:e.getUTCMinutes(),second:e.getUTCSeconds()}}function R(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 S(i){const e=Math.round(i/1e3),t=Math.floor(e/60),s=e%60;return`${t}:${String(s).padStart(2,"0")}`}const _="rr0-ufo";function pe(){customElements.get(_)||customElements.define(_,fe)}pe();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rr0/ufoathome",
3
3
  "type": "module",
4
- "version": "0.8.0",
4
+ "version": "0.9.0",
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",
@@ -1 +0,0 @@
1
- const e={oval:"Ovale",saucer:"Soucoupe",triangle:"Triangle",color:"Couleur",transparency:"Transparence",halo:"Halo",shape:"Forme",addShape:"Ajouter une forme",deleteShape:"Supprimer la forme",bringToFront:"Placer devant",sendToBack:"Placer derrière",contextMenuDelete:"Supprimer",confirmDeleteShape:"Supprimer {name} ? Cette action est irréversible.",onlyOneShape:"Il n'y a qu'une seule forme",alreadyAtFront:"Cette forme est déjà la plus en avant",alreadyAtBack:"Cette forme est déjà la plus en arrière",samplingRate:"Fréquence d'échantillonnage (ms)",duration:"Durée (s)",durationPlaceholder:"durée de l'enregistrement",export:"Exporter le JSON",record:"Enregistrer",stop:"Arrêter",latitude:"Latitude",longitude:"Longitude",heading:"Orientation (°)",headingPlaceholder:"inconnu",pitch:"Inclinaison (°)",observationTime:"Début de l'observation (optionnel)",yearPlaceholder:"année",monthPlaceholder:"mois",dayPlaceholder:"jour",hourPlaceholder:"heure",minutePlaceholder:"min",weather:"Météo",cloudCover:"Couverture nuageuse",cloudDarkness:"Obscurité des nuages",precipitationType:"Précipitations",precipitationNone:"Aucune",precipitationRain:"Pluie",precipitationSnow:"Neige",precipitationHail:"Grêle",precipitationIntensity:"Intensité",windDirection:"Direction du vent (°)",windSpeed:"Force du vent",lightning:"Éclairs"};export{e as ufoRecorderMessages_fr};