   #
   # - `ms.duration`- Duration of sleep and wake animations.
   # - `ms.delay` - Delay before sleeping and waking.
-  # - `cls.stem` - Empty to remove the stem.
-  # - `cls.follow` - Empty to disable cursor following.
+  # - `classNames.stem` - Empty to remove the stem.
+  # - `classNames.follow` - Empty to disable cursor following.
   # - `safeToggle` - Prevents orphan tips, since timers are sometimes unreliable.
   hlf.tip =
     debug: off

        when 'class'  then 'js-tips'
        else 'hlf.tip'

    defaults: do (pre='js-tip-') ->
      ms:
        duration:
          in: 200

      yes

    # - Much simpler toggler. As long as tip isn't truly visible, sleep is unneeded.
    sleepByTrigger: ($trigger) ->
      return no if @_state isnt 'awake'
      @_state = 'sleeping'
      clearTimeout @_wakeCountdown
      @_sleepCountdown = setTimeout =>
        @onHide()
        @$tip.fadeOut @ms.duration.out, =>
          @_state = 'asleep'
          @afterHide()
      , @ms.delay.out
      # - Success.
      yes

    # Hooks
    onShow: (triggerChanged, event) -> return
    onHide: $.noop
    afterShow: (triggerChanged, event) -> return
    afterHide: $.noop
    htmlOnRender: $.noop
    offsetOnTriggerMouseMove: (event, offset, $trigger) -> no

  # SnapTip API
  # -----------
  class SnapTip extends Tip

    constructor: ($triggers, options, $context) ->
      super $triggers, options, $context
      if @snap.toTrigger is off
        @snap.toTrigger = @snap.toXAxis is on or @snap.toYAxis is on
      if @snap.toXAxis is on then @cursorHeight = 0
      if @snap.toYAxis is on then @cursorHeight = 2
      @_offsetStart = null
      # - Add snapping config as classes.
      @$tip.addClass @classNames.snap[key] for key, active of @snap when active

    # ###Protected

    # - The main positioner. Uses the trigger offset as the base.
    #   TODO - Still needs to support all the directions.
    _moveToTrigger: ($trigger, baseOffset) ->
      #@debugLog baseOffset
      offset = $trigger.offset()
      if @snap.toXAxis is on
        if @isDirection 'south' then offset.top += $trigger.outerHeight()
        if @snap.toYAxis is off
          offset.left = baseOffset.left - (@$tip.outerWidth() - 12)/ 2 # Note arbitrary buffer offset.
      if @snap.toYAxis is on
        if @isDirection 'east' then offset.left += $trigger.outerWidth()
        if @snap.toXAxis is off
          offset.top = baseOffset.top - @$tip.outerHeight() / 2
      offset

    # - Bind to get initial position for snapping. This is only for snapping
    #   without snapping to the trigger, which is only what's currently supported.
    #   See `afterShow` hook.
    _bindTrigger: ($trigger) ->
      super $trigger
      $trigger.on @evt('truemouseleave'), (event) => @_offsetStart = null

    # ###Public

    # Hooked

    # - Make the tip invisible while it's being positioned, then reveal it.
    onShow: (triggerChanged, event) ->
      if triggerChanged is yes
        @$tip.css 'visibility', 'hidden'

    afterShow: (triggerChanged, event) ->
      if triggerChanged is yes
        @$tip.css 'visibility', 'visible'
        @_offsetStart =
          top: event.pageY
          left: event.pageX

    # - Main positioning handler.
    offsetOnTriggerMouseMove: (event, offset, $trigger) ->
      newOffset = _.clone offset
      if @snap.toTrigger is on
        newOffset = @_moveToTrigger $trigger, newOffset
      else
        if @snap.toXAxis is on
          newOffset.top = @_offsetStart.top
          @debugLog 'xSnap'
        if @snap.toYAxis is on
          newOffset.left = @_offsetStart.left
          @debugLog 'ySnap'
      newOffset
