├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── README.md ├── app ├── images │ ├── background01.jpg │ └── logo.png ├── index.html ├── robots.txt ├── scripts │ ├── contentloaded.js │ ├── demo.js │ └── main.js └── styles │ ├── _mixins.scss │ └── main.scss ├── gulpfile.js ├── load-seed.sublime-project └── package.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # change these settings to your own preference 11 | indent_style = space 12 | indent_size = 4 13 | 14 | # we recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | 23 | [{package,bower}.json] 24 | indent_style = space 25 | indent_size = 2 26 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | .sass-cache 5 | 6 | *.TODO 7 | *.sublime-workspace 8 | 9 | logevents.js 10 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 4, 10 | "latedef": true, 11 | "newcap": false, 12 | "noarg": true, 13 | "quotmark": false, 14 | "undef": true, 15 | "unused": false, 16 | "strict": true, 17 | "jquery": false, 18 | "predef": [ 19 | "GameDetails", 20 | "DownloadingFile", 21 | "SetStatusChanged", 22 | "SetFilesTotal", 23 | "SetFilesNeeded" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Grunt is used to publish the loading screen demo to github 4 | module.exports = function (grunt) { 5 | grunt.loadNpmTasks('grunt-build-control'); 6 | 7 | var repo = 'git@github.com:gmodcoders/load-seed.git'; 8 | 9 | grunt.initConfig({ 10 | buildcontrol: { 11 | options: { 12 | dir: 'dist', 13 | commit: true, 14 | push: true, 15 | message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%' 16 | }, 17 | pages: { 18 | options: { 19 | remote: 'repo', 20 | branch: 'gh-pages' 21 | } 22 | } 23 | } 24 | }); 25 | 26 | grunt.registerTask('deploy', ['buildcontrol:pages']); 27 | }; 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Loading Screen Seed 2 | 3 | This project is an application skeleton for building a Garry's Mod loading screen. You can use it to quickly get started building your design. 4 | 5 | The seed comes preconfigured with modern frontend tools to make developing a loading screen faster and easier. 6 | 7 | ![Preview Image](http://wiki.garrysmod.com/images/e/e5/load-seed-preview.jpg) 8 | 9 | ## Getting Started 10 | 11 | To get started you can simply clone the load-seed repository and install the dependencies: 12 | 13 | ### Prerequisites 14 | 15 | You need git to clone the load-seed repository. You can get git from http://git-scm.com/ 16 | 17 | The project also uses a number of node.js tools to initialize and build load-seed. You must have node.js and its package manager (npm) installed. You can get them from http://nodejs.org/ 18 | 19 | ### Clone load-seed 20 | 21 | Clone the load-seed repository using git: 22 | 23 | ``` 24 | git clone https://github.com/glua/load-seed 25 | cd load-seed 26 | ``` 27 | 28 | If you just want to start a new project without the load-seed commit history then you can do: 29 | 30 | ```bash 31 | git clone --depth=1 https://github.com/glua/load-seed.git 32 | ``` 33 | 34 | The `depth=1` tells git to only pull down one commit worth of historical data. 35 | 36 | ### Install Dependencies 37 | 38 | This project requires a few tools before you can begin developing your loading screen with it. After installing node.js, you can simply run the following commands to install all dependencies: 39 | 40 | ``` 41 | npm install 42 | npm install -g gulp 43 | ``` 44 | 45 | After running these commands, you should find the a `node_modules` folder within your project. 46 | 47 | ### Run the Application 48 | 49 | The project is preconfigured with a simple development web server. To start this server, enter the following: 50 | 51 | ``` 52 | gulp serve 53 | ``` 54 | 55 | Now browse to the loading screen at `http://localhost:9000/`. 56 | 57 | ## Hosting 58 | 59 | When you're ready to host your loading screen, enter the following: 60 | 61 | ``` 62 | gulp build 63 | ``` 64 | 65 | The final project files will be built into a `dist` folder within your project. You'll need to upload these files to a web server. 66 | 67 | ## Directory Layout 68 | 69 | ``` 70 | app/ --> all of the source files for the loading screen 71 | fonts/ --> any fonts to include 72 | images/ --> logo image, backgrounds, etc. 73 | scripts/ --> JavaScript files 74 | main.js --> loading screen code 75 | demo.js --> test data used for emulating connecting to a server in the browser 76 | styles/ --> loading screen styles 77 | main.scss --> default stylesheet 78 | index.html --> loading screen layout file 79 | robots.txt --> disallows web robots from indexing loading screen files 80 | ``` 81 | -------------------------------------------------------------------------------- /app/images/background01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glua/load-seed/405e7ac33ed0f56632bfc969f29091d0bf233718/app/images/background01.jpg -------------------------------------------------------------------------------- /app/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glua/load-seed/405e7ac33ed0f56632bfc969f29091d0bf233718/app/images/logo.png -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | Logo 15 |
16 | 17 |
18 |
19 |
Server Info
20 |
21 | 22 |

23 |

24 |

25 |
26 |
27 | 28 |
29 | 30 |
31 |
Rules
32 |
    33 |
  1. No chat/mic spam
  2. 34 |
  3. No prop killing
  4. 35 |
  5. No RDM'ing
  6. 36 |
  7. No ghosting
  8. 37 |
38 |
39 |
40 | 41 |
42 |
43 | Connecting... 44 | 0% 45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /app/scripts/contentloaded.js: -------------------------------------------------------------------------------- 1 | /* jshint ignore:start */ 2 | 3 | /*! 4 | * contentloaded.js 5 | * 6 | * Author: Diego Perini (diego.perini at gmail.com) 7 | * Summary: cross-browser wrapper for DOMContentLoaded 8 | * Updated: 20101020 9 | * License: MIT 10 | * Version: 1.2 11 | * 12 | * URL: 13 | * http://javascript.nwbox.com/ContentLoaded/ 14 | * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE 15 | * 16 | */ 17 | 18 | // @win window reference 19 | // @fn function reference 20 | function contentLoaded(win, fn) { 21 | 22 | var done = false, top = true, 23 | 24 | doc = win.document, 25 | root = doc.documentElement, 26 | modern = doc.addEventListener, 27 | 28 | add = modern ? 'addEventListener' : 'attachEvent', 29 | rem = modern ? 'removeEventListener' : 'detachEvent', 30 | pre = modern ? '' : 'on', 31 | 32 | init = function(e) { 33 | if (e.type == 'readystatechange' && doc.readyState != 'complete') return; 34 | (e.type == 'load' ? win : doc)[rem](pre + e.type, init, false); 35 | if (!done && (done = true)) fn.call(win, e.type || e); 36 | }, 37 | 38 | poll = function() { 39 | try { root.doScroll('left'); } catch(e) { setTimeout(poll, 50); return; } 40 | init('poll'); 41 | }; 42 | 43 | if (doc.readyState == 'complete') fn.call(win, 'lazy'); 44 | else { 45 | if (!modern && root.doScroll) { 46 | try { top = !win.frameElement; } catch(e) { } 47 | if (top) poll(); 48 | } 49 | doc[add](pre + 'DOMContentLoaded', init, false); 50 | doc[add](pre + 'readystatechange', init, false); 51 | win[add](pre + 'load', init, false); 52 | } 53 | 54 | } 55 | 56 | /* jshint ignore:end */ 57 | -------------------------------------------------------------------------------- /app/scripts/demo.js: -------------------------------------------------------------------------------- 1 | /* jshint devel:true */ 2 | /* global contentLoaded */ 3 | 4 | /** 5 | * Start calling loading screen functions when all assets have finished loading. 6 | */ 7 | contentLoaded(window, function () { 8 | 'use strict'; 9 | 10 | /** 11 | * Loading screen event fixture. This contains an array of actual functions 12 | * called while connecting to a server with the associated time. 13 | * 14 | * @type {Array} 15 | */ 16 | var fixture = [ 17 | {"func":"GameDetails","args":["[cG] 24/7 TTT Minecraft/Dolls | SpecDM | LowGrav | PointShop","st.compactgamers.com/loading/garrysmod/ttt/bg-pointshop.php?steamid=76561197991989781","ttt_minecraftcity_v4_dark",40,"76561197991989781","terrortown"],"time":854}, 18 | {"func":"DownloadingFile","args":["maps/ttt_minecraftcity_v4_dark.bsp"],"time":1972}, 19 | {"func":"SetFilesNeeded","args":[21],"time":2435}, 20 | {"func":"SetFilesTotal","args":[73],"time":2436}, 21 | {"func":"DownloadingFile","args":["maps/graphs/ttt_minecraftcity_v4_dark.ain"],"time":9380}, 22 | {"func":"DownloadingFile","args":["materials/vgui/cg/cg_ps_logo.png"],"time":9621}, 23 | {"func":"DownloadingFile","args":["materials/nook/heart_hud/horizontal_heart/flash.png"],"time":9853}, 24 | {"func":"DownloadingFile","args":["materials/nook/heart_hud/horizontal_heart/full.png"],"time":9980}, 25 | {"func":"DownloadingFile","args":["materials/nook/heart_hud/horizontal_heart/hurt_ten.png"],"time":10107}, 26 | {"func":"DownloadingFile","args":["materials/nook/heart_hud/horizontal_heart/hurt_nine.png"],"time":10235}, 27 | {"func":"DownloadingFile","args":["materials/nook/heart_hud/horizontal_heart/hurt_eight.png"],"time":10356}, 28 | {"func":"DownloadingFile","args":["materials/nook/heart_hud/horizontal_heart/hurt_seven.png"],"time":10484}, 29 | {"func":"DownloadingFile","args":["materials/nook/heart_hud/horizontal_heart/hurt_six.png"],"time":10611}, 30 | {"func":"DownloadingFile","args":["materials/nook/heart_hud/horizontal_heart/hurt_five.png"],"time":10737}, 31 | {"func":"DownloadingFile","args":["materials/nook/heart_hud/horizontal_heart/hurt_four.png"],"time":10864}, 32 | {"func":"DownloadingFile","args":["materials/nook/heart_hud/horizontal_heart/hurt_thrice.png"],"time":10987}, 33 | {"func":"DownloadingFile","args":["materials/nook/heart_hud/horizontal_heart/hurt_twice.png"],"time":11115}, 34 | {"func":"DownloadingFile","args":["materials/nook/heart_hud/horizontal_heart/hurt_once.png"],"time":11240}, 35 | {"func":"DownloadingFile","args":["materials/vgui/ttt/icon_melon_laun.png"],"time":11366}, 36 | {"func":"DownloadingFile","args":["materials/vgui/ttt/icon_poison_dart.png"],"time":11496}, 37 | {"func":"DownloadingFile","args":["materials/vgui/ttt/icon_cloak.png"],"time":11617}, 38 | {"func":"DownloadingFile","args":["materials/vgui/ttt/icon_crabpod.png"],"time":11747}, 39 | {"func":"DownloadingFile","args":["sound/siege/leeroy.mp3"],"time":11871}, 40 | {"func":"DownloadingFile","args":["materials/tripmine/icon_tripwire.png"],"time":12000}, 41 | {"func":"SetStatusChanged","args":["Sending client info..."],"time":17669} 42 | ]; 43 | 44 | // Emit all loading screen events over time 45 | [].forEach.call(fixture, function (loadEvent) { 46 | var func = window[loadEvent.func]; 47 | if (func !== undefined) { 48 | setTimeout(function () { 49 | func.apply(window, loadEvent.args); 50 | }, loadEvent.time); 51 | } 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /app/scripts/main.js: -------------------------------------------------------------------------------- 1 | /* jshint devel:true */ 2 | (function () { 3 | 'use strict'; 4 | 5 | var LOAD = {}; 6 | 7 | /** 8 | * Initialize the loading screen. 9 | */ 10 | LOAD.init = function () { 11 | this.progress = 0.0; 12 | this.filesNeeded = 1; 13 | this.filesTotal = 1; 14 | 15 | this.$ = {}; 16 | 17 | // loading bar 18 | this.$.progressBar = document.getElementById('progressbar'); 19 | this.$.status = document.getElementById('status'); 20 | this.$.percentage = document.getElementById('percentage'); 21 | 22 | // server info 23 | this.$.mapPreview = document.getElementById('mappreview'); 24 | this.$.serverName = document.getElementById('servername'); 25 | this.$.mapName = document.getElementById('mapname'); 26 | this.$.playerSlots = document.getElementById('playerslots'); 27 | 28 | this.updateProgress(); 29 | }; 30 | 31 | /** 32 | * Set the total number of files to be downloaded. This will be called on 33 | * the `SetFilesTotal` loading screen event. 34 | */ 35 | LOAD.setFilesTotal = function (numFiles) { 36 | this.filesTotal = Math.max(0, numFiles); 37 | }; 38 | 39 | /** 40 | * Sets the number of files needed to be downloaded. This will be called on 41 | * the `SetFilesNeeded` loading screen event. 42 | */ 43 | LOAD.setFilesNeeded = function (numFiles) { 44 | this.filesNeeded = Math.max(0, numFiles); 45 | }; 46 | 47 | /** 48 | * Sets the server info data on the loading screen. This will be called on 49 | * the `GameDetails` loading screen event. 50 | */ 51 | LOAD.setServerInfo = function (serverName, mapName, maxPlayers) { 52 | // set map preview image 53 | // this.$.mapPreview.src = 'asset://mapimage/' + mapName; 54 | 55 | // gametracker.com map previews can also be used 56 | this.$.mapPreview.src = 'http://image.www.gametracker.com/images/maps/160x120/garrysmod/' + mapName + '.jpg'; 57 | 58 | this.$.mapName.innerText = mapName; 59 | this.$.serverName.innerText = serverName; 60 | this.$.playerSlots.innerText = maxPlayers + ' player slots'; 61 | }; 62 | 63 | /** 64 | * Updates the progress bar on the loading screen. 65 | */ 66 | LOAD.updateProgress = function () { 67 | var filesRemaining = Math.max(0, this.filesTotal - this.filesNeeded), 68 | progress = (this.filesTotal > 0) ? 69 | (filesRemaining / this.filesTotal) : 1; 70 | 71 | progress = Math.round(progress * 100); 72 | 73 | this.$.percentage.innerText = progress + '%'; 74 | this.$.progressBar.style.right = (100 - progress) + '%'; 75 | }; 76 | 77 | /** 78 | * Called on the `DownloadingFile` loading screen event. 79 | * Updates the loading progress and shows which file is currently being 80 | * downloaded. 81 | */ 82 | LOAD.onFileDownloading = function (filePath) { 83 | this.filesNeeded = Math.max(0, this.filesNeeded - 1); 84 | this.updateProgress(); 85 | 86 | var status = 'Downloading ' + filePath + '...'; 87 | this.onStatusChanged(status); 88 | }; 89 | 90 | /** 91 | * Called on the `SetStatusChanged` loading screen event. 92 | */ 93 | LOAD.onStatusChanged = function (status) { 94 | // final status 95 | if (status === 'Sending client info...') { 96 | this.filesNeeded = 0; 97 | this.updateProgress(); 98 | } 99 | 100 | this.$.status.innerText = status; 101 | }; 102 | 103 | LOAD.init(); 104 | window.LOAD = LOAD; 105 | 106 | /** 107 | * Called when the loading screen finishes loading all assets. 108 | * 109 | * @param {String} serverName Server name. 110 | * @param {String} serverUrl Server loading screen URL. 111 | * @param {String} mapName Map name. 112 | * @param {Number} maxPlayers Maximum players. 113 | * @param {String} steamid 64-bit Steam ID. 114 | * @param {String} gamemode Gamemode folder name. 115 | */ 116 | window.GameDetails = function (serverName, serverUrl, mapName, maxPlayers, steamid, gamemode) { 117 | LOAD.setServerInfo(serverName, mapName, maxPlayers); 118 | }; 119 | 120 | /** 121 | * Called when a file starts downloading. The filename includes the entire 122 | * path of the file; for example "materials/models/bobsModels/car.mdl". 123 | * 124 | * @param {String} filePath Full file path. 125 | */ 126 | window.DownloadingFile = function (filePath) { 127 | LOAD.onFileDownloading(filePath); 128 | }; 129 | 130 | /** 131 | * Called when something happens. This might be "Initialising Game Data", 132 | * "Sending Client Info", etc. 133 | * 134 | * @param {String} status Loading status. 135 | */ 136 | window.SetStatusChanged = function (status) { 137 | LOAD.onStatusChanged(status); 138 | }; 139 | 140 | /** 141 | * Called at the start, tells us how many files need to be downloaded in 142 | * total. 143 | * 144 | * @param {String} total Total files to be downloaded. 145 | */ 146 | window.SetFilesTotal = function (total) { 147 | LOAD.setFilesTotal(total); 148 | }; 149 | 150 | /** 151 | * Called when the number of files to download changes. 152 | * 153 | * @param {String} needed Number of files needed to download. 154 | */ 155 | window.SetFilesNeeded = function (needed) { 156 | LOAD.setFilesNeeded(needed); 157 | }; 158 | }()); 159 | -------------------------------------------------------------------------------- /app/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Flexbox isn't supported in the GMod version of Awesomium. 2 | // These mixins use the old flexbox standards. 3 | 4 | @mixin box { 5 | display: -webkit-box; 6 | display: -moz-box; 7 | display: box; 8 | } 9 | 10 | @mixin box-center { 11 | -webkit-box-pack: center; 12 | -webkit-box-align: center; 13 | } 14 | 15 | @mixin box-vertical { 16 | -webkit-box-orient: vertical; 17 | -moz-box-orient: vertical; 18 | -box-orient: vertical; 19 | } 20 | 21 | @mixin box-horizontal { 22 | -webkit-box-orient: horizontal; 23 | -moz-box-orient: horizontal; 24 | box-orient: horizontal; 25 | } 26 | 27 | @mixin box-flex($amount) { 28 | -webkit-box-flex: $amount; 29 | -moz-box-flex: $amount; 30 | box-flex: $amount; 31 | } 32 | 33 | @mixin box-no-stretch { 34 | -webkit-box-align: start; 35 | -moz-box-flex: start; 36 | box-flex: start; 37 | } 38 | 39 | @mixin calc($property, $expression) { 40 | #{$property}: -webkit-calc(#{$expression}); 41 | #{$property}: calc(#{$expression}); 42 | } 43 | 44 | @mixin single-line { 45 | white-space: nowrap; 46 | text-overflow: clip; 47 | overflow: hidden; 48 | } 49 | -------------------------------------------------------------------------------- /app/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import "mixins"; 2 | 3 | html, body { 4 | width: 100%; 5 | height: 100%; 6 | margin: 0; 7 | padding: 0; 8 | } 9 | 10 | body { 11 | background: #000; 12 | font-family: Arial, sans-serif; 13 | color: #fff; 14 | } 15 | 16 | * { 17 | box-sizing: border-box; 18 | } 19 | 20 | .loading-screen { 21 | $ls-width: 720px; 22 | $ls-section-margin: 25px; 23 | $ls-text-color: #ececec; 24 | 25 | width: 100%; 26 | height: 100%; 27 | 28 | background: #000 url(../images/background01.jpg) no-repeat 50% 50%; 29 | background-size: cover; 30 | 31 | font-family: Arial, sans-serif; 32 | 33 | @include box; 34 | @include box-center; 35 | @include box-vertical; 36 | 37 | .logo-container { 38 | margin: 0 0 $ls-section-margin 0; 39 | } 40 | 41 | .content-container { 42 | width: $ls-width; 43 | margin: 0 0 $ls-section-margin 0; 44 | 45 | @include box; 46 | @include box-horizontal; 47 | // @include box-no-stretch; 48 | 49 | .board { 50 | $board-padding: 10px; 51 | 52 | background-color: rgba(0,0,0,0.77); 53 | border-radius: 5px; 54 | 55 | @include box-flex(1); 56 | 57 | .title { 58 | color: $ls-text-color; 59 | padding: $board-padding 5px; 60 | 61 | font-size: 18px; 62 | font-weight: bold; 63 | text-align: center; 64 | 65 | border-bottom: 1px solid rgba(255,255,255,0.11); 66 | } 67 | 68 | .content { 69 | padding: $board-padding; 70 | } 71 | } 72 | 73 | .board-divider { 74 | max-width: 15px; 75 | @include box-flex(1); 76 | } 77 | } 78 | 79 | .loading-bar { 80 | $bar-height: 24px; 81 | $bar-color: #3498db; 82 | 83 | width: $ls-width; 84 | height: $bar-height; 85 | 86 | position: relative; 87 | 88 | background-color: darken($bar-color, 22%); 89 | 90 | .progress-bar { 91 | background-color: $bar-color; 92 | 93 | position: absolute; 94 | top: 0; 95 | bottom: 0; 96 | left: 0; 97 | 98 | right: 63%; 99 | } 100 | 101 | .status, .percentage { 102 | padding: 0 8px; 103 | 104 | color: $ls-text-color; 105 | text-shadow: 0 1px 2px rgba(0,0,0,0.33); 106 | font-size: 12px; 107 | line-height: $bar-height; 108 | 109 | position: absolute; 110 | top: 0; 111 | bottom: 0; 112 | right: 0; 113 | } 114 | 115 | .status { 116 | left: 0; 117 | 118 | // don't overflow overtop of percentage 119 | @include single-line; 120 | @include calc(max-width, "100% - 46px"); 121 | } 122 | 123 | .percentage { 124 | right: 0; 125 | } 126 | } 127 | 128 | .server-info { 129 | width: 140px; 130 | 131 | .preview { 132 | width: 128px; 133 | height: 128px; 134 | margin: 0 15px 15px 0; 135 | border-radius: 4px; 136 | } 137 | 138 | .map-name, .player-slots { 139 | @include single-line; 140 | } 141 | } 142 | 143 | .server-rules { 144 | width: 140px; 145 | 146 | .rules { 147 | counter-reset: rules-counter; 148 | margin: 0; 149 | padding: 0; 150 | 151 | $rule-padding-v: 8px; 152 | $rule-padding-h: 2.5em; 153 | $rule-height: 28px; 154 | 155 | > li { 156 | padding: $rule-padding-v $rule-padding-h; 157 | list-style: none; 158 | line-height: $rule-height; 159 | 160 | border-bottom: 1px solid rgba(255,255,255,0.11); 161 | 162 | position: relative; 163 | 164 | &:before { 165 | content: counter(rules-counter); 166 | counter-increment: rules-counter; 167 | 168 | width: $rule-padding-h; 169 | height: $rule-height + $rule-padding-v * 2; 170 | line-height: $rule-height + $rule-padding-v * 2; 171 | 172 | text-align: center; 173 | font-weight: bold; 174 | 175 | position: absolute; 176 | top: 0; 177 | left: 0; 178 | } 179 | 180 | &:last-child { 181 | border-bottom: none; 182 | } 183 | } 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | 'use strict'; 3 | 4 | var gulp = require('gulp'); 5 | var $ = require('gulp-load-plugins')(); 6 | 7 | gulp.task('styles', function () { 8 | return gulp.src('app/styles/main.scss') 9 | .pipe($.plumber()) 10 | .pipe($.sass({ 11 | style: 'expanded', 12 | precision: 10 13 | })) 14 | // GMod Awesomium uses Chrome v18 15 | .pipe($.autoprefixer({ 16 | browsers: ['Chrome >= 18', 'last 1 version'] 17 | })) 18 | .pipe(gulp.dest('.tmp/styles')); 19 | }); 20 | 21 | gulp.task('jshint', function () { 22 | return gulp.src('app/scripts/**/*.js') 23 | .pipe($.jshint()) 24 | .pipe($.jshint.reporter('jshint-stylish')) 25 | .pipe($.jshint.reporter('fail')); 26 | }); 27 | 28 | gulp.task('html', ['styles'], function () { 29 | return gulp.src('app/*.html') 30 | .pipe($.if('*.js', $.uglify())) 31 | .pipe($.if('*.css', $.csso())) 32 | .pipe($.useref({searchPath: '{.tmp,app}'})) 33 | .pipe($.if('*.html', $.minifyHtml({conditionals: true, loose: true}))) 34 | .pipe(gulp.dest('dist')); 35 | }); 36 | 37 | gulp.task('images', function () { 38 | return gulp.src('app/images/**/*') 39 | .pipe(gulp.dest('dist/images')); 40 | }); 41 | 42 | gulp.task('fonts', function () { 43 | return gulp.src('app/fonts/**/*') 44 | .pipe($.filter('**/*.{eot,svg,ttf,woff}')) 45 | .pipe($.flatten()) 46 | .pipe(gulp.dest('dist/fonts')); 47 | }); 48 | 49 | gulp.task('extras', function () { 50 | return gulp.src([ 51 | 'app/*.*', 52 | '!app/*.html', 53 | 'node_modules/apache-server-configs/dist/.htaccess' 54 | ], { 55 | dot: true 56 | }).pipe(gulp.dest('dist')); 57 | }); 58 | 59 | gulp.task('clean', require('del').bind(null, ['.tmp', 'dist'])); 60 | 61 | gulp.task('connect', ['styles'], function () { 62 | var serveStatic = require('serve-static'); 63 | var serveIndex = require('serve-index'); 64 | var app = require('connect')() 65 | .use(require('connect-livereload')({port: 35729})) 66 | .use(serveStatic('.tmp')) 67 | .use(serveStatic('app')) 68 | .use(serveIndex('app')); 69 | 70 | require('http').createServer(app) 71 | .listen(9000) 72 | .on('listening', function () { 73 | console.log('Started connect web server on http://localhost:9000'); 74 | }); 75 | }); 76 | 77 | gulp.task('serve', ['connect', 'watch'], function () { 78 | require('opn')('http://localhost:9000'); 79 | }); 80 | 81 | gulp.task('watch', ['connect'], function () { 82 | $.livereload.listen(); 83 | 84 | // watch for changes 85 | gulp.watch([ 86 | 'app/*.html', 87 | '.tmp/styles/**/*.css', 88 | 'app/scripts/**/*.js', 89 | 'app/images/**/*' 90 | ]).on('change', $.livereload.changed); 91 | 92 | gulp.watch('app/styles/**/*.scss', ['styles']); 93 | }); 94 | 95 | gulp.task('build', ['jshint', 'html', 'images', 'fonts', 'extras'], function () { 96 | return gulp.src('dist/**/*').pipe($.size({title: 'build', gzip: true})); 97 | }); 98 | 99 | gulp.task('default', ['clean'], function () { 100 | gulp.start('build'); 101 | }); 102 | -------------------------------------------------------------------------------- /load-seed.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "follow_symlinks": true, 6 | "path": "." 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "engines": { 4 | "node": ">=4.4.5" 5 | }, 6 | "devDependencies": { 7 | "apache-server-configs": "^2.14.0", 8 | "connect": "^3.4.1", 9 | "connect-livereload": "^0.5.4", 10 | "del": "^2.2.1", 11 | "gulp": "^3.9.1", 12 | "gulp-autoprefixer": "^3.1.0", 13 | "gulp-cache": "^0.4.5", 14 | "gulp-csso": "^2.0.0", 15 | "gulp-filter": "^4.0.0", 16 | "gulp-flatten": "^0.3.0", 17 | "gulp-if": "^2.0.1", 18 | "gulp-imagemin": "^3.0.2", 19 | "gulp-jshint": "^2.0.1", 20 | "gulp-livereload": "^3.8.1", 21 | "gulp-load-plugins": "^1.2.4", 22 | "gulp-minify-html": "^1.0.6", 23 | "gulp-plumber": "^1.1.0", 24 | "gulp-sass": "^2.3.2", 25 | "gulp-size": "^2.1.0", 26 | "gulp-uglify": "^2.0.0", 27 | "gulp-useref": "^3.1.0", 28 | "jshint": "^2.9.2", 29 | "jshint-stylish": "^2.2.0", 30 | "main-bower-files": "^2.13.1", 31 | "opn": "^4.0.2", 32 | "serve-index": "^1.8.0", 33 | "serve-static": "^1.11.1" 34 | } 35 | } 36 | --------------------------------------------------------------------------------