@rr0/ufoathome 0.43.1 → 0.45.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.
- package/LICENSE +21 -0
- package/README.md +21 -8
- package/dist-embed-scene/rr0-scene.mjs +318 -235
- package/dist-embed-scene/stars-mag7.5-9-BLV2tFmZ.bin +0 -0
- package/dist-embed-scene/stars-mag7.5-B43R6Wvg.bin +0 -0
- package/dist-embed-sighting/{SightingMessages_fr-BO7lrFxB.js → SightingMessages_fr-ghzJGk07.js} +1 -1
- package/dist-embed-sighting/rr0-sighting.mjs +303 -220
- package/dist-embed-sighting/stars-mag7.5-9-BLV2tFmZ.bin +0 -0
- package/dist-embed-sighting/stars-mag7.5-B43R6Wvg.bin +0 -0
- package/dist-embed-sighting-editor/SightingEditorMessages_fr-h_TFOqJZ.js +1 -0
- package/dist-embed-sighting-editor/rr0-sighting-editor.mjs +313 -218
- package/dist-embed-sighting-editor/stars-mag7.5-9-BLV2tFmZ.bin +0 -0
- package/dist-embed-sighting-editor/stars-mag7.5-B43R6Wvg.bin +0 -0
- package/dist-embed-ufo/rr0-ufo.mjs +84 -2
- package/package.json +1 -1
- package/dist-embed-scene/stars-mag7.5-DDr9QasD.bin +0 -0
- package/dist-embed-sighting/stars-mag7.5-DDr9QasD.bin +0 -0
- package/dist-embed-sighting-editor/SightingEditorMessages_fr-Bz9hXxtf.js +0 -1
- package/dist-embed-sighting-editor/stars-mag7.5-DDr9QasD.bin +0 -0
|
Binary file
|
|
Binary file
|
|
@@ -5,10 +5,20 @@ const he=`
|
|
|
5
5
|
</div>
|
|
6
6
|
<div id="tooltip" class="tooltip" hidden></div>
|
|
7
7
|
<button id="fullscreen" class="fullscreen-btn" type="button" title="Fullscreen" aria-label="Fullscreen">⛶</button>
|
|
8
|
+
<!-- What the account calls the moment now on screen (see Milestone) — above the controls rather
|
|
9
|
+
than inside them, because it is a sentence and the toolbar is a row of buttons. Empty and
|
|
10
|
+
hidden for the recordings that name no moment, which is most of them. -->
|
|
11
|
+
<div id="milestone-caption" class="milestone-caption" hidden></div>
|
|
8
12
|
<div class="toolbar" id="toolbar">
|
|
9
13
|
<button id="play-pause" type="button" title="Play" aria-label="Play">▶</button>
|
|
10
14
|
<span id="time-start" class="time-label" title="Current position">0:00</span>
|
|
11
|
-
|
|
15
|
+
<!-- The bar and the marks over it share one box so a mark can be placed by percentage of the
|
|
16
|
+
track. The input keeps its own full width inside it; the marks sit on top and only the
|
|
17
|
+
marks themselves take a click. -->
|
|
18
|
+
<div id="seek-track" class="seek-track">
|
|
19
|
+
<input id="seek" type="range" min="0" max="0" value="0" step="1"/>
|
|
20
|
+
<div id="milestone-marks" class="milestone-marks"></div>
|
|
21
|
+
</div>
|
|
12
22
|
<span id="time-end" class="time-label" title="Duration">0:00</span>
|
|
13
23
|
<button id="loop" type="button" title="Auto-replay" aria-label="Auto-replay" aria-pressed="true">↻</button>
|
|
14
24
|
</div>
|
|
@@ -158,6 +168,78 @@ canvas[data-cursor="rotate"] {
|
|
|
158
168
|
.toolbar.hidden {
|
|
159
169
|
display: none;
|
|
160
170
|
}
|
|
171
|
+
/* Takes the width the bare <input id="seek"> used to take, so nothing else in the row moves. */
|
|
172
|
+
.seek-track {
|
|
173
|
+
position: relative;
|
|
174
|
+
flex: 1;
|
|
175
|
+
display: flex;
|
|
176
|
+
align-items: center;
|
|
177
|
+
}
|
|
178
|
+
.seek-track #seek {
|
|
179
|
+
flex: 1;
|
|
180
|
+
min-width: 0;
|
|
181
|
+
}
|
|
182
|
+
/* Over the bar, and transparent to the pointer except on a mark itself — dragging the bar between
|
|
183
|
+
two moments has to keep working. */
|
|
184
|
+
.milestone-marks {
|
|
185
|
+
position: absolute;
|
|
186
|
+
left: 0;
|
|
187
|
+
right: 0;
|
|
188
|
+
top: 0;
|
|
189
|
+
bottom: 0;
|
|
190
|
+
pointer-events: none;
|
|
191
|
+
}
|
|
192
|
+
.milestone-mark {
|
|
193
|
+
position: absolute;
|
|
194
|
+
top: 0;
|
|
195
|
+
bottom: 0;
|
|
196
|
+
width: 10px;
|
|
197
|
+
padding: 0;
|
|
198
|
+
transform: translateX(-50%);
|
|
199
|
+
border: none;
|
|
200
|
+
background: none;
|
|
201
|
+
cursor: pointer;
|
|
202
|
+
pointer-events: auto;
|
|
203
|
+
color: inherit;
|
|
204
|
+
font: inherit;
|
|
205
|
+
line-height: 0;
|
|
206
|
+
}
|
|
207
|
+
/* The mark itself is the thin line inside that hit area: a 2 px tick is impossible to hit with a
|
|
208
|
+
finger, and a 10 px tick would hide the bar under it. */
|
|
209
|
+
.milestone-mark::before {
|
|
210
|
+
content: "";
|
|
211
|
+
position: absolute;
|
|
212
|
+
left: 50%;
|
|
213
|
+
top: 15%;
|
|
214
|
+
bottom: 15%;
|
|
215
|
+
width: 2px;
|
|
216
|
+
transform: translateX(-50%);
|
|
217
|
+
background: #fff;
|
|
218
|
+
box-shadow: 0 0 2px rgba(0, 0, 0, 0.8);
|
|
219
|
+
}
|
|
220
|
+
.milestone-mark:hover::before, .milestone-mark:focus-visible::before {
|
|
221
|
+
width: 4px;
|
|
222
|
+
}
|
|
223
|
+
.milestone-caption {
|
|
224
|
+
position: absolute;
|
|
225
|
+
left: 0;
|
|
226
|
+
right: 0;
|
|
227
|
+
bottom: 2.6em;
|
|
228
|
+
padding: 0 0.8em;
|
|
229
|
+
color: #fff;
|
|
230
|
+
font-size: 0.85em;
|
|
231
|
+
text-align: center;
|
|
232
|
+
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
|
|
233
|
+
pointer-events: none;
|
|
234
|
+
}
|
|
235
|
+
/* Same trap as .toolbar.hidden above and .context-menu[hidden] in the editor: a class that sets
|
|
236
|
+
its own display outranks the UA sheet's [hidden]. */
|
|
237
|
+
.milestone-caption[hidden] {
|
|
238
|
+
display: none;
|
|
239
|
+
}
|
|
240
|
+
.milestone-caption b {
|
|
241
|
+
font-weight: 700;
|
|
242
|
+
}
|
|
161
243
|
.stage:hover .auto-hide {
|
|
162
244
|
opacity: 1;
|
|
163
245
|
pointer-events: auto;
|
|
@@ -229,4 +311,4 @@ input[type=range] {
|
|
|
229
311
|
pointer-events: none;
|
|
230
312
|
white-space: nowrap;
|
|
231
313
|
}
|
|
232
|
-
`;class T extends Error{constructor(e,t,s){super(`${e}: ${t}${s===void 0?"":` (HTTP ${s})`}`),this.kind=e,this.url=t,this.status=s,this.name="SightingFetchError"}}class le{static async json(e){const t=new URL(e,location.href);if(location.protocol==="https:"&&t.protocol==="http:")throw new T("mixed-content",e);let s;try{s=await fetch(e)}catch{throw await this.diagnose(e,t)}if(!s.ok)throw new T("status",e,s.status);try{return await s.json()}catch{throw new T("malformed",e)}}static async diagnose(e,t){if(t.origin===location.origin)return new T("unreachable",e);try{return await fetch(e,{mode:"no-cors"}),new T("cors",e)}catch{return new T("unreachable",e)}}}const C=[{id:"eye",name:{en:"Naked eye",fr:"Œil nu"},flare:0,projection:"equidistant"},{id:"rectilinear-lens",name:{en:"Camera, unknown device",fr:"Appareil, modèle inconnu"},projection:"rectilinear",fNumber:8,fNumberRange:{min:2,max:22},exposureSeconds:1/250,exposureRangeSeconds:{min:1/1e3,max:8},apertureBlades:6},{id:"instamatic-126",name:{en:"Instamatic, 126 film",fr:"Instamatic, film 126"},projection:"rectilinear",frame:{widthMm:28,heightMm:28,focalLengthMm:43},fNumber:11,exposureSeconds:1/90,years:{from:1963,to:1988},apertureBlades:5},{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},{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}},{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},exposureSeconds:1/120,exposureRangeSeconds:{min:1/8e3,max:10},years:{from:2007},apertureBlades:void 0},{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},exposureSeconds:1/120,exposureRangeSeconds:{min:1/8e3,max:10},years:{from:2007},apertureBlades:void 0}];class v{static get default(){return C[0]}static byId(e){return C.find(t=>t.id===e)??this.default}static UNAIDED_FIELD_DEG=60;static UNAIDED_ASPECT=16/9;static fieldOfViewDeg(e){const t=e.frame;return t?2*Math.atan(t.heightMm/(2*t.focalLengthMm))*180/Math.PI:v.UNAIDED_FIELD_DEG}static aspectOf(e){const t=e.frame;return t?t.widthMm/t.heightMm:v.UNAIDED_ASPECT}static frameWidthPx(e,t){return Math.round(t*v.aspectOf(e))}static availableAt(e){return e===void 0?C:C.filter(t=>!t.years||e>=t.years.from&&(t.years.to===void 0||e<=t.years.to))}static fieldOfViewDegAt(e,t){const s=e.frame;if(!(!s||t<=0))return 2*Math.atan(s.heightMm/(2*t))*180/Math.PI}static focalLengthMmFor(e,t){const s=e.frame;if(!(!s||t<=0||t>=180))return s.heightMm/(2*Math.tan(t*Math.PI/360))}static bladesShowing(e,t){const s=t??e.fNumber;if(s===void 0||e.apertureBlades===void 0)return 0;const i=e.fNumberRange;return!i||i.max<=i.min?1:Math.max(0,Math.min(1,(s-i.min)/(i.max-i.min)))}static LENS_FLARE_ARTIFACTS=1;static flareArtifactsOf(e){return e.flare??v.LENS_FLARE_ARTIFACTS}static starPointsOf(e){const t=e.apertureBlades;return t===void 0||t<3?0:t%2===0?t:t*2}}class x{static MOON_ANGULAR_WIDTH_DEG=.5237;static CANVAS_WIDTH_PX=640;static CANVAS_HEIGHT_PX=360;static angularWidthDeg(e){return 2*Math.atan(e.sizeM/(2*e.distanceM))*180/Math.PI}static inMoons(e){return e/x.MOON_ANGULAR_WIDTH_DEG}static lerpAngular(e,t,s){if(!(!e||!t))return{widthDeg:e.widthDeg+(t.widthDeg-e.widthDeg)*s,heightDeg:e.heightDeg+(t.heightDeg-e.heightDeg)*s}}static sizeMAt(e,t){return 2*e*Math.tan(t*Math.PI/360)}static distanceMAt(e,t){return e/(2*Math.tan(t*Math.PI/360))}}function ue(n,e,t){const{bounds:s}=n;return e>=s.x&&e<=s.x+s.width&&t>=s.y&&t<=s.y+s.height}function g(n,e,t){return n+(e-n)*t}function G(n){const e=/^#([0-9a-f]{6})$/i.exec(n);if(!e)return;const t=parseInt(e[1],16);return[t>>16&255,t>>8&255,t&255]}function de(n){return`#${n.map(e=>Math.round(e).toString(16).padStart(2,"0")).join("")}`}function fe(n,e,t){const s=G(n),i=G(e);return!s||!i?t<1?n:e:de([g(s[0],i[0],t),g(s[1],i[1],t),g(s[2],i[2],t)])}function me(n,e,t){const s={x:g(n.bounds.x,e.bounds.x,t),y:g(n.bounds.y,e.bounds.y,t),width:g(n.bounds.width,e.bounds.width,t),height:g(n.bounds.height,e.bounds.height,t)},i=g(n.angle,e.angle,t),r=g(n.transparency,e.transparency,t),a=g(n.haloScale,e.haloScale,t),o=g(n.blur??0,e.blur??0,t),h=g(n.brightness??0,e.brightness??0,t),c=fe(n.color,e.color,t),u=x.lerpAngular(n.angular,e.angular,t);return n.kind==="polygon"&&e.kind==="polygon"&&n.points.length===e.points.length?{...n,bounds:s,angle:i,transparency:r,haloScale:a,blur:o,brightness:h,color:c,angular:u,behindCloud:t<1?n.behindCloud:e.behindCloud,points:n.points.map((l,d)=>({x:g(l.x,e.points[d].x,t),y:g(l.y,e.points[d].y,t)}))}:{...t<1?n:e,bounds:s,angle:i,transparency:r,haloScale:a,blur:o,brightness:h,color:c,angular:u}}class F{keyframes=[];order=[];groups=[];addKeyframe(e,t){const s=this.findInsertIndex(e);if(this.keyframes[s]?.t===e){const i=new Set(t.map(r=>r.sourceId));this.keyframes[s]={t:e,shapes:[...this.keyframes[s].shapes.filter(r=>!i.has(r.sourceId)),...t]}}else this.keyframes.splice(s,0,{t:e,shapes:[...t]});for(const i of t)this.order.includes(i.sourceId)||this.order.push(i.sourceId)}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}return t}getKeyframeAt(e){const t=this.findInsertIndex(e),s=this.keyframes[t];return s?.t===e?s:void 0}getShapeAt(e,t){return this.getKeyframeAt(e)?.shapes.find(s=>s.sourceId===t)?.shape}getLatestShapeAt(e,t){let s=this.findInsertIndex(e);for(this.keyframes[s]?.t!==e&&(s-=1);s>=0;s--){const i=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(i)return i.shape}}getInterpolatedShapeAt(e,t){const s=this.findShapeAtOrBefore(e,t);if(s?.t===e)return s.shape;const i=this.findShapeAtOrAfter(e,t);return s?i?me(s.shape,i.shape,(e-s.t)/(i.t-s.t)):s.shape:i?.shape}findShapeAtOrBefore(e,t){let s=this.findInsertIndex(e);for(this.keyframes[s]?.t!==e&&(s-=1);s>=0;s--){const i=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(i)return{t:this.keyframes[s].t,shape:i.shape}}}findShapeAtOrAfter(e,t){for(let s=this.findInsertIndex(e);s<this.keyframes.length;s++){const i=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(i)return{t:this.keyframes[s].t,shape:i.shape}}}hitTest(e,t,s,i){const r=this.sourceIds;for(let a=r.length-1;a>=0;a--){if(i?.has(r[a]))continue;const o=this.getInterpolatedShapeAt(e,r[a]);if(o&&ue(o,t,s))return{sourceId:r[a],shape:o}}}removeSource(e){for(let t=this.keyframes.length-1;t>=0;t--){const s=this.keyframes[t].shapes.filter(i=>i.sourceId!==e);s.length===0?this.keyframes.splice(t,1):s.length!==this.keyframes[t].shapes.length&&(this.keyframes[t]={t:this.keyframes[t].t,shapes:s})}this.order=this.order.filter(t=>t!==e),this.removeFromGroup(e)}group(e){if(!(e.length<2)){for(const t of e)this.removeFromGroup(t);this.groups.push([...e])}}ungroup(e){const t=this.groups.findIndex(s=>s.includes(e));t!==-1&&this.groups.splice(t,1)}groupMembers(e){return this.groups.find(t=>t.includes(e))}removeFromGroup(e){const t=this.groups.findIndex(i=>i.includes(e));if(t===-1)return;const s=this.groups[t].filter(i=>i!==e);s.length<2?this.groups.splice(t,1):this.groups[t]=s}bringToFront(e){const t=this.order.indexOf(e);t!==-1&&(this.order.splice(t,1),this.order.push(e))}sendToBack(e){const t=this.order.indexOf(e);t!==-1&&(this.order.splice(t,1),this.order.unshift(e))}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get sourceIds(){return[...this.order]}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes,order:this.order,groups:this.groups}}static fromJSON(e){const t=new F;for(const s of e.keyframes)t.addKeyframe(s.t,s.shapes);if(e.order){const s=t.order.filter(i=>!e.order.includes(i));t.order=[...e.order,...s]}return e.groups&&(t.groups=e.groups.map(s=>s.filter(i=>t.order.includes(i))).filter(s=>s.length>=2)),t}}function pe(n,e,t){return Math.max(e,Math.min(t,n))}function ge(n,e,t){const s=((e-n)%360+540)%360-180;return((n+s*t)%360+360)%360}function E(n,e,t){return n+(e-n)*t}function ye(n,e,t){return{lat:n.lat===void 0||e.lat===void 0?void 0:E(n.lat,e.lat,t),lng:n.lng===void 0||e.lng===void 0?void 0:E(n.lng,e.lng,t),elevationM:E(n.elevationM,e.elevationM,t),headingDeg:n.headingDeg===void 0||e.headingDeg===void 0?void 0:ge(n.headingDeg,e.headingDeg,t),pitchDeg:E(n.pitchDeg,e.pitchDeg,t),rollDeg:E(n.rollDeg??0,e.rollDeg??0,t),fovDeg:E(n.fovDeg,e.fovDeg,t),fNumber:n.fNumber,focusDistanceM:n.focusDistanceM}}class P{keyframes=[];addKeyframe(e,t){const s=this.findInsertIndex(e);this.keyframes[s]?.t===e?this.keyframes[s]={t:e,pose:t}:this.keyframes.splice(s,0,{t:e,pose:t})}clear(){this.keyframes.length=0}removeKeyframeAt(e){const t=this.findInsertIndex(e);this.keyframes[t]?.t===e&&this.keyframes.splice(t,1)}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}return t}getLatestPoseAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].pose:void 0}getInterpolatedPoseAt(e){const t=this.findInsertIndex(e),s=this.keyframes[t]?.t===e?this.keyframes[t]:this.keyframes[t-1];if(s?.t===e)return s.pose;const i=this.keyframes[t]?.t===e?void 0:this.keyframes[t];return s?i?ye(s.pose,i.pose,pe((e-s.t)/(i.t-s.t),0,1)):s.pose:i?.pose}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes}}static fromJSON(e){const t=new P;for(const s of e.keyframes)t.addKeyframe(s.t,s.pose);return t}}function w(n,e,t){return n+(e-n)*t}function ve(n,e,t){const s=((e-n)%360+540)%360-180;return((n+s*t)%360+360)%360}function be(n,e,t){return Math.max(e,Math.min(t,n))}function xe(n,e,t){return{cloudCover:w(n.cloudCover,e.cloudCover,t),cloudDarkness:w(n.cloudDarkness,e.cloudDarkness,t),highCloudCover:n.highCloudCover===void 0||e.highCloudCover===void 0?void 0:w(n.highCloudCover,e.highCloudCover,t),iceCrystalAlignment:n.iceCrystalAlignment===void 0||e.iceCrystalAlignment===void 0?void 0:w(n.iceCrystalAlignment,e.iceCrystalAlignment,t),lowerCloudCover:n.lowerCloudCover===void 0||e.lowerCloudCover===void 0?void 0:w(n.lowerCloudCover,e.lowerCloudCover,t),cloudBaseM:n.cloudBaseM===void 0||e.cloudBaseM===void 0?void 0:w(n.cloudBaseM,e.cloudBaseM,t),precipitationType:t<1?n.precipitationType:e.precipitationType,precipitationIntensity:w(n.precipitationIntensity,e.precipitationIntensity,t),windDirectionDeg:ve(n.windDirectionDeg,e.windDirectionDeg,t),windSpeed:w(n.windSpeed,e.windSpeed,t),storm:t<1?n.storm:e.storm}}class A{keyframes=[];addKeyframe(e,t){const s=this.findInsertIndex(e);this.keyframes[s]?.t===e?this.keyframes[s]={t:e,weather:t}:this.keyframes.splice(s,0,{t:e,weather:t})}clear(){this.keyframes.length=0}removeKeyframeAt(e){const t=this.findInsertIndex(e);this.keyframes[t]?.t===e&&this.keyframes.splice(t,1)}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}return t}getLatestWeatherAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].weather:void 0}getInterpolatedWeatherAt(e){const t=this.findInsertIndex(e),s=this.keyframes[t]?.t===e?this.keyframes[t]:this.keyframes[t-1];if(s?.t===e)return s.weather;const i=this.keyframes[t]?.t===e?void 0:this.keyframes[t];return s?i?xe(s.weather,i.weather,be((e-s.t)/(i.t-s.t),0,1)):s.weather:i?.weather}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes}}static fromJSON(e){const t=new A;for(const s of e.keyframes)t.addKeyframe(s.t,s.weather);return t}}class D{keyframes=[];addKeyframe(e,t){const s=this.findInsertIndex(e);this.keyframes[s]?.t===e?this.keyframes[s]={t:e,sound:t}:this.keyframes.splice(s,0,{t:e,sound:t})}clear(){this.keyframes.length=0}removeKeyframeAt(e){const t=this.findInsertIndex(e);this.keyframes[t]?.t===e&&this.keyframes.splice(t,1)}getLatestSoundAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].sound:void 0}getInterpolatedSoundAt(e){const t=this.findInsertIndex(e),s=this.keyframes[t]?.t===e?this.keyframes[t]:this.keyframes[t-1];if(s?.t===e)return s.sound;const i=this.keyframes[t]?.t===e?void 0:this.keyframes[t];if(!s)return i?.sound;if(!i)return s.sound;const r=(e-s.t)/(i.t-s.t);return this.lerp(s.sound,i.sound,Math.max(0,Math.min(1,r)))}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes}}static fromJSON(e){const t=new D;for(const s of e.keyframes)t.addKeyframe(s.t,s.sound);return t}lerp(e,t,s){return{kind:s<1?e.kind:t.kind,volume:e.volume+(t.volume-e.volume)*s,pitchHz:e.pitchHz+(t.pitchHz-e.pitchHz)*s,src:s<1?e.src:t.src}}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}return t}}const we=100,Se={kind:"none",volume:0,pitchHz:we};function z(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 $(n){return(((n.day??0)*24+(n.hour??0))*60+(n.minute??0))*6e4+(n.second??0)*1e3}const ke=["year","month","day","hour","minute"],Te=["day","hour","minute"];function V(n,e){return e.filter(t=>n[t]!==void 0).join(",")}function Ee(n){if(n.durationSeconds!==void 0)return n.durationSeconds*1e3;if(!n.time||!n.endTime)return;const e=n.time.year!==void 0&&n.endTime.year!==void 0,t=e?ke:Te,s=V(n.time,t);if(!(s===""||s!==V(n.endTime,t)))return e?z(n.endTime)-z(n.time):$(n.endTime)-$(n.time)}class N{constructor(e,t,s,i,r,a,o,h,c=[],u,l,d){this.event=e,this.timeline=t,this.witnessTrack=s,this.weatherTrack=i,this.soundTrack=r,this.witness=a,this.caseId=o,this.weather=h,this.decor=c,this.weatherSource=u,this.instrumentId=l,this.exposureSeconds=d}get exposure(){return this.exposureSeconds??this.instrument.exposureSeconds}get instrument(){return v.byId(this.instrumentId)}static create(e,t,s){return new N({eventType:"sighting",time:e,place:t},new F,new P,new A,new D,s)}}const Ie=0,Me=0;function ie(n,e){const t=n.witnessTrack.getInterpolatedPoseAt(e);if(t)return t;const s=n.event.place?.[0];if(s)return{lat:s.lat,lng:s.lng,elevationM:Ie,headingDeg:void 0,pitchDeg:Me,fovDeg:v.fieldOfViewDeg(n.instrument)}}function Pe(n,e){return n.soundTrack.getInterpolatedSoundAt(e)??Se}class Ae{constructor(e,t){this.timeline=e,this.onFrame=t}rafId=null;state="stopped";currentT=0;lastWallTime=0;playbackRate=1;loop=!1;onEnded;durationOverrideMs=0;get seekableDuration(){return Math.max(this.timeline.duration,this.durationOverrideMs)}play(){if(this.state==="playing")return;this.currentT>=this.seekableDuration&&(this.currentT=0,this.resolveFrame(0)),this.state="playing",this.lastWallTime=performance.now();const e=()=>{if(this.state!=="playing")return;const t=performance.now();if(this.currentT+=(t-this.lastWallTime)*this.playbackRate,this.lastWallTime=t,this.currentT>=this.seekableDuration){if(this.loop&&this.seekableDuration>0){this.currentT%=this.seekableDuration,this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(e);return}this.currentT=this.seekableDuration,this.stop(),this.resolveFrame(this.currentT),this.onEnded?.();return}this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(e)};this.rafId=requestAnimationFrame(e)}pause(){this.state==="playing"&&(this.state="paused",this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null))}stop(){this.state="stopped",this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null)}seek(e){this.currentT=Math.max(0,Math.min(e,this.seekableDuration)),this.resolveFrame(this.currentT)}get playbackState(){return this.state}get time(){return this.currentT}resolveFrame(e){const t=new Map;for(const s of this.timeline.sourceIds){const i=this.timeline.getInterpolatedShapeAt(e,s);i&&t.set(s,i)}this.onFrame(e,t)}}const ne=["nw","n","ne","e","se","s","sw","w"],De=24,L=8,Ce=3,Le=8;function R(n,e,t){const s=n.x-e.x,i=n.y-e.y,r=Math.cos(t),a=Math.sin(t);return{x:e.x+s*r-i*a,y:e.y+s*a+i*r}}function I(n){return{x:n.x+n.width/2,y:n.y+n.height/2}}const Re={nw:{left:!0,top:!0},n:{top:!0},ne:{right:!0,top:!0},e:{right:!0},se:{right:!0,bottom:!0},s:{bottom:!0},sw:{left:!0,bottom:!0},w:{left:!0}};class y{static handlePointsFor(e){const{x:t,y:s,width:i,height:r}=e.bounds,a={nw:{x:t,y:s},n:{x:t+i/2,y:s},ne:{x:t+i,y:s},e:{x:t+i,y:s+r/2},se:{x:t+i,y:s+r},s:{x:t+i/2,y:s+r},sw:{x:t,y:s+r},w:{x:t,y:s+r/2},rotate:{x:t+i/2,y:s-De}},o=I(e.bounds),h={};for(const c of Object.keys(a))h[c]=R(a[c],o,e.angle);return h}static hitTestHandle(e,t,s=8,i=[...ne,"rotate"]){const r=y.handlePointsFor(e);for(const a of i)if(Math.hypot(t.x-r[a].x,t.y-r[a].y)<=s)return a}static HANDLE_DIRECTION_DEG={e:0,se:45,s:90,sw:135,w:180,nw:225,n:270,ne:315};static resizeAxisFor(e,t){const i=((y.HANDLE_DIRECTION_DEG[e]+t*180/Math.PI)%180+180)%180/45,r=["ew","nwse","ns","nesw"];return r[Math.round(i)%r.length]}static resizeBounds(e,t,s,i){const r=I(e),a=R(i,r,-t),{x:o,y:h,width:c,height:u}=e,l=Re[s];let d=o,f=h,m=o+c,p=h+u;return l.left&&(d=Math.min(a.x,m-L)),l.right&&(m=Math.max(a.x,d+L)),l.top&&(f=Math.min(a.y,p-L)),l.bottom&&(p=Math.max(a.y,f+L)),{x:d,y:f,width:m-d,height:p-f}}static resizeShape(e,t,s){if(t==="rotate")throw new Error("resizeShape does not accept the rotate handle");const{width:i,height:r}=e.bounds,a=y.resizeBounds(e.bounds,e.angle,t,s);if(e.kind==="oval")return{...e,bounds:a};const o=i===0?1:a.width/i,h=r===0?1:a.height/r;return{...e,bounds:a,points:e.points.map(c=>({x:c.x*o,y:c.y*h}))}}static rotateShape(e,t){const s=I(e.bounds),i=Math.atan2(t.y-s.y,t.x-s.x)+Math.PI/2;return{...e,angle:i}}static groupBoundsFor(e){const t=Math.min(...e.map(a=>a.x)),s=Math.min(...e.map(a=>a.y)),i=Math.max(...e.map(a=>a.x+a.width)),r=Math.max(...e.map(a=>a.y+a.height));return{x:t,y:s,width:i-t,height:r-s}}static vertexPointsFor(e){const t=I(e.bounds);return e.points.map(s=>R({x:e.bounds.x+s.x,y:e.bounds.y+s.y},t,e.angle))}static hitTestVertex(e,t,s=Le){const i=y.vertexPointsFor(e);let r,a=s;return i.forEach((o,h)=>{const c=Math.hypot(t.x-o.x,t.y-o.y);c<=a&&(a=c,r=h)}),r}static toLocalPoint(e,t){const s=I(e.bounds),i=R(t,s,-e.angle);return{x:i.x-e.bounds.x,y:i.y-e.bounds.y}}static moveVertex(e,t,s){const i=e.points.map((l,d)=>d===t?y.toLocalPoint(e,s):l),r=Math.min(...i.map(l=>l.x)),a=Math.min(...i.map(l=>l.y)),o=Math.max(...i.map(l=>l.x)),h=Math.max(...i.map(l=>l.y)),c={x:e.bounds.x+r,y:e.bounds.y+a,width:o-r,height:h-a},u=i.map(l=>({x:l.x-r,y:l.y-a}));return{...e,bounds:c,points:u}}static insertVertexNear(e,t){const s=y.toLocalPoint(e,t),{points:i}=e;let r=i.length-1,a=1/0;for(let h=0;h<i.length;h++){const c=i[h],u=i[(h+1)%i.length],l=y.distanceToSegment(s,c,u);l<a&&(a=l,r=h)}const o=[...i.slice(0,r+1),s,...i.slice(r+1)];return{...e,points:o}}static distanceToSegment(e,t,s){const i=s.x-t.x,r=s.y-t.y,a=i*i+r*r,o=a===0?0:Math.max(0,Math.min(1,((e.x-t.x)*i+(e.y-t.y)*r)/a)),h=t.x+o*i,c=t.y+o*r;return Math.hypot(e.x-h,e.y-c)}static deleteVertex(e,t){return e.points.length<=Ce?e:{...e,points:e.points.filter((s,i)=>i!==t)}}}const U=6,B=U/2,Be=20,_e=24,Oe=9,X=6,Fe=7,q=4;class Ne{constructor(e){this.ctx=e}starPoints=0;roll=0;clear(e,t){this.ctx.clearRect(0,0,e,t)}paintShape(e){this.ctx.save(),this.ctx.globalAlpha=1-e.transparency;const t=e.blur??0;t>0&&(this.ctx.filter=`blur(${(_e*t).toFixed(2)}px)`);const s=e.brightness??0;s>0&&this.paintDazzle(e,s),e.haloScale>0&&this.paintHalo(e),this.paintBase(e),this.ctx.restore(),e.selected&&this.paintSelectionHandles(e)}setStarPoints(e){this.starPoints=Math.max(0,Math.floor(e))}setRoll(e){this.roll=e}paintDazzle(e,t){const{x:s,y:i,width:r,height:a}=e.bounds,o=s+r/2,h=i+a/2,c=Math.max(r,a)/2,u=c*(1+Oe*t);if(u<=0)return;this.ctx.save(),this.ctx.globalCompositeOperation="lighter";const l=this.ctx.createRadialGradient(o,h,0,o,h,u);for(let d=0;d<=X;d++){const f=d/X,m=1/(1+24*f*f);this.ctx.globalAlpha=1,l.addColorStop(f,this.withAlpha(e.color,m*t*.55))}if(this.ctx.fillStyle=l,this.ctx.beginPath(),this.ctx.arc(o,h,u,0,2*Math.PI),this.ctx.fill(),this.starPoints>0){const d=c*(1+Fe*t);this.ctx.lineWidth=Math.max(1,c*.12),this.ctx.lineCap="round";for(let f=0;f<this.starPoints;f++){const m=f/this.starPoints*Math.PI*2+this.roll,p=o+Math.cos(m)*d,b=h+Math.sin(m)*d,S=this.ctx.createLinearGradient(o,h,p,b);S.addColorStop(0,this.withAlpha(e.color,t*.55)),S.addColorStop(.35,this.withAlpha(e.color,t*.22)),S.addColorStop(1,this.withAlpha(e.color,0)),this.ctx.strokeStyle=S,this.ctx.beginPath(),this.ctx.moveTo(o,h),this.ctx.lineTo(p,b),this.ctx.stroke()}}this.ctx.restore()}dazzledFill(e,t){if(t<=0)return e.color;const s=/^#([0-9a-f]{6})$/i.exec(e.color.trim());if(!s)return e.color;const i=Number.parseInt(s[1],16),r=a=>Math.round(a+(255-a)*t);return`rgb(${r(i>>16&255)}, ${r(i>>8&255)}, ${r(i&255)})`}withAlpha(e,t){const s=Math.max(0,Math.min(1,t)),i=/^#([0-9a-f]{6})$/i.exec(e.trim());if(!i)return e;const r=Number.parseInt(i[1],16);return`rgba(${r>>16&255}, ${r>>8&255}, ${r&255}, ${s.toFixed(3)})`}paintBase(e){if(this.ctx.fillStyle=this.dazzledFill(e,e.brightness??0),this.ctx.beginPath(),e.kind==="oval"){const{x:t,y:s,width:i,height:r}=e.bounds,a=i/2,o=r/2;this.ctx.ellipse(t+a,s+o,a,o,e.angle,0,2*Math.PI)}else{const{x:t,y:s,width:i,height:r}=e.bounds;this.ctx.save(),this.ctx.translate(t+i/2,s+r/2),this.ctx.rotate(e.angle),this.ctx.translate(-i/2,-r/2),e.points.forEach((a,o)=>{o===0?this.ctx.moveTo(a.x,a.y):this.ctx.lineTo(a.x,a.y)}),this.ctx.closePath(),this.ctx.restore()}this.ctx.fill()}paintHalo(e){this.ctx.save(),this.ctx.shadowColor=e.color,this.ctx.shadowBlur=Be*e.haloScale,this.paintBase(e),this.ctx.restore()}paintSelectionHandles(e){this.paintHandleFrame(y.handlePointsFor(e),{includeRotate:!0}),e.kind==="polygon"&&this.paintVertexHandles(e)}paintVertexHandles(e){this.ctx.fillStyle="#39f";for(const t of y.vertexPointsFor(e))this.ctx.beginPath(),this.ctx.ellipse(t.x,t.y,q,q,0,0,2*Math.PI),this.ctx.fill()}paintMemberOutline(e){this.paintOutline(y.handlePointsFor(e))}paintGroupHandles(e){this.paintHandleFrame(y.handlePointsFor({bounds:e,angle:0}),{includeRotate:!0})}paintOutline(e){this.ctx.strokeStyle="lightgray",this.ctx.beginPath(),this.ctx.moveTo(e.nw.x,e.nw.y),this.ctx.lineTo(e.ne.x,e.ne.y),this.ctx.lineTo(e.se.x,e.se.y),this.ctx.lineTo(e.sw.x,e.sw.y),this.ctx.closePath(),this.ctx.stroke()}paintHandleFrame(e,{includeRotate:t}){this.paintOutline(e),this.ctx.fillStyle="lightgray";for(const s of ne){const i=e[s];this.ctx.fillRect(i.x-B,i.y-B,U,U)}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,B+1,B+1,0,0,2*Math.PI),this.ctx.fill())}}const W=.05,K=.08,J=2,Z=3,He=14,ze=.02;class Ue{context;voice;voiceToken=0;buffers=new Map;noiseBuffer;crackleBuffer;requested;resume(){if(!this.context){if(typeof AudioContext>"u")return;try{this.context=new AudioContext}catch(e){console.warn("SightingAudio: Web Audio unavailable, the sighting stays silent:",e);return}}this.context.state==="suspended"&&this.context.resume()}setSound(e){if(this.requested=e,!this.context)return;const t=this.keyFor(e);if(!t){this.silence();return}if(this.voice?.key!==t){this.stopVoice();const s=++this.voiceToken;t.startsWith("src:")?this.buildRecordedVoice(e.src,t,s):this.voice=this.buildSynthesizedVoice(e,t)}this.applyTo(this.voice,e)}silence(){this.voiceToken++,this.stopVoice()}dispose(){this.silence(),this.context?.close(),this.context=void 0,this.buffers.clear(),this.noiseBuffer=void 0,this.crackleBuffer=void 0}keyFor(e){if(!(e.volume<=0))return e.src?`src:${e.src}`:e.kind==="none"?void 0:e.kind}applyTo(e,t){const s=this.context;if(!e||!s)return;const i=s.currentTime;e.volumeParam.setTargetAtTime(Math.max(0,Math.min(1,t.volume))*e.gainScale,i,W);for(const{param:r,ratio:a}of e.pitch)r.setTargetAtTime(Math.min(t.pitchHz*a,s.sampleRate/2-1),i,W)}buildSynthesizedVoice(e,t){const s=this.context;if(s)switch(e.kind){case"hum":return this.buildHum(s,t);case"whistle":return this.buildWhistle(s,t);case"rumble":return this.buildRumble(s,t);case"crackle":return this.buildCrackle(s,t);default:return}}buildHum(e,t){const s=e.createOscillator();s.type="sawtooth";const i=e.createOscillator();i.type="sawtooth";const r=e.createBiquadFilter();r.type="lowpass",r.Q.value=1;const a=e.createGain();return a.gain.value=0,s.connect(r),i.connect(r),r.connect(a).connect(e.destination),s.start(),i.start(),{key:t,nodes:[r,a],sources:[s,i],volumeParam:a.gain,gainScale:.16,pitch:[{param:s.frequency,ratio:1},{param:i.frequency,ratio:1.006},{param:r.frequency,ratio:6}]}}buildWhistle(e,t){const s=e.createOscillator();s.type="sine";const i=e.createOscillator();i.type="sine",i.frequency.value=5;const r=e.createGain(),a=e.createGain();return a.gain.value=0,i.connect(r).connect(s.frequency),s.connect(a).connect(e.destination),s.start(),i.start(),{key:t,nodes:[r,a],sources:[s,i],volumeParam:a.gain,gainScale:.22,pitch:[{param:s.frequency,ratio:1},{param:r.gain,ratio:.012}]}}buildRumble(e,t){const s=e.createBufferSource();s.buffer=this.brownNoise(e),s.loop=!0;const i=e.createBiquadFilter();i.type="lowpass",i.Q.value=.8;const r=e.createGain();return r.gain.value=0,s.connect(i).connect(r).connect(e.destination),s.start(),{key:t,nodes:[i,r],sources:[s],volumeParam:r.gain,gainScale:1.4,pitch:[{param:i.frequency,ratio:1}]}}buildCrackle(e,t){const s=e.createBufferSource();s.buffer=this.whiteNoise(e),s.loop=!0;const i=e.createBiquadFilter();i.type="bandpass",i.Q.value=1.2;const r=e.createGain();r.gain.value=0;const a=e.createBufferSource();a.buffer=this.crackleEnvelope(e),a.loop=!0;const o=e.createGain();return o.gain.value=0,a.connect(o).connect(r.gain),s.connect(i).connect(r).connect(e.destination),s.start(),a.start(),{key:t,nodes:[i,r,o],sources:[s,a],volumeParam:o.gain,gainScale:.9,pitch:[{param:i.frequency,ratio:4}]}}async buildRecordedVoice(e,t,s){const i=this.context;if(!i)return;let r;try{r=await this.getBuffer(e)}catch(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:t,nodes:[o],sources:[a],volumeParam:o.gain,gainScale:1,pitch:[]},this.requested&&this.applyTo(this.voice,this.requested)}async getBuffer(e){const t=this.context;if(!t)throw new Error("SightingAudio: AudioContext not ready — resume() must be called first");let s=this.buffers.get(e);return s||(s=fetch(e).then(i=>{if(!i.ok)throw new Error(`Audio fetch failed (${i.status}): ${e}`);return i.arrayBuffer()}).then(i=>t.decodeAudioData(i)),this.buffers.set(e,s)),s}whiteNoise(e){if(!this.noiseBuffer){const t=e.createBuffer(1,Math.floor(e.sampleRate*J),e.sampleRate),s=t.getChannelData(0);for(let i=0;i<s.length;i++)s[i]=Math.random()*2-1;this.noiseBuffer=t}return this.noiseBuffer}brownNoise(e){const t=e.createBuffer(1,Math.floor(e.sampleRate*J),e.sampleRate),s=t.getChannelData(0);let i=0,r=0;for(let a=0;a<s.length;a++)i=(i+.02*(Math.random()*2-1))/1.02,s[a]=i,r=Math.max(r,Math.abs(i));if(r>0)for(let a=0;a<s.length;a++)s[a]/=r;return t}crackleEnvelope(e){if(!this.crackleBuffer){const t=e.createBuffer(1,Math.floor(e.sampleRate*Z),e.sampleRate),s=t.getChannelData(0),i=Math.max(1,Math.floor(ze*e.sampleRate)),r=Math.round(Z*He);for(let a=0;a<r;a++){const o=Math.floor(Math.random()*s.length),h=.4+Math.random()*.6;for(let c=0;c<i&&o+c<s.length;c++)s[o+c]=Math.max(s[o+c],h*(1-c/i))}this.crackleBuffer=t}return this.crackleBuffer}stopVoice(){const e=this.voice,t=this.context;if(this.voice=void 0,!e||!t)return;const s=t.currentTime;e.volumeParam.cancelScheduledValues(s),e.volumeParam.setTargetAtTime(0,s,K/3);const i=s+K;for(const r of e.sources){r.onended=()=>{r.disconnect();for(const a of e.nodes)a.disconnect()};try{r.stop(i)}catch{r.disconnect();for(const a of e.nodes)a.disconnect()}}}}const H=Math.PI/180,_=180/Math.PI;class M{constructor(e,t,s){this.kind=e,this.canvasHeightPx=t,this.fovDeg=s;const i=s*H/2;this.focalPx=e==="equidistant"?t/2/i:t/2/Math.tan(i)}focalPx;static of(e,t,s){return new M(e.projection,t,s)}degToPx(e){const t=e*H;return this.kind==="equidistant"?this.focalPx*t:2*this.focalPx*Math.tan(t/2)}pxToDeg(e){return this.kind==="equidistant"?e/this.focalPx*_:2*Math.atan(e/(2*this.focalPx))*_}angleDegToRadiusPx(e){const t=e*H;return this.kind==="equidistant"?this.focalPx*t:this.focalPx*Math.tan(t)}radiusPxToAngleDeg(e){return this.kind==="equidistant"?e/this.focalPx*_:Math.atan(e/this.focalPx)*_}ofBounds(e){return{widthDeg:this.pxToDeg(e.width),heightDeg:this.pxToDeg(e.height)}}toBoundsSize(e){return{width:this.degToPx(e.widthDeg),height:this.degToPx(e.heightDeg)}}widthPx(e){return this.degToPx(x.angularWidthDeg(e))}}class re{static fovOf(e,t){return ie(e,t)?.fovDeg??v.fieldOfViewDeg(e.instrument)}static toAngular(e){this.eachKeyframe(e,(t,s)=>({...t,angular:s.ofBounds(t.bounds)}))}static toBounds(e){this.eachKeyframe(e,(t,s)=>{if(!t.angular)return t;const{width:i,height:r}=s.toBoundsSize(t.angular),a={x:t.bounds.x+(t.bounds.width-i)/2,y:t.bounds.y+(t.bounds.height-r)/2,width:i,height:r};if(t.kind!=="polygon")return{...t,bounds:a};const o=t.bounds.width===0?1:i/t.bounds.width,h=t.bounds.height===0?1:r/t.bounds.height;return{...t,bounds:a,points:t.points.map(c=>({x:c.x*o,y:c.y*h}))}})}static reproject(e,t,s){const i=v.frameWidthPx(t,x.CANVAS_HEIGHT_PX)/2,r=v.frameWidthPx(e.instrument,x.CANVAS_HEIGHT_PX)/2,a=x.CANVAS_HEIGHT_PX/2;for(const o of[...e.timeline.allKeyframes]){const h=this.fovOf(e,o.t),c=s?.get(o.t)??h,u=M.of(t,x.CANVAS_HEIGHT_PX,c),l=M.of(e.instrument,x.CANVAS_HEIGHT_PX,h);e.timeline.addKeyframe(o.t,o.shapes.map(d=>{const{bounds:f}=d.shape,m=f.x+f.width/2-i,p=f.y+f.height/2-a,b=Math.hypot(m,p),S=b===0?1:l.angleDegToRadiusPx(u.radiusPxToAngleDeg(b))/b,oe={...f,x:r+m*S-f.width/2,y:a+p*S-f.height/2};return{...d,shape:{...d.shape,bounds:oe}}}))}this.toBounds(e)}static eachKeyframe(e,t){const{timeline:s}=e;for(const i of[...s.allKeyframes]){const r=this.fovOf(e,i.t),a=M.of(e.instrument,x.CANVAS_HEIGHT_PX,r);s.addKeyframe(i.t,i.shapes.map(o=>({...o,shape:t(o.shape,a)})))}}}function Ge(n){return re.toAngular(n),{version:1,time:n.event.time,endTime:n.event.endTime,durationSeconds:n.event.durationSeconds,utcOffsetHours:n.event.utcOffsetHours,timeZone:n.event.timeZone,place:n.event.place,witness:n.witness,caseId:n.caseId,description:n.event.description,tags:n.event.tags,timeline:n.timeline.toJSON(),witnessTrack:n.witnessTrack.toJSON(),weatherTrack:n.weatherTrack.toJSON(),soundTrack:n.soundTrack.toJSON(),weather:n.weather,decor:n.decor,weatherSource:n.weatherSource,instrument:n.instrumentId,exposureSeconds:n.exposureSeconds}}function $e(n){const e=new N({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},F.fromJSON(n.timeline),n.witnessTrack?P.fromJSON(n.witnessTrack):new P,n.weatherTrack?A.fromJSON(n.weatherTrack):new A,n.soundTrack?D.fromJSON(n.soundTrack):new D,n.witness,n.caseId,n.weather,n.decor??[],n.weatherSource,n.instrument,n.exposureSeconds??n.witnessTrack?.keyframes.map(t=>t.pose.exposureSeconds).find(t=>t!==void 0));return re.toBounds(e),e}function Ve(n,e){for(const t of n){const s=t.toLowerCase().split("-")[0];if(e.includes(s))return s}return"en"}class Xe{static preferencesFor(e){const t=navigator.languages??[],s=e.closest("[lang]")?.getAttribute("lang")?.trim();return s?[s,...t]:t}}const qe="modulepreload",We=function(n,e){return new URL(n,e).href},Y={},j=function(e,t,s){let i=Promise.resolve();if(t&&t.length>0){let a=function(u){return Promise.all(u.map(l=>Promise.resolve(l).then(d=>({status:"fulfilled",value:d}),d=>({status:"rejected",reason:d}))))};const o=document.getElementsByTagName("link"),h=document.querySelector("meta[property=csp-nonce]"),c=h?.nonce||h?.getAttribute("nonce");i=a(t.map(u=>{if(u=We(u,s),u in Y)return;Y[u]=!0;const l=u.endsWith(".css"),d=l?'[rel="stylesheet"]':"";if(!!s)for(let p=o.length-1;p>=0;p--){const b=o[p];if(b.href===u&&(!l||b.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${u}"]${d}`))return;const m=document.createElement("link");if(m.rel=l?"stylesheet":qe,l||(m.as="script"),m.crossOrigin="",m.href=u,c&&m.setAttribute("nonce",c),document.head.appendChild(m),l)return new Promise((p,b)=>{m.addEventListener("load",p),m.addEventListener("error",()=>b(new Error(`Unable to preload CSS for ${u}`)))})}))}function r(a){const o=new Event("vite:preloadError",{cancelable:!0});if(o.payload=a,window.dispatchEvent(o),!o.defaultPrevented)throw a}return i.then(a=>{for(const o of a||[])o.status==="rejected"&&r(o.reason);return e().catch(r)})},Ke=["en","fr"],Je={en:()=>j(()=>Promise.resolve().then(()=>Ye),void 0,import.meta.url).then(n=>n.ufoMessages_en),fr:()=>j(()=>import("./UfoMessages_fr-Hq24iVzT.js"),[],import.meta.url).then(n=>n.ufoMessages_fr)};function Ze(n){return Je[n]()}const ae={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"},Ye=Object.freeze(Object.defineProperty({__proto__:null,ufoMessages_en:ae},Symbol.toStringTag,{value:"Module"})),Q=new Set;class k extends HTMLElement{static get observedAttributes(){return["src"]}shadow;stageElement;canvas;canvasRenderer;tooltip;toolbar;playPauseButton;loopButton;fullscreenButton;seekInput;timeStartLabel;timeEndLabel;currentSighting=N.create();sightingAudio=new Ue;soundPreview;player;loopEnabled=!0;playbackBeforeClick;highlightedSourceIds=new Set;occludedSourceIds=Q;enableClickToPlay=!0;fullscreenTarget;messages=ae;realDurationMs;realStartMs;showClockTime=!0;handleFullscreenChange=()=>this.updateFullscreenButton();handlePointerMove=e=>{const t=this.canvasPointFromEvent(e),s=t&&this.shapeAt(t.x,t.y);if(!s?.shape.title){this.tooltip.hidden=!0;return}this.tooltip.textContent=s.shape.title,this.tooltip.hidden=!1;const i=this.stageElement.getBoundingClientRect();this.tooltip.style.left=`${e.clientX-i.left+12}px`,this.tooltip.style.top=`${e.clientY-i.top+12}px`};handlePointerLeave=()=>{this.tooltip.hidden=!0};constructor(){super(),this.shadow=this.attachShadow({mode:"open"});const e=document.createElement("template");e.innerHTML=`<style>${ce}</style>${he}`,this.shadow.appendChild(e.content.cloneNode(!0)),this.stageElement=this.shadow.getElementById("stage"),this.canvas=this.shadow.getElementById("canvas"),this.canvasRenderer=new Ne(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");for(const t of[this.timeStartLabel,this.timeEndLabel])t.addEventListener("click",()=>this.toggleTimeDisplay()),t.addEventListener("keydown",s=>{s.key!=="Enter"&&s.key!==" "||(s.preventDefault(),this.toggleTimeDisplay())});this.fullscreenTarget=this.stageElement,this.playPauseButton.addEventListener("click",()=>this.togglePlayPause()),this.loopButton.addEventListener("click",()=>this.toggleLoop()),this.fullscreenButton.addEventListener("click",()=>this.toggleFullscreen()),this.seekInput.addEventListener("input",()=>this.player.seek(Number(this.seekInput.value))),this.canvas.addEventListener("click",t=>{this.enableClickToPlay&&(t.detail<=1&&(this.playbackBeforeClick={state:this.playbackState,time:this.currentTime}),this.togglePlayPause())}),this.canvas.addEventListener("dblclick",t=>{this.enableClickToPlay&&(t.preventDefault(),this.restorePlayback(),this.toggleFullscreen())}),this.canvas.addEventListener("pointermove",this.handlePointerMove),this.canvas.addEventListener("pointerleave",this.handlePointerLeave),document.addEventListener("fullscreenchange",this.handleFullscreenChange),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.updateFullscreenButton(),this.refresh(),this.loadLocaleMessages()}connectedCallback(){const e=this.getAttribute("src");e&&this.loadFromSrc(e)}disconnectedCallback(){document.removeEventListener("fullscreenchange",this.handleFullscreenChange),this.sightingAudio.dispose()}attributeChangedCallback(e,t,s){e==="src"&&s&&s!==t&&this.isConnected&&this.loadFromSrc(s)}async loadFromSrc(e){this.sightingData=await le.json(e)}get sightingData(){return Ge(this.currentSighting)}set sightingData(e){this.player.stop(),this.soundPreview=void 0,this.sightingAudio.silence(),this.currentSighting=$e(e),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.refresh()}get sighting(){return this.currentSighting}previewSound(e){this.soundPreview=e,this.sightingAudio.resume(),this.sightingAudio.setSound(e)}stopSoundPreview(){this.soundPreview=void 0,this.sightingAudio.silence()}get canvasElement(){return this.canvas}get renderer(){return this.canvasRenderer}get currentTime(){return this.player.time}set currentTime(e){this.player.seek(e)}get seekableDuration(){return this.player.seekableDuration}get autoReplayEnabled(){return this.loopEnabled}set autoReplayEnabled(e){e!==this.loopEnabled&&this.toggleLoop()}play(){this.player.seekableDuration<=0||this.playbackState==="playing"||this.togglePlayPause()}pause(){this.playbackState==="playing"&&this.togglePlayPause()}get positionLabel(){return this.timeStartLabel.textContent??""}get durationLabel(){return this.timeEndLabel.textContent??""}get showingClockTime(){return this.canShowClockTime}get canSwitchTimeDisplay(){return this.realStartMs!==void 0}toggleTimeDisplay(){this.canSwitchTimeDisplay&&(this.showClockTime=!this.showClockTime,this.updateTimeLabels(),this.updateTimeLabelTitles(),this.dispatchEvent(new CustomEvent("timedisplaychange",{bubbles:!0,composed:!0})))}get canShowClockTime(){return this.realStartMs!==void 0&&this.showClockTime}set showToolbar(e){this.toolbar.classList.toggle("hidden",!e)}get playbackState(){return this.player.playbackState}get selectedSourceIds(){return this.highlightedSourceIds}get durationSeconds(){return this.currentSighting.event.durationSeconds}set durationSeconds(e){this.currentSighting.event.durationSeconds=e,this.updateTimeLabels(),this.refresh(),this.updatePlayPauseButton()}set selectedSourceIds(e){const t=new Set(e);t.size===this.highlightedSourceIds.size&&[...t].every(i=>this.highlightedSourceIds.has(i))||(this.highlightedSourceIds=t,this.refresh())}setOccludedSourceIds(e){e.size===this.occludedSourceIds.size&&[...e].every(s=>this.occludedSourceIds.has(s))||(this.occludedSourceIds=e,this.refresh())}hasVisibleShapeAt(e,t){return this.shapeAt(e,t)!==void 0}refresh(){this.applyFrameFormat(),this.updateTimeLabels(),this.seekInput.max=String(this.player.seekableDuration),this.player.seek(this.player.time)}exposureInstants(e){const t=this.exposureTimes(e),s=1/t.length;return t.map(i=>({shapes:this.shapesAt(i),share:s}))}exposureTimes(e=this.currentTime){const t=(this.currentSighting.exposure??0)*1e3;if(t<k.SHORTEST_VISIBLE_EXPOSURE_MS)return[e];const s=Math.min(k.MAX_EXPOSURE_STEPS,Math.max(2,Math.round(t/k.SHORTEST_VISIBLE_EXPOSURE_MS))),i=Math.ceil(this.travelPxOver(e,t)/k.EXPOSURE_STEP_PX),r=Math.min(k.MAX_TRAVEL_STEPS,Math.max(s,i)),a=[];for(let o=0;o<r;o++)a.push(e+t*o/r);return a}travelPxOver(e,t){const s=this.shapesAt(e),i=this.shapesAt(e+t);let r=0;for(const[a,o]of s){const h=i.get(a);if(!h)continue;const c=h.bounds.x+h.bounds.width/2-(o.bounds.x+o.bounds.width/2),u=h.bounds.y+h.bounds.height/2-(o.bounds.y+o.bounds.height/2);r=Math.max(r,Math.hypot(c,u))}return r}shapeAt(e,t,s=this.occludedSourceIds){for(const i of this.exposureTimes()){const r=this.currentSighting.timeline.hitTest(i,e,t,s);if(r)return r}}shapesAt(e){const t=new Map;for(const s of this.currentSighting.timeline.sourceIds){const i=this.currentSighting.timeline.getInterpolatedShapeAt(e,s);i&&t.set(s,i)}return t}static SHORTEST_VISIBLE_EXPOSURE_MS=20;static MAX_EXPOSURE_STEPS=48;static EXPOSURE_STEP_PX=2;static MAX_TRAVEL_STEPS=320;applyFrameFormat(){const e=v.frameWidthPx(this.currentSighting.instrument,this.canvas.height);if(this.canvas.width===e)return;this.canvas.width=e,this.canvas.parentElement?.style.setProperty("--frame-aspect",`${e} / ${this.canvas.height}`)}canvasPointFromEvent(e){const t=this.canvas.getBoundingClientRect();if(!(t.width===0||t.height===0))return{x:(e.clientX-t.left)/t.width*this.canvas.width,y:(e.clientY-t.top)/t.height*this.canvas.height}}onFrame(e,t){this.canvasRenderer.clear(this.canvas.width,this.canvas.height);const s=this.playbackState!=="playing"?this.highlightedSourceIds:Q;this.canvasRenderer.setStarPoints(v.starPointsOf(this.sighting.instrument)),this.canvasRenderer.setRoll((ie(this.sighting,e)?.rollDeg??0)*Math.PI/180);const i=this.exposureInstants(e);for(const r of i)for(const[a,o]of r.shapes){if(this.occludedSourceIds.has(a))continue;const h=r.share,c=h===1?o:{...o,transparency:1-(1-o.transparency)*h};this.canvasRenderer.paintShape(c)}for(const[r,a]of i[0].shapes)this.occludedSourceIds.has(r)||!s.has(r)||(s.size===1?this.canvasRenderer.paintShape({...a,selected:!0}):this.canvasRenderer.paintMemberOutline(a));if(s.size>1){const r=y.groupBoundsFor([...t].filter(([a])=>s.has(a)).map(([,a])=>a.bounds));this.canvasRenderer.paintGroupHandles(r)}this.seekInput.value=String(e),this.timeStartLabel.textContent=this.formatPosition(e),this.playbackState==="playing"?(this.soundPreview=void 0,this.sightingAudio.setSound(Pe(this.currentSighting,e))):this.soundPreview?this.sightingAudio.setSound(this.soundPreview):this.sightingAudio.silence(),this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{time:e}}))}createPlayer(){const e=new Ae(this.currentSighting.timeline,(t,s)=>this.onFrame(t,s));return e.loop=this.loopEnabled,e.onEnded=()=>{this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("ended",{bubbles:!0,composed:!0}))},e}togglePlayPause(){this.player.seekableDuration<=0||(this.player.playbackState==="playing"?(this.player.pause(),this.soundPreview=void 0,this.sightingAudio.silence(),this.refresh()):(this.sightingAudio.resume(),this.player.play()),this.updatePlayPauseButton())}updatePlayPauseButton(){const e=this.player.playbackState==="playing";this.playPauseButton.textContent=e?"⏸":"▶";const t=this.player.seekableDuration>0;this.playPauseButton.disabled=!t;const s=t?e?this.messages.pause:this.messages.play:this.messages.noDuration;this.playPauseButton.title=s,this.playPauseButton.setAttribute("aria-label",s),this.toolbar.classList.toggle("auto-hide",e),this.fullscreenButton.classList.toggle("auto-hide",e)}toggleLoop(){this.loopEnabled=!this.loopEnabled,this.loopButton.setAttribute("aria-pressed",String(this.loopEnabled)),this.player.loop=this.loopEnabled}restorePlayback(){const e=this.playbackBeforeClick;e&&(this.currentTime=e.time,e.state==="playing"?this.play():this.pause())}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=Ve(Xe.preferencesFor(this),Ke);e!=="en"&&this.applyMessages(await Ze(e))}updateTimeLabelTitles(){const e=this.showClockTime?this.messages.switchToElapsed:this.messages.switchToClockTime,t=this.canSwitchTimeDisplay?` — ${e}`:"";this.timeStartLabel.title=this.messages.currentPosition+t,this.timeEndLabel.title=this.messages.duration+t;for(const s of[this.timeStartLabel,this.timeEndLabel])s.classList.toggle("switchable",this.canSwitchTimeDisplay),this.canSwitchTimeDisplay?(s.setAttribute("role","button"),s.setAttribute("tabindex","0")):(s.removeAttribute("role"),s.removeAttribute("tabindex"))}applyMessages(e){this.messages=e,this.updateTimeLabelTitles(),this.loopButton.title=e.autoReplay,this.loopButton.setAttribute("aria-label",e.autoReplay),this.updatePlayPauseButton(),this.updateFullscreenButton()}updateTimeLabels(){const e=this.currentSighting.event,t=Ee(e);this.realDurationMs=t!==void 0&&t>0?t:void 0,this.realStartMs=e.time?z(e.time):void 0;const s=this.currentSighting.timeline.duration;this.player.playbackRate=this.realDurationMs!==void 0&&s>0?s/this.realDurationMs:1,this.player.durationOverrideMs=s>0?0:this.realDurationMs??0,this.timeEndLabel.textContent=this.formatEndOfTimeline(),this.timeStartLabel.textContent=this.formatPosition(this.player.time),this.updateTimeLabelTitles()}formatPosition(e){if(this.realDurationMs===void 0)return O(e);const t=this.currentSighting.timeline.duration,s=t>0&&e<=t?e/t*this.realDurationMs:e;return this.canShowClockTime?te(ee(this.realStartMs+s)):O(s)}formatEndOfTimeline(){return this.realDurationMs===void 0?O(this.currentSighting.timeline.duration):this.canShowClockTime?te(ee(this.realStartMs+this.realDurationMs)):O(this.realDurationMs)}}function ee(n){const e=new Date(n);return{hour:e.getUTCHours(),minute:e.getUTCMinutes(),second:e.getUTCSeconds()}}function te(n){if(n.hour===void 0)return"0:00";const e=t=>String(t).padStart(2,"0");return n.second?`${e(n.hour)}:${e(n.minute??0)}:${e(n.second)}`:`${e(n.hour)}:${e(n.minute??0)}`}function O(n){const e=Math.round(n/1e3),t=Math.floor(e/60),s=e%60;return`${t}:${String(s).padStart(2,"0")}`}const se="rr0-ufo";function je(){customElements.get(se)||customElements.define(se,k)}je();
|
|
314
|
+
`;class T extends Error{constructor(e,t,s){super(`${e}: ${t}${s===void 0?"":` (HTTP ${s})`}`),this.kind=e,this.url=t,this.status=s,this.name="SightingFetchError"}}class de{static async json(e){const t=new URL(e,location.href);if(location.protocol==="https:"&&t.protocol==="http:")throw new T("mixed-content",e);let s;try{s=await fetch(e)}catch{throw await this.diagnose(e,t)}if(!s.ok)throw new T("status",e,s.status);try{return await s.json()}catch{throw new T("malformed",e)}}static async diagnose(e,t){if(t.origin===location.origin)return new T("unreachable",e);try{return await fetch(e,{mode:"no-cors"}),new T("cors",e)}catch{return new T("unreachable",e)}}}const C=[{id:"eye",name:{en:"Naked eye",fr:"Œil nu"},flare:0,projection:"equidistant"},{id:"rectilinear-lens",name:{en:"Camera, unknown device",fr:"Appareil, modèle inconnu"},projection:"rectilinear",fNumber:8,fNumberRange:{min:2,max:22},exposureSeconds:1/250,exposureRangeSeconds:{min:1/1e3,max:8},apertureBlades:6},{id:"instamatic-126",name:{en:"Instamatic, 126 film",fr:"Instamatic, film 126"},projection:"rectilinear",frame:{widthMm:28,heightMm:28,focalLengthMm:43},fNumber:11,exposureSeconds:1/90,years:{from:1963,to:1988},apertureBlades:5,detailUm:20},{id:"slr-35mm-50",name:{en:"35 mm SLR, 50 mm lens",fr:"Reflex 35 mm, objectif 50 mm"},projection:"rectilinear",frame:{widthMm:36,heightMm:24,focalLengthMm:50},fNumber:8,fNumberRange:{min:2,max:16},exposureSeconds:1/250,exposureRangeSeconds:{min:1/1e3,max:3600},years:{from:1959},apertureBlades:6,detailUm:20},{id:"slr-35mm-zoom",name:{en:"35 mm SLR, 70-210 mm zoom",fr:"Reflex 35 mm, zoom 70-210 mm"},projection:"rectilinear",frame:{widthMm:36,heightMm:24,focalLengthMm:135,focalRangeMm:{minMm:70,maxMm:210}},fNumber:8,fNumberRange:{min:4,max:22},exposureSeconds:1/250,exposureRangeSeconds:{min:1/1e3,max:3600},apertureBlades:8,years:{from:1975},detailUm:20},{id:"phone-landscape",name:{en:"Phone, held sideways",fr:"Téléphone, tenu couché"},projection:"rectilinear",frame:{widthMm:7.6,heightMm:5.7,focalLengthMm:5.7},fNumber:1.8,exposureSeconds:1/120,exposureRangeSeconds:{min:1/8e3,max:10},years:{from:2007},apertureBlades:void 0,detailUm:1.4},{id:"phone-portrait",name:{en:"Phone, held upright",fr:"Téléphone, tenu debout"},projection:"rectilinear",frame:{widthMm:5.7,heightMm:7.6,focalLengthMm:5.7},fNumber:1.8,exposureSeconds:1/120,exposureRangeSeconds:{min:1/8e3,max:10},years:{from:2007},apertureBlades:void 0,detailUm:1.4}];class v{static get default(){return C[0]}static byId(e){return C.find(t=>t.id===e)??this.default}static UNAIDED_FIELD_DEG=60;static UNAIDED_ASPECT=16/9;static fieldOfViewDeg(e){const t=e.frame;return t?2*Math.atan(t.heightMm/(2*t.focalLengthMm))*180/Math.PI:v.UNAIDED_FIELD_DEG}static aspectOf(e){const t=e.frame;return t?t.widthMm/t.heightMm:v.UNAIDED_ASPECT}static frameWidthPx(e,t){return Math.round(t*v.aspectOf(e))}static availableAt(e){return e===void 0?C:C.filter(t=>!t.years||e>=t.years.from&&(t.years.to===void 0||e<=t.years.to))}static fieldOfViewDegAt(e,t){const s=e.frame;if(!(!s||t<=0))return 2*Math.atan(s.heightMm/(2*t))*180/Math.PI}static focalLengthMmFor(e,t){const s=e.frame;if(!(!s||t<=0||t>=180))return s.heightMm/(2*Math.tan(t*Math.PI/360))}static bladesShowing(e,t){const s=t??e.fNumber;if(s===void 0||e.apertureBlades===void 0)return 0;const i=e.fNumberRange;return!i||i.max<=i.min?1:Math.max(0,Math.min(1,(s-i.min)/(i.max-i.min)))}static LENS_FLARE_ARTIFACTS=1;static flareArtifactsOf(e){return e.flare??v.LENS_FLARE_ARTIFACTS}static starPointsOf(e){const t=e.apertureBlades;return t===void 0||t<3?0:t%2===0?t:t*2}}class w{static MOON_ANGULAR_WIDTH_DEG=.5237;static CANVAS_WIDTH_PX=640;static CANVAS_HEIGHT_PX=360;static angularWidthDeg(e){return 2*Math.atan(e.sizeM/(2*e.distanceM))*180/Math.PI}static inMoons(e){return e/w.MOON_ANGULAR_WIDTH_DEG}static lerpAngular(e,t,s){if(!(!e||!t))return{widthDeg:e.widthDeg+(t.widthDeg-e.widthDeg)*s,heightDeg:e.heightDeg+(t.heightDeg-e.heightDeg)*s}}static sizeMAt(e,t){return 2*e*Math.tan(t*Math.PI/360)}static distanceMAt(e,t){return e/(2*Math.tan(t*Math.PI/360))}}function ue(n,e,t){const{bounds:s}=n;return e>=s.x&&e<=s.x+s.width&&t>=s.y&&t<=s.y+s.height}function g(n,e,t){return n+(e-n)*t}function G(n){const e=/^#([0-9a-f]{6})$/i.exec(n);if(!e)return;const t=parseInt(e[1],16);return[t>>16&255,t>>8&255,t&255]}function fe(n){return`#${n.map(e=>Math.round(e).toString(16).padStart(2,"0")).join("")}`}function me(n,e,t){const s=G(n),i=G(e);return!s||!i?t<1?n:e:fe([g(s[0],i[0],t),g(s[1],i[1],t),g(s[2],i[2],t)])}function pe(n,e,t){const s={x:g(n.bounds.x,e.bounds.x,t),y:g(n.bounds.y,e.bounds.y,t),width:g(n.bounds.width,e.bounds.width,t),height:g(n.bounds.height,e.bounds.height,t)},i=g(n.angle,e.angle,t),r=g(n.transparency,e.transparency,t),o=g(n.haloScale,e.haloScale,t),a=g(n.blur??0,e.blur??0,t),l=g(n.brightness??0,e.brightness??0,t),h=me(n.color,e.color,t),d=w.lerpAngular(n.angular,e.angular,t);return n.kind==="polygon"&&e.kind==="polygon"&&n.points.length===e.points.length?{...n,bounds:s,angle:i,transparency:r,haloScale:o,blur:a,brightness:l,color:h,angular:d,behindCloud:t<1?n.behindCloud:e.behindCloud,points:n.points.map((c,u)=>({x:g(c.x,e.points[u].x,t),y:g(c.y,e.points[u].y,t)}))}:{...t<1?n:e,bounds:s,angle:i,transparency:r,haloScale:o,blur:a,brightness:l,color:h,angular:d}}class O{keyframes=[];order=[];groups=[];addKeyframe(e,t){const s=this.findInsertIndex(e);if(this.keyframes[s]?.t===e){const i=new Set(t.map(r=>r.sourceId));this.keyframes[s]={t:e,shapes:[...this.keyframes[s].shapes.filter(r=>!i.has(r.sourceId)),...t]}}else this.keyframes.splice(s,0,{t:e,shapes:[...t]});for(const i of t)this.order.includes(i.sourceId)||this.order.push(i.sourceId)}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}return t}getKeyframeAt(e){const t=this.findInsertIndex(e),s=this.keyframes[t];return s?.t===e?s:void 0}getShapeAt(e,t){return this.getKeyframeAt(e)?.shapes.find(s=>s.sourceId===t)?.shape}getLatestShapeAt(e,t){let s=this.findInsertIndex(e);for(this.keyframes[s]?.t!==e&&(s-=1);s>=0;s--){const i=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(i)return i.shape}}getInterpolatedShapeAt(e,t){const s=this.findShapeAtOrBefore(e,t);if(s?.t===e)return s.shape;const i=this.findShapeAtOrAfter(e,t);return s?i?pe(s.shape,i.shape,(e-s.t)/(i.t-s.t)):s.shape:i?.shape}findShapeAtOrBefore(e,t){let s=this.findInsertIndex(e);for(this.keyframes[s]?.t!==e&&(s-=1);s>=0;s--){const i=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(i)return{t:this.keyframes[s].t,shape:i.shape}}}findShapeAtOrAfter(e,t){for(let s=this.findInsertIndex(e);s<this.keyframes.length;s++){const i=this.keyframes[s].shapes.find(r=>r.sourceId===t);if(i)return{t:this.keyframes[s].t,shape:i.shape}}}hitTest(e,t,s,i){const r=this.sourceIds;for(let o=r.length-1;o>=0;o--){if(i?.has(r[o]))continue;const a=this.getInterpolatedShapeAt(e,r[o]);if(a&&ue(a,t,s))return{sourceId:r[o],shape:a}}}removeSource(e){for(let t=this.keyframes.length-1;t>=0;t--){const s=this.keyframes[t].shapes.filter(i=>i.sourceId!==e);s.length===0?this.keyframes.splice(t,1):s.length!==this.keyframes[t].shapes.length&&(this.keyframes[t]={t:this.keyframes[t].t,shapes:s})}this.order=this.order.filter(t=>t!==e),this.removeFromGroup(e)}group(e){if(!(e.length<2)){for(const t of e)this.removeFromGroup(t);this.groups.push([...e])}}ungroup(e){const t=this.groups.findIndex(s=>s.includes(e));t!==-1&&this.groups.splice(t,1)}groupMembers(e){return this.groups.find(t=>t.includes(e))}removeFromGroup(e){const t=this.groups.findIndex(i=>i.includes(e));if(t===-1)return;const s=this.groups[t].filter(i=>i!==e);s.length<2?this.groups.splice(t,1):this.groups[t]=s}bringToFront(e){const t=this.order.indexOf(e);t!==-1&&(this.order.splice(t,1),this.order.push(e))}sendToBack(e){const t=this.order.indexOf(e);t!==-1&&(this.order.splice(t,1),this.order.unshift(e))}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get sourceIds(){return[...this.order]}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes,order:this.order,groups:this.groups}}static fromJSON(e){const t=new O;for(const s of e.keyframes)t.addKeyframe(s.t,s.shapes);if(e.order){const s=t.order.filter(i=>!e.order.includes(i));t.order=[...e.order,...s]}return e.groups&&(t.groups=e.groups.map(s=>s.filter(i=>t.order.includes(i))).filter(s=>s.length>=2)),t}}function ge(n,e,t){return Math.max(e,Math.min(t,n))}function ye(n,e,t){const s=((e-n)%360+540)%360-180;return((n+s*t)%360+360)%360}function E(n,e,t){return n+(e-n)*t}function ve(n,e,t){return{lat:n.lat===void 0||e.lat===void 0?void 0:E(n.lat,e.lat,t),lng:n.lng===void 0||e.lng===void 0?void 0:E(n.lng,e.lng,t),elevationM:E(n.elevationM,e.elevationM,t),headingDeg:n.headingDeg===void 0||e.headingDeg===void 0?void 0:ye(n.headingDeg,e.headingDeg,t),pitchDeg:E(n.pitchDeg,e.pitchDeg,t),rollDeg:E(n.rollDeg??0,e.rollDeg??0,t),fovDeg:E(n.fovDeg,e.fovDeg,t),fNumber:n.fNumber,focusDistanceM:n.focusDistanceM}}class I{keyframes=[];addKeyframe(e,t){const s=this.findInsertIndex(e);this.keyframes[s]?.t===e?this.keyframes[s]={t:e,pose:t}:this.keyframes.splice(s,0,{t:e,pose:t})}clear(){this.keyframes.length=0}removeKeyframeAt(e){const t=this.findInsertIndex(e);this.keyframes[t]?.t===e&&this.keyframes.splice(t,1)}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}return t}getLatestPoseAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].pose:void 0}getInterpolatedPoseAt(e){const t=this.findInsertIndex(e),s=this.keyframes[t]?.t===e?this.keyframes[t]:this.keyframes[t-1];if(s?.t===e)return s.pose;const i=this.keyframes[t]?.t===e?void 0:this.keyframes[t];return s?i?ve(s.pose,i.pose,ge((e-s.t)/(i.t-s.t),0,1)):s.pose:i?.pose}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes}}static fromJSON(e){const t=new I;for(const s of e.keyframes)t.addKeyframe(s.t,s.pose);return t}}function x(n,e,t){return n+(e-n)*t}function be(n,e,t){const s=((e-n)%360+540)%360-180;return((n+s*t)%360+360)%360}function we(n,e,t){return Math.max(e,Math.min(t,n))}function xe(n,e,t){return{cloudCover:x(n.cloudCover,e.cloudCover,t),cloudDarkness:x(n.cloudDarkness,e.cloudDarkness,t),highCloudCover:n.highCloudCover===void 0||e.highCloudCover===void 0?void 0:x(n.highCloudCover,e.highCloudCover,t),iceCrystalAlignment:n.iceCrystalAlignment===void 0||e.iceCrystalAlignment===void 0?void 0:x(n.iceCrystalAlignment,e.iceCrystalAlignment,t),lowerCloudCover:n.lowerCloudCover===void 0||e.lowerCloudCover===void 0?void 0:x(n.lowerCloudCover,e.lowerCloudCover,t),cloudBaseM:n.cloudBaseM===void 0||e.cloudBaseM===void 0?void 0:x(n.cloudBaseM,e.cloudBaseM,t),precipitationType:t<1?n.precipitationType:e.precipitationType,precipitationIntensity:x(n.precipitationIntensity,e.precipitationIntensity,t),windDirectionDeg:be(n.windDirectionDeg,e.windDirectionDeg,t),windSpeed:x(n.windSpeed,e.windSpeed,t),storm:t<1?n.storm:e.storm}}class A{keyframes=[];addKeyframe(e,t){const s=this.findInsertIndex(e);this.keyframes[s]?.t===e?this.keyframes[s]={t:e,weather:t}:this.keyframes.splice(s,0,{t:e,weather:t})}clear(){this.keyframes.length=0}removeKeyframeAt(e){const t=this.findInsertIndex(e);this.keyframes[t]?.t===e&&this.keyframes.splice(t,1)}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}return t}getLatestWeatherAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].weather:void 0}getInterpolatedWeatherAt(e){const t=this.findInsertIndex(e),s=this.keyframes[t]?.t===e?this.keyframes[t]:this.keyframes[t-1];if(s?.t===e)return s.weather;const i=this.keyframes[t]?.t===e?void 0:this.keyframes[t];return s?i?xe(s.weather,i.weather,we((e-s.t)/(i.t-s.t),0,1)):s.weather:i?.weather}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes}}static fromJSON(e){const t=new A;for(const s of e.keyframes)t.addKeyframe(s.t,s.weather);return t}}class D{keyframes=[];addKeyframe(e,t){const s=this.findInsertIndex(e);this.keyframes[s]?.t===e?this.keyframes[s]={t:e,sound:t}:this.keyframes.splice(s,0,{t:e,sound:t})}clear(){this.keyframes.length=0}removeKeyframeAt(e){const t=this.findInsertIndex(e);this.keyframes[t]?.t===e&&this.keyframes.splice(t,1)}getLatestSoundAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].sound:void 0}getInterpolatedSoundAt(e){const t=this.findInsertIndex(e),s=this.keyframes[t]?.t===e?this.keyframes[t]:this.keyframes[t-1];if(s?.t===e)return s.sound;const i=this.keyframes[t]?.t===e?void 0:this.keyframes[t];if(!s)return i?.sound;if(!i)return s.sound;const r=(e-s.t)/(i.t-s.t);return this.lerp(s.sound,i.sound,Math.max(0,Math.min(1,r)))}get duration(){return this.keyframes.length===0?0:this.keyframes[this.keyframes.length-1].t}get allKeyframes(){return this.keyframes}toJSON(){return{keyframes:this.keyframes}}static fromJSON(e){const t=new D;for(const s of e.keyframes)t.addKeyframe(s.t,s.sound);return t}lerp(e,t,s){return{kind:s<1?e.kind:t.kind,volume:e.volume+(t.volume-e.volume)*s,pitchHz:e.pitchHz+(t.pitchHz-e.pitchHz)*s,src:s<1?e.src:t.src}}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}return t}}const ke=100,Se={kind:"none",volume:0,pitchHz:ke};function z(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 V(n){return(((n.day??0)*24+(n.hour??0))*60+(n.minute??0))*6e4+(n.second??0)*1e3}const Te=["year","month","day","hour","minute"],Ee=["day","hour","minute"];function X(n,e){return e.filter(t=>n[t]!==void 0).join(",")}function Me(n){if(n.durationSeconds!==void 0)return n.durationSeconds*1e3;if(!n.time||!n.endTime)return;const e=n.time.year!==void 0&&n.endTime.year!==void 0,t=e?Te:Ee,s=X(n.time,t);if(!(s===""||s!==X(n.endTime,t)))return e?z(n.endTime)-z(n.time):V(n.endTime)-V(n.time)}class N{constructor(e,t,s,i,r,o,a,l,h=[],d,c,u,f=[]){this.event=e,this.timeline=t,this.witnessTrack=s,this.weatherTrack=i,this.soundTrack=r,this.witness=o,this.caseId=a,this.weather=l,this.decor=h,this.weatherSource=d,this.instrumentId=c,this.exposureSeconds=u,this.milestones=f}get exposure(){return this.exposureSeconds??this.instrument.exposureSeconds}get instrument(){return v.byId(this.instrumentId)}static create(e,t,s){return new N({eventType:"sighting",time:e,place:t},new O,new I,new A,new D,s)}}const Pe=0,Ie=0;function ne(n,e){const t=n.witnessTrack.getInterpolatedPoseAt(e);if(t)return t;const s=n.event.place?.[0];if(s)return{lat:s.lat,lng:s.lng,elevationM:Pe,headingDeg:void 0,pitchDeg:Ie,fovDeg:v.fieldOfViewDeg(n.instrument)}}function Ae(n,e){return n.soundTrack.getInterpolatedSoundAt(e)??Se}class De{constructor(e,t){this.timeline=e,this.onFrame=t}rafId=null;state="stopped";currentT=0;lastWallTime=0;playbackRate=1;loop=!1;onEnded;durationOverrideMs=0;get seekableDuration(){return Math.max(this.timeline.duration,this.durationOverrideMs)}play(){if(this.state==="playing")return;this.currentT>=this.seekableDuration&&(this.currentT=0,this.resolveFrame(0)),this.state="playing",this.lastWallTime=performance.now();const e=()=>{if(this.state!=="playing")return;const t=performance.now();if(this.currentT+=(t-this.lastWallTime)*this.playbackRate,this.lastWallTime=t,this.currentT>=this.seekableDuration){if(this.loop&&this.seekableDuration>0){this.currentT%=this.seekableDuration,this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(e);return}this.currentT=this.seekableDuration,this.stop(),this.resolveFrame(this.currentT),this.onEnded?.();return}this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(e)};this.rafId=requestAnimationFrame(e)}pause(){this.state==="playing"&&(this.state="paused",this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null))}stop(){this.state="stopped",this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null)}seek(e){this.currentT=Math.max(0,Math.min(e,this.seekableDuration)),this.resolveFrame(this.currentT)}get playbackState(){return this.state}get time(){return this.currentT}resolveFrame(e){const t=new Map;for(const s of this.timeline.sourceIds){const i=this.timeline.getInterpolatedShapeAt(e,s);i&&t.set(s,i)}this.onFrame(e,t)}}const re=["nw","n","ne","e","se","s","sw","w"],Ce=24,L=8,Le=3,Be=8;function B(n,e,t){const s=n.x-e.x,i=n.y-e.y,r=Math.cos(t),o=Math.sin(t);return{x:e.x+s*r-i*o,y:e.y+s*o+i*r}}function M(n){return{x:n.x+n.width/2,y:n.y+n.height/2}}const Fe={nw:{left:!0,top:!0},n:{top:!0},ne:{right:!0,top:!0},e:{right:!0},se:{right:!0,bottom:!0},s:{bottom:!0},sw:{left:!0,bottom:!0},w:{left:!0}};class y{static handlePointsFor(e){const{x:t,y:s,width:i,height:r}=e.bounds,o={nw:{x:t,y:s},n:{x:t+i/2,y:s},ne:{x:t+i,y:s},e:{x:t+i,y:s+r/2},se:{x:t+i,y:s+r},s:{x:t+i/2,y:s+r},sw:{x:t,y:s+r},w:{x:t,y:s+r/2},rotate:{x:t+i/2,y:s-Ce}},a=M(e.bounds),l={};for(const h of Object.keys(o))l[h]=B(o[h],a,e.angle);return l}static hitTestHandle(e,t,s=8,i=[...re,"rotate"]){const r=y.handlePointsFor(e);for(const o of i)if(Math.hypot(t.x-r[o].x,t.y-r[o].y)<=s)return o}static HANDLE_DIRECTION_DEG={e:0,se:45,s:90,sw:135,w:180,nw:225,n:270,ne:315};static resizeAxisFor(e,t){const i=((y.HANDLE_DIRECTION_DEG[e]+t*180/Math.PI)%180+180)%180/45,r=["ew","nwse","ns","nesw"];return r[Math.round(i)%r.length]}static resizeBounds(e,t,s,i){const r=M(e),o=B(i,r,-t),{x:a,y:l,width:h,height:d}=e,c=Fe[s];let u=a,f=l,m=a+h,p=l+d;return c.left&&(u=Math.min(o.x,m-L)),c.right&&(m=Math.max(o.x,u+L)),c.top&&(f=Math.min(o.y,p-L)),c.bottom&&(p=Math.max(o.y,f+L)),{x:u,y:f,width:m-u,height:p-f}}static resizeShape(e,t,s){if(t==="rotate")throw new Error("resizeShape does not accept the rotate handle");const{width:i,height:r}=e.bounds,o=y.resizeBounds(e.bounds,e.angle,t,s);if(e.kind==="oval")return{...e,bounds:o};const a=i===0?1:o.width/i,l=r===0?1:o.height/r;return{...e,bounds:o,points:e.points.map(h=>({x:h.x*a,y:h.y*l}))}}static rotateShape(e,t){const s=M(e.bounds),i=Math.atan2(t.y-s.y,t.x-s.x)+Math.PI/2;return{...e,angle:i}}static groupBoundsFor(e){const t=Math.min(...e.map(o=>o.x)),s=Math.min(...e.map(o=>o.y)),i=Math.max(...e.map(o=>o.x+o.width)),r=Math.max(...e.map(o=>o.y+o.height));return{x:t,y:s,width:i-t,height:r-s}}static vertexPointsFor(e){const t=M(e.bounds);return e.points.map(s=>B({x:e.bounds.x+s.x,y:e.bounds.y+s.y},t,e.angle))}static hitTestVertex(e,t,s=Be){const i=y.vertexPointsFor(e);let r,o=s;return i.forEach((a,l)=>{const h=Math.hypot(t.x-a.x,t.y-a.y);h<=o&&(o=h,r=l)}),r}static toLocalPoint(e,t){const s=M(e.bounds),i=B(t,s,-e.angle);return{x:i.x-e.bounds.x,y:i.y-e.bounds.y}}static moveVertex(e,t,s){const i=e.points.map((c,u)=>u===t?y.toLocalPoint(e,s):c),r=Math.min(...i.map(c=>c.x)),o=Math.min(...i.map(c=>c.y)),a=Math.max(...i.map(c=>c.x)),l=Math.max(...i.map(c=>c.y)),h={x:e.bounds.x+r,y:e.bounds.y+o,width:a-r,height:l-o},d=i.map(c=>({x:c.x-r,y:c.y-o}));return{...e,bounds:h,points:d}}static insertVertexNear(e,t){const s=y.toLocalPoint(e,t),{points:i}=e;let r=i.length-1,o=1/0;for(let l=0;l<i.length;l++){const h=i[l],d=i[(l+1)%i.length],c=y.distanceToSegment(s,h,d);c<o&&(o=c,r=l)}const a=[...i.slice(0,r+1),s,...i.slice(r+1)];return{...e,points:a}}static distanceToSegment(e,t,s){const i=s.x-t.x,r=s.y-t.y,o=i*i+r*r,a=o===0?0:Math.max(0,Math.min(1,((e.x-t.x)*i+(e.y-t.y)*r)/o)),l=t.x+a*i,h=t.y+a*r;return Math.hypot(e.x-l,e.y-h)}static deleteVertex(e,t){return e.points.length<=Le?e:{...e,points:e.points.filter((s,i)=>i!==t)}}}const U=6,F=U/2,Re=20,_e=24,Oe=9,q=6,Ne=7,He=6,K=4;class ze{constructor(e){this.ctx=e}starPoints=0;roll=0;clear(e,t){this.ctx.clearRect(0,0,e,t)}paintShape(e){this.ctx.save(),this.ctx.globalAlpha=1-e.transparency;const t=e.blur??0;t>0&&(this.ctx.filter=`blur(${(_e*t).toFixed(2)}px)`);const s=e.brightness??0;s>0&&this.paintDazzle(e,s),e.haloScale>0&&this.paintHalo(e),this.paintBase(e),this.ctx.restore(),e.selected&&this.paintSelectionHandles(e)}setStarPoints(e){this.starPoints=Math.max(0,Math.floor(e))}setRoll(e){this.roll=e}paintDazzle(e,t){const{x:s,y:i,width:r,height:o}=e.bounds,a=s+r/2,l=i+o/2,h=Math.max(r,o)/2,d=Math.max(h*(1+Oe*t),He*t);if(d<=0)return;this.ctx.save(),this.ctx.globalCompositeOperation="lighter";const c=this.ctx.createRadialGradient(a,l,0,a,l,d);for(let u=0;u<=q;u++){const f=u/q,m=1/(1+24*f*f);this.ctx.globalAlpha=1,c.addColorStop(f,this.withAlpha(e.color,m*t*.55))}if(this.ctx.fillStyle=c,this.ctx.beginPath(),this.ctx.arc(a,l,d,0,2*Math.PI),this.ctx.fill(),this.starPoints>0){const u=h*(1+Ne*t);this.ctx.lineWidth=Math.max(1,h*.12),this.ctx.lineCap="round";for(let f=0;f<this.starPoints;f++){const m=f/this.starPoints*Math.PI*2+this.roll,p=a+Math.cos(m)*u,b=l+Math.sin(m)*u,k=this.ctx.createLinearGradient(a,l,p,b);k.addColorStop(0,this.withAlpha(e.color,t*.55)),k.addColorStop(.35,this.withAlpha(e.color,t*.22)),k.addColorStop(1,this.withAlpha(e.color,0)),this.ctx.strokeStyle=k,this.ctx.beginPath(),this.ctx.moveTo(a,l),this.ctx.lineTo(p,b),this.ctx.stroke()}}this.ctx.restore()}dazzledFill(e,t){if(t<=0)return e.color;const s=/^#([0-9a-f]{6})$/i.exec(e.color.trim());if(!s)return e.color;const i=Number.parseInt(s[1],16),r=o=>Math.round(o+(255-o)*t);return`rgb(${r(i>>16&255)}, ${r(i>>8&255)}, ${r(i&255)})`}withAlpha(e,t){const s=Math.max(0,Math.min(1,t)),i=/^#([0-9a-f]{6})$/i.exec(e.trim());if(!i)return e;const r=Number.parseInt(i[1],16);return`rgba(${r>>16&255}, ${r>>8&255}, ${r&255}, ${s.toFixed(3)})`}paintBase(e){if(this.ctx.fillStyle=this.dazzledFill(e,e.brightness??0),this.ctx.beginPath(),e.kind==="oval"){const{x:t,y:s,width:i,height:r}=e.bounds,o=i/2,a=r/2;this.ctx.ellipse(t+o,s+a,o,a,e.angle,0,2*Math.PI)}else{const{x:t,y:s,width:i,height:r}=e.bounds;this.ctx.save(),this.ctx.translate(t+i/2,s+r/2),this.ctx.rotate(e.angle),this.ctx.translate(-i/2,-r/2),e.points.forEach((o,a)=>{a===0?this.ctx.moveTo(o.x,o.y):this.ctx.lineTo(o.x,o.y)}),this.ctx.closePath(),this.ctx.restore()}this.ctx.fill()}paintHalo(e){this.ctx.save(),this.ctx.shadowColor=e.color,this.ctx.shadowBlur=Re*e.haloScale,this.paintBase(e),this.ctx.restore()}paintSelectionHandles(e){this.paintHandleFrame(y.handlePointsFor(e),{includeRotate:!0}),e.kind==="polygon"&&this.paintVertexHandles(e)}paintVertexHandles(e){this.ctx.fillStyle="#39f";for(const t of y.vertexPointsFor(e))this.ctx.beginPath(),this.ctx.ellipse(t.x,t.y,K,K,0,0,2*Math.PI),this.ctx.fill()}paintMemberOutline(e){this.paintOutline(y.handlePointsFor(e))}paintGroupHandles(e){this.paintHandleFrame(y.handlePointsFor({bounds:e,angle:0}),{includeRotate:!0})}paintOutline(e){this.ctx.strokeStyle="lightgray",this.ctx.beginPath(),this.ctx.moveTo(e.nw.x,e.nw.y),this.ctx.lineTo(e.ne.x,e.ne.y),this.ctx.lineTo(e.se.x,e.se.y),this.ctx.lineTo(e.sw.x,e.sw.y),this.ctx.closePath(),this.ctx.stroke()}paintHandleFrame(e,{includeRotate:t}){this.paintOutline(e),this.ctx.fillStyle="lightgray";for(const s of re){const i=e[s];this.ctx.fillRect(i.x-F,i.y-F,U,U)}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,F+1,F+1,0,0,2*Math.PI),this.ctx.fill())}}const W=.05,Z=.08,J=2,Y=3,Ue=14,$e=.02;class Ge{context;voice;voiceToken=0;buffers=new Map;noiseBuffer;crackleBuffer;requested;resume(){if(!this.context){if(typeof AudioContext>"u")return;try{this.context=new AudioContext}catch(e){console.warn("SightingAudio: Web Audio unavailable, the sighting stays silent:",e);return}}this.context.state==="suspended"&&this.context.resume()}setSound(e){if(this.requested=e,!this.context)return;const t=this.keyFor(e);if(!t){this.silence();return}if(this.voice?.key!==t){this.stopVoice();const s=++this.voiceToken;t.startsWith("src:")?this.buildRecordedVoice(e.src,t,s):this.voice=this.buildSynthesizedVoice(e,t)}this.applyTo(this.voice,e)}silence(){this.voiceToken++,this.stopVoice()}dispose(){this.silence(),this.context?.close(),this.context=void 0,this.buffers.clear(),this.noiseBuffer=void 0,this.crackleBuffer=void 0}keyFor(e){if(!(e.volume<=0))return e.src?`src:${e.src}`:e.kind==="none"?void 0:e.kind}applyTo(e,t){const s=this.context;if(!e||!s)return;const i=s.currentTime;e.volumeParam.setTargetAtTime(Math.max(0,Math.min(1,t.volume))*e.gainScale,i,W);for(const{param:r,ratio:o}of e.pitch)r.setTargetAtTime(Math.min(t.pitchHz*o,s.sampleRate/2-1),i,W)}buildSynthesizedVoice(e,t){const s=this.context;if(s)switch(e.kind){case"hum":return this.buildHum(s,t);case"whistle":return this.buildWhistle(s,t);case"rumble":return this.buildRumble(s,t);case"crackle":return this.buildCrackle(s,t);default:return}}buildHum(e,t){const s=e.createOscillator();s.type="sawtooth";const i=e.createOscillator();i.type="sawtooth";const r=e.createBiquadFilter();r.type="lowpass",r.Q.value=1;const o=e.createGain();return o.gain.value=0,s.connect(r),i.connect(r),r.connect(o).connect(e.destination),s.start(),i.start(),{key:t,nodes:[r,o],sources:[s,i],volumeParam:o.gain,gainScale:.16,pitch:[{param:s.frequency,ratio:1},{param:i.frequency,ratio:1.006},{param:r.frequency,ratio:6}]}}buildWhistle(e,t){const s=e.createOscillator();s.type="sine";const i=e.createOscillator();i.type="sine",i.frequency.value=5;const r=e.createGain(),o=e.createGain();return o.gain.value=0,i.connect(r).connect(s.frequency),s.connect(o).connect(e.destination),s.start(),i.start(),{key:t,nodes:[r,o],sources:[s,i],volumeParam:o.gain,gainScale:.22,pitch:[{param:s.frequency,ratio:1},{param:r.gain,ratio:.012}]}}buildRumble(e,t){const s=e.createBufferSource();s.buffer=this.brownNoise(e),s.loop=!0;const i=e.createBiquadFilter();i.type="lowpass",i.Q.value=.8;const r=e.createGain();return r.gain.value=0,s.connect(i).connect(r).connect(e.destination),s.start(),{key:t,nodes:[i,r],sources:[s],volumeParam:r.gain,gainScale:1.4,pitch:[{param:i.frequency,ratio:1}]}}buildCrackle(e,t){const s=e.createBufferSource();s.buffer=this.whiteNoise(e),s.loop=!0;const i=e.createBiquadFilter();i.type="bandpass",i.Q.value=1.2;const r=e.createGain();r.gain.value=0;const o=e.createBufferSource();o.buffer=this.crackleEnvelope(e),o.loop=!0;const a=e.createGain();return a.gain.value=0,o.connect(a).connect(r.gain),s.connect(i).connect(r).connect(e.destination),s.start(),o.start(),{key:t,nodes:[i,r,a],sources:[s,o],volumeParam:a.gain,gainScale:.9,pitch:[{param:i.frequency,ratio:4}]}}async buildRecordedVoice(e,t,s){const i=this.context;if(!i)return;let r;try{r=await this.getBuffer(e)}catch(l){console.warn("SightingAudio: the recorded sound failed to load, staying silent:",l);return}if(s!==this.voiceToken||!this.context)return;const o=i.createBufferSource();o.buffer=r,o.loop=!0;const a=i.createGain();a.gain.value=0,o.connect(a).connect(i.destination),o.start(),this.voice={key:t,nodes:[a],sources:[o],volumeParam:a.gain,gainScale:1,pitch:[]},this.requested&&this.applyTo(this.voice,this.requested)}async getBuffer(e){const t=this.context;if(!t)throw new Error("SightingAudio: AudioContext not ready — resume() must be called first");let s=this.buffers.get(e);return s||(s=fetch(e).then(i=>{if(!i.ok)throw new Error(`Audio fetch failed (${i.status}): ${e}`);return i.arrayBuffer()}).then(i=>t.decodeAudioData(i)),this.buffers.set(e,s)),s}whiteNoise(e){if(!this.noiseBuffer){const t=e.createBuffer(1,Math.floor(e.sampleRate*J),e.sampleRate),s=t.getChannelData(0);for(let i=0;i<s.length;i++)s[i]=Math.random()*2-1;this.noiseBuffer=t}return this.noiseBuffer}brownNoise(e){const t=e.createBuffer(1,Math.floor(e.sampleRate*J),e.sampleRate),s=t.getChannelData(0);let i=0,r=0;for(let o=0;o<s.length;o++)i=(i+.02*(Math.random()*2-1))/1.02,s[o]=i,r=Math.max(r,Math.abs(i));if(r>0)for(let o=0;o<s.length;o++)s[o]/=r;return t}crackleEnvelope(e){if(!this.crackleBuffer){const t=e.createBuffer(1,Math.floor(e.sampleRate*Y),e.sampleRate),s=t.getChannelData(0),i=Math.max(1,Math.floor($e*e.sampleRate)),r=Math.round(Y*Ue);for(let o=0;o<r;o++){const a=Math.floor(Math.random()*s.length),l=.4+Math.random()*.6;for(let h=0;h<i&&a+h<s.length;h++)s[a+h]=Math.max(s[a+h],l*(1-h/i))}this.crackleBuffer=t}return this.crackleBuffer}stopVoice(){const e=this.voice,t=this.context;if(this.voice=void 0,!e||!t)return;const s=t.currentTime;e.volumeParam.cancelScheduledValues(s),e.volumeParam.setTargetAtTime(0,s,Z/3);const i=s+Z;for(const r of e.sources){r.onended=()=>{r.disconnect();for(const o of e.nodes)o.disconnect()};try{r.stop(i)}catch{r.disconnect();for(const o of e.nodes)o.disconnect()}}}}function $(n){return[...n].sort((e,t)=>e.t-t.t)}function Ve(n,e){let t;for(const s of $(n)){if(s.t>e)break;t=s}return t}const H=Math.PI/180,R=180/Math.PI;class P{constructor(e,t,s){this.kind=e,this.canvasHeightPx=t,this.fovDeg=s;const i=s*H/2;this.focalPx=e==="equidistant"?t/2/i:t/2/Math.tan(i)}focalPx;static of(e,t,s){return new P(e.projection,t,s)}degToPx(e){const t=e*H;return this.kind==="equidistant"?this.focalPx*t:2*this.focalPx*Math.tan(t/2)}pxToDeg(e){return this.kind==="equidistant"?e/this.focalPx*R:2*Math.atan(e/(2*this.focalPx))*R}angleDegToRadiusPx(e){const t=e*H;return this.kind==="equidistant"?this.focalPx*t:this.focalPx*Math.tan(t)}radiusPxToAngleDeg(e){return this.kind==="equidistant"?e/this.focalPx*R:Math.atan(e/this.focalPx)*R}ofBounds(e){return{widthDeg:this.pxToDeg(e.width),heightDeg:this.pxToDeg(e.height)}}toBoundsSize(e){return{width:this.degToPx(e.widthDeg),height:this.degToPx(e.heightDeg)}}widthPx(e){return this.degToPx(w.angularWidthDeg(e))}}class oe{static fovOf(e,t){return ne(e,t)?.fovDeg??v.fieldOfViewDeg(e.instrument)}static toAngular(e){this.eachKeyframe(e,(t,s)=>({...t,angular:s.ofBounds(t.bounds)}))}static toBounds(e){this.eachKeyframe(e,(t,s)=>{if(!t.angular)return t;const{width:i,height:r}=s.toBoundsSize(t.angular),o={x:t.bounds.x+(t.bounds.width-i)/2,y:t.bounds.y+(t.bounds.height-r)/2,width:i,height:r};if(t.kind!=="polygon")return{...t,bounds:o};const a=t.bounds.width===0?1:i/t.bounds.width,l=t.bounds.height===0?1:r/t.bounds.height;return{...t,bounds:o,points:t.points.map(h=>({x:h.x*a,y:h.y*l}))}})}static reproject(e,t,s){const i=v.frameWidthPx(t,w.CANVAS_HEIGHT_PX)/2,r=v.frameWidthPx(e.instrument,w.CANVAS_HEIGHT_PX)/2,o=w.CANVAS_HEIGHT_PX/2;for(const a of[...e.timeline.allKeyframes]){const l=this.fovOf(e,a.t),h=s?.get(a.t)??l,d=P.of(t,w.CANVAS_HEIGHT_PX,h),c=P.of(e.instrument,w.CANVAS_HEIGHT_PX,l);e.timeline.addKeyframe(a.t,a.shapes.map(u=>{const{bounds:f}=u.shape,m=f.x+f.width/2-i,p=f.y+f.height/2-o,b=Math.hypot(m,p),k=b===0?1:c.angleDegToRadiusPx(d.radiusPxToAngleDeg(b))/b,le={...f,x:r+m*k-f.width/2,y:o+p*k-f.height/2};return{...u,shape:{...u.shape,bounds:le}}}))}this.toBounds(e)}static eachKeyframe(e,t){const{timeline:s}=e;for(const i of[...s.allKeyframes]){const r=this.fovOf(e,i.t),o=P.of(e.instrument,w.CANVAS_HEIGHT_PX,r);s.addKeyframe(i.t,i.shapes.map(a=>({...a,shape:t(a.shape,o)})))}}}function Xe(n){return oe.toAngular(n),{version:1,time:n.event.time,endTime:n.event.endTime,durationSeconds:n.event.durationSeconds,utcOffsetHours:n.event.utcOffsetHours,timeZone:n.event.timeZone,place:n.event.place,witness:n.witness,caseId:n.caseId,description:n.event.description,tags:n.event.tags,timeline:n.timeline.toJSON(),witnessTrack:n.witnessTrack.toJSON(),weatherTrack:n.weatherTrack.toJSON(),soundTrack:n.soundTrack.toJSON(),weather:n.weather,decor:n.decor,milestones:n.milestones.length>0?n.milestones:void 0,weatherSource:n.weatherSource,instrument:n.instrumentId,exposureSeconds:n.exposureSeconds}}function qe(n){const e=new N({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},O.fromJSON(n.timeline),n.witnessTrack?I.fromJSON(n.witnessTrack):new I,n.weatherTrack?A.fromJSON(n.weatherTrack):new A,n.soundTrack?D.fromJSON(n.soundTrack):new D,n.witness,n.caseId,n.weather,n.decor??[],n.weatherSource,n.instrument,n.exposureSeconds??n.witnessTrack?.keyframes.map(t=>t.pose.exposureSeconds).find(t=>t!==void 0),$(n.milestones??[]));return oe.toBounds(e),e}function Ke(n,e){for(const t of n){const s=t.toLowerCase().split("-")[0];if(e.includes(s))return s}return"en"}class We{static preferencesFor(e){const t=navigator.languages??[],s=e.closest("[lang]")?.getAttribute("lang")?.trim();return s?[s,...t]:t}}const Ze="modulepreload",Je=function(n,e){return new URL(n,e).href},j={},Q=function(e,t,s){let i=Promise.resolve();if(t&&t.length>0){let o=function(d){return Promise.all(d.map(c=>Promise.resolve(c).then(u=>({status:"fulfilled",value:u}),u=>({status:"rejected",reason:u}))))};const a=document.getElementsByTagName("link"),l=document.querySelector("meta[property=csp-nonce]"),h=l?.nonce||l?.getAttribute("nonce");i=o(t.map(d=>{if(d=Je(d,s),d in j)return;j[d]=!0;const c=d.endsWith(".css"),u=c?'[rel="stylesheet"]':"";if(!!s)for(let p=a.length-1;p>=0;p--){const b=a[p];if(b.href===d&&(!c||b.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${d}"]${u}`))return;const m=document.createElement("link");if(m.rel=c?"stylesheet":Ze,c||(m.as="script"),m.crossOrigin="",m.href=d,h&&m.setAttribute("nonce",h),document.head.appendChild(m),c)return new Promise((p,b)=>{m.addEventListener("load",p),m.addEventListener("error",()=>b(new Error(`Unable to preload CSS for ${d}`)))})}))}function r(o){const a=new Event("vite:preloadError",{cancelable:!0});if(a.payload=o,window.dispatchEvent(a),!a.defaultPrevented)throw o}return i.then(o=>{for(const a of o||[])a.status==="rejected"&&r(a.reason);return e().catch(r)})},Ye=["en","fr"],je={en:()=>Q(()=>Promise.resolve().then(()=>et),void 0,import.meta.url).then(n=>n.ufoMessages_en),fr:()=>Q(()=>import("./UfoMessages_fr-Hq24iVzT.js"),[],import.meta.url).then(n=>n.ufoMessages_fr)};function Qe(n){return je[n]()}const ae={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"},et=Object.freeze(Object.defineProperty({__proto__:null,ufoMessages_en:ae},Symbol.toStringTag,{value:"Module"})),ee=new Set;class S extends HTMLElement{static get observedAttributes(){return["src"]}shadow;stageElement;canvas;canvasRenderer;tooltip;toolbar;playPauseButton;loopButton;fullscreenButton;seekInput;milestoneMarks;milestoneCaption;timeStartLabel;timeEndLabel;currentSighting=N.create();sightingAudio=new Ge;soundPreview;player;loopEnabled=!0;playbackBeforeClick;highlightedSourceIds=new Set;occludedSourceIds=ee;enableClickToPlay=!0;fullscreenTarget;messages=ae;realDurationMs;realStartMs;showClockTime=!0;handleFullscreenChange=()=>this.updateFullscreenButton();simulatedFullscreen=!1;styleBeforeSimulatedFullscreen;bodyOverflowBeforeSimulatedFullscreen;handleSimulatedFullscreenKey=e=>{e.key==="Escape"&&this.exitSimulatedFullscreen()};handlePointerMove=e=>{const t=this.canvasPointFromEvent(e),s=t&&this.shapeAt(t.x,t.y);if(!s?.shape.title){this.tooltip.hidden=!0;return}this.tooltip.textContent=s.shape.title,this.tooltip.hidden=!1;const i=this.stageElement.getBoundingClientRect();this.tooltip.style.left=`${e.clientX-i.left+12}px`,this.tooltip.style.top=`${e.clientY-i.top+12}px`};handlePointerLeave=()=>{this.tooltip.hidden=!0};constructor(){super(),this.shadow=this.attachShadow({mode:"open"});const e=document.createElement("template");e.innerHTML=`<style>${ce}</style>${he}`,this.shadow.appendChild(e.content.cloneNode(!0)),this.stageElement=this.shadow.getElementById("stage"),this.canvas=this.shadow.getElementById("canvas"),this.canvasRenderer=new ze(this.canvas.getContext("2d")),this.tooltip=this.shadow.getElementById("tooltip"),this.toolbar=this.shadow.getElementById("toolbar"),this.playPauseButton=this.shadow.getElementById("play-pause"),this.loopButton=this.shadow.getElementById("loop"),this.fullscreenButton=this.shadow.getElementById("fullscreen"),this.seekInput=this.shadow.getElementById("seek"),this.milestoneMarks=this.shadow.getElementById("milestone-marks"),this.milestoneCaption=this.shadow.getElementById("milestone-caption"),this.timeStartLabel=this.shadow.getElementById("time-start"),this.timeEndLabel=this.shadow.getElementById("time-end");for(const t of[this.timeStartLabel,this.timeEndLabel])t.addEventListener("click",()=>this.toggleTimeDisplay()),t.addEventListener("keydown",s=>{s.key!=="Enter"&&s.key!==" "||(s.preventDefault(),this.toggleTimeDisplay())});this.fullscreenTarget=this.stageElement,this.playPauseButton.addEventListener("click",()=>this.togglePlayPause()),this.loopButton.addEventListener("click",()=>this.toggleLoop()),this.fullscreenButton.addEventListener("click",()=>this.toggleFullscreen()),this.seekInput.addEventListener("input",()=>this.player.seek(Number(this.seekInput.value))),this.canvas.addEventListener("click",t=>{this.enableClickToPlay&&(t.detail<=1&&(this.playbackBeforeClick={state:this.playbackState,time:this.currentTime}),this.togglePlayPause())}),this.canvas.addEventListener("dblclick",t=>{this.enableClickToPlay&&(t.preventDefault(),this.restorePlayback(),this.toggleFullscreen())}),this.canvas.addEventListener("pointermove",this.handlePointerMove),this.canvas.addEventListener("pointerleave",this.handlePointerLeave),document.addEventListener("fullscreenchange",this.handleFullscreenChange),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.updateFullscreenButton(),this.refresh(),this.loadLocaleMessages()}connectedCallback(){const e=this.getAttribute("src");e&&this.loadFromSrc(e)}disconnectedCallback(){document.removeEventListener("fullscreenchange",this.handleFullscreenChange),this.exitSimulatedFullscreen(),this.sightingAudio.dispose()}attributeChangedCallback(e,t,s){e==="src"&&s&&s!==t&&this.isConnected&&this.loadFromSrc(s)}async loadFromSrc(e){this.sightingData=await de.json(e)}get sightingData(){return Xe(this.currentSighting)}set sightingData(e){this.player.stop(),this.soundPreview=void 0,this.sightingAudio.silence(),this.currentSighting=qe(e),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.refresh()}get sighting(){return this.currentSighting}previewSound(e){this.soundPreview=e,this.sightingAudio.resume(),this.sightingAudio.setSound(e)}stopSoundPreview(){this.soundPreview=void 0,this.sightingAudio.silence()}get canvasElement(){return this.canvas}get renderer(){return this.canvasRenderer}get currentTime(){return this.player.time}set currentTime(e){this.player.seek(e)}get seekableDuration(){return this.player.seekableDuration}get autoReplayEnabled(){return this.loopEnabled}set autoReplayEnabled(e){e!==this.loopEnabled&&this.toggleLoop()}play(){this.player.seekableDuration<=0||this.playbackState==="playing"||this.togglePlayPause()}pause(){this.playbackState==="playing"&&this.togglePlayPause()}get positionLabel(){return this.timeStartLabel.textContent??""}get durationLabel(){return this.timeEndLabel.textContent??""}get showingClockTime(){return this.canShowClockTime}get canSwitchTimeDisplay(){return this.realStartMs!==void 0}toggleTimeDisplay(){this.canSwitchTimeDisplay&&(this.showClockTime=!this.showClockTime,this.updateTimeLabels(),this.updateTimeLabelTitles(),this.dispatchEvent(new CustomEvent("timedisplaychange",{bubbles:!0,composed:!0})))}get canShowClockTime(){return this.realStartMs!==void 0&&this.showClockTime}set showToolbar(e){this.toolbar.classList.toggle("hidden",!e)}get playbackState(){return this.player.playbackState}get selectedSourceIds(){return this.highlightedSourceIds}get durationSeconds(){return this.currentSighting.event.durationSeconds}set durationSeconds(e){this.currentSighting.event.durationSeconds=e,this.updateTimeLabels(),this.refresh(),this.updatePlayPauseButton()}set selectedSourceIds(e){const t=new Set(e);t.size===this.highlightedSourceIds.size&&[...t].every(i=>this.highlightedSourceIds.has(i))||(this.highlightedSourceIds=t,this.refresh())}setOccludedSourceIds(e){e.size===this.occludedSourceIds.size&&[...e].every(s=>this.occludedSourceIds.has(s))||(this.occludedSourceIds=e,this.refresh())}hasVisibleShapeAt(e,t){return this.shapeAt(e,t)!==void 0}refresh(){this.applyFrameFormat(),this.updateTimeLabels(),this.seekInput.max=String(this.player.seekableDuration),this.refreshMilestoneMarks(),this.player.seek(this.player.time)}refreshMilestoneMarks(){const e=this.player.seekableDuration,t=e>0?$(this.sighting.milestones):[];this.milestoneMarks.replaceChildren(...t.map(s=>{const i=document.createElement("button");i.type="button",i.className="milestone-mark",i.style.left=`${Math.min(Math.max(s.t/e,0),1)*100}%`;const r=s.note?`${s.label} — ${s.note}`:s.label;return i.title=r,i.setAttribute("aria-label",r),i.addEventListener("click",()=>this.player.seek(s.t)),i}))}showMilestoneAt(e){const t=this.sighting.milestones.length>0?Ve(this.sighting.milestones,e):void 0;if(this.milestoneCaption.hidden=t===void 0,!t)return;const s=document.createElement("b");s.textContent=t.label;const i=t.note?` — ${t.note}`:"";this.milestoneCaption.replaceChildren(s,document.createTextNode(i))}exposureInstants(e){const t=this.exposureTimes(e),s=1/t.length;return t.map(i=>({shapes:this.shapesAt(i),share:s}))}exposureTimes(e=this.currentTime){const t=(this.currentSighting.exposure??0)*1e3;if(t<S.SHORTEST_VISIBLE_EXPOSURE_MS)return[e];const s=Math.min(S.MAX_EXPOSURE_STEPS,Math.max(2,Math.round(t/S.SHORTEST_VISIBLE_EXPOSURE_MS))),i=Math.ceil(this.travelPxOver(e,t)/S.EXPOSURE_STEP_PX),r=Math.min(S.MAX_TRAVEL_STEPS,Math.max(s,i)),o=[];for(let a=0;a<r;a++)o.push(e+t*a/r);return o}travelPxOver(e,t){const s=this.shapesAt(e),i=this.shapesAt(e+t);let r=0;for(const[o,a]of s){const l=i.get(o);if(!l)continue;const h=l.bounds.x+l.bounds.width/2-(a.bounds.x+a.bounds.width/2),d=l.bounds.y+l.bounds.height/2-(a.bounds.y+a.bounds.height/2);r=Math.max(r,Math.hypot(h,d))}return r}shapeAt(e,t,s=this.occludedSourceIds){for(const i of this.exposureTimes()){const r=this.currentSighting.timeline.hitTest(i,e,t,s);if(r)return r}}shapesAt(e){const t=new Map;for(const s of this.currentSighting.timeline.sourceIds){const i=this.currentSighting.timeline.getInterpolatedShapeAt(e,s);i&&t.set(s,i)}return t}static SHORTEST_VISIBLE_EXPOSURE_MS=20;static MAX_EXPOSURE_STEPS=48;static EXPOSURE_STEP_PX=2;static MAX_TRAVEL_STEPS=320;applyFrameFormat(){const e=v.frameWidthPx(this.currentSighting.instrument,this.canvas.height);if(this.canvas.width===e)return;this.canvas.width=e,this.canvas.parentElement?.style.setProperty("--frame-aspect",`${e} / ${this.canvas.height}`)}canvasPointFromEvent(e){const t=this.canvas.getBoundingClientRect();if(!(t.width===0||t.height===0))return{x:(e.clientX-t.left)/t.width*this.canvas.width,y:(e.clientY-t.top)/t.height*this.canvas.height}}onFrame(e,t){this.canvasRenderer.clear(this.canvas.width,this.canvas.height);const s=this.playbackState!=="playing"?this.highlightedSourceIds:ee;this.canvasRenderer.setStarPoints(v.starPointsOf(this.sighting.instrument)),this.canvasRenderer.setRoll((ne(this.sighting,e)?.rollDeg??0)*Math.PI/180);const i=this.exposureInstants(e);for(const r of i)for(const[o,a]of r.shapes){if(this.occludedSourceIds.has(o))continue;const l=r.share,h=l===1?a:{...a,transparency:1-(1-a.transparency)*l};this.canvasRenderer.paintShape(h)}for(const[r,o]of i[0].shapes)this.occludedSourceIds.has(r)||!s.has(r)||(s.size===1?this.canvasRenderer.paintShape({...o,selected:!0}):this.canvasRenderer.paintMemberOutline(o));if(s.size>1){const r=y.groupBoundsFor([...t].filter(([o])=>s.has(o)).map(([,o])=>o.bounds));this.canvasRenderer.paintGroupHandles(r)}this.seekInput.value=String(e),this.timeStartLabel.textContent=this.formatPosition(e),this.showMilestoneAt(e),this.playbackState==="playing"?(this.soundPreview=void 0,this.sightingAudio.setSound(Ae(this.currentSighting,e))):this.soundPreview?this.sightingAudio.setSound(this.soundPreview):this.sightingAudio.silence(),this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{time:e}}))}createPlayer(){const e=new De(this.currentSighting.timeline,(t,s)=>this.onFrame(t,s));return e.loop=this.loopEnabled,e.onEnded=()=>{this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("ended",{bubbles:!0,composed:!0}))},e}togglePlayPause(){this.player.seekableDuration<=0||(this.player.playbackState==="playing"?(this.player.pause(),this.soundPreview=void 0,this.sightingAudio.silence(),this.refresh()):(this.sightingAudio.resume(),this.player.play()),this.updatePlayPauseButton())}updatePlayPauseButton(){const e=this.player.playbackState==="playing";this.playPauseButton.textContent=e?"⏸":"▶";const t=this.player.seekableDuration>0;this.playPauseButton.disabled=!t;const s=t?e?this.messages.pause:this.messages.play:this.messages.noDuration;this.playPauseButton.title=s,this.playPauseButton.setAttribute("aria-label",s),this.toolbar.classList.toggle("auto-hide",e),this.fullscreenButton.classList.toggle("auto-hide",e)}toggleLoop(){this.loopEnabled=!this.loopEnabled,this.loopButton.setAttribute("aria-pressed",String(this.loopEnabled)),this.player.loop=this.loopEnabled}restorePlayback(){const e=this.playbackBeforeClick;e&&(this.currentTime=e.time,e.state==="playing"?this.play():this.pause())}get canUseNativeFullscreen(){return typeof this.fullscreenTarget.requestFullscreen=="function"&&document.fullscreenEnabled}toggleFullscreen(){if(!this.canUseNativeFullscreen){this.simulatedFullscreen?this.exitSimulatedFullscreen():this.enterSimulatedFullscreen();return}document.fullscreenElement?document.exitFullscreen():this.fullscreenTarget.requestFullscreen().catch(e=>{console.error("<rr0-ufo>: requestFullscreen() failed —",e)})}enterSimulatedFullscreen(){const e=this.fullscreenTarget;this.styleBeforeSimulatedFullscreen=e.getAttribute("style")??"",this.bodyOverflowBeforeSimulatedFullscreen=document.body.style.overflow;const t=e.style;t.setProperty("position","fixed"),t.setProperty("inset","0"),t.setProperty("margin","0"),t.setProperty("max-width","none"),t.setProperty("max-height","none"),t.setProperty("aspect-ratio","auto"),t.setProperty("z-index","2147483647"),t.setProperty("background","#000"),t.setProperty("width","100vw"),t.setProperty("width","100dvw"),t.setProperty("height","100vh"),t.setProperty("height","100dvh"),document.body.style.overflow="hidden",this.simulatedFullscreen=!0,document.addEventListener("keydown",this.handleSimulatedFullscreenKey),this.updateFullscreenButton()}exitSimulatedFullscreen(){if(!this.simulatedFullscreen)return;const e=this.fullscreenTarget;this.styleBeforeSimulatedFullscreen?e.setAttribute("style",this.styleBeforeSimulatedFullscreen):e.removeAttribute("style"),document.body.style.overflow=this.bodyOverflowBeforeSimulatedFullscreen??"",this.simulatedFullscreen=!1,document.removeEventListener("keydown",this.handleSimulatedFullscreenKey),this.updateFullscreenButton()}updateFullscreenButton(){const e=this.simulatedFullscreen||document.fullscreenElement===this.fullscreenTarget;this.fullscreenButton.title=e?this.messages.exitFullscreen:this.messages.fullscreen,this.fullscreenButton.setAttribute("aria-label",this.fullscreenButton.title)}async loadLocaleMessages(){const e=Ke(We.preferencesFor(this),Ye);e!=="en"&&this.applyMessages(await Qe(e))}updateTimeLabelTitles(){const e=this.showClockTime?this.messages.switchToElapsed:this.messages.switchToClockTime,t=this.canSwitchTimeDisplay?` — ${e}`:"";this.timeStartLabel.title=this.messages.currentPosition+t,this.timeEndLabel.title=this.messages.duration+t;for(const s of[this.timeStartLabel,this.timeEndLabel])s.classList.toggle("switchable",this.canSwitchTimeDisplay),this.canSwitchTimeDisplay?(s.setAttribute("role","button"),s.setAttribute("tabindex","0")):(s.removeAttribute("role"),s.removeAttribute("tabindex"))}applyMessages(e){this.messages=e,this.updateTimeLabelTitles(),this.loopButton.title=e.autoReplay,this.loopButton.setAttribute("aria-label",e.autoReplay),this.updatePlayPauseButton(),this.updateFullscreenButton()}updateTimeLabels(){const e=this.currentSighting.event,t=Me(e);this.realDurationMs=t!==void 0&&t>0?t:void 0,this.realStartMs=e.time?z(e.time):void 0;const s=this.currentSighting.timeline.duration;this.player.playbackRate=this.realDurationMs!==void 0&&s>0?s/this.realDurationMs:1,this.player.durationOverrideMs=s>0?0:this.realDurationMs??0,this.timeEndLabel.textContent=this.formatEndOfTimeline(),this.timeStartLabel.textContent=this.formatPosition(this.player.time),this.updateTimeLabelTitles()}formatPosition(e){if(this.realDurationMs===void 0)return _(e);const t=this.currentSighting.timeline.duration,s=t>0&&e<=t?e/t*this.realDurationMs:e;return this.canShowClockTime?se(te(this.realStartMs+s)):_(s)}formatEndOfTimeline(){return this.realDurationMs===void 0?_(this.currentSighting.timeline.duration):this.canShowClockTime?se(te(this.realStartMs+this.realDurationMs)):_(this.realDurationMs)}}function te(n){const e=new Date(n);return{hour:e.getUTCHours(),minute:e.getUTCMinutes(),second:e.getUTCSeconds()}}function se(n){if(n.hour===void 0)return"0:00";const e=t=>String(t).padStart(2,"0");return n.second?`${e(n.hour)}:${e(n.minute??0)}:${e(n.second)}`:`${e(n.hour)}:${e(n.minute??0)}`}function _(n){const e=Math.round(n/1e3),t=Math.floor(e/60),s=e%60;return`${t}:${String(s).padStart(2,"0")}`}const ie="rr0-ufo";function tt(){customElements.get(ie)||customElements.define(ie,S)}tt();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rr0/ufoathome",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.45.0",
|
|
5
5
|
"description": "UFO@home — record and replay a UFO sighting's shape, movement and appearance",
|
|
6
6
|
"author": "Jérôme Beau <rr0@rr0.org> (https://rr0.org)",
|
|
7
7
|
"license": "MIT",
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const e={color:"Couleur",transparency:"Transparence",halo:"Halo",blur:"Flou",brightness:"Éclat",shapeTitle:"Nom",utcOffset:"Fuseau horaire",cloudBase:"Base des nuages",elevation:"Altitude",heightAboveGround:"Hauteur au-dessus du sol",duration:"Durée",placeName:"Lieu",latitude:"Latitude",longitude:"Longitude",heading:"Orientation",pitch:"Inclinaison",roll:"Roulis",observationTime:"Début de l'observation",observationEndTime:"Fin de l'observation",witnessGroup:"Témoin",witnessId:"ID 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",tags:"Mots-clés",cloudCover:"Couverture nuageuse",highCloudCover:"Nuages de glace (cirrus)",focalLength:"Focale",fieldOfView:"Champ de vision",aperture:"Diaphragme",exposure:"Temps de pose",focusDistance:"Mise au point à",iceCrystalAlignment:"Alignement des cristaux",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",soundKind:"Son",soundNone:"Aucun (silencieux)",soundHum:"Bourdonnement",soundWhistle:"Sifflement",soundRumble:"Grondement",soundCrackle:"Crépitement",soundVolume:"Intensité sonore",soundPitch:"Hauteur",soundSrc:"Enregistrement",instrument:"Observé avec",decorAircraft:"Aéronef",decorAltitude:"Altitude",decorLights:"Feux",decorLightsNone:"aucun",decor:"Environnement",decorBuilding:"Bâtiment",decorTree:"Arbre",decorStreetlight:"Lampadaire",decorVehicle:"Véhicule",decorWitness:"Autre témoin",decorEast:"Distance à l'est",decorNorth:"Distance au nord",decorHeading:"Orientation",decorLit:"Allumé",decorTitle:"Nom",confirmAccept:"Supprimer",confirmDecline:"Annuler",decorWidth:"Largeur",decorLength:"Longueur",decorHeight:"Hauteur",decorModel:"Modèle 3D",decorModelNone:"Forme intégrée",decorModelAdvanced:"Modèle depuis une adresse",decorModelUrl:"Adresse glTF/GLB",decorModelTitle:"Nom du modèle",decorModelAuthor:"Auteur",decorModelLicense:"Licence",decorModelSource:"Provenance",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"},i={...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",shape:"Forme",objectSize:"Essayer une taille",objectDistance:"à une distance de",apparentSize:"soit {deg}° — {moons}× la Lune",utcOffsetPlaceholder:"d'après la longitude",objectSizePlaceholder:"largeur supposée",objectDistancePlaceholder:"distance supposée",realSizeBetween:"Largeur réelle : entre {min} et {max} m, d'après ce qu'il croise",realSizeAtLeast:"Largeur réelle : au moins {min} m, d'après ce qu'il passe derrière",realSizeAtMost:"Largeur réelle : au plus {max} m, d'après ce qu'il passe devant",realSizeUnknown:"Largeur réelle indéterminée : rien dans la scène ne croise sa ligne de visée",blurBound:"Flou à travers cet objectif : plus près que {max} m",blurBoundNoInstrument:"Le flou à l'œil nu ne borne aucune distance — un œil n'a pas de diaphragme dont sortir",blurBoundNotAtInfinity:"Le flou ne borne une distance que pour une mise au point à l'infini : celle-ci est à {focus} m, qui floute des deux côtés",realSizeContradiction:"Contradiction : les passages déclarés ne peuvent pas être vrais ensemble",realDistanceHere:" — soit ici entre {min} et {max} m",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",currentPosition:"Position actuelle",switchToElapsed:"cliquer pour afficher la durée écoulée",switchToClockTime:"cliquer pour afficher l'heure de l'observation",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",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.",importErrorCors:"Cette adresse répond, mais le navigateur n'a pas le droit de la lire depuis cette page. Le fichier lui-même est bon : c'est son serveur qui doit envoyer l'en-tête « Access-Control-Allow-Origin: * » avec. Si ce serveur n'est pas le vôtre, c'est ce qu'il faut demander à son administrateur.",importErrorMixedContent:"Une page sécurisée ne peut pas charger une adresse commençant par http://. Utilisez https:// si ce serveur le propose.",importErrorUnreachable:"Rien n'a répondu à cette adresse. Vérifiez l'orthographe, et que vous êtes connecté.",importErrorStatus:"Ce serveur a répondu par une erreur ({status}). L'adresse est probablement fausse, ou le fichier a été déplacé.",importErrorMalformed:"Cette adresse a répondu, mais ce qui revient n'est pas un enregistrement.",record:"Enregistrer",stop:"Arrêter",according:"selon",sourceElevation:"Relief",sourceImagery:"Imagerie",placeNamePlaceholder:"Valensole, France",searchPlace:"Localiser",placeMatch:"Correspondances",placeSearching:"Recherche du lieu…",placeMatchFound:"lieu trouvé",placeMatchesFound:"lieux trouvés",placeNotFound:"Aucun lieu de ce nom",placeSearchFailed:"Recherche de lieu momentanément indisponible",utcOffsetImplausible:"Ce fuseau ne correspond pas à la longitude saisie, dont l'heure solaire est UTC{solar} — la scène et le relevé météo obéissent pourtant à ce qui est déclaré ici. Videz le champ pour le déduire de la longitude.",timeZoneManual:"décalage saisi",altitudeAboveSeaLevel:"Altitude du témoin au-dessus du niveau de la mer — le sol du lieu saisi en fixe le minimum : un témoin dans les Alpes n'est pas à 0 m.",groundAt:"sol à {m} m",headingPlaceholder:"inconnu",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",witnessDirName:"Répertoire du témoin",description:"Description",tagsPlaceholder:"séparés par des virgules",weather:"Météo",shapeGroup:"Phénomène",soundGroup:"Son",temporalGroup:"Moment",timeQualifierExact:"Exacte",timeQualifierApproximate:"Approximative",timeQualifierUncertain:"Incertaine",timeQualifierBoth:"Incertaine et approximative",edtfModeTitle:"Saisir plutôt une date imprécise : une année seule, un mois, une heure sans date",locationGroup:"Lieu",observationGroup:"Observation",weatherWhilePlaying:"Mettez la lecture en pause pour décrire le temps — pendant qu'elle tourne, l'instant où ceci s'écrirait se déplace",weatherInferred:"D'après les relevés",weatherInferredTitle:"La météo n'est pas un témoignage : c'est un fait mesurable, relevé pour la date, l'heure et le lieu de l'observation. Décochez pour saisir les conditions rapportées par le témoin, qui priment alors sur le relevé.",weatherLookingUp:"Recherche du relevé…",weatherNeedsDateAndPlace:"Date complète et lieu requis pour retrouver le relevé",weatherNoRecord:"Aucun relevé pour cette date et ce lieu",weatherLookupFailed:"Relevés météo momentanément inaccessibles",instrumentOutOfPeriod:"{name} — hors de son époque",unitMillimetres:"mm",unitDegrees:"°",soundSrcPlaceholder:"URL d'un enregistrement réel",skyLine:"Ciel : {parts}",skyDetails:"Voir le détail des relevés",skyDetailsHide:"Masquer le détail des relevés",skyShowerActive:"{name} — radiant à {altitude}° de hauteur, {bearing} — environ {rate}/h sous ce ciel, sur un fond sporadique de {sporadic}/h",skyShowerBelowHorizon:"{name} est actif, mais son radiant est sous l'horizon — il n'a rien pu produire",skyNothingActive:"aucune pluie de météores active à cette date — un fond sporadique d'environ {sporadic}/h",showMeteor:"Montrer un météore",skyComet:"La {name}, magnitude {magnitude}, à {altitude}° de hauteur {bearing}, queue longue de {tail}°",skyCometNoTail:"La {name}, magnitude {magnitude}, à {altitude}° de hauteur {bearing}",skyCometBelowHorizon:"La {name} était là, magnitude {magnitude}, mais sous l'horizon — personne ici ne l'a vue",skyCometInDaylight:"La {name}, magnitude {magnitude}, à {altitude}° de hauteur {bearing} — mais à {elongation}° du Soleil seulement",showComet:"Montrer la comète",skyOpticsPossible:"nuages de glace suffisants, autour {source}, pour {forms}",skyOpticsAlignment:" — tout sauf les halos exige des cristaux tombant à plat, ici supposés alignés à {alignment} %, qu'aucun relevé ne donne",skyOpticsHalo22:"un halo à {angle}°",skyOpticsHalo46:"un halo à {angle}°",skyOpticsParhelia:"des parhélies à {angle}° de part et d'autre",skyOpticsTangentArc:"un arc tangent au sommet",skyOpticsParhelicCircle:"un cercle parhélique blanc faisant le tour du ciel à sa hauteur",skyOpticsCircumzenithal:"un arc circumzénithal à {angle}° de hauteur",skyOpticsCircumhorizontal:"un arc circumhorizontal très au-dessous",skyOpticsPillar:"un pilier dressé au-dessus",skyOpticsNoIce:"aucun nuage de glace : ni halo ni parhélie",skyOpticsHidden:"des nuages de glace en altitude, mais une couche plus basse les cache",skyOpticsSun:"du Soleil",skyOpticsMoon:"de la Lune",skyBowPossible:"pluie et soleil suffisants pour {forms}",skyBowMoon:"pluie sous une Lune éclairée à {lit} %, de quoi former {forms} — un arc-en-ciel lunaire, que l'œil voit blanc",skyGlowBand:"la Voie lactée à {contrast}× le fond du ciel, à {altitude}° de hauteur {bearing}",skyGlowCone:"la lumière zodiacale à {contrast}× le fond du ciel, à {altitude}° de hauteur {bearing}, à {elongation}° du Soleil — un cône penché, sans bord, disparu dans l'heure",skyGlowZodiacalBand:"la bande zodiacale à {contrast}× le fond du ciel, à {altitude}° de hauteur {bearing} — toute l'écliptique faiblement allumée, ce qui n'est pas ce que les gens rapportent",skyGlowMoon:"ni Voie lactée ni lumière zodiacale : une Lune éclairée à {lit} % tenait le ciel à {sky} magnitudes par seconde d'arc, là où il leur en faut 22",skyGlowTwilight:"ni Voie lactée ni lumière zodiacale : le crépuscule tenait encore le ciel à {sky} magnitudes par seconde d'arc, là où il leur en faut 22",skyGlowNothingUp:"ciel assez noir ({sky} magnitudes par seconde d'arc), mais ni le plan de la Galaxie ni l'écliptique ne s'y tenaient assez haut",skyBowPrimary:"un arc de {radius}° de rayon, dont le sommet montait à {top}°",skyBowSecondary:"un second, plus pâle, à {radius}°, aux couleurs inversées",skyBowSourceTooHigh:"pluie, mais l'astre à {altitude}° de hauteur : aucun arc ne pouvait dépasser l'horizon, ils se tiennent à {radius}° du point opposé",skyBowHidden:"pluie, mais une couche continue entre elle et la lumière : aucun arc",skyBowNoSource:"pluie, et rien d'allumé pour en tirer un arc",skySatellitesLit:"l'ombre de la Terre montait à {height} km au-dessus du témoin, l'orbite basse était donc encore éclairée — {count} objets suivis étaient en orbite ce mois-là",skySatellitesLitWith:"l'ombre de la Terre montait à {height} km au-dessus du témoin, l'orbite basse était donc encore éclairée — {count} objets suivis étaient en orbite ce mois-là, dont {eras}",skySatellitesShadowed:"l'ombre de la Terre montait à {height} km au-dessus du témoin : sur les {count} objets suivis en orbite ce mois-là, rien en orbite basse n'était éclairé",skySatellitesNotYet:"rien n'était encore en orbite",skySatellitesDaylight:"tout ce qui est en orbite était éclairé, comme toujours de jour, et {eras} pouvaient l'emporter même sur ce ciel",skySatellitesDaylightOne:"tout ce qui est en orbite était éclairé, comme toujours de jour, et {eras} pouvait l'emporter même sur ce ciel",skyUnknown:"indéterminable sans date ni lieu",lookAtDecor:"Regarder cet élément",addDecor:"Ajouter",deleteDecor:"Supprimer l'élément de décor",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"};export{i as sightingEditorMessages_fr};
|
|
Binary file
|