🎮ArcadeLab

The Next Letter Lab

by PhantomViper73
575 lines20.1 KB🛠️ Three.js (3D graphics)
▶ Play
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+'. Read

Game 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.