{"id":139332,"date":"2026-09-21T01:20:57","date_gmt":"2026-09-21T07:20:57","guid":{"rendered":"https:\/\/solarbacklight.com\/?page_id=139332"},"modified":"2026-09-27T03:26:11","modified_gmt":"2026-09-27T09:26:11","slug":"xaos-coin","status":"publish","type":"page","link":"https:\/\/solarbacklight.com\/?page_id=139332","title":{"rendered":""},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>XAOS Coin \u2013 Browser Miner + Living Museum<\/title>\n<style>\n  body { background:#0a0a0f; color:#ddd; font-family:monospace; padding:1rem; }\n  h1 { color:#f0e68c; text-align:center; }\n  #miner-section { margin-bottom:2rem; }\n  #pyodide-status { margin-bottom:0.5rem; color:#aaa; text-align:center; }\n  #key-status { text-align:center; margin-bottom:0.5rem; }\n  .miner-controls { display:flex; flex-wrap:wrap; gap:1rem; margin-bottom:1rem; align-items:center; justify-content:center; }\n  .miner-controls label { display:flex; flex-direction:column; gap:0.3rem; }\n  #mine-btn, #generate-key { padding:0.5rem 1rem; font-family:monospace; background:#1a1a2f; color:#f0e68c; border:1px solid #555; border-radius:4px; cursor:pointer; }\n  #mine-btn:disabled { opacity:0.5; cursor:not-allowed; }\n  #miner-output { white-space: pre-wrap; background:#111; border:1px solid #333; padding:1rem; height:400px; overflow-y:auto; font-size:0.9em; width:80%; max-width:800px; margin:0 auto; }\n  .ascii-miner { font-size: 3px; line-height:1; text-align:center; }\n  #museum-section { margin-top:1rem; }\n  .controls { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; flex-wrap: wrap; justify-content:center; }\n  input#search, input#wallet-filter, input#msg-input { padding:0.4rem; background:#1a1a2f; color:#ddd; border:1px solid #555; border-radius:4px; font-family:monospace; }\n  input#search, input#wallet-filter { width:200px; }\n  input#msg-input { flex:1; }\n  input[type=range] { width: 120px; }\n  input#zoom-input { width: 60px; padding:0.3rem; background:#1a1a2f; color:#ddd; border:1px solid #555; border-radius:4px; font-family:monospace; text-align:center; }\n\n  \/* Art viewport *\/\n  .art-viewport {\n    width: 100%;\n    max-height: 70vh;\n    background: #12121f;\n    border: 1px solid #333;\n    border-radius: 4px;\n    margin-bottom: 0.5rem;\n    overflow: hidden;\n    position: relative;\n    cursor: grab;\n  }\n  .art-viewport:active { cursor: grabbing; }\n  .art-container {\n    font-family: 'Courier New', Courier, monospace;\n    font-size: 0.18vw;\n    line-height: 1;\n    white-space: pre;\n    text-align: center;\n    color: #fffacd;\n    transform-origin: 0 0;\n    will-change: transform;\n    position: absolute;\n    top: 0;\n    left: 0;\n  }\n\n  #messages-section { margin-bottom: 1rem; background: #0a0a0f; padding: 0.5rem; border: 1px solid #333; border-radius: 4px; }\n  .message { background:#1a1a2f; padding:0.5rem; margin-bottom:0.5rem; border-radius:4px; }\n  .message .meta { font-size:0.8em; color:#888; }\n  .verify-btn { cursor:pointer; margin-left:1rem; color:#f0e68c; text-decoration:underline; }\n  .verify-status { font-size:0.8em; }\n  table { border-collapse:collapse; width:100%; margin-bottom:1rem; }\n  th,td { border:1px solid #333; padding:0.5rem; text-align:left; }\n  th { background:#1a1a2f; cursor:pointer; user-select:none; }\n  th:hover { background:#2a2a4f; }\n  tr:hover { background:#1a1a2f; cursor:pointer; }\n  .high-novelty { color:#ffd700; font-weight:bold; }\n  .error { color:#ff6b6b; }\n  #status { margin-bottom:1rem; }\n  #post-msg-btn { padding:0.4rem 1rem; background:#1a1a2f; color:#f0e68c; border:1px solid #555; border-radius:4px; cursor:pointer; }\n  #post-msg-btn:disabled { opacity:0.5; cursor:not-allowed; }\n  #post-note { font-size:0.8em; color:#888; margin-top:0.3rem; }\n  .zoom-controls { display:flex; align-items:center; gap:0.5rem; justify-content:center; margin-top:0.5rem; flex-wrap: wrap; }\n  #copy-art-btn { padding:0.4rem 1rem; background:#1a1a2f; color:#f0e68c; border:1px solid #555; border-radius:4px; cursor:pointer; font-family:monospace; }\n  #copy-art-btn:hover { background:#2a2a4f; }\n<\/style>\n<\/head>\n<body>\n<h1>\u26cf\ufe0f XAOS Coin <\/h1>\n\n<!-- MINER SECTION -->\n<div id=\"miner-section\">\n  <div id=\"pyodide-status\">Loading Pyodide\u2026<\/div>\n  <div id=\"key-status\">No key generated yet.<\/div>\n  <div class=\"miner-controls\">\n    <label>\n      Difficulty (1\u201310):\n      <input type=\"range\" id=\"difficulty-slider\" min=\"1\" max=\"10\" value=\"4\" step=\"1\">\n      <span id=\"difficulty-label\">4<\/span>\n    <\/label>\n    <label>\n      Novelty threshold:\n      <input type=\"range\" id=\"novelty-slider\" min=\"0.01\" max=\"0.5\" value=\"0.2\" step=\"0.01\">\n      <span id=\"novelty-label\">0.20<\/span>\n    <\/label>\n    <button id=\"generate-key\">Generate \/ Download Private Key<\/button>\n    <button id=\"mine-btn\" disabled>Mine One Coin<\/button>\n  <\/div>\n  <div id=\"miner-output\"><\/div>\n<\/div>\n\n<!-- MUSEUM SECTION -->\n<div id=\"museum-section\">\n  <div class=\"controls\">\n    <input type=\"text\" id=\"search\" placeholder=\"Search hash or coordinate...\">\n    <input type=\"text\" id=\"wallet-filter\" placeholder=\"Filter by PubKey hash...\">\n  <\/div>\n\n  <div class=\"art-viewport\" id=\"art-viewport\">\n    <div id=\"art-display\" class=\"art-container\"><\/div>\n  <\/div>\n\n  <div class=\"zoom-controls\">\n    <button id=\"reset-zoom\">Reset View<\/button>\n    <span>Zoom: <span id=\"zoom-value\">1.0<\/span>x<\/span>\n    <input type=\"range\" id=\"zoom-slider\" min=\"0.1\" max=\"5\" step=\"0.01\" value=\"1.0\">\n    <input type=\"text\" id=\"zoom-input\" value=\"1.0\">\n    <button id=\"copy-art-btn\">\ud83d\udccb Copy ASCII Art<\/button>\n    <small style=\"color:#aaa;\">\ud83d\uddb1\ufe0f Wheel to zoom \u00b7 Drag to pan<\/small>\n  <\/div>\n\n  <div style=\"display:flex; gap:0.5rem; margin-bottom:0.5rem; margin-top:0.5rem;\">\n    <input type=\"text\" id=\"msg-input\" placeholder=\"Write a message for this coin\u2026\" disabled>\n    <button id=\"post-msg-btn\" disabled>Post Message<\/button>\n  <\/div>\n  <div id=\"post-note\"><\/div>\n  <div id=\"messages-section\" style=\"display:none;\">\n    <h3>\ud83d\udcdd Messages<\/h3>\n    <div id=\"messages-list\"><\/div>\n  <\/div>\n  <div id=\"status\">Loading museum\u2026<\/div>\n  <p>Click any row to see its ASCII Mandelbrot fractal. Click <strong>Novelty<\/strong> or <strong>Date<\/strong> to sort.<\/p>\n  <table id=\"coin-table\">\n    <thead>\n      <tr>\n        <th>Hash<\/th>\n        <th>Coordinate<\/th>\n        <th>Scale<\/th>\n        <th>Author<\/th>\n        <th id=\"sort-novelty\">Novelty \u25be<\/th>\n        <th id=\"sort-date\">Date \u25be<\/th>\n        <th>PubKey<\/th>\n      <\/tr>\n    <\/thead>\n    <tbody><\/tbody>\n  <\/table>\n<\/div>\n\n<script src=\"https:\/\/cdn.jsdelivr.net\/pyodide\/v0.25.0\/full\/pyodide.js\"><\/script>\n<script>\n\/\/ ======================================================================\n\/\/  GLOBALS & CONSTANTS\n\/\/ ======================================================================\nconst SUPABASE_URL = \"https:\/\/lnmmaqgyihqwugrjzwjk.supabase.co\";\nconst SUPABASE_KEY = \"sb_publishable_6E-zfNX6NGxw8N88Oo3k5Q_hwIt7OBJ\";\nconst TABLE = \"mandelia\";\nconst MSG_TABLE = \"mandelia_messages\";\nlet pyodide = null;\nlet coins = [];\nlet sortBy = { key: 'date', asc: false };\nlet selectedCoin = null;\nlet keyPair = null;\nlet publicKeyB64 = \"\";\nlet publicKeyHash = \"\";\n\n\/\/ Pan & zoom state\nlet currentZoom = 1.0;\nlet panX = 0, panY = 0;\nlet isPanning = false;\nlet startPanX, startPanY, startMouseX, startMouseY;\n\n\/\/ ========== KEY GENERATION ==========\nasync function generateKeyPair() {\n  try {\n    keyPair = await crypto.subtle.generateKey(\n      { name: \"ECDSA\", namedCurve: \"P-256\" },\n      true,\n      [\"sign\", \"verify\"]\n    );\n    const spki = await crypto.subtle.exportKey(\"spki\", keyPair.publicKey);\n    const rawBytes = new Uint8Array(spki);\n    let binary = '';\n    for (const byte of rawBytes) binary += String.fromCharCode(byte);\n    publicKeyB64 = btoa(binary);\n    const hashBuffer = await crypto.subtle.digest(\"SHA-256\", spki);\n    publicKeyHash = [...new Uint8Array(hashBuffer)].map(b => b.toString(16).padStart(2, '0')).join('');\n    document.getElementById('key-status').textContent = `Public key hash: ${publicKeyHash.slice(0,16)}\u2026 (key ready)`;\n    document.getElementById('mine-btn').disabled = false;\n    return true;\n  } catch (err) {\n    console.error(\"Key generation failed:\", err);\n    document.getElementById('key-status').textContent = \"Failed to generate key.\";\n    return false;\n  }\n}\nfunction downloadPrivateKey() {\n  if (!keyPair) return;\n  crypto.subtle.exportKey(\"pkcs8\", keyPair.privateKey)\n    .then(pkcs8 => {\n      const rawBytes = new Uint8Array(pkcs8);\n      let binary = '';\n      for (const byte of rawBytes) binary += String.fromCharCode(byte);\n      const privateKeyB64 = btoa(binary);\n      const blob = new Blob([JSON.stringify({ privateKey: privateKeyB64 }, null, 2)], { type: \"application\/json\" });\n      const url = URL.createObjectURL(blob);\n      const a = document.createElement(\"a\");\n      a.href = url;\n      a.download = \"xaos_private_key.json\";\n      a.click();\n      URL.revokeObjectURL(url);\n    });\n}\ndocument.getElementById('generate-key').addEventListener('click', async () => {\n  const ok = await generateKeyPair();\n  if (ok) downloadPrivateKey();\n});\n\n\/\/ ========== ENHANCED MINER (full ASCII save, blank-art check) ==========\nconst minerPythonCode = String.raw`\nimport hashlib, math, os, random, time, json, asyncio, pyodide.http\n\n# ============ CONFIGURATION ============\nEVAL_GRID = 48                      # Increased from 24 to 48 for better detail\nEVAL_MAX_ITER = 500\nNUM_CANDIDATES_PER_COIN = 30\nMINING_TIMEOUT = 50\nMAX_INTERIOR = 0.45\nMIN_SPATIAL = 0.04\nMIN_BEAUTY_SCORE = 0.3\nTELEPORT_RANDOM_PROB = 0.6\nMAX_STUCK_STEPS = 7\nTHRESHOLD_RELAX_FACTOR = 0.85\nDETAIL_LEVEL = 1.8\nMUSEUM_WIDTH = 800\nMUSEUM_HEIGHT = 400\nCHAR_ASPECT = 1.0\nASCII_CHARS = ' .\\`^\",:;~=+*#%@'\nSUPABASE_URL = \"${SUPABASE_URL}\"\nSUPABASE_KEY = \"${SUPABASE_KEY}\"\nTABLE_NAME = \"mandelia\"\n\n# Combined score threshold (novelty 0.5 + beauty 0.5)\nMIN_COMBINED_SCORE = 0.2\n\nCURATED_SEEDS = [\n    (-0.745, 0.105), (-0.7453, 0.1127), (-0.7746, -0.1374), (-0.1226, 0.7449),\n    (0.275, 0.007), (-1.7548, 0.0), (-1.7687, 0.0017), (0.0, 1.0),\n    (-0.1592, 1.0317), (-0.55, 0.65), (-0.1011, 0.9563), (-0.7463, 0.1027),\n    (-1.401155, 0.0), (0.3245, 0.04855), (-1.7688, 0.0017), (-0.7533, 0.1177),\n]\nEXPLORER = {\"pos\": complex(-0.745, 0.105), \"scale\": 0.002, \"drift\": complex(0.0001, 0.0001), \"consecutive_interesting\": 0, \"stuck_counter\": 0}\nARCHIVE = []          # will store full 7-dim descriptors\nMINED_LOCATIONS = []\nRECENT_MINED = []\ntry:\n    import js\n    archive_json = js.localStorage.getItem(\"xaos_novelty_archive\")\n    if archive_json: ARCHIVE = [tuple(item) for item in json.loads(archive_json)]\n    mined_json = js.localStorage.getItem(\"xaos_mined_locations\")\n    if mined_json: MINED_LOCATIONS = [tuple(item) for item in json.loads(mined_json)]\nexcept: pass\ndef save_archive():\n    try:\n        import js\n        js.localStorage.setItem(\"xaos_novelty_archive\", json.dumps([list(item) for item in ARCHIVE]))\n        js.localStorage.setItem(\"xaos_mined_locations\", json.dumps([list(item) for item in MINED_LOCATIONS]))\n    except: pass\n\n# ============ DUPLICATE \/ RECENT CHECKS ============\ndef is_duplicate_location(c_real, c_imag, scale):\n    for (r,i,s) in MINED_LOCATIONS:\n        if abs(c_real-r)<0.01 and abs(c_imag-i)<0.01: return True\n        if abs(c_real-r)<0.005 and abs(c_imag-i)<0.005 and abs(scale-s)<0.0005: return True\n    return False\n\ndef is_too_recent(c_real, c_imag, scale, time_window=300):\n    now = time.time()\n    for (r,i,t) in RECENT_MINED:\n        if now - t > time_window: continue\n        if math.hypot(c_real-r, c_imag-i) < max(scale*20.0, 0.01): return True\n    return False\n\n# ============ MANDELBROT RENDERING ============\ndef escape_grid(c_real, c_imag, grid_w, grid_h, max_iter, zoom):\n    xmin = c_real - zoom; xmax = c_real + zoom\n    aspect = grid_h \/ grid_w\n    ymin = c_imag - (zoom * aspect * CHAR_ASPECT); ymax = c_imag + (zoom * aspect * CHAR_ASPECT)\n    x_step = (xmax - xmin) \/ grid_w; y_step = (ymax - ymin) \/ grid_h\n    result = [[0]*grid_w for _ in range(grid_h)]\n    for i in range(grid_h):\n        y = ymin + i*y_step\n        for j in range(grid_w):\n            x = xmin + j*x_step\n            q = (x-0.25)**2 + y*y\n            if q*(q + (x-0.25)) <= 0.25*y*y or (x+1.0)**2 + y*y <= 0.0625:\n                result[i][j] = max_iter\n                continue\n            zr, zi = 0.0, 0.0\n            for k in range(max_iter):\n                zr2, zi2 = zr*zr, zi*zi\n                if zr2 + zi2 > 4.0:\n                    result[i][j] = k\n                    break\n                zi, zr = 2*zr*zi + y, zr2 - zi2 + x\n            else: result[i][j] = max_iter\n    return result\n\ndef ascii_art_simple(grid):\n    flat = [v for row in grid for v in row]\n    if not flat: return \"\"\n    max_val = max(flat) if max(flat)>0 else 1; min_val = min(flat)\n    nchars = len(ASCII_CHARS)\n    lines = []\n    for row in grid:\n        chars = []\n        for v in row:\n            norm = 0 if max_val==min_val else (v-min_val)\/(max_val-min_val)\n            idx = int(norm*(nchars-1))\n            chars.append(ASCII_CHARS[min(max(idx,0), nchars-1)])\n        lines.append(''.join(chars))\n    return '\\n'.join(lines)\n\ndef needed_iterations(scale, for_museum):\n    depth = max(0.0, math.log2(0.003 \/ max(scale, 1e-15)))\n    base = 1200.0 if for_museum else 500.0\n    cap = 8000 if for_museum else 3000\n    return min(int((base + depth*700.0)*DETAIL_LEVEL), cap)\n\n# ============ DESCRIPTORS ============\ndef get_full_descriptor(grid):\n    \"\"\"Returns 7-dim descriptor: interior, edge_rich, total_sym, sym_score, avg, spatial, entropy\"\"\"\n    rows, cols = len(grid), len(grid[0])\n    flat = [v for row in grid for v in row]\n    max_val = max(flat) if max(flat)>0 else 1\n    interior = sum(1 for v in flat if v==max_val)\/len(flat)\n    avg = sum(flat)\/len(flat)\n    variance = sum((v-avg)**2 for v in flat)\/len(flat)\n    edge_rich = math.sqrt(variance)\/max_val\n\n    # Symmetry\n    sym_score = 0.0\n    for i in range(rows):\n        for j in range(cols\/\/2):\n            j2 = cols-1-j\n            sym_score += 1.0 - abs(grid[i][j]-grid[i][j2])\/(max_val+1)\n    sym_score = sym_score\/(rows*(cols\/\/2)) if rows*(cols\/\/2)>0 else 0\n    tb_sym = 0.0\n    for i in range(rows\/\/2):\n        i2 = rows-1-i\n        for j in range(cols):\n            tb_sym += 1.0 - abs(grid[i][j]-grid[i2][j])\/(max_val+1)\n    tb_sym = tb_sym\/((rows\/\/2)*cols) if (rows\/\/2)*cols>0 else 0\n    total_sym = (sym_score+tb_sym)\/2.0\n\n    # Spatial complexity & entropy\n    spatial = spatial_complexity(grid)\n    entropy = escape_entropy(grid)\n\n    return interior, edge_rich, total_sym, sym_score, avg, spatial, entropy\n\n# The old get_descriptor kept for compatibility if needed (not used)\ndef get_descriptor(grid):\n    return get_full_descriptor(grid)[:5]\n\ndef spatial_complexity(grid, threshold_ratio=0.06):\n    rows, cols = len(grid), len(grid[0])\n    if rows<2 or cols<2: return 0.0\n    max_val = max(max(row) for row in grid) if grid else 0\n    if max_val==0: return 0.0\n    thr = max_val * threshold_ratio\n    diffs = total = 0\n    for i in range(rows):\n        for j in range(cols-1):\n            total += 1\n            if abs(grid[i][j]-grid[i][j+1]) > thr: diffs += 1\n    for i in range(rows-1):\n        for j in range(cols):\n            total += 1\n            if abs(grid[i][j]-grid[i+1][j]) > thr: diffs += 1\n    return diffs\/total\n\ndef escape_entropy(grid, bins=16):\n    flat = [v for row in grid for v in row]\n    total = len(flat)\n    if total==0: return 0.0\n    max_val = max(flat) if max(flat)>0 else 1\n    hist = [0]*bins\n    for v in flat:\n        idx = min(int(v\/max_val*(bins-1)), bins-2) if v<max_val else bins-1\n        hist[max(idx,0)] += 1\n    H = 0.0\n    for h in hist:\n        p = h\/total\n        if p>0: H -= p*math.log2(p)\n    return H\/math.log2(bins)\n\ndef has_visual_structure(c_real, c_imag, zoom):\n    grid = escape_grid(c_real, c_imag, 12, 12, 150, zoom)\n    flat = [v for row in grid for v in row]\n    avg = sum(flat)\/len(flat)\n    variance = sum((v-avg)**2 for v in flat)\/len(flat)\n    return math.sqrt(variance) > 2.0\n\n# ============ NOVELTY (7-dim descriptor) ============\ndef is_novel(desc, archive, min_novelty):\n    \"\"\"desc is 7-dim tuple. Returns (novel, novelty_score).\"\"\"\n    if not archive:\n        return True, 1.0\n    min_dist = float('inf')\n    # weights for dimensions: interior, edge_rich, total_sym, sym_score, avg, spatial, entropy\n    weights = [3.5, 6.0, 3.0, 2.0, 1.0\/150.0, 2.0, 1.5]\n    for stored in archive:\n        dist = 0.0\n        for d, s, w in zip(desc, stored, weights):\n            dist += abs(d - s) * w\n        if dist < min_dist:\n            min_dist = dist\n    return min_dist >= min_novelty, min_dist\n\n# ============ MINING ============\ndef hash_val(c_real, c_imag, nonce):\n    return hashlib.sha256(f\"{c_real:.5f},{c_imag:.5f},{nonce}\".encode()).hexdigest()\n\nasync def mine(c_real, c_imag, timeout, difficulty_prefix):\n    start = time.time(); nonce = 0\n    while time.time()-start < timeout:\n        h = hash_val(c_real, c_imag, nonce)\n        if h.startswith(difficulty_prefix): return nonce, h\n        nonce += 1\n        if nonce % 1000 == 0: await asyncio.sleep(0)\n    return None\n\nasync def post_coin_to_supabase(coin_data):\n    url = f\"{SUPABASE_URL}\/rest\/v1\/{TABLE_NAME}\"\n    headers = {\"apikey\": SUPABASE_KEY, \"Authorization\": f\"Bearer {SUPABASE_KEY}\", \"Content-Type\": \"application\/json\", \"Prefer\": \"return=minimal\"}\n    try:\n        body = json.dumps(coin_data)\n        resp = await pyodide.http.pyfetch(url, method=\"POST\", body=body, headers=headers)\n        return resp.status, await resp.text()\n    except Exception as e:\n        print(\"Supabase error:\", e)\n        return 500, str(e)\n\n# Explorer movement (unchanged)\ndef walk_explorer(step_multiplier=1.0):\n    turn = complex(random.gauss(0,0.30), random.gauss(0,0.30))\n    EXPLORER['drift'] = EXPLORER['drift']*0.9 + turn*0.1\n    mag = abs(EXPLORER['drift'])\n    if mag>0:\n        speed = EXPLORER['scale']*1.0*step_multiplier\n        EXPLORER['drift'] = EXPLORER['drift']\/mag*speed\n    EXPLORER['pos'] += EXPLORER['drift']\n    if EXPLORER['pos'].real < -2.0 or EXPLORER['pos'].real > 0.5: EXPLORER['drift'] = complex(-EXPLORER['drift'].real, EXPLORER['drift'].imag)\n    if EXPLORER['pos'].imag < -1.3 or EXPLORER['pos'].imag > 1.3: EXPLORER['drift'] = complex(EXPLORER['drift'].real, -EXPLORER['drift'].imag)\n\ndef teleport_to_new_region():\n    current_pos = EXPLORER['pos']\n    for _ in range(10):\n        if random.random() < TELEPORT_RANDOM_PROB:\n            new_pos = complex(random.uniform(-2.0,0.5), random.uniform(-1.3,1.3))\n        else:\n            seed = random.choice(CURATED_SEEDS)\n            new_pos = complex(seed[0], seed[1]) + complex(random.uniform(-0.3,0.3), random.uniform(-0.3,0.3))\n        if abs(new_pos - current_pos) > 0.05:\n            EXPLORER['pos'] = new_pos\n            break\n    else:\n        EXPLORER['pos'] = complex(random.uniform(-2.0,0.5), random.uniform(-1.3,1.3))\n    EXPLORER['scale'] = random.uniform(0.0005,0.015)\n    EXPLORER['drift'] = complex(random.uniform(-0.001,0.001), random.uniform(-0.001,0.001))\n\ndef move_explorer_after_mine(spatial, novelty, interior):\n    EXPLORER['stuck_counter'] = 0\n    is_interesting = (spatial>0.15) and (novelty>0.03) and (interior<0.45)\n    if is_interesting: EXPLORER['consecutive_interesting'] += 1\n    else: EXPLORER['consecutive_interesting'] = max(EXPLORER['consecutive_interesting']-1, 0)\n    turn = complex(random.gauss(0,0.40), random.gauss(0,0.40))\n    EXPLORER['drift'] = EXPLORER['drift']*0.85 + turn*0.15\n    if EXPLORER['consecutive_interesting']>=3:\n        speed = EXPLORER['scale']*0.10; EXPLORER['scale'] *= 0.98\n    elif is_interesting:\n        speed = EXPLORER['scale']*0.15; EXPLORER['scale'] *= 0.97\n    else:\n        EXPLORER['scale'] *= 1.05; speed = EXPLORER['scale']*0.40\n    EXPLORER['scale'] = min(max(EXPLORER['scale'], 0.0005), 0.015)\n    mag = abs(EXPLORER['drift'])\n    if mag>0: EXPLORER['drift'] = EXPLORER['drift']\/mag*speed\n    EXPLORER['pos'] += EXPLORER['drift']\n    if EXPLORER['pos'].real < -2.0 or EXPLORER['pos'].real > 0.5: EXPLORER['drift'] = complex(-EXPLORER['drift'].real, EXPLORER['drift'].imag)\n    if EXPLORER['pos'].imag < -1.3 or EXPLORER['pos'].imag > 1.3: EXPLORER['drift'] = complex(EXPLORER['drift'].real, -EXPLORER['drift'].imag)\n\ndef get_dynamic_thresholds():\n    relax = THRESHOLD_RELAX_FACTOR ** EXPLORER['stuck_counter']\n    return (max(MIN_SPATIAL*relax, 0.03), max(MIN_BEAUTY_SCORE*relax, 0.1))\n\n# ============ MAIN MINING ROUTINE ============\nasync def mine_one_coin(difficulty_str='0000', min_novelty=0.2, pub_key='', pub_key_hash=''):\n    for attempt in range(10):\n        for cand_idx in range(NUM_CANDIDATES_PER_COIN):\n            c = EXPLORER['pos'] + complex(random.gauss(0, EXPLORER['scale']*0.3), random.gauss(0, EXPLORER['scale']*0.3))\n            if is_duplicate_location(c.real, c.imag, EXPLORER['scale']): continue\n            if is_too_recent(c.real, c.imag, EXPLORER['scale']): continue\n            if not has_visual_structure(c.real, c.imag, EXPLORER['scale']):\n                EXPLORER['stuck_counter'] += 1\n                walk_explorer()\n                if EXPLORER['stuck_counter'] >= MAX_STUCK_STEPS:\n                    teleport_to_new_region(); EXPLORER['stuck_counter'] = 0\n                continue\n            best = None; best_score = 0.0\n            for scale_mult in [0.8,1.0,1.25]:\n                test_scale = EXPLORER['scale']*scale_mult\n                test_scale = max(min(test_scale, 0.015), 0.0005)\n                if is_duplicate_location(c.real, c.imag, test_scale): continue\n                grid = escape_grid(c.real, c.imag, EVAL_GRID, EVAL_GRID, EVAL_MAX_ITER, test_scale)\n                # Get full 7-dim descriptor\n                desc_full = get_full_descriptor(grid)\n                interior, edge_rich, total_sym, sym, avg_val, spatial, entropy = desc_full\n                # Compute novelty on full descriptor\n                novel, novelty_score = is_novel(desc_full, ARCHIVE, min_novelty)\n                if interior>MAX_INTERIOR or not novel: continue\n                # Compute beauty score (same as before)\n                beauty_score = spatial*0.60 + entropy*0.25 + edge_rich*0.15\n                # Blend novelty and beauty\n                combined_score = 0.5 * novelty_score + 0.5 * beauty_score\n                if combined_score < MIN_COMBINED_SCORE: continue\n                if beauty_score < get_dynamic_thresholds()[1]: continue\n                # Keep best based on combined score\n                if combined_score > best_score:\n                    best_score = combined_score\n                    best = (c, interior, edge_rich, total_sym, sym, avg_val, novelty_score, spatial, test_scale, desc_full)\n            if best is None: continue\n            c, interior, edge_rich, total_sym, sym, avg_val, novelty_score, spatial, best_scale, desc_full = best\n            mine_result = await mine(c.real, c.imag, MINING_TIMEOUT, difficulty_str)\n            if mine_result is None: walk_explorer(); continue\n            nonce, h = mine_result\n            preview_grid = escape_grid(c.real, c.imag, 200, 100, 400, best_scale)\n            preview_ascii = ascii_art_simple(preview_grid)\n            raw_iters = needed_iterations(best_scale, True)\n            muse_iters = min(raw_iters, 1200)   # lower cap to avoid completely interior regions\n            museum_grid = escape_grid(c.real, c.imag, MUSEUM_WIDTH, MUSEUM_HEIGHT, muse_iters, best_scale)\n            museum_ascii = ascii_art_simple(museum_grid)\n\n            # ----- BLANK ART CHECK -----\n            # If museum art is all spaces (uniform grid), skip this candidate and keep mining.\n            if not any(c != ' ' and c != '\\n' for c in museum_ascii):\n                print(\"Blank museum art \u2013 skipping candidate\")\n                walk_explorer()\n                continue\n            # ---------------------------\n\n            coin_data = {\"hash\": h, \"c_real\": c.real, \"c_imag\": c.imag, \"scale\": best_scale, \"nonce\": nonce, \"message\": f\"Coin at {c.real:.5f} + {c.imag:.5f}i\", \"author\": \"BrowserMiner\", \"ascii_art\": museum_ascii, \"novelty\": novelty_score, \"public_key\": pub_key, \"public_key_hash\": pub_key_hash}\n            status, body = await post_coin_to_supabase(coin_data)\n            ARCHIVE.append(desc_full)  # store full 7-dim descriptor\n            MINED_LOCATIONS.append((c.real, c.imag, best_scale))\n            RECENT_MINED.append((c.real, c.imag, time.time()))\n            save_archive()\n            move_explorer_after_mine(spatial, novelty_score, interior)\n            return json.dumps({\"success\": True, \"hash\": h, \"c_real\": c.real, \"c_imag\": c.imag, \"scale\": best_scale, \"novelty\": novelty_score, \"ascii_art\": preview_ascii, \"post_status\": status, \"post_body\": body[:200]})\n        teleport_to_new_region()\n    return json.dumps({\"success\": False, \"message\": \"No coin found after 10 attempts.\"})\n`;\n\nasync function initPyodide() {\n  try {\n    pyodide = await loadPyodide();\n    document.getElementById('pyodide-status').textContent = \"Pyodide ready \u2713\";\n    await pyodide.runPythonAsync(minerPythonCode);\n    if (!keyPair) document.getElementById('mine-btn').disabled = false;\n  } catch (err) {\n    console.error(\"Pyodide init error:\", err);\n    document.getElementById('pyodide-status').textContent = \"Pyodide failed to initialize: \" + err.message;\n  }\n}\n\n\/\/ Sliders\ndocument.getElementById('difficulty-slider').addEventListener('input', e => {\n  document.getElementById('difficulty-label').textContent = e.target.value;\n});\ndocument.getElementById('novelty-slider').addEventListener('input', e => {\n  document.getElementById('novelty-label').textContent = parseFloat(e.target.value).toFixed(2);\n});\n\n\/\/ Mining button\ndocument.getElementById('mine-btn').addEventListener('click', async () => {\n  const outputDiv = document.getElementById('miner-output');\n  outputDiv.innerHTML = '';\n  outputDiv.textContent = \"Mining in progress...\\n\";\n  if (!keyPair) outputDiv.textContent += \"\u26a0\ufe0f No key generated. Coins will have empty public_key.\\n\";\n  try {\n    const mineFn = pyodide.globals.get('mine_one_coin');\n    if (!mineFn) { outputDiv.textContent += \"\u274c miner function not found.\"; return; }\n    const difficultyLevel = parseInt(document.getElementById('difficulty-slider').value);\n    const difficultyStr = '0'.repeat(difficultyLevel);\n    const minNovelty = parseFloat(document.getElementById('novelty-slider').value);\n    const pubKey = publicKeyB64 || \"\";\n    const pubHash = publicKeyHash || \"\";\n    const jsonResult = await mineFn(difficultyStr, minNovelty, pubKey, pubHash);\n    const result = JSON.parse(jsonResult);\n    if (result.success) {\n      outputDiv.textContent += `\ud83c\udf89 Coin mined!\\nHash: ${result.hash}\\nCoord: ${result.c_real} + ${result.c_imag}i\\nNovelty: ${result.novelty}\\nSupabase status: ${result.post_status}\\nResponse: ${result.post_body}\\n\\n`;\n      const pre = document.createElement('pre');\n      pre.className = 'ascii-miner';\n      pre.textContent = result.ascii_art;\n      outputDiv.appendChild(pre);\n      refreshMuseum();\n    } else {\n      outputDiv.textContent += `\u274c Mining failed: ${result.message || result.error || \"Unknown error\"}`;\n    }\n  } catch (err) {\n    console.error(\"Mining error:\", err);\n    outputDiv.textContent += `\u274c Exception: ${err.message}`;\n  }\n});\n\n\/\/ ========== MUSEUM (unchanged) ==========\nfunction escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; }\nasync function loadMuseum() {\n  const status = document.getElementById(\"status\");\n  try {\n    const url = `${SUPABASE_URL}\/rest\/v1\/${TABLE}?select=hash,c_real,c_imag,scale,author,novelty,created_at,public_key_hash`;\n    const resp = await fetch(url, { headers: {\"apikey\": SUPABASE_KEY, \"Authorization\": `Bearer ${SUPABASE_KEY}`} });\n    if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${resp.statusText} - ${await resp.text()}`);\n    coins = await resp.json();\n    status.textContent = `\u2705 Loaded ${coins.length} coins. Click any row to see fractal art and messages.`;\n    renderTable();\n  } catch (err) {\n    status.innerHTML = `<span class=\"error\">\u274c Failed to load: ${escapeHtml(err.message)}<\/span>`;\n  }\n}\nasync function refreshMuseum() { await loadMuseum(); }\nfunction renderTable() {\n  const searchTerm = document.getElementById('search').value.toLowerCase();\n  const walletFilter = document.getElementById('wallet-filter').value.toLowerCase().trim();\n  const filtered = coins.filter(c => {\n    const hashMatch = c.hash.toLowerCase().includes(searchTerm);\n    const coordMatch = `${c.c_real.toFixed(5)} + ${c.c_imag.toFixed(5)}i`.toLowerCase().includes(searchTerm);\n    const walletMatch = !walletFilter || (c.public_key_hash || '').toLowerCase().includes(walletFilter);\n    return (hashMatch || coordMatch) && walletMatch;\n  });\n  const sorted = [...filtered].sort((a,b) => {\n    if (sortBy.key === 'date') {\n      const diff = new Date(b.created_at) - new Date(a.created_at);\n      return sortBy.asc ? -diff : diff;\n    } else if (sortBy.key === 'novelty') {\n      const diff = (b.novelty || 0) - (a.novelty || 0);\n      return sortBy.asc ? -diff : diff;\n    }\n    return 0;\n  });\n  const tbody = document.querySelector(\"#coin-table tbody\");\n  tbody.innerHTML = sorted.map(c => {\n    const noveltyClass = (c.novelty && c.novelty > 0.2) ? 'high-novelty' : '';\n    const dateStr = c.created_at ? new Date(c.created_at).toISOString().slice(0,16).replace('T',' ') : '\u2014';\n    return `<tr data-hash=\"${escapeHtml(c.hash)}\">\n      <td>${escapeHtml(c.hash.slice(0,12))}...<\/td>\n      <td>${escapeHtml(c.c_real.toFixed(5))} + ${escapeHtml(c.c_imag.toFixed(5))}i<\/td>\n      <td>${escapeHtml(c.scale.toFixed(6))}<\/td>\n      <td>${escapeHtml(c.author || '\u2014')}<\/td>\n      <td class=\"${noveltyClass}\">${escapeHtml(c.novelty ? c.novelty.toFixed(4) : '\u2014')}<\/td>\n      <td>${escapeHtml(dateStr)}<\/td>\n      <td class=\"pubkey-hash\" style=\"font-size:0.7em;\">${escapeHtml(c.public_key_hash || '\u2014')}<\/td>\n    <\/tr>`;\n  }).join(\"\");\n}\n\n\/\/ Row click -> load art + messages\ndocument.querySelector(\"#coin-table tbody\").addEventListener(\"click\", async (e) => {\n  const row = e.target.closest(\"tr[data-hash]\");\n  if (!row) return;\n  const hash = row.dataset.hash;\n  const coin = coins.find(c => c.hash === hash);\n  selectedCoin = coin;\n  const msgInput = document.getElementById('msg-input');\n  const postBtn = document.getElementById('post-msg-btn');\n  const postNote = document.getElementById('post-note');\n  if (publicKeyHash && coin.public_key_hash === publicKeyHash) {\n    msgInput.disabled = false; postBtn.disabled = false;\n    postNote.textContent = 'You can post a message (you own this coin).';\n  } else {\n    msgInput.disabled = true; postBtn.disabled = true;\n    postNote.textContent = publicKeyHash ? 'You can only post messages on coins you mined (public key hash mismatch).' : 'Generate a key to post messages on your coins.';\n  }\n  const artDiv = document.getElementById(\"art-display\");\n  try {\n    const artUrl = `${SUPABASE_URL}\/rest\/v1\/${TABLE}?select=ascii_art&hash=eq.${hash}`;\n    const resp = await fetch(artUrl, { headers: {\"apikey\": SUPABASE_KEY, \"Authorization\": `Bearer ${SUPABASE_KEY}`} });\n    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\n    const data = await resp.json();\n    if (data.length > 0 && data[0].ascii_art) {\n      artDiv.textContent = data[0].ascii_art;\n      resetView();\n    } else {\n      artDiv.textContent = \"No fractal art available for this entry.\";\n    }\n  } catch (err) { alert(\"Failed to load fractal art: \" + err.message); }\n\n  const msgSection = document.getElementById('messages-section');\n  const msgList = document.getElementById('messages-list');\n  try {\n    const msgUrl = `${SUPABASE_URL}\/rest\/v1\/${MSG_TABLE}?select=*&coin_hash=eq.${hash}`;\n    const msgResp = await fetch(msgUrl, { headers: {\"apikey\": SUPABASE_KEY, \"Authorization\": `Bearer ${SUPABASE_KEY}`} });\n    if (msgResp.ok) {\n      const messages = await msgResp.json();\n      msgList.innerHTML = messages.length ? messages.map(m => {\n        const date = new Date(m.created_at).toISOString().slice(0,16).replace('T',' ');\n        return `<div class=\"message\">\n          <div><strong>${escapeHtml(m.author || 'Anonymous')}<\/strong> (${date})<\/div>\n          <div>${escapeHtml(m.message)}<\/div>\n          <div class=\"meta\">\n            Signature: <code>${escapeHtml(m.signature.slice(0,20))}\u2026<\/code>\n            <span class=\"verify-btn\" data-msg='${JSON.stringify(m)}'>\ud83d\udd0d Verify<\/span>\n            <span class=\"verify-status\"><\/span>\n          <\/div>\n        <\/div>`;\n      }).join('') : '<p>No messages yet<\/p>';\n      msgSection.style.display = 'block';\n    }\n  } catch (err) { msgList.innerHTML = `<p class=\"error\">Error loading messages: ${escapeHtml(err.message)}<\/p>`; msgSection.style.display = 'block'; }\n});\n\n\/\/ Post message (unchanged)\ndocument.getElementById('post-msg-btn').addEventListener('click', async () => {\n  const msg = document.getElementById('msg-input').value.trim();\n  if (!msg || !selectedCoin) return;\n  if (!publicKeyHash || selectedCoin.public_key_hash !== publicKeyHash) {\n    alert(\"You can only post messages on coins you mined.\"); return;\n  }\n  const payload = { coin_hash: selectedCoin.hash, message: msg, author: \"BrowserMiner\", public_key_hash: publicKeyHash, signature: \"0\".repeat(128) };\n  try {\n    const resp = await fetch(`${SUPABASE_URL}\/rest\/v1\/${MSG_TABLE}`, {\n      method: 'POST',\n      headers: {'apikey': SUPABASE_KEY, 'Authorization': `Bearer ${SUPABASE_KEY}`, 'Content-Type': 'application\/json', 'Prefer': 'return=minimal'},\n      body: JSON.stringify(payload)\n    });\n    if (resp.ok) {\n      document.getElementById('msg-input').value = '';\n      alert('Message posted!');\n      document.querySelector(`tr[data-hash=\"${selectedCoin.hash}\"]`).click();\n    } else alert('Failed to post message.');\n  } catch (err) { alert('Error posting message.'); }\n});\n\n\/\/ ========== STABLE PAN & ZOOM (PROVEN FORMULAS) ==========\nconst viewport = document.getElementById('art-viewport');\nconst artDisplay = document.getElementById('art-display');\nconst zoomSlider = document.getElementById('zoom-slider');\nconst zoomInput = document.getElementById('zoom-input');\nconst zoomValueSpan = document.getElementById('zoom-value');\nconst resetBtn = document.getElementById('reset-zoom');\nconst copyBtn = document.getElementById('copy-art-btn');\n\nfunction applyTransform() {\n  artDisplay.style.transform = `scale(${currentZoom}) translate(${panX}px, ${panY}px)`;\n  zoomValueSpan.textContent = currentZoom.toFixed(1);\n  zoomSlider.value = currentZoom;\n  zoomInput.value = currentZoom.toFixed(1);\n}\n\nfunction setViewportSizeAndCenter() {\n  const artW = artDisplay.offsetWidth;\n  const artH = artDisplay.offsetHeight;\n  const maxWidth = window.innerWidth * 0.95;\n  const maxHeight = window.innerHeight * 0.7;\n  const newWidth = Math.min(artW, maxWidth);\n  const newHeight = Math.min(artH, maxHeight);\n  viewport.style.width = newWidth + 'px';\n  viewport.style.height = newHeight + 'px';\n  panX = (newWidth - artW) \/ 2;\n  panY = (newHeight - artH) \/ 2;\n  applyTransform();\n}\n\nfunction resetView() {\n  currentZoom = 1.0;\n  setViewportSizeAndCenter();\n}\n\n\/\/ Panning\nviewport.addEventListener('mousedown', (e) => {\n  isPanning = true;\n  startMouseX = e.clientX;\n  startMouseY = e.clientY;\n  startPanX = panX;\n  startPanY = panY;\n  viewport.style.cursor = 'grabbing';\n  e.preventDefault();\n});\nwindow.addEventListener('mousemove', (e) => {\n  if (!isPanning) return;\n  const dx = e.clientX - startMouseX;\n  const dy = e.clientY - startMouseY;\n  panX = startPanX + dx \/ currentZoom;\n  panY = startPanY + dy \/ currentZoom;\n  applyTransform();\n});\nwindow.addEventListener('mouseup', () => {\n  if (isPanning) {\n    isPanning = false;\n    viewport.style.cursor = 'grab';\n  }\n});\n\n\/\/ Mouse wheel zoom (exact same as your working example)\nviewport.addEventListener('wheel', (e) => {\n  e.preventDefault();\n  const rect = viewport.getBoundingClientRect();\n  const mouseX = e.clientX - rect.left;\n  const mouseY = e.clientY - rect.top;\n\n  const oldZoom = currentZoom;\n  let newZoom = oldZoom * (e.deltaY < 0 ? 1.1 : 0.9);\n  newZoom = Math.min(5, Math.max(0.1, newZoom));\n\n  const artX = (mouseX \/ oldZoom) - panX;\n  const artY = (mouseY \/ oldZoom) - panY;\n  panX = (mouseX \/ newZoom) - artX;\n  panY = (mouseY \/ newZoom) - artY;\n  currentZoom = newZoom;\n\n  applyTransform();\n}, { passive: false });\n\n\/\/ Slider &#038; input\nzoomSlider.addEventListener('input', () => {\n  currentZoom = parseFloat(zoomSlider.value);\n  applyTransform();\n});\nzoomInput.addEventListener('change', () => {\n  let val = parseFloat(zoomInput.value);\n  if (isNaN(val)) return;\n  val = Math.min(5, Math.max(0.1, val));\n  currentZoom = val;\n  applyTransform();\n});\nresetBtn.addEventListener('click', resetView);\n\n\/\/ Copy ASCII Art button\ncopyBtn.addEventListener('click', () => {\n  const artText = artDisplay.textContent;\n  if (!artText || artText.startsWith('Click a coin')) {\n    alert('No fractal art loaded to copy.');\n    return;\n  }\n  navigator.clipboard.writeText(artText).then(() => {\n    copyBtn.textContent = '\u2705 Copied!';\n    setTimeout(() => { copyBtn.textContent = '\ud83d\udccb Copy ASCII Art'; }, 2000);\n  }).catch(err => {\n    alert('Failed to copy: ' + err);\n  });\n});\n\n\/\/ Sorting (unchanged)\ndocument.getElementById(\"sort-novelty\").addEventListener(\"click\", () => {\n  if (sortBy.key === 'novelty') { sortBy.asc = !sortBy.asc; }\n  else { sortBy.key = 'novelty'; sortBy.asc = false; }\n  updateSortUI(); renderTable();\n});\ndocument.getElementById(\"sort-date\").addEventListener(\"click\", () => {\n  if (sortBy.key === 'date') { sortBy.asc = !sortBy.asc; }\n  else { sortBy.key = 'date'; sortBy.asc = false; }\n  updateSortUI(); renderTable();\n});\nfunction updateSortUI() {\n  document.getElementById('sort-novelty').textContent = (sortBy.key === 'novelty') ? (sortBy.asc ? 'Novelty \u25b4' : 'Novelty \u25be') : 'Novelty';\n  document.getElementById('sort-date').textContent = (sortBy.key === 'date') ? (sortBy.asc ? 'Date \u25b4' : 'Date \u25be') : 'Date';\n}\ndocument.getElementById('search').addEventListener('input', renderTable);\ndocument.getElementById('wallet-filter').addEventListener('input', renderTable);\n\n\/\/ Init\nloadMuseum();\ninitPyodide();\n<\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>XAOS Coin \u2013 Browser Miner + Living Museum \u26cf\ufe0f XAOS Coin Loading Pyodide\u2026 No key generated yet. Difficulty (1\u201310): 4 Novelty threshold: 0.20 Generate \/ Download Private Key Mine One Coin Reset View Zoom: 1.0x \ud83d\udccb Copy ASCII Art \ud83d\uddb1\ufe0f Wheel to zoom \u00b7 Drag to pan Post Message \ud83d\udcdd Messages Loading museum\u2026 Click any [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_canvas","meta":{"iawp_total_views":37,"footnotes":""},"class_list":["post-139332","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/solarbacklight.com\/index.php?rest_route=\/wp\/v2\/pages\/139332","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/solarbacklight.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/solarbacklight.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/solarbacklight.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/solarbacklight.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=139332"}],"version-history":[{"count":0,"href":"https:\/\/solarbacklight.com\/index.php?rest_route=\/wp\/v2\/pages\/139332\/revisions"}],"wp:attachment":[{"href":"https:\/\/solarbacklight.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=139332"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}