PageSourceSearch

https://codelamp.co.uk/js/makeshift.parallax.js?build=BUILDTOKEN

js codelamp.co.uk collected 2026-10-04 13:34:34 UTC 62,399 bytes, 2,030 lines download raw bytes

1var makeshift = makeshift || {};
2makeshift.getParallaxBuilder = function($, global){
3
4  // some hasty fixes for ff
5  var ua = {};
6  ua.firefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
7  //ua.firefox = false;
8
9  var log = function(msg){
10    $('#log').html(msg);
11  };
12
13  /**
14   * The handler behind the parallax handlings
15   *
16   * @memberof! makeshift
17   * @namespace makeshift.parallax
18   */
19  var parallax = {
20
21    /**
22     * Create an instance of `makeshift.parallax`
23     *
24     * @memberof! makeshift.parallax
25     */
26    create: function(parallaxData, site){
27      return this.prep.apply(Object.create(this), arguments);
28    },
29
30    /**
31     * Internal preping of a `makeshift.parallax` instance
32     *
33     * @memberof! makeshift
34     * @private
35     */
36    prep: function(parallaxData, site){
37      this.site = site;
38      this.data = parallaxData;
39      this.location = {
40        x: this.point.create(0, undefined, 0),
41        y: this.point.create(0),
42        z: this.point.create(0)
43      };
44      this.shifting = null;
45      this.workings = {};
46      this.ui = {
47        container: jQuery('<div class="parallax use-cssTransitions" />'),
48        nav: jQuery('' +
49          '<a href="#pm700" class="pagemark-previous">' +
50            '<i class="fa fa-chevron-left"></i>' +
51            '<i class="fa fa-chevron-left"></i>' +
52          '</a>' +
53          '<a href="#pm100" class="pagemark-next">' +
54            '<i class="fa fa-chevron-right"></i>' +
55            '<i class="fa fa-chevron-right"></i>' +
56          '</a>' +
57        '')
58      };
59      // prevent unwanted scrolling
60      this.ui.container.add('body').add(document.documentElement).scroll(function(e){
61        e.target.scrollLeft = e.target.scrollTop = 0;
62      });
63      this.layers = this.layers.create({
64        ui: this.ui
65      });
66      this.tick = this.tick.bind(this); /// @TODO: polyfill bind
67      // if we are in justTouch mode i.e. mobile, enable JS transitions always
68      this.justTouch = 'ontouchstart' in window || navigator.msMaxTouchPoints;
69      this.promise = this.loadResources()
70        .then(this.bind(this.processResources))
71        .then(this.bind(this.maximizeLayers))
72        .then(this.bind(this.createPagemarks))
73        .then(this.bind(this.setupEvents))
74        .then(this.bind(this.setupTouchDrag))
75      ;
76      return this;
77    },
78
79    /**
80     * Turn on CSS Transitions (on by default)
81     *
82     * @memberof! makeshift.parallax
83     */
84    useCSSTransitions: function(){
85      this.ui.container
86        .removeClass('use-cssTransitions use-jsTransitions')
87        .addClass('use-cssTransitions')
88      ;
89      return this;
90    },
91
92    /**
93     * Turn on JS Transitions (currently disabled)
94     *
95     * @memberof! makeshift.parallax
96     */
97    useJSTransitions: function(){
98      this.ui.container
99        .removeClass('use-cssTransitions use-jsTransitions')
100        .addClass('use-jsTransitions')
101      ;
102      return this;
103    },
104
105    /**
106     * Step each layer, and identify the content based on page name
107     * at this point the page content should have been already loaded
108     * into contentData, keyed by name.
109     *
110     * @memberof! makeshift.parallax
111     */
112    assignContent: function(contentData){
113      if ( !contentData ) {
114        throw new Error('No content data supplied');
115      }
116      this.layers.pm = this.pagemarker.create();
117      this.layers.each(this.assignContentPerLayer(contentData), this);
118    },
119
120    /**
121     * Find + add the content on a per layer basis
122     *
123     * @memberof! makeshift.parallax
124     */
125    assignContentPerLayer: function(contentData){
126      return function(n, layer){
127        var i, l, pages, page, pageData, pm = this.layers.pm, pmBase;
128        if ( !layer || !layer.config.content ) { return; }
129        if ( !(pages=layer.config.content.pages) ) { return; }
130        if ( !(pmBase=layer.config.content['pagemark-base']) ) {
131          throw new Error('please define a pagemark-base');
132        }
133        for ( i=0, l=pages.length; i<l; i++ ) {
134          if ( !(page=pages[i]) ) { continue; }
135          if ( !(pageData=contentData[page]) ) { continue; }
136          pm.setFromValue('pm' + i + pmBase, true);
137          this.assignContentItem({
138            x: i,
139            page: page,
140            pageData: pageData,
141            pm: pm,
142            layer: layer
143          });
144        }
145      };
146    },
147
148    /**
149     * For each item of content, we do a bit of processing. This involves
150     * assigning markup, styles and scripts that are associated to the content
151     *
152     * @memberof! makeshift.parallax
153     */
154    assignContentItem: function(content){
155      var pm = content.pm;
156      content.display = content.pageData.displays['default'];
157      // calculate the surrounding pagemarks @TODO this could be improved in the next system
158      content.marks = {
159        pmc: pm.get(),
160        pma: pm.getAbove(),
161        pmb: pm.getBelow(),
162        pmr: pm.getRight(),
163        pml: pm.getLeft(),
164        pmi: pm.getIn(),
165        pmo: pm.getOut()
166      };
167      // assign the triforce
168      this.assignContentMarkup(content);
169      this.assignContentStyles(content);
170      this.assignContentScript(content);
171    },
172
173    /**
174     * For each content found, add the markup
175     *
176     * @memberof! makeshift.parallax
177     */
178    assignContentMarkup: function(content){
179      content.layer.ui.inner.append(
180        '<div class="pagemark pagemark-' + content.x + ' '+ content.marks.pmc + '">' +
181          content.display.getMarkup() +
182        '</div>'
183      );
184    },
185
186    /**
187     * For each content found, add the scripts
188     *
189     * @TODO: support for other event types, but that will probably
190     * only occur after a full rewrite of the system ;)
191     * @TODO: refactor the access to the global site var
192     *
193     * @memberof! makeshift.parallax
194     */
195    assignContentScript: function(content){
196      var self = this;
197      var scripts = content.display.getScript();
198      if ( scripts['pocket/focus'] ) {
199        this.assignContentScriptEvent(content, scripts['pocket/focus'], function(content){
200          return self.pagemark.is(content.marks.pmc);
201        });
202      }
203      if ( scripts['pocket/blur'] ) {
204        this.assignContentScriptEvent(content, scripts['pocket/blur'], function(content){
205          return self.pagemark.getLast() == content.marks.pmc;
206        });
207      }
208    },
209
210    /**
211     *
212     */
213    assignContentScriptEvent: function(content, script, validation){
214      var site = this.site;
215      var event = {
216        method: function(){
217          // if we aren't the content's pagemark, do nothing
218          if ( !validation(content) ) {
219            return;
220          }
221          raw();
222        },
223        off: function(){
224          site.off('pagemarkchange', event.method);
225        },
226        on: function(){
227          site.on('pagemarkchange', event.method);
228        }
229      };
230      var raw; eval('raw = (function(){' +
231        script.content +
232      '});');
233      event.on();
234    },
235
236    /**
237     * For each content found, add the styles
238     *
239     * @memberof! makeshift.parallax
240     */
241    assignContentStyles: function(content){
242      var marks = content.marks;
243      // @TODO: this needs to be refactored, perhaps introduce twig as templating for the CSS.
244      styles = content.display.getStyle(marks.pmc, {
245        local: '.' + marks.pmc,
246        above: '.' + marks.pma,
247        below: '.' + marks.pmb,
248        right: '.' + marks.pmr,
249        leftt: '.' + marks.pml,
250        "in": '.' + marks.pmi,
251        "out": '.' + marks.pmo,
252        // There are two target systems, one using :target and one using :checked
253        target_right: '.parallax-state:checked ~ #pagemarks #' + marks.pmr + ':target ~ .parallax',
254        target_left: '.parallax-state:checked ~ #pagemarks #' + marks.pml + ':target ~ .parallax',
255        target_below: '.parallax-state:checked ~ #pagemarks #' + marks.pmb + ':target ~ .parallax',
256        target_above: '.parallax-state:checked ~ #pagemarks #' + marks.pma + ':target ~ .parallax',
257        target_in: '.parallax-state:checked ~ #pagemarks #' + marks.pmi + ':target ~ .parallax',
258        target_out: '.parallax-state:checked ~ #pagemarks #' + marks.pmo + ':target ~ .parallax',
259        target: '.parallax-state:checked ~ #pagemarks #' + marks.pmc + ':target ~ .parallax',
260        target_right_radio: '.parallax-state:checked ~ #pagemarks .rnav.' + marks.pmr + ':checked ~ .parallax',
261        target_left_radio: '.parallax-state:checked ~ #pagemarks .rnav.' + marks.pml + ':checked ~ .parallax',
262        target_below_radio: '.parallax-state:checked ~ #pagemarks .rnav.' + marks.pmb + ':checked ~ .parallax',
263        target_above_radio: '.parallax-state:checked ~ #pagemarks .rnav.' + marks.pma + ':checked ~ .parallax',
264        target_in_radio: '.parallax-state:checked ~ #pagemarks .rnav.' + marks.pmi + ':checked ~ .parallax',
265        target_out_radio: '.parallax-state:checked ~ #pagemarks .rnav.' + marks.pmo + ':checked ~ .parallax',
266        target_radio: '.parallax-state:checked ~ #pagemarks .rnav.' + marks.pmc + ':checked ~ .parallax',
267        // breakpoints
268        breakpoints_mobile: '@media screen and (max-width: 700px)',
269        breakpoints_mobile_vert: '@media screen and (max-width: 700px) and (orientation: portrait)',
270        breakpoints_tablet: '@media screen and (min-width: 701px) and (max-width: 1000px)'
271      });
272      styles && content.layer.ui.inner.append(styles);
273    },
274
275    /**
276     *
277     */
278    maximizeLayers: function(){
279      var layers = this.layers.getAll(), self = this;
280      this.workings.layerHeights = jQuery.map(layers, function(layer, i){
281        return layer.ui.image ? Number(layer.ui.image[0].height) : 0;
282      });
283      this.workings.layerMaxHeight = Math.max.apply(Math, this.workings.layerHeights);
284      //jQuery.each(layers, function(i, layer){
285        //layer.ui.outer.css('height', self.workings.layerMaxHeight + 'px');
286      //});
287      return this;
288    },
289
290    /**
291     * Pagemarks are navigable targets, targetted by a `#fragment`.
292     *
293     * Pagemarks are used to divide up each content parallax layer, at predictable offsets.
294     * This allows the user to navigate step by step horizontally across the parallax effect.
295     *
296     * @memberof! makeshift.parallax
297     */
298    createPagemarks: function(){
299      console.log('createPagemarks');
300      var d = jQuery.Deferred();
301      this.pagemarks = this.pagemarks.create(this);
302      this.pagemark = this.pagemarker.create(this);
303      this.pagemark.setFromFragment();
304      return d.resolve(this).promise();
305    },
306
307    /**
308     * A PageMarker is just an object that represents the decoded `#fragment` in terms
309     * of x, y, z coordinates.
310     *
311     * It also exposes methods to navigate these axis.
312     *
313     * @memberof! makeshift.parallax
314     * @namespace makeshift.parallax.pagemarker
315     */
316    pagemarker: {
317
318      /**
319       * Create an instance of `makeshift.parallax.pagemarker
320       *`
321       * @memberof! makeshift.parallax.pagemarker
322       */
323      create: function(){
324        return this.prep.apply(Object.create(this), arguments);
325      },
326
327      /**
328       * Internal prep of a newly created instance
329       *`
330       * @memberof! makeshift.parallax.pagemarker
331       * @private
332       */
333      prep: function(parallax){
334        var self = this;
335        this.history = [];
336        this.hash = ['pm', 0, 0, 0];
337        if ( parallax ) {
338          this.parallax = parallax;
339          this.win = jQuery(window);
340          this.win.on('hashchange', this.bind(this.hashchange));
341        }
342        this.handlers = jQuery({});
343        return this;
344      },
345
346      /**
347       * Clone an instance of pagemarker into a brand new object
348       *`
349       * @memberof! makeshift.parallax.pagemarker
350       */
351      doppel: function(){
352        return this.create().set(this.x(), this.y(), this.z(), true);
353      },
354
355      /**
356       * Bind a method to the current context of the `.bind()` method
357       * which if called `makeshift.parallax.pagemarker.bind()` will be
358       * `makeshift.parallax.pagemarker`
359       *`
360       * @memberof! makeshift.parallax.pagemarker
361       */
362      bind: function(method){
363        return method.bind(this);
364      },
365
366      on: function(){
367        this.handlers.on.apply(this.handlers, arguments);
368        return this;
369      },
370
371      trigger: function(){
372        this.handlers.trigger.apply(this.handlers, arguments);
373        return this;
374      },
375
376      is: function(test){
377        return this.get() === test;
378      },
379
380      getFromFragment: function(){
381        return new String(window.location.hash).replace('#', '');
382      },
383
384      getLast: function(){
385        return this.history.length > 1 ? this.history[this.history.length - 2] : null;
386      },
387
388      get: function(){
389        return this.hash.join('');
390      },
391
392      getLeft: function(){
393        return this.doppel().x('-1').get();
394      },
395
396      getRight: function(){
397        return this.doppel().x('+1').get();
398      },
399
400      getBelow: function(){
401        return this.doppel().y('+1').get();
402      },
403
404      getAbove: function(){
405        return this.doppel().y('-1').get();
406      },
407
408      getIn: function(){
409        return this.doppel().z('-1').get();
410      },
411
412      getOut: function(){
413        return this.doppel().z('1').get();
414      },
415
416      set: function(x, y, z, withoutUpdate){
417        this.x(x);
418        this.y(y);
419        this.z(z);
420        this.trigger('change', this.get());
421        !withoutUpdate ? this.update() : this.withoutUpdate();
422        return this;
423      },
424
425      setFromValue: function(pm, withoutUpdate){
426        if ( pm[0] + pm[1] == 'pm' ) {
427          !withoutUpdate && console.log('setting pagemark to', pm);
428          this.set(pm[2], pm[3], pm[4], withoutUpdate);
429        }
430        return this;
431      },
432
433      setFromFragment: function(withoutUpdate){
434        this.setFromValue(this.getFromFragment(), withoutUpdate);
435        return this;
436      },
437
438      str: function(a, b){
439        if ( b.charAt(0) == '+' ) {
440          a += parseInt(b, 10);
441        }
442        else if ( b.charAt(0) == '-' ) {
443          a += parseInt(b, 10);
444        }
445        else {
446          a = parseInt(b, 10);
447        }
448        return a;
449      },
450
451      x: function(v){
452        if ( arguments.length ) {
453          if ( v.charAt ) { v = this.str(this.x(), v); }
454          this.hash[1] = this.limit(v, 0, 7, false);
455          return this;
456        }
457        else {
458          return this.hash[1];
459        }
460      },
461
462      y: function(v){
463        if ( arguments.length ) {
464          if ( v.charAt ) { v = this.str(this.y(), v); }
465          this.hash[2] = this.limit(v, 0, 1, true);
466          return this;
467        }
468        else {
469          return this.hash[2];
470        }
471      },
472
473      z: function(v){
474        if ( arguments.length ) {
475          if ( v.charAt ) { v = this.str(this.z(), v); }
476          this.hash[3] = this.limit(v, 0, 2, false);
477          return this;
478        }
479        else {
480          return this.hash[3];
481        }
482      },
483
484      limit: function(v, mi, ma, wrap){
485        if ( v < mi ) v = wrap ? ma : mi;
486        if ( v > ma ) v = wrap ? mi : ma;
487        return v;
488      },
489
490      update: function(){
491        var v = this.get();
492        this.updateHistory(v);
493        this.parallax && this.parallax.jumpTo(v);
494        return this;
495      },
496
497      withoutUpdate: function(){
498        this.updateHistory(this.get());
499      },
500
501      updateHistory: function(_v){
502        var v = _v || this.get(), l = this.history.length;
503        if ( this.history[l-1] ) {
504          if ( this.history[l-1] != v ) {
505            this.history.push(v);
506          }
507        }
508        else {
509          this.history.push(v);
510        }
511      },
512
513      force: function(pm){
514        var current = pm || this.get();
515        this.beingForced = true;
516        window.location.hash = '';
517        // update the internal tracking inside pagemark, but don't react
518        this.setFromValue(current, true);
519        // then trigger the hash change through the parallxx handler
520        this.parallax && this.parallax.jumpTo(current);
521      },
522
523      /**
524       * Hashchange can trigger in a number of different ways
525       * - user types manually
526       * - user clicks a link
527       * - js calls this.parallax.jumpTo()
528       * - history state
529       */
530      hashchange: function(e){
531        if ( this.beingForced ) {
532          this.beingForced = false;
533          return;
534        }
535        // update the recording of the pagemark without triggering anything
536        this.setFromFragment(true);
537        // @BADHACK: this file shouldn't reference site
538        this.parallax.site.trigger('pagemarkchange', {});
539      }
540
541    },
542
543    tabfocusHandler: {
544
545      create: function(){
546        return this.prep.apply(Object.create(this), arguments);
547      },
548
549      prep: function(parallax){
550        this.parallax = parallax;
551        this.doc = jQuery(document);
552        this.doc.on('focusin', this.bind(this.focusIn));
553        this.doc.on('focusout', this.bind(this.focusOut));
554        this.doc.on('mousedown touchstart', this.bind(this.nonTabInput));
555        this.reactTabFocus = this.bind(this.reactTabFocus);
556        this.clearTabFocus = this.bind(this.clearTabFocus);
557        return this;
558      },
559
560      bind: function(method){
561        return method.bind(this);
562      },
563
564      /**
565       * Whenever an element is focused, mark it as being tab focused
566       * however, focus events are triggered by all types of different
567       * interactions, so we have to filter out the ones we don't want.
568       *
569       * notes:
570       * focusin can trigger several times before the delayed reactTabFocus
571       * is triggered.
572       */
573      focusIn: function(e){
574        var elm = jQuery(e.target);
575        if ( this.actualTarget && this.actualTarget.is(e.target) ) return;
576        // if the nonTabInput has triggered on a child of the focusable element, the nonTabInput
577        // will trigger before the focusin event. So here we try and detect that.
578        if ( this.nonTabTarget && jQuery.contains(elm.get(0), this.nonTabTarget.get(0)) ) {
579          console.log('non tab input before focus', e.target);
580          elm.each(this.clearTabFocus);
581        }
582        else {
583          console.log('focus in for', e.target);
584          jQuery('.tab-focused').each(this.clearTabFocus);
585          this.lastTarget = elm.addClass('tab-focused');
586          this.delayed = setTimeout(this.reactTabFocus, 100);
587        }
588      },
589
590      /**
591       * focusout can be triggered on elements that aren't the current
592       * focus, so we need to handle removing anything set on those
593       * tabbed away elements.
594       */
595      focusOut: function(e){
596        jQuery(e.target).each(this.clearTabFocus);
597      },
598
599      /**
600       * If we get non-tab input on any element or sub-element of a tab-focused
601       * group, then cancel the tab focus reaction.
602       */
603      nonTabInput: function(e){
604        console.log('non tab input for', e.target);
605        var elm = jQuery(e.target), target = elm;
606        this.nonTabTarget = target;
607        if ( elm.is('.tab-focused') || (target=elm.closest('.tab-focused')) ) {
608          target.each(this.clearTabFocus);
609        }
610      },
611
612      /**
613       * Responsible for clearing any left behind tab-focus handling and
614       * cancelling the tab-focus reaction when it isn't needed.
615       */
616      clearTabFocus: function(i, elm){
617        console.log('clearing tab-focus for', elm);
618        jQuery(elm).removeClass('tab-focused');
619        if ( this.lastTarget && (this.lastTarget.get(0) === elm) ) {
620          this.delayed && clearTimeout(this.delayed);
621          this.delayed = null;
622          this.lastTarget = null;
623        }
624      },
625
626      reactTabFocus: function(){
627        if ( !this.lastTarget ) return;
628        if ( this.actualTarget && this.lastTarget.is(this.actualTarget.get(0)) ) return;
629        console.log('reacting to focus');
630        var fre = new RegExp('pm([0-9])([0-9])([0-9])', 'gi');
631        this.actualTarget = this.lastTarget;
632        if ( this.actualTarget ) {
633          var pm = this.actualTarget.closest('.pagemark'), m, sp;
634          if ( pm.length ) {
635            fre.lastIndex = 0;
636            m = fre.exec(pm.attr('class'));
637            if ( m && m[0] && m[0] !== this.parallax.pagemark.last ) {
638              this.parallax.pagemark.setFromValue(m[0]);
639              // @TODO: need to investigate why a refocus is required
640              // there must be something occuring that is wiping focus
641              // perhaps the hash change?
642              this.actualTarget.focus();
643            }
644          }
645        }
646      }
647
648    },
649
650    /**
651     *
652     */
653    setupTouchDrag: function(){
654      console.log('setupTouchDrag');
655      this.touchDrag = this.touchDragController.create(this, {
656        type: this.justTouch ? 'swipe' : 'pan'
657      });
658      return jQuery.Deferred().resolve(this).promise();
659    },
660
661    /**
662     *
663     */
664    setupEvents: function(){
665      console.log('setupEvents');
666      var d = jQuery.Deferred(), container = this.ui.container, self = this, win = jQuery(window), doc = jQuery(document);
667      var fre = new RegExp('pm([0-9])([0-9])([0-9])', 'gi');
668
669      this.tabfocus = this.tabfocusHandler.create(this);
670
671      /*
672      doc.on('focusin', function(e){
673        jQuery(e.target).addClass('tab-focused');
674        if ( self.tabFocused === e.target ) {
675          return;
676        }
677        self.tabFocused = e.target;
678        var pm = $(e.target).closest('.pagemark'), m, se;
679        jQuery('.tab-focused').removeClass('tab-focused');
680        jQuery(e.target).addClass('tab-focused');
681        console.log('focused in', e.target);
682        log('focused in ' + jQuery(e.target).elementPath());
683        if ( pm.length ) {
684          fre.lastIndex = 0;
685          m = fre.exec(pm.attr('class'));
686          if ( m && m[0] && m[0] !== self.pagemark.last ) {
687            self.pagemark.setFromValue(m[0]);
688            se = self.getScrollParent(e.target);
689            if ( se ) {
690              se.scrollTop = 0;
691              se.scrollLeft = 0;
692            }
693            //e.target.focus && e.target.focus();
694          }
695        }
696      });
697
698      doc.on('focusout', function(e){
699        log('focused out ' + jQuery(e.target).elementPath());
700        jQuery(e.target).removeClass('tab-focused');
701      });
702      */
703
704      /*
705      win
706        //
707        .on('hashchange', function(e){
708          container.find('.at-rest').removeClass('at-rest');
709        })
710        // because transition start doesn't exist, why??!
711        .on('transitionstart', function(e){
712          jQuery(e.target).removeClass('at-rest');
713        })
714        //
715        .on('transitionend', function(e){
716          jQuery(e.target).addClass('at-rest');
717        })
718      ;
719      */
720      return d.resolve(this).promise();
721    },
722
723    /**
724     *
725     */
726    pagemarks: {
727
728      /**
729       * Create our pagemarks
730       *
731       *   Put simply this means generate a list of element wrappers and associated
732       *   CSS rules, that allow a change in parallax based on a specific url hash/fragment
733       */
734      create: function(parallax){
735        return this.prep.apply(Object.create(this), arguments);
736      },
737
738      /**
739       *
740       */
741      prep: function(parallax){
742        this.data = parallax.data.pagemarks;
743        this.parallax = parallax;
744        this.states = this.data.states;
745        this.mode = 'flat';
746        this.ui = parallax.ui;
747        this.ui.pagemarks = {
748          targets: jQuery('#pagemarks'),
749          styles: null
750        };
751        this.build();
752        return this;
753      },
754
755      styleListGroup: {
756
757        create: function(){
758          return this.prep.apply(Object.create(this), arguments);
759        },
760
761        prep: function(){
762          this.items = arguments;
763          return this;
764        },
765
766        add: function(data){
767          this.each(function(item, i){
768            item.add(data);
769          });
770          return this;
771        },
772
773        set: function(name, value){
774          this.each(function(item, i){
775            item.set(name, value);
776          });
777          return this;
778        },
779
780        /**
781         *
782         */
783        n: function(n){
784          return this.items[n];
785        },
786
787        render: function(){
788          var ret = [];
789          this.each(function(item, i){
790            ret.push(item.render());
791          });
792          return ret.join("");
793        },
794
795        each: function(m, context){
796          for ( var i=0, l=this.items.length; i<l; i++ ) {
797            m.call(context || this.items[i], this.items[i], i);
798          }
799          return this;
800        }
801
802      },
803
804      styleList: {
805
806        create: function(){
807          return this.prep.apply(Object.create(this), arguments);
808        },
809
810        /**
811         *
812         */
813        prep: function(){
814          this.re = new RegExp('\\$([a-z]+) ?', 'gi');
815          this.i = {};
816          this.i.values = {};
817          this.i.list = [];
818          return this;
819        },
820
821        /**
822         *
823         */
824        instance: function(){
825          var inst = this.create();
826          inst.i = Object.create(this.i);
827          inst.i.values = Object.create(this.i.values);
828          inst.i.list = this.i.list.slice(0);
829          inst.i.template = this.i.template;
830          inst.i.varmap = this.i.varmap;
831          return inst;
832        },
833
834        /**
835         *
836         */
837        set: function(name, value){
838          this.i.values[name] = value;
839          return this;
840        },
841
842        /**
843         *
844         */
845        get: function(name){
846          return this.i.values[name];
847        },
848
849        /**
850         *
851         */
852        n: function(){
853          return this;
854        },
855
856        /**
857         *
858         */
859        add: function(data){
860          var str = this.i.template;
861          for ( var k in this.i.varmap ) {
862            if ( data[k] !== undefined ) {
863              str = str.replace('$' + k, data[k]);
864            }
865            else if ( this.i.values[k] !== undefined ) {
866              str = str.replace('$' + k, this.i.values[k]);
867            }
868          }
869          this.i.list.push(str);
870          return this;
871        },
872
873        /**
874         *
875         */
876        template: function(template){
877          var m, i = 0;
878          if ( arguments.length ) {
879            this.i.template = template;
880            this.i.varmap = {};
881            this.re.lastIndex = 0;
882            while ( (m=this.re.exec(template)) ) {
883              this.i.varmap[m[1]] = i++;
884            }
885            return this;
886          }
887          else {
888            return this.i.template;
889          }
890        },
891
892        /**
893         *
894         */
895        renderer: function(){
896          if ( arguments.length ) {
897            this.i.renderer = arguments[0];
898            return this;
899          }
900          else {
901            return this.i.renderer;
902          }
903        },
904
905        /**
906         *
907         */
908        render: function(){
909          if ( this.i.renderer ) {
910            return this.i.renderer.apply(this, arguments);
911          }
912          else {
913            return jQuery.trim(this.i.list.join("\r\n")) + "\r\n";
914          }
915        }
916
917      },
918
919      splitPID: function(pid){
920        return 'x' + pid.charAt(0)+' y' + pid.charAt(1) + ' z' + pid.charAt(2);
921      },
922
923      addNav: function(workspace, pagemark, type, pid, icon, isDefault){
924        workspace.navs.push(
925          '<a href="#pm' + pagemark.nav[type] + '" class="pagemark-' + type + ' pm-nav with-pm' + pid + (
926              isDefault ? ' pm-default' : ''
927            ) + '">' +
928            '<i class="fa fa-chevron-' + icon + '"></i>' +
929            '<i class="fa fa-chevron-' + icon + '"></i>' +
930          '</a>'
931        );
932      },
933
934      owns: function(obj, key){
935        return !Object.hasOwnProperty || Object.hasOwnProperty.call(obj, key);
936      },
937
938      /**
939       *
940       */
941      build: function(){
942        var workspace = {
943          navs: [],
944          radios: {
945            items: {},
946            list: []
947          },
948          states: [],
949          head: this.ui.pagemarks.targets,
950          layers: this.parallax.layers.getAll(),
951          hzf: 0 ? ' height: 0 !important;' : '',
952          isDefault: false,
953          styles: {
954            withs: {},
955            layers: {}
956          }
957        };
958        workspace.base = this.styleList.create()
959          .template('$state $before $target $after $rules')
960          .set('state', 'state-default')
961          .set('before', '')
962          .set('after', '')
963          .renderer(function(){
964            return jQuery.trim(this.i.list.join("\r\n").replace(/ +/gi, ' ')) + "\r\n";
965          })
966        ;
967        // this stores the styles on a per layer basis
968        workspace.styles.layers.initial = this.styleListGroup.create(
969          workspace.base.instance().set('target', '')
970        );
971        // this stores the styles for the items that should appear with the targeted pagemark
972        workspace.styles.withs.initial = this.styleListGroup.create(
973          workspace.base.instance().set('target', '')
974        );
975        workspace.styles.layers.targeted = this.styleListGroup.create(
976          workspace.base.instance().set('target', ''),
977          workspace.base.instance().set('target', '')
978        );
979        workspace.styles.withs.targeted = this.styleListGroup.create(
980          workspace.base.instance().set('target', ''),
981          workspace.base.instance().set('target', '')
982        );
983        var order = this.data.order || Object.keys(this.data.items);
984        for ( var pid, i=0, l=order.length; i<l,(pid=order[i]); i++ ) {
985          this.owns(this.data.items, pid) && this.buildPagemark(workspace, pid, this.data.items[pid]);
986        }
987        this.ui.pagemarks.styles = jQuery('<style id="pagemark-styles" />').text(
988          "/**\n"+
989          " * Withs initial\n"+
990          " */\n"+
991          workspace.styles.withs.initial.render() +
992          "/**\n"+
993          " * Layers initial\n"+
994          " */\n"+
995          workspace.styles.layers.initial.render() +
996          "/**\n"+
997          " * Withs targeted\n"+
998          " */\n"+
999          workspace.styles.withs.targeted.render() +
1000          "/**\n"+
1001          " * Layers targeted\n"+
1002          " */\n"+
1003          workspace.styles.layers.targeted.render() +
1004        '').appendTo('head');
1005        //
1006        //workspace.head = this.ui.pagemarks.targets;
1007        //
1008        workspace.head.addClass('finalmark');
1009        //
1010        workspace.navs.length && workspace.head.append(workspace.navs.join("\r\n"));
1011        /// add radios for the non-js handling
1012        workspace.radios.list.length && workspace.head.append(workspace.radios.list.join("\r\n"));
1013        var item, state;
1014        //
1015        for ( state in this.states.items ) {
1016          item = this.states.items[state];
1017          jQuery('body').prepend('<input ' +
1018            'type="radio" id="state-' + state + '" name="ps" value="' + state + '" class="' + item['class'] + '" ' +
1019            ( item['default'] ? 'checked="checked" ' : '' ) +
1020          '/>');
1021        }
1022        /// add our parallax within the pagemark structure
1023        workspace.head.append(this.ui.container);
1024        this.states.trigger && this.changeState(this.states.trigger);
1025
1026        // move our nav into the pagemarks so we can target based on nav point
1027        jQuery('#menu').appendTo(this.ui.pagemarks.targets);
1028        jQuery('#hud').appendTo(this.ui.pagemarks.targets);
1029
1030        ///
1031        this.ui.pagemarks.finalmark = workspace.head;
1032        this.ui.pagemarks.targets.addClass('rendered');
1033      },
1034
1035      /**
1036       * @TODO: work pid to be referenced from the workspace
1037       */
1038      buildPagemark: function(workspace, pid, pagemark){
1039        workspace.pid = { value: pid, split: this.splitPID(pid) };
1040        if ( this.mode === 'flat' ) {
1041          workspace.styles.layers.targeted.n(0).set('target', '#pm' + pid + ':target ~ .parallax');
1042          workspace.styles.layers.targeted.n(1).set('target', '.rnav.pm' + pid + ':checked ~ .parallax');
1043          workspace.styles.withs.targeted.n(0).set('target', '#pm' + pid + ':target ~');
1044          workspace.styles.withs.targeted.n(1).set('target', '.rnav.pm' + pid + ':checked ~');
1045          //
1046          jQuery('<span id="pm' + pid + '" />')
1047            .addClass('pm')
1048            .addClass(workspace.pid.split)
1049            .appendTo(this.ui.pagemarks.targets)
1050          ;
1051        }
1052        else {
1053          workspace.styles.layers.targeted.n(0).set('target', '#pm' + pid + ':target');
1054          workspace.styles.layers.targeted.n(1).set('target', '.rnav.pm' + pid + ':checked ~ .parallax');
1055          workspace.styles.withs.targeted.n(0).set('target', '#pm' + pid + ':target');
1056          workspace.styles.withs.targeted.n(1).set('target', '.rnav.pm' + pid + ':checked ~ .parallax');
1057          // nest one pagemark inside the next, creates hideous HTML, but means a functional non-js
1058          // impressively navigational website.
1059          workspace.head = jQuery('<span id="pm' + pid + '" />')
1060            .addClass('pm pm-nested')
1061            .addClass(this.splitPID(pid))
1062            .appendTo(workspace.head)
1063          ;
1064        }
1065        // step each state and create the required HTML and Styles
1066        if ( !pagemark.states ) { pagemark.states = {}; }
1067        var i, l, state, sel, order = this.data.states.order || Object.keys(pagemark.states);
1068        for ( i=0, l=order.length; i<l,(state=order[i]); i++ ) {
1069          if ( !pagemark.states[state] ) continue;
1070          sel = '#state-' + state + ':checked ~ #pagemarks';
1071          workspace.styles.withs.initial.set('state', sel);
1072          workspace.styles.layers.initial.set('state', sel);
1073          workspace.styles.withs.targeted.set('state', sel);
1074          workspace.styles.layers.targeted.set('state', sel);
1075          this.buildPagemarkNav(workspace, pid, state, pagemark, !!pagemark['default']);
1076          this.buildPagemarkLayers(workspace, pid, state, pagemark, !!pagemark['default']);
1077        }
1078      },
1079
1080      /**
1081       *
1082       */
1083      buildPagemarkNav: function(workspace, pid, state, pagemark, isDefault){
1084        if ( !workspace.radios.items[pid] ) {
1085          workspace.radios.items[pid] = true;
1086          workspace.radios.list.push('<input type="radio" name="keynav" class="rnav pm' + pid + ' ' + workspace.pid.split + '" autofocus /><span class="rnav-gap"></span>');
1087        }
1088        if ( pagemark.nav ) {
1089          pagemark.nav.up   && this.addNav(workspace, pagemark, 'up',   pid,    'up', isDefault);
1090          pagemark.nav.down && this.addNav(workspace, pagemark, 'down', pid,  'down', isDefault);
1091          pagemark.nav.prev && this.addNav(workspace, pagemark, 'prev', pid,  'left', isDefault);
1092          pagemark.nav.next && this.addNav(workspace, pagemark, 'next', pid, 'right', isDefault);
1093          if ( isDefault ) {
1094            // if we are the default page mark, show our with-pms for this default pagemark, by default.
1095            workspace.styles.withs.initial.add({
1096              after: '.with-pm' + pid,
1097              rules: '{ display: block; }'
1098            });
1099          }
1100          else {
1101            // if we aren't a default page mark, disable the with-pms for the default pagemark
1102            workspace.styles.withs.targeted.add({
1103              after: '.pm-default',
1104              rules: '{ display: none; }'
1105            });
1106          }
1107          // enable any with-pm elements for the targeted page mark.
1108          workspace.styles.withs.targeted.add({
1109            after: '.with-pm' + pid,
1110            rules: '{ display: block; }'
1111          });
1112        }
1113        else {
1114          // if there is no nav present for this page mark, just make sure to hide the default
1115          workspace.styles.withs.targeted.add({
1116            after: '.pm-default',
1117            rules: '{ display: none; }'
1118          });
1119        }
1120      },
1121
1122      /**
1123       *
1124       */
1125      buildPagemarkLayers: function(workspace, pid, state, pagemark, isDefault){
1126        workspace.layers.forEach(function(layer){
1127          var visible = layer.checkVisibility(pagemark.states[state]);
1128          /// for the first pagemark, use this as the "default" setup
1129          if ( isDefault ) {
1130            workspace.styles.layers.initial.add({
1131              after: '.layer.' + layer.name,
1132              rules: '{ ' +
1133                'transform: ' +
1134                  layer.translateString(
1135                    pagemark.states[state].x,
1136                    pagemark.states[state].y + 10,
1137                    pagemark.states[state].z
1138                  ) +
1139                ';' +
1140                (visible ? ' opacity: 1;' : ' opacity: 0;') +
1141              '}'
1142            });
1143          }
1144          /// then generate styles specific to the pagemark
1145          workspace.styles.layers.targeted.add({
1146            after: '.layer.' + layer.name,
1147            rules: '{ ' +
1148              'transform: ' +
1149                layer.translateString(
1150                  pagemark.states[state].x,
1151                  visible ? pagemark.states[state].y : pagemark.states[state].y + 10,
1152                  pagemark.states[state].z
1153                ) +
1154              ';' +
1155              (visible ? ' opacity: 1;' : ' opacity: 0;' + workspace.hzf) +
1156            '}'
1157          });
1158          // if we have a content layer, switch pointer events depending on visibility
1159          if ( !workspace.hzf && layer.isContentLayer() ) {
1160            if ( isDefault ) {
1161              workspace.styles.layers.initial.add({
1162                after: '.layer.' + layer.name,
1163                rules: '{ ' + (visible ? 'pointer-events: auto;' : 'pointer-events: none;') + ' }'
1164              });
1165            }
1166            workspace.styles.layers.targeted.add({
1167              after: '.layer.' + layer.name,
1168              rules: '{ ' + (visible ? 'pointer-events: auto;' : 'pointer-events: none;') + ' }'
1169            });
1170          }
1171        });
1172      },
1173
1174      getState: function(){
1175        return jQuery('.parallax-state:checked').attr('value');
1176      },
1177
1178      toggleState: function(){
1179        var state = this.getState(), states = arguments;
1180        for ( var i=0, l=states.length-1; i<=l; i++ ) {
1181          if ( states[i] === state ) {
1182            this.changeState(states[i === l ? 0 : i + 1]);
1183            return this;
1184          }
1185        }
1186        return this;
1187      },
1188
1189      changeState: function(state){
1190        if ( this.states.items[state] ) {
1191          jQuery('#state-' + state).click();
1192        }
1193      }
1194
1195    },
1196
1197    /**
1198     * @static
1199     *
1200     * No cross vendor js developer should leave home without this...
1201     */
1202    calcVendorPrefix: function(){
1203      if ( !document.body ) {
1204        console && console.enabled && console.log('prefix() requires the DOM to be ready.');
1205        return null;
1206      }
1207      if ( !this.calcVendorPrefix.value ) {
1208        if ( document.body ) {
1209          for ( var a = ['Webkit','Moz','O','ms'], i=0, l = a.length; i<l; i++ ) {
1210            if ( document.body.style[a[i]+'AnimationName'] !== undefined ) {
1211              return { js: a[i], css: '-' + a[i].toLowerCase() + '-' };
1212            }
1213          }
1214        }
1215        return (this.calcVendorPrefix.value = { css:'', js:'' });
1216      }
1217      else {
1218        return this.calcVendorPrefix.value;
1219      }
1220    },
1221
1222    /**
1223     *
1224     */
1225    jumpTo: function(pagemarkID){
1226      this.useCSSTransitions();
1227      console.log('jumping', window.location.hash, pagemarkID);
1228      //history.replaceState(undefined, undefined, "#test");
1229      if ( window.location.hash != '#' + pagemarkID ) {
1230        if ( window.location.hash && window.location.replace ) {
1231          window.location.replace('#' + pagemarkID);
1232        }
1233        else {
1234          window.location.hash = pagemarkID;
1235        }
1236        this.clearLocation();
1237        return true;
1238      }
1239      this.clearLocation();
1240      return false;
1241    },
1242
1243    /**
1244     *
1245     */
1246    clearLocation: function(){
1247      this.layers.position(null);
1248    },
1249
1250    /**
1251     *
1252     */
1253    updateLocation: function(){
1254      var loc = this.layers.position();
1255      this.location.x.set(loc.x);
1256      this.location.y.set(loc.y);
1257      this.location.z.set(loc.z);
1258    },
1259
1260    /**
1261     *
1262     */
1263    getLocation: function(){
1264      return {
1265        x: 0 + this.location.x,
1266        y: 0 + this.location.y,
1267        z: 0 + this.location.z
1268      };
1269    },
1270
1271    /**
1272     *
1273     */
1274    setLocation: function(x, y, z){
1275      this.location.x.set(x);
1276      this.location.y.set(y);
1277      this.location.z.set(z);
1278      this.layers.position(this.location);
1279    },
1280
1281    /**
1282     *
1283     */
1284    shiftTo: function(x, y, z, options){
1285      if ( this.shifting ) {
1286
1287      }
1288      else {
1289        var shifting = this.shifting = {};
1290        shifting.promise = new Promise(function(resolve, reject){
1291          shifting.resolve = resolve;
1292          shifting.reject = reject;
1293        });
1294        options = options || {};
1295        this.updateLocation();
1296        this.useJSTransitions();
1297        this.location.x.target(x, options.duration);
1298        this.location.y.target(y, options.duration);
1299        this.location.z.target(z, options.duration);
1300      }
1301      return shifting.promise;
1302    },
1303
1304    /**
1305     *
1306     */
1307    tick: function(delta, changed){
1308      if ( this.shifting ) {
1309        this.location.x.tick && this.location.x.tick(delta);
1310        this.location.y.tick && this.location.y.tick(delta);
1311        this.location.z.tick && this.location.z.tick(delta);
1312        if ( this.location.x.changed || this.location.y.changed || this.location.z.changed ) {
1313          this.layers.position(this.location);
1314        }
1315        else if ( !this.location.x.animating && !this.location.y.animating && !this.location.z.animating ) {
1316          var p = this.shifting;
1317          this.shifting = null;
1318          p.resolve();
1319        }
1320      }
1321    },
1322
1323    /**
1324     *
1325     */
1326    touchDragController: {
1327
1328      create: function(){
1329        return this.prep.apply(Object.create(this), arguments);
1330      },
1331
1332      prep: function(parallax, options){
1333        this.options = options || {
1334          type: 'pan'
1335        };
1336        this.ui = {};
1337        this.ui.container = parallax.ui.container.get(0);
1338        this.parallax = parallax;
1339        if ( this.options.type == 'swipe' ) {
1340          this.a = this.createAllSwipe(this.ui.container);
1341        }
1342        else {
1343          this.h = this.createHorizontalPan(this.ui.container);
1344          this.v = this.createVerticalPan(this.ui.container);
1345          //Hammer.defaults.cssProps.userSelect = 'auto';
1346          this.h && this.v && this.h.get('pan').requireFailure(this.v.get('pan'));
1347        }
1348        return this;
1349      },
1350
1351      bind: function(method){
1352        return method.bind(this);
1353      },
1354
1355      createAllSwipe: function(container){
1356        var aa;
1357        aa = new Hammer.Manager(container);
1358        aa.userdata = {};
1359        aa.userdata.handler = this.swipeAll(aa);
1360        aa.add(new Hammer.Swipe({ direction: Hammer.DIRECTION_ALL }));
1361        return aa;
1362      },
1363
1364      createHorizontalPan: function(container){
1365        var hh;
1366        hh = new Hammer.Manager(container);
1367        hh.userdata = {};
1368        hh.userdata.handler = this.panHorizontal(hh);
1369        hh.add(new Hammer.Pan({ direction: Hammer.DIRECTION_HORIZONTAL, threshold: 10 }));
1370        return hh;
1371      },
1372
1373      createVerticalPan: function(container){
1374        var hh;
1375        hh = new Hammer.Manager(container);
1376        hh.userdata = {};
1377        hh.userdata.handler = this.panVertical(hh);
1378        hh.add(new Hammer.Pan({ direction: Hammer.DIRECTION_VERTICAL, threshold: 10 }));
1379        return hh;
1380      },
1381
1382      enable: function(){
1383        if ( this.options.type == 'swipe' ) {
1384          this.a && this.a.on("swipeleft swiperight swipeup swipedown", this.a.userdata.handler);
1385        }
1386        else {
1387          this.h && this.h.on("panstart panmove panend pancancel", this.h.userdata.handler);
1388          this.v && this.v.on("panstart panmove panend pancancel", this.v.userdata.handler);
1389        }
1390      },
1391
1392      disable: function(){
1393        if ( this.options.type == 'swipe' ) {
1394          this.a && this.a.off("swipeleft swiperight swipeup swipedown", this.a.userdata.handler);
1395        }
1396        else {
1397          this.h && this.h.off("panstart panmove panend pancancel", this.h.userdata.handler);
1398          this.v && this.v.off("panstart panmove panend pancancel", this.v.userdata.handler);
1399        }
1400      },
1401
1402      /**
1403       * It seems, for some reason, Chrome mobile does not like having two
1404       * Hammer managers, one for horizontal, one for vertical (like I was doing)
1405       * but switching to a singular handler that handles them all is fine.
1406       *
1407       * This is only really doable for the swipe actions however, as the pan
1408       * actions need the requireFailure() handling - so have to be separate.
1409       */
1410      swipeAll: function(aa){
1411        return this.bind(function(e){
1412          if ( e.type == 'swipeleft' ) {
1413            this.parallax.pagemark.x('+1').update();
1414          }
1415          else if ( e.type == 'swiperight' ) {
1416            this.parallax.pagemark.x('-1').update();
1417          }
1418          else if ( e.type == 'swipeup' ) {
1419            this.parallax.pagemark.z('+1').update();
1420          }
1421          else if ( e.type == 'swipedown' ) {
1422            this.parallax.pagemark.z('-1').update();
1423          }
1424        });
1425      },
1426
1427      panHorizontal: function(hh){
1428        return this.bind(function(e){
1429          //if ( e.pointerType == 'mouse' ) return true;
1430          if ( e.type == 'panstart' ) {
1431            hh.userdata.topmost = this.parallax.layers.getTopmostVisible();
1432            this.parallax.updateLocation();
1433            hh.userdata.location = this.parallax.getLocation();
1434            this.parallax.useJSTransitions();
1435          }
1436          if ( e.type != 'pancancel' ) {
1437            var xd = e.deltaX / hh.userdata.topmost.config.depth.xmul;
1438            var xp = (1 / 3000) * xd;
1439            this.parallax.setLocation(
1440              0 + hh.userdata.location.x + xd,
1441              0 + hh.userdata.location.y,
1442              0 + hh.userdata.location.z
1443            );
1444          }
1445          if ( e.type == 'panend' ) {
1446            if ( xp < -0.2 ) { this.parallax.pagemark.x('+1').update(); }
1447            if ( xp > 0.2 ) { this.parallax.pagemark.x('-1').update(); }
1448            this.parallax.useCSSTransitions();
1449            this.parallax.clearLocation();
1450          }
1451        });
1452      },
1453
1454      panVertical: function(hh){
1455        return this.bind(function(e){
1456          //if ( e.pointerType == 'mouse' ) return true;
1457          if ( e.type == 'panstart' ) {
1458            hh.userdata.topmost = this.parallax.layers.getTopmostVisible();
1459            this.parallax.updateLocation();
1460            hh.userdata.location = this.parallax.getLocation();
1461            this.parallax.useJSTransitions();
1462          }
1463          if ( e.type != 'pancancel' ) {
1464            var yp = (1 / this.parallax.ui.container.height()) * e.deltaY;
1465            //this.parallax.setLocation(
1466            //  0 + hh.userdata.location.x,
1467            //  0 + hh.userdata.location.y + e.deltaY,
1468            //  0 + hh.userdata.location.z
1469            //);
1470          }
1471          if ( e.type == 'panend' ) {
1472            //if ( yp < -0.3 ) { this.parallax.pagemark.y('+1').update(); }
1473            //if ( yp >  0.3 ) { this.parallax.pagemark.y('-1').update(); }
1474            if ( yp < -0.3 ) { this.parallax.pagemark.z('+1').update(); }
1475            if ( yp >  0.3 ) { this.parallax.pagemark.z('-1').update(); }
1476            this.parallax.useCSSTransitions();
1477            this.parallax.clearLocation();
1478          }
1479        });
1480      }
1481
1482    },
1483
1484    /**
1485     * Point API
1486     */
1487    point: {
1488
1489      create: function(){
1490        return this.prep.apply(Object.create(this), arguments);
1491      },
1492
1493      prep: function(value, min, max){
1494        this.last = null;
1495        this.value = value || 0;
1496        this.from = value || 0;
1497        this.to = value || 0;
1498        this.animating = false;
1499        this.changed = false;
1500        this.min = min !== undefined ? min : undefined;
1501        this.max = max !== undefined ? max : undefined;
1502        this.limited = (this.min !== undefined || this.max !== undefined);
1503        this.tween = new Tween();
1504        return this;
1505      },
1506
1507      limit: function(v){
1508        if ( this.min !== undefined ) {
1509          if ( v < this.min ){ v = this.min; }
1510        }
1511        if ( this.max !== undefined ) {
1512          if ( v > this.max ){ v = this.max; }
1513        }
1514        return v;
1515      },
1516
1517      set: function(v){
1518        if ( this.limited ) { v = this.limit(v); }
1519        this.value = v;
1520        this.from = v;
1521        // @TODO: what happens if we are already tweening?
1522      },
1523
1524      valueOf: function(){
1525        return this.value || 0;
1526      },
1527
1528      target: function(target, duration){
1529        this.from = this.value || 0;
1530        this.to = target || 0;
1531        this.animating = true;
1532        this.tween.set(this.from, this.to - this.from, duration||0, 'quadInOut', false);
1533        this.tween.reset();
1534      },
1535
1536      tick: function(delta){
1537        this.last = this.value;
1538        if ( this.animating ) {
1539          if ( this.tween.expired() ) {
1540            if ( this.limited ) {
1541              this.value = this.limit(this.to);
1542            }
1543            else {
1544              this.value = this.to;
1545            }
1546            this.animating = false;
1547          }
1548          else {
1549            if ( this.limited ) {
1550              this.value = this.limit(this.tween.getValue());
1551            }
1552            else {
1553              this.value = this.tween.getValue();
1554            }
1555          }
1556        }
1557        this.changed = this.value !== this.last;
1558      },
1559
1560      /**
1561       * @static
1562       *
1563       * Calculate the difference between two points, (taking into account offset).
1564       */
1565      calcPointDifference: function( a, b, o ){
1566        var obj = {
1567          x: Math.round(b.x - a.x),
1568          y: Math.round(b.y - a.y),
1569          y: Math.round(b.y - a.z)
1570        };
1571        if ( o ) {
1572          obj.x -= o.x;
1573          obj.y -= o.y;
1574          obj.z -= o.z;
1575        }
1576        return obj;
1577      },
1578
1579      /**
1580       * @static
1581       *
1582       * Take a current value, and a "speed", increment the value by speed
1583       * as you decrease the speed by the dec multiplier. When reaching
1584       * zero speed return the value. Allows for predicting where a
1585       * particular easing out velocity will land.
1586       */
1587      calcContinuedMomentum: function( v, vspeed, dec ){
1588        dec = Math.max(Math.min(dec||0.9, 0.95),0);
1589        while ( Math.abs(vspeed) > 1 ) { vspeed *= dec; v += vspeed; };
1590        return v;
1591      },
1592
1593      /**
1594       * @static
1595       *
1596       * A simple cubic easing function, slows in and out.
1597       *
1598       * t = time
1599       * b = start value
1600       * c = total change
1601       * d = duration
1602       */
1603      easeInOutCubic: function (t, b, c, d) {
1604        t /= d/2;
1605        if (t < 1) return c/2*t*t*t + b;
1606        t -= 2;
1607        return c/2*(t*t*t + 2) + b;
1608      }
1609
1610    },
1611
1612    /**
1613     * Layers API
1614     */
1615    layers: {
1616
1617      create: function(config){
1618        return this.prep.apply(Object.create(this), arguments);
1619      },
1620
1621      prep: function(config){
1622        this.ui = config.ui;
1623        this.list = [];
1624        this.lookup = {};
1625        return this;
1626      },
1627
1628      add: function(layer){
1629        this.list.push(layer);
1630        this.lookup[layer.name] = layer.appendTo(this.ui.container);
1631        return this;
1632      },
1633
1634      position: function(position){
1635        var i, a, l, layer;
1636        if ( position ) {
1637          for ( i=0, a=this.list, l=a.length, layer; (i<l) && (layer=a[i]); i++ ) {
1638            layer.transform(0 + position.x, 0 + position.y, 0 + position.z);
1639          }
1640          return this;
1641        }
1642        else if ( position === null ) {
1643          for ( i=0, a=this.list, l=a.length, layer; (i<l) && (layer=a[i]); i++ ) {
1644            layer.clearTransform();
1645          }
1646          return this;
1647        }
1648        else {
1649          layer=this.list[this.list.length-1];
1650          a = layer.transform();
1651          if ( !layer.isVisible() ) {
1652            a.y -= 10;
1653          }
1654          return a;
1655        }
1656      },
1657
1658      getByName: function(name){
1659        return this.lookup[name] || false;
1660      },
1661
1662      getAll: function(){
1663        return this.list;
1664      },
1665
1666      getTopmostVisible: function(){
1667        var i, a, l, layer, visibles = [];
1668        for ( i=0, a=this.list, l=a.length, layer; (i<l) && (layer=a[i]); i++ ) {
1669          layer.isVisible() && visibles.push(layer);
1670        }
1671        if ( visibles.length > 2 ) {
1672          return visibles[visibles.length-2];
1673        }
1674        else {
1675          return null;
1676        }
1677      },
1678
1679      each: function(callback, context){
1680        for ( var i=0, l=this.list.length; i<l; i++ ) {
1681          callback.call(context || this, i, this.list[i]);
1682        }
1683      }
1684
1685    },
1686
1687    /**
1688     * Layer API
1689     */
1690    layer: {
1691
1692      /**
1693       *
1694       */
1695      create: function(name, config, resource){
1696        return this.prep.apply(Object.create(this), arguments);
1697      },
1698
1699      /**
1700       *
1701       */
1702      prep: function(name, config, resource){
1703        var layer = this, style;
1704        this.ui = {};
1705        this.name = name;
1706        this.config = config;
1707        this.coords = jQuery.extend({x:0, y:0, z:0}, this.config.coords || {});
1708        this.resource = resource;
1709        this.ui.outer = jQuery('<div class="layer" />').addClass(name).addClass(config['class']);
1710        this.ui.inner = jQuery('<div class="inner" />').addClass(name);
1711        if ( config.width ) {
1712          this.ui.outer.css('width', config.width + "px");
1713          this.ui.inner.css('width', config.width + "px");
1714        }
1715        this.ui.image = resource && resource.data ? jQuery(resource.data) : null;
1716        //if ( name.indexOf('mist') != -1 ) return this;
1717        if ( this.ui.image ) {
1718          // we could do away for ua.firefox because createImageBitmap only exists for it
1719          // @TODO: it looks like firefox doesn't need this any more, at least not on Mac (but perhaps check Windows)
1720          if ( 0 && ua.firefox && resource.texture && window.createImageBitmap ) {
1721            var c = jQuery('<canvas class="parallax-graphic" />'), ctx = c.get(0).getContext('2d');
1722            if ( config.graphics.repeat != 'no-repeat' ) {
1723              var w = this.ui.outer.width() || 14000;
1723 // @TODO: find a good way to get the at least initial width
1724              c.attr({
1725                width: w,
1726                height: resource.texture.height
1727              });
1728              ctx.rect(0, 0, w, resource.texture.height);
1729              ctx.fillStyle = ctx.createPattern(this.ui.image.get(0), config.graphics.repeat);
1730              ctx.fill();
1731            }
1732            else {
1733              c.attr({
1734                width: resource.texture.width,
1735                height: resource.texture.height
1736              });
1737              c.get(0).getContext('2d').drawImage(this.ui.image.get(0), 0, 0);
1738            }
1739            this.ui.inner.append(c);
1740          }
1741          else if ( config.graphics.repeat && config.graphics.repeat != 'no-repeat' ) {
1742            style = { 'background-image': 'url('+ this.ui.image.attr('src') +')' };
1743            style['background-repeat'] = config.graphics.repeat;
1744            this.ui.inner.addClass('parallax-background').css(style);
1745          }
1746          else {
1747            if ( resource.texture ) {
1748              this.ui.image.attr({
1749                "class": 'parallax-graphic',
1750                "width": resource.texture.width,
1751                "height": resource.texture.height
1752              });
1753            }
1754            this.ui.inner.append(this.ui.image);
1755          }
1756        }
1757        if ( layer.name === 'audio' ) {
1758          this.ui.inner.html('<canvas id="audio-canvas" width="800" height="300" />');
1759        }
1760        return this;
1761      },
1762
1763      /**
1764       *
1765       */
1766      appendTo: function(target){
1767        jQuery(target).append(this.ui.outer);
1768        this.ui.outer.append(this.ui.inner);
1769        return this;
1770      },
1771
1772      /**
1773       *
1774       */
1775      translateString: function(x, y, z){
1776        return 'translate3d(' +
1777          Math.round(this.coords.x + x * this.config.depth.xmul) + 'px, ' +
1778          Math.round(this.coords.y + y) + 'px, ' +
1779          Math.round((-this.coords.z) + z) + 'px)'
1780        ;
1781      },
1782
1783      /**
1784       *
1785       */
1786      translateCoordsFromStyleValue: (function(){
1787        var matrix = /^matrix\((.+?)\)$/i, matrix3D = /^matrix3D\((.+?)\)$/i;
1788        return function(cssTransform){
1789          var m;
1790          if ( (m=matrix.exec(cssTransform))) {
1791            m = m[1].split(', ');
1792            return {
1793              x: parseFloat(m[4]),
1794              y: parseFloat(m[5]),
1795              z: 0
1796            };
1797          }
1798          else if ( (m=matrix3D.exec(cssTransform))) {
1799            m = m[1].split(', ');
1800            return {
1801              x: parseFloat(m[12]),
1802              y: parseFloat(m[13]),
1803              z: parseFloat(m[14])
1804            };
1805          }
1806          //console.log( DOMMatrix.setMatrixValue(cssTransform) );
1807          //console.log( CSSMatrix );
1808          return null;
1809        };
1810      })(),
1811
1812      isContentLayer: function(){
1813        return !!this.config.content;
1814      },
1815
1816      isVisible: function(){
1817        return this.ui.outer.css('opacity') != 0;
1818      },
1819
1820      /**
1821       * @TODO:
1822       */
1823      checkVisibility: function(viewpoint){
1824        var dv = this.config.depth.visibility;
1825        var yv = this.config.y.visibility;
1826        if ( yv === false ) {
1827          if ( viewpoint.y < -500 ) {
1828            return false;
1829          }
1830        }
1831        if ( dv === true ) {
1832          return true;
1833        }
1834        else if ( dv && dv.length ) {
1835          return ( viewpoint.z > (this.coords.z + dv[0]) && viewpoint.z < (this.coords.z + dv[1]) );
1836        }
1837        return false;
1838      },
1839
1840      /**
1841       *
1842       */
1843      clearTransform: function(){
1844        this.ui.outer.css('transform', '');
1845      },
1846
1847      /**
1848       *
1849       */
1850      transform: function(x, y, z){
1851        if ( arguments.length === 0 ) {
1852          return this.translateCoordsFromStyleValue(this.ui.outer.css('transform'));
1853        }
1854        else {
1855          this.ui.outer.css('transform', this.translateString(x, y, z));
1856          return this;
1857        }
1858      },
1859
1860      show: function(){
1861        this.ui.outer.fadeIn(1500);
1862      },
1863
1864      hide: function(){
1865        this.ui.outer.fadeOut(1500);
1866      }
1867
1868    },
1869
1870    /**
1871     * Promise API
1872     */
1873    progress: function(){
1874      return this.promise.progress.apply(this.promise, arguments);
1875    },
1876    then: function(){
1877      return this.promise.then.apply(this.promise, arguments);
1878    },
1879
1880    /**
1881     * utility API
1882     */
1883    each: function(array, callback){
1884      for ( var i=0, l=array.length; i<l; i++ ) {
1885        callback.call(this, i, array[i]);
1886      }
1887    },
1888
1889    bind: function(method){
1890      return method.bind(this);
1891    },
1892
1893    /**
1894     * Element API
1895     */
1896    appendTo: function(target){
1897      //this.ui.pagemarks.finalmark.append(this.ui.nav);
1898      /// add the pagemark targets to the target element
1899      jQuery(target).append(this.ui.pagemarks.targets);
1900      return this;
1901    },
1902
1903    /**
1904     * Resource Request API
1905     */
1906    processResources: function(resources){
1907      this.each(this.data.layers, function(i, layer){
1908        this.layers.add(this.layer.create(layer.name, layer, resources[layer.name] || null));
1909      });
1910      return this;
1911    },
1912
1913    resetRequest: function(){
1914      this.request = [];
1915      return this;
1916    },
1917
1918    buildRequest: function(){
1919      this.resetRequest();
1920      return jQuery.eachResolve(this.data.layers, this.extendRequest, this).then(function(){
1921        return this.request;
1922      });
1923    },
1924
1925    extendRequest: function(i, item){
1926      if ( item && item.graphics ) {
1927        this.request.push({
1928          name: item.name,
1929          url: item.graphics.src
1930        });
1931      }
1932      return this;
1933    },
1934
1935    loadResources: function(){
1936      var d = jQuery.Deferred(), self = this;
1937      this.buildRequest().then(function(request){
1938        PIXI.loader
1939          .add(request)
1940          .on('progress', function(state, resource){
1941            d.notifyWith(self, [Math.round(state.progress)/100, resource]);
1942          })
1943          .load(function (loader, resources) {
1944            d.resolve(resources);
1945          })
1946        ;
1947      });
1948      return d.promise();
1949    }
1950
1951  };
1952
1953  return parallax;
1954
1955};
1956
1957/**
1958 * Used for debugging, generates a tracable "css selector" from an element back up to the root node.
1959 *
1960 * @memberof! jQuery
1961 */
1962jQuery.fn.elementPath = function(){
1963  var ret = [];
1964  this.each(function(i, elm){
1965    var path = '';
1966    jQuery(elm).parents().not('div span').each(function(i, elm){
1967      var id; elm = jQuery(elm);
1968      if ( elm.attr('id') ) {
1969        id = '#' + elm.attr('id');
1970      }
1971      else if ( elm.attr('class') ) {
1972        id = elm.attr('class').split(' ').join('.');
1973      }
1974      else {
1975        id = String(elm.get(0).nodeName).toLowerCase();
1976      }
1977      path = ( id ? id + ' ' : '' ) + path;
1978    });
1979    ret.push(path);
1980  });
1981  return ret.join("\n");
1982};
1983
1984/**
1985 * Find the nearest scroll parent
1986 *
1987 * @memberof! jQuery
1988 */
1989jQuery.fn.getScrollParent = function( includeHidden ) {
1990  var position = this.css( "position" ),
1991    excludeStaticParent = position === "absolute",
1992    overflowRegex = includeHidden ? (/(auto|scroll|hidden)/) : (/(auto|scroll)/),
1993    scrollParent = this.parents().filter( function() {
1994      var parent = $( this );
1995      if ( excludeStaticParent && parent.css( "position" ) === "static" ) {
1996        return false;
1997      }
1998      return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) +
1999        parent.css( "overflow-x" ) );
2000    } ).eq( 0 );
2001  return position === "fixed" || !scrollParent.length ?
2002    $( this[ 0 ].ownerDocument || document ) :
2003    scrollParent;
2004};
2005
2006/**
2007 * Reset the nearest scroll parent to 0,0 scroll
2008 *
2009 * @memberof! jQuery
2010 */
2011jQuery.fn.resetScrollParent = function(){
2012  this.getScrollParent().each(function(i, element){
2013    element.scrollTop = 0;
2014    element.scrollLeft = 0;
2015    console.log('resetting scroll parent', element);
2016  });
2017  return this;
2018};
2019
2020/**
2021 * For each element in the collection, set scrollLeft and scrollTop to 0
2022 *
2023 * @memberof! jQuery
2024 */
2025jQuery.fn.resetScroll = function(){
2026  this.each(function(i, elm){
2027    elm.scrollLeft = elm.scrollTop = 0;
2028  });
2029  return this;
2030};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.