Public Source Viewer
비나래아카이브 개발자 포털
실제 서비스 구조를 살펴볼 수 있는 공개용 코드 뷰어입니다. 인증, 세션, 외부 연동, 토큰, 관리자 식별 등 보안상 민감한 구현은 파일 단위 또는 줄 단위로 검열됩니다.
view/hinana/info.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
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css">
20
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
21
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
22
23
<style>
24
/* [Theme Variables] */
25
:root {
26
--font-family: 'Noto Sans KR', sans-serif;
27
--bg-main: #ffffff; --bg-secondary: #f7f9f9; --bg-tertiary: #eff3f4;
28
--text-primary: #0f1419; --text-secondary: #536471;
29
--accent-color: #1d9bf0; --danger-color: #f4212e; --border-color: #eff3f4;
30
--shadow-sm: 0 1px 3px rgba(0,0,0,0.08); --shadow-md: 0 4px 12px rgba(0,0,0,0.10);
31
}
32
body.dark-mode {
33
--bg-main: #000000; --bg-secondary: #16181c; --bg-tertiary: #202327;
34
--text-primary: #e7e9ea; --text-secondary: #71767b;
35
--border-color: #2f3336; --accent-color: #1d9bf0; --danger-color: #f4212e;
36
--shadow-sm: 0 1px 3px rgba(255,255,255,0.04); --shadow-md: 0 4px 12px rgba(0,0,0,0.6);
37
}
38
39
html, body { height: 100%; margin: 0; font-family: var(--font-family); background-color: var(--bg-main); color: var(--text-primary); overflow: auto; }
40
a { text-decoration: none; color: inherit; }
41
42
/* [Header] */
43
.global-header {
44
height: 60px;
45
background-color: rgba(255,255,255,0.85); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
46
border-bottom: 1px solid var(--border-color);
47
display: flex; align-items: center; justify-content: space-between; padding: 0 20px;
48
position: sticky; top: 0; z-index: 1000;
49
}
50
body.dark-mode .global-header { background-color: rgba(0,0,0,0.85); }
51
.header-brand { display: flex; align-items: center; gap: 15px; }
52
.header-logo { height: 28px; width: auto; mix-blend-mode: multiply; }
53
body.dark-mode .header-logo { mix-blend-mode: screen; }
54
55
.header-nav {
56
position: absolute; left: 50%; transform: translateX(-50%);
57
display: flex; gap: 20px; align-items: center; z-index: 5;
58
}
59
.nav-link { font-weight: 600; font-size: 0.95rem; color: var(--text-secondary); }
60
.nav-link:hover { color: var(--accent-color); }
61
.nav-link.active { color: var(--text-primary); }
62
.nav-divider { opacity: 0.3; color: var(--text-secondary); }
63
.login-link { color: var(--accent-color); font-weight: bold; }
64
65
.header-controls { display: flex; align-items: center; gap: 10px; z-index: 10; position: relative; background-color: var(--bg-tertiary); }
66
.icon-btn { border: none; background: transparent; color: var(--text-secondary); font-size: 1.1rem; cursor: pointer; padding: 4px; }
67
.icon-btn:hover { color: var(--text-primary); }
68
69
/* [Layout] */
70
.layout-container { min-height: 100vh; display: flex; height: calc(100vh - 60px); }
71
.shelf-column {
72
width: 300px; min-width: 300px; background-color: var(--bg-secondary);
73
border-right: 1px solid var(--border-color); display: flex; flex-direction: column;
74
justify-content: center; align-items: center;
75
}
76
.content-column {
77
flex: 1; display: flex; flex-direction: column;
78
background-color: var(--bg-main); position: relative; overflow: hidden;
79
}
80
.content-scroll-area { flex: 1; overflow-y: auto; padding: 40px; }
81
.info-column {
82
width: 260px;
83
min-width: 260px;
84
background-color: var(--bg-secondary);
85
border-left: 1px solid var(--border-color);
86
padding: 20px;
87
88
display: flex;
89
flex-direction: column;
90
91
/* 스크롤 제거 */
92
overflow: visible;
93
}
94
.info-card {
95
background-color: var(--bg-main); border-radius: 12px; padding: 20px;
96
border: 1px solid var(--border-color); box-shadow: var(--shadow-sm); margin-bottom: 20px;
97
}
98
.info-card-title { font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-secondary); margin-bottom: 10px; }
99
100
/* [Toggle Switch] */
101
.theme-toggle-wrapper { display: flex; align-items: center; justify-content: space-between; }
102
.switch { position: relative; display: inline-block; width: 40px; height: 22px; vertical-align: middle; }
103
.switch input { opacity: 0; width: 0; height: 0; }
104
.slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; }
105
.slider:before { position: absolute; content: ""; height: 16px; width: 16px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; }
106
input:checked + .slider { background-color: var(--accent-color); }
107
input:checked + .slider:before { transform: translateX(18px); }
108
109
/* =========================================
110
[수정됨] Info 페이지 컨텐츠 스타일
111
========================================= */
112
.info-title {
113
text-align: center; font-weight: 700; margin-bottom: 40px; margin-top: 10px;
114
color: var(--accent-color); font-size: 2rem; letter-spacing: -1px;
115
}
116
117
.effect-content-section {
118
padding: 40px 0;
119
margin-bottom: 20px;
120
}
121
122
/* [수정] 이미지 박스 높이 축소 */
123
.effect-box {
124
position: relative;
125
height: 260px; /* 기존 400px -> 260px 로 축소 */
126
width: 100%;
127
display: flex; justify-content: center; align-items: center;
128
}
129
130
/* [수정] 이미지 크기를 레이아웃에 맞게 조정 */
131
.effect-box img {
132
position: absolute;
133
width: 100%; /* 반응형 너비 */
134
max-width: 340px; /* 최대 너비 제한 (레이아웃 침범 방지) */
135
height: 200px; /* 적절한 높이 */
136
object-fit: cover;
137
border: 4px solid var(--bg-secondary);
138
border-radius: 1rem;
139
box-shadow: var(--shadow-md);
140
transition: transform 0.3s ease-in-out;
141
}
142
143
/* 회전 및 블러 효과 */
144
.rotate-left-bg1 { transform: rotate(-12deg) scale(0.9); opacity: 0.6; z-index: 1; filter: blur(3px); }
145
.rotate-left-bg2 { transform: rotate(-6deg) scale(0.95); opacity: 0.8; z-index: 2; filter: blur(1px); }
146
.rotate-left-main { transform: rotate(0deg); z-index: 3; }
147
148
.rotate-right-bg1 { transform: rotate(12deg) scale(0.9); opacity: 0.6; z-index: 1; filter: blur(3px); }
149
.rotate-right-bg2 { transform: rotate(6deg) scale(0.95); opacity: 0.8; z-index: 2; filter: blur(1px); }
150
.rotate-right-main { transform: rotate(0deg); z-index: 3; }
151
152
/* [수정] 텍스트 크기 축소 */
153
.text-block {
154
display: flex; flex-direction: column; justify-content: center;
155
height: 100%;
156
padding: 0 10px; /* 좌우 여백 */
157
}
158
.text-block h3 {
159
font-weight: 700; color: var(--text-primary); margin-bottom: 12px;
160
font-size: 1.4rem; /* 1.6rem -> 1.4rem */
161
}
162
.text-block p {
163
font-size: 1rem; /* 1.15rem -> 1rem (표준 크기) */
164
line-height: 1.6; color: var(--text-primary); margin-bottom: 6px;
165
}
166
.text-block hr {
167
border-top: 1px solid var(--border-color); opacity: 0.6; margin: 10px 0; width: 40px;
168
}
169
170
.text-md-end { align-items: flex-end; text-align: right; }
171
.text-md-end hr { margin-right: 0; margin-left: auto; }
172
173
/* Footer */
174
.footer-area {
175
text-align: center; padding: 40px 0; color: var(--text-secondary);
176
font-size: 0.8rem; margin-top: 60px; border-top: 1px solid var(--border-color);
177
}
178
.footer-logo { width: 80px; opacity: 0.5; mix-blend-mode: multiply; margin-bottom: 8px; }
179
body.dark-mode .footer-logo { mix-blend-mode: screen; }
180
181
/* Responsive */
182
@media (max-width: 960px) {
183
html, body { overflow: auto !important; height: auto !important; }
184
.layout-container { flex-direction: column; height: auto !important; }
185
.shelf-column { display: none; }
186
.content-column { width: 100%; height: auto !important; border: none; order: 1; overflow: visible; }
187
.content-scroll-area { padding: 30px 20px; height: auto !important; overflow: visible; }
188
189
.info-column {
190
width: 100%;
191
max-width: 520px; /* 가운데 적당한 폭으로 */
192
margin: 0 auto 30px auto; /* 가운데 정렬 + 아래 여백 */
193
height: auto;
194
border-left: none;
195
border-top: 1px solid var(--border-color);
196
order: 2;
197
padding: 20px;
198
display: flex;
199
flex-direction: column; /* 세로로 배치 */
200
flex-wrap: nowrap;
201
align-items: stretch;
202
}
203
204
.info-card {
205
flex: none; /* 카드 늘어나지 않게 */
206
width: 100%;
207
min-width: 0;
208
margin-bottom: 15px; /* 카드 사이 간격 */
209
}
210
211
/* Header Mobile */
212
.global-header { flex-wrap: wrap; height: auto; padding: 10px 20px; }
213
.header-nav {
214
position: static; transform: none; width: 100%;
215
justify-content: center; margin-top: 10px; padding-top: 10px;
216
border-top: 1px solid rgba(0,0,0,0.05); order: 3;
217
}
218
.header-brand { flex: 1; order: 1; }
219
.header-controls { flex: auto; justify-content: flex-end; background-color: transparent; order: 2; }
220
221
/* Content Mobile */
222
.effect-content-section { text-align: center; padding: 30px 0; }
223
.effect-box { height: 240px; margin-bottom: 20px; }
224
.effect-box img { max-width: 280px; height: 160px; }
225
.text-block, .text-block.text-md-end { text-align: center !important; align-items: center; margin-top: 20px; padding: 0 !important; }
226
.text-block hr { margin: 12px auto; }
227
}
228
.d-none { display: none !important; }
229
.info-column .footer {
230
background-color: transparent !important;
231
box-shadow: none;
232
border: none;
233
padding: 0;
234
}
235
.info-column footer.footer {
236
background-color: transparent !important;
237
box-shadow: none;
238
border: none;
239
}
240
</style>
241
</head>
242
243
<body class="<%= (typeof theme !== 'undefined' && theme === 'dark') ? 'dark-mode' : '' %>">
244
245
<header class="global-header">
246
<div class="header-brand">
247
<a href="/hinana/index">
248
<img src="/image/<%= (typeof theme !== 'undefined' && theme === 'dark') ? 'archive1.png' : 'archive.png' %>"
249
alt="Hinana Archive" class="header-logo">
250
</a>
251
</div>
252
<nav class="header-nav">
253
<a href="/hinana/index" class="nav-link">Archive</a>
254
<a href="/hinana/info" class="nav-link active">Info</a>
255
<a href="/hinana/blog" class="nav-link">Blog</a>
256
<a href="/hinana/lounge" class="nav-link">Lounge</a>
257
258
<span class="nav-divider">|</span>
259
260
<% if(username) { %>
261
<a href="/logout?redirect=/hinana/info" class="nav-link text-danger fw-bold">Logout</a>
262
<% } else { %>
263
<a href="/login?redirect=/hinana/info" class="nav-link login-link fw-bold">Login</a>
264
<% } %>
265
</nav>
266
<div class="header-controls">
267
<a href="/hinana/gallery#brand-assets" class="nav-link" style="font-size:0.9rem;">사이트 맵</a>
268
<form action="/toggle-theme" method="POST" class="d-inline">
269
<button type="submit" class="icon-btn" title="테마 변경">
270
<i class="bi <%= (typeof theme !== 'undefined' && theme==='dark') ? 'bi-moon-stars-fill':'bi-sun-fill' %>"></i>
271
</button>
272
</form>
273
</div>
274
</header>
275
276
<div class="layout-container">
277
278
<div class="shelf-column">
279
<div class="text-center">
280
<a href="/hinana/index" class="btn btn-outline-secondary btn-sm">
281
<i class="bi bi-arrow-left"></i> 홈으로
282
</a>
283
</div>
284
</div>
285
286
<div class="content-column">
287
<div class="content-scroll-area custom-scrollbar">
288
289
<h2 class="info-title">비나래씨는요?</h2>
290
291
<div class="row align-items-center effect-content-section">
292
<div class="col-md-6">
293
<div class="effect-box">
294
<img src="/image/1.png" class="bg-img rotate-left-bg1">
295
<img src="/image/1.png" class="bg-img rotate-left-bg2">
296
<img src="/image/1.png" class="top-img rotate-left-main">
297
</div>
298
</div>
299
<div class="col-md-6">
300
<div class="text-block ps-md-5">
301
<h3>비나래씨?</h3>
302
<hr>
303
<p>아이돌마스터 샤이니 컬러즈 그룹 녹칠의<br>이치카와 히나나를 좋아하는 사람이에요!</p>
304
<hr>
305
<p>사이트의 도메인도 <strong>hinana.moe!</strong></p>
306
<hr>
307
<p>히나나 담당 비나래 프로듀서!</p>
308
</div>
309
</div>
310
</div>
311
312
<div class="row align-items-center effect-content-section">
313
<div class="col-md-6 order-md-2">
314
<div class="effect-box">
315
<img src="/image/2.png" class="bg-img rotate-right-bg1">
316
<img src="/image/2.png" class="bg-img rotate-right-bg2">
317
<img src="/image/2.png" class="top-img rotate-right-main">
318
</div>
319
</div>
320
<div class="col-md-6 order-md-1">
321
<div class="text-block pe-md-5 text-md-end">
322
<h3>하시는 게임은요?</h3>
323
<hr>
324
<p>VRChat!</p>
325
<hr>
326
<p>각종 리듬게임!</p>
327
<hr>
328
<p>프린세스 커넥트! Re:Dive!(일본서버)</p>
329
<hr>
330
<p>그 외 다수의 게임들!</p>
331
</div>
332
</div>
333
</div>
334
335
<div class="row align-items-center effect-content-section">
336
<div class="col-md-6">
337
<div class="effect-box">
338
<img src="/image/3.png" class="bg-img rotate-left-bg1">
339
<img src="/image/3.png" class="bg-img rotate-left-bg2">
340
<img src="/image/3.png" class="top-img rotate-left-main">
341
</div>
342
</div>
343
<div class="col-md-6">
344
<div class="text-block ps-md-5">
345
<h3>SNS나 게임 계정은요?</h3>
346
<hr>
347
<p>X / Twitter : <a href="https://x.com/NoctchillHinana" target="_blank">@NoctchillHinana</a></p>
348
<hr>
349
<p>VRChat : <a href="https://vrchat.com/home/user/usr_0082e06a-7f20-46e9-97e5-ef253083ba13" target="_blank">morikubo</a></p>
350
<hr>
351
<p>Discord : ichikawahinana</p>
352
<hr>
353
<p>Email : noctchillhinana@gmail.com</p>
354
</div>
355
</div>
356
</div>
357
358
<div class="row align-items-center effect-content-section">
359
<div class="col-md-6 order-md-2">
360
<div class="effect-box">
361
<img src="/image/4.png" class="bg-img rotate-right-bg1">
362
<img src="/image/4.png" class="bg-img rotate-right-bg2">
363
<img src="/image/4.png" class="top-img rotate-right-main">
364
</div>
365
</div>
366
<div class="col-md-6 order-md-1">
367
<div class="text-block pe-md-5 text-md-end">
368
<h3>하고싶은 말이 있나요?</h3>
369
<hr>
370
<p>언제나 Discord 계정 멘션이나 X(Twitter)를 통해서 편히 말씀 주세요!</p>
371
<hr>
372
<p>VRChat을 통해서 말씀 주셔도 돼요!</p>
373
<h6 class="text-muted mt-3" style="font-size: 0.9rem;">얼굴(?)을 보고 대화하고 싶을 때도 있잖아요!</h6>
374
</div>
375
</div>
376
</div>
377
</div>
378
</div>
379
380
<div class="info-column">
381
<div class="info-card">
382
<div class="info-card-title">Current User</div>
383
<div class="d-flex align-items-center gap-2">
384
<% if (typeof currentUserProfileImage !== 'undefined' && currentUserProfileImage) { %>
385
<img src="<%= currentUserProfileImage %>" style="width:32px; height:32px; border-radius:50%; object-fit:cover;">
386
<% } else { %>
387
<i class="bi bi-person-circle fs-4 text-secondary"></i>
388
<% } %>
389
<div class="fw-bold">
390
<% if(username) { %><a href="/hinana/userInfo" style="color: inherit;"><%= username %></a><% } else { %>Guest<% } %>
391
</div>
392
393
[SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
394
<span class="badge bg-warning text-dark" style="font-size:0.6rem; vertical-align:middle;">ADMIN</span>
395
<% } %>
396
</div>
397
398
<div class="mt-3">
399
<% if(username) { %>
400
<a href="/logout?redirect=/hinana/info" class="btn btn-outline-secondary btn-sm w-100">Logout</a>
401
<% } else { %>
402
<a href="/login?redirect=/hinana/info" class="btn btn-primary btn-sm w-100">Login</a>
403
<% } %>
404
</div>
405
406
</div>
407
408
<div class="info-card">
409
<div class="info-card-title">Settings</div>
410
<div class="theme-toggle-wrapper">
411
<span class="d-flex align-items-center gap-2 small">
412
<i class="bi <%= theme==='dark'?'bi-moon-stars-fill':'bi-sun-fill' %>"></i>
413
<%= theme==='dark'?'Dark Mode':'Light Mode' %>
414
<form action="/toggle-theme" method="POST" id="theme-form">
415
<label class="switch" style="transform:scale(0.8);">
416
<input type="checkbox" <%= theme==='dark'?'checked':'' %> onchange="document.getElementById('theme-form').submit()">
417
<span class="slider"></span>
418
</label>
419
</form>
420
</span>
421
</div>
422
</div>
423
424
<div class="info-card mt-3">
425
<div class="info-card-title">System Info</div> <ul class="small text-secondary list-unstyled mb-0">
426
<li class="mb-1 d-flex justify-content-between"><span>Version</span></li>
427
<li class="mb-1 d-flex justify-content-between"><span> Ver. 6.5.4.0-Kozeki Ui</span></li>
428
</ul>
429
</div>
430
431
<div style="text-align:center; margin-top: 20px;" class="footer">
432
<img src="/image/sign.png" id="fumika_sign" style="max-width:200px; width:80%; opacity:0.8; mix-blend-mode:multiply;" />
433
</div>
434
<footer class="text-center footer mt-2">
435
<p style="margin-bottom: 0rem; font-size: 0.8rem;">X - @NoctchillHinana</p>
436
<p style="margin-bottom: 0rem; font-size: 0.8rem;">ⓒ 2024~2026. 비나래 | hinana.moe All rights reserved.</p>
437
</footer>
438
</div>
439
</div>
440
<script>if ("serviceWorker" in navigator) { navigator.serviceWorker.register("/sw.js"); }</script>
441
</body>
442
</html>
443