diff --git a/next.config.js b/next.config.js index a843cbee..da265d11 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,9 @@ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, -} + images: { + domains: ['sprint-fe-project.s3.ap-northeast-2.amazonaws.com'], + }, +}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/pages/_app.tsx b/pages/_app.tsx index 021681f4..ad0f0594 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,6 +1,12 @@ -import '@/styles/globals.css' -import type { AppProps } from 'next/app' +import GlobalLayout from '@/src/components/GlobalLayout'; +import '@/styles/reset.css'; +import '@/styles/globals.css'; +import type { AppProps } from 'next/app'; export default function App({ Component, pageProps }: AppProps) { - return + return ( + + + + ); } diff --git a/pages/_document.tsx b/pages/_document.tsx index 54e8bf3e..713078f7 100644 --- a/pages/_document.tsx +++ b/pages/_document.tsx @@ -1,13 +1,13 @@ -import { Html, Head, Main, NextScript } from 'next/document' +import { Html, Head, Main, NextScript } from 'next/document'; export default function Document() { return ( - +
- ) + ); } diff --git a/pages/api/hello.ts b/pages/api/hello.ts deleted file mode 100644 index f8bcc7e5..00000000 --- a/pages/api/hello.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from 'next' - -type Data = { - name: string -} - -export default function handler( - req: NextApiRequest, - res: NextApiResponse -) { - res.status(200).json({ name: 'John Doe' }) -} diff --git a/pages/boards/components/BestBoardCard.module.css b/pages/boards/components/BestBoardCard.module.css new file mode 100644 index 00000000..169ff0d5 --- /dev/null +++ b/pages/boards/components/BestBoardCard.module.css @@ -0,0 +1,80 @@ +.bestBoardCardContainer { + border-radius: 8px; + background: var(--Secondary-50, #f9fafb); + padding: 0px 24px; + padding-bottom: 15px; + + display: flex; + flex-direction: column; + gap: 18px; +} + +.bestBoardBadge { + width: 102px; + height: 30px; + text-align: center; + padding: 2px 24px; + background: var(--Primary-100, #3692ff); + border-radius: 0px 0px 16px 16px; + + display: flex; + align-items: center; + gap: 4px; +} + +.bestText { + color: #fff; + font-size: 16px; + font-weight: 600; +} + +.contentContainer { + width: 100%; + display: flex; + justify-content: space-between; + flex-wrap: wrap; +} + +.contentTitle { + color: var(--Secondary-800, #1f2937); + font-size: 20px; + font-weight: 600; + max-width: 256px; +} + +.contentImgWrapper { + position: relative; + width: 72px; + height: 72px; + + border-radius: 6px; + border: 1px solid var(--Secondary-200, #e5e7eb); + background: #fff; +} + +.additionalInfo { + color: var(--Secondary-600, #4b5563); + font-size: 14px; + font-weight: 400; + + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; +} + +.likeCountWrapper { + flex: 1; + + display: flex; + align-items: center; + gap: 4px; +} + +/* tablet */ +@media screen and (max-width: 1199px) { + .contentTitle { + gap: 40px; + max-width: 180px; + } +} diff --git a/pages/boards/components/BestBoardCard.tsx b/pages/boards/components/BestBoardCard.tsx new file mode 100644 index 00000000..555d7496 --- /dev/null +++ b/pages/boards/components/BestBoardCard.tsx @@ -0,0 +1,42 @@ +import Image from 'next/image'; +import styles from './BestBoardCard.module.css'; +import medalSvg from '@/src/assets/ic_medal.svg'; +import heardSvg from '@/src/assets/ic_heart.svg'; +import { Board } from '@/src/apis/boardTypes'; + +interface BestBoardCardProps + extends Pick< + Board, + 'title' | 'image' | 'writer' | 'likeCount' | 'createdAt' + > {} + +export default function BestBoardCard({ + title, + image, + writer, + likeCount, + createdAt, +}: BestBoardCardProps) { + return ( +
+
+ medal + Best +
+
+

{title}

+
+ medal +
+
+
+ {writer.nickname} +
+ heardIcon + {likeCount} +
+ {new Date(createdAt).toLocaleDateString()} +
+
+ ); +} diff --git a/pages/boards/components/BestBoards.module.css b/pages/boards/components/BestBoards.module.css new file mode 100644 index 00000000..9c335b01 --- /dev/null +++ b/pages/boards/components/BestBoards.module.css @@ -0,0 +1,43 @@ +.bestBoards { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; + + margin-bottom: 40px; +} + +.title { + color: var(--Secondary-900, #111827); + font-size: 20px; + font-weight: 700; + + padding: 24px 0; +} + +/* tablet */ +@media screen and (max-width: 1199px) { + .bestBoards { + grid-template-columns: repeat(2, 1fr); + gap: 16px; + margin-bottom: 24px; + } + + .bestBoards > *:nth-child(n + 3) { + display: none; + } +} + +/* mobile */ +@media screen and (max-width: 767px) { + .bestBoards { + grid-template-columns: repeat(1, 1fr); + } + + .title { + padding: 16px 0; + } + + .bestBoards > *:nth-child(n + 2) { + display: none; + } +} diff --git a/pages/boards/components/BestBoards.tsx b/pages/boards/components/BestBoards.tsx new file mode 100644 index 00000000..5a81735e --- /dev/null +++ b/pages/boards/components/BestBoards.tsx @@ -0,0 +1,20 @@ +import { Board } from '@/src/apis/boardTypes'; +import BestBoardCard from './BestBoardCard'; +import styles from './BestBoards.module.css'; + +interface BestBoardsProps { + boards: Board[]; +} + +export default function BestBoards({ boards }: BestBoardsProps) { + return ( +
+

베스트 게시글

+
+ {boards.map((board) => ( + + ))} +
+
+ ); +} diff --git a/pages/boards/components/Boards.module.css b/pages/boards/components/Boards.module.css new file mode 100644 index 00000000..fdc84945 --- /dev/null +++ b/pages/boards/components/Boards.module.css @@ -0,0 +1,46 @@ +.boardsHeader { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 24px; +} + +.title { + color: var(--Secondary-800, #1f2937); + font-size: 20px; + font-weight: 700; +} + +.searchBar { + padding: 9px 20px 9px 16px; + border-radius: 12px; + background: var(--Secondary-100, #f3f4f6); + + display: flex; + align-items: center; + gap: 4px; +} + +.searchData { + font-size: 16px; + font-weight: 400; + background: transparent; +} + +.searchData::placeholder { + color: var(--Secondary-400, #9ca3af); +} + +/* tablet */ +@media screen and (max-width: 1199px) { + .boardsHeader { + margin-bottom: 48px; + } +} + +/* mobile */ +@media screen and (max-width: 767px) { + .boardsHeader { + margin-bottom: 16px; + } +} diff --git a/pages/boards/components/Boards.tsx b/pages/boards/components/Boards.tsx new file mode 100644 index 00000000..1d4d8495 --- /dev/null +++ b/pages/boards/components/Boards.tsx @@ -0,0 +1,26 @@ +import Image from 'next/image'; +import Button from '@/src/components/Button'; +import styles from './Boards.module.css'; +import searchSvg from '@/src/assets/ic_search.svg'; + +export default function Boards() { + return ( +
+
+

게시글

+
+ +
+
+
+
+ searchIcon + +
+
+
+ ); +} diff --git a/pages/boards/index.tsx b/pages/boards/index.tsx new file mode 100644 index 00000000..5e584f2c --- /dev/null +++ b/pages/boards/index.tsx @@ -0,0 +1,33 @@ +import { GetStaticProps } from 'next'; +import BestBoards from './components/BestBoards'; +import { Board } from '@/src/apis/boardTypes'; +import { getBoards } from '@/src/apis/boardsApi'; +import Boards from './components/Boards'; + +interface BoardsIndexProps { + boards: Board[]; +} + +export default function BoardsIndex({ boards }: BoardsIndexProps) { + return ( +
+ + +
+ ); +} + +export const getStaticProps: GetStaticProps = async () => { + const { list } = await getBoards({ + page: 1, + pageSize: 3, + orderBy: 'like', + }); + + return { + props: { + boards: list || [], + }, + revalidate: 600, // Re-generate the page every 600 seconds (ISR) + }; +}; diff --git a/pages/index.tsx b/pages/index.tsx index 02c4dee0..e53f8cde 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,114 +1,7 @@ -import Head from 'next/head' -import Image from 'next/image' -import { Inter } from 'next/font/google' -import styles from '@/styles/Home.module.css' - -const inter = Inter({ subsets: ['latin'] }) - export default function Home() { return ( <> - - Create Next App - - - - -
-
-

- Get started by editing  - pages/index.tsx -

- -
- -
- Next.js Logo -
- - -
+

index

- ) + ); } diff --git a/public/next.svg b/public/next.svg deleted file mode 100644 index 5174b28c..00000000 --- a/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/vercel.svg b/public/vercel.svg deleted file mode 100644 index d2f84222..00000000 --- a/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/apis/boardTypes.ts b/src/apis/boardTypes.ts new file mode 100644 index 00000000..e44f3fdf --- /dev/null +++ b/src/apis/boardTypes.ts @@ -0,0 +1,27 @@ +export interface Writer { + id: number; + nickname: string; +} + +export interface Board { + id: number; + title: string; + content: string; + image: string; // TODO image null 처리 + likeCount: number; + createdAt: string; + updatedAt: string; + writer: Writer; +} + +export interface GetBoardsResponse { + list: Board[]; + totalCount: number; +} + +export interface GetBoardsRequestParams { + page?: number; + pageSize?: number; + orderBy?: 'recent' | 'like'; + keyword?: string; +} diff --git a/src/apis/boardsApi.ts b/src/apis/boardsApi.ts new file mode 100644 index 00000000..d0b14090 --- /dev/null +++ b/src/apis/boardsApi.ts @@ -0,0 +1,39 @@ +import { Board, GetBoardsResponse, GetBoardsRequestParams } from './boardTypes'; + +const BASE_URL = 'https://panda-market-api.vercel.app'; + +async function fetchFromAPI(endpoint: string): Promise { + try { + const response = await fetch(endpoint); + if (!response.ok) { + throw new Error( + `HTTP error! Status: ${response.status} / response: ${response}` + ); + } + + return await response.json(); + } catch (error) { + console.error('Failed to fetch data:', error); + throw error; + } +} + +export async function getBoards({ + page = 1, + pageSize = 10, + orderBy = 'recent', + keyword, +}: GetBoardsRequestParams) { + const queryParams = new URLSearchParams({ + page: page.toString(), + pageSize: pageSize.toString(), + orderBy, + }); + + if (keyword) { + queryParams.append('keyword', keyword); + } + + const endpoint = `${BASE_URL}/articles?${queryParams.toString()}`; + return await fetchFromAPI(endpoint); +} diff --git a/src/assets/avatar.svg b/src/assets/avatar.svg new file mode 100644 index 00000000..3136c4fa --- /dev/null +++ b/src/assets/avatar.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/ic_heart.svg b/src/assets/ic_heart.svg new file mode 100644 index 00000000..2d4338bf --- /dev/null +++ b/src/assets/ic_heart.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/ic_medal.svg b/src/assets/ic_medal.svg new file mode 100644 index 00000000..d650c401 --- /dev/null +++ b/src/assets/ic_medal.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/ic_search.svg b/src/assets/ic_search.svg new file mode 100644 index 00000000..e8b451e4 --- /dev/null +++ b/src/assets/ic_search.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/logo_lg.png b/src/assets/logo_lg.png new file mode 100644 index 00000000..1fa247ed Binary files /dev/null and b/src/assets/logo_lg.png differ diff --git a/src/assets/logo_sm.png b/src/assets/logo_sm.png new file mode 100644 index 00000000..c72c1788 Binary files /dev/null and b/src/assets/logo_sm.png differ diff --git a/src/components/Button.module.css b/src/components/Button.module.css new file mode 100644 index 00000000..f01db878 --- /dev/null +++ b/src/components/Button.module.css @@ -0,0 +1,12 @@ +.primaryButton { + border-radius: 8px; + background: var(--Primary-100, #3692ff); + + color: #fff; + font-size: 16px; + font-weight: 600; + + padding: 12px 23px; + width: 100%; + height: 100%; +} diff --git a/src/components/Button.tsx b/src/components/Button.tsx new file mode 100644 index 00000000..56cd4dd6 --- /dev/null +++ b/src/components/Button.tsx @@ -0,0 +1,19 @@ +import { ButtonHTMLAttributes } from 'react'; +import styles from './Button.module.css'; + +interface PrimaryButtonProps extends ButtonHTMLAttributes { + children: React.ReactNode; + className?: string; +} + +export default function Button({ + children, + className, + ...props +}: PrimaryButtonProps) { + return ( + + ); +} diff --git a/src/components/GlobalLayout.module.css b/src/components/GlobalLayout.module.css new file mode 100644 index 00000000..2ccbc164 --- /dev/null +++ b/src/components/GlobalLayout.module.css @@ -0,0 +1,4 @@ +.main { + margin-top: 75px; + width: 100%; +} diff --git a/src/components/GlobalLayout.tsx b/src/components/GlobalLayout.tsx new file mode 100644 index 00000000..f4ce9201 --- /dev/null +++ b/src/components/GlobalLayout.tsx @@ -0,0 +1,14 @@ +import { ReactNode } from 'react'; +import Header from './Header'; +import styles from './GlobalLayout.module.css'; + +export default function GlobalLayout({ children }: { children: ReactNode }) { + return ( + <> +
+
+
{children}
+
+ + ); +} diff --git a/src/components/Header.module.css b/src/components/Header.module.css new file mode 100644 index 00000000..49aa3e69 --- /dev/null +++ b/src/components/Header.module.css @@ -0,0 +1,56 @@ +.header { + z-index: 999; + position: fixed; + top: 0; + width: 100%; + border-bottom: 1px solid #dfdfdf; +} + +.headerContainer { + padding: 10px 0; + display: flex; + align-items: center; + justify-content: space-between; + max-width: 1300px; + margin: auto; +} + +.logoWrapper { + width: 153px; + height: 51px; + background-image: url('../assets/logo_lg.png'); + background-size: contain; + background-repeat: no-repeat; + cursor: pointer; +} + +.nav { + color: var(--Secondary-600, #4b5563); + text-align: center; + font-size: 18px; + font-weight: 700; + flex: 1; + + display: flex; + gap: 30px; + margin-left: 30px; +} + +.avatarWrapper { + position: relative; + width: 40px; + height: 40px; +} + +/* mobile */ +@media screen and (max-width: 767px) { + .logoWrapper { + width: 81px; + height: 40px; + background-image: url('../assets/logo_sm.png'); + } + + .nav { + font-size: 16px; + } +} diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 00000000..1f5362ba --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,25 @@ +import Link from 'next/link'; +import styles from './Header.module.css'; +import Image from 'next/image'; +import avatarSvg from '@/src/assets/avatar.svg'; + +export default function Header() { + return ( +
+
+ + +
+ avatar +
+
+
+ ); +} diff --git a/styles/Home.module.css b/styles/Home.module.css deleted file mode 100644 index 6676d2c6..00000000 --- a/styles/Home.module.css +++ /dev/null @@ -1,229 +0,0 @@ -.main { - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - padding: 6rem; - min-height: 100vh; -} - -.description { - display: inherit; - justify-content: inherit; - align-items: inherit; - font-size: 0.85rem; - max-width: var(--max-width); - width: 100%; - z-index: 2; - font-family: var(--font-mono); -} - -.description a { - display: flex; - justify-content: center; - align-items: center; - gap: 0.5rem; -} - -.description p { - position: relative; - margin: 0; - padding: 1rem; - background-color: rgba(var(--callout-rgb), 0.5); - border: 1px solid rgba(var(--callout-border-rgb), 0.3); - border-radius: var(--border-radius); -} - -.code { - font-weight: 700; - font-family: var(--font-mono); -} - -.grid { - display: grid; - grid-template-columns: repeat(4, minmax(25%, auto)); - max-width: 100%; - width: var(--max-width); -} - -.card { - padding: 1rem 1.2rem; - border-radius: var(--border-radius); - background: rgba(var(--card-rgb), 0); - border: 1px solid rgba(var(--card-border-rgb), 0); - transition: background 200ms, border 200ms; -} - -.card span { - display: inline-block; - transition: transform 200ms; -} - -.card h2 { - font-weight: 600; - margin-bottom: 0.7rem; -} - -.card p { - margin: 0; - opacity: 0.6; - font-size: 0.9rem; - line-height: 1.5; - max-width: 30ch; -} - -.center { - display: flex; - justify-content: center; - align-items: center; - position: relative; - padding: 4rem 0; -} - -.center::before { - background: var(--secondary-glow); - border-radius: 50%; - width: 480px; - height: 360px; - margin-left: -400px; -} - -.center::after { - background: var(--primary-glow); - width: 240px; - height: 180px; - z-index: -1; -} - -.center::before, -.center::after { - content: ''; - left: 50%; - position: absolute; - filter: blur(45px); - transform: translateZ(0); -} - -.logo { - position: relative; -} -/* Enable hover only on non-touch devices */ -@media (hover: hover) and (pointer: fine) { - .card:hover { - background: rgba(var(--card-rgb), 0.1); - border: 1px solid rgba(var(--card-border-rgb), 0.15); - } - - .card:hover span { - transform: translateX(4px); - } -} - -@media (prefers-reduced-motion) { - .card:hover span { - transform: none; - } -} - -/* Mobile */ -@media (max-width: 700px) { - .content { - padding: 4rem; - } - - .grid { - grid-template-columns: 1fr; - margin-bottom: 120px; - max-width: 320px; - text-align: center; - } - - .card { - padding: 1rem 2.5rem; - } - - .card h2 { - margin-bottom: 0.5rem; - } - - .center { - padding: 8rem 0 6rem; - } - - .center::before { - transform: none; - height: 300px; - } - - .description { - font-size: 0.8rem; - } - - .description a { - padding: 1rem; - } - - .description p, - .description div { - display: flex; - justify-content: center; - position: fixed; - width: 100%; - } - - .description p { - align-items: center; - inset: 0 0 auto; - padding: 2rem 1rem 1.4rem; - border-radius: 0; - border: none; - border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); - background: linear-gradient( - to bottom, - rgba(var(--background-start-rgb), 1), - rgba(var(--callout-rgb), 0.5) - ); - background-clip: padding-box; - backdrop-filter: blur(24px); - } - - .description div { - align-items: flex-end; - pointer-events: none; - inset: auto 0 0; - padding: 2rem; - height: 200px; - background: linear-gradient( - to bottom, - transparent 0%, - rgb(var(--background-end-rgb)) 40% - ); - z-index: 1; - } -} - -/* Tablet and Smaller Desktop */ -@media (min-width: 701px) and (max-width: 1120px) { - .grid { - grid-template-columns: repeat(2, 50%); - } -} - -@media (prefers-color-scheme: dark) { - .vercelLogo { - filter: invert(1); - } - - .logo { - filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); - } -} - -@keyframes rotate { - from { - transform: rotate(360deg); - } - to { - transform: rotate(0deg); - } -} diff --git a/styles/globals.css b/styles/globals.css index d4f491e1..14b366ae 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -1,107 +1,50 @@ -:root { - --max-width: 1100px; - --border-radius: 12px; - --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', - 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', - 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; - - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; - - --primary-glow: conic-gradient( - from 180deg at 50% 50%, - #16abff33 0deg, - #0885ff33 55deg, - #54d6ff33 120deg, - #0071ff33 160deg, - transparent 360deg - ); - --secondary-glow: radial-gradient( - rgba(255, 255, 255, 1), - rgba(255, 255, 255, 0) - ); +@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css'); - --tile-start-rgb: 239, 245, 249; - --tile-end-rgb: 228, 232, 233; - --tile-border: conic-gradient( - #00000080, - #00000040, - #00000030, - #00000020, - #00000010, - #00000010, - #00000080 - ); - - --callout-rgb: 238, 240, 241; - --callout-border-rgb: 172, 175, 176; - --card-rgb: 180, 185, 188; - --card-border-rgb: 131, 134, 135; +:root { + --Primary-300: #1251aa; + --Primary-200: #1967d6; + --Primary-100: #3692ff; + --main-bg-color: #cfe5ff; + --Secondary-900: #111827; + --Secondary-800: #1f2937; + --Secondary-700: #374151; + --Secondary-600: #4b5563; + --Secondary-500: #6b7280; + --Secondary-400: #9ca3af; + --Secondary-200: #e5e7eb; + --Secondary-100: #f3f4f6; + --Secondary-50: #f9fafb; + --error-red: #f74747; + --size-max-width: 1200px; } -@media (prefers-color-scheme: dark) { - :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; - - --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); - --secondary-glow: linear-gradient( - to bottom right, - rgba(1, 65, 255, 0), - rgba(1, 65, 255, 0), - rgba(1, 65, 255, 0.3) - ); - - --tile-start-rgb: 2, 13, 46; - --tile-end-rgb: 2, 5, 19; - --tile-border: conic-gradient( - #ffffff80, - #ffffff40, - #ffffff30, - #ffffff20, - #ffffff10, - #ffffff10, - #ffffff80 - ); - - --callout-rgb: 20, 20, 20; - --callout-border-rgb: 108, 108, 108; - --card-rgb: 100, 100, 100; - --card-border-rgb: 200, 200, 200; - } +* { + font-family: 'Pretendard', sans-serif; } -* { - box-sizing: border-box; - padding: 0; - margin: 0; +header { + background-color: #fff; } -html, -body { - max-width: 100vw; - overflow-x: hidden; +footer { + background-color: var(--Secondary-900); } -body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); +.max-container { + max-width: var(--size-max-width); + margin: auto; } -a { - color: inherit; - text-decoration: none; +/* tablet */ +@media screen and (max-width: 1199px) { + .max-container { + max-width: 760px; + } } -@media (prefers-color-scheme: dark) { - html { - color-scheme: dark; +/* mobile */ +@media screen and (max-width: 767px) { + .max-container { + max-width: 360px; } } diff --git a/styles/reset.css b/styles/reset.css new file mode 100644 index 00000000..68d34450 --- /dev/null +++ b/styles/reset.css @@ -0,0 +1,56 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + color: inherit; + vertical-align: baseline; +} + +body { + margin: 0; + padding: 0; + height: 100vh; + width: 100%; +} + +a { + text-decoration: none; +} + +ul { + list-style: none; + padding: 0; +} + +button { + border: none; + outline: none; + cursor: pointer; +} + +footer { + width: 100%; +} + +input { + outline: none; + border: none; +} + +input:focus { + outline: none; +} + +input[type='submit'] { + cursor: pointer; + outline: none; +} + +@media screen and (max-width: 767px) { + body { + height: 100%; + } +}