Ski downhill, dodge trees, land tricks for bonus points, and try to outlast the dog chasers and the snowman that ends every run. By default you have nine lives; toggle the 9 button to play 1-life classic. Pick a cat color and a mountain theme, then press SPACE to start.
| Input | Action |
|---|---|
| Arrow keys or WASD | Steer left and right |
| Down arrow or S | Tuck for higher speed |
| Up arrow or W | Carve for lower speed |
| 1, 2, 3 (in mid-air) | Trick: Meow Roll, Whisker Twist, Cat Nap |
| Space | Start, retry, or resume |
| P or Esc | Pause |
| R | Restart the current run |
On phones and tablets, an on-screen joystick replaces the keyboard. The trick buttons sit alongside the joystick and a SWAP button lets you put either control on whichever side fits your thumb.
Cat Ski 1989 is a free browser game inspired by SkiFree, the 1991 release that shipped in Microsoft Entertainment Pack 3 for Windows 3.0. The original presented an endless downhill ski run with trees, jumps, and an unkillable abominable yeti that eventually appeared and ended every run. Cat Ski 1989 recreates the same loop with a cat as the player character. The game includes three regional mountain themes (Alps, Rockies, Himalayas), four cat color variants, an in-air trick system with chained scoring, gold escape ramps that spawn alongside each dog wave, and a snowman that takes the role of the original yeti. Difficulty is tunable through two sliders, with four named presets for casual players.
The game ships with a 9-lives casual mode enabled by default. Each crash, dog grab, or snowman chomp consumes a life, with a brief invincibility window before the next hit registers. A toggle on the picker row drops the cat back to 1-life classic for players who want the original SkiFree edge. A global top-3 leaderboard sits at the top of the page; it splits into separate boards for 9-lives and 1-life so the two modes stay apples-to-apples. Submitting a name is optional and only happens after a qualifying run. The UI is bilingual (English and rioplatense Spanish) via a slider on the controls row.
The project is open source at github.com/ashtonmorrow/cat-ski. It was developed by Mike Lee using Claude as a coding collaborator. The full game is one self-contained HTML file with no build step or framework, and depends only on Google Fonts at runtime plus a Supabase REST endpoint for the leaderboard. It is installable as a Progressive Web App for offline play. Per-difficulty best scores persist locally; the global leaderboard is shared across all players.
Before starting, the player chooses a cat color (black, tabby, calico, or orange) and a mountain theme (Alps, Rockies, or Himalayas). Both selections persist across sessions through localStorage.
The score increases with distance traveled, plus bonuses for collected flags, completed jumps, and chained tricks. Each subsequent trick within a single jump compounds the bonus, so chaining several tricks before landing produces a higher reward than performing them across separate jumps.
After a fixed time, dogs begin to appear at the top of the screen and chase the cat down the slope. Each dog crosses the cat's screen position at a calculable moment determined by its descent rate. The player can dodge horizontally before the crossing, or launch from a gold escape ramp to be airborne while the dog passes. After the configured number of dog waves are cleared, a snowman appears within a randomized window and ends the run on contact. The snowman is the project's tribute to the original SkiFree yeti.
Difficulty is set with two sliders. Intensity (0.8x to 5x) scales speed, obstacle density, spawn cadence, and dog tracking. Dog count (1 to 10) sets how many dog waves appear before the snowman. Four named presets (EASY, MEDIUM, HARD, and INSTA DEATH) snap both sliders to canonical positions for players who do not want to tune individual parameters.
Lives mode (the 9 button on the picker row) gives the cat nine lives instead of one. Each crash, dog grab, or snowman chomp consumes a life with a brief invincibility window before the next hit can register. The global leaderboard splits by mode, so 9-lives runs and 1-life runs are scored on separate boards.
The game is contained in a single HTML file of approximately 140 KB, with a separate service worker for offline support and a manifest file for PWA installability. There is no build step, framework, or runtime dependency beyond two Google Fonts. The constraint of keeping the game in one file is partly a tribute to the original SkiFree, which fit in 60 KB on a 3.5-inch floppy disk. The practical benefit is that every change is deployable in under a minute. A push to the GitHub repository triggers an automatic Vercel rebuild, and the new version is served from the CDN with no build pipeline to wait for.
| Component | Implementation |
|---|---|
| Rendering | HTML5 Canvas at 360x270 internal resolution, scaled to viewport with image-rendering: pixelated |
| Game loop | Vanilla JavaScript, requestAnimationFrame |
| Audio | Web Audio API, all sounds synthesized at runtime via OscillatorNode and filtered BufferSource noise |
| Sprites | Procedural drawing with ctx.fillRect calls; no image assets at runtime |
| State persistence | localStorage for cat color, theme, difficulty, and best score |
| Hosting | Vercel, deployed from the GitHub repository on push |
| PWA | Single service worker, app shell precache, network-first for HTML |
Each system in the game started with a written criteria question before any code was written. The cat sprite needed to read as a cat at 16 pixels wide and support four color variants without forking the drawing code. The solution was a procedural drawing function parameterized by a palette object, which produces all four cat colors from a single set of geometry.
The dog chase needed to be escapable through skill but not through extended tucking. Dogs in the game move down the screen at a constant rate relative to the camera, so the moment they cross the cat's screen position is mathematically predictable. The player has one timing window per dog to either dodge horizontally or launch from a bonus ramp. The bonus ramps spawn at distances calculated from the player's current speed and the dog's descent rate, so the airborne window aligns with the crossing moment regardless of whether the player is carving slowly or tucking fast.
Difficulty needed to satisfy both casual players who want presets and players who want to fine-tune individual parameters. The solution is two continuous sliders (intensity 0.8x to 5x, dog count 1 to 10) paired with four preset buttons that snap the sliders to canonical positions. New visitors land on EASY by default, which matches the casual experience. Players who want more challenge use the sliders or pick HARD or INSTA DEATH.
The first version of the three mountain themes used generic palettes: a pinky-cream Rockies, a default cool-blue Alps, and a default-cool Himalayas. The result felt synthetic. To make each region read as the actual mountain range, the palettes were rebuilt from descriptions of how each range typically photographs.
| Range | Description used as input | Palette character |
|---|---|---|
| Alps | Cool crisp blue snow, deep emerald Norway spruce, granite-gray peaks fading blue with atmospheric haze | Cool-saturated, classic Swiss alpine |
| Rockies | Warm afternoon light on sandstone and exposed granite, slightly bluer-green lodgepole pine, rust-toned distant peaks | Warm earth tones, golden-hour reading |
| Himalayas | Deep saturated alpine sky, brilliant white snow, near-black slate rock, sparse dusty juniper at low elevations | High contrast, austere, dramatic |
The same description-first approach extended to the obstacles. Norway spruce silhouettes were drawn for the Alps, lodgepole pine (taller and narrower, with a visible trunk between branch tiers) for the Rockies, and stunted krummholz juniper (short and gnarled) for the Himalayas. The Rockies use aspen logs with white bark and black eye scars, since aspen is widely associated with the American Rocky Mountain landscape. The Himalayas use granite boulders rather than logs, because trees do not occur above the tree line where the game is conceptually set. Each species was researched, the silhouette was described in plain language, and Claude produced approximately 30 lines of fillRect calls per variant.
The project was built with Claude functioning as a design collaborator rather than purely a code-writing assistant. The pattern that worked across the build was three questions asked before any new feature.
For tasks involving aesthetic judgment, the prompt approach mattered. Asking Claude to produce a Rockies palette returned generic results. Describing the visual character of the source ("warm afternoon light on sandstone, slightly bluer-green pine, rust-toned distant peaks") returned palettes that read as the place. The same approach worked for sounds. Describing the intent ("a dog double-bark for a dramatic entrance"; "a trick chain that ascends in pitch as the chain grows") produced OscillatorNode envelopes that matched the description.
The constraint that made the iteration loop effective was the no-build deployment. Each change reached the live site in under a minute, which made the cycle of pushing, observing, and refining fast enough that the production environment doubled as the development environment. Across approximately 50 commits, this cadence affected the final quality more than any individual technical choice.
The full toolchain for a project like this is free and small enough to learn in an afternoon. You need a code editor, a browser, a Git client, a GitHub account, and a Vercel account. Claude is the writing tool; you can use the web interface at claude.ai or the Claude Code CLI from a terminal. No frameworks, build systems, or asset pipelines are required for a single-file game.
The workflow starts in a Claude conversation. Describe what you want to build in plain language, including the visual character, gameplay mechanics, audience, and any constraints such as a single-file output or a maximum file size. Claude produces a starting scaffold. From there, the iteration loop is short: describe a change, get the updated code, paste it into the editor, refresh the browser, and decide what to refine. Commit each working change to Git. Push to a GitHub repository connected to a Vercel project, and the new version is live within a minute. To add a custom subdomain, create a CNAME record at your DNS provider that points the subdomain to cname.vercel-dns.com, then assign the domain in the Vercel project settings.
Three practices kept the build moving. First, treat the conversation with Claude as design work rather than code generation. State the criteria, propose the simplest approach, and identify the trade-offs before any feature is built. Second, deploy frequently. Ship each working commit so the live site stays current with the project. Third, when working on aesthetic decisions, describe the source material in concrete terms. Asking Claude for a palette inspired by "warm afternoon light on sandstone" produces hex codes that read as the place; asking for a palette that feels "rugged" returns generic results. The same pattern holds for sounds, sprites, and animation timing: the more specific the input, the more usable the output.
Cat Ski 1989 is a free, single-page browser game with no accounts, no signups, and no email collection. The data the site does touch is described in full below so a player can decide what to consent to.
The site loads Google Analytics 4 to count plays and surface basic usage patterns (page views, country-level location, device type). GA is configured with Consent Mode v2 and defaults to denied, which means no GA cookies are written and no data is sent until the visitor accepts the cookie banner. Declining the banner leaves analytics off for that browser; the decision is stored in localStorage and respected on subsequent visits. GA does not collect names, email addresses, IP addresses (Google anonymizes the last octet by default), or any personally identifying information.
The game writes the following to the browser's localStorage on the device it runs on. None of it leaves the device:
All localStorage entries are prefixed with skior_. Clearing browser storage for this site removes them entirely.
When a player chooses to submit a high score, the site sends the entered name (up to five characters), the score, and the run's settings (intensity, dog count, lives mode, theme) to a hosted database. The name is whatever the player types; it is not connected to any account or identifier. There is no IP address logging on the database side. Anyone can read the global top-3 through the same anonymous channel, which is what powers the strip at the top of the page.
To remove a leaderboard entry, contact the project author on LinkedIn with the name and approximate score.
The site is hosted on Vercel, which receives standard HTTP request metadata (URL, user agent, referrer, IP address) for the purposes of serving the page and protecting against abuse. Vercel's privacy policy applies to that traffic. Fonts are loaded from Google Fonts, which Google describes as not setting cookies for font requests. The leaderboard database is hosted on Supabase. No advertising networks, social trackers, or fingerprinting libraries are loaded.
The game is appropriate for all ages and does not knowingly collect personal information from anyone, including children under 13.
If this notice changes in a substantive way, the date next to Last updated below will move forward. Last updated: 26 April 2026.