Tamaru banner

Tamaru

7 devlogs
8h 55m 19s

A trackball that scrolls the page, made for fun.
Uses a bunch of cool CSS to appear 3D, and has some cool physics!

This project uses AI

Used AI to improve my readme a bit, but not docs
Edit: To clarify, the ONLY use of AI was for the README, nothing else.

Demo Repository

Loading README...

NellowTCS

Shipped this project!

I built Tamaru, a physics‑driven virtual trackball widget for the web!
It lets you scroll any page or container by flicking a little 3D orb, complete with inertia, audio, haptics, themes, and now… Stick Mode (pointer‑lock scrolling), which nearly broke me (/silly) but was so worth it.

The hardest part was definitely Stick Mode. Pointer lock, scroll‑target cycling, highlight logic, restoring state, and making it all feel like a real trackball was a whole boss fight. I figured it out by rewriting half the scroll engine, adding a target‑cycling system, and bribing the browser with sheer stubbornness (yes your browser’s now corrupt 😔).

I’m really proud of how polished it feels now, the physics, the audio, the theming, the demo, the docs, everything. It started as a tiny 6 AM experiment and somehow turned into a full npm package with real features and real users. I’m honestly so happy with how it turned out.

NellowTCS

So uh.

I DID IT.
I ACTUALLY SHIPPED TAMARU.
It’s on npm.
It’s real.
It’s alive.
It has provenance logs and everything.
I’m screaming.

(I’m using the bookmarklet, by the way, in the first two images)

Attachment
Attachment
Attachment
2

Comments

NellowTCS
NellowTCS 3 days ago

*Last two images, the upload order is backwards

NellowTCS
NellowTCS 2 days ago

HEY REVIEWERS/READERS

SO someone asked me to make this an extension. firstly WHY DIDN’T I THINK OF THAT, and secondly I’m working on that and will reship soon™

NellowTCS

so uh. stick mode.

I finally did it.
I added stick to cursor mode.
And let me tell you:

this was
SO
HARD.

like “why am I doing this to myself” hard
like “pointer lock is a demon” hard
like “scroll target cycling is a cursed ritual” hard

BUT ALSO
SO
WORTH IT.

the descent into stick‑mode madness

pointer lock hell

I had to fight the browser for custody of the mouse.
It did NOT want to give it to me.
I won.
with a smirk cause why not >:3

mini‑mode orb

stick mode now force‑minimizes the orb
centers it

absolute movement scrolling

no more dragging the orb
no more “go to the corner”
just pure raw mouse movement -> scroll velocity
like a REAL trackball
but digital!

target cycling

hold Shift
scroll wheel (or two fingers on laptop)
Tamaru goes:

“oh you want to scroll THAT div?
okay let me highlight it for you”

it even scrolls it into view configurably
because I’m extra.

state management purgatory

enter stick mode
exit stick mode
restore position
restore size
restore sanity (attempted, not guaranteed, often fails)

UI updates

added a cute little ⌖ button
it mocks me
but it works

do these exist? yes. was it pain? yes.

  • stick mode button (desktop only because mobile said “no”)
  • pointer‑lock based scrolling
  • scroll target cycling with Shift + wheel
  • highlighting the active scroll target
  • auto‑snap into mini mode when entering stick mode
  • auto‑restore when exiting
  • scrollIntoView because I’m dramatic
  • scroll engine now respects stick mode like it’s a law of nature

bugs I fixed (and bugs I created then fixed)

  • stick mode not restoring size
  • stick mode not restoring position
  • stick mode scrolling the wrong thing
  • stick mode scrolling nothing
  • stick mode scrolling EVERYTHING
  • highlight not clearing
  • highlight clearing too fast
  • highlight clearing too slow
  • scroll engine forgetting what a scrollable element is
  • me forgetting what a scrollable element is
  • me forgetting who I am

demo

updated the demo again
because of course I did
stick mode works
it’s beautiful
it’s terrifying
I love it

Attachment
Attachment
Attachment
0
NellowTCS

So uh…
I kinda want to ship this today…
Maybe a bit obsessed but here’s another devlog haha.

I decided to try not to do like 10 things in one devlog.
And then I…
did not do that.
Apparently I cannot touch this project without accidentally doing a full renovation.

Anyway.

Stuff I Did

  • fixed the GitHub Pages workflow (for real this time… I think… I hope…)
  • added a favicon because the docs looked naked without one
  • added an icon.png because why stop at one image when you can have two
  • updated the docs config so the favicon actually shows up instead of silently judging me
  • added OpenGraph images so sharing Tamaru links doesn’t look like a 404
  • added a “Try the Demo” link because yes, you should try the demo (duh)
  • updated the README to stop lying about “no external dependencies” (pain)
  • tweaked the TODO list because I keep pretending I’m done
  • moved “tweak base config” to Done because it actually is good now
  • stared at the workflow file like it personally betrayed me
  • committed something
  • realized I forgot something
  • committed again
  • realized I forgot something else
  • and realized i made the devlog look like an essay again

Bugs I Fixed

  • “I thought I changed this” (I did not)
  • “why is the favicon not showing up” (wrong path)
  • “why is the docs folder empty” (wrong folder)
  • “why is the README lying” (me)
  • “why is the demo favicon broken” (also me)
  • “why is this taking so long” (me again)

Demo

Updated the demo again and it now is curvomorphic and prettyyy :3
Now with a working favicon.
Probably.
Confirmed now, yeah it works
I love it, it’s perfect :D

Attachment
Attachment
0
NellowTCS

So uh.

I did… a lot of things.
Like, a silly amount of things for someone who allegedly “was just fixing bugs.”

This commit arc is basically:

“I’m only going to clean my room a little”
proceeds to renovate the entire house, add two new rooms, repaint the walls, and fix a installed sound system

Anyway.

Stuff I Did

  • rewrote some janky code because Past Me was clearly having a moment
  • fixed like 6 bugs that were all conspiring together
  • made mini‑mode actually behave
  • added visibility + blur handlers so the orb stops rolling when you tab away (polite orb!!)
  • fixed pointer‑events so the widget stops gatekeeping interaction (polite orb x2!!!)
  • made the rolling sound smoother, less “grate‑y”, more “roll‑y”
  • tuned the filters again because apparently I’m an audio engineer now (if that was ever a question)
  • fixed the tiny audio gap when continuously scrolling (this one was haunting me, I don’t like not seamless sound)
  • improved haptics with iOS detection + rate limiting so it doesn’t vibrate itself into another dimension
  • added scrollbar‑hiding CSS because WebKit refuses to listen to me (seriously Apple?!! why must you be so different)
  • updated the theme loader
  • updated the README
  • updated the demo
  • updated the TODO (again…)
  • oh and uhhh

I added two new themes

neon

gradient, heheheheheheh
(I NEED MORE GIVE ME MOREEEEEEEEEE)

sunset

Orange, mic drop.
(but also it looks like mars sob)

Features

  • smoother rolling sound with smarter fade logic
  • mini‑mode that doesn’t break when you click it (yay)
  • scrollbars that disappear like they were never there
  • haptics that actually work and don’t fire 900 times a second
  • glossy theme is now ✨ glossier ✨
  • rolling physics + audio feel way more natural now
  • the orb is just… nicer?? like it has manners now almost??

Bugs I Fixed

  • rollSoundLevel mysteriously breaking (fixed)
  • rolling continuing after tabbing away (fixed)
  • clicking the mini trackball breaking the ball (fixed)
  • stuff near the trackball not being interactive (fixed)
  • WebKit scrollbars ignoring me (fixed via CSS war crimes)
  • tiny audio gap during continuous scroll (fixed)
  • haptics not working (fixed)
  • glossy theme not glossy enough (fixed)
  • rolling too grate‑y (fixed)

Demo

Updated the demo again because if I’m suffering, you get to see the results :3

Attachment
Attachment
Attachment
0
NellowTCS

So… I did a bunch of one thing:

This commit message best summarizes it:

I just wrote a manual audio engine
d6e51b1
 ¡ 
2 hours ago
this took ages

help
help me
someone
/j this is normal for me I made an *audio codec*
no it isn't why didn't i use audio files
WAIT
OH
OMG
AAAAAAAAAAAAAAAAAAA

So uh.
I sat down to “add some sounds.”

Anyway, I guess I wrote a manual audio engine.

Like.
A whole one.

From scratch.

Why?
I don’t know.
Ask the version of me who thought “hm yes, procedural rolling‑bearing noise with dynamic playbackRate modulation sounds fun.”

Stuff I Did (aka: why am I like this)

  • Built a full WebAudio pipeline with:

    • master gain
    • compressor
    • noise generator
    • rolling loop buffer
    • bandpass / highshelf filters
    • waveshaper distortion
    • dynamic envelopes
    • speed‑scaled playback
    • and a tiny sprinkle of “why didn’t I just use .wav files”
  • Added per‑event sound design:

    • grab -> crunchy mechanical click
    • release -> softer mechanical click
    • snap -> UI‑meets‑industrial “thunk”
    • spin -> tick‑tick‑tick with speed‑based pitch
    • stop -> inertia decay with settling noise
    • rolling -> continuous bearing rumble with dynamic stuff
  • Implemented a rolling sound layer that:

    • loops seamlessly
    • crossfades
    • modulates playbackRate based on speed
    • tears itself down when idle like a polite guest
  • Added speed‑aware feedback everywhere so the widget feels alive and so realistic.

Features

  • Rolling bed sound with adjustable intensity (rollSoundLevel).
  • Speed‑scaled spin ticks so fast spins sound fast and slow spins sound like a sleepy hamster wheel.
  • Grab/release/snap/stop events that feel like a physical object instead of a div.
  • Automatic fade‑outs, teardown, and cleanup so your CPU doesn’t cry.
  • Configurable everything because apparently I can’t stop adding knobs.

A Bug I Fixed (and by “bug” I mean “my sanity”)

  • Prevented the rolling layer from stacking 400 overlapping loops because I forgot to gate it.
    (It sounded like a jet engine and now my ears hurt.)

Demo

Updated the demo to expose rollSoundLevel because if I suffered for this, you get to play with the dial.

1

Comments

NellowTCS
NellowTCS 4 days ago

Yes I’m serious, I actually forgot that music files existed and that people have made audio clips of trackballs before.

NellowTCS

DEVLOG TIMEEEEEEE
Hackatime is behaving much more nicely, kudos to the maintainers. XD

I did stuff.

Okay, actually, I turned the little trackball experiment into a proper, modular toolkit with theming, config, and haptics.

Stuff I did

  • Added a config/type system so users can pass options and they merge with sensible defaults (types.ts, config wiring in main.ts).
  • Theme system: JSON themes + a loader, added multiple theme files (default, aqua, glossy, metal, red) and wired them to CSS variables so themes actually apply
  • Scroll mode & utilities: initial scroll helpers landed (page/nearest/horizontal/momentum), plus a scroll engine to drive snapping and scroll behavior
  • Modularization: split the code into small modules: DOM manager, controls manager, physics loop, scroll engine, sound, and haptics, so each piece is easier to reason about and test
  • Haptics: integrated the tactus package and added a simple haptic engine that calls triggerHaptic(duration)
  • Auto-reconfig: when config changes after mount, things reapply (theme, physics params), so runtime updates are respected (main.ts, physicsEngine.ts).

Features (what you, yes you John, get now)

  • themes loaded from JSON.
  • CSS-variable theming so switching a theme is immediate and global.
  • Trackball scrolling with multiple scroll modes and snap behavior.
  • Haptics through tactus for native-feeling pulses.
  • Auto reconfiguration so changing options at runtime updates behavior.

A bug/UX fix

  • Made theme application safe and non-destructive so it won’t “explode” rendering; also added a small delay/guard where needed so controls hide/show behavior is nicer.

Demo (cause we all need one in our lives dont we)

  • Updated the demo to show the new stuff using something really cool hehe
Attachment
Attachment
Attachment
Attachment
Attachment
Attachment
0
NellowTCS

So first devlog for Tamaru!!!

I initially made this today at like 6 AM as a small JS experiment, then was like, this is actually so useful!!!

Then i decided to make this an npm module/library, for fun :3.

Stuff I did to the original JS module

  • Made into TypeScript to be cleaner (almost just a rename to .ts, not much complexity)
  • MODULARIZATION: core movement/physics, DOM helpers, and entry point.
  • Styles are imported as text and injected at runtime for a self-contained build (I don’t like having to import styles separately for a few reasons)
  • Used tsup for TypeScript bundling.
  • Symlinked Build/dist to Demo/dist for easy demo updates. (so useful omg i should do this everywhere)
  • Copied a bunch of CI, Config, etc from another project, Sairin, and edited it for Tamaru
  • Created Demo/index.html to showcase the widget.
  • Added a large block of lorem ipsum in a scrollable div to test trackball scrolling.
  • Disabled native page scrolling; only the trackball or scroll area can scroll content.

Features

  • pseudo-3d ball
  • pseudo-3d ball (it’s so cool omg)
  • Draggable, floating widget with snap-to-edge logic.
  • Trackball area for scrolling with inertia.
  • Minimize/restore toggle.

A bug that was fixed

  • Controls popup uses a delay before hiding, so i don’t have to speed click before the controls hide.
Attachment
Attachment
Attachment
Attachment
1

Comments

NellowTCS
NellowTCS 16 days ago

i used the demo page to make the banner
that’s so funny to me
it’s just the Tamaru with the orb and it’s perfect