OverviewCode › R70
Code · blind round

R70 · Binary search, step by step

blind round 5/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 a binary search on a sorted array of 40 values drawn as labelled bars on a <canvas>. Show the low, high and middle markers, highlight the middle element being compared, and grey out the half that gets discarded in each step. Display the target value and the number of comparisons used. 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.5:9bHANGLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
<title>Binary Search Animation</title>
<style>body { background: #111; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; flex-direction: column; color: white; } canvas { border: 1px solid #333; } .info { margin-top: 20px; font-family: monospace; }</style>
</head>
<body>
<canvas id="canvas" width="800" height="250"></canvas>
<div class="info">Target: <span id="target-display"></span> | Comparisons: <span id="comparisons">0</span></div>

<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const ARRAY_SIZE = 40;
    const BAR_WIDTH = (canvas.width - 30) / ARRAY_SIZE * 0.8;
    const PADDING = 15;

    let array = [];
    let targetValue = 0;
    let low = 0;
    let high = ARRAY_SIZE - 1;
    let comparisons = 0;
    let animationFrameId = null;
    let isSearching = false;

    function generateArray() {
        array = [];
        for (let i = 0; i < ARRAY_SIZE; i++) {
            array.push(Math.floor(Math.random() * 90) + 10); // Values between 10 and 100
        }
        targetValue = array[Math.floor(Math.random() * ARRAY_SIZE)];
    }

    function drawArrayState(discardedLow, discardedHigh) {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        const barSpacing = (canvas.width - PADDING * 2) / Array.from({length: ARRAY_SIZE}, () => 1).length;

        // Draw bars and highlight discarded segments
        for (let i = 0; i < ARRAY_SIZE; i++) {
            let x = PADDING + barSpacing * i + BAR_WIDTH * (i % 2 === 0 ? -0.4 : 0.4);
            let y = 0;
            let height = array[i] * 1.5;

            // Check if the index is in the discarded range [discardedLow, discardedHigh]
            if (i >= Math.min(low, high) && i <= Math.max(low, high)) {
                if ((i < low || i > high) && i !== Math.floor((low + high) / 2)) {
                     ctx.fillStyle = '#444';
                } else if (i === Math.floor((low + high) / 2)) {
                    ctx.fillStyle = '#FFD700'; // Yellow for middle element
                } else {
                    ctx.fillStyle = '#3b82f6';
                }
            } else {
                 // This simplified block assumes we only highlight the search space [low, high]
                 if (i < low) ctx.fillStyle = '#4a5568'; // Grey out left half
                 else if (i > high) ctx.fillStyle = '#4a5568'; // Grey out right half
                 else ctx.fillStyle = '#3b82f6'; 
            }

            ctx.fillRect(x, canvas.height - height, BAR_WIDTH * (i % 2 === 0 ? 1 : 0.8), height);
        }
    }
    
    function drawMarkers(lowIndex, highIndex, midIndex) {
         // Draw low marker
        ctx.beginPath();
        ctx.fillStyle = '#dc2626'; // Red
        ctx.fillRect(PADDING + (canvas.width - PADDING * 2) / ARRAY_SIZE * lowIndex, canvas.height - 10, 8, 10);

        // Draw high marker
        ctx.beginPath();
        ctx.fillStyle = '#dc2626'; // Red
        ctx.fillRect(PADDING + (canvas.width - PADDING * 2) / ARRAY_SIZE * highIndex, canvas.height - 10, 8, 10);

        // Draw middle marker
        ctx.beginPath();
        ctx.fillStyle = '#f59e0b'; // Amber/Yellow
        ctx.fillRect(PADDING + (canvas.width - PADDING * 2) / ARRAY_SIZE * midIndex, canvas.height - 10, 8, 10);
    }

    function updateDisplay() {
        document.getElementById('target-display').textContent = targetValue;
        document.getElementById('comparisons').textContent = comparisons;
    }
    
    function animateSearch(l, h) {
        if (!isSearching) return;

        const mid = Math.floor((l + h) / 2);
        const currentL = low;
        const currentH = high;
        
        drawArrayState(currentL, currentH);
        drawMarkers(currentL, currentH, mid);

        // Search logic visualization: Compare and update state
        comparisons++;
        if (array[mid] === targetValue) {
            isSearching = false;
            alert(`Found target ${targetValue} at index ${mid}! Total comparisons: ${comparisons}`);
            generateArray(); // Restart on success
            animateSearch(0, ARRAY_SIZE - 1);
        } else if (array[mid] < targetValue) {
            low = mid + 1;
            if (low > high) {
                isSearching = false;
                alert(`Target ${targetValue} not found. Total comparisons: ${comparisons}`);
                generateArray(); // Restart on failure
                animateSearch(0, ARRAY_SIZE - 1);
            }
        } else {
            high = mid - 1;
        }

        updateDisplay();

        if (low <= high) {
            animationFrameId = requestAnimationFrame(() => animateSearch(low, high));
        } else {
             isSearching = false;
             // Final cleanup and restart attempt after a delay
             setTimeout(() => {
                alert(`Target ${targetValue} not found. Total comparisons: ${comparisons}`);
                generateArray(); 
                animateSearch(0, ARRAY_SIZE - 1);
            }, 500);
        }
    }

    function startAnimation() {
        if (animationFrameId) cancelAnimationFrame(animationFrameId);
        
        // Reset state and generate new data
        low = 0;
        high = ARRAY_SIZE - 1;
        comparisons = 0;
        isSearching = true;

        generateArray();
        updateDisplay();
        animateSearch(low, high);
    }
    
    // Start the animation immediately upon loading
    startAnimation();

</script>
</body>
</html>
```
MODEL Bqwen3-coder:30bHANGLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
<title>Binary Search Animation</title>
<style>
body { background: #f0f0f0; display: flex; justify-content: center; padding: 20px; }
canvas { border: 1px solid #ccc; background: white; }
#info { margin-left: 30px; font-family: monospace; }
</style>
</head>
<body>
<canvas id="searchCanvas" width="800" height="250"></canvas>
<div id="info">Target: N/A<br>Comparisons: 0</div>

<script>
const canvas = document.getElementById('searchCanvas');
const ctx = canvas.getContext('2d');
const infoDiv = document.getElementById('info');

let arr = [];
let target = 0;
let low = 0;
let high = 39;
let comparisons = 0;
let isSearching = false;
let animationFrameId = null;

function generateArray() {
    arr = Array.from({ length: 40 }, () => Math.floor(Math.random() * 100) + 5);
    arr.sort((a, b) => a - b);
}

function setup() {
    generateArray();
    target = arr[Math.floor(Math.random() * 40)];
    low = 0;
    high = 39;
    comparisons = 0;
    isSearching = true;
    document.getElementById('info').innerHTML = `Target: ${target}<br>Comparisons: 0`;
}

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    const barWidth = (canvas.width - 20) / 40;
    const padding = 10;

    for (let i = 0; i < 40; i++) {
        ctx.fillStyle = 'skyblue';
        ctx.fillRect(padding + i * barWidth, 0, barWidth - 2, arr[i]);
    }

    // Low marker
    ctx.strokeStyle = 'red';
    ctx.lineWidth = 3;
    ctx.beginPath();
    ctx.moveTo(padding + low * (barWidth), canvas.height);
    ctx.lineTo(padding + low * (barWidth), 0);
    ctx.stroke();

    // High marker
    ctx.strokeStyle = 'red';
    ctx.lineWidth = 3;
    ctx.beginPath();
    ctx.moveTo(padding + high * (barWidth) + barWidth - 2, canvas.height);
    ctx.lineTo(padding + high * (barWidth) + barWidth - 2, 0);
    ctx.stroke();

    // Middle marker (Highlight)
    if (!isNaN(mid)) {
        const midX = padding + mid * barWidth;
        ctx.fillStyle = 'rgba(255, 165, 0, 0.7)'; // Orange highlight
        ctx.fillRect(midX, 0, barWidth - 2, canvas.height);

    } else { // Draw discarded segment (Grey)
        if (low > 0 && low < high) {
            const startX = padding + Math.min(low, mid_old) * barWidth;
            const endX = padding + Math.max(low, mid_old) * barWidth + (barWidth - 2);
            ctx.fillStyle = 'rgba(180, 180, 180, 0.5)'; // Grey overlay
            ctx.fillRect(startX, 0, endX - startX, canvas.height);
        }
    }

    // Update Info Display
    infoDiv.innerHTML = `Target: ${target}<br>Comparisons: ${comparisons}`;
}

let mid_old = null;

function step() {
    if (!isSearching) return;

    mid = Math.floor((low + high) / 2);
    const comparisonResult = arr[mid] < target ? 1 : (arr[mid] > target ? -1 : 0);

    draw(); // Draw before state change, showing the element being compared

    setTimeout(() => {
        if (comparisonResult === 0) {
            isSearching = false;
            alert(`Found ${target} at index ${mid}!`);
            setup(); // Restart animation
            return;
        } else if (comparisonResult > 0) {
            low = mid + 1;
        } else {
            high = mid - 1;
        }

        comparisons++;
        mid_old = mid; // Record previous position for grey highlight on next draw
        draw(); // Draw after state change (new low/high)

        if (low > high) {
            isSearching = false;
            alert(`Target ${target} not found.`);
            setup(); // Restart animation
        } else {
            animationFrameId = requestAnimationFrame(step);
        }
    }, 500);
}

let mid = null;

function animate() {
    if (!isSearching) return;
    requestAnimationFrame(step);
}

// Start the process
setup();
animate();
</script>
</body>
</html>
```
MODEL Cqwen3:8bDNF
No usable answer reached the harness (ERR). Counted as a did-not-finish.
MODEL Dmistral-small:24bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Binary Search Animation</title>
<style>
  body { font-family: Arial, sans-serif; }
  #canvas { border: 1px solid black; }
</style>
</head>
<body>

<canvas id="canvas" width="800" height="400"></canvas>
<div id="info"></div>

<script>
// Initialize canvas and context
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

let array = Array.from({ length: 40 }, (_, i) => i + 1);
array.sort(() => Math.random() - 0.5);

let target = array[Math.floor(Math.random() * 40)];
let low = 0;
let high = array.length - 1;
let comparisons = 0;

function drawArray(arr, low, high, mid) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  const barWidth = canvas.width / arr.length;
  for (let i = 0; i < arr.length; i++) {
    let color = 'grey';
    if (i === mid) color = 'red';
    else if (i >= low && i <= high) color = 'blue';

    ctx.fillStyle = color;
    ctx.fillRect(i * barWidth, canvas.height - arr[i] * 10, barWidth, arr[i] * 10);
    ctx.fillStyle = 'black';
    ctx.fillText(arr[i], i * barWidth + 2, canvas.height - arr[i] * 10 - 5);
  }
}

function animateBinarySearch() {
  if (low > high) return;

  let mid = Math.floor((low + high) / 2);

  drawArray(array, low, high, mid);
  comparisons++;
  document.getElementById('info').innerText = `Target: ${target}, Comparisons: ${comparisons}`;

  requestAnimationFrame(() => {
    if (array[mid] === target) {
      setTimeout(() => animateBinarySearch(), 500);
    } else if (array[mid] < target) {
      low = mid + 1;
      array.fill('grey', 0, mid);
      setTimeout(() => animateBinarySearch(), 500);
    } else {
      high = mid - 1;
      array.fill('grey', mid + 1, high + 1);
      setTimeout(() => animateBinarySearch(), 500);
    }
  });
}

animateBinarySearch();
</script>

</body>
</html>
```
MODEL Edeepseek-r1:14bDNF
No usable answer reached the harness (TIMEOUT). Counted as a did-not-finish.
MODEL Fqwen3:14bOKLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Animated Binary Search</title>
    <style>
        body { background: #1e1e2e; color: white; font-family: monospace; display: flex; flex-direction: column; align-items: center; padding: 20px; }
        #canvas { border: 1px solid #444; background: #27293d; }
        .info { margin-top: 15px; font-size: 1.1em; }
    </style>
</head>
<body>
    <h1>Binary Search Simulation (N=40)</h1>
    <canvas id="canvas" width="800" height="300"></canvas>

    <div class="info">
        Target Value: <span id="target-val"></span> | Comparisons: <span id="comp-count">0</span>
        <p>(Greyed area is discarded)</p>
    </div>

    <script>
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        const N = 40;
        let array = [];
        let target = 0;
        let low, high;
        let comparisons = 0;
        
        // State machine for animation
        let state = 'INIT'; // INIT -> RUNNING_STEP -> RESULT
        let currentStep = 0;
        const MAX_STEPS = 15;

        function init() {
            array = Array.from({ length: N }, () => Math.floor(Math.random() * 90) + 10);
            // Ensure target exists and is within the array range
            target = array[Math.floor(Math.random() * N)];

            low = 0;
            high = N - 1;
            comparisons = 0;
            currentStep = 0;
            state = 'INIT';

            document.getElementById('target-val').textContent = target;
            document.getElementById('comp-count').textContent = comparisons;
        }

        function draw() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            const barWidth = (canvas.width / N) * 0.8;
            const padding = (canvas.width / N) * 0.2;

            // 1. Draw Bars and Labels
            for (let i = 0; i < N; i++) {
                ctx.fillStyle = '#6a7b9c'; // Default bar color
                ctx.fillRect(i * (barWidth + padding) + padding, 20, barWidth, array[i] * 10);
                ctx.fillStyle = 'white';
                ctx.font = '10px monospace';
                ctx.fillText(array[i], i * (barWidth + padding) + padding - 5, 15);
            }

            // 2. Highlight Discarded Area (Previous step's discard)
            if (state === 'RUNNING_STEP' && currentStep > 0) {
                let start = Math.max(0, low);
                let end = Math.min(N - 1, high);

                // If we are in the middle of a comparison cycle and have established bounds:
                if (start < N && end >= 0) {
                    ctx.fillStyle = 'rgba(105, 105, 105, 0.3)'; // Grey highlight
                    const xStart = start * (barWidth + padding) + padding;
                    const width = Math.min(N, Math.max(0, end - start + 1)) * (barWidth + padding);
                    ctx.fillRect(xStart, 0, width, canvas.height);
                }
            }

            // 3. Draw Low/High/Mid Markers
            let currentLow = low;
            let currentHigh = high;
            let mid = Math.floor((currentLow + currentHigh) / 2);

            if (currentLow >= 0 && currentLow < N) {
                ctx.strokeStyle = '#ff5555'; // Low marker color
                ctx.lineWidth = 3;
                ctx.beginPath();
                ctx.moveTo(currentLow * (barWidth + padding) + padding - 10, 0);
                ctx.lineTo(currentLow * (barWidth + padding) + padding - 10, canvas.height);
                ctx.stroke();
            }

            if (currentHigh >= 0 && currentHigh < N) {
                ctx.strokeStyle = '#55aaff'; // High marker color
                ctx.lineWidth = 3;
                ctx.beginPath();
                ctx.moveTo(currentHigh * (barWidth + padding) + padding - 10, 0);
                ctx.lineTo(currentHigh * (barWidth + padding) + padding - 10, canvas.height);
                ctx.stroke();
            }

            // 4. Highlight Middle Element being compared
            if (mid >= 0 && mid < N) {
                ctx.fillStyle = 'rgba(255, 200, 0, 0.7)'; // Yellow highlight
                const x = mid * (barWidth + padding) + padding;
                ctx.fillRect(x - 15, 0, barWidth + 30, canvas.height);

                // Draw 'Mid' label
                ctx.fillStyle = '#ffcc00';
                ctx.font = 'bold 14px monospace';
                ctx.fillText('MID', x, 25);
            }

             // 5. Target Marker (Conceptual display)
            ctx.strokeStyle = '#8aff8a'; // Green outline for target spot
            ctx.lineWidth = 2;
            const targetX = (array.indexOf(target) * (barWidth + padding)) + padding - 10;

            // Instead of drawing a line, just draw the bar itself with a subtle green border
            if (array.indexOf(target) !== -1) {
                 ctx.strokeStyle = '#8aff8a';
                 ctx.lineWidth = 5;
                 ctx.strokeRect(array.indexOf(target) * (barWidth + padding) + padding - 2, 0, barWidth + 4, canvas.height);
            }
        }

        function stepSearch() {
            if (low > high || state === 'RESULT') {
                state = 'FINISHED';
                draw();
                return;
            }

            const mid = Math.floor((low + high) / 2);
            comparisons++;
            document.getElementById('comp-count').textContent = comparisons;

            if (array[mid] === target) {
                state = 'SUCCESS';
                draw();
                return;
            } else if (array[mid] < target) {
                low = mid + 1;
            } else {
                high = mid - 1;
            }

            // Check for failure condition after update
            if (low > high) {
                 state = 'FAILURE';
            }
        }

        function animate() {
            if (state === 'INIT' || state === 'RUNNING_STEP') {
                draw();
                stepSearch();
                // Only step and redraw if the search is still active or hasn't completed its steps
                if (state !== 'FINISHED' && state !== 'SUCCESS' && state !== 'FAILURE') {
                    setTimeout(() => requestAnimationFrame(animate), 500); // Wait half a second per comparison
                } else {
                     // If it reached the final state, stop recursion. The final draw() happens in stepSearch().
                    return;
                }

            } else if (state === 'FINISHED' || state === 'SUCCESS' || state === 'FAILURE') {
                clearInterval(window.animationInterval);
                const result = state === 'SUCCESS' ? "Found!" : 
                              state === 'FAILURE' ? "Not found." : "";
                alert(`Search Finished! ${result} Comparisons used: ${comparisons}`);

                // Restart the process after a delay
                setTimeout(() => {
                    init();
                    requestAnimationFrame(animate);
                }, 2000);
            }
        }

        // Initialization and Start
        init();
        window.animationInterval = requestAnimationFrame(animate);

    </script>
</body>
</html>
```
MODEL Ggemma4:26bDNF
No usable answer reached the harness (TIMEOUT). Counted as a did-not-finish.
MODEL Hllama3.1:8bSTATFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
  <title>Binary Search Animation</title>
  <style>
    /* add some basic styling */
    body {
      background-color: #f0f0f0;
    }
    canvas {
      border: 1px solid black;
    }
  </style>
</head>
<body>
<canvas id="canvas" width="600" height="400"></canvas>

<script>
  var ctx = document.getElementById('canvas').getContext('2d');
  var array = Array.from({length:40}, (_, i) => Math.random() * (100 - 1) + 1);
  array.sort((a, b) => a-b);

  function draw(arr) {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = 'black';
    for(let i = 0; i < arr.length; i++) {
      const x = (canvas.width / arr.length) * i;
      ctx.fillRect(x, canvas.height - (arr[i] + 1), canvas.width/arr.length, 2);
      ctx.fillStyle = 'black';
      ctx.font = "12px Arial";
      ctx.textAlign = "center";
      ctx.textBaseline = "top";
      ctx.fillText(i, x + (canvas.width / arr.length) / 2, canvas.height - (arr[i] + 1));
    }
    ctx.beginPath();
    ctx.lineWidth = 3;
    ctx.moveTo(0, canvas.height);
    for(let i = 0; i < arr.length; i++) {
      const x = (canvas.width / arr.length) * i;
      ctx.lineTo(x, canvas.height - 2);
      if(i === low && i <= high) ctx.fillStyle = 'red';
      else if(i >= low && i > high) ctx.fillStyle = 'grey';
      else ctx.fillStyle = 'black';
    }
    ctx.stroke();
    ctx.fillStyle = 'black';
    ctx.font = "24px Arial";
    ctx.textAlign = "left";
    ctx.textBaseline = "top";
    ctx.fillText(`Target: ${target}`, 20, 20);
    ctx.font = "18px Arial";
    ctx.fillText(`Comparsions: ${comparsions}`, 20, 50);
  }

  var low = 0;
  var high = array.length - 1;
  var target = Math.floor(Math.random() * (array[array.length-1] + 1));
  var comparsions = 0;

  function animate() {
    requestAnimationFrame(animate);
    const mid = Math.floor((low+high)/2);
    ctx.fillStyle = 'black';
    ctx.font = "24px Arial";
    ctx.textAlign = "left";
    ctx.textBaseline = "top";
    ctx.fillText(`Target: ${target}`, 20, 20);
    if(mid === high || mid < low) {
      draw(array);
      comparsions++;
      setTimeout(() => {location.reload()}, 2000);
      return;
    }
    const middleValue = array[mid];
    ctx.fillStyle = 'yellow';
    const x = (canvas.width / array.length) * mid;
    ctx.fillRect(x, canvas.height - (middleValue + 1), canvas.width/array.length, 2);
    ctx.fillStyle = 'black';
    ctx.font = "12px Arial";
    ctx.textAlign = "center";
    ctx.textBaseline = "top";
    ctx.fillText(mid, x + (canvas.width / array.length) / 2, canvas.height - (middleValue + 1));

    if(target < middleValue) {
      high = mid;
      comparsions++;
    } else {
      low = mid + 1;
      comparsions++;
    }
    draw(array);
  }

  animate();
</script>
</body>
</html>
```
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.5:9b 26.0 sOK
MODEL B qwen3-coder:30b 23.2 sOK
MODEL C qwen3:8b DNF
MODEL D mistral-small:24b 78.0 sOK
MODEL E deepseek-r1:14b DNF
MODEL F qwen3:14b 32.1 sOK
MODEL G gemma4:26b DNF
MODEL H llama3.1:8b 10.4 sOK

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"><title>Binary Search Visualizer</title>
<style>
html,body{margin:0;height:100%;background:radial-gradient(1200px 700px at 50% 0%,#141c34,#080b16 70%);
display:flex;align-items:center;justify-content:center;font-family:ui-monospace,Menlo,Consolas,monospace}
canvas{border-radius:14px;box-shadow:0 20px 60px rgba(0,0,0,.6);background:#0b1020}
</style></head><body><canvas id="c"></canvas><script>
const cv=document.getElementById('c'),g=cv.getContext('2d');
const W=980,H=480,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';g.scale(DPR,DPR);
const N=40,PL=50,PR=50,TOP=104,BASE=374,SLOT=(W-PL-PR)/N,BW=SLOT*0.62;
let vals,target,steps,idx,alpha,mLo,mHi,mMid,found,t0=0,last=0,maxV;
function xOf(i){return PL+i*SLOT+SLOT/2}
function init(){
  const s=new Set();while(s.size<N)s.add(4+Math.floor(Math.random()*392));
  vals=[...s].sort((a,b)=>a-b);maxV=vals[N-1];
  target=Math.random()<0.8?vals[Math.floor(Math.random()*N)]:1+Math.floor(Math.random()*400);
  steps=[];found=-1;let lo=0,hi=N-1;
  while(lo<=hi){const mid=(lo+hi)>>1,c=vals[mid]===target?0:(vals[mid]<target?-1:1);
    steps.push({lo:lo,hi:hi,mid:mid,c:c});if(c===0){found=mid;break}
    if(c<0)lo=mid+1;else hi=mid-1}
  if(found<0)steps.push({lo:lo,hi:hi,mid:-1,c:null});
  idx=0;t0=0;alpha=new Array(N).fill(1);
  mLo=xOf(0);mHi=xOf(N-1);mMid=xOf(steps[0].mid);
}
function lerp(a,b,k){return a+(b-a)*k}
function marker(x,y,label,col,dir){
  g.fillStyle=col;g.beginPath();g.moveTo(x,y);g.lineTo(x-6,y+11);g.lineTo(x+6,y+11);g.closePath();g.fill();
  g.font='bold 12px ui-monospace,monospace';g.textAlign='center';g.fillText(label,x+dir*11,y+22);
}
function draw(ts){
  const dt=Math.min(60,ts-last||16);last=ts;if(!t0)t0=ts;
  const cur=steps[idx],end=idx===steps.length-1,D=end?1900:1000;
  if(ts-t0>D){if(!end){idx++;t0=ts}else{init();t0=ts}}
  const st=steps[idx],k=1-Math.exp(-dt/110);
  mLo=lerp(mLo,xOf(Math.min(st.lo,N-1)),k);mHi=lerp(mHi,xOf(Math.max(st.hi,0)),k);
  if(st.mid>=0)mMid=lerp(mMid,xOf(st.mid),k);
  let cmps=0;for(let i=0;i<=idx;i++)if(steps[i].mid>=0)cmps++;
  g.clearRect(0,0,W,H);
  g.fillStyle='#0b1020';g.fillRect(0,0,W,H);
  g.fillStyle='#f1f5f9';g.textAlign='left';g.font='bold 20px ui-monospace,monospace';
  g.fillText('Binary Search',PL,44);
  g.font='13px ui-monospace,monospace';g.fillStyle='#7f8ca8';
  g.fillText('sorted array · n = '+N,PL+186,44);
  g.textAlign='right';g.font='bold 17px ui-monospace,monospace';g.fillStyle='#fbbf24';
  g.fillText('target = '+target,W-PR,32);
  g.fillStyle='#7dd3fc';g.font='13px ui-monospace,monospace';
  g.fillText('comparisons: '+cmps+'   range: '+(st.hi>=st.lo?'['+st.lo+' … '+st.hi+']':'empty'),W-PR,54);
  let msg,mc='#94a3b8';
  if(st.mid<0){msg='target not present — search space empty';mc='#f87171'}
  else if(st.c===0){msg='a[mid] = '+vals[st.mid]+' == target  →  FOUND at index '+st.mid;mc='#4ade80'}
  else if(st.c<0){msg='a[mid] = '+vals[st.mid]+' < '+target+'  →  discard left half';mc='#fbbf24'}
  else {msg='a[mid] = '+vals[st.mid]+' > '+target+'  →  discard right half';mc='#fbbf24'}
  g.textAlign='left';g.fillStyle=mc;g.font='14px ui-monospace,monospace';g.fillText(msg,PL,78);
  g.strokeStyle='#1e293b';g.lineWidth=1;g.beginPath();g.moveTo(PL-14,BASE+.5);g.lineTo(W-PR+14,BASE+.5);g.stroke();
  const pulse=0.62+0.38*Math.sin(ts/150);
  for(let i=0;i<N;i++){
    const on=i>=st.lo&&i<=st.hi?1:0.16;alpha[i]=lerp(alpha[i],on,k);
    const h=8+(vals[i]/maxV)*(BASE-TOP-8),x=xOf(i),y=BASE-h,a=alpha[i];
    const isMid=i===st.mid,hit=isMid&&st.c===0;
    let col=hit?'74,222,128':isMid?'251,191,36':'45,212,191';
    g.fillStyle='rgba('+col+','+(a*(isMid?pulse:0.9))+')';
    g.beginPath();g.roundRect(x-BW/2,y,BW,h,3);g.fill();
    if(isMid){g.strokeStyle='rgba(255,255,255,'+(0.55*a)+')';g.lineWidth=1.4;g.stroke()}
    g.save();g.translate(x,y-7);g.rotate(-Math.PI/2);g.textAlign='left';
    g.font=(isMid?'bold ':'')+'10px ui-monospace,monospace';
    g.fillStyle='rgba('+(isMid?'255,255,255':'203,213,225')+','+(a*0.95)+')';
    g.fillText(vals[i],0,3.5);g.restore();
    if(i%5===0){g.textAlign='center';g.font='9px ui-monospace,monospace';
      g.fillStyle='rgba(100,116,139,'+(0.35+a*0.5)+')';g.fillText(i,x,BASE+13)}
  }
  if(st.hi>=st.lo){
    g.strokeStyle='rgba(125,211,252,.45)';g.lineWidth=2;g.beginPath();
    g.moveTo(mLo-SLOT/2+3,BASE+26);g.lineTo(mHi+SLOT/2-3,BASE+26);g.stroke();
    g.fillStyle='rgba(125,211,252,.10)';g.fillRect(mLo-SLOT/2+3,TOP-14,(mHi-mLo)+SLOT-6,BASE-TOP+14);
    marker(mLo,BASE+32,'low','#7dd3fc',-1);marker(mHi,BASE+32,'high','#7dd3fc',1);
  }
  if(st.mid>=0)marker(mMid,BASE+64,'mid',st.c===0?'#4ade80':'#fbbf24',0);
  g.textAlign='center';g.fillStyle='#475569';g.font='11px ui-monospace,monospace';
  g.fillText('O(log n) — worst case '+Math.ceil(Math.log2(N+1))+' comparisons',W/2,H-12);
  requestAnimationFrame(draw);
}
init();requestAnimationFrame(draw);
</script></body></html>