@rr0/ufoathome 0.12.4 → 0.12.6
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.
package/README.md
CHANGED
|
@@ -100,7 +100,8 @@ mechanism.
|
|
|
100
100
|
## `<rr0-ufo-recorder>` — full editor
|
|
101
101
|
|
|
102
102
|
The authoring component (~540KB gzip — see below for why): everything `<rr0-ufo>` has, plus a shape/appearance
|
|
103
|
-
toolbar (oval/
|
|
103
|
+
toolbar (oval/polygon presets, color, transparency, halo, and the object's real reported
|
|
104
|
+
size/distance — see [Apparent size](#apparent-size)) and drag-to-record. It composes a nested
|
|
104
105
|
`<rr0-scene>` internally — not a bare `<rr0-ufo>` — so the shape being drawn is always seen against the
|
|
105
106
|
sighting's own real sky, computed live from whatever latitude/longitude/heading/orientation/observation-time
|
|
106
107
|
fields the toolbar currently holds (see [Architecture](#architecture)). This absorbs `<rr0-scene>`'s own
|
|
@@ -111,8 +112,23 @@ this heavier authoring component.
|
|
|
111
112
|
|
|
112
113
|
```html
|
|
113
114
|
<rr0-ufo-recorder></rr0-ufo-recorder>
|
|
115
|
+
<rr0-ufo-recorder src="sighting.json"></rr0-ufo-recorder>
|
|
114
116
|
```
|
|
115
117
|
|
|
118
|
+
With `src`, the editor opens on an existing recording instead of an empty canvas — the same
|
|
119
|
+
attribute the three other elements take. That is what makes an editor URL per observation possible:
|
|
120
|
+
rr0.org's own editor page maps its `?sighting=` parameter onto it, and
|
|
121
|
+
[ufoathome.org](https://ufoathome.org) redirects any path it is given into that parameter, so
|
|
122
|
+
|
|
123
|
+
- `https://ufoathome.org/science/crypto/ufo/enquete/dossier/Socorro/sighting.json`, or simply
|
|
124
|
+
- `https://ufoathome.org/Socorro` for a case dossier of that site (a value with no `/` is expanded
|
|
125
|
+
to `/science/crypto/ufo/enquete/dossier/<name>/sighting.json`),
|
|
126
|
+
|
|
127
|
+
opens that observation for editing. The page only accepts recordings from its own origin: a shared
|
|
128
|
+
link must not be able to display a recording fabricated elsewhere inside an rr0.org page. To load
|
|
129
|
+
one from anywhere else, use the editor's own **Load from URL** field, which is an explicit gesture
|
|
130
|
+
by whoever is sitting at the keyboard.
|
|
131
|
+
|
|
116
132
|
Usage: click **Record**, move the pointer over the canvas to draw the UFO's path, click **Stop**, then **Play** to
|
|
117
133
|
replay it. The nested `<rr0-ufo>`'s `enableClickToPlay` is set to `false` here — a completed recording drag also
|
|
118
134
|
fires a native "click" on the canvas, which would otherwise spuriously toggle playback right after recording.
|
|
@@ -123,8 +139,9 @@ Duration) are translated (English/French) the same way `<rr0-ufo>`'s own labels
|
|
|
123
139
|
|
|
124
140
|
| Member | Kind | Description |
|
|
125
141
|
|---|---|---|
|
|
142
|
+
| `src` | attribute | URL of a `SightingRecordingJson` to open in the editor, fetched on connect and whenever the attribute changes |
|
|
126
143
|
| `sightingData` | property (get/set) | Delegates to the nested `<rr0-ufo>`'s `sightingData` |
|
|
127
|
-
| `appearance` | property (get/set, accepts a partial object on set) | `{ presetId: "oval" \| "
|
|
144
|
+
| `appearance` | property (get/set, accepts a partial object on set) | `{ presetId: "oval" \| "polygon", color: string, transparency: number, haloScale: number }` — the UFO's appearance used for the next recording |
|
|
128
145
|
|
|
129
146
|
## `<rr0-scene>` — 3D decor
|
|
130
147
|
|
|
@@ -208,10 +225,10 @@ URL (typically relative to the case's own page, same as `<rr0-ufo>`'s own `src`)
|
|
|
208
225
|
|
|
209
226
|
The two shapes are told apart automatically — a fetched JSON array is a manifest, a plain object is one witness's
|
|
210
227
|
own recording. No labels or ids are duplicated in a manifest itself — each witness's display name and the shared
|
|
211
|
-
case id grouping them together are read from that witness's *own* file (`
|
|
228
|
+
case id grouping them together are read from that witness's *own* file (`witness`/`caseId`, see
|
|
212
229
|
[Data format](#data-format)), so there's a single source of truth and nothing to drift out of sync. This means
|
|
213
230
|
every listed witness's recording is fetched upfront (to read its name), not lazily on selection — fine at the
|
|
214
|
-
scale a case's witness list actually has. If a witness has no `
|
|
231
|
+
scale a case's witness list actually has. If a witness has no `witness.title`, its `witness.id` is shown instead, or
|
|
215
232
|
the URL itself as a last resort. A mismatched `caseId` across the listed witnesses logs a console warning (doesn't
|
|
216
233
|
block) — likely means unrelated recordings got listed together by mistake.
|
|
217
234
|
|
|
@@ -229,13 +246,35 @@ selector loads that witness's already-fetched recording into the nested `<rr0-sc
|
|
|
229
246
|
`witnessUrls` again (e.g. a manifest refresh) keeps the current selection if that witness is still present, instead
|
|
230
247
|
of resetting back to the first.
|
|
231
248
|
|
|
232
|
-
Clicking "?" opens a panel
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
249
|
+
Clicking "?" opens a panel anchored under the button (it never shifts the canvas below it). Where the browser has
|
|
250
|
+
the popover API the panel is a top-layer `popover="auto"` — the one placement a host page's own `overflow: hidden`
|
|
251
|
+
wrapper cannot clip, which is what rr0.org's layout was doing to it — kept under the button by CSS anchor
|
|
252
|
+
positioning, flipping above it or centring in the viewport when that side is too short, and closing on Escape or a
|
|
253
|
+
click outside. Browsers without the API get the plain absolutely-positioned overlay instead.
|
|
254
|
+
|
|
255
|
+
Its main content is the currently-selected witness's observation metadata (date, location, case id, description,
|
|
256
|
+
tags — whichever are actually present in that witness's own `sighting.json`; the witness's own name isn't repeated
|
|
257
|
+
here, since it's already in the toolbar's testimony line). The date is shown on the WITNESS's own clock, never
|
|
258
|
+
converted into the reader's time zone (see `utcOffsetHours` in [Data format](#data-format)).
|
|
259
|
+
|
|
260
|
+
A footer row holds the app's own name/version on the left — linking to that very observation in the editor (see
|
|
261
|
+
[`<rr0-ufo-recorder>`](#rr0-ufo-recorder--full-editor)'s own `src`), not to the application's home page — and two
|
|
262
|
+
fold-outs on the right, both closed until asked for:
|
|
263
|
+
|
|
264
|
+
- **Embed** hands out the two self-contained lines it takes to put this observation on any other page, either as a
|
|
265
|
+
replay (`<rr0-eyewitness>`) or as the editor (`<rr0-ufo-recorder>`), with absolute URLs and a copy button:
|
|
266
|
+
|
|
267
|
+
```html
|
|
268
|
+
<script type="module" src="https://rr0.org/science/crypto/ufo/rr0-eyewitness.mjs"></script>
|
|
269
|
+
<rr0-eyewitness src="https://rr0.org/science/crypto/ufo/enquete/dossier/Socorro/sighting.json"></rr0-eyewitness>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The script URL is derived from where the running bundle was itself loaded from (`import.meta.url`), never
|
|
273
|
+
hardcoded, so a snippet generated from a local or staging copy points back at that copy. Pasting it into a site
|
|
274
|
+
of your own needs the bundle and the recording to be readable cross-origin (rr0.org serves
|
|
275
|
+
`/science/crypto/ufo/*` with `Access-Control-Allow-Origin: *` for exactly this).
|
|
276
|
+
- **Credits** reveals third-party credits (the live terrain imagery attribution, once a real relief patch has
|
|
277
|
+
resolved, plus the bundled thunder sound's own required attribution — see [`CREDITS.md`](CREDITS.md)).
|
|
239
278
|
|
|
240
279
|
All of this component's own labels (Testimony by, About, Close, Observation/Date/Location/Case, Credits) are
|
|
241
280
|
translated (English/French) the same way as `<rr0-ufo>`'s own labels.
|
|
@@ -250,10 +289,12 @@ interface SightingRecordingJson {
|
|
|
250
289
|
time?: { year?: number, month?: number, day?: number, hour?: number, minute?: number, second?: number }
|
|
251
290
|
endTime?: { year?: number, month?: number, day?: number, hour?: number, minute?: number, second?: number } // alternative to durationSeconds
|
|
252
291
|
durationSeconds?: number // alternative to endTime; takes precedence if both are set
|
|
292
|
+
utcOffsetHours?: number // the LEGAL time zone the witness's clock was on (+1 for France in 1965, -7 for New Mexico in April 1964). Absent = approximated from the longitude, which cannot know legal time or a daylight-saving switch
|
|
253
293
|
place?: { lat: number, lng: number }[]
|
|
254
|
-
|
|
255
|
-
witnessName?: string // for cases where the witness is already publicly named in the published material (e.g. Chiles-Whitted) — omit for anonymous witnesses
|
|
294
|
+
witness?: { id?: string, dirName?: string, title?: string, lastName?: string, firstNames?: string[] } // every field optional — supply whichever is known; omit entirely for an anonymous witness
|
|
256
295
|
caseId?: string // shared by every witness's own sighting.json for the same case — see <rr0-eyewitness>
|
|
296
|
+
description?: string
|
|
297
|
+
tags?: string[]
|
|
257
298
|
timeline: {
|
|
258
299
|
keyframes: Array<{
|
|
259
300
|
t: number // milliseconds since recording start
|
|
@@ -267,14 +308,35 @@ interface SightingRecordingJson {
|
|
|
267
308
|
transparency: number // 0 = opaque, 1 = fully transparent
|
|
268
309
|
haloScale: number // 0 = no glow
|
|
269
310
|
selected: boolean
|
|
311
|
+
title?: string // shown as an on-canvas tooltip when hovered
|
|
312
|
+
physical?: { sizeM: number, distanceM: number } // what the witness reported — see Apparent size
|
|
270
313
|
points?: { x: number, y: number }[] // "polygon" shapes only
|
|
271
314
|
}
|
|
272
315
|
}>
|
|
273
316
|
}>
|
|
317
|
+
order?: string[] // back-to-front paint/hit-test order; absent = first-appearance order
|
|
318
|
+
groups?: string[][] // each inner array is one group's member sourceIds
|
|
274
319
|
}
|
|
320
|
+
witnessTrack?: { keyframes: Array<{ t: number, pose: { lat?: number, lng?: number, elevationM: number, headingDeg?: number, pitchDeg: number, fovDeg: number } }> }
|
|
321
|
+
weatherTrack?: { keyframes: Array<{ t: number, weather: Weather }> }
|
|
322
|
+
weather?: Weather // legacy static fallback for recordings predating weatherTrack
|
|
323
|
+
decor?: DecorObject[] // buildings, trees, streetlights, vehicles, other witnesses — see src/engine/model/Decor.ts
|
|
275
324
|
}
|
|
276
325
|
```
|
|
277
326
|
|
|
327
|
+
A shape left out of a later keyframe is **held** at its last recorded state, not hidden — and one whose first
|
|
328
|
+
keyframe is at `t=5000` is already painted, in that state, from `t=0` (hold-first/hold-last at both ends of a
|
|
329
|
+
source's own range). To make something stop being visible, keyframe it with `transparency: 1`.
|
|
330
|
+
|
|
331
|
+
### Apparent size
|
|
332
|
+
|
|
333
|
+
`physical` is the witness's own reported size and distance, and the on-screen `bounds.width` is derived from it
|
|
334
|
+
rather than drawn by eye: on the 640x360 canvas at the default 60° vertical field of view, one degree is about
|
|
335
|
+
5.4px and the full Moon about 2.8px, so an object of 3.5m at 90m is 12px wide — not the 90px an author reaches for
|
|
336
|
+
unaided. `ApparentSize` (`src/engine/shape/ApparentSize.ts`) does the conversion both ways, and the editor's own
|
|
337
|
+
**Real size** / **Distance** fields apply it and read back what the current drawing actually spans, in degrees and
|
|
338
|
+
in full Moons.
|
|
339
|
+
|
|
278
340
|
This format is deliberately independent of [`@rr0/data`](https://github.com/RR0/data)'s `RR0Event`/`@rr0/time`'s
|
|
279
341
|
`Level2Date`/`@rr0/place`'s `Place` classes, even though its `time`/`place` fields are structurally aligned with
|
|
280
342
|
them — importing those classes into browser-bundled code pulls in Node-only file-scanning dependencies that break a
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e={oval:"Ovale",polygon:"Polygone",addVertex:"Ajouter un sommet",deleteVertex:"Supprimer le sommet",notAPolygon:"Sélectionnez une seule forme polygonale",tooFewVertices:"Une forme a besoin d'au moins 3 points",color:"Couleur",transparency:"Transparence",halo:"Halo",shape:"Forme",shapeTitle:"Nom",objectSize:"Taille réelle",objectDistance:"Distance",apparentSize:"soit {deg}° — {moons}× la Lune",utcOffset:"Fuseau horaire",utcOffsetPlaceholder:"d'après la longitude",objectSizePlaceholder:"largeur rapportée",objectDistancePlaceholder:"distance rapportée",addShape:"Ajouter une forme",deleteShape:"Supprimer la forme",play:"Lecture",pause:"Pause",noDuration:"Aucune durée d'observation",autoReplay:"Lecture automatique",group:"Grouper",ungroup:"Dégrouper",bringToFront:"Placer devant",sendToBack:"Placer derrière",contextMenuDelete:"Supprimer",confirmDeleteShape:"Supprimer {name} ? Cette action est irréversible.",confirmDeleteShapes:"Supprimer {count} formes ? Cette action est irréversible.",onlyOneShape:"Il n'y a qu'une seule forme",alreadyAtFront:"Cette forme est déjà la plus en avant",alreadyAtBack:"Cette forme est déjà la plus en arrière",needTwoShapesToGroup:"Sélectionnez au moins deux formes pour les grouper",notGrouped:"Cette forme ne fait pas partie d'un groupe",multipleShapesSelected:"Plusieurs formes sélectionnées — sélectionnez-en une seule pour modifier ceci",samplingRate:"Échantillonnage",duration:"Durée",durationPlaceholder:"durée de l'observation",durationImprecise:"Ces dates ne permettent pas de déterminer précisément une durée — saisissez-la manuellement.",export:"Exporter le JSON",importFile:"Charger un fichier JSON",importUrl:"Ou charger depuis une URL",importUrlPlaceholder:"https://…/sighting.json",importButton:"Charger",importError:"Impossible de charger cet enregistrement — vérifiez le fichier ou l'URL et réessayez.",record:"Enregistrer",stop:"Arrêter",latitude:"Latitude",longitude:"Longitude",heading:"Orientation",headingPlaceholder:"inconnu",pitch:"Inclinaison",observationTime:"Début de l'observation",observationEndTime:"Fin de l'observation",edtfInvalid:"Date/heure EDTF invalide (ex. 1965-07-01T05:00, 2025-06?, 2025~).",edtfPlaceholder:"AAAA-MM-JJThh:mm[?~%] ou hh:mm",observationTimeHint:"EDTF — ex. 1965-07-01T05:00, 2025-06? (incertain), 2025~ (approximatif), ou juste 05:00 si la date est inconnue",observationEndTimeHint:"EDTF — ex. 1965-07-01T05:10, 2025-06? (incertain), 2025~ (approximatif), ou juste 05:10 si la date est inconnue",presetsGroupLabel:"Forme de l'ovni",witnessId:"ID témoin",witnessDirName:"Répertoire du témoin",witnessTitle:"Titre du témoin",witnessLastName:"Nom de famille du témoin",witnessFirstNames:"Prénoms du témoin",caseId:"ID de l'affaire",description:"Description",tags:"Mots-clés",tagsPlaceholder:"séparés par des virgules",weather:"Météo",shapeGroup:"Forme",temporalGroup:"Temporel",locationGroup:"Lieu",observationGroup:"Observation",witnessGroup:"Témoin",circumstancesGroup:"Circonstances",cloudCover:"Couverture nuageuse",cloudDarkness:"Obscurité des nuages",precipitationType:"Précipitations",precipitationNone:"Aucune",precipitationRain:"Pluie",precipitationSnow:"Neige",precipitationHail:"Grêle",precipitationIntensity:"Intensité",windDirection:"Direction du vent",windSpeed:"Force du vent",storm:"Orage",lensFlareBrightness:"Intensité lumineuse",cameraDevice:"Appareil photo/vidéo",decor:"Décor",decorBuilding:"Bâtiment",decorTree:"Arbre",decorStreetlight:"Lampadaire",decorVehicle:"Véhicule",decorWitness:"Autre témoin",addDecor:"Ajouter",deleteDecor:"Supprimer l'élément de décor",decorEast:"Distance à l'est",decorNorth:"Distance au nord",decorHeading:"Orientation",decorLit:"Allumé",decorTitle:"Nom",decorSightingUrl:"URL de l'enregistrement du témoin",viewTestimony:"Voir le témoignage",noWitnessRecording:"Aucune URL d'enregistrement définie pour ce témoin",masks:"Masque",addWitness:"Ajouter un témoin",decorFloors:"Étages",decorOccupiedFloor:"Étage occupé",decorWitnessSide:"Emplacement du témoin",decorWitnessSideNone:"Non présent",decorWindows:"Fenêtres",decorSideFront:"Avant",decorSideBehind:"Arrière",decorSideLeft:"Gauche",decorSideRight:"Droite",decorSideFrontLeft:"Avant-gauche",decorSideFrontRight:"Avant-droit",decorSideBehindLeft:"Arrière-gauche",decorSideBehindRight:"Arrière-droit"};export{e as ufoRecorderMessages_fr};
|
|
@@ -261,11 +261,15 @@ input.invalid {
|
|
|
261
261
|
}
|
|
262
262
|
/* Record and the sampling rate it records at are one control, not two: grouping them makes them a
|
|
263
263
|
single flex item of the wrapping toolbar, so they stay on the same line together wherever that
|
|
264
|
-
line happens to break, instead of the button drifting up next to whatever field precedes it.
|
|
264
|
+
line happens to break, instead of the button drifting up next to whatever field precedes it.
|
|
265
|
+
flex-basis: 100% then gives them that line outright, always — the pair is wider than whatever
|
|
266
|
+
the appearance fields leave at the end of theirs, so it was going to wrap anyway; taking the
|
|
267
|
+
whole line makes that a deliberate row rather than a break that moves with the window width. */
|
|
265
268
|
.record-row {
|
|
266
269
|
display: flex;
|
|
267
270
|
align-items: center;
|
|
268
271
|
gap: 0.5em;
|
|
272
|
+
flex-basis: 100%;
|
|
269
273
|
}
|
|
270
274
|
details {
|
|
271
275
|
border: 1px solid #444;
|
|
@@ -691,7 +695,7 @@ input[type=range] {
|
|
|
691
695
|
pointer-events: none;
|
|
692
696
|
white-space: nowrap;
|
|
693
697
|
}
|
|
694
|
-
`;class fs{static MOON_ANGULAR_WIDTH_DEG=.5237;static angularWidthDeg(e){return 2*Math.atan(e.sizeM/(2*e.distanceM))*180/Math.PI}static widthPx(e,t,n){return t*e.sizeM/(2*e.distanceM*Math.tan(n*Math.PI/360))}static pxToDeg(e,t,n){return 2*Math.atan(e*Math.tan(n*Math.PI/360)/t)*180/Math.PI}static inMoons(e){return e/fs.MOON_ANGULAR_WIDTH_DEG}static lerp(e,t,n){if(!(!e||!t))return{sizeM:e.sizeM+(t.sizeM-e.sizeM)*n,distanceM:e.distanceM+(t.distanceM-e.distanceM)*n}}}function I0(i,e="#39ff14"){return{kind:"oval",bounds:i,color:e,angle:0,transparency:0,haloScale:0,selected:!1}}function R0(i,e,t="#39ff14"){return{kind:"polygon",bounds:i,points:e,color:t,angle:0,transparency:0,haloScale:0,selected:!1}}function C0(i,e="#39ff14"){const{width:t,height:n}=i;return R0(i,[{x:0,y:0},{x:t,y:0},{x:t,y:n},{x:0,y:n}],e)}const D0={oval:I0,polygon:C0};function Gc(i,e){return{...D0[e.presetId](i,e.color),transparency:e.transparency,haloScale:e.haloScale}}function P0(i,e,t){const{bounds:n}=i;return e>=n.x&&e<=n.x+n.width&&t>=n.y&&t<=n.y+n.height}function Yd(i,e,t){const n=i.bounds.width,s=i.bounds.height;return{...i,bounds:{x:e-n/2,y:t-s/2,width:n,height:s}}}function Qt(i,e,t){return i+(e-i)*t}function Hc(i){const e=/^#([0-9a-f]{6})$/i.exec(i);if(!e)return;const t=parseInt(e[1],16);return[t>>16&255,t>>8&255,t&255]}function L0(i){return`#${i.map(e=>Math.round(e).toString(16).padStart(2,"0")).join("")}`}function N0(i,e,t){const n=Hc(i),s=Hc(e);return!n||!s?t<1?i:e:L0([Qt(n[0],s[0],t),Qt(n[1],s[1],t),Qt(n[2],s[2],t)])}function U0(i,e,t){const n={x:Qt(i.bounds.x,e.bounds.x,t),y:Qt(i.bounds.y,e.bounds.y,t),width:Qt(i.bounds.width,e.bounds.width,t),height:Qt(i.bounds.height,e.bounds.height,t)},s=Qt(i.angle,e.angle,t),r=Qt(i.transparency,e.transparency,t),a=Qt(i.haloScale,e.haloScale,t),o=N0(i.color,e.color,t),l=fs.lerp(i.physical,e.physical,t);return i.kind==="polygon"&&e.kind==="polygon"&&i.points.length===e.points.length?{...i,bounds:n,angle:s,transparency:r,haloScale:a,color:o,physical:l,points:i.points.map((c,h)=>({x:Qt(c.x,e.points[h].x,t),y:Qt(c.y,e.points[h].y,t)}))}:{...t<1?i:e,bounds:n,angle:s,transparency:r,haloScale:a,color:o,physical:l}}class wa{keyframes=[];order=[];groups=[];addKeyframe(e,t){const n=this.findInsertIndex(e);if(this.keyframes[n]?.t===e){const s=new Set(t.map(r=>r.sourceId));this.keyframes[n]={t:e,shapes:[...this.keyframes[n].shapes.filter(r=>!s.has(r.sourceId)),...t]}}else this.keyframes.splice(n,0,{t:e,shapes:[...t]});for(const s of t)this.order.includes(s.sourceId)||this.order.push(s.sourceId)}findInsertIndex(e){let t=0,n=this.keyframes.length;for(;t<n;){const s=t+n>>>1;this.keyframes[s].t<e?t=s+1:n=s}return t}getKeyframeAt(e){const t=this.findInsertIndex(e),n=this.keyframes[t];return n?.t===e?n:void 0}getShapeAt(e,t){return this.getKeyframeAt(e)?.shapes.find(n=>n.sourceId===t)?.shape}getLatestShapeAt(e,t){let n=this.findInsertIndex(e);for(this.keyframes[n]?.t!==e&&(n-=1);n>=0;n--){const s=this.keyframes[n].shapes.find(r=>r.sourceId===t);if(s)return s.shape}}getInterpolatedShapeAt(e,t){const n=this.findShapeAtOrBefore(e,t);if(n?.t===e)return n.shape;const s=this.findShapeAtOrAfter(e,t);return n?s?U0(n.shape,s.shape,(e-n.t)/(s.t-n.t)):n.shape:s?.shape}findShapeAtOrBefore(e,t){let n=this.findInsertIndex(e);for(this.keyframes[n]?.t!==e&&(n-=1);n>=0;n--){const s=this.keyframes[n].shapes.find(r=>r.sourceId===t);if(s)return{t:this.keyframes[n].t,shape:s.shape}}}findShapeAtOrAfter(e,t){for(let n=this.findInsertIndex(e);n<this.keyframes.length;n++){const s=this.keyframes[n].shapes.find(r=>r.sourceId===t);if(s)return{t:this.keyframes[n].t,shape:s.shape}}}hitTest(e,t,n,s){const r=this.sourceIds;for(let a=r.length-1;a>=0;a--){if(s?.has(r[a]))continue;const o=this.getInterpolatedShapeAt(e,r[a]);if(o&&P0(o,t,n))return{sourceId:r[a],shape:o}}}removeSource(e){for(let t=this.keyframes.length-1;t>=0;t--){const n=this.keyframes[t].shapes.filter(s=>s.sourceId!==e);n.length===0?this.keyframes.splice(t,1):n.length!==this.keyframes[t].shapes.length&&(this.keyframes[t]={t:this.keyframes[t].t,shapes:n})}this.order=this.order.filter(t=>t!==e),this.removeFromGroup(e)}group(e){if(!(e.length<2)){for(const t of e)this.removeFromGroup(t);this.groups.push([...e])}}ungroup(e){const t=this.groups.findIndex(n=>n.includes(e));t!==-1&&this.groups.splice(t,1)}groupMembers(e){return this.groups.find(t=>t.includes(e))}removeFromGroup(e){const t=this.groups.findIndex(s=>s.includes(e));if(t===-1)return;const n=this.groups[t].filter(s=>s!==e);n.length<2?this.groups.splice(t,1):this.groups[t]=n}bringToFront(e){const t=this.order.indexOf(e);t!==-1&&(this.order.splice(t,1),this.order.push(e))}sendToBack(e){const t=this.order.indexOf(e);t!==-1&&(this.order.splice(t,1),this.order.unshift(e))}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get sourceIds(){return[...this.order]}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes,order:this.order,groups:this.groups}}static fromJSON(e){const t=new wa;for(const n of e.keyframes)t.addKeyframe(n.t,n.shapes);if(e.order){const n=t.order.filter(s=>!e.order.includes(s));t.order=[...e.order,...n]}return e.groups&&(t.groups=e.groups.map(n=>n.filter(s=>t.order.includes(s))).filter(n=>n.length>=2)),t}}function F0(i,e,t){return Math.max(e,Math.min(t,i))}function O0(i,e,t){const n=((e-i)%360+540)%360-180;return((i+n*t)%360+360)%360}function Ts(i,e,t){return i+(e-i)*t}function B0(i,e,t){return{lat:i.lat===void 0||e.lat===void 0?void 0:Ts(i.lat,e.lat,t),lng:i.lng===void 0||e.lng===void 0?void 0:Ts(i.lng,e.lng,t),elevationM:Ts(i.elevationM,e.elevationM,t),headingDeg:i.headingDeg===void 0||e.headingDeg===void 0?void 0:O0(i.headingDeg,e.headingDeg,t),pitchDeg:Ts(i.pitchDeg,e.pitchDeg,t),fovDeg:Ts(i.fovDeg,e.fovDeg,t)}}class Js{keyframes=[];addKeyframe(e,t){const n=this.findInsertIndex(e);this.keyframes[n]?.t===e?this.keyframes[n]={t:e,pose:t}:this.keyframes.splice(n,0,{t:e,pose:t})}clear(){this.keyframes.length=0}removeKeyframeAt(e){const t=this.findInsertIndex(e);this.keyframes[t]?.t===e&&this.keyframes.splice(t,1)}findInsertIndex(e){let t=0,n=this.keyframes.length;for(;t<n;){const s=t+n>>>1;this.keyframes[s].t<e?t=s+1:n=s}return t}getLatestPoseAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].pose:void 0}getInterpolatedPoseAt(e){const t=this.findInsertIndex(e),n=this.keyframes[t]?.t===e?this.keyframes[t]:this.keyframes[t-1];if(n?.t===e)return n.pose;const s=this.keyframes[t]?.t===e?void 0:this.keyframes[t];return n?s?B0(n.pose,s.pose,F0((e-n.t)/(s.t-n.t),0,1)):n.pose:s?.pose}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes}}static fromJSON(e){const t=new Js;for(const n of e.keyframes)t.addKeyframe(n.t,n.pose);return t}}function cr(i,e,t){return i+(e-i)*t}function z0(i,e,t){const n=((e-i)%360+540)%360-180;return((i+n*t)%360+360)%360}function k0(i,e,t){return Math.max(e,Math.min(t,i))}function G0(i,e,t){return{cloudCover:cr(i.cloudCover,e.cloudCover,t),cloudDarkness:cr(i.cloudDarkness,e.cloudDarkness,t),precipitationType:t<1?i.precipitationType:e.precipitationType,precipitationIntensity:cr(i.precipitationIntensity,e.precipitationIntensity,t),windDirectionDeg:z0(i.windDirectionDeg,e.windDirectionDeg,t),windSpeed:cr(i.windSpeed,e.windSpeed,t),storm:t<1?i.storm:e.storm}}class Qs{keyframes=[];addKeyframe(e,t){const n=this.findInsertIndex(e);this.keyframes[n]?.t===e?this.keyframes[n]={t:e,weather:t}:this.keyframes.splice(n,0,{t:e,weather:t})}clear(){this.keyframes.length=0}removeKeyframeAt(e){const t=this.findInsertIndex(e);this.keyframes[t]?.t===e&&this.keyframes.splice(t,1)}findInsertIndex(e){let t=0,n=this.keyframes.length;for(;t<n;){const s=t+n>>>1;this.keyframes[s].t<e?t=s+1:n=s}return t}getLatestWeatherAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].weather:void 0}getInterpolatedWeatherAt(e){const t=this.findInsertIndex(e),n=this.keyframes[t]?.t===e?this.keyframes[t]:this.keyframes[t-1];if(n?.t===e)return n.weather;const s=this.keyframes[t]?.t===e?void 0:this.keyframes[t];return n?s?G0(n.weather,s.weather,k0((e-n.t)/(s.t-n.t),0,1)):n.weather:s?.weather}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes}}static fromJSON(e){const t=new Qs;for(const n of e.keyframes)t.addKeyframe(n.t,n.weather);return t}}const qd={cloudCover:0,cloudDarkness:0,precipitationType:"none",precipitationIntensity:0,windDirectionDeg:0,windSpeed:0,storm:!1},H0=/^(?:(?<year>\d{4}|\d{3}X|\d{2}XX)(-(?<month>0[1-9]|1[0-2]))?(-(?<day>0[1-9]|[12]\d|3[01]))?(?:[T ](?<hour>[01]?\d|2[0-3]):(?<minute>[0-5]\d)(?::(?<second>[0-5]\d))?)?|(?<timeOnlyHour>[01]?\d|2[0-3]):(?<timeOnlyMinute>[0-5]\d)(?::(?<timeOnlySecond>[0-5]\d))?)[?~%]?$/;function Vc(i){const e=H0.exec(i.trim());if(!e?.groups)return;const{year:t,month:n,day:s,hour:r,minute:a,second:o,timeOnlyHour:l,timeOnlyMinute:c,timeOnlySecond:h}=e.groups,u=r??l,d=a??c,p=o??h;return{year:t&&!t.includes("X")?Number(t):void 0,month:n?Number(n):void 0,day:s?Number(s):void 0,hour:u?Number(u):void 0,minute:d?Number(d):void 0,second:p?Number(p):void 0,raw:i.trim()}}function Wc(i){if(i.raw!==void 0)return i.raw;if(i.year===void 0){if(i.hour===void 0)return"";let t=`${String(i.hour).padStart(2,"0")}:${String(i.minute??0).padStart(2,"0")}`;return i.second!==void 0&&(t+=`:${String(i.second).padStart(2,"0")}`),t}let e=String(i.year).padStart(4,"0");return i.month===void 0||(e+=`-${String(i.month).padStart(2,"0")}`,i.day===void 0)||(e+=`-${String(i.day).padStart(2,"0")}`,i.hour===void 0)||(e+=`T${String(i.hour).padStart(2,"0")}:${String(i.minute??0).padStart(2,"0")}`,i.second!==void 0&&(e+=`:${String(i.second).padStart(2,"0")}`)),e}function Oo(i){if(i.year!==void 0)return Date.UTC(i.year,(i.month??1)-1,i.day??1,i.hour??0,i.minute??0,i.second??0)}function Xc(i){return(((i.day??0)*24+(i.hour??0))*60+(i.minute??0))*6e4+(i.second??0)*1e3}const V0=["year","month","day","hour","minute"],W0=["day","hour","minute"];function Yc(i,e){return e.filter(t=>i[t]!==void 0).join(",")}function lc(i){if(i.durationSeconds!==void 0)return i.durationSeconds*1e3;if(!i.time||!i.endTime)return;const e=i.time.year!==void 0&&i.endTime.year!==void 0,t=e?V0:W0,n=Yc(i.time,t);if(!(n===""||n!==Yc(i.endTime,t)))return e?Oo(i.endTime)-Oo(i.time):Xc(i.endTime)-Xc(i.time)}function X0(i){if(i.durationSeconds===void 0&&!(!i.time||!i.endTime))return lc(i)===void 0?"imprecise":void 0}class Ta{constructor(e,t,n,s,r,a,o,l=[]){this.event=e,this.timeline=t,this.witnessTrack=n,this.weatherTrack=s,this.witness=r,this.caseId=a,this.weather=o,this.decor=l}static create(e,t,n){return new Ta({eventType:"sighting",time:e,place:t},new wa,new Js,new Qs,n)}}const Y0=0,q0=0,$0=60;function Bo(i,e){const t=i.witnessTrack.getInterpolatedPoseAt(e);if(t)return t;const n=i.event.place?.[0];if(n)return{lat:n.lat,lng:n.lng,elevationM:Y0,headingDeg:void 0,pitchDeg:q0,fovDeg:$0}}function zo(i,e){return i.weatherTrack.getInterpolatedWeatherAt(e)??i.weather??qd}class K0{constructor(e,t){this.timeline=e,this.onFrame=t}rafId=null;state="stopped";currentT=0;lastWallTime=0;playbackRate=1;loop=!1;durationOverrideMs=0;get seekableDuration(){return Math.max(this.timeline.duration,this.durationOverrideMs)}play(){if(this.state==="playing")return;this.currentT>=this.seekableDuration&&(this.currentT=0,this.resolveFrame(0)),this.state="playing",this.lastWallTime=performance.now();const e=()=>{if(this.state!=="playing")return;const t=performance.now();if(this.currentT+=(t-this.lastWallTime)*this.playbackRate,this.lastWallTime=t,this.currentT>=this.seekableDuration){if(this.loop&&this.seekableDuration>0){this.currentT%=this.seekableDuration,this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(e);return}this.currentT=this.seekableDuration,this.stop(),this.resolveFrame(this.currentT);return}this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(e)};this.rafId=requestAnimationFrame(e)}pause(){this.state==="playing"&&(this.state="paused",this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null))}stop(){this.state="stopped",this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null)}seek(e){this.currentT=Math.max(0,Math.min(e,this.seekableDuration)),this.resolveFrame(this.currentT)}get playbackState(){return this.state}get time(){return this.currentT}resolveFrame(e){const t=new Map;for(const n of this.timeline.sourceIds){const s=this.timeline.getInterpolatedShapeAt(e,n);s&&t.set(n,s)}this.onFrame(e,t)}}const $d=["nw","n","ne","e","se","s","sw","w"],Z0=24,ri=8,Kd=3,J0=8;function Hs(i,e,t){const n=i.x-e.x,s=i.y-e.y,r=Math.cos(t),a=Math.sin(t);return{x:e.x+n*r-s*a,y:e.y+n*a+s*r}}function Si(i){return{x:i.x+i.width/2,y:i.y+i.height/2}}const Q0={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 Ke{static handlePointsFor(e){const{x:t,y:n,width:s,height:r}=e.bounds,a={nw:{x:t,y:n},n:{x:t+s/2,y:n},ne:{x:t+s,y:n},e:{x:t+s,y:n+r/2},se:{x:t+s,y:n+r},s:{x:t+s/2,y:n+r},sw:{x:t,y:n+r},w:{x:t,y:n+r/2},rotate:{x:t+s/2,y:n-Z0}},o=Si(e.bounds),l={};for(const c of Object.keys(a))l[c]=Hs(a[c],o,e.angle);return l}static hitTestHandle(e,t,n=8,s=[...$d,"rotate"]){const r=Ke.handlePointsFor(e);for(const a of s)if(Math.hypot(t.x-r[a].x,t.y-r[a].y)<=n)return a}static HANDLE_DIRECTION_DEG={e:0,se:45,s:90,sw:135,w:180,nw:225,n:270,ne:315};static resizeAxisFor(e,t){const s=((Ke.HANDLE_DIRECTION_DEG[e]+t*180/Math.PI)%180+180)%180/45,r=["ew","nwse","ns","nesw"];return r[Math.round(s)%r.length]}static resizeBounds(e,t,n,s){const r=Si(e),a=Hs(s,r,-t),{x:o,y:l,width:c,height:h}=e,u=Q0[n];let d=o,p=l,v=o+c,y=l+h;return u.left&&(d=Math.min(a.x,v-ri)),u.right&&(v=Math.max(a.x,d+ri)),u.top&&(p=Math.min(a.y,y-ri)),u.bottom&&(y=Math.max(a.y,p+ri)),{x:d,y:p,width:v-d,height:y-p}}static resizeShape(e,t,n){if(t==="rotate")throw new Error("resizeShape does not accept the rotate handle");const{width:s,height:r}=e.bounds,a=Ke.resizeBounds(e.bounds,e.angle,t,n);if(e.kind==="oval")return{...e,bounds:a};const o=s===0?1:a.width/s,l=r===0?1:a.height/r;return{...e,bounds:a,points:e.points.map(c=>({x:c.x*o,y:c.y*l}))}}static rotateShape(e,t){const n=Si(e.bounds),s=Math.atan2(t.y-n.y,t.x-n.x)+Math.PI/2;return{...e,angle:s}}static groupBoundsFor(e){const t=Math.min(...e.map(a=>a.x)),n=Math.min(...e.map(a=>a.y)),s=Math.max(...e.map(a=>a.x+a.width)),r=Math.max(...e.map(a=>a.y+a.height));return{x:t,y:n,width:s-t,height:r-n}}static vertexPointsFor(e){const t=Si(e.bounds);return e.points.map(n=>Hs({x:e.bounds.x+n.x,y:e.bounds.y+n.y},t,e.angle))}static hitTestVertex(e,t,n=J0){const s=Ke.vertexPointsFor(e);let r,a=n;return s.forEach((o,l)=>{const c=Math.hypot(t.x-o.x,t.y-o.y);c<=a&&(a=c,r=l)}),r}static toLocalPoint(e,t){const n=Si(e.bounds),s=Hs(t,n,-e.angle);return{x:s.x-e.bounds.x,y:s.y-e.bounds.y}}static moveVertex(e,t,n){const s=e.points.map((u,d)=>d===t?Ke.toLocalPoint(e,n):u),r=Math.min(...s.map(u=>u.x)),a=Math.min(...s.map(u=>u.y)),o=Math.max(...s.map(u=>u.x)),l=Math.max(...s.map(u=>u.y)),c={x:e.bounds.x+r,y:e.bounds.y+a,width:o-r,height:l-a},h=s.map(u=>({x:u.x-r,y:u.y-a}));return{...e,bounds:c,points:h}}static insertVertexNear(e,t){const n=Ke.toLocalPoint(e,t),{points:s}=e;let r=s.length-1,a=1/0;for(let l=0;l<s.length;l++){const c=s[l],h=s[(l+1)%s.length],u=Ke.distanceToSegment(n,c,h);u<a&&(a=u,r=l)}const o=[...s.slice(0,r+1),n,...s.slice(r+1)];return{...e,points:o}}static distanceToSegment(e,t,n){const s=n.x-t.x,r=n.y-t.y,a=s*s+r*r,o=a===0?0:Math.max(0,Math.min(1,((e.x-t.x)*s+(e.y-t.y)*r)/a)),l=t.x+o*s,c=t.y+o*r;return Math.hypot(e.x-l,e.y-c)}static deleteVertex(e,t){return e.points.length<=Kd?e:{...e,points:e.points.filter((n,s)=>s!==t)}}}class Ba{constructor(e){this.members=e}bounds(){return Ke.groupBoundsFor(this.members.map(e=>e.shape.bounds))}scaleTo(e){const t=this.bounds(),n=t.width===0?1:e.width/t.width,s=t.height===0?1:e.height/t.height;return this.members.map(({sourceId:r,shape:a})=>{const o=Math.max(a.bounds.width*n,ri),l=Math.max(a.bounds.height*s,ri),c=e.x+(a.bounds.x-t.x)*n,h=e.y+(a.bounds.y-t.y)*s,u={x:c,y:h,width:o,height:l};return a.kind==="oval"?{sourceId:r,shape:{...a,bounds:u}}:{sourceId:r,shape:{...a,bounds:u,points:a.points.map(d=>({x:d.x*n,y:d.y*s}))}}})}resize(e,t){return this.scaleTo(Ke.resizeBounds(this.bounds(),0,e,t))}rotate(e,t){const n=Si(this.bounds()),s=a=>Math.atan2(a.y-n.y,a.x-n.x),r=s(e)-s(t);return this.members.map(({sourceId:a,shape:o})=>{const l=Si(o.bounds),c=Hs(l,n,r),h={x:c.x-o.bounds.width/2,y:c.y-o.bounds.height/2,width:o.bounds.width,height:o.bounds.height};return{sourceId:a,shape:{...o,bounds:h,angle:o.angle+r}}})}}const ko=6,hr=ko/2,j0=20,qc=4;class ef{constructor(e){this.ctx=e}clear(e,t){this.ctx.clearRect(0,0,e,t)}paintShape(e){this.ctx.save(),this.ctx.globalAlpha=1-e.transparency,e.haloScale>0&&this.paintHalo(e),this.paintBase(e),this.ctx.restore(),e.selected&&this.paintSelectionHandles(e)}paintBase(e){if(this.ctx.fillStyle=e.color,this.ctx.beginPath(),e.kind==="oval"){const{x:t,y:n,width:s,height:r}=e.bounds,a=s/2,o=r/2;this.ctx.ellipse(t+a,n+o,a,o,e.angle,0,2*Math.PI)}else{const{x:t,y:n,width:s,height:r}=e.bounds;this.ctx.save(),this.ctx.translate(t+s/2,n+r/2),this.ctx.rotate(e.angle),this.ctx.translate(-s/2,-r/2),e.points.forEach((a,o)=>{o===0?this.ctx.moveTo(a.x,a.y):this.ctx.lineTo(a.x,a.y)}),this.ctx.closePath(),this.ctx.restore()}this.ctx.fill()}paintHalo(e){this.ctx.save(),this.ctx.shadowColor=e.color,this.ctx.shadowBlur=j0*e.haloScale,this.paintBase(e),this.ctx.restore()}paintSelectionHandles(e){this.paintHandleFrame(Ke.handlePointsFor(e),{includeRotate:!0}),e.kind==="polygon"&&this.paintVertexHandles(e)}paintVertexHandles(e){this.ctx.fillStyle="#39f";for(const t of Ke.vertexPointsFor(e))this.ctx.beginPath(),this.ctx.ellipse(t.x,t.y,qc,qc,0,0,2*Math.PI),this.ctx.fill()}paintMemberOutline(e){this.paintOutline(Ke.handlePointsFor(e))}paintGroupHandles(e){this.paintHandleFrame(Ke.handlePointsFor({bounds:e,angle:0}),{includeRotate:!0})}paintOutline(e){this.ctx.strokeStyle="lightgray",this.ctx.beginPath(),this.ctx.moveTo(e.nw.x,e.nw.y),this.ctx.lineTo(e.ne.x,e.ne.y),this.ctx.lineTo(e.se.x,e.se.y),this.ctx.lineTo(e.sw.x,e.sw.y),this.ctx.closePath(),this.ctx.stroke()}paintHandleFrame(e,{includeRotate:t}){this.paintOutline(e),this.ctx.fillStyle="lightgray";for(const n of $d){const s=e[n];this.ctx.fillRect(s.x-hr,s.y-hr,ko,ko)}t&&(this.ctx.beginPath(),this.ctx.moveTo(e.n.x,e.n.y),this.ctx.lineTo(e.rotate.x,e.rotate.y),this.ctx.stroke(),this.ctx.beginPath(),this.ctx.ellipse(e.rotate.x,e.rotate.y,hr+1,hr+1,0,0,2*Math.PI),this.ctx.fill())}}function tf(i){return{version:1,time:i.event.time,endTime:i.event.endTime,durationSeconds:i.event.durationSeconds,utcOffsetHours:i.event.utcOffsetHours,place:i.event.place,witness:i.witness,caseId:i.caseId,description:i.event.description,tags:i.event.tags,timeline:i.timeline.toJSON(),witnessTrack:i.witnessTrack.toJSON(),weatherTrack:i.weatherTrack.toJSON(),weather:i.weather,decor:i.decor}}function nf(i){return new Ta({eventType:"sighting",time:i.time,endTime:i.endTime,durationSeconds:i.durationSeconds,utcOffsetHours:i.utcOffsetHours,place:i.place,description:i.description,tags:i.tags},wa.fromJSON(i.timeline),i.witnessTrack?Js.fromJSON(i.witnessTrack):new Js,i.weatherTrack?Qs.fromJSON(i.weatherTrack):new Qs,i.witness,i.caseId,i.weather,i.decor??[])}function Aa(i,e){for(const t of i){const n=t.toLowerCase().split("-")[0];if(e.includes(n))return n}return"en"}const sf="modulepreload",rf=function(i,e){return new URL(i,e).href},$c={},ha=function(e,t,n){let s=Promise.resolve();if(t&&t.length>0){let a=function(h){return Promise.all(h.map(u=>Promise.resolve(u).then(d=>({status:"fulfilled",value:d}),d=>({status:"rejected",reason:d}))))};const o=document.getElementsByTagName("link"),l=document.querySelector("meta[property=csp-nonce]"),c=l?.nonce||l?.getAttribute("nonce");s=a(t.map(h=>{if(h=rf(h,n),h in $c)return;$c[h]=!0;const u=h.endsWith(".css"),d=u?'[rel="stylesheet"]':"";if(!!n)for(let y=o.length-1;y>=0;y--){const m=o[y];if(m.href===h&&(!u||m.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${h}"]${d}`))return;const v=document.createElement("link");if(v.rel=u?"stylesheet":sf,u||(v.as="script"),v.crossOrigin="",v.href=h,c&&v.setAttribute("nonce",c),document.head.appendChild(v),u)return new Promise((y,m)=>{v.addEventListener("load",y),v.addEventListener("error",()=>m(new Error(`Unable to preload CSS for ${h}`)))})}))}function r(a){const o=new Event("vite:preloadError",{cancelable:!0});if(o.payload=a,window.dispatchEvent(o),!o.defaultPrevented)throw a}return s.then(a=>{for(const o of a||[])o.status==="rejected"&&r(o.reason);return e().catch(r)})},Zd=["en","fr"],af={en:()=>ha(()=>Promise.resolve().then(()=>hf),void 0,import.meta.url).then(i=>i.ufoMessages_en),fr:()=>ha(()=>import("./UfoMessages_fr-NZrwNFCQ.js"),[],import.meta.url).then(i=>i.ufoMessages_fr)};function of(i){return af[i]()}const lf={en:()=>ha(()=>Promise.resolve().then(()=>oy),void 0,import.meta.url).then(i=>i.ufoRecorderMessages_en),fr:()=>ha(()=>import("./UfoRecorderMessages_fr-CUB6w4ij.js"),[],import.meta.url).then(i=>i.ufoRecorderMessages_fr)};function cf(i){return lf[i]()}const Jd={play:"Play",pause:"Pause",noDuration:"No observation duration",autoReplay:"Auto-replay",currentPosition:"Current position",duration:"Duration",fullscreen:"Fullscreen",exitFullscreen:"Exit fullscreen"},hf=Object.freeze(Object.defineProperty({__proto__:null,ufoMessages_en:Jd},Symbol.toStringTag,{value:"Module"})),Kc=new Set;class df extends HTMLElement{static get observedAttributes(){return["src"]}shadow;stageElement;canvas;canvasRenderer;tooltip;toolbar;playPauseButton;loopButton;fullscreenButton;seekInput;timeStartLabel;timeEndLabel;currentSighting=Ta.create();player;loopEnabled=!0;highlightedSourceIds=new Set;occludedSourceIds=Kc;enableClickToPlay=!0;fullscreenTarget;messages=Jd;realDurationMs;realStartMs;handleFullscreenChange=()=>this.updateFullscreenButton();handlePointerMove=e=>{const t=this.canvasPointFromEvent(e),n=t&&this.currentSighting.timeline.hitTest(this.currentTime,t.x,t.y,this.occludedSourceIds);if(!n?.shape.title){this.tooltip.hidden=!0;return}this.tooltip.textContent=n.shape.title,this.tooltip.hidden=!1;const s=this.stageElement.getBoundingClientRect();this.tooltip.style.left=`${e.clientX-s.left+12}px`,this.tooltip.style.top=`${e.clientY-s.top+12}px`};handlePointerLeave=()=>{this.tooltip.hidden=!0};constructor(){super(),this.shadow=this.attachShadow({mode:"open"});const e=document.createElement("template");e.innerHTML=`<style>${A0}</style>${T0}`,this.shadow.appendChild(e.content.cloneNode(!0)),this.stageElement=this.shadow.getElementById("stage"),this.canvas=this.shadow.getElementById("canvas"),this.canvasRenderer=new ef(this.canvas.getContext("2d")),this.tooltip=this.shadow.getElementById("tooltip"),this.toolbar=this.shadow.getElementById("toolbar"),this.playPauseButton=this.shadow.getElementById("play-pause"),this.loopButton=this.shadow.getElementById("loop"),this.fullscreenButton=this.shadow.getElementById("fullscreen"),this.seekInput=this.shadow.getElementById("seek"),this.timeStartLabel=this.shadow.getElementById("time-start"),this.timeEndLabel=this.shadow.getElementById("time-end"),this.fullscreenTarget=this.stageElement,this.playPauseButton.addEventListener("click",()=>this.togglePlayPause()),this.loopButton.addEventListener("click",()=>this.toggleLoop()),this.fullscreenButton.addEventListener("click",()=>this.toggleFullscreen()),this.seekInput.addEventListener("input",()=>this.player.seek(Number(this.seekInput.value))),this.canvas.addEventListener("click",()=>{this.enableClickToPlay&&this.togglePlayPause()}),this.canvas.addEventListener("pointermove",this.handlePointerMove),this.canvas.addEventListener("pointerleave",this.handlePointerLeave),document.addEventListener("fullscreenchange",this.handleFullscreenChange),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.updateFullscreenButton(),this.refresh(),this.loadLocaleMessages()}connectedCallback(){const e=this.getAttribute("src");e&&this.loadFromSrc(e)}disconnectedCallback(){document.removeEventListener("fullscreenchange",this.handleFullscreenChange)}attributeChangedCallback(e,t,n){e==="src"&&n&&n!==t&&this.isConnected&&this.loadFromSrc(n)}async loadFromSrc(e){const t=await fetch(e);this.sightingData=await t.json()}get sightingData(){return tf(this.currentSighting)}set sightingData(e){this.player.stop(),this.currentSighting=nf(e),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.refresh()}get sighting(){return this.currentSighting}get canvasElement(){return this.canvas}get renderer(){return this.canvasRenderer}get currentTime(){return this.player.time}set currentTime(e){this.player.seek(e)}get seekableDuration(){return this.player.seekableDuration}get autoReplayEnabled(){return this.loopEnabled}get positionLabel(){return this.timeStartLabel.textContent??""}get durationLabel(){return this.timeEndLabel.textContent??""}set showToolbar(e){this.toolbar.classList.toggle("hidden",!e)}get playbackState(){return this.player.playbackState}get selectedSourceIds(){return this.highlightedSourceIds}get durationSeconds(){return this.currentSighting.event.durationSeconds}set durationSeconds(e){this.currentSighting.event.durationSeconds=e,this.updateTimeLabels(),this.refresh(),this.updatePlayPauseButton()}set selectedSourceIds(e){const t=new Set(e);t.size===this.highlightedSourceIds.size&&[...t].every(s=>this.highlightedSourceIds.has(s))||(this.highlightedSourceIds=t,this.refresh())}setOccludedSourceIds(e){e.size===this.occludedSourceIds.size&&[...e].every(n=>this.occludedSourceIds.has(n))||(this.occludedSourceIds=e,this.refresh())}hasVisibleShapeAt(e,t){return this.currentSighting.timeline.hitTest(this.currentTime,e,t,this.occludedSourceIds)!==void 0}refresh(){this.seekInput.max=String(this.player.seekableDuration),this.player.seek(this.player.time)}canvasPointFromEvent(e){const t=this.canvas.getBoundingClientRect();if(!(t.width===0||t.height===0))return{x:(e.clientX-t.left)/t.width*this.canvas.width,y:(e.clientY-t.top)/t.height*this.canvas.height}}onFrame(e,t){this.canvasRenderer.clear(this.canvas.width,this.canvas.height);const n=this.playbackState!=="playing"?this.highlightedSourceIds:Kc;for(const[s,r]of t){if(this.occludedSourceIds.has(s))continue;const a=n.has(s);a&&n.size===1?this.canvasRenderer.paintShape({...r,selected:!0}):(this.canvasRenderer.paintShape(r),a&&this.canvasRenderer.paintMemberOutline(r))}if(n.size>1){const s=Ke.groupBoundsFor([...t].filter(([r])=>n.has(r)).map(([,r])=>r.bounds));this.canvasRenderer.paintGroupHandles(s)}this.seekInput.value=String(e),this.timeStartLabel.textContent=this.formatPosition(e),this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{time:e}}))}createPlayer(){const e=new K0(this.currentSighting.timeline,(t,n)=>this.onFrame(t,n));return e.loop=this.loopEnabled,e}togglePlayPause(){this.player.seekableDuration<=0||(this.player.playbackState==="playing"?(this.player.pause(),this.refresh()):this.player.play(),this.updatePlayPauseButton())}updatePlayPauseButton(){const e=this.player.playbackState==="playing";this.playPauseButton.textContent=e?"⏸":"▶";const t=this.player.seekableDuration>0;this.playPauseButton.disabled=!t;const n=t?e?this.messages.pause:this.messages.play:this.messages.noDuration;this.playPauseButton.title=n,this.playPauseButton.setAttribute("aria-label",n),this.toolbar.classList.toggle("auto-hide",e),this.fullscreenButton.classList.toggle("auto-hide",e)}toggleLoop(){this.loopEnabled=!this.loopEnabled,this.loopButton.setAttribute("aria-pressed",String(this.loopEnabled)),this.player.loop=this.loopEnabled}toggleFullscreen(){document.fullscreenElement?document.exitFullscreen():this.fullscreenTarget.requestFullscreen().catch(e=>{console.error("<rr0-ufo>: requestFullscreen() failed —",e)})}updateFullscreenButton(){const e=document.fullscreenElement===this.fullscreenTarget;this.fullscreenButton.title=e?this.messages.exitFullscreen:this.messages.fullscreen,this.fullscreenButton.setAttribute("aria-label",this.fullscreenButton.title)}async loadLocaleMessages(){const e=Aa(navigator.languages,Zd);e!=="en"&&this.applyMessages(await of(e))}applyMessages(e){this.messages=e,this.timeStartLabel.title=e.currentPosition,this.timeEndLabel.title=e.duration,this.loopButton.title=e.autoReplay,this.loopButton.setAttribute("aria-label",e.autoReplay),this.updatePlayPauseButton(),this.updateFullscreenButton()}updateTimeLabels(){const e=this.currentSighting.event,t=lc(e);this.realDurationMs=t!==void 0&&t>0?t:void 0,this.realStartMs=e.time?Oo(e.time):void 0;const n=this.currentSighting.timeline.duration;this.player.playbackRate=this.realDurationMs!==void 0&&n>0?n/this.realDurationMs:1,this.player.durationOverrideMs=n>0?0:this.realDurationMs??0,this.timeEndLabel.textContent=this.formatEndOfTimeline(),this.timeStartLabel.textContent=this.formatPosition(this.player.time)}formatPosition(e){if(this.realDurationMs===void 0)return dr(e);const t=this.currentSighting.timeline.duration,n=t>0&&e<=t?e/t*this.realDurationMs:e;return this.realStartMs!==void 0?Jc(Zc(this.realStartMs+n)):dr(n)}formatEndOfTimeline(){return this.realDurationMs===void 0?dr(this.currentSighting.timeline.duration):this.realStartMs!==void 0?Jc(Zc(this.realStartMs+this.realDurationMs)):dr(this.realDurationMs)}}function Zc(i){const e=new Date(i);return{hour:e.getUTCHours(),minute:e.getUTCMinutes(),second:e.getUTCSeconds()}}function Jc(i){if(i.hour===void 0)return"0:00";const e=t=>String(t).padStart(2,"0");return i.second?`${e(i.hour)}:${e(i.minute??0)}:${e(i.second)}`:`${e(i.hour)}:${e(i.minute??0)}`}function dr(i){const e=Math.round(i/1e3),t=Math.floor(e/60),n=e%60;return`${t}:${String(n).padStart(2,"0")}`}const Go="rr0-ufo";function Ia(){customElements.get(Go)||customElements.define(Go,df)}const uf=`
|
|
698
|
+
`;class fs{static MOON_ANGULAR_WIDTH_DEG=.5237;static angularWidthDeg(e){return 2*Math.atan(e.sizeM/(2*e.distanceM))*180/Math.PI}static widthPx(e,t,n){return t*e.sizeM/(2*e.distanceM*Math.tan(n*Math.PI/360))}static pxToDeg(e,t,n){return 2*Math.atan(e*Math.tan(n*Math.PI/360)/t)*180/Math.PI}static inMoons(e){return e/fs.MOON_ANGULAR_WIDTH_DEG}static lerp(e,t,n){if(!(!e||!t))return{sizeM:e.sizeM+(t.sizeM-e.sizeM)*n,distanceM:e.distanceM+(t.distanceM-e.distanceM)*n}}}function I0(i,e="#39ff14"){return{kind:"oval",bounds:i,color:e,angle:0,transparency:0,haloScale:0,selected:!1}}function R0(i,e,t="#39ff14"){return{kind:"polygon",bounds:i,points:e,color:t,angle:0,transparency:0,haloScale:0,selected:!1}}function C0(i,e="#39ff14"){const{width:t,height:n}=i;return R0(i,[{x:0,y:0},{x:t,y:0},{x:t,y:n},{x:0,y:n}],e)}const D0={oval:I0,polygon:C0};function Gc(i,e){return{...D0[e.presetId](i,e.color),transparency:e.transparency,haloScale:e.haloScale}}function P0(i,e,t){const{bounds:n}=i;return e>=n.x&&e<=n.x+n.width&&t>=n.y&&t<=n.y+n.height}function Yd(i,e,t){const n=i.bounds.width,s=i.bounds.height;return{...i,bounds:{x:e-n/2,y:t-s/2,width:n,height:s}}}function Qt(i,e,t){return i+(e-i)*t}function Hc(i){const e=/^#([0-9a-f]{6})$/i.exec(i);if(!e)return;const t=parseInt(e[1],16);return[t>>16&255,t>>8&255,t&255]}function L0(i){return`#${i.map(e=>Math.round(e).toString(16).padStart(2,"0")).join("")}`}function N0(i,e,t){const n=Hc(i),s=Hc(e);return!n||!s?t<1?i:e:L0([Qt(n[0],s[0],t),Qt(n[1],s[1],t),Qt(n[2],s[2],t)])}function U0(i,e,t){const n={x:Qt(i.bounds.x,e.bounds.x,t),y:Qt(i.bounds.y,e.bounds.y,t),width:Qt(i.bounds.width,e.bounds.width,t),height:Qt(i.bounds.height,e.bounds.height,t)},s=Qt(i.angle,e.angle,t),r=Qt(i.transparency,e.transparency,t),a=Qt(i.haloScale,e.haloScale,t),o=N0(i.color,e.color,t),l=fs.lerp(i.physical,e.physical,t);return i.kind==="polygon"&&e.kind==="polygon"&&i.points.length===e.points.length?{...i,bounds:n,angle:s,transparency:r,haloScale:a,color:o,physical:l,points:i.points.map((c,h)=>({x:Qt(c.x,e.points[h].x,t),y:Qt(c.y,e.points[h].y,t)}))}:{...t<1?i:e,bounds:n,angle:s,transparency:r,haloScale:a,color:o,physical:l}}class wa{keyframes=[];order=[];groups=[];addKeyframe(e,t){const n=this.findInsertIndex(e);if(this.keyframes[n]?.t===e){const s=new Set(t.map(r=>r.sourceId));this.keyframes[n]={t:e,shapes:[...this.keyframes[n].shapes.filter(r=>!s.has(r.sourceId)),...t]}}else this.keyframes.splice(n,0,{t:e,shapes:[...t]});for(const s of t)this.order.includes(s.sourceId)||this.order.push(s.sourceId)}findInsertIndex(e){let t=0,n=this.keyframes.length;for(;t<n;){const s=t+n>>>1;this.keyframes[s].t<e?t=s+1:n=s}return t}getKeyframeAt(e){const t=this.findInsertIndex(e),n=this.keyframes[t];return n?.t===e?n:void 0}getShapeAt(e,t){return this.getKeyframeAt(e)?.shapes.find(n=>n.sourceId===t)?.shape}getLatestShapeAt(e,t){let n=this.findInsertIndex(e);for(this.keyframes[n]?.t!==e&&(n-=1);n>=0;n--){const s=this.keyframes[n].shapes.find(r=>r.sourceId===t);if(s)return s.shape}}getInterpolatedShapeAt(e,t){const n=this.findShapeAtOrBefore(e,t);if(n?.t===e)return n.shape;const s=this.findShapeAtOrAfter(e,t);return n?s?U0(n.shape,s.shape,(e-n.t)/(s.t-n.t)):n.shape:s?.shape}findShapeAtOrBefore(e,t){let n=this.findInsertIndex(e);for(this.keyframes[n]?.t!==e&&(n-=1);n>=0;n--){const s=this.keyframes[n].shapes.find(r=>r.sourceId===t);if(s)return{t:this.keyframes[n].t,shape:s.shape}}}findShapeAtOrAfter(e,t){for(let n=this.findInsertIndex(e);n<this.keyframes.length;n++){const s=this.keyframes[n].shapes.find(r=>r.sourceId===t);if(s)return{t:this.keyframes[n].t,shape:s.shape}}}hitTest(e,t,n,s){const r=this.sourceIds;for(let a=r.length-1;a>=0;a--){if(s?.has(r[a]))continue;const o=this.getInterpolatedShapeAt(e,r[a]);if(o&&P0(o,t,n))return{sourceId:r[a],shape:o}}}removeSource(e){for(let t=this.keyframes.length-1;t>=0;t--){const n=this.keyframes[t].shapes.filter(s=>s.sourceId!==e);n.length===0?this.keyframes.splice(t,1):n.length!==this.keyframes[t].shapes.length&&(this.keyframes[t]={t:this.keyframes[t].t,shapes:n})}this.order=this.order.filter(t=>t!==e),this.removeFromGroup(e)}group(e){if(!(e.length<2)){for(const t of e)this.removeFromGroup(t);this.groups.push([...e])}}ungroup(e){const t=this.groups.findIndex(n=>n.includes(e));t!==-1&&this.groups.splice(t,1)}groupMembers(e){return this.groups.find(t=>t.includes(e))}removeFromGroup(e){const t=this.groups.findIndex(s=>s.includes(e));if(t===-1)return;const n=this.groups[t].filter(s=>s!==e);n.length<2?this.groups.splice(t,1):this.groups[t]=n}bringToFront(e){const t=this.order.indexOf(e);t!==-1&&(this.order.splice(t,1),this.order.push(e))}sendToBack(e){const t=this.order.indexOf(e);t!==-1&&(this.order.splice(t,1),this.order.unshift(e))}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get sourceIds(){return[...this.order]}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes,order:this.order,groups:this.groups}}static fromJSON(e){const t=new wa;for(const n of e.keyframes)t.addKeyframe(n.t,n.shapes);if(e.order){const n=t.order.filter(s=>!e.order.includes(s));t.order=[...e.order,...n]}return e.groups&&(t.groups=e.groups.map(n=>n.filter(s=>t.order.includes(s))).filter(n=>n.length>=2)),t}}function F0(i,e,t){return Math.max(e,Math.min(t,i))}function O0(i,e,t){const n=((e-i)%360+540)%360-180;return((i+n*t)%360+360)%360}function Ts(i,e,t){return i+(e-i)*t}function B0(i,e,t){return{lat:i.lat===void 0||e.lat===void 0?void 0:Ts(i.lat,e.lat,t),lng:i.lng===void 0||e.lng===void 0?void 0:Ts(i.lng,e.lng,t),elevationM:Ts(i.elevationM,e.elevationM,t),headingDeg:i.headingDeg===void 0||e.headingDeg===void 0?void 0:O0(i.headingDeg,e.headingDeg,t),pitchDeg:Ts(i.pitchDeg,e.pitchDeg,t),fovDeg:Ts(i.fovDeg,e.fovDeg,t)}}class Js{keyframes=[];addKeyframe(e,t){const n=this.findInsertIndex(e);this.keyframes[n]?.t===e?this.keyframes[n]={t:e,pose:t}:this.keyframes.splice(n,0,{t:e,pose:t})}clear(){this.keyframes.length=0}removeKeyframeAt(e){const t=this.findInsertIndex(e);this.keyframes[t]?.t===e&&this.keyframes.splice(t,1)}findInsertIndex(e){let t=0,n=this.keyframes.length;for(;t<n;){const s=t+n>>>1;this.keyframes[s].t<e?t=s+1:n=s}return t}getLatestPoseAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].pose:void 0}getInterpolatedPoseAt(e){const t=this.findInsertIndex(e),n=this.keyframes[t]?.t===e?this.keyframes[t]:this.keyframes[t-1];if(n?.t===e)return n.pose;const s=this.keyframes[t]?.t===e?void 0:this.keyframes[t];return n?s?B0(n.pose,s.pose,F0((e-n.t)/(s.t-n.t),0,1)):n.pose:s?.pose}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes}}static fromJSON(e){const t=new Js;for(const n of e.keyframes)t.addKeyframe(n.t,n.pose);return t}}function cr(i,e,t){return i+(e-i)*t}function z0(i,e,t){const n=((e-i)%360+540)%360-180;return((i+n*t)%360+360)%360}function k0(i,e,t){return Math.max(e,Math.min(t,i))}function G0(i,e,t){return{cloudCover:cr(i.cloudCover,e.cloudCover,t),cloudDarkness:cr(i.cloudDarkness,e.cloudDarkness,t),precipitationType:t<1?i.precipitationType:e.precipitationType,precipitationIntensity:cr(i.precipitationIntensity,e.precipitationIntensity,t),windDirectionDeg:z0(i.windDirectionDeg,e.windDirectionDeg,t),windSpeed:cr(i.windSpeed,e.windSpeed,t),storm:t<1?i.storm:e.storm}}class Qs{keyframes=[];addKeyframe(e,t){const n=this.findInsertIndex(e);this.keyframes[n]?.t===e?this.keyframes[n]={t:e,weather:t}:this.keyframes.splice(n,0,{t:e,weather:t})}clear(){this.keyframes.length=0}removeKeyframeAt(e){const t=this.findInsertIndex(e);this.keyframes[t]?.t===e&&this.keyframes.splice(t,1)}findInsertIndex(e){let t=0,n=this.keyframes.length;for(;t<n;){const s=t+n>>>1;this.keyframes[s].t<e?t=s+1:n=s}return t}getLatestWeatherAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].weather:void 0}getInterpolatedWeatherAt(e){const t=this.findInsertIndex(e),n=this.keyframes[t]?.t===e?this.keyframes[t]:this.keyframes[t-1];if(n?.t===e)return n.weather;const s=this.keyframes[t]?.t===e?void 0:this.keyframes[t];return n?s?G0(n.weather,s.weather,k0((e-n.t)/(s.t-n.t),0,1)):n.weather:s?.weather}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes}}static fromJSON(e){const t=new Qs;for(const n of e.keyframes)t.addKeyframe(n.t,n.weather);return t}}const qd={cloudCover:0,cloudDarkness:0,precipitationType:"none",precipitationIntensity:0,windDirectionDeg:0,windSpeed:0,storm:!1},H0=/^(?:(?<year>\d{4}|\d{3}X|\d{2}XX)(-(?<month>0[1-9]|1[0-2]))?(-(?<day>0[1-9]|[12]\d|3[01]))?(?:[T ](?<hour>[01]?\d|2[0-3]):(?<minute>[0-5]\d)(?::(?<second>[0-5]\d))?)?|(?<timeOnlyHour>[01]?\d|2[0-3]):(?<timeOnlyMinute>[0-5]\d)(?::(?<timeOnlySecond>[0-5]\d))?)[?~%]?$/;function Vc(i){const e=H0.exec(i.trim());if(!e?.groups)return;const{year:t,month:n,day:s,hour:r,minute:a,second:o,timeOnlyHour:l,timeOnlyMinute:c,timeOnlySecond:h}=e.groups,u=r??l,d=a??c,p=o??h;return{year:t&&!t.includes("X")?Number(t):void 0,month:n?Number(n):void 0,day:s?Number(s):void 0,hour:u?Number(u):void 0,minute:d?Number(d):void 0,second:p?Number(p):void 0,raw:i.trim()}}function Wc(i){if(i.raw!==void 0)return i.raw;if(i.year===void 0){if(i.hour===void 0)return"";let t=`${String(i.hour).padStart(2,"0")}:${String(i.minute??0).padStart(2,"0")}`;return i.second!==void 0&&(t+=`:${String(i.second).padStart(2,"0")}`),t}let e=String(i.year).padStart(4,"0");return i.month===void 0||(e+=`-${String(i.month).padStart(2,"0")}`,i.day===void 0)||(e+=`-${String(i.day).padStart(2,"0")}`,i.hour===void 0)||(e+=`T${String(i.hour).padStart(2,"0")}:${String(i.minute??0).padStart(2,"0")}`,i.second!==void 0&&(e+=`:${String(i.second).padStart(2,"0")}`)),e}function Oo(i){if(i.year!==void 0)return Date.UTC(i.year,(i.month??1)-1,i.day??1,i.hour??0,i.minute??0,i.second??0)}function Xc(i){return(((i.day??0)*24+(i.hour??0))*60+(i.minute??0))*6e4+(i.second??0)*1e3}const V0=["year","month","day","hour","minute"],W0=["day","hour","minute"];function Yc(i,e){return e.filter(t=>i[t]!==void 0).join(",")}function lc(i){if(i.durationSeconds!==void 0)return i.durationSeconds*1e3;if(!i.time||!i.endTime)return;const e=i.time.year!==void 0&&i.endTime.year!==void 0,t=e?V0:W0,n=Yc(i.time,t);if(!(n===""||n!==Yc(i.endTime,t)))return e?Oo(i.endTime)-Oo(i.time):Xc(i.endTime)-Xc(i.time)}function X0(i){if(i.durationSeconds===void 0&&!(!i.time||!i.endTime))return lc(i)===void 0?"imprecise":void 0}class Ta{constructor(e,t,n,s,r,a,o,l=[]){this.event=e,this.timeline=t,this.witnessTrack=n,this.weatherTrack=s,this.witness=r,this.caseId=a,this.weather=o,this.decor=l}static create(e,t,n){return new Ta({eventType:"sighting",time:e,place:t},new wa,new Js,new Qs,n)}}const Y0=0,q0=0,$0=60;function Bo(i,e){const t=i.witnessTrack.getInterpolatedPoseAt(e);if(t)return t;const n=i.event.place?.[0];if(n)return{lat:n.lat,lng:n.lng,elevationM:Y0,headingDeg:void 0,pitchDeg:q0,fovDeg:$0}}function zo(i,e){return i.weatherTrack.getInterpolatedWeatherAt(e)??i.weather??qd}class K0{constructor(e,t){this.timeline=e,this.onFrame=t}rafId=null;state="stopped";currentT=0;lastWallTime=0;playbackRate=1;loop=!1;durationOverrideMs=0;get seekableDuration(){return Math.max(this.timeline.duration,this.durationOverrideMs)}play(){if(this.state==="playing")return;this.currentT>=this.seekableDuration&&(this.currentT=0,this.resolveFrame(0)),this.state="playing",this.lastWallTime=performance.now();const e=()=>{if(this.state!=="playing")return;const t=performance.now();if(this.currentT+=(t-this.lastWallTime)*this.playbackRate,this.lastWallTime=t,this.currentT>=this.seekableDuration){if(this.loop&&this.seekableDuration>0){this.currentT%=this.seekableDuration,this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(e);return}this.currentT=this.seekableDuration,this.stop(),this.resolveFrame(this.currentT);return}this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(e)};this.rafId=requestAnimationFrame(e)}pause(){this.state==="playing"&&(this.state="paused",this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null))}stop(){this.state="stopped",this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null)}seek(e){this.currentT=Math.max(0,Math.min(e,this.seekableDuration)),this.resolveFrame(this.currentT)}get playbackState(){return this.state}get time(){return this.currentT}resolveFrame(e){const t=new Map;for(const n of this.timeline.sourceIds){const s=this.timeline.getInterpolatedShapeAt(e,n);s&&t.set(n,s)}this.onFrame(e,t)}}const $d=["nw","n","ne","e","se","s","sw","w"],Z0=24,ri=8,Kd=3,J0=8;function Hs(i,e,t){const n=i.x-e.x,s=i.y-e.y,r=Math.cos(t),a=Math.sin(t);return{x:e.x+n*r-s*a,y:e.y+n*a+s*r}}function Si(i){return{x:i.x+i.width/2,y:i.y+i.height/2}}const Q0={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 Ke{static handlePointsFor(e){const{x:t,y:n,width:s,height:r}=e.bounds,a={nw:{x:t,y:n},n:{x:t+s/2,y:n},ne:{x:t+s,y:n},e:{x:t+s,y:n+r/2},se:{x:t+s,y:n+r},s:{x:t+s/2,y:n+r},sw:{x:t,y:n+r},w:{x:t,y:n+r/2},rotate:{x:t+s/2,y:n-Z0}},o=Si(e.bounds),l={};for(const c of Object.keys(a))l[c]=Hs(a[c],o,e.angle);return l}static hitTestHandle(e,t,n=8,s=[...$d,"rotate"]){const r=Ke.handlePointsFor(e);for(const a of s)if(Math.hypot(t.x-r[a].x,t.y-r[a].y)<=n)return a}static HANDLE_DIRECTION_DEG={e:0,se:45,s:90,sw:135,w:180,nw:225,n:270,ne:315};static resizeAxisFor(e,t){const s=((Ke.HANDLE_DIRECTION_DEG[e]+t*180/Math.PI)%180+180)%180/45,r=["ew","nwse","ns","nesw"];return r[Math.round(s)%r.length]}static resizeBounds(e,t,n,s){const r=Si(e),a=Hs(s,r,-t),{x:o,y:l,width:c,height:h}=e,u=Q0[n];let d=o,p=l,v=o+c,y=l+h;return u.left&&(d=Math.min(a.x,v-ri)),u.right&&(v=Math.max(a.x,d+ri)),u.top&&(p=Math.min(a.y,y-ri)),u.bottom&&(y=Math.max(a.y,p+ri)),{x:d,y:p,width:v-d,height:y-p}}static resizeShape(e,t,n){if(t==="rotate")throw new Error("resizeShape does not accept the rotate handle");const{width:s,height:r}=e.bounds,a=Ke.resizeBounds(e.bounds,e.angle,t,n);if(e.kind==="oval")return{...e,bounds:a};const o=s===0?1:a.width/s,l=r===0?1:a.height/r;return{...e,bounds:a,points:e.points.map(c=>({x:c.x*o,y:c.y*l}))}}static rotateShape(e,t){const n=Si(e.bounds),s=Math.atan2(t.y-n.y,t.x-n.x)+Math.PI/2;return{...e,angle:s}}static groupBoundsFor(e){const t=Math.min(...e.map(a=>a.x)),n=Math.min(...e.map(a=>a.y)),s=Math.max(...e.map(a=>a.x+a.width)),r=Math.max(...e.map(a=>a.y+a.height));return{x:t,y:n,width:s-t,height:r-n}}static vertexPointsFor(e){const t=Si(e.bounds);return e.points.map(n=>Hs({x:e.bounds.x+n.x,y:e.bounds.y+n.y},t,e.angle))}static hitTestVertex(e,t,n=J0){const s=Ke.vertexPointsFor(e);let r,a=n;return s.forEach((o,l)=>{const c=Math.hypot(t.x-o.x,t.y-o.y);c<=a&&(a=c,r=l)}),r}static toLocalPoint(e,t){const n=Si(e.bounds),s=Hs(t,n,-e.angle);return{x:s.x-e.bounds.x,y:s.y-e.bounds.y}}static moveVertex(e,t,n){const s=e.points.map((u,d)=>d===t?Ke.toLocalPoint(e,n):u),r=Math.min(...s.map(u=>u.x)),a=Math.min(...s.map(u=>u.y)),o=Math.max(...s.map(u=>u.x)),l=Math.max(...s.map(u=>u.y)),c={x:e.bounds.x+r,y:e.bounds.y+a,width:o-r,height:l-a},h=s.map(u=>({x:u.x-r,y:u.y-a}));return{...e,bounds:c,points:h}}static insertVertexNear(e,t){const n=Ke.toLocalPoint(e,t),{points:s}=e;let r=s.length-1,a=1/0;for(let l=0;l<s.length;l++){const c=s[l],h=s[(l+1)%s.length],u=Ke.distanceToSegment(n,c,h);u<a&&(a=u,r=l)}const o=[...s.slice(0,r+1),n,...s.slice(r+1)];return{...e,points:o}}static distanceToSegment(e,t,n){const s=n.x-t.x,r=n.y-t.y,a=s*s+r*r,o=a===0?0:Math.max(0,Math.min(1,((e.x-t.x)*s+(e.y-t.y)*r)/a)),l=t.x+o*s,c=t.y+o*r;return Math.hypot(e.x-l,e.y-c)}static deleteVertex(e,t){return e.points.length<=Kd?e:{...e,points:e.points.filter((n,s)=>s!==t)}}}class Ba{constructor(e){this.members=e}bounds(){return Ke.groupBoundsFor(this.members.map(e=>e.shape.bounds))}scaleTo(e){const t=this.bounds(),n=t.width===0?1:e.width/t.width,s=t.height===0?1:e.height/t.height;return this.members.map(({sourceId:r,shape:a})=>{const o=Math.max(a.bounds.width*n,ri),l=Math.max(a.bounds.height*s,ri),c=e.x+(a.bounds.x-t.x)*n,h=e.y+(a.bounds.y-t.y)*s,u={x:c,y:h,width:o,height:l};return a.kind==="oval"?{sourceId:r,shape:{...a,bounds:u}}:{sourceId:r,shape:{...a,bounds:u,points:a.points.map(d=>({x:d.x*n,y:d.y*s}))}}})}resize(e,t){return this.scaleTo(Ke.resizeBounds(this.bounds(),0,e,t))}rotate(e,t){const n=Si(this.bounds()),s=a=>Math.atan2(a.y-n.y,a.x-n.x),r=s(e)-s(t);return this.members.map(({sourceId:a,shape:o})=>{const l=Si(o.bounds),c=Hs(l,n,r),h={x:c.x-o.bounds.width/2,y:c.y-o.bounds.height/2,width:o.bounds.width,height:o.bounds.height};return{sourceId:a,shape:{...o,bounds:h,angle:o.angle+r}}})}}const ko=6,hr=ko/2,j0=20,qc=4;class ef{constructor(e){this.ctx=e}clear(e,t){this.ctx.clearRect(0,0,e,t)}paintShape(e){this.ctx.save(),this.ctx.globalAlpha=1-e.transparency,e.haloScale>0&&this.paintHalo(e),this.paintBase(e),this.ctx.restore(),e.selected&&this.paintSelectionHandles(e)}paintBase(e){if(this.ctx.fillStyle=e.color,this.ctx.beginPath(),e.kind==="oval"){const{x:t,y:n,width:s,height:r}=e.bounds,a=s/2,o=r/2;this.ctx.ellipse(t+a,n+o,a,o,e.angle,0,2*Math.PI)}else{const{x:t,y:n,width:s,height:r}=e.bounds;this.ctx.save(),this.ctx.translate(t+s/2,n+r/2),this.ctx.rotate(e.angle),this.ctx.translate(-s/2,-r/2),e.points.forEach((a,o)=>{o===0?this.ctx.moveTo(a.x,a.y):this.ctx.lineTo(a.x,a.y)}),this.ctx.closePath(),this.ctx.restore()}this.ctx.fill()}paintHalo(e){this.ctx.save(),this.ctx.shadowColor=e.color,this.ctx.shadowBlur=j0*e.haloScale,this.paintBase(e),this.ctx.restore()}paintSelectionHandles(e){this.paintHandleFrame(Ke.handlePointsFor(e),{includeRotate:!0}),e.kind==="polygon"&&this.paintVertexHandles(e)}paintVertexHandles(e){this.ctx.fillStyle="#39f";for(const t of Ke.vertexPointsFor(e))this.ctx.beginPath(),this.ctx.ellipse(t.x,t.y,qc,qc,0,0,2*Math.PI),this.ctx.fill()}paintMemberOutline(e){this.paintOutline(Ke.handlePointsFor(e))}paintGroupHandles(e){this.paintHandleFrame(Ke.handlePointsFor({bounds:e,angle:0}),{includeRotate:!0})}paintOutline(e){this.ctx.strokeStyle="lightgray",this.ctx.beginPath(),this.ctx.moveTo(e.nw.x,e.nw.y),this.ctx.lineTo(e.ne.x,e.ne.y),this.ctx.lineTo(e.se.x,e.se.y),this.ctx.lineTo(e.sw.x,e.sw.y),this.ctx.closePath(),this.ctx.stroke()}paintHandleFrame(e,{includeRotate:t}){this.paintOutline(e),this.ctx.fillStyle="lightgray";for(const n of $d){const s=e[n];this.ctx.fillRect(s.x-hr,s.y-hr,ko,ko)}t&&(this.ctx.beginPath(),this.ctx.moveTo(e.n.x,e.n.y),this.ctx.lineTo(e.rotate.x,e.rotate.y),this.ctx.stroke(),this.ctx.beginPath(),this.ctx.ellipse(e.rotate.x,e.rotate.y,hr+1,hr+1,0,0,2*Math.PI),this.ctx.fill())}}function tf(i){return{version:1,time:i.event.time,endTime:i.event.endTime,durationSeconds:i.event.durationSeconds,utcOffsetHours:i.event.utcOffsetHours,place:i.event.place,witness:i.witness,caseId:i.caseId,description:i.event.description,tags:i.event.tags,timeline:i.timeline.toJSON(),witnessTrack:i.witnessTrack.toJSON(),weatherTrack:i.weatherTrack.toJSON(),weather:i.weather,decor:i.decor}}function nf(i){return new Ta({eventType:"sighting",time:i.time,endTime:i.endTime,durationSeconds:i.durationSeconds,utcOffsetHours:i.utcOffsetHours,place:i.place,description:i.description,tags:i.tags},wa.fromJSON(i.timeline),i.witnessTrack?Js.fromJSON(i.witnessTrack):new Js,i.weatherTrack?Qs.fromJSON(i.weatherTrack):new Qs,i.witness,i.caseId,i.weather,i.decor??[])}function Aa(i,e){for(const t of i){const n=t.toLowerCase().split("-")[0];if(e.includes(n))return n}return"en"}const sf="modulepreload",rf=function(i,e){return new URL(i,e).href},$c={},ha=function(e,t,n){let s=Promise.resolve();if(t&&t.length>0){let a=function(h){return Promise.all(h.map(u=>Promise.resolve(u).then(d=>({status:"fulfilled",value:d}),d=>({status:"rejected",reason:d}))))};const o=document.getElementsByTagName("link"),l=document.querySelector("meta[property=csp-nonce]"),c=l?.nonce||l?.getAttribute("nonce");s=a(t.map(h=>{if(h=rf(h,n),h in $c)return;$c[h]=!0;const u=h.endsWith(".css"),d=u?'[rel="stylesheet"]':"";if(!!n)for(let y=o.length-1;y>=0;y--){const m=o[y];if(m.href===h&&(!u||m.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${h}"]${d}`))return;const v=document.createElement("link");if(v.rel=u?"stylesheet":sf,u||(v.as="script"),v.crossOrigin="",v.href=h,c&&v.setAttribute("nonce",c),document.head.appendChild(v),u)return new Promise((y,m)=>{v.addEventListener("load",y),v.addEventListener("error",()=>m(new Error(`Unable to preload CSS for ${h}`)))})}))}function r(a){const o=new Event("vite:preloadError",{cancelable:!0});if(o.payload=a,window.dispatchEvent(o),!o.defaultPrevented)throw a}return s.then(a=>{for(const o of a||[])o.status==="rejected"&&r(o.reason);return e().catch(r)})},Zd=["en","fr"],af={en:()=>ha(()=>Promise.resolve().then(()=>hf),void 0,import.meta.url).then(i=>i.ufoMessages_en),fr:()=>ha(()=>import("./UfoMessages_fr-NZrwNFCQ.js"),[],import.meta.url).then(i=>i.ufoMessages_fr)};function of(i){return af[i]()}const lf={en:()=>ha(()=>Promise.resolve().then(()=>oy),void 0,import.meta.url).then(i=>i.ufoRecorderMessages_en),fr:()=>ha(()=>import("./UfoRecorderMessages_fr-CsLFK8nn.js"),[],import.meta.url).then(i=>i.ufoRecorderMessages_fr)};function cf(i){return lf[i]()}const Jd={play:"Play",pause:"Pause",noDuration:"No observation duration",autoReplay:"Auto-replay",currentPosition:"Current position",duration:"Duration",fullscreen:"Fullscreen",exitFullscreen:"Exit fullscreen"},hf=Object.freeze(Object.defineProperty({__proto__:null,ufoMessages_en:Jd},Symbol.toStringTag,{value:"Module"})),Kc=new Set;class df extends HTMLElement{static get observedAttributes(){return["src"]}shadow;stageElement;canvas;canvasRenderer;tooltip;toolbar;playPauseButton;loopButton;fullscreenButton;seekInput;timeStartLabel;timeEndLabel;currentSighting=Ta.create();player;loopEnabled=!0;highlightedSourceIds=new Set;occludedSourceIds=Kc;enableClickToPlay=!0;fullscreenTarget;messages=Jd;realDurationMs;realStartMs;handleFullscreenChange=()=>this.updateFullscreenButton();handlePointerMove=e=>{const t=this.canvasPointFromEvent(e),n=t&&this.currentSighting.timeline.hitTest(this.currentTime,t.x,t.y,this.occludedSourceIds);if(!n?.shape.title){this.tooltip.hidden=!0;return}this.tooltip.textContent=n.shape.title,this.tooltip.hidden=!1;const s=this.stageElement.getBoundingClientRect();this.tooltip.style.left=`${e.clientX-s.left+12}px`,this.tooltip.style.top=`${e.clientY-s.top+12}px`};handlePointerLeave=()=>{this.tooltip.hidden=!0};constructor(){super(),this.shadow=this.attachShadow({mode:"open"});const e=document.createElement("template");e.innerHTML=`<style>${A0}</style>${T0}`,this.shadow.appendChild(e.content.cloneNode(!0)),this.stageElement=this.shadow.getElementById("stage"),this.canvas=this.shadow.getElementById("canvas"),this.canvasRenderer=new ef(this.canvas.getContext("2d")),this.tooltip=this.shadow.getElementById("tooltip"),this.toolbar=this.shadow.getElementById("toolbar"),this.playPauseButton=this.shadow.getElementById("play-pause"),this.loopButton=this.shadow.getElementById("loop"),this.fullscreenButton=this.shadow.getElementById("fullscreen"),this.seekInput=this.shadow.getElementById("seek"),this.timeStartLabel=this.shadow.getElementById("time-start"),this.timeEndLabel=this.shadow.getElementById("time-end"),this.fullscreenTarget=this.stageElement,this.playPauseButton.addEventListener("click",()=>this.togglePlayPause()),this.loopButton.addEventListener("click",()=>this.toggleLoop()),this.fullscreenButton.addEventListener("click",()=>this.toggleFullscreen()),this.seekInput.addEventListener("input",()=>this.player.seek(Number(this.seekInput.value))),this.canvas.addEventListener("click",()=>{this.enableClickToPlay&&this.togglePlayPause()}),this.canvas.addEventListener("pointermove",this.handlePointerMove),this.canvas.addEventListener("pointerleave",this.handlePointerLeave),document.addEventListener("fullscreenchange",this.handleFullscreenChange),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.updateFullscreenButton(),this.refresh(),this.loadLocaleMessages()}connectedCallback(){const e=this.getAttribute("src");e&&this.loadFromSrc(e)}disconnectedCallback(){document.removeEventListener("fullscreenchange",this.handleFullscreenChange)}attributeChangedCallback(e,t,n){e==="src"&&n&&n!==t&&this.isConnected&&this.loadFromSrc(n)}async loadFromSrc(e){const t=await fetch(e);this.sightingData=await t.json()}get sightingData(){return tf(this.currentSighting)}set sightingData(e){this.player.stop(),this.currentSighting=nf(e),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.refresh()}get sighting(){return this.currentSighting}get canvasElement(){return this.canvas}get renderer(){return this.canvasRenderer}get currentTime(){return this.player.time}set currentTime(e){this.player.seek(e)}get seekableDuration(){return this.player.seekableDuration}get autoReplayEnabled(){return this.loopEnabled}get positionLabel(){return this.timeStartLabel.textContent??""}get durationLabel(){return this.timeEndLabel.textContent??""}set showToolbar(e){this.toolbar.classList.toggle("hidden",!e)}get playbackState(){return this.player.playbackState}get selectedSourceIds(){return this.highlightedSourceIds}get durationSeconds(){return this.currentSighting.event.durationSeconds}set durationSeconds(e){this.currentSighting.event.durationSeconds=e,this.updateTimeLabels(),this.refresh(),this.updatePlayPauseButton()}set selectedSourceIds(e){const t=new Set(e);t.size===this.highlightedSourceIds.size&&[...t].every(s=>this.highlightedSourceIds.has(s))||(this.highlightedSourceIds=t,this.refresh())}setOccludedSourceIds(e){e.size===this.occludedSourceIds.size&&[...e].every(n=>this.occludedSourceIds.has(n))||(this.occludedSourceIds=e,this.refresh())}hasVisibleShapeAt(e,t){return this.currentSighting.timeline.hitTest(this.currentTime,e,t,this.occludedSourceIds)!==void 0}refresh(){this.seekInput.max=String(this.player.seekableDuration),this.player.seek(this.player.time)}canvasPointFromEvent(e){const t=this.canvas.getBoundingClientRect();if(!(t.width===0||t.height===0))return{x:(e.clientX-t.left)/t.width*this.canvas.width,y:(e.clientY-t.top)/t.height*this.canvas.height}}onFrame(e,t){this.canvasRenderer.clear(this.canvas.width,this.canvas.height);const n=this.playbackState!=="playing"?this.highlightedSourceIds:Kc;for(const[s,r]of t){if(this.occludedSourceIds.has(s))continue;const a=n.has(s);a&&n.size===1?this.canvasRenderer.paintShape({...r,selected:!0}):(this.canvasRenderer.paintShape(r),a&&this.canvasRenderer.paintMemberOutline(r))}if(n.size>1){const s=Ke.groupBoundsFor([...t].filter(([r])=>n.has(r)).map(([,r])=>r.bounds));this.canvasRenderer.paintGroupHandles(s)}this.seekInput.value=String(e),this.timeStartLabel.textContent=this.formatPosition(e),this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{time:e}}))}createPlayer(){const e=new K0(this.currentSighting.timeline,(t,n)=>this.onFrame(t,n));return e.loop=this.loopEnabled,e}togglePlayPause(){this.player.seekableDuration<=0||(this.player.playbackState==="playing"?(this.player.pause(),this.refresh()):this.player.play(),this.updatePlayPauseButton())}updatePlayPauseButton(){const e=this.player.playbackState==="playing";this.playPauseButton.textContent=e?"⏸":"▶";const t=this.player.seekableDuration>0;this.playPauseButton.disabled=!t;const n=t?e?this.messages.pause:this.messages.play:this.messages.noDuration;this.playPauseButton.title=n,this.playPauseButton.setAttribute("aria-label",n),this.toolbar.classList.toggle("auto-hide",e),this.fullscreenButton.classList.toggle("auto-hide",e)}toggleLoop(){this.loopEnabled=!this.loopEnabled,this.loopButton.setAttribute("aria-pressed",String(this.loopEnabled)),this.player.loop=this.loopEnabled}toggleFullscreen(){document.fullscreenElement?document.exitFullscreen():this.fullscreenTarget.requestFullscreen().catch(e=>{console.error("<rr0-ufo>: requestFullscreen() failed —",e)})}updateFullscreenButton(){const e=document.fullscreenElement===this.fullscreenTarget;this.fullscreenButton.title=e?this.messages.exitFullscreen:this.messages.fullscreen,this.fullscreenButton.setAttribute("aria-label",this.fullscreenButton.title)}async loadLocaleMessages(){const e=Aa(navigator.languages,Zd);e!=="en"&&this.applyMessages(await of(e))}applyMessages(e){this.messages=e,this.timeStartLabel.title=e.currentPosition,this.timeEndLabel.title=e.duration,this.loopButton.title=e.autoReplay,this.loopButton.setAttribute("aria-label",e.autoReplay),this.updatePlayPauseButton(),this.updateFullscreenButton()}updateTimeLabels(){const e=this.currentSighting.event,t=lc(e);this.realDurationMs=t!==void 0&&t>0?t:void 0,this.realStartMs=e.time?Oo(e.time):void 0;const n=this.currentSighting.timeline.duration;this.player.playbackRate=this.realDurationMs!==void 0&&n>0?n/this.realDurationMs:1,this.player.durationOverrideMs=n>0?0:this.realDurationMs??0,this.timeEndLabel.textContent=this.formatEndOfTimeline(),this.timeStartLabel.textContent=this.formatPosition(this.player.time)}formatPosition(e){if(this.realDurationMs===void 0)return dr(e);const t=this.currentSighting.timeline.duration,n=t>0&&e<=t?e/t*this.realDurationMs:e;return this.realStartMs!==void 0?Jc(Zc(this.realStartMs+n)):dr(n)}formatEndOfTimeline(){return this.realDurationMs===void 0?dr(this.currentSighting.timeline.duration):this.realStartMs!==void 0?Jc(Zc(this.realStartMs+this.realDurationMs)):dr(this.realDurationMs)}}function Zc(i){const e=new Date(i);return{hour:e.getUTCHours(),minute:e.getUTCMinutes(),second:e.getUTCSeconds()}}function Jc(i){if(i.hour===void 0)return"0:00";const e=t=>String(t).padStart(2,"0");return i.second?`${e(i.hour)}:${e(i.minute??0)}:${e(i.second)}`:`${e(i.hour)}:${e(i.minute??0)}`}function dr(i){const e=Math.round(i/1e3),t=Math.floor(e/60),n=e%60;return`${t}:${String(n).padStart(2,"0")}`}const Go="rr0-ufo";function Ia(){customElements.get(Go)||customElements.define(Go,df)}const uf=`
|
|
695
699
|
<div class="stage" id="stage">
|
|
696
700
|
<div class="frame" id="frame">
|
|
697
701
|
<canvas id="scene-canvas"></canvas>
|