A glass mascot for OverSub that reacts to what happens in the app and stays light next to a running game.
Type
Personal project
Product
OverSub, a Mac app that translates game dialogue and reads it aloud
Role
Design and build: character, motion and in-app behavior
Year
2026
Platform
macOS, Apple silicon
Tools
three.js, Swift, Core Animation
Status
Shipped in OverSub 1.1.86, October 2026
Expressions
When nothing is happening, Ove picks one of these every 5 to 9 seconds. Hover or tap a face to play it.
Context
OverSub is a free Mac app I design and build on my own. It reads dialogue in games, translates it and reads it aloud. The main window has three round controls: Subtitles, Voice-over in the middle, and Screen translation.
Underneath, it is a technical tool: screen recording permission, text regions, translation services. To many players that sounds like a heavy setup they would rather avoid. I wanted a character with a soul to make it feel approachable: a face for the voice, a way to show what the app is doing without words while your eyes are on the game, and a personality people remember.
Until October 2026 the middle control was a flat letter-O mascot, the same shape as the app icon. Ove replaced it: a glass head with an orange core that blinks, looks around, reads lines aloud and reacts to what the app is doing.
My role
OverSub is a one-person project, and I design and build it end to end. For Ove I designed the character and its eight expressions, built the 3D head in three.js, animated every state, and wrote how it behaves in the app: what it reacts to, how often, and what it never does.
Constraints
Constraint
What it meant
It runs next to a game
Any animation that redraws the window every frame costs CPU the game needs.
It is also a button
Clicking Ove turns the voice on and off, so it has to stay readable as a control.
128 points wide
Ove sits between two 100-point controls in the main window. Every face has to read at that size.
Light and dark
The main window follows the system appearance.
The player is busy
Reactions must not cover the line being read or pull attention from the game.
Process
The first mascot shipped on 7 October 2026 in version 1.1.43. On 10 October I built the glass head as a 3D prototype in three.js, with a spring-based motion engine and full mouse play: it follows the cursor, sulks when you leave and gets dizzy when you spin it. The app version went through eight builds that day, from 1.1.75 to 1.1.82, and shipped publicly in 1.1.86 on 13 October. Ove also narrates OverSub’s launch video.
Decision
Every expression outside idle time is tied to something the app actually did. The first translated line of a session makes Ove happy. A paused translation service makes it sad, a key out of quota makes it angry, a stuck text recognizer makes it dizzy, and nothing translating at all makes it cry.
If Ove is busy reading, a reaction waits up to 8 seconds, the more serious one goes first, and the same reaction plays at most once every 20 seconds.
Decision
Before a session starts, Ove does something small every 5 to 9 seconds, and every third time it plays a short scene with the controls around it: it eyes the Start button and winks, dozes off and wakes up embarrassed.
When subtitles are coming in, the player is in the game, so Ove only looks around every 30 to 60 seconds. Hovering over Ove tickles it. Resting the pointer on a control for 0.3 seconds makes Ove turn to look at it; passing over it does nothing.
Decision
A few days before Ove, I measured what per-frame drawing costs in this window: any animation redrawn by SwiftUI on every frame made the whole glass window redraw, at 14 to 16% of one CPU core. So Ove is never rendered in 3D inside the app. Every state is rendered ahead of time into frame sheets, and Core Animation flips through them.
Each frame is rendered twice, on black and on white, to recover the real transparency of the glass, so one set works in light and dark mode.
The reading loop: 72 frames at 24 fps, 280 pixels each. Measured on an M1 Pro.
Decision
A cross-fade between two poses shows two pairs of eyes for a moment, and that single frame is enough to break the character. Every clip starts and ends on Ove’s resting face. If a clip is interrupted, it rewinds to that face before the next one starts, and a turned head comes back to center first. I checked each build frame by frame from screen recordings: version 1.1.78 went from 15 broken frames to none.
Some ideas stayed in the prototype. Following the cursor, petting and teasing Ove with the mouse felt alive in a browser, but next to a game they read as noise, so the app keeps only the tickle and the glance at a control.
In the app
Ove in the main window, reading a line, and in the dialogs where it stands in for the app icon.

Dark mode, reading a line. The speech bubble points up at Ove.

Light mode, reading the voice preview.

The update dialog. Ove stands in for the app icon.

The support window. The bigger the amount, the happier Ove gets. No amount makes it sad.
Launch video
Voice and captions are in Vietnamese.
Outcome
Ove shipped in OverSub 1.1.86, a public release on GitHub, on 13 October 2026. It appears in the main window, the update dialog, the bug report dialog and the support window.
Played while nothing is happening.
Happy, sad, angry, dizzy and cry.
Across 27 frame sheets.
Of one core, about 0.4% idle.
Counted from the frame sheets in OverSub 1.1.86. CPU measured on an M1 Pro with the window visible.
What I learned
I had never made a 3D mascot before. The flat mascot could show what the app was doing, but it could not feel alive, or like it was keeping you company. That is why I moved to 3D and spent most of the work on emotion and the small signs of life: a blink, a glance at the Start button, a sneeze, dozing off between lines.
Thanks for reading
A design system I built solo, where light and dark share one elevation rule and brands swap as token sets.
