diff --git a/css/auth.css b/css/auth.css index 3897a973..18a7e050 100644 --- a/css/auth.css +++ b/css/auth.css @@ -69,40 +69,6 @@ height: 24px; } -/* 버튼 스타일 */ -.button { - background-color: var(--primary-100); - color: #ffffff; - display: inline-flex; - align-items: center; - justify-content: center; -} - -.button:hover { - background-color: var(--primary-200); -} - -.button:focus { - background-color: var(--primary-300); -} - -.button:disabled { - background-color: var(--gray-400); - cursor: default; - pointer-events: none; -} - -.pill-button { - font-size: 20px; - font-weight: 700; - border-radius: 999px; - padding: 16px 126px; -} - -.full-width { - width: 100%; -} - /* 소셜 로그인 */ .social-login-container { background-color: #e6f2ff; diff --git a/css/banner.css b/css/banner.css deleted file mode 100644 index 25aae4a9..00000000 --- a/css/banner.css +++ /dev/null @@ -1,18 +0,0 @@ -.banner { - background-color: #cfe5ff; - height: 540px; - display: flex; - align-items: center; - background-repeat: no-repeat; - background-position: 80% bottom; - background-size: 55%; - transition: background-size 0.3s ease, padding 0.3s ease; -} - -#hero { - background-image: url("../public/images/img_home_top.png"); -} - -#bottomBanner { - background-image: url("../public/images/img_home_bottom.png"); -} diff --git a/css/buttons.css b/css/buttons.css index e664539e..22a1d55e 100644 --- a/css/buttons.css +++ b/css/buttons.css @@ -1,21 +1,4 @@ -.login-btn { - background-color: var(--primary-100); - color: #ffffff; - width: 128px; - height: 48px; - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 16px; - font-weight: 600; - border-radius: 8px; - margin-right: 200px; -} - -.login-btn:hover { - background-color: var(--primary-200); -} - +/* 공통 버튼 스타일 */ .button { background-color: var(--primary-100); color: #ffffff; @@ -32,6 +15,13 @@ background-color: var(--primary-300); } +.button:disabled { + background-color: var(--gray-400); + cursor: default; + pointer-events: none; +} + +/* 둥근 버튼 스타일 */ .pill-button { font-size: 20px; font-weight: 700; @@ -39,6 +29,31 @@ padding: 16px 124px; } +/* 로그인 버튼 스타일 */ +.login-btn { + background-color: var(--primary-100); + color: #ffffff; + width: 128px; + height: 48px; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 16px; + font-weight: 600; + border-radius: 8px; + margin-right: 200px; +} + +.login-btn:hover { + background-color: var(--primary-200); +} + +/* 배너 내 버튼 스타일 */ .banner .pill-button { margin-top: 32px; } + +/* 전체 너비 버튼 스타일 */ +.full-width { + width: 100%; +} diff --git a/css/feature-elements.css b/css/feature-elements.css deleted file mode 100644 index 711f2361..00000000 --- a/css/feature-elements.css +++ /dev/null @@ -1,7 +0,0 @@ -.feature-tag { - color: var(--primary-100); - font-size: 18px; - line-height: 25px; - font-weight: 700; - margin-bottom: 12px; -} diff --git a/css/responsive.css b/css/responsive.css index 5a3f8235..23beb247 100644 --- a/css/responsive.css +++ b/css/responsive.css @@ -1,4 +1,3 @@ -/* 대형 데스크탑 (1920px 이상) */ @media (min-width: 1920px) { .header, .main-banner, @@ -10,7 +9,6 @@ max-width: 1520px; } - /* 배너 배경 이미지 조정 */ #hero { background-size: 45%; background-position: 85% bottom; @@ -21,13 +19,11 @@ background-position: 85% bottom; } - /* 배너 컨테이너 최대 너비 제한 */ .wrapper { max-width: 1520px; } } -/* 중형-대형 데스크탑 (1600px 이하) */ @media (max-width: 1600px) { .logo { margin-left: 100px; @@ -47,7 +43,6 @@ } } -/* PC (1200px 이상) */ @media (min-width: 1200px) { .logo { margin-left: 200px; @@ -63,12 +58,16 @@ } } -/* 태블릿 (768px 이상 ~ 1199px 이하) */ @media (min-width: 768px) and (max-width: 1199px) { .logo { margin-left: 24px; } + h2 { + font-size: 32px; + line-height: 44px; + } + .login-btn { margin-right: 24px; } @@ -152,7 +151,6 @@ } .feature img { - /* width: 85%; */ width: auto; padding: 0 24px; margin: 0 auto; @@ -164,8 +162,7 @@ } } -/* 모바일 (375px 이상 ~ 767px 이하) */ -@media (min-width: 375px) and (max-width: 767px) { +@media (max-width: 767px) { .header { height: 60px; } @@ -174,6 +171,16 @@ margin-left: 16px; } + h1 { + font-size: 32px; + line-height: 44px; + } + + h2 { + font-size: 24px; + line-height: 34px; + } + .login-btn { margin-right: 16px; font-size: 14px; diff --git a/css/sections.css b/css/sections.css index aa9616d2..00310b46 100644 --- a/css/sections.css +++ b/css/sections.css @@ -7,3 +7,22 @@ position: relative; overflow: hidden; } + +.banner { + background-color: #cfe5ff; + height: 540px; + display: flex; + align-items: center; + background-repeat: no-repeat; + background-position: 80% bottom; + background-size: 55%; + transition: background-size 0.3s ease, padding 0.3s ease; +} + +#hero { + background-image: url("../public/images/img_home_top.png"); +} + +#bottomBanner { + background-image: url("../public/images/img_home_bottom.png"); +} diff --git a/css/typography.css b/css/typography.css index 8d64ea49..cfdb7700 100644 --- a/css/typography.css +++ b/css/typography.css @@ -12,6 +12,14 @@ h2 { letter-spacing: 0.02em; } +.feature-tag { + color: var(--primary-100); + font-size: 18px; + line-height: 25px; + font-weight: 700; + margin-bottom: 12px; +} + .feature-description { font-size: 24px; font-weight: 500; diff --git a/css/utilities.css b/css/utilities.css new file mode 100644 index 00000000..f2e86625 --- /dev/null +++ b/css/utilities.css @@ -0,0 +1,166 @@ +/* 색상 */ +.text-error { + color: var(--error-red); +} + +.text-primary { + color: var(--primary-100); +} + +.text-primary-dark { + color: var(--primary-200); +} + +.text-primary-darker { + color: var(--primary-300); +} + +.text-gray-50 { + color: var(--gray-50); +} + +.text-gray-100 { + color: var(--gray-100); +} + +.text-gray-200 { + color: var(--gray-200); +} + +.text-gray-400 { + color: var(--gray-400); +} + +.text-gray-500 { + color: var(--gray-500); +} + +.text-gray-600 { + color: var(--gray-600); +} + +.text-gray-700 { + color: var(--gray-700); +} + +.text-gray-800 { + color: var(--gray-800); +} + +.text-gray-900 { + color: var(--gray-900); +} + +/* 폰트 크기 */ +.text-xs { + font-size: 12px; +} + +.text-sm { + font-size: 14px; +} + +.text-base { + font-size: 16px; +} + +.text-lg { + font-size: 18px; +} + +.text-xl { + font-size: 20px; +} + +/* 폰트 무게 */ +.font-normal { + font-weight: 400; +} + +.font-medium { + font-weight: 500; +} + +.font-semibold { + font-weight: 600; +} + +.font-bold { + font-weight: 700; +} + +/* 여백 */ +.mt-1 { + margin-top: 4px; +} + +.mt-2 { + margin-top: 8px; +} + +.mt-3 { + margin-top: 12px; +} + +.mt-4 { + margin-top: 16px; +} + +.mb-1 { + margin-bottom: 4px; +} + +.mb-2 { + margin-bottom: 8px; +} + +.mb-3 { + margin-bottom: 12px; +} + +.mb-4 { + margin-bottom: 16px; +} + +/* 디스플레이 */ +.block { + display: block; +} + +.flex { + display: flex; +} + +.hidden { + display: none; +} + +/* 테두리 */ +.border { + border-width: 1px; + border-style: solid; +} + +.border-red { + border: 1px solid var(--error-red); +} + +.rounded { + border-radius: 12px; +} + +.rounded-full { + border-radius: 999px; +} + +/* 입력창 스타일 */ +.input-invalid { + border-color: var(--error-red); + outline-color: var(--error-red); +} + +/* 버튼 상태 */ +.btn-disabled { + background-color: var(--gray-400); + cursor: not-allowed; +} diff --git a/index.html b/index.html index 3b9cd9b6..a2b0d83c 100644 --- a/index.html +++ b/index.html @@ -26,11 +26,9 @@ - - diff --git a/login.html b/login.html index 288e5a41..f7432dec 100644 --- a/login.html +++ b/login.html @@ -6,8 +6,8 @@ + - @@ -80,5 +80,7 @@

간편 로그인하기

판다마켓이 처음이신가요? 회원가입 + + diff --git a/public/logos/panda-market.png b/public/logos/panda-market.png index 7db8527a..427e1ccf 100644 Binary files a/public/logos/panda-market.png and b/public/logos/panda-market.png differ diff --git a/public/logos/panda-market2x.png b/public/logos/panda-market2x.png index 89751ba8..d5b358d6 100644 Binary files a/public/logos/panda-market2x.png and b/public/logos/panda-market2x.png differ diff --git a/scripts/login.js b/scripts/login.js new file mode 100644 index 00000000..991822d8 --- /dev/null +++ b/scripts/login.js @@ -0,0 +1,71 @@ +document.addEventListener("DOMContentLoaded", function () { + const emailInput = document.getElementById("email"); + const passwordInput = document.getElementById("password"); + const loginForm = document.querySelector(".auth-form"); + const loginButton = loginForm.querySelector('button[type="submit"]'); + const togglePasswordButton = document.querySelector(".toggle-password"); + + emailInput.addEventListener("blur", function () { + if (!isNotEmpty(this.value)) { + showError(this, "이메일을 입력해주세요."); + } else if (!isValidEmail(this.value)) { + showError(this, "잘못된 이메일 형식입니다."); + } else { + removeError(this); + } + validateForm(); + }); + + passwordInput.addEventListener("blur", function () { + if (!isNotEmpty(this.value)) { + showError(this, "비밀번호를 입력해주세요."); + } else if (!isValidPasswordLength(this.value)) { + showError(this, "비밀번호를 8자 이상 입력해주세요."); + } else { + removeError(this); + } + validateForm(); + }); + + // 입력 변화 감지 -> 폼 유효성 검사 + emailInput.addEventListener("input", validateForm); + passwordInput.addEventListener("input", validateForm); + + function validateForm() { + const isEmailValid = + isNotEmpty(emailInput.value) && isValidEmail(emailInput.value); + const isPasswordValid = + isNotEmpty(passwordInput.value) && + isValidPasswordLength(passwordInput.value); + + const hasErrors = + document.querySelectorAll('[class*="text-error"]').length > 0; + + setButtonState(loginButton, isEmailValid && isPasswordValid && !hasErrors); + } + + loginForm.addEventListener("submit", function (e) { + e.preventDefault(); + + if (!loginButton.disabled) { + window.location.href = "items.html"; + } + }); + + togglePasswordButton.addEventListener("click", function () { + const type = + passwordInput.getAttribute("type") === "password" ? "text" : "password"; + passwordInput.setAttribute("type", type); + + const img = this.querySelector("img"); + if (type === "text") { + img.src = "public/icons/ic_visibility_on.png"; + img.alt = "비밀번호 표시"; + } else { + img.src = "public/icons/ic_visibility_off.png"; + img.alt = "비밀번호 숨김"; + } + }); + + validateForm(); +}); diff --git a/scripts/signup.js b/scripts/signup.js new file mode 100644 index 00000000..1e0bd488 --- /dev/null +++ b/scripts/signup.js @@ -0,0 +1,114 @@ +document.addEventListener("DOMContentLoaded", function () { + const emailInput = document.getElementById("email"); + const nicknameInput = document.getElementById("nickname"); + const passwordInput = document.getElementById("password"); + const passwordConfirmInput = document.getElementById("passwordConfirmation"); + const signupForm = document.querySelector(".auth-form"); + const signupButton = signupForm.querySelector('button[type="submit"]'); + const togglePasswordButtons = document.querySelectorAll(".toggle-password"); + + emailInput.addEventListener("blur", function () { + if (!isNotEmpty(this.value)) { + showError(this, "이메일을 입력해주세요."); + } else if (!isValidEmail(this.value)) { + showError(this, "잘못된 이메일 형식입니다."); + } else { + removeError(this); + } + validateForm(); + }); + + nicknameInput.addEventListener("blur", function () { + if (!isNotEmpty(this.value)) { + showError(this, "닉네임을 입력해주세요."); + } else { + removeError(this); + } + validateForm(); + }); + + passwordInput.addEventListener("blur", function () { + if (!isNotEmpty(this.value)) { + showError(this, "비밀번호를 입력해주세요."); + } else if (!isValidPasswordLength(this.value)) { + showError(this, "비밀번호를 8자 이상 입력해주세요."); + } else { + removeError(this); + } + validatePasswordMatch(); + validateForm(); + }); + + passwordConfirmInput.addEventListener("blur", function () { + validatePasswordMatch(); + validateForm(); + }); + + function validatePasswordMatch() { + if (isNotEmpty(passwordConfirmInput.value)) { + if (!isMatching(passwordInput.value, passwordConfirmInput.value)) { + showError(passwordConfirmInput, "비밀번호가 일치하지 않습니다."); + } else { + removeError(passwordConfirmInput); + } + } + } + + // 입력 변화 감지 -> 폼 유효성 검사 + emailInput.addEventListener("input", validateForm); + nicknameInput.addEventListener("input", validateForm); + passwordInput.addEventListener("input", validateForm); + passwordConfirmInput.addEventListener("input", validateForm); + + function validateForm() { + const isEmailValid = + isNotEmpty(emailInput.value) && isValidEmail(emailInput.value); + const isNicknameValid = isNotEmpty(nicknameInput.value); + const isPasswordValid = + isNotEmpty(passwordInput.value) && + isValidPasswordLength(passwordInput.value); + const isPasswordConfirmValid = + isNotEmpty(passwordConfirmInput.value) && + isMatching(passwordInput.value, passwordConfirmInput.value); + + const hasErrors = + document.querySelectorAll('[class*="text-error"]').length > 0; + + setButtonState( + signupButton, + isEmailValid && + isNicknameValid && + isPasswordValid && + isPasswordConfirmValid && + !hasErrors + ); + } + + signupForm.addEventListener("submit", function (e) { + e.preventDefault(); + + if (!signupButton.disabled) { + window.location.href = "login.html"; + } + }); + + togglePasswordButtons.forEach((button) => { + button.addEventListener("click", function () { + const input = this.parentElement.querySelector("input"); + const type = + input.getAttribute("type") === "password" ? "text" : "password"; + input.setAttribute("type", type); + + const img = this.querySelector("img"); + if (type === "text") { + img.src = "public/icons/ic_visibility_on.png"; + img.alt = "비밀번호 표시"; + } else { + img.src = "public/icons/ic_visibility_off.png"; + img.alt = "비밀번호 숨김"; + } + }); + }); + + validateForm(); +}); diff --git a/scripts/validation.js b/scripts/validation.js new file mode 100644 index 00000000..2a989b79 --- /dev/null +++ b/scripts/validation.js @@ -0,0 +1,58 @@ +function isValidEmail(email) { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); +} + +function isValidPasswordLength(password, minLength = 8) { + return password.length >= minLength; +} + +function isNotEmpty(value) { + return value.trim() !== ""; +} + +function isMatching(value1, value2) { + return value1 === value2; +} + +function showError(inputElement, message) { + const container = inputElement.closest(".input-item"); + + const existingError = container.querySelector('[class*="text-error"]'); + if (existingError) { + existingError.remove(); + } + + inputElement.classList.add("input-invalid"); + + const errorSpan = document.createElement("span"); + errorSpan.className = "text-error text-sm block mt-2 font-semibold"; + errorSpan.textContent = message; + + if (inputElement.parentElement.classList.contains("input-wrapper")) { + inputElement.parentElement.parentElement.appendChild(errorSpan); + } else { + container.appendChild(errorSpan); + } +} + +function removeError(inputElement) { + const container = inputElement.closest(".input-item"); + const errorElement = container.querySelector('[class*="text-error"]'); + + if (errorElement) { + errorElement.remove(); + } + + inputElement.classList.remove("input-invalid"); +} + +function setButtonState(buttonElement, isEnabled) { + if (isEnabled) { + buttonElement.disabled = false; + buttonElement.classList.remove("btn-disabled"); + } else { + buttonElement.disabled = true; + buttonElement.classList.add("btn-disabled"); + } +} diff --git a/signup.html b/signup.html index 4aa829cb..fb1c2ea2 100644 --- a/signup.html +++ b/signup.html @@ -6,8 +6,8 @@ + - @@ -106,5 +106,7 @@

간편 로그인하기

이미 회원이신가요? 로그인 + +