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') ? '#050505' : '#fbfcff' %>">
9 <meta name="apple-mobile-web-app-title" content="비나래 아카이브">
10 <meta name="description" content="비나래를 소개하는 비나래 아카이브의 인터랙티브 프로필 페이지예요.">
11 <meta property="og:type" content="profile">
12 <meta property="og:site_name" content="비나래 아카이브">
13 <meta property="og:title" content="비나래 소개">
14 <meta property="og:description" content="비나래의 기록, 취향, 계정, 아카이브를 한눈에 보는 소개 페이지예요.">
15 <meta property="og:url" content="https://hinana.moe/hinana/info">
16 <meta property="og:image" content="https://hinana.moe/image/hinana_normal.png">
17 <meta name="twitter:card" content="summary_large_image">
18 <meta name="twitter:title" content="비나래 소개">
19 <meta name="twitter:description" content="비나래의 기록, 취향, 계정, 아카이브를 한눈에 보는 소개 페이지예요.">
20 <meta name="twitter:image" content="https://hinana.moe/image/hinana_normal.png">
21 <title>비나래 소개 - 비나래 아카이브</title>
22
23 <link rel="stylesheet" href="/vendors/bootstrap/css/bootstrap.min.css" />
24 <script src="/vendors/bootstrap/js/bootstrap.min.js"></script>
25 <link rel="stylesheet" href="/css/hinana.css" type="text/css">
26 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css">
27
28 <style>
29 :root {
30 --font-family: 'Noto Sans KR', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
31 --bg-main: #fbfcff;
32 --bg-elevated: rgba(255, 255, 255, 0.78);
33 --bg-solid: #ffffff;
34 --bg-soft: #eef6f2;
35 --text-primary: #111827;
36 --text-secondary: #526171;
37 --accent-color: #0ea5e9;
38 --accent-green: #10b981;
39 --accent-rose: #f43f5e;
40 --accent-gold: #c99333;
41 --border-color: rgba(15, 23, 42, 0.11);
42 --shadow-soft: 0 18px 50px rgba(15, 23, 42, 0.12);
43 --shadow-line: 0 1px 0 rgba(255,255,255,0.65) inset;
44 --header-bg: rgba(251, 252, 255, 0.82);
45 }
46 body.dark-mode {
47 --bg-main: #050505;
48 --bg-elevated: rgba(18, 22, 30, 0.78);
49 --bg-solid: #0d1117;
50 --bg-soft: #111827;
51 --text-primary: #f4f7fb;
52 --text-secondary: #9aa7b7;
53 --accent-color: #38bdf8;
54 --accent-green: #34d399;
55 --accent-rose: #fb7185;
56 --accent-gold: #f5c76b;
57 --border-color: rgba(226, 232, 240, 0.13);
58 --shadow-soft: 0 18px 60px rgba(0, 0, 0, 0.48);
59 --shadow-line: 0 1px 0 rgba(255,255,255,0.07) inset;
60 --header-bg: rgba(5, 5, 5, 0.82);
61 }
62 * { box-sizing: border-box; }
63 html { scroll-behavior: smooth; }
64 html, body {
65 margin: 0; min-height: 100%;
66 font-family: var(--font-family);
67 background: var(--bg-main);
68 color: var(--text-primary);
69 }
70 body {
71 overflow-x: hidden;
72 background-image:
73 linear-gradient(90deg, rgba(14,165,233,0.06) 1px, transparent 1px),
74 linear-gradient(180deg, rgba(16,185,129,0.05) 1px, transparent 1px);
75 background-size: 46px 46px;
76 }
77 body.dark-mode {
78 background-image:
79 linear-gradient(90deg, rgba(56,189,248,0.055) 1px, transparent 1px),
80 linear-gradient(180deg, rgba(52,211,153,0.045) 1px, transparent 1px);
81 }
82 a { color: inherit; text-decoration: none; }
83
84 .global-header {
85 height: 62px;
86 background: var(--header-bg);
87 border-bottom: 1px solid var(--border-color);
88 display: flex; align-items: center; justify-content: space-between; gap: 18px;
89 padding: 0 20px;
90 position: sticky; top: 0; z-index: 1000;
91 backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
92 }
93 .header-brand { display: flex; align-items: center; gap: 15px; min-width: 0; }
94 .header-logo { height: 28px; width: auto; mix-blend-mode: multiply; }
95 body.dark-mode .header-logo { mix-blend-mode: screen; }
96 .header-nav {
97 position: absolute; left: 50%; transform: translateX(-50%);
98 display: flex; gap: 20px; align-items: center;
99 }
100 .nav-link {
101 font-weight: 750; font-size: 0.95rem; color: var(--text-secondary);
102 transition: color 0.18s, transform 0.18s;
103 white-space: nowrap;
104 }
105 .nav-link:hover, .nav-link.active { color: var(--text-primary); transform: translateY(-1px); }
106 .nav-divider { opacity: 0.32; color: var(--text-secondary); }
107 .login-link { color: var(--accent-color); }
108 .header-controls { display: flex; align-items: center; gap: 10px; z-index: 5; }
109 .icon-btn {
110 width: 36px; height: 36px; border-radius: 50%;
111 border: 1px solid var(--border-color); background: var(--bg-elevated);
112 color: var(--text-secondary); display: inline-flex; align-items: center; justify-content: center;
113 cursor: pointer; transition: color 0.18s, transform 0.18s, border-color 0.18s;
114 }
115 .icon-btn:hover { color: var(--accent-color); transform: translateY(-1px); border-color: rgba(14,165,233,0.35); }
116
117 .info-page { position: relative; isolation: isolate; }
118 #spark-canvas {
119 position: fixed; inset: 0; width: 100%; height: 100%;
120 z-index: -1; pointer-events: none; opacity: 0.72;
121 }
122 .hero {
123 min-height: calc(100svh - 62px);
124 display: grid; grid-template-columns: minmax(0, 1.02fr) minmax(320px, 0.98fr);
125 align-items: center; gap: clamp(24px, 5vw, 70px);
126 width: min(1180px, calc(100% - 36px));
127 margin: 0 auto;
128 padding: clamp(38px, 7vw, 86px) 0;
129 }
130 .hero-copy { position: relative; z-index: 2; }
131 .eyebrow {
132 display: inline-flex; align-items: center; gap: 8px;
133 color: var(--accent-gold); font-size: 0.78rem; font-weight: 900;
134 letter-spacing: 0.18em; text-transform: uppercase; margin-bottom: 14px;
135 }
136 .signal-dot {
137 width: 9px; height: 9px; border-radius: 2px; background: var(--accent-green);
138 box-shadow: 0 0 0 6px rgba(16,185,129,0.14);
139 animation: signalPulse 1.8s ease-in-out infinite;
140 }
141 .hero-title {
142 margin: 0;
143 font-size: clamp(3.2rem, 10vw, 8.4rem);
144 line-height: 0.9;
145 font-weight: 950;
146 letter-spacing: 0;
147 }
148 .hero-title .line {
149 display: block;
150 text-shadow: 0 1px 0 rgba(255,255,255,0.45), 0 18px 42px rgba(14,165,233,0.18);
151 }
152 body.dark-mode .hero-title .line { text-shadow: 0 0 24px rgba(56,189,248,0.2); }
153 .hero-title .accent {
154 color: transparent;
155 background: linear-gradient(90deg, var(--accent-color), var(--accent-green), var(--accent-gold), var(--accent-rose));
156 background-size: 220% 100%;
157 -webkit-background-clip: text; background-clip: text;
158 animation: titleSweep 7s ease-in-out infinite;
159 }
160 .hero-lead {
161 margin: 24px 0 0;
162 max-width: 640px;
163 color: var(--text-secondary);
164 font-size: clamp(1rem, 2vw, 1.2rem);
165 line-height: 1.8;
166 }
167 .hero-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 28px; }
168 .action-btn {
169 display: inline-flex; align-items: center; gap: 8px;
170 min-height: 42px; padding: 0 15px; border-radius: 999px;
171 border: 1px solid var(--border-color); background: var(--bg-elevated);
172 color: var(--text-primary); font-size: 0.9rem; font-weight: 850;
173 box-shadow: var(--shadow-line);
174 transition: transform 0.18s, border-color 0.18s, color 0.18s;
175 }
176 .action-btn.primary { background: #0f1419; color: #fff; border-color: #0f1419; }
177 body.dark-mode .action-btn.primary { background: #f4f7fb; color: #111827; border-color: #f4f7fb; }
178 .action-btn:hover { transform: translateY(-2px); border-color: rgba(14,165,233,0.45); color: var(--accent-color); }
179 .hero-stats {
180 margin-top: 30px;
181 display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
182 gap: 10px; max-width: 650px;
183 }
184 .stat-tile {
185 padding: 14px; border-radius: 8px;
186 border: 1px solid var(--border-color); background: var(--bg-elevated);
187 box-shadow: var(--shadow-line);
188 }
189 .stat-value { font-size: 1.08rem; font-weight: 950; color: var(--text-primary); }
190 .stat-label { margin-top: 3px; font-size: 0.73rem; color: var(--text-secondary); font-weight: 750; }
191
192 .hero-visual { position: relative; min-height: min(620px, 75svh); display: grid; place-items: center; }
193 .portrait-stage {
194 position: relative;
195 width: min(480px, 86vw);
196 aspect-ratio: 0.82;
197 transform-style: preserve-3d;
198 perspective: 1200px;
199 }
200 .scene-panel {
201 position: absolute; inset: 5% 3% 10% 8%;
202 border-radius: 8px; overflow: hidden;
203 border: 1px solid var(--border-color);
204 box-shadow: var(--shadow-soft);
205 transform: rotate(-4deg) translateZ(-24px);
206 background: var(--bg-soft);
207 }
208 .scene-panel img { width: 100%; height: 100%; object-fit: cover; filter: saturate(1.05) contrast(1.02); }
209 .portrait-card {
210 position: absolute; inset: 0 8% 0 4%;
211 display: grid; place-items: end center;
212 transform: rotate(2.5deg) translateZ(44px);
213 }
214 .portrait-card::before {
215 content: "";
216 position: absolute; inset: 10% 10% 4%;
217 border: 1px solid rgba(14,165,233,0.42);
218 border-radius: 8px;
219 transform: translate(18px, 18px);
220 }
221 .portrait-card img {
222 position: relative; z-index: 2;
223 width: 100%; height: 100%; object-fit: contain; object-position: bottom center;
224 filter: drop-shadow(0 28px 34px rgba(15, 23, 42, 0.22));
225 animation: floatPortrait 5s ease-in-out infinite;
226 }
227 .scanner {
228 position: absolute; inset: 10% 11% 4% 7%;
229 z-index: 3; pointer-events: none; border-radius: 8px; overflow: hidden;
230 mix-blend-mode: screen;
231 }
232 .scanner::before {
233 content: ""; position: absolute; left: 0; right: 0; top: -20%;
234 height: 24%; background: linear-gradient(180deg, transparent, rgba(56,189,248,0.28), transparent);
235 animation: scanLine 4.8s linear infinite;
236 }
237 .floating-chip {
238 position: absolute; z-index: 4;
239 display: inline-flex; align-items: center; gap: 7px;
240 padding: 8px 10px; border-radius: 8px;
241 border: 1px solid var(--border-color); background: var(--bg-elevated);
242 box-shadow: var(--shadow-soft), var(--shadow-line);
243 color: var(--text-primary); font-size: 0.78rem; font-weight: 900;
244 backdrop-filter: blur(14px);
245 }
246 .chip-a { top: 11%; left: 0; animation: floatChip 4.4s ease-in-out infinite; }
247 .chip-b { right: 0; top: 45%; animation: floatChip 5s ease-in-out infinite reverse; }
248 .chip-c { left: 11%; bottom: 4%; animation: floatChip 4.7s ease-in-out infinite; }
249
250 .marquee-band {
251 border-block: 1px solid var(--border-color);
252 background: var(--bg-elevated);
253 overflow: hidden;
254 backdrop-filter: blur(12px);
255 }
256 .marquee-track {
257 display: flex; gap: 28px; width: max-content;
258 padding: 12px 0;
259 color: var(--text-secondary); font-size: 0.78rem; font-weight: 900;
260 letter-spacing: 0.18em; text-transform: uppercase;
261 animation: marquee 28s linear infinite;
262 }
263 .marquee-track span { white-space: nowrap; }
264
265 .section {
266 width: min(1180px, calc(100% - 36px));
267 margin: 0 auto;
268 padding: clamp(54px, 8vw, 94px) 0;
269 }
270 .section-head {
271 display: flex; align-items: flex-end; justify-content: space-between; gap: 22px;
272 margin-bottom: 24px;
273 }
274 .section-title { margin: 0; font-size: clamp(1.8rem, 4vw, 3.1rem); font-weight: 950; letter-spacing: 0; }
275 .section-desc { max-width: 560px; color: var(--text-secondary); line-height: 1.7; margin: 0; }
276 .profile-grid {
277 display: grid; grid-template-columns: repeat(12, 1fr); gap: 14px;
278 }
279 .glass-panel {
280 border: 1px solid var(--border-color);
281 background: var(--bg-elevated);
282 border-radius: 8px;
283 box-shadow: var(--shadow-line);
284 backdrop-filter: blur(18px);
285 overflow: hidden;
286 }
287 .identity-panel { grid-column: span 5; padding: 22px; }
288 .about-panel { grid-column: span 7; padding: 22px; }
289 .wide-panel { grid-column: span 12; padding: 0; }
290 .panel-label {
291 color: var(--accent-color); font-size: 0.72rem; font-weight: 950;
292 letter-spacing: 0.16em; text-transform: uppercase; margin-bottom: 12px;
293 }
294 .identity-name { font-size: clamp(2rem, 5vw, 4.1rem); line-height: 0.95; font-weight: 950; margin: 0 0 18px; }
295 .tag-row { display: flex; flex-wrap: wrap; gap: 8px; }
296 .tag {
297 display: inline-flex; align-items: center; gap: 6px;
298 min-height: 30px; padding: 0 10px; border-radius: 999px;
299 border: 1px solid var(--border-color); background: var(--bg-solid);
300 color: var(--text-secondary); font-size: 0.78rem; font-weight: 800;
301 }
302 .about-text { color: var(--text-secondary); font-size: 1rem; line-height: 1.85; margin: 0; }
303 .link-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); border-top: 1px solid var(--border-color); }
304 .social-link {
305 min-height: 110px; padding: 18px;
306 display: flex; flex-direction: column; justify-content: space-between;
307 border-right: 1px solid var(--border-color);
308 transition: background 0.18s, color 0.18s;
309 }
310 .social-link:last-child { border-right: none; }
311 .social-link:hover { background: rgba(14,165,233,0.08); color: var(--accent-color); }
312 .social-link i { font-size: 1.35rem; }
313 .social-label { font-size: 0.78rem; color: var(--text-secondary); font-weight: 850; }
314 .social-value { font-weight: 950; overflow-wrap: anywhere; }
315
316 .timeline {
317 display: grid; grid-template-columns: 0.9fr 1.1fr; gap: 16px; align-items: stretch;
318 }
319 .motion-frame {
320 min-height: 460px; position: relative; overflow: hidden;
321 background: var(--bg-soft);
322 }
323 .motion-frame img {
324 width: 100%; height: 100%; min-height: 460px; object-fit: cover;
325 transform: scale(1.05);
326 transition: transform 1.2s ease;
327 }
328 .motion-frame.is-visible img { transform: scale(1); }
329 .motion-frame::after {
330 content: ""; position: absolute; inset: 0;
331 background:
332 linear-gradient(180deg, transparent 55%, rgba(0,0,0,0.38)),
333 repeating-linear-gradient(0deg, rgba(255,255,255,0.10) 0 1px, transparent 1px 9px);
334 pointer-events: none;
335 }
336 .timeline-list { display: grid; gap: 12px; }
337 .timeline-item {
338 position: relative; padding: 18px 18px 18px 52px;
339 border: 1px solid var(--border-color); border-radius: 8px;
340 background: var(--bg-elevated); box-shadow: var(--shadow-line);
341 overflow: hidden;
342 }
343 .timeline-item::before {
344 content: ""; position: absolute; left: 20px; top: 20px;
345 width: 14px; height: 14px; border-radius: 4px;
346 background: var(--accent-color); box-shadow: 0 0 0 7px rgba(14,165,233,0.12);
347 }
348 .timeline-item:nth-child(2)::before { background: var(--accent-green); box-shadow: 0 0 0 7px rgba(16,185,129,0.12); }
349 .timeline-item:nth-child(3)::before { background: var(--accent-gold); box-shadow: 0 0 0 7px rgba(201,147,51,0.14); }
350 .timeline-item:nth-child(4)::before { background: var(--accent-rose); box-shadow: 0 0 0 7px rgba(244,63,94,0.12); }
351 .timeline-title { font-weight: 950; margin-bottom: 6px; }
352 .timeline-copy { color: var(--text-secondary); line-height: 1.65; font-size: 0.92rem; }
353
354 .status-strip {
355 display: grid; grid-template-columns: 1.05fr 0.95fr; gap: 16px; align-items: stretch;
356 }
357 .status-card { padding: 22px; }
358 .status-now {
359 display: flex; align-items: center; justify-content: space-between; gap: 14px;
360 padding: 16px; border-radius: 8px; background: var(--bg-solid); border: 1px solid var(--border-color);
361 }
362 .status-heading {
363 display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
364 margin-bottom: 4px;
365 }
366 .status-updated {
367 color: var(--text-secondary);
368 font-size: 0.72rem;
369 font-weight: 800;
370 opacity: 0.78;
371 white-space: nowrap;
372 }
373 .status-text { font-size: clamp(1.2rem, 3vw, 2.3rem); font-weight: 950; }
374 .mini-terminal {
375 min-height: 260px; padding: 18px;
376 background: #0f172a; color: #dbeafe;
377 font-family: Consolas, "SFMono-Regular", Menlo, monospace;
378 font-size: 0.86rem; line-height: 1.7;
379 }
380 .terminal-line { display: block; }
381 .terminal-prompt { color: #38bdf8; }
382 .terminal-green { color: #34d399; }
383 .terminal-gold { color: #facc15; }
384 .cursor {
385 display: inline-block; width: 8px; height: 1em; margin-left: 3px;
386 background: #dbeafe; vertical-align: -2px;
387 animation: blink 1s steps(2, start) infinite;
388 }
389
390 .site-footer {
391 width: min(1180px, calc(100% - 36px));
392 margin: 0 auto 38px; padding-top: 28px;
393 border-top: 1px solid var(--border-color);
394 color: var(--text-secondary);
395 display: flex; align-items: center; justify-content: space-between; gap: 20px;
396 font-size: 0.78rem;
397 }
398 .footer-brand { display: flex; align-items: center; gap: 12px; min-width: 0; }
399 .footer-logo { width: 44px; height: 44px; object-fit: contain; opacity: 0.76; mix-blend-mode: multiply; }
400 body.dark-mode .footer-logo { mix-blend-mode: screen; opacity: 0.88; }
401 .footer-title { color: var(--text-primary); font-weight: 900; font-size: 0.9rem; }
402 .footer-links { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; justify-content: flex-end; }
403 .footer-links a { color: var(--text-secondary); font-weight: 800; }
404 .footer-links a:hover { color: var(--accent-color); }
405 .footer-copy { margin-top: 4px; text-align: right; font-size: 0.72rem; }
406 .footer-version {
407 display: inline-flex; align-items: center; min-height: 20px;
408 padding: 0 8px; margin-right: 8px; border: 1px solid var(--border-color);
409 border-radius: 999px; color: var(--accent-color); font-weight: 850;
410 background: var(--bg-card);
411 }
412
413 .reveal { opacity: 0; transform: translateY(22px); transition: opacity 0.72s ease, transform 0.72s ease; }
414 .reveal.is-visible { opacity: 1; transform: translateY(0); }
415
416 @keyframes titleSweep { 0%, 100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } }
417 @keyframes signalPulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(0.7); } }
418 @keyframes floatPortrait { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-12px); } }
419 @keyframes scanLine { from { transform: translateY(-10%); } to { transform: translateY(520%); } }
420 @keyframes floatChip { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } }
421 @keyframes marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
422 @keyframes blink { 50% { opacity: 0; } }
423
424 @media (max-width: 980px) {
425 .global-header { flex-wrap: wrap; height: auto; min-height: 62px; padding: 10px 16px; }
426 .header-nav {
427 position: static; transform: none; width: 100%; order: 3;
428 justify-content: center; gap: 16px; padding-top: 10px;
429 border-top: 1px solid var(--border-color); overflow-x: auto;
430 }
431 .header-brand { flex: 1; }
432 .hero { grid-template-columns: 1fr; padding-top: 34px; }
433 .hero-visual { min-height: 520px; order: -1; }
434 .hero-stats { grid-template-columns: 1fr; }
435 .section-head { display: block; }
436 .section-desc { margin-top: 10px; }
437 .identity-panel, .about-panel, .wide-panel { grid-column: span 12; }
438 .timeline, .status-strip { grid-template-columns: 1fr; }
439 .link-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
440 .social-link:nth-child(2) { border-right: none; }
441 .social-link:nth-child(1), .social-link:nth-child(2) { border-bottom: 1px solid var(--border-color); }
442 }
443 @media (max-width: 620px) {
444 body { background-size: 32px 32px; }
445 .hero, .section, .site-footer { width: min(100% - 24px, 1180px); }
446 .hero-title { font-size: clamp(3rem, 19vw, 5.2rem); }
447 .hero-visual { min-height: 410px; }
448 .portrait-stage { width: min(360px, 92vw); }
449 .floating-chip { font-size: 0.68rem; padding: 7px 8px; }
450 .chip-a { top: 6%; left: 4%; }
451 .chip-b { right: 2%; top: 42%; }
452 .chip-c { left: 6%; bottom: 1%; }
453 .link-grid { grid-template-columns: 1fr; }
454 .social-link { border-right: none; border-bottom: 1px solid var(--border-color); }
455 .social-link:last-child { border-bottom: none; }
456 .motion-frame, .motion-frame img { min-height: 320px; }
457 .status-now { display: block; }
458 .status-now .action-btn { margin-top: 12px; }
459 .site-footer { flex-direction: column; align-items: flex-start; }
460 .footer-links { justify-content: flex-start; }
461 .footer-copy { text-align: left; }
462 }
463 @media (prefers-reduced-motion: reduce) {
464 *, *::before, *::after {
465 animation-duration: 0.01ms !important;
466 animation-iteration-count: 1 !important;
467 scroll-behavior: auto !important;
468 transition-duration: 0.01ms !important;
469 }
470 #spark-canvas { display: none; }
471 }
472 </style>
473 </head>
474
475 <body class="<%= (typeof theme !== 'undefined' && theme === 'dark') ? 'dark-mode' : '' %>">
476 <header class="global-header">
477 <div class="header-brand">
478 <a href="/hinana/index">
479 <img src="/image/<%= (typeof theme !== 'undefined' && theme === 'dark') ? 'archive1.png' : 'archive.png' %>" alt="비나래 아카이브" class="header-logo">
480 </a>
481 </div>
482 <nav class="header-nav">
483 <a href="/hinana/index" class="nav-link">Archive</a>
484 <a href="/hinana/info" class="nav-link active">Info</a>
485 <a href="/hinana/blog" class="nav-link">Blog</a>
486 <a href="/hinana/lounge" class="nav-link">Lounge</a>
487 <span class="nav-divider">|</span>
488 <% if(username) { %>
489 <a href="/logout?redirect=/hinana/info" class="nav-link text-danger fw-bold">Logout</a>
490 <% } else { %>
491 <a href="/login?redirect=/hinana/info" class="nav-link login-link fw-bold">Login</a>
492 <% } %>
493 </nav>
494 <div class="header-controls">
495 <a href="/hinana/gallery#brand-assets" class="nav-link" style="font-size:0.9rem;">사이트 맵</a>
496 <form action="/toggle-theme" method="POST" class="d-inline">
497 <button type="submit" class="icon-btn" title="테마 변경">
498 <i class="bi <%= (typeof theme !== 'undefined' && theme==='dark') ? 'bi-moon-stars-fill':'bi-sun-fill' %>"></i>
499 </button>
500 </form>
501 </div>
502 </header>
503
504 <main class="info-page">
505 <canvas id="spark-canvas" aria-hidden="true"></canvas>
506
507 <section class="hero">
508 <div class="hero-copy reveal">
509 <div class="eyebrow"><span class="signal-dot"></span> Hinana Archive Identity</div>
510 <h1 class="hero-title">
511 <span class="line">비나래</span>
512 <span class="line accent">예요.</span>
513 </h1>
514 <p class="hero-lead">
515 샤니마스와 이치카와 히나나를 좋아하고, 기록과 작은 기능들을 직접 만들며 쌓아두는 사람이에요.
516 이 페이지는 비나래라는 계정과 취향, 그리고 hinana.moe가 어떤 분위기로 굴러가는지 보여주는 소개 화면이에요.
517 </p>
518 <div class="hero-actions">
519 <a class="action-btn primary" href="https://x.com/NoctchillHinana" target="_blank" rel="noreferrer">
520 <i class="bi bi-twitter-x"></i> X 보기
521 </a>
522 <a class="action-btn" href="/hinana/blog">
523 <i class="bi bi-journal-text"></i> Blog
524 </a>
525 <a class="action-btn" href="/hinana/lounge">
526 <i class="bi bi-stars"></i> Lounge
527 </a>
528 </div>
529 <div class="hero-stats">
530 <div class="stat-tile">
531 <div class="stat-value">hinana.moe</div>
532 <div class="stat-label">개인 아카이브 도메인</div>
533 </div>
534 <div class="stat-tile">
535 <div class="stat-value">fresh signal</div>
536 <div class="stat-label">오늘의 분위기</div>
537 </div>
538 <div class="stat-tile">
539 <div class="stat-value"><%= username ? username : 'Guest' %></div>
540 <div class="stat-label">현재 방문자</div>
541 </div>
542 </div>
543 </div>
544
545 <div class="hero-visual reveal" data-parallax-root>
546 <div class="portrait-stage">
547 <div class="scene-panel" data-parallax="0.08">
548 <img src="/image/scenery10.png" alt="비나래 아카이브 배경">
549 </div>
550 <div class="portrait-card" data-parallax="0.14">
551 <img src="/image/hinana_normal.png" alt="비나래">
552 <div class="scanner"></div>
553 </div>
554 <div class="floating-chip chip-a"><i class="bi bi-broadcast-pin"></i> Archive Online</div>
555 <div class="floating-chip chip-b"><i class="bi bi-controller"></i> VRChat / Rhythm</div>
556 <div class="floating-chip chip-c"><i class="bi bi-gem"></i> Memory Engine</div>
557 </div>
558 </div>
559 </section>
560
561 <div class="marquee-band" aria-hidden="true">
562 <div class="marquee-track">
563 <span>BINARAE ARCHIVE</span><span>NOCTCHILL</span><span>ICHIKAWA HINANA</span><span>VRCHAT</span><span>MEMORY LOG</span><span>PUBLIC SOURCE VIEWER</span>
564 <span>BINARAE ARCHIVE</span><span>NOCTCHILL</span><span>ICHIKAWA HINANA</span><span>VRCHAT</span><span>MEMORY LOG</span><span>PUBLIC SOURCE VIEWER</span>
565 </div>
566 </div>
567
568 <section class="section" id="profile">
569 <div class="section-head reveal">
570 <h2 class="section-title">소개 신호</h2>
571 <p class="section-desc">좋아하는 것, 굴러가는 계정, 그리고 이 사이트가 가진 분위기를 한 화면에 담아봤어요.</p>
572 </div>
573 <div class="profile-grid">
574 <article class="glass-panel identity-panel reveal">
575 <div class="panel-label">Profile</div>
576 <h3 class="identity-name">비나래</h3>
577 <div class="tag-row">
578 <span class="tag"><i class="bi bi-music-note-beamed"></i> 샤니마스</span>
579 <span class="tag"><i class="bi bi-stars"></i> 히나나</span>
580 <span class="tag"><i class="bi bi-vr"></i> VRChat</span>
581 <span class="tag"><i class="bi bi-terminal"></i> 개인 개발</span>
582 <span class="tag"><i class="bi bi-journal-richtext"></i> 기록</span>
583 </div>
584 </article>
585 <article class="glass-panel about-panel reveal">
586 <div class="panel-label">About</div>
587 <p class="about-text">
588 이치카와 히나나를 좋아하는 사람이에요. 좋아하는 것을 그냥 좋아하는 데서 멈추지 않고,
589 작은 페이지와 도구, 기록 시스템으로 남겨두는 쪽을 더 좋아해요.
590 hinana.moe는 그런 취향이 쌓인 개인 아카이브이자, 놀이터이자, 가끔은 실험실이에요.
591 </p>
592 </article>
593 <article class="glass-panel wide-panel reveal">
594 <div class="link-grid">
595 <a class="social-link" href="https://x.com/NoctchillHinana" target="_blank" rel="noreferrer">
596 <i class="bi bi-twitter-x"></i>
597 <div>
598 <div class="social-label">X / Twitter</div>
599 <div class="social-value">@NoctchillHinana</div>
600 </div>
601 </a>
602 <a class="social-link" href="https://vrchat.com/home/user/usr_0082e06a-7f20-46e9-97e5-ef253083ba13" target="_blank" rel="noreferrer">
603 <i class="bi bi-badge-vr"></i>
604 <div>
605 <div class="social-label">VRChat</div>
606 <div class="social-value">morikubo</div>
607 </div>
608 </a>
609 <div class="social-link">
610 <i class="bi bi-discord"></i>
611 <div>
612 <div class="social-label">Discord</div>
613 <div class="social-value">ichikawahinana</div>
614 </div>
615 </div>
616 <a class="social-link" href="mailto:noctchillhinana@gmail.com">
617 <i class="bi bi-envelope"></i>
618 <div>
619 <div class="social-label">Email</div>
620 <div class="social-value">noctchillhinana@gmail.com</div>
621 </div>
622 </a>
623 </div>
624 </article>
625 </div>
626 </section>
627
628 <section class="section">
629 <div class="section-head reveal">
630 <h2 class="section-title">취향 로그</h2>
631 <p class="section-desc">한 사람을 설명하는 건 한 문장보다 반복되는 취향과 습관 쪽에 더 가까워요.</p>
632 </div>
633 <div class="timeline">
634 <div class="glass-panel motion-frame reveal">
635 <img src="/image/scenery11.png" alt="비나래 아카이브 풍경">
636 </div>
637 <div class="timeline-list">
638 <div class="timeline-item reveal">
639 <div class="timeline-title">샤니마스와 히나나</div>
640 <div class="timeline-copy">이 사이트의 이름과 분위기를 만든 가장 큰 취향이에요. 귀엽고 밝은 것, 그런데 어딘가 오래 남는 감각을 좋아해요.</div>
641 </div>
642 <div class="timeline-item reveal">
643 <div class="timeline-title">VRChat</div>
644 <div class="timeline-copy">사람을 만나고, 공간을 돌아다니고, 계정의 분위기를 조금씩 쌓아가는 쪽의 취미예요.</div>
645 </div>
646 <div class="timeline-item reveal">
647 <div class="timeline-title">리듬게임과 여러 게임</div>
648 <div class="timeline-copy">반복해서 손에 익히는 플레이를 좋아해요. 가볍게 켜도 결국 오래 붙잡게 되는 편이에요.</div>
649 </div>
650 <div class="timeline-item reveal">
651 <div class="timeline-title">기록과 기능 만들기</div>
652 <div class="timeline-copy">아카이브, 블로그, 라운지, 모니터링, AI 도구처럼 필요한 것을 직접 만들며 사이트에 붙여두는 걸 좋아해요.</div>
653 </div>
654 </div>
655 </div>
656 </section>
657
658 <%
659 const formatSignalUpdatedAgo = (updatedAt) => {
660 const updatedDate = updatedAt ? new Date(updatedAt) : null;
661 if (!updatedDate || Number.isNaN(updatedDate.getTime()) || updatedDate.getFullYear() <= 2000) return null;
662
663 const diffMs = Date.now() - updatedDate.getTime();
664 const diffMinutes = Math.max(0, Math.floor(diffMs / 60000));
665 if (diffMinutes < 1) return '방금 전';
666 if (diffMinutes < 60) return `${diffMinutes}분 전`;
667
668 const diffHours = Math.floor(diffMinutes / 60);
669 if (diffHours < 24) return `${diffHours}시간 전`;
670
671 const diffDays = Math.floor(diffHours / 24);
672 if (diffDays < 30) return `${diffDays}일 전`;
673
674 const diffMonths = Math.floor(diffDays / 30);
675 if (diffMonths < 12) return `${diffMonths}개월 전`;
676
677 return `${Math.floor(diffMonths / 12)}년 전`;
678 };
679 const binaraeSignalUpdatedAgo = formatSignalUpdatedAgo(typeof binaraeSignal !== 'undefined' ? binaraeSignal.updatedAt : null);
680 %>
681
682 <section class="section">
683 <div class="status-strip">
684 <article class="glass-panel status-card reveal">
685 <div class="panel-label">Fresh Signal</div>
686 <div class="status-now">
687 <div>
688 <div class="status-heading">
689 <div class="social-label">오늘의 신호</div>
690 <% if (binaraeSignalUpdatedAgo) { %>
691 <div class="status-updated">최근 변경 : <%= binaraeSignalUpdatedAgo %></div>
692 <% } %>
693 </div>
694 <div class="status-text"><%= (typeof binaraeSignal !== 'undefined' && binaraeSignal.statusText) ? binaraeSignal.statusText : '파릇하게 기록 중' %></div>
695 </div>
696 <% if(username) { %>
697 <a class="action-btn" href="/hinana/userInfo">
698 <% if (typeof currentUserProfileImage !== 'undefined' && currentUserProfileImage) { %>
699 <img src="<%= currentUserProfileImage %>" alt="" style="width:22px;height:22px;border-radius:50%;object-fit:cover;">
700 <% } else { %>
701 <i class="bi bi-person-circle"></i>
702 <% } %>
703 <%= username %>
704 </a>
705 <% } else { %>
706 <a class="action-btn" href="/login?redirect=/hinana/info"><i class="bi bi-box-arrow-in-right"></i> Login</a>
707 <% } %>
708 </div>
709 <p class="about-text mt-3">
710 말을 걸고 싶으시면 X, Discord, VRChat 어느 쪽이든 괜찮아요.
711 사이트의 기록을 둘러보다가 마음에 드는 부분이 있어도 알려주시면 기뻐요.
712 </p>
713 </article>
714 <article class="glass-panel mini-terminal reveal" aria-label="비나래 터미널">
715 <span class="terminal-line"><span class="terminal-prompt">$</span> whoami</span>
716 <span class="terminal-line terminal-green">binarae / hinana.moe operator</span>
717 <span class="terminal-line"><span class="terminal-prompt">$</span> likes --top</span>
718 <span class="terminal-line">shinycolors, hinana, vrchat, rhythm-games, archives</span>
719 <span class="terminal-line"><span class="terminal-prompt">$</span> <%= (typeof binaraeSignal !== 'undefined' && binaraeSignal.terminalCommand) ? binaraeSignal.terminalCommand : 'mood --fresh' %></span>
720 <span class="terminal-line terminal-gold"><%= (typeof binaraeSignal !== 'undefined' && binaraeSignal.terminalText) ? binaraeSignal.terminalText : 'green signal, memory online' %><span class="cursor"></span></span>
721 <% if (typeof binaraeSignal !== 'undefined' && binaraeSignal.keywords && binaraeSignal.keywords.length) { %>
722 <span class="terminal-line"><span class="terminal-prompt">$</span> keywords</span>
723 <span class="terminal-line"><%= binaraeSignal.keywords.join(' / ') %></span>
724 <% } %>
725 </article>
726 </div>
727 </section>
728 </main>
729
730 <footer class="site-footer">
731 <div class="footer-brand">
732 <img src="/image/sign.png" alt="비나래" class="footer-logo">
733 <div>
734 <div class="footer-title">비나래 아카이브</div>
735 <div>Archive My Memory</div>
736 </div>
737 </div>
738 <div>
739 <div class="footer-links">
740 <a href="/hinana/index">Archive</a>
741 <a href="/hinana/info">Info</a>
742 <a href="/hinana/blog">Blog</a>
743 <a href="/hinana/lounge">Lounge</a>
744 <a href="/hinana/developer">Developer</a>
745 <a href="https://x.com/NoctchillHinana" target="_blank" rel="noreferrer">X</a>
746 </div>
747 <div class="footer-copy"><span class="footer-version">Ver. 8.0.3.2-Utsumi Aoba</span>ⓒ 2024~2026. 비나래 | hinana.moe</div>
748 </div>
749 </footer>
750
751 <script>
752 if ("serviceWorker" in navigator) { navigator.serviceWorker.register("/sw.js"); }
753
754 (function() {
755 var reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
756 var reveals = document.querySelectorAll('.reveal, .motion-frame');
757 if ('IntersectionObserver' in window) {
758 var observer = new IntersectionObserver(function(entries) {
759 entries.forEach(function(entry) {
760 if (entry.isIntersecting) {
761 entry.target.classList.add('is-visible');
762 observer.unobserve(entry.target);
763 }
764 });
765 }, { threshold: 0.16 });
766 reveals.forEach(function(el) { observer.observe(el); });
767 } else {
768 reveals.forEach(function(el) { el.classList.add('is-visible'); });
769 }
770
771 var root = document.querySelector('[data-parallax-root]');
772 if (root && !reduceMotion) {
773 root.addEventListener('mousemove', function(event) {
774 var rect = root.getBoundingClientRect();
775 var x = (event.clientX - rect.left) / rect.width - 0.5;
776 var y = (event.clientY - rect.top) / rect.height - 0.5;
777 root.querySelectorAll('[data-parallax]').forEach(function(layer) {
778 var depth = Number(layer.getAttribute('data-parallax') || 0);
779 layer.style.transform = 'translate3d(' + (x * depth * 120) + 'px,' + (y * depth * 120) + 'px,0) rotate(' + (depth > 0.1 ? 2.5 : -4) + 'deg)';
780 });
781 });
782 root.addEventListener('mouseleave', function() {
783 root.querySelectorAll('[data-parallax]').forEach(function(layer) {
784 layer.style.transform = '';
785 });
786 });
787 }
788
789 var canvas = document.getElementById('spark-canvas');
790 if (!canvas || reduceMotion) return;
791 var ctx = canvas.getContext('2d');
792 var particles = [];
793 var width = 0;
794 var height = 0;
795 var dpr = Math.min(window.devicePixelRatio || 1, 2);
796
797 function resize() {
798 width = window.innerWidth;
799 height = window.innerHeight;
800 canvas.width = Math.floor(width * dpr);
801 canvas.height = Math.floor(height * dpr);
802 canvas.style.width = width + 'px';
803 canvas.style.height = height + 'px';
804 ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
805 var count = Math.min(90, Math.max(34, Math.floor(width * height / 18000)));
806 particles = Array.from({ length: count }, function() {
807 return {
808 x: Math.random() * width,
809 y: Math.random() * height,
810 vx: (Math.random() - 0.5) * 0.28,
811 vy: (Math.random() - 0.5) * 0.28,
812 size: Math.random() * 2 + 1,
813 hue: Math.random() > 0.5 ? 198 : 158
814 };
815 });
816 }
817
818 function tick() {
819 ctx.clearRect(0, 0, width, height);
820 particles.forEach(function(p, index) {
821 p.x += p.vx;
822 p.y += p.vy;
823 if (p.x < -20) p.x = width + 20;
824 if (p.x > width + 20) p.x = -20;
825 if (p.y < -20) p.y = height + 20;
826 if (p.y > height + 20) p.y = -20;
827
828 ctx.fillStyle = 'hsla(' + p.hue + ', 85%, 55%, 0.42)';
829 ctx.fillRect(p.x, p.y, p.size, p.size);
830
831 for (var j = index + 1; j < particles.length; j++) {
832 var q = particles[j];
833 var dx = p.x - q.x;
834 var dy = p.y - q.y;
835 var dist = Math.sqrt(dx * dx + dy * dy);
836 if (dist < 92) {
837 ctx.strokeStyle = 'rgba(14, 165, 233,' + (0.12 * (1 - dist / 92)) + ')';
838 ctx.lineWidth = 1;
839 ctx.beginPath();
840 ctx.moveTo(p.x, p.y);
841 ctx.lineTo(q.x, q.y);
842 ctx.stroke();
843 }
844 }
845 });
846 requestAnimationFrame(tick);
847 }
848
849 resize();
850 window.addEventListener('resize', resize);
851 tick();
852 })();
853 </script>
854 </body>
855 </html>
856