Public Source Viewer

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

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

Redacted View
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