3310.love
A pixel-accurate Snake II running on a 3D Nokia 3310 in your browser. The real game logic, the real LCD green, Finnish radio, the whole experience.
The Idea
In the autumn of 2000, Nokia released the 3310. It cost 189 markka. Within two years, 126 million units had shipped. For many people it was their first phone — and Snake II was the reason they wore out the 5 key.
The 3310 can't connect to modern networks anymore, but the feeling of playing Snake at 2am under the covers is universal. I wanted to recreate that feeling in a browser — not as a flat retro clone, but as a full 3D scene you can orbit around, with the actual game running on the phone's screen.

The Phone
The centerpiece is a 3D Nokia 3310 loaded from an FBX model with four PBR texture maps — albedo, normal, metalness, and roughness — applied to a MeshPhysicalMaterial in Three.js. The material uses clearcoat for the subtle sheen of injection-molded plastic.
The LCD screen is a separate plane mesh floating just inside the phone's screen window. A custom onBeforeCompile shader on the phone body makes the screen area transparent (alpha 0.09) while keeping PBR reflections on the plastic cover — so you see the LCD underneath with a layer of clear plastic on top catching light from the environment. The same shader darkens the bezel frame around the screen to match how the raised plastic looks in real life.
A second custom shader on the screen mesh adds a dot-matrix overlay: 84 columns by 48 rows of softened circles at 4% opacity, simulating the individual LCD pixels. The shader also handles contrast and brightness adjustments to get the exact feel of the original display.
Clickable Buttons
Every button on the phone is clickable. A raycaster fires from the camera through the mouse position and intersects the 3D model. When a hit lands, the local-space coordinates are tested against calibrated bounding boxes for each button zone — the d-pad, menu button, C button, and all 12 keys of the numpad. Each zone maps to a game action (up, down, left, right, ok, back).
When you press a button, the vertex shader physically deforms the mesh inward at that zone, creating a press animation that decays over time. It looks and feels like pressing a real button.

The Game
Snake II is recreated with pixel accuracy on the 84x48 pixel display. The game runs on a 23x13 cell grid at 8 pixels per cell, with a 14-pixel header bar showing the score and bonus indicators.
Faithful Mechanics
The speed table matches the original: level 1 runs at 658ms per tick, accelerating through 15 speed indices down to 8ms per tick at the fastest setting. The snake starts as 7 segments facing right. Bonus food appears every 5-7 nibbles, occupying two cells and despawning after 20 ticks. The "full" bulge animation propagates down the snake's body when it eats. The death sequence blinks the snake 10 times at 270ms intervals before transitioning to the game over screen.
Eight maze layouts match the original game. The menu system — New Game, Level, Top Score, Select — is rendered in a 3x5 pixel font matching the original Nokia display font.
The game state machine handles eight states: splash, menu, menuLevel, leaderboard, playing, paused, dying, and gameover. A shared dispatch router maps both keyboard and button inputs to the correct action for the current state.
The Atmosphere
Five HDRI environments place the phone in different scenes — a sunflower field, Shanghai's Bund at night, an autumn park, a Sicilian beach, and a golden sunset on rocky plains. Each environment loads a 2K HDR file for reflections and a 4K JPEG backplate for the background. A scene picker at the bottom of the screen cycles between them.

Post-processing runs four passes through an EffectComposer: Unreal Bloom (strength 0.06, threshold 0.92), a custom color grading shader with 8 presets (cinematic warm, cool blue, vintage, noir, teal-orange, cross-process, faded film), a vignette shader, and SMAA anti-aliasing. Ambient dust particles float through the scene. The camera opens with a 2.5-second intro dolly that eases out with a cubic curve into the final position.
Finnish Radio
Seven live YLE radio stations stream through the Web Audio API: Radio Suomi, Radio 1, YleX, Klassinen, Vega, Sámi, and X3M. The audio signal passes through a three-stage EQ chain — 300Hz highpass, 1.8kHz peaking boost, 6kHz lowpass — simulating the frequency response of the 3310's tiny piezo speaker.

When you switch stations, an FM scrub sound plays before the new stream fades in. There are four scrub variations, each synthesized in real-time from noise buffers run through a speaker-simulation filter chain: a dial sweep (bandpass frequency ramp), interstation hiss (broadband static), flutter (LFO-modulated noise simulating a weak signal), and crackle (noise with random pops). Each scrub picks a different variation from the last to keep it feeling natural.
Sound Effects
All game sounds are synthesized square-wave tones through the Web Audio API — no audio files. A 12-voice pool with time-based slot tracking handles polyphony. Each voice routes through a shared speaker simulation chain: 400Hz highpass, 3.8kHz lowpass, then into a dry/wet split with a short delay-based reverb (40ms delay, 15% feedback) for room ambiance. The eat sound is a two-note rise (B4→D5). Death is a four-note descending phrase (F#5→D5→A4→E4). Menu selection is a quick two-note chirp (A5→B5).
The Details That Matter
A Win98-style Internet Explorer "About" popup — draggable by the title bar, resizable via a corner grip — with a navigation sidebar linking to Home, Leaderboard, Guestbook, and Credits pages. The address bar reads http://www.3310.love and updates as you navigate. Min, max, and close buttons work exactly like Windows 98. It's a full retro web experience nested inside a 3D one.

The share page leans into the Y2K aesthetic with a table of sharing methods: E-Mail (opens a mailto link), Copy Link, AIM Messenger, ICQ, Print This Page ("Preparing to print..."), and Save to Floppy Disk ("Drive A: not found :-)"). Each method has a period-accurate pixel-art icon. A webring section and link-to-us banner complete the 2001 experience.

A local leaderboard stores your top 5 high scores in localStorage. The loading screen. The camera orbit. A Nokia pixel font. The visitor counter at the bottom of the About page. Every detail is a nod to the era.
Technical Stack
| Layer | Detail |
|---|---|
| Runtime | Vanilla JS — ES modules, no framework, no bundler runtime |
| 3D Engine | Three.js r182 — PBR materials, FBX loader, EffectComposer |
| Model | Nokia 3310 FBX with albedo, normal, metalness, and roughness maps |
| Shaders | 4 custom onBeforeCompile patches — LCD transparency window, dot-matrix pixel grid, bezel darkening, vertex-deformed button press |
| Post-FX | Unreal Bloom, custom color grading (8 math-based LUT presets), vignette, SMAA |
| Audio | Web Audio API — 12-voice square-wave synth with speaker EQ, 7 live YLE radio streams with FM scrub synthesis |
| Game | 8-state machine, 15-step speed table, 8×8 sprite atlas, 3×5 pixel font, 8 maze layouts |
| Build | Vite 7 — tree-shakes debug panel from production |
| Tests | Vitest |
| Deploy | GitHub Pages |
Architecture
The codebase splits cleanly between game logic and rendering. The game/ directory is pure logic with no DOM or Three.js dependencies — the snake state machine, LCD canvas renderer, input dispatch, sprite data, font rasterizer, maze layouts, and leaderboard. The scene/ directory handles everything visual — the Three.js scene, phone model loading, shader customization, button raycasting, camera control, post-processing, particles, UI overlays, and the Win98 popups. The audio/ directory owns both the synthesized sound effects and the radio streaming.

This is a love letter to the phone that started it all — built in Brooklyn by someone who wore out the 5 key.