diff --git a/review2.pl~ b/review2.pl
index e57d63a..f8520c2 100644
--- a/review2.pl~
+++ b/review2.pl
@@ -31,7 +31,53 @@ open (HEADER, "/org/lists.debian.org/html/header.in") or
   die ("$0: can't open /org/lists.debian.org/html/header.in: $!\n");
 while (<HEADER>) {
   s/u_TITLE_u/Listarchive Review $names[0]/;
-  print "<META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\"></head>\n" if m#^</head>$#;
+  print "<META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\">\n" if m#^</head>$#;
+  print "<script type=\"text/javascript\">
+<!--
+document.defaultAction = true;
+document['onkeydown'] = detectEvent;
+
+function detectEvent(e)
+{
+  var evt = e || window.event;
+  var pos = parseInt(window.location.hash.substring(1)) || $count_out+1;
+  var elPrefix = '';
+
+  switch(evt.keyCode) {
+    case 74: // j
+      pos -= 1;
+      if (pos < 1) { pos = 1; }
+      break;
+    case 75: // k
+      pos += 1;
+      if (pos > $count_out) { pos = $count_out; }
+      break;
+    case 65: // a
+      elPrefix = 'u';
+      break;
+    case 83: // s
+      elPrefix = 'h';
+      break;
+    case 68: // d
+      elPrefix = 'i';
+      break;
+    case 70: // f
+      elPrefix = 's';
+      break;
+  }
+
+  if (1 <= pos && pos <= $count_out) {
+    window.location.hash = pos;
+  }
+
+  if (elPrefix != '') {
+    document.getElementById(elPrefix+pos).checked = true;
+  }
+
+  return document.defaultAction;
+}
+// -->
+</script></head>\n" if m#^</head>$#;
   print;
 }
 close (HEADER);
@@ -61,6 +107,11 @@ if (-e $cachedir . '/' . $names[0]) {
 	<tr><td>Inappropriate<td>misguided ((un)subscribe to list, test messages, replies to spam messages, vac messages, spam backscatter, probably NOT votes sent to debian-devel instead of devotee) messages, is is not entirely clear what to do with those, but please tag them accordingly.
 	<tr><td>Unsure<td><strong>The Default</strong>. You are not sure if it is Spam or Ham or Inappropriate.</table>\n";
 
+  print "
+	<p><small>
+		Keys: <b>j</b>/<b>k</b> move down/up, <b>a</b>/<b>s</b>/<b>d</b>/<b>f</b> mark current entry as Ham/Spam/Inappropriate/Unsure respectively
+	</small></p>\n";
+
   print "<form action=\"/cgi-bin/review/review3.pl\" method=\"GET\">\n";
 
   foreach (<CACHE>) {
@@ -76,16 +127,17 @@ if (-e $cachedir . '/' . $names[0]) {
     next if $reviewcount{$post} >= $SpamReviewConf::max_reviews;
     last unless $count_out--;
     print "<hr noshade width=\"100%\" size=\"1\"><a href=\"https://lists.debian.org/$post\" target=\"_blank\">https://lists.debian.org/$post</a>
+      <a name=\"$count_out\">&nbsp;</a>
       <iframe src=\"https://lists.debian.org/$post\" name=\"https://lists.debian.org/$post\"
       width=\"95%\" height=\"400\" scrolling=\"yes\"
       hspace=\"10\" vspace=\"10\"
       marginwidth=\"20\" marginheight=\"10\" frameborder=\"1\">
       <a href=\"https://lists.debian.org/$post\" target=\"_blank\">https://lists.debian.org/$post</a>
       </iframe><br>\n";
-    print "<label><input type=\"radio\" name=\"$post\" value=\"Unsure\" checked> Unsure </label>\n";
-    print "<label><input type=\"radio\" name=\"$post\" value=\"Ham\"> Ham </label>\n";
-    print "<label><input type=\"radio\" name=\"$post\" value=\"Inappropriate\"> Inappropriate </label>\n";
-    print "<label><input type=\"radio\" name=\"$post\" value=\"Spam\"> Spam </label>\n";
+    print "<label><input id=\"u$count_out\" type=\"radio\" name=\"$post\" value=\"Unsure\" checked> Unsure </label>\n";
+    print "<label><input id=\"h$count_out\" type=\"radio\" name=\"$post\" value=\"Ham\"> Ham </label>\n";
+    print "<label><input id=\"i$count_out\" type=\"radio\" name=\"$post\" value=\"Inappropriate\"> Inappropriate </label>\n";
+    print "<label><input id=\"s$count_out\" type=\"radio\" name=\"$post\" value=\"Spam\"> Spam </label>\n";
   }
   close (CACHE);
 } else {
