From 52953dc4e2c0ca2ed8965df30c18c13d878febbb Mon Sep 17 00:00:00 2001 From: Nick Downing Date: Tue, 4 Feb 2020 02:34:44 +1100 Subject: [PATCH] Basic website with home page, contact/feedback forms (project links do nothing) --- .gitignore | 11 + _config/email_contact.json | 6 + _config/email_feedback.json | 6 + _config/globals.json | 11 + _config/server.jst | 16 + _config/site.jst | 77 + _favicon/favicons.html | 6 + _favicon/favicons.zip | Bin 0 -> 119334 bytes _ssl/ca.conf | 25 + _ssl/ca_cert.pem | 21 + _ssl/ca_cert.srl | 1 + _ssl/ca_key.pem | 27 + _ssl/localhost.conf | 66 + _ssl/localhost_cert.pem | 21 + _ssl/localhost_cert_bundle.pem | 42 + _ssl/localhost_csr.pem | 19 + _ssl/localhost_ext.conf | 13 + _ssl/localhost_key.pem | 27 + _ssl/n.sh | 30 + _svg/icon_jst.svg | 167 ++ _svg/icon_pitree.svg | 137 ++ _svg/icon_search.svg | 4 + _svg/logo_large.svg | 134 ++ _svg/rescale.py | 54 + _zet/index.sh | 22 + contact.html.jst | 72 + contact_form.jst | 63 + css/alerts.less | 73 + css/badges.less | 66 + css/bootstrap.css.less | 59 + css/breadcrumbs.less | 26 + css/button-groups.less | 244 +++ css/buttons.less | 166 ++ css/carousel.less | 270 +++ css/close.less | 34 + css/code.less | 69 + css/component-animations.less | 33 + css/custom.less | 81 + css/dropdowns.less | 216 ++ css/forms.less | 613 ++++++ css/glyphicons.less | 305 +++ css/grid.less | 84 + css/ie10-viewport-bug-workaround.css.min | 13 + css/input-groups.less | 171 ++ css/jumbotron.less | 54 + css/labels.less | 64 + css/list-group.less | 130 ++ css/login.css.min | 40 + css/media.less | 66 + css/mixins.less | 40 + css/mixins/alerts.less | 14 + css/mixins/background-variant.less | 9 + css/mixins/border-radius.less | 18 + css/mixins/buttons.less | 65 + css/mixins/center-block.less | 7 + css/mixins/clearfix.less | 22 + css/mixins/forms.less | 85 + css/mixins/gradients.less | 59 + css/mixins/grid-framework.less | 91 + css/mixins/grid.less | 122 ++ css/mixins/hide-text.less | 21 + css/mixins/image.less | 33 + css/mixins/labels.less | 12 + css/mixins/list-group.less | 30 + css/mixins/nav-divider.less | 10 + css/mixins/nav-vertical-align.less | 9 + css/mixins/opacity.less | 8 + css/mixins/pagination.less | 24 + css/mixins/panels.less | 24 + css/mixins/progress-bar.less | 10 + css/mixins/reset-filter.less | 8 + css/mixins/reset-text.less | 18 + css/mixins/resize.less | 6 + css/mixins/responsive-visibility.less | 15 + css/mixins/size.less | 10 + css/mixins/tab-focus.less | 9 + css/mixins/table-row.less | 28 + css/mixins/text-emphasis.less | 9 + css/mixins/text-overflow.less | 8 + css/mixins/vendor-prefixes.less | 227 +++ css/modals.less | 150 ++ css/navbar.less | 660 ++++++ css/navs.less | 242 +++ css/normalize.less | 424 ++++ css/pager.less | 54 + css/pagination.less | 89 + css/panels.less | 271 +++ css/popovers.less | 131 ++ css/print.less | 101 + css/progress-bars.less | 87 + css/recover_account.css.min | 40 + css/responsive-embed.less | 35 + css/responsive-utilities.less | 194 ++ css/scaffolding.less | 161 ++ css/tables.less | 234 +++ css/theme.less | 291 +++ css/thumbnails.less | 36 + css/tooltip.less | 101 + css/type.less | 302 +++ css/utilities.less | 55 + css/variables.less | 870 ++++++++ css/wells.less | 29 + feedback.html.jst | 48 + feedback_form.jst | 26 + images/by-sa_3.0_88x31.png | Bin 0 -> 1697 bytes index.html.jst | 57 + js/bootstrap.js.min | 2377 ++++++++++++++++++++++ js/ie-emulation-modes-warning.js.min | 51 + js/ie10-viewport-bug-workaround.js.min | 23 + navbar.jst | 200 ++ package.json | 18 + page.jst | 116 ++ search.html.jst | 64 + 113 files changed, 12543 insertions(+) create mode 100644 .gitignore create mode 100644 _config/email_contact.json create mode 100644 _config/email_feedback.json create mode 100644 _config/globals.json create mode 100644 _config/server.jst create mode 100644 _config/site.jst create mode 100644 _favicon/favicons.html create mode 100644 _favicon/favicons.zip create mode 100644 _ssl/ca.conf create mode 100644 _ssl/ca_cert.pem create mode 100644 _ssl/ca_cert.srl create mode 100644 _ssl/ca_key.pem create mode 100644 _ssl/localhost.conf create mode 100644 _ssl/localhost_cert.pem create mode 100644 _ssl/localhost_cert_bundle.pem create mode 100644 _ssl/localhost_csr.pem create mode 100644 _ssl/localhost_ext.conf create mode 100644 _ssl/localhost_key.pem create mode 100755 _ssl/n.sh create mode 100644 _svg/icon_jst.svg create mode 100644 _svg/icon_pitree.svg create mode 100644 _svg/icon_search.svg create mode 100644 _svg/logo_large.svg create mode 100755 _svg/rescale.py create mode 100755 _zet/index.sh create mode 100644 contact.html.jst create mode 100644 contact_form.jst create mode 100644 css/alerts.less create mode 100644 css/badges.less create mode 100644 css/bootstrap.css.less create mode 100644 css/breadcrumbs.less create mode 100644 css/button-groups.less create mode 100644 css/buttons.less create mode 100644 css/carousel.less create mode 100644 css/close.less create mode 100644 css/code.less create mode 100644 css/component-animations.less create mode 100644 css/custom.less create mode 100644 css/dropdowns.less create mode 100644 css/forms.less create mode 100644 css/glyphicons.less create mode 100644 css/grid.less create mode 100644 css/ie10-viewport-bug-workaround.css.min create mode 100644 css/input-groups.less create mode 100644 css/jumbotron.less create mode 100644 css/labels.less create mode 100644 css/list-group.less create mode 100644 css/login.css.min create mode 100644 css/media.less create mode 100644 css/mixins.less create mode 100644 css/mixins/alerts.less create mode 100644 css/mixins/background-variant.less create mode 100644 css/mixins/border-radius.less create mode 100644 css/mixins/buttons.less create mode 100644 css/mixins/center-block.less create mode 100644 css/mixins/clearfix.less create mode 100644 css/mixins/forms.less create mode 100644 css/mixins/gradients.less create mode 100644 css/mixins/grid-framework.less create mode 100644 css/mixins/grid.less create mode 100644 css/mixins/hide-text.less create mode 100644 css/mixins/image.less create mode 100644 css/mixins/labels.less create mode 100644 css/mixins/list-group.less create mode 100644 css/mixins/nav-divider.less create mode 100644 css/mixins/nav-vertical-align.less create mode 100644 css/mixins/opacity.less create mode 100644 css/mixins/pagination.less create mode 100644 css/mixins/panels.less create mode 100644 css/mixins/progress-bar.less create mode 100644 css/mixins/reset-filter.less create mode 100644 css/mixins/reset-text.less create mode 100644 css/mixins/resize.less create mode 100644 css/mixins/responsive-visibility.less create mode 100644 css/mixins/size.less create mode 100644 css/mixins/tab-focus.less create mode 100644 css/mixins/table-row.less create mode 100644 css/mixins/text-emphasis.less create mode 100644 css/mixins/text-overflow.less create mode 100644 css/mixins/vendor-prefixes.less create mode 100644 css/modals.less create mode 100644 css/navbar.less create mode 100644 css/navs.less create mode 100644 css/normalize.less create mode 100644 css/pager.less create mode 100644 css/pagination.less create mode 100644 css/panels.less create mode 100644 css/popovers.less create mode 100644 css/print.less create mode 100644 css/progress-bars.less create mode 100644 css/recover_account.css.min create mode 100644 css/responsive-embed.less create mode 100644 css/responsive-utilities.less create mode 100644 css/scaffolding.less create mode 100644 css/tables.less create mode 100644 css/theme.less create mode 100644 css/thumbnails.less create mode 100644 css/tooltip.less create mode 100644 css/type.less create mode 100644 css/utilities.less create mode 100644 css/variables.less create mode 100644 css/wells.less create mode 100644 feedback.html.jst create mode 100644 feedback_form.jst create mode 100644 images/by-sa_3.0_88x31.png create mode 100644 index.html.jst create mode 100644 js/bootstrap.js.min create mode 100644 js/ie-emulation-modes-warning.js.min create mode 100644 js/ie10-viewport-bug-workaround.js.min create mode 100644 navbar.jst create mode 100644 package.json create mode 100644 page.jst create mode 100644 search.html.jst diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eca904c --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +.*.deps +.*.html +.*.jst +.*.less +.*.min +.*.svg +/_analytics +/_logs +/_zet/site.* +/node_modules +/pnpm-lock.yaml diff --git a/_config/email_contact.json b/_config/email_contact.json new file mode 100644 index 0000000..078879d --- /dev/null +++ b/_config/email_contact.json @@ -0,0 +1,6 @@ +{ + "user": "contact@ndcode.org", + "password": "XXXContact12", + "host": "mail.ndcode.org", + "tls": {"ciphers": "SSLv3"} +} diff --git a/_config/email_feedback.json b/_config/email_feedback.json new file mode 100644 index 0000000..9358eb1 --- /dev/null +++ b/_config/email_feedback.json @@ -0,0 +1,6 @@ +{ + "user": "feedback@ndcode.org", + "password": "XXXFeedback12", + "host": "mail.ndcode.org", + "tls": {"ciphers": "SSLv3"} +} diff --git a/_config/globals.json b/_config/globals.json new file mode 100644 index 0000000..a791936 --- /dev/null +++ b/_config/globals.json @@ -0,0 +1,11 @@ +{ + "site_title": "NDCODE", + "navigation": [ + "/index.html", + "/contact.html" + ], + "page_to_title": { + "/index.html": "Home", + "/contact.html": "Contact" + } +} diff --git a/_config/server.jst b/_config/server.jst new file mode 100644 index 0000000..e6ebffc --- /dev/null +++ b/_config/server.jst @@ -0,0 +1,16 @@ +return async (resources, prev_server) => new _jst_server.Server( + resources, + { + hosts: { + 'localhost': { + type: 'site', + root: '.' + }, + 'localhost.localdomain': { + type: 'redirect', + host: 'localhost' + } + } + }, + prev_server +) diff --git a/_config/site.jst b/_config/site.jst new file mode 100644 index 0000000..fd44767 --- /dev/null +++ b/_config/site.jst @@ -0,0 +1,77 @@ +let EmailJSCache = require('@ndcode/emailjs_cache') +let ZettairCache = require('@ndcode/zettair_cache') +let assert = require('assert') + +let CustomSite = function(resources, root, options, prev_site) { + if (!this instanceof CustomSite) + throw Error('CustomSite is a constructor') + _jst_server.Site.call(this, resources, root, options, prev_site) + + this.emailjs_cache = undefined + this.zettair_cache = undefined +} + +CustomSite.prototype = Object.create(_jst_server.Site.prototype) + +CustomSite.prototype.start = async function() { + await _jst_server.Site.prototype.start.call(this) + + assert(this.emailjs_cache === undefined) + this.emailjs_cache = await this.resources.ref( + 'emailjs_cache', + async () => new EmailJSCache(true) + ) + + assert(this.zettair_cache === undefined) + this.zettair_cache = await this.resources.ref( + 'zettair_cache', + async () => new ZettairCache(true) + ) +} + +CustomSite.prototype.stop = async function() { + await _jst_server.Site.prototype.stop.call(this) + + assert(this.emailjs_cache !== undefined) + await this.resources.unref('emailjs_cache') + + assert(this.zettair_cache !== undefined) + await this.resources.unref('zettair_cache') +} + +CustomSite.prototype.kick = async function() { + await _jst_server.Site.prototype.kick.call(this) + + assert(this.emailjs_cache !== undefined) + this.emailjs_cache.kick() + + assert(this.zettair_cache !== undefined) + this.zettair_cache.kick() +} + +CustomSite.prototype.get_emailjs = function(pathname) { + return /*await*/ this.emailjs_cache.get(this.root + pathname) +} + +CustomSite.prototype.get_zettair = function(pathname) { + return /*await*/ this.zettair_cache.get(this.root + pathname) +} + +CustomSite.prototype.respond = async function(env) { + if ( + env.parsed_url.pathname === '/node_modules' || + env.parsed_url.pathname.slice(0, 14) === '/node_modules/' || + env.parsed_url.pathname === '/package.json' + ) { + this.die(env, `banned file ${env.parsed_url.pathname}`) + return + } + return /*await*/ _jst_server.Site.prototype.respond.call(this, env) +} + +return async (resources, root, prev_site) => new CustomSite( + resources, + root, + {}, + prev_site +) diff --git a/_favicon/favicons.html b/_favicon/favicons.html new file mode 100644 index 0000000..02ba31d --- /dev/null +++ b/_favicon/favicons.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/_favicon/favicons.zip b/_favicon/favicons.zip new file mode 100644 index 0000000000000000000000000000000000000000..11ac24bd0cd200dd95480701c25dd627fff2f57c GIT binary patch literal 119334 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 literal 0 HcmV?d00001 diff --git a/_ssl/ca.conf b/_ssl/ca.conf new file mode 100644 index 0000000..d0d9a5f --- /dev/null +++ b/_ssl/ca.conf @@ -0,0 +1,25 @@ +[ ca ] +default_ca = CA_default # The default ca section + +[ CA_default ] + +#dir = ./demoCA # top dir +#database = $dir/index.txt # index file. +#new_certs_dir = $dir/newcerts # new certs dir +# +#certificate = $dir/cacert.pem # The CA cert +#serial = $dir/serial # serial no file +#private_key = $dir/private/cakey.pem# CA private key +#RANDFILE = $dir/private/.rand # random number file +# +default_days = 365 # how long to certify for +#default_crl_days= 30 # how long before next CRL +#default_md = md5 # md to use +# +#policy = policy_any # default policy +#email_in_dn = no # Don't add the email into cert DN +# +#name_opt = ca_default # Subject name display option +#cert_opt = ca_default # Certificate display option +#copy_extensions = none +copy_extensions = copyall diff --git a/_ssl/ca_cert.pem b/_ssl/ca_cert.pem new file mode 100644 index 0000000..a77b130 --- /dev/null +++ b/_ssl/ca_cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDYDCCAkigAwIBAgIJANSi4mXBF9a+MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTgxMTE2MTAyNjU1WhcNMTgxMjE2MTAyNjU1WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAy2PngRdvbUTVsDW+Lg3VlALPu4w8m3qLPeBPLYDyjiRHNn5QcKJZGvRl +N+e8fSeb9igpzrgpCgK4S9rZpJrOX9heBHs2/5fQ568lHXJKgjCNm8AM/2v0/txy +46iOJeZmsBNtSVzYIKgHHIsUVGP4Ct/lezPsznacEXOL+wmtEbAcMH73vFrPMaiq +8lRb8yTNEF4gI2b8TKS8oHW55owiilQmNNDH4dtCqh2ATCDB2jNEYXUa74vP9qD3 +0XDAco2XY5/AUPslMEF28z9jAvoqvO5S7sVMRGBmkFOJ8yDB9exmtObes4ReNY6g +5624yoG4MTxjTcb2rv666Qp6/vjUPwIDAQABo1MwUTAdBgNVHQ4EFgQUQbZw0emw +0IwXLQAg13thU7AEgRMwHwYDVR0jBBgwFoAUQbZw0emw0IwXLQAg13thU7AEgRMw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAv9RHAheIwsVqdvbF +USorz0z7PjDM8dmI1yanPNBUjNIMceZ0h+CcFOztMH8dvMM0r5pCQkIJJyFCyXtK +y61AOrNbOAgnDm3z1YrVB8NEnji9Qicctpbse8UymD2kOdBXjU1Egrd/uUAgJZmP +ASYeEYf/n7/bMwTNAJ+qc0o793ILIwJ20Ois43oh3RB57xDzHlHWEbN+lZFZWjmT +yuVPvwULYmgLP8huh3EOwnsh0eCYkhgImhJICawjWQLT+EHbD3xt+QRYElDgv/Av +kqRliJCa3yF8NLUdsQp7jD5QdpWUfcnl0Kfrc1I3KlM3SpneGcv6FT/U4hjW++AZ +ezss0w== +-----END CERTIFICATE----- diff --git a/_ssl/ca_cert.srl b/_ssl/ca_cert.srl new file mode 100644 index 0000000..401cc51 --- /dev/null +++ b/_ssl/ca_cert.srl @@ -0,0 +1 @@ +D29D1C6A226490AC diff --git a/_ssl/ca_key.pem b/_ssl/ca_key.pem new file mode 100644 index 0000000..e65d8ce --- /dev/null +++ b/_ssl/ca_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAy2PngRdvbUTVsDW+Lg3VlALPu4w8m3qLPeBPLYDyjiRHNn5Q +cKJZGvRlN+e8fSeb9igpzrgpCgK4S9rZpJrOX9heBHs2/5fQ568lHXJKgjCNm8AM +/2v0/txy46iOJeZmsBNtSVzYIKgHHIsUVGP4Ct/lezPsznacEXOL+wmtEbAcMH73 +vFrPMaiq8lRb8yTNEF4gI2b8TKS8oHW55owiilQmNNDH4dtCqh2ATCDB2jNEYXUa +74vP9qD30XDAco2XY5/AUPslMEF28z9jAvoqvO5S7sVMRGBmkFOJ8yDB9exmtObe +s4ReNY6g5624yoG4MTxjTcb2rv666Qp6/vjUPwIDAQABAoIBAA3vOEsl2eJ4ltNN +u0vYcsuDLcxBnV1hleyVU5dggD2wypg3KzesR8KK/+xGmilQ72R79/FLuLQQ36OC +yOp4GK+EWVyhPHFia1OUMkzEKcqlnO4QyFMviEd1vwFN6P87u8lQa2pdTAlguawA +81Gcz7+e+0/njM/QXHztl6eJUCwn7zEbG8+we0g8GkLILOBA3Tuy5Vu+ox1LQZQp +WPqIMhm0GXPOVRKBr0mPVWtJvyAYvTX13GqNM1YFbElkbnkbsTaJhg/L3J9UJNsm +BbOg0oUdq1yWoy7cjOXiq3aG9rNws9PBXbbDB/G67aae/1SJ7eBTV/kCEuYh0YDx +MSn5xYECgYEA/uZueW4dFTKMBebNFvJreQei1ecaGwEFckUYD5u02VugtYc9inQ2 +sK7ENq/CsA10WKBxV84lIdiG5yXjG1RwCLvTV6YBP5uL9dVMln25x6ZDUxOlYzuZ +OZUcabR3Y3Ar349vDV+vFle47J0YW8H89O5YlVxvLfZ+CFFFSBSMRCECgYEAzESS +5IvNH1RUCR0tqRkHA0SXMiNxF85lFbUrbS2yMI60LCaTE2q/uK5008f5laD3LYKU +Gq7SVP1U4z0+g9jGa0rvYd1h3o6fJMrFBiBN1ZRRzMFaiQxxNbyjCV7LR86m6aSo +pz4f7k8EmzrsmZw7MF/l9Dy3AWHpg1cwYdR6DF8CgYB17kS0d6aK9Rzlahf/At+I +WmkTD937Gmjbqm3sYry0R3k+IzjswsG+0szDBGRNsZvfmTN3TU/OrfAUJ2pAbbt7 +vvKTvaEcPanubeYGRlrarOi/GfrNw3grtPo1SaJm5jHWN/VIObm225UaG8B2S3Tu +GQvw5pglqsI6tOcZ5y/SIQKBgBVJuD1VTIVNVoy0m8OZth9jEJbLFsgyXFqMzP/N +2VoyJRjM1FsbrutiUw6XMq2jXt9BUooNWiI9XJFqGo/HEbaw0o3ScpatKmy9LRdc +WoA9uuCp7fOGdm3xQNSDKpBLOx3yaRk04kMFvScoVuwTWh/Kfr6bbT8Zoypq9cHc +UPPlAoGBAIEtVPhH6R51/HREPMWXWgwZujkeD0VQX+hNP8VOgSlts9L4bDn7res5 +DXHDXU3Q+vY+86qDvJgJaOdmt7IoNXgRlU9tUZP4MD8YG32Xjn3dnB5e/3SmBJwD +NV4mVx+7X6me5rb4NdpMcSzulVXy3SfnQTaDsNghpcTqOXLFI0ti +-----END RSA PRIVATE KEY----- diff --git a/_ssl/localhost.conf b/_ssl/localhost.conf new file mode 100644 index 0000000..fa3ef54 --- /dev/null +++ b/_ssl/localhost.conf @@ -0,0 +1,66 @@ +# The main section is named req because the command we are using is req +# (openssl req ...) +[ req ] +## This specifies the default key size in bits. If not specified then 512 is +## used. It is used if the -new option is used. It can be overridden by using +## the -newkey option. +#default_bits = 2048 +# +## This is the default filename to write a private key to. If not specified the +## key is written to standard output. This can be overridden by the -keyout +## option. +#default_keyfile = oats.key +# +## If this is set to no then if a private key is generated it is not encrypted. +## This is equivalent to the -nodes command line option. For compatibility +## encrypt_rsa_key is an equivalent option. +#encrypt_key = no + +# This option specifies the digest algorithm to use. Possible values include +# md5 sha1 mdc2. If not present then MD5 is used. This option can be overridden +# on the command line. +default_md = sha1 + +# if set to the value no this disables prompting of certificate fields and just +# takes values from the config file directly. It also changes the expected +# format of the distinguished_name and attributes sections. +prompt = no + +# if set to the value yes then field values to be interpreted as UTF8 strings, +# by default they are interpreted as ASCII. This means that the field values, +# whether prompted from a terminal or obtained from a configuration file, must +# be valid UTF8 strings. +utf8 = yes + +# This specifies the section containing the distinguished name fields to +# prompt for when generating a certificate or certificate request. +distinguished_name = my_req_distinguished_name + +# this specifies the configuration file section containing a list of extensions +# to add to the certificate request. It can be overridden by the -reqexts +# command line switch. See the x509v3_config(5) manual page for details of the +# extension section format. +req_extensions = my_extensions + +[ my_req_distinguished_name ] +C = PT +ST = Lisboa +L = Lisboa +O = Oats In The Water +#CN = *.oats.org +CN = localhost + +[ my_extensions ] +basicConstraints=CA:FALSE +subjectAltName=@my_subject_alt_names +subjectKeyIdentifier = hash + +[ my_subject_alt_names ] +#DNS.1 = *.oats.org +#DNS.2 = *.oats.net +#DNS.3 = *.oats.in +#DNS.4 = oats.org +#DNS.5 = oats.net +#DNS.6 = oats.in +DNS.1 = localhost +DNS.2 = localhost.localdomain diff --git a/_ssl/localhost_cert.pem b/_ssl/localhost_cert.pem new file mode 100644 index 0000000..8820da7 --- /dev/null +++ b/_ssl/localhost_cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDgDCCAmigAwIBAgIJANKdHGoiZJCsMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTgxMTE2MTAyNjU1WhcNMTgxMjE2MTAyNjU1WjBf +MQswCQYDVQQGEwJQVDEPMA0GA1UECAwGTGlzYm9hMQ8wDQYDVQQHDAZMaXNib2Ex +GjAYBgNVBAoMEU9hdHMgSW4gVGhlIFdhdGVyMRIwEAYDVQQDDAlsb2NhbGhvc3Qw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDJLav5I1Dxx9Lzv7DyHig +ApIkm9U8tcRKuM3Q4q3xj8P2VmSHVHZ0FfjKs/NZ8hLi3go+EzGolmiRKPA7V9UQ +Zgm5MmC6tewiol76lsjehUKuG+DmT3n79xSHsj5PC6wkE/IkifcQIeGVJWgQbRR4 +JSGaX4tmmpxNrDDgpVnaXEO8I5KR/jvniAn2DoTKlY5XSIUXlbZQPHdlLdLUU7in +5M19b9qnIdJRxa4mcOWNtsUHU16SJZAmQBmg4vAhMc4XyJ23YE8cNxUxusKrhoUB +sWmvjGgbbaYgtwBmCFQNSFiJhyzgg6sdUot7wVXDkXBTJPQRQO82CJyw6YoDnTCL +AgMBAAGjWTBXMAkGA1UdEwQCMAAwKwYDVR0RBCQwIoIJbG9jYWxob3N0ghVsb2Nh +bGhvc3QubG9jYWxkb21haW4wHQYDVR0OBBYEFPKfGhTkosDI90l245Si/Ce/CLr/ +MA0GCSqGSIb3DQEBCwUAA4IBAQBYjiwzD88QKfJvnmU6TTRTKOOWKpKdEIlZGPBu +4opyLgB0N/cuiNKYN2WW2Z57TPZLwwKMTlMODco1MTnyVBU0kft+yPxcc9yao7wV +Fxrb8WWylY5bFWKXwxB0SW01tgaDCAlKtCrTZVoXi/3578cru2/gf4pqoXhuIuG6 +pp5Z9baVOwVTbkNX+vSU8vxkWgpFoAcSaFkULbKCqnilqjn49myydJuorgvxvdOB +hMHUpGWjhdkb121BvJjZfDxC6jGWmQ2ZGhJkLKU5OmOhl8GocEyDsYglx5PSJTvA +zGvABNuodcksOPLyzPiKcTPVks5Ls1WxDgOYoRNeEYHszYDW +-----END CERTIFICATE----- diff --git a/_ssl/localhost_cert_bundle.pem b/_ssl/localhost_cert_bundle.pem new file mode 100644 index 0000000..48fa2d7 --- /dev/null +++ b/_ssl/localhost_cert_bundle.pem @@ -0,0 +1,42 @@ +-----BEGIN CERTIFICATE----- +MIIDgDCCAmigAwIBAgIJANKdHGoiZJCsMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTgxMTE2MTAyNjU1WhcNMTgxMjE2MTAyNjU1WjBf +MQswCQYDVQQGEwJQVDEPMA0GA1UECAwGTGlzYm9hMQ8wDQYDVQQHDAZMaXNib2Ex +GjAYBgNVBAoMEU9hdHMgSW4gVGhlIFdhdGVyMRIwEAYDVQQDDAlsb2NhbGhvc3Qw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDJLav5I1Dxx9Lzv7DyHig +ApIkm9U8tcRKuM3Q4q3xj8P2VmSHVHZ0FfjKs/NZ8hLi3go+EzGolmiRKPA7V9UQ +Zgm5MmC6tewiol76lsjehUKuG+DmT3n79xSHsj5PC6wkE/IkifcQIeGVJWgQbRR4 +JSGaX4tmmpxNrDDgpVnaXEO8I5KR/jvniAn2DoTKlY5XSIUXlbZQPHdlLdLUU7in +5M19b9qnIdJRxa4mcOWNtsUHU16SJZAmQBmg4vAhMc4XyJ23YE8cNxUxusKrhoUB +sWmvjGgbbaYgtwBmCFQNSFiJhyzgg6sdUot7wVXDkXBTJPQRQO82CJyw6YoDnTCL +AgMBAAGjWTBXMAkGA1UdEwQCMAAwKwYDVR0RBCQwIoIJbG9jYWxob3N0ghVsb2Nh +bGhvc3QubG9jYWxkb21haW4wHQYDVR0OBBYEFPKfGhTkosDI90l245Si/Ce/CLr/ +MA0GCSqGSIb3DQEBCwUAA4IBAQBYjiwzD88QKfJvnmU6TTRTKOOWKpKdEIlZGPBu +4opyLgB0N/cuiNKYN2WW2Z57TPZLwwKMTlMODco1MTnyVBU0kft+yPxcc9yao7wV +Fxrb8WWylY5bFWKXwxB0SW01tgaDCAlKtCrTZVoXi/3578cru2/gf4pqoXhuIuG6 +pp5Z9baVOwVTbkNX+vSU8vxkWgpFoAcSaFkULbKCqnilqjn49myydJuorgvxvdOB +hMHUpGWjhdkb121BvJjZfDxC6jGWmQ2ZGhJkLKU5OmOhl8GocEyDsYglx5PSJTvA +zGvABNuodcksOPLyzPiKcTPVks5Ls1WxDgOYoRNeEYHszYDW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDYDCCAkigAwIBAgIJANSi4mXBF9a+MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTgxMTE2MTAyNjU1WhcNMTgxMjE2MTAyNjU1WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAy2PngRdvbUTVsDW+Lg3VlALPu4w8m3qLPeBPLYDyjiRHNn5QcKJZGvRl +N+e8fSeb9igpzrgpCgK4S9rZpJrOX9heBHs2/5fQ568lHXJKgjCNm8AM/2v0/txy +46iOJeZmsBNtSVzYIKgHHIsUVGP4Ct/lezPsznacEXOL+wmtEbAcMH73vFrPMaiq +8lRb8yTNEF4gI2b8TKS8oHW55owiilQmNNDH4dtCqh2ATCDB2jNEYXUa74vP9qD3 +0XDAco2XY5/AUPslMEF28z9jAvoqvO5S7sVMRGBmkFOJ8yDB9exmtObes4ReNY6g +5624yoG4MTxjTcb2rv666Qp6/vjUPwIDAQABo1MwUTAdBgNVHQ4EFgQUQbZw0emw +0IwXLQAg13thU7AEgRMwHwYDVR0jBBgwFoAUQbZw0emw0IwXLQAg13thU7AEgRMw +DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAv9RHAheIwsVqdvbF +USorz0z7PjDM8dmI1yanPNBUjNIMceZ0h+CcFOztMH8dvMM0r5pCQkIJJyFCyXtK +y61AOrNbOAgnDm3z1YrVB8NEnji9Qicctpbse8UymD2kOdBXjU1Egrd/uUAgJZmP +ASYeEYf/n7/bMwTNAJ+qc0o793ILIwJ20Ois43oh3RB57xDzHlHWEbN+lZFZWjmT +yuVPvwULYmgLP8huh3EOwnsh0eCYkhgImhJICawjWQLT+EHbD3xt+QRYElDgv/Av +kqRliJCa3yF8NLUdsQp7jD5QdpWUfcnl0Kfrc1I3KlM3SpneGcv6FT/U4hjW++AZ +ezss0w== +-----END CERTIFICATE----- diff --git a/_ssl/localhost_csr.pem b/_ssl/localhost_csr.pem new file mode 100644 index 0000000..b28c691 --- /dev/null +++ b/_ssl/localhost_csr.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIDDDCCAfQCAQAwXzELMAkGA1UEBhMCUFQxDzANBgNVBAgMBkxpc2JvYTEPMA0G +A1UEBwwGTGlzYm9hMRowGAYDVQQKDBFPYXRzIEluIFRoZSBXYXRlcjESMBAGA1UE +AwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwyS2 +r+SNQ8cfS87+w8h4oAKSJJvVPLXESrjN0OKt8Y/D9lZkh1R2dBX4yrPzWfIS4t4K +PhMxqJZokSjwO1fVEGYJuTJgurXsIqJe+pbI3oVCrhvg5k95+/cUh7I+TwusJBPy +JIn3ECHhlSVoEG0UeCUhml+LZpqcTaww4KVZ2lxDvCOSkf4754gJ9g6EypWOV0iF +F5W2UDx3ZS3S1FO4p+TNfW/apyHSUcWuJnDljbbFB1NekiWQJkAZoOLwITHOF8id +t2BPHDcVMbrCq4aFAbFpr4xoG22mILcAZghUDUhYiYcs4IOrHVKLe8FVw5FwUyT0 +EUDvNgicsOmKA50wiwIDAQABoGgwZgYJKoZIhvcNAQkOMVkwVzAJBgNVHRMEAjAA +MCsGA1UdEQQkMCKCCWxvY2FsaG9zdIIVbG9jYWxob3N0LmxvY2FsZG9tYWluMB0G +A1UdDgQWBBTynxoU5KLAyPdJduOUovwnvwi6/zANBgkqhkiG9w0BAQUFAAOCAQEA +L4XofNRjzahIDCj/pHVnsaXtioidv5Hp0vE+9LPhzZz/bH/e7s8lJnTalEkPNUxD +FPwiRar6MILQwn5N1b+kToSoiYDsCL77Y5WSlhcwe7gLwgDTlNwu2H030BZEr0ve +AEAems45TJk8o3kC5s3dt0KaGbRikub1HMTpFrlQUBLbO848t6qXcZCVjoAWYKlp +jaYn7r3bWVyZ2W2oIlQ19Tbxz+kG81Vxrg1FtAo7aBHTaOvEskgnEQ2Emc0mbLoe +YoT0Gg7Vrurl+T5gazmV1WMKszSjP8NKhTUcMESiRIUsmQczWiDBONiH9PGmGKHI +7aEBzQIK7m3goyS3I4q6nw== +-----END CERTIFICATE REQUEST----- diff --git a/_ssl/localhost_ext.conf b/_ssl/localhost_ext.conf new file mode 100644 index 0000000..d5005dc --- /dev/null +++ b/_ssl/localhost_ext.conf @@ -0,0 +1,13 @@ +basicConstraints=CA:FALSE +subjectAltName=@my_subject_alt_names +subjectKeyIdentifier = hash + +[ my_subject_alt_names ] +#DNS.1 = *.oats.org +#DNS.2 = *.oats.net +#DNS.3 = *.oats.in +#DNS.4 = oats.org +#DNS.5 = oats.net +#DNS.6 = oats.in +DNS.1 = localhost +DNS.2 = localhost.localdomain diff --git a/_ssl/localhost_key.pem b/_ssl/localhost_key.pem new file mode 100644 index 0000000..6c94353 --- /dev/null +++ b/_ssl/localhost_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAwyS2r+SNQ8cfS87+w8h4oAKSJJvVPLXESrjN0OKt8Y/D9lZk +h1R2dBX4yrPzWfIS4t4KPhMxqJZokSjwO1fVEGYJuTJgurXsIqJe+pbI3oVCrhvg +5k95+/cUh7I+TwusJBPyJIn3ECHhlSVoEG0UeCUhml+LZpqcTaww4KVZ2lxDvCOS +kf4754gJ9g6EypWOV0iFF5W2UDx3ZS3S1FO4p+TNfW/apyHSUcWuJnDljbbFB1Ne +kiWQJkAZoOLwITHOF8idt2BPHDcVMbrCq4aFAbFpr4xoG22mILcAZghUDUhYiYcs +4IOrHVKLe8FVw5FwUyT0EUDvNgicsOmKA50wiwIDAQABAoIBAEuOiImcJbIrhAuX +Lv9hPIs/05QHHk4uVr1TxqTtT8orDwXvN2dKpb6Wz0i02jFmUDe1HyQfzGdpLT2f +Kzze6ik6SOODBP7l93MFiV7fSREXadT+CFtERIfxh+pucj+q1lD1xBivrpB5fd2A +qUVK5tUKE3OxMMlebcyJMjeY7ixkRVgjROFeXs0t50xitzn/wPN0dVPZ6JFCPgRD +fc72E1K+DmfrzFZ2+rzqoVzAE7OSYQns3r2jNXlCEVw2jXsmnIRIi9wejbRthjbx +gA1IdenZcf4FkOMTTFuWGynT5tfF3cVzy5QtYemGZWSK4LNuWaTSzZCk3/sCxWk5 +Vbz/AdkCgYEA8gc2WEfbk5kVhU7UV5SRDRFtGuS8fmZ0RnqFdx6qy2EWYnwYXgzT +cZc6TiEuJ5getmEVsqzxFm3gWMyD0bEhY0jqoMVKMM70QQOQOZOB4lykXmpRDKny +/DU7/0bp7sBzFCKCWISCjGKWc98KWy24ZdKl8ZAr3zjDxE5UYvquKIcCgYEAzmie +r+1Y7DUtniogqpDLr6NnuvH52d1s4DEsHZQ6KRWE82d/X+2YU2R7/0QwfLBvcbT7 +OxlQP5rRoL6pX+ViZBmky0CSc/HYZZ+8uGwFc1X46zGfuvTSWaAStdIFPOX2O0JF +LLyF5/0/CK56agL3YxGREx2iQdauqTsOyr2LLN0CgYEAzYEgPN9u6ymd415m4KKO +c1krmh1Ei9M0wa9A6j9I6H3cgu05x4n+c8HjyPlVdlstINDYmqlL4C6VwvCMsR1E +60e7qZ07fKwNK7L54FmGfI0LJ/wAK8+WOV5+PiiZc5dHX35ZzQ1eyBLiCysEYR02 +KIcvTiiLh/NsDqAv+Qc/n7sCgYAup0z+3LnVoeturXz2sIWpbFi804ayrK64OcUL +5n4C3T9QuNr8drqQVs0EFIiVFlgKLmr+n6kYx0iMeavU5gcIMxehbTXtCQPtbF5+ +nMPantsFZhEBc+a7pUe1WwQeEKhIGqGCDBaEKiR+NNmsboE2HjlZRcBv+zM9QwED +6DW17QKBgQCRZfGUWasWXW6s9aZ1FgLPAXz/M9BLZjnx627KvbF4C+ZwfszntJxn +1A74mhHWJyKbt8YzfOTsTj40lp/npSpgjRQmUoqThx8d2cA41ALo0ktk6E9Zpp1j +phi0woa+TdEpqE2Jb8hJcyRKGCOVOKL/bnZL/QVgMebhg02n//wa9w== +-----END RSA PRIVATE KEY----- diff --git a/_ssl/n.sh b/_ssl/n.sh new file mode 100755 index 0000000..9fbf379 --- /dev/null +++ b/_ssl/n.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# see https://gist.github.com/Soarez/9688998 + +# note that the CSR contains X509 extensions, particularly the SAN which +# is mandatory for getting Google Chrome to accept the certificate, but +# unfortunately "openssl x509" will strip extensions -- this can be fixed +# by using the "openssl ca" command with "extensions = copyall" (or in +# the normal case filters would be specified on what extensions to copy), +# but the "openssl ca" command is very complicated and requires a directory +# structure to be set up, so we fake it with the "localhost_ext.conf" file + +# generate a key for the subject +#openssl genrsa -out localhost_key.pem 2048 + +# generate a CSR +openssl req -new -key localhost_key.pem -out localhost_csr.pem -config localhost.conf + +# generate a key for the CA +#openssl genrsa -out ca_key.pem 2048 + +# generate a self signed certificate for the CA +openssl req -new -x509 -key ca_key.pem -out ca_cert.pem + +# sign the certificate +#openssl x509 -req -in localhost_csr.pem -extfile localhost_ext.conf -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out localhost_cert.pem +openssl x509 -req -in localhost_csr.pem -extfile localhost_ext.conf -CA ca_cert.pem -CAkey ca_key.pem -CAserial ca_cert.srl -out localhost_cert.pem + +# resolve problems with not including the signing chain (can improve this?) +cat localhost_cert.pem ca_cert.pem > localhost_cert_bundle.pem diff --git a/_svg/icon_jst.svg b/_svg/icon_jst.svg new file mode 100644 index 0000000..d3dda87 --- /dev/null +++ b/_svg/icon_jst.svg @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_svg/icon_pitree.svg b/_svg/icon_pitree.svg new file mode 100644 index 0000000..c27a850 --- /dev/null +++ b/_svg/icon_pitree.svg @@ -0,0 +1,137 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_svg/icon_search.svg b/_svg/icon_search.svg new file mode 100644 index 0000000..dd2d342 --- /dev/null +++ b/_svg/icon_search.svg @@ -0,0 +1,4 @@ + + search + + diff --git a/_svg/logo_large.svg b/_svg/logo_large.svg new file mode 100644 index 0000000..0d447a7 --- /dev/null +++ b/_svg/logo_large.svg @@ -0,0 +1,134 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_svg/rescale.py b/_svg/rescale.py new file mode 100755 index 0000000..07d5857 --- /dev/null +++ b/_svg/rescale.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 + +import re +import sys + +EXIT_SUCCESS = 0 +EXIT_FAILURE = 1 + +if len(sys.argv) < 2: + print(f'usage: {argv[0]:s} scale') + sys.exit(EXIT_FAILURE) +scale = float(sys.argv[1]) + +re_scalar = re.compile('( *(cx|cy|dx|dy|r|stroke-width|x1|x2|y1|y2)=")([^"]*)(".*)') +re_scalar2 = re.compile('( *stdDeviation=")([^ "]*)( ([^ "]*))?(".*)') +re_viewbox = re.compile('( *viewBox="0 0 )([^ "]*) ([^ "]*)(".*)') +re_path = re.compile('( *d=")([^"]*)(".*)') +re_path_command = re.compile('([A-Za-z,]) *(.*)') +re_path_number = re.compile('(-?([0-9]*\.[0-9]+|[0-9]+)) *(.*)') +for line in sys.stdin: + match = re_scalar.match(line) + if match is not None: + line = match.group(1) + str(float(match.group(3)) * scale) + match.group(4) + '\n' + else: + match = re_scalar2.match(line) + if match is not None: + out = [str(float(match.group(2)) * scale)] + if match.group(3) is not None: + out.append(str(float(match.group(4)) * scale)) + line = match.group(1) + ' '.join(out) + match.group(5) + '\n' + else: + match = re_viewbox.match(line) + if match is not None: + line = match.group(1) + str(float(match.group(2)) * scale) + ' ' + str(float(match.group(3)) * scale) + match.group(4) + '\n' + else: + match = re_path.match(line) + if match is not None: + path = match.group(2).lstrip() + out = [] + while True: + match2 = re_path_command.match(path) + if match2 is not None: + out.append(match2.group(1)) + path = match2.group(2) + else: + match2 = re_path_number.match(path) + if match2 is not None: + out.append(str(float(match2.group(1)) * scale)) + path = match2.group(3) + else: + break + assert len(path) == 0 + line = match.group(1) + ' '.join(out).replace(' ,', ',').replace(', ', ',') + match.group(3) + '\n' + sys.stdout.write(line) diff --git a/_zet/index.sh b/_zet/index.sh new file mode 100755 index 0000000..88ec56c --- /dev/null +++ b/_zet/index.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +if test $# -lt 1 +then + url=http://localhost +else + url="$1" +fi + +rm -rf site.* + +mkdir site.dir +wget -r -l inf --accept=.html --domains=localhost --directory-prefix=site.dir --no-host-directories $url + +for i in `find site.dir -type f` +do + echo -n $i |sed -e 's/^site\.dir\(.*\)/\1<\/DOCNO>/' + recode --diacritics UTF-8..HTML <$i + echo -n '' +done >site.txt + +zet -f site -i site.txt diff --git a/contact.html.jst b/contact.html.jst new file mode 100644 index 0000000..879760f --- /dev/null +++ b/contact.html.jst @@ -0,0 +1,72 @@ +let querystring = require('querystring') +let stream_buffers = require('stream-buffers') +let XDate = require('xdate') + +return async env => { + let navbar = await _require('/navbar.jst') + let contact_form = await _require('/contact_form.jst') + + await navbar( + env, + async _out => {}, + async _out => { + h1 {'Contact NDCODE'} + + div(style="height: 15px;") {} + + if (env.request.method == 'POST') { + let write_stream = new stream_buffers.WritableStreamBuffer() + let data = new Promise( + (resolve, reject) => { + write_stream. + on('finish', () => {resolve(write_stream.getContents())}). + on('error', () => {reject()}) + } + ) + env.request.pipe(write_stream) + let query = querystring.parse((await data).toString()) + let full_email = `${query.first_name} ${query.last_name} <${query.email}>` + console.log('received contact form:', full_email) + + // save the form contents in a dated logfile, so that we can + // recover manually if the email doesn't send for some reason + date = new XDate() + query.date = date.toUTCString() + + await env.site.ensure_dir('/_logs') + env.site.modify_json( + `/_logs/contact_${date.toUTCString('yyyyMMdd')}.json`, + [], + async result => {result.value.push(query)} + ) + + // send email (asynchronously) + ;(await env.site.get_emailjs('/_config/email_contact.json')).send( + { + text: query.message, + from: 'Contact form ', + 'reply-to': full_email, + to: 'Nick Downing ', + subject: + Object.prototype.hasOwnProperty.call(query, 'company') ? + 'Enquiry: ' + query.company : + 'Enquiry' + }, + (err, message) => { + if (err) + console.error(err.stack || err.message) + else + console.log('sent contact email:', full_email) + } + ) + + p {'Thanks! We\'ll be in touch as soon as we can.'} + } + else { + p {'Do you require more information or consulting assistance with integrating the projects on this site? We\'d love to hear from you.'} + + await contact_form(env, _out) + } + } + ) +} diff --git a/contact_form.jst b/contact_form.jst new file mode 100644 index 0000000..370a0ff --- /dev/null +++ b/contact_form.jst @@ -0,0 +1,63 @@ +return async (env, _out) => { + form#contact-form(method="post" action="contact.html" role="form") { + div.row { + div.col-md-6 { + div.form-group { + label(for="contact_form_first_name") {'First name *'} + input.form-control#contact_form_first_name(type="text" name="first_name" placeholder="Please enter your first name" required="required" data-error="First name is required.") {} + div.help-block.with-errors {} + } + } + div.col-md-6 { + div.form-group { + label(for="contact_form_last_name") {'Last name *'} + input.form-control#contact_form_last_name(type="text" name="last_name" placeholder="Please enter your last name" required="required" data-error="Last name is required.") {} + div.help-block.with-errors {} + } + } + } + div.row { + div.col-md-6 { + div.form-group { + label(for="contact_form_company") {'Company'} + input.form-control#contact_form_company(type="company" name="company" placeholder="Please enter your company") {} + div.help-block.with-errors {} + } + } + div.col-md-6 { + div.form-group { + label(for="contact_form_email") {'Email *'} + input.form-control#contact_form_email(type="email" name="email" placeholder="Please enter your email" required="required" data-error="Valid email is required.") {} + div.help-block.with-errors {} + } + } + } + div.row { + div.col-md-12 { + div.form-group { + label(for="contact_form_message") {'Message *'} + textarea.form-control#contact_form_message(name="message" placeholder="Please explain your situation" rows="4" required="required" data-error="Please, leave us a message.") {} + div.help-block.with-errors {} + } + } + } + p {} // fix this later + div.row { + div.col-md-12 { + input.btn.btn-success.btn-send(type="submit" value="Send message") {} + } + } + p {} // fix this later + div.row { + div.col-md-12 { + p.text-muted { + strong {'*'} + 'These fields are required.' + //'Contact form template by ' + //a(href="https://bootstrapious.com/p/how-to-build-a-working-bootstrap-contact-form" target="_blank") {'Bootstrapious'} + //'.' + } + } + } + } +} diff --git a/css/alerts.less b/css/alerts.less new file mode 100644 index 0000000..c4199db --- /dev/null +++ b/css/alerts.less @@ -0,0 +1,73 @@ +// +// Alerts +// -------------------------------------------------- + + +// Base styles +// ------------------------- + +.alert { + padding: @alert-padding; + margin-bottom: @line-height-computed; + border: 1px solid transparent; + border-radius: @alert-border-radius; + + // Headings for larger alerts + h4 { + margin-top: 0; + // Specified for the h4 to prevent conflicts of changing @headings-color + color: inherit; + } + + // Provide class for links that match alerts + .alert-link { + font-weight: @alert-link-font-weight; + } + + // Improve alignment and spacing of inner content + > p, + > ul { + margin-bottom: 0; + } + + > p + p { + margin-top: 5px; + } +} + +// Dismissible alerts +// +// Expand the right padding and account for the close button's positioning. + +.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0. +.alert-dismissible { + padding-right: (@alert-padding + 20); + + // Adjust close link position + .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; + } +} + +// Alternate styles +// +// Generate contextual modifier classes for colorizing the alert. + +.alert-success { + .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text); +} + +.alert-info { + .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text); +} + +.alert-warning { + .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text); +} + +.alert-danger { + .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text); +} diff --git a/css/badges.less b/css/badges.less new file mode 100644 index 0000000..6ee16dc --- /dev/null +++ b/css/badges.less @@ -0,0 +1,66 @@ +// +// Badges +// -------------------------------------------------- + + +// Base class +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: @font-size-small; + font-weight: @badge-font-weight; + color: @badge-color; + line-height: @badge-line-height; + vertical-align: middle; + white-space: nowrap; + text-align: center; + background-color: @badge-bg; + border-radius: @badge-border-radius; + + // Empty badges collapse automatically (not available in IE8) + &:empty { + display: none; + } + + // Quick fix for badges in buttons + .btn & { + position: relative; + top: -1px; + } + + .btn-xs &, + .btn-group-xs > .btn & { + top: 0; + padding: 1px 5px; + } + + // Hover state, but only for links + a& { + &:hover, + &:focus { + color: @badge-link-hover-color; + text-decoration: none; + cursor: pointer; + } + } + + // Account for badges in navs + .list-group-item.active > &, + .nav-pills > .active > a > & { + color: @badge-active-color; + background-color: @badge-active-bg; + } + + .list-group-item > & { + float: right; + } + + .list-group-item > & + & { + margin-right: 5px; + } + + .nav-pills > li > a > & { + margin-left: 3px; + } +} diff --git a/css/bootstrap.css.less b/css/bootstrap.css.less new file mode 100644 index 0000000..7dfdf38 --- /dev/null +++ b/css/bootstrap.css.less @@ -0,0 +1,59 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +// Core variables and mixins +@import "variables.less"; +@import "mixins.less"; + +// Reset and dependencies +@import "normalize.less"; +@import "print.less"; +@import "glyphicons.less"; + +// Core CSS +@import "scaffolding.less"; +@import "type.less"; +@import "code.less"; +@import "grid.less"; +@import "tables.less"; +@import "forms.less"; +@import "buttons.less"; + +// Components +@import "component-animations.less"; +@import "dropdowns.less"; +@import "button-groups.less"; +@import "input-groups.less"; +@import "navs.less"; +@import "navbar.less"; +@import "breadcrumbs.less"; +@import "pagination.less"; +@import "pager.less"; +@import "labels.less"; +@import "badges.less"; +@import "jumbotron.less"; +@import "thumbnails.less"; +@import "alerts.less"; +@import "progress-bars.less"; +@import "media.less"; +@import "list-group.less"; +@import "panels.less"; +@import "responsive-embed.less"; +@import "wells.less"; +@import "close.less"; + +// Components w/ JavaScript +@import "modals.less"; +@import "tooltip.less"; +@import "popovers.less"; +@import "carousel.less"; + +// Utility classes +@import "utilities.less"; +@import "responsive-utilities.less"; + +// Nick +@import "custom.less"; diff --git a/css/breadcrumbs.less b/css/breadcrumbs.less new file mode 100644 index 0000000..cb01d50 --- /dev/null +++ b/css/breadcrumbs.less @@ -0,0 +1,26 @@ +// +// Breadcrumbs +// -------------------------------------------------- + + +.breadcrumb { + padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal; + margin-bottom: @line-height-computed; + list-style: none; + background-color: @breadcrumb-bg; + border-radius: @border-radius-base; + + > li { + display: inline-block; + + + li:before { + content: "@{breadcrumb-separator}\00a0"; // Unicode space added since inline-block means non-collapsing white-space + padding: 0 5px; + color: @breadcrumb-color; + } + } + + > .active { + color: @breadcrumb-active-color; + } +} diff --git a/css/button-groups.less b/css/button-groups.less new file mode 100644 index 0000000..16db0c6 --- /dev/null +++ b/css/button-groups.less @@ -0,0 +1,244 @@ +// +// Button groups +// -------------------------------------------------- + +// Make the div behave like a button +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; // match .btn alignment given font-size hack above + > .btn { + position: relative; + float: left; + // Bring the "active" button to the front + &:hover, + &:focus, + &:active, + &.active { + z-index: 2; + } + } +} + +// Prevent double borders when buttons are next to each other +.btn-group { + .btn + .btn, + .btn + .btn-group, + .btn-group + .btn, + .btn-group + .btn-group { + margin-left: -1px; + } +} + +// Optional: Group multiple button groups together for a toolbar +.btn-toolbar { + margin-left: -5px; // Offset the first child's margin + &:extend(.clearfix all); + + .btn, + .btn-group, + .input-group { + float: left; + } + > .btn, + > .btn-group, + > .input-group { + margin-left: 5px; + } +} + +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} + +// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match +.btn-group > .btn:first-child { + margin-left: 0; + &:not(:last-child):not(.dropdown-toggle) { + .border-right-radius(0); + } +} +// Need .dropdown-toggle since :last-child doesn't apply, given that a .dropdown-menu is used immediately after it +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + .border-left-radius(0); +} + +// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group) +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child:not(:last-child) { + > .btn:last-child, + > .dropdown-toggle { + .border-right-radius(0); + } +} +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + .border-left-radius(0); +} + +// On active and open, don't show outline +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + + +// Sizing +// +// Remix the default button sizing classes into new ones for easier manipulation. + +.btn-group-xs > .btn { &:extend(.btn-xs); } +.btn-group-sm > .btn { &:extend(.btn-sm); } +.btn-group-lg > .btn { &:extend(.btn-lg); } + + +// Split button dropdowns +// ---------------------- + +// Give the line between buttons some depth +.btn-group > .btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-left: 12px; + padding-right: 12px; +} + +// The clickable button for toggling the menu +// Remove the gradient and set the same inset shadow as the :active state +.btn-group.open .dropdown-toggle { + .box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); + + // Show no shadow for `.btn-link` since it has no other button styles. + &.btn-link { + .box-shadow(none); + } +} + + +// Reposition the caret +.btn .caret { + margin-left: 0; +} +// Carets in other button sizes +.btn-lg .caret { + border-width: @caret-width-large @caret-width-large 0; + border-bottom-width: 0; +} +// Upside down carets for .dropup +.dropup .btn-lg .caret { + border-width: 0 @caret-width-large @caret-width-large; +} + + +// Vertical button groups +// ---------------------- + +.btn-group-vertical { + > .btn, + > .btn-group, + > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; + } + + // Clear floats so dropdown menus can be properly placed + > .btn-group { + &:extend(.clearfix all); + > .btn { + float: none; + } + } + + > .btn + .btn, + > .btn + .btn-group, + > .btn-group + .btn, + > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; + } +} + +.btn-group-vertical > .btn { + &:not(:first-child):not(:last-child) { + border-radius: 0; + } + &:first-child:not(:last-child) { + .border-top-radius(@btn-border-radius-base); + .border-bottom-radius(0); + } + &:last-child:not(:first-child) { + .border-top-radius(0); + .border-bottom-radius(@btn-border-radius-base); + } +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) { + > .btn:last-child, + > .dropdown-toggle { + .border-bottom-radius(0); + } +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + .border-top-radius(0); +} + + +// Justified button groups +// ---------------------- + +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; + > .btn, + > .btn-group { + float: none; + display: table-cell; + width: 1%; + } + > .btn-group .btn { + width: 100%; + } + + > .btn-group .dropdown-menu { + left: auto; + } +} + + +// Checkbox and radio options +// +// In order to support the browser's form validation feedback, powered by the +// `required` attribute, we have to "hide" the inputs via `clip`. We cannot use +// `display: none;` or `visibility: hidden;` as that also hides the popover. +// Simply visually hiding the inputs via `opacity` would leave them clickable in +// certain cases which is prevented by using `clip` and `pointer-events`. +// This way, we ensure a DOM element is visible to position the popover from. +// +// See https://github.com/twbs/bootstrap/pull/12794 and +// https://github.com/twbs/bootstrap/pull/14559 for more information. + +[data-toggle="buttons"] { + > .btn, + > .btn-group > .btn { + input[type="radio"], + input[type="checkbox"] { + position: absolute; + clip: rect(0,0,0,0); + pointer-events: none; + } + } +} diff --git a/css/buttons.less b/css/buttons.less new file mode 100644 index 0000000..9cbb8f4 --- /dev/null +++ b/css/buttons.less @@ -0,0 +1,166 @@ +// +// Buttons +// -------------------------------------------------- + + +// Base styles +// -------------------------------------------------- + +.btn { + display: inline-block; + margin-bottom: 0; // For input.btn + font-weight: @btn-font-weight; + text-align: center; + vertical-align: middle; + touch-action: manipulation; + cursor: pointer; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid transparent; + white-space: nowrap; + .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @btn-border-radius-base); + .user-select(none); + + &, + &:active, + &.active { + &:focus, + &.focus { + .tab-focus(); + } + } + + &:hover, + &:focus, + &.focus { + color: @btn-default-color; + text-decoration: none; + } + + &:active, + &.active { + outline: 0; + background-image: none; + .box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); + } + + &.disabled, + &[disabled], + fieldset[disabled] & { + cursor: @cursor-disabled; + .opacity(.65); + .box-shadow(none); + } + + a& { + &.disabled, + fieldset[disabled] & { + pointer-events: none; // Future-proof disabling of clicks on `` elements + } + } +} + + +// Alternate buttons +// -------------------------------------------------- + +.btn-default { + .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border); +} +.btn-primary { + .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border); +} +// Success appears as green +.btn-success { + .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border); +} +// Info appears as blue-green +.btn-info { + .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border); +} +// Warning appears as orange +.btn-warning { + .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border); +} +// Danger and error appear as red +.btn-danger { + .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border); +} + + +// Link buttons +// ------------------------- + +// Make a button look and behave like a link +.btn-link { + color: @link-color; + font-weight: normal; + border-radius: 0; + + &, + &:active, + &.active, + &[disabled], + fieldset[disabled] & { + background-color: transparent; + .box-shadow(none); + } + &, + &:hover, + &:focus, + &:active { + border-color: transparent; + } + &:hover, + &:focus { + color: @link-hover-color; + text-decoration: @link-hover-decoration; + background-color: transparent; + } + &[disabled], + fieldset[disabled] & { + &:hover, + &:focus { + color: @btn-link-disabled-color; + text-decoration: none; + } + } +} + + +// Button Sizes +// -------------------------------------------------- + +.btn-lg { + // line-height: ensure even-numbered height of button next to large input + .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @btn-border-radius-large); +} +.btn-sm { + // line-height: ensure proper height of button next to small input + .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small); +} +.btn-xs { + .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @btn-border-radius-small); +} + + +// Block button +// -------------------------------------------------- + +.btn-block { + display: block; + width: 100%; +} + +// Vertically space out multiple block buttons +.btn-block + .btn-block { + margin-top: 5px; +} + +// Specificity overrides +input[type="submit"], +input[type="reset"], +input[type="button"] { + &.btn-block { + width: 100%; + } +} diff --git a/css/carousel.less b/css/carousel.less new file mode 100644 index 0000000..252011e --- /dev/null +++ b/css/carousel.less @@ -0,0 +1,270 @@ +// +// Carousel +// -------------------------------------------------- + + +// Wrapper for the slide container and indicators +.carousel { + position: relative; +} + +.carousel-inner { + position: relative; + overflow: hidden; + width: 100%; + + > .item { + display: none; + position: relative; + .transition(.6s ease-in-out left); + + // Account for jankitude on images + > img, + > a > img { + &:extend(.img-responsive); + line-height: 1; + } + + // WebKit CSS3 transforms for supported devices + @media all and (transform-3d), (-webkit-transform-3d) { + .transition-transform(~'0.6s ease-in-out'); + .backface-visibility(~'hidden'); + .perspective(1000px); + + &.next, + &.active.right { + .translate3d(100%, 0, 0); + left: 0; + } + &.prev, + &.active.left { + .translate3d(-100%, 0, 0); + left: 0; + } + &.next.left, + &.prev.right, + &.active { + .translate3d(0, 0, 0); + left: 0; + } + } + } + + > .active, + > .next, + > .prev { + display: block; + } + + > .active { + left: 0; + } + + > .next, + > .prev { + position: absolute; + top: 0; + width: 100%; + } + + > .next { + left: 100%; + } + > .prev { + left: -100%; + } + > .next.left, + > .prev.right { + left: 0; + } + + > .active.left { + left: -100%; + } + > .active.right { + left: 100%; + } + +} + +// Left/right controls for nav +// --------------------------- + +.carousel-control { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: @carousel-control-width; + .opacity(@carousel-control-opacity); + font-size: @carousel-control-font-size; + color: @carousel-control-color; + text-align: center; + text-shadow: @carousel-text-shadow; + background-color: rgba(0, 0, 0, 0); // Fix IE9 click-thru bug + // We can't have this transition here because WebKit cancels the carousel + // animation if you trip this while in the middle of another animation. + + // Set gradients for backgrounds + &.left { + #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001)); + } + &.right { + left: auto; + right: 0; + #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5)); + } + + // Hover/focus state + &:hover, + &:focus { + outline: 0; + color: @carousel-control-color; + text-decoration: none; + .opacity(.9); + } + + // Toggles + .icon-prev, + .icon-next, + .glyphicon-chevron-left, + .glyphicon-chevron-right { + position: absolute; + top: 50%; + margin-top: -10px; + z-index: 5; + display: inline-block; + } + .icon-prev, + .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; + } + .icon-next, + .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; + } + .icon-prev, + .icon-next { + width: 20px; + height: 20px; + line-height: 1; + font-family: serif; + } + + + .icon-prev { + &:before { + content: '\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039) + } + } + .icon-next { + &:before { + content: '\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A) + } + } +} + +// Optional indicator pips +// +// Add an unordered list with the following class and add a list item for each +// slide your carousel holds. + +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + margin-left: -30%; + padding-left: 0; + list-style: none; + text-align: center; + + li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + border: 1px solid @carousel-indicator-border-color; + border-radius: 10px; + cursor: pointer; + + // IE8-9 hack for event handling + // + // Internet Explorer 8-9 does not support clicks on elements without a set + // `background-color`. We cannot use `filter` since that's not viewed as a + // background color by the browser. Thus, a hack is needed. + // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Internet_Explorer + // + // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we + // set alpha transparency for the best results possible. + background-color: #000 \9; // IE8 + background-color: rgba(0,0,0,0); // IE9 + } + .active { + margin: 0; + width: 12px; + height: 12px; + background-color: @carousel-indicator-active-bg; + } +} + +// Optional captions +// ----------------------------- +// Hidden by default for smaller viewports +.carousel-caption { + position: absolute; + left: 15%; + right: 15%; + bottom: 20px; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: @carousel-caption-color; + text-align: center; + text-shadow: @carousel-text-shadow; + & .btn { + text-shadow: none; // No shadow for button elements in carousel-caption + } +} + + +// Scale up controls for tablets and up +@media screen and (min-width: @screen-sm-min) { + + // Scale up the controls a smidge + .carousel-control { + .glyphicon-chevron-left, + .glyphicon-chevron-right, + .icon-prev, + .icon-next { + width: (@carousel-control-font-size * 1.5); + height: (@carousel-control-font-size * 1.5); + margin-top: (@carousel-control-font-size / -2); + font-size: (@carousel-control-font-size * 1.5); + } + .glyphicon-chevron-left, + .icon-prev { + margin-left: (@carousel-control-font-size / -2); + } + .glyphicon-chevron-right, + .icon-next { + margin-right: (@carousel-control-font-size / -2); + } + } + + // Show and left align the captions + .carousel-caption { + left: 20%; + right: 20%; + padding-bottom: 30px; + } + + // Move up the indicators + .carousel-indicators { + bottom: 20px; + } +} diff --git a/css/close.less b/css/close.less new file mode 100644 index 0000000..6d5bfe0 --- /dev/null +++ b/css/close.less @@ -0,0 +1,34 @@ +// +// Close icons +// -------------------------------------------------- + + +.close { + float: right; + font-size: (@font-size-base * 1.5); + font-weight: @close-font-weight; + line-height: 1; + color: @close-color; + text-shadow: @close-text-shadow; + .opacity(.2); + + &:hover, + &:focus { + color: @close-color; + text-decoration: none; + cursor: pointer; + .opacity(.5); + } + + // Additional properties for button version + // iOS requires the button element instead of an anchor tag. + // If you want the anchor version, it requires `href="#"`. + // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile + button& { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; + } +} diff --git a/css/code.less b/css/code.less new file mode 100644 index 0000000..a08b4d4 --- /dev/null +++ b/css/code.less @@ -0,0 +1,69 @@ +// +// Code (inline and block) +// -------------------------------------------------- + + +// Inline and block code styles +code, +kbd, +pre, +samp { + font-family: @font-family-monospace; +} + +// Inline code +code { + padding: 2px 4px; + font-size: 90%; + color: @code-color; + background-color: @code-bg; + border-radius: @border-radius-base; +} + +// User input typically entered via keyboard +kbd { + padding: 2px 4px; + font-size: 90%; + color: @kbd-color; + background-color: @kbd-bg; + border-radius: @border-radius-small; + box-shadow: inset 0 -1px 0 rgba(0,0,0,.25); + + kbd { + padding: 0; + font-size: 100%; + font-weight: bold; + box-shadow: none; + } +} + +// Blocks of code +pre { + display: block; + padding: ((@line-height-computed - 1) / 2); + margin: 0 0 (@line-height-computed / 2); + font-size: (@font-size-base - 1); // 14px to 13px + line-height: @line-height-base; + word-break: break-all; + word-wrap: break-word; + color: @pre-color; + background-color: @pre-bg; + border: 1px solid @pre-border-color; + border-radius: @border-radius-base; + + // Account for some code outputs that place code tags in pre tags + code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; + } +} + +// Enable scrollable blocks of code +.pre-scrollable { + max-height: @pre-scrollable-max-height; + overflow-y: scroll; +} diff --git a/css/component-animations.less b/css/component-animations.less new file mode 100644 index 0000000..0bcee91 --- /dev/null +++ b/css/component-animations.less @@ -0,0 +1,33 @@ +// +// Component animations +// -------------------------------------------------- + +// Heads up! +// +// We don't use the `.opacity()` mixin here since it causes a bug with text +// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552. + +.fade { + opacity: 0; + .transition(opacity .15s linear); + &.in { + opacity: 1; + } +} + +.collapse { + display: none; + + &.in { display: block; } + tr&.in { display: table-row; } + tbody&.in { display: table-row-group; } +} + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + .transition-property(~"height, visibility"); + .transition-duration(.35s); + .transition-timing-function(ease); +} diff --git a/css/custom.less b/css/custom.less new file mode 100644 index 0000000..0f05cda --- /dev/null +++ b/css/custom.less @@ -0,0 +1,81 @@ +// Nick + +@import "variables.less"; + +@footer-link-color: darken(@brand-success, 10%); +@footer-link-hover-color: darken(@footer-link-color, 15%); + +// use these for heading-sized text when you don't want extra spacing +.text-h1 { + font-size: @font-size-h1; +} + +.text-h2 { + font-size: @font-size-h2; +} + +.text-h3 { + font-size: @font-size-h3; +} + +.text-h4 { + font-size: @font-size-h4; +} + +.text-h5 { + font-size: @font-size-h5; +} + +.text-h6 { + font-size: @font-size-h6; +} + +// apply this to svg, see _svg/icon_search.svg +.svg-icon { + display: inline-block; + width: 1em; + height: 1em; + stroke-width: 0; + stroke: currentColor; + fill: currentColor; + position: relative; + bottom: -.125em; +} + +// use this to display the pi symbol without horrible blocky look in Sans +.text-serif { + font-family: @font-family-serif; +} + +// apply this to table to get an icon with vertically centred text next to it +.icon-and-text { + vertical-align: middle; + td { + padding: .25em .5em; + } +} + +.search-results li { + margin-bottom: .5em; +} + +// see https://stackoverflow.com/questions/20547819/vertical-align-with-bootstrap-3 +.vbottom { + display: inline-block; + vertical-align: bottom; + float: none; +} + +.footer { + padding: 20px; + background-color: @gray-light2; + a { + color: @footer-link-color; + + &:hover, + &:focus { + color: @footer-link-hover-color; + //background-color: @footer-link-hover-bg; + } + } +} diff --git a/css/dropdowns.less b/css/dropdowns.less new file mode 100644 index 0000000..f6876c1 --- /dev/null +++ b/css/dropdowns.less @@ -0,0 +1,216 @@ +// +// Dropdown menus +// -------------------------------------------------- + + +// Dropdown arrow/caret +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: @caret-width-base dashed; + border-top: @caret-width-base solid ~"\9"; // IE8 + border-right: @caret-width-base solid transparent; + border-left: @caret-width-base solid transparent; +} + +// The dropdown wrapper (div) +.dropup, +.dropdown { + position: relative; +} + +// Prevent the focus on the dropdown toggle when closing dropdowns +.dropdown-toggle:focus { + outline: 0; +} + +// The dropdown menu (ul) +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: @zindex-dropdown; + display: none; // none by default, but block on "open" of the menu + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; // override default ul + list-style: none; + font-size: @font-size-base; + text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer) + background-color: @dropdown-bg; + border: 1px solid @dropdown-fallback-border; // IE8 fallback + border: 1px solid @dropdown-border; + border-radius: @border-radius-base; + .box-shadow(0 6px 12px rgba(0,0,0,.175)); + background-clip: padding-box; + + // Aligns the dropdown menu to right + // + // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]` + &.pull-right { + right: 0; + left: auto; + } + + // Dividers (basically an hr) within the dropdown + .divider { + .nav-divider(@dropdown-divider-bg); + } + + // Links within the dropdown menu + > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: @line-height-base; + color: @dropdown-link-color; + white-space: nowrap; // prevent links from randomly breaking onto new lines + } +} + +// Hover/Focus state +.dropdown-menu > li > a { + &:hover, + &:focus { + text-decoration: none; + color: @dropdown-link-hover-color; + background-color: @dropdown-link-hover-bg; + } +} + +// Active state +.dropdown-menu > .active > a { + &, + &:hover, + &:focus { + color: @dropdown-link-active-color; + text-decoration: none; + outline: 0; + background-color: @dropdown-link-active-bg; + } +} + +// Disabled state +// +// Gray out text and ensure the hover/focus state remains gray + +.dropdown-menu > .disabled > a { + &, + &:hover, + &:focus { + color: @dropdown-link-disabled-color; + } + + // Nuke hover/focus effects + &:hover, + &:focus { + text-decoration: none; + background-color: transparent; + background-image: none; // Remove CSS gradient + .reset-filter(); + cursor: @cursor-disabled; + } +} + +// Open state for the dropdown +.open { + // Show the menu + > .dropdown-menu { + display: block; + } + + // Remove the outline when :focus is triggered + > a { + outline: 0; + } +} + +// Menu positioning +// +// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown +// menu with the parent. +.dropdown-menu-right { + left: auto; // Reset the default from `.dropdown-menu` + right: 0; +} +// With v3, we enabled auto-flipping if you have a dropdown within a right +// aligned nav component. To enable the undoing of that, we provide an override +// to restore the default dropdown menu alignment. +// +// This is only for left-aligning a dropdown menu within a `.navbar-right` or +// `.pull-right` nav component. +.dropdown-menu-left { + left: 0; + right: auto; +} + +// Dropdown section headers +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: @font-size-small; + line-height: @line-height-base; + color: @dropdown-header-color; + white-space: nowrap; // as with > li > a +} + +// Backdrop to catch body clicks on mobile, etc. +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: (@zindex-dropdown - 10); +} + +// Right aligned dropdowns +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} + +// Allow for dropdowns to go bottom up (aka, dropup-menu) +// +// Just add .dropup after the standard .dropdown class and you're set, bro. +// TODO: abstract this so that the navbar fixed styles are not placed here? + +.dropup, +.navbar-fixed-bottom .dropdown { + // Reverse the caret + .caret { + border-top: 0; + border-bottom: @caret-width-base dashed; + border-bottom: @caret-width-base solid ~"\9"; // IE8 + content: ""; + } + // Different positioning for bottom up menu + .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; + } +} + + +// Component alignment +// +// Reiterate per navbar.less and the modified component alignment there. + +@media (min-width: @grid-float-breakpoint) { + .navbar-right { + .dropdown-menu { + .dropdown-menu-right(); + } + // Necessary for overrides of the default right aligned menu. + // Will remove come v4 in all likelihood. + .dropdown-menu-left { + .dropdown-menu-left(); + } + } +} diff --git a/css/forms.less b/css/forms.less new file mode 100644 index 0000000..9377d38 --- /dev/null +++ b/css/forms.less @@ -0,0 +1,613 @@ +// +// Forms +// -------------------------------------------------- + + +// Normalize non-controls +// +// Restyle and baseline non-control form elements. + +fieldset { + padding: 0; + margin: 0; + border: 0; + // Chrome and Firefox set a `min-width: min-content;` on fieldsets, + // so we reset that to ensure it behaves more like a standard block element. + // See https://github.com/twbs/bootstrap/issues/12359. + min-width: 0; +} + +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: @line-height-computed; + font-size: (@font-size-base * 1.5); + line-height: inherit; + color: @legend-color; + border: 0; + border-bottom: 1px solid @legend-border-color; +} + +label { + display: inline-block; + max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141) + margin-bottom: 5px; + font-weight: bold; +} + + +// Normalize form controls +// +// While most of our form styles require extra classes, some basic normalization +// is required to ensure optimum display with or without those classes to better +// address browser inconsistencies. + +// Override content-box in Normalize (* isn't specific enough) +input[type="search"] { + .box-sizing(border-box); +} + +// Position radios and checkboxes better +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; // IE8-9 + line-height: normal; +} + +input[type="file"] { + display: block; +} + +// Make range inputs behave like textual form controls +input[type="range"] { + display: block; + width: 100%; +} + +// Make multiple select elements height not fixed +select[multiple], +select[size] { + height: auto; +} + +// Focus for file, radio, and checkbox +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + .tab-focus(); +} + +// Adjust output element +output { + display: block; + padding-top: (@padding-base-vertical + 1); + font-size: @font-size-base; + line-height: @line-height-base; + color: @input-color; +} + + +// Common form controls +// +// Shared size and type resets for form controls. Apply `.form-control` to any +// of the following form controls: +// +// select +// textarea +// input[type="text"] +// input[type="password"] +// input[type="datetime"] +// input[type="datetime-local"] +// input[type="date"] +// input[type="month"] +// input[type="time"] +// input[type="week"] +// input[type="number"] +// input[type="email"] +// input[type="url"] +// input[type="search"] +// input[type="tel"] +// input[type="color"] + +.form-control { + display: block; + width: 100%; + height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border) + padding: @padding-base-vertical @padding-base-horizontal; + font-size: @font-size-base; + line-height: @line-height-base; + color: @input-color; + background-color: @input-bg; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid @input-border; + border-radius: @input-border-radius; // Note: This has no effect on s in CSS. + .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); + .transition(~"border-color ease-in-out .15s, box-shadow ease-in-out .15s"); + + // Customize the `:focus` state to imitate native WebKit styles. + .form-control-focus(); + + // Placeholder + .placeholder(); + + // Unstyle the caret on `` +// element gets special love because it's special, and that's a fact! +.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) { + height: @input-height; + padding: @padding-vertical @padding-horizontal; + font-size: @font-size; + line-height: @line-height; + border-radius: @border-radius; + + select& { + height: @input-height; + line-height: @input-height; + } + + textarea&, + select[multiple]& { + height: auto; + } +} diff --git a/css/mixins/gradients.less b/css/mixins/gradients.less new file mode 100644 index 0000000..0b88a89 --- /dev/null +++ b/css/mixins/gradients.less @@ -0,0 +1,59 @@ +// Gradients + +#gradient { + + // Horizontal gradient, from left to right + // + // Creates two color stops, start and end, by specifying a color and position for each color stop. + // Color stops are not available in IE9 and below. + .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) { + background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+ + background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12 + background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@start-color),argb(@end-color))); // IE9 and down + } + + // Vertical gradient, from top to bottom + // + // Creates two color stops, start and end, by specifying a color and position for each color stop. + // Color stops are not available in IE9 and below. + .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) { + background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+ + background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12 + background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@start-color),argb(@end-color))); // IE9 and down + } + + .directional(@start-color: #555; @end-color: #333; @deg: 45deg) { + background-repeat: repeat-x; + background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+ + background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12 + background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+ + } + .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) { + background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color); + background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color); + background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback + } + .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) { + background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color); + background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color); + background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback + } + .radial(@inner-color: #555; @outer-color: #333) { + background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color); + background-image: radial-gradient(circle, @inner-color, @outer-color); + background-repeat: no-repeat; + } + .striped(@color: rgba(255,255,255,.15); @angle: 45deg) { + background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent); + background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent); + } +} diff --git a/css/mixins/grid-framework.less b/css/mixins/grid-framework.less new file mode 100644 index 0000000..8c23eed --- /dev/null +++ b/css/mixins/grid-framework.less @@ -0,0 +1,91 @@ +// Framework grid generation +// +// Used only by Bootstrap to generate the correct number of grid classes given +// any value of `@grid-columns`. + +.make-grid-columns() { + // Common styles for all sizes of grid columns, widths 1-12 + .col(@index) { // initial + @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}"; + .col((@index + 1), @item); + } + .col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo + @item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}"; + .col((@index + 1), ~"@{list}, @{item}"); + } + .col(@index, @list) when (@index > @grid-columns) { // terminal + @{list} { + position: relative; + // Prevent columns from collapsing when empty + min-height: 1px; + // Inner gutter via padding + padding-left: ceil((@grid-gutter-width / 2)); + padding-right: floor((@grid-gutter-width / 2)); + } + } + .col(1); // kickstart it +} + +.float-grid-columns(@class) { + .col(@index) { // initial + @item: ~".col-@{class}-@{index}"; + .col((@index + 1), @item); + } + .col(@index, @list) when (@index =< @grid-columns) { // general + @item: ~".col-@{class}-@{index}"; + .col((@index + 1), ~"@{list}, @{item}"); + } + .col(@index, @list) when (@index > @grid-columns) { // terminal + @{list} { + float: left; + } + } + .col(1); // kickstart it +} + +.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) { + .col-@{class}-@{index} { + width: percentage((@index / @grid-columns)); + } +} +.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) { + .col-@{class}-push-@{index} { + left: percentage((@index / @grid-columns)); + } +} +.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) { + .col-@{class}-push-0 { + left: auto; + } +} +.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) { + .col-@{class}-pull-@{index} { + right: percentage((@index / @grid-columns)); + } +} +.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) { + .col-@{class}-pull-0 { + right: auto; + } +} +.calc-grid-column(@index, @class, @type) when (@type = offset) { + .col-@{class}-offset-@{index} { + margin-left: percentage((@index / @grid-columns)); + } +} + +// Basic looping in LESS +.loop-grid-columns(@index, @class, @type) when (@index >= 0) { + .calc-grid-column(@index, @class, @type); + // next iteration + .loop-grid-columns((@index - 1), @class, @type); +} + +// Create grid for specific class +.make-grid(@class) { + .float-grid-columns(@class); + .loop-grid-columns(@grid-columns, @class, width); + .loop-grid-columns(@grid-columns, @class, pull); + .loop-grid-columns(@grid-columns, @class, push); + .loop-grid-columns(@grid-columns, @class, offset); +} diff --git a/css/mixins/grid.less b/css/mixins/grid.less new file mode 100644 index 0000000..df496d0 --- /dev/null +++ b/css/mixins/grid.less @@ -0,0 +1,122 @@ +// Grid system +// +// Generate semantic grid columns with these mixins. + +// Centered container element +.container-fixed(@gutter: @grid-gutter-width) { + margin-right: auto; + margin-left: auto; + padding-left: floor((@gutter / 2)); + padding-right: ceil((@gutter / 2)); + &:extend(.clearfix all); +} + +// Creates a wrapper for a series of columns +.make-row(@gutter: @grid-gutter-width) { + margin-left: ceil((@gutter / -2)); + margin-right: floor((@gutter / -2)); + &:extend(.clearfix all); +} + +// Generate the extra small columns +.make-xs-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + float: left; + width: percentage((@columns / @grid-columns)); + min-height: 1px; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); +} +.make-xs-column-offset(@columns) { + margin-left: percentage((@columns / @grid-columns)); +} +.make-xs-column-push(@columns) { + left: percentage((@columns / @grid-columns)); +} +.make-xs-column-pull(@columns) { + right: percentage((@columns / @grid-columns)); +} + +// Generate the small columns +.make-sm-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); + + @media (min-width: @screen-sm-min) { + float: left; + width: percentage((@columns / @grid-columns)); + } +} +.make-sm-column-offset(@columns) { + @media (min-width: @screen-sm-min) { + margin-left: percentage((@columns / @grid-columns)); + } +} +.make-sm-column-push(@columns) { + @media (min-width: @screen-sm-min) { + left: percentage((@columns / @grid-columns)); + } +} +.make-sm-column-pull(@columns) { + @media (min-width: @screen-sm-min) { + right: percentage((@columns / @grid-columns)); + } +} + +// Generate the medium columns +.make-md-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); + + @media (min-width: @screen-md-min) { + float: left; + width: percentage((@columns / @grid-columns)); + } +} +.make-md-column-offset(@columns) { + @media (min-width: @screen-md-min) { + margin-left: percentage((@columns / @grid-columns)); + } +} +.make-md-column-push(@columns) { + @media (min-width: @screen-md-min) { + left: percentage((@columns / @grid-columns)); + } +} +.make-md-column-pull(@columns) { + @media (min-width: @screen-md-min) { + right: percentage((@columns / @grid-columns)); + } +} + +// Generate the large columns +.make-lg-column(@columns; @gutter: @grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: (@gutter / 2); + padding-right: (@gutter / 2); + + @media (min-width: @screen-lg-min) { + float: left; + width: percentage((@columns / @grid-columns)); + } +} +.make-lg-column-offset(@columns) { + @media (min-width: @screen-lg-min) { + margin-left: percentage((@columns / @grid-columns)); + } +} +.make-lg-column-push(@columns) { + @media (min-width: @screen-lg-min) { + left: percentage((@columns / @grid-columns)); + } +} +.make-lg-column-pull(@columns) { + @media (min-width: @screen-lg-min) { + right: percentage((@columns / @grid-columns)); + } +} diff --git a/css/mixins/hide-text.less b/css/mixins/hide-text.less new file mode 100644 index 0000000..2bb84a3 --- /dev/null +++ b/css/mixins/hide-text.less @@ -0,0 +1,21 @@ +// CSS image replacement +// +// Heads up! v3 launched with only `.hide-text()`, but per our pattern for +// mixins being reused as classes with the same name, this doesn't hold up. As +// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`. +// +// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 + +// Deprecated as of v3.0.1 (has been removed in v4) +.hide-text() { + font: ~"0/0" a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +// New mixin to use as of v3.0.1 +.text-hide() { + .hide-text(); +} diff --git a/css/mixins/image.less b/css/mixins/image.less new file mode 100644 index 0000000..f233cb3 --- /dev/null +++ b/css/mixins/image.less @@ -0,0 +1,33 @@ +// Image Mixins +// - Responsive image +// - Retina image + + +// Responsive image +// +// Keep images from scaling beyond the width of their parents. +.img-responsive(@display: block) { + display: @display; + max-width: 100%; // Part 1: Set a maximum relative to the parent + height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching +} + + +// Retina image +// +// Short retina mixin for setting background-image and -size. Note that the +// spelling of `min--moz-device-pixel-ratio` is intentional. +.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) { + background-image: url("@{file-1x}"); + + @media + only screen and (-webkit-min-device-pixel-ratio: 2), + only screen and ( min--moz-device-pixel-ratio: 2), + only screen and ( -o-min-device-pixel-ratio: 2/1), + only screen and ( min-device-pixel-ratio: 2), + only screen and ( min-resolution: 192dpi), + only screen and ( min-resolution: 2dppx) { + background-image: url("@{file-2x}"); + background-size: @width-1x @height-1x; + } +} diff --git a/css/mixins/labels.less b/css/mixins/labels.less new file mode 100644 index 0000000..9f7a67e --- /dev/null +++ b/css/mixins/labels.less @@ -0,0 +1,12 @@ +// Labels + +.label-variant(@color) { + background-color: @color; + + &[href] { + &:hover, + &:focus { + background-color: darken(@color, 10%); + } + } +} diff --git a/css/mixins/list-group.less b/css/mixins/list-group.less new file mode 100644 index 0000000..03aa190 --- /dev/null +++ b/css/mixins/list-group.less @@ -0,0 +1,30 @@ +// List Groups + +.list-group-item-variant(@state; @background; @color) { + .list-group-item-@{state} { + color: @color; + background-color: @background; + + a&, + button& { + color: @color; + + .list-group-item-heading { + color: inherit; + } + + &:hover, + &:focus { + color: @color; + background-color: darken(@background, 5%); + } + &.active, + &.active:hover, + &.active:focus { + color: #fff; + background-color: @color; + border-color: @color; + } + } + } +} diff --git a/css/mixins/nav-divider.less b/css/mixins/nav-divider.less new file mode 100644 index 0000000..feb1e9e --- /dev/null +++ b/css/mixins/nav-divider.less @@ -0,0 +1,10 @@ +// Horizontal dividers +// +// Dividers (basically an hr) within dropdowns and nav lists + +.nav-divider(@color: #e5e5e5) { + height: 1px; + margin: ((@line-height-computed / 2) - 1) 0; + overflow: hidden; + background-color: @color; +} diff --git a/css/mixins/nav-vertical-align.less b/css/mixins/nav-vertical-align.less new file mode 100644 index 0000000..d458c78 --- /dev/null +++ b/css/mixins/nav-vertical-align.less @@ -0,0 +1,9 @@ +// Navbar vertical align +// +// Vertically center elements in the navbar. +// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin. + +.navbar-vertical-align(@element-height) { + margin-top: ((@navbar-height - @element-height) / 2); + margin-bottom: ((@navbar-height - @element-height) / 2); +} diff --git a/css/mixins/opacity.less b/css/mixins/opacity.less new file mode 100644 index 0000000..33ed25c --- /dev/null +++ b/css/mixins/opacity.less @@ -0,0 +1,8 @@ +// Opacity + +.opacity(@opacity) { + opacity: @opacity; + // IE8 filter + @opacity-ie: (@opacity * 100); + filter: ~"alpha(opacity=@{opacity-ie})"; +} diff --git a/css/mixins/pagination.less b/css/mixins/pagination.less new file mode 100644 index 0000000..618804f --- /dev/null +++ b/css/mixins/pagination.less @@ -0,0 +1,24 @@ +// Pagination + +.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) { + > li { + > a, + > span { + padding: @padding-vertical @padding-horizontal; + font-size: @font-size; + line-height: @line-height; + } + &:first-child { + > a, + > span { + .border-left-radius(@border-radius); + } + } + &:last-child { + > a, + > span { + .border-right-radius(@border-radius); + } + } + } +} diff --git a/css/mixins/panels.less b/css/mixins/panels.less new file mode 100644 index 0000000..49ee10d --- /dev/null +++ b/css/mixins/panels.less @@ -0,0 +1,24 @@ +// Panels + +.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) { + border-color: @border; + + & > .panel-heading { + color: @heading-text-color; + background-color: @heading-bg-color; + border-color: @heading-border; + + + .panel-collapse > .panel-body { + border-top-color: @border; + } + .badge { + color: @heading-bg-color; + background-color: @heading-text-color; + } + } + & > .panel-footer { + + .panel-collapse > .panel-body { + border-bottom-color: @border; + } + } +} diff --git a/css/mixins/progress-bar.less b/css/mixins/progress-bar.less new file mode 100644 index 0000000..f07996a --- /dev/null +++ b/css/mixins/progress-bar.less @@ -0,0 +1,10 @@ +// Progress bars + +.progress-bar-variant(@color) { + background-color: @color; + + // Deprecated parent class requirement as of v3.2.0 + .progress-striped & { + #gradient > .striped(); + } +} diff --git a/css/mixins/reset-filter.less b/css/mixins/reset-filter.less new file mode 100644 index 0000000..68cdb5e --- /dev/null +++ b/css/mixins/reset-filter.less @@ -0,0 +1,8 @@ +// Reset filters for IE +// +// When you need to remove a gradient background, do not forget to use this to reset +// the IE filter for IE9 and below. + +.reset-filter() { + filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); +} diff --git a/css/mixins/reset-text.less b/css/mixins/reset-text.less new file mode 100644 index 0000000..58dd4d1 --- /dev/null +++ b/css/mixins/reset-text.less @@ -0,0 +1,18 @@ +.reset-text() { + font-family: @font-family-base; + // We deliberately do NOT reset font-size. + font-style: normal; + font-weight: normal; + letter-spacing: normal; + line-break: auto; + line-height: @line-height-base; + text-align: left; // Fallback for where `start` is not supported + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + white-space: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; +} diff --git a/css/mixins/resize.less b/css/mixins/resize.less new file mode 100644 index 0000000..3acd3af --- /dev/null +++ b/css/mixins/resize.less @@ -0,0 +1,6 @@ +// Resize anything + +.resizable(@direction) { + resize: @direction; // Options: horizontal, vertical, both + overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible` +} diff --git a/css/mixins/responsive-visibility.less b/css/mixins/responsive-visibility.less new file mode 100644 index 0000000..ecf1e97 --- /dev/null +++ b/css/mixins/responsive-visibility.less @@ -0,0 +1,15 @@ +// Responsive utilities + +// +// More easily include all the states for responsive-utilities.less. +.responsive-visibility() { + display: block !important; + table& { display: table !important; } + tr& { display: table-row !important; } + th&, + td& { display: table-cell !important; } +} + +.responsive-invisibility() { + display: none !important; +} diff --git a/css/mixins/size.less b/css/mixins/size.less new file mode 100644 index 0000000..a8be650 --- /dev/null +++ b/css/mixins/size.less @@ -0,0 +1,10 @@ +// Sizing shortcuts + +.size(@width; @height) { + width: @width; + height: @height; +} + +.square(@size) { + .size(@size; @size); +} diff --git a/css/mixins/tab-focus.less b/css/mixins/tab-focus.less new file mode 100644 index 0000000..d12d236 --- /dev/null +++ b/css/mixins/tab-focus.less @@ -0,0 +1,9 @@ +// WebKit-style focus + +.tab-focus() { + // WebKit-specific. Other browsers will keep their default outline style. + // (Initially tried to also force default via `outline: initial`, + // but that seems to erroneously remove the outline in Firefox altogether.) + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} diff --git a/css/mixins/table-row.less b/css/mixins/table-row.less new file mode 100644 index 0000000..0f287f1 --- /dev/null +++ b/css/mixins/table-row.less @@ -0,0 +1,28 @@ +// Tables + +.table-row-variant(@state; @background) { + // Exact selectors below required to override `.table-striped` and prevent + // inheritance to nested tables. + .table > thead > tr, + .table > tbody > tr, + .table > tfoot > tr { + > td.@{state}, + > th.@{state}, + &.@{state} > td, + &.@{state} > th { + background-color: @background; + } + } + + // Hover states for `.table-hover` + // Note: this is not available for cells or rows within `thead` or `tfoot`. + .table-hover > tbody > tr { + > td.@{state}:hover, + > th.@{state}:hover, + &.@{state}:hover > td, + &:hover > .@{state}, + &.@{state}:hover > th { + background-color: darken(@background, 5%); + } + } +} diff --git a/css/mixins/text-emphasis.less b/css/mixins/text-emphasis.less new file mode 100644 index 0000000..9e8a77a --- /dev/null +++ b/css/mixins/text-emphasis.less @@ -0,0 +1,9 @@ +// Typography + +.text-emphasis-variant(@color) { + color: @color; + a&:hover, + a&:focus { + color: darken(@color, 10%); + } +} diff --git a/css/mixins/text-overflow.less b/css/mixins/text-overflow.less new file mode 100644 index 0000000..c11ad2f --- /dev/null +++ b/css/mixins/text-overflow.less @@ -0,0 +1,8 @@ +// Text overflow +// Requires inline-block or block for proper styling + +.text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/css/mixins/vendor-prefixes.less b/css/mixins/vendor-prefixes.less new file mode 100644 index 0000000..2b5e74b --- /dev/null +++ b/css/mixins/vendor-prefixes.less @@ -0,0 +1,227 @@ +// Vendor Prefixes +// +// All vendor mixins are deprecated as of v3.2.0 due to the introduction of +// Autoprefixer in our Gruntfile. They have been removed in v4. + +// - Animations +// - Backface visibility +// - Box shadow +// - Box sizing +// - Content columns +// - Hyphens +// - Placeholder text +// - Transformations +// - Transitions +// - User Select + + +// Animations +.animation(@animation) { + -webkit-animation: @animation; + -o-animation: @animation; + animation: @animation; +} +.animation-name(@name) { + -webkit-animation-name: @name; + animation-name: @name; +} +.animation-duration(@duration) { + -webkit-animation-duration: @duration; + animation-duration: @duration; +} +.animation-timing-function(@timing-function) { + -webkit-animation-timing-function: @timing-function; + animation-timing-function: @timing-function; +} +.animation-delay(@delay) { + -webkit-animation-delay: @delay; + animation-delay: @delay; +} +.animation-iteration-count(@iteration-count) { + -webkit-animation-iteration-count: @iteration-count; + animation-iteration-count: @iteration-count; +} +.animation-direction(@direction) { + -webkit-animation-direction: @direction; + animation-direction: @direction; +} +.animation-fill-mode(@fill-mode) { + -webkit-animation-fill-mode: @fill-mode; + animation-fill-mode: @fill-mode; +} + +// Backface visibility +// Prevent browsers from flickering when using CSS 3D transforms. +// Default value is `visible`, but can be changed to `hidden` + +.backface-visibility(@visibility) { + -webkit-backface-visibility: @visibility; + -moz-backface-visibility: @visibility; + backface-visibility: @visibility; +} + +// Drop shadows +// +// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's +// supported browsers that have box shadow capabilities now support it. + +.box-shadow(@shadow) { + -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1 + box-shadow: @shadow; +} + +// Box sizing +.box-sizing(@boxmodel) { + -webkit-box-sizing: @boxmodel; + -moz-box-sizing: @boxmodel; + box-sizing: @boxmodel; +} + +// CSS3 Content Columns +.content-columns(@column-count; @column-gap: @grid-gutter-width) { + -webkit-column-count: @column-count; + -moz-column-count: @column-count; + column-count: @column-count; + -webkit-column-gap: @column-gap; + -moz-column-gap: @column-gap; + column-gap: @column-gap; +} + +// Optional hyphenation +.hyphens(@mode: auto) { + word-wrap: break-word; + -webkit-hyphens: @mode; + -moz-hyphens: @mode; + -ms-hyphens: @mode; // IE10+ + -o-hyphens: @mode; + hyphens: @mode; +} + +// Placeholder text +.placeholder(@color: @input-color-placeholder) { + // Firefox + &::-moz-placeholder { + color: @color; + opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526 + } + &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+ + &::-webkit-input-placeholder { color: @color; } // Safari and Chrome +} + +// Transformations +.scale(@ratio) { + -webkit-transform: scale(@ratio); + -ms-transform: scale(@ratio); // IE9 only + -o-transform: scale(@ratio); + transform: scale(@ratio); +} +.scale(@ratioX; @ratioY) { + -webkit-transform: scale(@ratioX, @ratioY); + -ms-transform: scale(@ratioX, @ratioY); // IE9 only + -o-transform: scale(@ratioX, @ratioY); + transform: scale(@ratioX, @ratioY); +} +.scaleX(@ratio) { + -webkit-transform: scaleX(@ratio); + -ms-transform: scaleX(@ratio); // IE9 only + -o-transform: scaleX(@ratio); + transform: scaleX(@ratio); +} +.scaleY(@ratio) { + -webkit-transform: scaleY(@ratio); + -ms-transform: scaleY(@ratio); // IE9 only + -o-transform: scaleY(@ratio); + transform: scaleY(@ratio); +} +.skew(@x; @y) { + -webkit-transform: skewX(@x) skewY(@y); + -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+ + -o-transform: skewX(@x) skewY(@y); + transform: skewX(@x) skewY(@y); +} +.translate(@x; @y) { + -webkit-transform: translate(@x, @y); + -ms-transform: translate(@x, @y); // IE9 only + -o-transform: translate(@x, @y); + transform: translate(@x, @y); +} +.translate3d(@x; @y; @z) { + -webkit-transform: translate3d(@x, @y, @z); + transform: translate3d(@x, @y, @z); +} +.rotate(@degrees) { + -webkit-transform: rotate(@degrees); + -ms-transform: rotate(@degrees); // IE9 only + -o-transform: rotate(@degrees); + transform: rotate(@degrees); +} +.rotateX(@degrees) { + -webkit-transform: rotateX(@degrees); + -ms-transform: rotateX(@degrees); // IE9 only + -o-transform: rotateX(@degrees); + transform: rotateX(@degrees); +} +.rotateY(@degrees) { + -webkit-transform: rotateY(@degrees); + -ms-transform: rotateY(@degrees); // IE9 only + -o-transform: rotateY(@degrees); + transform: rotateY(@degrees); +} +.perspective(@perspective) { + -webkit-perspective: @perspective; + -moz-perspective: @perspective; + perspective: @perspective; +} +.perspective-origin(@perspective) { + -webkit-perspective-origin: @perspective; + -moz-perspective-origin: @perspective; + perspective-origin: @perspective; +} +.transform-origin(@origin) { + -webkit-transform-origin: @origin; + -moz-transform-origin: @origin; + -ms-transform-origin: @origin; // IE9 only + transform-origin: @origin; +} + + +// Transitions + +.transition(@transition) { + -webkit-transition: @transition; + -o-transition: @transition; + transition: @transition; +} +.transition-property(@transition-property) { + -webkit-transition-property: @transition-property; + transition-property: @transition-property; +} +.transition-delay(@transition-delay) { + -webkit-transition-delay: @transition-delay; + transition-delay: @transition-delay; +} +.transition-duration(@transition-duration) { + -webkit-transition-duration: @transition-duration; + transition-duration: @transition-duration; +} +.transition-timing-function(@timing-function) { + -webkit-transition-timing-function: @timing-function; + transition-timing-function: @timing-function; +} +.transition-transform(@transition) { + -webkit-transition: -webkit-transform @transition; + -moz-transition: -moz-transform @transition; + -o-transition: -o-transform @transition; + transition: transform @transition; +} + + +// User select +// For selecting text on the page + +.user-select(@select) { + -webkit-user-select: @select; + -moz-user-select: @select; + -ms-user-select: @select; // IE10+ + user-select: @select; +} diff --git a/css/modals.less b/css/modals.less new file mode 100644 index 0000000..767ce36 --- /dev/null +++ b/css/modals.less @@ -0,0 +1,150 @@ +// +// Modals +// -------------------------------------------------- + +// .modal-open - body class for killing the scroll +// .modal - container to scroll within +// .modal-dialog - positioning shell for the actual modal +// .modal-content - actual modal w/ bg and corners and shit + +// Kill the scroll on the body +.modal-open { + overflow: hidden; +} + +// Container that the modal scrolls within +.modal { + display: none; + overflow: hidden; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: @zindex-modal; + -webkit-overflow-scrolling: touch; + + // Prevent Chrome on Windows from adding a focus outline. For details, see + // https://github.com/twbs/bootstrap/pull/10951. + outline: 0; + + // When fading in the modal, animate it to slide down + &.fade .modal-dialog { + .translate(0, -25%); + .transition-transform(~"0.3s ease-out"); + } + &.in .modal-dialog { .translate(0, 0) } +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} + +// Shell div to position the modal with bottom padding +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} + +// Actual modal +.modal-content { + position: relative; + background-color: @modal-content-bg; + border: 1px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc) + border: 1px solid @modal-content-border-color; + border-radius: @border-radius-large; + .box-shadow(0 3px 9px rgba(0,0,0,.5)); + background-clip: padding-box; + // Remove focus outline from opened modal + outline: 0; +} + +// Modal background +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: @zindex-modal-background; + background-color: @modal-backdrop-bg; + // Fade for backdrop + &.fade { .opacity(0); } + &.in { .opacity(@modal-backdrop-opacity); } +} + +// Modal header +// Top section of the modal w/ title and dismiss +.modal-header { + padding: @modal-title-padding; + border-bottom: 1px solid @modal-header-border-color; + &:extend(.clearfix all); +} +// Close icon +.modal-header .close { + margin-top: -2px; +} + +// Title text within header +.modal-title { + margin: 0; + line-height: @modal-title-line-height; +} + +// Modal body +// Where all modal content resides (sibling of .modal-header and .modal-footer) +.modal-body { + position: relative; + padding: @modal-inner-padding; +} + +// Footer (for actions) +.modal-footer { + padding: @modal-inner-padding; + text-align: right; // right align buttons + border-top: 1px solid @modal-footer-border-color; + &:extend(.clearfix all); // clear it in case folks use .pull-* classes on buttons + + // Properly space out buttons + .btn + .btn { + margin-left: 5px; + margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs + } + // but override that for button groups + .btn-group .btn + .btn { + margin-left: -1px; + } + // and override it for block buttons as well + .btn-block + .btn-block { + margin-left: 0; + } +} + +// Measure scrollbar width for padding body during modal show/hide +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} + +// Scale up the modal +@media (min-width: @screen-sm-min) { + // Automatically set modal's width for larger viewports + .modal-dialog { + width: @modal-md; + margin: 30px auto; + } + .modal-content { + .box-shadow(0 5px 15px rgba(0,0,0,.5)); + } + + // Modal sizes + .modal-sm { width: @modal-sm; } +} + +@media (min-width: @screen-md-min) { + .modal-lg { width: @modal-lg; } +} diff --git a/css/navbar.less b/css/navbar.less new file mode 100644 index 0000000..fc07613 --- /dev/null +++ b/css/navbar.less @@ -0,0 +1,660 @@ +// +// Navbars +// -------------------------------------------------- + + +// Wrapper and base class +// +// Provide a static navbar from which we expand to create full-width, fixed, and +// other navbar variations. + +.navbar { + position: relative; + min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode) + margin-bottom: @navbar-margin-bottom; + // Nick border: 1px solid transparent; + + // Prevent floats from breaking the navbar + &:extend(.clearfix all); + + @media (min-width: @grid-float-breakpoint) { + border-radius: @navbar-border-radius; + } +} + + +// Navbar heading +// +// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy +// styling of responsive aspects. + +.navbar-header { + &:extend(.clearfix all); + + @media (min-width: @grid-float-breakpoint) { + float: left; + } +} + + +// Navbar collapse (body) +// +// Group your navbar content into this for easy collapsing and expanding across +// various device sizes. By default, this content is collapsed when <768px, but +// will expand past that for a horizontal display. +// +// To start (on mobile devices) the navbar links, forms, and buttons are stacked +// vertically and include a `max-height` to overflow in case you have too much +// content for the user's viewport. + +.navbar-collapse { + overflow-x: visible; + padding-right: @navbar-padding-horizontal; + padding-left: @navbar-padding-horizontal; + border-top: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255,255,255,.1); + &:extend(.clearfix all); + -webkit-overflow-scrolling: touch; + + &.in { + overflow-y: auto; + } + + @media (min-width: @grid-float-breakpoint) { + width: auto; + border-top: 0; + box-shadow: none; + + &.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; // Override default setting + overflow: visible !important; + } + + &.in { + overflow-y: visible; + } + + // Undo the collapse side padding for navbars with containers to ensure + // alignment of right-aligned contents. + .navbar-fixed-top &, + .navbar-static-top &, + .navbar-fixed-bottom & { + padding-left: 0; + padding-right: 0; + } + } +} + +.navbar-fixed-top, +.navbar-fixed-bottom { + .navbar-collapse { + max-height: @navbar-collapse-max-height; + + @media (max-device-width: @screen-xs-min) and (orientation: landscape) { + max-height: 200px; + } + } +} + + +// Both navbar header and collapse +// +// When a container is present, change the behavior of the header and collapse. + +.container, +.container-fluid { + > .navbar-header, + > .navbar-collapse { + margin-right: -@navbar-padding-horizontal; + margin-left: -@navbar-padding-horizontal; + + @media (min-width: @grid-float-breakpoint) { + margin-right: 0; + margin-left: 0; + } + } +} + + +// +// Navbar alignment options +// +// Display the navbar across the entirety of the page or fixed it to the top or +// bottom of the page. + +// Static top (unfixed, but 100% wide) navbar +.navbar-static-top { + z-index: @zindex-navbar; + border-width: 0 0 1px; + + @media (min-width: @grid-float-breakpoint) { + border-radius: 0; + } +} + +// Fix the top/bottom navbars when screen real estate supports it +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: @zindex-navbar-fixed; + + // Undo the rounded corners + @media (min-width: @grid-float-breakpoint) { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; // override .navbar defaults + border-width: 1px 0 0; +} + + +// Brand/project name + +.navbar-brand { + float: left; + padding: @navbar-padding-vertical @navbar-padding-horizontal; + font-size: @font-size-large; + line-height: @line-height-computed; + height: @navbar-height; + + &:hover, + &:focus { + text-decoration: none; + } + + > img { + display: block; + } + + @media (min-width: @grid-float-breakpoint) { + .navbar > .container &, + .navbar > .container-fluid & { + margin-left: -@navbar-padding-horizontal; + } + } +} + + +// Navbar toggle +// +// Custom button for toggling the `.navbar-collapse`, powered by the collapse +// JavaScript plugin. + +.navbar-toggle { + position: relative; + float: right; + margin-right: @navbar-padding-horizontal; + padding: 9px 10px; + .navbar-vertical-align(34px); + background-color: transparent; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid transparent; + border-radius: @border-radius-base; + + // We remove the `outline` here, but later compensate by attaching `:hover` + // styles to `:focus`. + &:focus { + outline: 0; + } + + // Bars + .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; + } + .icon-bar + .icon-bar { + margin-top: 4px; + } + + @media (min-width: @grid-float-breakpoint) { + display: none; + } +} + + +// Navbar nav links +// +// Builds on top of the `.nav` components with its own modifier class to make +// the nav the full height of the horizontal nav (above 768px). + +.navbar-nav { + margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal; + + > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: @line-height-computed; + } + + @media (max-width: @grid-float-breakpoint-max) { + // Dropdowns get custom display when collapsed + .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + box-shadow: none; + > li > a, + .dropdown-header { + padding: 5px 15px 5px 25px; + } + > li > a { + line-height: @line-height-computed; + &:hover, + &:focus { + background-image: none; + } + } + } + } + + // Uncollapse the nav + @media (min-width: @grid-float-breakpoint) { + float: left; + margin: 0; + + > li { + float: left; + > a { + padding-top: @navbar-padding-vertical; + padding-bottom: @navbar-padding-vertical; + } + } + } +} + + +// Navbar form +// +// Extension of the `.form-inline` with some extra flavor for optimum display in +// our navbars. + +.navbar-form { + margin-left: -@navbar-padding-horizontal; + margin-right: -@navbar-padding-horizontal; + padding: 10px @navbar-padding-horizontal; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1); + .box-shadow(@shadow); + + // Mixin behavior for optimum display + .form-inline(); + + .form-group { + @media (max-width: @grid-float-breakpoint-max) { + margin-bottom: 5px; + + &:last-child { + margin-bottom: 0; + } + } + } + + // Vertically center in expanded, horizontal navbar + .navbar-vertical-align(@input-height-base); + + // Undo 100% width for pull classes + @media (min-width: @grid-float-breakpoint) { + width: auto; + border: 0; + margin-left: 0; + margin-right: 0; + padding-top: 0; + padding-bottom: 0; + .box-shadow(none); + } +} + + +// Dropdown menus + +// Menu position and menu carets +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + .border-top-radius(0); +} +// Menu position and menu caret support for dropups via extra dropup class +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + .border-top-radius(@navbar-border-radius); + .border-bottom-radius(0); +} + + +// Buttons in navbars +// +// Vertically center a button within a navbar (when *not* in a form). + +.navbar-btn { + .navbar-vertical-align(@input-height-base); + + &.btn-sm { + .navbar-vertical-align(@input-height-small); + } + &.btn-xs { + .navbar-vertical-align(22); + } +} + + +// Text in navbars +// +// Add a class to make any element properly align itself vertically within the navbars. + +.navbar-text { + .navbar-vertical-align(@line-height-computed); + + @media (min-width: @grid-float-breakpoint) { + float: left; + margin-left: @navbar-padding-horizontal; + margin-right: @navbar-padding-horizontal; + } +} + + +// Component alignment +// +// Repurpose the pull utilities as their own navbar utilities to avoid specificity +// issues with parents and chaining. Only do this when the navbar is uncollapsed +// though so that navbar contents properly stack and align in mobile. +// +// Declared after the navbar components to ensure more specificity on the margins. + +@media (min-width: @grid-float-breakpoint) { + .navbar-left { .pull-left(); } + .navbar-right { + .pull-right(); + margin-right: -@navbar-padding-horizontal; + + ~ .navbar-right { + margin-right: 0; + } + } +} + + +// Alternate navbars +// -------------------------------------------------- + +// Default navbar +.navbar-default { + background-color: @navbar-default-bg; + border-color: @navbar-default-border; + + .navbar-brand { + color: @navbar-default-brand-color; + &:hover, + &:focus { + color: @navbar-default-brand-hover-color; + background-color: @navbar-default-brand-hover-bg; + } + } + + .navbar-text { + color: @navbar-default-color; + } + + .navbar-nav { + > li > a { + color: @navbar-default-link-color; + + &:hover, + &:focus { + color: @navbar-default-link-hover-color; + background-color: @navbar-default-link-hover-bg; + } + } + > .active > a { + &, + &:hover, + &:focus { + color: @navbar-default-link-active-color; + background-color: @navbar-default-link-active-bg; + } + } + > .disabled > a { + &, + &:hover, + &:focus { + color: @navbar-default-link-disabled-color; + background-color: @navbar-default-link-disabled-bg; + } + } + } + + .navbar-toggle { + border-color: @navbar-default-toggle-border-color; + &:hover, + &:focus { + background-color: @navbar-default-toggle-hover-bg; + } + .icon-bar { + background-color: @navbar-default-toggle-icon-bar-bg; + } + } + + .navbar-collapse, + .navbar-form { + border-color: @navbar-default-border; + } + + // Dropdown menu items + .navbar-nav { + // Remove background color from open dropdown + > .open > a { + &, + &:hover, + &:focus { + background-color: @navbar-default-link-active-bg; + color: @navbar-default-link-active-color; + } + } + + @media (max-width: @grid-float-breakpoint-max) { + // Dropdowns get custom display when collapsed + .open .dropdown-menu { + > li > a { + color: @navbar-default-link-color; + &:hover, + &:focus { + color: @navbar-default-link-hover-color; + background-color: @navbar-default-link-hover-bg; + } + } + > .active > a { + &, + &:hover, + &:focus { + color: @navbar-default-link-active-color; + background-color: @navbar-default-link-active-bg; + } + } + > .disabled > a { + &, + &:hover, + &:focus { + color: @navbar-default-link-disabled-color; + background-color: @navbar-default-link-disabled-bg; + } + } + } + } + } + + + // Links in navbars + // + // Add a class to ensure links outside the navbar nav are colored correctly. + + .navbar-link { + color: @navbar-default-link-color; + &:hover { + color: @navbar-default-link-hover-color; + } + } + + .btn-link { + color: @navbar-default-link-color; + &:hover, + &:focus { + color: @navbar-default-link-hover-color; + } + &[disabled], + fieldset[disabled] & { + &:hover, + &:focus { + color: @navbar-default-link-disabled-color; + } + } + } +} + +// Inverse navbar + +.navbar-inverse { + background-color: @navbar-inverse-bg; + border-color: @navbar-inverse-border; + + .navbar-brand { + color: @navbar-inverse-brand-color; + &:hover, + &:focus { + color: @navbar-inverse-brand-hover-color; + background-color: @navbar-inverse-brand-hover-bg; + } + } + + .navbar-text { + color: @navbar-inverse-color; + } + + .navbar-nav { + > li > a { + color: @navbar-inverse-link-color; + + &:hover, + &:focus { + color: @navbar-inverse-link-hover-color; + background-color: @navbar-inverse-link-hover-bg; + } + } + > .active > a { + &, + &:hover, + &:focus { + color: @navbar-inverse-link-active-color; + background-color: @navbar-inverse-link-active-bg; + } + } + > .disabled > a { + &, + &:hover, + &:focus { + color: @navbar-inverse-link-disabled-color; + background-color: @navbar-inverse-link-disabled-bg; + } + } + } + + // Darken the responsive nav toggle + .navbar-toggle { + border-color: @navbar-inverse-toggle-border-color; + &:hover, + &:focus { + background-color: @navbar-inverse-toggle-hover-bg; + } + .icon-bar { + background-color: @navbar-inverse-toggle-icon-bar-bg; + } + } + + .navbar-collapse, + .navbar-form { + border-color: darken(@navbar-inverse-bg, 7%); + } + + // Dropdowns + .navbar-nav { + > .open > a { + &, + &:hover, + &:focus { + background-color: @navbar-inverse-link-active-bg; + color: @navbar-inverse-link-active-color; + } + } + + @media (max-width: @grid-float-breakpoint-max) { + // Dropdowns get custom display + .open .dropdown-menu { + > .dropdown-header { + border-color: @navbar-inverse-border; + } + .divider { + background-color: @navbar-inverse-border; + } + > li > a { + color: @navbar-inverse-link-color; + &:hover, + &:focus { + color: @navbar-inverse-link-hover-color; + background-color: @navbar-inverse-link-hover-bg; + } + } + > .active > a { + &, + &:hover, + &:focus { + color: @navbar-inverse-link-active-color; + background-color: @navbar-inverse-link-active-bg; + } + } + > .disabled > a { + &, + &:hover, + &:focus { + color: @navbar-inverse-link-disabled-color; + background-color: @navbar-inverse-link-disabled-bg; + } + } + } + } + } + + .navbar-link { + color: @navbar-inverse-link-color; + &:hover { + color: @navbar-inverse-link-hover-color; + } + } + + .btn-link { + color: @navbar-inverse-link-color; + &:hover, + &:focus { + color: @navbar-inverse-link-hover-color; + } + &[disabled], + fieldset[disabled] & { + &:hover, + &:focus { + color: @navbar-inverse-link-disabled-color; + } + } + } +} diff --git a/css/navs.less b/css/navs.less new file mode 100644 index 0000000..a3d11b1 --- /dev/null +++ b/css/navs.less @@ -0,0 +1,242 @@ +// +// Navs +// -------------------------------------------------- + + +// Base class +// -------------------------------------------------- + +.nav { + margin-bottom: 0; + padding-left: 0; // Override default ul/ol + list-style: none; + &:extend(.clearfix all); + + > li { + position: relative; + display: block; + + > a { + position: relative; + display: block; + padding: @nav-link-padding; + &:hover, + &:focus { + text-decoration: none; + background-color: @nav-link-hover-bg; + } + } + + // Disabled state sets text to gray and nukes hover/tab effects + &.disabled > a { + color: @nav-disabled-link-color; + + &:hover, + &:focus { + color: @nav-disabled-link-hover-color; + text-decoration: none; + background-color: transparent; + cursor: @cursor-disabled; + } + } + } + + // Open dropdowns + .open > a { + &, + &:hover, + &:focus { + background-color: @nav-link-hover-bg; + border-color: @link-color; + } + } + + // Nav dividers (deprecated with v3.0.1) + // + // This should have been removed in v3 with the dropping of `.nav-list`, but + // we missed it. We don't currently support this anywhere, but in the interest + // of maintaining backward compatibility in case you use it, it's deprecated. + .nav-divider { + .nav-divider(); + } + + // Prevent IE8 from misplacing imgs + // + // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989 + > li > a > img { + max-width: none; + } +} + + +// Tabs +// ------------------------- + +// Give the tabs something to sit on +.nav-tabs { + border-bottom: 1px solid @nav-tabs-border-color; + > li { + float: left; + // Make the list-items overlay the bottom border + margin-bottom: -1px; + + // Actual tabs (as links) + > a { + margin-right: 2px; + line-height: @line-height-base; + border: 1px solid transparent; + border-radius: @border-radius-base @border-radius-base 0 0; + &:hover { + border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color; + } + } + + // Active state, and its :hover to override normal :hover + &.active > a { + &, + &:hover, + &:focus { + color: @nav-tabs-active-link-hover-color; + background-color: @nav-tabs-active-link-hover-bg; + border: 1px solid @nav-tabs-active-link-hover-border-color; + border-bottom-color: transparent; + cursor: default; + } + } + } + // pulling this in mainly for less shorthand + &.nav-justified { + .nav-justified(); + .nav-tabs-justified(); + } +} + + +// Pills +// ------------------------- +.nav-pills { + > li { + float: left; + + // Links rendered as pills + > a { + border-radius: @nav-pills-border-radius; + } + + li { + margin-left: 2px; + } + + // Active state + &.active > a { + &, + &:hover, + &:focus { + color: @nav-pills-active-link-hover-color; + background-color: @nav-pills-active-link-hover-bg; + } + } + } +} + + +// Stacked pills +.nav-stacked { + > li { + float: none; + + li { + margin-top: 2px; + margin-left: 0; // no need for this gap between nav items + } + } +} + + +// Nav variations +// -------------------------------------------------- + +// Justified nav links +// ------------------------- + +.nav-justified { + width: 100%; + + > li { + float: none; + > a { + text-align: center; + margin-bottom: 5px; + } + } + + > .dropdown .dropdown-menu { + top: auto; + left: auto; + } + + @media (min-width: @screen-sm-min) { + > li { + display: table-cell; + width: 1%; + > a { + margin-bottom: 0; + } + } + } +} + +// Move borders to anchors instead of bottom of list +// +// Mixin for adding on top the shared `.nav-justified` styles for our tabs +.nav-tabs-justified { + border-bottom: 0; + + > li > a { + // Override margin from .nav-tabs + margin-right: 0; + border-radius: @border-radius-base; + } + + > .active > a, + > .active > a:hover, + > .active > a:focus { + border: 1px solid @nav-tabs-justified-link-border-color; + } + + @media (min-width: @screen-sm-min) { + > li > a { + border-bottom: 1px solid @nav-tabs-justified-link-border-color; + border-radius: @border-radius-base @border-radius-base 0 0; + } + > .active > a, + > .active > a:hover, + > .active > a:focus { + border-bottom-color: @nav-tabs-justified-active-link-border-color; + } + } +} + + +// Tabbable tabs +// ------------------------- + +// Hide tabbable panes to start, show them when `.active` +.tab-content { + > .tab-pane { + display: none; + } + > .active { + display: block; + } +} + + +// Dropdowns +// ------------------------- + +// Specific dropdowns +.nav-tabs .dropdown-menu { + // make dropdown border overlap tab border + margin-top: -1px; + // Remove the top rounded corners here since there is a hard edge above the menu + .border-top-radius(0); +} diff --git a/css/normalize.less b/css/normalize.less new file mode 100644 index 0000000..9dddf73 --- /dev/null +++ b/css/normalize.less @@ -0,0 +1,424 @@ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ + +// +// 1. Set default font family to sans-serif. +// 2. Prevent iOS and IE text size adjust after device orientation change, +// without disabling user zoom. +// + +html { + font-family: sans-serif; // 1 + -ms-text-size-adjust: 100%; // 2 + -webkit-text-size-adjust: 100%; // 2 +} + +// +// Remove default margin. +// + +body { + margin: 0; +} + +// HTML5 display definitions +// ========================================================================== + +// +// Correct `block` display not defined for any HTML5 element in IE 8/9. +// Correct `block` display not defined for `details` or `summary` in IE 10/11 +// and Firefox. +// Correct `block` display not defined for `main` in IE 11. +// + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +// +// 1. Correct `inline-block` display not defined in IE 8/9. +// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. +// + +audio, +canvas, +progress, +video { + display: inline-block; // 1 + vertical-align: baseline; // 2 +} + +// +// Prevent modern browsers from displaying `audio` without controls. +// Remove excess height in iOS 5 devices. +// + +audio:not([controls]) { + display: none; + height: 0; +} + +// +// Address `[hidden]` styling not present in IE 8/9/10. +// Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. +// + +[hidden], +template { + display: none; +} + +// Links +// ========================================================================== + +// +// Remove the gray background color from active links in IE 10. +// + +a { + background-color: transparent; +} + +// +// Improve readability of focused elements when they are also in an +// active/hover state. +// + +a:active, +a:hover { + outline: 0; +} + +// Text-level semantics +// ========================================================================== + +// +// Address styling not present in IE 8/9/10/11, Safari, and Chrome. +// + +abbr[title] { + border-bottom: 1px dotted; +} + +// +// Address style set to `bolder` in Firefox 4+, Safari, and Chrome. +// + +b, +strong { + font-weight: bold; +} + +// +// Address styling not present in Safari and Chrome. +// + +dfn { + font-style: italic; +} + +// +// Address variable `h1` font-size and margin within `section` and `article` +// contexts in Firefox 4+, Safari, and Chrome. +// + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +// +// Address styling not present in IE 8/9. +// + +mark { + background: #ff0; + color: #000; +} + +// +// Address inconsistent and variable font size in all browsers. +// + +small { + font-size: 80%; +} + +// +// Prevent `sub` and `sup` affecting `line-height` in all browsers. +// + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +// Embedded content +// ========================================================================== + +// +// Remove border when inside `a` element in IE 8/9/10. +// + +img { + border: 0; +} + +// +// Correct overflow not hidden in IE 9/10/11. +// + +svg:not(:root) { + overflow: hidden; +} + +// Grouping content +// ========================================================================== + +// +// Address margin not present in IE 8/9 and Safari. +// + +figure { + margin: 1em 40px; +} + +// +// Address differences between Firefox and other browsers. +// + +hr { + box-sizing: content-box; + height: 0; +} + +// +// Contain overflow in all browsers. +// + +pre { + overflow: auto; +} + +// +// Address odd `em`-unit font size rendering in all browsers. +// + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +// Forms +// ========================================================================== + +// +// Known limitation: by default, Chrome and Safari on OS X allow very limited +// styling of `select`, unless a `border` property is set. +// + +// +// 1. Correct color not being inherited. +// Known issue: affects color of disabled elements. +// 2. Correct font properties not being inherited. +// 3. Address margins set differently in Firefox 4+, Safari, and Chrome. +// + +button, +input, +optgroup, +select, +textarea { + color: inherit; // 1 + font: inherit; // 2 + margin: 0; // 3 +} + +// +// Address `overflow` set to `hidden` in IE 8/9/10/11. +// + +button { + overflow: visible; +} + +// +// Address inconsistent `text-transform` inheritance for `button` and `select`. +// All other form control elements do not inherit `text-transform` values. +// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. +// Correct `select` style inheritance in Firefox. +// + +button, +select { + text-transform: none; +} + +// +// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` +// and `video` controls. +// 2. Correct inability to style clickable `input` types in iOS. +// 3. Improve usability and consistency of cursor style between image-type +// `input` and others. +// + +button, +html input[type="button"], // 1 +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; // 2 + cursor: pointer; // 3 +} + +// +// Re-set default cursor for disabled elements. +// + +button[disabled], +html input[disabled] { + cursor: default; +} + +// +// Remove inner padding and border in Firefox 4+. +// + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +// +// Address Firefox 4+ setting `line-height` on `input` using `!important` in +// the UA stylesheet. +// + +input { + line-height: normal; +} + +// +// It's recommended that you don't attempt to style these elements. +// Firefox's implementation doesn't respect box-sizing, padding, or width. +// +// 1. Address box sizing set to `content-box` in IE 8/9/10. +// 2. Remove excess padding in IE 8/9/10. +// + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; // 1 + padding: 0; // 2 +} + +// +// Fix the cursor style for Chrome's increment/decrement buttons. For certain +// `font-size` values of the `input`, it causes the cursor style of the +// decrement button to change from `default` to `text`. +// + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +// +// 1. Address `appearance` set to `searchfield` in Safari and Chrome. +// 2. Address `box-sizing` set to `border-box` in Safari and Chrome. +// + +input[type="search"] { + -webkit-appearance: textfield; // 1 + box-sizing: content-box; //2 +} + +// +// Remove inner padding and search cancel button in Safari and Chrome on OS X. +// Safari (but not Chrome) clips the cancel button when the search input has +// padding (and `textfield` appearance). +// + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +// +// Define consistent border, margin, and padding. +// + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +// +// 1. Correct `color` not being inherited in IE 8/9/10/11. +// 2. Remove padding so people aren't caught out if they zero out fieldsets. +// + +legend { + border: 0; // 1 + padding: 0; // 2 +} + +// +// Remove default vertical scrollbar in IE 8/9/10/11. +// + +textarea { + overflow: auto; +} + +// +// Don't inherit the `font-weight` (applied by a rule above). +// NOTE: the default cannot safely be changed in Chrome and Safari on OS X. +// + +optgroup { + font-weight: bold; +} + +// Tables +// ========================================================================== + +// +// Remove most spacing between table cells. +// + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} diff --git a/css/pager.less b/css/pager.less new file mode 100644 index 0000000..41abaaa --- /dev/null +++ b/css/pager.less @@ -0,0 +1,54 @@ +// +// Pager pagination +// -------------------------------------------------- + + +.pager { + padding-left: 0; + margin: @line-height-computed 0; + list-style: none; + text-align: center; + &:extend(.clearfix all); + li { + display: inline; + > a, + > span { + display: inline-block; + padding: 5px 14px; + background-color: @pager-bg; + border: 1px solid @pager-border; + border-radius: @pager-border-radius; + } + + > a:hover, + > a:focus { + text-decoration: none; + background-color: @pager-hover-bg; + } + } + + .next { + > a, + > span { + float: right; + } + } + + .previous { + > a, + > span { + float: left; + } + } + + .disabled { + > a, + > a:hover, + > a:focus, + > span { + color: @pager-disabled-color; + background-color: @pager-bg; + cursor: @cursor-disabled; + } + } +} diff --git a/css/pagination.less b/css/pagination.less new file mode 100644 index 0000000..31f77aa --- /dev/null +++ b/css/pagination.less @@ -0,0 +1,89 @@ +// +// Pagination (multiple pages) +// -------------------------------------------------- +.pagination { + display: inline-block; + padding-left: 0; + margin: @line-height-computed 0; + border-radius: @border-radius-base; + + > li { + display: inline; // Remove list-style and block-level defaults + > a, + > span { + position: relative; + float: left; // Collapse white-space + padding: @padding-base-vertical @padding-base-horizontal; + line-height: @line-height-base; + text-decoration: none; + color: @pagination-color; + background-color: @pagination-bg; + border: 1px solid @pagination-border; + margin-left: -1px; + } + &:first-child { + > a, + > span { + margin-left: 0; + .border-left-radius(@border-radius-base); + } + } + &:last-child { + > a, + > span { + .border-right-radius(@border-radius-base); + } + } + } + + > li > a, + > li > span { + &:hover, + &:focus { + z-index: 2; + color: @pagination-hover-color; + background-color: @pagination-hover-bg; + border-color: @pagination-hover-border; + } + } + + > .active > a, + > .active > span { + &, + &:hover, + &:focus { + z-index: 3; + color: @pagination-active-color; + background-color: @pagination-active-bg; + border-color: @pagination-active-border; + cursor: default; + } + } + + > .disabled { + > span, + > span:hover, + > span:focus, + > a, + > a:hover, + > a:focus { + color: @pagination-disabled-color; + background-color: @pagination-disabled-bg; + border-color: @pagination-disabled-border; + cursor: @cursor-disabled; + } + } +} + +// Sizing +// -------------------------------------------------- + +// Large +.pagination-lg { + .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large); +} + +// Small +.pagination-sm { + .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small); +} diff --git a/css/panels.less b/css/panels.less new file mode 100644 index 0000000..65aa3a8 --- /dev/null +++ b/css/panels.less @@ -0,0 +1,271 @@ +// +// Panels +// -------------------------------------------------- + + +// Base class +.panel { + margin-bottom: @line-height-computed; + background-color: @panel-bg; + border: 1px solid transparent; + border-radius: @panel-border-radius; + .box-shadow(0 1px 1px rgba(0,0,0,.05)); +} + +// Panel contents +.panel-body { + padding: @panel-body-padding; + &:extend(.clearfix all); +} + +// Optional heading +.panel-heading { + padding: @panel-heading-padding; + border-bottom: 1px solid transparent; + .border-top-radius((@panel-border-radius - 1)); + + > .dropdown .dropdown-toggle { + color: inherit; + } +} + +// Within heading, strip any `h*` tag of its default margins for spacing. +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: ceil((@font-size-base * 1.125)); + color: inherit; + + > a, + > small, + > .small, + > small > a, + > .small > a { + color: inherit; + } +} + +// Optional footer (stays gray in every modifier class) +.panel-footer { + padding: @panel-footer-padding; + background-color: @panel-footer-bg; + border-top: 1px solid @panel-inner-border; + .border-bottom-radius((@panel-border-radius - 1)); +} + + +// List groups in panels +// +// By default, space out list group content from panel headings to account for +// any kind of custom content between the two. + +.panel { + > .list-group, + > .panel-collapse > .list-group { + margin-bottom: 0; + + .list-group-item { + border-width: 1px 0; + border-radius: 0; + } + + // Add border top radius for first one + &:first-child { + .list-group-item:first-child { + border-top: 0; + .border-top-radius((@panel-border-radius - 1)); + } + } + + // Add border bottom radius for last one + &:last-child { + .list-group-item:last-child { + border-bottom: 0; + .border-bottom-radius((@panel-border-radius - 1)); + } + } + } + > .panel-heading + .panel-collapse > .list-group { + .list-group-item:first-child { + .border-top-radius(0); + } + } +} +// Collapse space between when there's no additional content. +.panel-heading + .list-group { + .list-group-item:first-child { + border-top-width: 0; + } +} +.list-group + .panel-footer { + border-top-width: 0; +} + +// Tables in panels +// +// Place a non-bordered `.table` within a panel (not within a `.panel-body`) and +// watch it go full width. + +.panel { + > .table, + > .table-responsive > .table, + > .panel-collapse > .table { + margin-bottom: 0; + + caption { + padding-left: @panel-body-padding; + padding-right: @panel-body-padding; + } + } + // Add border top radius for first one + > .table:first-child, + > .table-responsive:first-child > .table:first-child { + .border-top-radius((@panel-border-radius - 1)); + + > thead:first-child, + > tbody:first-child { + > tr:first-child { + border-top-left-radius: (@panel-border-radius - 1); + border-top-right-radius: (@panel-border-radius - 1); + + td:first-child, + th:first-child { + border-top-left-radius: (@panel-border-radius - 1); + } + td:last-child, + th:last-child { + border-top-right-radius: (@panel-border-radius - 1); + } + } + } + } + // Add border bottom radius for last one + > .table:last-child, + > .table-responsive:last-child > .table:last-child { + .border-bottom-radius((@panel-border-radius - 1)); + + > tbody:last-child, + > tfoot:last-child { + > tr:last-child { + border-bottom-left-radius: (@panel-border-radius - 1); + border-bottom-right-radius: (@panel-border-radius - 1); + + td:first-child, + th:first-child { + border-bottom-left-radius: (@panel-border-radius - 1); + } + td:last-child, + th:last-child { + border-bottom-right-radius: (@panel-border-radius - 1); + } + } + } + } + > .panel-body + .table, + > .panel-body + .table-responsive, + > .table + .panel-body, + > .table-responsive + .panel-body { + border-top: 1px solid @table-border-color; + } + > .table > tbody:first-child > tr:first-child th, + > .table > tbody:first-child > tr:first-child td { + border-top: 0; + } + > .table-bordered, + > .table-responsive > .table-bordered { + border: 0; + > thead, + > tbody, + > tfoot { + > tr { + > th:first-child, + > td:first-child { + border-left: 0; + } + > th:last-child, + > td:last-child { + border-right: 0; + } + } + } + > thead, + > tbody { + > tr:first-child { + > td, + > th { + border-bottom: 0; + } + } + } + > tbody, + > tfoot { + > tr:last-child { + > td, + > th { + border-bottom: 0; + } + } + } + } + > .table-responsive { + border: 0; + margin-bottom: 0; + } +} + + +// Collapsible panels (aka, accordion) +// +// Wrap a series of panels in `.panel-group` to turn them into an accordion with +// the help of our collapse JavaScript plugin. + +.panel-group { + margin-bottom: @line-height-computed; + + // Tighten up margin so it's only between panels + .panel { + margin-bottom: 0; + border-radius: @panel-border-radius; + + + .panel { + margin-top: 5px; + } + } + + .panel-heading { + border-bottom: 0; + + + .panel-collapse > .panel-body, + + .panel-collapse > .list-group { + border-top: 1px solid @panel-inner-border; + } + } + + .panel-footer { + border-top: 0; + + .panel-collapse .panel-body { + border-bottom: 1px solid @panel-inner-border; + } + } +} + + +// Contextual variations +.panel-default { + .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border); +} +.panel-primary { + .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border); +} +.panel-success { + .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border); +} +.panel-info { + .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border); +} +.panel-warning { + .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border); +} +.panel-danger { + .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border); +} diff --git a/css/popovers.less b/css/popovers.less new file mode 100644 index 0000000..3a62a64 --- /dev/null +++ b/css/popovers.less @@ -0,0 +1,131 @@ +// +// Popovers +// -------------------------------------------------- + + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: @zindex-popover; + display: none; + max-width: @popover-max-width; + padding: 1px; + // Our parent element can be arbitrary since popovers are by default inserted as a sibling of their target element. + // So reset our font and text properties to avoid inheriting weird values. + .reset-text(); + font-size: @font-size-base; + + background-color: @popover-bg; + background-clip: padding-box; + border: 1px solid @popover-fallback-border-color; + border: 1px solid @popover-border-color; + border-radius: @border-radius-large; + .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; } +} + +.popover-title { + margin: 0; // reset heading margin + padding: 8px 14px; + font-size: @font-size-base; + background-color: @popover-title-bg; + border-bottom: 1px solid darken(@popover-title-bg, 5%); + border-radius: (@border-radius-large - 1) (@border-radius-large - 1) 0 0; +} + +.popover-content { + padding: 9px 14px; +} + +// Arrows +// +// .arrow is outer, .arrow:after is inner + +.popover > .arrow { + &, + &:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + } +} +.popover > .arrow { + border-width: @popover-arrow-outer-width; +} +.popover > .arrow:after { + border-width: @popover-arrow-width; + content: ""; +} + +.popover { + &.top > .arrow { + left: 50%; + margin-left: -@popover-arrow-outer-width; + border-bottom-width: 0; + border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback + border-top-color: @popover-arrow-outer-color; + bottom: -@popover-arrow-outer-width; + &:after { + content: " "; + bottom: 1px; + margin-left: -@popover-arrow-width; + border-bottom-width: 0; + border-top-color: @popover-arrow-color; + } + } + &.right > .arrow { + top: 50%; + left: -@popover-arrow-outer-width; + margin-top: -@popover-arrow-outer-width; + border-left-width: 0; + border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback + border-right-color: @popover-arrow-outer-color; + &:after { + content: " "; + left: 1px; + bottom: -@popover-arrow-width; + border-left-width: 0; + border-right-color: @popover-arrow-color; + } + } + &.bottom > .arrow { + left: 50%; + margin-left: -@popover-arrow-outer-width; + border-top-width: 0; + border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback + border-bottom-color: @popover-arrow-outer-color; + top: -@popover-arrow-outer-width; + &:after { + content: " "; + top: 1px; + margin-left: -@popover-arrow-width; + border-top-width: 0; + border-bottom-color: @popover-arrow-color; + } + } + + &.left > .arrow { + top: 50%; + right: -@popover-arrow-outer-width; + margin-top: -@popover-arrow-outer-width; + border-right-width: 0; + border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback + border-left-color: @popover-arrow-outer-color; + &:after { + content: " "; + right: 1px; + border-right-width: 0; + border-left-color: @popover-arrow-color; + bottom: -@popover-arrow-width; + } + } +} diff --git a/css/print.less b/css/print.less new file mode 100644 index 0000000..66e54ab --- /dev/null +++ b/css/print.less @@ -0,0 +1,101 @@ +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ + +// ========================================================================== +// Print styles. +// Inlined to avoid the additional HTTP request: h5bp.com/r +// ========================================================================== + +@media print { + *, + *:before, + *:after { + background: transparent !important; + color: #000 !important; // Black prints faster: h5bp.com/s + box-shadow: none !important; + text-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + a[href]:after { + content: " (" attr(href) ")"; + } + + abbr[title]:after { + content: " (" attr(title) ")"; + } + + // Don't show links that are fragment identifiers, + // or use the `javascript:` pseudo protocol + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + + thead { + display: table-header-group; // h5bp.com/t + } + + tr, + img { + page-break-inside: avoid; + } + + img { + max-width: 100% !important; + } + + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + + h2, + h3 { + page-break-after: avoid; + } + + // Bootstrap specific changes start + + // Bootstrap components + .navbar { + display: none; + } + .btn, + .dropup > .btn { + > .caret { + border-top-color: #000 !important; + } + } + .label { + border: 1px solid #000; + } + + .table { + border-collapse: collapse !important; + + td, + th { + background-color: #fff !important; + } + } + .table-bordered { + th, + td { + border: 1px solid #ddd !important; + } + } + + // Bootstrap specific changes end +} diff --git a/css/progress-bars.less b/css/progress-bars.less new file mode 100644 index 0000000..8868a1f --- /dev/null +++ b/css/progress-bars.less @@ -0,0 +1,87 @@ +// +// Progress bars +// -------------------------------------------------- + + +// Bar animations +// ------------------------- + +// WebKit +@-webkit-keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + +// Spec and IE10+ +@keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + + +// Bar itself +// ------------------------- + +// Outer container +.progress { + overflow: hidden; + height: @line-height-computed; + margin-bottom: @line-height-computed; + background-color: @progress-bg; + border-radius: @progress-border-radius; + .box-shadow(inset 0 1px 2px rgba(0,0,0,.1)); +} + +// Bar of progress +.progress-bar { + float: left; + width: 0%; + height: 100%; + font-size: @font-size-small; + line-height: @line-height-computed; + color: @progress-bar-color; + text-align: center; + background-color: @progress-bar-bg; + .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15)); + .transition(width .6s ease); +} + +// Striped bars +// +// `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the +// `.progress-bar-striped` class, which you just add to an existing +// `.progress-bar`. +.progress-striped .progress-bar, +.progress-bar-striped { + #gradient > .striped(); + background-size: 40px 40px; +} + +// Call animation for the active one +// +// `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the +// `.progress-bar.active` approach. +.progress.active .progress-bar, +.progress-bar.active { + .animation(progress-bar-stripes 2s linear infinite); +} + + +// Variations +// ------------------------- + +.progress-bar-success { + .progress-bar-variant(@progress-bar-success-bg); +} + +.progress-bar-info { + .progress-bar-variant(@progress-bar-info-bg); +} + +.progress-bar-warning { + .progress-bar-variant(@progress-bar-warning-bg); +} + +.progress-bar-danger { + .progress-bar-variant(@progress-bar-danger-bg); +} diff --git a/css/recover_account.css.min b/css/recover_account.css.min new file mode 100644 index 0000000..562f94d --- /dev/null +++ b/css/recover_account.css.min @@ -0,0 +1,40 @@ +body { + padding-top: 40px; + padding-bottom: 40px; + background-color: #eee; +} + +.form-recover { + max-width: 330px; + padding: 15px; + margin: 0 auto; +} +.form-recover .form-recover-heading, +.form-recover .checkbox { + margin-bottom: 10px; +} +.form-recover .checkbox { + font-weight: normal; +} +.form-recover .form-control { + position: relative; + height: auto; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 10px; + font-size: 16px; +} +.form-recover .form-control:focus { + z-index: 2; +} +//.form-recover input[type="email"] { +// margin-bottom: -1px; +// border-bottom-right-radius: 0; +// border-bottom-left-radius: 0; +//} +//.form-recover input[type="password"] { +// margin-bottom: 10px; +// border-top-left-radius: 0; +// border-top-right-radius: 0; +//} diff --git a/css/responsive-embed.less b/css/responsive-embed.less new file mode 100644 index 0000000..080a511 --- /dev/null +++ b/css/responsive-embed.less @@ -0,0 +1,35 @@ +// Embeds responsive +// +// Credit: Nicolas Gallagher and SUIT CSS. + +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; + + .embed-responsive-item, + iframe, + embed, + object, + video { + position: absolute; + top: 0; + left: 0; + bottom: 0; + height: 100%; + width: 100%; + border: 0; + } +} + +// Modifier class for 16:9 aspect ratio +.embed-responsive-16by9 { + padding-bottom: 56.25%; +} + +// Modifier class for 4:3 aspect ratio +.embed-responsive-4by3 { + padding-bottom: 75%; +} diff --git a/css/responsive-utilities.less b/css/responsive-utilities.less new file mode 100644 index 0000000..b1db31d --- /dev/null +++ b/css/responsive-utilities.less @@ -0,0 +1,194 @@ +// +// Responsive: Utility classes +// -------------------------------------------------- + + +// IE10 in Windows (Phone) 8 +// +// Support for responsive views via media queries is kind of borked in IE10, for +// Surface/desktop in split view and for Windows Phone 8. This particular fix +// must be accompanied by a snippet of JavaScript to sniff the user agent and +// apply some conditional CSS to *only* the Surface/desktop Windows 8. Look at +// our Getting Started page for more information on this bug. +// +// For more information, see the following: +// +// Issue: https://github.com/twbs/bootstrap/issues/10497 +// Docs: http://getbootstrap.com/getting-started/#support-ie10-width +// Source: http://timkadlec.com/2013/01/windows-phone-8-and-device-width/ +// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/ + +@-ms-viewport { + width: device-width; +} + + +// Visibility utilities +// Note: Deprecated .visible-xs, .visible-sm, .visible-md, and .visible-lg as of v3.2.0 +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + .responsive-invisibility(); +} + +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} + +.visible-xs { + @media (max-width: @screen-xs-max) { + .responsive-visibility(); + } +} +.visible-xs-block { + @media (max-width: @screen-xs-max) { + display: block !important; + } +} +.visible-xs-inline { + @media (max-width: @screen-xs-max) { + display: inline !important; + } +} +.visible-xs-inline-block { + @media (max-width: @screen-xs-max) { + display: inline-block !important; + } +} + +.visible-sm { + @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { + .responsive-visibility(); + } +} +.visible-sm-block { + @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { + display: block !important; + } +} +.visible-sm-inline { + @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { + display: inline !important; + } +} +.visible-sm-inline-block { + @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { + display: inline-block !important; + } +} + +.visible-md { + @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { + .responsive-visibility(); + } +} +.visible-md-block { + @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { + display: block !important; + } +} +.visible-md-inline { + @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { + display: inline !important; + } +} +.visible-md-inline-block { + @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { + display: inline-block !important; + } +} + +.visible-lg { + @media (min-width: @screen-lg-min) { + .responsive-visibility(); + } +} +.visible-lg-block { + @media (min-width: @screen-lg-min) { + display: block !important; + } +} +.visible-lg-inline { + @media (min-width: @screen-lg-min) { + display: inline !important; + } +} +.visible-lg-inline-block { + @media (min-width: @screen-lg-min) { + display: inline-block !important; + } +} + +.hidden-xs { + @media (max-width: @screen-xs-max) { + .responsive-invisibility(); + } +} +.hidden-sm { + @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { + .responsive-invisibility(); + } +} +.hidden-md { + @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { + .responsive-invisibility(); + } +} +.hidden-lg { + @media (min-width: @screen-lg-min) { + .responsive-invisibility(); + } +} + + +// Print utilities +// +// Media queries are placed on the inside to be mixin-friendly. + +// Note: Deprecated .visible-print as of v3.2.0 +.visible-print { + .responsive-invisibility(); + + @media print { + .responsive-visibility(); + } +} +.visible-print-block { + display: none !important; + + @media print { + display: block !important; + } +} +.visible-print-inline { + display: none !important; + + @media print { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; + + @media print { + display: inline-block !important; + } +} + +.hidden-print { + @media print { + .responsive-invisibility(); + } +} diff --git a/css/scaffolding.less b/css/scaffolding.less new file mode 100644 index 0000000..64a29c6 --- /dev/null +++ b/css/scaffolding.less @@ -0,0 +1,161 @@ +// +// Scaffolding +// -------------------------------------------------- + + +// Reset the box-sizing +// +// Heads up! This reset may cause conflicts with some third-party widgets. +// For recommendations on resolving such conflicts, see +// http://getbootstrap.com/getting-started/#third-box-sizing +* { + .box-sizing(border-box); +} +*:before, +*:after { + .box-sizing(border-box); +} + + +// Body reset + +html { + font-size: 10px; + -webkit-tap-highlight-color: rgba(0,0,0,0); +} + +body { + font-family: @font-family-base; + font-size: @font-size-base; + line-height: @line-height-base; + color: @text-color; + background-color: @body-bg; +} + +// Reset fonts for relevant elements +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + + +// Links + +a { + color: @link-color; + text-decoration: none; + + &:hover, + &:focus { + color: @link-hover-color; + text-decoration: @link-hover-decoration; + } + + &:focus { + .tab-focus(); + } +} + + +// Figures +// +// We reset this here because previously Normalize had no `figure` margins. This +// ensures we don't break anyone's use of the element. + +figure { + margin: 0; +} + + +// Images + +img { + vertical-align: middle; +} + +// Responsive images (ensure images don't scale beyond their parents) +.img-responsive { + .img-responsive(); +} + +// Rounded corners +.img-rounded { + border-radius: @border-radius-large; +} + +// Image thumbnails +// +// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`. +.img-thumbnail { + padding: @thumbnail-padding; + line-height: @line-height-base; + background-color: @thumbnail-bg; + border: 1px solid @thumbnail-border; + border-radius: @thumbnail-border-radius; + .transition(all .2s ease-in-out); + + // Keep them at most 100% wide + .img-responsive(inline-block); +} + +// Perfect circle +.img-circle { + border-radius: 50%; // set radius in percents +} + + +// Horizontal rules + +hr { + margin-top: @line-height-computed; + margin-bottom: @line-height-computed; + border: 0; + border-top: 1px solid @hr-border; +} + + +// Only display content to screen readers +// +// See: http://a11yproject.com/posts/how-to-hide-content + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; +} + +// Use in conjunction with .sr-only to only display content when it's focused. +// Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 +// Credit: HTML5 Boilerplate + +.sr-only-focusable { + &:active, + &:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; + } +} + + +// iOS "clickable elements" fix for role="button" +// +// Fixes "clickability" issue (and more generally, the firing of events such as focus as well) +// for traditionally non-focusable elements with role="button" +// see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile + +[role="button"] { + cursor: pointer; +} diff --git a/css/tables.less b/css/tables.less new file mode 100644 index 0000000..2242c03 --- /dev/null +++ b/css/tables.less @@ -0,0 +1,234 @@ +// +// Tables +// -------------------------------------------------- + + +table { + background-color: @table-bg; +} +caption { + padding-top: @table-cell-padding; + padding-bottom: @table-cell-padding; + color: @text-muted; + text-align: left; +} +th { + text-align: left; +} + + +// Baseline styles + +.table { + width: 100%; + max-width: 100%; + margin-bottom: @line-height-computed; + // Cells + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + padding: @table-cell-padding; + line-height: @line-height-base; + vertical-align: top; + border-top: 1px solid @table-border-color; + } + } + } + // Bottom align for column headings + > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid @table-border-color; + } + // Remove top border from thead by default + > caption + thead, + > colgroup + thead, + > thead:first-child { + > tr:first-child { + > th, + > td { + border-top: 0; + } + } + } + // Account for multiple tbody instances + > tbody + tbody { + border-top: 2px solid @table-border-color; + } + + // Nesting + .table { + background-color: @body-bg; + } +} + + +// Condensed table w/ half padding + +.table-condensed { + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + padding: @table-condensed-cell-padding; + } + } + } +} + + +// Bordered version +// +// Add borders all around the table and between all the columns. + +.table-bordered { + border: 1px solid @table-border-color; + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + border: 1px solid @table-border-color; + } + } + } + > thead > tr { + > th, + > td { + border-bottom-width: 2px; + } + } +} + + +// Zebra-striping +// +// Default zebra-stripe styles (alternating gray and transparent backgrounds) + +.table-striped { + > tbody > tr:nth-of-type(odd) { + background-color: @table-bg-accent; + } +} + + +// Hover effect +// +// Placed here since it has to come after the potential zebra striping + +.table-hover { + > tbody > tr:hover { + background-color: @table-bg-hover; + } +} + + +// Table cell sizing +// +// Reset default table behavior + +table col[class*="col-"] { + position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623) + float: none; + display: table-column; +} +table { + td, + th { + &[class*="col-"] { + position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623) + float: none; + display: table-cell; + } + } +} + + +// Table backgrounds +// +// Exact selectors below required to override `.table-striped` and prevent +// inheritance to nested tables. + +// Generate the contextual variants +.table-row-variant(active; @table-bg-active); +.table-row-variant(success; @state-success-bg); +.table-row-variant(info; @state-info-bg); +.table-row-variant(warning; @state-warning-bg); +.table-row-variant(danger; @state-danger-bg); + + +// Responsive tables +// +// Wrap your tables in `.table-responsive` and we'll make them mobile friendly +// by enabling horizontal scrolling. Only applies <768px. Everything above that +// will display normally. + +.table-responsive { + overflow-x: auto; + min-height: 0.01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837) + + @media screen and (max-width: @screen-xs-max) { + width: 100%; + margin-bottom: (@line-height-computed * 0.75); + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid @table-border-color; + + // Tighten up spacing + > .table { + margin-bottom: 0; + + // Ensure the content doesn't wrap + > thead, + > tbody, + > tfoot { + > tr { + > th, + > td { + white-space: nowrap; + } + } + } + } + + // Special overrides for the bordered tables + > .table-bordered { + border: 0; + + // Nuke the appropriate borders so that the parent can handle them + > thead, + > tbody, + > tfoot { + > tr { + > th:first-child, + > td:first-child { + border-left: 0; + } + > th:last-child, + > td:last-child { + border-right: 0; + } + } + } + + // Only nuke the last row's bottom-border in `tbody` and `tfoot` since + // chances are there will be only one `tr` in a `thead` and that would + // remove the border altogether. + > tbody, + > tfoot { + > tr:last-child { + > th, + > td { + border-bottom: 0; + } + } + } + + } + } +} diff --git a/css/theme.less b/css/theme.less new file mode 100644 index 0000000..fb61744 --- /dev/null +++ b/css/theme.less @@ -0,0 +1,291 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +// +// Load core variables and mixins +// -------------------------------------------------- + +@import "variables.less"; +@import "mixins.less"; + + +// +// Buttons +// -------------------------------------------------- + +// Common styles +.btn-default, +.btn-primary, +.btn-success, +.btn-info, +.btn-warning, +.btn-danger { + text-shadow: 0 -1px 0 rgba(0,0,0,.2); + @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075); + .box-shadow(@shadow); + + // Reset the shadow + &:active, + &.active { + .box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); + } + + &.disabled, + &[disabled], + fieldset[disabled] & { + .box-shadow(none); + } + + .badge { + text-shadow: none; + } +} + +// Mixin for generating new styles +.btn-styles(@btn-color: #555) { + #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%)); + .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620 + background-repeat: repeat-x; + border-color: darken(@btn-color, 14%); + + &:hover, + &:focus { + background-color: darken(@btn-color, 12%); + background-position: 0 -15px; + } + + &:active, + &.active { + background-color: darken(@btn-color, 12%); + border-color: darken(@btn-color, 14%); + } + + &.disabled, + &[disabled], + fieldset[disabled] & { + &, + &:hover, + &:focus, + &.focus, + &:active, + &.active { + background-color: darken(@btn-color, 12%); + background-image: none; + } + } +} + +// Common styles +.btn { + // Remove the gradient for the pressed/active state + &:active, + &.active { + background-image: none; + } +} + +// Apply the mixin to the buttons +.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; } +.btn-primary { .btn-styles(@btn-primary-bg); } +.btn-success { .btn-styles(@btn-success-bg); } +.btn-info { .btn-styles(@btn-info-bg); } +.btn-warning { .btn-styles(@btn-warning-bg); } +.btn-danger { .btn-styles(@btn-danger-bg); } + + +// +// Images +// -------------------------------------------------- + +.thumbnail, +.img-thumbnail { + .box-shadow(0 1px 2px rgba(0,0,0,.075)); +} + + +// +// Dropdowns +// -------------------------------------------------- + +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%)); + background-color: darken(@dropdown-link-hover-bg, 5%); +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%)); + background-color: darken(@dropdown-link-active-bg, 5%); +} + + +// +// Navbar +// -------------------------------------------------- + +// Default navbar +.navbar-default { + #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg); + .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered + border-radius: @navbar-border-radius; + @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075); + .box-shadow(@shadow); + + .navbar-nav > .open > a, + .navbar-nav > .active > a { + #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%)); + .box-shadow(inset 0 3px 9px rgba(0,0,0,.075)); + } +} +.navbar-brand, +.navbar-nav > li > a { + text-shadow: 0 1px 0 rgba(255,255,255,.25); +} + +// Inverted navbar +.navbar-inverse { + #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg); + .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257 + border-radius: @navbar-border-radius; + .navbar-nav > .open > a, + .navbar-nav > .active > a { + #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%)); + .box-shadow(inset 0 3px 9px rgba(0,0,0,.25)); + } + + .navbar-brand, + .navbar-nav > li > a { + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + } +} + +// Undo rounded corners in static and fixed navbars +.navbar-static-top, +.navbar-fixed-top, +.navbar-fixed-bottom { + border-radius: 0; +} + +// Fix active state of dropdown items in collapsed mode +@media (max-width: @grid-float-breakpoint-max) { + .navbar .navbar-nav .open .dropdown-menu > .active > a { + &, + &:hover, + &:focus { + color: #fff; + #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%)); + } + } +} + + +// +// Alerts +// -------------------------------------------------- + +// Common styles +.alert { + text-shadow: 0 1px 0 rgba(255,255,255,.2); + @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05); + .box-shadow(@shadow); +} + +// Mixin for generating new styles +.alert-styles(@color) { + #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%)); + border-color: darken(@color, 15%); +} + +// Apply the mixin to the alerts +.alert-success { .alert-styles(@alert-success-bg); } +.alert-info { .alert-styles(@alert-info-bg); } +.alert-warning { .alert-styles(@alert-warning-bg); } +.alert-danger { .alert-styles(@alert-danger-bg); } + + +// +// Progress bars +// -------------------------------------------------- + +// Give the progress background some depth +.progress { + #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg) +} + +// Mixin for generating new styles +.progress-bar-styles(@color) { + #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%)); +} + +// Apply the mixin to the progress bars +.progress-bar { .progress-bar-styles(@progress-bar-bg); } +.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); } +.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); } +.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); } +.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); } + +// Reset the striped class because our mixins don't do multiple gradients and +// the above custom styles override the new `.progress-bar-striped` in v3.2.0. +.progress-bar-striped { + #gradient > .striped(); +} + + +// +// List groups +// -------------------------------------------------- + +.list-group { + border-radius: @border-radius-base; + .box-shadow(0 1px 2px rgba(0,0,0,.075)); +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%); + #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%)); + border-color: darken(@list-group-active-border, 7.5%); + + .badge { + text-shadow: none; + } +} + + +// +// Panels +// -------------------------------------------------- + +// Common styles +.panel { + .box-shadow(0 1px 2px rgba(0,0,0,.05)); +} + +// Mixin for generating new styles +.panel-heading-styles(@color) { + #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%)); +} + +// Apply the mixin to the panel headings only +.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); } +.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); } +.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); } +.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); } +.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); } +.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); } + + +// +// Wells +// -------------------------------------------------- + +.well { + #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg); + border-color: darken(@well-bg, 10%); + @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1); + .box-shadow(@shadow); +} diff --git a/css/thumbnails.less b/css/thumbnails.less new file mode 100644 index 0000000..0713e67 --- /dev/null +++ b/css/thumbnails.less @@ -0,0 +1,36 @@ +// +// Thumbnails +// -------------------------------------------------- + + +// Mixin and adjust the regular image class +.thumbnail { + display: block; + padding: @thumbnail-padding; + margin-bottom: @line-height-computed; + line-height: @line-height-base; + background-color: @thumbnail-bg; + border: 1px solid @thumbnail-border; + border-radius: @thumbnail-border-radius; + .transition(border .2s ease-in-out); + + > img, + a > img { + &:extend(.img-responsive); + margin-left: auto; + margin-right: auto; + } + + // Add a hover state for linked versions only + a&:hover, + a&:focus, + a&.active { + border-color: @link-color; + } + + // Image captions + .caption { + padding: @thumbnail-caption-padding; + color: @thumbnail-caption-color; + } +} diff --git a/css/tooltip.less b/css/tooltip.less new file mode 100644 index 0000000..b48d63e --- /dev/null +++ b/css/tooltip.less @@ -0,0 +1,101 @@ +// +// Tooltips +// -------------------------------------------------- + + +// Base class +.tooltip { + position: absolute; + z-index: @zindex-tooltip; + display: block; + // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element. + // So reset our font and text properties to avoid inheriting weird values. + .reset-text(); + font-size: @font-size-small; + + .opacity(0); + + &.in { .opacity(@tooltip-opacity); } + &.top { margin-top: -3px; padding: @tooltip-arrow-width 0; } + &.right { margin-left: 3px; padding: 0 @tooltip-arrow-width; } + &.bottom { margin-top: 3px; padding: @tooltip-arrow-width 0; } + &.left { margin-left: -3px; padding: 0 @tooltip-arrow-width; } +} + +// Wrapper for the tooltip content +.tooltip-inner { + max-width: @tooltip-max-width; + padding: 3px 8px; + color: @tooltip-color; + text-align: center; + background-color: @tooltip-bg; + border-radius: @border-radius-base; +} + +// Arrows +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + 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 { + bottom: 0; + right: @tooltip-arrow-width; + 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; + } +} diff --git a/css/type.less b/css/type.less new file mode 100644 index 0000000..0d4fee4 --- /dev/null +++ b/css/type.less @@ -0,0 +1,302 @@ +// +// Typography +// -------------------------------------------------- + + +// Headings +// ------------------------- + +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + font-family: @headings-font-family; + font-weight: @headings-font-weight; + line-height: @headings-line-height; + color: @headings-color; + + small, + .small { + font-weight: normal; + line-height: 1; + color: @headings-small-color; + } +} + +h1, .h1, +h2, .h2, +h3, .h3 { + margin-top: @line-height-computed; + margin-bottom: (@line-height-computed / 2); + + small, + .small { + font-size: 65%; + } +} +h4, .h4, +h5, .h5, +h6, .h6 { + margin-top: (@line-height-computed / 2); + margin-bottom: (@line-height-computed / 2); + + small, + .small { + font-size: 75%; + } +} + +h1, .h1 { font-size: @font-size-h1; } +h2, .h2 { font-size: @font-size-h2; } +h3, .h3 { font-size: @font-size-h3; } +h4, .h4 { font-size: @font-size-h4; } +h5, .h5 { font-size: @font-size-h5; } +h6, .h6 { font-size: @font-size-h6; } + + +// Body text +// ------------------------- + +p { + margin: 0 0 (@line-height-computed / 2); +} + +.lead { + margin-bottom: @line-height-computed; + font-size: floor((@font-size-base * 1.15)); + font-weight: 300; + line-height: 1.4; + + @media (min-width: @screen-sm-min) { + font-size: (@font-size-base * 1.5); + } +} + + +// Emphasis & misc +// ------------------------- + +// Ex: (12px small font / 14px base font) * 100% = about 85% +small, +.small { + font-size: floor((100% * @font-size-small / @font-size-base)); +} + +mark, +.mark { + background-color: @state-warning-bg; + padding: .2em; +} + +// Alignment +.text-left { text-align: left; } +.text-right { text-align: right; } +.text-center { text-align: center; } +.text-justify { text-align: justify; } +.text-nowrap { white-space: nowrap; } + +// Transformation +.text-lowercase { text-transform: lowercase; } +.text-uppercase { text-transform: uppercase; } +.text-capitalize { text-transform: capitalize; } + +// Contextual colors +.text-muted { + color: @text-muted; +} +.text-primary { + .text-emphasis-variant(@brand-primary); +} +.text-success { + .text-emphasis-variant(@state-success-text); +} +.text-info { + .text-emphasis-variant(@state-info-text); +} +.text-warning { + .text-emphasis-variant(@state-warning-text); +} +.text-danger { + .text-emphasis-variant(@state-danger-text); +} + +// Contextual backgrounds +// For now we'll leave these alongside the text classes until v4 when we can +// safely shift things around (per SemVer rules). +.bg-primary { + // Given the contrast here, this is the only class to have its color inverted + // automatically. + color: #fff; + .bg-variant(@brand-primary); +} +.bg-success { + .bg-variant(@state-success-bg); +} +.bg-info { + .bg-variant(@state-info-bg); +} +.bg-warning { + .bg-variant(@state-warning-bg); +} +.bg-danger { + .bg-variant(@state-danger-bg); +} + + +// Page header +// ------------------------- + +.page-header { + padding-bottom: ((@line-height-computed / 2) - 1); + margin: (@line-height-computed * 2) 0 @line-height-computed; + border-bottom: 1px solid @page-header-border-color; +} + + +// Lists +// ------------------------- + +// Unordered and Ordered lists +ul, +ol { + margin-top: 0; + margin-bottom: (@line-height-computed / 2); + ul, + ol { + margin-bottom: 0; + } +} + +// List options + +// Unstyled keeps list items block level, just removes default browser padding and list-style +.list-unstyled { + padding-left: 0; + list-style: none; +} + +// Inline turns list items into inline-block +.list-inline { + .list-unstyled(); + margin-left: -5px; + + > li { + display: inline-block; + padding-left: 5px; + padding-right: 5px; + } +} + +// Description Lists +dl { + margin-top: 0; // Remove browser default + margin-bottom: @line-height-computed; +} +dt, +dd { + line-height: @line-height-base; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; // Undo browser default +} + +// Horizontal description lists +// +// Defaults to being stacked without any of the below styles applied, until the +// grid breakpoint is reached (default of ~768px). + +.dl-horizontal { + dd { + &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present + } + + @media (min-width: @dl-horizontal-breakpoint) { + dt { + float: left; + width: (@dl-horizontal-offset - 20); + clear: left; + text-align: right; + .text-overflow(); + } + dd { + margin-left: @dl-horizontal-offset; + } + } +} + + +// Misc +// ------------------------- + +// Abbreviations and acronyms +abbr[title], +// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257 +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted @abbr-border-color; +} +.initialism { + font-size: 90%; + .text-uppercase(); +} + +// Blockquotes +blockquote { + padding: (@line-height-computed / 2) @line-height-computed; + margin: 0 0 @line-height-computed; + font-size: @blockquote-font-size; + border-left: 5px solid @blockquote-border-color; + + p, + ul, + ol { + &:last-child { + margin-bottom: 0; + } + } + + // Note: Deprecated small and .small as of v3.1.0 + // Context: https://github.com/twbs/bootstrap/issues/11660 + footer, + small, + .small { + display: block; + font-size: 80%; // back to default font-size + line-height: @line-height-base; + color: @blockquote-small-color; + + &:before { + content: '\2014 \00A0'; // em dash, nbsp + } + } +} + +// Opposite alignment of blockquote +// +// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0. +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + border-right: 5px solid @blockquote-border-color; + border-left: 0; + text-align: right; + + // Account for citation + footer, + small, + .small { + &:before { content: ''; } + &:after { + content: '\00A0 \2014'; // nbsp, em dash + } + } +} + +// Addresses +address { + margin-bottom: @line-height-computed; + font-style: normal; + line-height: @line-height-base; +} diff --git a/css/utilities.less b/css/utilities.less new file mode 100644 index 0000000..7a8ca27 --- /dev/null +++ b/css/utilities.less @@ -0,0 +1,55 @@ +// +// Utility classes +// -------------------------------------------------- + + +// Floats +// ------------------------- + +.clearfix { + .clearfix(); +} +.center-block { + .center-block(); +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} + + +// Toggling content +// ------------------------- + +// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1 +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + .text-hide(); +} + + +// Hide from screenreaders and browsers +// +// Credit: HTML5 Boilerplate + +.hidden { + display: none !important; +} + + +// For Affix plugin +// ------------------------- + +.affix { + position: fixed; +} diff --git a/css/variables.less b/css/variables.less new file mode 100644 index 0000000..6d7fecb --- /dev/null +++ b/css/variables.less @@ -0,0 +1,870 @@ +// +// Variables +// -------------------------------------------------- + + +//== Colors +// +//## Gray and brand colors for use across Bootstrap. + +@gray-base: #000; +@gray-darker: lighten(@gray-base, 13.5%); // #222 +@gray-dark: lighten(@gray-base, 20%); // #333 +@gray: lighten(@gray-base, 33.5%); // #555 +@gray-light: lighten(@gray-base, 46.7%); // #777 +@gray-light2: lighten(@gray-base, 80%); // Nick +@gray-lighter: lighten(@gray-base, 93.5%); // #eee + +@brand-primary: #0a0; // Nick darken(#428bca, 6.5%); // #337ab7 +@brand-success: #ff8000; // Nick #5cb85c; +@brand-info: #5bc0de; +@brand-warning: #f0ad4e; +@brand-danger: #d9534f; + + +//== Scaffolding +// +//## Settings for some of the most global styles. + +//** Background color for ``. +@body-bg: #fff; +//** Global text color on ``. +@text-color: #000; // Nick @gray-dark; + +//** Global textual link color. +@link-color: darken(@brand-primary, 10%); // Nick @brand-primary; +//** Link hover color set via `darken()` function. +@link-hover-color: darken(@link-color, 10%); // Nick 15%); +//** Link hover decoration. +@link-hover-decoration: underline; + + +//== Typography +// +//## Font, line-height, and color for body text, headings, and more. + +@font-family-sans-serif: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; // Nick added Nunito Sans +@font-family-serif: "Frank Ruhl Libre", Georgia, "Times New Roman", Times, serif; // Nick added Frank Ruhl Libre +//** Default monospace fonts for ``, ``, and `
`.
+@font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
+@font-family-base:        @font-family-sans-serif;
+
+@font-size-base:          17px; // Nick 14px;
+@font-size-large:         ceil((@font-size-base * 1.25)); // ~18px
+@font-size-small:         ceil((@font-size-base * 0.85)); // ~12px
+
+@font-size-h1:            floor((@font-size-base * 1.8)); // Nick 2.6)); // ~36px
+@font-size-h2:            floor((@font-size-base * 1.6)); // Nick 2.15)); // ~30px
+@font-size-h3:            ceil((@font-size-base * 1.4)); // Nick 1.7)); // ~24px
+@font-size-h4:            ceil((@font-size-base * 1.2)); // ~18px
+@font-size-h5:            @font-size-base;
+@font-size-h6:            ceil((@font-size-base * 0.85)); // ~12px
+
+//** Unit-less `line-height` for use in components like buttons.
+@line-height-base:        1.428571429; // 20/14
+//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
+@line-height-computed:    floor((@font-size-base * @line-height-base)); // ~20px
+
+//** By default, this inherits from the ``.
+@headings-font-family:    inherit;
+@headings-font-weight:    600; // Nick 500;
+@headings-line-height:    1.1;
+@headings-color:          inherit;
+
+
+//== Iconography
+//
+//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
+
+//** Load fonts from this directory.
+@icon-font-path:          "../fonts/";
+//** File name for all font files.
+@icon-font-name:          "glyphicons-halflings-regular";
+//** Element ID within SVG icon file.
+@icon-font-svg-id:        "glyphicons_halflingsregular";
+
+
+//== Components
+//
+//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
+
+@padding-base-vertical:     6px;
+@padding-base-horizontal:   12px;
+
+@padding-large-vertical:    10px;
+@padding-large-horizontal:  16px;
+
+@padding-small-vertical:    5px;
+@padding-small-horizontal:  10px;
+
+@padding-xs-vertical:       1px;
+@padding-xs-horizontal:     5px;
+
+@line-height-large:         1.3333333; // extra decimals for Win 8.1 Chrome
+@line-height-small:         1.5;
+
+@border-radius-base:        4px;
+@border-radius-large:       6px;
+@border-radius-small:       3px;
+
+//** Global color for active items (e.g., navs or dropdowns).
+@component-active-color:    #fff;
+//** Global background color for active items (e.g., navs or dropdowns).
+@component-active-bg:       @brand-primary;
+
+//** Width of the `border` for generating carets that indicate dropdowns.
+@caret-width-base:          4px;
+//** Carets increase slightly in size for larger components.
+@caret-width-large:         5px;
+
+
+//== Tables
+//
+//## Customizes the `.table` component with basic values, each used across all table variations.
+
+//** Padding for ``s and ``s.
+@table-cell-padding:            8px;
+//** Padding for cells in `.table-condensed`.
+@table-condensed-cell-padding:  5px;
+
+//** Default background color used for all tables.
+@table-bg:                      transparent;
+//** Background color used for `.table-striped`.
+@table-bg-accent:               #f9f9f9;
+//** Background color used for `.table-hover`.
+@table-bg-hover:                #f5f5f5;
+@table-bg-active:               @table-bg-hover;
+
+//** Border color for table and cell borders.
+@table-border-color:            #ddd;
+
+
+//== Buttons
+//
+//## For each of Bootstrap's buttons, define text, background and border color.
+
+@btn-font-weight:                normal;
+
+@btn-default-color:              #333;
+@btn-default-bg:                 #fff;
+@btn-default-border:             #ccc;
+
+@btn-primary-color:              #fff;
+@btn-primary-bg:                 @brand-primary;
+@btn-primary-border:             darken(@btn-primary-bg, 5%);
+
+@btn-success-color:              #fff;
+@btn-success-bg:                 @brand-success;
+@btn-success-border:             darken(@btn-success-bg, 5%);
+
+@btn-info-color:                 #fff;
+@btn-info-bg:                    @brand-info;
+@btn-info-border:                darken(@btn-info-bg, 5%);
+
+@btn-warning-color:              #fff;
+@btn-warning-bg:                 @brand-warning;
+@btn-warning-border:             darken(@btn-warning-bg, 5%);
+
+@btn-danger-color:               #fff;
+@btn-danger-bg:                  @brand-danger;
+@btn-danger-border:              darken(@btn-danger-bg, 5%);
+
+@btn-link-disabled-color:        @gray-light;
+
+// Allows for customizing button radius independently from global border radius
+@btn-border-radius-base:         @border-radius-base;
+@btn-border-radius-large:        @border-radius-large;
+@btn-border-radius-small:        @border-radius-small;
+
+
+//== Forms
+//
+//##
+
+//** `` background color
+@input-bg:                       #fff;
+//** `` background color
+@input-bg-disabled:              @gray-lighter;
+
+//** Text color for ``s
+@input-color:                    @gray;
+//** `` border color
+@input-border:                   #ccc;
+
+// TODO: Rename `@input-border-radius` to `@input-border-radius-base` in v4
+//** Default `.form-control` border radius
+// This has no effect on ``s in CSS.
+@input-border-radius:            @border-radius-base;
+//** Large `.form-control` border radius
+@input-border-radius-large:      @border-radius-large;
+//** Small `.form-control` border radius
+@input-border-radius-small:      @border-radius-small;
+
+//** Border color for inputs on focus
+@input-border-focus:             #66afe9;
+
+//** Placeholder text color
+@input-color-placeholder:        #999;
+
+//** Default `.form-control` height
+@input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);
+//** Large `.form-control` height
+@input-height-large:             (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);
+//** Small `.form-control` height
+@input-height-small:             (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);
+
+//** `.form-group` margin
+@form-group-margin-bottom:       15px;
+
+@legend-color:                   @gray-dark;
+@legend-border-color:            #e5e5e5;
+
+//** Background color for textual input addons
+@input-group-addon-bg:           @gray-lighter;
+//** Border color for textual input addons
+@input-group-addon-border-color: @input-border;
+
+//** Disabled cursor for form controls and buttons.
+@cursor-disabled:                not-allowed;
+
+
+//== Dropdowns
+//
+//## Dropdown menu container and contents.
+
+//** Background for the dropdown menu.
+@dropdown-bg:                    #fff;
+//** Dropdown menu `border-color`.
+@dropdown-border:                rgba(0,0,0,.15);
+//** Dropdown menu `border-color` **for IE8**.
+@dropdown-fallback-border:       #ccc;
+//** Divider color for between dropdown items.
+@dropdown-divider-bg:            #e5e5e5;
+
+//** Dropdown link text color.
+@dropdown-link-color:            @gray-dark;
+//** Hover color for dropdown links.
+@dropdown-link-hover-color:      darken(@gray-dark, 5%);
+//** Hover background for dropdown links.
+@dropdown-link-hover-bg:         #f5f5f5;
+
+//** Active dropdown menu item text color.
+@dropdown-link-active-color:     @component-active-color;
+//** Active dropdown menu item background color.
+@dropdown-link-active-bg:        @component-active-bg;
+
+//** Disabled dropdown menu item background color.
+@dropdown-link-disabled-color:   @gray-light;
+
+//** Text color for headers within dropdown menus.
+@dropdown-header-color:          @gray-light;
+
+//** Deprecated `@dropdown-caret-color` as of v3.1.0
+@dropdown-caret-color:           #000;
+
+
+//-- Z-index master list
+//
+// Warning: Avoid customizing these values. They're used for a bird's eye view
+// of components dependent on the z-axis and are designed to all work together.
+//
+// Note: These variables are not generated into the Customizer.
+
+@zindex-navbar:            1000;
+@zindex-dropdown:          1000;
+@zindex-popover:           1060;
+@zindex-tooltip:           1070;
+@zindex-navbar-fixed:      1030;
+@zindex-modal-background:  1040;
+@zindex-modal:             1050;
+
+
+//== Media queries breakpoints
+//
+//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
+
+// Extra small screen / phone
+//** Deprecated `@screen-xs` as of v3.0.1
+@screen-xs:                  480px;
+//** Deprecated `@screen-xs-min` as of v3.2.0
+@screen-xs-min:              @screen-xs;
+//** Deprecated `@screen-phone` as of v3.0.1
+@screen-phone:               @screen-xs-min;
+
+// Small screen / tablet
+//** Deprecated `@screen-sm` as of v3.0.1
+@screen-sm:                  768px;
+@screen-sm-min:              @screen-sm;
+//** Deprecated `@screen-tablet` as of v3.0.1
+@screen-tablet:              @screen-sm-min;
+
+// Medium screen / desktop
+//** Deprecated `@screen-md` as of v3.0.1
+@screen-md:                  992px;
+@screen-md-min:              @screen-md;
+//** Deprecated `@screen-desktop` as of v3.0.1
+@screen-desktop:             @screen-md-min;
+
+// Large screen / wide desktop
+//** Deprecated `@screen-lg` as of v3.0.1
+@screen-lg:                  1200px;
+@screen-lg-min:              @screen-lg;
+//** Deprecated `@screen-lg-desktop` as of v3.0.1
+@screen-lg-desktop:          @screen-lg-min;
+
+// So media queries don't overlap when required, provide a maximum
+@screen-xs-max:              (@screen-sm-min - 1);
+@screen-sm-max:              (@screen-md-min - 1);
+@screen-md-max:              (@screen-lg-min - 1);
+
+
+//== Grid system
+//
+//## Define your custom responsive grid.
+
+//** Number of columns in the grid.
+@grid-columns:              12;
+//** Padding between columns. Gets divided in half for the left and right.
+@grid-gutter-width:         30px;
+// Navbar collapse
+//** Point at which the navbar becomes uncollapsed.
+@grid-float-breakpoint:     @screen-sm-min;
+//** Point at which the navbar begins collapsing.
+@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
+
+
+//== Container sizes
+//
+//## Define the maximum width of `.container` for different screen sizes.
+
+// Small screen / tablet
+@container-tablet:             (720px + @grid-gutter-width);
+//** For `@screen-sm-min` and up.
+@container-sm:                 @container-tablet;
+
+// Medium screen / desktop
+@container-desktop:            (940px + @grid-gutter-width);
+//** For `@screen-md-min` and up.
+@container-md:                 @container-desktop;
+
+// Large screen / wide desktop
+@container-large-desktop:      (1140px + @grid-gutter-width);
+//** For `@screen-lg-min` and up.
+@container-lg:                 @container-large-desktop;
+
+
+//== Navbar
+//
+//##
+
+// Basics of a navbar
+@navbar-height:                    50px;
+@navbar-margin-bottom:             @line-height-computed;
+@navbar-border-radius:             0; // Nick @border-radius-base;
+@navbar-padding-horizontal:        0; // Nick floor((@grid-gutter-width / 2));
+@navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);
+@navbar-collapse-max-height:       340px;
+
+@navbar-default-color:             #fff; // Nick #777;
+@navbar-default-bg:                #0a0; // Nick #f8f8f8;
+@navbar-default-border:            darken(@navbar-default-bg, 6.5%);
+
+// Navbar links
+@navbar-default-link-color:                #fff; // Nick #777;
+@navbar-default-link-hover-color:          #000; // Nick #333;
+@navbar-default-link-hover-bg:             #ccc; // Nick transparent;
+@navbar-default-link-active-color:         #fff; // Nick #555;
+@navbar-default-link-active-bg:            darken(@navbar-default-bg, 6.5%);
+@navbar-default-link-disabled-color:       #ccc;
+@navbar-default-link-disabled-bg:          transparent;
+
+// Navbar brand label
+@navbar-default-brand-color:               @navbar-default-link-color;
+@navbar-default-brand-hover-color:         darken(@navbar-default-brand-color, 10%);
+@navbar-default-brand-hover-bg:            transparent;
+
+// Navbar toggle
+@navbar-default-toggle-hover-bg:           #ddd;
+@navbar-default-toggle-icon-bar-bg:        #888;
+@navbar-default-toggle-border-color:       #ddd;
+
+
+//=== Inverted navbar
+// Reset inverted navbar basics
+@navbar-inverse-color:                      lighten(@gray-light, 15%);
+@navbar-inverse-bg:                         #222;
+@navbar-inverse-border:                     darken(@navbar-inverse-bg, 10%);
+
+// Inverted navbar links
+@navbar-inverse-link-color:                 lighten(@gray-light, 15%);
+@navbar-inverse-link-hover-color:           #fff;
+@navbar-inverse-link-hover-bg:              transparent;
+@navbar-inverse-link-active-color:          @navbar-inverse-link-hover-color;
+@navbar-inverse-link-active-bg:             darken(@navbar-inverse-bg, 10%);
+@navbar-inverse-link-disabled-color:        #444;
+@navbar-inverse-link-disabled-bg:           transparent;
+
+// Inverted navbar brand label
+@navbar-inverse-brand-color:                @navbar-inverse-link-color;
+@navbar-inverse-brand-hover-color:          #fff;
+@navbar-inverse-brand-hover-bg:             transparent;
+
+// Inverted navbar toggle
+@navbar-inverse-toggle-hover-bg:            #333;
+@navbar-inverse-toggle-icon-bar-bg:         #fff;
+@navbar-inverse-toggle-border-color:        #333;
+
+
+//== Navs
+//
+//##
+
+//=== Shared nav styles
+@nav-link-padding:                          10px 15px;
+@nav-link-hover-bg:                         @gray-lighter;
+
+@nav-disabled-link-color:                   @gray-light;
+@nav-disabled-link-hover-color:             @gray-light;
+
+//== Tabs
+@nav-tabs-border-color:                     #ddd;
+
+@nav-tabs-link-hover-border-color:          @gray-lighter;
+
+@nav-tabs-active-link-hover-bg:             @body-bg;
+@nav-tabs-active-link-hover-color:          @gray;
+@nav-tabs-active-link-hover-border-color:   #ddd;
+
+@nav-tabs-justified-link-border-color:            #ddd;
+@nav-tabs-justified-active-link-border-color:     @body-bg;
+
+//== Pills
+@nav-pills-border-radius:                   @border-radius-base;
+@nav-pills-active-link-hover-bg:            @component-active-bg;
+@nav-pills-active-link-hover-color:         @component-active-color;
+
+
+//== Pagination
+//
+//##
+
+@pagination-color:                     @link-color;
+@pagination-bg:                        #fff;
+@pagination-border:                    #ddd;
+
+@pagination-hover-color:               @link-hover-color;
+@pagination-hover-bg:                  @gray-lighter;
+@pagination-hover-border:              #ddd;
+
+@pagination-active-color:              #fff;
+@pagination-active-bg:                 @brand-primary;
+@pagination-active-border:             @brand-primary;
+
+@pagination-disabled-color:            @gray-light;
+@pagination-disabled-bg:               #fff;
+@pagination-disabled-border:           #ddd;
+
+
+//== Pager
+//
+//##
+
+@pager-bg:                             @pagination-bg;
+@pager-border:                         @pagination-border;
+@pager-border-radius:                  15px;
+
+@pager-hover-bg:                       @pagination-hover-bg;
+
+@pager-active-bg:                      @pagination-active-bg;
+@pager-active-color:                   @pagination-active-color;
+
+@pager-disabled-color:                 @pagination-disabled-color;
+
+
+//== Jumbotron
+//
+//##
+
+@jumbotron-padding:              30px;
+@jumbotron-color:                inherit;
+@jumbotron-bg:                   @gray-lighter;
+@jumbotron-heading-color:        inherit;
+@jumbotron-font-size:            ceil((@font-size-base * 1.5));
+@jumbotron-heading-font-size:    ceil((@font-size-base * 4.5));
+
+
+//== Form states and alerts
+//
+//## Define colors for form feedback states and, by default, alerts.
+
+@state-success-text:             #3c763d;
+@state-success-bg:               #dff0d8;
+@state-success-border:           darken(spin(@state-success-bg, -10), 5%);
+
+@state-info-text:                #31708f;
+@state-info-bg:                  #d9edf7;
+@state-info-border:              darken(spin(@state-info-bg, -10), 7%);
+
+@state-warning-text:             #8a6d3b;
+@state-warning-bg:               #fcf8e3;
+@state-warning-border:           darken(spin(@state-warning-bg, -10), 5%);
+
+@state-danger-text:              #a94442;
+@state-danger-bg:                #f2dede;
+@state-danger-border:            darken(spin(@state-danger-bg, -10), 5%);
+
+
+//== Tooltips
+//
+//##
+
+//** Tooltip max width
+@tooltip-max-width:           200px;
+//** Tooltip text color
+@tooltip-color:               #fff;
+//** Tooltip background color
+@tooltip-bg:                  #000;
+@tooltip-opacity:             .9;
+
+//** Tooltip arrow width
+@tooltip-arrow-width:         5px;
+//** Tooltip arrow color
+@tooltip-arrow-color:         @tooltip-bg;
+
+
+//== Popovers
+//
+//##
+
+//** Popover body background color
+@popover-bg:                          #fff;
+//** Popover maximum width
+@popover-max-width:                   276px;
+//** Popover border color
+@popover-border-color:                rgba(0,0,0,.2);
+//** Popover fallback border color
+@popover-fallback-border-color:       #ccc;
+
+//** Popover title background color
+@popover-title-bg:                    darken(@popover-bg, 3%);
+
+//** Popover arrow width
+@popover-arrow-width:                 10px;
+//** Popover arrow color
+@popover-arrow-color:                 @popover-bg;
+
+//** Popover outer arrow width
+@popover-arrow-outer-width:           (@popover-arrow-width + 1);
+//** Popover outer arrow color
+@popover-arrow-outer-color:           fadein(@popover-border-color, 5%);
+//** Popover outer arrow fallback color
+@popover-arrow-outer-fallback-color:  darken(@popover-fallback-border-color, 20%);
+
+
+//== Labels
+//
+//##
+
+//** Default label background color
+@label-default-bg:            @gray-light;
+//** Primary label background color
+@label-primary-bg:            @brand-primary;
+//** Success label background color
+@label-success-bg:            @brand-success;
+//** Info label background color
+@label-info-bg:               @brand-info;
+//** Warning label background color
+@label-warning-bg:            @brand-warning;
+//** Danger label background color
+@label-danger-bg:             @brand-danger;
+
+//** Default label text color
+@label-color:                 #fff;
+//** Default text color of a linked label
+@label-link-hover-color:      #fff;
+
+
+//== Modals
+//
+//##
+
+//** Padding applied to the modal body
+@modal-inner-padding:         15px;
+
+//** Padding applied to the modal title
+@modal-title-padding:         15px;
+//** Modal title line-height
+@modal-title-line-height:     @line-height-base;
+
+//** Background color of modal content area
+@modal-content-bg:                             #fff;
+//** Modal content border color
+@modal-content-border-color:                   rgba(0,0,0,.2);
+//** Modal content border color **for IE8**
+@modal-content-fallback-border-color:          #999;
+
+//** Modal backdrop background color
+@modal-backdrop-bg:           #000;
+//** Modal backdrop opacity
+@modal-backdrop-opacity:      .5;
+//** Modal header border color
+@modal-header-border-color:   #e5e5e5;
+//** Modal footer border color
+@modal-footer-border-color:   @modal-header-border-color;
+
+@modal-lg:                    900px;
+@modal-md:                    600px;
+@modal-sm:                    300px;
+
+
+//== Alerts
+//
+//## Define alert colors, border radius, and padding.
+
+@alert-padding:               15px;
+@alert-border-radius:         @border-radius-base;
+@alert-link-font-weight:      bold;
+
+@alert-success-bg:            @state-success-bg;
+@alert-success-text:          @state-success-text;
+@alert-success-border:        @state-success-border;
+
+@alert-info-bg:               @state-info-bg;
+@alert-info-text:             @state-info-text;
+@alert-info-border:           @state-info-border;
+
+@alert-warning-bg:            @state-warning-bg;
+@alert-warning-text:          @state-warning-text;
+@alert-warning-border:        @state-warning-border;
+
+@alert-danger-bg:             @state-danger-bg;
+@alert-danger-text:           @state-danger-text;
+@alert-danger-border:         @state-danger-border;
+
+
+//== Progress bars
+//
+//##
+
+//** Background color of the whole progress component
+@progress-bg:                 #f5f5f5;
+//** Progress bar text color
+@progress-bar-color:          #fff;
+//** Variable for setting rounded corners on progress bar.
+@progress-border-radius:      @border-radius-base;
+
+//** Default progress bar color
+@progress-bar-bg:             @brand-primary;
+//** Success progress bar color
+@progress-bar-success-bg:     @brand-success;
+//** Warning progress bar color
+@progress-bar-warning-bg:     @brand-warning;
+//** Danger progress bar color
+@progress-bar-danger-bg:      @brand-danger;
+//** Info progress bar color
+@progress-bar-info-bg:        @brand-info;
+
+
+//== List group
+//
+//##
+
+//** Background color on `.list-group-item`
+@list-group-bg:                 #fff;
+//** `.list-group-item` border color
+@list-group-border:             #ddd;
+//** List group border radius
+@list-group-border-radius:      @border-radius-base;
+
+//** Background color of single list items on hover
+@list-group-hover-bg:           #f5f5f5;
+//** Text color of active list items
+@list-group-active-color:       @component-active-color;
+//** Background color of active list items
+@list-group-active-bg:          @component-active-bg;
+//** Border color of active list elements
+@list-group-active-border:      @list-group-active-bg;
+//** Text color for content within active list items
+@list-group-active-text-color:  lighten(@list-group-active-bg, 40%);
+
+//** Text color of disabled list items
+@list-group-disabled-color:      @gray-light;
+//** Background color of disabled list items
+@list-group-disabled-bg:         @gray-lighter;
+//** Text color for content within disabled list items
+@list-group-disabled-text-color: @list-group-disabled-color;
+
+@list-group-link-color:         #555;
+@list-group-link-hover-color:   @list-group-link-color;
+@list-group-link-heading-color: #333;
+
+
+//== Panels
+//
+//##
+
+@panel-bg:                    #fff;
+@panel-body-padding:          15px;
+@panel-heading-padding:       10px 15px;
+@panel-footer-padding:        @panel-heading-padding;
+@panel-border-radius:         @border-radius-base;
+
+//** Border color for elements within panels
+@panel-inner-border:          #ddd;
+@panel-footer-bg:             #f5f5f5;
+
+@panel-default-text:          @gray-dark;
+@panel-default-border:        #ddd;
+@panel-default-heading-bg:    #f5f5f5;
+
+@panel-primary-text:          #fff;
+@panel-primary-border:        @brand-primary;
+@panel-primary-heading-bg:    @brand-primary;
+
+@panel-success-text:          @state-success-text;
+@panel-success-border:        @state-success-border;
+@panel-success-heading-bg:    @state-success-bg;
+
+@panel-info-text:             @state-info-text;
+@panel-info-border:           @state-info-border;
+@panel-info-heading-bg:       @state-info-bg;
+
+@panel-warning-text:          @state-warning-text;
+@panel-warning-border:        @state-warning-border;
+@panel-warning-heading-bg:    @state-warning-bg;
+
+@panel-danger-text:           @state-danger-text;
+@panel-danger-border:         @state-danger-border;
+@panel-danger-heading-bg:     @state-danger-bg;
+
+
+//== Thumbnails
+//
+//##
+
+//** Padding around the thumbnail image
+@thumbnail-padding:           4px;
+//** Thumbnail background color
+@thumbnail-bg:                @body-bg;
+//** Thumbnail border color
+@thumbnail-border:            #ddd;
+//** Thumbnail border radius
+@thumbnail-border-radius:     @border-radius-base;
+
+//** Custom text color for thumbnail captions
+@thumbnail-caption-color:     @text-color;
+//** Padding around the thumbnail caption
+@thumbnail-caption-padding:   9px;
+
+
+//== Wells
+//
+//##
+
+@well-bg:                     #f5f5f5;
+@well-border:                 darken(@well-bg, 7%);
+
+
+//== Badges
+//
+//##
+
+@badge-color:                 #fff;
+//** Linked badge text color on hover
+@badge-link-hover-color:      #fff;
+@badge-bg:                    @gray-light;
+
+//** Badge text color in active nav link
+@badge-active-color:          @link-color;
+//** Badge background color in active nav link
+@badge-active-bg:             #fff;
+
+@badge-font-weight:           bold;
+@badge-line-height:           1;
+@badge-border-radius:         10px;
+
+
+//== Breadcrumbs
+//
+//##
+
+@breadcrumb-padding-vertical:   8px;
+@breadcrumb-padding-horizontal: 15px;
+//** Breadcrumb background color
+@breadcrumb-bg:                 #f5f5f5;
+//** Breadcrumb text color
+@breadcrumb-color:              #ccc;
+//** Text color of current page in the breadcrumb
+@breadcrumb-active-color:       @gray-light;
+//** Textual separator for between breadcrumb elements
+@breadcrumb-separator:          "/";
+
+
+//== Carousel
+//
+//##
+
+@carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6);
+
+@carousel-control-color:                      #fff;
+@carousel-control-width:                      15%;
+@carousel-control-opacity:                    .5;
+@carousel-control-font-size:                  20px;
+
+@carousel-indicator-active-bg:                #fff;
+@carousel-indicator-border-color:             #fff;
+
+@carousel-caption-color:                      #fff;
+
+
+//== Close
+//
+//##
+
+@close-font-weight:           bold;
+@close-color:                 #000;
+@close-text-shadow:           0 1px 0 #fff;
+
+
+//== Code
+//
+//##
+
+@code-color:                  #c7254e;
+@code-bg:                     #f9f2f4;
+
+@kbd-color:                   #fff;
+@kbd-bg:                      #333;
+
+@pre-bg:                      #f5f5f5;
+@pre-color:                   @gray-dark;
+@pre-border-color:            #ccc;
+@pre-scrollable-max-height:   340px;
+
+
+//== Type
+//
+//##
+
+//** Horizontal offset for forms and lists.
+@component-offset-horizontal: 180px;
+//** Text muted color
+@text-muted:                  @gray-light;
+//** Abbreviations and acronyms border color
+@abbr-border-color:           @gray-light;
+//** Headings small color
+@headings-small-color:        @gray-light;
+//** Blockquote small color
+@blockquote-small-color:      @gray-light;
+//** Blockquote font size
+@blockquote-font-size:        (@font-size-base * 1.25);
+//** Blockquote border color
+@blockquote-border-color:     @gray-lighter;
+//** Page header border color
+@page-header-border-color:    @gray-lighter;
+//** Width of horizontal description list titles
+@dl-horizontal-offset:        @component-offset-horizontal;
+//** Point at which .dl-horizontal becomes horizontal
+@dl-horizontal-breakpoint:    @grid-float-breakpoint;
+//** Horizontal line color.
+@hr-border:                   @gray-lighter;
diff --git a/css/wells.less b/css/wells.less
new file mode 100644
index 0000000..15d072b
--- /dev/null
+++ b/css/wells.less
@@ -0,0 +1,29 @@
+//
+// Wells
+// --------------------------------------------------
+
+
+// Base class
+.well {
+  min-height: 20px;
+  padding: 19px;
+  margin-bottom: 20px;
+  background-color: @well-bg;
+  border: 1px solid @well-border;
+  border-radius: @border-radius-base;
+  .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));
+  blockquote {
+    border-color: #ddd;
+    border-color: rgba(0,0,0,.15);
+  }
+}
+
+// Sizes
+.well-lg {
+  padding: 24px;
+  border-radius: @border-radius-large;
+}
+.well-sm {
+  padding: 9px;
+  border-radius: @border-radius-small;
+}
diff --git a/feedback.html.jst b/feedback.html.jst
new file mode 100644
index 0000000..6a3d1d9
--- /dev/null
+++ b/feedback.html.jst
@@ -0,0 +1,48 @@
+let querystring = require('querystring')
+let stream_buffers = require('stream-buffers')
+let XDate = require('xdate')
+
+return async env => {
+  if (env.request.method == 'POST') {
+    let write_stream = new stream_buffers.WritableStreamBuffer()
+    let data = new Promise(
+      (resolve, reject) => {
+        write_stream.
+        on('finish', () => {resolve(write_stream.getContents())}).
+        on('error', () => {reject()})
+      }
+    )
+    env.request.pipe(write_stream)
+    let query = querystring.parse((await data).toString())
+    console.log('received feedback form:', JSON.stringify(query)) //query.page)
+
+    // save the form contents in a dated logfile, so that we can
+    // recover manually if the email doesn't send for some reason
+    date = new XDate()
+    query.date = date.toUTCString()
+
+    await env.site.ensure_dir('/_logs')
+    env.site.modify_json(
+      `/_logs/feedback_${date.toUTCString('yyyyMMdd')}.json`,
+      [],
+      async result => {result.value.push(query)}
+    )
+
+    // send email (asynchronously)
+    ;(await env.site.get_emailjs('/_config/email_feedback.json')).send(
+      {
+        text: query.message,
+        from: 'Feedback form ',
+        to: 'Nick Downing ',
+        subject: 'Page: ' + query.page
+      },
+      (err, message) => {
+        if (err)
+          console.error(err.stack || err.message)
+        else
+          console.log('sent feedback email:', query.page)
+      }
+    )
+  }
+  env.site.serve(env, 200, Buffer.from('Thanks!'), 'feedback_form.html.jst')
+}
diff --git a/feedback_form.jst b/feedback_form.jst
new file mode 100644
index 0000000..402e868
--- /dev/null
+++ b/feedback_form.jst
@@ -0,0 +1,26 @@
+return async (env, _out) => {
+  form#feedback-form(method="post" action="feedback.html" role="form") {
+    div.row {
+      div.col-md-12 {
+        div.form-group {
+          label(for="feedback-form-message") {'Message *'}
+          textarea.form-control#feedback-form-message(name="message" placeholder="Please tell us your thoughts" rows="4" required="required" data-error="Please, leave us a message.") {}
+          div.help-block.with-errors {}
+        }
+      }
+    }
+    p {} // fix this later
+    div.row {
+      div.col-md-12 {
+        p.text-muted {
+          strong {'*'}
+          'These fields are required.'
+          //'Contact form template by '
+          //a(href="https://bootstrapious.com/p/how-to-build-a-working-bootstrap-feedback-form" target="_blank") {'Bootstrapious'}
+          //'.'
+        }
+      }
+    }
+    input.btn.btn-success.btn-send(style="display: none;" type="submit" value="Send message") {}
+  }
+}
diff --git a/images/by-sa_3.0_88x31.png b/images/by-sa_3.0_88x31.png
new file mode 100644
index 0000000000000000000000000000000000000000..e76aeffd876ae1ca70e124863b7acfa06831e83d
GIT binary patch
literal 1697
zcmV;S244AzP)Pwv1JY9U
zHEK~?Rfs$&tklX>JyNOm
ztV)N+hjjE@6I|Dx)p!hZ6jkASa4&SEvPsqn-DZ%3;Z+@E0NkY@`;6dbQ*41bCCAnZ
z&;aVp!x_5s$sM}ZbM4YmKp)0tewyLAmBR{e3M@KP$gnl_2pamIR08^?eY?b~wKlSzX+2$S0_5beYdqE+v=By5K
zT&Ii8NQgGJHISQG@!aD%8XOwb^^cB@(!|6B3$8^!=3uT<5IU(}2N*mFTDSnW#FjYr
z>6ih$-rimsA0MZwsVPb(lPtJKAI9XlxHpqaQ@N&r3`+)GXq>!5Td}Z_DlDux@N|)S
zdU`Z{V`Fr3azf^YK8#@w<~jkP)B1IQ;n2XBpaod28C-w|AkWUus96gH0tTS5>i`)W
zhK<7-aD1^;ly-KvHL9?%V(|loWnly5CV6SY+G0(+09ffiJw2s-F2{mvH~MveaeU#F
zz_~cuGPolnBbGbcs9_Uuk8H`%2~Nfu7gQlx@zxpzH&+cb0p1gq_5Ap_CBSIH((G`i
zmHwwsp3pmQ_ZuV{iL|BP0C1YAA<=gskb;M<3IdpMOE$fBT&dvVV5gv3>)6!wC^woT!IwGEJBPh9@^Si)_iz
zaF1Ppg?<1$FfhPi4h{|)_*hu+zb{`p(r*A58Jc>eax5#XSEdF;3*aB@9z+J;JT_ZD
zPNE4bhHh{%GQLYR5eaG0gv7(I6JWG(I84LC!~B1&0Df`uqJ4nj+;D_N5+NTwENB()
z0?c4AXgMa|FOD>HBjW=VW$PhZEEVHu0z53jwf}-5(BRDAsa0T0Q|-12OSI@t`HZau{V7;C90_(EzGB
zO#ABIJ?3avVcP!@V5f^M2G^O}>Z@>+JBoD70ojKO9?KlIAUOuNONPU^oDrim08p_TcVHwx1LvgkLD
zp~0f%Fs&ekY0+fkFs-Y~l_JrQ(`0iz>t&*`Ur#hpx)g~OY6x_1~>r~lE9l+~U
zxvKz{i#>~fFL+<fKA6*N0Uvn5$ZKL3hG0c$*?l(WY$<%;28yonBKE5}0{P19slL3G%TXH*(
zi~w%ATB7+s=iRD8&a;MJ*0k;gE{OJHA@
r1-Ih(jstupE`*5Qx&oqeZ&$to@Ym>elSGv`00000NkvXXu0mjf@eCw0

literal 0
HcmV?d00001

diff --git a/index.html.jst b/index.html.jst
new file mode 100644
index 0000000..5375d31
--- /dev/null
+++ b/index.html.jst
@@ -0,0 +1,57 @@
+return async env => {
+  let icon_jst = await env.site.get_min_svg('/_svg/icon_jst.svg')
+  let navbar = await _require('/navbar.jst')
+  let icon_pitree = await env.site.get_min_svg('/_svg/icon_pitree.svg')
+ 
+  await navbar(
+    env,
+    async _out => {},
+    async _out => {
+      h1 {'NDCODE projects'}
+
+      p {'We have developed these projects because we wanted to do things differently — and make our vision come to life. At times our vision is quite radical and creative, at other times we just fall back on good engineering and making it as simple as possible.'}
+
+      p {'Given that we do things differently, we have generally had to build up each idea into a complete framework that provides a comprehensive solution to a family of problems. Please do contribute to help make each framework more comprehensive over time.'}
+
+      p {'Go on, take a look inside!'}
+
+      ul.nav.nav-stacked {
+        li {
+          a {
+            table.icon-and-text {
+              tr {
+                td {
+                  _out.push(icon_pitree)
+                }
+                td {
+                  span.text-h1 {
+                    span.text-serif {'π'}
+                    'tree'
+                  }
+                  br {}
+                  span.text-h3 {'the source code analysis and transformation framework'}
+                }
+              }
+            }
+          }
+        }
+        li {
+          a {
+            table.icon-and-text {
+              tr {
+                td {
+                  _out.push(icon_jst)
+                }
+                td {
+                  span.text-h1{'JST'}
+                  br {}
+                  span.text-h3{'the web development framework with JavaScript Templates'}
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  )
+}
diff --git a/js/bootstrap.js.min b/js/bootstrap.js.min
new file mode 100644
index 0000000..8a2e99a
--- /dev/null
+++ b/js/bootstrap.js.min
@@ -0,0 +1,2377 @@
+/*!
+ * Bootstrap v3.3.7 (http://getbootstrap.com)
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under the MIT license
+ */
+
+if (typeof jQuery === 'undefined') {
+  throw new Error('Bootstrap\'s JavaScript requires jQuery')
+}
+
++function ($) {
+  'use strict';
+  var version = $.fn.jquery.split(' ')[0].split('.')
+  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) {
+    throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4')
+  }
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: transition.js v3.3.7
+ * http://getbootstrap.com/javascript/#transitions
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
+  // ============================================================
+
+  function transitionEnd() {
+    var el = document.createElement('bootstrap')
+
+    var transEndEventNames = {
+      WebkitTransition : 'webkitTransitionEnd',
+      MozTransition    : 'transitionend',
+      OTransition      : 'oTransitionEnd otransitionend',
+      transition       : 'transitionend'
+    }
+
+    for (var name in transEndEventNames) {
+      if (el.style[name] !== undefined) {
+        return { end: transEndEventNames[name] }
+      }
+    }
+
+    return false // explicit for ie8 (  ._.)
+  }
+
+  // http://blog.alexmaccaw.com/css-transitions
+  $.fn.emulateTransitionEnd = function (duration) {
+    var called = false
+    var $el = this
+    $(this).one('bsTransitionEnd', function () { called = true })
+    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
+    setTimeout(callback, duration)
+    return this
+  }
+
+  $(function () {
+    $.support.transition = transitionEnd()
+
+    if (!$.support.transition) return
+
+    $.event.special.bsTransitionEnd = {
+      bindType: $.support.transition.end,
+      delegateType: $.support.transition.end,
+      handle: function (e) {
+        if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
+      }
+    }
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: alert.js v3.3.7
+ * http://getbootstrap.com/javascript/#alerts
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // ALERT CLASS DEFINITION
+  // ======================
+
+  var dismiss = '[data-dismiss="alert"]'
+  var Alert   = function (el) {
+    $(el).on('click', dismiss, this.close)
+  }
+
+  Alert.VERSION = '3.3.7'
+
+  Alert.TRANSITION_DURATION = 150
+
+  Alert.prototype.close = function (e) {
+    var $this    = $(this)
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    var $parent = $(selector === '#' ? [] : selector)
+
+    if (e) e.preventDefault()
+
+    if (!$parent.length) {
+      $parent = $this.closest('.alert')
+    }
+
+    $parent.trigger(e = $.Event('close.bs.alert'))
+
+    if (e.isDefaultPrevented()) return
+
+    $parent.removeClass('in')
+
+    function removeElement() {
+      // detach from parent, fire event then clean up data
+      $parent.detach().trigger('closed.bs.alert').remove()
+    }
+
+    $.support.transition && $parent.hasClass('fade') ?
+      $parent
+        .one('bsTransitionEnd', removeElement)
+        .emulateTransitionEnd(Alert.TRANSITION_DURATION) :
+      removeElement()
+  }
+
+
+  // ALERT PLUGIN DEFINITION
+  // =======================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.alert')
+
+      if (!data) $this.data('bs.alert', (data = new Alert(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  var old = $.fn.alert
+
+  $.fn.alert             = Plugin
+  $.fn.alert.Constructor = Alert
+
+
+  // ALERT NO CONFLICT
+  // =================
+
+  $.fn.alert.noConflict = function () {
+    $.fn.alert = old
+    return this
+  }
+
+
+  // ALERT DATA-API
+  // ==============
+
+  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: button.js v3.3.7
+ * http://getbootstrap.com/javascript/#buttons
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // BUTTON PUBLIC CLASS DEFINITION
+  // ==============================
+
+  var Button = function (element, options) {
+    this.$element  = $(element)
+    this.options   = $.extend({}, Button.DEFAULTS, options)
+    this.isLoading = false
+  }
+
+  Button.VERSION  = '3.3.7'
+
+  Button.DEFAULTS = {
+    loadingText: 'loading...'
+  }
+
+  Button.prototype.setState = function (state) {
+    var d    = 'disabled'
+    var $el  = this.$element
+    var val  = $el.is('input') ? 'val' : 'html'
+    var data = $el.data()
+
+    state += 'Text'
+
+    if (data.resetText == null) $el.data('resetText', $el[val]())
+
+    // push to event loop to allow forms to submit
+    setTimeout($.proxy(function () {
+      $el[val](data[state] == null ? this.options[state] : data[state])
+
+      if (state == 'loadingText') {
+        this.isLoading = true
+        $el.addClass(d).attr(d, d).prop(d, true)
+      } else if (this.isLoading) {
+        this.isLoading = false
+        $el.removeClass(d).removeAttr(d).prop(d, false)
+      }
+    }, this), 0)
+  }
+
+  Button.prototype.toggle = function () {
+    var changed = true
+    var $parent = this.$element.closest('[data-toggle="buttons"]')
+
+    if ($parent.length) {
+      var $input = this.$element.find('input')
+      if ($input.prop('type') == 'radio') {
+        if ($input.prop('checked')) changed = false
+        $parent.find('.active').removeClass('active')
+        this.$element.addClass('active')
+      } else if ($input.prop('type') == 'checkbox') {
+        if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false
+        this.$element.toggleClass('active')
+      }
+      $input.prop('checked', this.$element.hasClass('active'))
+      if (changed) $input.trigger('change')
+    } else {
+      this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
+      this.$element.toggleClass('active')
+    }
+  }
+
+
+  // BUTTON PLUGIN DEFINITION
+  // ========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.button')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.button', (data = new Button(this, options)))
+
+      if (option == 'toggle') data.toggle()
+      else if (option) data.setState(option)
+    })
+  }
+
+  var old = $.fn.button
+
+  $.fn.button             = Plugin
+  $.fn.button.Constructor = Button
+
+
+  // BUTTON NO CONFLICT
+  // ==================
+
+  $.fn.button.noConflict = function () {
+    $.fn.button = old
+    return this
+  }
+
+
+  // BUTTON DATA-API
+  // ===============
+
+  $(document)
+    .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
+      var $btn = $(e.target).closest('.btn')
+      Plugin.call($btn, 'toggle')
+      if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) {
+        // Prevent double click on radios, and the double selections (so cancellation) on checkboxes
+        e.preventDefault()
+        // The target component still receive the focus
+        if ($btn.is('input,button')) $btn.trigger('focus')
+        else $btn.find('input:visible,button:visible').first().trigger('focus')
+      }
+    })
+    .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
+      $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
+    })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: carousel.js v3.3.7
+ * http://getbootstrap.com/javascript/#carousel
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // CAROUSEL CLASS DEFINITION
+  // =========================
+
+  var Carousel = function (element, options) {
+    this.$element    = $(element)
+    this.$indicators = this.$element.find('.carousel-indicators')
+    this.options     = options
+    this.paused      = null
+    this.sliding     = null
+    this.interval    = null
+    this.$active     = null
+    this.$items      = null
+
+    this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
+
+    this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
+      .on('mouseenter.bs.carousel', $.proxy(this.pause, this))
+      .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
+  }
+
+  Carousel.VERSION  = '3.3.7'
+
+  Carousel.TRANSITION_DURATION = 600
+
+  Carousel.DEFAULTS = {
+    interval: 5000,
+    pause: 'hover',
+    wrap: true,
+    keyboard: true
+  }
+
+  Carousel.prototype.keydown = function (e) {
+    if (/input|textarea/i.test(e.target.tagName)) return
+    switch (e.which) {
+      case 37: this.prev(); break
+      case 39: this.next(); break
+      default: return
+    }
+
+    e.preventDefault()
+  }
+
+  Carousel.prototype.cycle = function (e) {
+    e || (this.paused = false)
+
+    this.interval && clearInterval(this.interval)
+
+    this.options.interval
+      && !this.paused
+      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+
+    return this
+  }
+
+  Carousel.prototype.getItemIndex = function (item) {
+    this.$items = item.parent().children('.item')
+    return this.$items.index(item || this.$active)
+  }
+
+  Carousel.prototype.getItemForDirection = function (direction, active) {
+    var activeIndex = this.getItemIndex(active)
+    var willWrap = (direction == 'prev' && activeIndex === 0)
+                || (direction == 'next' && activeIndex == (this.$items.length - 1))
+    if (willWrap && !this.options.wrap) return active
+    var delta = direction == 'prev' ? -1 : 1
+    var itemIndex = (activeIndex + delta) % this.$items.length
+    return this.$items.eq(itemIndex)
+  }
+
+  Carousel.prototype.to = function (pos) {
+    var that        = this
+    var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))
+
+    if (pos > (this.$items.length - 1) || pos < 0) return
+
+    if (this.sliding)       return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
+    if (activeIndex == pos) return this.pause().cycle()
+
+    return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))
+  }
+
+  Carousel.prototype.pause = function (e) {
+    e || (this.paused = true)
+
+    if (this.$element.find('.next, .prev').length && $.support.transition) {
+      this.$element.trigger($.support.transition.end)
+      this.cycle(true)
+    }
+
+    this.interval = clearInterval(this.interval)
+
+    return this
+  }
+
+  Carousel.prototype.next = function () {
+    if (this.sliding) return
+    return this.slide('next')
+  }
+
+  Carousel.prototype.prev = function () {
+    if (this.sliding) return
+    return this.slide('prev')
+  }
+
+  Carousel.prototype.slide = function (type, next) {
+    var $active   = this.$element.find('.item.active')
+    var $next     = next || this.getItemForDirection(type, $active)
+    var isCycling = this.interval
+    var direction = type == 'next' ? 'left' : 'right'
+    var that      = this
+
+    if ($next.hasClass('active')) return (this.sliding = false)
+
+    var relatedTarget = $next[0]
+    var slideEvent = $.Event('slide.bs.carousel', {
+      relatedTarget: relatedTarget,
+      direction: direction
+    })
+    this.$element.trigger(slideEvent)
+    if (slideEvent.isDefaultPrevented()) return
+
+    this.sliding = true
+
+    isCycling && this.pause()
+
+    if (this.$indicators.length) {
+      this.$indicators.find('.active').removeClass('active')
+      var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
+      $nextIndicator && $nextIndicator.addClass('active')
+    }
+
+    var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
+    if ($.support.transition && this.$element.hasClass('slide')) {
+      $next.addClass(type)
+      $next[0].offsetWidth // force reflow
+      $active.addClass(direction)
+      $next.addClass(direction)
+      $active
+        .one('bsTransitionEnd', function () {
+          $next.removeClass([type, direction].join(' ')).addClass('active')
+          $active.removeClass(['active', direction].join(' '))
+          that.sliding = false
+          setTimeout(function () {
+            that.$element.trigger(slidEvent)
+          }, 0)
+        })
+        .emulateTransitionEnd(Carousel.TRANSITION_DURATION)
+    } else {
+      $active.removeClass('active')
+      $next.addClass('active')
+      this.sliding = false
+      this.$element.trigger(slidEvent)
+    }
+
+    isCycling && this.cycle()
+
+    return this
+  }
+
+
+  // CAROUSEL PLUGIN DEFINITION
+  // ==========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.carousel')
+      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
+      var action  = typeof option == 'string' ? option : options.slide
+
+      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
+      if (typeof option == 'number') data.to(option)
+      else if (action) data[action]()
+      else if (options.interval) data.pause().cycle()
+    })
+  }
+
+  var old = $.fn.carousel
+
+  $.fn.carousel             = Plugin
+  $.fn.carousel.Constructor = Carousel
+
+
+  // CAROUSEL NO CONFLICT
+  // ====================
+
+  $.fn.carousel.noConflict = function () {
+    $.fn.carousel = old
+    return this
+  }
+
+
+  // CAROUSEL DATA-API
+  // =================
+
+  var clickHandler = function (e) {
+    var href
+    var $this   = $(this)
+    var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
+    if (!$target.hasClass('carousel')) return
+    var options = $.extend({}, $target.data(), $this.data())
+    var slideIndex = $this.attr('data-slide-to')
+    if (slideIndex) options.interval = false
+
+    Plugin.call($target, options)
+
+    if (slideIndex) {
+      $target.data('bs.carousel').to(slideIndex)
+    }
+
+    e.preventDefault()
+  }
+
+  $(document)
+    .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)
+    .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)
+
+  $(window).on('load', function () {
+    $('[data-ride="carousel"]').each(function () {
+      var $carousel = $(this)
+      Plugin.call($carousel, $carousel.data())
+    })
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: collapse.js v3.3.7
+ * http://getbootstrap.com/javascript/#collapse
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+/* jshint latedef: false */
+
++function ($) {
+  'use strict';
+
+  // COLLAPSE PUBLIC CLASS DEFINITION
+  // ================================
+
+  var Collapse = function (element, options) {
+    this.$element      = $(element)
+    this.options       = $.extend({}, Collapse.DEFAULTS, options)
+    this.$trigger      = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
+                           '[data-toggle="collapse"][data-target="#' + element.id + '"]')
+    this.transitioning = null
+
+    if (this.options.parent) {
+      this.$parent = this.getParent()
+    } else {
+      this.addAriaAndCollapsedClass(this.$element, this.$trigger)
+    }
+
+    if (this.options.toggle) this.toggle()
+  }
+
+  Collapse.VERSION  = '3.3.7'
+
+  Collapse.TRANSITION_DURATION = 350
+
+  Collapse.DEFAULTS = {
+    toggle: true
+  }
+
+  Collapse.prototype.dimension = function () {
+    var hasWidth = this.$element.hasClass('width')
+    return hasWidth ? 'width' : 'height'
+  }
+
+  Collapse.prototype.show = function () {
+    if (this.transitioning || this.$element.hasClass('in')) return
+
+    var activesData
+    var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
+
+    if (actives && actives.length) {
+      activesData = actives.data('bs.collapse')
+      if (activesData && activesData.transitioning) return
+    }
+
+    var startEvent = $.Event('show.bs.collapse')
+    this.$element.trigger(startEvent)
+    if (startEvent.isDefaultPrevented()) return
+
+    if (actives && actives.length) {
+      Plugin.call(actives, 'hide')
+      activesData || actives.data('bs.collapse', null)
+    }
+
+    var dimension = this.dimension()
+
+    this.$element
+      .removeClass('collapse')
+      .addClass('collapsing')[dimension](0)
+      .attr('aria-expanded', true)
+
+    this.$trigger
+      .removeClass('collapsed')
+      .attr('aria-expanded', true)
+
+    this.transitioning = 1
+
+    var complete = function () {
+      this.$element
+        .removeClass('collapsing')
+        .addClass('collapse in')[dimension]('')
+      this.transitioning = 0
+      this.$element
+        .trigger('shown.bs.collapse')
+    }
+
+    if (!$.support.transition) return complete.call(this)
+
+    var scrollSize = $.camelCase(['scroll', dimension].join('-'))
+
+    this.$element
+      .one('bsTransitionEnd', $.proxy(complete, this))
+      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
+  }
+
+  Collapse.prototype.hide = function () {
+    if (this.transitioning || !this.$element.hasClass('in')) return
+
+    var startEvent = $.Event('hide.bs.collapse')
+    this.$element.trigger(startEvent)
+    if (startEvent.isDefaultPrevented()) return
+
+    var dimension = this.dimension()
+
+    this.$element[dimension](this.$element[dimension]())[0].offsetHeight
+
+    this.$element
+      .addClass('collapsing')
+      .removeClass('collapse in')
+      .attr('aria-expanded', false)
+
+    this.$trigger
+      .addClass('collapsed')
+      .attr('aria-expanded', false)
+
+    this.transitioning = 1
+
+    var complete = function () {
+      this.transitioning = 0
+      this.$element
+        .removeClass('collapsing')
+        .addClass('collapse')
+        .trigger('hidden.bs.collapse')
+    }
+
+    if (!$.support.transition) return complete.call(this)
+
+    this.$element
+      [dimension](0)
+      .one('bsTransitionEnd', $.proxy(complete, this))
+      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)
+  }
+
+  Collapse.prototype.toggle = function () {
+    this[this.$element.hasClass('in') ? 'hide' : 'show']()
+  }
+
+  Collapse.prototype.getParent = function () {
+    return $(this.options.parent)
+      .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
+      .each($.proxy(function (i, element) {
+        var $element = $(element)
+        this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
+      }, this))
+      .end()
+  }
+
+  Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
+    var isOpen = $element.hasClass('in')
+
+    $element.attr('aria-expanded', isOpen)
+    $trigger
+      .toggleClass('collapsed', !isOpen)
+      .attr('aria-expanded', isOpen)
+  }
+
+  function getTargetFromTrigger($trigger) {
+    var href
+    var target = $trigger.attr('data-target')
+      || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
+
+    return $(target)
+  }
+
+
+  // COLLAPSE PLUGIN DEFINITION
+  // ==========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.collapse')
+      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+      if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
+      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.collapse
+
+  $.fn.collapse             = Plugin
+  $.fn.collapse.Constructor = Collapse
+
+
+  // COLLAPSE NO CONFLICT
+  // ====================
+
+  $.fn.collapse.noConflict = function () {
+    $.fn.collapse = old
+    return this
+  }
+
+
+  // COLLAPSE DATA-API
+  // =================
+
+  $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
+    var $this   = $(this)
+
+    if (!$this.attr('data-target')) e.preventDefault()
+
+    var $target = getTargetFromTrigger($this)
+    var data    = $target.data('bs.collapse')
+    var option  = data ? 'toggle' : $this.data()
+
+    Plugin.call($target, option)
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: dropdown.js v3.3.7
+ * http://getbootstrap.com/javascript/#dropdowns
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // DROPDOWN CLASS DEFINITION
+  // =========================
+
+  var backdrop = '.dropdown-backdrop'
+  var toggle   = '[data-toggle="dropdown"]'
+  var Dropdown = function (element) {
+    $(element).on('click.bs.dropdown', this.toggle)
+  }
+
+  Dropdown.VERSION = '3.3.7'
+
+  function getParent($this) {
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    var $parent = selector && $(selector)
+
+    return $parent && $parent.length ? $parent : $this.parent()
+  }
+
+  function clearMenus(e) {
+    if (e && e.which === 3) return
+    $(backdrop).remove()
+    $(toggle).each(function () {
+      var $this         = $(this)
+      var $parent       = getParent($this)
+      var relatedTarget = { relatedTarget: this }
+
+      if (!$parent.hasClass('open')) return
+
+      if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
+
+      $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
+
+      if (e.isDefaultPrevented()) return
+
+      $this.attr('aria-expanded', 'false')
+      $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))
+    })
+  }
+
+  Dropdown.prototype.toggle = function (e) {
+    var $this = $(this)
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    clearMenus()
+
+    if (!isActive) {
+      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
+        // if mobile we use a backdrop because click events don't delegate
+        $(document.createElement('div'))
+          .addClass('dropdown-backdrop')
+          .insertAfter($(this))
+          .on('click', clearMenus)
+      }
+
+      var relatedTarget = { relatedTarget: this }
+      $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
+
+      if (e.isDefaultPrevented()) return
+
+      $this
+        .trigger('focus')
+        .attr('aria-expanded', 'true')
+
+      $parent
+        .toggleClass('open')
+        .trigger($.Event('shown.bs.dropdown', relatedTarget))
+    }
+
+    return false
+  }
+
+  Dropdown.prototype.keydown = function (e) {
+    if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
+
+    var $this = $(this)
+
+    e.preventDefault()
+    e.stopPropagation()
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    if (!isActive && e.which != 27 || isActive && e.which == 27) {
+      if (e.which == 27) $parent.find(toggle).trigger('focus')
+      return $this.trigger('click')
+    }
+
+    var desc = ' li:not(.disabled):visible a'
+    var $items = $parent.find('.dropdown-menu' + desc)
+
+    if (!$items.length) return
+
+    var index = $items.index(e.target)
+
+    if (e.which == 38 && index > 0)                 index--         // up
+    if (e.which == 40 && index < $items.length - 1) index++         // down
+    if (!~index)                                    index = 0
+
+    $items.eq(index).trigger('focus')
+  }
+
+
+  // DROPDOWN PLUGIN DEFINITION
+  // ==========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.dropdown')
+
+      if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  var old = $.fn.dropdown
+
+  $.fn.dropdown             = Plugin
+  $.fn.dropdown.Constructor = Dropdown
+
+
+  // DROPDOWN NO CONFLICT
+  // ====================
+
+  $.fn.dropdown.noConflict = function () {
+    $.fn.dropdown = old
+    return this
+  }
+
+
+  // APPLY TO STANDARD DROPDOWN ELEMENTS
+  // ===================================
+
+  $(document)
+    .on('click.bs.dropdown.data-api', clearMenus)
+    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+    .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
+    .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
+    .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: modal.js v3.3.7
+ * http://getbootstrap.com/javascript/#modals
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // MODAL CLASS DEFINITION
+  // ======================
+
+  var Modal = function (element, options) {
+    this.options             = options
+    this.$body               = $(document.body)
+    this.$element            = $(element)
+    this.$dialog             = this.$element.find('.modal-dialog')
+    this.$backdrop           = null
+    this.isShown             = null
+    this.originalBodyPad     = null
+    this.scrollbarWidth      = 0
+    this.ignoreBackdropClick = false
+
+    if (this.options.remote) {
+      this.$element
+        .find('.modal-content')
+        .load(this.options.remote, $.proxy(function () {
+          this.$element.trigger('loaded.bs.modal')
+        }, this))
+    }
+  }
+
+  Modal.VERSION  = '3.3.7'
+
+  Modal.TRANSITION_DURATION = 300
+  Modal.BACKDROP_TRANSITION_DURATION = 150
+
+  Modal.DEFAULTS = {
+    backdrop: true,
+    keyboard: true,
+    show: true
+  }
+
+  Modal.prototype.toggle = function (_relatedTarget) {
+    return this.isShown ? this.hide() : this.show(_relatedTarget)
+  }
+
+  Modal.prototype.show = function (_relatedTarget) {
+    var that = this
+    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
+
+    this.$element.trigger(e)
+
+    if (this.isShown || e.isDefaultPrevented()) return
+
+    this.isShown = true
+
+    this.checkScrollbar()
+    this.setScrollbar()
+    this.$body.addClass('modal-open')
+
+    this.escape()
+    this.resize()
+
+    this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
+
+    this.$dialog.on('mousedown.dismiss.bs.modal', function () {
+      that.$element.one('mouseup.dismiss.bs.modal', function (e) {
+        if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true
+      })
+    })
+
+    this.backdrop(function () {
+      var transition = $.support.transition && that.$element.hasClass('fade')
+
+      if (!that.$element.parent().length) {
+        that.$element.appendTo(that.$body) // don't move modals dom position
+      }
+
+      that.$element
+        .show()
+        .scrollTop(0)
+
+      that.adjustDialog()
+
+      if (transition) {
+        that.$element[0].offsetWidth // force reflow
+      }
+
+      that.$element.addClass('in')
+
+      that.enforceFocus()
+
+      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
+
+      transition ?
+        that.$dialog // wait for modal to slide in
+          .one('bsTransitionEnd', function () {
+            that.$element.trigger('focus').trigger(e)
+          })
+          .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
+        that.$element.trigger('focus').trigger(e)
+    })
+  }
+
+  Modal.prototype.hide = function (e) {
+    if (e) e.preventDefault()
+
+    e = $.Event('hide.bs.modal')
+
+    this.$element.trigger(e)
+
+    if (!this.isShown || e.isDefaultPrevented()) return
+
+    this.isShown = false
+
+    this.escape()
+    this.resize()
+
+    $(document).off('focusin.bs.modal')
+
+    this.$element
+      .removeClass('in')
+      .off('click.dismiss.bs.modal')
+      .off('mouseup.dismiss.bs.modal')
+
+    this.$dialog.off('mousedown.dismiss.bs.modal')
+
+    $.support.transition && this.$element.hasClass('fade') ?
+      this.$element
+        .one('bsTransitionEnd', $.proxy(this.hideModal, this))
+        .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
+      this.hideModal()
+  }
+
+  Modal.prototype.enforceFocus = function () {
+    $(document)
+      .off('focusin.bs.modal') // guard against infinite focus loop
+      .on('focusin.bs.modal', $.proxy(function (e) {
+        if (document !== e.target &&
+            this.$element[0] !== e.target &&
+            !this.$element.has(e.target).length) {
+          this.$element.trigger('focus')
+        }
+      }, this))
+  }
+
+  Modal.prototype.escape = function () {
+    if (this.isShown && this.options.keyboard) {
+      this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
+        e.which == 27 && this.hide()
+      }, this))
+    } else if (!this.isShown) {
+      this.$element.off('keydown.dismiss.bs.modal')
+    }
+  }
+
+  Modal.prototype.resize = function () {
+    if (this.isShown) {
+      $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
+    } else {
+      $(window).off('resize.bs.modal')
+    }
+  }
+
+  Modal.prototype.hideModal = function () {
+    var that = this
+    this.$element.hide()
+    this.backdrop(function () {
+      that.$body.removeClass('modal-open')
+      that.resetAdjustments()
+      that.resetScrollbar()
+      that.$element.trigger('hidden.bs.modal')
+    })
+  }
+
+  Modal.prototype.removeBackdrop = function () {
+    this.$backdrop && this.$backdrop.remove()
+    this.$backdrop = null
+  }
+
+  Modal.prototype.backdrop = function (callback) {
+    var that = this
+    var animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+    if (this.isShown && this.options.backdrop) {
+      var doAnimate = $.support.transition && animate
+
+      this.$backdrop = $(document.createElement('div'))
+        .addClass('modal-backdrop ' + animate)
+        .appendTo(this.$body)
+
+      this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
+        if (this.ignoreBackdropClick) {
+          this.ignoreBackdropClick = false
+          return
+        }
+        if (e.target !== e.currentTarget) return
+        this.options.backdrop == 'static'
+          ? this.$element[0].focus()
+          : this.hide()
+      }, this))
+
+      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+      this.$backdrop.addClass('in')
+
+      if (!callback) return
+
+      doAnimate ?
+        this.$backdrop
+          .one('bsTransitionEnd', callback)
+          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
+        callback()
+
+    } else if (!this.isShown && this.$backdrop) {
+      this.$backdrop.removeClass('in')
+
+      var callbackRemove = function () {
+        that.removeBackdrop()
+        callback && callback()
+      }
+      $.support.transition && this.$element.hasClass('fade') ?
+        this.$backdrop
+          .one('bsTransitionEnd', callbackRemove)
+          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
+        callbackRemove()
+
+    } else if (callback) {
+      callback()
+    }
+  }
+
+  // these following methods are used to handle overflowing modals
+
+  Modal.prototype.handleUpdate = function () {
+    this.adjustDialog()
+  }
+
+  Modal.prototype.adjustDialog = function () {
+    var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
+
+    this.$element.css({
+      paddingLeft:  !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
+      paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
+    })
+  }
+
+  Modal.prototype.resetAdjustments = function () {
+    this.$element.css({
+      paddingLeft: '',
+      paddingRight: ''
+    })
+  }
+
+  Modal.prototype.checkScrollbar = function () {
+    var fullWindowWidth = window.innerWidth
+    if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
+      var documentElementRect = document.documentElement.getBoundingClientRect()
+      fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
+    }
+    this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
+    this.scrollbarWidth = this.measureScrollbar()
+  }
+
+  Modal.prototype.setScrollbar = function () {
+    var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
+    this.originalBodyPad = document.body.style.paddingRight || ''
+    if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
+  }
+
+  Modal.prototype.resetScrollbar = function () {
+    this.$body.css('padding-right', this.originalBodyPad)
+  }
+
+  Modal.prototype.measureScrollbar = function () { // thx walsh
+    var scrollDiv = document.createElement('div')
+    scrollDiv.className = 'modal-scrollbar-measure'
+    this.$body.append(scrollDiv)
+    var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
+    this.$body[0].removeChild(scrollDiv)
+    return scrollbarWidth
+  }
+
+
+  // MODAL PLUGIN DEFINITION
+  // =======================
+
+  function Plugin(option, _relatedTarget) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.modal')
+      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
+      if (typeof option == 'string') data[option](_relatedTarget)
+      else if (options.show) data.show(_relatedTarget)
+    })
+  }
+
+  var old = $.fn.modal
+
+  $.fn.modal             = Plugin
+  $.fn.modal.Constructor = Modal
+
+
+  // MODAL NO CONFLICT
+  // =================
+
+  $.fn.modal.noConflict = function () {
+    $.fn.modal = old
+    return this
+  }
+
+
+  // MODAL DATA-API
+  // ==============
+
+  $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
+    var $this   = $(this)
+    var href    = $this.attr('href')
+    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
+    var option  = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+    if ($this.is('a')) e.preventDefault()
+
+    $target.one('show.bs.modal', function (showEvent) {
+      if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
+      $target.one('hidden.bs.modal', function () {
+        $this.is(':visible') && $this.trigger('focus')
+      })
+    })
+    Plugin.call($target, option, this)
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tooltip.js v3.3.7
+ * http://getbootstrap.com/javascript/#tooltip
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // TOOLTIP PUBLIC CLASS DEFINITION
+  // ===============================
+
+  var Tooltip = function (element, options) {
+    this.type       = null
+    this.options    = null
+    this.enabled    = null
+    this.timeout    = null
+    this.hoverState = null
+    this.$element   = null
+    this.inState    = null
+
+    this.init('tooltip', element, options)
+  }
+
+  Tooltip.VERSION  = '3.3.7'
+
+  Tooltip.TRANSITION_DURATION = 150
+
+  Tooltip.DEFAULTS = {
+    animation: true,
+    placement: 'top',
+    selector: false,
+    template: '',
+    trigger: 'hover focus',
+    title: '',
+    delay: 0,
+    html: false,
+    container: false,
+    viewport: {
+      selector: 'body',
+      padding: 0
+    }
+  }
+
+  Tooltip.prototype.init = function (type, element, options) {
+    this.enabled   = true
+    this.type      = type
+    this.$element  = $(element)
+    this.options   = this.getOptions(options)
+    this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
+    this.inState   = { click: false, hover: false, focus: false }
+
+    if (this.$element[0] instanceof document.constructor && !this.options.selector) {
+      throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
+    }
+
+    var triggers = this.options.trigger.split(' ')
+
+    for (var i = triggers.length; i--;) {
+      var trigger = triggers[i]
+
+      if (trigger == 'click') {
+        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+      } else if (trigger != 'manual') {
+        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'
+        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
+
+        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+      }
+    }
+
+    this.options.selector ?
+      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+      this.fixTitle()
+  }
+
+  Tooltip.prototype.getDefaults = function () {
+    return Tooltip.DEFAULTS
+  }
+
+  Tooltip.prototype.getOptions = function (options) {
+    options = $.extend({}, this.getDefaults(), this.$element.data(), options)
+
+    if (options.delay && typeof options.delay == 'number') {
+      options.delay = {
+        show: options.delay,
+        hide: options.delay
+      }
+    }
+
+    return options
+  }
+
+  Tooltip.prototype.getDelegateOptions = function () {
+    var options  = {}
+    var defaults = this.getDefaults()
+
+    this._options && $.each(this._options, function (key, value) {
+      if (defaults[key] != value) options[key] = value
+    })
+
+    return options
+  }
+
+  Tooltip.prototype.enter = function (obj) {
+    var self = obj instanceof this.constructor ?
+      obj : $(obj.currentTarget).data('bs.' + this.type)
+
+    if (!self) {
+      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
+      $(obj.currentTarget).data('bs.' + this.type, self)
+    }
+
+    if (obj instanceof $.Event) {
+      self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
+    }
+
+    if (self.tip().hasClass('in') || self.hoverState == 'in') {
+      self.hoverState = 'in'
+      return
+    }
+
+    clearTimeout(self.timeout)
+
+    self.hoverState = 'in'
+
+    if (!self.options.delay || !self.options.delay.show) return self.show()
+
+    self.timeout = setTimeout(function () {
+      if (self.hoverState == 'in') self.show()
+    }, self.options.delay.show)
+  }
+
+  Tooltip.prototype.isInStateTrue = function () {
+    for (var key in this.inState) {
+      if (this.inState[key]) return true
+    }
+
+    return false
+  }
+
+  Tooltip.prototype.leave = function (obj) {
+    var self = obj instanceof this.constructor ?
+      obj : $(obj.currentTarget).data('bs.' + this.type)
+
+    if (!self) {
+      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
+      $(obj.currentTarget).data('bs.' + this.type, self)
+    }
+
+    if (obj instanceof $.Event) {
+      self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
+    }
+
+    if (self.isInStateTrue()) return
+
+    clearTimeout(self.timeout)
+
+    self.hoverState = 'out'
+
+    if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+    self.timeout = setTimeout(function () {
+      if (self.hoverState == 'out') self.hide()
+    }, self.options.delay.hide)
+  }
+
+  Tooltip.prototype.show = function () {
+    var e = $.Event('show.bs.' + this.type)
+
+    if (this.hasContent() && this.enabled) {
+      this.$element.trigger(e)
+
+      var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
+      if (e.isDefaultPrevented() || !inDom) return
+      var that = this
+
+      var $tip = this.tip()
+
+      var tipId = this.getUID(this.type)
+
+      this.setContent()
+      $tip.attr('id', tipId)
+      this.$element.attr('aria-describedby', tipId)
+
+      if (this.options.animation) $tip.addClass('fade')
+
+      var placement = typeof this.options.placement == 'function' ?
+        this.options.placement.call(this, $tip[0], this.$element[0]) :
+        this.options.placement
+
+      var autoToken = /\s?auto?\s?/i
+      var autoPlace = autoToken.test(placement)
+      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
+
+      $tip
+        .detach()
+        .css({ top: 0, left: 0, display: 'block' })
+        .addClass(placement)
+        .data('bs.' + this.type, this)
+
+      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+      this.$element.trigger('inserted.bs.' + this.type)
+
+      var pos          = this.getPosition()
+      var actualWidth  = $tip[0].offsetWidth
+      var actualHeight = $tip[0].offsetHeight
+
+      if (autoPlace) {
+        var orgPlacement = placement
+        var viewportDim = this.getPosition(this.$viewport)
+
+        placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top'    :
+                    placement == 'top'    && pos.top    - actualHeight < viewportDim.top    ? 'bottom' :
+                    placement == 'right'  && pos.right  + actualWidth  > viewportDim.width  ? 'left'   :
+                    placement == 'left'   && pos.left   - actualWidth  < viewportDim.left   ? 'right'  :
+                    placement
+
+        $tip
+          .removeClass(orgPlacement)
+          .addClass(placement)
+      }
+
+      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
+
+      this.applyPlacement(calculatedOffset, placement)
+
+      var complete = function () {
+        var prevHoverState = that.hoverState
+        that.$element.trigger('shown.bs.' + that.type)
+        that.hoverState = null
+
+        if (prevHoverState == 'out') that.leave(that)
+      }
+
+      $.support.transition && this.$tip.hasClass('fade') ?
+        $tip
+          .one('bsTransitionEnd', complete)
+          .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
+        complete()
+    }
+  }
+
+  Tooltip.prototype.applyPlacement = function (offset, placement) {
+    var $tip   = this.tip()
+    var width  = $tip[0].offsetWidth
+    var height = $tip[0].offsetHeight
+
+    // manually read margins because getBoundingClientRect includes difference
+    var marginTop = parseInt($tip.css('margin-top'), 10)
+    var marginLeft = parseInt($tip.css('margin-left'), 10)
+
+    // we must check for NaN for ie 8/9
+    if (isNaN(marginTop))  marginTop  = 0
+    if (isNaN(marginLeft)) marginLeft = 0
+
+    offset.top  += marginTop
+    offset.left += marginLeft
+
+    // $.fn.offset doesn't round pixel values
+    // so we use setOffset directly with our own function B-0
+    $.offset.setOffset($tip[0], $.extend({
+      using: function (props) {
+        $tip.css({
+          top: Math.round(props.top),
+          left: Math.round(props.left)
+        })
+      }
+    }, offset), 0)
+
+    $tip.addClass('in')
+
+    // check to see if placing tip in new offset caused the tip to resize itself
+    var actualWidth  = $tip[0].offsetWidth
+    var actualHeight = $tip[0].offsetHeight
+
+    if (placement == 'top' && actualHeight != height) {
+      offset.top = offset.top + height - actualHeight
+    }
+
+    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
+
+    if (delta.left) offset.left += delta.left
+    else offset.top += delta.top
+
+    var isVertical          = /top|bottom/.test(placement)
+    var arrowDelta          = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
+    var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
+
+    $tip.offset(offset)
+    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
+  }
+
+  Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
+    this.arrow()
+      .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
+      .css(isVertical ? 'top' : 'left', '')
+  }
+
+  Tooltip.prototype.setContent = function () {
+    var $tip  = this.tip()
+    var title = this.getTitle()
+
+    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+    $tip.removeClass('fade in top bottom left right')
+  }
+
+  Tooltip.prototype.hide = function (callback) {
+    var that = this
+    var $tip = $(this.$tip)
+    var e    = $.Event('hide.bs.' + this.type)
+
+    function complete() {
+      if (that.hoverState != 'in') $tip.detach()
+      if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary.
+        that.$element
+          .removeAttr('aria-describedby')
+          .trigger('hidden.bs.' + that.type)
+      }
+      callback && callback()
+    }
+
+    this.$element.trigger(e)
+
+    if (e.isDefaultPrevented()) return
+
+    $tip.removeClass('in')
+
+    $.support.transition && $tip.hasClass('fade') ?
+      $tip
+        .one('bsTransitionEnd', complete)
+        .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
+      complete()
+
+    this.hoverState = null
+
+    return this
+  }
+
+  Tooltip.prototype.fixTitle = function () {
+    var $e = this.$element
+    if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
+      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+    }
+  }
+
+  Tooltip.prototype.hasContent = function () {
+    return this.getTitle()
+  }
+
+  Tooltip.prototype.getPosition = function ($element) {
+    $element   = $element || this.$element
+
+    var el     = $element[0]
+    var isBody = el.tagName == 'BODY'
+
+    var elRect    = el.getBoundingClientRect()
+    if (elRect.width == null) {
+      // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
+      elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
+    }
+    var isSvg = window.SVGElement && el instanceof window.SVGElement
+    // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3.
+    // See https://github.com/twbs/bootstrap/issues/20280
+    var elOffset  = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset())
+    var scroll    = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
+    var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
+
+    return $.extend({}, elRect, scroll, outerDims, elOffset)
+  }
+
+  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
+    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2 } :
+           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
+           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
+        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
+
+  }
+
+  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
+    var delta = { top: 0, left: 0 }
+    if (!this.$viewport) return delta
+
+    var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
+    var viewportDimensions = this.getPosition(this.$viewport)
+
+    if (/right|left/.test(placement)) {
+      var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll
+      var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
+      if (topEdgeOffset < viewportDimensions.top) { // top overflow
+        delta.top = viewportDimensions.top - topEdgeOffset
+      } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
+        delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
+      }
+    } else {
+      var leftEdgeOffset  = pos.left - viewportPadding
+      var rightEdgeOffset = pos.left + viewportPadding + actualWidth
+      if (leftEdgeOffset < viewportDimensions.left) { // left overflow
+        delta.left = viewportDimensions.left - leftEdgeOffset
+      } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
+        delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
+      }
+    }
+
+    return delta
+  }
+
+  Tooltip.prototype.getTitle = function () {
+    var title
+    var $e = this.$element
+    var o  = this.options
+
+    title = $e.attr('data-original-title')
+      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
+
+    return title
+  }
+
+  Tooltip.prototype.getUID = function (prefix) {
+    do prefix += ~~(Math.random() * 1000000)
+    while (document.getElementById(prefix))
+    return prefix
+  }
+
+  Tooltip.prototype.tip = function () {
+    if (!this.$tip) {
+      this.$tip = $(this.options.template)
+      if (this.$tip.length != 1) {
+        throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
+      }
+    }
+    return this.$tip
+  }
+
+  Tooltip.prototype.arrow = function () {
+    return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
+  }
+
+  Tooltip.prototype.enable = function () {
+    this.enabled = true
+  }
+
+  Tooltip.prototype.disable = function () {
+    this.enabled = false
+  }
+
+  Tooltip.prototype.toggleEnabled = function () {
+    this.enabled = !this.enabled
+  }
+
+  Tooltip.prototype.toggle = function (e) {
+    var self = this
+    if (e) {
+      self = $(e.currentTarget).data('bs.' + this.type)
+      if (!self) {
+        self = new this.constructor(e.currentTarget, this.getDelegateOptions())
+        $(e.currentTarget).data('bs.' + this.type, self)
+      }
+    }
+
+    if (e) {
+      self.inState.click = !self.inState.click
+      if (self.isInStateTrue()) self.enter(self)
+      else self.leave(self)
+    } else {
+      self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
+    }
+  }
+
+  Tooltip.prototype.destroy = function () {
+    var that = this
+    clearTimeout(this.timeout)
+    this.hide(function () {
+      that.$element.off('.' + that.type).removeData('bs.' + that.type)
+      if (that.$tip) {
+        that.$tip.detach()
+      }
+      that.$tip = null
+      that.$arrow = null
+      that.$viewport = null
+      that.$element = null
+    })
+  }
+
+
+  // TOOLTIP PLUGIN DEFINITION
+  // =========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.tooltip')
+      var options = typeof option == 'object' && option
+
+      if (!data && /destroy|hide/.test(option)) return
+      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.tooltip
+
+  $.fn.tooltip             = Plugin
+  $.fn.tooltip.Constructor = Tooltip
+
+
+  // TOOLTIP NO CONFLICT
+  // ===================
+
+  $.fn.tooltip.noConflict = function () {
+    $.fn.tooltip = old
+    return this
+  }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: popover.js v3.3.7
+ * http://getbootstrap.com/javascript/#popovers
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // POPOVER PUBLIC CLASS DEFINITION
+  // ===============================
+
+  var Popover = function (element, options) {
+    this.init('popover', element, options)
+  }
+
+  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
+
+  Popover.VERSION  = '3.3.7'
+
+  Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
+    placement: 'right',
+    trigger: 'click',
+    content: '',
+    template: ''
+  })
+
+
+  // NOTE: POPOVER EXTENDS tooltip.js
+  // ================================
+
+  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
+
+  Popover.prototype.constructor = Popover
+
+  Popover.prototype.getDefaults = function () {
+    return Popover.DEFAULTS
+  }
+
+  Popover.prototype.setContent = function () {
+    var $tip    = this.tip()
+    var title   = this.getTitle()
+    var content = this.getContent()
+
+    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+    $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
+      this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
+    ](content)
+
+    $tip.removeClass('fade top bottom left right in')
+
+    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
+    // this manually by checking the contents.
+    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
+  }
+
+  Popover.prototype.hasContent = function () {
+    return this.getTitle() || this.getContent()
+  }
+
+  Popover.prototype.getContent = function () {
+    var $e = this.$element
+    var o  = this.options
+
+    return $e.attr('data-content')
+      || (typeof o.content == 'function' ?
+            o.content.call($e[0]) :
+            o.content)
+  }
+
+  Popover.prototype.arrow = function () {
+    return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
+  }
+
+
+  // POPOVER PLUGIN DEFINITION
+  // =========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.popover')
+      var options = typeof option == 'object' && option
+
+      if (!data && /destroy|hide/.test(option)) return
+      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.popover
+
+  $.fn.popover             = Plugin
+  $.fn.popover.Constructor = Popover
+
+
+  // POPOVER NO CONFLICT
+  // ===================
+
+  $.fn.popover.noConflict = function () {
+    $.fn.popover = old
+    return this
+  }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: scrollspy.js v3.3.7
+ * http://getbootstrap.com/javascript/#scrollspy
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // SCROLLSPY CLASS DEFINITION
+  // ==========================
+
+  function ScrollSpy(element, options) {
+    this.$body          = $(document.body)
+    this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)
+    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)
+    this.selector       = (this.options.target || '') + ' .nav li > a'
+    this.offsets        = []
+    this.targets        = []
+    this.activeTarget   = null
+    this.scrollHeight   = 0
+
+    this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))
+    this.refresh()
+    this.process()
+  }
+
+  ScrollSpy.VERSION  = '3.3.7'
+
+  ScrollSpy.DEFAULTS = {
+    offset: 10
+  }
+
+  ScrollSpy.prototype.getScrollHeight = function () {
+    return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
+  }
+
+  ScrollSpy.prototype.refresh = function () {
+    var that          = this
+    var offsetMethod  = 'offset'
+    var offsetBase    = 0
+
+    this.offsets      = []
+    this.targets      = []
+    this.scrollHeight = this.getScrollHeight()
+
+    if (!$.isWindow(this.$scrollElement[0])) {
+      offsetMethod = 'position'
+      offsetBase   = this.$scrollElement.scrollTop()
+    }
+
+    this.$body
+      .find(this.selector)
+      .map(function () {
+        var $el   = $(this)
+        var href  = $el.data('target') || $el.attr('href')
+        var $href = /^#./.test(href) && $(href)
+
+        return ($href
+          && $href.length
+          && $href.is(':visible')
+          && [[$href[offsetMethod]().top + offsetBase, href]]) || null
+      })
+      .sort(function (a, b) { return a[0] - b[0] })
+      .each(function () {
+        that.offsets.push(this[0])
+        that.targets.push(this[1])
+      })
+  }
+
+  ScrollSpy.prototype.process = function () {
+    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset
+    var scrollHeight = this.getScrollHeight()
+    var maxScroll    = this.options.offset + scrollHeight - this.$scrollElement.height()
+    var offsets      = this.offsets
+    var targets      = this.targets
+    var activeTarget = this.activeTarget
+    var i
+
+    if (this.scrollHeight != scrollHeight) {
+      this.refresh()
+    }
+
+    if (scrollTop >= maxScroll) {
+      return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
+    }
+
+    if (activeTarget && scrollTop < offsets[0]) {
+      this.activeTarget = null
+      return this.clear()
+    }
+
+    for (i = offsets.length; i--;) {
+      activeTarget != targets[i]
+        && scrollTop >= offsets[i]
+        && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1])
+        && this.activate(targets[i])
+    }
+  }
+
+  ScrollSpy.prototype.activate = function (target) {
+    this.activeTarget = target
+
+    this.clear()
+
+    var selector = this.selector +
+      '[data-target="' + target + '"],' +
+      this.selector + '[href="' + target + '"]'
+
+    var active = $(selector)
+      .parents('li')
+      .addClass('active')
+
+    if (active.parent('.dropdown-menu').length) {
+      active = active
+        .closest('li.dropdown')
+        .addClass('active')
+    }
+
+    active.trigger('activate.bs.scrollspy')
+  }
+
+  ScrollSpy.prototype.clear = function () {
+    $(this.selector)
+      .parentsUntil(this.options.target, '.active')
+      .removeClass('active')
+  }
+
+
+  // SCROLLSPY PLUGIN DEFINITION
+  // ===========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.scrollspy')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.scrollspy
+
+  $.fn.scrollspy             = Plugin
+  $.fn.scrollspy.Constructor = ScrollSpy
+
+
+  // SCROLLSPY NO CONFLICT
+  // =====================
+
+  $.fn.scrollspy.noConflict = function () {
+    $.fn.scrollspy = old
+    return this
+  }
+
+
+  // SCROLLSPY DATA-API
+  // ==================
+
+  $(window).on('load.bs.scrollspy.data-api', function () {
+    $('[data-spy="scroll"]').each(function () {
+      var $spy = $(this)
+      Plugin.call($spy, $spy.data())
+    })
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tab.js v3.3.7
+ * http://getbootstrap.com/javascript/#tabs
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // TAB CLASS DEFINITION
+  // ====================
+
+  var Tab = function (element) {
+    // jscs:disable requireDollarBeforejQueryAssignment
+    this.element = $(element)
+    // jscs:enable requireDollarBeforejQueryAssignment
+  }
+
+  Tab.VERSION = '3.3.7'
+
+  Tab.TRANSITION_DURATION = 150
+
+  Tab.prototype.show = function () {
+    var $this    = this.element
+    var $ul      = $this.closest('ul:not(.dropdown-menu)')
+    var selector = $this.data('target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    if ($this.parent('li').hasClass('active')) return
+
+    var $previous = $ul.find('.active:last a')
+    var hideEvent = $.Event('hide.bs.tab', {
+      relatedTarget: $this[0]
+    })
+    var showEvent = $.Event('show.bs.tab', {
+      relatedTarget: $previous[0]
+    })
+
+    $previous.trigger(hideEvent)
+    $this.trigger(showEvent)
+
+    if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
+
+    var $target = $(selector)
+
+    this.activate($this.closest('li'), $ul)
+    this.activate($target, $target.parent(), function () {
+      $previous.trigger({
+        type: 'hidden.bs.tab',
+        relatedTarget: $this[0]
+      })
+      $this.trigger({
+        type: 'shown.bs.tab',
+        relatedTarget: $previous[0]
+      })
+    })
+  }
+
+  Tab.prototype.activate = function (element, container, callback) {
+    var $active    = container.find('> .active')
+    var transition = callback
+      && $.support.transition
+      && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)
+
+    function next() {
+      $active
+        .removeClass('active')
+        .find('> .dropdown-menu > .active')
+          .removeClass('active')
+        .end()
+        .find('[data-toggle="tab"]')
+          .attr('aria-expanded', false)
+
+      element
+        .addClass('active')
+        .find('[data-toggle="tab"]')
+          .attr('aria-expanded', true)
+
+      if (transition) {
+        element[0].offsetWidth // reflow for transition
+        element.addClass('in')
+      } else {
+        element.removeClass('fade')
+      }
+
+      if (element.parent('.dropdown-menu').length) {
+        element
+          .closest('li.dropdown')
+            .addClass('active')
+          .end()
+          .find('[data-toggle="tab"]')
+            .attr('aria-expanded', true)
+      }
+
+      callback && callback()
+    }
+
+    $active.length && transition ?
+      $active
+        .one('bsTransitionEnd', next)
+        .emulateTransitionEnd(Tab.TRANSITION_DURATION) :
+      next()
+
+    $active.removeClass('in')
+  }
+
+
+  // TAB PLUGIN DEFINITION
+  // =====================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.tab')
+
+      if (!data) $this.data('bs.tab', (data = new Tab(this)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.tab
+
+  $.fn.tab             = Plugin
+  $.fn.tab.Constructor = Tab
+
+
+  // TAB NO CONFLICT
+  // ===============
+
+  $.fn.tab.noConflict = function () {
+    $.fn.tab = old
+    return this
+  }
+
+
+  // TAB DATA-API
+  // ============
+
+  var clickHandler = function (e) {
+    e.preventDefault()
+    Plugin.call($(this), 'show')
+  }
+
+  $(document)
+    .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
+    .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: affix.js v3.3.7
+ * http://getbootstrap.com/javascript/#affix
+ * ========================================================================
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // AFFIX CLASS DEFINITION
+  // ======================
+
+  var Affix = function (element, options) {
+    this.options = $.extend({}, Affix.DEFAULTS, options)
+
+    this.$target = $(this.options.target)
+      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
+      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))
+
+    this.$element     = $(element)
+    this.affixed      = null
+    this.unpin        = null
+    this.pinnedOffset = null
+
+    this.checkPosition()
+  }
+
+  Affix.VERSION  = '3.3.7'
+
+  Affix.RESET    = 'affix affix-top affix-bottom'
+
+  Affix.DEFAULTS = {
+    offset: 0,
+    target: window
+  }
+
+  Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
+    var scrollTop    = this.$target.scrollTop()
+    var position     = this.$element.offset()
+    var targetHeight = this.$target.height()
+
+    if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
+
+    if (this.affixed == 'bottom') {
+      if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
+      return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
+    }
+
+    var initializing   = this.affixed == null
+    var colliderTop    = initializing ? scrollTop : position.top
+    var colliderHeight = initializing ? targetHeight : height
+
+    if (offsetTop != null && scrollTop <= offsetTop) return 'top'
+    if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
+
+    return false
+  }
+
+  Affix.prototype.getPinnedOffset = function () {
+    if (this.pinnedOffset) return this.pinnedOffset
+    this.$element.removeClass(Affix.RESET).addClass('affix')
+    var scrollTop = this.$target.scrollTop()
+    var position  = this.$element.offset()
+    return (this.pinnedOffset = position.top - scrollTop)
+  }
+
+  Affix.prototype.checkPositionWithEventLoop = function () {
+    setTimeout($.proxy(this.checkPosition, this), 1)
+  }
+
+  Affix.prototype.checkPosition = function () {
+    if (!this.$element.is(':visible')) return
+
+    var height       = this.$element.height()
+    var offset       = this.options.offset
+    var offsetTop    = offset.top
+    var offsetBottom = offset.bottom
+    var scrollHeight = Math.max($(document).height(), $(document.body).height())
+
+    if (typeof offset != 'object')         offsetBottom = offsetTop = offset
+    if (typeof offsetTop == 'function')    offsetTop    = offset.top(this.$element)
+    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
+
+    var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
+
+    if (this.affixed != affix) {
+      if (this.unpin != null) this.$element.css('top', '')
+
+      var affixType = 'affix' + (affix ? '-' + affix : '')
+      var e         = $.Event(affixType + '.bs.affix')
+
+      this.$element.trigger(e)
+
+      if (e.isDefaultPrevented()) return
+
+      this.affixed = affix
+      this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
+
+      this.$element
+        .removeClass(Affix.RESET)
+        .addClass(affixType)
+        .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
+    }
+
+    if (affix == 'bottom') {
+      this.$element.offset({
+        top: scrollHeight - height - offsetBottom
+      })
+    }
+  }
+
+
+  // AFFIX PLUGIN DEFINITION
+  // =======================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.affix')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.affix
+
+  $.fn.affix             = Plugin
+  $.fn.affix.Constructor = Affix
+
+
+  // AFFIX NO CONFLICT
+  // =================
+
+  $.fn.affix.noConflict = function () {
+    $.fn.affix = old
+    return this
+  }
+
+
+  // AFFIX DATA-API
+  // ==============
+
+  $(window).on('load', function () {
+    $('[data-spy="affix"]').each(function () {
+      var $spy = $(this)
+      var data = $spy.data()
+
+      data.offset = data.offset || {}
+
+      if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
+      if (data.offsetTop    != null) data.offset.top    = data.offsetTop
+
+      Plugin.call($spy, data)
+    })
+  })
+
+}(jQuery);
diff --git a/js/ie-emulation-modes-warning.js.min b/js/ie-emulation-modes-warning.js.min
new file mode 100644
index 0000000..3f97ba5
--- /dev/null
+++ b/js/ie-emulation-modes-warning.js.min
@@ -0,0 +1,51 @@
+// NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT
+// IT'S JUST JUNK FOR OUR DOCS!
+// ++++++++++++++++++++++++++++++++++++++++++
+/*!
+ * Copyright 2014-2015 Twitter, Inc.
+ *
+ * Licensed under the Creative Commons Attribution 3.0 Unported License. For
+ * details, see https://creativecommons.org/licenses/by/3.0/.
+ */
+// Intended to prevent false-positive bug reports about Bootstrap not working properly in old versions of IE due to folks testing using IE's unreliable emulation modes.
+(function () {
+  'use strict';
+
+  function emulatedIEMajorVersion() {
+    var groups = /MSIE ([0-9.]+)/.exec(window.navigator.userAgent)
+    if (groups === null) {
+      return null
+    }
+    var ieVersionNum = parseInt(groups[1], 10)
+    var ieMajorVersion = Math.floor(ieVersionNum)
+    return ieMajorVersion
+  }
+
+  function actualNonEmulatedIEMajorVersion() {
+    // Detects the actual version of IE in use, even if it's in an older-IE emulation mode.
+    // IE JavaScript conditional compilation docs: https://msdn.microsoft.com/library/121hztk3%28v=vs.94%29.aspx
+    // @cc_on docs: https://msdn.microsoft.com/library/8ka90k2e%28v=vs.94%29.aspx
+    var jscriptVersion = new Function('/*@cc_on return @_jscript_version; @*/')() // jshint ignore:line
+    if (jscriptVersion === undefined) {
+      return 11 // IE11+ not in emulation mode
+    }
+    if (jscriptVersion < 9) {
+      return 8 // IE8 (or lower; haven't tested on IE<8)
+    }
+    return jscriptVersion // IE9 or IE10 in any mode, or IE11 in non-IE11 mode
+  }
+
+  var ua = window.navigator.userAgent
+  if (ua.indexOf('Opera') > -1 || ua.indexOf('Presto') > -1) {
+    return // Opera, which might pretend to be IE
+  }
+  var emulated = emulatedIEMajorVersion()
+  if (emulated === null) {
+    return // Not IE
+  }
+  var nonEmulated = actualNonEmulatedIEMajorVersion()
+
+  if (emulated !== nonEmulated) {
+    window.alert('WARNING: You appear to be using IE' + nonEmulated + ' in IE' + emulated + ' emulation mode.\nIE emulation modes can behave significantly differently from ACTUAL older versions of IE.\nPLEASE DON\'T FILE BOOTSTRAP BUGS based on testing in IE emulation modes!')
+  }
+})();
diff --git a/js/ie10-viewport-bug-workaround.js.min b/js/ie10-viewport-bug-workaround.js.min
new file mode 100644
index 0000000..479a6eb
--- /dev/null
+++ b/js/ie10-viewport-bug-workaround.js.min
@@ -0,0 +1,23 @@
+/*!
+ * IE10 viewport hack for Surface/desktop Windows 8 bug
+ * Copyright 2014-2015 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+// See the Getting Started docs for more information:
+// http://getbootstrap.com/getting-started/#support-ie10-width
+
+(function () {
+  'use strict';
+
+  if (navigator.userAgent.match(/IEMobile\/10\.0/)) {
+    var msViewportStyle = document.createElement('style')
+    msViewportStyle.appendChild(
+      document.createTextNode(
+        '@-ms-viewport{width:auto!important}'
+      )
+    )
+    document.querySelector('head').appendChild(msViewportStyle)
+  }
+
+})();
diff --git a/navbar.jst b/navbar.jst
new file mode 100644
index 0000000..1a1b907
--- /dev/null
+++ b/navbar.jst
@@ -0,0 +1,200 @@
+let XDate = require('xdate')
+
+return async (env, head, body) => {
+  let feedback_form = await _require('/feedback_form.jst')
+  let globals = await env.site.get_json('/_config/globals.json')
+  let icon_search = await env.site.get_min_svg('/_svg/icon_search.svg')
+  let logo_large = await env.site.get_min_svg('/_svg/logo_large.svg')
+  let page = await _require('/page.jst')
+
+  await page(
+    env,
+    head,
+    // body
+    async _out => {
+      div(style="padding-left: calc(100vw - 100%);") {
+        div.container(style="margin-top: 15px; margin-bottom: 15px;") {
+          //div.row {
+          //  div.col-sm-12(style="text-align: right;") {
+          //    a(href="/login.html") {'Login'}
+          //  }
+          //}
+          div.row {
+            div.'col-sm-8'.vbottom {
+              _out.push(logo_large)
+            }
+            div.'col-sm-4'.vbottom(style="padding-bottom: 15px;") {
+              form(action="/search.html") {
+                div.input-group {
+                  input.form-control(name="query" type="text" placeholder="Search") {}
+                  span.input-group-btn {
+                    button.btn.btn-default(type="submit") {
+                      _out.push(icon_search)
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+      nav.navbar.navbar-default(style="margin-top: 0px; margin-bottom: 0px;") {
+        div(style="padding-left: calc(100vw - 100%);") {
+          div.container { //-fluid") {
+            //  Brand and toggle get grouped for better mobile display 
+            div.navbar-header {
+              button.navbar-toggle.collapsed(type="button" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false") {
+                span.sr-only {'Toggle navigation'}
+                span.icon-bar {}
+                span.icon-bar {}
+                span.icon-bar {}
+              }
+              //a.navbar-brand(href="#") {'Brand'}
+            }
+
+            //  Collect the nav links, forms, and other content for toggling 
+            div.collapse.navbar-collapse#bs-example-navbar-collapse-1 {
+              ul.nav.navbar-nav {
+                let navigation = globals.navigation
+                for (let i = 0; i < navigation.length; ++i) {
+                  let page = navigation[i]
+                  let title = globals.page_to_title[page] || page
+                  if (page === env.parsed_url.pathname)
+                    li.active {
+                      a(href=page) {
+                        `${title}`
+                        span.sr-only {'(current)'}
+                      }
+                    }
+                  else
+                    li {
+                      a(href=page) {`${title}`}
+                    }
+                }
+              }
+              ul.nav.navbar-nav.navbar-right {
+                li {
+                  a#give-feedback {'Give feedback'}
+                }
+              } 
+            }
+          }
+        }
+      }
+      div(style="padding-left: calc(100vw - 100%);") {
+        div.container {
+          await body(_out)
+        }
+      }
+      br {}
+      div.footer(style="padding-left: calc(100vw - 100%);") {
+        div.container { //-fluid") {
+          div(style="height: 16px;") {}
+          a(rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/") {
+            img(alt="Creative Commons License" style="border-width:0" src="images/by-sa_3.0_88x31.png") {}
+          }
+          p {
+            'This work is licensed under a '
+            a(rel="license" href="http://creativecommons.org/licenses/by-sa/3.0/") {
+              'Creative Commons Attribution-ShareAlike 3.0 Unported License'
+            }
+            '.'
+          }
+
+          p {'Code samples within the page are placed in the public domain unless otherwise noted.'}
+
+          p {`Copyright © ${new XDate().getUTCFullYear()} Integration Logic Pty Ltd trading as NDCODE and contributors.`}
+        }
+      }
+
+      // hidden part
+      div#feedback-modal.modal.fade(role="dialog") {
+        div.modal-dialog {
+          div.modal-content {
+            div.modal-header {
+              button.close(type="button" data-dismiss="modal") {
+                '×'
+              }
+              h4.modal-title {
+                'Give feedback'
+              }
+            }
+            div.modal-body {
+              p {
+                'Did you notice something not quite right, or just want to share your impression of this page?'
+              }
+              await feedback_form(env, _out)
+            }
+            div.modal-footer {
+              button.btn.btn-primary(type="submit" form="feedback-form") {
+                'Submit'
+              }
+              button.btn.btn-default(type="button" data-dismiss="modal") {
+                'Close'
+              }
+            }
+          }
+        }
+      }
+
+      div#message-modal.modal.fade(role="dialog") {
+        div.modal-dialog {
+          div.modal-content {
+            div.modal-header {
+              button.close(type="button" data-dismiss="modal") {
+                '×'
+              }
+              h4.modal-title {
+                'Message'
+              }
+            }
+            div.modal-body {
+              p#message-modal-message {}
+            }
+            div.modal-footer {
+              button.btn.btn-default(type="button" data-dismiss="modal") {
+                'Close'
+              }
+            }
+          }
+        }
+      }
+    },
+    // scripts
+    async _out => {
+      // when feedback form is submitted, do not reload the page
+      script {
+        $(document).ready(function() {
+          $('#give-feedback').click(function() {
+            $('#feedback-modal').modal('show')
+            $('#feedback-form-message').text('')
+          })
+          $('#feedback-modal').on('shown.bs.modal', function() {
+            $('#feedback-form-message').focus()
+          })
+          $(document).on('submit', '#feedback-form', function(e) {
+            e.preventDefault()
+            $.ajax({
+              url: '/feedback.html',
+              type: 'POST',
+              data: {
+                page: window.location.href,
+                message: $('#feedback-form-message').val()
+              },
+              success: function(data, textStatus, jqXHR) {
+                $('#feedback-modal').modal('hide')
+                $('#message-modal-message').text(data)
+                $('#message-modal').modal('show')
+              },
+              error: function(jqXHR, textStatus, errorThrown) {
+                $('#feedback-modal').modal('hide')
+                $('#message-modal-message').text(errorThrown)
+                $('#message-modal').modal('show')
+              }               
+            })
+          })
+        })
+      }
+    }
+  )
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..3b52d86
--- /dev/null
+++ b/package.json
@@ -0,0 +1,18 @@
+{
+  "name": "@ndcode/ndcode_site",
+  "version": "0.0.1",
+  "description": "Example website using JavaScript Template system",
+  "directories": {},
+  "dependencies": {
+    "@ndcode/emailjs_cache": "^0.1.0",
+    "@ndcode/zettair_cache": "^0.1.0",
+    "cookie": "^0.3.1",
+    "querystring": "^0.2.0",
+    "stream-buffers": "^3.0.2",
+    "xdate": "^0.8.2"
+  },
+  "devDependencies": {},
+  "scripts": {},
+  "author": "Nick Downing",
+  "license": "CC-BY-SA-3.0"
+}
diff --git a/page.jst b/page.jst
new file mode 100644
index 0000000..528bf26
--- /dev/null
+++ b/page.jst
@@ -0,0 +1,116 @@
+let XDate = require('xdate')
+let cookie = require('cookie')
+let crypto = require('crypto')
+
+return async (env, head, body, scripts) => {
+  let favicons = await env.site.get_min_html('/_favicon/favicons.html')
+  let globals = await env.site.get_json('/_config/globals.json')
+
+  await env.site.ensure_dir('/_analytics')
+  let sessions = await env.site.read_json('/_analytics/sessions.json', {})
+  let pageviews = await env.site.read_json('/_analytics/pageviews.json', {})
+
+  let cookies = cookie.parse(env.request.headers.cookie || '')
+  let session_key
+  if (
+    !Object.prototype.hasOwnProperty.call(cookies, 'session_key') ||
+    !Object.prototype.hasOwnProperty.call(
+      sessions,
+      session_key = cookies.session_key
+    )
+  ) {
+    session_key = crypto.randomBytes(16).toString('hex')
+    console.log('new session', session_key)
+    sessions[session_key] = {pageviews: {}}
+  }
+  let session = sessions[session_key]
+    
+  let expires = new XDate()
+  expires.addMonths(1)
+  session.expires = expires.toUTCString()
+  env.response.setHeader(
+    'Set-Cookie',
+    'session_key=' +
+    session_key +
+    '; expires=' +
+    session.expires +
+    '; path=/;'
+  )
+
+  if (
+    !Object.prototype.hasOwnProperty.call(
+      pageviews,
+      env.parsed_url.pathname
+    )
+  ) {
+    console.log('new pageview', env.parsed_url.pathname)
+    pageviews[env.parsed_url.pathname] = {visits: 0, unique_visits: 0}
+  }
+  let pageview = pageviews[env.parsed_url.pathname]
+  ++pageview.visits;
+
+  //if (!Object.prototype.hasOwnProperty.call(session, 'pageviews'))
+  //  session.pageviews = {}
+  let session_pageviews = session.pageviews
+  if (
+    !Object.prototype.hasOwnProperty.call(
+      session_pageviews,
+      env.parsed_url.pathname
+    )
+  ) {
+    console.log('new session_pageview', env.parsed_url.pathname)
+    session_pageviews[env.parsed_url.pathname] = 0
+    ++pageview.unique_visits
+  }
+  ++session_pageviews[env.parsed_url.pathname]
+
+  env.site.write_json('/_analytics/sessions.json', sessions)
+  env.site.write_json('/_analytics/pageviews.json', pageviews)
+
+  let _out = []
+  _out.push('')
+  html(lang="en") {
+    head {
+      meta(charset="utf-8") {}
+      meta(http-equiv="X-UA-Compatible" content="IE=edge") {}
+      meta(name="viewport" content="width=device-width,initial-scale=1") {}
+      // The above 3 meta tags *must* come first in the head;
+      // any other head content must come *after* these tags
+      title {
+        _out.push(
+          globals.site_title +
+          ": " +
+          (globals.page_to_title[env.parsed_url.pathname] || env.parsed_url.pathname)
+        )
+      }
+  
+      link(rel="stylesheet" href="css/bootstrap.css") {}
+      link(rel="stylesheet" href="//fonts.googleapis.com/css?family=Frank+Ruhl+Libre:regular,regularitalic,semibold,semibolditalic,bold,bolditalic,black,blackitalic") {}
+      link(rel="stylesheet" href="//fonts.googleapis.com/css?family=Nunito+Sans:regular,regularitalic,semibold,semibolditalic,bold,bolditalic,black,blackitalic") {}
+  
+      await head(_out)
+
+      // HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries
+      // WARNING: Respond.js doesn't work if you view the page via file://
+      _out.push('')
+
+      // use favicon generator website to get favicons.zip and favicons.html
+      _out.push(favicons)
+    }
+    body {
+      await body(_out)
+  
+      // jQuery (necessary for Bootstrap's JavaScript plugins)
+      script(src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js") {}
+      // Include all compiled plugins (below), or include individual files as needed
+      //script(src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous") {}
+      script(src="js/bootstrap.js") {}
+
+      await scripts(_out)
+    }
+  }
+  env.site.serve(env, 200, Buffer.from(_out.join('')), 'page.jst')
+}
diff --git a/search.html.jst b/search.html.jst
new file mode 100644
index 0000000..cad9570
--- /dev/null
+++ b/search.html.jst
@@ -0,0 +1,64 @@
+let querystring = require('querystring')
+
+return async env => {
+  let globals = await env.site.get_json('/_config/globals.json')
+  let navbar = await _require('/navbar.jst')
+  let zet_site = await env.site.get_zettair('/_zet/site')
+
+  let query = env.parsed_url.query.query
+  let first = parseInt(env.parsed_url.query.first || '0')
+  let search = zet_site.search(query, first, 10)
+  console.log(
+    `${env.parsed_url.host} search "${query}" first ${first} results ${search.results.length} total results ${search.total_results}`
+  )
+
+  await navbar(
+    env,
+    async _out => {},
+    async _out => {
+      h1 {'Search results'}
+
+      h4 {
+        'Query: '
+        strong {`${query}`}
+      }
+    
+      if (search.results.length) {
+        p {`Showing results ${first + 1}–${first + search.results.length} of ${search.total_results}`}
+
+        ol(start=first + 1) {
+          for (let i = 0; i < search.results.length; ++i) {
+            let page = search.results[i].auxiliary
+            li {
+              a(href=page) {`${globals.page_to_title[page] || page}`}
+              br {}
+              p {_out.push(search.results[i].summary)} // note: contains HTML
+            }
+          }
+        }
+
+        ul.pagination {
+          for (let i = 0; i * 10 < search.total_results; ++i) {
+            let page = '/search.html?' + querystring.stringify(
+              {query: query, first: i * 10}
+            )
+            let text = (i + 1).toString()
+            if (i * 10 === first)
+              li.active {
+                a(href=page) {
+                  `${text}`
+                  span.sr-only {'(current)'}
+                }
+              }
+            else
+              li {
+                a(href=page) {`${text}`}
+              }
+          }
+        }
+      }
+      else
+        p {'No results'}
+    }
+  )
+}
-- 
2.34.1