The logo for aye aye is two googly eyes sitting inside the letters "aa". So obviously the eyes had to follow your cursor. That took longer than I expected, and I built most of it with a coding agent, which is a little funny given what aye aye is for. More on that at the end.
The logo was never drawn to move
The SVG came out of the design tool as three paths: eye whites, letters, pupils. Moving the pupils gave every eye two pupils. The letter path also drew a pupil inside each eye, and the white shape had a hole cut exactly where that pupil sat.
The fix was boring: drop those two subpaths from the letters, fill the hole with a white copy of the pupil, and clip the moving pupil to the eye so it can't wander onto the letter.
const clip = el("clipPath", { id: `eye${i}` }, defs);
el("path", { d: whiteD }, clip);
el("path", { d: pupilD }, clip); // the hole, so the clip is the whole eye
const pupil = el("g", { "clip-path": `url(#eye${i})` }, eye);
Following the cursor without looking like a security camera
Point the pupil at the cursor, cap it at the edge of the eye, and ease toward the target every frame. The easing is the whole trick; without it the eyes snap, and snapping eyes feel like surveillance.
const dx = mouse.x - eyeX, dy = mouse.y - eyeY;
const d = Math.hypot(dx, dy) || 1;
const reach = Math.min(1, d / 400); // close cursor, small movement
const tx = (dx / d) * rangeX * reach, ty = (dy / d) * rangeY * reach;
e.x += (tx - e.x) * 0.14; // lag a little, every frame
e.y += (ty - e.y) * 0.14;
Shrinking on scroll without sitting on the headline
The logo starts huge in the middle of the screen and docks small in the corner. My first version shrank it by scroll progress, so it reached the top while still huge and parked itself on the headline.
What worked: let the headline push it. The logo rides up with the page, and once it hits the top, its scale is whatever room is left above the headline.
const y = Math.max(dockY, startY - scrollY);
const room = headlineTop - scrollY - gap - y;
const scale = clamp(room / logoHeight, dockScale, 1);
They can't overlap, because the overlap is the input.
The bug I nearly shipped
While you type an email, the eyes watch the text cursor. The field keeps focus after you type, though, so if you scrolled away, the small eyes in the corner stared upward at a field that was no longer on screen, until you clicked somewhere. Now they only watch the field while it's visible and typing is newer than the last mouse move.
Happy is harder than suspicious
A wrong email gets a side-eye: heavy lids, pupils to one side. Easy. Joining the waitlist was meant to look happy, and it didn't, twice. A flat lower lid looked exactly like the side-eye. A dome rising from below left two white arches glowing in black bowls, which was somehow worse. I called it scary, and it was.
What finally worked was cartoon logic: close the eyes, draw a thin white ^ where each one was, and make the whole logo do one small hop.
The part that's actually the point
A coding agent wrote most of this. I still had to understand why every eye had two pupils, why they froze after scrolling, and why "happy" looked haunted, because those are the questions that show whether I own this code or just approved it.
That's what we're building aye aye for. When an agent makes a risky change, the engineer who owns it explains it out loud, and the answer gets checked against the real change, not the agent's summary of it. A landing page isn't risky. Your auth code is.
The eyes are at ayeayecaptain.vercel.app. Join the waitlist and they'll smile at you.
**Notes:**
- Every bug in the post really happened in this project, in that order, so nothing is made up. The code snippets are trimmed from the real code in `site/index.html`.
- It sits as `published: false` so you can preview it on dev.to first.
- The cover image is your yellow card from the live site.
- The four tags are the most dev.to allows, and `showdev` is where people share things they built.
Want me to save it as a file in `social/` so it's in the repo?
Top comments (0)