Repository: nprapps/lunchbox
Branch: master
Commit: 5678043c0387
Files: 120
Total size: 1.2 MB
Directory structure:
gitextract_pbzm31xv/
├── .gitignore
├── LICENSE
├── README.md
├── app.py
├── app_config.py
├── fabfile/
│ ├── __init__.py
│ ├── flat.py
│ ├── render.py
│ └── utils.py
├── less/
│ ├── app.less
│ ├── factlist.less
│ ├── lib/
│ │ ├── bootstrap/
│ │ │ ├── alerts.less
│ │ │ ├── badges.less
│ │ │ ├── bootstrap.less
│ │ │ ├── breadcrumbs.less
│ │ │ ├── button-groups.less
│ │ │ ├── buttons.less
│ │ │ ├── carousel.less
│ │ │ ├── close.less
│ │ │ ├── code.less
│ │ │ ├── component-animations.less
│ │ │ ├── dropdowns.less
│ │ │ ├── forms.less
│ │ │ ├── glyphicons.less
│ │ │ ├── grid.less
│ │ │ ├── input-groups.less
│ │ │ ├── jumbotron.less
│ │ │ ├── labels.less
│ │ │ ├── list-group.less
│ │ │ ├── media.less
│ │ │ ├── mixins.less
│ │ │ ├── modals.less
│ │ │ ├── navbar.less
│ │ │ ├── navs.less
│ │ │ ├── normalize.less
│ │ │ ├── pager.less
│ │ │ ├── pagination.less
│ │ │ ├── panels.less
│ │ │ ├── popovers.less
│ │ │ ├── print.less
│ │ │ ├── progress-bars.less
│ │ │ ├── responsive-utilities.less
│ │ │ ├── scaffolding.less
│ │ │ ├── tables.less
│ │ │ ├── theme.less
│ │ │ ├── thumbnails.less
│ │ │ ├── tooltip.less
│ │ │ ├── type.less
│ │ │ ├── utilities.less
│ │ │ ├── variables.less
│ │ │ └── wells.less
│ │ ├── font-awesome/
│ │ │ ├── bootstrap.less
│ │ │ ├── bordered-pulled.less
│ │ │ ├── core.less
│ │ │ ├── extras.less
│ │ │ ├── fixed-width.less
│ │ │ ├── font-awesome-ie7.less
│ │ │ ├── font-awesome.less
│ │ │ ├── icons.less
│ │ │ ├── larger.less
│ │ │ ├── list.less
│ │ │ ├── mixins.less
│ │ │ ├── path.less
│ │ │ ├── rotated-flipped.less
│ │ │ ├── spinning.less
│ │ │ ├── stacked.less
│ │ │ └── variables.less
│ │ └── prefixer.less
│ ├── medium.less
│ ├── quotable.less
│ ├── variables.less
│ └── waterbug.less
├── package.json
├── packager-config.json
├── render_utils.py
├── requirements.txt
├── static.py
├── templates/
│ ├── _base.html
│ ├── _css_header.css
│ ├── _fonts.html
│ ├── _js_header.js
│ ├── _meta.html
│ ├── factlist.html
│ ├── index.html
│ ├── oauth/
│ │ ├── _oauth_base.html
│ │ ├── authenticate.html
│ │ ├── oauth.html
│ │ └── warning.html
│ ├── quotable.html
│ └── waterbug.html
├── tests/
│ ├── __init__.py
│ └── test_app.py
└── www/
├── css/
│ └── .placeholder
├── font/
│ └── FontAwesome.otf
├── img/
│ └── icon-lunchbox.icns
├── js/
│ ├── app.js
│ ├── factlist.js
│ ├── lib/
│ │ ├── bootstrap.js
│ │ ├── fileinput.js
│ │ ├── html2canvas.js
│ │ ├── jquery.js
│ │ ├── modernizr.js
│ │ ├── moment.js
│ │ ├── underscore.js
│ │ └── webfont.js
│ ├── quotable.js
│ ├── waterbug-config.js
│ └── waterbug.js
├── main.js
├── package.json
└── test/
├── SpecRunner.html
├── fixtures/
│ └── example.json
├── lib/
│ ├── jasmine-1.3.1/
│ │ ├── MIT.LICENSE
│ │ ├── jasmine-html.js
│ │ ├── jasmine.css
│ │ └── jasmine.js
│ ├── jasmine-jquery.js
│ └── sinon-1.5.2.js
└── spec/
├── _tests.js
└── app.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.py[co]
*.sw[op]
# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
node_modules
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
#Translations
*.mo
.DS_store
# Rendered files
www/*.html
www/css/*.min.*.css
www/css/*.min.css
www/css/*.less.css
www/js/*.min.*.js
www/js/*.min.js
www/js/templates.js
www/js/app_config.js
www/js/copy.js
www/test/test.html
www/comments/index.html
confs/rendered/*
www/factlist
www/quotable
www/waterbug
# Local data
data/gdoc*.csv
data/copy.xls
data/copy.xlsx
www/assets/*
!www/assets/assetsignore
www/live-data/*
# built apps
electron
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 NPR
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
=============
* [What is this?](#what-is-this)
* [Assumptions?](#assumptions)
* [What's in here?](#what-is-in-here)
* [Quick start](#quick-start)
* [Configuration](#configuration)
* [Deploy the desktop app](#deploy-the-desktop-app)
* [About](#about)
What is this?
-------------
**Lunchbox** is a suite of tools to create images intended for social media sharing. It includes:
* *Quotable*: Converts quoted text into a branded image.
* *Factlist*: Produces a branded image with a list of items.
* *Waterbug*: Creates a watermarked image with attribution.
Assumptions
-------------
**Lunchbox** is a customizable toolset deployable as a web app. The following instructions are meant for developers setting up and customizing the app for their organization. For end-users of the tools, see [usage guidelines](http://blog.apps.npr.org/lunchbox).
The following things are assumed to be true in this documentation.
* You are running OSX.
* You are using Python 2.7. (Probably the version that came OSX.)
* You have [virtualenv](https://pypi.python.org/pypi/virtualenv) and [virtualenvwrapper](https://pypi.python.org/pypi/virtualenvwrapper) installed and working.
What's in here?
-------------
* ``fabfile`` -- [Fabric](http://docs.fabfile.org/en/latest/) commands for automating setup and deployment.
* ``less`` -- Application styles and [Bootstrap](http://getbootstrap.com/css/) less files.
* ``templates`` -- HTML ([Jinja2](http://jinja.pocoo.org/docs/)) templates, to be compiled locally.
* ``www`` -- App assets and rendered files.
* ``Lunchbox Setup.exe`` -- Lunchbox Demo installer for Windows.
* ``Lunchbox.dmg`` -- Lunchbox Demo installer for OSX.
* ``app.py`` -- A [Flask](http://flask.pocoo.org/) app for rendering the project locally.
* ``app_config.py`` -- Configuration variables for the Flask app.
* ``package.json`` -- Node dependencies and scripts for building [Electron](https://github.com/atom/electron) app.
* ``packager-config.json`` -- Configuration for create installers with [Electron](https://github.com/atom/electron).
* ``render_utils.py`` -- Helper functions for baking out Flask app.
* ``requirements.txt`` -- Python requirements.
* ``static.py`` -- Routes for static files in Flask app.
Quick Start
-------------
Clone or fork this repo (NPR users: Use the `npr` branch), then do the following:
Change to the project directory you just cloned:
```
cd lunchbox
```
Create a new virtualenv to get an isolated Python environment:
| with virtualenvwrapper | with Anaconda |
|------------------------|---------------|
| ```mkvirtualenv lunchbox``` | ```conda create --name lunchbox python=2.7``` |
Then, activate your virtual environment.
| with virtualenvwrapper | with Anaconda |
|------------------------|---------------|
| ```workon lunchbox``` | ```conda activate lunchbox``` |
Next, install Python dependencies:
```
pip install -r requirements.txt
```
Install the Node.js dependencies (most importantly, Less):
```
npm install
```
Then run the app:
```
fab app
```
Visit [localhost:8000](http://127.0.0.1:8000/) in your browser to see the app.
Configuration
-------------
You can skip configuration if you just want to [deploy Lunchbox](#deploy-the-desktop-app) and start using it with the application's default branding (or you can [download the Demo](http://blog.apps.npr.org/lunchbox/) ). Configuration options allow you to tailor the app to match your organization's branding and theme.
### Assets
If you are customizing the branding of the apps, you will probably want to use your organization's web fonts and logos.
For fonts, we provide a Jinja template at `templates/_fonts.html` using Typekit's [webfontloader](https://github.com/typekit/webfontloader) for loading fonts from Google, Typekit, or custom stylesheets. Then, the fonts will be available in the CSS and JavaScript in all of the apps.
For your organization's logos, you can provide SVGs or PNGs. Make sure that there is no whitespace around the logo so that the padding performs properly. You can place them anywhere in the `www` folder as long as you link them correctly when you [define your global variables](#define-your-global-variables), but we recommend `www/img`.
For Waterbug, you will want to have a white version and a black version of your logo so that you can choose the appropriate version for light and dark photos.
### Define global variables
There are two places where variables are defined, one place for Quotable and Factlist and one place for Waterbug.
#### Quotable/Factlist
For Quotable and Factlist, all configuration takes places in `less/variables.less`. You can define font families, establish the default background color/text color and define the logo used on the images.
Importantly, if you use a custom logo, you will also need to explicitly define the width and height of the logo in both square crop and 16:9 crop scenarios. The variables at the top of the file will do this:
```
@logo-path: url('../path/to/logo.svg');
@logo-sq-width: 145px;
@logo-sq-height: 48px;
@logo-16x9-width: 121px;
@logo-16x9-height: 40px;
```
Additionally, you can fine-tune various aspects of Quotable and Factlist using the app-specific variables also listed in the file. The defaults should work well out of the box, but your organization's logo or font may require tweaks.
#### Waterbug
Waterbug has a different configuration system because it cannot be controlled through CSS. To customize Waterbug, go to `www/js/waterbug-config.js` and customize the variables at the top of the file.
In this file, you can define the logos used and the sizes with which they render by editing the `logos` object.
```
var logos = {
'name-of-logo': {
whitePath: '../path/to/logo-white.svg', // path to white logo
blackPath: '../path/to/logo-black.svg', // path to black logo
w: 200, // width of logo
h: 67, // height of logo
display: 'Name of logo' // how the button toggle will appear in the UI
},
'name-of-second-logo': {
whitePath: '../path/to/second-logo-white.svg',
blackPath: '../path/to/second-logo-black.svg',
w: 150,
h: 51,
display: 'Name of second logo'
}
};
```
If you have more than one logo, the UI will automatically add toggle buttons so that you can switch between logos on the fly.
Additionally, You can change every property of the font rendering (font face, size, shadow, etc.) as well as the padding around all of the elements (`elementPadding`) in the image and the export width of the image (`canvasWidth`).
You will want to configure the copyright options for Waterbug based on the photo providers your news organization can use. This is defined in an large object that contains an object for each copyright option. The boolean values control the behavior of the form:
```
// copyright options
var orgName = 'Your News Organization';
var freelanceString = 'for ' + orgName;
var copyrightOptions = {
'internal': {
showPhotographer: true, // show the photographer input box
showSource: false, // show the source input box
photographerRequired: false, // require a photographer
sourceRequired: false, // require a source
source: orgName, // How the source should appear on the image, e.g. 'NPR'
display: orgName, // How the option will appear in the dropdown menu
},
'freelance': {
showPhotographer: true,
showSource: false,
photographerRequired: true,
sourceRequired: false,
source: freelanceString,
display: 'Freelance'
},
'ap': {
showPhotographer: true,
showSource: false,
photographerRequired: false,
sourceRequired: false,
source: 'AP',
display: 'AP'
},
'getty': {
showPhotographer: true,
showSource: false,
photographerRequired: false,
sourceRequired: false,
source: 'Getty Images',
display: 'Getty'
},
'thirdParty': {
showPhotographer: true,
showSource: true,
photographerRequired: false,
sourceRequired: true,
source: '',
display: 'Third Party/Courtesy'
}
}
```
The app will automatically add all of your copyright options to the dropdown menu. Also, it will perform form validation based on the boolean values above.
Finally, you can configure the application defaults. Ensure that the logo and image paths point to existing files:
```
// app load defaults
var currentCrop = 'twitter'; // default crop size
var currentLogo = 'lunchbox'; // default logo slug
var currentLogoColor = 'white'; // default logo color
var currentTextColor = 'white'; // default text color
var defaultImage = '../img/test-kitten.jpg'; // path to image to load as test image
var defaultLogo = logos[currentLogo]['whitePath'] // path to default logo
```
At the bottom of the form, you will notice a Sharing Guidelines section. To edit that section, you can just update the list in `templates/waterbug.html`.
### Multiple Themes
For Quotable and Factlist, you can provide up to three themes in addition to the default theme if your news organization requires different branding for different accounts (think [NPR](http://twitter.com/npr) vs. [NPR Music](http://twitter.com/nprmusic)).
In `less/variables.less`, you can define themes at the bottom of the file. For each theme, you can change the background color, text color, and logo:
```
@theme2-bg-color: #41474E;
@theme2-text-color: #dbe0e6;
@theme2-logo-path: url('../img/icon-socializr-white.svg');
@theme2-sq-logo-width: 145px;
@theme2-sq-logo-height: 48px;
@theme2-16x9-logo-width: 121px;
@theme2-16x9-logo-height: 40px;
```
In the form UI, you can change the display of the theme selection buttons in each app's HTML template (`templates/quotable.html`, `templates/factlist.html`). Be sure not to change the ID attribute of the button, as these IDs control the JavaScript that adds and removes classes on the image.
Deployment
===============
We support two separate deployment options: Amazon S3 and any fileserver that you can SSH into.
Deploy to Amazon S3
-------------------
For Amazon S3, ensure that you've installed the AWS command-line interface (if you're using brew, you can use `brew install awscli`), and set up a new S3 bucket.
Store your AWS Access Key ID and Secret Access Key as environment variables by running the following in Terminal:
```
export AWS_ACCESS_KEY_ID="YOURACCESSKEYID"
export AWS_SECRET_ACCESS_KEY="YOURSECRETACCESSKEY"
```
Then, in `app_config.py`, change your staging and production S3 targets:
```
PRODUCTION_S3_BUCKET = 'your.bucket.org'
STAGING_S3_BUCKET = 'stage-your.bucket.org'
```
Note: The placeholder is the name of your bucket and not its url. For a simple S3 bucket with no custom DNS named `lunchbox-s3`, you would use `lunchbox-s3` instead of `s3.amazonaws.com/lunchbox-s3`, for instance.
With these variables set, you can run `fab [production/staging] master deploy` to deploy Lunchbox to your S3 bucket.
Deploy to other file server
---------------------------
For other file servers, you can change the following app_config variables:
```
FILE_SERVER_USER = 'ubuntu' # set this to the user you use to SSH onto the server
FILE_SERVER = 'your.fileserver.org' # set this to either the hostname or IP address of your file server
FILE_SERVER_PATH = '~/www' # set this to the path that your server serves files to the web from
```
Then, you can run `fab fileserver master deploy`. This will `rsync` the rendered files to `FILE_SERVER_PATH/lunchbox`.
Known Issues
-------------
- Firefox compatibility with SVG: Firefox is not capable of rendering SVG logos with Quotable or Factlist.
About
-------------
Lunchbox consolidates [NPR](https://github.com/nprapps/)’s [Quotable](https://github.com/nprapps/quotable), [Factlist](https://github.com/nprapps/factlist) and [Waterbug](https://github.com/nprapps/waterbug), apps into a suite of tools for the newsroom.
It was worked on during the [OpenNews](http://opennews.org) Portland Code Convening on July 23-24, 2015.
Additional contributors:
- [Jason Emory Parker](https://github.com/postandcourier)
- [Ben Chartoff](https://github.com/bchartoff)
- [Chris Barna](https://github.com/ctbarna)
- [David Ryan](https://github.com/dryanmedia)
- [Davis Shaver](https://github.com/davisshaver)
================================================
FILE: app.py
================================================
#!/usr/bin/env python
"""
Example application views.
Note that `render_template` is wrapped with `make_response` in all application
routes. While not necessary for most Flask apps, it is required in the
App Template for static publishing.
"""
from datetime import datetime
import app_config
import json
import static
from flask import Flask, make_response, render_template
from render_utils import make_context, smarty_filter, urlencode_filter
from werkzeug.debug import DebuggedApplication
app = Flask(__name__)
app.debug = app_config.DEBUG
app.add_template_filter(smarty_filter, name='smarty')
app.add_template_filter(urlencode_filter, name='urlencode')
@app.route('/')
@app.route('/index.html')
def index():
"""
Example view demonstrating rendering a simple HTML page.
"""
context = make_context()
context['name'] = 'Lunchbox'
context['id'] = 'home'
context['now'] = datetime.now().strftime('%B %-d, %Y')
return make_response(render_template('index.html', **context))
@app.route('/factlist/index.html')
def factlist():
context = make_context()
context['name'] = 'Factlist'
context['id'] = context['name']
return make_response(render_template('factlist.html', **context))
@app.route('/quotable/index.html')
def quotable():
context = make_context()
context['name'] = 'Quotable'
context['id'] = context['name']
return make_response(render_template('quotable.html', **context))
@app.route('/waterbug/index.html')
def waterbug():
context = make_context()
context['name'] = 'Waterbug'
context['id'] = context['name']
return make_response(render_template('waterbug.html', **context))
app.register_blueprint(static.static)
# Enable Werkzeug debug pages
if app_config.DEBUG:
wsgi_app = DebuggedApplication(app, evalex=False)
else:
wsgi_app = app
# Catch attempts to run the app directly
if __name__ == '__main__':
print 'This command has been removed! Please run "fab app" instead!'
================================================
FILE: app_config.py
================================================
#!/usr/bin/env python
"""
Project-wide application configuration.
DO NOT STORE SECRETS, PASSWORDS, ETC. IN THIS FILE.
They will be exposed to users. Use environment variables instead.
See get_secrets() below for a fast way to access them.
"""
import os
"""
NAMES
"""
# Project name to be used in urls
# Use dashes, not underscores!
PROJECT_SLUG = 'lunchbox-test'
# Project name to be used in file paths
PROJECT_FILENAME = 'lunchbox'
# The name of the repository containing the source
REPOSITORY_NAME = 'lunchbox'
GITHUB_USERNAME = 'nprapps'
REPOSITORY_URL = 'git@github.com:%s/%s.git' % (GITHUB_USERNAME, REPOSITORY_NAME)
REPOSITORY_ALT_URL = None # 'git@bitbucket.org:nprapps/%s.git' % REPOSITORY_NAME'
DEV_CONTACT = 'EDIT THIS IN APP_CONFIG.PY'
"""
DEPLOYMENT
"""
PRODUCTION_S3_BUCKET = 'apps.npr.org'
STAGING_S3_BUCKET = 'stage-apps.npr.org'
DEFAULT_MAX_AGE = 20
FILE_SERVER_USER = 'ubuntu'
FILE_SERVER = 'tools.apps.npr.org'
FILE_SERVER_PATH = '~/www'
# These variables will be set at runtime. See configure_targets() below
S3_BUCKET = None
S3_BASE_URL = None
S3_DEPLOY_URL = None
DEBUG = True
"""
Utilities
"""
def get_secrets():
"""
A method for accessing our secrets.
"""
secrets_dict = {}
for k,v in os.environ.items():
if k.startswith(PROJECT_SLUG):
k = k[len(PROJECT_SLUG) + 1:]
secrets_dict[k] = v
return secrets_dict
def configure_targets(deployment_target):
"""
Configure deployment targets. Abstracted so this can be
overriden for rendering before deployment.
"""
global S3_BUCKET
global S3_BASE_URL
global S3_DEPLOY_URL
global DEBUG
global DEPLOYMENT_TARGET
global ASSETS_MAX_AGE
if deployment_target == 'electron':
S3_BUCKET = None
S3_BASE_URL = None
S3_DEPLOY_URL = None
DEBUG = False
ASSETS_MAX_AGE = 0
if deployment_target == 'fileserver':
S3_BUCKET = None
S3_BASE_URL = None
S3_DEPLOY_URL = None
DEBUG = False
ASSETS_MAX_AGE = 0
if deployment_target == 'production':
S3_BUCKET = PRODUCTION_S3_BUCKET
S3_BASE_URL = 'http://%s/%s' % (S3_BUCKET, PROJECT_SLUG)
S3_DEPLOY_URL = 's3://%s/%s' % (S3_BUCKET, PROJECT_SLUG)
DEBUG = False
ASSETS_MAX_AGE = 86400
elif deployment_target == 'staging':
S3_BUCKET = STAGING_S3_BUCKET
S3_BASE_URL = 'http://%s/%s' % (S3_BUCKET, PROJECT_SLUG)
S3_DEPLOY_URL = 's3://%s/%s' % (S3_BUCKET, PROJECT_SLUG)
DEBUG = True
ASSETS_MAX_AGE = 20
else:
S3_BUCKET = None
S3_BASE_URL = 'http://127.0.0.1:8000'
S3_DEPLOY_URL = None
DEBUG = True
ASSETS_MAX_AGE = 20
DEPLOYMENT_TARGET = deployment_target
"""
Run automated configuration
"""
DEPLOYMENT_TARGET = os.environ.get('DEPLOYMENT_TARGET', None)
configure_targets(DEPLOYMENT_TARGET)
================================================
FILE: fabfile/__init__.py
================================================
#!/usr/bin/env python
from datetime import datetime
import json
import os
from boto.s3.key import Key
from fabric.api import local, require, settings, task
from fabric.state import env
from termcolor import colored
import app_config
# Other fabfiles
import flat
import render
import utils
# Bootstrap can only be run once, then it's disabled
if app_config.PROJECT_SLUG == '$NEW_PROJECT_SLUG':
import bootstrap
"""
Base configuration
"""
env.forward_agent = True
env.hosts = []
env.settings = None
"""
Environments
Changing environment requires a full-stack test.
An environment points to both a server and an S3
bucket.
"""
@task
def electron():
"""
Run as though building electron app.
"""
env.settings = 'electron'
app_config.configure_targets(env.settings)
@task
def fileserver():
"""
Run as though building electron app.
"""
env.settings = 'fileserver'
app_config.configure_targets(env.settings)
@task
def production():
"""
Run as though on production.
"""
env.settings = 'production'
app_config.configure_targets(env.settings)
@task
def staging():
"""
Run as though on staging.
"""
env.settings = 'staging'
app_config.configure_targets(env.settings)
"""
Branches
Changing branches requires deploying that branch to a host.
"""
@task
def stable():
"""
Work on stable branch.
"""
env.branch = 'stable'
@task
def master():
"""
Work on development branch.
"""
env.branch = 'master'
@task
def branch(branch_name):
"""
Work on any specified branch.
"""
env.branch = branch_name
"""
Running the app
"""
@task
def app(port='8000'):
"""
Serve app.py.
"""
if env.settings:
local("DEPLOYMENT_TARGET=%s bash -c 'gunicorn -b 0.0.0.0:%s --timeout 3600 --debug --reload app:wsgi_app'" % (env.settings, port))
else:
local('gunicorn -b 0.0.0.0:%s --timeout 3600 --debug --reload app:wsgi_app' % port)
@task
def public_app(port='8001'):
"""
Serve public_app.py.
"""
if env.settings:
local("DEPLOYMENT_TARGET=%s bash -c 'gunicorn -b 0.0.0.0:%s --timeout 3600 --debug --reload public_app:wsgi_app'" % (env.settings, port))
else:
local('gunicorn -b 0.0.0.0:%s --timeout 3600 --debug --reload public_app:wsgi_app' % port)
@task
def tests():
"""
Run Python unit tests.
"""
local('nosetests')
"""
Deployment
Changes to deployment requires a full-stack test. Deployment
has two primary functions: Pushing flat files to S3 and deploying
code to a remote server if required.
"""
@task
def deploy(remote='origin', reload=False):
"""
Deploy the latest app to S3 and, if configured, to our servers.
"""
require('settings', provided_by=[production, staging, electron])
render.render_all()
if env.settings == 'electron':
if not os.path.exists('electron'):
os.makedirs('electron')
local('npm run-script pack')
if env.settings == 'fileserver':
local('rsync -vr www/ %s@%s:%s/%s' % (
app_config.FILE_SERVER_USER,
app_config.FILE_SERVER,
app_config.FILE_SERVER_PATH,
app_config.PROJECT_SLUG
))
if env.settings == 'production' or env.settings == 'staging':
flat.deploy_folder(
app_config.S3_BUCKET,
'www',
app_config.PROJECT_SLUG,
headers={
'Cache-Control': 'max-age=%i' % app_config.DEFAULT_MAX_AGE
},
ignore=['www/img/*', 'www/live-data/*']
)
flat.deploy_folder(
app_config.S3_BUCKET,
'www/img',
'%s/img' % app_config.PROJECT_SLUG,
headers={
'Cache-Control': 'max-age=%i' % app_config.ASSETS_MAX_AGE
}
)
"""
Destruction
Changes to destruction require setup/deploy to a test host in order to test.
Destruction should remove all files related to the project from both a remote
host and S3.
"""
@task
def shiva_the_destroyer():
"""
Deletes the app from s3
"""
require('settings', provided_by=[production, staging])
utils.confirm(
colored("You are about to destroy everything deployed to %s for this project.\nDo you know what you're doing?')" % app_config.DEPLOYMENT_TARGET, "red")
)
with settings(warn_only=True):
flat.delete_folder(app_config.S3_BUCKET, app_config.PROJECT_SLUG)
if app_config.DEPLOY_TO_SERVERS:
servers.delete_project()
if app_config.DEPLOY_CRONTAB:
servers.uninstall_crontab()
if app_config.DEPLOY_SERVICES:
servers.nuke_confs()
================================================
FILE: fabfile/flat.py
================================================
#!/usr/bin/env python
import copy
from cStringIO import StringIO
from fnmatch import fnmatch
import gzip
import hashlib
import mimetypes
import os
from boto.s3.key import Key
import app_config
import utils
GZIP_FILE_TYPES = ['.html', '.js', '.json', '.css', '.xml']
class FakeTime:
def time(self):
return 1261130520.0
# Hack to override gzip's time implementation
# See: http://stackoverflow.com/questions/264224/setting-the-gzip-timestamp-from-python
gzip.time = FakeTime()
def deploy_file(bucket, src, dst, headers={}):
"""
Deploy a single file to S3, if the local version is different.
"""
k = bucket.get_key(dst)
s3_md5 = None
if k:
s3_md5 = k.etag.strip('"')
else:
k = Key(bucket)
k.key = dst
file_headers = copy.copy(headers)
if 'Content-Type' not in headers:
file_headers['Content-Type'] = mimetypes.guess_type(src)[0]
# Gzip file
if os.path.splitext(src)[1].lower() in GZIP_FILE_TYPES:
file_headers['Content-Encoding'] = 'gzip'
with open(src, 'rb') as f_in:
contents = f_in.read()
output = StringIO()
f_out = gzip.GzipFile(filename=dst, mode='wb', fileobj=output)
f_out.write(contents)
f_out.close()
local_md5 = hashlib.md5()
local_md5.update(output.getvalue())
local_md5 = local_md5.hexdigest()
if local_md5 == s3_md5:
print 'Skipping %s (has not changed)' % src
else:
print 'Uploading %s --> %s (gzipped)' % (src, dst)
k.set_contents_from_string(output.getvalue(), file_headers, policy='public-read')
# Non-gzip file
else:
with open(src, 'rb') as f:
local_md5 = hashlib.md5()
local_md5.update(f.read())
local_md5 = local_md5.hexdigest()
if local_md5 == s3_md5:
print 'Skipping %s (has not changed)' % src
else:
print 'Uploading %s --> %s' % (src, dst)
k.set_contents_from_filename(src, file_headers, policy='public-read')
def deploy_folder(bucket_name, src, dst, headers={}, ignore=[]):
"""
Deploy a folder to S3, checking each file to see if it has changed.
"""
to_deploy = []
for local_path, subdirs, filenames in os.walk(src, topdown=True):
rel_path = os.path.relpath(local_path, src)
for name in filenames:
if name.startswith('.'):
continue
src_path = os.path.join(local_path, name)
skip = False
for pattern in ignore:
if fnmatch(src_path, pattern):
skip = True
break
if skip:
continue
if rel_path == '.':
dst_path = os.path.join(dst, name)
else:
dst_path = os.path.join(dst, rel_path, name)
to_deploy.append((src_path, dst_path))
bucket = utils.get_bucket(bucket_name)
for src, dst in to_deploy:
deploy_file(bucket, src, dst, headers)
def delete_folder(bucket_name, dst):
"""
Delete a folder from S3.
"""
bucket = utils.get_bucket(bucket_name)
for key in bucket.list(prefix='%s/' % dst):
print 'Deleting %s' % (key.key)
key.delete()
================================================
FILE: fabfile/render.py
================================================
#!/usr/bin/env python
"""
Commands for rendering various parts of the app stack.
"""
from glob import glob
import os
from fabric.api import local, task
import app
def _fake_context(path):
"""
Create a fact request context for a given path.
"""
return app.app.test_request_context(path=path)
def _view_from_name(name):
"""
Determine what module a view resides in, then get
a reference to it.
"""
bits = name.split('.')
# Determine which module the view resides in
if len(bits) > 1:
module, name = bits
else:
module = 'app'
return globals()[module].__dict__[name]
@task
def less():
"""
Render LESS files to CSS.
"""
for path in glob('less/*.less'):
filename = os.path.split(path)[-1]
name = os.path.splitext(filename)[0]
out_path = 'www/css/%s.less.css' % name
try:
local('node_modules/less/bin/lessc %s %s' % (path, out_path))
except:
print 'It looks like "lessc" isn\'t installed. Try running: "npm install"'
raise
@task
def app_config_js():
"""
Render app_config.js to file.
"""
from static import _app_config_js
with _fake_context('/js/app_config.js'):
response = _app_config_js()
with open('www/js/app_config.js', 'w') as f:
f.write(response.data)
@task(default=True)
def render_all():
"""
Render HTML templates and compile assets.
"""
from flask import g
less()
app_config_js()
compiled_includes = {}
# Loop over all views in the app
for rule in app.app.url_map.iter_rules():
rule_string = rule.rule
name = rule.endpoint
# Skip utility views
if name == 'static' or name.startswith('_'):
print 'Skipping %s' % name
continue
# Convert trailing slashes to index.html files
if rule_string.endswith('/'):
filename = 'www' + rule_string + 'index.html'
elif rule_string.endswith('.html'):
filename = 'www' + rule_string
else:
print 'Skipping %s' % name
continue
# Create the output path
dirname = os.path.dirname(filename)
if not (os.path.exists(dirname)):
os.makedirs(dirname)
print 'Rendering %s' % (filename)
# Render views, reusing compiled assets
with _fake_context(rule_string):
g.compile_includes = True
g.compiled_includes = compiled_includes
view = _view_from_name(name)
content = view().data
compiled_includes = g.compiled_includes
# Write rendered view
# NB: Flask response object has utf-8 encoded the data
with open(filename, 'w') as f:
f.write(content)
================================================
FILE: fabfile/utils.py
================================================
#!/usr/bin/env python
import boto
from boto.s3.connection import OrdinaryCallingFormat
"""
Utilities used by multiple commands.
"""
from fabric.api import prompt
def confirm(message):
"""
Verify a users intentions.
"""
answer = prompt(message, default="Not at all")
if answer.lower() not in ('y', 'yes', 'buzz off', 'screw you'):
exit()
def get_bucket(bucket_name):
"""
Established a connection and gets s3 bucket
"""
if '.' in bucket_name:
s3 = boto.connect_s3(calling_format=OrdinaryCallingFormat())
else:
s3 = boto.connect_s3()
return s3.get_bucket(bucket_name)
================================================
FILE: less/app.less
================================================
@import "less/lib/bootstrap/bootstrap";
@import "less/lib/font-awesome/font-awesome";
@import "less/medium";
@import "less/variables";
body {
font-family: @font-family !important;
font-size: @base-font-size !important;
}
canvas {
display: none;
background: @musicDark;
}
header {
background: @gray-darker;
margin: 0;
padding: 25px @grid-gutter-width / 2;
color: @gray-lighter;
.home & {
@media screen and (min-width: 700px){
padding-top: 44px;
padding-bottom: 44px;
}
.logo-wrapper {
width: 100%;
text-align: center;
img {
height: 150px;
width: auto;
}
}
}
a {
color: @gray-lighter;
&:hover {
color: @gray;
text-decoration: none;
}
}
.nav-links {
margin: 0 0 11px 0;
padding: 0;
@media screen and (min-width: 700px){
margin: 0 auto;
padding: 0 @grid-gutter-width / 2;
float: right;
}
a {
line-height: 32px;
margin-right: @grid-gutter-width / 2;
font-size: 16px;
text-transform: lowercase;
-webkit-font-smoothing: antialiased;
opacity: 0.7;
&.active { font-weight: bold; }
&:last-child { margin-right: 0; }
&:hover {
color: #eee;
opacity: 0.5;
}
@media screen and (min-width: 700px){
text-align: right;
}
}
}
h1 {
display: inline-block;
text-transform: uppercase;
font-weight: normal;
line-height: 32px;
font-size: @font-size-h3;
letter-spacing: 2px;
margin: 0;
-webkit-font-smoothing: antialiased;
font-family: @font-family;
.home & {
display: block;
text-align: center;
}
span.help {
font-size: 14px;
margin-left: 11px;
opacity: 0.7;
text-transform: none;
}
@media screen and (min-width: 700px){
font-size: @font-size-h1;
}
}
}
.home #content {
padding-top: 33px;
padding-bottom: 33px;
.poster-wrapper {
text-align: center;
margin-bottom: 15px;
a:hover {
text-decoration: none;
img { opacity: 0.7; }
}
img {
display: block;
margin: 0 auto 22px auto;
}
h3 {
letter-spacing: 1px;
margin: 0 0 11px 0;
font-size: 24px;
font-weight: 600;
text-transform: uppercase;
-webkit-font-smoothing: antialiased;
}
p {
color: #666;
font-size: 14px;
margin: 0;
i {
font-style: italic;
color: #999;
display: block;
font-size: 13px;
margin-top: 5px;
}
}
}
}
.content {
display: table;
width: 100%;
max-width: 1200px;
min-width: 640px;
margin: 0 auto;
.poster-wrapper,
.container {
display: table-cell;
vertical-align: top;
}
.poster-wrapper {
width: 640px;
}
@media screen and (max-width: @screen-sm-max){
display: block;
.poster-wrapper,
.container {
display: block;
}
}
@media screen and (max-width: @screen-xs-max){
.controls-wrapper {
font-size: 250%;
.btn {
font-size: 100%;
height: 88px;
min-width: 88px;
}
}
#fontsize {
display: block;
width: 90%;
}
}
.touch & {
input[type="range"] {
background: #fff;
width: 100%;
height: 88px;
-webkit-appearance: none;
border-radius: 8px;
-moz-border-radius: 8px;
-wekkit-border-radius: 8px;
border: 1px solid #ddd;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance:none !important;
width:88px;
height:88px;
-webkit-appearance: none;
border-radius: 8px;
-moz-border-radius: 8px;
-wekkit-border-radius: 8px;
border:none;
background: #aaa;
}
@media screen and (min-width: @screen-sm) {
input[type="range"]{
height: 44px;
}
input[type="range"]::-webkit-slider-thumb {
width:44px;
height:44px;
}
}
}
}
.controls-wrapper {
padding-top: @line-height;
small {
font-size: 100%;
opacity: 0.5;
}
.btn-group { -webkit-font-smoothing: antialiased; }
.btn-group,
.btn-group + .btn {
margin-right: @padding / 2;
}
.btn-group-vertical .btn {
text-align: left;
}
.form-group,
.btn-group {
margin-bottom: @line-height / 2;
}
.btn-group,
.btn-group-vertical,
.input-group .btn {
-webkit-font-smoothing: antialiased;
}
.input-group .btn:focus {
outline: none;
}
.btn-group-sm > .btn {
font-size: 13px;
padding: 5px;
}
.btn:active,
.btn.active {
outline: 0;
background-image: none;
-webkit-box-shadow: none;
box-shadow: none;
}
input[type="range"] {
width: 100%;
}
label {
display: block;
font-weight: normal;
letter-spacing: 0.1rem;
color: #000;
&.btn {
color: #fff;
}
.tooltip {
text-transform: none;
letter-spacing: 0;
}
}
.help { color: @gray-light; }
}
.warning {
font-size: 12px;
font-style: italic;
margin-top: 6px;
}
================================================
FILE: less/factlist.less
================================================
@import "less/lib/bootstrap/bootstrap";
@import "less/lib/font-awesome/font-awesome";
@import "less/medium";
@import "less/variables";
// Global variables
@img-root: "../assets";
@icon-sprite: "data:image/png;base64,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";
html {
min-width: 680px;
}
body {
background: @gray-lighter;
min-width: 680px;
}
canvas {
display: none;
background: @musicDark;
}
.content {
display: table;
width: 100%;
max-width: 1200px;
min-width: 640px;
margin: 0 auto;
.poster-wrapper,
.container {
display: table-cell;
vertical-align: top;
}
.poster-wrapper {
width: 640px;
}
@media screen and (max-width: @screen-sm-max){
display: block;
.poster-wrapper,
.container {
display: block;
}
}
@media screen and (max-width: @screen-xs-max){
.controls-wrapper {
font-size: 250%;
.btn {
font-size: 100%;
height: 88px;
min-width: 88px;
}
}
#fontsize {
display: block;
width: 90%;
}
}
.touch & {
input[type="range"]{
background: #fff;
width: 100%;
height: 88px;
-webkit-appearance: none;
border-radius: 8px;
-moz-border-radius: 8px;
-wekkit-border-radius: 8px;
border: 1px solid #ddd;
}
input[type="range"]::-webkit-slider-thumb{
-webkit-appearance:none !important;
width:88px;
height:88px;
-webkit-appearance: none;
border-radius: 8px;
-moz-border-radius: 8px;
-wekkit-border-radius: 8px;
border:none;
background: #aaa;
}
@media screen and (min-width: @screen-sm){
input[type="range"]{
height: 44px;
}
input[type="range"]::-webkit-slider-thumb {
width:44px;
height:44px;
}
}
}
}
.controls-wrapper {
padding-top: @line-height;
small {
font-size: 100%;
opacity: 0.5;
}
.btn-group,
.btn-group + .btn {
margin-right: @padding / 2;
}
.btn-group-vertical .btn {
text-align: left;
}
.form-group,
.btn-group {
margin-bottom: @line-height / 2;
}
.btn-group,
.btn-group-vertical,
.input-group .btn {
-webkit-font-smoothing: antialiased;
}
.input-group .btn:focus {
outline: none;
}
.btn-group-sm > .btn {
font-size: 13px;
padding: 5px;
}
.btn:active,
.btn.active {
outline: 0;
background-image: none;
-webkit-box-shadow: none;
box-shadow: none;
}
label {
display: block;
font-weight: normal;
font-family: @condensed-font-family;
letter-spacing: 0.1rem;
.tooltip {
text-transform: none;
letter-spacing: 0;
}
}
.help {
color: @gray-light;
}
}
.poster {
height: 640px;
width: 640px;
background: @bg-color;
margin: @line-height;
position: relative;
padding: @padding * 2;
font-size: @base-font-size * 2;
line-height: 1 !important;
overflow: hidden;
color: @text-color;
&.poster-theme1 {
background: @bg-color;
color: @text-color;
.logo-wrapper {
background-image: @logo-path;
background-repeat: no-repeat;
background-size: @logo-sq-width @logo-sq-height;
width: @logo-sq-width + @padding;
height: @logo-sq-height + @padding;
.sixteen-by-nine& {
background-size: @logo-16x9-width @logo-16x9-height;
width: @logo-16x9-width + @padding;
height: @logo-16x9-height + @padding;
}
}
}
&.poster-theme2 {
background: @theme2-bg-color;
color: @theme2-text-color;
.logo-wrapper {
background: @theme2-bg-color;
color: @theme2-text-color;
background-image: @theme2-logo-path;
background-repeat: no-repeat;
background-size: @theme2-sq-logo-width @theme2-sq-logo-height;
width: @theme2-sq-logo-width + @padding;
height: @theme2-sq-logo-height + @padding;
.sixteen-by-nine& {
background-size: @theme2-16x9-logo-width @theme2-16x9-logo-height;
width: @theme2-16x9-logo-width + @padding;
height: @theme2-16x9-logo-height + @padding;
}
}
}
&.poster-theme3 {
background: @theme3-bg-color;
color: @theme3-text-color;
.logo-wrapper {
background: @theme3-bg-color;
color: @theme3-text-color;
background-image: @theme3-logo-path;
background-repeat: no-repeat;
background-size: @theme3-sq-logo-width @theme3-sq-logo-height;
width: @theme3-sq-logo-width + @padding;
height: @theme3-sq-logo-height + @padding;
.sixteen-by-nine& {
background-size: @theme3-16x9-logo-width @theme3-16x9-logo-height;
width: @theme3-16x9-logo-width + @padding;
height: @theme3-16x9-logo-height + @padding;
}
}
}
&.poster-theme4 {
background: @theme4-bg-color;
color: @theme4-text-color;
.logo-wrapper {
background: @theme4-bg-color;
color: @theme4-text-color;
background-image: @theme4-logo-path;
background-repeat: no-repeat;
background-size: @theme4-sq-logo-width @theme4-sq-logo-height;
width: @theme4-sq-logo-width + @padding;
height: @theme4-sq-logo-height + @padding;
.sixteen-by-nine& {
background-size: @theme4-16x9-logo-width @theme4-16x9-logo-height;
width: @theme4-16x9-logo-width + @padding;
height: @theme4-16x9-logo-height + @padding;
}
}
}
&.sixteen-by-nine {
height: 360px;
padding: @padding;
.quote& {
.left-quote {
top: @padding;
left: @padding;
}
}
blockquote {
margin-bottom: 20px;
}
.logo-wrapper {
padding: @padding;
img {
width: 150px;
}
.poster-music& {
padding-bottom: @padding - 10;
}
}
}
h2 {
text-transform: uppercase;
letter-spacing: 0.05em;
font-size: @base-font-size;
font-family: @condensed-font-family;
margin-top: 0;
margin-bottom: 1em;
overflow: hidden;
}
.kicker {
white-space: nowrap;
}
blockquote {
padding: 0 0 0 1em;
margin: 0 0 @line-height * 2;
border-left: none;
font-size: inherit;
line-height: 100%;
font-weight: normal;
display: block;
overflow: visible;
&:focus {
border: none;
outline: none;
}
p {
font-size: inherit;
font-weight: inherit;
margin: 0;
line-height: 110%;
text-indent: @list-text-indent;
margin-left: @list-margin-left;
margin-bottom: @list-margin-bottom;
span {
line-height: inherit !important;
}
&:before {
content: @bullet-symbol;
margin-right: @bullet-margin;
}
}
&:last-child {
color: red;
}
}
p.timestamp {
font-size: @base-font-size;
color: @timestamp-color;
text-transform: uppercase;
letter-spacing: 0.1em;
font-weight: normal;
line-height: 1.3;
display: block;
margin-top: 0;
z-index: 101;
position: relative;
&:after {
content: @timestamp-timezone;
}
}
.logo-wrapper {
position: absolute;
width: 100%;
z-index: 100;
bottom: 0;
right: 0;
text-align: right;
padding: @padding @padding * 2 @padding * 2;
margin: 0;
font-size: 46px;
background: @bg-color;
background-image: @logo-path;
background-repeat: no-repeat;
background-size: @logo-sq-width @logo-sq-height;
width: @logo-sq-width + @padding;
height: @logo-sq-height + @padding;
.sixteen-by-nine& {
background-size: @logo-16x9-width @logo-16x9-height;
width: @logo-16x9-width + @padding;
height: @logo-16x9-height + @padding;
}
.logo {
.hide;
}
}
}
.warning {
font-size: 12px;
font-style: italic;
margin-top: 6px;
}
================================================
FILE: less/lib/bootstrap/alerts.less
================================================
//
// Alerts
// --------------------------------------------------
// Base styles
// -------------------------
.alert {
padding: @alert-padding;
margin-bottom: @line-height-computed;
border: 1px solid transparent;
border-radius: @alert-border-radius;
// Headings for larger alerts
h4 {
margin-top: 0;
// Specified for the h4 to prevent conflicts of changing @headings-color
color: inherit;
}
// Provide class for links that match alerts
.alert-link {
font-weight: @alert-link-font-weight;
}
// Improve alignment and spacing of inner content
> p,
> ul {
margin-bottom: 0;
}
> p + p {
margin-top: 5px;
}
}
// Dismissable alerts
//
// Expand the right padding and account for the close button's positioning.
.alert-dismissable {
padding-right: (@alert-padding + 20);
// Adjust close link position
.close {
position: relative;
top: -2px;
right: -21px;
color: inherit;
}
}
// Alternate styles
//
// Generate contextual modifier classes for colorizing the alert.
.alert-success {
.alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text);
}
.alert-info {
.alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text);
}
.alert-warning {
.alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);
}
.alert-danger {
.alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);
}
================================================
FILE: less/lib/bootstrap/badges.less
================================================
//
// Badges
// --------------------------------------------------
// Base classes
.badge {
display: inline-block;
min-width: 10px;
padding: 3px 7px;
font-size: @font-size-small;
font-weight: @badge-font-weight;
color: @badge-color;
line-height: @badge-line-height;
vertical-align: baseline;
white-space: nowrap;
text-align: center;
background-color: @badge-bg;
border-radius: @badge-border-radius;
// Empty badges collapse automatically (not available in IE8)
&:empty {
display: none;
}
// Quick fix for badges in buttons
.btn & {
position: relative;
top: -1px;
}
}
// Hover state, but only for links
a.badge {
&:hover,
&:focus {
color: @badge-link-hover-color;
text-decoration: none;
cursor: pointer;
}
}
// Account for counters in navs
a.list-group-item.active > .badge,
.nav-pills > .active > a > .badge {
color: @badge-active-color;
background-color: @badge-active-bg;
}
.nav-pills > li > a > .badge {
margin-left: 3px;
}
================================================
FILE: less/lib/bootstrap/bootstrap.less
================================================
// Core variables and mixins
@import "variables.less";
@import "mixins.less";
// Reset
@import "normalize.less";
@import "print.less";
// Core CSS
@import "scaffolding.less";
@import "type.less";
@import "code.less";
@import "grid.less";
@import "tables.less";
@import "forms.less";
@import "buttons.less";
// Components
@import "component-animations.less";
@import "glyphicons.less";
@import "dropdowns.less";
@import "button-groups.less";
@import "input-groups.less";
@import "navs.less";
@import "navbar.less";
@import "breadcrumbs.less";
@import "pagination.less";
@import "pager.less";
@import "labels.less";
@import "badges.less";
@import "jumbotron.less";
@import "thumbnails.less";
@import "alerts.less";
@import "progress-bars.less";
@import "media.less";
@import "list-group.less";
@import "panels.less";
@import "wells.less";
@import "close.less";
// Components w/ JavaScript
@import "modals.less";
@import "tooltip.less";
@import "popovers.less";
@import "carousel.less";
// Utility classes
@import "utilities.less";
@import "responsive-utilities.less";
================================================
FILE: less/lib/bootstrap/breadcrumbs.less
================================================
//
// Breadcrumbs
// --------------------------------------------------
.breadcrumb {
padding: 8px 15px;
margin-bottom: @line-height-computed;
list-style: none;
background-color: @breadcrumb-bg;
border-radius: @border-radius-base;
> li {
display: inline-block;
+ li:before {
content: "@{breadcrumb-separator}\00a0"; // Unicode space added since inline-block means non-collapsing white-space
padding: 0 5px;
color: @breadcrumb-color;
}
}
> .active {
color: @breadcrumb-active-color;
}
}
================================================
FILE: less/lib/bootstrap/button-groups.less
================================================
//
// Button groups
// --------------------------------------------------
// Make the div behave like a button
.btn-group,
.btn-group-vertical {
position: relative;
display: inline-block;
vertical-align: middle; // match .btn alignment given font-size hack above
> .btn {
position: relative;
float: left;
// Bring the "active" button to the front
&:hover,
&:focus,
&:active,
&.active {
z-index: 2;
}
&:focus {
// Remove focus outline when dropdown JS adds it after closing the menu
outline: none;
}
}
}
// Prevent double borders when buttons are next to each other
.btn-group {
.btn + .btn,
.btn + .btn-group,
.btn-group + .btn,
.btn-group + .btn-group {
margin-left: -1px;
}
}
// Optional: Group multiple button groups together for a toolbar
.btn-toolbar {
.clearfix();
.btn-group {
float: left;
}
// Space out series of button groups
> .btn,
> .btn-group {
+ .btn,
+ .btn-group {
margin-left: 5px;
}
}
}
.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
border-radius: 0;
}
// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match
.btn-group > .btn:first-child {
margin-left: 0;
&:not(:last-child):not(.dropdown-toggle) {
.border-right-radius(0);
}
}
// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it
.btn-group > .btn:last-child:not(:first-child),
.btn-group > .dropdown-toggle:not(:first-child) {
.border-left-radius(0);
}
// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group)
.btn-group > .btn-group {
float: left;
}
.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
border-radius: 0;
}
.btn-group > .btn-group:first-child {
> .btn:last-child,
> .dropdown-toggle {
.border-right-radius(0);
}
}
.btn-group > .btn-group:last-child > .btn:first-child {
.border-left-radius(0);
}
// On active and open, don't show outline
.btn-group .dropdown-toggle:active,
.btn-group.open .dropdown-toggle {
outline: 0;
}
// Sizing
//
// Remix the default button sizing classes into new ones for easier manipulation.
.btn-group-xs > .btn { .btn-xs(); }
.btn-group-sm > .btn { .btn-sm(); }
.btn-group-lg > .btn { .btn-lg(); }
// Split button dropdowns
// ----------------------
// Give the line between buttons some depth
.btn-group > .btn + .dropdown-toggle {
padding-left: 8px;
padding-right: 8px;
}
.btn-group > .btn-lg + .dropdown-toggle {
padding-left: 12px;
padding-right: 12px;
}
// The clickable button for toggling the menu
// Remove the gradient and set the same inset shadow as the :active state
.btn-group.open .dropdown-toggle {
.box-shadow(inset 0 3px 5px rgba(0,0,0,.125));
// Show no shadow for `.btn-link` since it has no other button styles.
&.btn-link {
.box-shadow(none);
}
}
// Reposition the caret
.btn .caret {
margin-left: 0;
}
// Carets in other button sizes
.btn-lg .caret {
border-width: @caret-width-large @caret-width-large 0;
border-bottom-width: 0;
}
// Upside down carets for .dropup
.dropup .btn-lg .caret {
border-width: 0 @caret-width-large @caret-width-large;
}
// Vertical button groups
// ----------------------
.btn-group-vertical {
> .btn,
> .btn-group,
> .btn-group > .btn {
display: block;
float: none;
width: 100%;
max-width: 100%;
}
// Clear floats so dropdown menus can be properly placed
> .btn-group {
.clearfix();
> .btn {
float: none;
}
}
> .btn + .btn,
> .btn + .btn-group,
> .btn-group + .btn,
> .btn-group + .btn-group {
margin-top: -1px;
margin-left: 0;
}
}
.btn-group-vertical > .btn {
&:not(:first-child):not(:last-child) {
border-radius: 0;
}
&:first-child:not(:last-child) {
border-top-right-radius: @border-radius-base;
.border-bottom-radius(0);
}
&:last-child:not(:first-child) {
border-bottom-left-radius: @border-radius-base;
.border-top-radius(0);
}
}
.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
border-radius: 0;
}
.btn-group-vertical > .btn-group:first-child {
> .btn:last-child,
> .dropdown-toggle {
.border-bottom-radius(0);
}
}
.btn-group-vertical > .btn-group:last-child > .btn:first-child {
.border-top-radius(0);
}
// Justified button groups
// ----------------------
.btn-group-justified {
display: table;
width: 100%;
table-layout: fixed;
border-collapse: separate;
> .btn,
> .btn-group {
float: none;
display: table-cell;
width: 1%;
}
> .btn-group .btn {
width: 100%;
}
}
// Checkbox and radio options
[data-toggle="buttons"] > .btn > input[type="radio"],
[data-toggle="buttons"] > .btn > input[type="checkbox"] {
display: none;
}
================================================
FILE: less/lib/bootstrap/buttons.less
================================================
//
// Buttons
// --------------------------------------------------
// Base styles
// --------------------------------------------------
.btn {
display: inline-block;
margin-bottom: 0; // For input.btn
font-weight: @btn-font-weight;
text-align: center;
vertical-align: middle;
cursor: pointer;
background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214
border: 1px solid transparent;
white-space: nowrap;
.button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @border-radius-base);
.user-select(none);
&:focus {
.tab-focus();
}
&:hover,
&:focus {
color: @btn-default-color;
text-decoration: none;
}
&:active,
&.active {
outline: 0;
background-image: none;
.box-shadow(inset 0 3px 5px rgba(0,0,0,.125));
}
&.disabled,
&[disabled],
fieldset[disabled] & {
cursor: not-allowed;
pointer-events: none; // Future-proof disabling of clicks
.opacity(.65);
.box-shadow(none);
}
}
// Alternate buttons
// --------------------------------------------------
.btn-default {
.button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);
}
.btn-primary {
.button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);
}
// Warning appears as orange
.btn-warning {
.button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);
}
// Danger and error appear as red
.btn-danger {
.button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);
}
// Success appears as green
.btn-success {
.button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);
}
// Info appears as blue-green
.btn-info {
.button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);
}
// Link buttons
// -------------------------
// Make a button look and behave like a link
.btn-link {
color: @link-color;
font-weight: normal;
cursor: pointer;
border-radius: 0;
&,
&:active,
&[disabled],
fieldset[disabled] & {
background-color: transparent;
.box-shadow(none);
}
&,
&:hover,
&:focus,
&:active {
border-color: transparent;
}
&:hover,
&:focus {
color: @link-hover-color;
text-decoration: underline;
background-color: transparent;
}
&[disabled],
fieldset[disabled] & {
&:hover,
&:focus {
color: @btn-link-disabled-color;
text-decoration: none;
}
}
}
// Button Sizes
// --------------------------------------------------
.btn-lg {
// line-height: ensure even-numbered height of button next to large input
.button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);
}
.btn-sm {
// line-height: ensure proper height of button next to small input
.button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);
}
.btn-xs {
.button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @border-radius-small);
}
// Block button
// --------------------------------------------------
.btn-block {
display: block;
width: 100%;
padding-left: 0;
padding-right: 0;
}
// Vertically space out multiple block buttons
.btn-block + .btn-block {
margin-top: 5px;
}
// Specificity overrides
input[type="submit"],
input[type="reset"],
input[type="button"] {
&.btn-block {
width: 100%;
}
}
================================================
FILE: less/lib/bootstrap/carousel.less
================================================
//
// Carousel
// --------------------------------------------------
// Wrapper for the slide container and indicators
.carousel {
position: relative;
}
.carousel-inner {
position: relative;
overflow: hidden;
width: 100%;
> .item {
display: none;
position: relative;
.transition(.6s ease-in-out left);
// Account for jankitude on images
> img,
> a > img {
.img-responsive();
line-height: 1;
}
}
> .active,
> .next,
> .prev { display: block; }
> .active {
left: 0;
}
> .next,
> .prev {
position: absolute;
top: 0;
width: 100%;
}
> .next {
left: 100%;
}
> .prev {
left: -100%;
}
> .next.left,
> .prev.right {
left: 0;
}
> .active.left {
left: -100%;
}
> .active.right {
left: 100%;
}
}
// Left/right controls for nav
// ---------------------------
.carousel-control {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: @carousel-control-width;
.opacity(@carousel-control-opacity);
font-size: @carousel-control-font-size;
color: @carousel-control-color;
text-align: center;
text-shadow: @carousel-text-shadow;
// We can't have this transition here because WebKit cancels the carousel
// animation if you trip this while in the middle of another animation.
// Set gradients for backgrounds
&.left {
#gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001));
}
&.right {
left: auto;
right: 0;
#gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5));
}
// Hover/focus state
&:hover,
&:focus {
outline: none;
color: @carousel-control-color;
text-decoration: none;
.opacity(.9);
}
// Toggles
.icon-prev,
.icon-next,
.glyphicon-chevron-left,
.glyphicon-chevron-right {
position: absolute;
top: 50%;
z-index: 5;
display: inline-block;
}
.icon-prev,
.glyphicon-chevron-left {
left: 50%;
}
.icon-next,
.glyphicon-chevron-right {
right: 50%;
}
.icon-prev,
.icon-next {
width: 20px;
height: 20px;
margin-top: -10px;
margin-left: -10px;
font-family: serif;
}
.icon-prev {
&:before {
content: '\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)
}
}
.icon-next {
&:before {
content: '\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)
}
}
}
// Optional indicator pips
//
// Add an unordered list with the following class and add a list item for each
// slide your carousel holds.
.carousel-indicators {
position: absolute;
bottom: 10px;
left: 50%;
z-index: 15;
width: 60%;
margin-left: -30%;
padding-left: 0;
list-style: none;
text-align: center;
li {
display: inline-block;
width: 10px;
height: 10px;
margin: 1px;
text-indent: -999px;
border: 1px solid @carousel-indicator-border-color;
border-radius: 10px;
cursor: pointer;
// IE8-9 hack for event handling
//
// Internet Explorer 8-9 does not support clicks on elements without a set
// `background-color`. We cannot use `filter` since that's not viewed as a
// background color by the browser. Thus, a hack is needed.
//
// For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we
// set alpha transparency for the best results possible.
background-color: #000 \9; // IE8
background-color: rgba(0,0,0,0); // IE9
}
.active {
margin: 0;
width: 12px;
height: 12px;
background-color: @carousel-indicator-active-bg;
}
}
// Optional captions
// -----------------------------
// Hidden by default for smaller viewports
.carousel-caption {
position: absolute;
left: 15%;
right: 15%;
bottom: 20px;
z-index: 10;
padding-top: 20px;
padding-bottom: 20px;
color: @carousel-caption-color;
text-align: center;
text-shadow: @carousel-text-shadow;
& .btn {
text-shadow: none; // No shadow for button elements in carousel-caption
}
}
// Scale up controls for tablets and up
@media screen and (min-width: @screen-sm-min) {
// Scale up the controls a smidge
.carousel-control {
.glyphicons-chevron-left,
.glyphicons-chevron-right,
.icon-prev,
.icon-next {
width: 30px;
height: 30px;
margin-top: -15px;
margin-left: -15px;
font-size: 30px;
}
}
// Show and left align the captions
.carousel-caption {
left: 20%;
right: 20%;
padding-bottom: 30px;
}
// Move up the indicators
.carousel-indicators {
bottom: 20px;
}
}
================================================
FILE: less/lib/bootstrap/close.less
================================================
//
// Close icons
// --------------------------------------------------
.close {
float: right;
font-size: (@font-size-base * 1.5);
font-weight: @close-font-weight;
line-height: 1;
color: @close-color;
text-shadow: @close-text-shadow;
.opacity(.2);
&:hover,
&:focus {
color: @close-color;
text-decoration: none;
cursor: pointer;
.opacity(.5);
}
// Additional properties for button version
// iOS requires the button element instead of an anchor tag.
// If you want the anchor version, it requires `href="#"`.
button& {
padding: 0;
cursor: pointer;
background: transparent;
border: 0;
-webkit-appearance: none;
}
}
================================================
FILE: less/lib/bootstrap/code.less
================================================
//
// Code (inline and block)
// --------------------------------------------------
// Inline and block code styles
code,
kbd,
pre,
samp {
font-family: @font-family-monospace;
}
// Inline code
code {
padding: 2px 4px;
font-size: 90%;
color: @code-color;
background-color: @code-bg;
white-space: nowrap;
border-radius: @border-radius-base;
}
// Blocks of code
pre {
display: block;
padding: ((@line-height-computed - 1) / 2);
margin: 0 0 (@line-height-computed / 2);
font-size: (@font-size-base - 1); // 14px to 13px
line-height: @line-height-base;
word-break: break-all;
word-wrap: break-word;
color: @pre-color;
background-color: @pre-bg;
border: 1px solid @pre-border-color;
border-radius: @border-radius-base;
// Account for some code outputs that place code tags in pre tags
code {
padding: 0;
font-size: inherit;
color: inherit;
white-space: pre-wrap;
background-color: transparent;
border-radius: 0;
}
}
// Enable scrollable blocks of code
.pre-scrollable {
max-height: @pre-scrollable-max-height;
overflow-y: scroll;
}
================================================
FILE: less/lib/bootstrap/component-animations.less
================================================
//
// Component animations
// --------------------------------------------------
// Heads up!
//
// We don't use the `.opacity()` mixin here since it causes a bug with text
// fields in IE7-8. Source: https://github.com/twitter/bootstrap/pull/3552.
.fade {
opacity: 0;
.transition(opacity .15s linear);
&.in {
opacity: 1;
}
}
.collapse {
display: none;
&.in {
display: block;
}
}
.collapsing {
position: relative;
height: 0;
overflow: hidden;
.transition(height .35s ease);
}
================================================
FILE: less/lib/bootstrap/dropdowns.less
================================================
//
// Dropdown menus
// --------------------------------------------------
// Dropdown arrow/caret
.caret {
display: inline-block;
width: 0;
height: 0;
margin-left: 2px;
vertical-align: middle;
border-top: @caret-width-base solid;
border-right: @caret-width-base solid transparent;
border-left: @caret-width-base solid transparent;
}
// The dropdown wrapper (div)
.dropdown {
position: relative;
}
// Prevent the focus on the dropdown toggle when closing dropdowns
.dropdown-toggle:focus {
outline: 0;
}
// The dropdown menu (ul)
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
z-index: @zindex-dropdown;
display: none; // none by default, but block on "open" of the menu
float: left;
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0; // override default ul
list-style: none;
font-size: @font-size-base;
background-color: @dropdown-bg;
border: 1px solid @dropdown-fallback-border; // IE8 fallback
border: 1px solid @dropdown-border;
border-radius: @border-radius-base;
.box-shadow(0 6px 12px rgba(0,0,0,.175));
background-clip: padding-box;
// Aligns the dropdown menu to right
&.pull-right {
right: 0;
left: auto;
}
// Dividers (basically an hr) within the dropdown
.divider {
.nav-divider(@dropdown-divider-bg);
}
// Links within the dropdown menu
> li > a {
display: block;
padding: 3px 20px;
clear: both;
font-weight: normal;
line-height: @line-height-base;
color: @dropdown-link-color;
white-space: nowrap; // prevent links from randomly breaking onto new lines
}
}
// Hover/Focus state
.dropdown-menu > li > a {
&:hover,
&:focus {
text-decoration: none;
color: @dropdown-link-hover-color;
background-color: @dropdown-link-hover-bg;
}
}
// Active state
.dropdown-menu > .active > a {
&,
&:hover,
&:focus {
color: @dropdown-link-active-color;
text-decoration: none;
outline: 0;
background-color: @dropdown-link-active-bg;
}
}
// Disabled state
//
// Gray out text and ensure the hover/focus state remains gray
.dropdown-menu > .disabled > a {
&,
&:hover,
&:focus {
color: @dropdown-link-disabled-color;
}
}
// Nuke hover/focus effects
.dropdown-menu > .disabled > a {
&:hover,
&:focus {
text-decoration: none;
background-color: transparent;
background-image: none; // Remove CSS gradient
.reset-filter();
cursor: not-allowed;
}
}
// Open state for the dropdown
.open {
// Show the menu
> .dropdown-menu {
display: block;
}
// Remove the outline when :focus is triggered
> a {
outline: 0;
}
}
// Dropdown section headers
.dropdown-header {
display: block;
padding: 3px 20px;
font-size: @font-size-small;
line-height: @line-height-base;
color: @dropdown-header-color;
}
// Backdrop to catch body clicks on mobile, etc.
.dropdown-backdrop {
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
z-index: @zindex-dropdown - 10;
}
// Right aligned dropdowns
.pull-right > .dropdown-menu {
right: 0;
left: auto;
}
// Allow for dropdowns to go bottom up (aka, dropup-menu)
//
// Just add .dropup after the standard .dropdown class and you're set, bro.
// TODO: abstract this so that the navbar fixed styles are not placed here?
.dropup,
.navbar-fixed-bottom .dropdown {
// Reverse the caret
.caret {
border-top: 0;
border-bottom: @caret-width-base solid;
content: "";
}
// Different positioning for bottom up menu
.dropdown-menu {
top: auto;
bottom: 100%;
margin-bottom: 1px;
}
}
// Component alignment
//
// Reiterate per navbar.less and the modified component alignment there.
@media (min-width: @grid-float-breakpoint) {
.navbar-right {
.dropdown-menu {
.pull-right > .dropdown-menu();
}
}
}
================================================
FILE: less/lib/bootstrap/forms.less
================================================
//
// Forms
// --------------------------------------------------
// Normalize non-controls
//
// Restyle and baseline non-control form elements.
fieldset {
padding: 0;
margin: 0;
border: 0;
}
legend {
display: block;
width: 100%;
padding: 0;
margin-bottom: @line-height-computed;
font-size: (@font-size-base * 1.5);
line-height: inherit;
color: @legend-color;
border: 0;
border-bottom: 1px solid @legend-border-color;
}
label {
display: inline-block;
margin-bottom: 5px;
font-weight: bold;
}
// Normalize form controls
// Override content-box in Normalize (* isn't specific enough)
input[type="search"] {
.box-sizing(border-box);
}
// Position radios and checkboxes better
input[type="radio"],
input[type="checkbox"] {
margin: 4px 0 0;
margin-top: 1px \9; /* IE8-9 */
line-height: normal;
}
// Set the height of select and file controls to match text inputs
input[type="file"] {
display: block;
}
// Make multiple select elements height not fixed
select[multiple],
select[size] {
height: auto;
}
// Fix optgroup Firefox bug per https://github.com/twbs/bootstrap/issues/7611
select optgroup {
font-size: inherit;
font-style: inherit;
font-family: inherit;
}
// Focus for select, file, radio, and checkbox
input[type="file"]:focus,
input[type="radio"]:focus,
input[type="checkbox"]:focus {
.tab-focus();
}
// Fix for Chrome number input
// Setting certain font-sizes causes the `I` bar to appear on hover of the bottom increment button.
// See https://github.com/twbs/bootstrap/issues/8350 for more.
input[type="number"] {
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
height: auto;
}
}
// Adjust output element
output {
display: block;
padding-top: (@padding-base-vertical + 1);
font-size: @font-size-base;
line-height: @line-height-base;
color: @input-color;
vertical-align: middle;
}
// Common form controls
//
// Shared size and type resets for form controls. Apply `.form-control` to any
// of the following form controls:
//
// select
// textarea
// input[type="text"]
// input[type="password"]
// input[type="datetime"]
// input[type="datetime-local"]
// input[type="date"]
// input[type="month"]
// input[type="time"]
// input[type="week"]
// input[type="number"]
// input[type="email"]
// input[type="url"]
// input[type="search"]
// input[type="tel"]
// input[type="color"]
.form-control {
display: block;
width: 100%;
height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)
padding: @padding-base-vertical @padding-base-horizontal;
font-size: @font-size-base;
line-height: @line-height-base;
color: @input-color;
vertical-align: middle;
background-color: @input-bg;
background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214
border: 1px solid @input-border;
border-radius: @input-border-radius;
.box-shadow(inset 0 1px 1px rgba(0,0,0,.075));
.transition(~"border-color ease-in-out .15s, box-shadow ease-in-out .15s");
// Customize the `:focus` state to imitate native WebKit styles.
.form-control-focus();
// Placeholder
//
// Placeholder text gets special styles because when browsers invalidate entire
// lines if it doesn't understand a selector/
.placeholder();
// Disabled and read-only inputs
// Note: HTML5 says that controls under a fieldset > legend:first-child won't
// be disabled if the fieldset is disabled. Due to implementation difficulty,
// we don't honor that edge case; we style them as disabled anyway.
&[disabled],
&[readonly],
fieldset[disabled] & {
cursor: not-allowed;
background-color: @input-bg-disabled;
}
// Reset height for `textarea`s
textarea& {
height: auto;
}
}
// Form groups
//
// Designed to help with the organization and spacing of vertical forms. For
// horizontal forms, use the predefined grid classes.
.form-group {
margin-bottom: 15px;
}
// Checkboxes and radios
//
// Indent the labels to position radios/checkboxes as hanging controls.
.radio,
.checkbox {
display: block;
min-height: @line-height-computed; // clear the floating input if there is no label text
margin-top: 10px;
margin-bottom: 10px;
padding-left: 20px;
vertical-align: middle;
label {
display: inline;
margin-bottom: 0;
font-weight: normal;
cursor: pointer;
}
}
.radio input[type="radio"],
.radio-inline input[type="radio"],
.checkbox input[type="checkbox"],
.checkbox-inline input[type="checkbox"] {
float: left;
margin-left: -20px;
}
.radio + .radio,
.checkbox + .checkbox {
margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing
}
// Radios and checkboxes on same line
.radio-inline,
.checkbox-inline {
display: inline-block;
padding-left: 20px;
margin-bottom: 0;
vertical-align: middle;
font-weight: normal;
cursor: pointer;
}
.radio-inline + .radio-inline,
.checkbox-inline + .checkbox-inline {
margin-top: 0;
margin-left: 10px; // space out consecutive inline controls
}
// Apply same disabled cursor tweak as for inputs
//
// Note: Neither radios nor checkboxes can be readonly.
input[type="radio"],
input[type="checkbox"],
.radio,
.radio-inline,
.checkbox,
.checkbox-inline {
&[disabled],
fieldset[disabled] & {
cursor: not-allowed;
}
}
// Form control sizing
.input-sm {
.input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);
}
.input-lg {
.input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);
}
// Form control feedback states
//
// Apply contextual and semantic states to individual form controls.
// Warning
.has-warning {
.form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg);
}
// Error
.has-error {
.form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg);
}
// Success
.has-success {
.form-control-validation(@state-success-text; @state-success-text; @state-success-bg);
}
// Static form control text
//
// Apply class to a `p` element to make any string of text align with labels in
// a horizontal form layout.
.form-control-static {
margin-bottom: 0; // Remove default margin from `p`
}
// Help text
//
// Apply to any element you wish to create light text for placement immediately
// below a form control. Use for general help, formatting, or instructional text.
.help-block {
display: block; // account for any element using help-block
margin-top: 5px;
margin-bottom: 10px;
color: lighten(@text-color, 25%); // lighten the text some for contrast
}
// Inline forms
//
// Make forms appear inline(-block) by adding the `.form-inline` class. Inline
// forms begin stacked on extra small (mobile) devices and then go inline when
// viewports reach <768px.
//
// Requires wrapping inputs and labels with `.form-group` for proper display of
// default HTML form controls and our custom form controls (e.g., input groups).
//
// Heads up! This is mixin-ed into `.navbar-form` in navbars.less.
.form-inline {
// Kick in the inline
@media (min-width: @screen-sm) {
// Inline-block all the things for "inline"
.form-group {
display: inline-block;
margin-bottom: 0;
vertical-align: middle;
}
// In navbar-form, allow folks to *not* use `.form-group`
.form-control {
display: inline-block;
}
// Override `width: 100%;` when not within a `.form-group`
select.form-control {
width: auto;
}
// Remove default margin on radios/checkboxes that were used for stacking, and
// then undo the floating of radios and checkboxes to match (which also avoids
// a bug in WebKit: https://github.com/twbs/bootstrap/issues/1969).
.radio,
.checkbox {
display: inline-block;
margin-top: 0;
margin-bottom: 0;
padding-left: 0;
}
.radio input[type="radio"],
.checkbox input[type="checkbox"] {
float: none;
margin-left: 0;
}
}
}
// Horizontal forms
//
// Horizontal forms are built on grid classes and allow you to create forms with
// labels on the left and inputs on the right.
.form-horizontal {
// Consistent vertical alignment of labels, radios, and checkboxes
.control-label,
.radio,
.checkbox,
.radio-inline,
.checkbox-inline {
margin-top: 0;
margin-bottom: 0;
padding-top: (@padding-base-vertical + 1); // Default padding plus a border
}
// Account for padding we're adding to ensure the alignment and of help text
// and other content below items
.radio,
.checkbox {
min-height: @line-height-computed + (@padding-base-vertical + 1);
}
// Make form groups behave like rows
.form-group {
.make-row();
}
.form-control-static {
padding-top: (@padding-base-vertical + 1);
}
// Only right align form labels here when the columns stop stacking
@media (min-width: @screen-sm-min) {
.control-label {
text-align: right;
}
}
}
================================================
FILE: less/lib/bootstrap/glyphicons.less
================================================
//
// Glyphicons for Bootstrap
//
// Since icons are fonts, they can be placed anywhere text is placed and are
// thus automatically sized to match the surrounding child. To use, create an
// inline element with the appropriate classes, like so:
//
// Star
// Import the fonts
@font-face {
font-family: 'Glyphicons Halflings';
src: ~"url('@{icon-font-path}@{icon-font-name}.eot')";
src: ~"url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype')",
~"url('@{icon-font-path}@{icon-font-name}.woff') format('woff')",
~"url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype')",
~"url('@{icon-font-path}@{icon-font-name}.svg#glyphicons-halflingsregular') format('svg')";
}
// Catchall baseclass
.glyphicon {
position: relative;
top: 1px;
display: inline-block;
font-family: 'Glyphicons Halflings';
font-style: normal;
font-weight: normal;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
&:empty {
width: 1em;
}
}
// Individual icons
.glyphicon-asterisk { &:before { content: "\2a"; } }
.glyphicon-plus { &:before { content: "\2b"; } }
.glyphicon-euro { &:before { content: "\20ac"; } }
.glyphicon-minus { &:before { content: "\2212"; } }
.glyphicon-cloud { &:before { content: "\2601"; } }
.glyphicon-envelope { &:before { content: "\2709"; } }
.glyphicon-pencil { &:before { content: "\270f"; } }
.glyphicon-glass { &:before { content: "\e001"; } }
.glyphicon-music { &:before { content: "\e002"; } }
.glyphicon-search { &:before { content: "\e003"; } }
.glyphicon-heart { &:before { content: "\e005"; } }
.glyphicon-star { &:before { content: "\e006"; } }
.glyphicon-star-empty { &:before { content: "\e007"; } }
.glyphicon-user { &:before { content: "\e008"; } }
.glyphicon-film { &:before { content: "\e009"; } }
.glyphicon-th-large { &:before { content: "\e010"; } }
.glyphicon-th { &:before { content: "\e011"; } }
.glyphicon-th-list { &:before { content: "\e012"; } }
.glyphicon-ok { &:before { content: "\e013"; } }
.glyphicon-remove { &:before { content: "\e014"; } }
.glyphicon-zoom-in { &:before { content: "\e015"; } }
.glyphicon-zoom-out { &:before { content: "\e016"; } }
.glyphicon-off { &:before { content: "\e017"; } }
.glyphicon-signal { &:before { content: "\e018"; } }
.glyphicon-cog { &:before { content: "\e019"; } }
.glyphicon-trash { &:before { content: "\e020"; } }
.glyphicon-home { &:before { content: "\e021"; } }
.glyphicon-file { &:before { content: "\e022"; } }
.glyphicon-time { &:before { content: "\e023"; } }
.glyphicon-road { &:before { content: "\e024"; } }
.glyphicon-download-alt { &:before { content: "\e025"; } }
.glyphicon-download { &:before { content: "\e026"; } }
.glyphicon-upload { &:before { content: "\e027"; } }
.glyphicon-inbox { &:before { content: "\e028"; } }
.glyphicon-play-circle { &:before { content: "\e029"; } }
.glyphicon-repeat { &:before { content: "\e030"; } }
.glyphicon-refresh { &:before { content: "\e031"; } }
.glyphicon-list-alt { &:before { content: "\e032"; } }
.glyphicon-lock { &:before { content: "\e033"; } }
.glyphicon-flag { &:before { content: "\e034"; } }
.glyphicon-headphones { &:before { content: "\e035"; } }
.glyphicon-volume-off { &:before { content: "\e036"; } }
.glyphicon-volume-down { &:before { content: "\e037"; } }
.glyphicon-volume-up { &:before { content: "\e038"; } }
.glyphicon-qrcode { &:before { content: "\e039"; } }
.glyphicon-barcode { &:before { content: "\e040"; } }
.glyphicon-tag { &:before { content: "\e041"; } }
.glyphicon-tags { &:before { content: "\e042"; } }
.glyphicon-book { &:before { content: "\e043"; } }
.glyphicon-bookmark { &:before { content: "\e044"; } }
.glyphicon-print { &:before { content: "\e045"; } }
.glyphicon-camera { &:before { content: "\e046"; } }
.glyphicon-font { &:before { content: "\e047"; } }
.glyphicon-bold { &:before { content: "\e048"; } }
.glyphicon-italic { &:before { content: "\e049"; } }
.glyphicon-text-height { &:before { content: "\e050"; } }
.glyphicon-text-width { &:before { content: "\e051"; } }
.glyphicon-align-left { &:before { content: "\e052"; } }
.glyphicon-align-center { &:before { content: "\e053"; } }
.glyphicon-align-right { &:before { content: "\e054"; } }
.glyphicon-align-justify { &:before { content: "\e055"; } }
.glyphicon-list { &:before { content: "\e056"; } }
.glyphicon-indent-left { &:before { content: "\e057"; } }
.glyphicon-indent-right { &:before { content: "\e058"; } }
.glyphicon-facetime-video { &:before { content: "\e059"; } }
.glyphicon-picture { &:before { content: "\e060"; } }
.glyphicon-map-marker { &:before { content: "\e062"; } }
.glyphicon-adjust { &:before { content: "\e063"; } }
.glyphicon-tint { &:before { content: "\e064"; } }
.glyphicon-edit { &:before { content: "\e065"; } }
.glyphicon-share { &:before { content: "\e066"; } }
.glyphicon-check { &:before { content: "\e067"; } }
.glyphicon-move { &:before { content: "\e068"; } }
.glyphicon-step-backward { &:before { content: "\e069"; } }
.glyphicon-fast-backward { &:before { content: "\e070"; } }
.glyphicon-backward { &:before { content: "\e071"; } }
.glyphicon-play { &:before { content: "\e072"; } }
.glyphicon-pause { &:before { content: "\e073"; } }
.glyphicon-stop { &:before { content: "\e074"; } }
.glyphicon-forward { &:before { content: "\e075"; } }
.glyphicon-fast-forward { &:before { content: "\e076"; } }
.glyphicon-step-forward { &:before { content: "\e077"; } }
.glyphicon-eject { &:before { content: "\e078"; } }
.glyphicon-chevron-left { &:before { content: "\e079"; } }
.glyphicon-chevron-right { &:before { content: "\e080"; } }
.glyphicon-plus-sign { &:before { content: "\e081"; } }
.glyphicon-minus-sign { &:before { content: "\e082"; } }
.glyphicon-remove-sign { &:before { content: "\e083"; } }
.glyphicon-ok-sign { &:before { content: "\e084"; } }
.glyphicon-question-sign { &:before { content: "\e085"; } }
.glyphicon-info-sign { &:before { content: "\e086"; } }
.glyphicon-screenshot { &:before { content: "\e087"; } }
.glyphicon-remove-circle { &:before { content: "\e088"; } }
.glyphicon-ok-circle { &:before { content: "\e089"; } }
.glyphicon-ban-circle { &:before { content: "\e090"; } }
.glyphicon-arrow-left { &:before { content: "\e091"; } }
.glyphicon-arrow-right { &:before { content: "\e092"; } }
.glyphicon-arrow-up { &:before { content: "\e093"; } }
.glyphicon-arrow-down { &:before { content: "\e094"; } }
.glyphicon-share-alt { &:before { content: "\e095"; } }
.glyphicon-resize-full { &:before { content: "\e096"; } }
.glyphicon-resize-small { &:before { content: "\e097"; } }
.glyphicon-exclamation-sign { &:before { content: "\e101"; } }
.glyphicon-gift { &:before { content: "\e102"; } }
.glyphicon-leaf { &:before { content: "\e103"; } }
.glyphicon-fire { &:before { content: "\e104"; } }
.glyphicon-eye-open { &:before { content: "\e105"; } }
.glyphicon-eye-close { &:before { content: "\e106"; } }
.glyphicon-warning-sign { &:before { content: "\e107"; } }
.glyphicon-plane { &:before { content: "\e108"; } }
.glyphicon-calendar { &:before { content: "\e109"; } }
.glyphicon-random { &:before { content: "\e110"; } }
.glyphicon-comment { &:before { content: "\e111"; } }
.glyphicon-magnet { &:before { content: "\e112"; } }
.glyphicon-chevron-up { &:before { content: "\e113"; } }
.glyphicon-chevron-down { &:before { content: "\e114"; } }
.glyphicon-retweet { &:before { content: "\e115"; } }
.glyphicon-shopping-cart { &:before { content: "\e116"; } }
.glyphicon-folder-close { &:before { content: "\e117"; } }
.glyphicon-folder-open { &:before { content: "\e118"; } }
.glyphicon-resize-vertical { &:before { content: "\e119"; } }
.glyphicon-resize-horizontal { &:before { content: "\e120"; } }
.glyphicon-hdd { &:before { content: "\e121"; } }
.glyphicon-bullhorn { &:before { content: "\e122"; } }
.glyphicon-bell { &:before { content: "\e123"; } }
.glyphicon-certificate { &:before { content: "\e124"; } }
.glyphicon-thumbs-up { &:before { content: "\e125"; } }
.glyphicon-thumbs-down { &:before { content: "\e126"; } }
.glyphicon-hand-right { &:before { content: "\e127"; } }
.glyphicon-hand-left { &:before { content: "\e128"; } }
.glyphicon-hand-up { &:before { content: "\e129"; } }
.glyphicon-hand-down { &:before { content: "\e130"; } }
.glyphicon-circle-arrow-right { &:before { content: "\e131"; } }
.glyphicon-circle-arrow-left { &:before { content: "\e132"; } }
.glyphicon-circle-arrow-up { &:before { content: "\e133"; } }
.glyphicon-circle-arrow-down { &:before { content: "\e134"; } }
.glyphicon-globe { &:before { content: "\e135"; } }
.glyphicon-wrench { &:before { content: "\e136"; } }
.glyphicon-tasks { &:before { content: "\e137"; } }
.glyphicon-filter { &:before { content: "\e138"; } }
.glyphicon-briefcase { &:before { content: "\e139"; } }
.glyphicon-fullscreen { &:before { content: "\e140"; } }
.glyphicon-dashboard { &:before { content: "\e141"; } }
.glyphicon-paperclip { &:before { content: "\e142"; } }
.glyphicon-heart-empty { &:before { content: "\e143"; } }
.glyphicon-link { &:before { content: "\e144"; } }
.glyphicon-phone { &:before { content: "\e145"; } }
.glyphicon-pushpin { &:before { content: "\e146"; } }
.glyphicon-usd { &:before { content: "\e148"; } }
.glyphicon-gbp { &:before { content: "\e149"; } }
.glyphicon-sort { &:before { content: "\e150"; } }
.glyphicon-sort-by-alphabet { &:before { content: "\e151"; } }
.glyphicon-sort-by-alphabet-alt { &:before { content: "\e152"; } }
.glyphicon-sort-by-order { &:before { content: "\e153"; } }
.glyphicon-sort-by-order-alt { &:before { content: "\e154"; } }
.glyphicon-sort-by-attributes { &:before { content: "\e155"; } }
.glyphicon-sort-by-attributes-alt { &:before { content: "\e156"; } }
.glyphicon-unchecked { &:before { content: "\e157"; } }
.glyphicon-expand { &:before { content: "\e158"; } }
.glyphicon-collapse-down { &:before { content: "\e159"; } }
.glyphicon-collapse-up { &:before { content: "\e160"; } }
.glyphicon-log-in { &:before { content: "\e161"; } }
.glyphicon-flash { &:before { content: "\e162"; } }
.glyphicon-log-out { &:before { content: "\e163"; } }
.glyphicon-new-window { &:before { content: "\e164"; } }
.glyphicon-record { &:before { content: "\e165"; } }
.glyphicon-save { &:before { content: "\e166"; } }
.glyphicon-open { &:before { content: "\e167"; } }
.glyphicon-saved { &:before { content: "\e168"; } }
.glyphicon-import { &:before { content: "\e169"; } }
.glyphicon-export { &:before { content: "\e170"; } }
.glyphicon-send { &:before { content: "\e171"; } }
.glyphicon-floppy-disk { &:before { content: "\e172"; } }
.glyphicon-floppy-saved { &:before { content: "\e173"; } }
.glyphicon-floppy-remove { &:before { content: "\e174"; } }
.glyphicon-floppy-save { &:before { content: "\e175"; } }
.glyphicon-floppy-open { &:before { content: "\e176"; } }
.glyphicon-credit-card { &:before { content: "\e177"; } }
.glyphicon-transfer { &:before { content: "\e178"; } }
.glyphicon-cutlery { &:before { content: "\e179"; } }
.glyphicon-header { &:before { content: "\e180"; } }
.glyphicon-compressed { &:before { content: "\e181"; } }
.glyphicon-earphone { &:before { content: "\e182"; } }
.glyphicon-phone-alt { &:before { content: "\e183"; } }
.glyphicon-tower { &:before { content: "\e184"; } }
.glyphicon-stats { &:before { content: "\e185"; } }
.glyphicon-sd-video { &:before { content: "\e186"; } }
.glyphicon-hd-video { &:before { content: "\e187"; } }
.glyphicon-subtitles { &:before { content: "\e188"; } }
.glyphicon-sound-stereo { &:before { content: "\e189"; } }
.glyphicon-sound-dolby { &:before { content: "\e190"; } }
.glyphicon-sound-5-1 { &:before { content: "\e191"; } }
.glyphicon-sound-6-1 { &:before { content: "\e192"; } }
.glyphicon-sound-7-1 { &:before { content: "\e193"; } }
.glyphicon-copyright-mark { &:before { content: "\e194"; } }
.glyphicon-registration-mark { &:before { content: "\e195"; } }
.glyphicon-cloud-download { &:before { content: "\e197"; } }
.glyphicon-cloud-upload { &:before { content: "\e198"; } }
.glyphicon-tree-conifer { &:before { content: "\e199"; } }
.glyphicon-tree-deciduous { &:before { content: "\e200"; } }
================================================
FILE: less/lib/bootstrap/grid.less
================================================
//
// Grid system
// --------------------------------------------------
// Set the container width, and override it for fixed navbars in media queries
.container {
.container-fixed();
@media (min-width: @screen-sm) {
width: @container-sm;
}
@media (min-width: @screen-md) {
width: @container-md;
}
@media (min-width: @screen-lg-min) {
width: @container-lg;
}
}
// mobile first defaults
.row {
.make-row();
}
// Common styles for small and large grid columns
.make-grid-columns();
// Extra small grid
//
// Columns, offsets, pushes, and pulls for extra small devices like
// smartphones.
.make-grid-columns-float(xs);
.make-grid(@grid-columns, xs, width);
.make-grid(@grid-columns, xs, pull);
.make-grid(@grid-columns, xs, push);
.make-grid(@grid-columns, xs, offset);
// Small grid
//
// Columns, offsets, pushes, and pulls for the small device range, from phones
// to tablets.
@media (min-width: @screen-sm-min) {
.make-grid-columns-float(sm);
.make-grid(@grid-columns, sm, width);
.make-grid(@grid-columns, sm, pull);
.make-grid(@grid-columns, sm, push);
.make-grid(@grid-columns, sm, offset);
}
// Medium grid
//
// Columns, offsets, pushes, and pulls for the desktop device range.
@media (min-width: @screen-md-min) {
.make-grid-columns-float(md);
.make-grid(@grid-columns, md, width);
.make-grid(@grid-columns, md, pull);
.make-grid(@grid-columns, md, push);
.make-grid(@grid-columns, md, offset);
}
// Large grid
//
// Columns, offsets, pushes, and pulls for the large desktop device range.
@media (min-width: @screen-lg-min) {
.make-grid-columns-float(lg);
.make-grid(@grid-columns, lg, width);
.make-grid(@grid-columns, lg, pull);
.make-grid(@grid-columns, lg, push);
.make-grid(@grid-columns, lg, offset);
}
================================================
FILE: less/lib/bootstrap/input-groups.less
================================================
//
// Input groups
// --------------------------------------------------
// Base styles
// -------------------------
.input-group {
position: relative; // For dropdowns
display: table;
border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table
// Undo padding and float of grid classes
&[class*="col-"] {
float: none;
padding-left: 0;
padding-right: 0;
}
.form-control {
width: 100%;
margin-bottom: 0;
}
}
// Sizing options
//
// Remix the default form control sizing classes into new ones for easier
// manipulation.
.input-group-lg > .form-control,
.input-group-lg > .input-group-addon,
.input-group-lg > .input-group-btn > .btn { .input-lg(); }
.input-group-sm > .form-control,
.input-group-sm > .input-group-addon,
.input-group-sm > .input-group-btn > .btn { .input-sm(); }
// Display as table-cell
// -------------------------
.input-group-addon,
.input-group-btn,
.input-group .form-control {
display: table-cell;
&:not(:first-child):not(:last-child) {
border-radius: 0;
}
}
// Addon and addon wrapper for buttons
.input-group-addon,
.input-group-btn {
width: 1%;
white-space: nowrap;
vertical-align: middle; // Match the inputs
}
// Text input groups
// -------------------------
.input-group-addon {
padding: @padding-base-vertical @padding-base-horizontal;
font-size: @font-size-base;
font-weight: normal;
line-height: 1;
color: @input-color;
text-align: center;
background-color: @input-group-addon-bg;
border: 1px solid @input-group-addon-border-color;
border-radius: @border-radius-base;
// Sizing
&.input-sm {
padding: @padding-small-vertical @padding-small-horizontal;
font-size: @font-size-small;
border-radius: @border-radius-small;
}
&.input-lg {
padding: @padding-large-vertical @padding-large-horizontal;
font-size: @font-size-large;
border-radius: @border-radius-large;
}
// Nuke default margins from checkboxes and radios to vertically center within.
input[type="radio"],
input[type="checkbox"] {
margin-top: 0;
}
}
// Reset rounded corners
.input-group .form-control:first-child,
.input-group-addon:first-child,
.input-group-btn:first-child > .btn,
.input-group-btn:first-child > .dropdown-toggle,
.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) {
.border-right-radius(0);
}
.input-group-addon:first-child {
border-right: 0;
}
.input-group .form-control:last-child,
.input-group-addon:last-child,
.input-group-btn:last-child > .btn,
.input-group-btn:last-child > .dropdown-toggle,
.input-group-btn:first-child > .btn:not(:first-child) {
.border-left-radius(0);
}
.input-group-addon:last-child {
border-left: 0;
}
// Button input groups
// -------------------------
.input-group-btn {
position: relative;
white-space: nowrap;
// Negative margin to only have a 1px border between the two
&:first-child > .btn {
margin-right: -1px;
}
&:last-child > .btn {
margin-left: -1px;
}
}
.input-group-btn > .btn {
position: relative;
// Jankily prevent input button groups from wrapping
+ .btn {
margin-left: -4px;
}
// Bring the "active" button to the front
&:hover,
&:active {
z-index: 2;
}
}
================================================
FILE: less/lib/bootstrap/jumbotron.less
================================================
//
// Jumbotron
// --------------------------------------------------
.jumbotron {
padding: @jumbotron-padding;
margin-bottom: @jumbotron-padding;
font-size: @jumbotron-font-size;
font-weight: 200;
line-height: (@line-height-base * 1.5);
color: @jumbotron-color;
background-color: @jumbotron-bg;
h1,
.h1 {
line-height: 1;
color: @jumbotron-heading-color;
}
p {
line-height: 1.4;
}
.container & {
border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container
}
.container {
max-width: 100%;
}
@media screen and (min-width: @screen-sm-min) {
padding-top: (@jumbotron-padding * 1.6);
padding-bottom: (@jumbotron-padding * 1.6);
.container & {
padding-left: (@jumbotron-padding * 2);
padding-right: (@jumbotron-padding * 2);
}
h1,
.h1 {
font-size: (@font-size-base * 4.5);
}
}
}
================================================
FILE: less/lib/bootstrap/labels.less
================================================
//
// Labels
// --------------------------------------------------
.label {
display: inline;
padding: .2em .6em .3em;
font-size: 75%;
font-weight: bold;
line-height: 1;
color: @label-color;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: .25em;
// Add hover effects, but only for links
&[href] {
&:hover,
&:focus {
color: @label-link-hover-color;
text-decoration: none;
cursor: pointer;
}
}
// Empty labels collapse automatically (not available in IE8)
&:empty {
display: none;
}
// Quick fix for labels in buttons
.btn & {
position: relative;
top: -1px;
}
}
// Colors
// Contextual variations (linked labels get darker on :hover)
.label-default {
.label-variant(@label-default-bg);
}
.label-primary {
.label-variant(@label-primary-bg);
}
.label-success {
.label-variant(@label-success-bg);
}
.label-info {
.label-variant(@label-info-bg);
}
.label-warning {
.label-variant(@label-warning-bg);
}
.label-danger {
.label-variant(@label-danger-bg);
}
================================================
FILE: less/lib/bootstrap/list-group.less
================================================
//
// List groups
// --------------------------------------------------
// Base class
//
// Easily usable on
, , or .
.list-group {
// No need to set list-style: none; since .list-group-item is block level
margin-bottom: 20px;
padding-left: 0; // reset padding because ul and ol
}
// Individual list items
// -------------------------
.list-group-item {
position: relative;
display: block;
padding: 10px 15px;
// Place the border on the list items and negative margin up for better styling
margin-bottom: -1px;
background-color: @list-group-bg;
border: 1px solid @list-group-border;
// Round the first and last items
&:first-child {
.border-top-radius(@list-group-border-radius);
}
&:last-child {
margin-bottom: 0;
.border-bottom-radius(@list-group-border-radius);
}
// Align badges within list items
> .badge {
float: right;
}
> .badge + .badge {
margin-right: 5px;
}
}
// Linked list items
a.list-group-item {
color: @list-group-link-color;
.list-group-item-heading {
color: @list-group-link-heading-color;
}
// Hover state
&:hover,
&:focus {
text-decoration: none;
background-color: @list-group-hover-bg;
}
// Active class on item itself, not parent
&.active,
&.active:hover,
&.active:focus {
z-index: 2; // Place active items above their siblings for proper border styling
color: @list-group-active-color;
background-color: @list-group-active-bg;
border-color: @list-group-active-border;
// Force color to inherit for custom content
.list-group-item-heading {
color: inherit;
}
.list-group-item-text {
color: lighten(@list-group-active-bg, 40%);
}
}
}
// Custom content options
// -------------------------
.list-group-item-heading {
margin-top: 0;
margin-bottom: 5px;
}
.list-group-item-text {
margin-bottom: 0;
line-height: 1.3;
}
================================================
FILE: less/lib/bootstrap/media.less
================================================
// Media objects
// Source: http://stubbornella.org/content/?p=497
// --------------------------------------------------
// Common styles
// -------------------------
// Clear the floats
.media,
.media-body {
overflow: hidden;
zoom: 1;
}
// Proper spacing between instances of .media
.media,
.media .media {
margin-top: 15px;
}
.media:first-child {
margin-top: 0;
}
// For images and videos, set to block
.media-object {
display: block;
}
// Reset margins on headings for tighter default spacing
.media-heading {
margin: 0 0 5px;
}
// Media image alignment
// -------------------------
.media {
> .pull-left {
margin-right: 10px;
}
> .pull-right {
margin-left: 10px;
}
}
// Media list variation
// -------------------------
// Undo default ul/ol styles
.media-list {
padding-left: 0;
list-style: none;
}
================================================
FILE: less/lib/bootstrap/mixins.less
================================================
//
// Mixins
// --------------------------------------------------
// Utilities
// -------------------------
// Clearfix
// Source: http://nicolasgallagher.com/micro-clearfix-hack/
//
// For modern browsers
// 1. The space content is one way to avoid an Opera bug when the
// contenteditable attribute is included anywhere else in the document.
// Otherwise it causes space to appear at the top and bottom of elements
// that are clearfixed.
// 2. The use of `table` rather than `block` is only necessary if using
// `:before` to contain the top-margins of child elements.
.clearfix() {
&:before,
&:after {
content: " "; // 1
display: table; // 2
}
&:after {
clear: both;
}
}
// WebKit-style focus
.tab-focus() {
// Default
outline: thin dotted;
// WebKit
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
// Center-align a block level element
.center-block() {
display: block;
margin-left: auto;
margin-right: auto;
}
// Sizing shortcuts
.size(@width; @height) {
width: @width;
height: @height;
}
.square(@size) {
.size(@size; @size);
}
// Placeholder text
.placeholder(@color: @input-color-placeholder) {
&:-moz-placeholder { color: @color; } // Firefox 4-18
&::-moz-placeholder { color: @color; // Firefox 19+
opacity: 1; } // See https://github.com/twbs/bootstrap/pull/11526
&:-ms-input-placeholder { color: @color; } // Internet Explorer 10+
&::-webkit-input-placeholder { color: @color; } // Safari and Chrome
}
// Text overflow
// Requires inline-block or block for proper styling
.text-overflow() {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// CSS image replacement
//
// Heads up! v3 launched with with only `.hide-text()`, but per our pattern for
// mixins being reused as classes with the same name, this doesn't hold up. As
// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`. Note
// that we cannot chain the mixins together in Less, so they are repeated.
//
// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757
// Deprecated as of v3.0.1 (will be removed in v4)
.hide-text() {
font: ~"0/0" a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0;
}
// New mixin to use as of v3.0.1
.text-hide() {
.hide-text();
}
// CSS3 PROPERTIES
// --------------------------------------------------
// Single side border-radius
.border-top-radius(@radius) {
border-top-right-radius: @radius;
border-top-left-radius: @radius;
}
.border-right-radius(@radius) {
border-bottom-right-radius: @radius;
border-top-right-radius: @radius;
}
.border-bottom-radius(@radius) {
border-bottom-right-radius: @radius;
border-bottom-left-radius: @radius;
}
.border-left-radius(@radius) {
border-bottom-left-radius: @radius;
border-top-left-radius: @radius;
}
// Drop shadows
.box-shadow(@shadow) {
-webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1
box-shadow: @shadow;
}
// Transitions
.transition(@transition) {
-webkit-transition: @transition;
transition: @transition;
}
.transition-property(@transition-property) {
-webkit-transition-property: @transition-property;
transition-property: @transition-property;
}
.transition-delay(@transition-delay) {
-webkit-transition-delay: @transition-delay;
transition-delay: @transition-delay;
}
.transition-duration(@transition-duration) {
-webkit-transition-duration: @transition-duration;
transition-duration: @transition-duration;
}
.transition-transform(@transition) {
-webkit-transition: -webkit-transform @transition;
-moz-transition: -moz-transform @transition;
-o-transition: -o-transform @transition;
transition: transform @transition;
}
// Transformations
.rotate(@degrees) {
-webkit-transform: rotate(@degrees);
-ms-transform: rotate(@degrees); // IE9+
transform: rotate(@degrees);
}
.scale(@ratio) {
-webkit-transform: scale(@ratio);
-ms-transform: scale(@ratio); // IE9+
transform: scale(@ratio);
}
.translate(@x; @y) {
-webkit-transform: translate(@x, @y);
-ms-transform: translate(@x, @y); // IE9+
transform: translate(@x, @y);
}
.skew(@x; @y) {
-webkit-transform: skew(@x, @y);
-ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+
transform: skew(@x, @y);
}
.translate3d(@x; @y; @z) {
-webkit-transform: translate3d(@x, @y, @z);
transform: translate3d(@x, @y, @z);
}
.rotateX(@degrees) {
-webkit-transform: rotateX(@degrees);
-ms-transform: rotateX(@degrees); // IE9+
transform: rotateX(@degrees);
}
.rotateY(@degrees) {
-webkit-transform: rotateY(@degrees);
-ms-transform: rotateY(@degrees); // IE9+
transform: rotateY(@degrees);
}
.perspective(@perspective) {
-webkit-perspective: @perspective;
-moz-perspective: @perspective;
perspective: @perspective;
}
.perspective-origin(@perspective) {
-webkit-perspective-origin: @perspective;
-moz-perspective-origin: @perspective;
perspective-origin: @perspective;
}
.transform-origin(@origin) {
-webkit-transform-origin: @origin;
-moz-transform-origin: @origin;
transform-origin: @origin;
}
// Animations
.animation(@animation) {
-webkit-animation: @animation;
animation: @animation;
}
// Backface visibility
// Prevent browsers from flickering when using CSS 3D transforms.
// Default value is `visible`, but can be changed to `hidden`
.backface-visibility(@visibility){
-webkit-backface-visibility: @visibility;
-moz-backface-visibility: @visibility;
backface-visibility: @visibility;
}
// Box sizing
.box-sizing(@boxmodel) {
-webkit-box-sizing: @boxmodel;
-moz-box-sizing: @boxmodel;
box-sizing: @boxmodel;
}
// User select
// For selecting text on the page
.user-select(@select) {
-webkit-user-select: @select;
-moz-user-select: @select;
-ms-user-select: @select; // IE10+
-o-user-select: @select;
user-select: @select;
}
// Resize anything
.resizable(@direction) {
resize: @direction; // Options: horizontal, vertical, both
overflow: auto; // Safari fix
}
// CSS3 Content Columns
.content-columns(@column-count; @column-gap: @grid-gutter-width) {
-webkit-column-count: @column-count;
-moz-column-count: @column-count;
column-count: @column-count;
-webkit-column-gap: @column-gap;
-moz-column-gap: @column-gap;
column-gap: @column-gap;
}
// Optional hyphenation
.hyphens(@mode: auto) {
word-wrap: break-word;
-webkit-hyphens: @mode;
-moz-hyphens: @mode;
-ms-hyphens: @mode; // IE10+
-o-hyphens: @mode;
hyphens: @mode;
}
// Opacity
.opacity(@opacity) {
opacity: @opacity;
// IE8 filter
@opacity-ie: (@opacity * 100);
filter: ~"alpha(opacity=@{opacity-ie})";
}
// GRADIENTS
// --------------------------------------------------
#gradient {
// Horizontal gradient, from left to right
//
// Creates two color stops, start and end, by specifying a color and position for each color stop.
// Color stops are not available in IE9 and below.
.horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {
background-image: -webkit-linear-gradient(left, color-stop(@start-color @start-percent), color-stop(@end-color @end-percent)); // Safari 5.1-6, Chrome 10+
background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+
background-repeat: repeat-x;
filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@start-color),argb(@end-color))); // IE9 and down
}
// Vertical gradient, from top to bottom
//
// Creates two color stops, start and end, by specifying a color and position for each color stop.
// Color stops are not available in IE9 and below.
.vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {
background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+
background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+
background-repeat: repeat-x;
filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@start-color),argb(@end-color))); // IE9 and down
}
.directional(@start-color: #555; @end-color: #333; @deg: 45deg) {
background-repeat: repeat-x;
background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+
background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+
}
.horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {
background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);
background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);
background-repeat: no-repeat;
filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback
}
.vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {
background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);
background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);
background-repeat: no-repeat;
filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback
}
.radial(@inner-color: #555; @outer-color: #333) {
background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);
background-image: radial-gradient(circle, @inner-color, @outer-color);
background-repeat: no-repeat;
}
.striped(@color: rgba(255,255,255,.15); @angle: 45deg) {
background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);
background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);
}
}
// Reset filters for IE
//
// When you need to remove a gradient background, do not forget to use this to reset
// the IE filter for IE9 and below.
.reset-filter() {
filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)"));
}
// Retina images
//
// Short retina mixin for setting background-image and -size
.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {
background-image: url("@{file-1x}");
@media
only screen and (-webkit-min-device-pixel-ratio: 2),
only screen and ( min--moz-device-pixel-ratio: 2),
only screen and ( -o-min-device-pixel-ratio: 2/1),
only screen and ( min-device-pixel-ratio: 2),
only screen and ( min-resolution: 192dpi),
only screen and ( min-resolution: 2dppx) {
background-image: url("@{file-2x}");
background-size: @width-1x @height-1x;
}
}
// Responsive image
//
// Keep images from scaling beyond the width of their parents.
.img-responsive(@display: block;) {
display: @display;
max-width: 100%; // Part 1: Set a maximum relative to the parent
height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching
}
// COMPONENT MIXINS
// --------------------------------------------------
// Horizontal dividers
// -------------------------
// Dividers (basically an hr) within dropdowns and nav lists
.nav-divider(@color: #e5e5e5) {
height: 1px;
margin: ((@line-height-computed / 2) - 1) 0;
overflow: hidden;
background-color: @color;
}
// Panels
// -------------------------
.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) {
border-color: @border;
& > .panel-heading {
color: @heading-text-color;
background-color: @heading-bg-color;
border-color: @heading-border;
+ .panel-collapse .panel-body {
border-top-color: @border;
}
}
& > .panel-footer {
+ .panel-collapse .panel-body {
border-bottom-color: @border;
}
}
}
// Alerts
// -------------------------
.alert-variant(@background; @border; @text-color) {
background-color: @background;
border-color: @border;
color: @text-color;
hr {
border-top-color: darken(@border, 5%);
}
.alert-link {
color: darken(@text-color, 10%);
}
}
// Tables
// -------------------------
.table-row-variant(@state; @background) {
// Exact selectors below required to override `.table-striped` and prevent
// inheritance to nested tables.
.table {
> thead,
> tbody,
> tfoot {
> tr > .@{state},
> .@{state} > td,
> .@{state} > th {
background-color: @background;
}
}
}
// Hover states for `.table-hover`
// Note: this is not available for cells or rows within `thead` or `tfoot`.
.table-hover > tbody {
> tr > .@{state}:hover,
> .@{state}:hover > td,
> .@{state}:hover > th {
background-color: darken(@background, 5%);
}
}
}
// Button variants
// -------------------------
// Easily pump out default styles, as well as :hover, :focus, :active,
// and disabled options for all buttons
.button-variant(@color; @background; @border) {
color: @color;
background-color: @background;
border-color: @border;
&:hover,
&:focus,
&:active,
&.active,
.open .dropdown-toggle& {
color: @color;
background-color: darken(@background, 12%);
border-color: darken(@border, 12%);
}
&:active,
&.active,
.open .dropdown-toggle& {
background-image: none;
}
&.disabled,
&[disabled],
fieldset[disabled] & {
&,
&:hover,
&:focus,
&:active,
&.active {
background-color: @background;
border-color: @border;
}
}
.badge {
color: @background;
background-color: #fff;
}
}
// Button sizes
// -------------------------
.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {
padding: @padding-vertical @padding-horizontal;
font-size: @font-size;
line-height: @line-height;
border-radius: @border-radius;
}
// Pagination
// -------------------------
.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @border-radius) {
> li {
> a,
> span {
padding: @padding-vertical @padding-horizontal;
font-size: @font-size;
}
&:first-child {
> a,
> span {
.border-left-radius(@border-radius);
}
}
&:last-child {
> a,
> span {
.border-right-radius(@border-radius);
}
}
}
}
// Labels
// -------------------------
.label-variant(@color) {
background-color: @color;
&[href] {
&:hover,
&:focus {
background-color: darken(@color, 10%);
}
}
}
// Navbar vertical align
// -------------------------
// Vertically center elements in the navbar.
// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.
.navbar-vertical-align(@element-height) {
margin-top: ((@navbar-height - @element-height) / 2);
margin-bottom: ((@navbar-height - @element-height) / 2);
}
// Progress bars
// -------------------------
.progress-bar-variant(@color) {
background-color: @color;
.progress-striped & {
#gradient > .striped();
}
}
// Responsive utilities
// -------------------------
// More easily include all the states for responsive-utilities.less.
.responsive-visibility() {
display: block !important;
table& { display: table; }
tr& { display: table-row !important; }
th&,
td& { display: table-cell !important; }
}
.responsive-invisibility() {
&,
tr&,
th&,
td& { display: none !important; }
}
// Grid System
// -----------
// Centered container element
.container-fixed() {
margin-right: auto;
margin-left: auto;
padding-left: (@grid-gutter-width / 2);
padding-right: (@grid-gutter-width / 2);
.clearfix();
}
// Creates a wrapper for a series of columns
.make-row(@gutter: @grid-gutter-width) {
margin-left: (@gutter / -2);
margin-right: (@gutter / -2);
.clearfix();
}
// Generate the extra small columns
.make-xs-column(@columns; @gutter: @grid-gutter-width) {
position: relative;
float: left;
width: percentage((@columns / @grid-columns));
// Prevent columns from collapsing when empty
min-height: 1px;
// Inner gutter via padding
padding-left: (@gutter / 2);
padding-right: (@gutter / 2);
}
// Generate the small columns
.make-sm-column(@columns; @gutter: @grid-gutter-width) {
position: relative;
// Prevent columns from collapsing when empty
min-height: 1px;
// Inner gutter via padding
padding-left: (@gutter / 2);
padding-right: (@gutter / 2);
// Calculate width based on number of columns available
@media (min-width: @screen-sm-min) {
float: left;
width: percentage((@columns / @grid-columns));
}
}
// Generate the small column offsets
.make-sm-column-offset(@columns) {
@media (min-width: @screen-sm-min) {
margin-left: percentage((@columns / @grid-columns));
}
}
.make-sm-column-push(@columns) {
@media (min-width: @screen-sm-min) {
left: percentage((@columns / @grid-columns));
}
}
.make-sm-column-pull(@columns) {
@media (min-width: @screen-sm-min) {
right: percentage((@columns / @grid-columns));
}
}
// Generate the medium columns
.make-md-column(@columns; @gutter: @grid-gutter-width) {
position: relative;
// Prevent columns from collapsing when empty
min-height: 1px;
// Inner gutter via padding
padding-left: (@gutter / 2);
padding-right: (@gutter / 2);
// Calculate width based on number of columns available
@media (min-width: @screen-md-min) {
float: left;
width: percentage((@columns / @grid-columns));
}
}
// Generate the medium column offsets
.make-md-column-offset(@columns) {
@media (min-width: @screen-md-min) {
margin-left: percentage((@columns / @grid-columns));
}
}
.make-md-column-push(@columns) {
@media (min-width: @screen-md) {
left: percentage((@columns / @grid-columns));
}
}
.make-md-column-pull(@columns) {
@media (min-width: @screen-md-min) {
right: percentage((@columns / @grid-columns));
}
}
// Generate the large columns
.make-lg-column(@columns; @gutter: @grid-gutter-width) {
position: relative;
// Prevent columns from collapsing when empty
min-height: 1px;
// Inner gutter via padding
padding-left: (@gutter / 2);
padding-right: (@gutter / 2);
// Calculate width based on number of columns available
@media (min-width: @screen-lg-min) {
float: left;
width: percentage((@columns / @grid-columns));
}
}
// Generate the large column offsets
.make-lg-column-offset(@columns) {
@media (min-width: @screen-lg-min) {
margin-left: percentage((@columns / @grid-columns));
}
}
.make-lg-column-push(@columns) {
@media (min-width: @screen-lg-min) {
left: percentage((@columns / @grid-columns));
}
}
.make-lg-column-pull(@columns) {
@media (min-width: @screen-lg-min) {
right: percentage((@columns / @grid-columns));
}
}
// Framework grid generation
//
// Used only by Bootstrap to generate the correct number of grid classes given
// any value of `@grid-columns`.
.make-grid-columns() {
// Common styles for all sizes of grid columns, widths 1-12
.col(@index) when (@index = 1) { // initial
@item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
.col(@index + 1, @item);
}
.col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo
@item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
.col(@index + 1, ~"@{list}, @{item}");
}
.col(@index, @list) when (@index > @grid-columns) { // terminal
@{list} {
position: relative;
// Prevent columns from collapsing when empty
min-height: 1px;
// Inner gutter via padding
padding-left: (@grid-gutter-width / 2);
padding-right: (@grid-gutter-width / 2);
}
}
.col(1); // kickstart it
}
.make-grid-columns-float(@class) {
.col(@index) when (@index = 1) { // initial
@item: ~".col-@{class}-@{index}";
.col(@index + 1, @item);
}
.col(@index, @list) when (@index =< @grid-columns) { // general
@item: ~".col-@{class}-@{index}";
.col(@index + 1, ~"@{list}, @{item}");
}
.col(@index, @list) when (@index > @grid-columns) { // terminal
@{list} {
float: left;
}
}
.col(1); // kickstart it
}
.calc-grid(@index, @class, @type) when (@type = width) and (@index > 0) {
.col-@{class}-@{index} {
width: percentage((@index / @grid-columns));
}
}
.calc-grid(@index, @class, @type) when (@type = push) {
.col-@{class}-push-@{index} {
left: percentage((@index / @grid-columns));
}
}
.calc-grid(@index, @class, @type) when (@type = pull) {
.col-@{class}-pull-@{index} {
right: percentage((@index / @grid-columns));
}
}
.calc-grid(@index, @class, @type) when (@type = offset) {
.col-@{class}-offset-@{index} {
margin-left: percentage((@index / @grid-columns));
}
}
// Basic looping in LESS
.make-grid(@index, @class, @type) when (@index >= 0) {
.calc-grid(@index, @class, @type);
// next iteration
.make-grid(@index - 1, @class, @type);
}
// Form validation states
//
// Used in forms.less to generate the form validation CSS for warnings, errors,
// and successes.
.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) {
// Color the label and help text
.help-block,
.control-label,
.radio,
.checkbox,
.radio-inline,
.checkbox-inline {
color: @text-color;
}
// Set the border and box shadow on specific inputs to match
.form-control {
border-color: @border-color;
.box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work
&:focus {
border-color: darken(@border-color, 10%);
@shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%);
.box-shadow(@shadow);
}
}
// Set validation states also for addons
.input-group-addon {
color: @text-color;
border-color: @border-color;
background-color: @background-color;
}
}
// Form control focus state
//
// Generate a customized focus state and for any input with the specified color,
// which defaults to the `@input-focus-border` variable.
//
// We highly encourage you to not customize the default value, but instead use
// this to tweak colors on an as-needed basis. This aesthetic change is based on
// WebKit's default styles, but applicable to a wider range of browsers. Its
// usability and accessibility should be taken into account with any change.
//
// Example usage: change the default blue border and shadow to white for better
// contrast against a dark gray background.
.form-control-focus(@color: @input-border-focus) {
@color-rgba: rgba(red(@color), green(@color), blue(@color), .6);
&:focus {
border-color: @color;
outline: 0;
.box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}");
}
}
// Form control sizing
//
// Relative text size, padding, and border-radii changes for form controls. For
// horizontal sizing, wrap controls in the predefined grid classes. `