lecodes-cli 2.4.3 → 2.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- var ft=Object.defineProperty;var mt=(e,n,t)=>n in e?ft(e,n,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[n]=t;var p=(e,n,t)=>mt(e,typeof n!="symbol"?n+"":n,t);(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))s(i);new MutationObserver(i=>{for(const r of i)if(r.type==="childList")for(const a of r.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&s(a)}).observe(document,{childList:!0,subtree:!0});function t(i){const r={};return i.integrity&&(r.integrity=i.integrity),i.referrerPolicy&&(r.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?r.credentials="include":i.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function s(i){if(i.ep)return;i.ep=!0;const r=t(i);fetch(i.href,r)}})();const Y=[{id:"iphone",name:"iPhone",width:393,height:852,radius:55,bezel:10,keyboard:336,keyboardLook:"ios",insets:{portrait:{top:59,right:0,bottom:34,left:0},landscape:{top:0,right:59,bottom:21,left:59}},marks:{island:{width:126,height:37,inset:11},homeIndicator:{width:139,height:5,inset:8},status:{height:59}}},{id:"android",name:"Android",width:412,height:915,radius:28,bezel:8,keyboard:300,keyboardLook:"android",insets:{portrait:{top:24,right:0,bottom:48,left:0,bars:["top","bottom"]},landscape:{top:24,right:0,bottom:48,left:0,bars:["top","bottom"]}},marks:{camera:{size:12,inset:6},navigation:{height:48},status:{height:24}}},{id:"tablet",name:"Tablet",width:820,height:1180,radius:18,bezel:14,keyboard:0,keyboardLook:"ios",insets:{portrait:{top:24,right:0,bottom:20,left:0},landscape:{top:24,right:0,bottom:20,left:0}},marks:{homeIndicator:{width:260,height:5,inset:7},status:{height:24}}}],ot=e=>Y.find(n=>n.id===e)??null,Q=(e,n)=>n==="landscape"?{width:e.height,height:e.width}:{width:e.width,height:e.height},bt=(e,n,t)=>{const s=Q(e,n),i=s.width+e.bezel*2,r=s.height+e.bezel*2;return t.width<=0||t.height<=0?1:Math.min(1,t.width/i,t.height/r)},kt=(e,n,t)=>{if(e>=1)return 1;const s=Math.floor(n*e*t);return s>0?s/(n*t):e},yt=`
1
+ var ft=Object.defineProperty;var mt=(s,n,t)=>n in s?ft(s,n,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[n]=t;var p=(s,n,t)=>mt(s,typeof n!="symbol"?n+"":n,t);(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))e(i);new MutationObserver(i=>{for(const r of i)if(r.type==="childList")for(const a of r.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&e(a)}).observe(document,{childList:!0,subtree:!0});function t(i){const r={};return i.integrity&&(r.integrity=i.integrity),i.referrerPolicy&&(r.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?r.credentials="include":i.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function e(i){if(i.ep)return;i.ep=!0;const r=t(i);fetch(i.href,r)}})();const Y=[{id:"iphone",name:"iPhone",width:393,height:852,radius:55,bezel:10,keyboard:336,keyboardLook:"ios",insets:{portrait:{top:59,right:0,bottom:34,left:0},landscape:{top:0,right:59,bottom:21,left:59}},marks:{island:{width:126,height:37,inset:11},homeIndicator:{width:139,height:5,inset:8},status:{height:59}}},{id:"android",name:"Android",width:412,height:915,radius:28,bezel:8,keyboard:300,keyboardLook:"android",insets:{portrait:{top:24,right:0,bottom:48,left:0,bars:["top","bottom"]},landscape:{top:24,right:0,bottom:48,left:0,bars:["top","bottom"]}},marks:{camera:{size:12,inset:6},navigation:{height:48},status:{height:24}}},{id:"tablet",name:"Tablet",width:820,height:1180,radius:18,bezel:14,keyboard:0,keyboardLook:"ios",insets:{portrait:{top:24,right:0,bottom:20,left:0},landscape:{top:24,right:0,bottom:20,left:0}},marks:{homeIndicator:{width:260,height:5,inset:7},status:{height:24}}}],ot=s=>Y.find(n=>n.id===s)??null,Q=(s,n)=>n==="landscape"?{width:s.height,height:s.width}:{width:s.width,height:s.height},bt=(s,n,t)=>{const e=Q(s,n),i=e.width+s.bezel*2,r=e.height+s.bezel*2;return t.width<=0||t.height<=0?1:Math.min(1,t.width/i,t.height/r)},kt=(s,n,t)=>{if(s>=1)return 1;const e=Math.floor(n*s),i=r=>Math.abs(r-Math.round(r))<1e-6;for(let r=e;r>0&&r>e-8;r--)if(i(r*t))return r/n;return e>0?e/n:s},yt=`
2
2
  .marks { position: absolute; inset: 0; pointer-events: none; color: var(--lecodes-frame-marks, #ffffff); font: 600 15px/1 system-ui, sans-serif; }
3
3
  /* what the system draws OVER the app is seen on any app: light on a dark one, dark on a light one */
4
4
  .marks > * { position: absolute; mix-blend-mode: difference; }
@@ -12,7 +12,7 @@ var ft=Object.defineProperty;var mt=(e,n,t)=>n in e?ft(e,n,{enumerable:!0,config
12
12
  .navigation .back { border: 0; width: 0; height: 0; border-style: solid; border-width: 8px 13px 8px 0; border-color: transparent currentColor transparent transparent; }
13
13
  .navigation .homeButton { border-radius: 50%; }
14
14
  .navigation .recent { border-radius: 3px; }
15
- `,y=e=>`${e}px`,I=(e,n,t)=>{const s=e.createElement("div");return s.className=n,Object.assign(s.style,t),s},wt=(e,n,t)=>{const s=e.ownerDocument,{marks:i}=n,r=t==="landscape",a=[];if(i.island){const{width:h,height:b,inset:w}=i.island;a.push(r?I(s,"island solid",{left:y(w),top:"50%",width:y(b),height:y(h),transform:"translateY(-50%)"}):I(s,"island solid",{top:y(w),left:"50%",width:y(h),height:y(b),transform:"translateX(-50%)"}))}if(i.camera){const{size:h,inset:b}=i.camera;a.push(r?I(s,"camera solid",{left:y(b),top:"50%",width:y(h),height:y(h),transform:"translateY(-50%)"}):I(s,"camera solid",{top:y(b),left:"50%",width:y(h),height:y(h),transform:"translateX(-50%)"}))}if(i.homeIndicator){const{width:h,height:b,inset:w}=i.homeIndicator;a.push(I(s,"home",{bottom:y(w),left:"50%",width:y(h),height:y(b),transform:"translateX(-50%)"}))}const g=n.insets[t].top;if(i.status&&g>0){const h=I(s,"clock",{top:"0",left:y(i.island?34:18),height:y(Math.min(g,i.status.height))});h.textContent="9:41",i.island||(h.style.fontSize="13px"),a.push(h)}if(i.navigation){const h=I(s,"navigation",{height:y(i.navigation.height)});for(const b of["back","homeButton","recent"]){const w=s.createElement("i");w.className=b,h.appendChild(w)}a.push(h)}e.replaceChildren(...a)},vt=[.38,.7,.125,1],xt=`cubic-bezier(${vt.join(", ")})`,Et=`
15
+ `,y=s=>`${s}px`,I=(s,n,t)=>{const e=s.createElement("div");return e.className=n,Object.assign(e.style,t),e},wt=(s,n,t)=>{const e=s.ownerDocument,{marks:i}=n,r=t==="landscape",a=[];if(i.island){const{width:h,height:b,inset:w}=i.island;a.push(r?I(e,"island solid",{left:y(w),top:"50%",width:y(b),height:y(h),transform:"translateY(-50%)"}):I(e,"island solid",{top:y(w),left:"50%",width:y(h),height:y(b),transform:"translateX(-50%)"}))}if(i.camera){const{size:h,inset:b}=i.camera;a.push(r?I(e,"camera solid",{left:y(b),top:"50%",width:y(h),height:y(h),transform:"translateY(-50%)"}):I(e,"camera solid",{top:y(b),left:"50%",width:y(h),height:y(h),transform:"translateX(-50%)"}))}if(i.homeIndicator){const{width:h,height:b,inset:w}=i.homeIndicator;a.push(I(e,"home",{bottom:y(w),left:"50%",width:y(h),height:y(b),transform:"translateX(-50%)"}))}const g=n.insets[t].top;if(i.status&&g>0){const h=I(e,"clock",{top:"0",left:y(i.island?34:18),height:y(Math.min(g,i.status.height))});h.textContent="9:41",i.island||(h.style.fontSize="13px"),a.push(h)}if(i.navigation){const h=I(e,"navigation",{height:y(i.navigation.height)});for(const b of["back","homeButton","recent"]){const w=e.createElement("i");w.className=b,h.appendChild(w)}a.push(h)}s.replaceChildren(...a)},vt=[.38,.7,.125,1],xt=`cubic-bezier(${vt.join(", ")})`,Et=`
16
16
  .keys { position: absolute; left: 0; right: 0; bottom: 0; overflow: hidden; box-sizing: border-box; font: 400 22px/1 -apple-system, "SF Pro Text", Roboto, system-ui, sans-serif; color: #000; transform: translateY(100%); will-change: transform; }
17
17
  .keys.shown { transform: translateY(0); }
18
18
  .keys.ios { background: #d1d3d9; }
@@ -29,13 +29,13 @@ var ft=Object.defineProperty;var mt=(e,n,t)=>n in e?ft(e,n,{enumerable:!0,config
29
29
  .keys .k.shift::before { content: ""; display: block; width: 0; height: 0; border-style: solid; border-width: 0 9px 10px 9px; border-color: transparent transparent #000 transparent; }
30
30
  .keys .k.del::before { content: "⌫"; font-size: 20px; }
31
31
  .keys .k.emoji::before { content: "☺"; font-size: 20px; }
32
- `,$t={en:[["q","w","e","r","t","y","u","i","o","p"],["a","s","d","f","g","h","j","k","l"],["z","x","c","v","b","n","m"]],ru:[["й","ц","у","к","е","н","г","ш","щ","з","х"],["ф","ы","в","а","п","р","о","л","д","ж","э"],["я","ч","с","м","и","т","ь","б","ю"]]},Lt={en:{space:"space",enter:"return"},ru:{space:"Пробел",enter:"Ввод"}},N=e=>`${Math.round(e*2)/2}px`,St=(e,n)=>{const{look:t,width:s,height:i,inset:r}=n,a=n.language.slice(0,2)==="ru"?"ru":"en",g=$t[a],h=Lt[a],b=t==="ios",w=[],f=(k,z,F,V,G,gt)=>{const q=e.createElement("div");q.className=`k${z?` ${z}`:""}`,q.textContent=k,Object.assign(q.style,{left:N(F),top:N(V),width:N(G),height:N(gt)}),w.push(q)},B=b?44:40,j=e.createElement("div");j.className="bar",Object.assign(j.style,{top:"0",height:N(B)});for(let k=0;k<(b?2:3);k++)j.append(e.createElement("i"));w.push(j);const m=3,x=6,M=b?11:9,P=g[0].length,S=(s-m*2-x*(P-1))/P,o=i-B-r,c=Math.min(b?42:46,(o-M*5)/4),d=B+Math.max(M,(o-(c*4+M*3))/2-2),l=k=>d+k*(c+M),E=k=>(s-k*S-(k-1)*x)/2;g.forEach((k,z)=>{const F=E(k.length);k.forEach((V,G)=>f(V,"",F+G*(S+x),l(z),S,c))});const C=g[2],L=E(C.length)-x-m;f("","s shift",m,l(2),L,c),f("","s del",s-m-L,l(2),L,c);const v=l(3),A=S*2+x;if(b){f("123","s",m,v,A,c),f("","s emoji",m+A+x,v,S,c);const k=m+A+x+S+x;f(h.space,"space",k,v,s-k-m-A-x,c),f(h.enter,"s",s-m-A,v,A,c)}else{f("?123","s",m,v,A,c),f(",","",m+A+x,v,S,c);const k=m+A+x+S+x,z=s-m-A-x-S;f("","space",k,v,z-x-k,c),f(".","",z,v,S,c),f("↵","s",s-m-A,v,A,c)}return w},rt=(e,n)=>{e.style.transition=n>0?`transform ${n}ms ${xt}`:""},At=(e,n,t=0)=>{const s=e.classList.contains("shown");rt(e,t),e.className=`keys ${n.look}${s?" shown":""}`,e.style.height=N(n.height),e.replaceChildren(...St(e.ownerDocument,n)),s||(t>0&&e.offsetHeight,e.classList.add("shown"))},Ct=(e,n=0)=>{const t=()=>{e.classList.contains("shown")||(e.replaceChildren(),e.style.height="0")};if(!e.classList.contains("shown")){t();return}rt(e,n),e.classList.remove("shown"),n>0?e.addEventListener("transitionend",t,{once:!0}):t()},Rt=e=>{if(e.dataset.pointer)return;e.dataset.pointer="taken",e.style.pointerEvents="auto";const n=t=>{t.preventDefault(),t.stopPropagation()};for(const t of["pointerdown","mousedown","touchstart","click"])e.addEventListener(t,n,{passive:!1})},Ot=`
32
+ `,St={en:[["q","w","e","r","t","y","u","i","o","p"],["a","s","d","f","g","h","j","k","l"],["z","x","c","v","b","n","m"]],ru:[["й","ц","у","к","е","н","г","ш","щ","з","х"],["ф","ы","в","а","п","р","о","л","д","ж","э"],["я","ч","с","м","и","т","ь","б","ю"]]},$t={en:{space:"space",enter:"return"},ru:{space:"Пробел",enter:"Ввод"}},N=s=>`${Math.round(s*2)/2}px`,Lt=(s,n)=>{const{look:t,width:e,height:i,inset:r}=n,a=n.language.slice(0,2)==="ru"?"ru":"en",g=St[a],h=$t[a],b=t==="ios",w=[],f=(k,z,F,V,G,gt)=>{const q=s.createElement("div");q.className=`k${z?` ${z}`:""}`,q.textContent=k,Object.assign(q.style,{left:N(F),top:N(V),width:N(G),height:N(gt)}),w.push(q)},B=b?44:40,j=s.createElement("div");j.className="bar",Object.assign(j.style,{top:"0",height:N(B)});for(let k=0;k<(b?2:3);k++)j.append(s.createElement("i"));w.push(j);const m=3,x=6,M=b?11:9,P=g[0].length,L=(e-m*2-x*(P-1))/P,o=i-B-r,c=Math.min(b?42:46,(o-M*5)/4),d=B+Math.max(M,(o-(c*4+M*3))/2-2),l=k=>d+k*(c+M),E=k=>(e-k*L-(k-1)*x)/2;g.forEach((k,z)=>{const F=E(k.length);k.forEach((V,G)=>f(V,"",F+G*(L+x),l(z),L,c))});const C=g[2],$=E(C.length)-x-m;f("","s shift",m,l(2),$,c),f("","s del",e-m-$,l(2),$,c);const v=l(3),A=L*2+x;if(b){f("123","s",m,v,A,c),f("","s emoji",m+A+x,v,L,c);const k=m+A+x+L+x;f(h.space,"space",k,v,e-k-m-A-x,c),f(h.enter,"s",e-m-A,v,A,c)}else{f("?123","s",m,v,A,c),f(",","",m+A+x,v,L,c);const k=m+A+x+L+x,z=e-m-A-x-L;f("","space",k,v,z-x-k,c),f(".","",z,v,L,c),f("↵","s",e-m-A,v,A,c)}return w},rt=(s,n)=>{s.style.transition=n>0?`transform ${n}ms ${xt}`:""},At=(s,n,t=0)=>{const e=s.classList.contains("shown");rt(s,t),s.className=`keys ${n.look}${e?" shown":""}`,s.style.height=N(n.height),s.replaceChildren(...Lt(s.ownerDocument,n)),e||(t>0&&s.offsetHeight,s.classList.add("shown"))},Ct=(s,n=0)=>{const t=()=>{s.classList.contains("shown")||(s.replaceChildren(),s.style.height="0")};if(!s.classList.contains("shown")){t();return}rt(s,n),s.classList.remove("shown"),n>0?s.addEventListener("transitionend",t,{once:!0}):t()},Ot=s=>{if(s.dataset.pointer)return;s.dataset.pointer="taken",s.style.pointerEvents="auto";const n=t=>{t.preventDefault(),t.stopPropagation()};for(const t of["pointerdown","mousedown","touchstart","click"])s.addEventListener(t,n,{passive:!1})},Rt=`
33
33
  :host { display: block; position: relative; overflow: hidden; min-width: 0; min-height: 0; }
34
34
  .stage { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
35
35
  .device { position: relative; flex: none; box-sizing: content-box; background: var(--lecodes-frame-body, #0b0b0d); box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.10), 0 24px 60px rgba(0, 0, 0, 0.35); }
36
36
  .screen { position: relative; overflow: hidden; background: #000; width: 100%; height: 100%; }
37
37
  iframe { display: block; width: 100%; height: 100%; border: 0; background: #000; }
38
- .over { position: absolute; inset: 0; pointer-events: none; }
38
+ .over { position: absolute; left: 0; top: 0; width: 100%; height: 100%; pointer-events: none; }
39
39
  :host([bare]) .device { box-shadow: none; background: none; }
40
40
  .pick { position: absolute; inset: 0; pointer-events: none; }
41
41
  :host([picking]) .pick { pointer-events: auto; cursor: crosshair; }
@@ -45,5 +45,5 @@ iframe { display: block; width: 100%; height: 100%; border: 0; background: #000;
45
45
  .found.low > b { bottom: auto; top: 100%; margin: 3px 0 0; }
46
46
  ${yt}
47
47
  ${Et}
48
- `,jt=600,Bt=["player","src","device","orientation","language","bare","inspect"],zt=3e3,H=class H extends HTMLElement{constructor(){super();p(this,"stage");p(this,"body");p(this,"screen");p(this,"over");p(this,"marks");p(this,"keys");p(this,"pick");p(this,"found");p(this,"asked",new Map);p(this,"requests",0);p(this,"pointing",null);p(this,"pointed",!1);p(this,"iframe",null);p(this,"observer");p(this,"loaded",!1);p(this,"mounting",!1);p(this,"turned",null);p(this,"locked",!1);p(this,"shownScale",1);p(this,"onWindow",()=>{this.layout(),this.tellSafeArea()});p(this,"onMessage",t=>{var i;if(!this.iframe||t.source!==this.iframe.contentWindow)return;const s=t.data;if(!(!s||typeof s!="object"))switch(s.type){case"lecodes:ready":this.highlight(null),this.emit("app-ready",{src:String(s.src)});break;case"lecodes:error":this.emit("app-error",s.error);break;case"lecodes:log":this.emit("app-log",{level:s.level,text:String(s.text)});break;case"lecodes:inspected":(i=this.asked.get(s.request))==null||i(s.answer);break;case"lecodes:keyboard":this.drawKeyboard(s.height,s.duration);break;case"lecodes:orientation":{s.mode==="landscape"||s.mode==="portrait"?(this.turned=s.mode,this.locked=!0):this.locked=!1,this.layout(),this.tellSafeArea(),this.emit("app-orientation",{orientation:this.orientation,locked:this.locked});break}}});const t=this.attachShadow({mode:"open"}),s=this.ownerDocument,i=s.createElement("style");i.textContent=Ot;const r=a=>{const g=s.createElement("div");return g.className=a,g};this.stage=r("stage"),this.body=r("device"),this.screen=r("screen"),this.over=r("over"),this.marks=r("marks"),this.keys=r("keys"),Rt(this.keys),this.pick=r("pick"),this.found=r("found"),this.found.append(s.createElement("i"),s.createElement("b")),this.over.append(this.found,this.keys,this.marks,this.pick),this.pick.addEventListener("pointermove",a=>this.point(a)),this.pick.addEventListener("pointerleave",()=>{this.pointing=null,this.picking&&(this.highlight(null),this.emit("app-point",null))}),this.pick.addEventListener("click",a=>{this.at(a).then(g=>{g&&(this.highlight(g),this.emit("app-pick",g))})}),this.screen.appendChild(this.over),this.body.appendChild(this.screen),this.stage.appendChild(this.body),t.append(i,this.stage),this.observer=new ResizeObserver(()=>this.layout())}static get noRoom(){return window.innerWidth<jt}get player(){return this.getAttribute("player")??""}set player(t){this.setAttribute("player",t)}get src(){return this.getAttribute("src")??""}set src(t){this.setAttribute("src",t)}get device(){return this.getAttribute("device")??Y[0].id}set device(t){this.setAttribute("device",t)}get language(){return this.getAttribute("language")??""}set language(t){this.setAttribute("language",t)}get orientation(){return this.turned??(this.getAttribute("orientation")==="landscape"?"landscape":"portrait")}set orientation(t){this.turned=null,this.locked=!1,this.setAttribute("orientation",t)}get orientationLocked(){return this.locked}get bare(){return this.hasAttribute("bare")||this.current===null}get inspect(){return this.hasAttribute("inspect")}set inspect(t){this.toggleAttribute("inspect",t)}get picking(){return this.hasAttribute("picking")}set picking(t){this.toggleAttribute("picking",t),t||this.highlight(null)}get scale(){return this.shownScale}get current(){return this.hasAttribute("bare")||H.noRoom?null:ot(this.device)}get playerUrl(){return this.urlFor(null)}rotate(){this.locked||(this.orientation=this.orientation==="portrait"?"landscape":"portrait",this.emit("app-orientation",{orientation:this.orientation,locked:!1}))}ask(t){return new Promise(s=>{const i=++this.requests,r=a=>{this.asked.delete(i)&&s(a&&a.ask===t.ask?a:null)};this.asked.set(i,r),window.setTimeout(()=>r(null),zt),this.post({type:"lecodes:inspect",request:i,question:t})})}highlight(t){const s=this.found;if(!t){s.style.display="none";return}const{rect:i,padding:r}=t;Object.assign(s.style,{display:"block",left:`${i.x}px`,top:`${i.y}px`,width:`${i.width}px`,height:`${i.height}px`}),Object.assign(s.firstElementChild.style,{left:`${r.left}px`,top:`${r.top}px`,right:`${r.right}px`,bottom:`${r.bottom}px`}),s.lastElementChild.textContent=`${t.type}${t.name?` "${t.name}"`:""} ${Math.round(i.width)} × ${Math.round(i.height)}`,s.classList.toggle("low",i.y<24)}reload(){this.post({type:"lecodes:reload"})}connectedCallback(){this.observer.observe(this),window.addEventListener("message",this.onMessage),window.addEventListener("resize",this.onWindow),this.remount()}disconnectedCallback(){var t;this.observer.disconnect(),window.removeEventListener("message",this.onMessage),window.removeEventListener("resize",this.onWindow),(t=this.iframe)==null||t.remove(),this.iframe=null}attributeChangedCallback(t,s,i){if(!(!this.isConnected||s===i))switch(t){case"player":this.remount();break;case"src":this.loaded?i&&this.post({type:"lecodes:run",src:i}):this.remount();break;case"language":this.loaded?i&&this.post({type:"lecodes:language",language:i}):this.remount();break;case"inspect":this.remount();break;default:this.layout(),this.tellSafeArea()}}toApp(t){const s=this.screen.getBoundingClientRect();return{x:(t.clientX-s.left)/this.shownScale,y:(t.clientY-s.top)/this.shownScale}}async at(t){var s;return((s=await this.ask({ask:"at",...this.toApp(t)}))==null?void 0:s.node)??null}point(t){this.pointing=this.toApp(t),!this.pointed&&(this.pointed=!0,(async()=>{var s;for(;this.pointing&&this.picking;){const{x:i,y:r}=this.pointing;this.pointing=null;const a=((s=await this.ask({ask:"at",x:i,y:r}))==null?void 0:s.node)??null;if(!this.picking)break;this.highlight(a),this.emit("app-point",a)}this.pointed=!1})())}remount(){this.mounting||(this.mounting=!0,queueMicrotask(()=>{this.mounting=!1,this.isConnected&&this.mount()}))}mount(){var s;if((s=this.iframe)==null||s.remove(),this.iframe=null,this.loaded=!1,!this.player)return;const t=this.ownerDocument.createElement("iframe");t.addEventListener("load",()=>{this.iframe===t&&(this.loaded=!0,this.tellSafeArea())}),t.setAttribute("allow","autoplay; fullscreen; camera; microphone; geolocation; clipboard-read; clipboard-write; gamepad"),t.title="LeCodes app",t.src=this.urlFor(this.current),this.screen.prepend(t),this.iframe=t,this.layout()}urlFor(t){var i;const s=new URL(this.player,this.ownerDocument.baseURI);if(this.src&&s.searchParams.set("src",this.src),this.language&&s.searchParams.set("lang",this.language),this.inspect&&s.searchParams.set("inspect",""),t){const r=t.insets[this.orientation];s.searchParams.set("safe-area",[r.top,r.right,r.bottom,r.left].join(",")),(i=r.bars)!=null&&i.length&&s.searchParams.set("bars",r.bars.join(","));const a=this.keyboardBand(t);a>0&&s.searchParams.set("keyboard-band",String(a))}return s.href}layout(){const t=this.current;this.toggleAttribute("framed",t!==null);const s=this.body.style;if(!t){this.shownScale=1,Object.assign(s,{width:"100%",height:"100%",padding:"0",borderRadius:"0",zoom:""}),this.screen.style.borderRadius="0",this.marks.replaceChildren();return}const i=this.orientation,r=Q(t,i);this.shownScale=kt(bt(t,i,{width:this.clientWidth,height:this.clientHeight}),r.width,window.devicePixelRatio||1),Object.assign(s,{width:`${r.width}px`,height:`${r.height}px`,padding:`${t.bezel}px`,borderRadius:`${t.radius+t.bezel}px`,zoom:String(this.shownScale)}),this.screen.style.borderRadius=`${t.radius}px`,wt(this.marks,t,i)}post(t){var i;const s=(i=this.iframe)==null?void 0:i.contentWindow;!s||!this.player||s.postMessage(t,new URL(this.player,this.ownerDocument.baseURI).origin)}tellSafeArea(){const t=this.current;this.post({type:"lecodes:safe-area",insets:t?{...t.insets[this.orientation]}:null});const s=t?this.keyboardBand(t):0;this.post({type:"lecodes:keyboard-band",height:s>0?s:null}),s===0&&this.drawKeyboard(0)}keyboardBand(t){return this.orientation==="portrait"?t.keyboard:0}drawKeyboard(t,s=0){const i=this.current;if(!i||t<=0){Ct(this.keys,s);return}const r=Q(i,this.orientation);At(this.keys,{look:i.keyboardLook,language:this.language,width:r.width,height:t,inset:i.insets[this.orientation].bottom},s)}emit(t,s){this.dispatchEvent(new CustomEvent(t,{detail:s}))}};p(H,"observedAttributes",Bt);let D=H;const It=(e="lecodes-frame")=>{customElements.get(e)||customElements.define(e,D)};typeof customElements<"u"&&It();const $=(e,n,t)=>{const s=document.createElement(e);return n&&(s.className=n),t!==void 0&&(s.textContent=t),s},st=e=>e.type+(e.name?` "${e.name}"`:""),it=/^(width|height|minWidth|minHeight|maxWidth|maxHeight|aspectRatio|flex|flexGrow|flexShrink|flexBasis|flexDirection|flexWrap|alignItems|alignSelf|alignContent|justifyContent|gap|rowGap|columnGap|position|left|top|right|bottom|inset|display|overflow|zIndex|m|mx|my|mt|mr|mb|ml|margin\w*|p|px|py|pt|pr|pb|pl|padding\w*)$/,R=e=>typeof e=="string"?e:typeof e=="number"?String(Math.round(e*100)/100):Array.isArray(e)?e.map(R).join(" "):typeof e=="object"&&e!==null?Object.entries(e).map(([n,t])=>`${n}: ${R(t)}`).join(", "):String(e),Mt=(e,n,t)=>{const s=n.querySelector("#pick"),i=n.querySelector("#tree"),r=n.querySelector("#details"),a=new Set;let g=0,h=!1;const b=(o,c,d)=>{const l=$("div","node");l.dataset.id=String(o.id),l.style.paddingLeft=`${8+c*14}px`,l.classList.toggle("chosen",o.id===g);const E=$("span","fold",o.children.length===0?"":a.has(o.id)?"▸":"▾");if(E.addEventListener("click",C=>{C.stopPropagation(),a.has(o.id)?a.delete(o.id):a.add(o.id),w()}),l.append(E,$("span","type",o.type)),o.name&&l.append($("span","name",` "${o.name}"`)),o.text&&l.append($("span","text",` ${o.text.replace(/\s+/g," ")}`)),l.addEventListener("mouseenter",()=>e.highlight({...o,padding:{top:0,right:0,bottom:0,left:0}})),l.addEventListener("click",()=>void x(o.id)),d.append(l),!a.has(o.id))for(const C of o.children)b(C,c+1,d)},w=async()=>{if(!h)return;const o=await e.ask({ask:"tree"}),c=$("div");o||c.append($("div","empty","No app is running."));for(const d of(o==null?void 0:o.layers)??[])d.kind==="widget"&&c.append($("div","layer","widget")),b(d.tree,0,c);i.replaceChildren(c)};i.addEventListener("mouseleave",()=>{M()});const f=(o,c)=>{if(c.length===0)return null;const d=$("div","section");d.append($("div","title",o));for(const[l,E]of c){const C=$("div","pair");C.append($("span","key",l),$("span","value",E)),d.append(C)}return d},B=o=>typeof o=="object"&&o!==null&&!Array.isArray(o),j=(o,c)=>Object.entries(o??{}).filter(([d,l])=>c(d,l)).map(([d,l])=>[d,R(l)]),m=o=>{var _;if(!o){r.replaceChildren($("div","empty","Pick an element: in the app, or in the outline."));return}const c=$("div","head",st(o)),d=$("div","path",o.path.slice(0,-1).map(st).join(" › ")),{rect:l,padding:E}=o,C=[f("Box",[["size",`${R(l.width)} × ${R(l.height)}`],["position",`${R(l.x)}, ${R(l.y)}`],...E.top||E.right||E.bottom||E.left?[["padding",`${E.top} ${E.right} ${E.bottom} ${E.left}`]]:[],...o.scroll?[["scroll",`${o.scroll.axis}: ${R(o.scroll.offset)} of ${R(o.scroll.content)}`]]:[],...(_=o.state)!=null&&_.length?[["state",o.state.join(", ")]]:[]]),o.text!==void 0?f("Text",[["",o.text]]):null,f("Layout",j(o.set,(L,v)=>it.test(L)&&!B(v))),f("Set by the app",j(o.set,(L,v)=>!it.test(L)&&!B(v))),...Object.entries(o.set??{}).filter(([,L])=>B(L)).map(([L,v])=>f(L,j(v,()=>!0))),f("Painted",Object.entries(o.style).map(([L,v])=>[L,R(v)])),o.source?f("Made at",[["",`${o.source.file}:${o.source.line}:${o.source.column}`]]):null];r.replaceChildren(c,d,...C.filter(L=>L!==null))},x=async o=>{var d;g=o;const c=((d=await e.ask({ask:"node",node:o}))==null?void 0:d.node)??null;e.highlight(c),m(c);for(const l of i.querySelectorAll(".node"))l.classList.toggle("chosen",l.dataset.id===String(o))},M=async()=>{var o;e.picking||e.highlight(g?((o=await e.ask({ask:"node",node:g}))==null?void 0:o.node)??null:null)},P=o=>{e.picking=o,s.setAttribute("aria-pressed",String(o))};s.addEventListener("click",()=>P(!e.picking)),e.addEventListener("app-pick",o=>{const c=o.detail;P(!1),e.highlight(c),g=c.id;for(const d of c.path)a.delete(d.id);m(c),w().then(()=>{var d;return(d=i.querySelector(".node.chosen"))==null?void 0:d.scrollIntoView({block:"nearest"})})}),e.addEventListener("app-ready",()=>{g=0,a.clear(),m(null),w()});const S=o=>{h=o,n.hidden=!o,t.setAttribute("aria-pressed",String(o)),o?w():(P(!1),e.highlight(null))};return t.addEventListener("click",()=>S(!h)),n.querySelector("#refresh").addEventListener("click",()=>void w()),m(null),{setOpen:S,get open(){return h}}},W=new URLSearchParams(location.search),u=document.getElementById("frame");u.player=W.get("player")??"player/?dev=../..";W.get("src")&&(u.src=W.get("src"));u.inspect=!0;D.noRoom&&location.replace(u.playerUrl);const K=e=>{try{return localStorage.getItem(`lecodes-dev:${e}`)}catch{return null}},U=(e,n)=>{try{localStorage.setItem(`lecodes-dev:${e}`,n)}catch{}},at="none",J=W.get("device")??K("device")??Y[0].id;u.device=J===at||ot(J)?J:Y[0].id;u.orientation=(W.get("orientation")??K("orientation"))==="landscape"?"landscape":"portrait";const ct=document.getElementById("devices"),Z=document.getElementById("rotate"),Pt=document.getElementById("scale"),X=()=>{for(const n of ct.querySelectorAll("button"))n.setAttribute("aria-pressed",String(n.dataset.device===u.device));const e=!u.bare;Z.disabled=!e||u.orientationLocked,Z.title=u.orientationLocked?"The app holds the orientation (app.setOrientation)":"Turn the device",Pt.textContent=e&&u.scale<1?`${Math.round(u.scale*100)}%`:""};for(const{id:e,name:n}of[...Y,{id:at,name:"No frame"}]){const t=document.createElement("button");t.type="button",t.dataset.device=e,t.textContent=n,t.addEventListener("click",()=>{u.device=e,U("device",e),X()}),ct.appendChild(t)}Z.addEventListener("click",()=>{u.rotate(),U("orientation",u.orientation)});document.getElementById("reload").addEventListener("click",()=>u.reload());u.addEventListener("app-orientation",X);new ResizeObserver(X).observe(u);X();const lt=document.getElementById("console"),O=document.getElementById("console-lines"),dt=document.getElementById("console-toggle"),nt=document.getElementById("console-count"),Nt=500;let T=0;const tt=e=>{lt.hidden=!e,dt.setAttribute("aria-pressed",String(e)),U("console",e?"1":"0")},et=()=>{nt.textContent=T>0?String(T):"",nt.classList.toggle("errors",T>0)},ht=(e,n,t)=>{const s=O.scrollTop+O.clientHeight>=O.scrollHeight-4,i=document.createElement("div");if(i.className=`line ${e}`,i.textContent=n,t){const r=document.createElement("span");r.className="where",r.textContent=`
49
- at ${t}`,i.appendChild(r)}for(O.appendChild(i);O.childElementCount>Nt;)O.firstElementChild.remove();s&&(O.scrollTop=O.scrollHeight)};u.addEventListener("app-log",e=>{var i;const{level:n,text:t}=e.detail,s=(i=/^\[(ERROR|WARN)\]/.exec(t))==null?void 0:i[1];ht(s==="ERROR"?"error":s==="WARN"?"warn":n,t)});u.addEventListener("app-error",e=>{var s,i;const n=e.detail,t=(s=n.frames)==null?void 0:s[0];T++,et(),ht("error",n.detail?`${n.message}: ${n.detail}`:n.message,t?`${t.source}:${t.line}:${t.column}${t.fn?` (${t.fn})`:""}`:(i=n.nativeFrames)==null?void 0:i[0]),tt(!0)});u.addEventListener("app-ready",()=>{T=0,et()});const pt=document.getElementById("inspect-toggle"),ut=Mt(u,document.getElementById("inspector"),pt);pt.addEventListener("click",()=>U("inspect",ut.open?"1":"0"));ut.setOpen(K("inspect")==="1");dt.addEventListener("click",()=>tt(lt.hidden));document.getElementById("console-clear").addEventListener("click",()=>{O.replaceChildren(),T=0,et()});tt(K("console")==="1");
48
+ `,jt=600,Bt=["player","src","device","orientation","language","bare","inspect"],zt=3e3,H=class H extends HTMLElement{constructor(){super();p(this,"stage");p(this,"body");p(this,"screen");p(this,"over");p(this,"marks");p(this,"keys");p(this,"pick");p(this,"found");p(this,"asked",new Map);p(this,"requests",0);p(this,"pointing",null);p(this,"pointed",!1);p(this,"iframe",null);p(this,"observer");p(this,"loaded",!1);p(this,"mounting",!1);p(this,"turned",null);p(this,"locked",!1);p(this,"shownScale",1);p(this,"toldScale",1);p(this,"onWindow",()=>{this.layout(),this.tellSafeArea()});p(this,"onMessage",t=>{var i;if(!this.iframe||t.source!==this.iframe.contentWindow)return;const e=t.data;if(!(!e||typeof e!="object"))switch(e.type){case"lecodes:ready":this.highlight(null),this.emit("app-ready",{src:String(e.src)});break;case"lecodes:error":this.emit("app-error",e.error);break;case"lecodes:log":this.emit("app-log",{level:e.level,text:String(e.text)});break;case"lecodes:inspected":(i=this.asked.get(e.request))==null||i(e.answer);break;case"lecodes:keyboard":this.drawKeyboard(e.height,e.duration);break;case"lecodes:orientation":{e.mode==="landscape"||e.mode==="portrait"?(this.turned=e.mode,this.locked=!0):this.locked=!1,this.layout(),this.tellSafeArea(),this.emit("app-orientation",{orientation:this.orientation,locked:this.locked});break}}});const t=this.attachShadow({mode:"open"}),e=this.ownerDocument,i=e.createElement("style");i.textContent=Rt;const r=a=>{const g=e.createElement("div");return g.className=a,g};this.stage=r("stage"),this.body=r("device"),this.screen=r("screen"),this.over=r("over"),this.marks=r("marks"),this.keys=r("keys"),Ot(this.keys),this.pick=r("pick"),this.found=r("found"),this.found.append(e.createElement("i"),e.createElement("b")),this.over.append(this.found,this.keys,this.marks,this.pick),this.pick.addEventListener("pointermove",a=>this.point(a)),this.pick.addEventListener("pointerleave",()=>{this.pointing=null,this.picking&&(this.highlight(null),this.emit("app-point",null))}),this.pick.addEventListener("click",a=>{this.at(a).then(g=>{g&&(this.highlight(g),this.emit("app-pick",g))})}),this.screen.appendChild(this.over),this.body.appendChild(this.screen),this.stage.appendChild(this.body),t.append(i,this.stage),this.observer=new ResizeObserver(()=>this.layout())}static get noRoom(){return window.innerWidth<jt}get player(){return this.getAttribute("player")??""}set player(t){this.setAttribute("player",t)}get src(){return this.getAttribute("src")??""}set src(t){this.setAttribute("src",t)}get device(){return this.getAttribute("device")??Y[0].id}set device(t){this.setAttribute("device",t)}get language(){return this.getAttribute("language")??""}set language(t){this.setAttribute("language",t)}get orientation(){return this.turned??(this.getAttribute("orientation")==="landscape"?"landscape":"portrait")}set orientation(t){this.turned=null,this.locked=!1,this.setAttribute("orientation",t)}get orientationLocked(){return this.locked}get bare(){return this.hasAttribute("bare")||this.current===null}get inspect(){return this.hasAttribute("inspect")}set inspect(t){this.toggleAttribute("inspect",t)}get picking(){return this.hasAttribute("picking")}set picking(t){this.toggleAttribute("picking",t),t||this.highlight(null)}get scale(){return this.shownScale}get current(){return this.hasAttribute("bare")||H.noRoom?null:ot(this.device)}get playerUrl(){return this.urlFor(null)}rotate(){this.locked||(this.orientation=this.orientation==="portrait"?"landscape":"portrait",this.emit("app-orientation",{orientation:this.orientation,locked:!1}))}ask(t){return new Promise(e=>{const i=++this.requests,r=a=>{this.asked.delete(i)&&e(a&&a.ask===t.ask?a:null)};this.asked.set(i,r),window.setTimeout(()=>r(null),zt),this.post({type:"lecodes:inspect",request:i,question:t})})}highlight(t){const e=this.found;if(!t){e.style.display="none";return}const{rect:i,padding:r}=t;Object.assign(e.style,{display:"block",left:`${i.x}px`,top:`${i.y}px`,width:`${i.width}px`,height:`${i.height}px`}),Object.assign(e.firstElementChild.style,{left:`${r.left}px`,top:`${r.top}px`,right:`${r.right}px`,bottom:`${r.bottom}px`}),e.lastElementChild.textContent=`${t.type}${t.name?` "${t.name}"`:""} ${Math.round(i.width)} × ${Math.round(i.height)}`,e.classList.toggle("low",i.y<24)}reload(){this.post({type:"lecodes:reload"})}connectedCallback(){this.observer.observe(this),window.addEventListener("message",this.onMessage),window.addEventListener("resize",this.onWindow),this.remount()}disconnectedCallback(){var t;this.observer.disconnect(),window.removeEventListener("message",this.onMessage),window.removeEventListener("resize",this.onWindow),(t=this.iframe)==null||t.remove(),this.iframe=null}attributeChangedCallback(t,e,i){if(!(!this.isConnected||e===i))switch(t){case"player":this.remount();break;case"src":this.loaded?i&&this.post({type:"lecodes:run",src:i}):this.remount();break;case"language":this.loaded?i&&this.post({type:"lecodes:language",language:i}):this.remount();break;case"inspect":this.remount();break;default:this.layout(),this.tellSafeArea()}}toApp(t){const e=this.screen.getBoundingClientRect();return{x:(t.clientX-e.left)/this.shownScale,y:(t.clientY-e.top)/this.shownScale}}async at(t){var e;return((e=await this.ask({ask:"at",...this.toApp(t)}))==null?void 0:e.node)??null}point(t){this.pointing=this.toApp(t),!this.pointed&&(this.pointed=!0,(async()=>{var e;for(;this.pointing&&this.picking;){const{x:i,y:r}=this.pointing;this.pointing=null;const a=((e=await this.ask({ask:"at",x:i,y:r}))==null?void 0:e.node)??null;if(!this.picking)break;this.highlight(a),this.emit("app-point",a)}this.pointed=!1})())}remount(){this.mounting||(this.mounting=!0,queueMicrotask(()=>{this.mounting=!1,this.isConnected&&this.mount()}))}mount(){var e;if((e=this.iframe)==null||e.remove(),this.iframe=null,this.loaded=!1,!this.player)return;this.layout();const t=this.ownerDocument.createElement("iframe");t.addEventListener("load",()=>{this.iframe===t&&(this.loaded=!0,this.tellSafeArea(),this.tellScale())}),t.setAttribute("allow","autoplay; fullscreen; camera; microphone; geolocation; clipboard-read; clipboard-write; gamepad"),t.title="LeCodes app",t.src=this.urlFor(this.current),this.screen.prepend(t),this.iframe=t}urlFor(t){var i;const e=new URL(this.player,this.ownerDocument.baseURI);if(this.src&&e.searchParams.set("src",this.src),this.language&&e.searchParams.set("lang",this.language),this.inspect&&e.searchParams.set("inspect",""),t&&this.shownScale!==1&&(e.searchParams.set("scale",String(this.shownScale)),this.toldScale=this.shownScale),t){const r=t.insets[this.orientation];e.searchParams.set("safe-area",[r.top,r.right,r.bottom,r.left].join(",")),(i=r.bars)!=null&&i.length&&e.searchParams.set("bars",r.bars.join(","));const a=this.keyboardBand(t);a>0&&e.searchParams.set("keyboard-band",String(a))}return e.href}layout(){const t=this.current;this.toggleAttribute("framed",t!==null);const e=this.body.style;if(!t){this.shownScale=1,Object.assign(e,{width:"100%",height:"100%",padding:"0",borderRadius:"0"}),this.screen.style.borderRadius="0",Object.assign(this.over.style,{width:"",height:"",zoom:""}),this.marks.replaceChildren(),this.tellScale();return}const i=this.orientation,r=Q(t,i),a=this.shownScale=kt(bt(t,i,{width:this.clientWidth,height:this.clientHeight}),r.width,window.devicePixelRatio||1);Object.assign(e,{width:`${r.width*a}px`,height:`${r.height*a}px`,padding:`${t.bezel*a}px`,borderRadius:`${(t.radius+t.bezel)*a}px`}),this.screen.style.borderRadius=`${t.radius*a}px`,Object.assign(this.over.style,{width:`${r.width}px`,height:`${r.height}px`,zoom:a===1?"":String(a)}),wt(this.marks,t,i),this.tellScale()}post(t){var i;const e=(i=this.iframe)==null?void 0:i.contentWindow;!e||!this.player||e.postMessage(t,new URL(this.player,this.ownerDocument.baseURI).origin)}tellSafeArea(){const t=this.current;this.post({type:"lecodes:safe-area",insets:t?{...t.insets[this.orientation]}:null});const e=t?this.keyboardBand(t):0;this.post({type:"lecodes:keyboard-band",height:e>0?e:null}),e===0&&this.drawKeyboard(0)}tellScale(){!this.loaded||this.toldScale===this.shownScale||(this.toldScale=this.shownScale,this.post({type:"lecodes:scale",scale:this.shownScale}))}keyboardBand(t){return this.orientation==="portrait"?t.keyboard:0}drawKeyboard(t,e=0){const i=this.current;if(!i||t<=0){Ct(this.keys,e);return}const r=Q(i,this.orientation);At(this.keys,{look:i.keyboardLook,language:this.language,width:r.width,height:t,inset:i.insets[this.orientation].bottom},e)}emit(t,e){this.dispatchEvent(new CustomEvent(t,{detail:e}))}};p(H,"observedAttributes",Bt);let D=H;const It=(s="lecodes-frame")=>{customElements.get(s)||customElements.define(s,D)};typeof customElements<"u"&&It();const S=(s,n,t)=>{const e=document.createElement(s);return n&&(e.className=n),t!==void 0&&(e.textContent=t),e},st=s=>s.type+(s.name?` "${s.name}"`:""),it=/^(width|height|minWidth|minHeight|maxWidth|maxHeight|aspectRatio|flex|flexGrow|flexShrink|flexBasis|flexDirection|flexWrap|alignItems|alignSelf|alignContent|justifyContent|gap|rowGap|columnGap|position|left|top|right|bottom|inset|display|overflow|zIndex|m|mx|my|mt|mr|mb|ml|margin\w*|p|px|py|pt|pr|pb|pl|padding\w*)$/,O=s=>typeof s=="string"?s:typeof s=="number"?String(Math.round(s*100)/100):Array.isArray(s)?s.map(O).join(" "):typeof s=="object"&&s!==null?Object.entries(s).map(([n,t])=>`${n}: ${O(t)}`).join(", "):String(s),Mt=(s,n,t)=>{const e=n.querySelector("#pick"),i=n.querySelector("#tree"),r=n.querySelector("#details"),a=new Set;let g=0,h=!1;const b=(o,c,d)=>{const l=S("div","node");l.dataset.id=String(o.id),l.style.paddingLeft=`${8+c*14}px`,l.classList.toggle("chosen",o.id===g);const E=S("span","fold",o.children.length===0?"":a.has(o.id)?"▸":"▾");if(E.addEventListener("click",C=>{C.stopPropagation(),a.has(o.id)?a.delete(o.id):a.add(o.id),w()}),l.append(E,S("span","type",o.type)),o.name&&l.append(S("span","name",` "${o.name}"`)),o.text&&l.append(S("span","text",` ${o.text.replace(/\s+/g," ")}`)),l.addEventListener("mouseenter",()=>s.highlight({...o,padding:{top:0,right:0,bottom:0,left:0}})),l.addEventListener("click",()=>void x(o.id)),d.append(l),!a.has(o.id))for(const C of o.children)b(C,c+1,d)},w=async()=>{if(!h)return;const o=await s.ask({ask:"tree"}),c=S("div");o||c.append(S("div","empty","No app is running."));for(const d of(o==null?void 0:o.layers)??[])d.kind==="widget"&&c.append(S("div","layer","widget")),b(d.tree,0,c);i.replaceChildren(c)};i.addEventListener("mouseleave",()=>{M()});const f=(o,c)=>{if(c.length===0)return null;const d=S("div","section");d.append(S("div","title",o));for(const[l,E]of c){const C=S("div","pair");C.append(S("span","key",l),S("span","value",E)),d.append(C)}return d},B=o=>typeof o=="object"&&o!==null&&!Array.isArray(o),j=(o,c)=>Object.entries(o??{}).filter(([d,l])=>c(d,l)).map(([d,l])=>[d,O(l)]),m=o=>{var _;if(!o){r.replaceChildren(S("div","empty","Pick an element: in the app, or in the outline."));return}const c=S("div","head",st(o)),d=S("div","path",o.path.slice(0,-1).map(st).join(" › ")),{rect:l,padding:E}=o,C=[f("Box",[["size",`${O(l.width)} × ${O(l.height)}`],["position",`${O(l.x)}, ${O(l.y)}`],...E.top||E.right||E.bottom||E.left?[["padding",`${E.top} ${E.right} ${E.bottom} ${E.left}`]]:[],...o.scroll?[["scroll",`${o.scroll.axis}: ${O(o.scroll.offset)} of ${O(o.scroll.content)}`]]:[],...(_=o.state)!=null&&_.length?[["state",o.state.join(", ")]]:[]]),o.text!==void 0?f("Text",[["",o.text]]):null,f("Layout",j(o.set,($,v)=>it.test($)&&!B(v))),f("Set by the app",j(o.set,($,v)=>!it.test($)&&!B(v))),...Object.entries(o.set??{}).filter(([,$])=>B($)).map(([$,v])=>f($,j(v,()=>!0))),f("Painted",Object.entries(o.style).map(([$,v])=>[$,O(v)])),o.source?f("Made at",[["",`${o.source.file}:${o.source.line}:${o.source.column}`]]):null];r.replaceChildren(c,d,...C.filter($=>$!==null))},x=async o=>{var d;g=o;const c=((d=await s.ask({ask:"node",node:o}))==null?void 0:d.node)??null;s.highlight(c),m(c);for(const l of i.querySelectorAll(".node"))l.classList.toggle("chosen",l.dataset.id===String(o))},M=async()=>{var o;s.picking||s.highlight(g?((o=await s.ask({ask:"node",node:g}))==null?void 0:o.node)??null:null)},P=o=>{s.picking=o,e.setAttribute("aria-pressed",String(o))};e.addEventListener("click",()=>P(!s.picking)),s.addEventListener("app-pick",o=>{const c=o.detail;P(!1),s.highlight(c),g=c.id;for(const d of c.path)a.delete(d.id);m(c),w().then(()=>{var d;return(d=i.querySelector(".node.chosen"))==null?void 0:d.scrollIntoView({block:"nearest"})})}),s.addEventListener("app-ready",()=>{g=0,a.clear(),m(null),w()});const L=o=>{h=o,n.hidden=!o,t.setAttribute("aria-pressed",String(o)),o?w():(P(!1),s.highlight(null))};return t.addEventListener("click",()=>L(!h)),n.querySelector("#refresh").addEventListener("click",()=>void w()),m(null),{setOpen:L,get open(){return h}}},W=new URLSearchParams(location.search),u=document.getElementById("frame");u.player=W.get("player")??"player/?dev=../..";W.get("src")&&(u.src=W.get("src"));u.inspect=!0;D.noRoom&&location.replace(u.playerUrl);const K=s=>{try{return localStorage.getItem(`lecodes-dev:${s}`)}catch{return null}},U=(s,n)=>{try{localStorage.setItem(`lecodes-dev:${s}`,n)}catch{}},at="none",J=W.get("device")??K("device")??Y[0].id;u.device=J===at||ot(J)?J:Y[0].id;u.orientation=(W.get("orientation")??K("orientation"))==="landscape"?"landscape":"portrait";const ct=document.getElementById("devices"),Z=document.getElementById("rotate"),Pt=document.getElementById("scale"),X=()=>{for(const n of ct.querySelectorAll("button"))n.setAttribute("aria-pressed",String(n.dataset.device===u.device));const s=!u.bare;Z.disabled=!s||u.orientationLocked,Z.title=u.orientationLocked?"The app holds the orientation (app.setOrientation)":"Turn the device",Pt.textContent=s&&u.scale<1?`${Math.round(u.scale*100)}%`:""};for(const{id:s,name:n}of[...Y,{id:at,name:"No frame"}]){const t=document.createElement("button");t.type="button",t.dataset.device=s,t.textContent=n,t.addEventListener("click",()=>{u.device=s,U("device",s),X()}),ct.appendChild(t)}Z.addEventListener("click",()=>{u.rotate(),U("orientation",u.orientation)});document.getElementById("reload").addEventListener("click",()=>u.reload());u.addEventListener("app-orientation",X);new ResizeObserver(X).observe(u);X();const lt=document.getElementById("console"),R=document.getElementById("console-lines"),dt=document.getElementById("console-toggle"),nt=document.getElementById("console-count"),Nt=500;let T=0;const tt=s=>{lt.hidden=!s,dt.setAttribute("aria-pressed",String(s)),U("console",s?"1":"0")},et=()=>{nt.textContent=T>0?String(T):"",nt.classList.toggle("errors",T>0)},ht=(s,n,t)=>{const e=R.scrollTop+R.clientHeight>=R.scrollHeight-4,i=document.createElement("div");if(i.className=`line ${s}`,i.textContent=n,t){const r=document.createElement("span");r.className="where",r.textContent=`
49
+ at ${t}`,i.appendChild(r)}for(R.appendChild(i);R.childElementCount>Nt;)R.firstElementChild.remove();e&&(R.scrollTop=R.scrollHeight)};u.addEventListener("app-log",s=>{var i;const{level:n,text:t}=s.detail,e=(i=/^\[(ERROR|WARN)\]/.exec(t))==null?void 0:i[1];ht(e==="ERROR"?"error":e==="WARN"?"warn":n,t)});u.addEventListener("app-error",s=>{var e,i;const n=s.detail,t=(e=n.frames)==null?void 0:e[0];T++,et(),ht("error",n.detail?`${n.message}: ${n.detail}`:n.message,t?`${t.source}:${t.line}:${t.column}${t.fn?` (${t.fn})`:""}`:(i=n.nativeFrames)==null?void 0:i[0]),tt(!0)});u.addEventListener("app-ready",()=>{T=0,et()});const pt=document.getElementById("inspect-toggle"),ut=Mt(u,document.getElementById("inspector"),pt);pt.addEventListener("click",()=>U("inspect",ut.open?"1":"0"));ut.setOpen(K("inspect")==="1");dt.addEventListener("click",()=>tt(lt.hidden));document.getElementById("console-clear").addEventListener("click",()=>{R.replaceChildren(),T=0,et()});tt(K("console")==="1");
@@ -484,7 +484,7 @@ const sync = defineCommand({
484
484
  name: "sync",
485
485
  summary: "Make every existing shell match the project: bundle + assets + SDK variant + plugins + CLI-owned files",
486
486
  usage: "[ios|android]",
487
- description: "Compiles the bundle and embeds the assets it references (ios/App/Resources/ resp. the android assets/; offline by default, also for server projects — app.json \"resources\": {\"remote\": [globs]} keeps heavy assets streaming), picks the SDK variant from the bundle's engine use, vendors the app.json plugins (pinned in plugins.lock.json) and regenerates the CLI-owned shell files. User files (ios/App/*.swift, android/app/) are never touched.\n\nNative OTA JS updates at launch (both shells): the first launch of an installed build waits (≤ 5 s, the splash held) for the check and runs the newest bundle; every later launch runs the newest LOCAL bundle and a download applies on the next. On by default for a pushed project (its bundle on the platform, the launcher's own URL), app.json \"update\": {\"url\": …} for custom hosting, \"update\": false turns it off. A download runs only when it is of the shell's SDK major and not older than the bundle running; a store update keeps a downloaded bundle only when it is newer code than the one it ships. app.json \"icon\" (a square ≥1024px image) renders the app icons every sync (\"iconBackground\": the adaptive / flatten backdrop). app.json \"splash\" ({\"background\": \"#RRGGBB\", \"image\": \"./splash.png\", \"size\": 160}, all optional) renders the launch screen of both shells every sync — the colour with the image fitted centred into a size × size pt / dp square, 160 by default (iOS LaunchScreen.storyboard, Android the system splash of Theme.LeCodesRuntime, which the app's theme inherits). app.json \"urlScheme\" (\"myshop\") is the scheme the shells open on: iOS CFBundleURLTypes, Android a BROWSABLE intent-filter — a link arrives as app.launchUrl / the \"url\" event (the return of Browser.open with a returnUrl). app.json \"appLinks\" ([\"https://shop.example/pay/\"]) are the verified https links they open on — iOS universal links (associated-domains in App.entitlements), Android App Links (autoVerify intent-filters in the manifest); sync prints the apple-app-site-association / assetlinks.json each site must serve. No platform = every shell that exists; a dual-shell project compiles once.",
487
+ description: "Compiles the bundle and embeds the assets it references (ios/App/Resources/ resp. the android assets/; offline by default, also for server projects — app.json \"resources\": {\"remote\": [globs]} keeps heavy assets streaming), picks the SDK variant from the bundle's engine use, vendors the app.json plugins (pinned in plugins.lock.json) and regenerates the CLI-owned shell files. User files (ios/App/*.swift, android/app/) are never touched.\n\nNative OTA JS updates at launch (both shells): the first launch of an installed build waits (≤ 5 s, the splash held) for the check and runs the newest bundle (a source not newer than the build answers 304 to the build's compile date: nothing downloaded, the embedded bundle runs); every later launch runs the newest LOCAL bundle and a download applies on the next. On by default for a pushed project (its bundle on the platform, the launcher's own URL), app.json \"update\": {\"url\": …} for custom hosting, \"update\": false turns it off. A download runs only when it is of the shell's SDK major and not older than the bundle running; a store update keeps a downloaded bundle only when it is newer code than the one it ships. app.json \"icon\" (a square ≥1024px image) renders the app icons every sync (\"iconBackground\": the adaptive / flatten backdrop). app.json \"splash\" ({\"background\": \"#RRGGBB\", \"image\": \"./splash.png\", \"size\": 160}, all optional) renders the launch screen of both shells every sync — the colour with the image fitted centred into a size × size pt / dp square, 160 by default (iOS LaunchScreen.storyboard, Android the system splash of Theme.LeCodesRuntime, which the app's theme inherits). app.json \"urlScheme\" (\"myshop\") is the scheme the shells open on: iOS CFBundleURLTypes, Android a BROWSABLE intent-filter — a link arrives as app.launchUrl / the \"url\" event (the return of Browser.open with a returnUrl). app.json \"appLinks\" ([\"https://shop.example/pay/\"]) are the verified https links they open on — iOS universal links (associated-domains in App.entitlements), Android App Links (autoVerify intent-filters in the manifest); sync prints the apple-app-site-association / assetlinks.json each site must serve. No platform = every shell that exists; a dual-shell project compiles once.",
488
488
  examples: ["lecodes app sync", "lecodes app sync android"],
489
489
  run: ({ args }) => runSync(args[0]),
490
490
  })
@@ -86,8 +86,10 @@ export interface AppConfig {
86
86
  resources?: { remote?: string[] }
87
87
  /** Over-the-air JS updates: where the shell checks for newer compiled code at launch
88
88
  * (natively, before the JS engine — offline is a no-op). The first launch of an installed
89
- * build waits for the check (≤ 5 s, the splash held) and runs the newest bundle; every later
90
- * launch runs the local bundle at once and new code applies next launch.
89
+ * build waits for the check (≤ 5 s, the splash held) and runs the newest bundle — a 304 when
90
+ * the source is not newer than the build (If-Modified-Since from the embedded bundle's compile
91
+ * date: nothing downloaded, the embedded bundle runs); every later launch runs the local
92
+ * bundle at once and new code applies next launch.
91
93
  * `true` = this project's live bundle on the platform (server-backed only); or an https
92
94
  * URL to a compiled bundle on any hosting (string or `{ "url": … }`). Absent = no OTA.
93
95
  * Both shells: iOS reads it from Info.plist (LeCodesUpdater), Android from the manifest
@@ -193,9 +193,9 @@ export const runtimeThemeTemplate = (splash?: AndroidSplashResources): string =>
193
193
 
194
194
  /** lecodes-runtime/src/main/res/values-v31/lecodes_splash.xml — only with app.json "splash": the
195
195
  * theme's Android 12+ twin (a v31 style replaces the base one whole, hence every item again) adding
196
- * the system splash — the colour, and as the icon the image inset (288 − size) / 2 dp on each side
197
- * (the 288 dp icon canvas with the image at its size, 160 dp by default) or nothing (transparent: the system would draw the launcher
198
- * icon otherwise). */
196
+ * the system splash — the colour, and as the icon the image inset (288 − size) / 2 of the 288 dp
197
+ * icon canvas on each side (a fraction: splashIconTemplate says why) or nothing (transparent: the
198
+ * system would draw the launcher icon otherwise). */
199
199
  export const runtimeThemeV31Template = (splash: AndroidSplashResources): string => `<?xml version="1.0" encoding="utf-8"?>
200
200
  <!-- Generated by \`lecodes app sync\` from app.json "splash" — regenerated on every sync, do not edit. -->
201
201
  <resources>
@@ -218,14 +218,23 @@ export const splashWindowTemplate = (splash: AndroidSplashResources): string =>
218
218
 
219
219
  /** lecodes-runtime/src/main/res/drawable/lecodes_splash_icon.xml — Android 12+'s splash icon: the
220
220
  * size dp image inset to the 288 dp canvas the system draws (and masks to a 192 dp circle). The
221
- * inset is rounded down — an odd size leaves the image half a dp larger, which nobody sees. */
221
+ * inset is a FRACTION of the canvas, never dp: the system pre-draws the icon into a bitmap of its
222
+ * default icon size (108 dp, SplashscreenIconDrawableFactory.ImmobileIconDrawable) and scales that
223
+ * bitmap up to the splash's 192 dp — a dp inset is taken off the small canvas as it is (the image
224
+ * gets what is left of the 1.5 × 108 = 162 dp layer: 34 dp at the default inset of 64, 4 dp — a dot
225
+ * — at size 130), a fraction scales with it. */
222
226
  export const splashIconTemplate = (size: number): string => `<?xml version="1.0" encoding="utf-8"?>
223
227
  <!-- Generated by \`lecodes app sync\` from app.json "splash" — regenerated on every sync, do not edit. -->
224
228
  <inset xmlns:android="http://schemas.android.com/apk/res/android"
225
229
  android:drawable="@drawable/lecodes_splash"
226
- android:inset="${Math.floor((SPLASH_ICON_CANVAS_DP - size) / 2)}dp" />
230
+ android:inset="${splashIconInsetPercent(size)}%" />
227
231
  `
228
232
 
233
+ /** The inset of the splash icon as a percentage of its 288 dp canvas, (288 − size) / 2 of 288, at
234
+ * most four decimals (0.0001 % of 288 dp is nothing). */
235
+ export const splashIconInsetPercent = (size: number): string =>
236
+ (((SPLASH_ICON_CANVAS_DP - size) / 2 / SPLASH_ICON_CANVAS_DP) * 100).toFixed(4).replace(/\.?0+$/, "")
237
+
229
238
  /** lecodes-runtime/src/main/kotlin/io/letary/lecodes/runtime/LeCodesRuntime.kt — the generated
230
239
  * registration + host-hook surface. Plugins are called by FQN (no import collisions); the
231
240
  * activity hooks forward to the SDK's LeCodesAppHooks, which NAMES NO PLUGIN — a plugin that
@@ -426,8 +435,9 @@ colour (\`background\`, default \`"iconBackground"\`, default white) with the im
426
435
  a \`size\`×\`size\` dp square (160 by default) — never at its pixel size — from
427
436
  \`drawable-mdpi…xxxhdpi/lecodes_splash.png\` (size × 1/1.5/2/3/4 px: 160…640 at the default) of the one
428
437
  master. Android 12+ shows it as the system splash (\`windowSplashScreenBackground\` +
429
- \`windowSplashScreenAnimatedIcon\`: the image on the 288 dp icon canvas, inset (288 − size) / 2 dp,
430
- of which the system shows only the inner 192 dp circle — keep the mark inside one; a \`size\` above
438
+ \`windowSplashScreenAnimatedIcon\`: the image on the 288 dp icon canvas, inset (288 − size) / 2 on
439
+ each side as a fraction of the canvas — the system draws the icon small and scales it up, so a dp
440
+ inset would leave a dot — of which the system shows only the inner 192 dp circle — keep the mark inside one; a \`size\` above
431
441
  192 is cut there, sync warns, and 288 is the most it takes), API 23–30 as the window background. Use
432
442
  a square mark of at least 3 × size px (a non-square one is fitted into the square, a smaller one is
433
443
  upscaled). No image = the colour