In seiner Funktionalität auf die Lehre in gestalterischen Studiengängen zugeschnitten... Schnittstelle für die moderne Lehre
In seiner Funktionalität auf die Lehre in gestalterischen Studiengängen zugeschnitten... Schnittstelle für die moderne Lehre
“Pixelprint” measures what no fitness tracker does: stillness. It captures the digital imprint of a workday—every mouse movement, every scroll, every keystroke—and makes tangible what we never notice. The cursor traveled hundreds of meters, steered by a hand moving across just ten centimeters of trackpad. It is movement that feels like activity, even as the body remains at rest. For the first time, stillness has a number and an image.
Zur Website:
https://pixelprint.framer.website
Zum Framer Projekt:
https://framer.com/projects/pixelprint-Pixelmarathon--tnYe6Bz21DLXy377HC5q-cBZ6I
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.
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
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)
https://openprocessing.org/@u449495/2921698
zu unübersichtlich und praktisch nicht auslesebar plus bei längeren Aufnahmen nicht performant
Vibe-Coding mit Claude Code
https://openprocessing.org/@u449495/2926747
nicht benutzerfreundlich was den CSV-Datei Upload angeht.
Visuell nicht sehr ansprechend.
Vibe-Coding mit Claude Code
https://openprocessing.org/@u449495/2926787
Feinschliff von der 2. Variante aber immer noch die gleichen Probleme wie bei 2.
Vibe-Coding mit Claude Code
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
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
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
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)
Recorder und Visualisierung alles in einer macOS-App vereint
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);
}
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);
}
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);
}
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);
}
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);
}