[ CRT: off ]
Visitors: 000003492 | Server: 2026-07-13 23:25:20 UTC | UTC: 2026-07-13 23:25:20 UTC
Best Viewed in Firefox | Powered by PHP 7.4.33 | Connection: HTTPS (secure) | No Cookies · No Tracking
*** Welcome to the last honest website on the internet *** +++ This site stores NOTHING about you -- everything is computed in YOUR browser +++ NEW! Live browser fingerprinting module added +++ NEW! Interactive terminal -- type help to begin +++ Best viewed in Firefox at 1024x768 or higher +++ Remember: if the product is free, usually YOU are the product -- but not here +++ Greetz to everyone still running an old CRT monitor <3

disk Source Code

The entire project, readable in your browser. No hidden JavaScript, no sneaky beacons. If we claimed we don't track you, this is where you check.

disk assets/js/fingerprint.js  (12.0 KB, 322 lines)
   1  /* ============================================================================
   2     fingerprint.js  --  Local browser fingerprinting
   3     ----------------------------------------------------------------------------
   4     Builds several independent fingerprints (canvas, audio, WebGL, fonts, screen,
   5     timezone, language, hardware), then hashes the whole bundle into one stable
   6     SHA-256 identifier. Also produces per-signal hashes and a rough uniqueness /
   7     tracking-risk estimate.
   8  
   9     Exposes:  window.EYES.fingerprint()  ->  Promise<object>
  10  
  11     NOTHING is uploaded. The hash never leaves the browser. This exists purely to
  12     show the visitor how identifiable they are with no cookies involved.
  13     ========================================================================== */
  14  (function () {
  15    "use strict";
  16  
  17    window.EYES = window.EYES || {};
  18  
  19    /* ---- SHA-256 via Web Crypto, with a pure-JS fallback ------------------- */
  20    function sha256(str) {
  21      // Prefer the native, correct implementation.
  22      if (window.crypto && window.crypto.subtle && window.TextEncoder) {
  23        try {
  24          var data = new TextEncoder().encode(str);
  25          return window.crypto.subtle.digest('SHA-256', data).then(function (buf) {
  26            var arr = Array.prototype.slice.call(new Uint8Array(buf));
  27            return arr.map(function (b) { return ('00' + b.toString(16)).slice(-2); }).join('');
  28          });
  29        } catch (e) { /* fall through */ }
  30      }
  31      // Fallback: synchronous pure-JS SHA-256 wrapped in a resolved Promise.
  32      return Promise.resolve(sha256_js(str));
  33    }
  34  
  35    /* Pure-JS SHA-256 (used only if Web Crypto is unavailable, e.g. old/insecure
  36       contexts). Compact but complete implementation. */
  37    function sha256_js(ascii) {
  38      function rightRotate(value, amount) { return (value >>> amount) | (value << (32 - amount)); }
  39      var mathPow = Math.pow;
  40      var maxWord = mathPow(2, 32);
  41      var result = '';
  42      var words = [];
  43      var asciiBitLength = ascii.length * 8;
  44  
  45      var hash = sha256_js.h = sha256_js.h || [];
  46      var k = sha256_js.k = sha256_js.k || [];
  47      var primeCounter = k.length;
  48      var isComposite = {};
  49      for (var candidate = 2; primeCounter < 64; candidate++) {
  50        if (!isComposite[candidate]) {
  51          for (var i = 0; i < 313; i += candidate) isComposite[i] = candidate;
  52          hash[primeCounter] = (mathPow(candidate, 0.5) * maxWord) | 0;
  53          k[primeCounter++] = (mathPow(candidate, 1 / 3) * maxWord) | 0;
  54        }
  55      }
  56  
  57      // Reset hash to initial values each call.
  58      hash = hash.slice(0, 8);
  59  
  60      ascii += '\x80';
  61      while (ascii.length % 64 - 56) ascii += '\x00';
  62      for (i = 0; i < ascii.length; i++) {
  63        var j = ascii.charCodeAt(i);
  64        if (j >> 8) return ''; // ASCII only in fallback path
  65        words[i >> 2] |= j << ((3 - i) % 4) * 8;
  66      }
  67      words[words.length] = (asciiBitLength / maxWord) | 0;
  68      words[words.length] = asciiBitLength;
  69  
  70      for (j = 0; j < words.length;) {
  71        var w = words.slice(j, j += 16);
  72        var oldHash = hash.slice(0);
  73  
  74        for (i = 0; i < 64; i++) {
  75          var w15 = w[i - 15], w2 = w[i - 2];
  76          var a = hash[0], e = hash[4];
  77          var temp1 = hash[7]
  78            + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25))
  79            + ((e & hash[5]) ^ (~e & hash[6]))
  80            + k[i]
  81            + (w[i] = (i < 16) ? w[i] : (
  82                w[i - 16]
  83                + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3))
  84                + w[i - 7]
  85                + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10))
  86              ) | 0);
  87          var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22))
  88            + ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2]));
  89  
  90          hash = [(temp1 + temp2) | 0].concat(hash);
  91          hash[4] = (hash[4] + temp1) | 0;
  92        }
  93  
  94        for (i = 0; i < 8; i++) hash[i] = (hash[i] + oldHash[i]) | 0;
  95      }
  96  
  97      for (i = 0; i < 8; i++) {
  98        for (j = 3; j + 1; j--) {
  99          var b = (hash[i] >> (j * 8)) & 255;
 100          result += ((b < 16) ? 0 : '') + b.toString(16);
 101        }
 102      }
 103      return result;
 104    }
 105  
 106    /* ---- Canvas fingerprint ------------------------------------------------ */
 107    function canvasFP() {
 108      try {
 109        var canvas = document.createElement('canvas');
 110        canvas.width = 280; canvas.height = 60;
 111        var ctx = canvas.getContext('2d');
 112        if (!ctx) return { supported: false, data: '' };
 113  
 114        ctx.textBaseline = 'top';
 115        ctx.font = "14px 'Arial'";
 116        ctx.textBaseline = 'alphabetic';
 117        ctx.fillStyle = '#f60';
 118        ctx.fillRect(125, 1, 62, 20);
 119        ctx.fillStyle = '#069';
 120        ctx.font = "11pt no-real-font-123";
 121        ctx.fillText('WhatCanILearn <canvas> 1.0 ☠🔒', 2, 15);
 122        ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
 123        ctx.font = "18pt Arial";
 124        ctx.fillText('WhatCanILearn <canvas> 1.0 ☠🔒', 4, 45);
 125  
 126        // geometry
 127        ctx.globalCompositeOperation = 'multiply';
 128        ['rgb(255,0,255)','rgb(0,255,255)','rgb(255,255,0)'].forEach(function (c, i) {
 129          ctx.fillStyle = c;
 130          ctx.beginPath();
 131          ctx.arc(50 + i * 80, 40, 40, 0, Math.PI * 2, true);
 132          ctx.closePath(); ctx.fill();
 133        });
 134        ctx.fillStyle = 'rgb(255,0,255)';
 135        ctx.arc(75, 40, 75, 0, Math.PI * 2, true);
 136        ctx.arc(75, 40, 25, 0, Math.PI * 2, true);
 137        ctx.fill('evenodd');
 138  
 139        var data = canvas.toDataURL();
 140        return { supported: true, data: data };
 141      } catch (e) {
 142        return { supported: false, data: '' };
 143      }
 144    }
 145  
 146    /* ---- Audio fingerprint (offline oscillator, no sound played) ----------- */
 147    function audioFP() {
 148      return new Promise(function (resolve) {
 149        var AC = window.OfflineAudioContext || window.webkitOfflineAudioContext;
 150        if (!AC) { resolve({ supported: false, data: '' }); return; }
 151        var done = false;
 152        var t = setTimeout(function () {
 153          if (!done) { done = true; resolve({ supported: true, data: 'timeout' }); }
 154        }, 1500);
 155        try {
 156          var ctx = new AC(1, 44100, 44100);
 157          var osc = ctx.createOscillator();
 158          osc.type = 'triangle';
 159          osc.frequency.value = 10000;
 160          var comp = ctx.createDynamicsCompressor();
 161          comp.threshold.value = -50;
 162          comp.knee.value = 40;
 163          comp.ratio.value = 12;
 164          comp.attack.value = 0;
 165          comp.release.value = 0.25;
 166          osc.connect(comp);
 167          comp.connect(ctx.destination);
 168          osc.start(0);
 169          ctx.startRendering();
 170          ctx.oncomplete = function (ev) {
 171            if (done) return; done = true; clearTimeout(t);
 172            try {
 173              var buf = ev.renderedBuffer.getChannelData(0);
 174              var sum = 0;
 175              for (var i = 4500; i < 5000; i++) sum += Math.abs(buf[i]);
 176              resolve({ supported: true, data: sum.toString() });
 177            } catch (e) { resolve({ supported: true, data: 'error' }); }
 178          };
 179        } catch (e) {
 180          if (!done) { done = true; clearTimeout(t); resolve({ supported: false, data: '' }); }
 181        }
 182      });
 183    }
 184  
 185    /* ---- WebGL fingerprint ------------------------------------------------- */
 186    function webglFP() {
 187      try {
 188        var canvas = document.createElement('canvas');
 189        var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
 190        if (!gl) return { supported: false, data: '' };
 191  
 192        var parts = [];
 193        var dbg = gl.getExtension('WEBGL_debug_renderer_info');
 194        if (dbg) {
 195          parts.push(gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL));
 196          parts.push(gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL));
 197        }
 198        parts.push(gl.getParameter(gl.VERSION));
 199        parts.push(gl.getParameter(gl.SHADING_LANGUAGE_VERSION));
 200        parts.push(gl.getParameter(gl.VENDOR));
 201        parts.push(gl.getParameter(gl.RENDERER));
 202        parts.push(gl.getParameter(gl.MAX_VERTEX_ATTRIBS));
 203        parts.push(gl.getParameter(gl.MAX_TEXTURE_SIZE));
 204        parts.push(gl.getParameter(gl.MAX_VIEWPORT_DIMS).join('x'));
 205        parts.push(gl.getParameter(gl.MAX_RENDERBUFFER_SIZE));
 206        parts.push(gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE).join(','));
 207        parts.push(gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE).join(','));
 208        var ext = gl.getSupportedExtensions() || [];
 209        parts.push(ext.sort().join(','));
 210  
 211        // A small render to capture pixel-level driver quirks.
 212        try {
 213          var buffer = gl.createBuffer();
 214          gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
 215          var verts = new Float32Array([-0.2,-0.9,0, 0.4,-0.26,0, 0,0.7,0]);
 216          gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW);
 217          parts.push('buf:ok');
 218        } catch (e) {}
 219  
 220        return { supported: true, data: parts.join('~') };
 221      } catch (e) {
 222        return { supported: false, data: '' };
 223      }
 224    }
 225  
 226    /* ==== main ============================================================== */
 227    EYES.fingerprint = function fingerprint() {
 228      var nav = navigator;
 229  
 230      // Gather synchronous signals.
 231      var canvas = canvasFP();
 232      var webgl  = webglFP();
 233  
 234      var fonts = (EYES._detectedFonts) || (function () {
 235        // Reuse detect.js font logic if it already ran; otherwise a quick probe.
 236        try {
 237          if (EYES.lastReport && EYES.lastReport.browser && EYES.lastReport.browser.fonts) {
 238            return EYES.lastReport.browser.fonts;
 239          }
 240        } catch (e) {}
 241        return [];
 242      })();
 243  
 244      var screenSig = [
 245        screen.width, screen.height, screen.availWidth, screen.availHeight,
 246        screen.colorDepth, screen.pixelDepth, window.devicePixelRatio
 247      ].join('x');
 248  
 249      var tzSig = (function () {
 250        try { return Intl.DateTimeFormat().resolvedOptions().timeZone + '|' + new Date().getTimezoneOffset(); }
 251        catch (e) { return String(new Date().getTimezoneOffset()); }
 252      })();
 253  
 254      var langSig = ((nav.languages || [nav.language]).join(',')) + '|' + (nav.language || '');
 255  
 256      var hwSig = [
 257        nav.hardwareConcurrency || '?', nav.deviceMemory || '?', nav.platform || '?',
 258        nav.maxTouchPoints || 0, nav.vendor || '?'
 259      ].join('|');
 260  
 261      var uaSig = nav.userAgent || '';
 262  
 263      return audioFP().then(function (audio) {
 264        // Per-signal hashes (parallel).
 265        var signals = {
 266          canvas:   canvas.data,
 267          audio:    audio.data,
 268          webgl:    webgl.data,
 269          fonts:    fonts.join(','),
 270          screen:   screenSig,
 271          timezone: tzSig,
 272          language: langSig,
 273          hardware: hwSig,
 274          ua:       uaSig
 275        };
 276  
 277        var keys = Object.keys(signals);
 278        var hashJobs = keys.map(function (k) { return sha256(signals[k] || '(none)'); });
 279        // The combined signal that produces the master ID.
 280        var combined = keys.map(function (k) { return k + '=' + (signals[k] || ''); }).join('||');
 281        hashJobs.push(sha256(combined));
 282  
 283        return Promise.all(hashJobs).then(function (hashes) {
 284          var out = { supported: { canvas: canvas.supported, audio: audio.supported, webgl: webgl.supported },
 285                      signals: {}, id: hashes[hashes.length - 1] };
 286          keys.forEach(function (k, i) {
 287            out.signals[k] = { value: signals[k], hash: hashes[i], short: hashes[i].slice(0, 16) };
 288          });
 289  
 290          // Uniqueness / risk heuristic. More distinguishing signals present ->
 291          // more identifiable. This is a rough, honest-ish estimate, not science.
 292          var entropyBits = 0;
 293          if (canvas.supported && canvas.data) entropyBits += 8;
 294          if (webgl.supported && webgl.data)   entropyBits += 7;
 295          if (audio.supported && audio.data && audio.data !== 'timeout') entropyBits += 4;
 296          if (fonts.length)                    entropyBits += Math.min(6, Math.round(fonts.length / 6));
 297          entropyBits += 3;  // screen
 298          entropyBits += 2;  // timezone
 299          entropyBits += 2;  // language
 300          entropyBits += 3;  // hardware
 301          if (uaSig)                           entropyBits += 4;
 302  
 303          var population = Math.pow(2, entropyBits);
 304          out.entropyBits = entropyBits;
 305          out.oneIn = population;
 306          out.uniqueness = Math.min(100, Math.round((entropyBits / 40) * 100));
 307  
 308          var risk = 'LOW';
 309          if (entropyBits >= 30) risk = 'HIGH';
 310          else if (entropyBits >= 20) risk = 'MEDIUM';
 311          out.trackingRisk = risk;
 312  
 313          out._collectedAt = new Date().toISOString();
 314          return out;
 315        });
 316      });
 317    };
 318  
 319    EYES._sha256 = sha256;
 320  
 321  })();
 322  

You are reading the real file being executed on the server. What you see is what runs.