Repository: caljrimmer/portfolio-redux-app Branch: master Commit: 1c5297756eb1 Files: 39 Total size: 56.7 KB Directory structure: gitextract_hpkv_v1h/ ├── .gitignore ├── README.md ├── package.json ├── src/ │ ├── client/ │ │ └── index.js │ ├── common/ │ │ ├── actions/ │ │ │ ├── about.js │ │ │ └── layout.js │ │ ├── api/ │ │ │ ├── fetchComponentDataBeforeRender.js │ │ │ ├── portfolio.js │ │ │ └── promiseMiddleware.js │ │ ├── components/ │ │ │ ├── 404.js │ │ │ ├── About.js │ │ │ ├── Home.js │ │ │ ├── Portfolio.js │ │ │ ├── Services.js │ │ │ ├── about/ │ │ │ │ └── Repos.js │ │ │ └── layout/ │ │ │ ├── Banner.js │ │ │ ├── Header.js │ │ │ ├── Loader.js │ │ │ └── Sidebar.js │ │ ├── containers/ │ │ │ ├── AboutPage.js │ │ │ ├── App.js │ │ │ └── HomePage.js │ │ ├── reducers/ │ │ │ ├── about.js │ │ │ ├── index.js │ │ │ └── layout.js │ │ ├── routes.js │ │ └── store/ │ │ └── configureStore.js │ └── server/ │ ├── devtools.js │ ├── index.js │ ├── server.js │ └── webpack.js ├── styles/ │ ├── about.css │ ├── base.css │ ├── custom.css │ ├── index.css │ ├── layout.css │ ├── portfolio.css │ └── services.css └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ node_modules npm-debug.log lib .DS_Store dist ================================================ FILE: README.md ================================================ # Portfolio Redux App A Portfolio example of an isomorphic rendering application in React and Redux. [Live Example Here](http://www.callumrimmer.co.uk) ## Development Installation In the project's directory, run the following commands: ``` $ npm install $ npm start ``` Then Visit ``` http://localhost:3002 ``` ## Releasing to Production Production has Devtools, logging and hot reloading middleware removed and the scripts/css compressed. In the project's directory, run the following commands: ``` $ npm run build $ npm run start-prod ``` Then Visit ``` http://localhost:3002 ``` ## Other Boilerplate code [General Redux App (with Undo)](https://github.com/caljrimmer/isomorphic-redux-app) ## Credit App template was based on [Lanyon Theme](https://github.com/poole/lanyon) by [mdo](https://github.com/mdo) ================================================ FILE: package.json ================================================ { "name": "portfolio-redux-app", "version": "0.0.1", "description": "A portfolio template built with Redux and React", "scripts": { "slate": "rm -rf node_modules && npm install", "clean": "rm -rf dist", "start": "node src/server/index.js --progress --colors --profile", "build": "npm run clean && NODE_ENV=production webpack -p --progress --colors --profile", "postinstall": "npm run build" }, "repository": { "type": "git", "url": "git+https://github.com/caljrimmer/portfolio-redux-app.git" }, "license": "MIT", "dependencies": { "express": "^4.13.3", "react": "^0.13.3", "react-router": "^1.0.0-rc1", "redux-router": "^1.0.0-beta3", "react-redux": "^2.1.2", "redux": "^3.0.0", "redux-thunk": "^0.1.0", "history": "1.13.0", "classnames" : "^2.1.5", "axios" : "^0.7.0" }, "devDependencies": { "babel": "^5.8.21", "babel-core": "^5.8.22", "babel-loader": "^5.3.2", "redux-devtools": "^2.1.5", "redux-logger" : "^2.0.2", "babel-plugin-react-transform": "^1.1.0", "babel-runtime": "^5.8.20", "webpack": "^1.11.0", "style-loader": "^0.8.0", "css-loader": "^0.9.0", "url-loader": "^0.5.6", "file-loader": "0.8.5", "react-transform-hmr": "^1.0.0", "webpack-dev-middleware": "^1.2.0", "webpack-hot-middleware": "^2.2.0", "merge":"^1.2.0", "extract-text-webpack-plugin" : "^0.8.2" } } ================================================ FILE: src/client/index.js ================================================ import 'babel-core/polyfill'; import React from 'react'; import { Router } from 'react-router'; import { Provider } from 'react-redux'; import { ReduxRouter } from 'redux-router'; import createBrowserHistory from 'history/lib/createBrowserHistory' import configureStore from '../common/store/configureStore'; import routes from '../common/routes'; import "../../styles/index.css"; const history = createBrowserHistory(); const initialState = window.__INITIAL_STATE__; const store = configureStore(initialState); const rootElement = document.getElementById('root'); React.render( {() => } , document.getElementById('root') ); if (process.env.NODE_ENV !== 'production') { require('../server/devtools')(store); } ================================================ FILE: src/common/actions/about.js ================================================ import request from 'axios'; export const INVALIDATE_REPOS = 'INVALIDATE_REPOS'; export const REPOS_GET = 'REPOS_GET'; export const REPOS_GET_REQUEST = 'REPOS_GET_REQUEST'; export const REPOS_GET_SUCCESS = 'REPOS_GET_SUCCESS'; export const REPOS_GET_FAILURE = 'REPOS_GET_FAILURE'; export function invalidateRepos(repos) { return { type: INVALIDATE_REPOS, repos }; } export function fetchRepos() { return { type: REPOS_GET, promise: request.get(`https://api.github.com/users/caljrimmer/repos`) } } function shouldFetchRepos(state) { const repos = state.repos.results; if (!repos.length) { return true; } else if (repos.isFetching) { return false; } else { return repos.didInvalidate; } } export function fetchReposIfNeeded() { return (dispatch, getState) => { if (shouldFetchRepos(getState())) { return dispatch(fetchRepos()); } }; } ================================================ FILE: src/common/actions/layout.js ================================================ export const TOGGLE_SIDEBAR = 'TOGGLE_SIDEBAR'; export function toggleSidebar(value) { return { type: TOGGLE_SIDEBAR, value : value }; } ================================================ FILE: src/common/api/fetchComponentDataBeforeRender.js ================================================ /** * This looks at static needs parameter in components and waits for the promise to be fullfilled * It is used to make sure server side rendered pages wait for APIs to resolve before returning res.end() */ export function fetchComponentDataBeforeRender(dispatch, components, params) { const needs = components.reduce( (prev, current) => { return (current.need || []) .concat((current.WrappedComponent ? current.WrappedComponent.need : []) || []) .concat(prev); }, []); const promises = needs.map(need => dispatch(need())); return Promise.all(promises); } ================================================ FILE: src/common/api/portfolio.js ================================================ export function getPortfolio() { return [ { link : 'http://www.goldmansachs.com', title : 'Goldman Sachs', classname : 'goldmans', roles : [ { title : 'Front End Developer', skills : 'React, Backbone, D3' }, { title : 'Back End Developer', skills : 'Node, MongoDB, Webpack' } ] }, { link : 'http://www.libon.com/', title : 'Orange (Libon)', classname : 'orange', roles : [ { title : 'Front End Developer', skills : 'Backbone, WebRTC, FireFoxOS' }, { title : 'Back End Developer', skills : 'Node, MongoDB, Grunt' } ] }, { link : 'http://www.pwul.net', title : 'Pay What You Like', classname : 'pwul', roles : [ { title : 'Front End Developer', skills : 'React, Backbone, D3' }, { title : 'Back End Developer', skills : 'Node, MongoDB, Gulp' } ] }, { link : 'http://www.timeout.com/london/card', title : 'Timeout', classname : 'timeout', roles : [ { title : 'Front End Developer', skills : 'Backbone, CSS3, HTML5' } ] }, { link : 'http://www.tesco.com/direct', title : 'Tesco Entertainment', classname : 'tesco', roles : [ { title : 'Front End Developer', skills : 'Backbone, CSS3, HTML5' } ] }, { link : 'http://onlinelibrary.wiley.com', title : 'John Wiley and Sons', classname : 'wiley', roles : [ { title : 'Front End Developer', skills : 'Backbone, CSS3, HTML5' } ] }, { link : 'http://www.covestor.com', title : 'Covestor', classname : 'covestor', roles : [ { title : 'Front End Developer', skills : 'OO Javascript, CSS3, HTML5' } ] }, { link : 'http://www.shipserv.com', title : 'ShipServ', classname : 'shipserv', roles : [ { title : 'Front End Developer', skills : 'OO Javascript, CSS3, HTML5' } ] } ] } ================================================ FILE: src/common/api/promiseMiddleware.js ================================================ export default function promiseMiddleware() { return next => action => { const { promise, type, ...rest } = action; if (!promise) return next(action); const SUCCESS = type + '_SUCCESS'; const REQUEST = type + '_REQUEST'; const FAILURE = type + '_FAILURE'; next({ ...rest, type: REQUEST }); return promise .then(req => { next({ ...rest, req, type: SUCCESS }); return true; }) .catch(error => { next({ ...rest, error, type: FAILURE }); console.log(error); return false; }); }; } ================================================ FILE: src/common/components/404.js ================================================ import React, { Component } from 'react'; import { Link } from 'react-router'; class Error404 extends Component { render() { return (

404: Page not found

Sorry, we've misplaced that URL or it's pointing to something that does not exist.

> Head back home

); } } export default Error404; ================================================ FILE: src/common/components/About.js ================================================ import React, { Component, PropTypes } from 'react'; import { connect } from 'react-redux'; import Repos from './about/Repos'; import Loader from './layout/Loader'; import Banner from './layout/Banner'; class About extends Component { constructor(props) { super(props); } componentDidMount() { this.props.fetchReposIfNeeded(); } render () { const { results, isFetching, lastUpdated, error } = this.props; return (

About Me

I have been developing web sites for over 15 years. Over the years, I have adopted, mastered and moved on from many languages, frameworks and architectures. I currently focus on Javascript heavy web application development.

I created my first start-up in 2004 which raised £30K pounds investment which sold custom built software to companies like News International, NHS and Guardian.

In 2007, I was the CTO of My Football Club. MyFC crowdsourced over £1.5 Million to buy a British football club. In the following year, we got to Wembley and won the FA Trophy.

Over the years, I have developed web apps for start-ups (Covestor, iSubscribe, Shipserv), for multi-national bluechip companies (Goldman Sachs, Tesco, Timeout, John Wiley and Sons) and created my own companies (PWUL, Muffle, Pencil Training)

About Site

This site is a single page web app built with React and Redux. It is Isomorphic (all the code renders on the server and well as the browser) which has the advantage of initially rendering quicker and being indexed by search engines.

I built the site as a simple example of what can be built with React and Redux. You can get the code base forfrom my github repo.

Feel free to use this sites code for whatever you want. I hope it inspires you to build something awesome or learn something new

My GitHub Repos

{isFetching && results.length === 0 && } {!isFetching && error && results.length === 0 &&

There has been an Error

} {!isFetching && !error && results.length === 0 &&

Empty

} {results.length > 0 &&
}
); } } About.propTypes = { results: PropTypes.array.isRequired, error: PropTypes.object.isRequired, isFetching: PropTypes.bool.isRequired }; export default About; ================================================ FILE: src/common/components/Home.js ================================================ import React, { Component } from 'react'; import Banner from './layout/Banner'; class Home extends Component { constructor(props){ super(props); this.eventToggleSidebar = this.eventToggleSidebar.bind(this) } eventToggleSidebar(e) { e.preventDefault(); this.props.toggleSidebar(!this.props.layout.sidebarOpen); } render() { return (

I build scalable, maintainable and secure enterprise web applications.

for agencies, bluechips, start-ups and sometimes, myself. Find out More

Technologies I build with:

Client Side JS

  • Backbone
  • **React
    • *Redux
    • Flux
  • D3

Server Side JS

  • MongoDB
  • *Node
    • *Express
    • Hapi

Testing

  • Jasmine
  • Karma
  • Protractor
  • Jest

Deployment

  • *Nginx
  • *Webpack
  • Gulp
  • Grunt
* This site is built with these technologies. View the github repo here.
** Yep, I know React can be used on the server side too. This site is Isomorphical rendered.

Companies I have worked with:

); } } export default Home; ================================================ FILE: src/common/components/Portfolio.js ================================================ import React, { Component } from 'react'; import { getPortfolio } from '../api/portfolio'; import classNames from 'classnames'; class Portfolio extends Component { render() { const portfolio = getPortfolio(); const RoleRows = (roles) => { return roles.map((role) => { return (

Role

{role.title}
{role.skills}

) }); } const PortfolioRows = portfolio.map((row) => { const classname = classNames('portfolio_item','clearfix',row.classname); return (

(visit site) {row.title}

{RoleRows(row.roles)}
) }); return (
{PortfolioRows}
); } } export default Portfolio; ================================================ FILE: src/common/components/Services.js ================================================ import React, { Component, PropTypes } from 'react'; import Banner from './layout/Banner'; class Services extends Component { render () { return (

Prototypes/MVPs

I can build you a prototype or a minimal viable product (MVP)for your idea. An MVP can validate your idea and help you raise funding.

The code I deliver will be the best possible foundations from which your application and idea can grow.

Training

I can help you or your employees develop their web development coding skills. I have been teaching code to both beginners and professionals for many years.

I have my own training company called Pencil Training. More details can be found here.

Enterprise Web Apps

Whether you are a Start-up or Bluechip company, I can help you build you a production ready web application.

The web application will be built with the best technologies and will be scalable, secure and maintainable.

Hybridised Development

Do you need a mobile app and web app with the same code base? I can code your web application so that it utilises Phonegap/Cordova to build both a web application and a cross-device mobile application.

); } } export default Services; ================================================ FILE: src/common/components/about/Repos.js ================================================ import React, { PropTypes, Component } from 'react'; export default class Repos extends Component { render () { return (
{this.props.results.map((repo, i) =>
{repo.name}
{repo.description}
)}
); } } Repos.propTypes = { results: PropTypes.array.isRequired }; ================================================ FILE: src/common/components/layout/Banner.js ================================================ import React, { Component } from 'react'; class Banner extends Component { render() { return (

Contact me at callum(at)deadtrendy.co.uk or call 07919 411 405

); } } export default Banner; ================================================ FILE: src/common/components/layout/Header.js ================================================ import React, { Component } from 'react'; class Header extends Component { render() { return (

Callum Rimmer Full Stack Web Developer based in London

); } } export default Header; ================================================ FILE: src/common/components/layout/Loader.js ================================================ import React, { Component } from 'react'; class Loader extends Component { render() { return (
); } } export default Loader; ================================================ FILE: src/common/components/layout/Sidebar.js ================================================ import React, { Component } from 'react'; import { Link } from 'react-router'; import classNames from 'classnames'; class Sidebar extends Component { constructor(props){ super(props); this.eventCloseSidebar = this.eventCloseSidebar.bind(this) } eventCloseSidebar (e) { this.props.toggleSidebar(!this.props.layout.sidebarOpen); } render() { return (

I built this site with Redux and React. You can get the source code here

Visit My GitHub Repo
Visit My Linkedin
Visit My Twitter

Design based on Lanyon Theme

); } } export default Sidebar; ================================================ FILE: src/common/containers/AboutPage.js ================================================ import { bindActionCreators } from 'redux'; import React, { Component} from 'react'; import { connect } from 'react-redux'; import About from '../components/About'; import * as AboutActions from '../actions/about'; //Data that needs to be called before rendering the component //This is used for server side rending via the fetchComponentDataBeforeRending() method About.need = [ AboutActions.fetchRepos ] function mapStateToProps(state) { const { isFetching, lastUpdated, error, results } = state.repos || { isFetching: true, error:false, results: [] }; return { isFetching, lastUpdated, error, results }; } function mapDispatchToProps(dispatch) { return bindActionCreators(AboutActions, dispatch); } export default connect(mapStateToProps,mapDispatchToProps)(About); ================================================ FILE: src/common/containers/App.js ================================================ import React, { Component } from 'react'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { Link } from 'react-router'; import classNames from 'classnames'; import * as LayoutActions from '../actions/layout'; import Home from '../components/Home' import Header from '../components/layout/Header' import Sidebar from '../components/layout/Sidebar' class App extends Component { constructor(props){ super(props); this.eventToggleSidebar = this.eventToggleSidebar.bind(this) } eventToggleSidebar(e) { e.preventDefault(); this.props.toggleSidebar(!this.props.layout.sidebarOpen); } render() { const { layout, toggleSidebar } = this.props; const { sidebarOpen } = layout; const layoutClass = classNames({open : sidebarOpen}); return (
{!this.props.children && } {this.props.children}
); } } function mapStateToProps(state) { return { layout : state.layout }; } function mapDispatchToProps(dispatch) { return bindActionCreators(LayoutActions,dispatch); } export default connect(mapStateToProps, mapDispatchToProps)(App); ================================================ FILE: src/common/containers/HomePage.js ================================================ import { bindActionCreators } from 'redux'; import React, { Component} from 'react'; import { connect } from 'react-redux'; import Home from '../components/Home'; import * as LayoutActions from '../actions/layout'; function mapStateToProps(state) { return { layout : state.layout } } function mapDispatchToProps(dispatch) { return bindActionCreators(LayoutActions, dispatch); } export default connect(mapStateToProps,mapDispatchToProps)(Home); ================================================ FILE: src/common/reducers/about.js ================================================ import { INVALIDATE_REPOS, REPOS_GET_REQUEST, REPOS_GET_SUCCESS, REPOS_GET_FAILURE } from '../actions/about'; export function reposByUser(state = { error: {}, isFetching: false, didInvalidate: false, results: [] }, action) { switch (action.type) { case INVALIDATE_REPOS: return Object.assign({}, state, { didInvalidate: true }); case REPOS_GET_REQUEST: return Object.assign({}, state, { isFetching: true, didInvalidate: false, }); case REPOS_GET_SUCCESS: let data = []; if(action.req && action.req.data){ data = action.req.data.sort((a,b) => { return new Date(b.pushed_at) - new Date(a.pushed_at); }); } return Object.assign({}, state, { isFetching: false, results: data, lastUpdated: new Date() }); case REPOS_GET_FAILURE: return Object.assign({}, state, { isFetching: false, error : { status: action.error.status, statusText : action.error.statusText } }); default: return state; } } ================================================ FILE: src/common/reducers/index.js ================================================ import { combineReducers } from 'redux'; import { routerStateReducer } from 'redux-router'; import layout from './layout'; import { reposByUser } from './about'; const rootReducer = combineReducers({ layout : layout, repos : reposByUser, router : routerStateReducer }); export default rootReducer; ================================================ FILE: src/common/reducers/layout.js ================================================ import { TOGGLE_SIDEBAR } from '../actions/layout'; export default function layout(state = {sidebarOpen: false}, action) { switch (action.type) { case TOGGLE_SIDEBAR: return { sidebarOpen : action.value }; default: return state; } } ================================================ FILE: src/common/routes.js ================================================ import { Route } from "react-router"; import React from "react"; import App from "./containers/App"; //Redux Smart import AboutPage from "./containers/AboutPage"; import HomePage from "./containers/HomePage"; //Redux Dumb import PortfolioPage from "./components/Portfolio"; import ServicesPage from "./components/Services"; import error404 from "./components/404"; export default ( ); ================================================ FILE: src/common/store/configureStore.js ================================================ import { createStore, applyMiddleware, compose } from 'redux'; import { devTools } from 'redux-devtools'; import { reduxReactRouter } from 'redux-router'; import thunk from 'redux-thunk'; import createHistory from 'history/lib/createBrowserHistory'; import createLogger from 'redux-logger'; import promiseMiddleware from '../api/promiseMiddleware'; import rootReducer from '../reducers'; const middlewareBuilder = () => { let middleware = {}; let universalMiddleware = [thunk,promiseMiddleware]; let allComposeElements = []; if(process.browser){ if(process.env.NODE_ENV === 'production'){ middleware = applyMiddleware(...universalMiddleware); allComposeElements = [ middleware, reduxReactRouter({ createHistory }) ] }else{ middleware = applyMiddleware(...universalMiddleware,createLogger()); allComposeElements = [ middleware, reduxReactRouter({ createHistory }), devTools() ] } }else{ middleware = applyMiddleware(...universalMiddleware); allComposeElements = [ middleware ] } return allComposeElements; } const finalCreateStore = compose(...middlewareBuilder())(createStore); export default function configureStore(initialState) { const store = finalCreateStore(rootReducer, initialState); if (module.hot) { // Enable Webpack hot module replacement for reducers module.hot.accept('../reducers', () => { const nextRootReducer = require('../reducers'); store.replaceReducer(nextRootReducer); }); } return store; } ================================================ FILE: src/server/devtools.js ================================================ import React from 'react'; import { DevTools, DebugPanel, LogMonitor } from 'redux-devtools/lib/react'; /* * Puts Redux DevTools into a separate window. * Based on https://gist.github.com/tlrobinson/1e63d15d3e5f33410ef7#gistcomment-1560218. */ export default function createDevToolsWindow(store) { // Give it a name so it reuses the same window const name = 'Redux DevTools'; const win = window.open( null, name, 'menubar=no,location=no,resizable=yes,scrollbars=no,status=no,width=450,height=600' ); if (!win) { console.error( // eslint-disable-line no-console 'Couldn\'t open Redux DevTools due to a popup blocker. ' + 'Please disable the popup blocker for the current page.' ); return; } // Reload in case it's reusing the same window with the old content. win.location.reload(); // Set visible Window title. win.document.title = name; // Wait a little bit for it to reload, then render. setTimeout(() => React.render( , win.document.body.appendChild(document.createElement('div')) ), 10); } ================================================ FILE: src/server/index.js ================================================ require('babel/register'); require('./server'); ================================================ FILE: src/server/server.js ================================================ import express from 'express'; import webpack from 'webpack'; import webpackConfig from '../../webpack.config'; import webpackDevMiddleware from 'webpack-dev-middleware'; import webpackHotMiddleware from 'webpack-hot-middleware'; import React from 'react'; import { RoutingContext, match } from 'react-router'; import { Provider } from 'react-redux'; import createLocation from 'history/lib/createLocation'; import { fetchComponentDataBeforeRender } from '../common/api/fetchComponentDataBeforeRender'; import configureStore from '../common/store/configureStore'; import routes from '../common/routes'; import packagejson from '../../package.json'; const app = express(); const renderFullPage = (html, initialState) => { return ` Full Stack Web Developer based in London
${html}
`; } if(process.env.NODE_ENV !== 'production'){ const compiler = webpack(webpackConfig); app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: webpackConfig.output.publicPath })); app.use(webpackHotMiddleware(compiler)); }else{ app.use('/static', express.static(__dirname + '/../../dist')); } app.get('/*', function (req, res) { const location = createLocation(req.url); match({ routes, location }, (err, redirectLocation, renderProps) => { if(err) { console.error(err); return res.status(500).end('Internal server error'); } if(!renderProps) return res.status(404).end('Not found'); const store = configureStore(); const InitialView = ( {() => } ); //This method waits for all render component promises to resolve before returning to browser fetchComponentDataBeforeRender(store.dispatch, renderProps.components, renderProps.params) .then(html => { const componentHTML = React.renderToString(InitialView); const initialState = store.getState(); res.status(200).end(renderFullPage(componentHTML,initialState)) }) .catch(err => { console.log(err) res.end(renderFullPage("",{})) }); }); }); const server = app.listen(process.env.PORT || 3002, function () { const host = server.address().address; const port = server.address().port; console.log('Example app listening at http://%s:%s', host, port); }); ================================================ FILE: src/server/webpack.js ================================================ // Webpack dev server // Ran in parallel with the Express server import WebpackDevServer from "webpack-dev-server"; import webpack from "webpack"; import config from "../../webpack.config.dev"; var server = new WebpackDevServer(webpack(config), { // webpack-dev-server options publicPath: config.output.publicPath, hot: true, stats: { colors: true }, }); server.listen(8080, "localhost", function() {}); ================================================ FILE: styles/about.css ================================================ .repos { width:46%; float:right; } .about, .services { width:50%; float:left; font-size:16px;} .repos h3 { margin-bottom:20px; } .repo-item { background:#fff; padding:10px; box-shadow:1px 1px 1px #eaeaea; border:1px solid #eaeaea; margin-bottom:10px; font-size:16px; } @media (max-width: 40em) { .about, .repos { width:100%; } } ================================================ FILE: styles/base.css ================================================ /* * Contents * * Body resets * Custom type * Messages * Container * Masthead * Posts and pages * Pagination * Reverse layout * Themes */ /* * Body resets * * Update the foundational and global aspects of the page. */ * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } html, body { margin: 0; padding: 0; } html { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.5; } @media (min-width: 38em) { html { font-size: 20px; } } body { color: #515151; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } /* No `:visited` state is required by default (browsers will use `a`) */ a { color: #1db7a9; text-decoration: none; } a strong { color: inherit; } /* `:focus` is linked to `:hover` for basic accessibility */ a:hover, a:focus { text-decoration: underline; } /* Headings */ h1, h2, h3, h4, h5, h6 { margin-bottom: .5rem; font-weight: bold; line-height: 1.25; color: #313131; text-rendering: optimizeLegibility; } h1 { font-size: 2.6rem; line-height: 3.6rem; } h2 { margin-top: 1rem; font-size: 1.5rem; } h3 { margin-top: 1.5rem; font-size: 1.25rem; } h4, h5, h6 { margin-top: 1rem; font-size: 1rem; } /* Body text */ p { margin-top: 0; margin-bottom: 1rem; } p em { font-weight:bold; color:#000; font-size:1.2em; } strong { color: #303030; } /* Lists */ ul, ol, dl { margin-top: 0; margin-bottom: 1rem; } dt { font-weight: bold; } dd { margin-bottom: .5rem; } /* Misc */ hr { position: relative; margin: 1.5rem 0; border: 0; border-top: 1px solid #eee; border-bottom: 1px solid #fff; } abbr { font-size: 85%; font-weight: bold; color: #555; text-transform: uppercase; } abbr[title] { cursor: help; border-bottom: 1px dotted #e5e5e5; } /* Code */ code, pre { font-family: Menlo, Monaco, "Courier New", monospace; } code { padding: .25em .5em; font-size: 85%; color: #bf616a; background-color: #f9f9f9; border-radius: 3px; } pre { display: block; margin-top: 0; margin-bottom: 1rem; padding: 1rem; font-size: .8rem; line-height: 1.4; white-space: pre; white-space: pre-wrap; word-break: break-all; word-wrap: break-word; background-color: #f9f9f9; } pre code { padding: 0; font-size: 100%; color: inherit; background-color: transparent; } /* Pygments via Jekyll */ .highlight { margin-bottom: 1rem; border-radius: 4px; } .highlight pre { margin-bottom: 0; } /* Gist via GitHub Pages */ .gist .gist-file { font-family: Menlo, Monaco, "Courier New", monospace !important; } .gist .markdown-body { padding: 15px; } .gist pre { padding: 0; background-color: transparent; } .gist .gist-file .gist-data { font-size: .8rem !important; line-height: 1.4; } .gist code { padding: 0; color: inherit; background-color: transparent; border-radius: 0; } /* Quotes */ blockquote { padding: .5rem 1rem; margin: .8rem 0; color: #7a7a7a; border-left: .25rem solid #e5e5e5; } blockquote p:last-child { margin-bottom: 0; } @media (min-width: 30em) { blockquote { padding-right: 5rem; padding-left: 1.25rem; } } img { display: block; max-width: 100%; margin: 0 0 1rem; border-radius: 5px; } /* Tables */ table { margin-bottom: 1rem; width: 100%; border: 1px solid #e5e5e5; border-collapse: collapse; } td, th { padding: .25rem .5rem; border: 1px solid #e5e5e5; } tbody tr:nth-child(odd) td, tbody tr:nth-child(odd) th { background-color: #f9f9f9; } /* * Custom type * * Extend paragraphs with `.lead` for larger introductory text. */ .lead { font-size: 1.25rem; font-weight: 300; } /* * Messages * * Show alert messages to users. You may add it to single elements like a `

`, * or to a parent if there are multiple elements to show. */ .message { margin-bottom: 1rem; padding: 1rem; color: #717171; background-color: #f9f9f9; } /* * Container * * Center the page content. */ .container { max-width: 38rem; padding-left: 1rem; padding-right: 1rem; margin-left: auto; margin-right: auto; } /* * Masthead * * Super small header above the content for site name and short description. */ .masthead { padding-top: 1rem; padding-bottom: 1rem; margin-bottom: 3rem; } .masthead-title { margin-top: 0; margin-bottom: 0; color: #505050; } .masthead-title a { color: #505050; } .masthead-title small { font-size: 75%; font-weight: 400; color: #c0c0c0; letter-spacing: 0; } /* * Posts and pages * * Each post is wrapped in `.post` and is used on default and post layouts. Each * page is wrapped in `.page` and is only used on the page layout. */ .page, .post { margin-bottom: 4em; } /* Blog post or page title */ .page-title, .post-title, .post-title a { color: #303030; } .page-title, .post-title { margin-top: 0; } /* Meta data line below post title */ .post-date { display: block; margin-top: -.5rem; margin-bottom: 1rem; color: #9a9a9a; } /* Related posts */ .related { padding-top: 2rem; padding-bottom: 2rem; border-top: 1px solid #eee; } .related-posts { padding-left: 0; list-style: none; } .related-posts h3 { margin-top: 0; } .related-posts li small { font-size: 75%; color: #999; } .related-posts li a:hover { color: #268bd2; text-decoration: none; } .related-posts li a:hover small { color: inherit; } /* * Pagination * * Super lightweight (HTML-wise) blog pagination. `span`s are provide for when * there are no more previous or next posts to show. */ .pagination { overflow: hidden; /* clearfix */ margin-left: -1rem; margin-right: -1rem; font-family: "PT Sans", Helvetica, Arial, sans-serif; color: #ccc; text-align: center; } /* Pagination items can be `span`s or `a`s */ .pagination-item { display: block; padding: 1rem; border: 1px solid #eee; } .pagination-item:first-child { margin-bottom: -1px; } /* Only provide a hover state for linked pagination items */ a.pagination-item:hover { background-color: #f5f5f5; } @media (min-width: 30em) { .pagination { margin: 3rem 0; } .pagination-item { float: left; width: 50%; } .pagination-item:first-child { margin-bottom: 0; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } .pagination-item:last-child { margin-left: -1px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } } .clearfix:after { visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } .clearfix { display: inline-block; } /* start commented backslash hack \*/ * html .clearfix { height: 1%; } .clearfix { display: block; } /* close commented backslash hack */ ================================================ FILE: styles/custom.css ================================================ .sidebar-item p b { color:#fff; } /** * Loader */ @keyframes pulse { 0% { opacity: 1; } 100% { opacity: 0; } } body { margin: 0; } .loader { display: flex; top: 0; right: 0; bottom: 0; left: 0; } .ldr { display: flex; flex-direction: row; flex-wrap: wrap; justify-content: space-around; align-items: center; margin: 50px auto 50px; width: 2.5em; height: 2.5em; } .ldr-blk { height: 35%; width: 35%; animation: pulse 0.75s ease-in infinite alternate; background-color: #1db7a9; } .an_delay { animation-delay: 0.75s; } /** * Clients on Home Page */ ul.clients { margin:0; padding:0; } ul.clients li { list-style:none; float:left; margin:1em 1.6em 1em 0em; height:70px; text-indent:-5000px; box-shadow:1px 1px 1px #ccc; } ul.clients li.goldmans-logo { background: url('assets/goldmans-logo.jpg') no-repeat; width:124px; } ul.clients li.orange-logo { background: url('assets/orange-logo.jpg') no-repeat; width:70px; } ul.clients li.tesco-logo { background: url('assets/tesco-logo.jpg') no-repeat; width:181px; } ul.clients li.timeout-logo { background: url('assets/timeout-logo.jpg') no-repeat; width:151px; } ul.clients li.wiley-logo { background: url('assets/wiley-logo.jpg') no-repeat; width:169px; } ul.clients li.covestor-logo { background: url('assets/covestor-logo.jpg') no-repeat; width:218px; } ul.clients li.shipserv-logo { background: url('assets/shipserv-logo.jpg') no-repeat; width:218px; } /** * Skill Boxes on Home Page */ .skill-item { float:left; margin: 0 3.5em 2em 0; font-size:0.8em; } .skill-item h4 { font-size:20px; font-weight:bold; } .posts em b { font-weight:bold; margin-right:5px; margin-left:-5px; text-shadow:1px 1px 1px #666; } .skill-item ul{ list-style:none; padding-left:0; font-size:16px; } .skill-item ul li { display:inline-block; float:left; clear:both; } .skill-item ul ul { padding-left:2em; } .skill-item ul li em { background: #1db7a9; display:inline-block; float:left; color:#fff; padding:0.2em 1em 0.2em; margin:0.2em; clear:both; font-style:normal; box-shadow:1px 1px 1px #ccc; } .skill-item ul ul li em { background: #d9ead3; color: #1db7a9; } .exclaimation { clear:both; font-size:14px; } /** * Banner */ .contact-banner { background:#1db7a9; color:#d9ead3; text-align:center; } .contact-banner p { padding:20px; } .contact-banner p em { color:#fff; font-weight:bold; font-style:normal; } ================================================ FILE: styles/index.css ================================================ @import 'https://fonts.googleapis.com/css?family=PT+Serif:400,400italic,700%7CPT+Sans:400'; @import 'base.css'; @import 'portfolio.css'; @import 'about.css'; @import 'services.css'; @import 'layout.css'; @import 'custom.css'; ================================================ FILE: styles/layout.css ================================================ /* * Contents * * Global resets * Masthead * Sidebar * Slide effect * Posts and pages * Pagination * Reverse layout * Themes */ /* * Global resets * * Update the foundational and global aspects of the page. */ /* Prevent scroll on narrow devices */ html, body { overflow-x: hidden; } html { font-family: "PT Sans",Helvetica,Arial,sans-serif; background:#fafafa; } h1, h2, h3, h4, h5, h6 { font-family: "PT Sans", Helvetica, Arial, sans-serif; font-weight: 400; color: #313131; letter-spacing: -.025rem; } /* * Wrapper * * The wrapper is used to position site content when the sidebar is toggled. We * use an outter wrap to position the sidebar without interferring with the * regular page content. */ .wrap { position: relative; width: 100%; } /* * Container * * Center the page content. */ .container { max-width: 28rem; } @media (min-width: 38em) { .container { max-width: 32rem; } } @media (min-width: 56em) { .container { max-width: 38rem; } } /* * Masthead * * Super small header above the content for site name and short description. */ .masthead { padding-top: 1rem; padding-bottom: 1rem; margin-bottom: 3rem; border-bottom: 1px solid #eee; background:#fff; } .masthead-title { margin-top: 0; margin-bottom: 0; color: #505050; } .masthead-title a { color: #505050; font-weight:bold; } .masthead-title small { font-size: 75%; font-weight: 400; color: #c0c0c0; letter-spacing: 0; margin-left:0.6em; } @media (max-width: 48em) { .masthead-title { text-align: center; } .masthead-title small { display: none; } } /* * Sidebar * * The sidebar is the drawer, the item we are toggling with our handy hamburger * button in the corner of the page. * * This particular sidebar implementation was inspired by Chris Coyier's * "Offcanvas Menu with CSS Target" article, and the checkbox variation from the * comments by a reader. It modifies both implementations to continue using the * checkbox (no change in URL means no polluted browser history), but this uses * `position` for the menu to avoid some potential content reflow issues. * * Source: http://css-tricks.com/off-canvas-menu-with-css-target/#comment-207504 */ /* Style and "hide" the sidebar */ .sidebar { position: fixed; top: 0; bottom: 0; left: -14rem; width: 14rem; visibility: hidden; overflow-y: auto; font-family: "PT Sans", Helvetica, Arial, sans-serif; font-size: .875rem; /* 15px */ color: rgba(255,255,255,.6); background-color: #202020; -webkit-transition: all .3s ease-in-out; transition: all .3s ease-in-out; } @media (min-width: 30em) { .sidebar { font-size: .75rem; /* 14px */ } } /* Sidebar content */ .sidebar a { font-weight: normal; color: #fff; } .sidebar-item { padding: 1rem; } .sidebar-item p:last-child { margin-bottom: 0; } /* Sidebar nav */ .sidebar-nav { border-bottom: 1px solid rgba(255,255,255,.1); } .sidebar-nav-item { display: block; padding: .5rem 1rem; border-top: 1px solid rgba(255,255,255,.1); } .sidebar-nav-item.active, a.sidebar-nav-item:hover, a.sidebar-nav-item:focus { text-decoration: none; background-color: rgba(255,255,255,.1); border-color: transparent; } .sidebar-nav-item span.nav-note { text-transform:uppercase; font-size:0.6em; opacity:0.5; } @media (min-width: 48em) { .sidebar-item { padding: 1.5rem; } .sidebar-nav-item { padding-left: 1.5rem; padding-right: 1.5rem; } } /* Style the `label` that we use to target the `.sidebar-checkbox` */ .sidebar-toggle { position: absolute; top: .8rem; left: 1rem; display: block; padding: .25rem .75rem; color: #505050; background-color: #fff; border-radius: .25rem; cursor: pointer; } .sidebar-toggle:before { display: inline-block; width: 1rem; height: .75rem; content: ""; background-image: -webkit-linear-gradient(to bottom, #555, #555 20%, #fff 20%, #fff 40%, #555 40%, #555 60%, #fff 60%, #fff 80%, #555 80%, #555 100%); background-image: -moz-linear-gradient(to bottom, #555, #555 20%, #fff 20%, #fff 40%, #555 40%, #555 60%, #fff 60%, #fff 80%, #555 80%, #555 100%); background-image: -ms-linear-gradient(to bottom, #555, #555 20%, #fff 20%, #fff 40%, #555 40%, #555 60%, #fff 60%, #fff 80%, #555 80%, #555 100%); background-image: linear-gradient(to bottom, #555, #555 20%, #fff 20%, #fff 40%, #555 40%, #555 60%, #fff 60%, #fff 80%, #555 80%, #555 100%); } .open .sidebar-toggle:active, .open .sidebar-toggle, .open .sidebar-toggle { color: #fff; background-color: #555; } .open .sidebar-toggle:active:before, .open .sidebar-toggle:before, .open .sidebar-toggle:before { background-image: -webkit-linear-gradient(to bottom, #fff, #fff 20%, #555 20%, #555 40%, #fff 40%, #fff 60%, #555 60%, #555 80%, #fff 80%, #fff 100%); background-image: -moz-linear-gradient(to bottom, #fff, #fff 20%, #555 20%, #555 40%, #fff 40%, #fff 60%, #555 60%, #555 80%, #fff 80%, #fff 100%); background-image: -ms-linear-gradient(to bottom, #fff, #fff 20%, #555 20%, #555 40%, #fff 40%, #fff 60%, #555 60%, #555 80%, #fff 80%, #fff 100%); background-image: linear-gradient(to bottom, #fff, #fff 20%, #555 20%, #555 40%, #fff 40%, #fff 60%, #555 60%, #555 80%, #fff 80%, #fff 100%); } @media (min-width: 30.1em) { .sidebar-toggle { position: fixed; } } @media print { .sidebar-toggle { display: none; } } /* Slide effect * * Handle the sliding effects of the sidebar and content in one spot, seperate * from the default styles. * * As an a heads up, we don't use `transform: translate3d()` here because when * mixed with `position: fixed;` for the sidebar toggle, it creates a new * containing block. Put simply, the fixed sidebar toggle behaves like * `position: absolute;` when transformed. * * Read more about it at http://meyerweb.com/eric/thoughts/2011/09/12/. */ .wrap, .sidebar, .sidebar-toggle { -webkit-backface-visibility: hidden; -ms-backface-visibility: hidden; backface-visibility: hidden; } .wrap, .sidebar-toggle { -webkit-transition: -webkit-transform .3s ease-in-out; transition: transform .3s ease-in-out; } .open .sidebar-toggle, .open .sidebar, .open .wrap{ z-index: 10; visibility: visible; -webkit-transform: translateX(14rem); -ms-transform: translateX(14rem); transform: translateX(14rem); } .sidebar-footer a{ color: #1db7a9; opacity: 0.8; border-bottom:1px dotted #1db7a9; margin-right:1em; } .sidebar-footer a:hover { opacity:1; text-decoration:none; } /* * Posts and pages * * Each post is wrapped in `.post` and is used on default and post layouts. Each * page is wrapped in `.page` and is only used on the page layout. */ .page, .post { margin-bottom: 4em; } .banner { margin-top:4em; margin-bottom: 6em; } .banner a { border-bottom: 1px dotted #1db7a9; text-decoration:none; } .banner a:hover, .banner a:active .banner a:visited { border-bottom: 2px dotted #1db7a9; } /* Blog post or page title */ .page-title, .post-title, .post-title a { color: #303030; } .page-title, .post-title { margin-top: 0; } .post-title em { background:#d9ead3; color:#1db7a9; padding:0em 0.3em 0em; margin:0.1em; font-style:normal; } /* Meta data line below post title */ .post-date { display: block; margin-top: -.5rem; margin-bottom: 1rem; color: #9a9a9a; } /* Related posts */ .related { padding-top: 2rem; padding-bottom: 2rem; border-top: 1px solid #eee; } .related-posts { padding-left: 0; list-style: none; } .related-posts h3 { margin-top: 0; } .related-posts li small { font-size: 75%; color: #999; } .related-posts li a:hover { color: #1db7a9; text-decoration: none; } .related-posts li a:hover small { color: inherit; } /* * Pagination * * Super lightweight (HTML-wise) blog pagination. `span`s are provide for when * there are no more previous or next posts to show. */ .pagination { overflow: hidden; /* clearfix */ margin-left: -1rem; margin-right: -1rem; font-family: "PT Sans", Helvetica, Arial, sans-serif; color: #ccc; text-align: center; } /* Pagination items can be `span`s or `a`s */ .pagination-item { display: block; padding: 1rem; border: 1px solid #eee; } .pagination-item:first-child { margin-bottom: -1px; } /* Only provide a hover state for linked pagination items */ a.pagination-item:hover { background-color: #f5f5f5; } @media (min-width: 30em) { .pagination { margin: 3rem 0; } .pagination-item { float: left; width: 50%; } .pagination-item:first-child { margin-bottom: 0; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } .pagination-item:last-child { margin-left: -1px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } } /* * Reverse layout * * Flip the orientation of the page by placing the `.sidebar` and sidebar toggle * on the right side. */ .layout-reverse .sidebar { left: auto; right: -14rem; } .layout-reverse .sidebar-toggle { left: auto; right: 1rem; } .layout-reverse #sidebar-checkbox:checked ~ .sidebar, .layout-reverse #sidebar-checkbox:checked ~ .wrap, .layout-reverse #sidebar-checkbox:checked ~ .sidebar-toggle { -webkit-transform: translateX(-14rem); -ms-transform: translateX(-14rem); transform: translateX(-14rem); } /* * Themes * * Apply custom color schemes by adding the appropriate class to the `body`. * Based on colors from Base16: http://chriskempson.github.io/base16/#default. */ /* Red */ .theme-base-08 .sidebar, .theme-base-08 .sidebar-toggle:active, .theme-base-08 #sidebar-checkbox:checked ~ .sidebar-toggle { background-color: #ac4142; } .theme-base-08 .container a, .theme-base-08 .sidebar-toggle, .theme-base-08 .related-posts li a:hover { color: #ac4142; } /* Orange */ .theme-base-09 .sidebar, .theme-base-09 .sidebar-toggle:active, .theme-base-09 #sidebar-checkbox:checked ~ .sidebar-toggle { background-color: #d28445; } .theme-base-09 .container a, .theme-base-09 .sidebar-toggle, .theme-base-09 .related-posts li a:hover { color: #d28445; } /* Yellow */ .theme-base-0a .sidebar, .theme-base-0a .sidebar-toggle:active, .theme-base-0a #sidebar-checkbox:checked ~ .sidebar-toggle { background-color: #f4bf75; } .theme-base-0a .container a, .theme-base-0a .sidebar-toggle, .theme-base-0a .related-posts li a:hover { color: #f4bf75; } /* Green */ .theme-base-0b .sidebar, .theme-base-0b .sidebar-toggle:active, .theme-base-0b #sidebar-checkbox:checked ~ .sidebar-toggle { background-color: #90a959; } .theme-base-0b .container a, .theme-base-0b .sidebar-toggle, .theme-base-0b .related-posts li a:hover { color: #90a959; } /* Cyan */ .theme-base-0c .sidebar, .theme-base-0c .sidebar-toggle:active, .theme-base-0c #sidebar-checkbox:checked ~ .sidebar-toggle { background-color: #75b5aa; } .theme-base-0c .container a, .theme-base-0c .sidebar-toggle, .theme-base-0c .related-posts li a:hover { color: #75b5aa; } /* Blue */ .theme-base-0d .sidebar, .theme-base-0d .sidebar-toggle:active, .theme-base-0d #sidebar-checkbox:checked ~ .sidebar-toggle { background-color: #6a9fb5; } .theme-base-0d .container a, .theme-base-0d .sidebar-toggle, .theme-base-0d .related-posts li a:hover { color: #6a9fb5; } /* Magenta */ .theme-base-0e .sidebar, .theme-base-0e .sidebar-toggle:active, .theme-base-0e #sidebar-checkbox:checked ~ .sidebar-toggle { background-color: #aa759f; } .theme-base-0e .container a, .theme-base-0e .sidebar-toggle, .theme-base-0e .related-posts li a:hover { color: #aa759f; } /* Brown */ .theme-base-0f .sidebar, .theme-base-0f .sidebar-toggle:active, .theme-base-0f #sidebar-checkbox:checked ~ .sidebar-toggle { background-color: #8f5536; } .theme-base-0f .container a, .theme-base-0f .sidebar-toggle, .theme-base-0f .related-posts li a:hover { color: #8f5536; } /* * Overlay sidebar * * Make the sidebar content overlay the viewport content instead of pushing it * aside when toggled. */ .sidebar-overlay #sidebar-checkbox:checked ~ .wrap { -webkit-transform: translateX(0); -ms-transform: translateX(0); transform: translateX(0); } .sidebar-overlay #sidebar-checkbox:checked ~ .sidebar-toggle { box-shadow: 0 0 0 .25rem #fff; } .sidebar-overlay #sidebar-checkbox:checked ~ .sidebar { box-shadow: .25rem 0 .5rem rgba(0,0,0,.1); } /* Only one tweak for a reverse layout */ .layout-reverse.sidebar-overlay #sidebar-checkbox:checked ~ .sidebar { box-shadow: -.25rem 0 .5rem rgba(0,0,0,.1); } ================================================ FILE: styles/portfolio.css ================================================ /** * Layout styles */ .portfolio_item { left: right; margin: 0 0 20px 0; padding:20px 20px 0 20px; box-shadow:1px 2px 2px #eaeaea; border:1px solid #eaeaea; } .portfolio_item h2 { font-size: 24px; margin: 20px 0 20px 0; text-align: right; padding-top: 250px; text-transform:uppercase; font-weight:bold; } .portfolio_item h2 a { font-size: 14px; text-decoration: none; font-weight: bold; } .portfolio_item .role_wrapper { border-bottom: 1px dotted #ccc; position: relative; margin-bottom:20px; } .portfolio_item .role { float: left; color: #1db7a9; font-size: 30px; font-weight: bold; text-transform:uppercase; margin: 16px 10px 10px 10px; opacity:0.5; } .portfolio_item .role_title { float: right; font-size: 22px; font-weight: bold; margin: 10px; color: #1db7a9; line-height: 24px; text-align: right; } .portfolio_item span.role_skills { font-size: 18px; color: #a6a6a6; } /** * Individual */ .goldmans { background:#fff url(assets/goldmans.jpg) top no-repeat; } .orange { background:#fff url(assets/orange.jpg) top no-repeat; } .tesco { background:#fff url(assets/tesco.png) top no-repeat; } .timeout { background:#fff url(assets/timeout.png) top no-repeat; } .wiley { background:#fff url(assets/wiley.png) top no-repeat; } .hellas { background:#fff url(assets/hellas.png) top no-repeat; } .shipserv { background:#fff url(assets/shipserv.png) top no-repeat; } .covestor { background:#fff url(assets/covestor.png) top no-repeat; } .pwul { background:#fff url(assets/pwul.jpg) top no-repeat; } ================================================ FILE: styles/services.css ================================================ .services { width:46%; float:left; font-size:16px; } .services:last-child{ float:right; } @media (max-width: 40em) { .services { width:100%; } } ================================================ FILE: webpack.config.js ================================================ var path = require('path'); var webpack = require('webpack'); var merge = require('merge'); var ExtractTextPlugin = require('extract-text-webpack-plugin'); var webpackConfig = { output: { path: path.join(__dirname, 'dist'), filename: 'bundle.js', publicPath: '/static/' }, plugins: [ new webpack.optimize.OccurenceOrderPlugin(), new webpack.NoErrorsPlugin() ] }; if (process.env.NODE_ENV === 'production') { webpackConfig = merge(webpackConfig,{ devtool: "source-map", entry : [ './src/client/index.js' ], module: { loaders: [{ test: /\.js$/, loader: 'babel', exclude: /node_modules/, include: __dirname }, { test: /\.(png|jpg|gif|jpeg)$/, loader: 'url-loader?limit=8192'}, { test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader?sourceMap') } ]}, plugins : [ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('production') } }), new ExtractTextPlugin("app.css"), new webpack.optimize.UglifyJsPlugin({minimize: true}) ] }); }else{ webpackConfig = merge(webpackConfig,{ devtool: 'inline-source-map', module: { loaders: [{ test: /\.js$/, loader: 'babel', exclude: /node_modules/, include: __dirname, query: { optional: ['runtime'], stage: 2, env: { development: { plugins: [ 'react-transform' ], extra: { 'react-transform': { transforms: [{ transform: 'react-transform-hmr', imports: ['react'], locals: ['module'] }] } } } } } }, { test: /\.(png|jpg|gif|jpeg)$/, loader: 'url-loader?limit=8192'}, { test: /\.css$/, loader: 'style-loader!css-loader' } ]}, entry : [ 'webpack-hot-middleware/client', './src/client/index.js' ], plugins : [ new webpack.HotModuleReplacementPlugin() ] }); } module.exports = webpackConfig;