Files
vanta-relay-20260727-095415/index.html
T

200 lines
14 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#050711">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath fill='%233df2ff' d='M16 2 30 16 16 30 2 16Z'/%3E%3Cpath fill='%23050711' d='m16 8 8 8-8 8-8-8Z'/%3E%3C/svg%3E">
<title>VANTA//RELAY — Echo puzzle</title>
<style>
:root {
color-scheme: dark;
--ink: #e6fbff;
--muted: #7f9bab;
--cyan: #3df2ff;
--violet: #a98bff;
--pink: #ff62d1;
--line: rgba(116, 229, 255, .22);
--panel: rgba(6, 12, 26, .67);
--shadow: rgba(0, 0, 0, .56);
font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
}
* { box-sizing: border-box; }
html, body { min-height: 100%; margin: 0; }
body {
overflow-x: hidden;
color: var(--ink);
background:
radial-gradient(circle at 50% -20%, #25347a 0, transparent 35rem),
radial-gradient(circle at 105% 80%, #321442 0, transparent 32rem),
#050711;
}
body::before {
content: "";
pointer-events: none;
position: fixed;
inset: 0;
opacity: .24;
background-image: linear-gradient(rgba(255,255,255,.025) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.018) 1px, transparent 1px);
background-size: 4px 4px, 4px 4px;
mix-blend-mode: screen;
z-index: 5;
}
button { font: inherit; color: inherit; }
button:focus-visible { outline: 2px solid var(--cyan); outline-offset: 3px; }
.app-shell { width: min(1540px, 100%); min-height: 100vh; margin: 0 auto; padding: 20px; position: relative; }
.masthead { display: flex; align-items: center; justify-content: space-between; gap: 14px; margin-bottom: 14px; }
.brand { display: flex; align-items: baseline; gap: 10px; letter-spacing: .08em; }
.brand-mark { font-weight: 900; font-size: clamp(1.2rem, 3vw, 2rem); text-shadow: 0 0 18px var(--cyan); }
.brand-sub, .eyebrow, .mini-label { color: var(--muted); font-size: .66rem; letter-spacing: .16em; text-transform: uppercase; }
.system-status { display: flex; gap: 9px; align-items: center; color: #a7eeb7; font-size: .69rem; letter-spacing: .1em; }
.status-dot { width: 8px; height: 8px; border-radius: 50%; background: #66ff9a; box-shadow: 0 0 15px #66ff9a; animation: blink 1.4s infinite ease-in-out; }
.game-grid { display: grid; grid-template-columns: minmax(180px, 230px) minmax(0, 1fr) minmax(190px, 250px); gap: 14px; align-items: stretch; }
.panel, .stage-frame {
border: 1px solid var(--line);
background: linear-gradient(145deg, rgba(14, 27, 51, .79), var(--panel));
box-shadow: 0 18px 60px var(--shadow), inset 0 1px 0 rgba(255,255,255,.045);
position: relative;
overflow: hidden;
}
.panel::before, .stage-frame::before { content: ""; position: absolute; inset: 0; pointer-events: none; border: 1px solid rgba(255,255,255,.025); }
.panel { min-height: 626px; padding: 17px; }
.panel h2 { margin: 5px 0 14px; font-size: .92rem; letter-spacing: .03em; line-height: 1.35; }
.panel-section { border-top: 1px solid rgba(104, 227, 255, .16); padding-top: 14px; margin-top: 15px; }
.panel-section:first-child { border-top: 0; padding-top: 0; margin-top: 0; }
.readout { display: grid; gap: 8px; }
.readout-row { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; font-size: .73rem; }
.readout-row span:last-child { color: var(--cyan); text-align: right; }
.big-count { color: #fff; text-shadow: 0 0 14px var(--pink); font-size: 1.5rem; letter-spacing: -.08em; }
.mission-copy { color: #adbfca; font: 400 .76rem/1.58 ui-monospace, monospace; margin: 0; }
.legend { display: grid; gap: 8px; font-size: .7rem; color: #a9c1ce; }
.legend-item { display: flex; gap: 9px; align-items: center; }
.glyph { width: 12px; height: 12px; transform: rotate(45deg); border: 1px solid var(--cyan); box-shadow: 0 0 10px var(--cyan); }
.glyph.echo { border-radius: 50%; border-color: var(--pink); box-shadow: 0 0 10px var(--pink); }
.glyph.gate { background: linear-gradient(135deg, var(--violet), transparent); border-color: var(--violet); box-shadow: 0 0 10px var(--violet); }
.stage-frame { min-height: 626px; isolation: isolate; background: #080c1b; }
#relay-canvas { display: block; width: 100%; height: 100%; min-height: 626px; position: relative; z-index: 0; cursor: crosshair; }
.stage-corners::before, .stage-corners::after { content: ""; position: absolute; z-index: 2; width: 36px; height: 36px; pointer-events: none; border-color: var(--cyan); opacity: .7; }
.stage-corners::before { left: 10px; top: 10px; border-left: 2px solid; border-top: 2px solid; }
.stage-corners::after { right: 10px; bottom: 10px; border-right: 2px solid; border-bottom: 2px solid; }
.stage-readout { position: absolute; z-index: 2; left: 18px; right: 18px; top: 15px; display: flex; justify-content: space-between; pointer-events: none; font-size: .63rem; color: rgba(216, 250, 255, .74); letter-spacing: .14em; }
.objective-band { position: absolute; z-index: 2; left: 18px; right: 18px; bottom: 16px; display: flex; align-items: end; justify-content: space-between; gap: 14px; pointer-events: none; }
.objective { background: rgba(4, 8, 18, .68); border-left: 2px solid var(--cyan); padding: 9px 11px; max-width: 70%; color: #c8e3ec; font-size: .7rem; line-height: 1.45; }
.event-feed { color: var(--cyan); text-shadow: 0 0 10px var(--cyan); font-size: .63rem; text-align: right; letter-spacing: .08em; max-width: 34%; }
.protocol-list { display: grid; gap: 7px; margin-top: 10px; }
.protocol-node { display: flex; justify-content: space-between; gap: 6px; padding: 7px 8px; border: 1px solid rgba(116, 229, 255, .16); color: #7796a4; background: rgba(1, 5, 14, .26); font-size: .67rem; }
.protocol-node.active { color: #edfeff; border-color: rgba(61, 242, 255, .74); box-shadow: inset 3px 0 0 var(--cyan), 0 0 15px rgba(61, 242, 255, .12); }
.protocol-node.current { color: var(--pink); border-color: rgba(255, 98, 209, .62); }
.control-stack { display: grid; gap: 9px; margin-top: 10px; }
.action-button, .tiny-button, .dpad button, .modal-button {
border: 1px solid rgba(112, 230, 255, .34);
background: linear-gradient(160deg, rgba(33, 70, 101, .75), rgba(7, 14, 30, .9));
min-height: 38px;
cursor: pointer;
transition: transform .16s ease, border-color .16s ease, box-shadow .16s ease;
}
.action-button:hover, .tiny-button:hover, .dpad button:hover, .modal-button:hover { transform: translateY(-2px); border-color: var(--cyan); box-shadow: 0 0 20px rgba(61, 242, 255, .2); }
.action-button { letter-spacing: .11em; font-size: .72rem; }
.action-button.primary { color: #07131d; background: linear-gradient(135deg, #a8fbff, #3df2ff); border-color: #d0ffff; font-weight: 800; box-shadow: 0 0 24px rgba(61, 242, 255, .25); }
.small-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.tiny-button { min-height: 31px; font-size: .62rem; color: #a9c2cd; }
.dpad { display: grid; grid-template-columns: repeat(3, 38px); grid-template-rows: repeat(2, 34px); justify-content: center; gap: 4px; margin-top: 13px; }
.dpad button { min-height: 34px; font-size: .72rem; }
.dpad [data-move="U"] { grid-column: 2; }
.dpad [data-move="L"] { grid-column: 1; }
.dpad [data-move="D"] { grid-column: 2; }
.dpad [data-move="R"] { grid-column: 3; }
.key-hints { text-align: center; margin-top: 11px; color: #66838f; font-size: .58rem; line-height: 1.65; }
kbd { border: 1px solid #395365; padding: 1px 3px; color: #c5e6ee; background: #0b1726; }
.signal-modal { position: fixed; z-index: 10; inset: 0; display: grid; place-items: center; padding: 22px; background: rgba(1, 3, 11, .78); backdrop-filter: blur(9px); }
.signal-modal[hidden], .help-card[hidden] { display: none; }
.modal-card { width: min(440px, 100%); padding: 28px; border: 1px solid var(--cyan); background: linear-gradient(145deg, #12264b, #070d1c 70%); box-shadow: 0 0 80px rgba(61, 242, 255, .28); text-align: center; }
.modal-card h2 { font-size: clamp(1.45rem, 5vw, 2.3rem); letter-spacing: .09em; margin: 10px 0; text-shadow: 0 0 20px var(--cyan); }
.modal-card p { color: #b4cad2; font: .78rem/1.6 ui-monospace, monospace; margin: 0 0 20px; }
.modal-button { width: 100%; min-height: 44px; letter-spacing: .12em; color: #07131d; background: var(--cyan); font-weight: 800; }
.help-card { position: fixed; z-index: 9; right: 25px; bottom: 25px; max-width: 330px; padding: 16px; border: 1px solid var(--violet); background: #0a1023; box-shadow: 0 18px 60px #000; font: .72rem/1.55 ui-monospace, monospace; color: #c3d5df; }
.help-card strong { color: var(--cyan); }
@keyframes blink { 50% { opacity: .35; transform: scale(.7); } }
@media (max-width: 1040px) { .game-grid { grid-template-columns: 200px minmax(0, 1fr); } .right-panel { grid-column: 1 / -1; min-height: auto; display: grid; grid-template-columns: 1fr 1fr; gap: 17px; } .right-panel .panel-section:first-child { border-top: 0; padding-top: 0; } }
@media (max-width: 700px) { .app-shell { padding: 10px; } .masthead { align-items: flex-start; } .brand-sub { display: none; } .game-grid { display: flex; flex-direction: column; } .panel { min-height: auto; } .left-panel { order: 2; } .right-panel { order: 3; display: block; } .stage-frame { order: 1; min-height: 480px; } #relay-canvas { min-height: 480px; } .objective { max-width: 78%; font-size: .62rem; } .event-feed { display: none; } }
</style>
</head>
<body>
<main class="app-shell">
<header class="masthead">
<div class="brand"><span class="brand-mark">VANTA//RELAY</span><span class="brand-sub">a memory-routing experiment</span></div>
<div class="system-status"><span class="status-dot"></span><span id="system-status">NEURAL LINK STABLE</span></div>
</header>
<section class="game-grid" aria-label="Vanta Relay game console">
<aside class="panel left-panel" aria-label="Mission and controls">
<section class="panel-section">
<div class="eyebrow" id="level-code">01 / COLD BOOT</div>
<h2 id="level-title">Wake the first witness</h2>
<div class="readout">
<div class="readout-row"><span>ECHOS</span><span><b class="big-count" id="echo-count">00</b> / <span id="echo-capacity">01</span></span></div>
<div class="readout-row"><span>STEPS</span><span id="move-count">000</span></div>
<div class="readout-row"><span>GATES</span><span id="gate-count">00 ACTIVE</span></div>
</div>
</section>
<section class="panel-section">
<div class="eyebrow">Mission note</div>
<p class="mission-copy" id="briefing">The exit only remembers a body that is no longer there. Leave one echo on the amber node.</p>
</section>
<section class="panel-section">
<div class="eyebrow">Matter legend</div>
<div class="legend">
<div class="legend-item"><i class="glyph"></i><span>YOU / volatile present</span></div>
<div class="legend-item"><i class="glyph echo"></i><span>ECHO / frozen witness</span></div>
<div class="legend-item"><i class="glyph gate"></i><span>GATE / needs an echo</span></div>
</div>
</section>
</aside>
<section class="stage-frame" aria-label="Interactive relay board">
<canvas id="relay-canvas" width="1380" height="860" aria-label="Isometric neon relay puzzle board"></canvas>
<div class="stage-corners" aria-hidden="true"></div>
<div class="stage-readout"><span id="coordinate-readout">POS // 01.01</span><span id="cycle-readout">CYCLE // 000</span></div>
<div class="objective-band"><div class="objective" id="objective-text">Walk onto a lower-case signal node, then press ANCHOR. Your new echo stays there while you restart.</div><div class="event-feed" id="event-feed">AWAITING INPUT</div></div>
</section>
<aside class="panel right-panel" aria-label="Relay controls">
<section class="panel-section">
<div class="eyebrow">Signal protocol</div>
<div class="protocol-list" id="protocol-list"></div>
</section>
<section class="panel-section">
<div class="eyebrow">Temporal controls</div>
<div class="control-stack">
<button class="action-button primary" type="button" data-action="anchor">✦ ANCHOR ECHO</button>
<div class="small-actions"><button class="tiny-button" type="button" data-action="undo">↶ UNDO ECHO</button><button class="tiny-button" type="button" data-action="reset">⌁ RESET RELAY</button></div>
</div>
<div class="dpad" aria-label="Movement controls">
<button type="button" data-move="U" aria-label="Move up"></button>
<button type="button" data-move="L" aria-label="Move left"></button>
<button type="button" data-move="D" aria-label="Move down"></button>
<button type="button" data-move="R" aria-label="Move right"></button>
</div>
<div class="key-hints"><kbd>WASD</kbd> / <kbd>ARROWS</kbd> MOVE<br><kbd>SPACE</kbd> ANCHOR · <kbd>Z</kbd> UNDO · <kbd>R</kbd> RESET</div>
</section>
<section class="panel-section">
<div class="small-actions"><button class="tiny-button" type="button" data-action="mute">◌ AUDIO: ON</button><button class="tiny-button" type="button" data-action="help">? PROTOCOL</button></div>
</section>
</aside>
</section>
</main>
<section class="signal-modal" id="signal-modal" hidden aria-live="polite" aria-modal="true" role="dialog" aria-labelledby="modal-title">
<div class="modal-card">
<div class="eyebrow">relay status // verified</div>
<h2 id="modal-title">SIGNAL HOLDS</h2>
<p id="modal-copy">The next memory chamber is now responsive.</p>
<button class="modal-button" id="next-level" type="button">CONTINUE</button>
</div>
</section>
<aside class="help-card" id="help-card" hidden><strong>HOW THE RELAY WORKS</strong><br>Only frozen echoes power lower-case signal nodes. Reach a node, anchor a copy of yourself, and you snap back to the entrance. Gates use the matching upper-case letter. The current you never powers a gate.</aside>
<script type="module" src="./app.js"></script>
</body>
</html>