There are dozens of AI chatbots today, but most people only care about one thing: which one is actually the best for free?

Instead of reading marketing pages or benchmark charts, I ran the same prompts through ChatGPT, Gemini, Claude, and the newly released GLM myself - and compared what actually came back.


Quick AI Overview

1. ChatGPT
The most widely used AI assistant, with the broadest plugin/integration ecosystem and the largest general user base. Strong all-rounder for writing, coding, and daily tasks, and its free tier includes built-in image generation (DALL-E), which neither Claude nor GLM offer for free. Limitations: the free tier caps you at a limited number of top-model messages before falling back to a smaller model, and its answers can lean toward safe, formulaic structures compared to competitors.

2. Gemini
Google's flagship model, tightly integrated with Search, Workspace, and Android. Its biggest strength is native, real-time web access baked into how it answers, making it strong for anything current or fact-dependent, plus a large context window and free image generation via Imagen. Limitations: instruction-following can be less precise than Claude on complex, multi-step prompts, and some users find its free tier throttles under heavy use.

3. Claude
Known for producing the most natural, least "AI-sounding" prose, careful instruction-following, and strong performance on coding and long-document reasoning. Frequently leads published coding benchmarks among the three major labs. Limitations: no built-in image generation on the free (or paid) tier - it can analyze images but not create raster ones - and once you hit the free-tier session limit, Claude cuts you off entirely until it resets, rather than dropping you to a smaller model the way ChatGPT and Gemini do.

4. GLM
The newest name on this list, from Chinese lab Zhipu AI (Z.ai). It's an open-weight model (MIT licensed) that's rapidly closed the gap with Western frontier models, particularly on coding benchmarks, and its free coding-plan access punches well above what you'd expect from a "free" tier. Limitations: like Claude, no free raster image generation - only SVG/vector output - and availability of its top model can be inconsistent (more on this below).


How I Tested

  • Same prompt, typed identically into every AI - no edits, no re-rolls
  • Free plans only, default settings (no extended thinking/reasoning)
  • Judged on real usefulness, not benchmark scores
  • Screenshots included so you can see the raw output, not my paraphrase of it

Test 1: Blog / SEO Writing

Prompt:

Write a 200-word SEO-friendly article on why electric vehicles are becoming popular in 2026.

ChatGPT:

Why Electric Vehicles Are Becoming Popular in 2026

Electric vehicles (EVs) are becoming increasingly popular in 2026 as more people look for cleaner, smarter, and more affordable transportation. Rising fuel prices, improved battery technology, and expanding charging infrastructure have made EVs a practical choice for everyday driving.

Modern electric cars now offer longer driving ranges, faster charging times, and lower maintenance costs compared to traditional petrol and diesel vehicles. Governments worldwide continue to encourage EV adoption through tax incentives, subsidies, and stricter emissions regulations, making electric vehicles more accessible to consumers.

Another major reason for the growing popularity of electric vehicles in 2026 is environmental awareness. Drivers are choosing EVs to reduce carbon emissions and support sustainable transportation. Many manufacturers are also introducing stylish, feature-rich electric SUVs, sedans, and compact cars that appeal to a wide range of buyers.

Advancements in smart technology, including over-the-air software updates, advanced driver assistance systems, and connected features, have further enhanced the EV ownership experience. As battery costs continue to decline and renewable energy becomes more common, electric vehicles are expected to dominate the automotive industry.

With their economic, environmental, and technological advantages, electric vehicles are shaping the future of transportation and becoming the preferred choice for millions of drivers worldwide.

Gemini:

The shift toward electric vehicles (EVs) has officially moved from a futuristic trend to a mainstream reality. Driving down any highway, the surge of electric cars, SUVs, and trucks is unmistakable.

Several key factors drive this massive wave of adoption:

Unmatched Cost Savings: With volatile gas prices, charging an electric car remains significantly cheaper than fueling an internal combustion engine. Additionally, EVs have fewer moving parts, drastically reducing long-term maintenance and repair costs.

Infrastructure Breakthroughs: The "range anxiety" of the early 2020s is fading. Massive expansions in high-speed public charging networks mean drivers can power up quickly on road trips, making electric cars practical for everyone, not just urban commuters.

Performance and Technology: Modern EVs aren't just eco-friendly; they offer incredible performance. Drivers love the instant torque, smooth acceleration, and quiet rides, alongside cutting-edge, tech-forward infotainment systems.

Environmental Urgency: As climate concerns grow, consumers are aligning their purchases with their values. Opting for zero-emission transportation allows drivers to actively lower their carbon footprints.

Electric vehicles deliver the perfect mix of sustainability, advanced tech, and economic sense, securing their place as the definitive future of driving.

Claude:

Why Electric Vehicles Are Becoming Popular in 2026

Electric vehicles (EVs) have moved from a niche choice to a mainstream option in 2026, and several converging factors explain why. Battery technology has improved significantly, offering longer ranges, faster charging times, and lower production costs. This has made EVs more affordable and practical for everyday drivers, closing the price gap with traditional gasoline-powered cars.

Government incentives and stricter emissions regulations across many countries continue to push both manufacturers and consumers toward electric alternatives. Tax credits, rebates, and infrastructure investments have made switching to an EV more financially attractive than ever before.

Charging infrastructure has also expanded rapidly. Fast-charging networks now cover highways, cities, and residential areas, reducing range anxiety that once discouraged buyers. Many new apartment complexes and workplaces now include EV charging stations as a standard amenity.

Consumer attitudes are shifting too. Growing awareness of climate change and a desire for lower long-term running costs are driving demand. EVs require less maintenance than combustion engines, with no oil changes and fewer moving parts, saving owners money over time.

Automakers are responding with a wider range of models, from affordable compacts to luxury SUVs, giving buyers more choice than ever. As technology, infrastructure, and policy align, electric vehicles are set to become the new standard on roads worldwide.

GLM:

Why Are Electric Vehicles So Popular in 2026?

In 2026, electric vehicles (EVs) have shifted from a niche market to a global automotive standard. The rapid rise in EV popularity this year is driven by three major factors: unmatched battery range, affordable pricing, and expanding charging infrastructure.

First, solid-state battery technology has revolutionized the industry. Modern electric cars now easily exceed 400 miles per charge, completely eliminating range anxiety. Drivers can travel long distances without worrying about finding their next power source.

Second, EV prices have finally achieved parity with traditional gas-powered cars. Government tax incentives combined with streamlined manufacturing have made 2026 electric vehicles incredibly affordable. Consumers are eager to invest in electric cars to avoid volatile gas prices and reduce their overall carbon footprint.

Finally, the charging infrastructure is everywhere. Ultra-fast charging stations are now as common as traditional gas stations. These stations can fully recharge an EV in under ten minutes, making electric vehicle ownership more convenient than ever.

Furthermore, smart autonomous driving features are now standard in most new electric models. With lower maintenance costs, cutting-edge technology, and a massive push toward green energy, it is clear why electric vehicles are dominating the roads in 2026. The future of driving is fully electric.

Analysis πŸ”:

All four handled this well - the gap here is smaller than in any other test. No major errors, all four stayed close to the word count and topic.

ChatGPT played it safest: a traditional article structure, readable, but the most generic of the four.

Gemini broke the piece into bolded sub-points instead of prose - reads more like a listicle than an article, which trades off actual readability for surface-level skimmability.

Claude read the most like something a person would actually write: natural, un-hyped, and structured as a real article rather than a bullet dump - the strongest balance of SEO-friendly and genuinely readable.

GLM leaned hardest into specific-sounding claims ("exceed 400 miles per charge," "under ten minutes") - punchy, but unverified and slightly overconfident.

Winner πŸ†:

Claude, for writing something that reads like a real article rather than a listicle or a stats dump - genuinely the best balance of tone and SEO-readiness.

Test 2: Coding From Scratch

Prompt:

Build a responsive calculator using HTML, CSS, and JavaScript.
Tap the eye button to see the working calculator made by each AI chatbot

ChatGPT:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Responsive Calculator</title>

<style>
*{
    margin:0;
    padding:0;
    box-sizing:border-box;
    font-family:Arial, Helvetica, sans-serif;
}

body{
    min-height:100vh;
    display:flex;
    justify-content:center;
    align-items:center;
    background:linear-gradient(135deg,#1f2937,#111827);
    padding:20px;
}

.calculator{
    width:100%;
    max-width:380px;
    background:#20232b;
    border-radius:20px;
    padding:20px;
    box-shadow:0 15px 40px rgba(0,0,0,.4);
}

.display{
    width:100%;
    height:80px;
    border:none;
    outline:none;
    background:#111;
    color:#fff;
    font-size:2rem;
    text-align:right;
    padding:15px;
    border-radius:12px;
    margin-bottom:20px;
    overflow-x:auto;
}

.buttons{
    display:grid;
    grid-template-columns:repeat(4,1fr);
    gap:12px;
}

button{
    border:none;
    border-radius:12px;
    font-size:1.2rem;
    height:65px;
    cursor:pointer;
    transition:.15s;
    background:#323844;
    color:white;
}

button:hover{
    filter:brightness(1.15);
}

button:active{
    transform:scale(.96);
}

.operator{
    background:#f59e0b;
}

.equal{
    background:#10b981;
    grid-row:span 2;
    height:100%;
}

.zero{
    grid-column:span 2;
}

.clear{
    background:#ef4444;
}

@media (max-width:450px){
    .calculator{
        padding:15px;
    }

    button{
        height:58px;
        font-size:1.1rem;
    }

    .display{
        height:70px;
        font-size:1.8rem;
    }
}
</style>
</head>
<body>

<div class="calculator">

<input type="text" id="display" class="display" readonly>

<div class="buttons">

<button class="clear" onclick="clearDisplay()">AC</button>
<button onclick="deleteLast()">⌫</button>
<button class="operator" onclick="append('%')">%</button>
<button class="operator" onclick="append('/')">Γ·</button>

<button onclick="append('7')">7</button>
<button onclick="append('8')">8</button>
<button onclick="append('9')">9</button>
<button class="operator" onclick="append('*')">Γ—</button>

<button onclick="append('4')">4</button>
<button onclick="append('5')">5</button>
<button onclick="append('6')">6</button>
<button class="operator" onclick="append('-')">βˆ’</button>

<button onclick="append('1')">1</button>
<button onclick="append('2')">2</button>
<button onclick="append('3')">3</button>
<button class="operator" onclick="append('+')">+</button>

<button class="zero" onclick="append('0')">0</button>
<button onclick="append('.')">.</button>
<button class="equal" onclick="calculate()">=</button>

</div>
</div>

<script>
const display = document.getElementById("display");

function append(value){
    display.value += value;
}

function clearDisplay(){
    display.value = "";
}

function deleteLast(){
    display.value = display.value.slice(0,-1);
}

function calculate(){
    try{
        let expression = display.value.replace(/Γ—/g,'*').replace(/Γ·/g,'/');
        display.value = eval(expression);
    }
    catch{
        display.value = "Error";
        setTimeout(clearDisplay,1000);
    }
}

document.addEventListener("keydown",(e)=>{
    const key = e.key;

    if("0123456789+-*/.%".includes(key)){
        append(key);
    }
    else if(key==="Enter"){
        e.preventDefault();
        calculate();
    }
    else if(key==="Backspace"){
        deleteLast();
    }
    else if(key==="Escape"){
        clearDisplay();
    }
});
</script>

</body>
</html>

Gemini:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Responsive Calculator</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        }

        body {
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background-color: #f0f2f5;
            padding: 20px;
        }

        .calculator {
            width: 100%;
            max-width: 360px;
            background-color: #1c1c1c;
            border-radius: 24px;
            padding: 24px;
            box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
        }

        .display {
            display: flex;
            flex-direction: column;
            justify-content: flex-end;
            align-items: flex-end;
            min-height: 120px;
            padding: 10px;
            margin-bottom: 20px;
            word-wrap: break-word;
            word-break: break-all;
            text-align: right;
        }

        .previous-operand {
            color: rgba(255, 255, 255, 0.5);
            font-size: 1.1rem;
            min-height: 1.5rem;
            margin-bottom: 5px;
        }

        .current-operand {
            color: #ffffff;
            font-size: 2.5rem;
            font-weight: 300;
            max-width: 100%;
        }

        .buttons {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 12px;
        }

        button {
            border: none;
            border-radius: 50%;
            aspect-ratio: 1;
            font-size: 1.5rem;
            font-weight: 500;
            cursor: pointer;
            transition: background-color 0.15s ease, transform 0.05s ease;
            user-select: none;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        button:active {
            transform: scale(0.95);
        }

        /* Number Buttons */
        .btn-number {
            background-color: #333333;
            color: #ffffff;
        }

        .btn-number:hover {
            background-color: #444444;
        }

        /* Operator Buttons (+, -, *, /) */
        .btn-operator {
            background-color: #ff9f0a;
            color: #ffffff;
        }

        .btn-operator:hover {
            background-color: #cc7f08;
        }

        /* Function Buttons (Clear, Delete, Percentage) */
        .btn-function {
            background-color: #a5a5a5;
            color: #000000;
        }

        .btn-function:hover {
            background-color: #8e8e8e;
        }

        /* Span equal button across two column spaces */
        .span-two {
            grid-column: span 2;
            border-radius: 40px;
            aspect-ratio: auto;
            height: 100%;
        }

        /* Responsive scaling for smaller mobile screens */
        @media (max-width: 350px) {
            .calculator {
                padding: 16px;
            }
            .buttons {
                gap: 8px;
            }
            button {
                font-size: 1.25rem;
            }
            .current-operand {
                font-size: 2rem;
            }
        }
    </style>
</head>
<body>

    <div class="calculator">
        <div class="display">
            <div id="previous-operand" class="previous-operand"></div>
            <div id="current-operand" class="current-operand">0</div>
        </div>
        <div class="buttons">
            <button class="btn-function" onclick="clearDisplay()">AC</button>
            <button class="btn-function" onclick="deleteNumber()">DEL</button>
            <button class="btn-function" onclick="appendOperator('%')">%</button>
            <button class="btn-operator" onclick="appendOperator('/')">Γ·</button>

            <button class="btn-number" onclick="appendNumber('7')">7</button>
            <button class="btn-number" onclick="appendNumber('8')">8</button>
            <button class="btn-number" onclick="appendNumber('9')">9</button>
            <button class="btn-operator" onclick="appendOperator('*')">Γ—</button>

            <button class="btn-number" onclick="appendNumber('4')">4</button>
            <button class="btn-number" onclick="appendNumber('5')">5</button>
            <button class="btn-number" onclick="appendNumber('6')">6</button>
            <button class="btn-operator" onclick="appendOperator('-')">βˆ’</button>

            <button class="btn-number" onclick="appendNumber('1')">1</button>
            <button class="btn-number" onclick="appendNumber('2')">2</button>
            <button class="btn-number" onclick="appendNumber('3')">3</button>
            <button class="btn-operator" onclick="appendOperator('+')">+</button>

            <button class="btn-number" onclick="appendNumber('0')">0</button>
            <button class="btn-number" onclick="appendNumber('.')">.</button>
            <button class="btn-operator span-two" onclick="compute()">=</button>
        </div>
    </div>

    <script>
        const previousOperandText = document.getElementById('previous-operand');
        const currentOperandText = document.getElementById('current-operand');
        
        let currentOperand = '0';
        let previousOperand = '';
        let operation = undefined;
        let shouldResetDisplay = false;

        function clearDisplay() {
            currentOperand = '0';
            previousOperand = '';
            operation = undefined;
            updateDisplay();
        }

        function deleteNumber() {
            if (currentOperand === '0') return;
            if (currentOperand.length === 1) {
                currentOperand = '0';
            } else {
                currentOperand = currentOperand.slice(0, -1);
            }
            updateDisplay();
        }

        function appendNumber(number) {
            if (shouldResetDisplay) {
                currentOperand = '';
                shouldResetDisplay = false;
            }
            if (number === '.' && currentOperand.includes('.')) return;
            if (currentOperand === '0' && number !== '.') {
                currentOperand = number;
            } else {
                currentOperand = currentOperand.toString() + number.toString();
            }
            updateDisplay();
        }

        function appendOperator(selectedOperation) {
            if (currentOperand === '') return;
            
            // Allow computing percentage immediately if no existing operation
            if (selectedOperation === '%') {
                currentOperand = (parseFloat(currentOperand) / 100).toString();
                updateDisplay();
                return;
            }

            if (previousOperand !== '') {
                compute();
            }
            
            operation = selectedOperation;
            previousOperand = currentOperand;
            shouldResetDisplay = true;
            updateDisplay();
        }

        function compute() {
            let computation;
            const prev = parseFloat(previousOperand);
            const current = parseFloat(currentOperand);
            if (isNaN(prev) || isNaN(current)) return;

            switch (operation) {
                case '+':
                    computation = prev + current;
                    break;
                case '-':
                    computation = prev - current;
                    break;
                case '*':
                    computation = prev * current;
                    break;
                case '/':
                    if (current === 0) {
                        alert("Cannot divide by zero");
                        clearDisplay();
                        return;
                    }
                    computation = prev / current;
                    break;
                default:
                    return;
            }

            // Fix potential floating point precision errors (e.g., 0.1 + 0.2)
            currentOperand = parseFloat(computation.toFixed(10)).toString();
            operation = undefined;
            previousOperand = '';
            shouldResetDisplay = true;
            updateDisplay();
        }

        function getDisplaySymbol(op) {
            if (op === '*') return 'Γ—';
            if (op === '/') return 'Γ·';
            return op;
        }

        function updateDisplay() {
            currentOperandText.innerText = currentOperand;
            if (operation != null) {
                previousOperandText.innerText = `${previousOperand} ${getDisplaySymbol(operation)}`;
            } else {
                previousOperandText.innerText = '';
            }
        }
    </script>
</body>
</html>

Claude:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Calculator</title>
<style>
  @import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Archivo:wght@500;700;900&display=swap');

  :root{
    --amber: #ff8c1a;
    --amber-dim: #a85c10;
    --bg-deep: #14110f;
    --case: #232019;
    --case-light: #2c2822;
    --key-face: #302b23;
    --key-face-hi: #3a342b;
    --key-op: #423225;
    --key-op-hi: #4d3b2c;
    --key-eq: #ff8c1a;
    --screen-bg: #1b1712;
    --ink: #f4ead8;
    --ink-dim: #8a8074;
    --shadow-deep: rgba(0,0,0,0.6);
  }

  *{ box-sizing: border-box; }

  html, body{
    height: 100%;
    margin: 0;
  }

  body{
    background:
      radial-gradient(circle at 20% 10%, #221d17 0%, transparent 55%),
      radial-gradient(circle at 80% 90%, #1a1611 0%, transparent 55%),
      var(--bg-deep);
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: 'Archivo', sans-serif;
    padding: 24px;
    position: relative;
    overflow-x: hidden;
  }

  /* subtle grain */
  body::before{
    content: "";
    position: fixed;
    inset: 0;
    pointer-events: none;
    opacity: 0.05;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  }

  .device{
    width: 100%;
    max-width: 380px;
    background: linear-gradient(160deg, var(--case-light), var(--case) 60%);
    border-radius: 28px;
    padding: 22px 18px 26px;
    box-shadow:
      0 40px 80px -20px var(--shadow-deep),
      0 2px 0 rgba(255,255,255,0.03) inset,
      0 -30px 60px -40px rgba(0,0,0,0.5) inset;
    position: relative;
    border: 1px solid rgba(255,255,255,0.04);
  }

  .brand{
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    padding: 4px 8px 16px;
    color: var(--ink-dim);
  }
  .brand .name{
    font-family: 'Space Mono', monospace;
    font-size: 12px;
    letter-spacing: 0.28em;
    text-transform: uppercase;
    color: var(--amber-dim);
  }
  .brand .model{
    font-size: 10px;
    letter-spacing: 0.15em;
    font-family: 'Space Mono', monospace;
    opacity: 0.6;
  }

  .screen{
    background: var(--screen-bg);
    border-radius: 14px;
    padding: 22px 18px 16px;
    margin-bottom: 18px;
    box-shadow:
      inset 0 4px 14px rgba(0,0,0,0.7),
      inset 0 0 0 1px rgba(0,0,0,0.6);
    position: relative;
    min-height: 108px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    overflow: hidden;
  }

  .screen::after{
    content:"";
    position:absolute;
    inset:0;
    background: repeating-linear-gradient(
      to bottom,
      rgba(255,255,255,0.015) 0px,
      rgba(255,255,255,0.015) 1px,
      transparent 1px,
      transparent 3px
    );
    pointer-events:none;
  }

  .history{
    font-family: 'Space Mono', monospace;
    font-size: 14px;
    color: var(--ink-dim);
    min-height: 18px;
    text-align: right;
    letter-spacing: 0.02em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-shadow: 0 0 8px rgba(255,140,26,0.15);
  }

  .display{
    font-family: 'Space Mono', monospace;
    font-weight: 700;
    font-size: clamp(32px, 9vw, 44px);
    color: var(--amber);
    text-align: right;
    line-height: 1.1;
    letter-spacing: 0.01em;
    text-shadow: 0 0 18px rgba(255,140,26,0.45), 0 0 2px rgba(255,140,26,0.6);
    overflow-x: auto;
    white-space: nowrap;
    scrollbar-width: none;
  }
  .display::-webkit-scrollbar{ display:none; }

  .pad{
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 10px;
  }

  button{
    border: none;
    outline: none;
    font-family: 'Archivo', sans-serif;
    font-weight: 700;
    font-size: 19px;
    color: var(--ink);
    background: linear-gradient(160deg, var(--key-face-hi), var(--key-face));
    border-radius: 12px;
    padding: 18px 0;
    cursor: pointer;
    position: relative;
    box-shadow:
      0 3px 0 rgba(0,0,0,0.45),
      0 6px 10px rgba(0,0,0,0.35),
      inset 0 1px 0 rgba(255,255,255,0.06);
    transition: transform 0.06s ease, box-shadow 0.06s ease;
    -webkit-tap-highlight-color: transparent;
    user-select: none;
  }

  button:active{
    transform: translateY(3px);
    box-shadow:
      0 0 0 rgba(0,0,0,0.45),
      0 2px 4px rgba(0,0,0,0.3),
      inset 0 1px 0 rgba(255,255,255,0.06);
  }

  button.op{
    background: linear-gradient(160deg, var(--key-op-hi), var(--key-op));
    color: var(--amber);
  }

  button.func{
    color: #d9c9b0;
    font-size: 16px;
    background: linear-gradient(160deg, #363028, #2a251e);
  }

  button.eq{
    background: linear-gradient(160deg, #ffab4d, var(--key-eq));
    color: #2a1400;
    box-shadow:
      0 3px 0 #a85c10,
      0 6px 14px rgba(255,140,26,0.35),
      inset 0 1px 0 rgba(255,255,255,0.3);
  }
  button.eq:active{
    box-shadow:
      0 0 0 #a85c10,
      0 2px 6px rgba(255,140,26,0.25),
      inset 0 1px 0 rgba(255,255,255,0.3);
  }

  .span2{ grid-column: span 2; }

  @media (max-width: 360px){
    button{ font-size: 17px; padding: 16px 0; }
    .display{ font-size: clamp(26px, 8vw, 36px); }
  }

  .screws span{
    position: absolute;
    width: 6px; height: 6px;
    border-radius: 50%;
    background: radial-gradient(circle at 35% 35%, #55493a, #14110f 70%);
    box-shadow: 0 1px 1px rgba(255,255,255,0.05);
  }
  .screws .tl{ top: 12px; left: 12px; }
  .screws .tr{ top: 12px; right: 12px; }
  .screws .bl{ bottom: 12px; left: 12px; }
  .screws .br{ bottom: 12px; right: 12px; }
</style>
</head>
<body>

<div class="device">
  <div class="screws">
    <span class="tl"></span><span class="tr"></span><span class="bl"></span><span class="br"></span>
  </div>

  <div class="brand">
    <span class="name">Ember&nbsp;Β·&nbsp;41</span>
    <span class="model">SOLARΒ·MK2</span>
  </div>

  <div class="screen">
    <div class="history" id="history">&nbsp;</div>
    <div class="display" id="display">0</div>
  </div>

  <div class="pad">
    <button class="func" data-action="clear">AC</button>
    <button class="func" data-action="negate">Β±</button>
    <button class="func" data-action="percent">%</button>
    <button class="op" data-action="operator" data-op="Γ·">Γ·</button>

    <button data-action="digit" data-val="7">7</button>
    <button data-action="digit" data-val="8">8</button>
    <button data-action="digit" data-val="9">9</button>
    <button class="op" data-action="operator" data-op="Γ—">Γ—</button>

    <button data-action="digit" data-val="4">4</button>
    <button data-action="digit" data-val="5">5</button>
    <button data-action="digit" data-val="6">6</button>
    <button class="op" data-action="operator" data-op="βˆ’">βˆ’</button>

    <button data-action="digit" data-val="1">1</button>
    <button data-action="digit" data-val="2">2</button>
    <button data-action="digit" data-val="3">3</button>
    <button class="op" data-action="operator" data-op="+">+</button>

    <button class="span2" data-action="digit" data-val="0">0</button>
    <button data-action="decimal">.</button>
    <button class="eq" data-action="equals">=</button>
  </div>
</div>

<script>
(function(){
  const displayEl = document.getElementById('display');
  const historyEl = document.getElementById('history');

  let current = "0";
  let previous = null;
  let operator = null;
  let justEvaluated = false;
  const MAX_LEN = 14;

  function updateDisplay(){
    displayEl.textContent = formatForDisplay(current);
  }

  function formatForDisplay(str){
    if (str === "Error") return str;
    // avoid overly long numbers breaking layout
    if (str.replace(/[-.]/g,'').length > MAX_LEN){
      const num = parseFloat(str);
      return num.toExponential(6);
    }
    return str;
  }

  function updateHistory(text){
    historyEl.textContent = text || "\u00A0";
  }

  function inputDigit(d){
    if (justEvaluated){
      current = d;
      justEvaluated = false;
      updateHistory("");
      updateDisplay();
      return;
    }
    if (current === "0"){
      current = d;
    } else if (current.replace(/[-.]/g,'').length < MAX_LEN){
      current += d;
    }
    updateDisplay();
  }

  function inputDecimal(){
    if (justEvaluated){
      current = "0.";
      justEvaluated = false;
      updateHistory("");
      updateDisplay();
      return;
    }
    if (!current.includes(".")){
      current += ".";
      updateDisplay();
    }
  }

  function clearAll(){
    current = "0";
    previous = null;
    operator = null;
    justEvaluated = false;
    updateHistory("");
    updateDisplay();
  }

  function negate(){
    if (current === "0") return;
    current = current.startsWith("-") ? current.slice(1) : "-" + current;
    updateDisplay();
  }

  function percent(){
    const val = parseFloat(current);
    if (isNaN(val)) return;
    current = trimNumber(val / 100);
    updateDisplay();
  }

  function trimNumber(num){
    if (!isFinite(num)) return "Error";
    let s = num.toPrecision(12);
    if (s.includes(".")) {
      s = s.replace(/0+$/,"").replace(/\.$/,"");
    }
    // handle scientific notation from toPrecision on very small/large
    if (s.includes("e") || s.includes("E")){
      return String(num);
    }
    return s;
  }

  function compute(a, b, op){
    a = parseFloat(a);
    b = parseFloat(b);
    switch(op){
      case "+": return a + b;
      case "βˆ’": return a - b;
      case "Γ—": return a * b;
      case "Γ·": return b === 0 ? NaN : a / b;
      default: return b;
    }
  }

  function setOperator(op){
    if (operator && previous !== null && !justEvaluated){
      const result = compute(previous, current, operator);
      if (isNaN(result) || !isFinite(result)){
        current = "Error";
        previous = null;
        operator = null;
        justEvaluated = true;
        updateHistory("");
        updateDisplay();
        return;
      }
      current = trimNumber(result);
    }
    previous = current;
    operator = op;
    justEvaluated = false;
    updateHistory(`${previous} ${operator}`);
    // next digit press should start fresh
    current = "0";
    updateDisplay();
    // show previous+op in history, current will reset on next digit
    justEvaluated = true; // reuse flag so next digit overwrites the "0"
  }

  function equals(){
    if (operator === null || previous === null) return;
    const result = compute(previous, current, operator);
    if (isNaN(result) || !isFinite(result)){
      updateHistory("");
      current = "Error";
      previous = null;
      operator = null;
      justEvaluated = true;
      updateDisplay();
      return;
    }
    updateHistory(`${previous} ${operator} ${current} =`);
    current = trimNumber(result);
    previous = null;
    operator = null;
    justEvaluated = true;
    updateDisplay();
  }

  document.querySelectorAll('button').forEach(btn => {
    btn.addEventListener('click', () => {
      const action = btn.dataset.action;
      if (current === "Error" && action !== "clear"){
        clearAll();
      }
      switch(action){
        case "digit": inputDigit(btn.dataset.val); break;
        case "decimal": inputDecimal(); break;
        case "clear": clearAll(); break;
        case "negate": negate(); break;
        case "percent": percent(); break;
        case "operator": setOperator(btn.dataset.op); break;
        case "equals": equals(); break;
      }
    });
  });

  // keyboard support
  window.addEventListener('keydown', (e) => {
    if (e.key >= "0" && e.key <= "9"){ inputDigit(e.key); return; }
    if (e.key === "."){ inputDecimal(); return; }
    if (e.key === "Enter" || e.key === "="){ e.preventDefault(); equals(); return; }
    if (e.key === "Backspace"){
      if (current.length > 1) current = current.slice(0, -1);
      else current = "0";
      updateDisplay();
      return;
    }
    if (e.key === "Escape"){ clearAll(); return; }
    const opMap = { "+": "+", "-": "βˆ’", "*": "Γ—", "/": "Γ·" };
    if (opMap[e.key]){ setOperator(opMap[e.key]); return; }
  });

  updateDisplay();
})();
</script>

</body>
</html>

GLM:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NEXUS - Scientific Calculator</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
  :root {
    --bg: #0a0a0f;
    --bg-soft: #12121a;
    --card: rgba(22, 22, 30, 0.7);
    --border: rgba(255, 165, 0, 0.12);
    --border-strong: rgba(255, 165, 0, 0.28);
    --fg: #fafafa;
    --fg-soft: #a1a1aa;
    --muted: #71717a;
    --accent: #f59e0b;
    --accent-2: #fb923c;
    --accent-glow: rgba(245, 158, 11, 0.45);
    --teal: #2dd4bf;
    --teal-glow: rgba(45, 212, 191, 0.4);
    --danger: #ef4444;
    --danger-glow: rgba(239, 68, 68, 0.4);
    --radius: 18px;
    --shadow-lg: 0 30px 60px -15px rgba(0, 0, 0, 0.7);
  }

  * { box-sizing: border-box; margin: 0; padding: 0; }

  html, body { height: 100%; }

  body {
    font-family: 'Space Grotesk', sans-serif;
    background: var(--bg);
    color: var(--fg);
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    position: relative;
    overflow-x: hidden;
    -webkit-font-smoothing: antialiased;
  }

  /* Background: layered gradients + subtle grid + floating orbs */
  body::before {
    content: '';
    position: fixed; inset: 0;
    background:
      radial-gradient(circle at 18% 20%, rgba(245, 158, 11, 0.18), transparent 45%),
      radial-gradient(circle at 82% 75%, rgba(45, 212, 191, 0.12), transparent 50%),
      radial-gradient(circle at 50% 50%, rgba(251, 146, 60, 0.05), transparent 60%),
      linear-gradient(180deg, #0a0a0f 0%, #0d0d14 100%);
    z-index: -2;
  }
  body::after {
    content: '';
    position: fixed; inset: 0;
    background-image:
      linear-gradient(rgba(245, 158, 11, 0.025) 1px, transparent 1px),
      linear-gradient(90deg, rgba(245, 158, 11, 0.025) 1px, transparent 1px);
    background-size: 60px 60px;
    mask-image: radial-gradient(circle at center, black 30%, transparent 80%);
    -webkit-mask-image: radial-gradient(circle at center, black 30%, transparent 80%);
    z-index: -1;
    pointer-events: none;
  }

  .orb {
    position: fixed;
    border-radius: 50%;
    filter: blur(80px);
    z-index: -1;
    pointer-events: none;
    opacity: 0.6;
  }
  .orb-1 {
    width: 420px; height: 420px;
    background: var(--accent);
    top: -150px; left: -120px;
    animation: drift1 18s ease-in-out infinite;
  }
  .orb-2 {
    width: 380px; height: 380px;
    background: var(--teal);
    bottom: -150px; right: -100px;
    animation: drift2 22s ease-in-out infinite;
  }
  @keyframes drift1 {
    0%, 100% { transform: translate(0, 0); }
    50% { transform: translate(80px, 60px); }
  }
  @keyframes drift2 {
    0%, 100% { transform: translate(0, 0); }
    50% { transform: translate(-60px, -40px); }
  }

  /* Calculator shell */
  .calc-shell {
    width: 100%;
    max-width: 440px;
    background: var(--card);
    backdrop-filter: blur(30px);
    -webkit-backdrop-filter: blur(30px);
    border: 1px solid var(--border);
    border-radius: 28px;
    overflow: hidden;
    box-shadow: var(--shadow-lg), 0 0 0 1px rgba(255,255,255,0.02) inset;
    position: relative;
  }

  /* Top status bar */
  .calc-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 18px 22px 12px;
    border-bottom: 1px solid rgba(255,255,255,0.04);
  }
  .brand {
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .brand-dot {
    width: 8px; height: 8px;
    background: var(--accent);
    border-radius: 50%;
    box-shadow: 0 0 12px var(--accent-glow);
    animation: pulse 2s ease-in-out infinite;
  }
  @keyframes pulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50% { opacity: 0.5; transform: scale(0.8); }
  }
  .brand-name {
    font-weight: 700;
    font-size: 13px;
    letter-spacing: 0.18em;
    color: var(--fg);
  }
  .status {
    display: flex;
    gap: 14px;
    align-items: center;
    font-family: 'JetBrains Mono', monospace;
    font-size: 11px;
    color: var(--muted);
  }
  .status .mode {
    color: var(--accent);
    padding: 3px 8px;
    border: 1px solid var(--border-strong);
    border-radius: 6px;
    background: rgba(245, 158, 11, 0.08);
  }

  /* Display */
  .display {
    padding: 28px 22px 24px;
    min-height: 180px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    position: relative;
  }
  .expression {
    font-family: 'JetBrains Mono', monospace;
    font-size: 15px;
    color: var(--fg-soft);
    text-align: right;
    min-height: 22px;
    word-wrap: break-word;
    overflow-wrap: break-word;
    word-break: break-all;
    margin-bottom: 8px;
    opacity: 0.7;
  }
  .result {
    font-family: 'JetBrains Mono', monospace;
    font-size: 56px;
    font-weight: 500;
    text-align: right;
    color: var(--fg);
    line-height: 1;
    word-wrap: break-word;
    overflow-wrap: break-word;
    word-break: break-all;
    transition: color 0.2s ease;
  }
  .result.error { color: var(--danger); font-size: 32px; }
  .result.flash { animation: resultFlash 0.4s ease; }
  @keyframes resultFlash {
    0% { transform: scale(1); }
    40% { transform: scale(1.04); color: var(--accent); }
    100% { transform: scale(1); }
  }

  .caret {
    display: inline-block;
    width: 2px;
    height: 1em;
    background: var(--accent);
    margin-left: 2px;
    vertical-align: middle;
    animation: blink 1.1s steps(2) infinite;
  }
  @keyframes blink { 50% { opacity: 0; } }

  /* History strip */
  .history-strip {
    border-top: 1px solid rgba(255,255,255,0.04);
    border-bottom: 1px solid rgba(255,255,255,0.04);
    padding: 10px 22px;
    max-height: 88px;
    overflow-y: auto;
    font-family: 'JetBrains Mono', monospace;
    font-size: 12px;
    color: var(--muted);
    display: flex;
    flex-direction: column;
    gap: 4px;
    scrollbar-width: thin;
    scrollbar-color: var(--border-strong) transparent;
  }
  .history-strip::-webkit-scrollbar { width: 4px; }
  .history-strip::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 4px; }
  .history-item {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    cursor: pointer;
    padding: 3px 0;
    border-radius: 4px;
    transition: color 0.15s ease;
  }
  .history-item:hover { color: var(--accent); }
  .history-item .expr { opacity: 0.6; }
  .history-item .val { color: var(--accent-2); }
  .history-empty {
    text-align: center;
    opacity: 0.4;
    padding: 6px 0;
    letter-spacing: 0.1em;
    font-size: 10px;
  }

  /* Keypad */
  .keypad {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 10px;
    padding: 18px;
  }

  .key {
    position: relative;
    border: 1px solid rgba(255,255,255,0.06);
    background: linear-gradient(180deg, rgba(40, 40, 50, 0.6), rgba(20, 20, 28, 0.6));
    color: var(--fg);
    font-family: 'Space Grotesk', sans-serif;
    font-size: 22px;
    font-weight: 500;
    height: 64px;
    border-radius: 16px;
    cursor: pointer;
    transition: transform 0.08s ease, box-shadow 0.15s ease, background 0.15s ease, border-color 0.15s ease;
    overflow: hidden;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
  }
  .key::before {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(circle at 50% 0%, rgba(255,255,255,0.08), transparent 60%);
    pointer-events: none;
  }
  .key:hover {
    border-color: var(--border-strong);
    background: linear-gradient(180deg, rgba(50, 50, 60, 0.7), rgba(28, 28, 36, 0.7));
  }
  .key:active, .key.pressed {
    transform: translateY(2px) scale(0.97);
  }
  .key.pressed {
    box-shadow: 0 0 24px var(--accent-glow), 0 0 0 1px var(--accent) inset;
  }

  .key.op {
    color: var(--accent);
    font-weight: 600;
  }
  .key.op.pressed {
    box-shadow: 0 0 24px var(--accent-glow), 0 0 0 1px var(--accent) inset;
  }
  .key.fn {
    color: var(--teal);
    font-size: 16px;
    font-weight: 600;
  }
  .key.fn.pressed {
    box-shadow: 0 0 24px var(--teal-glow), 0 0 0 1px var(--teal) inset;
  }
  .key.eq {
    background: linear-gradient(135deg, var(--accent), var(--accent-2));
    color: #1a1208;
    font-weight: 700;
    border-color: transparent;
    box-shadow: 0 8px 20px -6px var(--accent-glow);
  }
  .key.eq:hover {
    background: linear-gradient(135deg, var(--accent-2), var(--accent));
    box-shadow: 0 12px 28px -6px var(--accent-glow);
  }
  .key.eq.pressed {
    box-shadow: 0 0 32px var(--accent-glow), 0 0 0 2px var(--accent) inset;
  }
  .key.clear {
    color: var(--danger);
  }
  .key.clear.pressed {
    box-shadow: 0 0 24px var(--danger-glow), 0 0 0 1px var(--danger) inset;
  }
  .key.span2 { grid-column: span 2; }

  /* Bottom hint */
  .hint {
    text-align: center;
    padding: 8px 18px 16px;
    font-size: 10px;
    color: var(--muted);
    letter-spacing: 0.15em;
    text-transform: uppercase;
  }
  .hint kbd {
    font-family: 'JetBrains Mono', monospace;
    background: rgba(255,255,255,0.05);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 4px;
    padding: 2px 6px;
    color: var(--fg-soft);
    margin: 0 2px;
    font-size: 10px;
  }

  /* Responsive */
  @media (max-width: 480px) {
    body { padding: 12px; }
    .calc-shell { border-radius: 22px; }
    .keypad { gap: 8px; padding: 14px; }
    .key { height: 56px; font-size: 20px; border-radius: 14px; }
    .key.fn { font-size: 14px; }
    .result { font-size: 46px; }
    .display { padding: 22px 18px 18px; min-height: 150px; }
    .hint { display: none; }
  }
  @media (max-width: 360px) {
    .key { height: 50px; font-size: 18px; }
    .result { font-size: 38px; }
  }

  /* Reduced motion */
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.01ms !important;
      transition-duration: 0.01ms !important;
    }
  }
</style>
</head>
<body>

<div class="orb orb-1"></div>
<div class="orb orb-2"></div>

<main class="calc-shell" role="application" aria-label="Calculator">
  <!-- Top bar -->
  <header class="calc-top">
    <div class="brand">
      <span class="brand-dot" aria-hidden="true"></span>
      <span class="brand-name">NEXUS</span>
    </div>
    <div class="status" aria-live="polite">
      <span class="mode" id="modeIndicator">STD</span>
      <span id="memoryIndicator" style="opacity:0; transition: opacity 0.2s;">M</span>
    </div>
  </header>

  <!-- Display -->
  <section class="display" aria-label="Calculator display">
    <div class="expression" id="expression" aria-live="polite"></div>
    <div class="result" id="result" aria-live="polite" aria-atomic="true">0</div>
  </section>

  <!-- History -->
  <section class="history-strip" id="historyStrip" aria-label="Calculation history">
    <div class="history-empty">NO HISTORY YET</div>
  </section>

  <!-- Keypad -->
  <section class="keypad" role="group" aria-label="Calculator keys">
    <button class="key clear" data-action="clear" aria-label="All clear">AC</button>
    <button class="key fn" data-action="sign" aria-label="Toggle sign">Β±</button>
    <button class="key fn" data-action="percent" aria-label="Percent">%</button>
    <button class="key op" data-op="/" aria-label="Divide">Γ·</button>

    <button class="key" data-num="7">7</button>
    <button class="key" data-num="8">8</button>
    <button class="key" data-num="9">9</button>
    <button class="key op" data-op="*" aria-label="Multiply">Γ—</button>

    <button class="key" data-num="4">4</button>
    <button class="key" data-num="5">5</button>
    <button class="key" data-num="6">6</button>
    <button class="key op" data-op="-" aria-label="Subtract">βˆ’</button>

    <button class="key" data-num="1">1</button>
    <button class="key" data-num="2">2</button>
    <button class="key" data-num="3">3</button>
    <button class="key op" data-op="+" aria-label="Add">+</button>

    <button class="key span2" data-num="0">0</button>
    <button class="key" data-num=".">.</button>
    <button class="key eq" data-action="equals" aria-label="Equals">=</button>
  </section>

  <footer class="hint">
    <kbd>0-9</kbd> input <kbd>+-*/</kbd> operators <kbd>Enter</kbd> equals <kbd>Esc</kbd> clear <kbd>⌫</kbd> backspace
  </footer>
</main>

<script>
(function () {
  'use strict';

  // ---- State ----
  const state = {
    current: '0',         // current number being typed (string)
    previous: null,       // stored operand for chained operations (number|null)
    operator: null,       // pending operator
    expression: '',       // visible expression text
    justEvaluated: false, // true right after '=' pressed
    history: [],          // [{expr, val}]
  };

  // ---- DOM refs ----
  const $result = document.getElementById('result');
  const $expression = document.getElementById('expression');
  const $historyStrip = document.getElementById('historyStrip');
  const $modeIndicator = document.getElementById('modeIndicator');

  // ---- Helpers ----
  const OP_SYMBOL = { '+': '+', '-': 'βˆ’', '*': 'Γ—', '/': 'Γ·' };

  function formatNumber(n) {
    if (n === null || n === undefined) return '';
    if (typeof n !== 'number' || !isFinite(n)) return String(n);
    // Trim very long decimals without losing precision unexpectedly
    if (Math.abs(n) < 1e-10) return '0';
    let s;
    if (Math.abs(n) >= 1e15 || (Math.abs(n) < 1e-6 && n !== 0)) {
      s = n.toExponential(6).replace(/\.?0+e/, 'e');
    } else {
      // Round to 12 significant digits to tame float errors
      s = parseFloat(n.toPrecision(12)).toString();
    }
    // Add thousands separators to integer part
    const [intPart, decPart] = s.split('.');
    const neg = intPart.startsWith('-');
    const intDigits = neg ? intPart.slice(1) : intPart;
    const withSep = intDigits.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    let out = (neg ? '-' : '') + withSep;
    if (decPart) out += '.' + decPart;
    // Handle exponential format display
    if (s.includes('e')) {
      return s.replace(/(\d)(?=(\d{3})+\b)/g, '$1,');
    }
    return out;
  }

  function operate(a, b, op) {
    switch (op) {
      case '+': return a + b;
      case '-': return a - b;
      case '*': return a * b;
      case '/':
        if (b === 0) return NaN;
        return a / b;
    }
    return b;
  }

  function updateDisplay() {
    $result.textContent = state.current;
    $expression.textContent = state.expression;
  }

  function flashResult() {
    $result.classList.remove('flash');
    // force reflow to restart animation
    void $result.offsetWidth;
    $result.classList.add('flash');
  }

  function setError(msg) {
    state.current = msg || 'Error';
    state.previous = null;
    state.operator = null;
    state.expression = '';
    state.justEvaluated = true;
    $result.classList.add('error');
    updateDisplay();
  }

  function clearError() {
    if ($result.classList.contains('error')) {
      $result.classList.remove('error');
    }
  }

  // ---- Input handlers ----
  function inputDigit(d) {
    clearError();

    if (state.justEvaluated) {
      // Start fresh after '='
      state.current = d === '.' ? '0.' : d;
      state.previous = null;
      state.operator = null;
      state.expression = '';
      state.justEvaluated = false;
      updateDisplay();
      return;
    }

    if (state.operator && state.current === '') {
      // We had an operator queued; begin second operand
      state.current = d === '.' ? '0.' : d;
    } else if (d === '.') {
      if (!state.current.includes('.')) {
        state.current = state.current + '.';
      }
    } else {
      if (state.current === '0') {
        state.current = d;
      } else {
        // Limit length to avoid runaway strings
        if (state.current.replace(/[^0-9]/g, '').length < 15) {
          state.current = state.current + d;
        }
      }
    }
    updateDisplay();
  }

  function inputOperator(op) {
    clearError();
    const cur = parseFloat(state.current);

    if (state.operator && state.current !== '' && state.previous !== null) {
      // Chain: compute previous op first
      const result = operate(state.previous, cur, state.operator);
      if (!isFinite(result) || isNaN(result)) {
        setError('Math Error');
        return;
      }
      state.previous = result;
      state.current = '';
      state.operator = op;
      state.expression = formatNumber(result) + ' ' + OP_SYMBOL[op];
    } else {
      if (!isFinite(cur) || isNaN(cur)) {
        setError('Error');
        return;
      }
      state.previous = cur;
      state.current = '';
      state.operator = op;
      state.expression = formatNumber(cur) + ' ' + OP_SYMBOL[op];
    }
    state.justEvaluated = false;
    updateDisplay();
  }

  function equals() {
    clearError();
    if (state.operator === null || state.previous === null) {
      // Nothing to compute
      if (state.current !== '') {
        state.expression = formatNumber(parseFloat(state.current)) + ' =';
        state.justEvaluated = true;
        flashResult();
      }
      updateDisplay();
      return;
    }

    const cur = state.current === '' ? state.previous : parseFloat(state.current);
    const result = operate(state.previous, cur, state.operator);

    const exprText = formatNumber(state.previous) + ' ' + OP_SYMBOL[state.operator] + ' ' + formatNumber(cur) + ' =';

    if (!isFinite(result) || isNaN(result)) {
      setError('Math Error');
      // Still record attempt in history
      addHistory(exprText, 'Error');
      return;
    }

    addHistory(exprText, formatNumber(result));

    state.current = formatNumber(result);
    state.expression = exprText;
    state.previous = null;
    state.operator = null;
    state.justEvaluated = true;
    flashResult();
    updateDisplay();
  }

  function allClear() {
    clearError();
    state.current = '0';
    state.previous = null;
    state.operator = null;
    state.expression = '';
    state.justEvaluated = false;
    updateDisplay();
  }

  function backspace() {
    clearError();
    if (state.justEvaluated) {
      allClear();
      return;
    }
    if (state.current === '') {
      // We're after operator press - revert: drop operator, restore previous as current
      if (state.operator && state.previous !== null) {
        state.current = formatNumber(state.previous);
        state.previous = null;
        state.operator = null;
        state.expression = '';
      }
      updateDisplay();
      return;
    }
    if (state.current.length <= 1 || (state.current.length === 2 && state.current.startsWith('-'))) {
      state.current = '0';
    } else {
      state.current = state.current.slice(0, -1);
    }
    updateDisplay();
  }

  function toggleSign() {
    clearError();
    if (state.current === '' || state.current === '0') return;
    if (state.current.startsWith('-')) {
      state.current = state.current.slice(1);
    } else {
      state.current = '-' + state.current;
    }
    updateDisplay();
  }

  function percent() {
    clearError();
    if (state.current === '') return;
    const cur = parseFloat(state.current);
    let result;
    if (state.previous !== null && state.operator) {
      // percent of previous operand (typical calc behavior)
      result = state.previous * (cur / 100);
    } else {
      result = cur / 100;
    }
    state.current = formatNumber(result);
    updateDisplay();
  }

  // ---- History ----
  function addHistory(expr, val) {
    state.history.unshift({ expr, val });
    if (state.history.length > 30) state.history.pop();
    renderHistory();
  }

  function renderHistory() {
    if (state.history.length === 0) {
      $historyStrip.innerHTML = '<div class="history-empty">NO HISTORY YET</div>';
      return;
    }
    $historyStrip.innerHTML = state.history.map((h, i) =>
      `<div class="history-item" data-idx="${i}" tabindex="0" role="button" aria-label="Recall result ${h.val}">
        <span class="expr">${escapeHtml(h.expr)}</span>
        <span class="val">${escapeHtml(h.val)}</span>
      </div>`
    ).join('');
  }

  function escapeHtml(s) {
    return String(s).replace(/[&<>"']/g, c => ({
      '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'
    }[c]));
  }

  // Click history item to recall value
  $historyStrip.addEventListener('click', (e) => {
    const item = e.target.closest('.history-item');
    if (!item) return;
    const idx = parseInt(item.dataset.idx, 10);
    if (isNaN(idx) || !state.history[idx]) return;
    const val = state.history[idx].val;
    // Strip commas from formatted value for re-input
    const numStr = val.replace(/,/g, '');
    if (state.justEvaluated) {
      state.current = numStr;
      state.previous = null;
      state.operator = null;
      state.expression = '';
      state.justEvaluated = false;
    } else {
      state.current = numStr;
    }
    clearError();
    updateDisplay();
  });

  // ---- Key bindings ----
  const keyToAction = {
    '0':'num','1':'num','2':'num','3':'num','4':'num',
    '5':'num','6':'num','7':'num','8':'num','9':'num','.':'num',
    '+':'op','-':'op','*':'op','/':'op',
    'Enter':'equals','=':'equals',
    'Escape':'clear','Backspace':'backspace',
    '%':'percent'
  };

  document.addEventListener('keydown', (e) => {
    // Don't hijack if user is in an input/textarea (none here, but safe)
    if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;

    const action = keyToAction[e.key];
    if (!action) return;
    e.preventDefault();

    if (action === 'num') inputDigit(e.key);
    else if (action === 'op') inputOperator(e.key);
    else if (action === 'equals') equals();
    else if (action === 'clear') allClear();
    else if (action === 'backspace') backspace();
    else if (action === 'percent') percent();

    // Visual feedback: find matching button and flash it
    flashKey(e.key);
  });

  function flashKey(key) {
    let btn = null;
    if (/[0-9.]/.test(key)) {
      btn = document.querySelector(`[data-num="${key}"]`);
    } else if (['+','-','*','/'].includes(key)) {
      btn = document.querySelector(`[data-op="${key}"]`);
    } else if (key === 'Enter' || key === '=') {
      btn = document.querySelector('[data-action="equals"]');
    } else if (key === 'Escape') {
      btn = document.querySelector('[data-action="clear"]');
    } else if (key === 'Backspace') {
      // No dedicated backspace button - animate AC briefly
      btn = document.querySelector('[data-action="clear"]');
    } else if (key === '%') {
      btn = document.querySelector('[data-action="percent"]');
    }
    if (btn) {
      btn.classList.add('pressed');
      setTimeout(() => btn.classList.remove('pressed'), 120);
    }
  }

  // ---- Button click handling (event delegation) ----
  document.querySelector('.keypad').addEventListener('click', (e) => {
    const btn = e.target.closest('.key');
    if (!btn) return;

    // Pressed animation
    btn.classList.add('pressed');
    setTimeout(() => btn.classList.remove('pressed'), 120);

    if (btn.dataset.num !== undefined) {
      inputDigit(btn.dataset.num);
    } else if (btn.dataset.op !== undefined) {
      inputOperator(btn.dataset.op);
    } else if (btn.dataset.action !== undefined) {
      const a = btn.dataset.action;
      if (a === 'equals') equals();
      else if (a === 'clear') allClear();
      else if (a === 'sign') toggleSign();
      else if (a === 'percent') percent();
    }
  });

  // ---- Init ----
  updateDisplay();
  renderHistory();

  // Subtle: blink mode indicator on activity (visual flourish)
  let activityTimer = null;
  function pulseMode() {
    $modeIndicator.style.transition = 'color 0.1s ease, background 0.1s ease';
    $modeIndicator.style.color = '#1a1208';
    $modeIndicator.style.background = 'var(--accent)';
    clearTimeout(activityTimer);
    activityTimer = setTimeout(() => {
      $modeIndicator.style.color = '';
      $modeIndicator.style.background = '';
    }, 140);
  }
  document.querySelector('.keypad').addEventListener('click', pulseMode);
  document.addEventListener('keydown', pulseMode);
})();
</script>
</body>
</html>

Analysis πŸ”:

This is where the four really pulled apart. All four produced working calculators, but the design philosophy split down the middle.

ChatGPT delivered exactly what you'd picture - clean, dark-themed, functional, nothing surprising, genuinely production-usable.

Gemini went similarly minimal, with iOS-style circular buttons - safe and solid, same "no-frills" camp as ChatGPT.

Claude took it somewhere else: a full "retro hardware" calculator with a fake brand name, screw details, CRT scanlines, and an amber LED aesthetic - optimizing for visual delight over minimalism.

GLM went even further: a glassmorphic "NEXUS" calculator with animated orbs, a clickable history panel, and external font/icon imports - genuinely more ambitious, though heavier than the prompt technically asked for.

Winner πŸ†:

GLM, for the most ambitious output that added real functionality (clickable history), not just visual polish.

Test 3: Debugging Broken Code

Prompt:

        body {
  font-family: Georgia, serif;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.6;
}
      There is a problem that when any element is too big for the body it expands the body size rather than clamping itself inside of it

Fix this code and explain what was wrong
      

ChatGPT:

        The issue is not actually with the body styles. By default, CSS allows the <html> and <body> elements to expand to fit overflowing content.

The body doesn't "contain" this elementβ€”it grows to accommodate it, causing the page to become wider and creating horizontal scrolling.

If you want children to never make the page wider, add an overflow rule:
      html,
body {
  margin: 0;
  overflow-x: hidden;
}
      What was wrong?

In CSS, overflow is handled by the overflowing element or by an ancestor with an overflow property. The body is not a clipping container by default, so oversized children can extend beyond the viewport and make the document scrollable.
      

Gemini:

        The reason this happens is because, by default, the body element expands to fit its content, even if that content overflows the screen width. To stop elements from breaking your layout, you need to tell the body to strictly hide anything that spills out.

Here is the fixed code:
        body {
  font-family: Georgia, serif;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.6;

  /* Add This */
  overflow-x: hidden;
}
        What went wrong?

No overflow rules: Adding overflow-x: hidden; cuts off any content that tries to poke out horizontally past the edge of the screen, forcing it to stay contained.
      

Claude:

        The fix: add these rules so the body stops stretching and instead clips/wraps content that's too big.
        body {
  font-family: Georgia, serif;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.6;
  max-width: 100%;
  overflow-x: hidden;
  margin: 0;
}
        What was wrong?

The problem was your CSS only sets font styling - there's nothing telling the body to contain its children. By default, a <body> (and most containers) will grow to fit whatever's inside it, even if a child element (a wide image, a long unbroken word, a fixed-width div) is bigger than the screen.
      

GLM:

        Add these two lines to your CSS:
        body {
  font-family: Georgia, serif;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.6;

  /* Add these: */
  max-width: 100vw;
  overflow-x: hidden;
}
        What was wrong?

By default, web pages let elements spill outside the edges of the screen if they are too wide.

Adding overflow-x: hidden tells the browser to cut off and hide anything that goes past the left or right edges of the screen, stopping it from stretching your page and creating an unwanted horizontal scrollbar. max-width: 100vw simply ensures the body never tries to be wider than the screen itself.
      

Analysis πŸ”:

This is the test that actually separated the AIs, because I could verify the fix myself: ChatGPT and Gemini's suggested fix didn't solve my problem. All four diagnosed the root cause correctly, but only two gave a fix that worked in practice.

ChatGPT suggested overflow-x: hidden alone - explained well, but didn't actually fix the issue.

Gemini gave the same fix as ChatGPT, with the same result: confident explanation, incomplete solution.

Claude added max-width: 100% alongside overflow-x: hidden - and it worked.

GLM added max-width: 100vw alongside overflow-x: hidden - also worked, same approach as Claude.

Winner πŸ†:

Claude, for the cleanest correct fix. GLM matched it functionally, but Claude's max-width: 100% is the more standard, widely-taught approach over GLM's max-width: 100vw.

Test 4: Explaining a Concept Simply

Prompt:

Explain quantum computing to a 12-year-old in brief.

ChatGPT:

Quantum computing is a new way of using computers to solve some problems much faster than regular computers.

A normal computer uses bits, which can be either 0 or 1. A quantum computer uses qubits, which can be 0, 1, or both at the same time. This special ability helps it explore many possible answers at once.

Imagine you're trying to find your way through a huge maze. A regular computer checks one path after another, while a quantum computer can explore many paths at the same time, making it much faster for certain tasks.

Quantum computers are still being developed, but they could one day help scientists discover new medicines, improve weather forecasting, and solve very complex problems. They won't replace regular computers, but they will be useful for special kinds of jobs.

Gemini:

Imagine your regular computer uses tiny light switches to think. These switches can only be turned off, which stands for a zero, or turned on, which stands for a one. Everything your computer does, from video games to homework, is just millions of these ones and zeros.

A quantum computer doesn't use regular switches. It uses magical quantum switches that can be both off and on at the exact same time. Think of it like spinning a coin on a table. While it is spinning, it isn't just heads or tails; it is a blur of both.

Because its switches can do this trick, a quantum computer can test thousands of answers all at once instead of checking them one by one. This lets them solve incredibly massive puzzles, like designing brand-new medicines or figuring out the absolute fastest delivery routes for the whole planet, in just a few seconds. They won't replace your phone or gaming console, but they will solve giant problems that regular computers are too slow to ever fix.

Claude:

Regular computers store information as bits - tiny switches that are either 0 or 1, like a light switch that's on or off.

Quantum computers use something called qubits. Thanks to weird physics rules (called quantum mechanics), a qubit can be 0, 1, or a mix of both at the same time. This is called superposition. Imagine a spinning coin - while it's spinning, it's not really heads or tails, it's kind of both until it lands.

Qubits can also be linked together in a special way called entanglement, so changing one instantly affects the other, even if they're far apart.

Because of superposition and entanglement, a quantum computer can explore many possible answers to a problem all at once, instead of trying them one at a time like a regular computer. This makes them potentially much faster at certain hard problems, like breaking codes, discovering new medicines, or simulating molecules.

They're not better at everything, though - for normal tasks like browsing the internet or playing games, regular computers are still the way to go. Quantum computers are special tools for special problems, kind of like a race car is great on a track but not for grocery shopping.

GLM:

Imagine a regular computer is like a coin. It can only be heads (1) or tails (0). Everything your phone or laptop does is just millions of these coins flipping really fast.

A quantum computer is different. Imagine spinning a coin in the air. While it is spinning, it is a blur of both heads and tails at the exact same time. 

Because a quantum computer can look at many different possibilities all at once instead of one by one, it is incredibly fast. It could solve giant puzzles, like finding a cure for a disease or predicting the weather, in seconds. A regular computer would take thousands of years to figure those same puzzles out!

Analysis πŸ”:

All four leaned on the same core analogy - a spinning coin for superposition - which says more about how AI models converge than anything else. What separated them was depth and restraint.

ChatGPT's maze analogy was solid and age-appropriate, if a bit plain.

Gemini read the most naturally kid-friendly ("magical quantum switches"), though "magical" slightly undersells the physics.

Claude was the only one to add entanglement alongside superposition - more complete, but longer than "brief" called for.

GLM was the shortest and punchiest, but skipped entanglement and leaned on an exaggerated claim ("thousands of years").

Winner πŸ†:

Claude, for the only explanation that's actually complete - covering both superposition and entanglement without sacrificing clarity. Gemini reads better line-by-line, but Claude is the one that leaves you knowing more.

Test 5: Image Generation

Prompt:

Create a cinematic cyberpunk city at night.
Note: Claude and GLM don't generate raster images - but they can generate SVG graphics. For this test, I generated SVG images and converted to PNG.

ChatGPT:

A cinematic cyberpunk city at night made by chatgpt

Gemini:

A cinematic cyberpunk city at night made by gemini

Claude:

A cinematic cyberpunk city at night made by claude

GLM:

A cinematic cyberpunk city at night made by glm

Analysis πŸ”:

This test isn't a fair fight, and it's worth saying so upfront. ChatGPT and Gemini generate real raster images; Claude and GLM only produce SVGs on the free tier, converted to PNG here.

ChatGPT went moody and atmospheric - a lone figure overlooking a neon skyline.

Gemini went bustling and street-level - rain, signage, reflections, closer to "cinematic" in the genre sense.

Claude's SVG is a minimal skyline silhouette with a gradient sky - more abstract poster art than cyberpunk city.

GLM's SVG leans pixel-art/retro, closer to a synthwave album cover than a photorealistic scene.

Winner πŸ†:

Gemini and ChatGPT for actual image generation - Gemini slightly ahead on detail. Claude and GLM are usable for vector art, but this category isn't built for what they do.


Scoreboard

Category ChatGPT Gemini Claude GLM
Writing ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
Coding ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
Debugging ⭐⭐ ⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐
Reasoning ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐
Images ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐ ⭐⭐

Pros & Cons

ChatGPT
Pros Real raster image generation on free tier; solid, dependable, no-frills writing and coding; consistent quality across every test
Cons Debugging fix was incomplete and didn't actually solve the reported issue; output tends to be the most "generic" of the four, both visually and in writing tone
Gemini
Pros Best writing structure for SEO out of the box; clearest, most naturally kid-friendly explanations; strong photorealistic image generation
Cons Same incomplete-fix problem as ChatGPT on the debugging test; can lean into slightly informal or "flowery" phrasing that may need editing for professional use
Claude
Pros Correctly fixed the CSS bug the other two got wrong; won both writing (natural tone, real article structure) and reasoning (only complete explanation) tests; most visually ambitious and polished code output
Cons No raster image generation on the free plan at all; once you hit the free-tier limit, Claude stops responding entirely until it resets, rather than falling back to a smaller model like ChatGPT and Gemini do; answers can run longer than needed when brevity was requested
GLM
Pros Correctly fixed the CSS bug; added genuinely useful extra functionality (clickable history) unprompted in the coding test; most concise explanations; generous usage limits during off-peak hours
Cons Top model is frequently unavailable during peak hours, making it unreliable when you actually need it in the moment; no raster image generation, same SVG-only limitation as Claude; occasionally states specific-sounding numbers/facts with more confidence than they deserve

Conclusion

No single overall winner - recommend by use case once results are in
  • πŸ† Best Overall: ChatGPT - the most consistently dependable across every test, with no single glaring weakness
  • πŸ† Best Writer: Claude - the only one that read like a genuinely written article, not a listicle or a stats dump
  • πŸ† Best Programmer: GLM - correctly fixed the bug the others missed and shipped the most ambitious, feature-complete code
  • πŸ† Best at Explaining Things: Claude - the only complete explanation, covering both superposition and entanglement
  • πŸ† Best Free Images: Gemini - most detailed, most "cinematic" of the four
  • πŸ† Best Completely Free AI: GLM - most capable per free chat, if you can catch it outside peak hours. If it's unavailable, Claude is the best fallback: same free tier that can correctly fix real bugs, just watch for the hard session cutoff

Every AI here wins at least one category, which is really the point: there's no single "best" pick, only the best pick for what you're doing right now. Debugging alone flipped two of the four rankings I'd have expected going in. Run your own prompts before committing to one. And let me know your observations through the comment section below.