Download static/browser.html from abcd118q/Browser: direct link, hf CLI and curl.
- Browser
- Download file 10.9 kB
-
https://huggingface.co/spaces/abcd118q/Browser/resolve/main/static/browser.html
- Command line
-
hf download hf://spaces/abcd118q/Browser/static/browser.html
-
curl -L -o browser.html https://huggingface.co/spaces/abcd118q/Browser/resolve/main/static/browser.html
10.9 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Browser</title> | |
| <style> | |
| *{box-sizing:border-box;margin:0;padding:0} | |
| :root{ | |
| --bg:#dee1e6; | |
| --tab-active:#ffffff; | |
| --blue:#1a73e8; | |
| --text:#202124; | |
| --icon:#5f6368; | |
| --hover:rgba(0,0,0,0.08); | |
| --green:#188038; | |
| } | |
| html,body{ | |
| height:100vh;overflow:hidden; | |
| font-family:-apple-system,'Segoe UI',Roboto,sans-serif; | |
| font-size:13px;background:#202124; | |
| display:flex;flex-direction:column; | |
| } | |
| /* ββ Tab strip ββ */ | |
| #tab-strip{ | |
| display:flex;align-items:flex-end; | |
| background:var(--bg); | |
| padding:6px 0 0 6px;height:36px; | |
| -webkit-app-region:drag;user-select:none; | |
| } | |
| .tab{ | |
| display:flex;align-items:center;gap:6px; | |
| padding:0 8px 0 10px;height:28px; | |
| background:var(--tab-active); | |
| border-radius:8px 8px 0 0; | |
| min-width:120px;max-width:200px;flex:0 0 auto; | |
| position:relative;cursor:default; | |
| } | |
| .tab-fav{width:16px;height:16px;border-radius:2px;flex-shrink:0} | |
| .tab-lbl{ | |
| flex:1;overflow:hidden;white-space:nowrap; | |
| text-overflow:ellipsis;color:var(--text);font-size:12px; | |
| } | |
| .tab-x{ | |
| width:16px;height:16px;border:none;background:transparent; | |
| border-radius:50%;cursor:pointer;color:var(--icon); | |
| font-size:16px;line-height:1;display:flex; | |
| align-items:center;justify-content:center;flex-shrink:0; | |
| } | |
| .tab-x:hover{background:rgba(0,0,0,0.12)} | |
| /* ββ Toolbar ββ */ | |
| #toolbar{ | |
| display:flex;align-items:center;gap:2px; | |
| background:var(--bg);padding:4px 8px;height:40px; | |
| } | |
| .nb{ | |
| width:32px;height:32px;border:none;background:transparent; | |
| border-radius:50%;cursor:pointer;display:flex; | |
| align-items:center;justify-content:center; | |
| color:var(--icon);flex-shrink:0; | |
| } | |
| .nb:hover:not([disabled]){background:var(--hover)} | |
| .nb[disabled]{opacity:.38;cursor:default} | |
| .nb svg{width:18px;height:18px;fill:var(--icon)} | |
| /* Omnibox */ | |
| #omni{ | |
| flex:1;display:flex;align-items:center; | |
| background:#fff;border-radius:100px; | |
| height:32px;padding:0 10px;gap:6px; | |
| border:1.5px solid transparent; | |
| transition:border-color .15s,box-shadow .15s; | |
| } | |
| #omni:focus-within{ | |
| border-color:var(--blue); | |
| box-shadow:0 0 0 3px rgba(26,115,232,.2); | |
| } | |
| #lock{display:flex;align-items:center;flex-shrink:0} | |
| #lock svg{width:13px;height:13px;fill:var(--green)} | |
| #url{ | |
| flex:1;border:none;outline:none; | |
| font-size:13px;color:var(--text); | |
| background:transparent;min-width:0; | |
| } | |
| #star{flex-shrink:0;cursor:pointer;color:var(--icon);line-height:0} | |
| #star svg{width:16px;height:16px;fill:var(--icon)} | |
| /* ββ Viewport ββ */ | |
| #vp-wrap{ | |
| flex:1;background:#fff;position:relative;overflow:hidden; | |
| } | |
| #vp{ | |
| width:100%;height:100%;display:block; | |
| cursor:default;outline:none; | |
| } | |
| /* Loading */ | |
| #overlay{ | |
| position:absolute;inset:0;background:#fff; | |
| display:flex;flex-direction:column; | |
| align-items:center;justify-content:center; | |
| gap:14px;z-index:9;transition:opacity .3s; | |
| } | |
| #overlay.gone{opacity:0;pointer-events:none} | |
| .spin{ | |
| width:36px;height:36px;border:3px solid #dee1e6; | |
| border-top-color:var(--blue);border-radius:50%; | |
| animation:sp .7s linear infinite; | |
| } | |
| @keyframes sp{to{transform:rotate(360deg)}} | |
| /* Status */ | |
| #status{ | |
| position:absolute;bottom:8px;right:10px; | |
| background:rgba(0,0,0,.5);color:#fff; | |
| font-size:11px;padding:2px 8px;border-radius:10px; | |
| z-index:10;pointer-events:none;transition:opacity .5s; | |
| } | |
| #status.ok{opacity:0} | |
| </style> | |
| </head> | |
| <body> | |
| <div id="tab-strip"> | |
| <div class="tab"> | |
| <img class="tab-fav" id="fav" | |
| src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='7' fill='%23bbb'/%3E%3C/svg%3E"> | |
| <span class="tab-lbl" id="t-title">New Tab</span> | |
| <button class="tab-x">β</button> | |
| </div> | |
| </div> | |
| <div id="toolbar"> | |
| <button class="nb" id="b-back" disabled title="Go back"> | |
| <svg viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg> | |
| </button> | |
| <button class="nb" id="b-fwd" disabled title="Go forward"> | |
| <svg viewBox="0 0 24 24"><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"/></svg> | |
| </button> | |
| <button class="nb" id="b-rel" title="Reload"> | |
| <svg viewBox="0 0 24 24"><path d="M17.65 6.35A7.958 7.958 0 0012 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0112 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg> | |
| </button> | |
| <div id="omni"> | |
| <span id="lock"> | |
| <svg viewBox="0 0 24 24"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/></svg> | |
| </span> | |
| <input id="url" type="text" placeholder="Search or type a URL" | |
| autocomplete="off" spellcheck="false"> | |
| <span id="star"> | |
| <svg viewBox="0 0 24 24"><path d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z"/></svg> | |
| </span> | |
| </div> | |
| <button class="nb" title="Chrome menu" style="margin-left:2px"> | |
| <svg viewBox="0 0 24 24"><circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/></svg> | |
| </button> | |
| </div> | |
| <div id="vp-wrap"> | |
| <canvas id="vp" tabindex="0"></canvas> | |
| <div id="overlay"> | |
| <div class="spin"></div> | |
| <span style="color:#5f6368">Connecting to browserβ¦</span> | |
| </div> | |
| <div id="status">β</div> | |
| </div> | |
| <script> | |
| const vp = document.getElementById('vp'); | |
| const ctx = vp.getContext('2d'); | |
| const urlIn = document.getElementById('url'); | |
| const tTitle = document.getElementById('t-title'); | |
| const fav = document.getElementById('fav'); | |
| const overlay = document.getElementById('overlay'); | |
| const statusEl= document.getElementById('status'); | |
| const bBack = document.getElementById('b-back'); | |
| const bFwd = document.getElementById('b-fwd'); | |
| const bRel = document.getElementById('b-rel'); | |
| const lock = document.getElementById('lock'); | |
| const BW = 1280, BH = 800; | |
| // ββ WebSocket βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'; | |
| // FIXED WEBSOCKET URL HERE | |
| const WS = `${proto}//${location.host}/ws`; | |
| let ws = null; | |
| function setStatus(msg, ok=false){ | |
| statusEl.textContent = msg; | |
| statusEl.className = ok ? 'ok' : ''; | |
| } | |
| function connect(){ | |
| ws = new WebSocket(WS); | |
| ws.onopen = () => { setStatus('β', true); overlay.classList.add('gone'); }; | |
| ws.onclose = () => { setStatus('Reconnectingβ¦'); setTimeout(connect, 2000); }; | |
| ws.onerror = () => setStatus('Error'); | |
| ws.onmessage = async ({data}) => { | |
| const msg = JSON.parse(data); | |
| if (msg.type === 'frame'){ | |
| const ab = Uint8Array.from(atob(msg.data), c=>c.charCodeAt(0)); | |
| const bmp = await createImageBitmap(new Blob([ab],{type:'image/jpeg'})); | |
| const cw = vp.parentElement.clientWidth; | |
| const ch = vp.parentElement.clientHeight; | |
| if (vp.width!==cw) vp.width=cw; | |
| if (vp.height!==ch) vp.height=ch; | |
| ctx.drawImage(bmp, 0, 0, cw, ch); | |
| bmp.close(); | |
| } else if (msg.type === 'nav'){ | |
| urlIn.value = msg.url||''; | |
| document.title = tTitle.textContent = msg.title||'New Tab'; | |
| // favicon | |
| try { | |
| const u = new URL(msg.url); | |
| fav.src = u.origin+'/favicon.ico'; | |
| fav.onerror = () => fav.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='7' fill='%23bbb'/%3E%3C/svg%3E"; | |
| } catch{} | |
| // lock colour | |
| const secure = msg.url?.startsWith('https'); | |
| lock.style.color = secure ? '#188038' : '#5f6368'; | |
| lock.querySelector('svg').style.fill = lock.style.color; | |
| } | |
| }; | |
| } | |
| const send = obj => ws?.readyState===1 && ws.send(JSON.stringify(obj)); | |
| // ββ Coords ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const coord = e => { | |
| const r = vp.getBoundingClientRect(); | |
| return { | |
| x: Math.round((e.clientX-r.left) * (BW/r.width)), | |
| y: Math.round((e.clientY-r.top) * (BH/r.height)) | |
| }; | |
| }; | |
| // ββ Mouse βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| vp.onmousedown = e => { vp.focus(); send({type:'mousedown', ...coord(e)}); }; | |
| vp.onmouseup = e => send({type:'mouseup', ...coord(e)}); | |
| vp.onclick = e => send({type:'click', ...coord(e)}); | |
| vp.ondblclick = e => send({type:'dblclick', ...coord(e)}); | |
| vp.onmousemove = e => send({type:'mousemove', ...coord(e)}); | |
| vp.onwheel = e => { e.preventDefault(); send({type:'wheel',dx:e.deltaX,dy:e.deltaY}); }; | |
| vp.oncontextmenu= e => e.preventDefault(); | |
| // ββ Keyboard ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const SPECIALS = new Set([ | |
| 'Enter','Backspace','Delete','Tab','Escape', | |
| 'ArrowLeft','ArrowRight','ArrowUp','ArrowDown', | |
| 'Home','End','PageUp','PageDown', | |
| 'F1','F2','F3','F4','F5','F6','F7','F8','F9','F10','F11','F12', | |
| 'Insert','CapsLock' | |
| ]); | |
| vp.addEventListener('keydown', e => { | |
| e.preventDefault(); | |
| const k = e.key; | |
| if (e.ctrlKey || e.metaKey || e.altKey || SPECIALS.has(k)){ | |
| send({type:'keydown', key:k, | |
| ctrl:e.ctrlKey, shift:e.shiftKey, alt:e.altKey, meta:e.metaKey}); | |
| } else if (k.length===1){ | |
| send({type:'type', text:k}); | |
| } | |
| }); | |
| // ββ URL bar βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| urlIn.addEventListener('keydown', e => { | |
| if (e.key==='Enter'){ | |
| send({type:'navigate', url:urlIn.value.trim()}); | |
| vp.focus(); | |
| } | |
| e.stopPropagation(); | |
| }); | |
| urlIn.onclick = e => e.stopPropagation(); | |
| // ββ Nav buttons βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| bBack.onclick = () => send({type:'back'}); | |
| bFwd.onclick = () => send({type:'forward'}); | |
| bRel.onclick = () => send({type:'reload'}); | |
| // ββ Resize ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| new ResizeObserver(() => { | |
| vp.width = vp.parentElement.clientWidth; | |
| vp.height = vp.parentElement.clientHeight; | |
| }).observe(vp.parentElement); | |
| connect(); | |
| </script> | |
| </body> | |
| </html> |