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>
180 lines
10 KiB
HTML
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 <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>
|
|
<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>
|