BlinkScroll — About the Project Inspiration Blink and you'll miss it. Literally.
BlinkScroll was conceived as a satirical commentary on the modern attention economy and modern human-computer interaction (HCI). It flips the conventional reading paradigm on its head: while reading naturally requires periodic involuntary blinking to keep the cornea lubricated, BlinkScroll translates each blink into downward scrolling momentum. As the reader blinks repeatedly to process the text, the news physically accelerates and runs away faster.
To amplify this mechanical irony, the core engine is paired with an original, razor-sharp suite of satirical journalistic articles parodying corporate tech culture, modern micro-services, and extreme hyper-optimization:
"Startup Introduces Meeting to Discuss Whether Previous Meeting Was Necessary" "City Unveils Smart Bench That Congratulates You for Standing" "Personal Weather App Promises Rain Only When You Forget an Umbrella"
As users engage with the platform, the user experience dynamically responds to their physiological blink rate, progressing through rolling blink frequency states:
Blink Frequency State Threshold (Rolling Window) UI Kinetic Behavior Composed 0–1 blinks / 3s Baseline ambient velocity; steady reading pace Mildly Inconvenienced 2 blinks / 3s Moderate kinetic jump; subtle UI feedback Aggressively Informed 3–4 blinks / 3s High impulse escalation; prominent status indicators Breaking News Velocity 5+ blinks / 3s Maximum momentum cap; page blur and visual runaway
How We Built It
- Zero-Backend, Privacy-Preserving Client Architecture BlinkScroll is engineered entirely as a zero-backend, client-side application executing 100% in-browser edge computation. Built with vanilla TypeScript and Vite, the platform operates without external backend services, cloud vision APIs, user accounts, databases, or remote telemetry.+-----------------------------------------------------------------------------------+
| BROWSER CLIENT |
| |
| +------------------+ +------------------------+ +---------------------+ |
| | MediaStream API | --> | WebGPU / WASM SIMD | --> | MediaPipe Task | |
| | (Local Camera) | | Execution Engine | | (face_landmarker) | |
| +------------------+ +------------------------+ +---------------------+ |
| | |
| v |
| +------------------+ +------------------------+ +---------------------+ |
| | ODE Physics | <-- | Finite State Machine | <-- | 3D Landmark Vector | |
| | Dynamic Integrator| | (Temporal Analysis) | | (468/478 Mesh) | |
| +------------------+ +------------------------+ +---------------------+ |
+-----------------------------------------------------------------------------------+
Runtime Model Execution: The engine pins @mediapipe/tasks-vision (v0.10.32), executing Google's face_landmarker.task model checkpoint directly via WebAssembly (WASM) with WebGPU acceleration. If WebGPU hardware context creation fails, the architecture falls back gracefully to CPU WebAssembly SIMD operations. Offline Local Asset Pipeline: Asset resolution is completely deterministic via scripts/prepare-assets.mjs. This build tool packages the WASM binaries and fetches the float16 model checkpoint, verifying integrity via SHA-256 asset manifests during compilation to guarantee offline capability.
Computer Vision Pipeline & Metric Eye Aspect Ratio (EAR) Geometry The core perceptual engine extracts 468 standard 3D facial landmarks (and 10 expanded iris landmarks, totaling 478) per video frame using MediaPipe FaceLandmarker running in VIDEO mode. p2 p3
o---o p1 / \ p4 (Right Eye: 33, 160, 158, 133, 153, 144) \ / (Left Eye: 362, 385, 387, 263, 373, 380) o---o p6 p5
Topological landmark indexing maps directly to ocular boundaries:
Right Eye Index Vector: $p_1 = 33, p_2 = 160, p_3 = 158, p_4 = 133, p_5 = 153, p_6 = 144$ Left Eye Index Vector: $p_1 = 362, p_2 = 385, p_3 = 387, p_4 = 263, p_5 = 373, p_6 = 380$
To eliminate geometric distortion caused by non-square camera sensor ratios or non-standard display viewports, normalized landmark coordinates $(x, y)$ are projected into intrinsic video pixel space before distance calculations:
$$x_{\text{pixel}} = x \cdot \text{videoWidth}, \quad y_{\text{pixel}} = y \cdot \text{videoHeight}$$
The scalar Eye Aspect Ratio (EAR) is evaluated using metric Euclidean distances:
$$\text{EAR} = \frac{|p_2 - p_6|_2 + |p_3 - p_5|_2}{2 \cdot |p_1 - p_4|_2}$$
- Multi-Stage Deterministic Quality Gating & Geometric Heuristics To prevent false positives from head motion, occlusion, or lighting changes, raw video frames undergo rigorous quality gating before EAR metrics are passed down the pipeline:+------------------+ +------------------+ +------------------+ +------------------+
| Single Face | --> | Bounding Box | --> | Ocular Resolution| --> | Physiological |
| Presence Gate | | Dimension Gate | | Gate | | Bounds Gate |
+------------------+ +------------------+ +------------------+ +------------------+
|
+------------------+ +------------------+ +------------------+ |
| Discontinuity | <-- | Yaw/Perspective | <-- | Head Roll | <------------+
| Protection Gate | | Proxy Gate | | Proxy Gate |
+------------------+ +------------------+ +------------------+
Single-Face Presence Enforcement: numFaces = 1 with strict thresholds: minFaceDetectionConfidence = 0.6, minFacePresenceConfidence = 0.6, and minTrackingConfidence = 0.6. Bounding Box Dimension Gate: Bounding box width must occupy between 18% and 90% of the horizontal frame width ($0.18 \le w_{\text{face}} / w_{\text{frame}} \le 0.90$). Ocular Resolution Gate: Horizontal eye corner span $|p_1 - p_4|_2 \ge 18$ source pixels to maintain mathematical precision. Physiological Plausibility Bounds: Raw EAR values must fall within $[0.0, 0.65]$. Head Roll Proxy: The angle of the vector connecting outer eye corners must remain within $\pm 25^\circ$ of horizontal alignment. Yaw / Perspective Proxy: Ratio of the smaller eye corner span to the larger eye corner span must be $\ge 0.60$. Kinematic Discontinuity Protection: Frame-to-frame facial center displacement must not exceed $15%$ of the image diagonal, and overall face width variation must be $\le 30%$ for samples occurring $\le 120\text{ ms}$ apart.
- Nonparametric Statistical Calibration & Normalized Hysteresis Because fixed EAR thresholds fail across diverse eye anatomy, webcams, and ambient lighting, BlinkScroll uses dynamic, nonparametric statistical calibration:
Open Baseline ($O$): Evaluated as the 75th percentile of EAR values across $\ge 40$ valid frames spanning at least 2 seconds. Ocular sensor noise ($\sigma_{\text{noise}}$) is continuously tracked using Median Absolute Deviation (MAD) over the upper half of samples:
$$\sigma_{\text{noise}} = 1.4826 \cdot \text{median}\left(|x - \text{median}(x)|\right)$$
Closed Baseline ($C$): Captured when bilateral EAR drops below $0.75 \cdot O$ for $\ge 150\text{ ms}$ ($\ge 3$ consecutive frames), calculated as the median of the lowest 5 valid samples. Admissibility Constraints: A baseline configuration is accepted only if $O \in [0.12, 0.50]$, the separation satisfies $(O - C) \ge \max(0.06, 4 \cdot \sigma_{\text{noise}})$, and the compression ratio satisfies $C / O \le 0.75$. Continuous EMA Smoothing: Raw EAR values undergo continuous-time Exponential Moving Average filtering with time constant $\tau_{\text{filter}} = 20\text{ ms}$:
$$\alpha(dt) = 1 - \exp\left(-\frac{dt}{20\text{ ms}}\right)$$
$$\text{EAR}{\text{smoothed}}(t) = \text{EAR}{\text{smoothed}}(t - dt) + \alpha(dt) \cdot \left(\text{EAR}(t) - \text{EAR}_{\text{smoothed}}(t - dt)\right)$$
Normalized Closure Index: Continuous eye closure is mapped linearly onto $[0, 1]$:
$$\text{Closure} = \text{clamp}\left(\frac{O - \text{EAR}_{\text{smoothed}}}{O - C}, 0, 1\right)$$
Dual-Threshold Hysteresis Band: State transitions use a closed threshold $\theta_{\text{closed}} = 0.65$ (adjustable via sensitivity slider within $[0.55, 0.75]$) and a reopen threshold $\theta_{\text{open}} = 0.30$, eliminating state jitter near the edge of eye movement.
Bilateral Temporal Finite State Machine (FSM) Eye tracking data is parsed by a seven-state deterministic finite state machine (FSM): +------------------+
| DISARMED | +------------------+ | v +------------------+ | OPEN | <-----------------------+ +------------------+ | | | v (Closure >= theta_closed) | +------------------+ | | CLOSING | | +------------------+ | | | v (Bilateral sync <= 100ms) | +------------------+ | | CLOSED | | +------------------+ | / \ |
(Dwell > 700ms) / \ (Closure <= theta_open)|
v v |
+------------------+ +------------------+ |
| HELD | | REOPENING | |
+------------------+ +------------------+ |
| | |
| v (Emit Blink Event) |
| +------------------+ |
| | REFRACTORY | |
| +------------------+ |
| | |
+----------------------+-----------------------+
(Rearm: 300ms continuous open)
Bilateral Synchronization Gate: Both eyes must pass $\theta_{\text{closed}}$ within a strict $100\text{ ms}$ window. Monocular winks or isolated facial twitches are filtered out and rejected. Temporal Dwell Constraints: Minimum bilateral closed dwell time: $\ge 45\text{ ms}$ across at least 2 distinct video samples. Maximum closure cap: $\le 700\text{ ms}$. Exceeding this limit transitions the state machine to HELD, preventing sustained squints or closed eyes from firing multiple scroll events. Event Emission: Scroll events emit strictly upon transitioning through REOPENING (confirmed by 2 consecutive open frames, $\ge 30\text{ ms}$). This guarantees that a closed eye never triggers multiple scroll events. Cooldown and Rearm: Upon event emission, the FSM enters REFRACTORY ($120\text{ ms}$ cooldown) followed by a $300\text{ ms}$ continuous open requirement before re-arming to OPEN.
Continuous Analytical Scroll Physics & Kinetic Escalation To avoid choppy steps from window.scrollBy promises, BlinkScroll uses a frame-rate-independent ordinary differential equation (ODE) physics engine. Velocity v(t)
|
v_max |-- - - - - - - - - - - - - - +------------------+ (Velocity Cap: 4500 px/s)
| /
| / <-- Additive Impulse Injection
| + / (I_0 * Multiplier)
| / \ /
| / \ /
I_0 |+ \ /
| \ /
| +------------+ <-- Exponential Viscous Decay
| \ v(t + dt) = v(t) * exp(-dt / tau)
0----------------------------------------------------> Time (t)
Viscous fluid drag is modeled as a first-order linear ODE:
$$\frac{dv}{dt} = -\frac{v}{\tau}$$
Integrating directly gives exact, frame-rate-independent velocity and displacement values for any frame interval $dt$:
$$v(t + dt) = v(t) \cdot \exp\left(-\frac{dt}{\tau}\right)$$
$$\Delta y = \int_{0}^{dt} v(t) , dt = v(t) \cdot \tau \cdot \left(1 - \exp\left(-\frac{dt}{\tau}\right)\right)$$
Where $\tau = 0.45\text{ s}$ represents the viscous time constant. Kinetic Escalation Dynamics When a valid blink occurs, an impulse is injected into the current velocity:
$$k = \text{Count of accepted blinks in rolling } 3000\text{ ms window}$$
$$\text{Multiplier} = \min\left(4.0, , 1.0 + 0.5 \cdot (k - 1)\right)$$
$$v_{\text{new}} = \min\left(v_{\text{max}}, , v_{\text{current}} + I_0 \cdot \text{Multiplier}\right)$$
Where $I_0 = 900\text{ px/s}$ and $v_{\text{max}} = 4500\text{ px/s}$.
A single isolated blink ($k=1$) yields a theoretical total displacement of:
$$\Delta y_{\infty} = I_0 \cdot \tau = 900 \cdot 0.45 = 405\text{ pixels}$$
Rapid consecutive blinks escalate momentum up to a 4x multiplier, accelerating page scroll speed smoothly across different display refresh rates (60Hz, 120Hz, or variable). Sub-pixel remainders are tracked in an internal floating-point accumulator. If a frame delay occurs ($dt > 250\text{ ms}$), momentum resets to zero to prevent visual teleportation.
- Systems Engineering, Performance, & Accessibility Frame Pipeline: Ingestion uses requestVideoFrameCallback with monotonic timestamp guards, decoupling hardware frame capture from the main rendering thread while locking inference smoothly to 30 FPS. Manual Override Intercept: Any manual user input (Escape, Space, mouse wheel, touchstart, scrollbar interactions, or PageUp/PageDown) instantly clears momentum, zeros the physics integrator, and pauses automated tracking. Accessibility Fallbacks: Reduced Motion: Fully respects prefers-reduced-motion. When active, continuous physics scrolling switches to discrete step jumps:
$$\Delta y_{\text{step}} = \min\left(120, , 0.2 \cdot \text{innerHeight}\right)$$
Screen Readers: Status updates route through non-polling ARIA live regions. No Camera Mode: Includes a full keyboard-driven synthetic blink simulator, providing complete functionality for users without webcams. What We Learned High-Performance Client-Side Edge ML: Executing high-density 3D face mesh models locally via WebGPU and WebAssembly SIMD proves that real-time computer vision can run smoothly at 30 FPS in standard browser environments—keeping user data fully private without relying on cloud APIs. Robust Statistical Signal Processing: Static thresholds are unreliable on consumer webcams due to variable exposure, dynamic lighting, and differing facial features. Combining continuous Exponential Moving Averages (EMA) with robust, non-parametric statistics (percentiles and Median Absolute Deviation) creates a reliable visual baseline across diverse hardware. Temporal State Machine Modeling: Biometric actions like blinking require more than single-frame detection. Modeling human facial movement through explicit state automata—with strict temporal dwell limits, bilateral synchronization, and hysteresis—filters out natural twitches, squints, and eye movements effectively. Closed-Form Continuous Physics Integration: Moving away from standard Euler integration steps toward closed-form analytical differential equations guarantees consistent scroll momentum and displacement across different display refresh rates (60Hz, 120Hz, 240Hz). Privacy-by-Design & Ethical HCI: Biometric interfaces do not need to compromise user privacy. Processing video frames ephemerally in memory proves that novel human-computer interactions can be rich, responsive, and engaging while upholding privacy and accessible design standards.
Built With
- accessibility
- computer-vision
- css3
- eye-aspect-ratio
- finite-state-machine
- getusermedia
- html5
- javascript
- mediapipe
- node.js
- physics-engine
- requestvideoframecallback
- signal-processing
- svg
- typescript
- vite
- webassembly
- webgpu
- webrtc
Log in or sign up for Devpost to join the conversation.