Merge remote-tracking branch 'upstream/master'

Conflicts:
	package.json
This commit is contained in:
Matt Roberts
2014-02-27 16:16:13 +00:00
32 changed files with 2051 additions and 1297 deletions

8
.travis.yml Normal file
View File

@ -0,0 +1,8 @@
language: node_js
services:
- mongodb
node_js:
- '0.11'
- '0.10'

165
README.md
View File

@ -1,5 +1,5 @@
![Alt](https://lh4.googleusercontent.com/-PVw-ZUM9vV8/UuWeH51os0I/AAAAAAAAD6M/0Ikg7viJftQ/w1286-h566-no/hackathon-starter-logo.jpg) ![Alt](https://lh4.googleusercontent.com/-PVw-ZUM9vV8/UuWeH51os0I/AAAAAAAAD6M/0Ikg7viJftQ/w1286-h566-no/hackathon-starter-logo.jpg)
Hackathon Starter [![Dependency Status](https://david-dm.org/sahat/hackathon-starter.png?theme=shields.io)](https://david-dm.org/sahat/hackathon-starter) Hackathon Starter [![Dependency Status](https://david-dm.org/sahat/hackathon-starter.png?theme=shields.io)](https://david-dm.org/sahat/hackathon-starter) [![Build Status](https://travis-ci.org/sahat/hackathon-starter.png)](https://travis-ci.org/sahat/hackathon-starter)
================= =================
A boilerplate for **Node.js** web applications. A boilerplate for **Node.js** web applications.
@ -35,6 +35,7 @@ Table of Contents
- [Getting Started](#getting-started) - [Getting Started](#getting-started)
- [Obtaining API Keys](#obtaining-api-keys) - [Obtaining API Keys](#obtaining-api-keys)
- [Project Structure](#project-structure) - [Project Structure](#project-structure)
- [List of Packages](#list-of-packages)
- [Useful Tools](#useful-tools) - [Useful Tools](#useful-tools)
- [Recommended Design](#recommended-design) - [Recommended Design](#recommended-design)
- [Recommended Node.js Libraries](#recommended-nodejs-libraries) - [Recommended Node.js Libraries](#recommended-nodejs-libraries)
@ -66,6 +67,7 @@ Features
- Change Password - Change Password
- Link multiple OAuth strategies to one account - Link multiple OAuth strategies to one account
- Delete Account - Delete Account
- Forgot Password
- **API Examples**: Facebook, Foursquare, Last.fm, Tumblr, Twitter, PayPal, and more. - **API Examples**: Facebook, Foursquare, Last.fm, Tumblr, Twitter, PayPal, and more.
Prerequisites Prerequisites
@ -89,7 +91,7 @@ Getting Started
The easiest way to get started is to clone the repository: The easiest way to get started is to clone the repository:
```bash ```bash
# Fetch only the latest commits. # Fetch only the latest commits
git clone --depth=1 git@github.com:sahat/hackathon-starter.git my-project git clone --depth=1 git@github.com:sahat/hackathon-starter.git my-project
cd my-project cd my-project
@ -100,12 +102,12 @@ npm install
node app.js node app.js
``` ```
>:exclamation: **Note**: I strongly recommend installing nodemon `sudo npm install -g nodemon`. :exclamation: **Note**: I strongly recommend installing nodemon `sudo npm install -g nodemon`.
>It will monitor for any changes in your node.js It will monitor for any changes in your node.js
>application and automatically restart the server. Once installed, instead of `node app.js` use `nodemon app.js`. application and automatically restart the server. Once installed, instead of `node app.js` use `nodemon app.js`.
>It is a big time saver in the long run. It will save you a lot of time in the long run, because you won't need to manually restart the server each time you make a change.
Next up, if you want to use any of the APIs or OAuth authentication methods, you will need to obtain Next, if you want to use any of the included APIs or OAuth authentication methods, you will need to obtain
appropriate credentials: Client ID, Client Secret, API Key, or Username & Password. You will appropriate credentials: Client ID, Client Secret, API Key, or Username & Password. You will
need to go through each provider to generate new credentials. need to go through each provider to generate new credentials.
@ -114,6 +116,13 @@ Obtaining API Keys
:pushpin: You could support all 5 authentication methods by setting up OAuth keys, but you don't have to. If you would only like to have **Facebook sign-in** and **Local sign-in** with email and password, in **secrets.js** set `googleAuth: false`, `twitterOauth: false`, `githubAuth: false`. By doing so, *Google, Twitter and Github* buttons will not show up on the *Login* page. If you set `localAuth: false`, users will not be able to login/create an account with email and password or change password in the *Account Management* page. :pushpin: You could support all 5 authentication methods by setting up OAuth keys, but you don't have to. If you would only like to have **Facebook sign-in** and **Local sign-in** with email and password, in **secrets.js** set `googleAuth: false`, `twitterOauth: false`, `githubAuth: false`. By doing so, *Google, Twitter and Github* buttons will not show up on the *Login* page. If you set `localAuth: false`, users will not be able to login/create an account with email and password or change password in the *Account Management* page.
:bulb: Alternatively, if you would like to completely remove authentication methods that you do not plan on using, you will need to manually delete the code yourself. Let's say you want to keep only **Local authentication**. Start by deleting *FacebookStrategy, TwitterStrategy, GitHubStrategy, GoogleStrategy* `require` lines and their corresponding defined strategies in **passport.js**. Then in **login.jade** template delete the entire `.btn-group`, leaving only the form with Email and Password.
Update **User.js** model by deleting the following fields: `facebook`, `github`, `google`, `twitter`. In your **profile.jade** template delete the entire code starting with **h3 Linked Accounts**. And finally delete the corresponding routes that have **/auth/provider** and **/auth/provider/callback**, for example:
```js
app.get('/auth/facebook', passport.authenticate('facebook', { scope: ['email', 'user_location'] }));
app.get('/auth/facebook/callback', passport.authenticate('facebook', { successRedirect: '/', failureRedirect: '/login' }));
```
<img src="http://images.google.com/intl/en_ALL/images/srpr/logo6w.png" width="200"> <img src="http://images.google.com/intl/en_ALL/images/srpr/logo6w.png" width="200">
- Visit [Google Cloud Console](https://cloud.google.com/console/project) - Visit [Google Cloud Console](https://cloud.google.com/console/project)
- Click **CREATE PROJECT** button - Click **CREATE PROJECT** button
@ -250,10 +259,42 @@ Project Structure
| app.js | Main application file. | | app.js | Main application file. |
| cluster_app.js | Runs multiple instances of `app.js` using <a href="http://nodejs.org/api/cluster.html" target="_blank">Node.js clusters</a>.| | cluster_app.js | Runs multiple instances of `app.js` using <a href="http://nodejs.org/api/cluster.html" target="_blank">Node.js clusters</a>.|
:exclamation: **Note:** There is no preference how you name or structure your views. You could place all your templates in a top-level `views` directory without having a nested folder structure, if that makes things easier for you. Just don't forget to update `extends ../layout` and corresponding `res.render()` method in controllers.
:exclamation: **Note:** There is no difference how you name or structure your views. You could place all your templates in a top-level `views` directory without having a nested folder structure, if that makes things easier for you. Just don't forget to update `extends ../layout` and corresponding `res.render()` method in controllers. For smaller apps, I find having a flat folder structure to be easier to work with. List of Packages
----------------
| Package | Description |
| ------------- |:-------------:|
| async | Utility library that provides asynchronous control flow. |
| bcrypt-nodejs | Library for hashing and salting user passwords. |
| cheerio | Scrape web pages using jQuery-style syntax. |
| connect-mongo | MongoDB session store for Express. |
| connect-assets | Compiles LESS stylesheets, concatenates/minifies JavaScript. |
| express | Web framework. |
| express-flash | Provides flash messages for Express. Uses connect-flash internally. |
| express-validator | Easy form validation for Express. Uses node-validator internally. |
| fbgraph | Facebook Graph API library |
| github-api | GitHub API library |
| jade | Template engine for node.js |
| lastfm | Last.fm API library |
| less | LESS compiler. Used implicitly by connect-assets. |
| mongoose | MongoDB object modeling tool |
| node-foursquare | Foursquare API library |
| nodemailer | Node.js library for sending emails |
| passport | Simple and elegant authentication library for node.js |
| passport-facebook | Sign-in with Facebook plugin. |
| passport-github | Sign-in with GitHub plugin. |
| passport-google-oauth | Sign-in with Google plugin. |
| passport-twitter | Sign-in with Twitter plugin. |
| passport-local | Sign-in with Username and Password plugin. |
| passport-oauth | Allows you to set up your own OAuth 1.0a and OAuth 2.0 strategies. |
| request | Simplified HTTP request library. |
| tumblr.js | Tumblr API library. |
| underscore | Handy JavaScript utlities library. |
| paypal-rest-sdk | PayPal API library. |
| twilio | Twilio API library. |
| validator | Used in conjunction with express-validator in **controllers/api.js**. |
:bangbang: **Note:** Although your main template - **layout.jade** only knows about `/css/styles.css` file, you should be editing **styles.less** stylesheet. Express will automatically generate minified **styles.css** whenever there are changes in LESS file. This is done via [less-middleware](https://github.com/emberfeather/less.js-middleware) node.js library.
Useful Tools Useful Tools
------------ ------------
@ -273,6 +314,7 @@ Recommended Design
- [Creative Link Effects](http://tympanus.net/Development/CreativeLinkEffects/) - Beautiful link effects in CSS. - [Creative Link Effects](http://tympanus.net/Development/CreativeLinkEffects/) - Beautiful link effects in CSS.
- [Medium Scroll Effect](http://codepen.io/andreasstorm/pen/pyjEh) - Fade in/out header background image as you scroll. - [Medium Scroll Effect](http://codepen.io/andreasstorm/pen/pyjEh) - Fade in/out header background image as you scroll.
- [HTML5UP](http://html5up.net/) - Beautifully designed HTML templates. - [HTML5UP](http://html5up.net/) - Beautifully designed HTML templates.
- [Progre(c)ss](https://github.com/jh3y/progre-c-ss) - Pure CSS progress bars.
Recommended Node.js Libraries Recommended Node.js Libraries
----------------------------- -----------------------------
@ -281,6 +323,7 @@ Recommended Node.js Libraries
- [Nodemailer](https://github.com/andris9/Nodemailer) - send emails with node.js (without sendgrid or mailgun). - [Nodemailer](https://github.com/andris9/Nodemailer) - send emails with node.js (without sendgrid or mailgun).
- [filesize.js](http://filesizejs.com/) - make file size pretty, e.g. `filesize(265318); // "265.32 kB"`. - [filesize.js](http://filesizejs.com/) - make file size pretty, e.g. `filesize(265318); // "265.32 kB"`.
- [Numeral.js](http://numeraljs.com) - a javascript library for formatting and manipulating numbers. - [Numeral.js](http://numeraljs.com) - a javascript library for formatting and manipulating numbers.
- [Node Inspector](https://github.com/node-inspector/node-inspector) - Node.js debugger based on Chrome Developer Tools.
Recommended Client-Side libraries Recommended Client-Side libraries
--------------------------------- ---------------------------------
@ -297,15 +340,15 @@ Recommended Client-Side libraries
- [select.js](http://github.hubspot.com/select/docs/welcome/) - Styleable select elements. - [select.js](http://github.hubspot.com/select/docs/welcome/) - Styleable select elements.
- [drop.js](http://github.hubspot.com/drop/docs/welcome/) - Powerful Javascript and CSS library for creating dropdowns and other floating displays. - [drop.js](http://github.hubspot.com/drop/docs/welcome/) - Powerful Javascript and CSS library for creating dropdowns and other floating displays.
- [scrollReveal.js](https://github.com/julianlloyd/scrollReveal.js) - Declarative on-scroll reveal animations. - [scrollReveal.js](https://github.com/julianlloyd/scrollReveal.js) - Declarative on-scroll reveal animations.
- [InstantClick](http://instantclick.io) - Makes your pages load instantly by pre-loading them on mouse hover.
Pro Tips Pro Tips
-------- --------
- When you install a new npm package, add a *--save* flag and it will be automatially - When installing an NPM package, add a *--save* flag, and it will be automatially
added to `package.json` as well. For example, `npm install --save moment`. added to `package.json` as well. For example, `npm install --save moment`.
- Use [async.parallel()](https://github.com/caolan/async#parallel) when you neeed to run multiple - Use [async.parallel()](https://github.com/caolan/async#parallel) when you neeed to run multiple
asynchronous tasks, and then render a page, but only when all tasks are completed. For example, you might asynchronous tasks, and then render a page, but only when all tasks are completed. For example, you might
want to scrape 3 different websites for some data (async operation) and render the results want to scrape 3 different websites for some data (async operation) and render the results in a template after all 3 websites have been scraped.
on a page after all 3 websites have been scraped.
- Need to find a specific object inside an Array? Use [_.findWhere](http://underscorejs.org/#findWhere) function from Underscore.js. For example, this is how you would retrieve a Twitter token from database: `var token = _.findWhere(req.user.tokens, { kind: 'twitter' });`, where `req.user.tokens` is an Array, and a second parameter is an object with a given key/value. - Need to find a specific object inside an Array? Use [_.findWhere](http://underscorejs.org/#findWhere) function from Underscore.js. For example, this is how you would retrieve a Twitter token from database: `var token = _.findWhere(req.user.tokens, { kind: 'twitter' });`, where `req.user.tokens` is an Array, and a second parameter is an object with a given key/value.
- If you right click and select **View Page Source**, notice how *Express* - If you right click and select **View Page Source**, notice how *Express*
minified HTML for you. If you would like to see non-minified markup, minified HTML for you. If you would like to see non-minified markup,
@ -316,9 +359,12 @@ FAQ
### Why do I get `403 Error: Forbidden` when submitting a POST form? ### Why do I get `403 Error: Forbidden` when submitting a POST form?
You need to add this hidden input element to your form. This has been added in the You need to add this hidden input element to your form. This has been added in the
pull request [#40](https://github.com/sahat/hackathon-starter/pull/40). pull request [#40](https://github.com/sahat/hackathon-starter/pull/40).
``` ```
input(type='hidden', name='_csrf', value=token) input(type='hidden', name='_csrf', value=token)
``` ```
You can read more about [CSRF protection middleware](http://expressjs.com/api.html#csrf) at the Express API Reference.
### What is `cluster_app.js`? ### What is `cluster_app.js`?
From the [Node.js Documentation](http://nodejs.org/api/cluster.html#cluster_how_it_works): From the [Node.js Documentation](http://nodejs.org/api/cluster.html#cluster_how_it_works):
@ -346,12 +392,15 @@ script(src='/js/application.js')
``` ```
As soon as you start bringing in more JavaScript libraries, the benefits of concatenating and minifying As soon as you start bringing in more JavaScript libraries, the benefits of concatenating and minifying
JavaScript files will be even greater. JavaScript files will be even greater.
Using connect-assets library it's as as simple as: Using **connect-assets** library, it is as as simple as declaring these two lines:
```jade
```
!= css('styles') // expects public/css/styles.less != css('styles') // expects public/css/styles.less
!= js('application') // expects public/js/application.js != js('application') // expects public/js/application.js
``` ```
:bulb: **Tip:** This works because in *connect-assets* middleware we have specified `helperContext: app.locals`.
The only thing you need to remember is to define your JavaScript files inside `public/js/application.js` using this The only thing you need to remember is to define your JavaScript files inside `public/js/application.js` using this
strange syntax notation (Sprockets-style) borrowed from Rails. I know it's an extra thing to learn strange syntax notation (Sprockets-style) borrowed from Rails. I know it's an extra thing to learn
for someone who has never seen Rails asset pipeline before, but in this case, I think benefits outweigh the costs. for someone who has never seen Rails asset pipeline before, but in this case, I think benefits outweigh the costs.
@ -468,14 +517,14 @@ app.get('/escape-velocity', homeController.escapeVelocity);
Restart the server (if you are not using **nodemon**), then you should see the new template at [http://localhost:3000/escape-velocity](http://localhost:3000/escape-velocity). Restart the server (if you are not using **nodemon**), then you should see the new template at [http://localhost:3000/escape-velocity](http://localhost:3000/escape-velocity).
I will stop here, but if you would like to use this template as more than just a single page, take a look at how these Jade templates work: `layout.jade` - base template, `index.jade` - home page, `partials/navigation.jade` - Bootstrap navbar, `partials/footer.jade` - sticky footer. You will have to manually break it apart into smaller pieces. Figure out which part of the template you want to keep the same on all pages - that's your new `layout.jade`. I will stop right here, but if you would like to use this template as more than just a single page, take a look at how these Jade templates work: `layout.jade` - base template, `index.jade` - home page, `partials/navigation.jade` - Bootstrap navbar, `partials/footer.jade` - sticky footer. You will have to manually break it apart into smaller pieces. Figure out which part of the template you want to keep the same on all pages - that's your new `layout.jade`.
Then, each page that changes, be it `index.jade`, `about.jade`, `contact.jade` Then, each page that changes, be it `index.jade`, `about.jade`, `contact.jade`
will be embedded in the new `layout.jade` via `block content`. will be embedded in your new `layout.jade` via `block content`. Use existing templates as a reference.
This is a lengthy process, I know, and templates you get from outside **HTML5**UP, This is a rather lengthy process, and templates you get from elsewhere,
will have yet another grid system. That's why I chose Bootstrap CSS for the Hackathon Starter. might have yet another grid system. That's why I chose *Bootstrap* for the Hackathon Starter.
Most people are familiar with Bootstrap, it's easy to get started, very extendable. Many people are already familiar with *Bootstrap*, plus it's easy to get started with it if you have never used *Bootstrap*.
You can also buy a Bootstrap theme drop it in into your project, and everything looks great without a single change to your markup or CSS class names. However, if you would like to go with a completely custom design, there you have it! You can also buy many beautifully designed *Bootstrap* themes at [Themeforest](http://themeforest.net/), and use them as a drop-in replacement for Hackathon Starter. However, if you would like to go with a completely custom HTML/CSS design, this should help you to get started!
<hr> <hr>
@ -562,11 +611,11 @@ or send a pull request if you would like to include something that I missed.
<hr> <hr>
###:snowman: How do I create a new page? ###:snowman: How do I create a new page?
A more correct way to be to say "How do I create a route". The main file `app.js` contains all the routes. A more correct way to be to say "How do I create a new route". The main file `app.js` contains all the routes.
Each route has a callback function (aka controller) associated with it. Sometimes you will see 3 or more arguments Each route has a callback function associated with it. Sometimes you will see 3 or more arguments
to routes. In cases like that, the first argument is still a URL string, the middle arguments to routes. In cases like that, the first argument is still a URL string, while middle arguments
are what's called middleware. Think of middleware as a door. If this door prevents you from are what's called middleware. Think of middleware as a door. If this door prevents you from
continuing forward, well, you won't get to your callback function (aka controller). One such example is authentication. continuing forward, you won't get to your callback function. One such example is a route that requires authentication.
```js ```js
app.get('/account', passportConf.isAuthenticated, userController.getAccount); app.get('/account', passportConf.isAuthenticated, userController.getAccount);
@ -577,14 +626,15 @@ checks if you are authenticated:
```js ```js
exports.isAuthenticated = function(req, res, next) { exports.isAuthenticated = function(req, res, next) {
if (req.isAuthenticated()) return next(); if (req.isAuthenticated()) {
return next();
}
res.redirect('/login'); res.redirect('/login');
}; };
``` ```
If you are authenticated, you let this visitor pass through your "door" by calling `return next();`. It then proceeds to the If you are authenticated, you let this visitor pass through your "door" by calling `return next();`. It then proceeds to the
next middleware until it reaches the last argument which is a callback function that usually renders a template, next middleware until it reaches the last argument, which is a callback function that typically renders a template on `GET` requests or redirects on `POST` requests. In this case, if you are authenticated, then you will see *Account Management* page, otherwise you will be redirected to *Login* page.
or responds with a JSON data, if you are building a REST API. But in this example it simply renders a page and nothing more:
```js ```js
exports.getAccount = function(req, res) { exports.getAccount = function(req, res) {
@ -597,7 +647,7 @@ exports.getAccount = function(req, res) {
Express.js has `app.get`, `app.post`, `app.put`, `app.del`, but for the most part you will only use the first two. Express.js has `app.get`, `app.post`, `app.put`, `app.del`, but for the most part you will only use the first two.
If you just want to display a page, then use `GET`, if you are submitting a form, sending a file then use `POST`. If you just want to display a page, then use `GET`, if you are submitting a form, sending a file then use `POST`.
Here is a typical workflow of adding new routes to your application. Let's say we are building Here is a typical workflow for adding new routes to your application. Let's say we are building
a page that lists all books from database. a page that lists all books from database.
**Step 1.** Start by defining a route. **Step 1.** Start by defining a route.
@ -730,7 +780,16 @@ io.sockets.on('connection', function(socket) {
}); });
``` ```
We are done with the server-side business. One last thing left to change:
```js
app.listen(app.get('port'), function() {
```
to
```js
server.listen(app.get('port'), function() {
```
At this point we are done with the back-end.
You now have a choice - to include your JavaScript code in Jade templates or have all your client-side You now have a choice - to include your JavaScript code in Jade templates or have all your client-side
JavaScript in a separate file - in `main.js`. I will admit, when I first started out with Node.js and JavaScript in general, JavaScript in a separate file - in `main.js`. I will admit, when I first started out with Node.js and JavaScript in general,
@ -781,6 +840,30 @@ And that's it, we are done!
If you want to see a really cool real-time dashboard check out this [live example](http://hackathonstarter.herokuapp.com/dashboard). Refer to the [pull request #23](https://github.com/sahat/hackathon-starter/pull/23/files) to see how it is implemented. If you want to see a really cool real-time dashboard check out this [live example](http://hackathonstarter.herokuapp.com/dashboard). Refer to the [pull request #23](https://github.com/sahat/hackathon-starter/pull/23/files) to see how it is implemented.
### How does “Forgot your password” feature work?
There are **4** routes in total that handle forgot password and reset password:
```js
app.get('/forgot', forgotController.getForgot);
app.post('/forgot', forgotController.postForgot);
app.get('/reset/:token', resetController.getReset);
app.post('/reset/:token', resetController.postReset);
```
The first step begins at the get `GET /forgot` when user clicks on **Forgot your password?** link on the *Login* page. The `POST /forgot` handles the form submission. If email address is valid, it creates a random 20-bit hash, finds that users email in the database and sets `resetPasswordToken` field to the newly generated random 20-bit hash, additionally `resetPasswordExpires` is set to 1 hour into the future. That means from the moment you receive an email, that reset link will be valid only for one hour (for security reasons its a good practice to expire reset password links). If 1 hour is too short for your needs, feel free to increase it. The final step is to actually send an email with a reset link. This is all elegantly done using **async.waterfall** control flow.
Notice how it handles the case when no email address exists:
```js
if (!user) {
req.flash('errors', { msg: 'No account with that email address exists.' });
return res.redirect('/forgot');
}
```
Some people might find this approach to be less secure. Maybe a better approach might have been to let the user know “If there is an account with provided e-mail address, we will send you a reset link”. Again, feel free to change it based on your application needs.
The second step involves resetting a password. After clicking on a reset link, it redirects you to a page where you can set a new password. The token validity check is performed twice - on `GET` request when you click on a reset link and on `POST` request after you submit a new password. After selecting a new password, both `passwordResetToken` and `resetPasswordExpire` fields are deleted from the database. This is easily done by setting their value to `undefined`; *Mongoose* will run `$unset` internally. And finally, user is logged in with the new password and a confirmation email is sent notifying about the password change.
Mongoose Cheatsheet Mongoose Cheatsheet
------------------- -------------------
#### Find all users: #### Find all users:
@ -886,12 +969,28 @@ Add this to `package.json`, after *name* and *version*. This is necessary becaus
- And you are done! (Not quite as simple as Heroku, huh?) - And you are done! (Not quite as simple as Heroku, huh?)
<img src="https://www.nodejitsu.com/img/media/nodejitsu-transparent.png" width="200"> <img src="https://www.nodejitsu.com/img/media/nodejitsu-transparent.png" width="200">
- To install **jitsu**, open a terminal and type: `sudo npm install -g jitsu`
TODO: Will be added soon. - Run `jitsu login` and enter your login credentials
- From your app directory, run `jitsu deploy`
- This will create a new application snapshot, generate and/or update project metadata
- Done!
<img src="http://upload.wikimedia.org/wikipedia/en/f/ff/Windows_Azure_logo.png" width="200"> <img src="http://upload.wikimedia.org/wikipedia/en/f/ff/Windows_Azure_logo.png" width="200">
TODO: Will be added soon. - Login to [Windows Azure Management Portal](http://manage.windowsazure.com/)
- Click the **+ NEW** button on the bottom left of the portal
- Click **WEB SITE**, then **QUICK CREATE**
- Enter a name for **URL** and select the datacenter **REGION** for your web site
- Click on **CREATE WEB SITE** button
- Once the web site status changes to *Running*, click on the name of the web site to access the Dashboard
- At the bottom right of the Quickstart page, select **Set up a deployment from source control**
- Select **Local Git repository** from the list, and then click the arrow
- To enable Git publishing, Azure will ask you to create a user name and password
- Once the Git repository is ready, you will be presented with a **GIT URL**
- Inside your *Hackathon Starter* directory, run `git remote add azure [Azure Git URL]`
- To push your changes simply run `git push azure master`
- **Note:** *You will be prompted for the password you created earlier*
- On **Deployments** tab of your Windows Azure Web Site, you will see the deployment history
TODO TODO
---- ----
@ -899,7 +998,7 @@ TODO
Contributing Contributing
------------ ------------
If something is unclear, confusing, or needs to be refactored, please let me know. Pull requests are always welcome, but due to the opinionated nature of this project, I cannot accept every pull request. Please open an issue before submitting a pull request. This project uses [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) with a few exceptions. If something is unclear, confusing, or needs to be refactored, please let me know. Pull requests are always welcome, but due to the opinionated nature of this project, I cannot accept every pull request. Please open an issue before submitting a pull request. This project uses [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) with a few minor exceptions. If you are submitting a pull request that involves Jade templates, please make sure you are using *spaces*, not tabs.
License License
------- -------

15
app.js
View File

@ -9,6 +9,7 @@ var path = require('path');
var mongoose = require('mongoose'); var mongoose = require('mongoose');
var passport = require('passport'); var passport = require('passport');
var expressValidator = require('express-validator'); var expressValidator = require('express-validator');
var connectAssets = require('connect-assets');
@ -20,6 +21,8 @@ var homeController = require('./controllers/home');
var userController = require('./controllers/user'); var userController = require('./controllers/user');
var apiController = require('./controllers/api'); var apiController = require('./controllers/api');
var contactController = require('./controllers/contact'); var contactController = require('./controllers/contact');
var forgotController = require('./controllers/forgot');
var resetController = require('./controllers/reset');
/** /**
* API keys + Passport configuration. * API keys + Passport configuration.
@ -55,8 +58,8 @@ var month = (day * 30);
app.set('port', process.env.PORT || 3000); app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views')); app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade'); app.set('view engine', 'jade');
app.use(require('connect-assets')({ app.use(connectAssets({
src: 'public', paths: ['public/css', 'public/js'],
helperContext: app.locals helperContext: app.locals
})); }));
app.use(express.compress()); app.use(express.compress());
@ -70,7 +73,7 @@ app.use(express.methodOverride());
app.use(express.session({ app.use(express.session({
secret: secrets.sessionSecret, secret: secrets.sessionSecret,
store: new MongoStore({ store: new MongoStore({
db: mongoose.connection.db, url: secrets.db,
auto_reconnect: true auto_reconnect: true
}) })
})); }));
@ -100,6 +103,10 @@ app.get('/', homeController.index);
app.get('/login', userController.getLogin); app.get('/login', userController.getLogin);
app.post('/login', userController.postLogin); app.post('/login', userController.postLogin);
app.get('/logout', userController.logout); app.get('/logout', userController.logout);
app.get('/forgot', forgotController.getForgot);
app.post('/forgot', forgotController.postForgot);
app.get('/reset/:token', resetController.getReset);
app.post('/reset/:token', resetController.postReset);
app.get('/signup', userController.getSignup); app.get('/signup', userController.getSignup);
app.post('/signup', userController.postSignup); app.post('/signup', userController.postSignup);
app.get('/contact', contactController.getContact); app.get('/contact', contactController.getContact);
@ -167,3 +174,5 @@ app.get('/auth/venmo/callback', passport.authorize('venmo', { failureRedirect: '
app.listen(app.get('port'), function() { app.listen(app.get('port'), function() {
console.log("✔ Express server listening on port %d in %s mode", app.get('port'), app.settings.env); console.log("✔ Express server listening on port %d in %s mode", app.get('port'), app.settings.env);
}); });
module.exports = app;

View File

@ -1,14 +1,14 @@
var _ = require('underscore');
var passport = require('passport'); var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy; var LocalStrategy = require('passport-local').Strategy;
var OAuthStrategy = require('passport-oauth').OAuthStrategy;
var OAuth2Strategy = require('passport-oauth').OAuth2Strategy;
var FacebookStrategy = require('passport-facebook').Strategy; var FacebookStrategy = require('passport-facebook').Strategy;
var TwitterStrategy = require('passport-twitter').Strategy; var TwitterStrategy = require('passport-twitter').Strategy;
var GitHubStrategy = require('passport-github').Strategy; var GitHubStrategy = require('passport-github').Strategy;
var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy; var GoogleStrategy = require('passport-google-oauth').OAuth2Strategy;
var OAuthStrategy = require('passport-oauth').OAuthStrategy; // Tumblr
var OAuth2Strategy = require('passport-oauth').OAuth2Strategy; // Venmo, Foursquare
var User = require('../models/User'); var User = require('../models/User');
var secrets = require('./secrets'); var secrets = require('./secrets');
var _ = require('underscore');
passport.serializeUser(function(user, done) { passport.serializeUser(function(user, done) {
done(null, user.id); done(null, user.id);
@ -20,6 +20,10 @@ passport.deserializeUser(function(id, done) {
}); });
}); });
/**
* Sign in using Email and Password.
*/
passport.use(new LocalStrategy({ usernameField: 'email' }, function(email, password, done) { passport.use(new LocalStrategy({ usernameField: 'email' }, function(email, password, done) {
User.findOne({ email: email }, function(err, user) { User.findOne({ email: email }, function(err, user) {
if (!user) return done(null, false, { message: 'Email ' + email + ' not found'}); if (!user) return done(null, false, { message: 'Email ' + email + ' not found'});
@ -59,8 +63,12 @@ passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, r
}); });
} else { } else {
User.findOne({ facebook: profile.id }, function(err, existingUser) { User.findOne({ facebook: profile.id }, function(err, existingUser) {
console.log(profile)
if (existingUser) return done(null, existingUser); if (existingUser) return done(null, existingUser);
User.findOne({ email: profile._json.email }, function(err, existingEmailUser) {
if (existingEmailUser) {
req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with Facebook manually from Account Settings.' });
done(err);
} else {
var user = new User(); var user = new User();
user.email = profile._json.email; user.email = profile._json.email;
user.facebook = profile.id; user.facebook = profile.id;
@ -72,6 +80,8 @@ passport.use(new FacebookStrategy(secrets.facebook, function(req, accessToken, r
user.save(function(err) { user.save(function(err) {
done(err, user); done(err, user);
}); });
}
});
}); });
} }
})); }));
@ -104,6 +114,11 @@ passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refre
} else { } else {
User.findOne({ github: profile.id }, function(err, existingUser) { User.findOne({ github: profile.id }, function(err, existingUser) {
if (existingUser) return done(null, existingUser); if (existingUser) return done(null, existingUser);
User.findOne({ email: profile._json.email }, function(err, existingEmailUser) {
if (existingEmailUser) {
req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with GitHub manually from Account Settings.' });
done(err);
} else {
var user = new User(); var user = new User();
user.email = profile._json.email; user.email = profile._json.email;
user.github = profile.id; user.github = profile.id;
@ -115,6 +130,8 @@ passport.use(new GitHubStrategy(secrets.github, function(req, accessToken, refre
user.save(function(err) { user.save(function(err) {
done(err, user); done(err, user);
}); });
}
});
}); });
} }
})); }));
@ -191,6 +208,11 @@ passport.use(new GoogleStrategy(secrets.google, function(req, accessToken, refre
} else { } else {
User.findOne({ google: profile.id }, function(err, existingUser) { User.findOne({ google: profile.id }, function(err, existingUser) {
if (existingUser) return done(null, existingUser); if (existingUser) return done(null, existingUser);
User.findOne({ email: profile._json.email }, function(err, existingEmailUser) {
if (existingEmailUser) {
req.flash('errors', { msg: 'There is already an account using this email address. Sign in to that account and link it with Google manually from Account Settings.' });
done(err);
} else {
var user = new User(); var user = new User();
user.email = profile._json.email; user.email = profile._json.email;
user.google = profile.id; user.google = profile.id;
@ -201,10 +223,17 @@ passport.use(new GoogleStrategy(secrets.google, function(req, accessToken, refre
user.save(function(err) { user.save(function(err) {
done(err, user); done(err, user);
}); });
}
});
}); });
} }
})); }));
/**
* Tumblr API
* Uses OAuth 1.0a Strategy.
*/
passport.use('tumblr', new OAuthStrategy({ passport.use('tumblr', new OAuthStrategy({
requestTokenURL: 'http://www.tumblr.com/oauth/request_token', requestTokenURL: 'http://www.tumblr.com/oauth/request_token',
accessTokenURL: 'http://www.tumblr.com/oauth/access_token', accessTokenURL: 'http://www.tumblr.com/oauth/access_token',
@ -224,6 +253,11 @@ passport.use('tumblr', new OAuthStrategy({
} }
)); ));
/**
* Foursquare API
* Uses OAuth 2.0 Strategy.
*/
passport.use('foursquare', new OAuth2Strategy({ passport.use('foursquare', new OAuth2Strategy({
authorizationURL: 'https://foursquare.com/oauth2/authorize', authorizationURL: 'https://foursquare.com/oauth2/authorize',
tokenURL: 'https://foursquare.com/oauth2/access_token', tokenURL: 'https://foursquare.com/oauth2/access_token',
@ -242,6 +276,11 @@ passport.use('foursquare', new OAuth2Strategy({
} }
)); ));
/**
* Venmo API
* Uses OAuth 2.0 Strategy.
*/
passport.use('venmo', new OAuth2Strategy({ passport.use('venmo', new OAuth2Strategy({
authorizationURL: 'https://api.venmo.com/v1/oauth/authorize', authorizationURL: 'https://api.venmo.com/v1/oauth/authorize',
tokenURL: 'https://api.venmo.com/v1/oauth/access_token', tokenURL: 'https://api.venmo.com/v1/oauth/access_token',
@ -260,11 +299,19 @@ passport.use('venmo', new OAuth2Strategy({
} }
)); ));
/**
* Login Required middleware.
*/
exports.isAuthenticated = function(req, res, next) { exports.isAuthenticated = function(req, res, next) {
if (req.isAuthenticated()) return next(); if (req.isAuthenticated()) return next();
res.redirect('/login'); res.redirect('/login');
}; };
/**
* Authorization Required middleware.
*/
exports.isAuthorized = function(req, res, next) { exports.isAuthorized = function(req, res, next) {
var provider = req.path.split('/').slice(-1)[0]; var provider = req.path.split('/').slice(-1)[0];
if (_.findWhere(req.user.tokens, { kind: provider })) next(); if (_.findWhere(req.user.tokens, { kind: provider })) next();

View File

@ -124,7 +124,7 @@ exports.getScraping = function(req, res, next) {
if (err) return next(err); if (err) return next(err);
var $ = cheerio.load(body); var $ = cheerio.load(body);
var links = []; var links = [];
$('.title a').each(function() { $(".title a[href^='http'], a[href^='https']").each(function() {
links.push($(this)); links.push($(this));
}); });
res.render('api/scraping', { res.render('api/scraping', {

85
controllers/forgot.js Normal file
View File

@ -0,0 +1,85 @@
var async = require('async');
var crypto = require('crypto');
var nodemailer = require("nodemailer");
var User = require('../models/User');
var secrets = require('../config/secrets');
/**
* GET /forgot
* Forgot Password page.
*/
exports.getForgot = function(req, res) {
if (req.isAuthenticated()) {
return res.redirect('/');
}
res.render('account/forgot', {
title: 'Forgot Password'
});
};
/**
* POST /forgot
* Create a random token, then the send user an email with a reset link.
* @param email
*/
exports.postForgot = function(req, res, next) {
req.assert('email', 'Please enter a valid email address.').isEmail();
var errors = req.validationErrors();
if (errors) {
req.flash('errors', errors);
return res.redirect('/forgot');
}
async.waterfall([
function(done) {
crypto.randomBytes(20, function(err, buf) {
var token = buf.toString('hex');
done(err, token);
});
},
function(token, done) {
User.findOne({ email: req.body.email.toLowerCase() }, function(err, user) {
if (!user) {
req.flash('errors', { msg: 'No account with that email address exists.' });
return res.redirect('/forgot');
}
user.resetPasswordToken = token;
user.resetPasswordExpires = Date.now() + 3600000; // 1 hour
user.save(function(err) {
done(err, token, user);
});
});
},
function(token, user, done) {
var smtpTransport = nodemailer.createTransport('SMTP', {
service: 'SendGrid',
auth: {
user: secrets.sendgrid.user,
pass: secrets.sendgrid.password
}
});
var mailOptions = {
to: user.profile.name + ' <' + user.email + '>',
from: 'hackathon@starter.com',
subject: 'Hackathon Starter Password Reset',
text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' +
'Please click on the following link, or paste this into your browser to complete the process:\n\n' +
'http://' + req.headers.host + '/reset/' + token + '\n\n' +
'If you did not request this, please ignore this email and your password will remain unchanged.\n'
};
smtpTransport.sendMail(mailOptions, function(err) {
req.flash('info', { msg: 'An e-mail has been sent to ' + user.email + ' with further instructions.' });
done(err, 'done');
});
}
], function(err) {
if (err) return next(err);
res.redirect('/forgot');
});
};

93
controllers/reset.js Normal file
View File

@ -0,0 +1,93 @@
var async = require('async');
var nodemailer = require('nodemailer');
var User = require('../models/User');
var secrets = require('../config/secrets');
/**
* GET /reset/:token
* Reset Password page.
*/
exports.getReset = function(req, res) {
if (req.isAuthenticated()) {
return res.redirect('/');
}
User
.findOne({ resetPasswordToken: req.params.token })
.where('resetPasswordExpires').gt(Date.now())
.exec(function(err, user) {
if (!user) {
req.flash('errors', { msg: 'Password reset token is invalid or has expired.' });
return res.redirect('/forgot');
}
res.render('account/reset', {
title: 'Password Reset'
});
});
};
/**
* POST /reset/:token
* Process the reset password request.
*/
exports.postReset = function(req, res, next) {
req.assert('password', 'Password must be at least 4 characters long.').len(4);
req.assert('confirm', 'Passwords must match.').equals(req.body.password);
var errors = req.validationErrors();
if (errors) {
req.flash('errors', errors);
return res.redirect('back');
}
async.waterfall([
function(done) {
User
.findOne({ resetPasswordToken: req.params.token })
.where('resetPasswordExpires').gt(Date.now())
.exec(function(err, user) {
if (!user) {
req.flash('errors', { msg: 'Password reset token is invalid or has expired.' });
return res.redirect('back');
}
user.password = req.body.password;
user.resetPasswordToken = undefined;
user.resetPasswordExpires = undefined;
user.save(function(err) {
if (err) return next(err);
req.logIn(user, function(err) {
done(err, user);
});
});
});
},
function(user, done) {
var smtpTransport = nodemailer.createTransport('SMTP', {
service: 'SendGrid',
auth: {
user: secrets.sendgrid.user,
pass: secrets.sendgrid.password
}
});
var mailOptions = {
to: user.profile.name + ' <' + user.email + '>',
from: 'hackathon@starter.com',
subject: 'Your Hackathon Starter password has been changed',
text: 'Hello,\n\n' +
'This is a confirmation that the password for your account ' + user.email + ' has just been changed.\n'
};
smtpTransport.sendMail(mailOptions, function(err) {
req.flash('success', { msg: 'Success! Your password has been changed.' });
done(err);
});
}
], function(err) {
if (err) return next(err);
res.redirect('/');
});
};

View File

@ -18,20 +18,23 @@ var userSchema = new mongoose.Schema({
location: { type: String, default: '' }, location: { type: String, default: '' },
website: { type: String, default: '' }, website: { type: String, default: '' },
picture: { type: String, default: '' } picture: { type: String, default: '' }
} },
resetPasswordToken: String,
resetPasswordExpires: Date
}); });
/** /**
* Hash the password for security. * Hash the password for security.
* "Pre" is a Mongoose middleware that executes before each user.save() call.
*/ */
userSchema.pre('save', function(next) { userSchema.pre('save', function(next) {
var user = this; var user = this;
var SALT_FACTOR = 5;
if (!user.isModified('password')) return next(); if (!user.isModified('password')) return next();
bcrypt.genSalt(SALT_FACTOR, function(err, salt) { bcrypt.genSalt(5, function(err, salt) {
if (err) return next(err); if (err) return next(err);
bcrypt.hash(user.password, salt, null, function(err, hash) { bcrypt.hash(user.password, salt, null, function(err, hash) {
@ -42,6 +45,11 @@ userSchema.pre('save', function(next) {
}); });
}); });
/**
* Validate user's password.
* Used by Passport-Local Strategy for password validation.
*/
userSchema.methods.comparePassword = function(candidatePassword, cb) { userSchema.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) { bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) return cb(err); if (err) return cb(err);
@ -50,12 +58,18 @@ userSchema.methods.comparePassword = function(candidatePassword, cb) {
}; };
/** /**
* Get a URL to a user's Gravatar email. * Get URL to a user's gravatar.
* Used in Navbar and Account Management page.
*/ */
userSchema.methods.gravatar = function(size, defaults) { userSchema.methods.gravatar = function(size, defaults) {
if (!size) size = 200; if (!size) size = 200;
if (!defaults) defaults = 'retro'; if (!defaults) defaults = 'retro';
if (!this.email) {
return 'https://gravatar.com/avatar/?s=' + size + '&d=' + defaults;
}
var md5 = crypto.createHash('md5').update(this.email); var md5 = crypto.createHash('md5').update(this.email);
return 'https://gravatar.com/avatar/' + md5.digest('hex').toString() + '?s=' + size + '&d=' + defaults; return 'https://gravatar.com/avatar/' + md5.digest('hex').toString() + '?s=' + size + '&d=' + defaults;
}; };

View File

@ -1,14 +1,19 @@
{ {
"name": "hackathon-starter", "name": "hackathon-starter",
"version": "0.0.0", "version": "0.0.0",
"repository": {
"type" : "git",
"url" : "https://github.com/sahat/hackathon-starter.git"
},
"scripts": { "scripts": {
"start": "node app.js" "start": "node app.js",
"test": "mocha"
}, },
"dependencies": { "dependencies": {
"async": "~0.2.10", "async": "~0.2.10",
"bcrypt-nodejs": "~0.0.3", "bcrypt-nodejs": "~0.0.3",
"cheerio": "~0.13.1", "cheerio": "~0.13.1",
"connect-assets": "~2.5.4", "connect-assets": "~3.0.0-beta1",
"express": "~3.4.8", "express": "~3.4.8",
"express-flash": "~0.0.2", "express-flash": "~0.0.2",
"express-validator": "~1.0.1", "express-validator": "~1.0.1",
@ -27,14 +32,21 @@
"passport-local": "~0.1.6", "passport-local": "~0.1.6",
"passport-oauth": "~1.0.0", "passport-oauth": "~1.0.0",
"passport-twitter": "~1.0.2", "passport-twitter": "~1.0.2",
"request": "~2.33.0", "request": "~2.34.0",
"tumblr.js": "~0.0.4", "tumblr.js": "~0.0.4",
"twit": "~1.1.12", "twit": "~1.1.12",
"underscore": "~1.6.0", "underscore": "~1.6.0",
"paypal-rest-sdk": "~0.6.4", "paypal-rest-sdk": "~0.6.4",
"connect-mongo": "~0.4.0", "connect-mongo": "~0.4.0",
"twilio": "~1.5.0", "twilio": "~1.5.0",
"validator": "~3.2.1", "clockwork": ">=0.1.0",
"clockwork": "0.1.0" "validator": "~3.3.0",
"csso": "~1.3.11",
"uglify-js": "~2.4.12"
},
"devDependencies": {
"chai": "~1.9.0",
"mocha": "~1.17.1",
"supertest": "~0.9.0"
} }
} }

View File

@ -38,7 +38,8 @@
// Optional: Group multiple button groups together for a toolbar // Optional: Group multiple button groups together for a toolbar
.btn-toolbar { .btn-toolbar {
margin-left: -5px; // Offset the first child's margin margin-left: -5px; // Offset the first child's margin
&:extend(.clearfix all); &:extend(.clearfix all)
;
.btn-group, .btn-group,
.input-group { .input-group {
@ -62,6 +63,7 @@
.border-right-radius(0); .border-right-radius(0);
} }
} }
// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it // 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 > .btn:last-child:not(:first-child),
.btn-group > .dropdown-toggle:not(:first-child) { .btn-group > .dropdown-toggle:not(:first-child) {
@ -72,15 +74,18 @@
.btn-group > .btn-group { .btn-group > .btn-group {
float: left; float: left;
} }
.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { .btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
border-radius: 0; border-radius: 0;
} }
.btn-group > .btn-group:first-child { .btn-group > .btn-group:first-child {
> .btn:last-child, > .btn:last-child,
> .dropdown-toggle { > .dropdown-toggle {
.border-right-radius(0); .border-right-radius(0);
} }
} }
.btn-group > .btn-group:last-child > .btn:first-child { .btn-group > .btn-group:last-child > .btn:first-child {
.border-left-radius(0); .border-left-radius(0);
} }
@ -91,15 +96,24 @@
outline: 0; outline: 0;
} }
// Sizing // Sizing
// //
// Remix the default button sizing classes into new ones for easier manipulation. // Remix the default button sizing classes into new ones for easier manipulation.
.btn-group-xs > .btn { .btn-xs(); } .btn-group-xs > .btn {
.btn-group-sm > .btn { .btn-sm(); } &:extend(.btn-xs)
.btn-group-lg > .btn { .btn-lg(); } ;
}
.btn-group-sm > .btn {
&:extend(.btn-sm)
;
}
.btn-group-lg > .btn {
&:extend(.btn-lg)
;
}
// Split button dropdowns // Split button dropdowns
// ---------------------- // ----------------------
@ -109,6 +123,7 @@
padding-left: 8px; padding-left: 8px;
padding-right: 8px; padding-right: 8px;
} }
.btn-group > .btn-lg + .dropdown-toggle { .btn-group > .btn-lg + .dropdown-toggle {
padding-left: 12px; padding-left: 12px;
padding-right: 12px; padding-right: 12px;
@ -125,22 +140,22 @@
} }
} }
// Reposition the caret // Reposition the caret
.btn .caret { .btn .caret {
margin-left: 0; margin-left: 0;
} }
// Carets in other button sizes // Carets in other button sizes
.btn-lg .caret { .btn-lg .caret {
border-width: @caret-width-large @caret-width-large 0; border-width: @caret-width-large @caret-width-large 0;
border-bottom-width: 0; border-bottom-width: 0;
} }
// Upside down carets for .dropup // Upside down carets for .dropup
.dropup .btn-lg .caret { .dropup .btn-lg .caret {
border-width: 0 @caret-width-large @caret-width-large; border-width: 0 @caret-width-large @caret-width-large;
} }
// Vertical button groups // Vertical button groups
// ---------------------- // ----------------------
@ -156,7 +171,8 @@
// Clear floats so dropdown menus can be properly placed // Clear floats so dropdown menus can be properly placed
> .btn-group { > .btn-group {
&:extend(.clearfix all); &:extend(.clearfix all)
;
> .btn { > .btn {
float: none; float: none;
} }
@ -184,21 +200,22 @@
.border-top-radius(0); .border-top-radius(0);
} }
} }
.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { .btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
border-radius: 0; border-radius: 0;
} }
.btn-group-vertical > .btn-group:first-child:not(:last-child) { .btn-group-vertical > .btn-group:first-child:not(:last-child) {
> .btn:last-child, > .btn:last-child,
> .dropdown-toggle { > .dropdown-toggle {
.border-bottom-radius(0); .border-bottom-radius(0);
} }
} }
.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { .btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {
.border-top-radius(0); .border-top-radius(0);
} }
// Justified button groups // Justified button groups
// ---------------------- // ----------------------
@ -218,7 +235,6 @@
} }
} }
// Checkbox and radio options // Checkbox and radio options
[data-toggle="buttons"] > .btn > input[type="radio"], [data-toggle="buttons"] > .btn > input[type="radio"],
[data-toggle="buttons"] > .btn > input[type="checkbox"] { [data-toggle="buttons"] > .btn > input[type="checkbox"] {

View File

@ -2,7 +2,6 @@
// Buttons // Buttons
// -------------------------------------------------- // --------------------------------------------------
// Base styles // Base styles
// -------------------------------------------------- // --------------------------------------------------
@ -19,9 +18,13 @@
.button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @border-radius-base); .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @border-radius-base);
.user-select(none); .user-select(none);
&,
&:active,
&.active {
&:focus { &:focus {
.tab-focus(); .tab-focus();
} }
}
&:hover, &:hover,
&:focus { &:focus {
@ -46,34 +49,37 @@
} }
} }
// Alternate buttons // Alternate buttons
// -------------------------------------------------- // --------------------------------------------------
.btn-default { .btn-default {
.button-variant(@btn-default-color; @btn-default-bg; @btn-default-border); .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);
} }
.btn-primary { .btn-primary {
.button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border); .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);
} }
// Success appears as green // Success appears as green
.btn-success { .btn-success {
.button-variant(@btn-success-color; @btn-success-bg; @btn-success-border); .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);
} }
// Info appears as blue-green // Info appears as blue-green
.btn-info { .btn-info {
.button-variant(@btn-info-color; @btn-info-bg; @btn-info-border); .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);
} }
// Warning appears as orange // Warning appears as orange
.btn-warning { .btn-warning {
.button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border); .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);
} }
// Danger and error appear as red // Danger and error appear as red
.btn-danger { .btn-danger {
.button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border); .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);
} }
// Link buttons // Link buttons
// ------------------------- // -------------------------
@ -113,7 +119,6 @@
} }
} }
// Button Sizes // Button Sizes
// -------------------------------------------------- // --------------------------------------------------
@ -121,15 +126,16 @@
// line-height: ensure even-numbered height of button next to large input // 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); .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);
} }
.btn-sm { .btn-sm {
// line-height: ensure proper height of button next to small input // 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); .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);
} }
.btn-xs { .btn-xs {
.button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @border-radius-small); .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @border-radius-small);
} }
// Block button // Block button
// -------------------------------------------------- // --------------------------------------------------

View File

@ -2,7 +2,6 @@
// Carousel // Carousel
// -------------------------------------------------- // --------------------------------------------------
// Wrapper for the slide container and indicators // Wrapper for the slide container and indicators
.carousel { .carousel {
position: relative; position: relative;
@ -21,14 +20,17 @@
// Account for jankitude on images // Account for jankitude on images
> img, > img,
> a > img { > a > img {
.img-responsive(); &:extend(.img-responsive)
;
line-height: 1; line-height: 1;
} }
} }
> .active, > .active,
> .next, > .next,
> .prev { display: block; } > .prev {
display: block;
}
> .active { > .active {
left: 0; left: 0;
@ -200,14 +202,13 @@
} }
} }
// Scale up controls for tablets and up // Scale up controls for tablets and up
@media screen and (min-width: @screen-sm-min) { @media screen and (min-width: @screen-sm-min) {
// Scale up the controls a smidge // Scale up the controls a smidge
.carousel-control { .carousel-control {
.glyphicons-chevron-left, .glyphicon-chevron-left,
.glyphicons-chevron-right, .glyphicon-chevron-right,
.icon-prev, .icon-prev,
.icon-next { .icon-next {
width: 30px; width: 30px;

View File

@ -2,7 +2,6 @@
// Forms // Forms
// -------------------------------------------------- // --------------------------------------------------
// Normalize non-controls // Normalize non-controls
// //
// Restyle and baseline non-control form elements. // Restyle and baseline non-control form elements.
@ -35,7 +34,6 @@ label {
font-weight: bold; font-weight: bold;
} }
// Normalize form controls // Normalize form controls
// //
// While most of our form styles require extra classes, some basic normalization // While most of our form styles require extra classes, some basic normalization
@ -88,7 +86,6 @@ output {
color: @input-color; color: @input-color;
} }
// Common form controls // Common form controls
// //
// Shared size and type resets for form controls. Apply `.form-control` to any // Shared size and type resets for form controls. Apply `.form-control` to any
@ -133,9 +130,10 @@ output {
.placeholder(); .placeholder();
// Disabled and read-only inputs // 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, // HTML5 says that controls under a fieldset > legend:first-child won't be
// we don't honor that edge case; we style them as disabled anyway. // disabled if the fieldset is disabled. Due to implementation difficulty, we
// don't honor that edge case; we style them as disabled anyway.
&[disabled], &[disabled],
&[readonly], &[readonly],
fieldset[disabled] & { fieldset[disabled] & {
@ -150,15 +148,26 @@ output {
} }
} }
// Search inputs in iOS
//
// This overrides the extra rounded corners on search inputs in iOS so that our
// `.form-control` class can properly style them. Note that this cannot simply
// be added to `.form-control` as it's not specific enough. For details, see
// https://github.com/twbs/bootstrap/issues/11586.
input[type="search"] {
-webkit-appearance: none;
}
// Special styles for iOS date input // Special styles for iOS date input
// //
// In Mobile Safari, date inputs require a pixel line-height that matches the // In Mobile Safari, date inputs require a pixel line-height that matches the
// given height of the input. // given height of the input.
input[type="date"] { input[type="date"] {
line-height: @input-height-base; line-height: @input-height-base;
} }
// Form groups // Form groups
// //
// Designed to help with the organization and spacing of vertical forms. For // Designed to help with the organization and spacing of vertical forms. For
@ -168,7 +177,6 @@ input[type="date"] {
margin-bottom: 15px; margin-bottom: 15px;
} }
// Checkboxes and radios // Checkboxes and radios
// //
// Indent the labels to position radios/checkboxes as hanging controls. // Indent the labels to position radios/checkboxes as hanging controls.
@ -186,6 +194,7 @@ input[type="date"] {
cursor: pointer; cursor: pointer;
} }
} }
.radio input[type="radio"], .radio input[type="radio"],
.radio-inline input[type="radio"], .radio-inline input[type="radio"],
.checkbox input[type="checkbox"], .checkbox input[type="checkbox"],
@ -193,6 +202,7 @@ input[type="date"] {
float: left; float: left;
margin-left: -20px; margin-left: -20px;
} }
.radio + .radio, .radio + .radio,
.checkbox + .checkbox { .checkbox + .checkbox {
margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing
@ -208,6 +218,7 @@ input[type="date"] {
font-weight: normal; font-weight: normal;
cursor: pointer; cursor: pointer;
} }
.radio-inline + .radio-inline, .radio-inline + .radio-inline,
.checkbox-inline + .checkbox-inline { .checkbox-inline + .checkbox-inline {
margin-top: 0; margin-top: 0;
@ -229,7 +240,6 @@ input[type="checkbox"],
} }
} }
// Form control sizing // Form control sizing
// //
// Build on `.form-control` with modifier classes to decrease or increase the // Build on `.form-control` with modifier classes to decrease or increase the
@ -243,7 +253,6 @@ input[type="checkbox"],
.input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large); .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);
} }
// Form control feedback states // Form control feedback states
// //
// Apply contextual and semantic states to individual form controls. // Apply contextual and semantic states to individual form controls.
@ -274,14 +283,15 @@ input[type="checkbox"],
.has-success { .has-success {
.form-control-validation(@state-success-text; @state-success-text; @state-success-bg); .form-control-validation(@state-success-text; @state-success-text; @state-success-bg);
} }
.has-warning { .has-warning {
.form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg); .form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg);
} }
.has-error { .has-error {
.form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg); .form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg);
} }
// Static form control text // Static form control text
// //
// Apply class to a `p` element to make any string of text align with labels in // Apply class to a `p` element to make any string of text align with labels in
@ -291,7 +301,6 @@ input[type="checkbox"],
margin-bottom: 0; // Remove default margin from `p` margin-bottom: 0; // Remove default margin from `p`
} }
// Help text // Help text
// //
// Apply to any element you wish to create light text for placement immediately // Apply to any element you wish to create light text for placement immediately
@ -304,8 +313,6 @@ input[type="checkbox"],
color: lighten(@text-color, 25%); // lighten the text some for contrast color: lighten(@text-color, 25%); // lighten the text some for contrast
} }
// Inline forms // Inline forms
// //
// Make forms appear inline(-block) by adding the `.form-inline` class. Inline // Make forms appear inline(-block) by adding the `.form-inline` class. Inline
@ -335,6 +342,11 @@ input[type="checkbox"],
vertical-align: middle; vertical-align: middle;
} }
// Input groups need that 100% width though
.input-group > .form-control {
width: 100%;
}
.control-label { .control-label {
margin-bottom: 0; margin-bottom: 0;
vertical-align: middle; vertical-align: middle;
@ -351,6 +363,7 @@ input[type="checkbox"],
padding-left: 0; padding-left: 0;
vertical-align: middle; vertical-align: middle;
} }
.radio input[type="radio"], .radio input[type="radio"],
.checkbox input[type="checkbox"] { .checkbox input[type="checkbox"] {
float: none; float: none;
@ -367,7 +380,6 @@ input[type="checkbox"],
} }
} }
// Horizontal forms // Horizontal forms
// //
// Horizontal forms are built on grid classes and allow you to create forms with // Horizontal forms are built on grid classes and allow you to create forms with

View File

@ -2,7 +2,6 @@
// Grid system // Grid system
// -------------------------------------------------- // --------------------------------------------------
// Container widths // Container widths
// //
// Set the container width, and override it for fixed navbars in media queries. // Set the container width, and override it for fixed navbars in media queries.
@ -21,7 +20,6 @@
} }
} }
// Fluid container // Fluid container
// //
// Utilizes the mixin meant for fixed width containers, but without any defined // Utilizes the mixin meant for fixed width containers, but without any defined
@ -31,7 +29,6 @@
.container-fixed(); .container-fixed();
} }
// Row // Row
// //
// Rows contain and clear the floats of your columns. // Rows contain and clear the floats of your columns.
@ -40,25 +37,18 @@
.make-row(); .make-row();
} }
// Columns // Columns
// //
// Common styles for small and large grid columns // Common styles for small and large grid columns
.make-grid-columns(); .make-grid-columns();
// Extra small grid // Extra small grid
// //
// Columns, offsets, pushes, and pulls for extra small devices like // Columns, offsets, pushes, and pulls for extra small devices like
// smartphones. // smartphones.
.make-grid-columns-float(xs); .make-grid(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 // Small grid
// //
@ -66,35 +56,21 @@
// to tablets. // to tablets.
@media (min-width: @screen-sm-min) { @media (min-width: @screen-sm-min) {
.make-grid-columns-float(sm); .make-grid(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 // Medium grid
// //
// Columns, offsets, pushes, and pulls for the desktop device range. // Columns, offsets, pushes, and pulls for the desktop device range.
@media (min-width: @screen-md-min) { @media (min-width: @screen-md-min) {
.make-grid-columns-float(md); .make-grid(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 // Large grid
// //
// Columns, offsets, pushes, and pulls for the large desktop device range. // Columns, offsets, pushes, and pulls for the large desktop device range.
@media (min-width: @screen-lg-min) { @media (min-width: @screen-lg-min) {
.make-grid-columns-float(lg); .make-grid(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);
} }

View File

@ -17,6 +17,11 @@
} }
.form-control { .form-control {
// Ensure that the input is always above the *appended* addon button for
// proper border colors.
position: relative;
z-index: 2;
// IE9 fubars the placeholder attribute in text inputs and the arrows on // IE9 fubars the placeholder attribute in text inputs and the arrows on
// select elements in input groups. To fix it, we float the input. Details: // select elements in input groups. To fix it, we float the input. Details:
// https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855 // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855
@ -34,11 +39,15 @@
.input-group-lg > .form-control, .input-group-lg > .form-control,
.input-group-lg > .input-group-addon, .input-group-lg > .input-group-addon,
.input-group-lg > .input-group-btn > .btn { .input-lg(); } .input-group-lg > .input-group-btn > .btn {
.input-lg();
}
.input-group-sm > .form-control, .input-group-sm > .form-control,
.input-group-sm > .input-group-addon, .input-group-sm > .input-group-addon,
.input-group-sm > .input-group-btn > .btn { .input-sm(); } .input-group-sm > .input-group-btn > .btn {
.input-sm();
}
// Display as table-cell // Display as table-cell
// ------------------------- // -------------------------
@ -51,6 +60,7 @@
border-radius: 0; border-radius: 0;
} }
} }
// Addon and addon wrapper for buttons // Addon and addon wrapper for buttons
.input-group-addon, .input-group-addon,
.input-group-btn { .input-group-btn {
@ -101,9 +111,11 @@
.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { .input-group-btn:last-child > .btn-group:not(:last-child) > .btn {
.border-right-radius(0); .border-right-radius(0);
} }
.input-group-addon:first-child { .input-group-addon:first-child {
border-right: 0; border-right: 0;
} }
.input-group .form-control:last-child, .input-group .form-control:last-child,
.input-group-addon:last-child, .input-group-addon:last-child,
.input-group-btn:last-child > .btn, .input-group-btn:last-child > .btn,
@ -113,6 +125,7 @@
.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { .input-group-btn:first-child > .btn-group:not(:first-child) > .btn {
.border-left-radius(0); .border-left-radius(0);
} }
.input-group-addon:last-child { .input-group-addon:last-child {
border-left: 0; border-left: 0;
} }

View File

@ -2,7 +2,6 @@
// Mixins // Mixins
// -------------------------------------------------- // --------------------------------------------------
// Utilities // Utilities
// ------------------------- // -------------------------
@ -48,17 +47,26 @@
width: @width; width: @width;
height: @height; height: @height;
} }
.square(@size) { .square(@size) {
.size(@size; @size); .size(@size; @size);
} }
// Placeholder text // Placeholder text
.placeholder(@color: @input-color-placeholder) { .placeholder(@color: @input-color-placeholder) {
&:-moz-placeholder { color: @color; } // Firefox 4-18 &::-moz-placeholder {
&::-moz-placeholder { color: @color; // Firefox 19+ color: @color; // Firefox
opacity: 1; } // See https://github.com/twbs/bootstrap/pull/11526 opacity: 1;
&:-ms-input-placeholder { color: @color; } // Internet Explorer 10+ }
&::-webkit-input-placeholder { color: @color; } // Safari and Chrome // 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 // Text overflow
@ -86,13 +94,12 @@
background-color: transparent; background-color: transparent;
border: 0; border: 0;
} }
// New mixin to use as of v3.0.1 // New mixin to use as of v3.0.1
.text-hide() { .text-hide() {
.hide-text(); .hide-text();
} }
// CSS3 PROPERTIES // CSS3 PROPERTIES
// -------------------------------------------------- // --------------------------------------------------
@ -101,14 +108,17 @@
border-top-right-radius: @radius; border-top-right-radius: @radius;
border-top-left-radius: @radius; border-top-left-radius: @radius;
} }
.border-right-radius(@radius) { .border-right-radius(@radius) {
border-bottom-right-radius: @radius; border-bottom-right-radius: @radius;
border-top-right-radius: @radius; border-top-right-radius: @radius;
} }
.border-bottom-radius(@radius) { .border-bottom-radius(@radius) {
border-bottom-right-radius: @radius; border-bottom-right-radius: @radius;
border-bottom-left-radius: @radius; border-bottom-left-radius: @radius;
} }
.border-left-radius(@radius) { .border-left-radius(@radius) {
border-bottom-left-radius: @radius; border-bottom-left-radius: @radius;
border-top-left-radius: @radius; border-top-left-radius: @radius;
@ -129,18 +139,22 @@
-webkit-transition: @transition; -webkit-transition: @transition;
transition: @transition; transition: @transition;
} }
.transition-property(@transition-property) { .transition-property(@transition-property) {
-webkit-transition-property: @transition-property; -webkit-transition-property: @transition-property;
transition-property: @transition-property; transition-property: @transition-property;
} }
.transition-delay(@transition-delay) { .transition-delay(@transition-delay) {
-webkit-transition-delay: @transition-delay; -webkit-transition-delay: @transition-delay;
transition-delay: @transition-delay; transition-delay: @transition-delay;
} }
.transition-duration(@transition-duration) { .transition-duration(@transition-duration) {
-webkit-transition-duration: @transition-duration; -webkit-transition-duration: @transition-duration;
transition-duration: @transition-duration; transition-duration: @transition-duration;
} }
.transition-transform(@transition) { .transition-transform(@transition) {
-webkit-transition: -webkit-transform @transition; -webkit-transition: -webkit-transform @transition;
-moz-transition: -moz-transform @transition; -moz-transition: -moz-transform @transition;
@ -154,21 +168,25 @@
-ms-transform: rotate(@degrees); // IE9 only -ms-transform: rotate(@degrees); // IE9 only
transform: rotate(@degrees); transform: rotate(@degrees);
} }
.scale(@ratio; @ratio-y...) { .scale(@ratio; @ratio-y...) {
-webkit-transform: scale(@ratio, @ratio-y); -webkit-transform: scale(@ratio, @ratio-y);
-ms-transform: scale(@ratio, @ratio-y); // IE9 only -ms-transform: scale(@ratio, @ratio-y); // IE9 only
transform: scale(@ratio, @ratio-y); transform: scale(@ratio, @ratio-y);
} }
.translate(@x; @y) { .translate(@x; @y) {
-webkit-transform: translate(@x, @y); -webkit-transform: translate(@x, @y);
-ms-transform: translate(@x, @y); // IE9 only -ms-transform: translate(@x, @y); // IE9 only
transform: translate(@x, @y); transform: translate(@x, @y);
} }
.skew(@x; @y) { .skew(@x; @y) {
-webkit-transform: skew(@x, @y); -webkit-transform: skew(@x, @y);
-ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+ -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+
transform: skew(@x, @y); transform: skew(@x, @y);
} }
.translate3d(@x; @y; @z) { .translate3d(@x; @y; @z) {
-webkit-transform: translate3d(@x, @y, @z); -webkit-transform: translate3d(@x, @y, @z);
transform: translate3d(@x, @y, @z); transform: translate3d(@x, @y, @z);
@ -179,21 +197,25 @@
-ms-transform: rotateX(@degrees); // IE9 only -ms-transform: rotateX(@degrees); // IE9 only
transform: rotateX(@degrees); transform: rotateX(@degrees);
} }
.rotateY(@degrees) { .rotateY(@degrees) {
-webkit-transform: rotateY(@degrees); -webkit-transform: rotateY(@degrees);
-ms-transform: rotateY(@degrees); // IE9 only -ms-transform: rotateY(@degrees); // IE9 only
transform: rotateY(@degrees); transform: rotateY(@degrees);
} }
.perspective(@perspective) { .perspective(@perspective) {
-webkit-perspective: @perspective; -webkit-perspective: @perspective;
-moz-perspective: @perspective; -moz-perspective: @perspective;
perspective: @perspective; perspective: @perspective;
} }
.perspective-origin(@perspective) { .perspective-origin(@perspective) {
-webkit-perspective-origin: @perspective; -webkit-perspective-origin: @perspective;
-moz-perspective-origin: @perspective; -moz-perspective-origin: @perspective;
perspective-origin: @perspective; perspective-origin: @perspective;
} }
.transform-origin(@origin) { .transform-origin(@origin) {
-webkit-transform-origin: @origin; -webkit-transform-origin: @origin;
-moz-transform-origin: @origin; -moz-transform-origin: @origin;
@ -206,26 +228,32 @@
-webkit-animation: @animation; -webkit-animation: @animation;
animation: @animation; animation: @animation;
} }
.animation-name(@name) { .animation-name(@name) {
-webkit-animation-name: @name; -webkit-animation-name: @name;
animation-name: @name; animation-name: @name;
} }
.animation-duration(@duration) { .animation-duration(@duration) {
-webkit-animation-duration: @duration; -webkit-animation-duration: @duration;
animation-duration: @duration; animation-duration: @duration;
} }
.animation-timing-function(@timing-function) { .animation-timing-function(@timing-function) {
-webkit-animation-timing-function: @timing-function; -webkit-animation-timing-function: @timing-function;
animation-timing-function: @timing-function; animation-timing-function: @timing-function;
} }
.animation-delay(@delay) { .animation-delay(@delay) {
-webkit-animation-delay: @delay; -webkit-animation-delay: @delay;
animation-delay: @delay; animation-delay: @delay;
} }
.animation-iteration-count(@iteration-count) { .animation-iteration-count(@iteration-count) {
-webkit-animation-iteration-count: @iteration-count; -webkit-animation-iteration-count: @iteration-count;
animation-iteration-count: @iteration-count; animation-iteration-count: @iteration-count;
} }
.animation-direction(@direction) { .animation-direction(@direction) {
-webkit-animation-direction: @direction; -webkit-animation-direction: @direction;
animation-direction: @direction; animation-direction: @direction;
@ -253,7 +281,6 @@
-webkit-user-select: @select; -webkit-user-select: @select;
-moz-user-select: @select; -moz-user-select: @select;
-ms-user-select: @select; // IE10+ -ms-user-select: @select; // IE10+
-o-user-select: @select;
user-select: @select; user-select: @select;
} }
@ -291,8 +318,6 @@
filter: ~"alpha(opacity=@{opacity-ie})"; filter: ~"alpha(opacity=@{opacity-ie})";
} }
// GRADIENTS // GRADIENTS
// -------------------------------------------------- // --------------------------------------------------
@ -356,8 +381,6 @@
filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)"));
} }
// Retina images // Retina images
// //
// Short retina mixin for setting background-image and -size // Short retina mixin for setting background-image and -size
@ -365,19 +388,12 @@
.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) { .img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {
background-image: url("@{file-1x}"); background-image: url("@{file-1x}");
@media @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) {
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-image: url("@{file-2x}");
background-size: @width-1x @height-1x; background-size: @width-1x @height-1x;
} }
} }
// Responsive image // Responsive image
// //
// Keep images from scaling beyond the width of their parents. // Keep images from scaling beyond the width of their parents.
@ -388,7 +404,6 @@
height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching
} }
// COMPONENT MIXINS // COMPONENT MIXINS
// -------------------------------------------------- // --------------------------------------------------
@ -476,7 +491,9 @@
a& { a& {
color: @color; color: @color;
.list-group-item-heading { color: inherit; } .list-group-item-heading {
color: inherit;
}
&:hover, &:hover,
&:focus { &:focus {
@ -622,20 +639,22 @@
// More easily include all the states for responsive-utilities.less. // More easily include all the states for responsive-utilities.less.
.responsive-visibility() { .responsive-visibility() {
display: block !important; display: block !important;
table& { display: table; } table& {
tr& { display: table-row !important; } display: table;
}
tr& {
display: table-row !important;
}
th&, th&,
td& { display: table-cell !important; } td& {
display: table-cell !important;
}
} }
.responsive-invisibility() { .responsive-invisibility() {
&, display: none !important;
tr&,
th&,
td& { display: none !important; }
} }
// Grid System // Grid System
// ----------- // -----------
@ -645,14 +664,16 @@
margin-left: auto; margin-left: auto;
padding-left: (@grid-gutter-width / 2); padding-left: (@grid-gutter-width / 2);
padding-right: (@grid-gutter-width / 2); padding-right: (@grid-gutter-width / 2);
&:extend(.clearfix all); &:extend(.clearfix all)
;
} }
// Creates a wrapper for a series of columns // Creates a wrapper for a series of columns
.make-row(@gutter: @grid-gutter-width) { .make-row(@gutter: @grid-gutter-width) {
margin-left: (@gutter / -2); margin-left: (@gutter / -2);
margin-right: (@gutter / -2); margin-right: (@gutter / -2);
&:extend(.clearfix all); &:extend(.clearfix all)
;
} }
// Generate the extra small columns // Generate the extra small columns
@ -664,23 +685,25 @@
padding-left: (@gutter / 2); padding-left: (@gutter / 2);
padding-right: (@gutter / 2); padding-right: (@gutter / 2);
} }
.make-xs-column-offset(@columns) { .make-xs-column-offset(@columns) {
@media (min-width: @screen-xs-min) { @media (min-width: @screen-xs-min) {
margin-left: percentage((@columns / @grid-columns)); margin-left: percentage((@columns / @grid-columns));
} }
} }
.make-xs-column-push(@columns) { .make-xs-column-push(@columns) {
@media (min-width: @screen-xs-min) { @media (min-width: @screen-xs-min) {
left: percentage((@columns / @grid-columns)); left: percentage((@columns / @grid-columns));
} }
} }
.make-xs-column-pull(@columns) { .make-xs-column-pull(@columns) {
@media (min-width: @screen-xs-min) { @media (min-width: @screen-xs-min) {
right: percentage((@columns / @grid-columns)); right: percentage((@columns / @grid-columns));
} }
} }
// Generate the small columns // Generate the small columns
.make-sm-column(@columns; @gutter: @grid-gutter-width) { .make-sm-column(@columns; @gutter: @grid-gutter-width) {
position: relative; position: relative;
@ -693,23 +716,25 @@
width: percentage((@columns / @grid-columns)); width: percentage((@columns / @grid-columns));
} }
} }
.make-sm-column-offset(@columns) { .make-sm-column-offset(@columns) {
@media (min-width: @screen-sm-min) { @media (min-width: @screen-sm-min) {
margin-left: percentage((@columns / @grid-columns)); margin-left: percentage((@columns / @grid-columns));
} }
} }
.make-sm-column-push(@columns) { .make-sm-column-push(@columns) {
@media (min-width: @screen-sm-min) { @media (min-width: @screen-sm-min) {
left: percentage((@columns / @grid-columns)); left: percentage((@columns / @grid-columns));
} }
} }
.make-sm-column-pull(@columns) { .make-sm-column-pull(@columns) {
@media (min-width: @screen-sm-min) { @media (min-width: @screen-sm-min) {
right: percentage((@columns / @grid-columns)); right: percentage((@columns / @grid-columns));
} }
} }
// Generate the medium columns // Generate the medium columns
.make-md-column(@columns; @gutter: @grid-gutter-width) { .make-md-column(@columns; @gutter: @grid-gutter-width) {
position: relative; position: relative;
@ -722,23 +747,25 @@
width: percentage((@columns / @grid-columns)); width: percentage((@columns / @grid-columns));
} }
} }
.make-md-column-offset(@columns) { .make-md-column-offset(@columns) {
@media (min-width: @screen-md-min) { @media (min-width: @screen-md-min) {
margin-left: percentage((@columns / @grid-columns)); margin-left: percentage((@columns / @grid-columns));
} }
} }
.make-md-column-push(@columns) { .make-md-column-push(@columns) {
@media (min-width: @screen-md-min) { @media (min-width: @screen-md-min) {
left: percentage((@columns / @grid-columns)); left: percentage((@columns / @grid-columns));
} }
} }
.make-md-column-pull(@columns) { .make-md-column-pull(@columns) {
@media (min-width: @screen-md-min) { @media (min-width: @screen-md-min) {
right: percentage((@columns / @grid-columns)); right: percentage((@columns / @grid-columns));
} }
} }
// Generate the large columns // Generate the large columns
.make-lg-column(@columns; @gutter: @grid-gutter-width) { .make-lg-column(@columns; @gutter: @grid-gutter-width) {
position: relative; position: relative;
@ -751,23 +778,25 @@
width: percentage((@columns / @grid-columns)); width: percentage((@columns / @grid-columns));
} }
} }
.make-lg-column-offset(@columns) { .make-lg-column-offset(@columns) {
@media (min-width: @screen-lg-min) { @media (min-width: @screen-lg-min) {
margin-left: percentage((@columns / @grid-columns)); margin-left: percentage((@columns / @grid-columns));
} }
} }
.make-lg-column-push(@columns) { .make-lg-column-push(@columns) {
@media (min-width: @screen-lg-min) { @media (min-width: @screen-lg-min) {
left: percentage((@columns / @grid-columns)); left: percentage((@columns / @grid-columns));
} }
} }
.make-lg-column-pull(@columns) { .make-lg-column-pull(@columns) {
@media (min-width: @screen-lg-min) { @media (min-width: @screen-lg-min) {
right: percentage((@columns / @grid-columns)); right: percentage((@columns / @grid-columns));
} }
} }
// Framework grid generation // Framework grid generation
// //
// Used only by Bootstrap to generate the correct number of grid classes given // Used only by Bootstrap to generate the correct number of grid classes given
@ -775,15 +804,18 @@
.make-grid-columns() { .make-grid-columns() {
// Common styles for all sizes of grid columns, widths 1-12 // Common styles for all sizes of grid columns, widths 1-12
.col(@index) when (@index = 1) { // initial .col(@index) when (@index = 1) {
// initial
@item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}"; @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
.col((@index + 1), @item); .col((@index + 1), @item);
} }
.col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo .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}"; @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
.col((@index + 1), ~"@{list}, @{item}"); .col((@index + 1), ~"@{list}, @{item}");
} }
.col(@index, @list) when (@index > @grid-columns) { // terminal .col(@index, @list) when (@index > @grid-columns) {
// terminal
@{list} { @{list} {
position: relative; position: relative;
// Prevent columns from collapsing when empty // Prevent columns from collapsing when empty
@ -796,16 +828,19 @@
.col(1); // kickstart it .col(1); // kickstart it
} }
.make-grid-columns-float(@class) { .float-grid-columns(@class) {
.col(@index) when (@index = 1) { // initial .col(@index) when (@index = 1) {
// initial
@item: ~".col-@{class}-@{index}"; @item: ~".col-@{class}-@{index}";
.col((@index + 1), @item); .col((@index + 1), @item);
} }
.col(@index, @list) when (@index =< @grid-columns) { // general .col(@index, @list) when (@index =< @grid-columns) {
// general
@item: ~".col-@{class}-@{index}"; @item: ~".col-@{class}-@{index}";
.col((@index + 1), ~"@{list}, @{item}"); .col((@index + 1), ~"@{list}, @{item}");
} }
.col(@index, @list) when (@index > @grid-columns) { // terminal .col(@index, @list) when (@index > @grid-columns) {
// terminal
@{list} { @{list} {
float: left; float: left;
} }
@ -813,34 +848,45 @@
.col(1); // kickstart it .col(1); // kickstart it
} }
.calc-grid(@index, @class, @type) when (@type = width) and (@index > 0) { .calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {
.col-@{class}-@{index} { .col-@{class}-@{index} {
width: percentage((@index / @grid-columns)); width: percentage((@index / @grid-columns));
} }
} }
.calc-grid(@index, @class, @type) when (@type = push) {
.calc-grid-column(@index, @class, @type) when (@type = push) {
.col-@{class}-push-@{index} { .col-@{class}-push-@{index} {
left: percentage((@index / @grid-columns)); left: percentage((@index / @grid-columns));
} }
} }
.calc-grid(@index, @class, @type) when (@type = pull) {
.calc-grid-column(@index, @class, @type) when (@type = pull) {
.col-@{class}-pull-@{index} { .col-@{class}-pull-@{index} {
right: percentage((@index / @grid-columns)); right: percentage((@index / @grid-columns));
} }
} }
.calc-grid(@index, @class, @type) when (@type = offset) {
.calc-grid-column(@index, @class, @type) when (@type = offset) {
.col-@{class}-offset-@{index} { .col-@{class}-offset-@{index} {
margin-left: percentage((@index / @grid-columns)); margin-left: percentage((@index / @grid-columns));
} }
} }
// Basic looping in LESS // Basic looping in LESS
.make-grid(@index, @class, @type) when (@index >= 0) { .loop-grid-columns(@index, @class, @type) when (@index >= 0) {
.calc-grid(@index, @class, @type); .calc-grid-column(@index, @class, @type);
// next iteration // next iteration
.make-grid((@index - 1), @class, @type); .loop-grid-columns((@index - 1), @class, @type);
} }
// Create grid for specific class
.make-grid(@class) {
.float-grid-columns(@class);
.loop-grid-columns(@grid-columns, @class, width);
.loop-grid-columns(@grid-columns, @class, pull);
.loop-grid-columns(@grid-columns, @class, push);
.loop-grid-columns(@grid-columns, @class, offset);
}
// Form validation states // Form validation states
// //

View File

@ -34,7 +34,9 @@
.translate(0, -25%); .translate(0, -25%);
.transition-transform(~"0.3s ease-out"); .transition-transform(~"0.3s ease-out");
} }
&.in .modal-dialog { .translate(0, 0)} &.in .modal-dialog {
.translate(0, 0)
}
} }
// Shell div to position the modal with bottom padding // Shell div to position the modal with bottom padding
@ -67,8 +69,12 @@
z-index: @zindex-modal-background; z-index: @zindex-modal-background;
background-color: @modal-backdrop-bg; background-color: @modal-backdrop-bg;
// Fade for backdrop // Fade for backdrop
&.fade { .opacity(0); } &.fade {
&.in { .opacity(@modal-backdrop-opacity); } .opacity(0);
}
&.in {
.opacity(@modal-backdrop-opacity);
}
} }
// Modal header // Modal header
@ -78,6 +84,7 @@
border-bottom: 1px solid @modal-header-border-color; border-bottom: 1px solid @modal-header-border-color;
min-height: (@modal-title-padding + @modal-title-line-height); min-height: (@modal-title-padding + @modal-title-line-height);
} }
// Close icon // Close icon
.modal-header .close { .modal-header .close {
margin-top: -2px; margin-top: -2px;
@ -102,7 +109,8 @@
padding: (@modal-inner-padding - 1) @modal-inner-padding @modal-inner-padding; padding: (@modal-inner-padding - 1) @modal-inner-padding @modal-inner-padding;
text-align: right; // right align buttons text-align: right; // right align buttons
border-top: 1px solid @modal-footer-border-color; border-top: 1px solid @modal-footer-border-color;
&:extend(.clearfix all); // clear it in case folks use .pull-* classes on buttons &:extend(.clearfix all)
; // clear it in case folks use .pull-* classes on buttons
// Properly space out buttons // Properly space out buttons
.btn + .btn { .btn + .btn {
@ -121,18 +129,24 @@
// Scale up the modal // Scale up the modal
@media (min-width: @screen-sm-min) { @media (min-width: @screen-sm-min) {
// Automatically set modal's width for larger viewports // Automatically set modal's width for larger viewports
.modal-dialog { .modal-dialog {
width: @modal-md; width: @modal-md;
margin: 30px auto; margin: 30px auto;
} }
.modal-content { .modal-content {
.box-shadow(0 5px 15px rgba(0, 0, 0, .5)); .box-shadow(0 5px 15px rgba(0, 0, 0, .5));
} }
// Modal sizes // Modal sizes
.modal-sm { width: @modal-sm; } .modal-sm {
.modal-lg { width: @modal-lg; } width: @modal-sm;
}
}
@media (min-width: @screen-md-min) {
.modal-lg {
width: @modal-lg;
}
} }

View File

@ -2,7 +2,6 @@
// Navbars // Navbars
// -------------------------------------------------- // --------------------------------------------------
// Wrapper and base class // Wrapper and base class
// //
// Provide a static navbar from which we expand to create full-width, fixed, and // Provide a static navbar from which we expand to create full-width, fixed, and
@ -15,28 +14,28 @@
border: 1px solid transparent; border: 1px solid transparent;
// Prevent floats from breaking the navbar // Prevent floats from breaking the navbar
&:extend(.clearfix all); &:extend(.clearfix all)
;
@media (min-width: @grid-float-breakpoint) { @media (min-width: @grid-float-breakpoint) {
border-radius: @navbar-border-radius; border-radius: @navbar-border-radius;
} }
} }
// Navbar heading // Navbar heading
// //
// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy // Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy
// styling of responsive aspects. // styling of responsive aspects.
.navbar-header { .navbar-header {
&:extend(.clearfix all); &:extend(.clearfix all)
;
@media (min-width: @grid-float-breakpoint) { @media (min-width: @grid-float-breakpoint) {
float: left; float: left;
} }
} }
// Navbar collapse (body) // Navbar collapse (body)
// //
// Group your navbar content into this for easy collapsing and expanding across // Group your navbar content into this for easy collapsing and expanding across
@ -54,7 +53,8 @@
padding-left: @navbar-padding-horizontal; padding-left: @navbar-padding-horizontal;
border-top: 1px solid transparent; border-top: 1px solid transparent;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);
&:extend(.clearfix all); &:extend(.clearfix all)
;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
&.in { &.in {
@ -88,7 +88,6 @@
} }
} }
// Both navbar header and collapse // Both navbar header and collapse
// //
// When a container is present, change the behavior of the header and collapse. // When a container is present, change the behavior of the header and collapse.
@ -107,7 +106,6 @@
} }
} }
// //
// Navbar alignment options // Navbar alignment options
// //
@ -137,17 +135,18 @@
border-radius: 0; border-radius: 0;
} }
} }
.navbar-fixed-top { .navbar-fixed-top {
top: 0; top: 0;
border-width: 0 0 1px; border-width: 0 0 1px;
} }
.navbar-fixed-bottom { .navbar-fixed-bottom {
bottom: 0; bottom: 0;
margin-bottom: 0; // override .navbar defaults margin-bottom: 0; // override .navbar defaults
border-width: 1px 0 0; border-width: 1px 0 0;
} }
// Brand/project name // Brand/project name
.navbar-brand { .navbar-brand {
@ -155,7 +154,7 @@
padding: @navbar-padding-vertical @navbar-padding-horizontal; padding: @navbar-padding-vertical @navbar-padding-horizontal;
font-size: @font-size-large; font-size: @font-size-large;
line-height: @line-height-computed; line-height: @line-height-computed;
height: @line-height-computed; height: @navbar-height;
&:hover, &:hover,
&:focus { &:focus {
@ -170,7 +169,6 @@
} }
} }
// Navbar toggle // Navbar toggle
// //
// Custom button for toggling the `.navbar-collapse`, powered by the collapse // Custom button for toggling the `.navbar-collapse`, powered by the collapse
@ -209,7 +207,6 @@
} }
} }
// Navbar nav links // Navbar nav links
// //
// Builds on top of the `.nav` components with its own modifier class to make // Builds on top of the `.nav` components with its own modifier class to make
@ -267,7 +264,6 @@
} }
} }
// Component alignment // Component alignment
// //
// Repurpose the pull utilities as their own navbar utilities to avoid specificity // Repurpose the pull utilities as their own navbar utilities to avoid specificity
@ -275,10 +271,14 @@
// though so that navbar contents properly stack and align in mobile. // though so that navbar contents properly stack and align in mobile.
@media (min-width: @grid-float-breakpoint) { @media (min-width: @grid-float-breakpoint) {
.navbar-left { .pull-left(); } .navbar-left {
.navbar-right { .pull-right(); } .pull-left();
} }
.navbar-right {
.pull-right();
}
}
// Navbar form // Navbar form
// //
@ -323,7 +323,6 @@
} }
} }
// Dropdown menus // Dropdown menus
// Menu position and menu carets // Menu position and menu carets
@ -331,12 +330,12 @@
margin-top: 0; margin-top: 0;
.border-top-radius(0); .border-top-radius(0);
} }
// Menu position and menu caret support for dropups via extra dropup class // Menu position and menu caret support for dropups via extra dropup class
.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { .navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {
.border-bottom-radius(0); .border-bottom-radius(0);
} }
// Buttons in navbars // Buttons in navbars
// //
// Vertically center a button within a navbar (when *not* in a form). // Vertically center a button within a navbar (when *not* in a form).
@ -352,7 +351,6 @@
} }
} }
// Text in navbars // Text in navbars
// //
// Add a class to make any element properly align itself vertically within the navbars. // Add a class to make any element properly align itself vertically within the navbars.
@ -480,7 +478,6 @@
} }
} }
// Links in navbars // Links in navbars
// //
// Add a class to ensure links outside the navbar nav are colored correctly. // Add a class to ensure links outside the navbar nav are colored correctly.

View File

@ -2,7 +2,6 @@
// Panels // Panels
// -------------------------------------------------- // --------------------------------------------------
// Base class // Base class
.panel { .panel {
margin-bottom: @line-height-computed; margin-bottom: @line-height-computed;
@ -15,9 +14,40 @@
// Panel contents // Panel contents
.panel-body { .panel-body {
padding: @panel-body-padding; padding: @panel-body-padding;
&:extend(.clearfix all); &:extend(.clearfix all)
;
} }
// Optional heading
.panel-heading {
padding: 10px 15px;
border-bottom: 1px solid transparent;
.border-top-radius((@panel-border-radius - 1));
> .dropdown .dropdown-toggle {
color: inherit;
}
}
// Within heading, strip any `h*` tag of its default margins for spacing.
.panel-title {
margin-top: 0;
margin-bottom: 0;
font-size: ceil((@font-size-base * 1.125));
color: inherit;
> a {
color: inherit;
}
}
// Optional footer (stays gray in every modifier class)
.panel-footer {
padding: 10px 15px;
background-color: @panel-footer-bg;
border-top: 1px solid @panel-inner-border;
.border-bottom-radius((@panel-border-radius - 1));
}
// List groups in panels // List groups in panels
// //
@ -27,30 +57,29 @@
.panel { .panel {
> .list-group { > .list-group {
margin-bottom: 0; margin-bottom: 0;
.list-group-item { .list-group-item {
border-width: 1px 0; border-width: 1px 0;
border-radius: 0; border-radius: 0;
&:first-child {
border-top: 0;
}
&:last-child {
border-bottom: 0;
}
} }
// Add border top radius for first one // Add border top radius for first one
&:first-child { &:first-child {
.list-group-item:first-child { .list-group-item:first-child {
border-top: 0;
.border-top-radius((@panel-border-radius - 1)); .border-top-radius((@panel-border-radius - 1));
} }
} }
// Add border bottom radius for last one // Add border bottom radius for last one
&:last-child { &:last-child {
.list-group-item:last-child { .list-group-item:last-child {
border-bottom: 0;
.border-bottom-radius((@panel-border-radius - 1)); .border-bottom-radius((@panel-border-radius - 1));
} }
} }
} }
} }
// Collapse space between when there's no additional content. // Collapse space between when there's no additional content.
.panel-heading + .list-group { .panel-heading + .list-group {
.list-group-item:first-child { .list-group-item:first-child {
@ -58,7 +87,6 @@
} }
} }
// Tables in panels // Tables in panels
// //
// Place a non-bordered `.table` within a panel (not within a `.panel-body`) and // Place a non-bordered `.table` within a panel (not within a `.panel-body`) and
@ -72,6 +100,8 @@
// Add border top radius for first one // Add border top radius for first one
> .table:first-child, > .table:first-child,
> .table-responsive:first-child > .table:first-child { > .table-responsive:first-child > .table:first-child {
.border-top-radius((@panel-border-radius - 1));
> thead:first-child, > thead:first-child,
> tbody:first-child { > tbody:first-child {
> tr:first-child { > tr:first-child {
@ -89,6 +119,8 @@
// Add border bottom radius for last one // Add border bottom radius for last one
> .table:last-child, > .table:last-child,
> .table-responsive:last-child > .table:last-child { > .table-responsive:last-child > .table:last-child {
.border-bottom-radius((@panel-border-radius - 1));
> tbody:last-child, > tbody:last-child,
> tfoot:last-child { > tfoot:last-child {
> tr:last-child { > tr:last-child {
@ -126,12 +158,22 @@
> td:last-child { > td:last-child {
border-right: 0; border-right: 0;
} }
&:first-child > th,
&:first-child > td {
border-top: 0;
} }
&:last-child > th, }
&:last-child > td { > thead,
> tbody {
> tr:first-child {
> td,
> th {
border-bottom: 0;
}
}
}
> tbody,
> tfoot {
> tr:last-child {
> td,
> th {
border-bottom: 0; border-bottom: 0;
} }
} }
@ -143,39 +185,6 @@
} }
} }
// Optional heading
.panel-heading {
padding: 10px 15px;
border-bottom: 1px solid transparent;
.border-top-radius((@panel-border-radius - 1));
> .dropdown .dropdown-toggle {
color: inherit;
}
}
// Within heading, strip any `h*` tag of its default margins for spacing.
.panel-title {
margin-top: 0;
margin-bottom: 0;
font-size: ceil((@font-size-base * 1.125));
color: inherit;
> a {
color: inherit;
}
}
// Optional footer (stays gray in every modifier class)
.panel-footer {
padding: 10px 15px;
background-color: @panel-footer-bg;
border-top: 1px solid @panel-inner-border;
.border-bottom-radius((@panel-border-radius - 1));
}
// Collapsable panels (aka, accordion) // Collapsable panels (aka, accordion)
// //
// Wrap a series of panels in `.panel-group` to turn them into an accordion with // Wrap a series of panels in `.panel-group` to turn them into an accordion with
@ -208,23 +217,27 @@
} }
} }
// Contextual variations // Contextual variations
.panel-default { .panel-default {
.panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border); .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border);
} }
.panel-primary { .panel-primary {
.panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border); .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border);
} }
.panel-success { .panel-success {
.panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border); .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border);
} }
.panel-info { .panel-info {
.panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border); .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border);
} }
.panel-warning { .panel-warning {
.panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border); .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border);
} }
.panel-danger { .panel-danger {
.panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border); .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border);
} }

View File

@ -2,7 +2,6 @@
// Popovers // Popovers
// -------------------------------------------------- // --------------------------------------------------
.popover { .popover {
position: absolute; position: absolute;
top: 0; top: 0;
@ -23,10 +22,18 @@
white-space: normal; white-space: normal;
// Offset the popover to account for the popover arrow // Offset the popover to account for the popover arrow
&.top { margin-top: -10px; } &.top {
&.right { margin-left: 10px; } margin-top: -@popover-arrow-width;
&.bottom { margin-top: 10px; } }
&.left { margin-left: -10px; } &.right {
margin-left: @popover-arrow-width;
}
&.bottom {
margin-top: @popover-arrow-width;
}
&.left {
margin-left: -@popover-arrow-width;
}
} }
.popover-title { .popover-title {
@ -48,7 +55,7 @@
// //
// .arrow is outer, .arrow:after is inner // .arrow is outer, .arrow:after is inner
.popover .arrow { .popover > .arrow {
&, &,
&:after { &:after {
position: absolute; position: absolute;
@ -59,16 +66,18 @@
border-style: solid; border-style: solid;
} }
} }
.popover .arrow {
.popover > .arrow {
border-width: @popover-arrow-outer-width; border-width: @popover-arrow-outer-width;
} }
.popover .arrow:after {
.popover > .arrow:after {
border-width: @popover-arrow-width; border-width: @popover-arrow-width;
content: ""; content: "";
} }
.popover { .popover {
&.top .arrow { &.top > .arrow {
left: 50%; left: 50%;
margin-left: -@popover-arrow-outer-width; margin-left: -@popover-arrow-outer-width;
border-bottom-width: 0; border-bottom-width: 0;
@ -83,7 +92,7 @@
border-top-color: @popover-arrow-color; border-top-color: @popover-arrow-color;
} }
} }
&.right .arrow { &.right > .arrow {
top: 50%; top: 50%;
left: -@popover-arrow-outer-width; left: -@popover-arrow-outer-width;
margin-top: -@popover-arrow-outer-width; margin-top: -@popover-arrow-outer-width;
@ -98,7 +107,7 @@
border-right-color: @popover-arrow-color; border-right-color: @popover-arrow-color;
} }
} }
&.bottom .arrow { &.bottom > .arrow {
left: 50%; left: 50%;
margin-left: -@popover-arrow-outer-width; margin-left: -@popover-arrow-outer-width;
border-top-width: 0; border-top-width: 0;
@ -114,7 +123,7 @@
} }
} }
&.left .arrow { &.left > .arrow {
top: 50%; top: 50%;
right: -@popover-arrow-outer-width; right: -@popover-arrow-outer-width;
margin-top: -@popover-arrow-outer-width; margin-top: -@popover-arrow-outer-width;

View File

@ -2,7 +2,6 @@
// Responsive: Utility classes // Responsive: Utility classes
// -------------------------------------------------- // --------------------------------------------------
// IE10 in Windows (Phone) 8 // IE10 in Windows (Phone) 8
// //
// Support for responsive views via media queries is kind of borked in IE10, for // Support for responsive views via media queries is kind of borked in IE10, for
@ -21,32 +20,33 @@
width: device-width; width: device-width;
} }
// Visibility utilities // Visibility utilities
.visible-xs { .visible-xs,
.visible-sm,
.visible-md,
.visible-lg {
.responsive-invisibility(); .responsive-invisibility();
}
.visible-xs {
@media (max-width: @screen-xs-max) { @media (max-width: @screen-xs-max) {
.responsive-visibility(); .responsive-visibility();
} }
} }
.visible-sm {
.responsive-invisibility();
.visible-sm {
@media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
.responsive-visibility(); .responsive-visibility();
} }
} }
.visible-md {
.responsive-invisibility();
.visible-md {
@media (min-width: @screen-md-min) and (max-width: @screen-md-max) { @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
.responsive-visibility(); .responsive-visibility();
} }
} }
.visible-lg {
.responsive-invisibility();
.visible-lg {
@media (min-width: @screen-lg-min) { @media (min-width: @screen-lg-min) {
.responsive-visibility(); .responsive-visibility();
} }
@ -57,23 +57,25 @@
.responsive-invisibility(); .responsive-invisibility();
} }
} }
.hidden-sm { .hidden-sm {
@media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
.responsive-invisibility(); .responsive-invisibility();
} }
} }
.hidden-md { .hidden-md {
@media (min-width: @screen-md-min) and (max-width: @screen-md-max) { @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
.responsive-invisibility(); .responsive-invisibility();
} }
} }
.hidden-lg { .hidden-lg {
@media (min-width: @screen-lg-min) { @media (min-width: @screen-lg-min) {
.responsive-invisibility(); .responsive-invisibility();
} }
} }
// Print utilities // Print utilities
// //
// Media queries are placed on the inside to be mixin-friendly. // Media queries are placed on the inside to be mixin-friendly.

View File

@ -2,7 +2,6 @@
// Thumbnails // Thumbnails
// -------------------------------------------------- // --------------------------------------------------
// Mixin and adjust the regular image class // Mixin and adjust the regular image class
.thumbnail { .thumbnail {
display: block; display: block;
@ -16,7 +15,8 @@
> img, > img,
a > img { a > img {
.img-responsive(); &:extend(.img-responsive)
;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }

View File

@ -2,7 +2,6 @@
// Typography // Typography
// -------------------------------------------------- // --------------------------------------------------
// Headings // Headings
// ------------------------- // -------------------------
@ -32,6 +31,7 @@ h3, .h3 {
font-size: 65%; font-size: 65%;
} }
} }
h4, .h4, h4, .h4,
h5, .h5, h5, .h5,
h6, .h6 { h6, .h6 {
@ -44,13 +44,29 @@ h6, .h6 {
} }
} }
h1, .h1 { font-size: @font-size-h1; } h1, .h1 {
h2, .h2 { font-size: @font-size-h2; } font-size: @font-size-h1;
h3, .h3 { font-size: @font-size-h3; } }
h4, .h4 { font-size: @font-size-h4; }
h5, .h5 { font-size: @font-size-h5; }
h6, .h6 { font-size: @font-size-h6; }
h2, .h2 {
font-size: @font-size-h2;
}
h3, .h3 {
font-size: @font-size-h3;
}
h4, .h4 {
font-size: @font-size-h4;
}
h5, .h5 {
font-size: @font-size-h5;
}
h6, .h6 {
font-size: @font-size-h6;
}
// Body text // Body text
// ------------------------- // -------------------------
@ -70,39 +86,58 @@ p {
} }
} }
// Emphasis & misc // Emphasis & misc
// ------------------------- // -------------------------
// Ex: 14px base font * 85% = about 12px // Ex: 14px base font * 85% = about 12px
small, small,
.small { font-size: 85%; } .small {
font-size: 85%;
}
// Undo browser default styling // Undo browser default styling
cite { font-style: normal; } cite {
font-style: normal;
}
// Alignment // Alignment
.text-left { text-align: left; } .text-left {
.text-right { text-align: right; } text-align: left;
.text-center { text-align: center; } }
.text-justify { text-align: justify; }
.text-right {
text-align: right;
}
.text-center {
text-align: center;
}
.text-justify {
text-align: justify;
}
// Contextual colors // Contextual colors
.text-muted { .text-muted {
color: @text-muted; color: @text-muted;
} }
.text-primary { .text-primary {
.text-emphasis-variant(@brand-primary); .text-emphasis-variant(@brand-primary);
} }
.text-success { .text-success {
.text-emphasis-variant(@state-success-text); .text-emphasis-variant(@state-success-text);
} }
.text-info { .text-info {
.text-emphasis-variant(@state-info-text); .text-emphasis-variant(@state-info-text);
} }
.text-warning { .text-warning {
.text-emphasis-variant(@state-warning-text); .text-emphasis-variant(@state-warning-text);
} }
.text-danger { .text-danger {
.text-emphasis-variant(@state-danger-text); .text-emphasis-variant(@state-danger-text);
} }
@ -116,20 +151,23 @@ cite { font-style: normal; }
color: #fff; color: #fff;
.bg-variant(@brand-primary); .bg-variant(@brand-primary);
} }
.bg-success { .bg-success {
.bg-variant(@state-success-bg); .bg-variant(@state-success-bg);
} }
.bg-info { .bg-info {
.bg-variant(@state-info-bg); .bg-variant(@state-info-bg);
} }
.bg-warning { .bg-warning {
.bg-variant(@state-warning-bg); .bg-variant(@state-warning-bg);
} }
.bg-danger { .bg-danger {
.bg-variant(@state-danger-bg); .bg-variant(@state-danger-bg);
} }
// Page header // Page header
// ------------------------- // -------------------------
@ -139,7 +177,6 @@ cite { font-style: normal; }
border-bottom: 1px solid @page-header-border-color; border-bottom: 1px solid @page-header-border-color;
} }
// Lists // Lists
// -------------------------------------------------- // --------------------------------------------------
@ -165,15 +202,12 @@ ol {
// Inline turns list items into inline-block // Inline turns list items into inline-block
.list-inline { .list-inline {
.list-unstyled(); .list-unstyled();
margin-left: -5px;
> li { > li {
display: inline-block; display: inline-block;
padding-left: 5px; padding-left: 5px;
padding-right: 5px; padding-right: 5px;
&:first-child {
padding-left: 0;
}
} }
} }
@ -182,13 +216,16 @@ dl {
margin-top: 0; // Remove browser default margin-top: 0; // Remove browser default
margin-bottom: @line-height-computed; margin-bottom: @line-height-computed;
} }
dt, dt,
dd { dd {
line-height: @line-height-base; line-height: @line-height-base;
} }
dt { dt {
font-weight: bold; font-weight: bold;
} }
dd { dd {
margin-left: 0; // Undo browser default margin-left: 0; // Undo browser default
} }
@ -209,7 +246,8 @@ dd {
} }
dd { dd {
margin-left: @component-offset-horizontal; margin-left: @component-offset-horizontal;
&:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present &:extend(.clearfix all)
; // Clear the floated `dt` if an empty `dd` is present
} }
} }
} }
@ -224,6 +262,7 @@ abbr[data-original-title] {
cursor: help; cursor: help;
border-bottom: 1px dotted @abbr-border-color; border-bottom: 1px dotted @abbr-border-color;
} }
.initialism { .initialism {
font-size: 90%; font-size: 90%;
text-transform: uppercase; text-transform: uppercase;
@ -233,7 +272,7 @@ abbr[data-original-title] {
blockquote { blockquote {
padding: (@line-height-computed / 2) @line-height-computed; padding: (@line-height-computed / 2) @line-height-computed;
margin: 0 0 @line-height-computed; margin: 0 0 @line-height-computed;
font-size: (@font-size-base * 1.25); font-size: @blockquote-font-size;
border-left: 5px solid @blockquote-border-color; border-left: 5px solid @blockquote-border-color;
p, p,
@ -275,7 +314,9 @@ blockquote.pull-right {
footer, footer,
small, small,
.small { .small {
&:before { content: ''; } &:before {
content: '';
}
&:after { &:after {
content: '\00A0 \2014'; // nbsp, em dash content: '\00A0 \2014'; // nbsp, em dash
} }

View File

@ -2,16 +2,20 @@
// Variables // Variables
// -------------------------------------------------- // --------------------------------------------------
//== Colors //== Colors
// //
//## Gray and brand colors for use across Bootstrap. //## Gray and brand colors for use across Bootstrap.
@gray-darker: lighten(#000, 13.5%); // #222 @gray-darker: lighten(#000, 13.5%);
@gray-dark: lighten(#000, 20%); // #333 // #222
@gray: lighten(#000, 33.5%); // #555 @gray-dark: lighten(#000, 20%);
@gray-light: lighten(#000, 60%); // #999 // #333
@gray-lighter: lighten(#000, 93.5%); // #eee @gray: lighten(#000, 33.5%);
// #555
@gray-light: lighten(#000, 60%);
// #999
@gray-lighter: lighten(#000, 93.5%);
// #eee
@brand-primary: #428bca; @brand-primary: #428bca;
@brand-success: #5cb85c; @brand-success: #5cb85c;
@ -19,7 +23,6 @@
@brand-warning: #f0ad4e; @brand-warning: #f0ad4e;
@brand-danger: #d9534f; @brand-danger: #d9534f;
//== Scaffolding //== Scaffolding
// //
// ## Settings for some of the most global styles. // ## Settings for some of the most global styles.
@ -34,7 +37,6 @@
//** Link hover color set via `darken()` function. //** Link hover color set via `darken()` function.
@link-hover-color: darken(@link-color, 15%); @link-hover-color: darken(@link-color, 15%);
//== Typography //== Typography
// //
//## Font, line-height, and color for body text, headings, and more. //## Font, line-height, and color for body text, headings, and more.
@ -46,20 +48,29 @@
@font-family-base: @font-family-sans-serif; @font-family-base: @font-family-sans-serif;
@font-size-base: 14px; @font-size-base: 14px;
@font-size-large: ceil((@font-size-base * 1.25)); // ~18px @font-size-large: ceil((@font-size-base * 1.25));
@font-size-small: ceil((@font-size-base * 0.85)); // ~12px // ~18px
@font-size-small: ceil((@font-size-base * 0.85));
// ~12px
@font-size-h1: floor((@font-size-base * 2.6)); // ~36px @font-size-h1: floor((@font-size-base * 2.6));
@font-size-h2: floor((@font-size-base * 2.15)); // ~30px // ~36px
@font-size-h3: ceil((@font-size-base * 1.7)); // ~24px @font-size-h2: floor((@font-size-base * 2.15));
@font-size-h4: ceil((@font-size-base * 1.25)); // ~18px // ~30px
@font-size-h3: ceil((@font-size-base * 1.7));
// ~24px
@font-size-h4: ceil((@font-size-base * 1.25));
// ~18px
@font-size-h5: @font-size-base; @font-size-h5: @font-size-base;
@font-size-h6: ceil((@font-size-base * 0.85)); // ~12px @font-size-h6: ceil((@font-size-base * 0.85));
// ~12px
//** Unit-less `line-height` for use in components like buttons. //** Unit-less `line-height` for use in components like buttons.
@line-height-base: 1.428571429; // 20/14 @line-height-base: 1.428571429;
// 20/14
//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc. //** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
@line-height-computed: floor((@font-size-base * @line-height-base)); // ~20px @line-height-computed: floor((@font-size-base * @line-height-base));
// ~20px
//** By default, this inherits from the `<body>`. //** By default, this inherits from the `<body>`.
@headings-font-family: inherit; @headings-font-family: inherit;
@ -67,7 +78,6 @@
@headings-line-height: 1.1; @headings-line-height: 1.1;
@headings-color: inherit; @headings-color: inherit;
//-- Iconography //-- Iconography
// //
//## Specify custom locations of the include Glyphicons icon font. Useful for those including Bootstrap via Bower. //## Specify custom locations of the include Glyphicons icon font. Useful for those including Bootstrap via Bower.
@ -109,7 +119,6 @@
//** Carets increase slightly in size for larger components. //** Carets increase slightly in size for larger components.
@caret-width-large: 5px; @caret-width-large: 5px;
//== Tables //== Tables
// //
//## Customizes the `.table` component with basic values, each used across all table variations. //## Customizes the `.table` component with basic values, each used across all table variations.
@ -130,7 +139,6 @@
//** Border color for table and cell borders. //** Border color for table and cell borders.
@table-border-color: #ddd; @table-border-color: #ddd;
//== Buttons //== Buttons
// //
//## For each of Bootstrap's buttons, define text, background and border color. //## For each of Bootstrap's buttons, define text, background and border color.
@ -163,7 +171,6 @@
@btn-link-disabled-color: @gray-light; @btn-link-disabled-color: @gray-light;
//== Forms //== Forms
// //
//## //##
@ -200,7 +207,6 @@
//** Border color for textual input addons //** Border color for textual input addons
@input-group-addon-border-color: @input-border; @input-group-addon-border-color: @input-border;
//== Dropdowns //== Dropdowns
// //
//## Dropdown menu container and contents. //## Dropdown menu container and contents.
@ -235,7 +241,6 @@
// Note: Deprecated @dropdown-caret-color as of v3.1.0 // Note: Deprecated @dropdown-caret-color as of v3.1.0
@dropdown-caret-color: #000; @dropdown-caret-color: #000;
//-- Z-index master list //-- Z-index master list
// //
// Warning: Avoid customizing these values. They're used for a bird's eye view // Warning: Avoid customizing these values. They're used for a bird's eye view
@ -251,7 +256,6 @@
@zindex-modal-background: 1040; @zindex-modal-background: 1040;
@zindex-modal: 1050; @zindex-modal: 1050;
//== Media queries breakpoints //== Media queries breakpoints
// //
//## Define the breakpoints at which your layout will change, adapting to different screen sizes. //## Define the breakpoints at which your layout will change, adapting to different screen sizes.
@ -285,7 +289,6 @@
@screen-sm-max: (@screen-md-min - 1); @screen-sm-max: (@screen-md-min - 1);
@screen-md-max: (@screen-lg-min - 1); @screen-md-max: (@screen-lg-min - 1);
//== Grid system //== Grid system
// //
//## Define your custom responsive grid. //## Define your custom responsive grid.
@ -300,6 +303,24 @@
//** Point at which the navbar begins collapsing. //** Point at which the navbar begins collapsing.
@grid-float-breakpoint-max: (@grid-float-breakpoint - 1); @grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
//== Container sizes
//
//## Define the maximum width of `.container` for different screen sizes.
// Small screen / tablet
@container-tablet: ((720px + @grid-gutter-width));
//** For `@screen-sm-min` and up.
@container-sm: @container-tablet;
// Medium screen / desktop
@container-desktop: ((940px + @grid-gutter-width));
//** For `@screen-md-min` and up.
@container-md: @container-desktop;
// Large screen / wide desktop
@container-large-desktop: ((1140px + @grid-gutter-width));
//** For `@screen-lg-min` and up.
@container-lg: @container-large-desktop;
//== Navbar //== Navbar
// //
@ -336,7 +357,6 @@
@navbar-default-toggle-icon-bar-bg: #888; @navbar-default-toggle-icon-bar-bg: #888;
@navbar-default-toggle-border-color: #ddd; @navbar-default-toggle-border-color: #ddd;
// Inverted navbar // Inverted navbar
// Reset inverted navbar basics // Reset inverted navbar basics
@navbar-inverse-color: @gray-light; @navbar-inverse-color: @gray-light;
@ -362,7 +382,6 @@
@navbar-inverse-toggle-icon-bar-bg: #fff; @navbar-inverse-toggle-icon-bar-bg: #fff;
@navbar-inverse-toggle-border-color: #333; @navbar-inverse-toggle-border-color: #333;
//== Navs //== Navs
// //
//## //##
@ -393,7 +412,6 @@
@nav-pills-active-link-hover-bg: @component-active-bg; @nav-pills-active-link-hover-bg: @component-active-bg;
@nav-pills-active-link-hover-color: @component-active-color; @nav-pills-active-link-hover-color: @component-active-color;
//== Pagination //== Pagination
// //
//## //##
@ -414,7 +432,6 @@
@pagination-disabled-bg: #fff; @pagination-disabled-bg: #fff;
@pagination-disabled-border: #ddd; @pagination-disabled-border: #ddd;
//== Pager //== Pager
// //
//## //##
@ -430,7 +447,6 @@
@pager-disabled-color: @pagination-disabled-color; @pager-disabled-color: @pagination-disabled-color;
//== Jumbotron //== Jumbotron
// //
//## //##
@ -441,7 +457,6 @@
@jumbotron-heading-color: inherit; @jumbotron-heading-color: inherit;
@jumbotron-font-size: ceil((@font-size-base * 1.5)); @jumbotron-font-size: ceil((@font-size-base * 1.5));
//== Form states and alerts //== Form states and alerts
// //
//## Define colors for form feedback states and, by default, alerts. //## Define colors for form feedback states and, by default, alerts.
@ -462,7 +477,6 @@
@state-danger-bg: #f2dede; @state-danger-bg: #f2dede;
@state-danger-border: darken(spin(@state-danger-bg, -10), 5%); @state-danger-border: darken(spin(@state-danger-bg, -10), 5%);
//== Tooltips //== Tooltips
// //
//## //##
@ -480,7 +494,6 @@
//** Tooltip arrow color //** Tooltip arrow color
@tooltip-arrow-color: @tooltip-bg; @tooltip-arrow-color: @tooltip-bg;
//== Popovers //== Popovers
// //
//## //##
@ -505,10 +518,9 @@
//** Popover outer arrow width //** Popover outer arrow width
@popover-arrow-outer-width: (@popover-arrow-width + 1); @popover-arrow-outer-width: (@popover-arrow-width + 1);
//** Popover outer arrow color //** Popover outer arrow color
@popover-arrow-outer-color: rgba(0,0,0,.25); @popover-arrow-outer-color: fadein(@popover-border-color, 5%);
//** Popover outer arrow fallback color //** Popover outer arrow fallback color
@popover-arrow-outer-fallback-color: #999; @popover-arrow-outer-fallback-color: darken(@popover-fallback-border-color, 20%);
//== Labels //== Labels
// //
@ -532,7 +544,6 @@
//** Default text color of a linked label //** Default text color of a linked label
@label-link-hover-color: #fff; @label-link-hover-color: #fff;
//== Modals //== Modals
// //
//## //##
@ -565,7 +576,6 @@
@modal-md: 600px; @modal-md: 600px;
@modal-sm: 300px; @modal-sm: 300px;
//== Alerts //== Alerts
// //
//## Define alert colors, border radius, and padding. //## Define alert colors, border radius, and padding.
@ -590,7 +600,6 @@
@alert-danger-text: @state-danger-text; @alert-danger-text: @state-danger-text;
@alert-danger-border: @state-danger-border; @alert-danger-border: @state-danger-border;
//== Progress bars //== Progress bars
// //
//## //##
@ -611,7 +620,6 @@
//** Info progress bar color //** Info progress bar color
@progress-bar-info-bg: @brand-info; @progress-bar-info-bg: @brand-info;
//== List group //== List group
// //
//## //##
@ -636,7 +644,6 @@
@list-group-link-color: #555; @list-group-link-color: #555;
@list-group-link-heading-color: #333; @list-group-link-heading-color: #333;
//== Panels //== Panels
// //
//## //##
@ -673,7 +680,6 @@
@panel-danger-border: @state-danger-border; @panel-danger-border: @state-danger-border;
@panel-danger-heading-bg: @state-danger-bg; @panel-danger-heading-bg: @state-danger-bg;
//== Thumbnails //== Thumbnails
// //
//## //##
@ -692,7 +698,6 @@
//** Padding around the thumbnail caption //** Padding around the thumbnail caption
@thumbnail-caption-padding: 9px; @thumbnail-caption-padding: 9px;
//== Wells //== Wells
// //
//## //##
@ -700,7 +705,6 @@
@well-bg: #f5f5f5; @well-bg: #f5f5f5;
@well-border: darken(@well-bg, 7%); @well-border: darken(@well-bg, 7%);
//== Badges //== Badges
// //
//## //##
@ -719,7 +723,6 @@
@badge-line-height: 1; @badge-line-height: 1;
@badge-border-radius: 10px; @badge-border-radius: 10px;
//== Breadcrumbs //== Breadcrumbs
// //
//## //##
@ -735,7 +738,6 @@
//** Textual separator for between breadcrumb elements //** Textual separator for between breadcrumb elements
@breadcrumb-separator: "/"; @breadcrumb-separator: "/";
//== Carousel //== Carousel
// //
//## //##
@ -752,7 +754,6 @@
@carousel-caption-color: #fff; @carousel-caption-color: #fff;
//== Close //== Close
// //
//## //##
@ -761,7 +762,6 @@
@close-color: #000; @close-color: #000;
@close-text-shadow: 0 1px 0 #fff; @close-text-shadow: 0 1px 0 #fff;
//== Code //== Code
// //
//## //##
@ -777,7 +777,6 @@
@pre-border-color: #ccc; @pre-border-color: #ccc;
@pre-scrollable-max-height: 340px; @pre-scrollable-max-height: 340px;
//== Type //== Type
// //
//## //##
@ -790,12 +789,13 @@
@headings-small-color: @gray-light; @headings-small-color: @gray-light;
//** Blockquote small color //** Blockquote small color
@blockquote-small-color: @gray-light; @blockquote-small-color: @gray-light;
//** Blockquote font size
@blockquote-font-size: (@font-size-base * 1.25);
//** Blockquote border color //** Blockquote border color
@blockquote-border-color: @gray-lighter; @blockquote-border-color: @gray-lighter;
//** Page header border color //** Page header border color
@page-header-border-color: @gray-lighter; @page-header-border-color: @gray-lighter;
//== Miscellaneous //== Miscellaneous
// //
//## //##
@ -805,23 +805,3 @@
//** Horizontal offset for forms and lists. //** Horizontal offset for forms and lists.
@component-offset-horizontal: 180px; @component-offset-horizontal: 180px;
//== Container sizes
//
//## Define the maximum width of `.container` for different screen sizes.
// Small screen / tablet
@container-tablet: ((720px + @grid-gutter-width));
//** For `@screen-sm-min` and up.
@container-sm: @container-tablet;
// Medium screen / desktop
@container-desktop: ((940px + @grid-gutter-width));
//** For `@screen-md-min` and up.
@container-md: @container-desktop;
// Large screen / wide desktop
@container-large-desktop: ((1140px + @grid-gutter-width));
//** For `@screen-lg-min` and up.
@container-lg: @container-large-desktop;

View File

@ -32,6 +32,10 @@
background-image: linear-gradient(to bottom, #ffffff 60%, #f8f8f8 100%); background-image: linear-gradient(to bottom, #ffffff 60%, #f8f8f8 100%);
} }
.btn-link {
box-shadow: none;
}
// Forms // Forms
// ------------------------- // -------------------------

View File

@ -1,8 +1,5 @@
// Flatly 3.0.3 // Flatly 3.1.1
// -------------------------------------------------- // Variables
// Global values
// -------------------------------------------------- // --------------------------------------------------
#footer { #footer {
@ -11,74 +8,91 @@
border-top: 1px solid @navbar-default-border; border-top: 1px solid @navbar-default-border;
} }
// Grays //== Colors
// ------------------------- //
//## Gray and brand colors for use across Bootstrap.
@gray-darker: lighten(#000, 13.5%); // #222 @gray-darker: lighten(#000, 13.5%); // #222
@gray-dark: #7b8a8b; @gray-dark: #7b8a8b;
// #333
@gray: #95a5a6; @gray: #95a5a6;
// #555
@gray-light: #b4bcc2; @gray-light: #b4bcc2;
// #999
@gray-lighter: #ecf0f1; @gray-lighter: #ecf0f1;
// #eee
// Brand colors
// -------------------------
@brand-primary: #2C3E50; @brand-primary: #2C3E50;
@brand-success: #18BC9C; @brand-success: #18BC9C;
@brand-info: #3498DB;
@brand-warning: #F39C12; @brand-warning: #F39C12;
@brand-danger: #E74C3C; @brand-danger: #E74C3C;
@brand-info: #3498DB;
// Scaffolding //== Scaffolding
// ------------------------- //
// ## Settings for some of the most global styles.
//** Background color for `<body>`.
@body-bg: #fff; @body-bg: #fff;
//** Global text color on `<body>`.
@text-color: @brand-primary; @text-color: @brand-primary;
// Links //** Global textual link color.
// -------------------------
@link-color: @brand-success; @link-color: @brand-success;
//** Link hover color set via `darken()` function.
@link-hover-color: @link-color; @link-hover-color: @link-color;
// Typography //== Typography
// ------------------------- //
//## Font, line-height, and color for body text, headings, and more.
@font-family-sans-serif: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif; @font-family-sans-serif: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif;
@font-family-serif: Georgia, "Times New Roman", Times, serif; @font-family-serif: Georgia, "Times New Roman", Times, serif;
//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`.
@font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; @font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
@font-family-base: @font-family-sans-serif; @font-family-base: @font-family-sans-serif;
@font-size-base: 15px; @font-size-base: 15px;
@font-size-large: ceil(@font-size-base * 1.25); // ~18px @font-size-large: ceil((@font-size-base * 1.25));
@font-size-small: ceil(@font-size-base * 0.85); // ~12px // ~18px
@font-size-small: ceil((@font-size-base * 0.85));
// ~12px
@font-size-h1: floor(@font-size-base * 2.6); // ~36px @font-size-h1: floor((@font-size-base * 2.6));
@font-size-h2: floor(@font-size-base * 2.15); // ~30px // ~36px
@font-size-h3: ceil(@font-size-base * 1.7); // ~24px @font-size-h2: floor((@font-size-base * 2.15));
@font-size-h4: ceil(@font-size-base * 1.25); // ~18px // ~30px
@font-size-h3: ceil((@font-size-base * 1.7));
// ~24px
@font-size-h4: ceil((@font-size-base * 1.25));
// ~18px
@font-size-h5: @font-size-base; @font-size-h5: @font-size-base;
@font-size-h6: ceil(@font-size-base * 0.85); // ~12px @font-size-h6: ceil((@font-size-base * 0.85));
// ~12px
//** Unit-less `line-height` for use in components like buttons.
@line-height-base: 1.428571429; // 20/14 @line-height-base: 1.428571429; // 20/14
@line-height-computed: floor(@font-size-base * @line-height-base); // ~20px //** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
@line-height-computed: floor((@font-size-base * @line-height-base));
// ~20px
//** By default, this inherits from the `<body>`.
@headings-font-family: @font-family-base; @headings-font-family: @font-family-base;
@headings-font-weight: 500; @headings-font-weight: 400;
@headings-line-height: 1.1; @headings-line-height: 1.1;
@headings-color: inherit; @headings-color: inherit;
//-- Iconography
// Iconography //
// ------------------------- //## Specify custom locations of the include Glyphicons icon font. Useful for those including Bootstrap via Bower.
@icon-font-path: "../fonts/"; @icon-font-path: "../fonts/";
@icon-font-name: "glyphicons-halflings-regular"; @icon-font-name: "glyphicons-halflings-regular";
@icon-font-svg-id: "glyphicons_halflingsregular";
//== Components
// Components //
// ------------------------- //## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
// Based on 14px font-size and 1.428 line-height (~20px to start)
@padding-base-vertical: 10px; @padding-base-vertical: 10px;
@padding-base-horizontal: 15px; @padding-base-horizontal: 15px;
@ -99,28 +113,39 @@
@border-radius-large: 6px; @border-radius-large: 6px;
@border-radius-small: 3px; @border-radius-small: 3px;
//** Global color for active items (e.g., navs or dropdowns).
@component-active-color: #fff; @component-active-color: #fff;
//** Global background color for active items (e.g., navs or dropdowns).
@component-active-bg: @brand-primary; @component-active-bg: @brand-primary;
//** Width of the `border` for generating carets that indicator dropdowns.
@caret-width-base: 4px; @caret-width-base: 4px;
//** Carets increase slightly in size for larger components.
@caret-width-large: 5px; @caret-width-large: 5px;
// Tables //== Tables
// ------------------------- //
//## Customizes the `.table` component with basic values, each used across all table variations.
//** Padding for `<th>`s and `<td>`s.
@table-cell-padding: 8px; @table-cell-padding: 8px;
//** Padding for cells in `.table-condensed`.
@table-condensed-cell-padding: 5px; @table-condensed-cell-padding: 5px;
@table-bg: transparent; // overall background-color //** Default background color used for all tables.
@table-bg-accent: #f9f9f9; // for striping @table-bg: transparent;
//** Background color used for `.table-striped`.
@table-bg-accent: #f9f9f9;
//** Background color used for `.table-hover`.
@table-bg-hover: @gray-lighter; @table-bg-hover: @gray-lighter;
@table-bg-active: @table-bg-hover; @table-bg-active: @table-bg-hover;
@table-border-color: @gray-lighter; // table and cell border //** Border color for table and cell borders.
@table-border-color: @gray-lighter;
//== Buttons
// Buttons //
// ------------------------- //## For each of Bootstrap's buttons, define text, background and border color.
@btn-font-weight: normal; @btn-font-weight: normal;
@ -136,6 +161,10 @@
@btn-success-bg: @brand-success; @btn-success-bg: @brand-success;
@btn-success-border: @btn-success-bg; @btn-success-border: @btn-success-bg;
@btn-info-color: @btn-default-color;
@btn-info-bg: @brand-info;
@btn-info-border: @btn-info-bg;
@btn-warning-color: @btn-default-color; @btn-warning-color: @btn-default-color;
@btn-warning-bg: @brand-warning; @btn-warning-bg: @brand-warning;
@btn-warning-border: @btn-warning-bg; @btn-warning-border: @btn-warning-bg;
@ -144,65 +173,84 @@
@btn-danger-bg: @brand-danger; @btn-danger-bg: @brand-danger;
@btn-danger-border: @btn-danger-bg; @btn-danger-border: @btn-danger-bg;
@btn-info-color: @btn-default-color;
@btn-info-bg: @brand-info;
@btn-info-border: @btn-info-bg;
@btn-link-disabled-color: @gray-light; @btn-link-disabled-color: @gray-light;
//== Forms
//
//##
// Forms //** `<input>` background color
// -------------------------
@input-bg: #fff; @input-bg: #fff;
//** `<input disabled>` background color
@input-bg-disabled: @gray-lighter; @input-bg-disabled: @gray-lighter;
//** Text color for `<input>`s
@input-color: @text-color; @input-color: @text-color;
//** `<input>` border color
@input-border: #dce4ec; @input-border: #dce4ec;
//** `<input>` border radius
@input-border-radius: @border-radius-base; @input-border-radius: @border-radius-base;
//** Border color for inputs on focus
@input-border-focus: #1abc9c; @input-border-focus: #1abc9c;
//** Placeholder text color
@input-color-placeholder: #acb6c0; @input-color-placeholder: #acb6c0;
//** Default `.form-control` height
@input-height-base: (@line-height-computed + (@padding-base-vertical * 2) + 2); @input-height-base: (@line-height-computed + (@padding-base-vertical * 2) + 2);
//** Large `.form-control` height
@input-height-large: (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2); @input-height-large: (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
//** Small `.form-control` height
@input-height-small: (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2); @input-height-small: (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
@legend-color: @text-color; @legend-color: @text-color;
@legend-border-color: #e5e5e5; @legend-border-color: #e5e5e5;
//** Background color for textual input addons
@input-group-addon-bg: @gray-lighter; @input-group-addon-bg: @gray-lighter;
//** Border color for textual input addons
@input-group-addon-border-color: @input-border; @input-group-addon-border-color: @input-border;
//== Dropdowns
//
//## Dropdown menu container and contents.
// Dropdowns //** Background for the dropdown menu.
// -------------------------
@dropdown-bg: #fff; @dropdown-bg: #fff;
//** Dropdown menu `border-color`.
@dropdown-border: rgba(0,0,0,.15); @dropdown-border: rgba(0,0,0,.15);
//** Dropdown menu `border-color` **for IE8**.
@dropdown-fallback-border: #ccc; @dropdown-fallback-border: #ccc;
//** Divider color for between dropdown items.
@dropdown-divider-bg: #e5e5e5; @dropdown-divider-bg: #e5e5e5;
//** Dropdown link text color.
@dropdown-link-color: @gray-dark; @dropdown-link-color: @gray-dark;
//** Hover color for dropdown links.
@dropdown-link-hover-color: #fff; @dropdown-link-hover-color: #fff;
//** Hover background for dropdown links.
@dropdown-link-hover-bg: @dropdown-link-active-bg; @dropdown-link-hover-bg: @dropdown-link-active-bg;
//** Active dropdown menu item text color.
@dropdown-link-active-color: #fff; @dropdown-link-active-color: #fff;
//** Active dropdown menu item background color.
@dropdown-link-active-bg: @component-active-bg; @dropdown-link-active-bg: @component-active-bg;
//** Disabled dropdown menu item background color.
@dropdown-link-disabled-color: @text-muted; @dropdown-link-disabled-color: @text-muted;
//** Text color for headers within dropdown menus.
@dropdown-header-color: @text-muted; @dropdown-header-color: @text-muted;
// Note: Deprecated @dropdown-caret-color as of v3.1.0
@dropdown-caret-color: #000;
// COMPONENT VARIABLES //-- Z-index master list
// -------------------------------------------------- //
// Warning: Avoid customizing these values. They're used for a bird's eye view
// of components dependent on the z-axis and are designed to all work together.
// Z-index master list //
// ------------------------- // Note: These variables are not generated into the Customizer.
// Used for a bird's eye view of components dependent on the z-axis
// Try to avoid customizing these :)
@zindex-navbar: 1000; @zindex-navbar: 1000;
@zindex-dropdown: 1000; @zindex-dropdown: 1000;
@ -212,8 +260,9 @@
@zindex-modal-background: 1040; @zindex-modal-background: 1040;
@zindex-modal: 1050; @zindex-modal: 1050;
// Media queries breakpoints //== Media queries breakpoints
// -------------------------------------------------- //
//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
// Extra small screen / phone // Extra small screen / phone
// Note: Deprecated @screen-xs and @screen-phone as of v3.0.1 // Note: Deprecated @screen-xs and @screen-phone as of v3.0.1
@ -244,32 +293,50 @@
@screen-sm-max: (@screen-md-min - 1); @screen-sm-max: (@screen-md-min - 1);
@screen-md-max: (@screen-lg-min - 1); @screen-md-max: (@screen-lg-min - 1);
//== Grid system
//
//## Define your custom responsive grid.
// Grid system //** Number of columns in the grid.
// --------------------------------------------------
// Number of columns in the grid system
@grid-columns: 12; @grid-columns: 12;
// Padding, to be divided by two and applied to the left and right of all columns //** Padding between columns. Gets divided in half for the left and right.
@grid-gutter-width: 30px; @grid-gutter-width: 30px;
// Navbar collapse // Navbar collapse
//** Point at which the navbar becomes uncollapsed.
// Point at which the navbar becomes uncollapsed
@grid-float-breakpoint: @screen-sm-min; @grid-float-breakpoint: @screen-sm-min;
// Point at which the navbar begins collapsing //** Point at which the navbar begins collapsing.
@grid-float-breakpoint-max: (@grid-float-breakpoint - 1); @grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
//== Container sizes
//
//## Define the maximum width of `.container` for different screen sizes.
// Navbar // Small screen / tablet
// ------------------------- @container-tablet: ((720px + @grid-gutter-width));
//** For `@screen-sm-min` and up.
@container-sm: @container-tablet;
// Medium screen / desktop
@container-desktop: ((940px + @grid-gutter-width));
//** For `@screen-md-min` and up.
@container-md: @container-desktop;
// Large screen / wide desktop
@container-large-desktop: ((1140px + @grid-gutter-width));
//** For `@screen-lg-min` and up.
@container-lg: @container-large-desktop;
//== Navbar
//
//##
// Basics of a navbar // Basics of a navbar
@navbar-height: 60px; @navbar-height: 60px;
@navbar-margin-bottom: @line-height-computed; @navbar-margin-bottom: @line-height-computed;
@navbar-border-radius: @border-radius-base; @navbar-border-radius: @border-radius-base;
@navbar-padding-horizontal: floor(@grid-gutter-width / 2); // ~15px @navbar-padding-horizontal: floor((@grid-gutter-width / 2));
@navbar-padding-vertical: ((@navbar-height - @line-height-computed) / 2); @navbar-padding-vertical: ((@navbar-height - @line-height-computed) / 2);
@navbar-collapse-max-height: 340px;
@navbar-default-color: #777; @navbar-default-color: #777;
@navbar-default-bg: @brand-primary; @navbar-default-bg: @brand-primary;
@ -296,7 +363,6 @@
// Inverted navbar // Inverted navbar
//
// Reset inverted navbar basics // Reset inverted navbar basics
@navbar-inverse-color: #fff; @navbar-inverse-color: #fff;
@navbar-inverse-bg: @brand-success; @navbar-inverse-bg: @brand-success;
@ -321,10 +387,11 @@
@navbar-inverse-toggle-icon-bar-bg: #fff; @navbar-inverse-toggle-icon-bar-bg: #fff;
@navbar-inverse-toggle-border-color: darken(@navbar-inverse-bg, 10%); @navbar-inverse-toggle-border-color: darken(@navbar-inverse-bg, 10%);
//== Navs
//
//##
// Navs //=== Shared nav styles
// -------------------------
@nav-link-padding: 10px 15px; @nav-link-padding: 10px 15px;
@nav-link-hover-bg: @gray-lighter; @nav-link-hover-bg: @gray-lighter;
@ -333,7 +400,7 @@
@nav-open-link-hover-color: #fff; @nav-open-link-hover-color: #fff;
// Tabs //== Tabs
@nav-tabs-border-color: @gray-lighter; @nav-tabs-border-color: @gray-lighter;
@nav-tabs-link-hover-border-color: @gray-lighter; @nav-tabs-link-hover-border-color: @gray-lighter;
@ -345,45 +412,60 @@
@nav-tabs-justified-link-border-color: @gray-lighter; @nav-tabs-justified-link-border-color: @gray-lighter;
@nav-tabs-justified-active-link-border-color: @body-bg; @nav-tabs-justified-active-link-border-color: @body-bg;
// Pills //== Pills
@nav-pills-border-radius: @border-radius-base; @nav-pills-border-radius: @border-radius-base;
@nav-pills-active-link-hover-bg: @component-active-bg; @nav-pills-active-link-hover-bg: @component-active-bg;
@nav-pills-active-link-hover-color: @component-active-color; @nav-pills-active-link-hover-color: @component-active-color;
//== Pagination
//
//##
// Pagination @pagination-color: #fff;
// -------------------------
@pagination-bg: @brand-success; @pagination-bg: @brand-success;
@pagination-border: transparent; @pagination-border: transparent;
@pagination-hover-color: #fff;
@pagination-hover-bg: darken(@brand-success, 15%); @pagination-hover-bg: darken(@brand-success, 15%);
@pagination-hover-border: transparent;
@pagination-active-bg: darken(@brand-success, 15%);
@pagination-active-color: #fff; @pagination-active-color: #fff;
@pagination-active-bg: darken(@brand-success, 15%);
@pagination-active-border: transparent;
@pagination-disabled-color: @gray-lighter; @pagination-disabled-color: @gray-lighter;
@pagination-disabled-bg: lighten(@brand-success, 15%);
;
@pagination-disabled-border: transparent;
//== Pager
//
//##
// Pager @pager-bg: @pagination-bg;
// ------------------------- @pager-border: @pagination-border;
@pager-border-radius: 15px; @pager-border-radius: 15px;
@pager-hover-bg: @pagination-hover-bg;
@pager-active-bg: @pagination-active-bg;
@pager-active-color: @pagination-active-color;
@pager-disabled-color: #fff; @pager-disabled-color: #fff;
//== Jumbotron
// Jumbotron //
// ------------------------- //##
@jumbotron-padding: 30px; @jumbotron-padding: 30px;
@jumbotron-color: inherit; @jumbotron-color: inherit;
@jumbotron-bg: @gray-lighter; @jumbotron-bg: @gray-lighter;
@jumbotron-heading-color: inherit; @jumbotron-heading-color: inherit;
@jumbotron-font-size: ceil(@font-size-base * 1.5); @jumbotron-font-size: ceil((@font-size-base * 1.5));
//== Form states and alerts
// Form states and alerts //
// ------------------------- //## Define colors for form feedback states and, by default, alerts.
@state-success-text: #fff; @state-success-text: #fff;
@state-success-bg: @brand-success; @state-success-bg: @brand-success;
@ -401,66 +483,109 @@
@state-danger-bg: @brand-danger; @state-danger-bg: @brand-danger;
@state-danger-border: @brand-danger; @state-danger-border: @brand-danger;
//== Tooltips
//
//##
// Tooltips //** Tooltip max width
// -------------------------
@tooltip-max-width: 200px; @tooltip-max-width: 200px;
//** Tooltip text color
@tooltip-color: #fff; @tooltip-color: #fff;
//** Tooltip background color
@tooltip-bg: rgba(0,0,0,.9); @tooltip-bg: rgba(0,0,0,.9);
@tooltip-opacity: .9;
//** Tooltip arrow width
@tooltip-arrow-width: 5px; @tooltip-arrow-width: 5px;
//** Tooltip arrow color
@tooltip-arrow-color: @tooltip-bg; @tooltip-arrow-color: @tooltip-bg;
//== Popovers
//
//##
// Popovers //** Popover body background color
// -------------------------
@popover-bg: #fff; @popover-bg: #fff;
//** Popover maximum width
@popover-max-width: 276px; @popover-max-width: 276px;
//** Popover border color
@popover-border-color: rgba(0,0,0,.2); @popover-border-color: rgba(0,0,0,.2);
//** Popover fallback border color
@popover-fallback-border-color: #ccc; @popover-fallback-border-color: #ccc;
//** Popover title background color
@popover-title-bg: darken(@popover-bg, 3%); @popover-title-bg: darken(@popover-bg, 3%);
//** Popover arrow width
@popover-arrow-width: 10px; @popover-arrow-width: 10px;
//** Popover arrow color
@popover-arrow-color: #fff; @popover-arrow-color: #fff;
//** Popover outer arrow width
@popover-arrow-outer-width: (@popover-arrow-width + 1); @popover-arrow-outer-width: (@popover-arrow-width + 1);
@popover-arrow-outer-color: rgba(0,0,0,.25); //** Popover outer arrow color
@popover-arrow-outer-fallback-color: #999; @popover-arrow-outer-color: fadein(@popover-border-color, 5%);
//** Popover outer arrow fallback color
@popover-arrow-outer-fallback-color: darken(@popover-fallback-border-color, 20%);
//== Labels
//
//##
// Labels //** Default label background color
// -------------------------
@label-default-bg: @btn-default-bg; @label-default-bg: @btn-default-bg;
//** Primary label background color
@label-primary-bg: @brand-primary; @label-primary-bg: @brand-primary;
//** Success label background color
@label-success-bg: @brand-success; @label-success-bg: @brand-success;
//** Info label background color
@label-info-bg: @brand-info; @label-info-bg: @brand-info;
//** Warning label background color
@label-warning-bg: @brand-warning; @label-warning-bg: @brand-warning;
//** Danger label background color
@label-danger-bg: @brand-danger; @label-danger-bg: @brand-danger;
//** Default label text color
@label-color: #fff; @label-color: #fff;
//** Default text color of a linked label
@label-link-hover-color: #fff; @label-link-hover-color: #fff;
//== Modals
//
//##
// Modals //** Padding applied to the modal body
// -------------------------
@modal-inner-padding: 20px; @modal-inner-padding: 20px;
//** Padding applied to the modal title
@modal-title-padding: 15px; @modal-title-padding: 15px;
//** Modal title line-height
@modal-title-line-height: @line-height-base; @modal-title-line-height: @line-height-base;
//** Background color of modal content area
@modal-content-bg: #fff; @modal-content-bg: #fff;
//** Modal content border color
@modal-content-border-color: rgba(0,0,0,.2); @modal-content-border-color: rgba(0,0,0,.2);
//** Modal content border color **for IE8**
@modal-content-fallback-border-color: #999; @modal-content-fallback-border-color: #999;
//** Modal backdrop background color
@modal-backdrop-bg: #000; @modal-backdrop-bg: #000;
//** Modal backdrop opacity
@modal-backdrop-opacity: .5;
//** Modal header border color
@modal-header-border-color: #e5e5e5; @modal-header-border-color: #e5e5e5;
//** Modal footer border color
@modal-footer-border-color: @modal-header-border-color; @modal-footer-border-color: @modal-header-border-color;
@modal-lg: 900px;
@modal-md: 600px;
@modal-sm: 300px;
//== Alerts
//
//## Define alert colors, border radius, and padding.
// Alerts
// -------------------------
@alert-padding: 15px; @alert-padding: 15px;
@alert-border-radius: @border-radius-base; @alert-border-radius: @border-radius-base;
@alert-link-font-weight: bold; @alert-link-font-weight: bold;
@ -481,39 +606,60 @@
@alert-danger-text: @state-danger-text; @alert-danger-text: @state-danger-text;
@alert-danger-border: @state-danger-border; @alert-danger-border: @state-danger-border;
//== Progress bars
//
//##
// Progress bars //** Background color of the whole progress component
// -------------------------
@progress-bg: @gray-lighter; @progress-bg: @gray-lighter;
//** Progress bar text color
@progress-bar-color: #fff; @progress-bar-color: #fff;
//** Default progress bar color
@progress-bar-bg: @brand-primary; @progress-bar-bg: @brand-primary;
//** Success progress bar color
@progress-bar-success-bg: @brand-success; @progress-bar-success-bg: @brand-success;
//** Warning progress bar color
@progress-bar-warning-bg: @brand-warning; @progress-bar-warning-bg: @brand-warning;
//** Danger progress bar color
@progress-bar-danger-bg: @brand-danger; @progress-bar-danger-bg: @brand-danger;
//** Info progress bar color
@progress-bar-info-bg: @brand-info; @progress-bar-info-bg: @brand-info;
//== List group
//
//##
// List group //** Background color on `.list-group-item`
// -------------------------
@list-group-bg: #fff; @list-group-bg: #fff;
//** `.list-group-item` border color
@list-group-border: @gray-lighter; @list-group-border: @gray-lighter;
//** List group border radius
@list-group-border-radius: @border-radius-base; @list-group-border-radius: @border-radius-base;
//** Background color of single list elements on hover
@list-group-hover-bg: @gray-lighter; @list-group-hover-bg: @gray-lighter;
//** Text color of active list elements
@list-group-active-color: @component-active-color; @list-group-active-color: @component-active-color;
//** Background color of active list elements
@list-group-active-bg: @component-active-bg; @list-group-active-bg: @component-active-bg;
//** Border color of active list elements
@list-group-active-border: @list-group-active-bg; @list-group-active-border: @list-group-active-bg;
@list-group-active-text-color: lighten(@list-group-active-bg, 40%);
@list-group-link-color: #555; @list-group-link-color: #555;
@list-group-link-heading-color: #333; @list-group-link-heading-color: #333;
//== Panels
//
//##
// Panels
// -------------------------
@panel-bg: #fff; @panel-bg: #fff;
@panel-inner-border: @gray-lighter; @panel-body-padding: 15px;
@panel-border-radius: @border-radius-base; @panel-border-radius: @border-radius-base;
//** Border color for elements within panels
@panel-inner-border: @gray-lighter;
@panel-footer-bg: @gray-lighter; @panel-footer-bg: @gray-lighter;
@panel-default-text: @text-color; @panel-default-text: @text-color;
@ -528,6 +674,10 @@
@panel-success-border: @state-success-border; @panel-success-border: @state-success-border;
@panel-success-heading-bg: @state-success-bg; @panel-success-heading-bg: @state-success-bg;
@panel-info-text: @state-info-text;
@panel-info-border: @state-info-border;
@panel-info-heading-bg: @state-info-bg;
@panel-warning-text: @state-warning-text; @panel-warning-text: @state-warning-text;
@panel-warning-border: @state-warning-border; @panel-warning-border: @state-warning-border;
@panel-warning-heading-bg: @state-warning-bg; @panel-warning-heading-bg: @state-warning-bg;
@ -536,51 +686,67 @@
@panel-danger-border: @state-danger-border; @panel-danger-border: @state-danger-border;
@panel-danger-heading-bg: @state-danger-bg; @panel-danger-heading-bg: @state-danger-bg;
@panel-info-text: @state-info-text; //== Thumbnails
@panel-info-border: @state-info-border; //
@panel-info-heading-bg: @state-info-bg; //##
//** Padding around the thumbnail image
// Thumbnails
// -------------------------
@thumbnail-padding: 4px; @thumbnail-padding: 4px;
//** Thumbnail background color
@thumbnail-bg: @body-bg; @thumbnail-bg: @body-bg;
//** Thumbnail border color
@thumbnail-border: @gray-lighter; @thumbnail-border: @gray-lighter;
//** Thumbnail border radius
@thumbnail-border-radius: @border-radius-base; @thumbnail-border-radius: @border-radius-base;
//** Custom text color for thumbnail captions
@thumbnail-caption-color: @text-color; @thumbnail-caption-color: @text-color;
//** Padding around the thumbnail caption
@thumbnail-caption-padding: 9px; @thumbnail-caption-padding: 9px;
//== Wells
//
//##
// Wells
// -------------------------
@well-bg: @gray-lighter; @well-bg: @gray-lighter;
@well-border: transparent;
//== Badges
//
//##
// Badges
// -------------------------
@badge-color: #fff; @badge-color: #fff;
//** Linked badge text color on hover
@badge-link-hover-color: #fff; @badge-link-hover-color: #fff;
@badge-bg: @gray; @badge-bg: @gray;
//** Badge text color in active nav link
@badge-active-color: @link-color; @badge-active-color: @link-color;
//** Badge background color in active nav link
@badge-active-bg: #fff; @badge-active-bg: #fff;
@badge-font-weight: bold; @badge-font-weight: bold;
@badge-line-height: 1; @badge-line-height: 1;
@badge-border-radius: 10px; @badge-border-radius: 10px;
//== Breadcrumbs
//
//##
// Breadcrumbs @breadcrumb-padding-vertical: 8px;
// ------------------------- @breadcrumb-padding-horizontal: 15px;
//** Breadcrumb background color
@breadcrumb-bg: @gray-lighter; @breadcrumb-bg: @gray-lighter;
//** Breadcrumb text color
@breadcrumb-color: #ccc; @breadcrumb-color: #ccc;
//** Text color of current page in the breadcrumb
@breadcrumb-active-color: @gray; @breadcrumb-active-color: @gray;
//** Textual separator for between breadcrumb elements
@breadcrumb-separator: "/"; @breadcrumb-separator: "/";
//== Carousel
// Carousel //
// ------------------------ //##
@carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6); @carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6);
@ -594,63 +760,59 @@
@carousel-caption-color: #fff; @carousel-caption-color: #fff;
//== Close
//
//##
// Close
// ------------------------
@close-font-weight: bold; @close-font-weight: bold;
@close-color: #000; @close-color: #000;
@close-text-shadow: 0 1px 0 #fff; @close-text-shadow: 0 1px 0 #fff;
//== Code
//
//##
// Code
// ------------------------
@code-color: #c7254e; @code-color: #c7254e;
@code-bg: #f9f2f4; @code-bg: #f9f2f4;
@kbd-color: #fff;
@kbd-bg: #333;
@pre-bg: @gray-lighter; @pre-bg: @gray-lighter;
@pre-color: @gray-dark; @pre-color: @gray-dark;
@pre-border-color: #ccc; @pre-border-color: #ccc;
@pre-scrollable-max-height: 340px; @pre-scrollable-max-height: 340px;
// Type //== Type
// ------------------------ //
//##
//** Text muted color
@text-muted: @gray-light; @text-muted: @gray-light;
//** Abbreviations and acronyms border color
@abbr-border-color: @gray-light; @abbr-border-color: @gray-light;
//** Headings small color
@headings-small-color: @gray-light; @headings-small-color: @gray-light;
//** Blockquote small color
@blockquote-small-color: @gray-light; @blockquote-small-color: @gray-light;
//** Blockquote font size
@blockquote-font-size: (@font-size-base * 1.25);
//** Blockquote border color
@blockquote-border-color: @gray-lighter; @blockquote-border-color: @gray-lighter;
//** Page header border color
@page-header-border-color: @gray-lighter; @page-header-border-color: @gray-lighter;
// Miscellaneous //== Miscellaneous
// ------------------------- //
//##
// Hr border color //** Horizontal line color.
@hr-border: @gray-lighter; @hr-border: @gray-lighter;
// Horizontal forms & lists //** Horizontal offset for forms and lists.
@component-offset-horizontal: 180px; @component-offset-horizontal: 180px;
@import url("//fonts.googleapis.com/css?family=Lato:400,700,400italic");
// Container sizes
// --------------------------------------------------
// Small screen / tablet
@container-tablet: ((720px + @grid-gutter-width));
@container-sm: @container-tablet;
// Medium screen / desktop
@container-desktop: ((940px + @grid-gutter-width));
@container-md: @container-desktop;
// Large screen / wide desktop
@container-large-desktop: ((1140px + @grid-gutter-width));
@container-lg: @container-large-desktop;
// Flatly 3.0.3
// Bootswatch
// -----------------------------------------------------
@import url("//fonts.googleapis.com/css?family=Lato:400,700,900,400italic");
// Navbar ===================================================================== // Navbar =====================================================================
@ -700,6 +862,24 @@
tr.danger { tr.danger {
color: #fff; color: #fff;
} }
> thead > tr > th,
> tbody > tr > th,
> tfoot > tr > th,
> thead > tr > td,
> tbody > tr > td,
> tfoot > tr > td {
border: none;
}
&-bordered > thead > tr > th,
&-bordered > tbody > tr > th,
&-bordered > tfoot > tr > th,
&-bordered > thead > tr > td,
&-bordered > tbody > tr > td,
&-bordered > tfoot > tr > td {
border: 1px solid @table-border-color;
}
} }
// Forms ====================================================================== // Forms ======================================================================
@ -774,23 +954,6 @@ input[type="color"],
} }
} }
.pagination {
a,
a:hover {
color: #fff;
}
.disabled {
&>a,
&>a:hover,
&>a:focus,
&>span {
background-color: lighten(@brand-success, 15%);
}
}
}
.pager { .pager {
a, a,
a:hover { a:hover {
@ -802,7 +965,7 @@ input[type="color"],
&>a:hover, &>a:hover,
&>a:focus, &>a:focus,
&>span { &>span {
background-color: lighten(@brand-success, 15%); background-color: @pagination-disabled-bg;
} }
} }
} }
@ -829,5 +992,4 @@ input[type="color"],
.well { .well {
.box-shadow(none); .box-shadow(none);
border-width: 0;
} }

19
test/app_test.js Normal file
View File

@ -0,0 +1,19 @@
var request = require('supertest');
var app = require('../app.js');
describe('GET /', function() {
it('should return 200 OK', function(done) {
request(app)
.get('/')
.expect(200, done);
});
});
describe('GET /reset', function() {
it('should return 404', function(done) {
request(app)
.get('/reset')
.expect(404, done);
// this will fail
});
});

1
test/mocha.opts Normal file
View File

@ -0,0 +1 @@
--reporter spec

43
test/user_test.js Normal file
View File

@ -0,0 +1,43 @@
var chai = require('chai');
var should = chai.should();
var User = require('../models/User');
describe('User attributes', function() {
before(function(done) {
user = new User({
email: 'janedoe@gmail.com',
password: 'password'
});
done();
});
it('email should be a string', function() {
user.email.should.be.a('string');
});
it('password should be a string', function() {
user.password.should.be.a('string');
});
it('should save a user', function(done) {
user.save();
done();
});
it('should find our newly created user', function(done) {
User.findOne({ email: user.email }, function(err, user) {
should.exist(user);
user.email.should.equal('janedoe@gmail.com');
done();
});
});
it('should not allow users with duplicate emails', function(done) {
user.save(function(err) {
if (err) {
err.code.should.equal(11000);
}
done();
});
});
});

15
views/account/forgot.jade Normal file
View File

@ -0,0 +1,15 @@
extends ../layout
block content
.col-sm-8.col-sm-offset-2
form(method='POST')
legend Forgot Password
input(type='hidden', name='_csrf', value=token)
.form-group
p Enter your email address below and we will send you password reset instructions.
label.control-label(for='email') Email
input.form-control(type='email', name='email', id='email', placeholder='Email', autofocus=true)
.form-group
button.btn.btn-primary(type='submit')
i.fa.fa-key
| Reset Password

View File

@ -3,8 +3,8 @@ extends ../layout
block content block content
.col-sm-8.col-sm-offset-2 .col-sm-8.col-sm-offset-2
form(method='POST') form(method='POST')
input(type='hidden', name='_csrf', value=token)
legend Sign In legend Sign In
input(type='hidden', name='_csrf', value=token)
.form-group .form-group
.btn-group.btn-group-justified .btn-group.btn-group-justified
if secrets.facebookAuth if secrets.facebookAuth
@ -34,4 +34,4 @@ block content
button.btn.btn-primary(type='submit') button.btn.btn-primary(type='submit')
i.fa.fa-unlock-alt i.fa.fa-unlock-alt
| Login | Login
a.btn.btn-link(href='/forgot') Forgot your password?

17
views/account/reset.jade Normal file
View File

@ -0,0 +1,17 @@
extends ../layout
block content
.col-sm-8.col-sm-offset-2
form(method='POST')
legend Reset Password
input(type='hidden', name='_csrf', value=token)
.form-group
label(for='password') New Password
input.form-control(type='password', name='password', value='', placeholder='New password', autofocus=true)
.form-group
label(for='confirm') Confirm Password
input.form-control(type='password', name='confirm', value='', placeholder='Confirm password')
.form-group
button.btn.btn-primary.btn-reset(type='submit')
i.fa.fa-keyboard-o
| Update Password