본문으로 건너뛰기
Public Source Viewer

비나래아카이브 개발자 포털

실제 서비스 구조를 살펴볼 수 있는 공개용 코드 뷰어입니다. 인증, 세션, 외부 연동, 토큰, 관리자 식별 등 보안상 민감한 구현은 파일 단위 또는 줄 단위로 검열됩니다.

Redacted View
view/hinana/signup.ejs
공개 가능
1 <!DOCTYPE html>
2 <html lang="ko" xmlns="http://www.w3.org/1999/xhtml">
3 <head>
4 <meta charset="utf-8" />
5 <meta name="color-scheme" content="light dark">
6 <meta name="viewport" content="width=device-width, initial-scale=1">
7 <link rel="manifest" href="/manifest.json">
8 <meta name="theme-color" content="<%= (typeof theme !== 'undefined' && theme === 'dark') ? '#000000' : '#ffffff' %>">
9 <meta name="apple-mobile-web-app-title" content="비나래 아카이브">
10 <meta property="og:image" content="/image/2.png" />
11 <meta property="og:description" content="morikubo"/>
12 <meta property="og:url" content="hinana.moe"/>
13 <meta property="og:title" content="비나래 아카이브"/>
14 <link rel='stylesheet' href='/vendors/bootstrap/css/bootstrap.min.css' />
15 <script src="/vendors/bootstrap/js/bootstrap.min.js"></script>
16 <link href="https://fonts.googleapis.com/css?family=Montserrat" rel="stylesheet" type="text/css">
17 <link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet" type="text/css">
18 <link rel="stylesheet" href="/css/hinana.css" type="text/css">
19 <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
20 <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
21 <title>비나래 아카이브</title>
22 <style>
23 /* [Theme Variables] */
24 :root {
25 --font-family: 'Noto Sans KR', sans-serif;
26 /* Light Mode */
27 --bg-main: #e4dfd7; --bg-secondary: #f2f0eb; --bg-tertiary: #dcd6ce;
28 --text-primary: #3e3a36; --text-secondary: #69615c;
29 --accent-color: #b45309; --danger-color: #dc2626; --border-color: #ccc6bc;
30 --shadow-sm: 0 1px 2px 0 rgba(60, 50, 40, 0.08);
31 }
32 /* Dark Mode */
33 body.dark-mode {
34 --bg-main: #1c1917; --bg-secondary: #292524; --bg-tertiary: #44403c;
35 --text-primary: #e7e5e4; --text-secondary: #a8a29e;
36 --border-color: #44403c; --accent-color: #f59e0b;
37 --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.5);
38 }
39
40 /* 기본 설정 */
41 html, body {
42 height: 100%; margin: 0; padding: 0;
43 font-family: var(--font-family);
44 background-color: var(--bg-main);
45 color: var(--text-primary);
46 overflow: hidden;
47 }
48 a { text-decoration: none; color: inherit; }
49
50 /* [Header] */
51 .global-header {
52 height: 60px; background-color: var(--bg-tertiary); border-bottom: 1px solid var(--border-color);
53 display: flex; align-items: center; justify-content: space-between; padding: 0 20px;
54 position: sticky; top: 0; z-index: 1000;
55 }
56 .header-brand { display: flex; align-items: center; gap: 15px; }
57 .header-logo { height: 28px; width: auto; mix-blend-mode: multiply; }
58 body.dark-mode .header-logo { mix-blend-mode: screen; }
59
60 .header-nav {
61 position: absolute; left: 50%; transform: translateX(-50%);
62 display: flex; gap: 20px; align-items: center; z-index: 5;
63 }
64 .nav-link { font-weight: 600; font-size: 0.95rem; color: var(--text-secondary); cursor: pointer; }
65 .nav-link:hover { color: var(--accent-color); }
66 .nav-link.active { color: var(--text-primary); }
67 .nav-divider { opacity: 0.3; color: var(--text-secondary); }
68 .login-link { color: var(--accent-color); font-weight: bold; }
69
70 .header-controls { display: flex; align-items: center; gap: 10px; z-index: 10; position: relative; background-color: var(--bg-tertiary); }
71 .icon-btn { border: none; background: transparent; color: var(--text-secondary); font-size: 1.1rem; cursor: pointer; padding: 4px; }
72 .icon-btn:hover { color: var(--text-primary); }
73
74 /* [Layout] */
75 .layout-container { display: flex; height: calc(100vh - 60px); }
76 .shelf-column { width: 300px; background-color: var(--bg-secondary); border-right: 1px solid var(--border-color); display: flex; flex-direction: column; justify-content: center; align-items: center; }
77
78 /* [Center Content] */
79 .content-column {
80 flex: 1; display: flex; flex-direction: column;
81 background-color: var(--bg-main); position: relative; overflow: hidden;
82 align-items: center; justify-content: center;
83 }
84
85 .login-card {
86 width: 100%; max-width: 400px;
87 background-color: var(--bg-secondary);
88 border: 1px solid var(--border-color);
89 border-radius: 12px;
90 box-shadow: var(--shadow-sm);
91 padding: 40px;
92 text-align: center;
93 }
94
95 .login-title {
96 font-size: 1.5rem; font-weight: 700; color: var(--accent-color); margin-bottom: 30px;
97 letter-spacing: -0.5px;
98 }
99
100 .form-control {
101 background-color: var(--bg-main); border: 1px solid var(--border-color);
102 color: var(--text-primary); padding: 12px; border-radius: 8px; margin-bottom: 5px;
103 }
104 .form-control:focus {
105 border-color: var(--accent-color); outline: none; box-shadow: 0 0 0 2px rgba(180, 83, 9, 0.1);
106 }
107 .form-text { font-size: 0.75rem; color: var(--text-secondary); text-align: left; margin-bottom: 15px; margin-left: 4px; }
108
109 .btn-login {
110 width: 100%; padding: 12px; border-radius: 8px; font-weight: bold;
111 background-color: var(--accent-color); border: none; color: var(--ui-on-accent, #fff);
112 transition: opacity 0.2s; margin-top: 10px;
113 }
114 .btn-login:hover { opacity: 0.9; }
115
116 .btn-back {
117 display: block; width: 100%; margin-top: 15px;
118 color: var(--text-secondary); text-decoration: none; font-size: 0.9rem;
119 }
120 .btn-back:hover { color: var(--text-primary); text-decoration: underline; }
121
122 /* [Right Sidebar] */
123 .info-column { width: 260px; background-color: var(--bg-secondary); border-left: 1px solid var(--border-color); padding: 30px 20px; display: flex; flex-direction: column; gap: 30px; }
124 .info-card { background-color: var(--bg-main); border-radius: 12px; padding: 20px; border: 1px solid var(--border-color); box-shadow: var(--shadow-sm); }
125 .info-card-title { font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-secondary); margin-bottom: 10px; }
126
127 .theme-toggle-wrapper { display: flex; align-items: center; justify-content: space-between; }
128 .switch { position: relative; display: inline-block; width: 40px; height: 22px; vertical-align: middle; }
129 .switch input { opacity: 0; width: 0; height: 0; }
130 .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; }
131 .slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
132 input:checked + .slider { background-color: var(--accent-color); }
133 input:checked + .slider:before { transform: translateX(18px); }
134
135 .info-column .footer, .info-column footer.footer {
136 background-color: transparent !important; box-shadow: none !important;
137 border: none !important; margin-top: 20px; color: var(--text-secondary) !important; text-align: center;
138 }
139 .footer-logo { width: 80px; opacity: 0.5; mix-blend-mode: multiply; margin-bottom: 8px; }
140 body.dark-mode .footer-logo { mix-blend-mode: screen; }
141
142 /* [반응형 수정: 검은 바 제거 및 레이아웃 정리] */
143 @media (max-width: 960px) {
144 html, body { overflow-y: auto !important; height: auto !important; padding-top: 0 !important; }
145 .global-header { position: fixed !important; top: 0; left: 0; right: 0; z-index: 10000 !important; height: auto !important; min-height: 60px; padding: 10px 15px; flex-wrap: wrap; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
146
147 .header-nav { position: static !important; transform: none !important; gap: 10px; font-size: 0.9rem; order: 3; width: 100%; justify-content: center; margin-top: 5px; padding-top: 5px; border-top: 1px solid rgba(0,0,0,0.05); }
148 .header-left { flex: auto; order: 1; }
149 .header-controls { flex: auto; order: 2; margin-left: auto; justify-content: flex-end; }
150
151 .layout-container {
152 flex-direction: column;
153 height: auto !important;
154 margin-top: 0 !important;
155 /* [수정] 상단 여백을 70~80px로 줄여서 헤더와 본문 사이 검은 바 제거 */
156 padding-top: 75px !important;
157 min-height: calc(100vh - 60px);
158 }
159
160 .shelf-column { display: none; }
161 .content-column { width: 100%; height: auto !important; min-height: 500px; flex: none; order: 1; border: none; padding: 40px 20px; }
162
163 /* 우측 패널 하단 배치 */
164 .info-column {
165 display: flex !important; width: 100%; height: auto;
166 border-left: none; border-top: 1px solid var(--border-color);
167 order: 2; padding: 30px 20px;
168 flex-direction: column; gap: 20px;
169 }
170 .info-card { width: 100%; margin-bottom: 0; }
171 .info-column .footer { display: block !important; width: 100%; text-align: center; margin-top: 20px; }
172 }
173 .d-none { display: none !important; }
174 </style>
175 <link rel="stylesheet" href="/css/site-ui.css?v=3">
176 <link rel="stylesheet" href="/css/site-app.css?v=1">
177 </head>
178
179 <body data-ui-page="signup" class="ui-shell <%= (typeof theme !== 'undefined' && theme === 'dark') ? 'dark-mode' : '' %>">
180
181 <%- include('partials/site-header', { uiPage: 'signup' }) %>
182
183 <div id="ui-content" tabindex="-1" role="main" class="layout-container">
184
185 <div class="shelf-column">
186 <div class="text-center">
187 <a href="/hinana/index" class="btn btn-outline-secondary btn-sm"><i class="bi bi-arrow-left"></i> 홈으로</a>
188 </div>
189 </div>
190
191 <div class="content-column">
192 <div class="login-card">
193 <h2 class="login-title">SIGN UP</h2>
194 <form action="/signup" method="POST" onsubmit="return validateForm()">
195 <div class="mb-3 text-start">
196 <label for="username" class="form-label small text-secondary fw-bold">USERNAME</label>
197 <input type="text" class="form-control" id="username" name="username"
198 required maxlength="20" pattern="^[a-zA-Z0-9_]+$"
199 placeholder="20자 이내 (영문, 숫자)" autocomplete="off">
200 <div class="form-text">* 영문, 숫자, 밑줄(_)만 사용 가능 (최대 20자)</div>
201 </div>
202 <div class="mb-4 text-start">
203 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
204 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
205 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
206 <div class="form-text">* 보안을 위해 4자리 이상 입력해주세요.</div>
207 </div>
208 <div class="mb-4 text-start">
209 <label for="code" class="form-label small text-secondary fw-bold">SIGN UP CODE</label>
210 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
211 required placeholder="가입 코드" autocomplete="off">
212 </div>
213 <button type="submit" class="btn-login" id="btn-submit">가입하기</button>
214 <a href="/login" class="btn-back">이미 계정이 있으신가요? 로그인</a>
215 </form>
216 </div>
217 </div>
218
219 <div class="info-column">
220 <div class="info-card">
221 <div class="info-card-title">Settings</div>
222 <div class="theme-toggle-wrapper">
223 <span class="d-flex align-items-center gap-2 small text-secondary">
224 <i class="bi <%= (typeof theme!=='undefined'&&theme==='dark')?'bi-moon-stars-fill':'bi-sun-fill' %>"></i>
225 <%= (typeof theme !== 'undefined' && theme === 'dark') ? 'Dark Mode' : 'Light Mode' %>
226 </span>
227 <form action="/toggle-theme" method="POST" id="theme-form">
228 <label class="switch" style="transform:scale(0.8);">
229 <input type="checkbox" <%= (typeof theme!=='undefined'&&theme==='dark')?'checked':'' %> onchange="document.getElementById('theme-form').submit()">
230 <span class="slider"></span>
231 </label>
232 </form>
233 </div>
234 </div>
235 <div class="info-card">
236 <div class="info-card-title">System Info</div>
237 <ul class="small text-secondary list-unstyled mb-0">
238 <li class="mb-1 d-flex justify-content-between"><span>Version</span></li>
239 <li class="mb-1 d-flex justify-content-between"><span> Ver. 9.2.1.0 - Iochi Mari</span></li>
240 </ul>
241 </div>
242 <div style="text-align:center; margin-top: 40px;" class="footer">
243 <img src="/image/sign.png" id="fumika_sign" style="max-width:200px; width:80%; opacity:0.8; mix-blend-mode:multiply;" />
244 </div>
245 <footer class="text-center footer mt-2">
246 <p style="margin-bottom: 0rem; font-size: 0.8rem; color: var(--text-secondary);">X - @NoctchillHinana</p>
247 <p style="margin-bottom: 0rem; font-size: 0.8rem; color: var(--text-secondary);">ⓒ 2024~2026. 비나래 | hinana.moe All rights reserved.</p>
248 </footer>
249 </div>
250 </div>
251
252 <script>
253 // 서버에서 테마가 안 넘어왔을 때 쿠키 확인
254 function getCookie(name) {
255 const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
256 return match ? match[2] : null;
257 }
258
259 const savedTheme = getCookie('theme');
260 if (savedTheme === 'dark') {
261 document.body.classList.add('dark-mode');
262 }
263
264 // 서명 이미지 블렌드 모드
265 if (document.body.classList.contains('dark-mode')) {
266 const sign = document.getElementById('fumika_sign');
267 if(sign) sign.style.mixBlendMode = 'screen';
268 }
269
270 function validateForm() {
271 const btn = document.getElementById('btn-submit');
272 btn.disabled = true;
273 btn.innerText = '처리 중...';
274 return true;
275 }
276 </script>
277 <script>if ("serviceWorker" in navigator) { navigator.serviceWorker.register("/sw.js"); }</script>
278 </body>
279 </html>
280