P R O J E K T

PIXELPRINT - JEDER TAG EIN PIXELMARATHON

MacOS-App: Pixelprint

Zum Tool:

https://github.com/janmaeusbacher-cpu/pixelprint.git

Website: Pixelmarathon

Zur Website:

https://pixelprint.framer.website

Zum Framer Projekt:

https://framer.com/projects/pixelprint-Pixelmarathon--tnYe6Bz21DLXy377HC5q-cBZ6I

Kurzbeschreibung

The Problem

A workday at the computer consists of tens of thousands of micro-movements: the mouse travels across distances, fingers scroll endlessly, and hands type rhythmically—yet none of this leaves a visible trace. Ironically, our most intense daily activity remains invisible, and existing tools merely count time or clicks rather than revealing what a day at the computer actually looks like.

The Solution

„pixelprint“ bridges this gap: a standalone macOS app that records mouse movements, clicks, scrolling, and keystrokes, translating raw interaction data into visual imagery.

The Tool

A single session generates thirteen coordinated perspectives on the same dataset—ranging from a time-lapse of the mouse path and attention heatmaps to a stress indicator that visualizes frantic activity as a curve. Recording takes place directly within the app—either in real-time or retrospectively—and everything remains local: no servers, no cloud, no tracking. The same data simultaneously gives rise to generative data art; thus, the tool does not merely analyze but creates a portrait of one’s workday.

The Website

The website „Pixelmarathon“ explains the project, offers the app for download, and places it within the broader context of digital versus physical movement.

In this way, the most fleeting aspects of our digital existence become visible—pixelprint serves as both a tool and a mirror.

Präsentationsvideo

Website Screencast

Bilder

Das Tool: pixelprint

2D Zeitraffer

3D-Tunnel

Mausklicks

Tastendrücke

Klick+Tastatur

Bewegung dx/dy

Programme

Übergänge

Rhythmus / Pausen

Screenregionen

Stress-Indikator

Gesten

Scrollen

App Icon

Die Website: Pixelmarathon

Ausstellung

Pitch Deck Final

Userjourney

_______________________________________

P R O Z E S S

1.) Ideen

2.) Recorder Tool

Maus- und Tastatur-Recorder für macOS

Zeichnet Mausbewegungen, Klicks, Scrolls und Tasteneingaben auf und speichert alles als CSV-Datei und als Tageszusammenfassung ebenfalls als CSV_Datei.

Zusätzlich:

- Distanz pro Tag (kumulativ in tagesstatistik.csv)

- Scroll-Strecke (dx/dy Summen)

- Aktive App pro Event

- App-Statistik pro Tag (app_statistik.csv) - Zeit + Klicks + Scrolls + Tasten + Bewegung pro App - Gesten-Verteilung pro Tag (in der tagesstatistik.csv)

Starten: python recorder.py Stoppen: ESC-Taste drücken

Vergleiche

Mausdistanz — 334,75 Meter:

- 3,3× der Höhe des Eiffelturms (324 m)

- Einer Runde um einen typischen Stadtblock

- Der Länge von ~3 Fußballfeldern (je ~105 m)

Klicks — 1.134:

- Anzahl der Fenster des Empire State Buildings auf einer Seite

- 13x alle Tasten einer Klaviatur (88)

Tastenanschläge — 2.307:

- Anzahl der Stufen auf den Eiffelturm (1.665 Stufen bis zur Spitze) — plus 642 extra

Dauer — 490 Minuten (~8,2 Stunden):

- Der Flugzeit von Frankfurt nach New York (~8,5 Std.)

- ~25 Folgen einer 20-Minuten-Serie (z. B. The Office)

3.) Pitch Deck

4.) Test: Visualisierungstool 1

https://openprocessing.org/@u449495/2921698

zu unübersichtlich und praktisch nicht auslesebar plus bei längeren Aufnahmen nicht performant

Vibe-Coding mit Claude Code

5.) Test: Visualisierungstool 2

https://openprocessing.org/@u449495/2926747

nicht benutzerfreundlich was den CSV-Datei Upload angeht.

Visuell nicht sehr ansprechend.

Vibe-Coding mit Claude Code

6.) Test: Visualisierungstool 3

https://openprocessing.org/@u449495/2926787

Feinschliff von der 2. Variante aber immer noch die gleichen Probleme wie bei 2.

Vibe-Coding mit Claude Code

7.) Prototyp

Vibe-Coding mit Claude Code

Prototyp 1

Prototyp 2

Prototyp 3

Neuerungen: Stress–Indikator

Richtungswechsel zählt nur noch wenn X und Y gleichzeitig die Richtung wechseln — das filtert normale Geradeausbewegungen mit kleinen Korrekturen heraus und erkennt nur echte Zickzack-Unruhe.

Geschwindigkeit nutzt jetzt den Median statt des Durchschnitts — ein einzelner schneller Ausreißer kann den Score nicht mehr alleine hochtreiben.

App-Wechsel wurde von 15% auf 25% angehoben, weil es das stärkste Kontextwechsel-Signal ist.

Synergie-Bonus — der entscheidende Unterschied: einzelne hohe Signale reichen nicht. Erst wenn 2 oder mehr Faktoren gleichzeitig erhöht sind gibt es einen Multiplikator (×1.15 bei 2, ×1.35 bei 3+). Das verhindert dass jemand der einfach schnell tippt aber konzentriert arbeitet einen hohen Score bekommt.

Pausen dämpfen den Score — Stillstandszeit im Fenster reduziert proportional den Gesamtscore. Wer kurze Denkpausen einlegt arbeitet strukturierter.

Präzisions-Dämpfung — langsame Bewegung mit wenig Unruhe halbiert den Score, unabhängig davon was die anderen Faktoren messen.

Prototyp 4

Prototyp 5

redesign

Prototyp 6

mit Logo

Prototyp 7

light/dark mode

Prototyp 8

Info Overlay

Prototyp 9

Vergleiche Funktion

Prototyp 10

Zeitmarke setzten Funktion

Prototyp 11

Sitzungstitel hinzufügen

Prototyp 12

Auswahl von Beispielsitzung für Schnellstart

Download-Button für Recorder 

Anleitung zum durchlesen/downloaden für Einrichtung und Nutzung des Recorders -> erste eigene User Aufnahmen

8.) Live Recorder

Maus- und Tastatur-Recorder für macOS

Zeichnet Mausbewegungen, Klicks, Scrolls und Tasteneingaben auf

und speichert alles als CSV-Datei.

Zusätzlich:

- Distanz pro Tag (kumulativ in tagesstatistik.csv)

- Scroll-Strecke (dx/dy Summen)

- Aktive App pro Event

- App-Statistik pro Tag (app_statistik.csv) - Zeit + Klicks + Scrolls + Tasten + Bewegung pro App

- Gesten-Verteilung pro Tag (in der tagesstatistik.csv)

Starten: python3 recorder.py

Starten mit Live: python3 recorder.py --live

(zusätzlich ws://localhost:8765 — pixelprint kann sich live verbinden

und alle Tabs in Echtzeit mitlaufen lassen, während die CSV wie gewohnt

im Hintergrund weitergeschrieben wird)

Stoppen: ESC-Taste drücken

9.) Prototyp mit Live Modus

Prototyp 13

Live Modus

Prototyp 14

Responsiv

Prototyp 15

Beispiel Datensätze direkt erreichbar über Startbildschirm

Prototyp 16

Letzte Datei öffnen Funktion

Prototyp 17

UI-Optimierung:

Dateien laden

- Dateien laden CSV

- Beispiele

- Letzte Dateien

10.) Prototyp mit Recorder-App

https://github.com/janmaeusbacher-cpu/pixelprint_recorder.git

Vorgehen: Recorder-App für macOS

- Ziel & Datenmodell: Aufzeichnung von Maus, Klicks, Scroll, Tastatur mit Zeitstempel + aktiver App; Ausgabe als CSV

- Technologie: Python, „pynput“ (Eingaben mitlesen), „pyobjc“ (macOS-Integration)

- Umgebung: virtuelle Umgebung („.venv“) wegen „externally managed„ Python, Abhängigkeiten installiert

- Eingabe-Listener – Maus- und Tastatur-Listener für “on_move„, “on_click„, “on_scroll„, “on_press„

- Aktive App erfassen: über “NSWorkspace„ das Vordergrund-Programm auslesen

- CSV-Ausgabe: Ereignisse in Datei schreiben (Zeit, x, y, Typ, Taste/Button, App)

- Menüleisten-Steuerung: “NSStatusItem„ zum Starten/Stoppen der Aufnahme

- Berechtigungen: „Eingabeüberwachung“ + „Bedienungshilfen„ in Systemeinstellungen erteilen, App neu starten

- Absturz beheben: Tastatur-Layout-Wert im Haupt-Thread cachen, “pyobjc„ aktualisieren

- Verpacken: mit PyInstaller (“--windowed„, Hidden-Imports) zur eigenständigen “.app„

- Verteilung: als DMG verpacken; unsigniert → erster Start per Rechtsklick → „Öffnen“

Prototyp 18

Download der Recorder-App und angepasste Anleitung zur Einrichtung

Prototyp 19

Anleitung und Download der Recorder-App in einem (UI-Optimierung)

11.) Pixelprint

https://github.com/janmaeusbacher-cpu/pixelprint.git

Recorder und Visualisierung alles in einer macOS-App vereint

_______________________________________

E X P E R I M E N T E

Vibe-Coding 1

function setup() {

createCanvas(500, 500);

noLoop();

}

function draw() {

background(255);

let cx = width / 2;

let cy = height / 2;

let circleR = 200;

// --- Kreis: nur Outline ---

noFill();

stroke(0);

strokeWeight(2);

ellipse(cx, cy, circleR * 2, circleR * 2);

// --- 4 Plus-Zeichen im Kreis ---

// Positionen: oben, rechts, unten, links

let dist = 100; // Abstand vom Mittelpunkt

let positions = [

[cx, cy - dist], // oben

[cx + dist, cy], // rechts

[cx, cy + dist], // unten

[cx - dist, cy], // links

]

for (let [px, py] of positions) {

drawPlus(px, py, 35, 12);

}

}

function drawPlus(cx, cy, armL, armW) {

fill(0);

noStroke();

// Horizontaler Balken

rect(cx - armL, cy - armW / 2, armL * 2, armW);

// Vertikaler Balken

rect(cx - armW / 2, cy - armL, armW, armL * 2);

}

Vibe-Coding 2

let countSlider;

function setup() {

createCanvas(500, 550);

countSlider = createSlider(1, 20, 4, 1);

countSlider.position(150, 520);

countSlider.style('width', '200px');

noLoop();

countSlider.input(() => redraw());

}

function draw() {

background(255);

let cx = width / 2;

let cy = 250;

let circleR = 200;

let count = countSlider.value();

// --- Label ---

fill(0);

noStroke();

textAlign(CENTER);

textSize(14);

text('Anzahl: ' + count, width / 2, 515);

// --- Kreis: nur Outline ---

noFill();

stroke(0);

strokeWeight(2);

ellipse(cx, cy, circleR * 2, circleR * 2);

// --- Plus-Zeichen gleichmäßig verteilt ---

let dist = 120;

for (let i = 0; i < count; i++) {

let angle = TWO_PI / count * i - HALF_PI;

let px = cx + cos(angle) * dist;

let py = cy + sin(angle) * dist;

drawPlus(px, py, 28, 10);

}

}

function drawPlus(cx, cy, armL, armW) {

fill(0);

noStroke();

rect(cx - armL, cy - armW / 2, armL * 2, armW);

rect(cx - armW / 2, cy - armL, armW, armL * 2);

}

Vibe-Coding 3

let countSlider;

function setup() {

createCanvas(500, 550);

countSlider = createSlider(1, 20, 4, 1);

countSlider.position(150, 520);

countSlider.style('width', '200px');

noLoop();

countSlider.input(() => redraw());

}

function draw() {

background(255);

let cx = width / 2;

let cy = 250;

let circleR = 200;

let armL = 28;

let count = countSlider.value();

// --- Label ---

fill(0);

noStroke();

textAlign(CENTER);

textSize(14);

text('Anzahl: ' + count, width / 2, 515);

// --- Kreis: nur Outline ---

noFill();

stroke(0);

strokeWeight(2);

ellipse(cx, cy, circleR * 2, circleR * 2);

// --- Plus-Zeichen tangential, innerhalb des Kreises ---

let dist = circleR - armL - 10; // Abstand vom Mittelpunkt: Rand minus Arm-Länge minus Puffer

for (let i = 0; i < count; i++) {

let angle = TWO_PI / count * i - HALF_PI;

let px = cx + cos(angle) * dist;

let py = cy + sin(angle) * dist;

push();

translate(px, py);

rotate(angle + HALF_PI);

drawPlus(0, 0, armL, 10);

pop();

}

}

function drawPlus(cx, cy, armL, armW) {

fill(0);

noStroke();

rect(cx - armL, cy - armW / 2, armL * 2, armW);

rect(cx - armW / 2, cy - armL, armW, armL * 2);

}

Vibe-Coding 4

let countSlider;

function setup() {

createCanvas(500, 550);

countSlider = createSlider(1, 20, 4, 1);

countSlider.position(150, 520);

countSlider.style('width', '200px');

noLoop();

countSlider.input(() => redraw());

}

function draw() {

background(255);

let cx = width / 2;

let cy = 250;

let circleR = 200;

let count = countSlider.value();

// --- Label ---

fill(0);

noStroke();

textAlign(CENTER);

textSize(14);

text('Anzahl: ' + count, width / 2, 515);

// --- Kreis: nur Outline ---

noFill();

stroke(0);

strokeWeight(2);

ellipse(cx, cy, circleR * 2, circleR * 2);

// --- Plus-Zeichen tangential ausgerichtet ---

for (let i = 0; i < count; i++) {

let angle = TWO_PI / count * i - HALF_PI;

let px = cx + cos(angle) * circleR;

let py = cy + sin(angle) * circleR;

push();

translate(px, py);

// Tangente = Winkel + 90°

rotate(angle + HALF_PI);

drawPlus(0, 0, 28, 10);

pop();

}

}

function drawPlus(cx, cy, armL, armW) {

fill(0);

noStroke();

rect(cx - armL, cy - armW / 2, armL * 2, armW);

rect(cx - armW / 2, cy - armL, armW, armL * 2);

}

Vibe-Coding 5

let countSlider;

let angle = 0;

function setup() {

createCanvas(500, 550);

countSlider = createSlider(1, 20, 4, 1);

countSlider.position(150, 520);

countSlider.style('width', '200px');

}

function draw() {

background(255);

let cx = width / 2;

let cy = 250;

let circleR = 200;

let armL = 28;

let count = countSlider.value();

angle += 0.02; // Rotationsgeschwindigkeit

// --- Label ---

fill(0);

noStroke();

textAlign(CENTER);

textSize(14);

text('Anzahl: ' + count, width / 2, 515);

// --- Kreis: nur Outline ---

noFill();

stroke(0);

strokeWeight(2);

ellipse(cx, cy, circleR * 2, circleR * 2);

// --- Plus-Zeichen tangential, innerhalb des Kreises ---

let dist = circleR - armL - 10;

for (let i = 0; i < count; i++) {

let a = TWO_PI / count * i - HALF_PI;

let px = cx + cos(a) * dist;

let py = cy + sin(a) * dist;

push();

translate(px, py);

rotate(a + HALF_PI + angle); // Tangente + eigene Rotation

drawPlus(0, 0, armL, 10);

pop();

}

}

function drawPlus(cx, cy, armL, armW) {

fill(0);

noStroke();

rect(cx - armL, cy - armW / 2, armL * 2, armW);

rect(cx - armW / 2, cy - armL, armW, armL * 2);

}

Blender Vibe-Coding