├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── LICENSE ├── README.md ├── assets └── images │ └── the-odin-project.png └── courses ├── foundations ├── calculator │ ├── assets │ │ └── style.css │ ├── index.html │ └── js │ │ └── script.js ├── etch-a-sketch │ ├── assets │ │ └── style.css │ ├── index.html │ └── js │ │ └── script.js ├── landing-page │ ├── assets │ │ └── style.css │ ├── designs │ │ ├── colours-and-font-styles.png │ │ └── full-design.png │ └── index.html ├── odin-recipes │ ├── assets │ │ ├── images │ │ │ ├── ice-cream.jpg │ │ │ ├── lasagna.jpg │ │ │ └── pizza.jpg │ │ └── style.css │ ├── index.html │ └── recipes │ │ ├── ice-cream.html │ │ ├── lasagna.html │ │ └── pizza.html └── rock-paper-scissors │ ├── assets │ ├── images │ │ ├── paper.png │ │ ├── rock.png │ │ └── scissors.png │ └── style.css │ ├── index.html │ └── js │ ├── constants.js │ ├── script.js │ └── utils.js └── full-stack-javascript ├── html-css ├── admin-dashboard │ ├── assets │ │ ├── icons.css │ │ ├── icons │ │ │ ├── account-group.svg │ │ │ ├── account.svg │ │ │ ├── bell-ring-outline.svg │ │ │ ├── clock.svg │ │ │ ├── cog.svg │ │ │ ├── eye-plus-outline.svg │ │ │ ├── help-circle.svg │ │ │ ├── home.svg │ │ │ ├── magnify.svg │ │ │ ├── message.svg │ │ │ ├── note-multiple.svg │ │ │ ├── shield-check.svg │ │ │ ├── source-fork.svg │ │ │ ├── star-plus-outline.svg │ │ │ └── view-dashboard.svg │ │ └── style.css │ ├── designs │ │ └── dashboard-project.png │ └── index.html └── sign-up-form │ ├── assets │ ├── fonts │ │ └── Norse-Bold.otf │ ├── images │ │ ├── odin-background.avif │ │ └── odin-lined.png │ └── style.css │ ├── designs │ └── design-file.png │ └── index.html ├── javascript └── library │ ├── index.html │ └── src │ └── library.js ├── nodejs ├── basic-informational-site │ ├── 404.html │ ├── about.html │ ├── contact-me.html │ ├── index.html │ └── index.js └── mini-message-board │ ├── app.js │ ├── package-lock.json │ ├── package.json │ ├── routes │ └── index.js │ └── views │ ├── form.ejs │ ├── index.ejs │ └── message.ejs └── react └── cv-application ├── .gitignore ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── src ├── App.css ├── App.jsx ├── components │ └── TextInput.jsx ├── data │ └── formFields.js ├── index.css └── main.jsx └── vite.config.js /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Single deploy job since we're just deploying 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | - name: Setup Pages 35 | uses: actions/configure-pages@v5 36 | - name: Upload artifact 37 | uses: actions/upload-pages-artifact@v3 38 | with: 39 | # Upload entire repository 40 | path: '.' 41 | - name: Deploy to GitHub Pages 42 | id: deployment 43 | uses: actions/deploy-pages@v4 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | # Environment Variables 11 | .env 12 | 13 | node_modules 14 | dist 15 | dist-ssr 16 | *.local 17 | 18 | # Editor directories and files 19 | .vscode/* 20 | !.vscode/extensions.json 21 | .idea 22 | .DS_Store 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Sam Hillier 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 | # The Odin Project 2 | 3 |
4 | The Odin Project 5 |
6 | 7 | --- 8 | 9 | The Odin Project provides a free open source coding curriculum that can be taken entirely online. This repository documents my personal progress through the course. 10 | 11 | ## Contents 12 | 13 | ### Foundations 14 | 15 | | Project | Technology | Code | Demo | 16 | | ---------- | ------------------- | -------------- | -------------- | 17 | | Odin Recipes | [![HTML](https://img.shields.io/badge/HTML-%23E34F26.svg?logo=html5&logoColor=white)](#) [![CSS](https://img.shields.io/badge/CSS-1572B6?logo=css3&logoColor=fff)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/foundations/odin-recipes/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/foundations/odin-recipes/) | 18 | | Landing Page | [![HTML](https://img.shields.io/badge/HTML-%23E34F26.svg?logo=html5&logoColor=white)](#) [![CSS](https://img.shields.io/badge/CSS-1572B6?logo=css3&logoColor=fff)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/foundations/landing-page/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/foundations/landing-page/) | 19 | | Rock Paper Scissors | [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/foundations/rock-paper-scissors/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/foundations/rock-paper-scissors/) | 20 | | Etch-a-Sketch | [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/foundations/etch-a-sketch/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/foundations/etch-a-sketch/) | 21 | | Calculator | [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/foundations/calculator/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/foundations/calculator/) | 22 | 23 | ### Full Stack JavaScript 24 | 25 | #### HTML & CSS 26 | 27 | | Project | Technology | Code | Demo | 28 | | -------- | --------------- | -------------- | -------------- | 29 | | Sign-up Form | [![HTML](https://img.shields.io/badge/HTML-%23E34F26.svg?logo=html5&logoColor=white)](#) [![CSS](https://img.shields.io/badge/CSS-1572B6?logo=css3&logoColor=fff)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/html-css/sign-up-form/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/html-css/sign-up-form/) | 30 | | Admin Dashboard | [![HTML](https://img.shields.io/badge/HTML-%23E34F26.svg?logo=html5&logoColor=white)](#) [![CSS](https://img.shields.io/badge/CSS-1572B6?logo=css3&logoColor=fff)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/html-css/admin-dashboard/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/html-css/admin-dashboard/) | 31 | | Homepage | [![HTML](https://img.shields.io/badge/HTML-%23E34F26.svg?logo=html5&logoColor=white)](#) [![CSS](https://img.shields.io/badge/CSS-1572B6?logo=css3&logoColor=fff)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/html-css/homepage/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/html-css/homepage/) | 32 | 33 | #### JavaScript 34 | 35 | | Project | Technology | Code | Demo | 36 | | ---------- | ------------------- | -------------- | -------------- | 37 | | Library | [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/javascript/library/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/javascript/library/) | 38 | | Tic Tac Toe | [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/javascript/tic-tac-toe/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/javascript/tic-tac-toe/) | 39 | | Restaurant Page | [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/javascript/restaurant-page/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/javascript/restaurant-page/) | 40 | | Todo List | [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/javascript/todo-list/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/javascript/todo-list/) | 41 | | Weather App | [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/javascript/weather-app/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/javascript/weather-app/) | 42 | | Recursion | [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/javascript/recursion/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/javascript/recursion/) | 43 | | Linked Lists | [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/javascript/linked-lists/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/javascript/linked-lists/) | 44 | | HashMap | [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/javascript/hashmap/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/javascript/hashmap/) | 45 | | Binary Search Trees | [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/javascript/binary-search-trees/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/javascript/binary-search-trees/) | 46 | | Knights Travails | [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/javascript/knights-travails/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/javascript/knights-travails/) | 47 | | Testing Practice | [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/javascript/testing-practice/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/javascript/testing-practice/) | 48 | | Battleship | [![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=000)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/javascript/battleship/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/javascript/battleship/) | 49 | 50 | #### React 51 | 52 | | Project | Technology | Code | Demo | 53 | | ------- | -------------- | -------------- | -------------- | 54 | | CV Application | [![React](https://img.shields.io/badge/React-%2320232a.svg?logo=react&logoColor=%2361DAFB)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/react/cv-application/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/react/cv-application/) | 55 | | Memory Card | [![React](https://img.shields.io/badge/React-%2320232a.svg?logo=react&logoColor=%2361DAFB)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/react/memory-card/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/react/memory-card/) | 56 | | Shopping Cart | [![React](https://img.shields.io/badge/React-%2320232a.svg?logo=react&logoColor=%2361DAFB)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/react/shopping-cart/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/react/shopping-cart/) | 57 | 58 | #### Node.js 59 | 60 | | Project | Technology | Code | Demo | 61 | | ------- | ------------------------ | -------------- | -------------- | 62 | | Basic Informational Site | [![Node.js](https://img.shields.io/badge/Node.js-6DA55F?logo=node.js&logoColor=white)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/nodejs/basic-informational-site/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/nodejs/basic-informational-site/) | 63 | | Mini Message Board | [![Node.js](https://img.shields.io/badge/Node.js-6DA55F?logo=node.js&logoColor=white)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/nodejs/mini-message-board/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/nodejs/mini-message-board/) | 64 | | Inventory Application | [![Node.js](https://img.shields.io/badge/Node.js-6DA55F?logo=node.js&logoColor=white)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/nodejs/inventory-application/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/nodejs/inventory-application/) | 65 | | Members Only | [![Node.js](https://img.shields.io/badge/Node.js-6DA55F?logo=node.js&logoColor=white)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/nodejs/members-only/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/nodejs/members-only/) | 66 | | File Uploader | [![Node.js](https://img.shields.io/badge/Node.js-6DA55F?logo=node.js&logoColor=white)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/nodejs/file-uploader/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/nodejs/file-uploader/) | 67 | | Blog API | [![Node.js](https://img.shields.io/badge/Node.js-6DA55F?logo=node.js&logoColor=white)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/nodejs/blog-api/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/nodejs/blog-api/) | 68 | | Where's Waldo | [![Node.js](https://img.shields.io/badge/Node.js-6DA55F?logo=node.js&logoColor=white)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/nodejs/wheres-waldo/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/nodejs/wheres-waldo/) | 69 | | Messaging App | [![Node.js](https://img.shields.io/badge/Node.js-6DA55F?logo=node.js&logoColor=white)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/nodejs/messaging-app/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/nodejs/messaging-app/) | 70 | | Odin-Book | [![Node.js](https://img.shields.io/badge/Node.js-6DA55F?logo=node.js&logoColor=white)](#) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-javascript/nodejs/odin-book/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-javascript/nodejs/odin-book/) | 71 | 72 | ### Full Stack Ruby on Rails 73 | 74 | #### Ruby 75 | 76 | | Project | Technology | Code | Demo | 77 | | ------- | ------------------ | -------------- | -------------- | 78 | | Caesar Cipher | ![Ruby](https://img.shields.io/badge/Ruby-%23CC342D.svg?&logo=ruby&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/ruby/caesar-cipher/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/ruby/caesar-cipher/) | 79 | | Sub Strings | ![Ruby](https://img.shields.io/badge/Ruby-%23CC342D.svg?&logo=ruby&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/ruby/sub-strings/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/ruby/sub-strings/) | 80 | | Stock Picker | ![Ruby](https://img.shields.io/badge/Ruby-%23CC342D.svg?&logo=ruby&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/ruby/stock-picker/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/ruby/stock-picker/) | 81 | | Bubble Sort | ![Ruby](https://img.shields.io/badge/Ruby-%23CC342D.svg?&logo=ruby&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/ruby/bubble-sort/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/ruby/bubble-sort/) | 82 | | Tic Tac Toe | ![Ruby](https://img.shields.io/badge/Ruby-%23CC342D.svg?&logo=ruby&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/ruby/tic-tac-toe/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/ruby/tic-tac-toe/) | 83 | | Mastermind | ![Ruby](https://img.shields.io/badge/Ruby-%23CC342D.svg?&logo=ruby&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/ruby/mastermind/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/ruby/mastermind/) | 84 | | Event Manager | ![Ruby](https://img.shields.io/badge/Ruby-%23CC342D.svg?&logo=ruby&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/ruby/event-manager/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/ruby/event-manager/) | 85 | | Hangman | ![Ruby](https://img.shields.io/badge/Ruby-%23CC342D.svg?&logo=ruby&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/ruby/hangman/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/ruby/hangman/) | 86 | | Custom Enumerables | ![Ruby](https://img.shields.io/badge/Ruby-%23CC342D.svg?&logo=ruby&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/ruby/custom-enumerables/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/ruby/custom-enumerables/) | 87 | | Connect Four | ![Ruby](https://img.shields.io/badge/Ruby-%23CC342D.svg?&logo=ruby&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/ruby/connect-four/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/ruby/connect-four/) | 88 | | Ruby Final Project | ![Ruby](https://img.shields.io/badge/Ruby-%23CC342D.svg?&logo=ruby&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/ruby/ruby-final-project/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/ruby/ruby-final-project/) | 89 | 90 | #### Ruby on Rails 91 | 92 | | Project | Technology | Code | Demo | 93 | | ------- | --------------------------- | -------------- | -------------- | 94 | | Blog App | ![Rails](https://img.shields.io/badge/Rails-%23CC0000.svg?&logo=ruby-on-rails&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/rails/blog-app/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/rails/blog-app/) | 95 | | Micro-Reddit | ![Rails](https://img.shields.io/badge/Rails-%23CC0000.svg?&logo=ruby-on-rails&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/rails/micro-reddit/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/rails/micro-reddit/) | 96 | | Forms | ![Rails](https://img.shields.io/badge/Rails-%23CC0000.svg?&logo=ruby-on-rails&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/rails/forms/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/rails/forms/) | 97 | | Members Only | ![Rails](https://img.shields.io/badge/Rails-%23CC0000.svg?&logo=ruby-on-rails&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/rails/members-only/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/rails/members-only/) | 98 | | Private Events | ![Rails](https://img.shields.io/badge/Rails-%23CC0000.svg?&logo=ruby-on-rails&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/rails/private-events/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/rails/private-events/) | 99 | | Flight Booker | ![Rails](https://img.shields.io/badge/Rails-%23CC0000.svg?&logo=ruby-on-rails&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/rails/flight-booker/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/rails/flight-booker/) | 100 | | Kittens API | ![Rails](https://img.shields.io/badge/Rails-%23CC0000.svg?&logo=ruby-on-rails&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/rails/kittens-api/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/rails/kittens-api/) | 101 | | Flickr API | ![Rails](https://img.shields.io/badge/Rails-%23CC0000.svg?&logo=ruby-on-rails&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/rails/flickr-api/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/rails/flickr-api/) | 102 | | Sending Confirmation Emails | ![Rails](https://img.shields.io/badge/Rails-%23CC0000.svg?&logo=ruby-on-rails&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/rails/sending-confirmation-emails/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/rails/sending-confirmation-emails/) | 103 | | Rails Final Project | ![Rails](https://img.shields.io/badge/Rails-%23CC0000.svg?&logo=ruby-on-rails&logoColor=white) | [View Code](https://github.com/SamHillierDev/the-odin-project/tree/main/courses/full-stack-ruby-on-rails/rails/rails-final-project/) | [View Demo](https://samhillierdev.github.io/the-odin-project/courses/full-stack-ruby-on-rails/rails/rails-final-project/) | 104 | 105 | ## Security Vulnerabilities 106 | 107 | Please review our [security policy](https://github.com/SamHillierDev/the-odin-project/security/policy) on how to report security vulnerabilities. 108 | 109 | ## Credits 110 | 111 | - [The Odin Project](https://theodinproject.com/) 112 | 113 | ## License 114 | 115 | Please see [License File](https://github.com/SamHillierDev/the-odin-project/blob/main/LICENSE) for more information. 116 | -------------------------------------------------------------------------------- /assets/images/the-odin-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamHillierDev/the-odin-project/cb415e792c9ad553ecd8692917c99a80ff9ba3e8/assets/images/the-odin-project.png -------------------------------------------------------------------------------- /courses/foundations/calculator/assets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | background-color: #f3f4f6; 4 | color: #333; 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | height: 100vh; 9 | margin: 0; 10 | flex-direction: column; 11 | } 12 | 13 | .calculator { 14 | background: #ffffff; 15 | border-radius: 10px; 16 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 17 | padding: 20px; 18 | } 19 | 20 | h1 { 21 | text-align: center; 22 | font-size: 24px; 23 | color: #4a5568; 24 | } 25 | 26 | output { 27 | display: block; 28 | background: #f7fafc; 29 | border: 1px solid #e2e8f0; 30 | border-radius: 5px; 31 | padding: 15px; 32 | font-size: 24px; 33 | text-align: right; 34 | margin-bottom: 20px; 35 | } 36 | 37 | div > div { 38 | display: flex; 39 | gap: 10px; 40 | margin-bottom: 10px; 41 | } 42 | 43 | button { 44 | flex: 1; 45 | padding: 15px; 46 | font-size: 18px; 47 | font-weight: bold; 48 | border: none; 49 | border-radius: 5px; 50 | cursor: pointer; 51 | background-color: #e2e8f0; 52 | color: #333; 53 | transition: all 0.2s; 54 | min-width: 60px; 55 | } 56 | 57 | button:hover { 58 | background-color: #cbd5e0; 59 | } 60 | 61 | button:active { 62 | background-color: #a0aec0; 63 | } 64 | 65 | button[aria-label="Delete"], 66 | button[aria-label="All Clear"] { 67 | background-color: #f56565; 68 | color: #fff; 69 | flex: 1; 70 | text-align: center; 71 | flex-shrink: 0; 72 | } 73 | 74 | button[aria-label="Delete"]:hover, 75 | button[aria-label="All Clear"]:hover { 76 | background-color: #e53e3e; 77 | } 78 | 79 | button[aria-label="Multiply"], 80 | button[aria-label="Divide"], 81 | button[aria-label="Add"], 82 | button[aria-label="Subtract"] { 83 | background-color: #63b3ed; 84 | color: #fff; 85 | } 86 | 87 | button[aria-label="Multiply"]:hover, 88 | button[aria-label="Divide"]:hover, 89 | button[aria-label="Add"]:hover, 90 | button[aria-label="Subtract"]:hover { 91 | background-color: #4299e1; 92 | } 93 | -------------------------------------------------------------------------------- /courses/foundations/calculator/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Calculator 7 | 8 | 9 | 10 |

Calculator

11 |
12 | 0 13 |
14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 | 36 | 37 | 38 |
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /courses/foundations/calculator/js/script.js: -------------------------------------------------------------------------------- 1 | const display = document.querySelector("output"); 2 | const buttons = document.querySelectorAll("button"); 3 | 4 | let firstNumber = ""; 5 | let secondNumber = ""; 6 | let operator = null; 7 | let shouldResetDisplay = false; 8 | 9 | function add(a, b) { 10 | return a + b; 11 | } 12 | 13 | function subtract(a, b) { 14 | return a - b; 15 | } 16 | 17 | function multiply(a, b) { 18 | return a * b; 19 | } 20 | 21 | function divide(a, b) { 22 | if (b === 0) { 23 | return "Can't divide by 0!"; 24 | } 25 | return a / b; 26 | } 27 | 28 | function operate(operator, a, b) { 29 | a = parseFloat(a); 30 | b = parseFloat(b); 31 | switch (operator) { 32 | case "+": 33 | return add(a, b); 34 | case "-": 35 | return subtract(a, b); 36 | case "x": 37 | return multiply(a, b); 38 | case "/": 39 | return divide(a, b); 40 | default: 41 | return null; 42 | } 43 | } 44 | 45 | function updateDisplay(value) { 46 | if (shouldResetDisplay) { 47 | display.textContent = value; 48 | shouldResetDisplay = false; 49 | } else { 50 | display.textContent = 51 | display.textContent === "0" ? value : display.textContent + value; 52 | } 53 | } 54 | 55 | function clear() { 56 | display.textContent = "0"; 57 | firstNumber = ""; 58 | secondNumber = ""; 59 | operator = null; 60 | } 61 | 62 | function deleteLast() { 63 | display.textContent = display.textContent.slice(0, -1) || "0"; 64 | } 65 | 66 | function handleButtonClick(e) { 67 | const button = e.target; 68 | const value = button.textContent; 69 | 70 | if (!isNaN(value)) { 71 | if (operator && !secondNumber) shouldResetDisplay = true; 72 | updateDisplay(value); 73 | } else if (value === ".") { 74 | if (!display.textContent.includes(".")) { 75 | updateDisplay(value); 76 | } 77 | } else if (value === "AC") { 78 | clear(); 79 | } else if (value === "DEL") { 80 | deleteLast(); 81 | } else if (value === "=") { 82 | if (firstNumber && operator && display.textContent) { 83 | secondNumber = display.textContent; 84 | const result = operate(operator, firstNumber, secondNumber); 85 | display.textContent = Math.round(result * 1000) / 1000; 86 | firstNumber = display.textContent; 87 | secondNumber = ""; 88 | operator = null; 89 | } 90 | } else { 91 | if (!firstNumber) { 92 | firstNumber = display.textContent; 93 | operator = value; 94 | shouldResetDisplay = true; 95 | } else if (!secondNumber) { 96 | operator = value; 97 | shouldResetDisplay = true; 98 | } else { 99 | secondNumber = display.textContent; 100 | const result = operate(operator, firstNumber, secondNumber); 101 | display.textContent = Math.round(result * 1000) / 1000; 102 | firstNumber = display.textContent; 103 | secondNumber = ""; 104 | operator = value; 105 | } 106 | } 107 | } 108 | 109 | buttons.forEach((button) => 110 | button.addEventListener("click", handleButtonClick) 111 | ); 112 | -------------------------------------------------------------------------------- /courses/foundations/etch-a-sketch/assets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | margin: 0 auto; 7 | padding: 0; 8 | min-height: 100vh; 9 | font-family: Arial, sans-serif; 10 | background-color: #f0f0f0; 11 | gap: 10px; 12 | } 13 | 14 | h1 { 15 | margin: 0; 16 | } 17 | 18 | label { 19 | font-size: 1rem; 20 | color: #555; 21 | } 22 | 23 | input[type="number"] { 24 | padding: 5px 10px; 25 | font-size: 1rem; 26 | border: 1px solid #ccc; 27 | border-radius: 5px; 28 | width: 60px; 29 | text-align: center; 30 | } 31 | 32 | button { 33 | padding: 5px 15px; 34 | font-size: 1rem; 35 | color: white; 36 | background-color: #007bff; 37 | border: none; 38 | border-radius: 5px; 39 | cursor: pointer; 40 | transition: background-color 0.3s ease; 41 | } 42 | 43 | button:hover { 44 | background-color: #0056b3; 45 | } 46 | 47 | button:disabled { 48 | background-color: #e0e0e0; 49 | cursor: not-allowed; 50 | } 51 | 52 | .grid-container { 53 | display: flex; 54 | flex-direction: column; 55 | gap: 20px; 56 | } 57 | 58 | #error { 59 | color: red; 60 | } 61 | -------------------------------------------------------------------------------- /courses/foundations/etch-a-sketch/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Etch-a-Sketch 7 | 8 | 9 | 10 |

Etch-a-Sketch

11 |
12 | 13 | 14 | 15 |
16 |
17 |
18 | 19 | 23 | 27 |
28 |
29 |
30 | 31 |
32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /courses/foundations/etch-a-sketch/js/script.js: -------------------------------------------------------------------------------- 1 | const gridContainer = document.querySelector(".grid"); 2 | const generateButton = document.getElementById("generate-grid"); 3 | const resetButton = document.getElementById("reset-grid"); 4 | const gridSizeInput = document.getElementById("grid-size"); 5 | const randomColorCheckbox = document.getElementById("random-color-checkbox"); 6 | const darkenCheckbox = document.getElementById("darken-checkbox"); 7 | 8 | let globalHue = 0; 9 | 10 | randomColorCheckbox.addEventListener("change", () => { 11 | if (randomColorCheckbox.checked) { 12 | darkenCheckbox.checked = false; 13 | } 14 | }); 15 | 16 | darkenCheckbox.addEventListener("change", () => { 17 | if (darkenCheckbox.checked) { 18 | randomColorCheckbox.checked = false; 19 | } 20 | }); 21 | 22 | gridContainer.style.display = "grid"; 23 | gridContainer.style.width = "400px"; 24 | gridContainer.style.height = "400px"; 25 | gridContainer.style.borderRadius = "5px"; 26 | gridContainer.style.overflow = "hidden"; 27 | 28 | const colorPicker = document.getElementById("color-picker"); 29 | 30 | function applyColor(square) { 31 | if (randomColorCheckbox.checked) { 32 | globalHue = (globalHue + 30) % 360; 33 | square.style.backgroundColor = `hsl(${globalHue}, 100%, 50%)`; 34 | } else if (darkenCheckbox.checked) { 35 | let currentLevel = parseInt(square.dataset.darkenLevel); 36 | if (currentLevel < 10) { 37 | currentLevel++; 38 | square.dataset.darkenLevel = currentLevel.toString(); 39 | const darkenPercentage = (10 - currentLevel) * 10; 40 | square.style.backgroundColor = `hsl(0, 0%, ${darkenPercentage}%)`; 41 | } 42 | } else { 43 | square.style.backgroundColor = colorPicker.value; 44 | } 45 | checkResetButtonState(); 46 | } 47 | 48 | function createGrid(size) { 49 | gridContainer.innerHTML = ""; 50 | 51 | const squareSize = 400 / size; 52 | gridContainer.style.gridTemplateColumns = `repeat(${size}, ${squareSize}px)`; 53 | gridContainer.style.gridTemplateRows = `repeat(${size}, ${squareSize}px)`; 54 | 55 | for (let i = 0; i < size * size; i++) { 56 | const square = document.createElement("div"); 57 | square.style.border = "1px solid lightgray"; 58 | square.style.backgroundColor = "white"; 59 | square.style.boxSizing = "border-box"; 60 | square.style.width = `${squareSize}px`; 61 | square.style.height = `${squareSize}px`; 62 | square.dataset.darkenLevel = "0"; 63 | 64 | square.addEventListener("mouseover", () => applyColor(square)); 65 | 66 | gridContainer.appendChild(square); 67 | } 68 | 69 | checkResetButtonState(); 70 | } 71 | 72 | resetButton.addEventListener("click", () => { 73 | const squares = gridContainer.querySelectorAll("div"); 74 | squares.forEach((square) => { 75 | square.style.backgroundColor = "white"; 76 | square.dataset.darkenLevel = "0"; 77 | }); 78 | globalHue = 0; 79 | checkResetButtonState(); 80 | }); 81 | 82 | function checkResetButtonState() { 83 | const squares = gridContainer.querySelectorAll("div"); 84 | const hasColoredSquares = Array.from(squares).some( 85 | (square) => square.style.backgroundColor !== "white" 86 | ); 87 | resetButton.disabled = !hasColoredSquares; 88 | } 89 | 90 | createGrid(16); 91 | 92 | generateButton.addEventListener("click", () => { 93 | generateGridFromInput(); 94 | }); 95 | 96 | gridSizeInput.addEventListener("keydown", (event) => { 97 | if (event.key === "Enter") { 98 | generateGridFromInput(); 99 | } 100 | }); 101 | 102 | function generateGridFromInput() { 103 | const gridSize = parseInt(gridSizeInput.value); 104 | 105 | if (gridSize >= 2 && gridSize <= 100) { 106 | globalHue = 0; 107 | createGrid(gridSize); 108 | document.getElementById("error").textContent = ""; 109 | } else { 110 | document.getElementById("error").textContent = 111 | "Please enter a grid size between 2 and 100."; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /courses/foundations/landing-page/assets/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* fonts */ 3 | --fs-sm: 18px; 4 | --fs-md: 24px; 5 | --fs-lg: 36px; 6 | --fs-xl: 48px; 7 | 8 | /* colors */ 9 | --black: #1f2937; 10 | --white: #f9faf8; 11 | --grey: #e5e7eb; 12 | --blue: #3b82f6; 13 | --blue-light: #65a0ff; 14 | --blue-dark: #1f2937; 15 | } 16 | 17 | * { 18 | margin: 0; 19 | padding: 0; 20 | box-sizing: border-box; 21 | } 22 | 23 | body { 24 | font-family: "Roboto", sans-serif; 25 | line-height: 1.6; 26 | color: var(--black); 27 | background-color: var(--white); 28 | font-size: var(--fs-sm); 29 | } 30 | 31 | a { 32 | text-decoration: none; 33 | } 34 | 35 | header { 36 | background-color: var(--blue-dark); 37 | padding: 20px 0; 38 | } 39 | 40 | .page-container { 41 | display: flex; 42 | flex-direction: column; 43 | min-height: 100vh; 44 | } 45 | 46 | .container { 47 | max-width: 1200px; 48 | margin: 0 auto; 49 | padding: 0 20px; 50 | } 51 | 52 | .menu-toggle { 53 | display: none; 54 | background: none; 55 | border: none; 56 | color: var(--white); 57 | font-size: var(--fs-lg); 58 | cursor: pointer; 59 | } 60 | 61 | .header-nav { 62 | display: flex; 63 | justify-content: space-between; 64 | align-items: center; 65 | gap: 20px; 66 | } 67 | 68 | .nav-links { 69 | display: flex; 70 | gap: 20px; 71 | list-style: none; 72 | margin: 0; 73 | padding: 0; 74 | } 75 | 76 | .nav-item { 77 | color: var(--white); 78 | text-decoration: none; 79 | transition: color 0.2s ease; 80 | font-weight: 500; 81 | font-size: var(--fs-sm); 82 | white-space: nowrap; 83 | } 84 | 85 | .logo { 86 | font-size: var(--fs-md); 87 | font-weight: bold; 88 | } 89 | 90 | .nav-item:hover { 91 | color: var(--blue); 92 | } 93 | 94 | .hero { 95 | background-color: var(--blue-dark); 96 | color: var(--white); 97 | padding: 50px 0; 98 | } 99 | 100 | .hero .container { 101 | display: flex; 102 | gap: 50px; 103 | justify-content: space-between; 104 | align-items: center; 105 | max-width: 1200px; 106 | margin: 0 auto; 107 | padding: 0 20px; 108 | } 109 | 110 | .hero-text { 111 | max-width: 50%; 112 | } 113 | 114 | .hero-text h1 { 115 | font-size: var(--fs-xl); 116 | line-height: 1.2; 117 | } 118 | 119 | .text-grey { 120 | color: var(--grey); 121 | font-size: var(--fs-sm); 122 | margin-bottom: 20px; 123 | } 124 | 125 | .button, 126 | .cta-button a { 127 | display: inline-block; 128 | background: radial-gradient(circle, var(--blue-light) 30%, var(--blue) 100%); 129 | color: var(--white); 130 | padding: 10px 40px; 131 | text-decoration: none; 132 | border-radius: 5px; 133 | font-weight: 500; 134 | font-size: var(--fs-sm); 135 | transition: transform 0.2s ease, background 0.2s, background-color 0.2s ease, 136 | color 0.2s ease; 137 | text-wrap: nowrap; 138 | } 139 | 140 | .button:hover { 141 | background: radial-gradient(circle, var(--white) 30%, var(--white) 100%); 142 | color: var(--blue); 143 | } 144 | 145 | .button:active, 146 | .cta-button:active { 147 | transform: scale(0.95); 148 | } 149 | 150 | .hero-image { 151 | width: 50%; 152 | background-color: var(--grey); 153 | color: var(--black); 154 | display: flex; 155 | justify-content: center; 156 | align-items: center; 157 | text-align: center; 158 | border-radius: 5px; 159 | font-size: var(--fs-sm); 160 | min-height: 200px; 161 | } 162 | 163 | .info { 164 | background-color: var(--white); 165 | padding: 60px 0; 166 | } 167 | 168 | .info .container { 169 | max-width: 1200px; 170 | margin: 0 auto; 171 | padding: 0 20px; 172 | text-align: center; 173 | } 174 | 175 | .info h2 { 176 | font-size: var(--fs-lg); 177 | margin-bottom: 30px; 178 | } 179 | 180 | .info-cards { 181 | display: grid; 182 | gap: 20px; 183 | grid-template-columns: repeat(4, 1fr); 184 | } 185 | 186 | .card-container { 187 | display: flex; 188 | flex-direction: column; 189 | align-items: center; 190 | } 191 | 192 | .card { 193 | background-color: var(--white); 194 | border: 3px solid var(--blue); 195 | border-radius: 20px; 196 | aspect-ratio: 1 / 1; 197 | width: 100%; 198 | transition: background-color 0.2s ease; 199 | } 200 | 201 | .card-container p { 202 | margin-top: 10px; 203 | font-size: var(--fs-sm); 204 | color: var(--black); 205 | } 206 | 207 | .card-container:hover p { 208 | text-decoration: underline; 209 | } 210 | 211 | .card-container:hover .card { 212 | background-color: var(--grey); 213 | } 214 | 215 | .quote { 216 | background-color: var(--grey); 217 | padding: 100px 0; 218 | } 219 | 220 | .quote .container { 221 | max-width: 1200px; 222 | margin: 0 auto; 223 | padding: 0 20px; 224 | } 225 | 226 | blockquote { 227 | font-size: var(--fs-md); 228 | font-style: italic; 229 | color: var(--black); 230 | margin-bottom: 10px; 231 | } 232 | 233 | figcaption { 234 | font-size: var(--fs-sm); 235 | color: var(--black); 236 | font-weight: 700; 237 | text-align: right; 238 | } 239 | 240 | .cta { 241 | padding: 50px 0; 242 | } 243 | 244 | .cta .container { 245 | max-width: 1200px; 246 | margin: 0 auto; 247 | padding: 0 20px; 248 | } 249 | 250 | .cta-container { 251 | display: flex; 252 | justify-content: space-between; 253 | align-items: center; 254 | gap: 50px; 255 | background-color: var(--blue); 256 | color: var(--white); 257 | padding: 30px; 258 | border-radius: 10px; 259 | } 260 | 261 | .cta-text h3 { 262 | font-size: var(--fs-md); 263 | } 264 | 265 | .cta-text p { 266 | font-size: var(--fs-sm); 267 | color: var(--grey); 268 | } 269 | 270 | .cta-button a { 271 | box-shadow: inset 0 0 0 2px var(--white); 272 | background-image: none; 273 | } 274 | 275 | .cta-button a:hover { 276 | background-color: var(--white); 277 | color: var(--blue); 278 | box-shadow: none; 279 | } 280 | 281 | footer { 282 | background-color: var(--blue-dark); 283 | color: var(--grey); 284 | padding: 20px 0; 285 | text-align: center; 286 | } 287 | 288 | footer .container { 289 | max-width: 1200px; 290 | margin: 0 auto; 291 | padding: 0 20px; 292 | } 293 | 294 | @media (max-width: 1024px) { 295 | .hero .container { 296 | flex-direction: column; 297 | gap: 25px; 298 | } 299 | 300 | .hero-text, 301 | .hero-image { 302 | max-width: 100%; 303 | width: 100%; 304 | } 305 | 306 | .hero-text h1 { 307 | font-size: var(--fs-lg); 308 | } 309 | } 310 | 311 | @media (max-width: 768px) { 312 | .hero-text h1 { 313 | font-size: var(--fs-md); 314 | } 315 | 316 | .header-nav { 317 | flex-wrap: wrap; 318 | } 319 | 320 | .menu-toggle { 321 | display: block; 322 | transition: color 0.2s ease; 323 | } 324 | 325 | .menu-toggle:hover { 326 | color: var(--blue); 327 | } 328 | 329 | .nav-links { 330 | display: none; 331 | flex-direction: column; 332 | width: 100%; 333 | text-align: center; 334 | background-color: var(--grey); 335 | padding: 1px 0; 336 | gap: 1px; 337 | } 338 | 339 | .nav-links.active { 340 | display: flex; 341 | } 342 | 343 | .nav-links li { 344 | width: 100%; 345 | margin: 0; 346 | } 347 | 348 | .nav-links li a { 349 | display: block; 350 | padding: 15px; 351 | background-color: var(--blue-dark); 352 | color: var(--white); 353 | text-decoration: none; 354 | transition: background-color 0.2s ease, color 0.2s ease; 355 | } 356 | 357 | .nav-links li a:hover { 358 | background-color: var(--blue); 359 | color: var(--white); 360 | } 361 | 362 | .info-cards { 363 | grid-template-columns: repeat(2, 1fr); 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /courses/foundations/landing-page/designs/colours-and-font-styles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamHillierDev/the-odin-project/cb415e792c9ad553ecd8692917c99a80ff9ba3e8/courses/foundations/landing-page/designs/colours-and-font-styles.png -------------------------------------------------------------------------------- /courses/foundations/landing-page/designs/full-design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamHillierDev/the-odin-project/cb415e792c9ad553ecd8692917c99a80ff9ba3e8/courses/foundations/landing-page/designs/full-design.png -------------------------------------------------------------------------------- /courses/foundations/landing-page/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | Landing Page | The Odin Project 26 | 27 | 28 |
29 |
30 |
31 | 42 |
43 |
44 |
45 |
46 |
47 |
48 |

This website is awesome

49 |

50 | This website has some subtext that goes here under the main 51 | title. It's a smaller font and the color is lower contrast. 52 |

53 | Sign up 54 |
55 |
56 |
this is a placeholder for an image
57 |
58 |
59 |
60 |
61 | 82 |
83 |
84 |
85 |
86 |
87 | This is an inspiring quote, or a testimonial from a customer. 88 | Maybe it's just filling up space, or maybe people will actually 89 | read it. Who knows? All I know is that it looks nice. 90 |
91 |
-Thor, God of Thunder
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |

Call to action! It's time!

100 |

101 | Sign up for our product by clicking that button right over 102 | there! 103 |

104 |
105 |
106 | Sign up 107 |
108 |
109 |
110 |
111 |
112 |
113 | 118 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /courses/foundations/odin-recipes/assets/images/ice-cream.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamHillierDev/the-odin-project/cb415e792c9ad553ecd8692917c99a80ff9ba3e8/courses/foundations/odin-recipes/assets/images/ice-cream.jpg -------------------------------------------------------------------------------- /courses/foundations/odin-recipes/assets/images/lasagna.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamHillierDev/the-odin-project/cb415e792c9ad553ecd8692917c99a80ff9ba3e8/courses/foundations/odin-recipes/assets/images/lasagna.jpg -------------------------------------------------------------------------------- /courses/foundations/odin-recipes/assets/images/pizza.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamHillierDev/the-odin-project/cb415e792c9ad553ecd8692917c99a80ff9ba3e8/courses/foundations/odin-recipes/assets/images/pizza.jpg -------------------------------------------------------------------------------- /courses/foundations/odin-recipes/assets/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | font-family: Arial, sans-serif; 9 | line-height: 1.6; 10 | background-color: #f8f8f8; 11 | color: #333; 12 | padding: 20px; 13 | max-width: 800px; 14 | margin: 0 auto; 15 | } 16 | 17 | header { 18 | background: #333; 19 | color: #fff; 20 | padding: 20px 0; 21 | text-align: center; 22 | border-radius: 8px; 23 | } 24 | 25 | header h1 { 26 | font-size: 2.5rem; 27 | } 28 | 29 | nav ul { 30 | list-style: none; 31 | display: flex; 32 | justify-content: center; 33 | gap: 15px; 34 | margin-top: 10px; 35 | } 36 | 37 | nav ul li { 38 | margin: 0; 39 | } 40 | 41 | nav ul li a { 42 | color: #fff; 43 | text-decoration: none; 44 | font-weight: bold; 45 | padding: 5px 10px; 46 | transition: background-color 0.3s ease, color 0.3s ease; 47 | } 48 | 49 | nav ul li a:hover { 50 | background-color: #fff; 51 | color: #333; 52 | border-radius: 4px; 53 | } 54 | 55 | main { 56 | margin-top: 20px; 57 | padding: 20px; 58 | background: #fff; 59 | border-radius: 8px; 60 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 61 | } 62 | 63 | main h2 { 64 | font-size: 2rem; 65 | margin-bottom: 10px; 66 | } 67 | 68 | main img { 69 | max-width: 100%; 70 | border-radius: 8px; 71 | margin: 20px 0; 72 | } 73 | 74 | main h3 { 75 | font-size: 1.5rem; 76 | margin-top: 20px; 77 | border-bottom: 2px solid #ccc; 78 | padding-bottom: 5px; 79 | } 80 | 81 | main p { 82 | margin: 10px 0; 83 | } 84 | 85 | main ul, 86 | main ol { 87 | margin: 10px 0 20px; 88 | padding-left: 20px; 89 | } 90 | 91 | main ul li, 92 | main ol li { 93 | margin-bottom: 8px; 94 | line-height: 1.4; 95 | } 96 | -------------------------------------------------------------------------------- /courses/foundations/odin-recipes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Odin Recipes 7 | 8 | 9 | 10 |
11 |

Odin Recipes

12 | 19 |
20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /courses/foundations/odin-recipes/recipes/ice-cream.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ice Cream | Odin Recipes 7 | 8 | 9 | 10 |
11 |

Ice Cream

12 | 17 |
18 |
19 |

Ice Cream Sandwich Cake

20 |

21 | Everyone knows there's nothing better than ice cream on a hot summer's 22 | day. So, as summer heats up, you'll want to cool down with this 23 | delicious ice cream treat. 24 |

25 | Ice Cream 26 |

Description

27 |

28 | This five-ingredient ice cream cake is super easy to prep by using 29 | store-bought ice cream sandwiches, whipped topping, hot fudge, caramel 30 | ice cream topping, and pecans. 31 |

32 |

Ingredients

33 | 42 |

Steps

43 |
    44 |
  1. Gather the ingredients.
  2. 45 |
  3. 46 | Arrange a layer of ice cream sandwiches in the bottom of a 9x13-inch 47 | dish; top with a layer of whipped topping, hot fudge topping, and 48 | caramel topping. 49 |
  4. 50 |
  5. 51 | Repeat layering with remaining ice cream sandwiches, whipped topping, 52 | hot fudge topping, and caramel topping, ending with a top layer of 53 | whipped topping. Sprinkle with pecans. 54 |
  6. 55 |
  7. 56 | Cover the dish with aluminum foil and freeze until set, about 30 57 | minutes. Slice and serve with your favorite toppings! 58 |
  8. 59 |
60 |
61 | 62 | 63 | -------------------------------------------------------------------------------- /courses/foundations/odin-recipes/recipes/lasagna.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Lasagna | Odin Recipes 7 | 8 | 9 | 10 |
11 |

Lasagna

12 | 17 |
18 |
19 |

World's Best Lasagna

20 |

21 | This lasagna recipe takes a little work, but it is so satisfying and 22 | filling that it's worth it! 23 |

24 | Lasagna 25 |

Description

26 |

27 | Making lasagna can be time-consuming, but the results are well worth the 28 | wait. You'll find a detailed ingredient list and step-by-step 29 | instructions in the recipe below. 30 |

31 |

Ingredients

32 | 54 |

Steps

55 |
    56 |
  1. Gather all your ingredients.
  2. 57 |
  3. 58 | Cook sausage, ground beef, onion, and garlic in a Dutch oven over 59 | medium heat until well browned. 60 |
  4. 61 |
  5. 62 | Stir in crushed tomatoes, tomato sauce, tomato paste, and water. 63 | Season with sugar, 2 tablespoons parsley, basil, 1 teaspoon salt, 64 | Italian seasoning, fennel seeds, and pepper. Simmer, covered, for 65 | about 1 ½ hours, stirring occasionally. 66 |
  6. 67 |
  7. 68 | Bring a large pot of lightly salted water to a boil. Cook lasagna 69 | noodles in boiling water for 8 to 10 minutes. Drain noodles, and rinse 70 | with cold water. 71 |
  8. 72 |
  9. 73 | In a mixing bowl, combine ricotta cheese with egg, remaining 2 74 | tablespoons parsley, and ½ teaspoon salt. 75 |
  10. 76 |
  11. Preheat the oven to 375 degrees F (190 degrees C).
  12. 77 |
  13. 78 | To assemble, spread 1 ½ cups of meat sauce in the bottom of a 79 | 9x13-inch baking dish. Arrange 6 noodles lengthwise over meat sauce, 80 | overlapping slightly. Spread with ½ of the ricotta cheese mixture. Top 81 | with ⅓ of the mozzarella cheese slices. Spoon 1 ½ cups meat sauce over 82 | mozzarella, and sprinkle with ¼ cup Parmesan cheese. 83 |
  14. 84 |
  15. 85 | Repeat layers, and top with remaining mozzarella and Parmesan cheese. 86 | Cover with foil: to prevent sticking, either spray foil with cooking 87 | spray or make sure the foil does not touch the cheese. 88 |
  16. 89 |
  17. 90 | Bake in the preheated oven for 25 minutes. Remove the foil and bake 91 | for an additional 25 minutes. 92 |
  18. 93 |
  19. Rest lasagna for 15 minutes before serving.
  20. 94 |
95 |
96 | 97 | 98 | -------------------------------------------------------------------------------- /courses/foundations/odin-recipes/recipes/pizza.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Pizza | Odin Recipes 7 | 8 | 9 | 10 |
11 |

Pizza

12 | 17 |
18 |
19 |

Easy Homemade Pizza Dough

20 |

21 | This is a great recipe when you don't want to wait for the dough to 22 | rise. You just mix it and allow it to rest for 5 minutes and then it's 23 | ready to go! It yields a soft, chewy crust. 24 |

25 | Pizza 26 |

Description

27 |

28 | If you're looking for a homemade pizza crust recipe that's great for 29 | beginners, you're in luck. This top-rated recipe is super easy to throw 30 | together on a whim - and it puts the store-bought stuff to shame. Learn 31 | how to make the best pizza crust of your life with just a few 32 | ingredients, find out how to shape the dough, and get our best storage 33 | secrets. 34 |

35 |

Ingredients

36 | 44 |

Steps

45 |
    46 |
  1. 47 | Gather all ingredients. Preheat oven to 450 degrees F (230 degrees C), 48 | and lightly grease a pizza pan. 49 |
  2. 50 |
  3. 51 | Place warm water in a bowl; add yeast and sugar. Mix and let stand 52 | until creamy, about 10 minutes. 53 |
  4. 54 |
  5. 55 | Add flour, oil, and salt to the yeast mixture; beat until smooth. You 56 | can do this by hand or use a stand mixer fitted with a dough hook to 57 | make it easier. 58 |
  6. 59 |
  7. Let rest for 5 minutes.
  8. 60 |
  9. 61 | Turn dough out onto a lightly floured surface and pat or roll into a 62 | 12-inch circle. 63 |
  10. 64 |
  11. Transfer to the prepared pizza pan.
  12. 65 |
  13. Spread crust with sauce and toppings of your choice.
  14. 66 |
  15. 67 | Bake in the preheated oven until golden brown, 15 to 20 minutes. 68 | Remove from the oven and let cool for 5 minutes before serving. 69 |
  16. 70 |
71 |
72 | 73 | 74 | -------------------------------------------------------------------------------- /courses/foundations/rock-paper-scissors/assets/images/paper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamHillierDev/the-odin-project/cb415e792c9ad553ecd8692917c99a80ff9ba3e8/courses/foundations/rock-paper-scissors/assets/images/paper.png -------------------------------------------------------------------------------- /courses/foundations/rock-paper-scissors/assets/images/rock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamHillierDev/the-odin-project/cb415e792c9ad553ecd8692917c99a80ff9ba3e8/courses/foundations/rock-paper-scissors/assets/images/rock.png -------------------------------------------------------------------------------- /courses/foundations/rock-paper-scissors/assets/images/scissors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamHillierDev/the-odin-project/cb415e792c9ad553ecd8692917c99a80ff9ba3e8/courses/foundations/rock-paper-scissors/assets/images/scissors.png -------------------------------------------------------------------------------- /courses/foundations/rock-paper-scissors/assets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | background-color: #f0f8ff; 4 | color: #333; 5 | text-align: center; 6 | padding: 20px; 7 | } 8 | 9 | h1 { 10 | font-size: 2.5rem; 11 | color: #0077cc; 12 | margin-bottom: 20px; 13 | } 14 | 15 | #buttons { 16 | display: flex; 17 | justify-content: center; 18 | gap: 20px; 19 | margin: 20px 0; 20 | } 21 | 22 | .choice-button { 23 | background-color: transparent; 24 | border: none; 25 | cursor: pointer; 26 | transition: transform 0.2s ease; 27 | } 28 | 29 | .choice-button img { 30 | max-width: 100px; 31 | height: auto; 32 | display: block; 33 | margin: 0 auto; 34 | } 35 | 36 | .choice-button:hover img { 37 | transform: scale(1.1); 38 | } 39 | 40 | .text-button { 41 | background-color: #0077cc; 42 | color: #fff; 43 | padding: 10px 20px; 44 | font-size: 1rem; 45 | border: none; 46 | border-radius: 5px; 47 | cursor: pointer; 48 | transition: background-color 0.3s ease; 49 | } 50 | 51 | .text-button:hover { 52 | background-color: #005fa3; 53 | } 54 | 55 | #messages { 56 | margin-top: 20px; 57 | font-size: 1.2rem; 58 | padding: 10px; 59 | background-color: #fff; 60 | border: 1px solid #ddd; 61 | border-radius: 5px; 62 | display: inline-block; 63 | text-align: left; 64 | } 65 | 66 | #messages p { 67 | margin: 5px 0; 68 | } 69 | 70 | @media (max-width: 600px) { 71 | .choice-button img { 72 | max-width: 80px; 73 | } 74 | 75 | h1 { 76 | font-size: 2rem; 77 | } 78 | 79 | #messages { 80 | font-size: 1rem; 81 | padding: 8px; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /courses/foundations/rock-paper-scissors/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Rock Paper Scissors 7 | 8 | 9 | 10 |

Rock Paper Scissors

11 |
12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /courses/foundations/rock-paper-scissors/js/constants.js: -------------------------------------------------------------------------------- 1 | export const CHOICES = ["rock", "paper", "scissors"]; 2 | 3 | export const WIN_CONDITIONS = { 4 | rock: "scissors", 5 | paper: "rock", 6 | scissors: "paper", 7 | }; 8 | 9 | export const MESSAGES = { 10 | roundChoices: (playerChoice, computerChoice) => 11 | `You chose ${playerChoice}, computer chose ${computerChoice}.`, 12 | roundWin: "You won this round!", 13 | roundLose: "You lost this round!", 14 | roundTie: "It's a tie!", 15 | roundScore: (player, computer) => 16 | `Score: Player ${player} - Computer ${computer}`, 17 | gameWin: "Congratulations! You won! 😀", 18 | gameLose: "Game over, you lost. Better luck next time! 😔", 19 | gameTotalWins: (playerWins) => `Wins: ${playerWins}`, 20 | gameTotalLosses: (computerWins) => `Losses: ${computerWins}`, 21 | playAgain: "Play Again", 22 | resetProgress: "Reset Progress", 23 | }; 24 | -------------------------------------------------------------------------------- /courses/foundations/rock-paper-scissors/js/script.js: -------------------------------------------------------------------------------- 1 | import { 2 | createButton, 3 | getComputerChoice, 4 | addMessagesToContainer, 5 | addResetProgressButton, 6 | saveGameStats, 7 | loadGameStats, 8 | getGameStatsMessage, 9 | } from "./utils.js"; 10 | 11 | import { CHOICES, WIN_CONDITIONS, MESSAGES } from "./constants.js"; 12 | 13 | let scores = { player: 0, computer: 0 }; 14 | let gameStats = loadGameStats(); 15 | 16 | const messagesContainer = document.getElementById("messages"); 17 | const buttonSection = document.getElementById("buttons"); 18 | 19 | function updateButtons(buttonConfig) { 20 | buttonSection.replaceChildren(); 21 | buttonConfig.forEach(({ id, onClick, imgSrc, text }) => { 22 | const button = document.createElement("button"); 23 | button.className = "choice-button"; 24 | button.id = id; 25 | button.onclick = onClick; 26 | 27 | if (imgSrc) { 28 | const img = document.createElement("img"); 29 | img.src = imgSrc; 30 | img.alt = `${id.charAt(0).toUpperCase() + id.slice(1)}`; 31 | button.appendChild(img); 32 | } else if (text) { 33 | button.textContent = text; 34 | button.classList.add("text-button"); 35 | } 36 | 37 | buttonSection.appendChild(button); 38 | }); 39 | } 40 | 41 | function resetGame() { 42 | scores = { player: 0, computer: 0 }; 43 | updateButtons( 44 | CHOICES.map((choice) => ({ 45 | text: choice.charAt(0).toUpperCase() + choice.slice(1), 46 | id: choice, 47 | onClick: () => playRound(choice), 48 | imgSrc: `assets/images/${choice}.png`, 49 | })) 50 | ); 51 | 52 | messagesContainer.replaceChildren(); 53 | 54 | addMessagesToContainer(messagesContainer, [ 55 | getGameStatsMessage(MESSAGES, gameStats), 56 | ]); 57 | 58 | addResetProgressButton(messagesContainer, resetProgress); 59 | } 60 | 61 | function updateMessage({ 62 | playerChoice, 63 | computerChoice, 64 | roundResult, 65 | scoreMessage, 66 | finalMessage, 67 | }) { 68 | messagesContainer.replaceChildren(); 69 | 70 | const messages = [ 71 | MESSAGES.roundChoices(playerChoice, computerChoice), 72 | finalMessage || roundResult, 73 | scoreMessage, 74 | getGameStatsMessage(MESSAGES, gameStats), 75 | ]; 76 | 77 | addMessagesToContainer(messagesContainer, messages); 78 | 79 | addResetProgressButton(messagesContainer, resetProgress); 80 | } 81 | 82 | function playRound(playerChoice) { 83 | const computerChoice = getComputerChoice(CHOICES); 84 | 85 | let roundResult; 86 | if (playerChoice === computerChoice) { 87 | roundResult = MESSAGES.roundTie; 88 | } else if (WIN_CONDITIONS[playerChoice] === computerChoice) { 89 | scores.player++; 90 | roundResult = MESSAGES.roundWin; 91 | } else { 92 | scores.computer++; 93 | roundResult = MESSAGES.roundLose; 94 | } 95 | 96 | const isGameOver = scores.player === 5 || scores.computer === 5; 97 | 98 | const scoreMessage = MESSAGES.roundScore(scores.player, scores.computer); 99 | 100 | if (isGameOver) { 101 | if (scores.player > scores.computer) { 102 | gameStats.player++; 103 | } else { 104 | gameStats.computer++; 105 | } 106 | 107 | saveGameStats(gameStats); 108 | 109 | const finalMessage = 110 | scores.player > scores.computer ? MESSAGES.gameWin : MESSAGES.gameLose; 111 | 112 | updateMessage({ 113 | playerChoice, 114 | computerChoice, 115 | finalMessage, 116 | scoreMessage, 117 | }); 118 | 119 | replaceButtonsForReplay(); 120 | } else { 121 | updateMessage({ 122 | playerChoice, 123 | computerChoice, 124 | roundResult, 125 | scoreMessage, 126 | }); 127 | } 128 | } 129 | 130 | function replaceButtonsForReplay() { 131 | updateButtons([ 132 | { 133 | text: MESSAGES.playAgain, 134 | onClick: resetGame, 135 | }, 136 | ]); 137 | } 138 | 139 | function resetProgress() { 140 | localStorage.removeItem("stats"); 141 | gameStats = { player: 0, computer: 0 }; 142 | resetGame(); 143 | } 144 | 145 | resetGame(); 146 | -------------------------------------------------------------------------------- /courses/foundations/rock-paper-scissors/js/utils.js: -------------------------------------------------------------------------------- 1 | import { MESSAGES } from "./constants.js"; 2 | 3 | export function createButton(text, id = null, onClick = null) { 4 | const button = document.createElement("button"); 5 | button.textContent = text; 6 | if (id) button.id = id; 7 | if (onClick) button.addEventListener("click", onClick); 8 | return button; 9 | } 10 | 11 | export function getComputerChoice(choices) { 12 | return choices[Math.floor(Math.random() * choices.length)]; 13 | } 14 | 15 | export function addMessagesToContainer(container, messages) { 16 | messages.forEach((text) => { 17 | const p = document.createElement("p"); 18 | p.textContent = text; 19 | container.appendChild(p); 20 | }); 21 | } 22 | 23 | export function addResetProgressButton(container, resetCallback) { 24 | if (localStorage.getItem("stats")) { 25 | const resetButton = document.createElement("button"); 26 | resetButton.textContent = MESSAGES.resetProgress; 27 | resetButton.className = "text-button reset-progress-button"; 28 | resetButton.addEventListener("click", resetCallback); 29 | container.appendChild(resetButton); 30 | } 31 | } 32 | 33 | export function saveGameStats(gameStats) { 34 | localStorage.setItem("stats", JSON.stringify(gameStats)); 35 | } 36 | 37 | export function loadGameStats() { 38 | const savedWins = localStorage.getItem("stats"); 39 | return savedWins ? JSON.parse(savedWins) : { player: 0, computer: 0 }; 40 | } 41 | 42 | export function getGameStatsMessage(messages, gameStats) { 43 | return `${messages.gameTotalWins( 44 | gameStats.player 45 | )} ${messages.gameTotalLosses(gameStats.computer)}`; 46 | } 47 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/icons.css: -------------------------------------------------------------------------------- 1 | .icon-btn { 2 | width: 25px; 3 | height: 25px; 4 | background-color: white; 5 | border: none; 6 | cursor: pointer; 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | transition: background-color 0.3s ease; 11 | padding: 0; 12 | -webkit-mask-size: cover; 13 | mask-size: cover; 14 | } 15 | 16 | .search-icon { 17 | -webkit-mask: url("./icons/magnify.svg") no-repeat center; 18 | mask: url("./icons/magnify.svg") no-repeat center; 19 | background-color: black; 20 | } 21 | 22 | .notifications-icon { 23 | -webkit-mask: url("./icons/bell-ring-outline.svg") no-repeat center; 24 | mask: url("./icons/bell-ring-outline.svg") no-repeat center; 25 | background-color: black; 26 | } 27 | 28 | .dashboard-icon { 29 | -webkit-mask: url("./icons/view-dashboard.svg") no-repeat center; 30 | mask: url("./icons/view-dashboard.svg") no-repeat center; 31 | } 32 | 33 | .home-icon { 34 | -webkit-mask: url("./icons/home.svg") no-repeat center; 35 | mask: url("./icons/home.svg") no-repeat center; 36 | } 37 | 38 | .profile-icon { 39 | -webkit-mask: url("./icons/account.svg") no-repeat center; 40 | mask: url("./icons/account.svg") no-repeat center; 41 | } 42 | 43 | .messages-icon { 44 | -webkit-mask: url("./icons/message.svg") no-repeat center; 45 | mask: url("./icons/message.svg") no-repeat center; 46 | } 47 | 48 | .history-icon { 49 | -webkit-mask: url("./icons/clock.svg") no-repeat center; 50 | mask: url("./icons/clock.svg") no-repeat center; 51 | } 52 | 53 | .tasks-icon { 54 | -webkit-mask: url("./icons/note-multiple.svg") no-repeat center; 55 | mask: url("./icons/note-multiple.svg") no-repeat center; 56 | } 57 | 58 | .communities-icon { 59 | -webkit-mask: url("./icons/account-group.svg") no-repeat center; 60 | mask: url("./icons/account-group.svg") no-repeat center; 61 | } 62 | 63 | .settings-icon { 64 | -webkit-mask: url("./icons/cog.svg") no-repeat center; 65 | mask: url("./icons/cog.svg") no-repeat center; 66 | } 67 | 68 | .support-icon { 69 | -webkit-mask: url("./icons/help-circle.svg") no-repeat center; 70 | mask: url("./icons/help-circle.svg") no-repeat center; 71 | } 72 | 73 | .privacy-icon { 74 | -webkit-mask: url("./icons/shield-check.svg") no-repeat center; 75 | mask: url("./icons/shield-check.svg") no-repeat center; 76 | } 77 | 78 | .star-icon { 79 | -webkit-mask: url("./icons/star-plus-outline.svg") no-repeat center; 80 | mask: url("./icons/star-plus-outline.svg") no-repeat center; 81 | background-color: black; 82 | } 83 | 84 | .watch-icon { 85 | -webkit-mask: url("./icons/eye-plus-outline.svg") no-repeat center; 86 | mask: url("./icons/eye-plus-outline.svg") no-repeat center; 87 | background-color: black; 88 | } 89 | 90 | .fork-icon { 91 | -webkit-mask: url("./icons/source-fork.svg") no-repeat center; 92 | mask: url("./icons/source-fork.svg") no-repeat center; 93 | background-color: black; 94 | } 95 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/icons/account-group.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/icons/account.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/icons/bell-ring-outline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/icons/clock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/icons/cog.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/icons/eye-plus-outline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/icons/help-circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/icons/home.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/icons/magnify.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/icons/message.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/icons/note-multiple.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/icons/shield-check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/icons/source-fork.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/icons/star-plus-outline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/icons/view-dashboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/assets/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | font-family: "Roboto", sans-serif; 6 | } 7 | 8 | body { 9 | display: flex; 10 | background-color: #f4f4f4; 11 | } 12 | 13 | nav { 14 | width: 250px; 15 | height: 100vh; 16 | background-color: #1e88e5; 17 | color: white; 18 | display: flex; 19 | flex-direction: column; 20 | padding: 20px; 21 | } 22 | 23 | nav div { 24 | display: flex; 25 | align-items: center; 26 | margin-bottom: 30px; 27 | } 28 | 29 | nav img { 30 | width: 40px; 31 | height: 40px; 32 | margin-right: 10px; 33 | } 34 | 35 | nav h1 { 36 | font-size: 20px; 37 | } 38 | 39 | nav ul { 40 | list-style: none; 41 | margin-bottom: 20px; 42 | } 43 | 44 | nav ul li { 45 | padding: 12px; 46 | cursor: pointer; 47 | } 48 | 49 | nav ul li:hover { 50 | background-color: #1992d4; 51 | border-radius: 5px; 52 | } 53 | 54 | header { 55 | width: 100%; 56 | padding: 20px; 57 | background-color: white; 58 | display: flex; 59 | flex-direction: column; 60 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); 61 | } 62 | 63 | header > div { 64 | display: flex; 65 | align-items: center; 66 | justify-content: space-between; 67 | } 68 | 69 | header input { 70 | padding: 8px; 71 | width: 250px; 72 | border: 1px solid #ccc; 73 | border-radius: 5px; 74 | } 75 | 76 | header button { 77 | background-color: #1e88e5; 78 | color: white; 79 | border: none; 80 | padding: 8px 15px; 81 | border-radius: 5px; 82 | cursor: pointer; 83 | } 84 | 85 | header button:hover { 86 | background-color: #1992d4; 87 | } 88 | 89 | header img { 90 | width: 40px; 91 | height: 40px; 92 | border-radius: 50%; 93 | } 94 | 95 | main { 96 | display: flex; 97 | padding: 20px; 98 | width: 100%; 99 | } 100 | 101 | section:first-child { 102 | flex: 2; 103 | } 104 | 105 | section h2 { 106 | margin-bottom: 15px; 107 | } 108 | 109 | article { 110 | background-color: white; 111 | padding: 15px; 112 | margin-bottom: 15px; 113 | border-radius: 5px; 114 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); 115 | } 116 | 117 | article h3 { 118 | margin-bottom: 5px; 119 | } 120 | 121 | article div { 122 | margin-top: 10px; 123 | } 124 | 125 | article button { 126 | background-color: transparent; 127 | border: 1px solid #ccc; 128 | padding: 5px 10px; 129 | margin-right: 5px; 130 | border-radius: 5px; 131 | cursor: pointer; 132 | } 133 | 134 | article button:hover { 135 | background-color: #eee; 136 | } 137 | 138 | section:nth-child(2) { 139 | flex: 1; 140 | margin-left: 20px; 141 | } 142 | 143 | section:nth-child(2) > div { 144 | background-color: white; 145 | padding: 15px; 146 | margin-bottom: 15px; 147 | border-radius: 5px; 148 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0, 0.1); 149 | } 150 | 151 | section:nth-child(2) h2 { 152 | margin-bottom: 15px; 153 | } 154 | 155 | section:nth-child(2) article { 156 | display: flex; 157 | align-items: center; 158 | margin-top: 10px; 159 | } 160 | 161 | section:nth-child(2) article img { 162 | width: 40px; 163 | height: 40px; 164 | border-radius: 50%; 165 | margin-right: 10px; 166 | } 167 | 168 | section:nth-child(2) article div h4 { 169 | margin-bottom: 3px; 170 | font-size: 14px; 171 | } 172 | 173 | section:nth-child(2) article div p { 174 | font-size: 12px; 175 | color: gray; 176 | } 177 | 178 | header div:last-child { 179 | display: flex; 180 | gap: 10px; 181 | } 182 | 183 | header div:last-child button { 184 | background-color: #1e88e5; 185 | color: white; 186 | border: none; 187 | padding: 10px 15px; 188 | border-radius: 5px; 189 | cursor: pointer; 190 | font-size: 14px; 191 | } 192 | 193 | .col { 194 | display: flex; 195 | flex-direction: column; 196 | width: 100%; 197 | } 198 | 199 | header div:last-child button:hover { 200 | background-color: #1992d4; 201 | } 202 | 203 | @media (max-width: 768px) { 204 | body { 205 | flex-direction: column; 206 | } 207 | 208 | nav { 209 | width: 100%; 210 | height: auto; 211 | flex-direction: row; 212 | justify-content: space-between; 213 | padding: 10px; 214 | } 215 | 216 | nav ul { 217 | display: flex; 218 | gap: 15px; 219 | } 220 | 221 | main { 222 | flex-direction: column; 223 | } 224 | 225 | section:nth-child(2) { 226 | margin-left: 0; 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/designs/dashboard-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamHillierDev/the-odin-project/cb415e792c9ad553ecd8692917c99a80ff9ba3e8/courses/full-stack-javascript/html-css/admin-dashboard/designs/dashboard-project.png -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/admin-dashboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Admin Dashboard 7 | 8 | 9 | 10 | 11 | 12 | 31 |
32 |
33 |
34 | 35 | 36 | 37 | Profile 38 |

Username

39 |
40 |
41 | Profile 42 |
43 |

Hi there,

44 |

Username (@username)

45 |
46 |
47 | 48 | 49 | 50 |
51 |
52 |
53 |
54 |
55 |

Your Projects

56 |
57 |

Super Cool Project

58 |

Lorem ipsum dolor sit amet consectetur adipisicing elit.

59 |
60 | 61 | 62 | 63 |
64 |
65 |
66 |
67 |

Announcements

68 |
69 |

Site Maintenance

70 |

Lorem ipsum dolor sit amet consectetur adipisicing elit.

71 |
72 |

Trending

73 |
74 | Trending 75 |
76 |

@Username

77 |

Lorem ipsum dolor sit amet consectetur adipisicing elit.

78 |
79 |
80 |
81 |
82 |
83 | 84 | 85 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/sign-up-form/assets/fonts/Norse-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamHillierDev/the-odin-project/cb415e792c9ad553ecd8692917c99a80ff9ba3e8/courses/full-stack-javascript/html-css/sign-up-form/assets/fonts/Norse-Bold.otf -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/sign-up-form/assets/images/odin-background.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamHillierDev/the-odin-project/cb415e792c9ad553ecd8692917c99a80ff9ba3e8/courses/full-stack-javascript/html-css/sign-up-form/assets/images/odin-background.avif -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/sign-up-form/assets/images/odin-lined.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamHillierDev/the-odin-project/cb415e792c9ad553ecd8692917c99a80ff9ba3e8/courses/full-stack-javascript/html-css/sign-up-form/assets/images/odin-lined.png -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/sign-up-form/assets/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Norse Bold"; 3 | src: url("../assets/fonts/Norse-Bold.otf") format("opentype"); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | 8 | * { 9 | margin: 0; 10 | padding: 0; 11 | box-sizing: border-box; 12 | } 13 | 14 | body { 15 | display: flex; 16 | height: 100vh; 17 | font-family: Arial, sans-serif; 18 | } 19 | 20 | header { 21 | background-color: #4e5f3e; 22 | background: url("../assets/images/odin-background.avif") no-repeat center 23 | center/cover; 24 | width: 40%; 25 | position: relative; 26 | } 27 | 28 | .header-logo { 29 | display: flex; 30 | justify-content: center; 31 | align-items: center; 32 | background: rgba(0, 0, 0, 0.5); 33 | gap: 2px; 34 | margin-top: 30%; 35 | padding: 10px; 36 | } 37 | 38 | .header-logo img { 39 | width: 120px; 40 | height: auto; 41 | } 42 | 43 | h1 { 44 | font-family: "Norse Bold", sans-serif; 45 | font-size: 7rem; 46 | color: white; 47 | text-align: center; 48 | } 49 | 50 | .photo-credit { 51 | position: absolute; 52 | bottom: 15px; 53 | width: 100%; 54 | text-align: center; 55 | font-size: 1rem; 56 | color: #f9f9f9; 57 | } 58 | 59 | .photo-credit a { 60 | text-decoration: underline; 61 | color: #f9f9f9; 62 | } 63 | 64 | main { 65 | width: 60%; 66 | display: flex; 67 | flex-direction: column; 68 | background: #f9f9f9; 69 | } 70 | 71 | .text-container { 72 | display: flex; 73 | flex-direction: column; 74 | gap: 10px; 75 | margin-top: 20%; 76 | font-size: 1.2rem; 77 | padding: 40px; 78 | } 79 | 80 | fieldset { 81 | border: none; 82 | display: grid; 83 | grid-template-columns: repeat(2, 1fr); 84 | gap: 20px; 85 | padding: 40px; 86 | background: #ffffff; 87 | box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); 88 | width: 100%; 89 | margin: 0; 90 | } 91 | 92 | form div { 93 | display: flex; 94 | flex-direction: column; 95 | width: 100%; 96 | } 97 | 98 | legend { 99 | font-size: 1.2rem; 100 | font-weight: bold; 101 | } 102 | 103 | label { 104 | font-size: 0.9rem; 105 | font-weight: bold; 106 | } 107 | 108 | input { 109 | padding: 8px; 110 | border: 1px solid #e5e7eb; 111 | border-radius: 5px; 112 | font-size: 1rem; 113 | } 114 | 115 | input:focus { 116 | border-color: #3b82f6; 117 | box-shadow: 0px 0px 5px rgba(59, 130, 246, 0.5); 118 | outline: none; 119 | } 120 | 121 | input:invalid { 122 | border-color: red; 123 | } 124 | 125 | .button-container { 126 | padding: 20px 40px; 127 | max-width: fit-content; 128 | } 129 | 130 | button { 131 | padding: 12px 50px; 132 | background-color: #596d48; 133 | text-wrap: nowrap; 134 | color: white; 135 | font-size: 1.1rem; 136 | font-weight: bold; 137 | border: none; 138 | border-radius: 10px; 139 | cursor: pointer; 140 | } 141 | 142 | button:hover { 143 | background-color: #4e5f3e; 144 | } 145 | 146 | footer { 147 | padding: 20px 40px; 148 | } 149 | 150 | footer a { 151 | color: #596d48; 152 | font-weight: bold; 153 | text-decoration: none; 154 | } 155 | 156 | footer a:hover { 157 | text-decoration: underline; 158 | } 159 | 160 | @media (max-width: 768px) { 161 | body { 162 | flex-direction: column; 163 | height: auto; 164 | } 165 | 166 | header { 167 | width: 100%; 168 | height: 200px; 169 | display: flex; 170 | flex-direction: column; 171 | justify-content: center; 172 | align-items: center; 173 | } 174 | 175 | main { 176 | width: 100%; 177 | padding: 20px; 178 | } 179 | 180 | .text-container { 181 | font-size: 1rem; 182 | padding: 20px; 183 | text-align: center; 184 | } 185 | 186 | fieldset { 187 | grid-template-columns: 1fr; 188 | width: 100%; 189 | } 190 | 191 | .button-container { 192 | text-align: center; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/sign-up-form/designs/design-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamHillierDev/the-odin-project/cb415e792c9ad553ecd8692917c99a80ff9ba3e8/courses/full-stack-javascript/html-css/sign-up-form/designs/design-file.png -------------------------------------------------------------------------------- /courses/full-stack-javascript/html-css/sign-up-form/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sign-up Form 7 | 8 | 9 | 10 |
11 | 15 |

16 | Photo by 17 | Halie West 23 | on 24 | Unsplash 27 |

28 |
29 |
30 |
31 |

32 | This is not a real online service! You know you need something like 33 | this in your life to help you realise your deepest dreams. 34 |

35 |

Sign up now to get started.

36 |

You know you want to.

39 |
40 |
41 |
42 | Let's do this! 43 |
44 | 45 | 46 |
47 |
48 | 49 | 50 |
51 |
52 | 53 | 54 |
55 |
56 | 57 | 58 |
59 |
60 | 61 | 62 |
63 |
64 | 65 | 71 |
72 |
73 |
74 | 75 |
76 |
77 | 80 |
81 | 82 | 83 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/javascript/library/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Library 7 | 8 | 9 |
10 | 11 | 18 |
19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/javascript/library/src/library.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", () => { 2 | const myLibrary = []; 3 | 4 | class Book { 5 | constructor(title, author, pages, read) { 6 | this.title = title; 7 | this.author = author; 8 | this.pages = pages; 9 | this.read = read; 10 | } 11 | 12 | toggleRead() { 13 | this.read = !this.read; 14 | } 15 | } 16 | 17 | function addBookToLibrary(title, author, pages, read) { 18 | const newBook = new Book(title, author, pages, read); 19 | myLibrary.push(newBook); 20 | displayBooks(); 21 | } 22 | 23 | function displayBooks() { 24 | localStorage.setItem("library", JSON.stringify(myLibrary)); 25 | const libraryContainer = document.getElementById("library"); 26 | libraryContainer.innerHTML = ""; 27 | 28 | myLibrary.forEach((book, index) => { 29 | const bookCard = document.createElement("div"); 30 | bookCard.dataset.index = index; 31 | 32 | bookCard.innerHTML = ` 33 |

${book.title}

34 |

Author: ${book.author}

35 |

Pages: ${book.pages}

36 |

Status: ${book.read ? "Read" : "Not Read"}

37 | 38 | 39 | `; 40 | 41 | libraryContainer.appendChild(bookCard); 42 | }); 43 | 44 | attachEventListeners(); 45 | } 46 | 47 | function attachEventListeners() { 48 | document.querySelectorAll(".toggle-read").forEach((button) => { 49 | button.addEventListener("click", (event) => { 50 | const index = event.target.parentElement.dataset.index; 51 | myLibrary[index].toggleRead(); 52 | displayBooks(); 53 | }); 54 | }); 55 | 56 | document.querySelectorAll(".remove-book").forEach((button) => { 57 | button.addEventListener("click", (event) => { 58 | const index = event.target.parentElement.dataset.index; 59 | myLibrary.splice(index, 1); 60 | displayBooks(); 61 | }); 62 | }); 63 | } 64 | 65 | document 66 | .getElementById("book-form") 67 | .addEventListener("submit", function (event) { 68 | event.preventDefault(); 69 | 70 | const title = document.getElementById("title").value; 71 | const author = document.getElementById("author").value; 72 | const pages = parseInt(document.getElementById("pages").value, 10); 73 | const read = document.getElementById("read").checked; 74 | 75 | addBookToLibrary(title, author, pages, read); 76 | this.reset(); 77 | }); 78 | 79 | document.getElementById("new-book-btn").addEventListener("click", () => { 80 | const form = document.getElementById("book-form"); 81 | form.style.display = form.style.display === "none" ? "block" : "none"; 82 | }); 83 | 84 | function loadLibraryFromStorage() { 85 | const savedLibrary = localStorage.getItem("library"); 86 | if (savedLibrary) { 87 | const parsedLibrary = JSON.parse(savedLibrary); 88 | parsedLibrary.forEach((bookData) => { 89 | myLibrary.push( 90 | new Book( 91 | bookData.title, 92 | bookData.author, 93 | bookData.pages, 94 | bookData.read 95 | ) 96 | ); 97 | }); 98 | displayBooks(); 99 | } 100 | } 101 | 102 | loadLibraryFromStorage(); 103 | }); 104 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/nodejs/basic-informational-site/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Page Not Found | Basic Informational Site 7 | 8 | 9 |

Page Not Found

10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/nodejs/basic-informational-site/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | About | Basic Informational Site 7 | 8 | 9 |

About

10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/nodejs/basic-informational-site/contact-me.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Contact Me | Basic Informational Site 7 | 8 | 9 |

Contact Me

10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/nodejs/basic-informational-site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Basic Informational Site 7 | 8 | 9 |

Basic Informational Site

10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/nodejs/basic-informational-site/index.js: -------------------------------------------------------------------------------- 1 | const http = require("node:http"); 2 | const fs = require("node:fs"); 3 | const path = require("node:path"); 4 | 5 | const server = http.createServer((req, res) => { 6 | let filePath = ""; 7 | 8 | switch (req.url) { 9 | case "/": 10 | filePath = "index.html"; 11 | break; 12 | case "/about": 13 | filePath = "about.html"; 14 | break; 15 | case "/contact-me": 16 | filePath = "contact-me.html"; 17 | break; 18 | default: 19 | filePath = "404.html"; 20 | break; 21 | } 22 | 23 | const fullPath = path.join(__dirname, filePath); 24 | 25 | fs.readFile(fullPath, "utf8", (err, data) => { 26 | if (err) { 27 | res.writeHead(500, { "Content-Type": "text/plain" }); 28 | res.end("500 Internal Server Error"); 29 | return; 30 | } 31 | res.writeHead(200, { "Content-Type": "text/html" }); 32 | res.end(data); 33 | }); 34 | }); 35 | 36 | const PORT = 8080; 37 | server.listen(PORT, () => { 38 | console.log(`Server is running on http://localhost:${PORT}`); 39 | }); 40 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/nodejs/mini-message-board/app.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const app = express(); 3 | const indexRouter = require("./routes/index"); 4 | 5 | app.set("view engine", "ejs"); 6 | app.use(express.urlencoded({ extended: true })); 7 | app.use("/", indexRouter); 8 | 9 | const PORT = 8080; 10 | app.listen(PORT, () => { 11 | console.log(`Server is running on http://localhost:${PORT}`); 12 | }); 13 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/nodejs/mini-message-board/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-message-board", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "ejs": "^3.1.10", 9 | "express": "^4.21.2" 10 | } 11 | }, 12 | "node_modules/accepts": { 13 | "version": "1.3.8", 14 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 15 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 16 | "license": "MIT", 17 | "dependencies": { 18 | "mime-types": "~2.1.34", 19 | "negotiator": "0.6.3" 20 | }, 21 | "engines": { 22 | "node": ">= 0.6" 23 | } 24 | }, 25 | "node_modules/ansi-styles": { 26 | "version": "4.3.0", 27 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 28 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 29 | "license": "MIT", 30 | "dependencies": { 31 | "color-convert": "^2.0.1" 32 | }, 33 | "engines": { 34 | "node": ">=8" 35 | }, 36 | "funding": { 37 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 38 | } 39 | }, 40 | "node_modules/array-flatten": { 41 | "version": "1.1.1", 42 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 43 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", 44 | "license": "MIT" 45 | }, 46 | "node_modules/async": { 47 | "version": "3.2.6", 48 | "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", 49 | "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", 50 | "license": "MIT" 51 | }, 52 | "node_modules/balanced-match": { 53 | "version": "1.0.2", 54 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 55 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 56 | "license": "MIT" 57 | }, 58 | "node_modules/body-parser": { 59 | "version": "1.20.3", 60 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", 61 | "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", 62 | "license": "MIT", 63 | "dependencies": { 64 | "bytes": "3.1.2", 65 | "content-type": "~1.0.5", 66 | "debug": "2.6.9", 67 | "depd": "2.0.0", 68 | "destroy": "1.2.0", 69 | "http-errors": "2.0.0", 70 | "iconv-lite": "0.4.24", 71 | "on-finished": "2.4.1", 72 | "qs": "6.13.0", 73 | "raw-body": "2.5.2", 74 | "type-is": "~1.6.18", 75 | "unpipe": "1.0.0" 76 | }, 77 | "engines": { 78 | "node": ">= 0.8", 79 | "npm": "1.2.8000 || >= 1.4.16" 80 | } 81 | }, 82 | "node_modules/brace-expansion": { 83 | "version": "1.1.11", 84 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 85 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 86 | "license": "MIT", 87 | "dependencies": { 88 | "balanced-match": "^1.0.0", 89 | "concat-map": "0.0.1" 90 | } 91 | }, 92 | "node_modules/bytes": { 93 | "version": "3.1.2", 94 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 95 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 96 | "license": "MIT", 97 | "engines": { 98 | "node": ">= 0.8" 99 | } 100 | }, 101 | "node_modules/call-bind-apply-helpers": { 102 | "version": "1.0.1", 103 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", 104 | "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", 105 | "license": "MIT", 106 | "dependencies": { 107 | "es-errors": "^1.3.0", 108 | "function-bind": "^1.1.2" 109 | }, 110 | "engines": { 111 | "node": ">= 0.4" 112 | } 113 | }, 114 | "node_modules/call-bound": { 115 | "version": "1.0.3", 116 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", 117 | "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", 118 | "license": "MIT", 119 | "dependencies": { 120 | "call-bind-apply-helpers": "^1.0.1", 121 | "get-intrinsic": "^1.2.6" 122 | }, 123 | "engines": { 124 | "node": ">= 0.4" 125 | }, 126 | "funding": { 127 | "url": "https://github.com/sponsors/ljharb" 128 | } 129 | }, 130 | "node_modules/chalk": { 131 | "version": "4.1.2", 132 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 133 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 134 | "license": "MIT", 135 | "dependencies": { 136 | "ansi-styles": "^4.1.0", 137 | "supports-color": "^7.1.0" 138 | }, 139 | "engines": { 140 | "node": ">=10" 141 | }, 142 | "funding": { 143 | "url": "https://github.com/chalk/chalk?sponsor=1" 144 | } 145 | }, 146 | "node_modules/color-convert": { 147 | "version": "2.0.1", 148 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 149 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 150 | "license": "MIT", 151 | "dependencies": { 152 | "color-name": "~1.1.4" 153 | }, 154 | "engines": { 155 | "node": ">=7.0.0" 156 | } 157 | }, 158 | "node_modules/color-name": { 159 | "version": "1.1.4", 160 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 161 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 162 | "license": "MIT" 163 | }, 164 | "node_modules/concat-map": { 165 | "version": "0.0.1", 166 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 167 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 168 | "license": "MIT" 169 | }, 170 | "node_modules/content-disposition": { 171 | "version": "0.5.4", 172 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 173 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 174 | "license": "MIT", 175 | "dependencies": { 176 | "safe-buffer": "5.2.1" 177 | }, 178 | "engines": { 179 | "node": ">= 0.6" 180 | } 181 | }, 182 | "node_modules/content-type": { 183 | "version": "1.0.5", 184 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 185 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 186 | "license": "MIT", 187 | "engines": { 188 | "node": ">= 0.6" 189 | } 190 | }, 191 | "node_modules/cookie": { 192 | "version": "0.7.1", 193 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", 194 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", 195 | "license": "MIT", 196 | "engines": { 197 | "node": ">= 0.6" 198 | } 199 | }, 200 | "node_modules/cookie-signature": { 201 | "version": "1.0.6", 202 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 203 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", 204 | "license": "MIT" 205 | }, 206 | "node_modules/debug": { 207 | "version": "2.6.9", 208 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 209 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 210 | "license": "MIT", 211 | "dependencies": { 212 | "ms": "2.0.0" 213 | } 214 | }, 215 | "node_modules/depd": { 216 | "version": "2.0.0", 217 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 218 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 219 | "license": "MIT", 220 | "engines": { 221 | "node": ">= 0.8" 222 | } 223 | }, 224 | "node_modules/destroy": { 225 | "version": "1.2.0", 226 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 227 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 228 | "license": "MIT", 229 | "engines": { 230 | "node": ">= 0.8", 231 | "npm": "1.2.8000 || >= 1.4.16" 232 | } 233 | }, 234 | "node_modules/dunder-proto": { 235 | "version": "1.0.1", 236 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 237 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 238 | "license": "MIT", 239 | "dependencies": { 240 | "call-bind-apply-helpers": "^1.0.1", 241 | "es-errors": "^1.3.0", 242 | "gopd": "^1.2.0" 243 | }, 244 | "engines": { 245 | "node": ">= 0.4" 246 | } 247 | }, 248 | "node_modules/ee-first": { 249 | "version": "1.1.1", 250 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 251 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 252 | "license": "MIT" 253 | }, 254 | "node_modules/ejs": { 255 | "version": "3.1.10", 256 | "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", 257 | "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", 258 | "license": "Apache-2.0", 259 | "dependencies": { 260 | "jake": "^10.8.5" 261 | }, 262 | "bin": { 263 | "ejs": "bin/cli.js" 264 | }, 265 | "engines": { 266 | "node": ">=0.10.0" 267 | } 268 | }, 269 | "node_modules/encodeurl": { 270 | "version": "2.0.0", 271 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 272 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 273 | "license": "MIT", 274 | "engines": { 275 | "node": ">= 0.8" 276 | } 277 | }, 278 | "node_modules/es-define-property": { 279 | "version": "1.0.1", 280 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 281 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 282 | "license": "MIT", 283 | "engines": { 284 | "node": ">= 0.4" 285 | } 286 | }, 287 | "node_modules/es-errors": { 288 | "version": "1.3.0", 289 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 290 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 291 | "license": "MIT", 292 | "engines": { 293 | "node": ">= 0.4" 294 | } 295 | }, 296 | "node_modules/es-object-atoms": { 297 | "version": "1.1.1", 298 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 299 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 300 | "license": "MIT", 301 | "dependencies": { 302 | "es-errors": "^1.3.0" 303 | }, 304 | "engines": { 305 | "node": ">= 0.4" 306 | } 307 | }, 308 | "node_modules/escape-html": { 309 | "version": "1.0.3", 310 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 311 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 312 | "license": "MIT" 313 | }, 314 | "node_modules/etag": { 315 | "version": "1.8.1", 316 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 317 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 318 | "license": "MIT", 319 | "engines": { 320 | "node": ">= 0.6" 321 | } 322 | }, 323 | "node_modules/express": { 324 | "version": "4.21.2", 325 | "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", 326 | "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", 327 | "license": "MIT", 328 | "dependencies": { 329 | "accepts": "~1.3.8", 330 | "array-flatten": "1.1.1", 331 | "body-parser": "1.20.3", 332 | "content-disposition": "0.5.4", 333 | "content-type": "~1.0.4", 334 | "cookie": "0.7.1", 335 | "cookie-signature": "1.0.6", 336 | "debug": "2.6.9", 337 | "depd": "2.0.0", 338 | "encodeurl": "~2.0.0", 339 | "escape-html": "~1.0.3", 340 | "etag": "~1.8.1", 341 | "finalhandler": "1.3.1", 342 | "fresh": "0.5.2", 343 | "http-errors": "2.0.0", 344 | "merge-descriptors": "1.0.3", 345 | "methods": "~1.1.2", 346 | "on-finished": "2.4.1", 347 | "parseurl": "~1.3.3", 348 | "path-to-regexp": "0.1.12", 349 | "proxy-addr": "~2.0.7", 350 | "qs": "6.13.0", 351 | "range-parser": "~1.2.1", 352 | "safe-buffer": "5.2.1", 353 | "send": "0.19.0", 354 | "serve-static": "1.16.2", 355 | "setprototypeof": "1.2.0", 356 | "statuses": "2.0.1", 357 | "type-is": "~1.6.18", 358 | "utils-merge": "1.0.1", 359 | "vary": "~1.1.2" 360 | }, 361 | "engines": { 362 | "node": ">= 0.10.0" 363 | }, 364 | "funding": { 365 | "type": "opencollective", 366 | "url": "https://opencollective.com/express" 367 | } 368 | }, 369 | "node_modules/filelist": { 370 | "version": "1.0.4", 371 | "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", 372 | "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", 373 | "license": "Apache-2.0", 374 | "dependencies": { 375 | "minimatch": "^5.0.1" 376 | } 377 | }, 378 | "node_modules/filelist/node_modules/brace-expansion": { 379 | "version": "2.0.1", 380 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 381 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 382 | "license": "MIT", 383 | "dependencies": { 384 | "balanced-match": "^1.0.0" 385 | } 386 | }, 387 | "node_modules/filelist/node_modules/minimatch": { 388 | "version": "5.1.6", 389 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", 390 | "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", 391 | "license": "ISC", 392 | "dependencies": { 393 | "brace-expansion": "^2.0.1" 394 | }, 395 | "engines": { 396 | "node": ">=10" 397 | } 398 | }, 399 | "node_modules/finalhandler": { 400 | "version": "1.3.1", 401 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", 402 | "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", 403 | "license": "MIT", 404 | "dependencies": { 405 | "debug": "2.6.9", 406 | "encodeurl": "~2.0.0", 407 | "escape-html": "~1.0.3", 408 | "on-finished": "2.4.1", 409 | "parseurl": "~1.3.3", 410 | "statuses": "2.0.1", 411 | "unpipe": "~1.0.0" 412 | }, 413 | "engines": { 414 | "node": ">= 0.8" 415 | } 416 | }, 417 | "node_modules/forwarded": { 418 | "version": "0.2.0", 419 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 420 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 421 | "license": "MIT", 422 | "engines": { 423 | "node": ">= 0.6" 424 | } 425 | }, 426 | "node_modules/fresh": { 427 | "version": "0.5.2", 428 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 429 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 430 | "license": "MIT", 431 | "engines": { 432 | "node": ">= 0.6" 433 | } 434 | }, 435 | "node_modules/function-bind": { 436 | "version": "1.1.2", 437 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 438 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 439 | "license": "MIT", 440 | "funding": { 441 | "url": "https://github.com/sponsors/ljharb" 442 | } 443 | }, 444 | "node_modules/get-intrinsic": { 445 | "version": "1.2.7", 446 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", 447 | "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", 448 | "license": "MIT", 449 | "dependencies": { 450 | "call-bind-apply-helpers": "^1.0.1", 451 | "es-define-property": "^1.0.1", 452 | "es-errors": "^1.3.0", 453 | "es-object-atoms": "^1.0.0", 454 | "function-bind": "^1.1.2", 455 | "get-proto": "^1.0.0", 456 | "gopd": "^1.2.0", 457 | "has-symbols": "^1.1.0", 458 | "hasown": "^2.0.2", 459 | "math-intrinsics": "^1.1.0" 460 | }, 461 | "engines": { 462 | "node": ">= 0.4" 463 | }, 464 | "funding": { 465 | "url": "https://github.com/sponsors/ljharb" 466 | } 467 | }, 468 | "node_modules/get-proto": { 469 | "version": "1.0.1", 470 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 471 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 472 | "license": "MIT", 473 | "dependencies": { 474 | "dunder-proto": "^1.0.1", 475 | "es-object-atoms": "^1.0.0" 476 | }, 477 | "engines": { 478 | "node": ">= 0.4" 479 | } 480 | }, 481 | "node_modules/gopd": { 482 | "version": "1.2.0", 483 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 484 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 485 | "license": "MIT", 486 | "engines": { 487 | "node": ">= 0.4" 488 | }, 489 | "funding": { 490 | "url": "https://github.com/sponsors/ljharb" 491 | } 492 | }, 493 | "node_modules/has-flag": { 494 | "version": "4.0.0", 495 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 496 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 497 | "license": "MIT", 498 | "engines": { 499 | "node": ">=8" 500 | } 501 | }, 502 | "node_modules/has-symbols": { 503 | "version": "1.1.0", 504 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 505 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 506 | "license": "MIT", 507 | "engines": { 508 | "node": ">= 0.4" 509 | }, 510 | "funding": { 511 | "url": "https://github.com/sponsors/ljharb" 512 | } 513 | }, 514 | "node_modules/hasown": { 515 | "version": "2.0.2", 516 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 517 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 518 | "license": "MIT", 519 | "dependencies": { 520 | "function-bind": "^1.1.2" 521 | }, 522 | "engines": { 523 | "node": ">= 0.4" 524 | } 525 | }, 526 | "node_modules/http-errors": { 527 | "version": "2.0.0", 528 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 529 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 530 | "license": "MIT", 531 | "dependencies": { 532 | "depd": "2.0.0", 533 | "inherits": "2.0.4", 534 | "setprototypeof": "1.2.0", 535 | "statuses": "2.0.1", 536 | "toidentifier": "1.0.1" 537 | }, 538 | "engines": { 539 | "node": ">= 0.8" 540 | } 541 | }, 542 | "node_modules/iconv-lite": { 543 | "version": "0.4.24", 544 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 545 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 546 | "license": "MIT", 547 | "dependencies": { 548 | "safer-buffer": ">= 2.1.2 < 3" 549 | }, 550 | "engines": { 551 | "node": ">=0.10.0" 552 | } 553 | }, 554 | "node_modules/inherits": { 555 | "version": "2.0.4", 556 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 557 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 558 | "license": "ISC" 559 | }, 560 | "node_modules/ipaddr.js": { 561 | "version": "1.9.1", 562 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 563 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 564 | "license": "MIT", 565 | "engines": { 566 | "node": ">= 0.10" 567 | } 568 | }, 569 | "node_modules/jake": { 570 | "version": "10.9.2", 571 | "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", 572 | "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", 573 | "license": "Apache-2.0", 574 | "dependencies": { 575 | "async": "^3.2.3", 576 | "chalk": "^4.0.2", 577 | "filelist": "^1.0.4", 578 | "minimatch": "^3.1.2" 579 | }, 580 | "bin": { 581 | "jake": "bin/cli.js" 582 | }, 583 | "engines": { 584 | "node": ">=10" 585 | } 586 | }, 587 | "node_modules/math-intrinsics": { 588 | "version": "1.1.0", 589 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 590 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 591 | "license": "MIT", 592 | "engines": { 593 | "node": ">= 0.4" 594 | } 595 | }, 596 | "node_modules/media-typer": { 597 | "version": "0.3.0", 598 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 599 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 600 | "license": "MIT", 601 | "engines": { 602 | "node": ">= 0.6" 603 | } 604 | }, 605 | "node_modules/merge-descriptors": { 606 | "version": "1.0.3", 607 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", 608 | "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", 609 | "license": "MIT", 610 | "funding": { 611 | "url": "https://github.com/sponsors/sindresorhus" 612 | } 613 | }, 614 | "node_modules/methods": { 615 | "version": "1.1.2", 616 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 617 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 618 | "license": "MIT", 619 | "engines": { 620 | "node": ">= 0.6" 621 | } 622 | }, 623 | "node_modules/mime": { 624 | "version": "1.6.0", 625 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 626 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 627 | "license": "MIT", 628 | "bin": { 629 | "mime": "cli.js" 630 | }, 631 | "engines": { 632 | "node": ">=4" 633 | } 634 | }, 635 | "node_modules/mime-db": { 636 | "version": "1.52.0", 637 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 638 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 639 | "license": "MIT", 640 | "engines": { 641 | "node": ">= 0.6" 642 | } 643 | }, 644 | "node_modules/mime-types": { 645 | "version": "2.1.35", 646 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 647 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 648 | "license": "MIT", 649 | "dependencies": { 650 | "mime-db": "1.52.0" 651 | }, 652 | "engines": { 653 | "node": ">= 0.6" 654 | } 655 | }, 656 | "node_modules/minimatch": { 657 | "version": "3.1.2", 658 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 659 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 660 | "license": "ISC", 661 | "dependencies": { 662 | "brace-expansion": "^1.1.7" 663 | }, 664 | "engines": { 665 | "node": "*" 666 | } 667 | }, 668 | "node_modules/ms": { 669 | "version": "2.0.0", 670 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 671 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", 672 | "license": "MIT" 673 | }, 674 | "node_modules/negotiator": { 675 | "version": "0.6.3", 676 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 677 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 678 | "license": "MIT", 679 | "engines": { 680 | "node": ">= 0.6" 681 | } 682 | }, 683 | "node_modules/object-inspect": { 684 | "version": "1.13.4", 685 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 686 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 687 | "license": "MIT", 688 | "engines": { 689 | "node": ">= 0.4" 690 | }, 691 | "funding": { 692 | "url": "https://github.com/sponsors/ljharb" 693 | } 694 | }, 695 | "node_modules/on-finished": { 696 | "version": "2.4.1", 697 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 698 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 699 | "license": "MIT", 700 | "dependencies": { 701 | "ee-first": "1.1.1" 702 | }, 703 | "engines": { 704 | "node": ">= 0.8" 705 | } 706 | }, 707 | "node_modules/parseurl": { 708 | "version": "1.3.3", 709 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 710 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 711 | "license": "MIT", 712 | "engines": { 713 | "node": ">= 0.8" 714 | } 715 | }, 716 | "node_modules/path-to-regexp": { 717 | "version": "0.1.12", 718 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", 719 | "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", 720 | "license": "MIT" 721 | }, 722 | "node_modules/proxy-addr": { 723 | "version": "2.0.7", 724 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 725 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 726 | "license": "MIT", 727 | "dependencies": { 728 | "forwarded": "0.2.0", 729 | "ipaddr.js": "1.9.1" 730 | }, 731 | "engines": { 732 | "node": ">= 0.10" 733 | } 734 | }, 735 | "node_modules/qs": { 736 | "version": "6.13.0", 737 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", 738 | "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", 739 | "license": "BSD-3-Clause", 740 | "dependencies": { 741 | "side-channel": "^1.0.6" 742 | }, 743 | "engines": { 744 | "node": ">=0.6" 745 | }, 746 | "funding": { 747 | "url": "https://github.com/sponsors/ljharb" 748 | } 749 | }, 750 | "node_modules/range-parser": { 751 | "version": "1.2.1", 752 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 753 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 754 | "license": "MIT", 755 | "engines": { 756 | "node": ">= 0.6" 757 | } 758 | }, 759 | "node_modules/raw-body": { 760 | "version": "2.5.2", 761 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 762 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 763 | "license": "MIT", 764 | "dependencies": { 765 | "bytes": "3.1.2", 766 | "http-errors": "2.0.0", 767 | "iconv-lite": "0.4.24", 768 | "unpipe": "1.0.0" 769 | }, 770 | "engines": { 771 | "node": ">= 0.8" 772 | } 773 | }, 774 | "node_modules/safe-buffer": { 775 | "version": "5.2.1", 776 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 777 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 778 | "funding": [ 779 | { 780 | "type": "github", 781 | "url": "https://github.com/sponsors/feross" 782 | }, 783 | { 784 | "type": "patreon", 785 | "url": "https://www.patreon.com/feross" 786 | }, 787 | { 788 | "type": "consulting", 789 | "url": "https://feross.org/support" 790 | } 791 | ], 792 | "license": "MIT" 793 | }, 794 | "node_modules/safer-buffer": { 795 | "version": "2.1.2", 796 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 797 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 798 | "license": "MIT" 799 | }, 800 | "node_modules/send": { 801 | "version": "0.19.0", 802 | "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", 803 | "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", 804 | "license": "MIT", 805 | "dependencies": { 806 | "debug": "2.6.9", 807 | "depd": "2.0.0", 808 | "destroy": "1.2.0", 809 | "encodeurl": "~1.0.2", 810 | "escape-html": "~1.0.3", 811 | "etag": "~1.8.1", 812 | "fresh": "0.5.2", 813 | "http-errors": "2.0.0", 814 | "mime": "1.6.0", 815 | "ms": "2.1.3", 816 | "on-finished": "2.4.1", 817 | "range-parser": "~1.2.1", 818 | "statuses": "2.0.1" 819 | }, 820 | "engines": { 821 | "node": ">= 0.8.0" 822 | } 823 | }, 824 | "node_modules/send/node_modules/encodeurl": { 825 | "version": "1.0.2", 826 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 827 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 828 | "license": "MIT", 829 | "engines": { 830 | "node": ">= 0.8" 831 | } 832 | }, 833 | "node_modules/send/node_modules/ms": { 834 | "version": "2.1.3", 835 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 836 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 837 | "license": "MIT" 838 | }, 839 | "node_modules/serve-static": { 840 | "version": "1.16.2", 841 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", 842 | "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", 843 | "license": "MIT", 844 | "dependencies": { 845 | "encodeurl": "~2.0.0", 846 | "escape-html": "~1.0.3", 847 | "parseurl": "~1.3.3", 848 | "send": "0.19.0" 849 | }, 850 | "engines": { 851 | "node": ">= 0.8.0" 852 | } 853 | }, 854 | "node_modules/setprototypeof": { 855 | "version": "1.2.0", 856 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 857 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 858 | "license": "ISC" 859 | }, 860 | "node_modules/side-channel": { 861 | "version": "1.1.0", 862 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 863 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 864 | "license": "MIT", 865 | "dependencies": { 866 | "es-errors": "^1.3.0", 867 | "object-inspect": "^1.13.3", 868 | "side-channel-list": "^1.0.0", 869 | "side-channel-map": "^1.0.1", 870 | "side-channel-weakmap": "^1.0.2" 871 | }, 872 | "engines": { 873 | "node": ">= 0.4" 874 | }, 875 | "funding": { 876 | "url": "https://github.com/sponsors/ljharb" 877 | } 878 | }, 879 | "node_modules/side-channel-list": { 880 | "version": "1.0.0", 881 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 882 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 883 | "license": "MIT", 884 | "dependencies": { 885 | "es-errors": "^1.3.0", 886 | "object-inspect": "^1.13.3" 887 | }, 888 | "engines": { 889 | "node": ">= 0.4" 890 | }, 891 | "funding": { 892 | "url": "https://github.com/sponsors/ljharb" 893 | } 894 | }, 895 | "node_modules/side-channel-map": { 896 | "version": "1.0.1", 897 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 898 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 899 | "license": "MIT", 900 | "dependencies": { 901 | "call-bound": "^1.0.2", 902 | "es-errors": "^1.3.0", 903 | "get-intrinsic": "^1.2.5", 904 | "object-inspect": "^1.13.3" 905 | }, 906 | "engines": { 907 | "node": ">= 0.4" 908 | }, 909 | "funding": { 910 | "url": "https://github.com/sponsors/ljharb" 911 | } 912 | }, 913 | "node_modules/side-channel-weakmap": { 914 | "version": "1.0.2", 915 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 916 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 917 | "license": "MIT", 918 | "dependencies": { 919 | "call-bound": "^1.0.2", 920 | "es-errors": "^1.3.0", 921 | "get-intrinsic": "^1.2.5", 922 | "object-inspect": "^1.13.3", 923 | "side-channel-map": "^1.0.1" 924 | }, 925 | "engines": { 926 | "node": ">= 0.4" 927 | }, 928 | "funding": { 929 | "url": "https://github.com/sponsors/ljharb" 930 | } 931 | }, 932 | "node_modules/statuses": { 933 | "version": "2.0.1", 934 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 935 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 936 | "license": "MIT", 937 | "engines": { 938 | "node": ">= 0.8" 939 | } 940 | }, 941 | "node_modules/supports-color": { 942 | "version": "7.2.0", 943 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 944 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 945 | "license": "MIT", 946 | "dependencies": { 947 | "has-flag": "^4.0.0" 948 | }, 949 | "engines": { 950 | "node": ">=8" 951 | } 952 | }, 953 | "node_modules/toidentifier": { 954 | "version": "1.0.1", 955 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 956 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 957 | "license": "MIT", 958 | "engines": { 959 | "node": ">=0.6" 960 | } 961 | }, 962 | "node_modules/type-is": { 963 | "version": "1.6.18", 964 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 965 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 966 | "license": "MIT", 967 | "dependencies": { 968 | "media-typer": "0.3.0", 969 | "mime-types": "~2.1.24" 970 | }, 971 | "engines": { 972 | "node": ">= 0.6" 973 | } 974 | }, 975 | "node_modules/unpipe": { 976 | "version": "1.0.0", 977 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 978 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 979 | "license": "MIT", 980 | "engines": { 981 | "node": ">= 0.8" 982 | } 983 | }, 984 | "node_modules/utils-merge": { 985 | "version": "1.0.1", 986 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 987 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 988 | "license": "MIT", 989 | "engines": { 990 | "node": ">= 0.4.0" 991 | } 992 | }, 993 | "node_modules/vary": { 994 | "version": "1.1.2", 995 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 996 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 997 | "license": "MIT", 998 | "engines": { 999 | "node": ">= 0.8" 1000 | } 1001 | } 1002 | } 1003 | } 1004 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/nodejs/mini-message-board/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "ejs": "^3.1.10", 4 | "express": "^4.21.2" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/nodejs/mini-message-board/routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | 4 | const messages = [ 5 | { 6 | text: "Hi there!", 7 | user: "Amando", 8 | added: new Date(), 9 | }, 10 | { 11 | text: "Hello World!", 12 | user: "Charles", 13 | added: new Date(), 14 | }, 15 | ]; 16 | 17 | router.get("/", (req, res) => { 18 | res.render("index", { title: "Mini Messageboard", messages }); 19 | }); 20 | 21 | router.get("/new", (req, res) => { 22 | res.render("form"); 23 | }); 24 | 25 | router.post("/new", (req, res) => { 26 | const messageUser = req.body.messageUser; 27 | const messageText = req.body.messageText; 28 | messages.push({ text: messageText, user: messageUser, added: new Date() }); 29 | res.redirect("/"); 30 | }); 31 | 32 | router.get("/message/:id", (req, res) => { 33 | const message = messages[req.params.id]; 34 | if (!message) { 35 | return res.status(404).send("Message not found"); 36 | } 37 | res.render("message", { message }); 38 | }); 39 | 40 | module.exports = router; 41 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/nodejs/mini-message-board/views/form.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Add a New Message 7 | 8 | 9 | 10 |

Add a New Message

11 |
12 | 13 | 14 | 15 | 16 | 17 |
18 | Back to messages 19 | 20 | 21 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/nodejs/mini-message-board/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= title %> 7 | 8 | 9 | 10 |

Mini Messageboard

11 | 20 | Add a New Message 21 | 22 | 23 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/nodejs/mini-message-board/views/message.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Message 7 | 8 | 9 |

Message Details

10 |

User: <%= message.user %>

11 |

Message: <%= message.text %>

12 |

Added: <%= message.added.toLocaleString() %>

13 | Back to messages 14 | 15 | 16 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/react/cv-application/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/react/cv-application/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import react from 'eslint-plugin-react' 4 | import reactHooks from 'eslint-plugin-react-hooks' 5 | import reactRefresh from 'eslint-plugin-react-refresh' 6 | 7 | export default [ 8 | { ignores: ['dist'] }, 9 | { 10 | files: ['**/*.{js,jsx}'], 11 | languageOptions: { 12 | ecmaVersion: 2020, 13 | globals: globals.browser, 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | ecmaFeatures: { jsx: true }, 17 | sourceType: 'module', 18 | }, 19 | }, 20 | settings: { react: { version: '18.3' } }, 21 | plugins: { 22 | react, 23 | 'react-hooks': reactHooks, 24 | 'react-refresh': reactRefresh, 25 | }, 26 | rules: { 27 | ...js.configs.recommended.rules, 28 | ...react.configs.recommended.rules, 29 | ...react.configs['jsx-runtime'].rules, 30 | ...reactHooks.configs.recommended.rules, 31 | 'react/jsx-no-target-blank': 'off', 32 | 'react-refresh/only-export-components': [ 33 | 'warn', 34 | { allowConstantExport: true }, 35 | ], 36 | }, 37 | }, 38 | ] 39 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/react/cv-application/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | CV Application 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/react/cv-application/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cv-application", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.3.1", 14 | "react-dom": "^18.3.1" 15 | }, 16 | "devDependencies": { 17 | "@eslint/js": "^9.17.0", 18 | "@types/react": "^18.3.18", 19 | "@types/react-dom": "^18.3.5", 20 | "@vitejs/plugin-react": "^4.3.4", 21 | "eslint": "^9.17.0", 22 | "eslint-plugin-react": "^7.37.2", 23 | "eslint-plugin-react-hooks": "^5.0.0", 24 | "eslint-plugin-react-refresh": "^0.4.16", 25 | "globals": "^15.14.0", 26 | "vite": "^6.0.5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/react/cv-application/src/App.css: -------------------------------------------------------------------------------- 1 | .cv-container { 2 | max-width: 800px; 3 | margin: 40px auto; 4 | padding: 20px; 5 | background: #fff; 6 | border: 1px solid #ddd; 7 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 8 | border-radius: 10px; 9 | font-family: Arial, sans-serif; 10 | } 11 | 12 | .cv-header { 13 | text-align: center; 14 | font-size: 26px; 15 | font-weight: bold; 16 | margin-bottom: 20px; 17 | color: #333; 18 | } 19 | 20 | .cv-section { 21 | margin-bottom: 30px; 22 | padding: 15px; 23 | border-bottom: 1px solid #ccc; 24 | } 25 | 26 | .section-title { 27 | font-size: 22px; 28 | color: #222; 29 | border-bottom: 2px solid #333; 30 | padding-bottom: 5px; 31 | margin-bottom: 15px; 32 | } 33 | 34 | .cv-input-group { 35 | display: flex; 36 | flex-direction: column; 37 | margin-bottom: 12px; 38 | } 39 | 40 | .cv-label { 41 | font-weight: bold; 42 | margin-bottom: 5px; 43 | color: #444; 44 | } 45 | 46 | .cv-input { 47 | width: 100%; 48 | padding: 8px; 49 | font-size: 16px; 50 | border: 1px solid #bbb; 51 | border-radius: 5px; 52 | transition: border 0.2s; 53 | } 54 | 55 | .cv-input:focus { 56 | border-color: #007bff; 57 | outline: none; 58 | } 59 | 60 | .cv-display { 61 | background: #f8f9fa; 62 | padding: 12px; 63 | border-radius: 5px; 64 | font-size: 16px; 65 | line-height: 1.5; 66 | } 67 | 68 | .cv-display p { 69 | margin: 5px 0; 70 | } 71 | 72 | .cv-btn { 73 | padding: 10px 15px; 74 | border: none; 75 | border-radius: 5px; 76 | cursor: pointer; 77 | font-size: 16px; 78 | margin-top: 10px; 79 | } 80 | 81 | .cv-btn.submit { 82 | background: #007bff; 83 | color: #fff; 84 | } 85 | 86 | .cv-btn.edit { 87 | background: #ffc107; 88 | color: #000; 89 | } 90 | 91 | .cv-btn:hover { 92 | opacity: 0.8; 93 | } 94 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/react/cv-application/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import "./App.css"; 3 | import TextInput from "./components/TextInput"; 4 | import { 5 | educationDetails, 6 | experienceDetails, 7 | personalDetails, 8 | } from "./data/formFields"; 9 | 10 | const App = () => { 11 | const initialiseForm = (fields) => 12 | fields.reduce((acc, { name }) => ({ ...acc, [name]: "" }), {}); 13 | 14 | const [formData, setFormData] = useState({ 15 | personal: initialiseForm(personalDetails), 16 | education: initialiseForm(educationDetails), 17 | experience: initialiseForm(experienceDetails), 18 | }); 19 | 20 | const [isEditing, setIsEditing] = useState({ 21 | personal: true, 22 | education: true, 23 | experience: true, 24 | }); 25 | 26 | const handleChange = (section) => (e) => { 27 | const { name, value } = e.target; 28 | setFormData((prev) => ({ 29 | ...prev, 30 | [section]: { ...prev[section], [name]: value }, 31 | })); 32 | }; 33 | 34 | const handleSubmit = (section) => { 35 | setIsEditing((prev) => ({ ...prev, [section]: false })); 36 | }; 37 | 38 | const handleEdit = (section) => { 39 | setIsEditing((prev) => ({ ...prev, [section]: true })); 40 | }; 41 | 42 | const renderFormSection = (title, section, fields) => { 43 | const sectionValues = formData[section]; 44 | 45 | return ( 46 |
47 |

{title}

48 | {isEditing[section] ? ( 49 | <> 50 | {fields.map(({ name, ...inputProps }) => ( 51 | 59 | ))} 60 | 66 | 67 | ) : ( 68 | <> 69 |
70 | {fields.map(({ name, label }) => ( 71 |

72 | {label}: {sectionValues[name]} 73 |

74 | ))} 75 |
76 | 79 | 80 | )} 81 |
82 | ); 83 | }; 84 | 85 | return ( 86 |
87 |

Curriculum Vitae

88 | {renderFormSection("Personal Details", "personal", personalDetails)} 89 | {renderFormSection("Education", "education", educationDetails)} 90 | {renderFormSection("Experience", "experience", experienceDetails)} 91 |
92 | ); 93 | }; 94 | 95 | export default App; 96 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/react/cv-application/src/components/TextInput.jsx: -------------------------------------------------------------------------------- 1 | const TextInput = ({ 2 | label, 3 | id, 4 | name, 5 | type = "text", 6 | placeholder = "", 7 | value, 8 | onChange, 9 | required = false, 10 | className, 11 | ...props 12 | }) => { 13 | const inputId = id || name; 14 | 15 | return ( 16 |
17 | {label && ( 18 | 21 | )} 22 | 33 |
34 | ); 35 | }; 36 | 37 | export default TextInput; 38 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/react/cv-application/src/data/formFields.js: -------------------------------------------------------------------------------- 1 | export const personalDetails = [ 2 | { name: "firstName", label: "First name", placeholder: "Enter first name" }, 3 | { name: "lastName", label: "Last name", placeholder: "Enter last name" }, 4 | { name: "email", label: "Email address", placeholder: "Enter email address", type: "email" }, 5 | { name: "phone", label: "Phone number", placeholder: "Enter phone number", type: "tel" }, 6 | ]; 7 | 8 | export const educationDetails = [ 9 | { name: "school", label: "School/University", placeholder: "Enter your school name" }, 10 | { name: "degree", label: "Degree", placeholder: "Enter your degree" }, 11 | { name: "year", label: "Year of Graduation", type: "number", placeholder: "Enter graduation year" }, 12 | ]; 13 | 14 | export const experienceDetails = [ 15 | { name: "company", label: "Company Name", placeholder: "Enter company name" }, 16 | { name: "position", label: "Position Title", placeholder: "Enter your job title" }, 17 | { name: "responsibilities", label: "Responsibilities", placeholder: "Describe your responsibilities", type: "textarea" }, 18 | { name: "dateFrom", label: "Date From", type: "date" }, 19 | { name: "dateUntil", label: "Date Until", type: "date" }, 20 | ]; 21 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/react/cv-application/src/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SamHillierDev/the-odin-project/cb415e792c9ad553ecd8692917c99a80ff9ba3e8/courses/full-stack-javascript/react/cv-application/src/index.css -------------------------------------------------------------------------------- /courses/full-stack-javascript/react/cv-application/src/main.jsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import App from "./App.jsx"; 4 | import "./index.css"; 5 | 6 | createRoot(document.getElementById("root")).render( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /courses/full-stack-javascript/react/cv-application/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }); 8 | --------------------------------------------------------------------------------