├── .bowerrc ├── .gitignore ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── bower.json ├── frontend └── src │ ├── js │ └── app.js │ └── stylus │ ├── _axis.styl │ ├── _dashboard.styl │ ├── _logs.styl │ ├── _playbooks.styl │ ├── _settings.styl │ ├── _triggers.styl │ ├── axis │ ├── README.md │ ├── animation.styl │ ├── breakpoint.styl │ ├── buttons.styl │ ├── code.styl │ ├── flex.styl │ ├── forms.styl │ ├── gradients.styl │ ├── grid.styl │ ├── index.styl │ ├── interaction.styl │ ├── positions.styl │ ├── reset.styl │ ├── settings.styl │ ├── tables.styl │ ├── typography.styl │ ├── ui.styl │ ├── utilities.styl │ ├── vendor.styl │ └── vertical-rhythm.styl │ └── style.styl ├── gulpfile.js ├── npm-shrinkwrap.json ├── package.json ├── src ├── config │ └── base.js ├── controllers │ ├── jobs.js │ ├── logs.js │ ├── main.js │ ├── playbooks.js │ └── triggers.js ├── forms │ └── addTrigger.js ├── models │ ├── job.js │ ├── log.js │ ├── playbook.js │ └── trigger.js ├── routes.js ├── support │ ├── db │ │ └── mongoose │ │ │ └── schema.js │ ├── exec-then.js │ ├── notifications │ │ └── hipChat.js │ ├── shutdown │ │ └── cleanupResources.js │ ├── startup │ │ ├── models.js │ │ ├── notifyAdminsOfStartup.js │ │ ├── setupAnsible.js │ │ ├── setupNotifications.js │ │ ├── setupTemplateHelpers.js │ │ ├── setupTriggerTypes.js │ │ └── startJobProcessor.js │ ├── timers.js │ ├── triggerType.js │ └── urlUtils.js ├── triggerTypes │ ├── drone.js │ └── simple.js └── views │ ├── _mixins.jade │ ├── index.jade │ ├── jobs │ └── view.jade │ ├── layout.jade │ ├── logs │ └── index.jade │ ├── playbooks │ ├── addTrigger.jade │ ├── addTriggerStep2.jade │ ├── index.jade │ └── view.jade │ └── triggers │ ├── index.jade │ └── view.jade ├── start-app.js └── test ├── data └── playbooks │ ├── hosts │ ├── normal.yml │ └── pause.yml ├── integration └── basic.test.js ├── unit └── support │ └── notifications │ └── hipChat.test.js └── utils.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "frontend/src/bower" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | $HOME 4 | ansible 5 | npm-debug.log 6 | frontend/build 7 | frontend/src/bower 8 | src/config/development.js 9 | src/config/production.js 10 | playbooks -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // Settings 3 | "passfail" : false, // Stop on first error. 4 | "maxerr" : 100, // Maximum error before stopping. 5 | 6 | 7 | // Predefined globals whom JSHint will ignore. 8 | "browser" : false, // Standard browser globals e.g. `window`, `document`. 9 | 10 | "node" : true, 11 | "rhino" : false, 12 | "couch" : false, 13 | "wsh" : true, // Windows Scripting Host. 14 | 15 | "jquery" : false, 16 | "prototypejs" : false, 17 | "mootools" : false, 18 | "dojo" : false, 19 | 20 | "predef" : [ // Custom globals. 21 | ], 22 | 23 | 24 | // Development. 25 | "debug" : false, // Allow debugger statements e.g. browser breakpoints. 26 | "devel" : true, // Allow developments statements e.g. `console.log();`. 27 | 28 | 29 | // ECMAScript 30 | "esnext" : true, // Using ES6 31 | "noyield" : true, // allow generators with no yields 32 | "strict" : true, // Require `use strict` pragma in every file. 33 | "globalstrict" : false, // Allow global "use strict" (also enables 'strict'). 34 | 35 | // The Good Parts. 36 | "asi" : true, // Tolerate Automatic Semicolon Insertion (no semicolons). 37 | "laxbreak" : true, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons. 38 | "bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.). 39 | "boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments. 40 | "curly" : true, // Require {} for every new block or scope. 41 | "eqeqeq" : true, // Require triple equals i.e. `===`. 42 | "eqnull" : false, // Tolerate use of `== null`. 43 | "evil" : false, // Tolerate use of `eval`. 44 | "expr" : false, // Tolerate `ExpressionStatement` as Programs. 45 | "forin" : false, // Tolerate `for in` loops without `hasOwnPrototype`. 46 | "immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` 47 | "latedef" : true, // Prohipit variable use before definition. 48 | "loopfunc" : false, // Allow functions to be defined within loops. 49 | "noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`. 50 | "regexp" : true, // Prohibit `.` and `[^...]` in regular expressions. 51 | "regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`. 52 | "scripturl" : true, // Tolerate script-targeted URLs. 53 | "shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`. 54 | "supernew" : false, // Tolerate `new function () { ... };` and `new Object;`. 55 | "undef" : true, // Require all non-global variables be declared before they are used. 56 | "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`. 57 | "noempty" : true, // Prohibit use of empty blocks. 58 | "nonew" : true, // Prohibit use of constructors for side-effects. 59 | "nomen" : false, // Allow use of initial or trailing underbars in names. 60 | "onevar" : false, // Allow only one `var` statement per function. 61 | "plusplus" : false, // Prohibit use of `++` & `--`. 62 | "sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`. 63 | "trailing" : true, // Prohibit trailing whitespaces. 64 | "white" : false, // Check against strict whitespace and indentation rules. 65 | "indent" : 2 // Specify indentation spacing 66 | } 67 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | 5 | language: node_js 6 | 7 | services: 8 | - mongodb 9 | 10 | node_js: 11 | - "0.11" 12 | 13 | before_script: 14 | - "sudo pip install ansible" 15 | - "npm install -g gulp" 16 | - "npm install -g bower" 17 | - "npm install" 18 | - "bower install" 19 | 20 | script: 21 | - "npm run ci" 22 | 23 | notifications: 24 | email: 25 | - ram@hiddentao.com 26 | 27 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This guide guidelines for those wishing to contribute to Ansijet 4 | 5 | ## Contributor license agreement 6 | 7 | By submitting code as an individual or as an entity you agree that your code is [licensed the same as Ansijet](README.md). 8 | 9 | ## Issues and pull requests 10 | 11 | Issues and merge requests should be in English and contain appropriate language for audiences of all ages. 12 | 13 | We will only accept a merge requests which meets the following criteria: 14 | 15 | * Includes proper tests and all tests pass (unless it contains a test exposing a bug in existing code) 16 | * Can be merged without problems (if not please use: `git rebase master`) 17 | * Does not break any existing functionality 18 | * Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed) 19 | * Keeps the code base clean and well structured 20 | * Contains functionality we think other users will benefit from too 21 | * Doesn't add unnessecary configuration options since they complicate future changes 22 | 23 | ## Building and testing the code 24 | 25 | $ npm install -g gulp bower 26 | $ npm install 27 | $ bower install 28 | $ npm run build 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 [Ramesh Nair](http://www.hiddentao.com/) 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansijet 2 | 3 | [![Build Status](https://secure.travis-ci.org/hiddentao/ansijet.png)](http://travis-ci.org/hiddentao/ansijet) 4 | 5 | An [Ansible](http://ansible.com/) playbook automation server. 6 | 7 | A node.js server which exposes a simple web API which triggers playbook runs 8 | when a request is received. This is especially useful if you are unable to run 9 | Ansible playbooks directly from within your continuous integration environment 10 | or if you simply wish to trigger playbook runs based on other 11 | events within your system. 12 | 13 | Features: 14 | * Trigger playbook runs from [different sources](#triggers), including from CI systems such as [Drone](https://github.com/drone/drone). 15 | * Run multiple playbooks in parallel, all in separate processes 16 | * Fast, friendly web interface with accompanying [REST API](#rest-api) 17 | * Highly asynchronous, scalable back-end 18 | * Full console [log capture](#execution-logs) and storage 19 | * Sends notifications of job status through [HipChat](#hipchat) 20 | 21 | 22 | ## Installation and startup 23 | 24 | **Pre-requisite: Ansible 1.5+** 25 | 26 | Installation instructions: http://docs.ansible.com/intro_installation.html. 27 | 28 | To ensure you have the latest version it is recommended that you install it 29 | using `pip`, the Python package manager. 30 | 31 | **Pre-requisite: Node.js 0.11.2+** 32 | 33 | Installation instructions: [http://nodejs.org/](http://nodejs.org/). 34 | 35 | Ensure the installed version is at least **0.11.2**. Ansijet will not work with 36 | earlier versions. 37 | 38 | _(For Ubuntu users I recommend the [Chris Lea PPA](https://launchpad.net/~chris-lea/+archive/ubuntu/node.js-devel))_. 39 | 40 | **Pre-requisite: MongoDB** 41 | 42 | Installation instructions: 43 | 44 | Ansijet stores its data in MongoDB. The default configuration expects to be able 45 | to connect to a MongoDB server running on `127.0.0.1` (i.e. `localhost`). 46 | 47 | **Setup your Ansible playbooks** 48 | 49 | Place your Ansible playbooks somewhere, e.g. `/playbooks`. 50 | 51 | Ansijet expects your playbooks folder to have a certain structure: 52 | 53 | ``` 54 | /*.yml <- your playbooks 55 | /hosts <- Ansible hosts file 56 | ``` 57 | 58 | Ensure that any [`roles`](http://docs.ansible.com/playbooks_roles.html) needed 59 | by your playbooks can be found by the 60 | `ansible-playbook` binary. An easy way to ensure this is to store your roles 61 | within the same folder, i.e. at `/roles/`. Ditto for 62 | `group_vars` and `host_vars` folders. 63 | 64 | 65 | **Setup Ansijet** 66 | 67 | ```bash 68 | $ git clone https://github.com/hiddentao/ansijet.git ansijet 69 | $ cd ansijet 70 | $ npm install -g gulp bower 71 | $ npm install 72 | $ bower install 73 | $ npm run build 74 | ``` 75 | 76 | Now create `ansijet/src/config/production.js`: 77 | 78 | ```javascript 79 | "use strict"; 80 | 81 | module.exports = function(config) { 82 | /** Path to folder containg Ansible playbooks */ 83 | config.playbooks = '/playbooks' 84 | 85 | /** Max no. of jobs to execute in parallel. Should match no. of CPU cores. */ 86 | config.jobsInParallel = 1; 87 | }; 88 | ``` 89 | 90 | If you look inside `ansijet/src/config/base.js` you will see other 91 | configuration settings MongoDB, logging, etc. You may 92 | override these too within the `config/production.js` you created. 93 | 94 | **Run Ansijet** 95 | 96 | ```bash 97 | $ cd ansijet 98 | $ NODE_ENV=production ./start-app.js 99 | ``` 100 | 101 | If you visit `http://localhost:3000` you should see the dashboard showing the 102 | _Active Jobs_ (there should be none currently). 103 | 104 | 105 | ## Setup playbook automation 106 | 107 | Once Ansijet is up and running and you can access the web interface you can view 108 | the list of Playbooks that Ansijet has found and assign triggers to them. 109 | 110 | ### Triggers 111 | 112 | A trigger is a mechanism which kicks of a playook run when an incoming URL 113 | request is received. 114 | 115 | Triggers have two purposes: 116 | 117 | 1. To perform any necessary additional checks when a request is received to 118 | ensure that the request is valid 119 | 2. To supply variables to the Ansible playbook, allowing for playbook execution 120 | to be configurable based on the incoming request and the trigger configuration. 121 | 122 | All triggers URLs look like `/invoke/?token=` with 123 | additional query parameters depending on the trigger type. 124 | 125 | _Note: The `` is randomly generated by Ansijet when a trigger is created 126 | and acts as an additional security check. If the token in an incoming request is 127 | incorrect Ansijet does not report this to the URL requester - it simply logs 128 | this fact in the back-end._ 129 | 130 | At present two trigger types are supported: 131 | 132 | **Trigger: Simple** 133 | 134 | This exposes a simple URL which triggers a playbook run. It does not 135 | perform any checks prior to triggering the playbook run. Neither does it supply 136 | any Ansible playbook variables. 137 | 138 | **Trigger: Drone** 139 | 140 | This exposes a URL to be called after a successful [Drone](https://github.com/drone/drone) 141 | build. It supplies the following Ansible variables: 142 | 143 | * `ci_expected_branch` <- Git branch to run playbook for, configured by user 144 | * `ci_build_commit` <- Git commit id, obtained from incoming request 145 | * `ci_build_branch` <- Git branch built, obtained from incoming request 146 | 147 | 148 | ### Jobs 149 | 150 | When a trigger is invoked it runs a playbook, known as a _Job_. Jobs are 151 | executed in parallel by 152 | Ansijet, with the maximum no. of simultaenous jobs determined by the 153 | `jobsInParallel` configuration parameter set during Ansijet installation. 154 | Ansijet is also smart enough to ensure that for each playbook, only one instance 155 | of it is being run at a time. 156 | 157 | Each job - i.e. playbook run - takes place in a separate shell process, allowing 158 | Ansijet to be scaled up according to your machine's cores. Ansijet also 159 | monitors each shell process such that if no output is received for 5 minutes 160 | (this time window is configurable) it will kill the shell process 161 | and assume the playbook run has failed. 162 | 163 | When a job is being processed it shows up as an _Active Job_ on your Ansijet 164 | server's homepage. You can click on it to view the current log output, including 165 | console log output. 166 | 167 | 168 | ### Execution logs 169 | 170 | All logs can be viewed by going to the _Logs_ site section. You can then drill 171 | down to view the logs pertaining to a particular trigger and/or a particular 172 | trigger job. 173 | 174 | 175 | 176 | ## REST API 177 | 178 | Ansijet is built using [Waigo](http://waigojs.com), which means that all the 179 | URL routes automatically have REST API counterparts. For any given URL, you can 180 | view REST JSON output by simply appending a `format=json` query parameter when 181 | making the request. This applies to form submissions too. For more information 182 | on this [see the Waigo docs](http://waigojs.com/guide.html#views-and-output-formats). 183 | 184 | 185 | ## HipChat 186 | 187 | Ansijet can be configured to send notifications to a 188 | [HipChat](https://hipchat.com) room using the 189 | [`send_room_notification`](https://www.hipchat.com/docs/apiv2/method/send_room_notification) API. 190 | 191 | Simply add the room id and auth token to your configuration file: 192 | 193 | ```javascript 194 | module.exports = function(config) { 195 | ... 196 | config.notifications.hipChat = { 197 | roomId: , 198 | authToken: 199 | }; 200 | ... 201 | }; 202 | ``` 203 | 204 | When Ansijet first starts up it will send a notification. Subsequent 205 | notifications will get sent for every job which gets processed. 206 | 207 | 208 | ## Securing Ansijet 209 | 210 | Ansijet does not come with any sort of authentication out of the box. Since it's 211 | running playbooks which most probably affect your servers you will likely want to 212 | protect access to it. 213 | 214 | My setup is to have Ansijet placed behind an Nginx front-end 215 | server, with SSL and HTTP Basic auth enforced on all incoming requests: 216 | 217 | ``` 218 | server { 219 | listen 80; 220 | server_name example.com www.example.com; 221 | return 301 https://$host$request_uri; 222 | } 223 | 224 | server { 225 | listen 443; 226 | server_name example.com www.example.com; 227 | 228 | ssl on; 229 | ssl_certificate /etc/ssl/certs/server.crt; 230 | ssl_certificate_key /etc/ssl/private/server.key; 231 | ssl_session_timeout 5m; 232 | 233 | # Perfect Forward Secrecy 234 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 235 | ssl_prefer_server_ciphers on; 236 | ssl_ciphers EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA256:EECDH+aRSA+RC4:EDH+aRSA:EECDH:RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS; 237 | 238 | root /ansijet/frontend/build; 239 | 240 | location ~ /\. { 241 | deny all; 242 | } 243 | 244 | location ~* ^/(css|fonts|img|js)/.+$ { 245 | gzip_static on; 246 | gzip_vary on; 247 | expires 30d; 248 | add_header Pragma public; 249 | add_header Cache-Control "public"; 250 | } 251 | 252 | location ~* ^(robots|humans)\.txt$ { 253 | expires 30d; 254 | add_header Pragma public; 255 | add_header Cache-Control "public"; 256 | } 257 | 258 | # If you want to monitor the status of Ansijet and check that it is running 259 | # you can call the `/ping` URL. This will output `Ansijet up` is Ansijet is 260 | # running 261 | location = /ping { 262 | proxy_pass http://127.0.0.1:3000; 263 | } 264 | 265 | # Everything else needs auth 266 | location / { 267 | auth_basic on; 268 | auth_basic_user_file /ansijet/httpd.auth; 269 | proxy_pass http://127.0.0.1:3000; 270 | } 271 | 272 | } 273 | ``` 274 | 275 | 276 | ## Contributing 277 | 278 | Though I am already using Ansijet in a production environment it is very much a 279 | work-in-progress. All suggestions and pull requests are welcome! 280 | 281 | See CONTRIBUTING.md for guidelines. 282 | 283 | ## License 284 | 285 | MIT - see LICENSE.md 286 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ansijet", 3 | "version": "1.0.0", 4 | "private": true, 5 | "homepage": "https://github.com/hiddentao/ansijet", 6 | "authors": [ 7 | "Ramesh Nair " 8 | ], 9 | "description": "Ansible automation server", 10 | "keywords": [ 11 | "ansible", 12 | "rest", 13 | "web", 14 | "ui", 15 | "api" 16 | ], 17 | "license": "MIT", 18 | "private": true, 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests" 25 | ], 26 | "dependencies": { 27 | "minified": "~2014.0.0-beta5.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/js/app.js: -------------------------------------------------------------------------------- 1 | var MINI = require('minified'); 2 | var _=MINI._, $=MINI.$, $$=MINI.$$, EE=MINI.EE, HTML=MINI.HTML; 3 | 4 | $.ready(function() { 5 | "use strict"; 6 | 7 | // expandable/collapsible content sections 8 | $('.expandable').each(function(elem) { 9 | var collapsed = $('.collapsed', elem)[0]; 10 | var expanded = $('.expanded', elem)[0]; 11 | 12 | collapsed.onclick = function() { 13 | expanded.style.display = 'block'; 14 | collapsed.style.display = 'none'; 15 | }; 16 | 17 | expanded.onclick = function() { 18 | expanded.style.display = 'none'; 19 | collapsed.style.display = 'block'; 20 | }; 21 | }); 22 | 23 | 24 | // delete buttons 25 | $('a.delete').on('?click', function(e) { 26 | var elem = this[0]; 27 | var str = elem.getAttribute('data-item-type') || 'item'; 28 | 29 | return confirm('Are you sure you want to delete this ' + str + '?'); 30 | }); 31 | }); 32 | 33 | -------------------------------------------------------------------------------- /frontend/src/stylus/_axis.styl: -------------------------------------------------------------------------------- 1 | @import "./axis" 2 | 3 | flash(type = notice) 4 | if type == notice 5 | notice(color: #6CC5FA) 6 | color: #fff 7 | font-weight: bold 8 | text-shadow: 1px 1px 1px rgba(#000,.2) 9 | if type == success 10 | notice(color: #7BDEA3) 11 | color: #fff 12 | font-weight: bold 13 | text-shadow: 1px 1px 1px rgba(#000,.2) 14 | if type == warning 15 | notice(color: lighten(pal1, 40%)) 16 | font-weight: bold 17 | text-shadow: 1px 1px 1px rgba(#fff,.2) 18 | if type == error 19 | notice(color: pal3) 20 | color: #fff 21 | font-weight: bold 22 | text-shadow: 1px 1px 1px rgba(#000,.2) 23 | -------------------------------------------------------------------------------- /frontend/src/stylus/_dashboard.styl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiddentao/ansijet/154061443497cf504874923e8ab8a645a42f85a3/frontend/src/stylus/_dashboard.styl -------------------------------------------------------------------------------- /frontend/src/stylus/_logs.styl: -------------------------------------------------------------------------------- 1 | .logs 2 | table.list 3 | thead 4 | th.time 5 | width 12% 6 | th.trigger 7 | max-width 10% 8 | width 10% 9 | th.job 10 | width 10% 11 | td.text 12 | word-wrap break-word 13 | 14 | &.console, &.code 15 | pre 16 | background-color pal1 17 | color #fff 18 | 19 | .error, pre.error 20 | flash(type: error) 21 | 22 | .warning, pre.warning 23 | flash(type: warning) 24 | 25 | .jobs 26 | table.list 27 | td.status 28 | span 29 | display inline-block 30 | 31 | -------------------------------------------------------------------------------- /frontend/src/stylus/_playbooks.styl: -------------------------------------------------------------------------------- 1 | #playbooks 2 | vertical-align top 3 | 4 | .sidebar 5 | float left 6 | width 15% 7 | overflow hidden 8 | border-collapse collapse 9 | border-right 1px dotted #666 10 | padding-right 2% 11 | margin-right 2% 12 | padding-top 1em 13 | 14 | ul 15 | margin 0 16 | padding 0 17 | 18 | li 19 | font-size 120% 20 | margin-bottom 1em 21 | 22 | & > a 23 | width 100% 24 | padding 0.2em 25 | 26 | &.active > a 27 | background-color #ccc 28 | border-radius(5px) 29 | color #000 30 | 31 | 32 | 33 | 34 | .content 35 | float left 36 | 37 | h1, h2:first-child 38 | margin-top 0 39 | 40 | 41 | 42 | #playbook 43 | a.addTrigger 44 | margin-top 1em -------------------------------------------------------------------------------- /frontend/src/stylus/_settings.styl: -------------------------------------------------------------------------------- 1 | /* General settings */ 2 | 3 | pal1 = #323a46 4 | pal2 = #22282f 5 | pal3 = #eb4a33 6 | pal4 = #ffffff 7 | pal5 = #e9f0f5 8 | 9 | body-bg-color = pal4 10 | body-fg-color = pal2 11 | 12 | header-height = 50px 13 | header-bg-color = pal2 14 | header-fg-color = #fff 15 | 16 | nav-btn-height = header-height / 2 17 | nav-btn-padding = nav-btn-height / 2 18 | 19 | text-hover-highlight-color = pal5 20 | 21 | btn() 22 | button(pal3, medium, #fff, pal3, pal3) 23 | 24 | 25 | /* Axis settings */ 26 | 27 | support-for-ie = false 28 | -------------------------------------------------------------------------------- /frontend/src/stylus/_triggers.styl: -------------------------------------------------------------------------------- 1 | #trigger 2 | .actions 3 | float right -------------------------------------------------------------------------------- /frontend/src/stylus/axis/README.md: -------------------------------------------------------------------------------- 1 | We've got a local copy of Axis (axis-css - 0.1.8) here because the 2 | NPM module causes parsing errors. And the latest 3 | version of the new NPM module (axis - 0.2.0) is incomplete (e.g. no grid styles). 4 | 5 | Above issues noted in a PR: https://github.com/jenius/axis/pull/126 -------------------------------------------------------------------------------- /frontend/src/stylus/axis/animation.styl: -------------------------------------------------------------------------------- 1 | // ------------------------------ 2 | // Preset animations 3 | // ------------------------------ 4 | 5 | // Ported directly from Eric Meyer's compass-animation module, which was adapted from Dan Eden's animate.css 6 | // https://github.com/ericam/compass-animation 7 | // http://daneden.me/animate/ 8 | 9 | // Includes: flash, bounce, shake, tada, swing, wobble, pulse, hinge, 10 | // rollIn/Out, bounceIn/Out(Up/Down/Left/Right), fadeIn/Out(Up/Down/Left/Right), and 11 | // rotateIn/Out(Up/Down/Left/Right) 12 | 13 | // Excludes the "big" animations because they are atrocious and should never be used. 14 | // Will include more custom-written ones in the future 15 | 16 | @keyframes flash 17 | 0% 18 | opacity: 1 19 | 25% 20 | opacity: 0 21 | 50% 22 | opacity: 1 23 | 75% 24 | opacity: 0 25 | 100% 26 | opacity: 1 27 | 28 | @keyframes bounce 29 | 0% 30 | transform: translateY(0) 31 | 20% 32 | transform: translateY(0) 33 | 40% 34 | transform: translateY(-30px) 35 | 50% 36 | transform: translateY(0) 37 | 60% 38 | transform: translateY(-15px) 39 | 80% 40 | transform: translateY(0) 41 | 100% 42 | transform: translateY(0) 43 | 44 | @keyframes shake 45 | 0% 46 | transform: translateX(0) 47 | 10% 48 | transform: translateX(-10px) 49 | 20% 50 | transform: translateX(10px) 51 | 30% 52 | transform: translateX(-10px) 53 | 40% 54 | transform: translateX(10px) 55 | 50% 56 | transform: translateX(-10px) 57 | 60% 58 | transform: translateX(10px) 59 | 70% 60 | transform: translateX(-10px) 61 | 80% 62 | transform: translateX(10px) 63 | 90% 64 | transform: translateX(-10px) 65 | 100% 66 | transform: translateX(0) 67 | 68 | @keyframes tada 69 | 0% 70 | transform: scale(1) 71 | 10% 72 | transform: scale(0.9) rotate(-3deg) 73 | 20% 74 | transform: scale(0.9) rotate(-3deg) 75 | 30% 76 | transform: scale(1.1) rotate(3deg) 77 | 40% 78 | transform: scale(1.1) rotate(-3deg) 79 | 50% 80 | transform: scale(1.1) rotate(3deg) 81 | 60% 82 | transform: scale(1.1) rotate(-3deg) 83 | 70% 84 | transform: scale(1.1) rotate(3deg) 85 | 80% 86 | transform: scale(1.1) rotate(-3deg) 87 | 90% 88 | transform: scale(1.1) rotate(3deg) 89 | 100% 90 | transform: scale(1) rotate(0) 91 | 92 | @keyframes swing 93 | 20% 94 | 40% 95 | 60% 96 | 80% 97 | 100% 98 | transform-origin: top center 99 | 20% 100 | transform: rotate(15deg) 101 | 40% 102 | transform: rotate(-10deg) 103 | 60% 104 | transform: rotate(5deg) 105 | 80% 106 | transform: rotate(-5deg) 107 | 100% 108 | transform: rotate(0deg) 109 | 110 | @keyframes wobble 111 | 0% 112 | transform: translateX(0%) 113 | 15% 114 | transform: translateX(-25%) rotate(-5deg) 115 | 30% 116 | transform: translateX(20%) rotate(3deg) 117 | 45% 118 | transform: translateX(-15%) rotate(-3deg) 119 | 60% 120 | transform: translateX(10%) rotate(2deg) 121 | 75% 122 | transform: translateX(-5%) rotate(-1deg) 123 | 100% 124 | transform: translateX(0%) 125 | 126 | @keyframes pulse 127 | 0% 128 | transform: scale(1) 129 | 50% 130 | transform: scale(1.1) 131 | 100% 132 | transform: scale(1) 133 | 134 | @keyframes hinge 135 | 0% 136 | transform: rotate(0) 137 | transform-origin: top left 138 | animation-timing-function: ease-in-out 139 | 20%, 60% 140 | transform: rotate(80deg) 141 | transform-origin: top left 142 | animation-timing-function: ease-in-out 143 | 40% 144 | transform: rotate(60deg) 145 | transform-origin: top left 146 | animation-timing-function: ease-in-out 147 | 80% 148 | transform: rotate(60deg) translateY(0) 149 | opacity: 1 150 | transform-origin: top left 151 | animation-timing-function: ease-in-out 152 | 100% 153 | transform: translateY(700px) 154 | opacity: 0 155 | 156 | @keyframes rollIn 157 | 0% 158 | opacity: 0 159 | transform: translateX(-100%) rotate(-120deg) 160 | 100% 161 | opacity: 1 162 | transform: translateX(0px) rotate(0deg) 163 | 164 | @keyframes rollOut 165 | 0% 166 | opacity: 1 167 | transform: translateX(0px) rotate(0deg) 168 | 100% 169 | opacity: 0 170 | transform: translateX(-100%) rotate(-120deg) 171 | 172 | @keyframes bounceIn 173 | 0% 174 | opacity: 0 175 | transform: scale(0.3) 176 | 50% 177 | opacity: 1 178 | transform: scale(1.05) 179 | 70% 180 | transform: scale(0.9) 181 | 100% 182 | transform: scale(1) 183 | 184 | @keyframes bounceInDown 185 | 0% 186 | opacity: 0 187 | transform: translateY(-2000px) 188 | 60% 189 | opacity: 1 190 | transform: translateY(30px) 191 | 80% 192 | transform: translateY(-10px) 193 | 100% 194 | transform: translateY(0) 195 | 196 | @keyframes bounceInUp 197 | 0% 198 | opacity: 0 199 | transform: translateY(2000px) 200 | 60% 201 | opacity: 1 202 | transform: translateY(-30px) 203 | 80% 204 | transform: translateY(10px) 205 | 100% 206 | transform: translateY(0) 207 | 208 | @keyframes bounceInRight 209 | 0% 210 | opacity: 0 211 | transform: translateX(2000px) 212 | 60% 213 | opacity: 1 214 | transform: translateX(-30px) 215 | 80% 216 | transform: translateX(10px) 217 | 100% 218 | transform: translateX(0) 219 | 220 | @keyframes bounceInLeft 221 | 0% 222 | opacity: 0 223 | transform: translateX(-2000px) 224 | 60% 225 | opacity: 1 226 | transform: translateX(30px) 227 | 80% 228 | transform: translateX(-10px) 229 | 100% 230 | transform: translateX(0) 231 | 232 | @keyframes bounceOut 233 | 0% 234 | transform: scale(1) 235 | 25% 236 | transform: scale(0.95) 237 | 50% 238 | opacity: 1 239 | transform: scale(1.1) 240 | 100% 241 | opacity: 0 242 | transform: scale(0.3) 243 | 244 | @keyframes bounceOutUp 245 | 0% 246 | transform: translateY(0) 247 | 20% 248 | opacity: 1 249 | transform: translateY(20px) 250 | 100% 251 | opacity: 0 252 | transform: translateY(-2000px) 253 | 254 | @keyframes bounceOutDown 255 | 0% 256 | transform: translateY(0) 257 | 20% 258 | opacity: 1 259 | transform: translateY(-20px) 260 | 100% 261 | opacity: 0 262 | transform: translateY(2000px) 263 | 264 | @keyframes bounceOutLeft 265 | 0% 266 | transform: translateX(0) 267 | 20% 268 | opacity: 1 269 | transform: translateX(20px) 270 | 100% 271 | opacity: 0 272 | transform: translateX(-2000px) 273 | 274 | @keyframes bounceOutRight 275 | 0% 276 | transform: translateX(0) 277 | 20% 278 | opacity: 1 279 | transform: translateX(-20px) 280 | 100% 281 | opacity: 0 282 | transform: translateX(2000px) 283 | 284 | @keyframes fadeIn 285 | 0% 286 | opacity: 0 287 | 100% 288 | opacity: 1 289 | 290 | @keyframes fadeInUp 291 | 0% 292 | transform: translateY(20px) 293 | opacity: 0 294 | 100% 295 | transform: translateY(0) 296 | opacity: 1 297 | 298 | @keyframes fadeInDown 299 | 0% 300 | transform: translateY(-20px) 301 | opacity: 0 302 | 100% 303 | transform: translateY(0) 304 | opacity: 1 305 | 306 | @keyframes fadeInRight 307 | 0% 308 | transform: translateX(20px) 309 | opacity: 0 310 | 100% 311 | transform: translateX(0) 312 | opacity: 1 313 | 314 | @keyframes fadeInLeft 315 | 0% 316 | transform: translateX(-20px) 317 | opacity: 0 318 | 100% 319 | transform: translateX(0) 320 | opacity: 1 321 | 322 | @keyframes fadeOut 323 | 0% 324 | opacity: 1 325 | 100% 326 | opacity: 0 327 | 328 | @keyframes fadeOutUp 329 | 0% 330 | transform: translateY(0) 331 | opacity: 1 332 | 100% 333 | transform: translateY(-20px) 334 | opacity: 0 335 | 336 | @keyframes fadeOutDown 337 | 0% 338 | transform: translateY(0) 339 | opacity: 1 340 | 100% 341 | transform: translateY(20px) 342 | opacity: 0 343 | 344 | @keyframes fadeOutRight 345 | 0% 346 | transform: translateX(0) 347 | opacity: 1 348 | 100% 349 | transform: translateX(20px) 350 | opacity: 0 351 | 352 | @keyframes fadeOutLeft 353 | 0% 354 | transform: translateX(0) 355 | opacity: 1 356 | 100% 357 | transform: translateX(-20px) 358 | opacity: 0 359 | 360 | @keyframes rotateIn 361 | 0% 362 | transform-origin: center center 363 | transform: rotate(-200deg) 364 | opacity: 0 365 | 100% 366 | transform-origin: center center 367 | transform: rotate(0) 368 | opacity: 1 369 | 370 | @keyframes rotateInDownLeft 371 | 0% 372 | transform-origin: left bottom 373 | transform: rotate(-90deg) 374 | opacity: 0 375 | 100% 376 | transform-origin: left bottom 377 | transform: rotate(0) 378 | opacity: 1 379 | 380 | @keyframes rotateInUpLeft 381 | 0% 382 | transform-origin: left bottom 383 | transform: rotate(90deg) 384 | opacity: 0 385 | 100% 386 | transform-origin: left bottom 387 | transform: rotate(0) 388 | opacity: 1 389 | 390 | @keyframes rotateInUpRight 391 | 0% 392 | transform-origin: right bottom 393 | transform: rotate(-90deg) 394 | opacity: 0 395 | 100% 396 | transform-origin: right bottom 397 | transform: rotate(0) 398 | opacity: 1 399 | 400 | @keyframes rotateInDownRight 401 | 0% 402 | transform-origin: right bottom 403 | transform: rotate(90deg) 404 | opacity: 0 405 | 100% 406 | transform-origin: right bottom 407 | transform: rotate(0) 408 | opacity: 1 409 | 410 | @keyframes rotateOut 411 | 0% 412 | transform-origin: center center 413 | transform: rotate(0) 414 | opacity: 1 415 | 100% 416 | transform-origin: center center 417 | transform: rotate(200deg) 418 | opacity: 0 419 | 420 | @keyframes rotateOutDownLeft 421 | 0% 422 | transform-origin: left bottom 423 | transform: rotate(0) 424 | opacity: 1 425 | 100% 426 | transform-origin: left bottom 427 | transform: rotate(90deg) 428 | opacity: 0 429 | 430 | @keyframes rotateOutUpLeft 431 | 0% 432 | transform-origin: left bottom 433 | transform: rotate(0) 434 | opacity: 1 435 | 100% 436 | transform-origin: left bottom 437 | transform: rotate(-90deg) 438 | opacity: 0 439 | 440 | @keyframes rotateOutDownRight 441 | 0% 442 | transform-origin: right bottom 443 | transform: rotate(0) 444 | opacity: 1 445 | 100% 446 | transform-origin: right bottom 447 | transform: rotate(-90deg) 448 | opacity: 0 449 | 450 | @keyframes rotateOutUpRight 451 | 0% 452 | transform-origin: right bottom 453 | transform: rotate(0) 454 | opacity: 1 455 | 100% 456 | transform-origin: right bottom 457 | transform: rotate(90deg) 458 | opacity: 0 -------------------------------------------------------------------------------- /frontend/src/stylus/axis/breakpoint.styl: -------------------------------------------------------------------------------- 1 | bp(w = 705px, mobile_first = false) 2 | if (mobile_first == mf) 3 | unquote("@media all and (min-width: " + w + ") {") 4 | else 5 | unquote("@media all and (max-width: " + w + ") {") 6 | endbp() 7 | unquote("}") 8 | 9 | // alias 10 | breakpoint = bp 11 | -------------------------------------------------------------------------------- /frontend/src/stylus/axis/buttons.styl: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------- 2 | // Buttons 3 | // ----------------------------------------------------- 4 | // I realize that this is insane, but trust me, it's worth it 5 | 6 | // Mixin: highlight 7 | // Adds a light brighter line across the top of the element for a perspective effect. 8 | // Takes an optional argument for strength, which takes a value between 0 and 1 9 | // ex. highlight 10 | // ex. highlight(.7) 11 | 12 | highlight(strength = .3) 13 | box-shadow(s("inset 0 1px 0 %s", rgba(255,255,255,strength))) 14 | 15 | // This is an internal function used for the button mixins, not intended for use. 16 | 17 | parse(size) 18 | if size == "small" 19 | return 10 20 | else if size == "medium" 21 | return 13 22 | else if size == "large" 23 | return 18 24 | else 25 | return size 26 | 27 | // Mixin: Simple Button 28 | // Creates a very basic looking button style. Two optional params: 29 | // - color: Takes any color, defaults to the default global color 30 | // - size: Takes 'small', 'medium', 'large', or an integer. The numbers reflects the 31 | // font size for the text inside the button, but scales all other aspects as well. 32 | // ex. +simple-button 33 | // ex. +simple-button(blue) 34 | // ex. +simple-button(#D4513B) 35 | // ex. +simple-button(green, large) 36 | // ex. +simple-button(#4C82D4, 24) 37 | 38 | // These will accept font icons interally in the very near future 39 | 40 | simple-button(color = default-color, size = "medium", textColor = null) 41 | 42 | // color detection and changes 43 | textColor = light(color) ? #494949 : #fff unless textColor 44 | parsed_size = parse(size) 45 | 46 | // now that we've got numbers, let's work some proportions 47 | height = (parsed_size * 1.53px) / 2 48 | width = ((parsed_size * 1.53px) * 2.2) / 2 49 | 50 | // dynamically calculated styles 51 | font-size: unit(parsed_size, px) 52 | padding: unit(height, px) unit(width, px) 53 | border-radius: (parsed_size / 4.33333px) 54 | background-color: color 55 | color: textColor 56 | color: rgba(textColor, .95) 57 | 58 | // constant styles 59 | cursor: pointer 60 | font-weight: bold 61 | font-family: font-stack 62 | line-height: 1 63 | text-align: center 64 | text-decoration: none 65 | display: inline-block 66 | 67 | &:hover, &:focus 68 | background-color: darken(color, 5%) 69 | color: darken(textColor, 3%) 70 | color: rgba(darken(textColor, 3%), .95) 71 | 72 | &:active 73 | background-color: darken(color, 10%) 74 | 75 | // Mixin: Button 76 | // Creates a more fancy looking button style. Two optional params: 77 | // - color: Takes any color, defaults to the default global color 78 | // - size: Takes 'small', 'medium', 'large', or an integer. The numbers reflects the 79 | // font size for the text inside the button, but scales all other aspects as well. 80 | // ex. +button 81 | // ex. +button(blue) 82 | // ex. +button(#D4513B) 83 | // ex. +button(green, large) 84 | // ex. +button(#4C82D4, 24) 85 | 86 | // These will accept font icons interally in the very near future 87 | 88 | button(color = default-color, size = "medium", textColor = null, shadowColor = null, hoverColor = null) 89 | 90 | // color detection and changes 91 | // this could be refactored for sure 92 | if textColor 93 | darkText = light(textColor) ? true : false 94 | else 95 | darkText = light(color) ? false : true 96 | textColor = darkText ? white : #494949 97 | 98 | shadowColor = darkText ? rgba(#000,.1) : rgba(#fff,.3) unless shadowColor 99 | hoverColor = darkText ? rgba(#000,.2) : rgba(#fff,.5) unless hoverColor 100 | downShadow = darkText ? 0 -1px 1px rgba(#000,.2) : 1px 1px 1px rgba(#fff,.6) 101 | 102 | // parse words, make sure it's a number 103 | parsed_size = parse(size) 104 | 105 | // now that we've got numbers, let's work some proportions 106 | height = (parsed_size*1.53px)/2 107 | width = ((parsed_size*1.53px)*2.2)/2 108 | 109 | // dynamically calculated styles 110 | font-size: unit(parsed_size, px) 111 | padding: unit(height, px) unit(width, px) 112 | border-radius: (parsed_size/4.33333px) 113 | simple-noise-gradient(color) 114 | text-shadow: 1px 1px 1px shadowColor 115 | border: s('1px solid %s', darken(color, 10%)) 116 | color: textColor 117 | color: rgba(textColor, .95) 118 | 119 | // constant styles 120 | highlight() 121 | cursor: pointer 122 | font-weight: bold 123 | font-family: font-stack 124 | line-height: 1 125 | text-align: center 126 | text-decoration: none 127 | display: inline-block 128 | 129 | &:hover, &:focus 130 | background-position: 0 unit(height * -1, px) 131 | color: darken(textColor, 3%) 132 | color: rgba(darken(textColor, 3%), .95) 133 | text-shadow: 1px 1px 1px hoverColor 134 | 135 | &:active 136 | box-shadow(s("inset 0 1px %s %s", parsed_size/2.6px, rgba(darken(color, 25%),.6))) 137 | text-shadow: downShadow 138 | border-color: darken(color, 18%) 139 | 140 | // Mixin: Button Disabled 141 | // Add this mixin to a button and it will make the button appear to be disabled. 142 | // Easiest to attach to a class like '.disabled' and add that class to the button. 143 | // Takes no arguments. 144 | // ex. .disabled 145 | // +button-disabled 146 | 147 | button-disabled() 148 | background: #ccc 149 | border-color: darken(#ccc, 10%) 150 | opacity: .5 151 | cursor: default 152 | no-select() 153 | &:hover 154 | background: #ccc 155 | background-position: 0 0 156 | &:active 157 | background: #ccc 158 | box-shadow(none) 159 | border-color: darken(#ccc, 10%) 160 | text-shadow: 1px 1px 1px rgba(#000,.1) 161 | 162 | // Additive Mixin: Buttons 163 | // WARNING: Creates classes in your css and styles them - not to be used inside an element. 164 | // This mixin makes it such that you can use classes to define buttons on your page. 165 | // .btn-simple: creates a simple button 166 | // .btn: creates a fancy button 167 | // .disabled: disables an existing button (will only work on .btn and .btn-simple) 168 | 169 | buttons() 170 | .btn-simple 171 | transition() 172 | simple-button(blue) 173 | .btn 174 | transition() 175 | button(blue) 176 | .btn.disabled, .btn-simple.disabled 177 | button-disabled() -------------------------------------------------------------------------------- /frontend/src/stylus/axis/code.styl: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------- 2 | // CODE 3 | // ----------------------------------------------------- 4 | 5 | // Mixin: Code 6 | // Styles inline code snippets on your page. Defaults to a beautiful red, but can be passed any color. 7 | // ex. +code(blue) 8 | // ex. +code(#7ED45F) 9 | 10 | code(color = #DF5C33) 11 | padding: 3px 4px 12 | color: color 13 | background-color: #F5F5F5 14 | border: 1px solid #E1E1E8 15 | border-radius: 3px 16 | font-family: Menlo, Monaco, monospace 17 | 18 | // Mixin: Pre 19 | // Styles code blocks on your page. Sytanx highlighting to be included in the future. 20 | // ex. +pre 21 | 22 | pre() 23 | display: block 24 | padding: 7px 25 | background-color: #F5F5F5 26 | border: 1px solid #E1E1E8 27 | border-radius: 3px 28 | white-space: pre-wrap 29 | word-break: break-all 30 | font-family: Menlo, Monaco, monospace 31 | line-height: 160% 32 | 33 | // Additive Mixin: Code Blocks 34 | // WARNING: Creates classes in your css and styles them - not to be used inside an element. 35 | // Adds roots styles by default to and
 tags.
36 | 
37 | code-blocks()
38 |   code
39 |     code()
40 |   pre
41 |     pre()


--------------------------------------------------------------------------------
/frontend/src/stylus/axis/flex.styl:
--------------------------------------------------------------------------------
  1 | /*
  2 |  * Vendor "display: flex" support with fallback to obsolete versions.
  3 |  * Ported directly from visionmedia/nib
  4 |  */
  5 | 
  6 | flex-version ?= box flex
  7 | 
  8 | //
  9 | // 1. Display values
 10 | //    - http://www.w3.org/TR/css3-flexbox/#flex-containers
 11 | //
 12 | display(type, args...)
 13 |   if flex == type || inline-flex == type
 14 |     display: vendor-value(box args, only: moz ms webkit) if box in flex-version
 15 |     display: vendor-value(arguments, only: webkit official) if flex in flex-version // overwrites old webkit
 16 |   else
 17 |     display: arguments
 18 | 
 19 | /*
 20 |  * New syntax for browsers like Google Chrome.
 21 |  * Plus a translation to the old syntax, if possible.
 22 |  */
 23 | 
 24 | //
 25 | // 5. Ordering and Orientation
 26 | //    - http://www.w3.org/TR/css3-flexbox/#ordering-and-orientation
 27 | //
 28 | -flex-obsolete-direction(direction)
 29 |   if box in flex-version
 30 |     if row-reverse == direction || column-reverse == direction
 31 |       vendor('box-direction', reverse, ignore: official)
 32 | 
 33 |     if row == direction || row-reverse == direction
 34 |       vendor('box-orient', horizontal, ignore: official)
 35 |     else if column == direction || column-reverse == direction
 36 |       vendor('box-orient', vertical, ignore: official)
 37 | 
 38 | -flex-obsolete-wrap(value)
 39 |   if box in flex-version
 40 |     // WARN: wrap-reverse does not have a box equivalent. This will render in different manners
 41 |     //    on box vs. flex values.
 42 |     if 'wrap' == value || wrap-reverse == value
 43 |       vendor('box-lines', multiple, ignore: official)
 44 |     else if nowrap == value
 45 |       vendor('box-lines', single, ignore: official)
 46 | 
 47 | flex-direction(direction)
 48 |   // obsolete
 49 |   -flex-obsolete-direction(direction)
 50 | 
 51 |   // new
 52 |   if flex in flex-version
 53 |     vendor('flex-direction', arguments, only: webkit official)
 54 | 
 55 | flex-wrap(value)
 56 |   // obsolete
 57 |   -flex-obsolete-wrap(value)
 58 | 
 59 |   if flex in flex-version
 60 |     vendor('flex-wrap', arguments, only: webkit official)
 61 | 
 62 | flex-flow()
 63 |   // obsolete
 64 |   -flex-obsolete-direction(arguments[0])
 65 |   -flex-obsolete-direction(arguments[1])
 66 |   -flex-obsolete-wrap(arguments[0])
 67 |   -flex-obsolete-wrap(arguments[1])
 68 | 
 69 |   // new
 70 |   if flex in flex-version
 71 |     vendor('flex-flow', arguments, only: webkit official)
 72 | 
 73 | order()
 74 |   // obsolete
 75 |   if box in flex-version
 76 |     vendor('box-ordinal-group', arguments, ignore: official)
 77 | 
 78 |   // new
 79 |   if flex in flex-version
 80 |     vendor('order', arguments, only: webkit official)
 81 | 
 82 | 
 83 | //
 84 | // 7. Flexibility
 85 | //    - http://www.w3.org/TR/css3-flexbox/#flexibility
 86 | //
 87 | flex-grow(growth)
 88 |   // obsolete
 89 |   if box in flex-version
 90 |     vendor('box-flex', growth)
 91 | 
 92 |   // new
 93 |   if flex in flex-version
 94 |     vendor('flex-grow', arguments, only: webkit official)
 95 | 
 96 | flex-basis()
 97 |   if flex in flex-version
 98 |     vendor('flex-basis', arguments, only: webkit official)
 99 | 
100 | flex-shrink()
101 |   if flex in flex-version
102 |     vendor('flex-shrink', arguments, only: webkit official)
103 | 
104 | flex(growth)
105 | 
106 |   // obsolete
107 |   if box in flex-version
108 |     shrink = 1
109 | 
110 |     if none == growth || initial == growth
111 |       // Well known values
112 |       shrink = 0 if none == growth
113 |       growth = 0
114 |     else if is-width(growth) == true
115 |       // Basis is defined as the first parameter
116 |       growth = arguments[1] || 0
117 |       shrink = arguments[2] if 3 <= length(arguments)
118 |     else if arguments[1] && is-width(arguments[1]) == false
119 |       // Growth is first and shrink is second
120 |       shrink = arguments[1]
121 | 
122 |     // Since we can't make the distinction between growing and shrinking in the box model, take
123 |     // the one that provides the most flexibility.
124 |     box-flex max(growth, shrink)
125 | 
126 |   // new
127 |   if flex in flex-version
128 |     vendor('flex', arguments, only: webkit official)
129 | 
130 | //
131 | // 8. Alignment
132 | //    - http://www.w3.org/TR/css3-flexbox/#alignment
133 | //
134 | justify-content(align)
135 |   // obsolete
136 |   if box in flex-version
137 |     if flex-start == align
138 |       align = start
139 |     else if flex-end == align
140 |       align = end
141 |     else if space-around == align || space-between == align
142 |       // WARN: space-around does not match exactly to the align pack method but this is the
143 |       //    closest option available.
144 |       align = justify
145 |     vendor('box-pack', align, ignore: official)
146 | 
147 |   // new
148 |   if flex in flex-version
149 |     vendor('justify-content', arguments, only: webkit official)
150 | 
151 | align-items(align)
152 |   // obsolete
153 |   if box in flex-version
154 |     if flex-start == align
155 |       align = start
156 |     else if flex-end == align
157 |       align = end
158 |     vendor('box-align', align, ignore: official)
159 | 
160 |   // new
161 |   if flex in flex-version
162 |     vendor('align-items', arguments, only: webkit official)
163 | 
164 | align-self()
165 |   // WARN: Obsolete spec does not allow for overriding alignment on individual items.
166 |   if flex in flex-version
167 |     vendor('align-self', arguments, only: webkit official)
168 | 
169 | align-content()
170 |   // WARN: Obsolete spec does not allow for adjustment here
171 |   if flex in flex-version
172 |     vendor('align-content', arguments, only: webkit official)


--------------------------------------------------------------------------------
/frontend/src/stylus/axis/forms.styl:
--------------------------------------------------------------------------------
  1 | // -----------------------------------------------------
  2 | // Forms
  3 | // -----------------------------------------------------
  4 | 
  5 | // This module is the most severely lacking, just because I'm not totally sure how to
  6 | // handle it - forms are so different. It will probably end up with this basic functionality
  7 | // for custom forms as well as a mixin you can drop on a form and it will style it for you
  8 | // and take care of everything, for something more like an early prototype or back end.
  9 | 
 10 | // It will also contain the ability to append or prepend little icons to your inputs
 11 | 
 12 | // Helper: Focus Glow
 13 | // Makes your inputs glow when focused. Takes a color - they will glow that color. Default color by default.
 14 | // ex. +focus-glow
 15 | // ex. +focus-glow(blue)
 16 | // ex. +focus-glow(#D45D86)
 17 | 
 18 | focus-glow(color = default-color)
 19 |   &:focus
 20 |     box-shadow(0 0 5px rgba(color,.7))
 21 |     border: 1px solid desaturate(color, 35%)
 22 |     outline: none !important
 23 | 
 24 | // Mixin: Input
 25 | // A general purpose mixin for text inputs. Provides an nice attractive default style that's easily
 26 | // customizable. Takes a color as the first param and an optional width as the second.
 27 | // ex. +input
 28 | // ex. +input(purple)
 29 | // ex. +input(#D45D86, 400px)
 30 | 
 31 | input(color = light-blue, width = 250px)
 32 |   box-shadow(inset 0 1px 1px rgba(#000, 0.1))
 33 |   font-size: unit(font-size, px)
 34 |   font-family: font-stack
 35 |   font-size: 90%
 36 |   transition()
 37 |   border: solid 1px #bbb
 38 |   border-radius: 2px
 39 |   outline: none !important
 40 |   padding: 4px 5px 4px // test this and make sure it's proportional at different sizes
 41 |   background: #fff
 42 |   color: #555
 43 |   width: width
 44 |   text-shadow: 0 0 1px rgba(#fff, .1)
 45 |   pie()
 46 |   if color
 47 |     focus-glow(color)
 48 |   else
 49 |     box-shadow(none)
 50 | 
 51 | // Mixin: Input Search
 52 | // A search style input with rounded corners and an optional search icon at the end. Takes
 53 | // any color.
 54 | // ex. +input-search
 55 | // ex. +input-search(yellow)
 56 | // ex. +input-search
 57 | 
 58 | // TODO: Implement search icon option with base64
 59 | 
 60 | // =field-search
 61 | //   position: relative
 62 | //   input
 63 | //     +input-search
 64 | //   &:after
 65 | //     font: 16px roots-icons
 66 | //     content: icon(arrow_down_alt1)
 67 | //     display: block
 68 | //     position: absolute
 69 | //     top: 8px
 70 | //     right: 8px
 71 | //     opacity: .5
 72 | 
 73 | input-search(color = blue)
 74 |   input(color)
 75 |   padding-left: 9px
 76 |   rounded()
 77 | 
 78 | // Mixin: Input Disabled
 79 | // Makes your input appear to be disabled. Note that you also need to add 'disabled' to your
 80 | // html tag in order for it to actually be disabled.
 81 | // ex. +disabled
 82 | 
 83 | input-disabled()
 84 |   cursor: not-allowed
 85 |   background: #F5F5F5 !important
 86 | 
 87 |   &:hover, &:focus
 88 |     border-color: #bbb
 89 |     box-shadow(inset 0 1px 1px rgba(#000, 0.1))
 90 | 
 91 | // Mixin: Label
 92 | // Very basic label for your form. Pass it an optional display type to have it inline or block.
 93 | // ex. +label
 94 | 
 95 | label(display = inline-block)
 96 |   line-height: 1.5em
 97 |   display: display
 98 | 
 99 | // Mixin: Field
100 | // Often times it's easier to wrap your input and label in a div called "field" so they can be
101 | // floated, position, and manipulated without screwing up the rest of the form. That's what this
102 | // mixin is for - put it on a div that contains a label and an input. Then feed it a direction to
103 | // align (default is right), and an optional width.
104 | // ex. +field
105 | // ex. +field(left)
106 | // ex. +field(right, 500px)
107 | 
108 | field(direction = right, width = 370px)
109 |   clear: both
110 |   margin-bottom: 5px
111 |   text-align: direction
112 |   width: width
113 | 
114 | // Mixin: Input Error
115 | // When someone F'd it up, you gotta show them how it is. Put this on an input to make it clear
116 | // that something bad is happening. Best implemented in a .error class or something similar.
117 | // ex. input.error
118 | //       +input-error
119 | 
120 | input-error(color = red)
121 |   color: color
122 |   border-color: color
123 |   focus-glow(color)
124 | 
125 | // Mixin: Field Error
126 | // If you are wrapping labels and inputs in a field div, this makes life even easier. Makes the
127 | // label text as well as the input field red.
128 | // ex. .field.error
129 | //       +field-error
130 | 
131 | field-error(color = red)
132 |   color: color
133 |   input
134 |     input-error()
135 | 
136 | // Mixin: Input Warning
137 | // Ok, so maybe you didn't totally F it up, but at very least you deserve a warning. Well here it is.
138 | // Best implemented in a .warning class or something similar.
139 | // ex. input.warning
140 | //       +input-warning
141 | 
142 | input-warning(color = yellow)
143 |   color: color
144 |   border-color: color
145 |   focus-glow(color)
146 | 
147 | // Mixin: Field Warning
148 | // If you are wrapping labels and inputs in a field div, this makes life even easier. Makes the
149 | // label text as well as the input field yellow.
150 | // ex. .field.warning
151 | //       +field-warning
152 | 
153 | field-warning(color = yellow)
154 |   color: color
155 |   input
156 |     input-warning()
157 | 
158 | // Mixin: Input Success
159 | // http://cl.ly/F4Em/great-success.jpeg - Best implemented in a .success class or something similar.
160 | // ex. input.success
161 | //       +input-success
162 | 
163 | input-success(color = green)
164 |   color: color
165 |   border-color: color
166 |   focus-glow(color)
167 | 
168 | // Mixin: Field Success
169 | // You're probably getting tired of this routine at this point. I'm sure you can imagine what this does.
170 | // ex. .field.success
171 | //       +field-success
172 | 
173 | field-success(color = green)
174 |   color: color
175 |   input
176 |     input-success()
177 | 
178 | // Additive Mixin: Fields
179 | // WARNING: Creates classes in your css and styles them - not to be used inside an element.
180 | // Add the field styles to .field as well as success, warning, and failure states. Also takes
181 | // direction and width. Highly recommended mixin.
182 | 
183 | fields(direction = right, width = 370px)
184 |   .field
185 |     field(direction, width)
186 | 
187 |     &.success
188 |       field-success()
189 |     &.warning
190 |       field-warning()
191 |     &.error
192 |       field-error()
193 | 
194 | // Additive Mixin: Forms
195 | // WARNING: Creates classes in your css and styles them - not to be used inside an element.
196 | // Adds nicer looking styles to all text inputs and textareas. Overrides the defaults.
197 | 
198 | forms()
199 |   input[type='email'],
200 |   input[type='number'],
201 |   input[type='password'],
202 |   input[type='search'],
203 |   input[type='tel'],
204 |   input[type='text'],
205 |   input[type='url'],
206 |   input[type='color'],
207 |   input[type='date'],
208 |   input[type='datetime'],
209 |   input[type='datetime-local'],
210 |   input[type='month'],
211 |   input[type='time'],
212 |   input[type='week'],
213 |   textarea
214 |     input()
215 | 
216 |     &.disabled
217 |       input-disabled()
218 |     &.success
219 |       input-success()
220 |     &.warning
221 |       input-warning()
222 |     &.error
223 |       input-error()
224 | 
225 | // shortcut to render all html5 input types
226 | html5-inputs = unquote("input[type='email'], input[type='number'], input[type='password'], input[type='search'], input[type='tel'], input[type='text'], input[type='url'], input[type='color'], input[type='date'], input[type='datetime'], input[type='datetime-local'], input[type='month'], input[type='time'], input[type='week']")
227 | 


--------------------------------------------------------------------------------
/frontend/src/stylus/axis/gradients.styl:
--------------------------------------------------------------------------------
  1 | // -----------------------------------------------------
  2 | // Gradients
  3 | // -----------------------------------------------------
  4 | // linear-gradient() ported from visionmedia/nib
  5 | 
  6 | 
  7 | // Replace the given str with val in the expr.
  8 | -replace(expr, str, val)
  9 |   expr = clone(expr)
 10 |   for e, i in expr
 11 |     if length(e) > 1
 12 |       expr[i] = -replace(e, str, val)
 13 |     else
 14 |       if str == e
 15 |         expr[i] = val
 16 |   expr
 17 | 
 18 | // Normalize gradient points.
 19 | -grad-point(pos)
 20 |   if length(pos) == 1
 21 |     return left pos if pos in (top bottom)
 22 |     return pos top if pos in (left right)
 23 |   else if pos[0] in (top bottom)
 24 |     pos[1] pos[0]
 25 |   else
 26 |     pos
 27 | 
 28 | // Implicit color stop position.
 29 | -pos-in-stops(i, stops)
 30 |   len = length(stops)
 31 |   if len - 1 == i
 32 |     100%
 33 |   else if i
 34 |     unit(i / len * 100, '%')
 35 |   else
 36 |     0%
 37 | 
 38 | // Normalize color stops:
 39 | // - (color pos) -> (pos color)
 40 | // - (color) -> (implied-pos color)
 41 | -normalize-stops(stops)
 42 |   stops = clone(stops)
 43 |   for stop, i in stops
 44 |     if length(stop) == 1
 45 |       color = stop[0]
 46 |       stop[0] = -pos-in-stops(i, stops)
 47 |       stop[1] = color
 48 |     else if typeof(stop[1]) == 'unit'
 49 |       pos = stop[1]
 50 |       stop[1] = stop[0]
 51 |       stop[0] = pos
 52 |   stops
 53 | 
 54 | // Join color stops with the given translation function.
 55 | -join-stops(stops, translate)
 56 |   str = ''
 57 |   len = length(stops)
 58 |   for stop, i in stops
 59 |     str += ', ' if i
 60 |     pos = stop[0]
 61 |     color = stop[1]
 62 |     str += translate(color, pos)
 63 |   unquote(str)
 64 | 
 65 | // Legacy Webkit color stop.
 66 | -webkit-stop(color, pos)
 67 |   'color-stop(%d, %s)' % (pos / 100 color)
 68 | 
 69 | // Standard color stop.
 70 | -std-stop(color, pos)
 71 |   '%s %s' % (color pos)
 72 | 
 73 | // Create a linear gradient with the given start position
 74 | // and variable number of color stops.
 75 | 
 76 | // Examples:
 77 | //   background: linear-gradient(top, red, green, blue)
 78 | //   background: linear-gradient(bottom, red, green 50%, blue)
 79 | //   background: linear-gradient(bottom, red, 50% green, blue)
 80 | //   background: linear-gradient(bottom, red, 50% green, 90% white, blue)
 81 | 
 82 | linear-gradient(start, stops...)
 83 |   error('color stops required') unless length(stops)
 84 |   prop = current-property[0]
 85 |   val = current-property[1]
 86 | 
 87 |   if start is a 'color'
 88 |     unshift(stops, start)
 89 |     start = top
 90 | 
 91 |   stops = -normalize-stops(stops)
 92 | 
 93 |   // gradient image
 94 |   if start[0] is a 'unit'
 95 |     start = start[1]
 96 | 
 97 |   // legacy webkit
 98 |   end = -grad-point(opposite-position(start))
 99 |   webkit-legacy = '-webkit-gradient(linear, %s, %s, %s)' % (-grad-point(start) end -join-stops(stops, -webkit-stop))
100 |   add-property(prop, -replace(val, '__CALL__', webkit-legacy))
101 | 
102 |   // vendor prefixed
103 |   stops = -join-stops(stops, -std-stop)
104 |   for prefix in vendors
105 |     unless prefix == official
106 |       gradient = '-%s-linear-gradient(%s, %s)' % (prefix start stops)
107 |       add-property(prop, -replace(val, '__CALL__', gradient))
108 | 
109 |   // standard
110 |   'linear-gradient(%s, %s)' % (start stops)
111 | 
112 | // Mixin: Gradient
113 | // Takes two colors and outputs a vertical gradient that fades between the two colors.
114 | // ex. gradient(#EB8468, #BA6952)
115 | 
116 | gradient(color1, color2)
117 |   background: color1 
118 |   background: color2 linear-gradient(top, color1, color2) repeat-x
119 |   if pie-enabled
120 |     -pie-background: unquote('linear-gradient(top,') color1 unquote(' 0%, ') color2 unquote('color2 100%)')
121 |     pie()
122 | 
123 | // Mixin: Simple Gradient
124 | // Takes one color and an optional strength. Darkens and lightens the color according to the strength
125 | // and creates a gradient between those two values, the average being the originally passed color.
126 | // Higher strength will result in a more strongly contrasted gradient
127 | // ex. simple-gradient(#EB8468)
128 | // ex. simple-gradient(#61D464, 20%)
129 | 
130 | simple-gradient(color, strength = 10%)
131 |   start = lighten(color, strength)
132 |   end = darken(color, strength)
133 |   gradient(start, end)
134 | 
135 | // Mixin: Noise Gradient
136 | // Exactly the same as the gradient mixin, but overlays a very light noise to give it more texture.
137 | // ex. noise-gradient(#EB8468, #BA6952)
138 | 
139 | noise-gradient(color1, color2, imagePath = 'noise.png')
140 |   background: color1 
141 |   background: url(img-path imagePath) repeat, color2 linear-gradient(top, color1, color2) repeat-x
142 |   if pie-enabled
143 |     -pie-background: unquote('linear-gradient(top,') color1 unquote(' 0%, ') color2 unquote('color2 100%)')
144 |     pie()
145 | 
146 | // Mixin: Simple Noise Gradient
147 | // Exactly like simple gradient, but overlays a very light noise to give it more texture.
148 | // ex. simple-noise-gradient(#EB8468, #BA6952)
149 | 
150 | simple-noise-gradient(color, strength = 10%, imagePath = 'noise.png')
151 |   start = lighten(color, strength)
152 |   end = darken(color, strength)
153 |   noise-gradient(start, end, imagePath)
154 | 
155 | // Mixin: Radial Gradient
156 | // Exactly like linear gradient, but has no sense of direction
157 | // ex. radial-gradient(red, blue)
158 | 
159 | radial-gradient(stops...)
160 |   error('color stops required') unless length(stops)
161 |   prop = current-property[0]
162 |   val = current-property[1]
163 |   stops = -normalize-stops(stops)
164 |   webkit-legacy = '-webkit-gradient(radial, center center 0px, center center 100%, %s, %s)' % -join-stops(stops, -webkit-stop)
165 |   add-property(prop, -replace(val, '__CALL__', webkit-legacy))
166 |   stops = -join-stops(stops, -std-stop)
167 |   for prefix in vendors
168 |     unless prefix is official
169 |       gradient = '-%s-radial-gradient(center, ellipse cover, %s)' % (prefix stops)
170 |       add-property(prop, -replace(val, '__CALL__', gradient))
171 |   'radial-gradient(ellipse at center, %s)' % stops


--------------------------------------------------------------------------------
/frontend/src/stylus/axis/grid.styl:
--------------------------------------------------------------------------------
  1 | /*  Syntax quick reference
  2 |  *  ----------------------
  3 |  *  gutter[g] = 1
  4 |  *  center(max_width = 1410px, padding = 15px)
  5 |  *  column[col](ratios = 1, offset = false)
  6 |  *  span(ratio = 1, offset = false)
  7 |  *  shift(ratios = 0)
  8 |  *  unshift()
  9 |  *  bp(w = 705px, mobile_first = false[mf])
 10 |  *  endbp()
 11 |  *  stack(padding = 0, align = center[c, left, l, right, r])
 12 |  *  edit()
 13 |  *  align(direction = both[b, vertical, v, horizontal, h])
 14 |  */
 15 | 
 16 | // Mixins
 17 | center(max_width = 1410px, padding = 15px)
 18 |     group()
 19 |     max-width max_width
 20 |     float none
 21 |     display block
 22 |     margin-right auto
 23 |     margin-left auto
 24 |     padding-left padding 
 25 |     padding-right padding 
 26 | 
 27 | column(ratios = 1, offset = 0, cycle = 0, uncycle = 0)
 28 |     side = get_layout_direction()
 29 |     widths = get_column(ratios)
 30 |     margin_l = margin_last = 0
 31 |     margin_r = widths[1]
 32 |     unless offset == 0
 33 |         if offset < 0
 34 |             offset *= -1
 35 |             offset = get_column(offset, widths[1])[0]
 36 |             margin_r = margin_last = offset + widths[1] * 2
 37 |         else
 38 |             offset = get_column(offset, widths[1])[0]
 39 |             margin_l = offset + widths[1]
 40 |             
 41 |     group()
 42 |     float side
 43 |     width (widths[0])%
 44 |     margin-{side} (margin_l)%
 45 |     margin-{opposite-position(side)} (margin_r)%
 46 |     if uncycle != 0
 47 |         &:nth-child({uncycle}n)
 48 |             margin-{opposite-position(side)} (margin_r)%
 49 |             float side
 50 |     if cycle != 0
 51 |         &:nth-child({cycle}n)
 52 |             margin-{opposite-position(side)} (margin_last)%
 53 |             float opposite-position(side)
 54 |     else
 55 |         &:last-child
 56 |             margin-{opposite-position(side)} (margin_last)%
 57 | 
 58 | span(ratio = 1, offset = 0)
 59 |     side = get_layout_direction()
 60 |     width = get_span(ratio)
 61 |     margin_l = margin_r = 0
 62 |     unless offset == 0
 63 |         if offset < 0
 64 |             offset *= -1
 65 |             margin_r = get_span(offset)
 66 |         else
 67 |             margin_l = get_span(offset)
 68 |     group()
 69 |     float side
 70 |     width (width)%
 71 |     margin-{side} (margin_l)%
 72 |     margin-{opposite-position(side)} (margin_r)%
 73 | 
 74 | shift(ratios = 0, col_or_span = column)
 75 |     side = get_layout_direction()
 76 |     if side == right
 77 |         ratios = replace_nth(ratios, 0, ratios[0] * -1)
 78 |     if col_or_span == column or col_or_span == col or col_or_span == c
 79 |         widths = get_column(ratios)
 80 |         translate = widths[0] + widths[1]
 81 |     else
 82 |         translate = get_span(ratios)
 83 |     position relative
 84 |     left (translate)%
 85 | 
 86 | unshift()
 87 |     position static
 88 |     left 0
 89 | 
 90 | bp(w = 705px, mobile_first = false)
 91 |     if (mobile_first == mf || mobile_first == true)
 92 |         unquote("@media all and (min-width: " + w + ") {")
 93 |     else
 94 |         unquote("@media all and (max-width: " + w + ") {")
 95 | endbp()
 96 |     unquote("}")
 97 | 
 98 | stack(padding = 0, align = center)
 99 |     side = get_layout_direction()
100 |     display block
101 |     float none
102 |     width 100%
103 |     margin-left auto
104 |     margin-right auto
105 |     if padding != 0
106 |         padding-left padding
107 |         padding-right padding  
108 |     &:first-child
109 |         margin-{side} auto
110 |     &:last-child
111 |         margin-{opposite-position(side)} auto
112 |     if (align == center) or (align == c)
113 |         text-align center
114 |     if (align == left) or (align == l)
115 |         text-align left
116 |     if (align == right) or (align == r)
117 |         text-align right
118 | 
119 | align(direction = both) // IE10+
120 |     if (direction == both) or (direction == b)
121 |         display -webkit-box
122 |         display -moz-box
123 |         display -ms-flexbox
124 |         display -webkit-flex
125 |         display flex
126 |         -webkit-box-align center
127 |         -webkit-flex-align center
128 |         -ms-flex-align center
129 |         -webkit-align-items center
130 |         align-items center
131 |         -webkit-box-pack center
132 |         -moz-box-pack center
133 |         -webkit-justify-content center
134 |         -ms-flex-pack center
135 |         justify-content center
136 |     if (direction == vertical) or (direction == v)
137 |         display -webkit-box
138 |         display -moz-box
139 |         display -ms-flexbox
140 |         display -webkit-flex
141 |         display flex
142 |         -webkit-box-align center
143 |         -webkit-flex-align center
144 |         -ms-flex-align center
145 |         -webkit-align-items center
146 |         align-items center
147 |     if (direction == horizontal) or (direction == h)
148 |         display -webkit-box
149 |         display -moz-box
150 |         display -ms-flexbox
151 |         display -webkit-flex
152 |         display flex
153 |         -webkit-box-pack center
154 |         -moz-box-pack center
155 |         -webkit-justify-content center
156 |         -ms-flex-pack center
157 |         justify-content center
158 | 
159 | // Grid utilities
160 | get_span(ratio = 1)
161 |     return ratio * 100
162 | 
163 | get_column(ratios = 1, g = gutter)
164 |     ratios = reverse(ratios) unless parent_first is true
165 |     w = 100
166 |     for ratio in ratios
167 |         g = g / w * 100
168 |         w = 100 * ratio - g + ratio * g
169 |     return w g
170 | 
171 | get_layout_direction()
172 |     layout_direction == RTL ? result = right : result = left
173 |     return result
174 | 
175 | replace_nth(list, index, value)
176 |     result = ()
177 |     index = length(list) + index if index < 0
178 |     for i in (0..(length(list) - 1))
179 |         if i == index
180 |             append(result, value)
181 |         else
182 |             append(result, list[i])
183 |     return result
184 | 
185 | reverse(list)
186 |     result = ()
187 |     for item in list
188 |         prepend(result, item)
189 |     return result
190 | 
191 | // Function aliases
192 | g = gutter
193 | col = column
194 | 
195 | // Fancy editing - root level mixin
196 | edit()
197 |   *
198 |     transition 200ms ease all
199 |     background rgba(#000, .15)
200 | 
201 | // Resets and defaults - root level mixin
202 | grid()
203 |   *
204 |     box-sizing(border-box)
205 |     *behavior: url(box-sizing-path)
206 | 
207 |   html
208 |     overflow-y: scroll
209 |     overflow-x: hidden
210 | 
211 |   body
212 |     width: 100%
213 | 
214 |   img, video, audio, embed, object, input, iframe
215 |     max-width: 100%
216 | 


--------------------------------------------------------------------------------
/frontend/src/stylus/axis/index.styl:
--------------------------------------------------------------------------------
 1 | // -------------------------------------------------------
 2 | // Importing all the files we need
 3 | // -------------------------------------------------------
 4 | 
 5 | @import 'settings'
 6 | @import 'reset'
 7 | @import 'vertical-rhythm'
 8 | @import 'vendor'
 9 | @import 'utilities'
10 | @import 'gradients'
11 | @import 'interaction'
12 | @import 'positions'
13 | @import 'typography'
14 | @import 'code'
15 | @import 'ui'
16 | @import 'buttons'
17 | @import 'forms'
18 | @import 'tables'
19 | @import 'grid'
20 | @import 'flex'
21 | @import 'breakpoint'
22 | 
23 | // -------------------------------------------------------
24 | // Framework Mixin (loads all default styles)
25 | // -------------------------------------------------------
26 | 
27 | framework()
28 |   base()
29 |   typography()
30 |   forms()
31 |   buttons()
32 |   code-blocks()
33 |   tables()
34 | 


--------------------------------------------------------------------------------
/frontend/src/stylus/axis/interaction.styl:
--------------------------------------------------------------------------------
  1 | // -----------------------------------------------------
  2 | // Interaction
  3 | // -----------------------------------------------------
  4 | 
  5 | // Mixin: Hover Darken
  6 | // Darkens the color and/or background color on your element when it's hovered.
  7 | // Takes an optional percentage to darken it.
  8 | // ex. hover-darken()
  9 | // ex. hover-darken: 30%
 10 | 
 11 | hover-darken(percent = 15%, force-use-color = false)
 12 |   &:hover
 13 |     if @background and !force-use-color
 14 |       background-color: darken(@background, percent)
 15 |     else if @background-color and !force-use-color
 16 |       background-color: darken(@background-color, percent)
 17 |     else if @color
 18 |       color: darken(@color, percent)
 19 |     else
 20 |       warn('you need to set a color or background color on your element for this to work')
 21 | 
 22 | // Mixin: Hover Lighten
 23 | // Works the same way as hover darken but lightens the color
 24 | 
 25 | hover-lighten(percent = 15%, force-use-color = false)
 26 |   &:hover
 27 |     if @background and !force-use-color
 28 |       background-color: lighten(@background, percent)
 29 |     else if @background-color and !force-use-color
 30 |       background-color: lighten(@background-color, percent)
 31 |     else if @color
 32 |       color: lighten(@color, percent)
 33 |     else
 34 |       warn('you need to set a color or background color on your element for this to work')
 35 | 
 36 | // Mixin: Hover Underline
 37 | // This one is interesting, and may need tweaking to get it to work right on the
 38 | // intended element. Works very nicely for text, like in a span, and can animate.
 39 | // ex. hover-underline()
 40 | // ex. hover-underline: 2px dotted
 41 | 
 42 | hover-underline(border-size = 1px, type = solid, color = null)
 43 |   border-bottom: border-size type transparent
 44 |   &:hover
 45 |     border-bottom: border-size type color
 46 |     
 47 | // Mixin: Hover Pop
 48 | // On hover, your element pops out from the page. For scale, it takes an integer or float,
 49 | // 1 represents 100%, 2 is 200%, etc. Optionally can also rotate, pass it a number followed by
 50 | // "deg", like 180deg. If you pass true for shadow, it will animate in a drop shadow to add to
 51 | // the effect
 52 | // ex. hover-pop: 1.5
 53 | // ex. hover-pop: 2.6 90deg
 54 | // ex. hover-pop: 1.2 45deg true
 55 | 
 56 | hover-pop(scale = 1.2, rotate = null, shadow = null)
 57 |   if shadow
 58 |     box-shadow(0 0 1px transparent)
 59 |   &:hover
 60 |     position: relative
 61 |     z-index: 10
 62 |     transform: scale(scale)
 63 |     if shadow
 64 |       box-shadow(0 0 5px rgba(#000, .3))
 65 |     if rotate
 66 |       transform: scale(scale) rotate(rotate)
 67 | 
 68 | // Mixin: Hover Fade
 69 | // On hover, fades the element's opacity down. Takes an amount as an integer between
 70 | // 0 and 1, like opacity normally does. Recommended to be used with transition.
 71 | // ex. hover-fade: .5
 72 | 
 73 | hover-fade(amount = .5)
 74 |   &:hover
 75 |     opacity: amount
 76 | 
 77 | // Mixin: Hover Color
 78 | // Will swap an elements color or background color on hover. Takes a color in any format
 79 | // as the first argument. Will first look for a background color to change, then a color.
 80 | // ex. hover-color: #D45D86
 81 | 
 82 | hover-color(color, force-use-color = false)
 83 |   &:hover
 84 |     if @background and !force-use-color
 85 |       background-color: color
 86 |     else if @background-color and !force-use-color
 87 |       background-color: color
 88 |     else if @background-color
 89 |       background-color: color
 90 |     else if @color
 91 |       color: color
 92 |     else
 93 |       warn('you need to set a color or background color on your element for this to work')
 94 | 
 95 | 
 96 | // To add:
 97 | // *+click-down(pixels)*
 98 | // *+click-shadow(radius)*
 99 | // *+button-interaction(color)*
100 | 


--------------------------------------------------------------------------------
/frontend/src/stylus/axis/positions.styl:
--------------------------------------------------------------------------------
  1 | // Direct port from the brilliant utility for nib
  2 | // https://github.com/visionmedia/nib/blob/master/lib/nib/positions.styl
  3 | 
  4 | -keys(props)
  5 | 
  6 |   props = arguments if length(arguments) > 1
  7 |   keys  = ()
  8 | 
  9 |   for prop in props
 10 |     unless prop is a 'unit'
 11 |       push(keys, prop)
 12 | 
 13 |   return keys
 14 | 
 15 | -vals(props)
 16 | 
 17 |   props  = arguments if length(arguments) > 1
 18 |   vals   = ()
 19 |   length = length(props)
 20 | 
 21 |   for prop, i in props
 22 |     if prop is a 'unit'
 23 |         push(vals, prop)
 24 |     else
 25 |       if previous is defined
 26 |         unless previous is a 'unit'
 27 |           push(vals,0)
 28 | 
 29 |       if (i+1) == length
 30 |         push(vals,0)
 31 | 
 32 |     previous = prop
 33 | 
 34 |   return vals
 35 | 
 36 | -pos(type, args)
 37 | 
 38 |   keys = -keys(args)
 39 |   vals = -vals(args)
 40 | 
 41 |   position: unquote(type)
 42 | 
 43 |   for key, i in keys
 44 |     {key}: vals[i];
 45 | 
 46 | /*
 47 |  * Position utility.
 48 |  * 
 49 |  * Synopsis:
 50 |  * 
 51 |  *   fixed:  [n]  [n]
 52 |  * 
 53 |  * Examples:
 54 |  * 
 55 |  *     fixed: top left
 56 |  *     fixed: top 5px left
 57 |  *     fixed: top left 5px
 58 |  *     fixed: top 5px left 5px
 59 |  * 
 60 |  */
 61 | 
 62 | fixed()
 63 |   -pos('fixed', arguments)
 64 | 
 65 | /*
 66 |  * Position utility.
 67 |  * 
 68 |  * Synopsis:
 69 |  * 
 70 |  *   absolute:  [n]  [n]
 71 |  * 
 72 |  * Examples:
 73 |  * 
 74 |  *     absolute: top left
 75 |  *     absolute: top 5px left
 76 |  *     absolute: top left 5px
 77 |  *     absolute: top 5px left 5px
 78 |  * 
 79 |  */
 80 | 
 81 | absolute()
 82 |   -pos('absolute', arguments)
 83 | 
 84 | /*
 85 |  * Position utility.
 86 |  * 
 87 |  * Synopsis:
 88 |  * 
 89 |  *   relative:  [n]  [n]
 90 |  * 
 91 |  * Examples:
 92 |  * 
 93 |  *     relative: top left
 94 |  *     relative: top 5px left
 95 |  *     relative: top left 5px
 96 |  *     relative: top 5px left 5px
 97 |  * 
 98 |  */
 99 | 
100 | relative()
101 |   -pos('relative', arguments)


--------------------------------------------------------------------------------
/frontend/src/stylus/axis/reset.styl:
--------------------------------------------------------------------------------
  1 | support-for-ie ?= true
  2 | 
  3 | // ---------------------------------------
  4 | // COMPLETE RESET
  5 | // ---------------------------------------
  6 | 
  7 | // Based on [Eric Meyer's reset](http://meyerweb.com/eric/thoughts/2007/05/01/reset-reloaded/)
  8 | 
  9 | global-reset()
 10 |   html
 11 |   body
 12 |   div
 13 |   span
 14 |   applet
 15 |   object
 16 |   iframe
 17 |   h1
 18 |   h2
 19 |   h3
 20 |   h5
 21 |   h6
 22 |   p
 23 |   blockquote
 24 |   pre
 25 |   a
 26 |   abbr
 27 |   acronym
 28 |   address
 29 |   big
 30 |   cite
 31 |   code
 32 |   del
 33 |   dfn
 34 |   em
 35 |   img
 36 |   ins
 37 |   kbd
 38 |   q
 39 |   s
 40 |   samp
 41 |   small
 42 |   strike
 43 |   strong
 44 |   sub
 45 |   sup
 46 |   tt
 47 |   var
 48 |   dl
 49 |   dt
 50 |   dd
 51 |   ol
 52 |   ul
 53 |   li
 54 |   fieldset
 55 |   form
 56 |   label
 57 |   legend
 58 |   caption
 59 |   tbody
 60 |   tfoot
 61 |   thead
 62 |   tr
 63 |   th
 64 |   td
 65 |     font-weight: inherit
 66 |     font-style: inherit
 67 |     font-family: inherit
 68 |     font-size: 100%
 69 |     vertical-align: baseline
 70 |     margin: 0
 71 |     padding: 0
 72 |     outline: 0
 73 |     border: none
 74 |   body
 75 |     line-height: 1
 76 |   ol, ul
 77 |     list-style: none
 78 |   // table reset
 79 |   table
 80 |     border-collapse: separate
 81 |     border-spacing: 0
 82 |     vertical-align: middle
 83 |   caption, th, td
 84 |     text-align: left
 85 |     font-weight: normal
 86 |     vertical-align: middle
 87 |   a img
 88 |     border: none
 89 |   // html5 reset
 90 |   article
 91 |   aside
 92 |   canvas
 93 |   details
 94 |   figcaption
 95 |   figure
 96 |   footer
 97 |   header
 98 |   hgroup
 99 |   menu
100 |   nav
101 |   section
102 |   summary
103 |     margin: 0
104 |     padding: 0
105 |     border: none
106 |     outline: 0
107 |     display: block
108 |   audio
109 |   canvas
110 |   video
111 |     display: inline-block
112 |     *display: inline
113 |     *zoom: 1
114 |   audio:not([controls])
115 |   [hidden]
116 |     display: none
117 | 
118 | // ---------------------------------------
119 | // NORMALIZE
120 | // ---------------------------------------
121 | 
122 | // Combination of v1 (IE 6/7 fixes) and v2
123 | // Ported from JohnAlbin/normalize.css-with-sass-or-compass and modified heavily
124 | 
125 | normalize()
126 |   article
127 |   aside
128 |   details
129 |   figcaption
130 |   figure
131 |   footer
132 |   header
133 |   hgroup
134 |   main
135 |   nav
136 |   section
137 |   summary
138 |     display: block
139 | 
140 |   audio
141 |   canvas
142 |   video
143 |     display: inline-block
144 |     if support-for-ie
145 |       *display: inline
146 |       *zoom: 1
147 | 
148 |   audio:not([controls])
149 |     display: none
150 |     height: 0
151 |   [hidden]
152 |     display: none
153 | 
154 |   html
155 |     font-family: sans-serif
156 |     -webkit-text-size-adjust: 100%
157 |     -ms-text-size-adjust: 100%
158 |     font-size: 100%
159 | 
160 |   if support-for-ie
161 |     button
162 |     input
163 |     select
164 |     textarea
165 |       font-family: sans-serif
166 | 
167 |   body
168 |     margin: 0
169 | 
170 |   a:focus
171 |     outline: thin dotted
172 | 
173 |   a:active
174 |   a:hover
175 |     outline: 0
176 | 
177 |   p
178 |   pre
179 |     margin: 1em 0
180 | 
181 |   blockquote
182 |     margin: 1em 40px
183 | 
184 |   h1
185 |     font-size: 2em
186 |     margin: 0.67em 0
187 | 
188 |   h2
189 |     font-size: 1.5em
190 |     margin: 0.83em 0
191 | 
192 |   h3
193 |     font-size: 1.17em
194 |     margin: 1em 0
195 | 
196 |   h4
197 |     font-size: 1em
198 |     margin: 1.33em 0
199 | 
200 |   h5
201 |     font-size: 0.83em
202 |     margin: 1.67em 0
203 | 
204 |   h6
205 |     font-size: 0.67em
206 |     margin: 2.33em 0
207 | 
208 |   abbr[title]
209 |     border-bottom: 1px dotted
210 | 
211 |   b
212 |   strong
213 |     font-weight: bold
214 | 
215 |   dfn
216 |     font-style: italic
217 | 
218 |   hr
219 |     -moz-box-sizing: content-box
220 |     box-sizing: content-box
221 |     height: 0
222 | 
223 |   mark
224 |     background: #ff0
225 |     color: #000
226 | 
227 |   code
228 |   kbd
229 |   pre
230 |   samp
231 |     font-family: monospace, serif
232 |     font-size: 1em
233 |     if support-for-ie
234 |       _font-family: 'courier new', monospace
235 | 
236 |   pre
237 |     white-space: pre-wrap
238 | 
239 |   q
240 |     quotes: "\201C" "\201D" "\2018" "\2019"
241 | 
242 |   small
243 |     font-size: 80%
244 | 
245 |   sub
246 |   sup
247 |     font-size: 75%
248 |     line-height: 0
249 |     position: relative
250 |     vertical-align: baseline
251 | 
252 |   sup
253 |     top: -0.5em
254 | 
255 |   sub
256 |     bottom: -0.25em
257 | 
258 |   dl
259 |   menu
260 |   ol
261 |   ul
262 |     margin: 1em 0
263 |     padding: 0 0 0 40px
264 | 
265 |   dl
266 |     padding: 0
267 | 
268 |   dd
269 |     margin: 0 0 0 40px
270 | 
271 |   if support-for-ie
272 |     nav
273 |       ul
274 |       ol
275 |         list-style: none
276 |         list-style-image: none
277 | 
278 |   img
279 |     border: 0
280 |     if support-for-ie
281 |       -ms-interpolation-mode: bicubic
282 | 
283 |   svg:not(:root)
284 |     overflow: hidden
285 | 
286 |   figure
287 |     margin: 0
288 | 
289 |   if support-for-ie
290 |     form
291 |       margin: 0
292 | 
293 |   fieldset
294 |     border-color: #c0c0c0
295 |     margin: 0 2px
296 |     border: 1px solid #c0c0c0
297 |     padding: 0.35em 0.625em 0.75em
298 | 
299 |   legend
300 |     border: 0
301 |     padding: 0
302 |     if support-for-ie
303 |       *margin-left: -7px
304 | 
305 |   button
306 |   input
307 |   select
308 |   textarea
309 |     font-family: inherit
310 |     font-size: 100%
311 |     margin: 0
312 |     if support-for-ie
313 |       vertical-align: baseline
314 |       *vertical-align: middle
315 | 
316 |   button
317 |   input
318 |     line-height: normal
319 | 
320 |   button
321 |   select
322 |     text-transform: none
323 | 
324 |   button, html input[type="button"], input[type="reset"], input[type="submit"]
325 |     -webkit-appearance: button
326 |     cursor: pointer
327 |     if support-for-ie
328 |       *overflow: visible
329 | 
330 |   button[disabled], html input[disabled]
331 |     cursor: default
332 | 
333 |   input[type="checkbox"], input[type="radio"]
334 |     box-sizing: border-box
335 |     padding: 0
336 |     if support-for-ie
337 |       *height: 13px
338 |       *width: 13px
339 | 
340 |   input[type="search"]
341 |     -webkit-appearance: textfield
342 |     box-sizing: content-box
343 | 
344 |   input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration
345 |     -webkit-appearance: none
346 | 
347 |   button::-moz-focus-inner
348 |   input::-moz-focus-inner
349 |     border: 0
350 |     padding: 0
351 | 
352 |   textarea
353 |     overflow: auto
354 |     vertical-align: top
355 | 
356 |   table
357 |     border-collapse: collapse
358 |     border-spacing: 0


--------------------------------------------------------------------------------
/frontend/src/stylus/axis/settings.styl:
--------------------------------------------------------------------------------
 1 | //////////////////////////////////////////////////////////
 2 | // Global settings - adjust these to your heart's desire//
 3 | //////////////////////////////////////////////////////////
 4 | 
 5 | // font stack (add your own!)
 6 | helvetica-neue = "Helvetica Neue", HelveticaNeue, Helvetica, Arial, sans-serif
 7 | helvetica = "Helvetica Neue", Helvetica, Arial, sans-serif
 8 | georgia = Georgia, Cambria, "Times New Roman", Times, serif
 9 | lucidia-grande = "Lucida Grande", Tahoma, Verdana, Arial, sans-serif
10 | monospace = unquote("'Bitstream Vera Sans Mono', Consolas, Courier, monospace")
11 | verdana = Verdana, Geneva, sans-serif
12 | 
13 | // default font stack
14 | font-stack = helvetica-neue
15 | font-size = 16
16 | font-color = #555
17 | 
18 | // ligatures
19 | ligatures = false
20 | 
21 | // vertical rhythm
22 | base-line-height = 24px
23 | default-rhythm-border-style = solid
24 | relative-font-sizing = true
25 | round-to-nearest-half-line = false
26 | min-line-padding = 2px
27 | 
28 | // colors
29 | red = #B44326
30 | orange = #F2A34F
31 | yellow = #F8CA5C
32 | green = #7FC028
33 | light-blue = #52D7FE
34 | blue = #00a6fc
35 | purple = #8E48C2
36 | white = #fff
37 | black = #272727
38 | 
39 | // default color
40 | default-color = blue
41 | 
42 | // text highlight color
43 | highlight-color = blue
44 | 
45 | // custom image base path for roots mixins
46 | img-path = ''
47 | 
48 | // Support for Internet Explorer flag
49 | support-for-ie = true
50 | 
51 | // progressive internet explorer (http://css3pie.com/)
52 | pie-enabled = false
53 | pie-path = '/pie.htc'
54 | 
55 | // box-sizing for internet explorer (https://github.com/Schepp/box-sizing-polyfill)
56 | box-sizing-enabled = false
57 | box-sizing-path = '/boxsizing.htc'
58 | 
59 | // grid system settings (http://jeetframework.com)
60 | gutter = 3
61 | parent_first = false
62 | layout_direction = LTR
63 | 


--------------------------------------------------------------------------------
/frontend/src/stylus/axis/tables.styl:
--------------------------------------------------------------------------------
 1 | // Mixin: Table
 2 | // Ported directly from Twitter Bootstrap, as they did an excellent job with the tables.
 3 | // Take three fairly self-explanitory arguments, all boolean. 1st makes the table bordered,
 4 | // 2nd adds zebra striping, and the 3rd indiated whether the table is condensed or not.
 5 | 
 6 | // TODO: Add options to customize colors
 7 | 
 8 | table(border = true, striped = true, condensed = false)
 9 | 
10 |   max-width: 100%
11 |   border-collapse: collapse
12 |   border-spacing: 0
13 |   width: 100%
14 |   margin-bottom: 18px
15 | 
16 |   th, td
17 |     padding: 8px
18 |     line-height: 18px
19 |     text-align: left
20 |     vertical-align: top
21 |     border-top: 1px solid #ddd
22 | 
23 |   th
24 |     font-weight: bold
25 | 
26 |   thead th
27 |     vertical-align: bottom
28 | 
29 |   thead:first-child tr th
30 |   thead:first-child tr td
31 |     border-top: 0
32 | 
33 |   tbody + tbody
34 |     border-top: 2px solid #ddd
35 | 
36 |   if condensed
37 |     th, td
38 |       padding: 4px 5px
39 | 
40 |   if border
41 |     border: 1px solid #ddd
42 |     border-collapse: separate
43 |     *border-collapse: collapsed
44 |     round(4px)
45 | 
46 |     @css {
47 |       th + th, td + td, th + td, td + th { border-left: 1px solid #ddd; }
48 |     }
49 | 
50 |     thead:first-child tr:first-child th
51 |     tbody:first-child tr:first-child th
52 |     tbody:first-child tr:first-child td
53 |       border-top: 0
54 | 
55 |     thead:first-child tr:first-child th:first-child
56 |     tbody:first-child tr:first-child td:first-child
57 |       round(4px 0 0 0)
58 | 
59 |     thead:first-child tr:first-child th:last-child
60 |     tbody:first-child tr:first-child td:last-child
61 |       round(0 4px 0 0)
62 | 
63 |     thead:last-child tr:last-child th:first-child
64 |     tbody:last-child tr:last-child td:first-child
65 |       round(0 0 0 4px)
66 | 
67 |     thead:last-child tr:last-child th:last-child
68 |     tbody:last-child tr:last-child td:last-child
69 |       round(0 0 4px 0)
70 | 
71 |   if striped
72 |     tbody tr:nth-child(odd) td
73 |     tbody tr:nth-child(odd) th
74 |       background-color: #f9f9f9
75 | 
76 |     tbody tr:hover td
77 |     tbody tr:hover th
78 |       background-color: #f5f5f5
79 | 
80 | // Additive Mixin: Tables
81 | // WARNING: Creates classes in your css and styles them - not to be used inside an element.
82 | // Makes tables look awesome by default. Highly recommended if you have tables on your site at all.
83 | 
84 | tables()
85 |   table
86 |     table()


--------------------------------------------------------------------------------
/frontend/src/stylus/axis/typography.styl:
--------------------------------------------------------------------------------
  1 | // -----------------------------------------------------
  2 | // Typography
  3 | // -----------------------------------------------------
  4 | 
  5 | // Mixin: Line Height
  6 | // If there is a font-size set on the parent element, it adjusts the line height
  7 | // to match that. If not, it uses the base font-size to calculate the line-height.
  8 | 
  9 | -line-height()
 10 |   if @font-size
 11 |     line-height: 1.6em
 12 |   else
 13 |     line-height: unit(font-size*1.6, px)
 14 |     line-height: 1.6rem
 15 | 
 16 | // Mixin: Text Margin
 17 | // Puts nice visually pleasing top and bottom margins on a paragraph of text.
 18 | // Put a font-size on your element to have it adjust accordingly.
 19 | 
 20 | text-margin(size=font-size)
 21 |   if @font-size
 22 |     margin: .75em 0
 23 |   else
 24 |     margin: unit(font-size*.75, px) 0
 25 | 
 26 | // Mixin: Paragraph
 27 | // Sets a nice size, line-height, and margins on a paragraph
 28 | // of text. Pass a size in to customize. Toggle margins off if
 29 | // you don't want them.
 30 | 
 31 | // ex. paragraph()
 32 | // ex. paragraph: 18px
 33 | // ex. paragraph: 14px false
 34 | 
 35 | paragraph(size=1rem, margins = true)
 36 |   font-size: size
 37 |   text-margin() if margins
 38 |   -line-height()
 39 |   openTypeLigatures() if ligatures
 40 | 
 41 | 
 42 | // Mixin: Fs
 43 | // An alias for 'font-size' with auto-fallback for rem
 44 | // ex. fs: 16px
 45 | // ex. fs: 1.2rem
 46 | 
 47 | fs(size)
 48 |   if (unit(size) == 'rem')
 49 |     font-size: unit((font-size*size), px)
 50 |     font-size: size
 51 |   else
 52 |     font-size: size
 53 | 
 54 | // Mixin: Uppercase
 55 | // An alias for 'text-transform: uppercase'
 56 | // ex. +uppercase
 57 | 
 58 | uppercase()
 59 |   text-transform: uppercase
 60 | 
 61 | // Mixin: Upcase
 62 | // An alias for 'text-transform: uppercase'
 63 | // ex. upcase()
 64 | 
 65 | upcase()
 66 |   uppercase()
 67 | 
 68 | // Mixin: Lowercase
 69 | // An alias for 'text-transform: lowercase'
 70 | // ex. lowercase()
 71 | 
 72 | lowercase()
 73 |   text-transform: lowercase
 74 | 
 75 | // Mixin: Downcase
 76 | // An alias for 'text-transform: lowercase'
 77 | // ex. downcase()
 78 | 
 79 | downcase()
 80 |   lowercase()
 81 | 
 82 | // Mixin: Reset Case
 83 | // Gets rid of an text transform
 84 | // ex. reset-case()
 85 | 
 86 | reset-case()
 87 |   text-transform: none
 88 | 
 89 | // Mixin: Small Text
 90 | // Makes your text smaller and a little lighter. Great on  tags.
 91 | // ex. small-text()
 92 | 
 93 | small-text()
 94 |   font-size: 55%
 95 |   opacity: .6
 96 |   font-weight: normal
 97 | 
 98 | // Helper: Heading
 99 | // For internal use within headings
100 | 
101 | -heading(multiplier)
102 |   font-size: unit((font-size*multiplier), px)
103 |   font-size: unit(multiplier, rem)
104 |   color: font-color
105 |   text-rendering: optimizelegibility
106 |   font-weight: bold
107 |   text-margin()
108 |   -line-height()
109 |   openTypeLigatures() if ligatures
110 | 
111 | // Mixin Set: h1, h2, h3, h4, h5, h6
112 | // These provide nice defaults for headings based off the default font size.
113 | // The can scale infinitely, and work best when matched to their corresponding html elements.
114 | // If you'd like to change the base size of a header, just pass it as an option.
115 | // ex. h3()
116 | // ex. h3(3.5)
117 | 
118 | h1(size=2.4)
119 |   -heading(size)
120 | 
121 | h2(size=1.8)
122 |   -heading(size)
123 | 
124 | h3(size=1.5)
125 |   -heading(size)
126 | 
127 | h4(size=1.3)
128 |   -heading(size)
129 | 
130 | h5(size=1.0)
131 |   -heading(size)
132 | 
133 | h6(size=0.9)
134 |   -heading(size)
135 |   text-transform: uppercase
136 | 
137 | // Mixin: Link
138 | // A nice default style for links. Accepts a color and a style. Color can be anything,
139 | // style can be underline, darken, lighten, or glow, each giving it a different style
140 | // of interaction when hovered. More suggestions here welcome.
141 | // ex. link
142 | // ex. link: green
143 | // ex. link: #57777E glow
144 | 
145 | link(color = blue, style = underline)
146 |   color: color
147 |   text-decoration: none
148 |   transition()
149 |   if style == underline
150 |     hover-underline()
151 |     &:hover
152 |       color: darken(@color, 15%)
153 |   else if style == darken
154 |     &:hover
155 |       color: darken(@color, 20%)
156 |   else if style == lighten
157 |     hover-lighten()
158 |   else if style == glow
159 |     &:hover
160 |       text-shadow: 0 0 7px rgba(color, .6)
161 |   &:visited
162 |     opacity: .8
163 | 
164 | // Mixin: Reset Link
165 | // Sometimes my link mixin of the browser defaults will give you questionable link defaults
166 | // that you don't want on certain elements. This guy gets rid of that.
167 | // ex. reset-link()
168 | 
169 | reset-link()
170 |   border: none
171 |   text-decoration: none
172 |   color: inherit
173 |   &:hover
174 |     border: none
175 |     text-decoration: none
176 |     color: inherit
177 |   &:visited
178 |     opacity: 1
179 | 
180 | // Mixin: Text Selection
181 | // This guy sets the text select color intelligently based on the highlight-color variable
182 | // found in the settings file. If you really want, you can pass it a color override too
183 | // ex. text-selection()
184 | 
185 | text-selection(color = highlight-color, textColor = null)
186 | 
187 |   textColor = light(color) ? white : #494949 unless textColor
188 | 
189 |   ::-moz-selection
190 |     background: color
191 |     color: textColor
192 |   ::selection
193 |     background: color
194 |     color: textColor
195 | 
196 | // Mixin: Ul
197 | // A nice default for list styles. More or less the same as browser defaults, scales nicely.
198 | // You can pass it any style that list-style-type would normally take. Defaults to disc.
199 | // Use this on a ul element por favor.
200 | // ex. ul
201 | // ex. ul: square
202 | 
203 | ul(style = disc)
204 |   margin: 5px 15px
205 |   margin: 0.32rem 0.94rem
206 |   padding-left: 1rem
207 | 
208 |   li
209 |     list-style-type: style
210 |     padding: 2px 0
211 |     padding: 0.125rem
212 | 
213 | // Mixin: Ol
214 | // A nice default for list styles. More or less the same as browser defaults, scales nicely.
215 | // You can pass it any style that list-style-type would normally take. Defaults to decimal.
216 | // Use this on a ol element por favor.
217 | // ex. ol
218 | // ex. ol: upper-roman
219 | 
220 | ol(style = decimal)
221 |   margin: 5px 18px
222 |   margin: 0.32rem 1.125rem
223 |   padding-left: 1rem
224 | 
225 |   li
226 |     list-style-type: style
227 |     padding: 2px 0
228 |     padding: 0.125rem
229 | 
230 | // Mixin: Inline List
231 | // For when you need your list to be horizontal. Pass it the spacing you want between list
232 | // elements, whatever units you'd like. Defaults to 20px
233 | // ex. inline-list
234 | // ex. inline-list: 15px
235 | 
236 | inline-list(spacing = 20px)
237 |   group()
238 |   margin: 0
239 |   padding: 0
240 | 
241 |   li
242 |     float: left
243 |     margin-right: spacing
244 |     list-style-type: none
245 | 
246 |   li:last-child
247 |     margin-right: 0
248 | 
249 | // Mixin: Reset List
250 | // If you're tired of all the list shenanigans and want to get rid of them for this special
251 | // list you're working on, this is your guy. Resets the margins, padding, and style.
252 | // ex. reset-list()
253 | 
254 | reset-list()
255 |   margin: 0
256 |   padding: 0
257 | 
258 |   li
259 |     float: none
260 |     list-style-type: none
261 |     padding: 0
262 |     margin: 0
263 | 
264 | // Mixin: Blockquote
265 | // Nice styles for a blockquote, and even puts a nice hyphen in before your citation.
266 | // Use with a 

and ,