haxtory/index.html
type-two 8030547e3c Philes you can read, a save, and MTU that costs you something
Three holes closed:

  philes  — 40 written, all in src/philes.js so the next 160 is a writing
            job and not a code job. P opens a reader: list on the left,
            the text on the right, attributed to whoever wrote it in
            whatever year they wrote it. Every level now drops at least
            one; previously you collected files you could never open.

  save    — ESTABLISHED. Every hop writes level/ttl/hop/baud/philes/
            cargo/flags to localStorage, and the boot screen offers
            R — RESUME. A 64-hop game with no save is not a 64-hop game.

  MTU     — the HUD has been showing your MTU drop as you take files off
            the board while nothing happened. Now two files are free and
            after that you arrive in pieces, one TTL per extra fragment,
            every hop. You can put things down again from the traceroute
            map. Lore costs distance, which is the economy the whole
            design is built on.

All 13 hops re-swept: clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 15:46:18 +10:00

180 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}
.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 &nbsp; <k>SPACE</k> jump &nbsp; <k>MOUSE</k> look &nbsp; <k>E</k> use<br>
<k>M</k> traceroute &nbsp; <k>ESC</k> release mouse &nbsp; <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>
<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>