The Next Letter Lab
by PhantomViper73575 lines20.1 KB🛠️ Three.js (3D graphics)
title: The Next Letter Lab
description: Enter a futuristic 3D AI laboratory and discover how artificial intelligence predicts and samples the next letter using probability.
libraries: three
emoji: 🧠
color: purple
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>The Next Letter Lab</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Orbitron:wght@500;700;800&display=swap');
:root {
--bg:#07091b;
--blue:#48baff;
--purple:#a47bff;
--gold:#ffd36b;
--white:#f3f5ff;
}
* {box-sizing:border-box}
body {
margin:0; background:var(--bg); color:var(--white);
font-family:'Space Grotesk',sans-serif;
overflow-x:hidden; min-height:100vh;
}
#scene {
position:fixed; inset:0; z-index:0; pointer-events:none;
}
#scene canvas {width:100%;height:100%;display:block}
.overlay {
position:relative; z-index:2; min-height:100vh;
display:flex; align-items:center; justify-content:center;
padding:24px;
}
.screen {display:none;width:100%;max-width:1100px;animation:appear .7s ease}
.screen.active {display:block}
@keyframes appear {
from {opacity:0;transform:translateY(18px)}
to {opacity:1;transform:translateY(0)}
}
h1,h2,h3,p {margin-top:0}
h1 {
font-family:Orbitron,sans-serif;
font-size:clamp(35px,7vw,76px);
line-height:1.12; margin-bottom:18px;
background:linear-gradient(90deg,#fff,#70caff,#b78cff);
-webkit-background-clip:text;color:transparent;
}
h2 {font-size:clamp(26px,4vw,42px)}
h3 {font-size:18px}
p {color:#b5c2df;line-height:1.7}
.eyebrow {
color:var(--gold);letter-spacing:4px;
font-size:12px;font-weight:700;margin-bottom:18px;
}
.hero {text-align:center;max-width:850px;margin:auto}
.hero p {max-width:620px;margin:0 auto 30px;font-size:17px}
.orb {
width:150px;height:150px;margin:0 auto 28px;
border-radius:50%;
background:radial-gradient(circle at 35% 30%,#b9e9ff,#6d63ff 35%,#1b1c69 70%);
box-shadow:0 0 35px #6a83ff,0 0 100px #6a4aff77;
animation:float 4s ease-in-out infinite;
display:grid;place-items:center;font-size:68px;
}
@keyframes float {
0%,100%{transform:translateY(0) rotate(-5deg)}
50%{transform:translateY(-16px) rotate(5deg)}
}
.btn {
border:0;border-radius:13px;padding:15px 27px;
font:700 15px 'Space Grotesk';cursor:pointer;
color:#071020;background:linear-gradient(110deg,#6bd3ff,#a58aff);
box-shadow:0 5px 25px #5a8cff44;
transition:.2s;
}
.btn:hover {transform:translateY(-3px);box-shadow:0 8px 30px #8b72ff77}
.btn.secondary {
color:white;background:#ffffff0c;border:1px solid #ffffff30;
box-shadow:none;
}
.btn:disabled {opacity:.4;cursor:not-allowed;transform:none}
.pill {
display:inline-block;border:1px solid #8b8aff66;
padding:8px 14px;border-radius:30px;color:#bcbaff;
font-size:12px;margin:5px;
}
.instruction-grid {
display:grid;grid-template-columns:repeat(3,1fr);
gap:15px;margin:30px 0;
}
.instruction {
background:#111832cc;border:1px solid #8d9cff30;
padding:22px;border-radius:18px;
}
.instruction .num {font-size:32px;margin-bottom:12px}
.instruction p {font-size:14px;margin:0}
.game-header {
display:flex;justify-content:space-between;align-items:center;
flex-wrap:wrap;gap:12px;margin-bottom:20px;
}
.logo {font-family:Orbitron;font-weight:700;font-size:16px;color:#a9dfff}
.stats {display:flex;gap:10px;flex-wrap:wrap}
.stat {
background:#111a35;border:1px solid #ffffff1c;
border-radius:12px;padding:10px 15px;font-size:13px;
}
.stat strong {color:var(--gold);font-size:17px;margin-left:6px}
.panel {
background:linear-gradient(140deg,#131b39eF,#0b1029f2);
border:1px solid #849bff30;border-radius:20px;
padding:22px;box-shadow:0 15px 60px #0005;
}
.game-grid {
display:grid;grid-template-columns:1fr 1fr;
gap:18px;
}
.center {text-align:center}
.prompt-label {color:#93a8d2;font-size:12px;letter-spacing:2px}
.word {
font-family:Orbitron;font-size:clamp(38px,7vw,65px);
color:white;letter-spacing:7px;margin:12px 0;
text-shadow:0 0 25px #60c8ff99;
}
.word span {color:var(--gold)}
.choices {
display:grid;grid-template-columns:repeat(4,1fr);
gap:10px;margin-top:20px;
}
.choice {
padding:17px 5px;border-radius:14px;
border:1px solid #748bce55;background:#182345;
color:white;font:700 25px Orbitron;cursor:pointer;
transition:.2s;
}
.choice:hover {border-color:#65d5ff;transform:translateY(-4px);background:#263967}
.choice.selected {border:2px solid var(--gold);background:#493b23}
.choice:disabled {cursor:default}
.prob-row {margin:17px 0}
.prob-info {display:flex;justify-content:space-between;font-size:13px;margin-bottom:7px}
.prob-info strong {color:#fff}
.bar {
height:12px;background:#ffffff12;border-radius:20px;overflow:hidden;
}
.bar-fill {
width:0;height:100%;border-radius:20px;
background:linear-gradient(90deg,#3aaeff,#aa80ff);
box-shadow:0 0 15px #8a75ff;
transition:width 1s cubic-bezier(.2,.8,.2,1);
}
.result {
min-height:65px;text-align:center;margin-top:18px;
color:#c6d4f3;font-size:14px;line-height:1.6;
}
.result strong {color:var(--gold)}
#feedback {margin-top:12px;font-size:15px;font-weight:600}
#nextBtn {margin-top:12px;display:none}
#sampleBtn {margin-top:15px}
.explain {
background:#ffffff08;border-left:3px solid var(--blue);
padding:12px 15px;border-radius:8px;
font-size:13px;color:#c6d2eb;margin-top:16px;line-height:1.6;
}
#roundInfo {color:#92b9e9;font-size:12px;letter-spacing:2px}
.footer {text-align:center;color:#69799d;font-size:11px;margin-top:22px}
@media(max-width:700px) {
.instruction-grid {grid-template-columns:1fr}
.game-grid {grid-template-columns:1fr}
.overlay {padding:15px}
.panel {padding:17px}
.choices {gap:6px}
.choice {font-size:21px;padding:15px 3px}
}
</style>
</head>
<body>
<div id="scene"></div>
<div class="overlay">
<!-- SCREEN 1: LANDING -->
<section id="landing" class="screen active">
<div class="hero">
<div class="orb">🧠</div>
<div class="eyebrow">ARTIFICIAL INTELLIGENCE • PROBABILITY • LANGUAGE</div>
<h1>THE NEXT<br>LETTER LAB</h1>
<p>Step inside an AI prediction engine. Explore how artificial intelligence calculates possibilities and chooses the next letter, one prediction at a time.</p>
<div>
<span class="pill">✦ 3D AI LAB</span>
<span class="pill">✦ LIVE PROBABILITY</span>
<span class="pill">✦ INTERACTIVE CHALLENGE</span>
</div>
<br><br>
<button class="btn" onclick="showScreen('instructions')">ENTER THE LAB →</button>
</div>
</section>
<!-- SCREEN 2: INSTRUCTIONS -->
<section id="instructions" class="screen">
<div class="center">
<div class="eyebrow">MISSION BRIEFING</div>
<h2>How does AI choose its next letter?</h2>
<p>Imagine an AI is writing a word. It looks at the letters already written and calculates what might come next.</p>
</div>
<div class="instruction-grid">
<div class="instruction">
<div class="num">🔍</div>
<h3>01. Analyse</h3>
<p>AI looks at the letters already written and considers possible next letters.</p>
</div>
<div class="instruction">
<div class="num">📊</div>
<h3>02. Calculate</h3>
<p>It assigns a probability to each possible letter. Higher probability means more likely.</p>
</div>
<div class="instruction">
<div class="num">⚡</div>
<h3>03. Predict</h3>
<p>You make a prediction. Then the AI samples a letter using its probability distribution.</p>
</div>
</div>
<div class="panel center">
<h3>Remember this!</h3>
<p>A 60% probability does not guarantee that a letter will be selected. A 5% possibility can still happen!</p>
<button class="btn" onclick="startGame()">START THE CHALLENGE →</button>
<br><br>
<button class="btn secondary" onclick="showScreen('landing')">← BACK</button>
</div>
</section>
<!-- SCREEN 3: GAME -->
<section id="game" class="screen">
<div class="game-header">
<div class="logo">✦ NEXT LETTER LAB</div>
<div class="stats">
<div class="stat">SCORE <strong id="score">0</strong></div>
<div class="stat">STREAK <strong id="streak">0</strong> 🔥</div>
<div class="stat">ROUND <strong id="round">1/5</strong></div>
</div>
</div>
<div class="game-grid">
<div class="panel center">
<div class="eyebrow">AI GENERATION ENGINE</div>
<div id="roundInfo">ROUND 01</div>
<p class="prompt-label">THE AI HAS ALREADY WRITTEN</p>
<div class="word" id="currentWord">CA<span>_</span></div>
<p>Which letter will the AI select next?</p>
<div class="choices" id="choices"></div>
<button class="btn" id="sampleBtn" onclick="sampleAI()" disabled>⚡ RUN AI SAMPLING</button>
<div class="result" id="feedback">Choose the letter you think AI will select.</div>
<button class="btn" id="nextBtn" onclick="nextRound()">NEXT CHALLENGE →</button>
</div>
<div class="panel">
<div class="eyebrow">LIVE PROBABILITY DISTRIBUTION</div>
<h3>What is the AI considering?</h3>
<p style="font-size:12px">These percentages represent the model's estimated probabilities.</p>
<div id="probabilities"></div>
<div class="explain" id="explanation">
The AI considers multiple possibilities. The percentages show how likely each letter is according to this model.
</div>
<div class="center" style="margin-top:20px">
<div class="prompt-label">AI DECISION PROCESS</div>
<div id="aiStatus" style="font-size:14px;color:#80d4ff;margin-top:9px">
Waiting for your prediction...
</div>
</div>
</div>
</div>
<div class="footer">THE NEXT LETTER LAB • LEARN HOW PROBABILITY POWERS LANGUAGE MODELS</div>
</section>
<!-- SCREEN 4: RESULTS -->
<section id="results" class="screen">
<div class="hero">
<div class="orb">🏆</div>
<div class="eyebrow">MISSION COMPLETE</div>
<h1>YOU CRACKED<br>THE CODE!</h1>
<h2 id="finalScore">0 / 5</h2>
<p id="finalMessage">You explored how probability helps AI generate language.</p>
<div class="panel">
<h3>What you discovered</h3>
<p>AI calculates probabilities for possible next letters and samples an outcome. It does not always choose the letter with the highest probability.</p>
<p id="accuracy"></p>
</div>
<br>
<button class="btn" onclick="startGame()">PLAY AGAIN ↻</button>
<button class="btn secondary" onclick="showScreen('landing')">HOME</button>
</div>
</section>
</div>
<script>
// ------------------------------------
// 1. THREE.JS 3D ENVIRONMENT
// ------------------------------------
let scene, camera, renderer, core, coreWire, letterObjects=[];
let particles=[], beams=[];
let mouseX=0, mouseY=0;
let clock;
function makeLabel(letter) {
const c=document.createElement('canvas');
c.width=256;c.height=256;
const ctx=c.getContext('2d');
ctx.fillStyle='#17295a';
ctx.beginPath();
ctx.roundRect(15,15,226,226,32);
ctx.fill();
ctx.strokeStyle='#70cfff';
ctx.lineWidth=7;
ctx.stroke();
ctx.fillStyle='#ffffff';
ctx.font='bold 125px Arial';
ctx.textAlign='center';ctx.textBaseline='middle';
ctx.fillText(letter,128,132);
const tex=new THREE.CanvasTexture(c);
const mat=new THREE.MeshStandardMaterial({
map:tex,metalness:.5,roughness:.25,
emissive:0x203a80,emissiveIntensity:.35
});
return new THREE.Mesh(new THREE.BoxGeometry(1.2,1.2,.22),mat);
}
function init3D() {
if(!window.THREE) return;
scene=new THREE.Scene();
scene.background=new THREE.Color(0x07091b);
scene.fog=new THREE.FogExp2(0x07091b,.035);
camera=new THREE.PerspectiveCamera(55,innerWidth/innerHeight,.1,100);
camera.position.set(0,1,10);
renderer=new THREE.WebGLRenderer({antialias:true,alpha:true});
renderer.setPixelRatio(Math.min(devicePixelRatio,2));
renderer.setSize(innerWidth,innerHeight);
renderer.toneMapping=THREE.ACESFilmicToneMapping;
document.getElementById('scene').appendChild(renderer.domElement);
scene.add(new THREE.AmbientLight(0x7188ff,1.5));
const light=new THREE.PointLight(0x54caff,90,30);
light.position.set(3,4,5);scene.add(light);
const purple=new THREE.PointLight(0x8a42ff,100,30);
purple.position.set(-4,-2,4);scene.add(purple);
core=new THREE.Mesh(
new THREE.IcosahedronGeometry(1.45,2),
new THREE.MeshStandardMaterial({
color:0x5244ff,emissive:0x3428bb,
emissiveIntensity:1.2,metalness:.5,roughness:.2,
wireframe:false
})
);
scene.add(core);
coreWire=new THREE.Mesh(
new THREE.IcosahedronGeometry(1.6,1),
new THREE.MeshBasicMaterial({
color:0x5dcaff,wireframe:true,transparent:true,opacity:.5
})
);
scene.add(coreWire);
const ring=new THREE.Mesh(
new THREE.TorusGeometry(2.05,.025,8,120),
new THREE.MeshBasicMaterial({color:0x9274ff})
);
ring.rotation.x=.9;scene.add(ring);
// Starfield
const positions=[];
for(let i=0;i<1000;i++){
positions.push((Math.random()-.5)*35,(Math.random()-.5)*25,(Math.random()-.5)*25);
}
const geo=new THREE.BufferGeometry();
geo.setAttribute('position',new THREE.Float32BufferAttribute(positions,3));
const stars=new THREE.Points(geo,new THREE.PointsMaterial({
color:0x9abfff,size:.035,transparent:true,opacity:.8
}));
scene.add(stars);
// Floating particles
for(let i=0;i<150;i++){
const p=new THREE.Mesh(
new THREE.SphereGeometry(.025,6,6),
new THREE.MeshBasicMaterial({color:i%2?0x5ccfff:0xa879ff})
);
p.position.set((Math.random()-.5)*18,(Math.random()-.5)*12,(Math.random()-.5)*8);
p.userData.phase=Math.random()*6.28;
p.userData.baseY=p.position.y;
scene.add(p);particles.push(p);
}
clock=new THREE.Clock();
animate3D();
}
function clearLetters() {
letterObjects.forEach(o=>scene.remove(o));
letterObjects=[];
beams.forEach(b=>scene.remove(b));
beams=[];
}
function drawLetters(letters,probs) {
if(!scene)return;
clearLetters();
const positions=[[-3.8,1.3,0],[-1.3,2.5,-.3],[1.3,2.5,-.3],[3.8,1.3,0]];
letters.forEach((letter,i)=>{
const tile=makeLabel(letter);
const pos=positions[i%4];
tile.position.set(...pos);
const scale=.8+probs[i]*.5;
tile.scale.setScalar(scale);
tile.userData.baseY=pos[1];
tile.userData.phase=i*1.6;
tile.userData.letter=letter;
scene.add(tile);letterObjects.push(tile);
const points=[new THREE.Vector3(0,0,0),new THREE.Vector3(...pos)];
const geo=new THREE.BufferGeometry().setFromPoints(points);
const line=new THREE.Line(geo,new THREE.LineBasicMaterial({
color:i===0?0x59d4ff:0x8b6bff,
transparent:true,opacity:.3
}));
scene.add(line);beams.push(line);
});
}
function burst() {
if(!scene)return;
for(let i=0;i<65;i++){
const p=new THREE.Mesh(
new THREE.SphereGeometry(.045,6,6),
new THREE.MeshBasicMaterial({color:i%2?0x55dfff:0xffd36b})
);
p.position.set(0,0,0);
p.userData.velocity=new THREE.Vector3(
(Math.random()-.5)*.2,
(Math.random()-.5)*.2,
(Math.random()-.5)*.2
);
p.userData.life=1;
scene.add(p);particles.push(p);
}
}
function animate3D() {
requestAnimationFrame(animate3D);
const t=clock.getElapsedTime();
core.rotation.y=t*.22;core.rotation.x=Math.sin(t*.4)*.15;
core.scale.setScalar(1+Math.sin(t*2)*.035);
coreWire.rotation.y=-t*.15;coreWire.rotation.x=t*.1;
letterObjects.forEach(o=>{
o.position.y=o.userData.baseY+Math.sin(t*1.5+o.userData.phase)*.14;
o.rotation.y=Math.sin(t+o.userData.phase)*.12;
});
particles.forEach(p=>{
if(p.userData.velocity){
p.position.add(p.userData.velocity);
p.userData.life-=.018;
p.scale.setScalar(Math.max(0,p.userData.life));
if(p.userData.life<=0){scene.remove(p);p.userData.life=0;}
} else {
p.position.y=p.userData.baseY+Math.sin(t+p.userData.phase)*.12;
}
});
camera.position.x+=(mouseX*.35-camera.position.x)*.015;
camera.position.y+=(mouseY*.25-camera.position.y)*.015;
camera.lookAt(0,0,0);
renderer.render(scene,camera);
}
window.addEventListener('mousemove',e=>{
mouseX=(e.clientX/innerWidth-.5)*2;
mouseY=-(e.clientY/innerHeight-.5)*2;
});
window.addEventListener('resize',()=>{
if(camera&&renderer){
camera.aspect=innerWidth/innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth,innerHeight);
}
});
// ------------------------------------
// 2. PROBABILITY MODEL
// ------------------------------------
// This is a teaching model with illustrative,
// preconfigured probability distributions.
// It is not a trained language model.
const rounds=[
{prefix:'CA',letters:['T','R','N','P'],probs:[.60,.20,.15,.05],word:'CAT',
why:'The model assigns the highest probability to T in this example.'},
{prefix:'TH',letters:['E','A','I','O'],probs:[.55,.25,.15,.05],word:'THE',
why:'E is a common continuation of TH in this illustrative model.'},
{prefix:'BO',letters:['O','A','T','X'],probs:[.45,.30,.15,.10],word:'BOOK',
why:'O has the largest probability, but other outcomes remain possible.'},
{prefix:'SH',letters:['I','O','A','E'],probs:[.50,.25,.15,.10],word:'SHIP',
why:'I is the most probable continuation in this example.'},
{prefix:'PL',letters:['A','E','U','O'],probs:[.50,.25,.15,.10],word:'PLAY',
why:'A is most probable here, but sampling is still random.'}
];
let roundIndex=0,score=0,streak=0,guess=null,locked=false;
let totalCorrect=0,selected=null;
function showScreen(id) {
document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active'));
document.getElementById(id).classList.add('active');
}
function startGame() {
roundIndex=0;score=0;streak=0;totalCorrect=0;
updateStats();showScreen('game');loadRound();
}
function updateStats() {
document.getElementById('score').textContent=score;
document.getElementById('streak').textContent=streak;
document.getElementById('round').textContent=(roundIndex+1)+'/5';
}
function loadRound() {
locked=false;guess=null;selected=null;
const r=rounds[roundIndex];
document.getElementById('roundInfo').textContent='ROUND 0'+(roundIndex+1);
document.getElementById('currentWord').innerHTML=r.prefix+'<span>_</span>';
document.getElementById('feedback').textContent='Choose the letter you think AI will select.';
document.getElementById('aiStatus').textContent='Waiting for your prediction...';
document.getElementById('sampleBtn').disabled=true;
document.getElementById('sampleBtn').style.display='inline-block';
document.getElementById('nextBtn').style.display='none';
document.getElementById('explanation').textContent=
'Observe the probability distribution. Your job is to predict which letter the AI will actually sample.';
renderChoices(r);
renderProbabilities(r);
if(scene)drawLetters(r.letters,r.probs);
}
function renderChoices(r) {
const el=document.getElementById('choices');el.innerHTML='';
r.letters.forEach(letter=>{
const b=document.createElement('button');
b.className='choice';b.textContent=letter;
b.onclick=()=>selectGuess(letter,b);
el.appendChild(b);
});
}
function renderProbabilities(r) {
const el=document.getElementById('probabilities');el.innerHTML='';
r.letters.forEach((letter,i)=>{
const row=document.createElement('div');
row.className='prob-row';
row.innerHTML=`
<div class="prob-info"><strong>${letter}</strong>
<span>${Math.round(r.probs[i]*100)}%</span></div>
<div class="bar"><div class="bar-fill" id="bar${i}"></div></div>`;
el.appendChild(row);
setTimeout(()=>document.getElementById('bar'+i).style.width=
(r.probs[i]*100)+'%',150+i*120);
});
}
function selectGuess(letter,button) {
if(locked)return;
guess=letter;
document.querySelectorAll('.choice').forEach(b=>b.classList.remove('selected'));
button.classList.add('selected');
document.getElementById('sampleBtn').disabled=false;
document.getElementById('feedback').textContent=
'Your prediction: '+letter+'. ReadGame Source: The Next Letter Lab
Creator: PhantomViper73
Libraries: three
Complexity: complex (575 lines, 20.1 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: the-next-letter-lab-phantomviper73" to link back to the original. Then publish at arcadelab.ai/publish.