diff --git a/package-lock.json b/package-lock.json index a1e590ee..d9442d2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^7.6.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" } @@ -14671,6 +14672,53 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz", + "integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.0.tgz", + "integrity": "sha512-DYgm6RDEuKdopSyGOWZGtDfSm7Aofb8CCzgkliTjtu/eDuB0gcsv6qdFhhi8HdtmA+KHkt5MfZ5K2PdzjugYsA==", + "license": "MIT", + "dependencies": { + "react-router": "7.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -15480,6 +15528,12 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", diff --git a/package.json b/package.json index 7ff0d6b5..cc88c710 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@testing-library/user-event": "^13.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^7.6.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index a11777cc..00000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/public/index.html b/public/index.html index aa069f27..0ca3abd0 100644 --- a/public/index.html +++ b/public/index.html @@ -1,5 +1,5 @@ - + @@ -24,7 +24,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - React App + 판다마켓 diff --git a/public/logo192.png b/public/logo192.png deleted file mode 100644 index fc44b0a3..00000000 Binary files a/public/logo192.png and /dev/null differ diff --git a/public/logo512.png b/public/logo512.png deleted file mode 100644 index a4e47a65..00000000 Binary files a/public/logo512.png and /dev/null differ diff --git a/public/robots.txt b/public/robots.txt deleted file mode 100644 index e9e57dc4..00000000 --- a/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 74b5e053..00000000 --- a/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/App.js b/src/App.js index 37845757..d9fe58ce 100644 --- a/src/App.js +++ b/src/App.js @@ -1,24 +1,23 @@ -import logo from './logo.svg'; -import './App.css'; +import { BrowserRouter, Route, Routes } from "react-router-dom"; +import Header from "./components/Header"; +import HomePage from "./pages/Homepage/HomePage"; +import CommunityPage from "./pages/CommunityPage/CommunityPage"; +import ProductsPage from "./pages/ProductsPage/ProductsPage"; +import LoginPage from "./pages/LoginPage/LoginPage"; function App() { return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
+ +
+
+ + } /> + } /> + } /> + } /> + +
+ ); } diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 1f03afee..00000000 --- a/src/App.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/api.js b/src/api.js new file mode 100644 index 00000000..1d81755a --- /dev/null +++ b/src/api.js @@ -0,0 +1,12 @@ +const BASE_URL = "https://panda-market-api.vercel.app"; + +export async function getProducts(params = {}) { + const query = new URLSearchParams(params).toString(); + + const response = await fetch(`${BASE_URL}/products?${query}`); + if (!response.ok) { + throw new Error("상품을 불러오는데 실패했습니다."); + } + const body = await response.json(); + return body; +} diff --git a/src/assets/heart.svg b/src/assets/heart.svg new file mode 100644 index 00000000..cad016c1 --- /dev/null +++ b/src/assets/heart.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/leftBtn.svg b/src/assets/leftBtn.svg new file mode 100644 index 00000000..2a9de23a --- /dev/null +++ b/src/assets/leftBtn.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 00000000..f663a9a8 Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/assets/rightBtn.svg b/src/assets/rightBtn.svg new file mode 100644 index 00000000..daa483c3 --- /dev/null +++ b/src/assets/rightBtn.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/searchicon.svg b/src/assets/searchicon.svg new file mode 100644 index 00000000..52241e6d --- /dev/null +++ b/src/assets/searchicon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/sorticon.svg b/src/assets/sorticon.svg new file mode 100644 index 00000000..657b44f9 --- /dev/null +++ b/src/assets/sorticon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/userMenu.png b/src/assets/userMenu.png new file mode 100644 index 00000000..3e28565b Binary files /dev/null and b/src/assets/userMenu.png differ diff --git a/src/components/Header.css b/src/components/Header.css new file mode 100644 index 00000000..33a07c69 --- /dev/null +++ b/src/components/Header.css @@ -0,0 +1,54 @@ +.header { + display: flex; + justify-content: space-between; + align-items: center; +} +.headerLeft { + display: flex; + align-items: center; +} + +.logo { + display: flex; + justify-content: center; + align-items: center; + gap: 10px; +} +.logo a { + color: var(--primary-color); + font-size: var(--text-xl); + font-weight: 700; + line-height: 100%; + cursor: pointer; +} + +.headerNav { + display: flex; + list-style: none; + gap: 10px; + margin-left: 16px; +} + +.headerNav a { + color: var(--gray); + font-size: var(--text-base); + line-height: 26px; + font-weight: 600; +} + +.headerUser { + width: 40px; + height: 40px; +} + +@media (min-width: 768px) { + .logo a { + font-size: 25.63px; + } + .headerNav a { + font-size: var(--text-lg); + } +} + +@media (min-width: 1280px) { +} diff --git a/src/components/Header.jsx b/src/components/Header.jsx new file mode 100644 index 00000000..7b8d872f --- /dev/null +++ b/src/components/Header.jsx @@ -0,0 +1,35 @@ +import { Link, NavLink } from "react-router-dom"; +import "./Header.css"; +import usermenu from "../assets/userMenu.png"; +import logo from "../assets/logo.png"; + +function Header() { + return ( +
+
+ + 판다마켓 로고 + + 판다마켓 + + + +
+ + + 유저메뉴 + +
+ ); +} + +export default Header; diff --git a/src/index.css b/src/index.css deleted file mode 100644 index ec2585e8..00000000 --- a/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/src/index.js b/src/index.js index d563c0fb..b391783d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,11 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./styles/global.css"; -const root = ReactDOM.createRoot(document.getElementById('root')); +const root = ReactDOM.createRoot(document.getElementById("root")); root.render( ); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 9dfc1c05..00000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/pages/CommunityPage/CommunityPage.jsx b/src/pages/CommunityPage/CommunityPage.jsx new file mode 100644 index 00000000..30216eb9 --- /dev/null +++ b/src/pages/CommunityPage/CommunityPage.jsx @@ -0,0 +1,5 @@ +function CommunityPage() { + return
CommunityPage
; +} + +export default CommunityPage; diff --git a/src/pages/Homepage/HomePage.jsx b/src/pages/Homepage/HomePage.jsx new file mode 100644 index 00000000..f7c6ae05 --- /dev/null +++ b/src/pages/Homepage/HomePage.jsx @@ -0,0 +1,5 @@ +function HomaPage() { + return
HomePage
; +} + +export default HomaPage; diff --git a/src/pages/LoginPage/LoginPage.jsx b/src/pages/LoginPage/LoginPage.jsx new file mode 100644 index 00000000..4f27ccc3 --- /dev/null +++ b/src/pages/LoginPage/LoginPage.jsx @@ -0,0 +1,5 @@ +function LoginPage() { + return
LoginPage
; +} + +export default LoginPage; diff --git a/src/pages/ProductsPage/AllItemsSection.jsx b/src/pages/ProductsPage/AllItemsSection.jsx new file mode 100644 index 00000000..0a710885 --- /dev/null +++ b/src/pages/ProductsPage/AllItemsSection.jsx @@ -0,0 +1,114 @@ +import { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; +import { getProducts } from "../../api"; +import Item from "./Item"; +import SortMethod from "./SortMethod"; +import searchicon from "../../assets/searchicon.svg"; +import sorticon from "../../assets/sorticon.svg"; +import PageBar from "./PageBar"; + +const getPageSize = () => { + const width = window.innerWidth; + if (width < 768) { + //mobile + return 4; + } else if (width < 1280) { + //tablet + return 6; + } else { + //desktop + return 10; + } +}; + +function AllItemsSection() { + const [orderBy, setOrderBy] = useState("recent"); + const [itemList, setItemList] = useState([]); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(getPageSize()); + const [totalPage, setTotalPage] = useState(); + const [isToggleVisible, setIsToggleVisible] = useState(false); + + const fetchData = async ({ orderBy, page, pageSize }) => { + const products = await getProducts({ orderBy, page, pageSize }); + setItemList(products.list); + setTotalPage(Math.ceil(products.totalCount / pageSize)); + }; + + const handleSortSelection = (sortOption) => { + setOrderBy(sortOption); + setIsToggleVisible(false); + }; + + useEffect(() => { + const handleSize = () => { + setPageSize(getPageSize()); + }; + + window.addEventListener("resize", handleSize); + fetchData({ orderBy, page, pageSize }); + + return () => { + window.removeEventListener("resize", handleSize); + }; + }, [orderBy, page, pageSize]); + + const toggleDown = () => { + setIsToggleVisible(!isToggleVisible); + }; + + const onPageChange = (pageNum) => { + setPage(pageNum); + }; + + return ( +
+
+

전체 상품

+ + 상품 등록하기 + + +
+ 검색버튼 + +
+ +
+ + {isToggleVisible && ( + + )} + + + {isToggleVisible && ( + + )} +
+
+ +
+ {itemList?.map((item) => ( + + ))} +
+ +
+ +
+
+ ); +} + +export default AllItemsSection; diff --git a/src/pages/ProductsPage/BestItemsSection.jsx b/src/pages/ProductsPage/BestItemsSection.jsx new file mode 100644 index 00000000..19678dda --- /dev/null +++ b/src/pages/ProductsPage/BestItemsSection.jsx @@ -0,0 +1,53 @@ +import { useEffect, useState } from "react"; +import { getProducts } from "../../api"; +import Item from "./Item"; + +const getPageSize = () => { + const width = window.innerWidth; + if (width < 768) { + //mobile + return 1; + } else if (width < 1280) { + //tablet + return 2; + } else { + //desktop + return 4; + } +}; + +function BestItemsSection() { + const [itemList, setItemList] = useState([]); + const [pageSize, setPageSize] = useState(getPageSize()); + + const fetchData = async ({ orderBy, pageSize }) => { + const products = await getProducts({ orderBy, pageSize }); + setItemList(products.list); + }; + + useEffect(() => { + const handleSize = () => { + setPageSize(getPageSize); + }; + + window.addEventListener("resize", handleSize); + fetchData({ orderBy: "favorite", pageSize }); + + return () => { + window.removeEventListener("resize", handleSize); + }; + }, [pageSize]); + + return ( +
+

베스트 상품

+
+ {itemList?.map((item) => ( + + ))} +
+
+ ); +} + +export default BestItemsSection; diff --git a/src/pages/ProductsPage/Item.jsx b/src/pages/ProductsPage/Item.jsx new file mode 100644 index 00000000..441be281 --- /dev/null +++ b/src/pages/ProductsPage/Item.jsx @@ -0,0 +1,19 @@ +import heart from "../../assets/heart.svg"; + +function Item({ item }) { + return ( +
+ {item.name} +
+

{item.name}

+

{item.price.toLocaleString()}원

+
+ 하트 + {item.favoriteCount} +
+
+
+ ); +} + +export default Item; diff --git a/src/pages/ProductsPage/PageBar.css b/src/pages/ProductsPage/PageBar.css new file mode 100644 index 00000000..ae0954a7 --- /dev/null +++ b/src/pages/ProductsPage/PageBar.css @@ -0,0 +1,19 @@ +.pageBar { + display: flex; + justify-content: center; + align-items: center; +} +.pageButton { + width: 40px; + height: 40px; + border-radius: 1px solid #e5e7eb; + border-radius: 50%; + border: none; + background-color: var(--white); + color: var(--gray-500); +} + +.pageButton.active { + background-color: var(--primary-color); + color: var(--white); +} diff --git a/src/pages/ProductsPage/PageBar.jsx b/src/pages/ProductsPage/PageBar.jsx new file mode 100644 index 00000000..51e6e936 --- /dev/null +++ b/src/pages/ProductsPage/PageBar.jsx @@ -0,0 +1,50 @@ +import { useState } from "react"; +import leftBtn from "../../assets/leftBtn.svg"; +import rightBtn from "../../assets/rightBtn.svg"; +import "./PageBar.css"; + +const PageBar = ({ totalPage, activePage, onPageChange }) => { + const maxVisiblePages = 5; + const [currentGroup, setCurrentGroup] = useState(0); + const startPage = currentGroup * maxVisiblePages + 1; + + const pages = Array.from( + { + length: Math.min(maxVisiblePages, totalPage - startPage + 1), + }, + (_, i) => startPage + i + ); + + const maxGroup = Math.floor((totalPage - 1) / maxVisiblePages); + + return ( +
+ + {pages.map((page) => ( + + ))} + + +
+ ); +}; + +export default PageBar; diff --git a/src/pages/ProductsPage/Products.css b/src/pages/ProductsPage/Products.css new file mode 100644 index 00000000..836a3eca --- /dev/null +++ b/src/pages/ProductsPage/Products.css @@ -0,0 +1,173 @@ +.itemCard { + display: flex; + flex-direction: column; + color: var(--dark-800); + cursor: pointer; + gap: 10px; +} + +.itemImage { + width: 100%; + height: auto; + aspect-ratio: 1; + border-radius: 16px; +} + +.itemDescription { + display: flex; + flex-direction: column; + color: var(--dark-800); + gap: 6px; +} + +.itemTitle { + font-size: var(--text-sm); +} +.itemPrice { + font-size: var(--text-base); +} + +.favoriteHeart { + color: var(--gray); + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; +} + +.bestItemContainer { + display: flex; + flex-direction: column; + padding-top: 17px; + padding-bottom: 24px; + gap: 16px; +} + +.bestItemSection { + color: #fcfcfc; + display: grid; + grid-template-columns: repeat(1, 1fr); + gap: 32px 8px; +} + +.allItemHeaderWrapper { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 16px; +} + +.registerButton { + background-color: var(--primary-color); + font-size: var(--text-base); + color: #f3f4f6; + padding: 12px 32px; + gap: 10px; + border-radius: 8px; + border: none; +} + +.searchBarContainer { + width: 288px; + display: flex; + align-items: center; + background-color: #f3f4f6; + padding: 9px 20px 9px 16px; + border-radius: 12px; + gap: 10px; +} + +.searchInput { + width: 174px; + background-color: inherit; + border: none; + line-height: 26px; +} + +.searchInput::placeholder { + color: #9ca3af; + font-size: 16px; +} + +.searchInput:focus { + outline: none; +} + +.sortButtonContainer { + position: relative; +} + +.sortIconButton { + background-color: var(--white); + border: 1px solid #e5e7eb; + border-radius: 12px; + padding: 9px; + gap: 10px; + align-items: center; +} +.sortTextButton { + display: none; +} + +.allItemSection { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 40px; +} + +.pageNavigationBar { + padding: 40px 0; +} + +@media (min-width: 768px) { + .bestItemSection { + grid-template-columns: repeat(2, 1fr); + } + + .allItemSection { + grid-template-columns: repeat(3, 1fr); + } + + .allItemTitle { + order: 1; + margin-right: auto; + } + .searchBarContainer { + order: 2; + } + .registerButton { + order: 3; + } + .sortButtonContainer { + order: 4; + } + + .sortIconButton { + display: none; + } + + .sortTextButton { + width: 130px; + height: 42px; + display: inline-block; + background-color: var(--white); + font-size: var(--text-base); + border: 1px solid #e5e7eb; + border-radius: 12px; + padding: 12px 20px; + gap: 10px; + align-items: center; + } +} + +@media (min-width: 1280px) { + .bestItemSection { + grid-template-columns: repeat(4, 1fr); + } + + .allItemSection { + grid-template-columns: repeat(5, 1fr); + } +} diff --git a/src/pages/ProductsPage/ProductsPage.jsx b/src/pages/ProductsPage/ProductsPage.jsx new file mode 100644 index 00000000..d8c4cd8f --- /dev/null +++ b/src/pages/ProductsPage/ProductsPage.jsx @@ -0,0 +1,17 @@ +import "./Products.css"; +import BestItemsSection from "./BestItemsSection"; +import AllItemsSection from "./AllItemsSection"; + +function ProductPage() { + return ( + <> +
+ + +
+
+ + ); +} + +export default ProductPage; diff --git a/src/pages/ProductsPage/SortMethod.css b/src/pages/ProductsPage/SortMethod.css new file mode 100644 index 00000000..0350e1cf --- /dev/null +++ b/src/pages/ProductsPage/SortMethod.css @@ -0,0 +1,19 @@ +.toggleList { + width: 130px; + top: 110%; + right: 0; + position: absolute; + background-color: var(--white); + border-radius: 8px; + border: 1px solid #e5e7eb; +} + +.toggleItem { + display: flex; + justify-content: center; + padding: 12px; + font-size: var(--text-base); + color: var(--dark-800); + cursor: pointer; + border-bottom: 1px solid #e5e7eb; +} diff --git a/src/pages/ProductsPage/SortMethod.jsx b/src/pages/ProductsPage/SortMethod.jsx new file mode 100644 index 00000000..b224418b --- /dev/null +++ b/src/pages/ProductsPage/SortMethod.jsx @@ -0,0 +1,16 @@ +import "./SortMethod.css"; + +function SortMethod({ onSortSelection }) { + return ( +
+
onSortSelection("recent")}> + 최신순 +
+
onSortSelection("favorite")}> + 좋아요순 +
+
+ ); +} + +export default SortMethod; diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js deleted file mode 100644 index 5253d3ad..00000000 --- a/src/reportWebVitals.js +++ /dev/null @@ -1,13 +0,0 @@ -const reportWebVitals = onPerfEntry => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/src/setupTests.js b/src/setupTests.js deleted file mode 100644 index 8f2609b7..00000000 --- a/src/setupTests.js +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; diff --git a/src/styles/global.css b/src/styles/global.css new file mode 100644 index 00000000..5b3b33aa --- /dev/null +++ b/src/styles/global.css @@ -0,0 +1,78 @@ +:root { + --primary-color: #3692ff; + --gray: #4b5563; + --gray-500: #6b7280; + --dark: #111827; + --dark-800: #1f2937; + --white: #ffffff; + + /* Font Sizes */ + --text-sm: 14px; + --text-base: 16px; + --text-lg: 18px; + --text-xl: 20px; + --text-2xl: 24px; + --text-3xl: 40px; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +header { + background-color: var(--white); + border-bottom: 1px solid #dfdfdf; + width: 100%; + height: 70px; + padding: 0 16px; + position: fixed; + top: 0; + z-index: 1; + display: flex; + justify-content: space-between; + align-items: center; +} + +a { + text-decoration: none; +} + +h1 { + font-size: var(--text-xl); +} + +button { + cursor: pointer; +} + +.container { + margin-top: 70px; +} + +.productsWrapper { + width: 100%; + padding: 0 16px; +} + +@media (min-width: 768px) { + header { + padding: 0 24px; + } + + .productsWrapper { + padding: 0 24px; + } +} + +@media (min-width: 1280px) { + header { + padding: 0 200px; + } + + .productsWrapper { + max-width: 1200px; + margin: 0 auto; + } +}