diff --git a/data/sound/alarmclock.ogg b/data/sound/alarmclock.ogg
new file mode 100644
index 0000000..1b566c7
Binary files /dev/null and b/data/sound/alarmclock.ogg differ
diff --git a/snake.c b/snake.c
index 4a69360..86995f3 100644
--- a/snake.c
+++ b/snake.c
@@ -370,8 +370,8 @@ int main() {
   worldSetWidth(SIZE * WIDTH);
   worldSetHeight(SIZE * HEIGHT);
   worldSetFrameRate(FRAMERATE);
-	worldSetInit(&init);
-	worldSetDraw(&draw);
-	worldRun();
-	return 0;
+  worldSetInit(&init);
+  worldSetDraw(&draw);
+  worldRun();
+  return 0;
 }
diff --git a/timer.c b/timer.c
new file mode 100644
index 0000000..305bf58
--- /dev/null
+++ b/timer.c
@@ -0,0 +1,137 @@
+
+#include <math.h>
+#include <stdio.h>
+#include <helianthus.h>
+
+
+int hours, minutes, seconds;
+double subSeconds;
+
+int started;
+int alarm;
+
+Sound alarmSound;
+
+
+void init() {
+  alarmSound = createSound("data/sound/alarmclock.ogg");
+}
+
+
+void draw() {
+  if (started) {
+    if (alarm) {
+      subSeconds += worldGetFrameTime();
+      while(subSeconds >= 1) subSeconds -= 1;
+    } else {
+      subSeconds += worldGetFrameTime();
+      while(subSeconds >= 1) { subSeconds -= 1; --seconds; }
+      while(seconds < 0) { --minutes; seconds += 60; }
+      while(minutes < 0) { --hours; minutes += 60; }
+      if (hours < 0 || (hours == 0 && minutes == 0 && seconds == 0)) {
+        hours = 0;
+        minutes = 0;
+        seconds = 0;
+        alarm = TRUE;
+        soundPlay(alarmSound, TRUE);
+      }
+    }
+  }
+
+  saveState();
+  translate( worldGetWidth()/2.0, worldGetHeight()/2.0 );
+
+  textAlign(HALIGN_CENTER, VALIGN_CENTER);
+
+  double size = 100;
+  int *values[3] = { &hours, &minutes, &seconds };
+  for(int i = 0; i < 3; ++i) {
+    int *value = values[i];
+
+    saveState();
+    translate( (i-1)*size, 0 );
+    textSize(64);
+    if (alarm) stroke(colorByRGBA(0, 0, 0, subSeconds));
+
+    double mx = transformedMouseX();
+    double my = transformedMouseY();
+    if (fabs(mx) <= 0.45*size && fabs(my) <= 0.45*size) {
+      *value += mouseScrolledY();
+      while(*value <   0) *value += 60;
+      while(*value >= 60) *value -= 60;
+      if (mouseWentDown("left")) *value = 0;
+      if (mouseWentDown("right")) *value = 30;
+    }
+
+    char buf[32] = {};
+    sprintf(buf, "%d", *value / 10);
+    text(buf, -0.2*size, 0);
+    sprintf(buf, "%d", *value % 10);
+    text(buf,  0.2*size, 0);
+
+    if (i > 0) {
+      translate( -size/2, 0 );
+      text(":", 0, 0);
+    }
+
+    restoreState();
+  }
+
+  if (started) {
+    saveState();
+    strokeWidth(0.1*size);
+    if (alarm) stroke(colorByRGBA(0, 0, 0, subSeconds)); else rotate(-60*subSeconds);
+    for(int i = 0; i < 12; ++i) {
+      point(2*size, 0);
+      rotate(-30);
+    }
+    restoreState();
+  }
+
+  saveState();
+  {
+    noFill();
+    textSize(24);
+    double buttonWidth = size*1.5;
+    double buttonHeight = size*0.5;
+    translate(-0.5*buttonWidth, 0.75*size);
+
+    double mx = transformedMouseX();
+    double my = transformedMouseY();
+    int click = mouseWentDown("left") && fabs(mx) <= 0.4*buttonWidth && fabs(my) <= 0.4*buttonHeight;
+    rect(-0.4*buttonWidth, -0.4*buttonHeight, 0.8*buttonWidth, 0.8*buttonHeight);
+    if (started) {
+      text("pause", 0, 0);
+      if (click) {
+        soundStop(alarmSound);
+        started = alarm = FALSE;
+      }
+    } else {
+      text("start", 0, 0);
+      if (click) { subSeconds = 0; started = TRUE; }
+    }
+
+    translate(buttonWidth, 0);
+    mx -= buttonWidth;
+    click = mouseWentDown("left") && fabs(mx) <= 0.4*buttonWidth && fabs(my) <= 0.4*buttonHeight;
+    rect(-0.4*buttonWidth, -0.4*buttonHeight, 0.8*buttonWidth, 0.8*buttonHeight);
+    text("reset", 0, 0);
+    if (click) {
+      soundStop(alarmSound);
+      started = alarm = FALSE;
+      hours = minutes= seconds = 0;
+    }
+  }
+  restoreState();
+
+  restoreState();
+}
+
+
+int main() {
+  worldSetVariableFrameRate();
+  worldSetInit(&init);
+  worldSetDraw(&draw);
+  worldRun();
+  return 0;
+}