├── src ├── models │ ├── mod.rs │ └── city.rs ├── routes │ ├── mod.rs │ └── cities.rs └── main.rs ├── images └── morthdemo.gif ├── views ├── welcome.hbs ├── cities │ ├── show.hbs │ ├── form.hbs │ └── list.hbs └── layouts │ └── main.hbs ├── .gitignore ├── Dockerfile ├── Cargo.toml ├── public ├── js │ └── site.js └── styles │ ├── custom.css │ ├── normalize.css │ └── skeleton.css ├── README.md └── LICENSE /src/models/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod city; 2 | -------------------------------------------------------------------------------- /images/morthdemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/No9/tide-morth-example/HEAD/images/morthdemo.gif -------------------------------------------------------------------------------- /views/welcome.hbs: -------------------------------------------------------------------------------- 1 | {{#*inline "page"}} 2 | 3 | 4 |
Hello, {{ name }}!
5 |

6 | Want to see some cities? 7 |

8 | {{/inline}} 9 | {{! remove whitespaces with ~ }} 10 | {{~> (parent)~}} -------------------------------------------------------------------------------- /views/cities/show.hbs: -------------------------------------------------------------------------------- 1 | {{#*inline "page"}} 2 |
{{city.name}}
3 |

{{city.country}}

4 |

{{city.description}}

5 |

6 | Back 7 |

8 | {{/inline}} 9 | {{! remove whitespaces with ~ }} 10 | {{~> (parent)~}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /src/models/city.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use wither::bson::{doc, oid::ObjectId}; 3 | use wither::prelude::*; 4 | 5 | // Define a model. Simple as deriving a few traits. 6 | #[derive(Debug, Model, Serialize, Deserialize)] 7 | pub struct City { 8 | #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] 9 | pub id: Option, 10 | #[model(index(index = "dsc", with(field = "country", index = "dsc")))] 11 | pub name: String, 12 | pub country: String, 13 | pub description: String, 14 | } 15 | -------------------------------------------------------------------------------- /src/routes/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cities; 2 | 3 | use super::*; 4 | use handlebars::to_json; 5 | use serde_json::value::Map; 6 | use tide::{http::mime, Request, Response}; 7 | 8 | pub async fn welcome(req: Request) -> tide::Result> { 9 | let state = &req.state(); 10 | let hb = &state.registry; 11 | let mut data = Map::new(); 12 | data.insert("name".to_string(), to_json("Tider!")); 13 | data.insert("parent".to_string(), to_json("layouts/main")); 14 | Ok(Response::builder(200) 15 | .body(hb.render("welcome", &data)?) 16 | .content_type(mime::HTML)) 17 | } 18 | -------------------------------------------------------------------------------- /views/cities/form.hbs: -------------------------------------------------------------------------------- 1 | {{#*inline "page"}} 2 | 3 |
4 |
5 |
6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 |
15 | 16 | 18 | 19 | Cancel 20 |
21 | {{/inline}} 22 | {{! remove whitespaces with ~ }} 23 | {{~> (parent)~}} -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Docker file to build an image with just the executable in. 2 | FROM rust:1.47.0-alpine as builder 3 | 4 | COPY . /app-build 5 | 6 | WORKDIR "/app-build" 7 | 8 | RUN \ 9 | apk add --no-cache musl-dev && \ 10 | cargo build --release \ 11 | && echo "#!/bin/bash" > run.sh \ 12 | && bin=$(find ./target/release -maxdepth 1 -perm -111 -type f| head -n 1) \ 13 | && echo ./${bin##*/} >> run.sh \ 14 | && chmod 755 run.sh 15 | 16 | FROM alpine 17 | 18 | RUN useradd -S rust 19 | 20 | WORKDIR "/app" 21 | 22 | # get files and built binary from previous image 23 | COPY --from=builder --chown=rust /app-build/run.sh /app-build/public/ /app-build/Cargo.toml /app-build/target/release/tide-morth-example ./ 24 | 25 | WORKDIR "/app/public" 26 | 27 | COPY --from=builder --chown=rust /app-build/run.sh /app-build/public/ ./ 28 | 29 | WORKDIR "/app" 30 | 31 | USER rust 32 | 33 | ENV PORT 8080 34 | 35 | EXPOSE 8080 36 | 37 | CMD ["./run.sh"] 38 | -------------------------------------------------------------------------------- /views/cities/list.hbs: -------------------------------------------------------------------------------- 1 | {{#*inline "page"}} 2 | 3 | {{#if cities}} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {{#each cities}} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {{/each}} 26 | 27 |
IdNameCountry
{{this._id.[$oid]}}{{this.name}}{{this.country}} Show Edit Delete
28 | {{/if}} 29 | 30 | Create new city 31 | 32 | 33 | 34 | {{/inline}} 35 | {{! remove whitespaces with ~ }} 36 | {{~> (parent)~}} -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tide-morth-example" 3 | version = "0.3.0" 4 | authors = ["Anton Whalley "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | tide = "0.14" 11 | tide-handlebars="0.6" 12 | handlebars="3.2.1" 13 | async-std = { version = "1.6.0", features = ["unstable", "attributes"] } 14 | mongodb = { version = "1", default-features=false } 15 | serde = "1.0.0" 16 | serde_json = "1.0.39" 17 | lazy_static = "1" 18 | wither = { version = "0.9.0-alpha.2", features = ["async-std-runtime"], default-features = false } 19 | tracing = { version = "0.1", default-features = true, features = ["log", "std"] } 20 | tracing-futures = { version = "0.2", default-features = false, features = ["std-future"] } 21 | tracing-subscriber = "0.2.7" 22 | tracing-log = "0.1" 23 | tracing-attributes = { version = "0.1.2"} 24 | 25 | tracing-opentelemetry = "*" 26 | # opentelemetry example 27 | opentelemetry = "0.6" 28 | opentelemetry-jaeger = "0.5" 29 | 30 | [profile.release] 31 | debug = true -------------------------------------------------------------------------------- /views/layouts/main.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tide MoRTH Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 |
25 | 35 |
36 | {{~> page}} 37 |
38 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /public/js/site.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | // Variables 4 | var $codeSnippets = $('.code-example-body'), 5 | $nav = $('.navbar'), 6 | $body = $('body'), 7 | $window = $(window), 8 | $popoverLink = $('[data-popover]'), 9 | navOffsetTop = $nav.offset().top, 10 | $document = $(document), 11 | entityMap = { 12 | "&": "&", 13 | "<": "<", 14 | ">": ">", 15 | '"': '"', 16 | "'": ''', 17 | "/": '/' 18 | } 19 | 20 | function init() { 21 | $window.on('scroll', onScroll) 22 | $window.on('resize', resize) 23 | $popoverLink.on('click', openPopover) 24 | $document.on('click', closePopover) 25 | $('a[href^="#"]').on('click', smoothScroll) 26 | buildSnippets(); 27 | } 28 | 29 | function smoothScroll(e) { 30 | e.preventDefault(); 31 | $(document).off("scroll"); 32 | var target = this.hash, 33 | menu = target; 34 | $target = $(target); 35 | $('html, body').stop().animate({ 36 | 'scrollTop': $target.offset().top-40 37 | }, 0, 'swing', function () { 38 | window.location.hash = target; 39 | $(document).on("scroll", onScroll); 40 | }); 41 | } 42 | 43 | function openPopover(e) { 44 | e.preventDefault() 45 | closePopover(); 46 | var popover = $($(this).data('popover')); 47 | popover.toggleClass('open') 48 | e.stopImmediatePropagation(); 49 | } 50 | 51 | function closePopover(e) { 52 | if($('.popover.open').length > 0) { 53 | $('.popover').removeClass('open') 54 | } 55 | } 56 | 57 | $("#button").click(function() { 58 | $('html, body').animate({ 59 | scrollTop: $("#elementtoScrollToID").offset().top 60 | }, 2000); 61 | }); 62 | 63 | function resize() { 64 | $body.removeClass('has-docked-nav') 65 | navOffsetTop = $nav.offset().top 66 | onScroll() 67 | } 68 | 69 | function onScroll() { 70 | if(navOffsetTop < $window.scrollTop() && !$body.hasClass('has-docked-nav')) { 71 | $body.addClass('has-docked-nav') 72 | } 73 | if(navOffsetTop > $window.scrollTop() && $body.hasClass('has-docked-nav')) { 74 | $body.removeClass('has-docked-nav') 75 | } 76 | } 77 | 78 | function escapeHtml(string) { 79 | return String(string).replace(/[&<>"'\/]/g, function (s) { 80 | return entityMap[s]; 81 | }); 82 | } 83 | 84 | function buildSnippets() { 85 | $codeSnippets.each(function() { 86 | var newContent = escapeHtml($(this).html()) 87 | $(this).html(newContent) 88 | }) 89 | } 90 | 91 | 92 | init(); 93 | 94 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tide-morth-example 2 | 3 | An example application using mongodb rust tide and handlebars A.K.A MoRTH stack 4 | 5 | It's prime focus is to provde a project structure that should feel familiar to folks who have used JS Handlebars, pybars or Java Handlebars projects. 6 | 7 | ![](images/morthdemo.gif) 8 | 9 | If you are evaluating tide patterns please look at [Rust Tide Example](https://github.com/jbr/tide-example). 10 | 11 | Once this project has matured it's intended that it will enter the [Rust Tide Example Contributions](https://github.com/jbr/tide-example#contributing) section in some form. 12 | 13 | ## usage 14 | 15 | This example requires a local mongo instance which can be ran with: 16 | 17 | ``` 18 | $ docker run -d -p 27017:27017 -v ~/data:/data/db mongo:4.2 19 | ``` 20 | 21 | If you wish to use a different mongodb please modify the [connection string in main.rs](https://github.com/No9/tide-morth-example/blob/master/src/main.rs#L21) 22 | 23 | It also requires the options dependency of jaeger a local instance can be ran with: 24 | 25 | ``` 26 | $ docker run -d --name jaeger \ 27 | -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \ 28 | -p 5775:5775/udp \ 29 | -p 6831:6831/udp \ 30 | -p 6832:6832/udp \ 31 | -p 5778:5778 \ 32 | -p 16686:16686 \ 33 | -p 14268:14268 \ 34 | -p 14250:14250 \ 35 | -p 9411:9411 \ 36 | jaegertracing/all-in-one:1.18 37 | ``` 38 | 39 | Then start the app 40 | 41 | ``` 42 | $ cargo run 43 | ``` 44 | 45 | ## Comments 46 | 47 | * Fast development for simple CRUD apps as no data access layer is required just as a decorated struct - See models/cities.rs 48 | 49 | ```rust 50 | use serde::{Deserialize, Serialize}; 51 | use wither::bson::{doc, oid::ObjectId}; 52 | use wither::prelude::*; 53 | 54 | // Define a model. Simple as deriving a few traits. 55 | #[derive(Debug, Model, Serialize, Deserialize)] 56 | pub struct City { 57 | #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] 58 | pub id: Option, 59 | #[model(index(index = "dsc", with(field = "country", index = "dsc")))] 60 | pub name: String, 61 | pub country: String, 62 | pub description: String, 63 | } 64 | ``` 65 | 66 | * Accessible to developers familiar with Javascript constructs 67 | 68 | ```html 69 | 70 | {{#each cities}} 71 | 72 | {{this._id.[$oid]}} 73 | {{this.name}} 74 | {{this.country}} 75 | Show 76 | Edit 77 | 78 | 79 | {{/each}} 80 | 81 | ``` 82 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | pub mod models; 2 | 3 | extern crate serde_json; 4 | 5 | use async_std::sync::Arc; 6 | use handlebars::Handlebars; 7 | use mongodb::{options::ClientOptions, Client}; 8 | use tide_handlebars::prelude::*; 9 | 10 | // use opentelemetry::api::Provider; 11 | // use opentelemetry::sdk; 12 | // use tracing_subscriber::prelude::*; 13 | use lazy_static::lazy_static; 14 | use std::env; 15 | 16 | mod routes; 17 | 18 | #[derive(Clone, Debug)] 19 | pub struct State { 20 | registry: Arc>, 21 | client: Arc, 22 | } 23 | 24 | lazy_static! { 25 | static ref HOST: String = env::var("HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); 26 | static ref PORT: String = env::var("PORT").unwrap_or_else(|_| "27017".to_string()); 27 | static ref CONNECTION_STRING: String = 28 | format!("mongodb://{}:{}/", HOST.as_str(), PORT.as_str()); 29 | } 30 | 31 | #[async_std::main] 32 | async fn main() -> Result<(), std::io::Error> { 33 | let mut client_options = match ClientOptions::parse(&CONNECTION_STRING).await { 34 | Ok(c) => c, 35 | Err(e) => panic!("Client Options Failed: {}", e), 36 | }; 37 | //.with_env_filter("async_fn=trace") 38 | 39 | // match init_tracer() { 40 | // Ok(o) => o, 41 | // Err(e) => panic!("tracing failed {}", e), 42 | // }; 43 | // Manually set an option. 44 | client_options.app_name = Some("MoRTH".to_string()); 45 | 46 | // Get a handle to the deployment. 47 | let client = match Client::with_options(client_options) { 48 | Ok(c) => c, 49 | Err(e) => panic!("Client Creation Failed: {}", e), 50 | }; 51 | let mut hb = Handlebars::new(); 52 | hb.register_templates_directory(".hbs", "./views").unwrap(); 53 | 54 | let engine = State { 55 | registry: Arc::new(hb), 56 | client: Arc::new(client), 57 | }; 58 | 59 | let mut app = tide::with_state(engine); 60 | app.at("/").get(tide::Redirect::new("/welcome")); 61 | 62 | app.at("/welcome").get(routes::welcome); 63 | 64 | let mut cities = app.at("/cities"); 65 | cities.get(routes::cities::index); 66 | cities 67 | .at("/new") 68 | .get(routes::cities::new) 69 | .post(routes::cities::create); 70 | 71 | cities.at("/:city_id").get(routes::cities::show); 72 | cities 73 | .at("/:city_id/edit") 74 | .get(routes::cities::edit) 75 | .post(routes::cities::update); 76 | cities 77 | .at("/:city_id/delete") 78 | .get(routes::cities::delete); 79 | 80 | app.at("/public").serve_dir("public/")?; 81 | app.listen("127.0.0.1:8080").await?; 82 | Ok(()) 83 | } 84 | 85 | // fn init_tracer() -> Result<(), Box> { 86 | // let exporter = opentelemetry_jaeger::Exporter::builder() 87 | // .with_agent_endpoint("127.0.0.1:6831".parse().unwrap()) 88 | // .with_process(opentelemetry_jaeger::Process { 89 | // service_name: "morth_example".to_string(), 90 | // tags: Vec::new(), 91 | // }) 92 | // .init()?; 93 | // let provider = sdk::Provider::builder() 94 | // .with_simple_exporter(exporter) 95 | // .with_config(sdk::Config { 96 | // default_sampler: Box::new(sdk::Sampler::Always), 97 | // ..Default::default() 98 | // }) 99 | // .build(); 100 | // let tracer = provider.get_tracer("tracing"); 101 | 102 | // let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer); 103 | // tracing_subscriber::registry() 104 | // .with(opentelemetry) 105 | // .try_init()?; 106 | 107 | // Ok(()) 108 | // } 109 | -------------------------------------------------------------------------------- /src/routes/cities.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::models::city::City; 3 | use async_std::stream::StreamExt; 4 | use handlebars::to_json; 5 | use serde_json::value::Map; 6 | use tide::Request; 7 | use tracing::{info, instrument}; 8 | use wither::bson::{doc, oid::ObjectId}; 9 | use wither::Model; 10 | 11 | #[instrument] 12 | pub async fn index(req: Request) -> tide::Result { 13 | info!("Tracing Started"); 14 | let state = &req.state(); 15 | let db = &state.client.database("test"); 16 | 17 | let mut cursor = City::find(&db.clone(), None, None).await?; 18 | let mut docs: Vec = Vec::new(); 19 | 20 | while let Some(city) = cursor.next().await { 21 | docs.push(city?); 22 | } 23 | 24 | let hb = &state.registry; 25 | let mut data = Map::new(); 26 | data.insert("title".to_string(), to_json("Cities")); 27 | data.insert("parent".to_string(), to_json("layouts/main")); 28 | 29 | data.insert("cities".to_string(), to_json(&docs)); 30 | Ok(hb.render_response_ext("cities/list", &data, "html")?) 31 | } 32 | 33 | #[instrument] 34 | pub async fn show(req: Request) -> tide::Result { 35 | let state = &req.state(); 36 | let db = &state.client.database("test"); 37 | let id = req.param("city_id")?; 38 | 39 | let object_id = ObjectId::with_string(&id).unwrap(); 40 | let filter = doc! { "_id": object_id }; 41 | 42 | let doc = City::find_one(&db.clone(), filter, None).await?; 43 | let hb = &state.registry; 44 | let mut data = Map::new(); 45 | data.insert("title".to_string(), to_json("Cities")); 46 | data.insert("parent".to_string(), to_json("layouts/main")); 47 | 48 | data.insert("city".to_string(), to_json(&doc)); 49 | Ok(hb.render_response_ext("cities/show", &data, "html")?) 50 | } 51 | 52 | #[instrument] 53 | pub async fn edit(req: Request) -> tide::Result { 54 | let state = &req.state(); 55 | let db = &state.client.database("test"); 56 | let id = req.param("city_id")?; 57 | 58 | let object_id = ObjectId::with_string(&id).unwrap(); 59 | let filter = doc! { "_id": object_id }; 60 | 61 | let doc = City::find_one(&db.clone(), filter, None).await?; 62 | let hb = &state.registry; 63 | let mut data = Map::new(); 64 | data.insert("title".to_string(), to_json("Cities")); 65 | data.insert("parent".to_string(), to_json("layouts/main")); 66 | data.insert( 67 | "action".to_string(), 68 | to_json(format!("/cities/{}/edit", id)), 69 | ); 70 | data.insert("city".to_string(), to_json(&doc)); 71 | Ok(hb.render_response_ext("cities/form", &data, "html")?) 72 | } 73 | 74 | #[instrument] 75 | pub async fn delete(req: Request) -> tide::Result { 76 | let state = &req.state(); 77 | let db = &state.client.database("test"); 78 | let id = req.param("city_id")?; 79 | 80 | let object_id = ObjectId::with_string(&id).unwrap(); 81 | let filter = doc! { "_id": object_id }; 82 | 83 | let _doc = City::find_one_and_delete(&db.clone(), filter, None).await?; 84 | 85 | Ok(tide::Redirect::new(format!("/cities")).into()) 86 | 87 | } 88 | 89 | #[instrument] 90 | pub async fn new(req: Request) -> tide::Result { 91 | let hb = &req.state().registry; 92 | let mut data = Map::new(); 93 | data.insert("title".to_string(), to_json("New City")); 94 | data.insert("parent".to_string(), to_json("layouts/main")); 95 | data.insert("action".to_string(), to_json("/cities/new")); 96 | Ok(hb.render_response_ext("cities/form", &data, "html")?) 97 | } 98 | 99 | #[instrument] 100 | pub async fn create(mut req: Request) -> tide::Result { 101 | let mut city: City = req.body_form().await?; 102 | let state = &req.state(); 103 | let db = &state.client.database("test"); 104 | city.save(&db.clone(), None).await?; 105 | 106 | let city_id = city.id.unwrap(); 107 | 108 | Ok(tide::Redirect::new(format!("/cities/{}", city_id.to_hex())).into()) 109 | } 110 | 111 | #[instrument] 112 | pub async fn update(mut req: Request) -> tide::Result { 113 | let mut city: City = req.body_form().await?; 114 | let state = &req.state(); 115 | let db = &state.client.database("test"); 116 | let id = req.param("city_id")?; 117 | city.id = Some(ObjectId::with_string(&id).unwrap()); 118 | 119 | city.save(&db.clone(), None).await?; 120 | 121 | let city_id = city.id.unwrap(); 122 | 123 | Ok(tide::Redirect::new(format!("/cities/{}", city_id.to_hex())).into()) 124 | } 125 | -------------------------------------------------------------------------------- /public/styles/custom.css: -------------------------------------------------------------------------------- 1 | .container { 2 | max-width: 800px; } 3 | .header { 4 | margin-top: 6rem; 5 | text-align: center; } 6 | .value-prop { 7 | margin-top: 1rem; } 8 | .value-props { 9 | margin-top: 4rem; 10 | margin-bottom: 4rem; } 11 | .docs-header { 12 | text-transform: uppercase; 13 | font-size: 1.4rem; 14 | letter-spacing: .2rem; 15 | font-weight: 600; } 16 | .docs-section { 17 | border-top: 1px solid #eee; 18 | padding: 4rem 0; 19 | margin-bottom: 0;} 20 | .value-img { 21 | display: block; 22 | text-align: center; 23 | margin: 2.5rem auto 0; } 24 | .example-grid .column, 25 | .example-grid .columns { 26 | background: #EEE; 27 | text-align: center; 28 | border-radius: 4px; 29 | font-size: 1rem; 30 | text-transform: uppercase; 31 | height: 30px; 32 | line-height: 30px; 33 | margin-bottom: .75rem; 34 | font-weight: 600; 35 | letter-spacing: .1rem; } 36 | .docs-example .row, 37 | .docs-example.row, 38 | .docs-example form { 39 | margin-bottom: 0; } 40 | .docs-example h1, 41 | .docs-example h2, 42 | .docs-example h3, 43 | .docs-example h4, 44 | .docs-example h5, 45 | .docs-example h6 { 46 | margin-bottom: 1rem; } 47 | .heading-font-size { 48 | font-size: 1.2rem; 49 | color: #999; 50 | letter-spacing: normal; } 51 | .code-example { 52 | margin-top: 1.5rem; 53 | margin-bottom: 0; } 54 | .code-example-body { 55 | white-space: pre; 56 | word-wrap: break-word } 57 | .example { 58 | position: relative; 59 | margin-top: 4rem; } 60 | .example-header { 61 | font-weight: 600; 62 | margin-top: 1.5rem; 63 | margin-bottom: .5rem; } 64 | .example-description { 65 | margin-bottom: 1.5rem; } 66 | .example-screenshot-wrapper { 67 | display: block; 68 | position: relative; 69 | overflow: hidden; 70 | border-radius: 6px; 71 | border: 1px solid #eee; 72 | height: 250px; } 73 | .example-screenshot { 74 | width: 100%; 75 | height: auto; } 76 | .example-screenshot.coming-soon { 77 | width: auto; 78 | position: absolute; 79 | background: #eee; 80 | top: 5px; 81 | right: 5px; 82 | bottom: 5px; 83 | left: 5px; } 84 | .navbar { 85 | display: none; } 86 | 87 | /* Larger than phone */ 88 | @media (min-width: 550px) { 89 | .header { 90 | margin-top: 18rem; } 91 | .value-props { 92 | margin-top: 9rem; 93 | margin-bottom: 7rem; } 94 | .value-img { 95 | margin-bottom: 1rem; } 96 | .example-grid .column, 97 | .example-grid .columns { 98 | margin-bottom: 1.5rem; } 99 | .docs-section { 100 | padding: 6rem 0; } 101 | .example-send-yourself-copy { 102 | float: right; 103 | margin-top: 12px; } 104 | .example-screenshot-wrapper { 105 | position: absolute; 106 | width: 48%; 107 | height: 100%; 108 | left: 0; 109 | max-height: none; } 110 | } 111 | 112 | /* Larger than tablet */ 113 | @media (min-width: 750px) { 114 | /* Navbar */ 115 | .navbar + .docs-section { 116 | border-top-width: 0; } 117 | .navbar, 118 | .navbar-spacer { 119 | display: block; 120 | width: 100%; 121 | height: 6.5rem; 122 | background: #fff; 123 | z-index: 99; 124 | border-top: 1px solid #eee; 125 | border-bottom: 1px solid #eee; } 126 | .navbar-spacer { 127 | display: none; } 128 | .navbar > .container { 129 | width: 100%; } 130 | .navbar-list { 131 | list-style: none; 132 | margin-bottom: 0; } 133 | .navbar-item { 134 | position: relative; 135 | float: left; 136 | margin-bottom: 0; } 137 | .navbar-link { 138 | text-transform: uppercase; 139 | font-size: 11px; 140 | font-weight: 600; 141 | letter-spacing: .2rem; 142 | margin-right: 35px; 143 | text-decoration: none; 144 | line-height: 6.5rem; 145 | color: #222; } 146 | .navbar-link.active { 147 | color: #33C3F0; } 148 | .has-docked-nav .navbar { 149 | position: fixed; 150 | top: 0; 151 | left: 0; } 152 | .has-docked-nav .navbar-spacer { 153 | display: block; } 154 | /* Re-overiding the width 100% declaration to match size of % based container */ 155 | .has-docked-nav .navbar > .container { 156 | width: 80%; } 157 | 158 | /* Popover */ 159 | .popover.open { 160 | display: block; 161 | } 162 | .popover { 163 | display: none; 164 | position: absolute; 165 | top: 0; 166 | left: 0; 167 | background: #fff; 168 | border: 1px solid #eee; 169 | border-radius: 4px; 170 | top: 92%; 171 | left: -50%; 172 | -webkit-filter: drop-shadow(0 0 6px rgba(0,0,0,.1)); 173 | -moz-filter: drop-shadow(0 0 6px rgba(0,0,0,.1)); 174 | filter: drop-shadow(0 0 6px rgba(0,0,0,.1)); } 175 | .popover-item:first-child .popover-link:after, 176 | .popover-item:first-child .popover-link:before { 177 | bottom: 100%; 178 | left: 50%; 179 | border: solid transparent; 180 | content: " "; 181 | height: 0; 182 | width: 0; 183 | position: absolute; 184 | pointer-events: none; } 185 | .popover-item:first-child .popover-link:after { 186 | border-color: rgba(255, 255, 255, 0); 187 | border-bottom-color: #fff; 188 | border-width: 10px; 189 | margin-left: -10px; } 190 | .popover-item:first-child .popover-link:before { 191 | border-color: rgba(238, 238, 238, 0); 192 | border-bottom-color: #eee; 193 | border-width: 11px; 194 | margin-left: -11px; } 195 | .popover-list { 196 | padding: 0; 197 | margin: 0; 198 | list-style: none; } 199 | .popover-item { 200 | padding: 0; 201 | margin: 0; } 202 | .popover-link { 203 | position: relative; 204 | color: #222; 205 | display: block; 206 | padding: 8px 20px; 207 | border-bottom: 1px solid #eee; 208 | text-decoration: none; 209 | text-transform: uppercase; 210 | font-size: 1.0rem; 211 | font-weight: 600; 212 | text-align: center; 213 | letter-spacing: .1rem; } 214 | .popover-item:first-child .popover-link { 215 | border-radius: 4px 4px 0 0; } 216 | .popover-item:last-child .popover-link { 217 | border-radius: 0 0 4px 4px; 218 | border-bottom-width: 0; } 219 | .popover-link:hover { 220 | color: #fff; 221 | background: #33C3F0; } 222 | .popover-link:hover, 223 | .popover-item:first-child .popover-link:hover:after { 224 | border-bottom-color: #33C3F0; } 225 | } -------------------------------------------------------------------------------- /public/styles/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability when focused and also mouse hovered in all browsers. 95 | */ 96 | 97 | a:active, 98 | a:hover { 99 | outline: 0; 100 | } 101 | 102 | /* Text-level semantics 103 | ========================================================================== */ 104 | 105 | /** 106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 107 | */ 108 | 109 | abbr[title] { 110 | border-bottom: 1px dotted; 111 | } 112 | 113 | /** 114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 115 | */ 116 | 117 | b, 118 | strong { 119 | font-weight: bold; 120 | } 121 | 122 | /** 123 | * Address styling not present in Safari and Chrome. 124 | */ 125 | 126 | dfn { 127 | font-style: italic; 128 | } 129 | 130 | /** 131 | * Address variable `h1` font-size and margin within `section` and `article` 132 | * contexts in Firefox 4+, Safari, and Chrome. 133 | */ 134 | 135 | h1 { 136 | font-size: 2em; 137 | margin: 0.67em 0; 138 | } 139 | 140 | /** 141 | * Address styling not present in IE 8/9. 142 | */ 143 | 144 | mark { 145 | background: #ff0; 146 | color: #000; 147 | } 148 | 149 | /** 150 | * Address inconsistent and variable font size in all browsers. 151 | */ 152 | 153 | small { 154 | font-size: 80%; 155 | } 156 | 157 | /** 158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 159 | */ 160 | 161 | sub, 162 | sup { 163 | font-size: 75%; 164 | line-height: 0; 165 | position: relative; 166 | vertical-align: baseline; 167 | } 168 | 169 | sup { 170 | top: -0.5em; 171 | } 172 | 173 | sub { 174 | bottom: -0.25em; 175 | } 176 | 177 | /* Embedded content 178 | ========================================================================== */ 179 | 180 | /** 181 | * Remove border when inside `a` element in IE 8/9/10. 182 | */ 183 | 184 | img { 185 | border: 0; 186 | } 187 | 188 | /** 189 | * Correct overflow not hidden in IE 9/10/11. 190 | */ 191 | 192 | svg:not(:root) { 193 | overflow: hidden; 194 | } 195 | 196 | /* Grouping content 197 | ========================================================================== */ 198 | 199 | /** 200 | * Address margin not present in IE 8/9 and Safari. 201 | */ 202 | 203 | figure { 204 | margin: 1em 40px; 205 | } 206 | 207 | /** 208 | * Address differences between Firefox and other browsers. 209 | */ 210 | 211 | hr { 212 | -moz-box-sizing: content-box; 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 354 | * (include `-moz` to future-proof). 355 | */ 356 | 357 | input[type="search"] { 358 | -webkit-appearance: textfield; /* 1 */ 359 | -moz-box-sizing: content-box; 360 | -webkit-box-sizing: content-box; /* 2 */ 361 | box-sizing: content-box; 362 | } 363 | 364 | /** 365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 366 | * Safari (but not Chrome) clips the cancel button when the search input has 367 | * padding (and `textfield` appearance). 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Define consistent border, margin, and padding. 377 | */ 378 | 379 | fieldset { 380 | border: 1px solid #c0c0c0; 381 | margin: 0 2px; 382 | padding: 0.35em 0.625em 0.75em; 383 | } 384 | 385 | /** 386 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 388 | */ 389 | 390 | legend { 391 | border: 0; /* 1 */ 392 | padding: 0; /* 2 */ 393 | } 394 | 395 | /** 396 | * Remove default vertical scrollbar in IE 8/9/10/11. 397 | */ 398 | 399 | textarea { 400 | overflow: auto; 401 | } 402 | 403 | /** 404 | * Don't inherit the `font-weight` (applied by a rule above). 405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 406 | */ 407 | 408 | optgroup { 409 | font-weight: bold; 410 | } 411 | 412 | /* Tables 413 | ========================================================================== */ 414 | 415 | /** 416 | * Remove most spacing between table cells. 417 | */ 418 | 419 | table { 420 | border-collapse: collapse; 421 | border-spacing: 0; 422 | } 423 | 424 | td, 425 | th { 426 | padding: 0; 427 | } -------------------------------------------------------------------------------- /public/styles/skeleton.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Skeleton V2.0.4 3 | * Copyright 2014, Dave Gamache 4 | * www.getskeleton.com 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 12/29/2014 8 | */ 9 | 10 | 11 | /* Table of contents 12 | –––––––––––––––––––––––––––––––––––––––––––––––––– 13 | - Grid 14 | - Base Styles 15 | - Typography 16 | - Links 17 | - Buttons 18 | - Forms 19 | - Lists 20 | - Code 21 | - Tables 22 | - Spacing 23 | - Utilities 24 | - Clearing 25 | - Media Queries 26 | */ 27 | 28 | 29 | /* Grid 30 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 31 | .container { 32 | position: relative; 33 | width: 100%; 34 | max-width: 960px; 35 | margin: 0 auto; 36 | padding: 0 20px; 37 | box-sizing: border-box; } 38 | .column, 39 | .columns { 40 | width: 100%; 41 | float: left; 42 | box-sizing: border-box; } 43 | 44 | /* For devices larger than 400px */ 45 | @media (min-width: 400px) { 46 | .container { 47 | width: 85%; 48 | padding: 0; } 49 | } 50 | 51 | /* For devices larger than 550px */ 52 | @media (min-width: 550px) { 53 | .container { 54 | width: 80%; } 55 | .column, 56 | .columns { 57 | margin-left: 4%; } 58 | .column:first-child, 59 | .columns:first-child { 60 | margin-left: 0; } 61 | 62 | .one.column, 63 | .one.columns { width: 4.66666666667%; } 64 | .two.columns { width: 13.3333333333%; } 65 | .three.columns { width: 22%; } 66 | .four.columns { width: 30.6666666667%; } 67 | .five.columns { width: 39.3333333333%; } 68 | .six.columns { width: 48%; } 69 | .seven.columns { width: 56.6666666667%; } 70 | .eight.columns { width: 65.3333333333%; } 71 | .nine.columns { width: 74.0%; } 72 | .ten.columns { width: 82.6666666667%; } 73 | .eleven.columns { width: 91.3333333333%; } 74 | .twelve.columns { width: 100%; margin-left: 0; } 75 | 76 | .one-third.column { width: 30.6666666667%; } 77 | .two-thirds.column { width: 65.3333333333%; } 78 | 79 | .one-half.column { width: 48%; } 80 | 81 | /* Offsets */ 82 | .offset-by-one.column, 83 | .offset-by-one.columns { margin-left: 8.66666666667%; } 84 | .offset-by-two.column, 85 | .offset-by-two.columns { margin-left: 17.3333333333%; } 86 | .offset-by-three.column, 87 | .offset-by-three.columns { margin-left: 26%; } 88 | .offset-by-four.column, 89 | .offset-by-four.columns { margin-left: 34.6666666667%; } 90 | .offset-by-five.column, 91 | .offset-by-five.columns { margin-left: 43.3333333333%; } 92 | .offset-by-six.column, 93 | .offset-by-six.columns { margin-left: 52%; } 94 | .offset-by-seven.column, 95 | .offset-by-seven.columns { margin-left: 60.6666666667%; } 96 | .offset-by-eight.column, 97 | .offset-by-eight.columns { margin-left: 69.3333333333%; } 98 | .offset-by-nine.column, 99 | .offset-by-nine.columns { margin-left: 78.0%; } 100 | .offset-by-ten.column, 101 | .offset-by-ten.columns { margin-left: 86.6666666667%; } 102 | .offset-by-eleven.column, 103 | .offset-by-eleven.columns { margin-left: 95.3333333333%; } 104 | 105 | .offset-by-one-third.column, 106 | .offset-by-one-third.columns { margin-left: 34.6666666667%; } 107 | .offset-by-two-thirds.column, 108 | .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } 109 | 110 | .offset-by-one-half.column, 111 | .offset-by-one-half.columns { margin-left: 52%; } 112 | 113 | } 114 | 115 | 116 | /* Base Styles 117 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 118 | /* NOTE 119 | html is set to 62.5% so that all the REM measurements throughout Skeleton 120 | are based on 10px sizing. So basically 1.5rem = 15px :) */ 121 | html { 122 | font-size: 62.5%; } 123 | body { 124 | font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ 125 | line-height: 1.6; 126 | font-weight: 400; 127 | font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; 128 | color: #222; } 129 | 130 | 131 | /* Typography 132 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 133 | h1, h2, h3, h4, h5, h6 { 134 | margin-top: 0; 135 | margin-bottom: 2rem; 136 | font-weight: 300; } 137 | h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;} 138 | h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; } 139 | h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; } 140 | h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } 141 | h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } 142 | h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } 143 | 144 | /* Larger than phablet */ 145 | @media (min-width: 550px) { 146 | h1 { font-size: 5.0rem; } 147 | h2 { font-size: 4.2rem; } 148 | h3 { font-size: 3.6rem; } 149 | h4 { font-size: 3.0rem; } 150 | h5 { font-size: 2.4rem; } 151 | h6 { font-size: 1.5rem; } 152 | } 153 | 154 | p { 155 | margin-top: 0; } 156 | 157 | 158 | /* Links 159 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 160 | a { 161 | color: #1EAEDB; } 162 | a:hover { 163 | color: #0FA0CE; } 164 | 165 | 166 | /* Buttons 167 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 168 | .button, 169 | button, 170 | input[type="submit"], 171 | input[type="reset"], 172 | input[type="button"] { 173 | display: inline-block; 174 | height: 38px; 175 | padding: 0 30px; 176 | color: #555; 177 | text-align: center; 178 | font-size: 11px; 179 | font-weight: 600; 180 | line-height: 38px; 181 | letter-spacing: .1rem; 182 | text-transform: uppercase; 183 | text-decoration: none; 184 | white-space: nowrap; 185 | background-color: transparent; 186 | border-radius: 4px; 187 | border: 1px solid #bbb; 188 | cursor: pointer; 189 | box-sizing: border-box; } 190 | .button:hover, 191 | button:hover, 192 | input[type="submit"]:hover, 193 | input[type="reset"]:hover, 194 | input[type="button"]:hover, 195 | .button:focus, 196 | button:focus, 197 | input[type="submit"]:focus, 198 | input[type="reset"]:focus, 199 | input[type="button"]:focus { 200 | color: #333; 201 | border-color: #888; 202 | outline: 0; } 203 | .button.button-primary, 204 | button.button-primary, 205 | input[type="submit"].button-primary, 206 | input[type="reset"].button-primary, 207 | input[type="button"].button-primary { 208 | color: #FFF; 209 | background-color: #33C3F0; 210 | border-color: #33C3F0; } 211 | .button.button-primary:hover, 212 | button.button-primary:hover, 213 | input[type="submit"].button-primary:hover, 214 | input[type="reset"].button-primary:hover, 215 | input[type="button"].button-primary:hover, 216 | .button.button-primary:focus, 217 | button.button-primary:focus, 218 | input[type="submit"].button-primary:focus, 219 | input[type="reset"].button-primary:focus, 220 | input[type="button"].button-primary:focus { 221 | color: #FFF; 222 | background-color: #1EAEDB; 223 | border-color: #1EAEDB; } 224 | 225 | 226 | /* Forms 227 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 228 | input[type="email"], 229 | input[type="number"], 230 | input[type="search"], 231 | input[type="text"], 232 | input[type="tel"], 233 | input[type="url"], 234 | input[type="password"], 235 | textarea, 236 | select { 237 | height: 38px; 238 | padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ 239 | background-color: #fff; 240 | border: 1px solid #D1D1D1; 241 | border-radius: 4px; 242 | box-shadow: none; 243 | box-sizing: border-box; } 244 | /* Removes awkward default styles on some inputs for iOS */ 245 | input[type="email"], 246 | input[type="number"], 247 | input[type="search"], 248 | input[type="text"], 249 | input[type="tel"], 250 | input[type="url"], 251 | input[type="password"], 252 | textarea { 253 | -webkit-appearance: none; 254 | -moz-appearance: none; 255 | appearance: none; } 256 | textarea { 257 | min-height: 65px; 258 | padding-top: 6px; 259 | padding-bottom: 6px; } 260 | input[type="email"]:focus, 261 | input[type="number"]:focus, 262 | input[type="search"]:focus, 263 | input[type="text"]:focus, 264 | input[type="tel"]:focus, 265 | input[type="url"]:focus, 266 | input[type="password"]:focus, 267 | textarea:focus, 268 | select:focus { 269 | border: 1px solid #33C3F0; 270 | outline: 0; } 271 | label, 272 | legend { 273 | display: block; 274 | margin-bottom: .5rem; 275 | font-weight: 600; } 276 | fieldset { 277 | padding: 0; 278 | border-width: 0; } 279 | input[type="checkbox"], 280 | input[type="radio"] { 281 | display: inline; } 282 | label > .label-body { 283 | display: inline-block; 284 | margin-left: .5rem; 285 | font-weight: normal; } 286 | 287 | 288 | /* Lists 289 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 290 | ul { 291 | list-style: circle inside; } 292 | ol { 293 | list-style: decimal inside; } 294 | ol, ul { 295 | padding-left: 0; 296 | margin-top: 0; } 297 | ul ul, 298 | ul ol, 299 | ol ol, 300 | ol ul { 301 | margin: 1.5rem 0 1.5rem 3rem; 302 | font-size: 90%; } 303 | li { 304 | margin-bottom: 1rem; } 305 | 306 | 307 | /* Code 308 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 309 | code { 310 | padding: .2rem .5rem; 311 | margin: 0 .2rem; 312 | font-size: 90%; 313 | white-space: nowrap; 314 | background: #F1F1F1; 315 | border: 1px solid #E1E1E1; 316 | border-radius: 4px; } 317 | pre > code { 318 | display: block; 319 | padding: 1rem 1.5rem; 320 | white-space: pre; } 321 | 322 | 323 | /* Tables 324 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 325 | th, 326 | td { 327 | padding: 12px 15px; 328 | text-align: left; 329 | border-bottom: 1px solid #E1E1E1; } 330 | th:first-child, 331 | td:first-child { 332 | padding-left: 0; } 333 | th:last-child, 334 | td:last-child { 335 | padding-right: 0; } 336 | 337 | 338 | /* Spacing 339 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 340 | button, 341 | .button { 342 | margin-bottom: 1rem; } 343 | input, 344 | textarea, 345 | select, 346 | fieldset { 347 | margin-bottom: 1.5rem; } 348 | pre, 349 | blockquote, 350 | dl, 351 | figure, 352 | table, 353 | p, 354 | ul, 355 | ol, 356 | form { 357 | margin-bottom: 2.5rem; } 358 | 359 | 360 | /* Utilities 361 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 362 | .u-full-width { 363 | width: 100%; 364 | box-sizing: border-box; } 365 | .u-max-full-width { 366 | max-width: 100%; 367 | box-sizing: border-box; } 368 | .u-pull-right { 369 | float: right; } 370 | .u-pull-left { 371 | float: left; } 372 | 373 | 374 | /* Misc 375 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 376 | hr { 377 | margin-top: 3rem; 378 | margin-bottom: 3.5rem; 379 | border-width: 0; 380 | border-top: 1px solid #E1E1E1; } 381 | 382 | 383 | /* Clearing 384 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 385 | 386 | /* Self Clearing Goodness */ 387 | .container:after, 388 | .row:after, 389 | .u-cf { 390 | content: ""; 391 | display: table; 392 | clear: both; } 393 | 394 | 395 | /* Media Queries 396 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 397 | /* 398 | Note: The best way to structure the use of media queries is to create the queries 399 | near the relevant code. For example, if you wanted to change the styles for buttons 400 | on small devices, paste the mobile query code up in the buttons section and style it 401 | there. 402 | */ 403 | 404 | 405 | /* Larger than mobile */ 406 | @media (min-width: 400px) {} 407 | 408 | /* Larger than phablet (also point when grid becomes active) */ 409 | @media (min-width: 550px) {} 410 | 411 | /* Larger than tablet */ 412 | @media (min-width: 750px) {} 413 | 414 | /* Larger than desktop */ 415 | @media (min-width: 1000px) {} 416 | 417 | /* Larger than Desktop HD */ 418 | @media (min-width: 1200px) {} 419 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------