Help
RSS
API
Feed
Maltego
Contact
Domain > ai-crews.com
×
More information on this domain is in
AlienVault OTX
Is this malicious?
Yes
No
DNS Resolutions
Date
IP Address
2019-11-28
23.81.201.231
(
ClassC
)
2026-01-28
216.239.32.21
(
ClassC
)
Port 443
HTTP/1.1 200 OKcontent-type: text/html; charsetutf-8vary: Accept-Encodingx-cloud-trace-context: 32c5a1d67eb07148af146e77f08fd171date: Wed, 28 Jan 2026 12:34:07 GMTserver: Google FrontendContent-Length: 60839 !DOCTYPE html>html langko>head> meta charsetUTF-8> meta nameviewport contentwidthdevice-width, initial-scale1.0> title>PREDAQ - 홈/title> link relcanonical hrefhttps://www.predaq.co.kr/> link relicon hrefhttps://storage.googleapis.com/web-editor-seoul-0038.appspot.com/docver/5693708162826240/chat/img-3/favicon.png typeimage/png> link hrefhttps://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css relstylesheet> link relpreconnect hrefhttps://fonts.googleapis.com> link relpreconnect hrefhttps://fonts.gstatic.com crossorigin> link hrefhttps://fonts.googleapis.com/css2?familyNoto+Sans+KR:wght@300;400;500;700&familyRoboto:wght@300;400;500;700&displayswap relstylesheet> script srchttps://www.gstatic.com/firebasejs/9.6.10/firebase-app-compat.js>/script> script srchttps://www.gstatic.com/firebasejs/9.6.10/firebase-auth-compat.js>/script> style> /* --- CSS 변수 정의 --- */ /* :root 정의는 위 user-theme-styles 에서 동적으로 처리되므로, 기본값은 여기 남겨두거나 주석 처리 가능 */ :root { --font-family-base: Noto Sans KR, Roboto, sans-serif; /* 기본 테마 값들 (사용자 테마 없을 시 fallback 또는 초기 로딩 시 깜빡임 방지용) */ --primary-color: #A7BED3; --primary-color-dark: #7B9ACC; --primary-color-light: #D3E0EA; --secondary-color: #C6DAA7; --secondary-color-light: #E1ECD5; --accent-color: #F3B0C3; --text-color-primary: #556B2F; --text-color-secondary: #8FBC8F; --text-color-light: #FFFFFF; --bg-color-light: #F5F5F5; --bg-color-secondary: #EEEEEE; --bg-color-dark: #6B8E23; --border-color: #D8BFD8; --border-color-dark: #C0AEC0; --border-radius-sm: 0.25rem; --border-radius-md: 0.5rem; --shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); --shadow-md: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); --gradient-primary: linear-gradient(135deg, var(--primary-color-light), var(--primary-color)); --gradient-secondary: linear-gradient(135deg,var(--secondary-color-light), var(--secondary-color)); --gradient-accent: linear-gradient(135deg, #F8DDE5, var(--accent-color)); --gradient-main-bg: linear-gradient(180deg, #F5F5F5 0%, #EEEEEE 100%); --gradient-dark-bg: linear-gradient(180deg, #8FBC8F 0%, #6B8E23 100%); --font-size-xs: 0.7rem; --font-size-sm: 0.8rem; --font-size-md: 0.9rem; --font-size-base: 1rem; --font-size-lg: 1.2rem; } html { max-width: 100%; } /* --- 공통 스타일 --- */ body { font-family: var(--font-family-base); background-color: var(--bg-color-secondary) !important; color: var(--text-color-primary); font-size: var(--font-size-md); line-height: 1.6; max-width: 100%; } .navbar-brand img { display: inline-block; vertical-align: middle; height: 40px; width: auto; } .submit-btn, .btn-info { border-radius: var(--border-radius-md); font-size: var(--font-size-sm); border: 1px solid var(--border-color); padding: 0.3rem 0.75rem; margin: 0.2rem; box-shadow: var(--shadow-sm); cursor: pointer; background-image: var(--gradient-primary); color: var(--text-color-light); transition: opacity 0.2s ease-in-out; } .btn-secondary { border-radius: var(--border-radius-md); font-size: var(--font-size-sm); border: 1px solid var(--border-color); padding: 0.3rem 0.75rem; margin: 0.2rem; box-shadow: var(--shadow-sm); cursor: pointer; background-image: var(--gradient-secondary); color: var(--text-color-light); transition: opacity 0.2s ease-in-out; } .submit-btn:hover { opacity: 0.9; } .submit-btn:disabled { background-image: none; background-color: var(--border-color); cursor: not-allowed; opacity: 0.7; } /* --- 네비게이션 바 로그인 영역 스타일 --- */ .auth-nav-container { font-size: var(--font-size-sm); background-color: #ffffff; border-bottom: 1px solid var(--border-color); } .nav-item { padding: .25rem .25rem; } .auth-nav-item { display: flex; align-items: center; padding: .25rem .25rem; } .auth-nav-item .form-control-sm { height: calc(1.5em + .5rem + 2px); padding: .25rem .5rem; font-size: var(--font-size-sm); border-radius: var(--border-radius-sm); } .auth-nav-item .btn-sm { font-size: var(--font-size-sm); padding: .25rem .5rem; border-radius: var(--border-radius-sm); background-color: var(--primary-color-light); color: var(--primary-color-dark); border: none; } .auth-nav-item .btn-sm:hover { background-color: var(--primary-color); color: var(--text-color-light); } .auth-nav-item .dropdown-item { font-size: var(--font-size-sm); } #user-email-display { font-weight: 500; margin-right: 10px; color: var(--text-color-primary); } #google-login-button img { height: 0.9rem; vertical-align: text-bottom; } /* START: Theme Selector Styles */ #user-theme-selector .btn-sm { background-color: transparent; color: var(--text-color-secondary); border: 1px solid var(--border-color); } #user-theme-selector .btn-sm:hover { background-color: var(--primary-color-light); color: var(--primary-color-dark); border-color: var(--primary-color-dark); } #current-theme-name { min-width: 120px; /* 테마 이름 길이에 따라 조정 */ text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text-color-primary); font-weight: 500; } /* END: Theme Selector Styles */ /* --- 메인 네비게이션 바 --- */ header .navbar { background-image: var(--gradient-main-bg); box-shadow: var(--shadow-sm); } header .nav-link { color: var(--text-color-secondary); font-weight: 500; padding: 0.5rem 1rem; border-radius: var(--border-radius-sm); transition: color 0.2s, background-color 0.2s; } header .nav-link:hover, header .nav-link.active { color: var(--primary-color); background-color: var(--primary-color-light); } .navbar-toggler { border-color: rgba(0,0,0,0.1); } .navbar-toggler-icon { background-image: url(data:image/svg+xml,%3csvg xmlnshttp://www.w3.org/2000/svg viewBox0 0 30 30%3e%3cpath strokergba(0, 0, 0, 0.55) stroke-linecapround stroke-miterlimit10 stroke-width2 dM4 7h22M4 15h22M4 23h22/%3e%3c/svg%3e); } /* --- 회원가입 모달 에러 메시지 --- */ #signup-error-message { color: #dc3545; font-size: var(--font-size-sm); margin-top: 5px; display: none; } .modal-content { border-radius: var(--border-radius-md); } .modal-header { background-image: var(--gradient-primary); color: var(--text-color-light); border-top-left-radius: var(--border-radius-md); border-top-right-radius: var(--border-radius-md); } .modal-header .btn-close { filter: invert(1) grayscale(100%) brightness(200%); } /* --- Footer 스타일 --- */ .footer { background-color: var(--bg-color-dark); color: var(--text-color-light); padding: 1.5rem 0; font-size: var(--font-size-sm); margin-top: auto; } .footer a { color: var(--primary-color-light); text-decoration: none; margin: 0 0.5rem; } .footer a:hover { color: var(--text-color-light); text-decoration: underline; } .footer p { margin-bottom: 0.5rem; } /* --- 반응형 스타일 (네비게이션 바) --- */ @media only screen and (max-width: 768px) { .auth-nav-container .nav { flex-direction: column; align-items: stretch; } .auth-nav-container .nav-item { margin-bottom: 5px; width: 100%; } .auth-nav-container > .container.d-flex { justify-content: center !important; } #logged-out-nav { justify-content: center; } .auth-nav-item inputtypetext, .auth-nav-item inputtypepassword { width: 100%; margin-bottom: 5px; } .auth-nav-item button { width: 100%; margin-top: 5px; } #user-info-nav { flex-direction: column; align-items: flex-start; } #user-email-display { margin-bottom: 5px; } /* START: Theme Selector Mobile Styles */ #user-theme-selector { width: 100%; justify-content: center; /* 버튼과 이름 중앙 정렬 */ margin-bottom: 10px; /* 다른 항목과의 간격 */ } #current-theme-name { flex-grow: 1; /* 가능한 공간 차지 */ max-width: calc(100% - 80px); /* 버튼 너비 제외 */ } /* END: Theme Selector Mobile Styles */ .navbar-collapse { background-color: var(--bg-color-main); padding: 1rem; border-radius: var(--border-radius-md); margin-top: 0.5rem; box-shadow: var(--shadow-md); } /* --- START: Custom styles for #logged-out-nav on mobile --- */ #logged-out-nav > li.nav-item.auth-nav-item { display: flex; flex-direction: column; align-items: stretch; margin-right: 0 !important; padding-left: 0; padding-right: 0; width: 100%; padding-left: 0; padding-right: 0; box-sizing: border-box; } #logged-out-nav > li.nav-item.auth-nav-item:first-child { margin-bottom: 15px !important; } #logged-out-nav > li.nav-item.auth-nav-item > .form-control-sm, #logged-out-nav > li.nav-item.auth-nav-item > .btn { width: 100% !important; margin-left: 0 !important; margin-right: 0 !important; margin-top: 0; margin-bottom: 8px; } #logged-out-nav > li.nav-item.auth-nav-item > *:last-child { margin-bottom: 0; } #logged-out-nav .auth-nav-separator { display: none; } /* --- END: Custom styles for #logged-out-nav on mobile --- */ } /* --- 페이지별 추가 스타일 블록 --- */ style> /* 공통 스타일 */ .text-center { text-align: center; } /* 버튼 스타일 */ .custom-button { display: inline-block; width: 120px; padding: 0.5rem; font-size: 1rem; font-weight: bold; color: white; background-color: #512da8; border: 2px solid #b39ddb; border-radius: 0.5rem; text-decoration: none; transition: background-color 0.3s ease, border-color 0.3s ease; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); margin-top: 1rem; text-align: center; } .custom-button:hover { background-color: #311b92; border-color: #9575cd; } /* 모바일 화면에만 보이는 스타일 */ @media (max-width: 767px) { #desktopCarousel { display: none; } } /* 데스크탑 화면에만 보이는 스타일 */ @media (min-width: 768px) { #mobileContent { display: none; } } /* 데스크탑 캐러셀 스타일 */ #customCarousel .carousel-item { height: 680px; } #customCarousel .carousel-item img, #customCarousel .carousel-item video { height: 100%; object-fit: cover; /* 이미지가 영역에 꽉 차도록 */ width: 100%; /* Ensure image/video takes full width of its container */ } #customCarousel .text-section { height: 100%; background: linear-gradient(to right, #4a00e0, #8e2de2); /* 진한 보라색 그라데이션 */ color: white; display: flex; flex-direction: column; justify-content: center; padding: 2rem; } /* 인디케이터 스타일 (선택 사항) */ #customCarousel .carousel-indicators data-bs-target { background-color: rgba(255, 255, 255, 0.7); /* 반투명 흰색 */ } #customCarousel .carousel-indicators .active { background-color: white; /* 활성 인디케이터 */ } /* 외부 게이지 스타일 */ #carouselGaugeContainer { height: 1rem; background-color: #e9d5ff; /* 밝은 보라색 배경 */ width: 100%; /* 캐러셀 너비와 동일하게 */ margin-top: 0; /* 캐러셀과의 공백 제거 */ overflow: hidden; /* 내부 게이지 바가 넘치지 않도록 */ } #carouselGauge { height: 100%; background-color: #a855f7; /* 게이지 바 색상 (밝은 보라색보다 조금 진하게) */ width: 0%; /* 초기 너비 */ transition: width 8000ms linear; /* 너비 변경 애니메이션 - 기본값, 스크립트에서 실제 인터벌 사용 */ } /style> /style> !-- START: User Theme Styles (Dynamically Injected) --> style iduser-theme-styles> :root { --text-color-primary: #2C3E50; --border-color-dark: #95A5A6; --gradient-main-bg: linear-gradient(180deg, #F4F6F7 0%, #E0E6E9 100%); --bg-color-dark: #34495E; --accent-color: #FFC75F; --border-color: #BDC3C7; --secondary-color-light: #89D9C5; --gradient-dark-bg: linear-gradient(180deg, #5d5e61 0%, #34495E 100%); --gradient-accent: linear-gradient(135deg, #FFD54F, var(--accent-color)); --gradient-secondary: linear-gradient(135deg,var(--secondary-color-light), var(--secondary-color)); --primary-color-dark: #4A4B8A; --bg-color-secondary: #E0E6E9; --primary-color-light: #A2A3D4; --text-color-light: #ECF0F1; --text-color-secondary: #7F8C8D; --gradient-primary: linear-gradient(135deg, var(--primary-color-light), var(--primary-color)); --bg-color-light: #F4F6F7; --primary-color: #6667AB; --secondary-color: #50B498; } /style> !-- END: User Theme Styles -->/head>!-- Google tag (gtag.js) -->script async srchttps://www.googletagmanager.com/gtag/js?idG-FBLQM9YMN6>/script>script> window.dataLayer window.dataLayer || ; function gtag(){dataLayer.push(arguments);} gtag(js, new Date()); gtag(config, G-FBLQM9YMN6);/script>body> !-- 상단 네비게이션 바 (로그인/로그아웃 UI) --> nav classpy-2 auth-nav-container> div classcontainer d-flex flex-wrap justify-content-end> !-- 로그아웃 상태 UI --> ul classnav idlogged-out-nav> li classnav-item auth-nav-item me-2> input idlogin-email nameid typetext classform-control form-control-sm mx-1 stylewidth:10rem; placeholder이메일> input idlogin-password namepassword typepassword classform-control form-control-sm mx-1 stylewidth:10rem; placeholder비밀번호> button idlogin-button classbtn btn-sm py-1 mx-1>로그인/button> button idsignup-modal-button classbtn btn-sm py-1 data-bs-togglemodal data-bs-target#signupModal>회원가입/button> /li> span classauth-nav-separator me-2 mt-2>|/span> li classnav-item auth-nav-item> button idgoogle-login-button classbtn btn-sm py-1> img src/static/img/google.svg classmx-1 altGoogle> Google 로그인 /button> /li> /ul> !-- 로그인 상태 UI (초기에는 숨김) --> ul classnav idlogged-in-nav> !-- START: Theme Selector UI --> li classnav-item auth-nav-item me-3 iduser-theme-selector> button idprev-theme-btn classbtn btn-sm py-1 px-2 title이전 테마>i classfas fa-chevron-left>/i>/button> span idcurrent-theme-name classmx-2 title현재 테마>/span> button idnext-theme-btn classbtn btn-sm py-1 px-2 title다음 테마>i classfas fa-chevron-right>/i>/button> /li> !-- END: Theme Selector UI --> li classnav-item auth-nav-item dropdown me-2 iduser-info-nav> a href# classdropdown-toggle text-decoration-none d-inline-flex align-items-center iduser-menu-dropdown-toggle rolebutton data-bs-toggledropdown aria-expandedfalse stylecolor: inherit; title사용자 메뉴> span iduser-email-display>/span> !-- Populated by JS. Existing CSS for color, font-weight, margin-right will apply. --> !-- The margin-right on the span will create space between the email text and the dropdown arrow. --> /a> ul classdropdown-menu dropdown-menu-end shadow-sm aria-labelledbyuser-menu-dropdown-toggle> li> button classdropdown-item idlogout-button typebutton>로그아웃/button> /li> li>hr classdropdown-divider my-1>/li> !-- my-1 for reduced vertical margin --> li> button classdropdown-item iddelete-account-button typebutton>회원탈퇴/button> /li> /ul> /li> /ul> /div> /nav> !-- 헤더 (로고 및 메인 메뉴) --> header> nav classnavbar navbar-expand-lg py-1> div classcontainer> a classnavbar-brand hrefhttps://www.predaq.co.kr/> img src/static/img/logo.png altLogo classmy-0 py-0> /a> button classnavbar-toggler typebutton data-bs-togglecollapse data-bs-target#navbarNav aria-controlsnavbarNav aria-expandedfalse aria-labelToggle navigation> span classnavbar-toggler-icon>/span> /button> div classcollapse navbar-collapse idnavbarNav> ul classnavbar-nav ms-auto> li classnav-item>a href/ classnav-link active>홈/a>/li> li classnav-item>a href/chat classnav-link >Chat/a>/li> li classnav-item>a href/context_management classnav-link >컨텍스트/a>/li> li classnav-item>a href/history_management classnav-link >대화기록/a>/li> li classnav-item>a href/document classnav-link >도움말/a>/li> /ul> /div> /div> /nav> /header> main classpy-1> !-- 모바일 화면에만 보이는 내용 -->div idmobileContent classtext-center py-5 my-5> h2>PREDAQ의 강력함과 편리함을, 지금 바로 경험하세요!/h2> p stylefont-size:0.8rem;line-height: 1.8rem;> 새로운 Google Gemini 2.5 pro/flash을 사용합니다.br> 이제 질문(프롬프트) 작성도 AI와 함께 합니다. br> 더 정확하고, 더 전문적으로 질문으로 원하는 답변을 얻으세요.br> 실시간 파일 분석, 풍부한 컨텍스트, 대화기록 기능으로 AI를 업그레이드 합니다. /p> a href/chat classcustom-button>시작하기/a>/div>!-- 데스크탑 화면에만 보이는 캐러셀 -->div iddesktopCarousel> div idcustomCarousel classcarousel slide data-bs-ridecarousel data-bs-interval8000> !-- 인디케이터 --> div classcarousel-indicators> button typebutton data-bs-target#customCarousel data-bs-slide-to0 classactive aria-currenttrue aria-labelSlide 1>/button> button typebutton data-bs-target#customCarousel data-bs-slide-to1 aria-labelSlide 2>/button> button typebutton data-bs-target#customCarousel data-bs-slide-to2 aria-labelSlide 3>/button> button typebutton data-bs-target#customCarousel data-bs-slide-to3 aria-labelSlide 4>/button> button typebutton data-bs-target#customCarousel data-bs-slide-to4 aria-labelSlide 5>/button> /div> !-- 슬라이드 내용 --> div classcarousel-inner> !-- 슬라이드 1 --> div classcarousel-item active> div classrow g-0 h-100> div classcol-md-5 h-100> img src/static/img/slide-1.jpg classd-block w-100 alt슬라이드 -1 이미지> /div> div classcol-md-7 text-section> h2>프롬프트 작성도 AI가 도와 드립니다./h2> p stylefont-size:0.8rem;> AI 전문가들은 프롬프트만 잘 작성해도 AI를 훨씬 잘 이용할 수 있다고 강조합니다.br> 프리닥(PREDAQ)은 프롬프트 작성도 AI의 도움을 받을 수 있습니다. br> 대화의 맥락, 컨텍스트 및 로컬파일, 사용자의 프롬프트 초안을 참고하여 br> 때로는 논점에 충실하게, 때로는 더욱 확장성 있는 프롬프트를 작성해 드립니다.br>/p> a href/context_management classcustom-button>더 알아보기/a> /div> /div> /div> !-- 슬라이드 2 --> div classcarousel-item> div classrow g-0 h-100> div classcol-md-5 h-100> img src/static/img/slide0.jpg classd-block w-100 alt슬라이드 0 이미지> /div> div classcol-md-7 text-section> h2>텍스트 파일, 프로그램 파일을 실시간으로 분석합니다./h2> p stylefont-size:0.8rem;> 문서파일의 업데이트 내용을 복사-붙이기하지 않아도 AI와 실시간으로 대화할 수 있습니다.br> 개발자들은 html, css, js, py, java, c, cpp, md, json, xm 파일들을 내 PC에서 실행하면서 실시간으로 분석할 수 있습니다.br> AI에게 질문할 때마다 수정되었던 파일들은 대화기록에 모두 저장되어 파일의 업데이트 과정을 확인할 수 있습니다.br>/p> a href/context_management classcustom-button>더 알아보기/a> /div> /div> /div> !-- 슬라이드 3 --> div classcarousel-item> div classrow g-0 h-100> div classcol-md-5 h-100> img src/static/img/slide1.jpg classd-block w-100 alt슬라이드 1 이미지> /div> div classcol-md-7 text-section> h2>방대한 정보도 컨텍스트로! 똑똑한 PREDAQ 활용법!/h2> p stylefont-size:0.8rem;>학습 자료, 지난 보고서, 최신 뉴스까지! 필요한 모든 정보를 컨텍스트로 활용해 보세요.br>궁금한 점을 질문하거나, 지난주 양식으로 이번 주 보고서를 뚝딱 만들 수도 있죠!br> 복잡한 정보 처리, 이제 PREDAQ과 함께라면 어렵지 않아요!/p> a href/context_management classcustom-button>더 알아보기/a> /div> /div> /div> !-- 슬라이드 4 --> div classcarousel-item> div classrow g-0 h-100> div classcol-md-5 h-100> img src/static/img/slide2.jpg classd-block w-100 alt슬라이드 2 이미지> /div> div classcol-md-7 text-section> h2>3,000라인의 코드도 한번에 출력! /h2> p stylefont-size:0.8rem;>출력도 아낌없이, 최신 2.5 모델들은 65,536 토큰까지 한번에 출력합니다.br> 3,000라인이 넘는 코드도 결과를 생략하지 않고 정직하게 응답합니다.br> 다른 AI들과 비교 사용해 보시고, 여러분의 프로젝트에 어떤 도움이 될지 확인해 보세요!/p> /div> /div> /div> !-- 슬라이드 5 --> div classcarousel-item> div classrow g-0 h-100> div classcol-md-5 h-100> video srchttps://storage.googleapis.com/web-editor-seoul-0038.appspot.com/mp4/predaq.mp4 classd-block w-100 autoplay loop muted playsinline styleheight: 100%; object-fit: cover;>/video> /div> div classcol-md-7 text-section> h2>대화기록도 저장했다가, 몇 번이든 다시 시작할 수 있습니다./h2> p stylefont-size:0.8rem;>PREDAQ에서는 다단계 폴더를 만들어 컨텍스트와 대화기록을 체계적으로 관리할 수 있어요! br>폴더, 컨텍스트, 대화기록 모두 드래그 앤 드롭으로 쉽게 이동하며 br>방대한 정보를 효율적으로 다룰 수 있습니다! 정말 편리하죠?/p> a href/context_management classcustom-button>더 알아보기/a> /div> /div> /div> /div> !-- 이전/다음 버튼 (선택 사항) --> button classcarousel-control-prev typebutton data-bs-target#customCarousel data-bs-slideprev> span classcarousel-control-prev-icon aria-hiddentrue>/span> span classvisually-hidden>Previous/span> /button> button classcarousel-control-next typebutton data-bs-target#customCarousel data-bs-slidenext> span classcarousel-control-next-icon aria-hiddentrue>/span> span classvisually-hidden>Next/span> /button> /div> !-- 외부 게이지 --> div idcarouselGaugeContainer> div idcarouselGauge>/div> /div>/div> /main> !-- 회원가입 모달 --> div classmodal fade idsignupModal tabindex-1 aria-labelledbysignupModalLabel aria-hiddentrue> div classmodal-dialog> div classmodal-content> div classmodal-header> h5 classmodal-title idsignupModalLabel>회원가입/h5> button typebutton classbtn-close data-bs-dismissmodal aria-labelClose>/button> /div> div classmodal-body> form idsignup-form> div classmb-3> label forsignup-email classform-label>이메일 주소/label> input typeemail classform-control idsignup-email required> /div> div classmb-3> label forsignup-password classform-label>비밀번호 (6자 이상)/label> input typepassword classform-control idsignup-password required minlength6> /div> div idsignup-error-message>/div> button typesubmit classbtn btn-primary w-100 submit-btn>가입하기/button> /form> /div> /div> /div> /div> !-- Footer 추가 --> footer classfooter text-center> div classcontainer> p classmb-1> a href/tos>Terms of Service/a> | a href/privacy>Privacy Policy/a> | a href/contact>Contact Us/a> /p> p classtext-secondary small>© 2024 PREDAQ. All Rights Reserved./p> /div> /footer> !-- Footer 끝 --> script srchttps://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js>/script> script srchttps://code.jquery.com/jquery-3.6.4.min.js>/script> script> // Firebase JS SDK v7.20.0 and later, measurementId is optional const firebaseConfig { apiKey: AIzaSyARSdckeq64lYKvpjoQIWykFZ_6DQyM33E, authDomain: ai-agent-seoul-0001.firebaseapp.com, projectId: ai-agent-seoul-0001, storageBucket: ai-agent-seoul-0001.firebasestorage.app, messagingSenderId: 311027087917, appId: 1:311027087917:web:9659ca8d3240e4128e56a4, measurementId: G-87497869YK }; if (!firebase.apps.length) { firebase.initializeApp(firebaseConfig); } else { firebase.app(); } const auth firebase.auth(); // initialUserInfo는 서버에서 user_info 객체를 JSON으로 변환하여 주입합니다. // user_info에는 user_theme_name, all_color_themes, current_theme_vars 등이 포함되어야 합니다. const initialUserInfo {all_color_themes: {admin_uid: W7xfnWA6nzbxZ9fFRcjKN8y7B8y2, created_datetime: 2025-05-28T06:36:43.508415+00:00, name: \ubaa8\ub358 \u0026 \ud074\ub9b0, safekey: ahV3ZWItZWRpdG9yLXNlb3VsLTAwMzhyHAsSD0FkbWluQ29sb3JUaGVtZRiAgICYndaBCQw, theme_type: light, updated_datetime: 2025-05-28T06:36:43.508433+00:00, var_accent_color: #DC3545;, var_bg_color_dark: #343A40;, var_bg_color_light: #F8F9FA;, var_bg_color_secondary: #E9ECEF;, var_border_color: #DEE2E6;, var_border_color_dark: #CED4DA;, var_gradient_accent: linear-gradient(135deg, #F5B5BA, var(--accent-color));, var_gradient_dark_bg: linear-gradient(180deg, #6C757D 0%, #343A40 100%);, var_gradient_main_bg: linear-gradient(180deg, #F8F9FA 0%, #E9ECEF 100%);, var_gradient_primary: linear-gradient(135deg, var(--primary-color-light), var(--primary-color));, var_gradient_secondary: linear-gradient(135deg,var(--secondary-color-light), var(--secondary-color));, var_primary_color: #007BFF;, var_primary_color_dark: #0056B3;, var_primary_color_light: #B3D7FF;, var_secondary_color: #28A745;, var_secondary_color_light: #7FE19A;, var_text_color_light: #FFFFFF;, var_text_color_primary: #212529;, var_text_color_secondary: #6C757D;}, {admin_uid: W7xfnWA6nzbxZ9fFRcjKN8y7B8y2, created_datetime: 2025-05-27T06:27:58.463079+00:00, name: \ubaa8\ub178\ud1a4, safekey: ahV3ZWItZWRpdG9yLXNlb3VsLTAwMzhyHAsSD0FkbWluQ29sb3JUaGVtZRiAgICEvPyFCQw, theme_type: light, updated_datetime: 2025-05-28T07:33:23.783719+00:00, var_accent_color: #9E9E9E, var_bg_color_dark: #333333, var_bg_color_light: #F8F8F8, var_bg_color_secondary: #EEEEEE, var_border_color: #DCDCDC, var_border_color_dark: #A0A0A0, var_gradient_accent: linear-gradient(135deg, #E0E0E0, var(--accent-color)), var_gradient_dark_bg: gradient-dark-bg: linear-gradient(180deg, var(--text-color-secondary) 0%, var(--bg-color-dark) 100%), var_gradient_main_bg: linear-gradient(180deg, var(--bg-color-light) 0%, var(--bg-color-secondary) 100%), var_gradient_primary: linear-gradient(135deg, var(--primary-color-light), var(--primary-color)), var_gradient_secondary: linear-gradient(135deg,var(--secondary-color-light), var(--secondary-color)), var_primary_color: #616161, var_primary_color_dark: #424242, var_primary_color_light: #CECECE, var_secondary_color: #BDBDBD, var_secondary_color_light: #E0E0E0, var_text_color_light: #FFFFFF, var_text_color_primary: #212121, var_text_color_secondary: #757575}, {admin_uid: W7xfnWA6nzbxZ9fFRcjKN8y7B8y2, created_datetime: 2025-05-29T08:37:53.944698+00:00, name: Classic Blue 2020, safekey: ahV3ZWItZWRpdG9yLXNlb3VsLTAwMzhyHAsSD0FkbWluQ29sb3JUaGVtZRiAgICYn_KxCQw, theme_type: light, updated_datetime: 2025-05-29T08:37:53.944714+00:00, var_accent_color: #FA8072; , var_bg_color_dark: #0B3A62;, var_bg_color_light: #FFFFFF; , var_bg_color_secondary: #e9f1f7, var_border_color: #E2E8F0;, var_border_color_dark: #CBD5E0;, var_gradient_accent: linear-gradient(135deg, #FBC1B7, var(--accent-color));, var_gradient_dark_bg: linear-gradient(180deg, var(--primary-color) 0%, var(--primary-color-dark) 100%);, var_gradient_main_bg: linear-gradient(180deg, var(--bg-color-light) 0%, var(--bg-color-secondary) 100%);, var_gradient_primary: linear-gradient(135deg, var(--primary-color-light), var(--primary-color));, var_gradient_secondary: linear-gradient(135deg, var(--secondary-color-light), var(--secondary-color));, var_primary_color: #0F4C81;, var_primary_color_dark: #0B3A62;, var_primary_color_light: #A9CCE3;, var_secondary_color: #D4A373;, var_secondary_color_light: #E6CBA8;, var_text_color_light: #F7FAFC;, var_text_color_primary: #1A202C;, var_text_color_secondary: #4A5568;}, {admin_uid: W7xfnWA6nzbxZ9fFRcjKN8y7B8y2, created_datetime: 2025-05-28T06:17:27.704780+00:00, name: \ud30c\uc2a4\ud154 \ud1a4, safekey: ahV3ZWItZWRpdG9yLXNlb3VsLTAwMzhyHAsSD0FkbWluQ29sb3JUaGVtZRiAgICYncmQCgw, theme_type: light, updated_datetime: 2025-05-28T06:17:27.704798+00:00, var_accent_color: #F3B0C3;, var_bg_color_dark: #6B8E23;, var_bg_color_light: #F5F5F5;, var_bg_color_secondary: #EEEEEE;, var_border_color: #D8BFD8;, var_border_color_dark: #C0AEC0;, var_gradient_accent: linear-gradient(135deg, #F8DDE5, var(--accent-color));, var_gradient_dark_bg: linear-gradient(180deg, #8FBC8F 0%, #6B8E23 100%);, var_gradient_main_bg: linear-gradient(180deg, #F5F5F5 0%, #EEEEEE 100%);, var_gradient_primary: linear-gradient(135deg, var(--primary-color-light), var(--primary-color));, var_gradient_secondary: linear-gradient(135deg,var(--secondary-color-light), var(--secondary-color));, var_primary_color: #A7BED3;, var_primary_color_dark: #7B9ACC;, var_primary_color_light: #D3E0EA;, var_secondary_color: #C6DAA7;, var_secondary_color_light: #E1ECD5;, var_text_color_light: #FFFFFF;, var_text_color_primary: #556B2F;, var_text_color_secondary: #8FBC8F;}, {admin_uid: W7xfnWA6nzbxZ9fFRcjKN8y7B8y2, created_datetime: 2025-05-28T06:11:12.844785+00:00, name: Greenery 2017, safekey: ahV3ZWItZWRpdG9yLXNlb3VsLTAwMzhyHAsSD0FkbWluQ29sb3JUaGVtZRiAgIDY9NSQCgw, theme_type: light, updated_datetime: 2025-05-28T06:11:12.844801+00:00, var_accent_color: #F0E68C;, var_bg_color_dark: #2C3E50;, var_bg_color_light: #F5FFFA;, var_bg_color_secondary: #E0EEE0;, var_border_color: #B0C4DE;, var_border_color_dark: #A4B7D0;, var_gradient_accent: linear-gradient(135deg, #FAFAD2, var(--accent-color));, var_gradient_dark_bg: linear-gradient(180deg, #4A627C 0%, #2C3E50 100%);, var_gradient_main_bg: linear-gradient(180deg, #F5FFFA 0%, #E0EEE0 100%);, var_gradient_primary: linear-gradient(135deg, var(--primary-color-light), var(--primary-color));, var_gradient_secondary: linear-gradient(135deg,var(--secondary-color-light), var(--secondary-color));, var_primary_color: #8FBC8F;, var_primary_color_dark: #6B8E23;, var_primary_color_light: #C1E1C1;, var_secondary_color: #4682B4;, var_secondary_color_light: #77AADD;, var_text_color_light: #F0F8FF;, var_text_color_primary: #2F4F4F;, var_text_color_secondary: #696969;}, {admin_uid: W7xfnWA6nzbxZ9fFRcjKN8y7B8y2, created_datetime: 2025-05-27T06:40:38.170340+00:00, name: Very Peri 2022, safekey: ahV3ZWItZWRpdG9yLXNlb3VsLTAwMzhyHAsSD0FkbWluQ29sb3JUaGVtZRiAgIC4yY2ZCgw, theme_type: light, updated_datetime: 2025-05-27T06:40:38.170352+00:00, var_accent_color: #FFC75F, var_bg_color_dark: #34495E, var_bg_color_light: #F4F6F7, var_bg_color_secondary: #E0E6E9, var_border_color: #BDC3C7, var_border_color_dark: #95A5A6, var_gradient_accent: linear-gradient(135deg, #FFD54F, var(--accent-color)), var_gradient_dark_bg: linear-gradient(180deg, #5d5e61 0%, #34495E 100%), var_gradient_main_bg: linear-gradient(180deg, #F4F6F7 0%, #E0E6E9 100%), var_gradient_primary: linear-gradient(135deg, var(--primary-color-light), var(--primary-color)), var_gradient_secondary: linear-gradient(135deg,var(--secondary-color-light), var(--secondary-color)), var_primary_color: #6667AB, var_primary_color_dark: #4A4B8A, var_primary_color_light: #A2A3D4, var_secondary_color: #50B498, var_secondary_color_light: #89D9C5, var_text_color_light: #ECF0F1, var_text_color_primary: #2C3E50, var_text_color_secondary: #7F8C8D}, {admin_uid: W7xfnWA6nzbxZ9fFRcjKN8y7B8y2, created_datetime: 2025-05-28T06:40:20.528624+00:00, name: \ud1a4\uc628\ud1a4 \ube14\ub8e8, safekey: ahV3ZWItZWRpdG9yLXNlb3VsLTAwMzhyHAsSD0FkbWluQ29sb3JUaGVtZRiAgID406qeCgw, theme_type: light, updated_datetime: 2025-05-28T06:40:20.528642+00:00, var_accent_color: #FFD700;, var_bg_color_dark: #1C3A5A;, var_bg_color_light: #E6F2FF; , var_bg_color_secondary: #CADEED;, var_border_color: #A9CCE3;, var_border_color_dark: #87A9C0;, var_gradient_accent: linear-gradient(135deg, #FFEB99, var(--accent-color));, var_gradient_dark_bg: linear-gradient(180deg, #5A7A9A 0%, #1C3A5A 100%);, var_gradient_main_bg: linear-gradient(180deg, #E6F2FF 0%, #CADEED 100%);, var_gradient_primary: linear-gradient(135deg, var(--primary-color-light), var(--primary-color));, var_gradient_secondary: linear-gradient(135deg,var(--secondary-color-light), var(--secondary-color));, var_primary_color: #4682B4;, var_primary_color_dark: #2D5E8A;, var_primary_color_light: #77AADD;, var_secondary_color: #5F9EA0; , var_secondary_color_light: #9BC5C7;, var_text_color_light: #E6F2FF;, var_text_color_primary: #1C3A5A;, var_text_color_secondary: #5A7A9A;}, {admin_uid: W7xfnWA6nzbxZ9fFRcjKN8y7B8y2, created_datetime: 2025-05-28T06:07:26.946080+00:00, name: Living Coral 2019, safekey: ahV3ZWItZWRpdG9yLXNlb3VsLTAwMzhyHAsSD0FkbWluQ29sb3JUaGVtZRiAgIDY9MCBCww, theme_type: light, updated_datetime: 2025-05-28T06:07:26.946099+00:00, var_accent_color: #FFD166, var_bg_color_dark: #333333, var_bg_color_light: #FFF5EE, var_bg_color_secondary: #EADBCF, var_border_color: #E0CFCF, var_border_color_dark: #C0B0B0, var_gradient_accent: linear-gradient(135deg, #FFDFA0, var(--accent-color));, var_gradient_dark_bg: linear-gradient(180deg, #555555 0%, #333333 100%);, var_gradient_main_bg: linear-gradient(180deg, #FFF5EE 0%, #EADBCF 100%);, var_gradient_primary: linear-gradient(135deg, var(--primary-color-light), var(--primary-color)), var_gradient_secondary: linear-gradient(135deg,var(--secondary-color-light), var(--secondary-color));, var_primary_color: #FF6F61, var_primary_color_dark: #E65C50, var_primary_color_light: #FFB3AA, var_secondary_color: #3A8D8E, var_secondary_color_light: #73B6B7, var_text_color_light: #F9F9F9, var_text_color_primary: #444444, var_text_color_secondary: #888888}, {admin_uid: W7xfnWA6nzbxZ9fFRcjKN8y7B8y2, created_datetime: 2025-05-28T06:03:17.310480+00:00, name: Viva Magenta 2023, safekey: ahV3ZWItZWRpdG9yLXNlb3VsLTAwMzhyHAsSD0FkbWluQ29sb3JUaGVtZRiAgIDYl9ucCww, theme_type: light, updated_datetime: 2025-05-28T06:03:17.310502+00:00, var_accent_color: #FFD700, var_bg_color_dark: #16213E, var_bg_color_light: #F8F0F5, var_bg_color_secondary: #EFE7ED, var_border_color: #D4D4D4, var_border_color_dark: #A9A9A9, var_gradient_accent: linear-gradient(135deg, #FFD54F, var(--accent-color)), var_gradient_dark_bg: linear-gradient(180deg, #3a3a4a 0%, #16213E 100%), var_gradient_main_bg: linear-gradient(180deg, #F8F0F5 0%, #EFE7ED 100%), var_gradient_primary: linear-gradient(135deg, var(--primary-color-light), var(--primary-color)), var_gradient_secondary: linear-gradient(135deg,var(--secondary-color-light), var(--secondary-color)), var_primary_color: #BB2649, var_primary_color_dark: #8B1A33, var_primary_color_light: #E84F6F, var_secondary_color: #0E7C7B, var_secondary_color_light: #4FB2B0, var_text_color_light: #E9ECF2, var_text_color_primary: #1A1A2E, var_text_color_secondary: #6A6A7D}, current_theme_vars: {accent-color: #FFC75F, bg-color-dark: #34495E, bg-color-light: #F4F6F7, bg-color-secondary: #E0E6E9, border-color: #BDC3C7, border-color-dark: #95A5A6, gradient-accent: linear-gradient(135deg, #FFD54F, var(--accent-color)), gradient-dark-bg: linear-gradient(180deg, #5d5e61 0%, #34495E 100%), gradient-main-bg: linear-gradient(180deg, #F4F6F7 0%, #E0E6E9 100%), gradient-primary: linear-gradient(135deg, var(--primary-color-light), var(--primary-color)), gradient-secondary: linear-gradient(135deg,var(--secondary-color-light), var(--secondary-color)), primary-color: #6667AB, primary-color-dark: #4A4B8A, primary-color-light: #A2A3D4, secondary-color: #50B498, secondary-color-light: #89D9C5, text-color-light: #ECF0F1, text-color-primary: #2C3E50, text-color-secondary: #7F8C8D}, email: null, is_admin: false, uid: null, user_model: gemini-2.0-flash, user_safe_key: null, user_style: paraiso-dark, user_temperature: 1.0, user_theme: Very Peri 2022, user_theme_name: Very Peri 2022}; const $loggedOutNav $(#logged-out-nav); const $loggedInNav $(#logged-in-nav); const $userEmailDisplay $(#user-email-display); const $loginEmailInput $(#login-email); const $loginPasswordInput $(#login-password); const $loginButton $(#login-button); const $googleLoginButton $(#google-login-button); const $signupModalButton $(#signup-modal-button); const $logoutButton $(#logout-button); const $signupForm $(#signup-form); const $signupEmailInput $(#signup-email); const $signupPasswordInput $(#signup-password); const $signupErrorMessage $(#signup-error-message); const $signupModalElement document.getElementById(signupModal); const signupModal $signupModalElement ? new bootstrap.Modal($signupModalElement) : null; // START: Theme related variables const $userThemeSelector $(#user-theme-selector); const $currentThemeNameDisplay $(#current-theme-name); const $prevThemeBtn $(#prev-theme-btn); const $nextThemeBtn $(#next-theme-btn); let allThemes ; // 서버에서 받은 all_color_themes 저장 let currentThemeIndex -1; // END: Theme related variables const $deleteAccountButton $(#delete-account-button); auth.onAuthStateChanged(async (user) > { if (user) { console.log(Firebase Auth: User is signed in:, user.email); try { const idToken await user.getIdToken(true); const response await fetch(/sessionLogin, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ idToken: idToken }), }); const responseText await response.text(); if (response.ok) { const sessionData JSON.parse(responseText); if (!sessionData || !sessionData.user_safe_key) { console.error(User Safe Key is missing in /sessionLogin response. Data:, sessionData); alert(사용자 식별 정보(SafeKey)를 가져오는데 실패했습니다. 자동으로 로그아웃됩니다.); await handleLogout(); return; } // 서버에서 전달된 initialUserInfo를 사용 (여기서는 이메일만 업데이트) // 실제 테마 정보 등은 initialUserInfo에 이미 포함되어 있어야 함. updateUIForLoggedIn(user.email, initialUserInfo); if (typeof initializePageData function) { // initializePageData는 각 페이지(chat.html 등)에서 정의되며, initialUserInfo를 사용합니다. initializePageData(initialUserInfo); } } else { let errorDetail 서버 응답 오류; try { errorDetail JSON.parse(responseText).error || response.statusText; } catch (e) { errorDetail responseText || response.statusText; } console.error(Failed to set session cookie via /sessionLogin. Status:, response.status, Detail:, errorDetail); alert(`세션 설정 실패: ${errorDetail}. 자동으로 로그아웃됩니다.`); await handleLogout(); } } catch (error) { console.error(Error during getIdToken or /sessionLogin fetch:, error); alert(`로그인 처리 중 오류 발생: ${error.message || String(error)}. 자동으로 로그아웃됩니다.`); await handleLogout(); } } else { console.log(Firebase Auth: User is signed out.); updateUIForLoggedOut(); if (typeof cleanupPageData function) { cleanupPageData(); } } }); function updateUIForLoggedIn(email, userInfo) { $userEmailDisplay.text(email); $loggedOutNav.hide(); $loggedInNav.show(); // START: Theme UI Update if (userInfo && userInfo.all_color_themes && userInfo.all_color_themes.length > 0) { allThemes userInfo.all_color_themes; const currentThemeNameFromServer userInfo.user_theme_name; currentThemeIndex allThemes.findIndex(theme > theme.name currentThemeNameFromServer); if (currentThemeIndex -1 && allThemes.length > 0) { currentThemeIndex 0; // Fallback to the first theme if current not found } updateThemeDisplayAndCSS(); $userThemeSelector.show(); } else { $userThemeSelector.hide(); } // END: Theme UI Update if (typeof onUserLoggedIn function) onUserLoggedIn(email); } function updateUIForLoggedOut() { $loginEmailInput.val(); $loginPasswordInput.val(); $loggedInNav.hide(); $loggedOutNav.show(); $userThemeSelector.hide(); // 로그아웃 시 테마 선택기 숨김 if (typeof onUserLoggedOut function) onUserLoggedOut(); } // START: Theme Change Logic function applyThemeVariables(themeVarsObject) { let cssText :root {\n; for (const varName in themeVarsObject) { if (Object.hasOwnProperty.call(themeVarsObject, varName)) { // varName은 이미 primary-color 형태라고 가정 (서버에서 변환) cssText + ` --${varName}: ${themeVarsObjectvarName};\n`; } } cssText + }; $(#user-theme-styles).text(cssText); } function updateThemeDisplayAndCSS() { if (currentThemeIndex -1 || !allThemes || allThemes.length 0) { $currentThemeNameDisplay.text(테마 없음); return; } const selectedTheme allThemescurrentThemeIndex; $currentThemeNameDisplay.text(selectedTheme.name); $currentThemeNameDisplay.attr(title, selectedTheme.name); // 툴팁 업데이트 // 서버에서 받은 current_theme_vars와 유사한 구조로 CSS 변수 객체 생성 const themeCssVars {}; for (const key in selectedTheme) { if (key.startsWith(var_) && selectedThemekey) { const cssVarName key.replace(var_, ).replace(/_/g, -); themeCssVarscssVarName selectedThemekey; } } applyThemeVariables(themeCssVars); }// base.html의 script> 내부 수정 부분 async function changeTheme(newThemeName) { try { const response await fetch(/membership/update_theme, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ user_theme: newThemeName }) }); if (response.ok) { const data await response.json(); // 🚨 수정: 응답 JSON 파싱 console.log(`Theme updated to ${newThemeName} on server. Response data:`, data); // 🚨 수정: 서버에서 받은 최신 테마 변수로 CSS 업데이트 if (data.updated_theme_vars) { applyThemeVariables(data.updated_theme_vars); } // 🚨 수정: 테마 이름 표시 업데이트 // currentThemeIndex는 버튼 클릭 핸들러에서 이미 업데이트 되었다고 가정합니다. const selectedTheme allThemescurrentThemeIndex; if (selectedTheme) { $currentThemeNameDisplay.text(selectedTheme.name); $currentThemeNameDisplay.attr(title, selectedTheme.name); } else { // Fallback: currentThemeIndex가 유효하지 않거나 allThemes에 해당 테마가 없는 경우 $currentThemeNameDisplay.text(newThemeName); $currentThemeNameDisplay.attr(title, newThemeName); } // 🚨 수정 (선택적): initialUserInfo의 테마 관련 정보 업데이트 if (initialUserInfo) { initialUserInfo.user_theme_name newThemeName; if (data.updated_theme_vars) { initialUserInfo.current_theme_vars data.updated_theme_vars; } } } else { const errorData await response.json(); console.error(Failed to update theme on server:, errorData.error); alert(`테마 변경 실패: ${errorData.error}`); // 실패 시 이전 테마로 복원 (현재 표시된 이름 기준) currentThemeIndex allThemes.findIndex(theme > theme.name $currentThemeNameDisplay.text()); updateThemeDisplayAndCSS(); } } catch (error) { console.error(Error calling update_theme API:, error); alert(테마 변경 중 네트워크 오류가 발생했습니다.); // 실패 시 이전 테마로 복원 (현재 표시된 이름 기준) currentThemeIndex allThemes.findIndex(theme > theme.name $currentThemeNameDisplay.text()); updateThemeDisplayAndCSS(); } } $prevThemeBtn.on(click, function() { if (allThemes.length 0) return; currentThemeIndex (currentThemeIndex - 1 + allThemes.length) % allThemes.length; changeTheme(allThemescurrentThemeIndex.name); }); $nextThemeBtn.on(click, function() { if (allThemes.length 0) return; currentThemeIndex (currentThemeIndex + 1) % allThemes.length; changeTheme(allThemescurrentThemeIndex.name); }); // END: Theme Change Logic $loginButton.on(click, async () > { const email $loginEmailInput.val().trim(); const password $loginPasswordInput.val().trim(); if (!email || !password) { alert(이메일과 비밀번호를 입력해주세요.); return; } try { await auth.signInWithEmailAndPassword(email, password); } catch (error) { alert(`로그인 실패: ${mapFirebaseAuthError(error.code)}`); } }); $googleLoginButton.on(click, async () > { const provider new firebase.auth.GoogleAuthProvider(); try { await auth.signInWithPopup(provider); } catch (error) { if (error.code ! auth/popup-closed-by-user) alert(`Google 로그인 실패: ${mapFirebaseAuthError(error.code)}`);} }); $logoutButton.on(click, async () > { await handleLogout(); }); async function handleLogout() { try { const response await fetch(/sessionLogout, { method: POST }); if (!response.ok) console.warn(Server session logout failed. Status:, response.status); else console.log(Server session logout successful.); await auth.signOut(); } catch (error) { console.error(Sign out error:, error); alert(로그아웃 중 오류가 발생했습니다.); try { await auth.signOut(); } catch (e) { console.error(Retry Firebase sign out failed:, e); } } } $signupForm.on(submit, async (event) > { event.preventDefault(); $signupErrorMessage.hide().text(); const email $signupEmailInput.val().trim(); const password $signupPasswordInput.val().trim(); if (!email || !password) { $signupErrorMessage.text(이메일과 비밀번호를 모두 입력해주세요.).show(); return; } if (password.length 6) { $signupErrorMessage.text(비밀번호는 6자 이상이어야 합니다.).show(); return; } try { const response await fetch(/signup, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ email: email, password: password }), }); const data await response.json(); if (response.ok) { try { await auth.signInWithEmailAndPassword(email, password); if (signupModal) signupModal.hide(); $signupForm0.reset(); } catch (signInError) { $signupErrorMessage.text(`회원가입은 완료되었으나 자동 로그인에 실패했습니다. 직접 로그인해주세요. (${mapFirebaseAuthError(signInError.code)})`).show(); } } else { $signupErrorMessage.text(`회원가입 실패: ${data.error || response.statusText}`).show(); } } catch (error) { $signupErrorMessage.text(회원가입 중 오류가 발생했습니다. 네트워크 연결을 확인해주세요.).show(); } }); if ($signupModalElement) { $signupModalElement.addEventListener(hidden.bs.modal, function () { $signupForm0.reset(); $signupErrorMessage.hide().text(); }); } function mapFirebaseAuthError(errorCode) { const messages { auth/invalid-email: 유효하지 않은 이메일 형식입니다., auth/user-disabled: 사용 중지된 계정입니다., auth/user-not-found: 존재하지 않는 사용자입니다., auth/wrong-password: 비밀번호가 일치하지 않습니다., auth/email-already-in-use: 이미 사용 중인 이메일입니다., auth/weak-password: 비밀번호는 6자 이상이어야 합니다., auth/operation-not-allowed: 이메일/비밀번호 로그인이 활성화되지 않았습니다., auth/popup-closed-by-user: 로그인 팝업이 사용자에 의해 닫혔습니다., auth/cancelled-popup-request: 하나의 로그인 팝업만 허용됩니다., auth/too-many-requests: 로그인 시도가 너무 많습니다. 잠시 후 다시 시도해주세요. }; return messageserrorCode || `알 수 없는 오류가 발생했습니다. (${errorCode})`; } $deleteAccountButton.on(click, async () > { if (!confirm(정말로 회원 탈퇴를 진행하시겠습니까? 이 작업은 되돌릴 수 없으며, 모든 데이터가 삭제됩니다.)) { return; } if (!confirm(다시 한번 확인합니다. 정말로 모든 데이터를 삭제하고 탈퇴하시겠습니까?)) { return; } try { const response await fetch(/delete_account, { method: POST, headers: { Content-Type: application/json, // CSRF 토큰이 필요하다면 헤더에 추가 } }); const data await response.json(); if (response.ok) { alert(data.message || 회원 탈퇴가 완료되었습니다. 자동으로 로그아웃됩니다.); // 서버에서 세션 쿠키를 삭제했으므로, Firebase 로그아웃만 처리 await auth.signOut(); // UI는 onAuthStateChanged에 의해 자동으로 업데이트됩니다. // 필요시 window.location.href /; 등으로 홈으로 리디렉션 } else { alert(`회원 탈퇴 실패: ${data.error || response.statusText}`); } } catch (error) { console.error(Error during account deletion request:, error); alert(회원 탈퇴 요청 중 오류가 발생했습니다. 네트워크 연결을 확인해주세요.); } }); $(document).ready(function() { if (initialUserInfo && initialUserInfo.uid) { // uid 존재 여부로 로그인 상태 판단 강화 updateUIForLoggedIn(initialUserInfo.email, initialUserInfo); } else { updateUIForLoggedOut(); } }); /script>script srchttps://kit.fontawesome.com/65c25998c5.js crossoriginanonymous>/script> script> document.addEventListener(DOMContentLoaded, () > { const carouselElement document.getElementById(customCarousel); const gaugeElement document.getElementById(carouselGauge); if (carouselElement && gaugeElement) { const totalSlides carouselElement.querySelectorAll(.carousel-item).length; const interval parseInt(carouselElement.dataset.bsInterval, 10) || 8000; // 캐러셀 인터벌 값 가져오기 function updateGauge(activeIndex) { const currentProgress (activeIndex / totalSlides) * 100; const targetWidthPercentage ((activeIndex + 1) / totalSlides) * 100; gaugeElement.style.transition none; // 애니메이션 즉시 리셋 gaugeElement.style.width currentProgress + %; // 다음 프레임에서 애니메이션 시작 (브라우저 리페인트 보장) requestAnimationFrame(() > { requestAnimationFrame(() > { gaugeElement.style.transition `width ${interval}ms linear`; gaugeElement.style.width targetWidthPercentage + %; }); }); } carouselElement.addEventListener(slid.bs.carousel, function (event) { updateGauge(event.to); // event.to는 새로 활성화된 슬라이드의 인덱스 }); // 페이지 로드 시 현재 활성화된 슬라이드 기준으로 게이지 초기화 const activeCarouselItem carouselElement.querySelector(.carousel-item.active); let initialActiveIndex 0; if (activeCarouselItem) { initialActiveIndex Array.from(carouselElement.querySelectorAll(.carousel-item)).indexOf(activeCarouselItem); } updateGauge(initialActiveIndex); } else { if (!carouselElement) console.warn(Carousel element #customCarousel not found for gauge.); if (!gaugeElement) console.warn(Gauge element #carouselGauge not found for carousel.); } });/script>/body>/html>
View on OTX
|
View on ThreatMiner
Please enable JavaScript to view the
comments powered by Disqus.
Data with thanks to
AlienVault OTX
,
VirusTotal
,
Malwr
and
others
. [
Sitemap
]