<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>493 PIP TERMINAL</title>
<style>
* { box-sizing: border-box; }
html, body {
margin: 0; padding: 0;
background: #050805;
color: #4fff88;
font-family: 'Courier New', monospace;
height: 100%;
overflow-x: hidden;
transition: filter 0.4s;
image-rendering: pixelated;
image-rendering: crisp-edges;
}
* { image-rendering: pixelated; }
body {
text-shadow: 1px 0 0 currentColor;
}
html.power-save { filter: brightness(0.45) saturate(0.6); }
html.alert-mode #console { box-shadow: 0 0 0 3px #1c1812, 0 0 40px 10px rgba(255,0,0,0.5), inset 0 2px 6px rgba(255,255,255,0.06); }
html.shake .screen-frame { animation: shakeit 0.4s infinite; }
@keyframes shakeit { 0%,100%{transform:translate(0,0);} 25%{transform:translate(1px,-1px);} 50%{transform:translate(-1px,1px);} 75%{transform:translate(1px,1px);} }
html.shields .screen-frame { box-shadow: 0 0 20px 4px #4fd1ff, inset 0 0 30px rgba(0,0,0,0.9); }
#console {
position: relative;
max-width: 1120px;
margin: 16px auto;
background: linear-gradient(180deg,#3a352c,#26221b);
border-radius: 18px;
padding: 34px 60px;
box-shadow: 0 0 0 3px #14110c, 0 10px 30px rgba(0,0,0,0.6), inset 0 2px 6px rgba(255,255,255,0.06);
border: 2px solid #14110c;
}
.bolt {
position:absolute;
width:16px; height:16px;
border-radius:50%;
background: radial-gradient(circle at 35% 35%, #8a8072, #2a251d 70%);
box-shadow: inset 0 0 2px #000, 0 1px 1px rgba(255,255,255,0.2);
}
.bolt.tl { top:12px; left:12px; }
.bolt.tr { top:12px; right:12px; }
.bolt.bl { bottom:12px; left:12px; }
.bolt.br { bottom:12px; right:12px; }
.indicator-strip {
display:flex; justify-content:center; gap:10px;
margin-bottom: 14px;
}
.indicator-strip .ilamp {
width:12px; height:12px; border-radius:50%;
background:#264d2e;
box-shadow: inset 0 0 3px #000;
}
.indicator-strip .ilamp.lit {
background:#4fff88;
box-shadow: 0 0 8px #4fff88, 0 0 16px #4fff88, inset 0 0 3px #0a3d1c;
animation: lampPulse 2.4s infinite;
}
@keyframes lampPulse { 0%,100%{opacity:1;} 50%{opacity:0.6;} }
.side-controls {
position:absolute;
top: 60px; bottom: 60px;
width: 44px;
display:flex; flex-direction:column; justify-content:space-around; align-items:center;
}
.side-controls.left { left: 8px; }
.side-controls.right { right: 8px; }
.lever-wrap {
display:flex; flex-direction:column; align-items:center; gap:6px;
cursor:pointer; user-select:none;
}
.lever-slot {
width:10px; height:70px;
background: #0d140f;
border-radius:6px;
box-shadow: inset 0 0 5px #000;
position:relative;
}
.lever-handle {
position:absolute;
left:50%; transform:translateX(-50%);
top: 2px;
width:26px; height:26px;
border-radius:50%;
background: radial-gradient(circle at 35% 30%, #4fff88, #0a3d1c 75%);
box-shadow: 0 0 8px rgba(79,255,136,0.6), inset 0 0 4px #000;
transition: top 0.25s;
}
.lever-wrap.up .lever-handle { top: 2px; }
.lever-wrap.down .lever-handle { top: 42px; }
.lever-label {
font-size:8px; letter-spacing:1px; color:#7fcf9a;
text-transform:uppercase; text-align:center;
}
.toggle-row {
display:flex; justify-content:center; gap:26px;
margin-top: 16px;
}
.toggle-wrap {
display:flex; flex-direction:column; align-items:center; gap:6px;
cursor:pointer; user-select:none;
}
.toggle-sw {
width:44px; height:22px;
background:#0d140f;
border-radius:11px;
box-shadow: inset 0 0 5px #000;
position:relative;
}
.toggle-knob {
position:absolute; top:2px; left:2px;
width:18px; height:18px;
border-radius:50%;
background: radial-gradient(circle at 35% 30%, #4fff88, #0a3d1c 75%);
box-shadow: 0 0 6px rgba(79,255,136,0.6);
transition: left 0.2s;
}
.toggle-wrap.on .toggle-knob { left: 24px; }
.toggle-wrap.on .toggle-sw { box-shadow: inset 0 0 5px #000, 0 0 8px #4fff88; }
.small-btn-row {
display:flex; justify-content:center; gap:14px;
margin-top: 16px;
}
.small-btn {
width:28px; height:28px;
border-radius:50%;
background: radial-gradient(circle at 35% 30%, #7fd99f, #234a2f 80%);
border: 1px solid #14110c;
box-shadow: 0 2px 3px rgba(0,0,0,0.5), inset 0 0 3px rgba(255,255,255,0.3);
cursor:pointer;
}
.small-btn:active {
box-shadow: inset 0 0 6px #000;
transform: translateY(1px);
}
.dial-wrap {
display:flex; flex-direction:column; align-items:center; gap:6px;
cursor:pointer; user-select:none;
}
.dial {
width:38px; height:38px;
border-radius:50%;
background: radial-gradient(circle at 35% 30%, #3f5c47, #14110c 80%);
border: 2px solid #14110c;
box-shadow: 0 2px 4px rgba(0,0,0,0.6), inset 0 0 4px rgba(255,255,255,0.15);
position:relative;
transition: transform 0.3s;
}
.dial-notch {
position:absolute;
top:3px; left:50%;
width:4px; height:12px;
background:#4fff88;
box-shadow:0 0 4px #4fff88;
transform: translateX(-50%);
}
.toggle-wrap.mini .toggle-sw { width:34px; height:18px; }
.toggle-wrap.mini .toggle-knob { width:14px; height:14px; }
.toggle-wrap.mini.on .toggle-knob { left: 18px; }
.redbtn-wrap {
display:flex; flex-direction:column; align-items:center; gap:6px;
margin-top: 18px;
}
.redbtn {
width:54px; height:54px;
border-radius:50%;
background: radial-gradient(circle at 35% 30%, #ff4d4d, #7a0000 80%);
border: 3px solid #2a0000;
box-shadow: 0 3px 6px rgba(0,0,0,0.6), inset 0 0 6px rgba(255,255,255,0.3);
cursor:pointer;
}
.redbtn:active { transform: translateY(2px); box-shadow: inset 0 0 10px #000; }
#alertOverlay {
position:fixed; top:0; left:0; right:0; bottom:0;
background: rgba(255,0,0,0.18);
z-index: 1000;
pointer-events:none;
display:none;
animation: alertFlash 0.4s steps(1) infinite;
}
@keyframes alertFlash { 50% { background: rgba(255,0,0,0.05); } }
#alertText {
position:fixed; top:14px; left:50%; transform:translateX(-50%);
z-index:1001; color:#ff4d4d; font-family:'Courier New',monospace;
font-size:14px; letter-spacing:2px; text-shadow:0 0 10px #ff0000;
display:none;
}
.screen-frame {
background:#000;
border-radius: 0;
border: 8px solid #14110c;
box-shadow:
inset 0 0 40px rgba(0,0,0,0.9), inset 0 0 80px rgba(79,255,136,0.05),
0 0 0 4px #3a352c,
0 0 0 8px #14110c,
0 0 0 12px #4fff88,
0 0 0 16px #14110c;
padding: 4px;
position: relative;
margin: 16px;
}
.screen-frame::before, .screen-frame::after {
content: '';
position: absolute;
width: 16px; height: 16px;
background: #4fff88;
box-shadow: 0 0 0 4px #14110c;
}
.screen-frame::before { top: -16px; left: -16px; }
.screen-frame::after { top: -16px; right: -16px; }
#crt {
position: fixed;
top:0; left:0; right:0; bottom:0;
pointer-events: none;
background: repeating-linear-gradient(
to bottom,
rgba(255,255,255,0.05) 0px,
rgba(255,255,255,0.05) 1px,
transparent 2px,
transparent 4px
);
mix-blend-mode: overlay;
z-index: 999;
animation: flicker 6s infinite;
}
@keyframes flicker {
0%, 96%, 100% { opacity: 1; }
97% { opacity: 0.85; }
98% { opacity: 1; }
99% { opacity: 0.9; }
}
#vignette {
position: fixed; top:0;left:0;right:0;bottom:0;
pointer-events:none;
box-shadow: inset 0 0 150px 40px #000;
z-index: 998;
}
.wrap {
max-width: 980px;
margin: 0 auto;
padding: 18px 16px 30px;
}
.panel {
border: 3px solid #276b3f;
background: linear-gradient(180deg, #001400, #000a00);
border-radius: 0;
box-shadow: 0 0 0 rgba(79,255,136,0.2), inset 0 0 0 rgba(0,0,0,0.7), 3px 3px 0 #0a2515;
margin-bottom: 16px;
padding: 14px 16px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
.title {
font-size: 22px;
letter-spacing: 3px;
text-shadow: 0 0 8px #4fff88, 0 0 2px #d6ffe4;
}
.blink {
animation: blink 1s steps(1) infinite;
}
@keyframes blink { 50% { opacity: 0; } }
.led-row { display:flex; gap:6px; align-items:center; }
.led {
width:10px; height:10px; border-radius:50%;
background:#264d2e;
box-shadow: 0 0 4px #264d2e;
}
.led.on {
background:#4fff88;
box-shadow: 0 0 8px #4fff88, 0 0 16px #4fff88;
}
.subhead {
font-size: 12px;
color: #7fcf9a;
margin-top: 4px;
letter-spacing: 1px;
}
.section-title {
font-size: 13px;
letter-spacing: 2px;
color: #a6ffca;
border-bottom: 1px dashed #276b3f;
padding-bottom: 6px;
margin-bottom: 10px;
text-transform: uppercase;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 10px;
}
.card {
border: 2px solid #276b3f;
background: #001700;
border-radius: 0;
padding: 10px 12px;
position: relative;
box-shadow: 2px 2px 0 #0a2515;
}
.card .tag {
display:inline-block;
font-size: 10px;
color: #001700;
background: #4fff88;
padding: 1px 6px;
border-radius: 3px;
letter-spacing: 1px;
margin-bottom: 6px;
}
.card p {
font-size: 12.5px;
margin: 4px 0 10px;
color: #c6ffd9;
line-height: 1.35;
}
button.btn {
font-family: inherit;
background: #002400;
color: #4fff88;
border: 2px solid #276b3f;
padding: 7px 12px;
border-radius: 0;
cursor: pointer;
font-size: 12px;
letter-spacing: 1px;
text-transform: uppercase;
transition: 0.1s;
box-shadow: 2px 2px 0 #0a2515;
}
button.btn:hover {
background: #4fff88;
color: #001700;
box-shadow: 2px 2px 0 #0a2515, 0 0 10px #4fff88;
}
button.btn:active {
box-shadow: none;
transform: translate(2px,2px);
}
.row { display:flex; gap:8px; flex-wrap:wrap; }
.mastery-bar {
height: 6px;
background: #06230f;
border-radius: 3px;
overflow: hidden;
margin-bottom: 8px;
}
.mastery-fill {
height: 100%;
background: linear-gradient(90deg,#276b3f,#4fff88);
}
.status-pill {
font-size: 10px;
padding: 1px 6px;
border-radius: 3px;
letter-spacing: 1px;
border: 1px solid #276b3f;
}
.status-done { background:#4fff88; color:#001700; }
.status-todo { color:#7fcf9a; }
.bignav {
display:flex; gap:10px; flex-wrap:wrap;
}
.bignav button {
flex: 1;
min-width: 140px;
padding: 14px 10px;
font-size: 13px;
}
.points {
font-size: 16px;
color: #baffcf;
text-shadow: 0 0 8px #baffcf;
}
.unit-label {
font-size: 10px;
color: #7fcf9a;
margin: 12px 0 6px;
letter-spacing: 1px;
text-transform: uppercase;
}
.empty {
font-size: 12px; color: #7fcf9a; font-style: italic;
}
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-thumb { background: #276b3f; }
#diagOverlay {
position: fixed; top:0; left:0; right:0; bottom:0;
background: rgba(0,0,0,0.7);
z-index: 2000;
display: none;
align-items: center;
justify-content: center;
}
#diagOverlay.open { display: flex; }
#diagWin {
width: 320px;
max-width: 90vw;
background: linear-gradient(180deg,#001400,#000a00);
border: 2px solid #4fff88;
border-radius: 4px;
box-shadow: 0 0 30px rgba(79,255,136,0.5), inset 0 0 20px rgba(0,0,0,0.8);
}
#diagHeader {
display: flex; justify-content: space-between; align-items: center;
background: #4fff88; color: #001700;
padding: 6px 10px;
font-size: 12px;
letter-spacing: 1px;
font-weight: bold;
}
#diagClose { cursor: pointer; }
#diagBody { padding: 14px 16px; }
.diag-row { margin-bottom: 14px; }
.diag-label {
font-size: 10px; color: #7fcf9a; letter-spacing: 1px;
margin-bottom: 6px; text-transform: uppercase;
}
.battery-shell {
width: 100%; height: 14px;
border: 1px solid #276b3f;
border-radius: 2px;
overflow: hidden;
background: #06230f;
margin-bottom: 4px;
}
.battery-fill {
height: 100%;
width: 0%;
background: linear-gradient(90deg,#276b3f,#4fff88);
transition: width 0.5s;
}
#batteryPct, #weatherLine, #timeLine {
font-size: 13px; color: #c6ffd9;
}
.side-battery {
position: absolute;
top: 60px;
left: -34px;
width: 20px;
height: 200px;
border: 2px solid #276b3f;
border-radius: 4px;
background: #06230f;
overflow: hidden;
display: flex;
flex-direction: column-reverse;
}
.side-battery-fill {
width: 100%;
background: linear-gradient(0deg,#276b3f,#4fff88);
transition: height 0.6s;
}
.tabbar {
display: flex;
gap: 4px;
margin-bottom: 14px;
flex-wrap: wrap;
border-bottom: 2px solid #276b3f;
padding-bottom: 0;
}
.tabbtn {
font-family: inherit;
background: #001700;
color: #7fcf9a;
border: 1px solid #276b3f;
border-bottom: none;
padding: 8px 14px;
cursor: pointer;
font-size: 12px;
letter-spacing: 2px;
text-transform: uppercase;
border-radius: 4px 4px 0 0;
}
.tabbtn.active {
background: #4fff88;
color: #001700;
box-shadow: 0 0 10px #4fff88;
}
.tabpage { display: none; }
.tabpage.active { display: block; }
#gameArea {
text-align: center;
}
#gameGrid {
display: inline-grid;
grid-template-columns: repeat(4, 60px);
gap: 8px;
margin: 14px 0;
}
.gword {
border: 1px solid #276b3f;
background: #001700;
color: #4fff88;
padding: 8px 4px;
font-size: 13px;
cursor: pointer;
border-radius: 2px;
letter-spacing: 1px;
}
.gword:hover { background: #0a3d1c; }
.gword.used { opacity: 0.25; pointer-events: none; }
#gameMsg { font-size: 14px; margin: 10px 0; color: #baffcf; min-height: 20px; }
#triesLine { font-size: 12px; color: #7fcf9a; }
</style>
</head>
<body>
<div id="console">
<div class="bolt tl"></div><div class="bolt tr"></div><div class="bolt bl"></div><div class="bolt br"></div>
<div class="indicator-strip" id="indicatorStrip"></div>
<div class="side-controls left">
<div class="lever-wrap up" id="leverA" onclick="toggleLever('leverA')">
<div class="lever-slot"><div class="lever-handle"></div></div>
<div class="lever-label">PWR</div>
</div>
<div class="lever-wrap down" id="leverB" onclick="toggleLever('leverB')">
<div class="lever-slot"><div class="lever-handle"></div></div>
<div class="lever-label">AUX</div>
</div>
<div class="dial-wrap" onclick="spinDial(this)">
<div class="dial"><div class="dial-notch"></div></div>
<div class="lever-label">FUEL</div>
</div>
<div class="toggle-wrap mini" onclick="toggleSwitch(this)"><div class="toggle-sw"><div class="toggle-knob"></div></div><div class="lever-label">O2</div></div>
</div>
<div class="side-controls right">
<div class="lever-wrap up" id="leverC" onclick="toggleLever('leverC')">
<div class="lever-slot"><div class="lever-handle"></div></div>
<div class="lever-label">NAV</div>
</div>
<div class="lever-wrap down" id="leverD" onclick="toggleLever('leverD')">
<div class="lever-slot"><div class="lever-handle"></div></div>
<div class="lever-label">COM</div>
</div>
<div class="dial-wrap" onclick="spinDial(this)">
<div class="dial"><div class="dial-notch"></div></div>
<div class="lever-label">TEMP</div>
</div>
<div class="toggle-wrap mini on" onclick="toggleSwitch(this)"><div class="toggle-sw"><div class="toggle-knob"></div></div><div class="lever-label">RADIO</div></div>
</div>
<div class="screen-frame">
<div class="side-battery"><div class="side-battery-fill" id="sideBatteryFill" style="height:0%;"></div></div>
<div id="crt"></div>
<div id="vignette"></div>
<div class="wrap">
<div class="panel header">
<div>
<div class="title">493 PIP TERMINAL <span class="blink">_</span></div>
<div class="subhead" id="dateLine">SYSTEM READY // AWAITING UPLINK...</div>
</div>
<div style="text-align:right;">
<div class="led-row" style="justify-content:flex-end;">
<div class="led on"></div><div class="led on"></div><div class="led"></div>
</div>
<div class="points" id="pointsLine">◆ 0 PTS</div>
</div>
</div>
<div class="tabbar">
<button class="tabbtn active" data-tab="status" onclick="showTab('status')">STATUS</button>
<button class="tabbtn" data-tab="lessons" onclick="showTab('lessons')">LESSONS</button>
<button class="tabbtn" data-tab="math" onclick="showTab('math')">MATH</button>
<button class="tabbtn" data-tab="chores" onclick="showTab('chores')">CHORES</button>
<button class="tabbtn" data-tab="projects" onclick="showTab('projects')">PROJECTS</button>
<button class="tabbtn" data-tab="apps" onclick="showTab('apps')">APPS</button>
<button class="tabbtn" data-tab="games" onclick="showTab('games')">GAMES</button>
</div>
<div id="page-status" class="tabpage active">
<div class="panel" style="display:flex; gap:20px; flex-wrap:wrap; align-items:center; justify-content:space-between;">
<div class="subhead" style="margin:0;">USE THE DIAGNOSTICS PANEL TO CHECK VITALS</div>
<button class="btn" onclick="openDiag()">OPEN DIAGNOSTICS</button>
</div>
<div class="panel">
<div class="section-title">Pilot Log</div>
<div id="greetLine" class="subhead" style="font-size:14px;color:#cdfde3;">Loading pilot data...</div>
</div>
<div class="panel">
<div class="section-title">Quick Jump</div>
<div class="bignav">
<button class="btn" onclick="go(ctx && ctx.links.studio)">STUDIO</button>
<button class="btn" onclick="go(ctx && ctx.links.projects)">PROJECTS</button>
<button class="btn" onclick="go(ctx && ctx.links.multiplication)">GAMES</button>
<button class="btn" onclick="go(ctx && ctx.links.apps)">ALL APPS</button>
</div>
</div>
</div>
<div id="page-lessons" class="tabpage">
<div class="panel">
<div class="section-title">Today's Lesson Feed</div>
<div class="grid" id="lessonsGrid"><div class="empty">No transmissions today.</div></div>
</div>
</div>
<div id="page-math" class="tabpage">
<div class="panel">
<div class="section-title">Math Core Sequence</div>
<div id="mathGrid"></div>
</div>
</div>
<div id="page-chores" class="tabpage">
<div class="panel">
<div class="section-title">Chore Duty Roster</div>
<div id="choreWalletLine" class="subhead" style="font-size:13px;color:#cdfde3;margin-bottom:10px;">Scanning duty roster...</div>
<div id="choresGrid"><div class="empty">No chores on the roster.</div></div>
<div class="row" style="margin-top:10px;">
<button class="btn" onclick="go(ctx && ctx.links.chores)">FULL CHORE STATION</button>
</div>
</div>
</div>
<div id="page-projects" class="tabpage">
<div class="panel">
<div class="section-title">Active Projects</div>
<div class="grid" id="projectsGrid"><div class="empty">No projects logged yet.</div></div>
<div class="row" style="margin-top:10px;">
<button class="btn" onclick="go(ctx && ctx.links.newProject)">NEW PROJECT LOG</button>
</div>
</div>
</div>
<div id="page-apps" class="tabpage">
<div class="panel">
<div class="section-title">Studio Apps</div>
<div class="grid" id="appsGrid"></div>
</div>
</div>
<div id="page-games" class="tabpage">
<div class="panel">
<div class="section-title">Arcade — Number Cracker</div>
<div class="subhead" style="margin-bottom:10px;">Crack the code! Pick numbers 1-16. Find the secret one before you run out of tries.</div>
<div id="gameArea">
<div id="triesLine">TRIES LEFT: 5</div>
<div id="gameGrid"></div>
<div id="gameMsg">PRESS RESET TO START</div>
<button class="btn" onclick="resetGame()">RESET TERMINAL</button>
</div>
</div>
<div class="panel">
<div class="section-title">Arcade — Radar Snake</div>
<div class="subhead" style="margin-bottom:10px;">Guide the blip across the radar. Eat the pings, don't hit the walls or your own trail!</div>
<div id="snakeArea" style="text-align:center;">
<div id="snakeMsg" style="font-size:13px;color:#baffcf;margin-bottom:8px;">PRESS START, THEN USE ARROW KEYS</div>
<canvas id="snakeCanvas" width="300" height="300" style="background:#06230f;border:2px solid #276b3f;border-radius:4px;"></canvas>
<div style="margin-top:10px;">
<button class="btn" onclick="startSnake()">START</button>
<button class="btn" onclick="moveSnakeDir(0,-1)">UP</button>
<button class="btn" onclick="moveSnakeDir(0,1)">DOWN</button>
<button class="btn" onclick="moveSnakeDir(-1,0)">LEFT</button>
<button class="btn" onclick="moveSnakeDir(1,0)">RIGHT</button>
</div>
</div>
</div>
<div class="panel">
<div class="section-title">Vault Tycoon</div>
<div class="subhead" style="margin-bottom:10px;">Build rooms, assign dwellers, and manage your vault's power, food and water!</div>
<div id="vaultArea">
<div class="row" style="margin-bottom:10px;">
<div class="card" style="flex:1;min-width:120px;">
<span class="tag">POWER</span>
<div class="mastery-bar"><div class="mastery-fill" id="vPowerBar" style="width:50%;"></div></div>
<div id="vPowerNum" style="font-size:12px;color:#ffb000;">50 / 100</div>
</div>
<div class="card" style="flex:1;min-width:120px;">
<span class="tag">FOOD</span>
<div class="mastery-bar"><div class="mastery-fill" id="vFoodBar" style="width:50%;"></div></div>
<div id="vFoodNum" style="font-size:12px;color:#ffb000;">50 / 100</div>
</div>
<div class="card" style="flex:1;min-width:120px;">
<span class="tag">WATER</span>
<div class="mastery-bar"><div class="mastery-fill" id="vWaterBar" style="width:50%;"></div></div>
<div id="vWaterNum" style="font-size:12px;color:#ffb000;">50 / 100</div>
</div>
</div>
<div class="row" style="margin-bottom:10px;align-items:center;">
<div class="card" style="flex:1;min-width:150px;">
<span class="tag">CAPS</span>
<div id="vCapsNum" style="font-size:16px;color:#ffb000;margin-top:6px;">100</div>
</div>
<div class="card" style="flex:1;min-width:150px;">
<span class="tag">DWELLERS</span>
<div id="vDwellersNum" style="font-size:16px;color:#ffb000;margin-top:6px;">1</div>
</div>
</div>
<div class="section-title" style="margin-top:16px;">Build a Room (cost in caps)</div>
<div class="row" style="margin-bottom:10px;">
<button class="btn" onclick="vaultBuild('power')">POWER PLANT (30)</button>
<button class="btn" onclick="vaultBuild('food')">DINER (30)</button>
<button class="btn" onclick="vaultBuild('water')">WATER PLANT (30)</button>
<button class="btn" onclick="vaultBuild('quarters')">LIVING QUARTERS (50)</button>
</div>
<div class="section-title">Your Rooms</div>
<div class="grid" id="vaultRoomsGrid"><div class="empty">No rooms built yet. Start with a Power Plant!</div></div>
<div id="vaultMsg" style="font-size:13px;color:#baffcf;margin-top:10px;min-height:18px;"></div>
</div>
</div>
<div class="panel">
<div class="section-title">More Games</div>
<div class="bignav">
<button class="btn" onclick="go(ctx && ctx.links.multiplication)">MULTIPLICATION GAME</button>
<button class="btn" onclick="go(ctx && ctx.links.apps)">PLAY STUDIO APPS</button>
</div>
</div>
</div>
</div>
<div class="toggle-row">
<div class="toggle-wrap on" id="toggleA" onclick="toggleSwitch('toggleA')"><div class="toggle-sw"><div class="toggle-knob"></div></div><div class="lever-label">LIFE SUPPORT</div></div>
<div class="toggle-wrap" id="toggleB" onclick="toggleSwitch('toggleB')"><div class="toggle-sw"><div class="toggle-knob"></div></div><div class="lever-label">SHIELDS</div></div>
</div>
<div class="small-btn-row">
<div class="small-btn" onclick="blinkLamps()"></div>
<div class="small-btn" onclick="blinkLamps()"></div>
<div class="small-btn" onclick="blinkLamps()"></div>
<div class="small-btn" onclick="blinkLamps()"></div>
</div>
<div class="redbtn-wrap">
<div class="redbtn" onclick="pressRedButton()"></div>
<div class="lever-label">DO NOT PRESS</div>
</div>
</div>
<div id="alertOverlay"></div>
<div id="alertText">⚠ WARNING: DO NOT TOUCH ANYTHING ⚠</div>
<div id="diagOverlay" onclick="if(event.target===this) closeDiag()">
<div id="diagWin">
<div id="diagHeader">
<span>SYSTEM DIAGNOSTICS</span>
<span id="diagClose" onclick="closeDiag()">[ X ]</span>
</div>
<div id="diagBody">
<div class="diag-row">
<div class="diag-label">BATTERY</div>
<div class="battery-shell"><div class="battery-fill" id="batteryFill"></div></div>
<div id="batteryPct">--</div>
</div>
<div class="diag-row">
<div class="diag-label">WEATHER</div>
<div id="weatherLine">SCANNING...</div>
</div>
<div class="diag-row">
<div class="diag-label">LOCAL TIME</div>
<div id="timeLine">--:--:--</div>
</div>
<div class="diag-row">
<div class="diag-label">CPU LOAD</div>
<div class="battery-shell"><div class="battery-fill" id="cpuFill" style="background:linear-gradient(90deg,#7a5a00,#ffb000);"></div></div>
<div id="cpuPct">--</div>
</div>
<div class="diag-row">
<div class="diag-label">FILE DIRECTORY // C:\\VAULT493</div>
<div id="fileList" style="font-size:11px; line-height:1.6; max-height:110px; overflow-y:auto; border:1px solid #276b3f; padding:6px 8px; background:#06230f;"></div>
</div>
</div>
</div>
</div>
<script>
let ctx = null;
function showTab(name) {
document.querySelectorAll('.tabpage').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.tabbtn').forEach(b => b.classList.remove('active'));
document.getElementById('page-' + name).classList.add('active');
document.querySelector('.tabbtn[data-tab="' + name + '"]').classList.add('active');
blinkLamps();
}
function toggleLever(id) {
const el = document.getElementById(id);
el.classList.toggle('up');
el.classList.toggle('down');
blinkLamps();
if (id === 'leverA') {
document.documentElement.classList.toggle('power-save', el.classList.contains('down'));
}
if (id === 'leverB') {
document.documentElement.classList.toggle('alert-mode', el.classList.contains('up'));
}
}
function toggleSwitch(idOrEl) {
const el = typeof idOrEl === 'string' ? document.getElementById(idOrEl) : idOrEl;
el.classList.toggle('on');
blinkLamps();
if (el.id === 'toggleA') {
document.documentElement.classList.toggle('shake', !el.classList.contains('on'));
}
if (el.id === 'toggleB') {
document.documentElement.classList.toggle('shields', el.classList.contains('on'));
}
}
const screenHues = ['#4fff88', '#ffb000', '#4fd1ff', '#ff5f8f'];
function spinScreenHue() {
const idx = Math.floor(Math.random() * screenHues.length);
document.querySelectorAll('.title, .points').forEach(el => { el.style.color = screenHues[idx]; el.style.textShadow = '0 0 8px ' + screenHues[idx]; });
}
function blinkLamps() {
document.querySelectorAll('.ilamp').forEach(l => {
if (Math.random() > 0.4) l.classList.toggle('lit');
});
}
function spinDial(el) {
const dial = el.querySelector('.dial');
const current = parseInt(dial.dataset.deg || '0', 10);
const next = current + 90;
dial.style.transform = 'rotate(' + next + 'deg)';
dial.dataset.deg = next;
blinkLamps();
spinScreenHue();
}
function pressRedButton() {
const overlay = document.getElementById('alertOverlay');
const text = document.getElementById('alertText');
overlay.style.display = 'block';
text.style.display = 'block';
for (let i=0;i<6;i++) setTimeout(blinkLamps, i*150);
setTimeout(() => { overlay.style.display='none'; text.style.display='none'; }, 1800);
}
(function initLamps(){
const strip = document.getElementById('indicatorStrip');
let html = '';
for (let i=0;i<14;i++) html += `<div class="ilamp ${Math.random()>0.5?'lit':''}"></div>`;
strip.innerHTML = html;
setInterval(blinkLamps, 3500);
})();
function updateBattery() {
const fill = document.getElementById('batteryFill');
const pct = document.getElementById('batteryPct');
const sideFill = document.getElementById('sideBatteryFill');
if (navigator.getBattery) {
navigator.getBattery().then(b => {
const p = Math.round(b.level * 100);
if (fill) fill.style.width = p + '%';
if (pct) pct.textContent = p + '%' + (b.charging ? ' CHG' : '');
if (fill) fill.style.background = p < 20 ? 'linear-gradient(90deg,#7a0000,#ff4d4d)' : 'linear-gradient(90deg,#276b3f,#4fff88)';
if (sideFill) sideFill.style.height = p + '%';
}).catch(() => {
if (fill) fill.style.width = '100%';
if (pct) pct.textContent = 'N/A';
if (sideFill) sideFill.style.height = '100%';
});
} else {
if (fill) fill.style.width = '100%';
if (pct) pct.textContent = 'N/A';
if (sideFill) sideFill.style.height = '100%';
}
}
updateBattery();
setInterval(updateBattery, 30000);
function updateWeather() {
const el = document.getElementById('weatherLine');
if (!el) return;
if (!navigator.geolocation) {
el.textContent = 'NO SENSOR LINK';
return;
}
navigator.geolocation.getCurrentPosition(pos => {
const { latitude, longitude } = pos.coords;
fetch(`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t_weather=true`)
.then(r => r.json())
.then(data => {
const w = data.current_weather;
if (!w) { el.textContent = 'NO SIGNAL'; return; }
const codes = {0:'CLEAR',1:'MOSTLY CLEAR',2:'PARTLY CLOUDY',3:'OVERCAST',45:'FOG',48:'FOG',51:'DRIZZLE',53:'DRIZZLE',55:'DRIZZLE',61:'RAIN',63:'RAIN',65:'HEAVY RAIN',71:'SNOW',73:'SNOW',75:'HEAVY SNOW',80:'SHOWERS',81:'SHOWERS',82:'STORMS',95:'STORMS'};
const desc = codes[w.weathercode] || 'UNKNOWN';
const f = Math.round(w.temperature * 9/5 + 32);
el.textContent = f + 'F // ' + desc;
}).catch(() => { el.textContent = 'SENSOR ERROR'; });
}, () => {
el.textContent = 'LOCATION DENIED';
}, { timeout: 8000 });
}
updateWeather();
setInterval(updateWeather, 600000);
const fakeFiles = [
{ name: 'LESSON_LOG.dat', size: '12KB' },
{ name: 'MATH_CORE_4A.sys', size: '48KB' },
{ name: 'PROJECT_MANEKI.tmp', size: '3KB' },
{ name: 'STUDIO_APPS/', size: '--' },
{ name: 'VAULT_TYCOON.sav', size: '7KB' },
{ name: 'RADAR_SNAKE.exe', size: '19KB' },
{ name: 'NUMBER_CRACKER.exe', size: '15KB' },
{ name: 'PIP_TERMINAL.cfg', size: '2KB' },
{ name: 'BACKUP_493.bak', size: '54KB' },
{ name: 'STUDENT_PAU.id', size: '1KB' }
];
function renderCpuAndFiles() {
const cpuFill = document.getElementById('cpuFill');
const cpuPct = document.getElementById('cpuPct');
const pct = Math.floor(Math.random()*60)+15;
if (cpuFill) cpuFill.style.width = pct + '%';
if (cpuPct) cpuPct.textContent = pct + '% // ' + (navigator.hardwareConcurrency || 4) + ' CORES DETECTED';
const fl = document.getElementById('fileList');
if (fl) {
fl.innerHTML = fakeFiles.map(f => `<div>${f.name.padEnd(22,'.')}${f.size}</div>`).join('');
}
}
function openDiag() {
document.getElementById('diagOverlay').classList.add('open');
updateBattery();
updateWeather();
renderCpuAndFiles();
}
function closeDiag() {
document.getElementById('diagOverlay').classList.remove('open');
}
function updateClock() {
const el = document.getElementById('timeLine');
if (!el) return;
el.textContent = new Date().toLocaleTimeString();
}
setInterval(updateClock, 1000);
updateClock();
function go(url) {
if (!url) return;
parent.postMessage({ type: 'academy:navigate', to: url }, '*');
}
function startLesson(id) {
parent.postMessage({ type: 'academy:startLesson', standardId: id }, '*');
}
function doChore(id, done) {
parent.postMessage({ type: done ? 'academy:undoChore' : 'academy:completeChore', choreId: id }, '*');
}
function awardItem(id) {}
let snakeGrid = 15, snakeCell = 20, snakeBody = [], snakeDir = {x:1,y:0}, snakeFood = {x:5,y:5}, snakeTimer = null, snakeAlive = false;
const snakeCanvas = document.getElementById('snakeCanvas');
const snakeCtx = snakeCanvas ? snakeCanvas.getContext('2d') : null;
function drawSnake() {
if (!snakeCtx) return;
snakeCtx.fillStyle = '#06230f';
snakeCtx.fillRect(0,0,300,300);
snakeCtx.fillStyle = '#ffb000';
snakeBody.forEach(p => snakeCtx.fillRect(p.x*snakeCell+1, p.y*snakeCell+1, snakeCell-2, snakeCell-2));
snakeCtx.fillStyle = '#4fff88';
snakeCtx.fillRect(snakeFood.x*snakeCell+3, snakeFood.y*snakeCell+3, snakeCell-6, snakeCell-6);
}
function placeFood() {
snakeFood = { x: Math.floor(Math.random()*snakeGrid), y: Math.floor(Math.random()*snakeGrid) };
if (snakeBody.some(p => p.x === snakeFood.x && p.y === snakeFood.y)) placeFood();
}
function startSnake() {
snakeBody = [{x:7,y:7},{x:6,y:7},{x:5,y:7}];
snakeDir = {x:1,y:0};
placeFood();
snakeAlive = true;
document.getElementById('snakeMsg').textContent = 'GO! USE ARROW KEYS OR BUTTONS';
if (snakeTimer) clearInterval(snakeTimer);
snakeTimer = setInterval(stepSnake, 180);
drawSnake();
}
function stepSnake() {
if (!snakeAlive) return;
const head = snakeBody[0];
const next = { x: head.x + snakeDir.x, y: head.y + snakeDir.y };
if (next.x < 0 || next.y < 0 || next.x >= snakeGrid || next.y >= snakeGrid || snakeBody.some(p => p.x===next.x && p.y===next.y)) {
snakeAlive = false;
clearInterval(snakeTimer);
const score = snakeBody.length - 3;
document.getElementById('snakeMsg').textContent = 'CRASHED! SCORE: ' + score + '. PRESS START TO RETRY.';
if (score >= 3) awardItem('cape');
return;
}
snakeBody.unshift(next);
if (next.x === snakeFood.x && next.y === snakeFood.y) {
placeFood();
} else {
snakeBody.pop();
}
drawSnake();
}
function moveSnakeDir(x,y) {
if (snakeDir.x === -x && snakeDir.y === -y) return;
snakeDir = {x,y};
}
document.addEventListener('keydown', (e) => {
if (!snakeAlive) return;
if (e.key === 'ArrowUp') { moveSnakeDir(0,-1); e.preventDefault(); }
if (e.key === 'ArrowDown') { moveSnakeDir(0,1); e.preventDefault(); }
if (e.key === 'ArrowLeft') { moveSnakeDir(-1,0); e.preventDefault(); }
if (e.key === 'ArrowRight') { moveSnakeDir(1,0); e.preventDefault(); }
});
drawSnake();
let secretNum = null, triesLeft = 5;
function resetGame() {
secretNum = Math.floor(Math.random()*16)+1;
triesLeft = 5;
document.getElementById('triesLine').textContent = 'TRIES LEFT: ' + triesLeft;
document.getElementById('gameMsg').textContent = 'PICK A NUMBER, ANY NUMBER...';
const grid = document.getElementById('gameGrid');
grid.innerHTML = '';
for (let i=1;i<=16;i++) {
const btn = document.createElement('div');
btn.className = 'gword';
btn.textContent = i;
btn.onclick = () => guessGame(i, btn);
grid.appendChild(btn);
}
}
function guessGame(n, btn) {
if (triesLeft <= 0 || btn.classList.contains('used')) return;
btn.classList.add('used');
if (n === secretNum) {
const usedTries = 5 - triesLeft;
document.getElementById('gameMsg').textContent = 'CODE CRACKED! IT WAS ' + n + '. TERMINAL UNLOCKED.';
triesLeft = 0;
blinkLamps();
if (!inventory.includes('cap')) awardItem('cap');
if (usedTries <= 1) {
awardItem('medal');
} else {
awardItem('goggles');
}
return;
}
triesLeft--;
document.getElementById('triesLine').textContent = 'TRIES LEFT: ' + triesLeft;
if (triesLeft <= 0) {
document.getElementById('gameMsg').textContent = 'LOCKED OUT! THE CODE WAS ' + secretNum + '.';
} else {
document.getElementById('gameMsg').textContent = n < secretNum ? 'TOO LOW. TRY HIGHER.' : 'TOO HIGH. TRY LOWER.';
}
blinkLamps();
}
resetGame();
let vault = {
power: 50, food: 50, water: 50, caps: 100, dwellers: 1,
rooms: []
};
const roomDefs = {
power: { name: 'Power Plant', cost: 30, produces: 'power', amount: 8 },
food: { name: 'Diner', cost: 30, produces: 'food', amount: 8 },
water: { name: 'Water Plant', cost: 30, produces: 'water', amount: 8 },
quarters: { name: 'Living Quarters', cost: 50, produces: 'dwellers', amount: 1 }
};
function vaultRender() {
document.getElementById('vPowerBar').style.width = vault.power + '%';
document.getElementById('vFoodBar').style.width = vault.food + '%';
document.getElementById('vWaterBar').style.width = vault.water + '%';
document.getElementById('vPowerNum').textContent = Math.round(vault.power) + ' / 100';
document.getElementById('vFoodNum').textContent = Math.round(vault.food) + ' / 100';
document.getElementById('vWaterNum').textContent = Math.round(vault.water) + ' / 100';
document.getElementById('vCapsNum').textContent = vault.caps;
document.getElementById('vDwellersNum').textContent = vault.dwellers;
const grid = document.getElementById('vaultRoomsGrid');
if (vault.rooms.length) {
grid.innerHTML = vault.rooms.map((r, i) => `
<div class="card">
<span class="tag">ROOM ${i+1}</span>
<div style="font-size:13px;color:#cdfde3;margin:4px 0;">${roomDefs[r].name}</div>
<p>Producing ${roomDefs[r].produces} over time.</p>
</div>
`).join('');
} else {
grid.innerHTML = '<div class="empty">No rooms built yet. Start with a Power Plant!</div>';
}
}
function vaultBuild(type) {
const def = roomDefs[type];
if (vault.caps < def.cost) {
document.getElementById('vaultMsg').textContent = 'NOT ENOUGH CAPS! NEED ' + def.cost + '.';
return;
}
vault.caps -= def.cost;
vault.rooms.push(type);
if (type === 'quarters') {
vault.dwellers += 1;
}
document.getElementById('vaultMsg').textContent = 'BUILT ' + def.name.toUpperCase() + '!';
blinkLamps();
vaultRender();
}
function vaultTick() {
vault.rooms.forEach(type => {
const def = roomDefs[type];
if (def.produces === 'power') vault.power = Math.min(100, vault.power + def.amount);
if (def.produces === 'food') vault.food = Math.min(100, vault.food + def.amount);
if (def.produces === 'water') vault.water = Math.min(100, vault.water + def.amount);
});
vault.power = Math.max(0, vault.power - 3 * vault.dwellers * 0.3);
vault.food = Math.max(0, vault.food - 3 * vault.dwellers * 0.3);
vault.water = Math.max(0, vault.water - 3 * vault.dwellers * 0.3);
vault.caps += 5 + vault.dwellers * 2;
vaultRender();
}
vaultRender();
setInterval(vaultTick, 4000);
function render(c) {
ctx = c;
document.getElementById('dateLine').textContent = 'STARDATE: ' + (c.today || '—').toUpperCase() + ' // UPLINK ESTABLISHED';
document.getElementById('pointsLine').textContent = '◆ ' + (c.points||0) + ' PTS';
document.getElementById('greetLine').textContent =
'WELCOME BACK, VAULT DWELLER ' + (c.student.name||'').toUpperCase() + '. GRADE ' + (c.student.grade||'') + ' CLEARANCE CONFIRMED. ' + (c.lessons.length) + ' MISSION(S) QUEUED.';
const lg = document.getElementById('lessonsGrid');
if (c.lessons && c.lessons.length) {
lg.innerHTML = c.lessons.map(l => `
<div class="card">
<span class="tag">${l.subject}</span>
<div style="font-size:11px;color:#4fbd85;">${l.code}</div>
<p>${l.description}</p>
<button class="btn" onclick="startLesson(${l.standardId})">START MISSION</button>
</div>
`).join('');
} else {
lg.innerHTML = '<div class="empty">No transmissions today.</div>';
}
const mg = document.getElementById('mathGrid');
if (c.math && c.math.length) {
let units = {};
c.math.forEach(m => {
units[m.unit] = units[m.unit] || [];
units[m.unit].push(m);
});
mg.innerHTML = Object.keys(units).map(u => `
<div class="unit-label">◈ ${u}</div>
<div class="grid">
${units[u].map(m => `
<div class="card">
<span class="tag">${m.code}</span>
<span class="status-pill ${m.status==='completed' ? 'status-done':'status-todo'}" style="float:right;">
${m.status==='completed' ? 'DONE' : 'PENDING'}
</span>
<p>${m.description}</p>
${m.mastery!=null ? `
<div class="mastery-bar"><div class="mastery-fill" style="width:${m.mastery}%;"></div></div>
<div style="font-size:10px;color:#4fbd85;margin-bottom:8px;">MASTERY: ${m.mastery}%</div>
` : ''}
<button class="btn" onclick="startLesson(${m.standardId})">ENGAGE</button>
</div>
`).join('')}
</div>
`).join('');
} else {
mg.innerHTML = '<div class="empty">No math sequence loaded.</div>';
}
const cg = document.getElementById('choresGrid');
const cwl = document.getElementById('choreWalletLine');
if (cg) {
const ch = c.chores;
if (cwl) cwl.textContent = ch
? ('\u25c6 CHORE CREDITS: $' + (ch.walletUsd || 0).toFixed(2) + ' // EARNED TODAY: $' + (ch.earnedTodayUsd || 0).toFixed(2))
: '\u25c6 CHORE STATION OFFLINE';
if (ch && ch.menu && ch.menu.length) {
cg.innerHTML = '<div class="grid">' + ch.menu.map(m => `
<div class="card">
<span class="tag">$${m.amountUsd.toFixed(2)}</span>
<span class="status-pill ${m.doneToday ? 'status-done' : 'status-todo'}" style="float:right;">
${m.doneToday ? 'DONE' : 'PENDING'}
</span>
<p>${m.title}</p>
<button class="btn" onclick="doChore(${m.choreId}, ${m.doneToday})">${m.doneToday ? 'UNDO' : 'MARK DONE'}</button>
</div>
`).join('') + '</div>';
} else {
cg.innerHTML = '<div class="empty">No chores on the roster.</div>';
}
}
const pg = document.getElementById('projectsGrid');
if (c.projects && c.projects.length) {
pg.innerHTML = c.projects.map(p => `
<div class="card">
<span class="tag">PROJECT</span>
<div style="font-size:13px;color:#cdfde3;margin:4px 0;">${p.title}</div>
<p>${p.description || ''} — ${p.entryCount} log entr${p.entryCount===1?'y':'ies'}</p>
<button class="btn" onclick="go('${p.url}')">OPEN LOG</button>
</div>
`).join('');
} else {
pg.innerHTML = '<div class="empty">No projects logged yet.</div>';
}
const ag = document.getElementById('appsGrid');
if (c.apps && c.apps.length) {
ag.innerHTML = c.apps.map(a => `
<div class="card">
<span class="tag">${a.published ? 'LIVE' : 'DRAFT'}</span>
<div style="font-size:13px;color:#cdfde3;margin:4px 0;">${a.title}</div>
<div class="row">
${a.published && a.playUrl ? `<button class="btn" onclick="go('${a.playUrl}')">PLAY</button>` : ''}
<button class="btn" onclick="go('${a.editUrl}')">EDIT</button>
</div>
</div>
`).join('');
} else {
ag.innerHTML = '<div class="empty">No apps built yet.</div>';
}
}
window.addEventListener('message', (e) => {
if (e.data && e.data.type === 'academy:context') render(e.data.context);
});
parent.postMessage({ type: 'academy:ready' }, '*');
</script>
</body>
</html>