ducky/web

Paddy 2015-07-07 Parent:bc1478742a50

22:21f80f56cda9 tip Browse Files

Switch to being a website instead of a Chrome app. Update our CNAME to be the more appropriate "prototype.useducky.com" when we deploy. Create a homepage and a non-onboarding page template. This mostly consisted of setting up a header component and the associated styles, and then a logged-in vs. guest flavor of said header, changing the links appropriately. We also created a simple homepage that describes what Ducky is and does, and gave a jumping-off point for it. Stubbed out a basic links page, just to get an idea for what the homepage would be like when a logged-in user navigated to the homepage (e.g., not the marketing copy). Updated our login page to _actually work_, and redirected it to the new URL for the payment setup page. Updated the payment page to actually create a subscription, and moved it from /register/payment to just /payment. Fixed a bug in our registration page that was looking for an invalid_form error when it really meant an invalid_format error. Ooops. Also, updated it to point to the new /payment endpoint instead of /register/payment. Updated our router to use the new homepage, the new links page, and updated the URL for the payment page. Updated our button styles so they should all have the right font color, padding, and border-radius, but could've potentially screwed something up. Oops. Updated our backgrounds all over to have a transparent-y white background behind the content, and a simple pattern for the rest of the body. Not sure how I feel about it just yet, but I'm not going to keep futzing with it.

build/CNAME src/components/header-guest.jsx src/components/header-user.jsx src/components/header.jsx src/img/ps_neutral.png src/pages/home.jsx src/pages/links.jsx src/pages/login.jsx src/pages/payment.jsx src/pages/register.jsx src/router.jsx src/styles/_button.scss src/styles/header.scss src/styles/homepage.scss src/styles/onboarding.scss

     1.1 --- a/build/CNAME	Tue Jul 07 21:29:19 2015 -0400
     1.2 +++ b/build/CNAME	Tue Jul 07 21:39:53 2015 -0400
     1.3 @@ -1,1 +1,1 @@
     1.4 -chromeappproto.useducky.com
     1.5 +prototype.useducky.com
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/src/components/header-guest.jsx	Tue Jul 07 21:39:53 2015 -0400
     2.3 @@ -0,0 +1,46 @@
     2.4 +import React from 'react'
     2.5 +import styles from '../styles/header'
     2.6 +
     2.7 +export default React.createClass({
     2.8 +  displayName: 'GuestMenu',
     2.9 +  getInitialState () {
    2.10 +    return {
    2.11 +      showMenu: false,
    2.12 +    }
    2.13 +  },
    2.14 +
    2.15 +  toggleMenu (e) {
    2.16 +    e.preventDefault()
    2.17 +    this.setState({showMenu: !this.state.showMenu})
    2.18 +  },
    2.19 +
    2.20 +  render () {
    2.21 +    const classes = React.addons.classSet({
    2.22 +      'centered-navigation-menu': true,
    2.23 +      'show': this.state.showMenu,
    2.24 +    })
    2.25 +    return (
    2.26 +      <header className='centered-navigation' role='banner'>
    2.27 +        <div className='centered-navigation-wrapper'>
    2.28 +          <a href='/' className='mobile-logo'>
    2.29 +            <img src={require('./ducky.svg')} alt='Ducky' />
    2.30 +          </a>
    2.31 +          <a href='#' onClick={this.toggleMenu} className='centered-navigation-mobile-menu'>MENU</a>
    2.32 +          <nav role='navigation'>
    2.33 +            <ul id='js-centered-navigation-menu' className={classes}>
    2.34 +              <li className='nav-link'><a href='https://chrome.google.com/webstore'>Chrome</a></li>
    2.35 +              <li className='nav-link'><a href='https://play.google.com/store/apps'>Android</a></li>
    2.36 +              <li className='nav-link logo'>
    2.37 +                <a href='/' className='logo'>
    2.38 +                  <img src={require('./ducky.svg')} alt='Ducky' />
    2.39 +                </a>
    2.40 +              </li>
    2.41 +              <li className='nav-link'><a href='/login'>Log In</a></li>
    2.42 +              <li className='nav-link'><a href='/register'>Sign up</a></li>
    2.43 +            </ul>
    2.44 +          </nav>
    2.45 +        </div>
    2.46 +      </header>
    2.47 +    )
    2.48 +  }
    2.49 +})
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/src/components/header-user.jsx	Tue Jul 07 21:39:53 2015 -0400
     3.3 @@ -0,0 +1,46 @@
     3.4 +import React from 'react'
     3.5 +import styles from '../styles/header'
     3.6 +
     3.7 +export default React.createClass({
     3.8 +  displayName: 'UserMenu',
     3.9 +  getInitialState () {
    3.10 +    return {
    3.11 +      showMenu: false,
    3.12 +    }
    3.13 +  },
    3.14 +
    3.15 +  toggleMenu (e) {
    3.16 +    e.preventDefault()
    3.17 +    this.setState({showMenu: !this.state.showMenu})
    3.18 +  },
    3.19 +
    3.20 +  render () {
    3.21 +    const classes = React.addons.classSet({
    3.22 +      'centered-navigation-menu': true,
    3.23 +      'show': this.state.showMenu,
    3.24 +    })
    3.25 +    return (
    3.26 +      <header className='centered-navigation' role='banner'>
    3.27 +        <div className='centered-navigation-wrapper'>
    3.28 +          <a href='/' className='mobile-logo'>
    3.29 +            <img src={require('./ducky.svg')} alt='Ducky' />
    3.30 +          </a>
    3.31 +          <a href='#' onClick={this.toggleMenu} className='centered-navigation-mobile-menu'>MENU</a>
    3.32 +          <nav role='navigation'>
    3.33 +            <ul id='js-centered-navigation-menu' className={classes}>
    3.34 +              <li className='nav-link'><a href='/links'>Links</a></li>
    3.35 +              <li className='nav-link'><a href='/devices'>Devices</a></li>
    3.36 +              <li className='nav-link logo'>
    3.37 +                <a href='/' className='logo'>
    3.38 +                  <img src={require('./ducky.svg')} alt='Ducky' />
    3.39 +                </a>
    3.40 +              </li>
    3.41 +              <li className='nav-link'><a href='/account'>Account</a></li>
    3.42 +              <li className='nav-link'><a href='/logout'>Log Out</a></li>
    3.43 +            </ul>
    3.44 +          </nav>
    3.45 +        </div>
    3.46 +      </header>
    3.47 +    )
    3.48 +  }
    3.49 +})
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/src/components/header.jsx	Tue Jul 07 21:39:53 2015 -0400
     4.3 @@ -0,0 +1,19 @@
     4.4 +import React from 'react'
     4.5 +import GuestHeader from './header-guest'
     4.6 +import UserHeader from './header-user'
     4.7 +import app from 'ampersand-app'
     4.8 +
     4.9 +export default React.createClass({
    4.10 +  displayName: 'Menu',
    4.11 +
    4.12 +  render () {
    4.13 +    if (app.me && app.me.loggedIn) {
    4.14 +      return (
    4.15 +        <UserHeader />
    4.16 +      )
    4.17 +    }
    4.18 +    return (
    4.19 +      <GuestHeader />
    4.20 +    )
    4.21 +  }
    4.22 +})
     5.1 Binary file src/img/ps_neutral.png has changed
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/src/pages/home.jsx	Tue Jul 07 21:39:53 2015 -0400
     6.3 @@ -0,0 +1,38 @@
     6.4 +import app from 'ampersand-app'
     6.5 +import React from 'react'
     6.6 +import Header from '../components/header'
     6.7 +import homepageStyles from '../styles/homepage'
     6.8 +
     6.9 +export default React.createClass({
    6.10 +  displayName: 'HomePage',
    6.11 +
    6.12 +  render () {
    6.13 +    return (
    6.14 +      <div className='container'>
    6.15 +        <Header />
    6.16 +        <div className="device-text">
    6.17 +          <h4>Ducky keeps your devices connected</h4>
    6.18 +          <p>Send links from anywhere, and watch them pop open on your Chrome and Android devices immediately.</p>
    6.19 +          <p><a href='/register' className='button primary'>Sign up</a></p>
    6.20 +          <h4>Pricing</h4>
    6.21 +
    6.22 +          <div className="grid-items-lines">
    6.23 +            <div className="grid-item left">
    6.24 +              <h1>$2/month</h1>
    6.25 +              <p>Pay month-to-month. Cancel any time.</p>
    6.26 +            </div>
    6.27 +            <div className="grid-item right">
    6.28 +              <h1>$20/year</h1>
    6.29 +              <p>Pay a full year at a time, save over 15%.</p>
    6.30 +            </div>
    6.31 +          </div>
    6.32 +        </div>
    6.33 +        <div className="device">
    6.34 +          <div className="screen"></div>
    6.35 +        </div>
    6.36 +
    6.37 +
    6.38 +      </div>
    6.39 +    )
    6.40 +  }
    6.41 +})
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/src/pages/links.jsx	Tue Jul 07 21:39:53 2015 -0400
     7.3 @@ -0,0 +1,16 @@
     7.4 +import app from 'ampersand-app'
     7.5 +import React from 'react'
     7.6 +import Header from '../components/header'
     7.7 +
     7.8 +export default React.createClass({
     7.9 +  displayName: 'LinksPage',
    7.10 +
    7.11 +  render () {
    7.12 +    return (
    7.13 +      <div className='container'>
    7.14 +        <Header />
    7.15 +        <h2>Links</h2>
    7.16 +      </div>
    7.17 +    )
    7.18 +  }
    7.19 +})
     8.1 --- a/src/pages/login.jsx	Tue Jul 07 21:29:19 2015 -0400
     8.2 +++ b/src/pages/login.jsx	Tue Jul 07 21:39:53 2015 -0400
     8.3 @@ -4,17 +4,63 @@
     8.4  import LaddaButton from 'react-ladda'
     8.5  import LaddaCSS from '../../node_modules/ladda/dist/ladda.min.css'
     8.6  import HeroUnit from '../components/hero'
     8.7 +import ValidationError from '../components/validation-error'
     8.8  import onboardingStyles from '../styles/onboarding.scss'
     8.9 +import flashStyles from '../styles/_flashes.scss'
    8.10  
    8.11  export default React.createClass({
    8.12    displayName: 'LoginPage',
    8.13 +  mixins: [React.addons.LinkedStateMixin],
    8.14  
    8.15    getInitialState () {
    8.16 -    return {active: false, progress: 0}
    8.17 +    return {
    8.18 +      email: null,
    8.19 +      passphrase: null,
    8.20 +      active: false,
    8.21 +      clientErrors: [],
    8.22 +      serverErrors: [],
    8.23 +    }
    8.24    },
    8.25  
    8.26 -  toggle () {
    8.27 -    this.setState({active: !this.state.active})
    8.28 +  emailValidationOutputs: {
    8.29 +    'missing': 'Oops! Gotta enter your email, so we know who you are.',
    8.30 +  },
    8.31 +
    8.32 +  passphraseValidationOutputs: {
    8.33 +    'missing': 'You didn’t enter a passphrase. You should do that.',
    8.34 +  },
    8.35 +
    8.36 +  catchAllValidationOutputs: {
    8.37 +    'act_of_god': 'Hm, something went wrong. Try again? Or let support know.',
    8.38 +    'invalid_format': 'Uh oh, things went really wrong. Let support know you saw the dreaded invalid_format error!',
    8.39 +    'access_denied': 'Oops, something’s gone awry. Let support know your client isn’t working.',
    8.40 +    'invalid_value': 'Hm, those credentials seem wrong. Are you sure they’re right?',
    8.41 +  },
    8.42 +
    8.43 +  validate (email, passphrase) {
    8.44 +    const errors = []
    8.45 +    if (!email || !email.length) {
    8.46 +      errors.push({'error': 'missing', 'field': '/email'})
    8.47 +    }
    8.48 +    if (!passphrase || !passphrase.length) {
    8.49 +      errors.push({'error': 'missing', 'field': '/passphrase'})
    8.50 +    }
    8.51 +    return errors
    8.52 +  },
    8.53 +
    8.54 +  validateForm () {
    8.55 +    event.preventDefault()
    8.56 +    const errors = this.validate(this.state.email, this.state.passphrase)
    8.57 +    this.setState({clientErrors: errors, serverErrors: []})
    8.58 +    return errors.length <= 0
    8.59 +  },
    8.60 +
    8.61 +  login (event) {
    8.62 +    event.preventDefault()
    8.63 +    if (!this.validateForm()) {
    8.64 +      return
    8.65 +    }
    8.66 +    app.me.login(this.state.email, this.state.passphrase)
    8.67    },
    8.68  
    8.69    onBackClick (event) {
    8.70 @@ -22,27 +68,58 @@
    8.71      window.history.back()
    8.72    },
    8.73  
    8.74 +  componentDidMount () {
    8.75 +    app.me.on('request', (moc, xhr, options) => {
    8.76 +      this.setState({active: true})
    8.77 +    })
    8.78 +    app.me.on('sync', (moc, xhr, options) => {
    8.79 +      this.setState({active: false})
    8.80 +      app.router.navigate('/payment')
    8.81 +    })
    8.82 +    app.me.on('error', (moc, xhr, options) => {
    8.83 +      let state = {active: false}
    8.84 +      let resp = {}
    8.85 +      if (xhr && xhr.response) {
    8.86 +        resp = JSON.parse(xhr.response)
    8.87 +      }
    8.88 +      console.log(resp)
    8.89 +      if (resp.errors && resp.errors.length) {
    8.90 +        state.serverErrors = resp.errors
    8.91 +      } else if (resp.error && resp.error == 'invalid_client') {
    8.92 +        state.serverErrors = [{'error': 'access_denied'}]
    8.93 +      } else if (resp.error && resp.error == 'invalid_grant') {
    8.94 +        state.serverErrors = [{'error': 'invalid_value'}]
    8.95 +      } else {
    8.96 +        state.serverErrors = [{'error': 'act_of_god'}]
    8.97 +      }
    8.98 +      this.setState(state)
    8.99 +    })
   8.100 +  },
   8.101 +
   8.102    render () {
   8.103      return (
   8.104        <div className='container'>
   8.105          <HeroUnit title='Welcome Back'>We missed you.</HeroUnit>
   8.106          <article className='onboarding login'>
   8.107 -          <form>
   8.108 +          <form onSubmit={this.login}>
   8.109              <div>
   8.110                <label htmlFor='emailLoginInput'>Email</label>
   8.111 -              <input id='emailLoginInput' type='email'/>
   8.112 +              <input id='emailLoginInput' type='email' valueLink={this.linkState('email')} disabled={this.state.active} />
   8.113 +              <ValidationError errors={this.state.clientErrors.concat(this.state.serverErrors)} field='/email' outputs={this.emailValidationOutputs} />
   8.114  
   8.115                <label htmlFor='passwordLoginInput'>Passphrase</label>
   8.116 -              <input id='passwordLoginInput' type='password'/>
   8.117 +              <input id='passwordLoginInput' type='password' valueLink={this.linkState('passphrase')} disabled={this.state.active} />
   8.118 +              <ValidationError errors={this.state.clientErrors.concat(this.state.serverErrors)} field='/passphrase' outputs={this.passphraseValidationOutputs} />
   8.119  
   8.120 +              <ValidationError errors={this.state.clientErrors.concat(this.state.serverErrors)} notFields={['/email', '/passphrase']} notHeaders={[]} notParams={[]} outputs={this.catchAllValidationOutputs} />
   8.121 +            </div>
   8.122 +            <div className='actionbuttons'>
   8.123 +              <button onClick={this.onBackClick} disabled={this.state.active} type='button' className='ladda-button'>Back</button>
   8.124 +              <LaddaButton style='expand-right' active={this.state.active}>
   8.125 +                <button type='submit' disabled={this.state.active} className='primary'>Login</button>
   8.126 +              </LaddaButton>
   8.127              </div>
   8.128            </form>
   8.129 -          <div className='actionbuttons'>
   8.130 -            <button onClick={this.onBackClick}>Back</button>
   8.131 -            <LaddaButton style='expand-right' active={this.state.active} progress={this.state.progress}>
   8.132 -              <button onClick={this.toggle} className='primary'>Login</button>
   8.133 -            </LaddaButton>
   8.134 -          </div>
   8.135          </article>
   8.136        </div>
   8.137      )
     9.1 --- a/src/pages/payment.jsx	Tue Jul 07 21:29:19 2015 -0400
     9.2 +++ b/src/pages/payment.jsx	Tue Jul 07 21:39:53 2015 -0400
     9.3 @@ -58,7 +58,12 @@
     9.4      'invalid_value': 'That’s not a valid plan!',
     9.5    },
     9.6  
     9.7 +  userValidationOutputs: {
     9.8 +    'conflict': 'You already have a subscription! Head to your account page to manage it.',
     9.9 +  },
    9.10 +
    9.11    catchAllValidationOutputs: {
    9.12 +    'act_of_god': 'Whoops. Something went wrong. Let support know!',
    9.13    },
    9.14  
    9.15    onScriptLoaded () {
    9.16 @@ -70,12 +75,32 @@
    9.17      this.setState({stripeFailedToLoad: true, errors: [{'error': 'act_of_god'}]})
    9.18    },
    9.19  
    9.20 +  componentDidMount () {
    9.21 +    app.subscriptions.on('error', (moc, xhr, options) => {
    9.22 +      let state = {active: false}
    9.23 +      let resp = {}
    9.24 +      if (xhr && xhr.response) {
    9.25 +        resp = JSON.parse(xhr.response)
    9.26 +      }
    9.27 +      if (resp.errors && resp.errors.length) {
    9.28 +        state.errors = resp.errors
    9.29 +      } else {
    9.30 +        state.errors = [{'error': 'act_of_god'}]
    9.31 +      }
    9.32 +      this.setState(state)
    9.33 +    })
    9.34 +    app.subscriptions.on('sync', (moc, xhr, options) => {
    9.35 +      this.setState({active: false})
    9.36 +      // TODO: do something?
    9.37 +    })
    9.38 +  },
    9.39 +
    9.40    addCard (e) {
    9.41      e.preventDefault()
    9.42      if (this.state.stripeLoading || this.state.stripeFailedToLoad) {
    9.43        return
    9.44      }
    9.45 -    this.setState({active: true})
    9.46 +    this.setState({active: true, errors: []})
    9.47      const t = this
    9.48      const errors = []
    9.49      Stripe.card.createToken({
    9.50 @@ -132,9 +157,13 @@
    9.51            console.log('Error:', response.error.message)
    9.52          }
    9.53        } else {
    9.54 -        console.log('Sending '+response.id+' to server to create customer')
    9.55 +        app.subscriptions.create({
    9.56 +          'user_id': app.me.profileID,
    9.57 +          'plan': t.state.plan,
    9.58 +          'stripe_token': response.id,
    9.59 +          'email': app.me.email,
    9.60 +        })
    9.61        }
    9.62 -      t.setState({active: false, errors: errors})
    9.63      })
    9.64    },
    9.65  
    9.66 @@ -175,11 +204,12 @@
    9.67                <p>There’s no difference between the Supporter and Basic tiers. The Supporter tier is just for people who love Ducky and want to see it grow and improve.</p>
    9.68  
    9.69                <ValidationError errors={this.state.errors} field='/balance' outputs={this.balanceValidationOutputs} />
    9.70 -              <ValidationError errors={this.state.errors} notFields={['/name', '/number', '/cvc', '/expiration', '/plan', '/balance']} notParams={[]} notHeaders={[]} outputs={this.catchAllValidationOutputs} />
    9.71 +              <ValidationError errors={this.state.errors} field='/user_id' outputs={this.userValidationOutputs} />
    9.72 +              <ValidationError errors={this.state.errors} notFields={['/name', '/number', '/cvc', '/expiration', '/plan', '/balance', '/user_id']} notParams={[]} notHeaders={[]} outputs={this.catchAllValidationOutputs} />
    9.73  
    9.74                <div className='actionbuttons'>
    9.75                  <LaddaButton style='expand-right' active={this.state.active}>
    9.76 -                  <button type='submit' className='primary' disabled={this.state.stripeLoading || this.state.stripeFailedToLoad || this.state.active || this.state.errors.length}>Add Card</button>
    9.77 +                  <button type='submit' className='primary' disabled={this.state.stripeLoading || this.state.stripeFailedToLoad || this.state.active }>Add Card</button>
    9.78                  </LaddaButton>
    9.79                </div>
    9.80              </div>
    10.1 --- a/src/pages/register.jsx	Tue Jul 07 21:29:19 2015 -0400
    10.2 +++ b/src/pages/register.jsx	Tue Jul 07 21:39:53 2015 -0400
    10.3 @@ -48,7 +48,7 @@
    10.4  
    10.5    catchAllValidationOutputs: {
    10.6      'act_of_god': 'Hm, something went wrong. Try again? Or let support know.',
    10.7 -    'invalid_form': 'Uh oh, things went really wrong. Let support know you saw the dreaded invalid_format error!',
    10.8 +    'invalid_format': 'Uh oh, things went really wrong. Let support know you saw the dreaded invalid_format error!',
    10.9      'conflict': 'Something went wrong, but trying again will probably fix it.',
   10.10      'access_denied': 'Oops, something’s gone awry. Let support know your client isn’t working.',
   10.11    },
   10.12 @@ -147,7 +147,7 @@
   10.13      })
   10.14      app.me.on('sync', (moc, xhr, options) => {
   10.15        this.setState({active: false})
   10.16 -      app.router.navigate('/register/payment')
   10.17 +      app.router.navigate('/payment')
   10.18      })
   10.19      app.me.on('error', (moc, xhr, options) => {
   10.20        let state = {active: false}
    11.1 --- a/src/router.jsx	Tue Jul 07 21:29:19 2015 -0400
    11.2 +++ b/src/router.jsx	Tue Jul 07 21:39:53 2015 -0400
    11.3 @@ -1,23 +1,29 @@
    11.4  import Router from 'ampersand-router'
    11.5  import React from 'react'
    11.6  import MessagePage from './pages/message'
    11.7 -import OnboardingPage from './pages/onboard'
    11.8 +import HomePage from './pages/home'
    11.9  import RegisterPage from './pages/register'
   11.10  import PaymentMethodPage from './pages/payment'
   11.11  import LoginPage from './pages/login'
   11.12 +import LinksPage from './pages/links'
   11.13  
   11.14  export default Router.extend({
   11.15    routes: {
   11.16      '': 'home',
   11.17      'register': 'register',
   11.18 -    'register/payment': 'payment',
   11.19 +    'payment': 'payment',
   11.20      'login': 'login',
   11.21      'logout': 'logout',
   11.22 +    'links': 'links',
   11.23      '*404': 'fourOhFour'
   11.24    },
   11.25  
   11.26    home () {
   11.27 -    React.render(<OnboardingPage/>, document.body)
   11.28 +    if (app.me && app.me.loggedIn) {
   11.29 +      React.render(<LinksPage/>, document.body)
   11.30 +      return
   11.31 +    }
   11.32 +    React.render(<HomePage/>, document.body)
   11.33    },
   11.34  
   11.35    register () {
   11.36 @@ -32,6 +38,10 @@
   11.37      React.render(<LoginPage/>, document.body)
   11.38    },
   11.39  
   11.40 +  links () {
   11.41 +    React.render(<LinksPage/>, document.body)
   11.42 +  },
   11.43 +
   11.44    logout () {
   11.45      window.localStorage.clear()
   11.46      window.location = '/'
    12.1 --- a/src/styles/_button.scss	Tue Jul 07 21:29:19 2015 -0400
    12.2 +++ b/src/styles/_button.scss	Tue Jul 07 21:39:53 2015 -0400
    12.3 @@ -8,6 +8,9 @@
    12.4  	position: relative;
    12.5  	display: inline-block;
    12.6  	background-color: $base-button-color;
    12.7 +	color: #fff!important;
    12.8 +	padding: 1em 2em;
    12.9 +	border-radius: 5px;
   12.10  
   12.11  	&.primary, &.primary.ladda-button {
   12.12  		background-color: $primary-button-color;
    13.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.2 +++ b/src/styles/header.scss	Tue Jul 07 21:39:53 2015 -0400
    13.3 @@ -0,0 +1,270 @@
    13.4 +@import "bourbon";
    13.5 +@import "neat";
    13.6 +@import "base/base";
    13.7 +.centered-navigation {
    13.8 +	$base-border-radius: 3px !default;
    13.9 +	$large-screen: em(860) !default;
   13.10 +	$base-font-color: #fff;
   13.11 +	$centered-navigation-padding: 1em;
   13.12 +	$centered-navigation-logo-height: 2em;
   13.13 +	$centered-navigation-background: #40526b;
   13.14 +	$centered-navigation-color: transparentize($base-font-color, 0.3);
   13.15 +	$centered-navigation-color-hover: $base-font-color;
   13.16 +	$centered-navigation-height: 60px;
   13.17 +	$centered-navigation-item-padding: 1em;
   13.18 +	$centered-navigation-submenu-padding: 1em;
   13.19 +	$centered-navigation-submenu-width: 12em;
   13.20 +	$centered-navigation-item-nudge: 2.2em;
   13.21 +	$horizontal-bar-mode: $large-screen;
   13.22 +
   13.23 +	background-color: $centered-navigation-background;
   13.24 +	border-bottom: 1px solid darken($centered-navigation-background, 6%);
   13.25 +	min-height: $centered-navigation-height;
   13.26 +	width: 100%;
   13.27 +	z-index: 9999;
   13.28 +
   13.29 +	
   13.30 +// Mobile view
   13.31 +
   13.32 +	.mobile-logo {
   13.33 +		display: inline;
   13.34 +		float: left;
   13.35 +		max-height: $centered-navigation-height;
   13.36 +		padding-left: $centered-navigation-padding;
   13.37 +
   13.38 +		img {
   13.39 +			max-height: $centered-navigation-height;
   13.40 +			opacity: .6;
   13.41 +			padding: .8em 0;
   13.42 +		}
   13.43 +
   13.44 +		@include media($horizontal-bar-mode) {
   13.45 +			display: none;
   13.46 +		}
   13.47 +	}
   13.48 +
   13.49 +	.centered-navigation-mobile-menu {
   13.50 +		color: $centered-navigation-color;
   13.51 +		display: block;
   13.52 +		float: right;
   13.53 +		font-weight: 700;
   13.54 +		line-height: $centered-navigation-height;
   13.55 +		margin: 0;
   13.56 +		padding-right: $centered-navigation-submenu-padding;
   13.57 +		text-decoration: none;
   13.58 +		text-transform: uppercase;
   13.59 +
   13.60 +		@include media ($horizontal-bar-mode) {
   13.61 +			display: none;
   13.62 +		}
   13.63 +
   13.64 +		&:focus,
   13.65 +		&:hover {
   13.66 +			color: $centered-navigation-color-hover;
   13.67 +		}
   13.68 +	}
   13.69 +
   13.70 +	
   13.71 +// Nav menu
   13.72 +
   13.73 +	.centered-navigation-wrapper {
   13.74 +		@include outer-container;
   13.75 +		@include clearfix;
   13.76 +		position: relative;
   13.77 +		z-index: 999;
   13.78 +	}
   13.79 +
   13.80 +	ul.centered-navigation-menu {
   13.81 +		-webkit-transform-style: preserve-3d; 
   13.82 +// stop webkit flicker
   13.83 +		clear: both;
   13.84 +		display: none;
   13.85 +		margin: 0 auto;
   13.86 +		overflow: visible;
   13.87 +		padding: 0;
   13.88 +		width: 100%;
   13.89 +		z-index: 99999;
   13.90 +
   13.91 +		&.show {
   13.92 +			display: block;
   13.93 +		}
   13.94 +
   13.95 +		@include media ($horizontal-bar-mode) {
   13.96 +			display: block;
   13.97 +			text-align: center;
   13.98 +		}
   13.99 +	}
  13.100 +
  13.101 +	
  13.102 +// The nav items
  13.103 +
  13.104 +	.nav-link:first-child {
  13.105 +		@include media($horizontal-bar-mode) {
  13.106 +			margin-left: $centered-navigation-item-nudge;
  13.107 +		}
  13.108 +	}
  13.109 +
  13.110 +	ul li.nav-link {
  13.111 +		background: $centered-navigation-background;
  13.112 +		display: block;
  13.113 +		line-height: $centered-navigation-height;
  13.114 +		overflow: hidden;
  13.115 +		padding-right: $centered-navigation-submenu-padding;
  13.116 +		text-align: right;
  13.117 +		width: 100%;
  13.118 +		z-index: 9999;
  13.119 +
  13.120 +		a {
  13.121 +			color: $centered-navigation-color;
  13.122 +			display: inline-block;
  13.123 +			outline: none;
  13.124 +			text-decoration: none;
  13.125 +
  13.126 +			&:focus,
  13.127 +			&:hover {
  13.128 +				color: $centered-navigation-color-hover;
  13.129 +			}
  13.130 +		}
  13.131 +
  13.132 +		@include media($horizontal-bar-mode) {
  13.133 +			background: transparent;
  13.134 +			display: inline;
  13.135 +			line-height: $centered-navigation-height;
  13.136 +
  13.137 +			a {
  13.138 +				padding-right: $centered-navigation-item-padding;
  13.139 +			}
  13.140 +		}
  13.141 +	}
  13.142 +
  13.143 +	li.logo.nav-link {
  13.144 +		display: none;
  13.145 +		line-height: 0;
  13.146 +
  13.147 +		@include media($large-screen) {
  13.148 +			display: inline;
  13.149 +		}
  13.150 +	}
  13.151 +
  13.152 +	.logo img {
  13.153 +		margin-bottom: -$centered-navigation-logo-height / 3;
  13.154 +		max-height: $centered-navigation-logo-height;
  13.155 +	}
  13.156 +
  13.157 +	
  13.158 +// Sub menus
  13.159 +
  13.160 +	li.more.nav-link {
  13.161 +		padding-right: 0;
  13.162 +
  13.163 +		@include media($large-screen) {
  13.164 +			padding-right: $centered-navigation-submenu-padding;
  13.165 +		}
  13.166 +
  13.167 +		> ul > li:first-child a	{
  13.168 +			padding-top: 1em;
  13.169 +		}
  13.170 +
  13.171 +		a {
  13.172 +			margin-right: $centered-navigation-submenu-padding;
  13.173 +		}
  13.174 +
  13.175 +		> a {
  13.176 +			padding-right: 0.6em;
  13.177 +		}
  13.178 +
  13.179 +		> a:after {
  13.180 +			@include position(absolute, auto -0.4em auto auto);
  13.181 +			color: $centered-navigation-color;
  13.182 +			content: "\25BE";
  13.183 +		}
  13.184 +	}
  13.185 +
  13.186 +	li.more {
  13.187 +		overflow: visible;
  13.188 +		padding-right: 0;
  13.189 +
  13.190 +		a {
  13.191 +			padding-right: $centered-navigation-submenu-padding;
  13.192 +		}
  13.193 +
  13.194 +		> a {
  13.195 +			padding-right: 1.6em;
  13.196 +			position: relative;
  13.197 +
  13.198 +			@include media($large-screen) {
  13.199 +				margin-right: $centered-navigation-submenu-padding;
  13.200 +			}
  13.201 +
  13.202 +			&:after {
  13.203 +				content: "›";
  13.204 +				font-size: 1.2em;
  13.205 +				position: absolute;
  13.206 +				right: $centered-navigation-submenu-padding / 2;
  13.207 +			}
  13.208 +		}
  13.209 +
  13.210 +		&:focus > .submenu,
  13.211 +		&:hover > .submenu {
  13.212 +			display: block;
  13.213 +		}
  13.214 +
  13.215 +		@include media($horizontal-bar-mode) {
  13.216 +			padding-right: $centered-navigation-submenu-padding;
  13.217 +			position: relative;
  13.218 +		}
  13.219 +	}
  13.220 +
  13.221 +	ul.submenu {
  13.222 +		display: none;
  13.223 +		padding-left: 0;
  13.224 +
  13.225 +		@include media($horizontal-bar-mode) {
  13.226 +			left: -$centered-navigation-submenu-padding;
  13.227 +			position: absolute;
  13.228 +			top: 1.5em;
  13.229 +		}
  13.230 +
  13.231 +		.submenu {
  13.232 +			@include media($horizontal-bar-mode) {
  13.233 +				left: $centered-navigation-submenu-width - 0.2em;
  13.234 +				top: 0;
  13.235 +			}
  13.236 +		}
  13.237 +
  13.238 +		li {
  13.239 +			display: block;
  13.240 +			padding-right: 0;
  13.241 +
  13.242 +			@include media($horizontal-bar-mode) {
  13.243 +				line-height: $centered-navigation-height / 1.3;
  13.244 +
  13.245 +				&:first-child > a {
  13.246 +					border-top-left-radius: $base-border-radius;
  13.247 +					border-top-right-radius: $base-border-radius;
  13.248 +				}
  13.249 +
  13.250 +				&:last-child > a {
  13.251 +					border-bottom-left-radius: $base-border-radius;
  13.252 +					border-bottom-right-radius: $base-border-radius;
  13.253 +					padding-bottom: .7em;
  13.254 +				}
  13.255 +			}
  13.256 +
  13.257 +			a {
  13.258 +				background-color: darken($centered-navigation-background, 3%);
  13.259 +				display: inline-block;
  13.260 +				text-align: right;
  13.261 +				text-decoration: none;
  13.262 +				width: 100%;
  13.263 +
  13.264 +				@include media($horizontal-bar-mode) {
  13.265 +					background-color: $centered-navigation-background;
  13.266 +					padding-left: $centered-navigation-submenu-padding;
  13.267 +					text-align: left;
  13.268 +					width: $centered-navigation-submenu-width;
  13.269 +				}
  13.270 +			}
  13.271 +		}
  13.272 +	}
  13.273 +}
    14.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.2 +++ b/src/styles/homepage.scss	Tue Jul 07 21:39:53 2015 -0400
    14.3 @@ -0,0 +1,170 @@
    14.4 +@import "bourbon";
    14.5 +@Import "neat";
    14.6 +@import "base/base";
    14.7 +@import "hero";
    14.8 +@import "button";
    14.9 +
   14.10 +body {
   14.11 +	$base-border-radius: 3px !default;
   14.12 +	$action-color: #477DCA !default;
   14.13 +	$large-screen: em(860) !default;
   14.14 +	$device-padding-vertical: 5em;
   14.15 +	$device-offset-vertical: 10em;
   14.16 +	$device-padding-horizontal: 0.4em;
   14.17 +	$device-screen-width: 14em;
   14.18 +	$device-screen-height: 25em;
   14.19 +	$device-background: darken(gray, 40%);
   14.20 +	$device-backside-background: darken($device-background, 10%);
   14.21 +	$device-text-color: #010101;
   14.22 +	$device-text-background: transparentize(white, .5);
   14.23 +	$device-background-top: #162C4C; 
   14.24 +	$device-background-bottom: #0A120D;
   14.25 +	$gradient-angle: 10deg;
   14.26 +	$device-image: '../img/ps_neutral.png';
   14.27 +	$device-screen-image: 'https://raw.githubusercontent.com/thoughtbot/refills/master/source/images/cosmin_capitanu_screen.jpg';
   14.28 +
   14.29 +	@include background(url($device-image), linear-gradient($gradient-angle, $device-background-bottom, $device-background-top), no-repeat $device-background-top scroll);
   14.30 +	background-repeat: repeat;
   14.31 +	//background-size: cover;
   14.32 +	width: 100%;
   14.33 +
   14.34 +	.device-text {
   14.35 +		color: $device-text-color;
   14.36 +		background-color: $device-text-background;
   14.37 +		padding: 2em;
   14.38 +		margin: 2em;
   14.39 +		border-radius: 5px;
   14.40 +
   14.41 +		@include media($large-screen) {
   14.42 +			float: left;
   14.43 +			max-width: 50%;
   14.44 +		}
   14.45 +
   14.46 +		h4 {
   14.47 +			border-bottom: 1px solid transparentize($device-text-color, 0.7);
   14.48 +			font-size: 1.5em;
   14.49 +			margin: 0 0 0.5em 0;
   14.50 +			padding-bottom: 0.5em;
   14.51 +		}
   14.52 +
   14.53 +		p {
   14.54 +			line-height: 1.5em;
   14.55 +			margin-bottom: 1.5em;
   14.56 +		}
   14.57 +
   14.58 +		.button {
   14.59 +			font-size: 1.5em;
   14.60 +			padding: .75em 2em;
   14.61 +		}
   14.62 +	}
   14.63 +
   14.64 +	.device {
   14.65 +		display: none;
   14.66 +		position: relative;
   14.67 +
   14.68 +		@include media($large-screen) {
   14.69 +			@include transform(
   14.70 +				perspective(800px) 
   14.71 +				translateX(0px) 
   14.72 +				translateY(-100px) 
   14.73 +				translateZ(50px) 
   14.74 +				rotateX(0deg) 
   14.75 +				rotateY(-20deg) 
   14.76 +				rotateZ(-0deg));
   14.77 +			background: $device-background;
   14.78 +			margin-top: $device-offset-vertical;
   14.79 +			border-radius: 2em;
   14.80 +			box-shadow: 
   14.81 +				1px 0px lighten($device-backside-background, 20%), 
   14.82 +				4px 0px lighten($device-backside-background, 2%),
   14.83 +				7px 0px $device-backside-background,
   14.84 +				10px 0px $device-backside-background,
   14.85 +				13px 0px $device-backside-background;
   14.86 +			display: block;
   14.87 +			float: right;
   14.88 +			margin-bottom: -12em;
   14.89 +			margin-right: 5em;
   14.90 +			padding: $device-padding-vertical 0;
   14.91 +			width: $device-screen-width + (2 * $device-padding-horizontal);
   14.92 +			z-index: 99999;
   14.93 +
   14.94 +			.screen {
   14.95 +				@include size($device-screen-width $device-screen-height);
   14.96 +				background-image: url($device-screen-image);
   14.97 +				background-size: cover;
   14.98 +				border-radius: 0.2em;
   14.99 +				box-shadow: inset 0 1px 8px transparentize(black, 0.5);
  14.100 +				margin: auto;
  14.101 +			}
  14.102 +		}
  14.103 +	}
  14.104 +
  14.105 +
  14.106 +	@mixin grid-item-columns($columns) {
  14.107 +		width: (100% / 12) * $columns;
  14.108 +	}
  14.109 +
  14.110 +	.grid-items-lines {
  14.111 +		$base-background-color: transparent;
  14.112 +		$dark-gray: #333 !default;
  14.113 +		$light-gray: #DDD !default;
  14.114 +		$medium-screen: em(640) !default;
  14.115 +		$large-screen: em(860) !default;
  14.116 +		$base-font-color: $dark-gray !default;
  14.117 +		$grid-items-background: $base-background-color;
  14.118 +		$grid-item-background: $base-background-color;
  14.119 +		$grid-item-border: 1px solid transparentize($base-font-color, 0.8); 
  14.120 +		$grid-item-columns: 6;
  14.121 +		$grid-item-big-columns: 8;
  14.122 +		$grid-item-color: $base-font-color;
  14.123 +		$grid-item-height: 14em;
  14.124 +
  14.125 +		@include clearfix;
  14.126 +		position: relative;
  14.127 +		width: 100%;
  14.128 +			
  14.129 +		.grid-item {
  14.130 +			@include transition (all 0.2s ease-in-out);
  14.131 +			background: $grid-item-background;
  14.132 +			float: left;
  14.133 +			overflow: hidden;
  14.134 +			outline: none;
  14.135 +			text-decoration: none;
  14.136 +			width: 100%;
  14.137 +
  14.138 +			@include media($large-screen) {
  14.139 +				@include grid-item-columns($grid-item-columns);
  14.140 +				padding: 2em;
  14.141 +				height: $grid-item-height;
  14.142 +				&.left {
  14.143 +					border-right: $grid-item-border;
  14.144 +				}
  14.145 +				&.right {
  14.146 +					border-left: $grid-item-border;
  14.147 +				}
  14.148 +			}
  14.149 +		}
  14.150 +
  14.151 +		.grid-item img {
  14.152 +				display: block;
  14.153 +				height: 2.5em;
  14.154 +				margin-bottom: 1.2em;
  14.155 +				opacity: 0.2;
  14.156 +		}
  14.157 +
  14.158 +		.grid-item h1 {
  14.159 +				color: $grid-item-color;
  14.160 +				font-size: 1.3em;
  14.161 +				margin-bottom: 0.4em;
  14.162 +		}
  14.163 +
  14.164 +		.grid-item p {
  14.165 +				color: transparentize($grid-item-color, 0.4);
  14.166 +				line-height: 1.5em;
  14.167 +				
  14.168 +				@include media($medium-screen) {
  14.169 +					max-width: 70%;
  14.170 +				}
  14.171 +		}
  14.172 +	}
  14.173 +}
    15.1 --- a/src/styles/onboarding.scss	Tue Jul 07 21:29:19 2015 -0400
    15.2 +++ b/src/styles/onboarding.scss	Tue Jul 07 21:39:53 2015 -0400
    15.3 @@ -13,6 +13,7 @@
    15.4  	max-width: $content-max-width;
    15.5  	width: $content-width;
    15.6  	margin: 0px auto;
    15.7 +	background-color: transparentize(white, .5);
    15.8  
    15.9  	ul, ol {
   15.10  		list-style-type: square;