diff --git a/.gitignore b/.gitignore index 123ae94..f69aad7 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,8 @@ build/Release # Dependency directory # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules + +# Compiled Public Directory +public/js/ +public/styles/ +public/views/ diff --git a/Gulpfile.js b/Gulpfile.js new file mode 100644 index 0000000..44f5f45 --- /dev/null +++ b/Gulpfile.js @@ -0,0 +1,16 @@ +/* + gulpfile.js + =========== + Rather than manage one giant configuration file responsible + for creating multiple tasks, each task has been broken out into + its own file in gulp/tasks. Any files in that directory get + automatically required below. + To add a new task, simply add a new task file that directory. + gulp/tasks/default.js specifies the default set of tasks to run + when you run `gulp`. +*/ + +var requireDir = require('require-dir'); + +// Require all tasks in gulp/tasks, including subfolders +requireDir('./gulp/tasks', { recurse: true }); diff --git a/comps/intelly-blog-III@2x.psd b/comps/intelly-blog-III@2x.psd deleted file mode 100644 index ebfdb1a..0000000 Binary files a/comps/intelly-blog-III@2x.psd and /dev/null differ diff --git a/comps/intelly-blog-post@2x.psd b/comps/intelly-blog-post@2x.psd deleted file mode 100644 index 0964434..0000000 Binary files a/comps/intelly-blog-post@2x.psd and /dev/null differ diff --git a/gulp/config.js b/gulp/config.js new file mode 100644 index 0000000..84bcda2 --- /dev/null +++ b/gulp/config.js @@ -0,0 +1,35 @@ +var dest = "./public"; +var src = './src'; + +module.exports = { + javascript: { + src: src + '/app/**/*.js', + dest: dest + '/js/', + entryPoint: src + '/app/entry.js', + packedFile: 'packed.js' + }, + sass: { + src: src + "/styles/**/*.{sass,scss}", + dest: dest + '/styles/', + settings: { + indentedSyntax: true, // Enable .sass syntax! + } + }, + fonts: { + src: src + '/styles/fonts/*', + dest: dest + "/styles/fonts/", + extensions: ['woff2', 'woff', 'eot', 'ttf', 'svg'] + }, + html: { + src: src + "/app/**/*.html", + dest: dest + "/views/" + }, + server: { + serverFile: './server.js' + }, + production: { + cssSrc: dest + '/styles/*.css', + jsSrc: dest + '/*.js', + dest: dest + } +}; diff --git a/gulp/tasks/default.js b/gulp/tasks/default.js new file mode 100644 index 0000000..8026d44 --- /dev/null +++ b/gulp/tasks/default.js @@ -0,0 +1,3 @@ +var gulp = require('gulp'); + +gulp.task('default', ['sass', 'fonts', 'html', 'webpack', 'watch', 'serve']); diff --git a/gulp/tasks/fonts.js b/gulp/tasks/fonts.js new file mode 100644 index 0000000..4347662 --- /dev/null +++ b/gulp/tasks/fonts.js @@ -0,0 +1,7 @@ +var gulp = require("gulp"); +var config = require('../config').fonts; + +gulp.task('fonts', function() { + return gulp.src(config.src) + .pipe(gulp.dest(config.dest)); +}); diff --git a/gulp/tasks/html.js b/gulp/tasks/html.js new file mode 100644 index 0000000..0b29aff --- /dev/null +++ b/gulp/tasks/html.js @@ -0,0 +1,7 @@ +var gulp = require("gulp"); +var config = require('../config').html; + +gulp.task('html', function() { + return gulp.src(config.src) + .pipe(gulp.dest(config.dest)); +}); diff --git a/gulp/tasks/sass.js b/gulp/tasks/sass.js new file mode 100644 index 0000000..2f24254 --- /dev/null +++ b/gulp/tasks/sass.js @@ -0,0 +1,16 @@ +var gulp = require('gulp'); +var sass = require('gulp-sass'); +var autoprefixer = require('gulp-autoprefixer'); +var handleErrors = require('../util/handleErrors'); +var config = require('../config').sass; + +gulp.task('sass', function () { + return gulp.src(config.src) + .pipe(sass(config.settings)) + .pipe(autoprefixer({ + browsers: ['last 2 versions'], + cascade: false + })) + .on('error', handleErrors) + .pipe(gulp.dest(config.dest)); +}); diff --git a/gulp/tasks/serve.js b/gulp/tasks/serve.js new file mode 100644 index 0000000..583b9fa --- /dev/null +++ b/gulp/tasks/serve.js @@ -0,0 +1,9 @@ +var gulp = require('gulp'); +var config = require('../config').server; +var server = require("gulp-express"); + +gulp.task('serve', ['sass', 'fonts', 'html', 'webpack', 'watch'], function() { + server.run([config.serverFile]); + + gulp.watch([config.serverFile], [server.run]); +}); diff --git a/gulp/tasks/watch.js b/gulp/tasks/watch.js new file mode 100644 index 0000000..a84ee24 --- /dev/null +++ b/gulp/tasks/watch.js @@ -0,0 +1,8 @@ +var gulp = require('gulp'); +var config = require('../config'); + +gulp.task('watch', function() { + gulp.watch(config.javascript.src, ['webpack']); + gulp.watch(config.sass.src, ['sass']); + gulp.watch(config.html.src, ['html']); +}); diff --git a/gulp/tasks/webpack.js b/gulp/tasks/webpack.js new file mode 100644 index 0000000..7dded07 --- /dev/null +++ b/gulp/tasks/webpack.js @@ -0,0 +1,13 @@ +var gulp = require('gulp'); +var config = require('../config').javascript; +var webpack = require('webpack-stream'); + +gulp.task('webpack', function(callback) { + return gulp.src(config.entryPoint) + .pipe(webpack({ + output: { + filename: config.packedFile + } + })) + .pipe(gulp.dest(config.dest)); +}); diff --git a/gulp/util/handleErrors.js b/gulp/util/handleErrors.js new file mode 100644 index 0000000..a6e481d --- /dev/null +++ b/gulp/util/handleErrors.js @@ -0,0 +1,15 @@ +var notify = require("gulp-notify"); + +module.exports = function() { + + var args = Array.prototype.slice.call(arguments); + + // Send error to notification center with gulp-notify + notify.onError({ + title: "Compile Error", + message: "<%= error %>" + }).apply(this, args); + + // Keep gulp from hanging on this task + this.emit('end'); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..50eeb4d --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "mean-stack-1", + "version": "1.0.0", + "description": "Last week we focussed on JavaScript basics. This week we're going to build up a function web application. You'll be expected to build on knowledge you've already learned (semantic HTML, Sass instead of CSS, in addition to all of the Angular work we'll be doing) and produce a \"production-ready\" blog for your assignment this week.", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/pstrum/mean-stack-1.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/pstrum/mean-stack-1/issues" + }, + "homepage": "https://github.com/pstrum/mean-stack-1#readme", + "dependencies": { + "angular": "^1.4.7", + "angular-route": "^1.4.7", + "body-parser": "^1.14.1", + "connect-livereload": "^0.5.3", + "express": "^4.13.3", + "gulp": "^3.9.0", + "gulp-autoprefixer": "^3.0.2", + "gulp-express": "^0.3.5", + "gulp-livereload": "^3.8.1", + "gulp-notify": "^2.2.0", + "gulp-rename": "^1.2.2", + "gulp-run": "^1.6.11", + "gulp-sass": "^2.0.4", + "mongoose": "^4.1.10", + "node-bourbon": "^4.2.3", + "require-dir": "^0.3.0", + "webpack": "^1.12.2", + "webpack-stream": "^2.1.1" + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..b477b50 --- /dev/null +++ b/public/index.html @@ -0,0 +1,80 @@ + + + + + + Intelly + + + + + + +
+

intelly

+ +
+
+
+

Blog

+

The secular cooling that must someday overtake our planet has already gone far indeed with our neighbour. Its physical condition is still largely a mystery, but we know now that even in its equatorial

+
+
+ + +
+
+ +
+
+

Serene in their assurance of their empire over matter

+

No one would have believed in the last years of the nine- teenth century that this world was being watched keenly and closely by intelligences greater than man’s

+ Contact now + Learn more +
+
+ + + + diff --git a/server.js b/server.js new file mode 100644 index 0000000..30fbf26 --- /dev/null +++ b/server.js @@ -0,0 +1,107 @@ +// CALL THE PACKAGES WE NEED TO SET UP THE SERVER +// ============================================== +// Require Express Module +var express = require('express'); +// Declare the variable 'app', create the Express Application +var app = express(); +// bodyParser to expose various factories to create middlewares +var bodyParser = require('body-parser'); +// Parses urlencoded bodies; Extend option chooses parsing with qs library +app.use(bodyParser.urlencoded({ extended: true })); +// Parses json +app.use(bodyParser.json()); +// Grab the mongoos package +var mongoose = require('mongoose'); +// Connect to our local database, named directory auto generated +mongoose.connect('mongodb://localhost/blogs'); +// Grab the blog post model +var Blog = require('./src/app/models/blog-post'); +// Environment defined port, OR 8000 +var port = process.env.PORT || 8000; +// Use built in middleware; pass the directory; use absolute path to directory to serve +app.use(express.static(__dirname + '/public')); +// API ROUTES - How our app responds to client requests +// ========== +// Get instance of Express Router to handle all our routes +var router = express.Router(); +// All routes prefixed with /api; add to middleware stack +app.use('/api', router); +// Something to happen every time a request is sent to our API +router.use(function(req, res, next) { + console.log('Something is HAPPENING...'); + // make sure we go to the next routes and don't stop here. + next(); +}); +// Define the home route (accessed at GET localhost:8000/api) +router.get('/', function(req, res) { + res.json({ message: 'YOLO! WELCOME to our API!' }); +}); +// app.route('/').get(function(req, res) { +// res.sendFile('index.html', {root: __dirname + '/public'}); +// }); +// To handle multiple routes for the same URI; all req that end in /blog-post +router.route('/blog-post') + // Create a POST (accessed at POST localhost:8000/api/posts) + .post(function(req, res) { + // New instance of the Blog model + var blog = new Blog(); + // Set the post title, author, content (comes from request) + blog.title = req.body.title; + blog.author = req.body.author; + blog.content = req.body.content; + // Save the post and check for errors + blog.save(function(err) { + if (err) + res.send(err); + res.json(blog); + }); + }) + // Get ALL the posts; chaining routes together... + .get(function(req, res) { + Blog.find(function(err, blogs) { + if (err) + res.send(err); + res.json(blogs); + }); + }); +// To handle all requests that have a :blog_id--accessed through body-parser +router.route('/blog-post/:blog_id') + // Get a post with that id (accessed at GET localhost:8000/api/blog-post/:blog_id) + .get(function(req, res) { + Blog.findById(req.params.blog_id, function(err, blog) { + if (err) + res.send(err); + res.json(blog); + }); + }) + // Update a post (accessed at PUT localhost:8080/api/blog-post/:blog_id) + .put(function(req, res) { + // Use the blog model to find the post we want + Blog.findById(req.params.blog_id, function(err, blog) { + if (err) + res.send(err); + // Update the post info + blog.title = req.body.title; + blog.author = req.body.author; + blog.content = req.body.content; + // Save the post! + blog.save(function(err) { + if (err) + res.send(err); + res.json(blog); + }); + }); + }) + // Delete a post (accessed at DELETE localhost:8080/api/blog-post/:blog_id) + .delete(function(req, res) { + Blog.remove({ + _id: req.params.blog_id + }, function(err, blog) { + if (err) + res.send(err); + res.json(blog); + }); + }); +// START THE SERVER +app.listen(port); +console.log("Fancy stuff on port " + port); diff --git a/src/app/app.js b/src/app/app.js new file mode 100644 index 0000000..739c911 --- /dev/null +++ b/src/app/app.js @@ -0,0 +1,50 @@ +require('angular'); +require('angular-route'); + +(function() { + + "use strict"; + + // Declare variable app, which equals a new AngularJS module + // with the name 'intelly'. ngRoute is a dependency used for + // routing and deeplinking services + var app = angular.module('intellyApp', ["ngRoute"]); + + // $routeProvider is a provider in the ngRoute module + // used for configuring routes. + // App routes are declared with $routeProvider. + app.config(["$routeProvider", function($routeProvider) { + // The $location service parses the URL in the browser address bar (based on the window.location) and makes the URL available to your application + // This method is getter / setter. + + // Return path of current url + // Add a new route definition to the $route service. + // The route path (/blog-post) is (matched against $location.path) + // which is a getter/setter that parses the address bar url + // and makes it available to our app + $routeProvider.when('/blog-post', { + // This is the route object: + templateUrl: "views/blog/blog-post.html", + controller: "BlogsCtrl as vm" + }). + when('/blog-post/new', { + templateUrl: "views/posts/blog-post-form.html", + controller: "BlogFormCtrl as vm", + }). + when('/blog-post/:blog_id', { + templateUrl: "views/posts/blog-post-detail.html", + controller: "BlogCtrl as vm" + }). + when('/blog-post/:blog_id/edit', { + templateUrl: "views/posts/blog-post-form.html", + controller: "BlogFormCtrl as vm" + }). + when('/blog-post/:blog_id/destroy', { + templateUrl: "views/posts/blog-post-detail.html", + controller: "BlogFormCtrl as vm" + }). + otherwise({ + redirectTo: "/blog-post" + }); + }]); +})(); diff --git a/src/app/blog/blog-post.html b/src/app/blog/blog-post.html new file mode 100644 index 0000000..d7811c1 --- /dev/null +++ b/src/app/blog/blog-post.html @@ -0,0 +1,23 @@ +
+

{{ blog.title }}

+

{{ blog.content | limitTo:150 }}...

+ +
+ Read More +
+
+

No Posts! Want to create one?

+ + diff --git a/src/app/blog/blog.service.js b/src/app/blog/blog.service.js new file mode 100644 index 0000000..7ff13ff --- /dev/null +++ b/src/app/blog/blog.service.js @@ -0,0 +1,40 @@ +require('../app.js'); + +// Organize and share code around out app +(function () { + + "use strict"; + + // Name the service and specify the $http service as a dependency -- inject it to use it + angular.module("intellyApp").service("BlogsService", ["$http", function ($http) { + + // Declare urlRoot + var urlRoot = "/api/blog-post"; + + // Declare blog object with 4 methods + var Blog = { + // Get function with blog post ID as argument + // Function will return a promise + get: function (id) { + // Determine if a reference (post ID) is defined + if (angular.isDefined(id)) { + // Send in the URL with ID to the get method + return $http.get(urlRoot + "/" + id); + } else { + // Send in the base URL + return $http.get(urlRoot); + } + }, + update: function (model) { + return $http.put(urlRoot + "/" + model._id, model); + }, + create: function (model) { + return $http.post(urlRoot, model); + }, + delete: function (model) { + return $http.delete(urlRoot + "/" + model._id); + } + }; + return Blog; + }]); +}()); diff --git a/src/app/blog/blogs.ctrl.js b/src/app/blog/blogs.ctrl.js new file mode 100644 index 0000000..01bd0c4 --- /dev/null +++ b/src/app/blog/blogs.ctrl.js @@ -0,0 +1,54 @@ +require('../app.js'); + +(function() { + + 'use strict'; + + angular.module('intellyApp').controller("BlogsCtrl", ["BlogsService", "$anchorScroll", "$location", function(BlogsService, $anchorScroll, $location) { + + var vm = this; + + // Initialize blogs to an empty array + // (since our page will render before data returns from get request) + vm.blogs = []; + + // Create delete method using deleteBlog function + vm.delete = deleteBlog; + // Create scroll method using toBreadcrumbs function + vm.scroll = toBreadcrumbs; + + // Initialize the controller + initialize(); + + function initialize () { + getBlogs(); + } + + function getBlogs () { + // Run the get method from the BlogsService service + // Then (or success) send in a callback with the response (from promise) + BlogsService.get().then(function(resp) { + // Store the data in the blogs array + // Set the property 'blogs' (array) equal to an array of objects + vm.blogs = resp.data; + console.log(vm.blogs); + }); + } + + function deleteBlog (blog) { + BlogsService.delete(blog).then(function () { + getBlogs(); + }); + } + + function toBreadcrumbs () { + // set the location.hash to the id of + // the element you wish to scroll to. + $location.hash('blog-scroll'); + + // call $anchorScroll() + $anchorScroll(); + } + + }]); +}()); diff --git a/src/app/entry.js b/src/app/entry.js new file mode 100644 index 0000000..dd23b0d --- /dev/null +++ b/src/app/entry.js @@ -0,0 +1,5 @@ +require('./app.js'); +require('./posts/blog.ctrl.js'); +require('./blog/blogs.ctrl.js'); +require('./posts/blog_form.ctrl.js'); +require('./blog/blog.service.js'); diff --git a/src/app/models/blog-post.js b/src/app/models/blog-post.js new file mode 100644 index 0000000..73c8712 --- /dev/null +++ b/src/app/models/blog-post.js @@ -0,0 +1,18 @@ +// Require mongoose +var mongoose = require('mongoose'); + +// Each schema maps to a MongoDB collection +// and defines the shape of the documents within that collection. +// Get a reference to Schema and define our blog. +var Schema = mongoose.Schema; + +var blogSchema = new Schema ({ + title: String, + author: String, + content: String, + comments: [{ body: String, date: Date}], + date: { type: Date, default: Date.now} +}); + +// Allow access in server.js to use in our app +module.exports = mongoose.model('Blog', blogSchema); diff --git a/src/app/posts/blog-post-detail.html b/src/app/posts/blog-post-detail.html new file mode 100644 index 0000000..f1ffb51 --- /dev/null +++ b/src/app/posts/blog-post-detail.html @@ -0,0 +1,13 @@ +
+

{{ vm.blog.title }}

+

{{ vm.blog.content }}

+ +
+ Edit + Home +
+
diff --git a/src/app/posts/blog-post-form.html b/src/app/posts/blog-post-form.html new file mode 100644 index 0000000..fe299e4 --- /dev/null +++ b/src/app/posts/blog-post-form.html @@ -0,0 +1,18 @@ +
+
+ + + + +
+
+

Your Post:

+

{{ vm.blog.title }}

+

{{ vm.blog.content }}

+

{{ vm.blog.author }}

+
+
diff --git a/src/app/posts/blog.ctrl.js b/src/app/posts/blog.ctrl.js new file mode 100644 index 0000000..c360f38 --- /dev/null +++ b/src/app/posts/blog.ctrl.js @@ -0,0 +1,24 @@ +require('../app.js'); + +(function () { + + "use strict"; + + angular.module("intellyApp").controller("BlogCtrl", ["BlogsService", "$routeParams", function (BlogsService, $routeParams) { + + var vm = this; + + vm.posts = []; + var urlRoot = "/api/blog-post"; + + initialize(); + + function initialize() { + BlogsService + .get($routeParams.blog_id) + .then(function (resp) { + vm.blog = resp.data; + }); + } + }]); +}()); diff --git a/src/app/posts/blog_form.ctrl.js b/src/app/posts/blog_form.ctrl.js new file mode 100644 index 0000000..ac579d6 --- /dev/null +++ b/src/app/posts/blog_form.ctrl.js @@ -0,0 +1,54 @@ +require('../app.js'); + +(function () { + + "use strict"; + + angular.module("intellyApp").controller("BlogFormCtrl", ["BlogsService", "$routeParams", "$location", "$http", function (BlogsService, $routeParams, $location, $http) { + + var vm = this; + + var urlRoot = "/api/blog-post"; + + vm.save = submitForm; + + vm.blog = {}; + + vm.delete = deleteBlog; + + initialize(); + + function initialize() { + if ($routeParams.blog_id) { + BlogsService.get($routeParams.blog_id).then(function (resp) { + vm.blog = resp.data; + }); + } + } + + function getBlogs () { + BlogsService.get().then(function(resp) { + // Set the property 'blogs' (array) equal to an array of objects + vm.blogs = resp.data; + }); + } + + function submitForm() { + + var method; + + method = $routeParams.blog_id ? "update" : "create"; + + BlogsService[method](vm.blog).then(function (resp) { + console.log(resp.data._id); + $location.path("/blog-post/" + resp.data._id); + }); + } + + function deleteBlog (blog) { + BlogsService.delete(blog).then(function () { + getBlogs(); + }); + } + }]); +}()); diff --git a/src/styles/base/_INDEX.scss b/src/styles/base/_INDEX.scss new file mode 100644 index 0000000..daccee9 --- /dev/null +++ b/src/styles/base/_INDEX.scss @@ -0,0 +1,4 @@ +@import "reset"; +@import "base"; +@import "icons"; +@import "typography"; diff --git a/src/styles/base/_base.scss b/src/styles/base/_base.scss new file mode 100644 index 0000000..7cbf8d6 --- /dev/null +++ b/src/styles/base/_base.scss @@ -0,0 +1,7 @@ +a { + color: color(primary); + } + +li { + display: inline; +} diff --git a/src/styles/base/_icons.scss b/src/styles/base/_icons.scss new file mode 100644 index 0000000..ce5f17a --- /dev/null +++ b/src/styles/base/_icons.scss @@ -0,0 +1,29 @@ +[class^="icon-"], [class*=" icon-"] { + font-family: 'icomoon'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-linkedin-with-circle:before { + content: "\e800"; +} +.icon-google-plus:before { + content: "\e801"; +} +.icon-facebook:before { + content: "\e802"; +} +.icon-twitter:before { + content: "\e803"; +} +.icon-pinterest:before { + content: "\e804"; +} diff --git a/src/styles/base/_reset.scss b/src/styles/base/_reset.scss new file mode 100644 index 0000000..07db64b --- /dev/null +++ b/src/styles/base/_reset.scss @@ -0,0 +1,29 @@ +* { + box-sizing: border-box; +} + +[ng-cloak] { + visibility: hidden; +} + +body { + width: 100%; + margin: 0 auto; +} + +ul { + padding: 0; +} + +li, a { + text-decoration: none; +} + +li { + list-style: none; +} + +h1, h2, p, figure, blockquote { + margin: 0; + font-weight: normal; +} diff --git a/src/styles/base/_typography.scss b/src/styles/base/_typography.scss new file mode 100644 index 0000000..7b5b476 --- /dev/null +++ b/src/styles/base/_typography.scss @@ -0,0 +1,10 @@ +@font-face { + font-family: 'icomoon'; + src:url('/styles/fonts/icomoon.eot?vjxn5v'); + src:url('/styles/fonts/icomoon.eot?vjxn5v#iefix') format('embedded-opentype'), + url('/styles/fonts/icomoon.ttf?vjxn5v') format('truetype'), + url('/styles/fonts/icomoon.woff?vjxn5v') format('woff'), + url('/styles/fonts/icomoon.svg?vjxn5v#icomoon') format('svg'); + font-weight: normal; + font-style: normal; +} diff --git a/src/styles/components/_INDEX.scss b/src/styles/components/_INDEX.scss new file mode 100644 index 0000000..d7cf15e --- /dev/null +++ b/src/styles/components/_INDEX.scss @@ -0,0 +1,10 @@ +@import "buttons"; +@import "logo-header"; +@import "navigation"; +@import "page-title-description"; +@import "social_icon.scss"; +@import "blog"; +@import "footer"; +@import "feature"; +@import "create-post"; +@import "post-preview"; diff --git a/src/styles/components/_blog.scss b/src/styles/components/_blog.scss new file mode 100644 index 0000000..769bda0 --- /dev/null +++ b/src/styles/components/_blog.scss @@ -0,0 +1,99 @@ +.blog { + background-color: color(base); + + .no-show { + display: block; + font-family: $font-primary; + a { + color: color(secondary); + } + + h2 { + font-size: font-size(2); + margin-top: 1em; + margin-bottom: 1em; + background-color: red; + } + } + + .breadcrumbs { + border-bottom: 1px solid shades-primary(2); + + h1 { + display: inline-block; + font-family: $font-primary; + font-size: font-size(0); + font-weight: font-weight(bold); + } + + ul { + display: inline-block; + } + + li { + padding-right: 10px; + padding-left: 10px; + font-family: $font-primary; + font-size: font-size(0); + color: shades-primary(2); + text-align: right; + } + } + + .blog-post { + padding: 0 0 5em; + text-align: left; + border-bottom: 1px solid shades-primary(2); + + h1 { + font-family: $font-primary; + font-size: 3em; + padding-left: 0; + padding-bottom: 1em; + } + + p { + font-family: $font-primary; + font-weight: weight(light); + font-size: font-size(2); + color: shades-primary(2); + line-height: 2.75rem; + padding-bottom: 1em; + white-space: pre-wrap; + } + + li { + font-family: $font-primary; + text-align: left; + font-style: italic; + color: shades-primary(2); + padding-left: 0; + padding-right: 10px; + + a { + color: shades-primary(2); + } + } + + li:before { + content: "•"; + padding-right: 10px; + } + + li:first-child:before { + display: none; + } + + ul { + display: inline; + } + } + + nav { + padding-top: 1em; + + li { + padding: 0 2px; + } + } +} diff --git a/src/styles/components/_buttons.scss b/src/styles/components/_buttons.scss new file mode 100644 index 0000000..9a5a1d0 --- /dev/null +++ b/src/styles/components/_buttons.scss @@ -0,0 +1,48 @@ +@mixin button($background, $text-color, $size) { + text-transform: uppercase; + background: $background; + color: $text-color; + border: 1px solid black; + border-radius: 5px; + padding-top: 1em; + padding-bottom: 1em; + padding-left: $size; + padding-right: $size; + margin-left: .25em; + margin-right: .25em; + font-family: $font-primary; + font-size: font-size(0); +} + +.button-big, +.button-read { + @include button(black, white, 2em); +} + +.button-change, +.button-create, +.button-feature:hover { + background: white; + color: black; +} + +.button-feature:hover { + font-weight: weight(regular) +} + +.button-feature { + display: inline-block; + background: transparent; + border: 1px solid white; + font-weight: weight(regular); + margin-bottom: 1em; +} + +.button-small { + @include button(white, black, 1em); +} + +.button-small:hover { + background: black; + color: white; +} diff --git a/src/styles/components/_create-post.scss b/src/styles/components/_create-post.scss new file mode 100644 index 0000000..9be2c4c --- /dev/null +++ b/src/styles/components/_create-post.scss @@ -0,0 +1,32 @@ +%form-style { + font-family: $font-primary; + font-size: font-size(0); + border: 1px solid color(secondary); + border-radius: 5px; +} + +.create-post { + li a { + display: inline-block; + } + + input { + @extend %form-style; + padding: .3em .3em; + } + textarea { + @extend %form-style; + padding: .3em .3em; + min-height: 20em; + } + input[type=text] { + padding: .5em; + } + input[type=submit] { + font-size: font-size(0); + padding: 1em 2em; + } + input[type=submit]:hover { + cursor: pointer; + } +} diff --git a/src/styles/components/_feature.scss b/src/styles/components/_feature.scss new file mode 100644 index 0000000..245f222 --- /dev/null +++ b/src/styles/components/_feature.scss @@ -0,0 +1,21 @@ +.feature { + font-family: $font-primary; + text-align: center; + background-color: color(accent); + h1 { + font-weight: weight(regular); + font-size: 3em; + line-height: 1; + color: color(base); + } + p { + font-weight: weight(regular); + font-size: font-size(1); + line-height: 2.25; + color: shades-accent(1); + } + .button-feature { + padding-top: 1em; + padding-bottom: 1em; + } +} diff --git a/src/styles/components/_footer.scss b/src/styles/components/_footer.scss new file mode 100644 index 0000000..0c3f296 --- /dev/null +++ b/src/styles/components/_footer.scss @@ -0,0 +1,27 @@ +footer { + padding: 2em 0; + + .footer-top { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 2em; + border-bottom: 1px solid shades-primary(4); + + a { + font-family: $font-primary; + } + } + + .footer-bottom { + text-align: center; + margin: 2em auto; + + li { + font-size: .917em; + font-family: $font-primary; + padding: 0 .5em; + } + } +} diff --git a/src/styles/components/_logo-header.scss b/src/styles/components/_logo-header.scss new file mode 100644 index 0000000..263341a --- /dev/null +++ b/src/styles/components/_logo-header.scss @@ -0,0 +1,12 @@ +header { + display: flex; + justify-content: space-between; + @extend %section-padding-top-bottom; + + h1 { + font-family: $font-primary; + font-weight: weight(medium); + font-size: font-size(3); + color: color(base); + } +} diff --git a/src/styles/components/_navigation.scss b/src/styles/components/_navigation.scss new file mode 100644 index 0000000..507d81b --- /dev/null +++ b/src/styles/components/_navigation.scss @@ -0,0 +1,20 @@ +.top-navigation { + font-family: $font-primary; + font-weight: weight(regular); + font-size: font-size(0); + text-transform: uppercase; + letter-spacing: 0.238em; + li:before { + content: "•"; + color: color(base); + } + li:first-child:before { + display: none; + } + a { + color: color(base); + } + a:hover { + color: color(accent); + } +} diff --git a/src/styles/components/_page-title-description.scss b/src/styles/components/_page-title-description.scss new file mode 100644 index 0000000..af55383 --- /dev/null +++ b/src/styles/components/_page-title-description.scss @@ -0,0 +1,16 @@ +.title-description { + font-family: $font-secondary; + background-color: color(accent); + border-bottom: 2px solid shades-accent(-1); + h1 { + font-weight: weight(regular); + font-size: 3.33em; + color: color(primary); + } + p { + font-weight: weight(light); + font-size: font-size(3); + line-height: 3.5rem; + color: shades-accent(-2); + } +}; diff --git a/src/styles/components/_post-preview.scss b/src/styles/components/_post-preview.scss new file mode 100644 index 0000000..70299f8 --- /dev/null +++ b/src/styles/components/_post-preview.scss @@ -0,0 +1,22 @@ +.post-preview { + + h1 { + font-family: $font-primary; + padding: 0; + font-size: font-size(3); + } + h2 { + font-family: $font-primary; + font-size: font-size(2); + padding-bottom: 1em; + } + p { + font-family: $font-primary; + font-weight: weight(light); + font-size: font-size(1); + color: shades-primary(2); + line-height: 2rem; + padding-bottom: 1em; + white-space: pre-wrap; + } +} diff --git a/src/styles/components/_social_icon.scss b/src/styles/components/_social_icon.scss new file mode 100644 index 0000000..65c1406 --- /dev/null +++ b/src/styles/components/_social_icon.scss @@ -0,0 +1,41 @@ +.icon-circle { + border-radius: 50%; + width: 8em ; + height: 8em; + padding-top: 3.5em; + padding-bottom: 2.5em; + padding-right: 1.2em; + padding-left: 1.2em; + border: .12em solid color(secondary); + position: relative; + display: block; + text-align: center; +} +.social-link-footer { + + li { + margin-right: 2em; + display: inline-block; + &:hover { + .icon-text { + color: color(accent); + } + } + .icon-circle { + .footer-icon { + left: 0; + bottom: 1.8em; + position: absolute; + font-size: 3em; + color: color(primary); + background-color: color(base); + } + } + } +} +.icon-text { + color: color(primary); + font-family: Ubuntu; + font-size: font-size(0); + font-weight: weight(bold); +} diff --git a/src/styles/fonts/icomoon.eot b/src/styles/fonts/icomoon.eot new file mode 100755 index 0000000..e1dc3f0 Binary files /dev/null and b/src/styles/fonts/icomoon.eot differ diff --git a/src/styles/fonts/icomoon.svg b/src/styles/fonts/icomoon.svg new file mode 100755 index 0000000..ecf19cf --- /dev/null +++ b/src/styles/fonts/icomoon.svg @@ -0,0 +1,15 @@ + + + +Generated by IcoMoon + + + + + + + + + + + \ No newline at end of file diff --git a/src/styles/fonts/icomoon.ttf b/src/styles/fonts/icomoon.ttf new file mode 100755 index 0000000..4090050 Binary files /dev/null and b/src/styles/fonts/icomoon.ttf differ diff --git a/src/styles/fonts/icomoon.woff b/src/styles/fonts/icomoon.woff new file mode 100755 index 0000000..88ae927 Binary files /dev/null and b/src/styles/fonts/icomoon.woff differ diff --git a/src/styles/layout/_INDEX.scss b/src/styles/layout/_INDEX.scss new file mode 100644 index 0000000..2da8672 --- /dev/null +++ b/src/styles/layout/_INDEX.scss @@ -0,0 +1,5 @@ +@import "header"; +@import "navigation-top"; +@import "page-title-description"; +@import "blog"; +@import "feature"; diff --git a/src/styles/layout/_blog.scss b/src/styles/layout/_blog.scss new file mode 100644 index 0000000..d81a367 --- /dev/null +++ b/src/styles/layout/_blog.scss @@ -0,0 +1,106 @@ +.blog { + margin-bottom: 6em; + + .breadcrumbs { + @extend %flex-box; + padding: 1em 6em; + ul { + margin: 0; + } + } + + .no-show { + margin: 3em auto; + text-align: center; + } + + .blog-post { + margin: 6em 20% 5em; + + div { + float: right; + } + + ul { + width: 75%; + margin: 0; + float: none; + } + + .button-read { + float: right; + } + } + + nav { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + width: 60%; + margin: 0 auto; + + ul { + margin: 0; + display: inline; + min-width: 175px; + + } + } + + .button-create { + display: block; + max-width: 12em; + margin: 2em auto 0; + } + + .blog-form { + display: flex; + justify-content: space-around; + padding: 0 10%; + } + + .post-preview { + display: none; + width: 30em; + + h1 { + margin-top: 0; + margin-bottom: 1em; + } + + @media screen and (min-width: 800px) { + display: block; + width: 30em; + margin: 2em 1em; + } + } + + .create-post { + display: block; + width: 30em; + margin: 2em 1em; + + @media screen and (max-width: 35em) { + width: 95%; + } + + input { + display: block; + } + input[type=text], textarea { + width: 100%; + margin-bottom: font-size(1); + } + ul { + display: flex; + justify-content: space-between; + margin: 0; + width: 100%; + height: 4em; + } + li { + display: inline-block; + } + } +} diff --git a/src/styles/layout/_feature.scss b/src/styles/layout/_feature.scss new file mode 100644 index 0000000..811a96d --- /dev/null +++ b/src/styles/layout/_feature.scss @@ -0,0 +1,14 @@ +.feature { + @extend %section-padding-top-bottom; + h1 { + margin-bottom: font-size(4); + } + p { + max-width: 38em; + margin: 0 auto font-size(4); + } + .button-feature { + margin-left: font-size(1); + margin-right: font-size(1); + } +} diff --git a/src/styles/layout/_header.scss b/src/styles/layout/_header.scss new file mode 100644 index 0000000..9f86807 --- /dev/null +++ b/src/styles/layout/_header.scss @@ -0,0 +1,7 @@ +header { + background-color: color(primary); + @extend %flex-box; + h1 { + display: inline-block; + } +} diff --git a/src/styles/layout/_navigation-top.scss b/src/styles/layout/_navigation-top.scss new file mode 100644 index 0000000..bc17742 --- /dev/null +++ b/src/styles/layout/_navigation-top.scss @@ -0,0 +1,18 @@ +.top-navigation { + display: inline-block; + ul { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + margin-left: font-size(1); + } + li { + display: inline-block; + } + li:before { + padding: 0 font-size(-1); + } + a { + margin: 0; + } +} diff --git a/src/styles/layout/_page-title-description.scss b/src/styles/layout/_page-title-description.scss new file mode 100644 index 0000000..13207b4 --- /dev/null +++ b/src/styles/layout/_page-title-description.scss @@ -0,0 +1,13 @@ +.title-description { + @extend %section-padding-top-bottom; + @extend %flex-box; + flex-direction: column; + justify-content: flex-start; + + h1 { + margin-bottom: font-size(1); + } + p { + max-width: 70rem; + } +} diff --git a/src/styles/main.scss b/src/styles/main.scss new file mode 100644 index 0000000..fc0e187 --- /dev/null +++ b/src/styles/main.scss @@ -0,0 +1,4 @@ +@import "utils/INDEX"; +@import "base/INDEX"; +@import "layout/INDEX"; +@import "components/INDEX"; diff --git a/src/styles/utils/_INDEX.scss b/src/styles/utils/_INDEX.scss new file mode 100644 index 0000000..df91727 --- /dev/null +++ b/src/styles/utils/_INDEX.scss @@ -0,0 +1,3 @@ +@import "variables"; +@import "functions"; +@import "placeholders"; diff --git a/src/styles/utils/_functions.scss b/src/styles/utils/_functions.scss new file mode 100644 index 0000000..6f8556e --- /dev/null +++ b/src/styles/utils/_functions.scss @@ -0,0 +1,23 @@ +@function color($key: 'primary') { + @return map-get($theme-colors, $key); +} + +@function shades-primary($key: '1') { + @return map-get($shades-primary, $key); +} + +@function shades-accent($key: '1') { + @return map-get($shades-accent, $key); +} + +@function weight($key: 'regular') { + @return map-get($font-weights, $key); +} + +@function font-size($key: '0') { + @return map-get($modular-scale, $key); +} + +@function fonts($key: 'primary') { + @return map-get($fonts, $key); +} diff --git a/src/styles/utils/_placeholders.scss b/src/styles/utils/_placeholders.scss new file mode 100644 index 0000000..e8bf496 --- /dev/null +++ b/src/styles/utils/_placeholders.scss @@ -0,0 +1,11 @@ +%section-padding-top-bottom, { + padding: 6em 6em; +} +%flex-box { + display:flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; + align-content: flex-start; + align-items: flex-start; +} diff --git a/src/styles/utils/_variables.scss b/src/styles/utils/_variables.scss new file mode 100644 index 0000000..6f8f297 --- /dev/null +++ b/src/styles/utils/_variables.scss @@ -0,0 +1,43 @@ +$break: 50em; + +$theme-colors: ( + primary: #1e1d24, + accent: #97f1e7, + secondary: #bbbbbd, + base: #ffffff +); + +$shades-primary: ( + 1: mix(white, map-get($theme-colors, primary), 52.5%), + 2: mix(white, map-get($theme-colors, primary), 70%), + 3: mix(white, map-get($theme-colors, primary), 77%), + 4: mix(white, map-get($theme-colors, primary), 80%), + 5: mix(white, map-get($theme-colors, primary), 90%) +); + +$shades-accent: ( + -2: mix(black, map-get($theme-colors, accent), 40%), + -1: mix(black, map-get($theme-colors, accent), 15.4%), + 1: mix(white, map-get($theme-colors, accent), 50%) +); + +$font-primary: 'Ubuntu', sans-serif; +$font-secondary: 'Roboto Slab', sans-serif; + +$base-font-size: 16px; + +$modular-scale: ( + -1: 0.75rem, + 0: $base-font-size, + 1: 1.333rem, + 2: 1.777rem, + 3: 2.369rem, + 4: 3.157rem +); + +$font-weights: ( + light: 300, + regular: 400, + medium: 500, + bold: 700 +); diff --git a/styleguide.md b/styleguide.md new file mode 100644 index 0000000..211f978 --- /dev/null +++ b/styleguide.md @@ -0,0 +1,122 @@ +# Style Guide + +## Color Palette + +### Monochromatic Theme Colors + +``` +$theme-colors: ( + primary: #1e1d24, // + accent: #97f1e7, // + secondary: #bbbbbd, // + base: #ffffff // White +); +``` + +Example: +`color: color(primary);` + +### Misc. Colors + +``` +$shades-primary: ( + 1: mix(white, color(primary), 52.5%), + 2: mix(white, color(primary), 70%), + 3: mix(white, color(primary), 77%), + 4: mix(white, color(primary), 80%), + 5: mix(white, color(primary), 90%) +); +``` + +Example: +`border-color: shades-primary(1);` + +``` +$shades-accent: ( + -2: mix(black, color(accent), 40%), + -1: mix(black, color(accent), 15.4%); + 1: mix(white, color(accent), 50%); +); +``` + +Example: +`border-color: shades-accent(-1);` + +## Typography + +### Font Families + +``` +Ubuntu +Robotoslab +``` + +### Font Weights + +``` +$font-weights: ( + light: 300, + regular: 400, + medium: 500, + bold: 700 +); +``` + +Example: +`font-weight: weight(light);` + +### Font Sizes + +``` +Base: 24px; + +$modular-scale: ( // Perfect Fourth + -1: 0.75rem, + 0: 1rem, + 1: 1.333rem, + 2: 1.777rem, + 3: 2.369rem, + 4: 3.157rem +); +``` + +Example: +`font-size: font-size(3);` + +## Guide: Colors & Typography by Section + +### Colors +Header Background: color(primary); +Logo color: color(base); +Header Text: color(base); +Page Header: color(primary); +Page Description: shades-accent(-2); +Share & Social Links: shades-primary(3); +Blog Summary & Meta: shades-primary(2); +Prev, Next, 1/2/3: shades-primary(1); +Last Section P: shades-accent(1); +Contact Text: #000; +Footer Text: #000; +Border Bottom Page Description: shades-accent(-1); +Border Bottom Breadcrumbs, Border top footer: shades-primary(4); +Border Bottom Blog Post: shades-primary(5); +Border Prev/Next: shades-primary(1); +Social Links Border: shades-primary(2); + +### Font Styles +Logo: ---------- Ubuntu, Medium, 56pt font-size(3); +Navigation: ----- Ubuntu, Regular, 24pt font-size(0); +Page Header: ---- Robotoslab, Regular, 80pt 3.33em; +Page Desc: ------ Robotoslab, Light, 56pt font-size(3); 3.5em line-height +Breadcrumbs: ---- Ubuntu, bold, 24pt font-size(0); +Share: ---------- Ubuntu, medium, 24pt font-size(0); +Post h1: -------- Ubuntu, regular, 72pt 3em; +Post P: --------- Ubuntu, light, 42pt font-size(2); 2.75em line-height +Post Meta: ------ Ubuntu, light italic, 32pt font-size(1); +Post Read More:-- Ubuntu, medium, 24pt font-size(0); +Last Sect. h1: -- Ubuntu, medium, 72pt 3em; +Last Sect. p: --- Ubuntu, regular, 32pt font-size(1); 2.25em line-height +Last Sect. button:Ubuntu, medium, 28pt 1.167em; +Contact links: -- Ubuntu, regular, 36pt 1.5em; +Social links: --- Ubuntu, bold, 24pt font-size(0); +Footer: --------- Ubuntu, Regular, 22pt .917em;