MODEL Aqwen3:8bDNF
No usable answer reached the harness (ERR). Counted as a did-not-finish.
Answers below are anonymized and shuffled. Read, judge, pick a favourite — then scroll down and reveal the models.
The reveal also holds a frontier reference on the same task (Claude Opus 5) — kept out of sight here so it cannot steer your judgement.
Create ONE self-contained HTML file (STRICT LIMIT: max 120 lines total) that animates the Sieve of Eratosthenes for the numbers 2 to 400 laid out as a grid of cells on a <canvas>. Step through the primes in order: highlight the current prime, then strike out its multiples one by one in a colour tied to that prime, so the diagonal striping patterns become visible. Show the current prime, the count of primes found and the count of cells eliminated. Smooth requestAnimationFrame animation, NO user input, starts immediately and keeps running (restart with a fresh random instance when finished). No external libraries, no comments needed. Output ONLY the HTML file content, nothing else.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sieve of Eratosthenes</title>
<style>
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<canvas id="canvas" width="400" height="50"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
function randomInstance() {
let instance = Array.apply(null, Array(401)).map(function () { return 1; });
for (let i = 0; i <= Math.floor(Math.random()*200); i++) {
instance[Math.floor(Math.random()*400)+2] = 0;
}
return instance;
}
let currentPrime = 2, primesCount = 0, eliminatedCells = 0;
function draw(instance) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 1; i <= 400; i++) {
if (instance[i]) {
ctx.fillStyle = 'black';
ctx.fillRect((i-1)%25*15+2, Math.floor(i/25)*16+2, 13, 14);
} else {
ctx.fillStyle = 'white';
ctx.fillRect((i-1)%25*15+2, Math.floor(i/25)*16+2, 13, 14);
}
}
ctx.font = "10px Arial";
ctx.fillStyle = 'black';
ctx.textAlign = "left";
ctx.fillText('Prime: ' + currentPrime, 10, canvas.height - 20);
ctx.textAlign = "right";
ctx.fillText('Primes: ' + primesCount, canvas.width - 40, canvas.height - 20);
ctx.textAlign = "center";
ctx.fillText('Eliminated: ' + eliminatedCells, canvas.width/2, canvas.height - 60);
if (currentPrime <= 400 && instance[currentPrime]) {
ctx.strokeStyle = '#' + currentPrime.toString(16).padStart(6, '0');
ctx.lineWidth = 3;
for (let multiple = currentPrime*2; multiple < 401; multiple += currentPrime) {
ctx.beginPath();
ctx.moveTo((multiple-1)%25*15+10, Math.floor(multiple/25)*16+20);
ctx.lineTo(((multiple-currentPrime-1)/currentPrime*15)+10, (Math.floor(multiple/currentPrime)-1)*16+20);
ctx.stroke();
instance[multiple] = 0;
eliminatedCells++;
}
}
if (++currentPrime > 400) {
currentPrime = 2;
primesCount++;
instance = randomInstance();
}
}
let instance = randomInstance();
draw(instance);
function animate() {
requestAnimationFrame(animate);
draw(instance);
}
animate();
</script>
</body>
</html>
``````html
<!DOCTYPE html>
<html>
<head>
<title>Sieve of Eratosthenes</title>
<style>
body { display: flex; justify-content: center; align-items: flex-start; background-color: #111; color: white; font-family: sans-serif; padding-top: 20px; }
#container { display: flex; gap: 30px; max-width: 1200px; }
canvas { border: 1px solid #333; background-color: #222; }
#info { padding: 15px; background-color: #2a2a2a; border-radius: 8px; width: 250px; }
h3 { margin-top: 0; color: #4CAF50; }
</style>
</head>
<body>
<div id="container">
<canvas id="sieveCanvas" width="700" height="400"></canvas>
<div id="info">
<h3>Sieve Status</h3>
<p>Current Prime: <span id="currentPrime">N/A</span></p>
<p>Primes Found: <span id="primeCount">0</span></p>
<p>Eliminated Cells: <span id="eliminatedCount">0</span></p>
</div>
</div>
<script>
const canvas = document.getElementById('sieveCanvas');
const ctx = canvas.getContext('2d');
const N = 400;
const COLS = Math.floor(Math.sqrt(N));
const CELL_SIZE = 35;
canvas.width = COLS * CELL_SIZE;
let isRunning = true;
let primeState = {
isSieved: new Array(N + 1).fill(false),
primes: [],
currentIndex: 0,
eliminatedCount: 0,
primeCounter: 0
};
function setupCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 2; i <= N; i++) {
const x = ((i - 2) % COLS) * CELL_SIZE + 15;
const y = Math.floor((i - 2) / COLS) * CELL_SIZE + 15;
ctx.fillStyle = '#3a3a3a';
ctx.fillRect(x, y, CELL_SIZE - 30, CELL_SIZE - 30);
}
}
function renderState() {
const state = primeState;
document.getElementById('currentPrime').textContent = state.currentIndex > 1 && state.primes[state.primes.length - 1] > 1 ? state.primes[state.primes.length - 1] : 'N/A';
document.getElementById('primeCount').textContent = state.primeCounter;
document.getElementById('eliminatedCount').textContent = state.eliminatedCount;
// Draw the grid cells based on current state (simplified drawing for performance)
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 2; i <= N; i++) {
const x = ((i - 2) % COLS) * CELL_SIZE + 15;
const y = Math.floor((i - 2) / COLS) * CELL_SIZE + 15;
if (state.isSieved[i]) {
ctx.fillStyle = '#3a3a3a';
} else if (i === state.currentIndex && i > 1) {
// Highlight current prime candidate
ctx.fillStyle = '#4CAF50';
} else if (state.primes.includes(i)) {
ctx.fillStyle = '#6b6b6b'; // Prime found color
} else if ((i % 2 === 0 && i > 2) || state.eliminated[i]) {
// Striked out/composite color (using the associated prime's color for effect)
let eliminatedBy = -1;
for(const p of state.primes) {
if (p * Math.floor(i / p) === i && p < Math.sqrt(N)) {
eliminatedBy = p;
break;
}
}
if (eliminatedBy > 0) {
ctx.fillStyle = `rgba(${Math.sin(eliminatedBy/10)*127 + 128}, ${Math.cos(eliminatedBy/10)*127 + 128}, ${eliminatedBy/5} * 4 % 255 || 50, 0.6)`;
} else {
ctx.fillStyle = '#3a3a3a'; // Default composite fill
}
} else {
ctx.fillStyle = '#3a3a3a';
}
ctx.fillRect(x, y, CELL_SIZE - 30, CELL_SIZE - 30);
}
}
function sieveStep() {
const state = primeState;
let currentPrime = state.primes[state.primeCounter];
if (currentPrime === undefined || currentPrime * currentPrime > N) {
// Sieve complete or no more candidates
clearInterval(animationId);
document.getElementById('currentPrime').textContent = 'FINISHED';
setTimeout(() => {
setupCanvas(); // Reset the canvas visual state
primeState = { isSieved: new Array(N + 1).fill(false), primes: [], currentIndex: 0, eliminatedCount: 0, primeCounter: 0 };
window.requestAnimationFrame(() => loop());
}, 2000);
return;
}
let foundNewPrime = false;
while (state.currentIndex <= N) {
const i = state.currentIndex;
if (i < 2) {
state.isSieved[i] = true; continue;
}
// Check if it has already been eliminated (Composite check)
let isEliminated = false;
for(const p of state.primes) {
if (p * Math.floor(i / p) === i && p < Math.sqrt(N)) {
isEliminated = true;
break;
}
}
if (!isEliminated) {
// This is a prime!
state.primes.push(i);
state.primeCounter++;
foundNewPrime = true;
}
// Mark it as checked for the next iteration, regardless of status
state.isSieved[i] = true;
state.currentIndex++;
}
if (foundNewPrime) {
// We only need to find primes up to sqrt(N). The actual step is finding the *next* prime candidate.
const nextPrimeCandidateIndex = state.primes[state.primeCounter - 1] + Math.floor((Math.random() * 5) + 1);
if (nextPrimeCandidateIndex > N) {
// If we jumped past N, reset the index to start checking from the last known prime's successor
state.currentIndex = state.primes[state.primeCounter - 1] + 1;
} else {
state.currentIndex = Math.max(2, state.currentIndex);
}
}
renderState();
}
let animationId;
function loop() {
if (isRunning) {
sieveStep();
animationId = window.requestAnimationFrame(loop);
}
}
// Initial setup and start
setupCanvas();
window.onload = () => {
loop();
};
</script>
</body>
</html>
``````html
<!DOCTYPE html>
<html>
<head>
<title>Sieve of Eratosthenes</title>
<style>body{display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#222;color:#eee}#container{padding:20px;border-radius:10px;background:#333;box-shadow:0 0 15px rgba(0,0,0,0.5)}canvas{border:1px solid #444;display:block}#stats{margin-top:15px;font-family:monospace;color:#ccc}
</style>
</head>
<body>
<div id="container">
<canvas id="sieveCanvas" width="800" height="320"></canvas>
<div id="stats">Primes Found: <span id="primesCount">0</span> | Eliminated: <span id="eliminatedCount">0</span></div>
</div>
<script>
const canvas = document.getElementById('sieveCanvas');
const ctx = canvas.getContext('2d');
const N = 400;
const COLS = Math.ceil(Math.sqrt(N));
const CELL_SIZE = (canvas.width / COLS);
let isRunning = true;
let state = {
isSieve: false,
primeIndex: 0,
primesFound: 0,
eliminatedCount: 0,
sifted: new Array(N + 1).fill(false), // True if eliminated/visited
currentPrime: null,
multiplesToMark: []
};
function getNextRandomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
let colorCode = '';
for (let i = 0; i < 6; i++) {
colorCode = Math.floor(Math.random() * 16).toString(16);
color += colorCode;
}
return color;
}
function resetState() {
state.sifted.fill(false);
state.primesFound = 0;
state.eliminatedCount = 0;
state.primeIndex = 0;
state.currentPrime = null;
state.multiplesToMark = [];
for (let i = 2; i <= N; i++) {
// Initial fill: false means not visited/not prime
if (!state.sifted[i]) state.sifted[i] = true;
}
state.isSieve = true;
}
function drawCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const offset = (x) => x - 1; // Numbers start at 2, index starts at 0 for drawing logic
for (let i = 2; i <= N; i++) {
const col = Math.floor((i - 2) / COLS);
const row = (i - 2) % COLS;
const x = col * CELL_SIZE;
const y = row * CELL_SIZE;
ctx.strokeStyle = '#444';
ctx.lineWidth = 1;
if (state.sifted[i]) {
// Crossed out or visited non-prime multiple
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + CELL_SIZE, y + CELL_SIZE);
ctx.stroke();
} else if (state.currentPrime === i) {
// Current prime being highlighted
ctx.fillStyle = '#00FF7F';
ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE);
} else if (!state.sifted[i] && i > 1) {
// Prime number (but not currently active/highlighted)
ctx.fillStyle = '#3a3a50';
ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE);
} else {
// Background for non-numbers or initial state empty cells
}
}
// Draw labels (optional but good for visualization)
ctx.fillStyle = '#aaa';
for (let i = 2; i <= N; i++) {
const col = Math.floor((i - 2) / COLS);
const row = (i - 2) % COLS;
ctx.fillText(i, col * CELL_SIZE + 5, row * CELL_SIZE + CELL_SIZE * 0.7);
}
}
function updateStats() {
document.getElementById('primesCount').textContent = state.primesFound;
document.getElementById('eliminatedCount').textContent = state.eliminatedCount;
}
function checkAndRunSieveStep() {
if (!state.isSieve) return;
let i = 2;
while (i * i <= N && state.primesFound < Math.sqrt(N)) {
const num = i;
// Check if 'num' is prime (i.e., not yet eliminated/sifted)
if (!state.sifted[num]) {
state.currentPrime = num;
// Highlight current prime
drawCanvas();
updateStats();
// Mark multiples of 'num'
let tempMultiples = [];
for (let j = num * num; j <= N; j += num) {
if (!state.sifted[j]) {
tempMultiples.push(j);
}
}
state.multiplesToMark = tempMultiples;
// Animation step delay (process the multiples one by one)
setTimeout(() => {
for (let k = 0; k < state.multiplesToMark.length; k++) {
const multiple = state.multiplesToMark[k];
state.sifted[multiple] = true; // Mark as eliminated/visited
// Draw the specific elimination step
drawCanvas();
updateStats();
}
// After marking all multiples, finalize this prime's count and move on
state.primesFound++;
state.eliminatedCount += state.multiplesToMark.length;
if (state.primesFound < Math.sqrt(N)) {
i++; // Move to the next potential prime
setTimeout(() => checkAndRunSieveStep(), 50); // Delay for visual separation between primes
} else {
// Sieve complete or near completion
console.log("Sieve finished.");
state.isSieve = false;
}
}, 20); // Timeout delay per multiple elimination step
}
i++;
}
}
function animationLoop() {
if (state.isSieve) {
checkAndRunSieveStep();
} else if (!state.sifted[state.currentPrime] && state.primesFound < Math.sqrt(N)) {
// If the loop finished naturally before finding enough primes, restart or stop gracefully
console.log("Animation complete.");
}
requestAnimationFrame(animationLoop);
}
// Initialization
resetState();
drawCanvas(); // Initial draw
window.onload = () => {
animationLoop();
};
</script>
</body>
</html>
``````html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sieve of Eratosthenes Animation</title>
<style>
body { background: #111; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; color: white; font-family: monospace; }
canvas { border: 2px solid #444; background: #222; }
#status { margin-left: 30px; padding: 10px; background: #333; border-radius: 5px; width: 300px; }
</style>
</head>
<body>
<canvas id="sieveCanvas" width="600" height="240"></canvas>
<div id="status"></div>
<script>
const canvas = document.getElementById('sieveCanvas');
const ctx = canvas.getContext('2d');
const statusDiv = document.getElementById('status');
const MAX_N = 400;
const CANVAS_WIDTH = 600;
const CANVAS_HEIGHT = 240;
ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// State variables
let sieve = Array(MAX_N + 1).fill(true);
let isPrime = Array(MAX_N + 1).fill(false);
let eliminated = Array(MAX_N + 1).fill(0); // Tracks which prime struck it out
// Animation state
let currentPrimeIndex = 2;
let stepCount = 0;
let totalEliminated = 0;
let primesFound = 0;
let animationRunning = true;
function setupSieve() {
sieve.fill(true);
isPrime.fill(false);
eliminated.fill(0);
currentPrimeIndex = 2;
stepCount = 0;
totalEliminated = 0;
primesFound = 0;
animationRunning = true;
}
function drawGrid() {
ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
const gapX = CANVAS_WIDTH / (MAX_N - 1);
const cellWidth = Math.max(1, gapX * 0.8); // Slightly smaller than ideal spacing for visibility
for (let i = 2; i <= MAX_N; i++) {
const x = (i - 2) * cellWidth + (cellWidth / 2);
const y = CANVAS_HEIGHT / 2;
// Draw the number text
ctx.fillStyle = '#fff';
ctx.fillText(String(i), x, y + 5);
if (!sieve[i]) continue; // Skip drawing if marked composite
let color = '#333'; // Default background for active numbers
let alpha = 1.0;
// Check if this cell has been struck out by any prime
const p = eliminated[i];
if (p !== 0) {
color = `rgba(255, ${255 - p * 3}px, ${255 - p*3}px, 0.4)`; // Pseudo-random color based on prime index
alpha = 0.6;
} else if (isPrime[i]) {
color = '#4CAF50'; // Highlight the current prime/found primes
}
ctx.fillStyle = color;
ctx.fillRect(x - cellWidth / 2, y - cellWidth / 2, cellWidth, cellWidth);
}
}
function updateStatus() {
statusDiv.innerHTML = `
<strong>Prime Found:</strong> ${currentPrimeIndex === undefined ? 'N/A' : currentPrimeIndex}<br>
<strong>Primes Count:</strong> ${primesFound}/${Math.floor(MAX_N / Math.log(MAX_N))} (Est.)<br>
<strong>Cells Eliminated:</strong> ${totalEliminated}/${MAX_N - 2}
`;
}
function animate() {
if (!animationRunning) return;
// Check if we need to find the next prime or finish
if (currentPrimeIndex === undefined || currentPrimeIndex * currentPrimeIndex > MAX_N) {
const finished = sieve.every(b => b); // Quick check for completion state (not perfect, but good enough)
if (totalEliminated >= MAX_N - 2 && primesFound > 0) {
animationRunning = false;
// Final cleanup visualization
currentPrimeIndex = 'Finished';
drawGrid();
updateStatus();
return;
}
// Find next prime starting from current index + 1
let foundNext = false;
for (let i = Math.max(2, currentPrimeIndex === undefined ? 2 : currentPrimeIndex); i <= MAX_N; i++) {
if (sieve[i] && !isPrime[i]) {
currentPrimeIndex = i;
foundNext = true;
break;
}
}
if (!foundNext) {
// This handles the case where we passed 400 or only unmarked numbers remain.
animationRunning = false;
currentPrimeIndex = 'Finished';
drawGrid();
updateStatus();
return;
}
}
const p = currentPrimeIndex;
// --- Core Animation Step Logic ---
if (p < 2) {
currentPrimeIndex++;
requestAnimationFrame(animate);
return;
}
// Highlight the prime just found
isPrime[p] = true;
drawGrid(); // Redraw to show highlight
updateStatus();
// Determine the current multiple step (k)
const startMultiple = Math.max(p * p, p * 2);
let lastMultipleProcessed = (p === 2 && totalEliminated < 10) ? 6 : (totalEliminated > 0 ? Math.floor((stepCount - 1) / 5) + 2 : 4);
// Limit steps to prevent immediate overflow/underflow issues on small ranges
let k = lastMultipleProcessed;
for (; k * p <= MAX_N && totalEliminated < MAX_N - 2; k++) {
const multiple = k * p;
if (multiple > MAX_N) break;
if (sieve[multiple]) { // Only strike out if it hasn't been struck out yet
sieve[multiple] = false;
eliminated[multiple] = p;
totalEliminated++;
stepCount++;
// Progressive visual update: Redraw every single step
drawGrid();
updateStatus();
}
}
// Move to the next prime candidate
currentPrimeIndex++;
primesFound++; // Count the prime just processed
requestAnimationFrame(animate);
}
setupSieve();
animate();
</script>
</body>
</html>
```| Blind label | Model | Blind score | Latency | Status |
|---|---|---|---|---|
| MODEL A | qwen3:8b | — | — | DNF |
| MODEL B | llama3.1:8b | — | 7.9 s | OK |
| MODEL C | mistral-small:24b | — | — | DNF |
| MODEL D | qwen3-coder:30b | — | 29.6 s | OK |
| MODEL E | qwen3:14b | — | 26.7 s | OK |
| MODEL F | deepseek-r1:14b | — | — | DNF |
| MODEL G | qwen3.5:9b | — | 27.8 s | OK |
| MODEL H | gemma4:26b | — | — | DNF |
These models answered the identical prompt outside the blind field. They are deliberately not scored and not part of the blind comparison — they are there so you can see where a frontier model lands on the same task.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Sieve of Eratosthenes</title>
<style>
html,body{margin:0;height:100%;background:#070a12;overflow:hidden;display:flex;align-items:center;justify-content:center}
canvas{max-width:100vw;max-height:100vh;display:block}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const N=400,COLS=20,ROWS=20,CELL=32,PADX=26,TOP=104,BOT=30;
const W=PADX*2+COLS*CELL,H=TOP+ROWS*CELL+BOT;
const F='ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif';
const cv=document.getElementById('c'),ctx=cv.getContext('2d');
const dpr=Math.min(2,window.devicePixelRatio||1);
cv.width=W*dpr;cv.height=H*dpr;cv.style.width=W+'px';cv.style.height=H+'px';ctx.scale(dpr,dpr);
let S,prev=0;
function reset(){
S={mark:new Int16Array(N+1).fill(-1),primes:[],cur:1,mults:[],mi:0,mode:'find',elim:0,last:0,flash:0,
hue0:Math.random()*360,spin:Math.random()<.5?1:-1,gap:15+Math.random()*13,step:15+Math.random()*13,acc:0,done:0,hold:0};
}
function hueOf(p){let h=(S.hue0+S.spin*p*S.gap)%360;return (h+360)%360;}
function findNext(){for(let n=S.cur+1;n<=N;n++)if(S.mark[n]<0)return n;return 0;}
function step(){
if(S.mode==='mult'){
while(S.mi<S.mults.length){
const m=S.mults[S.mi++];
if(S.mark[m]<0){S.mark[m]=hueOf(S.cur)|0;S.elim++;S.last=m;S.flash=1;return;}
}
S.mode='find';
}
const n=findNext();
if(!n){S.done=1;return;}
S.cur=n;S.primes.push(n);S.last=n;S.flash=1;S.mults=[];
for(let m=n*2;m<=N;m+=n)S.mults.push(m);
S.mi=0;S.mode='mult';
}
function rr(x,y,w,h,r){ctx.beginPath();if(ctx.roundRect)ctx.roundRect(x,y,w,h,r);else ctx.rect(x,y,w,h);}
function stat(x,label,val,col){
ctx.textAlign='left';ctx.textBaseline='alphabetic';
ctx.fillStyle='#59648c';ctx.font='600 10px '+F;ctx.fillText(label,x,56);
ctx.fillStyle=col;ctx.font='700 27px '+F;ctx.fillText(val,x,88);
}
function draw(){
const g=ctx.createLinearGradient(0,0,W,H);
g.addColorStop(0,'#070a12');g.addColorStop(1,'#101629');ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
const ch=hueOf(S.cur||2);
ctx.textAlign='left';ctx.textBaseline='alphabetic';
ctx.fillStyle='#4a5578';ctx.font='600 11px '+F;
ctx.fillText('SIEVE OF ERATOSTHENES · 2 – 400',PADX,28);
const cw=(W-PADX*2)/3;
stat(PADX,'CURRENT PRIME',S.primes.length?String(S.cur):'–','hsl('+ch+',90%,66%)');
stat(PADX+cw,'PRIMES FOUND',String(S.primes.length),'#e8eeff');
stat(PADX+cw*2,'CELLS ELIMINATED',String(S.elim),'#8fa2d6');
for(let n=1;n<=N;n++){
const i=n-1,x=PADX+(i%COLS)*CELL,y=TOP+((i/COLS)|0)*CELL,m=S.mark[n];
const bx=x+2,by=y+2,s=CELL-4;
let txt='#8c98bd',bold=false;
rr(bx,by,s,s,6);
if(n===1){ctx.fillStyle='#0e1322';ctx.fill();txt='#333c58';}
else if(m>=0){ctx.fillStyle='hsl('+m+',55%,13%)';ctx.fill();txt='hsl('+m+',35%,44%)';}
else if(n<=S.cur||S.done){
ctx.fillStyle='hsl('+hueOf(n)+',75%,20%)';ctx.fill();
ctx.strokeStyle='hsl('+hueOf(n)+',90%,62%)';ctx.lineWidth=1.4;ctx.stroke();
txt='#ffffff';bold=true;
}else{ctx.fillStyle='#0f1524';ctx.fill();ctx.strokeStyle='#1b2337';ctx.lineWidth=1;ctx.stroke();}
ctx.fillStyle=txt;ctx.font=(bold?'700 ':'500 ')+11+'px '+F;
ctx.textAlign='center';ctx.textBaseline='middle';
ctx.fillText(String(n),x+CELL/2,y+CELL/2+.5);
if(m>=0){
ctx.strokeStyle='hsla('+m+',90%,62%,.85)';ctx.lineWidth=2;ctx.lineCap='round';
ctx.beginPath();ctx.moveTo(bx+4,by+s-4);ctx.lineTo(bx+s-4,by+4);ctx.stroke();
}
if(n===S.last&&S.flash>0){
ctx.save();ctx.globalAlpha=S.flash*.85;
ctx.strokeStyle='#ffffff';ctx.lineWidth=2;rr(bx-1,by-1,s+2,s+2,7);ctx.stroke();ctx.restore();
}
if(n===S.cur&&!S.done){
const pulse=.55+.45*Math.sin(performance.now()/210);
ctx.save();ctx.shadowColor='hsl('+ch+',95%,60%)';ctx.shadowBlur=14*pulse;
ctx.strokeStyle='hsla('+ch+',95%,70%,'+(.6+.4*pulse)+')';ctx.lineWidth=2.4;
rr(bx-1.5,by-1.5,s+3,s+3,8);ctx.stroke();ctx.restore();
}
}
const prog=S.done?1:(S.primes.length?(S.cur-1)/(N-1):0);
ctx.fillStyle='#161d31';rr(PADX,H-16,W-PADX*2,4,2);ctx.fill();
ctx.fillStyle='hsl('+ch+',85%,58%)';rr(PADX,H-16,(W-PADX*2)*prog,4,2);ctx.fill();
}
function frame(ts){
if(!prev)prev=ts;
const dt=Math.min(64,ts-prev);prev=ts;
S.flash=Math.max(0,S.flash-dt/280);
if(S.done){S.hold+=dt;if(S.hold>1700)reset();}
else{S.acc+=dt;let guard=0;while(S.acc>=S.step&&!S.done&&guard++<24){S.acc-=S.step;step();}}
draw();
requestAnimationFrame(frame);
}
reset();requestAnimationFrame(frame);
</script>
</body>
</html>