├── .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 |
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 | [](#) [](#) | [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 | [](#) [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) [](#) | [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 | [](#) [](#) | [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 | [](#) [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 | [](#) | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 |  | [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 | 7
15 | 8
16 | 9
17 | DEL
18 | AC
19 |
20 |
21 | 4
22 | 5
23 | 6
24 | x
25 | /
26 |
27 |
28 | 1
29 | 2
30 | 3
31 | +
32 | -
33 |
34 |
35 | 0
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 |
17 |
28 |
29 |
30 | Reset Grid
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 |
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 |
62 |
Some random information.
63 |
81 |
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 |
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 |
13 |
18 |
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 |
13 |
16 |
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 |
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 |
34 | 24 vanilla ice cream sandwiches, unwrapped
35 |
36 | 2 (8 ounce) containers whipped topping (such as Cool Whip®), thawed
37 |
38 | 1 (12 ounce) jar hot fudge ice cream topping, warmed
39 | 1 (12 ounce) jar caramel ice cream topping
40 | ¼ cup chopped pecans, or to taste
41 |
42 | Steps
43 |
44 | Gather the ingredients.
45 |
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 |
50 |
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 |
55 |
56 | Cover the dish with aluminum foil and freeze until set, about 30
57 | minutes. Slice and serve with your favorite toppings!
58 |
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 |
13 |
16 |
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 |
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 |
33 | 1 pound sweet Italian sausage
34 | ¾ pound lean ground beef
35 | ½ cup minced onion
36 | 2 cloves garlic, crushed
37 | 1 (28-ounce) can crushed tomatoes
38 | 2 (6.5 ounce) cans canned tomato sauce
39 | 2 (6-ounce) cans tomato paste
40 | ½ cup water
41 | 2 tablespoons white sugar
42 | 4 tablespoons chopped fresh parsley, divided
43 | 1 ½ teaspoons dried basil leaves
44 | 1 ½ teaspoons salt, divided, or to taste
45 | 1 teaspoon Italian seasoning
46 | ½ teaspoon fennel seeds
47 | ¼ teaspoon ground black pepper
48 | 12 lasagna noodles
49 | 16 ounces ricotta cheese
50 | 1 egg
51 | ¾ pound mozzarella cheese, sliced
52 | ¾ cup grated Parmesan cheese
53 |
54 | Steps
55 |
56 | Gather all your ingredients.
57 |
58 | Cook sausage, ground beef, onion, and garlic in a Dutch oven over
59 | medium heat until well browned.
60 |
61 |
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 |
67 |
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 |
72 |
73 | In a mixing bowl, combine ricotta cheese with egg, remaining 2
74 | tablespoons parsley, and ½ teaspoon salt.
75 |
76 | Preheat the oven to 375 degrees F (190 degrees C).
77 |
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 |
84 |
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 |
89 |
90 | Bake in the preheated oven for 25 minutes. Remove the foil and bake
91 | for an additional 25 minutes.
92 |
93 | Rest lasagna for 15 minutes before serving.
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 |
13 |
16 |
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 |
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 |
37 | 1 cup warm water (110 degrees F/45 degrees C)
38 | 1 (.25 ounce) package active dry yeast
39 | 1 teaspoon white sugar
40 | 2 ½ cups bread flour
41 | 2 tablespoons olive oil
42 | 1 teaspoon salt
43 |
44 | Steps
45 |
46 |
47 | Gather all ingredients. Preheat oven to 450 degrees F (230 degrees C),
48 | and lightly grease a pizza pan.
49 |
50 |
51 | Place warm water in a bowl; add yeast and sugar. Mix and let stand
52 | until creamy, about 10 minutes.
53 |
54 |
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 |
59 | Let rest for 5 minutes.
60 |
61 | Turn dough out onto a lightly floured surface and pat or roll into a
62 | 12-inch circle.
63 |
64 | Transfer to the prepared pizza pan.
65 | Spread crust with sauce and toppings of your choice.
66 |
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 |
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 |
13 |
14 |
15 |
Dashboard
16 |
17 |
18 | Home
19 | Profile
20 | Messages
21 | History
22 | Tasks
23 | Communities
24 |
25 |
26 | Settings
27 | Support
28 | Privacy
29 |
30 |
31 |
32 |
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 |
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 |
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 |
77 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/courses/full-stack-javascript/javascript/library/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Library
7 |
8 |
9 |
10 | New Book
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 | Toggle Read
38 | Remove
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 |
11 | Home
12 | About
13 | Contact Me
14 |
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 |
11 | Home
12 | About
13 | Contact Me
14 |
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 |
11 | Home
12 | About
13 | Contact Me
14 |
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 |
11 | Home
12 | About
13 | Contact Me
14 |
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 |
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 |
12 | <% messages.forEach((msg, index)=> { %>
13 |
14 | <%= msg.user %> : <%= msg.text %>
15 | (<%= msg.added.toLocaleString() %>)
16 | [Open]
17 |
18 | <% }) %>
19 |
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 | handleSubmit(section)}
63 | >
64 | Submit
65 |
66 | >
67 | ) : (
68 | <>
69 |
70 | {fields.map(({ name, label }) => (
71 |
72 | {label}: {sectionValues[name]}
73 |
74 | ))}
75 |
76 | handleEdit(section)}>
77 | Edit
78 |
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 |
19 | {label}
20 |
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 |
--------------------------------------------------------------------------------