diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..25c8fdba
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+package-lock.json
\ No newline at end of file
diff --git a/README.md b/README.md
index 95e87fdf..f7ff37a9 100644
--- a/README.md
+++ b/README.md
@@ -1,60 +1,151 @@
-`#html` `#css` `#js` `#dom` `#JSON` `#HTTP` `#API` `#Bootstrap` `#master-in-software-development`
+`#html` `#css` `#js` `#dom` `#JSON-server` `#JSON`
+
-# Blog with API
+
-
-
+
+
+# Blog CHAIN
+
+A fast, simple, elegant & powerful blog powered by HTML, CSS and JS Vanilla.
+
+According to a recent survey, blogs have ranked as the third most trustworthy source of information, following only friends and family. That's right β bloggers are trusted more than celebrities, journalists, brands, and politicians.
+
+With this blog we will get people to fall in love with it. Well, just as your website homepage is like the front door to your business, our blog's design is the front door of ourselves.
+
+With an elegant design, and an organized structure, we have created this fashionable blog to read about any topics you want.
+
+
+
+# Preview
+
+
+
+
+
+Gold sponsors π₯β€οΈ
+
+
+
+
+
+
+
+
-> In this pill you will put into practice the knowledge learned about making HTTP requests to create a blog consuming the information from a third-party API. You will also learn how to use Bootstrap Framework for the layout.
+# Table of contents
+
+
+
+- [Getting started](#getting-started)
+- [What can you do in blog chain?](#What-can-you-do-in-blog-chain?)
+- [Roadmap](#Roadmap)
+- [Responsive](#responsive)
+- [Technologies](#Technologies)
+- [Team](#Team)
+- [Links](#links)
+ * [Video](#video)
+ * [Third-party tools](#third-party-tools)
+ * [Linkedin](#Linkedin)
+
+
+
+
+# Getting started
+
+First of all, you must launch the local API server created with JSON server to be able to fetch the data to the blog web page.
+
+```
+npm run server
+```
+
+Also when doing requests, it's good to know that:
+
+- If you make POST, PUT, PATCH or DELETE requests, changes will be automatically and safely saved to `db.json` using [lowdb](https://github.com/typicode/lowdb).
+- Your request body JSON should be object enclosed, just like the GET output. (for example `{"name": "Foobar"}`)
+- Id values are not mutable. Any `id` value in the body of your PUT or PATCH request will be ignored. Only a value set in a POST request will be respected, but only if not already taken.
+- A POST, PUT or PATCH request should include a `Content-Type: application/json` header to use the JSON in the request body. Otherwise it will return a 2XX status code, but without changes being made to the data.
+
+
+
+# What can you do in blog chain?
+
+
+
+- Read articles about whatever topic you want.
+- Modify articles clicking in the edit button of the desired article card.
+- Remove articles clicking in the delete button of the desired article card.
+
+
+
+# Roadmap
+
+
+
+### Project Organization
+
+
+
+We started designing the structure of the blog with its differents html tags and giving it classes and ids.
+
+
+
+
+
+
+
+# Responsive
+
+
+
+This blog web page is adapted for all type of devices.
-## Index
+- Small devices (mobiles 360px and up)
+- Medium devices (landscape tablets, 768px and up) */
+- Large devices (laptops/desktops, 992px and up) */
+- Extra large devices (large laptops and desktops, 1200px and up) */
-- [Requirements](#requirements)
-- [Repository](#repository)
-- [Technologies used](#technologies-used)
-- [Project delivery](#project-delivery)
-- [Resources](#resources)
-## Requirements
+
-- You must use semantic HTML5 elements for all the contents of the application
-- You must use JSON server library to create your own local repository
-- You must use fecth to do the requests
-- You have to use Bootstrap Framework for the Layout and the styles
+
+# Technologies
-## Repository
+- HTML
+- CSS
+- JAVASCRIPT VANILLA
+- JSON SERVER
+- BOOTSTRAP 5
-First of all you must fork this project into your GitHub account.
+
-To create a fork on GitHub is as easy as clicking the βforkβ button on the repository page.
+# Team
-
+- πΊ Ivan Escribano (https://github.com/ivan-escribano)
+- π Alicia Cembranos (https://github.com/alicembranos)
-## Technologies used
+
-\* HTML
+# Links
-\* CSS
+## Video
-\* JS
+
-\* Bootstrap
+
-\* HTTP Requests
+## Third-party tools
-\* JSON
+* [JSON Server](https://github.com/typicode/json-server/blob/master/README.md)
-\* API
+
-## Project delivery
+## Linkedin
-To deliver this project you must follow the steps indicated in the document:
+[](https://www.linkedin.com/in/ivan-escribano-382671217/) Ivan Escribano
-- [Submitting a solution](https://www.notion.so/Submitting-a-solution-524dab1a71dd4b96903f26385e24cdb6)
+[](https://www.linkedin.com/in/aliciacembranos/) Alicia Cembranos
-## Resources
-- [JSON server](https://github.com/typicode/json-server)
-- [Official Bootstrap](https://getbootstrap.com/)
\ No newline at end of file
diff --git a/data/db.json b/data/db.json
index 7fcbe2a9..5590d767 100644
--- a/data/db.json
+++ b/data/db.json
@@ -14,15 +14,15 @@
},
{
"userId": 1,
- "id": 3,
"title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
- "body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut"
+ "body": "et iusto sed quo iurevoluptatem occaecati omnis eligendi aut advoluptatem doloribus vel accusantium quis pariaturmolestiae porro eius odio et labore et velit aut",
+ "id": 3
},
{
"userId": 1,
- "id": 4,
"title": "eum et est occaecati",
- "body": "ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit"
+ "body": "ullam et saepe reiciendis voluptatem adipiscisit amet autem assumenda provident rerum culpaquis hic commodi nesciunt rem tenetur doloremque ipsam iurequis sunt voluptatem rerum illo velit",
+ "id": 4
},
{
"userId": 1,
@@ -36,12 +36,6 @@
"title": "dolorem eum magni eos aperiam quia",
"body": "ut aspernatur corporis harum nihil quis provident sequi\nmollitia nobis aliquid molestiae\nperspiciatis et ea nemo ab reprehenderit accusantium quas\nvoluptate dolores velit et doloremque molestiae"
},
- {
- "userId": 1,
- "id": 7,
- "title": "magnam facilis autem",
- "body": "dolore placeat quibusdam ea quo vitae\nmagni quis enim qui quis quo nemo aut saepe\nquidem repellat excepturi ut quia\nsunt ut sequi eos ea sed quas"
- },
{
"userId": 1,
"id": 8,
@@ -1044,41 +1038,6 @@
"email": "Lurline@marvin.biz",
"body": "consequuntur quia voluptate assumenda et\nautem voluptatem reiciendis ipsum animi est provident\nearum aperiam sapiente ad vitae iste\naccusantium aperiam eius qui dolore voluptatem et"
},
- {
- "postId": 7,
- "id": 31,
- "name": "ex velit ut cum eius odio ad placeat",
- "email": "Buford@shaylee.biz",
- "body": "quia incidunt ut\naliquid est ut rerum deleniti iure est\nipsum quia ea sint et\nvoluptatem quaerat eaque repudiandae eveniet aut"
- },
- {
- "postId": 7,
- "id": 32,
- "name": "dolorem architecto ut pariatur quae qui suscipit",
- "email": "Maria@laurel.name",
- "body": "nihil ea itaque libero illo\nofficiis quo quo dicta inventore consequatur voluptas voluptatem\ncorporis sed necessitatibus velit tempore\nrerum velit et temporibus"
- },
- {
- "postId": 7,
- "id": 33,
- "name": "voluptatum totam vel voluptate omnis",
- "email": "Jaeden.Towne@arlene.tv",
- "body": "fugit harum quae vero\nlibero unde tempore\nsoluta eaque culpa sequi quibusdam nulla id\net et necessitatibus"
- },
- {
- "postId": 7,
- "id": 34,
- "name": "omnis nemo sunt ab autem",
- "email": "Ethelyn.Schneider@emelia.co.uk",
- "body": "omnis temporibus quasi ab omnis\nfacilis et omnis illum quae quasi aut\nminus iure ex rem ut reprehenderit\nin non fugit"
- },
- {
- "postId": 7,
- "id": 35,
- "name": "repellendus sapiente omnis praesentium aliquam ipsum id molestiae omnis",
- "email": "Georgianna@florence.io",
- "body": "dolor mollitia quidem facere et\nvel est ut\nut repudiandae est quidem dolorem sed atque\nrem quia aut adipisci sunt"
- },
{
"postId": 8,
"id": 36,
@@ -4335,4 +4294,4 @@
"body": "perspiciatis quis doloremque\nveniam nisi eos velit sed\nid totam inventore voluptatem laborum et eveniet\naut aut aut maxime quia temporibus ut omnis"
}
]
-}
+}
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..5083d928
--- /dev/null
+++ b/index.html
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+ BLOG
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Previous
+
+
+
+ Next
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..dde608c0
--- /dev/null
+++ b/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "blog-with-api",
+ "version": "1.0.0",
+ "description": "`#html` `#css` `#js` `#dom` `#JSON` `#HTTP` `#API` `#Bootstrap` `#master-in-software-development`",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "server": "json-server --watch ./data/db.json"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/ivan-escribano/blog-with-api.git"
+ },
+ "author": "",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/ivan-escribano/blog-with-api/issues"
+ },
+ "homepage": "https://github.com/ivan-escribano/blog-with-api#readme",
+ "dependencies": {
+ "bootstrap": "^5.1.3",
+ "json-server": "^0.17.0"
+ }
+}
diff --git a/src/assets/icons/bootstrap.png b/src/assets/icons/bootstrap.png
new file mode 100644
index 00000000..e7d44a34
Binary files /dev/null and b/src/assets/icons/bootstrap.png differ
diff --git a/src/assets/icons/css-3.png b/src/assets/icons/css-3.png
new file mode 100644
index 00000000..9b1b83d2
Binary files /dev/null and b/src/assets/icons/css-3.png differ
diff --git a/src/assets/icons/html5.png b/src/assets/icons/html5.png
new file mode 100644
index 00000000..4c064b2e
Binary files /dev/null and b/src/assets/icons/html5.png differ
diff --git a/src/assets/icons/js.png b/src/assets/icons/js.png
new file mode 100644
index 00000000..ff193e1a
Binary files /dev/null and b/src/assets/icons/js.png differ
diff --git a/src/assets/images/assembler-logo.png b/src/assets/images/assembler-logo.png
new file mode 100644
index 00000000..12ef77dd
Binary files /dev/null and b/src/assets/images/assembler-logo.png differ
diff --git a/src/assets/images/blog.gif b/src/assets/images/blog.gif
new file mode 100644
index 00000000..9cc2f945
Binary files /dev/null and b/src/assets/images/blog.gif differ
diff --git a/src/assets/images/blog_structure-BLOG-MAIN.drawio.png b/src/assets/images/blog_structure-BLOG-MAIN.drawio.png
new file mode 100644
index 00000000..be6f3481
Binary files /dev/null and b/src/assets/images/blog_structure-BLOG-MAIN.drawio.png differ
diff --git a/src/assets/images/blog_structure-MODAL-EDIT.drawio.png b/src/assets/images/blog_structure-MODAL-EDIT.drawio.png
new file mode 100644
index 00000000..1ed05943
Binary files /dev/null and b/src/assets/images/blog_structure-MODAL-EDIT.drawio.png differ
diff --git a/src/assets/images/blog_structure-MODAL-INFO.drawio.png b/src/assets/images/blog_structure-MODAL-INFO.drawio.png
new file mode 100644
index 00000000..99451d40
Binary files /dev/null and b/src/assets/images/blog_structure-MODAL-INFO.drawio.png differ
diff --git a/src/assets/images/edit1.png b/src/assets/images/edit1.png
new file mode 100644
index 00000000..612c098b
Binary files /dev/null and b/src/assets/images/edit1.png differ
diff --git a/src/assets/images/logo (2).png b/src/assets/images/logo (2).png
new file mode 100644
index 00000000..261d2d18
Binary files /dev/null and b/src/assets/images/logo (2).png differ
diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png
new file mode 100644
index 00000000..8c9927bd
Binary files /dev/null and b/src/assets/images/logo.png differ
diff --git a/src/assets/images/logo_blog.png b/src/assets/images/logo_blog.png
new file mode 100644
index 00000000..e390c08c
Binary files /dev/null and b/src/assets/images/logo_blog.png differ
diff --git a/src/assets/images/modal-edit_background.jpg b/src/assets/images/modal-edit_background.jpg
new file mode 100644
index 00000000..e3e2330b
Binary files /dev/null and b/src/assets/images/modal-edit_background.jpg differ
diff --git a/src/assets/images/modal-info-img.jpg b/src/assets/images/modal-info-img.jpg
new file mode 100644
index 00000000..2130a326
Binary files /dev/null and b/src/assets/images/modal-info-img.jpg differ
diff --git a/src/assets/images/preview.png b/src/assets/images/preview.png
new file mode 100644
index 00000000..5b91ccbb
Binary files /dev/null and b/src/assets/images/preview.png differ
diff --git a/src/assets/images/responsive-design.jpg b/src/assets/images/responsive-design.jpg
new file mode 100644
index 00000000..899d6a8e
Binary files /dev/null and b/src/assets/images/responsive-design.jpg differ
diff --git a/src/assets/video/videoplayback.mp4 b/src/assets/video/videoplayback.mp4
new file mode 100644
index 00000000..65e8f05c
Binary files /dev/null and b/src/assets/video/videoplayback.mp4 differ
diff --git a/src/css/blog.css b/src/css/blog.css
new file mode 100644
index 00000000..8637fd71
--- /dev/null
+++ b/src/css/blog.css
@@ -0,0 +1,267 @@
+/*!BLOG GENERAL CONTAINER*/
+.blog {
+ border-radius: 0.5rem;
+ padding: 0.5rem;
+ width: 80%;
+ height: 90%;
+ background-color: rgba(240, 248, 255, 0.76);
+ display: flex;
+ flex-direction: column;
+}
+
+/*!BLOG HEADER*/
+.blog__header {
+ display: flex;
+ justify-content: space-between;
+ flex: 1;
+ align-items: flex-end;
+ padding: 0.2rem 1rem;
+}
+
+.header__logo {
+ flex: 1;
+}
+
+.header__logo img {
+ height: 4rem;
+}
+
+.header__menu {
+ flex: 2;
+}
+
+.header__menu>ul {
+ display: flex;
+ justify-content: center;
+ list-style: none;
+ font-size: 1.4rem;
+}
+
+.header__menu>ul>li {
+ padding: 0 3rem;
+}
+
+.header__menu a {
+ text-decoration: none;
+ color: black;
+ font-weight: 600;
+}
+
+.header__menu a.selected {
+ background-color: #85b6ff;
+ color: white;
+ padding: 0.2rem 0.5rem;
+ border-radius: 0.5rem;
+}
+
+/*!BLOG CONTENT*/
+.blog__content {
+ display: flex;
+ flex: 11;
+ height: 100%;
+ overflow: hidden;
+}
+
+/*!BLOG-SLIDER*/
+.blog__slider {
+ display: flex;
+ align-items: center;
+ flex: 1;
+ width: 100%;
+ height: 100%;
+}
+
+.carousel-item:hover {
+ cursor: pointer;
+}
+
+.carousel-item {
+ height: 67vh;
+ padding: 1rem 0 0 1rem;
+ border-radius: 12px;
+}
+
+.carousel-control-next,
+.carousel-control-prev {
+ margin-top: 1rem;
+}
+
+.carousel-item img {
+ height: 100%;
+ border-radius: 12px;
+ object-fit: cover;
+ object-position: center;
+ /* opacity: 0.85; */
+}
+
+.carousel-item>h5 {
+ position: absolute;
+ bottom: 10%;
+ width: 100%;
+ padding: 0rem 2rem 0 3.5rem;
+ font-size: 3rem;
+ font-weight: 700;
+ color: var(--quaternary-color);
+}
+
+/*!BLOG-POSTS CONTAINER*/
+.blog__posts {
+ flex: 1;
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 1rem;
+ margin: 1rem;
+ overflow-y: overlay;
+}
+
+.card {
+ position: relative;
+ width: 100%;
+ height: 450px;
+ margin-right: 25px;
+ border: none;
+ border-radius: 12px;
+ box-shadow: 0px 13px 10px -7px rgba(0, 0, 0, 0.1);
+ cursor: pointer;
+}
+
+.card__img {
+ visibility: hidden;
+ width: 100%;
+ height: 100px;
+ border-top-left-radius: 12px;
+ border-top-right-radius: 12px;
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+
+.card__img--hover {
+ position: absolute;
+ top: 0;
+ width: 100%;
+ height: 200px;
+ border-top-left-radius: 12px;
+ border-top-right-radius: 12px;
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ transition: 0.2s all ease-out;
+}
+
+.card-info {
+ position: absolute;
+ top: 200px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ width: 100%;
+ height: 250px;
+ padding: 16px 24px 24px 24px;
+ border-bottom-right-radius: 12px;
+ border-bottom-left-radius: 12px;
+ background-color: #fff;
+}
+
+.card-buttons {
+ display: flex;
+ justify-content: space-between;
+}
+
+.card-title {
+ font-size: 1.5rem;
+ padding-bottom: 0.75rem;
+}
+
+.card:hover .card__img--hover {
+ height: 100%;
+ opacity: 0.3;
+ border-bottom-left-radius: 12px;
+ border-bottom-right-radius: 12px;
+ cursor: pointer;
+}
+
+.card:hover .card-info {
+ background-color: transparent;
+}
+
+.card__img--hover:hover .card-buttons {
+ display: none;
+}
+
+.buttons_posts {
+ width: 60px;
+ padding: 0.2rem 0.5rem;
+ border: none;
+ border-color: var(--quaternary-color);
+ background-color: var(--quaternary-color);
+ font-size: 12px;
+ color: var(--primary-color);
+ text-align: center;
+ text-transform: uppercase;
+}
+
+.buttons_posts:hover {
+ background-color: var(--primary-blue);
+ background-size: cover;
+ color: var(--quaternary-color);
+}
+
+/*!SKELETON POST CARD*/
+.card.is-loading>.card-img,
+.card.is-loading>.card-info>h2,
+.card.is-loading>.card-info>p,
+.card.is-loading>.card-info>.card-buttons>button {
+ background: #eee;
+ background: linear-gradient(110deg, #ececec 8%, #f5f5f5 18%, #ececec 33%);
+ border-top-right-radius: 12px;
+ border-top-left-radius: 12px;
+ background-size: 200% 100%;
+ animation: 1.5s shine linear infinite;
+}
+
+.card.is-loading>.card-info>h2,
+.card.is-loading>.card-info>p,
+.card.is-loading>.card-info>.card-buttons>button {
+ border-radius: 0px;
+}
+
+.card.is-loading>.card-img {
+ height: 200px;
+}
+
+.card.is-loading>.card-info>h2 {
+ margin-top: 1rem;
+ height: 80px;
+}
+
+.card.is-loading>.card-info>p {
+ height: 20px;
+}
+
+.card.is-loading>.card-info>.card-buttons>button {
+ height: 20px;
+}
+
+@keyframes shine {
+ to {
+ background-position-x: -200%;
+ }
+}
+
+/*!SCROLLBAR POSTS CONTAINER*/
+/* Scroll 1 */
+.scrollbar::-webkit-scrollbar {
+ width: 5px;
+ height: 5px;
+}
+
+.scrollbar::-webkit-scrollbar-track {
+ background-color: rgba(163, 163, 163, 0.877);
+ border-radius: 10px;
+}
+
+.scrollbar::-webkit-scrollbar-thumb {
+ background-color: var(--primary-blue);
+ border-radius: 10px;
+}
\ No newline at end of file
diff --git a/src/css/main.css b/src/css/main.css
new file mode 100644
index 00000000..814da07d
--- /dev/null
+++ b/src/css/main.css
@@ -0,0 +1,115 @@
+@import url("https://fonts.googleapis.com/css2?family=Amiri:ital,wght@0,400;0,700;1,400;1,700&family=Libre+Baskerville:wght@700&display=swap");
+@import url("https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap");
+@import url(./modal.css);
+@import url(./blog.css);
+
+
+/*!RESET*/
+* {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+ font-family: "Amiri", serif;
+}
+
+/*!GENERAL STYLES*/
+:root {
+ --primary-blue: #85b6ff;
+ --light-color: aliceblue;
+ --primary-color: #000000;
+ --secondary-color: #064663;
+ --tertiary-color: #ffb72b;
+ --quaternary-color: #f7f7f7;
+ --font-family-lato: "Lato", sans-serif;
+}
+
+body {
+ background-color: #85b6ff8e;
+ height: 100vh;
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.video {
+ position: fixed;
+ right: 0;
+ bottom: 0;
+ min-width: 100%;
+ min-height: 100%;
+ z-index: -1;
+ filter:opacity(60%);
+}
+
+/*!SMALL DEVICES MEDIA QUERIES */
+@media only screen and (max-width: 963px) {
+ .header__menu {
+ display: none !important;
+ }
+ body {
+ background-color: aliceblue;
+ }
+ .video {
+ display: none;
+ }
+ .blog__content {
+ flex-direction: column;
+ background-color: none;
+ overflow: unset;
+ }
+ .blog__posts {
+ grid-template-columns: repeat(1, 1fr);
+ overflow-y: unset;
+ }
+ .blog__slider {
+ justify-content: center;
+ padding: 0 1rem;
+ }
+ .blog {
+ width: 100%;
+ height: 100%;
+ background-color: none;
+ }
+ .header__menu {
+ display: none;
+ }
+ .modal {
+ position: fixed;
+ }
+ .modal__img {
+ display: none;
+ }
+ .modal > * {
+ width: 90%;
+ height: 90%;
+ }
+
+ .carousel-item > h5 {
+ font-size: 2rem;
+ }
+ .modal__title {
+ font-size: 1.2rem;
+ }
+ .modal__body {
+ font-size: 1rem;
+ }
+}
+/*!MEDIUM DEVICES*/
+@media only screen and (max-width: 1300px) {
+ .blog__posts {
+ grid-template-columns: repeat(1, 1fr);
+ }
+}
+/*!EXTRA LARGE DEVICES*/
+@media only screen and (min-width: 1800px) {
+ .blog {
+ width: 70%;
+ }
+}
+/* Medium devices (landscape tablets, 768px and up) */
+/* @media only screen and (min-width: 768px) {...} */
+/* Large devices (laptops/desktops, 992px and up) */
+/* @media only screen and (min-width: 992px) {...} */
+/* Extra large devices (large laptops and desktops, 1200px and up) */
+/* @media only screen and (min-width: 1200px) {...} */
diff --git a/src/css/modal.css b/src/css/modal.css
new file mode 100644
index 00000000..486567c2
--- /dev/null
+++ b/src/css/modal.css
@@ -0,0 +1,209 @@
+/*!MODAL GENERAL*/
+.modal {
+ position: absolute;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ height: 100vh;
+ background-color: rgba(128, 128, 128, 0.966);
+}
+
+.container--hide {
+ display: none !important;
+}
+
+.modal__close {
+ position: absolute;
+ color: black;
+ top: 1rem;
+ right: 1rem;
+ cursor: pointer;
+ background-color: transparent;
+ border: none;
+ padding: 1rem;
+ font-size: 1.5rem;
+}
+
+/*!MODAL INFO*/
+.modal-info {
+ display: flex;
+ width: 70%;
+ height: 70%;
+ -webkit-box-shadow: 0px 0px 23px -8px rgba(255, 255, 255, 1);
+ -moz-box-shadow: 0px 0px 23px -8px rgba(255, 255, 255, 1);
+ box-shadow: 0px 0px 23px -8px rgba(255, 255, 255, 1);
+}
+
+.modal__content {
+ flex: 2;
+ position: relative;
+ background-color: aliceblue;
+ color: #000000;
+ display: flex;
+ flex-direction: column;
+ padding: 0 2rem;
+ justify-content: space-evenly;
+ border-top-right-radius: 0.5rem;
+ border-bottom-right-radius: 0.5rem;
+}
+
+.modal__title {
+ font-size: 2.1rem;
+ text-transform: uppercase;
+}
+
+.modal__body {
+ font-size: 1.2rem;
+}
+
+.user__container,
+.email__container {
+ display: flex;
+ padding: 0.3rem 0;
+}
+
+.user__container>i,
+.email__container>i {
+ color: var(--primary-blue);
+ padding: 0 1rem 0 0;
+}
+
+.modal__button-container {
+ display: flex;
+ justify-content: center;
+}
+
+/*!MODAL IMG*/
+.modal__img {
+ flex: 1.2;
+ background-image: url("../assets/images/modal-info-img.jpg");
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ border-top-left-radius: 0.5rem;
+ border-bottom-left-radius: 0.5rem;
+}
+
+/**Comments*/
+.comments-container {
+ height: 40%;
+ overflow-y: auto;
+}
+
+.comment__item {
+ padding: 1rem 0;
+}
+
+.comment__item>* {
+ padding: 0.2rem 0;
+}
+
+.comment__name {
+ text-transform: uppercase;
+ font-size: 1.2rem;
+}
+
+.comment__email {
+ font-size: 0.8rem;
+ opacity: 0.7;
+}
+
+.comment__separator {
+ background-color: var(--primary-blue);
+ margin-top: 1rem;
+ padding: 0.02rem 0 !important;
+ border: none;
+}
+
+/*!BUTTONS*/
+.primary__btn {
+ cursor: pointer;
+ background-color: var(--primary-blue);
+ color: white;
+ font-size: 0.9rem;
+ padding: 0.8rem 1rem;
+ border-radius: 0.2rem;
+ border: none;
+}
+
+/*!MODAL EDIT*/
+.modal-edit__content {
+ background-color: aliceblue;
+ color: black;
+ display: flex;
+ flex-direction: column;
+ width: 30vw;
+ height: 70%;
+ border-radius: 0.5rem;
+}
+
+.modal-edit__img {
+ background-image: url("../assets/images/modal-edit_background.jpg");
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ flex: 0.8;
+ display: flex;
+ border-top-left-radius: 0.5rem;
+ border-top-right-radius: 0.5rem;
+}
+
+.modal-edit__illustration {
+ background-image: url("../assets/images/edit1.png");
+ background-size: contain;
+ background-position: center;
+ background-repeat: no-repeat;
+ flex: 1;
+}
+
+.modal-edit__form {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ padding: 1rem;
+}
+
+.modal-edit__form>* {
+ padding: 0.5rem 0;
+}
+
+.modal-edit__label {
+ display: flex;
+ flex-direction: column;
+}
+
+textarea {
+ resize: unset;
+ height: 8rem;
+}
+
+.modal-edit__container-btn {
+ display: flex;
+ justify-content: space-between;
+ padding: 1rem 1rem;
+}
+
+.modal-edit__input,
+.modal-edit__textarea {
+ font-size: 1rem;
+}
+
+.modal-edit__btn {
+ cursor: pointer;
+ background-color: var(--primary-blue);
+ color: white;
+ font-size: 0.9rem;
+ padding: 0.5rem 2rem;
+ border-radius: 0.2rem;
+ border: none;
+ font-size: 1.1rem;
+}
+
+.modal-edit__btn-close {
+ background-color: #000000 !important;
+}
+
+.modal-edit__label {
+ font-size: 1.2rem;
+}
\ No newline at end of file
diff --git a/src/js/main.js b/src/js/main.js
new file mode 100644
index 00000000..ca569e92
--- /dev/null
+++ b/src/js/main.js
@@ -0,0 +1,611 @@
+//!GENERAL VARIABLES
+const urlPosts = "http://localhost:3000/posts";
+const postContainer = document.getElementById("blogPosts");
+let indexPost = 0; //Last post load
+
+window.onload = () => {
+ createSkeleton(4);
+ getPosts();
+};
+
+//!ADD INFINITY SCROLL
+postContainer.addEventListener("scroll", () => {
+ if (
+ postContainer.scrollTop + postContainer.clientHeight >=
+ postContainer.scrollHeight
+ ) {
+ createSkeleton(4);
+ getPosts();
+ }
+});
+
+//!GET DATA
+const getPosts = () => {
+ fetch(urlPosts)
+ .then((response) => response.json())
+ .then((data) => {
+ for (let i = indexPost; i < indexPost + 4; i++) {
+ addPosts(data[i]);
+ }
+ indexPost = indexPost + 4;
+ });
+};
+
+//!CREATE SKELETON TEMPLATE
+function createSkeleton(num) {
+ const skeleton = [...Array(num).keys()].map((card) => {
+ //Article
+ const article = document.createElement("article");
+ article.classList.add("card", "is-loading");
+
+ //Img element
+ const img1 = document.createElement("div");
+ img1.classList.add("card-img");
+
+ //Body container
+ const sectionBody = document.createElement("section");
+ sectionBody.classList.add("card-info");
+
+ //Buttons container
+ const sectionButtons = document.createElement("section");
+ sectionButtons.classList.add("card-buttons");
+
+ //Delete and Edit buttons
+ const deleteButton = document.createElement("button");
+ const editButton = document.createElement("button");
+ deleteButton.classList.add("btn", "buttons_posts");
+ editButton.classList.add("btn", "buttons_posts");
+
+ //add buttons to their section
+ sectionButtons.append(editButton, deleteButton);
+
+ //create title
+ const h2 = document.createElement("h2");
+ h2.classList.add("card-title");
+ //create username
+ const userName = document.createElement("p");
+ userName.classList.add("card-text");
+
+ //add title,username and buttons to body
+ sectionBody.append(h2, userName, sectionButtons);
+ //add all to article
+ article.append(img1, sectionBody);
+
+ //ADD to general container
+ postContainer.appendChild(article);
+ });
+
+ return skeleton;
+}
+
+//!REMOVE SKELETON CARDS
+function removeSkeleton() {
+ const skeletonCards = document.querySelectorAll(".is-loading");
+ if (skeletonCards) {
+ Array.from(skeletonCards).map((card) => card.remove());
+ }
+}
+
+//!ADD POSTS HTML
+async function addPosts(post) {
+ let titlePost = capitalizeFirstLetter(post.title);
+ let usernamePost = await getUsername(post.userId);
+ let idPost = post.id;
+ let imagePost = getImagesSplash(99);
+ removeSkeleton();
+ createHTMLpostSection(imagePost, titlePost, usernamePost, idPost);
+ createHTMLsliderSection(imagePost, titlePost, idPost);
+
+ //Remove active class
+ const divsCarousel = document.querySelectorAll(".carousel-item");
+ divsCarousel.forEach((div) => {
+ div.classList.remove("active");
+ });
+ divsCarousel[0].classList.add("active");
+}
+
+async function createHTMLsliderSection(image, title, id) {
+ const carouselContent = document.getElementById("carouselContent");
+
+ const div = document.createElement("div");
+ div.classList.add("carousel-item");
+ div.dataset.id = id;
+ div.addEventListener("click", getDataModal);
+
+ const imgSlider = document.createElement("img");
+ imgSlider.classList.add("d-block", "w-100");
+ imgSlider.src = image;
+
+ const titleSlider = document.createElement("h5");
+ titleSlider.textContent = title;
+
+ div.append(imgSlider, titleSlider);
+ carouselContent.append(div);
+}
+
+async function createHTMLpostSection(image, title, username, id) {
+ //Article
+ const article = document.createElement("article");
+ article.classList.add("card");
+ // article.style.width = "18rem";
+ article.dataset.id = id;
+ article.addEventListener("click", getDataModal);
+
+ //Img element
+ const img1 = document.createElement("div");
+ img1.classList.add("card-img");
+ // img1.classList.add("card-img-top", "card-img");
+ img1.style.backgroundImage = `url(${image})`;
+
+ const img2 = document.createElement("div");
+ img2.classList.add("card__img--hover");
+ img2.style.backgroundImage = `url(${image})`;
+
+ //Body container
+ const sectionBody = document.createElement("section");
+ sectionBody.classList.add("card-info");
+
+ //Buttons container
+ const sectionButtons = document.createElement("section");
+ sectionButtons.classList.add("card-buttons");
+
+ //Delete and Edit buttons
+ const deleteButton = document.createElement("button");
+ const editButton = document.createElement("button");
+ deleteButton.id = "deleteBtn";
+ editButton.id = "editBtn";
+ deleteButton.textContent = "Delete";
+ editButton.textContent = "Edit";
+ deleteButton.classList.add("btn", "buttons_posts");
+ editButton.classList.add("btn", "buttons_posts");
+
+ //add buttons to their section
+ sectionButtons.append(editButton, deleteButton);
+
+ //create title
+ const h2 = document.createElement("h2");
+ h2.classList.add("card-title");
+ h2.textContent = title;
+ //create username
+ const userName = document.createElement("p");
+ userName.classList.add("card-text");
+ userName.textContent = `by ${username.username}`;
+
+ //add title,username and buttons to body
+ sectionBody.append(h2, userName, sectionButtons);
+ //add all to article
+ article.append(img1, img2, sectionBody);
+
+ //ADD to general container
+ postContainer.appendChild(article);
+}
+
+//!GET USERNAME
+async function getUsername(userId) {
+ const userURL = `http://localhost:3000/users/${userId}`;
+ const response = await fetch(userURL);
+ const user = await response.json();
+ return user;
+}
+
+//!GET IMAGE FROM UNSPLASH SOURCE
+function getImagesSplash(index) {
+ const randomNumber = randomIndex(index);
+ const srcImages = `https://source.unsplash.com/16${randomNumber}x9${randomNumber}/`;
+ return srcImages;
+}
+
+function randomIndex(num) {
+ return Math.floor(Math.random() * num + 1);
+}
+
+//!GET DATA FOR MODAL SPECIFIC POST
+async function getDataModal(e) {
+
+ if (!(e.target.id === "deleteBtn" || e.target.id === "editBtn")) {
+ const clickPostID = this.dataset.id;
+ fetchToServerPosts(clickPostID);
+ } else if (e.target.id === "editBtn") {
+ const clickPostID = e.target.parentElement.parentElement.parentElement.dataset.id;
+ fetchToServerPosts(clickPostID, false);
+ } else if (e.target.id === "deleteBtn") {
+ const popupModal = document.getElementById("popup");
+ const responseDelete = await deletePost(e);
+ const clickPostID = e.target.parentElement.parentElement.parentElement.dataset.id;
+ popup(clickPostID, popupModal, false, responseDelete);
+ removeCard(clickPostID);
+ }
+
+}
+
+async function fetchToServerPosts(id, info = true) {
+ const urlPost = `http://localhost:3000/posts/${id}`;
+ fetch(urlPost)
+ .then((response) => response.json())
+ .then((data) => {
+ if (info) {
+ addElementModal(data);
+ } else {
+ addElementModalEdit(data);
+ }
+ });
+}
+
+// !ADD ELEMENTS TO MODAL
+async function addElementModal(post) {
+ const parentContainer = document.getElementById("modalContent");
+ parentContainer.textContent = "";
+
+ //Close modal
+ const closeModalBtn = document.createElement("button");
+ closeModalBtn.textContent = "X";
+ closeModalBtn.classList.add("modal__close");
+ closeModalBtn.addEventListener("click", () =>
+ toogleDisplay(parentContainer.parentElement.parentElement)
+ );
+ //Create title
+ const h2 = document.createElement("h2");
+ h2.classList.add("modal__title");
+ h2.textContent = post.title;
+
+ //Create body
+ const body = document.createElement("p");
+ body.textContent = post.body;
+ body.classList.add("modal__body");
+
+ //Create div container user info
+ const divUser = document.createElement("div");
+ divUser.classList.add("modal__user-info");
+
+ //Create username
+ const userContainer = document.createElement("div");
+ userContainer.classList.add("user__container");
+ const user = await getUsername(post.userId);
+ const username = document.createElement("p");
+ username.classList.add("user-info__username");
+ username.textContent = user.username;
+ const userIcon = document.createElement("i");
+ userIcon.className = "fa-solid fa-circle-user";
+ userContainer.append(userIcon, username);
+
+ //Create email
+ const emailContainer = document.createElement("div");
+ emailContainer.classList.add("email__container");
+ const email = document.createElement("p");
+ email.classList.add("user-info__email");
+ email.textContent = user.email;
+ const emailIcon = document.createElement("i");
+ emailIcon.className = "fa-solid fa-envelope";
+ emailContainer.append(emailIcon, email);
+
+ //add to userinfo div the p
+ divUser.append(userContainer, emailContainer);
+
+ //button container
+ const btnContainer = document.createElement("div");
+ btnContainer.classList.add("modal__button-container");
+ //create button show comments
+ const showCommentesBtn = document.createElement("button");
+ showCommentesBtn.classList.add("comments__show-btn");
+ showCommentesBtn.classList.add("primary__btn");
+ showCommentesBtn.textContent = "Show comments";
+ showCommentesBtn.addEventListener("click", () => getDataComments(post.id));
+ btnContainer.append(showCommentesBtn);
+ //Create comments container
+ const containerComments = document.createElement("section");
+ containerComments.classList.add("comments-container");
+ containerComments.classList.add("container--hide");
+ containerComments.id = "commentsContainer";
+
+ //ADD ALL TO CONTAINER
+ parentContainer.append(
+ closeModalBtn,
+ h2,
+ body,
+ divUser,
+ btnContainer,
+ containerComments
+ );
+
+ toogleDisplay(parentContainer.parentElement.parentElement);
+}
+
+//!GET DATA FROM URL COMMENTS
+function getDataComments(postId) {
+ const containerComments = document.getElementById("commentsContainer");
+ containerComments.textContent = "";
+ const commentsURL = `http://localhost:3000/comments`;
+ fetch(commentsURL)
+ .then((response) => response.json())
+ .then((data) => {
+ for (const comment of data) {
+ if (comment.postId === postId) {
+ addCommentsToSection(comment, containerComments);
+ }
+ }
+ });
+}
+
+//!ADD COMMENTS TO HTML
+function addCommentsToSection(comment, containerComments) {
+ containerComments.classList.add("scrollbar");
+ const commentItem = document.createElement("div");
+ commentItem.classList.add("comment__item");
+ //create comments elements
+ const commentName = document.createElement("h3");
+ commentName.classList.add("comment__name");
+ commentName.textContent = comment.name;
+
+ const commentEmail = document.createElement("p");
+ commentEmail.classList.add("comment__email");
+ commentEmail.textContent = comment.email;
+
+ const commentBody = document.createElement("p");
+ commentBody.classList.add("comment__body");
+ commentBody.textContent = comment.body;
+
+ const line = document.createElement("hr");
+ line.classList.add("comment__separator");
+ //add comments elements to containerComments
+ commentItem.append(commentName, commentEmail, commentBody, line);
+ containerComments.append(commentItem);
+ toogleDisplay(containerComments);
+}
+
+// !ADD ELEMENTS TO MODAL EDIT
+async function addElementModalEdit(post) {
+ const parentContainer = document.getElementById("modalContentEdit");
+ parentContainer.textContent = "";
+ //Close modal
+ parentContainer.parentElement.addEventListener("click", (event) => {
+ closeModal(parentContainer.parentElement, event);
+ });
+
+ //Image create background
+ const backgorundImage = document.createElement("div");
+ backgorundImage.classList.add("modal-edit__img");
+
+ //Image illustration
+ const illustrationImage = document.createElement("div");
+ illustrationImage.classList.add("modal-edit__illustration");
+
+ backgorundImage.append(illustrationImage);
+
+ //Create form
+ const formBody = document.createElement("form");
+ formBody.classList.add("modal-edit__form");
+ formBody.dataset.id = post.id;
+
+ //Create form content
+ //Create label title
+ const labelTitle = document.createElement("label");
+ labelTitle.classList.add("modal-edit__label");
+ labelTitle.htmlFor = "titleInput";
+ labelTitle.textContent = "Title:";
+ //Create input title
+ const inputTitle = document.createElement("input");
+ inputTitle.classList.add("modal-edit__input");
+ inputTitle.id = "titleInput";
+ inputTitle.type = "text";
+ inputTitle.value = post.title;
+ inputTitle.addEventListener("change", updateValue);
+
+ labelTitle.append(inputTitle);
+
+ //Create label body
+ const labelBody = document.createElement("label");
+ labelBody.classList.add("modal-edit__label");
+ labelBody.htmlFor = "bodyInput";
+ labelBody.textContent = "Body:";
+ //Create input body
+ const inputBody = document.createElement("textarea");
+ inputBody.classList.add("modal-edit__textarea");
+ inputBody.id = "bodyInput";
+ // inputBody.type = "text";
+ inputBody.value = post.body;
+ inputBody.addEventListener('change', updateValue);
+
+ labelBody.append(inputBody);
+
+ //Create input savebutton
+ const inputSave = document.createElement("input");
+ inputSave.classList.add("modal-edit__btn");
+ inputSave.id = "saveBtn";
+ inputSave.type = "submit";
+ inputSave.value = "Save";
+
+ //Close btn
+ const inputClose = document.createElement("input");
+ inputClose.className = "modal-edit__btn modal-edit__btn-close";
+ inputClose.id = "deleteBtn";
+ inputClose.type = "button";
+ inputClose.value = "Close";
+ inputClose.addEventListener("click", (e) => {
+ toogleDisplay(parentContainer.parentElement);
+ });
+
+ //Container buttons
+ const containerButtons = document.createElement("div");
+ containerButtons.append(inputClose, inputSave);
+ containerButtons.classList.add("modal-edit__container-btn");
+ //ADD TO FORM
+ formBody.append(labelTitle, labelBody);
+ //ADD FORM TO CONTAINER
+ parentContainer.append(backgorundImage, formBody, containerButtons);
+
+ inputSave.addEventListener("click", (e) => {
+ e.preventDefault();
+ modifyPost(e);
+ });
+
+ parentContainer.parentElement.classList.toggle("container--hide");
+}
+
+//!UPDATE INPUT FIELDS
+function updateValue(e) {
+ if ((e.target.id === "titleInput")) {
+ const title = document.getElementById("titleInput");
+ title.value = e.target.value;
+ } else if ((e.target.id === "bodyInput")) {
+ const body = document.getElementById("bodyInput");
+ body.value = e.target.value;
+ }
+}
+
+//!MODIFY POST BY FETCH REQUE
+//!GET DATA FOR MODAL SPECIFIC POST
+async function modifyPost(e) {
+ const popupModal = document.getElementById("popup");
+ const clickPostID = e.target.parentElement.previousSibling.dataset.id;
+ const urlPost = `http://localhost:3000/posts/${clickPostID}`;
+ const response = await fetch(urlPost);
+ const post = await response.json();
+ const result = await fetchToModifyPosts(urlPost, post);
+ //show popup info after modify post
+ popup(clickPostID, popupModal, true, result);
+}
+
+async function fetchToModifyPosts(url, post) {
+ const titlePost = document.getElementById("titleInput").value;
+ const bodyPost = document.getElementById("bodyInput").value;
+ try {
+ await fetch(url, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ userId: post.userId,
+ title: titlePost,
+ body: bodyPost,
+ id: post.id
+ })
+ });
+ updateCard(post.id, titlePost);
+ return false;
+ } catch (error) {
+ console.log(error);
+ return true;
+ }
+}
+
+//!FUNCTION TO REMOVE POST
+function removeCard(id) {
+ const element = document.querySelector((`article[data-id='${id}']`));
+ element.remove();
+}
+
+//!FUNCTION TO UPDATE POST CARD INFO
+function updateCard(id, title) {
+ const element = document.querySelector((`article[data-id='${id}']`));
+ const titleElement = element.querySelector(".card-title");
+ titleElement.textContent = title;
+}
+
+//!DELETE POST BY FETCH REQUEST
+async function deletePost(e) {
+ const clickPostID = e.target.parentElement.parentElement.parentElement.dataset.id;
+ const urlPost = `http://localhost:3000/posts/${clickPostID}`;
+ try {
+ await fetch(urlPost, {
+ method: 'DELETE'
+ });
+ // const result = await responseDELETE.text();
+ return false;
+ } catch (error) {
+ console.log(error);
+ return true;
+ }
+
+}
+
+//!CAPITALIZE FIRST LETTER
+function capitalizeFirstLetter(string) {
+ return string.charAt(0).toUpperCase() + string.slice(1);
+}
+
+//!POPUP MODAL EDIT/DELETE INFO
+function popup(id, container, edit = true, error = false) {
+
+ //Remove last popup create if exists
+ const popup = document.getElementById("popup").childNodes;
+ if (popup.length > 0) {
+ Array.from(popup).map(child => child.remove());
+ }
+
+ const modalDialog = document.createElement("div");
+ modalDialog.classList.add("modal-dialog");
+
+ const modalContent = document.createElement("section");
+ modalContent.classList.add("modal-content");
+
+ //Popup header
+ const modalHeader = document.createElement("article");
+ modalHeader.classList.add("modal-header");
+
+ const h5Header = document.createElement("h5");
+ h5Header.classList.add("modal-title");
+ h5Header.textContent = "Info"
+
+ modalHeader.append(h5Header);
+
+ //Popupbody
+ const modalBody = document.createElement("article");
+ modalBody.classList.add("modal-body");
+
+ const pBody = document.createElement("p");
+ //Add content based on response and method
+ if (!error) {
+ if (edit) {
+ pBody.textContent = `Post with the id ${id} has been succesfully modified`;
+ } else {
+ pBody.textContent = `Post with the id ${id} has been succesfully removed`;
+ }
+ } else {
+ pBody.textContent = `An error has occured when updating post`;
+ }
+
+ modalBody.append(pBody);
+ //Popupfooter
+ const modalFooter = document.createElement("modal-footer");
+ modalFooter.classList.add("modal-footer");
+
+ //Popup buttons
+ const buttonClose = document.createElement("button");
+ buttonClose.classList.add("btn", "btn-secondary");
+ buttonClose.textContent = "Close";
+ buttonClose.addEventListener("click", () => {
+ const editModal = document.getElementById("modalContentEdit").parentElement;
+ toogleDisplay(container);
+ //Depending on delete or edit function
+ if (!editModal.classList.contains("container--hide")) {
+ toogleDisplay(editModal);
+ }
+ });
+
+ modalFooter.append(buttonClose);
+ modalContent.append(modalHeader, modalBody, modalFooter);
+ modalDialog.append(modalContent);
+
+ container.append(modalDialog);
+
+ toogleDisplay(container);
+
+}
+
+//!MODAL FUNCTIONALITY
+const parentContainer = document.querySelector(".modal");
+parentContainer.addEventListener("click", (event) =>
+ closeModal(parentContainer, event)
+);
+
+function closeModal(element, event) {
+ if (element === event.target) {
+ toogleDisplay(element);
+ }
+}
+
+//!TOGGLE DISPLAY ELEMENT HIDE/SHOW
+function toogleDisplay(element) {
+ element.classList.toggle("container--hide");
+}
\ No newline at end of file
diff --git a/wireframe/blog_structure.drawio b/wireframe/blog_structure.drawio
new file mode 100644
index 00000000..aa9ff9b8
--- /dev/null
+++ b/wireframe/blog_structure.drawio
@@ -0,0 +1,332 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+