@rr0/ufoathome 0.133.0 → 0.135.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.
Files changed (50) hide show
  1. package/dist-embed-interview/rr0-interview.mjs +6 -6
  2. package/dist-embed-scene/rr0-scene.mjs +268 -266
  3. package/dist-embed-scene/sightingSchema-DqeNTmuQ.js +1 -0
  4. package/dist-embed-sighting/{SightingMessages_es-CxoccMw6.js → SightingMessages_es-Cck_4IWk.js} +1 -1
  5. package/dist-embed-sighting/{SightingMessages_fr-CdqAz1Hz.js → SightingMessages_fr-C3wmZbM0.js} +1 -1
  6. package/dist-embed-sighting/{SightingMessages_it-CkQVXDPv.js → SightingMessages_it-DCQUpWk8.js} +1 -1
  7. package/dist-embed-sighting/rr0-sighting.mjs +288 -279
  8. package/dist-embed-sighting/sightingSchema-DqeNTmuQ.js +1 -0
  9. package/dist-embed-sighting-editor/{AdsbLolArchiveProvider-DU2U8NdB.js → AdsbLolArchiveProvider-De-_PiBH.js} +1 -1
  10. package/dist-embed-sighting-editor/BeamSystem-YGYbbAkU.js +1 -0
  11. package/dist-embed-sighting-editor/{CaseEditorMessages_es-BrH54rIz.js → CaseEditorMessages_es-DUelIN8x.js} +1 -1
  12. package/dist-embed-sighting-editor/{CaseEditorMessages_fr-DF-qjRtx.js → CaseEditorMessages_fr-BppxXK1E.js} +1 -1
  13. package/dist-embed-sighting-editor/{CaseEditorMessages_it-Cb_U9B3i.js → CaseEditorMessages_it-BIYStAk9.js} +1 -1
  14. package/dist-embed-sighting-editor/ContrailSystem-CyYvtUTV.js +1 -0
  15. package/dist-embed-sighting-editor/{CoverageAssessor-DXVjXoUX.js → CoverageAssessor-DbLjQSsv.js} +1 -1
  16. package/dist-embed-sighting-editor/GLTFLoader-Clgrcwp4.js +1 -0
  17. package/dist-embed-sighting-editor/HeadlightSystem-wFRogS2e.js +34 -0
  18. package/dist-embed-sighting-editor/{HynekAssessor-Bc2SulU3.js → HynekAssessor-C6Lge7G-.js} +1 -1
  19. package/dist-embed-sighting-editor/{InterviewEditorElement-bUyn6irj.js → InterviewEditorElement-DzxGD2_i.js} +6 -6
  20. package/dist-embed-sighting-editor/ModelSource-D9Q4lorr.js +1 -0
  21. package/dist-embed-sighting-editor/{ModelThumbnails-CYHW7hxV.js → ModelThumbnails-DO6uXzoW.js} +1 -1
  22. package/dist-embed-sighting-editor/{ModelViewer-BzHV3bjz.js → ModelViewer-Ct9JM_Rx.js} +1 -1
  23. package/dist-embed-sighting-editor/RailwaySystem-CNhqPQHR.js +41 -0
  24. package/dist-embed-sighting-editor/{RoadMarkings-BWoz2Cuq.js → RoadMarkings-xnEtzMn8.js} +1 -1
  25. package/dist-embed-sighting-editor/{SightingEditorMessages_es-Dw15ohkc.js → SightingEditorMessages_es-BFzjoSef.js} +1 -1
  26. package/dist-embed-sighting-editor/{SightingEditorMessages_fr-DmH_cDSV.js → SightingEditorMessages_fr-90Ojknvv.js} +1 -1
  27. package/dist-embed-sighting-editor/{SightingEditorMessages_it-DcdM8wve.js → SightingEditorMessages_it-BtQIjKie.js} +2 -2
  28. package/dist-embed-sighting-editor/{SightingFileEditor-LAzFduCJ.js → SightingFileEditor-B6vMDoEQ.js} +1 -1
  29. package/dist-embed-sighting-editor/{TraceSystem-Dgxizn7s.js → TraceSystem-CArVw3Pm.js} +2 -2
  30. package/dist-embed-sighting-editor/WaterSystem-B-MThsCt.js +1 -0
  31. package/dist-embed-sighting-editor/caseSchema-DixCNMzt.js +1 -0
  32. package/dist-embed-sighting-editor/interviewJson-DXD8FSBX.js +8 -0
  33. package/dist-embed-sighting-editor/interviewSchema-D82l9P1r.js +1 -0
  34. package/dist-embed-sighting-editor/rr0-sighting-editor.mjs +365 -346
  35. package/dist-embed-sighting-editor/sightingSchema-DqeNTmuQ.js +1 -0
  36. package/dist-embed-sighting-editor/{trafficRuntime-STzu7-Xp.js → trafficRuntime-DiOPs8Lf.js} +1 -1
  37. package/package.json +1 -1
  38. package/dist-embed-scene/sightingSchema-DfbuP60Y.js +0 -1
  39. package/dist-embed-sighting/sightingSchema-DfbuP60Y.js +0 -1
  40. package/dist-embed-sighting-editor/BeamSystem-BnI9L86u.js +0 -1
  41. package/dist-embed-sighting-editor/ContrailSystem-DugiVtAv.js +0 -1
  42. package/dist-embed-sighting-editor/GLTFLoader-DLQh4lKb.js +0 -1
  43. package/dist-embed-sighting-editor/HeadlightSystem-BEWiubrn.js +0 -34
  44. package/dist-embed-sighting-editor/ModelSource-CnDfgMtY.js +0 -1
  45. package/dist-embed-sighting-editor/RailwaySystem-CzPTp4PW.js +0 -41
  46. package/dist-embed-sighting-editor/WaterSystem-Bv5uZSRK.js +0 -1
  47. package/dist-embed-sighting-editor/caseSchema-ByeWR3Rx.js +0 -1
  48. package/dist-embed-sighting-editor/interviewJson-CfwBPiuG.js +0 -8
  49. package/dist-embed-sighting-editor/interviewSchema-7rMvPyAl.js +0 -1
  50. package/dist-embed-sighting-editor/sightingSchema-DfbuP60Y.js +0 -1
@@ -1,5 +1,5 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./SightingFileEditor-LAzFduCJ.js","./sightingSchema-DfbuP60Y.js"])))=>i.map(i=>d[i]);
2
- import{S as L,a as T,H as j,s as P,U as H,l as I,I as W,P as z,b as y,W as O,r as J,c as D,E as C,A as U,d as E,M as q,_ as A,e as V}from"./rr0-sighting-editor.mjs";import{interviewMessages_en as S}from"./InterviewMessages_en-CRvWVmlL.js";import{InterviewMedia as N}from"./InterviewMedia-CvAxC62V.js";import{InterviewFile as b}from"./interviewJson-CfwBPiuG.js";const F="rr0-interview",G=`
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./SightingFileEditor-B6vMDoEQ.js","./sightingSchema-DqeNTmuQ.js"])))=>i.map(i=>d[i]);
2
+ import{S as L,a as T,H as j,s as P,U as H,l as I,I as W,P as z,b as y,W as O,r as J,c as D,E as C,A as U,d as E,M as q,_ as A,e as V}from"./rr0-sighting-editor.mjs";import{interviewMessages_en as S}from"./InterviewMessages_en-CRvWVmlL.js";import{InterviewMedia as N}from"./InterviewMedia-CvAxC62V.js";import{InterviewFile as g}from"./interviewJson-DXD8FSBX.js";const F="rr0-interview",G=`
3
3
  rr0-interview { display: block; --rr0-interview-line: color-mix(in srgb, currentColor 35%, transparent); }
4
4
  rr0-interview [hidden] { display: none !important; }
5
5
  rr0-interview .rr0-interview-status { font-size: .85em; opacity: .85; margin: 0 0 .5em; }
@@ -26,7 +26,7 @@ rr0-interview .turn .refs { display: flex; flex-wrap: wrap; gap: .25em; }
26
26
  rr0-interview .turn .ref { font: inherit; font-size: .8em; cursor: pointer; border: 1px solid var(--rr0-interview-line); border-radius: 999px; background: transparent; color: inherit;
27
27
  padding: 0 .35em; font-variant-numeric: tabular-nums; white-space: nowrap; display: inline-flex; align-items: center; gap: .15em; }
28
28
  rr0-interview .turn .ref svg { width: 1.1em; height: 1.1em; }
29
- `;class R extends HTMLElement{messages=S;said=new L(["en"]);localeToken=0;loadToken=0;json;loading;base;status=document.createElement("p");turns=document.createElement("div");blocks=new Map;constructor(){super(),this.status.className="rr0-interview-status",this.status.setAttribute("role","status"),this.turns.className="rr0-interview-turns"}static get observedAttributes(){return["src"]}attributeChangedCallback(e,t,r){e==="src"&&r!==t&&this.isConnected&&this.load()}connectedCallback(){R.styleRoot(this.getRootNode()),this.contains(this.turns)||this.replaceChildren(this.status,this.turns),this.loadLocale(),this.hasAttribute("src")&&this.json===void 0&&this.loading!==this.getAttribute("src")&&this.load()}get interview(){return this.json}set interview(e){this.loadToken++,this.json=e,this.draw()}set documentUrl(e){this.base=e}static styleRoot(e){const t=e instanceof ShadowRoot?e:document.head;if(t.querySelector("style[data-rr0-interview]"))return;const r=document.createElement("style");r.dataset.rr0Interview="",r.textContent=G,t.append(r)}async load(){const e=this.getAttribute("src"),t=++this.loadToken;if(this.loading=e??void 0,!e)return;const r=new URL(e,location.href).href;let s;try{s=await T.json(r)}catch{s=void 0}t===this.loadToken&&(this.base=r,this.json=s,this.draw())}async loadLocale(){const e=++this.localeToken,t=j.preferencesFor(this),r=P(t,H),s=r==="en"?S:await I(r);e===this.localeToken&&(this.said=new L(t),this.messages=s,this.draw())}speakerName(e,t){const r=b.speakerName(e,t),s=e.speakers?.[t]?.role;return r===s&&s in this.messages.roles?this.messages.roles[s]:r}seek(e,t){const r=this.json,s=t?b.said(t):void 0,i={sighting:e.sighting??b.sightingsOf(r)[0],at:e.at,...s?{quote:s}:{}};this.dispatchEvent(new CustomEvent(W,{bubbles:!0,composed:!0,detail:i}));const a=this.getAttribute("player");if(!a)return;const l=this.getRootNode().getElementById?.(a);typeof l?.seekTo=="function"&&(l.scrollIntoView?.({block:"center",behavior:"smooth"}),l.seekTo(i.at,i.sighting))}gap(e,t){const r=document.createElement(t),s=document.createElement("span");s.className="bar";const i=`${this.messages.redacted} : ${this.said.read(e)??""}`;return s.dataset.tip=i,s.tabIndex=0,s.setAttribute("role","img"),s.setAttribute("aria-label",i),r.append(s),r}saidNodes(e){if(!/[<&]/.test(e))return[document.createTextNode(e)];const t=document.createElement("template");t.innerHTML=e;for(const r of t.content.querySelectorAll("*")){if(["SCRIPT","STYLE","IFRAME","OBJECT","EMBED"].includes(r.tagName)){r.remove();continue}for(const s of[...r.attributes])if(/^on/i.test(s.name)||/^\s*javascript:/i.test(s.value))r.removeAttribute(s.name);else if(["href","src"].includes(s.name))try{s.value=new URL(s.value,this.base??location.href).href}catch{}}return[...t.content.childNodes]}draw(){const e=this.messages,t=this.json;this.blocks.clear();const r=Array.isArray(t?.segments)?t.segments:[];if(!t||r.length===0){this.status.hidden=!0;const d=document.createElement("p");d.textContent=e.exchangesEmpty,this.turns.replaceChildren(d);return}const s=t.speakers??{},i=Object.keys(s);if(t.proofreading){const d=i.filter(h=>!["approved","covered"].includes(t.proofreading?.[h]?.status??""));this.status.textContent=d.length===0?e.proofreadAll:e.proofreadPending.replace("{names}",d.map(h=>this.speakerName(t,h)).join(", ")),this.status.hidden=!1}else this.status.hidden=!0;const a=[];let n,l=!1,c,o;r.forEach((d,h)=>{const u=d.redacted?JSON.stringify(d.redacted.reason):void 0;if(u!==void 0&&c?.reason===u){this.blocks.set(h,c.block),d.speaker!==o&&(o=void 0);return}const p=document.createElement("div");p.className="turn",p.dataset.segment=String(h);const g=document.createElement("div");g.className="who";const x=document.createElement("span");if(x.textContent=d.redacted?"":this.speakerName(t,d.speaker),d.start!==void 0&&(p.dataset.time=b.clock(d.start,!0)),o!==d.speaker&&(n=document.createElement("div"),n.className="intervention",n.style.setProperty("--speaker-hue",String((Math.max(0,i.indexOf(d.speaker))*67+210)%360)),a.push(n),l=!1),d.redacted||(l&&p.classList.add("goes-on"),l=!0),n.append(p),o=d.speaker,g.append(x),d.redacted){p.classList.add("is-redacted");const v=this.gap(d.redacted.reason,"p");v.className="redacted",c={block:p,reason:u},p.append(g,v)}else{const v=document.createElement("p");if(v.className="said",d.parts){let k="";for(const w of d.parts){const M="redacted"in w?"▮":b.plain(w.text);/[\p{L}\p{N}.,;:!?)»”…▮]$/u.test(k)&&/^[\p{L}\p{N}(«“▮]/u.test(M)&&v.append(" "),"redacted"in w?v.append(this.gap(w.redacted.reason,"span")):v.append(...this.saidNodes(w.text)),k=M}}else v.append(...this.saidNodes(d.text??""));c=void 0,p.append(g,v)}if(d.refs?.length){const v=document.createElement("div");v.className="refs";for(const k of d.refs){const w=document.createElement("button");w.type="button",w.className="ref",w.innerHTML=z.PLAY,w.append(b.clock(k.at,!0)),w.addEventListener("click",()=>this.seek(k,d)),v.append(w)}p.append(v)}this.blocks.set(h,p)}),this.turns.replaceChildren(...a)}revealSegment(e){const t=this.blocks.get(e);t&&(t.scrollIntoView?.({block:"center"}),t.classList.add("flash"),window.setTimeout(()=>t.classList.remove("flash"),1600))}}function Y(){customElements.get(F)||customElements.define(F,R)}class f{static partsOf(e){return e.parts?e.parts:e.redacted?[]:[{text:e.text??""}]}static shownRanges(e){const t=[];let r="",s=0;return e.forEach((i,a)=>{const n="redacted"in i?"▮":b.plain(i.text);/[\p{L}\p{N}.,;:!?)»”…▮]$/u.test(r)&&/^[\p{L}\p{N}(«“▮]/u.test(n)&&(s+=1);const l="redacted"in i?0:n.length;t.push({part:a,from:s,to:s+l}),s+=l,r=n}),t}static words(e){return e.trim()===""?0:e.trim().split(/\s+/u).length}static redact(e,t,r,s){const i=[...f.partsOf(e)],a=f.shownRanges(i).find(x=>!("redacted"in i[x.part])&&t>=x.from&&r<=x.to);if(!a)return;const n=b.plain(i[a.part].text),l=/[\p{L}\p{N}'’-]/u;let c=t-a.from,o=r-a.from;for(;c>0&&l.test(n[c-1])&&l.test(n[c]);)c--;for(;o<n.length&&l.test(n[o])&&l.test(n[o-1]);)o++;const d=n.slice(c,o);if(d.trim()==="")return;const h=n.slice(0,c).trimEnd(),u=n.slice(o).trimStart(),p=[...h!==""?[{text:h}]:[],{redacted:{reason:s,words:f.words(d)}},...u!==""?[{text:u}]:[]],g=i.slice(0,a.part).filter(x=>"redacted"in x).length;return i.splice(a.part,1,...p),f.setParts(e,i),{text:d.trim(),nth:g}}static redactWhole(e,t){const r=JSON.stringify({text:e.text,parts:e.parts}),s=f.partsOf(e).reduce((i,a)=>i+("redacted"in a?a.redacted.words??0:f.words(b.plain(a.text))),0);if(delete e.text,delete e.parts,e.redacted={reason:t,...s>0?{words:s}:{}},e.refs)for(const i of e.refs)delete i.range;return r}static restoreWhole(e,t){const{text:r,parts:s}=JSON.parse(t);delete e.redacted,s?e.parts=s:e.text=r??""}static restorePart(e,t,r){const s=[...f.partsOf(e)];let i=-1;const a=s.findIndex(n=>"redacted"in n&&++i===t);a!==-1&&(s.splice(a,1,{text:r}),f.setParts(e,s))}static raw(e,t=()=>{}){let r=-1,s="",i="";for(const a of f.partsOf(e)){const n="redacted"in a?`⟦${t(++r)??"…"}⟧`:a.text;s!==""&&!/\s$/u.test(i)&&!/^\s/u.test(n)&&(s+=" "),s+=n,i=n}return s}static fromRaw(e,t,r){const s=f.partsOf(e).filter(l=>"redacted"in l),i=[],a=new Map;let n=-1;return t.split(/⟦([^⟧]*)⟧/u).forEach((l,c)=>{if(c%2===0){l.trim()!==""&&i.push({text:l.trim()});return}n++;const o=l.trim();o===""||o==="…"||o==="..."?i.push({redacted:s[n]?.redacted??{reason:r}}):(i.push({redacted:{reason:r,words:f.words(o)}}),a.set(n,o))}),delete e.redacted,f.setParts(e,i),a}static setParts(e,t){const r=[];for(const s of t){const i=r[r.length-1],a=!("redacted"in s)&&/^[,.;:!?)»”…]/u.test(s.text)?"":" ";i&&!("redacted"in i)&&!("redacted"in s)?r[r.length-1]={text:`${i.text}${a}${s.text}`.replace(/\s+/gu," ").trim()}:r.push(s)}if(r.some(s=>"redacted"in s)){if(e.parts=r,delete e.text,e.refs)for(const s of e.refs)delete s.range}else e.text=r.map(s=>s.text).join(" "),delete e.parts}}class B{constructor(e,t,r){this.host=t,this.messages=r,e.classList.add("player"),e.innerHTML=`<div class="player-video" hidden></div>
29
+ `;class R extends HTMLElement{messages=S;said=new L(["en"]);localeToken=0;loadToken=0;json;loading;base;status=document.createElement("p");turns=document.createElement("div");blocks=new Map;constructor(){super(),this.status.className="rr0-interview-status",this.status.setAttribute("role","status"),this.turns.className="rr0-interview-turns"}static get observedAttributes(){return["src"]}attributeChangedCallback(e,t,r){e==="src"&&r!==t&&this.isConnected&&this.load()}connectedCallback(){R.styleRoot(this.getRootNode()),this.contains(this.turns)||this.replaceChildren(this.status,this.turns),this.loadLocale(),this.hasAttribute("src")&&this.json===void 0&&this.loading!==this.getAttribute("src")&&this.load()}get interview(){return this.json}set interview(e){this.loadToken++,this.json=e,this.draw()}set documentUrl(e){this.base=e}static styleRoot(e){const t=e instanceof ShadowRoot?e:document.head;if(t.querySelector("style[data-rr0-interview]"))return;const r=document.createElement("style");r.dataset.rr0Interview="",r.textContent=G,t.append(r)}async load(){const e=this.getAttribute("src"),t=++this.loadToken;if(this.loading=e??void 0,!e)return;const r=new URL(e,location.href).href;let s;try{s=g.read(await T.json(r))}catch{s=void 0}t===this.loadToken&&(this.base=r,this.json=s,this.draw())}async loadLocale(){const e=++this.localeToken,t=j.preferencesFor(this),r=P(t,H),s=r==="en"?S:await I(r);e===this.localeToken&&(this.said=new L(t),this.messages=s,this.draw())}speakerName(e,t){const r=g.speakerName(e,t),s=e.speakers?.[t]?.role;return r===s&&s in this.messages.roles?this.messages.roles[s]:r}seek(e,t){const r=this.json,s=t?g.said(t):void 0,i={sighting:e.sighting??g.sightingsOf(r)[0],at:e.at,...s?{quote:s}:{}};this.dispatchEvent(new CustomEvent(W,{bubbles:!0,composed:!0,detail:i}));const a=this.getAttribute("player");if(!a)return;const l=this.getRootNode().getElementById?.(a);typeof l?.seekTo=="function"&&(l.scrollIntoView?.({block:"center",behavior:"smooth"}),l.seekTo(i.at,i.sighting))}gap(e,t){const r=document.createElement(t),s=document.createElement("span");s.className="bar";const i=`${this.messages.redacted} : ${this.said.read(e)??""}`;return s.dataset.tip=i,s.tabIndex=0,s.setAttribute("role","img"),s.setAttribute("aria-label",i),r.append(s),r}saidNodes(e){if(!/[<&]/.test(e))return[document.createTextNode(e)];const t=document.createElement("template");t.innerHTML=e;for(const r of t.content.querySelectorAll("*")){if(["SCRIPT","STYLE","IFRAME","OBJECT","EMBED"].includes(r.tagName)){r.remove();continue}for(const s of[...r.attributes])if(/^on/i.test(s.name)||/^\s*javascript:/i.test(s.value))r.removeAttribute(s.name);else if(["href","src"].includes(s.name))try{s.value=new URL(s.value,this.base??location.href).href}catch{}}return[...t.content.childNodes]}draw(){const e=this.messages,t=this.json;this.blocks.clear();const r=Array.isArray(t?.segments)?t.segments:[];if(!t||r.length===0){this.status.hidden=!0;const d=document.createElement("p");d.textContent=e.exchangesEmpty,this.turns.replaceChildren(d);return}const s=t.speakers??{},i=Object.keys(s);if(t.proofreading){const d=i.filter(h=>!["approved","covered"].includes(t.proofreading?.[h]?.status??""));this.status.textContent=d.length===0?e.proofreadAll:e.proofreadPending.replace("{names}",d.map(h=>this.speakerName(t,h)).join(", ")),this.status.hidden=!1}else this.status.hidden=!0;const a=[];let n,l=!1,c,o;r.forEach((d,h)=>{const u=d.redacted?JSON.stringify(d.redacted.reason):void 0;if(u!==void 0&&c?.reason===u){this.blocks.set(h,c.block),d.speaker!==o&&(o=void 0);return}const p=document.createElement("div");p.className="turn",p.dataset.segment=String(h);const b=document.createElement("div");b.className="who";const x=document.createElement("span");if(x.textContent=d.redacted?"":this.speakerName(t,d.speaker),d.start!==void 0&&(p.dataset.time=g.clock(d.start,!0)),o!==d.speaker&&(n=document.createElement("div"),n.className="intervention",n.style.setProperty("--speaker-hue",String((Math.max(0,i.indexOf(d.speaker))*67+210)%360)),a.push(n),l=!1),d.redacted||(l&&p.classList.add("goes-on"),l=!0),n.append(p),o=d.speaker,b.append(x),d.redacted){p.classList.add("is-redacted");const v=this.gap(d.redacted.reason,"p");v.className="redacted",c={block:p,reason:u},p.append(b,v)}else{const v=document.createElement("p");if(v.className="said",d.parts){let k="";for(const w of d.parts){const M="redacted"in w?"▮":g.plain(w.text);/[\p{L}\p{N}.,;:!?)»”…▮]$/u.test(k)&&/^[\p{L}\p{N}(«“▮]/u.test(M)&&v.append(" "),"redacted"in w?v.append(this.gap(w.redacted.reason,"span")):v.append(...this.saidNodes(w.text)),k=M}}else v.append(...this.saidNodes(d.text??""));c=void 0,p.append(b,v)}if(d.refs?.length){const v=document.createElement("div");v.className="refs";for(const k of d.refs){const w=document.createElement("button");w.type="button",w.className="ref",w.innerHTML=z.PLAY,w.append(g.clock(k.at,!0)),w.addEventListener("click",()=>this.seek(k,d)),v.append(w)}p.append(v)}this.blocks.set(h,p)}),this.turns.replaceChildren(...a)}revealSegment(e){const t=this.blocks.get(e);t&&(t.scrollIntoView?.({block:"center"}),t.classList.add("flash"),window.setTimeout(()=>t.classList.remove("flash"),1600))}}function Y(){customElements.get(F)||customElements.define(F,R)}class f{static partsOf(e){return e.parts?e.parts:e.redacted?[]:[{text:e.text??""}]}static shownRanges(e){const t=[];let r="",s=0;return e.forEach((i,a)=>{const n="redacted"in i?"▮":g.plain(i.text);/[\p{L}\p{N}.,;:!?)»”…▮]$/u.test(r)&&/^[\p{L}\p{N}(«“▮]/u.test(n)&&(s+=1);const l="redacted"in i?0:n.length;t.push({part:a,from:s,to:s+l}),s+=l,r=n}),t}static words(e){return e.trim()===""?0:e.trim().split(/\s+/u).length}static redact(e,t,r,s){const i=[...f.partsOf(e)],a=f.shownRanges(i).find(x=>!("redacted"in i[x.part])&&t>=x.from&&r<=x.to);if(!a)return;const n=g.plain(i[a.part].text),l=/[\p{L}\p{N}'’-]/u;let c=t-a.from,o=r-a.from;for(;c>0&&l.test(n[c-1])&&l.test(n[c]);)c--;for(;o<n.length&&l.test(n[o])&&l.test(n[o-1]);)o++;const d=n.slice(c,o);if(d.trim()==="")return;const h=n.slice(0,c).trimEnd(),u=n.slice(o).trimStart(),p=[...h!==""?[{text:h}]:[],{redacted:{reason:s,words:f.words(d)}},...u!==""?[{text:u}]:[]],b=i.slice(0,a.part).filter(x=>"redacted"in x).length;return i.splice(a.part,1,...p),f.setParts(e,i),{text:d.trim(),nth:b}}static redactWhole(e,t){const r=JSON.stringify({text:e.text,parts:e.parts}),s=f.partsOf(e).reduce((i,a)=>i+("redacted"in a?a.redacted.words??0:f.words(g.plain(a.text))),0);if(delete e.text,delete e.parts,e.redacted={reason:t,...s>0?{words:s}:{}},e.refs)for(const i of e.refs)delete i.range;return r}static restoreWhole(e,t){const{text:r,parts:s}=JSON.parse(t);delete e.redacted,s?e.parts=s:e.text=r??""}static restorePart(e,t,r){const s=[...f.partsOf(e)];let i=-1;const a=s.findIndex(n=>"redacted"in n&&++i===t);a!==-1&&(s.splice(a,1,{text:r}),f.setParts(e,s))}static raw(e,t=()=>{}){let r=-1,s="",i="";for(const a of f.partsOf(e)){const n="redacted"in a?`⟦${t(++r)??"…"}⟧`:a.text;s!==""&&!/\s$/u.test(i)&&!/^\s/u.test(n)&&(s+=" "),s+=n,i=n}return s}static fromRaw(e,t,r){const s=f.partsOf(e).filter(l=>"redacted"in l),i=[],a=new Map;let n=-1;return t.split(/⟦([^⟧]*)⟧/u).forEach((l,c)=>{if(c%2===0){l.trim()!==""&&i.push({text:l.trim()});return}n++;const o=l.trim();o===""||o==="…"||o==="..."?i.push({redacted:s[n]?.redacted??{reason:r}}):(i.push({redacted:{reason:r,words:f.words(o)}}),a.set(n,o))}),delete e.redacted,f.setParts(e,i),a}static setParts(e,t){const r=[];for(const s of t){const i=r[r.length-1],a=!("redacted"in s)&&/^[,.;:!?)»”…]/u.test(s.text)?"":" ";i&&!("redacted"in i)&&!("redacted"in s)?r[r.length-1]={text:`${i.text}${a}${s.text}`.replace(/\s+/gu," ").trim()}:r.push(s)}if(r.some(s=>"redacted"in s)){if(e.parts=r,delete e.text,e.refs)for(const s of e.refs)delete s.range}else e.text=r.map(s=>s.text).join(" "),delete e.parts}}class B{constructor(e,t,r){this.host=t,this.messages=r,e.classList.add("player"),e.innerHTML=`<div class="player-video" hidden></div>
30
30
  <div class="player-bar">
31
31
  <button type="button" class="icon-btn player-play" disabled>${y.PLAY}</button>
32
32
  <span class="player-time">0:00</span>
@@ -59,7 +59,7 @@ rr0-interview .turn .ref svg { width: 1.1em; height: 1.1em; }
59
59
  .recording-file { display: flex; gap: 6px; }
60
60
  .recording-status { grid-column: 1 / -1; margin: -4px 0 0; font-size: 12px; color: var(--ink-faint); }
61
61
  .recording-status.error { color: var(--error); }
62
- `;json;base;media;mediaFor="";picked=new Map;messages;play;time;end;track;video;empty;open;window;setMessages(e){this.messages=e,this.render()}show(e,t){this.json=e,this.base=t,this.render()}get now(){return this.media?Math.max(0,this.media.currentTime-(this.recordings[0]?.offset??0)):this.position}seek(e){this.position=Math.min(this.duration,Math.max(0,e)),this.media&&(this.media.currentTime=this.position+(this.recordings[0]?.offset??0)),this.placeHead(),this.host.timeChanged(this.position)}position=0;get recordings(){return Array.isArray(this.json?.recordings)?this.json.recordings:[]}get duration(){const e=this.media&&Number.isFinite(this.media.duration)?this.media.duration-(this.recordings[0]?.offset??0):0,t=Math.max(0,...(this.json?.segments??[]).map(r=>r.end??r.start??0));return Math.max(1,e,t)}render(){const e=this.messages,t=this.recordings;this.open.innerHTML='<svg class="icon" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M6 12V3.5l7-1.5V10.5"/><circle cx="4.5" cy="12" r="1.5"/><circle cx="11.5" cy="10.5" r="1.5"/></svg>',this.open.append(e.recordings.replace("{n}",String(t.length))),this.empty.textContent=t.length===0?e.noRecording:"",this.empty.hidden=t.length>0,this.play.title=e.play,this.play.setAttribute("aria-label",e.play),this.track.setAttribute("aria-label",e.recordingsTitle),this.ensureMedia(),this.drawTrack(),this.window.open&&this.drawRows()}ensureMedia(){const e=this.recordings[0],t=e?`${e.kind}|${e.src}`:"";if(t===this.mediaFor||(this.mediaFor=t,this.media?.pause(),this.media=void 0,this.video.replaceChildren(),this.video.hidden=!0,this.play.disabled=!0,!e||e.src===""))return;const r=document.createElement(e.kind==="video"?"video":"audio");r.preload="metadata",r.src=this.urlOf(e.src),r.addEventListener("loadedmetadata",()=>this.drawTrack()),r.addEventListener("timeupdate",()=>{this.placeHead(),this.host.timeChanged(this.now)});const s=()=>{const i=!r.paused;this.play.innerHTML=i?y.PAUSE:y.PLAY;const a=i?this.messages.pause:this.messages.play;this.play.title=a,this.play.setAttribute("aria-label",a)};r.addEventListener("play",s),r.addEventListener("pause",s),e.kind==="video"&&(this.video.append(r),this.video.hidden=!1),this.media=r,this.play.disabled=!1}urlOf(e){const t=this.picked.get(e);if(t)return t;try{return new URL(e,this.base??location.href).href}catch{return e}}drawTrack(){const e=this.json,t=this.duration,r=(e?.segments??[]).filter(i=>i.start!==void 0);this.track.replaceChildren(...r.map(i=>{const a=document.createElement("span"),n=e?.speakers?.[i.speaker]?.role;a.className=i.redacted?"seg redacted":"seg",a.dataset.role=n==="witness"||n==="interviewer"?n:"other";const l=i.start??0,c=i.end??l+1;return a.style.left=`${l/t*100}%`,a.style.width=`${Math.max(.3,(c-l)/t*100)}%`,a}));const s=document.createElement("span");s.className="head",this.track.append(s),this.track.setAttribute("aria-valuemax",String(Math.round(t))),this.end.textContent=b.clock(t),this.placeHead()}placeHead(){const e=this.now,t=this.track.querySelector(".head");t&&(t.style.left=`${Math.min(e,this.duration)/this.duration*100}%`),this.time.textContent=b.clock(Math.floor(e)),this.track.setAttribute("aria-valuenow",String(Math.round(e)))}showWindow(){this.drawRows(),typeof this.window.showModal=="function"?this.window.showModal():this.window.setAttribute("open","")}drawRows(){const e=this.messages;this.window.querySelector("h3").textContent=e.recordingsTitle,this.window.querySelector(".recording-add span").textContent=e.recordingAdd,O.dialog(this.window,e.close,()=>this.window.close()),this.window.querySelector(".recording-rows").replaceChildren(...this.recordings.map((r,s)=>{const i=document.createElement("div");i.className="recording-row";const a=document.createElement("select");a.append(new Option(e.recordingAudio,"audio"),new Option(e.recordingVideo,"video")),a.value=r.kind,a.addEventListener("change",()=>this.edit(p=>{p[s].kind=a.value}));const n=document.createElement("input");n.type="text",n.value=r.src,n.placeholder="interview.mp3",n.addEventListener("change",()=>this.edit(p=>{p[s].src=n.value.trim()}));const l=document.createElement("input");l.type="file",l.accept="audio/*,video/*",l.hidden=!0,l.addEventListener("change",()=>{const p=l.files?.[0];p&&(this.picked.set(p.name,URL.createObjectURL(p)),this.edit(g=>{g[s].src=p.name,p.type.startsWith("video/")&&(g[s].kind="video")}))});const c=document.createElement("button");c.type="button",c.className="icon-btn",c.title=e.recordingFile,c.setAttribute("aria-label",e.recordingFile),c.innerHTML='<svg class="icon" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 4.5h4l1.5 1.5h5.5v6.5h-11z"/></svg>',c.addEventListener("click",()=>l.click());const o=document.createElement("span");o.className="recording-file",o.append(n,c,l);const d=document.createElement("input");d.type="number",d.step="0.1",d.value=r.offset!==void 0?String(r.offset):"",d.placeholder="0",d.addEventListener("change",()=>this.edit(p=>{const g=Number(d.value);d.value.trim()===""||!Number.isFinite(g)||g===0?delete p[s].offset:p[s].offset=g}));const h=document.createElement("button");h.type="button",h.className="icon-btn",h.innerHTML=y.TRASH,h.title=e.recordingRemove,h.setAttribute("aria-label",e.recordingRemove),h.addEventListener("click",()=>this.edit(p=>{p.splice(s,1)}));const u=document.createElement("p");return u.className="recording-status",this.check(r,u),i.append(this.labelled(e.recordingKind,a),this.labelled(e.recordingFile,o),this.labelled(e.recordingOffset,d),h,u),i}))}labelled(e,t){const r=document.createElement("label"),s=document.createElement("span");return s.textContent=e,r.append(s,t),r}check(e,t){if(e.src==="")return;const r=document.createElement(e.kind==="video"?"video":"audio");r.preload="metadata",r.addEventListener("loadedmetadata",()=>{t.textContent=b.clock(Math.round(r.duration)),t.classList.remove("error")}),r.addEventListener("error",()=>{t.textContent=this.messages.loadFailed,t.classList.add("error")}),r.src=this.urlOf(e.src)}edit(e){const t=structuredClone(this.recordings);e(t),this.host.recordingsChanged(t)}}const $="rr0-interview-editor",K=`
62
+ `;json;base;media;mediaFor="";picked=new Map;messages;play;time;end;track;video;empty;open;window;setMessages(e){this.messages=e,this.render()}show(e,t){this.json=e,this.base=t,this.render()}get now(){return this.media?Math.max(0,this.media.currentTime-(this.recordings[0]?.offset??0)):this.position}seek(e){this.position=Math.min(this.duration,Math.max(0,e)),this.media&&(this.media.currentTime=this.position+(this.recordings[0]?.offset??0)),this.placeHead(),this.host.timeChanged(this.position)}position=0;get recordings(){return Array.isArray(this.json?.recordings)?this.json.recordings:[]}get duration(){const e=this.media&&Number.isFinite(this.media.duration)?this.media.duration-(this.recordings[0]?.offset??0):0,t=Math.max(0,...(this.json?.segments??[]).map(r=>r.end??r.start??0));return Math.max(1,e,t)}render(){const e=this.messages,t=this.recordings;this.open.innerHTML='<svg class="icon" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M6 12V3.5l7-1.5V10.5"/><circle cx="4.5" cy="12" r="1.5"/><circle cx="11.5" cy="10.5" r="1.5"/></svg>',this.open.append(e.recordings.replace("{n}",String(t.length))),this.empty.textContent=t.length===0?e.noRecording:"",this.empty.hidden=t.length>0,this.play.title=e.play,this.play.setAttribute("aria-label",e.play),this.track.setAttribute("aria-label",e.recordingsTitle),this.ensureMedia(),this.drawTrack(),this.window.open&&this.drawRows()}ensureMedia(){const e=this.recordings[0],t=e?`${e.kind}|${e.src}`:"";if(t===this.mediaFor||(this.mediaFor=t,this.media?.pause(),this.media=void 0,this.video.replaceChildren(),this.video.hidden=!0,this.play.disabled=!0,!e||e.src===""))return;const r=document.createElement(e.kind==="video"?"video":"audio");r.preload="metadata",r.src=this.urlOf(e.src),r.addEventListener("loadedmetadata",()=>this.drawTrack()),r.addEventListener("timeupdate",()=>{this.placeHead(),this.host.timeChanged(this.now)});const s=()=>{const i=!r.paused;this.play.innerHTML=i?y.PAUSE:y.PLAY;const a=i?this.messages.pause:this.messages.play;this.play.title=a,this.play.setAttribute("aria-label",a)};r.addEventListener("play",s),r.addEventListener("pause",s),e.kind==="video"&&(this.video.append(r),this.video.hidden=!1),this.media=r,this.play.disabled=!1}urlOf(e){const t=this.picked.get(e);if(t)return t;try{return new URL(e,this.base??location.href).href}catch{return e}}drawTrack(){const e=this.json,t=this.duration,r=(e?.segments??[]).filter(i=>i.start!==void 0);this.track.replaceChildren(...r.map(i=>{const a=document.createElement("span"),n=e?.speakers?.[i.speaker]?.role;a.className=i.redacted?"seg redacted":"seg",a.dataset.role=n==="witness"||n==="interviewer"?n:"other";const l=i.start??0,c=i.end??l+1;return a.style.left=`${l/t*100}%`,a.style.width=`${Math.max(.3,(c-l)/t*100)}%`,a}));const s=document.createElement("span");s.className="head",this.track.append(s),this.track.setAttribute("aria-valuemax",String(Math.round(t))),this.end.textContent=g.clock(t),this.placeHead()}placeHead(){const e=this.now,t=this.track.querySelector(".head");t&&(t.style.left=`${Math.min(e,this.duration)/this.duration*100}%`),this.time.textContent=g.clock(Math.floor(e)),this.track.setAttribute("aria-valuenow",String(Math.round(e)))}showWindow(){this.drawRows(),typeof this.window.showModal=="function"?this.window.showModal():this.window.setAttribute("open","")}drawRows(){const e=this.messages;this.window.querySelector("h3").textContent=e.recordingsTitle,this.window.querySelector(".recording-add span").textContent=e.recordingAdd,O.dialog(this.window,e.close,()=>this.window.close()),this.window.querySelector(".recording-rows").replaceChildren(...this.recordings.map((r,s)=>{const i=document.createElement("div");i.className="recording-row";const a=document.createElement("select");a.append(new Option(e.recordingAudio,"audio"),new Option(e.recordingVideo,"video")),a.value=r.kind,a.addEventListener("change",()=>this.edit(p=>{p[s].kind=a.value}));const n=document.createElement("input");n.type="text",n.value=r.src,n.placeholder="interview.mp3",n.addEventListener("change",()=>this.edit(p=>{p[s].src=n.value.trim()}));const l=document.createElement("input");l.type="file",l.accept="audio/*,video/*",l.hidden=!0,l.addEventListener("change",()=>{const p=l.files?.[0];p&&(this.picked.set(p.name,URL.createObjectURL(p)),this.edit(b=>{b[s].src=p.name,p.type.startsWith("video/")&&(b[s].kind="video")}))});const c=document.createElement("button");c.type="button",c.className="icon-btn",c.title=e.recordingFile,c.setAttribute("aria-label",e.recordingFile),c.innerHTML='<svg class="icon" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 4.5h4l1.5 1.5h5.5v6.5h-11z"/></svg>',c.addEventListener("click",()=>l.click());const o=document.createElement("span");o.className="recording-file",o.append(n,c,l);const d=document.createElement("input");d.type="number",d.step="0.1",d.value=r.offset!==void 0?String(r.offset):"",d.placeholder="0",d.addEventListener("change",()=>this.edit(p=>{const b=Number(d.value);d.value.trim()===""||!Number.isFinite(b)||b===0?delete p[s].offset:p[s].offset=b}));const h=document.createElement("button");h.type="button",h.className="icon-btn",h.innerHTML=y.TRASH,h.title=e.recordingRemove,h.setAttribute("aria-label",e.recordingRemove),h.addEventListener("click",()=>this.edit(p=>{p.splice(s,1)}));const u=document.createElement("p");return u.className="recording-status",this.check(r,u),i.append(this.labelled(e.recordingKind,a),this.labelled(e.recordingFile,o),this.labelled(e.recordingOffset,d),h,u),i}))}labelled(e,t){const r=document.createElement("label"),s=document.createElement("span");return s.textContent=e,r.append(s,t),r}check(e,t){if(e.src==="")return;const r=document.createElement(e.kind==="video"?"video":"audio");r.preload="metadata",r.addEventListener("loadedmetadata",()=>{t.textContent=g.clock(Math.round(r.duration)),t.classList.remove("error")}),r.addEventListener("error",()=>{t.textContent=this.messages.loadFailed,t.classList.add("error")}),r.src=this.urlOf(e.src)}edit(e){const t=structuredClone(this.recordings);e(t),this.host.recordingsChanged(t)}}const $="rr0-interview-editor",K=`
63
63
  <style>
64
64
  :host { display: block; }
65
65
  /* The colours of the transcript's text editor (see SightingFileEditor): a key, a string, a number or a word, and a mistake. Dark mode redefines them. */
@@ -225,5 +225,5 @@ rr0-interview .turn .ref svg { width: 1.1em; height: 1.1em; }
225
225
  <div id="file-editor" class="file-editor"></div>
226
226
  </section>
227
227
  </div>
228
- `;class m extends HTMLElement{shadow=this.attachShadow({mode:"open"});get importInput(){return this.shadow.getElementById("import")}messages=S;said=new L(["en"]);language="en";localeToken=0;current;options=[];group="summary";base;text;fileEditor;fileEditorLoading;shownFor;player;drawn;takenOut=new Map;constructor(){super(),this.shadow.innerHTML=K,customElements.upgrade(this.shadow),this.importInput.build();for(const e of["title","time","by","url","summary","witness","medium"])this.shadow.getElementById(e).addEventListener(e==="witness"||e==="medium"?"change":"input",()=>this.write(e));for(const e of this.shadow.querySelectorAll("#tabs .group-tab"))e.addEventListener("click",()=>this.openGroup(m.GROUPS[e.getAttribute("aria-controls")]??"summary"));this.shadow.getElementById("url").addEventListener("change",()=>{this.shownFor=void 0,this.text===void 0&&this.group==="file"&&this.showTranscript()}),this.importInput.addEventListener("filepick",e=>{const t=e.detail;t.file?this.importFromFile(t.file):this.importFromUrl(t.url)}),this.shadow.getElementById("save").addEventListener("click",()=>this.save()),this.player=new B(this.shadow.getElementById("player"),{timeChanged:e=>this.markCurrentTurn(e),recordingsChanged:e=>void this.editTranscript(t=>{e.length===0?delete t.recordings:t.recordings=e})},this.messages),this.shadow.getElementById("redact-selection").addEventListener("click",()=>void this.redactSelection()),this.shadow.getElementById("exchanges").addEventListener("click",e=>{const t=e.target.closest(".bar[data-restorable]");t&&this.restore(t.dataset.restorable)})}static GROUPS={"group-summary":"summary","group-exchanges":"exchanges","group-transcript":"file"};static get observedAttributes(){return["readonly"]}attributeChangedCallback(){this.applyReadonly()}connectedCallback(){this.loadLocale()}disconnectedCallback(){queueMicrotask(()=>{this.isConnected||this.releaseFileEditor()})}releaseFileEditor(){this.fileEditor&&(this.text=this.fileEditor.text,this.fileEditor.destroy(),this.fileEditor=void 0,this.fileEditorLoading=void 0,this.shownFor=void 0)}get event(){return this.current}set event(e){e!==this.current&&(this.shownFor=void 0),this.current=e,this.render(),this.takenOut.clear(),this.group==="file"&&this.showTranscript(),queueMicrotask(()=>{this.current===e&&this.showExchanges()})}set witnesses(e){this.options=e,this.render()}set documentUrl(e){this.base=e}get transcriptText(){return this.text}set transcriptText(e){this.text=e,this.shownFor=void 0}field(e){return this.shadow.getElementById(e)}async loadLocale(){const e=++this.localeToken,t=j.preferencesFor(this),r=P(t,H),s=r==="en"?S:await I(r);e===this.localeToken&&(this.language=r,this.said=new L(t),this.messages=s,this.render())}applyReadonly(){const e=this.hasAttribute("readonly");for(const t of["title","time","by","url","summary"])this.field(t).readOnly=e;for(const t of this.shadow.querySelectorAll("#witness input"))t.disabled=e;this.field("medium").disabled=e,this.importInput.disabled=e}static byText(e){return(e??[]).map(t=>"people"in t?t.people:"org"in t?t.org:t.title??"").filter(t=>t!=="").join(", ")}render(){const e=this.messages,t=(o,d)=>{this.shadow.getElementById(o).textContent=d};t("label-summary-group",e.summaryTab),t("label-file-group",e.fileTab),t("label-exchanges-group",e.exchangesTab),t("label-title",e.title),t("label-witness",e.witness),t("label-medium",e.medium),t("label-time",e.time),t("label-by",e.by),t("label-summary",e.summary),t("label-url",e.transcript),t("label-import-file",e.importFile),t("redact-selection",e.redactSelection),this.player?.setMessages(e),this.importInput.messages=e,t("save",e.saveButton),this.field("url").title=e.transcriptHint,this.field("by").title=e.byHint;const r=this.current,s=this.shadow.activeElement,i=(o,d)=>{this.field(o)!==s&&(this.field(o).value=d)};i("title",this.said.read(r?.title)??""),i("time",r?.time??""),i("by",m.byText(r?.by)),i("url",r?.url??""),i("summary",this.said.read(r?.summary)??"");const a=[r?.interviewOf].flat().filter(o=>typeof o=="string"),n=this.shadow.getElementById("witness"),l=[...this.options,...a.filter(o=>!this.options.some(d=>d.id===o)).map(o=>({id:o,label:o}))];n.replaceChildren(n.querySelector("legend"),...l.map(o=>{const d=document.createElement("label"),h=document.createElement("input");return h.type="checkbox",h.value=o.id,h.checked=a.includes(o.id),d.append(h,o.label),d}));const c=this.shadow.getElementById("medium");if(c.replaceChildren(...N.ALL.map(o=>new Option(N.label(o,this.language),o))),r?.medium!==void 0&&!N.ALL.includes(r.medium)&&c.append(new Option(String(r.medium),String(r.medium))),r?.medium===void 0){const o=new Option(e.witnessNone,"");o.disabled=!0,o.hidden=!0,c.prepend(o)}c.value=r?.medium??"",this.applyReadonly()}write(e){const t=this.current;if(!t)return;const r=e==="witness"?"":this.field(e).value;switch(e){case"title":{const s=this.said.write(t.title,r,this.language);s===void 0?delete t.title:t.title=s;break}case"summary":{const s=this.said.write(t.summary,r,this.language);s===void 0?delete t.summary:t.summary=s;break}case"time":r.trim()===""?delete t.time:t.time=r.trim();break;case"url":r.trim()===""?delete t.url:t.url=r.trim();break;case"by":{const s=r.split(",").map(i=>i.trim()).filter(i=>i!=="");s.length===0?delete t.by:t.by=s.map(i=>({people:i}));break}case"witness":{const s=[...this.shadow.querySelectorAll("#witness input:checked")].map(i=>i.value);if(s.length===0){this.render();return}t.interviewOf=s.length===1?s[0]:s;break}case"medium":r===""?delete t.medium:t.medium=r;break}this.dispatchEvent(new CustomEvent(E,{bubbles:!0,composed:!0,detail:{field:e,event:t}})),e==="medium"&&this.render(),e!=="url"&&this.syncFileFromEvent()}openGroup(e){this.group=e;const t={summary:"group-summary",exchanges:"group-exchanges",file:"group-transcript"};for(const r of this.shadow.querySelectorAll("#tabs .group-tab"))r.setAttribute("aria-expanded",String(r.getAttribute("aria-controls")===t[e]));for(const[r,s]of Object.entries(t))this.shadow.getElementById(s).hidden=r!==e;e==="file"&&this.showTranscript(),e==="exchanges"&&this.showExchanges()}exchangesToken=0;async transcriptJson(e){try{const t=this.fileEditor?.text??this.text;if(t!==void 0)return JSON.parse(t);const r=this.transcriptUrl(e);if(r!==void 0)return await T.json(r)}catch{}}async showExchanges(){const e=this.current;if(!e)return;const t=++this.exchangesToken,r=await this.transcriptJson(e);t!==this.exchangesToken||this.current!==e||this.drawExchanges(r)}drawExchanges(e){const t=this.shadow.getElementById("exchanges");t.documentUrl=this.current?this.transcriptUrl(this.current):void 0,t.interview=e,this.drawn=e,this.player.show(e,this.current?this.transcriptUrl(this.current):void 0),this.renderReview(e),this.decorateExchanges(e)}async editTranscript(e){const t=this.current;if(!t||this.hasAttribute("readonly"))return;const r=await this.transcriptJson(t)??JSON.parse(this.template(t));Array.isArray(r.segments)||(r.segments=[]),e(r);const s=m.pretty(m.withEventFields(r,t));this.fileEditor?.replaceText(s,!1),this.onEdited(s)}get redactReason(){return this.said.write(void 0,this.messages.redactReason,this.language)??this.messages.redactReason}speakerLabel(e,t){const r=b.speakerName(e,t),s=e.speakers?.[t]?.role;return r===s&&s in this.messages.roles?this.messages.roles[s]:r}renderReview(e){const t=this.shadow.getElementById("review"),r=Object.keys(e?.speakers??{});if(t.hidden=!e||r.length===0,!e||r.length===0)return;const s=l=>["approved","covered"].includes(e.proofreading?.[l]?.status??""),i=r.filter(l=>!s(l)),a=document.createElement("span");a.textContent=i.length===0?this.messages.proofreadAll:this.messages.proofreadPending.replace("{names}",i.map(l=>this.speakerLabel(e,l)).join(", ")),t.toggleAttribute("data-done",i.length===0);const n=document.createElement("span");n.className="review-boxes";for(const l of r){const c=document.createElement("label"),o=document.createElement("input");o.type="checkbox",o.checked=s(l),o.disabled=this.hasAttribute("readonly"),o.addEventListener("change",()=>void this.editTranscript(d=>{d.proofreading??={},o.checked?d.proofreading[l]={status:"approved",date:new Date().toISOString().slice(0,10)}:d.proofreading[l]={status:"pending"}})),c.append(o,this.messages.reviewedBy.replace("{name}",this.speakerLabel(e,l))),n.append(c)}t.replaceChildren(a,n)}momentsOf(e,t){const r=e.speakers?.[t]?.of??b.sightingsOf(e)[0];return this.options.find(s=>s.id===r)?.moments??this.options.flatMap(s=>s.moments??[])}decorateExchanges(e){if(!e)return;const t=this.messages,r=this.hasAttribute("readonly");for(const s of this.shadow.querySelectorAll("#exchanges .turn[data-segment]")){const i=Number(s.dataset.segment),a=e.segments[i];if(!a)continue;if(a.start!==void 0){const c=document.createElement("button");c.type="button",c.className="turn-time",c.textContent=b.clock(a.start),c.addEventListener("click",()=>this.player.seek(a.start)),s.prepend(c)}const n=a.redacted?[a.redacted]:f.partsOf(a).flatMap(c=>"redacted"in c?[c.redacted]:[]);s.querySelectorAll(".bar").forEach((c,o)=>{const d=n[o]?.words;c.textContent=d!==void 0?t.redactedWords.replace("{n}",String(d)):t.redacted,c.classList.add("redacted-label");const h=a.redacted?`${i}:all`:`${i}:${o}`;this.takenOut.has(h)&&(c.dataset.restorable=h)});const l=this.momentsOf(e,a.speaker);s.querySelectorAll(".ref").forEach((c,o)=>{const d=a.refs?.[o];if(!d)return;const h=l.find(p=>Math.abs(p.at-d.at)<.05);if(h){const p=document.createElement("span");p.className="moment-letter",p.textContent=h.label.slice(0,2),c.replaceChildren(p,t.momentLabel.replace("{k}",h.label).replace("{text}",h.note??b.clock(d.at)))}if(r)return;const u=document.createElement("button");u.type="button",u.className="ref-unlink",u.textContent="×",u.title=t.unlinkMoment,u.setAttribute("aria-label",t.unlinkMoment),u.addEventListener("click",()=>void this.editTranscript(p=>{p.segments[i].refs?.splice(o,1),p.segments[i].refs?.length===0&&delete p.segments[i].refs})),c.after(u)}),r||s.append(this.turnTools(e,i,a,l))}}turnTools(e,t,r,s){const i=this.messages,a=document.createElement("div");a.className="turn-tools";const n=(o,d)=>{const h=document.createElement("button");return h.type="button",h.textContent=o,h.addEventListener("click",d),h};if(e.speakers?.[r.speaker]?.role==="witness"&&!r.redacted){const o=n(i.linkMoment,()=>{});a.append(o),new U(o,`moment-menu-${t}`,h=>void this.editTranscript(u=>{const p=u.segments[t].refs??[];p.some(g=>Math.abs(g.at-Number(h))<.05)||p.push({at:Number(h)}),u.segments[t].refs=p}),h=>`moment-${t}-${h}`).setKinds(s.length>0?s.map(h=>({value:String(h.at),label:i.momentLabel.replace("{k}",h.label).replace("{text}",h.note??b.clock(h.at))})):[{value:"",label:i.noMoments,disabledReason:i.noMoments}])}r.redacted||a.append(n(i.editTurn,()=>this.editTurn(t)));const l=this.takenOut.has(`${t}:all`)||[...this.takenOut.keys()].some(o=>o.startsWith(`${t}:`));if(r.redacted||r.parts&&l){const o=n(i.restoreAll,()=>void this.restoreAll(t));o.disabled=!l,a.append(o)}else a.append(n(i.redactAll,()=>void this.editTranscript(o=>{this.forget(t),this.takenOut.set(`${t}:all`,f.redactWhole(o.segments[t],this.redactReason))})));const c=n("",()=>void this.editTranscript(o=>{o.segments.splice(t,1),this.shiftTakenOut(t)}));return c.className="icon-btn",c.innerHTML=y.TRASH,c.title=i.deleteTurn,c.setAttribute("aria-label",i.deleteTurn),a.append(c),a}async redactSelection(){const e=this.messages,t=this.shadow.getElementById("redact-note"),r=(h,u=!1)=>{t.textContent=h,t.classList.toggle("error",u)},s=this.shadow.getSelection?.()??document.getSelection(),i=s&&s.rangeCount>0?s.getRangeAt(0):void 0;if(!i||i.collapsed){r(e.selectionNone);return}const a=h=>h instanceof Element?h:h.parentElement,n=a(i.startContainer)?.closest(".said");if(!n||a(i.endContainer)?.closest(".said")!==n||a(i.startContainer)?.closest(".bar")||a(i.endContainer)?.closest(".bar")){r(e.selectionOnePassage,!0);return}const l=Number(n.closest(".turn").dataset.segment),c=m.offsetIn(n,i.startContainer,i.startOffset),o=m.offsetIn(n,i.endContainer,i.endOffset);let d=!1;await this.editTranscript(h=>{const u=f.redact(h.segments[l],Math.min(c,o),Math.max(c,o),this.redactReason);u&&(d=!0,this.shiftParts(l,u.nth),this.takenOut.set(`${l}:${u.nth}`,u.text))}),s?.removeAllRanges(),r(d?"":e.selectionOnePassage,!d)}static offsetIn(e,t,r){let s=0;const i=document.createTreeWalker(e,NodeFilter.SHOW_TEXT,{acceptNode:a=>a.parentElement?.closest(".bar")?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT});for(let a=i.nextNode();a;a=i.nextNode()){if(a===t)return s+r;if(t.contains(a)&&t!==a){const l=[...t.childNodes].slice(0,r);return s+l.reduce((c,o)=>c+(o instanceof Element&&o.closest(".bar")?0:(o.textContent??"").length),0)}s+=(a.textContent??"").length}return s}async restore(e){const t=this.takenOut.get(e);if(t===void 0)return;const[r,s]=e.split(":"),i=Number(r);await this.editTranscript(a=>{s==="all"?f.restoreWhole(a.segments[i],t):(f.restorePart(a.segments[i],Number(s),t),this.shiftParts(i,Number(s),-1)),this.takenOut.delete(e)})}async restoreAll(e){if(this.takenOut.has(`${e}:all`)){await this.restore(`${e}:all`);return}const t=[...this.takenOut.keys()].filter(r=>r.startsWith(`${e}:`)).map(r=>Number(r.split(":")[1])).sort((r,s)=>s-r);for(const r of t)await this.restore(`${e}:${r}`)}editTurn(e){const r=this.drawn?.segments[e],s=this.shadow.querySelector(`#exchanges .turn[data-segment="${e}"] .said`);if(!r||!s)return;const i=this.messages,a=document.createElement("div");a.className="turn-edit";const n=document.createElement("textarea");n.value=f.raw(r,h=>this.takenOut.get(`${e}:${h}`));const l=document.createElement("span");l.className="hint",l.textContent=i.turnEditHint;const c=document.createElement("div");c.className="buttons";const o=document.createElement("button");o.type="button",o.className="pill-btn primary",o.textContent=i.saveTurn,o.addEventListener("click",()=>void this.editTranscript(h=>{const u=f.fromRaw(h.segments[e],n.value,this.redactReason);this.forget(e);for(const[p,g]of u)this.takenOut.set(`${e}:${p}`,g)}));const d=document.createElement("button");d.type="button",d.className="pill-btn",d.textContent=i.cancelTurn,d.addEventListener("click",()=>this.drawExchanges(this.drawn)),c.append(o,d),a.append(n,l,c),s.replaceWith(a),n.focus()}forget(e){for(const t of[...this.takenOut.keys()])t.startsWith(`${e}:`)&&this.takenOut.delete(t)}shiftParts(e,t,r=1){const s=[...this.takenOut].filter(([i])=>i.startsWith(`${e}:`)&&i!==`${e}:all`&&Number(i.split(":")[1])>=t+(r<0?1:0));for(const[i]of s)this.takenOut.delete(i);for(const[i,a]of s)this.takenOut.set(`${e}:${Number(i.split(":")[1])+r}`,a)}shiftTakenOut(e){const t=[...this.takenOut];this.takenOut.clear();for(const[r,s]of t){const[i,a]=r.split(":"),n=Number(i);n<e?this.takenOut.set(r,s):n>e&&this.takenOut.set(`${n-1}:${a}`,s)}}markCurrentTurn(e){const t=this.drawn?.segments??[];let r=-1;t.forEach((a,n)=>{a.start!==void 0&&a.start<=e&&(r=n)});for(const a of this.shadow.querySelectorAll("#exchanges .turn.current"))a.classList.remove("current");const s=this.shadow.querySelector(`#exchanges .turn[data-segment="${r}"]`);if(!s)return;s.classList.add("current");const i=s.closest(".rr0-interview-turns");i&&(s.offsetTop<i.scrollTop||s.offsetTop>i.scrollTop+i.clientHeight-s.offsetHeight)&&i.scrollTo({top:s.offsetTop-i.clientHeight/3,behavior:"smooth"})}async revealSegment(e){this.openGroup("exchanges"),await this.showExchanges(),this.shadow.getElementById("exchanges").revealSegment(e)}note(e,t="info"){q.show(this.shadow.getElementById("file-note"),e,t)}transcriptUrl(e){return e.url?new URL(e.url,this.base??location.href).href:void 0}static pretty(e){return JSON.stringify(e,null,2)+`
229
- `}static SHARED=["title","time","medium","by","interviewOf","summary"];static ORDER=["version","id","language","title","time","medium","by","interviewOf","summary","speakers","transcription","proofreading","anonymization","recordings","segments"];static ordered(e){const t={};for(const r of m.ORDER)r in e&&(t[r]=e[r]);for(const r of Object.keys(e))r in t||(t[r]=e[r]);return t}static witnessesOf(e){return[e].flat().filter(t=>typeof t=="string")}static withEventFields(e,t){const r={...e};for(const i of m.SHARED)t[i]===void 0?delete r[i]:r[i]=structuredClone(t[i]);const s=r.speakers;if(typeof s=="object"&&s!==null){const i={...s},a=(n,l,c)=>{c.forEach((o,d)=>{const h=d===0?n:`${n}${d+1}`;i[h]?i[h]={...i[h],[l]:o}:d>0&&(i[h]={role:n,[l]:o})})};a("interviewer","by",t.by??[]),a("witness","of",m.witnessesOf(t.interviewOf)),r.speakers=i}return m.ordered(r)}applyFileToEvent(e,t){const r=this.current;if(!r)return!1;const s=JSON.stringify(m.SHARED.map(n=>r[n])),i=n=>typeof n=="string"||typeof n=="object"&&n!==null&&!Array.isArray(n),a={title:i,summary:i,time:n=>typeof n=="string",medium:n=>typeof n=="string",by:n=>Array.isArray(n),interviewOf:n=>typeof n=="string"||Array.isArray(n)};for(const n of m.SHARED){const l=e[n];l!==void 0&&a[n](l)?r[n]=structuredClone(l):t&&delete r[n]}return JSON.stringify(m.SHARED.map(n=>r[n]))!==s}syncFileFromEvent(){const e=this.current,t=this.fileEditor?.text??this.text;if(!e||t===void 0)return;let r;try{r=JSON.parse(t)}catch{return}if(typeof r!="object"||r===null||Array.isArray(r))return;const s=m.pretty(m.withEventFields(r,e));s!==t&&(this.text=s,this.fileEditor?.replaceText(s,!1),this.dispatchEvent(new CustomEvent(E,{bubbles:!0,composed:!0,detail:{field:"transcript",event:e,text:s}})))}template(e){return m.pretty(m.withEventFields({version:1,language:this.language,speakers:{interviewer:{role:"interviewer"},witness:{role:"witness"}},segments:[]},e))}loadFileEditor(e){return this.fileEditorLoading??=(async()=>{try{const[{SightingFileEditor:t},{default:r},s]=await Promise.all([A(()=>import("./SightingFileEditor-LAzFduCJ.js"),__vite__mapDeps([0,1]),import.meta.url),A(()=>import("./interviewSchema-7rMvPyAl.js"),[],import.meta.url),V(this.language)]);return this.fileEditor=new t(this.shadow.getElementById("file-editor"),e,i=>this.onEdited(i),this.messages.editorLabel,{baseline:e,phrases:s.fileSearchPhrases,schema:r}),this.fileEditor}catch(t){throw this.fileEditorLoading=void 0,t}})(),this.fileEditorLoading}async showTranscript(){const e=this.current;if(!e||this.shownFor===e)return;this.note(this.messages.loading);let t=this.text;if(t===void 0){const r=this.transcriptUrl(e);if(r!==void 0)try{const s=await T.json(r);this.current===e&&typeof s=="object"&&s!==null&&!Array.isArray(s)?(this.applyFileToEvent(s,!1)&&this.fileChangedEvent(),t=m.pretty(m.withEventFields(s,e))):t=m.pretty(s)}catch{this.note(this.messages.loadFailed,"error")}t??=this.template(e)}if(this.current===e)try{const r=await this.loadFileEditor(t);if(this.current!==e)return;r.replaceText(t,!1),this.shownFor=e,this.text!==void 0||this.transcriptUrl(e)===void 0?this.note(void 0):this.shadow.getElementById("file-note").textContent===this.messages.loading&&this.note(void 0)}catch{this.note(this.messages.loadFailed,"error")}}onEdited(e){this.text=e;let t,r=!0;try{t=JSON.parse(e)}catch{r=!1}this.note(r?void 0:this.messages.invalid,"error");const s=this.current;if(r&&s&&typeof t=="object"&&t!==null&&!Array.isArray(t)&&this.applyFileToEvent(t,!0)&&this.fileChangedEvent(),s&&!s.url&&e.trim()!==""){const i=s.id??"1";s.url=`${i.startsWith("interview")?i:`interview-${i}`}.json`,this.field("url").value=s.url,this.dispatchEvent(new CustomEvent(E,{bubbles:!0,composed:!0,detail:{field:"url",event:s}}))}this.dispatchEvent(new CustomEvent(E,{bubbles:!0,composed:!0,detail:{field:"transcript",event:s,text:e}})),this.showExchanges()}fileChangedEvent(){this.render(),this.dispatchEvent(new CustomEvent(E,{bubbles:!0,composed:!0,detail:{field:"file",event:this.current}}))}async take(e){if(typeof e!="object"||e===null||!Array.isArray(e.segments)){this.note(this.messages.notATranscript,"error");return}const t=this.current;t&&this.applyFileToEvent(e,!1)&&this.fileChangedEvent();const r=m.pretty(t?m.withEventFields(e,t):e);await this.loadFileEditor(r).then(s=>s.replaceText(r,!1),()=>{}),this.shownFor=this.current,this.note(void 0),this.onEdited(r)}async importFromFile(e){try{const t=await e.text();let r;try{r=JSON.parse(t)}catch{r=void 0}if(typeof r!="object"||r===null){const{InterviewImport:s}=await A(async()=>{const{InterviewImport:l}=await import("./interviewImport-C-TiARLK.js");return{InterviewImport:l}},[],import.meta.url),i=this.current,a=[i?.interviewOf].flat().filter(l=>typeof l=="string"),n=s.parse(t,{by:(i?.by??[]).map(l=>m.byText([l])).filter(l=>l!==""),witnesses:this.options.filter(l=>a.length===0||a.includes(l.id))});r=n&&{...n,transcription:{note:`Imported from ${e.name}: who speaks and when are as the file gave them, to be checked.`}}}r===void 0?this.note(this.messages.notATranscript,"error"):await this.take(r)}catch{this.note(this.messages.loadFailed,"error")}}async importFromUrl(e){try{await this.take(await T.json(new URL(e,this.base??location.href).href))}catch{this.note(this.messages.loadFailed,"error")}}save(){const e=this.fileEditor?.text??this.text;if(e===void 0)return;const t=(this.current?.url??"").split("/").pop()||`${this.current?.id??"interview"}.json`,r=URL.createObjectURL(new Blob([e],{type:"application/json"})),s=document.createElement("a");s.href=r,s.download=t,s.click(),URL.revokeObjectURL(r)}}function te(){Y(),J(),customElements.get($)||customElements.define($,m)}export{E as INTERVIEW_CHANGE_EVENT,$ as INTERVIEW_EDITOR_ELEMENT_NAME,W as INTERVIEW_SEEK_EVENT,m as InterviewEditorElement,te as register};
228
+ `;class m extends HTMLElement{shadow=this.attachShadow({mode:"open"});get importInput(){return this.shadow.getElementById("import")}messages=S;said=new L(["en"]);language="en";localeToken=0;current;options=[];group="summary";base;text;fileEditor;fileEditorLoading;shownFor;player;drawn;takenOut=new Map;constructor(){super(),this.shadow.innerHTML=K,customElements.upgrade(this.shadow),this.importInput.build();for(const e of["title","time","by","url","summary","witness","medium"])this.shadow.getElementById(e).addEventListener(e==="witness"||e==="medium"?"change":"input",()=>this.write(e));for(const e of this.shadow.querySelectorAll("#tabs .group-tab"))e.addEventListener("click",()=>this.openGroup(m.GROUPS[e.getAttribute("aria-controls")]??"summary"));this.shadow.getElementById("url").addEventListener("change",()=>{this.shownFor=void 0,this.text===void 0&&this.group==="file"&&this.showTranscript()}),this.importInput.addEventListener("filepick",e=>{const t=e.detail;t.file?this.importFromFile(t.file):this.importFromUrl(t.url)}),this.shadow.getElementById("save").addEventListener("click",()=>this.save()),this.player=new B(this.shadow.getElementById("player"),{timeChanged:e=>this.markCurrentTurn(e),recordingsChanged:e=>void this.editTranscript(t=>{e.length===0?delete t.recordings:t.recordings=e})},this.messages),this.shadow.getElementById("redact-selection").addEventListener("click",()=>void this.redactSelection()),this.shadow.getElementById("exchanges").addEventListener("click",e=>{const t=e.target.closest(".bar[data-restorable]");t&&this.restore(t.dataset.restorable)})}static GROUPS={"group-summary":"summary","group-exchanges":"exchanges","group-transcript":"file"};static get observedAttributes(){return["readonly"]}attributeChangedCallback(){this.applyReadonly()}connectedCallback(){this.loadLocale()}disconnectedCallback(){queueMicrotask(()=>{this.isConnected||this.releaseFileEditor()})}releaseFileEditor(){this.fileEditor&&(this.text=this.fileEditor.text,this.fileEditor.destroy(),this.fileEditor=void 0,this.fileEditorLoading=void 0,this.shownFor=void 0)}get event(){return this.current}set event(e){e!==this.current&&(this.shownFor=void 0),this.current=e,this.render(),this.takenOut.clear(),this.group==="file"&&this.showTranscript(),queueMicrotask(()=>{this.current===e&&this.showExchanges()})}set witnesses(e){this.options=e,this.render()}set documentUrl(e){this.base=e}get transcriptText(){return this.text}set transcriptText(e){this.text=e,this.shownFor=void 0}field(e){return this.shadow.getElementById(e)}async loadLocale(){const e=++this.localeToken,t=j.preferencesFor(this),r=P(t,H),s=r==="en"?S:await I(r);e===this.localeToken&&(this.language=r,this.said=new L(t),this.messages=s,this.render())}applyReadonly(){const e=this.hasAttribute("readonly");for(const t of["title","time","by","url","summary"])this.field(t).readOnly=e;for(const t of this.shadow.querySelectorAll("#witness input"))t.disabled=e;this.field("medium").disabled=e,this.importInput.disabled=e}static byText(e){return(e??[]).map(t=>"people"in t?t.people:"org"in t?t.org:t.title??"").filter(t=>t!=="").join(", ")}render(){const e=this.messages,t=(o,d)=>{this.shadow.getElementById(o).textContent=d};t("label-summary-group",e.summaryTab),t("label-file-group",e.fileTab),t("label-exchanges-group",e.exchangesTab),t("label-title",e.title),t("label-witness",e.witness),t("label-medium",e.medium),t("label-time",e.time),t("label-by",e.by),t("label-summary",e.summary),t("label-url",e.transcript),t("label-import-file",e.importFile),t("redact-selection",e.redactSelection),this.player?.setMessages(e),this.importInput.messages=e,t("save",e.saveButton),this.field("url").title=e.transcriptHint,this.field("by").title=e.byHint;const r=this.current,s=this.shadow.activeElement,i=(o,d)=>{this.field(o)!==s&&(this.field(o).value=d)};i("title",this.said.read(r?.title)??""),i("time",r?.time??""),i("by",m.byText(r?.by)),i("url",r?.url??""),i("summary",this.said.read(r?.summary)??"");const a=[r?.interviewOf].flat().filter(o=>typeof o=="string"),n=this.shadow.getElementById("witness"),l=[...this.options,...a.filter(o=>!this.options.some(d=>d.id===o)).map(o=>({id:o,label:o}))];n.replaceChildren(n.querySelector("legend"),...l.map(o=>{const d=document.createElement("label"),h=document.createElement("input");return h.type="checkbox",h.value=o.id,h.checked=a.includes(o.id),d.append(h,o.label),d}));const c=this.shadow.getElementById("medium");if(c.replaceChildren(...N.ALL.map(o=>new Option(N.label(o,this.language),o))),r?.medium!==void 0&&!N.ALL.includes(r.medium)&&c.append(new Option(String(r.medium),String(r.medium))),r?.medium===void 0){const o=new Option(e.witnessNone,"");o.disabled=!0,o.hidden=!0,c.prepend(o)}c.value=r?.medium??"",this.applyReadonly()}write(e){const t=this.current;if(!t)return;const r=e==="witness"?"":this.field(e).value;switch(e){case"title":{const s=this.said.write(t.title,r,this.language);s===void 0?delete t.title:t.title=s;break}case"summary":{const s=this.said.write(t.summary,r,this.language);s===void 0?delete t.summary:t.summary=s;break}case"time":r.trim()===""?delete t.time:t.time=r.trim();break;case"url":r.trim()===""?delete t.url:t.url=r.trim();break;case"by":{const s=r.split(",").map(i=>i.trim()).filter(i=>i!=="");s.length===0?delete t.by:t.by=s.map(i=>({people:i}));break}case"witness":{const s=[...this.shadow.querySelectorAll("#witness input:checked")].map(i=>i.value);if(s.length===0){this.render();return}t.interviewOf=s.length===1?s[0]:s;break}case"medium":r===""?delete t.medium:t.medium=r;break}this.dispatchEvent(new CustomEvent(E,{bubbles:!0,composed:!0,detail:{field:e,event:t}})),e==="medium"&&this.render(),e!=="url"&&this.syncFileFromEvent()}openGroup(e){this.group=e;const t={summary:"group-summary",exchanges:"group-exchanges",file:"group-transcript"};for(const r of this.shadow.querySelectorAll("#tabs .group-tab"))r.setAttribute("aria-expanded",String(r.getAttribute("aria-controls")===t[e]));for(const[r,s]of Object.entries(t))this.shadow.getElementById(s).hidden=r!==e;e==="file"&&this.showTranscript(),e==="exchanges"&&this.showExchanges()}exchangesToken=0;async transcriptJson(e){try{const t=this.fileEditor?.text??this.text;if(t!==void 0)return g.read(JSON.parse(t));const r=this.transcriptUrl(e);if(r!==void 0)return g.read(await T.json(r))}catch{}}async showExchanges(){const e=this.current;if(!e)return;const t=++this.exchangesToken,r=await this.transcriptJson(e);t!==this.exchangesToken||this.current!==e||this.drawExchanges(r)}drawExchanges(e){const t=this.shadow.getElementById("exchanges");t.documentUrl=this.current?this.transcriptUrl(this.current):void 0,t.interview=e,this.drawn=e,this.player.show(e,this.current?this.transcriptUrl(this.current):void 0),this.renderReview(e),this.decorateExchanges(e)}async editTranscript(e){const t=this.current;if(!t||this.hasAttribute("readonly"))return;const r=await this.transcriptJson(t)??JSON.parse(this.template(t));Array.isArray(r.segments)||(r.segments=[]),e(r);const s=m.pretty(m.withEventFields(r,t));this.fileEditor?.replaceText(s,!1),this.onEdited(s)}get redactReason(){return this.said.write(void 0,this.messages.redactReason,this.language)??this.messages.redactReason}speakerLabel(e,t){const r=g.speakerName(e,t),s=e.speakers?.[t]?.role;return r===s&&s in this.messages.roles?this.messages.roles[s]:r}renderReview(e){const t=this.shadow.getElementById("review"),r=Object.keys(e?.speakers??{});if(t.hidden=!e||r.length===0,!e||r.length===0)return;const s=l=>["approved","covered"].includes(e.proofreading?.[l]?.status??""),i=r.filter(l=>!s(l)),a=document.createElement("span");a.textContent=i.length===0?this.messages.proofreadAll:this.messages.proofreadPending.replace("{names}",i.map(l=>this.speakerLabel(e,l)).join(", ")),t.toggleAttribute("data-done",i.length===0);const n=document.createElement("span");n.className="review-boxes";for(const l of r){const c=document.createElement("label"),o=document.createElement("input");o.type="checkbox",o.checked=s(l),o.disabled=this.hasAttribute("readonly"),o.addEventListener("change",()=>void this.editTranscript(d=>{d.proofreading??={},o.checked?d.proofreading[l]={status:"approved",date:new Date().toISOString().slice(0,10)}:d.proofreading[l]={status:"pending"}})),c.append(o,this.messages.reviewedBy.replace("{name}",this.speakerLabel(e,l))),n.append(c)}t.replaceChildren(a,n)}momentsOf(e,t){const r=e.speakers?.[t]?.of??g.sightingsOf(e)[0];return this.options.find(s=>s.id===r)?.moments??this.options.flatMap(s=>s.moments??[])}decorateExchanges(e){if(!e)return;const t=this.messages,r=this.hasAttribute("readonly");for(const s of this.shadow.querySelectorAll("#exchanges .turn[data-segment]")){const i=Number(s.dataset.segment),a=e.segments[i];if(!a)continue;if(a.start!==void 0){const c=document.createElement("button");c.type="button",c.className="turn-time",c.textContent=g.clock(a.start),c.addEventListener("click",()=>this.player.seek(a.start)),s.prepend(c)}const n=a.redacted?[a.redacted]:f.partsOf(a).flatMap(c=>"redacted"in c?[c.redacted]:[]);s.querySelectorAll(".bar").forEach((c,o)=>{const d=n[o]?.words;c.textContent=d!==void 0?t.redactedWords.replace("{n}",String(d)):t.redacted,c.classList.add("redacted-label");const h=a.redacted?`${i}:all`:`${i}:${o}`;this.takenOut.has(h)&&(c.dataset.restorable=h)});const l=this.momentsOf(e,a.speaker);s.querySelectorAll(".ref").forEach((c,o)=>{const d=a.refs?.[o];if(!d)return;const h=l.find(p=>Math.abs(p.at-d.at)<.05);if(h){const p=document.createElement("span");p.className="moment-letter",p.textContent=h.label.slice(0,2),c.replaceChildren(p,t.momentLabel.replace("{k}",h.label).replace("{text}",h.note??g.clock(d.at)))}if(r)return;const u=document.createElement("button");u.type="button",u.className="ref-unlink",u.textContent="×",u.title=t.unlinkMoment,u.setAttribute("aria-label",t.unlinkMoment),u.addEventListener("click",()=>void this.editTranscript(p=>{p.segments[i].refs?.splice(o,1),p.segments[i].refs?.length===0&&delete p.segments[i].refs})),c.after(u)}),r||s.append(this.turnTools(e,i,a,l))}}turnTools(e,t,r,s){const i=this.messages,a=document.createElement("div");a.className="turn-tools";const n=(o,d)=>{const h=document.createElement("button");return h.type="button",h.textContent=o,h.addEventListener("click",d),h};if(e.speakers?.[r.speaker]?.role==="witness"&&!r.redacted){const o=n(i.linkMoment,()=>{});a.append(o),new U(o,`moment-menu-${t}`,h=>void this.editTranscript(u=>{const p=u.segments[t].refs??[];p.some(b=>Math.abs(b.at-Number(h))<.05)||p.push({at:Number(h)}),u.segments[t].refs=p}),h=>`moment-${t}-${h}`).setKinds(s.length>0?s.map(h=>({value:String(h.at),label:i.momentLabel.replace("{k}",h.label).replace("{text}",h.note??g.clock(h.at))})):[{value:"",label:i.noMoments,disabledReason:i.noMoments}])}r.redacted||a.append(n(i.editTurn,()=>this.editTurn(t)));const l=this.takenOut.has(`${t}:all`)||[...this.takenOut.keys()].some(o=>o.startsWith(`${t}:`));if(r.redacted||r.parts&&l){const o=n(i.restoreAll,()=>void this.restoreAll(t));o.disabled=!l,a.append(o)}else a.append(n(i.redactAll,()=>void this.editTranscript(o=>{this.forget(t),this.takenOut.set(`${t}:all`,f.redactWhole(o.segments[t],this.redactReason))})));const c=n("",()=>void this.editTranscript(o=>{o.segments.splice(t,1),this.shiftTakenOut(t)}));return c.className="icon-btn",c.innerHTML=y.TRASH,c.title=i.deleteTurn,c.setAttribute("aria-label",i.deleteTurn),a.append(c),a}async redactSelection(){const e=this.messages,t=this.shadow.getElementById("redact-note"),r=(h,u=!1)=>{t.textContent=h,t.classList.toggle("error",u)},s=this.shadow.getSelection?.()??document.getSelection(),i=s&&s.rangeCount>0?s.getRangeAt(0):void 0;if(!i||i.collapsed){r(e.selectionNone);return}const a=h=>h instanceof Element?h:h.parentElement,n=a(i.startContainer)?.closest(".said");if(!n||a(i.endContainer)?.closest(".said")!==n||a(i.startContainer)?.closest(".bar")||a(i.endContainer)?.closest(".bar")){r(e.selectionOnePassage,!0);return}const l=Number(n.closest(".turn").dataset.segment),c=m.offsetIn(n,i.startContainer,i.startOffset),o=m.offsetIn(n,i.endContainer,i.endOffset);let d=!1;await this.editTranscript(h=>{const u=f.redact(h.segments[l],Math.min(c,o),Math.max(c,o),this.redactReason);u&&(d=!0,this.shiftParts(l,u.nth),this.takenOut.set(`${l}:${u.nth}`,u.text))}),s?.removeAllRanges(),r(d?"":e.selectionOnePassage,!d)}static offsetIn(e,t,r){let s=0;const i=document.createTreeWalker(e,NodeFilter.SHOW_TEXT,{acceptNode:a=>a.parentElement?.closest(".bar")?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT});for(let a=i.nextNode();a;a=i.nextNode()){if(a===t)return s+r;if(t.contains(a)&&t!==a){const l=[...t.childNodes].slice(0,r);return s+l.reduce((c,o)=>c+(o instanceof Element&&o.closest(".bar")?0:(o.textContent??"").length),0)}s+=(a.textContent??"").length}return s}async restore(e){const t=this.takenOut.get(e);if(t===void 0)return;const[r,s]=e.split(":"),i=Number(r);await this.editTranscript(a=>{s==="all"?f.restoreWhole(a.segments[i],t):(f.restorePart(a.segments[i],Number(s),t),this.shiftParts(i,Number(s),-1)),this.takenOut.delete(e)})}async restoreAll(e){if(this.takenOut.has(`${e}:all`)){await this.restore(`${e}:all`);return}const t=[...this.takenOut.keys()].filter(r=>r.startsWith(`${e}:`)).map(r=>Number(r.split(":")[1])).sort((r,s)=>s-r);for(const r of t)await this.restore(`${e}:${r}`)}editTurn(e){const r=this.drawn?.segments[e],s=this.shadow.querySelector(`#exchanges .turn[data-segment="${e}"] .said`);if(!r||!s)return;const i=this.messages,a=document.createElement("div");a.className="turn-edit";const n=document.createElement("textarea");n.value=f.raw(r,h=>this.takenOut.get(`${e}:${h}`));const l=document.createElement("span");l.className="hint",l.textContent=i.turnEditHint;const c=document.createElement("div");c.className="buttons";const o=document.createElement("button");o.type="button",o.className="pill-btn primary",o.textContent=i.saveTurn,o.addEventListener("click",()=>void this.editTranscript(h=>{const u=f.fromRaw(h.segments[e],n.value,this.redactReason);this.forget(e);for(const[p,b]of u)this.takenOut.set(`${e}:${p}`,b)}));const d=document.createElement("button");d.type="button",d.className="pill-btn",d.textContent=i.cancelTurn,d.addEventListener("click",()=>this.drawExchanges(this.drawn)),c.append(o,d),a.append(n,l,c),s.replaceWith(a),n.focus()}forget(e){for(const t of[...this.takenOut.keys()])t.startsWith(`${e}:`)&&this.takenOut.delete(t)}shiftParts(e,t,r=1){const s=[...this.takenOut].filter(([i])=>i.startsWith(`${e}:`)&&i!==`${e}:all`&&Number(i.split(":")[1])>=t+(r<0?1:0));for(const[i]of s)this.takenOut.delete(i);for(const[i,a]of s)this.takenOut.set(`${e}:${Number(i.split(":")[1])+r}`,a)}shiftTakenOut(e){const t=[...this.takenOut];this.takenOut.clear();for(const[r,s]of t){const[i,a]=r.split(":"),n=Number(i);n<e?this.takenOut.set(r,s):n>e&&this.takenOut.set(`${n-1}:${a}`,s)}}markCurrentTurn(e){const t=this.drawn?.segments??[];let r=-1;t.forEach((a,n)=>{a.start!==void 0&&a.start<=e&&(r=n)});for(const a of this.shadow.querySelectorAll("#exchanges .turn.current"))a.classList.remove("current");const s=this.shadow.querySelector(`#exchanges .turn[data-segment="${r}"]`);if(!s)return;s.classList.add("current");const i=s.closest(".rr0-interview-turns");i&&(s.offsetTop<i.scrollTop||s.offsetTop>i.scrollTop+i.clientHeight-s.offsetHeight)&&i.scrollTo({top:s.offsetTop-i.clientHeight/3,behavior:"smooth"})}async revealSegment(e){this.openGroup("exchanges"),await this.showExchanges(),this.shadow.getElementById("exchanges").revealSegment(e)}note(e,t="info"){q.show(this.shadow.getElementById("file-note"),e,t)}transcriptUrl(e){return e.url?new URL(e.url,this.base??location.href).href:void 0}static pretty(e){return JSON.stringify(e,null,2)+`
229
+ `}static SHARED=["title","time","medium","by","interviewOf","summary"];static ORDER=["version","id","language","title","time","medium","by","interviewOf","summary","speakers","transcription","proofreading","anonymization","recordings","segments"];static ordered(e){const t={};for(const r of m.ORDER)r in e&&(t[r]=e[r]);for(const r of Object.keys(e))r in t||(t[r]=e[r]);return t}static witnessesOf(e){return[e].flat().filter(t=>typeof t=="string")}static withEventFields(e,t){const r={...e};for(const i of m.SHARED)t[i]===void 0?delete r[i]:r[i]=structuredClone(t[i]);const s=r.speakers;if(typeof s=="object"&&s!==null){const i={...s},a=(n,l,c)=>{c.forEach((o,d)=>{const h=d===0?n:`${n}${d+1}`;i[h]?i[h]={...i[h],[l]:o}:d>0&&(i[h]={role:n,[l]:o})})};a("interviewer","by",t.by??[]),a("witness","of",m.witnessesOf(t.interviewOf)),r.speakers=i}return m.ordered(r)}applyFileToEvent(e,t){const r=this.current;if(!r)return!1;const s=JSON.stringify(m.SHARED.map(n=>r[n])),i=n=>typeof n=="string"||typeof n=="object"&&n!==null&&!Array.isArray(n),a={title:i,summary:i,time:n=>typeof n=="string",medium:n=>typeof n=="string",by:n=>Array.isArray(n),interviewOf:n=>typeof n=="string"||Array.isArray(n)};for(const n of m.SHARED){const l=e[n];l!==void 0&&a[n](l)?r[n]=structuredClone(l):t&&delete r[n]}return JSON.stringify(m.SHARED.map(n=>r[n]))!==s}syncFileFromEvent(){const e=this.current,t=this.fileEditor?.text??this.text;if(!e||t===void 0)return;let r;try{r=JSON.parse(t)}catch{return}if(typeof r!="object"||r===null||Array.isArray(r))return;const s=m.pretty(m.withEventFields(r,e));s!==t&&(this.text=s,this.fileEditor?.replaceText(s,!1),this.dispatchEvent(new CustomEvent(E,{bubbles:!0,composed:!0,detail:{field:"transcript",event:e,text:s}})))}template(e){return m.pretty(m.withEventFields({version:1,language:this.language,speakers:{interviewer:{role:"interviewer"},witness:{role:"witness"}},segments:[]},e))}loadFileEditor(e){return this.fileEditorLoading??=(async()=>{try{const[{SightingFileEditor:t},{default:r},s]=await Promise.all([A(()=>import("./SightingFileEditor-B6vMDoEQ.js"),__vite__mapDeps([0,1]),import.meta.url),A(()=>import("./interviewSchema-D82l9P1r.js"),[],import.meta.url),V(this.language)]);return this.fileEditor=new t(this.shadow.getElementById("file-editor"),e,i=>this.onEdited(i),this.messages.editorLabel,{baseline:e,phrases:s.fileSearchPhrases,schema:r}),this.fileEditor}catch(t){throw this.fileEditorLoading=void 0,t}})(),this.fileEditorLoading}async showTranscript(){const e=this.current;if(!e||this.shownFor===e)return;this.note(this.messages.loading);let t=this.text;if(t===void 0){const r=this.transcriptUrl(e);if(r!==void 0)try{const s=await T.json(r);this.current===e&&typeof s=="object"&&s!==null&&!Array.isArray(s)?(this.applyFileToEvent(s,!1)&&this.fileChangedEvent(),t=m.pretty(m.withEventFields(s,e))):t=m.pretty(s)}catch{this.note(this.messages.loadFailed,"error")}t??=this.template(e)}if(this.current===e)try{const r=await this.loadFileEditor(t);if(this.current!==e)return;r.replaceText(t,!1),this.shownFor=e,this.text!==void 0||this.transcriptUrl(e)===void 0?this.note(void 0):this.shadow.getElementById("file-note").textContent===this.messages.loading&&this.note(void 0)}catch{this.note(this.messages.loadFailed,"error")}}onEdited(e){this.text=e;let t,r=!0;try{t=JSON.parse(e)}catch{r=!1}this.note(r?void 0:this.messages.invalid,"error");const s=this.current;if(r&&s&&typeof t=="object"&&t!==null&&!Array.isArray(t)&&this.applyFileToEvent(t,!0)&&this.fileChangedEvent(),s&&!s.url&&e.trim()!==""){const i=s.id??"1";s.url=`${i.startsWith("interview")?i:`interview-${i}`}.json`,this.field("url").value=s.url,this.dispatchEvent(new CustomEvent(E,{bubbles:!0,composed:!0,detail:{field:"url",event:s}}))}this.dispatchEvent(new CustomEvent(E,{bubbles:!0,composed:!0,detail:{field:"transcript",event:s,text:e}})),this.showExchanges()}fileChangedEvent(){this.render(),this.dispatchEvent(new CustomEvent(E,{bubbles:!0,composed:!0,detail:{field:"file",event:this.current}}))}async take(e){if(typeof e!="object"||e===null||!Array.isArray(e.segments)){this.note(this.messages.notATranscript,"error");return}const t=this.current;t&&this.applyFileToEvent(e,!1)&&this.fileChangedEvent();const r=m.pretty(t?m.withEventFields(e,t):e);await this.loadFileEditor(r).then(s=>s.replaceText(r,!1),()=>{}),this.shownFor=this.current,this.note(void 0),this.onEdited(r)}async importFromFile(e){try{const t=await e.text();let r;try{r=JSON.parse(t)}catch{r=void 0}if(typeof r!="object"||r===null){const{InterviewImport:s}=await A(async()=>{const{InterviewImport:l}=await import("./interviewImport-C-TiARLK.js");return{InterviewImport:l}},[],import.meta.url),i=this.current,a=[i?.interviewOf].flat().filter(l=>typeof l=="string"),n=s.parse(t,{by:(i?.by??[]).map(l=>m.byText([l])).filter(l=>l!==""),witnesses:this.options.filter(l=>a.length===0||a.includes(l.id))});r=n&&{...n,transcription:{note:`Imported from ${e.name}: who speaks and when are as the file gave them, to be checked.`}}}r===void 0?this.note(this.messages.notATranscript,"error"):await this.take(r)}catch{this.note(this.messages.loadFailed,"error")}}async importFromUrl(e){try{await this.take(await T.json(new URL(e,this.base??location.href).href))}catch{this.note(this.messages.loadFailed,"error")}}save(){const e=this.fileEditor?.text??this.text;if(e===void 0)return;const t=(this.current?.url??"").split("/").pop()||`${this.current?.id??"interview"}.json`,r=URL.createObjectURL(new Blob([e],{type:"application/json"})),s=document.createElement("a");s.href=r,s.download=t,s.click(),URL.revokeObjectURL(r)}}function te(){Y(),J(),customElements.get($)||customElements.define($,m)}export{E as INTERVIEW_CHANGE_EVENT,$ as INTERVIEW_EDITOR_ELEMENT_NAME,W as INTERVIEW_SEEK_EVENT,m as InterviewEditorElement,te as register};
@@ -0,0 +1 @@
1
+ import{Y as d,Z as c,$ as l,a0 as f,a1 as m,a2 as u,a3 as w,a4 as g,a5 as y,a6 as M,a7 as S,a8 as v,a9 as O}from"./rr0-sighting-editor.mjs";class h{static key(e){return"url"in e?`url:${e.url}|${e.headingOffsetDeg??0}`:"shape"in e?`shape:${e.shape}`:`decor:${e.decorKind}`}static async build(e){const a=new d;if("url"in e){const s=await c(e.url);s.rotation.y=-(e.headingOffsetDeg??0)*Math.PI/180,a.add(s)}else"shape"in e?a.add(h.shapeOf(e.shape)):a.add(l.build({id:"preview",kind:e.decorKind,eastM:0,northM:0},!1));return a}static shapeOf(e){if(e==="figure")return l.build({id:"preview",kind:"entity",eastM:0,northM:0},!1);const a=new f(m.unitGeometry(e),new u({color:13158608,roughness:.5,metalness:0}));return e==="ellipsoid"&&a.scale.set(1,.55,1),a}static dispose(e){e.traverse(a=>{const s=a;if(s.isMesh){s.geometry?.dispose();for(const t of[s.material].flat())t.dispose()}})}static stage(e,a){const s=new w().setFromObject(e);if(s.isEmpty())return;const t=s.getBoundingSphere(new g),r=new d;e.position.sub(t.center),r.add(e);const n=new y;n.add(r),n.add(new M(16777215,6715272,1.6));const o=new S(16777215,2.5);o.position.set(1,2,1.5),n.add(o);const i=new v(30,a,t.radius*.05,t.radius*20),p=t.radius/Math.sin(i.fov*Math.PI/360)*1.05;return i.position.copy(new O(.55,.3,-.78).normalize().multiplyScalar(p)),i.lookAt(0,0,0),{scene:n,camera:i,pivot:r}}}export{h as M};
@@ -1 +1 @@
1
- import{Q as c}from"./rr0-sighting-editor.mjs";import{M as i}from"./ModelSource-CnDfgMtY.js";class l{static SIZE_PX={width:192,height:144};static cache=new Map;static queue=Promise.resolve();static pending=0;static renderer;static of(r){const t=i.key(r);let e=this.cache.get(t);return e||(this.pending++,e=this.queue.then(()=>this.draw(r)).catch(()=>{}).finally(()=>{--this.pending===0&&this.release()}),this.queue=e,this.cache.set(t,e)),e}static async draw(r){const t=await i.build(r);try{const{width:e,height:n}=this.SIZE_PX,a=i.stage(t,e/n);if(!a)return;const s=this.renderer??=new c({antialias:!0,alpha:!0,preserveDrawingBuffer:!0});return s.setSize(e,n,!1),s.setClearColor(0,0),s.render(a.scene,a.camera),s.domElement.toDataURL("image/png")}finally{i.dispose(t)}}static release(){this.renderer?.dispose(),this.renderer?.forceContextLoss(),this.renderer=void 0}}export{l as ModelThumbnails};
1
+ import{X as c}from"./rr0-sighting-editor.mjs";import{M as i}from"./ModelSource-D9Q4lorr.js";class l{static SIZE_PX={width:192,height:144};static cache=new Map;static queue=Promise.resolve();static pending=0;static renderer;static of(r){const t=i.key(r);let e=this.cache.get(t);return e||(this.pending++,e=this.queue.then(()=>this.draw(r)).catch(()=>{}).finally(()=>{--this.pending===0&&this.release()}),this.queue=e,this.cache.set(t,e)),e}static async draw(r){const t=await i.build(r);try{const{width:e,height:n}=this.SIZE_PX,a=i.stage(t,e/n);if(!a)return;const s=this.renderer??=new c({antialias:!0,alpha:!0,preserveDrawingBuffer:!0});return s.setSize(e,n,!1),s.setClearColor(0,0),s.render(a.scene,a.camera),s.domElement.toDataURL("image/png")}finally{i.dispose(t)}}static release(){this.renderer?.dispose(),this.renderer?.forceContextLoss(),this.renderer=void 0}}export{l as ModelThumbnails};
@@ -1 +1 @@
1
- import{Q as h}from"./rr0-sighting-editor.mjs";import{M as c}from"./ModelSource-CnDfgMtY.js";class a{constructor(t,e){this.container=t,this.source=e,this.canvas=document.createElement("canvas"),this.canvas.className="model-live",this.canvas.style.cssText="position:absolute;inset:0;width:100%;height:100%;touch-action:none;cursor:grab;display:none",this.canvas.addEventListener("pointerdown",s=>this.press(s)),this.canvas.addEventListener("pointermove",s=>this.move(s)),this.canvas.addEventListener("pointerup",()=>this.release()),this.canvas.addEventListener("pointercancel",()=>this.release())}static renderer;static SPIN_RAD_PER_S=.7;static DRAG_RAD_PER_PX=.012;static CLICK_TOLERANCE_PX=4;static current;object;stage;frame=0;stopped=!1;last=0;dragging;draggedPastClick=!1;canvas;static show(t,e){a.current?.stop();const s=new a(t,e);return a.current=s,s.start(),s}static end(){a.current?.stop(),a.current=void 0,a.renderer?.dispose(),a.renderer?.forceContextLoss(),a.renderer=void 0}consumeDrag(){const t=this.draggedPastClick;return this.draggedPastClick=!1,t}get active(){return!this.stopped}stop(){this.stopped=!0,cancelAnimationFrame(this.frame),this.canvas.remove(),this.object&&c.dispose(this.object),this.object=void 0,a.current===this&&(a.current=void 0)}async start(){try{const t=await c.build(this.source);if(this.stopped){c.dispose(t);return}this.object=t;const{width:e,height:s}=this.container.getBoundingClientRect(),i=e>0&&s>0?e/s:4/3;if(this.stage=c.stage(t,i),!this.stage)return;(a.renderer??=new h({antialias:!0,alpha:!0})).setClearColor(0,0),this.container.append(this.canvas),this.canvas.style.display="block",this.last=performance.now(),this.tick()}catch{}}tick=()=>{if(this.stopped||!this.stage)return;const t=performance.now(),e=Math.min(.1,(t-this.last)/1e3);this.last=t,this.dragging||(this.stage.pivot.rotation.y+=a.SPIN_RAD_PER_S*e);const s=a.renderer,{width:i,height:n}=this.canvas.getBoundingClientRect(),r=Math.min(2,window.devicePixelRatio||1);(this.canvas.width!==Math.round(i*r)||this.canvas.height!==Math.round(n*r))&&(s.setPixelRatio(r),s.setSize(i,n,!1),this.canvas.width=Math.round(i*r),this.canvas.height=Math.round(n*r),this.stage.camera.aspect=i/Math.max(1,n),this.stage.camera.updateProjectionMatrix()),s.render(this.stage.scene,this.stage.camera);const o=this.canvas.getContext("2d");o?.clearRect(0,0,this.canvas.width,this.canvas.height),o?.drawImage(s.domElement,0,0,this.canvas.width,this.canvas.height),this.frame=requestAnimationFrame(this.tick)};press(t){this.dragging={x:t.clientX,y:t.clientY,moved:!1},this.canvas.setPointerCapture(t.pointerId),this.canvas.style.cursor="grabbing"}move(t){const e=this.dragging;if(!e||!this.stage)return;const s=t.clientX-e.x,i=t.clientY-e.y;!e.moved&&Math.hypot(s,i)<a.CLICK_TOLERANCE_PX||(e.moved=!0,this.draggedPastClick=!0,this.stage.pivot.rotation.y+=s*a.DRAG_RAD_PER_PX,this.stage.pivot.rotation.x=Math.max(-1.4,Math.min(1.4,this.stage.pivot.rotation.x-i*a.DRAG_RAD_PER_PX)),e.x=t.clientX,e.y=t.clientY)}release(){this.dragging=void 0,this.canvas.style.cursor="grab"}}export{a as ModelViewer};
1
+ import{X as h}from"./rr0-sighting-editor.mjs";import{M as c}from"./ModelSource-D9Q4lorr.js";class a{constructor(t,e){this.container=t,this.source=e,this.canvas=document.createElement("canvas"),this.canvas.className="model-live",this.canvas.style.cssText="position:absolute;inset:0;width:100%;height:100%;touch-action:none;cursor:grab;display:none",this.canvas.addEventListener("pointerdown",s=>this.press(s)),this.canvas.addEventListener("pointermove",s=>this.move(s)),this.canvas.addEventListener("pointerup",()=>this.release()),this.canvas.addEventListener("pointercancel",()=>this.release())}static renderer;static SPIN_RAD_PER_S=.7;static DRAG_RAD_PER_PX=.012;static CLICK_TOLERANCE_PX=4;static current;object;stage;frame=0;stopped=!1;last=0;dragging;draggedPastClick=!1;canvas;static show(t,e){a.current?.stop();const s=new a(t,e);return a.current=s,s.start(),s}static end(){a.current?.stop(),a.current=void 0,a.renderer?.dispose(),a.renderer?.forceContextLoss(),a.renderer=void 0}consumeDrag(){const t=this.draggedPastClick;return this.draggedPastClick=!1,t}get active(){return!this.stopped}stop(){this.stopped=!0,cancelAnimationFrame(this.frame),this.canvas.remove(),this.object&&c.dispose(this.object),this.object=void 0,a.current===this&&(a.current=void 0)}async start(){try{const t=await c.build(this.source);if(this.stopped){c.dispose(t);return}this.object=t;const{width:e,height:s}=this.container.getBoundingClientRect(),i=e>0&&s>0?e/s:4/3;if(this.stage=c.stage(t,i),!this.stage)return;(a.renderer??=new h({antialias:!0,alpha:!0})).setClearColor(0,0),this.container.append(this.canvas),this.canvas.style.display="block",this.last=performance.now(),this.tick()}catch{}}tick=()=>{if(this.stopped||!this.stage)return;const t=performance.now(),e=Math.min(.1,(t-this.last)/1e3);this.last=t,this.dragging||(this.stage.pivot.rotation.y+=a.SPIN_RAD_PER_S*e);const s=a.renderer,{width:i,height:n}=this.canvas.getBoundingClientRect(),r=Math.min(2,window.devicePixelRatio||1);(this.canvas.width!==Math.round(i*r)||this.canvas.height!==Math.round(n*r))&&(s.setPixelRatio(r),s.setSize(i,n,!1),this.canvas.width=Math.round(i*r),this.canvas.height=Math.round(n*r),this.stage.camera.aspect=i/Math.max(1,n),this.stage.camera.updateProjectionMatrix()),s.render(this.stage.scene,this.stage.camera);const o=this.canvas.getContext("2d");o?.clearRect(0,0,this.canvas.width,this.canvas.height),o?.drawImage(s.domElement,0,0,this.canvas.width,this.canvas.height),this.frame=requestAnimationFrame(this.tick)};press(t){this.dragging={x:t.clientX,y:t.clientY,moved:!1},this.canvas.setPointerCapture(t.pointerId),this.canvas.style.cursor="grabbing"}move(t){const e=this.dragging;if(!e||!this.stage)return;const s=t.clientX-e.x,i=t.clientY-e.y;!e.moved&&Math.hypot(s,i)<a.CLICK_TOLERANCE_PX||(e.moved=!0,this.draggedPastClick=!0,this.stage.pivot.rotation.y+=s*a.DRAG_RAD_PER_PX,this.stage.pivot.rotation.x=Math.max(-1.4,Math.min(1.4,this.stage.pivot.rotation.x-i*a.DRAG_RAD_PER_PX)),e.x=t.clientX,e.y=t.clientY)}release(){this.dragging=void 0,this.canvas.style.cursor="grab"}}export{a as ModelViewer};
@@ -0,0 +1,41 @@
1
+ import{Y as F,a2 as P,af as H,ag as w,aj as K,al as N,a0 as U,am as k}from"./rr0-sighting-editor.mjs";class b{static HOLLOW_M=1;static COVERED_M=3;static WINDOW_M=30;static SAMPLE_M=4;static MAX_GRADE={rail:.025,highSpeed:.035,subway:.04,light_rail:.06,tram:.08,funicular:1/0};static heights(t,n,e){const s=t.map(a=>a.level===0),o=t.map((a,l)=>s[l]?b.targets(a,n,e):[]),c=t.map((a,l)=>s[l]?b.samples(a,o[l]):[]),r=new Map;t.forEach((a,l)=>{if(s[l])for(const M of b.ends(a)){const h=b.key(M),d=r.get(h);d?d.push(l):r.set(h,[l])}});const p=a=>{const l=r.get(b.key(a));if(l)return b.median(l.flatMap(M=>c[M]),a)};return t.map((a,l)=>{if(s[l]){const M=[...new Set([l,...b.ends(a).flatMap(g=>r.get(b.key(g))??[])])].flatMap(g=>c[g]),h=a.centre.map((g,D)=>b.median(M,g)??o[l][D]),[d,L]=b.ends(a);return h[0]=p(d)??h[0],h[h.length-1]=p(L)??h[h.length-1],b.graded(h,a.along,a.highSpeed?b.MAX_GRADE.highSpeed:b.MAX_GRADE[a.kind])}return b.carried(a,n,e,p)})}static targets(t,n,e){return t.centre.map(s=>{const o=n(s.x,s.z);if(e===void 0||s.altitudeM===void 0)return o;const c=s.altitudeM+e;return c-o>b.HOLLOW_M||o-c>b.COVERED_M?c:o})}static samples(t,n){const e=[];let s=0;return t.centre.forEach((o,c)=>{t.along[c]+1e-6<s&&c<t.centre.length-1||(e.push({x:o.x,z:o.z,y:n[c]}),s=t.along[c]+b.SAMPLE_M)}),e}static median(t,n){const e=[];for(const o of t)Math.hypot(o.x-n.x,o.z-n.z)<=b.WINDOW_M&&e.push(o.y);if(e.length===0)return;e.sort((o,c)=>o-c);const s=e.length>>1;return e.length%2===1?e[s]:(e[s-1]+e[s])/2}static graded(t,n,e){if(!Number.isFinite(e)||t.length<3)return t;const s=[...t],o=s.length-1,c=n[o];if(Math.abs(s[o]-s[0])>e*c)return n.map(r=>s[0]+(s[o]-s[0])*r/c);for(let r=1;r<o;r++){const p=e*(n[r]-n[r-1]);s[r]=Math.min(s[r-1]+p,Math.max(s[r-1]-p,s[r]))}for(let r=o-1;r>0;r--){const p=e*(n[r+1]-n[r]);s[r]=Math.min(s[r+1]+p,Math.max(s[r+1]-p,s[r]))}return s}static carried(t,n,e,s){const[o,c]=b.ends(t),r=t.along[t.along.length-1],p=s(o),a=s(c);if(e!==void 0&&t.centre.every(h=>h.altitudeM!==void 0)){const h=t.centre.map(g=>g.altitudeM+e),d=p!==void 0?p-h[0]:0,L=a!==void 0?a-h[h.length-1]:0;return h.map((g,D)=>g+d+(L-d)*t.along[D]/r)}const l=p??n(o.x,o.z),M=a??n(c.x,c.z);return t.along.map(h=>l+(M-l)*h/r)}static ends(t){return[t.centre[0],t.centre[t.centre.length-1]]}static key(t){return`${Math.round(t.x*2)},${Math.round(t.z*2)}`}}class B{positions=[];indices=[];along=[];get vertexCount(){return this.positions.length/3}strip(t,n,e){const s=this.vertexCount;for(let o=0;o<t.length;o++)this.positions.push(t[o].x,t[o].y,t[o].z,n[o].x,n[o].y,n[o].z),e&&this.along.push(e[o],e[o]);for(let o=0;o<t.length-1;o++){const c=s+o*2;this.indices.push(c,c+2,c+1,c+1,c+2,c+3)}}beam(t,n,e,s=!0){const o=n.x-t.x,c=n.y-t.y,r=n.z-t.z,p=Math.hypot(o,c,r);if(p<1e-6)return;const a={x:o/p,y:c/p,z:r/p},l=Math.abs(a.y)>.9?{x:1,y:0,z:0}:{x:0,y:1,z:0},M=a.y*l.z-a.z*l.y,h=a.z*l.x-a.x*l.z,d=a.x*l.y-a.y*l.x,L=Math.hypot(M,h,d),g={x:M/L*e/2,y:h/L*e/2,z:d/L*e/2},D={x:a.y*g.z-a.z*g.y,y:a.z*g.x-a.x*g.z,z:a.x*g.y-a.y*g.x},E=(u,A,f)=>({x:u.x+g.x*A+D.x*f,y:u.y+g.y*A+D.y*f,z:u.z+g.z*A+D.z*f}),O=[[1,1],[-1,1],[-1,-1],[1,-1]];for(let u=0;u<4;u++){const[A,f]=O[u],[v,x]=O[(u+1)%4];this.quad(E(t,A,f),E(t,v,x),E(n,v,x),E(n,A,f))}s&&(this.quad(...[...O].reverse().map(([u,A])=>E(t,u,A))),this.quad(...O.map(([u,A])=>E(n,u,A))))}quad(t,n,e,s){const o=this.vertexCount;this.positions.push(t.x,t.y,t.z,n.x,n.y,n.z,e.x,e.y,e.z,s.x,s.y,s.z),this.indices.push(o,o+1,o+2,o,o+2,o+3)}}class i{static STEP_M=6;static BALLAST_HEIGHT_M=.5;static BALLAST_SLOPE=1.5;static BALLAST_SHOULDER_M=.45;static DECK_DEPTH_M=.8;static BANK_SLOPE=1.5;static SLEEPER_LENGTH_M=2.4;static SLEEPER_WIDTH_M=.29;static SLEEPER_SPACING_M=.6;static RAIL_HEAD_M=.072;static RAIL_HEIGHT_M=.18;static TRAM_RAIL_HEIGHT_M=.03;static LIFT_M=.06;static CATENARY={rail:{contactM:5.5,systemM:1.4,spanM:54,staggerM:.2},highSpeed:{contactM:5.08,systemM:1.4,spanM:63,staggerM:.2},service:{contactM:5.5,systemM:1.2,spanM:45,staggerM:.2},tram:{contactM:6,systemM:0,spanM:30,staggerM:.2}};static MESSENGER_SAG_M=.55;static MAST_SIDE_M=.26;static MAST_OFFSET_M=3.1;static MAST_ABOVE_M=.6;static CONTACT_DIAMETER_M=.012;static MESSENGER_DIAMETER_M=.011;static ARM_SIDE_M=.06;static TRACK_SPACING_M={rail:3.7,highSpeed:4.5,tram:3.1,subway:3.4,light_rail:3.4,funicular:3.2};static BALLAST_ALBEDO=[.17,.16,.15];static BANK_ALBEDO=[.085,.09,.05];static DECK_ALBEDO=[.26,.255,.24];static CONCRETE_ALBEDO=[.36,.35,.33];static WOOD_ALBEDO=[.07,.055,.045];static RUST_ALBEDO=[.085,.068,.058];static STEEL_ALBEDO=[.3,.31,.32];static COPPER_ALBEDO=[.09,.085,.07];static LINE_REACH_M=20;group=new F;stepM=i.STEP_M;drawn=0;laid=[];ballastMaterial=i.pebbled(i.lambert(i.BALLAST_ALBEDO,!0));earthMaterial=i.lambert(i.BANK_ALBEDO,!0);deckMaterial=i.lambert(i.DECK_ALBEDO,!1);concreteSleepers=i.sleeperMaterial(i.CONCRETE_ALBEDO);woodSleepers=i.sleeperMaterial(i.WOOD_ALBEDO);rustMaterial=i.lambert(i.RUST_ALBEDO,!0);headMaterial=new P({color:new H(.62,.62,.62),metalness:1,roughness:.28,polygonOffset:!0,polygonOffsetFactor:-2,polygonOffsetUnits:-2});steelMaterial=i.lambert(i.STEEL_ALBEDO,!1);wireMaterial=i.lambert(i.COPPER_ALBEDO,!1);constructor(){this.group.name="railways"}get count(){return this.drawn}setGroundSpacing(t){this.stepM=Number.isFinite(t)&&t>0?Math.min(i.STEP_M,Math.max(1.5,t/2)):i.STEP_M}set(t,n,e,s,o=1/0){this.clear();const r=t.map(d=>({line:d,path:d.points.map(L=>({...w(L.lat,L.lng,n,e),altitudeM:L.altitudeM}))})).filter(({line:d,path:L})=>d.level>=0&&i.within(L,o)),p=i.altitudeOffset(r,s),a=r.flatMap(({line:d,path:L})=>{const g=this.layoutOf(d,L);return g?[{line:d,layout:g}]:[]}),l=b.heights(a.map(({layout:d})=>d),s,p),M={ballast:new B,earth:new B,deck:new B,concrete:new B,wood:new B,rust:new B,head:new B,steel:new B,wire:new B};a.forEach(({line:d,layout:L},g)=>{this.lay(d,{...L,y:l[g]},s,M),this.laid.push({line:d,centre:L.centre}),this.drawn++});const h=[[M.ballast,this.ballastMaterial,"ballast",!1],[M.earth,this.earthMaterial,"embankments",!1],[M.deck,this.deckMaterial,"decks",!0],[M.concrete,this.concreteSleepers,"sleepers",!1],[M.wood,this.woodSleepers,"sleepers wood",!1],[M.rust,this.rustMaterial,"rails",!1],[M.head,this.headMaterial,"rail heads",!1],[M.steel,this.steelMaterial,"catenary masts",!0],[M.wire,this.wireMaterial,"catenary wires",!1]];for(const[d,L,g,D]of h){const E=i.meshOf(d,L);E&&(E.name=`railways ${g}`,E.castShadow=D,E.receiveShadow=!0,this.group.add(E))}}clear(){for(const t of[...this.group.children])this.group.remove(t),t.geometry?.dispose();this.drawn=0,this.laid=[]}lineAt(t,n){let e;for(const{line:s,centre:o}of this.laid)for(let c=0;c<o.length-1;c++){const r=o[c],p=o[c+1],a=p.x-r.x,l=p.z-r.z,M=a*a+l*l,h=M===0?0:Math.max(0,Math.min(1,((t-r.x)*a+(n-r.z)*l)/M)),d=Math.hypot(t-(r.x+a*h),n-(r.z+l*h));d<=i.LINE_REACH_M&&(!e||d<e.distance)&&(e={line:s,distance:d})}return e?.line}contactHeightM(t){if(!(t.electrification!=="overhead"||t.kind==="subway"||t.kind==="funicular"))return i.catenaryOf(t).contactM}static catenaryOf(t){return t.kind==="tram"?i.CATENARY.tram:t.highSpeed?i.CATENARY.highSpeed:t.service?i.CATENARY.service:i.CATENARY.rail}dispose(){this.clear();for(const t of[this.ballastMaterial,this.earthMaterial,this.deckMaterial,this.concreteSleepers,this.woodSleepers,this.rustMaterial,this.headMaterial,this.steelMaterial,this.wireMaterial])t.dispose()}static within(t,n){if(!Number.isFinite(n))return!0;const e=n*1.1+100;return t.some((s,o)=>Math.hypot(s.x,s.z)<=e||o>0&&Math.hypot((s.x+t[o-1].x)/2,(s.z+t[o-1].z)/2)<=e)}static altitudeOffset(t,n){const e=[];for(const{line:s,path:o}of t)if(s.level===0)for(const c of o)c.altitudeM!==void 0&&e.push(n(c.x,c.z)-c.altitudeM);if(!(e.length<5))return e.sort((s,o)=>s-o),e[Math.floor(e.length/2)]}layoutOf(t,n){const e=[];for(let c=0;c<n.length-1;c++){const r=n[c],p=n[c+1],a=Math.max(1,Math.ceil(Math.hypot(p.x-r.x,p.z-r.z)/this.stepM));for(let l=0;l<a;l++){const M=l/a,h=r.altitudeM!==void 0&&p.altitudeM!==void 0?r.altitudeM+(p.altitudeM-r.altitudeM)*M:void 0;e.push({x:r.x+(p.x-r.x)*M,z:r.z+(p.z-r.z)*M,altitudeM:h})}}if(e.push(n[n.length-1]),e.length<2)return;const s=[0];for(let c=1;c<e.length;c++)s.push(s[c-1]+Math.hypot(e[c].x-e[c-1].x,e[c].z-e[c-1].z));if(s[s.length-1]<.5)return;const o=e.map((c,r)=>{const p=e[Math.max(0,r-1)],a=e[Math.min(e.length-1,r+1)],l=a.x-p.x,M=a.z-p.z,h=Math.hypot(l,M);return h<1e-6?{x:1,z:0}:{x:l/h,z:M/h}});return{kind:t.kind,...t.highSpeed?{highSpeed:!0}:{},level:t.level,centre:e,along:s,dir:o}}lay(t,n,e,s){const o=t.highSpeed?i.TRACK_SPACING_M.highSpeed:i.TRACK_SPACING_M[t.kind],c=Math.min(8,Math.max(1,t.tracks)),r=Array.from({length:c},(E,O)=>(O-(c-1)/2)*o),p=t.kind==="tram",a=p?0:i.BALLAST_HEIGHT_M,l=i.LIFT_M,M=(E,O,u)=>({x:n.centre[E].x-n.dir[E].z*O,y:n.y[E]+u,z:n.centre[E].z+n.dir[E].x*O}),h=n.centre.map((E,O)=>O);if(!p){const E=i.SLEEPER_LENGTH_M/2+i.BALLAST_SHOULDER_M+(r[r.length-1]??0),O=h.map(A=>M(A,E,a+l)),u=h.map(A=>M(A,-E,a+l));if(s.ballast.strip(O,u),t.level===0){const A=E+a*i.BALLAST_SLOPE;for(const f of[1,-1]){const v=[],x=[];let m=!1;for(const z of h){const S=M(z,f*A,l),C=e(S.x,S.z)+l,T=S.y-C;T<0&&(S.y=Math.min(C,n.y[z]+a-.05+l)),v.push(S);const I=M(z,f*(A+Math.max(0,T)*i.BANK_SLOPE),l);I.y=Math.min(e(I.x,I.z)+l,S.y),S.y-I.y>.02&&(m=!0),x.push(I)}const _=f>0?O:u;f>0?s.ballast.strip(v,_):s.ballast.strip(_,v),m&&(f>0?s.earth.strip(x,v):s.earth.strip(v,x))}}else{const A=-i.DECK_DEPTH_M,f=E+.15,v=h.map(z=>M(z,f,a+l+.1)),x=h.map(z=>M(z,-f,a+l+.1)),m=h.map(z=>M(z,f,A)),_=h.map(z=>M(z,-f,A));s.deck.strip(m,v),s.deck.strip(x,_),s.deck.strip(_,m),s.deck.strip(v,h.map(z=>M(z,E,a+l+.1))),s.deck.strip(h.map(z=>M(z,-E,a+l+.1)),x)}}const d=t.service?s.wood:s.concrete,L=t.gaugeMm/2e3+i.RAIL_HEAD_M/2,g=p?i.TRAM_RAIL_HEIGHT_M:i.RAIL_HEIGHT_M,D=a+l;for(const E of r){if(!p){const O=i.SLEEPER_LENGTH_M/2;d.strip(h.map(u=>M(u,E+O,D+.004)),h.map(u=>M(u,E-O,D+.004)),n.along)}for(const O of[1,-1]){const u=E+O*L,A=i.RAIL_HEAD_M/2,f=h.map(x=>M(x,u+A,D+g)),v=h.map(x=>M(x,u-A,D+g));s.head.strip(f,v),s.rust.strip(h.map(x=>M(x,u+A,D)),f),s.rust.strip(v,h.map(x=>M(x,u-A,D)))}}t.electrification==="overhead"&&t.kind!=="subway"&&t.kind!=="funicular"&&this.catenary(t,n,r,D+g,M,e,s)}catenary(t,n,e,s,o,c,r){const p=i.catenaryOf(t),a=n.along[n.along.length-1],l=Math.max(1,Math.round(a/p.spanM)),M=a/l,h=Array.from({length:l},(f,v)=>(v+.5)*M),d=s+p.contactM,L=d+p.systemM,g=(p.systemM>0?L:d)+i.MAST_ABOVE_M,D=e[e.length-1],E=e.length>2,O=e.length===1?[1]:[1,-1],u=(f,v,x)=>{let m=0,_=n.along.length-2;for(;m<_;){const T=m+_+1>>1;n.along[T]<=f?m=T:_=T-1}const z=Math.min(1,Math.max(0,(f-n.along[m])/Math.max(1e-6,n.along[m+1]-n.along[m]))),S=o(m,v,x),C=o(m+1,v,x);return{x:S.x+(C.x-S.x)*z,y:S.y+(C.y-S.y)*z,z:S.z+(C.z-S.z)*z}};for(const f of h){const v=u(f,0,d);if(!(t.level===0&&c(v.x,v.z)>v.y)){for(const x of O){const m=x*(D+i.MAST_OFFSET_M),_=u(f,m,0);t.level===0&&(_.y=Math.min(_.y,c(_.x,_.z)));const z=u(f,m,g);if(r.steel.beam(_,z,i.MAST_SIDE_M),E)continue;const C=(O.length===1?e[0]:x*D)-x*.6,T=p.systemM>0?L:d+.3;r.steel.beam(u(f,m,T),u(f,C,T),i.ARM_SIDE_M),r.steel.beam(u(f,m,d-.4),u(f,C,T),i.ARM_SIDE_M)}if(E){const x=D+i.MAST_OFFSET_M;r.steel.beam(u(f,x,g-.2),u(f,-x,g-.2),i.MAST_SIDE_M*.8)}}}const A=[0,...h,a];for(const[f,v]of e.entries()){const x=m=>m===0||m===A.length-1?0:((m+f)%2===0?1:-1)*p.staggerM;for(let m=0;m<A.length-1;m++){const _=A[m],z=A[m+1];if(z-_<.1||(r.wire.beam(u(_,v+x(m),d),u(z,v+x(m+1),d),i.CONTACT_DIAMETER_M,!1),p.systemM<=0))continue;const S=4,C=T=>{const I=m===0?.5+T/2:m===A.length-2?T/2:T;return L-i.MESSENGER_SAG_M*4*I*(1-I)};for(let T=0;T<S;T++){const I=T/S,G=(T+1)/S;r.wire.beam(u(_+(z-_)*I,v,C(I)),u(_+(z-_)*G,v,C(G)),i.MESSENGER_DIAMETER_M,!1)}}}}static meshOf(t,n){if(t.indices.length===0)return;const e=new K;return e.setAttribute("position",new N(new Float32Array(t.positions),3)),t.along.length>0&&e.setAttribute("aAlong",new N(new Float32Array(t.along),1)),e.setIndex(new N(new Uint32Array(t.indices),1)),e.computeVertexNormals(),new U(e,n)}static lambert(t,n){return new k({color:new H(...t),...n?{polygonOffset:!0,polygonOffsetFactor:-2,polygonOffsetUnits:-2}:{}})}static pebbled(t){return t.onBeforeCompile=n=>{n.vertexShader=n.vertexShader.replace("#include <common>",`#include <common>
2
+ varying vec3 vBallast;`).replace("#include <begin_vertex>",`#include <begin_vertex>
3
+ vBallast = position;`),n.fragmentShader=n.fragmentShader.replace("#include <common>",`#include <common>
4
+ varying vec3 vBallast;
5
+ float ballastHash(vec2 p) {
6
+ p = fract(p * vec2(127.1, 311.7));
7
+ p += dot(p, p + 34.5);
8
+ return fract(p.x * p.y);
9
+ }
10
+ float ballastNoise(vec2 p) {
11
+ vec2 i = floor(p);
12
+ vec2 f = fract(p);
13
+ f = f * f * (3.0 - 2.0 * f);
14
+ return mix(mix(ballastHash(i), ballastHash(i + vec2(1.0, 0.0)), f.x), mix(ballastHash(i + vec2(0.0, 1.0)), ballastHash(i + vec2(1.0, 1.0)), f.x), f.y);
15
+ }`).replace("#include <map_fragment>",`#include <map_fragment>
16
+ {
17
+ float eye = length(vViewPosition);
18
+ vec2 at = vBallast.xz + vec2(0.0, vBallast.y);
19
+ float stones = (ballastHash(floor(at * 22.0)) - 0.5) * 0.9 * (1.0 - smoothstep(4.0, 18.0, eye))
20
+ + (ballastNoise(at * 6.0) - 0.5) * 0.5 * (1.0 - smoothstep(15.0, 60.0, eye))
21
+ + (ballastNoise(at * 0.7) - 0.5) * 0.35 * (1.0 - smoothstep(80.0, 300.0, eye));
22
+ diffuseColor.rgb *= 1.0 + stones;
23
+ }`)},t.customProgramCacheKey=()=>"rr0-railway-ballast",t}static sleeperMaterial(t){const n=new k({color:new H(1,1,1),polygonOffset:!0,polygonOffsetFactor:-3,polygonOffsetUnits:-3}),e=new H(...t),s=new H(...i.BALLAST_ALBEDO);return n.onBeforeCompile=o=>{o.uniforms.uSleeper={value:e},o.uniforms.uCrib={value:s},o.uniforms.uSpacing={value:i.SLEEPER_SPACING_M},o.uniforms.uDuty={value:i.SLEEPER_WIDTH_M/i.SLEEPER_SPACING_M},o.vertexShader=o.vertexShader.replace("#include <common>",`#include <common>
24
+ attribute float aAlong;
25
+ varying float vAlong;`).replace("#include <begin_vertex>",`#include <begin_vertex>
26
+ vAlong = aAlong;`),o.fragmentShader=o.fragmentShader.replace("#include <common>",`#include <common>
27
+ varying float vAlong;
28
+ uniform vec3 uSleeper;
29
+ uniform vec3 uCrib;
30
+ uniform float uSpacing;
31
+ uniform float uDuty;
32
+ // How much of [0, x] periods is sleeper: the integral of a pulse of width uDuty at the start of each period.
33
+ float sleeperIntegral(float x) {
34
+ return floor(x) * uDuty + min(fract(x), uDuty);
35
+ }`).replace("#include <map_fragment>",`#include <map_fragment>
36
+ {
37
+ float u = vAlong / uSpacing;
38
+ float w = max(fwidth(u), 1e-4);
39
+ float cover = (sleeperIntegral(u + 0.5 * w) - sleeperIntegral(u - 0.5 * w)) / w;
40
+ diffuseColor.rgb *= mix(uCrib, uSleeper, clamp(cover, 0.0, 1.0));
41
+ }`)},n.customProgramCacheKey=()=>"rr0-railway-sleepers",n}}export{i as RailwaySystem};
@@ -1 +1 @@
1
- import{af as D}from"./rr0-sighting-editor.mjs";class M{static MIN_ROAD_M=5.5;static WIDE_ROAD_M=9;static LINE_WIDTH_M=.12;static DASH_M=3;static GAP_M=10;static RIVE_DASH_M=2.5;static RIVE_GAP_M=3.5;static MIN_DASH_M=.5;static LANE_M=3.5;static SHOULDER_MIN_M=1.5;static SOLID_EDGE_FROM_M=7;static SOLID_EDGE_FROM_KMH=80;static BEND_RADIUS_M=300;static EDGE_SETBACK_M=.4;static PAINT_RADIUS_M=450;static LIFT_M=.015;static BARRIER_MAX_AXES_M=40;static BARRIER_MIN_AXES_M=5;static BARRIER_HEIGHT_M=.8;static BARRIER_HALF_WIDTH_M=.2;static BARRIER_STEP_M=4;static painted(t){return t.surface==="paved"&&t.widthM>=M.MIN_ROAD_M&&t.kind!=="roundabout"}static plan(t){if(!M.painted(t))return[];const u=t.widthM,l=u/2,i=M.EDGE_SETBACK_M;if(!(t.direction!==void 0&&t.lanes!==void 0)){const I=[{offsetM:l-i,pattern:"solid"},{offsetM:-(l-i),pattern:"solid"}],A=u>=M.WIDE_ROAD_M?[-u/4,u/4]:[0];for(const x of A)I.push({offsetM:x,pattern:"dashed"});return I}const s=t.lanes,n=u>=M.SOLID_EDGE_FROM_M||(t.speedKmh??0)>=M.SOLID_EDGE_FROM_KMH?"solid":"rive";if(t.direction==="both"){if(s<2)return[];const I=[{offsetM:l-i,pattern:n},{offsetM:-(l-i),pattern:n}];if(s>=4){I.push({offsetM:.1,pattern:"solid"},{offsetM:-.1,pattern:"solid"});const A=Math.floor(s/2),x=u/s;for(let z=1;z<A;z++)I.push({offsetM:z*x,pattern:"dashed"},{offsetM:-z*x,pattern:"dashed"})}else I.push({offsetM:0,pattern:"dashed",solidInBends:!0});return I}const r=t.direction==="backward"?-1:1,o=I=>r*(l-I),c=Math.min(M.LANE_M,u/Math.max(s,1)),h=s*c,d=u-h,_=(t.kind==="motorway"||t.kind==="dual")&&d>=M.SHOULDER_MIN_M,E=[{offsetM:o(i),pattern:t.kind==="motorway"||t.kind==="dual"?"solid":n}];E.push({offsetM:o(_?h:u-i),pattern:t.kind==="motorway"||t.kind==="dual"?"solid":n});for(let I=1;I<s;I++)E.push({offsetM:o(I*c),pattern:"dashed"});return E}static lay(t,u,l,i){let e=0;const s=(f,n)=>{i(f,M.LINE_WIDTH_M,{offsetM:n,liftM:M.LIFT_M,minHalfM:M.LINE_WIDTH_M/2}),e++};for(const f of t){const n=M.plan(f);if(n.length===0)continue;const r=f.points.map(o=>D(o.lat,o.lng,u,l));for(const o of M.near(r,M.PAINT_RADIUS_M))for(const c of n){if(c.pattern==="solid"){s(o,c.offsetM);continue}const[h,d]=c.pattern==="dashed"?[M.DASH_M,M.GAP_M]:[M.RIVE_DASH_M,M.RIVE_GAP_M];for(const _ of c.solidInBends?M.byBends(o):[{path:o,bend:!1}])if(_.bend)s(_.path,c.offsetM);else for(const E of M.dashes(_.path,h,d))s(E,c.offsetM)}}return e}static byBends(t){if(t.length<3)return[{path:t,bend:!1}];const u=(e,s)=>Math.atan2(s.x-e.x,-(s.z-e.z)),l=new Array(t.length-1).fill(!1);for(let e=1;e<t.length-1;e++){const s=Math.hypot(t[e].x-t[e-1].x,t[e].z-t[e-1].z),f=Math.hypot(t[e+1].x-t[e].x,t[e+1].z-t[e].z);if(s<1e-6||f<1e-6)continue;let n=Math.abs(u(t[e],t[e+1])-u(t[e-1],t[e]));n>Math.PI&&(n=2*Math.PI-n),(s+f)/2/Math.max(n,1e-6)<M.BEND_RADIUS_M&&(l[e-1]=!0,l[e]=!0)}const i=[];for(let e=0;e<l.length;e++){const s=i.at(-1);s&&s.bend===l[e]?s.path.push(t[e+1]):i.push({path:[t[e],t[e+1]],bend:l[e]})}return i}static barriers(t,u,l){const i=t.filter(n=>(n.kind==="motorway"||n.kind==="dual")&&(n.direction==="forward"||n.direction==="backward")),e=i.map(n=>{const r=n.points.map(o=>D(o.lat,o.lng,u,l));return n.direction==="backward"?[...r].reverse():r}),s=[],f=M.BARRIER_STEP_M;return i.forEach((n,r)=>{for(const o of M.near(e[r],M.PAINT_RADIUS_M)){let c=[];for(const h of M.resample(o,f)){const d=M.midwayToOpposite(h,r,i,e);d?c.push(d):c.length>0&&(c.length>=2&&s.push(c),c=[])}c.length>=2&&s.push(c)}}),s}static midwayToOpposite(t,u,l,i){let e;for(let s=0;s<i.length;s++){if(s===u||l[s].id<l[u].id)continue;const f=i[s];for(let n=0;n<f.length-1;n++){const r=f[n+1].x-f[n].x,o=f[n+1].z-f[n].z,c=r*r+o*o;if(c<1e-9||(r*t.dx+o*t.dz)/Math.sqrt(c)>-.9)continue;const h=Math.max(0,Math.min(1,((t.x-f[n].x)*r+(t.z-f[n].z)*o)/c)),d={x:f[n].x+r*h,z:f[n].z+o*h},_=Math.hypot(t.x-d.x,t.z-d.z);_<M.BARRIER_MIN_AXES_M||_>M.BARRIER_MAX_AXES_M||(!e||_<e.distance)&&(e={distance:_,point:d})}}return e?{x:(t.x+e.point.x)/2,z:(t.z+e.point.z)/2}:void 0}static resample(t,u){const l=[];for(let e=0;e<t.length-1;e++){const s=Math.hypot(t[e+1].x-t[e].x,t[e+1].z-t[e].z);if(s<1e-6)continue;const f=(t[e+1].x-t[e].x)/s,n=(t[e+1].z-t[e].z)/s;for(let r=0;r<s;r+=u)l.push({x:t[e].x+f*r,z:t[e].z+n*r,dx:f,dz:n})}const i=l.at(-1);return i&&l.push({...t[t.length-1],dx:i.dx,dz:i.dz}),l}static barrierGeometry(t,u){const l=[],i=[],e=M.BARRIER_HALF_WIDTH_M;for(const s of t){if(s.length<2)continue;const f=l.length/3;for(let n=0;n<s.length;n++){const r=s[Math.max(0,n-1)],o=s[Math.min(s.length-1,n+1)],c=Math.hypot(o.x-r.x,o.z-r.z)||1,h=-(o.z-r.z)/c*e,d=(o.x-r.x)/c*e,_=u(s[n].x,s[n].z);l.push(s[n].x+h,_,s[n].z+d,s[n].x+h,_+M.BARRIER_HEIGHT_M,s[n].z+d,s[n].x-h,_+M.BARRIER_HEIGHT_M,s[n].z-d,s[n].x-h,_,s[n].z-d)}for(let n=0;n<s.length-1;n++){const r=f+n*4,o=r+4;for(let c=0;c<3;c++)i.push(r+c,o+c,r+c+1,r+c+1,o+c,o+c+1)}}return{positions:l,indices:i}}static near(t,u){const l=[];let i;for(let e=0;e<t.length-1;e++)Math.min(Math.hypot(t[e].x,t[e].z),Math.hypot(t[e+1].x,t[e+1].z))<=u||Math.hypot((t[e].x+t[e+1].x)/2,(t[e].z+t[e+1].z)/2)<=u?(i||(i=[t[e]]),i.push(t[e+1])):i&&(l.push(i),i=void 0);return i&&l.push(i),l}static dashes(t,u,l){const i=[],e=u+l;let s=0,f;const n=(o,c)=>({x:t[o].x+(t[o+1].x-t[o].x)*c,z:t[o].z+(t[o+1].z-t[o].z)*c});for(let o=0;o<t.length-1;o++){const c=Math.hypot(t[o+1].x-t[o].x,t[o+1].z-t[o].z);if(c<1e-6)continue;let h=0;for(;h<c;){const d=(s+h)%e,_=d<u,E=_?u-d:e-d,I=Math.min(c,h+E);_&&(f||(f=[n(o,h/c)]),f.push(n(o,I/c)),I-h>=E-1e-9&&(i.push(f),f=void 0)),h=I}s+=c}f&&f.length>=2&&i.push(f);const r=o=>o.slice(1).reduce((c,h,d)=>c+Math.hypot(h.x-o[d].x,h.z-o[d].z),0);return i.filter(o=>o.length>=2&&r(o)>=M.MIN_DASH_M)}}export{M as RoadMarkings};
1
+ import{ag as D}from"./rr0-sighting-editor.mjs";class M{static MIN_ROAD_M=5.5;static WIDE_ROAD_M=9;static LINE_WIDTH_M=.12;static DASH_M=3;static GAP_M=10;static RIVE_DASH_M=2.5;static RIVE_GAP_M=3.5;static MIN_DASH_M=.5;static LANE_M=3.5;static SHOULDER_MIN_M=1.5;static SOLID_EDGE_FROM_M=7;static SOLID_EDGE_FROM_KMH=80;static BEND_RADIUS_M=300;static EDGE_SETBACK_M=.4;static PAINT_RADIUS_M=450;static LIFT_M=.015;static BARRIER_MAX_AXES_M=40;static BARRIER_MIN_AXES_M=5;static BARRIER_HEIGHT_M=.8;static BARRIER_HALF_WIDTH_M=.2;static BARRIER_STEP_M=4;static painted(t){return t.surface==="paved"&&t.widthM>=M.MIN_ROAD_M&&t.kind!=="roundabout"}static plan(t){if(!M.painted(t))return[];const u=t.widthM,l=u/2,i=M.EDGE_SETBACK_M;if(!(t.direction!==void 0&&t.lanes!==void 0)){const I=[{offsetM:l-i,pattern:"solid"},{offsetM:-(l-i),pattern:"solid"}],A=u>=M.WIDE_ROAD_M?[-u/4,u/4]:[0];for(const x of A)I.push({offsetM:x,pattern:"dashed"});return I}const s=t.lanes,n=u>=M.SOLID_EDGE_FROM_M||(t.speedKmh??0)>=M.SOLID_EDGE_FROM_KMH?"solid":"rive";if(t.direction==="both"){if(s<2)return[];const I=[{offsetM:l-i,pattern:n},{offsetM:-(l-i),pattern:n}];if(s>=4){I.push({offsetM:.1,pattern:"solid"},{offsetM:-.1,pattern:"solid"});const A=Math.floor(s/2),x=u/s;for(let z=1;z<A;z++)I.push({offsetM:z*x,pattern:"dashed"},{offsetM:-z*x,pattern:"dashed"})}else I.push({offsetM:0,pattern:"dashed",solidInBends:!0});return I}const r=t.direction==="backward"?-1:1,o=I=>r*(l-I),c=Math.min(M.LANE_M,u/Math.max(s,1)),h=s*c,d=u-h,_=(t.kind==="motorway"||t.kind==="dual")&&d>=M.SHOULDER_MIN_M,E=[{offsetM:o(i),pattern:t.kind==="motorway"||t.kind==="dual"?"solid":n}];E.push({offsetM:o(_?h:u-i),pattern:t.kind==="motorway"||t.kind==="dual"?"solid":n});for(let I=1;I<s;I++)E.push({offsetM:o(I*c),pattern:"dashed"});return E}static lay(t,u,l,i){let e=0;const s=(f,n)=>{i(f,M.LINE_WIDTH_M,{offsetM:n,liftM:M.LIFT_M,minHalfM:M.LINE_WIDTH_M/2}),e++};for(const f of t){const n=M.plan(f);if(n.length===0)continue;const r=f.points.map(o=>D(o.lat,o.lng,u,l));for(const o of M.near(r,M.PAINT_RADIUS_M))for(const c of n){if(c.pattern==="solid"){s(o,c.offsetM);continue}const[h,d]=c.pattern==="dashed"?[M.DASH_M,M.GAP_M]:[M.RIVE_DASH_M,M.RIVE_GAP_M];for(const _ of c.solidInBends?M.byBends(o):[{path:o,bend:!1}])if(_.bend)s(_.path,c.offsetM);else for(const E of M.dashes(_.path,h,d))s(E,c.offsetM)}}return e}static byBends(t){if(t.length<3)return[{path:t,bend:!1}];const u=(e,s)=>Math.atan2(s.x-e.x,-(s.z-e.z)),l=new Array(t.length-1).fill(!1);for(let e=1;e<t.length-1;e++){const s=Math.hypot(t[e].x-t[e-1].x,t[e].z-t[e-1].z),f=Math.hypot(t[e+1].x-t[e].x,t[e+1].z-t[e].z);if(s<1e-6||f<1e-6)continue;let n=Math.abs(u(t[e],t[e+1])-u(t[e-1],t[e]));n>Math.PI&&(n=2*Math.PI-n),(s+f)/2/Math.max(n,1e-6)<M.BEND_RADIUS_M&&(l[e-1]=!0,l[e]=!0)}const i=[];for(let e=0;e<l.length;e++){const s=i.at(-1);s&&s.bend===l[e]?s.path.push(t[e+1]):i.push({path:[t[e],t[e+1]],bend:l[e]})}return i}static barriers(t,u,l){const i=t.filter(n=>(n.kind==="motorway"||n.kind==="dual")&&(n.direction==="forward"||n.direction==="backward")),e=i.map(n=>{const r=n.points.map(o=>D(o.lat,o.lng,u,l));return n.direction==="backward"?[...r].reverse():r}),s=[],f=M.BARRIER_STEP_M;return i.forEach((n,r)=>{for(const o of M.near(e[r],M.PAINT_RADIUS_M)){let c=[];for(const h of M.resample(o,f)){const d=M.midwayToOpposite(h,r,i,e);d?c.push(d):c.length>0&&(c.length>=2&&s.push(c),c=[])}c.length>=2&&s.push(c)}}),s}static midwayToOpposite(t,u,l,i){let e;for(let s=0;s<i.length;s++){if(s===u||l[s].id<l[u].id)continue;const f=i[s];for(let n=0;n<f.length-1;n++){const r=f[n+1].x-f[n].x,o=f[n+1].z-f[n].z,c=r*r+o*o;if(c<1e-9||(r*t.dx+o*t.dz)/Math.sqrt(c)>-.9)continue;const h=Math.max(0,Math.min(1,((t.x-f[n].x)*r+(t.z-f[n].z)*o)/c)),d={x:f[n].x+r*h,z:f[n].z+o*h},_=Math.hypot(t.x-d.x,t.z-d.z);_<M.BARRIER_MIN_AXES_M||_>M.BARRIER_MAX_AXES_M||(!e||_<e.distance)&&(e={distance:_,point:d})}}return e?{x:(t.x+e.point.x)/2,z:(t.z+e.point.z)/2}:void 0}static resample(t,u){const l=[];for(let e=0;e<t.length-1;e++){const s=Math.hypot(t[e+1].x-t[e].x,t[e+1].z-t[e].z);if(s<1e-6)continue;const f=(t[e+1].x-t[e].x)/s,n=(t[e+1].z-t[e].z)/s;for(let r=0;r<s;r+=u)l.push({x:t[e].x+f*r,z:t[e].z+n*r,dx:f,dz:n})}const i=l.at(-1);return i&&l.push({...t[t.length-1],dx:i.dx,dz:i.dz}),l}static barrierGeometry(t,u){const l=[],i=[],e=M.BARRIER_HALF_WIDTH_M;for(const s of t){if(s.length<2)continue;const f=l.length/3;for(let n=0;n<s.length;n++){const r=s[Math.max(0,n-1)],o=s[Math.min(s.length-1,n+1)],c=Math.hypot(o.x-r.x,o.z-r.z)||1,h=-(o.z-r.z)/c*e,d=(o.x-r.x)/c*e,_=u(s[n].x,s[n].z);l.push(s[n].x+h,_,s[n].z+d,s[n].x+h,_+M.BARRIER_HEIGHT_M,s[n].z+d,s[n].x-h,_+M.BARRIER_HEIGHT_M,s[n].z-d,s[n].x-h,_,s[n].z-d)}for(let n=0;n<s.length-1;n++){const r=f+n*4,o=r+4;for(let c=0;c<3;c++)i.push(r+c,o+c,r+c+1,r+c+1,o+c,o+c+1)}}return{positions:l,indices:i}}static near(t,u){const l=[];let i;for(let e=0;e<t.length-1;e++)Math.min(Math.hypot(t[e].x,t[e].z),Math.hypot(t[e+1].x,t[e+1].z))<=u||Math.hypot((t[e].x+t[e+1].x)/2,(t[e].z+t[e+1].z)/2)<=u?(i||(i=[t[e]]),i.push(t[e+1])):i&&(l.push(i),i=void 0);return i&&l.push(i),l}static dashes(t,u,l){const i=[],e=u+l;let s=0,f;const n=(o,c)=>({x:t[o].x+(t[o+1].x-t[o].x)*c,z:t[o].z+(t[o+1].z-t[o].z)*c});for(let o=0;o<t.length-1;o++){const c=Math.hypot(t[o+1].x-t[o].x,t[o+1].z-t[o].z);if(c<1e-6)continue;let h=0;for(;h<c;){const d=(s+h)%e,_=d<u,E=_?u-d:e-d,I=Math.min(c,h+E);_&&(f||(f=[n(o,h/c)]),f.push(n(o,I/c)),I-h>=E-1e-9&&(i.push(f),f=void 0)),h=I}s+=c}f&&f.length>=2&&i.push(f);const r=o=>o.slice(1).reduce((c,h,d)=>c+Math.hypot(h.x-o[d].x,h.z-o[d].z),0);return i.filter(o=>o.length>=2&&r(o)>=M.MIN_DASH_M)}}export{M as RoadMarkings};
@@ -1,4 +1,4 @@
1
- const e={basisTitle:"Cómo se obtuvo este valor",basisField:"Base",howField:"Cómo",rationaleField:"Justificación",basisReset:"Base devuelta a «declarada»: el valor se volvió a escribir (era: {basis}).",lawSources:"Fuentes: {sources}",extrapolation:"Estas leyes se midieron en observaciones nocturnas: aplicarlas a una observación diurna es una extrapolación.",sectionAccountConditions:"Condiciones del testimonio",sectionViewing:"Condiciones de visión",sectionMoon:"Calibre Luna",sectionErrorParameters:"Parámetros de error de este testigo",errorModelField:"Modelo de error",delayField:"Plazo antes de este relato",delayUnit:"días",versionField:"Versión",wordField:"Palabra empleada",exposureField:"Exposición",independenceField:"Independencia",independentYes:"sin haber hablado con los otros testigos",independentNo:"tras hablar con los otros testigos",unknownChoice:"no se sabe",darkAdapted:"Adaptado a la oscuridad",correctedVision:"Vista corregida (gafas, lentillas)",glare:"Luz intensa justo antes",viewingNote:"El cristal o el parabrisas y los prismáticos se ajustan en «Visión» más arriba.",moonIntro:"El testigo muestra la Luna llena como mostró el objeto. La razón da su propio factor de tamaño angular: la única medida que hace individual la ley general. A tomar una vez por testigo.",moonField:"Luna tal como se muestra",moonHint:"Con el brazo extendido, 1 cm ≈ 1°",moonNone:"Aún no medida: se aplica la ley general ({law}).",moonFactor:"Factor ×{k}: sustituye a la ley general en todos los tamaños angulares de este testigo.",moonReal:"Luna real · 0,52°",parametersIntro:"Los errores de un mismo testigo están ligados: el mismo factor en todos sus tamaños, el mismo desfase en todas sus horas. Calculados, nunca escritos: ningún margen «±» se introduce a mano.",showMore:"Mostrar",basis:{measured:"◆ medida",stated:"declarada",derived:"deducida",assumed:"supuesta"},how:{clock:"leída en un reloj",anchored:"anclada a un evento fechado",estimated:"estimada","shown-on-the-spot":"mostrada en el lugar","arm-length-gauge":"calibre con el brazo extendido","recalled-object":"referida a un objeto conocido",instrument:"instrumento",landmark:"referencia del paisaje",occlusion:"ocultación","known-size":"tamaño conocido",triangulation:"triangulación"},status:{consolidated:"consolidada",individual:"individual",single:"estudio único",argued:"argumentada",contested:"discutida"},laws:{timeClock:"± {minutes} min (leída en un reloj)",timeAnchored:"± {minutes} min (anclada a un evento fechado)",timeEstimated:"± {minutes} min para 9 testigos de 10 (estimada)",duration:"{low} a {high}: ×/÷{factor} (log-normal)",elevationOver:"{low} a {high}: altura sobreestimada",measuredInstrument:"± {degrees}° (medida)",measuredLandmark:"± {degrees}° (según una referencia)",headingCross:"{low} a {high}: ± {degrees}°, sentido a veces invertido",sizeMoon:"{low} a {high}: factor ×{factor} del calibre Luna",sizeShown:"{low} a {high}: mostrada en el lugar, +2 a +17 %",sizeRecalled:"{low} a {high}: referida a un objeto conocido, ~30 % demasiado grande",sizeArm:"{low} a {high}: sobreestimada ×{from} a ×{to} con el brazo extendido",sizeGauge:"{low} a {high}: calibre, ± {percent} %",distanceAnchored:"Anclada: su error sigue al de aquello de lo que se deduce",distanceNotData:"No es un dato: errónea en un factor {factor} o más sin anclaje"},parameterNames:{clock:"Desfase del reloj",date:"Desfase de fecha",heading:"Rotación del rumbo",elevation:"Compresión de alturas",size:"Factor de tamaño angular",duration:"Factor de duración"},parameterScopes:{clock:"todas las horas",date:"la fecha",heading:"todos los rumbos",elevation:"todas las alturas",size:"todos los tamaños angulares",duration:"la duración y la escala de la línea de tiempo"},parameters:{clockClock:"± {minutes} min (reloj)",clockAnchored:"± {minutes} min (anclada)",clockEstimated:"± {minutes} min (estimada)",dateShift:"±1 día, mes o año; horario de verano ({percent} % de las fechas)",headingRotation:"± {degrees}°, espejo posible",elevationCompression:"sobreestimadas, nunca subestimadas",sizeMoon:"×{factor} (calibre Luna)",sizeArm:"×{from} a ×{to} con el brazo extendido",sizeShown:"+2 a +17 % mostrada en el lugar",durationFactor:"×/÷{factor} (log-normal)"},words:{light:"luz",object:"objeto",craft:"aparato",ufo:"ovni",other:"otra"},exposures:{none:"ninguna",press:"prensa",broadcast:"radio, televisión","other-witnesses":"otros testigos",investigators:"investigadores"},models:{consolidated:"Lecturas consolidadas (UFO@home)",keul:"Keul (tamaños mostrados en el lugar)"},candidates:{title:"Explicaciones candidatas",verdicts:{consolidated:"Compatible con sesgos consolidados",contested:"Solo invocando sesgos discutidos",incompatible:"Incompatible incluso en los límites"},cost:"coste {x} σ",seeVariant:"Ver esta variante",variantLabel:"Variante: {name}",backToAccount:"Volver al relato",costNote:"Un coste no es una probabilidad: dice qué sesgos hay que invocar, y cuánto.",scopeNote:"Matriz de Poher (1977) para la lista de partida; la Luna, los planetas, los pasos de satélites, las reentradas, los bólidos registrados y los aviones del registro se calculan y se confrontan con el relato.",fields:{time:"Hora",heading:"Acimut",elevation:"Altura sobre el horizonte",size:"Anchura aparente",duration:"Duración",sweep:"Recorrido en el cielo"},biases:{clock:"desfase del reloj {x} min",heading:"rotación del rumbo {x}°",elevation:"altura sobreestimada ×{x}",size:"tamaño sobreestimado ×{x}",duration:"duración mal estimada ×{x}",sweep:"recorrido mal estimado {x}°"},reasons:{"never-seen":"Nunca visible desde este lugar en el margen de la hora dada.",direction:"Demasiado lejos de la dirección dada, incluso en los límites de las leyes.",size:"Tamaño demasiado distinto, incluso en los límites de las leyes.",motion:"No se mueve como dice el relato, y nada hace leer uno por otro.",growth:"No crece como dice el relato, ni se vuelve más brillante."},readingTitle:"Lectura",readings:{grouped:"luces leídas como un solo cuerpo","still-seen-moving":"una luz fija vista en movimiento (efecto autocinético, nubes que pasan)","moving-seen-still":"una luz en movimiento vista inmóvil (llegando de frente)","brightening-as-approach":"un brillo creciente leído como un acercamiento"},notAgainst:"No es un argumento contra el relato.",noDirection:"El relato no sitúa el fenómeno en el cielo: no hay nada que confrontar.",noSky:"Sin fecha, hora y lugar, ningún astro puede situarse.",bodies:{Moon:"Luna",Venus:"Venus",Mars:"Marte",Jupiter:"Júpiter",Saturn:"Saturno"},aircraft:"Aeronave {name}",interpretation:"Interpretación: {name}",keepVariant:"Guardar esta variante",keptTitle:"Variantes guardadas ({n})",seeKept:"Ver",removeVariant:"Quitar",keptRecomputed:"calculada de nuevo: {verdict}, coste {x} σ",keptModel:"modelo: {model}",reentry:"Reentrada {name}",star:"Estrella {name}",fireball:"Bólido {name}",poherLoading:"Cargando la matriz de Poher…",poherFailed:"No se pudo leer la matriz de Poher: solo se proponen los astros.",poherTitle:"Según la matriz de Poher (1977)",poherIntro:"Los demás fenómenos conocidos de la matriz, sin trayectoria calculada aquí: su nota es el juicio de Poher, a confrontar a mano.",poherPossible:"Posibles",poherExcluded:"Excluidos ({n})",poherExcludedBy:"excluido por: {list}",poherDoubted:"dudoso según estimaciones: {list}",poherScore:"nota {x}",poherTag:"Poher: {state}",poherStatePossible:"posible, nota {x}",poherStateExcluded:"excluido",answersTitle:"Respuestas deducidas de la grabación ({n})",firm:"firme",soft:"estimación, no excluye nada por sí sola"}},a={color:"Color",transparency:"Transparencia",halo:"Halo",blur:"Desenfoque",brightness:"Brillo",shapeTitle:"Nombre",utcOffset:"Zona horaria",cloudBase:"Base de las nubes",elevation:"Altitud",heightAboveGround:"Altura sobre el suelo",duration:"Duración",placeName:"Lugar",latitude:"Latitud",longitude:"Longitud",heading:"Orientación",pitch:"Inclinación",roll:"Alabeo",sway:"Vaivén en reposo",swayTitle:"Cuánto mueve la vista el cuerpo que sostiene el instrumento cuando no camina: 1 para una persona de pie, 0 para un trípode. Vacío: 1 (0 si el relato dice que el observador estaba paralizado)",lightPollution:"Cielo nocturno",lightPollutionTitle:"El cenit de una noche sin Luna en este lugar, en magnitudes por segundo de arco cuadrado, tal como lo lee un Sky Quality Meter o lo da el atlas mundial del brillo artificial del cielo (Falchi et al. 2016, lightpollutionmap.info, «SQM»). 22 es un cielo natural; un suburbio ronda 19, el centro de una ciudad 17. Vacío: un cielo natural",observationTime:"Inicio de la observación",observationEndTime:"Fin de la observación",assessmentGroup:"Evaluación",assessmentNames:{coverage:"Datos del observador"},assessmentVerdicts:{"hynek.nl":"NL — luz nocturna","hynek.dd":"DD — disco diurno","hynek.rv":"RV — radar-visual","hynek.ce1":"EC1 — encuentro cercano","hynek.ce2":"EC2 — con huella física","hynek.ce3":"EC3 — con entidades"},timeQualifierApproximate:"Aproximada",timeQualifierUncertain:"Incierta",timeQualifierBoth:"Incierta y aproximada",timeSeasonSpring:"Primavera",timeSeasonSummer:"Verano",timeSeasonAutumn:"Otoño",timeSeasonWinter:"Invierno",summaryGroups:{observation:"Observación",location:"Posición",temporal:"Tiempo",weather:"Clima",sound:"Sonido"},summaryGroup:"Resumen",summaryDescription:"Descripción",closeDescription:"Cerrar la descripción",observerGroup:"Observador",observerId:"ID",observerTitle:"Tratamiento",observerLastName:"Apellidos",observerFirstNames:"Nombres",sightingId:"ID",tags:"Etiquetas",cloudCover:"Nubosidad",highCloudCover:"Nubes de hielo (cirros)",focalLength:"Distancia focal",fieldOfView:"Campo de visión",aperture:"Apertura",exposure:"Exposición",focusDistance:"Enfocado a",iceCrystalAlignment:"Alineación de los cristales",rareIceCrystals:"Cristales raros",cloudDarkness:"Oscuridad de las nubes",precipitationType:"Precipitación",precipitationNone:"Ninguna",precipitationRain:"Lluvia",precipitationSnow:"Nieve",precipitationHail:"Granizo",precipitationIntensity:"Intensidad",windDirection:"Dirección del viento",windSpeed:"Velocidad del viento",storm:"Tormenta",cloudsGroup:"Nubes",precipitationGroup:"Precipitación",windGroup:"Viento",soundKind:"Sonido",soundNone:"Ninguno (silencioso)",soundHum:"Zumbido",soundWhistle:"Silbido",soundRumble:"Retumbo",soundCrackle:"Crepitación",soundVolume:"Volumen",soundPitch:"Tono",soundSrc:"Grabación",instrument:"Observado con",filter:"Delante",filterNone:"nada",filterSolarVisual:"un filtro solar para el ojo (gafas de eclipse, densidad 5)",filterSolarPhoto:"una película solar para cámara (densidad 3,8)",decorAircraft:"Aeronave",decorEntity:"Ser",decorAltitude:"Altitud",decorLights:"Luces",decorLightsNone:"ninguna",decor:"Entorno",decorBuilding:"Edificio",decorTree:"Árbol",decorShrub:"Arbusto",decorBridge:"Puente",decorWire:"Línea aérea",decorCrop:"Hilera de cultivo",decorTerrace:"Bancal (terraza)",decorMound:"Montón de piedras",decorWall:"Muro de piedra seca",decorStreetlight:"Farola",decorTrafficLight:"Semáforo",decorCrossing:"Paso de peatones",decorRoadSign:"Señal de tráfico",decorVehicle:"Vehículo",decorObserver:"Otro observador",decorEast:"Distancia al este",decorNorth:"Distancia al norte",decorHeading:"Orientación",decorColor:"Color",decorLit:"Encendido",decorTitle:"Nombre",milestones:"Momentos",milestoneLabel:"Etiqueta",milestoneNote:"Qué ocurre",addMilestone:"Nombrar este momento",deleteMilestone:"Eliminar momento",goToMilestone:"Ir a este momento",decorWidth:"Anchura",decorLength:"Longitud",decorHeight:"Altura",decorModel:"Modelo 3D",decorModelAdvanced:"Modelo desde una dirección",decorModelUrl:"Dirección glTF/GLB",decorModelTitle:"Nombre del modelo",decorModelAuthor:"Autor",decorModelLicense:"Licencia",decorModelSource:"Procedencia",decorObserverSide:"Ubicación del observador",decorObserverSideNone:"No presente",decorSideFront:"Delante",decorSideBehind:"Detrás",decorSideLeft:"Izquierda",decorSideRight:"Derecha",decorSideFrontLeft:"Delante a la izquierda",decorSideFrontRight:"Delante a la derecha",decorSideBehindLeft:"Detrás a la izquierda",decorSideBehindRight:"Detrás a la derecha"},o={...a,oval:"Óvalo",polygon:"Polígono",modelTriangle:"Triángulo",modelSaucer:"Platillo",modelCigar:"Cigarro",modelChevron:"Chevrón",modelLight:"Luz",modelPartDisc:"disco",modelPartDome:"cúpula",addVertex:"Añadir vértice",deleteVertex:"Eliminar vértice",notAPolygon:"Selecciona una sola forma poligonal",tooFewVertices:"Una forma necesita al menos 3 puntos",shape:"Forma",apparentWidth:"Anchura aparente",realWidth:"Anchura real",distance:"Distancia",sizeLock:"Mantener",lockNone:"nada",lockApparent:"la anchura aparente",lockReal:"la anchura real",lockDistance:"la distancia",apparentSize:"{moons}× la Luna",utcOffsetPlaceholder:"según la longitud",realSizeBetween:"La escena acota esa anchura: entre {min} y {max} m, según lo que cruza",realSizeAtLeast:"La escena acota esa anchura: al menos {min} m, según lo que pasa por detrás",realSizeAtMost:"La escena acota esa anchura: como mucho {max} m, según lo que pasa por delante",realSizeUnknown:"Nada en la escena acota esa anchura: solo es lo que implica la distancia",realSizeOutside:" — la anchura de arriba queda fuera",blurBound:"Desenfocado a través de este objetivo: más cerca de {max} m",blurBoundNoInstrument:"El desenfoque a simple vista no acota ninguna distancia — un ojo no tiene diafragma con el que desenfocar",blurBoundNotAtInfinity:"El desenfoque solo acota una distancia con un objetivo enfocado al infinito: este está enfocado a {focus} m, lo que desenfoca a ambos lados",realSizeContradiction:"Contradicción: los cruces declarados no pueden ser todos ciertos",realDistanceHere:" — es decir, aquí entre {min} y {max} m",clearDistanceHypothesis:"Retirar la hipótesis de distancia",clearDistanceHypothesisTitle:"Volver a lo que establece la grabación",depthStated:"Dibujado a {m} m, como indica la grabación",depthHypothesis:"Dibujado a {m} m: tu hipótesis — lo que el decorado oculta de él es lo que perdería algo tan lejano",depthDerived:"Dibujado a {m} m, según la propia marcha del observador",depthBounded:"Dibujado a {m} m, según lo que pasa por detrás y por delante",depthConventional:"Dibujado a {m} m por convención: nada en la grabación establece una distancia",addShape:"Añadir forma",deleteShape:"Eliminar forma",play:"Reproducir",pause:"Pausa",noDuration:"Sin duración de observación",previousKeyframe:"Fotograma clave anterior",nextKeyframe:"Fotograma clave siguiente",keyframeMark:"Fotograma clave",shapesTrack:"formas",observerTrack:"observador",weatherTrack:"meteorología",soundTrack:"sonido",group:"Agrupar",ungroup:"Desagrupar",duplicate:"Duplicar",bringToFront:"Traer al frente",sendToBack:"Enviar al fondo",contextMenuDelete:"Eliminar",onlyOneShape:"Solo hay una forma",alreadyAtFront:"Esta forma ya está al frente",alreadyAtBack:"Esta forma ya está al fondo",needTwoShapesToGroup:"Selecciona al menos dos formas para agruparlas",notGrouped:"Esta forma no forma parte de un grupo",multipleShapesSelected:"Varias formas seleccionadas — selecciona solo una para editar esto",samplingRate:"Frecuencia de muestreo",currentPosition:"Posición actual",switchToElapsed:"haz clic para mostrar el tiempo transcurrido",switchToClockTime:"haz clic para mostrar la hora del día",durationPlaceholder:"duración de la observación",durationImprecise:"Estas fechas no determinan con suficiente precisión una duración — introdúcela manualmente.",export:"Guardar",frozenTitle:"Datos congelados",frozenHint:"Lo que la escena pediría si no a terceros en cada reproducción (relieve, foto del suelo, carreteras, agua, vías férreas, atmósfera alta), tomado al guardar y conservado en archivos junto a la grabación, para que se reproduzca igual sin pedir nada a nadie.",frozenLive:"Consultar las fuentes en cada reproducción (no congelar nada)",frozenRelief:"Relieve",frozenImagery:"Foto del suelo",frozenRoads:"Carreteras",frozenWater:"Agua",frozenRailways:"Vías férreas",frozenUpperAir:"Atmósfera alta",frozenMap:"Foto del mapa",fileItemRecording:"Registro",fileItemWeather:"Tiempo",fileItemPlace:"Lugar",fileItemLightPollution:"Contaminación lumínica",fileItemDecor:"Decorado",weatherStatedByObserver:"tal como la dio el observador",annexSource:"Archivo",annexStorageInline:"dentro del propio registro",annexEditorLabel:"El anexo como texto JSON",annexUnreadable:"Este archivo no es un anexo de este tipo: no se ha cambiado nada.",frozenTakenOn:"tomado el {date}",frozenPending:"se toma al guardar",frozenLiveState:"consultado en directo",frozenRefresh:"Actualizar",frozenTaking:"Tomando: {group}… {done}/{of}",frozenFailed:"{group}: la fuente no respondió, no se tomó nada.",frozenMissing:"{file} no se pudo leer: queda fuera del archivo.",frozenExported:"Guardado con {count} anexos ({size}), en un archivo que descomprimir donde se publica la grabación.",identityHint:"Nombra a alguien solo si lo consintió o publicó él mismo su testimonio; si no, bastan unas iniciales.",identityAsk:"Esta grabación nombra o sitúa a una persona. Respondes de lo que publicas: nombra a alguien solo si lo consintió, o si publicó él mismo su testimonio. Si no, bastan unas iniciales.",identityLastName:"Apellidos: {value}",identityFirstNames:"Nombre: {value}",identityNameInId:"Nombre en un identificador: {value}",identityNameInText:"Nombre en el texto: {value}",identityAddress:"Dirección: {value}",identityEmail:"Correo electrónico: {value}",identityPhone:"Número de teléfono: {value}",identityInitials:"Sustituir por iniciales",identityKeep:"Guardar de todos modos",identityBack:"Volver",importFile:"Cargar un archivo de observación",paramSummaryTitle:"Lo que indica la grabación ({n})",wholeKeyframes:"Cada fotograma clave entero",wholeKeyframesHint:"Si no, un fotograma clave solo dice lo que cambió desde el anterior, como se escribe una grabación a mano. Cambiar esto vacía el historial de deshacer.",importUrlPlaceholder:"https://…/sighting.json",inputFileFromFile:"desde su disco",inputFileFromUrl:"desde una dirección",inputFileModeHint:"Pulsar para darlo de la otra forma: un archivo suyo o una dirección",inputFileLoad:"Cargar",importError:"No se pudo cargar esa grabación — revisa el archivo o la URL e inténtalo de nuevo.",importErrorCors:'Esa dirección responde, pero el navegador no tiene permiso para leerla desde esta página. El archivo en sí está bien: su servidor debe enviarlo con la cabecera "Access-Control-Allow-Origin: *". Si no es tu servidor, eso es lo que debes pedir a su administrador.',importErrorMixedContent:"Una página segura no puede cargar una dirección que empiece por http://. Usa https:// si ese servidor lo ofrece.",importErrorUnreachable:"Nada respondió en esa dirección. Revisa cómo está escrita y que tengas conexión.",importErrorStatus:"Ese servidor respondió con un error ({status}). Probablemente la dirección es incorrecta o el archivo se ha movido.",importErrorMalformed:"Esa dirección respondió, pero lo que devolvió no es una grabación.",record:"Grabar",stop:"Detener",according:"según",sourceElevation:"Relieve",sourceImagery:"Imágenes",sourceAircraft:"Tráfico aéreo",sourceFile:"Archivo",sourceKind:"Datos",placeNamePlaceholder:"Valensole, Francia",searchPlace:"Localizar",placeMatch:"Coincidencias",placeSearching:"Buscando el lugar…",placeMatchFound:"lugar encontrado",placeMatchesFound:"lugares encontrados",placeNotFound:"Ningún lugar con ese nombre",placeSearchFailed:"Búsqueda de lugares no disponible por el momento",utcOffsetNoZone:"Ninguna zona horaria marca este desfase ese día",utcOffsetImplausible:"Esta zona horaria no corresponde a la longitud introducida, cuya hora solar es UTC{solar} — aun así, la escena y el registro meteorológico obedecen a lo que se declara aquí. Vacía el campo para deducirla de la longitud.",timeZoneManual:"desconocido",altitudeAboveSeaLevel:"Altitud del observador sobre el nivel del mar — el suelo del lugar introducido fija su mínimo: un observador en los Alpes no está a 0 m.",groundAt:"suelo a {m} m",headingPlaceholder:"desconocido",edtfInvalid:"Fecha/hora EDTF no válida (p. ej. 1965-07-01T05:00, 2025-06?, 2025~).",edtfPlaceholder:"AAAA-MM-DDThh:mm[?~%] o hh:mm",observationTimeHint:"EDTF — p. ej. 1965-07-01T05:00, 2025-06? (incierto), 2025~ (aproximado), o solo 05:00 si no se conoce la fecha",observationEndTimeHint:"EDTF — p. ej. 1965-07-01T05:10, 2025-06? (incierto), 2025~ (aproximado), o solo 05:10 si no se conoce la fecha",presetsGroupLabel:"Forma del ovni",description:"Descripción",tagsPlaceholder:"separadas por comas",weather:"Meteorología",shapeGroup:"Fenómeno",shapesSubgroup:"Formas",bodiesSubgroup:"Cuerpos",launchesSubgroup:"Lanzamientos",sky:"Cielo",dataSources:"Fuentes de datos",beamsSubgroup:"Haces",soundGroup:"Sonido",close:"Cerrar",editorKicker:"Editor de avistamiento",witnessError:e,expandTracks:"Desplegar las pistas",collapseTracks:"Plegar las pistas",addKeyframeHere:"Añadir un fotograma clave aquí",keyframeHere:"Aquí hay un fotograma clave: editarlo lo modifica",sceneLinkedLegend:"Campo visible en la escena: haga clic en un elemento de la escena para editarlo.",overlayHorizon:"Horizonte · inclinación {deg}°",overlayFov:"Campo de visión {v}° × {h}°",overlayWind:"Sopla hacia {point} · {speed} m/s",sectionIdentity:"Identidad",sectionProfile:"Perfil",sectionTestimony:"Testimonio",sectionVision:"Visión",sectionPerception:"Percepción",sectionCoordinates:"Coordenadas",sectionGaze:"Mirada",sectionNightSky:"Cielo nocturno",sectionPeriod:"Periodo",sectionSurvey:"Decorado tomado de un levantamiento",sectionHeard:"Lo que se oyó",sectionSoundRecording:"Grabación",sectionPicture:"Foto",sectionPose:"Pose",sectionAppearance:"Apariencia",sectionSize:"Tamaño y distancia",sectionPath:"Trayectoria",moreSettings:"Más ajustes ({n})",lessSettings:"Menos ajustes",showSettings:"Mostrar ({n} ajustes)",showSettingsOne:"Mostrar (1 ajuste)",showSettingsAny:"Mostrar",hideSettings:"Ocultar",save:"Guardar",stateNew:"Nueva grabación",panelIntroObservation:"Lo que pasó, en pocas líneas, y lo que queda por completar.",panelIntroObserver:"Quién lo vio, y con qué. Cada observador tiene su propio punto de vista en la escena.",panelIntroLocation:"Desde dónde miraba el observador, y en qué dirección.",panelIntroTemporal:"Cuándo empezó, cuánto duró, y los momentos del relato.",panelIntroDecor:"Lo que rodeaba al observador: vehículos, edificios, vegetación.",panelIntroWeather:"El tiempo que hacía, y lo que cruzaba el cielo sin ser el fenómeno: lanzamientos, haces.",panelIntroShape:"Lo que se vio, dibujado en la escena desde el punto de vista del observador.",panelIntroSound:"Lo que se oyó.",panelIntroReference:"Fotos y croquis, ajustados a la escena para compararla.",panelIntroFile:"La grabación completa, para guardar o volver a cargar.",temporalGroup:"Fecha",fileGroup:"Archivos",fileLoading:"Cargando el editor…",weatherEditedAsk:"Esta edición toca el tiempo, un dato leído dinámicamente desde {source}. ¿Quieres dejar de leerlo desde {source}?",weatherEditedStop:"Dejar de leerlo",weatherEditedKeep:"Seguir leyéndolo (deshacer mi edición)",fileEditorLabel:"El registro como texto JSON",fileSearchPhrases:{Find:"Buscar",Replace:"Reemplazar",next:"siguiente",previous:"anterior",all:"todo","match case":"mayúsculas","by word":"palabra completa",regexp:"regexp",replace:"reemplazar","replace all":"reemplazar todo",close:"cerrar","current match":"coincidencia actual","on line":"en la línea","replaced $ matches":"$ coincidencias reemplazadas","replaced match on line $":"coincidencia reemplazada en la línea $","Go to line":"Ir a la línea",go:"ir"},timeQualifierExact:"Exacta",durationPrecise:"precisa",durationIso:"imprecisa",durationModeTitle:"Decir la duración exactamente, o como se enunció: aproximadamente, entre dos duraciones",durationInvalid:"No es una duración: PT10M, PT1H30M, PT5M/PT10M…",durationHelpTitle:"Lo que acepta el texto (EDTF)",durationHelpLines:`PT45S — 45 segundos
1
+ const e={basisTitle:"Cómo se obtuvo este valor",basisField:"Base",howField:"Cómo",rationaleField:"Justificación",basisReset:"Base devuelta a «declarada»: el valor se volvió a escribir (era: {basis}).",lawSources:"Fuentes: {sources}",extrapolation:"Estas leyes se midieron en observaciones nocturnas: aplicarlas a una observación diurna es una extrapolación.",sectionAccountConditions:"Condiciones del testimonio",sectionViewing:"Condiciones de visión",sectionMoon:"Calibre Luna",sectionErrorParameters:"Parámetros de error de este testigo",errorModelField:"Modelo de error",delayField:"Plazo antes de este relato",delayUnit:"días",versionField:"Versión",wordField:"Palabra empleada",exposureField:"Exposición",independenceField:"Independencia",independentYes:"sin haber hablado con los otros testigos",independentNo:"tras hablar con los otros testigos",unknownChoice:"no se sabe",darkAdapted:"Adaptado a la oscuridad",correctedVision:"Vista corregida (gafas, lentillas)",glare:"Luz intensa justo antes",viewingNote:"El cristal o el parabrisas y los prismáticos se ajustan en «Visión» más arriba.",moonIntro:"El testigo muestra la Luna llena como mostró el objeto. La razón da su propio factor de tamaño angular: la única medida que hace individual la ley general. A tomar una vez por testigo.",moonField:"Luna tal como se muestra",moonHint:"Con el brazo extendido, 1 cm ≈ 1°",moonNone:"Aún no medida: se aplica la ley general ({law}).",moonFactor:"Factor ×{k}: sustituye a la ley general en todos los tamaños angulares de este testigo.",moonReal:"Luna real · 0,52°",parametersIntro:"Los errores de un mismo testigo están ligados: el mismo factor en todos sus tamaños, el mismo desfase en todas sus horas. Calculados, nunca escritos: ningún margen «±» se introduce a mano.",showMore:"Mostrar",basis:{measured:"◆ medida",stated:"declarada",derived:"deducida",assumed:"supuesta"},how:{clock:"leída en un reloj",anchored:"anclada a un evento fechado",estimated:"estimada","shown-on-the-spot":"mostrada en el lugar","arm-length-gauge":"calibre con el brazo extendido","recalled-object":"referida a un objeto conocido",instrument:"instrumento",landmark:"referencia del paisaje",occlusion:"ocultación","known-size":"tamaño conocido",triangulation:"triangulación"},status:{consolidated:"consolidada",individual:"individual",single:"estudio único",argued:"argumentada",contested:"discutida"},laws:{timeClock:"± {minutes} min (leída en un reloj)",timeAnchored:"± {minutes} min (anclada a un evento fechado)",timeEstimated:"± {minutes} min para 9 testigos de 10 (estimada)",duration:"{low} a {high}: ×/÷{factor} (log-normal)",elevationOver:"{low} a {high}: altura sobreestimada",measuredInstrument:"± {degrees}° (medida)",measuredLandmark:"± {degrees}° (según una referencia)",headingCross:"{low} a {high}: ± {degrees}°, sentido a veces invertido",sizeMoon:"{low} a {high}: factor ×{factor} del calibre Luna",sizeShown:"{low} a {high}: mostrada en el lugar, +2 a +17 %",sizeRecalled:"{low} a {high}: referida a un objeto conocido, ~30 % demasiado grande",sizeArm:"{low} a {high}: sobreestimada ×{from} a ×{to} con el brazo extendido",sizeGauge:"{low} a {high}: calibre, ± {percent} %",distanceAnchored:"Anclada: su error sigue al de aquello de lo que se deduce",distanceNotData:"No es un dato: errónea en un factor {factor} o más sin anclaje"},parameterNames:{clock:"Desfase del reloj",date:"Desfase de fecha",heading:"Rotación del rumbo",elevation:"Compresión de alturas",size:"Factor de tamaño angular",duration:"Factor de duración"},parameterScopes:{clock:"todas las horas",date:"la fecha",heading:"todos los rumbos",elevation:"todas las alturas",size:"todos los tamaños angulares",duration:"la duración y la escala de la línea de tiempo"},parameters:{clockClock:"± {minutes} min (reloj)",clockAnchored:"± {minutes} min (anclada)",clockEstimated:"± {minutes} min (estimada)",dateShift:"±1 día, mes o año; horario de verano ({percent} % de las fechas)",headingRotation:"± {degrees}°, espejo posible",elevationCompression:"sobreestimadas, nunca subestimadas",sizeMoon:"×{factor} (calibre Luna)",sizeArm:"×{from} a ×{to} con el brazo extendido",sizeShown:"+2 a +17 % mostrada en el lugar",durationFactor:"×/÷{factor} (log-normal)"},words:{light:"luz",object:"objeto",craft:"aparato",ufo:"ovni",other:"otra"},exposures:{none:"ninguna",press:"prensa",broadcast:"radio, televisión","other-witnesses":"otros testigos",investigators:"investigadores"},models:{consolidated:"Lecturas consolidadas (UFO@home)",keul:"Keul (tamaños mostrados en el lugar)"},candidates:{title:"Explicaciones candidatas",verdicts:{consolidated:"Compatible con sesgos consolidados",contested:"Solo invocando sesgos discutidos",incompatible:"Incompatible incluso en los límites"},cost:"coste {x} σ",seeVariant:"Ver esta variante",variantLabel:"Variante: {name}",backToAccount:"Volver al relato",costNote:"Un coste no es una probabilidad: dice qué sesgos hay que invocar, y cuánto.",scopeNote:"Matriz de Poher (1977) para la lista de partida; la Luna, los planetas, los pasos de satélites, las reentradas, los bólidos registrados y los aviones del registro se calculan y se confrontan con el relato.",fields:{time:"Hora",heading:"Acimut",elevation:"Altura sobre el horizonte",size:"Anchura aparente",duration:"Duración",sweep:"Recorrido en el cielo"},biases:{clock:"desfase del reloj {x} min",heading:"rotación del rumbo {x}°",elevation:"altura sobreestimada ×{x}",size:"tamaño sobreestimado ×{x}",duration:"duración mal estimada ×{x}",sweep:"recorrido mal estimado {x}°"},reasons:{"never-seen":"Nunca visible desde este lugar en el margen de la hora dada.",direction:"Demasiado lejos de la dirección dada, incluso en los límites de las leyes.",size:"Tamaño demasiado distinto, incluso en los límites de las leyes.",motion:"No se mueve como dice el relato, y nada hace leer uno por otro.",growth:"No crece como dice el relato, ni se vuelve más brillante."},readingTitle:"Lectura",readings:{grouped:"luces leídas como un solo cuerpo","still-seen-moving":"una luz fija vista en movimiento (efecto autocinético, nubes que pasan)","moving-seen-still":"una luz en movimiento vista inmóvil (llegando de frente)","brightening-as-approach":"un brillo creciente leído como un acercamiento"},notAgainst:"No es un argumento contra el relato.",noDirection:"El relato no sitúa el fenómeno en el cielo: no hay nada que confrontar.",noSky:"Sin fecha, hora y lugar, ningún astro puede situarse.",bodies:{Moon:"Luna",Venus:"Venus",Mars:"Marte",Jupiter:"Júpiter",Saturn:"Saturno"},aircraft:"Aeronave {name}",interpretation:"Interpretación: {name}",keepVariant:"Guardar esta variante",keptTitle:"Variantes guardadas ({n})",seeKept:"Ver",removeVariant:"Quitar",keptRecomputed:"calculada de nuevo: {verdict}, coste {x} σ",keptModel:"modelo: {model}",reentry:"Reentrada {name}",star:"Estrella {name}",fireball:"Bólido {name}",poherLoading:"Cargando la matriz de Poher…",poherFailed:"No se pudo leer la matriz de Poher: solo se proponen los astros.",poherTitle:"Según la matriz de Poher (1977)",poherIntro:"Los demás fenómenos conocidos de la matriz, sin trayectoria calculada aquí: su nota es el juicio de Poher, a confrontar a mano.",poherPossible:"Posibles",poherExcluded:"Excluidos ({n})",poherExcludedBy:"excluido por: {list}",poherDoubted:"dudoso según estimaciones: {list}",poherScore:"nota {x}",poherTag:"Poher: {state}",poherStatePossible:"posible, nota {x}",poherStateExcluded:"excluido",answersTitle:"Respuestas deducidas de la grabación ({n})",firm:"firme",soft:"estimación, no excluye nada por sí sola"}},a={color:"Color",transparency:"Transparencia",halo:"Halo",blur:"Desenfoque",brightness:"Brillo",shapeTitle:"Nombre",utcOffset:"Zona horaria",cloudBase:"Base de las nubes",elevation:"Altitud",heightAboveGround:"Altura sobre el suelo",duration:"Duración",placeName:"Lugar",latitude:"Latitud",longitude:"Longitud",heading:"Orientación",pitch:"Inclinación",roll:"Alabeo",sway:"Vaivén en reposo",swayTitle:"Cuánto mueve la vista el cuerpo que sostiene el instrumento cuando no camina: 1 para una persona de pie, 0 para un trípode. Vacío: 1 (0 si el relato dice que el observador estaba paralizado)",lightPollution:"Cielo nocturno",lightPollutionTitle:"El cenit de una noche sin Luna en este lugar, en magnitudes por segundo de arco cuadrado, tal como lo lee un Sky Quality Meter o lo da el atlas mundial del brillo artificial del cielo (Falchi et al. 2016, lightpollutionmap.info, «SQM»). 22 es un cielo natural; un suburbio ronda 19, el centro de una ciudad 17. Vacío: un cielo natural",observationTime:"Inicio de la observación",observationEndTime:"Fin de la observación",assessmentGroup:"Evaluación",assessmentNames:{coverage:"Datos del observador"},assessmentVerdicts:{"hynek.nl":"NL — luz nocturna","hynek.dd":"DD — disco diurno","hynek.rv":"RV — radar-visual","hynek.ce1":"EC1 — encuentro cercano","hynek.ce2":"EC2 — con huella física","hynek.ce3":"EC3 — con entidades"},timeQualifierApproximate:"Aproximada",timeQualifierUncertain:"Incierta",timeQualifierBoth:"Incierta y aproximada",timeSeasonSpring:"Primavera",timeSeasonSummer:"Verano",timeSeasonAutumn:"Otoño",timeSeasonWinter:"Invierno",summaryGroups:{observation:"Observación",location:"Posición",temporal:"Tiempo",weather:"Clima",sound:"Sonido"},summaryGroup:"Resumen",summaryDescription:"Descripción",closeDescription:"Cerrar la descripción",observerGroup:"Observador",observerId:"ID",observerTitle:"Tratamiento",observerLastName:"Apellidos",observerFirstNames:"Nombres",sightingId:"ID",tags:"Etiquetas",cloudCover:"Nubosidad",highCloudCover:"Nubes de hielo (cirros)",focalLength:"Distancia focal",fieldOfView:"Campo de visión",aperture:"Apertura",exposure:"Exposición",focusDistance:"Enfocado a",iceCrystalAlignment:"Alineación de los cristales",rareIceCrystals:"Cristales raros",cloudDarkness:"Oscuridad de las nubes",precipitationType:"Precipitación",precipitationNone:"Ninguna",precipitationRain:"Lluvia",precipitationSnow:"Nieve",precipitationHail:"Granizo",precipitationIntensity:"Intensidad",windDirection:"Dirección del viento",windSpeed:"Velocidad del viento",storm:"Tormenta",cloudsGroup:"Nubes",precipitationGroup:"Precipitación",windGroup:"Viento",soundKind:"Sonido",soundNone:"Ninguno (silencioso)",soundHum:"Zumbido",soundWhistle:"Silbido",soundRumble:"Retumbo",soundCrackle:"Crepitación",soundVolume:"Volumen",soundPitch:"Tono",soundSrc:"Grabación",instrument:"Observado con",filter:"Delante",filterNone:"nada",filterSolarVisual:"un filtro solar para el ojo (gafas de eclipse, densidad 5)",filterSolarPhoto:"una película solar para cámara (densidad 3,8)",decorAircraft:"Aeronave",decorEntity:"Ser",decorAltitude:"Altitud",decorLights:"Luces",decorLightsNone:"ninguna",decor:"Entorno",decorBuilding:"Edificio",decorTree:"Árbol",decorShrub:"Arbusto",decorBridge:"Puente",decorWire:"Línea aérea",decorCrop:"Hilera de cultivo",decorTerrace:"Bancal (terraza)",decorMound:"Montón de piedras",decorWall:"Muro de piedra seca",decorStreetlight:"Farola",decorTrafficLight:"Semáforo",decorCrossing:"Paso de peatones",decorRoadSign:"Señal de tráfico",decorVehicle:"Vehículo",decorObserver:"Otro observador",decorEast:"Distancia al este",decorNorth:"Distancia al norte",decorHeading:"Orientación",decorColor:"Color",decorLit:"Encendido",decorTitle:"Nombre",milestones:"Momentos",milestoneLabel:"Etiqueta",milestoneNote:"Qué ocurre",addMilestone:"Nombrar este momento",deleteMilestone:"Eliminar momento",goToMilestone:"Ir a este momento",decorWidth:"Anchura",decorLength:"Longitud",decorHeight:"Altura",decorModel:"Modelo 3D",decorModelAdvanced:"Modelo desde una dirección",decorModelUrl:"Dirección glTF/GLB",decorModelTitle:"Nombre del modelo",decorModelAuthor:"Autor",decorModelLicense:"Licencia",decorModelSource:"Procedencia",decorObserverSide:"Ubicación del observador",decorObserverSideNone:"No presente",decorSideFront:"Delante",decorSideBehind:"Detrás",decorSideLeft:"Izquierda",decorSideRight:"Derecha",decorSideFrontLeft:"Delante a la izquierda",decorSideFrontRight:"Delante a la derecha",decorSideBehindLeft:"Detrás a la izquierda",decorSideBehindRight:"Detrás a la derecha"},o={...a,oval:"Óvalo",polygon:"Polígono",modelTriangle:"Triángulo",modelSaucer:"Platillo",modelCigar:"Cigarro",modelChevron:"Chevrón",modelLight:"Luz",modelPartDisc:"disco",modelPartDome:"cúpula",addVertex:"Añadir vértice",deleteVertex:"Eliminar vértice",notAPolygon:"Selecciona una sola forma poligonal",tooFewVertices:"Una forma necesita al menos 3 puntos",shape:"Forma",apparentWidth:"Anchura aparente",realWidth:"Anchura real",distance:"Distancia",shapeRotation:"Rotación",shapeRotationTitle:"Cuánto está girada la forma en el dibujo, en sentido horario, en grados: lo que ajusta su asa de rotación",sizeLock:"Mantener",lockNone:"nada",lockApparent:"la anchura aparente",lockReal:"la anchura real",lockDistance:"la distancia",apparentSize:"{moons}× la Luna",utcOffsetPlaceholder:"según la longitud",realSizeBetween:"La escena acota esa anchura: entre {min} y {max} m, según lo que cruza",realSizeAtLeast:"La escena acota esa anchura: al menos {min} m, según lo que pasa por detrás",realSizeAtMost:"La escena acota esa anchura: como mucho {max} m, según lo que pasa por delante",realSizeUnknown:"Nada en la escena acota esa anchura: solo es lo que implica la distancia",realSizeOutside:" — la anchura de arriba queda fuera",blurBound:"Desenfocado a través de este objetivo: más cerca de {max} m",blurBoundNoInstrument:"El desenfoque a simple vista no acota ninguna distancia — un ojo no tiene diafragma con el que desenfocar",blurBoundNotAtInfinity:"El desenfoque solo acota una distancia con un objetivo enfocado al infinito: este está enfocado a {focus} m, lo que desenfoca a ambos lados",realSizeContradiction:"Contradicción: los cruces declarados no pueden ser todos ciertos",realDistanceHere:" — es decir, aquí entre {min} y {max} m",clearDistanceHypothesis:"Retirar la hipótesis de distancia",clearDistanceHypothesisTitle:"Volver a lo que establece la grabación",depthStated:"Dibujado a {m} m, como indica la grabación",depthHypothesis:"Dibujado a {m} m: tu hipótesis — lo que el decorado oculta de él es lo que perdería algo tan lejano",depthDerived:"Dibujado a {m} m, según la propia marcha del observador",depthBounded:"Dibujado a {m} m, según lo que pasa por detrás y por delante",depthConventional:"Dibujado a {m} m por convención: nada en la grabación establece una distancia",addShape:"Añadir forma",deleteShape:"Eliminar forma",play:"Reproducir",pause:"Pausa",noDuration:"Sin duración de observación",previousKeyframe:"Fotograma clave anterior",nextKeyframe:"Fotograma clave siguiente",keyframeMark:"Fotograma clave",shapesTrack:"formas",observerTrack:"observador",weatherTrack:"meteorología",soundTrack:"sonido",group:"Agrupar",ungroup:"Desagrupar",duplicate:"Duplicar",bringToFront:"Traer al frente",sendToBack:"Enviar al fondo",contextMenuDelete:"Eliminar",onlyOneShape:"Solo hay una forma",alreadyAtFront:"Esta forma ya está al frente",alreadyAtBack:"Esta forma ya está al fondo",needTwoShapesToGroup:"Selecciona al menos dos formas para agruparlas",notGrouped:"Esta forma no forma parte de un grupo",multipleShapesSelected:"Varias formas seleccionadas — selecciona solo una para editar esto",samplingRate:"Frecuencia de muestreo",currentPosition:"Posición actual",switchToElapsed:"haz clic para mostrar el tiempo transcurrido",switchToClockTime:"haz clic para mostrar la hora del día",durationPlaceholder:"duración de la observación",durationImprecise:"Estas fechas no determinan con suficiente precisión una duración — introdúcela manualmente.",export:"Guardar",frozenTitle:"Datos congelados",frozenHint:"Lo que la escena pediría si no a terceros en cada reproducción (relieve, foto del suelo, carreteras, agua, vías férreas, atmósfera alta), tomado al guardar y conservado en archivos junto a la grabación, para que se reproduzca igual sin pedir nada a nadie.",frozenLive:"Consultar las fuentes en cada reproducción (no congelar nada)",frozenRelief:"Relieve",frozenImagery:"Foto del suelo",frozenRoads:"Carreteras",frozenWater:"Agua",frozenRailways:"Vías férreas",frozenUpperAir:"Atmósfera alta",frozenMap:"Foto del mapa",fileItemRecording:"Registro",fileItemWeather:"Tiempo",fileItemPlace:"Lugar",fileItemLightPollution:"Contaminación lumínica",fileItemDecor:"Decorado",weatherStatedByObserver:"tal como la dio el observador",annexSource:"Archivo",annexStorageInline:"dentro del propio registro",annexEditorLabel:"El anexo como texto JSON",annexUnreadable:"Este archivo no es un anexo de este tipo: no se ha cambiado nada.",frozenTakenOn:"tomado el {date}",frozenPending:"se toma al guardar",frozenLiveState:"consultado en directo",frozenRefresh:"Actualizar",frozenTaking:"Tomando: {group}… {done}/{of}",frozenFailed:"{group}: la fuente no respondió, no se tomó nada.",frozenMissing:"{file} no se pudo leer: queda fuera del archivo.",frozenExported:"Guardado con {count} anexos ({size}), en un archivo que descomprimir donde se publica la grabación.",identityHint:"Nombra a alguien solo si lo consintió o publicó él mismo su testimonio; si no, bastan unas iniciales.",identityAsk:"Esta grabación nombra o sitúa a una persona. Respondes de lo que publicas: nombra a alguien solo si lo consintió, o si publicó él mismo su testimonio. Si no, bastan unas iniciales.",identityLastName:"Apellidos: {value}",identityFirstNames:"Nombre: {value}",identityNameInId:"Nombre en un identificador: {value}",identityNameInText:"Nombre en el texto: {value}",identityAddress:"Dirección: {value}",identityEmail:"Correo electrónico: {value}",identityPhone:"Número de teléfono: {value}",identityInitials:"Sustituir por iniciales",identityKeep:"Guardar de todos modos",identityBack:"Volver",importFile:"Cargar un archivo de observación",paramSummaryTitle:"Lo que indica la grabación ({n})",wholeKeyframes:"Cada fotograma clave entero",wholeKeyframesHint:"Si no, un fotograma clave solo dice lo que cambió desde el anterior, como se escribe una grabación a mano. Cambiar esto vacía el historial de deshacer.",importUrlPlaceholder:"https://…/sighting.json",inputFileFromFile:"desde su disco",inputFileFromUrl:"desde una dirección",inputFileModeHint:"Pulsar para darlo de la otra forma: un archivo suyo o una dirección",inputFileLoad:"Cargar",importError:"No se pudo cargar esa grabación — revisa el archivo o la URL e inténtalo de nuevo.",importErrorCors:'Esa dirección responde, pero el navegador no tiene permiso para leerla desde esta página. El archivo en sí está bien: su servidor debe enviarlo con la cabecera "Access-Control-Allow-Origin: *". Si no es tu servidor, eso es lo que debes pedir a su administrador.',importErrorMixedContent:"Una página segura no puede cargar una dirección que empiece por http://. Usa https:// si ese servidor lo ofrece.",importErrorUnreachable:"Nada respondió en esa dirección. Revisa cómo está escrita y que tengas conexión.",importErrorStatus:"Ese servidor respondió con un error ({status}). Probablemente la dirección es incorrecta o el archivo se ha movido.",importErrorMalformed:"Esa dirección respondió, pero lo que devolvió no es una grabación.",formatVersionError:"Este archivo indica la versión {found} de su formato; este UFO@home lee hasta la versión {supported}. Ábralo con un UFO@home más reciente.",record:"Grabar",stop:"Detener",according:"según",sourceElevation:"Relieve",sourceImagery:"Imágenes",sourceAircraft:"Tráfico aéreo",sourceFile:"Archivo",sourceKind:"Datos",placeNamePlaceholder:"Valensole, Francia",searchPlace:"Localizar",placeMatch:"Coincidencias",placeSearching:"Buscando el lugar…",placeMatchFound:"lugar encontrado",placeMatchesFound:"lugares encontrados",placeNotFound:"Ningún lugar con ese nombre",placeSearchFailed:"Búsqueda de lugares no disponible por el momento",utcOffsetNoZone:"Ninguna zona horaria marca este desfase ese día",utcOffsetImplausible:"Esta zona horaria no corresponde a la longitud introducida, cuya hora solar es UTC{solar} — aun así, la escena y el registro meteorológico obedecen a lo que se declara aquí. Vacía el campo para deducirla de la longitud.",timeZoneManual:"desconocido",altitudeAboveSeaLevel:"Altitud del observador sobre el nivel del mar — el suelo del lugar introducido fija su mínimo: un observador en los Alpes no está a 0 m.",groundAt:"suelo a {m} m",headingPlaceholder:"desconocido",edtfInvalid:"Fecha/hora EDTF no válida (p. ej. 1965-07-01T05:00, 2025-06?, 2025~).",edtfPlaceholder:"AAAA-MM-DDThh:mm[?~%] o hh:mm",observationTimeHint:"EDTF — p. ej. 1965-07-01T05:00, 2025-06? (incierto), 2025~ (aproximado), o solo 05:00 si no se conoce la fecha",observationEndTimeHint:"EDTF — p. ej. 1965-07-01T05:10, 2025-06? (incierto), 2025~ (aproximado), o solo 05:10 si no se conoce la fecha",presetsGroupLabel:"Forma del ovni",description:"Descripción",tagsPlaceholder:"separadas por comas",weather:"Meteorología",shapeGroup:"Fenómeno",shapesSubgroup:"Formas",bodiesSubgroup:"Cuerpos",launchesSubgroup:"Lanzamientos",sky:"Cielo",dataSources:"Fuentes de datos",beamsSubgroup:"Haces",soundGroup:"Sonido",close:"Cerrar",editorKicker:"Editor de avistamiento",witnessError:e,expandTracks:"Desplegar las pistas",collapseTracks:"Plegar las pistas",addKeyframeHere:"Añadir un fotograma clave aquí",keyframeHere:"Aquí hay un fotograma clave: editarlo lo modifica",overlayHorizon:"Horizonte · inclinación {deg}°",overlayFov:"Campo de visión {v}° × {h}°",overlayWind:"Sopla hacia {point} · {speed} m/s",sectionIdentity:"Identidad",sectionProfile:"Perfil",sectionTestimony:"Testimonio",sectionVision:"Visión",sectionPerception:"Percepción",sectionCoordinates:"Coordenadas",sectionGaze:"Mirada",sectionNightSky:"Cielo nocturno",sectionPeriod:"Periodo",sectionSurvey:"Decorado tomado de un levantamiento",sectionHeard:"Lo que se oyó",sectionSoundRecording:"Grabación",sectionPicture:"Foto",sectionPose:"Pose",sectionAppearance:"Apariencia",sectionSize:"Tamaño y distancia",sectionPath:"Trayectoria",moreSettings:"Más ajustes ({n})",lessSettings:"Menos ajustes",showSettings:"Mostrar ({n} ajustes)",showSettingsOne:"Mostrar (1 ajuste)",showSettingsAny:"Mostrar",hideSettings:"Ocultar",save:"Guardar",stateNew:"Nueva grabación",panelIntroObservation:"Lo que pasó, en pocas líneas, y lo que queda por completar.",panelIntroObserver:"Quién lo vio, y con qué. Cada observador tiene su propio punto de vista en la escena.",panelIntroLocation:"Desde dónde miraba el observador, y en qué dirección.",panelIntroTemporal:"Cuándo empezó, cuánto duró, y los momentos del relato.",panelIntroDecor:"Lo que rodeaba al observador: vehículos, edificios, vegetación.",panelIntroWeather:"El tiempo que hacía, y lo que cruzaba el cielo sin ser el fenómeno: lanzamientos, haces.",panelIntroShape:"Lo que se vio, dibujado en la escena desde el punto de vista del observador.",panelIntroSound:"Lo que se oyó.",panelIntroReference:"Fotos y croquis, ajustados a la escena para compararla.",panelIntroFile:"La grabación completa, para guardar o volver a cargar.",temporalGroup:"Fecha",fileGroup:"Archivos",fileLoading:"Cargando el editor…",weatherEditedAsk:"Esta edición toca el tiempo, un dato leído dinámicamente desde {source}. ¿Quieres dejar de leerlo desde {source}?",weatherEditedStop:"Dejar de leerlo",weatherEditedKeep:"Seguir leyéndolo (deshacer mi edición)",fileEditorLabel:"El registro como texto JSON",fileSearchPhrases:{Find:"Buscar",Replace:"Reemplazar",next:"siguiente",previous:"anterior",all:"todo","match case":"mayúsculas","by word":"palabra completa",regexp:"regexp",replace:"reemplazar","replace all":"reemplazar todo",close:"cerrar","current match":"coincidencia actual","on line":"en la línea","replaced $ matches":"$ coincidencias reemplazadas","replaced match on line $":"coincidencia reemplazada en la línea $","Go to line":"Ir a la línea",go:"ir"},timeQualifierExact:"Exacta",durationPrecise:"precisa",durationIso:"imprecisa",durationModeTitle:"Decir la duración exactamente, o como se enunció: aproximadamente, entre dos duraciones",durationInvalid:"No es una duración: PT10M, PT1H30M, PT5M/PT10M…",durationHelpTitle:"Lo que acepta el texto (EDTF)",durationHelpLines:`PT45S — 45 segundos
2
2
  PT10M — 10 minutos
3
3
  PT1H30M — 1 hora 30 minutos
4
4
  PT5M/PT10M — entre 5 y 10 minutos