Deployed to monsterrobot.games/games/haxtory/ — the botchat box, bind mounted into forum-nginx, same pattern as the other games. card.html is a model card in the AI sense rather than the arcade sense: what the game is, the three ideas it is built on, an itemised list of which parts are historically true (the frequencies, Strowger, the 1-in-7, the 75 cents, SS7, the Linux dates) and which parts are invented (all the people, the board itself, the romance), and then an honest list of what it does not do — it is short for what it is, 75 of 200 philes, no multiplayer, not accessible, unprofiled, and never playtested by a stranger. Linked from the boot screen. Also fixed: the blue box has been sitting in the far corner of the frustum since it was built, where perspective squashed it into a sliver and half of it fell outside the visible cone. I twice assumed occlusion and twice wrote it off as a capture artifact. It was framing — the camera was pointed past the only object in the level that matters. Camera now faces it, and the routing card and Joybubbles moved to balance the shot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
184 lines
10 KiB
HTML
184 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>HANDSHAKE — a love story in 64 hops</title>
|
|
<style>
|
|
:root{
|
|
--ink:#c9f4ff; --dim:#4d6b78; --hot:#ff4a5e; --warm:#ffb648; --green:#63ffa2;
|
|
--mono: ui-monospace, Menlo, "DejaVu Sans Mono", Consolas, monospace;
|
|
}
|
|
*{box-sizing:border-box}
|
|
html,body{margin:0;padding:0;height:100%;overflow:hidden;background:#000;color:var(--ink);
|
|
font-family:var(--mono);-webkit-font-smoothing:antialiased;}
|
|
canvas{display:block;position:fixed;inset:0;width:100%;height:100%;}
|
|
#term{z-index:2;display:none;image-rendering:pixelated;background:#000;}
|
|
.layer{position:fixed;inset:0;z-index:5;pointer-events:none;}
|
|
|
|
/* ── status line ───────────────────────────────────────────── */
|
|
#hud{position:fixed;top:0;left:0;right:0;z-index:6;pointer-events:none;
|
|
display:flex;gap:22px;padding:12px 16px;font-size:12px;letter-spacing:.16em;
|
|
color:var(--dim);text-shadow:0 0 12px #000,0 1px 0 #000;transition:opacity .4s;}
|
|
#ttl{color:var(--ink);min-width:66px}
|
|
#ttl.warn{color:var(--warm)} #ttl.crit{color:var(--hot);animation:pulse 1s steps(2) infinite}
|
|
@keyframes pulse{50%{opacity:.35}}
|
|
#link{color:#7fdcff}
|
|
#extra{margin-left:auto;color:#3d5560}
|
|
|
|
/* ── subtitles ─────────────────────────────────────────────── */
|
|
#sub{position:fixed;left:50%;bottom:8.5vh;transform:translateX(-50%);z-index:7;
|
|
max-width:62ch;text-align:center;font-size:15px;line-height:1.65;color:#e8f9ff;
|
|
text-shadow:0 2px 18px #000,0 0 40px #000;opacity:0;transition:opacity .35s;
|
|
pointer-events:none;letter-spacing:.02em;}
|
|
#sub.on{opacity:1}
|
|
#sub b{display:block;color:#6fe0ff;font-size:11px;letter-spacing:.28em;
|
|
margin-bottom:6px;font-weight:600;text-transform:uppercase;}
|
|
#sub i{color:#8fb6c4;font-style:normal;opacity:.8}
|
|
|
|
/* ── hints ─────────────────────────────────────────────────── */
|
|
#hint{position:fixed;right:16px;bottom:14px;z-index:6;font-size:11px;line-height:1.9;
|
|
color:#3f5d69;text-align:right;letter-spacing:.1em;opacity:0;transition:opacity .4s;
|
|
pointer-events:none;text-shadow:0 1px 0 #000;}
|
|
#hint k{color:#9fe6ff;background:#0d1a20;border:1px solid #1e3a45;border-radius:3px;
|
|
padding:1px 6px;margin-right:6px;font-size:10px;}
|
|
|
|
/* ── title cards ───────────────────────────────────────────── */
|
|
#card{position:fixed;inset:0;z-index:9;display:flex;flex-direction:column;
|
|
align-items:center;justify-content:center;gap:14px;opacity:0;pointer-events:none;
|
|
transition:opacity .7s;background:radial-gradient(ellipse at center,#0000 30%,#000c 100%);}
|
|
#card.on{opacity:1}
|
|
#card-t{font-size:min(9vw,62px);letter-spacing:.34em;color:#eafcff;font-weight:700;
|
|
text-shadow:0 0 60px #1a5c78,0 0 8px #000;margin-left:.34em;}
|
|
#card-s{font-size:12px;letter-spacing:.4em;color:#5b8494;text-transform:uppercase;}
|
|
#card.warm #card-t{color:#ffd9a8;text-shadow:0 0 60px #7a3c0c}
|
|
#card.warm #card-s{color:#9a6b40}
|
|
#card.phos #card-t{color:#63ffa2;text-shadow:0 0 60px #0a6b34;font-size:min(7vw,48px)}
|
|
#card.phos #card-s{color:#2f8f57}
|
|
|
|
/* ── NO CARRIER ────────────────────────────────────────────── */
|
|
#flash{position:fixed;inset:0;z-index:11;display:flex;align-items:center;justify-content:center;
|
|
background:#000;opacity:0;pointer-events:none;transition:opacity .18s;
|
|
font-size:min(8vw,54px);letter-spacing:.3em;color:#ff4a5e;}
|
|
#flash.on{opacity:1}
|
|
#flash.nc{color:#ff4a5e;text-shadow:0 0 40px #5c000c}
|
|
#flash.est{color:#63ffa2;text-shadow:0 0 60px #0a6b34}
|
|
|
|
/* below the title cards, above everything that is actually being played */
|
|
#fade{position:fixed;inset:0;z-index:8;background:#000;opacity:1;pointer-events:none;}
|
|
|
|
/* ── traceroute map ────────────────────────────────────────── */
|
|
#map{position:fixed;inset:0;z-index:12;display:none;align-items:center;justify-content:center;
|
|
background:#000000ee;backdrop-filter:blur(2px);}
|
|
.trace{font-size:13px;line-height:1.9;min-width:min(720px,92vw);}
|
|
.trace .hdr{color:#4d6b78;margin-bottom:14px;letter-spacing:.05em}
|
|
.trace .ftr{color:#3d5560;margin-top:16px;font-size:11px;letter-spacing:.1em}
|
|
.trace .dim{color:#2a3f48}
|
|
.row{display:flex;gap:16px;color:#243740;padding:1px 8px;border-radius:3px;}
|
|
.row.seen{color:#7fa8b8;cursor:pointer}
|
|
.row.seen:hover{background:#0c1a20;color:#c9f4ff}
|
|
.row.here{color:#ffd9a8}
|
|
.row.here .nm::after{content:' ←'}
|
|
.row .n{color:#3d5560;min-width:2.5em}
|
|
.row .nm{min-width:22ch;letter-spacing:.1em}
|
|
.row .via{color:#3d5560;flex:1;font-size:11px}
|
|
.row .ms{color:#2a3f48;font-size:11px}
|
|
|
|
/* ── boot ──────────────────────────────────────────────────── */
|
|
#boot{position:fixed;inset:0;z-index:20;display:flex;flex-direction:column;
|
|
align-items:center;justify-content:center;gap:22px;background:#000;cursor:pointer;}
|
|
#boot h1{font-size:min(12vw,88px);letter-spacing:.32em;margin:0 0 0 .32em;font-weight:700;
|
|
color:#eafcff;text-shadow:0 0 80px #17627f;}
|
|
#boot .tag{color:#4d6b78;letter-spacing:.36em;font-size:11px;text-transform:uppercase}
|
|
#boot .go{color:#63ffa2;letter-spacing:.28em;font-size:12px;margin-top:18px}
|
|
#boot .keys{color:#2a3f48;font-size:11px;line-height:2;text-align:center;letter-spacing:.1em}
|
|
#boot .keys k{color:#5b8494}
|
|
#cd{color:#ff4a5e;font-size:10px;letter-spacing:.3em;transition:opacity .2s}
|
|
#boot .cardlink{color:#3d5560;font-size:11px;letter-spacing:.12em;text-decoration:none;
|
|
border-bottom:1px solid #1a2a32;margin-top:4px}
|
|
#boot .cardlink:hover{color:#9fe6ff;border-color:#2a5566}
|
|
|
|
.row .ms{color:#2a3f48;font-size:11px}
|
|
.cargo{margin-top:14px;font-size:11px;color:#5b8494;letter-spacing:.08em;line-height:2}
|
|
.cargo .bad{color:#ffb648}
|
|
.cargo .drop{color:#7fa8b8;border:1px solid #1e3a45;border-radius:3px;
|
|
padding:2px 7px;cursor:pointer;display:inline-block;margin:2px 3px 0 0}
|
|
.cargo .drop:hover{color:#ff4a5e;border-color:#5c1a22;background:#1a0a0d}
|
|
|
|
/* ── the philes ────────────────────────────────────────────── */
|
|
#reader{position:fixed;inset:0;z-index:12;display:none;align-items:center;justify-content:center;
|
|
background:#000000f2;backdrop-filter:blur(3px);}
|
|
.rd{width:min(1000px,94vw);height:min(76vh,720px);display:flex;flex-direction:column;font-size:13px}
|
|
.rd .hdr{color:#4d6b78;letter-spacing:.2em;margin-bottom:12px;text-transform:uppercase;font-size:11px}
|
|
.rd .ftr{color:#3d5560;margin-top:12px;font-size:11px;letter-spacing:.1em}
|
|
.rd .dim{color:#2a3f48}
|
|
.rd .empty{color:#4d6b78;line-height:2;padding:40px 0}
|
|
.rd .cols{display:flex;gap:26px;flex:1;min-height:0}
|
|
.rd .list{width:28ch;overflow-y:auto;border-right:1px solid #14252d;padding-right:14px;flex:none}
|
|
.rd .it{color:#5b8494;padding:4px 6px;cursor:pointer;border-radius:3px;line-height:1.4;
|
|
font-size:12px;letter-spacing:.02em}
|
|
.rd .it:hover{background:#0c1a20;color:#c9f4ff}
|
|
.rd .it.on{background:#12252d;color:#eafcff}
|
|
.rd .body{flex:1;overflow-y:auto;padding-right:8px}
|
|
.rd .body h2{margin:0 0 2px;font-size:17px;color:#eafcff;letter-spacing:.08em;font-weight:600}
|
|
.rd .body .from{color:#3d5560;font-size:11px;letter-spacing:.14em;margin-bottom:16px}
|
|
.rd .body pre{margin:0;white-space:pre-wrap;color:#a8ccd8;line-height:1.75;
|
|
font-family:var(--mono);font-size:13px}
|
|
|
|
#resume{color:#ffb648;letter-spacing:.14em;font-size:11px;margin-top:6px}
|
|
#resume b{color:#ffe14a}
|
|
|
|
/* ── dark ink, for the levels that are white ───────────────── */
|
|
body.ink-dark #hud{color:#8a9aa6;text-shadow:0 1px 0 #fff}
|
|
body.ink-dark #ttl{color:#14232c}
|
|
body.ink-dark #link{color:#1a6d8e}
|
|
body.ink-dark #extra{color:#9aacb8}
|
|
body.ink-dark #sub{color:#101c24;text-shadow:0 1px 16px #fff,0 0 40px #fff}
|
|
body.ink-dark #sub b{color:#1a6d8e}
|
|
body.ink-dark #sub i{color:#54707e}
|
|
body.ink-dark #hint{color:#8fa1ad;text-shadow:0 1px 0 #fff}
|
|
body.ink-dark #hint k{color:#14232c;background:#e8f0f5;border-color:#c2d2dc}
|
|
|
|
/* ── dev ───────────────────────────────────────────────────── */
|
|
#dev{position:fixed;left:12px;bottom:12px;z-index:13;display:none;max-width:360px}
|
|
#dev button{font-family:var(--mono);font-size:10px;background:#0b1519;color:#7fa8b8;
|
|
border:1px solid #1e3a45;border-radius:3px;padding:3px 7px;margin:2px;cursor:pointer;letter-spacing:.08em}
|
|
#dev button:hover{background:#12252d;color:#c9f4ff}
|
|
#dev .baud{margin-top:6px;color:#3d5560;font-size:10px}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="gl"></canvas>
|
|
<canvas id="term"></canvas>
|
|
|
|
<div id="hud"><span id="ttl">TTL 64</span><span id="hop">hop 01/64</span><span id="link">v.90</span><span id="extra"></span></div>
|
|
<div id="sub"></div>
|
|
<div id="hint"></div>
|
|
<div id="card"><div id="card-t"></div><div id="card-s"></div></div>
|
|
<div id="flash"></div>
|
|
<div id="fade"></div>
|
|
<div id="map"></div>
|
|
<div id="reader"></div>
|
|
<div id="dev"></div>
|
|
|
|
<div id="boot">
|
|
<h1>HANDSHAKE</h1>
|
|
<div class="tag">a love story in 64 hops</div>
|
|
<div class="keys">
|
|
<k>WASD</k> move <k>SPACE</k> jump <k>MOUSE</k> look <k>E</k> use<br>
|
|
<k>M</k> traceroute <k>ESC</k> release mouse <k>`</k> hop select<br><br>
|
|
headphones. every frequency in this is the real frequency.
|
|
</div>
|
|
<div id="resume" style="display:none"></div>
|
|
<div class="go">CLICK OR PRESS ANY KEY TO DIAL</div>
|
|
<a class="cardlink" href="./card.html">what this is & what it doesn't do →</a>
|
|
<div id="cd">▮ NO CARRIER</div>
|
|
</div>
|
|
|
|
<script type="importmap">
|
|
{ "imports": { "three": "./vendor/three.module.js" } }
|
|
</script>
|
|
<script type="module" src="./src/main.js"></script>
|
|
</body>
|
|
</html>
|