[ CRT: off ]
Visitors: 000003491 | Server: 2026-07-13 23:25:07 UTC | UTC: 2026-07-13 23:25:07 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/scan.js  (31.6 KB, 601 lines)
   1  /* ============================================================================
   2     scan.js  --  Orchestration, dashboard rendering, scoring, fun facts
   3     ----------------------------------------------------------------------------
   4     Ties detect.js + fingerprint.js + api/scan.php together. Runs the retro boot
   5     animation, then renders the bordered "dashboard" sections, computes risk
   6     levels + a 0-100 privacy score, and generates personalized fun facts.
   7  
   8     Exposes on window.EYES:
   9       BASE            - site base URL (set inline by the page)
  10       fetchServer()   - Promise: GET api/scan.php
  11       buildReport()   - Promise: {server, browser, fingerprint, ts}
  12       computePrivacy(report)  -> {score, categories[]}
  13       funFacts(report)        -> [strings]
  14       renderDashboard(el, report)
  15       runBoot(termEl, onDone) - types the boot sequence, then calls onDone()
  16       report          - last built report (also used by terminal.js / export.js)
  17  
  18     Stores nothing server-side. Everything computed here stays in the page.
  19     ========================================================================== */
  20  (function () {
  21    "use strict";
  22  
  23    var EYES = window.EYES = window.EYES || {};
  24    EYES.BASE = EYES.BASE || '';
  25  
  26    /* ---- small DOM / string helpers --------------------------------------- */
  27    function esc(s) {
  28      return String(s == null ? '' : s)
  29        .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
  30        .replace(/"/g, '&quot;');
  31    }
  32    function yn(v) {
  33      if (v === true)  return '<span class="val-yes">Yes</span>';
  34      if (v === false) return '<span class="val-no">No</span>';
  35      if (v == null || v === '' ) return '<span class="val-unk">Unknown</span>';
  36      return esc(v);
  37    }
  38    function riskBadge(level) {
  39      var l = String(level || 'INFO').toUpperCase();
  40      var cls = l === 'HIGH' ? 'risk-high' : l === 'MEDIUM' ? 'risk-medium'
  41              : l === 'LOW' ? 'risk-low' : 'risk-info';
  42      return '<span class="risk ' + cls + '">' + esc(l) + '</span>';
  43    }
  44    function row(k, v) {
  45      return '<tr><td class="k">' + esc(k) + '</td><td class="v">' + (v == null ? '<span class="val-unk">Unknown</span>' : v) + '</td></tr>';
  46    }
  47    function kvTable(rows) {
  48      return '<table class="data kv">' + rows.join('') + '</table>';
  49    }
  50    function asciiBar(pct) {
  51      pct = Math.max(0, Math.min(100, pct | 0));
  52      var total = 20, filled = Math.round((pct / 100) * total);
  53      var s = '[';
  54      for (var i = 0; i < total; i++) s += (i < filled ? '#' : '<span class="empty">-</span>');
  55      s += '] ' + pct + '%';
  56      return '<span class="asciibar">' + s + '</span>';
  57    }
  58  
  59    EYES._esc = esc;
  60  
  61    /* ======================================================================
  62       DATA COLLECTION
  63       ====================================================================== */
  64    EYES.fetchServer = function () {
  65      var url = (EYES.BASE || '') + '/api/scan.php';
  66      return fetch(url, { credentials: 'omit', cache: 'no-store' })
  67        .then(function (r) { return r.json(); })
  68        .catch(function () { return { ok: false, error: 'server unreachable' }; });
  69    };
  70  
  71    EYES.buildReport = function () {
  72      var jobs = [
  73        EYES.fetchServer(),
  74        (EYES.detect ? EYES.detect() : Promise.resolve(null)),
  75        (EYES.fingerprint ? EYES.fingerprint() : Promise.resolve(null))
  76      ];
  77      return Promise.all(jobs).then(function (res) {
  78        var report = { server: res[0], browser: res[1], fingerprint: res[2], ts: new Date().toISOString() };
  79        EYES.report = report;
  80        EYES.lastReport = report;
  81        return report;
  82      });
  83    };
  84  
  85    /* ======================================================================
  86       RISK CATALOG  -- what/why/impact/fix per item, and severity
  87       ====================================================================== */
  88    EYES.riskInfo = {
  89      ip: { level: 'HIGH', what: 'Your public IP address.',
  90        why: 'Every server you connect to sees it -- it is how replies find you.',
  91        impact: 'Reveals your approximate location and identifies your connection to any site or logger.',
  92        fix: 'Use a trustworthy VPN or the Tor Browser to replace it with a shared address.' },
  93      geo: { level: 'HIGH', what: 'Geolocation derived from your IP.',
  94        why: 'A public database maps IP ranges to cities and providers.',
  95        impact: 'Sites can localize, price-discriminate, or region-block you without asking.',
  96        fix: 'A VPN in another region changes this. GPS-level location still requires a permission prompt.' },
  97      isp: { level: 'MEDIUM', what: 'Your Internet Service Provider / organization / ASN.',
  98        why: 'It is attached to the IP block your address comes from.',
  99        impact: 'Narrows who you are, especially on corporate or small-ISP networks.',
 100        fix: 'VPN/Tor hide your real ISP behind the provider network instead.' },
 101      ua: { level: 'MEDIUM', what: 'User-Agent string: browser, version, OS.',
 102        why: 'Your browser sends it on every single request, by design.',
 103        impact: 'Feeds fingerprinting and lets sites target or block specific setups.',
 104        fix: 'Firefox resistFingerprinting or the Tor Browser normalize this.' },
 105      headers: { level: 'LOW', what: 'The full set of HTTP request headers.',
 106        why: 'They negotiate language, compression, and content with the server.',
 107        impact: 'Order and contents add a few bits of fingerprinting entropy.',
 108        fix: 'Little you can do; anti-fingerprinting browsers minimize the variation.' },
 109      canvas: { level: 'HIGH', what: 'Canvas fingerprint.',
 110        why: 'Your GPU/driver/font stack renders the same text/shapes slightly differently.',
 111        impact: 'A very stable, cookie-free identifier used widely by trackers.',
 112        fix: 'Firefox privacy.resistFingerprinting, Brave, or the Tor Browser randomize/block it.' },
 113      webgl: { level: 'HIGH', what: 'WebGL vendor/renderer + capabilities.',
 114        why: 'The 3D API exposes your exact GPU model and driver limits.',
 115        impact: 'Strongly identifying, and reveals your graphics hardware.',
 116        fix: 'Anti-fingerprinting modes spoof or disable WebGL details.' },
 117      audio: { level: 'MEDIUM', what: 'Audio-stack fingerprint.',
 118        why: 'Floating-point audio processing differs subtly per device/OS.',
 119        impact: 'Adds stable entropy to a cross-site fingerprint.',
 120        fix: 'resistFingerprinting / Tor Browser neutralize it.' },
 121      fonts: { level: 'MEDIUM', what: 'Installed fonts (probed indirectly).',
 122        why: 'Text rendered in a font only changes size if that font exists.',
 123        impact: 'Your installed-software footprint is fairly unique.',
 124        fix: 'Browsers that restrict font enumeration reduce this.' },
 125      screen: { level: 'MEDIUM', what: 'Screen resolution, color depth, pixel ratio.',
 126        why: 'Exposed so pages can lay themselves out.',
 127        impact: 'Combined with other signals, narrows you considerably.',
 128        fix: 'Non-maximized windows and standardized sizes (Tor) help.' },
 129      hardware: { level: 'MEDIUM', what: 'CPU cores, device memory, platform.',
 130        why: 'Exposed to help pages tune performance.',
 131        impact: 'Adds hardware entropy to your fingerprint.',
 132        fix: 'Firefox caps or spoofs these with resistFingerprinting.' },
 133      permissions: { level: 'LOW', what: 'Permission states (camera, mic, geo...).',
 134        why: 'Queryable so sites can avoid re-prompting.',
 135        impact: 'Prior grants can be detected silently.',
 136        fix: 'Review and revoke site permissions periodically.' },
 137      webrtc: { level: 'HIGH', what: 'WebRTC.',
 138        why: 'Real-time media API that can enumerate local network addresses.',
 139        impact: 'Classic "leak" that can reveal your real IP even behind a VPN.',
 140        fix: 'Disable WebRTC or use an extension that prevents the local-IP leak.' },
 141      dnt: { level: 'LOW', what: 'Do Not Track / Global Privacy Control.',
 142        why: 'A voluntary request you send; most sites ignore DNT.',
 143        impact: 'Ironically, sending it is itself one more fingerprint bit.',
 144        fix: 'GPC has some legal weight in a few regions; DNT largely does not.' },
 145      cookies: { level: 'MEDIUM', what: 'Cookie support.',
 146        why: 'The original stateful tracking mechanism.',
 147        impact: 'Enables persistent identifiers across visits.',
 148        fix: 'Block third-party cookies; clear on exit; use container tabs.' }
 149    };
 150  
 151    /* ======================================================================
 152       PRIVACY SCORE  (0 = fully exposed, 100 = well protected)
 153       ====================================================================== */
 154    EYES.computePrivacy = function (report) {
 155      var s = report.server || {}, b = report.browser || {}, fp = report.fingerprint || {};
 156      var geo = (s.geo) || {};
 157      var cats = [];
 158  
 159      function cat(name, score, notes) { cats.push({ name: name, score: Math.max(0, Math.min(100, Math.round(score))), notes: notes }); }
 160  
 161      /* Tracking: cookies + DNT + storage */
 162      (function () {
 163        var score = 100, notes = [];
 164        if (b.apis && b.apis.cookies) { score -= 30; notes.push('Cookies enabled'); }
 165        if (b.storage && b.storage.localStorage) { score -= 15; notes.push('localStorage writable'); }
 166        if (b.storage && b.storage.indexedDB) { score -= 10; notes.push('IndexedDB available'); }
 167        var dnt = b.platform && b.platform.doNotTrack;
 168        if (dnt === '1' || dnt === 'yes') { score += 10; notes.push('DNT signal sent'); }
 169        cat('Tracking', score, notes);
 170      })();
 171  
 172      /* Identity: IP exposure + VPN/proxy status */
 173      (function () {
 174        var score = 100, notes = [];
 175        if (geo && geo.ok) {
 176          score -= 40; notes.push('IP geolocated to ' + (geo.city || geo.country || 'a location'));
 177          if (geo.proxy || geo.vpn) { score += 25; notes.push('Behind VPN/proxy'); }
 178          else if (geo.hosting) { score += 15; notes.push('Hosting/datacenter IP'); }
 179          else { notes.push('Residential IP (directly identifiable)'); }
 180        } else {
 181          notes.push('IP not geolocated');
 182        }
 183        cat('Identity', score, notes);
 184      })();
 185  
 186      /* Fingerprint: how identifiable */
 187      (function () {
 188        var notes = [];
 189        var bits = fp && fp.entropyBits ? fp.entropyBits : 0;
 190        // 40 bits ~ fully unique; invert to a protection score.
 191        var score = 100 - Math.min(100, (bits / 40) * 100);
 192        notes.push('~' + bits + ' bits of entropy');
 193        if (fp && fp.trackingRisk) notes.push('Risk: ' + fp.trackingRisk);
 194        cat('Fingerprint', score, notes);
 195      })();
 196  
 197      /* Browser security: HTTPS + up-to-date-ish + webdriver */
 198      (function () {
 199        var score = 100, notes = [];
 200        if (!(s.request && s.request.is_https)) { score -= 40; notes.push('Connection is plaintext HTTP'); }
 201        else notes.push('HTTPS in use');
 202        if (b.platform && b.platform.webdriver) { score -= 10; notes.push('Automation flag set'); }
 203        if (b.ua && b.ua.browser === 'Internet Explorer') { score -= 30; notes.push('Ancient browser'); }
 204        cat('Browser Security', score, notes);
 205      })();
 206  
 207      /* Permissions: fewer granted = better */
 208      (function () {
 209        var score = 100, notes = [];
 210        var perms = b.permissions || {};
 211        Object.keys(perms).forEach(function (k) {
 212          if (perms[k] === 'granted') { score -= 15; notes.push(k + ' granted'); }
 213        });
 214        if (notes.length === 0) notes.push('No sensitive permissions granted');
 215        cat('Permissions', score, notes);
 216      })();
 217  
 218      /* Network: VPN + WebRTC leak surface */
 219      (function () {
 220        var score = 100, notes = [];
 221        if (b.apis && b.apis.webRTC) { score -= 25; notes.push('WebRTC present (possible IP leak)'); }
 222        if (s.network && s.network.behind_cloudflare) { notes.push('Behind Cloudflare'); }
 223        if (geo && (geo.proxy || geo.vpn)) { score += 10; notes.push('VPN/proxy active'); }
 224        cat('Network', score, notes);
 225      })();
 226  
 227      var overall = Math.round(cats.reduce(function (a, c) { return a + c.score; }, 0) / cats.length);
 228      return { score: overall, categories: cats };
 229    };
 230  
 231    /* ======================================================================
 232       FUN FACTS
 233       ====================================================================== */
 234    EYES.funFacts = function (report) {
 235      var b = report.browser || {}, fp = report.fingerprint || {}, s = report.server || {};
 236      var geo = s.geo || {};
 237      var facts = [];
 238      function push(f) { if (f) facts.push(f); }
 239  
 240      if (b.preferences && b.preferences.darkMode) push('Your browser prefers dark mode -- a person of taste.');
 241      if (b.preferences && b.preferences.reducedMotion) push('You have "reduce motion" enabled; the blinking cursor apologizes.');
 242      if (b.platform && b.platform.cores) push('You are exposing ' + b.platform.cores + ' CPU cores to every website you visit.');
 243      if (b.platform && b.platform.memory) push('Your device reports roughly ' + b.platform.memory + ' GB of RAM to the web.');
 244      if (b.codecs && /probably|maybe/.test(b.codecs.av1)) push('Your browser can decode AV1 video -- fairly modern hardware.');
 245      if (b.webgl && b.webgl.webgl2) push('Your GPU supports WebGL2: ' + (b.webgl.renderer || 'a real GPU') + '.');
 246      if (b.webgl && b.webgl.renderer) push('Websites can read your exact GPU: "' + b.webgl.renderer + '".');
 247      if (fp && fp.supported && fp.supported.canvas) push('Canvas fingerprinting works in your browser -- you can be tracked without cookies.');
 248      if (fp && fp.entropyBits) push('Your fingerprint carries about ' + fp.entropyBits + ' bits of entropy (1 in ' + fp.oneIn.toLocaleString() + ').');
 249      if (b.fonts && b.fonts.length) push('We detected ' + b.fonts.length + ' installed fonts -- a surprisingly personal detail.');
 250      if (b.screen && b.screen.width) push('Your screen is ' + b.screen.width + 'x' + b.screen.height + ' at ' + (b.screen.pixelRatio || 1) + 'x pixel ratio.');
 251      if (b.time && b.time.timezone) push('Your timezone gives you away as ' + b.time.timezone + '.');
 252      if (geo && geo.ok && geo.city) push('Your IP places you near ' + geo.city + ', ' + (geo.country || '') + '.');
 253      if (geo && geo.ok && geo.isp) push('Your connection appears to run through ' + geo.isp + '.');
 254      if (geo && (geo.proxy || geo.vpn)) push('Nice: you appear to be behind a VPN or proxy.');
 255      if (b.apis && b.apis.webRTC) push('WebRTC is enabled -- a classic way to leak your real IP past a VPN.');
 256      if (b.apis && b.apis.bluetooth) push('The Web Bluetooth API is available in your browser.');
 257      if (b.apis && b.apis.usb) push('The WebUSB API is available -- pages can request access to USB devices.');
 258      if (b.platform && b.platform.languages && b.platform.languages.length) push('You advertise these languages: ' + b.platform.languages.join(', ') + '.');
 259      if (b.network && b.network.effectiveType) push('Your connection reports itself as roughly "' + b.network.effectiveType + '".');
 260      if (b.battery) push('Your battery is at ' + b.battery.level + '% and ' + (b.battery.charging ? 'charging' : 'discharging') + '.');
 261      if (b.platform && b.platform.doNotTrack === '1') push('You send Do Not Track. Most sites will politely ignore it.');
 262  
 263      // Guarantee at least ten.
 264      var filler = [
 265        'Everything on this page was computed on your machine, not ours.',
 266        'No cookie was set to show you any of this.',
 267        'This report will vanish the moment you close the tab.',
 268        'A plain <marquee> tag is scrolling above. Respect.',
 269        'The whole site is a few PHP files and some vanilla JavaScript.'
 270      ];
 271      var i = 0;
 272      while (facts.length < 10 && i < filler.length) push(filler[i++]);
 273      return facts;
 274    };
 275  
 276    /* ======================================================================
 277       DASHBOARD RENDERING
 278       ====================================================================== */
 279    function panel(title, iconHtml, bodyHtml) {
 280      return '<div class="panel"><div class="panel-title">' + (iconHtml || '') +
 281        ' ' + esc(title) + '</div><div class="panel-body">' + bodyHtml + '</div></div>';
 282    }
 283    function icon(name, color) {
 284      // Uses the same 8x8 grids as PHP by drawing simple inline SVG here too.
 285      // For simplicity in JS we reuse a filled box; PHP-rendered icons appear in
 286      // the static page chrome. Keep it lightweight.
 287      color = color || '#00FF00';
 288      return '<span style="display:inline-block;width:12px;height:12px;background:' + color + ';vertical-align:middle"></span>';
 289    }
 290    function riskLine(key) {
 291      var r = EYES.riskInfo[key];
 292      if (!r) return '';
 293      return '<details><summary>' + riskBadge(r.level) + ' ' + esc(r.what) + '</summary>' +
 294        '<div class="details-body">' +
 295        '<div><b>Why sites know it:</b> ' + esc(r.why) + '</div>' +
 296        '<div class="impact"><b>Privacy impact:</b> ' + esc(r.impact) + '</div>' +
 297        '<div class="fix"><b>How to reduce it:</b> ' + esc(r.fix) + '</div>' +
 298        '</div></details>';
 299    }
 300  
 301    EYES.renderDashboard = function (el, report) {
 302      if (!el) return;
 303      var s = report.server || {}, b = report.browser || {}, fp = report.fingerprint || {};
 304      var geo = s.geo || {}, req = s.request || {}, net = s.network || {};
 305      var html = [];
 306  
 307      /* ----- GENERAL ----- */
 308      html.push(panel('General', icon('globe', '#00FFFF'), kvTable([
 309        row('Your IP Address', '<b class="yellow">' + esc(s.ip || req.ip || '?') + '</b>'),
 310        row('Connection', (req.is_https ? '<span class="val-yes">HTTPS (encrypted)</span>' : '<span class="val-no">HTTP (plaintext)</span>')),
 311        row('Protocol', esc(req.protocol || '?')),
 312        row('Host', esc((s.server && s.server.host) || '?')),
 313        row('Server Software', esc((s.server && s.server.software) || '?')),
 314        row('Report Generated', esc(report.ts))
 315      ]) + riskLine('ip')));
 316  
 317      /* ----- NETWORK / GEO ----- */
 318      var geoRows = [];
 319      if (geo && geo.ok) {
 320        geoRows = [
 321          row('Country', esc(geo.country) + (geo.country_code ? ' (' + esc(geo.country_code) + ')' : '')),
 322          row('City / Region', esc([geo.city, geo.region].filter(Boolean).join(', ') || '?')),
 323          row('Continent', esc(geo.continent || '?')),
 324          row('Coordinates', geo.lat != null ? esc(geo.lat + ', ' + geo.lon) : '<span class="val-unk">Unknown</span>'),
 325          row('Timezone', esc(geo.timezone || '?')),
 326          row('Currency', esc(geo.currency || '?')),
 327          row('ISP', esc(geo.isp || '?')),
 328          row('Organization', esc(geo.org || '?')),
 329          row('ASN', esc(geo.asn || '?') + (geo.as_name ? ' (' + esc(geo.as_name) + ')' : '')),
 330          row('VPN / Proxy', yn(geo.proxy || geo.vpn)),
 331          row('Hosting / Datacenter', yn(geo.hosting)),
 332          row('Tor Exit', yn(geo.tor)),
 333          row('Mobile Network', yn(geo.mobile)),
 334          row('Lookup Provider', esc(geo.provider || '?'))
 335        ];
 336      } else {
 337        geoRows = [row('Geolocation', '<span class="val-unk">' + esc((geo && geo.note) || 'Unavailable') + '</span>')];
 338      }
 339      geoRows.push(row('Behind Proxy', yn(net.behind_proxy)));
 340      geoRows.push(row('Behind Cloudflare', yn(net.behind_cloudflare)));
 341      if (b.network && b.network.supported) {
 342        geoRows.push(row('Connection Type', esc(b.network.effectiveType || b.network.type || '?')));
 343        geoRows.push(row('Downlink (Mbps)', b.network.downlink != null ? esc(b.network.downlink) : '<span class="val-unk">Unknown</span>'));
 344        geoRows.push(row('RTT (ms)', b.network.rtt != null ? esc(b.network.rtt) : '<span class="val-unk">Unknown</span>'));
 345        geoRows.push(row('Save-Data', yn(b.network.saveData)));
 346      }
 347      html.push(panel('Network', icon('globe'), kvTable(geoRows) + riskLine('geo') + riskLine('isp')));
 348  
 349      /* ----- BROWSER ----- */
 350      if (b.ua) {
 351        html.push(panel('Browser', icon('monitor', '#00FFFF'), kvTable([
 352          row('Browser', '<b>' + esc(b.ua.browser) + ' ' + esc(b.ua.version) + '</b>'),
 353          row('Engine', esc(b.ua.engine)),
 354          row('Operating System', esc(b.ua.os) + ' ' + esc(b.ua.osVersion)),
 355          row('Platform', esc(b.platform.platform)),
 356          row('Architecture', esc(b.platform.architecture)),
 357          row('Languages', esc((b.platform.languages || []).join(', '))),
 358          row('Vendor', esc(b.ua.vendor || '?')),
 359          row('Cookies Enabled', yn(b.apis.cookies)),
 360          row('Do Not Track', esc(b.platform.doNotTrack || '(not set)')),
 361          row('WebDriver (automation)', yn(b.platform.webdriver)),
 362          row('User-Agent', '<span style="font-size:14px">' + esc(b.ua.raw) + '</span>')
 363        ]) + riskLine('ua') + riskLine('cookies') + riskLine('dnt')));
 364      }
 365  
 366      /* ----- DISPLAY ----- */
 367      if (b.screen) {
 368        html.push(panel('Display', icon('monitor'), kvTable([
 369          row('Screen Resolution', esc(b.screen.width + ' x ' + b.screen.height)),
 370          row('Available Screen', esc(b.screen.availWidth + ' x ' + b.screen.availHeight)),
 371          row('Viewport', esc(b.screen.viewportW + ' x ' + b.screen.viewportH)),
 372          row('Pixel Ratio', esc(b.screen.pixelRatio)),
 373          row('Color Depth', esc(b.screen.colorDepth + '-bit')),
 374          row('Orientation', esc(b.screen.orientation || '?')),
 375          row('Dark Mode', yn(b.preferences.darkMode)),
 376          row('Reduced Motion', yn(b.preferences.reducedMotion)),
 377          row('Touch Support', yn(b.preferences.touch)),
 378          row('Fine Pointer', yn(b.preferences.pointerFine))
 379        ]) + riskLine('screen')));
 380      }
 381  
 382      /* ----- HARDWARE ----- */
 383      if (b.platform) {
 384        var hwRows = [
 385          row('CPU Cores', esc(b.platform.cores || 'Unknown')),
 386          row('Device Memory', b.platform.memory ? esc(b.platform.memory + ' GB (approx)') : '<span class="val-unk">Unknown</span>'),
 387          row('Max Touch Points', esc(b.platform.maxTouchPoints)),
 388          row('JS Heap Limit', b.jsHeapLimit ? esc(Math.round(b.jsHeapLimit / 1048576) + ' MB') : '<span class="val-unk">Unknown</span>')
 389        ];
 390        if (b.battery) {
 391          hwRows.push(row('Battery Level', esc(b.battery.level + '%')));
 392          hwRows.push(row('Battery Charging', yn(b.battery.charging)));
 393        }
 394        if (b.mediaDevices && b.mediaDevices.supported) {
 395          hwRows.push(row('Cameras', esc(b.mediaDevices.cameras)));
 396          hwRows.push(row('Microphones', esc(b.mediaDevices.microphones)));
 397          hwRows.push(row('Speakers', esc(b.mediaDevices.speakers)));
 398        }
 399        html.push(panel('Hardware', icon('chip', '#FFFF00'), kvTable(hwRows) + riskLine('hardware')));
 400      }
 401  
 402      /* ----- GRAPHICS ----- */
 403      if (b.webgl) {
 404        var gRows = [
 405          row('WebGL', yn(b.webgl.supported)),
 406          row('WebGL2', yn(b.webgl.webgl2)),
 407          row('GPU Vendor', esc(b.webgl.vendor || '?')),
 408          row('GPU Renderer', esc(b.webgl.renderer || '?')),
 409          row('GLSL Version', esc(b.webgl.shading || '?')),
 410          row('Max Texture Size', esc(b.webgl.maxTexture || '?')),
 411          row('WebGL Extensions', esc(b.webgl.extensions))
 412        ];
 413        if (b.webgpu) gRows.push(row('WebGPU', yn(b.webgpu.supported) + (b.webgpu.vendor ? ' (' + esc(b.webgpu.vendor) + ')' : '')));
 414        html.push(panel('Graphics', icon('chip'), kvTable(gRows) + riskLine('webgl')));
 415      }
 416  
 417      /* ----- STORAGE ----- */
 418      if (b.storage) {
 419        html.push(panel('Storage', icon('disk', '#00FFAA'), kvTable([
 420          row('Cookies', yn(b.storage.cookiesEnabled)),
 421          row('localStorage', yn(b.storage.localStorage)),
 422          row('sessionStorage', yn(b.storage.sessionStorage)),
 423          row('IndexedDB', yn(b.storage.indexedDB)),
 424          row('Cache API', yn(b.storage.cacheAPI)),
 425          row('CookieStore API', yn(b.storage.cookieStore)),
 426          row('Estimated Quota', (b.storageQuota && b.storageQuota.quota) ? esc(Math.round(b.storageQuota.quota / 1048576) + ' MB') : '<span class="val-unk">Unknown</span>'),
 427          row('Estimated Usage', (b.storageQuota && b.storageQuota.usage != null) ? esc(Math.round(b.storageQuota.usage / 1024) + ' KB') : '<span class="val-unk">Unknown</span>')
 428        ])));
 429      }
 430  
 431      /* ----- PERMISSIONS ----- */
 432      if (b.permissions) {
 433        var pRows = Object.keys(b.permissions).map(function (k) {
 434          var st = b.permissions[k];
 435          var cls = st === 'granted' ? 'val-yes' : st === 'denied' ? 'val-no' : 'val-unk';
 436          return row(k, '<span class="' + cls + '">' + esc(st) + '</span>');
 437        });
 438        if (!pRows.length) pRows = [row('Permissions API', '<span class="val-unk">Not available</span>')];
 439        html.push(panel('Permissions', icon('key', '#FFFF00'), kvTable(pRows) + riskLine('permissions')));
 440      }
 441  
 442      /* ----- SECURITY / HEADERS ----- */
 443      var hdrs = s.headers || {};
 444      var hdrRows = Object.keys(hdrs).map(function (k) {
 445        return '<tr><td class="k">' + esc(k) + '</td><td class="v" style="font-size:14px">' + esc(hdrs[k]) + '</td></tr>';
 446      });
 447      html.push(panel('Security & Headers', icon('lock', '#00FF00'),
 448        kvTable([
 449          row('HTTPS', yn(req.is_https)),
 450          row('WebRTC Present', yn(b.apis && b.apis.webRTC)),
 451          row('Service Workers', yn(b.apis && b.apis.serviceWorker)),
 452          row('Notifications API', yn(b.apis && b.apis.notification)),
 453          row('Credentials API', yn(b.apis && b.apis.credentials))
 454        ]) + riskLine('webrtc') +
 455        '<details><summary>' + riskBadge('LOW') + ' All request headers (' + hdrRows.length + ')</summary>' +
 456        '<div class="details-body"><table class="data">' + hdrRows.join('') + '</table></div></details>'
 457      ));
 458  
 459      /* ----- FINGERPRINT ----- */
 460      if (fp && fp.id) {
 461        var sigRows = Object.keys(fp.signals).map(function (k) {
 462          return row(k, '<span class="mono" style="font-size:13px">' + esc(fp.signals[k].short) + '...</span> ' +
 463            (fp.signals[k].value && fp.signals[k].value !== '(none)' ? '' : '<span class="val-unk">(unavailable)</span>'));
 464        });
 465        html.push(panel('Fingerprint', icon('eye', '#FF3030'),
 466          '<div class="badbx"><b>Your fingerprint ID (SHA-256):</b><br>' +
 467          '<span class="mono" style="font-size:14px;word-break:break-all">' + esc(fp.id) + '</span></div>' +
 468          kvTable([
 469            row('Estimated Entropy', esc(fp.entropyBits + ' bits')),
 470            row('Approx. Uniqueness', asciiBar(fp.uniqueness)),
 471            row('You are ~1 in', esc(fp.oneIn.toLocaleString())),
 472            row('Tracking Risk', riskBadge(fp.trackingRisk))
 473          ]) +
 474          '<h4>Per-signal hashes</h4>' + kvTable(sigRows) +
 475          riskLine('canvas') + riskLine('webgl') + riskLine('audio') + riskLine('fonts')
 476        ));
 477      }
 478  
 479      /* ----- EXPERIMENTAL APIS ----- */
 480      if (b.apis) {
 481        var expKeys = ['bluetooth','usb','hid','serial','nfc','webGPU','webXR','wakeLock',
 482          'idleDetection','fileSystemAccess','eyeDropper','share','contacts','virtualKeyboard',
 483          'ambientLight','accelerometer','gyroscope','magnetometer','speechRecognition',
 484          'speechSynthesis','paymentRequest','vibration'];
 485        var expRows = expKeys.map(function (k) { return row(k, yn(b.apis[k])); });
 486        html.push(panel('Experimental APIs', icon('plug', '#FF3030'), kvTable(expRows)));
 487      }
 488  
 489      /* ----- CODECS & FORMATS ----- */
 490      if (b.codecs) {
 491        var cRows = Object.keys(b.codecs).map(function (k) {
 492          var v = b.codecs[k];
 493          var ok = v && v !== 'no';
 494          return row(k.toUpperCase(), ok ? '<span class="val-yes">' + esc(v) + '</span>' : '<span class="val-no">no</span>');
 495        });
 496        var iRows = Object.keys(b.imageFormats).map(function (k) { return row(k.toUpperCase(), yn(b.imageFormats[k])); });
 497        html.push(panel('Codecs & Image Formats', icon('monitor'),
 498          '<h4>Video / Audio Codecs</h4>' + kvTable(cRows) +
 499          '<h4>Image Formats</h4>' + kvTable(iRows)));
 500      }
 501  
 502      /* ----- FONTS ----- */
 503      if (b.fonts && b.fonts.length) {
 504        html.push(panel('Detected Fonts (' + b.fonts.length + ')', icon('folder'),
 505          '<div style="font-size:15px">' + b.fonts.map(esc).join(' &middot; ') + '</div>' + riskLine('fonts')));
 506      }
 507  
 508      /* ----- PRIVACY SCORE ----- */
 509      var priv = EYES.computePrivacy(report);
 510      var scoreClass = priv.score >= 66 ? '' : priv.score >= 33 ? 'warn' : 'bad';
 511      var catRows = priv.categories.map(function (c) {
 512        var cc = c.score >= 66 ? '' : c.score >= 33 ? 'warn' : 'bad';
 513        return '<tr><td class="k">' + esc(c.name) + '</td><td class="v">' +
 514          '<div class="bar ' + cc + '"><div class="bar-fill" style="width:' + c.score + '%"></div>' +
 515          '<div class="bar-label">' + c.score + '/100</div></div>' +
 516          '<div class="dim" style="font-size:13px">' + esc(c.notes.join('; ')) + '</div></td></tr>';
 517      });
 518      html.unshift(panel('Privacy Score', icon('lock', '#00FF00'),
 519        '<div class="center"><pre class="' + (priv.score >= 66 ? 'green' : priv.score >= 33 ? 'yellow' : 'red') +
 520        '" style="font-size:22px;margin:0">OVERALL: ' + priv.score + ' / 100</pre></div>' +
 521        '<div class="bar ' + scoreClass + '" style="height:26px"><div class="bar-fill" style="width:' + priv.score + '%"></div>' +
 522        '<div class="bar-label" style="font-size:16px">' + priv.score + ' / 100</div></div>' +
 523        '<table class="data kv" style="margin-top:8px">' + catRows.join('') + '</table>' +
 524        '<p class="dim">Higher is better. 100 = well protected, 0 = fully exposed. This is a rough, honest heuristic, not a certification.</p>'
 525      ));
 526  
 527      /* ----- FUN FACTS ----- */
 528      var facts = EYES.funFacts(report);
 529      html.push(panel('Fun Facts About You', icon('warning', '#FFFF00'),
 530        '<ul class="facts">' + facts.map(function (f) { return '<li>' + esc(f) + '</li>'; }).join('') + '</ul>'));
 531  
 532      el.innerHTML = html.join('\n');
 533    };
 534  
 535    /* ======================================================================
 536       BOOT ANIMATION
 537       ====================================================================== */
 538    EYES.runBoot = function (termEl, onDone) {
 539      if (!termEl) { if (onDone) onDone(); return; }
 540      var lines = [
 541        { t: 'Initializing...', d: 250 },
 542        { t: 'Loading browser modules...', d: 350, ok: true },
 543        { t: 'Enumerating HTTP headers...', d: 400, ok: true },
 544        { t: 'Analyzing network...', d: 450, ok: true },
 545        { t: 'Resolving IP intelligence...', d: 500, ok: true },
 546        { t: 'Probing 60+ Web APIs...', d: 450, ok: true },
 547        { t: 'Generating browser fingerprint...', d: 550, ok: true },
 548        { t: 'Hashing signals (SHA-256)...', d: 400, ok: true },
 549        { t: 'Calculating privacy score...', d: 400, ok: true },
 550        { t: 'Generating report...', d: 350, ok: true }
 551      ];
 552      termEl.innerHTML = '<div class="dim">WhatCanILearn scanner v1.0 -- starting...</div>';
 553      var i = 0;
 554      function step() {
 555        if (i >= lines.length) {
 556          var d = document.createElement('div');
 557          d.innerHTML = '<span class="ok">All modules complete.</span> Rendering dashboard <span class="cursor"></span>';
 558          termEl.appendChild(d);
 559          termEl.scrollTop = termEl.scrollHeight;
 560          setTimeout(function () { if (onDone) onDone(); }, 500);
 561          return;
 562        }
 563        var ln = lines[i++];
 564        var div = document.createElement('div');
 565        div.innerHTML = '<span class="prompt">$</span> ' + esc(ln.t) + ' <span class="typing"></span>';
 566        termEl.appendChild(div);
 567        termEl.scrollTop = termEl.scrollHeight;
 568        setTimeout(function () {
 569          div.innerHTML = '<span class="prompt">$</span> ' + esc(ln.t) +
 570            (ln.ok !== false ? ' <span class="ok">[ DONE ]</span>' : ' <span class="warn">[ .. ]</span>');
 571          termEl.scrollTop = termEl.scrollHeight;
 572          step();
 573        }, ln.d);
 574      }
 575      step();
 576    };
 577  
 578    /* ======================================================================
 579       AUTO-WIRE the scan page if the expected containers exist.
 580       ====================================================================== */
 581    EYES.autoScan = function () {
 582      var termEl = document.getElementById('bootTerminal');
 583      var dashEl = document.getElementById('dashboard');
 584      if (!termEl || !dashEl) return;
 585      EYES.runBoot(termEl, function () {
 586        EYES.buildReport().then(function (report) {
 587          EYES.renderDashboard(dashEl, report);
 588          var status = document.getElementById('scanStatus');
 589          if (status) status.innerHTML = '<span class="ok">Scan complete.</span> ' +
 590            'Nothing was stored. <a href="' + (EYES.BASE || '') + '/fingerprint.php">See your fingerprint</a> &middot; ' +
 591            '<a href="' + (EYES.BASE || '') + '/privacy.php">Privacy score</a>';
 592          var exp = document.getElementById('exportBar');
 593          if (exp) exp.style.display = 'block';
 594        }).catch(function (err) {
 595          dashEl.innerHTML = '<div class="badbx">Scan failed: ' + esc(err && err.message ? err.message : err) + '</div>';
 596        });
 597      });
 598    };
 599  
 600  })();
 601  

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