@rr0/ufoathome 0.10.5 → 0.11.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.
@@ -13,7 +13,7 @@ const K=`
13
13
  <button id="loop" type="button" title="Auto-replay" aria-label="Auto-replay" aria-pressed="true">↻</button>
14
14
  </div>
15
15
  </div>
16
- `,V=`
16
+ `,W=`
17
17
  :host {
18
18
  display: block;
19
19
  font-family: sans-serif;
@@ -71,6 +71,51 @@ canvas {
71
71
  border: var(--ufo-canvas-border, 1px solid #333);
72
72
  box-sizing: border-box;
73
73
  }
74
+ /* Hover feedback for the editor (<rr0-ufo-recorder>): what the pointer is over is drawn INSIDE
75
+ the canvas, so only script can hit-test it — but the appearance stays here, in CSS. The
76
+ component only ever states what is under the pointer (data-cursor="move", "resize-ns", ...);
77
+ which actual cursor that means is this stylesheet's business alone. Plain <rr0-ufo> playback
78
+ never sets the attribute, so it keeps the default arrow throughout.
79
+ Directions are SCREEN axes, already accounting for the shape's own rotation (see
80
+ ShapeHandles.resizeAxisFor) — a 45-degree-rotated shape's top-left handle really does resize
81
+ along the screen's north-east/south-west diagonal, so that is the cursor it gets. */
82
+ canvas[data-cursor="record"] {
83
+ cursor: crosshair;
84
+ }
85
+ canvas[data-cursor="select"] {
86
+ cursor: pointer;
87
+ }
88
+ canvas[data-cursor="move"] {
89
+ cursor: move;
90
+ }
91
+ canvas[data-cursor="vertex"] {
92
+ cursor: cell;
93
+ }
94
+ canvas[data-cursor="pan"] {
95
+ cursor: grab;
96
+ }
97
+ canvas[data-cursor="panning"] {
98
+ cursor: grabbing;
99
+ }
100
+ canvas[data-cursor="resize-ew"] {
101
+ cursor: ew-resize;
102
+ }
103
+ canvas[data-cursor="resize-ns"] {
104
+ cursor: ns-resize;
105
+ }
106
+ canvas[data-cursor="resize-nwse"] {
107
+ cursor: nwse-resize;
108
+ }
109
+ canvas[data-cursor="resize-nesw"] {
110
+ cursor: nesw-resize;
111
+ }
112
+ /* No native CSS cursor means "rotate", so this one is drawn here: a circular arrow, white with a
113
+ black outline so it stays readable over both a bright daytime sky and a night one. 12 12 is its
114
+ hotspot (the 24x24 image's own center, i.e. the middle of the circle it draws). The grab
115
+ fallback applies if the data URI cursor is ever rejected. */
116
+ canvas[data-cursor="rotate"] {
117
+ cursor: url("data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2224%22%20height=%2224%22%3E%3Cpath%20d=%22M12%205A7%207%200%201%201%206.1%208.5%22%20fill=%22none%22%20stroke=%22%23000%22%20stroke-width=%224%22%20stroke-linecap=%22round%22/%3E%3Cpath%20d=%22M12%201.5%2012%208.5%2017%205Z%22%20fill=%22%23000%22%20stroke=%22%23000%22%20stroke-width=%223%22%20stroke-linejoin=%22round%22/%3E%3Cpath%20d=%22M12%205A7%207%200%201%201%206.1%208.5%22%20fill=%22none%22%20stroke=%22%23fff%22%20stroke-width=%221.6%22%20stroke-linecap=%22round%22/%3E%3Cpath%20d=%22M12%201.5%2012%208.5%2017%205Z%22%20fill=%22%23fff%22/%3E%3C/svg%3E") 12 12, grab;
118
+ }
74
119
  .toolbar {
75
120
  position: absolute;
76
121
  left: 0;
@@ -164,4 +209,4 @@ input[type=range] {
164
209
  pointer-events: none;
165
210
  white-space: nowrap;
166
211
  }
167
- `;function W(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 p(n,e,t){return n+(e-n)*t}function A(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 G(n){return`#${n.map(e=>Math.round(e).toString(16).padStart(2,"0")).join("")}`}function Y(n,e,t){const s=A(n),i=A(e);return!s||!i?t<1?n:e:G([p(s[0],i[0],t),p(s[1],i[1],t),p(s[2],i[2],t)])}function q(n,e,t){const s={x:p(n.bounds.x,e.bounds.x,t),y:p(n.bounds.y,e.bounds.y,t),width:p(n.bounds.width,e.bounds.width,t),height:p(n.bounds.height,e.bounds.height,t)},i=p(n.angle,e.angle,t),a=p(n.transparency,e.transparency,t),r=p(n.haloScale,e.haloScale,t),o=Y(n.color,e.color,t);return n.kind==="polygon"&&e.kind==="polygon"&&n.points.length===e.points.length?{...n,bounds:s,angle:i,transparency:a,haloScale:r,color:o,points:n.points.map((h,c)=>({x:p(h.x,e.points[c].x,t),y:p(h.y,e.points[c].y,t)}))}:{...t<1?n:e,bounds:s,angle:i,transparency:a,haloScale:r,color:o}}class M{keyframes=[];order=[];groups=[];addKeyframe(e,t){const s=this.findInsertIndex(e);if(this.keyframes[s]?.t===e){const i=new Set(t.map(a=>a.sourceId));this.keyframes[s]={t:e,shapes:[...this.keyframes[s].shapes.filter(a=>!i.has(a.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(a=>a.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?q(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(a=>a.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(a=>a.sourceId===t);if(i)return{t:this.keyframes[s].t,shape:i.shape}}}hitTest(e,t,s,i){const a=this.sourceIds;for(let r=a.length-1;r>=0;r--){if(i?.has(a[r]))continue;const o=this.getInterpolatedShapeAt(e,a[r]);if(o&&W(o,t,s))return{sourceId:a[r],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 M;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 X(n,e,t){return Math.max(e,Math.min(t,n))}function Z(n,e,t){const s=((e-n)%360+540)%360-180;return((n+s*t)%360+360)%360}function x(n,e,t){return n+(e-n)*t}function Q(n,e,t){return{lat:n.lat===void 0||e.lat===void 0?void 0:x(n.lat,e.lat,t),lng:n.lng===void 0||e.lng===void 0?void 0:x(n.lng,e.lng,t),elevationM:x(n.elevationM,e.elevationM,t),headingDeg:n.headingDeg===void 0||e.headingDeg===void 0?void 0:Z(n.headingDeg,e.headingDeg,t),pitchDeg:x(n.pitchDeg,e.pitchDeg,t),fovDeg:x(n.fovDeg,e.fovDeg,t)}}class b{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?Q(s.pose,i.pose,X((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 b;for(const s of e.keyframes)t.addKeyframe(s.t,s.pose);return t}}function S(n,e,t){return n+(e-n)*t}function j(n,e,t){const s=((e-n)%360+540)%360-180;return((n+s*t)%360+360)%360}function ee(n,e,t){return Math.max(e,Math.min(t,n))}function te(n,e,t){return{cloudCover:S(n.cloudCover,e.cloudCover,t),cloudDarkness:S(n.cloudDarkness,e.cloudDarkness,t),precipitationType:t<1?n.precipitationType:e.precipitationType,precipitationIntensity:S(n.precipitationIntensity,e.precipitationIntensity,t),windDirectionDeg:j(n.windDirectionDeg,e.windDirectionDeg,t),windSpeed:S(n.windSpeed,e.windSpeed,t),storm:t<1?n.storm:e.storm}}class w{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?te(s.weather,i.weather,ee((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 w;for(const s of e.keyframes)t.addKeyframe(s.t,s.weather);return t}}function L(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 B(n){return(((n.day??0)*24+(n.hour??0))*60+(n.minute??0))*6e4+(n.second??0)*1e3}const se=["year","month","day","hour","minute"],ne=["day","hour","minute"];function O(n,e){return e.filter(t=>n[t]!==void 0).join(",")}function ie(n){if(n.durationSeconds!==void 0)return n.durationSeconds*1e3;if(!n.time||!n.endTime)return;const e=n.time.year!==void 0&&n.endTime.year!==void 0,t=e?se:ne,s=O(n.time,t);if(!(s===""||s!==O(n.endTime,t)))return e?L(n.endTime)-L(n.time):B(n.endTime)-B(n.time)}class D{constructor(e,t,s,i,a,r,o,h=[]){this.event=e,this.timeline=t,this.witnessTrack=s,this.weatherTrack=i,this.witness=a,this.caseId=r,this.weather=o,this.decor=h}static create(e,t,s){return new D({eventType:"sighting",time:e,place:t},new M,new b,new w,s)}}class re{constructor(e,t){this.timeline=e,this.onFrame=t}rafId=null;state="stopped";currentT=0;lastWallTime=0;playbackRate=1;loop=!1;durationOverrideMs=0;get seekableDuration(){return Math.max(this.timeline.duration,this.durationOverrideMs)}play(){if(this.state==="playing")return;this.currentT>=this.seekableDuration&&(this.currentT=0,this.resolveFrame(0)),this.state="playing",this.lastWallTime=performance.now();const e=()=>{if(this.state!=="playing")return;const t=performance.now();if(this.currentT+=(t-this.lastWallTime)*this.playbackRate,this.lastWallTime=t,this.currentT>=this.seekableDuration){if(this.loop&&this.seekableDuration>0){this.currentT%=this.seekableDuration,this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(e);return}this.currentT=this.seekableDuration,this.stop(),this.resolveFrame(this.currentT);return}this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(e)};this.rafId=requestAnimationFrame(e)}pause(){this.state==="playing"&&(this.state="paused",this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null))}stop(){this.state="stopped",this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null)}seek(e){this.currentT=Math.max(0,Math.min(e,this.seekableDuration)),this.resolveFrame(this.currentT)}get playbackState(){return this.state}get time(){return this.currentT}resolveFrame(e){const t=new Map;for(const s of this.timeline.sourceIds){const i=this.timeline.getInterpolatedShapeAt(e,s);i&&t.set(s,i)}this.onFrame(e,t)}}const $=["nw","n","ne","e","se","s","sw","w"],ae=24,I=8,oe=3,le=8;function E(n,e,t){const s=n.x-e.x,i=n.y-e.y,a=Math.cos(t),r=Math.sin(t);return{x:e.x+s*a-i*r,y:e.y+s*r+i*a}}function v(n){return{x:n.x+n.width/2,y:n.y+n.height/2}}const he={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 m{static handlePointsFor(e){const{x:t,y:s,width:i,height:a}=e.bounds,r={nw:{x:t,y:s},n:{x:t+i/2,y:s},ne:{x:t+i,y:s},e:{x:t+i,y:s+a/2},se:{x:t+i,y:s+a},s:{x:t+i/2,y:s+a},sw:{x:t,y:s+a},w:{x:t,y:s+a/2},rotate:{x:t+i/2,y:s-ae}},o=v(e.bounds),h={};for(const c of Object.keys(r))h[c]=E(r[c],o,e.angle);return h}static hitTestHandle(e,t,s=8,i=[...$,"rotate"]){const a=m.handlePointsFor(e);for(const r of i)if(Math.hypot(t.x-a[r].x,t.y-a[r].y)<=s)return r}static resizeBounds(e,t,s,i){const a=v(e),r=E(i,a,-t),{x:o,y:h,width:c,height:d}=e,l=he[s];let u=o,y=h,f=o+c,g=h+d;return l.left&&(u=Math.min(r.x,f-I)),l.right&&(f=Math.max(r.x,u+I)),l.top&&(y=Math.min(r.y,g-I)),l.bottom&&(g=Math.max(r.y,y+I)),{x:u,y,width:f-u,height:g-y}}static resizeShape(e,t,s){if(t==="rotate")throw new Error("resizeShape does not accept the rotate handle");const{width:i,height:a}=e.bounds,r=m.resizeBounds(e.bounds,e.angle,t,s);if(e.kind==="oval")return{...e,bounds:r};const o=i===0?1:r.width/i,h=a===0?1:r.height/a;return{...e,bounds:r,points:e.points.map(c=>({x:c.x*o,y:c.y*h}))}}static rotateShape(e,t){const s=v(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(r=>r.x)),s=Math.min(...e.map(r=>r.y)),i=Math.max(...e.map(r=>r.x+r.width)),a=Math.max(...e.map(r=>r.y+r.height));return{x:t,y:s,width:i-t,height:a-s}}static vertexPointsFor(e){const t=v(e.bounds);return e.points.map(s=>E({x:e.bounds.x+s.x,y:e.bounds.y+s.y},t,e.angle))}static hitTestVertex(e,t,s=le){const i=m.vertexPointsFor(e);let a,r=s;return i.forEach((o,h)=>{const c=Math.hypot(t.x-o.x,t.y-o.y);c<=r&&(r=c,a=h)}),a}static toLocalPoint(e,t){const s=v(e.bounds),i=E(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,u)=>u===t?m.toLocalPoint(e,s):l),a=Math.min(...i.map(l=>l.x)),r=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+a,y:e.bounds.y+r,width:o-a,height:h-r},d=i.map(l=>({x:l.x-a,y:l.y-r}));return{...e,bounds:c,points:d}}static insertVertexNear(e,t){const s=m.toLocalPoint(e,t),{points:i}=e;let a=i.length-1,r=1/0;for(let h=0;h<i.length;h++){const c=i[h],d=i[(h+1)%i.length],l=m.distanceToSegment(s,c,d);l<r&&(r=l,a=h)}const o=[...i.slice(0,a+1),s,...i.slice(a+1)];return{...e,points:o}}static distanceToSegment(e,t,s){const i=s.x-t.x,a=s.y-t.y,r=i*i+a*a,o=r===0?0:Math.max(0,Math.min(1,((e.x-t.x)*i+(e.y-t.y)*a)/r)),h=t.x+o*i,c=t.y+o*a;return Math.hypot(e.x-h,e.y-c)}static deleteVertex(e,t){return e.points.length<=oe?e:{...e,points:e.points.filter((s,i)=>i!==t)}}}const F=6,T=F/2,ce=20,C=4;class de{constructor(e){this.ctx=e}clear(e,t){this.ctx.clearRect(0,0,e,t)}paintShape(e){this.ctx.save(),this.ctx.globalAlpha=1-e.transparency,e.haloScale>0&&this.paintHalo(e),this.paintBase(e),this.ctx.restore(),e.selected&&this.paintSelectionHandles(e)}paintBase(e){if(this.ctx.fillStyle=e.color,this.ctx.beginPath(),e.kind==="oval"){const{x:t,y:s,width:i,height:a}=e.bounds,r=i/2,o=a/2;this.ctx.ellipse(t+r,s+o,r,o,e.angle,0,2*Math.PI)}else{const{x:t,y:s,width:i,height:a}=e.bounds;this.ctx.save(),this.ctx.translate(t+i/2,s+a/2),this.ctx.rotate(e.angle),this.ctx.translate(-i/2,-a/2),e.points.forEach((r,o)=>{o===0?this.ctx.moveTo(r.x,r.y):this.ctx.lineTo(r.x,r.y)}),this.ctx.closePath(),this.ctx.restore()}this.ctx.fill()}paintHalo(e){this.ctx.save(),this.ctx.shadowColor=e.color,this.ctx.shadowBlur=ce*e.haloScale,this.paintBase(e),this.ctx.restore()}paintSelectionHandles(e){this.paintHandleFrame(m.handlePointsFor(e),{includeRotate:!0}),e.kind==="polygon"&&this.paintVertexHandles(e)}paintVertexHandles(e){this.ctx.fillStyle="#39f";for(const t of m.vertexPointsFor(e))this.ctx.beginPath(),this.ctx.ellipse(t.x,t.y,C,C,0,0,2*Math.PI),this.ctx.fill()}paintMemberOutline(e){this.paintOutline(m.handlePointsFor(e))}paintGroupHandles(e){this.paintHandleFrame(m.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 $){const i=e[s];this.ctx.fillRect(i.x-T,i.y-T,F,F)}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,T+1,T+1,0,0,2*Math.PI),this.ctx.fill())}}function ue(n){return{version:1,time:n.event.time,endTime:n.event.endTime,durationSeconds:n.event.durationSeconds,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(),weather:n.weather,decor:n.decor}}function fe(n){return new D({eventType:"sighting",time:n.time,endTime:n.endTime,durationSeconds:n.durationSeconds,place:n.place,description:n.description,tags:n.tags},M.fromJSON(n.timeline),n.witnessTrack?b.fromJSON(n.witnessTrack):new b,n.weatherTrack?w.fromJSON(n.weatherTrack):new w,n.witness,n.caseId,n.weather,n.decor??[])}function pe(n,e){for(const t of n){const s=t.toLowerCase().split("-")[0];if(e.includes(s))return s}return"en"}const me="modulepreload",ge=function(n,e){return new URL(n,e).href},R={},N=function(e,t,s){let i=Promise.resolve();if(t&&t.length>0){let r=function(d){return Promise.all(d.map(l=>Promise.resolve(l).then(u=>({status:"fulfilled",value:u}),u=>({status:"rejected",reason:u}))))};const o=document.getElementsByTagName("link"),h=document.querySelector("meta[property=csp-nonce]"),c=h?.nonce||h?.getAttribute("nonce");i=r(t.map(d=>{if(d=ge(d,s),d in R)return;R[d]=!0;const l=d.endsWith(".css"),u=l?'[rel="stylesheet"]':"";if(!!s)for(let g=o.length-1;g>=0;g--){const k=o[g];if(k.href===d&&(!l||k.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${d}"]${u}`))return;const f=document.createElement("link");if(f.rel=l?"stylesheet":me,l||(f.as="script"),f.crossOrigin="",f.href=d,c&&f.setAttribute("nonce",c),document.head.appendChild(f),l)return new Promise((g,k)=>{f.addEventListener("load",g),f.addEventListener("error",()=>k(new Error(`Unable to preload CSS for ${d}`)))})}))}function a(r){const o=new Event("vite:preloadError",{cancelable:!0});if(o.payload=r,window.dispatchEvent(o),!o.defaultPrevented)throw r}return i.then(r=>{for(const o of r||[])o.status==="rejected"&&a(o.reason);return e().catch(a)})},ye=["en","fr"],xe={en:()=>N(()=>Promise.resolve().then(()=>be),void 0,import.meta.url).then(n=>n.ufoMessages_en),fr:()=>N(()=>import("./UfoMessages_fr-NZrwNFCQ.js"),[],import.meta.url).then(n=>n.ufoMessages_fr)};function ve(n){return xe[n]()}const J={play:"Play",pause:"Pause",noDuration:"No observation duration",autoReplay:"Auto-replay",currentPosition:"Current position",duration:"Duration",fullscreen:"Fullscreen",exitFullscreen:"Exit fullscreen"},be=Object.freeze(Object.defineProperty({__proto__:null,ufoMessages_en:J},Symbol.toStringTag,{value:"Module"})),_=new Set;class we extends HTMLElement{static get observedAttributes(){return["src"]}shadow;stageElement;canvas;canvasRenderer;tooltip;toolbar;playPauseButton;loopButton;fullscreenButton;seekInput;timeStartLabel;timeEndLabel;currentSighting=D.create();player;loopEnabled=!0;highlightedSourceIds=new Set;occludedSourceIds=_;enableClickToPlay=!0;fullscreenTarget;messages=J;realDurationMs;realStartMs;handleFullscreenChange=()=>this.updateFullscreenButton();handlePointerMove=e=>{const t=this.canvasPointFromEvent(e),s=t&&this.currentSighting.timeline.hitTest(this.currentTime,t.x,t.y,this.occludedSourceIds);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>${V}</style>${K}`,this.shadow.appendChild(e.content.cloneNode(!0)),this.stageElement=this.shadow.getElementById("stage"),this.canvas=this.shadow.getElementById("canvas"),this.canvasRenderer=new de(this.canvas.getContext("2d")),this.tooltip=this.shadow.getElementById("tooltip"),this.toolbar=this.shadow.getElementById("toolbar"),this.playPauseButton=this.shadow.getElementById("play-pause"),this.loopButton=this.shadow.getElementById("loop"),this.fullscreenButton=this.shadow.getElementById("fullscreen"),this.seekInput=this.shadow.getElementById("seek"),this.timeStartLabel=this.shadow.getElementById("time-start"),this.timeEndLabel=this.shadow.getElementById("time-end"),this.fullscreenTarget=this.stageElement,this.playPauseButton.addEventListener("click",()=>this.togglePlayPause()),this.loopButton.addEventListener("click",()=>this.toggleLoop()),this.fullscreenButton.addEventListener("click",()=>this.toggleFullscreen()),this.seekInput.addEventListener("input",()=>this.player.seek(Number(this.seekInput.value))),this.canvas.addEventListener("click",()=>{this.enableClickToPlay&&this.togglePlayPause()}),this.canvas.addEventListener("pointermove",this.handlePointerMove),this.canvas.addEventListener("pointerleave",this.handlePointerLeave),document.addEventListener("fullscreenchange",this.handleFullscreenChange),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.updateFullscreenButton(),this.refresh(),this.loadLocaleMessages()}connectedCallback(){const e=this.getAttribute("src");e&&this.loadFromSrc(e)}disconnectedCallback(){document.removeEventListener("fullscreenchange",this.handleFullscreenChange)}attributeChangedCallback(e,t,s){e==="src"&&s&&s!==t&&this.isConnected&&this.loadFromSrc(s)}async loadFromSrc(e){const t=await fetch(e);this.sightingData=await t.json()}get sightingData(){return ue(this.currentSighting)}set sightingData(e){this.player.stop(),this.currentSighting=fe(e),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.refresh()}get sighting(){return this.currentSighting}get canvasElement(){return this.canvas}get renderer(){return this.canvasRenderer}get currentTime(){return this.player.time}set currentTime(e){this.player.seek(e)}get seekableDuration(){return this.player.seekableDuration}get autoReplayEnabled(){return this.loopEnabled}get positionLabel(){return this.timeStartLabel.textContent??""}get durationLabel(){return this.timeEndLabel.textContent??""}set showToolbar(e){this.toolbar.classList.toggle("hidden",!e)}get playbackState(){return this.player.playbackState}get selectedSourceIds(){return this.highlightedSourceIds}get durationSeconds(){return this.currentSighting.event.durationSeconds}set durationSeconds(e){this.currentSighting.event.durationSeconds=e,this.updateTimeLabels(),this.refresh(),this.updatePlayPauseButton()}set selectedSourceIds(e){const t=new Set(e);t.size===this.highlightedSourceIds.size&&[...t].every(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.currentSighting.timeline.hitTest(this.currentTime,e,t,this.occludedSourceIds)!==void 0}refresh(){this.seekInput.max=String(this.player.seekableDuration),this.player.seek(this.player.time)}canvasPointFromEvent(e){const t=this.canvas.getBoundingClientRect();if(!(t.width===0||t.height===0))return{x:(e.clientX-t.left)/t.width*this.canvas.width,y:(e.clientY-t.top)/t.height*this.canvas.height}}onFrame(e,t){this.canvasRenderer.clear(this.canvas.width,this.canvas.height);const s=this.playbackState!=="playing"?this.highlightedSourceIds:_;for(const[i,a]of t){if(this.occludedSourceIds.has(i))continue;const r=s.has(i);r&&s.size===1?this.canvasRenderer.paintShape({...a,selected:!0}):(this.canvasRenderer.paintShape(a),r&&this.canvasRenderer.paintMemberOutline(a))}if(s.size>1){const i=m.groupBoundsFor([...t].filter(([a])=>s.has(a)).map(([,a])=>a.bounds));this.canvasRenderer.paintGroupHandles(i)}this.seekInput.value=String(e),this.timeStartLabel.textContent=this.formatPosition(e),this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{time:e}}))}createPlayer(){const e=new re(this.currentSighting.timeline,(t,s)=>this.onFrame(t,s));return e.loop=this.loopEnabled,e}togglePlayPause(){this.player.seekableDuration<=0||(this.player.playbackState==="playing"?(this.player.pause(),this.refresh()):this.player.play(),this.updatePlayPauseButton())}updatePlayPauseButton(){const e=this.player.playbackState==="playing";this.playPauseButton.textContent=e?"⏸":"▶";const t=this.player.seekableDuration>0;this.playPauseButton.disabled=!t;const 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}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=pe(navigator.languages,ye);e!=="en"&&this.applyMessages(await ve(e))}applyMessages(e){this.messages=e,this.timeStartLabel.title=e.currentPosition,this.timeEndLabel.title=e.duration,this.loopButton.title=e.autoReplay,this.loopButton.setAttribute("aria-label",e.autoReplay),this.updatePlayPauseButton(),this.updateFullscreenButton()}updateTimeLabels(){const e=this.currentSighting.event,t=ie(e);this.realDurationMs=t!==void 0&&t>0?t:void 0,this.realStartMs=e.time?L(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=this.realDurationMs??0,this.timeEndLabel.textContent=this.formatEndOfTimeline(),this.timeStartLabel.textContent=this.formatPosition(this.player.time)}formatPosition(e){if(this.realDurationMs===void 0)return P(e);const t=this.currentSighting.timeline.duration,s=t>0&&e<=t?e/t*this.realDurationMs:e;return this.realStartMs!==void 0?z(H(this.realStartMs+s)):P(s)}formatEndOfTimeline(){return this.realDurationMs===void 0?P(this.currentSighting.timeline.duration):this.realStartMs!==void 0?z(H(this.realStartMs+this.realDurationMs)):P(this.realDurationMs)}}function H(n){const e=new Date(n);return{hour:e.getUTCHours(),minute:e.getUTCMinutes(),second:e.getUTCSeconds()}}function z(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 P(n){const e=Math.round(n/1e3),t=Math.floor(e/60),s=e%60;return`${t}:${String(s).padStart(2,"0")}`}const U="rr0-ufo";function ke(){customElements.get(U)||customElements.define(U,we)}ke();
212
+ `;class F{static MOON_ANGULAR_WIDTH_DEG=.5237;static angularWidthDeg(e){return 2*Math.atan(e.sizeM/(2*e.distanceM))*180/Math.PI}static widthPx(e,t,s){return t*e.sizeM/(2*e.distanceM*Math.tan(s*Math.PI/360))}static pxToDeg(e,t,s){return 2*Math.atan(e*Math.tan(s*Math.PI/360)/t)*180/Math.PI}static inMoons(e){return e/F.MOON_ANGULAR_WIDTH_DEG}static lerp(e,t,s){if(!(!e||!t))return{sizeM:e.sizeM+(t.sizeM-e.sizeM)*s,distanceM:e.distanceM+(t.distanceM-e.distanceM)*s}}}function V(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 m(n,e,t){return n+(e-n)*t}function O(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 Y(n){return`#${n.map(e=>Math.round(e).toString(16).padStart(2,"0")).join("")}`}function q(n,e,t){const s=O(n),i=O(e);return!s||!i?t<1?n:e:Y([m(s[0],i[0],t),m(s[1],i[1],t),m(s[2],i[2],t)])}function X(n,e,t){const s={x:m(n.bounds.x,e.bounds.x,t),y:m(n.bounds.y,e.bounds.y,t),width:m(n.bounds.width,e.bounds.width,t),height:m(n.bounds.height,e.bounds.height,t)},i=m(n.angle,e.angle,t),r=m(n.transparency,e.transparency,t),a=m(n.haloScale,e.haloScale,t),o=q(n.color,e.color,t),h=F.lerp(n.physical,e.physical,t);return n.kind==="polygon"&&e.kind==="polygon"&&n.points.length===e.points.length?{...n,bounds:s,angle:i,transparency:r,haloScale:a,color:o,physical:h,points:n.points.map((c,u)=>({x:m(c.x,e.points[u].x,t),y:m(c.y,e.points[u].y,t)}))}:{...t<1?n:e,bounds:s,angle:i,transparency:r,haloScale:a,color:o,physical:h}}class P{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?X(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&&V(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 P;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 Z(n,e,t){return Math.max(e,Math.min(t,n))}function Q(n,e,t){const s=((e-n)%360+540)%360-180;return((n+s*t)%360+360)%360}function v(n,e,t){return n+(e-n)*t}function j(n,e,t){return{lat:n.lat===void 0||e.lat===void 0?void 0:v(n.lat,e.lat,t),lng:n.lng===void 0||e.lng===void 0?void 0:v(n.lng,e.lng,t),elevationM:v(n.elevationM,e.elevationM,t),headingDeg:n.headingDeg===void 0||e.headingDeg===void 0?void 0:Q(n.headingDeg,e.headingDeg,t),pitchDeg:v(n.pitchDeg,e.pitchDeg,t),fovDeg:v(n.fovDeg,e.fovDeg,t)}}class b{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?j(s.pose,i.pose,Z((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 b;for(const s of e.keyframes)t.addKeyframe(s.t,s.pose);return t}}function S(n,e,t){return n+(e-n)*t}function ee(n,e,t){const s=((e-n)%360+540)%360-180;return((n+s*t)%360+360)%360}function te(n,e,t){return Math.max(e,Math.min(t,n))}function se(n,e,t){return{cloudCover:S(n.cloudCover,e.cloudCover,t),cloudDarkness:S(n.cloudDarkness,e.cloudDarkness,t),precipitationType:t<1?n.precipitationType:e.precipitationType,precipitationIntensity:S(n.precipitationIntensity,e.precipitationIntensity,t),windDirectionDeg:ee(n.windDirectionDeg,e.windDirectionDeg,t),windSpeed:S(n.windSpeed,e.windSpeed,t),storm:t<1?n.storm:e.storm}}class w{keyframes=[];addKeyframe(e,t){const s=this.findInsertIndex(e);this.keyframes[s]?.t===e?this.keyframes[s]={t:e,weather:t}:this.keyframes.splice(s,0,{t:e,weather:t})}clear(){this.keyframes.length=0}removeKeyframeAt(e){const t=this.findInsertIndex(e);this.keyframes[t]?.t===e&&this.keyframes.splice(t,1)}findInsertIndex(e){let t=0,s=this.keyframes.length;for(;t<s;){const i=t+s>>>1;this.keyframes[i].t<e?t=i+1:s=i}return t}getLatestWeatherAt(e){let t=this.findInsertIndex(e);return this.keyframes[t]?.t!==e&&(t-=1),t>=0?this.keyframes[t].weather:void 0}getInterpolatedWeatherAt(e){const t=this.findInsertIndex(e),s=this.keyframes[t]?.t===e?this.keyframes[t]:this.keyframes[t-1];if(s?.t===e)return s.weather;const i=this.keyframes[t]?.t===e?void 0:this.keyframes[t];return s?i?se(s.weather,i.weather,te((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 w;for(const s of e.keyframes)t.addKeyframe(s.t,s.weather);return t}}function A(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 B(n){return(((n.day??0)*24+(n.hour??0))*60+(n.minute??0))*6e4+(n.second??0)*1e3}const ne=["year","month","day","hour","minute"],ie=["day","hour","minute"];function C(n,e){return e.filter(t=>n[t]!==void 0).join(",")}function re(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?ne:ie,s=C(n.time,t);if(!(s===""||s!==C(n.endTime,t)))return e?A(n.endTime)-A(n.time):B(n.endTime)-B(n.time)}class D{constructor(e,t,s,i,r,a,o,h=[]){this.event=e,this.timeline=t,this.witnessTrack=s,this.weatherTrack=i,this.witness=r,this.caseId=a,this.weather=o,this.decor=h}static create(e,t,s){return new D({eventType:"sighting",time:e,place:t},new P,new b,new w,s)}}class ae{constructor(e,t){this.timeline=e,this.onFrame=t}rafId=null;state="stopped";currentT=0;lastWallTime=0;playbackRate=1;loop=!1;durationOverrideMs=0;get seekableDuration(){return Math.max(this.timeline.duration,this.durationOverrideMs)}play(){if(this.state==="playing")return;this.currentT>=this.seekableDuration&&(this.currentT=0,this.resolveFrame(0)),this.state="playing",this.lastWallTime=performance.now();const e=()=>{if(this.state!=="playing")return;const t=performance.now();if(this.currentT+=(t-this.lastWallTime)*this.playbackRate,this.lastWallTime=t,this.currentT>=this.seekableDuration){if(this.loop&&this.seekableDuration>0){this.currentT%=this.seekableDuration,this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(e);return}this.currentT=this.seekableDuration,this.stop(),this.resolveFrame(this.currentT);return}this.resolveFrame(this.currentT),this.rafId=requestAnimationFrame(e)};this.rafId=requestAnimationFrame(e)}pause(){this.state==="playing"&&(this.state="paused",this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null))}stop(){this.state="stopped",this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null)}seek(e){this.currentT=Math.max(0,Math.min(e,this.seekableDuration)),this.resolveFrame(this.currentT)}get playbackState(){return this.state}get time(){return this.currentT}resolveFrame(e){const t=new Map;for(const s of this.timeline.sourceIds){const i=this.timeline.getInterpolatedShapeAt(e,s);i&&t.set(s,i)}this.onFrame(e,t)}}const G=["nw","n","ne","e","se","s","sw","w"],oe=24,I=8,le=3,he=8;function E(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 x(n){return{x:n.x+n.width/2,y:n.y+n.height/2}}const ce={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 d{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-oe}},o=x(e.bounds),h={};for(const c of Object.keys(a))h[c]=E(a[c],o,e.angle);return h}static hitTestHandle(e,t,s=8,i=[...G,"rotate"]){const r=d.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=((d.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=x(e),a=E(i,r,-t),{x:o,y:h,width:c,height:u}=e,l=ce[s];let f=o,y=h,p=o+c,g=h+u;return l.left&&(f=Math.min(a.x,p-I)),l.right&&(p=Math.max(a.x,f+I)),l.top&&(y=Math.min(a.y,g-I)),l.bottom&&(g=Math.max(a.y,y+I)),{x:f,y,width:p-f,height:g-y}}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=d.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=x(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=x(e.bounds);return e.points.map(s=>E({x:e.bounds.x+s.x,y:e.bounds.y+s.y},t,e.angle))}static hitTestVertex(e,t,s=he){const i=d.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=x(e.bounds),i=E(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,f)=>f===t?d.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=d.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=d.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<=le?e:{...e,points:e.points.filter((s,i)=>i!==t)}}}const L=6,T=L/2,ue=20,R=4;class de{constructor(e){this.ctx=e}clear(e,t){this.ctx.clearRect(0,0,e,t)}paintShape(e){this.ctx.save(),this.ctx.globalAlpha=1-e.transparency,e.haloScale>0&&this.paintHalo(e),this.paintBase(e),this.ctx.restore(),e.selected&&this.paintSelectionHandles(e)}paintBase(e){if(this.ctx.fillStyle=e.color,this.ctx.beginPath(),e.kind==="oval"){const{x:t,y: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=ue*e.haloScale,this.paintBase(e),this.ctx.restore()}paintSelectionHandles(e){this.paintHandleFrame(d.handlePointsFor(e),{includeRotate:!0}),e.kind==="polygon"&&this.paintVertexHandles(e)}paintVertexHandles(e){this.ctx.fillStyle="#39f";for(const t of d.vertexPointsFor(e))this.ctx.beginPath(),this.ctx.ellipse(t.x,t.y,R,R,0,0,2*Math.PI),this.ctx.fill()}paintMemberOutline(e){this.paintOutline(d.handlePointsFor(e))}paintGroupHandles(e){this.paintHandleFrame(d.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 G){const i=e[s];this.ctx.fillRect(i.x-T,i.y-T,L,L)}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,T+1,T+1,0,0,2*Math.PI),this.ctx.fill())}}function fe(n){return{version:1,time:n.event.time,endTime:n.event.endTime,durationSeconds:n.event.durationSeconds,utcOffsetHours:n.event.utcOffsetHours,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(),weather:n.weather,decor:n.decor}}function pe(n){return new D({eventType:"sighting",time:n.time,endTime:n.endTime,durationSeconds:n.durationSeconds,utcOffsetHours:n.utcOffsetHours,place:n.place,description:n.description,tags:n.tags},P.fromJSON(n.timeline),n.witnessTrack?b.fromJSON(n.witnessTrack):new b,n.weatherTrack?w.fromJSON(n.weatherTrack):new w,n.witness,n.caseId,n.weather,n.decor??[])}function me(n,e){for(const t of n){const s=t.toLowerCase().split("-")[0];if(e.includes(s))return s}return"en"}const ge="modulepreload",ye=function(n,e){return new URL(n,e).href},N={},_=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(f=>({status:"fulfilled",value:f}),f=>({status:"rejected",reason:f}))))};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=ye(u,s),u in N)return;N[u]=!0;const l=u.endsWith(".css"),f=l?'[rel="stylesheet"]':"";if(!!s)for(let g=o.length-1;g>=0;g--){const k=o[g];if(k.href===u&&(!l||k.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${u}"]${f}`))return;const p=document.createElement("link");if(p.rel=l?"stylesheet":ge,l||(p.as="script"),p.crossOrigin="",p.href=u,c&&p.setAttribute("nonce",c),document.head.appendChild(p),l)return new Promise((g,k)=>{p.addEventListener("load",g),p.addEventListener("error",()=>k(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)})},ve=["en","fr"],xe={en:()=>_(()=>Promise.resolve().then(()=>we),void 0,import.meta.url).then(n=>n.ufoMessages_en),fr:()=>_(()=>import("./UfoMessages_fr-NZrwNFCQ.js"),[],import.meta.url).then(n=>n.ufoMessages_fr)};function be(n){return xe[n]()}const J={play:"Play",pause:"Pause",noDuration:"No observation duration",autoReplay:"Auto-replay",currentPosition:"Current position",duration:"Duration",fullscreen:"Fullscreen",exitFullscreen:"Exit fullscreen"},we=Object.freeze(Object.defineProperty({__proto__:null,ufoMessages_en:J},Symbol.toStringTag,{value:"Module"})),z=new Set;class ke extends HTMLElement{static get observedAttributes(){return["src"]}shadow;stageElement;canvas;canvasRenderer;tooltip;toolbar;playPauseButton;loopButton;fullscreenButton;seekInput;timeStartLabel;timeEndLabel;currentSighting=D.create();player;loopEnabled=!0;highlightedSourceIds=new Set;occludedSourceIds=z;enableClickToPlay=!0;fullscreenTarget;messages=J;realDurationMs;realStartMs;handleFullscreenChange=()=>this.updateFullscreenButton();handlePointerMove=e=>{const t=this.canvasPointFromEvent(e),s=t&&this.currentSighting.timeline.hitTest(this.currentTime,t.x,t.y,this.occludedSourceIds);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>${W}</style>${K}`,this.shadow.appendChild(e.content.cloneNode(!0)),this.stageElement=this.shadow.getElementById("stage"),this.canvas=this.shadow.getElementById("canvas"),this.canvasRenderer=new de(this.canvas.getContext("2d")),this.tooltip=this.shadow.getElementById("tooltip"),this.toolbar=this.shadow.getElementById("toolbar"),this.playPauseButton=this.shadow.getElementById("play-pause"),this.loopButton=this.shadow.getElementById("loop"),this.fullscreenButton=this.shadow.getElementById("fullscreen"),this.seekInput=this.shadow.getElementById("seek"),this.timeStartLabel=this.shadow.getElementById("time-start"),this.timeEndLabel=this.shadow.getElementById("time-end"),this.fullscreenTarget=this.stageElement,this.playPauseButton.addEventListener("click",()=>this.togglePlayPause()),this.loopButton.addEventListener("click",()=>this.toggleLoop()),this.fullscreenButton.addEventListener("click",()=>this.toggleFullscreen()),this.seekInput.addEventListener("input",()=>this.player.seek(Number(this.seekInput.value))),this.canvas.addEventListener("click",()=>{this.enableClickToPlay&&this.togglePlayPause()}),this.canvas.addEventListener("pointermove",this.handlePointerMove),this.canvas.addEventListener("pointerleave",this.handlePointerLeave),document.addEventListener("fullscreenchange",this.handleFullscreenChange),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.updateFullscreenButton(),this.refresh(),this.loadLocaleMessages()}connectedCallback(){const e=this.getAttribute("src");e&&this.loadFromSrc(e)}disconnectedCallback(){document.removeEventListener("fullscreenchange",this.handleFullscreenChange)}attributeChangedCallback(e,t,s){e==="src"&&s&&s!==t&&this.isConnected&&this.loadFromSrc(s)}async loadFromSrc(e){const t=await fetch(e);this.sightingData=await t.json()}get sightingData(){return fe(this.currentSighting)}set sightingData(e){this.player.stop(),this.currentSighting=pe(e),this.player=this.createPlayer(),this.updateTimeLabels(),this.updatePlayPauseButton(),this.refresh()}get sighting(){return this.currentSighting}get canvasElement(){return this.canvas}get renderer(){return this.canvasRenderer}get currentTime(){return this.player.time}set currentTime(e){this.player.seek(e)}get seekableDuration(){return this.player.seekableDuration}get autoReplayEnabled(){return this.loopEnabled}get positionLabel(){return this.timeStartLabel.textContent??""}get durationLabel(){return this.timeEndLabel.textContent??""}set showToolbar(e){this.toolbar.classList.toggle("hidden",!e)}get playbackState(){return this.player.playbackState}get selectedSourceIds(){return this.highlightedSourceIds}get durationSeconds(){return this.currentSighting.event.durationSeconds}set durationSeconds(e){this.currentSighting.event.durationSeconds=e,this.updateTimeLabels(),this.refresh(),this.updatePlayPauseButton()}set selectedSourceIds(e){const t=new Set(e);t.size===this.highlightedSourceIds.size&&[...t].every(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.currentSighting.timeline.hitTest(this.currentTime,e,t,this.occludedSourceIds)!==void 0}refresh(){this.seekInput.max=String(this.player.seekableDuration),this.player.seek(this.player.time)}canvasPointFromEvent(e){const t=this.canvas.getBoundingClientRect();if(!(t.width===0||t.height===0))return{x:(e.clientX-t.left)/t.width*this.canvas.width,y:(e.clientY-t.top)/t.height*this.canvas.height}}onFrame(e,t){this.canvasRenderer.clear(this.canvas.width,this.canvas.height);const s=this.playbackState!=="playing"?this.highlightedSourceIds:z;for(const[i,r]of t){if(this.occludedSourceIds.has(i))continue;const a=s.has(i);a&&s.size===1?this.canvasRenderer.paintShape({...r,selected:!0}):(this.canvasRenderer.paintShape(r),a&&this.canvasRenderer.paintMemberOutline(r))}if(s.size>1){const i=d.groupBoundsFor([...t].filter(([r])=>s.has(r)).map(([,r])=>r.bounds));this.canvasRenderer.paintGroupHandles(i)}this.seekInput.value=String(e),this.timeStartLabel.textContent=this.formatPosition(e),this.updatePlayPauseButton(),this.dispatchEvent(new CustomEvent("timeupdate",{detail:{time:e}}))}createPlayer(){const e=new ae(this.currentSighting.timeline,(t,s)=>this.onFrame(t,s));return e.loop=this.loopEnabled,e}togglePlayPause(){this.player.seekableDuration<=0||(this.player.playbackState==="playing"?(this.player.pause(),this.refresh()):this.player.play(),this.updatePlayPauseButton())}updatePlayPauseButton(){const e=this.player.playbackState==="playing";this.playPauseButton.textContent=e?"⏸":"▶";const t=this.player.seekableDuration>0;this.playPauseButton.disabled=!t;const 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}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=me(navigator.languages,ve);e!=="en"&&this.applyMessages(await be(e))}applyMessages(e){this.messages=e,this.timeStartLabel.title=e.currentPosition,this.timeEndLabel.title=e.duration,this.loopButton.title=e.autoReplay,this.loopButton.setAttribute("aria-label",e.autoReplay),this.updatePlayPauseButton(),this.updateFullscreenButton()}updateTimeLabels(){const e=this.currentSighting.event,t=re(e);this.realDurationMs=t!==void 0&&t>0?t:void 0,this.realStartMs=e.time?A(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)}formatPosition(e){if(this.realDurationMs===void 0)return M(e);const t=this.currentSighting.timeline.duration,s=t>0&&e<=t?e/t*this.realDurationMs:e;return this.realStartMs!==void 0?U(H(this.realStartMs+s)):M(s)}formatEndOfTimeline(){return this.realDurationMs===void 0?M(this.currentSighting.timeline.duration):this.realStartMs!==void 0?U(H(this.realStartMs+this.realDurationMs)):M(this.realDurationMs)}}function H(n){const e=new Date(n);return{hour:e.getUTCHours(),minute:e.getUTCMinutes(),second:e.getUTCSeconds()}}function U(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 M(n){const e=Math.round(n/1e3),t=Math.floor(e/60),s=e%60;return`${t}:${String(s).padStart(2,"0")}`}const $="rr0-ufo";function Se(){customElements.get($)||customElements.define($,ke)}Se();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rr0/ufoathome",
3
3
  "type": "module",
4
- "version": "0.10.5",
4
+ "version": "0.11.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",
@@ -1 +0,0 @@
1
- const e={oval:"Ovale",polygon:"Polygone",addVertex:"Ajouter un sommet",deleteVertex:"Supprimer le sommet",notAPolygon:"Sélectionnez une seule forme polygonale",tooFewVertices:"Une forme a besoin d'au moins 3 points",color:"Couleur",transparency:"Transparence",halo:"Halo",shape:"Forme",shapeTitle:"Nom",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:"Fréquence d'échantillonnage",duration:"Durée",durationPlaceholder:"durée de l'observation",durationImprecise:"Ces dates ne permettent pas de déterminer précisément une durée — saisissez-la manuellement.",export:"Exporter le JSON",importFile:"Charger un fichier JSON",importUrl:"Ou charger depuis une URL",importUrlPlaceholder:"https://…/sighting.json",importButton:"Charger",importError:"Impossible de charger cet enregistrement — vérifiez le fichier ou l'URL et réessayez.",record:"Enregistrer",stop:"Arrêter",latitude:"Latitude",longitude:"Longitude",heading:"Orientation",headingPlaceholder:"inconnu",pitch:"Inclinaison",observationTime:"Début de l'observation",observationEndTime:"Fin de l'observation",edtfInvalid:"Date/heure EDTF invalide (ex. 1965-07-01T05:00, 2025-06?, 2025~).",edtfPlaceholder:"AAAA-MM-JJThh:mm[?~%] ou hh:mm",observationTimeHint:"EDTF — ex. 1965-07-01T05:00, 2025-06? (incertain), 2025~ (approximatif), ou juste 05:00 si la date est inconnue",observationEndTimeHint:"EDTF — ex. 1965-07-01T05:10, 2025-06? (incertain), 2025~ (approximatif), ou juste 05:10 si la date est inconnue",presetsGroupLabel:"Forme de l'ovni",witnessId:"ID témoin",witnessDirName:"Répertoire du témoin",witnessTitle:"Titre du témoin",witnessLastName:"Nom de famille du témoin",witnessFirstNames:"Prénoms du témoin",caseId:"ID de l'affaire",description:"Description",tags:"Mots-clés",tagsPlaceholder:"séparés par des virgules",weather:"Météo",shapeGroup:"Forme",temporalGroup:"Temporel",locationGroup:"Lieu",observationGroup:"Observation",witnessGroup:"Témoin",circumstancesGroup:"Circonstances",cloudCover:"Couverture nuageuse",cloudDarkness:"Obscurité des nuages",precipitationType:"Précipitations",precipitationNone:"Aucune",precipitationRain:"Pluie",precipitationSnow:"Neige",precipitationHail:"Grêle",precipitationIntensity:"Intensité",windDirection:"Direction du vent",windSpeed:"Force du vent",storm:"Orage",lensFlareBrightness:"Intensité lumineuse",cameraDevice:"Appareil photo/vidéo",decor:"Décor",decorBuilding:"Bâtiment",decorTree:"Arbre",decorStreetlight:"Lampadaire",decorVehicle:"Véhicule",decorWitness:"Autre témoin",addDecor:"Ajouter",deleteDecor:"Supprimer l'élément de décor",decorEast:"Distance à l'est",decorNorth:"Distance au nord",decorHeading:"Orientation",decorLit:"Allumé",decorTitle:"Nom",decorSightingUrl:"URL de l'enregistrement du témoin",viewTestimony:"Voir le témoignage",noWitnessRecording:"Aucune URL d'enregistrement définie pour ce témoin",masks:"Masque",addWitness:"Ajouter un témoin",decorFloors:"Étages",decorOccupiedFloor:"Étage occupé",decorWitnessSide:"Emplacement du témoin",decorWitnessSideNone:"Non présent",decorWindows:"Fenêtres",decorSideFront:"Avant",decorSideBehind:"Arrière",decorSideLeft:"Gauche",decorSideRight:"Droite"};export{e as ufoRecorderMessages_fr};