Note: No affiliate links and no sponsor. Both accounts are free plans. Every screenshot and video below came off my own screen. Claude's free plan runs Sonnet 5; Opus is locked behind Pro, so this is not a comparison of each company's best model.
I ran the same prompt through ChatGPT and Claude on free accounts, twice, with two different difficulty levels. I expected the harder task to separate them on polish. It separated them on whether the file opens at all.
Round 1: a cube in a room
"Build a 3D scene I can open in a browser. One single HTML file, no build step, no installs. A rotating cube inside a room, and I can look around by dragging the mouse. Give me the complete file."
Both delivered a file that runs. They did not agree on how.
Claude, round 1. Exactly what I asked for and nothing else.
ChatGPT, round 1. More than I asked for, including WASD movement I never requested.
| Claude | ChatGPT | |
|---|---|---|
| File size | 5,107 bytes | 14,931 bytes |
| Lines | 167 | 774 |
| External dependencies | 1 (three.js) | 0 |
ChatGPT wrote its own renderer. Not a figure of speech: it compiles its own shaders and issues its own draw calls.
gl_Position = uProjection * uView * worldPos; // its own vertex shader
const shader = gl.createShader(type);
gl.drawArrays(gl.TRIANGLES, 0, cube.count);
Claude imported a library and spoke in scene-graph terms:
const scene = new THREE.Scene();
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
const pointLight = new THREE.PointLight(0xffffff, 1.0, 20);
That is why one file is 167 lines and the other is 774.
The smaller file is the bigger download
Claude's file is a third the size on disk. Loading it is the other way around. With the cache off:
| Requests | Total transferred | |
|---|---|---|
| Claude | 3 | 124 KB |
| ChatGPT | 1 | 15 KB |
The 5 KB file pulls 119 KB of three.js behind it. The 15 KB file pulls nothing.
Then I cut the network
I blocked outside hosts and reloaded both from a local server. This is a plane, a locked-down office network, or any machine that cannot reach a CDN.
ChatGPT's file: canvas 900x600, scene renders, controls panel visible.
Claude's file: no canvas at all. A black screen with one line of text at the bottom, Click and drag to look around, floating over nothing. Its instruction survived. Its scene did not.
Claude's file with the CDN unreachable. The instruction is still there. The room is not.
My prompt said "no installs." Claude's file satisfies that literally, and there is nothing to install. But the dependency did not disappear; it moved from install time to open time, which is the moment you control least.
If the article stopped here it would read as a point for ChatGPT. Round 2 went the other way, hard.
Round 2: a solar system
"Build a 3D solar system I can explore in a browser. One single HTML file, no build step, no installs. The sun at the center, several planets orbiting at different speeds, at least one moon, a starfield background, and I can orbit the camera by dragging the mouse and zoom with the wheel. Make it look good. Give me the complete file."
Claude's file runs. Sun, labelled planets, orbit rings, an asteroid belt, Saturn's rings, a starfield, a speed slider, and a heads-up line reading Drag to orbit · Scroll / Pinch to zoom · Click a planet to focus. I dragged and scrolled it while recording, and the camera responds.
Claude, round 2. Recorded live below.
Nine seconds of Claude's file: the system running on its own, then a drag to orbit, then a wheel zoom. No edits, no speed-up.
ChatGPT's file does not run. It shows a black screen with a loading spinner that never resolves. Not a degraded scene. Nothing.
ChatGPT, round 2, after waiting. The spinner does not stop.
Note what flipped: this time both files imported three.js. ChatGPT's self-contained approach from round 1 did not carry over, which is the first sign that neither result generalizes.
Why it fails, exactly
I instrumented the page before navigation, because errors thrown during load are invisible if you attach the listener afterward. Two lines explain everything.
ChatGPT's file requests two scripts:
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/examples/js/controls/OrbitControls.js"></script>
That first one loads with a 200. The second is blocked (net::ERR_BLOCKED_BY_ORB), and then:
TypeError: THREE.OrbitControls is not a constructor
The examples/js/ folder was removed from three.js after r147. Version 0.160.0 does not contain that path. ChatGPT pinned a modern three.js and then reached for a script layout that version deleted, so the camera controls never exist and initialization stops before the scene is built.
This is not a CDN being flaky. It is a version mismatch the model introduced inside its own file, between two lines sitting three lines apart.
Claude avoided it by not importing controls at all. It pinned older three.js (r128) and wrote its own mouse and wheel handlers, which is why its file has six mouse-related handlers and no second dependency.
What I actually measured
| Round 1 cube | Round 2 solar system | |
|---|---|---|
| Claude | runs; breaks offline | runs |
| ChatGPT | runs; survives offline | does not run |
| Claude file | 167 lines, 1 CDN | 484 lines, 1 CDN |
| ChatGPT file | 774 lines, 0 CDN | 802 lines, 2 CDNs |
Every cell there is something I opened, ran, and checked in a browser with the network panel open.
What this does not prove
One prompt each, one run each, one day. ChatGPT wrote a dependency-free renderer for the easy task and a broken two-dependency file for the hard one. Run it again tomorrow and you may get the reverse. This is a sample of one and I am not extrapolating it into "Claude is better at 3D."
Both accounts are free, so this says nothing about the paid tiers. Claude's free plan showed Sonnet 5 next to the input box, with Opus 5, Opus 4.8 and Fable 5 all labelled "Pro upgrade" and unselectable.
What a free Claude account can pick. Sonnet 5 and Haiku 4.5, and nothing else. ChatGPT's free interface never showed me a model name at all, so I do not know what wrote either of its files.
I also never hit a rate limit on either account, so I cannot tell you where those limits sit.
The part worth keeping
Both rounds failed in the same shape, on opposite sides: a file that looks complete, presents itself as complete, and depends on something it does not mention.
Claude said "no server or install needed" while requiring a CDN at open time. ChatGPT shipped a <script> tag pointing at a path its own pinned version had deleted. Neither model flagged the dependency it had chosen, and in both cases the failure only appears when you actually open the file.
That is the practical takeaway, and it costs about ten seconds: before you trust a generated HTML file, look at the top of it for <script src="https://...">. Then open it once with your network off. Both of my failures would have shown up right there.