@rr0/ufoathome 0.53.0 → 0.55.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,404 +0,0 @@
1
- const Dt=`
2
- <div class="stage" id="stage">
3
- <div class="frame" id="frame">
4
- <canvas id="canvas" width="640" height="360"></canvas>
5
- </div>
6
- <div id="tooltip" class="tooltip" hidden></div>
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>
21
- <!-- The account's own sentence and the controls, stacked from the bottom edge up. One box rather
22
- than two independently-anchored ones: the caption used to sit at a fixed 2.6em from the
23
- bottom, which is a guess at the toolbar's height, and a caption long enough to wrap to two
24
- lines then ran under it — Socorro's (E) lost the whole of its second line. Stacked, the
25
- caption sits on whatever height the toolbar actually has, in any language and at any font
26
- size. -->
27
- <div class="bottom-stack" id="bottom-stack">
28
- <!-- What the account calls the moment now on screen (see Milestone) — above the controls rather
29
- than inside them, because it is a sentence and the toolbar is a row of buttons. Empty and
30
- hidden for the recordings that name no moment, which is most of them. -->
31
- <div id="milestone-caption" class="milestone-caption" hidden></div>
32
- <div class="toolbar" id="toolbar">
33
- <button id="play-pause" type="button" title="Play" aria-label="Play">▶</button>
34
- <span id="time-start" class="time-label" title="Current position">0:00</span>
35
- <!-- The bar and the marks over it share one box so a mark can be placed by percentage of the
36
- track. The input keeps its own full width inside it; the marks sit on top and only the
37
- marks themselves take a click. -->
38
- <div id="seek-track" class="seek-track">
39
- <input id="seek" type="range" min="0" max="0" value="0" step="1"/>
40
- <div id="milestone-marks" class="milestone-marks"></div>
41
- </div>
42
- <span id="time-end" class="time-label" title="Duration">0:00</span>
43
- <button id="loop" type="button" title="Auto-replay" aria-label="Auto-replay" aria-pressed="true">↻</button>
44
- </div>
45
- </div>
46
- </div>
47
- `,Ct=`
48
- :host {
49
- display: block;
50
- font-family: sans-serif;
51
- }
52
- /* height:100% is a no-op fallback (resolves to auto) whenever .stage's own parent/host has no
53
- definite height of its own (the normal, standalone case — .stage's height stays driven by
54
- .frame's content, unchanged) — but it matters when this element is embedded with a definite
55
- host size from outside (e.g. <rr0-scene>'s .ufo-overlay sizing this element to fill its own
56
- #stage while THAT is fullscreen): it lets .toolbar/.corner-buttons, anchored to .stage below,
57
- actually reach that outer element's true edges instead of only .frame's letterboxed ones.
58
- display:flex + centering is unconditional (not just under :fullscreen below) for the same
59
- nested-embedding case: this .stage is never itself the real fullscreen element when nested
60
- inside <rr0-scene>'s .ufo-overlay (only the *outer* stage is, so :fullscreen never matches
61
- here even while genuinely full-viewport-sized) — without this, .frame just sat at .stage's
62
- top-left in that oversized box instead of centered, misaligning every shape's canvas
63
- coordinates against the outer 3D scene's own (correctly centered) letterboxed content the
64
- instant .stage's height actually exceeds .frame's. Harmless in the normal standalone case:
65
- .stage's height already matches .frame's exactly there (see above), so there's no extra
66
- space to center within regardless. */
67
- /* The WIDGET's own box, which does not move when the instrument does. A camera's format changes
68
- the shape of the PICTURE, not the shape of the page: a square 126 frame or a phone held upright
69
- is letterboxed inside this box, leaving space to either side that is honest — it is sky the
70
- device never recorded. Without a definite height here the frame would size the widget instead,
71
- and choosing an Instamatic would double the height of the page. Ignored, as it should be,
72
- wherever a real height is imposed (fullscreen, or nested inside <rr0-scene>). */
73
- .stage {
74
- position: relative;
75
- width: 100%;
76
- /* AUTO, with the aspect ratio deciding the height — never a percentage.
77
- A percentage height resolves against a parent whose own height this box is producing, and that
78
- cycle does not always settle the same way: in a page column 585 px wide it came out 851 px tall
79
- on rr0.org's case dossiers and 329 px in a bare page of the same width, from the same CSS. A
80
- portrait box for a 16:9 recording, with the overlay stretched two and a half times vertically
81
- over a 3D scene that had correctly reshaped itself to match. The ratio alone has no cycle to
82
- settle: the width is definite, so the height follows from it.
83
- The two places a real height IS imposed override this below — fullscreen, and (in
84
- ufoTemplate) an overlay stretched over an outer stage by inset:0. */
85
- height: auto;
86
- aspect-ratio: 640 / 360;
87
- display: flex;
88
- align-items: center;
89
- justify-content: center;
90
- }
91
- /* Stretched over an outer stage by <rr0-scene> (see its .ufo-overlay rule, position:absolute with
92
- inset:0): there the height is genuinely imposed from outside and this box has to take all of it,
93
- or the toolbar and the corner buttons anchored to it stop reaching the real edges — which is what
94
- they are anchored to it FOR. The host carries that class, so the two cases are told apart by the
95
- one thing that actually differs between them, rather than by a percentage that has to guess. */
96
- :host(.ufo-overlay) .stage {
97
- height: 100%;
98
- }
99
- /* The browser's own fullscreen UA styles force the fullscreened element (.stage) to fill the
100
- whole viewport (100vw/100vh) regardless of its content's aspect ratio. .toolbar/.corner-buttons
101
- are anchored to .stage itself (not .frame) specifically so they stay pinned to the true screen
102
- edges, full width, like a normal video player's controls — not stuck to the letterboxed
103
- content's own (possibly smaller, centered) box above/around them. */
104
- .stage:fullscreen {
105
- width: 100vw;
106
- height: 100vh;
107
- background: #000;
108
- }
109
- /* max-width/max-height are unconditional (not just a :fullscreen override): percentages resolve
110
- against .stage's height, which is only definite when .stage itself has one (fullscreen, or the
111
- nested-in-<rr0-scene> case above) — otherwise they're inert, so this is always safe. When
112
- definite, the browser's aspect-ratio/min-max interplay algorithm correctly derives whichever
113
- of width/height is the tighter constraint from the other — a real "contain, centered" fit,
114
- not just a single-axis cap that can let the other axis overflow and crop instead of shrink. */
115
- .frame {
116
- /* Contained inside the stage's own box rather than sizing it: the HEIGHT is taken from the
117
- stage and the width follows the format, so a square or upright frame leaves space to either
118
- side instead of making the widget taller. Width first (100%) with a max-height cap would not
119
- do it — a max-height clamp does not shrink a definite width back, it just breaks the ratio. */
120
- height: 100%;
121
- width: auto;
122
- max-width: 100%;
123
- /* The instrument's own format — see Instruments.aspectOf. An eye and an unidentified camera
124
- have no frame of their own, and fall back to the shape this project draws its scene in. */
125
- aspect-ratio: var(--frame-aspect, 640 / 360);
126
- }
127
- canvas {
128
- display: block;
129
- width: 100%;
130
- height: 100%;
131
- background: var(--ufo-canvas-background, #050510);
132
- border: var(--ufo-canvas-border, 1px solid #333);
133
- box-sizing: border-box;
134
- }
135
- /* Hover feedback for the editor (<rr0-sighting-editor>): what the pointer is over is drawn INSIDE
136
- the canvas, so only script can hit-test it — but the appearance stays here, in CSS. The
137
- component only ever states what is under the pointer (data-cursor="move", "resize-ns", ...);
138
- which actual cursor that means is this stylesheet's business alone. Plain <rr0-ufo> playback
139
- never sets the attribute, so it keeps the default arrow throughout.
140
- Directions are SCREEN axes, already accounting for the shape's own rotation (see
141
- ShapeHandles.resizeAxisFor) — a 45-degree-rotated shape's top-left handle really does resize
142
- along the screen's north-east/south-west diagonal, so that is the cursor it gets. */
143
- canvas[data-cursor="record"] {
144
- cursor: crosshair;
145
- }
146
- canvas[data-cursor="select"] {
147
- cursor: pointer;
148
- }
149
- canvas[data-cursor="move"] {
150
- cursor: move;
151
- }
152
- canvas[data-cursor="vertex"] {
153
- cursor: cell;
154
- }
155
- canvas[data-cursor="pan"] {
156
- cursor: grab;
157
- }
158
- canvas[data-cursor="panning"] {
159
- cursor: grabbing;
160
- }
161
- canvas[data-cursor="resize-ew"] {
162
- cursor: ew-resize;
163
- }
164
- canvas[data-cursor="resize-ns"] {
165
- cursor: ns-resize;
166
- }
167
- canvas[data-cursor="resize-nwse"] {
168
- cursor: nwse-resize;
169
- }
170
- canvas[data-cursor="resize-nesw"] {
171
- cursor: nesw-resize;
172
- }
173
- /* No native CSS cursor means "rotate", so this one is drawn here: a circular arrow, white with a
174
- black outline so it stays readable over both a bright daytime sky and a night one. 12 12 is its
175
- hotspot (the 24x24 image's own center, i.e. the middle of the circle it draws). The grab
176
- fallback applies if the data URI cursor is ever rejected. */
177
- canvas[data-cursor="rotate"] {
178
- cursor: url("data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2224%22%20height=%2224%22%3E%3Cpath%20d=%22M12%205A7%207%200%201%201%206.1%208.5%22%20fill=%22none%22%20stroke=%22%23000%22%20stroke-width=%224%22%20stroke-linecap=%22round%22/%3E%3Cpath%20d=%22M12%201.5%2012%208.5%2017%205Z%22%20fill=%22%23000%22%20stroke=%22%23000%22%20stroke-width=%223%22%20stroke-linejoin=%22round%22/%3E%3Cpath%20d=%22M12%205A7%207%200%201%201%206.1%208.5%22%20fill=%22none%22%20stroke=%22%23fff%22%20stroke-width=%221.6%22%20stroke-linecap=%22round%22/%3E%3Cpath%20d=%22M12%201.5%2012%208.5%2017%205Z%22%20fill=%22%23fff%22/%3E%3C/svg%3E") 12 12, grab;
179
- }
180
- /* Anchored once, for the caption and the controls together — see the markup. Transparent to the
181
- pointer as a box; the toolbar takes its own clicks back below. */
182
- .bottom-stack {
183
- position: absolute;
184
- left: 0;
185
- right: 0;
186
- bottom: 0;
187
- display: flex;
188
- flex-direction: column;
189
- pointer-events: none;
190
- }
191
- .toolbar {
192
- pointer-events: auto;
193
- display: flex;
194
- align-items: center;
195
- gap: 0.5em;
196
- padding: 0.4em 0.6em;
197
- background: rgba(0, 0, 0, 0.55);
198
- transition: opacity 0.15s ease;
199
- }
200
- /* While playing, the toolbar and fullscreen button auto-hide and only reappear on hover — kept
201
- always visible while paused/stopped, since that's when the user is most likely to want them.
202
- Deliberately hover-only, not :focus-within: a clicked button/range input keeps keyboard focus
203
- after the pointer moves away, which would otherwise keep them stuck visible indefinitely after
204
- any interaction. */
205
- .auto-hide {
206
- opacity: 0;
207
- pointer-events: none;
208
- }
209
- /* A compound class selector (0,2,0) so this reliably beats the plain .toolbar rule above (0,1,0)
210
- regardless of declaration order — set via UfoElement's showToolbar setter by a composing
211
- element (see SightingEditorElement) that drives its own external playback controls instead, since
212
- this overlay's flex:1 seek bar would otherwise intercept nearly the full width of the canvas's
213
- bottom edge, blocking shape drag/resize there. */
214
- .toolbar.hidden {
215
- display: none;
216
- }
217
- /* Takes the width the bare <input id="seek"> used to take, so nothing else in the row moves. */
218
- .seek-track {
219
- position: relative;
220
- flex: 1;
221
- display: flex;
222
- align-items: center;
223
- }
224
- .seek-track #seek {
225
- flex: 1;
226
- min-width: 0;
227
- }
228
- /* Over the bar, and transparent to the pointer except on a mark itself — dragging the bar between
229
- two moments has to keep working. */
230
- .milestone-marks[hidden] {
231
- display: none;
232
- }
233
- .milestone-marks {
234
- position: absolute;
235
- left: 0;
236
- right: 0;
237
- top: 0;
238
- bottom: 0;
239
- pointer-events: none;
240
- }
241
- .milestone-mark {
242
- position: absolute;
243
- top: 0;
244
- bottom: 0;
245
- width: 10px;
246
- padding: 0;
247
- transform: translateX(-50%);
248
- border: none;
249
- background: none;
250
- cursor: pointer;
251
- pointer-events: auto;
252
- color: inherit;
253
- font: inherit;
254
- line-height: 0;
255
- }
256
- /* The mark itself is the thin line inside that hit area: a 2 px tick is impossible to hit with a
257
- finger, and a 10 px tick would hide the bar under it. */
258
- .milestone-mark::before {
259
- content: "";
260
- position: absolute;
261
- left: 50%;
262
- top: 15%;
263
- bottom: 15%;
264
- width: 2px;
265
- transform: translateX(-50%);
266
- background: #fff;
267
- box-shadow: 0 0 2px rgba(0, 0, 0, 0.8);
268
- }
269
- .milestone-mark:hover::before, .milestone-mark:focus-visible::before {
270
- width: 4px;
271
- }
272
- .milestone-caption {
273
- padding: 0 0.8em 0.35em;
274
- color: #fff;
275
- font-size: 0.85em;
276
- text-align: center;
277
- text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
278
- pointer-events: none;
279
- }
280
- /* Same trap as .toolbar.hidden above and .context-menu[hidden] in the editor: a class that sets
281
- its own display outranks the UA sheet's [hidden]. */
282
- .milestone-caption[hidden] {
283
- display: none;
284
- }
285
- .milestone-caption b {
286
- font-weight: 700;
287
- }
288
- .stage:hover .auto-hide {
289
- opacity: 1;
290
- pointer-events: auto;
291
- }
292
- .corner-buttons {
293
- position: absolute;
294
- top: 0.4em;
295
- right: 0.4em;
296
- display: flex;
297
- gap: 0.3em;
298
- }
299
- /* Same trap as .toolbar.hidden and .milestone-caption[hidden]: a class or rule that sets its own
300
- display outranks the UA sheet's [hidden], and both of these are hidden for the recordings that
301
- have nothing for them to show — no coordinates, or no named moment. */
302
- .corner-buttons button[hidden] {
303
- display: none;
304
- }
305
- .corner-buttons button {
306
- display: inline-flex;
307
- align-items: center;
308
- justify-content: center;
309
- width: 1.8em;
310
- height: 1.8em;
311
- padding: 0;
312
- border: none;
313
- border-radius: 3px;
314
- cursor: pointer;
315
- font-size: 1em;
316
- line-height: 1;
317
- background: rgba(0, 0, 0, 0.55);
318
- color: #fff;
319
- transition: opacity 0.15s ease;
320
- }
321
- .corner-buttons button[aria-pressed="true"] {
322
- outline: 2px solid #39f;
323
- }
324
- /* Below the buttons that open it, same right edge. A share of the stage rather than a fixed pixel
325
- size, so it stays the same fraction of the picture in a 320 px embed and in fullscreen — but
326
- floored, since a map too small to tell a road from a wash is not worth the tiles it costs. */
327
- .witness-map-panel {
328
- position: absolute;
329
- top: 2.7em;
330
- right: 0.4em;
331
- width: clamp(140px, 30%, 280px);
332
- aspect-ratio: 1;
333
- border: 1px solid rgba(255, 255, 255, 0.35);
334
- border-radius: 3px;
335
- overflow: hidden;
336
- background: rgba(0, 0, 0, 0.55);
337
- /* Its marks answer the pointer: hovering one names it, clicking one goes to it — a named moment
338
- is an instant, everything else is a place to turn and look at. Closing it is the button that
339
- opened it, which is where a reader looks for it anyway. */
340
- }
341
- .witness-map-panel[hidden] {
342
- display: none;
343
- }
344
- /* Out of the phenomenon's way — see UfoElement.keepWitnessMapClear. The corner it normally sits in
345
- is the emptiest part of most of these skies, which is exactly why it is there; when it is not,
346
- the recording wins and the map moves. */
347
- .witness-map-panel.on-the-left {
348
- right: auto;
349
- left: 0.4em;
350
- }
351
- .witness-map-panel canvas {
352
- display: block;
353
- width: 100%;
354
- height: 100%;
355
- }
356
- input[type=range] {
357
- flex: 1;
358
- }
359
- .toolbar button {
360
- display: inline-flex;
361
- align-items: center;
362
- justify-content: center;
363
- width: 1.8em;
364
- height: 1.8em;
365
- padding: 0;
366
- border-radius: 3px;
367
- cursor: pointer;
368
- font-size: 1em;
369
- line-height: 1;
370
- }
371
- .toolbar button[aria-pressed="true"] {
372
- outline: 2px solid #39f;
373
- }
374
- .toolbar button:disabled {
375
- cursor: default;
376
- opacity: 0.4;
377
- }
378
- .time-label.switchable {
379
- cursor: pointer;
380
- }
381
- .time-label.switchable:hover,
382
- .time-label.switchable:focus-visible {
383
- text-decoration: underline;
384
- }
385
- .time-label {
386
- color: #fff;
387
- font-variant-numeric: tabular-nums;
388
- font-size: 0.85em;
389
- min-width: 3em;
390
- text-align: center;
391
- }
392
- .tooltip {
393
- position: absolute;
394
- z-index: 1;
395
- padding: 0.2em 0.5em;
396
- background: rgba(0, 0, 0, 0.7);
397
- color: #fff;
398
- font-family: sans-serif;
399
- font-size: 0.85em;
400
- border-radius: 3px;
401
- pointer-events: none;
402
- white-space: nowrap;
403
- }
404
- `;class _ 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 _t{static async json(t){const e=new URL(t,location.href);if(location.protocol==="https:"&&e.protocol==="http:")throw new _("mixed-content",t);let s;try{s=await fetch(t)}catch{throw await this.diagnose(t,e)}if(!s.ok)throw new _("status",t,s.status);try{return await s.json()}catch{throw new _("malformed",t)}}static async diagnose(t,e){if(e.origin===location.origin)return new _("unreachable",t);try{return await fetch(t,{mode:"no-cors"}),new _("cors",t)}catch{return new _("unreachable",t)}}}const H=[{id:"eye",name:{en:"Naked eye",fr:"Œil nu"},flare:0,projection:"equidistant",stabilization:.97},{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 H[0]}static byId(t){return H.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?H:H.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}}const Et=Math.PI/180,q=111320;function D(n,t,e,s){const i=q*Math.cos(e*Et),r=(n-e)*q;return{x:(t-s)*i,z:-r}}function L(n,t,e,s){const i=q*Math.cos(e*Et),r=-t,a=n;return{lat:e+r/q,lng:s+a/i}}class F{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 F(e)}get moved(){return this.spanM>F.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(D(e,t.east,e,t.west).x),Math.abs(D(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:L(0,-r,s,i).lat,south:L(0,r,s,i).lat,east:L(r,0,s,i).lng,west:L(-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)}}}class g{constructor(t,e){this.stretches=t,this.rotationPassed=e}static REFERENCE_SPEED_M_PER_S=1.4;static REFERENCE_STEP_HZ=1.9;static CADENCE_EXPONENT=.43;static RISE_M_PER_M_PER_S=.0175;static SWAY_M=.0175;static SLOWEST_WALK_M_PER_S=.2;static FASTEST_WALK_M_PER_S=2.2;static HEAD_ROLL_DEG=1.5;static HEAD_PITCH_DEG=1.25;static HEAD_YAW_DEG=1;static STILL=Object.freeze({eastM:0,northM:0,upM:0,rollDeg:0,pitchDeg:0,yawDeg:0});static of(t){const e=F.of(t);if(!e||e.points.length<2)return;const s=[];let i=0;for(let r=1;r<e.points.length;r++){const a=e.points[r-1],o=e.points[r],h=(o.t-a.t)/1e3;if(h<=0)continue;const l=D(o.lat,o.lng,a.lat,a.lng),d=l.x,c=-l.z,u=Math.hypot(d,c),f=u/h,p=g.stepHzFor(f);s.push({startMs:a.t,endMs:o.t,speedMPerS:f,eastUnit:u>0?d/u:0,northUnit:u>0?c/u:0,stepHz:p,stepsBefore:i,rampsIn:!0,rampsOut:!0}),i+=p*h}for(let r=0;r<s.length;r++){const a=s[r-1],o=s[r+1];s[r].rampsIn=a===void 0||a.stepHz===0,s[r].rampsOut=o===void 0||o.stepHz===0}if(s.some(r=>r.stepHz>0))return new g(s,1-(t.instrument.stabilization??0))}static stepHzFor(t){return t<g.SLOWEST_WALK_M_PER_S||t>g.FASTEST_WALK_M_PER_S?0:g.REFERENCE_STEP_HZ*Math.pow(t/g.REFERENCE_SPEED_M_PER_S,g.CADENCE_EXPONENT)}offsetAt(t){const e=this.stretches.find(h=>t>=h.startMs&&t<=h.endMs);if(!e||e.stepHz===0)return g.STILL;const s=e.stepsBefore+e.stepHz*(t-e.startMs)/1e3,i=2*Math.PI*s,r=this.amplitudeAt(e,t),a=g.SWAY_M*r*Math.cos(i/2),o=r*this.rotationPassed*e.speedMPerS/g.REFERENCE_SPEED_M_PER_S;return{eastM:a*e.northUnit,northM:-a*e.eastUnit,upM:g.RISE_M_PER_M_PER_S*e.speedMPerS*r*Math.cos(i),rollDeg:g.HEAD_ROLL_DEG*o*Math.cos(i/2),pitchDeg:-1.25*o*Math.cos(i),yawDeg:g.HEAD_YAW_DEG*o*Math.cos(i/2)}}amplitudeAt(t,e){const s=1e3/t.stepHz,i=Math.min(s,(t.endMs-t.startMs)/2);if(i<=0)return 1;const r=e-t.startMs,a=t.endMs-e,o=Math.min(t.rampsIn?r/i:1,t.rampsOut?a/i:1,1);return(1-Math.cos(Math.PI*Math.max(o,0)))/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 Rt(n,t,e){if(!n||!t)return e<1?n:t;const s=((t.azimuthDeg-n.azimuthDeg)%360+540)%360-180;return{azimuthDeg:((n.azimuthDeg+s*e)%360+360)%360,altitudeDeg:n.altitudeDeg+(t.altitudeDeg-n.altitudeDeg)*e}}function Bt(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 w(n,t,e){return n+(t-n)*e}function rt(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 Lt(n){return`#${n.map(t=>Math.round(t).toString(16).padStart(2,"0")).join("")}`}function Ft(n,t,e){const s=rt(n),i=rt(t);return!s||!i?e<1?n:t:Lt([w(s[0],i[0],e),w(s[1],i[1],e),w(s[2],i[2],e)])}function Ot(n,t,e){const s={x:w(n.bounds.x,t.bounds.x,e),y:w(n.bounds.y,t.bounds.y,e),width:w(n.bounds.width,t.bounds.width,e),height:w(n.bounds.height,t.bounds.height,e)},i=w(n.angle,t.angle,e),r=w(n.transparency,t.transparency,e),a=w(n.haloScale,t.haloScale,e),o=w(n.blur??0,t.blur??0,e),h=w(n.brightness??0,t.brightness??0,e),l=Ft(n.color,t.color,e),d=M.lerpAngular(n.angular,t.angular,e),c=Rt(n.aim,t.aim,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:h,color:l,angular:d,aim:c,behindCloud:e<1?n.behindCloud:t.behindCloud,points:n.points.map((u,f)=>({x:w(u.x,t.points[f].x,e),y:w(u.y,t.points[f].y,e)}))}:{...e<1?n:t,bounds:s,angle:i,transparency:r,haloScale:a,blur:o,brightness:h,color:l,angular:d,aim:c}}class Z{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?Ot(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&&Bt(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 Z;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 Nt(n,t,e){return Math.max(t,Math.min(e,n))}function Wt(n,t,e){const s=((t-n)%360+540)%360-180;return((n+s*e)%360+360)%360}function R(n,t,e){return n+(t-n)*e}function zt(n,t,e){return{lat:n.lat===void 0||t.lat===void 0?void 0:R(n.lat,t.lat,e),lng:n.lng===void 0||t.lng===void 0?void 0:R(n.lng,t.lng,e),elevationM:R(n.elevationM,t.elevationM,e),headingDeg:n.headingDeg===void 0||t.headingDeg===void 0?void 0:Wt(n.headingDeg,t.headingDeg,e),pitchDeg:R(n.pitchDeg,t.pitchDeg,e),rollDeg:R(n.rollDeg??0,t.rollDeg??0,e),fovDeg:R(n.fovDeg,t.fovDeg,e),fNumber:n.fNumber,focusDistanceM:n.focusDistanceM}}class O{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?zt(s.pose,i.pose,Nt((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 O;for(const s of t.keyframes)e.addKeyframe(s.t,s.pose);return e}}function E(n,t,e){return n+(t-n)*e}function Ht(n,t,e){const s=((t-n)%360+540)%360-180;return((n+s*e)%360+360)%360}function Ut(n,t,e){return Math.max(t,Math.min(e,n))}function $t(n,t,e){return{cloudCover:E(n.cloudCover,t.cloudCover,e),cloudDarkness:E(n.cloudDarkness,t.cloudDarkness,e),highCloudCover:n.highCloudCover===void 0||t.highCloudCover===void 0?void 0:E(n.highCloudCover,t.highCloudCover,e),iceCrystalAlignment:n.iceCrystalAlignment===void 0||t.iceCrystalAlignment===void 0?void 0:E(n.iceCrystalAlignment,t.iceCrystalAlignment,e),lowerCloudCover:n.lowerCloudCover===void 0||t.lowerCloudCover===void 0?void 0:E(n.lowerCloudCover,t.lowerCloudCover,e),cloudBaseM:n.cloudBaseM===void 0||t.cloudBaseM===void 0?void 0:E(n.cloudBaseM,t.cloudBaseM,e),precipitationType:e<1?n.precipitationType:t.precipitationType,precipitationIntensity:E(n.precipitationIntensity,t.precipitationIntensity,e),windDirectionDeg:Ht(n.windDirectionDeg,t.windDirectionDeg,e),windSpeed:E(n.windSpeed,t.windSpeed,e),storm:e<1?n.storm:t.storm}}class N{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?$t(s.weather,i.weather,Ut((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 N;for(const s of t.keyframes)e.addKeyframe(s.t,s.weather);return e}}class W{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 W;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 Xt=100,Gt={kind:"none",volume:0,pitchHz:Xt};class S{constructor(t){this.entries=t}static empty(){return new S(new Map)}get size(){return this.entries.size}at(t){return this.entries.get(t)}paths(){return[...this.entries.keys()]}set(t,e){e.basis==="stated"&&e.rationale===void 0?this.entries.delete(t):this.entries.set(t,e)}clear(t){for(const e of this.entries.keys())(e===t||e.startsWith(`${t}.`))&&this.entries.delete(e)}static strip(t){const e=new S(new Map);return{recording:S.stripValue(t,[],e),provenance:e}}static restore(t,e){if(e.size===0)return t;const s=structuredClone(t);for(const i of e.paths()){const r=e.at(i),a=i.split("."),o=S.holderOf(s,a);if(!o)continue;const h=a[a.length-1],l=o[h];l!==void 0&&(r.of!==void 0&&JSON.stringify(r.of)!==JSON.stringify(l)||(o[h]={value:l,...r.basis==="stated"?{}:{basis:r.basis},...r.rationale===void 0?{}:{rationale:r.rationale}}))}return s}static isWrapper(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)&&"value"in t}static stripValue(t,e,s){return S.isWrapper(t)?(s.set(e.join("."),{basis:t.basis??"stated",...t.rationale===void 0?{}:{rationale:t.rationale},of:t.value}),S.stripValue(t.value,e,s)):Array.isArray(t)?t.map((i,r)=>S.stripValue(i,[...e,String(r)],s)):typeof t=="object"&&t!==null?Object.fromEntries(Object.entries(t).map(([i,r])=>[i,S.stripValue(r,[...e,i],s)])):t}static holderOf(t,e){let s=t;for(const i of e.slice(0,-1)){if(typeof s!="object"||s===null)return;s=s[i]}return typeof s=="object"&&s!==null?s:void 0}}function it(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 at(n){return(((n.day??0)*24+(n.hour??0))*60+(n.minute??0))*6e4+(n.second??0)*1e3}const Vt=["year","month","day","hour","minute"],Kt=["day","hour","minute"];function ot(n,t){return t.filter(e=>n[e]!==void 0).join(",")}function qt(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?Vt:Kt,s=ot(n.time,e);if(!(s===""||s!==ot(n.endTime,e)))return t?it(n.endTime)-it(n.time):at(n.endTime)-at(n.time)}class J{constructor(t,e,s,i,r,a,o,h,l=[],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=h,this.decor=l,this.weatherSource=d,this.instrumentId=c,this.exposureSeconds=u,this.milestones=f}provenance=S.empty();testimony;get witnessCount(){return 1+this.decor.filter(t=>t.kind==="witness").length}get exposure(){return this.exposureSeconds??this.instrument.exposureSeconds}get instrument(){return b.byId(this.instrumentId)}static create(t,e,s){return new J({eventType:"sighting",time:t,place:e},new Z,new O,new N,new W,s)}}const Yt=0,Zt=0;function T(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:Yt,headingDeg:void 0,pitchDeg:Zt,fovDeg:b.fieldOfViewDeg(n.instrument)}}function Jt(n,t){return n.soundTrack.getInterpolatedSoundAt(t)??Gt}class jt{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 Tt=["nw","n","ne","e","se","s","sw","w"],Qt=24,U=8,te=3,ee=8;function $(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 B(n){return{x:n.x+n.width/2,y:n.y+n.height/2}}const se={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 v{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-Qt}},o=B(t.bounds),h={};for(const l of Object.keys(a))h[l]=$(a[l],o,t.angle);return h}static hitTestHandle(t,e,s=8,i=[...Tt,"rotate"]){const r=v.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=((v.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=B(t),a=$(i,r,-e),{x:o,y:h,width:l,height:d}=t,c=se[s];let u=o,f=h,p=o+l,m=h+d;return c.left&&(u=Math.min(a.x,p-U)),c.right&&(p=Math.max(a.x,u+U)),c.top&&(f=Math.min(a.y,m-U)),c.bottom&&(m=Math.max(a.y,f+U)),{x:u,y:f,width:p-u,height:m-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=v.resizeBounds(t.bounds,t.angle,e,s);if(t.kind==="oval")return{...t,bounds:a};const o=i===0?1:a.width/i,h=r===0?1:a.height/r;return{...t,bounds:a,points:t.points.map(l=>({x:l.x*o,y:l.y*h}))}}static rotateShape(t,e){const s=B(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=B(t.bounds);return t.points.map(s=>$({x:t.bounds.x+s.x,y:t.bounds.y+s.y},e,t.angle))}static hitTestVertex(t,e,s=ee){const i=v.vertexPointsFor(t);let r,a=s;return i.forEach((o,h)=>{const l=Math.hypot(e.x-o.x,e.y-o.y);l<=a&&(a=l,r=h)}),r}static toLocalPoint(t,e){const s=B(t.bounds),i=$(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?v.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)),h=Math.max(...i.map(c=>c.y)),l={x:t.bounds.x+r,y:t.bounds.y+a,width:o-r,height:h-a},d=i.map(c=>({x:c.x-r,y:c.y-a}));return{...t,bounds:l,points:d}}static insertVertexNear(t,e){const s=v.toLocalPoint(t,e),{points:i}=t;let r=i.length-1,a=1/0;for(let h=0;h<i.length;h++){const l=i[h],d=i[(h+1)%i.length],c=v.distanceToSegment(s,l,d);c<a&&(a=c,r=h)}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)),h=e.x+o*i,l=e.y+o*r;return Math.hypot(t.x-h,t.y-l)}static deleteVertex(t,e){return t.points.length<=te?t:{...t,points:t.points.filter((s,i)=>i!==e)}}}const nt=6,X=nt/2,ie=20,ne=24,re=9,ht=6,ae=7,oe=6,lt=4;class he{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(${(ne*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,h=i+a/2,l=Math.max(r,a)/2,d=Math.max(l*(1+re*e),oe*e);if(d<=0)return;this.ctx.save(),this.ctx.globalCompositeOperation="lighter";const c=this.ctx.createRadialGradient(o,h,0,o,h,d);for(let u=0;u<=ht;u++){const f=u/ht,p=1/(1+24*f*f);this.ctx.globalAlpha=1,c.addColorStop(f,this.withAlpha(t.color,p*e*.55))}if(this.ctx.fillStyle=c,this.ctx.beginPath(),this.ctx.arc(o,h,d,0,2*Math.PI),this.ctx.fill(),this.starPoints>0){const u=l*(1+ae*e);this.ctx.lineWidth=Math.max(1,l*.12),this.ctx.lineCap="round";for(let f=0;f<this.starPoints;f++){const p=f/this.starPoints*Math.PI*2+this.roll,m=o+Math.cos(p)*u,y=h+Math.sin(p)*u,k=this.ctx.createLinearGradient(o,h,m,y);k.addColorStop(0,this.withAlpha(t.color,e*.55)),k.addColorStop(.35,this.withAlpha(t.color,e*.22)),k.addColorStop(1,this.withAlpha(t.color,0)),this.ctx.strokeStyle=k,this.ctx.beginPath(),this.ctx.moveTo(o,h),this.ctx.lineTo(m,y),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=ie*t.haloScale,this.paintBase(t),this.ctx.restore()}paintSelectionHandles(t){this.paintHandleFrame(v.handlePointsFor(t),{includeRotate:!0}),t.kind==="polygon"&&this.paintVertexHandles(t)}paintVertexHandles(t){this.ctx.fillStyle="#39f";for(const e of v.vertexPointsFor(t))this.ctx.beginPath(),this.ctx.ellipse(e.x,e.y,lt,lt,0,0,2*Math.PI),this.ctx.fill()}paintMemberOutline(t){this.paintOutline(v.handlePointsFor(t))}paintGroupHandles(t){this.paintHandleFrame(v.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 Tt){const i=t[s];this.ctx.fillRect(i.x-X,i.y-X,nt,nt)}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,X+1,X+1,0,0,2*Math.PI),this.ctx.fill())}}const ct=.05,dt=.08,ut=2,ft=3,le=14,ce=.02;class de{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,ct);for(const{param:r,ratio:a}of t.pitch)r.setTargetAtTime(Math.min(e.pitchHz*a,s.sampleRate/2-1),i,ct)}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(h){console.warn("SightingAudio: the recorded sound failed to load, staying silent:",h);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*ut),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*ut),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*ft),t.sampleRate),s=e.getChannelData(0),i=Math.max(1,Math.floor(ce*t.sampleRate)),r=Math.round(ft*le);for(let a=0;a<r;a++){const o=Math.floor(Math.random()*s.length),h=.4+Math.random()*.6;for(let l=0;l<i&&o+l<s.length;l++)s[o+l]=Math.max(s[o+l],h*(1-l/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,dt/3);const i=s+dt;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 Y(n){return[...n].sort((t,e)=>t.t-e.t)}function pt(n,t){let e;for(const s of Y(n)){if(s.t>t)break;e=s}return e}const Q=Math.PI/180,G=180/Math.PI;class I{constructor(t,e,s){this.kind=t,this.canvasHeightPx=e,this.fovDeg=s;const i=s*Q/2;this.focalPx=t==="equidistant"?e/2/i:e/2/Math.tan(i)}focalPx;static of(t,e,s){return new I(t.projection,e,s)}degToPx(t){const e=t*Q;return this.kind==="equidistant"?this.focalPx*e:2*this.focalPx*Math.tan(e/2)}pxToDeg(t){return this.kind==="equidistant"?t/this.focalPx*G:2*Math.atan(t/(2*this.focalPx))*G}angleDegToRadiusPx(t){const e=t*Q;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*G:Math.atan(t/this.focalPx)*G}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 C{static fovOf(t,e){return T(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,h=e.bounds.height===0?1:r/e.bounds.height;return{...e,bounds:a,points:e.points.map(l=>({x:l.x*o,y:l.y*h}))}})}static toAim(t){this.eachKeyframe(t,(e,s,i)=>{if(i?.headingDeg===void 0)return e;const r=this.frameCentre(t),a=s.radiusPxToAngleDeg(e.bounds.x+e.bounds.width/2-r.x),o=s.radiusPxToAngleDeg(r.y-(e.bounds.y+e.bounds.height/2));return{...e,aim:{azimuthDeg:((i.headingDeg+a)%360+360)%360,altitudeDeg:i.pitchDeg+o}}})}static toPosition(t){this.eachKeyframe(t,(e,s,i)=>{if(!e.aim||i?.headingDeg===void 0)return e;const r=this.frameCentre(t),a=this.shortestArc(e.aim.azimuthDeg-i.headingDeg),o=e.aim.altitudeDeg-i.pitchDeg,h=r.x+this.offAxisPx(s,a)-e.bounds.width/2,l=r.y-this.offAxisPx(s,o)-e.bounds.height/2;return{...e,bounds:{...e.bounds,x:h,y:l}}})}static frameCentre(t){return{x:b.frameWidthPx(t.instrument,M.CANVAS_HEIGHT_PX)/2,y:M.CANVAS_HEIGHT_PX/2}}static shortestArc(t){return(t%360+540)%360-180}static offAxisPx(t,e){return Math.abs(e)>=C.OFF_CANVAS_DEG?Math.sign(e)*C.OFF_CANVAS_PX:t.angleDegToRadiusPx(e)}static OFF_CANVAS_DEG=89;static OFF_CANVAS_PX=1e5;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 h=this.fovOf(t,o.t),l=s?.get(o.t)??h,d=I.of(e,M.CANVAS_HEIGHT_PX,l),c=I.of(t.instrument,M.CANVAS_HEIGHT_PX,h);t.timeline.addKeyframe(o.t,o.shapes.map(u=>{const{bounds:f}=u.shape,p=f.x+f.width/2-i,m=f.y+f.height/2-a,y=Math.hypot(p,m),k=y===0?1:c.angleDegToRadiusPx(d.radiusPxToAngleDeg(y))/y,z={...f,x:r+p*k-f.width/2,y:a+m*k-f.height/2};return{...u,shape:{...u.shape,bounds:z}}}))}this.toBounds(t)}static eachKeyframe(t,e){const{timeline:s}=t;for(const i of[...s.allKeyframes]){const r=T(t,i.t),a=this.fovOf(t,i.t),o=I.of(t.instrument,M.CANVAS_HEIGHT_PX,a);s.addKeyframe(i.t,i.shapes.map(h=>({...h,shape:e(h.shape,o,r)})))}}}function ue(n){return S.restore(fe(n),n.provenance)}function fe(n){return C.toAngular(n),C.toAim(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,testimony:n.testimony,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 pe(n){const{recording:t,provenance:e}=S.strip(n),s=me(t);return s.provenance=e,s}function me(n){const t=new J({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},Z.fromJSON(n.timeline),n.witnessTrack?O.fromJSON(n.witnessTrack):new O,n.weatherTrack?N.fromJSON(n.weatherTrack):new N,n.soundTrack?W.fromJSON(n.soundTrack):new W,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),Y(n.milestones??[]));return t.testimony=n.testimony,C.toBounds(t),C.toPosition(t),t}function ge(n,t){for(const e of n){const s=e.toLowerCase().split("-")[0];if(t.includes(s))return s}return"en"}class mt{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 P{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[P.base(s)];if(i)return i}const e=new Set(this.preferences.map(s=>P.base(s)));for(const[s,i]of Object.entries(t))if(i&&e.has(P.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=>P.base(o)===P.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 ye="modulepreload",we=function(n,t){return new URL(n,t).href},gt={},yt=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"),h=document.querySelector("meta[property=csp-nonce]"),l=h?.nonce||h?.getAttribute("nonce");i=a(e.map(d=>{if(d=we(d,s),d in gt)return;gt[d]=!0;const c=d.endsWith(".css"),u=c?'[rel="stylesheet"]':"";if(!!s)for(let m=o.length-1;m>=0;m--){const y=o[m];if(y.href===d&&(!c||y.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${d}"]${u}`))return;const p=document.createElement("link");if(p.rel=c?"stylesheet":ye,c||(p.as="script"),p.crossOrigin="",p.href=d,l&&p.setAttribute("nonce",l),document.head.appendChild(p),c)return new Promise((m,y)=>{p.addEventListener("load",m),p.addEventListener("error",()=>y(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)})},be=["en","fr"],ve={en:()=>yt(()=>Promise.resolve().then(()=>Me),void 0,import.meta.url).then(n=>n.ufoMessages_en),fr:()=>yt(()=>import("./UfoMessages_fr-BJPYHN17.js"),[],import.meta.url).then(n=>n.ufoMessages_fr)};function xe(n){return ve[n]()}const Pt={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",witnessHere:"The witness, here",decorHere:"Scenery"},Me=Object.freeze(Object.defineProperty({__proto__:null,ufoMessages_en:Pt},Symbol.toStringTag,{value:"Module"}));function Se(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 wt(e[0],n);const i=e[e.length-1];if(t>=i.t)return wt(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 h=o.t-a.t,l=h===0?0:(t-a.t)/h;return{eastM:a.eastM+(o.eastM-a.eastM)*l,northM:a.northM+(o.northM-a.northM)*l,altitudeM:(a.altitudeM??0)+((o.altitudeM??0)-(a.altitudeM??0))*l,headingDeg:a.headingDeg??n.headingDeg}}return s}function wt(n,t){return{eastM:n.eastM,northM:n.northM,altitudeM:n.altitudeM??0,headingDeg:n.headingDeg??t.headingDeg}}const At=Math.PI/180,ke=180/Math.PI,Ee=256,Te=19;function Pe(n,t){return 156543.03392*Math.cos(n*At)/2**t}function Ae(n,t){return Pe(n,t)*Ee}function Ie(n,t){let e=0;for(;e<Te&&Ae(n,e+1)>=t;)e++;return e}function K(n){const t=n*At;return(1-Math.log(Math.tan(t)+1/Math.cos(t))/Math.PI)/2}function De(n,t,e){const s=2**e;return{x:(n+180)/360*s,y:K(t)*s}}function Ce(n,t,e){const s=K(n.north),i=K(n.south);return{x:(t-n.west)/(n.east-n.west),y:(K(e)-s)/(i-s)}}function _e(n,t,e){const{x:s,y:i}=De(n,t,e);return{x:Math.floor(s),y:Math.floor(i),z:e}}function Re(n){const t=2**n.z,e=n.x/t*360-180,s=(n.x+1)/t*360-180,i=bt(n.y,t);return{south:bt(n.y+1,t),north:i,west:e,east:s}}function bt(n,t){const e=n/t;return Math.atan(Math.sinh(Math.PI*(1-2*e)))*ke}function Be(n,t,e,s){const i=_e(n,t,e),r=Math.floor(s/2),a=[];for(let l=-r;l<=r;l++)for(let d=-r;d<=r;d++)a.push({x:i.x+d,y:i.y+l,z:e});const o=a.map(Re),h={south:Math.min(...o.map(l=>l.south)),north:Math.max(...o.map(l=>l.north)),west:Math.min(...o.map(l=>l.west)),east:Math.max(...o.map(l=>l.east))};return{tiles:a,bounds:h}}class A{constructor(t){this.ctx=t}static SCALE_STEPS_M=[5e3,2e3,1e3,500,200,100,50,20,10,5];static MARKER_RADIUS_PX=8.5;static ON_MARKER_M=3;targets=[];hitTest(t,e){let s,i=1/0;for(const r of this.targets){const a=Math.hypot(r.x-t,r.y-e);a>r.radius||a>i||(s=r,i=a)}return s}get width(){return this.ctx.canvas.width}get height(){return this.ctx.canvas.height}paint(t){const{ctx:e}=this;e.save(),this.targets=[],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=Ce(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(s.label&&this.targets.push({kind:"decor",label:s.label,lat:s.lat,lng:s.lng,x:i.x,y:i.y,radius:7}),e.beginPath(),e.rect(i.x-3.5,i.y-3.5,7,7),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)*10,i.y+Math.sin(r)*10),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),h=(s-90)*Math.PI/180,l=i*Math.PI/180;r.save(),r.beginPath(),r.moveTo(a.x,a.y),r.arc(a.x,a.y,o,h-l,h+l),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);this.targets.push({kind:"milestone",label:s.note?`${s.label} — ${s.note}`:s.label,lat:s.lat,lng:s.lng,t:s.t,x:i.x,y:i.y,radius:A.MARKER_RADIUS_PX}),e.beginPath(),e.arc(i.x,i.y,A.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=2,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);t.witnessLabel&&this.targets.push({kind:"witness",label:t.witnessLabel,lat:e.lat,lng:e.lng,x:i.x,y:i.y,radius:8});const r=t.markers.some(a=>{if(!a.current)return!1;const o=D(a.lat,a.lng,e.lat,e.lng);return Math.hypot(o.x,o.z)<=A.ON_MARKER_M});s.beginPath(),s.arc(i.x,i.y,r?A.MARKER_RADIUS_PX+2.5:5.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?3:2.5,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,h=Math.abs(D(a,t.east,a,t.west).x)/this.width,l=A.SCALE_STEPS_M.find(f=>f/h<=this.width/3);if(l===void 0)return;const d=l/h,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(l>=1e3?`${l/1e3} km`:`${l} 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 tt=256,vt=3;async function Le(n,t,e,s){const i=(n.south+n.north)/2,r=(n.west+n.east)/2,a=Math.abs(D(i,n.east,i,n.west).x),o=Math.abs(D(n.north,r,n.south,r).z),h=Ie(i,Math.max(a,o)/2),{tiles:l,bounds:d}=Be(r,i,h,vt),c=vt*tt,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 p=l[0];await Promise.all(l.map(async k=>{const z=e(k),j=await s(z);if(!j.ok)throw new Error(`Imagery tile fetch failed (${j.status}): ${z}`);const It=await createImageBitmap(await j.blob());f.drawImage(It,(k.x-p.x)*tt,(k.y-p.y)*tt)}));const m=document.createElement("canvas");m.width=t.width,m.height=t.height;const y=m.getContext("2d");if(!y)throw new Error("2D canvas context unavailable");return y.drawImage(u,0,0,c,c,0,0,t.width,t.height),{source:m,width:t.width,height:t.height,bounds:d}}class Fe{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 Le(t,e,s=>this.tileUrlTemplate.replace("{z}",String(s.z)).replace("{y}",String(s.y)).replace("{x}",String(s.x)),this.fetchImpl)}}function Oe(){return new Fe}const xt=new Set,et="show-witness-map",st="hide-milestones";class x extends HTMLElement{static get observedAttributes(){return["src",et,st]}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;lookYawDeg=0;lookPitchDeg=0;milestonesShown=!0;currentSighting=J.create();sightingAudio=new de;soundPreview;player;loopEnabled=!0;playbackBeforeClick;highlightedSourceIds=new Set;occludedSourceIds=xt;enableClickToPlay=!0;fullscreenTarget;messages=Pt;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`};handleWitnessMapPointerMove=t=>{const e=this.witnessMapTargetFrom(t);if(!e){this.tooltip.hidden=!0,this.witnessMapCanvas.style.cursor="default";return}this.witnessMapCanvas.style.cursor="pointer",this.tooltip.textContent=e.label,this.tooltip.hidden=!1;const s=this.stageElement.getBoundingClientRect();this.tooltip.style.left=`${t.clientX-s.left+12}px`,this.tooltip.style.top=`${t.clientY-s.top+12}px`};handleWitnessMapClick=t=>{const e=this.witnessMapTargetFrom(t);if(e){if(e.kind==="milestone"&&e.t!==void 0){this.player.seek(e.t);return}this.dispatchEvent(new CustomEvent("lookat",{detail:{lat:e.lat,lng:e.lng,kind:e.kind},bubbles:!0,composed:!0}))}};witnessMapTargetFrom(t){const e=this.witnessMapCanvas.getBoundingClientRect();if(!(e.width===0||e.height===0))return this.witnessMapRenderer.hitTest((t.clientX-e.left)/e.width*this.witnessMapCanvas.width,(t.clientY-e.top)/e.height*this.witnessMapCanvas.height)}handlePointerLeave=()=>{this.tooltip.hidden=!0};constructor(){super(),this.shadow=this.attachShadow({mode:"open"});const t=document.createElement("template");t.innerHTML=`<style>${Ct}</style>${Dt}`,this.shadow.appendChild(t.content.cloneNode(!0)),this.stageElement=this.shadow.getElementById("stage"),this.canvas=this.shadow.getElementById("canvas"),this.canvasRenderer=new he(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 A(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.witnessMapCanvas.addEventListener("pointermove",this.handleWitnessMapPointerMove),this.witnessMapCanvas.addEventListener("pointerleave",this.handlePointerLeave),this.witnessMapCanvas.addEventListener("click",this.handleWitnessMapClick),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===et&&this.applyWitnessMapDefault(),t===st&&this.setMilestonesShown(!this.hasAttribute(st))}async loadFromSrc(t){this.sightingData=await _t.json(t)}get sightingData(){return ue(this.currentSighting)}set sightingData(t){this.player.stop(),this.soundPreview=void 0,this.sightingAudio.silence(),this.currentSighting=pe(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?Y(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?pt(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 h=i.get(a);if(!h)continue;const l=h.bounds.x+h.bounds.width/2-(o.bounds.x+o.bounds.width/2),d=h.bounds.y+h.bounds.height/2-(o.bounds.y+o.bounds.height/2);r=Math.max(r,Math.hypot(l,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:xt;this.canvasRenderer.setStarPoints(b.starPointsOf(this.sighting.instrument));const i=(T(this.sighting,t)?.rollDeg??0)+this.gaitAt(t).rollDeg;this.canvasRenderer.setRoll(i*Math.PI/180);const r=this.exposureInstants(t),a=this.frameShift;for(const o of r)for(const[h,l]of o.shapes){if(this.occludedSourceIds.has(h))continue;const d=o.share,c=d===1?l:{...l,transparency:1-(1-l.transparency)*d};this.canvasRenderer.paintShape(this.shifted(c,a))}for(const[o,h]of r[0].shapes)this.occludedSourceIds.has(o)||!s.has(o)||(s.size===1?this.canvasRenderer.paintShape({...h,selected:!0}):this.canvasRenderer.paintMemberOutline(h));if(s.size>1){const o=v.groupBoundsFor([...e].filter(([h])=>s.has(h)).map(([,h])=>h.bounds));this.canvasRenderer.paintGroupHandles(o)}this.keepWitnessMapClear(r[0].shapes),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(Jt(this.currentSighting,t))):this.soundPreview?this.sightingAudio.setSound(this.soundPreview):this.sightingAudio.silence(),this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{time:t}}))}shifted(t,e){return e.x===0&&e.y===0?t:{...t,bounds:{...t.bounds,x:t.bounds.x+e.x,y:t.bounds.y+e.y}}}createPlayer(){const t=new jt(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(et)&&this.witnessPath!==void 0)}setWitnessMapOpen(t){this.witnessMapPanel.hidden=!t,this.witnessMapPanel.removeAttribute("title"),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=F.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=Oe(),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}keepWitnessMapClear(t){if(this.witnessMapPanel.hidden)return;const e=this.witnessMapBoxPx;if(!e)return;const s=(o,h,l)=>{for(const[d,c]of t)if(!(this.occludedSourceIds.has(d)||c.transparency>=1)&&!(c.bounds.x+c.bounds.width<o-l||c.bounds.x>h+l)&&!(c.bounds.y>e.bottom+l||c.bounds.y+c.bounds.height<e.top-l))return!0;return!1},i=[this.canvas.width-e.width,this.canvas.width],r=[0,e.width],a=x.WITNESS_MAP_CLEARANCE_PX;if(this.witnessMapPanel.classList.contains("on-the-left")){s(i[0],i[1],a)||this.witnessMapPanel.classList.remove("on-the-left");return}s(i[0],i[1],0)&&!s(r[0],r[1],0)&&this.witnessMapPanel.classList.add("on-the-left")}witnessMapBoxPx;static WITNESS_MAP_CLEARANCE_PX=24;sizeWitnessMapCanvas(){const t=this.witnessMapPanel.clientWidth;if(t===0)return;const e=Math.round(t*(globalThis.devicePixelRatio??1));this.measureWitnessMapBox(),this.witnessMapCanvas.width!==e&&(this.witnessMapCanvas.width=e,this.witnessMapCanvas.height=e)}measureWitnessMapBox(){const t=this.witnessMapPanel.getBoundingClientRect(),e=this.canvas.getBoundingClientRect();if(e.width===0||e.height===0)return;const s=this.canvas.width/e.width,i=this.canvas.height/e.height;this.witnessMapBoxPx={width:t.width*s,top:(t.top-e.top)*i,bottom:(t.bottom-e.top)*i}}paintWitnessMap(t){if(this.witnessMapPanel.hidden||!this.witnessPath||!this.witnessMapBounds)return;const e=T(this.currentSighting,t),s=this.currentSighting.instrument,i=[],r=this.currentSighting.milestones.length>0?pt(this.currentSighting.milestones,t):void 0;for(const a of this.milestonesShown?Y(this.currentSighting.milestones):[]){const o=T(this.currentSighting,a.t);o?.lat===void 0||o.lng===void 0||i.push({label:this.said.read(a.label)??"",note:this.said.read(a.note),t:a.t,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===void 0?void 0:e.headingDeg+this.lookYawDeg,coneHalfAngleDeg:e?I.of(s,this.canvas.height,e.fovDeg).halfWidthAngleDeg(b.aspectOf(s)):void 0,markers:i,decor:this.witnessMapDecorAt(t),witnessLabel:this.said.read(this.currentSighting.witness?.title)??this.messages.witnessHere,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=T(this.currentSighting,0);return e?.lat===void 0||e.lng===void 0?[]:this.currentSighting.decor.filter(s=>s.kind!=="crop").map(s=>{const i=Se(s,t),{lat:r,lng:a}=L(i.eastM,-i.northM,e.lat,e.lng);return{lat:r,lng:a,headingDeg:i.headingDeg,label:this.said.read(s.title)??this.messages.decorHere}})}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))}setLookOffset(t,e){t===this.lookYawDeg&&e===this.lookPitchDeg||(this.lookYawDeg=t,this.lookPitchDeg=e,this.refresh())}get frameShiftPx(){return this.frameShift}get frameShift(){const t=this.gaitAt(this.currentTime),e=this.lookYawDeg+t.yawDeg,s=this.lookPitchDeg+t.pitchDeg;if(e===0&&s===0)return{x:0,y:0};const i=T(this.currentSighting,this.currentTime),r=I.of(this.currentSighting.instrument,this.canvas.height,i?.fovDeg??60);return{x:-r.angleDegToRadiusPx(e),y:r.angleDegToRadiusPx(s)}}gaitAt(t){return g.of(this.currentSighting)?.offsetAt(t)??g.STILL}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 P(mt.preferencesFor(this))}saidTexts;async loadLocaleMessages(){const t=ge(mt.preferencesFor(this),be);t!=="en"&&this.applyMessages(await xe(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=qt(t);this.realDurationMs=e!==void 0&&e>0?e:void 0,this.realStartMs=t.time?it(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 V(t);const e=this.currentSighting.timeline.duration,s=e>0&&t<=e?t/e*this.realDurationMs:t;return this.canShowClockTime?St(Mt(this.realStartMs+s)):V(s)}formatEndOfTimeline(){return this.realDurationMs===void 0?V(this.currentSighting.timeline.duration):this.canShowClockTime?St(Mt(this.realStartMs+this.realDurationMs)):V(this.realDurationMs)}}function Mt(n){const t=new Date(n);return{hour:t.getUTCHours(),minute:t.getUTCMinutes(),second:t.getUTCSeconds()}}function St(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 V(n){const t=Math.round(n/1e3),e=Math.floor(t/60),s=t%60;return`${e}:${String(s).padStart(2,"0")}`}const kt="rr0-ufo";function Ne(){customElements.get(kt)||customElements.define(kt,x)}Ne();