OverviewCode › R67
Code · blind round

R67 · Sieve of Eratosthenes on a number grid

blind round 4/8 local models answered

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.

Show task
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.

The answers — blind

Local models (Brain cluster) — identical prompt, anonymized order
MODEL Aqwen3:8bDNF
No usable answer reached the harness (ERR). Counted as a did-not-finish.
MODEL Bllama3.1:8bOKFORMAT
Show code
```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>
```
No usable answer reached the harness (TIMEOUT). Counted as a did-not-finish.
MODEL Dqwen3-coder:30bOKLIMFORMAT
Show code
```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>
```
MODEL Eqwen3:14bSTATLIMFORMAT
Show code
```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>
```
MODEL Fdeepseek-r1:14bDNF
No usable answer reached the harness (TIMEOUT). Counted as a did-not-finish.
MODEL Gqwen3.5:9bBLANKLIMFORMAT
Show code
```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>
```
MODEL Hgemma4:26bDNF
No usable answer reached the harness (TIMEOUT). Counted as a did-not-finish.
Reveal the models— compare the answers first, then open. Names, blind scores and latencies are hidden until you do.

Who wrote what

Blind labelModelBlind scoreLatencyStatus
MODEL A qwen3:8b DNF
MODEL B llama3.1:8b 7.9 sOK
MODEL C mistral-small:24b DNF
MODEL D qwen3-coder:30b 29.6 sOK
MODEL E qwen3:14b 26.7 sOK
MODEL F deepseek-r1:14b DNF
MODEL G qwen3.5:9b 27.8 sOK
MODEL H gemma4:26b DNF

Frontier reference — outside the blind field

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.

Collected differently from the blind field: the local models run through a plain API harness, the frontier references through a context-free agent, one shot, no retries. Response times are therefore not comparable and are not shown. On two English tasks the reference answer came back in German — an artefact of that harness, flagged on the affected rounds.
Claude Opus 5frontier reference
Show code
<!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>