@rr0/ufoathome 0.46.1 → 0.48.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",showMilestones:"Voir les moments du récit",hideMilestones:"Masquer les moments du récit"};export{e as ufoMessages_fr};
@@ -1,10 +1,23 @@
1
- const de=`
1
+ const Et=`
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="milestones" type="button" title="Named moments" aria-label="Named moments" aria-pressed="true" hidden>🔖</button>
12
+ <button id="witness-map" type="button" title="Witness's position" aria-label="Witness's position" aria-pressed="false" hidden>🗺</button>
13
+ <button id="fullscreen" type="button" title="Fullscreen" aria-label="Fullscreen">⛶</button>
14
+ </div>
15
+ <!-- Where the witness stood and which way they faced, on real ground — see WitnessMapRenderer.
16
+ Under the buttons that toggle it, and inert to the pointer: the canvas beneath it is the
17
+ recording, and clicking it plays. -->
18
+ <div id="witness-map-panel" class="witness-map-panel" hidden>
19
+ <canvas id="witness-map-canvas" width="240" height="240"></canvas>
20
+ </div>
8
21
  <!-- What the account calls the moment now on screen (see Milestone) — above the controls rather
9
22
  than inside them, because it is a sentence and the toolbar is a row of buttons. Empty and
10
23
  hidden for the recordings that name no moment, which is most of them. -->
@@ -23,7 +36,7 @@ const de=`
23
36
  <button id="loop" type="button" title="Auto-replay" aria-label="Auto-replay" aria-pressed="true">↻</button>
24
37
  </div>
25
38
  </div>
26
- `,ue=`
39
+ `,At=`
27
40
  :host {
28
41
  display: block;
29
42
  font-family: sans-serif;
@@ -32,7 +45,7 @@ const de=`
32
45
  definite height of its own (the normal, standalone case — .stage's height stays driven by
33
46
  .frame's content, unchanged) — but it matters when this element is embedded with a definite
34
47
  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,
48
+ #stage while THAT is fullscreen): it lets .toolbar/.corner-buttons, anchored to .stage below,
36
49
  actually reach that outer element's true edges instead of only .frame's letterboxed ones.
37
50
  display:flex + centering is unconditional (not just under :fullscreen below) for the same
38
51
  nested-embedding case: this .stage is never itself the real fullscreen element when nested
@@ -59,7 +72,7 @@ const de=`
59
72
  justify-content: center;
60
73
  }
61
74
  /* 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
75
+ whole viewport (100vw/100vh) regardless of its content's aspect ratio. .toolbar/.corner-buttons
63
76
  are anchored to .stage itself (not .frame) specifically so they stay pinned to the true screen
64
77
  edges, full width, like a normal video player's controls — not stuck to the letterboxed
65
78
  content's own (possibly smaller, centered) box above/around them. */
@@ -181,6 +194,9 @@ canvas[data-cursor="rotate"] {
181
194
  }
182
195
  /* Over the bar, and transparent to the pointer except on a mark itself — dragging the bar between
183
196
  two moments has to keep working. */
197
+ .milestone-marks[hidden] {
198
+ display: none;
199
+ }
184
200
  .milestone-marks {
185
201
  position: absolute;
186
202
  left: 0;
@@ -244,10 +260,20 @@ canvas[data-cursor="rotate"] {
244
260
  opacity: 1;
245
261
  pointer-events: auto;
246
262
  }
247
- .fullscreen-btn {
263
+ .corner-buttons {
248
264
  position: absolute;
249
265
  top: 0.4em;
250
266
  right: 0.4em;
267
+ display: flex;
268
+ gap: 0.3em;
269
+ }
270
+ /* Same trap as .toolbar.hidden and .milestone-caption[hidden]: a class or rule that sets its own
271
+ display outranks the UA sheet's [hidden], and both of these are hidden for the recordings that
272
+ have nothing for them to show — no coordinates, or no named moment. */
273
+ .corner-buttons button[hidden] {
274
+ display: none;
275
+ }
276
+ .corner-buttons button {
251
277
  display: inline-flex;
252
278
  align-items: center;
253
279
  justify-content: center;
@@ -263,6 +289,34 @@ canvas[data-cursor="rotate"] {
263
289
  color: #fff;
264
290
  transition: opacity 0.15s ease;
265
291
  }
292
+ .corner-buttons button[aria-pressed="true"] {
293
+ outline: 2px solid #39f;
294
+ }
295
+ /* Below the buttons that open it, same right edge. A share of the stage rather than a fixed pixel
296
+ size, so it stays the same fraction of the picture in a 320 px embed and in fullscreen — but
297
+ floored, since a map too small to tell a road from a wash is not worth the tiles it costs. */
298
+ .witness-map-panel {
299
+ position: absolute;
300
+ top: 2.7em;
301
+ right: 0.4em;
302
+ width: clamp(140px, 30%, 280px);
303
+ aspect-ratio: 1;
304
+ border: 1px solid rgba(255, 255, 255, 0.35);
305
+ border-radius: 3px;
306
+ overflow: hidden;
307
+ background: rgba(0, 0, 0, 0.55);
308
+ /* The recording underneath stays clickable through it: this is something to look at, not
309
+ something to operate. */
310
+ pointer-events: none;
311
+ }
312
+ .witness-map-panel[hidden] {
313
+ display: none;
314
+ }
315
+ .witness-map-panel canvas {
316
+ display: block;
317
+ width: 100%;
318
+ height: 100%;
319
+ }
266
320
  input[type=range] {
267
321
  flex: 1;
268
322
  }
@@ -311,4 +365,4 @@ input[type=range] {
311
365
  pointer-events: none;
312
366
  white-space: nowrap;
313
367
  }
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();
368
+ `;class T extends Error{constructor(t,e,s){super(`${t}: ${e}${s===void 0?"":` (HTTP ${s})`}`),this.kind=t,this.url=e,this.status=s,this.name="SightingFetchError"}}class Dt{static async json(t){const e=new URL(t,location.href);if(location.protocol==="https:"&&e.protocol==="http:")throw new T("mixed-content",t);let s;try{s=await fetch(t)}catch{throw await this.diagnose(t,e)}if(!s.ok)throw new T("status",t,s.status);try{return await s.json()}catch{throw new T("malformed",t)}}static async diagnose(t,e){if(e.origin===location.origin)return new T("unreachable",t);try{return await fetch(t,{mode:"no-cors"}),new T("cors",t)}catch{return new T("unreachable",t)}}}const O=[{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 O[0]}static byId(t){return O.find(e=>e.id===t)??this.default}static UNAIDED_FIELD_DEG=60;static UNAIDED_ASPECT=16/9;static fieldOfViewDeg(t){const e=t.frame;return e?2*Math.atan(e.heightMm/(2*e.focalLengthMm))*180/Math.PI:b.UNAIDED_FIELD_DEG}static aspectOf(t){const e=t.frame;return e?e.widthMm/e.heightMm:b.UNAIDED_ASPECT}static frameWidthPx(t,e){return Math.round(e*b.aspectOf(t))}static availableAt(t){return t===void 0?O:O.filter(e=>!e.years||t>=e.years.from&&(e.years.to===void 0||t<=e.years.to))}static fieldOfViewDegAt(t,e){const s=t.frame;if(!(!s||e<=0))return 2*Math.atan(s.heightMm/(2*e))*180/Math.PI}static focalLengthMmFor(t,e){const s=t.frame;if(!(!s||e<=0||e>=180))return s.heightMm/(2*Math.tan(e*Math.PI/360))}static bladesShowing(t,e){const s=e??t.fNumber;if(s===void 0||t.apertureBlades===void 0)return 0;const i=t.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(t){return t.flare??b.LENS_FLARE_ARTIFACTS}static starPointsOf(t){const e=t.apertureBlades;return e===void 0||e<3?0:e%2===0?e:e*2}}class M{static MOON_ANGULAR_WIDTH_DEG=.5237;static CANVAS_WIDTH_PX=640;static CANVAS_HEIGHT_PX=360;static angularWidthDeg(t){return 2*Math.atan(t.sizeM/(2*t.distanceM))*180/Math.PI}static inMoons(t){return t/M.MOON_ANGULAR_WIDTH_DEG}static lerpAngular(t,e,s){if(!(!t||!e))return{widthDeg:t.widthDeg+(e.widthDeg-t.widthDeg)*s,heightDeg:t.heightDeg+(e.heightDeg-t.heightDeg)*s}}static sizeMAt(t,e){return 2*t*Math.tan(e*Math.PI/360)}static distanceMAt(t,e){return t/(2*Math.tan(e*Math.PI/360))}}function Ct(n,t,e){const{bounds:s}=n;return t>=s.x&&t<=s.x+s.width&&e>=s.y&&e<=s.y+s.height}function y(n,t,e){return n+(t-n)*e}function st(n){const t=/^#([0-9a-f]{6})$/i.exec(n);if(!t)return;const e=parseInt(t[1],16);return[e>>16&255,e>>8&255,e&255]}function Bt(n){return`#${n.map(t=>Math.round(t).toString(16).padStart(2,"0")).join("")}`}function Rt(n,t,e){const s=st(n),i=st(t);return!s||!i?e<1?n:t:Bt([y(s[0],i[0],e),y(s[1],i[1],e),y(s[2],i[2],e)])}function _t(n,t,e){const s={x:y(n.bounds.x,t.bounds.x,e),y:y(n.bounds.y,t.bounds.y,e),width:y(n.bounds.width,t.bounds.width,e),height:y(n.bounds.height,t.bounds.height,e)},i=y(n.angle,t.angle,e),r=y(n.transparency,t.transparency,e),a=y(n.haloScale,t.haloScale,e),o=y(n.blur??0,t.blur??0,e),l=y(n.brightness??0,t.brightness??0,e),h=Rt(n.color,t.color,e),d=M.lerpAngular(n.angular,t.angular,e);return n.kind==="polygon"&&t.kind==="polygon"&&n.points.length===t.points.length?{...n,bounds:s,angle:i,transparency:r,haloScale:a,blur:o,brightness:l,color:h,angular:d,behindCloud:e<1?n.behindCloud:t.behindCloud,points:n.points.map((c,u)=>({x:y(c.x,t.points[u].x,e),y:y(c.y,t.points[u].y,e)}))}:{...e<1?n:t,bounds:s,angle:i,transparency:r,haloScale:a,blur:o,brightness:l,color:h,angular:d}}class q{keyframes=[];order=[];groups=[];addKeyframe(t,e){const s=this.findInsertIndex(t);if(this.keyframes[s]?.t===t){const i=new Set(e.map(r=>r.sourceId));this.keyframes[s]={t,shapes:[...this.keyframes[s].shapes.filter(r=>!i.has(r.sourceId)),...e]}}else this.keyframes.splice(s,0,{t,shapes:[...e]});for(const i of e)this.order.includes(i.sourceId)||this.order.push(i.sourceId)}findInsertIndex(t){let e=0,s=this.keyframes.length;for(;e<s;){const i=e+s>>>1;this.keyframes[i].t<t?e=i+1:s=i}return e}getKeyframeAt(t){const e=this.findInsertIndex(t),s=this.keyframes[e];return s?.t===t?s:void 0}getShapeAt(t,e){return this.getKeyframeAt(t)?.shapes.find(s=>s.sourceId===e)?.shape}getLatestShapeAt(t,e){let s=this.findInsertIndex(t);for(this.keyframes[s]?.t!==t&&(s-=1);s>=0;s--){const i=this.keyframes[s].shapes.find(r=>r.sourceId===e);if(i)return i.shape}}getInterpolatedShapeAt(t,e){const s=this.findShapeAtOrBefore(t,e);if(s?.t===t)return s.shape;const i=this.findShapeAtOrAfter(t,e);return s?i?_t(s.shape,i.shape,(t-s.t)/(i.t-s.t)):s.shape:i?.shape}findShapeAtOrBefore(t,e){let s=this.findInsertIndex(t);for(this.keyframes[s]?.t!==t&&(s-=1);s>=0;s--){const i=this.keyframes[s].shapes.find(r=>r.sourceId===e);if(i)return{t:this.keyframes[s].t,shape:i.shape}}}findShapeAtOrAfter(t,e){for(let s=this.findInsertIndex(t);s<this.keyframes.length;s++){const i=this.keyframes[s].shapes.find(r=>r.sourceId===e);if(i)return{t:this.keyframes[s].t,shape:i.shape}}}hitTest(t,e,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(t,r[a]);if(o&&Ct(o,e,s))return{sourceId:r[a],shape:o}}}removeSource(t){for(let e=this.keyframes.length-1;e>=0;e--){const s=this.keyframes[e].shapes.filter(i=>i.sourceId!==t);s.length===0?this.keyframes.splice(e,1):s.length!==this.keyframes[e].shapes.length&&(this.keyframes[e]={t:this.keyframes[e].t,shapes:s})}this.order=this.order.filter(e=>e!==t),this.removeFromGroup(t)}group(t){if(!(t.length<2)){for(const e of t)this.removeFromGroup(e);this.groups.push([...t])}}ungroup(t){const e=this.groups.findIndex(s=>s.includes(t));e!==-1&&this.groups.splice(e,1)}groupMembers(t){return this.groups.find(e=>e.includes(t))}removeFromGroup(t){const e=this.groups.findIndex(i=>i.includes(t));if(e===-1)return;const s=this.groups[e].filter(i=>i!==t);s.length<2?this.groups.splice(e,1):this.groups[e]=s}bringToFront(t){const e=this.order.indexOf(t);e!==-1&&(this.order.splice(e,1),this.order.push(t))}sendToBack(t){const e=this.order.indexOf(t);e!==-1&&(this.order.splice(e,1),this.order.unshift(t))}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(t){const e=new q;for(const s of t.keyframes)e.addKeyframe(s.t,s.shapes);if(t.order){const s=e.order.filter(i=>!t.order.includes(i));e.order=[...t.order,...s]}return t.groups&&(e.groups=t.groups.map(s=>s.filter(i=>e.order.includes(i))).filter(s=>s.length>=2)),e}}function Lt(n,t,e){return Math.max(t,Math.min(e,n))}function Ft(n,t,e){const s=((t-n)%360+540)%360-180;return((n+s*e)%360+360)%360}function I(n,t,e){return n+(t-n)*e}function Ot(n,t,e){return{lat:n.lat===void 0||t.lat===void 0?void 0:I(n.lat,t.lat,e),lng:n.lng===void 0||t.lng===void 0?void 0:I(n.lng,t.lng,e),elevationM:I(n.elevationM,t.elevationM,e),headingDeg:n.headingDeg===void 0||t.headingDeg===void 0?void 0:Ft(n.headingDeg,t.headingDeg,e),pitchDeg:I(n.pitchDeg,t.pitchDeg,e),rollDeg:I(n.rollDeg??0,t.rollDeg??0,e),fovDeg:I(n.fovDeg,t.fovDeg,e),fNumber:n.fNumber,focusDistanceM:n.focusDistanceM}}class B{keyframes=[];addKeyframe(t,e){const s=this.findInsertIndex(t);this.keyframes[s]?.t===t?this.keyframes[s]={t,pose:e}:this.keyframes.splice(s,0,{t,pose:e})}clear(){this.keyframes.length=0}removeKeyframeAt(t){const e=this.findInsertIndex(t);this.keyframes[e]?.t===t&&this.keyframes.splice(e,1)}findInsertIndex(t){let e=0,s=this.keyframes.length;for(;e<s;){const i=e+s>>>1;this.keyframes[i].t<t?e=i+1:s=i}return e}getLatestPoseAt(t){let e=this.findInsertIndex(t);return this.keyframes[e]?.t!==t&&(e-=1),e>=0?this.keyframes[e].pose:void 0}getInterpolatedPoseAt(t){const e=this.findInsertIndex(t),s=this.keyframes[e]?.t===t?this.keyframes[e]:this.keyframes[e-1];if(s?.t===t)return s.pose;const i=this.keyframes[e]?.t===t?void 0:this.keyframes[e];return s?i?Ot(s.pose,i.pose,Lt((t-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(t){const e=new B;for(const s of t.keyframes)e.addKeyframe(s.t,s.pose);return e}}function S(n,t,e){return n+(t-n)*e}function Nt(n,t,e){const s=((t-n)%360+540)%360-180;return((n+s*e)%360+360)%360}function Wt(n,t,e){return Math.max(t,Math.min(e,n))}function zt(n,t,e){return{cloudCover:S(n.cloudCover,t.cloudCover,e),cloudDarkness:S(n.cloudDarkness,t.cloudDarkness,e),highCloudCover:n.highCloudCover===void 0||t.highCloudCover===void 0?void 0:S(n.highCloudCover,t.highCloudCover,e),iceCrystalAlignment:n.iceCrystalAlignment===void 0||t.iceCrystalAlignment===void 0?void 0:S(n.iceCrystalAlignment,t.iceCrystalAlignment,e),lowerCloudCover:n.lowerCloudCover===void 0||t.lowerCloudCover===void 0?void 0:S(n.lowerCloudCover,t.lowerCloudCover,e),cloudBaseM:n.cloudBaseM===void 0||t.cloudBaseM===void 0?void 0:S(n.cloudBaseM,t.cloudBaseM,e),precipitationType:e<1?n.precipitationType:t.precipitationType,precipitationIntensity:S(n.precipitationIntensity,t.precipitationIntensity,e),windDirectionDeg:Nt(n.windDirectionDeg,t.windDirectionDeg,e),windSpeed:S(n.windSpeed,t.windSpeed,e),storm:e<1?n.storm:t.storm}}class R{keyframes=[];addKeyframe(t,e){const s=this.findInsertIndex(t);this.keyframes[s]?.t===t?this.keyframes[s]={t,weather:e}:this.keyframes.splice(s,0,{t,weather:e})}clear(){this.keyframes.length=0}removeKeyframeAt(t){const e=this.findInsertIndex(t);this.keyframes[e]?.t===t&&this.keyframes.splice(e,1)}findInsertIndex(t){let e=0,s=this.keyframes.length;for(;e<s;){const i=e+s>>>1;this.keyframes[i].t<t?e=i+1:s=i}return e}getLatestWeatherAt(t){let e=this.findInsertIndex(t);return this.keyframes[e]?.t!==t&&(e-=1),e>=0?this.keyframes[e].weather:void 0}getInterpolatedWeatherAt(t){const e=this.findInsertIndex(t),s=this.keyframes[e]?.t===t?this.keyframes[e]:this.keyframes[e-1];if(s?.t===t)return s.weather;const i=this.keyframes[e]?.t===t?void 0:this.keyframes[e];return s?i?zt(s.weather,i.weather,Wt((t-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(t){const e=new R;for(const s of t.keyframes)e.addKeyframe(s.t,s.weather);return e}}class _{keyframes=[];addKeyframe(t,e){const s=this.findInsertIndex(t);this.keyframes[s]?.t===t?this.keyframes[s]={t,sound:e}:this.keyframes.splice(s,0,{t,sound:e})}clear(){this.keyframes.length=0}removeKeyframeAt(t){const e=this.findInsertIndex(t);this.keyframes[e]?.t===t&&this.keyframes.splice(e,1)}getLatestSoundAt(t){let e=this.findInsertIndex(t);return this.keyframes[e]?.t!==t&&(e-=1),e>=0?this.keyframes[e].sound:void 0}getInterpolatedSoundAt(t){const e=this.findInsertIndex(t),s=this.keyframes[e]?.t===t?this.keyframes[e]:this.keyframes[e-1];if(s?.t===t)return s.sound;const i=this.keyframes[e]?.t===t?void 0:this.keyframes[e];if(!s)return i?.sound;if(!i)return s.sound;const r=(t-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(t){const e=new _;for(const s of t.keyframes)e.addKeyframe(s.t,s.sound);return e}lerp(t,e,s){return{kind:s<1?t.kind:e.kind,volume:t.volume+(e.volume-t.volume)*s,pitchHz:t.pitchHz+(e.pitchHz-t.pitchHz)*s,src:s<1?t.src:e.src}}findInsertIndex(t){let e=0,s=this.keyframes.length;for(;e<s;){const i=e+s>>>1;this.keyframes[i].t<t?e=i+1:s=i}return e}}const Ht=100,Gt={kind:"none",volume:0,pitchHz:Ht};function tt(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 it(n){return(((n.day??0)*24+(n.hour??0))*60+(n.minute??0))*6e4+(n.second??0)*1e3}const Ut=["year","month","day","hour","minute"],$t=["day","hour","minute"];function nt(n,t){return t.filter(e=>n[e]!==void 0).join(",")}function Xt(n){if(n.durationSeconds!==void 0)return n.durationSeconds*1e3;if(!n.time||!n.endTime)return;const t=n.time.year!==void 0&&n.endTime.year!==void 0,e=t?Ut:$t,s=nt(n.time,e);if(!(s===""||s!==nt(n.endTime,e)))return t?tt(n.endTime)-tt(n.time):it(n.endTime)-it(n.time)}class K{constructor(t,e,s,i,r,a,o,l,h=[],d,c,u,f=[]){this.event=t,this.timeline=e,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(t,e,s){return new K({eventType:"sighting",time:t,place:e},new q,new B,new R,new _,s)}}const Vt=0,qt=0;function D(n,t){const e=n.witnessTrack.getInterpolatedPoseAt(t);if(e)return e;const s=n.event.place?.[0];if(s)return{lat:s.lat,lng:s.lng,elevationM:Vt,headingDeg:void 0,pitchDeg:qt,fovDeg:b.fieldOfViewDeg(n.instrument)}}function Kt(n,t){return n.soundTrack.getInterpolatedSoundAt(t)??Gt}class Zt{constructor(t,e){this.timeline=t,this.onFrame=e}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 t=()=>{if(this.state!=="playing")return;const e=performance.now();if(this.currentT+=(e-this.lastWallTime)*this.playbackRate,this.lastWallTime=e,this.currentT>=this.seekableDuration){if(this.loop&&this.seekableDuration>0){this.currentT%=this.seekableDuration,this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(t);return}this.currentT=this.seekableDuration,this.stop(),this.resolveFrame(this.currentT),this.onEnded?.();return}this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(t)};this.rafId=requestAnimationFrame(t)}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(t){this.currentT=Math.max(0,Math.min(t,this.seekableDuration)),this.resolveFrame(this.currentT)}get playbackState(){return this.state}get time(){return this.currentT}resolveFrame(t){const e=new Map;for(const s of this.timeline.sourceIds){const i=this.timeline.getInterpolatedShapeAt(t,s);i&&e.set(s,i)}this.onFrame(t,e)}}const Mt=["nw","n","ne","e","se","s","sw","w"],Yt=24,N=8,Jt=3,jt=8;function W(n,t,e){const s=n.x-t.x,i=n.y-t.y,r=Math.cos(e),a=Math.sin(e);return{x:t.x+s*r-i*a,y:t.y+s*a+i*r}}function A(n){return{x:n.x+n.width/2,y:n.y+n.height/2}}const Qt={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(t){const{x:e,y:s,width:i,height:r}=t.bounds,a={nw:{x:e,y:s},n:{x:e+i/2,y:s},ne:{x:e+i,y:s},e:{x:e+i,y:s+r/2},se:{x:e+i,y:s+r},s:{x:e+i/2,y:s+r},sw:{x:e,y:s+r},w:{x:e,y:s+r/2},rotate:{x:e+i/2,y:s-Yt}},o=A(t.bounds),l={};for(const h of Object.keys(a))l[h]=W(a[h],o,t.angle);return l}static hitTestHandle(t,e,s=8,i=[...Mt,"rotate"]){const r=w.handlePointsFor(t);for(const a of i)if(Math.hypot(e.x-r[a].x,e.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(t,e){const i=((w.HANDLE_DIRECTION_DEG[t]+e*180/Math.PI)%180+180)%180/45,r=["ew","nwse","ns","nesw"];return r[Math.round(i)%r.length]}static resizeBounds(t,e,s,i){const r=A(t),a=W(i,r,-e),{x:o,y:l,width:h,height:d}=t,c=Qt[s];let u=o,f=l,m=o+h,p=l+d;return c.left&&(u=Math.min(a.x,m-N)),c.right&&(m=Math.max(a.x,u+N)),c.top&&(f=Math.min(a.y,p-N)),c.bottom&&(p=Math.max(a.y,f+N)),{x:u,y:f,width:m-u,height:p-f}}static resizeShape(t,e,s){if(e==="rotate")throw new Error("resizeShape does not accept the rotate handle");const{width:i,height:r}=t.bounds,a=w.resizeBounds(t.bounds,t.angle,e,s);if(t.kind==="oval")return{...t,bounds:a};const o=i===0?1:a.width/i,l=r===0?1:a.height/r;return{...t,bounds:a,points:t.points.map(h=>({x:h.x*o,y:h.y*l}))}}static rotateShape(t,e){const s=A(t.bounds),i=Math.atan2(e.y-s.y,e.x-s.x)+Math.PI/2;return{...t,angle:i}}static groupBoundsFor(t){const e=Math.min(...t.map(a=>a.x)),s=Math.min(...t.map(a=>a.y)),i=Math.max(...t.map(a=>a.x+a.width)),r=Math.max(...t.map(a=>a.y+a.height));return{x:e,y:s,width:i-e,height:r-s}}static vertexPointsFor(t){const e=A(t.bounds);return t.points.map(s=>W({x:t.bounds.x+s.x,y:t.bounds.y+s.y},e,t.angle))}static hitTestVertex(t,e,s=jt){const i=w.vertexPointsFor(t);let r,a=s;return i.forEach((o,l)=>{const h=Math.hypot(e.x-o.x,e.y-o.y);h<=a&&(a=h,r=l)}),r}static toLocalPoint(t,e){const s=A(t.bounds),i=W(e,s,-t.angle);return{x:i.x-t.bounds.x,y:i.y-t.bounds.y}}static moveVertex(t,e,s){const i=t.points.map((c,u)=>u===e?w.toLocalPoint(t,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:t.bounds.x+r,y:t.bounds.y+a,width:o-r,height:l-a},d=i.map(c=>({x:c.x-r,y:c.y-a}));return{...t,bounds:h,points:d}}static insertVertexNear(t,e){const s=w.toLocalPoint(t,e),{points:i}=t;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{...t,points:o}}static distanceToSegment(t,e,s){const i=s.x-e.x,r=s.y-e.y,a=i*i+r*r,o=a===0?0:Math.max(0,Math.min(1,((t.x-e.x)*i+(t.y-e.y)*r)/a)),l=e.x+o*i,h=e.y+o*r;return Math.hypot(t.x-l,t.y-h)}static deleteVertex(t,e){return t.points.length<=Jt?t:{...t,points:t.points.filter((s,i)=>i!==e)}}}const et=6,z=et/2,te=20,ee=24,se=9,rt=6,ie=7,ne=6,at=4;class re{constructor(t){this.ctx=t}starPoints=0;roll=0;clear(t,e){this.ctx.clearRect(0,0,t,e)}paintShape(t){this.ctx.save(),this.ctx.globalAlpha=1-t.transparency;const e=t.blur??0;e>0&&(this.ctx.filter=`blur(${(ee*e).toFixed(2)}px)`);const s=t.brightness??0;s>0&&this.paintDazzle(t,s),t.haloScale>0&&this.paintHalo(t),this.paintBase(t),this.ctx.restore(),t.selected&&this.paintSelectionHandles(t)}setStarPoints(t){this.starPoints=Math.max(0,Math.floor(t))}setRoll(t){this.roll=t}paintDazzle(t,e){const{x:s,y:i,width:r,height:a}=t.bounds,o=s+r/2,l=i+a/2,h=Math.max(r,a)/2,d=Math.max(h*(1+se*e),ne*e);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<=rt;u++){const f=u/rt,m=1/(1+24*f*f);this.ctx.globalAlpha=1,c.addColorStop(f,this.withAlpha(t.color,m*e*.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+ie*e);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(t.color,e*.55)),v.addColorStop(.35,this.withAlpha(t.color,e*.22)),v.addColorStop(1,this.withAlpha(t.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(t,e){if(e<=0)return t.color;const s=/^#([0-9a-f]{6})$/i.exec(t.color.trim());if(!s)return t.color;const i=Number.parseInt(s[1],16),r=a=>Math.round(a+(255-a)*e);return`rgb(${r(i>>16&255)}, ${r(i>>8&255)}, ${r(i&255)})`}withAlpha(t,e){const s=Math.max(0,Math.min(1,e)),i=/^#([0-9a-f]{6})$/i.exec(t.trim());if(!i)return t;const r=Number.parseInt(i[1],16);return`rgba(${r>>16&255}, ${r>>8&255}, ${r&255}, ${s.toFixed(3)})`}paintBase(t){if(this.ctx.fillStyle=this.dazzledFill(t,t.brightness??0),this.ctx.beginPath(),t.kind==="oval"){const{x:e,y:s,width:i,height:r}=t.bounds,a=i/2,o=r/2;this.ctx.ellipse(e+a,s+o,a,o,t.angle,0,2*Math.PI)}else{const{x:e,y:s,width:i,height:r}=t.bounds;this.ctx.save(),this.ctx.translate(e+i/2,s+r/2),this.ctx.rotate(t.angle),this.ctx.translate(-i/2,-r/2),t.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(t){this.ctx.save(),this.ctx.shadowColor=t.color,this.ctx.shadowBlur=te*t.haloScale,this.paintBase(t),this.ctx.restore()}paintSelectionHandles(t){this.paintHandleFrame(w.handlePointsFor(t),{includeRotate:!0}),t.kind==="polygon"&&this.paintVertexHandles(t)}paintVertexHandles(t){this.ctx.fillStyle="#39f";for(const e of w.vertexPointsFor(t))this.ctx.beginPath(),this.ctx.ellipse(e.x,e.y,at,at,0,0,2*Math.PI),this.ctx.fill()}paintMemberOutline(t){this.paintOutline(w.handlePointsFor(t))}paintGroupHandles(t){this.paintHandleFrame(w.handlePointsFor({bounds:t,angle:0}),{includeRotate:!0})}paintOutline(t){this.ctx.strokeStyle="lightgray",this.ctx.beginPath(),this.ctx.moveTo(t.nw.x,t.nw.y),this.ctx.lineTo(t.ne.x,t.ne.y),this.ctx.lineTo(t.se.x,t.se.y),this.ctx.lineTo(t.sw.x,t.sw.y),this.ctx.closePath(),this.ctx.stroke()}paintHandleFrame(t,{includeRotate:e}){this.paintOutline(t),this.ctx.fillStyle="lightgray";for(const s of Mt){const i=t[s];this.ctx.fillRect(i.x-z,i.y-z,et,et)}e&&(this.ctx.beginPath(),this.ctx.moveTo(t.n.x,t.n.y),this.ctx.lineTo(t.rotate.x,t.rotate.y),this.ctx.stroke(),this.ctx.beginPath(),this.ctx.ellipse(t.rotate.x,t.rotate.y,z+1,z+1,0,0,2*Math.PI),this.ctx.fill())}}const ot=.05,ht=.08,lt=2,ct=3,ae=14,oe=.02;class he{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(t){console.warn("SightingAudio: Web Audio unavailable, the sighting stays silent:",t);return}}this.context.state==="suspended"&&this.context.resume()}setSound(t){if(this.requested=t,!this.context)return;const e=this.keyFor(t);if(!e){this.silence();return}if(this.voice?.key!==e){this.stopVoice();const s=++this.voiceToken;e.startsWith("src:")?this.buildRecordedVoice(t.src,e,s):this.voice=this.buildSynthesizedVoice(t,e)}this.applyTo(this.voice,t)}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(t){if(!(t.volume<=0))return t.src?`src:${t.src}`:t.kind==="none"?void 0:t.kind}applyTo(t,e){const s=this.context;if(!t||!s)return;const i=s.currentTime;t.volumeParam.setTargetAtTime(Math.max(0,Math.min(1,e.volume))*t.gainScale,i,ot);for(const{param:r,ratio:a}of t.pitch)r.setTargetAtTime(Math.min(e.pitchHz*a,s.sampleRate/2-1),i,ot)}buildSynthesizedVoice(t,e){const s=this.context;if(s)switch(t.kind){case"hum":return this.buildHum(s,e);case"whistle":return this.buildWhistle(s,e);case"rumble":return this.buildRumble(s,e);case"crackle":return this.buildCrackle(s,e);default:return}}buildHum(t,e){const s=t.createOscillator();s.type="sawtooth";const i=t.createOscillator();i.type="sawtooth";const r=t.createBiquadFilter();r.type="lowpass",r.Q.value=1;const a=t.createGain();return a.gain.value=0,s.connect(r),i.connect(r),r.connect(a).connect(t.destination),s.start(),i.start(),{key:e,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(t,e){const s=t.createOscillator();s.type="sine";const i=t.createOscillator();i.type="sine",i.frequency.value=5;const r=t.createGain(),a=t.createGain();return a.gain.value=0,i.connect(r).connect(s.frequency),s.connect(a).connect(t.destination),s.start(),i.start(),{key:e,nodes:[r,a],sources:[s,i],volumeParam:a.gain,gainScale:.22,pitch:[{param:s.frequency,ratio:1},{param:r.gain,ratio:.012}]}}buildRumble(t,e){const s=t.createBufferSource();s.buffer=this.brownNoise(t),s.loop=!0;const i=t.createBiquadFilter();i.type="lowpass",i.Q.value=.8;const r=t.createGain();return r.gain.value=0,s.connect(i).connect(r).connect(t.destination),s.start(),{key:e,nodes:[i,r],sources:[s],volumeParam:r.gain,gainScale:1.4,pitch:[{param:i.frequency,ratio:1}]}}buildCrackle(t,e){const s=t.createBufferSource();s.buffer=this.whiteNoise(t),s.loop=!0;const i=t.createBiquadFilter();i.type="bandpass",i.Q.value=1.2;const r=t.createGain();r.gain.value=0;const a=t.createBufferSource();a.buffer=this.crackleEnvelope(t),a.loop=!0;const o=t.createGain();return o.gain.value=0,a.connect(o).connect(r.gain),s.connect(i).connect(r).connect(t.destination),s.start(),a.start(),{key:e,nodes:[i,r,o],sources:[s,a],volumeParam:o.gain,gainScale:.9,pitch:[{param:i.frequency,ratio:4}]}}async buildRecordedVoice(t,e,s){const i=this.context;if(!i)return;let r;try{r=await this.getBuffer(t)}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:e,nodes:[o],sources:[a],volumeParam:o.gain,gainScale:1,pitch:[]},this.requested&&this.applyTo(this.voice,this.requested)}async getBuffer(t){const e=this.context;if(!e)throw new Error("SightingAudio: AudioContext not ready — resume() must be called first");let s=this.buffers.get(t);return s||(s=fetch(t).then(i=>{if(!i.ok)throw new Error(`Audio fetch failed (${i.status}): ${t}`);return i.arrayBuffer()}).then(i=>e.decodeAudioData(i)),this.buffers.set(t,s)),s}whiteNoise(t){if(!this.noiseBuffer){const e=t.createBuffer(1,Math.floor(t.sampleRate*lt),t.sampleRate),s=e.getChannelData(0);for(let i=0;i<s.length;i++)s[i]=Math.random()*2-1;this.noiseBuffer=e}return this.noiseBuffer}brownNoise(t){const e=t.createBuffer(1,Math.floor(t.sampleRate*lt),t.sampleRate),s=e.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 e}crackleEnvelope(t){if(!this.crackleBuffer){const e=t.createBuffer(1,Math.floor(t.sampleRate*ct),t.sampleRate),s=e.getChannelData(0),i=Math.max(1,Math.floor(oe*t.sampleRate)),r=Math.round(ct*ae);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=e}return this.crackleBuffer}stopVoice(){const t=this.voice,e=this.context;if(this.voice=void 0,!t||!e)return;const s=e.currentTime;t.volumeParam.cancelScheduledValues(s),t.volumeParam.setTargetAtTime(0,s,ht/3);const i=s+ht;for(const r of t.sources){r.onended=()=>{r.disconnect();for(const a of t.nodes)a.disconnect()};try{r.stop(i)}catch{r.disconnect();for(const a of t.nodes)a.disconnect()}}}}function $(n){return[...n].sort((t,e)=>t.t-e.t)}function dt(n,t){let e;for(const s of $(n)){if(s.t>t)break;e=s}return e}const Y=Math.PI/180,H=180/Math.PI;class E{constructor(t,e,s){this.kind=t,this.canvasHeightPx=e,this.fovDeg=s;const i=s*Y/2;this.focalPx=t==="equidistant"?e/2/i:e/2/Math.tan(i)}focalPx;static of(t,e,s){return new E(t.projection,e,s)}degToPx(t){const e=t*Y;return this.kind==="equidistant"?this.focalPx*e:2*this.focalPx*Math.tan(e/2)}pxToDeg(t){return this.kind==="equidistant"?t/this.focalPx*H:2*Math.atan(t/(2*this.focalPx))*H}angleDegToRadiusPx(t){const e=t*Y;return this.kind==="equidistant"?this.focalPx*e:this.focalPx*Math.tan(e)}halfWidthAngleDeg(t){return this.radiusPxToAngleDeg(this.canvasHeightPx*t/2)}radiusPxToAngleDeg(t){return this.kind==="equidistant"?t/this.focalPx*H:Math.atan(t/this.focalPx)*H}ofBounds(t){return{widthDeg:this.pxToDeg(t.width),heightDeg:this.pxToDeg(t.height)}}toBoundsSize(t){return{width:this.degToPx(t.widthDeg),height:this.degToPx(t.heightDeg)}}widthPx(t){return this.degToPx(M.angularWidthDeg(t))}}class St{static fovOf(t,e){return D(t,e)?.fovDeg??b.fieldOfViewDeg(t.instrument)}static toAngular(t){this.eachKeyframe(t,(e,s)=>({...e,angular:s.ofBounds(e.bounds)}))}static toBounds(t){this.eachKeyframe(t,(e,s)=>{if(!e.angular)return e;const{width:i,height:r}=s.toBoundsSize(e.angular),a={x:e.bounds.x+(e.bounds.width-i)/2,y:e.bounds.y+(e.bounds.height-r)/2,width:i,height:r};if(e.kind!=="polygon")return{...e,bounds:a};const o=e.bounds.width===0?1:i/e.bounds.width,l=e.bounds.height===0?1:r/e.bounds.height;return{...e,bounds:a,points:e.points.map(h=>({x:h.x*o,y:h.y*l}))}})}static reproject(t,e,s){const i=b.frameWidthPx(e,M.CANVAS_HEIGHT_PX)/2,r=b.frameWidthPx(t.instrument,M.CANVAS_HEIGHT_PX)/2,a=M.CANVAS_HEIGHT_PX/2;for(const o of[...t.timeline.allKeyframes]){const l=this.fovOf(t,o.t),h=s?.get(o.t)??l,d=E.of(e,M.CANVAS_HEIGHT_PX,h),c=E.of(t.instrument,M.CANVAS_HEIGHT_PX,l);t.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={...f,x:r+m*v-f.width/2,y:a+p*v-f.height/2};return{...u,shape:{...u.shape,bounds:F}}}))}this.toBounds(t)}static eachKeyframe(t,e){const{timeline:s}=t;for(const i of[...s.allKeyframes]){const r=this.fovOf(t,i.t),a=E.of(t.instrument,M.CANVAS_HEIGHT_PX,r);s.addKeyframe(i.t,i.shapes.map(o=>({...o,shape:e(o.shape,a)})))}}}function le(n){return St.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 ce(n){const t=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?B.fromJSON(n.witnessTrack):new B,n.weatherTrack?R.fromJSON(n.weatherTrack):new R,n.soundTrack?_.fromJSON(n.soundTrack):new _,n.witness,n.caseId,n.weather,n.decor??[],n.weatherSource,n.instrument,n.exposureSeconds??n.witnessTrack?.keyframes.map(e=>e.pose.exposureSeconds).find(e=>e!==void 0),$(n.milestones??[]));return St.toBounds(t),t}function de(n,t){for(const e of n){const s=e.toLowerCase().split("-")[0];if(t.includes(s))return s}return"en"}class ut{static preferencesFor(t){const e=navigator.languages??[],s=this.declaredFor(t);return s?[s,...e]:e}static declaredFor(t){for(let e=t;e;){const s=e.closest("[lang]")?.getAttribute("lang")?.trim();if(s)return s;const i=e.getRootNode();e=i instanceof ShadowRoot?i.host:void 0}}}class k{constructor(t){this.preferences=t}read(t){if(t===void 0||typeof t=="string")return t===""?void 0:t;for(const s of this.preferences){const i=t[s]??t[k.base(s)];if(i)return i}const e=new Set(this.preferences.map(s=>k.base(s)));for(const[s,i]of Object.entries(t))if(i&&e.has(k.base(s)))return i;return Object.values(t).find(s=>s)??void 0}write(t,e,s){const i=e?.trim()?e.trim():void 0;if(t===void 0||typeof t=="string")return i;const r={...t},a=Object.keys(r).find(o=>o===s)??Object.keys(r).find(o=>k.base(o)===k.base(s))??s;return i===void 0?delete r[a]:r[a]=i,Object.keys(r).length===0?void 0:r}static base(t){return t.toLowerCase().split("-")[0]}}const ue="modulepreload",fe=function(n,t){return new URL(n,t).href},ft={},mt=function(t,e,s){let i=Promise.resolve();if(e&&e.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(e.map(d=>{if(d=fe(d,s),d in ft)return;ft[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":ue,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 t().catch(r)})},me=["en","fr"],pe={en:()=>mt(()=>Promise.resolve().then(()=>ye),void 0,import.meta.url).then(n=>n.ufoMessages_en),fr:()=>mt(()=>import("./UfoMessages_fr-CYappnH6.js"),[],import.meta.url).then(n=>n.ufoMessages_fr)};function ge(n){return pe[n]()}const kt={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",showMilestones:"Show the account's moments",hideMilestones:"Hide the account's moments"},ye=Object.freeze(Object.defineProperty({__proto__:null,ufoMessages_en:kt},Symbol.toStringTag,{value:"Module"})),Tt=Math.PI/180,X=111320;function L(n,t,e,s){const i=X*Math.cos(e*Tt),r=(n-e)*X;return{x:(t-s)*i,z:-r}}function C(n,t,e,s){const i=X*Math.cos(e*Tt),r=-t,a=n;return{lat:e+r/X,lng:s+a/i}}class V{constructor(t){this.points=t}static STATIONARY_M=5;static of(t){const e=[];for(const s of t.witnessTrack.allKeyframes){const{lat:i,lng:r,headingDeg:a}=s.pose;i===void 0||r===void 0||e.push({t:s.t,lat:i,lng:r,headingDeg:a})}if(e.length===0){const s=t.event.place?.[0];if(!s)return;e.push({t:0,lat:s.lat,lng:s.lng})}return new V(e)}get moved(){return this.spanM>V.STATIONARY_M}get spanM(){const t=this.box,e=(t.south+t.north)/2,s=(t.west+t.east)/2;return Math.max(Math.abs(L(e,t.east,e,t.west).x),Math.abs(L(t.north,s,t.south,s).z))}get center(){const t=this.box;return{lat:(t.south+t.north)/2,lng:(t.west+t.east)/2}}boundsAround(t,e){const{lat:s,lng:i}=this.center,r=Math.max(this.spanM*(1+e),t)/2;return{north:C(0,-r,s,i).lat,south:C(0,r,s,i).lat,east:C(r,0,s,i).lng,west:C(-r,0,s,i).lng}}get box(){const t=this.points.map(s=>s.lat),e=this.points.map(s=>s.lng);return{south:Math.min(...t),north:Math.max(...t),west:Math.min(...e),east:Math.max(...e)}}}function we(n,t){const e=n.track,s={eastM:n.eastM,northM:n.northM,altitudeM:0,headingDeg:n.headingDeg};if(!e||e.length===0)return s;if(e.length===1||t<=e[0].t)return pt(e[0],n);const i=e[e.length-1];if(t>=i.t)return pt(i,n);for(let r=0;r<e.length-1;r++){const a=e[r],o=e[r+1];if(t<a.t||t>o.t)continue;const l=o.t-a.t,h=l===0?0:(t-a.t)/l;return{eastM:a.eastM+(o.eastM-a.eastM)*h,northM:a.northM+(o.northM-a.northM)*h,altitudeM:(a.altitudeM??0)+((o.altitudeM??0)-(a.altitudeM??0))*h,headingDeg:a.headingDeg??n.headingDeg}}return s}function pt(n,t){return{eastM:n.eastM,northM:n.northM,altitudeM:n.altitudeM??0,headingDeg:n.headingDeg??t.headingDeg}}const It=Math.PI/180,be=180/Math.PI,ve=256,xe=19;function Me(n,t){return 156543.03392*Math.cos(n*It)/2**t}function Se(n,t){return Me(n,t)*ve}function ke(n,t){let e=0;for(;e<xe&&Se(n,e+1)>=t;)e++;return e}function U(n){const t=n*It;return(1-Math.log(Math.tan(t)+1/Math.cos(t))/Math.PI)/2}function Te(n,t,e){const s=2**e;return{x:(n+180)/360*s,y:U(t)*s}}function Ie(n,t,e){const s=U(n.north),i=U(n.south);return{x:(t-n.west)/(n.east-n.west),y:(U(e)-s)/(i-s)}}function Pe(n,t,e){const{x:s,y:i}=Te(n,t,e);return{x:Math.floor(s),y:Math.floor(i),z:e}}function Ee(n){const t=2**n.z,e=n.x/t*360-180,s=(n.x+1)/t*360-180,i=gt(n.y,t);return{south:gt(n.y+1,t),north:i,west:e,east:s}}function gt(n,t){const e=n/t;return Math.atan(Math.sinh(Math.PI*(1-2*e)))*be}function Ae(n,t,e,s){const i=Pe(n,t,e),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:e});const o=a.map(Ee),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 P{constructor(t){this.ctx=t}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(t){const{ctx:e}=this;e.save(),e.clearRect(0,0,this.width,this.height),this.paintGround(t),this.paintNight(t.nightFraction),this.paintPath(t),t.position?(this.paintCone(t,t.position),this.paintDecor(t),this.paintMarkers(t),this.paintWitness(t,t.position)):(this.paintDecor(t),this.paintMarkers(t)),this.paintNorth(),this.paintFooter(t.bounds,t.attribution),e.restore()}toCanvas(t,e,s){const i=Ie(t,s,e);return{x:i.x*this.width,y:i.y*this.height}}paintGround(t){const{ctx:e}=this;e.fillStyle="#1d2321",e.fillRect(0,0,this.width,this.height);const s=t.imagery;if(!s)return;const i=this.toCanvas(t.bounds,s.bounds.north,s.bounds.west),r=this.toCanvas(t.bounds,s.bounds.south,s.bounds.east);e.drawImage(s.source,i.x,i.y,r.x-i.x,r.y-i.y)}paintNight(t=0){if(t<=0)return;const{ctx:e}=this;e.save(),e.fillStyle=`rgba(6, 12, 30, ${Math.min(t,1)*.78})`,e.fillRect(0,0,this.width,this.height),e.restore()}paintDecor(t){const{ctx:e}=this;for(const s of t.decor){const i=this.toCanvas(t.bounds,s.lat,s.lng);if(e.beginPath(),e.rect(i.x-3,i.y-3,6,6),e.fillStyle="rgba(120, 220, 255, 0.85)",e.fill(),e.lineWidth=1.5,e.strokeStyle="rgba(0, 0, 0, 0.7)",e.stroke(),s.headingDeg===void 0)continue;const r=(s.headingDeg-90)*Math.PI/180;e.beginPath(),e.moveTo(i.x,i.y),e.lineTo(i.x+Math.cos(r)*9,i.y+Math.sin(r)*9),e.lineWidth=3,e.strokeStyle="rgba(0, 0, 0, 0.6)",e.stroke(),e.lineWidth=1.5,e.strokeStyle="rgba(120, 220, 255, 0.9)",e.stroke()}}paintPath(t){if(!t.path.moved)return;const{ctx:e}=this,s=t.path.points.map(i=>this.toCanvas(t.bounds,i.lat,i.lng));e.beginPath(),e.moveTo(s[0].x,s[0].y);for(const i of s.slice(1))e.lineTo(i.x,i.y);e.lineJoin="round",e.lineCap="round",e.strokeStyle="rgba(0, 0, 0, 0.65)",e.lineWidth=5,e.stroke(),e.strokeStyle="rgba(255, 255, 255, 0.9)",e.lineWidth=2,e.stroke()}paintCone(t,e){const{headingDeg:s,coneHalfAngleDeg:i}=t;if(s===void 0||i===void 0)return;const{ctx:r}=this,a=this.toCanvas(t.bounds,e.lat,e.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(t){const{ctx:e}=this;e.textAlign="center",e.textBaseline="middle",e.font="bold 10px sans-serif";for(const s of[...t.markers].sort((i,r)=>Number(i.current)-Number(r.current))){const i=this.toCanvas(t.bounds,s.lat,s.lng);e.beginPath(),e.arc(i.x,i.y,P.MARKER_RADIUS_PX,0,Math.PI*2),e.fillStyle=s.current?"rgba(255, 224, 130, 0.95)":"rgba(0, 0, 0, 0.55)",e.fill(),e.lineWidth=1.5,e.strokeStyle="rgba(255, 255, 255, 0.9)",e.stroke(),e.fillStyle=s.current?"#1d2321":"#fff",e.fillText(s.label,i.x,i.y)}}paintWitness(t,e){const{ctx:s}=this,i=this.toCanvas(t.bounds,e.lat,e.lng),r=t.markers.some(a=>{if(!a.current)return!1;const o=this.toCanvas(t.bounds,a.lat,a.lng);return Math.hypot(o.x-i.x,o.y-i.y)<=P.MARKER_RADIUS_PX});s.beginPath(),s.arc(i.x,i.y,r?P.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:t}=this,e=this.width-14,s=16;t.beginPath(),t.moveTo(e,s-9),t.lineTo(e-4.5,s+5),t.lineTo(e,s+2),t.lineTo(e+4.5,s+5),t.closePath(),t.fillStyle="rgba(255, 255, 255, 0.92)",t.strokeStyle="rgba(0, 0, 0, 0.6)",t.lineWidth=1,t.fill(),t.stroke(),t.font="bold 9px sans-serif",t.textAlign="center",t.textBaseline="top",t.strokeText("N",e,s+6),t.fillText("N",e,s+6)}paintFooter(t,e){const{ctx:s}=this;s.font="9px sans-serif",s.textBaseline="bottom";const i=e?this.wrap(e,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=(t.south+t.north)/2,l=Math.abs(L(a,t.east,a,t.west).x)/this.width,h=P.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(t,e,s){const{ctx:i}=this;i.lineWidth=3,i.strokeStyle="rgba(0, 0, 0, 0.75)",i.strokeText(t,e,s),i.fillStyle="#fff",i.fillText(t,e,s)}wrap(t,e){const s=[];let i="";for(const r of t.split(" ")){const a=i?`${i} ${r}`:r;i&&this.ctx.measureText(a).width>e?(s.push(i),i=r):i=a}return i&&s.push(i),s}}const J=256,yt=3;async function De(n,t,e,s){const i=(n.south+n.north)/2,r=(n.west+n.east)/2,a=Math.abs(L(i,n.east,i,n.west).x),o=Math.abs(L(n.north,r,n.south,r).z),l=ke(i,Math.max(a,o)/2),{tiles:h,bounds:d}=Ae(r,i,l,yt),c=yt*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 F=e(v),Z=await s(F);if(!Z.ok)throw new Error(`Imagery tile fetch failed (${Z.status}): ${F}`);const Pt=await createImageBitmap(await Z.blob());f.drawImage(Pt,(v.x-m.x)*J,(v.y-m.y)*J)}));const p=document.createElement("canvas");p.width=t.width,p.height=t.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,t.width,t.height),{source:p,width:t.width,height:t.height,bounds:d}}class Ce{attribution="Imagery © Esri, Maxar, Earthstar Geographics, and the GIS User Community";fetchImpl;tileUrlTemplate;constructor(t={}){this.fetchImpl=t.fetchImpl??fetch.bind(globalThis),this.tileUrlTemplate=t.tileUrlTemplate??"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"}async getImageryTexture(t,e){return De(t,e,s=>this.tileUrlTemplate.replace("{z}",String(s.z)).replace("{y}",String(s.y)).replace("{x}",String(s.x)),this.fetchImpl)}}function Be(){return new Ce}const wt=new Set,j="show-witness-map",Q="hide-milestones";class x extends HTMLElement{static get observedAttributes(){return["src",j,Q]}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;milestonesButton;witnessMapPanel;witnessMapCanvas;witnessMapRenderer;seekInput;milestoneMarks;milestoneCaption;timeStartLabel;timeEndLabel;witnessPath;witnessMapBounds;witnessMapImagery;witnessMapImageryRequested=!1;witnessMapImageryCredit;witnessMapImageryFailed=!1;creditShownExternally=!1;milestonesShown=!0;currentSighting=K.create();sightingAudio=new he;soundPreview;player;loopEnabled=!0;playbackBeforeClick;highlightedSourceIds=new Set;occludedSourceIds=wt;enableClickToPlay=!0;fullscreenTarget;messages=kt;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=t=>{t.key==="Escape"&&this.exitSimulatedFullscreen()};handlePointerMove=t=>{const e=this.canvasPointFromEvent(t),s=e&&this.shapeAt(e.x,e.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=`${t.clientX-r.left+12}px`,this.tooltip.style.top=`${t.clientY-r.top+12}px`};handlePointerLeave=()=>{this.tooltip.hidden=!0};constructor(){super(),this.shadow=this.attachShadow({mode:"open"});const t=document.createElement("template");t.innerHTML=`<style>${At}</style>${Et}`,this.shadow.appendChild(t.content.cloneNode(!0)),this.stageElement=this.shadow.getElementById("stage"),this.canvas=this.shadow.getElementById("canvas"),this.canvasRenderer=new re(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.milestonesButton=this.shadow.getElementById("milestones"),this.witnessMapPanel=this.shadow.getElementById("witness-map-panel"),this.witnessMapCanvas=this.shadow.getElementById("witness-map-canvas"),this.witnessMapRenderer=new P(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 e of[this.timeStartLabel,this.timeEndLabel])e.addEventListener("click",()=>this.toggleTimeDisplay()),e.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.milestonesButton.addEventListener("click",()=>this.toggleMilestones()),this.seekInput.addEventListener("input",()=>this.player.seek(Number(this.seekInput.value))),this.canvas.addEventListener("click",e=>{this.enableClickToPlay&&(e.detail<=1&&(this.playbackBeforeClick={state:this.playbackState,time:this.currentTime}),this.togglePlayPause())}),this.canvas.addEventListener("dblclick",e=>{this.enableClickToPlay&&(e.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.updateMilestonesButton(),this.refresh(),this.loadLocaleMessages()}connectedCallback(){this.saidTexts=void 0;const t=this.getAttribute("src");t&&this.loadFromSrc(t)}disconnectedCallback(){document.removeEventListener("fullscreenchange",this.handleFullscreenChange),this.witnessMapResizeObserver?.disconnect(),this.exitSimulatedFullscreen(),this.sightingAudio.dispose()}attributeChangedCallback(t,e,s){t==="src"&&s&&s!==e&&this.isConnected&&this.loadFromSrc(s),t===j&&this.applyWitnessMapDefault(),t===Q&&this.setMilestonesShown(!this.hasAttribute(Q))}async loadFromSrc(t){this.sightingData=await Dt.json(t)}get sightingData(){return le(this.currentSighting)}set sightingData(t){this.player.stop(),this.soundPreview=void 0,this.sightingAudio.silence(),this.currentSighting=ce(t),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.refresh(),this.applyWitnessMapDefault()}get sighting(){return this.currentSighting}previewSound(t){this.soundPreview=t,this.sightingAudio.resume(),this.sightingAudio.setSound(t)}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(t){this.player.seek(t)}get seekableDuration(){return this.player.seekableDuration}get autoReplayEnabled(){return this.loopEnabled}set autoReplayEnabled(t){t!==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(t){this.toolbar.classList.toggle("hidden",!t)}get playbackState(){return this.player.playbackState}get selectedSourceIds(){return this.highlightedSourceIds}get durationSeconds(){return this.currentSighting.event.durationSeconds}set durationSeconds(t){this.currentSighting.event.durationSeconds=t,this.updateTimeLabels(),this.refresh(),this.updatePlayPauseButton()}set selectedSourceIds(t){const e=new Set(t);e.size===this.highlightedSourceIds.size&&[...e].every(i=>this.highlightedSourceIds.has(i))||(this.highlightedSourceIds=e,this.refresh())}setOccludedSourceIds(t){t.size===this.occludedSourceIds.size&&[...t].every(s=>this.occludedSourceIds.has(s))||(this.occludedSourceIds=t,this.refresh())}hasVisibleShapeAt(t,e){return this.shapeAt(t,e)!==void 0}refresh(){this.applyFrameFormat(),this.updateTimeLabels(),this.seekInput.max=String(this.player.seekableDuration),this.refreshMilestoneMarks(),this.updateMilestonesButton(),this.updateWitnessMap(),this.player.seek(this.player.time)}refreshMilestoneMarks(){const t=this.player.seekableDuration,e=t>0?$(this.sighting.milestones):[];this.milestoneMarks.replaceChildren(...e.map(s=>{const i=document.createElement("button");i.type="button",i.className="milestone-mark",i.style.left=`${Math.min(Math.max(s.t/t,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(t){const e=this.milestonesShown&&this.sighting.milestones.length>0?dt(this.sighting.milestones,t):void 0;if(this.milestoneCaption.hidden=e===void 0,!e)return;const s=document.createElement("b");s.textContent=this.said.read(e.label)??"";const i=this.said.read(e.note),r=i?` — ${i}`:"";this.milestoneCaption.replaceChildren(s,document.createTextNode(r))}exposureInstants(t){const e=this.exposureTimes(t),s=1/e.length;return e.map(i=>({shapes:this.shapesAt(i),share:s}))}exposureTimes(t=this.currentTime){const e=(this.currentSighting.exposure??0)*1e3;if(e<x.SHORTEST_VISIBLE_EXPOSURE_MS)return[t];const s=Math.min(x.MAX_EXPOSURE_STEPS,Math.max(2,Math.round(e/x.SHORTEST_VISIBLE_EXPOSURE_MS))),i=Math.ceil(this.travelPxOver(t,e)/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(t+e*o/r);return a}travelPxOver(t,e){const s=this.shapesAt(t),i=this.shapesAt(t+e);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(t,e,s=this.occludedSourceIds){for(const i of this.exposureTimes()){const r=this.currentSighting.timeline.hitTest(i,t,e,s);if(r)return r}}shapesAt(t){const e=new Map;for(const s of this.currentSighting.timeline.sourceIds){const i=this.currentSighting.timeline.getInterpolatedShapeAt(t,s);i&&e.set(s,i)}return e}static SHORTEST_VISIBLE_EXPOSURE_MS=20;static MAX_EXPOSURE_STEPS=48;static EXPOSURE_STEP_PX=2;static MAX_TRAVEL_STEPS=320;applyFrameFormat(){const t=b.frameWidthPx(this.currentSighting.instrument,this.canvas.height);if(this.canvas.width===t)return;this.canvas.width=t,this.canvas.parentElement?.style.setProperty("--frame-aspect",`${t} / ${this.canvas.height}`)}canvasPointFromEvent(t){const e=this.canvas.getBoundingClientRect();if(!(e.width===0||e.height===0))return{x:(t.clientX-e.left)/e.width*this.canvas.width,y:(t.clientY-e.top)/e.height*this.canvas.height}}onFrame(t,e){this.canvasRenderer.clear(this.canvas.width,this.canvas.height);const s=this.playbackState!=="playing"?this.highlightedSourceIds:wt;this.canvasRenderer.setStarPoints(b.starPointsOf(this.sighting.instrument)),this.canvasRenderer.setRoll((D(this.sighting,t)?.rollDeg??0)*Math.PI/180);const i=this.exposureInstants(t);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([...e].filter(([a])=>s.has(a)).map(([,a])=>a.bounds));this.canvasRenderer.paintGroupHandles(r)}this.seekInput.value=String(t),this.timeStartLabel.textContent=this.formatPosition(t),this.showMilestoneAt(t),this.paintWitnessMap(t),this.playbackState==="playing"?(this.soundPreview=void 0,this.sightingAudio.setSound(Kt(this.currentSighting,t))):this.soundPreview?this.sightingAudio.setSound(this.soundPreview):this.sightingAudio.silence(),this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{time:t}}))}createPlayer(){const t=new Zt(this.currentSighting.timeline,(e,s)=>this.onFrame(e,s));return t.loop=this.loopEnabled,t.onEnded=()=>{this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("ended",{bubbles:!0,composed:!0}))},t}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 t=this.player.playbackState==="playing";this.playPauseButton.textContent=t?"⏸":"▶";const e=this.player.seekableDuration>0;this.playPauseButton.disabled=!e;const s=e?t?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",t),this.cornerButtons.classList.toggle("auto-hide",t)}toggleLoop(){this.loopEnabled=!this.loopEnabled,this.loopButton.setAttribute("aria-pressed",String(this.loopEnabled)),this.player.loop=this.loopEnabled}restorePlayback(){const t=this.playbackBeforeClick;t&&(this.currentTime=t.time,t.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(t=>{console.error("<rr0-ufo>: requestFullscreen() failed —",t)})}enterSimulatedFullscreen(){const t=this.fullscreenTarget;this.styleBeforeSimulatedFullscreen=t.getAttribute("style")??"",this.bodyOverflowBeforeSimulatedFullscreen=document.body.style.overflow;const e=t.style;e.setProperty("position","fixed"),e.setProperty("inset","0"),e.setProperty("margin","0"),e.setProperty("max-width","none"),e.setProperty("max-height","none"),e.setProperty("aspect-ratio","auto"),e.setProperty("z-index","2147483647"),e.setProperty("background","#000"),e.setProperty("width","100vw"),e.setProperty("width","100dvw"),e.setProperty("height","100vh"),e.setProperty("height","100dvh"),document.body.style.overflow="hidden",this.simulatedFullscreen=!0,document.addEventListener("keydown",this.handleSimulatedFullscreenKey),this.updateFullscreenButton()}exitSimulatedFullscreen(){if(!this.simulatedFullscreen)return;const t=this.fullscreenTarget;this.styleBeforeSimulatedFullscreen?t.setAttribute("style",this.styleBeforeSimulatedFullscreen):t.removeAttribute("style"),document.body.style.overflow=this.bodyOverflowBeforeSimulatedFullscreen??"",this.simulatedFullscreen=!1,document.removeEventListener("keydown",this.handleSimulatedFullscreenKey),this.updateFullscreenButton()}toggleWitnessMap(){this.setWitnessMapOpen(this.witnessMapPanel.hidden)}applyWitnessMapDefault(){this.setWitnessMapOpen(this.hasAttribute(j)&&this.witnessPath!==void 0)}setWitnessMapOpen(t){this.witnessMapPanel.hidden=!t,this.witnessMapButton.setAttribute("aria-pressed",String(t)),this.updateWitnessMapButton(),t&&(this.sizeWitnessMapCanvas(),this.loadWitnessMapImagery(),this.paintWitnessMap(this.currentTime))}updateWitnessMapButton(){const e=!this.witnessMapPanel.hidden?this.messages.hideWitnessMap:this.messages.showWitnessMap;this.witnessMapButton.title=e,this.witnessMapButton.setAttribute("aria-label",e)}updateWitnessMap(){const t=this.witnessPath!==void 0;if(this.witnessPath=V.of(this.currentSighting),this.witnessMapButton.hidden=this.witnessPath===void 0,!t&&this.witnessPath&&this.applyWitnessMapDefault(),!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.witnessMapImageryCredit=void 0,this.witnessMapImageryFailed=!1,this.witnessMapImageryRequested=!1,this.witnessMapPanel.hidden||this.loadWitnessMapImagery())}sameGround(t,e){const s=Math.abs(t.east-t.west)/20;return Math.abs(t.north-e.north)<s&&Math.abs(t.south-e.south)<s&&Math.abs(t.west-e.west)<s&&Math.abs(t.east-e.east)<s}async loadWitnessMapImagery(){if(this.witnessMapImageryRequested||!this.witnessMapBounds)return;this.witnessMapImageryRequested=!0;const t=Be(),e=this.witnessMapBounds;try{const s=await t.getImageryTexture(e,{width:x.WITNESS_MAP_IMAGERY_PX,height:x.WITNESS_MAP_IMAGERY_PX});if(this.witnessMapBounds!==e)return;this.witnessMapImagery=s,this.witnessMapImageryCredit=t.attribution}catch{this.witnessMapImageryFailed=!0}this.paintWitnessMap(this.currentTime)}get witnessMapCredit(){return this.witnessMapImageryCredit}get witnessMapFooterLine(){return this.witnessMapImageryFailed?this.messages.mapImageryUnavailable:this.creditShownExternally?void 0:this.witnessMapImageryCredit}sizeWitnessMapCanvas(){const t=this.witnessMapPanel.clientWidth;if(t===0)return;const e=Math.round(t*(globalThis.devicePixelRatio??1));this.witnessMapCanvas.width!==e&&(this.witnessMapCanvas.width=e,this.witnessMapCanvas.height=e)}paintWitnessMap(t){if(this.witnessMapPanel.hidden||!this.witnessPath||!this.witnessMapBounds)return;const e=D(this.currentSighting,t),s=this.currentSighting.instrument,i=[],r=this.currentSighting.milestones.length>0?dt(this.currentSighting.milestones,t):void 0;for(const a of this.milestonesShown?$(this.currentSighting.milestones):[]){const o=D(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:e?.lat!==void 0&&e.lng!==void 0?{lat:e.lat,lng:e.lng}:void 0,headingDeg:e?.headingDeg,coneHalfAngleDeg:e?E.of(s,this.canvas.height,e.fovDeg).halfWidthAngleDeg(b.aspectOf(s)):void 0,markers:i,decor:this.witnessMapDecorAt(t),nightFraction:this.witnessMapNightFraction,attribution:this.witnessMapFooterLine})}toggleMilestones(){this.setMilestonesShown(!this.milestonesShown)}setMilestonesShown(t){this.milestonesShown=t,this.milestoneMarks.hidden=!t,this.milestonesButton.setAttribute("aria-pressed",String(t)),this.updateMilestonesButton(),this.showMilestoneAt(this.currentTime),this.paintWitnessMap(this.currentTime)}updateMilestonesButton(){this.milestonesButton.hidden=this.currentSighting.milestones.length===0;const t=this.milestonesShown?this.messages.hideMilestones:this.messages.showMilestones;this.milestonesButton.title=t,this.milestonesButton.setAttribute("aria-label",t)}witnessMapDecorAt(t){const e=D(this.currentSighting,0);return e?.lat===void 0||e.lng===void 0?[]:this.currentSighting.decor.map(s=>{const i=we(s,t),{lat:r,lng:a}=C(i.eastM,-i.northM,e.lat,e.lng);return{lat:r,lng:a,headingDeg:i.headingDeg}})}witnessMapNightFraction;setSunAltitude(t){const e=t===void 0?void 0:Math.max(0,Math.min(1,-t/12));e!==this.witnessMapNightFraction&&(this.witnessMapNightFraction=e,this.paintWitnessMap(this.currentTime))}updateFullscreenButton(){const t=this.simulatedFullscreen||document.fullscreenElement===this.fullscreenTarget;this.fullscreenButton.title=t?this.messages.exitFullscreen:this.messages.fullscreen,this.fullscreenButton.setAttribute("aria-label",this.fullscreenButton.title)}get said(){return this.saidTexts??=new k(ut.preferencesFor(this))}saidTexts;async loadLocaleMessages(){const t=de(ut.preferencesFor(this),me);t!=="en"&&this.applyMessages(await ge(t))}updateTimeLabelTitles(){const t=this.showClockTime?this.messages.switchToElapsed:this.messages.switchToClockTime,e=this.canSwitchTimeDisplay?` — ${t}`:"";this.timeStartLabel.title=this.messages.currentPosition+e,this.timeEndLabel.title=this.messages.duration+e;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(t){this.messages=t,this.updateTimeLabelTitles(),this.loopButton.title=t.autoReplay,this.loopButton.setAttribute("aria-label",t.autoReplay),this.updatePlayPauseButton(),this.updateFullscreenButton(),this.updateWitnessMapButton(),this.updateMilestonesButton()}updateTimeLabels(){const t=this.currentSighting.event,e=Xt(t);this.realDurationMs=e!==void 0&&e>0?e:void 0,this.realStartMs=t.time?tt(t.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(t){if(this.realDurationMs===void 0)return G(t);const e=this.currentSighting.timeline.duration,s=e>0&&t<=e?t/e*this.realDurationMs:t;return this.canShowClockTime?vt(bt(this.realStartMs+s)):G(s)}formatEndOfTimeline(){return this.realDurationMs===void 0?G(this.currentSighting.timeline.duration):this.canShowClockTime?vt(bt(this.realStartMs+this.realDurationMs)):G(this.realDurationMs)}}function bt(n){const t=new Date(n);return{hour:t.getUTCHours(),minute:t.getUTCMinutes(),second:t.getUTCSeconds()}}function vt(n){if(n.hour===void 0)return"0:00";const t=e=>String(e).padStart(2,"0");return n.second?`${t(n.hour)}:${t(n.minute??0)}:${t(n.second)}`:`${t(n.hour)}:${t(n.minute??0)}`}function G(n){const t=Math.round(n/1e3),e=Math.floor(t/60),s=t%60;return`${e}:${String(s).padStart(2,"0")}`}const xt="rr0-ufo";function Re(){customElements.get(xt)||customElements.define(xt,x)}Re();
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@rr0/ufoathome",
3
3
  "type": "module",
4
- "version": "0.46.1",
4
+ "version": "0.48.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"