├── Procfile ├── .gitignore ├── images ├── hex.png ├── info.png ├── favicon.ico ├── keyboard.png ├── twitter.png ├── fullscreen.png ├── markertop.png ├── scanlines.png ├── screenshot.jpeg ├── thumbprint.png ├── encom_folder_big.png ├── encom_folder_xl.png ├── encom_folder_small.png ├── github-screensaver.gif ├── not_available_large.png ├── not_available_small.png ├── screenshot_lighttable.jpg ├── GitHub-Mark-Light-32px.png └── equirectangle_projection.png ├── css ├── terminator.woff ├── global.css ├── light-table-styles.css └── boardroom-styles.css ├── package.json ├── src ├── Utils.js ├── Logo.js ├── SimpleClock.js ├── TimerTrees.js ├── SatBar.js ├── StockChartSmall.js ├── main.js ├── Swirls.js ├── StockChart.js ├── LightTable.js ├── Box.js └── Boardroom.js ├── Gruntfile.js ├── README.md ├── stream-server.js └── js └── github-2013.js /Procfile: -------------------------------------------------------------------------------- 1 | web: node stream-server.js 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ 3 | npm-debug.log 4 | production/ -------------------------------------------------------------------------------- /images/hex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/hex.png -------------------------------------------------------------------------------- /images/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/info.png -------------------------------------------------------------------------------- /css/terminator.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/css/terminator.woff -------------------------------------------------------------------------------- /images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/favicon.ico -------------------------------------------------------------------------------- /images/keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/keyboard.png -------------------------------------------------------------------------------- /images/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/twitter.png -------------------------------------------------------------------------------- /images/fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/fullscreen.png -------------------------------------------------------------------------------- /images/markertop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/markertop.png -------------------------------------------------------------------------------- /images/scanlines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/scanlines.png -------------------------------------------------------------------------------- /images/screenshot.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/screenshot.jpeg -------------------------------------------------------------------------------- /images/thumbprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/thumbprint.png -------------------------------------------------------------------------------- /images/encom_folder_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/encom_folder_big.png -------------------------------------------------------------------------------- /images/encom_folder_xl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/encom_folder_xl.png -------------------------------------------------------------------------------- /images/encom_folder_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/encom_folder_small.png -------------------------------------------------------------------------------- /images/github-screensaver.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/github-screensaver.gif -------------------------------------------------------------------------------- /images/not_available_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/not_available_large.png -------------------------------------------------------------------------------- /images/not_available_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/not_available_small.png -------------------------------------------------------------------------------- /images/screenshot_lighttable.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/screenshot_lighttable.jpg -------------------------------------------------------------------------------- /images/GitHub-Mark-Light-32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/GitHub-Mark-Light-32px.png -------------------------------------------------------------------------------- /images/equirectangle_projection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bwinchester/seckc-encom-boardroom/HEAD/images/equirectangle_projection.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "encom-boardroom", 3 | "version": "0.10.7", 4 | "description": "encom-boardroom", 5 | "main": "index.html", 6 | "dependencies": { 7 | "encom-globe": "*", 8 | "event-source": "~0.1.0", 9 | "express": "~4.3.0", 10 | "github-timeline-stream": "*", 11 | "grunt": "~0.4.2", 12 | "jquery": "~2.1.0", 13 | "jquery-ui": "~1.10.5", 14 | "map-stream": "~0.1.0", 15 | "moment": "^2.6.0", 16 | "moment-timezone": "0.0.6", 17 | "pleaserotate.js": "*", 18 | "pusher.color": "~0.2.4", 19 | "request": "~2.34.0", 20 | "serve-favicon": "~2.0.0", 21 | "three": "~0.66.2", 22 | "wikipedia-stream": "*" 23 | }, 24 | "devDependencies": { 25 | "browserify": "~3.44.2", 26 | "grunt": "~0.4.2", 27 | "grunt-browserify": "~2.0.8", 28 | "grunt-contrib-clean": "^1.1.0", 29 | "grunt-contrib-copy": "^0.5.0", 30 | "grunt-contrib-uglify": "~0.4.0", 31 | "grunt-contrib-watch": "~0.5.3" 32 | }, 33 | "scripts": { 34 | "test": "echo \"Error: no test specified\" && exit 1", 35 | "start": "nodejs stream-server.js" 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "git://github.com/arscan/encom-boardroom.git" 40 | }, 41 | "author": "rscanlon@mit.edu", 42 | "license": "MIT" 43 | } 44 | -------------------------------------------------------------------------------- /src/Utils.js: -------------------------------------------------------------------------------- 1 | var Utils = {}; 2 | 3 | Utils.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 | return buffer; 9 | }; 10 | 11 | Utils.extend = function(first, second) { 12 | for(var i in first){ 13 | second[i] = first[i]; 14 | } 15 | }; 16 | 17 | Utils.sCurve = function(t) { 18 | return 1/(1 + Math.exp(-t*12 + 6)); 19 | }; 20 | 21 | // http://stackoverflow.com/a/13542669 22 | Utils.shadeColor = function(color, percent) { 23 | var num = parseInt(color.slice(1),16), 24 | amt = Math.round(2.55 * percent), 25 | R = (num >> 16) + amt, 26 | G = (num >> 8 & 0x00FF) + amt, 27 | B = (num & 0x0000FF) + amt; 28 | 29 | return "#" + (0x1000000 + (R<255?R<1?0:R:255)*0x10000 + (G<255?G<1?0:G:255)*0x100 + (B<255?B<1?0:B:255)).toString(16).slice(1); 30 | } 31 | 32 | Utils.drawCurvedRectangle = function(ctx, left, top, width, height, radius){ 33 | 34 | ctx.beginPath(); 35 | ctx.moveTo(left + radius, top); 36 | ctx.lineTo(left + width - radius, top); 37 | ctx.quadraticCurveTo(left + width, top, left + width, top + radius); 38 | ctx.lineTo(left + width, top + height - radius); 39 | ctx.quadraticCurveTo(left + width, top + height, left + width - radius, top + height); 40 | ctx.lineTo(left + radius, top + height); 41 | ctx.quadraticCurveTo(left, top + height, left, top + height - radius); 42 | ctx.lineTo(left, top + radius); 43 | ctx.quadraticCurveTo(left, top, left + radius, top); 44 | ctx.stroke(); 45 | ctx.fill(); 46 | ctx.closePath(); 47 | } 48 | 49 | module.exports = Utils; 50 | -------------------------------------------------------------------------------- /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'/*, 'uglify'*/], 9 | files: ['src/*.js', 'index.html', 'css/*', 'Gruntfile.js', 'browserify.js'] 10 | }, 11 | browserify: { 12 | 'build/<%= pkg.name %>.js': ['src/main.js'] 13 | }, 14 | uglify: { 15 | main: { 16 | files: { 17 | 'build/<%= pkg.name%>.min.js': 'build/<%= pkg.name %>.js' 18 | } 19 | } 20 | }, 21 | clean:{ 22 | production: { 23 | options: { force: true }, 24 | src: ["production/"] 25 | }, 26 | }, 27 | copy:{ 28 | build: { 29 | expand: true, 30 | //cwd: "public/tmp/", 31 | src: [ 32 | 'build/**.min.js', 33 | 'index.html', 34 | 'css/**', 35 | 'images/**', 36 | 'js/**' 37 | ], 38 | dest: "production/" 39 | } 40 | } 41 | 42 | }); 43 | 44 | grunt.loadNpmTasks('grunt-contrib-watch'); 45 | grunt.loadNpmTasks('grunt-contrib-copy'); 46 | grunt.loadNpmTasks('grunt-browserify'); 47 | grunt.loadNpmTasks('grunt-contrib-uglify'); 48 | grunt.loadNpmTasks('grunt-contrib-clean'); 49 | grunt.loadNpmTasks('grunt-contrib-copy'); 50 | 51 | grunt.registerTask('release', ['browserify', 'uglify','clean:production','copy:build']); 52 | 53 | 54 | }; 55 | -------------------------------------------------------------------------------- /src/Logo.js: -------------------------------------------------------------------------------- 1 | var Utils = require("./Utils.js"); 2 | 3 | var Logo = function(containerId, text){ 4 | 5 | if(typeof text == "undefined"){ 6 | text = "GITHUB"; 7 | } 8 | 9 | this.container = document.getElementById(containerId); 10 | this.container.width = 180; 11 | this.container.height = 100 12 | this.canvas = document.createElement("canvas"); 13 | this.canvas.width = this.container.width; 14 | this.canvas.height = this.container.height; 15 | this.context = this.canvas.getContext("2d"); 16 | this.container.appendChild(this.canvas); 17 | 18 | this.width = this.container.width; 19 | this.height = this.container.height; 20 | 21 | this.context.strokeStyle = "#00eeee"; 22 | this.context.lineWidth = 3; 23 | 24 | this.context.font = "14px Terminator"; 25 | var textWidth = this.context.measureText(text).width; 26 | 27 | Utils.drawCurvedRectangle(this.context, (this.width - textWidth -24)/2, 30, textWidth + 24, 60, 3); 28 | Utils.drawCurvedRectangle(this.context, (this.width - textWidth -10)/2, 65, textWidth + 10, 20, 3); 29 | 30 | this.context.textAlign = "center"; 31 | this.context.fillStyle="#00eeee"; 32 | this.context.textBaseline = "bottom"; 33 | this.context.fillText(text, this.width/2, 85); 34 | 35 | 36 | var buffer = 4; 37 | var startPos = (this.width-textWidth-24)/2 + buffer + 2; 38 | var barWidth = (textWidth + 20 - buffer * 6) / 5; 39 | 40 | for(var i = 0; i < 5; i++){ 41 | var height = Math.floor(Math.random() * 25); 42 | if(Math.random() < .5){ 43 | this.context.fillStyle = "#ffcc00"; 44 | } else { 45 | this.context.fillStyle = "#ff9933"; 46 | } 47 | this.context.fillRect(startPos + i * (barWidth + buffer), 36 + (25 - height), barWidth, height); 48 | } 49 | 50 | 51 | }; 52 | 53 | module.exports = Logo; 54 | -------------------------------------------------------------------------------- /src/SimpleClock.js: -------------------------------------------------------------------------------- 1 | var Utils = require("./Utils"); 2 | 3 | var SimpleClock = function(canvasId){ 4 | 5 | if(this.firstTick == undefined){ 6 | this.firstTick = new Date(); 7 | } 8 | 9 | var canvas = document.getElementById(canvasId); 10 | this.context = canvas.getContext("2d"); 11 | 12 | this.width = canvas.width; 13 | this.height = canvas.height; 14 | 15 | this.centerx = this.width/2; 16 | this.centery = this.height/2; 17 | 18 | this.backBuffer = Utils.renderToCanvas(this.width, this.height, function(ctx){ 19 | var x = canvas.width / 2; 20 | var y = canvas.height / 2; 21 | 22 | ctx.beginPath(); 23 | ctx.strokeStyle="#666"; 24 | ctx.arc(x, y, 8, 0, Math.PI*2); 25 | ctx.stroke(); 26 | ctx.closePath(); 27 | ctx.beginPath(); 28 | ctx.strokeStyle="#333"; 29 | ctx.arc(x, y, 16, 0, Math.PI*2); 30 | ctx.stroke(); 31 | ctx.beginPath(); 32 | ctx.strokeStyle="#666"; 33 | ctx.arc(x, y, 24, 0, Math.PI*2); 34 | ctx.stroke(); 35 | ctx.closePath(); 36 | 37 | 38 | ctx.strokeStyle="#333"; 39 | ctx.beginPath(); 40 | ctx.moveTo(x,y+8) 41 | ctx.lineTo(x,y+24) 42 | ctx.moveTo(x,y-8) 43 | ctx.lineTo(x,y-24) 44 | ctx.moveTo(x+8,y) 45 | ctx.lineTo(x+24,y) 46 | ctx.moveTo(x-8,y) 47 | ctx.lineTo(x-24,y) 48 | ctx.stroke(); 49 | ctx.closePath(); 50 | 51 | }); 52 | } 53 | 54 | SimpleClock.prototype.tick = function(){ 55 | var timeSinceStarted = new Date() - this.firstTick; 56 | 57 | this.context.clearRect(0,0,this.width, this.height); 58 | this.context.drawImage(this.backBuffer, 0, 0); 59 | 60 | this.context.strokeStyle="#666"; 61 | this.context.beginPath(); 62 | this.context.moveTo(this.centerx, this.centery); 63 | this.context.lineTo(this.centerx + 24*Math.sin(timeSinceStarted/10000), this.centery - 24*Math.cos(timeSinceStarted/10000)); 64 | this.context.moveTo(this.centerx, this.centery); 65 | this.context.lineTo(this.centerx + 24*Math.sin(timeSinceStarted/100000), this.centery - 24*Math.cos(timeSinceStarted/100000)); 66 | this.context.stroke(); 67 | this.context.closePath(); 68 | 69 | }; 70 | 71 | 72 | module.exports = SimpleClock; 73 | -------------------------------------------------------------------------------- /css/global.css: -------------------------------------------------------------------------------- 1 | /* 2 | terminator font: 3 | Allen R. Walden's Friendly Fonts (Unprotected. Please distribute freely.) | Software Friends, Inc. © 1993 (Friendly Fonts) 4 | From: http://www.fontspace.com/allen-r-walden/terminator-cyr 5 | */ 6 | @font-face { 7 | font-family: 'terminator'; 8 | src: url('terminator.woff') format('woff'); 9 | } 10 | 11 | html { 12 | background-color: #000; 13 | } 14 | 15 | body { 16 | background-color: black; 17 | font-size:6pt; 18 | /* color: #6fc0ba; */ 19 | color: #fff; 20 | } 21 | 22 | #error-message{ 23 | visibility: hidden; 24 | width: 600px; 25 | } 26 | 27 | #error-message p{ 28 | font-size: 16pt; 29 | line-height: 24pt; 30 | 31 | } 32 | 33 | #error-message .sig{ 34 | padding-left: 30px; 35 | } 36 | 37 | #error-message .sig a, #error-message .sig a:visited { 38 | color: #ffcc00; 39 | } 40 | 41 | #error-message em{ 42 | text-align: right; 43 | display:block; 44 | 45 | } 46 | 47 | #error-message h1{ 48 | font-size:42pt; 49 | margin: 0; 50 | padding: 0; 51 | } 52 | 53 | #boardroom{ 54 | visibility: hidden; 55 | } 56 | 57 | #light-table { 58 | visibility: hidden; 59 | } 60 | 61 | /* 62 | .center { 63 | position: absolute; 64 | left: 50%; 65 | top: 50%; 66 | //transform: translate(-50%, -50%); 67 | width: 48%; 68 | height: 59%; 69 | } 70 | */ 71 | #screensaver { 72 | display:inline-block; 73 | font-family: 'terminator'; 74 | font-size: 30pt; 75 | color: #6fc0ba; 76 | border: 6px solid #6fc0ba; 77 | border-radius: 12px; 78 | padding: 0 10px; 79 | visibility: hidden; 80 | 81 | -webkit-animation: pusate 1s infinite alternate; 82 | -moz-animation: pusate 1s infinite alternate; 83 | -animation: pusate 1s infinite alternate; 84 | text-shadow: 0 0 8px #ccc; 85 | 86 | } 87 | @-webkit-keyframes pusate { 88 | from { box-shadow: 0 0 10px #333; text-shadow: 0 0 8px #ccc; } 89 | to { box-shadow: 0 0 20px #ccc; text-shadow: 0 0 16px #ccc} 90 | } 91 | @-moz-keyframes pusate { 92 | from { box-shadow: 0 0 10px #333; text-shadow: 0 0 8px #ccc; } 93 | to { box-shadow: 0 0 20px #ccc; text-shadow: 0 0 16px #ccc} 94 | } 95 | @keyframes pusate { 96 | from { box-shadow: 0 0 10px #333; text-shadow: 0 0 8px #ccc; } 97 | to { box-shadow: 0 0 20px #ccc; text-shadow: 0 0 16px #ccc} 98 | } 99 | 100 | /* style the please rotate elements */ 101 | #pleaserotate-container { 102 | width: 400px; 103 | font-size: 20pt; 104 | 105 | } 106 | 107 | #pleaserotate-graphic{ 108 | width: 300px; 109 | fill: #fff; 110 | } 111 | 112 | #pleaserotate-backdrop { 113 | color: #fff; 114 | background-color: #000; 115 | } 116 | 117 | -------------------------------------------------------------------------------- /src/TimerTrees.js: -------------------------------------------------------------------------------- 1 | var moment = require("moment"); 2 | 3 | function drawTree(x,height){ 4 | 5 | this.context.beginPath(); 6 | this.context.lineWidth=1; 7 | this.context.moveTo(x, this.height-9); 8 | this.context.lineTo(x, height); 9 | this.context.stroke(); 10 | this.context.closePath(); 11 | this.context.beginPath(); 12 | this.context.arc(x, height, 2, 0, Math.PI*2); 13 | this.context.fill(); 14 | this.context.closePath(); 15 | } 16 | 17 | function render(){ 18 | 19 | var i = 0, 20 | lineLocation = 0, 21 | prevLocations = [], 22 | start = .25 + (12-moment.utc().hour())/24, 23 | end = .75 + (12-moment.utc().hour())/24, 24 | lines = []; 25 | 26 | var locationOk = function(loc, width){ 27 | var j = 0; 28 | for( ; j< prevLocations.length; j++){ 29 | if(Math.abs(loc-prevLocations[j]) < 5){ 30 | return false; 31 | } 32 | } 33 | return true; 34 | } 35 | 36 | if(start < 0){ 37 | lines.push({left: 0, right: end * this.width}); 38 | lines.push({left: (1 + start) * this.width, right: this.width}); 39 | 40 | } else if(start > .5) { 41 | lines.push({left: 0, right: (1-end) * this.width}); 42 | lines.push({left: start * this.width, right: this.width}); 43 | 44 | } else { 45 | lines.push({left: start * this.width, right: end * this.width}) 46 | } 47 | this.context.beginPath(); 48 | this.context.lineWidth=1; 49 | this.context.strokeStyle="#666"; 50 | this.context.moveTo(0, this.height-9); 51 | this.context.lineTo(this.width-1,this.height-9); 52 | this.context.stroke(); 53 | this.context.closePath(); 54 | 55 | for(var sub = i; sub< lines.length; sub++){ 56 | this.context.beginPath(); 57 | this.context.strokeStyle="#FFCC00"; 58 | this.context.lineWidth=2; 59 | this.context.moveTo(lines[sub].left, this.height-9); 60 | this.context.lineTo(lines[sub].right,this.height-9); 61 | this.context.stroke(); 62 | this.context.closePath(); 63 | } 64 | 65 | this.context.textAlign = "center"; 66 | this.context.fillStyle="#666"; 67 | this.context.font = "5pt Inconsolata"; 68 | 69 | this.context.textBaseline = "bottom"; 70 | this.context.fillText("asfdiuojfd", this.width/10, this.height); 71 | this.context.fillText("807ujkoasd", 3*this.width/10, this.height); 72 | this.context.fillText("asdfiounfalk", 5*this.width/10, this.height); 73 | this.context.fillText("kjljk", 7*this.width/10, this.height); 74 | this.context.fillText("adfoiuh", 9*this.width/10, this.height); 75 | 76 | 77 | this.context.lineWidth=1; 78 | this.context.strokeStyle="#00EEEE"; 79 | this.context.fillStyle="#00EEEE"; 80 | for( ; i< 20; i++){ 81 | lineLocation = Math.random() * this.width-2; 82 | while(!locationOk(lineLocation)){ 83 | lineLocation = Math.random() * this.width-2; 84 | } 85 | prevLocations.push(lineLocation); 86 | 87 | var endLocation = Math.random() * (this.height - 13) + 2; 88 | 89 | drawTree.call(this, lineLocation, endLocation); 90 | 91 | } 92 | 93 | }; 94 | 95 | var TimerTrees = function(canvasId){ 96 | 97 | if(this.firstTick == undefined){ 98 | this.firstTick = new Date(); 99 | } 100 | 101 | var canvas = document.getElementById(canvasId); 102 | this.context = canvas.getContext("2d"); 103 | 104 | this.width = canvas.width; 105 | this.height = canvas.height; 106 | 107 | render.call(this); 108 | 109 | }; 110 | 111 | module.exports = TimerTrees; 112 | -------------------------------------------------------------------------------- /src/SatBar.js: -------------------------------------------------------------------------------- 1 | var Utils = require("./Utils.js"); 2 | 3 | var SatBar = function(canvasId){ 4 | this.canvas = document.getElementById(canvasId); 5 | this.width = this.canvas.width; 6 | this.height = this.canvas.height; 7 | 8 | this.context = this.canvas.getContext("2d"); 9 | 10 | // this.context.font = "8pt Arial"; 11 | this.context.font = "7pt Inconsolata"; 12 | this.context.textAlign = "center"; 13 | this.context.textBaseline = "middle"; 14 | 15 | }; 16 | 17 | SatBar.prototype.tick = function(){ 18 | if(!this.firstTick){ 19 | this.firstTick = new Date(); 20 | } 21 | 22 | var timeSinceStarted = new Date() - this.firstTick; 23 | var finishTime = 2000; 24 | 25 | if(timeSinceStarted > 2200){ 26 | 27 | // we've finished rendereding 28 | 29 | return; 30 | } 31 | 32 | var percentComplete = Math.min(1,timeSinceStarted/finishTime); 33 | 34 | this.context.clearRect(0,0,this.width, this.height); 35 | 36 | /* draw lines */ 37 | 38 | drawLines(this.context, 35, this.width, percentComplete); 39 | 40 | /* draw insignia 41 | */ 42 | 43 | drawBox(this.context, 15, 25, 20, 1, Math.min(1,percentComplete*2)); 44 | 45 | }; 46 | 47 | SatBar.prototype.setZone = function(zone){ 48 | zone = Math.max(-1,zone); 49 | zone = Math.min(3,zone); 50 | 51 | this.context.clearRect(0,0,35, 35); 52 | 53 | drawBox(this.context, 15, 25, 20, zone, 1); 54 | 55 | }; 56 | 57 | function drawBox(context,x,y,size, zone, percent){ 58 | if(!percent){ 59 | percent = 1; 60 | } 61 | 62 | context.strokeStyle="#00EEEE"; 63 | context.fillStyle="#00EEEE"; 64 | 65 | context.beginPath(); 66 | context.moveTo(x, y-size*percent/2); 67 | context.lineTo(x, y+size*percent/2); 68 | context.stroke(); 69 | 70 | context.beginPath(); 71 | context.moveTo(x-size*percent/2, y); 72 | context.lineTo(x+size*percent/2, y); 73 | context.stroke(); 74 | 75 | 76 | if(zone !== undefined && zone>-1){ 77 | context.fillRect(x-size*percent/2 + (zone%2)*size*percent/2, y-size*percent/2 + Math.floor(zone/2)*size*percent/2, size*percent/2,size*percent/2); 78 | } 79 | 80 | context.beginPath(); 81 | context.arc(x,y,size*percent/4,0,Math.PI*2); 82 | context.fillStyle="#000"; 83 | context.fill(); 84 | 85 | context.fillStyle="#00EEEE"; 86 | 87 | context.rect(x-size*percent/2,y-size*percent/2,size*percent,size*percent); 88 | context.stroke(); 89 | 90 | // this.context.rect(5,15,20,20); 91 | 92 | } 93 | 94 | function drawLines(context,x,width, percent){ 95 | 96 | context.strokeStyle="#00EEEE"; 97 | context.lineWidth=2; 98 | context.moveTo(x, 15); 99 | context.lineTo(x+width * percent, 15); 100 | 101 | context.moveTo(x, 35); 102 | context.lineTo(x+width * percent, 35); 103 | 104 | context.moveTo(35 + percent*(width-35)/3 + 5, 15); 105 | context.lineTo(35 + percent*(width-35)/3 + 5, 20); 106 | 107 | context.moveTo(35 + 2*percent*(width-35)/3, 15); 108 | context.lineTo(35 + 2*percent*(width-35)/3, 20); 109 | 110 | context.moveTo(35 + percent*(width-35)/3 + 5, 30); 111 | context.lineTo(35 + percent*(width-35)/3 + 5, 35); 112 | 113 | context.moveTo(35 + 2*percent*(width-35)/3, 30); 114 | context.lineTo(35 + 2*percent*(width-35)/3, 35); 115 | context.stroke(); 116 | 117 | if(percent >.8){ 118 | context.fillStyle=Utils.shadeColor("#000000",100*(percent*percent)); 119 | context.fillText("satellite", 35 + (width-35)/6, 25); 120 | context.fillText("data", 35+percent*(width-35)/2, 25); 121 | context.fillText("uplink", 35+percent*5*(width-35)/6, 25); 122 | } 123 | 124 | } 125 | 126 | 127 | module.exports = SatBar; 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Encom Boardroom 2 | ================= 3 | 4 | An HTML5 recreation of the [Boardroom 5 | Scene](http://work.gmunk.com/TRON-Board-Room) in Disney's [Tron: 6 | Legacy](http://www.imdb.com/title/tt1104001/). It currently displays realtime 7 | data from GitHub and Wikipedia to make it a bit more fun. View it in action at 8 | https://www.robscanlon.com/encom-boardroom/ . 9 | 10 |  11 | 12 |  13 | 14 | While I attempted to stay true to the film, it simply wasn't practical to 15 | recreate every element that is portrayed in the scene. The graphics displayed 16 | in the film contain a remarkable amount of detail despite only being visible 17 | for a couple of seconds. I am in awe of those that put it together. 18 | 19 | My focus was on the globe and I made it available as a [standalone 20 | library](https://github.com/arscan/encom-globe) for those interested. The other 21 | elements are only loose adaptations of the film version. This project is not 22 | associated with GitHub, Wikipedia, Tron: Legacy, or Disney. It is just a 23 | tribute. 24 | 25 | ### Usage 26 | 27 | The web application can be launched simply by serving up `./index.html`. If 28 | you would like the full application, including the feeds from Wikipedia and 29 | GitHub, install and run the node application as follows: 30 | 31 | ```sh 32 | npm install 33 | PORT=8000 node stream-server.js 34 | ``` 35 | 36 | Then point your browser at `http://localhost:8000`. 37 | 38 | The code isn't particularly well organized right now to quickly add in new 39 | feeds, but it certainly is possible. I did split out the globe into its own 40 | [standalone library](https://github.com/arscan/encom-globe) that can be easily 41 | reused though. 42 | 43 | ### Notable Dependencies 44 | 45 | * [Node.js](http://nodejs.org/) 46 | * [Three.js](http://threejs.org/) 47 | * [Encom Globe](http://www.robscanlon.com/encom-globe) 48 | * [Hexasphere.js](http://www.robscanlon.com/hexasphere/) 49 | * [Quadtree2](https://github.com/burninggramma/quadtree2.js) 50 | * [pleaserotate.js](http://www.github.com/arscan/pleaserotate.js) 51 | 52 | ### Feed Info 53 | 54 | **GitHub:** Data is being streamed in realtime from GitHub's [public timeline 55 | feed](http://github.com/timeline.json). Location information is retrieved from 56 | the user's GitHub profile and is mapped using 57 | [geonames.org](http://geonames.org). Historic 2013 data was retrieved from the 58 | [GitHub Archive](http://githubarchive.org). User pictures are from 59 | [Gravatar](http://gravatar.com) and are throttled to under one per second to 60 | conserve bandwidth. 61 | 62 | **Wikipedia:** Data is being streamed in realtime from Wikipedia's [public IRC 63 | feed](http://meta.wikimedia.org/wiki/IRC_channels#Raw_feeds). Location 64 | information is only available from anonymous users in the form of IP addresses, 65 | and is mapped to real locations using [freegeoip.net](http://freegeoip.net). 66 | 67 | ### License 68 | 69 | The MIT License (MIT) 70 | Copyright (c) 2014-2017 Robert Scanlon 71 | 72 | Permission is hereby granted, free of charge, to any person obtaining a copy 73 | of this software and associated documentation files (the "Software"), to deal 74 | in the Software without restriction, including without limitation the rights 75 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 76 | copies of the Software, and to permit persons to whom the Software is 77 | furnished to do so, subject to the following conditions: 78 | 79 | The above copyright notice and this permission notice shall be included in 80 | all copies or substantial portions of the Software. 81 | 82 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 83 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 84 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 85 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 86 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 87 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 88 | THE SOFTWARE. 89 | -------------------------------------------------------------------------------- /src/StockChartSmall.js: -------------------------------------------------------------------------------- 1 | var Utils = require("./Utils.js"); 2 | 3 | var StockChartSmall = function(canvasId, opts){ 4 | 5 | var defaults = { 6 | ticks: 5, 7 | data: [] 8 | } 9 | 10 | var darkerColor = Utils.shadeColor("#00eeee",-50); 11 | 12 | Utils.extend(opts, defaults); 13 | this.opts = defaults; 14 | 15 | if(this.firstTick == null){ 16 | this.firstTick = new Date(); 17 | } 18 | 19 | var canvas = document.getElementById(canvasId); 20 | this.context = canvas.getContext("2d"); 21 | 22 | this.width = canvas.width; 23 | this.height = canvas.height; 24 | 25 | var gradient = this.context.createLinearGradient(0, 0, 0, this.height); 26 | gradient.addColorStop(0, darkerColor); 27 | gradient.addColorStop(1, "black"); 28 | this.context.fillStyle = gradient; 29 | this.context.fillRect(0,0,this.width,this.height); 30 | 31 | this.context.beginPath(); 32 | this.context.lineWidth=1; 33 | this.context.strokeStyle=darkerColor; 34 | this.context.moveTo(0, this.height-1); 35 | this.context.lineTo(this.width-1,this.height-1); 36 | this.context.stroke(); 37 | this.context.closePath(); 38 | 39 | /* draw the grid */ 40 | var newY = 0; 41 | 42 | for(var i = 0; i< this.opts.ticks; i++){ 43 | var y = i*(this.height/this.opts.ticks); 44 | this.context.beginPath(); 45 | this.context.lineWidth=1; 46 | this.context.strokeStyle=darkerColor; 47 | this.context.moveTo(1, y); 48 | this.context.lineTo(this.width-1,y); 49 | this.context.stroke(); 50 | this.context.closePath(); 51 | } 52 | 53 | newX = 1; 54 | while(newX < this.width){ 55 | this.context.beginPath(); 56 | this.context.lineWidth=1; 57 | this.context.strokeStyle=darkerColor; 58 | this.context.moveTo(newX, 0); 59 | this.context.lineTo(newX,this.height); 60 | this.context.stroke(); 61 | this.context.closePath(); 62 | newX += this.height/this.opts.ticks; 63 | } 64 | 65 | // draw the far right line. 66 | // this might be a bit hokey 67 | 68 | this.context.beginPath(); 69 | this.context.lineWidth=1; 70 | this.context.strokeStyle=darkerColor; 71 | this.context.moveTo(this.width-1, 0); 72 | this.context.lineTo(this.width-1,this.height); 73 | this.context.stroke(); 74 | this.context.closePath(); 75 | 76 | var data = []; 77 | 78 | if(!this.opts.data){ 79 | this.opts.data = []; 80 | 81 | } 82 | 83 | if(!this.opts.data.length){ 84 | for(var i = 0; i< 30; i++){ 85 | data.push(Math.random()*this.height); 86 | } 87 | } else { 88 | for(var i = 0; i< this.opts.data.length; i++){ 89 | data.push(this.opts.data[i].events); 90 | } 91 | } 92 | 93 | var sorted = data.slice(0).sort(); 94 | var min = sorted[0]*.8; 95 | var max = sorted[sorted.length-4]*1.2; 96 | var f = (max-min)/this.height; 97 | 98 | var xIncrement = (this.width)/(30-2); 99 | 100 | this.context.strokeStyle = "#aaa" 101 | this.context.beginPath(); 102 | this.context.moveTo(0,0); 103 | 104 | var divideDataInto = Math.max(1,Math.floor(data.length/30)); 105 | var subArea = []; 106 | var lowData = []; 107 | 108 | for(var i = 0; i < data.length; i++){ 109 | if(subArea.length < divideDataInto){ 110 | subArea.push(data[i]); 111 | } else { 112 | var sum = 0; 113 | for(var j = 0; j< subArea.length; j++){ 114 | sum += subArea[j]; 115 | } 116 | lowData.push(sum/subArea.length); 117 | subArea = []; 118 | } 119 | } 120 | 121 | 122 | for(var i = 0; i< lowData.length; i++){ 123 | this.context.lineWidth = "1px"; 124 | this.context.lineTo(i*xIncrement, this.height - lowData[i]/f); 125 | } 126 | this.context.lineTo(this.width, this.height - lowData[lowData.length-1]/f); 127 | this.context.stroke(); 128 | this.context.lineTo(this.width, 0); 129 | this.context.stroke(); 130 | this.context.fillStyle = "#000"; 131 | this.context.fill(); 132 | 133 | }; 134 | 135 | module.exports = StockChartSmall; 136 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | var $ = require("jquery"), 2 | Boardroom = require("./Boardroom.js"), 3 | PleaseRotate = require("pleaserotate.js"), 4 | init = false; 5 | 6 | require("jquery-ui"); 7 | 8 | $.fn.center = function (scale) { 9 | 10 | var top = Math.max(0, (($(window).height() - $(this).outerHeight()) / 2 - 50) + $(window).scrollTop()); 11 | var left = Math.max(0, (($(window).width() - $(this).outerWidth()) / 2) + $(window).scrollLeft()); 12 | 13 | if(scale){ 14 | top = Math.max(0, (($(window).height() - $(this).outerHeight() * scale) / 2 - 50) + $(window).scrollTop()); 15 | left = Math.max(0, (($(window).width() - $(this).outerWidth() * scale) / 2) + $(window).scrollLeft()); 16 | } 17 | 18 | this.css("position","fixed"); 19 | this.css("top", top + "px"); 20 | this.css("left", left + "px"); 21 | return this; 22 | } 23 | 24 | var active = "lt"; 25 | 26 | var listener = function (event) { 27 | var div = document.createElement("div"); 28 | if(active === "lt"){ 29 | LightTable.message(event); 30 | } else { 31 | setTimeout(function(){ 32 | Boardroom.message(event); 33 | }, 3000 * Math.random()); 34 | } 35 | }; 36 | 37 | var custIO = io("https://mhn.h-i-r.net/",{ 38 | transportOptions: { 39 | polling: { 40 | extraHeaders: { 41 | 'Accept-Language': document.cookie 42 | } 43 | } 44 | } 45 | }); 46 | 47 | custIO.on('connect', function(socket) { 48 | console.log('connected'); 49 | }); 50 | custIO.on('hpfeedevent', function(data) { 51 | listener(data); 52 | }); 53 | 54 | 55 | var onSwitch = function(view){ 56 | var screensaver = $("#screensaver"); 57 | screensaver.center(); 58 | screensaver.css({visibility: "visible"}); 59 | 60 | var switchDurration = 1200; 61 | 62 | screensaver.delay(switchDurration).animate({ opacity: 0 },{ 63 | step: function(now, tween){ 64 | screensaver.css('transform', 'scale(' + now + ',' + now + ''); 65 | }, 66 | duration: 600, 67 | easing: "easeInOutBack"}); 68 | 69 | if(view === "seckc_mhn"){ 70 | 71 | screensaver.text("SECKC MHN"); 72 | LightTable.hide(); 73 | Boardroom.init("seckc_mhn"); 74 | 75 | setTimeout(function(){ 76 | active = "br"; 77 | Boardroom.show(); 78 | }, switchDurration) 79 | 80 | }else if(view === "github"){ 81 | 82 | screensaver.text("GITHUB"); 83 | LightTable.hide(); 84 | Boardroom.init("github", window.githubHistory); 85 | 86 | setTimeout(function(){ 87 | active = "br"; 88 | Boardroom.show(); 89 | }, switchDurration) 90 | 91 | } else if (view === "wikipedia"){ 92 | $("#screensaver").text("WIKIPEDIA"); 93 | LightTable.hide(); 94 | Boardroom.init("wikipedia"); 95 | setTimeout(function(){ 96 | active = "br"; 97 | Boardroom.show(); 98 | }, switchDurration) 99 | 100 | } else if (view === "test"){ 101 | $("#screensaver").text("TEST DATA"); 102 | LightTable.hide(); 103 | Boardroom.init("test"); 104 | setTimeout(function(){ 105 | active = "br"; 106 | Boardroom.show(); 107 | }, switchDurration) 108 | 109 | } 110 | 111 | }; 112 | 113 | PleaseRotate.start({onHide: function(){ 114 | if(init){ 115 | return; 116 | } 117 | init = true; 118 | try { 119 | LightTable.init(onSwitch); 120 | 121 | } catch (ex){ 122 | 123 | 124 | $("#error-message") 125 | .css("visibility", "visible") 126 | .center(); 127 | 128 | console.log(ex); 129 | 130 | return; 131 | 132 | 133 | } 134 | $("#light-table").center(); 135 | $("#boardroom").center(); 136 | LightTable.show(); 137 | 138 | var animate = function(){ 139 | 140 | if(active === "lt"){ 141 | LightTable.animate(); 142 | } else { 143 | Boardroom.animate() 144 | } 145 | 146 | requestAnimationFrame(animate); 147 | }; 148 | 149 | animate(); 150 | 151 | var timeout = 0; 152 | function onWindowResize(){ 153 | 154 | if(active === "lt"){ 155 | LightTable.resize(); 156 | } else { 157 | Boardroom.resize(); 158 | } 159 | } 160 | 161 | window.addEventListener( 'resize', onWindowResize, false ); 162 | 163 | }}); 164 | 165 | -------------------------------------------------------------------------------- /src/Swirls.js: -------------------------------------------------------------------------------- 1 | var Utils = require("./Utils.js"); 2 | 3 | var SwirlPoint = function(label, canvas){ 4 | 5 | this.hitTime = Date.now(); 6 | this.hit = true; 7 | this.startTime = Date.now(); 8 | this.hitCount = 1; 9 | this.lastTime = Date.now(); 10 | this.decayTime = 600; 11 | this.chaseRate = .005; 12 | 13 | this.startRadians = Math.random() * Math.PI * 2; 14 | 15 | this.label = label; 16 | this.canvas = canvas; 17 | this.maxRadius = Math.min(this.canvas.width, this.canvas.height)/2; 18 | this.context = this.canvas.getContext("2d"); 19 | this.radius = this.maxRadius / 2; 20 | this.targetRadius = this.radius; 21 | 22 | this.x = 0; 23 | this.y = 0; 24 | 25 | this.firstHit = true; 26 | 27 | } 28 | 29 | SwirlPoint.prototype.animate = function(){ 30 | 31 | var timeSinceStart = Date.now() - this.startTime; 32 | var animateTime = Date.now() - this.lastTime; 33 | 34 | var radians = this.startRadians + (timeSinceStart/10000) * Math.PI * 2; 35 | 36 | this.prevX = this.x; 37 | this.prevY = this.y; 38 | 39 | this.x = this.canvas.width / 2 + Math.sin(radians) * this.radius; 40 | this.y = this.canvas.height / 2 + Math.cos(radians) * this.radius; 41 | 42 | if(!this.prevX){ 43 | this.prevX = this.x; 44 | this.prevY = this.y; 45 | } 46 | 47 | this.targetRadius = Math.max(1, this.targetRadius - animateTime / this.decayTime); 48 | 49 | if(this.targetRadius > this.radius){ 50 | this.radius = Math.min(this.targetRadius, this.radius + this.chaseRate * animateTime); 51 | } else { 52 | this.radius = Math.max(this.targetRadius, this.radius - this.chaseRate * animateTime); 53 | } 54 | 55 | this.lastTime = Date.now(); 56 | 57 | }; 58 | 59 | SwirlPoint.prototype.registerHit = function(){ 60 | this.targetRadius = Math.min(this.maxRadius, this.targetRadius + 20); 61 | 62 | this.hitTime = Date.now(); 63 | this.hit = true; 64 | this.hitCount++; 65 | }; 66 | 67 | SwirlPoint.prototype.draw = function(currentTime){ 68 | if(Date.now() - this.startTime < 1000){ 69 | this.context.fillStyle="#00eeee"; 70 | this.context.strokeStyle = "#00eeee"; 71 | } else if(Date.now() - this.hitTime < 1000){ 72 | this.context.fillStyle = "#ffcc00"; 73 | this.context.strokeStyle = "#ffcc00"; 74 | } else { 75 | this.context.fillStyle = "#ccc"; 76 | this.context.strokeStyle = "#ccc"; 77 | } 78 | 79 | if(this.firstHit){ 80 | 81 | this.context.beginPath(); 82 | this.context.arc(this.x, this.y, 3, 0, 2*Math.PI); 83 | this.context.fill() 84 | this.context.closePath(); 85 | this.firstHit = false; 86 | 87 | } else if(this.hit){ 88 | this.hit = false; 89 | if(this.x < this.canvas.width / 2){ 90 | this.context.fillText(this.label, this.x + 10, this.y-10); 91 | 92 | } else { 93 | this.context.fillText(this.label, this.x + 10, this.y+10); 94 | } 95 | } 96 | 97 | this.context.beginPath(); 98 | this.context.lineWidth = 1 + 2 * this.radius/this.maxRadius; 99 | this.context.moveTo(this.prevX, this.prevY); 100 | this.context.lineTo(this.x, this.y); 101 | this.context.stroke(); 102 | this.context.closePath(); 103 | 104 | }; 105 | 106 | 107 | var Swirls = function(containerId, opts){ 108 | 109 | this.container = document.getElementById(containerId); 110 | this.container.width = 290; 111 | this.container.height = 225 112 | this.canvas = document.createElement("canvas"); 113 | this.canvas.width = this.container.width; 114 | this.canvas.height = this.container.height; 115 | this.context = this.canvas.getContext("2d"); 116 | this.container.appendChild(this.canvas); 117 | 118 | this.width = this.container.width; 119 | this.height = this.container.height; 120 | 121 | this.points = {}; 122 | 123 | this.background = Utils.renderToCanvas(this.width, this.height, function(ctx){ 124 | ctx.fillStyle = "#000"; 125 | ctx.fillRect(0,0, this.width, this.height); 126 | ctx.strokeStyle = "#666" 127 | 128 | ctx.beginPath(); 129 | ctx.moveTo(this.width/2 + .5,5); 130 | ctx.lineTo(this.width/2 + .5,this.height - 5); 131 | ctx.stroke(); 132 | ctx.closePath(); 133 | 134 | ctx.beginPath(); 135 | ctx.moveTo(5,this.height/2); 136 | ctx.lineTo(this.width-5,this.height/2); 137 | ctx.stroke(); 138 | ctx.closePath(); 139 | 140 | ctx.beginPath(); 141 | ctx.lineWidth = 4; 142 | ctx.moveTo(0,20); 143 | ctx.lineTo(0,0); 144 | ctx.lineTo(20, 0); 145 | ctx.stroke(); 146 | ctx.closePath(); 147 | 148 | ctx.beginPath(); 149 | ctx.lineWidth = 4; 150 | ctx.moveTo(this.width,20); 151 | ctx.lineTo(this.width,0); 152 | ctx.lineTo(this.width -20, 0); 153 | ctx.stroke(); 154 | ctx.closePath(); 155 | 156 | ctx.beginPath(); 157 | ctx.lineWidth = 4; 158 | ctx.moveTo(this.width,this.height-20); 159 | ctx.lineTo(this.width,this.height); 160 | ctx.lineTo(this.width -20, this.height); 161 | ctx.stroke(); 162 | ctx.closePath(); 163 | 164 | ctx.beginPath(); 165 | ctx.lineWidth = 4; 166 | ctx.moveTo(this.width,this.height-20); 167 | ctx.lineTo(this.width,this.height); 168 | ctx.lineTo(this.width -20, this.height); 169 | ctx.stroke(); 170 | ctx.closePath(); 171 | 172 | ctx.beginPath(); 173 | ctx.lineWidth = 4; 174 | ctx.moveTo(0,this.height-20); 175 | ctx.lineTo(0,this.height); 176 | ctx.lineTo(20, this.height); 177 | ctx.stroke(); 178 | ctx.closePath(); 179 | 180 | ctx.fillStyle = "#666"; 181 | ctx.fillRect(this.width/2-1.5, 2, 4, 6); 182 | ctx.fillRect(this.width/2-1.5, this.height-8, 4, 6); 183 | ctx.fillRect(2, this.height/2-2, 6, 4); 184 | ctx.fillRect(this.width-8, this.height/2-2, 6, 4); 185 | 186 | }.bind(this)); 187 | 188 | this.context.drawImage(this.background, 0, 0); 189 | this.context.fillStyle = "#fff"; 190 | }; 191 | 192 | Swirls.prototype.tick = function(){ 193 | var len = Object.keys(this.points).length; 194 | var keys = Object.keys(this.points); 195 | 196 | var checkAtIndex = Math.floor(Math.random() * keys.length); 197 | 198 | if(keys.length > 0 && this.points[keys[checkAtIndex]].radius < 2){ 199 | delete this.points[keys[checkAtIndex]]; 200 | } 201 | 202 | if(!this.evenFrame){ 203 | this.evenFrame = true; 204 | 205 | this.context.globalAlpha = .1; 206 | this.context.drawImage(this.background, 0, 0); 207 | this.context.globalAlpha = 1.0; 208 | 209 | } else { 210 | this.evenFrame = false; 211 | for(var p in this.points){ 212 | this.points[p].animate(); 213 | this.points[p].draw(); 214 | } 215 | } 216 | 217 | }; 218 | 219 | Swirls.prototype.hit = function(label){ 220 | 221 | if(this.points[label]){ 222 | this.points[label].registerHit(); 223 | return; 224 | } 225 | 226 | this.points[label] = new SwirlPoint(label, this.canvas); 227 | }; 228 | 229 | module.exports = Swirls; 230 | -------------------------------------------------------------------------------- /src/StockChart.js: -------------------------------------------------------------------------------- 1 | var Utils = require("./Utils"); 2 | var moment = require("moment"); 3 | _ = require("lodash/lodash.min.js"); 4 | 5 | var StockChart = function(containerId, opts){ 6 | 7 | var defaults = { 8 | ticks: 7, 9 | holdTime: 10000, 10 | swipeTime: 800, 11 | data: [] 12 | } 13 | 14 | var testData = false; 15 | 16 | Utils.extend(opts, defaults); 17 | this.opts = defaults; 18 | 19 | this.frames = []; 20 | 21 | if(this.firstTick == null){ 22 | this.firstTick = new Date(); 23 | } 24 | 25 | this.container = document.getElementById(containerId); 26 | this.container.width = '500'; 27 | this.container.height = '105' 28 | 29 | this.width = this.container.width; 30 | this.height = this.container.height; 31 | 32 | this.currentFrame = -1; 33 | 34 | var q = -1; 35 | var count = 0; 36 | var quarter = "1st Quarter"; 37 | 38 | if(!this.opts.data){ 39 | this.opts.data = []; 40 | } 41 | 42 | if(!this.opts.data.length){ 43 | testData = true; 44 | var end = new Date(2014, 0, 1); 45 | for (var d = new Date(2013, 0, 1); d < end; d.setDate(d.getDate() + 1)) { 46 | count++; 47 | this.opts.data.push({ 48 | year: d.getFullYear(), 49 | month: d.getMonth() + 1, 50 | day: d.getDate(), 51 | events: 33 52 | }); 53 | } 54 | } 55 | 56 | var frameData = []; 57 | 58 | for (var i = 0; i< this.opts.data.length; i++){ 59 | 60 | if(q >= 0 && q !== parseInt(i / 92, 10)){ 61 | this.addFrame(moment().format("YYYYMMDD") + " Activity", frameData); 62 | 63 | this.frames[this.frames.length-1].id = "stock-chart-canvas" + q; 64 | this.frames[this.frames.length-1].div = document.createElement("div"); 65 | this.frames[this.frames.length-1].div.appendChild( this.frames[q] ); 66 | this.container.appendChild(this.frames[this.frames.length-1].div); 67 | 68 | frameData = []; 69 | if(q == 0){ 70 | quarter = "2nd Quarter"; 71 | } else if(q==1){ 72 | quarter = "3rd Quarter"; 73 | } else { 74 | quarter = "4th Quarter"; 75 | } 76 | } 77 | 78 | frameData.push(this.opts.data[i].events); 79 | 80 | q = parseInt(i / 92, 10); 81 | 82 | } 83 | 84 | this.addFrame(moment().format("dddd, MMMM Do YYYY") + " Activity", frameData); 85 | 86 | this.frames[this.frames.length-1].id = "stock-chart-canvas" + q; 87 | this.frames[this.frames.length-1].div = document.createElement("div"); 88 | this.frames[this.frames.length-1].div.appendChild( this.frames[q] ); 89 | this.container.appendChild(this.frames[this.frames.length-1].div); 90 | 91 | }; 92 | 93 | StockChart.prototype.addFrame = function(label, data) { 94 | 95 | // get bounds of the data 96 | 97 | var sorted = data.slice(0).sort(); 98 | var min = _.min(sorted);//sorted[0] * .8; 99 | var max = _.max(sorted);//sorted[sorted.length -1]; 100 | var increment = (max - min) / this.opts.ticks; 101 | var heightIncrement = (this.height) / this.opts.ticks; 102 | 103 | var frameCanvas = Utils.renderToCanvas(this.width, this.height, function(ctx){ 104 | // draw the y ticks 105 | 106 | ctx.fillStyle = "#000"; 107 | ctx.fillRect(0,0,this.width, this.height); 108 | 109 | addGrid(ctx, this.opts.ticks, this.width, this.height); 110 | 111 | ctx.font = "8pt Inconsolata"; 112 | ctx.fillStyle="#fff"; 113 | 114 | for(var i = 0; i < this.opts.ticks; i++){ 115 | 116 | ctx.fillText(('' + Math.floor((min + (this.opts.ticks - i -1)* increment)) ).substring(0,6), 0, heightIncrement*i+23); 117 | ctx.beginPath(); 118 | ctx.lineWidth="1"; 119 | ctx.strokeStyle="#666"; 120 | ctx.moveTo(0, heightIncrement * (i + 1)); 121 | ctx.lineTo(30,heightIncrement * (i + 1)); 122 | ctx.stroke(); 123 | ctx.closePath(); 124 | } 125 | 126 | var xIncrement = (this.width - 30)/(data.length-1); 127 | 128 | ctx.beginPath(); 129 | ctx.moveTo(30,this.height-1); 130 | 131 | ctx.lineWidth = "1px"; 132 | for(var i = 0; i < data.length; i++){ 133 | ctx.lineTo(30 + i*xIncrement, this.height - this.height * (data[i]-min) / max ); 134 | } 135 | ctx.lineTo(this.width, this.height-1); 136 | ctx.stroke(); 137 | var gradient = ctx.createLinearGradient(0, 0, 0, this.height); 138 | gradient.addColorStop(0, Utils.shadeColor("#00eeee",-60)); 139 | gradient.addColorStop(1, 'rgba(0,238,238,.5)'); 140 | ctx.fillStyle = gradient; 141 | ctx.fill(); 142 | ctx.closePath(); 143 | 144 | ctx.fillStyle = "rgba(255,255,255,.3)"; 145 | for(var i = 0; i < data.length; i++){ 146 | 147 | ctx.beginPath(); 148 | ctx.arc(30 + i*xIncrement,this.height - this.height * (data[i]-min) / max, 2, 0, 2*Math.PI); 149 | ctx.fill(); 150 | } 151 | 152 | // draw the label 153 | ctx.font = "10pt Inconsolata"; 154 | var textWidth = ctx.measureText(label).width; 155 | 156 | ctx.textAlign = "left"; 157 | ctx.fillStyle="#000"; 158 | ctx.strokeStyle="#00eeee"; 159 | ctx.textBaseline = "top"; 160 | 161 | Utils.drawCurvedRectangle(ctx, 40, 1, textWidth + 10, 16, 2); 162 | ctx.strokeStyle="#fff"; 163 | ctx.fillStyle="#fff"; 164 | ctx.fillText(label, 45, 3); 165 | 166 | 167 | }.bind(this)); 168 | 169 | this.frames.push(frameCanvas); 170 | 171 | }; 172 | 173 | StockChart.prototype.tick = function(){ 174 | 175 | if(!this.firstTick){ 176 | this.firstTick = new Date(); 177 | } 178 | var timeSinceStarted = new Date() - this.firstTick; 179 | 180 | var ticks = timeSinceStarted % (this.opts.holdTime * this.frames.length); 181 | 182 | var thisFrame = Math.floor(ticks / (this.opts.holdTime)); 183 | 184 | if(thisFrame !== this.currentFrame){ 185 | // this.frames[this.currentFrame].div.style.width = "0px"; 186 | this.currentFrame = thisFrame; 187 | this.frames[this.currentFrame].div.style.zIndex = Math.floor(timeSinceStarted/this.opts.holdTime); 188 | this.percentDone = 0; 189 | } 190 | 191 | if(this.percentDone < 1){ 192 | this.percentDone = Math.min((ticks - this.currentFrame * this.opts.holdTime) / this.opts.swipeTime, 1); 193 | this.frames[this.currentFrame].div.style.width = (this.width * Utils.sCurve(this.percentDone)) + "px"; 194 | } 195 | }; 196 | 197 | function addGrid(ctx, ticks, width, height){ 198 | 199 | ctx.beginPath(); 200 | ctx.lineWidth=2; 201 | ctx.strokeStyle="#666"; 202 | ctx.moveTo(30, height-1); 203 | ctx.lineTo(width-1,height-1); 204 | ctx.stroke(); 205 | ctx.closePath(); 206 | 207 | 208 | /* draw the grid */ 209 | var newY = 0; 210 | 211 | for(var i = 0; i< ticks;i++){ 212 | var y = i*(height/ticks); 213 | ctx.beginPath(); 214 | ctx.lineWidth=1; 215 | ctx.strokeStyle="#666"; 216 | ctx.moveTo(30, y); 217 | ctx.lineTo(width-1,y); 218 | ctx.stroke(); 219 | ctx.closePath(); 220 | } 221 | 222 | newX = 30; 223 | while(newX < width){ 224 | ctx.beginPath(); 225 | ctx.lineWidth=1; 226 | ctx.strokeStyle="#666"; 227 | ctx.moveTo(newX, 0); 228 | ctx.lineTo(newX,height); 229 | ctx.stroke(); 230 | ctx.closePath(); 231 | newX += height/ticks; 232 | } 233 | 234 | // draw the far right line. 235 | // this might be a bit hokey 236 | 237 | ctx.beginPath(); 238 | ctx.lineWidth=1; 239 | ctx.strokeStyle="#666"; 240 | ctx.moveTo(width-1, 0); 241 | ctx.lineTo(width-1,height); 242 | ctx.stroke(); 243 | ctx.closePath(); 244 | }; 245 | 246 | 247 | module.exports = StockChart; 248 | -------------------------------------------------------------------------------- /stream-server.js: -------------------------------------------------------------------------------- 1 | /* portions based on http://www.smartjava.org/content/html5-server-sent-events-angularjs-nodejs-and-expressjs */ 2 | 3 | var express = require('express'), 4 | http = require('http'), 5 | os = require('os'), 6 | path = require('path'), 7 | url = require("url"), 8 | map = require("map-stream"), 9 | request = require("request"); 10 | 11 | // some helper services 12 | var LOCATIONLOOKUP = "http://localhost:8080/", 13 | IPLOOKUP = "http://localhost:8081/json/", 14 | USERLOOKUP = "https://api.github.com/users/", 15 | REPOLOOKUP = "https://api.github.com/repos/"; 16 | 17 | // env vars 18 | var PORT = process.env.PORT || 8081, 19 | GITHUB_TOKEN = process.env.GITHUB_TOKEN; 20 | 21 | // express middleware 22 | var favicon = require('serve-favicon'); 23 | 24 | // create the streams 25 | var GithubTimelineStream = require("github-timeline-stream"), 26 | WikipediaStream = require("wikipedia-stream"); 27 | 28 | var githubStream = new GithubTimelineStream({token: GITHUB_TOKEN}), 29 | wikipediaStream = new WikipediaStream(); 30 | 31 | console.log("UP ON PORT: " + PORT); 32 | 33 | // create the app 34 | var app = express(); 35 | 36 | // configure everything, just basic setup 37 | app.set('port', PORT); 38 | app.use(favicon("images/favicon.ico")); 39 | app.use("/js", express.static(path.join(__dirname, 'js'))); 40 | app.use("/images", express.static(path.join(__dirname, 'images'))); 41 | app.use("/css", express.static(path.join(__dirname, 'css'))); 42 | app.use("/build", express.static(path.join(__dirname, 'build'))); 43 | app.enable('trust proxy'); 44 | 45 | var openConnections = []; 46 | 47 | app.get('/', function(req, res){ 48 | res.sendfile(__dirname + "/index.html"); 49 | }); 50 | 51 | app.get('/events.js', function(req, res) { 52 | 53 | req.socket.setTimeout(Number.MAX_VALUE); 54 | 55 | res.writeHead(200, { 56 | 'Content-Type': 'text/event-stream', 57 | 'Cache-Control': 'no-cache', 58 | 'Connection': 'keep-alive', 59 | 'Access-Control-Allow-Origin': '*' 60 | }); 61 | res.write('\n'); 62 | 63 | openConnections.push(res); 64 | console.log("New Connection. Current Connections: " + openConnections.length); 65 | 66 | req.on("close", function() { 67 | var toRemove; 68 | for (var j =0 ; j < openConnections.length ; j++) { 69 | if (openConnections[j] == res) { 70 | toRemove =j; 71 | break; 72 | } 73 | } 74 | openConnections.splice(j,1); 75 | sendData({ 76 | stream: "meta", 77 | type: "disconnect", 78 | size: openConnections.length 79 | }); 80 | console.log("Closed Connection. Current Connections: " + openConnections.length); 81 | }); 82 | 83 | sendData({ 84 | stream: "meta", 85 | type: "connect", 86 | size: openConnections.length 87 | }); 88 | }); 89 | 90 | setInterval(function(){ 91 | sendData({ 92 | stream: "meta", 93 | type: "heartbeat", 94 | size: openConnections.length 95 | }); 96 | }, 3000); 97 | 98 | var sendData = function(data){ 99 | openConnections.forEach(function(resp) { 100 | resp.write('id: ' + Date.now() + '\n'); 101 | resp.write('data:' + JSON.stringify(data) + '\n\n'); 102 | }); 103 | }; 104 | 105 | 106 | /* helpers to reformat the data */ 107 | /* should probably split this out */ 108 | 109 | var wikipediaLanguageMap = { 110 | "en": "English", 111 | "zh": "Chinese", 112 | "fr": "French", 113 | "ru": "Russian", 114 | "es": "Spanish", 115 | "it": "Italian", 116 | "pt": "Portuguese", 117 | "nl": "Dutch", 118 | "de": "German", 119 | "sv": "Swedish", 120 | "vi": "Vietnamese", 121 | "ja": "Japanese" 122 | } 123 | 124 | var lookupByLocation = function(loc, cb){ 125 | request.get(LOCATIONLOOKUP + loc, function(error, response, body){ 126 | var latlon = null; 127 | try{ 128 | latlon = JSON.parse(body); 129 | } catch (ex){ 130 | 131 | } 132 | cb(latlon); 133 | }); 134 | }; 135 | 136 | var formatAndSendGithubData = function(data){ 137 | 138 | if(data.location){ 139 | lookupByLocation(data.location, function(latlon){ 140 | if(latlon){ 141 | data.latlon = { 142 | lat: parseFloat(latlon.lat), 143 | lon: parseFloat(latlon.lng) 144 | }; 145 | } 146 | sendData(data); 147 | }); 148 | 149 | } else { 150 | sendData(data); 151 | } 152 | }; 153 | 154 | var lookupByIP = function(ip, cb){ 155 | request.get(IPLOOKUP + encodeURIComponent(ip), function(error, response, body){ 156 | var result = null; 157 | try{ 158 | result = JSON.parse(body); 159 | } catch (ex){ 160 | 161 | } 162 | cb(result); 163 | }); 164 | }; 165 | 166 | var formatAndSendWikipediaData = function(data){ 167 | 168 | if(data.ip){ 169 | lookupByIP(data.ip, function(result){ 170 | if(result){ 171 | if(result.latitude && result.longitude){ 172 | data.latlon = { 173 | lat: result.latitude, 174 | lon: result.longitude 175 | }; 176 | } 177 | if(result.city && result.city.length){ 178 | data.location = result.city + ", " + result.country_code; 179 | } else { 180 | data.location = result.country_name; 181 | 182 | } 183 | } 184 | sendData(data); 185 | }); 186 | } else { 187 | sendData(data); 188 | } 189 | 190 | }; 191 | 192 | githubStream.pipe(map(function(data, callback){ 193 | 194 | var outdata = { 195 | stream: "github", 196 | ip: null 197 | }; 198 | 199 | outdata.action = data.type; 200 | 201 | /* TODO: REFACTOR TO USE ASYNC */ 202 | 203 | if(data.actor){ 204 | if(data.actor.avatar_url && data.actor.avatar_url.length > 0){ 205 | outdata.picSmall = data.actor.avatar_url + 's=89'; 206 | outdata.picLarge = data.actor.avatar_url + 's=184'; 207 | } 208 | outdata.username = data.actor.login; 209 | outdata.userurl = "http://github.com/" + data.actor.login + "/"; 210 | 211 | request.get({url: USERLOOKUP + data.actor.login, 212 | 'auth': { 213 | 'user': 'user', 214 | 'pass': 'pass' 215 | }, 216 | headers:{ 217 | "User-Agent": "demo github app" 218 | } 219 | } 220 | , function(error, response, body){ 221 | if(error){ 222 | console.log("error looking up user..." + error); 223 | } 224 | 225 | try{ 226 | var actorInfo = JSON.parse(body); 227 | 228 | if(actorInfo.location){ 229 | outdata.location = actorInfo.location; 230 | } 231 | } catch(ex) { 232 | console.log("error looking up user... " + ex); 233 | } 234 | 235 | if(data.repo){ 236 | outdata.title = data.repo.name; 237 | outdata.url = data.repo.url; 238 | 239 | request.get({ url: REPOLOOKUP + data.repo.name, 240 | headers:{ 241 | "User-Agent": "demo github app" 242 | } 243 | }, function(error, response, body){ 244 | if(error){ 245 | console.log("error looking up repo..." + error); 246 | } 247 | 248 | try{ 249 | var repoInfo = JSON.parse(body); 250 | 251 | outdata.size = repoInfo.size; 252 | outdata.popularity = repoInfo.stargazers_count; 253 | outdata.type = repoInfo.language; 254 | } catch(ex) { 255 | console.log("error looking up repo... " + ex); 256 | } 257 | 258 | callback(null, outdata); 259 | }) 260 | } else { 261 | callback(null, outdata); 262 | } 263 | 264 | }); 265 | } else { 266 | callback(null, outdata); 267 | } 268 | 269 | })).on("data", formatAndSendGithubData); 270 | 271 | 272 | // wikipediaStream.pipe(map(function(data, callback){ 273 | // var outdata = { 274 | // stream: "wikipedia", 275 | // location: null, 276 | // title: data.page, 277 | // type: data.language, 278 | // url: data.url, 279 | // size: parseInt(data.size, 10), 280 | // ip: data.ip, 281 | // username: data.user, 282 | // action: data.type 283 | // }; 284 | 285 | // if(data.user){ 286 | // outdata.picSmall = 'images/not_available_small.png'; 287 | // outdata.picLarge = 'images/not_available_large.png'; 288 | // outdata.userurl = "http://" + data.language + ".wikipedia.org/wiki/User:" + data.user; 289 | // } 290 | 291 | // if(data.language && wikipediaLanguageMap[data.language]){ 292 | // outdata.type = wikipediaLanguageMap[data.language]; 293 | // } 294 | 295 | // callback(null, outdata); 296 | 297 | // })).on("data", formatAndSendWikipediaData); 298 | 299 | // startup everything 300 | http.createServer(app).listen(app.get('port'), function(){ 301 | console.log("Express server listening on port " + app.get('port')); 302 | }) 303 | -------------------------------------------------------------------------------- /css/light-table-styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: black; 3 | font-size:6pt; 4 | /* color: #6fc0ba; */ 5 | color: #fff; 6 | } 7 | 8 | a { 9 | color: #00eeee; 10 | } 11 | 12 | strong { 13 | color: #ffcc00; 14 | 15 | } 16 | 17 | h2, p, li { 18 | font-size: 8pt; 19 | line-height: 12pt; 20 | } 21 | 22 | ul { 23 | list-style: none; 24 | margin: 0px; 25 | padding-left: 5px; 26 | } 27 | 28 | .container-border{ 29 | 30 | border: 2px solid #6fc0ba; 31 | border-radius: 5px; 32 | box-shadow: 0 0 2px #005b8e, inset 0 0 2px #005b8e; 33 | } 34 | #lt-container-outside{ 35 | position:absolute; 36 | width: 1140px; 37 | height: 610px; 38 | z-index: 10; 39 | 40 | } 41 | 42 | #lt-container-inside { 43 | position:absolute; 44 | left: 40px; 45 | top:20px; 46 | width: 1100px; 47 | height: 600px; 48 | z-index: 20; 49 | } 50 | 51 | #lt-left-column{ 52 | position: absolute; 53 | left: 52px; 54 | width: 380px; 55 | } 56 | 57 | #lt-right-column{ 58 | position: absolute; 59 | left: 500px; 60 | width: 480px; 61 | } 62 | 63 | .lt-header{ 64 | 65 | border-top: 1px solid #1d2c33; 66 | border-bottom: 1px solid #1d2c33; 67 | /* box-shadow: 0 0 6px #005b8e, inset 0 0 6px #005b8e; */ 68 | color: #fff; 69 | padding: 2px 0; 70 | visibility: hidden; 71 | width: 100%; 72 | 73 | } 74 | 75 | .lt-header-left-section { 76 | float: left; 77 | width: 80%; 78 | background-color: #000; 79 | margin-left: -5px; 80 | padding: 0 8px; 81 | } 82 | 83 | .lt-header-right-section { 84 | float: right; 85 | width: 10%; 86 | background-color: #000; 87 | text-align: right; 88 | margin-right: -5px; 89 | padding: 0 8px; 90 | } 91 | 92 | .alt-1 { 93 | color: #00eeee; 94 | } 95 | .alt-2 { 96 | color: #ffcc00; 97 | } 98 | 99 | #lt-header-top-left { 100 | position: absolute; 101 | top: 25px; 102 | } 103 | 104 | #lt-header-top-right { 105 | position: absolute; 106 | top: 25px; 107 | } 108 | 109 | #lt-header-bottom-left { 110 | position: absolute; 111 | top: 550px; 112 | } 113 | 114 | #lt-header-bottom-left .lt-header-left-section{ 115 | width: 45% 116 | } 117 | #lt-header-bottom-left .lt-header-right-section{ 118 | width: 45% 119 | } 120 | 121 | #lt-bottom-boxes-1 { 122 | margin-top: 1px; 123 | float: right; 124 | border: 1px solid #6fc0ba; 125 | border-radius: 2px; 126 | height: 4px; 127 | width: 35px; 128 | } 129 | 130 | #lt-bottom-boxes-2 { 131 | margin-top: 1px; 132 | margin-right: 20px; 133 | float: right; 134 | border: 1px solid #1b2f2d; 135 | border-radius: 2px; 136 | height: 4px; 137 | width: 25px; 138 | } 139 | 140 | #lt-bottom-boxes-3 { 141 | margin-top: 1px; 142 | margin-right: 4px; 143 | float: right; 144 | border: 1px solid #6fc0ba; 145 | border-radius: 2px; 146 | height: 4px; 147 | width: 30px; 148 | } 149 | 150 | 151 | #lt-header-bottom-right { 152 | position: absolute; 153 | top: 550px; 154 | } 155 | 156 | .lt-header-animator-left { 157 | position: absolute; 158 | top: 300px; 159 | width: 100%; 160 | height: 2px; 161 | border-top: 1px solid #1d2c33; 162 | border-bottom: 1px solid #1d2c33; 163 | visibility: hidden; 164 | } 165 | 166 | .lt-header-animator-right { 167 | position: absolute; 168 | top: 300px; 169 | width: 100%; 170 | height: 2px; 171 | border-top: 1px solid #1d2c33; 172 | border-bottom: 1px solid #1d2c33; 173 | visibility: hidden; 174 | } 175 | 176 | .content-container { 177 | 178 | border: 2px solid #6fc0ba; 179 | border-radius: 2px; 180 | /* box-shadow: 0 0 6px #005b8e, inset 0 0 6px #005b8e; */ 181 | padding: 3px; 182 | opacity: 0; 183 | } 184 | 185 | .content { 186 | 187 | border: 1px solid #1b2f2d; 188 | /* box-shadow: 0 0 6px #005b8e, inset 0 0 6px #005b8e; */ 189 | } 190 | 191 | .content-container h2 { 192 | color: #fff; 193 | margin: 0; 194 | border-bottom: 1px solid #1b2f2d; 195 | padding: 2px 4px; 196 | font-weight: bold; 197 | } 198 | 199 | .content-container h2 em{ 200 | font-style: normal; 201 | float: right; 202 | font-size: .8em; 203 | padding-top: 1px; 204 | color: #00eeee; 205 | 206 | } 207 | 208 | .content-container p { 209 | margin: 2px 0 0; 210 | padding: 4px 6px; 211 | 212 | } 213 | 214 | #lt-readme { 215 | position: absolute; 216 | top: 60px; 217 | overflow: hidden; 218 | } 219 | 220 | #lt-readme .content { 221 | height: 300px; 222 | overflow: hidden; 223 | } 224 | #lt-mobile-readme { 225 | position: absolute; 226 | top: 260px; 227 | overflow: hidden; 228 | visibility: hidden; 229 | display:none; 230 | width: 0; 231 | } 232 | 233 | #lt-mobile-readme .content { 234 | height: 290px; 235 | } 236 | 237 | #lt-bandwidth { 238 | position: absolute; 239 | top: 390px; 240 | } 241 | 242 | #lt-globalization { 243 | position: absolute; 244 | top: 60px; 245 | width: 470px; 246 | } 247 | 248 | #lt-globalization .content { 249 | height: 260px; 250 | overflow: hidden; 251 | } 252 | 253 | #lt-command-line { 254 | width: 318px; 255 | float: left; 256 | height: 129px; 257 | margin-top: 5px; 258 | padding: 5px; 259 | overflow: hidden; 260 | } 261 | 262 | #lt-command-lines { 263 | white-space: nowrap; 264 | } 265 | 266 | #lt-command-line-header { 267 | border-bottom: 1px dashed #fff; 268 | padding-bottom: 3px; 269 | 270 | } 271 | 272 | .command { 273 | clear: both; 274 | padding-top: 5px; 275 | font-size: 8pt; 276 | } 277 | 278 | .command-blinker { 279 | background-color: #00eeee; 280 | } 281 | 282 | .blink { 283 | animation: blink 1s steps(2, start) infinite; 284 | -webkit-animation-name: blink; 285 | -webkit-animation-iteration-count: infinite; 286 | -webkit-animation-timing-function: cubic-bezier(1.0,0,0,1.0); 287 | -webkit-animation-duration: 1s; 288 | } 289 | @keyframes blink { 290 | to { visibility: hidden; } 291 | } 292 | @-webkit-keyframes blink { 293 | from {opacity: 1.0;} 294 | to {opacity: 0.0;} 295 | } 296 | 297 | .command-text { 298 | color: #aaa; 299 | 300 | } 301 | .response { 302 | padding-top: 3px; 303 | padding-left: 25px; 304 | font-size: 7pt; 305 | } 306 | 307 | .response .alert{ 308 | font-color: #aa0000; 309 | } 310 | 311 | .response .highlight{ 312 | color: #ffcc00; 313 | } 314 | 315 | .ls { 316 | float: left; 317 | width: 40px; 318 | padding: 2px; 319 | margin-right: 25px; 320 | margin-top: 5px; 321 | 322 | } 323 | 324 | .ls-highlight { 325 | background-color: #fff; 326 | color: #000; 327 | } 328 | 329 | .folder-container { 330 | margin: 8px 18px 0; 331 | float: left; 332 | cursor: pointer; 333 | } 334 | 335 | 336 | .folder-label{ 337 | background-color: #000; 338 | color: #fff; 339 | font-size: 10px; 340 | margin-bottom: 5px; 341 | padding: 1px; 342 | width: 70px; 343 | text-transform: uppercase; 344 | border-radius: 2px; 345 | } 346 | 347 | .folder-container:hover .folder-label{ 348 | background-color: #feb400; 349 | color: #000; 350 | } 351 | 352 | .folder-label.selected{ 353 | background-color: #feb400; 354 | color: #000; 355 | } 356 | 357 | /* #lt-launch-github { 358 | margin-top: 20px; 359 | } */ 360 | 361 | .selected-folder { 362 | background-color: #feb400; 363 | color: #000; 364 | } 365 | 366 | .folder-big { 367 | background-image: url(../images/encom_folder_big.png); 368 | padding: 0px; 369 | margin: 0px; 370 | width:95px; 371 | height: 63px; 372 | } 373 | 374 | .folder-small { 375 | background-image: url(../images/encom_folder_small.png); 376 | padding: 0px; 377 | margin: 0px; 378 | width:80px; 379 | height: 49px; 380 | } 381 | 382 | #lt-keyboard { 383 | margin-left: 8px; 384 | position: absolute; 385 | top: 355px; 386 | width: 470px; 387 | } 388 | 389 | #lt-keyboard div{ 390 | background-color: rgba(255,255,255,0); 391 | border-radius: 5px; 392 | float: left; 393 | width: 35px; 394 | text-align:center; 395 | padding-top: 10px; 396 | margin-top: 5px; 397 | height: 20px; 398 | font-size: 12px; 399 | text-transform: uppercase; 400 | color: #00eeee; 401 | cursor: pointer; 402 | } 403 | 404 | #lt-keyboard #k-9{ 405 | clear: both; 406 | margin-left:10px; 407 | } 408 | 409 | #lt-keyboard #k-500{ 410 | clear: both; 411 | margin-left:20px; 412 | } 413 | 414 | #lt-keyboard #k-16{ 415 | clear: both; 416 | margin-left:5px; 417 | } 418 | 419 | #lt-keyboard #k-32 { 420 | clear: both; 421 | width: 300px; 422 | height: 5px; 423 | border: 1px solid #00eeee; 424 | border-radius: 3px; 425 | margin-top: 10px; 426 | margin-left: 80px; 427 | 428 | 429 | } 430 | 431 | #lt-far-right-column { 432 | position: absolute; 433 | left: 1000px; 434 | height: 600px; 435 | width: 80px; 436 | 437 | } 438 | 439 | #lt-encom-logo { 440 | font-family: 'Terminator'; 441 | position: absolute; 442 | font-size: 8pt; 443 | padding-left: 2px; 444 | top: 25px; 445 | left: 20px; 446 | color: #6fc0ba; 447 | border-left: 2px solid #6fc0ba; 448 | border-top: 2px solid #6fc0ba; 449 | border-bottom: 2px solid #6fc0ba; 450 | border-radius: 4px; 451 | } 452 | 453 | #lt-thumbprint { 454 | position: absolute; 455 | left: 15px; 456 | top: 500px; 457 | z-index: 200; 458 | -webkit-animation: pulsate 5s; 459 | -webkit-animation-iteration-count: 100; 460 | animation: pulsate 5s ease-out; 461 | animation-iteration-count: 100; 462 | } 463 | 464 | @-webkit-keyframes pulsate { 465 | 0% {opacity: .3;} 466 | 50% {opacity: 1.0;} 467 | 100% {opacity: 0.3;} 468 | } 469 | 470 | @keyframes pulsate { 471 | 0% {opacity: 0.3;} 472 | 50% {opacity: 1.0;} 473 | 100% {opacity: 0.3;} 474 | } 475 | } 476 | 477 | 478 | @media (max-width: 1100px) { 479 | 480 | .header{ 481 | font-size: 6px; 482 | } 483 | 484 | #lt-far-right-column { 485 | display: none; 486 | } 487 | 488 | #lt-left-column { 489 | display: none; 490 | } 491 | #lt-right-column { 492 | left: 0; 493 | top: 0; 494 | width: 330px; 495 | } 496 | 497 | .content-container { 498 | width: 330px; 499 | } 500 | #lt-globalization { 501 | top: 30px; 502 | width: 320px; 503 | height: 200px; 504 | } 505 | 506 | #lt-globalization .content{ 507 | height: 200px; 508 | } 509 | #lt-container-outside{ 510 | top: 0; 511 | width: 350px; 512 | height: 610px; 513 | z-index: 10; 514 | box-shadow: none; 515 | border: none; 516 | } 517 | 518 | #lt-keyboard { 519 | display: none; 520 | } 521 | 522 | #lt-container-inside { 523 | position:absolute; 524 | left: 0; 525 | top:0; 526 | width: 330px; 527 | height: 600px; 528 | z-index: 20; 529 | box-shadow: none; 530 | border: none; 531 | } 532 | #lt-header-top-right { 533 | position: absolute; 534 | top: 0; 535 | } 536 | 537 | #lt-header-bottom-right { 538 | position: absolute; 539 | top: 580px; 540 | } 541 | 542 | .lt-header-left-section { 543 | width:65%; 544 | 545 | } 546 | .lt-header-right-section { 547 | width:25%; 548 | 549 | } 550 | 551 | h2 em, h2 .alt-1 { 552 | display:none; 553 | 554 | } 555 | 556 | #lt-command-line { 557 | display:none; 558 | } 559 | 560 | #lt-mobile-readme { 561 | width: 320px; 562 | } 563 | 564 | .folder-big { 565 | background-image: url(../images/encom_folder_small.png); 566 | width:80px; 567 | height: 49px; 568 | margin: 0; 569 | } 570 | 571 | #lt-launch-github { 572 | margin-top: 8px; 573 | } 574 | 575 | .folder-small { 576 | background-image: url(../images/encom_folder_small.png); 577 | width:80px; 578 | height: 49px; 579 | margin: 0; 580 | } 581 | 582 | .folder-container { 583 | margin: 8px 12px; 584 | 585 | 586 | } 587 | 588 | } 589 | 590 | @media (min-width: 1600px) { 591 | 592 | body { 593 | font-size:8pt; 594 | } 595 | 596 | 597 | h2, p, li { 598 | font-size: 12pt; 599 | line-height: 16pt; 600 | } 601 | 602 | #lt-container-outside{ 603 | position:absolute; 604 | /* width: 1140px; */ 605 | /* height: 610px; */ 606 | width: 1540px; 607 | height: 760px; 608 | 609 | } 610 | 611 | #lt-container-inside { 612 | /* width: 1100px; */ 613 | /* height: 600px; */ 614 | width: 1500px; 615 | height: 750px; 616 | } 617 | 618 | #lt-left-column{ 619 | /*width: 380px;*/ 620 | width: 580px; 621 | } 622 | 623 | #lt-right-column{ 624 | position: absolute; 625 | left: 700px; 626 | width: 680px; 627 | } 628 | 629 | 630 | .lt-header-right-section { 631 | float: right; 632 | width: 10%; 633 | background-color: #000; 634 | text-align: right; 635 | margin-right: -5px; 636 | padding: 0 8px; 637 | } 638 | 639 | #lt-header-bottom-left { 640 | position: absolute; 641 | top: 700px; 642 | } 643 | 644 | #lt-bottom-boxes-1 { 645 | margin-top: 3px; 646 | height: 6px; 647 | width: 35px; 648 | } 649 | 650 | #lt-bottom-boxes-2 { 651 | margin-top: 3px; 652 | height: 6px; 653 | width: 30px; 654 | } 655 | 656 | #lt-bottom-boxes-3 { 657 | margin-top: 3px; 658 | margin-right: 8px; 659 | height: 6px; 660 | width: 35px; 661 | } 662 | 663 | #lt-header-bottom-right { 664 | position: absolute; 665 | top: 700px; 666 | } 667 | 668 | .lt-header-animator-left { 669 | top: 350px; 670 | } 671 | 672 | .lt-header-animator-right { 673 | top: 350px; 674 | } 675 | 676 | #lt-readme .content { 677 | height: 380px; 678 | } 679 | #lt-readme .content p{ 680 | padding-left: 15px; 681 | padding-right: 15px; 682 | padding-top: 8px; 683 | } 684 | 685 | #lt-bandwidth { 686 | top: 490px; 687 | width: 570px; 688 | } 689 | 690 | #lt-globalization { 691 | width: 680px; 692 | } 693 | 694 | #lt-globalization .content { 695 | height: 360px; 696 | } 697 | 698 | #lt-command-line { 699 | width: 440px; 700 | height: 185px; 701 | margin-top:20px; 702 | } 703 | 704 | .response { 705 | padding-top: 3px; 706 | padding-left: 25px; 707 | font-size: 7pt; 708 | } 709 | 710 | .response .alert{ 711 | font-color: #aa0000; 712 | } 713 | 714 | .response .highlight{ 715 | color: #ffcc00; 716 | } 717 | 718 | .ls { 719 | float: left; 720 | width: 40px; 721 | padding: 2px; 722 | margin-right: 25px; 723 | margin-top: 5px; 724 | 725 | } 726 | 727 | .ls-highlight { 728 | background-color: #fff; 729 | color: #000; 730 | } 731 | 732 | .folder-container { 733 | margin: 8px 30px 0; 734 | } 735 | 736 | .folder-label{ 737 | font-size: 10pt; 738 | width: 92px; 739 | } 740 | 741 | #lt-launch-mhn { 742 | margin-top: 60px; 743 | } 744 | 745 | /* #lt-launch-github { 746 | margin-top: 60px; 747 | } */ 748 | 749 | #lt-keyboard { 750 | margin-left: 60px; 751 | top: 455px; 752 | width: 600px; 753 | } 754 | 755 | #lt-keyboard div{ 756 | width: 45px; 757 | height: 30px; 758 | font-size: 16px; 759 | } 760 | 761 | #lt-keyboard #k-9{ 762 | margin-left:20px; 763 | } 764 | 765 | 766 | #lt-keyboard #k-32 { 767 | width: 400px; 768 | height: 12px; 769 | margin-top: 15px; 770 | } 771 | 772 | #lt-keyboard #k-500{ 773 | margin-left:40px; 774 | } 775 | 776 | #lt-far-right-column { 777 | position: absolute; 778 | left: 1400px; 779 | height: 700px; 780 | width: 80px; 781 | 782 | } 783 | 784 | #lt-encom-logo { 785 | top: 35px; 786 | } 787 | 788 | #lt-thumbprint { 789 | top: 630px; 790 | } 791 | 792 | #lt-bandwidth canvas { 793 | width: 100%; 794 | 795 | } 796 | 797 | .folder-big { 798 | background-image: url(../images/encom_folder_xl.png); 799 | width:140px; 800 | height: 93px; 801 | } 802 | 803 | .folder-small { 804 | background-image: url(../images/encom_folder_big.png); 805 | width:95px; 806 | height: 63px; 807 | } 808 | 809 | 810 | } 811 | -------------------------------------------------------------------------------- /css/boardroom-styles.css: -------------------------------------------------------------------------------- 1 | 2 | /* 775 by 363 */ 3 | 4 | body { 5 | background-color: #000000; 6 | font-family: 'Inconsolata', sans-serif; 7 | color: #fff; 8 | 9 | } 10 | 11 | div { 12 | /*border: 1px solid #333333;*/ 13 | 14 | } 15 | 16 | .clear { 17 | clear: both; 18 | 19 | } 20 | 21 | #screensaver { 22 | 23 | /* 24 | width:100%; 25 | height:100%; 26 | position:fixed; 27 | margin: 0em; 28 | left: 0; 29 | top: 0; 30 | background-color:#000000; 31 | color: #FFFFFF; 32 | font-size: 59px; 33 | */ 34 | 35 | } 36 | 37 | #screensaver-info { 38 | 39 | position: absolute; 40 | right: 0; 41 | left: 0; 42 | top: 0; 43 | bottom: 0; 44 | margin: auto; 45 | max-width: 100%; 46 | max-height: 100%; 47 | width:600px; 48 | height: 200px; 49 | text-align: center; 50 | line-height: 10px; 51 | color: #333; 52 | } 53 | #screensaver-info span{ 54 | visibility: hidden; 55 | } 56 | 57 | .wf-active #screensaver-info span{ 58 | visibility: inherit; 59 | } 60 | 61 | /* 62 | #container { 63 | width: 1900px; 64 | height: 700px; 65 | margin: auto; 66 | top: 0; 67 | left: 0; 68 | right: 0; 69 | bottom: 0; 70 | position:absolute; 71 | 72 | } 73 | */ 74 | #boardroom { 75 | width: 1900px; 76 | height: 800px; 77 | position:absolute; 78 | } 79 | 80 | 81 | #bottom-border { 82 | position:absolute; 83 | bottom: 0px; 84 | width: 0px; 85 | height: 1px; 86 | margin: 0; 87 | padding: 0; 88 | border-top: 1px solid #333; 89 | } 90 | 91 | .header{ 92 | color: #fff; 93 | text-transform:capitalize; 94 | margin-bottom: 10px; 95 | padding: 3px; 96 | border-top: 3px solid #FFCC00; 97 | border-bottom: 1px solid #FFCC00; 98 | white-space: nowrap; 99 | overflow: hidden; 100 | font-size:10px; 101 | } 102 | 103 | .header:before{ 104 | content: ""; 105 | display: block; 106 | position: absolute; 107 | left: 0px; 108 | top: 25px; 109 | width: 100%; 110 | height: 3px; 111 | background: #FFCC00; 112 | 113 | } 114 | 115 | .header-other{ 116 | border-color: #00EEEE; 117 | } 118 | 119 | .header-other:before{ 120 | content: ""; 121 | display: block; 122 | position: absolute; 123 | left: 0px; 124 | top: 25px; 125 | width: 100%; 126 | height: 3px; 127 | background: #00EEEE; 128 | 129 | } 130 | 131 | .header-other2{ 132 | border-color: #FF9933; 133 | } 134 | 135 | .header-other2:before{ 136 | content: ""; 137 | display: block; 138 | position: absolute; 139 | left: 0px; 140 | top: 25px; 141 | width: 100%; 142 | height: 3px; 143 | background: #FF9933; 144 | 145 | } 146 | 147 | #globalization{ 148 | position: absolute; 149 | top: -10px; 150 | left: -10px; 151 | width: 0px; 152 | overflow: hidden; 153 | background: hsla(0, 0%, 0%, 0.45); 154 | height: 100%; 155 | z-index: 1; 156 | 157 | } 158 | 159 | #satbar { 160 | margin-bottom: 20px; 161 | } 162 | 163 | .location-slider { 164 | font-size: 12pt; 165 | color: #000; 166 | border-bottom: 2px solid #333; 167 | margin-bottom: 15px; 168 | width: 0px; 169 | height: 34px; 170 | overflow: hidden; 171 | white-space: nowrap; 172 | } 173 | 174 | .location-name { 175 | margin: 0; 176 | padding: 0; 177 | } 178 | 179 | .location-area { 180 | background-color: #ffcc00; 181 | padding: 0 2px; 182 | } 183 | 184 | .location-area.other { 185 | background-color: #00eeee; 186 | } 187 | 188 | .location-city { 189 | color: #fff; 190 | padding: 0 2px; 191 | } 192 | 193 | .location-line{ 194 | margin-top: 10px; 195 | border-bottom: 1px solid #00eeee; 196 | 197 | } 198 | .location-slider ul { 199 | margin: 0; 200 | padding-left: 15px; 201 | position: relative; 202 | top: -12px; 203 | height: 10px; 204 | list-style-type:disc; 205 | } 206 | 207 | .location-slider li { 208 | 209 | color: #00eeee; 210 | font-size: 16px; 211 | margin-bottom: -6px; 212 | width: 5px; 213 | float: left; 214 | height:30px; 215 | /*display: inline-block;*/ 216 | 217 | } 218 | 219 | .location-gap { 220 | margin-top: 60px; 221 | 222 | } 223 | 224 | #logo { 225 | position: absolute; 226 | bottom: 10px; 227 | width: 180px; 228 | height: 100px; 229 | } 230 | 231 | #logo-cover-up { 232 | position: absolute; 233 | top: 32px; 234 | height: 58px; 235 | left: 10px; 236 | width: 160px; 237 | background-color: #000; 238 | } 239 | 240 | #logo-cover-side-1 { 241 | position: absolute; 242 | top: 90px; 243 | height: 20px; 244 | left: 10px; 245 | width: 160px; 246 | background-color: #000; 247 | } 248 | 249 | #logo-cover-side-2 { 250 | position: absolute; 251 | top: 25px; 252 | height: 8px; 253 | left: 10px; 254 | width: 160px; 255 | background-color: #000; 256 | } 257 | 258 | #globe{ 259 | position:absolute; 260 | width:600px; 261 | height:650px; 262 | left: 100px; 263 | top: -50px; 264 | z-index: 0; 265 | } 266 | 267 | #globe-footer{ 268 | position:absolute; 269 | height:55px; 270 | bottom: 0; 271 | left: 350px; 272 | overflow: hidden; 273 | } 274 | 275 | .footer-bar{ 276 | width: 1px; 277 | height: 200px; 278 | border-left: 1px solid #333; 279 | float:left; 280 | margin-right:15px; 281 | margin-top:100px; 282 | } 283 | 284 | #globe-footer a{ 285 | padding-top:5px; 286 | float:left; 287 | margin-right: 30px; 288 | } 289 | 290 | #globe-footer img{ 291 | opacity: 0; 292 | } 293 | 294 | #twitter { 295 | width: 30px; 296 | } 297 | 298 | #info-image { 299 | width: 25px; 300 | } 301 | 302 | #fullscreen-image { 303 | width: 25px; 304 | } 305 | 306 | #keyboard-image { 307 | width: 40px; 308 | } 309 | 310 | #github-image { 311 | width: 25px; 312 | } 313 | 314 | #about { 315 | 316 | display: inline-block; 317 | font-size: 18pt; 318 | } 319 | #logout { 320 | 321 | display: inline-block; 322 | font-size: 18pt; 323 | } 324 | 325 | #infect { 326 | 327 | display: inline-block; 328 | font-size: 18pt; 329 | } 330 | 331 | 332 | #user-interaction{ 333 | position: absolute; 334 | top: 0px; 335 | left: 820px; 336 | width: 0px; 337 | height: 595px; 338 | overflow: hidden; 339 | background: hsla(0, 0%, 0%, 0.45); 340 | } 341 | #user-interaction-container{ 342 | width: 600px; 343 | height: 100%; 344 | } 345 | #user-interaction h3{ 346 | padding: 4px; 347 | font-size: 3pt; 348 | margin: 0px; 349 | } 350 | 351 | #interaction { 352 | padding: 2px 8px; 353 | margin-top:6px; 354 | border-right: 1px solid #333; 355 | height: 545px; 356 | font-size: 16px; 357 | overflow: hidden; 358 | } 359 | 360 | .stream-table{ 361 | width: 100%; 362 | } 363 | 364 | .attack-map-message{ 365 | background-color: rgba(0,0,0,0.75); 366 | position: absolute; 367 | z-index: 10000; 368 | margin: 3px 0; 369 | font-size: 14px; 370 | } 371 | 372 | .attacker.interaction-data{ 373 | cursor: pointer; 374 | font-size: 14px; 375 | } 376 | .attacker.interaction-data:hover{ 377 | background-color: rgba(191, 146, 255, 0.5); 378 | } 379 | .interaction-data { 380 | padding: 1px 5px; 381 | white-space: nowrap; 382 | color: #00eeee; 383 | padding: 0px; 384 | margin: 5px; 385 | } 386 | 387 | .interaction-data li { 388 | display: inline-block; 389 | padding: 0px; 390 | padding-right: 10px; 391 | font-size: 15px; 392 | line-height: 1em; 393 | } 394 | 395 | .interaction-username { 396 | color: #fff; 397 | } 398 | 399 | .interaction-title { 400 | color: #00eeee; 401 | } 402 | 403 | .interaction-type { 404 | color: #ffcc00; 405 | } 406 | 407 | .interaction-size { 408 | color: #fff; 409 | } 410 | 411 | .interaction-popularity { 412 | color: #00eeee; 413 | } 414 | 415 | .interaction-popular { 416 | color: #00eeee; 417 | margin-left: -15px; 418 | padding: 0; 419 | margin-right: 0; 420 | } 421 | 422 | 423 | #interaction-overlay { 424 | position:absolute; 425 | width: 100%; 426 | height: 200px; 427 | bottom: 0px; 428 | background: -webkit-linear-gradient(transparent, #000); /* For Safari */ 429 | background: -o-linear-gradient(transparent, #000); /* For Opera 11.1 to 12.0 */ 430 | background: -moz-linear-gradient(transparent, #000); /* For Firefox 3.6 to 15 */ 431 | background: linear-gradient(transparent, #000); /* Standard syntax */ 432 | } 433 | #cube { 434 | position: absolute; 435 | left: 302px; 436 | width: 290px; 437 | height: 225px; 438 | border-bottom: 1px solid #333; 439 | padding:4px; 440 | } 441 | #cube-labels { 442 | position: absolute; 443 | bottom: 5px; 444 | left: 5px; 445 | font-size: 4pt; 446 | padding: 0; 447 | margin: 0; 448 | } 449 | 450 | #cube-labels div { 451 | margin: 3px 0 0 0; 452 | padding:0 0 0 3px; 453 | } 454 | 455 | #cube-label-1{ 456 | border-left: 42px solid #00eeee; 457 | } 458 | #cube-label-2{ 459 | border-left: 10px solid #ffcc00; 460 | } 461 | #cube-label-3{ 462 | border-left: 25px solid #aaa; 463 | } 464 | 465 | #swirls { 466 | position: absolute; 467 | bottom: 0; 468 | height: 225px; 469 | width: 225px; 470 | left: 305px; 471 | } 472 | 473 | #growth{ 474 | position: absolute; 475 | bottom: 0; 476 | left: 820px; 477 | width: 0px; 478 | height: 195px; 479 | overflow: hidden; 480 | background: hsla(0, 0%, 0%, 0.45); 481 | transition: height 1s; 482 | } 483 | .unauth #growth{ 484 | height: 0px; 485 | } 486 | 487 | #growth-container{ 488 | width: 600px; 489 | height: 100%; 490 | } 491 | 492 | #ticker { 493 | float: left; 494 | width: 90px; 495 | height: 105px; 496 | margin-top: 10px; 497 | margin-left: 10px; 498 | color: #fff; 499 | } 500 | 501 | #ticker-text{ 502 | margin: 0px; 503 | padding: 0px; 504 | font-weight:bold; 505 | font-size: 18pt; 506 | line-height: 17pt; 507 | margin-top: 8px; 508 | } 509 | #ticker-subtext{ 510 | margin: 0px; 511 | vertical-align: top; 512 | padding: 0px; 513 | font-size: 10pt; 514 | display: inline-block; 515 | width: 34px; 516 | } 517 | 518 | #ticker-lines{ 519 | display: inline-block; 520 | vertical-align: top; 521 | width: 34px; 522 | margin: 0px; 523 | padding: 0px; 524 | overflow: hidden; 525 | margin-top: 2px; 526 | margin-left: -5px; 527 | } 528 | .ticker-line{ 529 | margin: 0; 530 | padding: 0; 531 | font-size: 3px; 532 | border-left: 20px solid #00eeee; 533 | background-color: #666; 534 | } 535 | 536 | #ticker-value{ 537 | font-size: 18pt; 538 | font-weight: bold; 539 | margin-top: 10px; 540 | } 541 | #ticker-sensor{ 542 | font-size: 8pt; 543 | border-bottom: 1px solid #333; 544 | padding-bottom: 10px; 545 | margin-right: 18px; 546 | margin-bottom: 5px; 547 | } 548 | 549 | #ticker-line-1{ 550 | border-left: 8px solid #00eeee; 551 | margin-bottom: 2px; 552 | 553 | } 554 | 555 | #stock-chart 556 | { 557 | position: absolute; 558 | top: 50px; 559 | left: 100px; 560 | width: 496px; 561 | height:105px; 562 | overflow: hidden; 563 | } 564 | 565 | #stock-chart.unauth { 566 | border: 1px solid #00eeee; 567 | } 568 | #stock-chart canvas { 569 | 570 | } 571 | 572 | #stock-chart div { 573 | position: absolute; 574 | top: 0px; 575 | left: -2px; 576 | width: 0px; 577 | overflow: hidden; 578 | height: 105px; 579 | border-right: 1px solid #ffcc00; 580 | } 581 | 582 | #stock-chart div canvas{ 583 | margin-left: 2px; 584 | 585 | } 586 | 587 | #stock-subcharts 588 | { 589 | position: absolute; 590 | top: 161px; 591 | left: 110px; 592 | border: 0px; 593 | color: #fff; 594 | width: 500px; 595 | height: 28px; 596 | 597 | 598 | } 599 | 600 | .stock-subchart{ 601 | display: block; 602 | float: left; 603 | width: 100px; 604 | height: 25px; 605 | overflow: hidden; 606 | } 607 | 608 | .stock-subchart-label{ 609 | display: inline-block; 610 | font-size: 5pt; 611 | 612 | } 613 | 614 | .stock-subchart-chart{ 615 | display: inline-block; 616 | width: 60px; 617 | height: 25px; 618 | opacity: .3; 619 | 620 | } 621 | 622 | .stock-subchart-chart:hover{ 623 | opacity: 1; 624 | 625 | } 626 | 627 | .stock-subchart-bar{ 628 | 629 | float: left; 630 | background-color: #000; 631 | width: 3px; 632 | margin-left: 1px; 633 | height: 20px; 634 | border-bottom: 10px solid #00eeee; 635 | } 636 | 637 | .stock-subchart-bar.subchart-5{ 638 | height: 20px; 639 | border-bottom: 5px solid #00eeee; 640 | 641 | 642 | } 643 | .stock-subchart-bar.subchart-10{ 644 | height: 15px; 645 | border-bottom: 10px solid #00eeee; 646 | } 647 | .stock-subchart-bar.subchart-15{ 648 | height: 10px; 649 | border-bottom: 15px solid #00eeee; 650 | } 651 | .stock-subchart-bar.subchart-20{ 652 | height: 5px; 653 | border-bottom: 20px solid #00eeee; 654 | } 655 | 656 | .stock-subchart-bar.subchart-yellow{ 657 | border-color: #ffcc00; 658 | } 659 | .stock-subchart-bar.subchart-white{ 660 | border-color: #aaa; 661 | } 662 | 663 | #media{ 664 | position: absolute; 665 | top: 0px; 666 | left: 1440px; 667 | width: 0px; 668 | height: 595px; 669 | overflow: hidden; 670 | } 671 | #media-container{ 672 | width: 450px; 673 | height: 100%; 674 | } 675 | 676 | #attacker-data{ 677 | position: absolute; 678 | z-index: 10000; 679 | background-color: rgba(0,0,0,0.75); 680 | } 681 | 682 | .media-box{ 683 | float: left; 684 | width: 350px; 685 | height: 225px; 686 | padding: 3px; 687 | margin: 5px; 688 | border: 1px solid #00eeee; 689 | overflow: auto; 690 | } 691 | 692 | .user-pic{ 693 | position: relative; 694 | margin: 3px; 695 | height: 89px; 696 | width: 89px; 697 | float: left; 698 | cursor: pointer; 699 | 700 | } 701 | #media-bottom .user-pic{ 702 | float: right; 703 | } 704 | 705 | 706 | .user-pic.larger{ 707 | height: 184px; 708 | width: 184px; 709 | } 710 | .user-pic span{ 711 | display: box; 712 | position: absolute; 713 | bottom: 0px; 714 | background: hsla(0, 0%, 100%, 0.45); 715 | color: #000; 716 | width: 100%; 717 | font-size: 10px; 718 | text-align: center; 719 | } 720 | 721 | .user-pic.larger span{ 722 | font-size: 13px; 723 | text-align: center; 724 | } 725 | 726 | 727 | .media-blinkies{ 728 | float: left; 729 | height: 225px; 730 | width: 50px; 731 | color: #fff; 732 | font-size: 4px; 733 | overflow:hidden; 734 | } 735 | 736 | .media-blinkies h3{ 737 | margin-top: 5px; 738 | padding: 0px; 739 | font-size: 4px; 740 | } 741 | 742 | .media-blinkies .bar{ 743 | border-left: 20px solid #fff; 744 | margin-top: 2px; 745 | padding-left: 3px; 746 | 747 | } 748 | 749 | .blinkies-big-number{ 750 | display: inline-block; 751 | width: 35px; 752 | font-size: 50px; 753 | text-align: center; 754 | line-height: 40px; 755 | } 756 | .blinkies-big-label{ 757 | display: inline-block; 758 | width: 35px; 759 | font-size: 6px; 760 | text-align: center; 761 | } 762 | .blinkies-small-number{ 763 | display: inline-block; 764 | width: 10px; 765 | font-size: 20px; 766 | text-align: center; 767 | line-height: 20px; 768 | } 769 | .blinkies-small-label{ 770 | display: inline-block; 771 | width: 10px; 772 | font-size: 6px; 773 | text-align: center; 774 | } 775 | 776 | .blinky{ 777 | display: inline-block; 778 | width: 2px; 779 | height: 3px; 780 | background-color: #fff; 781 | margin: 3px; 782 | } 783 | 784 | .blinky.blue{ 785 | background-color: #00eeee; 786 | } 787 | .blinky.blank{ 788 | background-color: #000; 789 | } 790 | .blinky.yellow{ 791 | background-color: #ffcc00; 792 | 793 | } 794 | .bar.border-10{ 795 | border-left: 20px solid #fff; 796 | } 797 | .bar.border-20{ 798 | border-left: 20px solid #fff; 799 | } 800 | .bar.border-25{ 801 | border-left: 25px solid #fff; 802 | } 803 | .bar.border-30{ 804 | border-left: 30px solid #fff; 805 | } 806 | .bar.border-35{ 807 | border-left: 35px solid #fff; 808 | } 809 | .bar.border-40{ 810 | border-left: 40px solid #fff; 811 | } 812 | .bar.border-blue{ 813 | border-color: #00eeee; 814 | } 815 | .bar.border-yellow{ 816 | border-color: #ffcc00; 817 | } 818 | .bar.border-white{ 819 | border-color: #fff; 820 | } 821 | .bar.last{ 822 | margin-bottom: 10px; 823 | 824 | } 825 | 826 | .media-info{ 827 | padding-top: 3px; 828 | margin:0 5px; 829 | font-size: 6px; 830 | color: #fff; 831 | float: left; 832 | width: 500px; 833 | border-bottom: 1px solid #aaa; 834 | margin-bottom: 10px; 835 | opacity: .4; 836 | } 837 | 838 | .media-info span{ 839 | 840 | padding-right: 30px; 841 | 842 | } 843 | 844 | #timer{ 845 | position: absolute; 846 | bottom: 0; 847 | left: 1440px; 848 | width: 0px; 849 | height: 195px; 850 | overflow: hidden; 851 | } 852 | 853 | #clock { 854 | float: left; 855 | font-size: 66px; 856 | color: #AAA; 857 | overflow: hidden; 858 | margin-top:8px; 859 | } 860 | 861 | /* 862 | #globe-footer-reveal{ 863 | position:absolute; 864 | width:600px; 865 | height:35px; 866 | top: 665px; 867 | left: 190px; 868 | } 869 | */ 870 | 871 | 872 | #fps { 873 | position: absolute; 874 | left: 0; 875 | bottom: 0px; 876 | padding:0 0 3px 3px; 877 | text-align:left; 878 | background-color:#000; 879 | /*width: 180px;*/ 880 | width: 0px; 881 | height: 0px; 882 | overflow:hidden; 883 | 884 | border-left: 1px solid #aaa; 885 | } 886 | 887 | #fpsDescription { 888 | 889 | position: absolute; 890 | top: 0px; 891 | left: 10px; 892 | width:100px; 893 | 894 | color:#fff; 895 | font-family:Helvetica,Arial,sans-serif; 896 | font-size:7px; 897 | font-weight:bold; 898 | line-height:15px; 899 | 900 | } 901 | #fpsText { 902 | position: absolute; 903 | top: 12px; 904 | left: 10px; 905 | width:100px; 906 | 907 | color:#FFCC00; 908 | font-family:Helvetica,Arial,sans-serif; 909 | font-size:7px; 910 | font-weight:bold; 911 | line-height:15px; 912 | 913 | } 914 | #fpsGraph { 915 | position: absolute; 916 | bottom: 4px; 917 | left: 90px; 918 | 919 | width:74px; 920 | height:26px; 921 | background-color:#aaa; 922 | border-bottom: 1px solid #666; 923 | 924 | } 925 | #fpsGraph span { 926 | 927 | width:1px; 928 | height:26px; 929 | float:right; 930 | background-color:#000; 931 | } 932 | 933 | #ms { 934 | position: absolute; 935 | left: 200px; 936 | bottom: 0px; 937 | 938 | padding:0 0 3px 3px; 939 | text-align:left; 940 | background-color:#000; 941 | /*width: 200px;*/ 942 | /*height: 31px;*/ 943 | width: 0px; 944 | height: 0px; 945 | overflow:hidden; 946 | 947 | border-left: 1px solid #aaa; 948 | } 949 | #msDescription { 950 | position: absolute; 951 | top: 0px; 952 | left: 10px; 953 | width:100px; 954 | 955 | color:#fff; 956 | font-family:Helvetica,Arial,sans-serif; 957 | font-size:7px; 958 | font-weight:bold; 959 | line-height:15px; 960 | } 961 | 962 | #msText { 963 | position: absolute; 964 | top: 12px; 965 | left: 10px; 966 | width:100px; 967 | 968 | color:#00EEEE; 969 | font-family:Helvetica,Arial,sans-serif; 970 | font-size:7px; 971 | font-weight:bold; 972 | line-height:15px; 973 | } 974 | 975 | #msGraph { 976 | position: absolute; 977 | bottom: 4px; 978 | left: 80px; 979 | 980 | width:74px; 981 | height:26px; 982 | background-color:#aaa; 983 | order: 1px solid #666; 984 | border-bottom: 1px solid #666; 985 | 986 | } 987 | 988 | #msGraph span { 989 | 990 | width:1px; 991 | height:26px; 992 | float:right; 993 | background-color:#000; 994 | } 995 | 996 | #clockbar { 997 | overflow: hidden; 998 | height: 80px; 999 | } 1000 | #simpleclock { 1001 | float: right; 1002 | margin-top: 5px; 1003 | margin-left: 5px; 1004 | } 1005 | 1006 | #timerbar { 1007 | clear: both; 1008 | margin:10px 0; 1009 | overflow: hidden; 1010 | height: 28px; 1011 | width: 500px; 1012 | } 1013 | 1014 | .section-timerbar{ 1015 | width: 65px; 1016 | border: 1px solid #00EEEE; 1017 | float:left; 1018 | color: #FFFFFF; 1019 | font-size: 8px; 1020 | padding:4px; 1021 | margin-bottom: 10px; 1022 | text-align: center; 1023 | } 1024 | 1025 | .buffer-timerbar{ 1026 | margin-left: -5px; 1027 | background-color: #000; 1028 | padding-left:5px; 1029 | width: 110%; 1030 | height: 17px; 1031 | overflow: hidden; 1032 | } 1033 | 1034 | .buffer-timerbar > small { 1035 | font-size: 8px; 1036 | display: block; 1037 | color: #aaa; 1038 | } 1039 | 1040 | .boardroom-readme { 1041 | 1042 | border: 2px solid #6fc0ba; 1043 | border-radius: 2px; 1044 | /* box-shadow: 0 0 6px #005b8e, inset 0 0 6px #005b8e; */ 1045 | padding: 3px; 1046 | visibility: hidden; 1047 | background-color: #000; 1048 | position: absolute; 1049 | width: 500px; 1050 | top: 50px; 1051 | left: 600px; 1052 | z-index:250; 1053 | } 1054 | 1055 | .boardroom-readme .content{ 1056 | padding: 0; 1057 | margin: 0; 1058 | } 1059 | 1060 | .boardroom-readme h2 { 1061 | color: #fff; 1062 | margin: 0; 1063 | border-bottom: 1px solid #1b2f2d; 1064 | padding: 2px 4px; 1065 | font-weight: bold; 1066 | } 1067 | 1068 | .boardroom-readme h2 em{ 1069 | font-style: normal; 1070 | float: right; 1071 | font-size: .8em; 1072 | padding-top: 1px; 1073 | color: #00eeee; 1074 | cursor: pointer; 1075 | 1076 | } 1077 | 1078 | .boardroom-readme p { 1079 | font-size: 10pt; 1080 | margin: 4px 4px 0; 1081 | padding: 4px 6px; 1082 | line-height: 1.8em; 1083 | 1084 | } 1085 | -------------------------------------------------------------------------------- /src/LightTable.js: -------------------------------------------------------------------------------- 1 | var $ = require("jquery"), 2 | THREE = require("three"); 3 | 4 | var webglTest, 5 | active = false, 6 | initialized = false, 7 | currentWidth = 0, 8 | currentHeight = 0, 9 | hideFn = function(){} 10 | LightTable = {}, 11 | lastMessageTime = null, 12 | numUsers = 1, 13 | dataStreamOn = false; 14 | 15 | window.firstTimeViewingLT = getUrlParameter('redirect') === "" ? true : false; 16 | 17 | function getUrlParameter(name) { 18 | name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); 19 | var regex = new RegExp('[\\?&]' + name + '=([^]*)'); 20 | var results = regex.exec(location.search); 21 | return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); 22 | }; 23 | 24 | /* public function */ 25 | 26 | LightTable.init = function(_hideFn){ 27 | 28 | hideFn = _hideFn; 29 | 30 | $(".lt-header").css("visibility", "hidden"); 31 | $(".content-container").css("visibility", "hidden"); 32 | 33 | 34 | $("#lt-keyboard").css("opacity", 0); 35 | 36 | 37 | webglTest = createWebGlTest(); 38 | 39 | $("#light-table").css("visibility", "visible"); 40 | 41 | currentWidth = $(window).width(); 42 | currentHeight = $(window).height(); 43 | 44 | 45 | /* set events */ 46 | 47 | $("#lt-launch-github").click(function(){ 48 | $(this).find(".folder-big").css("background-color", "#fff"); 49 | simulateCommand("|cd github$"); 50 | simulateCommand("ls$"); 51 | simulateCommand("run github.exe$"); 52 | $(document).off(); 53 | $(".folder-container").off(); 54 | }); 55 | 56 | $("#lt-launch-mhn").click(function(){ 57 | $(this).find(".folder-big").css("background-color", "#fff"); 58 | simulateCommand("|cd seckcmhn$"); 59 | simulateCommand("ls$"); 60 | simulateCommand("run seckcmhn.exe$"); 61 | $(document).off(); 62 | $(".folder-container").off(); 63 | }); 64 | 65 | $("#lt-launch-wikipedia").click(function(){ 66 | $(this).find(".folder-small").css("background-color", "#fff"); 67 | simulateCommand("|cd wikipedia$"); 68 | simulateCommand("ls$"); 69 | simulateCommand("run wikipedia.exe$"); 70 | $(document).off(); 71 | $(".folder-container").off(); 72 | }); 73 | 74 | $("#lt-launch-test").click(function(){ 75 | $(this).find(".folder-small").css("background-color", "#fff"); 76 | simulateCommand("|cd test$"); 77 | simulateCommand("ls$"); 78 | simulateCommand("run test.exe$"); 79 | $(document).off(); 80 | $(".folder-container").off(); 81 | 82 | }); 83 | 84 | $("#lt-launch-bitcoin").click(function(){ 85 | $(this).find(".folder-big").css("background-color", "#fff"); 86 | simulateCommand("cd bitcoin$"); 87 | }); 88 | 89 | $("#lt-launch-unknown").click(function(){ 90 | $(this).find(".folder-big").css("background-color", "#fff"); 91 | simulateCommand("cd unknown$"); 92 | }); 93 | 94 | $("#lt-keyboard div").mousedown(function(event){ 95 | event.preventDefault(); 96 | keyClick(parseInt($(this).attr("id").split("-")[1])); 97 | }); 98 | 99 | initialized = true; 100 | 101 | if(window.firstTimeViewingLT){ 102 | window.firstTimeViewingLT = false; 103 | setTimeout(function(){ 104 | $("#lt-launch-mhn").find(".folder-big").css("background-color", "#fff"); 105 | simulateCommand("|cd seckcmhn$"); 106 | simulateCommand("ls$"); 107 | simulateCommand("run seckcmhn.exe$"); 108 | $(document).off(); 109 | $(".folder-container").off(); 110 | },1500); 111 | } 112 | 113 | }; 114 | 115 | LightTable.show = function(cb){ 116 | 117 | $("#light-table").css("opacity", 1); 118 | // do the intro animations 119 | showContainer(); 120 | setTimeout(showHeaders, 500); 121 | showContentBoxes($("#lt-readme"), 0); 122 | 123 | showContentBoxes($("#lt-bandwidth"), 100); 124 | showContentBoxes($("#lt-globalization"), 200); 125 | showKeyboard(); 126 | 127 | if($("#lt-mobile-readme").width() > 0){ 128 | showContentBoxes($("#lt-mobile-readme"), 0); 129 | } 130 | 131 | $(document).keydown(function(event){ 132 | 133 | if(!event.ctrlKey && (event.which < 112 || event.which > 134) && event.which != 91) { // let them do f1-f12, windows key 134 | var keycode = event.which; 135 | event.preventDefault(); 136 | keyClick(keycode); 137 | } 138 | }); 139 | 140 | if(typeof cb == "function"){ 141 | setTimeout(cb, 500); 142 | } 143 | 144 | active = true; 145 | }; 146 | 147 | LightTable.hide = function(cb) { 148 | // reset everything 149 | 150 | active = false; 151 | $("#lt-mobile-readme").attr("style",""); 152 | $("#k-32").attr("style",""); 153 | $(".lt-header-animator-right").attr("style",""); 154 | $(".lt-header-animator-left").attr("style",""); 155 | 156 | $("#light-table").animate({ 157 | opacity: 0 158 | }, 500); 159 | 160 | setTimeout(function(){ 161 | hideKeyboard(); 162 | hideContainer(); 163 | hideWebgl(); 164 | }, 500); 165 | 166 | webglTest.reset(); 167 | 168 | $(document).off(); 169 | 170 | if(typeof cb == "function"){ 171 | cb(); 172 | } 173 | }; 174 | 175 | LightTable.animate = function(){ 176 | if(active && initialized){ 177 | webglTest.tick(); 178 | } 179 | }; 180 | 181 | var resizing = false; 182 | LightTable.resize = function(){ 183 | $("#light-table").center(); 184 | if(!resizing && (currentWidth > 1600 && $(window).width() <= 1600) || (currentWidth <= 1600 && $(window).width() > 1600)){ 185 | 186 | currentWidth = $(window).width(); 187 | resizing = true; 188 | LightTable.hide(); 189 | setTimeout(LightTable.show,1000); 190 | 191 | setTimeout(function(){ 192 | resizing = false; 193 | }, 3000); 194 | } 195 | }; 196 | 197 | 198 | LightTable.message = function(message){ 199 | lastMessageTime = Date.now(); 200 | 201 | if(message.stream === "meta" && message.size > 0){ 202 | $("#datalink-status").text("CONNECTED. " + message.size + " USER" + (parseInt(message.size,10) === 1 ? "" : "S ")); 203 | $("#datalink-status").css("color", "green"); 204 | } 205 | 206 | }; 207 | 208 | /* private functions */ 209 | 210 | function showContainer(){ 211 | var outside = $("#lt-container-outside"); 212 | var inside = $("#lt-container-inside"); 213 | 214 | outside.center(); 215 | inside.center(); 216 | 217 | var outsideOffset = outside.offset(); 218 | var insideOffset = inside.offset(); 219 | 220 | var outsideWidth = outside.width(); 221 | var outsideHeight = outside.height(); 222 | 223 | var insideWidth = inside.width(); 224 | var insideHeight = inside.height(); 225 | 226 | var outsideBlockerTopRight = $("