PageSourceSearch

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

js codelamp.co.uk collected 2026-10-04 13:34:32 UTC 18,601 bytes, 601 lines download raw bytes

1/**
2 * Makeshift is the name of the system responsible for my site's parallax.
3 *
4 * @namespace makeshift
5 * @author Phil Glanville
6 */
7var makeshift = makeshift || {};
8
9/**
10 * @namespace jQuery
11 */
12var jQuery;
13
14/**
15 * Used to generate the parallax structure in the form of CSS and HTML.
16 * This returns the initial `site` object, which is then extended.
17 *
18 * @memberof! makeshift
19 */
20makeshift.generateSite = function($, global, parallax, pockets){
21
22  parallax = makeshift.getParallaxBuilder.apply(makeshift, arguments);
23  pockets = makeshift.getPocketsBuilder.apply(makeshift, arguments);
24
25  /**
26   *
27   */
28  var site = {
29
30    listeners: {},
31
32    /**
33     * Create a new instance of `site`
34     *
35     * @memberof! site
36     */
37    create: function(config){
38      return this.prep.apply(Object.create(this), arguments);
39    },
40
41    /**
42     * Preps a new instance of `site`, used directly by {@link site.create}
43     *
44     * @memberof! site
45     * @private
46     */
47    prep: function(config){
48      var d = jQuery.Deferred();
49      this.promise = d.promise();
50      this.config = config || {};
51      this.config.frameRate = this.config.frameRate || 40;
52      this.config.stopped = false;
53      this.config.then = 0;
54      this.config.delta = 0;
55      this.config.interval = 1000 / this.config.frameRate;
56      this.config.raf = requestAnimationFrame;
57      this.config.buildCallback = null;
58      this.animate = this.animate.bind(this); /// @TODO: --- bind needs to exist
59      this.keyboard = new window.keypress.Listener();
60      this.body = jQuery('body');
61      this.ui = {};
62      jQuery(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange', this.events.fullscreenchange);
63      if ( this.isRendered() ) {
64        // don't build anything
65      }
66      else {
67        if ( this.config.progress ) {
68          this.ui.progress = progress
69            .create(this.config.progress)
70            .userdata(this)
71            .then(function(site){d.resolve(site);})
72          ;
73          this.build().progress(this.ui.progress.update.bind(this.ui.progress));
74        }
75        else {
76          this.build().then(function(){d.resolve(this);});
77        }
78      }
79      if ( this.config.ready ) {
80        this.promise = this.promise.then(this.config.ready.bind(this));
81      }
82      return this;
83    },
84
85    on: function(name, callback){
86      if ( !this.listeners[name] ) {
87        this.listeners[name] = [];
88      }
89      this.listeners[name].push(callback);
90    },
91    
92    off: function(name, callback){
93      if ( this.listeners[name] ) {
94        var i = this.listeners[name].indexOf(callback);
95        if ( i != -1 ) {
96          this.listeners[name].splice(i, 1);
97        }
98      }
99    },
100    
101    trigger: function(name, args, context){
102      if ( this.listeners[name] ) {
103        // operate on a copy of the list, to avoid modification whilst looping
104        for ( var i=0, a=this.listeners[name].slice(0), l=a.length; i<l; i++ ) {
105          a[i].apply(context || this, args);
106        }
107      }
108    },
109
110   /**
111    * Extend the `site` object with the own items of the passed in obj param.
112    *
113    * @memberof! site
114    * @param {object} obj The object the properties will be copied from
115    */
116    extend: function(obj){
117      var i, own = Object.prototype.hasOwnProperty ? Object.prototype.hasOwnProperty : function(){return true;};
118      for ( i in obj ) {
119        if ( own.call(obj, i) ) {
120          this[i] = obj[i];
121        }
122      }
123      return this;
124    },
125
126   /**
127    * A wrapper function for the Keyboard.js register_combo method.
128    *
129    * @memberof! site
130    * @param {string} keys - a string of keys in keyboard.js combo notation
131    * @param {function} callback - the callback to trigger when the combo is typed
132    * @param {mixed} context - the context to use for the callback method i.e. this
133    */
134    registerSimpleCombo: function(keys, callback, context){
135      this.keyboard.register_combo({
136        "keys"              : keys,
137        "on_keydown"        : callback,
138        "on_keyup"          : null,
139        "on_release"        : null,
140        "this"              : context,
141        "prevent_default"   : false,
142        "prevent_repeat"    : true,
143        "is_unordered"      : false,
144        "is_counting"       : false,
145        "is_exclusive"      : false,
146        "is_solitary"       : false,
147        "is_sequence"       : false
148      });
149    },
150
151    /**
152     * Determine if pagemarks have already been rendered, or not.
153     *
154     * @memberof! site
155     */
156    isRendered: function(){
157      return !!this.body.find('#pagemarks.rendered').length;
158    },
159
160    /**
161     * Trigger the dynamic build of the site, first step, load the content.
162     *
163     * @memberof! site
164     */
165    build: function(callback){
166      this.config.buildCallback = callback;
167      var d = jQuery.Deferred(), instance = this;
168      $.get(this.config.source).done(function(siteData){
169        var progress = { parallax: 0, content: 0 };
170        //if ( siteData.intro ) { instance.intro = instance.createIntro(siteData); }
171        site.siteData = siteData;
172        var parallaxComplete = site.createParallax(siteData)
173          .progress(function(p){ progress.parallax = p; d.notify(progress, instance); })
174          .then(function(parallax){instance.parallax = parallax;})
175        ;
176        var contentComplete = site.createContent(siteData)
177          .progress(function(p){ progress.content = p; d.notify(progress, instance); })
178          .then(function(content){instance.content = content;})
179        ;
180        var when = jQuery.when(parallaxComplete, contentComplete)
181          .then(function(){ instance.start(); d.resolve(instance); })
182        ;
183      });
184      return d.promise();
185    },
186
187    /**
188     * Promise API
189     */
190    progress: function(){
191      return this.promise.progress.apply(this.promise, arguments);
192    },
193    then: function(){
194      return this.promise.then.apply(this.promise, arguments);
195    },
196
197    /**
198     * Tick -- for every beat of animation
199     */
200    tick: function(callback){
201      if ( callback && callback.call ) {
202        !this.tick.list && (this.tick.list = []);
203        this.tick.list.push(callback);
204      }
205      else if ( this.tick && this.tick.list && this.tick.list.length ) {
206        for ( var i=0, a=this.tick.list, l=a.length; i<l; i++ ) {
207          this.tick.list[i].apply(this, arguments);
208        }
209      }
210    },
211
212    /**
213     * Trigger off the intro.js introduction to the site.
214     *
215     * @memberof! site
216     */
217    intro: function(){
218      var data = JSON.parse(JSON.stringify(this.siteData));
219      var i = this.createIntro(data); i.start();
220    },
221
222    /**
223     * Special code to get intro.js to work with transformed layers
224     * also had to hack intro.js slightly -- introducing .show() and .hide();
225     * and changing the way it finds its elements to use getBoundingClientRect()
226     *
227     * @memberof! site
228     */
229    createIntro: function(siteData){
230      for ( var i=0, l=siteData.intro.steps.length, item; i<l, (item=siteData.intro.steps[i]); i++ ) {
231        if ( item.element ) {
232          item.element = jQuery(item.element).get(0);
233        }
234      }
235      var site = this, intro = introJs().setOptions(siteData.intro);
236      intro.onbeforechange(function(nextElm) {
237        if ( site.body.isNot('.introducing') ) { site.body.addClass('introducing'); }
238        var pm = jQuery(nextElm).closest('.pagemark'), res;
239        if ( pm.length ) {
240          res = pm.attr('class').match(/\b(pm[0-9]+)/);
241          if ( res && res[1] ) {
242            if ( site.parallax.jumpTo(res[1]) ) {
243              intro.hide();
244              setTimeout(function(){
245                intro.refresh();
246                intro.show();
247              }, 3000);
248            }
249          }
250        }
251        else {
252          intro.show();
253        }
254      });
255      intro.onexit(function(){ if ( site.body.is('.introducing') ) { site.body.removeClass('introducing'); } });
256      intro.oncomplete(function(){ if ( site.body.is('.introducing') ) { site.body.removeClass('introducing'); } });
257      return intro;
258    },
259
260    /**
261     * Hook into the getParallaxBuilder functionality
262     *
263     * @memberof! site
264     */
265    createParallax: function(siteData){
266      return parallax.create(siteData.parallax, this);
267    },
268
269    /**
270     * For each page of content that is specified in the JSON,
271     * attempt to load the corresponding HTML file.
272     *
273     * @memberof! site
274     */
275    createContent: function(siteData){
276      var pageKey, item, pocket, when = [], totals = [], content = {}, d = jQuery.Deferred(),
277        progressTracker = function(i){
278          totals[i] = 0;
279          return function(p){
280            var k, total = 0;
281            totals[i] = p;
282            for ( k=0; k<totals.length; k++ ) { total += totals[k]; }
283            d.notify(1/when.length * total);
284          };
285        },
286        contentTracker = function(pageKey){
287          return function(c){
288            content[pageKey] = c;
289          };
290        }
291      ;
292      try {
293        if ( siteData.pages ) {
294          for ( pageKey in siteData.pages ) {
295            if ( (item=siteData.pages[pageKey]) ) {
296              if ( item.pocket ) {
297                when.push(jQuery.getPocketTemplate(item.pocket)
298                  .progress(progressTracker(when.length))
299                  .done(contentTracker(pageKey))
300                );
301              }
302            }
303          }
304        }
305        jQuery.when.apply(jQuery, when)
306          .done(function(){d.resolve(content);})
307          .fail(function(){d.reject();})
308        ;
309      }
310      catch ( ex ) {
311        console.log(ex);
312      }
313      return d.promise();
314    },
315
316    /**
317     * @TODO: this is disabled for now as it wasn't yet being used
318     */
319    animate: function(now){
320      !this.config.stopped && this.config.raf.call(window, this.animate);
321      if ( now ) {
322        this.config.delta = ( this.config.then
323          ? now - this.config.then
324          : this.config.interval
325        );
326        if ( this.config.delta >= this.config.interval ) {
327          this.config.then = now - (this.config.delta % this.config.interval);
328          //tick(1 - (1 / interval) * (delta % interval));
329          //tick(delta / interval);
330          this.tick(this.config.interval / this.config.delta);
331        }
332      }
333    },
334
335    /**
336     *
337     */
338    start: function(){
339      this.config.stopped = false;
340      //this.animate();
341    },
342
343    /**
344     *
345     */
346    stop: function(){
347      this.config.stopped = true;
348    },
349    /**
350     * Test that we are in fullscreen mode
351     *
352     * @memberof! site
353     */
354    isFullscreen: function(){
355      return (
356        (document.fullScreenElement
357          && document.fullScreenElement !== null) ||
358        (document.mozFullScreenElement
359          && document.mozFullScreenElement !== null) ||
360        (document.webkitFullscreenElement
361          && document.webkitFullscreenElement !== null) ||
362        (document.msFullscreenElement
363          && document.msFullscreenElement !== null) ||
364        document.mozFullScreen ||
365        document.webkitIsFullScreen
366      );
367    },
368    /**
369     * Toggle fullscreen
370     *
371     * @memberof! site
372     */
373    fullscreen: function(){
374      if (!document.fullscreenElement &&
375          !document.mozFullScreenElement &&
376          !document.webkitFullscreenElement &&
377          !document.msFullscreenElement )
378      {
379        if (document.documentElement.requestFullscreen) { document.documentElement.requestFullscreen(); }
380        else if (document.documentElement.msRequestFullscreen) { document.documentElement.msRequestFullscreen(); }
381        else if (document.documentElement.mozRequestFullScreen) { document.documentElement.mozRequestFullScreen(); }
382        else if (document.documentElement.webkitRequestFullscreen) { document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); }
383      }
384      else {
385        if (document.exitFullscreen) { document.exitFullscreen(); }
386        else if (document.msExitFullscreen) { document.msExitFullscreen(); }
387        else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); }
388        else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); }
389      }
390    },
391
392    /**
393     * Add a class to the main wrapping element
394     *
395     * @TODO: currently hardcoded to body, should be improved.
396     *
397     * @memberof! site
398     */
399    addClass: function(classname){
400      jQuery('body').addClass(classname);
401    },
402    /**
403     * Remove a class to the main wrapping element
404     *
405     * @TODO: currently hardcoded to body, should be improved.
406     *
407     * @memberof! site
408     */
409    removeClass: function(classname){
410      jQuery('body').removeClass(classname);
411    },
412
413    /**
414     * Here you will find all the bespoke events that codelamp.co.uk uses
415     *
416     * @memberof! site
417     * @namespace! site.events
418     */
419    events: {
420      /**
421       * triggered when fullscreen changes
422       */
423      fullscreenchange: function(){
424        if ( site.isFullscreen() ) {
425          site.addClass('fullscreened');
426        }
427        else {
428          site.removeClass('fullscreened');
429        }
430      }
431
432    },
433
434    /**
435     * Toggle the "power" state of the website
436     *
437     * @memberof! site
438     */
439    power: function(icon){
440      var elm = jQuery(icon).find('i'), cls = elm.attr('class'), pow, nex;
441      switch ( cls ) {
442        case 'fa fa-battery-0': pow = 0; break;
443        case 'fa fa-battery-1': pow = 1; break;
444        case 'fa fa-battery-2': pow = 2; break;
445        case 'fa fa-battery-3': pow = 3; break;
446        case 'fa fa-battery-4': pow = 4; break;
447      }
448      nex = (pow+1)%5;
449      elm.removeClass('fa-battery-' + pow).addClass('fa-battery-'+nex);
450      jQuery('body').removeClass('power-' + pow).addClass('power-'+nex);
451    }
452
453  };
454
455  /**
456   * Specialised extension to jQuery to load our "pocket" templates
457   *
458   * @memberof! jQuery
459   */
460  jQuery.getPocketTemplate = function(url){
461    var d = jQuery.Deferred();
462    var config = {
463      type: 'get',
464      url: url,
465      cache: false,
466      xhr: function(xhr){
467        xhr = jQuery.ajaxSettings.xhr();
468        config.notify = function(e){ if ( isNaN(e) ) { e.lengthComputable && d.notify(e.loaded / e.total); } else { d.notify(e); } };
469        config.unnotify = function(){ xhr.removeEventListener("progress", config.notify); };
470        xhr.addEventListener("progress", config.notify, false);
471        return xhr;
472      }
473    };
474    try {
475      $.ajax(config)
476        // due to progress events not being fired when using cached content, .notify(1) is required
477        .always(function(){ config.notify(1); config.unnotify();})
478        .done(function(data){
479          var template = {};
480          template.elements = pockets.parsePocketTemplate(data);
481          template.sections = template.elements.filter('section');
482          template.displays = {};
483          /// if any sections have been defined, use these as template separators
484          if ( template.sections.length ) {
485            template.sections.each(function(){
486              template.displays[this.className] = pockets.parsePocketDisplay(this, this.className);
487            });
488          }
489          else {
490            template.displays['default'] = pockets.parsePocketDisplay(template.elements, 'default');
491          }
492          delete template.elements;
493          delete template.sections;
494          d.resolve(template);
495        })
496      ;
497    }
498    catch (ex) { console.log(ex); }
499    return d.promise();
500  };
501
502  /**
503   * Same as jQuery each, but with promise and the possibility of
504   * fetching data from elsewhere.
505   *
506   * @memberof! jQuery
507   */
508  jQuery.eachResolve = function(data, callback, context){
509    var d = jQuery.Deferred();
510    jQuery.each(data, jQuery.proxy(callback, context));
511    return d.resolveWith(context, [context]).promise();
512  };
513
514  /**
515   * Find links with `data-preview` attributes and set interaction events
516   * to display that data as a thumb image on hover. That should serve as a thumbnail
517   * preview of the link itself.
518   *
519   * I'm using images, rather than live previews, because it is a much lighter
520   * solution; that works even for remote/frame-protected websites.
521   *
522   * @memberof! jQuery
523   * @method thumbPreview
524   * @returns {undefined}
525   */
526  jQuery.fn.thumbPreview = function(){
527    this.each(function(){
528      var self = jQuery(this),
529          img = jQuery('<img src="' + self.attr('data-preview') + '" />'),
530          enter = function(){
531            self
532              .addClass('thumb-preview')
533              .prepend(img)
534            ;
535          },
536          leave = function(){
537            self.removeClass('thumb-preview');
538            img.remove();
539          },
540          toi
541      ;
542      img.on('load', function(){
543        this.style.marginLeft = (-this.width/2) + 'px';
544      });
545      self
546        .on('mouseenter', function(e){
547          toi && clearTimeout(toi);
548          toi = setTimeout(enter, 1000);
549        })
550        .on('mouseleave', function(e){
551          toi && clearTimeout(toi);
552          leave();
553        });
554      ;
555    });
556  };
557
558  jQuery.fn.isNot = function(){
559    return !this.is.apply(this, arguments);
560  };
561
562  return site;
563
564};
565
566/**
567 * @memberof! makeshift
568 * @deprecated for now, until I can get more time, will create the parallax in Canvas/WebGL rather than CSS/HTML transitions
569 */
570makeshift.generatePixiSite = function(){
571  /// layers[layers.topmost].inner.append('<div id="jumptest"><h1 style="color: white;">Hello</h1></div>');
572  var body = jQuery('body');
573  var wrapper = jQuery('<div />').addClass('wrapper').appendTo(body);
574  var site = {
575    create: function(config){
576      return this.prep.apply(Object.create(this), arguments);
577    },
578    prep: function(config){
579      this.i = {};
580      this.i.steps = {createStage: 1, createLayers: 1};
581      return this;
582    },
583    build: function(){},
584    createStage: function(){},
585    createLayers: function(){}
586  };
587  PIXI.loader
588    .add('sky',    'images/background-sky-big-3.jpg')
589    .add('bg-2',   'images/background-landmass-2-8b.png')
590    .add('fg-3',   'images/foreground-landmass-3.1-8b.png')
591    .add('fg-2',   'images/foreground-landmass-2-80px-8b.png')
592    .add('fg-1',   'images/foreground-landmass-extended-80px-8b.png')
593    .add('mist-1', 'images/solid-mist-8b.png')
594    .add('mist-2', 'images/solid-mist-8b.png')
595    .on('progress', function(loader, loadedResource){console.log('Progress:', loader.progress + '%');})
596    .load(function (loader, resources) {})
597  ;
598  return {
599    create: function(){}
600  };
601};

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.