diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 00000000..756df8b4
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,37 @@
+## π₯ κ²°κ³Ό
+
+
+
+- λ°°ν¬ν νμ΄μ§ μ κ·Ό κ²½λ‘(GitHub Pages):
+- μ€ν¬λ¦° 리λ νλ©΄ λ
Ήν μμ (before / after)
+
+## β
κ°μ μμ
λͺ©λ‘
+
+
+
+**1 μ»΄ν¬λνΈ μ κ·Όμ± κ°μ - μ΄λ―Έμ§ μΊλ‘μ
**
+
+- [ ] μ€ν¬λ¦° 리λκ° μΊλ‘μ
μ μ 체 μμ΄ν
μλ₯Ό μ½μ μ μμ΄μΌ ν©λλ€.
+- [ ] μ€ν¬λ¦° 리λκ° μ΄λ―Έμ§ μΊλ‘μ
λ΄ κ° μμ΄ν
μ 보λ₯Ό μ½μ μ μμ΄μΌ ν©λλ€.
+ - [ ] μ¬νμ§, μ’μ μ ν, κ°κ²© μ 보λ₯Ό νλ²μ μ½μ μ μμ΄μΌ ν©λλ€.
+ - [ ] μ΄μ /λ€μ μμ΄ν
μΌλ‘ μ΄λνκ³ νμ¬ λ³΄μ΄λ μμ΄ν
μ μ 보λ₯Ό μ½μ μ μμ΄μΌ ν©λλ€.
+- [ ] κ° μμ΄ν
μ μ ννλ©΄ κ°κ°μ λ§λ λ§ν¬λ‘ μ΄λν μ μμ΄μΌ ν©λλ€.
+
+**2 νμ΄μ§ μ κ·Όμ± κ°μ **
+
+- [ ] νμ΄μ§λ₯Ό νλμ λ¬Έμλ‘ μ½μ μ μμ΄μΌ ν©λλ€.
+ - [ ] νμ΄μ§μ μ μ ν μ λͺ©(title)μ μ 곡νμΈμ. μ λͺ©μ νμ΄μ§μ μ£Όμ λ΄μ©μ κ°κ²°νκ² μ€λͺ
ν΄μΌ ν©λλ€.
+ - [ ] νμ΄μ§μ μ£Όμ μμμ μλ§¨ν± νκ·Έλ₯Ό μ¬μ©ν΄ λͺ
νν ꡬλΆν΄ μ£ΌμΈμ
+ - [ ] ν€λ©μ λ
Όλ¦¬μ μΈ μμλ‘ μ¬μ©ν΄ νμ΄μ§ ꡬ쑰λ₯Ό λͺ
νν ν΄μ£ΌμΈμ
+- [ ] ν€λ³΄λ μ¬μ©μλ₯Ό μν΄ νμ΄μ§ μ΅μλ¨μ 'λ³Έλ¬ΈμΌλ‘ λ°λ‘κ°κΈ°' λ§ν¬λ₯Ό μ κ³΅ν΄ λ°λ³΅λλ λ©λ΄λ₯Ό 건λλΈ μ μκ² ν΄μ£ΌμΈμ
+
+## π§ μ°λ¦¬ νμ μ κ·Όμ± μ²΄ν¬λ¦¬μ€νΈ
+
+
diff --git a/.gitignore b/.gitignore
index 4d29575d..a547bf36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,23 +1,24 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.js
-
-# testing
-/coverage
-
-# production
-/build
-
-# misc
-.DS_Store
-.env.local
-.env.development.local
-.env.test.local
-.env.production.local
-
+# Logs
+logs
+*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..f26ae07d
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,7 @@
+{
+ "trailingComma": "none",
+ "tabWidth": 2,
+ "singleQuote": true,
+ "bracketSpacing": true,
+ "printWidth": 100
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 13ee2b04..45fe25f6 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,3 +1,10 @@
{
- "nuxt.isNuxtApp": false
-}
\ No newline at end of file
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "[typescript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[typescriptreact]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ }
+}
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 2a91af05..00000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2021 woowacourse
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/README.md b/README.md
index 4b8c274f..6ca73d28 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,3 @@
-
-
+
+
+ λ³Έλ¬ΈμΌλ‘ λ°λ‘κ°κΈ°
+
+
+
+
+
+
+
+
+
+ μ§κΈ λ λκΈ° μ’μ μ¬ν
+
+
+
+
+
+ {/* μΆκ° CHALLENGE: λͺ¨λ¬ ν¬μ»€μ€ νΈλ© */}
+ {/*
*/}
);
}
diff --git a/src/Typography.css b/src/Typography.css
new file mode 100644
index 00000000..2231d279
--- /dev/null
+++ b/src/Typography.css
@@ -0,0 +1,34 @@
+.heading-1-text {
+ font-size: 24px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: normal;
+}
+
+.heading-2-text {
+ font-size: 20px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: normal;
+}
+
+.heading-3-text {
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 800;
+ line-height: normal;
+}
+
+.body-text {
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 500;
+ line-height: 150%;
+}
+
+.button-text {
+ font-size: 16px;
+ font-style: normal;
+ font-weight: 500;
+ line-height: 100%;
+}
diff --git a/src/assets/booking-bg.png b/src/assets/booking-bg.png
new file mode 100644
index 00000000..92dcaf35
Binary files /dev/null and b/src/assets/booking-bg.png differ
diff --git a/src/assets/chevron-left.svg b/src/assets/chevron-left.svg
new file mode 100644
index 00000000..73ef3e28
--- /dev/null
+++ b/src/assets/chevron-left.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/chevron-right.svg b/src/assets/chevron-right.svg
new file mode 100644
index 00000000..7d4ea7be
--- /dev/null
+++ b/src/assets/chevron-right.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/close.svg b/src/assets/close.svg
new file mode 100644
index 00000000..c784d0c6
--- /dev/null
+++ b/src/assets/close.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/help.svg b/src/assets/help.svg
new file mode 100644
index 00000000..b7016a6d
--- /dev/null
+++ b/src/assets/help.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/minus.svg b/src/assets/minus.svg
new file mode 100644
index 00000000..f6105fc4
--- /dev/null
+++ b/src/assets/minus.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/plus.svg b/src/assets/plus.svg
new file mode 100644
index 00000000..87f15c1f
--- /dev/null
+++ b/src/assets/plus.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/travel-item-01.png b/src/assets/travel-item-01.png
new file mode 100644
index 00000000..d6ad14a2
Binary files /dev/null and b/src/assets/travel-item-01.png differ
diff --git a/src/assets/travel-item-02.png b/src/assets/travel-item-02.png
new file mode 100644
index 00000000..be18975e
Binary files /dev/null and b/src/assets/travel-item-02.png differ
diff --git a/src/assets/travel-item-03.png b/src/assets/travel-item-03.png
new file mode 100644
index 00000000..22a5a345
Binary files /dev/null and b/src/assets/travel-item-03.png differ
diff --git a/src/components/FlightBooking.module.css b/src/components/FlightBooking.module.css
new file mode 100644
index 00000000..904232ea
--- /dev/null
+++ b/src/components/FlightBooking.module.css
@@ -0,0 +1,95 @@
+.flightBooking {
+ background-color: white;
+ border-radius: 8px;
+ padding: 20px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+}
+
+.passengerLabel {
+ display: flex;
+ align-items: center;
+}
+
+.helpIconWrapper {
+ position: relative;
+ margin-left: 5px;
+ cursor: pointer;
+}
+
+.helpIcon {
+ width: 16px;
+ height: 16px;
+}
+
+.tooltip {
+ position: absolute;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ background-color: #333;
+ color: white;
+ padding: 5px 10px;
+ border-radius: 4px;
+ font-size: 12px;
+ white-space: nowrap;
+ z-index: 1;
+}
+
+.tooltip::after {
+ content: '';
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ border-width: 5px;
+ border-style: solid;
+ border-color: #333 transparent transparent transparent;
+}
+
+.passengerCount {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.passengerCount span {
+ font-weight: 700;
+}
+
+.counter {
+ display: flex;
+ align-items: center;
+}
+
+.counter button {
+ width: 30px;
+ height: 30px;
+ border-radius: 16px;
+ border: 1px solid #c0c0c0;
+ background-color: #fff;
+ font-size: 18px;
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.counter span {
+ font-size: 18px;
+ text-align: center;
+ font-size: 18px;
+ font-style: normal;
+ font-weight: 500;
+ line-height: normal;
+ padding: 0 16px;
+}
+
+.searchButton {
+ width: 100%;
+ padding: 10px;
+ background-color: #333;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+}
diff --git a/src/components/FlightBooking.tsx b/src/components/FlightBooking.tsx
new file mode 100644
index 00000000..535594e4
--- /dev/null
+++ b/src/components/FlightBooking.tsx
@@ -0,0 +1,72 @@
+import { useCallback, useState } from 'react';
+
+import helpIcon from '../assets/help.svg';
+import plus from '../assets/plus.svg';
+import minus from '../assets/minus.svg';
+
+import styles from './FlightBooking.module.css';
+
+const MIN_PASSENGERS = 1;
+const MAX_PASSENGERS = 3;
+
+const FlightBooking = () => {
+ const [adultCount, setAdultCount] = useState(MIN_PASSENGERS);
+ const [statusMessage, setStatusMessage] = useState('');
+ const [showTooltip, setShowTooltip] = useState(false);
+
+ const incrementCount = useCallback(() => {
+ if (adultCount === MAX_PASSENGERS) {
+ setStatusMessage('μ΅λ μΉκ° μμ λλ¬νμ΅λλ€');
+ return;
+ }
+
+ setAdultCount((prev) => Math.min(MAX_PASSENGERS, prev + 1));
+ setStatusMessage('');
+ }, [adultCount]);
+
+ const decrementCount = useCallback(() => {
+ if (adultCount === MIN_PASSENGERS) {
+ setStatusMessage('μ΅μ 1λͺ
μ μΉκ°μ΄ νμν©λλ€');
+ return;
+ }
+
+ setAdultCount((prev) => Math.max(MIN_PASSENGERS, prev - 1));
+ setStatusMessage('');
+ }, [adultCount]);
+
+ return (
+
+
νκ³΅κΆ μ맀
+
+
+
μ±μΈ
+
setShowTooltip(true)}
+ onMouseLeave={() => setShowTooltip(false)}
+ >
+
+ {showTooltip &&
μ΅λ 3λͺ
κΉμ§ μμ½ν μ μμ΅λλ€
}
+
+
+
+
+
{adultCount}
+
+
+
+ {statusMessage && (
+
+ {statusMessage}
+
+ )}
+
+
+ );
+};
+
+export default FlightBooking;
diff --git a/src/components/Navigation.module.css b/src/components/Navigation.module.css
new file mode 100644
index 00000000..85fb9e87
--- /dev/null
+++ b/src/components/Navigation.module.css
@@ -0,0 +1,110 @@
+.mainNav {
+ background-color: #f8f8f8;
+ padding: 10px 0;
+}
+
+.navList {
+ list-style-type: none;
+ padding: 10px 16px;
+ margin: 0;
+}
+
+.navItem {
+ display: inline-block;
+ position: relative;
+ margin-right: 20px;
+}
+
+.navItem a {
+ text-decoration: none;
+ color: #333;
+ font-size: 16px;
+ padding: 5px 10px;
+ display: block;
+}
+
+.navItem a:hover,
+.navItem a:focus {
+ background-color: #e0e0e0;
+ border-radius: 4px;
+}
+
+.navItem .navList {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+}
+
+.navItem:hover > .navList,
+.navItem:focus-within > .navList {
+ display: block;
+}
+
+.navItem .navList .navItem {
+ width: 200px;
+ display: block;
+ margin-right: 0;
+}
+
+.navItem .navList .navItem a {
+ padding: 10px;
+}
+
+.navItem .navList .navItem a:hover,
+.navItem .navList .navItem a:focus {
+ background-color: #f0f0f0;
+}
+
+.navToggle {
+ display: none;
+ font-size: 16px;
+ padding: 10px;
+ background-color: #333;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+@media (max-width: 768px) {
+ .navToggle {
+ display: block;
+ }
+
+ .mainNav {
+ display: none;
+ }
+
+ .mainNavActive {
+ display: block;
+ }
+
+ .navItem {
+ display: block;
+ margin-right: 0;
+ margin-bottom: 10px;
+ }
+
+ .navItem .navList {
+ position: static;
+ display: none;
+ width: auto;
+ border: none;
+ box-shadow: none;
+ margin-left: 20px;
+ }
+
+ .navItem:hover > .navList,
+ .navItem:focus-within > .navList {
+ display: block;
+ }
+
+ .navItem .navList .navItem {
+ width: auto;
+ }
+}
diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx
new file mode 100644
index 00000000..80a3f092
--- /dev/null
+++ b/src/components/Navigation.tsx
@@ -0,0 +1,70 @@
+import { useState } from 'react';
+import styles from './Navigation.module.css';
+
+interface NavItem {
+ title: string;
+ link: string;
+ subItems?: NavItem[];
+}
+
+const navItems: NavItem[] = [
+ {
+ title: 'μλΉμ€',
+ link: '#',
+ subItems: [
+ {
+ title: 'κΈ°λ΄ μλΉμ€',
+ link: 'https://www.koreanair.com/contents/plan-your-travel/in-flight-experience'
+ },
+ { title: 'μνλ¬Ό', link: 'https://www.koreanair.com/contents/plan-your-travel/baggage' },
+ {
+ title: 'λΌμ΄μ§',
+ link: 'https://www.koreanair.com/contents/plan-your-travel/at-the-airport/lounge'
+ }
+ ]
+ },
+ {
+ title: 'μ¬ν μ 보',
+ link: '#',
+ subItems: [
+ {
+ title: 'μ¬νμ 보ν',
+ link: 'https://www.koreanair.com/contents/booking/book-and-manage/partner-service'
+ },
+ { title: '체ν¬μΈ', link: 'https://www.koreanair.com/contents/plan-your-travel/check-in' }
+ ]
+ },
+ { title: 'κ³ κ° μ§μ', link: '#' }
+];
+
+const Navigation = () => {
+ const [isNavOpen, setIsNavOpen] = useState(false);
+
+ const toggleNav = () => {
+ setIsNavOpen((prev) => !prev);
+ };
+
+ const renderNavItems = (items: NavItem[]) => (
+
+ {items.map((item, index) => (
+ -
+ {item.title}
+ {item.subItems && renderNavItems(item.subItems)}
+
+ ))}
+
+ );
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export default Navigation;
diff --git a/src/components/PromotionModal.module.css b/src/components/PromotionModal.module.css
new file mode 100644
index 00000000..d43ddbf2
--- /dev/null
+++ b/src/components/PromotionModal.module.css
@@ -0,0 +1,60 @@
+.modal {
+ display: block;
+}
+
+.modalBackdrop {
+ position: fixed;
+ top: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 100%;
+ max-width: 480px;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 1000;
+}
+
+.modalContainer {
+ position: fixed;
+ bottom: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ background-color: white;
+ padding: 20px;
+ width: 100%;
+ max-width: 480px;
+ border-radius: 8px 8px 0 0;
+ z-index: 1001;
+}
+
+.modalContent {
+ position: relative;
+}
+
+.modalTitle {
+ margin: 16px 0;
+}
+
+.modalDescription {
+ margin-bottom: 24px;
+}
+
+.modalActionButton {
+ display: block;
+ width: 100%;
+ padding: 12px;
+ background-color: #333;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.modalCloseButton {
+ position: absolute;
+ top: -10px;
+ right: -10px;
+ background: none;
+ border: none;
+ cursor: pointer;
+}
diff --git a/src/components/PromotionModal.tsx b/src/components/PromotionModal.tsx
new file mode 100644
index 00000000..0b1fe3cf
--- /dev/null
+++ b/src/components/PromotionModal.tsx
@@ -0,0 +1,38 @@
+import { useState } from 'react';
+
+import close from '../assets/close.svg';
+
+import styles from './PromotionModal.module.css';
+
+const PromotionModal = () => {
+ const [isOpen, setIsOpen] = useState(true);
+
+ const closeModal = () => {
+ setIsOpen(false);
+ };
+
+ if (!isOpen) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
μ¬νν λ A11Y AIRLINE μ±
+
+ 체ν¬μΈ, νμΉκΆ μ μ₯, μνλ¬Ό μλ¦ΌκΉμ§
+
- μ±μΌλ‘ λμ± νΈνκ² μ¬ννμΈμ!
+
+
+
+
+
+
+ );
+};
+
+export default PromotionModal;
diff --git a/src/components/SpinButton.css b/src/components/SpinButton.css
deleted file mode 100644
index e8540b90..00000000
--- a/src/components/SpinButton.css
+++ /dev/null
@@ -1,67 +0,0 @@
-.spinButtonContainer {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 10px;
-}
-
-.spinButton {
- width: 40px;
- height: 40px;
- border: 1px solid #ddd;
- background-color: rgba(0, 0, 0, 0);
- text-align: center;
- text-decoration: none;
- display: inline-block;
- font-size: 16px;
- margin: 4px 12px;
- cursor: pointer;
- border-radius: 100%;
-}
-
-.spinButton:hover {
- background-color: #f3f3f3;
-}
-
-.spinButtonInput {
- padding: 10px;
- font-size: 16px;
- border-radius: 5px;
- text-align: center;
-}
-
-.helpIcon {
- width: 6px;
- height: 6px;
- display: flex;
- justify-content: center;
- align-items: center;
- bottom: 4px;
- margin-left: 10px;
- padding: 10px;
- background-color: rgba(0, 0, 0, 0);
- border-radius: 100%;
- border: 1px solid #ddd;
- cursor: help;
- position: relative;
-}
-
-.tooltip {
- position: absolute;
- bottom: 100%;
- left: 50%;
- transform: translateX(-50%);
- margin-bottom: 10px;
- padding: 10px;
- background-color: #000;
- color: #fff;
- border-radius: 5px;
- white-space: nowrap;
-}
-
-.spinButtonLabel {
- display: flex;
- font-weight: bold;
- padding: 0 16px;
- margin-bottom: 8px;
-}
diff --git a/src/components/SpinButton.tsx b/src/components/SpinButton.tsx
deleted file mode 100644
index 92915f97..00000000
--- a/src/components/SpinButton.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import React, { useState, MouseEvent } from "react";
-import "./SpinButton.css";
-
-const SpinButton: React.FC = () => {
- const [count, setCount] = useState
(0);
- const [isTooltipVisible, setIsTooltipVisible] = useState(false);
-
- const increment = () => {
- setCount((prevCount) => prevCount + 1);
- };
-
- const decrement = () => {
- setCount((prevCount) => prevCount - 1);
- };
-
- const toggleTooltip = (event: MouseEvent) => {
- setIsTooltipVisible(!isTooltipVisible);
- };
-
- return (
-
-
-
μΉκ° μ ν
-
-
-
- ?
- {isTooltipVisible && (
- μ΅λ μΈμμλ 3λͺ
κΉμ§ κ°λ₯ν©λλ€
- )}
-
-
-
-
-
-
-
- );
-};
-
-export default SpinButton;
diff --git a/src/components/TravelSection.module.css b/src/components/TravelSection.module.css
new file mode 100644
index 00000000..6206951c
--- /dev/null
+++ b/src/components/TravelSection.module.css
@@ -0,0 +1,89 @@
+.travelSection {
+ position: relative;
+ padding-bottom: 32px;
+}
+
+.carousel {
+ position: relative;
+ overflow: hidden;
+ padding: 0 24px;
+}
+
+.card {
+ position: relative;
+ border-radius: 4px;
+ border: 1px solid rgba(217, 217, 217, 0.5);
+ overflow: hidden;
+ display: none;
+ width: 100%;
+ height: 246px;
+ text-align: left;
+}
+
+.cardActive {
+ display: block;
+}
+
+.cardImage {
+ width: 100%;
+ height: 100%;
+ display: block;
+ object-fit: cover;
+ object-position: center;
+}
+
+.cardContent {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.9) 26%, rgba(255, 255, 255, 0) 100%);
+ padding: 24px 16px;
+}
+
+.cardTitle {
+ margin-bottom: 24px;
+}
+
+.cardType,
+.cardPrice {
+ font-size: 1rem;
+ font-style: normal;
+ font-weight: 500;
+ line-height: normal;
+ padding-bottom: 8px;
+}
+
+.navButton {
+ position: absolute;
+ top: 25%;
+ background-color: rgb(44, 44, 44);
+ color: black;
+ border: none;
+ width: 32px;
+ height: 64px;
+ font-size: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ z-index: 10;
+}
+
+.navButtonPrev {
+ left: 0;
+ border-radius: 0 40px 40px 0;
+ padding-left: 15px;
+}
+
+.navButtonNext {
+ right: 0;
+ border-radius: 40px 0 0 40px;
+ padding-right: 15px;
+}
+
+.navButtonIcon {
+ width: 24px;
+ height: 24px;
+}
diff --git a/src/components/TravelSection.tsx b/src/components/TravelSection.tsx
new file mode 100644
index 00000000..c67bc3c4
--- /dev/null
+++ b/src/components/TravelSection.tsx
@@ -0,0 +1,116 @@
+import { useState } from 'react';
+
+import travelItem01 from '../assets/travel-item-01.png';
+import travelItem02 from '../assets/travel-item-02.png';
+import travelItem03 from '../assets/travel-item-03.png';
+import chevronLeft from '../assets/chevron-left.svg';
+import chevronRight from '../assets/chevron-right.svg';
+
+import styles from './TravelSection.module.css';
+
+interface TravelOption {
+ departure: string;
+ destination: string;
+ type: string;
+ price: number;
+ image: string;
+ link: string;
+}
+
+const travelOptions: TravelOption[] = [
+ {
+ departure: 'μμΈ/μΈμ²',
+ destination: 'λλ°μ΄',
+ type: 'μΌλ°μ μ볡',
+ price: 1121600,
+ image: travelItem01,
+ link: 'https://koreanairkp.kaltour.com/ProductOverseas/OverseasList?TOURTYP=KALPAK&PKGBRA=KP&PKGARE=E5®NB1=%uC720%uB7FD®NB2=%uC911%uB3D9®TOP=1'
+ },
+ {
+ departure: 'μμΈ/μΈμ²',
+ destination: 'λ°λ₯΄μ
λ‘λ',
+ type: 'μΌλ°μ μ볡',
+ price: 1515200,
+ image: travelItem02,
+ link: 'https://koreanairkp.kaltour.com/ProductOverseas/OverseasView?pkgpnh=KP44129'
+ },
+ {
+ departure: 'μμΈ/μΈμ²',
+ destination: 'λ‘λ§',
+ type: 'μΌλ°μ μ볡',
+ price: 1415800,
+ image: travelItem03,
+ link: 'https://koreanairkp.kaltour.com/ProductOverseas/OverseasView?pkgpnh=KP41216'
+ }
+];
+
+const TravelSection = () => {
+ const [currentIndex, setCurrentIndex] = useState(0);
+
+ const getCardInfo = (index: number) => {
+ const option = travelOptions[index];
+
+ return `${travelOptions.length}κ°μ μ¬ν μν μ€ ${index + 1}λ²μ§Έ μν.
+ ${option.departure} μΆλ° ${option.destination} λμ°©,
+ ${option.type},
+ κ°κ²© ${option.price}μ. μ ννλ©΄ μμ½ νμ΄μ§λ‘ μ΄λν©λλ€.`;
+ };
+
+ const nextTravel = () => {
+ const newIndex = (currentIndex + 1) % travelOptions.length;
+ setCurrentIndex(newIndex);
+ };
+
+ const prevTravel = () => {
+ const newIndex = (currentIndex - 1 + travelOptions.length) % travelOptions.length;
+ setCurrentIndex(newIndex);
+ };
+
+ const handleCardClick = (link: string) => {
+ window.open(link, '_blank', 'noopener,noreferrer');
+ };
+
+ return (
+
+
+
+
+ {travelOptions.map((option, index) => (
+
+ ))}
+
+
+
+
+ );
+};
+
+export default TravelSection;
diff --git a/src/image.d.ts b/src/image.d.ts
new file mode 100644
index 00000000..cdb2b1a9
--- /dev/null
+++ b/src/image.d.ts
@@ -0,0 +1,4 @@
+declare module '*.svg' {
+ const content: string;
+ export default content;
+}
diff --git a/src/images/airplane.png b/src/images/airplane.png
deleted file mode 100644
index f1a06f23..00000000
Binary files a/src/images/airplane.png and /dev/null differ
diff --git a/src/images/carousel_mobile_sample.png b/src/images/carousel_mobile_sample.png
deleted file mode 100644
index 2353660e..00000000
Binary files a/src/images/carousel_mobile_sample.png and /dev/null differ
diff --git a/src/images/navigation_sample.png b/src/images/navigation_sample.png
deleted file mode 100644
index 03227a7e..00000000
Binary files a/src/images/navigation_sample.png and /dev/null differ
diff --git a/src/images/spin_button_sample.png b/src/images/spin_button_sample.png
deleted file mode 100644
index fd916fbc..00000000
Binary files a/src/images/spin_button_sample.png and /dev/null differ
diff --git a/src/index.css b/src/index.css
index ec2585e8..8710a346 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,13 +1,119 @@
+html,
+body,
+div,
+span,
+applet,
+object,
+iframe,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+blockquote,
+pre,
+a,
+abbr,
+acronym,
+address,
+big,
+cite,
+code,
+del,
+dfn,
+em,
+img,
+ins,
+kbd,
+q,
+s,
+samp,
+small,
+strike,
+strong,
+sub,
+sup,
+tt,
+var,
+b,
+u,
+i,
+center,
+dl,
+dt,
+dd,
+ol,
+ul,
+li,
+fieldset,
+form,
+label,
+legend,
+table,
+caption,
+tbody,
+tfoot,
+thead,
+tr,
+th,
+td,
+article,
+aside,
+canvas,
+details,
+embed,
+figure,
+figcaption,
+footer,
+header,
+hgroup,
+menu,
+nav,
+output,
+ruby,
+section,
+summary,
+time,
+mark,
+audio,
+video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+ box-sizing: border-box;
+}
+/* HTML5 display-role reset for older browsers */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol,
+ul {
+ list-style: none;
+}
+button {
+ color: #000;
+}
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;
+ padding: 0;
+ font-family: Arial, sans-serif;
+ background-color: #f5f5f5;
}
diff --git a/src/index.tsx b/src/index.tsx
deleted file mode 100644
index cfd77849..00000000
--- a/src/index.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from "react";
-import ReactDOM from "react-dom/client";
-import "./index.css";
-import App from "./App";
-
-const root = ReactDOM.createRoot(
- document.getElementById("root") as HTMLElement
-);
-root.render(
-
-
-
-);
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 00000000..c50b7b63
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,14 @@
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+
+import './Typography.css';
+import './Accessibility.css';
+import './index.css';
+
+import App from './App.tsx';
+
+createRoot(document.getElementById('root')!).render(
+
+
+
+);
diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts
deleted file mode 100644
index 6431bc5f..00000000
--- a/src/react-app-env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-///
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 00000000..11f02fe2
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/tsconfig.app.json b/tsconfig.app.json
new file mode 100644
index 00000000..f0a23505
--- /dev/null
+++ b/tsconfig.app.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"]
+}
diff --git a/tsconfig.json b/tsconfig.json
index a273b0cf..d32ff682 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,26 +1,4 @@
{
- "compilerOptions": {
- "target": "es5",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
- "allowJs": true,
- "skipLibCheck": true,
- "esModuleInterop": true,
- "allowSyntheticDefaultImports": true,
- "strict": true,
- "forceConsistentCasingInFileNames": true,
- "noFallthroughCasesInSwitch": true,
- "module": "esnext",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "react-jsx"
- },
- "include": [
- "src"
- ]
+ "files": [],
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 00000000..0d3d7144
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 00000000..43789d75
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ base: '/a11y-airline/',
+ server: {
+ host: '0.0.0.0'
+ }
+});