 
     unless keyHandler
       keyHandler = handlerStack.push
-        keydown: (event) -> lastEvent = event
+        keydown: -> lastEvent = event
         keyup: -> time += 1
 
     axisName = scrollProperties[direction].axisName

#   - For non-smooth scrolling, the entire scroll happens immediately.
#   - For smooth scrolling with distinct key presses, a separate animator is initiated for each key press.
#     Therefore, several animators may be active at the same time.  This ensures that two quick taps on `j`
#     scroll to the same position as two slower taps (modulo rounding errors).
#   - For smooth scrolling with keyboard repeat, the most recently-activated animator continues scrolling
#     at least until its corresponding keyup event is received.  We never initiate a new animator on keyboard
#     repeat.

# Scroll by a relative amount (a number) in some direction, possibly smoothly.
doScrollBy = do ->


    mostRecentActivationTime = activationTime = ++time

    isKeyStillDown = ->
      time == activationTime

    # Store amount's sign and make amount positive; the logic is clearer when amount is positive.
    sign = amount / Math.abs amount
    amount = Math.abs amount

    # Duration in ms. Allow a bit longer for longer scrolls.
    duration = Math.max 100, 20 * Math.log amount

    totalDelta = 0
    calibration = 1.0
    initialTimestamp = null
    previousTimestamp = null
    animatorId = null

    progressAnimation = ->
      animatorId = requestAnimationFrame animate

    cancelAnimation = ->
      # Make one final call of performScroll to check the visibility of the activated element.
      performScroll element, axisName, 0, true
      cancelAnimationFrame animatorId

    animate = (timestamp) ->
      initialTimestamp ?= timestamp
      previousTimestamp ?= timestamp

      if timestamp == previousTimestamp
        return progressAnimation()

      # The elapsed time is typically about 16ms.
      elapsed = timestamp - previousTimestamp
      totalElapsed = timestamp - initialTimestamp
      previousTimestamp = timestamp

      # The constants in the duration calculation, above, are chosen to provide reasonable scroll speeds for
      # scrolls resulting from distinct keypresses.  For continuous scrolls (where the key remains depressed),
      # some scrolls are too slow, and others too fast.  Here, we compensate a bit.
      if isKeyStillDown() and 50 <= totalElapsed and 0.5 <= calibration <= 1.6
        calibration *= 1.05 if 1.05 * calibration * amount <= 150 # Speed up slow scrolls.
        calibration *= 0.95 if 150 <= 0.95 * calibration * amount # Slow down fast scrolls.

      # Calculate the initial delta, rounding up to ensure progress.  Then, adjust delta to account for the
      # current scroll state.
      delta = Math.ceil amount * (elapsed / duration) * calibration
      delta = if isKeyStillDown() then delta else Math.max 0, Math.min delta, amount - totalDelta

      if delta and performScroll element, axisName, sign * delta, false
        totalDelta += delta
        progressAnimation()
      else
        # We're done. Make one final call of performScroll to check the visibility of the activated element.
        performScroll element, axisName, 0, true
        cancelAnimationFrame animatorId

    progressAnimation()

Scroller =
  init: (frontendSettings) ->
