├── .editorconfig ├── .gitignore ├── LICENSE.txt ├── README.md ├── package.json ├── screenshot.png ├── src ├── index.html └── main.css └── test └── portfolio.spec.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Code School 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build a Portfolio Using Bootstrap 2 | 3 | Welcome to the Bootstrap Portfolio Project! For this project, you'll be creating a personal web page to show off your work. We will test your HTML knowledge and then it will be up to you to use CSS to style your own page and make it unique. 4 | 5 | You’ll build a personal web page using Bootstrap, which should look similar to this one: 6 | 7 | ![Bootstrap Portfolio](http://courseware.codeschool.com.s3.amazonaws.com/projects/build-a-portfolio-using-bootstrap.png) 8 | 9 | ## What You’ll Use 10 | 11 | - HTML 12 | - Bootstrap 13 | 14 | We'll start you off with a few CSS styles that you can build on that should help get things started. 15 | 16 | ## What You’ll Learn 17 | 18 | You will further your Bootstrap skills, feel more comfortable writing Bootstrap code in a real-world scenario, and have the option of using this project as your real portfolio page. 19 | 20 | ## Live Demo 21 | 22 | [Check out this link](https://codeschool-project-demos.github.io/BootstrapPortfolioProject/) to see a working version of this project. Feel free to customize your project even further by adding more custom CSS styles to it once you've completed the steps. 23 | 24 | ## Setup 25 | 26 | Open this project’s directory in a text editor to complete this project. A text editor like [Atom](https://atom.io/) or [Sublime Text](https://www.sublimetext.com/) will do the job. You will make changes to the `src/index.html` file to satisfy the requirements. 27 | 28 | ## Tasks 29 | 30 | Complete the following tasks to finish this project. 31 | 32 | ### Create the Navigation Bar 33 | 34 | At the top of our page, we'll want to create a `navbar`. Here is a [sample navbar](https://github.com/codeschool/BootstrapPortfolioProject/wiki/Sample-Navigation-Bar) to help you get set up. Also feel free to check out Twitter Bootstrap's [documentation on navbars](http://getbootstrap.com/components/#navbar). MENU 35 | 36 | ### Name in Navbar 37 | 38 | Change the contents of the `` tag that has the `navbar-brand` CSS class to include your name instead of "Brand". MENU 39 | 40 | ### Menu in Navbar 41 | 42 | Change the contents of the `` tags that are within the `
  • ` menu elements. One tag should read "Home", and the other "About". MENU 43 | 44 | ### Create the Carousel 45 | 46 | Let's place a Bootstrap Carousel under our navbar. Here is a [sample carousel](https://github.com/codeschool/BootstrapPortfolioProject/wiki/Sample-Carousel) to help you get set up. You can always learn more about Bootstrap's Carousel in their [official documentation](http://getbootstrap.com/javascript/#carousel). MENU 47 | 48 | ### Carousel Items 49 | 50 | Change the three `

    ` and `

    ` tags that are found under each `.item` element within our carousel to include three tag-lines. You can either use similar content that was already available in the page _(under "Who I am", "What I do", etc)_ or come up with original content for yourself. MENU 51 | 52 | ### Using Bootstrap's Grid System 53 | 54 | Let's display some elements side-by-side using the Grid System. To do so, under our carousel, let's create three nested `

    ` tags. One should have the `marketing` CSS class, the next should have the `container` CSS class, and the third should have the `row` CSS class. MENU 55 | 56 | ### Creating the Marketing Columns 57 | 58 | Inside of `.row`, add three `div` elements with the `col-md-4` CSS class. Each of these elements should have a: - `span` tag with the `glyphicon` class, plus another class indicating [which icon you'd like to use](http://getbootstrap.com/components/#glyphicons-glyphs) _(ie. "glyphicon-music", or "glyphicon-camera", etc)_. - `h2` tag with a skill in it _(ie. "HTML & CSS", or "JavaScript", or "Design", etc)_. - `p` tag describing why you enjoy using each skill _(ie. "I enjoy making the web come to life with Angular", etc)_. MENU 59 | 60 | ### Create the Footer 61 | 62 | Under `.marketing`, let's create a `footer` element. Our footer needs to have a `div` inside it with the `container` CSS class. _You'll see the footer stick to the bottom of the page, which happens because of some styling code we made available._ MENU 63 | 64 | ### The Elements Inside our Footer 65 | 66 | Inside our footer container, let's create two elements: an `h3` tag with a title inviting your visitors to get in touch with you, and a `p` tag describing how your visitors can get in touch. MENU 67 | 68 | ### Cleaning Things Up 69 | 70 | Let's remove the `.header`, `.tagline`, `.skills` and `.contact` elements as the same information should all be part of the carousel, marketing and footer elements now. 71 | 72 | ## Next Steps 73 | 74 | Now that you’re done, we highly encourage you to open the `src/main.css` file and customize things as much as you’d like! 75 | 76 | You should also make your completed project available online so you can share your progress with others! One way of doing this is by using GitHub Pages. 77 | 78 | To deploy your `/src` directory to GitHub Pages, be sure to commit all of your changes and make a new branch called `gh-pages`. Once you are checked into the `gh-pages` branch, run the following command: 79 | 80 | ``` 81 | git subtree push --prefix src origin gh-pages 82 | ``` 83 | 84 | This will push the `src` folder up to GitHub on the `gh-pages` branch. After that, you should be able to open up `http://username.github.io/BootstrapPortfolioProject`, where `username` is your GitHub username. 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project-html-portfolio", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "scripts": { 7 | "start": "browser-sync start --server ./src --files ./src", 8 | "test": "mocha --compilers js:babel-register test/*.spec.js" 9 | }, 10 | "author": "Sergio Cruz ", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "babel-preset-es2015": "^6.18.0", 14 | "babel-register": "^6.18.0", 15 | "browser-sync": "^2.14.0", 16 | "chai": "^3.5.0", 17 | "jsdom": "^9.4.1", 18 | "mocha": "^3.0.1" 19 | }, 20 | "engines": { 21 | "node": ">=4.6", 22 | "npm": ">=2.15" 23 | }, 24 | "babel": { 25 | "presets": ["es2015"] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeschool-projects/BootstrapPortfolioProject/5dc3c7c78e0325879d1d525799e69e80f3998d57/screenshot.png -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | HTML Portfolio 9 | 10 | 11 | 12 | 15 |
    16 |

    Morgan McCircuit

    17 |

    I write code

    18 |
    19 | 20 |
    21 |

    Who I am

    22 |

    Hi, my name is Morgan and I love to write code that is efficient.

    23 |
    24 | 25 | 26 |
    27 |

    What I do

    28 |

    Here are some of the languages I use on a day-to-day.

    29 |
      30 |
    • HTML
    • 31 |
    • CSS
    • 32 |
    • JavaScript
    • 33 |
    34 |
    35 | 36 |
    40 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/main.css: -------------------------------------------------------------------------------- 1 | /* NAVBAR 2 | -------------------------------------------------- */ 3 | 4 | .navbar { 5 | margin-bottom: 0; 6 | } 7 | 8 | /* CUSTOMIZED CAROUSEL 9 | -------------------------------------------------- */ 10 | 11 | /* Carousel base class */ 12 | .carousel { 13 | height: 240px; 14 | margin-bottom: 30px; 15 | } 16 | /* Since positioning the image, we need to help out the caption */ 17 | .carousel-caption { 18 | z-index: 10; 19 | } 20 | 21 | /* Declare heights because of positioning of img element */ 22 | .carousel .item { 23 | height: 240px; 24 | background-color: #777; 25 | } 26 | .carousel-inner > .item > img { 27 | position: absolute; 28 | top: 0; 29 | left: 0; 30 | min-width: 100%; 31 | height: 240px; 32 | } 33 | 34 | @media (min-width: 768px) { 35 | 36 | /* Bump up size of carousel content */ 37 | .carousel-caption p { 38 | margin-bottom: 20px; 39 | font-size: 21px; 40 | line-height: 1.4; 41 | } 42 | 43 | } 44 | 45 | /* MARKETING 46 | -------------------------------------------------- */ 47 | 48 | .marketing { 49 | margin: 60px auto; 50 | } 51 | 52 | .marketing .col-md-4 { 53 | text-align: center; 54 | font-size: 16px; 55 | } 56 | 57 | .marketing .col-md-4 .glyphicon { 58 | font-size: 60px; 59 | background-color: #EAEAEA; 60 | border-radius: 50%; 61 | padding: 40px; 62 | color: #444; 63 | } 64 | 65 | 66 | /* STICKY FOOTER 67 | -------------------------------------------------- */ 68 | 69 | html { 70 | position: relative; 71 | min-height: 100%; 72 | } 73 | 74 | body { 75 | /* Margin bottom by footer height */ 76 | margin-bottom: 160px; 77 | } 78 | 79 | .footer, 80 | footer { 81 | position: absolute; 82 | bottom: 0; 83 | width: 100%; 84 | /* Set the fixed height of the footer here */ 85 | height: 160px; 86 | background: #444; 87 | color: white; 88 | text-shadow: 1px 1px 1px black; 89 | padding: 40px 0; 90 | } 91 | 92 | /* RESPONSIVE CSS 93 | -------------------------------------------------- */ 94 | -------------------------------------------------------------------------------- /test/portfolio.spec.js: -------------------------------------------------------------------------------- 1 | // Libraries 2 | const fs = require('fs'); 3 | const jsdom = require('jsdom'); 4 | const { assert } = require('chai'); 5 | 6 | // HTML 7 | const srcHtml = fs.readFileSync('./src/index.html'); 8 | const doc = jsdom.jsdom(srcHtml); 9 | 10 | // Tests 11 | describe('The webpage', () => { 12 | 13 | /** 14 | * STEP 1: NAVBAR 15 | */ 16 | describe('navbar', () => { 17 | let navbar; 18 | 19 | beforeEach( 20 | () => navbar = doc.querySelector('.navbar') 21 | ); 22 | 23 | it('should exist @navbar', () => { 24 | assert.isOk(navbar, 'We need a `.navbar` element.'); 25 | }); 26 | 27 | it('should have changed the contents of `.navbar-brand` @navbar-brand', () => { 28 | assert.isOk(navbar, 'We need a `.navbar` element.'); 29 | const el = navbar.querySelector('.navbar-brand'); 30 | assert(el, 'Our `.navbar` needs a `.navbar-brand` element.'); 31 | assert('brand' !== el.textContent.trim().toLowerCase(), 'The `.navbar-brand` element needs a text that is different than the default "Brand".'); 32 | }); 33 | 34 | it('should have have changed menu items @navbar-menu', () => { 35 | assert.isOk(navbar, 'We need a `.navbar` element.'); 36 | const menuItems = Array.from(navbar.querySelectorAll('.nav li a')); 37 | assert(menuItems.length >= 2, 'Our menu needs at least 2 `li` elements.'); 38 | 39 | // Does home link exist? 40 | const homeLink = menuItems.find(el => /home/i.test(el.textContent)); 41 | assert(homeLink, 'Our menu needs at least one link that reads "Home".'); 42 | 43 | // Does about link exist? 44 | const aboutLink = menuItems.find(el => /about/i.test(el.textContent)); 45 | assert(aboutLink, 'Our menu needs at least one link that reads "About".'); 46 | }); 47 | }); 48 | 49 | /** 50 | * STEP 2: CAROUSEL 51 | */ 52 | describe('carousel', () => { 53 | 54 | let carousel; 55 | 56 | beforeEach(() => carousel = doc.querySelector('.carousel')); 57 | 58 | it('should exist @carousel', () => { 59 | assert(carousel, 'We need a `.carousel` element.'); 60 | }); 61 | 62 | it('should have at least 3 items and have them modified @carousel-items', () => { 63 | assert(carousel, 'We need a `.carousel` element.'); 64 | const items = Array.from(carousel.querySelectorAll('.item')); 65 | assert(items.length >= 3, 'Our carousel needs at least 3 `.item` elements.'); 66 | 67 | items.forEach(item => { 68 | 69 | // Does each item contain an `

    ` tag? 70 | const h1 = item.querySelector('h1'); 71 | assert(h1, 'Each carousel item needs an `h1` element.'); 72 | assert( 73 | h1.textContent.trim() !== 'Example headline', 74 | 'Every `h1` inside the carousel items need a customized text content.' 75 | ); 76 | 77 | // Does each item contain an `

    ` tag? 78 | const p = item.querySelector('p'); 79 | assert(p, 'Each carousel item needs a `p` tag.'); 80 | 81 | assert( 82 | !p.textContent.trim().startsWith('Cras justo odio'), 83 | 'It does not look like you have customized every `p` tag inside the carousel items..' 84 | ); 85 | }); 86 | }); 87 | }); 88 | 89 | /** 90 | * STEP 3: GRID SYSTEM 91 | */ 92 | describe('marketing grid', () => { 93 | let marketing; 94 | 95 | beforeEach(() => marketing = doc.querySelector('.marketing')); 96 | 97 | it('should exist @marketing', () => { 98 | assert(marketing, 'We need a `.marketing` element.'); 99 | }); 100 | 101 | it('should have a cointainer @marketing', () => { 102 | assert(marketing, 'We need a `.marketing` element.'); 103 | const el = marketing.querySelector('.container'); 104 | assert(el, 'Our `.marketing` element needs a `.container` element.'); 105 | }); 106 | 107 | it('should have a row @marketing', () => { 108 | assert(marketing, 'We need a `.marketing` element.'); 109 | const el = marketing.querySelector('.container .row'); 110 | assert(el, 'Our marketing `.container` needs a `.row` element.'); 111 | }); 112 | 113 | describe('columns', () => { 114 | let columns = []; 115 | 116 | beforeEach(() => { 117 | if (marketing) { 118 | columns = Array.from(marketing.querySelectorAll('.row .col-sm-4, .row .col-md-4, .row .col-lg-4')); 119 | } 120 | }); 121 | 122 | it('should exist at least 3 @marketing-columns', () => { 123 | assert(columns.length === 3, 'Our `.row` element needs at least 3 column elements.'); 124 | }); 125 | 126 | it('should have an icon with the glyphicon icon class @marketing-columns', () => { 127 | columns.forEach(column => { 128 | const icon = column.querySelector('.glyphicon'); 129 | const iconClasses = Array.from(icon.classList); 130 | assert(icon, 'Every marketing row column needs an icon.'); 131 | assert( 132 | iconClasses.find(className => className.startsWith('glyphicon-')), 133 | 'Our marketing icons needs a class that starts with `glyphicon-` to describe which icon graphic it should contain.' 134 | ); 135 | }); 136 | }); 137 | 138 | it('should have a non-empty `

    ` title @marketing-columns', () => { 139 | columns.forEach(column => { 140 | const h2 = column.querySelector('h2'); 141 | assert(h2, 'Every marketing column needs an `h2` element.'); 142 | assert(h2.textContent.trim() !== '', 'Our marketing column `h2` elements cannot be empty.'); 143 | }) 144 | }); 145 | 146 | it('should have a non-empty paragraph @marketing-columns', () => { 147 | columns.forEach(column => { 148 | const p = column.querySelector('p'); 149 | assert(p, 'Every marketing column needs a `p` element.'); 150 | assert(p.textContent.trim() !== '', 'Our marketing column `p` elements cannot be empty.'); 151 | }); 152 | }); 153 | }); 154 | }); 155 | 156 | 157 | /** 158 | * STEP 3: GRID SYSTEM 159 | */ 160 | describe('footer', () => { 161 | let footer; 162 | beforeEach(() => footer = doc.querySelector('.footer, footer')); 163 | 164 | it('should exist @footer', () => { 165 | assert.isOk(footer, 'We need a `footer` element.'); 166 | }); 167 | 168 | it('should have a container @footer', () => { 169 | assert.isOk(footer, 'We need a `footer` element.'); 170 | const el = footer.querySelector('.container'); 171 | assert.isOk(el, 'We need a `.container` element inside our `footer`.'); 172 | }); 173 | }); 174 | 175 | describe('footer-elements', () => { 176 | let footer; 177 | beforeEach(() => footer = doc.querySelector('.footer, footer')); 178 | 179 | it('should exist @footer-elements', () => { 180 | assert.isOk(footer, 'We need a `footer` element.'); 181 | }); 182 | 183 | it('should have a non-empty `

    ` title @footer-elements', () => { 184 | const el = footer && footer.querySelector('.container h3'); 185 | assert.isOk(el, 'Our footer needs an `h3` element.'); 186 | assert.isOk(el.textContent.trim() !== '', 'Our footer\'s `h3` element cannot be empty'); 187 | }); 188 | 189 | it('should have a non-empty `

    ` tag @footer-elements', () => { 190 | const el = footer && footer.querySelector('.container p'); 191 | assert.isOk(el, 'Our footer needs a `p` element.'); 192 | assert.isOk(el.textContent.trim() !== '', 'Our footer\'s `p` element cannot be empty'); 193 | }); 194 | }) 195 | 196 | /** 197 | * STEP 5: ELEMENTS THAT SHOULD BE REMOVED 198 | */ 199 | describe('will have removed and', () => { 200 | it('should not contain the `.header` element @removal', () => { 201 | const el = doc.querySelector('body > .header'); 202 | assert.isNotOk(el, 'Our old `.header` element needs to be removed.'); 203 | }); 204 | 205 | it('should not contain the `.contact` element @removal', () => { 206 | const el = doc.querySelector('body > .contact'); 207 | assert.isNotOk(el, 'Our old `.contact` element needs to be removed.'); 208 | }); 209 | 210 | it('should not contain the `.tagline` element @removal', () => { 211 | const el = doc.querySelector('body > .tagline'); 212 | assert.isNotOk(el, 'Our old `.tagline` element needs to be removed.'); 213 | }); 214 | }); 215 | 216 | }); 217 | --------------------------------------------------------------------------------