3
\$\begingroup\$

An event source built around an observer design pattern implementation…

var observableValue = function(value) {
    
    return ( function() { 
        var observed = value;
        var get = function () { return observed; };
        var set = function (newValue) { observed = newValue; };
        
        var withdraw = function (value) {
            
            var once = function(values) {
                var index = values.indexOf(value);
                if ( index > -1 ) {
                    values.splice(index, 1);

                    return true;
                }

                return false;
            }
            var remover = once;
            
            return { from: function(values) {

                               return remover(values);
                     }
                   , entierly: function() {
                    
                                   remover = function(values) {
                                                 var index = values.indexOf(value);
                                                 if ( index == -1 ) { return false; }
                                                 for ( var i = 0; i < values.length; i++ ) {
                                                     if ( values[i] === value ) {
                                                         values.splice(i, 1);
                                                     }
                                                 }
                                                 return true;
                                             };
                    
                                   return this;
                   }
                 , once: function() {
                    
                             remover = once;
                    
                             return this;
            }};
        };
        
        return { value: get
               , observers: []
               , oneTimeObservers: []
               , is: function(value) {
                      
                         var priorValue = get();
                         set(value);
                         this.updateObservers(priorValue, value);
                 }
               , updateObservers: function(priorValue, value) {

                                       var context = this;
                                       setTimeout( () => {
                                           context.observers.forEach((observer) => { 
                                               observer.update.call(observer, priorValue, value);
                                           });

                                           var oneTimeObservers = context.oneTimeObservers;
                                           for ( var i = 0; i < oneTimeObservers.length; i++ ) {
                                               var observer = oneTimeObservers.pop();
                                               if ( !observer.update.call(observer
                                                                        , priorValue
                                                                        , value) ) {
                                                   context.oneTimeObservers.push(observer);
                                               }
                                           }
                                       }, 1);
                 }
               , observedBy: function(observer) {

                     this.observers.push(observer);
                     return true;
               }
               , oneTimeObservedBy: function(observer) {

                     this.oneTimeObservers.push(observer);
                     return true;
               }
               , withdrawObserver: function(observer) {
                     return withdraw(observer).from(this.observers);
               }
               , withdrawOneTimeObserver: function(observer) {
                   
                     return withdraw(observer).from(this.oneTimeObservers);
        }};
 })();};

…generating events in the interval from zero to a configurable value that could be changed during runtime…

var rotor = function(area, numberOfSteps, waver, stepDurationsMillis) {

    var gear = { supply: function(handler) {
        
        var next = observableValue(0);
        var activeHandlerIndex = 0;
        var neutral = () => { return null; };
        var handlers = [neutral];
        var handle = { it: function(handlee) { 
                           
                           if ( handlee ) { return handlers.push(handlee); }
                           return false;
                     } };
        (function(handlee) { handle.it(handlee); })(handler);
        
        return {
            handled: { by: function(handler) {

                return (handlers.length == 2) ? false : handle.it(handler) && true;
            } } 
            , activeHandler: function() {

                return handlers[activeHandlerIndex];
            }
            , couple: function(next, context, stepDurationMillis) {

                var handler = this.activeHandler()
                  , handlerContext = this;

                return handler.call(handlerContext, next, context, stepDurationMillis);
            }
            , shift: { it: function() {
                           activeHandlerIndex = (activeHandlerIndex + 1) % handlers.length;
                       }
                     , to: { neutral: function() { activeHandlerIndex = 0; } }
            }
            , isNeutral: function() { return activeHandlerIndex == 0; }
            , stepObservedBy: function(observer) { next.observedBy(observer); }
            , withdrawStepObserver: function(observer) { next.withdrawObserver(observer); }
            , stepValueIs: function(step) { next.is(step); }
            , stepValue: function() { return next.value(); }
        };
    } };

    var visualizer = function(numberOfValues, visual) {

        var valueShades = (function(numberOfShades) {

            var shades = []
            , shadeStep = Math.floor(50 / numberOfShades);

            for ( var i = 0; i < numberOfShades; i++ ) {

                shades.push("hsl(205, 20%, " + (50 + shadeStep * i) + "%)");
            }

            return shades;
        })(numberOfValues - 1).concat(["transparent"]);

        return {
            visual: visual
            , valueShades: valueShades
            , visual: visual
            , update: function(previousIndex, activeIndex) {

                this.visual.style.backgroundColor = this.valueShades[activeIndex];
            }
        };
    };
    
    var dot = (function() {

                   var dot = document.createElement("div");
                   dot.classList.add("dot");

                   return dot;
    })();

    var computeOrbitSteps = function(numberOfSteps) {

                                var steps = []
                                  , radians = ((360 / numberOfSteps) * 2 * Math.PI) / 360
                                  , cos = Math.cos(radians)
                                  , sin = Math.sin(radians)
                                  , radius = Math.round(area.offsetWidth / 2)
                                  , originX = radius
                                  , originY = radius
                                  , margin = radius - Math.round(radius / 2)
                                  , x1 = radius - margin
                                  , y1 = radius - margin;

                                for ( var i = 0; i < numberOfSteps; i++ ) {

                                    var x2 = (Math.round(x1 * cos - y1 * sin))
                                      , y2 = (Math.round(y1 * cos + x1 * sin))
                                      , x = originX + x2
                                      , y = originY + y2;

                                    steps.push([x, y]);

                                    x1 = x2;
                                    y1 = y2;
                                }

                                return steps;
    };
    var orbitSteps = computeOrbitSteps(numberOfSteps);

    var stepDurationsMillisArray = [stepDurationsMillis].reduce(
                                       (accumulator, element) => accumulator.concat(element), []
                                   );

    var handler = function(nextStep, context, durationMillis) {

                       this.stepValueIs(nextStep);

                       return setTimeout( () => { context.step.call(context, nextStep); }
                                       , durationMillis );
    };
    var coupler = gear.supply(handler);

    var observableStepDurationIndex = observableValue();
    var visualHint = visualizer(stepDurationsMillisArray.length, area);
    observableStepDurationIndex.observedBy(visualHint);
    
    return {
        orbiter: dot
        , rotorArea: area
        , orbitSteps: orbitSteps
        , waver: waver
        , inMotion: observableValue()
        , timedStepsId: []
        , stepDurationsMillisArray: stepDurationsMillisArray
        , coupler: coupler
        , stepDurationIndex: observableStepDurationIndex
        , visualHint: visualHint
        , stepDurationIndexObservedBy: function(observer) {

            this.stepDurationIndex.observedBy(observer);
        }
        , stepObservedBy: function(observer) {

            this.coupler.stepObservedBy(observer);
        }
        , computeOrbitSteps: computeOrbitSteps
        , on: function() {

            this.stepDurationIndex.is(0);
            this.resume();
            this.rotorArea.appendChild(this.orbiter);
        }
        , orbitStepsAre: function(numberOfSteps) {

            this.off();
            while (!this.isPaused()) {
                this.pauseOrShift();
            }
            this.orbitSteps = this.computeOrbitSteps(numberOfSteps);
            this.coupler.stepValueIs(this.coupler.stepValue() % numberOfSteps);
            this.resume();
        }
        , off: function() {
            
            this.pause();
            for ( var i = 0; i < this.timedStepsId.length; i++ ) {
                clearTimeout(this.timedStepsId[i]);
            }
        }
        , resume: function() {

            this.coupler.shift.it();
            this.step(this.coupler.stepValue());
            this.inMotion.is(true);
        }
        , pause: function() {

            this.coupler.shift.to.neutral();
            this.inMotion.is(false);
        }
        , isPaused: function() {

            return this.coupler.isNeutral();
        }
        , pauseOrShift: function() {

            if ( this.isPaused() ) {
                this.resume();
                return;
            }

            var stepMillisLength = this.stepDurationsMillisArray.length;
            var durationIndex = 0;
            if ( this.stepDurationIndex.value() == stepMillisLength - 1 ) {
                this.pause();
            } else {
                durationIndex = (this.stepDurationIndex.value() + 1) % stepMillisLength;
            }
            this.stepDurationIndex.is(durationIndex);
        }
        , motionObservedBy: function(observer) {
            this.inMotion.observedBy(observer);
        }
        , withdrawMotionObserver: function(observer) {
            return this.inMotion.withdrawObserver(observer);
        }
        , step: function(step) {
            this.timedStepsId.pop();
            this.orbiter.style.left = this.orbitSteps[step][0];
            this.orbiter.style.top = this.orbitSteps[step][1];
            var context = this
              , nextStep = this.waver.nextStep(step)
              , timedStepId = this.coupler
                                  .couple(nextStep
                                        , context
                                        , this.stepDurationsMillisArray[
                                              this.stepDurationIndex.value()
                                           ]
                                  );

            if ( timedStepId ) { this.timedStepsId.push(timedStepId); }
        }
    };
};

…in the direction given by the set of strategy design pattern implementations passed among the arguments (waver argument from previous code snippet that is of type wobbler see showcase implementation for its implementation details) supporting direction sensitive transition between disjunctive subintervals when the used strategy and the strategy to be used generates values in subintervals.

Technically it is a rotor taking observers for the step event it generates for a sequence of values between zero and including the numberOfSteps argument, in the direction retrieved from an argument of type wobbler with configurable frequency or frequencies, while visually representing the position of the value in the interval using precomputed coordinates for a graphical pointer that it is placed on the HTMLElement area argument and also the frequency of event generation using a visualizer that changes the background-color of the afore mentioned area argument. Along with step event it also generates events for pausing and for event generation frequency change. An additional event it is generated by the wobbler for changing the orbit, the direction of value generation strategy.

Following a showcase of features it supports responsive to click, double click, mouse down and mouse up mouse events…

<html>
<head>
<style>
body { height: 100%; margin: 0; padding: 0; width: 100%; }
.loading .whirl { height: 120px; width: 120px; }
.loading .whirl:hover, .loading .cluster:hover { box-shadow: none; }
.loading .roller { opacity: 0; }
.loading .bottom-lane { bottom: 0; height: 50%; left: 0; margin: auto; right: 0; top: 0; }
.loading .art-picker { opacity: 1; visibility: visible; }
.loading .cluster { width: 150px; }
.loading .cluster .alternative-text { color: #888; display: inline; font-size: 24px; opacity: 1; position: relative; top: 40%; width: 100%; }
.loading .cluster-hover { background-image: none; }
.loading .cluster-art { background-image: none; }
.loading .key, .loading .key-frame { display: none; opacity: 0; }
.art-transition {}
.key { transition: opacity 1s linear; }
.key-frame { position:relative; height: 100%; width: 100%; }
.key-wrap {}
.cluster { background-color: #ddd; background-position: center; background-repeat: no-repeat; background-size: contain; cursor: pointer; display: inline-block; height: 100%; margin-right: 5px; width: 90px; transition: background-image 1s linear, box-shadow 1s linear; }
.cluster .alternative-text { display: none; }
.cluster-hover {}
.cluster-hover .key-wrap { height: 70%; width: 70%; }
.cluster-hover .key { border-radius: 50%; box-shadow: 0px 0px 10px 5px #fff; height: 80%; width: 100%; }
.cluster-art {}
.cluster-art .key-wrap { height: 85%; width: 80%; }
.cluster-art .key { color: #fdfdfd; float: left; height: 27%; text-align: center; width: 32%; }
.centered { bottom: 0; left: 0; margin: auto; position: absolute; right: 0; top: 0; }
.sized { max-height: 400px; max-width: 400px; min-height: 30%; min-width: 30%; }
.art-picker { height: 50%; margin: 5px; text-align: center; opacity: 0; visibility: hidden; transition: opacity 1s linear, visibility 1s linear; }
.bottom-lane { background-color: transparent; bottom: 0; height: 210px; position: absolute; width: 100%; transition: margin 1s linear, height 1s linear; }
.whirl { background-color: #778899; border-radius: 50%; cursor: pointer; height: 80px; position: relative; transition: background-color 1s linear, box-shadow 1s linear; width: 80px; }
.whirl:hover, .cluster:hover { box-shadow: 0px 0px 10px 5px #fff, 0px 5px 20px 0 #778899; }
.dot { background-color: #dfdfdf; border-radius: 50% 50% 50% 50%; height: 3px; position: absolute; width: 3px; }
.alternative-text { display: inline; opacity: 0; }

.hover.paused { box-shadow: 0px 0px 5px 0px #fff, 0px 0px 100px 0px #778899; }

.hover .symbol { display: none; }
.hover .minute { border-top: 30px solid #777; border-bottom: 30px solid #777; background-color: transparent; position: absolute; width: 2px; height: 400px; }
.hover .minute.quarter { height: 370px; border-top: 45px solid #777; border-bottom: 45px solid #777; }
.hover .minute.fifth { height: 380px; border-top: 40px solid #777; border-bottom: 40px solid #777; }
.hover .minute .stage { background-color: transparent; height: 50%; position: absolute; width: 100%; }
.hover .zero {}
.hover .one { transform:rotate(6deg); }
.hover .two { transform:rotate(12deg); }
.hover .three { transform:rotate(18deg); }
.hover .four { transform:rotate(24deg); }
.hover .five { transform:rotate(30deg); }
.hover .six { transform:rotate(36deg); }
.hover .seven { transform:rotate(42deg); }
.hover .eight { transform:rotate(48deg); }
.hover .nine { transform:rotate(54deg); }
.hover .ten { transform:rotate(60deg); }
.hover .eleven { transform:rotate(66deg); }
.hover .twelve { transform:rotate(72deg); }
.hover .thirteen { transform:rotate(78deg); }
.hover .fourteen { transform:rotate(84deg); }
.hover .fifteen { transform:rotate(90deg); }
.hover .sixteen { transform:rotate(96deg); }
.hover .seventeen { transform:rotate(102deg); }
.hover .eighteen { transform:rotate(108deg); }
.hover .nineteen { transform:rotate(114deg); }
.hover .twenty { transform:rotate(120deg); }
.hover .twentyone { transform:rotate(126deg); }
.hover .twentytwo { transform:rotate(132deg); }
.hover .twentythree { transform:rotate(138deg); }
.hover .twentyfour { transform:rotate(144deg); }
.hover .twentyfive { transform:rotate(150deg); }
.hover .twentysix { transform:rotate(156deg); }
.hover .twentyseven { transform:rotate(162deg); }
.hover .twentyeight { transform:rotate(168deg); }
.hover .twentynine { transform:rotate(174deg); }

.hover.south-0 .zero
, .hover.south-1 .one
, .hover.south-2 .two
, .hover.south-3 .three
, .hover.south-4 .four
, .hover.south-5 .five
, .hover.south-6 .six
, .hover.south-7 .seven
, .hover.south-8 .eight
, .hover.south-9 .nine
, .hover.south-10 .ten
, .hover.south-11 .eleven
, .hover.south-12 .twelve
, .hover.south-13 .thirteen
, .hover.south-14 .fourteen
, .hover.south-15 .fifteen
, .hover.south-16 .sixteen
, .hover.south-17 .seventeen
, .hover.south-18 .eighteen
, .hover.south-19 .nineteen
, .hover.south-20 .twenty
, .hover.south-21 .twentyone
, .hover.south-22 .twentytwo
, .hover.south-23 .twentythree
, .hover.south-24 .twentyfour
, .hover.south-25 .twentyfive
, .hover.south-26 .twentysix
, .hover.south-27 .twentyseven
, .hover.south-28 .twentyeight
, .hover.south-29 .twentynine { border-bottom-color: orangered; }

.hover.south-0 .zero .stage
, .hover.south-1 .one .stage
, .hover.south-2 .two .stage
, .hover.south-3 .three .stage
, .hover.south-4 .four .stage
, .hover.south-5 .five .stage
, .hover.south-6 .six .stage
, .hover.south-7 .seven .stage
, .hover.south-8 .eight .stage
, .hover.south-9 .nine .stage
, .hover.south-10 .ten .stage
, .hover.south-11 .eleven .stage
, .hover.south-12 .twelve .stage
, .hover.south-13 .thirteen .stage
, .hover.south-14 .fourteen .stage
, .hover.south-15 .fifteen .stage
, .hover.south-16 .sixteen .stage
, .hover.south-17 .seventeen .stage
, .hover.south-18 .eighteen .stage
, .hover.south-19 .nineteen .stage
, .hover.south-20 .twenty .stage
, .hover.south-21 .twentyone .stage
, .hover.south-22 .twentytwo .stage
, .hover.south-23 .twentythree .stage
, .hover.south-24 .twentyfour .stage
, .hover.south-25 .twentyfive .stage
, .hover.south-26 .twentysix .stage
, .hover.south-27 .twentyseven .stage
, .hover.south-28 .twentyeight .stage
, .hover.south-29 .twentynine .stage { bottom: 0; background-color: orangered; }

.hover.north-0 .zero
, .hover.north-1 .one
, .hover.north-2 .two
, .hover.north-3 .three
, .hover.north-4 .four
, .hover.north-5 .five
, .hover.north-6 .six
, .hover.north-7 .seven
, .hover.north-8 .eight
, .hover.north-9 .nine
, .hover.north-10 .ten
, .hover.north-11 .eleven
, .hover.north-12 .twelve
, .hover.north-13 .thirteen
, .hover.north-14 .fourteen
, .hover.north-15 .fifteen
, .hover.north-16 .sixteen
, .hover.north-17 .seventeen
, .hover.north-18 .eighteen
, .hover.north-19 .nineteen
, .hover.north-20 .twenty
, .hover.north-21 .twentyone
, .hover.north-22 .twentytwo
, .hover.north-23 .twentythree
, .hover.north-24 .twentyfour
, .hover.north-25 .twentyfive
, .hover.north-26 .twentysix
, .hover.north-27 .twentyseven
, .hover.north-28 .twentyeight
, .hover.north-29 .twentynine { border-top-color: orangered; }

.hover.north-0 .zero .stage
, .hover.north-1 .one .stage
, .hover.north-2 .two .stage
, .hover.north-3 .three .stage
, .hover.north-4 .four .stage
, .hover.north-5 .five .stage
, .hover.north-6 .six .stage
, .hover.north-7 .seven .stage
, .hover.north-8 .eight .stage
, .hover.north-9 .nine .stage
, .hover.north-10 .ten .stage
, .hover.north-11 .eleven .stage
, .hover.north-12 .twelve .stage
, .hover.north-13 .thirteen .stage
, .hover.north-14 .fourteen .stage
, .hover.north-15 .fifteen .stage
, .hover.north-16 .sixteen .stage
, .hover.north-17 .seventeen .stage
, .hover.north-18 .eighteen .stage
, .hover.north-19 .nineteen .stage
, .hover.north-20 .twenty .stage
, .hover.north-21 .twentyone .stage
, .hover.north-22 .twentytwo .stage
, .hover.north-23 .twentythree .stage
, .hover.north-24 .twentyfour .stage
, .hover.north-25 .twentyfive .stage
, .hover.north-26 .twentysix .stage
, .hover.north-27 .twentyseven .stage
, .hover.north-28 .twentyeight .stage
, .hover.north-29 .twentynine .stage { background-color: orangered; top: 0; }

.art .minute { border: none; background-color: transparent; height: 100%; transform: none; width: 100%; }
.art .minute .symbol, .art .minute .stage { font-size: 100px; height: 50%; text-align: center; visibility: hidden; width: 100%; bottom: 0; left: 0; margin: auto; position: absolute; right: 0; top: 0; }
.art .minute .stage { height: 100%; width: 100%; }
.art .minute .stage .symbol { visibility: inherit; }

.art.north-0 .zero .symbol
, .art.north-1 .one .symbol
, .art.north-2 .two .symbol
, .art.north-3 .three .symbol
, .art.north-4 .four .symbol
, .art.north-5 .five .symbol
, .art.north-6 .six .symbol
, .art.north-7 .seven .symbol
, .art.north-8 .eight .symbol
, .art.north-9 .nine .symbol
, .art.north-10 .ten .symbol
, .art.north-11 .eleven .symbol
, .art.north-12 .twelve .symbol
, .art.north-13 .thirteen .symbol
, .art.north-14 .fourteen .symbol
, .art.north-15 .fifteen .symbol
, .art.north-16 .sixteen .symbol
, .art.north-17 .seventeen .symbol
, .art.north-18 .eighteen .symbol
, .art.north-19 .nineteen .symbol
, .art.north-20 .twenty .symbol
, .art.north-21 .twentyone .symbol
, .art.north-22 .twentytwo .symbol
, .art.north-23 .twentythree .symbol
, .art.north-24 .twentyfour .symbol
, .art.north-25 .twentyfive .symbol
, .art.north-26 .twentysix .symbol
, .art.north-27 .twentyseven .symbol
, .art.north-28 .twentyeight .symbol
, .art.north-29 .twentynine .symbol { visibility: visible; }

.art.north-0 .stage .symbol
, .art.north-1 .stage .symbol
, .art.north-2 .stage .symbol
, .art.north-3 .stage .symbol
, .art.north-4 .stage .symbol
, .art.north-5 .stage .symbol
, .art.north-6 .stage .symbol
, .art.north-7 .stage .symbol
, .art.north-8 .stage .symbol
, .art.north-9 .stage .symbol
, .art.north-10 .stage .symbol
, .art.north-11 .stage .symbol
, .art.north-12 .stage .symbol
, .art.north-13 .stage .symbol
, .art.north-14 .stage .symbol
, .art.north-15 .stage .symbol
, .art.north-16 .stage .symbol
, .art.north-17 .stage .symbol
, .art.north-18 .stage .symbol
, .art.north-19 .stage .symbol
, .art.north-20 .stage .symbol
, .art.north-21 .stage .symbol
, .art.north-22 .stage .symbol
, .art.north-23 .stage .symbol
, .art.north-24 .stage .symbol
, .art.north-25 .stage .symbol
, .art.north-26 .stage .symbol
, .art.north-27 .stage .symbol
, .art.north-28 .stage .symbol
, .art.north-29 .stage .symbol { visibility: hidden; }

.art.south-0 .zero .stage
, .art.south-1 .one .stage
, .art.south-2 .two .stage
, .art.south-3 .three .stage
, .art.south-4 .four .stage
, .art.south-5 .five .stage
, .art.south-6 .six .stage
, .art.south-7 .seven .stage
, .art.south-8 .eight .stage
, .art.south-9 .nine .stage
, .art.south-10 .ten .stage
, .art.south-11 .eleven .stage
, .art.south-12 .twelve .stage
, .art.south-13 .thirteen .stage
, .art.south-14 .fourteen .stage
, .art.south-15 .fifteen .stage
, .art.south-16 .sixteen .stage
, .art.south-17 .seventeen .stage
, .art.south-18 .eighteen .stage
, .art.south-19 .nineteen .stage
, .art.south-20 .twenty .stage
, .art.south-21 .twentyone .stage
, .art.south-22 .twentytwo .stage
, .art.south-23 .twentythree .stage
, .art.south-24 .twentyfour .stage
, .art.south-25 .twentyfive .stage
, .art.south-26 .twentysix .stage
, .art.south-27 .twentyseven .stage
, .art.south-28 .twentyeight .stage
, .art.south-29 .twentynine .stage { visibility: visible; }

.roller.slow { transition: opacity 1s linear 1s, background-color linear .4s, box-shadow 1s linear; }
.roller.moderate { transition: opacity 1s linear 1s, background-color linear .25s; }
.roller.fast { transition: opacity 1s linear 1s, background-color linear .05s; }
.roller { background-color: transparent; border-radius: 50%; background-position: center; background-repeat: no-repeat; background-size: contain; opacity: .2; transition: opacity 1s linear 1s; }
.roller.art-transition { transition: background-image 1s linear; }
.roller a { display: none; }
.roller.art .art-clock-animation { display: inline-block; }
.roller.art .art-clock-animation { display: inline-block; }
.roller.slow.hover .hover-slow-animation { display: inline-block; }
.roller.fast.hover .hover-fast-animation { display: inline-block; }
.shed { background-color: grey; cursor: auto; height: 100%; opacity: .3; position: absolute; width: 100%; }
.overlay { bottom: 0; margin: auto; position: absolute; right: 0; height: 100%; width: 100%; }
.transparent { opacity: 0; }
.visible { opacity: 1; visibility: visible; }
.image-preloader { height: 0; overflow: hidden; visibility: hidden; width: 0; }
</style>
<script type="text/javascript">
var hoverSequence = ["hover south-0", "hover south-1", "hover south-2", "hover south-3", "hover south-4", "hover south-5", "hover south-6", "hover south-7"
                   , "hover south-8", "hover south-9", "hover south-10", "hover south-11", "hover south-12", "hover south-13", "hover south-14", "hover south-15"
                   , "hover south-16", "hover south-17", "hover south-18", "hover south-19", "hover south-20", "hover south-21", "hover south-22", "hover south-23"
                   , "hover south-24", "hover south-25", "hover south-26", "hover south-27", "hover south-28", "hover south-29", "hover north-0", "hover north-1"
                   , "hover north-2", "hover north-3", "hover north-4", "hover north-5", "hover north-6", "hover north-7", "hover north-8", "hover north-9"
                   , "hover north-10", "hover north-11", "hover north-12", "hover north-13", "hover north-14", "hover north-15", "hover north-16", "hover north-17"
                   , "hover north-18", "hover north-19", "hover north-20", "hover north-21", "hover north-22", "hover north-23", "hover north-24", "hover north-25"
                   , "hover north-26", "hover north-27", "hover north-28", "hover north-29"]
  , artSequence = ["art south-0", "art south-1", "art south-2", "art south-3", "art south-4", "art south-5", "art south-6", "art south-7"
                 , "art south-8", "art south-9", "art south-10", "art south-11", "art south-12", "art south-13", "art south-14", "art south-15"
                 , "art south-16", "art south-17", "art south-18", "art south-19", "art south-20", "art south-21", "art south-22", "art south-23"
                 , "art south-24", "art south-25", "art south-26", "art south-27", "art south-28", "art south-29", "art north-0", "art north-1"
                 , "art north-2", "art north-3", "art north-4", "art north-5", "art north-6", "art north-7", "art north-8", "art north-9"
                 , "art north-10", "art north-11", "art north-12", "art north-13", "art north-14", "art north-15", "art north-16", "art north-17"
                 , "art north-18", "art north-19", "art north-20", "art north-21", "art north-22", "art north-23", "art north-24", "art north-25"
                 , "art north-26", "art north-27", "art north-28", "art north-29"]
  , sequence = hoverSequence
  , observer = { update: function(priorValue, activeValue) { return true; } };

var element = { of: (selector) => document.querySelector(selector) };

var threshold = (function() {
    
    return { after : (betweenShifts) => {
        
        var millisSinceShift = Date.now();
        
        return {
            
            millis : () => { return {
            
                or : (alternativeFunction) => {
                    
                    return (shiftFunction) => {
                        
                        if ( Date.now() - millisSinceShift >= betweenShifts ) {
                            millisSinceShift = Date.now();
                            shiftFunction();
                            return;
                        }
                        millisSinceShift = Date.now();
                        setTimeout( () => { alternativeFunction(); }, 0);
                    };
                    
                }
            } }
            , taps : () => {
                    
                    return { inMillis : (millisBetweenTaps) => {
                        
                        return { or : (alternativeFunction) => {
                            
                            var counter = (function(from) {
                                var tapCounter = from;
                                
                                return { incremented : () => { return ++tapCounter; } 
                                       , decremented : () => { return --tapCounter; }
                                       , counted : () => { return tapCounter; }
                                       , reset : () => { tapCounter = from; }
                                       };
                            })(1);
                            
                            return (shiftFunction) => {
                                
                                    if ( Date.now() - millisSinceShift >= millisBetweenTaps ) { 
                                        millisSinceShift = Date.now();
                                        counter.reset();
                                    }
                                    
                                    if ( counter.counted() == betweenShifts ) {
                                        setTimeout(() => { alternativeFunction(); }, 0);
                                        return;
                                    }
                                    
                                    if ( counter.incremented() == betweenShifts ) {
                                        shiftFunction();
                                    }
                            };
                        } };
                    }
                };
            }
        };
    } };
})();

var buffered = { source: function(bufferee) {
    
    var enabler = { supply: (purpose) => {
        
            var vain = purpose ? () => purpose() : () => false;
            var enablers = [vain];
            
            return { shift: function() { return enablers[0](); }
                   , schedule: function(schedulee) { 
                       enablers.unshift( () => { enablers.shift(); return schedulee(); } );
                       return this;
                   }
            };
    } };
    
    var returnee = bufferee();
    var singularity = enabler.supply(() => returnee);
    if ( !returnee ) { singularity.schedule(() => { returnee = bufferee(); return returnee; }); }
    
    return { element: () => singularity.shift()
           , reset: () => { returnee = bufferee(); } };
} };

function removeCssClassName(toRemoveFrom, classNames) {

    var index = -1
      , className = " " + toRemoveFrom.className + " ";

    for ( var i = 0; i < classNames.length; i++ ) {

        var toRemove = " " + classNames[i] + " ";

        if ( className.indexOf(toRemove) > -1 ) {
            index = i; 
            toRemoveFrom.className = className.replace(toRemove, " ");
            break;
        }
    }

    return index;
}

function joinCssClassNames(start, end) { return (start || "").trim() + " " + (end || "").trim(); }

function playHover() { playContent(hoverSequence); }

function playArt() { playContent(artSequence); }

function playContent(newSequence) {

    if ( sequence == newSequence ) { 
        selector.schedule.hide();
        return;
    }

    roller.classList.add("art-transition");

    var transitionMillis = 1000;
    setTimeout( () => { roller.classList.remove("art-transition"); }, transitionMillis );

    var transitionDelayMillis = 10;
    setTimeout( () => {
                    var index = removeCssClassName(roller, sequence);
                    if ( index > -1 ) {
                        roller.className = joinCssClassNames(roller.className, newSequence[index % newSequence.length]);
                    }
                    sequence = newSequence;
                    sparker.orbitStepsAre(sequence.length);
                    selector.schedule.hide();
    }, transitionDelayMillis );
}

var roll = (function() {
    var timedRollId;
    var clearTimedRoll = () => { timedRollId = null; };
    
    var shedWhirl = function() {

        var div = document.createElement("div");
        div.className = "shed whirl";
        div.addEventListener("click", function(event) { event.stopPropagation(); });

        element.of(".whirl").appendChild(div);

        return div;
    };

    var wave = () => {
        if ( timedRollId || sparker.isPaused() ) { return; }

        var rollDelayMillis = 250;
        timedRollId = setTimeout(() => {
                                     var shedDiv = shedWhirl();
                                     waver.wave({ update: function() { shedDiv.remove(); } });
                                     clearTimedRoll();
        }, rollDelayMillis );
    };
    var balance = () => {
        if (timedRollId) {
            clearTimeout(timedRollId);
            clearTimedRoll();
        }
        sparker.pauseOrShift();
    };
    var reveal = (event) => { selector.schedule.reveal(event); };
    var millisBetweenShifts = 500;
    var balancer = threshold.after(2).taps().inMillis(millisBetweenShifts/2).or(balance);
    var shift = threshold.after(millisBetweenShifts).millis().or(() => {});
    var animator = () => { balancer(() => { shift(() => { wave(); } ); }); };
    
    return { animation: (event) => { blur(); animator(); }
           , clear: { timedRoll: () => { clearTimedRoll(); } }
    };
})();

function rollAnimation(event) { roll.animation(event); }

var selector = (function(picker, gauge) {
    
    var scheduleds = (function() {
        var revealId, hideId;
        
        return { reveal: { id: (scheduleId) => { if ( scheduleId ) { revealId = scheduleId; } return revealId; }
                         , clear: () => { revealId = null; }
                 }
               , hide: { id: (scheduleId) => { if ( scheduleId) { hideId = scheduleId; } return hideId; }
                       , clear: () => { hideId = null; }
        } };
    })();
    
    var hideContentSelector = function() { if ( picker.element() ) { picker.element().classList.remove("visible"); } };
    
    var captureClick = function(event) { event.stopPropagation(); window.removeEventListener("click", captureClick, true); };
    
    return { schedule: { reveal: (event) => {

                             if ( picker.element().classList.contains(".visible") || event.target != gauge.element() ) { return; }

                             var mouseHoldDownMillis = 2000;
                             var visibilityMillis = 7000;
                             var timedRevealId = setTimeout( () => {
                                        window.addEventListener("click", captureClick, true);
                                        picker.element().classList.add("visible");
                                        scheduleds.reveal.clear();

                                        var timedHideId = setTimeout( () => {
                                                                          hideContentSelector();
                                                                          scheduleds.hide.clear();
                                                                      }
                                                                    , visibilityMillis );
                                        scheduleds.hide.id(timedHideId);
                             }, mouseHoldDownMillis );
                             scheduleds.reveal.id(timedRevealId);
                       }
                       , hide: () => { hideContentSelector(); }
             }
           , cancel: { reveal: (event) => {
                           if ( !scheduleds.reveal.id() || event.target != gauge.element() ) { return; }

                           clearTimeout(scheduleds.reveal.id());
                           scheduleds.reveal.clear();
                       }
                     , hide: () => {
                           if ( scheduleds.hide.id() ) {
                               clearTimeout(scheduleds.hide.id());
                               scheduleds.hide.clear();
                           }
             } }
    };
})(buffered.source(() => element.of(".art-picker")), buffered.source(() => element.of(".whirl")));

function scheduleRevealContentSelector(event) { selector.schedule.reveal(event); }

function cancelRevealContentSelector(event) { selector.cancel.reveal(event); }

var observableValue = function(value) {
    
    return ( function() { 
        var observed = value;
        var get = function () { return observed; };
        var set = function (newValue) { observed = newValue; };
        
        var withdraw = function (value) {
            
            var once = function(values) {
                var index = values.indexOf(value);
                if ( index > -1 ) {
                    values.splice(index, 1);

                    return true;
                }

                return false;
            }
            var remover = once;
            
            return { from: function(values) {

                               return remover(values);
                     }
                   , entierly: function() {
                    
                                   remover = function(values) {
                                                 var index = values.indexOf(value);
                                                 if ( index == -1 ) { return false; }
                                                 for ( var i = 0; i < values.length; i++ ) {
                                                     if ( values[i] === value ) {
                                                         values.splice(i, 1);
                                                     }
                                                 }
                                                 return true;
                                             };
                    
                                   return this;
                   }
                 , once: function() {
                    
                             remover = once;
                    
                             return this;
            }};
        };
        
        return { value: get
               , observers: []
               , oneTimeObservers: []
               , is: function(value) {
                      
                         var priorValue = get();
                         set(value);
                         this.updateObservers(priorValue, value);
                 }
               , updateObservers: function(priorValue, value) {

                                       var context = this;
                                       setTimeout( () => {
                                           context.observers.forEach((observer) => { observer.update.call(observer, priorValue, value); });

                                           var oneTimeObservers = context.oneTimeObservers;
                                           for ( var i = 0; i < oneTimeObservers.length; i++ ) {
                                               var observer = oneTimeObservers.pop();
                                               if ( !observer.update.call(observer, priorValue, value) ) {
                                                   context.oneTimeObservers.push(observer);
                                               }
                                           }
                                       }, 1);
                 }
               , observedBy: function(observer) {

                     this.observers.push(observer);
                     return true;
               }
               , oneTimeObservedBy: function(observer) {

                     this.oneTimeObservers.push(observer);
                     return true;
               }
               , withdrawObserver: function(observer) {
                     return withdraw(observer).from(this.observers);
               }
               , withdrawOneTimeObserver: function(observer) {
                   
                     return withdraw(observer).from(this.oneTimeObservers);
        }};
 })();};

var wobbler = function(orbitStrategies) {

    var orbit = orbitStrategies[0] || sameStepOrbitStrategy;

    var orbitDirectionOf = function(orbit, firstStep, secondStep) {
        var first = firstStep || orbit.startStep() || 0
          , second = secondStep || orbit.nextStep(first)
          , third = orbit.nextStep(second);

        return second == 0 && first > third || third == 0 && first < second || first < second && second < third ? "clock" : "counter clock";
    };

    return {
        activeOrbit: this.observableValue(orbit)
        , orbits: orbitStrategies
        , activeStep: this.observableValue()
        , orbitDirection: orbitDirectionOf(orbit, orbit.startStep() || 0)
        , orbitDirectionOf: orbitDirectionOf
        , orbitObservedBy: function(observer) {

            return this.activeOrbit.observedBy(observer);
        }
        , withdrawOrbitObserver: function(observer) {

            return this.activeOrbit.withdrawObserver(observer);
        }
        , stepOneTimeObservedBy: function(observer) {

            this.stepOneTimeObservers.push(observer);
        }
        , transitionOrbit: function() {

            return ( this.orbitDirection == "clock" ) ? clockOrbitStrategy : counterClockOrbitStrategy;
        }
        , orbitIs: function(orbit) {

            this.activeOrbit.is(orbit);
        }
        , nextStep: function(activeStep) {

            var orbit = this.activeOrbit.value();
            var nextStep = orbit.nextStep(activeStep);
            this.orbitDirection = this.orbitDirectionOf(orbit, activeStep, nextStep);
            this.activeStep.is(nextStep);

            return this.activeStep.value();
        }
        , wave: function(orbitOneTimeObserver) {

            var nextOrbit = this.nextOrbit()
              , orbit = this.activeOrbit.value()
              , activeWaver = this
              , oneTimeObserved = (orbit) => { if ( orbitOneTimeObserver ) { orbit.oneTimeObservedBy(orbitOneTimeObserver); } }
              , pendingOrbit = function(nextOrbit) { this.activeStep.oneTimeObservedBy({ step: nextOrbit.startStep()
                                                                                       , waver: activeWaver
                                                                                       , orbit: nextOrbit
                                                                                       , update: function(previousStep, step) {

                                                                                           if ( this.step != null && this.step != step ) { return false; }

                                                                                           this.waver.orbitIs(this.orbit);

                                                                                           oneTimeObserved(this.waver.activeOrbit);

                                                                                           return true;
                                                                                        } } ); };

            var nextOrbitDirection = this.orbitDirectionOf(nextOrbit);
            if ( nextOrbitDirection == this.orbitDirection ) {

                this.activeOrbit.is( this.transitionOrbit() );
                pendingOrbit.call(this, nextOrbit);

            } else {

                if ( orbit.endStep() || nextOrbit.startStep() ) {

                    this.activeStep.oneTimeObservedBy({ step: orbit.endStep()
                        , waver: activeWaver
                        , orbit: nextOrbit
                        , update: function(previousStep, step) {

                                      if ( this.step != null && this.step != step ) { return false; }

                                      var activeOrbit = this.waver.activeOrbit
                                        , startStep = this.orbit.startStep();
                                      if ( startStep && startStep != step ) {

                                          activeOrbit.is( this.waver.transitionOrbit() );
                                          pendingOrbit.call(this.waver, this.orbit);
                                      } else {
                                          activeOrbit.is( this.orbit );
                                          oneTimeObserved(activeOrbit);
                                      }

                                      return true;
                        } } );
                } else {

                    pendingOrbit.call(this, nextOrbit);
                }
            }
        }
        , nextOrbit: function() {

            var orbit = sameStepOrbitStrategy;
            var orbitCount = this.orbits.length;
            for ( var i = 0; i < orbitCount; i++ ) {
                if ( this.activeOrbit.value() == this.orbits[i] ) {
                    orbit = this.orbits[(i + 1) % orbitCount];
                    break;
                }
            }
            return orbit;
        }
    };
};

var rotor = function(area, numberOfSteps, waver, stepDurationsMillis) {

    var gear = { supply: function(handler) {
        
        var next = observableValue(0);
        var activeHandlerIndex = 0;
        var neutral = () => { return null; };
        var handlers = [neutral];
        var handle = { it: function(handlee) { if ( handlee ) { return handlers.push(handlee); } return false; } };
        (function(handlee) { handle.it(handlee); })(handler);
        
        return {
            handled: { by: function(handler) {

                return (handlers.length == 2) ? false : handle.it(handler) && true;
            } } 
            , activeHandler: function() {

                return handlers[activeHandlerIndex];
            }
            , couple: function(next, context, stepDurationMillis) {

                var handler = this.activeHandler()
                  , handlerContext = this;

                return handler.call(handlerContext, next, context, stepDurationMillis);
            }
            , shift: { it: function() { activeHandlerIndex = (activeHandlerIndex + 1) % handlers.length; }
                     , to: { neutral: function() { activeHandlerIndex = 0; } }
            }
            , isNeutral: function() { return activeHandlerIndex == 0; }
            , stepObservedBy: function(observer) { next.observedBy(observer); }
            , withdrawStepObserver: function(observer) { next.withdrawObserver(observer); }
            , stepValueIs: function(step) { next.is(step); }
            , stepValue: function() { return next.value(); }
        };
    } };

    var visualizer = function(numberOfValues, visual) {

        var valueShades = (function(numberOfShades) {

            var shades = []
            , shadeStep = Math.floor(50 / numberOfShades);

            for ( var i = 0; i < numberOfShades; i++ ) {

                shades.push("hsl(205, 20%, " + (50 + shadeStep * i) + "%)");
            }

            return shades;
        })(numberOfValues - 1).concat(["transparent"]);

        return {
            visual: visual
            , valueShades: valueShades
            , visual: visual
            , update: function(previousIndex, activeIndex) {

                this.visual.style.backgroundColor = this.valueShades[activeIndex];
            }
        };
    };
    
    var dot = (function() {

                   var dot = document.createElement("div");
                   dot.classList.add("dot");

                   return dot;
    })();

    var computeOrbitSteps = function(numberOfSteps) {

                                var steps = []
                                  , radians = ((360 / numberOfSteps) * 2 * Math.PI) / 360
                                  , cos = Math.cos(radians)
                                  , sin = Math.sin(radians)
                                  , radius = Math.round(area.offsetWidth / 2)
                                  , originX = radius
                                  , originY = radius
                                  , margin = radius - Math.round(radius / 2)
                                  , x1 = radius - margin
                                  , y1 = radius - margin;

                                for ( var i = 0; i < numberOfSteps; i++ ) {

                                    var x2 = (Math.round(x1 * cos - y1 * sin))
                                      , y2 = (Math.round(y1 * cos + x1 * sin))
                                      , x = originX + x2
                                      , y = originY + y2;

                                    steps.push([x, y]);

                                    x1 = x2;
                                    y1 = y2;
                                }

                                return steps;
    };
    var orbitSteps = computeOrbitSteps(numberOfSteps);

    var stepDurationsMillisArray = [stepDurationsMillis].reduce((accumulator, element) => accumulator.concat(element), []);

    var handler = function(nextStep, context, durationMillis) {

                       this.stepValueIs(nextStep);

                       return setTimeout( () => { context.step.call(context, nextStep); }
                                       , durationMillis );
    };
    var coupler = gear.supply(handler);

    var observableStepDurationIndex = this.observableValue();
    var visualHint = visualizer(stepDurationsMillisArray.length, area);
    observableStepDurationIndex.observedBy(visualHint);
    
    return {
        orbiter: dot
        , rotorArea: area
        , orbitSteps: orbitSteps
        , waver: waver
        , inMotion: this.observableValue()
        , timedStepsId: []
        , stepDurationsMillisArray: stepDurationsMillisArray
        , coupler: coupler
        , stepDurationIndex: observableStepDurationIndex
        , visualHint: visualHint
        , stepDurationIndexObservedBy: function(observer) {

            this.stepDurationIndex.observedBy(observer);
        }
        , stepObservedBy: function(observer) {

            this.coupler.stepObservedBy(observer);
        }
        , computeOrbitSteps: computeOrbitSteps
        , on: function() {

            this.stepDurationIndex.is(0);
            this.resume();
            this.rotorArea.appendChild(this.orbiter);
        }
        , orbitStepsAre: function(numberOfSteps) {

            this.off();
            while (!this.isPaused()) {
                this.pauseOrShift();
            }
            this.orbitSteps = this.computeOrbitSteps(numberOfSteps);
            this.coupler.stepValueIs(this.coupler.stepValue() % numberOfSteps);
            this.resume();
        }
        , off: function() {
            
            this.pause();
            for ( var i = 0; i < this.timedStepsId.length; i++ ) {
                clearTimeout(this.timedStepsId[i]);
            }
        }
        , resume: function() {

            this.coupler.shift.it();
            this.step(this.coupler.stepValue());
            this.inMotion.is(true);
        }
        , pause: function() {

            this.coupler.shift.to.neutral();
            this.inMotion.is(false);
        }
        , isPaused: function() {

            return this.coupler.isNeutral();
        }
        , pauseOrShift: function() {

            if ( this.isPaused() ) {
                this.resume();
                return;
            }

            var stepMillisLength = this.stepDurationsMillisArray.length;
            var durationIndex = 0;
            if ( this.stepDurationIndex.value() == stepMillisLength - 1 ) {
                this.pause();
            } else {
                durationIndex = (this.stepDurationIndex.value() + 1) % stepMillisLength;
            }
            this.stepDurationIndex.is(durationIndex);
        }
        , motionObservedBy: function(observer) {
            this.inMotion.observedBy(observer);
        }
        , withdrawMotionObserver: function(observer) {
            return this.inMotion.withdrawObserver(observer);
        }
        , step: function(step) {
            this.timedStepsId.pop();
            this.orbiter.style.left = this.orbitSteps[step][0];
            this.orbiter.style.top = this.orbitSteps[step][1];
            var context = this
              , nextStep = this.waver.nextStep(step)
              , timedStepId = this.coupler.couple(nextStep, context, this.stepDurationsMillisArray[this.stepDurationIndex.value()]);

            if ( timedStepId ) { this.timedStepsId.push(timedStepId); }
        }
    };
};

var rollOn = (function() {
    var loadedImagesCounter = 0;
    return () => {
        loadedImagesCounter++;
        if ( loadedImagesCounter < sequence.length ) { return; }

        var cssClasses = element.of(".roller").classList
          , cssClassName = sparker.waver.activeOrbit.value().cssClassName;

        if ( cssClassName.length > 0 ) { cssClasses.add(cssClassName); }
        cssClasses.remove("transparent");
        sparker.stepObservedBy( { update: function(previousStep, step) {

                                            removeCssClassName(roller, sequence);
                                            roller.className = joinCssClassNames(roller.className, sequence[step % sequence.length]); 
        } });
        setTimeout( () => {
            element.of("body").classList.remove("loading");
            sparker.orbitStepsAre(sequence.length);
        }, 8000 );
    };
})();
</script>
</head>
<body class="loading">
<div class="roller centered sized transparent">
    <div class="alternative-text">...</div>
    <div class="minute centered zero quarter"><div class="symbol">00</div><div class="stage"><div class="symbol">30</div></div></div>
    <div class="minute centered one"><div class="symbol">01</div><div class="stage"><div class="symbol">31</div></div></div>
    <div class="minute centered two"><div class="symbol">02</div><div class="stage"><div class="symbol">32</div></div></div>
    <div class="minute centered three"><div class="symbol">03</div><div class="stage"><div class="symbol">33</div></div></div>
    <div class="minute centered four"><div class="symbol">04</div><div class="stage"><div class="symbol">34</div></div></div>
    <div class="minute centered five fifth top"><div class="symbol">05</div><div class="stage"><div class="symbol">35</div></div></div>
    <div class="minute centered six"><div class="symbol">06</div><div class="stage"><div class="symbol">36</div></div></div>
    <div class="minute centered seven"><div class="symbol">07</div><div class="stage"><div class="symbol">37</div></div></div>
    <div class="minute centered eight"><div class="symbol">08</div><div class="stage"><div class="symbol">38</div></div></div>
    <div class="minute centered nine"><div class="symbol">09</div><div class="stage"><div class="symbol">39</div></div></div>
    <div class="minute centered ten fifth"><div class="symbol">10</div><div class="stage"><div class="symbol">40</div></div></div>
    <div class="minute centered eleven"><div class="symbol">11</div><div class="stage"><div class="symbol">41</div></div></div>
    <div class="minute centered twelve"><div class="symbol">12</div><div class="stage"><div class="symbol">42</div></div></div>
    <div class="minute centered thirteen"><div class="symbol">13</div><div class="stage"><div class="symbol">43</div></div></div>
    <div class="minute centered fourteen"><div class="symbol">14</div><div class="stage"><div class="symbol">44</div></div></div>
    <div class="minute centered fifteen quarter"><div class="symbol">15</div><div class="stage"><div class="symbol">45</div></div></div>
    <div class="minute centered sixteen"><div class="symbol">16</div><div class="stage"><div class="symbol">46</div></div></div>
    <div class="minute centered seventeen"><div class="symbol">17</div><div class="stage"><div class="symbol">47</div></div></div>
    <div class="minute centered eighteen"><div class="symbol">18</div><div class="stage"><div class="symbol">48</div></div></div>
    <div class="minute centered nineteen"><div class="symbol">19</div><div class="stage"><div class="symbol">49</div></div></div>
    <div class="minute centered twenty fifth"><div class="symbol">20</div><div class="stage"><div class="symbol">50</div></div></div>
    <div class="minute centered twentyone"><div class="symbol">21</div><div class="stage"><div class="symbol">51</div></div></div>
    <div class="minute centered twentytwo"><div class="symbol">22</div><div class="stage"><div class="symbol">52</div></div></div>
    <div class="minute centered twentythree"><div class="symbol">23</div><div class="stage"><div class="symbol">53</div></div></div>
    <div class="minute centered twentyfour"><div class="symbol">24</div><div class="stage"><div class="symbol">54</div></div></div>
    <div class="minute centered twentyfive fifth"><div class="symbol">25</div><div class="stage"><div class="symbol">55</div></div></div>
    <div class="minute centered twentysix"><div class="symbol">26</div><div class="stage"><div class="symbol">56</div></div></div>
    <div class="minute centered twentyseven"><div class="symbol">27</div><div class="stage"><div class="symbol">57</div></div></div>
    <div class="minute centered twentyeight"><div class="symbol">28</div><div class="stage"><div class="symbol">58</div></div></div>
    <div class="minute centered twentynine"><div class="symbol">29</div><div class="stage"><div class="symbol">59</div></div></div>
</div>
<div class="bottom-lane" onmousedown="scheduleRevealContentSelector(event)" onmouseup="cancelRevealContentSelector(event)">
        <div class="art-picker" onclick="blur()">
            <div class="cluster cluster-art" onclick="playArt()">
            <div class="alternative-text">.&nbsp;&nbsp;&nbsp;&nbsp;.&nbsp;&nbsp;&nbsp;&nbsp;.&nbsp;&nbsp;&nbsp;&nbsp;.</div>            
            <div class="key-frame"><div class="key-wrap centered">
                <div class="key">1</div><div class="key">2</div><div class="key">3</div><div class="key">4</div><div class="key">5</div>
                <div class="key">6</div><div class="key">7</div><div class="key">8</div><div class="key">9</div><div class="key">0</div>
            </div></div>
        </div>
        <div class="cluster cluster-hover" onclick="playHover()">
            <div class="alternative-text">. . . . . . . .</div>
            <div class="key-frame"><div class="key-wrap centered"><div class="key"></div></div></div>
        </div>
    </div>
    <div class="whirl centered" onclick="rollAnimation(event)"></div>
</div>
</body>
<script type="text/javascript">
var emptyFunction = () => { return null; }
  , sameStepOrbitStrategy = { type: "same step"
                          , cssClassName: ""
                          , startStep: emptyFunction
                          , endStep: emptyFunction
                          , nextStep: function(step) { return step; }
  }
  , clockOrbitStrategy = { type: "clock"
                       , cssClassName: "clock"
                       , startStep: emptyFunction
                       , endStep: emptyFunction
                       , nextStep: function(step) { return (step + 1) % sequence.length; }
  }
  , counterClockOrbitStrategy = { type: "counter clock"
                              , cssClassName: "counter-clock"
                              , startStep: emptyFunction
                              , endStep: emptyFunction
                              , nextStep: function(step) {

                                  if ( step == 0 ) { return sequence.length - 1; } else { return step - 1; }

  } }
  , alternateOrbitStrategy = { type: "alternate"
                           , cssClassName: "alternate"
                           , startStep: () => { return 1; }
                           , endStep: () => { return 7 % sequence.length; }
                           , stepFunction: (step) => { return step + 1; }
                           , nextStep: function(step) {

                               var adaptedStep = step % sequence.length;

                               if ( adaptedStep == this.endStep() ) { this.stepFunction = (step) => { return step - 1; }; }
                               if ( adaptedStep == this.startStep() ) { this.stepFunction = (step) => { return step + 1; }; }

                               return this.stepFunction(adaptedStep);
  } }
  , orbitStrategies = [clockOrbitStrategy, counterClockOrbitStrategy, alternateOrbitStrategy]
  , roller = element.of(".roller")
  , waver = wobbler(orbitStrategies);

waver.orbitObservedBy( { roller: roller
                       , update: function(previousOrbit, orbit) {

                                     var cssToRemove = previousOrbit.cssClassName
                                       , cssToAdd = orbit.cssClassName
                                       , className = " " + this.roller.className + " ";

                                     roller.className = className.replace(" " + cssToRemove + " ", " " + cssToAdd + " ").trim();
                                 } });

var sparker = this.rotor(element.of(".whirl"), sequence.length, waver, [1000, 500, 250]);

(function() {
    sparker.stepDurationIndexObservedBy( { speedMarkerCssClasses: ["slow", "moderate", "fast"]
                                       , update: function(activeIndex, index) {

                                             removeCssClassName(roller, this.speedMarkerCssClasses);
                                             roller.classList.add(this.speedMarkerCssClasses[index % this.speedMarkerCssClasses.length]);
                                       } });
    sparker.motionObservedBy( { update: function(previous, active) {
        if ( active ) {
            removeCssClassName(roller, ["paused"]);
        } else {
            roller.classList.add("paused");            
        }
    }});
    sparker.on();
})();
(function() {
    
    var delay = 1;
    for ( var i = 0; i < hoverSequence.length; i++ ) {
        setTimeout(() => { rollOn(); }, delay * i);
    }
})();
</script>
</html>
\$\endgroup\$
4
  • 3
    \$\begingroup\$ Tell us about the Use Case, please. Why would an engineer choose to use this library code, what would they see, how would they know it is working? When you Edit the question you chose not to click on the <> icon for a JS Snippet, so we cannot see it in action on this site, and you offered no links to a demo site. \$\endgroup\$
    – J_H
    Commented Jun 30 at 16:53
  • \$\begingroup\$ I'd autoformat this with Prettier so it's readable to other JS developers. \$\endgroup\$
    – ggorlen
    Commented Jul 1 at 1:08
  • \$\begingroup\$ Because of the unconventional coding style I find this code hard to parse. Why don't you use class notation with methods, instead of chaining functions in an object separated by a ,? Another potential issue is that functionality and graphic representation are mixed, so now I can't use the rotor to drive any different kind of animation than the one you hardcoded. \$\endgroup\$
    – Kokodoko
    Commented Jul 1 at 10:17
  • 1
    \$\begingroup\$ Please do not edit the question, especially the code, after an answer has been posted. Changing the question may cause answer invalidation. Everyone needs to be able to see what the reviewer was referring to. What to do after the question has been answered. \$\endgroup\$
    – pacmaninbw
    Commented Jul 1 at 11:49

1 Answer 1

6
\$\begingroup\$

Just some quick points:

You really should format the code according to common practices. Your unusual formatting makes the code very difficult to read, which will limit the number of people willing to review this.

Modern JavaScript shouldn't be using var.

Most inline functions could be written either as arrow functions or using the enhanced object syntax, which will also make the code more readable.

\$\endgroup\$
4
  • 1
    \$\begingroup\$ To expand on that middle item: Prefer let because it is block scoped, in contrast to var which is function scoped. \$\endgroup\$
    – J_H
    Commented Jun 30 at 19:01
  • 3
    \$\begingroup\$ And prefer const over let. \$\endgroup\$
    – RoToRa
    Commented Jun 30 at 19:03
  • 1
    \$\begingroup\$ I agree here with @J_H that you should phrase it as prefer instead of should'nt. There are multiple different reasons why you might want to use var instead of let. It has nothing to do with "modern JS" but that nowadays most new developers do not understand the differences. \$\endgroup\$
    – tacoshy
    Commented Jul 1 at 8:36
  • \$\begingroup\$ Do block scoped variables reduce code defect rates compared to function scoped variables. I have yet to see even the slightest evidence that block scope improves code quality. \$\endgroup\$
    – Blindman67
    Commented Jul 2 at 20:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.