Public Source Viewer
비나래아카이브 개발자 포털
실제 서비스 구조를 살펴볼 수 있는 공개용 코드 뷰어입니다. 인증, 세션, 외부 연동, 토큰, 관리자 식별 등 보안상 민감한 구현은 파일 단위 또는 줄 단위로 검열됩니다.
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