<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>493 Pip-Boy</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
background: #000;
color: #5dff6b;
font-family: 'Courier New', monospace;
font-size: 15px;
}
.screen {
max-width: 900px;
margin: 20px auto;
padding: 20px;
min-height: calc(100vh - 40px);
border-radius: 40px;
background:
repeating-linear-gradient(0deg, rgba(0,255,60,0.07) 0px, rgba(0,255,60,0.07) 1px, transparent 1px, transparent 3px),
radial-gradient(ellipse at center, #0a1f10 0%, #030a05 100%);
text-shadow: 0 0 4px #3dff55, 0 0 1px #3dff55;
position: relative;
box-shadow: 0 0 0 10px #1a1a1a, 0 0 0 14px #333, 0 0 40px rgba(93,255,107,0.5) inset, 0 10px 30px rgba(0,0,0,0.8);
overflow: hidden;
}
.screen::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: repeating-linear-gradient(0deg, rgba(0,0,0,0.15) 0px, rgba(0,0,0,0.15) 1px, transparent 1px, transparent 2px);
pointer-events: none;
animation: flicker 4s infinite;
z-index: 5;
}
.screen::after {
content: '';
position: absolute;
top: -20px; left: -20px; width: 34px; height: 34px;
background: radial-gradient(circle at 35% 35%, #888, #222 70%);
border-radius: 50%;
box-shadow: 900px 0 0 0 radial-gradient(circle,#888,#222);
}
@keyframes flicker {
0%, 96%, 100% { opacity: 1; }
97% { opacity: 0.85; }
98% { opacity: 1; }
}
.staticLines {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: repeating-linear-gradient(0deg, rgba(93,255,107,0.08) 0px, rgba(93,255,107,0.08) 1px, transparent 1px, transparent 4px);
pointer-events: none;
z-index: 7;
mix-blend-mode: screen;
animation: staticShift 0.15s steps(6) infinite;
}
@keyframes staticShift {
0% { transform: translateY(0px); opacity: 0.5; }
20% { transform: translateY(-2px); opacity: 0.8; }
40% { transform: translateY(1px); opacity: 0.4; }
60% { transform: translateY(-1px); opacity: 0.7; }
80% { transform: translateY(2px); opacity: 0.5; }
100% { transform: translateY(0px); opacity: 0.6; }
}
.staticNoise {
position: absolute;
top: -10%; left: -10%; right: -10%; bottom: -10%;
background:
repeating-linear-gradient(90deg, rgba(93,255,107,0.05) 0px, rgba(93,255,107,0.05) 1px, transparent 1px, transparent 3px),
repeating-linear-gradient(0deg, rgba(0,0,0,0.2) 0px, rgba(0,0,0,0.2) 2px, transparent 2px, transparent 5px);
pointer-events: none;
z-index: 8;
mix-blend-mode: screen;
opacity: 0.5;
animation: staticJitter 0.1s steps(4) infinite;
}
@keyframes staticJitter {
0% { transform: translate(0px,0px); }
25% { transform: translate(-3px,2px); }
50% { transform: translate(2px,-3px); }
75% { transform: translate(-2px,-2px); }
100% { transform: translate(3px,1px); }
}
.bolt {
position: absolute;
width: 16px; height: 16px;
border-radius: 50%;
background: radial-gradient(circle at 35% 35%, #999, #222 70%);
box-shadow: 0 0 4px rgba(0,0,0,0.8) inset;
z-index: 6;
}
.bolt.tl { top: 14px; left: 14px; }
.bolt.tr { top: 14px; right: 14px; }
.bolt.bl { bottom: 14px; left: 14px; }
.bolt.br { bottom: 14px; right: 14px; }
.bolt.tm { top: 14px; left: 50%; transform: translateX(-50%); }
.bolt.bm { bottom: 14px; left: 50%; transform: translateX(-50%); }
.cursor {
display: inline-block;
width: 9px; height: 16px;
background: #5dff6b;
box-shadow: 0 0 8px #5dff6b;
margin-left: 6px;
animation: blink 1s steps(1) infinite;
vertical-align: middle;
}
.rankTitle {
font-size: 12px;
letter-spacing: 2px;
margin-top: 2px;
color: #9dffb0;
}
.frame {
border: 2px solid #5dff6b;
border-radius: 14px;
padding: 14px;
box-shadow: 0 0 10px rgba(93,255,107,0.3) inset, 0 0 6px rgba(93,255,107,0.25);
position: relative;
z-index: 2;
}
.meterWrap {
margin: 10px 0 14px 0;
}
.meterLabel {
font-size: 11px;
letter-spacing: 2px;
margin-bottom: 3px;
display: flex;
justify-content: space-between;
}
.meterBar {
height: 14px;
border: 1px solid #5dff6b;
border-radius: 3px;
overflow: hidden;
background: repeating-linear-gradient(90deg, rgba(93,255,107,0.05) 0px, rgba(93,255,107,0.05) 4px, transparent 4px, transparent 8px);
}
.meterFill {
height: 100%;
background: repeating-linear-gradient(90deg, #5dff6b 0px, #5dff6b 6px, #0a1f10 6px, #0a1f10 9px);
box-shadow: 0 0 8px #5dff6b;
}
h1, h2 {
text-transform: uppercase;
letter-spacing: 2px;
margin: 6px 0;
}
h1 { text-shadow: 0 0 10px #5dff6b, 0 0 3px #5dff6b; }
h2 {
border-left: 4px solid #5dff6b;
padding-left: 8px;
font-size: 16px;
}
.card {
transition: box-shadow 0.2s, transform 0.15s;
}
.card:hover {
box-shadow: 0 0 12px rgba(93,255,107,0.4) inset, 0 0 8px rgba(93,255,107,0.3);
transform: translateX(2px);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #5dff6b;
padding-bottom: 8px;
margin-bottom: 10px;
flex-wrap: wrap;
}
.header h1 { font-size: 22px; }
.stat {
font-size: 13px;
text-align: right;
}
.tabs {
display: flex;
gap: 6px;
margin-bottom: 14px;
flex-wrap: wrap;
border-top: 1px solid rgba(93,255,107,0.5);
border-bottom: 1px solid rgba(93,255,107,0.5);
padding: 8px 4px;
position: relative;
}
.tabs::before {
content: '';
position: absolute;
top: -7px; left: 50%;
width: 10px; height: 10px;
border-radius: 50%;
background: radial-gradient(circle at 35% 35%, #9dffb0, #1a1a1a 70%);
box-shadow: 0 0 6px #5dff6b;
transform: translateX(-50%);
}
.tab {
background: transparent;
border: 1px solid #5dff6b;
color: #5dff6b;
font-family: inherit;
text-transform: uppercase;
letter-spacing: 1px;
padding: 8px 14px;
border-radius: 3px;
cursor: pointer;
font-size: 13px;
}
.tab.active {
background: #5dff6b;
color: #08130c;
box-shadow: 0 0 10px #5dff6b;
}
.panel { display: none; }
.panel.active { display: block; }
.card {
border: 1px solid #5dff6b;
border-radius: 4px;
padding: 10px 12px;
margin-bottom: 10px;
background: rgba(93,255,107,0.05);
}
.card .code {
font-weight: bold;
font-size: 14px;
}
.card .desc {
font-size: 13px;
opacity: 0.85;
margin: 4px 0;
}
.card .unit {
font-size: 11px;
opacity: 0.7;
}
.status {
font-size: 11px;
padding: 2px 8px;
border: 1px solid #5dff6b;
border-radius: 6px;
display: inline-block;
margin-top: 4px;
}
.status.done { background: #5dff6b; color: #08130c; }
button.go {
margin-top: 6px;
background: transparent;
border: 1px solid #5dff6b;
color: #5dff6b;
font-family: inherit;
padding: 6px 12px;
border-radius: 3px;
cursor: pointer;
text-transform: uppercase;
font-size: 12px;
}
button.go:hover { background: #5dff6b; color: #08130c; }
.row2 {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.row2 > .card { flex: 1; min-width: 220px; }
.bigbtn {
display: block;
width: 100%;
text-align: left;
background: rgba(93,255,107,0.08);
border: 1px solid #5dff6b;
color: #5dff6b;
font-family: inherit;
padding: 14px;
border-radius: 3px;
margin-bottom: 10px;
cursor: pointer;
font-size: 15px;
text-transform: uppercase;
letter-spacing: 1px;
}
.bigbtn:hover { background: #5dff6b; color: #08130c; }
.appTag {
font-size: 10px;
padding: 1px 6px;
border: 1px solid #5dff6b;
border-radius: 5px;
margin-left: 6px;
}
.footer {
text-align: center;
font-size: 11px;
opacity: 0.6;
margin-top: 14px;
}
.pbLogo {
font-size: 10px;
letter-spacing: 3px;
opacity: 0.7;
text-align: center;
margin-bottom: 4px;
}
.limbBox {
display: flex;
justify-content: space-between;
gap: 6px;
margin: 10px 0 14px 0;
flex-wrap: wrap;
}
.limb {
flex: 1;
min-width: 70px;
border: 1px solid #5dff6b;
border-radius: 3px;
padding: 4px 6px;
font-size: 10px;
text-align: center;
background: rgba(93,255,107,0.05);
}
.limb .pct { font-size: 12px; font-weight: bold; }
.blink {
animation: blink 1.5s infinite;
}
@keyframes blink { 50% { opacity: 0.3; } }
.radarBox {
position: relative;
width: 260px;
height: 260px;
margin: 20px auto;
border: 2px solid #5dff6b;
border-radius: 50%;
background: radial-gradient(circle, rgba(93,255,107,0.08), transparent 70%);
overflow: hidden;
}
.radarRing {
position: absolute;
border: 1px solid rgba(93,255,107,0.4);
border-radius: 50%;
top: 50%; left: 50%;
transform: translate(-50%,-50%);
}
.radarRing.r1 { width: 60%; height: 60%; }
.radarRing.r2 { width: 90%; height: 90%; }
.radarRing.r3 { width: 30%; height: 30%; }
.radarSweep {
position: absolute;
top: 50%; left: 50%;
width: 50%; height: 2px;
background: linear-gradient(90deg, #5dff6b, transparent);
transform-origin: left center;
animation: sweep 3s linear infinite;
box-shadow: 0 0 8px #5dff6b;
}
@keyframes sweep { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
.radarDot {
position: absolute;
width: 8px; height: 8px;
background: #5dff6b;
border-radius: 50%;
box-shadow: 0 0 6px #5dff6b;
transform: translate(-50%,-50%);
animation: radarPing 1.6s ease-out infinite;
}
.radarDot::after {
content: '';
position: absolute;
top: 50%; left: 50%;
width: 8px; height: 8px;
border: 1px solid #5dff6b;
border-radius: 50%;
transform: translate(-50%,-50%);
animation: radarPingRing 1.6s ease-out infinite;
}
@keyframes radarPing {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
@keyframes radarPingRing {
0% { width: 8px; height: 8px; opacity: 0.8; }
100% { width: 34px; height: 34px; opacity: 0; }
}
.specialBox {
border: 1px solid #5dff6b;
border-radius: 4px;
padding: 10px 12px;
margin-bottom: 12px;
background: rgba(93,255,107,0.05);
}
.specialTitle {
font-size: 13px;
letter-spacing: 2px;
margin-bottom: 8px;
text-align: center;
}
.specialRow {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
margin-bottom: 6px;
}
.specialRow span { width: 90px; flex-shrink: 0; }
.specialBar {
flex: 1;
height: 10px;
border: 1px solid #5dff6b;
border-radius: 3px;
overflow: hidden;
background: rgba(93,255,107,0.05);
}
.specialFill {
height: 100%;
background: #5dff6b;
box-shadow: 0 0 6px #5dff6b;
}
</style>
</head>
<body>
<div id="bootScreen" style="position:fixed; top:0; left:0; right:0; bottom:0; background:#000; color:#5dff6b; font-family:'Courier New', monospace; z-index:999; display:flex; flex-direction:column; align-items:center; justify-content:center; text-shadow:0 0 4px #3dff55;">
<div style="font-size:20px; letter-spacing:3px; margin-bottom:20px;">PIP-BOY 493 STARTING UP...</div>
<div style="width:340px; height:26px; border:2px solid #5dff6b; border-radius:2px; overflow:hidden; box-shadow:0 0 10px rgba(93,255,107,0.5); padding:3px; display:flex; gap:2px;" id="bootBlocksWrap">
</div>
<div id="bootPct" style="margin-top:10px; font-size:13px; letter-spacing:2px;">0%</div>
<div style="margin-top:6px; font-size:11px; opacity:0.8; letter-spacing:1px;">LOADING SUBSYSTEMS...</div>
<div style="margin-top:16px; font-size:11px; opacity:0.7; letter-spacing:1px;">VAULT-TEC INDUSTRIES (C) 2287</div>
</div>
<div class="screen">
<div class="staticLines"></div>
<div class="staticNoise"></div>
<div class="bolt tm"></div>
<div class="bolt bm"></div>
<div class="frame">
<div class="header">
<h1>★ 493 Pip-Boy ★<span class="cursor"></span><div class="pbLogo">R O B C O I N D U S T R I E S (TM) U N I F I E D O P E R A T I N G S Y S T E M</div></h1>
<div class="stat">
<div id="name">VAULT DWELLER: ...</div>
<div id="today">DATE: ...</div>
<div id="points">CAPS: ...</div>
<div class="rankTitle" id="rankTitle">RANK: ...</div>
</div>
</div>
<div class="tabs">
<button class="tab active" data-tab="lessons">STAT</button>
<button class="tab" data-tab="math">MATH</button>
<button class="tab" data-tab="chores">CHORES</button>
<button class="tab" data-tab="inv">INV</button>
<button class="tab" data-tab="projects">DATA</button>
<button class="tab" data-tab="map">MAP</button>
<button class="tab" data-tab="more">RADIO</button>
</div>
<div class="panel active" id="panel-lessons">
<h2>Status - Today's Lessons</h2>
<div class="meterWrap">
<div class="meterLabel"><span>HEALTH</span><span>100%</span></div>
<div class="meterBar"><div class="meterFill" style="width:100%"></div></div>
</div>
<div class="limbBox">
<div class="limb">HEAD<div class="pct">100%</div></div>
<div class="limb">TORSO<div class="pct">100%</div></div>
<div class="limb">L.ARM<div class="pct">100%</div></div>
<div class="limb">R.ARM<div class="pct">100%</div></div>
<div class="limb">L.LEG<div class="pct">100%</div></div>
<div class="limb">R.LEG<div class="pct">100%</div></div>
</div>
<div class="meterWrap">
<div class="meterLabel"><span>XP / CAPS</span><span id="xpTxt">0</span></div>
<div class="meterBar"><div class="meterFill" id="xpFill" style="width:0%; background: repeating-linear-gradient(90deg, #ffe45d 0px, #ffe45d 6px, #0a1f10 6px, #0a1f10 9px);"></div></div>
</div>
<div class="meterWrap">
<div class="meterLabel"><span>RAD LEVEL</span><span>LOW</span></div>
<div class="meterBar"><div class="meterFill" style="width:12%; background: repeating-linear-gradient(90deg, #9dffb0 0px, #9dffb0 6px, #0a1f10 6px, #0a1f10 9px);"></div></div>
</div>
<div class="meterWrap">
<div class="meterLabel"><span>CLOCK</span><span id="clockReadout">00:00:00</span></div>
</div>
<div class="meterWrap">
<div class="meterLabel"><span>BATTERY</span><span id="batteryPct">100%</span></div>
<div class="meterBar"><div class="meterFill" id="batteryFill" style="width:100%"></div></div>
</div>
<div class="meterWrap">
<div class="meterLabel"><span>RAD NEEDLE</span><span id="radNeedleTxt">0 rads</span></div>
<div class="meterBar"><div class="meterFill" id="radFill" style="width:5%; background: repeating-linear-gradient(90deg, #ffdf5d 0px, #ffdf5d 6px, #0a1f10 6px, #0a1f10 9px);"></div></div>
</div>
<div class="specialBox">
<div class="specialTitle">S.P.E.C.I.A.L.</div>
<div class="specialRow"><span>STRENGTH</span><div class="specialBar"><div class="specialFill" style="width:70%"></div></div></div>
<div class="specialRow"><span>PERCEPTION</span><div class="specialBar"><div class="specialFill" style="width:60%"></div></div></div>
<div class="specialRow"><span>ENDURANCE</span><div class="specialBar"><div class="specialFill" style="width:80%"></div></div></div>
<div class="specialRow"><span>CHARISMA</span><div class="specialBar"><div class="specialFill" id="charismaFill" style="width:50%"></div></div></div>
<div class="specialRow"><span>INTELLIGENCE</span><div class="specialBar"><div class="specialFill" id="intFill" style="width:50%"></div></div></div>
<div class="specialRow"><span>AGILITY</span><div class="specialBar"><div class="specialFill" style="width:65%"></div></div></div>
<div class="specialRow"><span>LUCK</span><div class="specialBar"><div class="specialFill" style="width:55%"></div></div></div>
</div>
<div class="card" id="sysMsgCard"><div class="code">SYSTEM LOG</div><div class="desc" id="sysMsg">Booting up...</div></div>
<div id="lessonsList">Loading...</div>
</div>
<div class="panel" id="panel-math">
<h2>Math Chapters</h2>
<div id="mathList">Loading...</div>
</div>
<div class="panel" id="panel-chores">
<h2>Chores - Daily Duties</h2>
<div class="card"><div class="code">STUDIO WALLET</div><div class="desc" id="choreWallet">Scanning duty roster...</div></div>
<div id="choresList">Loading...</div>
<button class="bigbtn" id="choresPageBtn">OPEN CHORE TERMINAL</button>
</div>
<div class="panel" id="panel-projects">
<h2>Data - Projects</h2>
<div id="projectsList">Loading...</div>
<button class="bigbtn" id="newProjectBtn">+ New Project</button>
<div class="card"><div class="code">ARCADE TERMINAL</div><div class="desc">Play the Multiplication Game.</div><button class="go" id="gamesBtn2">PLAY GAME ></button></div>
</div>
<div class="panel" id="panel-inv">
<h2>Inventory - My Apps</h2>
<div id="appsList">Loading...</div>
</div>
<div class="panel" id="panel-map">
<h2>Map - Local Area</h2>
<div class="radarBox">
<div class="radarSweep"></div>
<div class="radarRing r1"></div>
<div class="radarRing r2"></div>
<div class="radarRing r3"></div>
<div class="radarDot" style="top:30%; left:60%;" title="Studio"></div>
<div class="radarDot" style="top:65%; left:35%;" title="Projects"></div>
<div class="radarDot" style="top:50%; left:50%;" title="You"></div>
</div>
<div class="desc" style="text-align:center; margin-top:8px;">Signal detected: 493 Academy Territory</div>
<div id="compass" style="text-align:center; font-size:22px; letter-spacing:4px; margin-top:10px;">N 000°</div>
<div class="desc" style="text-align:center; margin-top:6px;" id="mapBlip">. tracking signal .</div>
<div class="card" style="margin-top:12px;"><div class="code">WASTELAND EVENT</div><div class="desc" id="eventTxt">All quiet nearby...</div></div>
</div>
<div class="panel" id="panel-more">
<h2>Radio - Signal List</h2>
<div class="card"><div class="code">GALAXY NEWS RADIO</div><div class="desc">Tune to Studio to build new apps.</div><button class="go" id="studioBtn">TUNE IN ></button></div>
<div class="card"><div class="code">CLASSICAL RADIO</div><div class="desc">Play the Multiplication Game.</div><button class="go" id="gamesBtn">TUNE IN ></button></div>
<div class="card"><div class="code">ENCLAVE FREQUENCY</div><div class="desc">Browse All Apps.</div><button class="go" id="appsBtn">TUNE IN ></button></div>
<div class="card"><div class="code">EMERGENCY BROADCAST</div><div class="desc">Go to Classic Home view.</div><button class="go" id="classicBtn">TUNE IN ></button></div>
<div class="card"><div class="code">WASTELAND WEATHER</div><div class="desc" id="weatherTxt">Scanning skies...</div></div>
<div class="desc" id="staticTxt" style="text-align:center; letter-spacing:2px; margin-top:10px;">*/-- ...signal... --/*</div>
</div>
<div class="footer blink">-- SIGNAL STABLE --</div>
</div>
</div>
<script>
let ctx = null;
const weatherFrames = [
'Clear skies over the vault.',
'Light rad-storm rolling in.',
'Gentle breeze, no danger.',
'Dust clouds on the horizon.',
'Sunny with a chance of mole rats.'
];
let weatherIdx = 0;
setInterval(() => {
const el = document.getElementById('weatherTxt');
if (!el) return;
weatherIdx = (weatherIdx + 1) % weatherFrames.length;
el.textContent = weatherFrames[weatherIdx];
}, 5000);
const eventFrames = [
'All quiet nearby...',
'Friendly trader spotted west.',
'Old radio tower found north.',
'A stray robot beeps in the distance.',
'Supplies cache nearby!'
];
let eventIdx = 0;
setInterval(() => {
const el = document.getElementById('eventTxt');
if (!el) return;
eventIdx = (eventIdx + 1) % eventFrames.length;
el.textContent = eventFrames[eventIdx];
}, 4500);
setInterval(() => {
const bars = document.querySelectorAll('.specialFill');
bars.forEach(b => {
const cur = parseFloat(b.style.width) || 50;
const next = Math.max(30, Math.min(95, cur + (Math.random() * 10 - 5)));
b.style.width = next + '%';
});
}, 3000);
document.querySelectorAll('.tab').forEach(t => {
t.addEventListener('click', () => {
const scr = document.querySelector('.screen');
if (scr) {
scr.style.opacity = '0.6';
setTimeout(() => { scr.style.opacity = '1'; }, 90);
}
});
});
function nav(url) {
if (!url) return;
parent.postMessage({ type: 'academy:navigate', to: url }, '*');
}
function startLesson(id) {
parent.postMessage({ type: 'academy:startLesson', standardId: id }, '*');
}
document.querySelectorAll('.tab').forEach(t => {
t.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(x => x.classList.remove('active'));
document.querySelectorAll('.panel').forEach(x => x.classList.remove('active'));
t.classList.add('active');
document.getElementById('panel-' + t.dataset.tab).classList.add('active');
});
});
document.getElementById('studioBtn').addEventListener('click', () => nav(ctx && ctx.links.studio));
document.getElementById('gamesBtn').addEventListener('click', () => nav(ctx && ctx.links.multiplication));
document.getElementById('appsBtn').addEventListener('click', () => nav(ctx && ctx.links.apps));
document.getElementById('newProjectBtn').addEventListener('click', () => nav(ctx && ctx.links.newProject));
document.getElementById('choresPageBtn').addEventListener('click', () => nav(ctx && ctx.links.chores));
document.getElementById('classicBtn').addEventListener('click', () => nav(ctx && ctx.links.classicHome));
document.getElementById('gamesBtn2').addEventListener('click', () => nav(ctx && ctx.links.multiplication));
function render(context) {
ctx = context;
document.getElementById('name').textContent = 'VAULT DWELLER: ' + context.student.name.toUpperCase();
document.getElementById('today').textContent = 'DATE: ' + context.today;
document.getElementById('points').textContent = 'CAPS: ' + context.points;
const xpFill = document.getElementById('xpFill');
const xpTxt = document.getElementById('xpTxt');
if (xpFill && xpTxt) {
const pct = Math.max(2, Math.min(100, context.points % 500 / 5));
xpFill.style.width = pct + '%';
xpTxt.textContent = context.points + ' CAPS';
}
const rankEl = document.getElementById('rankTitle');
if (rankEl) {
let rank = 'ROOKIE';
if (context.points >= 1000) rank = 'OVERSEER';
else if (context.points >= 500) rank = 'VAULT HERO';
else if (context.points >= 200) rank = 'WASTELANDER';
else if (context.points >= 50) rank = 'SCOUT';
rankEl.textContent = 'RANK: ' + rank;
}
const lessonsList = document.getElementById('lessonsList');
lessonsList.innerHTML = '';
if (context.lessons.length === 0) {
lessonsList.innerHTML = '<div class="card">No lessons today. Enjoy the wasteland!</div>';
}
context.lessons.forEach(l => {
const div = document.createElement('div');
div.className = 'card';
div.innerHTML = '<div class="code">' + l.subject + ' · ' + l.code + '</div>' +
'<div class="desc">' + l.description + '</div>';
const btn = document.createElement('button');
btn.className = 'go';
btn.textContent = 'START LESSON >';
btn.addEventListener('click', () => startLesson(l.standardId));
div.appendChild(btn);
lessonsList.appendChild(div);
});
const mathList = document.getElementById('mathList');
mathList.innerHTML = '';
let lastUnit = '';
context.math.forEach(m => {
if (m.unit !== lastUnit) {
const h = document.createElement('div');
h.style.margin = '10px 0 4px 0';
h.style.fontSize = '12px';
h.style.opacity = '0.7';
h.textContent = m.unit;
mathList.appendChild(h);
lastUnit = m.unit;
}
const div = document.createElement('div');
div.className = 'card';
const doneTag = m.status === 'completed' ? '<span class="status done">DONE</span>' : '<span class="status">PENDING</span>';
div.innerHTML = '<div class="code">' + m.code + ' ' + doneTag + '</div>' +
'<div class="desc">' + m.description + '</div>';
mathList.appendChild(div);
});
const choresList = document.getElementById('choresList');
const choreWallet = document.getElementById('choreWallet');
if (choresList) {
const ch = context.chores;
if (choreWallet) choreWallet.textContent = ch
? '$' + (ch.walletUsd || 0).toFixed(2) + ' saved for Studio // $' + (ch.earnedTodayUsd || 0).toFixed(2) + ' earned today'
: 'Duty roster offline.';
choresList.innerHTML = '';
if (!ch || !ch.menu || ch.menu.length === 0) {
choresList.innerHTML = '<div class="card">No chores posted.</div>';
} else {
ch.menu.forEach(m => {
const div = document.createElement('div');
div.className = 'card';
const doneTag = m.doneToday ? '<span class="status done">DONE</span>' : '<span class="status">PENDING</span>';
div.innerHTML = '<div class="code">' + m.title.toUpperCase() + ' ' + doneTag + '</div>' +
'<div class="desc">Reward: $' + m.amountUsd.toFixed(2) + '</div>';
const btn = document.createElement('button');
btn.className = 'go';
btn.textContent = m.doneToday ? 'UNDO >' : 'MARK DONE >';
btn.addEventListener('click', () => {
parent.postMessage({ type: m.doneToday ? 'academy:undoChore' : 'academy:completeChore', choreId: m.choreId }, '*');
});
div.appendChild(btn);
choresList.appendChild(div);
});
}
}
const projectsList = document.getElementById('projectsList');
projectsList.innerHTML = '';
if (context.projects.length === 0) {
projectsList.innerHTML = '<div class="card">No projects yet.</div>';
}
context.projects.forEach(p => {
const div = document.createElement('div');
div.className = 'card';
div.innerHTML = '<div class="code">' + p.title.toUpperCase() + '</div>' +
'<div class="desc">' + p.description + '</div>' +
'<div class="unit">Entries: ' + p.entryCount + '</div>';
const btn = document.createElement('button');
btn.className = 'go';
btn.textContent = 'OPEN >';
btn.addEventListener('click', () => nav(p.url));
div.appendChild(btn);
projectsList.appendChild(div);
});
const appsList = document.getElementById('appsList');
appsList.innerHTML = '';
context.apps.forEach(a => {
const div = document.createElement('div');
div.className = 'card';
const tag = a.published ? '<span class="appTag">LIVE</span>' : '<span class="appTag">DRAFT</span>';
div.innerHTML = '<div class="code">' + (a.title || 'Untitled') + tag + '</div>';
const btn = document.createElement('button');
btn.className = 'go';
btn.textContent = a.published ? 'PLAY >' : 'EDIT >';
btn.addEventListener('click', () => nav(a.published ? a.playUrl : a.editUrl));
div.appendChild(btn);
appsList.appendChild(div);
});
}
window.addEventListener('message', (e) => {
if (e.data && e.data.type === 'academy:context') render(e.data.context);
});
parent.postMessage({ type: 'academy:ready' }, '*');
const sysMessages = [
'All systems nominal.',
'Detecting nearby lessons...',
'Math module ready.',
'Inventory synced.',
'Radiation levels stable.',
'Data logs updated.',
'Searching for new signals...',
'Vault-Tec approved.'
];
let sysIdx = 0;
setInterval(() => {
const el = document.getElementById('sysMsg');
if (!el) return;
sysIdx = (sysIdx + 1) % sysMessages.length;
el.textContent = sysMessages[sysIdx];
}, 3000);
const staticFrames = ['*/-- ...signal... --/*','#~~ st4t1c ~~#','.. searching .. searching ..','//== signal lost ==//','*/-- ...signal... --/*'];
let staticIdx = 0;
setInterval(() => {
const el = document.getElementById('staticTxt');
if (!el) return;
staticIdx = (staticIdx + 1) % staticFrames.length;
el.textContent = staticFrames[staticIdx];
}, 900);
const blipFrames = ['. tracking signal .','.. tracking signal ..','... tracking signal ...','.. tracking signal ..'];
let blipIdx = 0;
setInterval(() => {
const el = document.getElementById('mapBlip');
if (!el) return;
blipIdx = (blipIdx + 1) % blipFrames.length;
el.textContent = blipFrames[blipIdx];
}, 500);
let battLevel = 100;
setInterval(() => {
const fill = document.getElementById('batteryFill');
const pct = document.getElementById('batteryPct');
if (!fill || !pct) return;
battLevel -= 1;
if (battLevel < 20) battLevel = 100;
fill.style.width = battLevel + '%';
pct.textContent = battLevel + '%';
}, 2000);
const dirs = ['N','NNE','NE','ENE','E','ESE','SE','SSE','S','SSW','SW','WSW','W','WNW','NW','NNW'];
let heading = 0;
setInterval(() => {
const compassEl = document.getElementById('compass');
if (!compassEl) return;
heading = (heading + 7) % 360;
const dir = dirs[Math.round(heading / 22.5) % 16];
compassEl.textContent = dir + ' ' + String(heading).padStart(3,'0') + '\u00b0';
compassEl.style.textShadow = '0 0 16px #9dffb0, 0 0 4px #9dffb0';
setTimeout(() => { compassEl.style.textShadow = ''; }, 150);
}, 400);
const screenEl = document.querySelector('.screen');
if (screenEl) {
screenEl.style.opacity = '0';
}
let bootPct = 0;
const TOTAL_BLOCKS = 34;
const bootBlocksWrap = document.getElementById('bootBlocksWrap');
if (bootBlocksWrap) {
for (let i = 0; i < TOTAL_BLOCKS; i++) {
const b = document.createElement('div');
b.style.flex = '1';
b.style.background = '#0a1f10';
b.style.transition = 'background 0.1s';
bootBlocksWrap.appendChild(b);
}
}
let litBlocks = 0;
const bootPctEl = document.getElementById('bootPct');
const bootScreenEl = document.getElementById('bootScreen');
const bootInterval = setInterval(() => {
bootPct += Math.floor(Math.random() * 8) + 4;
if (bootPct >= 100) bootPct = 100;
const targetBlocks = Math.round((bootPct / 100) * TOTAL_BLOCKS);
if (bootBlocksWrap) {
for (let i = litBlocks; i < targetBlocks; i++) {
const block = bootBlocksWrap.children[i];
if (block) {
block.style.background = '#5dff6b';
block.style.boxShadow = '0 0 6px #5dff6b';
}
}
litBlocks = targetBlocks;
}
if (bootPctEl) bootPctEl.textContent = bootPct + '%';
if (bootPct >= 100) {
clearInterval(bootInterval);
setTimeout(() => {
if (bootScreenEl) bootScreenEl.style.display = 'none';
if (screenEl) {
screenEl.style.transition = 'opacity 0.15s';
screenEl.style.opacity = '1';
let flickers = 0;
const bootFlicker = setInterval(() => {
screenEl.style.opacity = screenEl.style.opacity === '0.2' ? '1' : '0.2';
flickers++;
if (flickers > 6) {
clearInterval(bootFlicker);
screenEl.style.opacity = '1';
}
}, 80);
}
}, 300);
}
}, 200);
</script>
</body>
</html>