├── .gitignore ├── Gruntfile.js ├── README.md ├── bin └── buildgrid ├── bower.json ├── browserify.js ├── build ├── encom-globe.js └── encom-globe.min.js ├── data.js ├── grid.js ├── include ├── Detector.js ├── jquery.switchButton.css ├── jquery.switchButton.js ├── pusher.color.js ├── quadtree2.js ├── simple-slider.js ├── spectrum.css ├── spectrum.js ├── three.min.js ├── tween.min.js └── vec2.js ├── index.html ├── package.json ├── resources ├── equirectangle_projection.png ├── fp_icon.jpg ├── point_picker.png └── thumbprint.png ├── screenshot.jpg ├── src ├── Globe.js ├── Marker.js ├── Pin.js ├── Satellite.js ├── SmokeProvider.js ├── TextureAnimator.js └── utils.js └── styles.css /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | bower_components/* 3 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | watch: { 5 | options: { 6 | livereload: true 7 | }, 8 | tasks: ['browserify'], 9 | files: ['src/*.js', 'index.html', 'styles.css', 'Gruntfile.js', 'browserify.js'] 10 | }, 11 | browserify: { 12 | 'build/<%= pkg.name %>.js': ['browserify.js'] 13 | }, 14 | shell: { 15 | buildgrid: { 16 | command: "bin/buildgrid -r 500 -o grid.js -m resources/equirectangle_projection.png" 17 | } 18 | }, 19 | uglify: { 20 | main: { 21 | files: { 22 | 'build/<%= pkg.name%>.min.js': 'build/<%= pkg.name %>.js' 23 | } 24 | } 25 | } 26 | 27 | }); 28 | 29 | 30 | grunt.loadNpmTasks('grunt-contrib-watch'); 31 | grunt.loadNpmTasks('grunt-shell'); 32 | grunt.loadNpmTasks('grunt-browserify'); 33 | grunt.loadNpmTasks('grunt-contrib-uglify'); 34 | 35 | grunt.registerTask('buildgrid', ['shell:buildgrid']); 36 | grunt.registerTask('build', ['browserify', 'uglify']); 37 | 38 | 39 | }; 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The **Encom Globe** is a WebGL-based clone of the globe from the boardroom scene in [Tron: Legacy](http://www.imdb.com/title/tt1104001/). If you don't recall that scene, check out [Bradley Munkowitz's excellent writeup](http://work.gmunk.com/TRON-Board-Room) about its production for more information. 2 | 3 | This version attempts to stay as true to the in-movie original as possible while maintaining a high frame rate on low powered machines and devices. It is getting *pretty close* to acheiving that goal and mimics most of the details seen on screen, including the loading animations for the globe hexagon particles, the satellites, and the markers. 4 | 5 | ![Encom Globe](https://raw.github.com/arscan/encom-globe/master/screenshot.jpg "Encom Globe") 6 | 7 | It written using Three.js, with parts implemented in GLSL. It is not at all affiliated with Tron, Disney, or the team that created the original movie version. It is just a tribute. 8 | 9 | A [Demo](http://www.robscanlon.com/encom-globe) is available on my website. 10 | 11 | ### Usage 12 | 13 | The Encom Globe was written as part of a [larger project](http://www.robscanlon.com/encom-boardroom) and with a specific use in mind, but it does provide a certain level of flexibility if you would like to use it yourself as an alternative to the popular [WebGL Globe](http://www.chromeexperiments.com/globe). The blue markers, orange connected markers, and satellites can be placed wherever and whenever you would like. 14 | 15 | View [index.html](index.html) for a usage example. 16 | 17 | ### License 18 | 19 | The MIT License (MIT) 20 | Copyright (c) 2014-2017 Robert Scanlon 21 | 22 | Permission is hereby granted, free of charge, to any person obtaining a copy 23 | of this software and associated documentation files (the "Software"), to deal 24 | in the Software without restriction, including without limitation the rights 25 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 26 | copies of the Software, and to permit persons to whom the Software is 27 | furnished to do so, subject to the following conditions: 28 | 29 | The above copyright notice and this permission notice shall be included in 30 | all copies or substantial portions of the Software. 31 | 32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 33 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 34 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 35 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 36 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 37 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 38 | THE SOFTWARE. 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /bin/buildgrid: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 4 | 5 | var argv = require('minimist')(process.argv.slice(2)), 6 | PNG = require('pngjs').PNG, 7 | fs = require('fs'); 8 | 9 | var Hexasphere = require('hexasphere.js'); 10 | 11 | var radius = 30; 12 | var numDivisions = 35; 13 | var scale = .45; 14 | var map = argv.m; 15 | 16 | if(argv.r){ 17 | radius = parseInt(argv.r); 18 | } 19 | if(argv.d){ 20 | numDivisions = parseInt(argv.d); 21 | } 22 | if(argv.s){ 23 | scale = parseFloat(argv.s); 24 | } 25 | 26 | var hexasphere = new Hexasphere(radius, numDivisions, scale); 27 | 28 | var o = {tiles: []}; 29 | 30 | var latLonToXY = function(width, height, lat,lon){ 31 | 32 | var x = Math.floor(width/2.0 + (width/360.0)*lon); 33 | var y = Math.floor((height/2.0 + (height/180.0)*lat)); 34 | 35 | return {x: x, y:y}; 36 | }; 37 | 38 | var xyToIdx = function(x,y, width){ 39 | return (width * y + x) << 2; 40 | }; 41 | 42 | var rnd = function(num){ 43 | return Math.round(num * 100) / 100; 44 | }; 45 | 46 | fs.createReadStream(map) 47 | .pipe(new PNG({ 48 | filterType: 4 49 | })) 50 | .on('parsed', function() { 51 | for(var i = 0; i< hexasphere.tiles.length; i++){ 52 | var count = 0; 53 | for(var j = 0; j< hexasphere.tiles[i].boundary.length; j++){ 54 | var latLon = hexasphere.tiles[i].getLatLon(radius,j); 55 | var xy = latLonToXY(this.width, this.height, latLon.lat, latLon.lon); 56 | var idx = xyToIdx(xy.x, xy.y, this.width); 57 | count += 255 - this.data[idx]; 58 | } 59 | 60 | latLon = hexasphere.tiles[i].getLatLon(radius); 61 | xy = latLonToXY(this.width, this.height, latLon.lat, latLon.lon); 62 | idx = xyToIdx(xy.x, xy.y, this.width); 63 | count += 255 - this.data[idx]; 64 | 65 | var size = (count/(hexasphere.tiles[i].boundary.length + 1))/255; 66 | 67 | if(size > .1){ 68 | 69 | /* 70 | var c = { 71 | x: rnd(hexasphere.tiles[i].centerPoint.x), 72 | y: rnd(hexasphere.tiles[i].centerPoint.y), 73 | z: rnd(hexasphere.tiles[i].centerPoint.z) 74 | }; 75 | */ 76 | 77 | var tile = {lat: rnd(latLon.lat), lon: rnd(latLon.lon), b: [] }; 78 | var scale = size - Math.random() * .25; 79 | 80 | for(var j = 0; j< hexasphere.tiles[i].boundary.length; j++){ 81 | var newPoint = hexasphere.tiles[i].boundary[j].segment(hexasphere.tiles[i].centerPoint, size); 82 | tile.b.push({ 83 | x: rnd(newPoint.x), 84 | y: rnd(newPoint.y), 85 | z: rnd(newPoint.z) 86 | }); 87 | } 88 | 89 | o.tiles.push(tile); 90 | } 91 | } 92 | 93 | var output = "var grid = " + JSON.stringify(o) + ";"; 94 | 95 | if(argv.o){ 96 | var fs = require('fs'); 97 | 98 | fs.writeFile(argv.o, output); 99 | 100 | } else { 101 | console.log(output); 102 | } 103 | 104 | }); 105 | 106 | 107 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "encom-globe", 3 | "main": "encom-globe.js", 4 | "homepage": "https://github.com/arscan/encom-globe", 5 | "authors": [ 6 | "Rob Scanlon " 7 | ], 8 | "description": "WebGL globe based on the boardroom scene from Tron: Legacy", 9 | "keywords": [ 10 | "webgl", 11 | "tron", 12 | "globe", 13 | "geo", 14 | "visualization" 15 | ], 16 | "license": "MIT", 17 | "ignore": [ 18 | "**/.*", 19 | "bin", 20 | "include", 21 | "resources", 22 | "src", 23 | "browserify.js", 24 | "Gruntfile.js", 25 | "index.html", 26 | "package.json", 27 | "screenshot.jpg", 28 | "styles.css" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /browserify.js: -------------------------------------------------------------------------------- 1 | window.ENCOM = (window.ENCOM || {}); 2 | window.ENCOM.Globe = require('./src/Globe.js'); 3 | 4 | -------------------------------------------------------------------------------- /data.js: -------------------------------------------------------------------------------- 1 | var data = [ 2 | {lat: 69.59999999999997,lng:136.20000000000027,label:""}, 3 | {lat: 64.49999999999996,lng:-125.59999999999991,label:""}, 4 | {lat: 64.49999999999996,lng:95.4000000000002,label:""}, 5 | {lat: 62.799999999999955,lng:-47.399999999999814,label:""}, 6 | {lat: 61.09999999999995,lng:139.60000000000028,label:""}, 7 | {lat: 40.69999999999992,lng:119.20000000000024,label:""}, 8 | {lat: 32.1999999999999,lng:13.800000000000175,label:""}, 9 | {lat: 32.1999999999999,lng:88.6000000000002,label:""}, 10 | {lat: 25.399999999999906,lng:7.000000000000174,label:""}, 11 | {lat: 10.099999999999913,lng:10.400000000000174,label:""}, 12 | {lat: 8.399999999999913,lng:-3.1999999999998257,label:""}, 13 | {lat: 8.399999999999913,lng:3.600000000000174,label:""}, 14 | {lat: -0.10000000000008713,lng:102.20000000000022,label:""}, 15 | {lat: -6.900000000000087,lng:37.600000000000165,label:""}, 16 | {lat: -12.000000000000085,lng:20.600000000000172,label:""}, 17 | {lat: -27.30000000000008,lng:20.600000000000172,label:""}, 18 | {lat: -27.30000000000008,lng:30.800000000000168,label:""}, 19 | {lat: 60.24999999999996,lng:15.500000000000158,label:""}, 20 | {lat: 58.549999999999955,lng:-127.29999999999993,label:""}, 21 | {lat: 58.549999999999955,lng:93.70000000000019,label:""}, 22 | {lat: 56.84999999999995,lng:90.30000000000018,label:""}, 23 | {lat: 50.04999999999994,lng:-106.89999999999989,label:""}, 24 | {lat: 48.34999999999994,lng:-66.09999999999982,label:""}, 25 | {lat: 46.649999999999935,lng:90.30000000000018,label:""}, 26 | {lat: 44.94999999999993,lng:-79.69999999999985,label:""}, 27 | {lat: 39.84999999999992,lng:117.50000000000023,label:""}, 28 | {lat: 34.749999999999915,lng:-120.49999999999991,label:""}, 29 | {lat: 34.749999999999915,lng:-1.4999999999998423,label:""}, 30 | {lat: 16.04999999999992,lng:80.10000000000016,label:""}, 31 | {lat: -6.050000000000079,lng:-62.699999999999825,label:""}, 32 | {lat: -26.450000000000074,lng:124.30000000000024,label:""}, 33 | {lat: -26.450000000000074,lng:151.50000000000028,label:""}, 34 | {lat: -33.25000000000007,lng:-69.49999999999983,label:""}, 35 | {lat: -45.15000000000009,lng:168.5000000000003,label:""}, 36 | {lat: 79.79999999999998,lng:-26.999999999999822,label:""}, 37 | {lat: 74.69999999999997,lng:98.80000000000021,label:""}, 38 | {lat: 72.99999999999997,lng:-122.1999999999999,label:""}, 39 | {lat: 64.49999999999996,lng:-122.1999999999999,label:""}, 40 | {lat: 50.899999999999935,lng:-98.39999999999986,label:""}, 41 | {lat: 49.19999999999993,lng:119.20000000000024,label:""}, 42 | {lat: 47.49999999999993,lng:27.40000000000017,label:""}, 43 | {lat: 44.09999999999992,lng:-115.39999999999989,label:""}, 44 | {lat: 32.1999999999999,lng:54.60000000000016,label:""}, 45 | {lat: 30.499999999999904,lng:-94.99999999999986,label:""}, 46 | {lat: 28.799999999999905,lng:119.20000000000024,label:""}, 47 | {lat: 21.999999999999908,lng:10.400000000000174,label:""}, 48 | {lat: 18.59999999999991,lng:-71.19999999999982,label:""}, 49 | {lat: 15.19999999999991,lng:-3.1999999999998257,label:""}, 50 | {lat: 13.499999999999911,lng:105.60000000000022,label:""}, 51 | {lat: 8.399999999999913,lng:-71.19999999999982,label:""}, 52 | {lat: -0.10000000000008713,lng:-54.19999999999981,label:""}, 53 | {lat: -13.700000000000085,lng:-64.3999999999998,label:""}, 54 | {lat: -13.700000000000085,lng:30.800000000000168,label:""}, 55 | {lat: -18.800000000000082,lng:47.80000000000016,label:""}, 56 | {lat: -22.20000000000008,lng:27.40000000000017,label:""}, 57 | {lat: 72.14999999999998,lng:52.90000000000015,label:""}, 58 | {lat: 68.74999999999997,lng:-103.49999999999989,label:""}, 59 | {lat: 58.549999999999955,lng:52.90000000000015,label:""}, 60 | {lat: 56.84999999999995,lng:-62.699999999999825,label:""}, 61 | {lat: 50.04999999999994,lng:25.700000000000156,label:""}, 62 | {lat: 46.649999999999935,lng:22.300000000000157,label:""}, 63 | {lat: 46.649999999999935,lng:83.50000000000017,label:""}, 64 | {lat: 36.44999999999992,lng:-96.69999999999987,label:""}, 65 | {lat: 33.04999999999991,lng:117.50000000000023,label:""}, 66 | {lat: 27.949999999999914,lng:103.9000000000002,label:""}, 67 | {lat: 19.449999999999918,lng:-89.89999999999986,label:""}, 68 | {lat: 19.449999999999918,lng:42.70000000000015,label:""}, 69 | {lat: 5.8499999999999215,lng:-69.49999999999983,label:""}, 70 | {lat: -14.550000000000077,lng:-59.29999999999983,label:""}, 71 | {lat: -19.650000000000077,lng:124.30000000000024,label:""}, 72 | {lat: -89.35000000000016,lng:-35.49999999999984,label:""}, 73 | 74 | {lat:42.35843,lng:-71.05977, label: "Boston"}, 75 | {lat:25.77427,lng:-80.19366, label: "Miami"}, 76 | {lat:37.77493,lng:-122.41942, label: "San Francisco"}, 77 | {lat:-23.5475,lng:-46.63611, label: "Sao Paulo"}, 78 | {lat:-12.04318,lng:-77.02824, label: "Lima"}, 79 | {lat:21.30694,lng:-157.85833, label: "Honolulu"}, 80 | {lat:-31.95224,lng:115.8614, label: "Perth"}, 81 | {lat:-33.86785,lng:151.20732, label: "Sydney"}, 82 | {lat: -42, lng: 174, label: "New Zealand"}, 83 | {lat:22.28552,lng:114.15769, label: "Hong Kong"}, 84 | {lat:19.07283,lng:72.88261, label: "Mumbai"}, 85 | {lat:30.06263,lng:31.24967, label:"Cairo"}, 86 | {lat:-33.92584,lng:18.42322, label:"Cape Town"}, 87 | {lat:52.52437,lng:13.41053, label:"Berlin"}, 88 | {lat:55.95206,lng:-3.19648, label:"Edinburgh"}, 89 | {lat:55.75222,lng:37.61556, label:"Moscow"}, 90 | 91 | 92 | ] 93 | -------------------------------------------------------------------------------- /include/Detector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author arscan 3 | * @author alteredq / http://alteredqualia.com/ 4 | * @author mr.doob / http://mrdoob.com/ 5 | */ 6 | 7 | var Detector = { 8 | 9 | canvas: !! window.CanvasRenderingContext2D, 10 | webgl: ( function () { try { var canvas = document.createElement( 'canvas' ); return !! window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ); } catch( e ) { return false; } } )(), 11 | workers: !! window.Worker, 12 | fileapi: window.File && window.FileReader && window.FileList && window.Blob, 13 | 14 | getWebGLErrorMessage: function () { 15 | 16 | var element = document.createElement( 'div' ); 17 | element.id = 'webgl-error-message'; 18 | element.style.fontSize = '13px'; 19 | element.style.textAlign = 'center'; 20 | element.style.color = '#fff'; 21 | element.style.padding = '1.5em'; 22 | element.style.width = '400px'; 23 | element.style.margin = '5em auto 0'; 24 | 25 | if (!this.webgl ) { 26 | 27 | element.innerHTML = window.WebGLRenderingContext ? [ 28 | 'Your graphics card does not seem to support WebGL.
', 29 | 'Find out how to get it here.
', 30 | 'Check out the repo on Github to see what this looks like.' 31 | ].join( '\n' ) : [ 32 | 'Your browser does not seem to support WebGL.
', 33 | 'Find out how to get it here.
', 34 | 'Check out the repo on Github to see what this looks like.' 35 | ].join( '\n' ); 36 | 37 | } 38 | 39 | return element; 40 | 41 | }, 42 | 43 | addGetWebGLMessage: function ( parameters ) { 44 | 45 | var parent, id, element; 46 | 47 | parameters = parameters || {}; 48 | 49 | parent = parameters.parent !== undefined ? parameters.parent : document.body; 50 | id = parameters.id !== undefined ? parameters.id : 'oldie'; 51 | 52 | element = Detector.getWebGLErrorMessage(); 53 | element.id = id; 54 | 55 | if(!this.webgl){ 56 | parent.appendChild( element ); 57 | } 58 | 59 | } 60 | 61 | }; 62 | -------------------------------------------------------------------------------- /include/jquery.switchButton.css: -------------------------------------------------------------------------------- 1 | .switch-button-label { 2 | float: left; 3 | 4 | font-size: 10pt; 5 | cursor: pointer; 6 | } 7 | 8 | .switch-button-label.off { 9 | color: #adadad; 10 | } 11 | 12 | .switch-button-label.on { 13 | color: #0088CC; 14 | } 15 | 16 | .switch-button-background { 17 | float: left; 18 | position: relative; 19 | 20 | background: #ccc; 21 | border: 1px solid #aaa; 22 | 23 | margin: 1px 10px; 24 | 25 | -webkit-border-radius: 4px; 26 | -moz-border-radius: 4px; 27 | border-radius: 4px; 28 | 29 | cursor: pointer; 30 | } 31 | 32 | .switch-button-button { 33 | position: absolute; 34 | 35 | left: -1px; 36 | top : -1px; 37 | 38 | background: #FAFAFA; 39 | border: 1px solid #aaa; 40 | 41 | -webkit-border-radius: 4px; 42 | -moz-border-radius: 4px; 43 | border-radius: 4px; 44 | } 45 | -------------------------------------------------------------------------------- /include/jquery.switchButton.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jquery.switchButton.js v1.0 3 | * jQuery iPhone-like switch button 4 | * @author Olivier Lance 5 | * 6 | * Copyright (c) Olivier Lance - released under MIT License {{{ 7 | * 8 | * Permission is hereby granted, free of charge, to any person 9 | * obtaining a copy of this software and associated documentation 10 | * files (the "Software"), to deal in the Software without 11 | * restriction, including without limitation the rights to use, 12 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the 14 | * Software is furnished to do so, subject to the following 15 | * conditions: 16 | 17 | * The above copyright notice and this permission notice shall be 18 | * included in all copies or substantial portions of the Software. 19 | 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | * OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | * }}} 30 | */ 31 | 32 | /* 33 | * Meant to be used on a , this widget will replace the receiver element with an iPhone-style 34 | * switch button with two states: "on" and "off". 35 | * Labels of the states are customizable, as are their presence and position. The receiver element's "checked" attribute 36 | * is updated according to the state of the switch, so that it can be used in a
. 37 | * 38 | */ 39 | 40 | (function($) { 41 | 42 | $.widget("sylightsUI.switchButton", { 43 | 44 | options: { 45 | checked: undefined, // State of the switch 46 | 47 | show_labels: true, // Should we show the on and off labels? 48 | labels_placement: "both", // Position of the labels: "both", "left" or "right" 49 | on_label: "ON", // Text to be displayed when checked 50 | off_label: "OFF", // Text to be displayed when unchecked 51 | 52 | width: 25, // Width of the button in pixels 53 | height: 11, // Height of the button in pixels 54 | button_width: 12, // Width of the sliding part in pixels 55 | 56 | clear: true, // Should we insert a div with style="clear: both;" after the switch button? 57 | clear_after: null, // Override the element after which the clearing div should be inserted (null > right after the button) 58 | on_callback: undefined, //callback function that will be executed after going to on state 59 | off_callback: undefined //callback function that will be executed after going to off state 60 | }, 61 | 62 | _create: function() { 63 | // Init the switch from the checkbox if no state was specified on creation 64 | if (this.options.checked === undefined) { 65 | this.options.checked = this.element.prop("checked"); 66 | } 67 | 68 | this._initLayout(); 69 | this._initEvents(); 70 | }, 71 | 72 | _initLayout: function() { 73 | // Hide the receiver element 74 | this.element.hide(); 75 | 76 | // Create our objects: two labels and the button 77 | this.off_label = $("").addClass("switch-button-label"); 78 | this.on_label = $("").addClass("switch-button-label"); 79 | 80 | this.button_bg = $("
").addClass("switch-button-background"); 81 | this.button = $("
").addClass("switch-button-button"); 82 | 83 | // Insert the objects into the DOM 84 | this.off_label.insertAfter(this.element); 85 | this.button_bg.insertAfter(this.off_label); 86 | this.on_label.insertAfter(this.button_bg); 87 | 88 | this.button_bg.append(this.button); 89 | 90 | // Insert a clearing element after the specified element if needed 91 | if(this.options.clear) 92 | { 93 | if (this.options.clear_after === null) { 94 | this.options.clear_after = this.on_label; 95 | } 96 | $("
").css({ 97 | clear: "left" 98 | }).insertAfter(this.options.clear_after); 99 | } 100 | 101 | // Call refresh to update labels text and visibility 102 | this._refresh(); 103 | 104 | // Init labels and switch state 105 | // This will animate all checked switches to the ON position when 106 | // loading... this is intentional! 107 | this.options.checked = !this.options.checked; 108 | this._toggleSwitch(); 109 | }, 110 | 111 | _refresh: function() { 112 | // Refresh labels display 113 | if (this.options.show_labels) { 114 | this.off_label.show(); 115 | this.on_label.show(); 116 | } 117 | else { 118 | this.off_label.hide(); 119 | this.on_label.hide(); 120 | } 121 | 122 | // Move labels around depending on labels_placement option 123 | switch(this.options.labels_placement) { 124 | case "both": 125 | { 126 | // Don't move anything if labels are already in place 127 | if(this.button_bg.prev() !== this.off_label || this.button_bg.next() !== this.on_label) 128 | { 129 | // Detach labels form DOM and place them correctly 130 | this.off_label.detach(); 131 | this.on_label.detach(); 132 | this.off_label.insertBefore(this.button_bg); 133 | this.on_label.insertAfter(this.button_bg); 134 | 135 | // Update label classes 136 | this.on_label.addClass(this.options.checked ? "on" : "off").removeClass(this.options.checked ? "off" : "on"); 137 | this.off_label.addClass(this.options.checked ? "off" : "on").removeClass(this.options.checked ? "on" : "off"); 138 | 139 | } 140 | break; 141 | } 142 | 143 | case "left": 144 | { 145 | // Don't move anything if labels are already in place 146 | if(this.button_bg.prev() !== this.on_label || this.on_label.prev() !== this.off_label) 147 | { 148 | // Detach labels form DOM and place them correctly 149 | this.off_label.detach(); 150 | this.on_label.detach(); 151 | this.off_label.insertBefore(this.button_bg); 152 | this.on_label.insertBefore(this.button_bg); 153 | 154 | // update label classes 155 | this.on_label.addClass("on").removeClass("off"); 156 | this.off_label.addClass("off").removeClass("on"); 157 | } 158 | break; 159 | } 160 | 161 | case "right": 162 | { 163 | // Don't move anything if labels are already in place 164 | if(this.button_bg.next() !== this.off_label || this.off_label.next() !== this.on_label) 165 | { 166 | // Detach labels form DOM and place them correctly 167 | this.off_label.detach(); 168 | this.on_label.detach(); 169 | this.off_label.insertAfter(this.button_bg); 170 | this.on_label.insertAfter(this.off_label); 171 | 172 | // update label classes 173 | this.on_label.addClass("on").removeClass("off"); 174 | this.off_label.addClass("off").removeClass("on"); 175 | } 176 | break; 177 | } 178 | 179 | } 180 | 181 | // Refresh labels texts 182 | this.on_label.html(this.options.on_label); 183 | this.off_label.html(this.options.off_label); 184 | 185 | // Refresh button's dimensions 186 | this.button_bg.width(this.options.width); 187 | this.button_bg.height(this.options.height); 188 | this.button.width(this.options.button_width); 189 | this.button.height(this.options.height); 190 | }, 191 | 192 | _initEvents: function() { 193 | var self = this; 194 | 195 | // Toggle switch when the switch is clicked 196 | this.button_bg.click(function(e) { 197 | e.preventDefault(); 198 | e.stopPropagation(); 199 | self._toggleSwitch(); 200 | return false; 201 | }); 202 | this.button.click(function(e) { 203 | e.preventDefault(); 204 | e.stopPropagation(); 205 | self._toggleSwitch(); 206 | return false; 207 | }); 208 | 209 | // Set switch value when clicking labels 210 | this.on_label.click(function(e) { 211 | if (self.options.checked && self.options.labels_placement === "both") { 212 | return false; 213 | } 214 | 215 | self._toggleSwitch(); 216 | return false; 217 | }); 218 | 219 | this.off_label.click(function(e) { 220 | if (!self.options.checked && self.options.labels_placement === "both") { 221 | return false; 222 | } 223 | 224 | self._toggleSwitch(); 225 | return false; 226 | }); 227 | 228 | }, 229 | 230 | _setOption: function(key, value) { 231 | if (key === "checked") { 232 | this._setChecked(value); 233 | return; 234 | } 235 | 236 | this.options[key] = value; 237 | this._refresh(); 238 | }, 239 | 240 | _setChecked: function(value) { 241 | if (value === this.options.checked) { 242 | return; 243 | } 244 | 245 | this.options.checked = !value; 246 | this._toggleSwitch(); 247 | }, 248 | 249 | _toggleSwitch: function() { 250 | this.options.checked = !this.options.checked; 251 | var newLeft = ""; 252 | if (this.options.checked) { 253 | // Update the underlying checkbox state 254 | this.element.prop("checked", true); 255 | this.element.change(); 256 | 257 | var dLeft = this.options.width - this.options.button_width; 258 | newLeft = "+=" + dLeft; 259 | 260 | // Update labels states 261 | if(this.options.labels_placement == "both") 262 | { 263 | this.off_label.removeClass("on").addClass("off"); 264 | this.on_label.removeClass("off").addClass("on"); 265 | } 266 | else 267 | { 268 | this.off_label.hide(); 269 | this.on_label.show(); 270 | } 271 | this.button_bg.addClass("checked"); 272 | //execute on state callback if its supplied 273 | if(typeof this.options.on_callback === 'function') this.options.on_callback.call(this); 274 | } 275 | else { 276 | // Update the underlying checkbox state 277 | this.element.prop("checked", false); 278 | this.element.change(); 279 | newLeft = "-1px"; 280 | 281 | // Update labels states 282 | if(this.options.labels_placement == "both") 283 | { 284 | this.off_label.removeClass("off").addClass("on"); 285 | this.on_label.removeClass("on").addClass("off"); 286 | } 287 | else 288 | { 289 | this.off_label.show(); 290 | this.on_label.hide(); 291 | } 292 | this.button_bg.removeClass("checked"); 293 | //execute off state callback if its supplied 294 | if(typeof this.options.off_callback === 'function') this.options.off_callback.call(this); 295 | } 296 | // Animate the switch 297 | this.button.animate({ left: newLeft }, 250, "easeInOutCubic"); 298 | } 299 | 300 | }); 301 | 302 | })(jQuery); 303 | -------------------------------------------------------------------------------- /include/pusher.color.js: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------------------- 2 | pusher.color.js 3 | A color parsing and manipulation library 4 | ---------------------------------------------------------------------------- 5 | The MIT License (MIT). Copyright (c) 2013, Pusher Inc. 6 | */ 7 | (function(){function normalize360(v){v=v%360;return v<0?360+v:v}function unsigned(i){return i>>>0}function trimLc(s){return s.replace(/^\s+/,"").replace(/\s+$/,"").toLowerCase()}function slice(obj,index){return Array.prototype.slice.call(obj,index)}function append(arr,value){arr.push(value);return arr}function clamp(x,a,b){return!(x>a)?a:!(x0))return 0;else if(!(f<255))return 255;else return f&255}function b2f(b){return b/255}function rgbToHsl(r,g,b){var max=Math.max(r,g,b),min=Math.min(r,g,b);var h,s,l=(max+min)/2;if(max==min){h=s=0}else{var d=max-min;s=l>.5?d/(2-max-min):d/(max+min);switch(max){case r:h=(g-b)/d+(g1)t-=1;if(t<1/6)return p+(q-p)*6*t;if(t<1/2)return q;if(t<2/3)return p+(q-p)*(2/3-t)*6;return p}var q=l<.5?l*(1+s):l+s-l*s;var p=2*l-q;r=hue2rgb(p,q,h+1/3);g=hue2rgb(p,q,h);b=hue2rgb(p,q,h-1/3)}return[r,g,b]}function hex4ToRgba(color){var rgba=[parseInt(color.substr(1,1),16),parseInt(color.substr(2,1),16),parseInt(color.substr(3,1),16),1];for(var i=0;i<3;++i)rgba[i]=(rgba[i]*16+rgba[i])/255;return rgba}function hex7ToRgba(color){return[parseInt(color.substr(1,2),16)/255,parseInt(color.substr(3,2),16)/255,parseInt(color.substr(5,2),16)/255,1]}var namedColors={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,216],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[216,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]};function rgbaToHsva(rgba){var r=rgba[0];var g=rgba[1];var b=rgba[2];var min=Math.min(Math.min(r,g),b),max=Math.max(Math.max(r,g),b),delta=max-min;var value=max;var saturation,hue;if(max==min){hue=0}else if(max==r){hue=60*((g-b)/(max-min))%360}else if(max==g){hue=60*((b-r)/(max-min))+120}else if(max==b){hue=60*((r-g)/(max-min))+240}if(hue<0){hue+=360}if(max==0){saturation=0}else{saturation=1-min/max}return[Math.round(hue),Math.round(saturation*100),Math.round(value*100),rgba[3]]}function hsvaToRgba(hsva){var h=normalize360(hsva[0]);var s=hsva[1];var v=hsva[2];var s=s/100;var v=v/100;var hi=Math.floor(h/60%6);var f=h/60-hi;var p=v*(1-s);var q=v*(1-f*s);var t=v*(1-(1-f)*s);var rgb=[];switch(hi){case 0:rgb=[v,t,p];break;case 1:rgb=[q,v,p];break;case 2:rgb=[p,v,t];break;case 3:rgb=[p,q,v];break;case 4:rgb=[t,p,v];break;case 5:rgb=[v,p,q];break}return[rgb[0],rgb[1],rgb[2],hsva[3]]}function rgbaToHsl(c){var hsl=rgbToHsl(c[0],c[1],c[2]);hsl[0]=normalize360(Math.floor(hsl[0]*360));hsl[1]=Math.floor(hsl[1]*100);hsl[2]=Math.floor(hsl[2]*100);return hsl}function rgbaToHsla(c){var hsl=rgbaToHsl(c);hsl.push(c[3]);return hsl}function hslToRgba(c){var h=parseFloat(c[0])/360;var s=parseFloat(c[1])/100;var l=parseFloat(c[2])/100;var rgb=hslToRgb(h,s,l);return[rgb[0],rgb[1],rgb[2],1]}function hslaToRgba(c){var h=parseFloat(c[0])/360;var s=parseFloat(c[1])/100;var l=parseFloat(c[2])/100;var rgb=hslToRgb(h,s,l);return[rgb[0],rgb[1],rgb[2],parseFloat(c[3])]}var parse={byteOrPercent:function(s){var m;if(typeof s=="string"&&(m=s.match(/^([0-9]+)%$/)))return Math.floor(parseFloat(m[1])*255/100);else return parseFloat(s)},floatOrPercent:function(s){var m;if(typeof s=="string"&&(m=s.match(/^([0-9]+)%$/)))return parseFloat(m[1])/100;else return parseFloat(s)},numberOrPercent:function(s,scale){var m;if(typeof s=="string"&&(m=s.match(/^([0-9]+)%$/)))return parseFloat(m[1])/100*scale;else return parseFloat(s)},rgba:function(v){for(var i=0;i<3;++i)v[i]=b2f(parse.byteOrPercent(v[i]));v[3]=parse.floatOrPercent(v[i]);return new Color(v)},rgba8:function(v){return new Color([b2f(parse.byteOrPercent(v[0])),b2f(parse.byteOrPercent(v[1])),b2f(parse.byteOrPercent(v[2])),b2f(parse.byteOrPercent(v[3]))])},float3:function(v){for(var i=0;i<3;++i)v[i]=parse.floatOrPercent(v[i]);v[3]=1;return new Color(v)},float4:function(v){for(var i=0;i<3;++i)v[i]=parse.floatOrPercent(v[i]);v[3]=parse.floatOrPercent(v[i]);return new Color(v)},hsla:function(v){v[0]=parse.numberOrPercent(v[0],360);v[1]=parse.numberOrPercent(v[1],100);v[2]=parse.numberOrPercent(v[2],100);v[3]=parse.numberOrPercent(v[3],1);return new Color(hslaToRgba(v))},hsva:function(v){v[0]=normalize360(parseFloat(v[0]));v[1]=Math.max(0,Math.min(100,parseFloat(v[1])));v[2]=Math.max(0,Math.min(100,parseFloat(v[2])));v[3]=parse.floatOrPercent(v[3]);return new Color(hsvaToRgba(v))}};var supportedFormats={keyword:{},hex3:{},hex7:{},rgb:{parse:function(v){v=v.slice(0);v.push(1);return parse.rgba(v)}},rgba:{parse:parse.rgba},hsl:{parse:function(v){v=v.slice(0);v.push(1);return parse.hsla(v)}},hsla:{parse:parse.hsla},hsv:{parse:function(v){v=v.slice(0);v.push(1);return parse.hsva(v)}},hsva:{parse:parse.hsva},rgb8:{parse:function(v){v=v.slice(0);v.push(1);return parse.rgba(v)}},rgba8:{parse:function(v){return parse.rgba8(v)}},packed_rgba:{parse:function(v){v=[v>>24&255,v>>16&255,v>>8&255,(v&255)/255];return parse.rgba(v)},output:function(v){return unsigned(f2b(v[0])<<24|f2b(v[1])<<16|f2b(v[2])<<8|f2b(v[3]))}},packed_argb:{parse:function(v){v=[v>>16&255,v>>8&255,v>>0&255,(v>>24&255)/255];return parse.rgba(v)},output:function(v){return unsigned(f2b(v[3])<<24|f2b(v[0])<<16|f2b(v[1])<<8|f2b(v[2]))}},float3:{parse:parse.float3},float4:{parse:parse.float4}};function Color(value){this._value=value}var color=function(){var match=null;if(arguments[0]instanceof Color){return new Color(arguments[0]._value)}else if(typeof arguments[0]=="string"){var first=arguments[0][0];if(first=="#"){if(match=arguments[0].match(/^#([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])$/i)){return new Color(hex4ToRgba(match[0]))}else if(match=arguments[0].match(/^#([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])$/i)){return new Color(hex7ToRgba(match[0]))}}else if(match=supportedFormats[arguments[0].toLowerCase()]){if(arguments.length==2)return match.parse(arguments[1]);else return match.parse(slice(arguments,1))}else if(match=arguments[0].match(/^\s*([A-Z][A-Z0-9_]+)\s*\(\s*([\-0-9A-FX]+)\s*\)\s*$/i)){var format=supportedFormats[match[1].toLowerCase()];return format.parse(match[2])}else if(match=arguments[0].match(/^\s*([A-Z][A-Z0-9]+)\s*\(\s*([0-9\.]+%?)\s*,\s*([0-9\.]+%?)\s*,\s*([0-9\.]+%?)\s*(,\s*([0-9\.]+%?)\s*)?\)\s*$/i)){var format=supportedFormats[match[1].toLowerCase()];if(match[5]===undefined){var v=[match[2],match[3],match[4]];return format.parse(v)}else{var v=[match[2],match[3],match[4],match[6]];return format.parse(v)}}else if(arguments.length==1&&(match=namedColors[trimLc(arguments[0])])){var v=match;return new Color([b2f(v[0]),b2f(v[1]),b2f(v[2]),1])}}throw"Could not parse color '"+arguments[0]+"'"};var fixed={white:color("white"),black:color("black"),gray:color("gray")};function modifyComponent(index,arg){if(arg==undefined)return f2b(this._value[index]);var v=slice(this._value,0);if(typeof arg=="string"){var m;if(m=arg.match(/^([+\-\\*]=?)([0-9.]+)/)){var op=m[1];var offset=parseFloat(m[2]);switch(op[0]){case"+":v[index]+=offset/255;break;case"-":v[index]-=offset/255;break;case"*":v[index]*=offset;break}if(op[1]=="="){this._value=v;return this}else return new Color(v)}}else{var clone=this.clone();clone._value[index]=arg;return clone}}function modifyHsva(i){return function(){function change(obj,op,value){value=parseFloat(value);var hsva=rgbaToHsva(obj._value);var c=0;switch(op){case"=":hsva[i]=value;c=1;break;case"+":hsva[i]+=value;c=1;break;case"+=":hsva[i]+=value;break;case"-":hsva[i]-=value;c=1;break;case"-=":hsva[i]-=value;break;case"*":hsva[i]*=value;c=1;break;case"*=":hsva[i]*=value;break;default:throw"Bad op "+op}if(i==0)hsva[i]=normalize360(hsva[i]);else if(i==1||i==2){if(hsva[i]<0)hsva[i]=0;else if(hsva[i]>99)hsva[i]=99}if(c)obj=obj.clone();obj._value=hsvaToRgba(hsva);return obj}if(arguments.length==0)return rgbaToHsva(this._value)[i];else if(arguments.length==1){var m;if(typeof arguments[0]=="string"&&(m=arguments[0].match(/^([\+\-\*]=?)([0-9.]+)/)))return change(this,m[1],m[2]);else return change(this,"=",arguments[0])}else if(arguments.length==2)return change(this,arguments[0],arguments[1])}}var methods={clone:function(){return new Color(this._value.slice(0))},html:function(){var self=this;var v=this._value;var _fmt={hex3:function(){return self.hex3()},hex6:function(){return self.hex6()},rgb:function(){return"rgb("+self.rgb().join(",")+")"},rgba:function(){return"rgba("+self.rgba().join(",")+")"},hsl:function(){return"hsl("+rgbaToHsl(v).join(",")+")"},hsla:function(){return"hsla("+rgbaToHsla(v).join(",")+")"},keyword:function(){var dist=3*255*255+1;var keyword;for(name in namedColors){var c=namedColors[name];var d=0;for(var i=0;i<3;++i){var t=v[i]-b2f(c[i]);d+=t*t}if(d=30;s-=35)for(var v=100;v>=30;v-=35)set.push(this.hue("+",h).saturation(s).value(v));return set},hueRange:function(range,count){var base=this.hue();var set=[];for(var i=0;i= 0; b--) this.observers[b](this); 51 | return this; 52 | }, 53 | ignore: function(a) { 54 | if (this.observers) for (var b = this.observers, c = b.length; c--; ) b[c] === a && b.splice(c, 1); 55 | return this; 56 | }, 57 | set: function(a, b, c) { 58 | return "number" != typeof a && (c = b, b = a.y, a = a.x), this.x === a && this.y === b ? this : (this.x = f.clean(a), 59 | this.y = f.clean(b), c !== !1 ? this.change() : void 0); 60 | }, 61 | zero: function() { 62 | return this.set(0, 0); 63 | }, 64 | clone: function() { 65 | return new this.constructor(this.x, this.y); 66 | }, 67 | negate: function(a) { 68 | return a ? new this.constructor(-this.x, -this.y) : this.set(-this.x, -this.y); 69 | }, 70 | add: function(a, b) { 71 | return b ? new this.constructor(this.x + a.x, this.y + a.y) : (this.x += a.x, this.y += a.y, this.change()); 72 | }, 73 | subtract: function(a, b) { 74 | return b ? new this.constructor(this.x - a.x, this.y - a.y) : (this.x -= a.x, this.y -= a.y, this.change()); 75 | }, 76 | multiply: function(a, b) { 77 | var c, d; 78 | return "number" != typeof a ? (c = a.x, d = a.y) : c = d = a, b ? new this.constructor(this.x * c, this.y * d) : this.set(this.x * c, this.y * d); 79 | }, 80 | rotate: function(a, b, c) { 81 | var d, e, f = this.x, g = this.y, h = Math.cos(a), i = Math.sin(a); 82 | return b = b ? -1 : 1, d = h * f - b * i * g, e = b * i * f + h * g, c ? new this.constructor(d, e) : this.set(d, e); 83 | }, 84 | length: function() { 85 | var a = this.x, b = this.y; 86 | return Math.sqrt(a * a + b * b); 87 | }, 88 | lengthSquared: function() { 89 | var a = this.x, b = this.y; 90 | return a * a + b * b; 91 | }, 92 | distance: function(a) { 93 | var b = this.x - a.x, c = this.y - a.y; 94 | return Math.sqrt(b * b + c * c); 95 | }, 96 | normalize: function(a) { 97 | var b = this.length(), c = b < Number.MIN_VALUE ? 0 : 1 / b; 98 | return a ? new this.constructor(this.x * c, this.y * c) : this.set(this.x * c, this.y * c); 99 | }, 100 | equal: function(a, b) { 101 | return b === e && (b = a.y, a = a.x), f.clean(a) === this.x && f.clean(b) === this.y; 102 | }, 103 | abs: function(a) { 104 | var b = Math.abs(this.x), c = Math.abs(this.y); 105 | return a ? new this.constructor(b, c) : this.set(b, c); 106 | }, 107 | min: function(a, b) { 108 | var c = this.x, d = this.y, e = a.x, f = a.y, g = e > c ? c : e, h = f > d ? d : f; 109 | return b ? new this.constructor(g, h) : this.set(g, h); 110 | }, 111 | max: function(a, b) { 112 | var c = this.x, d = this.y, e = a.x, f = a.y, g = c > e ? c : e, h = d > f ? d : f; 113 | return b ? new this.constructor(g, h) : this.set(g, h); 114 | }, 115 | clamp: function(a, b, c) { 116 | var d = this.min(b, !0).max(a); 117 | return c ? d : this.set(d.x, d.y); 118 | }, 119 | lerp: function(a, b) { 120 | return this.add(a.subtract(this, !0).multiply(b), !0); 121 | }, 122 | skew: function() { 123 | return new this.constructor(-this.y, this.x); 124 | }, 125 | dot: function(a) { 126 | return f.clean(this.x * a.x + a.y * this.y); 127 | }, 128 | perpDot: function(a) { 129 | return f.clean(this.x * a.y - this.y * a.x); 130 | }, 131 | angleTo: function(a) { 132 | return Math.atan2(this.perpDot(a), this.dot(a)); 133 | }, 134 | divide: function(a, b) { 135 | var c, d; 136 | if ("number" != typeof a ? (c = a.x, d = a.y) : c = d = a, 0 === c || 0 === d) throw new Error("division by zero"); 137 | if (isNaN(c) || isNaN(d)) throw new Error("NaN detected"); 138 | return b ? new this.constructor(this.x / c, this.y / d) : this.set(this.x / c, this.y / d); 139 | }, 140 | isPointOnLine: function(a, b) { 141 | return (a.y - this.y) * (a.x - b.x) === (a.y - b.y) * (a.x - this.x); 142 | }, 143 | toArray: function() { 144 | return [ this.x, this.y ]; 145 | }, 146 | fromArray: function(a) { 147 | return this.set(a[0], a[1]); 148 | }, 149 | toJSON: function() { 150 | return { 151 | x: this.x, 152 | y: this.y 153 | }; 154 | }, 155 | toString: function() { 156 | return "(" + this.x + ", " + this.y + ")"; 157 | }, 158 | constructor: f 159 | }, f.fromArray = function(a, b) { 160 | return new (b || f)(a[0], a[1]); 161 | }, f.precision = d || 8; 162 | var h = Math.pow(10, f.precision); 163 | return f.clean = a || function(a) { 164 | if (isNaN(a)) throw new Error("NaN detected"); 165 | if (!isFinite(a)) throw new Error("Infinity detected"); 166 | return Math.round(a) === a ? a : Math.round(a * h) / h; 167 | }, f.inject = c, a || (f.fast = c(function(a) { 168 | return a; 169 | }), "undefined" != typeof b && "object" == typeof b.exports ? b.exports = f : window.Vec2 = window.Vec2 || f), 170 | f; 171 | }(); 172 | }, {} ], 173 | 3: [ function(a, b) { 174 | var c, d = a("vec2"), e = a("./quadtree2helper"), f = a("./quadtree2validator"), g = a("./quadtree2quadrant"); 175 | c = function(a, b, c) { 176 | var h, i = { 177 | objects_: {}, 178 | quadrants_: {}, 179 | ids_: 1, 180 | quadrantIds_: 1, 181 | autoId_: !0, 182 | inited_: !1, 183 | debug_: !1, 184 | size_: void 0, 185 | root_: void 0, 186 | quadrantObjectsLimit_: void 0, 187 | quadrantLevelLimit_: void 0, 188 | quadrantSizeLimit_: void 0 189 | }, j = new f(), k = { 190 | p: "pos_", 191 | r: "rad_", 192 | id: "id_" 193 | }, l = { 194 | data: { 195 | necessary: { 196 | size_: j.isVec2, 197 | quadrantObjectsLimit_: j.isNumber, 198 | quadrantLevelLimit_: j.isNumber 199 | } 200 | }, 201 | k: { 202 | necessary: { 203 | p: j.isVec2 204 | }, 205 | c: { 206 | necessary: { 207 | r: j.isNumber 208 | } 209 | } 210 | } 211 | }, m = { 212 | nextId: function() { 213 | return i.ids_++; 214 | }, 215 | nextQuadrantId: function(a) { 216 | var b = i.quadrantIds_; 217 | return i.quadrantIds_ += a || 4, b; 218 | }, 219 | hasCollision: function(a, b) { 220 | return a[k.r] + b[k.r] > a[k.p].distance(b[k.p]); 221 | }, 222 | removeQuadrantParentQuadrants: function(a, b) { 223 | a.parent_ && b[a.parent_.id_] && (delete b[a.parent_.id_], m.removeQuadrantParentQuadrants(a.parent_, b)); 224 | }, 225 | getSubtreeTopQuadrant: function p(a, b) { 226 | return a.parent_ && b[a.parent_.id_] ? p(a.parent_, b) : a; 227 | }, 228 | removeQuadrantChildtree: function(a, b) { 229 | var c, d = a.getChildren(); 230 | for (c = 0; c < d.length; c++) { 231 | if (!b[d[c].id_]) return; 232 | delete b[d[c].id_], m.removeQuadrantChildtree(d[c], b); 233 | } 234 | }, 235 | getIntersectingQuadrants: function q(a, b, c) { 236 | var d, e; 237 | if (!b.intersects(a[k.p], a[k.r])) return void m.removeQuadrantParentQuadrants(b, c.biggest); 238 | if (c.biggest[b.id_] = b, e = b.getChildren(), e.length) for (d = 0; d < e.length; d++) q(a, e[d], c); else c.leaves[b.id_] = b; 239 | }, 240 | getSmallestIntersectingQuadrants: function(a, b, c) { 241 | var d, e; 242 | b || (b = i.root_), c || (c = { 243 | leaves: {}, 244 | biggest: {} 245 | }), m.getIntersectingQuadrants(a, b, c); 246 | for (d in c.leaves) c.biggest[d] && (e = m.getSubtreeTopQuadrant(c.leaves[d], c.biggest), m.removeQuadrantChildtree(e, c.biggest)); 247 | return c.biggest; 248 | }, 249 | removeQuadrantObjects: function(a) { 250 | var b, c = a.removeObjects([], 1); 251 | for (b = 0; b < c.length; b++) delete i.quadrants_[c[b].obj[k.id]][c[b].quadrant.id_]; 252 | return c; 253 | }, 254 | removeObjectFromQuadrants: function(a, b) { 255 | var c; 256 | void 0 === b && (b = i.quadrants_[a[k.id]]); 257 | for (c in b) m.removeObjectFromQuadrant(a, b[c]); 258 | }, 259 | removeObjectFromQuadrant: function(a, b) { 260 | b.removeObject(a[k.id]), delete i.quadrants_[a[k.id]][b.id_], !b.hasChildren() && b.parent_ && m.refactorSubtree(b.parent_); 261 | }, 262 | refactorSubtree: function(a) { 263 | var b, c, d, e, f; 264 | if (!a.refactoring_) { 265 | for (b = 0; b < a.children_.length; b++) if (e = a.children_[b], e.hasChildren()) return; 266 | if (d = a.getObjectCountForLimit(), !(d > i.quadrantObjectsLimit_)) { 267 | for (a.refactoring_ = !0, b = 0; b < a.children_.length; b++) { 268 | e = a.children_[b]; 269 | for (c in e.objects_) f = e.objects_[c], m.removeObjectFromQuadrant(f, e), m.addObjectToQuadrant(f, a); 270 | } 271 | a.looseChildren(), a.refactoring_ = !1, a.parent_ && m.refactorSubtree(a.parent_); 272 | } 273 | } 274 | }, 275 | updateObjectQuadrants: function(a) { 276 | var b, c = i.quadrants_[a[k.id]], d = m.getSmallestIntersectingQuadrants(a), f = e.getIdsOfObjects(c), g = e.getIdsOfObjects(d), h = e.arrayDiffs(f, g), j = h[0], l = h[1]; 277 | for (b = 0; b < l.length; b++) m.populateSubtree(a, d[l[b]]); 278 | for (b = 0; b < j.length; b++) c[j[b]] && m.removeObjectFromQuadrant(a, c[j[b]]); 279 | }, 280 | addObjectToQuadrant: function(a, b) { 281 | var c = a[k.id]; 282 | void 0 === i.quadrants_[c] && (i.quadrants_[c] = {}), i.quadrants_[c][b.id_] = b, b.addObject(c, a); 283 | }, 284 | populateSubtree: function(a, b) { 285 | var c, d, e, f; 286 | if (b || (b = i.root_), b.hasChildren()) { 287 | e = m.getSmallestIntersectingQuadrants(a, b); 288 | for (d in e) { 289 | if (e[d] === b) return void m.addObjectToQuadrant(a, b); 290 | m.populateSubtree(a, e[d]); 291 | } 292 | } else if (b.getObjectCount() < i.quadrantObjectsLimit_ || b.size_.x < i.quadrantSizeLimit_.x) m.addObjectToQuadrant(a, b); else for (b.makeChildren(m.nextQuadrantId()), 293 | f = m.removeQuadrantObjects(b), f.push({ 294 | obj: a, 295 | quadrant: b 296 | }), c = 0; c < f.length; c++) m.populateSubtree(f[c].obj, f[c].quadrant); 297 | }, 298 | init: function() { 299 | var a; 300 | i.quadrantLevelLimit_ || (i.quadrantLevelLimit_ = 6), j.byCallbackObject(i, l.data.necessary), i.root_ = new g(new d(0, 0), i.size_.clone(), m.nextQuadrantId(1)), 301 | a = Math.pow(2, i.quadrantLevelLimit_), i.quadrantSizeLimit_ = i.size_.clone().divide(a), i.inited_ = !0; 302 | }, 303 | checkInit: function(a) { 304 | return a && !i.inited_ && m.init(), i.inited_; 305 | }, 306 | checkObjectKeys: function(a) { 307 | j.isNumber(a[k.id], k.id), j.isNumber(a[k.r], k.r), j.hasNoKey(i.objects_, a[k.id], k.id), j.byCallbackObject(a, l.k.necessary, k); 308 | }, 309 | setObjId: function(a) { 310 | i.autoId_ && !a[k.id] && (a[k.id] = m.nextId()); 311 | }, 312 | removeObjectById: function(a) { 313 | j.hasKey(i.objects_, a, k.id), m.removeObjectFromQuadrants(i.objects_[a]), delete i.objects_[a]; 314 | }, 315 | updateObjectById: function(a) { 316 | j.hasKey(i.objects_, a, k.id), m.updateObjectQuadrants(i.objects_[a]); 317 | }, 318 | getObjectsByObject: function(a) { 319 | var b, c, d = i.quadrants_[a[k.id]], e = { 320 | objects: {}, 321 | quadrants: {} 322 | }; 323 | for (c in d) for (d[c].getObjectsUp(e), b = 0; b < d[c].children_.length; b++) d[c].children_[b].getObjectsDown(e); 324 | return delete e.objects[a[k.id]], e.objects; 325 | } 326 | }, n = { 327 | getQuadrants: function() { 328 | return i.root_.getChildren(!0, [ i.root_ ]); 329 | }, 330 | getLeafQuadrants: function() { 331 | return n.getQuadrants().filter(function(a) { 332 | return !a.hasChildren(); 333 | }); 334 | } 335 | }, o = { 336 | getQuadrantObjectsLimit: function() { 337 | return i.quadrantObjectsLimit_; 338 | }, 339 | setQuadrantObjectsLimit: function(a) { 340 | void 0 !== a && (j.isNumber(a, "quadrantObjectsLimit_"), i.quadrantObjectsLimit_ = a); 341 | }, 342 | getQuadrantLevelLimit: function() { 343 | return i.quadrantLevelLimit_; 344 | }, 345 | setQuadrantLevelLimit: function(a) { 346 | void 0 !== a && (j.isNumber(a, "quadrantLevelLimit_"), i.quadrantLevelLimit_ = a); 347 | }, 348 | setObjectKey: function(a, b) { 349 | j.fnFalse(m.checkInit), void 0 !== b && (j.hasKey(k, a, a), j.isString(b, a), "id" === a && (i.autoId_ = !1), 350 | k[a] = b); 351 | }, 352 | getSize: function() { 353 | return i.size_.clone(); 354 | }, 355 | setSize: function(a) { 356 | void 0 !== a && (j.isVec2(a, "size_"), i.size_ = a.clone()); 357 | }, 358 | addObjects: function(a) { 359 | a.forEach(function(a) { 360 | o.addObject(a); 361 | }); 362 | }, 363 | addObject: function(a) { 364 | j.isDefined(a, "obj"), j.isObject(a, "obj"), m.checkInit(!0), m.setObjId(a), m.checkObjectKeys(a), m.populateSubtree(a), 365 | i.objects_[a[k.id]] = a; 366 | }, 367 | removeObjects: function(a) { 368 | var b; 369 | for (b = 0; b < a.length; b++) o.removeObject(a[b]); 370 | }, 371 | removeObject: function(a) { 372 | m.checkInit(!0), j.hasKey(a, k.id, k.id), m.removeObjectById(a[k.id]); 373 | }, 374 | updateObjects: function(a) { 375 | var b; 376 | for (b = 0; b < a.length; b++) o.updateObject(a[b]); 377 | }, 378 | updateObject: function(a) { 379 | m.checkInit(!0), j.hasKey(a, k.id, k.id), m.updateObjectById(a[k.id]); 380 | }, 381 | getPossibleCollisionsForObject: function(a) { 382 | return m.checkInit(!0), j.hasKey(a, k.id, k.id), m.getObjectsByObject(a); 383 | }, 384 | getCollisionsForObject: function(a) { 385 | var b, c; 386 | m.checkInit(!0), j.hasKey(a, k.id, k.id), c = m.getObjectsByObject(a); 387 | for (b in c) m.hasCollision(a, c[b]) || delete c[b]; 388 | return c; 389 | }, 390 | getCount: function() { 391 | return Object.keys(i.objects_).length; 392 | }, 393 | getObjects: function() { 394 | var a, b = {}; 395 | for (a in i.objects_) b[a] = i.objects_[a]; 396 | return b; 397 | }, 398 | getQuadrantCount: function(a) { 399 | return a ? Object.keys(i.quadrants_[a[k.id]]).length : 1 + i.root_.getChildCount(!0); 400 | }, 401 | getQuadrantObjectCount: function() { 402 | return i.root_.getObjectCount(!0); 403 | }, 404 | debug: function(a) { 405 | var b; 406 | if (void 0 !== a) { 407 | i.debug_ = a, m.checkInit(!0); 408 | for (b in n) this[b] = n[b]; 409 | for (b in m) this[b] = m[b]; 410 | this.data_ = i; 411 | } 412 | return i.debug_; 413 | } 414 | }; 415 | for (h in o) this[h] = o[h]; 416 | this.setSize(a), this.setQuadrantObjectsLimit(b), this.setQuadrantLevelLimit(c); 417 | }, b.exports = c; 418 | }, { 419 | "./quadtree2helper": 4, 420 | "./quadtree2quadrant": 5, 421 | "./quadtree2validator": 6, 422 | vec2: 2 423 | } ], 424 | 4: [ function(a, b) { 425 | var c = { 426 | fnName: function(a) { 427 | var b = a.toString(); 428 | return b = b.substr("function ".length), b = b.substr(0, b.indexOf("(")); 429 | }, 430 | thrower: function(a, b, c) { 431 | var d = a; 432 | throw c && (d += "_" + c), b && (d += " - "), b && c && (d += c + ": "), b && (d += b), new Error(d); 433 | }, 434 | getIdsOfObjects: function(a) { 435 | var b = []; 436 | for (var c in a) b.push(a[c].id_); 437 | return b; 438 | }, 439 | compare: function(a, b) { 440 | return a - b; 441 | }, 442 | arrayDiffs: function(a, b) { 443 | var c = 0, d = 0, e = [], f = []; 444 | for (a.sort(this.compare), b.sort(this.compare); c < a.length && d < b.length; ) a[c] !== b[d] ? a[c] < b[d] ? (e.push(a[c]), 445 | c++) : (f.push(b[d]), d++) : (c++, d++); 446 | return c < a.length ? e.push.apply(e, a.slice(c, a.length)) : f.push.apply(f, b.slice(d, b.length)), 447 | [ e, f ]; 448 | } 449 | }; 450 | b.exports = c; 451 | }, {} ], 452 | 5: [ function(a, b) { 453 | var c = function(a, b, c, d) { 454 | this.leftTop_ = a.clone(), this.children_ = [], this.objects_ = {}, this.objectCount_ = 0, this.id_ = c || 0, 455 | this.parent_ = d, this.refactoring_ = !1, this.setSize(b); 456 | }; 457 | c.prototype = { 458 | setSize: function(a) { 459 | a && (this.size_ = a, this.rad_ = a.multiply(.5, !0), this.center_ = this.leftTop_.add(this.rad_, !0), 460 | this.leftBot_ = this.leftTop_.clone(), this.leftBot_.y += a.y, this.rightTop_ = this.leftTop_.clone(), 461 | this.rightTop_.x += a.x, this.rightBot_ = this.leftTop_.add(a, !0), this.leftMid_ = this.center_.clone(), 462 | this.leftMid_.x = this.leftTop_.x, this.topMid_ = this.center_.clone(), this.topMid_.y = this.leftTop_.y); 463 | }, 464 | makeChildren: function(a) { 465 | return this.children_.length > 0 ? !1 : (this.children_.push(new c(this.leftTop_, this.rad_, a++, this), new c(this.topMid_, this.rad_, a++, this), new c(this.leftMid_, this.rad_, a++, this), new c(this.center_, this.rad_, a++, this)), 466 | a); 467 | }, 468 | looseChildren: function() { 469 | this.children_ = []; 470 | }, 471 | addObjects: function(a) { 472 | var b; 473 | for (b in a) this.addObject(b, a[b]); 474 | }, 475 | addObject: function(a, b) { 476 | this.objectCount_++, this.objects_[a] = b; 477 | }, 478 | removeObjects: function(a, b) { 479 | var c; 480 | a || (a = []); 481 | for (c in this.objects_) a.push({ 482 | obj: this.objects_[c], 483 | quadrant: this 484 | }), delete this.objects_[c]; 485 | return this.objectCount_ = 0, b && 1 !== b || this.parent_ && this.parent_.removeObjects(a, 1), b && -1 !== b || this.children_.forEach(function(b) { 486 | b.removeObjects(a, -1); 487 | }), a; 488 | }, 489 | removeObject: function(a) { 490 | var b = this.objects_[a]; 491 | return this.objectCount_--, delete this.objects_[a], b; 492 | }, 493 | getObjectCountForLimit: function() { 494 | var a, b, c = {}; 495 | for (b in this.objects_) c[b] = !0; 496 | for (a = 0; a < this.children_.length; a++) for (b in this.children_[a].objects_) c[b] = !0; 497 | return Object.keys(c).length; 498 | }, 499 | getObjectCount: function(a, b) { 500 | var c = this.objectCount_; 501 | return a && this.children_.forEach(function(d) { 502 | c += d.getObjectCount(!b && a); 503 | }), c; 504 | }, 505 | intersectingChildren: function(a, b) { 506 | return this.children_.filter(function(c) { 507 | return c.intersects(a, b); 508 | }); 509 | }, 510 | intersects: function(a, b) { 511 | var c = a.subtract(this.center_, !0).abs(); 512 | return c.x > this.rad_.x + b ? !1 : c.y > this.rad_.y + b ? !1 : c.x <= this.rad_.x ? !0 : c.y <= this.rad_.y ? !0 : (cornerDistSq = Math.pow(c.x - this.rad_.x, 2) + Math.pow(c.y - this.rad_.y, 2), 513 | cornerDistSq <= Math.pow(b, 2)); 514 | }, 515 | hasChildren: function() { 516 | return 0 !== this.getChildCount(); 517 | }, 518 | getChildCount: function(a) { 519 | var b = this.children_.length; 520 | return a && this.children_.forEach(function(c) { 521 | b += c.getChildCount(a); 522 | }), b; 523 | }, 524 | getChildren: function(a, b) { 525 | return b || (b = []), b.push.apply(b, this.children_), a && this.children_.forEach(function(c) { 526 | c.getChildren(a, b); 527 | }), b; 528 | }, 529 | getObjectsUp: function(a) { 530 | var b; 531 | if (!a.quadrants[this.id_]) { 532 | a.quadrants[this.id_] = !0; 533 | for (b in this.objects_) a.objects[b] = this.objects_[b]; 534 | this.parent_ && this.parent_.getObjectsUp(a); 535 | } 536 | }, 537 | getObjectsDown: function(a) { 538 | var b; 539 | if (!a.quadrants[this.id_]) { 540 | a.quadrants[this.id_] = !0; 541 | for (b in this.objects_) a.objects[b] = this.objects_[b]; 542 | for (b = 0; b < this.children_.length; b++) this.children_[b].getObjectsDown(a); 543 | } 544 | } 545 | }, b.exports = c; 546 | }, {} ], 547 | 6: [ function(a, b) { 548 | var c = (a("vec2"), a("./quadtree2helper")), d = function() {}; 549 | d.prototype = { 550 | isNumber: function(a, b) { 551 | "number" != typeof a && c.thrower("NaN", "Not a Number", b); 552 | }, 553 | isString: function(a, b) { 554 | "string" == typeof a || a instanceof String || c.thrower("NaS", "Not a String", b); 555 | }, 556 | isVec2: function(a, b) { 557 | var d = !1; 558 | d = "object" != typeof a || void 0 === a.x || void 0 === a.y, d && c.thrower("NaV", "Not a Vec2", b); 559 | }, 560 | isDefined: function(a, b) { 561 | void 0 === a && c.thrower("ND", "Not defined", b); 562 | }, 563 | isObject: function(a, b) { 564 | "object" != typeof a && c.thrower("NaO", "Not an Object", b); 565 | }, 566 | hasKey: function(a, b, d) { 567 | this.isDefined(a, "obj"), -1 === Object.keys(a).indexOf(b.toString()) && c.thrower("OhnK", "Object has no key", d + b); 568 | }, 569 | hasNoKey: function(a, b, d) { 570 | this.isDefined(a, "obj"), -1 !== Object.keys(a).indexOf(b.toString()) && c.thrower("OhK", "Object has key", d + b); 571 | }, 572 | fnFalse: function(a) { 573 | a() && c.thrower("FarT", "function already returns true", c.fnName(a)); 574 | }, 575 | byCallbackObject: function(a, b, c) { 576 | var d; 577 | for (d in b) void 0 !== c ? b[d](a[c[d]], c[d]) : b[d](a[d], d); 578 | } 579 | }, b.exports = d; 580 | }, { 581 | "./quadtree2helper": 4, 582 | vec2: 2 583 | } ] 584 | }, {}, [ 1 ]); -------------------------------------------------------------------------------- /include/simple-slider.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery Simple Slider 3 | 4 | Copyright (c) 2012 James Smith (http://loopj.com) 5 | 6 | Licensed under the MIT license (http://mit-license.org/) 7 | */ 8 | 9 | var __slice = [].slice, 10 | __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; 11 | 12 | (function($, window) { 13 | var SimpleSlider; 14 | SimpleSlider = (function() { 15 | 16 | function SimpleSlider(input, options) { 17 | var ratio, 18 | _this = this; 19 | this.input = input; 20 | this.defaultOptions = { 21 | animate: true, 22 | snapMid: false, 23 | classPrefix: null, 24 | classSuffix: null, 25 | theme: null, 26 | highlight: false 27 | }; 28 | this.settings = $.extend({}, this.defaultOptions, options); 29 | if (this.settings.theme) { 30 | this.settings.classSuffix = "-" + this.settings.theme; 31 | } 32 | this.input.hide(); 33 | this.slider = $("
").addClass("slider" + (this.settings.classSuffix || "")).css({ 34 | position: "relative", 35 | userSelect: "none", 36 | boxSizing: "border-box" 37 | }).insertBefore(this.input); 38 | if (this.input.attr("id")) { 39 | this.slider.attr("id", this.input.attr("id") + "-slider"); 40 | } 41 | this.track = this.createDivElement("track").css({ 42 | width: "100%" 43 | }); 44 | if (this.settings.highlight) { 45 | this.highlightTrack = this.createDivElement("highlight-track").css({ 46 | width: "0" 47 | }); 48 | } 49 | this.dragger = this.createDivElement("dragger"); 50 | this.slider.css({ 51 | minHeight: this.dragger.outerHeight(), 52 | marginLeft: this.dragger.outerWidth() / 2, 53 | marginRight: this.dragger.outerWidth() / 2 54 | }); 55 | this.track.css({ 56 | marginTop: this.track.outerHeight() / -2 57 | }); 58 | if (this.settings.highlight) { 59 | this.highlightTrack.css({ 60 | marginTop: this.track.outerHeight() / -2 61 | }); 62 | } 63 | this.dragger.css({ 64 | marginTop: this.dragger.outerHeight() / -2, 65 | marginLeft: this.dragger.outerWidth() / -2 66 | }); 67 | this.track.mousedown(function(e) { 68 | return _this.trackEvent(e); 69 | }); 70 | if (this.settings.highlight) { 71 | this.highlightTrack.mousedown(function(e) { 72 | return _this.trackEvent(e); 73 | }); 74 | } 75 | this.dragger.mousedown(function(e) { 76 | if (e.which !== 1) { 77 | return; 78 | } 79 | _this.dragging = true; 80 | _this.dragger.addClass("dragging"); 81 | _this.domDrag(e.pageX, e.pageY); 82 | return false; 83 | }); 84 | $("body").mousemove(function(e) { 85 | if (_this.dragging) { 86 | _this.domDrag(e.pageX, e.pageY); 87 | return $("body").css({ 88 | cursor: "pointer" 89 | }); 90 | } 91 | }).mouseup(function(e) { 92 | if (_this.dragging) { 93 | _this.dragging = false; 94 | _this.dragger.removeClass("dragging"); 95 | return $("body").css({ 96 | cursor: "auto" 97 | }); 98 | } 99 | }); 100 | this.pagePos = 0; 101 | if (this.input.val() === "") { 102 | this.value = this.getRange().min; 103 | this.input.val(this.value); 104 | } else { 105 | this.value = this.nearestValidValue(this.input.val()); 106 | } 107 | this.setSliderPositionFromValue(this.value); 108 | ratio = this.valueToRatio(this.value); 109 | this.input.trigger("slider:ready", { 110 | value: this.value, 111 | ratio: ratio, 112 | position: ratio * this.slider.outerWidth(), 113 | el: this.slider 114 | }); 115 | } 116 | 117 | SimpleSlider.prototype.createDivElement = function(classname) { 118 | var item; 119 | item = $("
").addClass(classname).css({ 120 | position: "absolute", 121 | top: "50%", 122 | userSelect: "none", 123 | cursor: "pointer" 124 | }).appendTo(this.slider); 125 | return item; 126 | }; 127 | 128 | SimpleSlider.prototype.setRatio = function(ratio) { 129 | var value; 130 | ratio = Math.min(1, ratio); 131 | ratio = Math.max(0, ratio); 132 | value = this.ratioToValue(ratio); 133 | this.setSliderPositionFromValue(value); 134 | return this.valueChanged(value, ratio, "setRatio"); 135 | }; 136 | 137 | SimpleSlider.prototype.setValue = function(value) { 138 | var ratio; 139 | value = this.nearestValidValue(value); 140 | ratio = this.valueToRatio(value); 141 | this.setSliderPositionFromValue(value); 142 | return this.valueChanged(value, ratio, "setValue"); 143 | }; 144 | 145 | SimpleSlider.prototype.trackEvent = function(e) { 146 | if (e.which !== 1) { 147 | return; 148 | } 149 | this.domDrag(e.pageX, e.pageY, true); 150 | this.dragging = true; 151 | return false; 152 | }; 153 | 154 | SimpleSlider.prototype.domDrag = function(pageX, pageY, animate) { 155 | var pagePos, ratio, value; 156 | if (animate == null) { 157 | animate = false; 158 | } 159 | pagePos = pageX - this.slider.offset().left; 160 | pagePos = Math.min(this.slider.outerWidth(), pagePos); 161 | pagePos = Math.max(0, pagePos); 162 | if (this.pagePos !== pagePos) { 163 | this.pagePos = pagePos; 164 | ratio = pagePos / this.slider.outerWidth(); 165 | value = this.ratioToValue(ratio); 166 | this.valueChanged(value, ratio, "domDrag"); 167 | if (this.settings.snap) { 168 | return this.setSliderPositionFromValue(value, animate); 169 | } else { 170 | return this.setSliderPosition(pagePos, animate); 171 | } 172 | } 173 | }; 174 | 175 | SimpleSlider.prototype.setSliderPosition = function(position, animate) { 176 | if (animate == null) { 177 | animate = false; 178 | } 179 | if (animate && this.settings.animate) { 180 | this.dragger.animate({ 181 | left: position 182 | }, 200); 183 | if (this.settings.highlight) { 184 | return this.highlightTrack.animate({ 185 | width: position 186 | }, 200); 187 | } 188 | } else { 189 | this.dragger.css({ 190 | left: position 191 | }); 192 | if (this.settings.highlight) { 193 | return this.highlightTrack.css({ 194 | width: position 195 | }); 196 | } 197 | } 198 | }; 199 | 200 | SimpleSlider.prototype.setSliderPositionFromValue = function(value, animate) { 201 | var ratio; 202 | if (animate == null) { 203 | animate = false; 204 | } 205 | ratio = this.valueToRatio(value); 206 | return this.setSliderPosition(ratio * this.slider.outerWidth(), animate); 207 | }; 208 | 209 | SimpleSlider.prototype.getRange = function() { 210 | if (this.settings.allowedValues) { 211 | return { 212 | min: Math.min.apply(Math, this.settings.allowedValues), 213 | max: Math.max.apply(Math, this.settings.allowedValues) 214 | }; 215 | } else if (this.settings.range) { 216 | return { 217 | min: parseFloat(this.settings.range[0]), 218 | max: parseFloat(this.settings.range[1]) 219 | }; 220 | } else { 221 | return { 222 | min: 0, 223 | max: 1 224 | }; 225 | } 226 | }; 227 | 228 | SimpleSlider.prototype.nearestValidValue = function(rawValue) { 229 | var closest, maxSteps, range, steps; 230 | range = this.getRange(); 231 | rawValue = Math.min(range.max, rawValue); 232 | rawValue = Math.max(range.min, rawValue); 233 | if (this.settings.allowedValues) { 234 | closest = null; 235 | $.each(this.settings.allowedValues, function() { 236 | if (closest === null || Math.abs(this - rawValue) < Math.abs(closest - rawValue)) { 237 | return closest = this; 238 | } 239 | }); 240 | return closest; 241 | } else if (this.settings.step) { 242 | maxSteps = (range.max - range.min) / this.settings.step; 243 | steps = Math.floor((rawValue - range.min) / this.settings.step); 244 | if ((rawValue - range.min) % this.settings.step > this.settings.step / 2 && steps < maxSteps) { 245 | steps += 1; 246 | } 247 | return steps * this.settings.step + range.min; 248 | } else { 249 | return rawValue; 250 | } 251 | }; 252 | 253 | SimpleSlider.prototype.valueToRatio = function(value) { 254 | var allowedVal, closest, closestIdx, idx, range, _i, _len, _ref; 255 | if (this.settings.equalSteps) { 256 | _ref = this.settings.allowedValues; 257 | for (idx = _i = 0, _len = _ref.length; _i < _len; idx = ++_i) { 258 | allowedVal = _ref[idx]; 259 | if (!(typeof closest !== "undefined" && closest !== null) || Math.abs(allowedVal - value) < Math.abs(closest - value)) { 260 | closest = allowedVal; 261 | closestIdx = idx; 262 | } 263 | } 264 | if (this.settings.snapMid) { 265 | return (closestIdx + 0.5) / this.settings.allowedValues.length; 266 | } else { 267 | return closestIdx / (this.settings.allowedValues.length - 1); 268 | } 269 | } else { 270 | range = this.getRange(); 271 | return (value - range.min) / (range.max - range.min); 272 | } 273 | }; 274 | 275 | SimpleSlider.prototype.ratioToValue = function(ratio) { 276 | var idx, range, rawValue, step, steps; 277 | if (this.settings.equalSteps) { 278 | steps = this.settings.allowedValues.length; 279 | step = Math.round(ratio * steps - 0.5); 280 | idx = Math.min(step, this.settings.allowedValues.length - 1); 281 | return this.settings.allowedValues[idx]; 282 | } else { 283 | range = this.getRange(); 284 | rawValue = ratio * (range.max - range.min) + range.min; 285 | return this.nearestValidValue(rawValue); 286 | } 287 | }; 288 | 289 | SimpleSlider.prototype.valueChanged = function(value, ratio, trigger) { 290 | var eventData; 291 | if (value.toString() === this.value.toString()) { 292 | return; 293 | } 294 | this.value = value; 295 | eventData = { 296 | value: value, 297 | ratio: ratio, 298 | position: ratio * this.slider.outerWidth(), 299 | trigger: trigger, 300 | el: this.slider 301 | }; 302 | return this.input.val(value).trigger($.Event("change", eventData)).trigger("slider:changed", eventData); 303 | }; 304 | 305 | return SimpleSlider; 306 | 307 | })(); 308 | $.extend($.fn, { 309 | simpleSlider: function() { 310 | var params, publicMethods, settingsOrMethod; 311 | settingsOrMethod = arguments[0], params = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 312 | publicMethods = ["setRatio", "setValue"]; 313 | return $(this).each(function() { 314 | var obj, settings; 315 | if (settingsOrMethod && __indexOf.call(publicMethods, settingsOrMethod) >= 0) { 316 | obj = $(this).data("slider-object"); 317 | return obj[settingsOrMethod].apply(obj, params); 318 | } else { 319 | settings = settingsOrMethod; 320 | return $(this).data("slider-object", new SimpleSlider($(this), settings)); 321 | } 322 | }); 323 | } 324 | }); 325 | return $(function() { 326 | return $("[data-slider]").each(function() { 327 | var $el, allowedValues, settings, x; 328 | $el = $(this); 329 | settings = {}; 330 | allowedValues = $el.data("slider-values"); 331 | if (allowedValues) { 332 | settings.allowedValues = (function() { 333 | var _i, _len, _ref, _results; 334 | _ref = allowedValues.split(","); 335 | _results = []; 336 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 337 | x = _ref[_i]; 338 | _results.push(parseFloat(x)); 339 | } 340 | return _results; 341 | })(); 342 | } 343 | if ($el.data("slider-range")) { 344 | settings.range = $el.data("slider-range").split(","); 345 | } 346 | if ($el.data("slider-step")) { 347 | settings.step = $el.data("slider-step"); 348 | } 349 | settings.snap = $el.data("slider-snap"); 350 | settings.equalSteps = $el.data("slider-equal-steps"); 351 | if ($el.data("slider-theme")) { 352 | settings.theme = $el.data("slider-theme"); 353 | } 354 | if ($el.attr("data-slider-highlight")) { 355 | settings.highlight = $el.data("slider-highlight"); 356 | } 357 | if ($el.data("slider-animate") != null) { 358 | settings.animate = $el.data("slider-animate"); 359 | } 360 | return $el.simpleSlider(settings); 361 | }); 362 | }); 363 | })(this.jQuery || this.Zepto, this); 364 | -------------------------------------------------------------------------------- /include/spectrum.css: -------------------------------------------------------------------------------- 1 | /*** 2 | Spectrum Colorpicker v1.3.3 3 | https://github.com/bgrins/spectrum 4 | Author: Brian Grinstead 5 | License: MIT 6 | ***/ 7 | 8 | .sp-container { 9 | position:absolute; 10 | top:0; 11 | left:0; 12 | display:inline-block; 13 | *display: inline; 14 | *zoom: 1; 15 | /* https://github.com/bgrins/spectrum/issues/40 */ 16 | z-index: 9999994; 17 | overflow: hidden; 18 | } 19 | .sp-container.sp-flat { 20 | position: relative; 21 | } 22 | 23 | /* Fix for * { box-sizing: border-box; } */ 24 | .sp-container, 25 | .sp-container * { 26 | -webkit-box-sizing: content-box; 27 | -moz-box-sizing: content-box; 28 | box-sizing: content-box; 29 | } 30 | 31 | /* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */ 32 | .sp-top { 33 | position:relative; 34 | width: 100%; 35 | display:inline-block; 36 | } 37 | .sp-top-inner { 38 | position:absolute; 39 | top:0; 40 | left:0; 41 | bottom:0; 42 | right:0; 43 | } 44 | .sp-color { 45 | position: absolute; 46 | top:0; 47 | left:0; 48 | bottom:0; 49 | right:20%; 50 | } 51 | .sp-hue { 52 | position: absolute; 53 | top:0; 54 | right:0; 55 | bottom:0; 56 | left:84%; 57 | height: 100%; 58 | } 59 | 60 | .sp-clear-enabled .sp-hue { 61 | top:33px; 62 | height: 77.5%; 63 | } 64 | 65 | .sp-fill { 66 | padding-top: 80%; 67 | } 68 | .sp-sat, .sp-val { 69 | position: absolute; 70 | top:0; 71 | left:0; 72 | right:0; 73 | bottom:0; 74 | } 75 | 76 | .sp-alpha-enabled .sp-top { 77 | margin-bottom: 18px; 78 | } 79 | .sp-alpha-enabled .sp-alpha { 80 | display: block; 81 | } 82 | .sp-alpha-handle { 83 | position:absolute; 84 | top:-4px; 85 | bottom: -4px; 86 | width: 6px; 87 | left: 50%; 88 | cursor: pointer; 89 | border: 1px solid black; 90 | background: white; 91 | opacity: .8; 92 | } 93 | .sp-alpha { 94 | display: none; 95 | position: absolute; 96 | bottom: -14px; 97 | right: 0; 98 | left: 0; 99 | height: 8px; 100 | } 101 | .sp-alpha-inner { 102 | border: solid 1px #333; 103 | } 104 | 105 | .sp-clear { 106 | display: none; 107 | } 108 | 109 | .sp-clear.sp-clear-display { 110 | background-position: center; 111 | } 112 | 113 | .sp-clear-enabled .sp-clear { 114 | display: block; 115 | position:absolute; 116 | top:0px; 117 | right:0; 118 | bottom:0; 119 | left:84%; 120 | height: 28px; 121 | } 122 | 123 | /* Don't allow text selection */ 124 | .sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button { 125 | -webkit-user-select:none; 126 | -moz-user-select: -moz-none; 127 | -o-user-select:none; 128 | user-select: none; 129 | } 130 | 131 | .sp-container.sp-input-disabled .sp-input-container { 132 | display: none; 133 | } 134 | .sp-container.sp-buttons-disabled .sp-button-container { 135 | display: none; 136 | } 137 | .sp-palette-only .sp-picker-container { 138 | display: none; 139 | } 140 | .sp-palette-disabled .sp-palette-container { 141 | display: none; 142 | } 143 | 144 | .sp-initial-disabled .sp-initial { 145 | display: none; 146 | } 147 | 148 | 149 | /* Gradients for hue, saturation and value instead of images. Not pretty... but it works */ 150 | .sp-sat { 151 | background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0))); 152 | background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0)); 153 | background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); 154 | background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); 155 | background-image: -ms-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); 156 | background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0)); 157 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)"; 158 | filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81'); 159 | } 160 | .sp-val { 161 | background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0))); 162 | background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0)); 163 | background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); 164 | background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); 165 | background-image: -ms-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); 166 | background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0)); 167 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)"; 168 | filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000'); 169 | } 170 | 171 | .sp-hue { 172 | background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 173 | background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 174 | background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 175 | background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000)); 176 | background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 177 | } 178 | 179 | /* IE filters do not support multiple color stops. 180 | Generate 6 divs, line them up, and do two color gradients for each. 181 | Yes, really. 182 | */ 183 | .sp-1 { 184 | height:17%; 185 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00'); 186 | } 187 | .sp-2 { 188 | height:16%; 189 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00'); 190 | } 191 | .sp-3 { 192 | height:17%; 193 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff'); 194 | } 195 | .sp-4 { 196 | height:17%; 197 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff'); 198 | } 199 | .sp-5 { 200 | height:16%; 201 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff'); 202 | } 203 | .sp-6 { 204 | height:17%; 205 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000'); 206 | } 207 | 208 | .sp-hidden { 209 | display: none !important; 210 | } 211 | 212 | /* Clearfix hack */ 213 | .sp-cf:before, .sp-cf:after { content: ""; display: table; } 214 | .sp-cf:after { clear: both; } 215 | .sp-cf { *zoom: 1; } 216 | 217 | /* Mobile devices, make hue slider bigger so it is easier to slide */ 218 | @media (max-device-width: 480px) { 219 | .sp-color { right: 40%; } 220 | .sp-hue { left: 63%; } 221 | .sp-fill { padding-top: 60%; } 222 | } 223 | .sp-dragger { 224 | border-radius: 5px; 225 | height: 5px; 226 | width: 5px; 227 | border: 1px solid #fff; 228 | background: #000; 229 | cursor: pointer; 230 | position:absolute; 231 | top:0; 232 | left: 0; 233 | } 234 | .sp-slider { 235 | position: absolute; 236 | top:0; 237 | cursor:pointer; 238 | height: 3px; 239 | left: -1px; 240 | right: -1px; 241 | border: 1px solid #000; 242 | background: white; 243 | opacity: .8; 244 | } 245 | 246 | /* 247 | Theme authors: 248 | Here are the basic themeable display options (colors, fonts, global widths). 249 | See http://bgrins.github.io/spectrum/themes/ for instructions. 250 | */ 251 | 252 | .sp-container { 253 | border-radius: 0; 254 | background-color: #ECECEC; 255 | border: solid 1px #f0c49B; 256 | padding: 0; 257 | } 258 | .sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear 259 | { 260 | font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; 261 | -webkit-box-sizing: border-box; 262 | -moz-box-sizing: border-box; 263 | -ms-box-sizing: border-box; 264 | box-sizing: border-box; 265 | } 266 | .sp-top 267 | { 268 | margin-bottom: 3px; 269 | } 270 | .sp-color, .sp-hue, .sp-clear 271 | { 272 | border: solid 1px #666; 273 | } 274 | 275 | /* Input */ 276 | .sp-input-container { 277 | float:right; 278 | width: 100px; 279 | margin-bottom: 4px; 280 | } 281 | .sp-initial-disabled .sp-input-container { 282 | width: 100%; 283 | } 284 | .sp-input { 285 | font-size: 12px !important; 286 | border: 1px inset; 287 | padding: 4px 5px; 288 | margin: 0; 289 | width: 100%; 290 | background:transparent; 291 | border-radius: 3px; 292 | color: #222; 293 | } 294 | .sp-input:focus { 295 | border: 1px solid orange; 296 | } 297 | .sp-input.sp-validation-error 298 | { 299 | border: 1px solid red; 300 | background: #fdd; 301 | } 302 | .sp-picker-container , .sp-palette-container 303 | { 304 | float:left; 305 | position: relative; 306 | padding: 10px; 307 | padding-bottom: 300px; 308 | margin-bottom: -290px; 309 | } 310 | .sp-picker-container 311 | { 312 | width: 172px; 313 | border-left: solid 1px #fff; 314 | } 315 | 316 | /* Palettes */ 317 | .sp-palette-container 318 | { 319 | border-right: solid 1px #ccc; 320 | } 321 | 322 | .sp-palette .sp-thumb-el { 323 | display: block; 324 | position:relative; 325 | float:left; 326 | width: 24px; 327 | height: 15px; 328 | margin: 3px; 329 | cursor: pointer; 330 | border:solid 2px transparent; 331 | } 332 | .sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active { 333 | border-color: orange; 334 | } 335 | .sp-thumb-el 336 | { 337 | position:relative; 338 | } 339 | 340 | /* Initial */ 341 | .sp-initial 342 | { 343 | float: left; 344 | border: solid 1px #333; 345 | } 346 | .sp-initial span { 347 | width: 30px; 348 | height: 25px; 349 | border:none; 350 | display:block; 351 | float:left; 352 | margin:0; 353 | } 354 | 355 | .sp-initial .sp-clear-display { 356 | background-position: center; 357 | } 358 | 359 | /* Buttons */ 360 | .sp-button-container { 361 | float: right; 362 | } 363 | 364 | /* Replacer (the little preview div that shows up instead of the ) */ 365 | .sp-replacer { 366 | margin:0; 367 | overflow:hidden; 368 | cursor:pointer; 369 | padding: 4px; 370 | display:inline-block; 371 | *zoom: 1; 372 | *display: inline; 373 | border: solid 1px #91765d; 374 | background: #eee; 375 | color: #333; 376 | vertical-align: middle; 377 | } 378 | .sp-replacer:hover, .sp-replacer.sp-active { 379 | border-color: #F0C49B; 380 | color: #111; 381 | } 382 | .sp-replacer.sp-disabled { 383 | cursor:default; 384 | border-color: silver; 385 | color: silver; 386 | } 387 | .sp-dd { 388 | padding: 2px 0; 389 | height: 16px; 390 | line-height: 16px; 391 | float:left; 392 | font-size:10px; 393 | } 394 | .sp-preview 395 | { 396 | position:relative; 397 | width:25px; 398 | height: 20px; 399 | border: solid 1px #222; 400 | margin-right: 5px; 401 | float:left; 402 | z-index: 0; 403 | } 404 | 405 | .sp-palette 406 | { 407 | *width: 220px; 408 | max-width: 220px; 409 | } 410 | .sp-palette .sp-thumb-el 411 | { 412 | width:16px; 413 | height: 16px; 414 | margin:2px 1px; 415 | border: solid 1px #d0d0d0; 416 | } 417 | 418 | .sp-container 419 | { 420 | padding-bottom:0; 421 | } 422 | 423 | 424 | /* Buttons: http://hellohappy.org/css3-buttons/ */ 425 | .sp-container button { 426 | background-color: #eeeeee; 427 | background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc); 428 | background-image: -moz-linear-gradient(top, #eeeeee, #cccccc); 429 | background-image: -ms-linear-gradient(top, #eeeeee, #cccccc); 430 | background-image: -o-linear-gradient(top, #eeeeee, #cccccc); 431 | background-image: linear-gradient(to bottom, #eeeeee, #cccccc); 432 | border: 1px solid #ccc; 433 | border-bottom: 1px solid #bbb; 434 | border-radius: 3px; 435 | color: #333; 436 | font-size: 14px; 437 | line-height: 1; 438 | padding: 5px 4px; 439 | text-align: center; 440 | text-shadow: 0 1px 0 #eee; 441 | vertical-align: middle; 442 | } 443 | .sp-container button:hover { 444 | background-color: #dddddd; 445 | background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb); 446 | background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb); 447 | background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb); 448 | background-image: -o-linear-gradient(top, #dddddd, #bbbbbb); 449 | background-image: linear-gradient(to bottom, #dddddd, #bbbbbb); 450 | border: 1px solid #bbb; 451 | border-bottom: 1px solid #999; 452 | cursor: pointer; 453 | text-shadow: 0 1px 0 #ddd; 454 | } 455 | .sp-container button:active { 456 | border: 1px solid #aaa; 457 | border-bottom: 1px solid #888; 458 | -webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 459 | -moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 460 | -ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 461 | -o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 462 | box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 463 | } 464 | .sp-cancel 465 | { 466 | font-size: 11px; 467 | color: #d93f3f !important; 468 | margin:0; 469 | padding:2px; 470 | margin-right: 5px; 471 | vertical-align: middle; 472 | text-decoration:none; 473 | 474 | } 475 | .sp-cancel:hover 476 | { 477 | color: #d93f3f !important; 478 | text-decoration: underline; 479 | } 480 | 481 | 482 | .sp-palette span:hover, .sp-palette span.sp-thumb-active 483 | { 484 | border-color: #000; 485 | } 486 | 487 | .sp-preview, .sp-alpha, .sp-thumb-el 488 | { 489 | position:relative; 490 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==); 491 | } 492 | .sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner 493 | { 494 | display:block; 495 | position:absolute; 496 | top:0;left:0;bottom:0;right:0; 497 | } 498 | 499 | .sp-palette .sp-thumb-inner 500 | { 501 | background-position: 50% 50%; 502 | background-repeat: no-repeat; 503 | } 504 | 505 | .sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner 506 | { 507 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=); 508 | } 509 | 510 | .sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner 511 | { 512 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=); 513 | } 514 | 515 | .sp-clear-display { 516 | background-repeat:no-repeat; 517 | background-position: center; 518 | background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==); 519 | } 520 | -------------------------------------------------------------------------------- /include/tween.min.js: -------------------------------------------------------------------------------- 1 | // tween.js - http://github.com/sole/tween.js 2 | 'use strict';var TWEEN=TWEEN||function(){var a=[];return{REVISION:"7",getAll:function(){return a},removeAll:function(){a=[]},add:function(c){a.push(c)},remove:function(c){c=a.indexOf(c);-1!==c&&a.splice(c,1)},update:function(c){if(0===a.length)return!1;for(var b=0,d=a.length,c=void 0!==c?c:Date.now();b(a*=2)?0.5*a*a:-0.5*(--a*(a-2)-1)}},Cubic:{In:function(a){return a*a*a},Out:function(a){return--a*a*a+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*a:0.5*((a-=2)*a*a+2)}},Quartic:{In:function(a){return a*a*a*a},Out:function(a){return 1- --a*a*a*a},InOut:function(a){return 1>(a*=2)?0.5*a*a*a*a:-0.5*((a-=2)*a*a*a-2)}},Quintic:{In:function(a){return a*a*a* 7 | a*a},Out:function(a){return--a*a*a*a*a+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*a*a*a:0.5*((a-=2)*a*a*a*a+2)}},Sinusoidal:{In:function(a){return 1-Math.cos(a*Math.PI/2)},Out:function(a){return Math.sin(a*Math.PI/2)},InOut:function(a){return 0.5*(1-Math.cos(Math.PI*a))}},Exponential:{In:function(a){return 0===a?0:Math.pow(1024,a-1)},Out:function(a){return 1===a?1:1-Math.pow(2,-10*a)},InOut:function(a){return 0===a?0:1===a?1:1>(a*=2)?0.5*Math.pow(1024,a-1):0.5*(-Math.pow(2,-10*(a-1))+2)}},Circular:{In:function(a){return 1- 8 | Math.sqrt(1-a*a)},Out:function(a){return Math.sqrt(1- --a*a)},InOut:function(a){return 1>(a*=2)?-0.5*(Math.sqrt(1-a*a)-1):0.5*(Math.sqrt(1-(a-=2)*a)+1)}},Elastic:{In:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return-(b*Math.pow(2,10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4))},Out:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return b*Math.pow(2,-10*a)*Math.sin((a-c)* 9 | 2*Math.PI/0.4)+1},InOut:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return 1>(a*=2)?-0.5*b*Math.pow(2,10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4):0.5*b*Math.pow(2,-10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4)+1}},Back:{In:function(a){return a*a*(2.70158*a-1.70158)},Out:function(a){return--a*a*(2.70158*a+1.70158)+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*(3.5949095*a-2.5949095):0.5*((a-=2)*a*(3.5949095*a+2.5949095)+2)}},Bounce:{In:function(a){return 1- 10 | TWEEN.Easing.Bounce.Out(1-a)},Out:function(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375},InOut:function(a){return 0.5>a?0.5*TWEEN.Easing.Bounce.In(2*a):0.5*TWEEN.Easing.Bounce.Out(2*a-1)+0.5}}}; 11 | TWEEN.Interpolation={Linear:function(a,c){var b=a.length-1,d=b*c,e=Math.floor(d),f=TWEEN.Interpolation.Utils.Linear;return 0>c?f(a[0],a[1],d):1b?b:e+1],d-e)},Bezier:function(a,c){var b=0,d=a.length-1,e=Math.pow,f=TWEEN.Interpolation.Utils.Bernstein,h;for(h=0;h<=d;h++)b+=e(1-c,d-h)*e(c,h)*a[h]*f(d,h);return b},CatmullRom:function(a,c){var b=a.length-1,d=b*c,e=Math.floor(d),f=TWEEN.Interpolation.Utils.CatmullRom;return a[0]===a[b]?(0>c&&(e=Math.floor(d=b*(1+c))),f(a[(e- 12 | 1+b)%b],a[e],a[(e+1)%b],a[(e+2)%b],d-e)):0>c?a[0]-(f(a[0],a[0],a[1],a[1],-d)-a[0]):1=0; i--) { 38 | this.observers[i](this, fn); 39 | } 40 | } 41 | 42 | return this; 43 | }, 44 | 45 | ignore : function(fn) { 46 | if (this.observers) { 47 | if (!fn) { 48 | this.observers = []; 49 | } else { 50 | var o = this.observers, l = o.length; 51 | while(l--) { 52 | o[l] === fn && o.splice(l, 1); 53 | } 54 | } 55 | } 56 | return this; 57 | }, 58 | 59 | // set x and y 60 | set: function(x, y, notify) { 61 | if('number' != typeof x) { 62 | notify = y; 63 | y = x.y; 64 | x = x.x; 65 | } 66 | 67 | if(this.x === x && this.y === y) { 68 | return this; 69 | } 70 | 71 | var orig = null; 72 | if (notify !== false && this.observers && this.observers.length) { 73 | orig = this.clone(); 74 | } 75 | 76 | this.x = Vec2.clean(x); 77 | this.y = Vec2.clean(y); 78 | 79 | if(notify !== false) { 80 | return this.change(orig); 81 | } 82 | }, 83 | 84 | // reset x and y to zero 85 | zero : function() { 86 | return this.set(0, 0); 87 | }, 88 | 89 | // return a new vector with the same component values 90 | // as this one 91 | clone : function() { 92 | return new (this.constructor)(this.x, this.y); 93 | }, 94 | 95 | // negate the values of this vector 96 | negate : function(returnNew) { 97 | if (returnNew) { 98 | return new (this.constructor)(-this.x, -this.y); 99 | } else { 100 | return this.set(-this.x, -this.y); 101 | } 102 | }, 103 | 104 | // Add the incoming `vec2` vector to this vector 105 | add : function(x, y, returnNew) { 106 | 107 | if (typeof x != 'number') { 108 | returnNew = y; 109 | if (isArray(x)) { 110 | y = x[1]; 111 | x = x[0]; 112 | } else { 113 | y = x.y; 114 | x = x.x; 115 | } 116 | } 117 | 118 | x += this.x; 119 | y += this.y; 120 | 121 | 122 | if (!returnNew) { 123 | return this.set(x, y); 124 | } else { 125 | // Return a new vector if `returnNew` is truthy 126 | return new (this.constructor)(x, y); 127 | } 128 | }, 129 | 130 | // Subtract the incoming `vec2` from this vector 131 | subtract : function(x, y, returnNew) { 132 | if (typeof x != 'number') { 133 | returnNew = y; 134 | if (isArray(x)) { 135 | y = x[1]; 136 | x = x[0]; 137 | } else { 138 | y = x.y; 139 | x = x.x; 140 | } 141 | } 142 | 143 | x = this.x - x; 144 | y = this.y - y; 145 | 146 | if (!returnNew) { 147 | return this.set(x, y); 148 | } else { 149 | // Return a new vector if `returnNew` is truthy 150 | return new (this.constructor)(x, y); 151 | } 152 | }, 153 | 154 | // Multiply this vector by the incoming `vec2` 155 | multiply : function(x, y, returnNew) { 156 | if (typeof x != 'number') { 157 | returnNew = y; 158 | if (isArray(x)) { 159 | y = x[1]; 160 | x = x[0]; 161 | } else { 162 | y = x.y; 163 | x = x.x; 164 | } 165 | } else if (typeof y != 'number') { 166 | returnNew = y; 167 | y = x; 168 | } 169 | 170 | x *= this.x; 171 | y *= this.y; 172 | 173 | if (!returnNew) { 174 | return this.set(x, y); 175 | } else { 176 | return new (this.constructor)(x, y); 177 | } 178 | }, 179 | 180 | // Rotate this vector. Accepts a `Rotation` or angle in radians. 181 | // 182 | // Passing a truthy `inverse` will cause the rotation to 183 | // be reversed. 184 | // 185 | // If `returnNew` is truthy, a new 186 | // `Vec2` will be created with the values resulting from 187 | // the rotation. Otherwise the rotation will be applied 188 | // to this vector directly, and this vector will be returned. 189 | rotate : function(r, inverse, returnNew) { 190 | var 191 | x = this.x, 192 | y = this.y, 193 | cos = Math.cos(r), 194 | sin = Math.sin(r), 195 | rx, ry; 196 | 197 | inverse = (inverse) ? -1 : 1; 198 | 199 | rx = cos * x - (inverse * sin) * y; 200 | ry = (inverse * sin) * x + cos * y; 201 | 202 | if (returnNew) { 203 | return new (this.constructor)(rx, ry); 204 | } else { 205 | return this.set(rx, ry); 206 | } 207 | }, 208 | 209 | // Calculate the length of this vector 210 | length : function() { 211 | var x = this.x, y = this.y; 212 | return Math.sqrt(x * x + y * y); 213 | }, 214 | 215 | // Get the length squared. For performance, use this instead of `Vec2#length` (if possible). 216 | lengthSquared : function() { 217 | var x = this.x, y = this.y; 218 | return x*x+y*y; 219 | }, 220 | 221 | // Return the distance betwen this `Vec2` and the incoming vec2 vector 222 | // and return a scalar 223 | distance : function(vec2) { 224 | var x = this.x - vec2.x; 225 | var y = this.y - vec2.y; 226 | return Math.sqrt(x*x + y*y); 227 | }, 228 | 229 | // Convert this vector into a unit vector. 230 | // Returns the length. 231 | normalize : function(returnNew) { 232 | var length = this.length(); 233 | 234 | // Collect a ratio to shrink the x and y coords 235 | var invertedLength = (length < Number.MIN_VALUE) ? 0 : 1/length; 236 | 237 | if (!returnNew) { 238 | // Convert the coords to be greater than zero 239 | // but smaller than or equal to 1.0 240 | return this.set(this.x * invertedLength, this.y * invertedLength); 241 | } else { 242 | return new (this.constructor)(this.x * invertedLength, this.y * invertedLength); 243 | } 244 | }, 245 | 246 | // Determine if another `Vec2`'s components match this one's 247 | // also accepts 2 scalars 248 | equal : function(v, w) { 249 | if (typeof v != 'number') { 250 | if (isArray(v)) { 251 | w = v[1]; 252 | v = v[0]; 253 | } else { 254 | w = v.y; 255 | v = v.x; 256 | } 257 | } 258 | 259 | return (Vec2.clean(v) === this.x && Vec2.clean(w) === this.y); 260 | }, 261 | 262 | // Return a new `Vec2` that contains the absolute value of 263 | // each of this vector's parts 264 | abs : function(returnNew) { 265 | var x = Math.abs(this.x), y = Math.abs(this.y); 266 | 267 | if (returnNew) { 268 | return new (this.constructor)(x, y); 269 | } else { 270 | return this.set(x, y); 271 | } 272 | }, 273 | 274 | // Return a new `Vec2` consisting of the smallest values 275 | // from this vector and the incoming 276 | // 277 | // When returnNew is truthy, a new `Vec2` will be returned 278 | // otherwise the minimum values in either this or `v` will 279 | // be applied to this vector. 280 | min : function(v, returnNew) { 281 | var 282 | tx = this.x, 283 | ty = this.y, 284 | vx = v.x, 285 | vy = v.y, 286 | x = tx < vx ? tx : vx, 287 | y = ty < vy ? ty : vy; 288 | 289 | if (returnNew) { 290 | return new (this.constructor)(x, y); 291 | } else { 292 | return this.set(x, y); 293 | } 294 | }, 295 | 296 | // Return a new `Vec2` consisting of the largest values 297 | // from this vector and the incoming 298 | // 299 | // When returnNew is truthy, a new `Vec2` will be returned 300 | // otherwise the minimum values in either this or `v` will 301 | // be applied to this vector. 302 | max : function(v, returnNew) { 303 | var 304 | tx = this.x, 305 | ty = this.y, 306 | vx = v.x, 307 | vy = v.y, 308 | x = tx > vx ? tx : vx, 309 | y = ty > vy ? ty : vy; 310 | 311 | if (returnNew) { 312 | return new (this.constructor)(x, y); 313 | } else { 314 | return this.set(x, y); 315 | } 316 | }, 317 | 318 | // Clamp values into a range. 319 | // If this vector's values are lower than the `low`'s 320 | // values, then raise them. If they are higher than 321 | // `high`'s then lower them. 322 | // 323 | // Passing returnNew as true will cause a new Vec2 to be 324 | // returned. Otherwise, this vector's values will be clamped 325 | clamp : function(low, high, returnNew) { 326 | var ret = this.min(high, true).max(low); 327 | if (returnNew) { 328 | return ret; 329 | } else { 330 | return this.set(ret.x, ret.y); 331 | } 332 | }, 333 | 334 | // Perform linear interpolation between two vectors 335 | // amount is a decimal between 0 and 1 336 | lerp : function(vec, amount, returnNew) { 337 | return this.add(vec.subtract(this, true).multiply(amount), returnNew); 338 | }, 339 | 340 | // Get the skew vector such that dot(skew_vec, other) == cross(vec, other) 341 | skew : function(returnNew) { 342 | if (!returnNew) { 343 | return this.set(-this.y, this.x) 344 | } else { 345 | return new (this.constructor)(-this.y, this.x); 346 | } 347 | }, 348 | 349 | // calculate the dot product between 350 | // this vector and the incoming 351 | dot : function(b) { 352 | return Vec2.clean(this.x * b.x + b.y * this.y); 353 | }, 354 | 355 | // calculate the perpendicular dot product between 356 | // this vector and the incoming 357 | perpDot : function(b) { 358 | return Vec2.clean(this.x * b.y - this.y * b.x); 359 | }, 360 | 361 | // Determine the angle between two vec2s 362 | angleTo : function(vec) { 363 | return Math.atan2(this.perpDot(vec), this.dot(vec)); 364 | }, 365 | 366 | // Divide this vector's components by a scalar 367 | divide : function(x, y, returnNew) { 368 | if (typeof x != 'number') { 369 | returnNew = y; 370 | if (isArray(x)) { 371 | y = x[1]; 372 | x = x[0]; 373 | } else { 374 | y = x.y; 375 | x = x.x; 376 | } 377 | } else if (typeof y != 'number') { 378 | returnNew = y; 379 | y = x; 380 | } 381 | 382 | if (x === 0 || y === 0) { 383 | throw new Error('division by zero') 384 | } 385 | 386 | if (isNaN(x) || isNaN(y)) { 387 | throw new Error('NaN detected'); 388 | } 389 | 390 | if (returnNew) { 391 | return new (this.constructor)(this.x / x, this.y / y); 392 | } 393 | 394 | return this.set(this.x / x, this.y / y); 395 | }, 396 | 397 | isPointOnLine : function(start, end) { 398 | return (start.y - this.y) * (start.x - end.x) === 399 | (start.y - end.y) * (start.x - this.x); 400 | }, 401 | 402 | toArray: function() { 403 | return [this.x, this.y]; 404 | }, 405 | 406 | fromArray: function(array) { 407 | return this.set(array[0], array[1]); 408 | }, 409 | toJSON: function () { 410 | return {x: this.x, y: this.y}; 411 | }, 412 | toString: function() { 413 | return '(' + this.x + ', ' + this.y + ')'; 414 | }, 415 | constructor : Vec2 416 | }; 417 | 418 | Vec2.fromArray = function(array, ctor) { 419 | return new (ctor || Vec2)(array[0], array[1]); 420 | }; 421 | 422 | // Floating point stability 423 | Vec2.precision = precision || 8; 424 | var p = Math.pow(10, Vec2.precision); 425 | 426 | Vec2.clean = clean || function(val) { 427 | if (isNaN(val)) { 428 | throw new Error('NaN detected'); 429 | } 430 | 431 | if (!isFinite(val)) { 432 | throw new Error('Infinity detected'); 433 | } 434 | 435 | if(Math.round(val) === val) { 436 | return val; 437 | } 438 | 439 | return Math.round(val * p)/p; 440 | }; 441 | 442 | Vec2.inject = inject; 443 | 444 | if(!clean) { 445 | Vec2.fast = inject(function (k) { return k; }); 446 | 447 | // Expose, but also allow creating a fresh Vec2 subclass. 448 | if (typeof module !== 'undefined' && typeof module.exports == 'object') { 449 | module.exports = Vec2; 450 | } else { 451 | window.Vec2 = window.Vec2 || Vec2; 452 | } 453 | } 454 | return Vec2; 455 | })(); 456 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Encom Globe 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 397 | 398 | 399 | 400 |
401 | 406 |
407 |
408 |
409 | A tribute to the boardroom scene in Disney's Tron: Legacy. Built for reuse. Source on Github. 410 |
411 |
412 |
413 |

Add element by clicking map below

414 | 415 |
    416 |
  • Drop Pin
  • 417 |
  • Marker
  • 418 |
  • Satellite
  • 419 |
420 |

Configure Globe

421 | 422 |
423 | 426 |
    427 |
  • 428 |
  • 429 |
  • 430 |
  • 431 |
432 | 433 | 436 | 437 | 438 | 441 | 442 | 443 | 446 | 447 | 448 | 459 | 460 | 463 | 464 | 465 | 468 | 469 | 470 | 473 | 474 | 475 | 478 | 479 | 480 | 483 | 484 | 485 | 488 |
489 | 490 |
491 |
492 | 493 |
Reload
494 | 495 | 496 |
497 |
498 |
499 |
CENTRAL SYSTEM DATA ... LAUNCH ENCOM GLOBALIZATION
500 |
OIA | 012
501 |
502 | 503 |
504 |
TOUCHPOINT KEYBOARD ... INTERACTION SEQUENCING
505 |
SYS | O12
506 |
507 |
508 | 509 |
510 | 511 |
512 |
513 | 514 |
515 | 516 | 517 | 518 | 519 | 520 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "encom-globe", 3 | "version": "0.5.4", 4 | "description": "encom-globe", 5 | "main": "src/Globe.js", 6 | "devDependencies": { 7 | "minimist": "0.0.8", 8 | "grunt": "~0.4.2", 9 | "grunt-contrib-watch": "~0.5.3", 10 | "grunt-shell": "~0.6.4", 11 | "browserify": "~3.41.0", 12 | "grunt-browserify": "~2.0.7", 13 | "pngjs": "~0.4.0", 14 | "grunt-contrib-uglify": "~0.4.0" 15 | }, 16 | "scripts": { 17 | "test": "echo \"Error: no test specified\" && exit 1" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/arscan/encom-globe.git" 22 | }, 23 | "author": "robscanlon@gmail.com", 24 | "license": "MIT", 25 | "dependencies": { 26 | "hexasphere.js": "*", 27 | "quadtree2": "~0.5.3", 28 | "vec2": "~1.5.0", 29 | "three": "~0.66.2", 30 | "pusher.color": "~0.2.4", 31 | "tween.js": "~0.13.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /resources/equirectangle_projection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arscan/encom-globe/8e49c01da28f8699fa78c42958db2f64697c501e/resources/equirectangle_projection.png -------------------------------------------------------------------------------- /resources/fp_icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arscan/encom-globe/8e49c01da28f8699fa78c42958db2f64697c501e/resources/fp_icon.jpg -------------------------------------------------------------------------------- /resources/point_picker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arscan/encom-globe/8e49c01da28f8699fa78c42958db2f64697c501e/resources/point_picker.png -------------------------------------------------------------------------------- /resources/thumbprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arscan/encom-globe/8e49c01da28f8699fa78c42958db2f64697c501e/resources/thumbprint.png -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arscan/encom-globe/8e49c01da28f8699fa78c42958db2f64697c501e/screenshot.jpg -------------------------------------------------------------------------------- /src/Globe.js: -------------------------------------------------------------------------------- 1 | var TWEEN = require('tween.js'), 2 | THREE = require('three'), 3 | Hexasphere = require('hexasphere.js'), 4 | Quadtree2 = require('quadtree2'), 5 | Vec2 = require('vec2'), 6 | Pin = require('./Pin'), 7 | Marker = require('./Marker'), 8 | Satellite = require('./Satellite'), 9 | SmokeProvider = require('./SmokeProvider'), 10 | pusherColor = require('pusher.color'), 11 | utils = require('./utils'); 12 | 13 | var latLonToXYZ = function(width, height, lat,lon){ 14 | 15 | var x = Math.floor(width/2.0 + (width/360.0)*lon); 16 | var y = Math.floor((height/2.0 + (height/180.0)*lat)); 17 | 18 | return {x: x, y:y}; 19 | }; 20 | 21 | var latLon2d = function(lat,lon){ 22 | 23 | var rad = 2 + (Math.abs(lat)/90) * 15; 24 | return {x: lat+90, y:lon + 180, rad: rad}; 25 | }; 26 | 27 | 28 | 29 | var addInitialData = function(){ 30 | if(this.data.length == 0){ 31 | return; 32 | } 33 | while(this.data.length > 0 && this.firstRunTime + (next = this.data.pop()).when < Date.now()){ 34 | this.addPin(next.lat, next.lng, next.label); 35 | } 36 | 37 | if(this.firstRunTime + next.when >= Date.now()){ 38 | this.data.push(next); 39 | } 40 | }; 41 | 42 | 43 | var createParticles = function(){ 44 | 45 | if(this.hexGrid){ 46 | this.scene.remove(this.hexGrid); 47 | } 48 | 49 | var pointVertexShader = [ 50 | "#define PI 3.141592653589793238462643", 51 | "#define DISTANCE 500.0", 52 | "#define INTRODURATION " + (parseFloat(this.introLinesDuration) + .00001), 53 | "#define INTROALTITUDE " + (parseFloat(this.introLinesAltitude) + .00001), 54 | "attribute float lng;", 55 | "uniform float currentTime;", 56 | "varying vec4 vColor;", 57 | "", 58 | "void main()", 59 | "{", 60 | " vec3 newPos = position;", 61 | " float opacityVal = 0.0;", 62 | " float introStart = INTRODURATION * ((180.0 + lng)/360.0);", 63 | " if(currentTime > introStart){", 64 | " opacityVal = 1.0;", 65 | " }", 66 | " if(currentTime > introStart && currentTime < introStart + INTRODURATION / 8.0){", 67 | " newPos = position * INTROALTITUDE;", 68 | " opacityVal = .3;", 69 | " }", 70 | " if(currentTime > introStart + INTRODURATION / 8.0 && currentTime < introStart + INTRODURATION / 8.0 + 200.0){", 71 | " newPos = position * (1.0 + ((INTROALTITUDE-1.0) * (1.0-(currentTime - introStart-(INTRODURATION/8.0))/200.0)));", 72 | " }", 73 | " vColor = vec4( color, opacityVal );", // set color associated to vertex; use later in fragment shader. 74 | " gl_Position = projectionMatrix * modelViewMatrix * vec4(newPos, 1.0);", 75 | "}" 76 | ].join("\n"); 77 | 78 | var pointFragmentShader = [ 79 | "varying vec4 vColor;", 80 | "void main()", 81 | "{", 82 | " gl_FragColor = vColor;", 83 | " float depth = gl_FragCoord.z / gl_FragCoord.w;", 84 | " float fogFactor = smoothstep(" + parseInt(this.cameraDistance) +".0," + (parseInt(this.cameraDistance+300)) +".0, depth );", 85 | " vec3 fogColor = vec3(0.0);", 86 | " gl_FragColor = mix( vColor, vec4( fogColor, gl_FragColor.w ), fogFactor );", 87 | "}" 88 | ].join("\n"); 89 | 90 | var pointAttributes = { 91 | lng: {type: 'f', value: null} 92 | }; 93 | 94 | this.pointUniforms = { 95 | currentTime: { type: 'f', value: 0.0} 96 | } 97 | 98 | var pointMaterial = new THREE.ShaderMaterial( { 99 | uniforms: this.pointUniforms, 100 | attributes: pointAttributes, 101 | vertexShader: pointVertexShader, 102 | fragmentShader: pointFragmentShader, 103 | transparent: true, 104 | vertexColors: THREE.VertexColors, 105 | side: THREE.DoubleSide 106 | }); 107 | 108 | var triangles = this.tiles.length * 4; 109 | 110 | var geometry = new THREE.BufferGeometry(); 111 | 112 | geometry.addAttribute( 'index', Uint16Array, triangles * 3, 1 ); 113 | geometry.addAttribute( 'position', Float32Array, triangles * 3, 3 ); 114 | geometry.addAttribute( 'normal', Float32Array, triangles * 3, 3 ); 115 | geometry.addAttribute( 'color', Float32Array, triangles * 3, 3 ); 116 | geometry.addAttribute( 'lng', Float32Array, triangles * 3, 1 ); 117 | 118 | var lng_values = geometry.attributes.lng.array; 119 | 120 | var baseColorSet = pusherColor(this.baseColor).hueSet(); 121 | var myColors = []; 122 | for(var i = 0; i< baseColorSet.length; i++){ 123 | myColors.push(baseColorSet[i].shade(Math.random()/3.0)); 124 | } 125 | 126 | // break geometry into 127 | // chunks of 21,845 triangles (3 unique vertices per triangle) 128 | // for indices to fit into 16 bit integer number 129 | // floor(2^16 / 3) = 21845 130 | 131 | var chunkSize = 21845; 132 | 133 | var indices = geometry.attributes.index.array; 134 | 135 | for ( var i = 0; i < indices.length; i ++ ) { 136 | 137 | indices[ i ] = i % ( 3 * chunkSize ); 138 | 139 | } 140 | 141 | var positions = geometry.attributes.position.array; 142 | var colors = geometry.attributes.color.array; 143 | 144 | 145 | var n = 800, n2 = n/2; // triangles spread in the cube 146 | var d = 12, d2 = d/2; // individual triangle size 147 | 148 | var addTriangle = function(k, ax, ay, az, bx, by, bz, cx, cy, cz, lat, lng, color){ 149 | var p = k * 3; 150 | var i = p * 3; 151 | var colorIndex = Math.floor(Math.random()*myColors.length); 152 | var colorRGB = myColors[colorIndex].rgb(); 153 | 154 | lng_values[p] = lng; 155 | lng_values[p+1] = lng; 156 | lng_values[p+2] = lng; 157 | 158 | positions[ i ] = ax; 159 | positions[ i + 1 ] = ay; 160 | positions[ i + 2 ] = az; 161 | 162 | positions[ i + 3 ] = bx; 163 | positions[ i + 4 ] = by; 164 | positions[ i + 5 ] = bz; 165 | 166 | positions[ i + 6 ] = cx; 167 | positions[ i + 7 ] = cy; 168 | positions[ i + 8 ] = cz; 169 | 170 | colors[ i ] = color.r; 171 | colors[ i + 1 ] = color.g; 172 | colors[ i + 2 ] = color.b; 173 | 174 | colors[ i + 3 ] = color.r; 175 | colors[ i + 4 ] = color.g; 176 | colors[ i + 5 ] = color.b; 177 | 178 | colors[ i + 6 ] = color.r; 179 | colors[ i + 7 ] = color.g; 180 | colors[ i + 8 ] = color.b; 181 | 182 | }; 183 | 184 | for(var i =0; i< this.tiles.length; i++){ 185 | var t = this.tiles[i]; 186 | var k = i * 4; 187 | 188 | var colorIndex = Math.floor(Math.random()*myColors.length); 189 | var colorRGB = myColors[colorIndex].rgb(); 190 | var color = new THREE.Color(); 191 | 192 | color.setRGB(colorRGB[0]/255.0, colorRGB[1]/255.0, colorRGB[2]/255.0); 193 | 194 | addTriangle(k, t.b[0].x, t.b[0].y, t.b[0].z, t.b[1].x, t.b[1].y, t.b[1].z, t.b[2].x, t.b[2].y, t.b[2].z, t.lat, t.lon, color); 195 | addTriangle(k+1, t.b[0].x, t.b[0].y, t.b[0].z, t.b[2].x, t.b[2].y, t.b[2].z, t.b[3].x, t.b[3].y, t.b[3].z, t.lat, t.lon, color); 196 | addTriangle(k+2, t.b[0].x, t.b[0].y, t.b[0].z, t.b[3].x, t.b[3].y, t.b[3].z, t.b[4].x, t.b[4].y, t.b[4].z, t.lat, t.lon, color); 197 | 198 | if(t.b.length > 5){ // for the occasional pentagon that i have to deal with 199 | addTriangle(k+3, t.b[0].x, t.b[0].y, t.b[0].z, t.b[5].x, t.b[5].y, t.b[5].z, t.b[4].x, t.b[4].y, t.b[4].z, t.lat, t.lon, color); 200 | } 201 | 202 | } 203 | 204 | geometry.offsets = []; 205 | 206 | var offsets = triangles / chunkSize; 207 | 208 | for ( var i = 0; i < offsets; i ++ ) { 209 | 210 | var offset = { 211 | start: i * chunkSize * 3, 212 | index: i * chunkSize * 3, 213 | count: Math.min( triangles - ( i * chunkSize ), chunkSize ) * 3 214 | }; 215 | 216 | geometry.offsets.push( offset ); 217 | 218 | } 219 | 220 | geometry.computeBoundingSphere(); 221 | 222 | this.hexGrid = new THREE.Mesh( geometry, pointMaterial ); 223 | this.scene.add( this.hexGrid ); 224 | 225 | }; 226 | 227 | var createIntroLines = function(){ 228 | var sPoint; 229 | var introLinesMaterial = new THREE.LineBasicMaterial({ 230 | color: this.introLinesColor, 231 | transparent: true, 232 | linewidth: 2, 233 | opacity: .5 234 | }); 235 | 236 | for(var i = 0; i 0){ 368 | _this.scene.remove(_this.scene.children[0]); 369 | } 370 | if(typeof callback == "function"){ 371 | callback(); 372 | } 373 | 374 | }, 1000); 375 | 376 | }; 377 | 378 | Globe.prototype.addPin = function(lat, lon, text){ 379 | 380 | lat = parseFloat(lat); 381 | lon = parseFloat(lon); 382 | 383 | var opts = { 384 | lineColor: this.pinColor, 385 | topColor: this.pinColor, 386 | font: this.font 387 | } 388 | 389 | var altitude = 1.2; 390 | 391 | if(typeof text != "string" || text.length === 0){ 392 | altitude -= .05 + Math.random() * .05; 393 | } 394 | 395 | var pin = new Pin(lat, lon, text, altitude, this.scene, this.smokeProvider, opts); 396 | 397 | this.pins.push(pin); 398 | 399 | // lets add quadtree stuff 400 | 401 | var pos = latLon2d(lat, lon); 402 | 403 | pin.pos_ = new Vec2(parseInt(pos.x),parseInt(pos.y)); 404 | 405 | if(text.length > 0){ 406 | pin.rad_ = pos.rad; 407 | } else { 408 | pin.rad_ = 1; 409 | } 410 | 411 | this.quadtree.addObject(pin); 412 | 413 | if(text.length > 0){ 414 | var collisions = this.quadtree.getCollisionsForObject(pin); 415 | var collisionCount = 0; 416 | var tooYoungCount = 0; 417 | var hidePins = []; 418 | 419 | for(var i in collisions){ 420 | if(collisions[i].text.length > 0){ 421 | collisionCount++; 422 | if(collisions[i].age() > 5000){ 423 | hidePins.push(collisions[i]); 424 | } else { 425 | tooYoungCount++; 426 | } 427 | } 428 | } 429 | 430 | if(collisionCount > 0 && tooYoungCount == 0){ 431 | for(var i = 0; i< hidePins.length; i++){ 432 | hidePins[i].hideLabel(); 433 | hidePins[i].hideSmoke(); 434 | hidePins[i].hideTop(); 435 | hidePins[i].changeAltitude(Math.random() * .05 + 1.1); 436 | } 437 | } else if (collisionCount > 0){ 438 | pin.hideLabel(); 439 | pin.hideSmoke(); 440 | pin.hideTop(); 441 | pin.changeAltitude(Math.random() * .05 + 1.1); 442 | } 443 | } 444 | 445 | if(this.pins.length > this.maxPins){ 446 | var oldPin = this.pins.shift(); 447 | this.quadtree.removeObject(oldPin); 448 | oldPin.remove(); 449 | 450 | } 451 | 452 | return pin; 453 | 454 | } 455 | 456 | Globe.prototype.addMarker = function(lat, lon, text, connected){ 457 | 458 | var marker; 459 | var opts = { 460 | markerColor: this.markerColor, 461 | lineColor: this.markerColor, 462 | font: this.font 463 | }; 464 | 465 | if(typeof connected == "boolean" && connected){ 466 | marker = new Marker(lat, lon, text, 1.2, this.markers[this.markers.length-1], this.scene, opts); 467 | } else if(typeof connected == "object"){ 468 | marker = new Marker(lat, lon, text, 1.2, connected, this.scene, opts); 469 | } else { 470 | marker = new Marker(lat, lon, text, 1.2, null, this.scene, opts); 471 | } 472 | 473 | this.markers.push(marker); 474 | 475 | if(this.markers.length > this.maxMarkers){ 476 | this.markers.shift().remove(); 477 | } 478 | 479 | return marker; 480 | } 481 | 482 | Globe.prototype.addSatellite = function(lat, lon, altitude, opts, texture, animator){ 483 | /* texture and animator are optimizations so we don't have to regenerate certain 484 | * redundant assets */ 485 | 486 | if(!opts){ 487 | opts = {}; 488 | } 489 | 490 | if(opts.coreColor == undefined){ 491 | opts.coreColor = this.satelliteColor; 492 | } 493 | 494 | var satellite = new Satellite(lat, lon, altitude, this.scene, opts, texture, animator); 495 | 496 | if(!this.satellites[satellite.toString()]){ 497 | this.satellites[satellite.toString()] = satellite; 498 | } 499 | 500 | satellite.onRemove(function(){ 501 | delete this.satellites[satellite.toString()]; 502 | }.bind(this)); 503 | 504 | return satellite; 505 | 506 | }; 507 | 508 | Globe.prototype.addConstellation = function(sats, opts){ 509 | 510 | /* TODO: make it so that when you remove the first in a constellation it removes all others */ 511 | 512 | var texture, 513 | animator, 514 | satellite, 515 | constellation = []; 516 | 517 | for(var i = 0; i< sats.length; i++){ 518 | if(i === 0){ 519 | satellite = this.addSatellite(sats[i].lat, sats[i].lon, sats[i].altitude, opts); 520 | } else { 521 | satellite = this.addSatellite(sats[i].lat, sats[i].lon, sats[i].altitude, opts, constellation[0].canvas, constellation[0].texture); 522 | } 523 | constellation.push(satellite); 524 | 525 | } 526 | 527 | return constellation; 528 | 529 | }; 530 | 531 | 532 | Globe.prototype.setMaxPins = function(_maxPins){ 533 | this.maxPins = _maxPins; 534 | 535 | while(this.pins.length > this.maxPins){ 536 | var oldPin = this.pins.shift(); 537 | this.quadtree.removeObject(oldPin); 538 | oldPin.remove(); 539 | } 540 | }; 541 | 542 | Globe.prototype.setMaxMarkers = function(_maxMarkers){ 543 | this.maxMarkers = _maxMarkers; 544 | while(this.markers.length > this.maxMarkers){ 545 | this.markers.shift().remove(); 546 | } 547 | }; 548 | 549 | Globe.prototype.setBaseColor = function(_color){ 550 | this.baseColor = _color; 551 | createParticles.call(this); 552 | }; 553 | 554 | Globe.prototype.setMarkerColor = function(_color){ 555 | this.markerColor = _color; 556 | this.scene._encom_markerTexture = null; 557 | 558 | }; 559 | 560 | Globe.prototype.setPinColor = function(_color){ 561 | this.pinColor = _color; 562 | }; 563 | 564 | Globe.prototype.setScale = function(_scale){ 565 | this.scale = _scale; 566 | this.cameraDistance = 1700/_scale; 567 | if(this.scene && this.scene.fog){ 568 | this.scene.fog.near = this.cameraDistance; 569 | this.scene.fog.far = this.cameraDistance + 300; 570 | createParticles.call(this); 571 | this.camera.far = this.cameraDistance + 300; 572 | this.camera.updateProjectionMatrix(); 573 | } 574 | }; 575 | 576 | Globe.prototype.tick = function(){ 577 | 578 | if(!this.camera){ 579 | return; 580 | } 581 | 582 | if(!this.firstRunTime){ 583 | this.firstRunTime = Date.now(); 584 | } 585 | addInitialData.call(this); 586 | TWEEN.update(); 587 | 588 | if(!this.lastRenderDate){ 589 | this.lastRenderDate = new Date(); 590 | } 591 | 592 | if(!this.firstRenderDate){ 593 | this.firstRenderDate = new Date(); 594 | } 595 | 596 | this.totalRunTime = new Date() - this.firstRenderDate; 597 | 598 | var renderTime = new Date() - this.lastRenderDate; 599 | this.lastRenderDate = new Date(); 600 | var rotateCameraBy = (2 * Math.PI)/(this.dayLength/renderTime); 601 | 602 | this.cameraAngle += rotateCameraBy; 603 | 604 | if(!this.active){ 605 | this.cameraDistance += (1000 * renderTime/1000); 606 | } 607 | 608 | 609 | this.camera.position.x = this.cameraDistance * Math.cos(this.cameraAngle) * Math.cos(this.viewAngle); 610 | this.camera.position.y = Math.sin(this.viewAngle) * this.cameraDistance; 611 | this.camera.position.z = this.cameraDistance * Math.sin(this.cameraAngle) * Math.cos(this.viewAngle); 612 | 613 | 614 | for(var i in this.satellites){ 615 | this.satellites[i].tick(this.camera.position, this.cameraAngle, renderTime); 616 | } 617 | 618 | for(var i = 0; i< this.satelliteMeshes.length; i++){ 619 | var mesh = this.satelliteMeshes[i]; 620 | mesh.lookAt(this.camera.position); 621 | mesh.rotateZ(mesh.tiltDirection * Math.PI/2); 622 | mesh.rotateZ(Math.sin(this.cameraAngle + (mesh.lon / 180) * Math.PI) * mesh.tiltMultiplier * mesh.tiltDirection * -1); 623 | 624 | } 625 | 626 | if(this.introLinesDuration > this.totalRunTime){ 627 | if(this.totalRunTime/this.introLinesDuration < .1){ 628 | this.introLines.children[0].material.opacity = (this.totalRunTime/this.introLinesDuration) * (1 / .1) - .2; 629 | }if(this.totalRunTime/this.introLinesDuration > .8){ 630 | this.introLines.children[0].material.opacity = Math.max(1-this.totalRunTime/this.introLinesDuration,0) * (1 / .2); 631 | } else { 632 | this.introLines.children[0].material.opacity = 1; 633 | } 634 | this.introLines.rotateY((2 * Math.PI)/(this.introLinesDuration/renderTime)); 635 | } else if(this.introLines){ 636 | this.scene.remove(this.introLines); 637 | delete[this.introLines]; 638 | } 639 | 640 | // do the shaders 641 | 642 | this.pointUniforms.currentTime.value = this.totalRunTime; 643 | 644 | this.smokeProvider.tick(this.totalRunTime); 645 | 646 | this.camera.lookAt( this.scene.position ); 647 | this.renderer.render( this.scene, this.camera ); 648 | 649 | } 650 | 651 | module.exports = Globe; 652 | 653 | -------------------------------------------------------------------------------- /src/Marker.js: -------------------------------------------------------------------------------- 1 | var THREE = require('three'), 2 | TWEEN = require('tween.js'), 3 | utils = require('./utils'); 4 | 5 | var createMarkerTexture = function(markerColor) { 6 | var markerWidth = 30, 7 | markerHeight = 30, 8 | canvas, 9 | texture; 10 | 11 | canvas = utils.renderToCanvas(markerWidth, markerHeight, function(ctx){ 12 | ctx.fillStyle=markerColor; 13 | ctx.strokeStyle=markerColor; 14 | ctx.lineWidth=3; 15 | ctx.beginPath(); 16 | ctx.arc(markerWidth/2, markerHeight/2, markerWidth/3, 0, 2* Math.PI); 17 | ctx.stroke(); 18 | 19 | ctx.beginPath(); 20 | ctx.arc(markerWidth/2, markerHeight/2, markerWidth/5, 0, 2* Math.PI); 21 | ctx.fill(); 22 | 23 | }); 24 | 25 | texture = new THREE.Texture(canvas); 26 | texture.needsUpdate = true; 27 | 28 | return texture; 29 | 30 | }; 31 | 32 | var Marker = function(lat, lon, text, altitude, previous, scene, _opts){ 33 | 34 | /* options that can be passed in */ 35 | var opts = { 36 | lineColor: "#FFCC00", 37 | lineWidth: 1, 38 | markerColor: "#FFCC00", 39 | labelColor: "#FFF", 40 | font: "Inconsolata", 41 | fontSize: 20, 42 | drawTime: 2000, 43 | lineSegments: 150 44 | } 45 | 46 | var point, 47 | previousPoint, 48 | markerMaterial, 49 | labelCanvas, 50 | labelTexture, 51 | labelMaterial 52 | ; 53 | 54 | 55 | this.lat = parseFloat(lat); 56 | this.lon = parseFloat(lon); 57 | this.text = text; 58 | this.altitude = parseFloat(altitude); 59 | this.scene = scene; 60 | this.previous = previous; 61 | this.next = []; 62 | 63 | if(this.previous){ 64 | this.previous.next.push(this); 65 | } 66 | 67 | if(_opts){ 68 | for(var i in opts){ 69 | if(_opts[i] != undefined){ 70 | opts[i] = _opts[i]; 71 | } 72 | } 73 | } 74 | 75 | this.opts = opts; 76 | 77 | 78 | point = utils.mapPoint(lat, lon); 79 | 80 | if(previous){ 81 | previousPoint = utils.mapPoint(previous.lat, previous.lon); 82 | } 83 | 84 | if(!scene._encom_markerTexture){ 85 | scene._encom_markerTexture = createMarkerTexture(this.opts.markerColor); 86 | } 87 | 88 | markerMaterial = new THREE.SpriteMaterial({map: scene._encom_markerTexture, opacity: .7, depthTest: true, fog: true}); 89 | this.marker = new THREE.Sprite(markerMaterial); 90 | 91 | this.marker.scale.set(0, 0); 92 | this.marker.position.set(point.x * altitude, point.y * altitude, point.z * altitude); 93 | 94 | labelCanvas = utils.createLabel(text.toUpperCase(), this.opts.fontSize, this.opts.labelColor, this.opts.font, this.opts.markerColor); 95 | labelTexture = new THREE.Texture(labelCanvas); 96 | labelTexture.needsUpdate = true; 97 | 98 | labelMaterial = new THREE.SpriteMaterial({ 99 | map : labelTexture, 100 | useScreenCoordinates: false, 101 | opacity: 0, 102 | depthTest: true, 103 | fog: true 104 | }); 105 | 106 | this.labelSprite = new THREE.Sprite(labelMaterial); 107 | this.labelSprite.position = {x: point.x * altitude * 1.1, y: point.y*altitude*1.05 + (point.y < 0 ? -15 : 30), z: point.z * altitude * 1.1}; 108 | this.labelSprite.scale.set(labelCanvas.width, labelCanvas.height); 109 | 110 | new TWEEN.Tween( {opacity: 0}) 111 | .to( {opacity: 1}, 500 ) 112 | .onUpdate(function(){ 113 | labelMaterial.opacity = this.opacity 114 | }).start(); 115 | 116 | 117 | var _this = this; //arrghghh 118 | 119 | new TWEEN.Tween({x: 0, y: 0}) 120 | .to({x: 50, y: 50}, 2000) 121 | .easing( TWEEN.Easing.Elastic.Out ) 122 | .onUpdate(function(){ 123 | _this.marker.scale.set(this.x, this.y); 124 | }) 125 | .delay((this.previous ? _this.opts.drawTime : 0)) 126 | .start(); 127 | 128 | if(this.previous){ 129 | 130 | var materialSpline, 131 | materialSplineDotted, 132 | latdist, 133 | londist, 134 | startPoint, 135 | pointList = [], 136 | pointList2 = [], 137 | nextlat, 138 | nextlon, 139 | currentLat, 140 | currentLon, 141 | currentPoint, 142 | currentVert, 143 | update; 144 | 145 | _this.geometrySpline = new THREE.Geometry(); 146 | materialSpline = new THREE.LineBasicMaterial({ 147 | color: this.opts.lineColor, 148 | transparent: true, 149 | linewidth: 3, 150 | opacity: .5 151 | }); 152 | 153 | _this.geometrySplineDotted = new THREE.Geometry(); 154 | materialSplineDotted = new THREE.LineBasicMaterial({ 155 | color: this.opts.lineColor, 156 | linewidth: 1, 157 | transparent: true, 158 | opacity: .5 159 | }); 160 | 161 | latdist = (lat - previous.lat)/_this.opts.lineSegments; 162 | londist = (lon - previous.lon)/_this.opts.lineSegments; 163 | startPoint = utils.mapPoint(previous.lat,previous.lon); 164 | pointList = []; 165 | pointList2 = []; 166 | 167 | for(var j = 0; j< _this.opts.lineSegments + 1; j++){ 168 | // var nextlat = ((90 + lat1 + j*1)%180)-90; 169 | // var nextlon = ((180 + lng1 + j*1)%360)-180; 170 | 171 | 172 | var nextlat = (((90 + previous.lat + j*latdist)%180)-90) * (.5 + Math.cos(j*(5*Math.PI/2)/_this.opts.lineSegments)/2) + (j*lat/_this.opts.lineSegments/2); 173 | var nextlon = ((180 + previous.lon + j*londist)%360)-180; 174 | pointList.push({lat: nextlat, lon: nextlon, index: j}); 175 | if(j == 0 || j == _this.opts.lineSegments){ 176 | pointList2.push({lat: nextlat, lon: nextlon, index: j}); 177 | } else { 178 | pointList2.push({lat: nextlat+1, lon: nextlon, index: j}); 179 | } 180 | // var thisPoint = mapPoint(nextlat, nextlon); 181 | sPoint = new THREE.Vector3(startPoint.x*1.2, startPoint.y*1.2, startPoint.z*1.2); 182 | sPoint2 = new THREE.Vector3(startPoint.x*1.2, startPoint.y*1.2, startPoint.z*1.2); 183 | // sPoint = new THREE.Vector3(thisPoint.x*1.2, thisPoint.y*1.2, thisPoint.z*1.2); 184 | 185 | sPoint.globe_index = j; 186 | sPoint2.globe_index = j; 187 | 188 | _this.geometrySpline.vertices.push(sPoint); 189 | _this.geometrySplineDotted.vertices.push(sPoint2); 190 | } 191 | 192 | 193 | currentLat = previous.lat; 194 | currentLon = previous.lon; 195 | currentPoint; 196 | currentVert; 197 | 198 | update = function(){ 199 | var nextSpot = pointList.shift(); 200 | var nextSpot2 = pointList2.shift(); 201 | 202 | for(var x = 0; x< _this.geometrySpline.vertices.length; x++){ 203 | 204 | currentVert = _this.geometrySpline.vertices[x]; 205 | currentPoint = utils.mapPoint(nextSpot.lat, nextSpot.lon); 206 | 207 | currentVert2 = _this.geometrySplineDotted.vertices[x]; 208 | currentPoint2 = utils.mapPoint(nextSpot2.lat, nextSpot2.lon); 209 | 210 | if(x >= nextSpot.index){ 211 | currentVert.set(currentPoint.x*1.2, currentPoint.y*1.2, currentPoint.z*1.2); 212 | currentVert2.set(currentPoint2.x*1.19, currentPoint2.y*1.19, currentPoint2.z*1.19); 213 | } 214 | _this.geometrySpline.verticesNeedUpdate = true; 215 | _this.geometrySplineDotted.verticesNeedUpdate = true; 216 | } 217 | if(pointList.length > 0){ 218 | setTimeout(update,_this.opts.drawTime/_this.opts.lineSegments); 219 | } 220 | 221 | }; 222 | 223 | update(); 224 | 225 | this.scene.add(new THREE.Line(_this.geometrySpline, materialSpline)); 226 | this.scene.add(new THREE.Line(_this.geometrySplineDotted, materialSplineDotted, THREE.LinePieces)); 227 | } 228 | 229 | this.scene.add(this.marker); 230 | this.scene.add(this.labelSprite); 231 | 232 | }; 233 | 234 | Marker.prototype.remove = function(){ 235 | var x = 0; 236 | var _this = this; 237 | 238 | var update = function(ref){ 239 | 240 | for(var i = 0; i< x; i++){ 241 | ref.geometrySpline.vertices[i].set(ref.geometrySpline.vertices[i+1]); 242 | ref.geometrySplineDotted.vertices[i].set(ref.geometrySplineDotted.vertices[i+1]); 243 | ref.geometrySpline.verticesNeedUpdate = true; 244 | ref.geometrySplineDotted.verticesNeedUpdate = true; 245 | } 246 | 247 | x++; 248 | if(x < ref.geometrySpline.vertices.length){ 249 | setTimeout(function(){update(ref)}, _this.opts.drawTime/_this.opts.lineSegments) 250 | } else { 251 | _this.scene.remove(ref.geometrySpline); 252 | _this.scene.remove(ref.geometrySplineDotted); 253 | } 254 | } 255 | 256 | for(var j = 0; j< _this.next.length; j++){ 257 | (function(k){ 258 | update(_this.next[k]); 259 | })(j); 260 | } 261 | 262 | _this.scene.remove(_this.marker); 263 | _this.scene.remove(_this.labelSprite); 264 | 265 | }; 266 | 267 | module.exports = Marker; 268 | -------------------------------------------------------------------------------- /src/Pin.js: -------------------------------------------------------------------------------- 1 | var THREE = require('three'), 2 | TWEEN = require('tween.js'), 3 | utils = require('./utils'); 4 | 5 | 6 | var createTopCanvas = function(color) { 7 | var markerWidth = 20, 8 | markerHeight = 20; 9 | 10 | return utils.renderToCanvas(markerWidth, markerHeight, function(ctx){ 11 | ctx.fillStyle=color; 12 | ctx.beginPath(); 13 | ctx.arc(markerWidth/2, markerHeight/2, markerWidth/4, 0, 2* Math.PI); 14 | ctx.fill(); 15 | }); 16 | 17 | }; 18 | 19 | var Pin = function(lat, lon, text, altitude, scene, smokeProvider, _opts){ 20 | 21 | /* options that can be passed in */ 22 | var opts = { 23 | lineColor: "#8FD8D8", 24 | lineWidth: 1, 25 | topColor: "#8FD8D8", 26 | smokeColor: "#FFF", 27 | labelColor: "#FFF", 28 | font: "Inconsolata", 29 | showLabel: (text.length > 0), 30 | showTop: (text.length > 0), 31 | showSmoke: (text.length > 0) 32 | } 33 | 34 | var lineMaterial, 35 | labelCanvas, 36 | labelTexture, 37 | labelMaterial, 38 | topTexture, 39 | topMaterial, 40 | point, 41 | line; 42 | 43 | this.lat = lat; 44 | this.lon = lon; 45 | this.text = text; 46 | this.altitude = altitude; 47 | this.scene = scene; 48 | this.smokeProvider = smokeProvider; 49 | this.dateCreated = Date.now(); 50 | 51 | if(_opts){ 52 | for(var i in opts){ 53 | if(_opts[i] != undefined){ 54 | opts[i] = _opts[i]; 55 | } 56 | } 57 | } 58 | 59 | this.opts = opts; 60 | 61 | this.topVisible = opts.showTop; 62 | this.smokeVisible = opts.showSmoke; 63 | this.labelVisible = opts.showLabel; 64 | 65 | /* the line */ 66 | 67 | this.lineGeometry = new THREE.Geometry(); 68 | lineMaterial = new THREE.LineBasicMaterial({ 69 | color: opts.lineColor, 70 | linewidth: opts.lineWidth 71 | }); 72 | 73 | point = utils.mapPoint(lat,lon); 74 | 75 | this.lineGeometry.vertices.push(new THREE.Vector3(point.x, point.y, point.z)); 76 | this.lineGeometry.vertices.push(new THREE.Vector3(point.x, point.y, point.z)); 77 | this.line = new THREE.Line(this.lineGeometry, lineMaterial); 78 | 79 | /* the label */ 80 | 81 | labelCanvas = utils.createLabel(text, 18, opts.labelColor, opts.font); 82 | labelTexture = new THREE.Texture(labelCanvas); 83 | labelTexture.needsUpdate = true; 84 | 85 | labelMaterial = new THREE.SpriteMaterial({ 86 | map : labelTexture, 87 | useScreenCoordinates: false, 88 | opacity:0, 89 | depthTest: true, 90 | fog: true 91 | }); 92 | 93 | this.labelSprite = new THREE.Sprite(labelMaterial); 94 | this.labelSprite.position = {x: point.x*altitude*1.1, y: point.y*altitude + (point.y < 0 ? -15 : 30), z: point.z*altitude*1.1}; 95 | this.labelSprite.scale.set(labelCanvas.width, labelCanvas.height); 96 | 97 | /* the top */ 98 | 99 | topTexture = new THREE.Texture(createTopCanvas(opts.topColor)); 100 | topTexture.needsUpdate = true; 101 | topMaterial = new THREE.SpriteMaterial({map: topTexture, depthTest: true, fog: true, opacity: 0}); 102 | this.topSprite = new THREE.Sprite(topMaterial); 103 | this.topSprite.scale.set(20, 20); 104 | this.topSprite.position.set(point.x * altitude, point.y * altitude, point.z * altitude); 105 | 106 | /* the smoke */ 107 | if(this.smokeVisible){ 108 | this.smokeId = smokeProvider.setFire(lat, lon, altitude); 109 | } 110 | 111 | var _this = this; //arghhh 112 | 113 | /* intro animations */ 114 | 115 | if(opts.showTop || opts.showLabel){ 116 | new TWEEN.Tween( {opacity: 0}) 117 | .to( {opacity: 1}, 500 ) 118 | .onUpdate(function(){ 119 | if(_this.topVisible){ 120 | topMaterial.opacity = this.opacity; 121 | } else { 122 | topMaterial.opacity = 0; 123 | } 124 | if(_this.labelVisible){ 125 | labelMaterial.opacity = this.opacity; 126 | } else { 127 | labelMaterial.opacity = 0; 128 | } 129 | }).delay(1000) 130 | .start(); 131 | } 132 | 133 | 134 | new TWEEN.Tween(point) 135 | .to( {x: point.x*altitude, y: point.y*altitude, z: point.z*altitude}, 1500 ) 136 | .easing( TWEEN.Easing.Elastic.Out ) 137 | .onUpdate(function(){ 138 | _this.lineGeometry.vertices[1].x = this.x; 139 | _this.lineGeometry.vertices[1].y = this.y; 140 | _this.lineGeometry.vertices[1].z = this.z; 141 | _this.lineGeometry.verticesNeedUpdate = true; 142 | }).start(); 143 | 144 | /* add to scene */ 145 | 146 | this.scene.add(this.labelSprite); 147 | this.scene.add(this.line); 148 | this.scene.add(this.topSprite); 149 | 150 | }; 151 | 152 | Pin.prototype.toString = function(){ 153 | return "" + this.lat + "_" + this.lon; 154 | } 155 | 156 | Pin.prototype.changeAltitude = function(altitude){ 157 | var point = utils.mapPoint(this.lat, this.lon); 158 | var _this = this; // arghhhh 159 | 160 | new TWEEN.Tween({altitude: this.altitude}) 161 | .to( {altitude: altitude}, 1500 ) 162 | .easing( TWEEN.Easing.Elastic.Out ) 163 | .onUpdate(function(){ 164 | if(_this.smokeVisible){ 165 | _this.smokeProvider.changeAltitude(this.altitude, _this.smokeId); 166 | } 167 | if(_this.topVisible){ 168 | _this.topSprite.position.set(point.x * this.altitude, point.y * this.altitude, point.z * this.altitude); 169 | } 170 | if(_this.labelVisible){ 171 | _this.labelSprite.position = {x: point.x*this.altitude*1.1, y: point.y*this.altitude + (point.y < 0 ? -15 : 30), z: point.z*this.altitude*1.1}; 172 | } 173 | _this.lineGeometry.vertices[1].x = point.x * this.altitude; 174 | _this.lineGeometry.vertices[1].y = point.y * this.altitude; 175 | _this.lineGeometry.vertices[1].z = point.z * this.altitude; 176 | _this.lineGeometry.verticesNeedUpdate = true; 177 | 178 | }) 179 | .onComplete(function(){ 180 | _this.altitude = altitude; 181 | 182 | }).start(); 183 | 184 | }; 185 | 186 | Pin.prototype.hideTop = function(){ 187 | if(this.topVisible){ 188 | this.topSprite.material.opacity = 0.0; 189 | this.topVisible = false; 190 | } 191 | }; 192 | 193 | Pin.prototype.showTop = function(){ 194 | if(!this.topVisible){ 195 | this.topSprite.material.opacity = 1.0; 196 | this.topVisible = true; 197 | } 198 | }; 199 | 200 | Pin.prototype.hideLabel = function(){ 201 | if(this.labelVisible){ 202 | this.labelSprite.material.opacity = 0.0; 203 | this.labelVisible = false; 204 | } 205 | }; 206 | 207 | Pin.prototype.showLabel = function(){ 208 | if(!this.labelVisible){ 209 | this.labelSprite.material.opacity = 1.0; 210 | this.labelVisible = true; 211 | } 212 | }; 213 | 214 | Pin.prototype.hideSmoke = function(){ 215 | if(this.smokeVisible){ 216 | this.smokeProvider.extinguish(this.smokeId); 217 | this.smokeVisible = false; 218 | } 219 | }; 220 | 221 | Pin.prototype.showSmoke = function(){ 222 | if(!this.smokeVisible){ 223 | this.smokeId = this.smokeProvider.setFire(this.lat, this.lon, this.altitude); 224 | this.smokeVisible = true; 225 | } 226 | }; 227 | 228 | Pin.prototype.age = function(){ 229 | return Date.now() - this.dateCreated; 230 | 231 | }; 232 | 233 | Pin.prototype.remove = function(){ 234 | this.scene.remove(this.labelSprite); 235 | this.scene.remove(this.line); 236 | this.scene.remove(this.topSprite); 237 | 238 | if(this.smokeVisible){ 239 | this.smokeProvider.extinguish(this.smokeId); 240 | } 241 | }; 242 | 243 | module.exports = Pin; 244 | 245 | -------------------------------------------------------------------------------- /src/Satellite.js: -------------------------------------------------------------------------------- 1 | var TextureAnimator = require('./TextureAnimator'), 2 | THREE = require('three'), 3 | utils = require('./utils'); 4 | 5 | 6 | var createCanvas = function(numFrames, pixels, rows, waveStart, numWaves, waveColor, coreColor, shieldColor) { 7 | 8 | var cols = numFrames / rows; 9 | var waveInterval = Math.floor((numFrames-waveStart)/numWaves); 10 | var waveDist = pixels - 25; // width - center of satellite 11 | var distPerFrame = waveDist / (numFrames-waveStart) 12 | var offsetx = 0; 13 | var offsety = 0; 14 | var curRow = 0; 15 | 16 | var waveColorRGB = utils.hexToRgb(waveColor); 17 | 18 | return utils.renderToCanvas(numFrames * pixels / rows, pixels * rows, function(ctx){ 19 | 20 | for(var i = 0; i< numFrames; i++){ 21 | if(i - curRow * cols >= cols){ 22 | offsetx = 0; 23 | offsety += pixels; 24 | curRow++; 25 | } 26 | 27 | var centerx = offsetx + 25; 28 | var centery = offsety + Math.floor(pixels/2); 29 | 30 | /* circle around core */ 31 | // i have between 0 and wavestart to fade in 32 | // i have between wavestart and waveend - (time between waves*2) 33 | // to do a full spin close and then back open 34 | // i have between waveend-2*(timebetween waves)/2 and waveend to rotate Math.PI/4 degrees 35 | // this is probably the ugliest code in all of here -- basically I just messed arund with stuff until it looked ok 36 | 37 | ctx.lineWidth=2; 38 | ctx.strokeStyle=shieldColor; 39 | var buffer=Math.PI/16; 40 | var start = -Math.PI + Math.PI/4; 41 | var radius = 8; 42 | var repeatAt = Math.floor(numFrames-2*(numFrames-waveStart)/numWaves)+1; 43 | 44 | /* fade in and out */ 45 | if(i=numFrames){ 55 | 56 | ctx.arc(centerx, centery, radius,n* Math.PI/2 + start+buffer, n*Math.PI/2 + start+Math.PI/2-2*buffer); 57 | 58 | } else if(i > waveStart && i < swirlDone){ 59 | var totalTimeToComplete = swirlDone - waveStart; 60 | var distToGo = 3*Math.PI/2; 61 | var currentStep = (i-waveStart); 62 | var movementPerStep = distToGo / totalTimeToComplete; 63 | 64 | var startAngle = -Math.PI + Math.PI/4 + buffer + movementPerStep*currentStep; 65 | 66 | ctx.arc(centerx, centery, radius,Math.max(n*Math.PI/2 + start,startAngle), Math.max(n*Math.PI/2 + start + Math.PI/2 - 2*buffer, startAngle +Math.PI/2 - 2*buffer)); 67 | 68 | } else if(i >= swirlDone && i< repeatAt){ 69 | var totalTimeToComplete = repeatAt - swirlDone; 70 | var distToGo = n*2*Math.PI/4; 71 | var currentStep = (i-swirlDone); 72 | var movementPerStep = distToGo / totalTimeToComplete; 73 | 74 | 75 | var startAngle = Math.PI/2 + Math.PI/4 + buffer + movementPerStep*currentStep; 76 | ctx.arc(centerx, centery, radius,startAngle, startAngle + Math.PI/2 - 2*buffer); 77 | 78 | } else if(i >= repeatAt && i < (numFrames-repeatAt)/2 + repeatAt){ 79 | 80 | var totalTimeToComplete = (numFrames-repeatAt)/2; 81 | var distToGo = Math.PI/2; 82 | var currentStep = (i-repeatAt); 83 | var movementPerStep = distToGo / totalTimeToComplete; 84 | var startAngle = n*(Math.PI/2)+ Math.PI/4 + buffer + movementPerStep*currentStep; 85 | 86 | ctx.arc(centerx, centery, radius,startAngle, startAngle + Math.PI/2 - 2*buffer); 87 | 88 | } else{ 89 | ctx.arc(centerx, centery, radius,n* Math.PI/2 + start+buffer, n*Math.PI/2 + start+Math.PI/2-2*buffer); 90 | } 91 | ctx.stroke(); 92 | } 93 | 94 | // frame i'm on * distance per frame 95 | 96 | /* waves going out */ 97 | var frameOn; 98 | 99 | for(var wi = 0; wi 0 && frameOn * distPerFrame < pixels - 25){ 102 | ctx.strokeStyle="rgba(" + waveColorRGB.r + "," + waveColorRGB.g + "," + waveColorRGB.b + "," + (.9-frameOn*distPerFrame/(pixels-25)) + ")"; 103 | ctx.lineWidth=2; 104 | ctx.beginPath(); 105 | ctx.arc(centerx, centery, frameOn * distPerFrame, -Math.PI/12, Math.PI/12); 106 | ctx.stroke(); 107 | } 108 | } 109 | /* red circle in middle */ 110 | 111 | ctx.fillStyle="#000"; 112 | ctx.beginPath(); 113 | ctx.arc(centerx,centery,3,0,2*Math.PI); 114 | ctx.fill(); 115 | 116 | ctx.strokeStyle=coreColor; 117 | ctx.lineWidth=2; 118 | ctx.beginPath(); 119 | if(i 0 ? -1 : 1); 209 | this.mesh.lon = lon; 210 | 211 | this.mesh.position.set(point.x, point.y, point.z); 212 | 213 | this.mesh.rotation.z = -1*(lat/90)* Math.PI/2; 214 | this.mesh.rotation.y = (lon/180)* Math.PI 215 | 216 | scene.add(this.mesh); 217 | 218 | } 219 | 220 | Satellite.prototype.changeAltitude = function(_altitude){ 221 | 222 | var newPoint = utils.mapPoint(this.lat, this.lon); 223 | newPoint.x *= _altitude; 224 | newPoint.y *= _altitude; 225 | newPoint.z *= _altitude; 226 | 227 | this.altitude = _altitude; 228 | 229 | this.mesh.position.set(newPoint.x, newPoint.y, newPoint.z); 230 | 231 | }; 232 | 233 | Satellite.prototype.changeCanvas = function(numWaves, waveColor, coreColor, shieldColor){ 234 | /* private vars */ 235 | numFrames = 50; 236 | pixels = 100; 237 | rows = 10; 238 | waveStart = Math.floor(numFrames/8); 239 | 240 | if(!numWaves){ 241 | numWaves = this.opts.numWaves; 242 | } else { 243 | this.opts.numWaves = numWaves; 244 | } 245 | if(!waveColor){ 246 | waveColor = this.opts.waveColor; 247 | } else { 248 | this.opts.waveColor = waveColor; 249 | } 250 | if(!coreColor){ 251 | coreColor = this.opts.coreColor; 252 | } else { 253 | this.opts.coreColor = coreColor; 254 | } 255 | if(!shieldColor){ 256 | shieldColor = this.opts.shieldColor; 257 | } else { 258 | this.opts.shieldColor = shieldColor; 259 | } 260 | 261 | this.canvas = createCanvas(numFrames, pixels, rows, waveStart, numWaves, waveColor, coreColor, shieldColor); 262 | this.texture = new THREE.Texture(this.canvas) 263 | this.texture.needsUpdate = true; 264 | repeatAt = Math.floor(numFrames-2*(numFrames-waveStart)/numWaves)+1; 265 | this.animator = new TextureAnimator(this.texture,rows, numFrames/rows, numFrames, 80, repeatAt); 266 | this.material.map = this.texture; 267 | }; 268 | 269 | Satellite.prototype.tick = function(cameraPosition, cameraAngle, renderTime) { 270 | // underscore should be good enough 271 | 272 | this.mesh.lookAt(cameraPosition); 273 | 274 | this.mesh.rotateZ(this.mesh.tiltDirection * Math.PI/2); 275 | this.mesh.rotateZ(Math.sin(cameraAngle + (this.mesh.lon / 180) * Math.PI) * this.mesh.tiltMultiplier * this.mesh.tiltDirection * -1); 276 | 277 | if(this.animator){ 278 | this.animator.update(renderTime); 279 | } 280 | 281 | 282 | }; 283 | 284 | Satellite.prototype.remove = function() { 285 | 286 | 287 | this.scene.remove(this.mesh); 288 | 289 | for(var i = 0; i< this.onRemoveList.length; i++){ 290 | this.onRemoveList[i](); 291 | } 292 | }; 293 | 294 | Satellite.prototype.onRemove = function(fn){ 295 | this.onRemoveList.push(fn); 296 | } 297 | 298 | Satellite.prototype.toString = function(){ 299 | return "" + this.lat + '_' + this.lon + '_' + this.altitude; 300 | }; 301 | 302 | module.exports = Satellite; 303 | 304 | -------------------------------------------------------------------------------- /src/SmokeProvider.js: -------------------------------------------------------------------------------- 1 | var THREE = require('three'), 2 | utils = require('./utils'); 3 | 4 | var vertexShader = [ 5 | "#define PI 3.141592653589793238462643", 6 | "#define DISTANCE 500.0", 7 | "attribute float myStartTime;", 8 | "attribute float myStartLat;", 9 | "attribute float myStartLon;", 10 | "attribute float altitude;", 11 | "attribute float active;", 12 | "uniform float currentTime;", 13 | "uniform vec3 color;", 14 | "varying vec4 vColor;", 15 | "", 16 | "vec3 getPos(float lat, float lon)", 17 | "{", 18 | " if (lon < -180.0){", 19 | " lon = lon + 360.0;", 20 | " }", 21 | " float phi = (90.0 - lat) * PI / 180.0;", 22 | " float theta = (180.0 - lon) * PI / 180.0;", 23 | " float x = DISTANCE * sin(phi) * cos(theta) * altitude;", 24 | " float y = DISTANCE * cos(phi) * altitude;", 25 | " float z = DISTANCE * sin(phi) * sin(theta) * altitude;", 26 | " return vec3(x, y, z);", 27 | "}", 28 | "", 29 | "void main()", 30 | "{", 31 | " float dt = currentTime - myStartTime;", 32 | " if (dt < 0.0){", 33 | " dt = 0.0;", 34 | " }", 35 | " if (dt > 0.0 && active > 0.0) {", 36 | " dt = mod(dt,1500.0);", 37 | " }", 38 | " float opacity = 1.0 - dt/ 1500.0;", 39 | " if (dt == 0.0 || active == 0.0){", 40 | " opacity = 0.0;", 41 | " }", 42 | " vec3 newPos = getPos(myStartLat, myStartLon - ( dt / 50.0));", 43 | " vColor = vec4( color, opacity );", // set color associated to vertex; use later in fragment shader. 44 | " vec4 mvPosition = modelViewMatrix * vec4( newPos, 1.0 );", 45 | " gl_PointSize = 2.5 - (dt / 1500.0);", 46 | " gl_Position = projectionMatrix * mvPosition;", 47 | "}" 48 | ].join("\n"); 49 | 50 | var fragmentShader = [ 51 | "varying vec4 vColor;", 52 | "void main()", 53 | "{", 54 | " gl_FragColor = vColor;", 55 | " float depth = gl_FragCoord.z / gl_FragCoord.w;", 56 | " float fogFactor = smoothstep(1500.0, 1800.0, depth );", 57 | " vec3 fogColor = vec3(0.0);", 58 | " gl_FragColor = mix( vColor, vec4( fogColor, gl_FragColor.w), fogFactor );", 59 | 60 | "}" 61 | ].join("\n"); 62 | 63 | var SmokeProvider = function(scene, _opts){ 64 | 65 | /* options that can be passed in */ 66 | var opts = { 67 | smokeCount: 5000, 68 | smokePerPin: 30, 69 | smokePerSecond: 20 70 | } 71 | 72 | if(_opts){ 73 | for(var i in opts){ 74 | if(_opts[i] !== undefined){ 75 | opts[i] = _opts[i]; 76 | } 77 | } 78 | } 79 | 80 | this.opts = opts; 81 | this.geometry = new THREE.Geometry(); 82 | this.attributes = { 83 | myStartTime: {type: 'f', value: []}, 84 | myStartLat: {type: 'f', value: []}, 85 | myStartLon: {type: 'f', value: []}, 86 | altitude: {type: 'f', value: []}, 87 | active: {type: 'f', value: []} 88 | }; 89 | 90 | this.uniforms = { 91 | currentTime: { type: 'f', value: 0.0}, 92 | color: { type: 'c', value: new THREE.Color("#aaa")}, 93 | } 94 | 95 | var material = new THREE.ShaderMaterial( { 96 | uniforms: this.uniforms, 97 | attributes: this.attributes, 98 | vertexShader: vertexShader, 99 | fragmentShader: fragmentShader, 100 | transparent: true 101 | }); 102 | 103 | for(var i = 0; i< opts.smokeCount; i++){ 104 | var vertex = new THREE.Vector3(); 105 | vertex.set(0,0,0); 106 | this.geometry.vertices.push( vertex ); 107 | this.attributes.myStartTime.value[i] = 0.0; 108 | this.attributes.myStartLat.value[i] = 0.0; 109 | this.attributes.myStartLon.value[i] = 0.0; 110 | this.attributes.altitude.value[i] = 0.0; 111 | this.attributes.active.value[i] = 0.0; 112 | } 113 | 114 | this.attributes.myStartTime.needsUpdate = true; 115 | this.attributes.myStartLat.needsUpdate = true; 116 | this.attributes.myStartLon.needsUpdate = true; 117 | this.attributes.altitude.needsUpdate = true; 118 | this.attributes.active.needsUpdate = true; 119 | 120 | this.smokeIndex = 0; 121 | this.totalRunTime = 0; 122 | 123 | scene.add( new THREE.ParticleSystem( this.geometry, material)); 124 | 125 | }; 126 | 127 | SmokeProvider.prototype.setFire = function(lat, lon, altitude){ 128 | 129 | var point = utils.mapPoint(lat, lon); 130 | 131 | /* add the smoke */ 132 | var startSmokeIndex = this.smokeIndex; 133 | 134 | for(var i = 0; i< this.opts.smokePerPin; i++){ 135 | this.geometry.vertices[this.smokeIndex].set(point.x * altitude, point.y * altitude, point.z * altitude); 136 | this.geometry.verticesNeedUpdate = true; 137 | this.attributes.myStartTime.value[this.smokeIndex] = this.totalRunTime + (1000*i/this.opts.smokePerSecond + 1500); 138 | this.attributes.myStartLat.value[this.smokeIndex] = lat; 139 | this.attributes.myStartLon.value[this.smokeIndex] = lon; 140 | this.attributes.altitude.value[this.smokeIndex] = altitude; 141 | this.attributes.active.value[this.smokeIndex] = 1.0; 142 | 143 | this.attributes.myStartTime.needsUpdate = true; 144 | this.attributes.myStartLat.needsUpdate = true; 145 | this.attributes.myStartLon.needsUpdate = true; 146 | this.attributes.altitude.needsUpdate = true; 147 | this.attributes.active.needsUpdate = true; 148 | 149 | this.smokeIndex++; 150 | this.smokeIndex = this.smokeIndex % this.geometry.vertices.length; 151 | } 152 | 153 | 154 | return startSmokeIndex; 155 | 156 | }; 157 | 158 | SmokeProvider.prototype.extinguish = function(index){ 159 | for(var i = 0; i< this.opts.smokePerPin; i++){ 160 | this.attributes.active.value[(i + index) % this.opts.smokeCount] = 0.0; 161 | this.attributes.active.needsUpdate = true; 162 | } 163 | }; 164 | 165 | SmokeProvider.prototype.changeAltitude = function(altitude, index){ 166 | for(var i = 0; i< this.opts.smokePerPin; i++){ 167 | this.attributes.altitude.value[(i + index) % this.opts.smokeCount] = altitude; 168 | this.attributes.altitude.needsUpdate = true; 169 | } 170 | 171 | }; 172 | 173 | SmokeProvider.prototype.tick = function(totalRunTime){ 174 | this.totalRunTime = totalRunTime; 175 | this.uniforms.currentTime.value = this.totalRunTime; 176 | }; 177 | 178 | module.exports = SmokeProvider; 179 | -------------------------------------------------------------------------------- /src/TextureAnimator.js: -------------------------------------------------------------------------------- 1 | var THREE = require('three'); 2 | 3 | // based on http://stemkoski.github.io/Three.js/Texture-Animation.html 4 | var TextureAnimator = function(texture, tilesVert, tilesHoriz, numTiles, tileDispDuration, repeatAtTile) 5 | { 6 | // note: texture passed by reference, will be updated by the update function. 7 | 8 | if(repeatAtTile == undefined){ 9 | this.repeatAtTile=-1; 10 | } 11 | 12 | this.shutDownFlag = (this.repeatAtTile < 0); 13 | this.done = false; 14 | 15 | this.tilesHorizontal = tilesHoriz; 16 | this.tilesVertical = tilesVert; 17 | 18 | // how many images does this spritesheet contain? 19 | // usually equals tilesHoriz * tilesVert, but not necessarily, 20 | // if there at blank tiles at the bottom of the spritesheet. 21 | this.numberOfTiles = numTiles; 22 | texture.wrapS = texture.wrapT = THREE.RepeatWrapping; 23 | texture.repeat.set( 1 / this.tilesHorizontal, 1 / this.tilesVertical ); 24 | 25 | // how long should each image be displayed? 26 | this.tileDisplayDuration = tileDispDuration; 27 | 28 | // how long has the current image been displayed? 29 | this.currentDisplayTime = 0; 30 | 31 | // which image is currently being displayed? 32 | this.currentTile = 0; 33 | 34 | texture.offset.y = 1; 35 | 36 | this.update = function( milliSec ) 37 | { 38 | this.currentDisplayTime += milliSec; 39 | while (!this.done && this.currentDisplayTime > this.tileDisplayDuration) 40 | { 41 | if(this.shutDownFlag && this.currentTile >= numTiles){ 42 | this.done = true; 43 | this.shutDownCb(); 44 | } else { 45 | this.currentDisplayTime -= this.tileDisplayDuration; 46 | this.currentTile++; 47 | if (this.currentTile == numTiles && !this.shutDownFlag) 48 | this.currentTile = repeatAtTile; 49 | var currentColumn = this.currentTile % this.tilesHorizontal; 50 | texture.offset.x = currentColumn / this.tilesHorizontal; 51 | var currentRow = Math.floor( this.currentTile / this.tilesHorizontal ); 52 | texture.offset.y = 1-(currentRow / this.tilesVertical) - 1/this.tilesVertical; 53 | } 54 | } 55 | }; 56 | this.shutDown = function(cb){ 57 | _this.shutDownFlag = true; 58 | _this.shutDownCb = cb; 59 | } 60 | 61 | }; 62 | 63 | module.exports = TextureAnimator; 64 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | var utils = { 2 | 3 | renderToCanvas: function (width, height, renderFunction) { 4 | var buffer = document.createElement('canvas'); 5 | buffer.width = width; 6 | buffer.height = height; 7 | renderFunction(buffer.getContext('2d')); 8 | 9 | return buffer; 10 | }, 11 | 12 | mapPoint: function(lat, lng, scale) { 13 | if(!scale){ 14 | scale = 500; 15 | } 16 | var phi = (90 - lat) * Math.PI / 180; 17 | var theta = (180 - lng) * Math.PI / 180; 18 | var x = scale * Math.sin(phi) * Math.cos(theta); 19 | var y = scale * Math.cos(phi); 20 | var z = scale * Math.sin(phi) * Math.sin(theta); 21 | return {x: x, y: y, z:z}; 22 | }, 23 | 24 | /* from http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb */ 25 | 26 | hexToRgb: function(hex) { 27 | // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") 28 | var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 29 | hex = hex.replace(shorthandRegex, function(m, r, g, b) { 30 | return r + r + g + g + b + b; 31 | }); 32 | 33 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 34 | return result ? { 35 | r: parseInt(result[1], 16), 36 | g: parseInt(result[2], 16), 37 | b: parseInt(result[3], 16) 38 | } : null; 39 | }, 40 | 41 | createLabel: function(text, size, color, font, underlineColor) { 42 | 43 | var canvas = document.createElement("canvas"); 44 | var context = canvas.getContext("2d"); 45 | context.font = size + "pt " + font; 46 | 47 | var textWidth = context.measureText(text).width; 48 | 49 | canvas.width = textWidth; 50 | canvas.height = size + 10; 51 | 52 | // better if canvases have even heights 53 | if(canvas.width % 2){ 54 | canvas.width++; 55 | } 56 | if(canvas.height % 2){ 57 | canvas.height++; 58 | } 59 | 60 | if(underlineColor){ 61 | canvas.height += 30; 62 | } 63 | context.font = size + "pt " + font; 64 | 65 | context.textAlign = "center"; 66 | context.textBaseline = "middle"; 67 | 68 | context.strokeStyle = 'black'; 69 | 70 | context.miterLimit = 2; 71 | context.lineJoin = 'circle'; 72 | context.lineWidth = 6; 73 | 74 | context.strokeText(text, canvas.width / 2, canvas.height / 2); 75 | 76 | context.lineWidth = 2; 77 | 78 | context.fillStyle = color; 79 | context.fillText(text, canvas.width / 2, canvas.height / 2); 80 | 81 | if(underlineColor){ 82 | context.strokeStyle=underlineColor; 83 | context.lineWidth=4; 84 | context.beginPath(); 85 | context.moveTo(0, canvas.height-10); 86 | context.lineTo(canvas.width-1, canvas.height-10); 87 | context.stroke(); 88 | } 89 | 90 | return canvas; 91 | 92 | }, 93 | 94 | }; 95 | 96 | module.exports = utils; 97 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | 2 | /* 775 by 363 */ 3 | 4 | body { 5 | background-color: #000000; 6 | font-family: 'Inconsolata', sans-serif; 7 | color: #fff; 8 | font-size: 8pt; 9 | margin: 0; 10 | overflow: hidden; 11 | } 12 | 13 | a { 14 | color: #00eeee; 15 | } 16 | 17 | a.github-link { 18 | color: #ffcc00; 19 | } 20 | 21 | a.arscan { 22 | color: #ffcc00; 23 | } 24 | 25 | h3 { 26 | font-size: 1.2em; 27 | margin: 0; 28 | padding: 5px 0; 29 | 30 | } 31 | 32 | #globe { 33 | margin: 0; 34 | padding: 0; 35 | } 36 | 37 | #info-box { 38 | position:absolute; 39 | bottom: 10px; 40 | right: 10px; 41 | visibility: hidden; 42 | background: rgba(0,0,0,.9); 43 | font-size: 12pt; 44 | z-index:10; 45 | margin-left: auto; 46 | margin-right: auto; 47 | line-height: 1.5em; 48 | 49 | } 50 | 51 | .wf-active #info-box{ 52 | visibility: visible; 53 | } 54 | 55 | #thumbprint { 56 | position: absolute; 57 | bottom: 10px; 58 | left: 10px; 59 | cursor: pointer; 60 | opacity: 0; 61 | -webkit-animation: pulsate 5s; 62 | -webkit-animation-iteration-count: 10; 63 | animation: pulsate 5s ease-out; 64 | animation-iteration-count: 10; 65 | } 66 | 67 | @-webkit-keyframes pulsate { 68 | 0% {opacity: .3;} 69 | 50% {opacity: 1.0;} 70 | 100% {opacity: 0.3;} 71 | } 72 | 73 | @keyframes pulsate { 74 | 0% {opacity: 0.3;} 75 | 50% {opacity: 1.0;} 76 | 100% {opacity: 0.3;} 77 | } 78 | 79 | #canvas { 80 | margin: 0; 81 | padding: 0; 82 | } 83 | 84 | #control-panel { 85 | position: absolute; 86 | left: 700px; 87 | top: 60px; 88 | width: 300px; 89 | } 90 | 91 | #options { 92 | position: absolute; 93 | top: 0; 94 | left: -270px; 95 | bottom: 0; 96 | overflow: hidden; 97 | margin: 0; 98 | width: 350px; 99 | background-color: rgba(0,0,0,.9); 100 | border-right: 2px solid #6fc0ba; 101 | visibility: hidden; 102 | } 103 | 104 | #options-content { 105 | opacity: 0; 106 | padding-left: 30px; 107 | } 108 | 109 | .header{ 110 | 111 | border-top: 1px solid #1d2c33; 112 | border-bottom: 1px solid #1d2c33; 113 | /* box-shadow: 0 0 6px #005b8e, inset 0 0 6px #005b8e; */ 114 | color: #fff; 115 | padding: 2px 0; 116 | visibility: hidden; 117 | left: 25px; 118 | width: 300px; 119 | font-size: 6pt 120 | 121 | } 122 | 123 | .header-left-section { 124 | float: left; 125 | width: 70%; 126 | background-color: #000; 127 | margin-left: -5px; 128 | padding: 0 8px; 129 | } 130 | 131 | .header-right-section { 132 | float: right; 133 | width: 20%; 134 | background-color: #000; 135 | text-align: right; 136 | margin-right: -5px; 137 | padding: 0 8px; 138 | } 139 | 140 | #header-bottom { 141 | position: absolute; 142 | bottom: 100px; 143 | } 144 | 145 | 146 | #header-top { 147 | position: absolute; 148 | top: 25px; 149 | } 150 | 151 | .header-animator{ 152 | position: absolute; 153 | top: 300px; 154 | left: 25px; 155 | width: 300px; 156 | height: 2px; 157 | border-top: 1px solid #1d2c33; 158 | border-bottom: 1px solid #1d2c33; 159 | visibility: hidden; 160 | } 161 | 162 | .projection { 163 | border-radius: 3px; 164 | opacity: .5; 165 | cursor: crosshair; 166 | } 167 | 168 | #add-element { 169 | list-style: none; 170 | margin: 0; 171 | padding: 0; 172 | display: inline-block; 173 | width: 275px; 174 | padding-top: 5px; 175 | border-bottom: 1px solid #1d2c33; 176 | padding-bottom: 5px; 177 | margin-bottom: 5px; 178 | 179 | } 180 | 181 | .button { 182 | text-align: center; 183 | border: 1px solid #1d2c33; 184 | border-radius: 3px; 185 | padding: 5px; 186 | cursor: pointer; 187 | } 188 | 189 | .button:hover { 190 | background-color: #111; 191 | } 192 | 193 | #add-element li { 194 | float: left; 195 | width: 25%; 196 | margin: 5px; 197 | } 198 | 199 | #add-element li:first-child { 200 | margin-left: 2px; 201 | 202 | } 203 | 204 | #add-element .selected { 205 | background-color: #111; 206 | } 207 | 208 | label { 209 | /* clear: both; */ 210 | /* float:left; */ 211 | display: inline-block; 212 | width: 120px; 213 | margin-top: 12px; 214 | } 215 | 216 | #color-options { 217 | list-style: none; 218 | margin: 0; 219 | padding: 0; 220 | display: inline-block; 221 | width: 50%; 222 | } 223 | 224 | #color-options li{ 225 | cursor: pointer; 226 | display: inline-block; 227 | } 228 | 229 | #pin-color { 230 | background-color: #00eeee; 231 | } 232 | 233 | #marker-color { 234 | background-color: #ffcc00; 235 | } 236 | 237 | #satellite-color { 238 | background-color: #ff0000; 239 | } 240 | 241 | #apply-button { 242 | clear: both; 243 | width: 100px; 244 | margin-top:20px; 245 | } 246 | 247 | 248 | /* spectrum changes */ 249 | 250 | .sp-dd { 251 | display: none; 252 | } 253 | 254 | .sp-replacer { 255 | padding: 0; 256 | background-color: inherit; 257 | border: none; 258 | } 259 | .sp-preview { 260 | float: none; 261 | width: 10px; 262 | height: 10px; 263 | margin-right: 0; 264 | border: 1px solid #1d2c33; 265 | } 266 | 267 | /* simple slider */ 268 | 269 | .slider { 270 | width: 100px; 271 | display: inline-block; 272 | } 273 | 274 | .slider > .dragger { 275 | background: #8DCA09; 276 | background: -webkit-linear-gradient(top, #8DCA09, #72A307); 277 | background: -moz-linear-gradient(top, #8DCA09, #72A307); 278 | background: linear-gradient(top, #8DCA09, #72A307); 279 | 280 | -webkit-box-shadow: inset 0 2px 2px rgba(255,255,255,0.5), 0 2px 8px rgba(0,0,0,0.2); 281 | -moz-box-shadow: inset 0 2px 2px rgba(255,255,255,0.5), 0 2px 8px rgba(0,0,0,0.2); 282 | box-shadow: inset 0 2px 2px rgba(255,255,255,0.5), 0 2px 8px rgba(0,0,0,0.2); 283 | 284 | -webkit-border-radius: 10px; 285 | -moz-border-radius: 10px; 286 | border-radius: 10px; 287 | 288 | border: 1px solid #496805; 289 | width: 8px; 290 | height: 8px; 291 | } 292 | 293 | .slider > .dragger:hover { 294 | background: -webkit-linear-gradient(top, #8DCA09, #8DCA09); 295 | } 296 | 297 | 298 | .slider > .track, .slider > .highlight-track { 299 | background: #ccc; 300 | background: -webkit-linear-gradient(top, #bbb, #ddd); 301 | background: -moz-linear-gradient(top, #bbb, #ddd); 302 | background: linear-gradient(top, #bbb, #ddd); 303 | 304 | -webkit-box-shadow: inset 0 2px 4px rgba(0,0,0,0.1); 305 | -moz-box-shadow: inset 0 2px 4px rgba(0,0,0,0.1); 306 | box-shadow: inset 0 2px 4px rgba(0,0,0,0.1); 307 | 308 | -webkit-border-radius: 8px; 309 | -moz-border-radius: 8px; 310 | border-radius: 8px; 311 | 312 | border: 1px solid #aaa; 313 | height: 2px; 314 | } 315 | 316 | .slider > .highlight-track { 317 | background-color: #8DCA09; 318 | background: -webkit-linear-gradient(top, #8DCA09, #72A307); 319 | background: -moz-linear-gradient(top, #8DCA09, #72A307); 320 | background: linear-gradient(top, #8DCA09, #72A307); 321 | 322 | border-color: #496805; 323 | } 324 | 325 | .slider-output { 326 | padding-left: 3px; 327 | 328 | } 329 | 330 | .switch { 331 | display: inline-block; 332 | width: 40%; 333 | 334 | 335 | } 336 | 337 | #banner { 338 | font: 28pt Georgia; 339 | font-weight: bold; 340 | position: absolute; 341 | top: 20px; 342 | right: 20px; 343 | z-index: 5; 344 | background-color: rgba(0,0,0,.9); 345 | margin: 0px; 346 | line-height: .5em; 347 | padding-left: 10px; 348 | 349 | 350 | } 351 | #banner small { 352 | font-size: 12pt; 353 | white-space: nowrap; 354 | padding-left:3px; 355 | 356 | } 357 | 358 | @media (max-width: 800px) { 359 | #options, #thumbprint, .header { 360 | display: none; 361 | } 362 | #info-box { 363 | padding: 10px; 364 | 365 | } 366 | } 367 | @media (min-width: 800px) { 368 | #globe { 369 | margin-left: 200px; 370 | } 371 | } 372 | 373 | @media (max-width: 800px) { 374 | #thumbprint { 375 | bottom: 30px; 376 | } 377 | #header-bottom { 378 | bottom: 120px; 379 | 380 | } 381 | 382 | } 383 | 384 | @media (max-width: 1400px) { 385 | #thumbprint { 386 | bottom: 30px; 387 | } 388 | #header-bottom { 389 | bottom: 120px; 390 | 391 | } 392 | 393 | } 394 | 395 | @media (max-height: 700px) { 396 | #options, #thumbprint, .header { 397 | display: none; 398 | } 399 | #globe { 400 | margin-left: 0; 401 | 402 | } 403 | } 404 | --------------------------------------------------------------------------------