Skip to content

Instantly share code, notes, and snippets.

@jmikedupont2
Created June 16, 2026 20:12
Show Gist options
  • Select an option

  • Save jmikedupont2/55fd5e8f6347066d05fafd8146b1b41c to your computer and use it in GitHub Desktop.

Select an option

Save jmikedupont2/55fd5e8f6347066d05fafd8146b1b41c to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>A Tour of the Monster</title>
<style>
:root { --gold:#ffd23f; --paper:#0c0c14; }
* { box-sizing:border-box; }
html,body { margin:0; height:100%; background:var(--paper);
color:#f4f0e6; font-family:'Trebuchet MS',Verdana,sans-serif; overflow:hidden; }
#wrap { position:fixed; inset:0; }
canvas { display:block; width:100%; height:100%; }
#titlecard { position:absolute; top:5%; left:50%; transform:translateX(-50%);
text-align:center; pointer-events:none; text-shadow:0 2px 12px #000; }
#title { font-size:clamp(22px,4vw,52px); font-weight:900; letter-spacing:2px;
margin:0; color:#fff; -webkit-text-stroke:1px #000; }
#theorem { font-family:'Courier New',monospace; color:var(--gold);
font-size:clamp(11px,1.6vw,18px); margin-top:6px; }
#caption { position:absolute; bottom:9%; left:50%; transform:translateX(-50%);
width:min(860px,90%); background:rgba(8,8,16,.72);
border:2px solid rgba(255,210,63,.55); border-radius:14px;
padding:14px 20px; font-size:clamp(13px,1.8vw,19px); line-height:1.4;
text-align:center; backdrop-filter:blur(3px); pointer-events:none; }
#hud { position:absolute; bottom:2.4%; left:50%; transform:translateX(-50%);
display:flex; gap:10px; align-items:center; z-index:5; }
button { background:#1a1a2e; color:#f4f0e6; border:1px solid #444;
border-radius:9px; padding:8px 14px; font-size:15px; cursor:pointer; }
button:hover { background:#2a2a44; }
#dots { display:flex; gap:7px; }
.dot { width:11px; height:11px; border-radius:50%; background:#444; cursor:pointer;
transition:all .25s; }
.dot.on { background:var(--gold); transform:scale(1.35); }
#brand { position:absolute; top:12px; left:16px; font-size:12px; color:#7a7a90;
letter-spacing:1px; }
#hint { position:absolute; top:12px; right:16px; font-size:12px; color:#7a7a90; }
</style>
</head>
<body>
<div id="wrap">
<canvas id="cv"></canvas>
<div id="brand">OEIS A001379 - character degrees of the Monster M</div>
<div id="hint">drag to look around - space = play/pause</div>
<div id="titlecard"><h1 id="title"></h1><div id="theorem"></div></div>
<div id="caption"></div>
<div id="hud">
<button id="prev">&#9664; prev</button>
<button id="play">&#10073;&#10073; pause</button>
<button id="next">next &#9654;</button>
<div id="dots"></div>
</div>
</div>
<script>
const DATA = {"M": [[46, 2, 0, 0, 2, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1], [42, 2, 1, 4, 0, 2, 0, 0, 1, 1, 0, 1, 0, 1, 0], [44, 0, 0, 6, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1], [46, 0, 0, 0, 2, 3, 0, 0, 1, 0, 1, 0, 1, 0, 0], [46, 0, 0, 0, 2, 3, 0, 0, 1, 0, 1, 0, 1, 0, 0], [42, 0, 7, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1], [43, 0, 0, 0, 2, 2, 0, 1, 1, 1, 0, 1, 0, 1, 0], [43, 0, 0, 0, 2, 2, 0, 1, 1, 1, 0, 1, 0, 1, 0], [43, 0, 0, 0, 2, 2, 0, 1, 1, 1, 0, 1, 0, 1, 0], [42, 0, 0, 4, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 0], [42, 0, 0, 0, 0, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1], [32, 0, 9, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1], [18, 19, 0, 0, 0, 3, 0, 0, 0, 1, 1, 1, 0, 1, 1], [32, 0, 1, 0, 0, 3, 1, 1, 1, 1, 1, 0, 1, 1, 1], [31, 1, 0, 3, 2, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0], [31, 1, 0, 3, 2, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0], [28, 1, 0, 1, 1, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1], [18, 3, 8, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1], [18, 0, 8, 5, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1], [21, 0, 0, 6, 1, 3, 1, 1, 0, 1, 0, 1, 0, 1, 1], [20, 0, 2, 5, 0, 3, 1, 0, 1, 1, 1, 1, 0, 1, 1], [18, 3, 2, 1, 2, 3, 0, 1, 1, 1, 0, 1, 1, 1, 1], [20, 2, 0, 0, 2, 3, 1, 0, 1, 1, 1, 1, 1, 1, 1], [18, 0, 0, 6, 1, 3, 1, 1, 1, 1, 1, 1, 1, 0, 0], [18, 0, 0, 6, 1, 3, 1, 1, 1, 1, 1, 1, 1, 0, 0], [18, 0, 0, 6, 1, 3, 1, 1, 0, 1, 0, 1, 1, 1, 1], [0, 17, 7, 4, 2, 2, 0, 0, 0, 0, 1, 0, 0, 1, 1], [12, 6, 2, 5, 0, 3, 1, 1, 1, 0, 1, 0, 1, 1, 1], [0, 20, 0, 6, 2, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1], [2, 19, 0, 4, 0, 3, 0, 0, 0, 1, 1, 1, 1, 1, 1], [6, 17, 0, 4, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1], [1, 19, 0, 4, 2, 3, 1, 0, 1, 1, 0, 1, 0, 0, 0], [1, 19, 0, 4, 2, 3, 1, 0, 1, 1, 0, 1, 0, 0, 0], [18, 0, 0, 6, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 19, 5, 1, 2, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1], [16, 1, 0, 4, 2, 2, 1, 1, 0, 1, 1, 1, 1, 1, 1], [1, 19, 1, 1, 2, 3, 0, 1, 0, 1, 1, 1, 1, 1, 0], [12, 1, 9, 1, 0, 2, 1, 1, 0, 1, 1, 1, 1, 1, 1], [1, 19, 0, 4, 2, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1], [2, 19, 0, 0, 2, 3, 1, 0, 1, 1, 1, 1, 0, 1, 1], [0, 17, 7, 0, 1, 2, 0, 0, 0, 1, 1, 1, 1, 1, 1], [0, 17, 7, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1], [18, 0, 0, 6, 1, 3, 1, 1, 0, 1, 0, 1, 0, 0, 0], [18, 0, 0, 6, 1, 3, 1, 1, 0, 1, 0, 1, 0, 0, 0], [18, 1, 2, 5, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1], [17, 0, 5, 0, 2, 3, 1, 1, 0, 0, 1, 0, 0, 1, 1], [17, 0, 5, 0, 2, 3, 1, 1, 0, 0, 1, 0, 0, 1, 1], [0, 18, 9, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1], [0, 18, 9, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1], [1, 17, 2, 5, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1], [1, 17, 1, 5, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1], [0, 19, 0, 1, 2, 3, 1, 1, 1, 1, 0, 1, 1, 0, 1], [0, 18, 0, 5, 0, 2, 1, 1, 0, 0, 1, 1, 1, 1, 1], [19, 1, 1, 1, 2, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1], [16, 3, 2, 0, 1, 2, 1, 0, 0, 1, 1, 1, 1, 1, 1], [13, 0, 2, 5, 2, 3, 0, 0, 1, 1, 0, 1, 1, 1, 1], [0, 19, 0, 0, 2, 3, 0, 1, 0, 1, 1, 1, 1, 1, 1], [0, 18, 0, 1, 2, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1], [3, 17, 0, 1, 1, 2, 1, 0, 1, 1, 1, 0, 0, 1, 1], [0, 19, 0, 0, 2, 3, 0, 1, 0, 1, 1, 1, 1, 1, 0], [0, 19, 0, 0, 2, 3, 1, 0, 1, 1, 0, 0, 1, 1, 1], [0, 19, 0, 0, 2, 3, 1, 0, 1, 1, 0, 0, 1, 1, 1], [9, 1, 8, 1, 1, 3, 0, 1, 1, 1, 1, 0, 1, 1, 1], [0, 17, 1, 0, 2, 2, 1, 1, 1, 0, 1, 1, 1, 1, 1], [3, 6, 7, 5, 1, 2, 0, 1, 0, 1, 1, 1, 0, 1, 1], [4, 7, 7, 1, 1, 3, 0, 1, 1, 0, 1, 1, 1, 1, 1], [0, 12, 5, 3, 2, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1], [0, 12, 7, 0, 0, 3, 1, 0, 1, 1, 1, 1, 1, 1, 1], [18, 0, 0, 0, 1, 3, 1, 1, 1, 0, 0, 1, 1, 1, 1], [4, 12, 0, 1, 2, 3, 0, 1, 0, 1, 1, 1, 1, 1, 1], [7, 9, 0, 0, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1], [10, 1, 0, 5, 2, 3, 0, 1, 1, 1, 1, 1, 1, 1, 1], [6, 0, 9, 4, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], [0, 12, 0, 6, 0, 3, 1, 1, 1, 1, 1, 1, 1, 0, 1], [1, 6, 8, 6, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], [0, 17, 2, 0, 0, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1], [10, 1, 2, 5, 0, 3, 0, 1, 0, 1, 1, 1, 1, 1, 1], [11, 2, 0, 3, 1, 3, 1, 0, 1, 1, 1, 1, 1, 1, 1], [0, 13, 0, 6, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1], [0, 13, 0, 6, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1], [3, 6, 8, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1], [2, 2, 9, 4, 2, 3, 0, 1, 1, 1, 1, 1, 0, 0, 1], [0, 12, 2, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1], [7, 2, 1, 4, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 19, 0, 0, 2, 3, 1, 0, 1, 1, 0, 0, 0, 0, 0], [0, 19, 0, 0, 2, 3, 1, 0, 1, 1, 0, 0, 0, 0, 0], [3, 1, 7, 5, 2, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1], [2, 0, 9, 6, 0, 3, 1, 1, 0, 0, 1, 1, 1, 1, 1], [1, 12, 0, 6, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0], [1, 12, 0, 6, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0], [12, 1, 0, 1, 2, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1], [1, 12, 2, 1, 0, 3, 1, 0, 1, 1, 1, 1, 0, 1, 1], [0, 9, 7, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1], [3, 6, 0, 6, 1, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1], [0, 2, 9, 6, 2, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1], [1, 0, 9, 6, 2, 2, 1, 1, 0, 1, 1, 0, 1, 1, 0], [1, 0, 9, 6, 2, 2, 1, 1, 0, 1, 1, 0, 1, 1, 0], [3, 0, 8, 6, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1], [0, 2, 9, 6, 1, 2, 1, 1, 0, 0, 0, 1, 1, 1, 1], [2, 4, 1, 6, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 0], [2, 2, 9, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [2, 0, 7, 5, 2, 3, 1, 0, 0, 1, 1, 1, 1, 1, 1], [0, 0, 9, 6, 2, 2, 1, 1, 1, 1, 0, 1, 0, 1, 1], [1, 0, 8, 6, 1, 3, 1, 0, 1, 1, 0, 1, 1, 1, 1], [1, 2, 9, 1, 2, 3, 0, 1, 1, 1, 1, 1, 1, 1, 1], [5, 7, 1, 1, 1, 3, 1, 1, 0, 1, 1, 1, 1, 0, 1], [6, 3, 0, 6, 1, 3, 1, 1, 0, 0, 0, 1, 1, 1, 1], [4, 3, 7, 0, 2, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1], [1, 1, 9, 6, 2, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1], [1, 1, 9, 6, 2, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1], [2, 0, 9, 6, 2, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1], [2, 0, 9, 6, 2, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1], [1, 3, 8, 4, 0, 3, 0, 0, 1, 1, 1, 1, 1, 1, 0], [6, 1, 2, 5, 1, 2, 1, 1, 0, 1, 1, 1, 1, 1, 1], [2, 0, 9, 4, 1, 3, 0, 1, 1, 0, 0, 1, 1, 1, 1], [2, 0, 7, 4, 2, 3, 0, 1, 0, 1, 1, 1, 1, 1, 1], [0, 0, 9, 3, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 0], [5, 0, 8, 4, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0], [2, 3, 7, 4, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1], [0, 6, 3, 2, 2, 3, 1, 1, 1, 1, 1, 1, 0, 1, 1], [0, 3, 9, 0, 1, 3, 1, 1, 1, 1, 0, 1, 1, 1, 1], [1, 2, 3, 4, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 1, 9, 0, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 4, 0, 6, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 4, 0, 6, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1], [3, 0, 7, 5, 0, 2, 0, 0, 1, 1, 1, 0, 1, 1, 1], [3, 2, 0, 6, 2, 3, 1, 0, 1, 0, 1, 1, 1, 1, 1], [1, 0, 8, 6, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1], [1, 0, 8, 6, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1], [1, 1, 8, 4, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], [3, 1, 1, 6, 2, 2, 1, 1, 1, 1, 1, 1, 0, 1, 1], [0, 3, 7, 1, 2, 3, 0, 0, 1, 1, 1, 1, 1, 1, 1], [3, 2, 0, 4, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1], [2, 1, 2, 6, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1], [2, 0, 2, 6, 2, 3, 1, 0, 1, 1, 1, 1, 1, 1, 1], [1, 0, 5, 4, 2, 3, 0, 1, 1, 1, 1, 1, 1, 1, 1], [2, 0, 1, 6, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1], [12, 0, 0, 4, 1, 2, 0, 0, 0, 0, 1, 0, 0, 1, 1], [1, 3, 8, 4, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1], [0, 0, 9, 6, 2, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1], [0, 0, 9, 6, 2, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1], [2, 0, 9, 0, 1, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1], [0, 0, 9, 1, 2, 3, 1, 1, 1, 1, 1, 1, 0, 0, 1], [0, 0, 9, 1, 2, 3, 1, 1, 1, 1, 1, 1, 0, 0, 1], [3, 0, 3, 5, 0, 3, 1, 1, 0, 1, 1, 1, 1, 1, 1], [2, 1, 2, 5, 2, 3, 0, 1, 1, 0, 1, 1, 1, 1, 1], [3, 0, 0, 6, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1], [3, 0, 8, 1, 0, 3, 1, 1, 0, 1, 0, 1, 0, 1, 1], [0, 1, 9, 0, 2, 3, 0, 1, 1, 1, 1, 1, 0, 1, 0], [0, 1, 9, 0, 2, 3, 0, 1, 1, 1, 1, 1, 0, 1, 0], [0, 6, 0, 5, 0, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1], [3, 0, 2, 3, 2, 3, 1, 1, 1, 1, 0, 1, 1, 1, 1], [0, 3, 1, 6, 0, 3, 1, 1, 1, 1, 0, 1, 1, 1, 1], [0, 0, 5, 6, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 6, 7, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1], [7, 0, 2, 0, 1, 3, 0, 1, 1, 0, 1, 1, 1, 1, 1], [2, 2, 0, 6, 2, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1], [2, 2, 0, 5, 2, 2, 0, 1, 1, 0, 1, 1, 1, 1, 1], [0, 0, 9, 6, 2, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 9, 6, 2, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0], [0, 3, 7, 0, 1, 2, 1, 0, 1, 1, 0, 0, 1, 1, 1], [0, 1, 7, 5, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1], [2, 0, 0, 6, 2, 3, 1, 0, 1, 0, 1, 1, 1, 1, 0], [2, 0, 0, 6, 2, 3, 1, 0, 1, 0, 1, 1, 1, 1, 0], [0, 0, 7, 1, 1, 2, 1, 1, 0, 1, 1, 1, 1, 1, 1], [3, 2, 0, 4, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1], [4, 0, 2, 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 0, 2, 4, 1, 3, 0, 1, 1, 1, 1, 1, 1, 0, 1], [1, 0, 0, 5, 2, 3, 0, 1, 1, 1, 1, 0, 1, 1, 1], [1, 0, 0, 5, 2, 3, 0, 1, 1, 1, 1, 0, 1, 1, 1], [2, 0, 7, 0, 0, 2, 0, 1, 0, 0, 1, 1, 1, 1, 1], [1, 1, 2, 5, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1], [0, 0, 1, 5, 0, 3, 0, 0, 1, 1, 1, 1, 1, 1, 1], [0, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 2, 1, 0, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 2, 5, 1, 2, 1, 0, 1, 0, 1, 0, 0, 1, 1], [1, 0, 2, 1, 1, 3, 1, 1, 0, 1, 1, 1, 1, 0, 1], [0, 0, 3, 1, 2, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1], [0, 6, 0, 1, 0, 2, 1, 1, 0, 0, 1, 0, 0, 1, 1], [3, 0, 0, 1, 0, 3, 1, 0, 0, 1, 1, 1, 1, 1, 1], [0, 0, 0, 5, 0, 2, 0, 1, 1, 1, 0, 1, 1, 1, 1], [0, 0, 2, 4, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1], [0, 3, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1], [0, 0, 0, 4, 1, 2, 0, 0, 0, 1, 0, 1, 1, 1, 1], [1, 0, 2, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 0, 1, 1, 2, 0, 1, 1, 0, 0, 1, 1, 1, 0], [0, 1, 0, 0, 1, 2, 1, 0, 1, 1, 1, 1, 1, 1, 0], [2, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1], [1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1], [1, 0, 0, 0, 0, 2, 0, 0, 0, 1, 1, 0, 1, 1, 0], [0, 0, 0, 0, 0, 2, 0, 0, 1, 1, 0, 1, 0, 1, 1], [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], "IDX": [192, 174, 180, 101, 102, 139, 122, 123, 124, 132, 171, 147, 168, 158, 80, 81, 144, 96, 118, 136, 157, 145, 125, 134, 135, 156, 160, 172, 175, 186, 77, 104, 105, 111, 121, 141, 151, 161, 162, 163, 177, 188, 40, 41, 45, 58, 59, 70, 71, 130, 140, 159, 167, 49, 63, 120, 152, 185, 65, 100, 106, 107, 129, 142, 150, 154, 181, 193, 42, 112, 115, 153, 169, 173, 191, 76, 95, 97, 98, 99, 119, 155, 170, 187, 25, 26, 166, 182, 43, 44, 60, 67, 91, 117, 126, 127, 128, 133, 137, 148, 149, 183, 184, 189, 190, 56, 64, 78, 82, 83, 84, 85, 90, 94, 110, 143, 176, 48, 66, 109, 113, 164, 165, 178, 179, 62, 87, 88, 89, 92, 93, 103, 114, 116, 131, 138, 146, 14, 36, 54, 55, 69, 73, 74, 75, 79, 108, 35, 52, 53, 61, 68, 72, 86, 27, 28, 50, 51, 15, 16, 29, 30, 38, 39, 57, 23, 33, 37, 46, 47, 20, 19, 31, 32, 34, 18, 22, 24, 8, 17, 21, 9, 10, 12, 13, 7, 11, 4, 6, 3, 5, 2, 1, 0], "R": 194, "C": 15, "PRIMES": [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 41, 47, 59, 71], "LANDMARKS": [{"id": "overview", "title": "THE MONSTER", "theorem": "Monster.lean - Sigma d_i^2 = |M|", "text": "194 irreducible representations of the Monster sporadic group, each a skyline of supersingular primes. We enter from -z.", "box": {"r0": 0, "c0": 0, "h": 194, "w": 15}, "view": "wide", "color": "#9b6cff"}, {"id": "ridge", "title": "MOUNT |M|", "theorem": "Monster.lean - prime_dvd_orderM_iff", "text": "The v2 ridge (column 2) towers to exponent 46 - the power of 2 in |M| = 2^46 . 3^20 . 5^9 . 7^6 . 11^2 . 13^3 . 17 . 19 . 23 ...", "box": {"r0": 0, "c0": 0, "h": 12, "w": 1}, "view": "left", "color": "#ff5d5d"}, {"id": "faithful", "title": "196883", "theorem": "Monster.lean - smallest_faithful", "text": "The smallest faithful representation: degree 47 . 59 . 71 = 196883, the q^1 coefficient of j minus 1 in Monstrous Moonshine.", "box": {"r0": 192, "c0": 11, "h": 1, "w": 4}, "view": "close", "color": "#ffd23f"}, {"id": "square", "title": "KA-BLOCK! square (side 6)", "theorem": "Shapes.lean - largest_solid_square", "text": "The biggest fully-filled square in the grid has side exactly 6, at row 119, col 4 (primes 11..23 all divide these degrees).", "box": {"r0": 119, "c0": 4, "h": 6, "w": 6}, "view": "iso", "color": "#e8413a"}, {"id": "tower", "title": "SKY-HIGH TOWER (24 x 2)", "theorem": "Shapes.lean - solid_rect_24x2 / no_solid_rect_25x2", "text": "A solid 24 x 2 wall over primes 59 & 71 - and 24 is the maximum height at width 2. The last two columns rarely vanish.", "box": {"r0": 118, "c0": 13, "h": 24, "w": 2}, "view": "right", "color": "#2f7bd6"}, {"id": "stair", "title": "THE STAIRCASE (height 7)", "theorem": "Shapes.lean - largest_triangle", "text": "The tallest filled right-triangle staircase has height exactly 7, climbing from row 118, col 4.", "box": {"r0": 118, "c0": 4, "h": 7, "w": 7}, "view": "iso", "color": "#28a55a"}, {"id": "diamond", "title": "THE DIAMOND (radius 3)", "theorem": "Shapes.lean - largest_circle", "text": "The largest discrete circle (an L1 ball / diamond - a true circle needs more than 15 columns) has radius 3, centred at row 71, col 11.", "box": {"r0": 68, "c0": 8, "h": 7, "w": 7}, "view": "close", "color": "#f2a324"}, {"id": "penteract", "title": "THE 5-CUBE SHADOW", "theorem": "Shadows.lean - cube5_shadow / no_cube6_shadow", "text": "Over primes {2,3,5,23,47} all 2^5 = 32 divisibility patterns occur: the matrix is the shadow of a penteract. 5 is the maximum.", "box": {"r0": 0, "c0": 0, "h": 194, "w": 15}, "view": "top", "color": "#37d6c4"}, {"id": "apex", "title": "THE APEX / VOID", "theorem": "GeometricPatterns.lean - apex_row_unique", "text": "Row 193 is the trivial representation, dimension 1: the unique all-zero row - flat ground at the far end of the Monster.", "box": {"r0": 193, "c0": 0, "h": 1, "w": 15}, "view": "close", "color": "#c9c9d6"}, {"id": "finale", "title": "THE WHOLE MONSTER", "theorem": "Monster.lean - each_degree_dvd_order", "text": "Every degree divides |M|, so only the 15 supersingular primes ever appear. One matrix, certified end to end by Lean.", "box": {"r0": 0, "c0": 0, "h": 194, "w": 15}, "view": "hero", "color": "#9b6cff"}]};
const {M, IDX, R, C, PRIMES, LANDMARKS} = DATA;
// ---- colours per prime column (cool->warm rainbow) -----------------------
const COLS = ["#ff5d5d","#ff924c","#ffd23f","#a8e05f","#5fe08a","#3fd6c4",
"#37b6ff","#5d8bff","#9b6cff","#c96cff","#ff6cd0","#ff6c97",
"#ff8c5d","#ffd86c","#d6ff6c"];
function shade(hex, f){ // f<1 darken, f>1 lighten
const n=parseInt(hex.slice(1),16); let r=(n>>16)&255,g=(n>>8)&255,b=n&255;
r=Math.min(255,r*f)|0; g=Math.min(255,g*f)|0; b=Math.min(255,b*f)|0;
return "rgb("+r+","+g+","+b+")";
}
// ---- world geometry ------------------------------------------------------
// grid cell (row i, col j): x = (j - C/2)*SX, z = i*SZ, height = M[i][j]*HY
const SX=2.2, SZ=1.0, HY=0.9;
const cx = c => (c - C/2 + 0.5)*SX;
const cz = r => r*SZ;
// focus point (world) of a landmark box centre
function boxCenter(b){
return { x: cx(b.c0 + b.w/2 - 0.5), y: 4, z: cz(b.r0 + b.h/2) };
}
// camera keyframe for a landmark
function camFor(L){
const ctr = boxCenter(L.box);
const span = Math.max(L.box.h*SZ, L.box.w*SX, 6);
const v = L.view;
let dx=0, dy=span*0.7+18, dz=-span*0.9-22; // default: behind (-z) & above
if(v==="wide"){ dx=0; dy=70; dz=-150; }
else if(v==="hero"){ dx=60; dy=95; dz=-150; }
else if(v==="top"){ dx=2; dy=170; dz=-30; }
else if(v==="left"){ dx=-55; dy=34; dz=-46; }
else if(v==="right"){ dx=55; dy=40; dz=-40; }
else if(v==="iso"){ dx=34; dy=30; dz=-34; }
else if(v==="close"){ dx=10; dy=14; dz=-26; }
return { eye:{x:ctr.x+dx,y:ctr.y+dy,z:ctr.z+dz}, tgt:ctr };
}
const KEYS = LANDMARKS.map(camFor);
// ---- precompute towers (one box per nonzero cell) ------------------------
const TOWERS=[];
for(let i=0;i<R;i++) for(let j=0;j<C;j++){
const h=M[i][j]; if(h<=0) continue;
TOWERS.push({i,j,h:h*HY, x:cx(j), z:cz(i), col:COLS[j%COLS.length]});
}
// ---- camera + projection -------------------------------------------------
const cv=document.getElementById("cv"), ctx=cv.getContext("2d");
let W=0,H=0,DPR=Math.min(2,window.devicePixelRatio||1);
function resize(){ W=cv.clientWidth; H=cv.clientHeight;
cv.width=W*DPR; cv.height=H*DPR; ctx.setTransform(DPR,0,0,DPR,0,0); }
window.addEventListener("resize",resize);
let eye={...KEYS[0].eye}, tgt={...KEYS[0].tgt};
let yawOff=0, pitchOff=0; // user drag look-around
function project(p){
// build camera basis from eye->tgt with user yaw/pitch offsets
let fx=tgt.x-eye.x, fy=tgt.y-eye.y, fz=tgt.z-eye.z;
let fl=Math.hypot(fx,fy,fz)||1; fx/=fl; fy/=fl; fz/=fl;
// apply yaw (around world up) and pitch offsets
const cy=Math.cos(yawOff), sy=Math.sin(yawOff);
let gx=fx*cy - fz*sy, gz=fx*sy + fz*cy, gy=fy;
// right = forward x up
let rx=gz, ry=0, rz=-gx; let rl=Math.hypot(rx,ry,rz)||1; rx/=rl; rz/=rl;
// pitch: tilt forward up/down
gy += pitchOff; let nl=Math.hypot(gx,gy,gz)||1; gx/=nl; gy/=nl; gz/=nl;
// up = right x forward
let ux=ry*gz-rz*gy, uy=rz*gx-rx*gz, uz=rx*gy-ry*gx;
const dx=p.x-eye.x, dy=p.y-eye.y, dz=p.z-eye.z;
const X=dx*rx+dy*ry+dz*rz;
const Y=dx*ux+dy*uy+dz*uz;
const Z=dx*gx+dy*gy+dz*gz;
if(Z<=0.1) return null;
const f=H*0.9;
return { x:W/2 + X/Z*f, y:H/2 - Y/Z*f, z:Z };
}
function quad(a,b,c,d,fill){
const A=project(a),B=project(b),Cc=project(c),D=project(d);
if(!A||!B||!Cc||!D) return;
ctx.beginPath(); ctx.moveTo(A.x,A.y); ctx.lineTo(B.x,B.y);
ctx.lineTo(Cc.x,Cc.y); ctx.lineTo(D.x,D.y); ctx.closePath();
ctx.fillStyle=fill; ctx.fill();
}
// ---- render --------------------------------------------------------------
let curIdx=0, glow=0;
function activeBox(){ return LANDMARKS[curIdx].box; }
function inBox(i,j,b){ return i>=b.r0&&i<b.r0+b.h&&j>=b.c0&&j<b.c0+b.w; }
function frame(t){
ctx.fillStyle="#0c0c14"; ctx.fillRect(0,0,W,H);
// floor grid glow
const g=ctx.createRadialGradient(W/2,H*0.62,40,W/2,H*0.62,Math.max(W,H));
g.addColorStop(0,"rgba(40,30,70,.55)"); g.addColorStop(1,"rgba(8,8,18,0)");
ctx.fillStyle=g; ctx.fillRect(0,0,W,H);
// depth sort towers (far first)
const box=activeBox(); const hi=LANDMARKS[curIdx].color;
const drawn=[];
for(const T of TOWERS){
const pc=project({x:T.x,y:0,z:T.z});
if(!pc) continue;
drawn.push([pc.z,T]);
}
drawn.sort((a,b)=>b[0]-a[0]);
const gl=0.5+0.5*Math.sin(glow);
for(const [,T] of drawn){
const i=T.i,j=T.j,x=T.x,z=T.z,h=T.h,w=SX*0.42,d=SZ*0.42;
const x0=x-w,x1=x+w,z0=z-d,z1=z+d;
const lit = inBox(i,j,box);
let top=T.col, side=shade(T.col,0.7), front=shade(T.col,0.5);
if(lit){ top=shade(hi,1.25); side=shade(hi,0.85); front=shade(hi,0.6); }
// back/left/right faces then top, then front (painter within tower)
quad({x:x0,y:0,z:z1},{x:x1,y:0,z:z1},{x:x1,y:h,z:z1},{x:x0,y:h,z:z1}, side); // back
quad({x:x0,y:0,z:z0},{x:x0,y:0,z:z1},{x:x0,y:h,z:z1},{x:x0,y:h,z:z0}, front);// left
quad({x:x1,y:0,z:z0},{x:x1,y:0,z:z1},{x:x1,y:h,z:z1},{x:x1,y:h,z:z0}, front);// right
quad({x:x0,y:h,z:z0},{x:x1,y:h,z:z0},{x:x1,y:h,z:z1},{x:x0,y:h,z:z1}, top); // top
quad({x:x0,y:0,z:z0},{x:x1,y:0,z:z0},{x:x1,y:h,z:z0},{x:x0,y:h,z:z0},
lit?shade(hi,1.5):shade(T.col,0.85)); // front face
if(lit){
ctx.save(); ctx.globalAlpha=0.25+0.45*gl;
const top2=project({x:x,y:h+1.2,z:z});
if(top2){ ctx.fillStyle=hi; ctx.beginPath();
ctx.arc(top2.x,top2.y,3.2,0,7); ctx.fill(); }
ctx.restore();
}
}
}
// ---- tour state machine --------------------------------------------------
let playing=true, segT=0;
const LINGER=2.6, TRAVEL=2.8; // seconds
let phase="travel"; // travel -> linger
let fromK=KEYS[0], toK=KEYS[0];
function setLandmark(k,instant){
curIdx=(k+LANDMARKS.length)%LANDMARKS.length;
fromK={eye:{...eye},tgt:{...tgt}}; toK=KEYS[curIdx];
segT=0; phase=instant?"linger":"travel";
if(instant){ eye={...toK.eye}; tgt={...toK.tgt}; }
updateHUD();
}
function ease(x){ return x<.5 ? 4*x*x*x : 1-Math.pow(-2*x+2,3)/2; }
function lerp(a,b,t){ return a+(b-a)*t; }
function lerpPt(a,b,t){ return {x:lerp(a.x,b.x,t),y:lerp(a.y,b.y,t),z:lerp(a.z,b.z,t)}; }
function updateHUD(){
const L=LANDMARKS[curIdx];
document.getElementById("title").textContent=L.title;
document.getElementById("theorem").textContent=L.theorem;
document.getElementById("caption").textContent=L.text;
document.querySelectorAll(".dot").forEach((d,i)=>d.classList.toggle("on",i===curIdx));
}
let last=performance.now();
function loop(now){
const dt=Math.min(0.05,(now-last)/1000); last=now; glow+=dt*3.2;
if(playing){
segT+=dt;
if(phase==="travel"){
const t=ease(Math.min(1,segT/TRAVEL));
eye=lerpPt(fromK.eye,toK.eye,t); tgt=lerpPt(fromK.tgt,toK.tgt,t);
yawOff*=0.92; pitchOff*=0.92;
if(segT>=TRAVEL){ phase="linger"; segT=0; }
} else {
// slow orbit drift while lingering
yawOff=0.18*Math.sin(segT*0.6);
if(segT>=LINGER){ setLandmark(curIdx+1,false); }
}
}
frame(now);
requestAnimationFrame(loop);
}
// ---- controls ------------------------------------------------------------
const dots=document.getElementById("dots");
LANDMARKS.forEach((L,i)=>{ const d=document.createElement("div");
d.className="dot"+(i===0?" on":""); d.title=L.title;
d.onclick=()=>{ setLandmark(i,false); playing=true; pbtn.innerHTML="&#10073;&#10073; pause"; };
dots.appendChild(d); });
const pbtn=document.getElementById("play");
pbtn.onclick=()=>{ playing=!playing;
pbtn.innerHTML=playing?"&#10073;&#10073; pause":"&#9654; play"; };
document.getElementById("next").onclick=()=>{ setLandmark(curIdx+1,false); };
document.getElementById("prev").onclick=()=>{ setLandmark(curIdx-1,false); };
window.addEventListener("keydown",e=>{
if(e.code==="Space"){ e.preventDefault(); pbtn.onclick(); }
if(e.code==="ArrowRight") setLandmark(curIdx+1,false);
if(e.code==="ArrowLeft") setLandmark(curIdx-1,false); });
// drag to look around
let drag=false, lx=0, ly=0;
cv.addEventListener("pointerdown",e=>{ drag=true; lx=e.clientX; ly=e.clientY; });
window.addEventListener("pointerup",()=>drag=false);
window.addEventListener("pointermove",e=>{ if(!drag) return;
yawOff += (e.clientX-lx)*0.004; pitchOff += (e.clientY-ly)*0.003;
pitchOff=Math.max(-0.8,Math.min(0.8,pitchOff)); lx=e.clientX; ly=e.clientY; });
resize(); setLandmark(0,true); requestAnimationFrame(loop);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment