1/** 2 * @requires makeshift 3 * @requires gtrack 4 * @requires jQuery 5 * @requires introJs 6 * @requires solarDisplay 7 * @requires steps 8 */ 9var makeshift, gtrack, introJs, solarDisplay, steps; 10/** 11 * ##### Overview 12 * 13 * As for nearly all my sites, I have a global `site` namespace. 14 * 15 * I find it useful having a singular global object that methods can be attached 16 * to that would be categorised as general code for the site. Obviously, if 17 * the code can be placed under any other namespace (a more specific one) then 18 * it makes sense to do that instead. 19 * 20 * ##### CHANGE LOG 21 * 22 * -- 20 April 2017 23 * - Refactored numerous scripts to finish off the site 24 * 25 * -- 9 March 2016 26 * 27 * - Fixed Firefox by forcing all image layers to render to canvas. 28 * - Added hammer touch handling (WIP). 29 * 30 * -- 6 March 2016 31 * 32 * - Added support to switch cleanly between JS Transitions and CSS Transitions. 33 * - Split makeshift down into smaller modules. 34 * 35 * @namespace site 36 * @author Phil Glanville 37 */ 38var siteCreator = makeshift.generateSite(jQuery, window).extend({ 39 40 /** 41 * Check if the site is in introducing mode 42 * @memberof! site 43 */ 44 isIntroducing: function(){ 45 return this.body.is('.introducing'); 46 }, 47 48 49 50 /** 51 * To help with exporting the rendered version of the site 52 * @memberof! site 53 */ 54 exportSource: function(){ 55 if ( window.sourceFrame ) { 56 window.sourceFrame.remove(); 57 window.sourceFrame = null; 58 return; 59 } 60 jQuery('body').removeClass('with-js'); 61 window.sourceFrame = jQuery('<iframe />').hide() 62 .appendTo('body') 63 .css({ 64 position: 'absolute', 65 left: 0, 66 top: 0, 67 right: 0, 68 bottom: 0, 69 width: '100%', 70 height: '100%', 71 zIndex: 10000 72 }) 73 .attr('src', 74 'data:text/html,' + 75 encodeURIComponent( 76 '<!DOCTYPE html><html><body>' + 77 '<style>body { color: white; background: rgba(0,0,0,0.8); font-size: 8px; }</style>' + 78 '<script>document.write("<xmp><!DOCTYPE html><html>" + window.parent.document.documentElement.innerHTML + "</html></xmp>");window.parent.sourceFrame.show();</script>' + 79 '</body></html>' 80 ) 81 ) 82 ; 83 }, 84 85 /** 86 * A quick hack method to allow iframe page content to be "lazy-loaded" but 87 * still also be workable for non-js browsers. 88 * 89 * ##### For non-JS browsers 90 * 91 * Without js, all the page iframes are targeted to a page that has a link, 92 * this link states "click to load content". When the iframe is called into 93 * existence, the user has the choice to click the link, causing the "lazy load". 94 * 95 * ##### For JS-enabled browsers 96 * 97 * `site.autoclick` is only triggered by js-browsers, and is executed by clicking the link 98 * that calls the iframe into vision. The function then "autoclicks" the link in the iframe, 99 * mentioned in the non-js version above. This triggers the load of the actual iframe 100 * content in a "on-request" manner. 101 * 102 * Because the iframe is triggered its load at the point it is requested, to avoid FOUC, 103 * the code hides the iframe - until the iframe content triggers `site.autoclickComplete`. 104 * 105 * @see site.autoclickComplete 106 * @memberof! site 107 * @param {HTMLElement} iframe - iframe element that is to be auto-navigated 108 * @param {String} target - html selector for the link to click inside the iframe 109 */ 110 autoclick: function(iframe, target){ 111 if ( !this.autoclick.list ) { 112 this.autoclick.keys = []; 113 this.autoclick.list = []; 114 } 115 iframe = jQuery(iframe); 116 target = iframe.contents().find(target); 117 if ( !target.length ) { return; } 118 var self = this; 119 var iwin = iframe.get(0).contentWindow; 120 this.autoclick.keys.push(iwin); 121 this.autoclick.list.push({ 122 tid: setTimeout(function(){ self.autoclickComplete(iwin); }, 2000), 123 target: target, 124 iframe: iframe.addClass('hidden-iframe'), 125 started: new Date().getTime(), 126 minimum: 2000 127 }); 128 if ( target.attr('data-href') ) { 129 target.length && iframe.attr('src', target.attr('data-href')); 130 } 131 else { 132 target.length && iframe.attr('src', target.attr('href')); 133 } 134 }, 135 136 /** 137 * Should be called by the loading iframe, to finalise the 'autoclick' 138 * 139 * @see site.autoclick 140 * @memberof! site 141 */ 142 autoclickComplete: function(iwin){ 143 if ( !this.autoclick.keys ) return; 144 var self = this, d, i = this.autoclick.keys.indexOf(iwin), item; 145 if ( i != -1 ) { 146 item = this.autoclick.list[i]; 147 item.tid && clearTimeout(item.tid); 148 d = (new Date().getTime() - item.started); 149 if ( d < item.minimum ) { 150 item.tid = setTimeout(function(){ 151 self.autoclickComplete(iwin); 152 }, (item.minimum - d) + 10); 153 } 154 else { 155 item.iframe.removeClass('hidden-iframe'); 156 site.readyGratuitousTracking(iwin.document); 157 } 158 } 159 }, 160 161 /** 162 * Trigger the start of the audioscape system, it is a layer within the parallax 163 * that responds to an audiotrack. The equaliser levels are calculated at runtime 164 * using the AudioContext API. 165 * 166 * @memberof! site 167 */ 168 audioscape: function(){ 169 if ( !this.audioscape.playing ) { 170 this.audioscape.playing = true; 171 jQuery(window).triggerHandler('audiolandscapestart'); 172 } 173 else { 174 this.audioscape.playing = false;
175 jQuery(window).triggerHandler('audiolandscapestop'); 176 } 177 }, 178 179 /** 180 * Enable/disable touch controls 181 * 182 * @memberof! site 183 */ 184 touchmode: function(icon){ 185 icon = jQuery(icon); 186 if ( icon.is('.enabled') ) { 187 icon.removeClass('enabled'); 188 this.parallax.touchDrag.disable(); 189 } 190 else { 191 icon.addClass('enabled'); 192 this.parallax.touchDrag.enable(); 193 } 194 }, 195 196 /** 197 * Trigger the site controls panel 198 * 199 * @memberof! site 200 */ 201 controls: function(){ 202 jQuery('body').toggleClass('controls-popover'); 203 }, 204 205 /** 206 * Trigger the StackOverflow panel 207 * 208 * @memberof! site 209 */ 210 showSO: function(){ 211 var i = introJs().setOptions({ 212 "showProgress": false, 213 "showBullets": false, 214 "showButtons": false, 215 "showStepNumbers": false, 216 "scrollToElement": false, 217 "steps": [{ 218 "element": ".item-stackoverflow", 219 "intro": 220 "<span class=\"text-content\"><h4>Stack Overflow</h4><p>I frequently help answer questions on Stack Overflow." + 221 "It is a great way to research and learn in a random way. You'd be surpised at the number of odd requests " + 222 "people make, that end up changing your own approach.</p><br>" + 223 "<a href=\"http://stackoverflow.com/users/1490904/pebbl\">" + 224 "<img width=\"208\" height=\"58\" title=\"profile for Pebbl at Stack Overflow, Q&A for professional and enthusiast programmers\" alt=\"profile for Pebbl at Stack Overflow, Q&A for professional and enthusiast programmers\" src=\"http://stackoverflow.com/users/flair/1490904.png?theme=dark\">" + 225 "</a>" + 226 "</span>", 227 "position": "bottom" 228 }] 229 }); 230 i.start(); 231 }, 232 233 /** 234 * Assign the loaded content to the parallax system - loaded from JSON/HTML snippets. 235 * 236 * @memberof! site 237 */ 238 readyParallax: function(){ 239 //this.tick(this.parallax.tick); 240 this.content && this.parallax.assignContent(this.content); 241 }, 242 243 /** 244 * Simple function to set-up the top right menu, enabling click events 245 * 246 * @memberof! site 247 */ 248 readyMenu: function(){ 249 var site = this; 250 // @TODO: quick fix, needs work 251 var hideSuperflous = function(){ 252 jQuery('.item-description').hide(); 253 jQuery('#mediaq').hide(); 254 site.off('pagemarkchange', hideSuperflous); 255 }; 256 jQuery('.item-introduction a') .click(function(){ site.intro(); }); 257 jQuery('.item-music a') .click(function(){ site.audioscape(); }); 258 jQuery('.item-fullscreen a') .click(function(){ site.fullscreen(); }); 259 jQuery('.item-power a') .click(function(){ site.power(this); }); 260 jQuery('.item-controls a') .click(function(){ site.controls(); }); 261 jQuery('.item-touchmode a') .click(function(){ site.touchmode(this); }); 262 jQuery('.item-stackoverflow a') .click(function(){ site.showSO(); }); 263 if ( this.parallax.justTouch ) { 264 jQuery('.item-touchmode a').click(); 265 } 266 this.on('pagemarkchange', hideSuperflous); 267 }, 268 269 /** 270 * ready the keyboard controlls for the site using Keyboard.js 271 * 272 * @memberof! site 273 */ 274 readyControls: function(){ 275 var site = this; 276 this.registerSimpleCombo('x', function(e, count){ if ( site.isIntroducing() ) { return; } site.exportSource(); }); 277 this.registerSimpleCombo('q', function(e, count){ if ( site.isIntroducing() ) { return; } site.parallax.pagemark.y('+1').update(); }); 278 this.registerSimpleCombo('down', function(e, count){ if ( site.isIntroducing() ) { return; } site.parallax.pagemark.z('+1').update(); }); 279 this.registerSimpleCombo('up', function(e, count){ if ( site.isIntroducing() ) { return; } site.parallax.pagemark.z('-1').update(); }); 280 this.registerSimpleCombo('left', function(e, count){ if ( site.isIntroducing() ) { return; } site.parallax.pagemark.x('-1').update(); }); 281 this.registerSimpleCombo('right', function(e, count){ if ( site.isIntroducing() ) { return; } site.parallax.pagemark.x('+1').update(); }); 282 this.registerSimpleCombo('w', function(e, count){ if ( site.isIntroducing() ) { return; } site.parallax.pagemarks.toggleState('offscreen', 'initial'); });
283 this.registerSimpleCombo('g', function(e, count){ jQuery('body').toggleClass('crazy-mode'); }); 284 }, 285 286 readyGratuitousTracking: function(context){ 287 console.log('tracking context', context||document); 288 try { gtrack(context||document); }catch( ex ) { console.log('tracking failed', ex); }; 289 }, 290 291 /** 292 * Because it is possible to load directly on an iframe-d page, at 293 * least with js we can fix this. Those without js will need to click the load link. 294 */ 295 readyFrameContent: function(){ 296 var site = this; 297 var runonce = function(){ 298 site.off('pagemarkchange', runonce); 299 var pm = site.parallax.pagemark.get(), target = jQuery('.more-info[href*="#'+pm+'"]'); 300 if ( target.length ) { 301 target.click(); 302 console.log('frame auto clicked for', pm); 303 } 304 }; 305 site.on('pagemarkchange', runonce); 306 } 307 308}); 309 310/** 311 * Build an instance of site 312 */ 313var site = siteCreator.create({ 314 315 /** 316 * The JSON source that powers the current site 317 * 318 * @memberof! site 319 * @type string 320 */ 321 source: String(window.location).indexOf('making') != -1 ? 'data/making.json?' : 'data/core.json?', 322 323 /** 324 * The frameRate of the tick operations, currently only used by the 325 * site when using js-transtions. These have been disabled in v4 of the 326 * site... until I get more time to refactor. 327 * 328 * @memberof! site 329 * @type number 330 */ 331 frameRate: 40, 332 333 /** 334 * hud configuration 335 * 336 * @memberof! site 337 */ 338 hud: { 339 target: 'nav.hud' 340 }, 341 342 /** 343 * progress configuration 344 * 345 * @memberof! site 346 */ 347 progress: { 348 target: '#loader', 349 canvas: { 350 width: 100, 351 height: 100 352 }, 353 hints: { 354 parallax: 'rgba(255, 128, 0, 1)', 355 content: 'rgba(255, 200, 0, 1)' 356 } 357 }, 358 359 /** 360 * Triggered once preload is completed. 361 * 362 * @memberof! site 363 * @returns {undefined} 364 */ 365 ready: function(){ 366 this.readyParallax(); 367 this.readyFrameContent(); 368 this.readyControls(); 369 this.readyMenu(); 370 this.readyGratuitousTracking(); 371 372 // specify CSS transitions to start 373 this.parallax && this.parallax.useCSSTransitions(); 374 375 // some links have previews 376 jQuery('.use-preview').thumbPreview(); 377 378 // handle the reveal of the website 379 steps.create().context(this) 380 .addDelay(250) 381 .addWithDelay(250, !this.parallax.pagemark.is('pm000') && function(){ this.parallax.pagemark.force(); }) 382 .addWithDefer(function(d){ 383 this.ui.progress.fadeOut(function(){ 384 d.resolve(); 385 }); 386 }) 387 .addWithDelay(500, function(){ 388 this.parallax.pagemarks.changeState('initial'); 389 }) 390 .start() 391 ; 392 //site.parallax.shiftTo(-8000, 0, 500, { duration: 10000 }) 393 } 394 395});
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.