diff --git a/.gitignore b/.gitignore
index d60e71a5fda5edebe58f2fa8895ca89d0e6550d9..042d47047f9c64d2492259e0544f18738df62073 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,11 @@
 # Ignore docs files
 _gh_pages
 _site
+
+# Ignore ruby files
 .ruby-version
-Gemfile.lock
+.bundle
+vendor/cache
 
 # Numerous always-ignore extensions
 *.diff
@@ -41,6 +44,9 @@ validation-status.json
 # SCSS-Lint
 scss-lint-report.xml
 
+# grunt-contrib-sass cache
+.sass-cache
+
 # Folders to ignore
 bower_components
 node_modules
diff --git a/.travis.yml b/.travis.yml
index 5899bbf0f72fb82551539e1c93b599410b31b59b..bb85115f6136c0b4bab5049c801c0ca82156117c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -15,15 +15,12 @@ before_install:
 install:
   - npm install -g grunt-cli
   - ./test-infra/s3_cache.py download npm-modules
-  - if [ "$TWBS_TEST" = validate-html ] && [ $TWBS_DO_VALIDATOR -ne 0 ]; then ./test-infra/s3_cache.py download rubygems; fi
+  - if [ -n "$BUNDLE_GEMFILE" ]; then ./test-infra/s3_cache.py download rubygems; fi
 after_script:
   - if [ "$TRAVIS_REPO_SLUG" != twbs-savage/bootstrap ] && [ "$TWBS_TEST" = core ]; then ./test-infra/s3_cache.py upload npm-modules; fi
-  - if [ "$TRAVIS_REPO_SLUG" != twbs-savage/bootstrap ] && [ "$TWBS_TEST" = validate-html ] && [ $TWBS_DO_VALIDATOR -ne 0 ]; then ./test-infra/s3_cache.py upload rubygems; fi
+  - if [ "$TRAVIS_REPO_SLUG" != twbs-savage/bootstrap ] && [ -n "$BUNDLE_GEMFILE" ]; then ./test-infra/s3_cache.py upload rubygems; fi
 env:
   global:
-    - JEKYLL_VERSION="2.5.1"
-    - ROUGE_VERSION="1.7.2"
-    - SCSS_LINT_VERSION="0.30.0"
     - SAUCE_USERNAME="bootstrap"
     - secure: "pJkBwnuae9dKU5tEcCqccfS1QQw7/meEcfz63fM7ba7QJNjoA6BaXj08L5Z3Vb5vBmVPwBawxo5Hp0jC0r/Z/O0hGnAmz/Cz09L+cy7dSAZ9x4hvZePSja/UAusaB5ogMoO8l2b773MzgQeSmrLbExr9BWLeqEfjC2hFgdgHLaQ="
     - secure: "gqjqISbxBJK6byFbsmr1AyP1qoWH+rap06A2gI7v72+Tn2PU2nYkIMUkCvhZw6K889jv+LhQ/ybcBxDOXHpNCExCnSgB4dcnmYp+9oeNZb37jSP0rQ+Ib4OTLjzc3/FawE/fUq5kukZTC7porzc/k0qJNLAZRx3YLALmK1GIdUY="
@@ -35,8 +32,9 @@ env:
     - secure: "PabpUdG2dE40hHUkMCdxk1e9Ak3BOo0h7Y5/uekosLKOz5N60Xmn/ooyrSkvicLthXO4cfONFhO3/xSVRKQOxlUw4on5i0VuNK+QSqxJk0IDaRSZnTCcC8J7083K0YL+FvMdGQwcYwMY9LiwS8aS014IRkSQjsa+mjo3owP+dOU="
     - secure: "G4/f4PVyVi9o6UbZMqw9YFmDu7cHqe9iymiXYd1RcnPXwhWAePX12m0PWMhUj5itJ180PTEddVip8PNOgBdqyrDxEPKkcgAW2EElVAPIKJXVfvDW64UjQ0H7NS7XvF7iLQUJp/XfmR7NJ7tT393AQdh8SGmuQpJhgYbwIWbES/k="
   matrix:
-    - TWBS_TEST=core
-    - TWBS_TEST=validate-html
+    - TWBS_TEST=core TWBS_SASS=libsass BUNDLE_GEMFILE=test-infra/gemfiles/core.gemfile
+    - TWBS_TEST=core TWBS_SASS=sass    BUNDLE_GEMFILE=test-infra/gemfiles/core.gemfile
+    - TWBS_TEST=validate-html BUNDLE_GEMFILE=Gemfile
     - TWBS_TEST=sauce-js-unit
 matrix:
   fast_finish: true
diff --git a/Gemfile b/Gemfile
index eb9646efef1108f495a604b3f9d27ee3b7220eec..23068bdfebf5d68492430c4c51b007cf015821b8 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,11 @@
+# Ruby Gems for building and testing Bootstrap
+# Run `grunt update-gemfile-lock` to update to the latest compatible versions
+
 source 'https://rubygems.org'
 
-group :development do
+group :development, :test do
   gem 'jekyll', '~> 2.5.2'
   gem 'rouge', '~> 1.7.4'
+  gem 'sass', '~> 3.4.9'
+  gem 'scss-lint', '~> 0.31'
 end
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000000000000000000000000000000000000..725379399c4294e5a28f0636810e66a9fb6327e6
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,78 @@
+GEM
+  remote: https://rubygems.org/
+  specs:
+    blankslate (2.1.2.4)
+    celluloid (0.16.0)
+      timers (~> 4.0.0)
+    classifier-reborn (2.0.2)
+      fast-stemmer (~> 1.0)
+    coffee-script (2.3.0)
+      coffee-script-source
+      execjs
+    coffee-script-source (1.8.0)
+    colorator (0.1)
+    execjs (2.2.2)
+    fast-stemmer (1.0.2)
+    ffi (1.9.6)
+    hitimes (1.2.2)
+    jekyll (2.5.2)
+      classifier-reborn (~> 2.0)
+      colorator (~> 0.1)
+      jekyll-coffeescript (~> 1.0)
+      jekyll-gist (~> 1.0)
+      jekyll-paginate (~> 1.0)
+      jekyll-sass-converter (~> 1.0)
+      jekyll-watch (~> 1.1)
+      kramdown (~> 1.3)
+      liquid (~> 2.6.1)
+      mercenary (~> 0.3.3)
+      pygments.rb (~> 0.6.0)
+      redcarpet (~> 3.1)
+      safe_yaml (~> 1.0)
+      toml (~> 0.1.0)
+    jekyll-coffeescript (1.0.1)
+      coffee-script (~> 2.2)
+    jekyll-gist (1.1.0)
+    jekyll-paginate (1.1.0)
+    jekyll-sass-converter (1.3.0)
+      sass (~> 3.2)
+    jekyll-watch (1.2.0)
+      listen (~> 2.7)
+    kramdown (1.5.0)
+    liquid (2.6.1)
+    listen (2.8.4)
+      celluloid (>= 0.15.2)
+      rb-fsevent (>= 0.9.3)
+      rb-inotify (>= 0.9)
+    mercenary (0.3.5)
+    parslet (1.5.0)
+      blankslate (~> 2.0)
+    posix-spawn (0.3.9)
+    pygments.rb (0.6.0)
+      posix-spawn (~> 0.3.6)
+      yajl-ruby (~> 1.1.0)
+    rainbow (2.0.0)
+    rb-fsevent (0.9.4)
+    rb-inotify (0.9.5)
+      ffi (>= 0.5.0)
+    redcarpet (3.2.2)
+    rouge (1.7.4)
+    safe_yaml (1.0.4)
+    sass (3.4.9)
+    scss-lint (0.31.0)
+      rainbow (~> 2.0)
+      sass (~> 3.4.1)
+    timers (4.0.1)
+      hitimes
+    toml (0.1.2)
+      parslet (~> 1.5.0)
+    yajl-ruby (1.1.0)
+
+PLATFORMS
+  ruby
+
+DEPENDENCIES
+  jekyll (~> 2.5.2)
+  rouge (~> 1.7.4)
+  sass (~> 3.4.9)
+  scss-lint (~> 0.31)
diff --git a/Gruntfile.js b/Gruntfile.js
index d09e6f5606ee0cf6f4eec70e8df06ef0db0d8cf4..582f4ac30840d5649c14682371e87844e74e840d 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -17,6 +17,7 @@ module.exports = function (grunt) {
 
   var fs = require('fs');
   var path = require('path');
+  var glob = require('glob');
   var npmShrinkwrap = require('npm-shrinkwrap');
 
   var generateCommonJSModule = require('./grunt/bs-commonjs-generator.js');
@@ -144,25 +145,6 @@ module.exports = function (grunt) {
       files: 'js/tests/index.html'
     },
 
-    sass: {
-      options: {
-        includePaths: ['scss'],
-        precision: 6,
-        sourceComments: false,
-        sourceMap: true
-      },
-      core: {
-        files: {
-          'dist/css/<%= pkg.name %>.css': 'scss/<%= pkg.name %>.scss'
-        }
-      },
-      docs: {
-        files: {
-          'docs/assets/css/docs.min.css': 'docs/assets/scss/docs.scss'
-        }
-      }
-    },
-
     scsslint: {
       scss: ['scss/*.scss', '!scss/_normalize.scss'],
       options: {
@@ -342,13 +324,23 @@ module.exports = function (grunt) {
     exec: {
       npmUpdate: {
         command: 'npm update'
+      },
+      bundleUpdate: {
+        command: function () {
+          // Update dev gems and all the test gemsets
+          return 'bundle update && ' + glob.sync('test-infra/gemfiles/*.gemfile').map(function (gemfile) {
+            return 'BUNDLE_GEMFILE=' + gemfile + ' bundle update';
+          }).join(' && ');
+        }
       }
     }
   });
 
 
   // These plugins provide necessary tasks.
-  require('load-grunt-tasks')(grunt, { scope: 'devDependencies' });
+  require('load-grunt-tasks')(grunt, { scope: 'devDependencies',
+    // Exclude Sass compilers. We choose the one to load later on.
+    pattern: ['grunt-*', '!grunt-sass', '!grunt-contrib-sass'] });
   require('time-grunt')(grunt);
 
   // Docs HTML validation task
@@ -366,7 +358,8 @@ module.exports = function (grunt) {
   // Skip core tests if running a different subset of the test suite
   if (runSubset('core') &&
     // Skip core tests if this is a Savage build
-    process.env.TRAVIS_REPO_SLUG !== 'twbs-savage/bootstrap') {    testSubtasks = testSubtasks.concat(['dist-css', 'dist-js', 'test-scss', 'test-js', 'docs']);
+    process.env.TRAVIS_REPO_SLUG !== 'twbs-savage/bootstrap') {
+    testSubtasks = testSubtasks.concat(['dist-css', 'dist-js', 'test-scss', 'test-js', 'docs']);
   }
   // Skip HTML validation if running a different subset of the test suite
   if (runSubset('validate-html') &&
@@ -392,7 +385,12 @@ module.exports = function (grunt) {
   grunt.registerTask('test-scss', ['scsslint:scss']);
 
   // CSS distribution task.
+  // Supported Compilers: sass (Ruby) and libsass.
+  (function (sassCompilerName) {
+    require('./grunt/bs-sass-compile/' + sassCompilerName + '.js')(grunt);
+  })(process.env.TWBS_SASS || 'libsass');
   grunt.registerTask('sass-compile', ['sass:core', 'sass:docs']);
+
   grunt.registerTask('dist-css', ['sass-compile', 'autoprefixer:core', 'usebanner', 'csscomb:dist', 'cssmin:core', 'cssmin:docs']);
 
   // Full distribution task.
@@ -435,4 +433,7 @@ module.exports = function (grunt) {
       done();
     });
   });
+  // Task for updating the cached RubyGem packages used by the Travis build (which are controlled by test-infra/Gemfile.lock).
+  // This task should be run and the updated file should be committed whenever Bootstrap's RubyGem dependencies change.
+  grunt.registerTask('update-gemfile-lock', ['exec:bundleUpdate']);
 };
diff --git a/dist/css/bootstrap.css b/dist/css/bootstrap.css
index fbb56e094661ba02ca9c1b9c68e7b6a661783ac7..5045aa462c996a121bd187e75ac36af68bae19e2 100644
--- a/dist/css/bootstrap.css
+++ b/dist/css/bootstrap.css
@@ -447,7 +447,7 @@ ul ul, ul ol, ol ul, ol ol {
   margin-bottom: 0;
 }
 
-.list-unstyled, .nav {
+.list-unstyled, .list-inline, .nav {
   padding-left: 0;
   list-style: none;
 }
@@ -607,7 +607,7 @@ pre code {
 }
 @media (min-width: 34em) {
   .container {
-    max-width: 34em;
+    max-width: 34rem;
   }
 }
 @media (min-width: 48em) {
@@ -620,6 +620,11 @@ pre code {
     max-width: 60rem;
   }
 }
+@media (min-width: 75em) {
+  .container {
+    max-width: 72.25rem;
+  }
+}
 
 .container-fluid {
   padding-right: .75rem; 
@@ -647,7 +652,7 @@ pre code {
   clear: both;
 }
 
-.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xl-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xl-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xl-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xl-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xl-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xl-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xl-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xl-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xl-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xl-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xl-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12, .col-xl-12 {
+.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12 {
   position: relative;
   min-height: 1px;
   padding-right: .75rem; 
@@ -1492,7 +1497,7 @@ pre code {
   }
 }
 
-@media (min-width: 62em) {
+@media (min-width: 75em) {
   .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12 {
     float: left;
   }
@@ -2725,7 +2730,7 @@ input[type="submit"].btn-block, input[type="reset"].btn-block, input[type="butto
   z-index: 990;
 }
 
-.pull-right > .dropdown-menu, .navbar-right > .dropdown-menu {
+.pull-right > .dropdown-menu {
   right: 0;
   left: auto;
 }
@@ -3485,7 +3490,12 @@ input[type="submit"].btn-block, input[type="reset"].btn-block, input[type="butto
 }
 
 @media (min-width: 34em) {
+  .navbar-left {
+    float: left !important;
+  }
+
   .navbar-right {
+    float: right !important;
     margin-right: -1rem;
   }
   .navbar-right ~ .navbar-right {
@@ -3990,7 +4000,7 @@ a.label:hover, a.label:focus {
 .badge:empty {
   display: none;
 }
-.badge.pull-left, .badge.navbar-left, .badge.pull-right, .badge.navbar-right {
+.badge.pull-left, .badge.pull-right {
   top: .2em;
 }
 .list-group-item.active > .badge, .nav-pills > .active > a > .badge {
@@ -4322,11 +4332,11 @@ a.badge:hover, a.badge:focus {
   display: block;
 }
 
-.media-right, .media > .pull-right, .media > .navbar-right {
+.media-right, .media > .pull-right {
   padding-left: 10px;
 }
 
-.media-left, .media > .pull-left, .media > .navbar-left {
+.media-left, .media > .pull-left {
   padding-right: 10px;
 }
 
@@ -4705,19 +4715,23 @@ a.list-group-item-state.active, a.list-group-item-state.active:hover, a.list-gro
 .tooltip.in {
   opacity: .9;
 }
-.tooltip.top {
+
+.tooltip-top {
   padding: 5px 0;
   margin-top: -3px;
 }
-.tooltip.right {
+
+.tooltip-right {
   padding: 0 5px;
   margin-left: 3px;
 }
-.tooltip.bottom {
+
+.tooltip-bottom {
   padding: 5px 0;
   margin-top: 3px;
 }
-.tooltip.left {
+
+.tooltip-left {
   padding: 0 5px;
   margin-left: -3px;
 }
@@ -4740,62 +4754,37 @@ a.list-group-item-state.active, a.list-group-item-state.active:hover, a.list-gro
   border-style: solid;
 }
 
-.tooltip.top .tooltip-arrow {
+.tooltip-top .tooltip-arrow {
   bottom: 0;
   left: 50%;
   margin-left: -5px;
   border-width: 5px 5px 0;
   border-top-color: #000;
 }
-.tooltip.top-left .tooltip-arrow {
-  right: 5px;
-  bottom: 0;
-  margin-bottom: -5px;
-  border-width: 5px 5px 0;
-  border-top-color: #000;
-}
-.tooltip.top-right .tooltip-arrow {
-  bottom: 0;
-  left: 5px;
-  margin-bottom: -5px;
-  border-width: 5px 5px 0;
-  border-top-color: #000;
-}
-.tooltip.right .tooltip-arrow {
+
+.tooltip-right .tooltip-arrow {
   top: 50%;
   left: 0;
   margin-top: -5px;
   border-width: 5px 5px 5px 0;
   border-right-color: #000;
 }
-.tooltip.left .tooltip-arrow {
+
+.tooltip-left .tooltip-arrow {
   top: 50%;
   right: 0;
   margin-top: -5px;
   border-width: 5px 0 5px 5px;
   border-left-color: #000;
 }
-.tooltip.bottom .tooltip-arrow {
+
+.tooltip-bottom .tooltip-arrow {
   top: 0;
   left: 50%;
   margin-left: -5px;
   border-width: 0 5px 5px;
   border-bottom-color: #000;
 }
-.tooltip.bottom-left .tooltip-arrow {
-  top: 0;
-  right: 5px;
-  margin-top: -5px;
-  border-width: 0 5px 5px;
-  border-bottom-color: #000;
-}
-.tooltip.bottom-right .tooltip-arrow {
-  top: 0;
-  left: 5px;
-  margin-top: -5px;
-  border-width: 0 5px 5px;
-  border-bottom-color: #000;
-}
 
 .popover {
   position: absolute;
@@ -4819,16 +4808,20 @@ a.list-group-item-state.active, a.list-group-item-state.active:hover, a.list-gro
   -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
           box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
 }
-.popover.top {
+
+.popover-top {
   margin-top: -10px;
 }
-.popover.right {
+
+.popover-right {
   margin-left: 10px;
 }
-.popover.bottom {
+
+.popover-bottom {
   margin-top: 10px;
 }
-.popover.left {
+
+.popover-left {
   margin-left: -10px;
 }
 
@@ -4845,7 +4838,7 @@ a.list-group-item-state.active, a.list-group-item-state.active:hover, a.list-gro
   padding: 9px 14px;
 }
 
-.popover > .arrow, .popover > .arrow:after {
+.popover-arrow, .popover-arrow:after {
   position: absolute;
   display: block;
   width: 0;
@@ -4854,65 +4847,68 @@ a.list-group-item-state.active, a.list-group-item-state.active:hover, a.list-gro
   border-style: solid;
 }
 
-.popover > .arrow {
+.popover-arrow {
   border-width: 11px;
 }
 
-.popover > .arrow:after {
+.popover-arrow:after {
   content: "";
   border-width: 10px;
 }
 
-.popover.top > .arrow {
+.popover-top > .popover-arrow {
   bottom: -11px;
   left: 50%;
   margin-left: -11px;
   border-top-color: rgba(0, 0, 0, .25);
   border-bottom-width: 0;
 }
-.popover.top > .arrow:after {
+.popover-top > .popover-arrow:after {
   bottom: 1px;
   margin-left: -10px;
   content: "";
   border-top-color: #fff;
   border-bottom-width: 0;
 }
-.popover.right > .arrow {
+
+.popover-right > .popover-arrow {
   top: 50%;
   left: -11px;
   margin-top: -11px;
   border-right-color: rgba(0, 0, 0, .25);
   border-left-width: 0;
 }
-.popover.right > .arrow:after {
+.popover-right > .popover-arrow:after {
   bottom: -10px;
   left: 1px;
   content: "";
   border-right-color: #fff;
   border-left-width: 0;
 }
-.popover.bottom > .arrow {
+
+.popover-bottom > .popover-arrow {
   top: -11px;
   left: 50%;
   margin-left: -11px;
   border-top-width: 0;
   border-bottom-color: rgba(0, 0, 0, .25);
 }
-.popover.bottom > .arrow:after {
+.popover-bottom > .popover-arrow:after {
   top: 1px;
   margin-left: -10px;
   content: "";
   border-top-width: 0;
   border-bottom-color: #fff;
 }
-.popover.left > .arrow {
+
+.popover-left > .popover-arrow {
   top: 50%;
   right: -11px;
   margin-top: -11px;
   border-right-width: 0;
   border-left-color: rgba(0, 0, 0, .25);
 }
-.popover.left > .arrow:after {
+.popover-left > .popover-arrow:after {
   right: 1px;
   bottom: -10px;
   content: "";
@@ -5139,11 +5135,11 @@ a.list-group-item-state.active, a.list-group-item-state.active:hover, a.list-gro
   margin-left: auto;
 }
 
-.pull-right, .navbar-right {
+.pull-right {
   float: right !important;
 }
 
-.pull-left, .navbar-left {
+.pull-left {
   float: left !important;
 }
 
diff --git a/docs/_includes/js/popovers.html b/docs/_includes/js/popovers.html
index ab33baccbdb537b8441dabfb0465913954d094cc..4557a44a4293c5fd3e2d4b65122f7af65a061105 100644
--- a/docs/_includes/js/popovers.html
+++ b/docs/_includes/js/popovers.html
@@ -39,24 +39,24 @@ $(function () {
   <h3>Static popover</h3>
   <p>Four options are available: top, right, bottom, and left aligned.</p>
   <div class="bs-example bs-example-popover">
-    <div class="popover top">
-      <div class="arrow"></div>
+    <div class="popover popover-top">
+      <div class="popover-arrow"></div>
       <h3 class="popover-title">Popover top</h3>
       <div class="popover-content">
         <p>Sed posuere consectetur est at lobortis. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum.</p>
       </div>
     </div>
 
-    <div class="popover right">
-      <div class="arrow"></div>
+    <div class="popover popover-right">
+      <div class="popover-arrow"></div>
       <h3 class="popover-title">Popover right</h3>
       <div class="popover-content">
         <p>Sed posuere consectetur est at lobortis. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum.</p>
       </div>
     </div>
 
-    <div class="popover bottom">
-      <div class="arrow"></div>
+    <div class="popover popover-bottom">
+      <div class="popover-arrow"></div>
       <h3 class="popover-title">Popover bottom</h3>
 
       <div class="popover-content">
@@ -64,8 +64,8 @@ $(function () {
       </div>
     </div>
 
-    <div class="popover left">
-      <div class="arrow"></div>
+    <div class="popover popover-left">
+      <div class="popover-arrow"></div>
       <h3 class="popover-title">Popover left</h3>
       <div class="popover-content">
         <p>Sed posuere consectetur est at lobortis. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum.</p>
@@ -207,12 +207,12 @@ sagittis lacus vel augue laoreet rutrum faucibus.">
         <tr>
           <td>template</td>
           <td>string</td>
-          <td><code>'&lt;div class="popover" role="tooltip"&gt;&lt;div class="arrow"&gt;&lt;/div&gt;&lt;h3 class="popover-title"&gt;&lt;/h3&gt;&lt;div class="popover-content"&gt;&lt;/div&gt;&lt;/div&gt;'</code></td>
+          <td><code>'&lt;div class="popover" role="tooltip"&gt;&lt;div class="popover-arrow"&gt;&lt;/div&gt;&lt;h3 class="popover-title"&gt;&lt;/h3&gt;&lt;div class="popover-content"&gt;&lt;/div&gt;&lt;/div&gt;'</code></td>
           <td>
             <p>Base HTML to use when creating the popover.</p>
             <p>The popover's <code>title</code> will be injected into the <code>.popover-title</code>.</p>
             <p>The popover's <code>content</code> will be injected into the <code>.popover-content</code>.</p>
-            <p><code>.arrow</code> will become the popover's arrow.</p>
+            <p><code>.popover-arrow</code> will become the popover's arrow.</p>
             <p>The outermost wrapper element should have the <code>.popover</code> class.</p>
           </td>
         </tr>
diff --git a/docs/_includes/js/tooltips.html b/docs/_includes/js/tooltips.html
index ae553e0df0ff20f718a7db4abc0fcc259ba115b9..b8293c9f5d3756182c7ee758bbba7ae1199d278e 100644
--- a/docs/_includes/js/tooltips.html
+++ b/docs/_includes/js/tooltips.html
@@ -12,25 +12,25 @@
   <h3>Static tooltip</h3>
   <p>Four options are available: top, right, bottom, and left aligned.</p>
   <div class="bs-example bs-example-tooltip">
-    <div class="tooltip left" role="tooltip">
+    <div class="tooltip tooltip-left" role="tooltip">
       <div class="tooltip-arrow"></div>
       <div class="tooltip-inner">
         Tooltip on the left
       </div>
     </div>
-    <div class="tooltip top" role="tooltip">
+    <div class="tooltip tooltip-top" role="tooltip">
       <div class="tooltip-arrow"></div>
       <div class="tooltip-inner">
         Tooltip on the top
       </div>
     </div>
-    <div class="tooltip bottom" role="tooltip">
+    <div class="tooltip tooltip-bottom" role="tooltip">
       <div class="tooltip-arrow"></div>
       <div class="tooltip-inner">
         Tooltip on the bottom
       </div>
     </div>
-    <div class="tooltip right" role="tooltip">
+    <div class="tooltip tooltip-right" role="tooltip">
       <div class="tooltip-arrow"></div>
       <div class="tooltip-inner">
         Tooltip on the right
diff --git a/docs/assets/js/src/application.js b/docs/assets/js/src/application.js
index 9c1ec22c47cbc3f0489f0082a69c8f0c1cd59102..8dfb826519b213b455d0b6ed4b54a83fe36957ef 100644
--- a/docs/assets/js/src/application.js
+++ b/docs/assets/js/src/application.js
@@ -10,6 +10,7 @@
  */
 
 /* global ZeroClipboard */
+/* global SimpleJekyllSearch */
 
 !function ($) {
   'use strict';
diff --git a/docs/components/card.md b/docs/components/card.md
index 237588c55eab3d30cd927d843933625465523bbb..7e7b4d8ee4ccac4e638276a230ce2edc208acb03 100644
--- a/docs/components/card.md
+++ b/docs/components/card.md
@@ -21,7 +21,7 @@ Cards require very little markup, but do require some additional classes to give
 
 ### Text alignment
 
-You can quickly change the text alignment of any card—in it's entirety or specific parts—with our [text align classes]().
+You can quickly change the text alignment of any card—in its entirety or specific parts—with our [text align classes]().
 
 {% example html %}
 <div class="card">
diff --git a/docs/content/tables.md b/docs/content/tables.md
index 23e25a2158c11ebd345b35b1333053d7c03b49ec..e4f69b06824e9f16ffa2ccf0548c8b30833e2166 100644
--- a/docs/content/tables.md
+++ b/docs/content/tables.md
@@ -3,7 +3,7 @@ layout: page
 title: Tables
 ---
 
-Due to the widespread use of tables across plugins like calendars and date pickers, we've designed our tables to be **opt-in**. Just add the base class `.table` to any `<table>`.
+Due to the widespread use of tables across third-party widgets like calendars and date pickers, we've designed our tables to be **opt-in**. Just add the base class `.table` to any `<table>`.
 
 ## Basic example
 
diff --git a/docs/examples/album/index.html b/docs/examples/album/index.html
index 6a3c085a8821478e40507a623dcc128552be9496..ac456377b0f367f8e9040bee8ada09f18aea8538 100644
--- a/docs/examples/album/index.html
+++ b/docs/examples/album/index.html
@@ -50,7 +50,7 @@
     <section class="jumbotron text-center">
       <div class="container">
         <h1 class="jumbotron-heading">Album example</h1>
-        <p class="lead text-muted">Something short and leading about the collection below—it's contents, the creator, etc. Make it short and sweet, but not too short so folks don't simply skip over it entirely.</p>
+        <p class="lead text-muted">Something short and leading about the collection below—its contents, the creator, etc. Make it short and sweet, but not too short so folks don't simply skip over it entirely.</p>
         <p>
           <a href="#" class="btn btn-primary">Main call to action</a>
           <a href="#" class="btn btn-secondary">Secondary action</a>
diff --git a/docs/examples/navbar-top/index.html b/docs/examples/navbar-top/index.html
index 334b8430ae82fd04f3c5709e1dc1d1f69fdc635c..f54f5eafd07435fc87e4e4426df56efd7df0a2e7 100644
--- a/docs/examples/navbar-top/index.html
+++ b/docs/examples/navbar-top/index.html
@@ -37,7 +37,7 @@
     <div class="container">
       <div class="jumbotron">
         <h1>Navbar example</h1>
-        <p class="lead">This example is a quick exercise to illustrate how the top-aligned navbar works. As you scroll, this navbar remains in it's original position and moves with the rest of the page.</p>
+        <p class="lead">This example is a quick exercise to illustrate how the top-aligned navbar works. As you scroll, this navbar remains in its original position and moves with the rest of the page.</p>
         <a class="btn btn-lg btn-primary" href="../../components/#navbar" role="button">View navbar docs &raquo;</a>
       </div>
     </div>
diff --git a/docs/examples/navbar/index.html b/docs/examples/navbar/index.html
index d6084e663ed95859b01f7720afca0f9706191efd..fde91fa8fa844cc895c20485cd8be53ba7cb5ab3 100644
--- a/docs/examples/navbar/index.html
+++ b/docs/examples/navbar/index.html
@@ -39,7 +39,7 @@
       <!-- Main component for a primary marketing message or call to action -->
       <div class="jumbotron">
         <h1>Navbar example</h1>
-        <p>This example is a quick exercise to illustrate how the default navbar works. It's placed within a <code>.container</code> to limit it's width and will scroll with the rest of the page's content.</p>
+        <p>This example is a quick exercise to illustrate how the default navbar works. It's placed within a <code>.container</code> to limit its width and will scroll with the rest of the page's content.</p>
         <p>
           <a class="btn btn-lg btn-primary" href="../../components/#navbar" role="button">View navbar docs &raquo;</a>
         </p>
diff --git a/docs/getting-started/compiling.md b/docs/getting-started/compiling.md
index 7a12becf757d93e664f7986209fe34d0e61078f0..38aa249d55ec90edb5b8ab0af4883171d4126e6c 100644
--- a/docs/getting-started/compiling.md
+++ b/docs/getting-started/compiling.md
@@ -13,11 +13,15 @@ From the command line:
 
 1. Install `grunt-cli` globally with `npm install -g grunt-cli`.
 2. Navigate to the root `/bootstrap` directory, then run `npm install`. npm will look at [package.json](https://github.com/twbs/bootstrap/blob/master/package.json) and automatically install the necessary local dependencies listed there.
+3. [Install Ruby][install-ruby], install [Bundler][gembundler] with `gem install bundler`, and finally run `bundle`. This will install all Ruby dependencies, such as Jekyll and Sass linter.
 
 When completed, you'll be able to run the various Grunt commands provided from the command line.
 
 **Unfamiliar with npm? Don't have node installed?** That's a-okay. npm stands for [node packaged modules](http://npmjs.org/) and is a way to manage development dependencies through node.js. [Download and install node.js](http://nodejs.org/download/) before proceeding.
 
+[install-ruby]: https://www.ruby-lang.org/en/documentation/installation/
+[gembundler]: http://bundler.io/
+
 ### Available Grunt commands
 
 #### Build - `grunt`
@@ -35,6 +39,20 @@ Builds and tests CSS, JavaScript, and other assets which are used when running t
 #### Watch - `grunt watch`
 This is a convenience method for watching just Sass files and automatically building them whenever you save.
 
+### Use another Sass compiler
+Bootstrap is compiled with [libsass][libsass] by default.
+Use another compiler by setting the `TWBS_SASS` environment variable to:
+
+* `sass` to use [Ruby Sass][ruby-sass] via [grunt-contrib-sass][grunt-contrib-sass].
+* `libsass` (default) to use [libsass][libsass] via [grunt-sass][grunt-sass].
+
+For example, run `TWBS_SASS=sass grunt` to test and build Bootstrap with Ruby Sass.
+
+[ruby-sass]: https://github.com/sass/sass
+[grunt-contrib-sass]: https://github.com/gruntjs/grunt-contrib-sass
+[libsass]: https://github.com/sass/libsass
+[grunt-sass]: https://github.com/sindresorhus/grunt-sass
+
 ### Troubleshooting dependencies
 
 Should you encounter problems with installing dependencies or running Grunt commands, uninstall all previous dependency versions (global and local). Then, rerun `npm install`.
diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md
index 6b1e682207ba3f79d1d33bf8a254dd297a2826fb..8e2f6814d8790cde3ac1d1fbf03372e3247d4ce5 100644
--- a/docs/getting-started/quick-start.md
+++ b/docs/getting-started/quick-start.md
@@ -22,7 +22,7 @@ Then, add the Bootstrap JavaScript—and jQuery—near the end of your pages. It
 Be sure to have your pages set up with the latest design and development standards. That means:
 
 * Using an HTML5 doctype
-* Forcing Internet Explorer to use it's latest rendering mode ([read more]())
+* Forcing Internet Explorer to use its latest rendering mode ([read more]())
 * And, utilizing the viewport meta tag.
 
 Put it all together and your pages should look like this:
diff --git a/docs/getting-started/third-party-support.md b/docs/getting-started/third-party-support.md
index c469b644e73a0f2cd4ed9ade2c3edc915886ff2d..ea22cf589fb212869f62733c7e35f543ab02e100 100644
--- a/docs/getting-started/third-party-support.md
+++ b/docs/getting-started/third-party-support.md
@@ -16,7 +16,7 @@ Depending on the context, you may override as-needed (Option 1) or reset the box
  *
  * Reset individual elements or override regions to avoid conflicts due to
  * global box model settings of Bootstrap. Two options, individual overrides and
- * region resets, are available as plain CSS and uncompiled Less formats.
+ * region resets, are available as plain CSS and uncompiled Sass formats.
  */
 
 /* Option 1A: Override a single element's box model via CSS */
@@ -26,7 +26,7 @@ Depending on the context, you may override as-needed (Option 1) or reset the box
           box-sizing: content-box;
 }
 
-/* Option 1B: Override a single element's box model by using a Bootstrap Less mixin */
+/* Option 1B: Override a single element's box model by using a Bootstrap Sass mixin */
 .element {
   .box-sizing(content-box);
 }
@@ -41,7 +41,7 @@ Depending on the context, you may override as-needed (Option 1) or reset the box
           box-sizing: content-box;
 }
 
-/* Option 2B: Reset an entire region with a custom Less mixin */
+/* Option 2B: Reset an entire region with a custom Sass mixin */
 .reset-box-sizing {
   &,
   *,
diff --git a/docs/index.html b/docs/index.html
index 2ebeb3ff90065ea5c5e3ade84310ad0be18d143a..d0ae59d72dab6cb6e8b0e85d261ebbd44e362450 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -25,9 +25,9 @@ title: Bootstrap &middot; The world's most popular mobile-first and responsive f
 
     <div class="row">
       <div class="col-sm-4">
-        <img src="assets/img/sass-less.png" alt="Sass and Less support" class="img-responsive">
+        <img src="assets/img/sass-less.png" alt="Sass support" class="img-responsive">
         <h3>Preprocessors</h3>
-        <p>Bootstrap ships with vanilla CSS, but its source code utilizes the two most popular CSS preprocessors, <a href="../css/#less">Less</a> and <a href="../css/#sass">Sass</a>. Quickly get started with precompiled CSS or build on the source.</p>
+        <p>Bootstrap ships with vanilla CSS, but its source code utilizes <a href="../css/#sass">Sass</a>, a popular CSS preprocessor. Quickly get started with precompiled CSS or build on the source.</p>
       </div>
       <div class="col-sm-4">
         <img src="assets/img/devices.png" alt="Responsive across devices" class="img-responsive">
diff --git a/docs/javascript/alerts.md b/docs/javascript/alerts.md
index bbe2eeab426eab6b700582a4bfdc8c28759779ca..8d3df5d2f8881cee473688dedf8b13cbc9a94607 100644
--- a/docs/javascript/alerts.md
+++ b/docs/javascript/alerts.md
@@ -22,7 +22,7 @@ When using a `.close` button, it must be the first child of the `.alert-dismissi
 {% example html %}
 <div class="alert alert-danger alert-dismissible fade in" role="alert">
   <button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
-  <h4>Oh snap! You got an error!</h4>
+  <h4 class="alert-heading">Oh snap! You got an error!</h4>
   <p>Change this and that and try again. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Cras mattis consectetur purus sit amet fermentum.</p>
   <p>
     <button type="button" class="btn btn-danger">Take this action</button>
diff --git a/docs/javascript/carousel.md b/docs/javascript/carousel.md
index dac8d00d1ead8c583e8040d5c970bfe5d56feb05..d69725a594ea76511cf0b0375b553cec19274a14 100644
--- a/docs/javascript/carousel.md
+++ b/docs/javascript/carousel.md
@@ -37,8 +37,8 @@ A slideshow component for cycling through elements—images or slides of text—
 {% endexample %}
 
 <div class="bs-callout bs-callout-warning" id="callout-carousel-transitions">
-  <h4>Transition animations not supported in Internet Explorer 8 &amp; 9</h4>
-  <p>Bootstrap exclusively uses CSS3 for its animations, but Internet Explorer 8 &amp; 9 don't support the necessary CSS properties. Thus, there are no slide transition animations when using these browsers. We have intentionally decided not to include jQuery-based fallbacks for the transitions.</p>
+  <h4>Transition animations not supported in Internet Explorer 9</h4>
+  <p>Bootstrap exclusively uses CSS3 for its animations, but Internet Explorer 9 doesn't support the necessary CSS properties. Thus, there are no slide transition animations when using that browser. We have intentionally decided not to include jQuery-based fallbacks for the transitions.</p>
 </div>
 
 <div class="bs-callout bs-callout-warning" id="callout-carousel-active">
diff --git a/docs/javascript/overview.md b/docs/javascript/overview.md
index 8d22d402026bdbdf00940887f274e9082369c71e..631ea519c55084461a841ed2568a99a6332f4c57 100644
--- a/docs/javascript/overview.md
+++ b/docs/javascript/overview.md
@@ -81,8 +81,6 @@ $.fn.bootstrapBtn = bootstrapButton            // give $().bootstrapBtn the Boot
 
 Bootstrap provides custom events for most plugins' unique actions. Generally, these come in an infinitive and past participle form - where the infinitive (ex. `show`) is triggered at the start of an event, and its past participle form (ex. `shown`) is triggered on the completion of an action.
 
-As of 3.0.0, all Bootstrap events are namespaced.
-
 All infinitive events provide `preventDefault` functionality. This provides the ability to stop the execution of an action before it starts.
 
 {% highlight js %}
diff --git a/docs/javascript/popovers.md b/docs/javascript/popovers.md
index 0844621ba306aab53b5beb32e71e8ca9b165e54a..28cafb1b0855f11592345c98363cd659b59c5ba7 100644
--- a/docs/javascript/popovers.md
+++ b/docs/javascript/popovers.md
@@ -43,24 +43,24 @@ $(function () {
 Four options are available: top, right, bottom, and left aligned.
 
 <div class="bs-example bs-example-popover">
-  <div class="popover top">
-    <div class="arrow"></div>
+  <div class="popover popover-top">
+    <div class="popover-arrow"></div>
     <h3 class="popover-title">Popover top</h3>
     <div class="popover-content">
       <p>Sed posuere consectetur est at lobortis. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum.</p>
     </div>
   </div>
 
-  <div class="popover right">
-    <div class="arrow"></div>
+  <div class="popover popover-right">
+    <div class="popover-arrow"></div>
     <h3 class="popover-title">Popover right</h3>
     <div class="popover-content">
       <p>Sed posuere consectetur est at lobortis. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum.</p>
     </div>
   </div>
 
-  <div class="popover bottom">
-    <div class="arrow"></div>
+  <div class="popover popover-bottom">
+    <div class="popover-arrow"></div>
     <h3 class="popover-title">Popover bottom</h3>
 
     <div class="popover-content">
@@ -68,8 +68,8 @@ Four options are available: top, right, bottom, and left aligned.
     </div>
   </div>
 
-  <div class="popover left">
-    <div class="arrow"></div>
+  <div class="popover popover-left">
+    <div class="popover-arrow"></div>
     <h3 class="popover-title">Popover left</h3>
     <div class="popover-content">
       <p>Sed posuere consectetur est at lobortis. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum.</p>
@@ -222,12 +222,12 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap
       <tr>
         <td>template</td>
         <td>string</td>
-        <td><code>'&lt;div class="popover" role="tooltip"&gt;&lt;div class="arrow"&gt;&lt;/div&gt;&lt;h3 class="popover-title"&gt;&lt;/h3&gt;&lt;div class="popover-content"&gt;&lt;/div&gt;&lt;/div&gt;'</code></td>
+        <td><code>'&lt;div class="popover" role="tooltip"&gt;&lt;div class="popover-arrow"&gt;&lt;/div&gt;&lt;h3 class="popover-title"&gt;&lt;/h3&gt;&lt;div class="popover-content"&gt;&lt;/div&gt;&lt;/div&gt;'</code></td>
         <td>
           <p>Base HTML to use when creating the popover.</p>
           <p>The popover's <code>title</code> will be injected into the <code>.popover-title</code>.</p>
           <p>The popover's <code>content</code> will be injected into the <code>.popover-content</code>.</p>
-          <p><code>.arrow</code> will become the popover's arrow.</p>
+          <p><code>.popover-arrow</code> will become the popover's arrow.</p>
           <p>The outermost wrapper element should have the <code>.popover</code> class.</p>
         </td>
       </tr>
diff --git a/grunt/bs-sass-compile/libsass.js b/grunt/bs-sass-compile/libsass.js
new file mode 100644
index 0000000000000000000000000000000000000000..e3d13a989ffc243134c69f1a2c390653c0751344
--- /dev/null
+++ b/grunt/bs-sass-compile/libsass.js
@@ -0,0 +1,26 @@
+// Compile Bootstrap with [libsass][1] using [grunt-sass][2]
+// [1]: https://github.com/sass/libsass
+// [2]: https://github.com/sindresorhus/grunt-sass
+module.exports = function configureLibsass(grunt) {
+  grunt.config.merge({
+    sass: {
+      options: {
+        includePaths: ['scss'],
+        precision: 6,
+        sourceComments: false,
+        sourceMap: true
+      },
+      core: {
+        files: {
+          'dist/css/<%= pkg.name %>.css': 'scss/<%= pkg.name %>.scss'
+        }
+      },
+      docs: {
+        files: {
+          'docs/assets/css/docs.min.css': 'docs/assets/scss/docs.scss'
+        }
+      }
+    }
+  });
+  grunt.loadNpmTasks('grunt-sass');
+};
diff --git a/grunt/bs-sass-compile/sass.js b/grunt/bs-sass-compile/sass.js
new file mode 100644
index 0000000000000000000000000000000000000000..d7743515d80fd3aaf775611d2e6ccd9828bdfc5f
--- /dev/null
+++ b/grunt/bs-sass-compile/sass.js
@@ -0,0 +1,30 @@
+// Compile Bootstrap with [Ruby Sass][1] using [grunt-contrib-sass][2]
+// [1]: https://github.com/sass/sass
+// [2]: https://github.com/gruntjs/grunt-contrib-sass
+module.exports = function configureRubySass(grunt) {
+  var options = {
+    loadPath: ['scss'],
+    precision: 6,
+    sourcemap: 'auto',
+    style: 'expanded',
+    trace: true,
+    bundleExec: true
+  };
+  grunt.config.merge({
+    sass: {
+      core: {
+        options: options,
+        files: {
+          'dist/css/<%= pkg.name %>.css': 'scss/<%= pkg.name %>.scss'
+        }
+      },
+      docs: {
+        options: options,
+        files: {
+          'docs/assets/css/docs.min.css': 'docs/assets/scss/docs.scss'
+        }
+      }
+    }
+  });
+  grunt.loadNpmTasks('grunt-contrib-sass');
+};
diff --git a/js/.jscsrc b/js/.jscsrc
index 9612c1683328ccfb22758050fb48d6871d7a81b6..ac1d73f55e4128a5728f338b020efb075ae73e30 100644
--- a/js/.jscsrc
+++ b/js/.jscsrc
@@ -22,6 +22,7 @@
   "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
   "requireSpaceAfterLineComment": true,
   "requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", "<", ">=", "<="],
+  "requireSpaceBetweenArguments": true,
   "requireSpacesInAnonymousFunctionExpression": { "beforeOpeningCurlyBrace": true, "beforeOpeningRoundBrace": true },
   "requireSpacesInConditionalExpression": true,
   "requireSpacesInFunctionDeclaration": { "beforeOpeningCurlyBrace": true },
diff --git a/js/popover.js b/js/popover.js
index db272bdee412f9a98ef803c476c6671bf31e86c0..085584fe8744846c8c7283844ec40edeedd5a94e 100644
--- a/js/popover.js
+++ b/js/popover.js
@@ -25,7 +25,7 @@
     placement: 'right',
     trigger: 'click',
     content: '',
-    template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+    template: '<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
   })
 
 
@@ -50,7 +50,7 @@
       this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
     ](content)
 
-    $tip.removeClass('fade top bottom left right in')
+    $tip.removeClass('fade popover-top popover-bottom popover-left popover-right in')
 
     // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
     // this manually by checking the contents.
@@ -72,7 +72,7 @@
   }
 
   Popover.prototype.arrow = function () {
-    return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
+    return (this.$arrow = this.$arrow || this.tip().find('.popover-arrow'))
   }
 
   Popover.prototype.tip = function () {
diff --git a/js/tests/unit/popover.js b/js/tests/unit/popover.js
index 466ebace26d5141298886eeb41e20285c69ff6ca..8f59d3483acc9b155b196d3ba1b3090502039967 100644
--- a/js/tests/unit/popover.js
+++ b/js/tests/unit/popover.js
@@ -141,7 +141,7 @@ $(function () {
       .bootstrapPopover({
         title: 'Test',
         content: 'Test',
-        template: '<div class="popover foobar"><div class="arrow"></div><div class="inner"><h3 class="title"/><div class="content"><p/></div></div></div>'
+        template: '<div class="popover foobar"><div class="popover-arrow"></div><div class="inner"><h3 class="title"/><div class="content"><p/></div></div></div>'
       })
 
     $popover.bootstrapPopover('show')
diff --git a/js/tests/unit/tooltip.js b/js/tests/unit/tooltip.js
index eb578c22af3565f7f72efcf89078585670580268..6232ccdcb3beec50548766fc8581cd91f3b06810 100644
--- a/js/tests/unit/tooltip.js
+++ b/js/tests/unit/tooltip.js
@@ -85,7 +85,7 @@ $(function () {
       .bootstrapTooltip({ placement: 'bottom' })
 
     $tooltip.bootstrapTooltip('show')
-    ok($('.tooltip').is('.fade.bottom.in'), 'has correct classes applied')
+    ok($('.tooltip').is('.fade.tooltip-bottom.in'), 'has correct classes applied')
 
     $tooltip.bootstrapTooltip('hide')
     equal($('.tooltip').length, 0, 'tooltip removed')
@@ -300,8 +300,8 @@ $(function () {
 
   test('should add position class before positioning so that position-specific styles are taken into account', function () {
     var styles = '<style>'
-        + '.tooltip.right { white-space: nowrap; }'
-        + '.tooltip.right .tooltip-inner { max-width: none; }'
+        + '.tooltip.tooltip-right { white-space: nowrap; }'
+        + '.tooltip.tooltip-right .tooltip-inner { max-width: none; }'
         + '</style>'
     var $styles = $(styles).appendTo('head')
 
@@ -384,7 +384,7 @@ $(function () {
       .bootstrapTooltip({ placement: 'auto' })
 
     $topTooltip.bootstrapTooltip('show')
-    ok($('.tooltip').is('.bottom'), 'top positioned tooltip is dynamically positioned to bottom')
+    ok($('.tooltip').is('.tooltip-bottom'), 'top positioned tooltip is dynamically positioned to bottom')
 
     $topTooltip.bootstrapTooltip('hide')
     equal($('.tooltip').length, 0, 'top positioned tooltip removed from dom')
@@ -394,7 +394,7 @@ $(function () {
       .bootstrapTooltip({ placement: 'right auto' })
 
     $rightTooltip.bootstrapTooltip('show')
-    ok($('.tooltip').is('.left'), 'right positioned tooltip is dynamically positioned left')
+    ok($('.tooltip').is('.tooltip-left'), 'right positioned tooltip is dynamically positioned left')
 
     $rightTooltip.bootstrapTooltip('hide')
     equal($('.tooltip').length, 0, 'right positioned tooltip removed from dom')
@@ -404,7 +404,7 @@ $(function () {
       .bootstrapTooltip({ placement: 'auto left' })
 
     $leftTooltip.bootstrapTooltip('show')
-    ok($('.tooltip').is('.right'), 'left positioned tooltip is dynamically positioned right')
+    ok($('.tooltip').is('.tooltip-right'), 'left positioned tooltip is dynamically positioned right')
 
     $leftTooltip.bootstrapTooltip('hide')
     equal($('.tooltip').length, 0, 'left positioned tooltip removed from dom')
@@ -430,7 +430,7 @@ $(function () {
       })
 
     $target.bootstrapTooltip('show')
-    ok($('.tooltip').is('.top'), 'top positioned tooltip is dynamically positioned to top')
+    ok($('.tooltip').is('.tooltip-top'), 'top positioned tooltip is dynamically positioned to top')
 
     $target.bootstrapTooltip('hide')
     equal($('.tooltip').length, 0, 'tooltip removed from dom')
@@ -455,7 +455,7 @@ $(function () {
       })
 
     $target.bootstrapTooltip('show')
-    ok($('.tooltip').is('.bottom'), 'top positioned tooltip is dynamically positioned to bottom')
+    ok($('.tooltip').is('.tooltip-bottom'), 'top positioned tooltip is dynamically positioned to bottom')
 
     $target.bootstrapTooltip('hide')
     equal($('.tooltip').length, 0, 'tooltip removed from dom')
@@ -481,7 +481,7 @@ $(function () {
     $('#scrollable-div').scrollTop(100)
 
     $target.bootstrapTooltip('show')
-    ok($('.tooltip').is('.fade.top.in'), 'has correct classes applied')
+    ok($('.tooltip').is('.fade.tooltip-top.in'), 'has correct classes applied')
 
     $target.bootstrapTooltip('hide')
     equal($('.tooltip').length, 0, 'tooltip removed from dom')
@@ -507,7 +507,7 @@ $(function () {
     $('#scrollable-div').scrollTop(200)
 
     $target.bootstrapTooltip('show')
-    ok($('.tooltip').is('.fade.bottom.in'), 'has correct classes applied')
+    ok($('.tooltip').is('.fade.tooltip-bottom.in'), 'has correct classes applied')
 
     $target.bootstrapTooltip('hide')
     equal($('.tooltip').length, 0, 'tooltip removed from dom')
@@ -537,7 +537,7 @@ $(function () {
     $('#scrollable-div').scrollTop(200)
 
     $target.bootstrapTooltip('show')
-    ok($('.tooltip').is('.fade.bottom.in'), 'has correct classes applied')
+    ok($('.tooltip').is('.fade.tooltip-bottom.in'), 'has correct classes applied')
 
     $target.bootstrapTooltip('hide')
     equal($('.tooltip').length, 0, 'tooltip removed from dom')
@@ -563,7 +563,7 @@ $(function () {
     $('#scrollable-div').scrollTop(400)
 
     $target.bootstrapTooltip('show')
-    ok($('.tooltip').is('.fade.top.in'), 'has correct classes applied')
+    ok($('.tooltip').is('.fade.tooltip-top.in'), 'has correct classes applied')
 
     $target.bootstrapTooltip('hide')
     equal($('.tooltip').length, 0, 'tooltip removed from dom')
diff --git a/js/tooltip.js b/js/tooltip.js
index bd376f77279be72aebc6685783a697ac033cd46b..9d3074cc048591f0b6992317919866d7a846cfa2 100644
--- a/js/tooltip.js
+++ b/js/tooltip.js
@@ -177,7 +177,7 @@
       $tip
         .detach()
         .css({ top: 0, left: 0, display: 'block' })
-        .addClass(placement)
+        .addClass(this.type + '-' + placement)
         .data('bs.' + this.type, this)
 
       this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
@@ -187,7 +187,7 @@
       var actualHeight = $tip[0].offsetHeight
 
       if (autoPlace) {
-        var orgPlacement = placement
+        var origPlacement = placement
         var $container   = this.options.container ? $(this.options.container) : this.$element.parent()
         var containerDim = this.getPosition($container)
 
@@ -198,8 +198,8 @@
                     placement
 
         $tip
-          .removeClass(orgPlacement)
-          .addClass(placement)
+          .removeClass(this.type + '-' + origPlacement)
+          .addClass(this.type + '-' + placement)
       }
 
       var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
@@ -283,7 +283,7 @@
     var title = this.getTitle()
 
     $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
-    $tip.removeClass('fade in top bottom left right')
+    $tip.removeClass('fade in tooltip-top tooltip-bottom tooltip-left tooltip-right')
   }
 
   Tooltip.prototype.hide = function (callback) {
diff --git a/package.json b/package.json
index 049eec9999b43cbf8a4fee808db6f9b02f92728b..ef03659e8d27f618bca96bd9629daaf6cd76cef7 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,7 @@
   },
   "devDependencies": {
     "btoa": "~1.1.2",
-    "glob": "~4.2.1",
+    "glob": "~4.3.1",
     "grunt": "~0.4.5",
     "grunt-autoprefixer": "~2.0.0",
     "grunt-banner": "~0.2.3",
@@ -42,23 +42,24 @@
     "grunt-contrib-copy": "~0.7.0",
     "grunt-contrib-csslint": "~0.3.1",
     "grunt-contrib-cssmin": "~0.10.0",
-    "grunt-contrib-jade": "~0.13.0",
+    "grunt-contrib-jade": "~0.14.0",
     "grunt-contrib-jshint": "~0.10.0",
     "grunt-contrib-qunit": "~0.5.2",
-    "grunt-contrib-uglify": "~0.6.0",
+    "grunt-contrib-sass": "^0.8.1",
+    "grunt-contrib-uglify": "~0.7.0",
     "grunt-contrib-watch": "~0.6.1",
     "grunt-csscomb": "~3.0.0",
     "grunt-exec": "~0.4.6",
     "grunt-html-validation": "~0.1.18",
     "grunt-jekyll": "~0.4.2",
-    "grunt-jscs": "~1.0.0",
+    "grunt-jscs": "~1.1.0",
     "grunt-sass": "~0.17.0",
-    "grunt-saucelabs": "~8.3.3",
+    "grunt-saucelabs": "~8.4.0",
     "grunt-scss-lint": "^0.3.4",
     "grunt-sed": "~0.1.1",
-    "load-grunt-tasks": "~1.0.0",
-    "npm-shrinkwrap": "5.0.0",
-    "remarkable": "~1.4.2",
+    "load-grunt-tasks": "~2.0.0",
+    "npm-shrinkwrap": "^200.0.0",
+    "remarkable": "~1.6.0",
     "time-grunt": "~1.0.0"
   },
   "engines": {
diff --git a/scss/_alert.scss b/scss/_alert.scss
index 685ca314b115565537513d615a18ad30ee249af4..8ec7d066283b222c5aead2ee80b11a668ea48751 100644
--- a/scss/_alert.scss
+++ b/scss/_alert.scss
@@ -12,17 +12,6 @@
   border: 1px solid transparent;
   @include border-radius($alert-border-radius);
 
-  // Headings for larger alerts
-  h4 {
-    margin-top: 0;
-    // Specified for the h4 to prevent conflicts of changing $headings-color
-    color: inherit;
-  }
-  // Provide class for links that match alerts
-  .alert-link {
-    font-weight: $alert-link-font-weight;
-  }
-
   // Improve alignment and spacing of inner content
   > p,
   > ul {
@@ -33,6 +22,18 @@
   }
 }
 
+// Headings for larger alerts
+.alert-heading {
+  margin-top: 0;
+  // Specified to prevent conflicts of changing $headings-color
+  color: inherit;
+}
+
+// Provide class for links that match alerts
+.alert-link {
+  font-weight: $alert-link-font-weight;
+}
+
 // Dismissible alerts
 //
 // Expand the right padding and account for the close button's positioning.
diff --git a/scss/_forms.scss b/scss/_forms.scss
index 67a4f8020372c9057b49556849dbab77a6a1c406..2d9867d32bd3dc920d7f7845594208232d2449e3 100644
--- a/scss/_forms.scss
+++ b/scss/_forms.scss
@@ -540,14 +540,14 @@ input[type="checkbox"] {
   // Quick utility class for applying `.input-lg` and `.input-sm` styles to the
   // inputs and labels within a `.form-group`.
   .form-group-lg {
-    @media (min-width: $screen-sm-min) {
+    @include media-sm {
       .control-label {
         padding-top: $padding-lg-vertical;
       }
     }
   }
   .form-group-sm {
-    @media (min-width: $screen-sm-min) {
+    @include media-sm {
       .control-label {
         padding-top: ($padding-sm-vertical + .1);
       }
diff --git a/scss/_grid.scss b/scss/_grid.scss
index 6374d77def3e5ef5fc41f1438eb25490227a8b1f..1129cff6d024a926c36ff3179dd5a517c9f7b954 100644
--- a/scss/_grid.scss
+++ b/scss/_grid.scss
@@ -10,14 +10,11 @@
 .container {
   @include make-container();
 
-  @media (min-width: $screen-sm-min) {
-    max-width: $container-sm;
-  }
-  @media (min-width: $screen-md-min) {
-    max-width: $container-md;
-  }
-  @media (min-width: $screen-lg-min) {
-    max-width: $container-lg;
+  // For each breakpoint, define the maximum width of the container in a media query
+  @each $breakpoint, $container-max-width in $container-max-widths {
+    @include media-breakpoint-min($breakpoint) {
+      max-width: $container-max-width;
+    }
   }
 }
 
@@ -47,47 +44,3 @@
 
 @include make-grid-columns();
 
-
-// Extra small grid
-//
-// Columns, offsets, pushes, and pulls for extra small devices like
-// smartphones.
-
-@include make-grid(xs);
-
-
-// Small grid
-//
-// Columns, offsets, pushes, and pulls for the small device range, from phones
-// to tablets.
-
-@include media-sm {
-  @include make-grid(sm);
-}
-
-
-// Medium grid
-//
-// Columns, offsets, pushes, and pulls for the desktop device range.
-
-@include media-md {
-  @include make-grid(md);
-}
-
-
-// Large grid
-//
-// Columns, offsets, pushes, and pulls for the large desktop device range.
-
-@include media-lg {
-  @include make-grid(lg);
-}
-
-
-// Large grid
-//
-// Columns, offsets, pushes, and pulls for the large desktop device range.
-
-@include media-lg {
-  @include make-grid(xl);
-}
diff --git a/scss/_mixins.scss b/scss/_mixins.scss
index d9f0e7a4b3d322894e24ab85d8d4b59d4d9a5a2e..801b5c7e992a937fffded5688f330bb8d543b3a9 100644
--- a/scss/_mixins.scss
+++ b/scss/_mixins.scss
@@ -24,6 +24,7 @@
 }
 
 // Utilities
+@import "mixins/breakpoints";
 @import "mixins/media-queries";
 @import "mixins/hide-text";
 @import "mixins/image";
@@ -57,3 +58,4 @@
 // @import "mixins/navbar-align";
 @import "mixins/grid-framework";
 @import "mixins/grid";
+@import "mixins/pulls";
diff --git a/scss/_modal.scss b/scss/_modal.scss
index 8fb7d201ddb5120b3079a10ec013aa90d0004b88..2566c3ef2de23824f71fcd38713f17208c3737dd 100644
--- a/scss/_modal.scss
+++ b/scss/_modal.scss
@@ -126,7 +126,7 @@
 }
 
 // Scale up the modal
-@media (min-width: $screen-sm-min) {
+@include media-sm {
   // Automatically set modal's width for larger viewports
   .modal-dialog {
     width: $modal-md;
@@ -140,6 +140,6 @@
   .modal-sm { width: $modal-sm; }
 }
 
-@media (min-width: $screen-md-min) {
+@include media-md {
   .modal-lg { width: $modal-lg; }
 }
diff --git a/scss/_navbar.scss b/scss/_navbar.scss
index d4dcb673f08f9e7c8aa44c47ed66fc6ae9a62aec..b163dad9cd9499e3b9e9a2522a0347133dfaa289 100644
--- a/scss/_navbar.scss
+++ b/scss/_navbar.scss
@@ -199,10 +199,10 @@
 
 @include media-sm {
   .navbar-left {
-    @extend .pull-left;
+    @include pull-left();
   }
   .navbar-right {
-    @extend .pull-right;
+    @include pull-right();
     margin-right: -$navbar-padding-horizontal;
 
     ~ .navbar-right {
diff --git a/scss/_popover.scss b/scss/_popover.scss
index 63928a4fce009ff7d72a72f9d7f2915f0d86a578..a614518c7f7efa7c46fa7f893629d2c162e33a65 100644
--- a/scss/_popover.scss
+++ b/scss/_popover.scss
@@ -24,14 +24,14 @@
   border: 1px solid $popover-border-color;
   @include border-radius($border-radius-lg);
   @include box-shadow(0 5px 10px rgba(0,0,0,.2));
-
-  // Offset the popover to account for the popover arrow
-  &.top     { margin-top: -$popover-arrow-width; }
-  &.right   { margin-left: $popover-arrow-width; }
-  &.bottom  { margin-top: $popover-arrow-width; }
-  &.left    { margin-left: -$popover-arrow-width; }
 }
 
+// Offset the popover to account for the popover arrow
+.popover-top     { margin-top: -$popover-arrow-width; }
+.popover-right   { margin-left: $popover-arrow-width; }
+.popover-bottom  { margin-top: $popover-arrow-width; }
+.popover-left    { margin-left: -$popover-arrow-width; }
+
 .popover-title {
   padding: 8px 14px;
   margin: 0; // reset heading margin
@@ -47,9 +47,9 @@
 
 // Arrows
 //
-// .arrow is outer, .arrow:after is inner
+// .popover-arrow is outer, .popover-arrow:after is inner
 
-.popover > .arrow {
+.popover-arrow {
   &,
   &:after {
     position: absolute;
@@ -60,70 +60,68 @@
     border-style: solid;
   }
 }
-.popover > .arrow {
+.popover-arrow {
   border-width: $popover-arrow-outer-width;
 }
-.popover > .arrow:after {
+.popover-arrow:after {
   content: "";
   border-width: $popover-arrow-width;
 }
 
-.popover {
-  &.top > .arrow {
-    bottom: -$popover-arrow-outer-width;
-    left: 50%;
-    margin-left: -$popover-arrow-outer-width;
-    border-top-color: $popover-arrow-outer-color;
+.popover-top > .popover-arrow {
+  bottom: -$popover-arrow-outer-width;
+  left: 50%;
+  margin-left: -$popover-arrow-outer-width;
+  border-top-color: $popover-arrow-outer-color;
+  border-bottom-width: 0;
+  &:after {
+    bottom: 1px;
+    margin-left: -$popover-arrow-width;
+    content: "";
+    border-top-color: $popover-arrow-color;
     border-bottom-width: 0;
-    &:after {
-      bottom: 1px;
-      margin-left: -$popover-arrow-width;
-      content: "";
-      border-top-color: $popover-arrow-color;
-      border-bottom-width: 0;
-    }
   }
-  &.right > .arrow {
-    top: 50%;
-    left: -$popover-arrow-outer-width;
-    margin-top: -$popover-arrow-outer-width;
-    border-right-color: $popover-arrow-outer-color;
+}
+.popover-right > .popover-arrow {
+  top: 50%;
+  left: -$popover-arrow-outer-width;
+  margin-top: -$popover-arrow-outer-width;
+  border-right-color: $popover-arrow-outer-color;
+  border-left-width: 0;
+  &:after {
+    bottom: -$popover-arrow-width;
+    left: 1px;
+    content: "";
+    border-right-color: $popover-arrow-color;
     border-left-width: 0;
-    &:after {
-      bottom: -$popover-arrow-width;
-      left: 1px;
-      content: "";
-      border-right-color: $popover-arrow-color;
-      border-left-width: 0;
-    }
   }
-  &.bottom > .arrow {
-    top: -$popover-arrow-outer-width;
-    left: 50%;
-    margin-left: -$popover-arrow-outer-width;
+}
+.popover-bottom > .popover-arrow {
+  top: -$popover-arrow-outer-width;
+  left: 50%;
+  margin-left: -$popover-arrow-outer-width;
+  border-top-width: 0;
+  border-bottom-color: $popover-arrow-outer-color;
+  &:after {
+    top: 1px;
+    margin-left: -$popover-arrow-width;
+    content: "";
     border-top-width: 0;
-    border-bottom-color: $popover-arrow-outer-color;
-    &:after {
-      top: 1px;
-      margin-left: -$popover-arrow-width;
-      content: "";
-      border-top-width: 0;
-      border-bottom-color: $popover-arrow-color;
-    }
+    border-bottom-color: $popover-arrow-color;
   }
+}
 
-  &.left > .arrow {
-    top: 50%;
-    right: -$popover-arrow-outer-width;
-    margin-top: -$popover-arrow-outer-width;
+.popover-left > .popover-arrow {
+  top: 50%;
+  right: -$popover-arrow-outer-width;
+  margin-top: -$popover-arrow-outer-width;
+  border-right-width: 0;
+  border-left-color: $popover-arrow-outer-color;
+  &:after {
+    right: 1px;
+    bottom: -$popover-arrow-width;
+    content: "";
     border-right-width: 0;
-    border-left-color: $popover-arrow-outer-color;
-    &:after {
-      right: 1px;
-      bottom: -$popover-arrow-width;
-      content: "";
-      border-right-width: 0;
-      border-left-color: $popover-arrow-color;
-    }
+    border-left-color: $popover-arrow-color;
   }
 }
diff --git a/scss/_tooltip.scss b/scss/_tooltip.scss
index 033c19f3b0243dd011929c9b38b8606d468cd376..49af2b28815cc6e5bc27844799fa19e6358a9bf6 100644
--- a/scss/_tooltip.scss
+++ b/scss/_tooltip.scss
@@ -17,26 +17,23 @@
   opacity: 0;
 
   &.in { opacity: $tooltip-opacity; }
+}
 
-  &.top {
-    padding: $tooltip-arrow-width 0;
-    margin-top: -3px;
-  }
-
-  &.right {
-    padding: 0 $tooltip-arrow-width;
-    margin-left: 3px;
-  }
-
-  &.bottom {
-    padding: $tooltip-arrow-width 0;
-    margin-top: 3px;
-  }
-
-  &.left {
-    padding: 0 $tooltip-arrow-width;
-    margin-left: -3px;
-  }
+.tooltip-top {
+  padding: $tooltip-arrow-width 0;
+  margin-top: -3px;
+}
+.tooltip-right {
+  padding: 0 $tooltip-arrow-width;
+  margin-left: 3px;
+}
+.tooltip-bottom {
+  padding: $tooltip-arrow-width 0;
+  margin-top: 3px;
+}
+.tooltip-left {
+  padding: 0 $tooltip-arrow-width;
+  margin-left: -3px;
 }
 
 // Wrapper for the tooltip content
@@ -58,62 +55,31 @@
   border-color: transparent;
   border-style: solid;
 }
-// Note: Deprecated .top-left, .top-right, .bottom-left, and .bottom-right as of v3.3.1
-.tooltip {
-  &.top .tooltip-arrow {
-    bottom: 0;
-    left: 50%;
-    margin-left: -$tooltip-arrow-width;
-    border-width: $tooltip-arrow-width $tooltip-arrow-width 0;
-    border-top-color: $tooltip-arrow-color;
-  }
-  &.top-left .tooltip-arrow {
-    right: $tooltip-arrow-width;
-    bottom: 0;
-    margin-bottom: -$tooltip-arrow-width;
-    border-width: $tooltip-arrow-width $tooltip-arrow-width 0;
-    border-top-color: $tooltip-arrow-color;
-  }
-  &.top-right .tooltip-arrow {
-    bottom: 0;
-    left: $tooltip-arrow-width;
-    margin-bottom: -$tooltip-arrow-width;
-    border-width: $tooltip-arrow-width $tooltip-arrow-width 0;
-    border-top-color: $tooltip-arrow-color;
-  }
-  &.right .tooltip-arrow {
-    top: 50%;
-    left: 0;
-    margin-top: -$tooltip-arrow-width;
-    border-width: $tooltip-arrow-width $tooltip-arrow-width $tooltip-arrow-width 0;
-    border-right-color: $tooltip-arrow-color;
-  }
-  &.left .tooltip-arrow {
-    top: 50%;
-    right: 0;
-    margin-top: -$tooltip-arrow-width;
-    border-width: $tooltip-arrow-width 0 $tooltip-arrow-width $tooltip-arrow-width;
-    border-left-color: $tooltip-arrow-color;
-  }
-  &.bottom .tooltip-arrow {
-    top: 0;
-    left: 50%;
-    margin-left: -$tooltip-arrow-width;
-    border-width: 0 $tooltip-arrow-width $tooltip-arrow-width;
-    border-bottom-color: $tooltip-arrow-color;
-  }
-  &.bottom-left .tooltip-arrow {
-    top: 0;
-    right: $tooltip-arrow-width;
-    margin-top: -$tooltip-arrow-width;
-    border-width: 0 $tooltip-arrow-width $tooltip-arrow-width;
-    border-bottom-color: $tooltip-arrow-color;
-  }
-  &.bottom-right .tooltip-arrow {
-    top: 0;
-    left: $tooltip-arrow-width;
-    margin-top: -$tooltip-arrow-width;
-    border-width: 0 $tooltip-arrow-width $tooltip-arrow-width;
-    border-bottom-color: $tooltip-arrow-color;
-  }
+.tooltip-top .tooltip-arrow {
+  bottom: 0;
+  left: 50%;
+  margin-left: -$tooltip-arrow-width;
+  border-width: $tooltip-arrow-width $tooltip-arrow-width 0;
+  border-top-color: $tooltip-arrow-color;
+}
+.tooltip-right .tooltip-arrow {
+  top: 50%;
+  left: 0;
+  margin-top: -$tooltip-arrow-width;
+  border-width: $tooltip-arrow-width $tooltip-arrow-width $tooltip-arrow-width 0;
+  border-right-color: $tooltip-arrow-color;
+}
+.tooltip-left .tooltip-arrow {
+  top: 50%;
+  right: 0;
+  margin-top: -$tooltip-arrow-width;
+  border-width: $tooltip-arrow-width 0 $tooltip-arrow-width $tooltip-arrow-width;
+  border-left-color: $tooltip-arrow-color;
+}
+.tooltip-bottom .tooltip-arrow {
+  top: 0;
+  left: 50%;
+  margin-left: -$tooltip-arrow-width;
+  border-width: 0 $tooltip-arrow-width $tooltip-arrow-width;
+  border-bottom-color: $tooltip-arrow-color;
 }
diff --git a/scss/_type.scss b/scss/_type.scss
index 395417324500149ff16b7866a1ef2e0cca9cb3f0..5bbbf5873e2f2f5302558aef1b2b05a03973b60c 100644
--- a/scss/_type.scss
+++ b/scss/_type.scss
@@ -116,7 +116,7 @@ ol {
 
 // Inline turns list items into inline-block
 .list-inline {
-  @extend list-unstyled();
+  @extend .list-unstyled;
   margin-left: -5px;
 
   > li {
diff --git a/scss/_utilities.scss b/scss/_utilities.scss
index aeffdcd78167caff6d20ac4788efe233097de015..5e297fc75eb0f28f18515be5c3d0a76ca0e6fac4 100644
--- a/scss/_utilities.scss
+++ b/scss/_utilities.scss
@@ -10,11 +10,12 @@
 .center-block {
   @include center-block();
 }
+
 .pull-right {
-  float: right !important;
+  @include pull-right();
 }
 .pull-left {
-  float: left !important;
+  @include pull-left();
 }
 
 
diff --git a/scss/_variables.scss b/scss/_variables.scss
index 24271131431d2e6432cf874da6bccbfbebdb3ca4..4b515d3a4c1bf6fb5dbe2947b015054337903f2f 100644
--- a/scss/_variables.scss
+++ b/scss/_variables.scss
@@ -272,29 +272,22 @@ $zindex-modal:             1040 !default;
 //
 //## Define the minimum and maximum dimensions at which your layout will change, adapting to different screen sizes.
 
-// Extra large screen / wide desktop
-$screen-xl-min:              75em !default;
-
-// Large screen / desktop
-$screen-lg-max:              ($screen-xl-min - .1) !default;
-$screen-lg-min:              62em !default;
-
-// Medium screen / tablet
-$screen-md-max:              ($screen-lg-min - .1) !default;
-$screen-md-min:              48em !default;
-
-// Small screen / phone
-$screen-sm-max:              ($screen-md-min - .1) !default;
-$screen-sm-min:              34em !default;
-
-// Extra small screen / phone
-$screen-xs-max:              ($screen-sm-min - .1) !default;
-
-
 //== Grid system
 //
 //## Define your custom responsive grid.
-$grid-breakpoints: (xs sm md lg xl);
+$grid-breakpoints: (
+  // Extra small screen / phone
+  xs: 0,
+  // Small screen / phone
+  sm: 34em,
+  // Medium screen / tablet
+  md: 48em,
+  // Large screen / desktop
+  lg: 62em,
+  // Extra large screen / wide desktop
+  xl: 75em
+) !default;
+
 //** Number of columns in the grid.
 $grid-columns:               12 !default;
 //** Padding between columns. Gets divided in half for the left and right.
@@ -305,17 +298,12 @@ $grid-gutter-width:          1.5rem !default;
 //
 //## Define the maximum width of `.container` for different screen sizes.
 
-//** For `$screen-xs-min` and up.
-$container-sm:               34em !default; // 480
-
-//** For `$screen-sm-min` and up.
-$container-md:               45rem !default; // 720
-
-//** For `$screen-md-min` and up.
-$container-lg:               60rem !default; // 960
-
-//** For `$screen-lg-min` and up.
-$container-xl:               72.25rem !default; // 1140
+$container-max-widths: (
+  sm: 34rem,    // 480
+  md: 45rem,    // 720
+  lg: 60rem,    // 960
+  xl: 72.25rem  // 1140
+) !default;
 
 
 //== Navbar
diff --git a/scss/mixins/_breakpoints.scss b/scss/mixins/_breakpoints.scss
new file mode 100644
index 0000000000000000000000000000000000000000..71a15cd0430a21def001094952151d7574c2501b
--- /dev/null
+++ b/scss/mixins/_breakpoints.scss
@@ -0,0 +1,85 @@
+// Breakpoint viewport sizes and media queries.
+//
+// Breakpoints are defined as a map of (name: minimum width), order from small to large:
+//
+//    (xs: 0, sm: 34rem, md: 45rem)
+//
+// The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default.
+
+// Name of the next breakpoint, or null for the last breakpoint.
+//
+//    >> breakpoint-next(sm)
+//    md
+//    >> breakpoint-next(sm, $breakpoints: (xs: 0, sm: 34rem, md: 45rem))
+//    md
+//    >> breakpoint-next(sm, $breakpoint-names: (xs sm md))
+//    md
+@function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) {
+  $n: index($breakpoint-names, $name);
+  @return if($n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null);
+}
+
+// Minimum breakpoint width. Null for the smallest (first) breakpoint.
+//
+//    >> breakpoint-min(sm, (xs: 0, sm: 34rem, md: 45rem))
+//    34rem
+@function breakpoint-min($name, $breakpoints: $grid-breakpoints) {
+  $min: map-get($breakpoints, $name);
+  @return if($min != 0, $min, null);
+}
+
+// Maximum breakpoint width. Null for the largest (last) breakpoint.
+// The maximum value is calculated as the minimum of the next one less 0.1.
+//
+//    >> breakpoint-max(sm, (xs: 0, sm: 34rem, md: 45rem))
+//    44.9rem
+@function breakpoint-max($name, $breakpoints: $grid-breakpoints) {
+  $next: breakpoint-next($name, $breakpoints);
+  @return if($next, breakpoint-min($next, $breakpoints) - 0.1, null);
+}
+
+// Media of at least the minimum breakpoint width. No query for the smallest breakpoint.
+@mixin media-breakpoint-min($name, $breakpoints: $grid-breakpoints) {
+  $min: breakpoint-min($name, $breakpoints);
+  @if $min {
+    @media (min-width: $min) {
+      @content;
+    }
+  } @else {
+    @content;
+  }
+}
+
+// Media of at most the maximum breakpoint width. No query for the largest breakpoint.
+@mixin media-breakpoint-max($name, $breakpoints: $grid-breakpoints) {
+  $max: breakpoint-max($name, $breakpoints);
+  @if $max {
+    @media (max-width: $max) {
+      @content;
+    }
+  } @else {
+    @content;
+  }
+}
+
+// Media between the breakpoint's minimum and maximum widths.
+// No minimum for the smallest breakpoint, and no maximum for the largest one.
+@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) {
+  // Nested media query combination does not work in libsass yet
+  // https://github.com/sass/libsass/issues/185
+  // Work around until the issue is resolved:
+  $min: breakpoint-min($name, $breakpoints);
+  $max: breakpoint-max($name, $breakpoints);
+  @if $min and $max {
+    @media (min-width: $min) and (max-width: $max) {
+      @content;
+    }
+  } @else {
+    // One of min or max is a no-op, so this branch is not affected by libsass#185
+    @include media-breakpoint-min($name, $breakpoints) {
+      @include media-breakpoint-max($name, $breakpoints) {
+        @content;
+      }
+    }
+  }
+}
diff --git a/scss/mixins/_grid-framework.scss b/scss/mixins/_grid-framework.scss
index 3eecbae8b27788c6edcc8c640757ac9d5b7c1409..0d346db57d81c987cb334869b581f61dc5d0db95 100644
--- a/scss/mixins/_grid-framework.scss
+++ b/scss/mixins/_grid-framework.scss
@@ -3,69 +3,40 @@
 // Used only by Bootstrap to generate the correct number of grid classes given
 // any value of `$grid-columns`.
 
-// Common properties for all breakpoints
-@mixin make-grid-columns($columns: $grid-columns, $breakpoints: $grid-breakpoints) {
+@mixin make-grid-columns($columns: $grid-columns, $gutter: $grid-gutter-width, $breakpoints: $grid-breakpoints) {
+  // Common properties for all breakpoints
   %grid-column {
     position: relative;
     // Prevent columns from collapsing when empty
     min-height: 1px;
     // Inner gutter via padding
-    padding-left: ($grid-gutter-width / 2);
-    padding-right: ($grid-gutter-width / 2);
+    padding-left: ($gutter / 2);
+    padding-right: ($gutter / 2);
   }
-  @for $i from 1 through $columns {
-    @each $breakpoint in $breakpoints {
+  @each $breakpoint in map-keys($breakpoints) {
+    @for $i from 1 through $columns {
       .col-#{$breakpoint}-#{$i} {
         @extend %grid-column;
       }
     }
-  }
-}
-
-// Breakpoint-specific properties
-@mixin make-grid($breakpoint, $columns: $grid-columns) {
-  // Work around cross-media @extend (https://github.com/sass/sass/issues/1050)
-  %grid-column-float-#{$breakpoint} {
-    float: left;
-  }
-  @for $i from 1 through $columns {
-    .col-#{$breakpoint}-#{$i} {
-      @extend %grid-column-float-#{$breakpoint};
-      @include grid-column-width($i, $columns);
-    }
-  }
-  @each $modifier in (pull, push, offset) {
-    @for $i from 0 through $columns {
-      .col-#{$breakpoint}-#{$modifier}-#{$i} {
-        @include grid-column-modifier($modifier, $i, $columns)
+    @include media-breakpoint-min($breakpoint) {
+      // Work around cross-media @extend (https://github.com/sass/sass/issues/1050)
+      %grid-column-float-#{$breakpoint} {
+        float: left;
+      }
+      @for $i from 1 through $columns {
+        .col-#{$breakpoint}-#{$i} {
+          @extend %grid-column-float-#{$breakpoint};
+          @include make-col-span($i, $columns);
+        }
+      }
+      @each $modifier in (pull, push, offset) {
+        @for $i from 0 through $columns {
+          .col-#{$breakpoint}-#{$modifier}-#{$i} {
+            @include make-col-modifier($modifier, $i, $columns)
+          }
+        }
       }
     }
   }
 }
-
-@mixin grid-column-width($index, $columns) {
-  width: percentage($index / $columns);
-}
-
-@mixin grid-column-push($index, $columns) {
-  left: if($index > 0, percentage($index / $columns), auto);
-}
-
-@mixin grid-column-pull($index, $columns) {
-  right: if($index > 0, percentage($index / $columns), auto);
-}
-
-@mixin grid-column-offset($index, $columns) {
-  margin-left: percentage($index / $columns);
-}
-
-// Work around the lack of dynamic mixin @include support (https://github.com/sass/sass/issues/626)
-@mixin grid-column-modifier($type, $index, $columns) {
-  @if $type == push {
-    @include grid-column-push($index, $columns);
-  } @else if $type == pull {
-    @include grid-column-pull($index, $columns);
-  } @else if $type == offset {
-    @include grid-column-offset($index, $columns);
-  }
-}
diff --git a/scss/mixins/_grid.scss b/scss/mixins/_grid.scss
index aceaeeb4c469bfdca7b54b5fd5e16fe399acd78c..29af269aacdd22f2d9cae0a0335edc0fe48ec7c0 100644
--- a/scss/mixins/_grid.scss
+++ b/scss/mixins/_grid.scss
@@ -24,18 +24,29 @@
   padding-right: ($gutter / 2);
 }
 
-@mixin make-col-span($columns) {
-  width: percentage(($columns / $grid-columns));
+@mixin make-col-span($size, $columns: $grid-columns) {
+  width: percentage($size / $columns);
 }
 
-@mixin make-col-offset($columns) {
-  margin-left: percentage(($columns / $grid-columns));
+@mixin make-col-offset($size, $columns: $grid-columns) {
+  margin-left: percentage($size / $columns);
 }
 
-@mixin make-col-push($columns) {
-  left: percentage(($columns / $grid-columns));
+@mixin make-col-push($size, $columns: $grid-columns) {
+  left: if($size > 0, percentage($size / $columns), auto);
 }
 
-@mixin make-col-pull($columns) {
-  right: percentage(($columns / $grid-columns));
+@mixin make-col-pull($size, $columns: $grid-columns) {
+  right: if($size > 0, percentage($size / $columns), auto);
+}
+
+@mixin make-col-modifier($type, $size, $columns) {
+  // Work around the lack of dynamic mixin @include support (https://github.com/sass/sass/issues/626)
+  @if $type == push {
+    @include make-col-push($size, $columns);
+  } @else if $type == pull {
+    @include make-col-pull($size, $columns);
+  } @else if $type == offset {
+    @include make-col-offset($size, $columns);
+  }
 }
diff --git a/scss/mixins/_media-queries.scss b/scss/mixins/_media-queries.scss
index b4e16ed888fee19a949c9a45b80bb89c7e3f67cd..28130d2e7ea25f4b98ef5541a3a2da771be2729e 100644
--- a/scss/mixins/_media-queries.scss
+++ b/scss/mixins/_media-queries.scss
@@ -1,25 +1,25 @@
-// Media query mixins
+// Media query mixins for default breakpoints
 
 @mixin media-xs() {
-  @media (max-width: $screen-xs-max) { @content }
+  @include media-breakpoint-max(xs) { @content }
 }
 
 @mixin media-sm() {
-  @media (min-width: $screen-sm-min) { @content }
+  @include media-breakpoint-min(sm) { @content }
 }
 
 @mixin media-sm-max() {
-  @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { @content }
+  @include media-breakpoint-only(sm) { @content }
 }
 
 @mixin media-md() {
-  @media (min-width: $screen-md-min) { @content }
+  @include media-breakpoint-min(md) { @content }
 }
 
 @mixin media-md-max() {
-  @media (min-width: $screen-md-min) and (max-width: $screen-md-max) { @content }
+  @include media-breakpoint-only(md) { @content }
 }
 
 @mixin media-lg() {
-  @media (min-width: $screen-lg-min) { @content }
+  @include media-breakpoint-min(lg) { @content }
 }
diff --git a/scss/mixins/_pulls.scss b/scss/mixins/_pulls.scss
new file mode 100644
index 0000000000000000000000000000000000000000..6bdff025d43cf858086b0eefb99f8febce118c8e
--- /dev/null
+++ b/scss/mixins/_pulls.scss
@@ -0,0 +1,6 @@
+@mixin pull-left {
+  float: left !important;
+}
+@mixin pull-right {
+  float: right !important;
+}
diff --git a/test-infra/S3Cachefile.json b/test-infra/S3Cachefile.json
index 04d809246774818776aac7687b3e2435574d9d62..90eae796dc7edc75d6ad20b67709b8c3d7b4d020 100644
--- a/test-infra/S3Cachefile.json
+++ b/test-infra/S3Cachefile.json
@@ -5,8 +5,8 @@
         "generate": "./uncached-npm-install.sh"
     },
     "rubygems": {
-        "key": "../pseudo_Gemfile.lock",
-        "cache": "$GEMDIR",
-        "generate": "gem install -N scss-lint -v $SCSS_LINT_VERSION && gem install -N jekyll -v $JEKYLL_VERSION && gem install -N rouge -v $ROUGE_VERSION"
+        "key": "$BUNDLE_GEMFILE",
+        "cache": "../vendor/cache",
+        "generate": "cd .. ; bundle install --path=\"`pwd`/vendor/cache\""
     }
 }
diff --git a/test-infra/gemfiles/core.gemfile b/test-infra/gemfiles/core.gemfile
new file mode 100644
index 0000000000000000000000000000000000000000..6a837269193ac9be705cafe9f383523dc991cb36
--- /dev/null
+++ b/test-infra/gemfiles/core.gemfile
@@ -0,0 +1,7 @@
+# Ruby Gems for the 'core' set of tests
+# Run `grunt update-gemfile-lock` to update to the latest compatible versions
+
+source 'https://rubygems.org'
+
+gem 'sass', '~> 3.4.9'
+gem 'scss-lint', '~> 0.31'
diff --git a/test-infra/gemfiles/core.gemfile.lock b/test-infra/gemfiles/core.gemfile.lock
new file mode 100644
index 0000000000000000000000000000000000000000..03580d83bb17bd933efabcdabad6341dd24cfda4
--- /dev/null
+++ b/test-infra/gemfiles/core.gemfile.lock
@@ -0,0 +1,15 @@
+GEM
+  remote: https://rubygems.org/
+  specs:
+    rainbow (2.0.0)
+    sass (3.4.9)
+    scss-lint (0.31.0)
+      rainbow (~> 2.0)
+      sass (~> 3.4.1)
+
+PLATFORMS
+  ruby
+
+DEPENDENCIES
+  sass (~> 3.4.9)
+  scss-lint (~> 0.31)