├── .bowerrc ├── .editorconfig ├── .gitignore ├── .jscsrc ├── .jshintrc ├── 001-studiovictory-proxy.conf ├── Berksfile ├── Gruntfile.js ├── README.md ├── Vagrantfile ├── Vagrantfile.chef ├── bower.json ├── cookbooks └── studiovictory │ ├── attributes │ └── default.rb │ ├── metadata.rb │ └── recipes │ └── default.rb ├── grunt └── grunt-server.js ├── karma.conf.js ├── logs └── .gitkeep ├── package.json ├── pm2.json ├── src ├── app.js ├── config │ └── config.yml ├── content │ └── i18n │ │ ├── en.json │ │ └── es.json ├── controllers │ └── home.js ├── lib │ ├── config.js │ ├── environment.js │ └── helpers │ │ ├── handlebars.js │ │ ├── i18n.js │ │ ├── security.js │ │ ├── session.js │ │ └── utils.js ├── public │ └── images │ │ ├── articles │ │ ├── article1.jpg │ │ └── article2.jpg │ │ └── layout │ │ ├── favicon.png │ │ ├── logo-black.png │ │ ├── logo_@1x.png │ │ ├── logo_@2x.png │ │ └── scroll-to-top.png ├── router.js ├── stylus │ ├── _global.styl │ ├── _variables.styl │ ├── blog │ │ ├── _post.styl │ │ └── _posts.styl │ ├── mediaqueries │ │ └── desktop.styl │ ├── mixins │ │ ├── _default.styl │ │ ├── _responsive.styl │ │ └── _studiovictory.styl │ ├── site │ │ ├── _footer.styl │ │ ├── _header.styl │ │ └── _pagination.styl │ └── style.styl └── views │ ├── error.hbs │ ├── home │ └── welcome.hbs │ └── layouts │ └── main.hbs └── test ├── fixtures ├── config.yml └── public │ └── js │ └── .gitkeep ├── lib └── configTest.js ├── public └── js │ └── fakeTest.js └── testHelper.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "src/public/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.hbs] 12 | indent_size = 4 13 | 14 | [*.html] 15 | indent_size = 4 16 | 17 | [*.styl] 18 | indent_size = 4 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vagrant 3 | Berksfile.lock 4 | git-info.txt 5 | logs/*.log 6 | node_modules 7 | npm-debug.log 8 | src/public/bower_components 9 | src/public/css/**/*.css 10 | src/public/css/*.css 11 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "google", 3 | "requireLineFeedAtFileEnd": null, 4 | "disallowCommaBeforeLineBreak": null, 5 | "disallowDanglingUnderscores": null, 6 | "disallowEmptyBlocks": null, 7 | "disallowKeywordsOnNewLine": ["else"], 8 | "disallowMixedSpacesAndTabs": null, 9 | "disallowMultipleLineStrings": null, 10 | "disallowSpaceAfterKeywords": null, 11 | "disallowTrailingComma": null, 12 | "disallowTrailingWhitespace": null, 13 | "maximumLineLength": null, 14 | "requireCamelCaseOrUpperCaseIdentifiers": null, 15 | "requireCapitalizedConstructors": null, 16 | "requireCommaBeforeLineBreak": null, 17 | "requireCurlyBraces": null, 18 | "requireDotNotation": null, 19 | "requireMultipleVarDecl": null, 20 | "requireOperatorBeforeLineBreak": null, 21 | "requireParenthesesAroundIIFE": null, 22 | "validateIndentation": null, 23 | "validateQuoteMarks": null 24 | } 25 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "es3": false, 7 | "forin": true, 8 | "freeze": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": "nofunc", 12 | "newcap": true, 13 | "noarg": true, 14 | "noempty": true, 15 | "nonbsp": true, 16 | "nonew": true, 17 | "plusplus": false, 18 | "quotmark": "single", 19 | "undef": true, 20 | "unused": false, 21 | "strict": false, 22 | "maxparams": 10, 23 | "maxdepth": 5, 24 | "maxstatements": 40, 25 | "maxcomplexity": 8, 26 | "maxlen": 120, 27 | "asi": false, 28 | "boss": false, 29 | "debug": false, 30 | "eqnull": true, 31 | "esnext": false, 32 | "evil": false, 33 | "expr": true, 34 | "funcscope": false, 35 | "globalstrict": false, 36 | "iterator": false, 37 | "lastsemic": false, 38 | "laxbreak": false, 39 | "laxcomma": false, 40 | "loopfunc": true, 41 | "maxerr": false, 42 | "moz": false, 43 | "multistr": true, 44 | "notypeof": false, 45 | "proto": false, 46 | "scripturl": false, 47 | "shadow": false, 48 | "sub": true, 49 | "supernew": false, 50 | "validthis": false, 51 | "noyield": false, 52 | "browser": true, 53 | "node": true, 54 | "globals": { 55 | "angular": false, 56 | "describe": false, 57 | "it": false, 58 | "before": false, 59 | "after": false, 60 | "beforeEach": false, 61 | "afterEach": false, 62 | "assert": true, 63 | "expect": false, 64 | "inject": false, 65 | "chai": false, 66 | "sinon": false, 67 | "$controller": false, 68 | "$httpBackend": false, 69 | "$location": false, 70 | "$q": false, 71 | "$rootScope": false, 72 | "$state": false, 73 | "$templateCache": false, 74 | "$": false, 75 | "define": false, 76 | "routerHelper": false, 77 | "specHelper": false, 78 | "mockData": false 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /001-studiovictory-proxy.conf: -------------------------------------------------------------------------------- 1 | NameVirtualHost local.studiovictory.com:80 2 | 3 | LoadModule proxy_module modules/mod_proxy.so 4 | LoadModule proxy_http_module modules/mod_proxy_http.so 5 | 6 | 7 | ServerName local.studiovictory.com 8 | ProxyPass / http://localhost:9898/ 9 | ProxyPassReverse / http://localhost:9898/ 10 | ProxyPassReverseCookieDomain localhost studiovictory.com 11 | 12 | -------------------------------------------------------------------------------- /Berksfile: -------------------------------------------------------------------------------- 1 | source 'https://supermarket.getchef.com' 2 | 3 | cookbook 'nodejs', '~> 2.2.0' 4 | cookbook 'yum-epel', '~> 0.6.0' 5 | cookbook 'studiovictory', path: 'cookbooks/studiovictory' 6 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.loadNpmTasks('grunt-contrib-jshint'); 5 | grunt.loadNpmTasks('grunt-contrib-stylus'); 6 | grunt.loadNpmTasks('grunt-jscs'); 7 | grunt.loadNpmTasks('grunt-githooks'); 8 | grunt.loadNpmTasks('grunt-karma'); 9 | grunt.loadNpmTasks('grunt-mocha-test'); 10 | grunt.loadNpmTasks('grunt-shell'); 11 | grunt.loadTasks('grunt'); 12 | 13 | grunt.initConfig({ 14 | pkg: grunt.file.readJSON('package.json'), 15 | jscs: { 16 | options: { 17 | config: '.jscsrc', 18 | reporter: 'checkstyle' 19 | }, 20 | src: [ 21 | 'Gruntfile.js', 22 | 'src/**/*.js', 23 | '!src/public/bower_components/**' 24 | ] 25 | }, 26 | jshint: { 27 | options: { 28 | jshintrc: '.jshintrc', 29 | reporter: 'checkstyle' 30 | }, 31 | src: [ 32 | 'Gruntfile.js', 33 | 'src/**/*.js', 34 | '!src/public/bower_components/**' 35 | ] 36 | }, 37 | githooks: { 38 | all: { 39 | options: { 40 | endMarker: '' 41 | }, 42 | 'pre-commit': 'analyze', 43 | 'pre-push': 'test', 44 | 'post-checkout': 'shell:gitLog', 45 | 'post-commit': 'shell:gitLog', 46 | 'post-merge': 'shell:gitLog shell:npmInstall' 47 | } 48 | }, 49 | shell: { 50 | gitLog: { 51 | command: 'git log -1 > git-info.txt' 52 | }, 53 | npmInstall: { 54 | command: 'npm install' 55 | }, 56 | vagrantLogs: { 57 | command: 'vagrant ssh -c "cd /vagrant && pm2 logs"' 58 | }, 59 | vagrantStatus: { 60 | command: 'vagrant ssh -c "cd /vagrant && pm2 list"' 61 | }, 62 | vagrantStop: { 63 | command: 'vagrant ssh -c "cd /vagrant && pm2 kill"' 64 | }, 65 | vagrantDelete: { 66 | command: 'vagrant ssh -c "cd /vagrant && pm2 delete pm2.json"' 67 | }, 68 | vagrantStart: { 69 | command: 'vagrant ssh -c "cd /vagrant && pm2 start pm2.json"' 70 | } 71 | }, 72 | mochaTest: { 73 | all: { 74 | options: { 75 | reporter: 'spec' 76 | }, 77 | src: ['test/**/*Test.js', '!test/public/js/**/*Test.js'] 78 | } 79 | }, 80 | karma: { 81 | client: { 82 | configFile: 'karma.conf.js' 83 | } 84 | }, 85 | stylus: { 86 | compile: { 87 | options: { 88 | compress: true, 89 | paths: ['source/stylus'] 90 | }, 91 | files: { 92 | 'src/public/css/stylus.css': 'src/stylus/style.styl' 93 | } 94 | } 95 | } 96 | }); 97 | 98 | // Code tasks 99 | grunt.registerTask('default', ['test']); 100 | grunt.registerTask('test', 'Runs unit tests', ['mochaTest', 'karma:client']); 101 | grunt.registerTask('analyze', 'Validates code style', ['jshint', 'jscs']); 102 | }; 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StudioVictory 2 | 3 | ## Setup Instructions (For Mac/Linux) 4 | 5 | 1. Install Apache Server, you can install it using MAMP, XAMPP, WAMPP or in native way. 6 | 7 | 2. Install the latest version of Vagrant () 8 | 9 | 3. Install the latest version of VirtualBox () 10 | 11 | 4. Install the latest version of ChefDK () 12 | 13 | 5. Install the latest version of Node.js () 14 | 15 | 6. Install the Berkshelf, Omnibus, and VBGuest plugins for VirtualBox: 16 | 17 | For Mac: 18 | 19 | `NOKOGIRI_USE_SYSTEM_LIBRARIES=1 vagrant plugin install vagrant-berkshelf vagrant-omnibus vagrant-vbguest` 20 | 21 | For Windows: 22 | 23 | `vagrant plugin install vagrant-berkshelf vagrant-omnibus vagrant-vbguest` 24 | 25 | 7. Install global dependencies: 26 | 27 | `npm install -g grunt-cli bower pm2 stylus` 28 | 29 | 8. Install local dependencies: 30 | 31 | `npm install` 32 | 33 | 9. Spin up your Vagrant VM: 34 | 35 | `vagrant up` 36 | 37 | ## Grunt Tasks 38 | `grunt --help` lists available tasks. 39 | 40 | * `grunt test` - Runs unit tests (default) 41 | * `grunt analyze` - Validates code style 42 | * `grunt status` - Shows status of node processes on Vagrant VM 43 | * `grunt stop` - Stop node processes on Vagrant VM 44 | * `grunt start` - Start node processes on Vagrant VM 45 | * `grunt restart` - Restart node processes on Vagrant VM 46 | * `grunt logs` - Tail logs for all node processes on Vagrant VM 47 | 48 | ## Vagrant Commands 49 | `vagrant --help` lists available commands. 50 | 51 | * `vagrant status` - Display status of the VM 52 | * `vagrant up` - Power up, un-pause, or create the VM (dependent on status) 53 | * `vagrant destroy` - Delete the VM 54 | * `vagrant halt` - Power down the VM 55 | * `vagrant suspend` - Pause the VM 56 | * `vagrant reload` - Reboot the VM 57 | * `vagrant ssh` - Open an SSH connection into the VM 58 | * `vagrant provision` - (Re)provision the VM 59 | * `vagrant destroy -f && vagrant up` - Rebuild VM 60 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | VM = 'studiovictory' 5 | 6 | Vagrant.configure(2) do |config| 7 | # Selecting Cent OS 7.0 Box 8 | config.vm.box = 'chef/centos-7.0' 9 | config.vbguest.auto_update = false 10 | 11 | # Proxy network, Node: 3000 & Apache: 9898 12 | config.vm.network :forwarded_port, guest: 3000, host: 9898, auto_correct: true 13 | config.vm.network :forwarded_port, guest: 22, host: 2200, id: "ssh", disabled: "true" 14 | config.vm.network :forwarded_port, guest: 22, host: 2201 15 | 16 | # Virtualbox setup 17 | config.vm.define VM 18 | config.vm.provider :virtualbox do |vb| 19 | vb.name = VM 20 | end 21 | 22 | # Fixing issue: stdin is not a tty 23 | ssh_fix = 'bash -c "BASH_ENV=/etc/profile exec bash"' 24 | config.ssh.shell = ssh_fix unless ARGV[0] == 'ssh' 25 | 26 | # Adding omnibus & berkshelf plugins 27 | config.omnibus.chef_version = :latest 28 | config.berkshelf.enabled = true 29 | config.berkshelf.berksfile_path = './Berksfile' 30 | 31 | # Chef provisioning 32 | config.vm.provision :chef_solo do |chef| 33 | chef.add_recipe VM 34 | chef.custom_config_path = 'Vagrantfile.chef' 35 | chef.json = { 36 | :nodejs => { 37 | :install_method => "package", 38 | :npm => "2.13.4" 39 | } 40 | } 41 | end 42 | 43 | # Start node app 44 | config.vm.provision :shell do |s| 45 | s.privileged = false 46 | s.inline = 'cd /vagrant && pm2 start pm2.json' 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /Vagrantfile.chef: -------------------------------------------------------------------------------- 1 | Chef::Config.ssl_verify_mode = :verify_peer -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "StudioVictory", 3 | "version": "0.0.1", 4 | "author": "Carlos Santana", 5 | "license": "MIT", 6 | "dependencies": { 7 | "angular": "~1.4.3", 8 | "font-awesome": "latest", 9 | "lodash": "2.4.1", 10 | "normalize-css": "latest", 11 | "requirejs": "latest" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /cookbooks/studiovictory/attributes/default.rb: -------------------------------------------------------------------------------- 1 | default['nodejs']['packages'] = %w(nodejs npm) 2 | default['npm']['packages'] = { 3 | 'pm2' => '0.14.6', 4 | 'stylus' => '0.52.0', 5 | 'grunt-cli' => '0.1.13', 6 | 'bower' => '1.4.1', 7 | 'mocha' => '2.2.5' 8 | } 9 | -------------------------------------------------------------------------------- /cookbooks/studiovictory/metadata.rb: -------------------------------------------------------------------------------- 1 | name 'studiovictory' 2 | maintainer 'Carlos Santana' 3 | maintainer_email 'carlos@milkzoft.com' 4 | license 'MIT' 5 | description 'Installs/Configures studiovictory' 6 | version '0.0.1' 7 | 8 | depends 'redisio' 9 | depends 'nodejs' 10 | -------------------------------------------------------------------------------- /cookbooks/studiovictory/recipes/default.rb: -------------------------------------------------------------------------------- 1 | include_recipe 'redisio' 2 | include_recipe 'redisio::enable' 3 | include_recipe 'yum-epel' 4 | 5 | node['nodejs']['packages'].each do |node_pkg| 6 | package node_pkg 7 | end 8 | 9 | node['npm']['packages'].each do |pkg, ver| 10 | nodejs_npm pkg do 11 | version ver 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /grunt/grunt-server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.registerTask('status', 'Shows status of node processes', ['shell:vagrantStatus']); 5 | grunt.registerTask('stop', 'Stop node processes', ['shell:vagrantStop']); 6 | grunt.registerTask('start', 'Start node processes', ['shell:vagrantStart']); 7 | grunt.registerTask('restart', 'Restart node processes', ['stop', 'start']); 8 | grunt.registerTask('logs', 'Tail logs for all pm2 processes', ['shell:vagrantLogs']); 9 | }; 10 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(config) { 4 | config.set({ 5 | basePath: '', 6 | 7 | frameworks: ['mocha', 'chai', 'sinon'], 8 | 9 | files: [ 10 | // Dependencies 11 | 'src/public/bower_components/lodash/dist/lodash.min.js', 12 | 13 | // Tests files 14 | 'test/public/js/**/*Test.js' 15 | ], 16 | 17 | exclude: [], 18 | 19 | preprocessors: { 20 | 'test/fixtures/public/js/**/*.html': ['html2js'], 21 | 'test/fixtures/public/js/**/*.json': ['html2js'] 22 | }, 23 | 24 | reporters: ['mocha'], 25 | 26 | port: 9876, 27 | 28 | colors: true, 29 | 30 | logLevel: config.LOG_INFO, 31 | 32 | autoWatch: false, 33 | 34 | browsers: ['PhantomJS'], 35 | 36 | singleRun: true, 37 | 38 | client: { 39 | captureConsole: true 40 | } 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeJobs/StudioVictory/464333ec84851bb99832119e3d476385c0dcbb57/logs/.gitkeep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "StudioVictory", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "preinstall": "npm prune", 7 | "postinstall": "bower prune; bower install; grunt githooks" 8 | }, 9 | "author": "Carlos Santana", 10 | "license": "MIT", 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/CodeJobs/StudioVictory.git" 14 | }, 15 | "dependencies": { 16 | "body-parser": "~1.10.2", 17 | "cookie-parser": "~1.3.3", 18 | "debug": "~2.1.1", 19 | "express": "~4.11.1", 20 | "express-handlebars": "latest", 21 | "lodash": "latest", 22 | "morgan": "~1.5.1", 23 | "stylus": "latest", 24 | "js-yaml": "latest", 25 | "html-minifier": "latest" 26 | }, 27 | "devDependencies": { 28 | "bower": "latest", 29 | "chai": "latest", 30 | "grunt": "latest", 31 | "grunt-contrib-jshint": "latest", 32 | "grunt-contrib-stylus": "latest", 33 | "grunt-githooks": "latest", 34 | "grunt-jscs": "latest", 35 | "grunt-karma": "latest", 36 | "grunt-mocha-test": "latest", 37 | "grunt-shell": "latest", 38 | "karma": "latest", 39 | "karma-chai": "latest", 40 | "karma-html2js-preprocessor": "latest", 41 | "karma-mocha": "latest", 42 | "karma-mocha-reporter": "latest", 43 | "karma-phantomjs-launcher": "latest", 44 | "karma-sinon": "latest", 45 | "mocha": "latest", 46 | "rewire": "latest", 47 | "sinon": "latest" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps": [{ 3 | "name" : "CLIENT", 4 | "script" : "./src/app.js", 5 | "port": "9898", 6 | "watch": "./src", 7 | "ignore_watch": ["./src/stylus", "./src/public"], 8 | "watch_options": { 9 | "usePolling": true 10 | }, 11 | "error_file": "./logs/app-error.log", 12 | "out_file": "./logs/app-out.log", 13 | "env": { 14 | "NODE_ENV": "development" 15 | } 16 | }] 17 | } 18 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Loading dependencies 4 | var express = require('express'); 5 | var path = require('path'); 6 | 7 | // Initializing express application 8 | var app = express(); 9 | 10 | // Loading Config 11 | var config = require('./lib/config'); 12 | 13 | // Body Parser 14 | var bodyParser = require('body-parser'); 15 | app.use(bodyParser.json()); 16 | app.use(bodyParser.urlencoded({ 17 | extended: false 18 | })); 19 | 20 | // Logger 21 | var logger = require('morgan'); 22 | app.use(logger('dev')); 23 | 24 | // Cookies / Session 25 | var cookieParser = require('cookie-parser'); 26 | var session = require('./lib/helpers/session'); 27 | 28 | app.use(cookieParser()); 29 | app.use(session); 30 | 31 | // Layout setup 32 | var exphbs = require('express-handlebars'); 33 | var hbsHelpers = require('./lib/helpers/handlebars'); 34 | 35 | // Stylus setup 36 | var stylus = require('stylus'); 37 | 38 | // Compile Stylus on the fly 39 | if (!config().html.css.stylusPrecompile) { 40 | app.use( 41 | stylus.middleware({ 42 | src: __dirname + '/stylus', 43 | dest: __dirname + '/public/css', 44 | compile: function(str, path) { 45 | return stylus(str) 46 | .set('filename', path) 47 | .set('compress', config().html.css.compress); 48 | } 49 | }) 50 | ); 51 | } 52 | 53 | // Handlebars setup 54 | app.engine(config().views.engine, exphbs({ 55 | extname: config().views.extension, 56 | defaultLayout: config().views.layout, 57 | layoutsDir: __dirname + '/views/layouts', 58 | partialsDir: __dirname + '/views/partials', 59 | helpers: hbsHelpers 60 | })); 61 | 62 | // View engine setup 63 | app.set('views', path.join(__dirname, 'views')); 64 | app.set('view engine', config().views.engine); 65 | app.use(express.static(path.join(__dirname, 'public'))); 66 | 67 | // Sending config to templates 68 | app.use(function(req, res, next) { 69 | res.locals.config = config(); 70 | next(); 71 | }); 72 | 73 | // Disabling x-powered-by 74 | app.disable('x-powered-by'); 75 | 76 | require('./router')(app); 77 | 78 | // Export application or start the server 79 | if (!!module.parent) { 80 | module.exports = app; 81 | } else { 82 | app.listen(config().serverPort); 83 | } 84 | -------------------------------------------------------------------------------- /src/config/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | production: &default 3 | serverPort: 3000 4 | baseUrl: http://www.studiovictory.com 5 | baseApi: /api/ 6 | views: 7 | engine: .hbs 8 | extension: .hbs 9 | layout: main 10 | html: 11 | minify: true 12 | css: 13 | compress: true 14 | stylusPrecompile: true 15 | controllers: 16 | default: home 17 | languages: 18 | default: en 19 | list: [en, es] 20 | security: 21 | secret: stud10v1ct0ry.com 22 | session: 23 | cookieDomain: .studiovictory.com 24 | maxAge: 86400 25 | cookiePrefix: 'svSession_' 26 | path: '/' 27 | httpOnly: true 28 | 29 | stage: &stage 30 | <<: *default 31 | baseUrl: http://stage.studiovictory.com 32 | 33 | latest: &latest 34 | <<: *stage 35 | baseUrl: http://latest.studiovictory.com 36 | 37 | development: 38 | <<: *latest 39 | baseUrl: http://local.studiovictory.com 40 | html: 41 | minify: false 42 | css: 43 | compress: false 44 | stylusPrecompile: false 45 | -------------------------------------------------------------------------------- /src/content/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "site": { 3 | "language": "en", 4 | "title": "StudioVictory - English", 5 | "meta": { 6 | "abstract": "Learn to code", 7 | "description": "Learn to code", 8 | "keywords": "HTML5, JavaScript, Node.js" 9 | } 10 | }, 11 | "welcome": "Welcome to", 12 | "visited": "You have visited this site", 13 | "time": "time", 14 | "times": "times" 15 | } 16 | -------------------------------------------------------------------------------- /src/content/i18n/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "site": { 3 | "language": "en", 4 | "title": "StudioVictory - Español", 5 | "meta": { 6 | "abstract": "Aprende a programar", 7 | "description": "Aprende a programar", 8 | "keywords": "HTML5, JavaScript, Node.js" 9 | } 10 | }, 11 | "welcome": "Bienvenido a", 12 | "visited": "Usted ha visitado este sitio", 13 | "time": "vez", 14 | "times": "veces" 15 | } 16 | -------------------------------------------------------------------------------- /src/controllers/home.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | router.get('/', function(req, res, next) { 5 | var visits = res.session('visits') || 0; 6 | 7 | res.session('visits', ++visits); 8 | 9 | res.render('home/welcome', { 10 | siteName: 'StudioVictory', 11 | visits: visits 12 | }); 13 | }); 14 | 15 | module.exports = router; 16 | -------------------------------------------------------------------------------- /src/lib/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var yaml = require('js-yaml'); 5 | var environment = require('./environment'); 6 | var config = yaml.safeLoad(fs.readFileSync(__dirname + '/../config/config.yml', 'utf-8')); 7 | 8 | module.exports = function() { 9 | return config[environment().name] || {}; 10 | }; 11 | -------------------------------------------------------------------------------- /src/lib/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function() { 4 | return { 5 | name: process.env.NODE_ENV ? process.env.NODE_ENV : 'production' 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /src/lib/helpers/handlebars.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../config'); 4 | var minify = require('html-minifier').minify; 5 | var utils = require('./utils'); 6 | 7 | module.exports = { 8 | // String helpers 9 | debug: function(variable) { 10 | console.log('Debugging Handlebars:'); 11 | console.log('====================='); 12 | console.log(this); 13 | 14 | console.log('Dumping Variable:'); 15 | console.log('========================'); 16 | console.log(variable); 17 | }, 18 | 19 | lowercase: function(str) { 20 | return str.toLowerCase(); 21 | }, 22 | 23 | uppercase: function(str) { 24 | return str.toUpperCase(); 25 | }, 26 | 27 | reverse: function(str) { 28 | return str.split('').reverse().join(''); 29 | }, 30 | 31 | // Numbers helpers 32 | ceil: function(number) { 33 | return Math.ceil(parseFloat(number)); 34 | }, 35 | 36 | // Date helpers 37 | now: function() { 38 | return new Date(); 39 | }, 40 | 41 | // Conditionals helpers 42 | is: function(variable, value, options) { 43 | if (variable && variable === value) { 44 | return options.fn(this); 45 | } else { 46 | return options.inverse(this); 47 | } 48 | }, 49 | 50 | isNot: function(variable, value, options) { 51 | if (!variable || variable !== value) { 52 | return options.fn(this); 53 | } else { 54 | return options.inverse(this); 55 | } 56 | }, 57 | 58 | gt: function(value1, value2, options) { 59 | if (value1 > value2) { 60 | return options.fn(this); 61 | } else { 62 | return options.inverse(this); 63 | } 64 | }, 65 | 66 | gte: function(value1, value2, options) { 67 | if (value1 >= value2) { 68 | return options.fn(this); 69 | } else { 70 | return options.inverse(this); 71 | } 72 | }, 73 | 74 | lt: function(value1, value2, options) { 75 | if (value1 < value2) { 76 | return options.fn(this); 77 | } else { 78 | return options.inverse(this); 79 | } 80 | }, 81 | 82 | lte: function(value1, value2, options) { 83 | if (value1 <= value2) { 84 | return options.fn(this); 85 | } else { 86 | return options.inverse(this); 87 | } 88 | }, 89 | 90 | // HTML & Json helpers 91 | json: function(content) { 92 | return JSON.stringify(content); 93 | }, 94 | 95 | minify: function(content) { 96 | if (config().html.minify) { 97 | return minify(content.fn(this), { 98 | removeComments: true, 99 | collapseWhitespace: true, 100 | minifyJS: true 101 | }); 102 | } 103 | 104 | return content.fn(this); 105 | } 106 | }; 107 | -------------------------------------------------------------------------------- /src/lib/helpers/i18n.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../config'); 4 | var utils = require('./utils'); 5 | var _ = require('lodash'); 6 | 7 | module.exports = { 8 | load: function(language) { 9 | var content; 10 | 11 | if (_.contains(config().languages.list, language)) { 12 | try { 13 | content = require('../../content/i18n/' + language); 14 | } catch (e) { 15 | content = require('../../content/i18n/' + config().languages.default); 16 | } 17 | } else { 18 | content = require('../../content/i18n/' + config().languages.default); 19 | } 20 | 21 | return content; 22 | }, 23 | 24 | getCurrentLanguage: function(url) { 25 | var params = utils.getParamsFromUrl(url); 26 | 27 | if (_.contains(config().languages.list, params[0])) { 28 | return params[0]; 29 | } else { 30 | return config().languages.default; 31 | } 32 | }, 33 | 34 | getLanguagePath: function(url) { 35 | var params = utils.getParamsFromUrl(url); 36 | 37 | if (_.contains(config().languages.list, params[0])) { 38 | return '/' + params[0]; 39 | } else { 40 | return ''; 41 | } 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /src/lib/helpers/security.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../config'); 4 | var crypto = require('crypto'); 5 | var salt = config().security.secret; 6 | 7 | module.exports = { 8 | sha1: function(str) { 9 | return crypto.createHash('sha1').update(salt + str.toString()).digest('hex'); 10 | }, 11 | 12 | md5: function(str) { 13 | return crypto.createHash('md5').update(salt + str.toString()).digest('hex'); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/lib/helpers/session.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var utils = require('./utils'); 5 | var config = require('../config'); 6 | 7 | module.exports = function(req, res, next) { 8 | var configData = config().session; 9 | var cookiePrefix = configData.cookiePrefix; 10 | var sessionData = parseSession(); 11 | 12 | var options = { 13 | domain: configData.cookieDomain, 14 | path: configData.path, 15 | maxAge: configData.maxAge, 16 | httpOnly: configData.httpOnly 17 | }; 18 | 19 | var deleteOptions = { 20 | domain: configData.cookieDomain, 21 | path: configData.path, 22 | httpOnly: configData.httpOnly 23 | }; 24 | 25 | res.session = session; 26 | res.clearSession = clearSession; 27 | res.destroySessions = destroySessions; 28 | 29 | next(); 30 | 31 | function parseSession() { 32 | var rVal = {}; 33 | 34 | _.forEach(req.cookies, function(value, key) { 35 | var sessionPrefix = new RegExp('^' + cookiePrefix); 36 | var isSessionCookie = key.search(sessionPrefix) !== -1; 37 | 38 | if (isSessionCookie) { 39 | key = key.replace(sessionPrefix, ''); 40 | 41 | if (utils.isJson(value)) { 42 | value = JSON.parse(value); 43 | } 44 | 45 | rVal[key] = value; 46 | } 47 | }); 48 | 49 | return rVal; 50 | } 51 | 52 | function session(key, value) { 53 | var domain; 54 | var cookieKey; 55 | var cookieValue; 56 | 57 | // required params missing 58 | if (!key && (typeof value === 'undefined' || value === null)) { 59 | return sessionData; 60 | } 61 | 62 | // retrieve value 63 | if (!value) { 64 | return sessionData[key]; 65 | } 66 | 67 | // set value 68 | sessionData[key] = value; 69 | 70 | // set cookie 71 | cookieKey = cookiePrefix + key; 72 | cookieValue = typeof value === 'string' ? value : JSON.stringify(value); 73 | 74 | res.cookie(cookieKey, cookieValue, options); 75 | } 76 | 77 | function clearSession(keys) { 78 | var cookieKey; 79 | var key = keys; 80 | 81 | if (keys instanceof Array) { 82 | _.forEach(keys, function(key) { 83 | delete sessionData[key]; 84 | cookieKey = cookiePrefix + key; 85 | res.clearCookie(cookieKey, deleteOptions); 86 | }); 87 | } else { 88 | delete sessionData[key]; 89 | cookieKey = cookiePrefix + key; 90 | res.clearCookie(cookieKey, deleteOptions); 91 | } 92 | } 93 | 94 | function destroySessions() { 95 | if (sessionData) { 96 | var cookieKey; 97 | 98 | _.forEach(sessionData, function(value, key) { 99 | delete sessionData[key]; 100 | 101 | cookieKey = cookiePrefix + key; 102 | res.clearCookie(cookieKey, deleteOptions); 103 | }); 104 | } else { 105 | return; 106 | } 107 | } 108 | }; 109 | -------------------------------------------------------------------------------- /src/lib/helpers/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('../config'); 4 | var security = require('./security'); 5 | 6 | module.exports = { 7 | md5: function(str) { 8 | if (this.isDefined(str)) { 9 | return security.md5(str); 10 | } 11 | 12 | return false; 13 | }, 14 | 15 | sha1: function(str) { 16 | if (this.isDefined(str)) { 17 | return security.sha1(str); 18 | } 19 | 20 | return false; 21 | }, 22 | 23 | encrypt: function(str) { 24 | return security.sha1(security.md5(str)); 25 | }, 26 | 27 | hash: function(str) { 28 | if (this.isDefined(str) && config().encryptInputs) { 29 | return security.md5(str); 30 | } 31 | 32 | return false; 33 | }, 34 | 35 | isYear: function(year) { 36 | return (typeof year !== 'undefined' && year.length === 4 && !isNaN(year)); 37 | }, 38 | 39 | isMonth: function(month) { 40 | return (typeof month !== 'undefined' && month.length === 2 && !isNaN(month) && month <= 12); 41 | }, 42 | 43 | isDay: function(day) { 44 | return (typeof day !== 'undefined' && day.length === 2 && !isNaN(day) && day <= 31); 45 | }, 46 | 47 | isDesktop: function(ua) { 48 | return !(/mobile/i.test(ua)); 49 | }, 50 | 51 | isMobile: function(ua) { 52 | return (/mobile/i.test(ua)); 53 | }, 54 | 55 | getCurrentDevice: function(ua) { 56 | return (/mobile/i.test(ua)) ? 'mobile' : 'desktop'; 57 | }, 58 | 59 | isFunction: function(func) { 60 | return (typeof func === 'function') ? true : false; 61 | }, 62 | 63 | isDefined: function(variable) { 64 | return (typeof variable !== 'undefined') ? true : false; 65 | }, 66 | 67 | isUndefined: function(variable) { 68 | return (typeof variable === 'undefined') ? true : false; 69 | }, 70 | 71 | isNumber: function(number) { 72 | return !isNaN(number) ? true : false; 73 | }, 74 | 75 | getParamsFromUrl: function(params) { 76 | params = params.split('/'); 77 | params.shift(); 78 | 79 | return params; 80 | }, 81 | 82 | randomCode: function(max, charSet) { 83 | var randomCode = ''; 84 | var randomPoz; 85 | 86 | max = max || 12; 87 | charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 88 | 89 | for (var i = 0; i < max; i++) { 90 | randomPoz = Math.floor(Math.random() * charSet.length); 91 | randomCode += charSet.substring(randomPoz, randomPoz + 1); 92 | } 93 | 94 | return randomCode; 95 | }, 96 | 97 | removeHTML: function(str) { 98 | if (this.isDefined(str)) { 99 | return str.replace(/(<([^>]+)>)/ig, ''); 100 | } 101 | 102 | return false; 103 | }, 104 | 105 | escape: function(str) { 106 | if (this.isDefined(str)) { 107 | return str 108 | .replace(/'/g, '\\\'') 109 | .replace(/"/g, '\\\\"') 110 | .replace(/&/g, '&') 111 | .replace(//g, '>'); 113 | } 114 | 115 | return false; 116 | }, 117 | 118 | clean: function(str) { 119 | if (this.isDefined(str)) { 120 | return this.removeHTML(str).replace(/[`ª´·¨Ç~¿!#$%^&*()_|+\-=?;'",<>\{\}\[\]\\]/gi, ''); 121 | } 122 | 123 | return false; 124 | }, 125 | 126 | convertSecondsToHHMMSS: function(seconds) { 127 | if (!seconds) { 128 | return '00:00:00'; 129 | } 130 | 131 | var time; 132 | var hours = Math.floor(seconds / 3600); 133 | var minutes = Math.floor((seconds - (hours * 3600)) / 60); 134 | 135 | seconds = seconds - (hours * 3600) - (minutes * 60); 136 | 137 | if (hours < 10) { 138 | hours = '0' + hours; 139 | } 140 | 141 | if (minutes < 10) { 142 | minutes = '0' + minutes; 143 | } 144 | 145 | if (seconds < 10) { 146 | seconds = '0' + seconds; 147 | } 148 | 149 | time = hours + ':' + minutes + ':' + seconds; 150 | 151 | return time; 152 | }, 153 | 154 | convertCamelToNatural: function(str) { 155 | str = str.charAt(0).toUpperCase() + str.slice(1); 156 | 157 | return str.split(/(?=[A-Z])/).join(' '); 158 | }, 159 | 160 | isJson: function(str) { 161 | if (str === null) { 162 | return false; 163 | } 164 | 165 | try { 166 | JSON.parse(str); 167 | } catch (e) { 168 | return false; 169 | } 170 | 171 | return true; 172 | } 173 | }; 174 | -------------------------------------------------------------------------------- /src/public/images/articles/article1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeJobs/StudioVictory/464333ec84851bb99832119e3d476385c0dcbb57/src/public/images/articles/article1.jpg -------------------------------------------------------------------------------- /src/public/images/articles/article2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeJobs/StudioVictory/464333ec84851bb99832119e3d476385c0dcbb57/src/public/images/articles/article2.jpg -------------------------------------------------------------------------------- /src/public/images/layout/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeJobs/StudioVictory/464333ec84851bb99832119e3d476385c0dcbb57/src/public/images/layout/favicon.png -------------------------------------------------------------------------------- /src/public/images/layout/logo-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeJobs/StudioVictory/464333ec84851bb99832119e3d476385c0dcbb57/src/public/images/layout/logo-black.png -------------------------------------------------------------------------------- /src/public/images/layout/logo_@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeJobs/StudioVictory/464333ec84851bb99832119e3d476385c0dcbb57/src/public/images/layout/logo_@1x.png -------------------------------------------------------------------------------- /src/public/images/layout/logo_@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeJobs/StudioVictory/464333ec84851bb99832119e3d476385c0dcbb57/src/public/images/layout/logo_@2x.png -------------------------------------------------------------------------------- /src/public/images/layout/scroll-to-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeJobs/StudioVictory/464333ec84851bb99832119e3d476385c0dcbb57/src/public/images/layout/scroll-to-top.png -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('./lib/config'); 4 | var availableLanguages = config().languages.list.join('|'); 5 | var defaultController; 6 | var homeController; 7 | 8 | module.exports = function(app) { 9 | // Loading controllers 10 | defaultController = require('./controllers/' + config().controllers.default); 11 | homeController = require('./controllers/home'); 12 | 13 | // Loading necessary helpers 14 | var i18n = require('./lib/helpers/i18n'); 15 | var utils = require('./lib/helpers/utils'); 16 | 17 | // Sending variables to templates 18 | app.use(function(req, res, next) { 19 | res.locals.isMobile = utils.isMobile(req.headers['user-agent']); 20 | res.locals.config.basePath = config().baseUrl + i18n.getLanguagePath(req.url); 21 | res.locals.currentLanguage = i18n.getCurrentLanguage(req.url); 22 | res.__ = res.locals.__ = i18n.load(i18n.getCurrentLanguage(req.url)); 23 | next(); 24 | }); 25 | 26 | // Default css & js files 27 | app.use(function(req, res, next) { 28 | res.locals.css = [ 29 | '/css/style.css' 30 | ]; 31 | 32 | res.locals.js = []; 33 | 34 | next(); 35 | }); 36 | 37 | // Controller dispatch 38 | app.use('/', defaultController); 39 | app.use('/:language(' + availableLanguages + ')', defaultController); 40 | app.use('/:language(' + availableLanguages + ')/home', homeController); 41 | 42 | // catch 404 and forward to error handler 43 | app.use(function(req, res, next) { 44 | var err = new Error('Not Found'); 45 | err.status = 404; 46 | next(err); 47 | }); 48 | 49 | // error handlers 50 | if (app.get('env') === 'development') { 51 | app.use(function(err, req, res, next) { 52 | res.status(err.status || 500); 53 | res.render('error', { 54 | message: err.message, 55 | error: err 56 | }); 57 | }); 58 | } 59 | 60 | app.use(function(err, req, res, next) { 61 | res.status(err.status || 500); 62 | res.render('error', { 63 | message: err.message, 64 | error: {} 65 | }); 66 | }); 67 | }; 68 | -------------------------------------------------------------------------------- /src/stylus/_global.styl: -------------------------------------------------------------------------------- 1 | body 2 | font('PT Serif', 1em) 3 | 4 | .no-display 5 | display none 6 | 7 | .clear 8 | clear both 9 | 10 | .btn 11 | borderRadius(5px) 12 | color grayColor(10) 13 | display inline-block 14 | font-size .875rem 15 | padding 10px 16 | 17 | #editor 18 | width 90% 19 | min-height 280px 20 | margin-left 5% 21 | 22 | -------------------------------------------------------------------------------- /src/stylus/_variables.styl: -------------------------------------------------------------------------------- 1 | /* Vendors Prefixes */ 2 | vendor-prefixes = o ms moz webkit khtml official 3 | 4 | /* General Colors */ 5 | graysColors = (#010 #111 #222 #333 #444 #555 #666 #777 #888 #999 #AAA #BBB #CCC #DDD #EEE) 6 | white = #FFF 7 | darkWhite = #FEFEFE 8 | almostWhite = #F5F5F5 9 | black = #000 10 | darkBlue = #069 11 | 12 | /* Dashboard colors */ 13 | blueLink = #06F 14 | 15 | /* Site */ 16 | blueFacebook = #3B5999 17 | blueBorderFacebook = #193777 18 | blueTwitter = #60A9DE 19 | blueBorderTwitter = #3E87BC 20 | blueLinkedin = #678BD5 21 | blueBorderLinkedin = #1D4D78 22 | redGooglePlus = #DE4B39 23 | redBorderGooglePlus = #B43525 24 | redYoutube = #ED4632 25 | grayGithub = #2E3436 26 | grayBorderGithub = #0C1214 27 | 28 | yellowStar = #f1c40f 29 | orangeBorderStar = #e67e22 30 | 31 | /* Logo & menu options */ 32 | grayWhite = #F5F5F5 33 | blueCodejobs = #0073BC 34 | darkBlueCodejobs = #004C88 35 | grayCodejobs = #6B6B6B 36 | 37 | /* Syntax */ 38 | darkGraySyntaxBg = #262720 39 | blueSyntax = #00D6FF 40 | pinkSyntax = #F92772 41 | seaBlueSyntax = #0099F5 42 | orangeSyntax = #FFA337 43 | graySyntax = #7B959E 44 | redSyntax = #8D0014 45 | purpleSyntax = #AE81FF 46 | greenSyntax = #8CBC2B 47 | lightYellowSyntax = #FFFFE3 48 | yellowSyntax = #FFF8AB 49 | 50 | /* Technologies */ 51 | blueCSS3 = #1CA8E2 52 | blueDocker = #007DA6 53 | blueVagrant = #1475AC 54 | brownMongo = #553924 55 | grayExpress = #666666 56 | greenNode = #569D00 57 | orangeHTML5 = #D14119 58 | purpleBootstrap = #55284A 59 | purplePHP = #455792 60 | redAngular = #CB0323 61 | yellowGrunt = #DD8D00 62 | yellowJS = #DFB300 63 | yellowSublime = #EA8B0F 64 | azureEntrepreneur = #00A0AA 65 | greenGames = #2F5500 66 | blueTesting = #24B9F9 67 | blueWordpress = #1A759D 68 | 69 | /* Buttons */ 70 | defaultBg = #E6E6E6 71 | defaultBorder = #ADADAD 72 | primaryBg = #428BCA 73 | primaryBorder = #357EBD 74 | primaryBgHover = #3071A9 75 | primaryBorderHover = #285E8E 76 | sucessBg = #5CB85C 77 | sucessBorder = #27763A 78 | sucessBgHover = #449D44 79 | sucessBorderHover = #398439 80 | infoBg = #3498DB 81 | infoBorder = #17374D 82 | infoBgHover = #1C7DC3 83 | infoBorderHover = #3B68A9 84 | infoDisabledBg = #5BC0DE 85 | infoDisabledBorder = #46B8DA 86 | warningBg = #F0AD4E 87 | warningBorder = #EEA236 88 | warningBgHover = #EC971F 89 | warningBorderHover = #D58512 90 | warningDisabledBg = #F0AD4E 91 | warningDisabledBorder = #EEA236 92 | dangerBg = #DA4D49 93 | dangerBorder = #762727 94 | dangerBgHover = #C9302C 95 | dangerBorderHover = #AC2925 96 | dangerDisabledBg = #D9534F 97 | dangerDisabledBorder = #D43F3A 98 | redPink = #e06161 99 | 100 | /* Font weights */ 101 | fontWeightLighter = 100 102 | fontWeightLight = 300 103 | fontWeightNormal = 400 104 | fontWeightRegular = 500 105 | fontWeightBold = 700 106 | fontWeightHeavy = 900 107 | 108 | /* Mediaqueries */ 109 | iPadW = 768px 110 | iPadH = 1024px 111 | iP4W = 320px 112 | iP4H = 480px 113 | iP5W = 320px 114 | iP5H = 568px 115 | iP6W = 375px 116 | iP6H = 667px 117 | iP6PW = 414px 118 | iP6PH = 736px 119 | gW = 360px 120 | gH = 640px 121 | all = 1023px 122 | 123 | s = 'only screen and' 124 | l = 'orientation: landscape' 125 | maxW = 'max-device-width:' 126 | minW = 'min-device-width:' 127 | 128 | /* Devices */ 129 | anyDevice = s + ' (' + maxW + all + ')' 130 | anyDeviceLandscape = s + ' (' + maxW + all + ') and (' + l + ')' 131 | galaxy = s + ' (' + minW + gW + ') and (' + maxW + gH + ')' 132 | galaxyLandscape = s + ' (' + minW + gW + ') and (' + maxW + gH + ') and (' + l + ')' 133 | iPad = s + ' (' + minW + iPadW + ') and (' + maxW + iPadH + ')' 134 | iPadLandscape = s + ' (' + minW + iPadW + ') and (' + maxW + iPadH + ') and (' + l + ')' 135 | iPhone6P = s + ' (' + minW + iP4W + ') and (' + maxW + iP4H + ')' 136 | iPhone6PLandscape = s + ' (' + minW + iP6PW + ') and (' + maxW + iP6PH + ') and (' + l + ')' 137 | iPhone6 = s + ' (' + minW + iP6W + ') and (' + maxW + iP6H + ')' 138 | iPhone6Landscape = s + ' (' + minW + iP6W + ') and (' + maxW + iP6H + ') and (' + l + ')' 139 | iPhone5 = s + ' (' + minW + iP5W + ') and (' + maxW + iP5H + ')' 140 | iPhone5Landscape = s + ' (' + minW + iP5W + ') and (' + maxW + iP5H + ') and (' + l + ')' 141 | iPhone4 = s + ' (' + minW + iP4W + ') and (' + maxW + iP4H + ')' 142 | iPhone4Landscape = s + ' (' + minW + iP4W + ') and (' + maxW + iP4H + ') and (' + l + ')' 143 | -------------------------------------------------------------------------------- /src/stylus/blog/_post.styl: -------------------------------------------------------------------------------- 1 | .post-content 2 | centerWithMargin(0, false, false, 40px) 3 | width 90% 4 | 5 | h1 6 | font-size 2.5rem 7 | margin 20px 8 | text-align center 9 | 10 | h2 11 | font-size 2rem 12 | margin 20px 13 | margin-left 5% 14 | 15 | .meta 16 | color grayColor(6) 17 | font-style italic 18 | margin-bottom 20px 19 | text-align center 20 | 21 | .main-image 22 | centerWithMargin(0, false, false, 20px) 23 | display block 24 | width 90% 25 | 26 | p 27 | centerWithMargin() 28 | font-size 1.1rem 29 | line-height 2.2rem 30 | padding-bottom 10px 31 | width 90% 32 | a 33 | color grayColor(6) 34 | font-weight bold 35 | text-decoration none 36 | &:hover 37 | padding-bottom 2px 38 | border-bottom 1px dotted grayColor(12) 39 | img 40 | width 100% 41 | 42 | ul 43 | margin-left 5% 44 | li 45 | list-style none 46 | ul 47 | margin-left -20px 48 | li 49 | &:before 50 | color grayColor(6) 51 | 52 | &:before 53 | font-family 'FontAwesome' 54 | content '\f0da' 55 | margin 0 5px 0 -15px 56 | color grayColor(3) 57 | -------------------------------------------------------------------------------- /src/stylus/blog/_posts.styl: -------------------------------------------------------------------------------- 1 | .page-content-wrapper 2 | centerWithMargin(50px, false, false, 0) 3 | width 88% 4 | 5 | .columns 6 | prefix(column-count, 3) 7 | prefix(column-gap, 10px) 8 | prefix(column-fill, auto) 9 | 10 | .post 11 | border(grayColor(13)) 12 | background -webkit-linear-gradient(45deg, white, almostWhite) 13 | background white 14 | column-break-inside avoid 15 | display inline-block 16 | margin 0 2px 15px 17 | padding 10px 18 | padding-bottom 5px 19 | 20 | img 21 | margin-bottom 5px 22 | padding-bottom 15px 23 | width 100% 24 | iframe 25 | height 250px 26 | margin-bottom 5px 27 | padding-bottom 15px 28 | width 100% 29 | h2 30 | font-size 1.7rem 31 | margin-bottom 10px 32 | a 33 | color grayColor(3) 34 | text-decoration none 35 | &:hover 36 | border-bottom 1px dotted grayColor(12) 37 | p 38 | color grayColor(3) 39 | font 0.7rem / 1.1rem Arial, sans-serif 40 | margin 0 41 | 42 | .details 43 | color grayColor(12) 44 | margin-top 20px 45 | font-weight fontWeightNormal 46 | 47 | .date 48 | float left 49 | 50 | .social 51 | margin-left 10px 52 | 53 | a 54 | color grayColor(12) 55 | text-decoration none 56 | 57 | &.facebook 58 | &:hover 59 | color blueFacebook 60 | &.twitter 61 | &:hover 62 | color blueTwitter 63 | &.linkedin 64 | &:hover 65 | color blueLinkedin 66 | &.google 67 | &:hover 68 | color redGooglePlus 69 | 70 | .category 71 | float right 72 | 73 | a 74 | color grayColor(12) 75 | text-decoration none 76 | &:hover 77 | color grayColor(6) 78 | &.angular 79 | &:hover 80 | color redAngular 81 | -------------------------------------------------------------------------------- /src/stylus/mediaqueries/desktop.styl: -------------------------------------------------------------------------------- 1 | @import '../mixins/_responsive' 2 | 3 | @media only screen and (max-width: 940px) 4 | changeColumns(2) 5 | 6 | @media only screen and (max-width: 768px) 7 | changeColumns(2) 8 | 9 | header 10 | nav 11 | noDisplay() 12 | 13 | .nav-mobile 14 | centerWithMargin(0, 30px) 15 | display block 16 | text-align center 17 | 18 | i 19 | color grayColor(3) 20 | font-size 24px 21 | 22 | @media only screen and (max-width: 568px) 23 | changeColumns(1) 24 | 25 | -------------------------------------------------------------------------------- /src/stylus/mixins/_default.styl: -------------------------------------------------------------------------------- 1 | /* Utils */ 2 | prefix(property, value) 3 | -webkit-{property} value 4 | -moz-{property} value 5 | -o-{property} value 6 | -ms-{property} value 7 | {property} value 8 | 9 | bg(color = transparent) 10 | background color 11 | 12 | bgImage(url, repeat = no-repeat) 13 | background-image url(url) 14 | background-repeat repeat 15 | 16 | blackRgba(opacity = 1) 17 | background-color rgba(0, 0, 0, opacity) 18 | 19 | cover() 20 | background-size cover 21 | 22 | resetElement() 23 | border none 24 | margin 0 25 | padding 0 26 | 27 | clearfix() 28 | *zoom 1 29 | &:after 30 | content '' 31 | display block 32 | clear both 33 | height 0 34 | 35 | position(position, args...) 36 | position position 37 | 38 | if length(args) == 4 39 | if args[0] != false 40 | top args[0] 41 | 42 | if args[1] != false 43 | right args[1] 44 | 45 | if args[2] != false 46 | bottom args[2] 47 | 48 | if args[3] != false 49 | left args[3] 50 | else if length(args) == 3 51 | if args[0] != false 52 | top args[0] 53 | 54 | if args[1] != false 55 | right args[1] 56 | 57 | if args[2] != false 58 | bottom args[2] 59 | else if length(args) == 2 60 | if args[0] != false 61 | top args[0] 62 | 63 | if args[1] != false 64 | right args[1] 65 | else 66 | if args[0] != false 67 | top args[0] 68 | 69 | font(font, size = false) 70 | font-family font, sans-serif 71 | 72 | if size != false 73 | font-size size 74 | 75 | capitalize() 76 | text-transform capitalize 77 | 78 | uppercase() 79 | text-transform uppercase 80 | 81 | lowercase() 82 | text-transform lowercase 83 | 84 | italic() 85 | font-style italic 86 | 87 | centerImage(width = 100%) 88 | display block 89 | margin 0 auto 90 | width width 91 | 92 | centerWithMargin(initMargin = 0, args...) 93 | margin initMargin auto 94 | 95 | if length(args) == 4 96 | if args[0] != false 97 | margin-top args[0] 98 | 99 | if args[1] != false 100 | margin-right args[1] 101 | 102 | if args[2] != false 103 | margin-bottom args[2] 104 | 105 | if args[3] != false 106 | margin-left args[3] 107 | else if length(args) == 3 108 | if args[0] != false 109 | margin-top args[0] 110 | 111 | if args[1] != false 112 | margin-right args[1] 113 | 114 | if args[2] != false 115 | margin-bottom args[2] 116 | else if length(args) == 2 117 | if args[0] != false 118 | margin-top args[0] 119 | 120 | if args[1] != false 121 | margin-right args[1] 122 | else 123 | if args[0] != false 124 | margin-top args[0] 125 | 126 | hide() 127 | display none 128 | 129 | noContent() 130 | content '' 131 | background transparent 132 | 133 | noBorder() 134 | border none 135 | 136 | noDisplay() 137 | display none 138 | 139 | noSelect() 140 | outline none 141 | touch-callout none 142 | user-select none 143 | 144 | pointer() 145 | cursor pointer 146 | 147 | /* Font Awesome */ 148 | fa(code, size = 1em, color = blueDevWay, bgColor = transparent) 149 | background-color bgColor 150 | color color 151 | content code 152 | font-family FontAwesome 153 | font-size size 154 | 155 | /* Borders */ 156 | border(color = red, size = 1px, type = solid) 157 | border size type color 158 | 159 | borderRadius(radius = 5px, args...) 160 | if length(args) == 3 161 | border-top-right-radius radius 162 | border-bottom-right-radius args[0] 163 | border-bottom-left-radius args[1] 164 | border-top-left-radius args[2] 165 | else if length(args) == 2 166 | border-top-right-radius radius 167 | border-bottom-right-radius args[0] 168 | border-bottom-left-radius args[1] 169 | else if length(args) == 1 170 | border-top-right-radius radius 171 | border-bottom-right-radius args[0] 172 | else 173 | border-radius radius 174 | 175 | /* BoxShadow */ 176 | boxShadow(h = 2px, v = 2px, blur = 2px, opacity = 0.3, inset = false) 177 | if (!inset) 178 | box-shadow h v blur rgba(0, 0, 0, opacity) 179 | else 180 | box-shadow inset h v blur rgba(0, 0, 0, opacity) 181 | 182 | /* Filters */ 183 | grayscale(percentage) 184 | filter grayscale(percentage) 185 | -webkit-filter grayscale(percentage) 186 | -o-filter grayscale(percentage) 187 | -moz-filter grayscale(percentage) 188 | -ms-filter grayscale(percentage) 189 | 190 | grayColor(position) 191 | return graysColors[position] 192 | 193 | /* Lists */ 194 | resetUl() 195 | margin 0 196 | padding 0 197 | list-style none 198 | 199 | /* Images */ 200 | retina(image, extension = 'png') 201 | background-image url(image'.'extension) 202 | 203 | @media (min-device-pixel-ratio: 2), 204 | (min-device-pixel-ratio: 2), 205 | (min-resolution: 192dpi), 206 | (min-resolution: 2dppx) 207 | background-image url(image'_@2x.'extension) 208 | background-size cover 209 | -------------------------------------------------------------------------------- /src/stylus/mixins/_responsive.styl: -------------------------------------------------------------------------------- 1 | @import '_default' 2 | 3 | changeColumns(count) 4 | .page-content-wrapper 5 | .columns 6 | prefix(column-count, count) 7 | -------------------------------------------------------------------------------- /src/stylus/mixins/_studiovictory.styl: -------------------------------------------------------------------------------- 1 | /* Logo */ 2 | $displayLogo() 3 | border-right 1px solid $lightGray 4 | font-size 26px 5 | line-height 49px 6 | margin 0 auto 7 | margin-left 10px 8 | padding-right 10px 9 | text-transform uppercase 10 | width 175px 11 | 12 | a 13 | $noSelect() 14 | 15 | .code 16 | color $grayCodejobs 17 | display inline-block 18 | vertical-align middle 19 | 20 | .jobs 21 | color $blueCodejobs 22 | display inline-block 23 | font-weight $fontWeightBold 24 | vertical-align middle 25 | 26 | .isotipo 27 | display inline-block 28 | height 25px 29 | vertical-align middle 30 | width 35px 31 | 32 | &:hover 33 | .code 34 | color $blueCodejobs 35 | 36 | .jobs 37 | color $grayCodejobs -------------------------------------------------------------------------------- /src/stylus/site/_footer.styl: -------------------------------------------------------------------------------- 1 | .scroll-to-top 2 | pointer() 3 | noDisplay() 4 | position(fixed, false, 0px) 5 | background url(../images/layout/scroll-to-top.png) no-repeat 6 | bottom 20px 7 | height 55px 8 | padding-right 1em 9 | width 55px 10 | z-index 9999 11 | 12 | .footer 13 | p 14 | color almostLightGray 15 | font-family Arial, serif 16 | font-size 0.95em 17 | margin-bottom 10px 18 | text-align center 19 | -------------------------------------------------------------------------------- /src/stylus/site/_header.styl: -------------------------------------------------------------------------------- 1 | header 2 | font('Montserrat') 3 | centerWithMargin() 4 | width 100% 5 | 6 | .logo 7 | centerWithMargin() 8 | width 260px 9 | 10 | img 11 | centerWithMargin() 12 | margin 0 13 | margin-top 10px 14 | padding 0 15 | width 40px 16 | 17 | .logo-text 18 | position(absolute, false) 19 | color grayColor(3) 20 | font-size 2em 21 | height 40px 22 | line-height 55px 23 | margin-left 5px 24 | nav 25 | margin 0 auto 26 | width 70% 27 | 28 | ul 29 | margin-top 20px 30 | text-align center 31 | li 32 | display inline-block 33 | a 34 | color grayColor(3) 35 | margin-left 15px 36 | text-decoration none 37 | &:hover 38 | border-bottom 1px dotted grayColor(10) 39 | padding-bottom 2px 40 | 41 | .nav-mobile-menu 42 | noDisplay() 43 | centerWithMargin() 44 | margin-top 20px 45 | text-align center 46 | 47 | ul 48 | resetUl() 49 | margin-left -10px 50 | li 51 | a 52 | color grayColor(3) 53 | line-height 30px 54 | margin-left 15px 55 | text-decoration none 56 | &:hover 57 | border-bottom 1px dotted grayColor(10) 58 | padding-bottom 2px 59 | -------------------------------------------------------------------------------- /src/stylus/site/_pagination.styl: -------------------------------------------------------------------------------- 1 | .pagination 2 | font('sans-serif', 12px) 3 | color grayColor(6) 4 | line-height 24px 5 | padding 20px 6 | margin-bottom 20px 7 | text-align center 8 | 9 | .page 10 | borderRadius(3px) 11 | background grayColor(14) 12 | border solid 1px grayColor(13) 13 | box-shadow inset 0px 1px 0px rgba(255, 255, 255, .8), 0px 1px 3px rgba(0, 0, 0, .1) 14 | color grayColor(7) 15 | display inline-block 16 | font-size .875em 17 | font-weight fontWeightBold 18 | margin-right 4px 19 | padding 0px 9px 20 | text-decoration none 21 | text-shadow 0px 1px 0px rgba(255, 255, 255, 1) 22 | 23 | &.active 24 | background grayColor(6) 25 | border none 26 | box-shadow inset 0px 0px 8px rgba(0, 0, 0, .5), 0px 1px 0px rgba(255, 255, 255, .8) 27 | color almostWhite 28 | text-shadow 0px 0px 3px rgba(0, 0, 0, .5) 29 | 30 | &.gradient 31 | background -moz-linear-gradient(0% 0% 270deg,#f8f8f8, #e9e9e9) 32 | background -webkit-gradient(linear, 0% 0%, 0% 100%, from(#f8f8f8), to(#e9e9e9)) 33 | 34 | &:hover, 35 | &.gradient 36 | &:hover 37 | background almostWhite 38 | background -webkit-gradient(linear, 0% 0%, 0% 100%, from(grayColor(14)), to(almostWhite)) 39 | background -moz-linear-gradient(0% 0% 270deg, grayColor(14), almostWhite) 40 | -------------------------------------------------------------------------------- /src/stylus/style.styl: -------------------------------------------------------------------------------- 1 | @import url('http://fonts.googleapis.com/css?family=PT+Serif') 2 | @import url('../bower_components/normalize-css/normalize.css') 3 | @import url('../bower_components/font-awesome/css/font-awesome.min.css') 4 | 5 | @import '_variables' 6 | @import 'mixins/_default' 7 | @import 'mixins/_studiovictory' 8 | 9 | @import '_global' 10 | 11 | @import 'site/_header' 12 | 13 | @import 'blog/_posts' 14 | 15 | @import 'site/_pagination' 16 | 17 | @import 'site/_footer' 18 | 19 | @import 'mediaqueries/desktop' 20 | -------------------------------------------------------------------------------- /src/views/error.hbs: -------------------------------------------------------------------------------- 1 |

Error: {{ message }}

2 | -------------------------------------------------------------------------------- /src/views/home/welcome.hbs: -------------------------------------------------------------------------------- 1 |

{{ __.welcome }} {{ siteName }}

2 | 3 |

4 | {{ __.visited }} {{ visits }} {{#is visits 1 }} {{ __.time }} {{else}} {{ __.times }} {{/is}} 5 |

6 | 7 | {{#debug visits }}{{/debug}} 8 | -------------------------------------------------------------------------------- /src/views/layouts/main.hbs: -------------------------------------------------------------------------------- 1 | {{#minify}} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {{ __.site.title }} 13 | 14 | {{#if isMobile}} 15 | 16 | {{/if}} 17 | 18 | {{#css}} 19 | 20 | {{/css}} 21 | 22 | 23 | 24 | 25 | 26 |
27 | 30 | 31 | 46 | 47 | 58 | 59 | 62 |
63 | 64 | {{{ body }}} 65 | 66 | 70 | 71 | 72 | {{/minify}} 73 | -------------------------------------------------------------------------------- /test/fixtures/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | foo: &default 3 | a: foo 4 | b: bar 5 | c: baz 6 | 7 | bar: 8 | <<: *default 9 | c: zab 10 | -------------------------------------------------------------------------------- /test/fixtures/public/js/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeJobs/StudioVictory/464333ec84851bb99832119e3d476385c0dcbb57/test/fixtures/public/js/.gitkeep -------------------------------------------------------------------------------- /test/lib/configTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var testHelper = require('./../testHelper'); 4 | var config = testHelper.rewireFromProjectRoot('lib/config'); 5 | 6 | // use fixture config 7 | config.__set__('environment', function() { 8 | return { name: 'foo' }; 9 | }); 10 | 11 | config.__set__('config', testHelper.loadFixtureYML('config.yml')); 12 | 13 | describe('Config', function() { 14 | it('should return an object', function() { 15 | assert.typeOf(config(), 'Object', 'Config returns an object'); 16 | }); 17 | 18 | it('should return config by environment', function() { 19 | config.__set__('environment', function() { 20 | return { name: 'foo' }; 21 | }); 22 | 23 | assert.deepEqual(config(), { a: 'foo', b: 'bar', c: 'baz' }, 'Config returns the foo config'); 24 | 25 | config.__set__('environment', function() { 26 | return { name: 'bar' }; 27 | }); 28 | 29 | assert.deepEqual(config(), { a: 'foo', b: 'bar', c: 'zab' }, 'Config returns the bar config'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/public/js/fakeTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Give it some context', function() { 4 | describe('maybe a bit more context here', function() { 5 | it('should run here few assertions', function() { 6 | console.log('Testing fake assertions....'); 7 | }); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /test/testHelper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var rewire = require('rewire'); 5 | var yaml = require('js-yaml'); 6 | 7 | global.sinon = require('sinon'); 8 | global.assert = require('chai').assert; 9 | 10 | module.exports = { 11 | rewireFromProjectRoot: function(relativePath) { 12 | return rewire(__dirname + '/../src/' + relativePath); 13 | }, 14 | loadFixtureYML: function(filename) { 15 | return yaml.safeLoad(fs.readFileSync(__dirname + '/fixtures/' + filename, 'utf8')); 16 | }, 17 | loadFixtureJSONRaw: function(filename) { 18 | return fs.readFileSync(__dirname + '/fixtures/' + filename, 'utf8'); 19 | }, 20 | loadFixtureJSON: function(filename) { 21 | return JSON.parse(this.loadFixtureJSONRaw(filename)); 22 | } 23 | }; 24 | --------------------------------------------------------------------------------