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>⏮</button><button id=prev>◀</button><span id=st>-</span><button id=next>▶</button><button id=pgn>⏭</button><button id=zi>+</button><button id=zo>−</button><button id=noteb>✎</button><button id=nlb>☰</button><button id=fitb>Fit</button><button id=hlpb>?</button><button id=xb title=Close>✕</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==='<'?'<':c==='>'?'>':'&'})}\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 ✎ 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) middle = controls double-tap = zoom region</div><b>Keyboard</b><div><kbd>←</kbd><kbd>→</kbd> / <kbd>j</kbd><kbd>k</kbd> panel <kbd>Space</kbd> next (<kbd>Shift</kbd> back)</div><div><kbd>n</kbd><kbd>p</kbd> / <kbd>PgDn</kbd><kbd>PgUp</kbd> page <kbd>Home</kbd><kbd>End</kbd> first/last <kbd>g</kbd> go to page</div><div><kbd>+</kbd><kbd>-</kbd> zoom <kbd>0</kbd>/<kbd>f</kbd> fit <kbd>F</kbd> fullscreen</div><div><kbd>m</kbd> cycle mode <kbd>r</kbd> flip direction <kbd>?</kbd> help <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}