@rr0/ufoathome 0.46.0 → 0.47.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 +1 @@
1
- const e={play:"Lecture",pause:"Pause",noDuration:"Aucune durée d'observation",autoReplay:"Lecture automatique",currentPosition:"Position actuelle",duration:"Durée",switchToElapsed:"cliquer pour afficher la durée écoulée",switchToClockTime:"cliquer pour afficher l'heure de l'observation",fullscreen:"Plein écran",exitFullscreen:"Quitter le plein écran"};export{e as ufoMessages_fr};
1
+ const e={play:"Lecture",pause:"Pause",noDuration:"Aucune durée d'observation",autoReplay:"Lecture automatique",currentPosition:"Position actuelle",duration:"Durée",switchToElapsed:"cliquer pour afficher la durée écoulée",switchToClockTime:"cliquer pour afficher l'heure de l'observation",fullscreen:"Plein écran",exitFullscreen:"Quitter le plein écran",showWitnessMap:"Voir où était le témoin",hideWitnessMap:"Masquer où était le témoin",mapImageryUnavailable:"Vue aérienne indisponible"};export{e as ufoMessages_fr};
@@ -1,10 +1,22 @@
1
- const de=`
1
+ const Pe=`
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
6
  <div id="tooltip" class="tooltip" hidden></div>
7
- <button id="fullscreen" class="fullscreen-btn" type="button" title="Fullscreen" aria-label="Fullscreen">⛶</button>
7
+ <!-- The corner's own row, rather than one absolutely-positioned button per corner: a second
8
+ button placed by its own right offset would have to hardcode the first one's width, and
9
+ every language names them differently the moment either grows a label. -->
10
+ <div class="corner-buttons" id="corner-buttons">
11
+ <button id="witness-map" type="button" title="Witness's position" aria-label="Witness's position" aria-pressed="false" hidden>🗺</button>
12
+ <button id="fullscreen" type="button" title="Fullscreen" aria-label="Fullscreen">⛶</button>
13
+ </div>
14
+ <!-- Where the witness stood and which way they faced, on real ground — see WitnessMapRenderer.
15
+ Under the buttons that toggle it, and inert to the pointer: the canvas beneath it is the
16
+ recording, and clicking it plays. -->
17
+ <div id="witness-map-panel" class="witness-map-panel" hidden>
18
+ <canvas id="witness-map-canvas" width="240" height="240"></canvas>
19
+ </div>
8
20
  <!-- What the account calls the moment now on screen (see Milestone) — above the controls rather
9
21
  than inside them, because it is a sentence and the toolbar is a row of buttons. Empty and
10
22
  hidden for the recordings that name no moment, which is most of them. -->
@@ -23,7 +35,7 @@ const de=`
23
35
  <button id="loop" type="button" title="Auto-replay" aria-label="Auto-replay" aria-pressed="true">↻</button>
24
36
  </div>
25
37
  </div>
26
- `,ue=`
38
+ `,Ee=`
27
39
  :host {
28
40
  display: block;
29
41
  font-family: sans-serif;
@@ -32,7 +44,7 @@ const de=`
32
44
  definite height of its own (the normal, standalone case — .stage's height stays driven by
33
45
  .frame's content, unchanged) — but it matters when this element is embedded with a definite
34
46
  host size from outside (e.g. <rr0-scene>'s .ufo-overlay sizing this element to fill its own
35
- #stage while THAT is fullscreen): it lets .toolbar/.fullscreen-btn, anchored to .stage below,
47
+ #stage while THAT is fullscreen): it lets .toolbar/.corner-buttons, anchored to .stage below,
36
48
  actually reach that outer element's true edges instead of only .frame's letterboxed ones.
37
49
  display:flex + centering is unconditional (not just under :fullscreen below) for the same
38
50
  nested-embedding case: this .stage is never itself the real fullscreen element when nested
@@ -59,7 +71,7 @@ const de=`
59
71
  justify-content: center;
60
72
  }
61
73
  /* The browser's own fullscreen UA styles force the fullscreened element (.stage) to fill the
62
- whole viewport (100vw/100vh) regardless of its content's aspect ratio. .toolbar/.fullscreen-btn
74
+ whole viewport (100vw/100vh) regardless of its content's aspect ratio. .toolbar/.corner-buttons
63
75
  are anchored to .stage itself (not .frame) specifically so they stay pinned to the true screen
64
76
  edges, full width, like a normal video player's controls — not stuck to the letterboxed
65
77
  content's own (possibly smaller, centered) box above/around them. */
@@ -244,10 +256,20 @@ canvas[data-cursor="rotate"] {
244
256
  opacity: 1;
245
257
  pointer-events: auto;
246
258
  }
247
- .fullscreen-btn {
259
+ .corner-buttons {
248
260
  position: absolute;
249
261
  top: 0.4em;
250
262
  right: 0.4em;
263
+ display: flex;
264
+ gap: 0.3em;
265
+ }
266
+ /* Same trap as .toolbar.hidden and .milestone-caption[hidden]: a class or rule that sets its own
267
+ display outranks the UA sheet's [hidden], and the map button is hidden for every recording that
268
+ states no coordinates — which is most of them. */
269
+ .corner-buttons button[hidden] {
270
+ display: none;
271
+ }
272
+ .corner-buttons button {
251
273
  display: inline-flex;
252
274
  align-items: center;
253
275
  justify-content: center;
@@ -263,6 +285,34 @@ canvas[data-cursor="rotate"] {
263
285
  color: #fff;
264
286
  transition: opacity 0.15s ease;
265
287
  }
288
+ .corner-buttons button[aria-pressed="true"] {
289
+ outline: 2px solid #39f;
290
+ }
291
+ /* Below the buttons that open it, same right edge. A share of the stage rather than a fixed pixel
292
+ size, so it stays the same fraction of the picture in a 320 px embed and in fullscreen — but
293
+ floored, since a map too small to tell a road from a wash is not worth the tiles it costs. */
294
+ .witness-map-panel {
295
+ position: absolute;
296
+ top: 2.7em;
297
+ right: 0.4em;
298
+ width: clamp(140px, 30%, 280px);
299
+ aspect-ratio: 1;
300
+ border: 1px solid rgba(255, 255, 255, 0.35);
301
+ border-radius: 3px;
302
+ overflow: hidden;
303
+ background: rgba(0, 0, 0, 0.55);
304
+ /* The recording underneath stays clickable through it: this is something to look at, not
305
+ something to operate. */
306
+ pointer-events: none;
307
+ }
308
+ .witness-map-panel[hidden] {
309
+ display: none;
310
+ }
311
+ .witness-map-panel canvas {
312
+ display: block;
313
+ width: 100%;
314
+ height: 100%;
315
+ }
266
316
  input[type=range] {
267
317
  flex: 1;
268
318
  }
@@ -311,4 +361,4 @@ input[type=range] {
311
361
  pointer-events: none;
312
362
  white-space: nowrap;
313
363
  }
314
- `;class E extends Error{constructor(e,t,s){super(`${e}: ${t}${s===void 0?"":` (HTTP ${s})`}`),this.kind=e,this.url=t,this.status=s,this.name="SightingFetchError"}}class fe{static async json(e){const t=new URL(e,location.href);if(location.protocol==="https:"&&t.protocol==="http:")throw new E("mixed-content",e);let s;try{s=await fetch(e)}catch{throw await this.diagnose(e,t)}if(!s.ok)throw new E("status",e,s.status);try{return await s.json()}catch{throw new E("malformed",e)}}static async diagnose(e,t){if(t.origin===location.origin)return new E("unreachable",e);try{return await fetch(e,{mode:"no-cors"}),new E("cors",e)}catch{return new E("unreachable",e)}}}const L=[{id:"eye",name:{en:"Naked eye",fr:"Œil nu"},flare:0,projection:"equidistant"},{id:"rectilinear-lens",name:{en:"Camera, unknown device",fr:"Appareil, modèle inconnu"},projection:"rectilinear",fNumber:8,fNumberRange:{min:2,max:22},exposureSeconds:1/250,exposureRangeSeconds:{min:1/1e3,max:8},apertureBlades:6},{id:"instamatic-126",name:{en:"Instamatic, 126 film",fr:"Instamatic, film 126"},projection:"rectilinear",frame:{widthMm:28,heightMm:28,focalLengthMm:43},fNumber:11,exposureSeconds:1/90,years:{from:1963,to:1988},apertureBlades:5,detailUm:20},{id:"slr-35mm-50",name:{en:"35 mm SLR, 50 mm lens",fr:"Reflex 35 mm, objectif 50 mm"},projection:"rectilinear",frame:{widthMm:36,heightMm:24,focalLengthMm:50},fNumber:8,fNumberRange:{min:2,max:16},exposureSeconds:1/250,exposureRangeSeconds:{min:1/1e3,max:3600},years:{from:1959},apertureBlades:6,detailUm:20},{id:"slr-35mm-zoom",name:{en:"35 mm SLR, 70-210 mm zoom",fr:"Reflex 35 mm, zoom 70-210 mm"},projection:"rectilinear",frame:{widthMm:36,heightMm:24,focalLengthMm:135,focalRangeMm:{minMm:70,maxMm:210}},fNumber:8,fNumberRange:{min:4,max:22},exposureSeconds:1/250,exposureRangeSeconds:{min:1/1e3,max:3600},apertureBlades:8,years:{from:1975},detailUm:20},{id:"phone-landscape",name:{en:"Phone, held sideways",fr:"Téléphone, tenu couché"},projection:"rectilinear",frame:{widthMm:7.6,heightMm:5.7,focalLengthMm:5.7},fNumber:1.8,exposureSeconds:1/120,exposureRangeSeconds:{min:1/8e3,max:10},years:{from:2007},apertureBlades:void 0,detailUm:1.4},{id:"phone-portrait",name:{en:"Phone, held upright",fr:"Téléphone, tenu debout"},projection:"rectilinear",frame:{widthMm:5.7,heightMm:7.6,focalLengthMm:5.7},fNumber:1.8,exposureSeconds:1/120,exposureRangeSeconds:{min:1/8e3,max:10},years:{from:2007},apertureBlades:void 0,detailUm:1.4}];class v{static get default(){return L[0]}static byId(e){return L.find(t=>t.id===e)??this.default}static UNAIDED_FIELD_DEG=60;static UNAIDED_ASPECT=16/9;static fieldOfViewDeg(e){const t=e.frame;return t?2*Math.atan(t.heightMm/(2*t.focalLengthMm))*180/Math.PI:v.UNAIDED_FIELD_DEG}static aspectOf(e){const t=e.frame;return t?t.widthMm/t.heightMm:v.UNAIDED_ASPECT}static frameWidthPx(e,t){return Math.round(t*v.aspectOf(e))}static availableAt(e){return e===void 0?L:L.filter(t=>!t.years||e>=t.years.from&&(t.years.to===void 0||e<=t.years.to))}static fieldOfViewDegAt(e,t){const s=e.frame;if(!(!s||t<=0))return 2*Math.atan(s.heightMm/(2*t))*180/Math.PI}static focalLengthMmFor(e,t){const s=e.frame;if(!(!s||t<=0||t>=180))return s.heightMm/(2*Math.tan(t*Math.PI/360))}static bladesShowing(e,t){const s=t??e.fNumber;if(s===void 0||e.apertureBlades===void 0)return 0;const i=e.fNumberRange;return!i||i.max<=i.min?1:Math.max(0,Math.min(1,(s-i.min)/(i.max-i.min)))}static LENS_FLARE_ARTIFACTS=1;static flareArtifactsOf(e){return e.flare??v.LENS_FLARE_ARTIFACTS}static starPointsOf(e){const t=e.apertureBlades;return t===void 0||t<3?0:t%2===0?t:t*2}}class w{static MOON_ANGULAR_WIDTH_DEG=.5237;static CANVAS_WIDTH_PX=640;static CANVAS_HEIGHT_PX=360;static angularWidthDeg(e){return 2*Math.atan(e.sizeM/(2*e.distanceM))*180/Math.PI}static inMoons(e){return e/w.MOON_ANGULAR_WIDTH_DEG}static lerpAngular(e,t,s){if(!(!e||!t))return{widthDeg:e.widthDeg+(t.widthDeg-e.widthDeg)*s,heightDeg:e.heightDeg+(t.heightDeg-e.heightDeg)*s}}static sizeMAt(e,t){return 2*e*Math.tan(t*Math.PI/360)}static distanceMAt(e,t){return e/(2*Math.tan(t*Math.PI/360))}}function me(n,e,t){const{bounds:s}=n;return e>=s.x&&e<=s.x+s.width&&t>=s.y&&t<=s.y+s.height}function g(n,e,t){return n+(e-n)*t}function V(n){const e=/^#([0-9a-f]{6})$/i.exec(n);if(!e)return;const t=parseInt(e[1],16);return[t>>16&255,t>>8&255,t&255]}function pe(n){return`#${n.map(e=>Math.round(e).toString(16).padStart(2,"0")).join("")}`}function ge(n,e,t){const s=V(n),i=V(e);return!s||!i?t<1?n:e:pe([g(s[0],i[0],t),g(s[1],i[1],t),g(s[2],i[2],t)])}function ye(n,e,t){const s={x:g(n.bounds.x,e.bounds.x,t),y:g(n.bounds.y,e.bounds.y,t),width:g(n.bounds.width,e.bounds.width,t),height:g(n.bounds.height,e.bounds.height,t)},i=g(n.angle,e.angle,t),r=g(n.transparency,e.transparency,t),o=g(n.haloScale,e.haloScale,t),a=g(n.blur??0,e.blur??0,t),l=g(n.brightness??0,e.brightness??0,t),h=ge(n.color,e.color,t),d=w.lerpAngular(n.angular,e.angular,t);return n.kind==="polygon"&&e.kind==="polygon"&&n.points.length===e.points.length?{...n,bounds:s,angle:i,transparency:r,haloScale:o,blur:a,brightness:l,color:h,angular:d,behindCloud:t<1?n.behindCloud:e.behindCloud,points:n.points.map((c,u)=>({x:g(c.x,e.points[u].x,t),y:g(c.y,e.points[u].y,t)}))}:{...t<1?n:e,bounds:s,angle:i,transparency:r,haloScale:o,blur:a,brightness:l,color:h,angular:d}}class N{keyframes=[];order=[];groups=[];addKeyframe(e,t){const s=this.findInsertIndex(e);if(this.keyframes[s]?.t===e){const i=new Set(t.map(r=>r.sourceId));this.keyframes[s]={t:e,shapes:[...this.keyframes[s].shapes.filter(r=>!i.has(r.sourceId)),...t]}}else this.keyframes.splice(s,0,{t:e,shapes:[...t]});for(const i of t)this.order.includes(i.sourceId)||this.order.push(i.sourceId)}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}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 i=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(i)return i.shape}}getInterpolatedShapeAt(e,t){const s=this.findShapeAtOrBefore(e,t);if(s?.t===e)return s.shape;const i=this.findShapeAtOrAfter(e,t);return s?i?ye(s.shape,i.shape,(e-s.t)/(i.t-s.t)):s.shape:i?.shape}findShapeAtOrBefore(e,t){let s=this.findInsertIndex(e);for(this.keyframes[s]?.t!==e&&(s-=1);s>=0;s--){const i=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(i)return{t:this.keyframes[s].t,shape:i.shape}}}findShapeAtOrAfter(e,t){for(let s=this.findInsertIndex(e);s<this.keyframes.length;s++){const i=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(i)return{t:this.keyframes[s].t,shape:i.shape}}}hitTest(e,t,s,i){const r=this.sourceIds;for(let o=r.length-1;o>=0;o--){if(i?.has(r[o]))continue;const a=this.getInterpolatedShapeAt(e,r[o]);if(a&&me(a,t,s))return{sourceId:r[o],shape:a}}}removeSource(e){for(let t=this.keyframes.length-1;t>=0;t--){const s=this.keyframes[t].shapes.filter(i=>i.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(i=>i.includes(e));if(t===-1)return;const s=this.groups[t].filter(i=>i!==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 N;for(const s of e.keyframes)t.addKeyframe(s.t,s.shapes);if(e.order){const s=t.order.filter(i=>!e.order.includes(i));t.order=[...e.order,...s]}return e.groups&&(t.groups=e.groups.map(s=>s.filter(i=>t.order.includes(i))).filter(s=>s.length>=2)),t}}function ve(n,e,t){return Math.max(e,Math.min(t,n))}function be(n,e,t){const s=((e-n)%360+540)%360-180;return((n+s*t)%360+360)%360}function M(n,e,t){return n+(e-n)*t}function we(n,e,t){return{lat:n.lat===void 0||e.lat===void 0?void 0:M(n.lat,e.lat,t),lng:n.lng===void 0||e.lng===void 0?void 0:M(n.lng,e.lng,t),elevationM:M(n.elevationM,e.elevationM,t),headingDeg:n.headingDeg===void 0||e.headingDeg===void 0?void 0:be(n.headingDeg,e.headingDeg,t),pitchDeg:M(n.pitchDeg,e.pitchDeg,t),rollDeg:M(n.rollDeg??0,e.rollDeg??0,t),fovDeg:M(n.fovDeg,e.fovDeg,t),fNumber:n.fNumber,focusDistanceM:n.focusDistanceM}}class A{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 i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}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 i=this.keyframes[t]?.t===e?void 0:this.keyframes[t];return s?i?we(s.pose,i.pose,ve((e-s.t)/(i.t-s.t),0,1)):s.pose:i?.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 A;for(const s of e.keyframes)t.addKeyframe(s.t,s.pose);return t}}function x(n,e,t){return n+(e-n)*t}function xe(n,e,t){const s=((e-n)%360+540)%360-180;return((n+s*t)%360+360)%360}function ke(n,e,t){return Math.max(e,Math.min(t,n))}function Se(n,e,t){return{cloudCover:x(n.cloudCover,e.cloudCover,t),cloudDarkness:x(n.cloudDarkness,e.cloudDarkness,t),highCloudCover:n.highCloudCover===void 0||e.highCloudCover===void 0?void 0:x(n.highCloudCover,e.highCloudCover,t),iceCrystalAlignment:n.iceCrystalAlignment===void 0||e.iceCrystalAlignment===void 0?void 0:x(n.iceCrystalAlignment,e.iceCrystalAlignment,t),lowerCloudCover:n.lowerCloudCover===void 0||e.lowerCloudCover===void 0?void 0:x(n.lowerCloudCover,e.lowerCloudCover,t),cloudBaseM:n.cloudBaseM===void 0||e.cloudBaseM===void 0?void 0:x(n.cloudBaseM,e.cloudBaseM,t),precipitationType:t<1?n.precipitationType:e.precipitationType,precipitationIntensity:x(n.precipitationIntensity,e.precipitationIntensity,t),windDirectionDeg:xe(n.windDirectionDeg,e.windDirectionDeg,t),windSpeed:x(n.windSpeed,e.windSpeed,t),storm:t<1?n.storm:e.storm}}class D{keyframes=[];addKeyframe(e,t){const s=this.findInsertIndex(e);this.keyframes[s]?.t===e?this.keyframes[s]={t:e,weather:t}:this.keyframes.splice(s,0,{t:e,weather: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 i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}return t}getLatestWeatherAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].weather:void 0}getInterpolatedWeatherAt(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.weather;const i=this.keyframes[t]?.t===e?void 0:this.keyframes[t];return s?i?Se(s.weather,i.weather,ke((e-s.t)/(i.t-s.t),0,1)):s.weather:i?.weather}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 D;for(const s of e.keyframes)t.addKeyframe(s.t,s.weather);return t}}class C{keyframes=[];addKeyframe(e,t){const s=this.findInsertIndex(e);this.keyframes[s]?.t===e?this.keyframes[s]={t:e,sound:t}:this.keyframes.splice(s,0,{t:e,sound:t})}clear(){this.keyframes.length=0}removeKeyframeAt(e){const t=this.findInsertIndex(e);this.keyframes[t]?.t===e&&this.keyframes.splice(t,1)}getLatestSoundAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].sound:void 0}getInterpolatedSoundAt(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.sound;const i=this.keyframes[t]?.t===e?void 0:this.keyframes[t];if(!s)return i?.sound;if(!i)return s.sound;const r=(e-s.t)/(i.t-s.t);return this.lerp(s.sound,i.sound,Math.max(0,Math.min(1,r)))}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 C;for(const s of e.keyframes)t.addKeyframe(s.t,s.sound);return t}lerp(e,t,s){return{kind:s<1?e.kind:t.kind,volume:e.volume+(t.volume-e.volume)*s,pitchHz:e.pitchHz+(t.pitchHz-e.pitchHz)*s,src:s<1?e.src:t.src}}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}return t}}const Te=100,Ee={kind:"none",volume:0,pitchHz:Te};function U(n){if(n.year!==void 0)return Date.UTC(n.year,(n.month??1)-1,n.day??1,n.hour??0,n.minute??0,n.second??0)}function X(n){return(((n.day??0)*24+(n.hour??0))*60+(n.minute??0))*6e4+(n.second??0)*1e3}const Me=["year","month","day","hour","minute"],Pe=["day","hour","minute"];function q(n,e){return e.filter(t=>n[t]!==void 0).join(",")}function Ie(n){if(n.durationSeconds!==void 0)return n.durationSeconds*1e3;if(!n.time||!n.endTime)return;const e=n.time.year!==void 0&&n.endTime.year!==void 0,t=e?Me:Pe,s=q(n.time,t);if(!(s===""||s!==q(n.endTime,t)))return e?U(n.endTime)-U(n.time):X(n.endTime)-X(n.time)}class H{constructor(e,t,s,i,r,o,a,l,h=[],d,c,u,f=[]){this.event=e,this.timeline=t,this.witnessTrack=s,this.weatherTrack=i,this.soundTrack=r,this.witness=o,this.caseId=a,this.weather=l,this.decor=h,this.weatherSource=d,this.instrumentId=c,this.exposureSeconds=u,this.milestones=f}get exposure(){return this.exposureSeconds??this.instrument.exposureSeconds}get instrument(){return v.byId(this.instrumentId)}static create(e,t,s){return new H({eventType:"sighting",time:e,place:t},new N,new A,new D,new C,s)}}const Ae=0,De=0;function oe(n,e){const t=n.witnessTrack.getInterpolatedPoseAt(e);if(t)return t;const s=n.event.place?.[0];if(s)return{lat:s.lat,lng:s.lng,elevationM:Ae,headingDeg:void 0,pitchDeg:De,fovDeg:v.fieldOfViewDeg(n.instrument)}}function Ce(n,e){return n.soundTrack.getInterpolatedSoundAt(e)??Ee}class Le{constructor(e,t){this.timeline=e,this.onFrame=t}rafId=null;state="stopped";currentT=0;lastWallTime=0;playbackRate=1;loop=!1;onEnded;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),this.onEnded?.();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 i=this.timeline.getInterpolatedShapeAt(e,s);i&&t.set(s,i)}this.onFrame(e,t)}}const ae=["nw","n","ne","e","se","s","sw","w"],Fe=24,F=8,Be=3,Re=8;function B(n,e,t){const s=n.x-e.x,i=n.y-e.y,r=Math.cos(t),o=Math.sin(t);return{x:e.x+s*r-i*o,y:e.y+s*o+i*r}}function P(n){return{x:n.x+n.width/2,y:n.y+n.height/2}}const Oe={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 y{static handlePointsFor(e){const{x:t,y:s,width:i,height:r}=e.bounds,o={nw:{x:t,y:s},n:{x:t+i/2,y:s},ne:{x:t+i,y:s},e:{x:t+i,y:s+r/2},se:{x:t+i,y:s+r},s:{x:t+i/2,y:s+r},sw:{x:t,y:s+r},w:{x:t,y:s+r/2},rotate:{x:t+i/2,y:s-Fe}},a=P(e.bounds),l={};for(const h of Object.keys(o))l[h]=B(o[h],a,e.angle);return l}static hitTestHandle(e,t,s=8,i=[...ae,"rotate"]){const r=y.handlePointsFor(e);for(const o of i)if(Math.hypot(t.x-r[o].x,t.y-r[o].y)<=s)return o}static HANDLE_DIRECTION_DEG={e:0,se:45,s:90,sw:135,w:180,nw:225,n:270,ne:315};static resizeAxisFor(e,t){const i=((y.HANDLE_DIRECTION_DEG[e]+t*180/Math.PI)%180+180)%180/45,r=["ew","nwse","ns","nesw"];return r[Math.round(i)%r.length]}static resizeBounds(e,t,s,i){const r=P(e),o=B(i,r,-t),{x:a,y:l,width:h,height:d}=e,c=Oe[s];let u=a,f=l,m=a+h,p=l+d;return c.left&&(u=Math.min(o.x,m-F)),c.right&&(m=Math.max(o.x,u+F)),c.top&&(f=Math.min(o.y,p-F)),c.bottom&&(p=Math.max(o.y,f+F)),{x:u,y:f,width:m-u,height:p-f}}static resizeShape(e,t,s){if(t==="rotate")throw new Error("resizeShape does not accept the rotate handle");const{width:i,height:r}=e.bounds,o=y.resizeBounds(e.bounds,e.angle,t,s);if(e.kind==="oval")return{...e,bounds:o};const a=i===0?1:o.width/i,l=r===0?1:o.height/r;return{...e,bounds:o,points:e.points.map(h=>({x:h.x*a,y:h.y*l}))}}static rotateShape(e,t){const s=P(e.bounds),i=Math.atan2(t.y-s.y,t.x-s.x)+Math.PI/2;return{...e,angle:i}}static groupBoundsFor(e){const t=Math.min(...e.map(o=>o.x)),s=Math.min(...e.map(o=>o.y)),i=Math.max(...e.map(o=>o.x+o.width)),r=Math.max(...e.map(o=>o.y+o.height));return{x:t,y:s,width:i-t,height:r-s}}static vertexPointsFor(e){const t=P(e.bounds);return e.points.map(s=>B({x:e.bounds.x+s.x,y:e.bounds.y+s.y},t,e.angle))}static hitTestVertex(e,t,s=Re){const i=y.vertexPointsFor(e);let r,o=s;return i.forEach((a,l)=>{const h=Math.hypot(t.x-a.x,t.y-a.y);h<=o&&(o=h,r=l)}),r}static toLocalPoint(e,t){const s=P(e.bounds),i=B(t,s,-e.angle);return{x:i.x-e.bounds.x,y:i.y-e.bounds.y}}static moveVertex(e,t,s){const i=e.points.map((c,u)=>u===t?y.toLocalPoint(e,s):c),r=Math.min(...i.map(c=>c.x)),o=Math.min(...i.map(c=>c.y)),a=Math.max(...i.map(c=>c.x)),l=Math.max(...i.map(c=>c.y)),h={x:e.bounds.x+r,y:e.bounds.y+o,width:a-r,height:l-o},d=i.map(c=>({x:c.x-r,y:c.y-o}));return{...e,bounds:h,points:d}}static insertVertexNear(e,t){const s=y.toLocalPoint(e,t),{points:i}=e;let r=i.length-1,o=1/0;for(let l=0;l<i.length;l++){const h=i[l],d=i[(l+1)%i.length],c=y.distanceToSegment(s,h,d);c<o&&(o=c,r=l)}const a=[...i.slice(0,r+1),s,...i.slice(r+1)];return{...e,points:a}}static distanceToSegment(e,t,s){const i=s.x-t.x,r=s.y-t.y,o=i*i+r*r,a=o===0?0:Math.max(0,Math.min(1,((e.x-t.x)*i+(e.y-t.y)*r)/o)),l=t.x+a*i,h=t.y+a*r;return Math.hypot(e.x-l,e.y-h)}static deleteVertex(e,t){return e.points.length<=Be?e:{...e,points:e.points.filter((s,i)=>i!==t)}}}const $=6,R=$/2,_e=20,Ne=24,He=9,K=6,ze=7,Ue=6,W=4;class $e{constructor(e){this.ctx=e}starPoints=0;roll=0;clear(e,t){this.ctx.clearRect(0,0,e,t)}paintShape(e){this.ctx.save(),this.ctx.globalAlpha=1-e.transparency;const t=e.blur??0;t>0&&(this.ctx.filter=`blur(${(Ne*t).toFixed(2)}px)`);const s=e.brightness??0;s>0&&this.paintDazzle(e,s),e.haloScale>0&&this.paintHalo(e),this.paintBase(e),this.ctx.restore(),e.selected&&this.paintSelectionHandles(e)}setStarPoints(e){this.starPoints=Math.max(0,Math.floor(e))}setRoll(e){this.roll=e}paintDazzle(e,t){const{x:s,y:i,width:r,height:o}=e.bounds,a=s+r/2,l=i+o/2,h=Math.max(r,o)/2,d=Math.max(h*(1+He*t),Ue*t);if(d<=0)return;this.ctx.save(),this.ctx.globalCompositeOperation="lighter";const c=this.ctx.createRadialGradient(a,l,0,a,l,d);for(let u=0;u<=K;u++){const f=u/K,m=1/(1+24*f*f);this.ctx.globalAlpha=1,c.addColorStop(f,this.withAlpha(e.color,m*t*.55))}if(this.ctx.fillStyle=c,this.ctx.beginPath(),this.ctx.arc(a,l,d,0,2*Math.PI),this.ctx.fill(),this.starPoints>0){const u=h*(1+ze*t);this.ctx.lineWidth=Math.max(1,h*.12),this.ctx.lineCap="round";for(let f=0;f<this.starPoints;f++){const m=f/this.starPoints*Math.PI*2+this.roll,p=a+Math.cos(m)*u,b=l+Math.sin(m)*u,k=this.ctx.createLinearGradient(a,l,p,b);k.addColorStop(0,this.withAlpha(e.color,t*.55)),k.addColorStop(.35,this.withAlpha(e.color,t*.22)),k.addColorStop(1,this.withAlpha(e.color,0)),this.ctx.strokeStyle=k,this.ctx.beginPath(),this.ctx.moveTo(a,l),this.ctx.lineTo(p,b),this.ctx.stroke()}}this.ctx.restore()}dazzledFill(e,t){if(t<=0)return e.color;const s=/^#([0-9a-f]{6})$/i.exec(e.color.trim());if(!s)return e.color;const i=Number.parseInt(s[1],16),r=o=>Math.round(o+(255-o)*t);return`rgb(${r(i>>16&255)}, ${r(i>>8&255)}, ${r(i&255)})`}withAlpha(e,t){const s=Math.max(0,Math.min(1,t)),i=/^#([0-9a-f]{6})$/i.exec(e.trim());if(!i)return e;const r=Number.parseInt(i[1],16);return`rgba(${r>>16&255}, ${r>>8&255}, ${r&255}, ${s.toFixed(3)})`}paintBase(e){if(this.ctx.fillStyle=this.dazzledFill(e,e.brightness??0),this.ctx.beginPath(),e.kind==="oval"){const{x:t,y:s,width:i,height:r}=e.bounds,o=i/2,a=r/2;this.ctx.ellipse(t+o,s+a,o,a,e.angle,0,2*Math.PI)}else{const{x:t,y:s,width:i,height:r}=e.bounds;this.ctx.save(),this.ctx.translate(t+i/2,s+r/2),this.ctx.rotate(e.angle),this.ctx.translate(-i/2,-r/2),e.points.forEach((o,a)=>{a===0?this.ctx.moveTo(o.x,o.y):this.ctx.lineTo(o.x,o.y)}),this.ctx.closePath(),this.ctx.restore()}this.ctx.fill()}paintHalo(e){this.ctx.save(),this.ctx.shadowColor=e.color,this.ctx.shadowBlur=_e*e.haloScale,this.paintBase(e),this.ctx.restore()}paintSelectionHandles(e){this.paintHandleFrame(y.handlePointsFor(e),{includeRotate:!0}),e.kind==="polygon"&&this.paintVertexHandles(e)}paintVertexHandles(e){this.ctx.fillStyle="#39f";for(const t of y.vertexPointsFor(e))this.ctx.beginPath(),this.ctx.ellipse(t.x,t.y,W,W,0,0,2*Math.PI),this.ctx.fill()}paintMemberOutline(e){this.paintOutline(y.handlePointsFor(e))}paintGroupHandles(e){this.paintHandleFrame(y.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 ae){const i=e[s];this.ctx.fillRect(i.x-R,i.y-R,$,$)}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,R+1,R+1,0,0,2*Math.PI),this.ctx.fill())}}const Z=.05,J=.08,Y=2,j=3,Ge=14,Ve=.02;class Xe{context;voice;voiceToken=0;buffers=new Map;noiseBuffer;crackleBuffer;requested;resume(){if(!this.context){if(typeof AudioContext>"u")return;try{this.context=new AudioContext}catch(e){console.warn("SightingAudio: Web Audio unavailable, the sighting stays silent:",e);return}}this.context.state==="suspended"&&this.context.resume()}setSound(e){if(this.requested=e,!this.context)return;const t=this.keyFor(e);if(!t){this.silence();return}if(this.voice?.key!==t){this.stopVoice();const s=++this.voiceToken;t.startsWith("src:")?this.buildRecordedVoice(e.src,t,s):this.voice=this.buildSynthesizedVoice(e,t)}this.applyTo(this.voice,e)}silence(){this.voiceToken++,this.stopVoice()}dispose(){this.silence(),this.context?.close(),this.context=void 0,this.buffers.clear(),this.noiseBuffer=void 0,this.crackleBuffer=void 0}keyFor(e){if(!(e.volume<=0))return e.src?`src:${e.src}`:e.kind==="none"?void 0:e.kind}applyTo(e,t){const s=this.context;if(!e||!s)return;const i=s.currentTime;e.volumeParam.setTargetAtTime(Math.max(0,Math.min(1,t.volume))*e.gainScale,i,Z);for(const{param:r,ratio:o}of e.pitch)r.setTargetAtTime(Math.min(t.pitchHz*o,s.sampleRate/2-1),i,Z)}buildSynthesizedVoice(e,t){const s=this.context;if(s)switch(e.kind){case"hum":return this.buildHum(s,t);case"whistle":return this.buildWhistle(s,t);case"rumble":return this.buildRumble(s,t);case"crackle":return this.buildCrackle(s,t);default:return}}buildHum(e,t){const s=e.createOscillator();s.type="sawtooth";const i=e.createOscillator();i.type="sawtooth";const r=e.createBiquadFilter();r.type="lowpass",r.Q.value=1;const o=e.createGain();return o.gain.value=0,s.connect(r),i.connect(r),r.connect(o).connect(e.destination),s.start(),i.start(),{key:t,nodes:[r,o],sources:[s,i],volumeParam:o.gain,gainScale:.16,pitch:[{param:s.frequency,ratio:1},{param:i.frequency,ratio:1.006},{param:r.frequency,ratio:6}]}}buildWhistle(e,t){const s=e.createOscillator();s.type="sine";const i=e.createOscillator();i.type="sine",i.frequency.value=5;const r=e.createGain(),o=e.createGain();return o.gain.value=0,i.connect(r).connect(s.frequency),s.connect(o).connect(e.destination),s.start(),i.start(),{key:t,nodes:[r,o],sources:[s,i],volumeParam:o.gain,gainScale:.22,pitch:[{param:s.frequency,ratio:1},{param:r.gain,ratio:.012}]}}buildRumble(e,t){const s=e.createBufferSource();s.buffer=this.brownNoise(e),s.loop=!0;const i=e.createBiquadFilter();i.type="lowpass",i.Q.value=.8;const r=e.createGain();return r.gain.value=0,s.connect(i).connect(r).connect(e.destination),s.start(),{key:t,nodes:[i,r],sources:[s],volumeParam:r.gain,gainScale:1.4,pitch:[{param:i.frequency,ratio:1}]}}buildCrackle(e,t){const s=e.createBufferSource();s.buffer=this.whiteNoise(e),s.loop=!0;const i=e.createBiquadFilter();i.type="bandpass",i.Q.value=1.2;const r=e.createGain();r.gain.value=0;const o=e.createBufferSource();o.buffer=this.crackleEnvelope(e),o.loop=!0;const a=e.createGain();return a.gain.value=0,o.connect(a).connect(r.gain),s.connect(i).connect(r).connect(e.destination),s.start(),o.start(),{key:t,nodes:[i,r,a],sources:[s,o],volumeParam:a.gain,gainScale:.9,pitch:[{param:i.frequency,ratio:4}]}}async buildRecordedVoice(e,t,s){const i=this.context;if(!i)return;let r;try{r=await this.getBuffer(e)}catch(l){console.warn("SightingAudio: the recorded sound failed to load, staying silent:",l);return}if(s!==this.voiceToken||!this.context)return;const o=i.createBufferSource();o.buffer=r,o.loop=!0;const a=i.createGain();a.gain.value=0,o.connect(a).connect(i.destination),o.start(),this.voice={key:t,nodes:[a],sources:[o],volumeParam:a.gain,gainScale:1,pitch:[]},this.requested&&this.applyTo(this.voice,this.requested)}async getBuffer(e){const t=this.context;if(!t)throw new Error("SightingAudio: AudioContext not ready — resume() must be called first");let s=this.buffers.get(e);return s||(s=fetch(e).then(i=>{if(!i.ok)throw new Error(`Audio fetch failed (${i.status}): ${e}`);return i.arrayBuffer()}).then(i=>t.decodeAudioData(i)),this.buffers.set(e,s)),s}whiteNoise(e){if(!this.noiseBuffer){const t=e.createBuffer(1,Math.floor(e.sampleRate*Y),e.sampleRate),s=t.getChannelData(0);for(let i=0;i<s.length;i++)s[i]=Math.random()*2-1;this.noiseBuffer=t}return this.noiseBuffer}brownNoise(e){const t=e.createBuffer(1,Math.floor(e.sampleRate*Y),e.sampleRate),s=t.getChannelData(0);let i=0,r=0;for(let o=0;o<s.length;o++)i=(i+.02*(Math.random()*2-1))/1.02,s[o]=i,r=Math.max(r,Math.abs(i));if(r>0)for(let o=0;o<s.length;o++)s[o]/=r;return t}crackleEnvelope(e){if(!this.crackleBuffer){const t=e.createBuffer(1,Math.floor(e.sampleRate*j),e.sampleRate),s=t.getChannelData(0),i=Math.max(1,Math.floor(Ve*e.sampleRate)),r=Math.round(j*Ge);for(let o=0;o<r;o++){const a=Math.floor(Math.random()*s.length),l=.4+Math.random()*.6;for(let h=0;h<i&&a+h<s.length;h++)s[a+h]=Math.max(s[a+h],l*(1-h/i))}this.crackleBuffer=t}return this.crackleBuffer}stopVoice(){const e=this.voice,t=this.context;if(this.voice=void 0,!e||!t)return;const s=t.currentTime;e.volumeParam.cancelScheduledValues(s),e.volumeParam.setTargetAtTime(0,s,J/3);const i=s+J;for(const r of e.sources){r.onended=()=>{r.disconnect();for(const o of e.nodes)o.disconnect()};try{r.stop(i)}catch{r.disconnect();for(const o of e.nodes)o.disconnect()}}}}function G(n){return[...n].sort((e,t)=>e.t-t.t)}function qe(n,e){let t;for(const s of G(n)){if(s.t>e)break;t=s}return t}const z=Math.PI/180,O=180/Math.PI;class I{constructor(e,t,s){this.kind=e,this.canvasHeightPx=t,this.fovDeg=s;const i=s*z/2;this.focalPx=e==="equidistant"?t/2/i:t/2/Math.tan(i)}focalPx;static of(e,t,s){return new I(e.projection,t,s)}degToPx(e){const t=e*z;return this.kind==="equidistant"?this.focalPx*t:2*this.focalPx*Math.tan(t/2)}pxToDeg(e){return this.kind==="equidistant"?e/this.focalPx*O:2*Math.atan(e/(2*this.focalPx))*O}angleDegToRadiusPx(e){const t=e*z;return this.kind==="equidistant"?this.focalPx*t:this.focalPx*Math.tan(t)}radiusPxToAngleDeg(e){return this.kind==="equidistant"?e/this.focalPx*O:Math.atan(e/this.focalPx)*O}ofBounds(e){return{widthDeg:this.pxToDeg(e.width),heightDeg:this.pxToDeg(e.height)}}toBoundsSize(e){return{width:this.degToPx(e.widthDeg),height:this.degToPx(e.heightDeg)}}widthPx(e){return this.degToPx(w.angularWidthDeg(e))}}class le{static fovOf(e,t){return oe(e,t)?.fovDeg??v.fieldOfViewDeg(e.instrument)}static toAngular(e){this.eachKeyframe(e,(t,s)=>({...t,angular:s.ofBounds(t.bounds)}))}static toBounds(e){this.eachKeyframe(e,(t,s)=>{if(!t.angular)return t;const{width:i,height:r}=s.toBoundsSize(t.angular),o={x:t.bounds.x+(t.bounds.width-i)/2,y:t.bounds.y+(t.bounds.height-r)/2,width:i,height:r};if(t.kind!=="polygon")return{...t,bounds:o};const a=t.bounds.width===0?1:i/t.bounds.width,l=t.bounds.height===0?1:r/t.bounds.height;return{...t,bounds:o,points:t.points.map(h=>({x:h.x*a,y:h.y*l}))}})}static reproject(e,t,s){const i=v.frameWidthPx(t,w.CANVAS_HEIGHT_PX)/2,r=v.frameWidthPx(e.instrument,w.CANVAS_HEIGHT_PX)/2,o=w.CANVAS_HEIGHT_PX/2;for(const a of[...e.timeline.allKeyframes]){const l=this.fovOf(e,a.t),h=s?.get(a.t)??l,d=I.of(t,w.CANVAS_HEIGHT_PX,h),c=I.of(e.instrument,w.CANVAS_HEIGHT_PX,l);e.timeline.addKeyframe(a.t,a.shapes.map(u=>{const{bounds:f}=u.shape,m=f.x+f.width/2-i,p=f.y+f.height/2-o,b=Math.hypot(m,p),k=b===0?1:c.angleDegToRadiusPx(d.radiusPxToAngleDeg(b))/b,ce={...f,x:r+m*k-f.width/2,y:o+p*k-f.height/2};return{...u,shape:{...u.shape,bounds:ce}}}))}this.toBounds(e)}static eachKeyframe(e,t){const{timeline:s}=e;for(const i of[...s.allKeyframes]){const r=this.fovOf(e,i.t),o=I.of(e.instrument,w.CANVAS_HEIGHT_PX,r);s.addKeyframe(i.t,i.shapes.map(a=>({...a,shape:t(a.shape,o)})))}}}function Ke(n){return le.toAngular(n),{version:1,time:n.event.time,endTime:n.event.endTime,durationSeconds:n.event.durationSeconds,utcOffsetHours:n.event.utcOffsetHours,timeZone:n.event.timeZone,place:n.event.place,witness:n.witness,caseId:n.caseId,description:n.event.description,tags:n.event.tags,timeline:n.timeline.toJSON(),witnessTrack:n.witnessTrack.toJSON(),weatherTrack:n.weatherTrack.toJSON(),soundTrack:n.soundTrack.toJSON(),weather:n.weather,decor:n.decor,milestones:n.milestones.length>0?n.milestones:void 0,weatherSource:n.weatherSource,instrument:n.instrumentId,exposureSeconds:n.exposureSeconds}}function We(n){const e=new H({eventType:"sighting",time:n.time,endTime:n.endTime,durationSeconds:n.durationSeconds,utcOffsetHours:n.utcOffsetHours,timeZone:n.timeZone,place:n.place,description:n.description,tags:n.tags},N.fromJSON(n.timeline),n.witnessTrack?A.fromJSON(n.witnessTrack):new A,n.weatherTrack?D.fromJSON(n.weatherTrack):new D,n.soundTrack?C.fromJSON(n.soundTrack):new C,n.witness,n.caseId,n.weather,n.decor??[],n.weatherSource,n.instrument,n.exposureSeconds??n.witnessTrack?.keyframes.map(t=>t.pose.exposureSeconds).find(t=>t!==void 0),G(n.milestones??[]));return le.toBounds(e),e}function Ze(n,e){for(const t of n){const s=t.toLowerCase().split("-")[0];if(e.includes(s))return s}return"en"}class Q{static preferencesFor(e){const t=navigator.languages??[],s=this.declaredFor(e);return s?[s,...t]:t}static declaredFor(e){for(let t=e;t;){const s=t.closest("[lang]")?.getAttribute("lang")?.trim();if(s)return s;const i=t.getRootNode();t=i instanceof ShadowRoot?i.host:void 0}}}class S{constructor(e){this.preferences=e}read(e){if(e===void 0||typeof e=="string")return e===""?void 0:e;for(const s of this.preferences){const i=e[s]??e[S.base(s)];if(i)return i}const t=new Set(this.preferences.map(s=>S.base(s)));for(const[s,i]of Object.entries(e))if(i&&t.has(S.base(s)))return i;return Object.values(e).find(s=>s)??void 0}write(e,t,s){const i=t?.trim()?t.trim():void 0;if(e===void 0||typeof e=="string")return i;const r={...e},o=Object.keys(r).find(a=>a===s)??Object.keys(r).find(a=>S.base(a)===S.base(s))??s;return i===void 0?delete r[o]:r[o]=i,Object.keys(r).length===0?void 0:r}static base(e){return e.toLowerCase().split("-")[0]}}const Je="modulepreload",Ye=function(n,e){return new URL(n,e).href},ee={},te=function(e,t,s){let i=Promise.resolve();if(t&&t.length>0){let o=function(d){return Promise.all(d.map(c=>Promise.resolve(c).then(u=>({status:"fulfilled",value:u}),u=>({status:"rejected",reason:u}))))};const a=document.getElementsByTagName("link"),l=document.querySelector("meta[property=csp-nonce]"),h=l?.nonce||l?.getAttribute("nonce");i=o(t.map(d=>{if(d=Ye(d,s),d in ee)return;ee[d]=!0;const c=d.endsWith(".css"),u=c?'[rel="stylesheet"]':"";if(!!s)for(let p=a.length-1;p>=0;p--){const b=a[p];if(b.href===d&&(!c||b.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${d}"]${u}`))return;const m=document.createElement("link");if(m.rel=c?"stylesheet":Je,c||(m.as="script"),m.crossOrigin="",m.href=d,h&&m.setAttribute("nonce",h),document.head.appendChild(m),c)return new Promise((p,b)=>{m.addEventListener("load",p),m.addEventListener("error",()=>b(new Error(`Unable to preload CSS for ${d}`)))})}))}function r(o){const a=new Event("vite:preloadError",{cancelable:!0});if(a.payload=o,window.dispatchEvent(a),!a.defaultPrevented)throw o}return i.then(o=>{for(const a of o||[])a.status==="rejected"&&r(a.reason);return e().catch(r)})},je=["en","fr"],Qe={en:()=>te(()=>Promise.resolve().then(()=>tt),void 0,import.meta.url).then(n=>n.ufoMessages_en),fr:()=>te(()=>import("./UfoMessages_fr-Hq24iVzT.js"),[],import.meta.url).then(n=>n.ufoMessages_fr)};function et(n){return Qe[n]()}const he={play:"Play",pause:"Pause",noDuration:"No observation duration",autoReplay:"Auto-replay",currentPosition:"Current position",duration:"Duration",switchToElapsed:"click to show elapsed time",switchToClockTime:"click to show the time of day",fullscreen:"Fullscreen",exitFullscreen:"Exit fullscreen"},tt=Object.freeze(Object.defineProperty({__proto__:null,ufoMessages_en:he},Symbol.toStringTag,{value:"Module"})),se=new Set;class T extends HTMLElement{static get observedAttributes(){return["src"]}shadow;stageElement;canvas;canvasRenderer;tooltip;toolbar;playPauseButton;loopButton;fullscreenButton;seekInput;milestoneMarks;milestoneCaption;timeStartLabel;timeEndLabel;currentSighting=H.create();sightingAudio=new Xe;soundPreview;player;loopEnabled=!0;playbackBeforeClick;highlightedSourceIds=new Set;occludedSourceIds=se;enableClickToPlay=!0;fullscreenTarget;messages=he;realDurationMs;realStartMs;showClockTime=!0;handleFullscreenChange=()=>this.updateFullscreenButton();simulatedFullscreen=!1;styleBeforeSimulatedFullscreen;bodyOverflowBeforeSimulatedFullscreen;handleSimulatedFullscreenKey=e=>{e.key==="Escape"&&this.exitSimulatedFullscreen()};handlePointerMove=e=>{const t=this.canvasPointFromEvent(e),s=t&&this.shapeAt(t.x,t.y),i=this.said.read(s?.shape.title);if(!i){this.tooltip.hidden=!0;return}this.tooltip.textContent=i,this.tooltip.hidden=!1;const r=this.stageElement.getBoundingClientRect();this.tooltip.style.left=`${e.clientX-r.left+12}px`,this.tooltip.style.top=`${e.clientY-r.top+12}px`};handlePointerLeave=()=>{this.tooltip.hidden=!0};constructor(){super(),this.shadow=this.attachShadow({mode:"open"});const e=document.createElement("template");e.innerHTML=`<style>${ue}</style>${de}`,this.shadow.appendChild(e.content.cloneNode(!0)),this.stageElement=this.shadow.getElementById("stage"),this.canvas=this.shadow.getElementById("canvas"),this.canvasRenderer=new $e(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.milestoneMarks=this.shadow.getElementById("milestone-marks"),this.milestoneCaption=this.shadow.getElementById("milestone-caption"),this.timeStartLabel=this.shadow.getElementById("time-start"),this.timeEndLabel=this.shadow.getElementById("time-end");for(const t of[this.timeStartLabel,this.timeEndLabel])t.addEventListener("click",()=>this.toggleTimeDisplay()),t.addEventListener("keydown",s=>{s.key!=="Enter"&&s.key!==" "||(s.preventDefault(),this.toggleTimeDisplay())});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",t=>{this.enableClickToPlay&&(t.detail<=1&&(this.playbackBeforeClick={state:this.playbackState,time:this.currentTime}),this.togglePlayPause())}),this.canvas.addEventListener("dblclick",t=>{this.enableClickToPlay&&(t.preventDefault(),this.restorePlayback(),this.toggleFullscreen())}),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(){this.saidTexts=void 0;const e=this.getAttribute("src");e&&this.loadFromSrc(e)}disconnectedCallback(){document.removeEventListener("fullscreenchange",this.handleFullscreenChange),this.exitSimulatedFullscreen(),this.sightingAudio.dispose()}attributeChangedCallback(e,t,s){e==="src"&&s&&s!==t&&this.isConnected&&this.loadFromSrc(s)}async loadFromSrc(e){this.sightingData=await fe.json(e)}get sightingData(){return Ke(this.currentSighting)}set sightingData(e){this.player.stop(),this.soundPreview=void 0,this.sightingAudio.silence(),this.currentSighting=We(e),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.refresh()}get sighting(){return this.currentSighting}previewSound(e){this.soundPreview=e,this.sightingAudio.resume(),this.sightingAudio.setSound(e)}stopSoundPreview(){this.soundPreview=void 0,this.sightingAudio.silence()}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}set autoReplayEnabled(e){e!==this.loopEnabled&&this.toggleLoop()}play(){this.player.seekableDuration<=0||this.playbackState==="playing"||this.togglePlayPause()}pause(){this.playbackState==="playing"&&this.togglePlayPause()}get positionLabel(){return this.timeStartLabel.textContent??""}get durationLabel(){return this.timeEndLabel.textContent??""}get showingClockTime(){return this.canShowClockTime}get canSwitchTimeDisplay(){return this.realStartMs!==void 0}toggleTimeDisplay(){this.canSwitchTimeDisplay&&(this.showClockTime=!this.showClockTime,this.updateTimeLabels(),this.updateTimeLabelTitles(),this.dispatchEvent(new CustomEvent("timedisplaychange",{bubbles:!0,composed:!0})))}get canShowClockTime(){return this.realStartMs!==void 0&&this.showClockTime}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(i=>this.highlightedSourceIds.has(i))||(this.highlightedSourceIds=t,this.refresh())}setOccludedSourceIds(e){e.size===this.occludedSourceIds.size&&[...e].every(s=>this.occludedSourceIds.has(s))||(this.occludedSourceIds=e,this.refresh())}hasVisibleShapeAt(e,t){return this.shapeAt(e,t)!==void 0}refresh(){this.applyFrameFormat(),this.updateTimeLabels(),this.seekInput.max=String(this.player.seekableDuration),this.refreshMilestoneMarks(),this.player.seek(this.player.time)}refreshMilestoneMarks(){const e=this.player.seekableDuration,t=e>0?G(this.sighting.milestones):[];this.milestoneMarks.replaceChildren(...t.map(s=>{const i=document.createElement("button");i.type="button",i.className="milestone-mark",i.style.left=`${Math.min(Math.max(s.t/e,0),1)*100}%`;const r=this.said.read(s.label)??"",o=this.said.read(s.note),a=o?`${r} — ${o}`:r;return i.title=a,i.setAttribute("aria-label",a),i.addEventListener("click",()=>this.player.seek(s.t)),i}))}showMilestoneAt(e){const t=this.sighting.milestones.length>0?qe(this.sighting.milestones,e):void 0;if(this.milestoneCaption.hidden=t===void 0,!t)return;const s=document.createElement("b");s.textContent=this.said.read(t.label)??"";const i=this.said.read(t.note),r=i?` — ${i}`:"";this.milestoneCaption.replaceChildren(s,document.createTextNode(r))}exposureInstants(e){const t=this.exposureTimes(e),s=1/t.length;return t.map(i=>({shapes:this.shapesAt(i),share:s}))}exposureTimes(e=this.currentTime){const t=(this.currentSighting.exposure??0)*1e3;if(t<T.SHORTEST_VISIBLE_EXPOSURE_MS)return[e];const s=Math.min(T.MAX_EXPOSURE_STEPS,Math.max(2,Math.round(t/T.SHORTEST_VISIBLE_EXPOSURE_MS))),i=Math.ceil(this.travelPxOver(e,t)/T.EXPOSURE_STEP_PX),r=Math.min(T.MAX_TRAVEL_STEPS,Math.max(s,i)),o=[];for(let a=0;a<r;a++)o.push(e+t*a/r);return o}travelPxOver(e,t){const s=this.shapesAt(e),i=this.shapesAt(e+t);let r=0;for(const[o,a]of s){const l=i.get(o);if(!l)continue;const h=l.bounds.x+l.bounds.width/2-(a.bounds.x+a.bounds.width/2),d=l.bounds.y+l.bounds.height/2-(a.bounds.y+a.bounds.height/2);r=Math.max(r,Math.hypot(h,d))}return r}shapeAt(e,t,s=this.occludedSourceIds){for(const i of this.exposureTimes()){const r=this.currentSighting.timeline.hitTest(i,e,t,s);if(r)return r}}shapesAt(e){const t=new Map;for(const s of this.currentSighting.timeline.sourceIds){const i=this.currentSighting.timeline.getInterpolatedShapeAt(e,s);i&&t.set(s,i)}return t}static SHORTEST_VISIBLE_EXPOSURE_MS=20;static MAX_EXPOSURE_STEPS=48;static EXPOSURE_STEP_PX=2;static MAX_TRAVEL_STEPS=320;applyFrameFormat(){const e=v.frameWidthPx(this.currentSighting.instrument,this.canvas.height);if(this.canvas.width===e)return;this.canvas.width=e,this.canvas.parentElement?.style.setProperty("--frame-aspect",`${e} / ${this.canvas.height}`)}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:se;this.canvasRenderer.setStarPoints(v.starPointsOf(this.sighting.instrument)),this.canvasRenderer.setRoll((oe(this.sighting,e)?.rollDeg??0)*Math.PI/180);const i=this.exposureInstants(e);for(const r of i)for(const[o,a]of r.shapes){if(this.occludedSourceIds.has(o))continue;const l=r.share,h=l===1?a:{...a,transparency:1-(1-a.transparency)*l};this.canvasRenderer.paintShape(h)}for(const[r,o]of i[0].shapes)this.occludedSourceIds.has(r)||!s.has(r)||(s.size===1?this.canvasRenderer.paintShape({...o,selected:!0}):this.canvasRenderer.paintMemberOutline(o));if(s.size>1){const r=y.groupBoundsFor([...t].filter(([o])=>s.has(o)).map(([,o])=>o.bounds));this.canvasRenderer.paintGroupHandles(r)}this.seekInput.value=String(e),this.timeStartLabel.textContent=this.formatPosition(e),this.showMilestoneAt(e),this.playbackState==="playing"?(this.soundPreview=void 0,this.sightingAudio.setSound(Ce(this.currentSighting,e))):this.soundPreview?this.sightingAudio.setSound(this.soundPreview):this.sightingAudio.silence(),this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{time:e}}))}createPlayer(){const e=new Le(this.currentSighting.timeline,(t,s)=>this.onFrame(t,s));return e.loop=this.loopEnabled,e.onEnded=()=>{this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("ended",{bubbles:!0,composed:!0}))},e}togglePlayPause(){this.player.seekableDuration<=0||(this.player.playbackState==="playing"?(this.player.pause(),this.soundPreview=void 0,this.sightingAudio.silence(),this.refresh()):(this.sightingAudio.resume(),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}restorePlayback(){const e=this.playbackBeforeClick;e&&(this.currentTime=e.time,e.state==="playing"?this.play():this.pause())}get canUseNativeFullscreen(){return typeof this.fullscreenTarget.requestFullscreen=="function"&&document.fullscreenEnabled}toggleFullscreen(){if(!this.canUseNativeFullscreen){this.simulatedFullscreen?this.exitSimulatedFullscreen():this.enterSimulatedFullscreen();return}document.fullscreenElement?document.exitFullscreen():this.fullscreenTarget.requestFullscreen().catch(e=>{console.error("<rr0-ufo>: requestFullscreen() failed —",e)})}enterSimulatedFullscreen(){const e=this.fullscreenTarget;this.styleBeforeSimulatedFullscreen=e.getAttribute("style")??"",this.bodyOverflowBeforeSimulatedFullscreen=document.body.style.overflow;const t=e.style;t.setProperty("position","fixed"),t.setProperty("inset","0"),t.setProperty("margin","0"),t.setProperty("max-width","none"),t.setProperty("max-height","none"),t.setProperty("aspect-ratio","auto"),t.setProperty("z-index","2147483647"),t.setProperty("background","#000"),t.setProperty("width","100vw"),t.setProperty("width","100dvw"),t.setProperty("height","100vh"),t.setProperty("height","100dvh"),document.body.style.overflow="hidden",this.simulatedFullscreen=!0,document.addEventListener("keydown",this.handleSimulatedFullscreenKey),this.updateFullscreenButton()}exitSimulatedFullscreen(){if(!this.simulatedFullscreen)return;const e=this.fullscreenTarget;this.styleBeforeSimulatedFullscreen?e.setAttribute("style",this.styleBeforeSimulatedFullscreen):e.removeAttribute("style"),document.body.style.overflow=this.bodyOverflowBeforeSimulatedFullscreen??"",this.simulatedFullscreen=!1,document.removeEventListener("keydown",this.handleSimulatedFullscreenKey),this.updateFullscreenButton()}updateFullscreenButton(){const e=this.simulatedFullscreen||document.fullscreenElement===this.fullscreenTarget;this.fullscreenButton.title=e?this.messages.exitFullscreen:this.messages.fullscreen,this.fullscreenButton.setAttribute("aria-label",this.fullscreenButton.title)}get said(){return this.saidTexts??=new S(Q.preferencesFor(this))}saidTexts;async loadLocaleMessages(){const e=Ze(Q.preferencesFor(this),je);e!=="en"&&this.applyMessages(await et(e))}updateTimeLabelTitles(){const e=this.showClockTime?this.messages.switchToElapsed:this.messages.switchToClockTime,t=this.canSwitchTimeDisplay?` — ${e}`:"";this.timeStartLabel.title=this.messages.currentPosition+t,this.timeEndLabel.title=this.messages.duration+t;for(const s of[this.timeStartLabel,this.timeEndLabel])s.classList.toggle("switchable",this.canSwitchTimeDisplay),this.canSwitchTimeDisplay?(s.setAttribute("role","button"),s.setAttribute("tabindex","0")):(s.removeAttribute("role"),s.removeAttribute("tabindex"))}applyMessages(e){this.messages=e,this.updateTimeLabelTitles(),this.loopButton.title=e.autoReplay,this.loopButton.setAttribute("aria-label",e.autoReplay),this.updatePlayPauseButton(),this.updateFullscreenButton()}updateTimeLabels(){const e=this.currentSighting.event,t=Ie(e);this.realDurationMs=t!==void 0&&t>0?t:void 0,this.realStartMs=e.time?U(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=s>0?0:this.realDurationMs??0,this.timeEndLabel.textContent=this.formatEndOfTimeline(),this.timeStartLabel.textContent=this.formatPosition(this.player.time),this.updateTimeLabelTitles()}formatPosition(e){if(this.realDurationMs===void 0)return _(e);const t=this.currentSighting.timeline.duration,s=t>0&&e<=t?e/t*this.realDurationMs:e;return this.canShowClockTime?ne(ie(this.realStartMs+s)):_(s)}formatEndOfTimeline(){return this.realDurationMs===void 0?_(this.currentSighting.timeline.duration):this.canShowClockTime?ne(ie(this.realStartMs+this.realDurationMs)):_(this.realDurationMs)}}function ie(n){const e=new Date(n);return{hour:e.getUTCHours(),minute:e.getUTCMinutes(),second:e.getUTCSeconds()}}function ne(n){if(n.hour===void 0)return"0:00";const e=t=>String(t).padStart(2,"0");return n.second?`${e(n.hour)}:${e(n.minute??0)}:${e(n.second)}`:`${e(n.hour)}:${e(n.minute??0)}`}function _(n){const e=Math.round(n/1e3),t=Math.floor(e/60),s=e%60;return`${t}:${String(s).padStart(2,"0")}`}const re="rr0-ufo";function st(){customElements.get(re)||customElements.define(re,T)}st();
364
+ `;class T extends Error{constructor(e,t,s){super(`${e}: ${t}${s===void 0?"":` (HTTP ${s})`}`),this.kind=e,this.url=t,this.status=s,this.name="SightingFetchError"}}class Ie{static async json(e){const t=new URL(e,location.href);if(location.protocol==="https:"&&t.protocol==="http:")throw new T("mixed-content",e);let s;try{s=await fetch(e)}catch{throw await this.diagnose(e,t)}if(!s.ok)throw new T("status",e,s.status);try{return await s.json()}catch{throw new T("malformed",e)}}static async diagnose(e,t){if(t.origin===location.origin)return new T("unreachable",e);try{return await fetch(e,{mode:"no-cors"}),new T("cors",e)}catch{return new T("unreachable",e)}}}const L=[{id:"eye",name:{en:"Naked eye",fr:"Œil nu"},flare:0,projection:"equidistant"},{id:"rectilinear-lens",name:{en:"Camera, unknown device",fr:"Appareil, modèle inconnu"},projection:"rectilinear",fNumber:8,fNumberRange:{min:2,max:22},exposureSeconds:1/250,exposureRangeSeconds:{min:1/1e3,max:8},apertureBlades:6},{id:"instamatic-126",name:{en:"Instamatic, 126 film",fr:"Instamatic, film 126"},projection:"rectilinear",frame:{widthMm:28,heightMm:28,focalLengthMm:43},fNumber:11,exposureSeconds:1/90,years:{from:1963,to:1988},apertureBlades:5,detailUm:20},{id:"slr-35mm-50",name:{en:"35 mm SLR, 50 mm lens",fr:"Reflex 35 mm, objectif 50 mm"},projection:"rectilinear",frame:{widthMm:36,heightMm:24,focalLengthMm:50},fNumber:8,fNumberRange:{min:2,max:16},exposureSeconds:1/250,exposureRangeSeconds:{min:1/1e3,max:3600},years:{from:1959},apertureBlades:6,detailUm:20},{id:"slr-35mm-zoom",name:{en:"35 mm SLR, 70-210 mm zoom",fr:"Reflex 35 mm, zoom 70-210 mm"},projection:"rectilinear",frame:{widthMm:36,heightMm:24,focalLengthMm:135,focalRangeMm:{minMm:70,maxMm:210}},fNumber:8,fNumberRange:{min:4,max:22},exposureSeconds:1/250,exposureRangeSeconds:{min:1/1e3,max:3600},apertureBlades:8,years:{from:1975},detailUm:20},{id:"phone-landscape",name:{en:"Phone, held sideways",fr:"Téléphone, tenu couché"},projection:"rectilinear",frame:{widthMm:7.6,heightMm:5.7,focalLengthMm:5.7},fNumber:1.8,exposureSeconds:1/120,exposureRangeSeconds:{min:1/8e3,max:10},years:{from:2007},apertureBlades:void 0,detailUm:1.4},{id:"phone-portrait",name:{en:"Phone, held upright",fr:"Téléphone, tenu debout"},projection:"rectilinear",frame:{widthMm:5.7,heightMm:7.6,focalLengthMm:5.7},fNumber:1.8,exposureSeconds:1/120,exposureRangeSeconds:{min:1/8e3,max:10},years:{from:2007},apertureBlades:void 0,detailUm:1.4}];class b{static get default(){return L[0]}static byId(e){return L.find(t=>t.id===e)??this.default}static UNAIDED_FIELD_DEG=60;static UNAIDED_ASPECT=16/9;static fieldOfViewDeg(e){const t=e.frame;return t?2*Math.atan(t.heightMm/(2*t.focalLengthMm))*180/Math.PI:b.UNAIDED_FIELD_DEG}static aspectOf(e){const t=e.frame;return t?t.widthMm/t.heightMm:b.UNAIDED_ASPECT}static frameWidthPx(e,t){return Math.round(t*b.aspectOf(e))}static availableAt(e){return e===void 0?L:L.filter(t=>!t.years||e>=t.years.from&&(t.years.to===void 0||e<=t.years.to))}static fieldOfViewDegAt(e,t){const s=e.frame;if(!(!s||t<=0))return 2*Math.atan(s.heightMm/(2*t))*180/Math.PI}static focalLengthMmFor(e,t){const s=e.frame;if(!(!s||t<=0||t>=180))return s.heightMm/(2*Math.tan(t*Math.PI/360))}static bladesShowing(e,t){const s=t??e.fNumber;if(s===void 0||e.apertureBlades===void 0)return 0;const i=e.fNumberRange;return!i||i.max<=i.min?1:Math.max(0,Math.min(1,(s-i.min)/(i.max-i.min)))}static LENS_FLARE_ARTIFACTS=1;static flareArtifactsOf(e){return e.flare??b.LENS_FLARE_ARTIFACTS}static starPointsOf(e){const t=e.apertureBlades;return t===void 0||t<3?0:t%2===0?t:t*2}}class k{static MOON_ANGULAR_WIDTH_DEG=.5237;static CANVAS_WIDTH_PX=640;static CANVAS_HEIGHT_PX=360;static angularWidthDeg(e){return 2*Math.atan(e.sizeM/(2*e.distanceM))*180/Math.PI}static inMoons(e){return e/k.MOON_ANGULAR_WIDTH_DEG}static lerpAngular(e,t,s){if(!(!e||!t))return{widthDeg:e.widthDeg+(t.widthDeg-e.widthDeg)*s,heightDeg:e.heightDeg+(t.heightDeg-e.heightDeg)*s}}static sizeMAt(e,t){return 2*e*Math.tan(t*Math.PI/360)}static distanceMAt(e,t){return e/(2*Math.tan(t*Math.PI/360))}}function Ae(n,e,t){const{bounds:s}=n;return e>=s.x&&e<=s.x+s.width&&t>=s.y&&t<=s.y+s.height}function y(n,e,t){return n+(e-n)*t}function te(n){const e=/^#([0-9a-f]{6})$/i.exec(n);if(!e)return;const t=parseInt(e[1],16);return[t>>16&255,t>>8&255,t&255]}function De(n){return`#${n.map(e=>Math.round(e).toString(16).padStart(2,"0")).join("")}`}function Ce(n,e,t){const s=te(n),i=te(e);return!s||!i?t<1?n:e:De([y(s[0],i[0],t),y(s[1],i[1],t),y(s[2],i[2],t)])}function Be(n,e,t){const s={x:y(n.bounds.x,e.bounds.x,t),y:y(n.bounds.y,e.bounds.y,t),width:y(n.bounds.width,e.bounds.width,t),height:y(n.bounds.height,e.bounds.height,t)},i=y(n.angle,e.angle,t),r=y(n.transparency,e.transparency,t),a=y(n.haloScale,e.haloScale,t),o=y(n.blur??0,e.blur??0,t),l=y(n.brightness??0,e.brightness??0,t),h=Ce(n.color,e.color,t),d=k.lerpAngular(n.angular,e.angular,t);return n.kind==="polygon"&&e.kind==="polygon"&&n.points.length===e.points.length?{...n,bounds:s,angle:i,transparency:r,haloScale:a,blur:o,brightness:l,color:h,angular:d,behindCloud:t<1?n.behindCloud:e.behindCloud,points:n.points.map((c,u)=>({x:y(c.x,e.points[u].x,t),y:y(c.y,e.points[u].y,t)}))}:{...t<1?n:e,bounds:s,angle:i,transparency:r,haloScale:a,blur:o,brightness:l,color:h,angular:d}}class q{keyframes=[];order=[];groups=[];addKeyframe(e,t){const s=this.findInsertIndex(e);if(this.keyframes[s]?.t===e){const i=new Set(t.map(r=>r.sourceId));this.keyframes[s]={t:e,shapes:[...this.keyframes[s].shapes.filter(r=>!i.has(r.sourceId)),...t]}}else this.keyframes.splice(s,0,{t:e,shapes:[...t]});for(const i of t)this.order.includes(i.sourceId)||this.order.push(i.sourceId)}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}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 i=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(i)return i.shape}}getInterpolatedShapeAt(e,t){const s=this.findShapeAtOrBefore(e,t);if(s?.t===e)return s.shape;const i=this.findShapeAtOrAfter(e,t);return s?i?Be(s.shape,i.shape,(e-s.t)/(i.t-s.t)):s.shape:i?.shape}findShapeAtOrBefore(e,t){let s=this.findInsertIndex(e);for(this.keyframes[s]?.t!==e&&(s-=1);s>=0;s--){const i=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(i)return{t:this.keyframes[s].t,shape:i.shape}}}findShapeAtOrAfter(e,t){for(let s=this.findInsertIndex(e);s<this.keyframes.length;s++){const i=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(i)return{t:this.keyframes[s].t,shape:i.shape}}}hitTest(e,t,s,i){const r=this.sourceIds;for(let a=r.length-1;a>=0;a--){if(i?.has(r[a]))continue;const o=this.getInterpolatedShapeAt(e,r[a]);if(o&&Ae(o,t,s))return{sourceId:r[a],shape:o}}}removeSource(e){for(let t=this.keyframes.length-1;t>=0;t--){const s=this.keyframes[t].shapes.filter(i=>i.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(i=>i.includes(e));if(t===-1)return;const s=this.groups[t].filter(i=>i!==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 q;for(const s of e.keyframes)t.addKeyframe(s.t,s.shapes);if(e.order){const s=t.order.filter(i=>!e.order.includes(i));t.order=[...e.order,...s]}return e.groups&&(t.groups=e.groups.map(s=>s.filter(i=>t.order.includes(i))).filter(s=>s.length>=2)),t}}function Re(n,e,t){return Math.max(e,Math.min(t,n))}function _e(n,e,t){const s=((e-n)%360+540)%360-180;return((n+s*t)%360+360)%360}function P(n,e,t){return n+(e-n)*t}function Le(n,e,t){return{lat:n.lat===void 0||e.lat===void 0?void 0:P(n.lat,e.lat,t),lng:n.lng===void 0||e.lng===void 0?void 0:P(n.lng,e.lng,t),elevationM:P(n.elevationM,e.elevationM,t),headingDeg:n.headingDeg===void 0||e.headingDeg===void 0?void 0:_e(n.headingDeg,e.headingDeg,t),pitchDeg:P(n.pitchDeg,e.pitchDeg,t),rollDeg:P(n.rollDeg??0,e.rollDeg??0,t),fovDeg:P(n.fovDeg,e.fovDeg,t),fNumber:n.fNumber,focusDistanceM:n.focusDistanceM}}class D{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 i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}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 i=this.keyframes[t]?.t===e?void 0:this.keyframes[t];return s?i?Le(s.pose,i.pose,Re((e-s.t)/(i.t-s.t),0,1)):s.pose:i?.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 D;for(const s of e.keyframes)t.addKeyframe(s.t,s.pose);return t}}function S(n,e,t){return n+(e-n)*t}function Oe(n,e,t){const s=((e-n)%360+540)%360-180;return((n+s*t)%360+360)%360}function Fe(n,e,t){return Math.max(e,Math.min(t,n))}function Ne(n,e,t){return{cloudCover:S(n.cloudCover,e.cloudCover,t),cloudDarkness:S(n.cloudDarkness,e.cloudDarkness,t),highCloudCover:n.highCloudCover===void 0||e.highCloudCover===void 0?void 0:S(n.highCloudCover,e.highCloudCover,t),iceCrystalAlignment:n.iceCrystalAlignment===void 0||e.iceCrystalAlignment===void 0?void 0:S(n.iceCrystalAlignment,e.iceCrystalAlignment,t),lowerCloudCover:n.lowerCloudCover===void 0||e.lowerCloudCover===void 0?void 0:S(n.lowerCloudCover,e.lowerCloudCover,t),cloudBaseM:n.cloudBaseM===void 0||e.cloudBaseM===void 0?void 0:S(n.cloudBaseM,e.cloudBaseM,t),precipitationType:t<1?n.precipitationType:e.precipitationType,precipitationIntensity:S(n.precipitationIntensity,e.precipitationIntensity,t),windDirectionDeg:Oe(n.windDirectionDeg,e.windDirectionDeg,t),windSpeed:S(n.windSpeed,e.windSpeed,t),storm:t<1?n.storm:e.storm}}class C{keyframes=[];addKeyframe(e,t){const s=this.findInsertIndex(e);this.keyframes[s]?.t===e?this.keyframes[s]={t:e,weather:t}:this.keyframes.splice(s,0,{t:e,weather: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 i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}return t}getLatestWeatherAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].weather:void 0}getInterpolatedWeatherAt(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.weather;const i=this.keyframes[t]?.t===e?void 0:this.keyframes[t];return s?i?Ne(s.weather,i.weather,Fe((e-s.t)/(i.t-s.t),0,1)):s.weather:i?.weather}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 C;for(const s of e.keyframes)t.addKeyframe(s.t,s.weather);return t}}class B{keyframes=[];addKeyframe(e,t){const s=this.findInsertIndex(e);this.keyframes[s]?.t===e?this.keyframes[s]={t:e,sound:t}:this.keyframes.splice(s,0,{t:e,sound:t})}clear(){this.keyframes.length=0}removeKeyframeAt(e){const t=this.findInsertIndex(e);this.keyframes[t]?.t===e&&this.keyframes.splice(t,1)}getLatestSoundAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].sound:void 0}getInterpolatedSoundAt(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.sound;const i=this.keyframes[t]?.t===e?void 0:this.keyframes[t];if(!s)return i?.sound;if(!i)return s.sound;const r=(e-s.t)/(i.t-s.t);return this.lerp(s.sound,i.sound,Math.max(0,Math.min(1,r)))}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.sound);return t}lerp(e,t,s){return{kind:s<1?e.kind:t.kind,volume:e.volume+(t.volume-e.volume)*s,pitchHz:e.pitchHz+(t.pitchHz-e.pitchHz)*s,src:s<1?e.src:t.src}}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}return t}}const We=100,ze={kind:"none",volume:0,pitchHz:We};function Q(n){if(n.year!==void 0)return Date.UTC(n.year,(n.month??1)-1,n.day??1,n.hour??0,n.minute??0,n.second??0)}function se(n){return(((n.day??0)*24+(n.hour??0))*60+(n.minute??0))*6e4+(n.second??0)*1e3}const He=["year","month","day","hour","minute"],Ge=["day","hour","minute"];function ie(n,e){return e.filter(t=>n[t]!==void 0).join(",")}function Ue(n){if(n.durationSeconds!==void 0)return n.durationSeconds*1e3;if(!n.time||!n.endTime)return;const e=n.time.year!==void 0&&n.endTime.year!==void 0,t=e?He:Ge,s=ie(n.time,t);if(!(s===""||s!==ie(n.endTime,t)))return e?Q(n.endTime)-Q(n.time):se(n.endTime)-se(n.time)}class K{constructor(e,t,s,i,r,a,o,l,h=[],d,c,u,f=[]){this.event=e,this.timeline=t,this.witnessTrack=s,this.weatherTrack=i,this.soundTrack=r,this.witness=a,this.caseId=o,this.weather=l,this.decor=h,this.weatherSource=d,this.instrumentId=c,this.exposureSeconds=u,this.milestones=f}get exposure(){return this.exposureSeconds??this.instrument.exposureSeconds}get instrument(){return b.byId(this.instrumentId)}static create(e,t,s){return new K({eventType:"sighting",time:e,place:t},new q,new D,new C,new B,s)}}const $e=0,Xe=0;function G(n,e){const t=n.witnessTrack.getInterpolatedPoseAt(e);if(t)return t;const s=n.event.place?.[0];if(s)return{lat:s.lat,lng:s.lng,elevationM:$e,headingDeg:void 0,pitchDeg:Xe,fovDeg:b.fieldOfViewDeg(n.instrument)}}function Ve(n,e){return n.soundTrack.getInterpolatedSoundAt(e)??ze}class qe{constructor(e,t){this.timeline=e,this.onFrame=t}rafId=null;state="stopped";currentT=0;lastWallTime=0;playbackRate=1;loop=!1;onEnded;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),this.onEnded?.();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 i=this.timeline.getInterpolatedShapeAt(e,s);i&&t.set(s,i)}this.onFrame(e,t)}}const ve=["nw","n","ne","e","se","s","sw","w"],Ke=24,O=8,Ze=3,Ye=8;function F(n,e,t){const s=n.x-e.x,i=n.y-e.y,r=Math.cos(t),a=Math.sin(t);return{x:e.x+s*r-i*a,y:e.y+s*a+i*r}}function A(n){return{x:n.x+n.width/2,y:n.y+n.height/2}}const Je={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 w{static handlePointsFor(e){const{x:t,y:s,width:i,height:r}=e.bounds,a={nw:{x:t,y:s},n:{x:t+i/2,y:s},ne:{x:t+i,y:s},e:{x:t+i,y:s+r/2},se:{x:t+i,y:s+r},s:{x:t+i/2,y:s+r},sw:{x:t,y:s+r},w:{x:t,y:s+r/2},rotate:{x:t+i/2,y:s-Ke}},o=A(e.bounds),l={};for(const h of Object.keys(a))l[h]=F(a[h],o,e.angle);return l}static hitTestHandle(e,t,s=8,i=[...ve,"rotate"]){const r=w.handlePointsFor(e);for(const a of i)if(Math.hypot(t.x-r[a].x,t.y-r[a].y)<=s)return a}static HANDLE_DIRECTION_DEG={e:0,se:45,s:90,sw:135,w:180,nw:225,n:270,ne:315};static resizeAxisFor(e,t){const i=((w.HANDLE_DIRECTION_DEG[e]+t*180/Math.PI)%180+180)%180/45,r=["ew","nwse","ns","nesw"];return r[Math.round(i)%r.length]}static resizeBounds(e,t,s,i){const r=A(e),a=F(i,r,-t),{x:o,y:l,width:h,height:d}=e,c=Je[s];let u=o,f=l,m=o+h,p=l+d;return c.left&&(u=Math.min(a.x,m-O)),c.right&&(m=Math.max(a.x,u+O)),c.top&&(f=Math.min(a.y,p-O)),c.bottom&&(p=Math.max(a.y,f+O)),{x:u,y:f,width:m-u,height:p-f}}static resizeShape(e,t,s){if(t==="rotate")throw new Error("resizeShape does not accept the rotate handle");const{width:i,height:r}=e.bounds,a=w.resizeBounds(e.bounds,e.angle,t,s);if(e.kind==="oval")return{...e,bounds:a};const o=i===0?1:a.width/i,l=r===0?1:a.height/r;return{...e,bounds:a,points:e.points.map(h=>({x:h.x*o,y:h.y*l}))}}static rotateShape(e,t){const s=A(e.bounds),i=Math.atan2(t.y-s.y,t.x-s.x)+Math.PI/2;return{...e,angle:i}}static groupBoundsFor(e){const t=Math.min(...e.map(a=>a.x)),s=Math.min(...e.map(a=>a.y)),i=Math.max(...e.map(a=>a.x+a.width)),r=Math.max(...e.map(a=>a.y+a.height));return{x:t,y:s,width:i-t,height:r-s}}static vertexPointsFor(e){const t=A(e.bounds);return e.points.map(s=>F({x:e.bounds.x+s.x,y:e.bounds.y+s.y},t,e.angle))}static hitTestVertex(e,t,s=Ye){const i=w.vertexPointsFor(e);let r,a=s;return i.forEach((o,l)=>{const h=Math.hypot(t.x-o.x,t.y-o.y);h<=a&&(a=h,r=l)}),r}static toLocalPoint(e,t){const s=A(e.bounds),i=F(t,s,-e.angle);return{x:i.x-e.bounds.x,y:i.y-e.bounds.y}}static moveVertex(e,t,s){const i=e.points.map((c,u)=>u===t?w.toLocalPoint(e,s):c),r=Math.min(...i.map(c=>c.x)),a=Math.min(...i.map(c=>c.y)),o=Math.max(...i.map(c=>c.x)),l=Math.max(...i.map(c=>c.y)),h={x:e.bounds.x+r,y:e.bounds.y+a,width:o-r,height:l-a},d=i.map(c=>({x:c.x-r,y:c.y-a}));return{...e,bounds:h,points:d}}static insertVertexNear(e,t){const s=w.toLocalPoint(e,t),{points:i}=e;let r=i.length-1,a=1/0;for(let l=0;l<i.length;l++){const h=i[l],d=i[(l+1)%i.length],c=w.distanceToSegment(s,h,d);c<a&&(a=c,r=l)}const o=[...i.slice(0,r+1),s,...i.slice(r+1)];return{...e,points:o}}static distanceToSegment(e,t,s){const i=s.x-t.x,r=s.y-t.y,a=i*i+r*r,o=a===0?0:Math.max(0,Math.min(1,((e.x-t.x)*i+(e.y-t.y)*r)/a)),l=t.x+o*i,h=t.y+o*r;return Math.hypot(e.x-l,e.y-h)}static deleteVertex(e,t){return e.points.length<=Ze?e:{...e,points:e.points.filter((s,i)=>i!==t)}}}const ee=6,N=ee/2,je=20,Qe=24,et=9,ne=6,tt=7,st=6,re=4;class it{constructor(e){this.ctx=e}starPoints=0;roll=0;clear(e,t){this.ctx.clearRect(0,0,e,t)}paintShape(e){this.ctx.save(),this.ctx.globalAlpha=1-e.transparency;const t=e.blur??0;t>0&&(this.ctx.filter=`blur(${(Qe*t).toFixed(2)}px)`);const s=e.brightness??0;s>0&&this.paintDazzle(e,s),e.haloScale>0&&this.paintHalo(e),this.paintBase(e),this.ctx.restore(),e.selected&&this.paintSelectionHandles(e)}setStarPoints(e){this.starPoints=Math.max(0,Math.floor(e))}setRoll(e){this.roll=e}paintDazzle(e,t){const{x:s,y:i,width:r,height:a}=e.bounds,o=s+r/2,l=i+a/2,h=Math.max(r,a)/2,d=Math.max(h*(1+et*t),st*t);if(d<=0)return;this.ctx.save(),this.ctx.globalCompositeOperation="lighter";const c=this.ctx.createRadialGradient(o,l,0,o,l,d);for(let u=0;u<=ne;u++){const f=u/ne,m=1/(1+24*f*f);this.ctx.globalAlpha=1,c.addColorStop(f,this.withAlpha(e.color,m*t*.55))}if(this.ctx.fillStyle=c,this.ctx.beginPath(),this.ctx.arc(o,l,d,0,2*Math.PI),this.ctx.fill(),this.starPoints>0){const u=h*(1+tt*t);this.ctx.lineWidth=Math.max(1,h*.12),this.ctx.lineCap="round";for(let f=0;f<this.starPoints;f++){const m=f/this.starPoints*Math.PI*2+this.roll,p=o+Math.cos(m)*u,g=l+Math.sin(m)*u,v=this.ctx.createLinearGradient(o,l,p,g);v.addColorStop(0,this.withAlpha(e.color,t*.55)),v.addColorStop(.35,this.withAlpha(e.color,t*.22)),v.addColorStop(1,this.withAlpha(e.color,0)),this.ctx.strokeStyle=v,this.ctx.beginPath(),this.ctx.moveTo(o,l),this.ctx.lineTo(p,g),this.ctx.stroke()}}this.ctx.restore()}dazzledFill(e,t){if(t<=0)return e.color;const s=/^#([0-9a-f]{6})$/i.exec(e.color.trim());if(!s)return e.color;const i=Number.parseInt(s[1],16),r=a=>Math.round(a+(255-a)*t);return`rgb(${r(i>>16&255)}, ${r(i>>8&255)}, ${r(i&255)})`}withAlpha(e,t){const s=Math.max(0,Math.min(1,t)),i=/^#([0-9a-f]{6})$/i.exec(e.trim());if(!i)return e;const r=Number.parseInt(i[1],16);return`rgba(${r>>16&255}, ${r>>8&255}, ${r&255}, ${s.toFixed(3)})`}paintBase(e){if(this.ctx.fillStyle=this.dazzledFill(e,e.brightness??0),this.ctx.beginPath(),e.kind==="oval"){const{x:t,y:s,width:i,height:r}=e.bounds,a=i/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:i,height:r}=e.bounds;this.ctx.save(),this.ctx.translate(t+i/2,s+r/2),this.ctx.rotate(e.angle),this.ctx.translate(-i/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=je*e.haloScale,this.paintBase(e),this.ctx.restore()}paintSelectionHandles(e){this.paintHandleFrame(w.handlePointsFor(e),{includeRotate:!0}),e.kind==="polygon"&&this.paintVertexHandles(e)}paintVertexHandles(e){this.ctx.fillStyle="#39f";for(const t of w.vertexPointsFor(e))this.ctx.beginPath(),this.ctx.ellipse(t.x,t.y,re,re,0,0,2*Math.PI),this.ctx.fill()}paintMemberOutline(e){this.paintOutline(w.handlePointsFor(e))}paintGroupHandles(e){this.paintHandleFrame(w.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 ve){const i=e[s];this.ctx.fillRect(i.x-N,i.y-N,ee,ee)}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,N+1,N+1,0,0,2*Math.PI),this.ctx.fill())}}const ae=.05,oe=.08,he=2,le=3,nt=14,rt=.02;class at{context;voice;voiceToken=0;buffers=new Map;noiseBuffer;crackleBuffer;requested;resume(){if(!this.context){if(typeof AudioContext>"u")return;try{this.context=new AudioContext}catch(e){console.warn("SightingAudio: Web Audio unavailable, the sighting stays silent:",e);return}}this.context.state==="suspended"&&this.context.resume()}setSound(e){if(this.requested=e,!this.context)return;const t=this.keyFor(e);if(!t){this.silence();return}if(this.voice?.key!==t){this.stopVoice();const s=++this.voiceToken;t.startsWith("src:")?this.buildRecordedVoice(e.src,t,s):this.voice=this.buildSynthesizedVoice(e,t)}this.applyTo(this.voice,e)}silence(){this.voiceToken++,this.stopVoice()}dispose(){this.silence(),this.context?.close(),this.context=void 0,this.buffers.clear(),this.noiseBuffer=void 0,this.crackleBuffer=void 0}keyFor(e){if(!(e.volume<=0))return e.src?`src:${e.src}`:e.kind==="none"?void 0:e.kind}applyTo(e,t){const s=this.context;if(!e||!s)return;const i=s.currentTime;e.volumeParam.setTargetAtTime(Math.max(0,Math.min(1,t.volume))*e.gainScale,i,ae);for(const{param:r,ratio:a}of e.pitch)r.setTargetAtTime(Math.min(t.pitchHz*a,s.sampleRate/2-1),i,ae)}buildSynthesizedVoice(e,t){const s=this.context;if(s)switch(e.kind){case"hum":return this.buildHum(s,t);case"whistle":return this.buildWhistle(s,t);case"rumble":return this.buildRumble(s,t);case"crackle":return this.buildCrackle(s,t);default:return}}buildHum(e,t){const s=e.createOscillator();s.type="sawtooth";const i=e.createOscillator();i.type="sawtooth";const r=e.createBiquadFilter();r.type="lowpass",r.Q.value=1;const a=e.createGain();return a.gain.value=0,s.connect(r),i.connect(r),r.connect(a).connect(e.destination),s.start(),i.start(),{key:t,nodes:[r,a],sources:[s,i],volumeParam:a.gain,gainScale:.16,pitch:[{param:s.frequency,ratio:1},{param:i.frequency,ratio:1.006},{param:r.frequency,ratio:6}]}}buildWhistle(e,t){const s=e.createOscillator();s.type="sine";const i=e.createOscillator();i.type="sine",i.frequency.value=5;const r=e.createGain(),a=e.createGain();return a.gain.value=0,i.connect(r).connect(s.frequency),s.connect(a).connect(e.destination),s.start(),i.start(),{key:t,nodes:[r,a],sources:[s,i],volumeParam:a.gain,gainScale:.22,pitch:[{param:s.frequency,ratio:1},{param:r.gain,ratio:.012}]}}buildRumble(e,t){const s=e.createBufferSource();s.buffer=this.brownNoise(e),s.loop=!0;const i=e.createBiquadFilter();i.type="lowpass",i.Q.value=.8;const r=e.createGain();return r.gain.value=0,s.connect(i).connect(r).connect(e.destination),s.start(),{key:t,nodes:[i,r],sources:[s],volumeParam:r.gain,gainScale:1.4,pitch:[{param:i.frequency,ratio:1}]}}buildCrackle(e,t){const s=e.createBufferSource();s.buffer=this.whiteNoise(e),s.loop=!0;const i=e.createBiquadFilter();i.type="bandpass",i.Q.value=1.2;const r=e.createGain();r.gain.value=0;const a=e.createBufferSource();a.buffer=this.crackleEnvelope(e),a.loop=!0;const o=e.createGain();return o.gain.value=0,a.connect(o).connect(r.gain),s.connect(i).connect(r).connect(e.destination),s.start(),a.start(),{key:t,nodes:[i,r,o],sources:[s,a],volumeParam:o.gain,gainScale:.9,pitch:[{param:i.frequency,ratio:4}]}}async buildRecordedVoice(e,t,s){const i=this.context;if(!i)return;let r;try{r=await this.getBuffer(e)}catch(l){console.warn("SightingAudio: the recorded sound failed to load, staying silent:",l);return}if(s!==this.voiceToken||!this.context)return;const a=i.createBufferSource();a.buffer=r,a.loop=!0;const o=i.createGain();o.gain.value=0,a.connect(o).connect(i.destination),a.start(),this.voice={key:t,nodes:[o],sources:[a],volumeParam:o.gain,gainScale:1,pitch:[]},this.requested&&this.applyTo(this.voice,this.requested)}async getBuffer(e){const t=this.context;if(!t)throw new Error("SightingAudio: AudioContext not ready — resume() must be called first");let s=this.buffers.get(e);return s||(s=fetch(e).then(i=>{if(!i.ok)throw new Error(`Audio fetch failed (${i.status}): ${e}`);return i.arrayBuffer()}).then(i=>t.decodeAudioData(i)),this.buffers.set(e,s)),s}whiteNoise(e){if(!this.noiseBuffer){const t=e.createBuffer(1,Math.floor(e.sampleRate*he),e.sampleRate),s=t.getChannelData(0);for(let i=0;i<s.length;i++)s[i]=Math.random()*2-1;this.noiseBuffer=t}return this.noiseBuffer}brownNoise(e){const t=e.createBuffer(1,Math.floor(e.sampleRate*he),e.sampleRate),s=t.getChannelData(0);let i=0,r=0;for(let a=0;a<s.length;a++)i=(i+.02*(Math.random()*2-1))/1.02,s[a]=i,r=Math.max(r,Math.abs(i));if(r>0)for(let a=0;a<s.length;a++)s[a]/=r;return t}crackleEnvelope(e){if(!this.crackleBuffer){const t=e.createBuffer(1,Math.floor(e.sampleRate*le),e.sampleRate),s=t.getChannelData(0),i=Math.max(1,Math.floor(rt*e.sampleRate)),r=Math.round(le*nt);for(let a=0;a<r;a++){const o=Math.floor(Math.random()*s.length),l=.4+Math.random()*.6;for(let h=0;h<i&&o+h<s.length;h++)s[o+h]=Math.max(s[o+h],l*(1-h/i))}this.crackleBuffer=t}return this.crackleBuffer}stopVoice(){const e=this.voice,t=this.context;if(this.voice=void 0,!e||!t)return;const s=t.currentTime;e.volumeParam.cancelScheduledValues(s),e.volumeParam.setTargetAtTime(0,s,oe/3);const i=s+oe;for(const r of e.sources){r.onended=()=>{r.disconnect();for(const a of e.nodes)a.disconnect()};try{r.stop(i)}catch{r.disconnect();for(const a of e.nodes)a.disconnect()}}}}function $(n){return[...n].sort((e,t)=>e.t-t.t)}function ce(n,e){let t;for(const s of $(n)){if(s.t>e)break;t=s}return t}const Y=Math.PI/180,W=180/Math.PI;class I{constructor(e,t,s){this.kind=e,this.canvasHeightPx=t,this.fovDeg=s;const i=s*Y/2;this.focalPx=e==="equidistant"?t/2/i:t/2/Math.tan(i)}focalPx;static of(e,t,s){return new I(e.projection,t,s)}degToPx(e){const t=e*Y;return this.kind==="equidistant"?this.focalPx*t:2*this.focalPx*Math.tan(t/2)}pxToDeg(e){return this.kind==="equidistant"?e/this.focalPx*W:2*Math.atan(e/(2*this.focalPx))*W}angleDegToRadiusPx(e){const t=e*Y;return this.kind==="equidistant"?this.focalPx*t:this.focalPx*Math.tan(t)}halfWidthAngleDeg(e){return this.radiusPxToAngleDeg(this.canvasHeightPx*e/2)}radiusPxToAngleDeg(e){return this.kind==="equidistant"?e/this.focalPx*W:Math.atan(e/this.focalPx)*W}ofBounds(e){return{widthDeg:this.pxToDeg(e.width),heightDeg:this.pxToDeg(e.height)}}toBoundsSize(e){return{width:this.degToPx(e.widthDeg),height:this.degToPx(e.heightDeg)}}widthPx(e){return this.degToPx(k.angularWidthDeg(e))}}class xe{static fovOf(e,t){return G(e,t)?.fovDeg??b.fieldOfViewDeg(e.instrument)}static toAngular(e){this.eachKeyframe(e,(t,s)=>({...t,angular:s.ofBounds(t.bounds)}))}static toBounds(e){this.eachKeyframe(e,(t,s)=>{if(!t.angular)return t;const{width:i,height:r}=s.toBoundsSize(t.angular),a={x:t.bounds.x+(t.bounds.width-i)/2,y:t.bounds.y+(t.bounds.height-r)/2,width:i,height:r};if(t.kind!=="polygon")return{...t,bounds:a};const o=t.bounds.width===0?1:i/t.bounds.width,l=t.bounds.height===0?1:r/t.bounds.height;return{...t,bounds:a,points:t.points.map(h=>({x:h.x*o,y:h.y*l}))}})}static reproject(e,t,s){const i=b.frameWidthPx(t,k.CANVAS_HEIGHT_PX)/2,r=b.frameWidthPx(e.instrument,k.CANVAS_HEIGHT_PX)/2,a=k.CANVAS_HEIGHT_PX/2;for(const o of[...e.timeline.allKeyframes]){const l=this.fovOf(e,o.t),h=s?.get(o.t)??l,d=I.of(t,k.CANVAS_HEIGHT_PX,h),c=I.of(e.instrument,k.CANVAS_HEIGHT_PX,l);e.timeline.addKeyframe(o.t,o.shapes.map(u=>{const{bounds:f}=u.shape,m=f.x+f.width/2-i,p=f.y+f.height/2-a,g=Math.hypot(m,p),v=g===0?1:c.angleDegToRadiusPx(d.radiusPxToAngleDeg(g))/g,_={...f,x:r+m*v-f.width/2,y:a+p*v-f.height/2};return{...u,shape:{...u.shape,bounds:_}}}))}this.toBounds(e)}static eachKeyframe(e,t){const{timeline:s}=e;for(const i of[...s.allKeyframes]){const r=this.fovOf(e,i.t),a=I.of(e.instrument,k.CANVAS_HEIGHT_PX,r);s.addKeyframe(i.t,i.shapes.map(o=>({...o,shape:t(o.shape,a)})))}}}function ot(n){return xe.toAngular(n),{version:1,time:n.event.time,endTime:n.event.endTime,durationSeconds:n.event.durationSeconds,utcOffsetHours:n.event.utcOffsetHours,timeZone:n.event.timeZone,place:n.event.place,witness:n.witness,caseId:n.caseId,description:n.event.description,tags:n.event.tags,timeline:n.timeline.toJSON(),witnessTrack:n.witnessTrack.toJSON(),weatherTrack:n.weatherTrack.toJSON(),soundTrack:n.soundTrack.toJSON(),weather:n.weather,decor:n.decor,milestones:n.milestones.length>0?n.milestones:void 0,weatherSource:n.weatherSource,instrument:n.instrumentId,exposureSeconds:n.exposureSeconds}}function ht(n){const e=new K({eventType:"sighting",time:n.time,endTime:n.endTime,durationSeconds:n.durationSeconds,utcOffsetHours:n.utcOffsetHours,timeZone:n.timeZone,place:n.place,description:n.description,tags:n.tags},q.fromJSON(n.timeline),n.witnessTrack?D.fromJSON(n.witnessTrack):new D,n.weatherTrack?C.fromJSON(n.weatherTrack):new C,n.soundTrack?B.fromJSON(n.soundTrack):new B,n.witness,n.caseId,n.weather,n.decor??[],n.weatherSource,n.instrument,n.exposureSeconds??n.witnessTrack?.keyframes.map(t=>t.pose.exposureSeconds).find(t=>t!==void 0),$(n.milestones??[]));return xe.toBounds(e),e}function lt(n,e){for(const t of n){const s=t.toLowerCase().split("-")[0];if(e.includes(s))return s}return"en"}class de{static preferencesFor(e){const t=navigator.languages??[],s=this.declaredFor(e);return s?[s,...t]:t}static declaredFor(e){for(let t=e;t;){const s=t.closest("[lang]")?.getAttribute("lang")?.trim();if(s)return s;const i=t.getRootNode();t=i instanceof ShadowRoot?i.host:void 0}}}class M{constructor(e){this.preferences=e}read(e){if(e===void 0||typeof e=="string")return e===""?void 0:e;for(const s of this.preferences){const i=e[s]??e[M.base(s)];if(i)return i}const t=new Set(this.preferences.map(s=>M.base(s)));for(const[s,i]of Object.entries(e))if(i&&t.has(M.base(s)))return i;return Object.values(e).find(s=>s)??void 0}write(e,t,s){const i=t?.trim()?t.trim():void 0;if(e===void 0||typeof e=="string")return i;const r={...e},a=Object.keys(r).find(o=>o===s)??Object.keys(r).find(o=>M.base(o)===M.base(s))??s;return i===void 0?delete r[a]:r[a]=i,Object.keys(r).length===0?void 0:r}static base(e){return e.toLowerCase().split("-")[0]}}const ct="modulepreload",dt=function(n,e){return new URL(n,e).href},ue={},fe=function(e,t,s){let i=Promise.resolve();if(t&&t.length>0){let a=function(d){return Promise.all(d.map(c=>Promise.resolve(c).then(u=>({status:"fulfilled",value:u}),u=>({status:"rejected",reason:u}))))};const o=document.getElementsByTagName("link"),l=document.querySelector("meta[property=csp-nonce]"),h=l?.nonce||l?.getAttribute("nonce");i=a(t.map(d=>{if(d=dt(d,s),d in ue)return;ue[d]=!0;const c=d.endsWith(".css"),u=c?'[rel="stylesheet"]':"";if(!!s)for(let p=o.length-1;p>=0;p--){const g=o[p];if(g.href===d&&(!c||g.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${d}"]${u}`))return;const m=document.createElement("link");if(m.rel=c?"stylesheet":ct,c||(m.as="script"),m.crossOrigin="",m.href=d,h&&m.setAttribute("nonce",h),document.head.appendChild(m),c)return new Promise((p,g)=>{m.addEventListener("load",p),m.addEventListener("error",()=>g(new Error(`Unable to preload CSS for ${d}`)))})}))}function r(a){const o=new Event("vite:preloadError",{cancelable:!0});if(o.payload=a,window.dispatchEvent(o),!o.defaultPrevented)throw a}return i.then(a=>{for(const o of a||[])o.status==="rejected"&&r(o.reason);return e().catch(r)})},ut=["en","fr"],ft={en:()=>fe(()=>Promise.resolve().then(()=>pt),void 0,import.meta.url).then(n=>n.ufoMessages_en),fr:()=>fe(()=>import("./UfoMessages_fr-C5IGZjVt.js"),[],import.meta.url).then(n=>n.ufoMessages_fr)};function mt(n){return ft[n]()}const ke={play:"Play",pause:"Pause",noDuration:"No observation duration",autoReplay:"Auto-replay",currentPosition:"Current position",duration:"Duration",switchToElapsed:"click to show elapsed time",switchToClockTime:"click to show the time of day",fullscreen:"Fullscreen",exitFullscreen:"Exit fullscreen",showWitnessMap:"Show where the witness was",hideWitnessMap:"Hide where the witness was",mapImageryUnavailable:"Aerial imagery unavailable"},pt=Object.freeze(Object.defineProperty({__proto__:null,ufoMessages_en:ke},Symbol.toStringTag,{value:"Module"})),Se=Math.PI/180,X=111320;function R(n,e,t,s){const i=X*Math.cos(t*Se),r=(n-t)*X;return{x:(e-s)*i,z:-r}}function z(n,e,t,s){const i=X*Math.cos(t*Se),r=-e,a=n;return{lat:t+r/X,lng:s+a/i}}class V{constructor(e){this.points=e}static STATIONARY_M=5;static of(e){const t=[];for(const s of e.witnessTrack.allKeyframes){const{lat:i,lng:r,headingDeg:a}=s.pose;i===void 0||r===void 0||t.push({t:s.t,lat:i,lng:r,headingDeg:a})}if(t.length===0){const s=e.event.place?.[0];if(!s)return;t.push({t:0,lat:s.lat,lng:s.lng})}return new V(t)}get moved(){return this.spanM>V.STATIONARY_M}get spanM(){const e=this.box,t=(e.south+e.north)/2,s=(e.west+e.east)/2;return Math.max(Math.abs(R(t,e.east,t,e.west).x),Math.abs(R(e.north,s,e.south,s).z))}get center(){const e=this.box;return{lat:(e.south+e.north)/2,lng:(e.west+e.east)/2}}boundsAround(e,t){const{lat:s,lng:i}=this.center,r=Math.max(this.spanM*(1+t),e)/2;return{north:z(0,-r,s,i).lat,south:z(0,r,s,i).lat,east:z(r,0,s,i).lng,west:z(-r,0,s,i).lng}}get box(){const e=this.points.map(s=>s.lat),t=this.points.map(s=>s.lng);return{south:Math.min(...e),north:Math.max(...e),west:Math.min(...t),east:Math.max(...t)}}}const Me=Math.PI/180,gt=180/Math.PI,yt=256,wt=19;function bt(n,e){return 156543.03392*Math.cos(n*Me)/2**e}function vt(n,e){return bt(n,e)*yt}function xt(n,e){let t=0;for(;t<wt&&vt(n,t+1)>=e;)t++;return t}function U(n){const e=n*Me;return(1-Math.log(Math.tan(e)+1/Math.cos(e))/Math.PI)/2}function kt(n,e,t){const s=2**t;return{x:(n+180)/360*s,y:U(e)*s}}function St(n,e,t){const s=U(n.north),i=U(n.south);return{x:(e-n.west)/(n.east-n.west),y:(U(t)-s)/(i-s)}}function Mt(n,e,t){const{x:s,y:i}=kt(n,e,t);return{x:Math.floor(s),y:Math.floor(i),z:t}}function Tt(n){const e=2**n.z,t=n.x/e*360-180,s=(n.x+1)/e*360-180,i=me(n.y,e);return{south:me(n.y+1,e),north:i,west:t,east:s}}function me(n,e){const t=n/e;return Math.atan(Math.sinh(Math.PI*(1-2*t)))*gt}function Pt(n,e,t,s){const i=Mt(n,e,t),r=Math.floor(s/2),a=[];for(let h=-r;h<=r;h++)for(let d=-r;d<=r;d++)a.push({x:i.x+d,y:i.y+h,z:t});const o=a.map(Tt),l={south:Math.min(...o.map(h=>h.south)),north:Math.max(...o.map(h=>h.north)),west:Math.min(...o.map(h=>h.west)),east:Math.max(...o.map(h=>h.east))};return{tiles:a,bounds:l}}class E{constructor(e){this.ctx=e}static SCALE_STEPS_M=[5e3,2e3,1e3,500,200,100,50,20,10,5];static MARKER_RADIUS_PX=7;get width(){return this.ctx.canvas.width}get height(){return this.ctx.canvas.height}paint(e){const{ctx:t}=this;t.save(),t.clearRect(0,0,this.width,this.height),this.paintGround(e),this.paintPath(e),e.position?(this.paintCone(e,e.position),this.paintMarkers(e),this.paintWitness(e,e.position)):this.paintMarkers(e),this.paintNorth(),this.paintFooter(e.bounds,e.attribution),t.restore()}toCanvas(e,t,s){const i=St(e,s,t);return{x:i.x*this.width,y:i.y*this.height}}paintGround(e){const{ctx:t}=this;t.fillStyle="#1d2321",t.fillRect(0,0,this.width,this.height);const s=e.imagery;if(!s)return;const i=this.toCanvas(e.bounds,s.bounds.north,s.bounds.west),r=this.toCanvas(e.bounds,s.bounds.south,s.bounds.east);t.drawImage(s.source,i.x,i.y,r.x-i.x,r.y-i.y)}paintPath(e){if(!e.path.moved)return;const{ctx:t}=this,s=e.path.points.map(i=>this.toCanvas(e.bounds,i.lat,i.lng));t.beginPath(),t.moveTo(s[0].x,s[0].y);for(const i of s.slice(1))t.lineTo(i.x,i.y);t.lineJoin="round",t.lineCap="round",t.strokeStyle="rgba(0, 0, 0, 0.65)",t.lineWidth=5,t.stroke(),t.strokeStyle="rgba(255, 255, 255, 0.9)",t.lineWidth=2,t.stroke()}paintCone(e,t){const{headingDeg:s,coneHalfAngleDeg:i}=e;if(s===void 0||i===void 0)return;const{ctx:r}=this,a=this.toCanvas(e.bounds,t.lat,t.lng),o=Math.hypot(this.width,this.height),l=(s-90)*Math.PI/180,h=i*Math.PI/180;r.save(),r.beginPath(),r.moveTo(a.x,a.y),r.arc(a.x,a.y,o,l-h,l+h),r.closePath();const d=r.createRadialGradient(a.x,a.y,0,a.x,a.y,o);d.addColorStop(0,"rgba(255, 224, 130, 0.55)"),d.addColorStop(.35,"rgba(255, 224, 130, 0.22)"),d.addColorStop(1,"rgba(255, 224, 130, 0)"),r.fillStyle=d,r.fill(),r.restore()}paintMarkers(e){const{ctx:t}=this;t.textAlign="center",t.textBaseline="middle",t.font="bold 10px sans-serif";for(const s of[...e.markers].sort((i,r)=>Number(i.current)-Number(r.current))){const i=this.toCanvas(e.bounds,s.lat,s.lng);t.beginPath(),t.arc(i.x,i.y,E.MARKER_RADIUS_PX,0,Math.PI*2),t.fillStyle=s.current?"rgba(255, 224, 130, 0.95)":"rgba(0, 0, 0, 0.55)",t.fill(),t.lineWidth=1.5,t.strokeStyle="rgba(255, 255, 255, 0.9)",t.stroke(),t.fillStyle=s.current?"#1d2321":"#fff",t.fillText(s.label,i.x,i.y)}}paintWitness(e,t){const{ctx:s}=this,i=this.toCanvas(e.bounds,t.lat,t.lng),r=e.markers.some(a=>{if(!a.current)return!1;const o=this.toCanvas(e.bounds,a.lat,a.lng);return Math.hypot(o.x-i.x,o.y-i.y)<=E.MARKER_RADIUS_PX});s.beginPath(),s.arc(i.x,i.y,r?E.MARKER_RADIUS_PX+2.5:4.5,0,Math.PI*2),r||(s.fillStyle="#ff5a3c",s.fill()),s.lineWidth=4,s.strokeStyle="rgba(0, 0, 0, 0.5)",s.stroke(),s.lineWidth=r?2.5:2,s.strokeStyle=r?"#ff5a3c":"#fff",s.stroke()}paintNorth(){const{ctx:e}=this,t=this.width-14,s=16;e.beginPath(),e.moveTo(t,s-9),e.lineTo(t-4.5,s+5),e.lineTo(t,s+2),e.lineTo(t+4.5,s+5),e.closePath(),e.fillStyle="rgba(255, 255, 255, 0.92)",e.strokeStyle="rgba(0, 0, 0, 0.6)",e.lineWidth=1,e.fill(),e.stroke(),e.font="bold 9px sans-serif",e.textAlign="center",e.textBaseline="top",e.strokeText("N",t,s+6),e.fillText("N",t,s+6)}paintFooter(e,t){const{ctx:s}=this;s.font="9px sans-serif",s.textBaseline="bottom";const i=t?this.wrap(t,this.width-20):[];let r=this.height-4;s.textAlign="right";for(const f of i.slice().reverse())this.paintOutlinedText(f,this.width-6,r),r-=11;const a=(e.south+e.north)/2,l=Math.abs(R(a,e.east,a,e.west).x)/this.width,h=E.SCALE_STEPS_M.find(f=>f/l<=this.width/3);if(h===void 0)return;const d=h/l,c=8,u=r-3;s.lineWidth=3,s.strokeStyle="rgba(0, 0, 0, 0.6)",s.beginPath(),s.moveTo(c,u),s.lineTo(c+d,u),s.stroke(),s.lineWidth=1.5,s.strokeStyle="rgba(255, 255, 255, 0.95)",s.stroke(),s.textAlign="left",this.paintOutlinedText(h>=1e3?`${h/1e3} km`:`${h} m`,c,u-3)}paintOutlinedText(e,t,s){const{ctx:i}=this;i.lineWidth=3,i.strokeStyle="rgba(0, 0, 0, 0.75)",i.strokeText(e,t,s),i.fillStyle="#fff",i.fillText(e,t,s)}wrap(e,t){const s=[];let i="";for(const r of e.split(" ")){const a=i?`${i} ${r}`:r;i&&this.ctx.measureText(a).width>t?(s.push(i),i=r):i=a}return i&&s.push(i),s}}const J=256,pe=3;async function Et(n,e,t,s){const i=(n.south+n.north)/2,r=(n.west+n.east)/2,a=Math.abs(R(i,n.east,i,n.west).x),o=Math.abs(R(n.north,r,n.south,r).z),l=xt(i,Math.max(a,o)/2),{tiles:h,bounds:d}=Pt(r,i,l,pe),c=pe*J,u=document.createElement("canvas");u.width=c,u.height=c;const f=u.getContext("2d");if(!f)throw new Error("2D canvas context unavailable");const m=h[0];await Promise.all(h.map(async v=>{const _=t(v),Z=await s(_);if(!Z.ok)throw new Error(`Imagery tile fetch failed (${Z.status}): ${_}`);const Te=await createImageBitmap(await Z.blob());f.drawImage(Te,(v.x-m.x)*J,(v.y-m.y)*J)}));const p=document.createElement("canvas");p.width=e.width,p.height=e.height;const g=p.getContext("2d");if(!g)throw new Error("2D canvas context unavailable");return g.drawImage(u,0,0,c,c,0,0,e.width,e.height),{source:p,width:e.width,height:e.height,bounds:d}}class It{attribution="Imagery © Esri, Maxar, Earthstar Geographics, and the GIS User Community";fetchImpl;tileUrlTemplate;constructor(e={}){this.fetchImpl=e.fetchImpl??fetch.bind(globalThis),this.tileUrlTemplate=e.tileUrlTemplate??"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"}async getImageryTexture(e,t){return Et(e,t,s=>this.tileUrlTemplate.replace("{z}",String(s.z)).replace("{y}",String(s.y)).replace("{x}",String(s.x)),this.fetchImpl)}}function At(){return new It}const ge=new Set,j="show-witness-map";class x extends HTMLElement{static get observedAttributes(){return["src",j]}shadow;stageElement;canvas;canvasRenderer;tooltip;toolbar;playPauseButton;loopButton;static WITNESS_MAP_MIN_SPAN_M=400;static WITNESS_MAP_MARGIN=.6;static WITNESS_MAP_IMAGERY_PX=768;fullscreenButton;cornerButtons;witnessMapButton;witnessMapPanel;witnessMapCanvas;witnessMapRenderer;seekInput;milestoneMarks;milestoneCaption;timeStartLabel;timeEndLabel;witnessPath;witnessMapBounds;witnessMapImagery;witnessMapImageryRequested=!1;witnessMapAttribution;currentSighting=K.create();sightingAudio=new at;soundPreview;player;loopEnabled=!0;playbackBeforeClick;highlightedSourceIds=new Set;occludedSourceIds=ge;enableClickToPlay=!0;fullscreenTarget;messages=ke;realDurationMs;realStartMs;showClockTime=!0;handleFullscreenChange=()=>{this.updateFullscreenButton(),this.resizeWitnessMap()};witnessMapResizeObserver=typeof ResizeObserver>"u"?void 0:new ResizeObserver(()=>this.resizeWitnessMap());resizeWitnessMap(){this.witnessMapPanel.hidden||(this.sizeWitnessMapCanvas(),this.paintWitnessMap(this.currentTime))}simulatedFullscreen=!1;styleBeforeSimulatedFullscreen;bodyOverflowBeforeSimulatedFullscreen;handleSimulatedFullscreenKey=e=>{e.key==="Escape"&&this.exitSimulatedFullscreen()};handlePointerMove=e=>{const t=this.canvasPointFromEvent(e),s=t&&this.shapeAt(t.x,t.y),i=this.said.read(s?.shape.title);if(!i){this.tooltip.hidden=!0;return}this.tooltip.textContent=i,this.tooltip.hidden=!1;const r=this.stageElement.getBoundingClientRect();this.tooltip.style.left=`${e.clientX-r.left+12}px`,this.tooltip.style.top=`${e.clientY-r.top+12}px`};handlePointerLeave=()=>{this.tooltip.hidden=!0};constructor(){super(),this.shadow=this.attachShadow({mode:"open"});const e=document.createElement("template");e.innerHTML=`<style>${Ee}</style>${Pe}`,this.shadow.appendChild(e.content.cloneNode(!0)),this.stageElement=this.shadow.getElementById("stage"),this.canvas=this.shadow.getElementById("canvas"),this.canvasRenderer=new it(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.cornerButtons=this.shadow.getElementById("corner-buttons"),this.witnessMapButton=this.shadow.getElementById("witness-map"),this.witnessMapPanel=this.shadow.getElementById("witness-map-panel"),this.witnessMapCanvas=this.shadow.getElementById("witness-map-canvas"),this.witnessMapRenderer=new E(this.witnessMapCanvas.getContext("2d")),this.seekInput=this.shadow.getElementById("seek"),this.milestoneMarks=this.shadow.getElementById("milestone-marks"),this.milestoneCaption=this.shadow.getElementById("milestone-caption"),this.timeStartLabel=this.shadow.getElementById("time-start"),this.timeEndLabel=this.shadow.getElementById("time-end");for(const t of[this.timeStartLabel,this.timeEndLabel])t.addEventListener("click",()=>this.toggleTimeDisplay()),t.addEventListener("keydown",s=>{s.key!=="Enter"&&s.key!==" "||(s.preventDefault(),this.toggleTimeDisplay())});this.fullscreenTarget=this.stageElement,this.playPauseButton.addEventListener("click",()=>this.togglePlayPause()),this.loopButton.addEventListener("click",()=>this.toggleLoop()),this.fullscreenButton.addEventListener("click",()=>this.toggleFullscreen()),this.witnessMapButton.addEventListener("click",()=>this.toggleWitnessMap()),this.seekInput.addEventListener("input",()=>this.player.seek(Number(this.seekInput.value))),this.canvas.addEventListener("click",t=>{this.enableClickToPlay&&(t.detail<=1&&(this.playbackBeforeClick={state:this.playbackState,time:this.currentTime}),this.togglePlayPause())}),this.canvas.addEventListener("dblclick",t=>{this.enableClickToPlay&&(t.preventDefault(),this.restorePlayback(),this.toggleFullscreen())}),this.canvas.addEventListener("pointermove",this.handlePointerMove),this.canvas.addEventListener("pointerleave",this.handlePointerLeave),document.addEventListener("fullscreenchange",this.handleFullscreenChange),this.witnessMapResizeObserver?.observe(this.witnessMapPanel),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.updateFullscreenButton(),this.updateWitnessMapButton(),this.refresh(),this.loadLocaleMessages()}connectedCallback(){this.saidTexts=void 0;const e=this.getAttribute("src");e&&this.loadFromSrc(e)}disconnectedCallback(){document.removeEventListener("fullscreenchange",this.handleFullscreenChange),this.witnessMapResizeObserver?.disconnect(),this.exitSimulatedFullscreen(),this.sightingAudio.dispose()}attributeChangedCallback(e,t,s){e==="src"&&s&&s!==t&&this.isConnected&&this.loadFromSrc(s),e===j&&this.updateWitnessMap()}async loadFromSrc(e){this.sightingData=await Ie.json(e)}get sightingData(){return ot(this.currentSighting)}set sightingData(e){this.player.stop(),this.soundPreview=void 0,this.sightingAudio.silence(),this.currentSighting=ht(e),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.refresh()}get sighting(){return this.currentSighting}previewSound(e){this.soundPreview=e,this.sightingAudio.resume(),this.sightingAudio.setSound(e)}stopSoundPreview(){this.soundPreview=void 0,this.sightingAudio.silence()}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}set autoReplayEnabled(e){e!==this.loopEnabled&&this.toggleLoop()}play(){this.player.seekableDuration<=0||this.playbackState==="playing"||this.togglePlayPause()}pause(){this.playbackState==="playing"&&this.togglePlayPause()}get positionLabel(){return this.timeStartLabel.textContent??""}get durationLabel(){return this.timeEndLabel.textContent??""}get showingClockTime(){return this.canShowClockTime}get canSwitchTimeDisplay(){return this.realStartMs!==void 0}toggleTimeDisplay(){this.canSwitchTimeDisplay&&(this.showClockTime=!this.showClockTime,this.updateTimeLabels(),this.updateTimeLabelTitles(),this.dispatchEvent(new CustomEvent("timedisplaychange",{bubbles:!0,composed:!0})))}get canShowClockTime(){return this.realStartMs!==void 0&&this.showClockTime}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(i=>this.highlightedSourceIds.has(i))||(this.highlightedSourceIds=t,this.refresh())}setOccludedSourceIds(e){e.size===this.occludedSourceIds.size&&[...e].every(s=>this.occludedSourceIds.has(s))||(this.occludedSourceIds=e,this.refresh())}hasVisibleShapeAt(e,t){return this.shapeAt(e,t)!==void 0}refresh(){this.applyFrameFormat(),this.updateTimeLabels(),this.seekInput.max=String(this.player.seekableDuration),this.refreshMilestoneMarks(),this.updateWitnessMap(),this.player.seek(this.player.time)}refreshMilestoneMarks(){const e=this.player.seekableDuration,t=e>0?$(this.sighting.milestones):[];this.milestoneMarks.replaceChildren(...t.map(s=>{const i=document.createElement("button");i.type="button",i.className="milestone-mark",i.style.left=`${Math.min(Math.max(s.t/e,0),1)*100}%`;const r=this.said.read(s.label)??"",a=this.said.read(s.note),o=a?`${r} — ${a}`:r;return i.title=o,i.setAttribute("aria-label",o),i.addEventListener("click",()=>this.player.seek(s.t)),i}))}showMilestoneAt(e){const t=this.sighting.milestones.length>0?ce(this.sighting.milestones,e):void 0;if(this.milestoneCaption.hidden=t===void 0,!t)return;const s=document.createElement("b");s.textContent=this.said.read(t.label)??"";const i=this.said.read(t.note),r=i?` — ${i}`:"";this.milestoneCaption.replaceChildren(s,document.createTextNode(r))}exposureInstants(e){const t=this.exposureTimes(e),s=1/t.length;return t.map(i=>({shapes:this.shapesAt(i),share:s}))}exposureTimes(e=this.currentTime){const t=(this.currentSighting.exposure??0)*1e3;if(t<x.SHORTEST_VISIBLE_EXPOSURE_MS)return[e];const s=Math.min(x.MAX_EXPOSURE_STEPS,Math.max(2,Math.round(t/x.SHORTEST_VISIBLE_EXPOSURE_MS))),i=Math.ceil(this.travelPxOver(e,t)/x.EXPOSURE_STEP_PX),r=Math.min(x.MAX_TRAVEL_STEPS,Math.max(s,i)),a=[];for(let o=0;o<r;o++)a.push(e+t*o/r);return a}travelPxOver(e,t){const s=this.shapesAt(e),i=this.shapesAt(e+t);let r=0;for(const[a,o]of s){const l=i.get(a);if(!l)continue;const h=l.bounds.x+l.bounds.width/2-(o.bounds.x+o.bounds.width/2),d=l.bounds.y+l.bounds.height/2-(o.bounds.y+o.bounds.height/2);r=Math.max(r,Math.hypot(h,d))}return r}shapeAt(e,t,s=this.occludedSourceIds){for(const i of this.exposureTimes()){const r=this.currentSighting.timeline.hitTest(i,e,t,s);if(r)return r}}shapesAt(e){const t=new Map;for(const s of this.currentSighting.timeline.sourceIds){const i=this.currentSighting.timeline.getInterpolatedShapeAt(e,s);i&&t.set(s,i)}return t}static SHORTEST_VISIBLE_EXPOSURE_MS=20;static MAX_EXPOSURE_STEPS=48;static EXPOSURE_STEP_PX=2;static MAX_TRAVEL_STEPS=320;applyFrameFormat(){const e=b.frameWidthPx(this.currentSighting.instrument,this.canvas.height);if(this.canvas.width===e)return;this.canvas.width=e,this.canvas.parentElement?.style.setProperty("--frame-aspect",`${e} / ${this.canvas.height}`)}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:ge;this.canvasRenderer.setStarPoints(b.starPointsOf(this.sighting.instrument)),this.canvasRenderer.setRoll((G(this.sighting,e)?.rollDeg??0)*Math.PI/180);const i=this.exposureInstants(e);for(const r of i)for(const[a,o]of r.shapes){if(this.occludedSourceIds.has(a))continue;const l=r.share,h=l===1?o:{...o,transparency:1-(1-o.transparency)*l};this.canvasRenderer.paintShape(h)}for(const[r,a]of i[0].shapes)this.occludedSourceIds.has(r)||!s.has(r)||(s.size===1?this.canvasRenderer.paintShape({...a,selected:!0}):this.canvasRenderer.paintMemberOutline(a));if(s.size>1){const r=w.groupBoundsFor([...t].filter(([a])=>s.has(a)).map(([,a])=>a.bounds));this.canvasRenderer.paintGroupHandles(r)}this.seekInput.value=String(e),this.timeStartLabel.textContent=this.formatPosition(e),this.showMilestoneAt(e),this.paintWitnessMap(e),this.playbackState==="playing"?(this.soundPreview=void 0,this.sightingAudio.setSound(Ve(this.currentSighting,e))):this.soundPreview?this.sightingAudio.setSound(this.soundPreview):this.sightingAudio.silence(),this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{time:e}}))}createPlayer(){const e=new qe(this.currentSighting.timeline,(t,s)=>this.onFrame(t,s));return e.loop=this.loopEnabled,e.onEnded=()=>{this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("ended",{bubbles:!0,composed:!0}))},e}togglePlayPause(){this.player.seekableDuration<=0||(this.player.playbackState==="playing"?(this.player.pause(),this.soundPreview=void 0,this.sightingAudio.silence(),this.refresh()):(this.sightingAudio.resume(),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.cornerButtons.classList.toggle("auto-hide",e)}toggleLoop(){this.loopEnabled=!this.loopEnabled,this.loopButton.setAttribute("aria-pressed",String(this.loopEnabled)),this.player.loop=this.loopEnabled}restorePlayback(){const e=this.playbackBeforeClick;e&&(this.currentTime=e.time,e.state==="playing"?this.play():this.pause())}get canUseNativeFullscreen(){return typeof this.fullscreenTarget.requestFullscreen=="function"&&document.fullscreenEnabled}toggleFullscreen(){if(!this.canUseNativeFullscreen){this.simulatedFullscreen?this.exitSimulatedFullscreen():this.enterSimulatedFullscreen();return}document.fullscreenElement?document.exitFullscreen():this.fullscreenTarget.requestFullscreen().catch(e=>{console.error("<rr0-ufo>: requestFullscreen() failed —",e)})}enterSimulatedFullscreen(){const e=this.fullscreenTarget;this.styleBeforeSimulatedFullscreen=e.getAttribute("style")??"",this.bodyOverflowBeforeSimulatedFullscreen=document.body.style.overflow;const t=e.style;t.setProperty("position","fixed"),t.setProperty("inset","0"),t.setProperty("margin","0"),t.setProperty("max-width","none"),t.setProperty("max-height","none"),t.setProperty("aspect-ratio","auto"),t.setProperty("z-index","2147483647"),t.setProperty("background","#000"),t.setProperty("width","100vw"),t.setProperty("width","100dvw"),t.setProperty("height","100vh"),t.setProperty("height","100dvh"),document.body.style.overflow="hidden",this.simulatedFullscreen=!0,document.addEventListener("keydown",this.handleSimulatedFullscreenKey),this.updateFullscreenButton()}exitSimulatedFullscreen(){if(!this.simulatedFullscreen)return;const e=this.fullscreenTarget;this.styleBeforeSimulatedFullscreen?e.setAttribute("style",this.styleBeforeSimulatedFullscreen):e.removeAttribute("style"),document.body.style.overflow=this.bodyOverflowBeforeSimulatedFullscreen??"",this.simulatedFullscreen=!1,document.removeEventListener("keydown",this.handleSimulatedFullscreenKey),this.updateFullscreenButton()}toggleWitnessMap(){this.setWitnessMapOpen(this.witnessMapPanel.hidden)}setWitnessMapOpen(e){this.witnessMapPanel.hidden=!e,this.witnessMapButton.setAttribute("aria-pressed",String(e)),this.updateWitnessMapButton(),e&&(this.sizeWitnessMapCanvas(),this.loadWitnessMapImagery(),this.paintWitnessMap(this.currentTime))}updateWitnessMapButton(){const t=!this.witnessMapPanel.hidden?this.messages.hideWitnessMap:this.messages.showWitnessMap;this.witnessMapButton.title=t,this.witnessMapButton.setAttribute("aria-label",t)}updateWitnessMap(){if(this.witnessPath=this.hasAttribute(j)?V.of(this.currentSighting):void 0,this.witnessMapButton.hidden=this.witnessPath===void 0,!this.witnessPath){this.setWitnessMapOpen(!1),this.witnessMapBounds=void 0;return}const e=this.witnessPath.boundsAround(x.WITNESS_MAP_MIN_SPAN_M,x.WITNESS_MAP_MARGIN);(!this.witnessMapBounds||!this.sameGround(this.witnessMapBounds,e))&&(this.witnessMapBounds=e,this.witnessMapImagery=void 0,this.witnessMapAttribution=void 0,this.witnessMapImageryRequested=!1,this.witnessMapPanel.hidden||this.loadWitnessMapImagery())}sameGround(e,t){const s=Math.abs(e.east-e.west)/20;return Math.abs(e.north-t.north)<s&&Math.abs(e.south-t.south)<s&&Math.abs(e.west-t.west)<s&&Math.abs(e.east-t.east)<s}async loadWitnessMapImagery(){if(this.witnessMapImageryRequested||!this.witnessMapBounds)return;this.witnessMapImageryRequested=!0;const e=At(),t=this.witnessMapBounds;try{const s=await e.getImageryTexture(t,{width:x.WITNESS_MAP_IMAGERY_PX,height:x.WITNESS_MAP_IMAGERY_PX});if(this.witnessMapBounds!==t)return;this.witnessMapImagery=s,this.witnessMapAttribution=e.attribution}catch{this.witnessMapAttribution=this.messages.mapImageryUnavailable}this.paintWitnessMap(this.currentTime)}sizeWitnessMapCanvas(){const e=this.witnessMapPanel.clientWidth;if(e===0)return;const t=Math.round(e*(globalThis.devicePixelRatio??1));this.witnessMapCanvas.width!==t&&(this.witnessMapCanvas.width=t,this.witnessMapCanvas.height=t)}paintWitnessMap(e){if(this.witnessMapPanel.hidden||!this.witnessPath||!this.witnessMapBounds)return;const t=G(this.currentSighting,e),s=this.currentSighting.instrument,i=[],r=this.currentSighting.milestones.length>0?ce(this.currentSighting.milestones,e):void 0;for(const a of $(this.currentSighting.milestones)){const o=G(this.currentSighting,a.t);o?.lat===void 0||o.lng===void 0||i.push({label:this.said.read(a.label)??"",lat:o.lat,lng:o.lng,current:a===r})}this.witnessMapRenderer.paint({bounds:this.witnessMapBounds,imagery:this.witnessMapImagery,path:this.witnessPath,position:t?.lat!==void 0&&t.lng!==void 0?{lat:t.lat,lng:t.lng}:void 0,headingDeg:t?.headingDeg,coneHalfAngleDeg:t?I.of(s,this.canvas.height,t.fovDeg).halfWidthAngleDeg(b.aspectOf(s)):void 0,markers:i,attribution:this.witnessMapAttribution})}updateFullscreenButton(){const e=this.simulatedFullscreen||document.fullscreenElement===this.fullscreenTarget;this.fullscreenButton.title=e?this.messages.exitFullscreen:this.messages.fullscreen,this.fullscreenButton.setAttribute("aria-label",this.fullscreenButton.title)}get said(){return this.saidTexts??=new M(de.preferencesFor(this))}saidTexts;async loadLocaleMessages(){const e=lt(de.preferencesFor(this),ut);e!=="en"&&this.applyMessages(await mt(e))}updateTimeLabelTitles(){const e=this.showClockTime?this.messages.switchToElapsed:this.messages.switchToClockTime,t=this.canSwitchTimeDisplay?` — ${e}`:"";this.timeStartLabel.title=this.messages.currentPosition+t,this.timeEndLabel.title=this.messages.duration+t;for(const s of[this.timeStartLabel,this.timeEndLabel])s.classList.toggle("switchable",this.canSwitchTimeDisplay),this.canSwitchTimeDisplay?(s.setAttribute("role","button"),s.setAttribute("tabindex","0")):(s.removeAttribute("role"),s.removeAttribute("tabindex"))}applyMessages(e){this.messages=e,this.updateTimeLabelTitles(),this.loopButton.title=e.autoReplay,this.loopButton.setAttribute("aria-label",e.autoReplay),this.updatePlayPauseButton(),this.updateFullscreenButton(),this.updateWitnessMapButton()}updateTimeLabels(){const e=this.currentSighting.event,t=Ue(e);this.realDurationMs=t!==void 0&&t>0?t:void 0,this.realStartMs=e.time?Q(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=s>0?0:this.realDurationMs??0,this.timeEndLabel.textContent=this.formatEndOfTimeline(),this.timeStartLabel.textContent=this.formatPosition(this.player.time),this.updateTimeLabelTitles()}formatPosition(e){if(this.realDurationMs===void 0)return H(e);const t=this.currentSighting.timeline.duration,s=t>0&&e<=t?e/t*this.realDurationMs:e;return this.canShowClockTime?we(ye(this.realStartMs+s)):H(s)}formatEndOfTimeline(){return this.realDurationMs===void 0?H(this.currentSighting.timeline.duration):this.canShowClockTime?we(ye(this.realStartMs+this.realDurationMs)):H(this.realDurationMs)}}function ye(n){const e=new Date(n);return{hour:e.getUTCHours(),minute:e.getUTCMinutes(),second:e.getUTCSeconds()}}function we(n){if(n.hour===void 0)return"0:00";const e=t=>String(t).padStart(2,"0");return n.second?`${e(n.hour)}:${e(n.minute??0)}:${e(n.second)}`:`${e(n.hour)}:${e(n.minute??0)}`}function H(n){const e=Math.round(n/1e3),t=Math.floor(e/60),s=e%60;return`${t}:${String(s).padStart(2,"0")}`}const be="rr0-ufo";function Dt(){customElements.get(be)||customElements.define(be,x)}Dt();
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@rr0/ufoathome",
3
3
  "type": "module",
4
- "version": "0.46.0",
4
+ "version": "0.47.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",
8
- "homepage": "https://github.com/RR0/UfoAtHome#readme",
8
+ "homepage": "https://ufoathome.org",
9
9
  "repository": {
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/RR0/UfoAtHome.git"