code wiki / _hdl_build / nx_reader_zoom_emit.nx

nx_reader_zoom_emit.nx source

↩ module page · 228 lines · 30785 B

1// nx_reader_zoom_emit.nx -- EMITS the sovereign INTELLIGENT-ZOOM viewer (census SC17 auto-zoom-to-panel + the 2// ZUM tiled-zoom gap; ComiXology-class guided view). Organ-EMITTED per doctrine (SC58: no hand-dropped HTML; the 3// zero-JS TEXT reader stays untouched -- this is the comic/scan MODE, where zoom+pan interactivity IS the 4// product). Written to BOTH web_assets/nx_zoom_reader.html (durable demo) AND knowledge/staging/media/zoom.html 5// (served ungated by the reader daemon at /zoom, like /reader + /cbx -- the shell holds no book data; every DATA 6// fetch carries the no-cookie X-Nishi-Session token from sessionStorage.nx_sess, and tiles load via 7// fetch->blob->objectURL because an <img src> cannot carry the header). 8// modes: ?slug=<slug>[&page=N][&rtl=0|1] = the real reader (cbx.json page list via /api/book; lazy 9// /api/zoomprep on a missing pyramid; page prev/next with guided continuity: past the last panel -> next 10// page's first panel; before the first -> prev page's last panel; background-preps the next page) 11// ?base=<dir-url> = direct demo over any nx_reader_page_prep output dir 12// reading: SCROLL (webtoon long-strip, fit-width full pages, IntersectionObserver lazy load) is the default 13// daily-driver; ZOOM is the guided/tiled DZI viewer. Toggle persists in localStorage (nx_rd_mode). Zoom viewer: 14// only-visible-tile fetch per zoom level (DZI-class) . pinch/wheel/drag . double-tap a panel = INTELLIGENT zoom 15// to that panel's rect . tap-zones (l/r page turn, mid controls) . fit page/width . 0-panel pages fall back to 16// whole-page zoom. The emitted file contains NO double-quote characters (single-quote HTML/JS throughout) and the 17// emitter REFUSES to write on any sovereignty violation (a stray double quote or an external <script src). 18// Deterministic: same organ -> byte-identical files. license_tier: ORIGINAL 19import "nx_syscalls.nx" 20const K_MAGIC_131072: i64 = 131072 21 22func za(dst: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { dst[o + i] = s[i]; i = i + 1 } return o + i } 23 24// build the full viewer HTML into dst; returns length. One line per za call; NO double quotes anywhere. 25func zr_build(dst: *u8) -> i64 { 26 var o: i64 = 0 27 o = za(dst, o, "<!doctype html>\n" as *u8) 28 o = za(dst, o, "<html><head><meta charset=utf-8><meta name=viewport content='width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no'>\n" as *u8) 29 o = za(dst, o, "<title>Nishi Zoom Reader</title>\n" as *u8) 30 o = za(dst, o, "<style>\n" as *u8) 31 o = za(dst, o, "html,body{margin:0;height:100%;background:#111;color:#ddd;font:14px system-ui;overflow:hidden}\n" as *u8) 32 o = za(dst, o, "#vp{position:fixed;inset:0;overflow:hidden;touch-action:none;cursor:grab}\n" as *u8) 33 o = za(dst, o, "#scroll{position:fixed;inset:0;overflow-y:auto;overflow-x:hidden;background:#111;display:none;-webkit-overflow-scrolling:touch;overscroll-behavior:contain}\n" as *u8) 34 o = za(dst, o, "#strip{max-width:1000px;margin:0 auto;padding-bottom:96px}\n" as *u8) 35 o = za(dst, o, "#strip .pg{min-height:60vh;margin-bottom:2px;background:#0d0d0d;position:relative}\n" as *u8) 36 o = za(dst, o, "#strip .pg img{width:100%;height:auto;display:block}\n" as *u8) 37 o = za(dst, o, "#world{position:absolute;left:0;top:0;transform-origin:0 0;will-change:transform}\n" as *u8) 38 o = za(dst, o, "#world img{position:absolute;display:block;user-select:none;-webkit-user-drag:none;pointer-events:none}\n" as *u8) 39 o = za(dst, o, "#bar{position:fixed;left:50%;bottom:max(12px,env(safe-area-inset-bottom));transform:translateX(-50%);display:flex;flex-wrap:wrap;justify-content:center;max-width:calc(100vw - 20px);gap:8px;align-items:center;background:rgba(20,20,24,.88);border:1px solid #333;border-radius:12px;padding:8px 12px;z-index:99}\n" as *u8) 40 o = za(dst, o, "#bar button{background:#26262c;color:#ddd;border:1px solid #444;border-radius:8px;min-width:44px;min-height:44px;font-size:16px}\n" as *u8) 41 o = za(dst, o, "#bar button:active{background:#3a3a44}\n" as *u8) 42 o = za(dst, o, "#bar button.on{background:#2f6d9e;border-color:#4f95cf;color:#fff}\n" as *u8) 43 o = za(dst, o, "#st{min-width:84px;text-align:center;color:#9a9}\n" as *u8) 44 o = za(dst, o, "#nl{position:fixed;left:10px;right:10px;top:10px;max-height:62%;overflow:auto;background:rgba(15,15,18,.96);border:1px solid #333;border-radius:12px;padding:10px;display:none;z-index:98;font-size:14px}\n" as *u8) 45 o = za(dst, o, "#nl .ni{padding:8px 6px;border-bottom:1px solid #222;cursor:pointer}\n" as *u8) 46 o = za(dst, o, "#nl .ni b{color:#8ac}\n" as *u8) 47 o = za(dst, o, "#hlp{position:fixed;inset:6% 8% auto;max-height:80%;overflow:auto;background:rgba(15,15,18,.97);border:1px solid #333;border-radius:12px;padding:16px 20px;display:none;z-index:100;line-height:2}#hlp b{color:#8ac;display:block;margin:8px 0 4px}#hlp kbd{background:#26262c;border:1px solid #555;border-radius:5px;padding:1px 8px;margin-right:8px;font-family:monospace;color:#eee}\n" as *u8) 48 o = za(dst, o, "</style></head><body>\n" as *u8) 49 o = za(dst, o, "<div id=vp><div id=world></div></div>\n" as *u8) 50 o = za(dst, o, "<div id=scroll><div id=strip></div></div>\n" as *u8) 51 o = za(dst, o, "<div id=bar><button id=mg>Guided</button><button id=ms>Scroll</button><button id=mz>Zoom</button><button id=dirb>R2L</button><button id=pgp>&#9198;</button><button id=prev>&#9664;</button><span id=st>-</span><button id=next>&#9654;</button><button id=pgn>&#9197;</button><button id=zi>+</button><button id=zo>&#8722;</button><button id=noteb>&#9998;</button><button id=nlb>&#9776;</button><button id=fitb>Fit</button><button id=hlpb>?</button><button id=xb title=Close>&#10005;</button></div>\n" as *u8) 52 o = za(dst, o, "<div id=nl></div><div id=hlp></div>\n" as *u8) 53 o = za(dst, o, "<script>\n" as *u8) 54 o = za(dst, o, "(function(){ // nx-zoom-reader v4 (organ-emitted, sovereign, zero external loads; guided/scroll/zoom)\n" as *u8) 55 o = za(dst, o, "var q=new URLSearchParams(location.search),slug=q.get('slug')||'',demo=q.get('base')||'';\n" as *u8) 56 o = za(dst, o, "var vp=document.getElementById('vp'),world=document.getElementById('world'),st=document.getElementById('st');\n" as *u8) 57 o = za(dst, o, "var dz=null,pans=null,S=1,TX=0,TY=0,cur=-1,tiles={},ntiles=0;\n" as *u8) 58 o = za(dst, o, "var pages=[],pg=parseInt(q.get('page')||'0')||0,base='';\n" as *u8) 59 o = za(dst, o, "var mode='guided',fitMode='page',slots=[],stripBuilt=0,io=null,rtl=1,userMode=0,guides=[],gn=0;\n" as *u8) 60 o = za(dst, o, "var scrollEl=document.getElementById('scroll'),strip=document.getElementById('strip');\n" as *u8) 61 o = za(dst, o, "var downX=0,downY=0,downT=0,moved=false,tapTimer=0,lastTapT=0,lastSaveT=0,schedT=0;\n" as *u8) 62 o = za(dst, o, "var _rp=q.get('rtl');if(_rp==='0'||_rp==='1'){rtl=parseInt(_rp)}else{try{var _lr=localStorage.nx_rd_rtl;if(_lr==='0'||_lr==='1')rtl=parseInt(_lr)}catch(_){}}\n" as *u8) 63 o = za(dst, o, "try{var _m=localStorage.nx_rd_mode;if(_m==='scroll'||_m==='zoom'||_m==='guided'){mode=_m;userMode=1}}catch(_){}\n" as *u8) 64 o = za(dst, o, "try{var _fm=localStorage.nx_rd_fit;if(_fm==='page'||_fm==='width')fitMode=_fm}catch(_){}\n" as *u8) 65 o = za(dst, o, "if(demo&&mode==='scroll')mode='guided';\n" as *u8) 66 o = za(dst, o, "function hdr(){var h={};try{if(sessionStorage.nx_sess)h['X-Nishi-Session']=sessionStorage.nx_sess}catch(_){}return h}\n" as *u8) 67 o = za(dst, o, "function jf(u,o2){var op=o2||{};op.headers=hdr();return fetch(u,op).then(function(r){if(r.status===401){st.textContent='login...';setTimeout(function(){location='.'},900);throw 0}return r})}\n" as *u8) 68 o = za(dst, o, "function vw(){return vp.clientWidth}function vh(){return vp.clientHeight}\n" as *u8) 69 o = za(dst, o, "function apply(){world.style.transform='translate('+TX+'px,'+TY+'px) scale('+S+')';render()}\n" as *u8) 70 o = za(dst, o, "function lvl(){var l=dz.maxlevel-Math.floor(Math.log(Math.max(1,1/S))/Math.LN2);if(l<0)l=0;if(l>dz.maxlevel)l=dz.maxlevel;return l}\n" as *u8) 71 o = za(dst, o, "function ldims(l){var sc=Math.pow(2,dz.maxlevel-l);var w=Math.max(8,Math.floor(Math.ceil(dz.width/sc)/8)*8),h=Math.max(8,Math.floor(Math.ceil(dz.height/sc)/8)*8);return[w,h,sc]}\n" as *u8) 72 o = za(dst, o, "function render(){if(!dz)return;if(dz.raw)return;var l=lvl(),d=ldims(l),lw=d[0],lh=d[1],T=dz.tilesize;\n" as *u8) 73 o = za(dst, o, "var fx=dz.width/lw,fy=dz.height/lh;\n" as *u8) 74 o = za(dst, o, "var x0=(-TX)/S,y0=(-TY)/S,x1=x0+vw()/S,y1=y0+vh()/S;\n" as *u8) 75 o = za(dst, o, "var c0=Math.max(0,Math.floor(x0/fx/T)),r0=Math.max(0,Math.floor(y0/fy/T));\n" as *u8) 76 o = za(dst, o, "var c1=Math.min(Math.ceil(lw/T)-1,Math.floor(x1/fx/T)),r1=Math.min(Math.ceil(lh/T)-1,Math.floor(y1/fy/T));\n" as *u8) 77 o = za(dst, o, "for(var r=r0;r<=r1;r++){for(var c=c0;c<=c1;c++){var k=l+'_'+c+'_'+r;if(tiles[k])continue;\n" as *u8) 78 o = za(dst, o, "var im=new Image();\n" as *u8) 79 o = za(dst, o, "var tw=Math.min(T,lw-c*T),th=Math.min(T,lh-r*T);\n" as *u8) 80 o = za(dst, o, "im.style.left=(c*T*fx)+'px';im.style.top=(r*T*fy)+'px';im.style.width=(tw*fx)+'px';im.style.height=(th*fy)+'px';im.style.zIndex=l;\n" as *u8) 81 o = za(dst, o, "world.appendChild(im);tiles[k]=im;ntiles++;\n" as *u8) 82 o = za(dst, o, "(function(url,img){jf(url).then(function(r){return r.blob()}).then(function(b){img.src=URL.createObjectURL(b)}).catch(function(){})})(base+'/'+l+'/'+c+'_'+r+'.jpg',im);}}\n" as *u8) 83 o = za(dst, o, "if(ntiles>240){for(var kk in tiles){if(parseInt(kk)!==l&&parseInt(kk)!==l-1){var oi=tiles[kk];if(oi.src&&oi.src.indexOf('blob:')===0)URL.revokeObjectURL(oi.src);world.removeChild(oi);delete tiles[kk];ntiles--;}}}}\n" as *u8) 84 o = za(dst, o, "function stTxt(){var s='';if(pages.length)s='p'+(pg+1)+'/'+pages.length;if(mode!=='scroll'){s+=' '+(cur<0?'-':(cur+1))+'/'+(gn||'-')}st.textContent=s||'-'}\n" as *u8) 85 o = za(dst, o, "function fit(){if(!dz)return;if(fitMode==='width'){S=vw()/dz.width;TX=(vw()-dz.width*S)/2;TY=0}else{S=Math.min(vw()/dz.width,vh()/dz.height);TX=(vw()-dz.width*S)/2;TY=(vh()-dz.height*S)/2}cur=-1;stTxt();apply()}\n" as *u8) 86 o = za(dst, o, "function zoomRect(x,y,w,h){var s=Math.min(vw()/w,vh()/h)*0.92;S=s;TX=(vw()-w*s)/2-x*s;TY=(vh()-h*s)/2-y*s;\n" as *u8) 87 o = za(dst, o, "world.style.transition='transform .28s ease';apply();setTimeout(function(){world.style.transition=''},300)}\n" as *u8) 88 o = za(dst, o, "function go(i){if(!gn){fit();return}if(i<0)i=0;if(i>=gn)i=gn-1;cur=i;var p=guides[i];zoomRect(p[0],p[1],p[2],p[3]);stTxt()}\n" as *u8) 89 o = za(dst, o, "function clearWorld(){for(var k in tiles){var oi=tiles[k];if(oi.src&&oi.src.indexOf('blob:')===0)URL.revokeObjectURL(oi.src)}world.innerHTML='';tiles={};ntiles=0;dz=null;pans=null;cur=-1}\n" as *u8) 90 o = za(dst, o, "function stem(f){var d=f.lastIndexOf('.');return d<0?f:f.slice(0,d)}\n" as *u8) 91 o = za(dst, o, "function prep(i){var f=pages[i]&&pages[i].file;if(!f)return Promise.reject(0);return jf('api/zoomprep?slug='+encodeURIComponent(slug)+'&file='+encodeURIComponent(f)+'&rtl='+rtl)}\n" as *u8) 92 o = za(dst, o, "function openPage(i,land){if(i<0||(pages.length&&i>=pages.length))return;pg=i;clearWorld();stTxt();st.textContent='loading...';\n" as *u8) 93 o = za(dst, o, "var f=pages.length?pages[i].file:'';base=demo||('api/book?p=reader/'+slug+'/zoom/'+stem(f));\n" as *u8) 94 o = za(dst, o, "function ld(){return jf(base+'/dz.json').then(function(r){if(!r.ok)throw 1;return r.json()})}\n" as *u8) 95 o = za(dst, o, "ld().catch(function(e){if(demo)throw e;return prep(i).then(ld)}).then(function(j){dz=j;\n" as *u8) 96 o = za(dst, o, "if(dz&&dz.raw){var rf=pages.length?pages[pg].file:'';var rim=new Image();rim.style.position='absolute';rim.style.left='0px';rim.style.top='0px';rim.style.width=dz.width+'px';rim.style.height=dz.height+'px';world.appendChild(rim);tiles['raw']=rim;jf('api/book?p=reader/'+slug+'/'+rf).then(function(r){return r.blob()}).then(function(b){rim.src=URL.createObjectURL(b)}).catch(function(){});}\n" as *u8) 97 o = za(dst, o, "return jf(base+'/panels.json').then(function(r){return r.ok?r.json():null}).catch(function(){return null})\n" as *u8) 98 o = za(dst, o, "}).then(function(p){pans=(p&&p.n>0)?p:null;computeGuides();fit();if(mode==='guided')go(land===2?gn-1:0);\n" as *u8) 99 o = za(dst, o, "if(!demo&&pages.length&&pg+1<pages.length)prep(pg+1).catch(function(){});\n" as *u8) 100 o = za(dst, o, "if(!demo&&pages.length&&pg+2<pages.length)prep(pg+2).catch(function(){});\n" as *u8) 101 o = za(dst, o, "savePos()\n" as *u8) 102 o = za(dst, o, "}).catch(function(){\n" as *u8) 103 o = za(dst, o, "var rf2=pages.length?pages[pg].file:'';if(!rf2){st.textContent='no page';return}\n" as *u8) 104 o = za(dst, o, "var rim2=new Image();rim2.style.position='absolute';rim2.style.left='0px';rim2.style.top='0px';\n" as *u8) 105 o = za(dst, o, "rim2.onload=function(){dz={raw:1,width:rim2.naturalWidth,height:rim2.naturalHeight,tilesize:256,maxlevel:0};rim2.style.width=dz.width+'px';rim2.style.height=dz.height+'px';world.appendChild(rim2);tiles['raw']=rim2;pans=null;computeGuides();stTxt();fit();if(mode==='guided')go(0)};\n" as *u8) 106 o = za(dst, o, "jf('api/book?p=reader/'+slug+'/'+rf2).then(function(r){return r.blob()}).then(function(b){rim2.src=URL.createObjectURL(b)}).catch(function(){st.textContent='no page'})\n" as *u8) 107 o = za(dst, o, "})}\n" as *u8) 108 o = za(dst, o, "var anns=[],nl=document.getElementById('nl');\n" as *u8) 109 o = za(dst, o, "function esc(s){return String(s).replace(/[<&>]/g,function(c){return c==='<'?'&lt;':c==='>'?'&gt;':'&amp;'})}\n" as *u8) 110 o = za(dst, o, "function saveAnns(){if(demo||!slug)return;jf('api/ann?slug='+encodeURIComponent(slug),{method:'POST',body:JSON.stringify(anns)}).catch(function(){})}\n" as *u8) 111 o = za(dst, o, "function savePos(){if(demo||!pages.length||!slug)return;jf('api/pos?slug='+encodeURIComponent(slug),{method:'POST',body:JSON.stringify({page:pg,ts:Date.now()})}).catch(function(){})}\n" as *u8) 112 o = za(dst, o, "function toggleBar(){var b=document.getElementById('bar');b.style.display=b.style.display==='none'?'flex':'none'}\n" as *u8) 113 o = za(dst, o, "function closeOverlays(){document.getElementById('hlp').style.display='none';nl.style.display='none'}\n" as *u8) 114 o = za(dst, o, "function loadSlot(d){if(!d||d.dataset.l)return;var i=parseInt(d.dataset.i);var f=pages[i]&&pages[i].file;if(!f)return;d.dataset.l='1';var im=d.querySelector('img');jf('api/book?p=reader/'+slug+'/'+f).then(function(r){if(!r.ok)throw 0;return r.blob()}).then(function(b){im.src=URL.createObjectURL(b)}).catch(function(){d.dataset.l=''})}\n" as *u8) 115 o = za(dst, o, "function buildScroll(){strip.innerHTML='';slots=[];io=new IntersectionObserver(function(es){for(var i=0;i<es.length;i++){if(es[i].isIntersecting)loadSlot(es[i].target)}},{root:scrollEl,rootMargin:'900px 0px'});for(var i=0;i<pages.length;i++){var d=document.createElement('div');d.className='pg';d.dataset.i=i;var im=document.createElement('img');d.appendChild(im);strip.appendChild(d);slots.push(d);io.observe(d)}stripBuilt=1}\n" as *u8) 116 o = za(dst, o, "function scrollToPage(i){if(i<0)i=0;if(pages.length&&i>=pages.length)i=pages.length-1;if(!slots||!slots[i])return;loadSlot(slots[i]);if(slots[i-1])loadSlot(slots[i-1]);if(slots[i+1])loadSlot(slots[i+1]);slots[i].scrollIntoView();pg=i;stTxt()}\n" as *u8) 117 o = za(dst, o, "function onScroll(){if(!stripBuilt||schedT)return;schedT=1;requestAnimationFrame(function(){schedT=0;var best=-1;for(var i=0;i<slots.length;i++){var r=slots[i].getBoundingClientRect();if(r.bottom>60){best=i;break}}if(best>=0&&best!==pg){pg=best;stTxt()}var now=Date.now();if(now-lastSaveT>1000){lastSaveT=now;savePos()}})}\n" as *u8) 118 o = za(dst, o, "function updateControls(){var z=['prev','next','zi','zo','pgp','pgn'];var d=mode==='scroll'?'none':'';for(var i=0;i<z.length;i++){var el=document.getElementById(z[i]);if(el)el.style.display=d}}\n" as *u8) 119 o = za(dst, o, "function updateModeBtns(){var mp={guided:'mg',scroll:'ms',zoom:'mz'};for(var k in mp){var b=document.getElementById(mp[k]);if(b)b.className=(k===mode?'on':'')}}\n" as *u8) 120 o = za(dst, o, "function updateDirBtn(){var b=document.getElementById('dirb');if(b)b.textContent=rtl?'R2L':'L2R'}\n" as *u8) 121 o = za(dst, o, "function cycleMode(){var od=['guided','scroll','zoom'];var i=od.indexOf(mode);if(i<0)i=0;setMode(od[(i+1)%3])}\n" as *u8) 122 o = za(dst, o, "function synthSlices(W,H){if(!W||!H)return [[0,0,W||1,H||1]];var sh=vh()*W/vw();if(sh<=0||sh>=H)return [[0,0,W,H]];var out=[],y=0;while(y<H-2){var hh=H-y;if(hh>sh)hh=sh;out.push([0,y,W,hh]);y=y+sh}return out.length?out:[[0,0,W,H]]}\n" as *u8) 123 o = za(dst, o, "function textish(p,W,H){if(!W||!H)return true;if(p.n>40)return true;var ln=0,i=0;for(i=0;i<p.n;i++){var r=p.panels[i];if(r[2]>=0.5*W&&r[3]<=0.14*H)ln=ln+1}return p.n>=5&&ln>=0.6*p.n}\n" as *u8) 124 o = za(dst, o, "function computeGuides(){var W=dz?dz.width:0,H=dz?dz.height:0;if(pans&&pans.n>=2&&!textish(pans,W,H)){guides=pans.panels;gn=pans.n}else{guides=synthSlices(W,H);gn=guides.length}cur=-1}\n" as *u8) 125 o = za(dst, o, "function enterScroll(i){clearWorld();scrollEl.style.display='block';vp.style.display='none';if(!stripBuilt)buildScroll();updateControls();scrollToPage(i)}\n" as *u8) 126 o = za(dst, o, "function enterView(i){scrollEl.style.display='none';vp.style.display='block';updateControls();openPage(i,mode==='guided'?1:0)}\n" as *u8) 127 o = za(dst, o, "function setMode(m){mode=m;userMode=1;try{localStorage.nx_rd_mode=m}catch(_){}updateModeBtns();if(m==='scroll')enterScroll(pg);else enterView(pg)}\n" as *u8) 128 o = za(dst, o, "function onFit(){if(mode==='scroll'){scrollToPage(pg);return}fitMode=fitMode==='page'?'width':'page';try{localStorage.nx_rd_fit=fitMode}catch(_){}fit()}\n" as *u8) 129 o = za(dst, o, "function singleTap(x,y){if(mode==='scroll'){toggleBar();return}var w=vw();if(x>w/3&&x<2*w/3){toggleBar();return}var goRight=x>=2*w/3;var fwd=rtl?!goRight:goRight;if(mode==='guided'){if(fwd)nextP();else prevP()}else{if(fwd)openPage(pg+1,0);else openPage(pg-1,0)}}\n" as *u8) 130 o = za(dst, o, "function handleTap(x,y){var now=Date.now();if(now-lastTapT<280){lastTapT=0;if(tapTimer){clearTimeout(tapTimer);tapTimer=0}if(mode!=='scroll')smartZoom(x,y);return}lastTapT=now;if(tapTimer)clearTimeout(tapTimer);tapTimer=setTimeout(function(){tapTimer=0;singleTap(x,y)},280)}\n" as *u8) 131 o = za(dst, o, "function dispatch(){if(mode==='scroll')enterScroll(pg);else enterView(pg)}\n" as *u8) 132 o = za(dst, o, "function start(){updateModeBtns();updateControls();if(userMode||demo){dispatch();return}var f=pages.length?pages[pg].file:'';var b='api/book?p=reader/'+slug+'/zoom/'+stem(f);jf(b+'/dz.json').then(function(r){return r.ok?r.json():null}).then(function(j){if(j&&j.width&&j.height&&(j.height/j.width>=2.4))mode='scroll';else mode='guided';updateModeBtns();updateControls();dispatch()}).catch(function(){dispatch()})}\n" as *u8) 133 o = za(dst, o, "function renderNotes(){if(!anns.length){nl.innerHTML='<div class=ni>no notes yet - tap &#9998; to add one</div>';return}var h='';\n" as *u8) 134 o = za(dst, o, "for(var i=0;i<anns.length;i++){var a=anns[i];h+='<div class=ni data-i='+i+'><b>p'+((a.page|0)+1)+'</b> '+esc(a.text||'')+'</div>'}nl.innerHTML=h;\n" as *u8) 135 o = za(dst, o, "var items=nl.querySelectorAll('.ni');for(var k=0;k<items.length;k++){(function(el){el.onclick=function(){var a=anns[parseInt(el.dataset.i)];if(a){nl.style.display='none';if(mode==='scroll'){scrollToPage(a.page|0)}else{openPage(a.page|0,1)}}}})(items[k])}}\n" as *u8) 136 o = za(dst, o, "function addNote(){if(demo||!pages.length)return;var t=prompt('Note for page '+(pg+1));if(!t)return;anns.push({page:pg,panel:cur,text:t,anchor:stem(pages[pg].file),ts:Date.now()});saveAnns();renderNotes()}\n" as *u8) 137 o = za(dst, o, "document.getElementById('noteb').onclick=addNote;\n" as *u8) 138 o = za(dst, o, "document.getElementById('nlb').onclick=function(){nl.style.display=nl.style.display==='block'?'none':'block';renderNotes()};\n" as *u8) 139 o = za(dst, o, "function nextP(){if(!gn){openPage(pg+1,1);return}if(cur<gn-1)go(cur<0?0:cur+1);else openPage(pg+1,1)}\n" as *u8) 140 o = za(dst, o, "function prevP(){if(!gn){openPage(pg-1,2);return}if(cur>0)go(cur-1);else openPage(pg-1,2)}\n" as *u8) 141 o = za(dst, o, "function hitGuide(ix,iy){for(var i=0;i<gn;i++){var p=guides[i];if(ix>=p[0]&&ix<=p[0]+p[2]&&iy>=p[1]&&iy<=p[1]+p[3])return i}return -1}\n" as *u8) 142 o = za(dst, o, "function smartZoom(cx,cy){var ix=(cx-TX)/S,iy=(cy-TY)/S;var i=hitGuide(ix,iy);if(i>=0){if(i===cur)fit();else go(i)}else{fit()}}\n" as *u8) 143 o = za(dst, o, "document.getElementById('next').onclick=nextP;document.getElementById('prev').onclick=prevP;\n" as *u8) 144 o = za(dst, o, "document.getElementById('pgn').onclick=function(){openPage(pg+1,1)};document.getElementById('pgp').onclick=function(){openPage(pg-1,0)};\n" as *u8) 145 o = za(dst, o, "document.getElementById('fitb').onclick=onFit;\n" as *u8) 146 o = za(dst, o, "document.getElementById('mg').onclick=function(){setMode('guided')};document.getElementById('ms').onclick=function(){setMode('scroll')};document.getElementById('mz').onclick=function(){setMode('zoom')};\n" as *u8) 147 o = za(dst, o, "document.getElementById('dirb').onclick=toggleRtl;\n" as *u8) 148 o = za(dst, o, "document.getElementById('xb').onclick=function(){if(document.referrer&&document.referrer.indexOf('/library')>=0&&history.length>1){history.back()}else{location.href='/library'}};\n" as *u8) 149 o = za(dst, o, "function zoomBy(f){var cx=vw()/2,cy=vh()/2;var ix=(cx-TX)/S,iy=(cy-TY)/S;S=S*f;if(S<0.05)S=0.05;if(S>12)S=12;TX=cx-ix*S;TY=cy-iy*S;cur=-1;apply()}\n" as *u8) 150 o = za(dst, o, "function gotoPage(){if(!pages.length)return;var t=prompt('Go to page (1-'+pages.length+')');if(!t)return;var i=parseInt(t,10);if(i>=1&&i<=pages.length){if(mode==='scroll'){scrollToPage(i-1)}else{openPage(i-1,1)}}}\n" as *u8) 151 o = za(dst, o, "function toggleFull(){try{if(document.fullscreenElement)document.exitFullscreen();else document.documentElement.requestFullscreen()}catch(_){}}\n" as *u8) 152 o = za(dst, o, "function toggleRtl(){rtl=rtl?0:1;try{localStorage.nx_rd_rtl=String(rtl)}catch(_){}updateDirBtn();if(mode==='zoom')openPage(pg,1)}\n" as *u8) 153 o = za(dst, o, "function buildHelp(){var h=document.getElementById('hlp');if(h.dataset.b)return;h.dataset.b=1;h.innerHTML='<b>Reading modes</b><div>Guided = step panel/region fit-to-screen (best on phone). Scroll = webtoon long-strip. Zoom = free pinch/pan.</div><div>Tap L/R = step (panel in Guided, page in Zoom) &nbsp; middle = controls &nbsp; double-tap = zoom region</div><b>Keyboard</b><div><kbd>&larr;</kbd><kbd>&rarr;</kbd> / <kbd>j</kbd><kbd>k</kbd> panel &nbsp; <kbd>Space</kbd> next (<kbd>Shift</kbd> back)</div><div><kbd>n</kbd><kbd>p</kbd> / <kbd>PgDn</kbd><kbd>PgUp</kbd> page &nbsp; <kbd>Home</kbd><kbd>End</kbd> first/last &nbsp; <kbd>g</kbd> go to page</div><div><kbd>+</kbd><kbd>-</kbd> zoom &nbsp; <kbd>0</kbd>/<kbd>f</kbd> fit &nbsp; <kbd>F</kbd> fullscreen</div><div><kbd>m</kbd> cycle mode &nbsp; <kbd>r</kbd> flip direction &nbsp; <kbd>?</kbd> help &nbsp; <kbd>Esc</kbd> close</div>'}\n" as *u8) 154 o = za(dst, o, "function toggleHelp(){buildHelp();var h=document.getElementById('hlp');h.style.display=h.style.display==='block'?'none':'block'}\n" as *u8) 155 o = za(dst, o, "document.getElementById('zi').onclick=function(){zoomBy(1.3)};document.getElementById('zo').onclick=function(){zoomBy(0.77)};document.getElementById('hlpb').onclick=toggleHelp;\n" as *u8) 156 o = za(dst, o, "function scrollKeys(e){var k=e.key;if(k==='n'||k==='PageDown'||k==='ArrowDown'||k==='ArrowRight'){e.preventDefault();scrollToPage(pg+1)}else if(k==='p'||k==='PageUp'||k==='ArrowUp'||k==='ArrowLeft'){e.preventDefault();scrollToPage(pg-1)}else if(k===' '){e.preventDefault();scrollToPage(e.shiftKey?pg-1:pg+1)}else if(k==='Home'){e.preventDefault();scrollToPage(0)}else if(k==='End'){e.preventDefault();if(pages.length)scrollToPage(pages.length-1)}else if(k==='g'){gotoPage()}else if(k==='m'){cycleMode()}else if(k==='r'){toggleRtl()}else if(k==='?'||k==='h'){toggleHelp()}else if(k==='Escape'){closeOverlays()}}\n" as *u8) 157 o = za(dst, o, "document.addEventListener('keydown',function(e){if(mode==='scroll'){scrollKeys(e);return}var k=e.key;if(k==='ArrowRight'||k==='j'){nextP()}else if(k==='ArrowLeft'||k==='k'){prevP()}else if(k===' '){e.preventDefault();if(e.shiftKey)prevP();else nextP()}else if(k==='PageDown'||k==='n'){openPage(pg+1,1)}else if(k==='PageUp'||k==='p'){openPage(pg-1,0)}else if(k==='Home'){openPage(0,1)}else if(k==='End'){if(pages.length)openPage(pages.length-1,1)}else if(k==='+'||k==='='){zoomBy(1.3)}else if(k==='-'||k==='_'){zoomBy(0.77)}else if(k==='0'||k==='f'){fit()}else if(k==='g'){gotoPage()}else if(k==='F'){toggleFull()}else if(k==='r'){toggleRtl()}else if(k==='m'){cycleMode()}else if(k==='?'||k==='h'){toggleHelp()}else if(k==='Escape'){closeOverlays()}});\n" as *u8) 158 o = za(dst, o, "var pts={},np=0,lx=0,ly=0,pd=0,lastTap=0;\n" as *u8) 159 o = za(dst, o, "function dist(){var a=[];for(var k in pts)a.push(pts[k]);var dx=a[0][0]-a[1][0],dy=a[0][1]-a[1][1];return Math.sqrt(dx*dx+dy*dy)}\n" as *u8) 160 o = za(dst, o, "function mid(){var a=[];for(var k in pts)a.push(pts[k]);return[(a[0][0]+a[1][0])/2,(a[0][1]+a[1][1])/2]}\n" as *u8) 161 o = za(dst, o, "vp.addEventListener('pointerdown',function(e){vp.setPointerCapture(e.pointerId);pts[e.pointerId]=[e.clientX,e.clientY];np++;\n" as *u8) 162 o = za(dst, o, "if(np===1){lx=e.clientX;ly=e.clientY;downX=e.clientX;downY=e.clientY;downT=Date.now();moved=false}\n" as *u8) 163 o = za(dst, o, "if(np===2){moved=true;if(tapTimer){clearTimeout(tapTimer);tapTimer=0}pd=dist()}});\n" as *u8) 164 o = za(dst, o, "vp.addEventListener('pointermove',function(e){if(!pts[e.pointerId])return;pts[e.pointerId]=[e.clientX,e.clientY];\n" as *u8) 165 o = za(dst, o, "if(np===1){if(Math.abs(e.clientX-downX)>10||Math.abs(e.clientY-downY)>10)moved=true;TX+=e.clientX-lx;TY+=e.clientY-ly;lx=e.clientX;ly=e.clientY;apply()}\n" as *u8) 166 o = za(dst, o, "else if(np===2){var d=dist(),m=mid();if(pd>0){var f=d/pd;var ns=Math.min(Math.max(S*f,0.02),40);f=ns/S;TX=m[0]-(m[0]-TX)*f;TY=m[1]-(m[1]-TY)*f;S=ns;apply()}pd=d}});\n" as *u8) 167 o = za(dst, o, "function drop(e){if(pts[e.pointerId]){delete pts[e.pointerId];np--;if(np<0)np=0;pd=0;\n" as *u8) 168 o = za(dst, o, "if(np===1){for(var kk2 in pts){lx=pts[kk2][0];ly=pts[kk2][1]}}}}\n" as *u8) 169 o = za(dst, o, "function onup(e){var single=(np===1&&!moved);drop(e);if(single&&e.pointerType!=='mouse'&&Date.now()-downT<400)handleTap(downX,downY)}\n" as *u8) 170 o = za(dst, o, "vp.addEventListener('pointerup',onup);vp.addEventListener('pointercancel',drop);\n" as *u8) 171 o = za(dst, o, "vp.addEventListener('wheel',function(e){e.preventDefault();var f=e.deltaY<0?1.25:0.8;var ns=Math.min(Math.max(S*f,0.02),40);f=ns/S;\n" as *u8) 172 o = za(dst, o, "TX=e.clientX-(e.clientX-TX)*f;TY=e.clientY-(e.clientY-TY)*f;S=ns;apply()},{passive:false});\n" as *u8) 173 o = za(dst, o, "vp.addEventListener('dblclick',function(e){if(mode!=='scroll')smartZoom(e.clientX,e.clientY)});\n" as *u8) 174 o = za(dst, o, "scrollEl.addEventListener('scroll',onScroll);\n" as *u8) 175 o = za(dst, o, "var _sdx=0,_sdy=0,_sdt=0,_smv=false;\n" as *u8) 176 o = za(dst, o, "scrollEl.addEventListener('pointerdown',function(e){_sdx=e.clientX;_sdy=e.clientY;_sdt=Date.now();_smv=false});\n" as *u8) 177 o = za(dst, o, "scrollEl.addEventListener('pointermove',function(e){if(Math.abs(e.clientX-_sdx)>10||Math.abs(e.clientY-_sdy)>10)_smv=true});\n" as *u8) 178 o = za(dst, o, "scrollEl.addEventListener('pointerup',function(e){if(!_smv&&Date.now()-_sdt<400)toggleBar()});\n" as *u8) 179 o = za(dst, o, "window.addEventListener('resize',function(){apply()});\n" as *u8) 180 o = za(dst, o, "updateModeBtns();updateDirBtn();updateControls();\n" as *u8) 181 o = za(dst, o, "if(demo){pages=[];updateModeBtns();updateControls();enterView(0)}\n" as *u8) 182 o = za(dst, o, "else if(!slug){st.textContent='no slug'}\n" as *u8) 183 o = za(dst, o, "else{jf('api/book?p=reader/'+encodeURIComponent(slug)+'/cbx.json').then(function(r){if(!r.ok)throw 0;return r.json()}).then(function(j){\n" as *u8) 184 o = za(dst, o, "pages=(j&&j.pages)||[];if(!pages.length){st.textContent='no pages';return}if(pg>=pages.length)pg=0;\n" as *u8) 185 o = za(dst, o, "jf('api/ann?slug='+encodeURIComponent(slug)).then(function(r){return r.json()}).then(function(aj){if(aj&&aj.ann&&aj.ann.length)anns=aj.ann}).catch(function(){});\n" as *u8) 186 o = za(dst, o, "if(q.get('page')!==null){start();return}\n" as *u8) 187 o = za(dst, o, "jf('api/pos?slug='+encodeURIComponent(slug)).then(function(r){return r.json()}).then(function(sv){\n" as *u8) 188 o = za(dst, o, "if(sv&&sv.pos&&typeof sv.pos.page==='number'&&sv.pos.page>=0&&sv.pos.page<pages.length)pg=sv.pos.page;start()\n" as *u8) 189 o = za(dst, o, "}).catch(function(){start()})\n" as *u8) 190 o = za(dst, o, "}).catch(function(){st.textContent='no comic'})}\n" as *u8) 191 o = za(dst, o, "})();\n" as *u8) 192 o = za(dst, o, "</script></body></html>\n" as *u8) 193 return o 194} 195 196func zr_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 197func zr_w(s: *u8) -> i64 { sys_write(1, s, zr_slen(s)); return 0 } 198func zr_save(path: *u8, buf: *u8, n: i64) -> i64 { 199 let fd: i64 = sys_openat_wr(path, 0x1a4) 200 if fd < 0 { return 0 - 1 } 201 sys_write(fd, buf, n) 202 sys_close(fd) 203 return n 204} 205 206func main() -> i64 { 207 let buf: *u8 = sys_mmap(K_MAGIC_131072) 208 let n: i64 = zr_build(buf) 209 // fail-closed sovereignty at emit: refuse any double-quote leak or external <script src BEFORE writing 210 var bad: i64 = 0 211 var i: i64 = 0 212 while i < n { 213 if buf[i] == (34 as u8) { bad = 1 } 214 if i + 12 <= n { 215 if buf[i] == (60 as u8) { if buf[i+1] == (115 as u8) { if buf[i+2] == (99 as u8) { if buf[i+3] == (114 as u8) { 216 if buf[i+7] == (32 as u8) { if buf[i+8] == (115 as u8) { if buf[i+9] == (114 as u8) { bad = 2 } } } 217 } } } } 218 } 219 i = i + 1 220 } 221 if bad != 0 { zr_w("ZOOM-EMIT REFUSED (sovereignty check failed)\n" as *u8); sys_exit(1); return 1 } 222 var ok: i64 = 1 223 if zr_save("web_assets/nx_zoom_reader.html" as *u8, buf, n) < 0 { ok = 0 } 224 if zr_save("knowledge/staging/media/zoom.html" as *u8, buf, n) < 0 { ok = 0 } 225 if ok == 1 { zr_w("ZOOM-EMIT OK web_assets/nx_zoom_reader.html + knowledge/staging/media/zoom.html\n" as *u8); sys_exit(0); return 0 } 226 zr_w("ZOOM-EMIT WRITE-FAIL\n" as *u8) 227 sys_exit(1); return 1 228}