| import QRCode from 'qrcode'; |
| import './style.css'; |
| const initial = 'https://huggingface.co/datasets/evalstate/mcp-clients'; |
| const themes = [ |
| {id:'phosphor',name:'Green phosphor',code:'P1',accent:'#a7f878',ink:'#123822',paper:'#edfadf',description:'Late nights. Green screens. Pure signal.'}, |
| {id:'amber',name:'Amber console',code:'P2',accent:'#ffbd69',ink:'#492b0d',paper:'#fff0d4',description:'A warm glow from a different era.'}, |
| {id:'ice',name:'Arctic terminal',code:'P3',accent:'#91d9ff',ink:'#12304a',paper:'#e9f6ff',description:'Cold pixels. A very clear connection.'} |
| ]; |
| let current=themes[0], svg='', revision=0; |
| let logoData=''; |
| const logo = new Image(); |
| const response = await fetch('/hf-logo-pirate.png'); |
| const blob = await response.blob(); |
| logoData = await new Promise(resolve=>{const reader=new FileReader();reader.onload=()=>resolve(reader.result);reader.readAsDataURL(blob)}); |
| logo.src=logoData; await logo.decode(); |
| document.querySelector('#app').innerHTML=` |
| <header><a class="wordmark" href="./"><span class="mark">▥</span> SIGNAL<span class="slash">/</span><span class="muted">QR STUDIO</span></a><span class="online"><i></i> LOCAL ENCODING · NO TRACKING</span></header> |
| <main><div class="eyebrow">UTILITY_001 / LINKS → PIXELS</div><h1>Your link.<br>A little more <em>signal.</em></h1><p class="intro">QR codes with an old-school soul. Pick a frequency,<br class="desktop"> drop in a link, and send it out into the world.</p> |
| <div class="workspace"><section class="controls"><div class="section-heading"><span>01 / INPUT</span><span class="muted">HTTP(S)</span></div><label for="url">Where are we going?</label><div class="input-shell"><span>›</span><input id="url" type="url" spellcheck="false" aria-describedby="error" value="${initial}"></div><p id="error" role="status" class="hint">Static by design. Your URL, directly encoded.</p> |
| <div class="section-heading theme-heading">02 / FREQUENCY</div><div class="themes">${themes.map((t,i)=>`<button class="theme ${i===0?'selected':''}" data-theme="${t.id}" aria-pressed="${i===0}" style="--swatch:${t.accent}"><span class="swatch"></span><span>${t.name}<small>${t.code} / ${['CLASSIC','WARM','COOL'][i]}</small></span><span class="radio"></span></button>`).join('')}</div> |
| <div class="logo-row"><div><label for="logo">Pirate on board</label><p>Hugging Face logo, front and center.</p></div><input id="logo" type="checkbox" checked role="switch"></div> |
| <div class="size-control"><label for="logo-size">Logo size <output id="size-value">17%</output></label><input id="logo-size" type="range" min="12" max="26" value="17" step="1"><p class="hint">Larger logos cover more data. Test with your phone before printing.</p></div> |
| <div class="logo-row"><div><label for="include-header">Include header</label><p>Shown in the preview and both downloads.</p></div><input id="include-header" type="checkbox" checked role="switch"></div><label class="header-label" for="header-text">Header text</label><input id="header-text" type="text" maxlength="40" value="HF / TRANSMISSION"><div class="note"><span>↳</span><p>Built for the real world.<br><span class="muted">High error correction. A clean quiet zone.<br>No redirects, accounts, or expiration.</span></p></div></section> |
| <section class="preview-panel"><div class="preview-bar"><span><i></i> LIVE PREVIEW</span><span id="preview-code">P1 / PHOSPHOR</span></div><div class="preview-stage"><div class="print-card"><div class="card-top"><span id="header-preview">HF / TRANSMISSION</span></div><div id="qr" aria-label="Generated QR code"></div></div></div><div class="preview-caption"><span id="description">${current.description}</span><span>ECC / H</span></div></section></div> |
| <div class="export-row"><div><span class="ready" id="status">● SIGNAL READY</span><p>Take your pixels with you.</p></div><div class="actions"><button id="svg">↓ Download SVG</button><button class="primary" id="png">↓ Download PNG <span>↗</span></button></div></div> |
| <footer><span>MADE OF PIXELS. BUILT FOR PEOPLE.</span><span>STATIC QR / OPEN DESTINATIONS <span class="cursor">▮</span></span></footer></main>`; |
| const input=document.querySelector('#url'); |
| function validURL(value){try {const u=new URL(value);return ['http:','https:'].includes(u.protocol)&&!!u.hostname;}catch{return false}} |
| function update(){ |
| revision++; |
| const includeHeader=document.querySelector("#include-header").checked; |
| document.querySelector(".card-top").hidden=!includeHeader; |
| document.querySelector("#header-preview").textContent=document.querySelector("#header-text").value; |
| document.querySelector("#header-text").disabled=!includeHeader; |
| document.querySelector("#size-value").textContent=document.querySelector("#logo-size").value+"%"; |
| document.querySelector("#logo-size").disabled=!document.querySelector("#logo").checked; |
| const value=input.value.trim(); |
| const valid=validURL(value); |
| document.querySelector('#error').textContent=valid?'Static by design. Your URL, directly encoded.':'Enter a complete URL starting with https:// or http://.'; |
| input.setAttribute('aria-invalid',String(!valid)); |
| document.querySelectorAll('.actions button').forEach(b=>b.disabled=!valid); |
| if(!valid){svg='';document.querySelector('#qr').innerHTML='<p class="empty">AWAITING VALID URL</p>';document.querySelector('#status').textContent='○ AWAITING INPUT';return} |
| try { |
| const qr=QRCode.create(value,{errorCorrectionLevel:'H'});const n=qr.modules.size, size=n+8; |
| |
| const logoSize=Math.floor(n*Number(document.querySelector("#logo-size").value)/100),start=(size-logoSize)/2; |
| let path='';for(let y=0;y<n;y++)for(let x=0;x<n;x++)if(qr.modules.get(y,x))path+=`M${x+4} ${y+4}h1v1h-1z`; |
| const withLogo=document.querySelector('#logo').checked; |
| svg=`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1024" height="1024" viewBox="0 0 ${size} ${size}"><rect width="${size}" height="${size}" fill="${current.paper}"/><path d="${path}" fill="${current.ink}"/>${withLogo?`<rect x="${start-.6}" y="${start-.6}" width="${logoSize+1.2}" height="${logoSize+1.2}" rx=".7" fill="${current.paper}"/><image x="${start}" y="${start}" width="${logoSize}" height="${logoSize}" xlink:href="${logoData}"/>`:''}</svg>`; |
| document.querySelector('#qr').innerHTML=svg;document.querySelector('#status').textContent='● SIGNAL READY'; |
| }catch{svg='';document.querySelector('#qr').innerHTML='<p class="empty">URL TOO LONG</p>';document.querySelector('#error').textContent='This URL exceeds QR capacity. Try a shorter URL.';document.querySelector('#status').textContent='○ INPUT TOO LONG';document.querySelectorAll('.actions button').forEach(b=>b.disabled=true)} |
| } |
| function save(blob,ext){const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=`signal-${current.id}.${ext}`;a.click();setTimeout(()=>URL.revokeObjectURL(a.href),1000)} |
| function exportSVG(){ |
| if(!document.querySelector('#include-header').checked)return svg; |
| const text=document.querySelector('#header-text').value.replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); |
| const nested=svg.replace('width="1024" height="1024"','x="0" y="112" width="1024" height="1024"'); |
| return `<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1136" viewBox="0 0 1024 1136"><rect width="1024" height="1136" fill="${current.paper}"/><text x="64" y="68" font-family="monospace" font-size="28" fill="${current.ink}">${text}</text><path d="M64 104H960" stroke="${current.ink}" stroke-opacity=".3"/>${nested}</svg>`; |
| } |
| document.querySelector('#svg').onclick=()=>{if(svg)save(new Blob([exportSVG()],{type:'image/svg+xml'}),'svg')}; |
| document.querySelector('#png').onclick=async()=>{if(!svg)return;const version=revision;const image=new Image();image.src=URL.createObjectURL(new Blob([exportSVG()],{type:'image/svg+xml'}));try{await image.decode();if(version!==revision)return;const canvas=document.createElement('canvas');canvas.width=1024;canvas.height=image.naturalHeight;canvas.getContext('2d').drawImage(image,0,0);canvas.toBlob(blob=>{if(blob)save(blob,'png')},'image/png')}finally{URL.revokeObjectURL(image.src)}}; |
| for(const id of ['logo-size','include-header','header-text'])document.getElementById(id).addEventListener('input',update); |
| input.addEventListener('input',update);document.querySelector('#logo').onchange=update; |
| document.querySelectorAll('.theme').forEach(button=>button.onclick=()=>{current=themes.find(t=>t.id===button.dataset.theme);document.documentElement.style.setProperty('--accent',current.accent);document.documentElement.style.setProperty('--paper',current.paper);document.documentElement.style.setProperty('--ink',current.ink);document.querySelectorAll('.theme').forEach(b=>{b.classList.toggle('selected',b===button);b.setAttribute('aria-pressed',String(b===button))});document.querySelector('#preview-code').textContent=`${current.code} / ${current.id.toUpperCase()}`;document.querySelector('#description').textContent=current.description;update()});update(); |
|
|