diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..7b1c8ed
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,3 @@
+Enable custom OpenAPI spec extensions for rendering examples
+Copy spec to target directory and link
+Implement company logo
diff --git a/app/helpers/schemaRange.js b/app/helpers/schemaRange.js
index 815fe1b..3be003b 100644
--- a/app/helpers/schemaRange.js
+++ b/app/helpers/schemaRange.js
@@ -1,3 +1,5 @@
+var util = require("util");
+
/**
*
* @param range a json-schema object with minimum, maximum, exclusiveMinimum, exclusiveMaximum
diff --git a/app/javascripts/main.js b/app/javascripts/main.js
index dbb2322..f4b3f23 100644
--- a/app/javascripts/main.js
+++ b/app/javascripts/main.js
@@ -1,16 +1,52 @@
$(function() {
- var $sidebar = $('#sidebar');
- var $nav = $sidebar.find('nav');
- var traverse = new Traverse($nav, {
- threshold: 10,
- barOffset: $sidebar.position().top
- });
+ // $(document).foundation();
- $nav.on('update.traverse', function(event, element) {
- $nav.find('section').removeClass('expand');
- var $section = element.parents('section:first');
- if ($section.length) {
- $section.addClass('expand');
- }
- });
+ var $sidebar = $('#sidebar');
+ if ($sidebar.length) {
+ var $docs = $('#docs');
+ var $nav = $sidebar.find('nav');
+
+ //
+ // Setup sidebar navigation
+ var traverse = new Traverse($nav, {
+ threshold: 10,
+ barOffset: $sidebar.position().top
+ });
+
+ $nav.on('update.traverse', function(event, element) {
+ $nav.find('section').removeClass('expand');
+ var $section = element.parents('section:first');
+ if ($section.length) {
+ $section.addClass('expand');
+ }
+ });
+
+ //
+ // Bind the drawer layout
+ var $drawerLayout = $('.drawer-layout'),
+ $drawer = $drawerLayout.find('.drawer'),
+ closeDrawer = function() {
+ $drawer.removeClass('slide-right slide-left');
+ $drawer.find('.drawer-overlay').remove();
+ $drawerLayout.removeClass('drawer-open drawer-slide-left-large drawer-slide-right-large');
+ return false;
+ };
+
+ // Drawer open buttons
+ $drawerLayout.find('[data-drawer-slide]').click(function(e) {
+ var $this = $(this),
+ direction = $this.data('drawer-slide');
+ $drawerLayout.addClass('drawer-open');
+ $drawer.addClass('slide-' + direction);
+
+ var $overlay = $('')
+ $drawer.append($overlay);
+ $overlay.click(closeDrawer);
+
+ return false;
+ });
+
+ // Drawer close buttons
+ $drawerLayout.find('[data-drawer-close]').click(closeDrawer);
+ }
});
diff --git a/app/lib/common.js b/app/lib/common.js
index 38569ae..340da4a 100644
--- a/app/lib/common.js
+++ b/app/lib/common.js
@@ -45,7 +45,7 @@ var common = {
// Add an extra CRLR before the code so the postprocessor can determine
// the correct line indent for the
tag.
- var $ = cheerio.load(marked("```json\r\n\r\n" + schemaString + "\n```"));
+ var $ = cheerio.load(marked("```json\r\n" + schemaString + "\n```"));
var definitions = $('span:not(:has(span)):contains("#/definitions/")');
definitions.each(function(index, item) {
var ref = $(item).html();
diff --git a/app/lib/config.js b/app/lib/config.js
index 1723830..ed78226 100644
--- a/app/lib/config.js
+++ b/app/lib/config.js
@@ -1,19 +1,34 @@
+var path = require('path');
+
module.exports = function(grunt, options, spec) {
return {
// Compile SCSS source files into the cache directory
compass: {
- dist: {
+ scss: {
options: {
- sassDir: options.appDir + '/stylesheets',
+ specify: options.appDir + '/stylesheets/spectacle.scss',
+ sassDir: path.resolve(options.appDir + '/stylesheets'),
cssDir: options.cacheDir + '/stylesheets',
environment: 'development',
- // outputStyle: 'compressed',
importPath: [
- 'node_modules/foundation-sites/scss'
+ options.appDir + '/vendor',
+ options.appDir + '/vendor/foundation/scss'
]
}
- }
+ },
+ foundation_scss: {
+ options: {
+ specify: path.resolve(options.appDir + '/stylesheets/foundation.scss'),
+ sassDir: path.resolve(options.appDir + '/stylesheets'),
+ cssDir: options.cacheDir + '/stylesheets',
+ environment: 'development',
+ importPath: [
+ options.appDir + '/vendor',
+ options.appDir + '/vendor/foundation/scss'
+ ]
+ }
+ },
},
concat: {
@@ -26,17 +41,39 @@ module.exports = function(grunt, options, spec) {
// Concentrate compiled CSS files into the traget directory
css: {
- src: [options.cacheDir + '/stylesheets/*.css'],
+ src: [options.cacheDir + '/stylesheets/spectacle.css'],
dest: options.targetDir + '/stylesheets/spectacle.css',
+ },
+
+ // // Concentrate required Foundation JS files into the traget directory
+ // foundation_js: {
+ // src: [
+ // options.appDir + '/vendor/foundation/js/foundation.core.js',
+ // options.appDir + '/vendor/foundation/js/foundation.util.mediaQuery.js',
+ // options.appDir + '/vendor/foundation/js/foundation.util.triggers.js',
+ // options.appDir + '/vendor/foundation/js/foundation.util.motion.js',
+ // options.appDir + '/vendor/foundation/js/foundation.offcanvas.js'
+ // ],
+ // dest: options.targetDir + '/javascripts/foundation.js',
+ // },
+
+ // Concentrate compiled Foundation CSS files into the traget directory
+ foundation_css: {
+ src: [options.cacheDir + '/stylesheets/foundation.css'],
+ dest: options.targetDir + '/stylesheets/foundation.css',
}
},
// Minify compiled JS files in the traget directory
uglify: {
- build: {
+ js: {
src: options.targetDir + '/javascripts/spectacle.js',
dest: options.targetDir + '/javascripts/spectacle.min.js'
}
+ // foundation_js: {
+ // src: options.targetDir + '/javascripts/foundation.js',
+ // dest: options.targetDir + '/javascripts/foundation.min.js'
+ // }
},
// Minify compiled CSS files in the traget directory
@@ -77,7 +114,7 @@ module.exports = function(grunt, options, spec) {
hostname: '*',
port: options.port,
base: options.targetDir,
- livereload: true
+ // livereload: true
}
}
},
@@ -85,23 +122,28 @@ module.exports = function(grunt, options, spec) {
// Watch the filesystem and regenerate docs if sources change
watch: {
options: {
- livereload: true
+ // livereload: true,
+ spawn: false
},
+ // options: {
+ // livereload: true
+ // },
js: {
files: [options.appDir + '/javascripts/**/*.js'],
- // tasks: ['javascripts']
+ tasks: ['javascripts']
},
css: {
files: [options.appDir + '/stylesheets/**/*.scss'],
- // tasks: ['stylesheets']
+ tasks: ['stylesheets']
},
templates: {
files: [
+ options.specFile,
options.appDir + '/views/**/*.hbs',
options.appDir + '/helpers/**/*.js',
options.appDir + '/lib/**/*.js'
],
- // tasks: ['templates']
+ tasks: ['templates']
}
}
}
diff --git a/app/lib/preprocessor.js b/app/lib/preprocessor.js
index f261b73..a729bfe 100644
--- a/app/lib/preprocessor.js
+++ b/app/lib/preprocessor.js
@@ -54,7 +54,7 @@ module.exports = function(swaggerJson) {
return true
})
// Show body section, if either a body-parameter or a consumes-property is present.
- operation._show_requst_body_section = operation._request_body || operation.consumes
+ operation._show_requst_body_section = !!operation._request_body //|| operation.consumes
})
})
// If there are multiple tags, we show the tag-based summary
diff --git a/app/stylesheets/_drawer.scss b/app/stylesheets/_drawer.scss
new file mode 100644
index 0000000..a209248
--- /dev/null
+++ b/app/stylesheets/_drawer.scss
@@ -0,0 +1,80 @@
+//
+//= Drawer layout
+//
+
+.drawer-layout {
+ .drawer {
+ box-shadow: 0 0 10px rgba($black, 0.5);
+ transition: transform $default-transition-length ease;
+ backface-visibility: hidden;
+
+ &.slide-left {
+ transform: translateX(-250px);
+ }
+ &.slide-right {
+ transform: translateX(250px);
+ }
+ .drawer-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba($white, 0.25);
+ }
+
+ @include breakpoint(large) {
+ &.slide-left {
+ transform: none;
+ margin-left: -250px;
+ }
+ &.slide-right {
+ transform: none;
+ margin-left: 250px;
+ }
+ .drawer-overlay {
+ display: none;
+ }
+ }
+ }
+
+ @include breakpoint(large) {
+ &.drawer-slide-left-large {
+ .drawer {
+ margin-left: -250px;
+ }
+ .floating-menu-icon {
+ opacity: 0;
+ }
+ }
+ &.drawer-slide-right-large {
+ .drawer {
+ margin-left: 250px;
+ }
+ .floating-menu-icon {
+ opacity: 0;
+ }
+ }
+ }
+
+ &.drawer-open {
+ .floating-menu-icon {
+ opacity: 0;
+ }
+ }
+
+ .floating-menu-icon {
+ position: fixed;
+ top: 0.75rem;
+ right: 0.75rem;
+ background-color: rgba($black, 0.75);
+ padding: 0.65rem;
+ z-index: 1;
+ border-radius: 5px;
+ transition: opacity $default-transition-length linear;
+
+ .hamburger {
+ @include hamburger($color: $white, $color-hover: $medium-gray);
+ }
+ }
+}
diff --git a/app/stylesheets/_foundation-includes.scss b/app/stylesheets/_foundation-includes.scss
deleted file mode 100644
index ec4c119..0000000
--- a/app/stylesheets/_foundation-includes.scss
+++ /dev/null
@@ -1,47 +0,0 @@
-//
-//= Include Foundation components
-
-@import 'foundation';
-
-// TODO: Conditionally include `foundation-global-styles`.
-// It's better if we don't have ANY global includes.
-@include foundation-global-styles;
-
-#spectacle {
-
- @include foundation-grid;
- //@include foundation-flex-grid;
- @include foundation-typography;
- @include foundation-button;
- @include foundation-forms;
- @include foundation-visibility-classes;
- @include foundation-float-classes;
- // @include foundation-accordion;
- // @include foundation-accordion-menu;
- // @include foundation-badge;
- // @include foundation-breadcrumbs;
- // @include foundation-button-group;
- // @include foundation-callout;
- // @include foundation-close-button;
- // @include foundation-drilldown-menu;
- // @include foundation-dropdown;
- // @include foundation-dropdown-menu;
- // @include foundation-flex-video;
- @include foundation-label;
- // @include foundation-media-object;
- @include foundation-menu;
- // @include foundation-off-canvas;
- // @include foundation-orbit;
- // @include foundation-pagination;
- // @include foundation-progress-bar;
- // @include foundation-slider;
- // @include foundation-sticky;
- // @include foundation-reveal;
- // @include foundation-switch;
- @include foundation-table;
- // @include foundation-tabs;
- // @include foundation-thumbnail;
- // @include foundation-title-bar;
- // @include foundation-tooltip;
- @include foundation-top-bar;
-}
diff --git a/app/stylesheets/_foundation-settings.scss b/app/stylesheets/_foundation-settings.scss
index 4a68a07..4dc130d 100644
--- a/app/stylesheets/_foundation-settings.scss
+++ b/app/stylesheets/_foundation-settings.scss
@@ -519,9 +519,9 @@ $thumbnail-radius: $global-radius;
// 33. Title Bar
// -------------
-$titlebar-background: $black;
+$titlebar-background: scale-color($black, $lightness: -10%);
$titlebar-color: $white;
-$titlebar-padding: 0.5rem;
+$titlebar-padding: 0.75rem 1.25rem; // 0.5rem
$titlebar-text-font-weight: bold;
$titlebar-icon-color: $white;
$titlebar-icon-color-hover: $medium-gray;
diff --git a/app/stylesheets/_index.scss b/app/stylesheets/_index.scss
index 6f0052d..75da8a7 100644
--- a/app/stylesheets/_index.scss
+++ b/app/stylesheets/_index.scss
@@ -2,15 +2,12 @@
//= Main includes
//
+// Scope everything inside #spectacle ...
#spectacle {
-
- // Include highlight.js style
- @import 'monokai';
-
- // Include Spectacle layout
@import 'variables';
@import 'utils';
@import 'schema';
@import 'layout';
-
+ @import 'drawer';
+ @import 'monokai';
}
diff --git a/app/stylesheets/_layout.scss b/app/stylesheets/_layout.scss
index a25902c..c6b17f5 100644
--- a/app/stylesheets/_layout.scss
+++ b/app/stylesheets/_layout.scss
@@ -13,65 +13,67 @@
margin: 0 !important;
}
+button:focus {
+ outline: none;
+}
+
.default-label {
- @include small-label('secondary');
+ @include small-label($secondary-color);
}
//
// Topbar layout
-.top-bar {
- padding: 0.5rem;
- height: $topbar-height;
- color: $white;
-
- // Make it fixed
- position: fixed;
- left: 0;
- right: 0;
- top: 0;
- z-index: 1;
-}
+// .top-bar {
+// padding: 0.5rem;
+// height: $topbar-height;
+// color: $white;
+//
+// // Make it fixed
+// position: fixed;
+// left: 0;
+// right: 0;
+// top: 0;
+// z-index: 1;
+// }
+//
#logo {
- span {
- opacity: 0.5;
- margin-left: 5px;
- }
}
-//
-// Page layout
-
-#page {
- margin-top: $topbar-height;
-}
+// Include the Foundation grid (the only required component)
+@include foundation-grid();
//
// Sidebar layout
#sidebar {
- padding-top: 1rem;
- padding-left: 1rem;
+ padding-top: 1.5rem;
+ padding-left: 1.5rem;
padding-right: 1rem;
padding-bottom: 2rem;
border-right: 1px solid #eee;
- background-color: #f6f6f6 !important;
+ background-color: #f6f6f6;
height: 100vh;
- //height: 100%;
- overflow: scroll;
+ overflow: auto;
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ top: 0;
+ width: $drawer-size;
h5 {
- margin: 1.25rem 0 0.5rem;
+ margin: 1.5rem 0 0.75rem;
text-transform: uppercase;
color: $medium-gray;
+ color: $accent-color;
font-size: 0.9rem;
}
a {
display: block;
- font-size: 0.95rem;
- margin: 0 0 0.175rem;
+ // font-size: 0.95rem;
+ margin: 0 0 0.25rem;
color: scale-color($black, $lightness: 15%);
white-space: nowrap;
overflow: hidden;
@@ -79,9 +81,9 @@
text-overflow: ellipsis;
&.active {
- // color: $primary-color;
- color: $accent-color;
- font-weight: bold;
+ color: $accent-color; //$primary-color;
+ color: $primary-color;
+ // font-weight: bold;
}
}
@@ -102,23 +104,19 @@
}
}
}
-}
-@media screen and (min-width: $medium-width) { // medium and up
- #sidebar {
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- top: $topbar-height;
+ .close-button {
+ opacity: 0.5;
}
}
+
//
// Documentation layout
.doc-content {
@extend .large-6;
+ // @include grid-column(6, $column-padding);
padding-left: $column-padding !important;
padding-right: $column-padding !important;
}
@@ -131,27 +129,31 @@
}
#docs {
+ background: $white;
overflow: hidden;
+ position: relative;
.example-box {
display: none;
}
- @media screen and (min-width: $large-width) { // medium and up
+
+ @include breakpoint(large) {
.example-box {
// width: 50%;
display: block;
- background-color: #2E2F28;
- background-color: #23241f;
+ // background-color: #2E2F28;
+ background-color: $black;
position: absolute;
right: 0;
top: 0;
bottom: 0;
- z-index: -1;
}
}
}
article {
+ position: relative;
+
.no-description {
color: $dark-gray;
}
@@ -173,15 +175,10 @@ article {
margin-bottom: 0;
}
- #introduction {
- padding-top: 2rem; // 30px
+ .tag-description {
+ // margin-bottom: 0 !important;
}
- // > h1:first-child {
- // margin-top: 0;
- // margin-bottom: 2.5rem;
- // }
-
//
// Titles
@@ -193,6 +190,7 @@ article {
padding-right: $column-padding;
border-top: 1px solid #e8e8e8;
border-bottom: 1px solid #e2e2e2;
+ background-color: #f6f6f6;
// border-top: 1px solid #ddd;
// border-bottom: 1px solid #ccc;
// background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgi…pZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA==');
@@ -201,7 +199,24 @@ article {
// background-image: -moz-linear-gradient(top, #fff, #f9f9f9);
// background-image: -webkit-linear-gradient(top, #fff, #f9f9f9);
// background-image: linear-gradient(to bottom, #fff, #f9f9f9);
- background-color: #f6f6f6 !important;
+ }
+
+ h1.doc-title {
+ @extend .doc-content;
+ margin: 0;
+ padding-top: 2.15rem;
+ padding-bottom: 0;
+ font-weight: bold;
+ background: transparent;
+ border: none;
+ color: scale-color($black, $lightness: 20%);
+
+ span {
+ display: none;
+ opacity: 0.65;
+ margin-left: 5px;
+ font-weight: normal;
+ }
}
h2 {
@@ -226,11 +241,15 @@ article {
@extend .doc-content;
}
- h1+.panel > h2, h1+span+.panel > h2, h1+span+span+.panel > h2 {
+ h1+.panel > h2 {
margin-top: 0;
border-top: none;
}
+ h1+.tag-description+.panel > h2 {
+ margin-top: 2rem;
+ }
+
h1+.panel h3 {
margin-top: 1rem;
}
@@ -271,20 +290,24 @@ article {
.prop-name {
@extend .columns;
- @extend .small-4;
- @extend .text-right;
+ @extend .small-5;
+ // @extend .text-right;
+ text-align: right;
padding-right: 0.85rem !important;
- word-break: break-all;
+ word-break: break-word;
}
.prop-value {
@extend .columns;
- @extend .small-8;
+ @extend .small-7;
padding-left: 0.85rem !important;
word-wrap: break-word;
}
&.prop-inner {
- @extend small;
+ // @extend small;
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+ font-size: $small-font-size;
.prop-name {
color: $dark-gray;
@@ -298,7 +321,7 @@ article {
.doc-row {
@extend .row;
@extend .collapse;
- margin: 30px 0 20px;
+ margin: 2rem 0 20px;
}
.doc-copy {
@@ -333,13 +356,13 @@ article {
//color: $dark-gray;
}
}
- }
- @media screen and (max-width: $large-width) { // medium and lower
- .doc-examples:not(:empty) {
- margin-top: 1.5rem;
- padding-top: 1.5rem;
- padding-bottom: 0.5rem;
+ @include breakpoint(medium down) {
+ &:not(:empty) {
+ margin-top: 1.5rem;
+ padding-top: 1.5rem;
+ padding-bottom: 0.5rem;
+ }
}
}
@@ -347,7 +370,8 @@ article {
// Powered by link
.powered-by {
- @extend small;
+ // @extend small;
+ font-size: $small-font-size;
color: $medium-gray;
span {
@@ -365,7 +389,9 @@ article {
text-align: right;
right: 0;
}
- @media screen and (min-width: $large-width) { // large and up
+
+ @include breakpoint(large) {
+ // @media screen and (min-width: $large-width) { // large and up
.operation-tags {
right: 50%;
}
@@ -378,18 +404,18 @@ article {
//
// Code highlighting
-//
.hljs {
- padding: 0;
+ padding: 0 !important;
+ margin-bottom: 1.5rem;
pre {
line-height: 1.25;
- padding: 0.15rem 2rem;
+ padding: 1.5rem 2rem;
border-radius: 5px;
box-shadow: 0 0 200px rgba(0, 0, 0, 0.33) inset;
- margin-bottom: 1.5rem;
+ margin: 0;
border-top: 1px solid #000;
border-bottom: 1px solid #404040;
diff --git a/app/stylesheets/_utils.scss b/app/stylesheets/_utils.scss
index 6309e36..29d6d41 100644
--- a/app/stylesheets/_utils.scss
+++ b/app/stylesheets/_utils.scss
@@ -7,7 +7,7 @@
&:before {
content: $title;
display: block;
- @extend h6;
+ // @extend h6;
margin-bottom: 0.5em;
color: $accent-color;
text-transform: uppercase;
@@ -15,17 +15,19 @@
}
}
-@mixin small-label($style:'alert') {
+@mixin small-label($color: $alert-color) {
@include label();
- @extend .label.#{$style};
+ // @extend .label.#{$style};
+ background: $color;
+ color: foreground($color);
font-size: 0.75rem;
border-radius: 4px;
padding: 3px 6px;
}
-@mixin named-label($name, $style:'alert') {
+@mixin named-label($name, $color: $alert-color) {
&:before {
- @include small-label($style);
+ @include small-label($color);
content: $name;
}
}
diff --git a/app/stylesheets/_variables.scss b/app/stylesheets/_variables.scss
index 232056f..15dad7f 100644
--- a/app/stylesheets/_variables.scss
+++ b/app/stylesheets/_variables.scss
@@ -2,23 +2,29 @@
//= Layout variables
//
-// The height of the top bar
-$topbar-height: 50px !default;
+// Width of the off-canvas menu.
+$drawer-size: 250px !default;
+
+// Length of animation.
+$default-transition-length: 0.5s !default;
// The width of the column padding
$column-padding: 2.25rem !default;
-// The accent color to use for nav highlights and section headings
+// The accent color to use for section headings
$accent-color: #f68b1f !default;
// The dark backgorund color behind code examples
$dark-background-color: $black !default;
-// Medium device width (from Foundation)
-$medium-width: 40em !default;
+// The height of the top bar
+// $topbar-height: 50px !default;
-// Large device width (from Foundation)
-$large-width: 64em !default;
+// // Medium device width (from Foundation)
+// $medium-width: 40em !default;
+//
+// // Large device width (from Foundation)
+// $large-width: 64em !default;
//
// JSON schema labels
diff --git a/app/stylesheets/foundation.scss b/app/stylesheets/foundation.scss
new file mode 100644
index 0000000..5379eba
--- /dev/null
+++ b/app/stylesheets/foundation.scss
@@ -0,0 +1,45 @@
+//
+//= Foundation
+//
+
+@import 'foundation-settings';
+@import 'foundation/scss/foundation';
+
+// Include required components to build
+
+@include foundation-global-styles;
+@include foundation-grid;
+//@include foundation-flex-grid;
+@include foundation-typography;
+@include foundation-button;
+@include foundation-forms;
+@include foundation-visibility-classes;
+@include foundation-float-classes;
+// @include foundation-accordion;
+// @include foundation-accordion-menu;
+// @include foundation-badge;
+// @include foundation-breadcrumbs;
+// @include foundation-button-group;
+// @include foundation-callout;
+@include foundation-close-button;
+// @include foundation-drilldown-menu;
+// @include foundation-dropdown;
+// @include foundation-dropdown-menu;
+// @include foundation-flex-video;
+@include foundation-label;
+// @include foundation-media-object;
+// @include foundation-menu;
+// @include foundation-off-canvas;
+// @include foundation-orbit;
+// @include foundation-pagination;
+// @include foundation-progress-bar;
+// @include foundation-slider;
+// @include foundation-sticky;
+// @include foundation-reveal;
+// @include foundation-switch;
+@include foundation-table;
+// @include foundation-tabs;
+// @include foundation-thumbnail;
+// @include foundation-title-bar;
+// @include foundation-tooltip;
+// @include foundation-top-bar;
diff --git a/app/stylesheets/spectacle-core.scss b/app/stylesheets/spectacle-core.scss
deleted file mode 100644
index b68cf57..0000000
--- a/app/stylesheets/spectacle-core.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-//
-//= Spectacle core
-//
-
-// Include just the Spectacle core without Foundation
-@import 'index';
diff --git a/app/stylesheets/spectacle.scss b/app/stylesheets/spectacle.scss
index 16fbb1b..8227432 100644
--- a/app/stylesheets/spectacle.scss
+++ b/app/stylesheets/spectacle.scss
@@ -2,10 +2,9 @@
//= Spectacle
//
-// Include Foundation
-// TODO: Don't include foundation if already included
+// Include Foundation components and utilities (but don't build anything)
@import 'foundation-settings';
-@import 'foundation-includes';
+@import 'foundation/scss/foundation';
// Include Spectacle
@import 'index';
diff --git a/app/vendor/foundation/LICENSE b/app/vendor/foundation/LICENSE
new file mode 100644
index 0000000..1e12290
--- /dev/null
+++ b/app/vendor/foundation/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2013-2015 ZURB, inc.
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/app/vendor/foundation/README.md b/app/vendor/foundation/README.md
new file mode 100644
index 0000000..1e5f4f6
--- /dev/null
+++ b/app/vendor/foundation/README.md
@@ -0,0 +1,51 @@
+# [Foundation for Sites](http://foundation.zurb.com) (v6.1)
+
+[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/zurb/foundation-sites?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
+
+Foundation is the most advanced responsive front-end framework in the world. Quickly go from prototype to production, building sites or apps that work on any kind of device with Foundation. Includes layout constructs, like a fully customizable, responsive grid, commonly used JavaScript plugins, and full A11Y support.
+
+## Requirements
+
+Requires NodeJS to be installed on your machine. Works with 0.10, 0.12, and 4.1! **Note that parts of our build process will break with NPM 3, due to the changes in how packages are installed.**
+
+The Sass is compiled using libsass, which requires the GCC to be installed on your machine. Windows users can install it through [MinGW](http://www.mingw.org/), and Mac users can install it through the [Xcode Command-line Tools](http://osxdaily.com/2014/02/12/install-command-line-tools-mac-os-x/).
+
+## Setup
+
+To get going with Foundation you can:
+
+
+ * [Install Yeti Launch](http://foundation.zurb.com/develop/yeti-launch.html): Seriously though, check it out!
+ * [Install the CLI](https://www.npmjs.com/package/foundation-cli): `npm install -g foundation-cli`
+ * [Download the latest release](http://foundation.zurb.com/sites/download.html)
+ * [Install with Bower](http://bower.io): `bower install foundation-sites`
+ * [Install with npm](http://npmjs.com): `npm install foundation-sites`
+ * [Install with Atmosphere for Meteor](https://atmospherejs.com): `meteor add zurb:foundation-sites`
+ * [Install with Composer](https://getcomposer.org/): `php composer.phar require zurb/foundation`
+
+## Documentation
+
+Foundation uses [Gulp](http://gulpjs.com/) and [SuperCollider](https://www.npmjs.com/package/supercollider) to generate its [documentation pages](http://foundation.zurb.com/sites/docs). Documentation can also be run from your local computer:
+
+### View documentation locally
+
+You'll want to clone the Foundation repo first and install all the dependencies. You can do this using the following commands:
+
+```
+git clone git@github.com:zurb/foundation-sites.git
+cd foundation-sites
+npm install
+```
+
+Then just run `npm start` and the documentation will be compiled. It will also launch an instance of [BrowserSync](http://www.browsersync.io/) and open a tab in your default browser.
+The file structure:
+
+```
+foundation/
+├── dist/
+│ └── ...
+├── docs/
+│ └── ...
+```
+
+Copyright (c) 2015 ZURB, inc.
diff --git a/app/vendor/foundation/js/foundation.abide.js b/app/vendor/foundation/js/foundation.abide.js
new file mode 100644
index 0000000..d5794a3
--- /dev/null
+++ b/app/vendor/foundation/js/foundation.abide.js
@@ -0,0 +1,418 @@
+!function(Foundation, $) {
+ 'use strict';
+
+ /**
+ * Creates a new instance of Abide.
+ * @class
+ * @fires Abide#init
+ * @param {Object} element - jQuery object to add the trigger to.
+ * @param {Object} options - Overrides to the default plugin settings.
+ */
+ function Abide(element, options) {
+ this.$element = element;
+ this.options = $.extend({}, Abide.defaults, this.$element.data(), options);
+
+ this._init();
+
+ Foundation.registerPlugin(this, 'Abide');
+ }
+
+ /**
+ * Default settings for plugin
+ */
+ Abide.defaults = {
+ /**
+ * The default event to validate inputs. Checkboxes and radios validate immediately.
+ * Remove or change this value for manual validation.
+ * @option
+ * @example 'fieldChange'
+ */
+ validateOn: 'fieldChange',
+ /**
+ * Class to be applied to input labels on failed validation.
+ * @option
+ * @example 'is-invalid-label'
+ */
+ labelErrorClass: 'is-invalid-label',
+ /**
+ * Class to be applied to inputs on failed validation.
+ * @option
+ * @example 'is-invalid-input'
+ */
+ inputErrorClass: 'is-invalid-input',
+ /**
+ * Class selector to use to target Form Errors for show/hide.
+ * @option
+ * @example '.form-error'
+ */
+ formErrorSelector: '.form-error',
+ /**
+ * Class added to Form Errors on failed validation.
+ * @option
+ * @example 'is-visible'
+ */
+ formErrorClass: 'is-visible',
+ /**
+ * Set to true to validate text inputs on any value change.
+ * @option
+ * @example false
+ */
+ liveValidate: false,
+
+ patterns: {
+ alpha : /^[a-zA-Z]+$/,
+ alpha_numeric : /^[a-zA-Z0-9]+$/,
+ integer : /^[-+]?\d+$/,
+ number : /^[-+]?\d*(?:[\.\,]\d+)?$/,
+
+ // amex, visa, diners
+ card : /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/,
+ cvv : /^([0-9]){3,4}$/,
+
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#valid-e-mail-address
+ email : /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/,
+
+ url : /^(https?|ftp|file|ssh):\/\/(((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-zA-Z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/,
+ // abc.de
+ domain : /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,8}$/,
+
+ datetime : /^([0-2][0-9]{3})\-([0-1][0-9])\-([0-3][0-9])T([0-5][0-9])\:([0-5][0-9])\:([0-5][0-9])(Z|([\-\+]([0-1][0-9])\:00))$/,
+ // YYYY-MM-DD
+ date : /(?:19|20)[0-9]{2}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9])|(?:(?!02)(?:0[1-9]|1[0-2])-(?:30))|(?:(?:0[13578]|1[02])-31))$/,
+ // HH:MM:SS
+ time : /^(0[0-9]|1[0-9]|2[0-3])(:[0-5][0-9]){2}$/,
+ dateISO : /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/,
+ // MM/DD/YYYY
+ month_day_year : /^(0[1-9]|1[012])[- \/.](0[1-9]|[12][0-9]|3[01])[- \/.]\d{4}$/,
+ // DD/MM/YYYY
+ day_month_year : /^(0[1-9]|[12][0-9]|3[01])[- \/.](0[1-9]|1[012])[- \/.]\d{4}$/,
+
+ // #FFF or #FFFFFF
+ color : /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/
+ },
+ /**
+ * Optional validation functions to be used. `equalTo` being the only default included function.
+ * Functions should return only a boolean if the input is valid or not. Functions are given the following arguments:
+ * el : The jQuery element to validate.
+ * required : Boolean value of the required attribute be present or not.
+ * parent : The direct parent of the input.
+ * @option
+ */
+ validators: {
+ equalTo: function (el, required, parent) {
+ return $('#' + el.attr('data-equalto')).val() === el.val();
+ }
+ }
+ };
+
+
+ /**
+ * Initializes the Abide plugin and calls functions to get Abide functioning on load.
+ * @private
+ */
+ Abide.prototype._init = function(){
+ this.$inputs = this.$element.find('input, textarea, select').not('[data-abide-ignore]');
+
+ this._events();
+ };
+
+ /**
+ * Initializes events for Abide.
+ * @private
+ */
+ Abide.prototype._events = function() {
+ var _this = this;
+
+ this.$element.off('.abide')
+ .on('reset.zf.abide', function(e){
+ _this.resetForm();
+ })
+ .on('submit.zf.abide', function(e){
+ return _this.validateForm();
+ });
+
+ if(this.options.validateOn === 'fieldChange'){
+ this.$inputs.off('change.zf.abide')
+ .on('change.zf.abide', function(e){
+ _this.validateInput($(this));
+ });
+ }
+
+ if(this.options.liveValidate){
+ this.$inputs.off('input.zf.abide')
+ .on('input.zf.abide', function(e){
+ _this.validateInput($(this));
+ });
+ }
+ },
+ /**
+ * Calls necessary functions to update Abide upon DOM change
+ * @private
+ */
+ Abide.prototype._reflow = function() {
+ this._init();
+ };
+ /**
+ * Checks whether or not a form element has the required attribute and if it's checked or not
+ * @param {Object} element - jQuery object to check for required attribute
+ * @returns {Boolean} Boolean value depends on whether or not attribute is checked or empty
+ */
+ Abide.prototype.requiredCheck = function($el) {
+ if(!$el.attr('required')) return true;
+ var isGood = true;
+ switch ($el[0].type) {
+
+ case 'checkbox':
+ case 'radio':
+ isGood = $el[0].checked;
+ break;
+
+ case 'select':
+ case 'select-one':
+ case 'select-multiple':
+ var opt = $el.find('option:selected');
+ if(!opt.length || !opt.val()) isGood = false;
+ break;
+
+ default:
+ if(!$el.val() || !$el.val().length) isGood = false;
+ }
+ return isGood;
+ };
+ /**
+ * Based on $el, get the first element with selector in this order:
+ * 1. The element's direct sibling('s).
+ * 3. The element's parent's children.
+ *
+ * This allows for multiple form errors per input, though if none are found, no form errors will be shown.
+ *
+ * @param {Object} $el - jQuery object to use as reference to find the form error selector.
+ * @returns {Object} jQuery object with the selector.
+ */
+ Abide.prototype.findFormError = function($el){
+ var $error = $el.siblings(this.options.formErrorSelector)
+ if(!$error.length){
+ $error = $el.parent().find(this.options.formErrorSelector);
+ }
+ return $error;
+ };
+ /**
+ * Get the first element in this order:
+ * 2. The