├── .editorconfig ├── .gitignore ├── .jshintrc ├── CHANGELOG.md ├── Gruntfile.js ├── Procfile ├── README.md ├── app.js ├── bin └── gitcanvas ├── bower.json ├── dat ├── cli.png ├── commits.png ├── favicon.ico ├── gitcanvas.png └── web.png ├── example └── some.json ├── lib ├── cli.js ├── executor.js ├── gc.js └── io.js ├── package.json └── web ├── public ├── css │ ├── gitcanvas.css │ ├── normalize.css │ └── sl.css ├── favicon.ico ├── humans.txt ├── img │ └── gitcanvas.png └── js │ ├── .jshintrc │ ├── app.js │ ├── gitcanvas.js │ └── vendor │ ├── ga.js │ └── moment.min.js ├── server └── routing.js └── views └── app.jade /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "globals": { 4 | "CLI": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 Octo Hate 2 | 3 | - Touches and removes `a` file so that GitHub actually considers it a contribution 4 | 5 | # 0.0.3 Octo Love 6 | 7 | - API endpoint `/api/v1/contributions/:username` retrieves contribution data from GitHub 8 | 9 | # 0.0.2 Infectious Larva 10 | 11 | - Support to iterate on `commits` count for a given date 12 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt) { 4 | grunt.initConfig({ 5 | jshint: { 6 | options: { 7 | reporter: require('jshint-stylish'), 8 | jshintrc: '.jshintrc' 9 | }, 10 | lib: ['lib/**/*.js'], 11 | support: ['Gruntfile.js'], 12 | client: { 13 | options: { 14 | jshintrc: 'web/public/js/.jshintrc' 15 | }, 16 | files: { 17 | src: [ 18 | 'web/public/js/**/*.js', 19 | '!web/public/js/vendor/**/*.js' 20 | ] 21 | } 22 | }, 23 | server: { 24 | files: { 25 | src: [ 26 | 'app.js', 27 | 'web/server/**/*.js' 28 | ] 29 | } 30 | } 31 | }, 32 | watch: { 33 | livereload: { 34 | options: { livereload: true }, 35 | files: [ 36 | 'web/public/**/*.{css,js}', 'web/views/**/*.jade' 37 | ] 38 | } 39 | } 40 | }); 41 | 42 | require('load-grunt-tasks')(grunt); 43 | 44 | function alias (name, tasks) { 45 | grunt.registerTask(name, tasks.split(' ')); 46 | } 47 | 48 | alias('default', 'jshint'); 49 | }; 50 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node app 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [gitcanvas](http://gitcanvas.herokuapp.com) 2 | 3 | #### Waste your time like you never did before! 4 | 5 | ![gitcanvas.png][1] 6 | 7 | ## What is this? 8 | 9 | `gitcanvas` is a service _and_ command-line tool that facilitates "drawing" in your contribution summary on GitHub, by creating commits for you in the right places. 10 | 11 | Just [hop onto the site](http://gitcanvas.herokuapp.com), _draw something_, commit with the CLI, and done! :rocket: 12 | 13 | ![web.png][2] 14 | 15 | # Using the CLI 16 | 17 | Install it via `npm`. 18 | 19 | ```shell 20 | npm install -g gitcanvas 21 | ``` 22 | 23 | Sit in the repository you want to generate the commits on, and then use `gitcanvas` CLI. 24 | 25 | ```shell 26 | gitcanvas --help 27 | gitcanvas --dry-run some.json 28 | gitcanvas some.json 29 | ``` 30 | 31 | ![cli.png][4] 32 | 33 | Provided that you give it a JSON file generated on the site, `gitcanvas` will generate a bunch of commits for you _(no pushing around, so you can take it back)_. 34 | 35 | ![commits.png][3] 36 | 37 | This is mostly harmless, as commits will be empty. No changes are part of the commit. You could always use the `--dry-run` flag to see what the commands would look like. 38 | 39 | Take into consideration that other people only see your _public contribution activity_, so using `gitcanvas` on a private repo won't always have the desired effect (showing off a cool, pixelated drawing). 40 | 41 | # Hosting the web application 42 | 43 | No special treatment, just `node app`, done. 44 | 45 | # Why, God, Why? 46 | 47 | Boredom, insomnia, and I just had to find a use for [emoji-random](https://github.com/bevacqua/node-emoji-random), which is largely useless too. 48 | 49 | [1]: https://github.com/bevacqua/gitcanvas/blob/master/dat/gitcanvas.png?raw=true 50 | [2]: https://github.com/bevacqua/gitcanvas/blob/master/dat/web.png?raw=true 51 | [3]: https://github.com/bevacqua/gitcanvas/blob/master/dat/commits.png?raw=true 52 | [4]: https://github.com/bevacqua/gitcanvas/blob/master/dat/cli.png?raw=true 53 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var port = process.env.PORT || 3000; 4 | var express = require('express'); 5 | var app = express(); 6 | 7 | app.engine('jade', require('jade').__express); 8 | app.set('views', 'web/views'); 9 | 10 | app.use(express.static(__dirname + '/web/public')); 11 | app.use(express.favicon(__dirname + '/web/public/favicon.ico')); 12 | app.use(app.router); 13 | 14 | require('./web/server/routing.js')(app); 15 | 16 | app.listen(port); 17 | 18 | console.log('express listening on port %s', port); 19 | -------------------------------------------------------------------------------- /bin/gitcanvas: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib/cli.js'); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitcanvas", 3 | "version": "0.0.3", 4 | "description": "Use your GitHub account's commit history as a canvas. Express the artist in you!", 5 | "author": { 6 | "name": "Nicolas Bevacqua", 7 | "email": "hello@bevacqua.io", 8 | "url": "http://bevacqua.io" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/bevacqua/gitcanvas" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/bevacqua/gitcanvas/issues" 16 | }, 17 | "dependencies": { 18 | "moment": "2.4.0", 19 | "normalize-css": "2.1.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /dat/cli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevacqua/gitcanvas/f4d2dadeb99a8e27720dcc21f472d570427191d2/dat/cli.png -------------------------------------------------------------------------------- /dat/commits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevacqua/gitcanvas/f4d2dadeb99a8e27720dcc21f472d570427191d2/dat/commits.png -------------------------------------------------------------------------------- /dat/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevacqua/gitcanvas/f4d2dadeb99a8e27720dcc21f472d570427191d2/dat/favicon.ico -------------------------------------------------------------------------------- /dat/gitcanvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevacqua/gitcanvas/f4d2dadeb99a8e27720dcc21f472d570427191d2/dat/gitcanvas.png -------------------------------------------------------------------------------- /dat/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevacqua/gitcanvas/f4d2dadeb99a8e27720dcc21f472d570427191d2/dat/web.png -------------------------------------------------------------------------------- /example/some.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "raw": "Tue Nov 27 2012 06:52:28 GMT-0300", 4 | "date": "Tue Nov 27 14:00 2012 -0300", 5 | "commits": 1 6 | }, 7 | { 8 | "raw": "Wed Nov 28 2012 06:52:28 GMT-0300", 9 | "date": "Wed Nov 28 14:00 2012 -0300", 10 | "commits": 1 11 | }, 12 | { 13 | "raw": "Wed Dec 05 2012 06:52:28 GMT-0300", 14 | "date": "Wed Dec 05 14:00 2012 -0300", 15 | "commits": 1 16 | }, 17 | { 18 | "raw": "Thu Feb 14 2013 06:52:28 GMT-0300", 19 | "date": "Thu Feb 14 14:00 2013 -0300", 20 | "commits": 1 21 | }, 22 | { 23 | "raw": "Fri Feb 15 2013 06:52:28 GMT-0300", 24 | "date": "Fri Feb 15 14:00 2013 -0300", 25 | "commits": 1 26 | }, 27 | { 28 | "raw": "Fri Feb 22 2013 06:52:28 GMT-0300", 29 | "date": "Fri Feb 22 14:00 2013 -0300", 30 | "commits": 1 31 | }, 32 | { 33 | "raw": "Fri Mar 01 2013 06:52:28 GMT-0300", 34 | "date": "Fri Mar 01 14:00 2013 -0300", 35 | "commits": 1 36 | }, 37 | { 38 | "raw": "Fri Mar 08 2013 06:52:28 GMT-0300", 39 | "date": "Fri Mar 08 14:00 2013 -0300", 40 | "commits": 1 41 | }, 42 | { 43 | "raw": "Fri Mar 15 2013 06:52:28 GMT-0300", 44 | "date": "Fri Mar 15 14:00 2013 -0300", 45 | "commits": 1 46 | }, 47 | { 48 | "raw": "Fri May 10 2013 06:52:28 GMT-0300", 49 | "date": "Fri May 10 14:00 2013 -0300", 50 | "commits": 1 51 | }, 52 | { 53 | "raw": "Fri May 17 2013 06:52:28 GMT-0300", 54 | "date": "Fri May 17 14:00 2013 -0300", 55 | "commits": 1 56 | }, 57 | { 58 | "raw": "Fri May 24 2013 06:52:28 GMT-0300", 59 | "date": "Fri May 24 14:00 2013 -0300", 60 | "commits": 1 61 | }, 62 | { 63 | "raw": "Thu May 30 2013 06:52:28 GMT-0300", 64 | "date": "Thu May 30 14:00 2013 -0300", 65 | "commits": 1 66 | }, 67 | { 68 | "raw": "Fri May 31 2013 06:52:28 GMT-0300", 69 | "date": "Fri May 31 14:00 2013 -0300", 70 | "commits": 1 71 | }, 72 | { 73 | "raw": "Thu Jun 06 2013 06:52:28 GMT-0300", 74 | "date": "Thu Jun 06 14:00 2013 -0300", 75 | "commits": 1 76 | }, 77 | { 78 | "raw": "Wed Jun 12 2013 06:52:28 GMT-0300", 79 | "date": "Wed Jun 12 14:00 2013 -0300", 80 | "commits": 1 81 | }, 82 | { 83 | "raw": "Thu Jun 13 2013 06:52:28 GMT-0300", 84 | "date": "Thu Jun 13 14:00 2013 -0300", 85 | "commits": 1 86 | }, 87 | { 88 | "raw": "Wed Jun 19 2013 06:52:28 GMT-0300", 89 | "date": "Wed Jun 19 14:00 2013 -0300", 90 | "commits": 1 91 | }, 92 | { 93 | "raw": "Wed Jun 26 2013 06:52:28 GMT-0300", 94 | "date": "Wed Jun 26 14:00 2013 -0300", 95 | "commits": 1 96 | }, 97 | { 98 | "raw": "Tue Jul 02 2013 06:52:28 GMT-0300", 99 | "date": "Tue Jul 02 14:00 2013 -0300", 100 | "commits": 1 101 | } 102 | ] 103 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var program = require('commander'); 4 | 5 | global.CLI = program 6 | .version('0.0.1') 7 | .usage('[options] ') 8 | .option('-d, --dry-run', 'just log some output, don\'t commit anything') 9 | .option('-v, --verbose', 'verbose output of operations') 10 | .parse(process.argv); 11 | 12 | var executor = require('./executor.js'); 13 | 14 | executor() 15 | .execute(program.args[0]); 16 | -------------------------------------------------------------------------------- /lib/executor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var io = require('./io.js'); 6 | var gc = require('./gc.js'); 7 | 8 | module.exports = function () { 9 | var api = {}; 10 | 11 | api.execute = function (filepath) { 12 | if (!filepath) { 13 | console.log(CLI.helpInformation()); 14 | process.exit(1); 15 | } 16 | 17 | var cwd = process.cwd(); 18 | var absolute = path.resolve(cwd, filepath); 19 | 20 | if (!fs.existsSync(absolute)) { 21 | io.die('Input file missing: %s', absolute); 22 | } 23 | 24 | io.verbose('Reading input...'); 25 | 26 | var raw = fs.readFileSync(absolute, { encoding: 'utf8' }); 27 | var entries = JSON.parse(raw || '[]') || []; 28 | 29 | io.verboseln('%s entries parsed.', entries.length); 30 | 31 | gc(entries); 32 | }; 33 | 34 | return api; 35 | }; 36 | -------------------------------------------------------------------------------- /lib/gc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var emoji = require('emoji-random'); 5 | var async = require('async'); 6 | var cp = require('child_process'); 7 | var sp = cp.spawn; 8 | var io = require('./io.js'); 9 | 10 | module.exports = function (entries) { 11 | 12 | if (CLI.dryRun) { 13 | io.verboseln('this is a dry run.'); 14 | } 15 | 16 | entries.forEach(function (entry) { 17 | var t = entry.commits || 1; 18 | var i = 0; 19 | for (; i < t; i++) { 20 | if (i % 2 === 0) { 21 | run('touch a') 22 | } else { 23 | run('rm a') 24 | } 25 | run('git add a') 26 | run('git commit -m "gitcanvas ... ' + emoji.random() + '" --date "' + new Date(entry.date).toISOString() + '"') 27 | } 28 | }); 29 | 30 | function run (command) { 31 | if (CLI.dryRun) { 32 | console.log(command); 33 | } else { 34 | cp.execSync(command, { stdio: 'inherit' }) 35 | } 36 | } 37 | 38 | }; 39 | 40 | -------------------------------------------------------------------------------- /lib/io.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var chalk = require('chalk'); 5 | var util = require('util'); 6 | 7 | var colors = { 8 | verb: 'cyan', 9 | error: 'red' 10 | }; 11 | 12 | function log (level, args, writeln) { 13 | var lev = util.format('[%s]', level); 14 | var prefix = chalk[colors[level]](lev); 15 | var msg = args.length === 1 ? args[0] : util.format(args[0], _.rest(args)); 16 | var out = util.format('%s %s', prefix, msg); 17 | var ln = writeln ? '\n' : ''; 18 | process.stdout.write(out + ln); 19 | } 20 | 21 | function logfn (level, writeln) { 22 | return function () { 23 | var args = _.toArray(arguments); 24 | log(level, args, writeln); 25 | }; 26 | } 27 | 28 | function noop () {} 29 | 30 | var verbose = CLI.verbose || CLI.dryRun; 31 | var diefn = logfn('error', true); 32 | var api = module.exports = { 33 | die: function () { 34 | var args = _.toArray(arguments); 35 | diefn.apply(null, args); 36 | process.exit(1); 37 | }, 38 | verbose: verbose ? logfn('verb') : noop, 39 | verboseln: verbose ? logfn('verb', true) : noop 40 | }; 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitcanvas", 3 | "version": "1.0.0", 4 | "description": "Use your GitHub account's commit history as a canvas. Express the artist in you!", 5 | "author": { 6 | "name": "Nicolas Bevacqua", 7 | "email": "hello@bevacqua.io", 8 | "url": "http://bevacqua.io" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/bevacqua/gitcanvas" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/bevacqua/gitcanvas/issues" 16 | }, 17 | "bin": { 18 | "gitcanvas": "bin/gitcanvas" 19 | }, 20 | "dependencies": { 21 | "async": "0.2.9", 22 | "chalk": "0.3.0", 23 | "commander": "2.0.0", 24 | "emoji-random": "0.1.0", 25 | "express": "3.4.4", 26 | "jade": "0.35.0", 27 | "lodash": "2.2.1", 28 | "request": "2.27.0" 29 | }, 30 | "devDependencies": { 31 | "grunt": "0.4.1", 32 | "grunt-contrib-jshint": "0.7.0", 33 | "grunt-contrib-watch": "0.5.3", 34 | "jshint-stylish": "0.1.3", 35 | "load-grunt-tasks": "0.2.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /web/public/css/gitcanvas.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | background: #333; 3 | } 4 | 5 | html { 6 | width: 980px; 7 | margin: 0 auto; 8 | font-family: 'Helvetica'; 9 | } 10 | 11 | body { 12 | margin: 40px 60px; 13 | } 14 | 15 | .gh-ribbon { 16 | position: absolute; 17 | top: 0; 18 | right: 0; 19 | border: 0; 20 | } 21 | 22 | .gh-title { 23 | text-align: center; 24 | font-size: 5em; 25 | color: #8cc665; 26 | text-shadow: 1px 0px 6px #1e6823; 27 | margin-bottom: 5px; 28 | } 29 | 30 | .gh-tagline { 31 | text-align: center; 32 | color: #d6e685; 33 | } 34 | 35 | .gh-disclaimer { 36 | margin-top: 60px; 37 | text-align: center; 38 | font-style: italic; 39 | color: #555; 40 | } 41 | 42 | .gh-canvas { 43 | position: relative; 44 | cursor: default; 45 | margin: 15px; 46 | width: 830px; 47 | height: 115px; 48 | } 49 | 50 | .gh-sq { 51 | position: absolute; 52 | cursor: pointer; 53 | width: 12px; 54 | height: 12px; 55 | border: 1px solid transparent; 56 | background-color: #eee; 57 | box-shadow: 1px 1px 8px rgba(0,0,0,1); 58 | } 59 | 60 | .gh-sq:hover { 61 | border: 1px solid #555; 62 | } 63 | 64 | .gh-button { 65 | color: #333; 66 | background-color: #44a340; 67 | box-shadow 1px 1px 5px #1e6823; 68 | padding: 4px 6px; 69 | margin-left: 30px; 70 | border: none; 71 | } 72 | 73 | .gh-result { 74 | margin-top: 40px; 75 | margin: 30px; 76 | background-color: #555; 77 | color: #d6e685; 78 | white-space: pre; 79 | max-height: 400px; 80 | overflow: auto; 81 | padding: 8px 10px; 82 | text-shadow: 1px 0px 2px #777; 83 | border: 2px solid; 84 | } 85 | 86 | .gh-steps-title { 87 | text-align: center; 88 | color: #44a340; 89 | } 90 | 91 | .gh-steps { 92 | list-style-type: binary; 93 | line-height: 1.4em; 94 | color: #d6e685; 95 | } 96 | 97 | .gh-code { 98 | border-bottom: 1px solid #343434; 99 | border-right: 1px solid #343434; 100 | background: #555; 101 | padding: 2px; 102 | } 103 | 104 | .gh-ct-none { background-color: #555; } 105 | .gh-ct-low { background-color: #d6e685; } 106 | .gh-ct-mid { background-color: #8cc665; } 107 | .gh-ct-high { background-color: #44a340; } 108 | .gh-ct-crazy { background-color: #1e6823; } 109 | 110 | .gh-sq-day-0 { top: 0px; } 111 | .gh-sq-day-1 { top: 15px; } 112 | .gh-sq-day-2 { top: 30px; } 113 | .gh-sq-day-3 { top: 45px; } 114 | .gh-sq-day-4 { top: 60px; } 115 | .gh-sq-day-5 { top: 75px; } 116 | .gh-sq-day-6 { top: 90px; } 117 | 118 | .gh-sq-week-0 { left: 0px; } 119 | .gh-sq-week-1 { left: 15px; } 120 | .gh-sq-week-2 { left: 30px; } 121 | .gh-sq-week-3 { left: 45px; } 122 | .gh-sq-week-4 { left: 60px; } 123 | .gh-sq-week-5 { left: 75px; } 124 | .gh-sq-week-6 { left: 90px; } 125 | .gh-sq-week-7 { left: 105px; } 126 | .gh-sq-week-8 { left: 120px; } 127 | .gh-sq-week-9 { left: 135px; } 128 | .gh-sq-week-10 { left: 150px; } 129 | .gh-sq-week-11 { left: 165px; } 130 | .gh-sq-week-12 { left: 180px; } 131 | .gh-sq-week-13 { left: 195px; } 132 | .gh-sq-week-14 { left: 210px; } 133 | .gh-sq-week-15 { left: 225px; } 134 | .gh-sq-week-16 { left: 240px; } 135 | .gh-sq-week-17 { left: 255px; } 136 | .gh-sq-week-18 { left: 270px; } 137 | .gh-sq-week-19 { left: 285px; } 138 | .gh-sq-week-20 { left: 300px; } 139 | .gh-sq-week-21 { left: 315px; } 140 | .gh-sq-week-22 { left: 330px; } 141 | .gh-sq-week-23 { left: 345px; } 142 | .gh-sq-week-24 { left: 360px; } 143 | .gh-sq-week-25 { left: 375px; } 144 | .gh-sq-week-26 { left: 390px; } 145 | .gh-sq-week-27 { left: 405px; } 146 | .gh-sq-week-28 { left: 420px; } 147 | .gh-sq-week-29 { left: 435px; } 148 | .gh-sq-week-30 { left: 450px; } 149 | .gh-sq-week-31 { left: 465px; } 150 | .gh-sq-week-32 { left: 480px; } 151 | .gh-sq-week-33 { left: 495px; } 152 | .gh-sq-week-34 { left: 510px; } 153 | .gh-sq-week-35 { left: 525px; } 154 | .gh-sq-week-36 { left: 540px; } 155 | .gh-sq-week-37 { left: 555px; } 156 | .gh-sq-week-38 { left: 570px; } 157 | .gh-sq-week-39 { left: 585px; } 158 | .gh-sq-week-40 { left: 600px; } 159 | .gh-sq-week-41 { left: 615px; } 160 | .gh-sq-week-42 { left: 630px; } 161 | .gh-sq-week-43 { left: 645px; } 162 | .gh-sq-week-44 { left: 660px; } 163 | .gh-sq-week-45 { left: 675px; } 164 | .gh-sq-week-46 { left: 690px; } 165 | .gh-sq-week-47 { left: 705px; } 166 | .gh-sq-week-48 { left: 720px; } 167 | .gh-sq-week-49 { left: 735px; } 168 | .gh-sq-week-50 { left: 750px; } 169 | .gh-sq-week-51 { left: 765px; } 170 | .gh-sq-week-52 { left: 780px; } 171 | .gh-sq-week-53 { left: 795px; } 172 | -------------------------------------------------------------------------------- /web/public/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.1.3 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /** 8 | * Correct `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | main, 20 | nav, 21 | section, 22 | summary { 23 | display: block; 24 | } 25 | 26 | /** 27 | * Correct `inline-block` display not defined in IE 8/9. 28 | */ 29 | 30 | audio, 31 | canvas, 32 | video { 33 | display: inline-block; 34 | } 35 | 36 | /** 37 | * Prevent modern browsers from displaying `audio` without controls. 38 | * Remove excess height in iOS 5 devices. 39 | */ 40 | 41 | audio:not([controls]) { 42 | display: none; 43 | height: 0; 44 | } 45 | 46 | /** 47 | * Address `[hidden]` styling not present in IE 8/9. 48 | * Hide the `template` element in IE, Safari, and Firefox < 22. 49 | */ 50 | 51 | [hidden], 52 | template { 53 | display: none; 54 | } 55 | 56 | /* ========================================================================== 57 | Base 58 | ========================================================================== */ 59 | 60 | /** 61 | * 1. Set default font family to sans-serif. 62 | * 2. Prevent iOS text size adjust after orientation change, without disabling 63 | * user zoom. 64 | */ 65 | 66 | html { 67 | font-family: sans-serif; /* 1 */ 68 | -ms-text-size-adjust: 100%; /* 2 */ 69 | -webkit-text-size-adjust: 100%; /* 2 */ 70 | } 71 | 72 | /** 73 | * Remove default margin. 74 | */ 75 | 76 | body { 77 | margin: 0; 78 | } 79 | 80 | /* ========================================================================== 81 | Links 82 | ========================================================================== */ 83 | 84 | /** 85 | * Remove the gray background color from active links in IE 10. 86 | */ 87 | 88 | a { 89 | background: transparent; 90 | } 91 | 92 | /** 93 | * Address `outline` inconsistency between Chrome and other browsers. 94 | */ 95 | 96 | a:focus { 97 | outline: thin dotted; 98 | } 99 | 100 | /** 101 | * Improve readability when focused and also mouse hovered in all browsers. 102 | */ 103 | 104 | a:active, 105 | a:hover { 106 | outline: 0; 107 | } 108 | 109 | /* ========================================================================== 110 | Typography 111 | ========================================================================== */ 112 | 113 | /** 114 | * Address variable `h1` font-size and margin within `section` and `article` 115 | * contexts in Firefox 4+, Safari 5, and Chrome. 116 | */ 117 | 118 | h1 { 119 | font-size: 2em; 120 | margin: 0.67em 0; 121 | } 122 | 123 | /** 124 | * Address styling not present in IE 8/9, Safari 5, and Chrome. 125 | */ 126 | 127 | abbr[title] { 128 | border-bottom: 1px dotted; 129 | } 130 | 131 | /** 132 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 133 | */ 134 | 135 | b, 136 | strong { 137 | font-weight: bold; 138 | } 139 | 140 | /** 141 | * Address styling not present in Safari 5 and Chrome. 142 | */ 143 | 144 | dfn { 145 | font-style: italic; 146 | } 147 | 148 | /** 149 | * Address differences between Firefox and other browsers. 150 | */ 151 | 152 | hr { 153 | -moz-box-sizing: content-box; 154 | box-sizing: content-box; 155 | height: 0; 156 | } 157 | 158 | /** 159 | * Address styling not present in IE 8/9. 160 | */ 161 | 162 | mark { 163 | background: #ff0; 164 | color: #000; 165 | } 166 | 167 | /** 168 | * Correct font family set oddly in Safari 5 and Chrome. 169 | */ 170 | 171 | code, 172 | kbd, 173 | pre, 174 | samp { 175 | font-family: monospace, serif; 176 | font-size: 1em; 177 | } 178 | 179 | /** 180 | * Improve readability of pre-formatted text in all browsers. 181 | */ 182 | 183 | pre { 184 | white-space: pre-wrap; 185 | } 186 | 187 | /** 188 | * Set consistent quote types. 189 | */ 190 | 191 | q { 192 | quotes: "\201C" "\201D" "\2018" "\2019"; 193 | } 194 | 195 | /** 196 | * Address inconsistent and variable font size in all browsers. 197 | */ 198 | 199 | small { 200 | font-size: 80%; 201 | } 202 | 203 | /** 204 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 205 | */ 206 | 207 | sub, 208 | sup { 209 | font-size: 75%; 210 | line-height: 0; 211 | position: relative; 212 | vertical-align: baseline; 213 | } 214 | 215 | sup { 216 | top: -0.5em; 217 | } 218 | 219 | sub { 220 | bottom: -0.25em; 221 | } 222 | 223 | /* ========================================================================== 224 | Embedded content 225 | ========================================================================== */ 226 | 227 | /** 228 | * Remove border when inside `a` element in IE 8/9. 229 | */ 230 | 231 | img { 232 | border: 0; 233 | } 234 | 235 | /** 236 | * Correct overflow displayed oddly in IE 9. 237 | */ 238 | 239 | svg:not(:root) { 240 | overflow: hidden; 241 | } 242 | 243 | /* ========================================================================== 244 | Figures 245 | ========================================================================== */ 246 | 247 | /** 248 | * Address margin not present in IE 8/9 and Safari 5. 249 | */ 250 | 251 | figure { 252 | margin: 0; 253 | } 254 | 255 | /* ========================================================================== 256 | Forms 257 | ========================================================================== */ 258 | 259 | /** 260 | * Define consistent border, margin, and padding. 261 | */ 262 | 263 | fieldset { 264 | border: 1px solid #c0c0c0; 265 | margin: 0 2px; 266 | padding: 0.35em 0.625em 0.75em; 267 | } 268 | 269 | /** 270 | * 1. Correct `color` not being inherited in IE 8/9. 271 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 272 | */ 273 | 274 | legend { 275 | border: 0; /* 1 */ 276 | padding: 0; /* 2 */ 277 | } 278 | 279 | /** 280 | * 1. Correct font family not being inherited in all browsers. 281 | * 2. Correct font size not being inherited in all browsers. 282 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. 283 | */ 284 | 285 | button, 286 | input, 287 | select, 288 | textarea { 289 | font-family: inherit; /* 1 */ 290 | font-size: 100%; /* 2 */ 291 | margin: 0; /* 3 */ 292 | } 293 | 294 | /** 295 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 296 | * the UA stylesheet. 297 | */ 298 | 299 | button, 300 | input { 301 | line-height: normal; 302 | } 303 | 304 | /** 305 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 306 | * All other form control elements do not inherit `text-transform` values. 307 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. 308 | * Correct `select` style inheritance in Firefox 4+ and Opera. 309 | */ 310 | 311 | button, 312 | select { 313 | text-transform: none; 314 | } 315 | 316 | /** 317 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 318 | * and `video` controls. 319 | * 2. Correct inability to style clickable `input` types in iOS. 320 | * 3. Improve usability and consistency of cursor style between image-type 321 | * `input` and others. 322 | */ 323 | 324 | button, 325 | html input[type="button"], /* 1 */ 326 | input[type="reset"], 327 | input[type="submit"] { 328 | -webkit-appearance: button; /* 2 */ 329 | cursor: pointer; /* 3 */ 330 | } 331 | 332 | /** 333 | * Re-set default cursor for disabled elements. 334 | */ 335 | 336 | button[disabled], 337 | html input[disabled] { 338 | cursor: default; 339 | } 340 | 341 | /** 342 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 343 | * 2. Remove excess padding in IE 8/9/10. 344 | */ 345 | 346 | input[type="checkbox"], 347 | input[type="radio"] { 348 | box-sizing: border-box; /* 1 */ 349 | padding: 0; /* 2 */ 350 | } 351 | 352 | /** 353 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 354 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 355 | * (include `-moz` to future-proof). 356 | */ 357 | 358 | input[type="search"] { 359 | -webkit-appearance: textfield; /* 1 */ 360 | -moz-box-sizing: content-box; 361 | -webkit-box-sizing: content-box; /* 2 */ 362 | box-sizing: content-box; 363 | } 364 | 365 | /** 366 | * Remove inner padding and search cancel button in Safari 5 and Chrome 367 | * on OS X. 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Remove inner padding and border in Firefox 4+. 377 | */ 378 | 379 | button::-moz-focus-inner, 380 | input::-moz-focus-inner { 381 | border: 0; 382 | padding: 0; 383 | } 384 | 385 | /** 386 | * 1. Remove default vertical scrollbar in IE 8/9. 387 | * 2. Improve readability and alignment in all browsers. 388 | */ 389 | 390 | textarea { 391 | overflow: auto; /* 1 */ 392 | vertical-align: top; /* 2 */ 393 | } 394 | 395 | /* ========================================================================== 396 | Tables 397 | ========================================================================== */ 398 | 399 | /** 400 | * Remove most spacing between table cells. 401 | */ 402 | 403 | table { 404 | border-collapse: collapse; 405 | border-spacing: 0; 406 | } 407 | -------------------------------------------------------------------------------- /web/public/css/sl.css: -------------------------------------------------------------------------------- 1 | .sl-container { 2 | display: none; 3 | } 4 | 5 | .sl-tweet { 6 | width: 100px; 7 | height: 20px; 8 | } 9 | 10 | .sl-mention { 11 | vertical-align: top; 12 | width: 110px; 13 | height: 20px; 14 | } 15 | 16 | .sl-gplus { 17 | width: 90px; 18 | height: 28px; 19 | } 20 | 21 | .sl-fb { 22 | width: 130px; 23 | height: 21px; 24 | } 25 | 26 | .sl-buffer { 27 | width: 110px; 28 | height: 21px; 29 | } 30 | 31 | .sl-hn { 32 | width: 100px; 33 | height: 21px; 34 | } 35 | 36 | .sl-lnkdn { 37 | display: inline-block; 38 | 39 | } 40 | .sl-lnkdn-btn { 41 | height: 18px; 42 | padding: 2px 5px; 43 | box-sizing: content-box; 44 | border-radius: 3px; 45 | box-shadow: 1px 1px 6px rgba(#000, 0.3); 46 | background-color: #fff; 47 | } 48 | 49 | .sl-lnkdn-btn:hover { 50 | opacity: 0.95; 51 | } 52 | 53 | .sl-links { 54 | margin-top: 10px; 55 | color: #555; 56 | text-align: center; 57 | } 58 | 59 | .sl-link { 60 | color: #1e6823; 61 | } 62 | 63 | .sl-link:hover { 64 | color: #d6e685; 65 | } 66 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevacqua/gitcanvas/f4d2dadeb99a8e27720dcc21f472d570427191d2/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/humans.txt: -------------------------------------------------------------------------------- 1 | /* TEAM */ 2 | Chef: Nicolas Bevacqua 3 | Contact: hello@bevacqua.io 4 | Twitter: @nzgb 5 | GitHub: bevacqua 6 | From: Buenos Aires, Argentina 7 | 8 | /* SITE */ 9 | Ingredients: Sublime Text, iTerm, zsh, git, JavaScript, love 10 | Recipe: https://github.com/bevacqua/gitcanvas 11 | -------------------------------------------------------------------------------- /web/public/img/gitcanvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bevacqua/gitcanvas/f4d2dadeb99a8e27720dcc21f472d570427191d2/web/public/img/gitcanvas.png -------------------------------------------------------------------------------- /web/public/js/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true 3 | } 4 | -------------------------------------------------------------------------------- /web/public/js/app.js: -------------------------------------------------------------------------------- 1 | (function (gitcanvas) { 2 | 'use strict'; 3 | 4 | var input = document.querySelector('.gh-canvas'); 5 | var output = document.querySelector('.gh-result'); 6 | var reset = document.querySelector('.gh-reset'); 7 | var fill = document.querySelector('.gh-fill'); 8 | 9 | var canvas = gitcanvas.create(input, output); 10 | 11 | fill.addEventListener('click', function () { 12 | gitcanvas.set(canvas, true); 13 | }); 14 | reset.addEventListener('click', function () { 15 | gitcanvas.set(canvas, false); 16 | }); 17 | 18 | canvas.report(); 19 | })(gitcanvas); 20 | -------------------------------------------------------------------------------- /web/public/js/gitcanvas.js: -------------------------------------------------------------------------------- 1 | (function (window, document, moment) { 2 | 'use strict'; 3 | 4 | function generateDOM () { 5 | var fragment = document.createDocumentFragment(); 6 | var now = moment(); 7 | var lastYear = now.clone().subtract('years', 1); 8 | var date = lastYear; 9 | var weeks = 52; 10 | var sq, day, week, firstWeek, flipped; 11 | 12 | // week index means "weeks since last year", which is weird. 13 | // to wit: 14 | function untangleWeek (week) { 15 | if(!firstWeek) { 16 | firstWeek = week; 17 | } 18 | if (week === firstWeek && flipped) { 19 | week += weeks; 20 | } else if (week < firstWeek) { 21 | week += weeks; 22 | flipped = true; 23 | } 24 | return week - firstWeek + 1; 25 | } 26 | 27 | for(date; date <= now; date.add('days', 1)) { 28 | day = date.day(); 29 | week = untangleWeek(date.week()); 30 | sq = document.createElement('span'); 31 | sq.classList.add('gh-sq', 'gh-sq-day-' + day, 'gh-sq-week-' + week); 32 | sq.dataset.date = date; 33 | 34 | fragment.appendChild(sq); 35 | } 36 | 37 | return fragment; 38 | } 39 | 40 | function debounce (func, wait, immediate) { 41 | var timeout; 42 | return function() { 43 | var context = this, args = arguments; 44 | var later = function() { 45 | timeout = null; 46 | if (!immediate) func.apply(context, args); 47 | }; 48 | var callNow = immediate && !timeout; 49 | clearTimeout(timeout); 50 | timeout = setTimeout(later, wait); 51 | if (callNow) func.apply(context, args); 52 | }; 53 | } 54 | 55 | function toggle (canvas, element) { 56 | if (element.classList.contains('gh-sq')) { 57 | element.classList.toggle('gh-ct-mid'); 58 | 59 | canvas.report(); 60 | } 61 | } 62 | 63 | function loopThroughDays(canvas, fn) { 64 | var len = canvas.sq.length; 65 | var i; 66 | for(i = 0; i < len; i++) { 67 | fn(canvas.sq[i]); 68 | } 69 | } 70 | 71 | function report (canvas) { 72 | var result = []; 73 | 74 | loopThroughDays(canvas, function(sq) { 75 | var commits = sq.classList.contains('gh-ct-mid'); 76 | if (commits) { 77 | result.push({ 78 | raw: sq.dataset.date, 79 | date: moment(sq.dataset.date).format('ddd MMM DD 14:00 YYYY ZZ'), 80 | commits: 1 81 | }); 82 | } 83 | }); 84 | 85 | canvas.reporter.innerHTML = JSON.stringify(result, null, 2); 86 | } 87 | 88 | function selectElementContents(el) { 89 | var range = document.createRange(); 90 | range.selectNodeContents(el); 91 | var sel = window.getSelection(); 92 | sel.removeAllRanges(); 93 | sel.addRange(range); 94 | } 95 | 96 | var gitcanvas = {}; 97 | 98 | gitcanvas.create = function (input, output) { 99 | var fragment = generateDOM(); 100 | var exports = {}; 101 | var down, lastTarget; 102 | 103 | input.appendChild(fragment); 104 | input.addEventListener('click', function (e) { 105 | toggle(exports, e.target); 106 | }); 107 | 108 | document.addEventListener('mouseup', function (e) { down = false; }); 109 | input.addEventListener('mousedown', function (e) { down = true; }); 110 | input.addEventListener('mousemove', function (e) { 111 | var target = e.target; 112 | if (target !== lastTarget && down) { 113 | toggle(exports, e.target); 114 | lastTarget = target; 115 | } 116 | }); 117 | 118 | output.addEventListener('click', function () { 119 | selectElementContents(output); 120 | }); 121 | 122 | exports.reporter = output; 123 | exports.sq = input.querySelectorAll('.gh-sq'); 124 | exports.report = debounce(function () { 125 | report(exports); 126 | }, 100); 127 | 128 | return exports; 129 | }; 130 | 131 | gitcanvas.set = function (canvas, enabled) { 132 | loopThroughDays(canvas, function(sq) { 133 | sq.classList.toggle('gh-ct-mid', !!enabled); 134 | }); 135 | canvas.report(); 136 | }; 137 | 138 | window.gitcanvas = gitcanvas; 139 | })(window, document, moment); 140 | -------------------------------------------------------------------------------- /web/public/js/vendor/ga.js: -------------------------------------------------------------------------------- 1 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 2 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 3 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 4 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 5 | 6 | ga('create', 'UA-35043128-5', 'gitcanvas.herokuapp.com'); 7 | ga('send', 'pageview'); 8 | -------------------------------------------------------------------------------- /web/public/js/vendor/moment.min.js: -------------------------------------------------------------------------------- 1 | //! moment.js 2 | //! version : 2.4.0 3 | //! authors : Tim Wood, Iskren Chernev, Moment.js contributors 4 | //! license : MIT 5 | //! momentjs.com 6 | (function(a){function b(a,b){return function(c){return i(a.call(this,c),b)}}function c(a,b){return function(c){return this.lang().ordinal(a.call(this,c),b)}}function d(){}function e(a){u(a),g(this,a)}function f(a){var b=o(a),c=b.year||0,d=b.month||0,e=b.week||0,f=b.day||0,g=b.hour||0,h=b.minute||0,i=b.second||0,j=b.millisecond||0;this._input=a,this._milliseconds=+j+1e3*i+6e4*h+36e5*g,this._days=+f+7*e,this._months=+d+12*c,this._data={},this._bubble()}function g(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return b.hasOwnProperty("toString")&&(a.toString=b.toString),b.hasOwnProperty("valueOf")&&(a.valueOf=b.valueOf),a}function h(a){return 0>a?Math.ceil(a):Math.floor(a)}function i(a,b){for(var c=a+"";c.lengthd;d++)(c&&a[d]!==b[d]||!c&&q(a[d])!==q(b[d]))&&g++;return g+f}function n(a){if(a){var b=a.toLowerCase().replace(/(.)s$/,"$1");a=Kb[a]||Lb[b]||b}return a}function o(a){var b,c,d={};for(c in a)a.hasOwnProperty(c)&&(b=n(c),b&&(d[b]=a[c]));return d}function p(b){var c,d;if(0===b.indexOf("week"))c=7,d="day";else{if(0!==b.indexOf("month"))return;c=12,d="month"}bb[b]=function(e,f){var g,h,i=bb.fn._lang[b],j=[];if("number"==typeof e&&(f=e,e=a),h=function(a){var b=bb().utc().set(d,a);return i.call(bb.fn._lang,b,e||"")},null!=f)return h(f);for(g=0;c>g;g++)j.push(h(g));return j}}function q(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function r(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function s(a){return t(a)?366:365}function t(a){return 0===a%4&&0!==a%100||0===a%400}function u(a){var b;a._a&&-2===a._pf.overflow&&(b=a._a[gb]<0||a._a[gb]>11?gb:a._a[hb]<1||a._a[hb]>r(a._a[fb],a._a[gb])?hb:a._a[ib]<0||a._a[ib]>23?ib:a._a[jb]<0||a._a[jb]>59?jb:a._a[kb]<0||a._a[kb]>59?kb:a._a[lb]<0||a._a[lb]>999?lb:-1,a._pf._overflowDayOfYear&&(fb>b||b>hb)&&(b=hb),a._pf.overflow=b)}function v(a){a._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function w(a){return null==a._isValid&&(a._isValid=!isNaN(a._d.getTime())&&a._pf.overflow<0&&!a._pf.empty&&!a._pf.invalidMonth&&!a._pf.nullInput&&!a._pf.invalidFormat&&!a._pf.userInvalidated,a._strict&&(a._isValid=a._isValid&&0===a._pf.charsLeftOver&&0===a._pf.unusedTokens.length)),a._isValid}function x(a){return a?a.toLowerCase().replace("_","-"):a}function y(a,b){return b.abbr=a,mb[a]||(mb[a]=new d),mb[a].set(b),mb[a]}function z(a){delete mb[a]}function A(a){var b,c,d,e,f=0,g=function(a){if(!mb[a]&&nb)try{require("./lang/"+a)}catch(b){}return mb[a]};if(!a)return bb.fn._lang;if(!k(a)){if(c=g(a))return c;a=[a]}for(;f0;){if(c=g(e.slice(0,b).join("-")))return c;if(d&&d.length>=b&&m(e,d,!0)>=b-1)break;b--}f++}return bb.fn._lang}function B(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function C(a){var b,c,d=a.match(rb);for(b=0,c=d.length;c>b;b++)d[b]=Pb[d[b]]?Pb[d[b]]:B(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function D(a,b){return a.isValid()?(b=E(b,a.lang()),Mb[b]||(Mb[b]=C(b)),Mb[b](a)):a.lang().invalidDate()}function E(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(sb.lastIndex=0;d>=0&&sb.test(a);)a=a.replace(sb,c),sb.lastIndex=0,d-=1;return a}function F(a,b){var c;switch(a){case"DDDD":return vb;case"YYYY":case"GGGG":case"gggg":return wb;case"YYYYY":case"GGGGG":case"ggggg":return xb;case"S":case"SS":case"SSS":case"DDD":return ub;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return zb;case"a":case"A":return A(b._l)._meridiemParse;case"X":return Cb;case"Z":case"ZZ":return Ab;case"T":return Bb;case"SSSS":return yb;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"ww":case"W":case"WW":case"e":case"E":return tb;default:return c=new RegExp(N(M(a.replace("\\","")),"i"))}}function G(a){var b=(Ab.exec(a)||[])[0],c=(b+"").match(Hb)||["-",0,0],d=+(60*c[1])+q(c[2]);return"+"===c[0]?-d:d}function H(a,b,c){var d,e=c._a;switch(a){case"M":case"MM":null!=b&&(e[gb]=q(b)-1);break;case"MMM":case"MMMM":d=A(c._l).monthsParse(b),null!=d?e[gb]=d:c._pf.invalidMonth=b;break;case"D":case"DD":null!=b&&(e[hb]=q(b));break;case"DDD":case"DDDD":null!=b&&(c._dayOfYear=q(b));break;case"YY":e[fb]=q(b)+(q(b)>68?1900:2e3);break;case"YYYY":case"YYYYY":e[fb]=q(b);break;case"a":case"A":c._isPm=A(c._l).isPM(b);break;case"H":case"HH":case"h":case"hh":e[ib]=q(b);break;case"m":case"mm":e[jb]=q(b);break;case"s":case"ss":e[kb]=q(b);break;case"S":case"SS":case"SSS":case"SSSS":e[lb]=q(1e3*("0."+b));break;case"X":c._d=new Date(1e3*parseFloat(b));break;case"Z":case"ZZ":c._useUTC=!0,c._tzm=G(b);break;case"w":case"ww":case"W":case"WW":case"d":case"dd":case"ddd":case"dddd":case"e":case"E":a=a.substr(0,1);case"gg":case"gggg":case"GG":case"GGGG":case"GGGGG":a=a.substr(0,2),b&&(c._w=c._w||{},c._w[a]=b)}}function I(a){var b,c,d,e,f,g,h,i,j,k,l=[];if(!a._d){for(d=K(a),a._w&&null==a._a[hb]&&null==a._a[gb]&&(f=function(b){return b?b.length<3?parseInt(b,10)>68?"19"+b:"20"+b:b:null==a._a[fb]?bb().weekYear():a._a[fb]},g=a._w,null!=g.GG||null!=g.W||null!=g.E?h=X(f(g.GG),g.W||1,g.E,4,1):(i=A(a._l),j=null!=g.d?T(g.d,i):null!=g.e?parseInt(g.e,10)+i._week.dow:0,k=parseInt(g.w,10)||1,null!=g.d&&js(e)&&(a._pf._overflowDayOfYear=!0),c=S(e,0,a._dayOfYear),a._a[gb]=c.getUTCMonth(),a._a[hb]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=l[b]=d[b];for(;7>b;b++)a._a[b]=l[b]=null==a._a[b]?2===b?1:0:a._a[b];l[ib]+=q((a._tzm||0)/60),l[jb]+=q((a._tzm||0)%60),a._d=(a._useUTC?S:R).apply(null,l)}}function J(a){var b;a._d||(b=o(a._i),a._a=[b.year,b.month,b.day,b.hour,b.minute,b.second,b.millisecond],I(a))}function K(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function L(a){a._a=[],a._pf.empty=!0;var b,c,d,e,f,g=A(a._l),h=""+a._i,i=h.length,j=0;for(d=E(a._f,g).match(rb)||[],b=0;b0&&a._pf.unusedInput.push(f),h=h.slice(h.indexOf(c)+c.length),j+=c.length),Pb[e]?(c?a._pf.empty=!1:a._pf.unusedTokens.push(e),H(e,c,a)):a._strict&&!c&&a._pf.unusedTokens.push(e);a._pf.charsLeftOver=i-j,h.length>0&&a._pf.unusedInput.push(h),a._isPm&&a._a[ib]<12&&(a._a[ib]+=12),a._isPm===!1&&12===a._a[ib]&&(a._a[ib]=0),I(a),u(a)}function M(a){return a.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e})}function N(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function O(a){var b,c,d,e,f;if(0===a._f.length)return a._pf.invalidFormat=!0,a._d=new Date(0/0),void 0;for(e=0;ef)&&(d=f,c=b));g(a,c||b)}function P(a){var b,c=a._i,d=Db.exec(c);if(d){for(a._pf.iso=!0,b=4;b>0;b--)if(d[b]){a._f=Fb[b-1]+(d[6]||" ");break}for(b=0;4>b;b++)if(Gb[b][1].exec(c)){a._f+=Gb[b][0];break}Ab.exec(c)&&(a._f+="Z"),L(a)}else a._d=new Date(c)}function Q(b){var c=b._i,d=ob.exec(c);c===a?b._d=new Date:d?b._d=new Date(+d[1]):"string"==typeof c?P(b):k(c)?(b._a=c.slice(0),I(b)):l(c)?b._d=new Date(+c):"object"==typeof c?J(b):b._d=new Date(c)}function R(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function S(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function T(a,b){if("string"==typeof a)if(isNaN(a)){if(a=b.weekdaysParse(a),"number"!=typeof a)return null}else a=parseInt(a,10);return a}function U(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function V(a,b,c){var d=eb(Math.abs(a)/1e3),e=eb(d/60),f=eb(e/60),g=eb(f/24),h=eb(g/365),i=45>d&&["s",d]||1===e&&["m"]||45>e&&["mm",e]||1===f&&["h"]||22>f&&["hh",f]||1===g&&["d"]||25>=g&&["dd",g]||45>=g&&["M"]||345>g&&["MM",eb(g/30)]||1===h&&["y"]||["yy",h];return i[2]=b,i[3]=a>0,i[4]=c,U.apply({},i)}function W(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=bb(a).add("d",f),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function X(a,b,c,d,e){var f,g,h=new Date(Date.UTC(a,0)).getUTCDay();return c=null!=c?c:e,f=e-h+(h>d?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:s(a-1)+g}}function Y(a){var b=a._i,c=a._f;return"undefined"==typeof a._pf&&v(a),null===b?bb.invalid({nullInput:!0}):("string"==typeof b&&(a._i=b=A().preparse(b)),bb.isMoment(b)?(a=g({},b),a._d=new Date(+b._d)):c?k(c)?O(a):L(a):Q(a),new e(a))}function Z(a,b){bb.fn[a]=bb.fn[a+"s"]=function(a){var c=this._isUTC?"UTC":"";return null!=a?(this._d["set"+c+b](a),bb.updateOffset(this),this):this._d["get"+c+b]()}}function $(a){bb.duration.fn[a]=function(){return this._data[a]}}function _(a,b){bb.duration.fn["as"+a]=function(){return+this/b}}function ab(a){var b=!1,c=bb;"undefined"==typeof ender&&(this.moment=a?function(){return!b&&console&&console.warn&&(b=!0,console.warn("Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release.")),c.apply(null,arguments)}:bb)}for(var bb,cb,db="2.4.0",eb=Math.round,fb=0,gb=1,hb=2,ib=3,jb=4,kb=5,lb=6,mb={},nb="undefined"!=typeof module&&module.exports,ob=/^\/?Date\((\-?\d+)/i,pb=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,qb=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,rb=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,sb=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,tb=/\d\d?/,ub=/\d{1,3}/,vb=/\d{3}/,wb=/\d{1,4}/,xb=/[+\-]?\d{1,6}/,yb=/\d+/,zb=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Ab=/Z|[\+\-]\d\d:?\d\d/i,Bb=/T/i,Cb=/[\+\-]?\d+(\.\d{1,3})?/,Db=/^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d:?\d\d|Z)?)?$/,Eb="YYYY-MM-DDTHH:mm:ssZ",Fb=["YYYY-MM-DD","GGGG-[W]WW","GGGG-[W]WW-E","YYYY-DDD"],Gb=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],Hb=/([\+\-]|\d\d)/gi,Ib="Date|Hours|Minutes|Seconds|Milliseconds".split("|"),Jb={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},Kb={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",D:"date",w:"week",W:"isoWeek",M:"month",y:"year",DDD:"dayOfYear",e:"weekday",E:"isoWeekday",gg:"weekYear",GG:"isoWeekYear"},Lb={dayofyear:"dayOfYear",isoweekday:"isoWeekday",isoweek:"isoWeek",weekyear:"weekYear",isoweekyear:"isoWeekYear"},Mb={},Nb="DDD w W M D d".split(" "),Ob="M D H h m s w W".split(" "),Pb={M:function(){return this.month()+1},MMM:function(a){return this.lang().monthsShort(this,a)},MMMM:function(a){return this.lang().months(this,a)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(a){return this.lang().weekdaysMin(this,a)},ddd:function(a){return this.lang().weekdaysShort(this,a)},dddd:function(a){return this.lang().weekdays(this,a)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return i(this.year()%100,2)},YYYY:function(){return i(this.year(),4)},YYYYY:function(){return i(this.year(),5)},gg:function(){return i(this.weekYear()%100,2)},gggg:function(){return this.weekYear()},ggggg:function(){return i(this.weekYear(),5)},GG:function(){return i(this.isoWeekYear()%100,2)},GGGG:function(){return this.isoWeekYear()},GGGGG:function(){return i(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return q(this.milliseconds()/100)},SS:function(){return i(q(this.milliseconds()/10),2)},SSS:function(){return i(this.milliseconds(),3)},SSSS:function(){return i(this.milliseconds(),3)},Z:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(a/60),2)+":"+i(q(a)%60,2)},ZZ:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(10*a/6),4)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},X:function(){return this.unix()}},Qb=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"];Nb.length;)cb=Nb.pop(),Pb[cb+"o"]=c(Pb[cb],cb);for(;Ob.length;)cb=Ob.pop(),Pb[cb+cb]=b(Pb[cb],2);for(Pb.DDDD=b(Pb.DDD,3),g(d.prototype,{set:function(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(a){return this._months[a.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(a){return this._monthsShort[a.month()]},monthsParse:function(a){var b,c,d;for(this._monthsParse||(this._monthsParse=[]),b=0;12>b;b++)if(this._monthsParse[b]||(c=bb.utc([2e3,b]),d="^"+this.months(c,"")+"|^"+this.monthsShort(c,""),this._monthsParse[b]=new RegExp(d.replace(".",""),"i")),this._monthsParse[b].test(a))return b},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(a){return this._weekdays[a.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(a){return this._weekdaysShort[a.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(a){return this._weekdaysMin[a.day()]},weekdaysParse:function(a){var b,c,d;for(this._weekdaysParse||(this._weekdaysParse=[]),b=0;7>b;b++)if(this._weekdaysParse[b]||(c=bb([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b},isPM:function(a){return"p"===(a+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(a,b){var c=this._calendar[a];return"function"==typeof c?c.apply(b):c},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)},pastFuture:function(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)},ordinal:function(a){return this._ordinal.replace("%d",a)},_ordinal:"%d",preparse:function(a){return a},postformat:function(a){return a},week:function(a){return W(a,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),bb=function(b,c,d,e){return"boolean"==typeof d&&(e=d,d=a),Y({_i:b,_f:c,_l:d,_strict:e,_isUTC:!1})},bb.utc=function(b,c,d,e){var f;return"boolean"==typeof d&&(e=d,d=a),f=Y({_useUTC:!0,_isUTC:!0,_l:d,_i:b,_f:c,_strict:e}).utc()},bb.unix=function(a){return bb(1e3*a)},bb.duration=function(a,b){var c,d,e,g=bb.isDuration(a),h="number"==typeof a,i=g?a._input:h?{}:a,j=null;return h?b?i[b]=a:i.milliseconds=a:(j=pb.exec(a))?(c="-"===j[1]?-1:1,i={y:0,d:q(j[hb])*c,h:q(j[ib])*c,m:q(j[jb])*c,s:q(j[kb])*c,ms:q(j[lb])*c}):(j=qb.exec(a))&&(c="-"===j[1]?-1:1,e=function(a){var b=a&&parseFloat(a.replace(",","."));return(isNaN(b)?0:b)*c},i={y:e(j[2]),M:e(j[3]),d:e(j[4]),h:e(j[5]),m:e(j[6]),s:e(j[7]),w:e(j[8])}),d=new f(i),g&&a.hasOwnProperty("_lang")&&(d._lang=a._lang),d},bb.version=db,bb.defaultFormat=Eb,bb.updateOffset=function(){},bb.lang=function(a,b){var c;return a?(b?y(x(a),b):null===b?(z(a),a="en"):mb[a]||A(a),c=bb.duration.fn._lang=bb.fn._lang=A(a),c._abbr):bb.fn._lang._abbr},bb.langData=function(a){return a&&a._lang&&a._lang._abbr&&(a=a._lang._abbr),A(a)},bb.isMoment=function(a){return a instanceof e},bb.isDuration=function(a){return a instanceof f},cb=Qb.length-1;cb>=0;--cb)p(Qb[cb]);for(bb.normalizeUnits=function(a){return n(a)},bb.invalid=function(a){var b=bb.utc(0/0);return null!=a?g(b._pf,a):b._pf.userInvalidated=!0,b},bb.parseZone=function(a){return bb(a).parseZone()},g(bb.fn=e.prototype,{clone:function(){return bb(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().lang("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){return D(bb(this).utc(),"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")},toArray:function(){var a=this;return[a.year(),a.month(),a.date(),a.hours(),a.minutes(),a.seconds(),a.milliseconds()]},isValid:function(){return w(this)},isDSTShifted:function(){return this._a?this.isValid()&&m(this._a,(this._isUTC?bb.utc(this._a):bb(this._a)).toArray())>0:!1},parsingFlags:function(){return g({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(){return this.zone(0)},local:function(){return this.zone(0),this._isUTC=!1,this},format:function(a){var b=D(this,a||bb.defaultFormat);return this.lang().postformat(b)},add:function(a,b){var c;return c="string"==typeof a?bb.duration(+b,a):bb.duration(a,b),j(this,c,1),this},subtract:function(a,b){var c;return c="string"==typeof a?bb.duration(+b,a):bb.duration(a,b),j(this,c,-1),this},diff:function(a,b,c){var d,e,f=this._isUTC?bb(a).zone(this._offset||0):bb(a).local(),g=6e4*(this.zone()-f.zone());return b=n(b),"year"===b||"month"===b?(d=432e5*(this.daysInMonth()+f.daysInMonth()),e=12*(this.year()-f.year())+(this.month()-f.month()),e+=(this-bb(this).startOf("month")-(f-bb(f).startOf("month")))/d,e-=6e4*(this.zone()-bb(this).startOf("month").zone()-(f.zone()-bb(f).startOf("month").zone()))/d,"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:h(e)},from:function(a,b){return bb.duration(this.diff(a)).lang(this.lang()._abbr).humanize(!b)},fromNow:function(a){return this.from(bb(),a)},calendar:function(){var a=this.diff(bb().zone(this.zone()).startOf("day"),"days",!0),b=-6>a?"sameElse":-1>a?"lastWeek":0>a?"lastDay":1>a?"sameDay":2>a?"nextDay":7>a?"nextWeek":"sameElse";return this.format(this.lang().calendar(b,this))},isLeapYear:function(){return t(this.year())},isDST:function(){return this.zone()+bb(a).startOf(b)},isBefore:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)<+bb(a).startOf(b)},isSame:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)===+bb(a).startOf(b)},min:function(a){return a=bb.apply(null,arguments),this>a?this:a},max:function(a){return a=bb.apply(null,arguments),a>this?this:a},zone:function(a){var b=this._offset||0;return null==a?this._isUTC?b:this._d.getTimezoneOffset():("string"==typeof a&&(a=G(a)),Math.abs(a)<16&&(a=60*a),this._offset=a,this._isUTC=!0,b!==a&&j(this,bb.duration(b-a,"m"),1,!0),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return"string"==typeof this._i&&this.zone(this._i),this},hasAlignedHourOffset:function(a){return a=a?bb(a).zone():0,0===(this.zone()-a)%60},daysInMonth:function(){return r(this.year(),this.month())},dayOfYear:function(a){var b=eb((bb(this).startOf("day")-bb(this).startOf("year"))/864e5)+1;return null==a?b:this.add("d",a-b)},weekYear:function(a){var b=W(this,this.lang()._week.dow,this.lang()._week.doy).year;return null==a?b:this.add("y",a-b)},isoWeekYear:function(a){var b=W(this,1,4).year;return null==a?b:this.add("y",a-b)},week:function(a){var b=this.lang().week(this);return null==a?b:this.add("d",7*(a-b))},isoWeek:function(a){var b=W(this,1,4).week;return null==a?b:this.add("d",7*(a-b))},weekday:function(a){var b=(this.day()+7-this.lang()._week.dow)%7;return null==a?b:this.add("d",a-b)},isoWeekday:function(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)},get:function(a){return a=n(a),this[a]()},set:function(a,b){return a=n(a),"function"==typeof this[a]&&this[a](b),this},lang:function(b){return b===a?this._lang:(this._lang=A(b),this)}}),cb=0;cb