login content edit

This commit is contained in:
unknown 2025-06-20 14:20:30 +09:00
parent 501c7ef3a4
commit 8b4af741bb
2 changed files with 283 additions and 235 deletions

View File

@ -135,36 +135,10 @@
required required
density="compact" density="compact"
class="mb-3" class="mb-3"
item-title="title"
item-value="value"
@update:model-value="handleTargetTypeChange" @update:model-value="handleTargetTypeChange"
> />
<template v-slot:item="{ props, item }">
<v-list-item
v-bind="props"
:disabled="selectedType === 'poster' && item.value !== 'menu'"
:class="{ 'v-list-item--disabled': selectedType === 'poster' && item.value !== 'menu' }"
@click="handleTargetItemClick(item.value, $event)"
>
<template v-slot:prepend>
<v-icon
:color="(selectedType === 'poster' && item.value !== 'menu') ? 'grey-lighten-2' : 'primary'"
>
mdi-target
</v-icon>
</template>
<v-list-item-title
:class="{ 'text-grey-lighten-1': selectedType === 'poster' && item.value !== 'menu' }"
>
{{ item.title }}
</v-list-item-title>
<v-list-item-subtitle
v-if="selectedType === 'poster' && item.value !== 'menu'"
class="text-caption text-grey-lighten-1"
>
현재 메뉴만 지원
</v-list-item-subtitle>
</v-list-item>
</template>
</v-select>
<!-- 이벤트명 (SNS에서 이벤트 선택 ) --> <!-- 이벤트명 (SNS에서 이벤트 선택 ) -->
<v-text-field <v-text-field

View File

@ -1,52 +1,55 @@
//* src/views/LoginView.vue - //* src/views/LoginView.vue
<template> <template>
<v-container fluid class="login-container"> <v-container fluid class="login-container">
<!-- 배경 패턴 -->
<div class="login-background"> <div class="login-background">
<!-- 배경 패턴 요소들 -->
<div class="bg-pattern pattern-1"></div> <div class="bg-pattern pattern-1"></div>
<div class="bg-pattern pattern-2"></div> <div class="bg-pattern pattern-2"></div>
<div class="bg-pattern pattern-3"></div> <div class="bg-pattern pattern-3"></div>
<div class="bg-pattern pattern-4"></div> <div class="bg-pattern pattern-4"></div>
</div> </div>
<v-row justify="center" align="center" style="min-height: 100vh"> <v-row justify="center" align="center" class="main-row">
<v-col cols="12" sm="8" md="6" lg="4" xl="3"> <v-col cols="12" sm="10" md="8" lg="6" xl="4">
<!-- 브랜드 로고 제목 --> <!-- 브랜드 섹션 -->
<div class="text-center brand-section"> <div class="brand-section">
<div class="logo-wrapper"> <div class="logo-wrapper">
<v-img <v-img
src="/images/logo192.png" src="/images/logo192.png"
alt="AI 마케팅 로고" alt="AI Marketing Logo"
max-width="100" max-width="80"
class="mx-auto logo-image" class="logo-image mx-auto"
/> />
<div class="logo-glow"></div> <div class="logo-glow"></div>
</div> </div>
<h1 class="brand-title">
<span class="ai-text">AI</span> <h1 class="brand-title">AI 마케팅</h1>
<span class="marketing-text">마케팅</span> <p class="brand-subtitle">소상공인을 위한 스마트 마케팅 솔루션</p>
</h1>
<p class="brand-subtitle">
<v-icon class="subtitle-icon">mdi-rocket-launch</v-icon>
소상공인을 위한 스마트 마케팅 솔루션
</p>
</div> </div>
<!-- 로그인 카드 --> <!-- 로그인 카드 -->
<v-card class="login-card" elevation="0"> <v-card class="login-card" elevation="0">
<div class="card-header">
<h2 class="login-title">로그인</h2>
<p class="login-subtitle">계정에 로그인하여 시작하세요</p>
</div>
<v-card-text class="card-content"> <v-card-text class="card-content">
<v-form v-model="isFormValid" ref="loginForm" @submit.prevent="handleLogin"> <v-form v-model="isFormValid" ref="loginForm" @submit.prevent="handleLogin">
<!-- 로그인 제목과 테스트 계정 버튼 -->
<div class="login-header">
<h2 class="login-title">로그인</h2>
<v-btn
size="small"
variant="text"
color="info"
@click="showTestAccountHint"
class="hint-btn-header"
>
💡 테스트 계정
</v-btn>
</div>
<!-- 아이디 입력 --> <!-- 아이디 입력 -->
<div class="input-group"> <div class="input-group">
<label class="input-label">아이디</label> <label class="input-label">아이디</label>
<v-text-field <v-text-field
v-model="credentials.username" v-model="credentials.username"
placeholder="아이디를 입력하세요"
prepend-inner-icon="mdi-account-outline" prepend-inner-icon="mdi-account-outline"
variant="outlined" variant="outlined"
:rules="usernameRules" :rules="usernameRules"
@ -63,7 +66,6 @@
<label class="input-label">비밀번호</label> <label class="input-label">비밀번호</label>
<v-text-field <v-text-field
v-model="credentials.password" v-model="credentials.password"
placeholder="비밀번호를 입력하세요"
prepend-inner-icon="mdi-lock-outline" prepend-inner-icon="mdi-lock-outline"
:type="showPassword ? 'text' : 'password'" :type="showPassword ? 'text' : 'password'"
:append-inner-icon="showPassword ? 'mdi-eye-outline' : 'mdi-eye-off-outline'" :append-inner-icon="showPassword ? 'mdi-eye-outline' : 'mdi-eye-off-outline'"
@ -148,7 +150,7 @@
비밀번호 찾기 비밀번호 찾기
</v-card-title> </v-card-title>
<v-card-text class="dialog-content"> <v-card-text class="dialog-content">
<p class="dialog-description">등록하신 이메일 주소를 입력해주세요.</p> <p class="dialog-text">등록하신 이메일 주소를 입력해주세요.</p>
<v-text-field <v-text-field
v-model="forgotEmail" v-model="forgotEmail"
label="이메일" label="이메일"
@ -163,7 +165,8 @@
<v-btn variant="text" @click="showForgotPassword = false" class="cancel-btn"> <v-btn variant="text" @click="showForgotPassword = false" class="cancel-btn">
취소 취소
</v-btn> </v-btn>
<v-btn @click="handleForgotPassword" class="send-btn"> <v-btn @click="handleForgotPassword" class="submit-btn">
<v-icon start>mdi-send</v-icon>
전송 전송
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@ -174,18 +177,22 @@
<v-dialog v-model="showSignup" max-width="600" persistent> <v-dialog v-model="showSignup" max-width="600" persistent>
<v-card class="signup-card"> <v-card class="signup-card">
<v-card-title class="dialog-title"> <v-card-title class="dialog-title">
<div class="title-section">
<v-icon class="title-icon">mdi-account-plus-outline</v-icon> <v-icon class="title-icon">mdi-account-plus-outline</v-icon>
회원가입 <span>회원가입</span>
<v-spacer /> </div>
<v-btn icon variant="text" @click="closeSignupDialog" class="close-btn"> <v-btn
icon
variant="text"
@click="closeSignupDialog"
class="close-btn"
>
<v-icon>mdi-close</v-icon> <v-icon>mdi-close</v-icon>
</v-btn> </v-btn>
</v-card-title> </v-card-title>
<v-card-text class="dialog-content"> <v-card-text class="dialog-content">
<p class="welcome-text"> <p class="welcome-text">AI 마케팅 서비스에 오신 것을 환영합니다!</p>
<v-icon class="welcome-icon">mdi-hand-wave</v-icon>
AI 마케팅 서비스에 오신 것을 환영합니다!
</p>
<!-- 회원가입 --> <!-- 회원가입 -->
<v-form v-model="isSignupFormValid" ref="signupForm"> <v-form v-model="isSignupFormValid" ref="signupForm">
@ -260,14 +267,14 @@
<!-- 사업자 번호 --> <!-- 사업자 번호 -->
<div class="input-group"> <div class="input-group">
<label class="input-label">사업자 번호 <span class="optional">(선택사항)</span></label> <label class="input-label">사업자번호 (선택사항)</label>
<v-text-field <v-text-field
v-model="signupData.businessNumber" v-model="signupData.businessNumber"
placeholder="1234567890" placeholder="10자리 숫자를 입력하세요"
variant="outlined" variant="outlined"
:rules="businessNumberRules" :rules="businessNumberRules"
class="signup-input" class="signup-input"
hint="10자리 숫자로 입력해주세요" hint="사업자등록증의 10자리 숫자를 입력해주세요"
persistent-hint persistent-hint
/> />
</div> </div>
@ -281,7 +288,6 @@
closable closable
@click:close="signupError = ''" @click:close="signupError = ''"
> >
<v-icon class="alert-icon">mdi-alert-circle-outline</v-icon>
{{ signupError }} {{ signupError }}
</v-alert> </v-alert>
@ -291,14 +297,14 @@
variant="tonal" variant="tonal"
class="signup-alert" class="signup-alert"
> >
<v-icon class="alert-icon">mdi-check-circle-outline</v-icon>
{{ signupSuccess }} {{ signupSuccess }}
</v-alert> </v-alert>
</v-form> </v-form>
</v-card-text> </v-card-text>
<v-card-actions class="dialog-actions"> <v-card-actions class="dialog-actions">
<v-spacer /> <v-spacer />
<v-btn variant="text" @click="closeSignupDialog" class="cancel-btn"> <v-btn @click="closeSignupDialog" class="cancel-btn">
취소 취소
</v-btn> </v-btn>
<v-btn <v-btn
@ -313,6 +319,32 @@
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-dialog> </v-dialog>
<!-- 테스트 계정 스낵바 - 가운데 위치 -->
<v-snackbar
v-model="showTestSnackbar"
:timeout="4000"
color="info"
location="center"
class="test-snackbar-center"
>
<div class="snackbar-content-center">
<v-icon start>mdi-information</v-icon>
<div class="test-info">
<div><strong>테스트 계정 정보</strong></div>
<div>아이디: testuser1</div>
<div>비밀번호: password123!</div>
</div>
</div>
<template #actions>
<v-btn variant="text" @click="fillTestAccountFromSnackbar" class="snackbar-btn">
자동 입력
</v-btn>
<v-btn icon @click="showTestSnackbar = false" class="snackbar-close">
<v-icon>mdi-close</v-icon>
</v-btn>
</template>
</v-snackbar>
</v-container> </v-container>
</template> </template>
@ -336,6 +368,9 @@ const loginError = ref('')
const showForgotPassword = ref(false) const showForgotPassword = ref(false)
const forgotEmail = ref('') const forgotEmail = ref('')
//
const showTestSnackbar = ref(false)
// //
const showSignup = ref(false) const showSignup = ref(false)
const signupForm = ref(null) const signupForm = ref(null)
@ -356,12 +391,12 @@ const credentials = ref({
// - // -
const signupData = ref({ const signupData = ref({
userId: '', // 4 userId: '',
password: '', // password: '',
passwordConfirm: '', passwordConfirm: '',
name: '', name: '',
email: '', email: '',
businessNumber: '', // 10 businessNumber: '',
}) })
const fieldErrors = ref({ const fieldErrors = ref({
@ -391,16 +426,15 @@ const passwordRules = [
// - // -
const signupUserIdRules = [ const signupUserIdRules = [
(v) => !!v || '아이디를 입력해주세요', (v) => !!v || '아이디를 입력해주세요',
(v) => (v && v.length >= 4) || '아이디는 4자 이상이어야 합니다', // : 4 (v) => (v && v.length >= 4) || '아이디는 4자 이상이어야 합니다',
(v) => (v && v.length <= 20) || '아이디는 20자 이하여야 합니다', (v) => (v && v.length <= 20) || '아이디는 20자 이하여야 합니다',
(v) => /^[a-zA-Z0-9]+$/.test(v) || '아이디는 영문과 숫자만 사용 가능합니다', // : _ (v) => /^[a-zA-Z0-9]+$/.test(v) || '아이디는 영문과 숫자만 사용 가능합니다',
] ]
const signupPasswordRules = [ const signupPasswordRules = [
(v) => !!v || '비밀번호를 입력해주세요', (v) => !!v || '비밀번호를 입력해주세요',
(v) => (v && v.length >= 8) || '비밀번호는 8자 이상이어야 합니다', (v) => (v && v.length >= 8) || '비밀번호는 8자 이상이어야 합니다',
(v) => (v && v.length <= 20) || '비밀번호는 20자 이하여야 합니다', (v) => (v && v.length <= 20) || '비밀번호는 20자 이하여야 합니다',
// : , , (@$!%*?&)
(v) => /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/.test(v) || (v) => /^(?=.*[a-zA-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/.test(v) ||
'영문, 숫자, 특수문자(@$!%*?&)를 모두 포함해야 합니다', '영문, 숫자, 특수문자(@$!%*?&)를 모두 포함해야 합니다',
] ]
@ -413,20 +447,32 @@ const passwordConfirmRules = [
const nameRules = [ const nameRules = [
(v) => !!v || '이름을 입력해주세요', (v) => !!v || '이름을 입력해주세요',
(v) => (v && v.length >= 2) || '이름은 2자 이상이어야 합니다', (v) => (v && v.length >= 2) || '이름은 2자 이상이어야 합니다',
(v) => (v && v.length <= 50) || '이름은 50자 이하여야 합니다', // : 50 (v) => (v && v.length <= 50) || '이름은 50자 이하여야 합니다',
] ]
const emailRules = [ const emailRules = [
(v) => !!v || '이메일을 입력해주세요', (v) => !!v || '이메일을 입력해주세요',
(v) => /.+@.+\..+/.test(v) || ' ', (v) => /.+@.+\..+/.test(v) || ' ',
(v) => (v && v.length <= 100) || '이메일은 100자 이하여야 합니다', // : 100 (v) => (v && v.length <= 100) || '이메일은 100자 이하여야 합니다',
] ]
const businessNumberRules = [ const businessNumberRules = [
// : 10 ( )
(v) => !v || (v.length === 10 && /^\d{10}$/.test(v)) || '사업자번호는 10자리 숫자여야 합니다', (v) => !v || (v.length === 10 && /^\d{10}$/.test(v)) || '사업자번호는 10자리 숫자여야 합니다',
] ]
//
const showTestAccountHint = () => {
showTestSnackbar.value = true
}
const fillTestAccountFromSnackbar = () => {
credentials.value.username = 'testuser1'
credentials.value.password = 'password123!'
loginError.value = ''
showTestSnackbar.value = false
appStore.showSnackbar('테스트 계정이 입력되었습니다', 'success')
}
// //
const handleLogin = async () => { const handleLogin = async () => {
if (!isFormValid.value) return if (!isFormValid.value) return
@ -498,29 +544,22 @@ const handleSignup = async () => {
if (response.data.status === 200 || response.data.message?.includes('완료')) { if (response.data.status === 200 || response.data.message?.includes('완료')) {
signupSuccess.value = '회원가입이 완료되었습니다!' signupSuccess.value = '회원가입이 완료되었습니다!'
appStore.showSnackbar('회원가입이 완료되었습니다!', 'success')
//
setTimeout(() => { setTimeout(() => {
closeSignupDialog() closeSignupDialog()
appStore.showSnackbar('회원가입이 완료되었습니다. 로그인해주세요.', 'success') }, 2000)
}, 1500)
} else { } else {
throw new Error(response.data.message || '회원가입에 실패했습니다.') signupError.value = response.data.message || '회원가입에 실패했습니다'
} }
} catch (error) { } catch (error) {
console.error('회원가입 실패:', error) console.error('회원가입 에러:', error)
if (error.response && error.response.status === 400) { if (error.response?.status === 400) {
// validation
const errorData = error.response.data const errorData = error.response.data
if (errorData.errors && Array.isArray(errorData.errors)) {
if (errorData.data && typeof errorData.data === 'object') { const errorMessages = errorData.errors.map(err => err.message || err).join(', ')
// signupError.value = errorMessages
const errorMessages = []
for (const [field, message] of Object.entries(errorData.data)) {
errorMessages.push(`${field}: ${message}`)
}
signupError.value = errorMessages.join('\n') || '입력값을 확인해주세요.'
} else { } else {
signupError.value = errorData.message || '입력값 검증에 실패했습니다.' signupError.value = errorData.message || '입력값 검증에 실패했습니다.'
} }
@ -536,13 +575,13 @@ const handleSignup = async () => {
const closeSignupDialog = () => { const closeSignupDialog = () => {
showSignup.value = false showSignup.value = false
// - //
signupData.value = { signupData.value = {
userId: 'user01', userId: '',
password: 'test1234!', password: '',
passwordConfirm: 'test1234!', passwordConfirm: '',
name: 'test', name: '',
email: 'test@test.com', email: '',
businessNumber: '', businessNumber: '',
} }
@ -558,7 +597,7 @@ const closeSignupDialog = () => {
} }
onMounted(() => { onMounted(() => {
// console.log('로그인 페이지 마운트됨')
}) })
</script> </script>
@ -640,11 +679,18 @@ onMounted(() => {
50% { transform: translateY(-20px) rotate(180deg); } 50% { transform: translateY(-20px) rotate(180deg); }
} }
/* 메인 로우 */
.main-row {
position: relative;
z-index: 1;
min-height: 100vh;
padding: 2rem 0;
}
/* 브랜드 섹션 */ /* 브랜드 섹션 */
.brand-section { .brand-section {
margin-bottom: 3rem; margin-bottom: 3rem;
position: relative; text-align: center;
z-index: 1;
} }
.logo-wrapper { .logo-wrapper {
@ -666,62 +712,33 @@ onMounted(() => {
left: -10px; left: -10px;
right: -10px; right: -10px;
bottom: -10px; bottom: -10px;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4); background: radial-gradient(circle, rgba(255, 255, 255, 0.3), transparent 70%);
border-radius: 50%; border-radius: 50%;
filter: blur(20px);
opacity: 0.6;
animation: logoGlow 3s ease-in-out infinite alternate;
z-index: 1; z-index: 1;
animation: glow 3s ease-in-out infinite alternate;
} }
@keyframes logoGlow { @keyframes glow {
0% { opacity: 0.6; transform: scale(1); } from { opacity: 0.5; transform: scale(0.95); }
100% { opacity: 0.9; transform: scale(1.1); } to { opacity: 1; transform: scale(1.05); }
} }
.brand-title { .brand-title {
font-size: 3.5rem; font-size: 3rem;
font-weight: 900; font-weight: 800;
margin-bottom: 1rem; background: linear-gradient(135deg, #ffffff, #f0f8ff);
text-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
letter-spacing: -0.02em;
}
.ai-text {
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
background-clip: text; background-clip: text;
color: transparent; margin-bottom: 0.5rem;
} text-shadow: 0 4px 20px rgba(255, 255, 255, 0.3);
.marketing-text {
background: linear-gradient(135deg, #f093fb, #f5576c);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
color: transparent;
} }
.brand-subtitle { .brand-subtitle {
font-size: 1.2rem; font-size: 1.1rem;
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
font-weight: 500; font-weight: 500;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.subtitle-icon {
color: #ffd700;
animation: rocket 2s ease-in-out infinite;
}
@keyframes rocket {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-5px); }
} }
/* 로그인 카드 */ /* 로그인 카드 */
@ -731,33 +748,24 @@ onMounted(() => {
border-radius: 24px; border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.2); border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1); box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
overflow: hidden; transition: all 0.3s ease;
position: relative;
z-index: 1;
} }
.card-header { .login-card:hover {
background: linear-gradient(135deg, #667eea, #764ba2); transform: translateY(-5px);
padding: 2rem; box-shadow: 0 30px 80px rgba(0, 0, 0, 0.15);
text-align: center;
color: white;
}
.login-title {
font-size: 2rem;
font-weight: 700;
margin-bottom: 0.5rem;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
}
.login-subtitle {
font-size: 1rem;
opacity: 0.9;
margin: 0;
} }
.card-content { .card-content {
padding: 2.5rem; padding: 3rem;
}
.login-title {
text-align: center;
font-size: 2rem;
font-weight: 700;
color: #1a202c;
margin: 0;
} }
/* 입력 필드 */ /* 입력 필드 */
@ -789,12 +797,17 @@ onMounted(() => {
font-size: 1rem; font-size: 1rem;
} }
.custom-input :deep(.v-field__input::placeholder) {
color: rgba(0, 0, 0, 0.4) !important;
font-style: italic;
}
/* 로그인 옵션 */ /* 로그인 옵션 */
.login-options { .login-options {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 2rem; margin-bottom: 1rem;
} }
.remember-checkbox :deep(.v-label) { .remember-checkbox :deep(.v-label) {
@ -812,6 +825,38 @@ onMounted(() => {
background: rgba(102, 126, 234, 0.1); background: rgba(102, 126, 234, 0.1);
} }
/* 로그인 헤더 */
.login-header {
position: relative;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 2rem;
}
.login-title {
text-align: center;
font-size: 2rem;
font-weight: 700;
color: #1a202c;
margin: 0;
}
.hint-btn-header {
position: absolute;
right: 0;
color: #667eea;
font-weight: 500;
text-transform: none;
border-radius: 12px;
font-size: 0.85rem;
padding: 0.4rem 0.8rem;
}
.hint-btn-header:hover {
background: rgba(102, 126, 234, 0.1);
}
/* 에러 알림 */ /* 에러 알림 */
.error-alert { .error-alert {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
@ -878,73 +923,68 @@ onMounted(() => {
.signup-card { .signup-card {
border-radius: 20px; border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.2); border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15); background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(20px);
} }
.dialog-title { .dialog-title {
background: linear-gradient(135deg, #667eea, #764ba2); background: linear-gradient(135deg, #f8fafc, #e2e8f0);
color: white; border-radius: 20px 20px 0 0;
padding: 1.5rem; padding: 1.5rem 2rem;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.75rem; gap: 0.75rem;
font-weight: 700; font-weight: 700;
color: #1a202c;
}
.title-section {
display: flex;
align-items: center;
gap: 0.75rem;
} }
.title-icon { .title-icon {
color: #667eea;
font-size: 1.5rem; font-size: 1.5rem;
} }
.close-btn {
color: #6b7280;
}
.dialog-content { .dialog-content {
padding: 2rem; padding: 2rem;
} }
.dialog-description { .dialog-text {
color: #6b7280; color: #4a5568;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
line-height: 1.6; line-height: 1.6;
} }
.welcome-text { .welcome-text {
color: #374151; color: #4a5568;
font-size: 1.1rem; margin-bottom: 1.5rem;
margin-bottom: 2rem; line-height: 1.6;
display: flex; text-align: center;
align-items: center; font-size: 1rem;
gap: 0.5rem;
} }
.welcome-icon { .dialog-input,
color: #fbbf24; .signup-input {
font-size: 1.3rem; border-radius: 12px;
} }
.dialog-input :deep(.v-field),
.signup-input :deep(.v-field) { .signup-input :deep(.v-field) {
border-radius: 12px; border-radius: 12px;
margin-bottom: 0.5rem; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.optional {
color: #9ca3af;
font-size: 0.85rem;
font-weight: 400;
}
.signup-alert {
margin-bottom: 1rem;
border-radius: 12px;
display: flex;
align-items: center;
gap: 0.5rem;
}
.alert-icon {
margin-right: 0.5rem;
} }
.dialog-actions { .dialog-actions {
padding: 1.5rem 2rem; padding: 1rem 2rem 2rem;
background: #f9fafb; gap: 1rem;
} }
.cancel-btn { .cancel-btn {
@ -952,26 +992,74 @@ onMounted(() => {
text-transform: none; text-transform: none;
} }
.send-btn, .submit-btn,
.signup-submit-btn { .signup-submit-btn {
background: linear-gradient(135deg, #667eea, #764ba2); background: linear-gradient(135deg, #667eea, #764ba2);
color: white; color: white;
border-radius: 12px;
text-transform: none; text-transform: none;
border-radius: 12px;
font-weight: 600; font-weight: 600;
} }
.close-btn { .submit-btn:hover,
color: rgba(255, 255, 255, 0.8); .signup-submit-btn:hover {
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
} }
.close-btn:hover { .signup-alert {
margin-top: 1rem;
border-radius: 12px;
}
/* 스낵바 스타일 - 가운데 위치 */
.test-snackbar-center {
backdrop-filter: blur(10px);
}
.test-snackbar-center :deep(.v-snackbar__wrapper) {
min-width: 320px;
background: rgba(33, 150, 243, 0.95);
border-radius: 16px;
box-shadow: 0 8px 32px rgba(33, 150, 243, 0.3);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.snackbar-content-center {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.5rem 0;
}
.test-info {
line-height: 1.4;
}
.test-info > div:first-child {
font-weight: 600;
margin-bottom: 0.25rem;
}
.test-info > div:not(:first-child) {
font-size: 0.9rem;
opacity: 0.95;
}
.snackbar-btn {
color: white;
font-weight: 600;
}
.snackbar-close {
color: white; color: white;
background: rgba(255, 255, 255, 0.1);
} }
/* 반응형 디자인 */ /* 반응형 디자인 */
@media (max-width: 768px) { @media (max-width: 768px) {
.card-content {
padding: 2rem 1.5rem;
}
.brand-title { .brand-title {
font-size: 2.5rem; font-size: 2.5rem;
} }
@ -980,40 +1068,26 @@ onMounted(() => {
font-size: 1rem; font-size: 1rem;
} }
.card-content { .dialog-content {
padding: 2rem; padding: 1.5rem;
} }
.login-options { .main-row {
flex-direction: column; padding: 1rem 0;
gap: 1rem;
align-items: flex-start;
}
.bg-pattern {
display: none; /* 모바일에서 배경 패턴 숨김 */
} }
} }
@media (max-width: 480px) { @media (max-width: 480px) {
.card-content { .card-content {
padding: 1.5rem; padding: 1.5rem 1rem;
} }
.dialog-content { .brand-title {
padding: 1.5rem; font-size: 2rem;
}
} }
/* 애니메이션 효과 */ .login-title {
.v-enter-active, font-size: 1.5rem;
.v-leave-active {
transition: all 0.3s ease;
} }
.v-enter-from,
.v-leave-to {
opacity: 0;
transform: translateY(30px);
} }
</style> </style>