Site Builder
Editing:
index1.php
writable 0666
<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Hash Spectrum Visualizer</title> <style> :root{ --bg: #0b0c10; /* deep charcoal */ --panel: #121419; --ink: #e7e9ee; --muted: #aeb6c2; --accent: #6ee7ff; --line: #293042; --good: #32d583; --warn: #f6c647; --danger: #ef5b5b; } *{box-sizing:border-box} html,body{height:100%} body{ margin:0; font: 15px/1.45 system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji"; color:var(--ink); background:radial-gradient(1200px 800px at 15% -10%, #172031 0%, #0b0c10 60%); } .wrap{max-width:1100px;margin:24px auto;padding:16px} header{display:flex;align-items:center;gap:12px;margin-bottom:10px} header h1{font-size:26px;letter-spacing:.3px;margin:0} .card{background:linear-gradient(180deg, rgba(255,255,255,0.035), rgba(255,255,255,0.02)); border:1px solid #1a2130; border-radius:14px; box-shadow:0 6px 18px rgba(0,0,0,.25); padding:16px;} .controls{display:grid;grid-template-columns:1.2fr .8fr; gap:16px} @media (max-width: 900px){.controls{grid-template-columns:1fr}} textarea{ width:100%; min-height:110px; resize:vertical; padding:12px 12px; background:#0e1118; border:1px solid #1c2331; color:var(--ink); border-radius:10px; font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } .row{display:grid; grid-template-columns:1fr 1fr; gap:12px} .row3{display:grid; grid-template-columns:1fr 1fr 1fr; gap:12px} .row4{display:grid; grid-template-columns:repeat(4,1fr); gap:12px} .field{background:#0e1118;border:1px solid #1c2331; border-radius:10px; padding:10px 12px} .field h4{margin:.1rem 0 .4rem; color:var(--muted); font-weight:600; font-size:12px; letter-spacing:.4px; text-transform:uppercase} .field label{display:flex;align-items:center;gap:8px} input[type="text"], select{ width:100%; padding:8px 10px; border-radius:8px; border:1px solid #2a3346; background:#0d1117; color:var(--ink); font-family:inherit; } .inline{display:flex; align-items:center; gap:12px; flex-wrap:wrap} .btn{ appearance:none; border:1px solid #2b3448; background:linear-gradient(180deg, #151b26,#0e131b); color:var(--ink); border-radius:10px; padding:10px 14px; cursor:pointer; font-weight:600; } .btn:hover{border-color:#3e4b67} .btn.primary{border-color:#1f97b3; background:linear-gradient(180deg, #0f232c,#0b1b22)} .note{color:#aab3c2; font-size:12px} /* Spectrum view */ .spectra{margin-top:18px;} .bandWrap{position:relative; border:1px solid #1a2130; border-radius:12px; overflow:auto; background:#0b0f15} .band{position:relative; display:flex; align-items:stretch; gap:0; min-height:50px} .segment{height:var(--seg-h, 56px); min-width:var(--seg-w, 22px); position:relative} .segment:hover::after{ content:attr(data-title); position:absolute; bottom:100%; left:50%; transform:translate(-50%,-6px); white-space:nowrap; background:#0b0f15; border:1px solid #2a3346; color:#cfe7ff; font:12px/1.2 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; padding:6px 8px; border-radius:8px; box-shadow:0 10px 24px rgba(0,0,0,.35); } canvas.overlay{position:absolute; inset:0; pointer-events:none} .legend{display:flex; gap:10px; flex-wrap:wrap; margin-top:12px} .pill{border:1px solid #2a3346; border-radius:999px; padding:4px 10px; font-size:12px; color:#c7d2e2} .grid{display:grid; grid-template-columns:repeat(2, 1fr); gap:12px} @media (max-width: 700px){.grid{grid-template-columns:1fr}} .mono{font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace} .kbd{font: 12px/1.2 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; padding:2px 6px; border:1px solid #2a3346; border-radius:6px; background:#0e1118} .footer{color:#93a0b7; font-size:12px; margin-top:18px} </style> </head> <body> <div class="wrap"> <header> <svg width="28" height="28" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> <circle cx="12" cy="12" r="10" stroke="#6ee7ff" stroke-width="1.6"/> <path d="M3 12h18" stroke="#6ee7ff" stroke-width="1.6"/> <path d="M12 3v18" stroke="#6ee7ff" stroke-width="1.6"/> </svg> <h1>Hash Spectrum Visualizer</h1> </header> <div class="card controls"> <div> <h3 style="margin:0 0 10px 0">Input</h3> <textarea id="hexInput" spellcheck="false" placeholder="Paste a hex string (e.g., 64-char block hash, 64-char private key, etc.)"></textarea> <div class="inline" style="margin-top:10px; gap:8px"> <button class="btn" id="btnGenesis">Use Bitcoin Genesis Hash</button> <button class="btn" id="btnRandom">Random 64‑hex</button> <span class="note">Non-hex characters will be stripped automatically.</span> </div> </div> <div> <h3 style="margin:0 0 10px 0">Mapping</h3> <div class="row"> <div class="field"> <h4>Color mapping</h4> <label><input type="radio" name="mode" value="rgb6" checked> Group by 6 hex → <span class="mono">#RRGGBB</span></label> <label><input type="radio" name="mode" value="hsl2"> Each byte (2 hex) → HSL (hue=v/255×360°)</label> </div> <div class="field"> <h4>Remainder handling</h4> <label><input type="radio" name="pad" value="wrap" checked> Wrap start to fill last chunk</label> <label><input type="radio" name="pad" value="drop"> Drop incomplete tail</label> </div> </div> <div class="row3" id="hslControls" style="display:none"> <div class="field"> <h4>HSL Saturation</h4> <div class="inline"><input type="range" id="sat" min="30" max="100" value="100"/><span id="satVal" class="kbd">100%</span></div> </div> <div class="field"> <h4>HSL Lightness</h4> <div class="inline"><input type="range" id="lit" min="30" max="70" value="50"/><span id="litVal" class="kbd">50%</span></div> </div> <div class="field"> <h4>Overlay metric</h4> <select id="overlayMetric"> <option value="brightness" selected>Perceived brightness</option> <option value="hue">Hue</option> <option value="value">Raw chunk value</option> </select> </div> </div> <div class="row3"> <div class="field"> <h4>Segment width</h4> <div class="inline"><input type="range" id="segWidth" min="6" max="48" value="22"/><span id="segWidthVal" class="kbd">22 px</span></div> </div> <div class="field"> <h4>Band height</h4> <div class="inline"><input type="range" id="segHeight" min="28" max="120" value="56"/><span id="segHeightVal" class="kbd">56 px</span></div> </div> <div class="field"> <h4>Overlay</h4> <label class="inline"><input type="checkbox" id="showOverlay" checked> Connect segments with a line</label> </div> </div> <div class="inline" style="margin-top:10px"> <button class="btn primary" id="renderBtn">Render Spectrum</button> <button class="btn" id="copyCSS">Copy CSS Gradient</button> <button class="btn" id="downloadPNG">Download PNG</button> </div> </div> </div> <div class="card spectra"> <div class="legend"> <div class="pill">Chunks → Colors</div> <div class="pill">Tooltip shows: index • chunk • color • wavelength (approx)</div> </div> <div class="bandWrap" id="bandWrap"> <div class="band" id="band"></div> <canvas class="overlay" id="overlay"></canvas> </div> <div class="footer"> Tip: try different mapping modes. <span class="mono">RGB(#RRGGBB)</span> groups 6 hex characters per color; <span class="mono">HSL</span> uses each byte’s value as hue (S/L adjustable). Hue≈wavelength via 380–700 nm mapping for visualization only. </div> </div> <div class="card"> <div class="grid"> <div> <h3 style="margin:0 0 8px 0">Highlights</h3> <div class="field"><h4>First 10 hex</h4><div class="mono" id="first10">—</div></div> <div class="field" style="margin-top:8px"><h4>Last 10 hex</h4><div class="mono" id="last10">—</div></div> </div> <div> <h3 style="margin:0 0 8px 0">Gradient Preview</h3> <div id="gradPreview" style="height:60px; border:1px solid #1a2130; border-radius:10px; background:#0e1118"></div> <div style="margin-top:8px; word-break:break-all" class="mono note" id="gradCSS">—</div> </div> </div> </div> </div> <script> // ====== Utilities ====== const genesisHash = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; function sanitizeHex(str){ if(!str) return ""; return (str+"").toLowerCase().replace(/[^0-9a-f]/g, ""); } function chunkHex(hex, size, padMode){ // returns array of equally sized chunks. If remainder exists: // - wrap: pull from start to complete last chunk // - drop: ignore leftover const arr = []; if(!hex) return arr; let i = 0; for(; i + size <= hex.length; i += size){ arr.push(hex.slice(i, i+size)); } const rem = hex.length - i; if(rem > 0){ if(padMode === 'wrap'){ const need = size - rem; const pad = hex.slice(0, need); arr.push(hex.slice(i) + pad); } // else drop } return arr; } function hexToRgbObj(chunk6){ const r = parseInt(chunk6.slice(0,2),16); const g = parseInt(chunk6.slice(2,4),16); const b = parseInt(chunk6.slice(4,6),16); return {r,g,b}; } function rgbToCss({r,g,b}){ return `rgb(${r}, ${g}, ${b})`; } function luma({r,g,b}){ // relative luminance approx return (0.2126*r + 0.7152*g + 0.0722*b) / 255; // 0..1 } function byteToHue(v){ return (v/255)*360; } function hueToWavelengthNm(h){ // crude map: 0..360 => 380..700 nm return 380 + (h/360)*(700-380); } function valToWavelengthNm01(x){ // x in 0..1 -> 380..700 return 380 + x*(700-380); } function buildColors(hex, mode, padMode, sat=100, lit=50){ const colors = []; if(mode === 'rgb6'){ const chunks = chunkHex(hex, 6, padMode); chunks.forEach((c, i) => { const rgb = hexToRgbObj(c); const css = `#${c}`; colors.push({index:i, chunk:c, css, rgb, hue:undefined, value: parseInt(c,16)/(0xFFFFFF), brightness: luma(rgb), wavelength: valToWavelengthNm01((rgb.r+rgb.g+rgb.b)/ (3*255))}); }); } else { // hsl2: each byte -> hue const bytes = chunkHex(hex, 2, padMode); bytes.forEach((c, i) => { const v = parseInt(c,16); const h = byteToHue(v); const css = `hsl(${h.toFixed(1)}deg, ${sat}%, ${lit}%)`; // approximate brightness from HSL const brightness = (2*lit + sat/100 * Math.min(lit, 100 - lit)) / 200; // rough colors.push({index:i, chunk:c, css, rgb:undefined, hue:h, value: v/255, brightness, wavelength: hueToWavelengthNm(h)}); }); } return colors; } function gradientCss(colors){ const stops = colors.map(c => c.css).join(', '); return `linear-gradient(to right, ${stops})`; } // ====== Rendering ====== const els = { input: document.getElementById('hexInput'), btnGenesis: document.getElementById('btnGenesis'), btnRandom: document.getElementById('btnRandom'), renderBtn: document.getElementById('renderBtn'), copyCSS: document.getElementById('copyCSS'), downloadPNG: document.getElementById('downloadPNG'), hslControls: document.getElementById('hslControls'), sat: document.getElementById('sat'), satVal: document.getElementById('satVal'), lit: document.getElementById('lit'), litVal: document.getElementById('litVal'), segWidth: document.getElementById('segWidth'), segWidthVal: document.getElementById('segWidthVal'), segHeight: document.getElementById('segHeight'), segHeightVal: document.getElementById('segHeightVal'), showOverlay: document.getElementById('showOverlay'), overlayMetric: document.getElementById('overlayMetric'), bandWrap: document.getElementById('bandWrap'), band: document.getElementById('band'), overlay: document.getElementById('overlay'), first10: document.getElementById('first10'), last10: document.getElementById('last10'), gradPreview: document.getElementById('gradPreview'), gradCSS: document.getElementById('gradCSS') }; function getMode(){ return document.querySelector('input[name="mode"]:checked').value; } function getPad(){ return document.querySelector('input[name="pad"]:checked').value; } function setHslControls(){ els.hslControls.style.display = getMode()==='hsl2' ? 'grid' : 'none'; } function render(){ const hex = sanitizeHex(els.input.value); els.input.value = hex; // reflect sanitization // Highlights els.first10.textContent = hex.slice(0,10) || '—'; els.last10.textContent = hex.slice(-10) || '—'; const colors = buildColors(hex, getMode(), getPad(), parseInt(els.sat.value,10), parseInt(els.lit.value,10)); // Seg sizes els.band.style.setProperty('--seg-w', els.segWidth.value + 'px'); els.band.style.setProperty('--seg-h', els.segHeight.value + 'px'); // Clear & render segments els.band.innerHTML = ''; colors.forEach(c => { const div = document.createElement('div'); div.className = 'segment'; div.style.background = c.css; const wl = c.wavelength ? `${c.wavelength.toFixed(0)} nm` : '—'; let label; if(getMode()==='rgb6'){ label = `#${c.index.toString().padStart(2,'0')} • ${c.chunk} → ${c.css} • ~${wl}`; } else { label = `#${c.index.toString().padStart(2,'0')} • ${c.chunk} → ${c.css} • ~${wl}`; } div.setAttribute('data-title', label); els.band.appendChild(div); }); // Gradient preview if(colors.length){ const g = gradientCss(colors); els.gradPreview.style.background = g; els.gradCSS.textContent = g; } else { els.gradPreview.style.background = '#0e1118'; els.gradCSS.textContent = '—'; } // Overlay drawing drawOverlay(colors); } function drawOverlay(colors){ const cw = els.band.scrollWidth; // natural width of band (could overflow container) const ch = els.band.getBoundingClientRect().height; // visible height els.overlay.width = cw; els.overlay.height = ch; const ctx = els.overlay.getContext('2d'); ctx.clearRect(0,0,cw,ch); if(!els.showOverlay.checked || colors.length === 0){ return; } // compute midpoints const segW = parseInt(els.segWidth.value,10); const metric = els.overlayMetric.value; // brightness | hue | value ctx.lineWidth = 2; ctx.strokeStyle = '#93c5fd88'; ctx.beginPath(); colors.forEach((c, i) => { const x = i*segW + segW/2; let yNorm = 0.5; // default middle if(metric === 'brightness') yNorm = 1 - (c.brightness ?? 0.5); else if(metric === 'hue') yNorm = 1 - ((c.hue ?? 0) / 360); else if(metric === 'value') yNorm = 1 - (c.value ?? 0.5); const y = Math.max(2, Math.min(ch-2, yNorm * ch)); if(i===0) ctx.moveTo(x,y); else ctx.lineTo(x,y); // optional point ctx.fillStyle = '#cfe7ff'; ctx.beginPath(); ctx.arc(x,y,1.6,0,Math.PI*2); ctx.fill(); // continue path ctx.beginPath(); ctx.moveTo(x,y); }); // Re-stroke as a smoother path ctx.strokeStyle = '#6ee7ffaa'; ctx.lineWidth = 1.6; ctx.beginPath(); colors.forEach((c, i) => { const x = i*segW + segW/2; let yNorm = 0.5; if(metric === 'brightness') yNorm = 1 - (c.brightness ?? 0.5); else if(metric === 'hue') yNorm = 1 - ((c.hue ?? 0) / 360); else if(metric === 'value') yNorm = 1 - (c.value ?? 0.5); const y = Math.max(2, Math.min(ch-2, yNorm * ch)); if(i===0) ctx.moveTo(x,y); else ctx.lineTo(x,y); }); ctx.stroke(); } function copyToClipboard(text){ navigator.clipboard?.writeText(text).then(()=>{ toast('CSS gradient copied'); }).catch(()=>{ // fallback const ta = document.createElement('textarea'); ta.value = text; document.body.appendChild(ta); ta.select(); try{ document.execCommand('copy'); toast('CSS gradient copied'); } finally { ta.remove(); } }); } function toast(msg){ const t = document.createElement('div'); t.textContent = msg; t.style.cssText = `position:fixed;left:50%;top:12px;transform:translateX(-50%);background:#0b0f15;border:1px solid #2a3346;color:#d7eaff;padding:8px 12px;border-radius:10px;box-shadow:0 12px 30px rgba(0,0,0,.4);z-index:9999`; document.body.appendChild(t); setTimeout(()=>t.remove(), 1500); } function downloadPng(){ // Draw to an offscreen canvas using the band’s natural width and height const colors = Array.from(els.band.children).map(div => div.style.background); if(colors.length === 0){ toast('Nothing to download'); return; } const segW = parseInt(els.segWidth.value,10); const segH = parseInt(els.segHeight.value,10); const pad = 20; // border around const overlayOn = els.showOverlay.checked; const w = colors.length * segW + pad*2; const h = segH + pad*2; const can = document.createElement('canvas'); can.width = w; can.height = h; const ctx = can.getContext('2d'); // bg ctx.fillStyle = '#0b0f15'; ctx.fillRect(0,0,w,h); // segments colors.forEach((css, i) => { ctx.fillStyle = css; ctx.fillRect(pad + i*segW, pad, segW, segH); }); // optional overlay (recompute minimal metric for export) if(overlayOn){ // We'll rebuild minimal color info from DOM labels const metric = els.overlayMetric.value; const info = Array.from(els.band.children).map(div => div.getAttribute('data-title')); const vals = info.map(label => { // recover approx numeric from label when present; otherwise default index ratio // Since we can't access original brightness easily, we approximate by sampling the pixel center. const x = pad + (info.indexOf(label))*segW + segW/2; const ySample = pad + segH/2; // mid const imgData = ctx.getImageData(x, ySample, 1, 1).data; // r,g,b const r=imgData[0], g=imgData[1], b=imgData[2]; const bright = (0.2126*r + 0.7152*g + 0.0722*b)/255; // We don't have hue/value here; use brightness return bright; }); ctx.strokeStyle = '#6ee7ffaa'; ctx.lineWidth = 1.6; ctx.beginPath(); vals.forEach((v,i)=>{ const x = pad + i*segW + segW/2; const y = pad + (1 - v) * segH; if(i===0) ctx.moveTo(x,y); else ctx.lineTo(x,y); }); ctx.stroke(); } const url = can.toDataURL('image/png'); const a = document.createElement('a'); a.href = url; a.download = 'hash-spectrum.png'; a.click(); } function randomHex(n){ const bytes = new Uint8Array(n/2); crypto.getRandomValues(bytes); return Array.from(bytes,b=>b.toString(16).padStart(2,'0')).join(''); } // ====== Event wiring ====== document.querySelectorAll('input[name="mode"]').forEach(r=> r.addEventListener('change', ()=>{ setHslControls(); render(); })); document.querySelectorAll('input[name="pad"]').forEach(r=> r.addEventListener('change', render)); els.btnGenesis.addEventListener('click', ()=>{ els.input.value = genesisHash; render(); }); els.btnRandom.addEventListener('click', ()=>{ els.input.value = randomHex(64); render(); }); els.renderBtn.addEventListener('click', render); els.copyCSS.addEventListener('click', ()=>{ const c = els.gradCSS.textContent; if(c && c!== '—') copyToClipboard(c); }); els.downloadPNG.addEventListener('click', downloadPng); ['input','change'].forEach(evt => { els.input.addEventListener(evt, ()=>{ /* live sanitize, defer render to button */ }); [els.sat,els.lit,els.segWidth,els.segHeight,els.showOverlay,els.overlayMetric].forEach(el=> el.addEventListener(evt, ()=>{ if(el===els.sat) els.satVal.textContent = els.sat.value + '%'; if(el===els.lit) els.litVal.textContent = els.lit.value + '%'; if(el===els.segWidth) els.segWidthVal.textContent = els.segWidth.value + ' px'; if(el===els.segHeight) els.segHeightVal.textContent = els.segHeight.value + ' px'; render(); })); }); // Boot (function init(){ els.input.value = genesisHash; els.satVal.textContent = els.sat.value + '%'; els.litVal.textContent = els.lit.value + '%'; els.segWidthVal.textContent = els.segWidth.value + ' px'; els.segHeightVal.textContent = els.segHeight.value + ' px'; setHslControls(); render(); })(); </script> </body> </html>
Save changes
Create folder
writable 0777
Create
Cancel