diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..8feb476
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,16 @@
+{
+  "env": {
+    "browser": true,
+    "es2021": true
+  },
+  "extends": ["google", "plugin:prettier/recommended"],
+  "parserOptions": {
+    "ecmaVersion": 12,
+    "sourceType": "module"
+  },
+  "rules": {
+    "require-jsdoc": "off",
+    "linebreak-style": ["error", "windows"],
+    "prettier/prettier": ["error", { "endOfLine": "auto" }]
+  }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9a94f57
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,180 @@
+# Created by https://www.toptal.com/developers/gitignore/api/node,yarn
+
+# Edit at https://www.toptal.com/developers/gitignore?templates=node,yarn
+
+### Node
+
+# Logs
+
+logs
+_.log
+npm-debug.log_
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+
+report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
+
+# Runtime data
+
+pids
+_.pid
+_.seed
+\*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+
+lib-cov
+
+# Coverage directory used by tools like istanbul
+
+coverage
+\*.lcov
+
+# nyc test coverage
+
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+
+bower_components
+
+# node-waf configuration
+
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+
+build/Release
+
+# Dependency directories
+
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+
+web_modules/
+
+# TypeScript cache
+
+\*.tsbuildinfo
+
+# Optional npm cache directory
+
+.npm
+
+# Optional eslint cache
+
+.eslintcache
+
+# Microbundle cache
+
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+
+.node_repl_history
+
+# Output of 'npm pack'
+
+\*.tgz
+
+# Yarn Integrity file
+
+.yarn-integrity
+
+# dotenv environment variables file
+
+.env
+.env.test
+.env.production
+
+# parcel-bundler cache (https://parceljs.org/)
+
+.cache
+.parcel-cache
+
+# Next.js build output
+
+.next
+out
+
+# Nuxt.js build / generate output
+
+.nuxt
+dist
+
+# Gatsby files
+
+.cache/
+
+# Comment in the public line in if your project uses Gatsby and not Next.js
+
+# https://nextjs.org/blog/next-9-1#public-directory-support
+
+# public
+
+# vuepress build output
+
+.vuepress/dist
+
+# Serverless directories
+
+.serverless/
+
+# FuseBox cache
+
+.fusebox/
+
+# DynamoDB Local files
+
+.dynamodb/
+
+# TernJS port file
+
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+
+.vscode-test
+
+# yarn v2
+
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.\*
+
+### yarn
+
+# https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored
+
+.yarn/\*
+!.yarn/releases
+!.yarn/plugins
+!.yarn/sdks
+!.yarn/versions
+
+# if you are NOT using Zero-installs, then:
+
+# comment the following lines
+
+!.yarn/cache
+
+# and uncomment the following lines
+
+# .pnp.\*
+
+# End of https://www.toptal.com/developers/gitignore/api/node,yarn
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 0000000..9a70205
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,10 @@
+{
+  "printWidth": 100,
+  "tabWidth": 2,
+  "singleQuote": true,
+  "trailingComma": "all",
+  "bracketSpacing": true,
+  "semi": true,
+  "useTabs": false,
+  "endOfLine": "lf"
+}
diff --git a/README.md b/README.md
index 8e7c12a..4eb605a 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,22 @@
 # 25 Beginner JavaScript Project Ideas
+
 ## 규칙
+
 - 브랜치명은 JSmini_lia 형식으로 만들어 주세요.
 - 매주 주어진 양의 project를 완성 후 project 단위로 PR해 주세요!
-    - [Project1/리아] 프로젝트명
-    - 예) [Project1/리아] Colors
+  - [Project1/리아] 프로젝트명
+  - 예) [Project1/리아] Colors
 
 ## 1회차(~9/12)
+
 ### Project 1 Colors
+
 
+
 ### Project 2 Hex colors gradient
+
 
+
 ### Project 3 Random quote generator
+
 
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..4be7bf1
--- /dev/null
+++ b/index.html
@@ -0,0 +1,20 @@
+
+
+  
+    
+    
+    
+    JS Mini Project 25
+    
+    
+    
+    
+  
+  
+    
+    
+  
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..2c3a1b3
--- /dev/null
+++ b/package.json
@@ -0,0 +1,16 @@
+{
+  "scripts": {
+    "start": "node main.js",
+    "lint": "eslint *.js",
+    "lint:fix": "eslint --fix *.js"
+  },
+  "devDependencies": {
+    "eslint": "^7.32.0",
+    "eslint-config-airbnb-base": "^14.2.1",
+    "eslint-config-google": "^0.14.0",
+    "eslint-config-prettier": "^8.3.0",
+    "eslint-plugin-import": "^2.24.0",
+    "eslint-plugin-prettier": "^3.4.0",
+    "prettier": "^2.3.2"
+  }
+}
diff --git a/project1-colors/src/Button.js b/project1-colors/src/Button.js
index e47c425..699107a 100644
--- a/project1-colors/src/Button.js
+++ b/project1-colors/src/Button.js
@@ -1,14 +1,12 @@
 export default function Button({ $target, onClick }) {
-  const $button = document.createElement("button");
+  const $button = document.createElement('button');
   $target.append($button);
-  $button.textContent = "Click Me!";
+  $button.textContent = 'Click Me!';
 
-  $button.addEventListener("click", (e) => {
+  $button.addEventListener('click', () => {
     const generateRandomColorCode = () => {
       const randomHex = Math.floor(Math.random() * 16777215).toString(16);
-      return randomHex.length < 6
-        ? `${"0".repeat(6 - randomHex.length)}${randomHex}`
-        : randomHex;
+      return randomHex.length < 6 ? `${'0'.repeat(6 - randomHex.length)}${randomHex}` : randomHex;
     };
     onClick(generateRandomColorCode());
   });
diff --git a/project1-colors/src/Project1Page.js b/project1-colors/src/Project1Page.js
new file mode 100644
index 0000000..0feb8af
--- /dev/null
+++ b/project1-colors/src/Project1Page.js
@@ -0,0 +1,18 @@
+import Button from './Button.js';
+
+export default function Project1Page({ $target }) {
+  const $page = document.createElement('div');
+
+  $page.className = 'project1 page';
+
+  new Button({
+    $target: $page,
+    onClick: (colorCode) => {
+      $page.style.backgroundColor = `#${colorCode}`;
+    },
+  });
+
+  this.render = () => {
+    $target.append($page);
+  };
+}
diff --git a/project1-colors/src/main.js b/project1-colors/src/main.js
index e1a9c64..0d3e70e 100644
--- a/project1-colors/src/main.js
+++ b/project1-colors/src/main.js
@@ -1,6 +1,6 @@
-import Button from "./Button.js";
+import Button from './Button.js';
 
-const $app = document.querySelector("#app");
+const $app = document.querySelector('#app');
 
 new Button({
   $target: $app,
diff --git a/project2-HexColorsGradient/src/Button.js b/project2-HexColorsGradient/src/Button.js
index c69201c..a6327ed 100644
--- a/project2-HexColorsGradient/src/Button.js
+++ b/project2-HexColorsGradient/src/Button.js
@@ -1,24 +1,18 @@
 export default function Button({ $target, onClick }) {
-  const $button = document.createElement("button");
+  const $button = document.createElement('button');
   $target.append($button);
-  $button.textContent = "Click Me!";
+  $button.textContent = 'Click Me!';
 
-  $button.addEventListener("click", (e) => {
+  $button.addEventListener('click', (e) => {
     const generateRandomDirection = () => {
       return Math.floor(Math.random() * 2) === 0 ? `right` : `left`;
     };
 
     const generateRandomColorCode = () => {
       const randomHex = Math.floor(Math.random() * 16777215).toString(16);
-      return randomHex.length < 6
-        ? `${"0".repeat(6 - randomHex.length)}${randomHex}`
-        : randomHex;
+      return randomHex.length < 6 ? `${'0'.repeat(6 - randomHex.length)}${randomHex}` : randomHex;
     };
 
-    onClick(
-      generateRandomDirection(),
-      generateRandomColorCode(),
-      generateRandomColorCode()
-    );
+    onClick(generateRandomDirection(), generateRandomColorCode(), generateRandomColorCode());
   });
 }
diff --git a/project2-HexColorsGradient/src/Project2Page.js b/project2-HexColorsGradient/src/Project2Page.js
new file mode 100644
index 0000000..2966efe
--- /dev/null
+++ b/project2-HexColorsGradient/src/Project2Page.js
@@ -0,0 +1,26 @@
+import Button from './Button.js';
+import Sign from './Sign.js';
+
+export default function Project2Page({ $target }) {
+  const $page = document.createElement('div');
+
+  $page.className = 'project2 page';
+
+  const signBoard = new Sign({ $target: $page });
+
+  new Button({
+    $target: $page,
+    onClick: (direction, startColor, endColor) => {
+      $page.style.background = `linear-gradient(to ${direction}, #${startColor}, #${endColor})`;
+      signBoard.setState({
+        ...signBoard.state,
+        direction,
+        colorCode: { start: startColor, end: endColor },
+      });
+    },
+  });
+
+  this.render = () => {
+    $target.append($page);
+  };
+}
diff --git a/project2-HexColorsGradient/src/Sign.js b/project2-HexColorsGradient/src/Sign.js
index e389a3f..e48b356 100644
--- a/project2-HexColorsGradient/src/Sign.js
+++ b/project2-HexColorsGradient/src/Sign.js
@@ -1,12 +1,12 @@
 export default function Sign({ $target }) {
-  const $signContainer = document.createElement("div");
+  const $signContainer = document.createElement('div');
   $target.append($signContainer);
 
   this.state = {
-    direction: "right",
+    direction: 'right',
     colorCode: {
-      start: "ffffff",
-      end: "ffffff",
+      start: 'ffffff',
+      end: 'ffffff',
     },
   };
 
diff --git a/project2-HexColorsGradient/src/main.js b/project2-HexColorsGradient/src/main.js
index e6f9e02..6eb1249 100644
--- a/project2-HexColorsGradient/src/main.js
+++ b/project2-HexColorsGradient/src/main.js
@@ -1,7 +1,7 @@
-import Button from "./Button.js";
-import Sign from "./Sign.js";
+import Button from './Button.js';
+import Sign from './Sign.js';
 
-const $app = document.querySelector("#app");
+const $app = document.querySelector('#app');
 
 const signBoard = new Sign({ $target: $app });
 
diff --git a/project2-HexColorsGradient/src/style.css b/project2-HexColorsGradient/src/style.css
index 7d81298..d0f6b5b 100644
--- a/project2-HexColorsGradient/src/style.css
+++ b/project2-HexColorsGradient/src/style.css
@@ -1,9 +1,9 @@
-@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap");
+@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap');
 
 * {
   margin: 0;
   padding: 0;
-  font-family: "Noto Sans KR", sans-serif;
+  font-family: 'Noto Sans KR', sans-serif;
 }
 
 :root {
diff --git a/project3-RandomQuoteGenerator/src/App.js b/project3-RandomQuoteGenerator/src/App.js
index 4d63ca1..3bede71 100644
--- a/project3-RandomQuoteGenerator/src/App.js
+++ b/project3-RandomQuoteGenerator/src/App.js
@@ -1,6 +1,6 @@
-import { request } from "./api.js";
-import Button from "./Button.js";
-import QuoteBox from "./QuoteBox.js";
+import { request } from './api.js';
+import Button from './Button.js';
+import QuoteBox from './QuoteBox.js';
 
 export default function App({ $target }) {
   const quoteBox = new QuoteBox({ $target });
@@ -8,10 +8,8 @@ export default function App({ $target }) {
     $target,
     onClick: async () => {
       const generatedQuoteSet = await request();
-      const quote = `"${generatedQuoteSet.quote.replace("\uFFFD", "'")}"`;
-      const author = generatedQuoteSet.author
-        ? `- ${generatedQuoteSet.author}`
-        : ``;
+      const quote = `"${generatedQuoteSet.quote.replace('\uFFFD', "'")}"`;
+      const author = generatedQuoteSet.author ? `- ${generatedQuoteSet.author}` : ``;
 
       quoteBox.setState({
         ...quoteBox.state,
diff --git a/project3-RandomQuoteGenerator/src/Button.js b/project3-RandomQuoteGenerator/src/Button.js
index ba81b72..649772b 100644
--- a/project3-RandomQuoteGenerator/src/Button.js
+++ b/project3-RandomQuoteGenerator/src/Button.js
@@ -1,9 +1,9 @@
 export default function Button({ $target, onClick }) {
-  const $button = document.createElement("button");
+  const $button = document.createElement('button');
   $target.append($button);
-  $button.textContent = "Generate Quote";
+  $button.textContent = 'Generate Quote';
 
-  $button.addEventListener("click", (e) => {
+  $button.addEventListener('click', (e) => {
     onClick();
   });
 }
diff --git a/project3-RandomQuoteGenerator/src/Project3Page.js b/project3-RandomQuoteGenerator/src/Project3Page.js
new file mode 100644
index 0000000..126d504
--- /dev/null
+++ b/project3-RandomQuoteGenerator/src/Project3Page.js
@@ -0,0 +1,13 @@
+import App from './App.js';
+
+export default function Project3Page({ $target }) {
+  const $page = document.createElement('div');
+
+  $page.className = 'project3 page';
+
+  new App({ $target: $page });
+
+  this.render = () => {
+    $target.append($page);
+  };
+}
diff --git a/project3-RandomQuoteGenerator/src/QuoteBox.js b/project3-RandomQuoteGenerator/src/QuoteBox.js
index 6d6a0fc..49afb46 100644
--- a/project3-RandomQuoteGenerator/src/QuoteBox.js
+++ b/project3-RandomQuoteGenerator/src/QuoteBox.js
@@ -1,11 +1,11 @@
 export default function QuoteBox({ $target }) {
-  const $box = document.createElement("div");
+  const $box = document.createElement('div');
   $target.append($box);
-  $box.className = "quoteContainer";
+  $box.className = 'quoteContainer';
 
   this.state = {
-    quote: "",
-    author: "",
+    quote: '',
+    author: '',
   };
 
   this.setState = (nextState) => {
diff --git a/project3-RandomQuoteGenerator/src/api.js b/project3-RandomQuoteGenerator/src/api.js
index 4467b8d..4eacc00 100644
--- a/project3-RandomQuoteGenerator/src/api.js
+++ b/project3-RandomQuoteGenerator/src/api.js
@@ -1,11 +1,11 @@
-export const API_END_POINT = "https://free-quotes-api.herokuapp.com/";
+export const API_END_POINT = 'https://free-quotes-api.herokuapp.com/';
 
 export const request = async () => {
   try {
     const res = await fetch(API_END_POINT);
 
     if (!res.ok) {
-      throw new Error("API 호출 에러");
+      throw new Error('API 호출 에러');
     }
     return await res.json();
   } catch (e) {
diff --git a/project3-RandomQuoteGenerator/src/main.js b/project3-RandomQuoteGenerator/src/main.js
index 4628364..e21a427 100644
--- a/project3-RandomQuoteGenerator/src/main.js
+++ b/project3-RandomQuoteGenerator/src/main.js
@@ -1,5 +1,5 @@
-import App from "./App.js";
+import App from './App.js';
 
-const $app = document.querySelector("#app");
+const $app = document.querySelector('#app');
 
 new App({ $target: $app });
diff --git a/project3-RandomQuoteGenerator/src/style.css b/project3-RandomQuoteGenerator/src/style.css
index 661116d..f581cee 100644
--- a/project3-RandomQuoteGenerator/src/style.css
+++ b/project3-RandomQuoteGenerator/src/style.css
@@ -1,4 +1,4 @@
-@import url("https://fonts.googleapis.com/css2?family=Caveat&family=Dancing+Script&display=swap");
+@import url('https://fonts.googleapis.com/css2?family=Caveat&family=Dancing+Script&display=swap');
 
 * {
   margin: 0;
@@ -12,8 +12,8 @@
   --button-hover-color: #138496;
   --button-font-color: white;
   --button-border-color: #117a8b;
-  --font-quote: "Caveat", cursive;
-  --font-author: "Dancing Script", cursive;
+  --font-quote: 'Caveat', cursive;
+  --font-author: 'Dancing Script', cursive;
 }
 
 #app {
diff --git a/project4-TheMessage/index.html b/project4-TheMessage/index.html
new file mode 100644
index 0000000..acc6440
--- /dev/null
+++ b/project4-TheMessage/index.html
@@ -0,0 +1,20 @@
+
+
+  
+    
+    
+    
+    메세지 보내기
+    
+    
+    
+    
+  
+  
+    
+    
+  
+
diff --git a/project4-TheMessage/src/App.js b/project4-TheMessage/src/App.js
new file mode 100644
index 0000000..52af36d
--- /dev/null
+++ b/project4-TheMessage/src/App.js
@@ -0,0 +1,23 @@
+import Form from './Form.js';
+
+export default function App({ $target }) {
+  const $messageBox = document.createElement('div');
+  const $previewBox = document.createElement('p');
+  $messageBox.className = 'messageBox';
+  $previewBox.className = 'previewBox';
+  $target.append($messageBox);
+
+  $messageBox.innerHTML = /* html */ `
+    메시지 보내기
+    
+  `;
+
+  new Form({
+    $target: $messageBox,
+    onSubmit: (messageContent) => {
+      $previewBox.textContent = messageContent;
+    },
+  });
+
+  $messageBox.append($previewBox);
+}
diff --git a/project4-TheMessage/src/Form.js b/project4-TheMessage/src/Form.js
new file mode 100644
index 0000000..140883a
--- /dev/null
+++ b/project4-TheMessage/src/Form.js
@@ -0,0 +1,25 @@
+export default function Form({ $target, onSubmit }) {
+  const $form = document.createElement('form');
+  $target.append($form);
+
+  $form.innerHTML = /* html */ `
+    
+    
+      
+      
+    
+    
+  `;
+
+  $form.addEventListener('submit', (e) => {
+    e.preventDefault();
+    const $input = document.querySelector('#messageInput');
+    if (!$input.value) {
+      alert('한 글자 이상 입력해주세요.');
+      return;
+    }
+    const messageContent = $input.value;
+    $input.value = '';
+    onSubmit(messageContent);
+  });
+}
diff --git a/project4-TheMessage/src/main.js b/project4-TheMessage/src/main.js
new file mode 100644
index 0000000..ddd8e60
--- /dev/null
+++ b/project4-TheMessage/src/main.js
@@ -0,0 +1,8 @@
+import App from './App.js';
+
+const $target = document.querySelector('main');
+const $page = document.createElement('div');
+$target.append($page);
+$page.className = 'project4 page';
+
+new App({ $target: $page });
diff --git a/project4-TheMessage/src/style.css b/project4-TheMessage/src/style.css
new file mode 100644
index 0000000..dab56c5
--- /dev/null
+++ b/project4-TheMessage/src/style.css
@@ -0,0 +1,114 @@
+:root .project4 {
+  --bg-color: linear-gradient(to top, #a8edea 0%, #fed6e3 100%);
+  --base-font-family: 'Noto Sans KR', sans-serif;
+  --border-radius: 0.4rem;
+  --box-shadow: 2px 2px 4px 0 rgba(0, 0, 0, 0.4);
+  --base-margin: 1.3rem;
+  --base-padding: 1.6rem;
+  --base-button-padding: 0.5rem;
+  --base-button-border: 1px solid gray;
+  --base-button-color: #d4e1e6;
+  --base-button-hover-color: #f4d9e4;
+  --base-line-height: 1.6rem;
+}
+
+.wrap {
+  width: 100vw;
+  height: 100vh;
+}
+
+.page {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.project4.page {
+  background-image: var(--bg-color);
+  padding: 1.6rem;
+  box-sizing: border-box;
+  flex-direction: row;
+  font-weight: bold;
+}
+
+.project4 .messageBox {
+  font-family: var(--base-font-family);
+  padding: 0 var(--base-padding);
+  background-color: white;
+  border-radius: var(--border-radius);
+  box-shadow: var(--box-shadow);
+  flex-basis: 500px;
+  box-sizing: border-box;
+}
+
+.project4 .messageBox h2 {
+  font-size: 1.5rem;
+  font-weight: bold;
+  padding: var(--base-padding);
+  text-align: center;
+}
+
+.project4 .messageBox hr {
+  background-color: lightgray;
+  height: 2px;
+  border: 0;
+  margin-top: 0;
+}
+
+.project4 .messageBox label {
+  display: block;
+  font-weight: normal;
+  margin: var(--base-margin) 0;
+}
+
+.project4 .messageBox .inputBox {
+  display: flex;
+}
+
+.project4 .messageBox .inputBox button {
+  padding: var(--base-button-padding);
+  border: var(--base-button-border);
+  border-right-width: 0;
+  border-radius: 0;
+  border-top-left-radius: var(--border-radius);
+}
+
+.project4 .messageBox .inputBox button:hover {
+  background-color: var(--base-button-hover-color);
+}
+
+.project4 .messageBox #messageInput {
+  flex-grow: 1;
+  padding: 0.5rem;
+  font-family: var(--base-font-family);
+  border: 1px solid gray;
+  border-radius: 0;
+  border-top-right-radius: var(--border-radius);
+}
+
+.project4 .messageBox #messageInput:focus {
+  outline: none;
+}
+
+.project4 .messageBox .button__submit {
+  width: 100%;
+  font-family: var(--base-font-family);
+  padding: var(--base-button-padding);
+  border-radius: 0;
+  border-bottom-left-radius: var(--border-radius);
+  border-bottom-right-radius: var(--border-radius);
+  border: var(--base-button-border);
+  border-top-width: 0;
+  background-color: var(--base-button-color);
+}
+
+.project4 .messageBox .button__submit:hover {
+  background-color: var(--base-button-hover-color);
+}
+
+.project4 .messageBox .previewBox {
+  padding: 1.4rem;
+  text-align: center;
+}
diff --git a/project5-Counter/index.html b/project5-Counter/index.html
new file mode 100644
index 0000000..7c37130
--- /dev/null
+++ b/project5-Counter/index.html
@@ -0,0 +1,20 @@
+
+
+  
+    
+    
+    
+    카운터
+    
+    
+    
+    
+  
+  
+    
+    
+  
+
diff --git a/project5-Counter/src/App.js b/project5-Counter/src/App.js
new file mode 100644
index 0000000..6f520bf
--- /dev/null
+++ b/project5-Counter/src/App.js
@@ -0,0 +1,30 @@
+import Button from './Button.js';
+import * as feature from './Constants.js';
+import Count from './Count.js';
+
+export default function App({ $target }) {
+  const $container = document.createElement('div');
+  $container.className = 'countContainer';
+  $target.append($container);
+  $container.innerHTML = /* html */ `
+    Counter
+  `;
+
+  const count = new Count({ $target: $container, initialState: 0 });
+
+  new Button({
+    $target: $container,
+    feature: feature.INCREASE,
+    onClick: () => {
+      count.setState(count.state + 1);
+    },
+  });
+
+  new Button({
+    $target: $container,
+    feature: feature.DECREASE,
+    onClick: () => {
+      count.setState(count.state - 1);
+    },
+  });
+}
diff --git a/project5-Counter/src/Button.js b/project5-Counter/src/Button.js
new file mode 100644
index 0000000..3e2ff37
--- /dev/null
+++ b/project5-Counter/src/Button.js
@@ -0,0 +1,11 @@
+export default function Button({ $target, feature, onClick }) {
+  const $button = document.createElement('button');
+  $target.append($button);
+
+  $button.textContent = feature;
+  $button.className = feature;
+
+  $button.addEventListener('click', (e) => {
+    onClick();
+  });
+}
diff --git a/project5-Counter/src/Constants.js b/project5-Counter/src/Constants.js
new file mode 100644
index 0000000..9df8287
--- /dev/null
+++ b/project5-Counter/src/Constants.js
@@ -0,0 +1,2 @@
+export const INCREASE = 'Increase';
+export const DECREASE = 'Decrease';
diff --git a/project5-Counter/src/Count.js b/project5-Counter/src/Count.js
new file mode 100644
index 0000000..6d63eae
--- /dev/null
+++ b/project5-Counter/src/Count.js
@@ -0,0 +1,17 @@
+export default function Count({ $target, initialState }) {
+  const $count = document.createElement('p');
+  $target.append($count);
+
+  this.state = initialState;
+
+  this.setState = (nextState) => {
+    this.state = nextState;
+    this.render();
+  };
+
+  this.render = () => {
+    $count.textContent = this.state;
+  };
+
+  this.render();
+}
diff --git a/project5-Counter/src/main.js b/project5-Counter/src/main.js
new file mode 100644
index 0000000..d425e6b
--- /dev/null
+++ b/project5-Counter/src/main.js
@@ -0,0 +1,8 @@
+import App from './App.js';
+
+const $target = document.querySelector('main');
+const $page = document.createElement('div');
+$target.append($page);
+$page.className = 'project5 page';
+
+new App({ $target: $page });
diff --git a/project5-Counter/src/style.css b/project5-Counter/src/style.css
new file mode 100644
index 0000000..7c1db8d
--- /dev/null
+++ b/project5-Counter/src/style.css
@@ -0,0 +1,74 @@
+:root .project5 {
+  --bg-color: linear-gradient(to top, #9795f0 0%, #fbc8d4 100%);
+  --base-font-family: 'Noto Sans KR', sans-serif;
+  --border-radius: 0.4rem;
+  --box-shadow: 2px 2px 4px 0 rgba(0, 0, 0, 0.4);
+  --base-margin: 1.3rem;
+  --base-padding: 1.6rem;
+  --button-padding: 0.5rem;
+  --button-border: 1px solid gray;
+  --button-color: rgb(52, 58, 64);
+  --button-bg-color: transparent;
+  --button-hover-color: purple;
+  --box-border: 5px solid rebeccapurple;
+}
+
+.wrap {
+  width: 100vw;
+  height: 100vh;
+}
+
+.page {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.project5 {
+  background-image: var(--bg-color);
+  font-weight: bold;
+}
+
+.project5 button {
+  padding: 10px 15px;
+  background-color: var(--button-bg-color);
+  border-radius: var(--border-radius);
+  cursor: pointer;
+  outline: 0;
+  color: var(--button-color);
+  font-size: 16px;
+  border: var(--button-border);
+  transition: all 0.2s;
+}
+
+.project5 button:hover {
+  color: white;
+  font-weight: 500;
+  background-color: var(--button-hover-color);
+}
+
+.project5 button + button {
+  margin-left: 20px;
+}
+
+.project5 .countContainer {
+  text-align: center;
+  background-color: white;
+  flex-basis: 500px;
+  padding: var(--base-padding);
+  box-sizing: border-box;
+  border-radius: var(--border-radius);
+  box-shadow: var(--box-shadow);
+  border: var(--box-border);
+}
+
+.project5 .countContainer h2 {
+  font-size: 2rem;
+}
+
+.project5 .countContainer p {
+  font-size: 6.5rem;
+  line-height: 12rem;
+}
diff --git a/project6-ImageCarousel/index.html b/project6-ImageCarousel/index.html
new file mode 100644
index 0000000..2c89d13
--- /dev/null
+++ b/project6-ImageCarousel/index.html
@@ -0,0 +1,20 @@
+
+
+  
+    
+    
+    
+    이미지 캐러셀
+    
+    
+    
+    
+  
+  
+    
+    
+  
+
diff --git a/project6-ImageCarousel/src/App.js b/project6-ImageCarousel/src/App.js
new file mode 100644
index 0000000..3c679c4
--- /dev/null
+++ b/project6-ImageCarousel/src/App.js
@@ -0,0 +1,61 @@
+import Controller from './Controller.js';
+import Dots from './Dots.js';
+import Slide from './Slide.js';
+
+export default function App({ $target }) {
+  const $container = document.createElement('div');
+  const $slideContainer = document.createElement('div');
+  $container.className = 'container';
+  $slideContainer.className = 'slideContainer';
+  $target.append($container);
+  $container.append($slideContainer);
+
+  this.state = {
+    imgLength: 7,
+    slideIndex: 1,
+  };
+
+  const { imgLength } = this.state;
+  let { slideIndex } = this.state;
+
+  for (let i = 1; i <= imgLength; i++) {
+    new Slide({
+      $target: $slideContainer,
+      initialState: {
+        curIndex: i,
+        imgLength,
+      },
+    });
+  }
+
+  new Dots({ $target: $container, initialState: { imgLength } });
+
+  const showSlides = (n) => {
+    const slides = document.querySelectorAll('.slide');
+    const dots = document.querySelectorAll('.dot');
+
+    if (n > imgLength) {
+      slideIndex = 1;
+    }
+    if (n < 1) {
+      slideIndex = imgLength;
+    }
+    slides.forEach((slide) => (slide.style.display = 'none'));
+    dots.forEach((dot) => (dot.className = dot.className.replace(' active', '')));
+
+    const curSlide = slides[slideIndex - 1];
+    const curdot = dots[slideIndex - 1];
+    curSlide.style.display = 'block';
+    curdot.className += ' active';
+  };
+
+  this.init = () => {
+    // slide가 렌더되기 전에 showSlides()가 실행되는 문제가 있어서
+    // wep API를 이용해 slide 렌더 이후에 showSlides()를 처리할 수 있게끔 함
+    setTimeout(() => showSlides(slideIndex), 0);
+  };
+
+  this.init();
+
+  new Controller({ $target: $slideContainer, onClick: (n) => showSlides((slideIndex += n)) });
+}
diff --git a/project6-ImageCarousel/src/Controller.js b/project6-ImageCarousel/src/Controller.js
new file mode 100644
index 0000000..0f9c878
--- /dev/null
+++ b/project6-ImageCarousel/src/Controller.js
@@ -0,0 +1,19 @@
+export default function Controller({ $target, onClick }) {
+  const $prevButton = document.createElement('a');
+  const $nextButton = document.createElement('a');
+  $prevButton.className = 'prev controlButton';
+  $prevButton.textContent = '❮';
+  $nextButton.className = 'next controlButton';
+  $nextButton.textContent = '❯';
+  $target.append($prevButton, $nextButton);
+
+  window.addEventListener('click', (e) => {
+    if (e.target.classList.contains('prev')) {
+      onClick(-1);
+    }
+
+    if (e.target.classList.contains('next')) {
+      onClick(1);
+    }
+  });
+}
diff --git a/project6-ImageCarousel/src/Dots.js b/project6-ImageCarousel/src/Dots.js
new file mode 100644
index 0000000..a28ca48
--- /dev/null
+++ b/project6-ImageCarousel/src/Dots.js
@@ -0,0 +1,11 @@
+export default function Dots({ $target, initialState }) {
+  const $dotsContainer = document.createElement('div');
+  $dotsContainer.className = 'dotsContainer';
+  $target.append($dotsContainer);
+
+  this.state = initialState;
+
+  $dotsContainer.innerHTML = /* html */ `
+    ${``.repeat(this.state.imgLength)}
+  `;
+}
diff --git a/project6-ImageCarousel/src/Slide.js b/project6-ImageCarousel/src/Slide.js
new file mode 100644
index 0000000..f5f7589
--- /dev/null
+++ b/project6-ImageCarousel/src/Slide.js
@@ -0,0 +1,18 @@
+import { IMG_API_END_POINT, KEYWORDS_FOR_IMAGE } from './constants.js';
+
+export default function Slide({ $target, initialState }) {
+  const $slide = document.createElement('div');
+  $slide.className = 'slide fade';
+  $target.append($slide);
+
+  this.state = initialState;
+
+  const { curIndex, imgLength } = this.state;
+
+  fetch(`${IMG_API_END_POINT}${KEYWORDS_FOR_IMAGE[curIndex - 1]}`).then((res) => {
+    $slide.innerHTML = /* html */ `
+      ${curIndex} / ${imgLength}
+       +    `;
+  });
+}
diff --git a/project6-ImageCarousel/src/constants.js b/project6-ImageCarousel/src/constants.js
new file mode 100644
index 0000000..88c50bd
--- /dev/null
+++ b/project6-ImageCarousel/src/constants.js
@@ -0,0 +1,5 @@
+export const IMG_API_END_POINT = `https://source.unsplash.com/1600x900/?`;
+
+// 같은 api를 한 번에 여러 번 호출하면 같은 이미지를 불러오는 문제가 있어
+// 각각 검색 키워드를 달리 지정해줌
+export const KEYWORDS_FOR_IMAGE = ['nature', 'water', 'beach', 'sea', 'park', 'mountain', 'star'];
diff --git a/project6-ImageCarousel/src/main.js b/project6-ImageCarousel/src/main.js
new file mode 100644
index 0000000..963f34b
--- /dev/null
+++ b/project6-ImageCarousel/src/main.js
@@ -0,0 +1,8 @@
+import App from './App.js';
+
+const $target = document.querySelector('main');
+const $page = document.createElement('div');
+$target.append($page);
+$page.className = 'project6 page';
+
+new App({ $target: $page });
diff --git a/project6-ImageCarousel/src/style.css b/project6-ImageCarousel/src/style.css
new file mode 100644
index 0000000..65552ff
--- /dev/null
+++ b/project6-ImageCarousel/src/style.css
@@ -0,0 +1,115 @@
+.wrap {
+  width: 100vw;
+  height: 100vh;
+}
+
+.page {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.project6 {
+  box-sizing: border-box;
+  padding: 1rem;
+  font-weight: bold;
+}
+
+.project6 .container {
+  width: 70%;
+  position: relative;
+  overflow: hidden;
+}
+
+.project6 .slideContainer {
+  position: relative;
+}
+
+.project6 .slideContainer .slide {
+  display: none;
+}
+
+.project6 .slideContainer .slideImg {
+  width: 100%;
+}
+
+.project6 .slideContainer .numberText {
+  color: #f2f2f2;
+  font-size: 16px;
+  padding: 8px 12px;
+  position: absolute;
+  top: 0;
+  background-color: rgba(0, 0, 0, 0.4);
+  border-radius: 1rem;
+  margin-top: 10px;
+  margin-left: 10px;
+}
+
+.project6 .fade {
+  animation-name: fade;
+  animation-duration: 1.5s;
+}
+
+.project6 .controlButton {
+  cursor: pointer;
+  position: absolute;
+  top: 50%;
+  margin-top: -22px;
+  padding: 16px;
+  color: white;
+  font-weight: bold;
+  font-size: 18px;
+  transition: 0.6s ease;
+  border-radius: 0 3px 3px 0;
+}
+
+.project6 .controlButton:hover {
+  background-color: rgba(0, 0, 0, 0.8);
+}
+
+.project6 .controlButton.next {
+  right: 0;
+  border-radius: 3px 0 0 3px;
+}
+
+.project6 .dotsContainer {
+  padding: 1rem;
+  text-align: center;
+}
+
+.project6 .dotsContainer .dot {
+  height: 10px;
+  width: 10px;
+  margin: 0 5px;
+  background-color: #bbb;
+  border-radius: 50%;
+  display: inline-block;
+  transition: background-color 0.6s ease;
+}
+
+.project6 .dotsContainer .dot.active {
+  background-color: #717171;
+}
+
+@keyframes fade {
+  from {
+    opacity: 0.4;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+@media (max-width: 800px) {
+  .project6 .container {
+    width: 100%;
+  }
+}
+
+@media (min-width: 1400px) {
+  .project6 .container {
+    width: 60%;
+  }
+}
diff --git a/project7-DigitalClock/index.html b/project7-DigitalClock/index.html
new file mode 100644
index 0000000..22f14e6
--- /dev/null
+++ b/project7-DigitalClock/index.html
@@ -0,0 +1,20 @@
+
+
+  
+    
+    
+    
+    디지털 시계
+    
+    
+    
+    
+  
+  
+    
+    
+  
+
diff --git a/project7-DigitalClock/src/App.js b/project7-DigitalClock/src/App.js
new file mode 100644
index 0000000..143a67f
--- /dev/null
+++ b/project7-DigitalClock/src/App.js
@@ -0,0 +1,5 @@
+import Clock from './Clock.js';
+
+export default function App({ $target }) {
+  new Clock({ $target });
+}
diff --git a/project7-DigitalClock/src/Clock.js b/project7-DigitalClock/src/Clock.js
new file mode 100644
index 0000000..d0434c9
--- /dev/null
+++ b/project7-DigitalClock/src/Clock.js
@@ -0,0 +1,35 @@
+export default function Clock({ $target }) {
+  const $clock = document.createElement('div');
+  $clock.className = 'clock';
+  $target.append($clock);
+
+  const getTime = () => {
+    const date = new Date();
+    const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
+    const dayNum = date.getDay();
+    const addZero = (time) => (time < 10 ? '0' + time : time);
+    const hours = addZero(date.getHours() % 12 ? date.getHours() % 12 : '12');
+    const minutes = addZero(date.getMinutes());
+    const seconds = addZero(date.getSeconds());
+    const ampm = date.getHours() < 12 ? 'AM' : 'PM';
+    $clock.textContent = `${days[dayNum]} ${hours}:${minutes}:${seconds} ${ampm}`;
+  };
+
+  const setBgColor = () => {
+    if (new Date().getHours() < 11) {
+      $target.className += ' dawn';
+    } else if (new Date().getHours() > 19) {
+      $target.className += ' night';
+    } else {
+      $target.className += ' day';
+    }
+  };
+
+  this.init = () => {
+    getTime();
+    setBgColor();
+    setInterval(getTime, 1000);
+  };
+
+  this.init();
+}
diff --git a/project7-DigitalClock/src/main.js b/project7-DigitalClock/src/main.js
new file mode 100644
index 0000000..3ed760a
--- /dev/null
+++ b/project7-DigitalClock/src/main.js
@@ -0,0 +1,8 @@
+import App from './App.js';
+
+const $target = document.querySelector('main');
+const $page = document.createElement('div');
+$target.append($page);
+$page.className = 'project7 page';
+
+new App({ $target: $page });
diff --git a/project7-DigitalClock/src/style.css b/project7-DigitalClock/src/style.css
new file mode 100644
index 0000000..e12172b
--- /dev/null
+++ b/project7-DigitalClock/src/style.css
@@ -0,0 +1,49 @@
+:root .project7 {
+  --clock-bg-color: rgba(0, 0, 0, 0.5);
+  --bg-dawn-color: linear-gradient(-225deg, #2cd8d5 0%, #c5c1ff 56%, #ffbac3 100%);
+  --bg-day-color: linear-gradient(to right, #f83600 0%, #f9d423 100%);
+  --bg-night-color: linear-gradient(to top, #0250c5 0%, #d43f8d 100%);
+  --base-font-family: 'Noto Sans KR', sans-serif;
+  --base-font-size: 3.3rem;
+  --base-padding: 2rem;
+}
+
+.wrap {
+  width: 100vw;
+  height: 100vh;
+}
+
+.page {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-weight: bold;
+}
+
+.project7.dawn {
+  background-image: var(--bg-dawn-color);
+}
+
+.project7.day {
+  background-image: var(--bg-day-color);
+}
+
+.project7.night {
+  background-image: var(--bg-night-color);
+}
+
+.project7 .clock {
+  background-color: var(--clock-bg-color);
+  font-family: var(--base-font-family);
+  color: white;
+  font-size: var(--base-font-size);
+  padding: var(--base-padding);
+}
+
+@media (max-width: 500px) {
+  .project7 .clock {
+    font-size: 2rem;
+  }
+}
diff --git a/src/assets/favicon.ico b/src/assets/favicon.ico
new file mode 100644
index 0000000..286cd97
Binary files /dev/null and b/src/assets/favicon.ico differ
diff --git a/src/components/App.js b/src/components/App.js
new file mode 100644
index 0000000..e2915f2
--- /dev/null
+++ b/src/components/App.js
@@ -0,0 +1,75 @@
+import Header from '../layouts/Header.js';
+
+import HomePage from '../pages/HomePage.js';
+import NotFoundPage from '../pages/NotFoundPage.js';
+import Project1Page from '../../project1-colors/src/Project1Page.js';
+import Project2Page from '../../project2-HexColorsGradient/src/Project2Page.js';
+import Project3Page from '../../project3-RandomQuoteGenerator/src/Project3Page.js';
+import Project4Page from '../pages/Project4Page.js';
+import Project5Page from '../pages/Project5Page.js';
+import Project6Page from '../pages/Project6Page.js';
+import Project7Page from '../pages/Project7Page.js';
+
+export default function App({ $target }) {
+  new Header({ $target });
+
+  const $main = document.createElement('main');
+  $target.append($main);
+
+  const homePage = new HomePage({ $target: $main });
+  const notFoundPage = new NotFoundPage({ $target: $main });
+  const project1Page = new Project1Page({ $target: $main });
+  const project2Page = new Project2Page({ $target: $main });
+  const project3Page = new Project3Page({ $target: $main });
+  const project4Page = new Project4Page({ $target: $main });
+  const project5Page = new Project5Page({ $target: $main });
+  const project6Page = new Project6Page({ $target: $main });
+  const project7Page = new Project7Page({ $target: $main });
+
+  this.route = () => {
+    $main.innerHTML = ``;
+    const { pathname } = location;
+
+    if (pathname === `/`) {
+      homePage.render();
+    } else if (pathname.indexOf(`/project/`) === 0) {
+      const [, , projectId] = pathname.split('/');
+      switch (parseInt(projectId)) {
+        case 1:
+          project1Page.render();
+          break;
+        case 2:
+          project2Page.render();
+          break;
+        case 3:
+          project3Page.render();
+          break;
+        case 4:
+          project4Page.render();
+          break;
+        case 5:
+          project5Page.render();
+          break;
+        case 6:
+          project6Page.render();
+          break;
+        case 7:
+          project7Page.render();
+          break;
+      }
+    } else {
+      notFoundPage.render();
+    }
+  };
+
+  this.route();
+
+  window.addEventListener('click', (e) => {
+    if (e.target.className === 'link') {
+      e.preventDefault();
+      history.pushState(null, null, e.target.href);
+
+      this.route();
+    }
+  });
+}
diff --git a/src/data/projectList.js b/src/data/projectList.js
new file mode 100644
index 0000000..e90c7ca
--- /dev/null
+++ b/src/data/projectList.js
@@ -0,0 +1,9 @@
+export const projectList = [
+  { id: 1, title: 'Colors' },
+  { id: 2, title: 'Hex Colors Gradient' },
+  { id: 3, title: 'Random Quote Generator' },
+  { id: 4, title: 'The Message' },
+  { id: 5, title: 'Counter' },
+  { id: 6, title: 'Image Carousel' },
+  { id: 7, title: 'Digital Clock' },
+];
diff --git a/src/layouts/Header.js b/src/layouts/Header.js
new file mode 100644
index 0000000..b197923
--- /dev/null
+++ b/src/layouts/Header.js
@@ -0,0 +1,12 @@
+export default function Header({ $target }) {
+  const $header = document.createElement('header');
+  $target.append($header);
+
+  this.render = () => {
+    $header.innerHTML = /* html */ `
+      
+    `;
+  };
+
+  this.render();
+}
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..f1e0972
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,5 @@
+import App from './components/App.js';
+
+const $target = document.querySelector('#app');
+
+new App({ $target });
diff --git a/src/pages/HomePage.js b/src/pages/HomePage.js
new file mode 100644
index 0000000..a62d487
--- /dev/null
+++ b/src/pages/HomePage.js
@@ -0,0 +1,24 @@
+import { projectList } from '../data/projectList.js';
+
+export default function HomePage({ $target }) {
+  const $page = document.createElement('div');
+  $page.className = 'home';
+
+  this.state = projectList;
+
+  this.render = () => {
+    $page.innerHTML = /* html */ `
+      
+    `;
+
+    $target.append($page);
+  };
+}
diff --git a/src/pages/NotFoundPage.js b/src/pages/NotFoundPage.js
new file mode 100644
index 0000000..f04ad5d
--- /dev/null
+++ b/src/pages/NotFoundPage.js
@@ -0,0 +1,15 @@
+export default function NotFoundPage({ $target }) {
+  const $page = document.createElement('div');
+  $page.className = 'notFound page';
+
+  this.render = () => {
+    $page.innerHTML = /* html */ `
+
+    `;
+  });
+}
diff --git a/project6-ImageCarousel/src/constants.js b/project6-ImageCarousel/src/constants.js
new file mode 100644
index 0000000..88c50bd
--- /dev/null
+++ b/project6-ImageCarousel/src/constants.js
@@ -0,0 +1,5 @@
+export const IMG_API_END_POINT = `https://source.unsplash.com/1600x900/?`;
+
+// 같은 api를 한 번에 여러 번 호출하면 같은 이미지를 불러오는 문제가 있어
+// 각각 검색 키워드를 달리 지정해줌
+export const KEYWORDS_FOR_IMAGE = ['nature', 'water', 'beach', 'sea', 'park', 'mountain', 'star'];
diff --git a/project6-ImageCarousel/src/main.js b/project6-ImageCarousel/src/main.js
new file mode 100644
index 0000000..963f34b
--- /dev/null
+++ b/project6-ImageCarousel/src/main.js
@@ -0,0 +1,8 @@
+import App from './App.js';
+
+const $target = document.querySelector('main');
+const $page = document.createElement('div');
+$target.append($page);
+$page.className = 'project6 page';
+
+new App({ $target: $page });
diff --git a/project6-ImageCarousel/src/style.css b/project6-ImageCarousel/src/style.css
new file mode 100644
index 0000000..65552ff
--- /dev/null
+++ b/project6-ImageCarousel/src/style.css
@@ -0,0 +1,115 @@
+.wrap {
+  width: 100vw;
+  height: 100vh;
+}
+
+.page {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.project6 {
+  box-sizing: border-box;
+  padding: 1rem;
+  font-weight: bold;
+}
+
+.project6 .container {
+  width: 70%;
+  position: relative;
+  overflow: hidden;
+}
+
+.project6 .slideContainer {
+  position: relative;
+}
+
+.project6 .slideContainer .slide {
+  display: none;
+}
+
+.project6 .slideContainer .slideImg {
+  width: 100%;
+}
+
+.project6 .slideContainer .numberText {
+  color: #f2f2f2;
+  font-size: 16px;
+  padding: 8px 12px;
+  position: absolute;
+  top: 0;
+  background-color: rgba(0, 0, 0, 0.4);
+  border-radius: 1rem;
+  margin-top: 10px;
+  margin-left: 10px;
+}
+
+.project6 .fade {
+  animation-name: fade;
+  animation-duration: 1.5s;
+}
+
+.project6 .controlButton {
+  cursor: pointer;
+  position: absolute;
+  top: 50%;
+  margin-top: -22px;
+  padding: 16px;
+  color: white;
+  font-weight: bold;
+  font-size: 18px;
+  transition: 0.6s ease;
+  border-radius: 0 3px 3px 0;
+}
+
+.project6 .controlButton:hover {
+  background-color: rgba(0, 0, 0, 0.8);
+}
+
+.project6 .controlButton.next {
+  right: 0;
+  border-radius: 3px 0 0 3px;
+}
+
+.project6 .dotsContainer {
+  padding: 1rem;
+  text-align: center;
+}
+
+.project6 .dotsContainer .dot {
+  height: 10px;
+  width: 10px;
+  margin: 0 5px;
+  background-color: #bbb;
+  border-radius: 50%;
+  display: inline-block;
+  transition: background-color 0.6s ease;
+}
+
+.project6 .dotsContainer .dot.active {
+  background-color: #717171;
+}
+
+@keyframes fade {
+  from {
+    opacity: 0.4;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+@media (max-width: 800px) {
+  .project6 .container {
+    width: 100%;
+  }
+}
+
+@media (min-width: 1400px) {
+  .project6 .container {
+    width: 60%;
+  }
+}
diff --git a/project7-DigitalClock/index.html b/project7-DigitalClock/index.html
new file mode 100644
index 0000000..22f14e6
--- /dev/null
+++ b/project7-DigitalClock/index.html
@@ -0,0 +1,20 @@
+
+
+  
+    
+    
+    
+    디지털 시계
+    
+    
+    
+    
+  
+  
+    
+    
+  
+
diff --git a/project7-DigitalClock/src/App.js b/project7-DigitalClock/src/App.js
new file mode 100644
index 0000000..143a67f
--- /dev/null
+++ b/project7-DigitalClock/src/App.js
@@ -0,0 +1,5 @@
+import Clock from './Clock.js';
+
+export default function App({ $target }) {
+  new Clock({ $target });
+}
diff --git a/project7-DigitalClock/src/Clock.js b/project7-DigitalClock/src/Clock.js
new file mode 100644
index 0000000..d0434c9
--- /dev/null
+++ b/project7-DigitalClock/src/Clock.js
@@ -0,0 +1,35 @@
+export default function Clock({ $target }) {
+  const $clock = document.createElement('div');
+  $clock.className = 'clock';
+  $target.append($clock);
+
+  const getTime = () => {
+    const date = new Date();
+    const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
+    const dayNum = date.getDay();
+    const addZero = (time) => (time < 10 ? '0' + time : time);
+    const hours = addZero(date.getHours() % 12 ? date.getHours() % 12 : '12');
+    const minutes = addZero(date.getMinutes());
+    const seconds = addZero(date.getSeconds());
+    const ampm = date.getHours() < 12 ? 'AM' : 'PM';
+    $clock.textContent = `${days[dayNum]} ${hours}:${minutes}:${seconds} ${ampm}`;
+  };
+
+  const setBgColor = () => {
+    if (new Date().getHours() < 11) {
+      $target.className += ' dawn';
+    } else if (new Date().getHours() > 19) {
+      $target.className += ' night';
+    } else {
+      $target.className += ' day';
+    }
+  };
+
+  this.init = () => {
+    getTime();
+    setBgColor();
+    setInterval(getTime, 1000);
+  };
+
+  this.init();
+}
diff --git a/project7-DigitalClock/src/main.js b/project7-DigitalClock/src/main.js
new file mode 100644
index 0000000..3ed760a
--- /dev/null
+++ b/project7-DigitalClock/src/main.js
@@ -0,0 +1,8 @@
+import App from './App.js';
+
+const $target = document.querySelector('main');
+const $page = document.createElement('div');
+$target.append($page);
+$page.className = 'project7 page';
+
+new App({ $target: $page });
diff --git a/project7-DigitalClock/src/style.css b/project7-DigitalClock/src/style.css
new file mode 100644
index 0000000..e12172b
--- /dev/null
+++ b/project7-DigitalClock/src/style.css
@@ -0,0 +1,49 @@
+:root .project7 {
+  --clock-bg-color: rgba(0, 0, 0, 0.5);
+  --bg-dawn-color: linear-gradient(-225deg, #2cd8d5 0%, #c5c1ff 56%, #ffbac3 100%);
+  --bg-day-color: linear-gradient(to right, #f83600 0%, #f9d423 100%);
+  --bg-night-color: linear-gradient(to top, #0250c5 0%, #d43f8d 100%);
+  --base-font-family: 'Noto Sans KR', sans-serif;
+  --base-font-size: 3.3rem;
+  --base-padding: 2rem;
+}
+
+.wrap {
+  width: 100vw;
+  height: 100vh;
+}
+
+.page {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-weight: bold;
+}
+
+.project7.dawn {
+  background-image: var(--bg-dawn-color);
+}
+
+.project7.day {
+  background-image: var(--bg-day-color);
+}
+
+.project7.night {
+  background-image: var(--bg-night-color);
+}
+
+.project7 .clock {
+  background-color: var(--clock-bg-color);
+  font-family: var(--base-font-family);
+  color: white;
+  font-size: var(--base-font-size);
+  padding: var(--base-padding);
+}
+
+@media (max-width: 500px) {
+  .project7 .clock {
+    font-size: 2rem;
+  }
+}
diff --git a/src/assets/favicon.ico b/src/assets/favicon.ico
new file mode 100644
index 0000000..286cd97
Binary files /dev/null and b/src/assets/favicon.ico differ
diff --git a/src/components/App.js b/src/components/App.js
new file mode 100644
index 0000000..e2915f2
--- /dev/null
+++ b/src/components/App.js
@@ -0,0 +1,75 @@
+import Header from '../layouts/Header.js';
+
+import HomePage from '../pages/HomePage.js';
+import NotFoundPage from '../pages/NotFoundPage.js';
+import Project1Page from '../../project1-colors/src/Project1Page.js';
+import Project2Page from '../../project2-HexColorsGradient/src/Project2Page.js';
+import Project3Page from '../../project3-RandomQuoteGenerator/src/Project3Page.js';
+import Project4Page from '../pages/Project4Page.js';
+import Project5Page from '../pages/Project5Page.js';
+import Project6Page from '../pages/Project6Page.js';
+import Project7Page from '../pages/Project7Page.js';
+
+export default function App({ $target }) {
+  new Header({ $target });
+
+  const $main = document.createElement('main');
+  $target.append($main);
+
+  const homePage = new HomePage({ $target: $main });
+  const notFoundPage = new NotFoundPage({ $target: $main });
+  const project1Page = new Project1Page({ $target: $main });
+  const project2Page = new Project2Page({ $target: $main });
+  const project3Page = new Project3Page({ $target: $main });
+  const project4Page = new Project4Page({ $target: $main });
+  const project5Page = new Project5Page({ $target: $main });
+  const project6Page = new Project6Page({ $target: $main });
+  const project7Page = new Project7Page({ $target: $main });
+
+  this.route = () => {
+    $main.innerHTML = ``;
+    const { pathname } = location;
+
+    if (pathname === `/`) {
+      homePage.render();
+    } else if (pathname.indexOf(`/project/`) === 0) {
+      const [, , projectId] = pathname.split('/');
+      switch (parseInt(projectId)) {
+        case 1:
+          project1Page.render();
+          break;
+        case 2:
+          project2Page.render();
+          break;
+        case 3:
+          project3Page.render();
+          break;
+        case 4:
+          project4Page.render();
+          break;
+        case 5:
+          project5Page.render();
+          break;
+        case 6:
+          project6Page.render();
+          break;
+        case 7:
+          project7Page.render();
+          break;
+      }
+    } else {
+      notFoundPage.render();
+    }
+  };
+
+  this.route();
+
+  window.addEventListener('click', (e) => {
+    if (e.target.className === 'link') {
+      e.preventDefault();
+      history.pushState(null, null, e.target.href);
+
+      this.route();
+    }
+  });
+}
diff --git a/src/data/projectList.js b/src/data/projectList.js
new file mode 100644
index 0000000..e90c7ca
--- /dev/null
+++ b/src/data/projectList.js
@@ -0,0 +1,9 @@
+export const projectList = [
+  { id: 1, title: 'Colors' },
+  { id: 2, title: 'Hex Colors Gradient' },
+  { id: 3, title: 'Random Quote Generator' },
+  { id: 4, title: 'The Message' },
+  { id: 5, title: 'Counter' },
+  { id: 6, title: 'Image Carousel' },
+  { id: 7, title: 'Digital Clock' },
+];
diff --git a/src/layouts/Header.js b/src/layouts/Header.js
new file mode 100644
index 0000000..b197923
--- /dev/null
+++ b/src/layouts/Header.js
@@ -0,0 +1,12 @@
+export default function Header({ $target }) {
+  const $header = document.createElement('header');
+  $target.append($header);
+
+  this.render = () => {
+    $header.innerHTML = /* html */ `
+      
+    `;
+  };
+
+  this.render();
+}
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..f1e0972
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,5 @@
+import App from './components/App.js';
+
+const $target = document.querySelector('#app');
+
+new App({ $target });
diff --git a/src/pages/HomePage.js b/src/pages/HomePage.js
new file mode 100644
index 0000000..a62d487
--- /dev/null
+++ b/src/pages/HomePage.js
@@ -0,0 +1,24 @@
+import { projectList } from '../data/projectList.js';
+
+export default function HomePage({ $target }) {
+  const $page = document.createElement('div');
+  $page.className = 'home';
+
+  this.state = projectList;
+
+  this.render = () => {
+    $page.innerHTML = /* html */ `
+      
+    `;
+
+    $target.append($page);
+  };
+}
diff --git a/src/pages/NotFoundPage.js b/src/pages/NotFoundPage.js
new file mode 100644
index 0000000..f04ad5d
--- /dev/null
+++ b/src/pages/NotFoundPage.js
@@ -0,0 +1,15 @@
+export default function NotFoundPage({ $target }) {
+  const $page = document.createElement('div');
+  $page.className = 'notFound page';
+
+  this.render = () => {
+    $page.innerHTML = /* html */ `
+      
+        
⛔ Not Found ⛔
+        
잘못된 접근입니다
+