diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..c13c5f6
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,3 @@
+{
+ "presets": ["es2015"]
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..345130c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,136 @@
+# Created by https://www.gitignore.io/api/osx,vim,node,macos,windows
+
+### macOS ###
+*.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### Node ###
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Typescript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+
+### OSX ###
+
+# Icon must end with two \r
+
+# Thumbnails
+
+# Files that might appear in the root of a volume
+
+# Directories potentially created on remote AFP share
+
+### Vim ###
+# swap
+[._]*.s[a-v][a-z]
+[._]*.sw[a-p]
+[._]s[a-v][a-z]
+[._]sw[a-p]
+# session
+Session.vim
+# temporary
+.netrwhist
+*~
+# auto-generated tag files
+tags
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.gitignore.io/api/osx,vim,node,macos,windows
diff --git a/app/entry.js b/app/entry.js
new file mode 100644
index 0000000..c4b08a2
--- /dev/null
+++ b/app/entry.js
@@ -0,0 +1,61 @@
+'use strict';
+
+require('./scss/reset.scss');
+require('./scss/main.scss');
+
+const angular = require('angular');
+const cowsay = require('cowsay-browser');
+
+const cowsayApp = angular.module('cowsayApp', []);
+
+cowsayApp.controller('CowsayController', ['$log', CowsayController]);
+
+function CowsayController($log) {
+ $log.debug('CowsayController');
+
+ this.title = 'Welcome to Cowville!';
+ this.history = [];
+
+ cowsay.list((err, cowfiles) => {
+ this.cowfiles = cowfiles;
+ this.current = this.cowfiles[0];
+ });
+
+ this.update = function(input) {
+ $log.debug('cowsayCtrl.update()');
+ return cowsay.say({ text: input || 'moooooo', f: this.current })
+ }
+
+ this.speak = function(input) {
+ $log.debug('cowsayCtrl.speak()');
+ this.spoken = this.update(input);
+ this.history.push(this.spoken);
+ };
+
+ this.undo = function() {
+ $log.debug('cowsayCtrl.undo()');
+ this.history.pop();
+ this.spoken = this.history.pop() || '';
+ }
+}
+
+cowsayApp.controller('NavController', ['$log', NavController]);
+
+function NavController($log) {
+ $log.debug('NavController');
+
+ this.routes = [
+ {
+ name: 'home',
+ url: '/home'
+ },
+ {
+ name: 'about',
+ url: '/about-us'
+ },
+ {
+ name: 'contact',
+ url: '/contact-us'
+ }
+ ]
+};
diff --git a/app/index.html b/app/index.html
new file mode 100644
index 0000000..b6c29c5
--- /dev/null
+++ b/app/index.html
@@ -0,0 +1,50 @@
+
+
+
+ Cowsay App
+
+
+
+
+
+
+ {{ cowsayCtrl.title }}
+
+
+ {{ cowsayCtrl.update(cowsayCtrl.text) }}
+
+
+
+
+
+
+
diff --git a/app/scss/main.scss b/app/scss/main.scss
new file mode 100644
index 0000000..1f9ec87
--- /dev/null
+++ b/app/scss/main.scss
@@ -0,0 +1,75 @@
+// ::::: $vars ::::: //
+$brand-primary: #444;
+$btn-primary: $brand-primary / 2;
+$btn-hover: #31b1c1;
+$black: #000;
+$white: #fff;
+$box-std: 100%;
+$gutter-std: 5%;
+$gutter-sm: $gutter-std / 2;
+$radius: 3px;
+$transition: 350ms all;
+
+// ::::: global styles ::::: //
+body {
+ font-family: helvetica;
+ font-size: 2vw;
+ background: $brand-primary * 3;
+}
+
+a {
+ text-decoration: none;
+ color: $black;
+ cursor: pointer;
+}
+
+h2 {
+ font-size: 5vw;
+ text-align: center;
+ text-decoration: underline;
+ color: $brand-primary;
+ margin-bottom: $gutter-std;
+}
+
+button {
+ width: 100%;
+ padding: 2% 0;
+ margin-bottom: 20px;
+ background: $btn-primary / 3;
+ color: $white;
+ border: none;
+ border-radius: $radius;
+ cursor: pointer;
+ transition: $transition;
+
+ &:hover {
+ background: $btn-hover;
+ }
+}
+
+input[type="text"] {
+ float: left;
+ width: 100%;
+ box-sizing: border-box;
+ padding: 2%;
+ border: none;
+ border-radius: $radius;
+ transition: $transition;
+
+ &:focus {
+ background: $black;
+ color: $white;
+ }
+}
+
+select {
+ width: 100%;
+ margin-bottom: $gutter-sm;
+}
+
+pre {
+ overflow: auto;
+ width: 100%;
+ font-family: monospace;
+ margin: $gutter-std;
+}
diff --git a/app/scss/reset.scss b/app/scss/reset.scss
new file mode 100644
index 0000000..ed11813
--- /dev/null
+++ b/app/scss/reset.scss
@@ -0,0 +1,48 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+ v2.0 | 20110126
+ License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol, ul {
+ list-style: none;
+}
+blockquote, q {
+ quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..df670d5
--- /dev/null
+++ b/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "37-angular-overview",
+ "version": "1.0.0",
+ "description": " 37: Angular Overview ======",
+ "main": "webpack.config.js",
+ "scripts": {
+ "watch": "webpack-dev-server --inline --hot"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/bretladenburg/37-angular-overview.git"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/bretladenburg/37-angular-overview/issues"
+ },
+ "homepage": "https://github.com/bretladenburg/37-angular-overview#readme",
+ "dependencies": {
+ "angular": "^1.6.6",
+ "babel-core": "^6.26.0",
+ "babel-loader": "^7.1.2",
+ "babel-preset-es2015": "^6.24.1",
+ "cowsay-browser": "^1.1.8",
+ "css-loader": "^0.28.7",
+ "extract-text-webpack-plugin": "^3.0.0",
+ "html-webpack-plugin": "^2.30.1",
+ "node-sass": "^4.5.3",
+ "sass-loader": "^6.0.6",
+ "style-loader": "^0.18.2",
+ "webpack": "^3.5.6",
+ "webpack-dev-server": "^2.8.1"
+ }
+}
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 0000000..422c544
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const HtmlPlugin = require('html-webpack-plugin');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
+
+module.exports = {
+ entry: `${__dirname}/app/entry.js`,
+ output: {
+ filename: 'bundle.js',
+ path: `${__dirname}/build`
+ },
+ plugins: [
+ new HtmlPlugin({ template: `${__dirname}/app/index.html` }),
+ new ExtractTextPlugin('bundle.css')
+ ],
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ exclude: /node_modules/,
+ loader: 'babel-loader'
+ },
+ {
+ test: /\.scss$/,
+ use: [
+ "style-loader",
+ "css-loader",
+ "sass-loader"
+ ]
+ }
+ ]
+ }
+};