๐ŸŒ
index_copy2.html
โ† Back
๐Ÿ“ Html โšก Executable Ctrl+S: Save โ€ข Ctrl+R: Run โ€ข Ctrl+F: Find
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/> <title>Tiles Grid + Pinch Zoom + PHP Gallery</title> <style> :root{ --bg:#121212; --bg2:#1e1e1e; --card:#2c2c2c; --ink:#eee; --ink2:#bbb; --accent:#4fc3f7; } *{ box-sizing:border-box } body{ margin:0; font-family:system-ui,ui-sans-serif; background:var(--bg); color:var(--ink); height:100vh; display:flex; flex-direction:column; } .topbar{ display:flex; gap:0; background:var(--bg2); box-shadow:0 2px 8px rgba(0,0,0,.5); z-index:10; } .topbar button{ flex:1; background:none; border:none; color:var(--ink); padding:.9rem 0; font-size:1.4rem; cursor:pointer; } .topbar button:hover{ color:var(--accent); background:#262626; } .panel{ max-height:0; overflow:hidden; background:#1b1b1b; transition:max-height .35s ease; border-bottom:1px solid #242424; } .panel.open{ max-height:360px; } /* Settings (steppers) */ .settings-track{ display:flex; gap:1rem; overflow-x:auto; padding:.6rem 1rem; } .stepper{ flex:0 0 auto; background:var(--card); border:1px solid #2a2a2a; border-radius:.6rem; padding:.5rem; min-width:122px; text-align:center; } .stepper label{ display:block; font-size:.8rem; color:var(--ink2); margin-bottom:.35rem; } .stepper-controls{ display:flex; align-items:center; background:var(--bg2); border-radius:.5rem; overflow:hidden; } .stepper button{ background:#333; border:none; color:var(--ink); font-size:1.1rem; padding:.35rem 0; width:34%; cursor:pointer; } .stepper button:hover{ background:var(--accent); color:#000; } .stepper input{ flex:1; background:var(--bg2); border:none; color:var(--ink); text-align:center; padding:.25rem 0; font-size:1rem; width:56px; } /* Gallery from PHP */ .folder-bar{ display:flex; align-items:center; gap:.5rem; padding:.6rem 1rem; border-bottom:1px solid #242424; } .crumbs{ display:flex; flex-wrap:wrap; gap:.4rem; font-size:.9rem; } .crumbs button{ background:none; border:none; color:var(--accent); cursor:pointer; padding:.2rem .3rem; } .crumbs .sep{ color:#666; } .folders-track, .gallery-track{ display:flex; gap:.8rem; overflow-x:auto; padding:.8rem 1rem; } .folder-card{ flex:0 0 auto; display:flex; align-items:center; gap:.6rem; background:var(--card); border:1px solid #2a2a2a; border-radius:.7rem; padding:.55rem .75rem; cursor:pointer; } .folder-card:hover{ border-color:var(--accent); background:#2f2f2f; } .gallery-track img{ width:220px; height:150px; object-fit:cover; border-radius:.8rem; background:#333; flex-shrink:0; border:1px solid #2a2a2a; cursor:pointer; } .gallery-track img:hover{ border-color:var(--accent); } .empty-hint{ color:#aaa; font-size:.9rem; padding:0 1rem .8rem 1rem; } /* Stage */ .content{ flex:1; display:flex; flex-direction:column; gap:.6rem; padding:1rem; } .meta{ font-size:.9rem; color:#cfcfcf; opacity:.9; } .stage{ position:relative; flex:1; background:#0f0f0f; border:1px solid #2a2a2a; border-radius:.8rem; overflow:hidden; touch-action:none; } .viewport{ position:absolute; left:0; top:0; transform-origin:0 0; will-change: transform; } .img-layer{ position:absolute; left:0; top:0; } .img-layer img{ display:block; image-rendering:pixelated; } .grid{ position:absolute; left:0; top:0; pointer-events:none; } .tile{ position:absolute; border:1px solid red; box-sizing:border-box; } </style> </head> <body> <div class="topbar"> <button id="settingsBtn" title="Settings">โš™๏ธ</button> <button id="imagesBtn" title="Images">๐Ÿ–ผ๏ธ</button> <button id="resetBtn" title="Reset view">๐Ÿ”„</button> </div> <!-- Settings panel --> <div class="panel" id="settingsPanel"> <div class="settings-track"> <div class="stepper" data-key="tileWidth"><label>tileWidth</label> <div class="stepper-controls"><button>-</button><input type="number" value="16"><button>+</button></div> </div> <div class="stepper" data-key="tileHeight"><label>tileHeight</label> <div class="stepper-controls"><button>-</button><input type="number" value="16"><button>+</button></div> </div> <div class="stepper" data-key="xOffset"><label>xOffset</label> <div class="stepper-controls"><button>-</button><input type="number" value="0"><button>+</button></div> </div> <div class="stepper" data-key="yOffset"><label>yOffset</label> <div class="stepper-controls"><button>-</button><input type="number" value="0"><button>+</button></div> </div> <div class="stepper" data-key="Hspacing"><label>Hspacing</label> <div class="stepper-controls"><button>-</button><input type="number" value="0"><button>+</button></div> </div> <div class="stepper" data-key="Vspacing"><label>Vspacing</label> <div class="stepper-controls"><button>-</button><input type="number" value="0"><button>+</button></div> </div> </div> </div> <!-- Images panel (PHP-driven) --> <div class="panel" id="imagesPanel"> <div class="folder-bar"> <strong style="margin-right:.5rem">๐Ÿ“</strong> <div class="crumbs" id="crumbs"></div> <div style="flex:1"></div> <button id="goUpBtn" title="Up one level" style="background:none;border:1px solid #2a2a2a;border-radius:.4rem;padding:.35rem .6rem;color:var(--ink)">โฌ†๏ธ Up</button> <button id="refreshBtn" title="Refresh" style="margin-left:.4rem;background:none;border:1px solid #2a2a2a;border-radius:.4rem;padding:.35rem .6rem;color:var(--ink)">๐Ÿ”</button> </div> <div class="folders-track" id="foldersTrack"></div> <div class="empty-hint" id="emptyFolders" hidden>No folders here.</div> <div class="gallery-track" id="galleryTrack"></div> <div class="empty-hint" id="emptyImages" hidden>No images in this folder.</div> </div> <div class="content"> <div class="meta" id="meta">No image loaded</div> <div class="stage" id="stage"> <div class="viewport" id="viewport"> <div class="img-layer" id="imgLayer"></div> <div class="grid" id="grid"></div> </div> </div> </div> <script> /* ---------- Panels ---------- */ const settingsBtn = document.getElementById('settingsBtn'); const imagesBtn = document.getElementById('imagesBtn'); const resetBtn = document.getElementById('resetBtn'); const settingsPanel = document.getElementById('settingsPanel'); const imagesPanel = document.getElementById('imagesPanel'); function closeAll(){ settingsPanel.classList.remove('open'); imagesPanel.classList.remove('open'); } function toggle(p){ const isOpen = p.classList.contains('open'); closeAll(); if(!isOpen) p.classList.add('open'); } settingsBtn.addEventListener('click', ()=> toggle(settingsPanel)); imagesBtn .addEventListener('click', ()=> { toggle(imagesPanel); if (!imagesPanel.dataset.inited){ imagesPanel.dataset.inited='1'; openSub(''); } }); /* ---------- Settings / steppers ---------- */ const defaults = { tileWidth:16, tileHeight:16, xOffset:0, yOffset:0, Hspacing:0, Vspacing:0 }; let settings = {...defaults}; document.querySelectorAll('.stepper').forEach(st=>{ const key = st.dataset.key; const input = st.querySelector('input'); const [minus, plus] = st.querySelectorAll('button'); input.value = settings[key]; const stepSize = (key === 'tileWidth' || key === 'tileHeight') ? 8 : 1; const commit = ()=>{ let v = parseInt(input.value)||0; if (key === 'tileWidth' || key === 'tileHeight') v = Math.max(1, Math.round(v/8)*8); else v = Math.max(0, v); settings[key] = v; input.value = v; drawGrid(); }; minus.addEventListener('click', ()=>{ input.value = (parseInt(input.value)||0) - stepSize; commit(); }); plus .addEventListener('click', ()=>{ input.value = (parseInt(input.value)||0) + stepSize; commit(); }); input.addEventListener('change', commit); }); /* ---------- Image & Grid ---------- */ const imgLayer = document.getElementById('imgLayer'); const gridEl = document.getElementById('grid'); const meta = document.getElementById('meta'); let imgEl = null, imgW = 0, imgH = 0; function setImage(src){ const img = new Image(); img.decoding = 'async'; img.onload = ()=>{ imgW = img.naturalWidth; imgH = img.naturalHeight; imgLayer.innerHTML = ''; img.style.position='absolute'; img.style.left='0px'; img.style.top='0px'; img.style.width = imgW+'px'; img.style.height = imgH+'px'; imgLayer.appendChild(img); gridEl.style.width = imgW+'px'; gridEl.style.height = imgH+'px'; meta.textContent = `Image: ${imgW}ร—${imgH} ยท pinch to zoom, drag to pan, double-tap reset`; drawGrid(); centerView(); }; img.src = src; imgEl = img; } function drawGrid(){ if (!imgW || !imgH) { gridEl.innerHTML=''; return; } const tw = settings.tileWidth|0, th = settings.tileHeight|0; const ox = settings.xOffset|0, oy = settings.yOffset|0; const hs = settings.Hspacing|0, vs = settings.Vspacing|0; gridEl.innerHTML = ''; for (let y = oy; y + th <= imgH; y += th + vs){ for (let x = ox; x + tw <= imgW; x += tw + hs){ const d = document.createElement('div'); d.className = 'tile'; d.style.left = x + 'px'; d.style.top = y + 'px'; d.style.width = tw + 'px'; d.style.height = th + 'px'; gridEl.appendChild(d); } } } /* ---------- Pinch-zoom + pan ---------- */ const stage = document.getElementById('stage'); const viewport = document.getElementById('viewport'); let scale = 1, tx = 0, ty = 0; const MIN_SCALE = 0.25, MAX_SCALE = 16; const pts = new Map(); function applyTransform(){ viewport.style.transform = `translate(${tx}px, ${ty}px) scale(${scale})`; } function centerView(){ if(!imgW || !imgH) return; const r = stage.getBoundingClientRect(); scale = Math.min( Math.max(1, Math.min(r.width/imgW, r.height/imgH)), 2 ); tx = (r.width - imgW*scale)/2; ty = (r.height - imgH*scale)/2; applyTransform(); } resetBtn.addEventListener('click', centerView); function dist(a,b){ const dx=a.x-b.x, dy=a.y-b.y; return Math.hypot(dx,dy); } function screenToWorld(x,y){ const r = stage.getBoundingClientRect(); const sx = x - r.left, sy = y - r.top; return { wx:(sx - tx)/scale, wy:(sy - ty)/scale }; } let lastPan=null, pinchRef=null; stage.addEventListener('pointerdown', e=>{ stage.setPointerCapture(e.pointerId); pts.set(e.pointerId,{x:e.clientX,y:e.clientY}); if (pts.size===1) lastPan={x:e.clientX,y:e.clientY}; else if (pts.size===2){ const [a,b]=Array.from(pts.values()); pinchRef={ d0:dist(a,b), m0:{x:(a.x+b.x)/2,y:(a.y+b.y)/2}, s0:scale }; } }); stage.addEventListener('pointermove', e=>{ if(!pts.has(e.pointerId)) return; pts.set(e.pointerId,{x:e.clientX,y:e.clientY}); if (pts.size===1 && lastPan){ const p=pts.get(e.pointerId); tx += p.x-lastPan.x; ty += p.y-lastPan.y; lastPan={x:p.x,y:p.y}; applyTransform(); } else if (pts.size===2 && pinchRef){ const [a,b]=Array.from(pts.values()); const d1=dist(a,b); if(pinchRef.d0>0){ const world = screenToWorld(pinchRef.m0.x, pinchRef.m0.y); let newScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, pinchRef.s0 * (d1/pinchRef.d0))); const r = stage.getBoundingClientRect(); const sx=pinchRef.m0.x - r.left; const sy=pinchRef.m0.y - r.top; tx = sx - world.wx * newScale; ty = sy - world.wy * newScale; scale = newScale; applyTransform(); }} }); function endPointer(e){ try{stage.releasePointerCapture(e.pointerId);}catch{} pts.delete(e.pointerId); if(pts.size<2) pinchRef=null; if(pts.size===0) lastPan=null; } stage.addEventListener('pointerup', endPointer); stage.addEventListener('pointercancel', endPointer); stage.addEventListener('pointerleave', e=>{ if(pts.has(e.pointerId)) endPointer(e); }); let lastTap=0; stage.addEventListener('pointerdown', e=>{ const now=performance.now(); if(now-lastTap<300) centerView(); lastTap=now; }, {capture:true}); stage.addEventListener('wheel', e=>{ if(!imgW||!imgH) return; e.preventDefault(); const k = Math.exp(-e.deltaY * 0.0015); const r = stage.getBoundingClientRect(); const sx=e.clientX - r.left; const sy=e.clientY - r.top; const world = screenToWorld(e.clientX, e.clientY); let newScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, scale * k)); tx = sx - world.wx * newScale; ty = sy - world.wy * newScale; scale = newScale; applyTransform(); },{passive:false}); /* ---------- PHP gallery wiring ---------- */ const crumbsEl = document.getElementById('crumbs'); const foldersTrack = document.getElementById('foldersTrack'); const emptyFolders = document.getElementById('emptyFolders'); const galleryTrack = document.getElementById('galleryTrack'); const emptyImages = document.getElementById('emptyImages'); const goUpBtn = document.getElementById('goUpBtn'); const refreshBtn = document.getElementById('refreshBtn'); let currentSub = ''; async function fetchDir(sub=''){ const res = await fetch(`media.php?sub=${encodeURIComponent(sub)}`, {cache:'no-store'}); if(!res.ok) throw new Error(`HTTP ${res.status}`); return await res.json(); } function renderBreadcrumb(bc){ crumbsEl.innerHTML=''; bc.forEach((c,i)=>{ const b=document.createElement('button'); b.textContent=c.label; b.onclick=()=>openSub(c.sub); crumbsEl.appendChild(b); if(i<bc.length-1){ const s=document.createElement('span'); s.className='sep'; s.textContent='โ€บ'; crumbsEl.appendChild(s);} }); } function renderFolders(items){ foldersTrack.innerHTML=''; emptyFolders.hidden = !!items.length; for(const f of items){ const card=document.createElement('div'); card.className='folder-card'; card.innerHTML=`<span>๐Ÿ“‚</span><span>${f.name}</span>`; card.onclick=()=>openSub(f.sub); foldersTrack.appendChild(card); } } function renderImages(items){ galleryTrack.innerHTML=''; emptyImages.hidden = !!items.length; for(const im of items){ const img=document.createElement('img'); img.alt=im.name; img.loading='lazy'; img.decoding='async'; img.src=im.url; img.onclick=()=> setImage(im.url); galleryTrack.appendChild(img); } } async function openSub(sub){ try{ const data = await fetchDir(sub); currentSub = data.cwd||''; renderBreadcrumb(data.breadcrumb||[]); renderFolders(data.folders||[]); renderImages(data.images||[]); } catch(err){ console.error(err); alert('Failed to load folder.'); } } function parentOf(sub){ if(!sub) return ''; const p=sub.split('/').filter(Boolean); p.pop(); return p.join('/'); } goUpBtn .addEventListener('click', ()=> openSub(parentOf(currentSub))); refreshBtn .addEventListener('click', ()=> openSub(currentSub)); /* ---------- Boot ---------- */ document.addEventListener('DOMContentLoaded', ()=>{ // Optional: auto-open PHP gallery root openSub(''); }); </script> </body> </html>