iOS · General and organic chemistry
Every atom has a face, and the face is computed.
An iOS app for general and organic chemistry, written in SwiftUI. A character's expression is derived from its valence shell rather than picked from a set of animations: unpaired electrons read as anxious, a filled octet reads as calm. Bonding two atoms changes both faces, because it changes both shells. The octet rule is running as the rendering rule.
This is the app's own model, ported to the browser: the same element table, the same octet arithmetic, and the same face rig with the same per-mood keyframes. Pick an element, then add bonds. Nothing here is decorative — every eyebrow angle is downstream of one integer, the number of electrons still unpaired.
The rule, verbatim from AtomMood.swift:
0 unshared is blissful, 1 is happy with lone pairs to lean on and merely
curious without, 2 uneasy, 3 worried, 4 overwhelmed.
Build mode can't be put into an invalid state: the next valid atom is the one that's glowing. Multiple bonds aren't free; they're raised by tapping the rail, so O₂, N₂ and CO₂ each take an extra step. Every recipe below is the real one, with the same angles, lone pairs and text the app ships.
Captured from a build running on an iPhone simulator. Every expression here comes
from the native MoodFace
rig. No canned expression loop, no imported animation files.
51 lessons across 16 units, from a first covalent bond to aromaticity, enolates, curved arrows, nucleophiles and radical chains, written in the vocabulary the course actually uses. Each unit is introduced by an element professor.
Hearts and a progress bar pin to the top; the verdict lands in a full-width banner at the bottom, within thumb reach, carrying the explanation and the one button that moves you on.
The Blueprint panel shows anything built as molecular formula, Lewis dots, structural lines and skeletal shorthand: the four notations a course asks for.
Parenthesis-aware molar mass, dilution and percent-yield calculators sitting next to persistent notes, so the bench work and the lesson aren't in two different apps.
18 organic chemistry cards that resurface on a schedule set by how well they were answered, plus XP, hearts and a daily streak. The session length it's designed around is a few minutes.
Progress lives on device and in the user's own private CloudKit database, which I can't read, with an automatic local-only fallback when iCloud isn't available. The App Store privacy answer is "we don't collect data."
AtomKit owns the chemistry. An Atom
knows its element, bonds, unshared electrons, lone pairs and current mood; the
character rig, the guided builder and the lesson engine are all different views
onto that one model. It's why the face can't drift out of sync with the
chemistry. There is nowhere else for the expression to come from.
| Language | Swift 6, strict concurrency |
|---|---|
| UI | SwiftUI on iOS 26, @Observable via the Observation framework |
| Persistence | SwiftData with CloudKit private database; automatic local-only fallback |
| Characters | Native MoodFace rig (animated arcs, not sprite sheets) |
| Design tokens | Figma → chemigo.tokens.json → generated ChemigoTokens.swift, re-run as a build pre-action |
| Project | XcodeGen from project.yml; the .xcodeproj isn't tracked |
| Bundle | edu.ucsd.chemigo.Chemigo |
This page is drawn with the app's actual tokens, so what you're reading is a specimen rather than a description. The buttons below press; the cards don't.
A surface is a fill plus a crisp 2pt border. Blur shadows are gone everywhere. The only thing with depth is something you can press.
Buttons, lesson nodes and answer options sit on a solid darker rim that they press down into. There are four button kinds: solid, outline, soft and disabled. There is no fifth.
Green carries the product. Blue, violet and coral mark a kind of activity, never a second brand. Element colors are fixed literals in both schemes, because the atoms are self-lit artwork and shouldn't shift between light and dark.
onTint
helper resolves foregrounds from the actual resolved fill, which
guarantees the dark-mode case; the light-mode solid button is the
accepted cost.