BlinkProbe measures your blink rate with the webcam and tests which subtle on-screen cue reliably triggers a blink. Everything runs locally in your browser.
HealthHTMLGitHub @AdmirBajricOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Measure your blink rate with the webcam and find out which subtle on-screen cue reliably triggers a blink. Single file, runs entirely in the browser.
Taken from the repository README
Loading the full README.
Read the full README ↗BlinkProbe is a single-file web app that uses your webcam to count blinks and tests which subtle on-screen cue most reliably triggers a reflex blink while you read on a screen.
Results view with sample data. Each cue's blink rate and 95% confidence interval is compared with the control trials that had no cue.
During concentrated screen work, people blink roughly half as often as usual. Relaxed, most people blink about 15 to 20 times a minute; focused on a screen, that often drops to 8 to 10. Many of the blinks that remain are incomplete, so the tear film is not renewed properly. It then evaporates, and the eyes can start to burn, itch or blur late in the day.
Spontaneous blinking tends to fade during deep concentration, but reflex blinks remain: something approaching the face or a sudden change of light still makes you blink without deciding to. A future blink reminder could use this by showing a subtle cue in your peripheral vision that triggers a reflex blink without pulling your attention away from the work.
BlinkProbe is the experiment tool for finding out which cue actually works for a given person. People cannot reliably tell by feel what helps, because blinks that would have happened anyway are easily credited to the cue. BlinkProbe therefore mixes in control trials with no cue, counts the blinks that follow each cue within a fixed response window, and compares every cue with the controls using confidence intervals and an exact test.
Live demo: admirbajric.github.io/blinkprobe
To run it locally, clone or download the repository and open index.html in a desktop browser. You can also serve the folder:
git clone https://github.com/AdmirBajric/blinkprobe.git
cd blinkprobe
python3 -m http.server 8000 # then open http://localhost:8000
# or
npx serve # then open the localhost address it prints
Requirements:
https:// page such as the live demo, http://localhost, or the file opened from disk (file://) in a desktop browser. A plain http:// address on another machine will not work.A session has four steps: Camera, Calibration, Test and Results. With the default settings, calibration and test take about 22 minutes.
| Cue | What you see | Notes |
|---|---|---|
| Looming shape | A small shape beside the text grows quickly, as if it were approaching. | Soft-edged and placed in the periphery, left or right of the text at a random height. Lasts about half a second. |
| Dimming | The whole screen dims slightly for a moment. | A translucent black layer over the page, so it covers the browser viewport rather than other applications. Lasts about half a second. |
| Blur | The text blurs slightly for a moment. | Only the reading text is blurred. Lasts about half a second. |
| Blinking eye | A tiny eye icon beside the text blinks twice. | Placed in the periphery like the Looming shape. Lasts about one second. |
Every cue is a single brief pulse and never flickers. Intensity scales how strong a cue is: size and opacity for the shape and the eye, depth for the dimming, strength for the blur. Each cue can be previewed and switched off before a test.
For every cue, BlinkProbe counts how often a blink starts shortly after it and compares that rate with the rate for control trials.
The verdict comes from the first rule that matches:
| Order | Verdict | Condition |
|---|---|---|
| 1 | Too little data | Fewer than 8 valid trials for the cue or for the control |
| 2 | No clear effect | The difference is zero or negative |
| 3 | Works | p < 0.05 and the difference is at least 10 pp |
| 4 | Possible effect | p < 0.15 |
| 5 | No clear effect | None of the above |
The forest-style plot (SVG) shows each cue's hit rate and 95% CI next to the control's rate, with the control's interval drawn as a shaded band. From your calibration rate the app also estimates how often a blink would land in the window by chance alone, assuming blinks occur at random at that rate: 1 - exp(-blinks per minute / 60 × window length in seconds). At 15 blinks per minute and the default 0.9 s window that is about 20%.
Because several cues are compared at once, a single positive result can be a fluke. Confirm it with a repeat test. As a rule of thumb, about 15 to 20 valid trials per cue are needed for a reliable conclusion. The app shows this hint until a cue has at least 8 valid trials.
localStorage under the key blinkprobe.v1. The key treptaj.v2 from the original prototype is read as a fallback. The data consists of blink timings relative to the cues, your settings, calibrations and ratings, never images or video. If storage is unavailable, the app warns you and results survive only while the page stays open, so export them before closing it.cdn.jsdelivr.net: the @mediapipe/tasks-vision library (vision_bundle.mjs and the WebAssembly runtime in wasm/). Versions 0.10.21, 0.10.14 and the latest release are tried in that order until one loads.storage.googleapis.com: the model mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task, a few MB, cached by the browser.fonts.googleapis.com and fonts.gstatic.com): the Figtree and Newsreader typefaces.As with any third-party resource, these hosts can see standard request metadata such as your IP address. No other requests are made.
The JSON file (blinkprobe-data.json) contains everything the app has stored:
The CSV file (blinkprobe-trials.csv) has one row per trial, with comma-separated fields and these columns:
| Column | Meaning |
|---|---|
session | Session id. |
mode | random or adaptive. |
cue | loom, dim, blur, eye, or sham for a control trial. |
intensity | Cue intensity as a fraction from 0 to 1. |
time | Time of the trial as an ISO 8601 UTC timestamp. |
valid | 1 if the trial is valid, 0 if it is excluded. |
hit | 1 if a blink started inside the response window, 0 if not; empty for invalid trials. |
latency_ms | Onset of the first blink inside the window, in ms after the cue; empty if there was none. |
blink_onsets_ms | Onsets of all blinks recorded around the trial, in ms relative to the cue. |
Blink onsets in blink_onsets_ms are joined with |, for example 340|780. The columns valid, hit and latency_ms use the response window set on the Results step at the time of export. All stored trials are exported, regardless of the other filters.
index.html, with the script as one inline ES module. There is no framework, no bundler and nothing to install. MediaPipe is the only external code and is loaded at runtime.eyeBlinkLeft and eyeBlinkRight scores are averaged into an eye-closure score from 0 (open) to 1 (closed). Frames are handled with requestVideoFrameCallback and the camera's capture timestamps where the browser provides them.BlinkDetector keeps an adaptive baseline of the resting score. The baseline adapts slowly when the score creeps up (time constant 8 s), so gradual shifts such as lowering your gaze are absorbed instead of counted as blinks, and faster when it drops (1.5 s). A blink starts when the score rises at least 0.22 above the baseline and ends when it falls back below the baseline plus 0.10. It is accepted if it stays raised for at least 2 frames, or reaches 0.45 above the baseline in a single frame, and lasts at most 1000 ms. Longer closures are dropped and the baseline resets. Each event records onset, end, duration, peak and a complete or incomplete flag.Recorder keeps the blinks and the stretches of lost tracking, and closes each trial 1.7 s after its cue. The cue time is taken at the next animation frame plus 16 ms, to approximate when the cue is actually on screen. Control trials ("sham" in the code) come from the same shuffled deck as the cues.module.exports hook for Node tests.window.__blinkprobe exposes the app state, the stored data and the recorder for inspection from the browser console.Planned, not yet implemented:
Issues and pull requests are welcome. When reporting a problem, please include your browser, operating system and camera, and what the live graph showed.
BlinkProbe is released under the MIT License. Maintained by AdmirBajric.