Paper Aquarium: a coloured sheet becomes a 3D fish on your TV
Бумажная раскраска превращается в 3D-рыбу: ребёнок раскрашивает лист, фотографирует телефоном — и рыбка плывёт в аквариуме на большом экране. Node без зависимостей + three.js.
At a glance
- What is it?
- MrMoT9I/paper-aquarium is a self-hosted home game: a child colours an A4 sheet, a phone photo becomes a texture, and the fish swims in a three.js tank on a big screen. The server is plain Node with no dependencies.
- Who is it for?
- Adopt it if you have a spare machine on the home Wi-Fi, a TV browser and a child who will colour the sheets, and if you accept that anyone with the link can add fish and backgrounds. Do not adopt it if you need accounts, per-user permissions or a public instance where the password header can travel without TLS.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 5, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem it solves, and who it is for
Most drawing apps for children end at the screen. The drawing stays a drawing. Paper Aquarium closes the loop: the output of a physical sheet becomes an object in a shared 3D scene. The README frames the goal plainly, calling it "a home game for a child, in the spirit of teamLab's Sketch Aquarium".
The audience is narrow on purpose. It is a household with a printer, a phone, a screen big enough to gather around, and a server or always-on machine on the same Wi-Fi. The README describes the flow as an A4 colouring sheet, then a phone photo, then a texture, then a 3D fish in the scene. There is no account system, so there is nothing to administer per child. That is the point: a parent sets it up once, prints sheets from print.html, and the child drives the rest from a phone.
It is also a reasonable fit for a classroom or a club where one adult can run the server and several children can colour sheets that land in the same tank. The language support (Russian, English and Polish) suggests that use case rather than a single-family deployment.
How a coloured sheet becomes a texture
The recognition does not rely on computer vision models or a printed QR code. Each sheet carries four black 6x6 markers in the corners, and their 16 inner cells encode the species and the corner number. The README states that the capture step uses those markers to find the sheet in a photo and undo the perspective. The markers must stay uncoloured; everything else on the sheet is fair game for the child.
assets/capture.js does the work. According to the README, it looks for the markers by sweeping brightness thresholds, undoes the perspective, cuts the drawing along the species contour taken from the manifest, and trims a strip along the printed line itself. That last step matters: without it the printed outline would remain as a dark rim on the fish. The result is a texture, mapped onto the 3D model through a planar unwrap of its side silhouette. Planar unwrap is a deliberate simplification. It works because the drawing is a side view, and it would not survive a model that needed the sheet wrapped around a complex body.
The printed sheet itself is designed around that constraint. The fish outline is printed as a thin grey line and the fin areas as a pale dashed one, so they are visible without the child taking the hint for part of the drawing. The corner markers are identical in every language version, so any printed sheet is recognised regardless of which caption is under the fish.
The tank, the menu and the TV code
The scene lives in demos/realistic-tank.html. Fish swim inside a volume that follows the camera frustum rather than a box. The README gives the reason directly: against a box, fish near the far wall would huddle towards the centre of the screen. That is a small detail with a visible payoff, and it is the kind of thing you only notice when you build the wrong version first.
Orientation is derived, not authored. assets/fish-frame.js works out where the nose is and where the back is from the tail beats in the animation. A modeller adding a new fish does not have to declare an axis convention, but the fish must animate for the frame to be computed.
A tap anywhere in the aquarium opens the menu: capture, ready-made fish from the pack, food, colouring sheets, background, removing fish, and "Open on another screen". Capture, background and sheets open in a frame rather than as copies, using the same pages with ?embed=1. The "Open on another screen" entry hands out a temporary five-digit code that lives five minutes and is kept in the server's memory; it goes into the same field on the home page as the regular code. A screen opened with ?tv never pops the menu by itself, which is the right call: the TV is for watching, the phone is for driving.
Access is split by consequence. Every aquarium has a 10-character code that is also its address, plus a password. Watching, adding a fish, feeding and changing the background need only the code. Deleting fish or the aquarium, and renaming it, need the password. The README defends the choice: capture and feeding are deliberately password-free because asking a child for a password on a phone would kill the idea, and everything irreversible sits behind the password.
Running it on a home machine
Node 18 or newer is required, and there is nothing to install. three.js sits in vendor/, and the server has no dependencies at all. From the repository root, start it with the command the README gives:
node server.js # http://localhost:8000The server prints the addresses of every network interface. Open one of those from the phone, not localhost, or the phone will look for a server on itself. The port is set by PORT. package.json also exposes npm start, which runs the same file, plus two tools: npm run coloring and npm run pdf, which call tools/make-coloring.js and tools/make-pdf.js for generating sheets and a PDF.
For a container deployment, the repository ships a Dockerfile, docker-compose.prod.yml and .env.example. The README gives this sequence:
cp .env.example .env # DOMAIN
docker compose -f docker-compose.prod.yml --env-file .env up -d --buildDOMAIN is the only value you must set; Traefik builds the router from it and obtains the certificate through the external web network. The image does not contain the model pack, which is mounted from the server as a volume. Note the Dockerfile comment: aquariums are written to /app/data, the volume is mounted from outside, and its owner must match the container user (uid 1000) or writes will fail. That is the most likely first deployment mistake.
Limits are environment variables with defaults, and they are the part worth reading before exposing anything: AQUA_MAX_TANKS (200), AQUA_TANKS_PER_HOUR (5), AQUA_MAX_FISH (40), AQUA_MAX_BG (8) and AQUA_MAX_DATA_MB (2048). AQUA_DEMO_TANK names a showcase aquarium; when it is unset, the "Peek at a live aquarium" card on the home page does not appear.
Where it breaks, and where it is the wrong tool
The password travels in plain text in the X-Tank-Pass header. The README says this outright and calls it acceptable inside a home network, with HTTPS mandatory on the internet and provided by the proxy. On the server only a salted scrypt hash is stored. This is the single design decision that decides your deployment: a bare node server.js on a public IP with no TLS in front puts that header on the wire in the clear.
The second limitation is the one the project chose on purpose. Adding fish and uploading backgrounds is password-free, so anyone who has the aquarium link can write to it. The environment limits above exist to stop that from filling the disk, not to stop it from happening. On a home network with a code that has roughly 8*10^14 combinations, guessing is not the threat. On a link that leaks into a group chat, it is.
Recognition depends on the markers being uncoloured and on the photo being usable. A marker coloured over, a sheet photographed at a steep angle in poor light, or a heavy shadow across a corner is a failure the README does not document a recovery path for; the capture screen is where you would retake it. The README also does not document rollback for a bad deployment, so treat DEPLOY.md (which is in Russian) as the source for the order of steps and backups.
Finally, this is not a multi-tenant service. There are no accounts, and the code is the address. If you need per-user isolation, quotas you can attribute to a person, or an audit trail, the model here does not provide it.
How it differs from a hosted sketch aquarium
The obvious comparison is a hosted product in the same genre, where you scan a sheet with a vendor app and the fish appears in a vendor tank. The difference is not the effect. It is where the data lives. With a hosted service, the drawing, the account and the scene are the vendor's, and the app is the only way in. Here, everything the game owns lives in data/, the server is a single Node process, and the scene is served to any browser that can reach the address.
That changes the failure modes you own. You own the machine, the volume, the TLS terminator and the backups. In exchange, a ten-year-old tablet with a browser can be the aquarium screen, and there is no app to install on the phone: capture.html is a page. The README's own framing of the trade is the honest one, since the access section reads as a deliberate split between what a child can do and what only an adult with the password can do. A hosted product would not let you make that split.
The other axis is the rendering. Using three.js in vendor/ means the scene is ordinary web code you can open and edit, and demos/realistic-tank.html is a file you can read. A closed service gives you settings, not a frustum-following volume.
Licence, maintenance and upgrade cost
The project is MIT licensed, and package.json marks it private and version 1.0.0. MIT is permissive: you can run, modify and redistribute it, including commercially, provided the copyright notice and permission notice are kept. The repository has no separate NOTICE or third-party licence file at the top level, so if you redistribute, check the licence of three.js in vendor/ yourself. That is a fact about the repository layout, not legal advice.
The last push was on 2026-09-15, two days before this writing, and the repository is not archived. No releases were retrieved, which fits a project that ships as a git checkout rather than a versioned artefact. Upgrades are therefore a git pull plus a restart, and the Docker path rebuilds the image: the model pack is a volume, so it survives. The real upgrade cost sits in data/. The Dockerfile writes aquariums into /app/data, and AQUA_MAX_DATA_MB caps that folder at 2048 MB by default. If you ever move the volume, ownership must stay at uid 1000 or the server stops writing, which is the same trap as the first deployment.
Editorial conclusion
Adopt it if you have a spare machine on the home Wi-Fi, a TV browser and a child who will colour the sheets, and if you accept that anyone with the link can add fish and backgrounds. Do not adopt it if you need accounts, per-user permissions or a public instance where the password header can travel without TLS. Before printing anything, run node server.js on Node 18 or newer and open the printed network address from the phone you intend to use, because the whole game depends on that phone reaching the server over the local network.
Frequently asked questions
How do I make an aquarium with paper in Paper Aquarium?
Print one of the A4 colouring sheets, colour it with markers while leaving the four black 6x6 corner markers untouched, then photograph the sheet from the capture screen. The server turns the photo into a texture and the fish appears in the 3D tank.
Can I make a DIY aquarium at home with Paper Aquarium?
Yes, that is the intended setup. Run node server.js on a machine on your Wi-Fi, open the network address it prints from a phone, and open the aquarium on a big screen. There are no accounts, and each aquarium has a 10-character code that is also its address.
Is a paper fish from Paper Aquarium a real thing?
The paper sheet is the input, not the fish. The drawing is cut along the species contour from the manifest and mapped onto a 3D model through a planar unwrap of its side silhouette, so what swims in the tank is a textured three.js model.
Does watching fish in Paper Aquarium lower blood pressure?
The repository makes no health claim and the README does not discuss blood pressure or any physiological effect. It describes the game itself: print a sheet, colour it, photograph it, and the fish swims in the aquarium on the big screen.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/mrmot9i-paper-aquarium)