Public Source Viewer

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

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

Redacted View
view/hinana/main.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') ? '#05070b' : '#f8fbff' %>">
9 <meta name="apple-mobile-web-app-title" content="비나래 아카이브">
10 <meta name="description" content="비나래 아카이브의 첫 화면이에요. 기록, 블로그, 라운지, 갤러리로 바로 이동할 수 있어요.">
11 <meta property="og:type" content="website">
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/">
16 <meta property="og:image" content="https://hinana.moe/image/10.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/10.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: 'Noto Sans KR', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
31 --bg-main: #f8fbff;
32 --bg-soft: rgba(255, 255, 255, 0.76);
33 --bg-solid: #ffffff;
34 --text-primary: #101828;
35 --text-secondary: #586879;
36 --accent: #1298e8;
37 --mint: #12b981;
38 --rose: #f05272;
39 --gold: #c99735;
40 --border: rgba(15, 23, 42, 0.12);
41 --shadow: 0 22px 70px rgba(15, 23, 42, 0.14);
42 --header: rgba(248, 251, 255, 0.82);
43 }
44 body.dark-mode {
45 --bg-main: #05070b;
46 --bg-soft: rgba(13, 17, 23, 0.76);
47 --bg-solid: #0c1119;
48 --text-primary: #f6f8fb;
49 --text-secondary: #9aa8ba;
50 --accent: #38bdf8;
51 --mint: #34d399;
52 --rose: #fb7185;
53 --gold: #f4c96e;
54 --border: rgba(226, 232, 240, 0.14);
55 --shadow: 0 22px 80px rgba(0, 0, 0, 0.46);
56 --header: rgba(5, 7, 11, 0.82);
57 }
58 * { box-sizing: border-box; }
59 html { scroll-behavior: smooth; }
60 html, body {
61 margin: 0; min-height: 100%;
62 font-family: var(--font);
63 background: var(--bg-main);
64 color: var(--text-primary);
65 overflow-x: hidden;
66 }
67 a { color: inherit; text-decoration: none; }
68
69 .global-header {
70 height: 62px;
71 position: fixed; top: 0; left: 0; right: 0; z-index: 50;
72 display: flex; align-items: center; justify-content: space-between; gap: 18px;
73 padding: 0 22px;
74 background: var(--header);
75 border-bottom: 1px solid var(--border);
76 backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
77 }
78 .header-logo { height: 28px; width: auto; mix-blend-mode: multiply; }
79 body.dark-mode .header-logo { mix-blend-mode: screen; }
80 .header-nav {
81 position: absolute; left: 50%; transform: translateX(-50%);
82 display: flex; align-items: center; gap: 18px;
83 }
84 .nav-link {
85 color: var(--text-secondary); font-weight: 850; font-size: 0.92rem;
86 white-space: nowrap; transition: color 0.18s, transform 0.18s;
87 }
88 .nav-link:hover { color: var(--text-primary); transform: translateY(-1px); }
89 .header-actions { display: flex; align-items: center; gap: 9px; z-index: 2; }
90 .round-btn {
91 width: 36px; height: 36px; border-radius: 50%;
92 border: 1px solid var(--border); background: var(--bg-soft);
93 color: var(--text-secondary);
94 display: inline-flex; align-items: center; justify-content: center;
95 cursor: pointer; transition: transform 0.18s, color 0.18s, border-color 0.18s;
96 }
97 .round-btn:hover { color: var(--accent); border-color: rgba(18,152,232,0.45); transform: translateY(-1px); }
98 .login-pill {
99 min-height: 36px; display: inline-flex; align-items: center; gap: 7px;
100 padding: 0 13px; border-radius: 999px; border: 1px solid var(--border);
101 background: var(--bg-soft); color: var(--text-primary); font-size: 0.82rem; font-weight: 900;
102 }
103 .home-mode-switch {
104 position: fixed;
105 top: 78px;
106 right: 18px;
107 z-index: 45;
108 min-height: 38px;
109 display: inline-flex;
110 align-items: center;
111 gap: 8px;
112 padding: 0 13px;
113 border-radius: 999px;
114 border: 1px solid var(--border);
115 background: var(--bg-soft);
116 color: var(--text-primary);
117 font-size: 0.82rem;
118 font-weight: 950;
119 box-shadow: var(--shadow);
120 backdrop-filter: blur(16px);
121 -webkit-backdrop-filter: blur(16px);
122 transition: transform 0.18s, border-color 0.18s, color 0.18s;
123 }
124 .home-mode-switch:hover {
125 transform: translateY(-2px);
126 border-color: rgba(18,152,232,0.48);
127 color: var(--accent);
128 }
129
130 .home-shell { position: relative; isolation: isolate; }
131 #home-canvas {
132 position: fixed; inset: 0; z-index: -1; width: 100%; height: 100%;
133 pointer-events: none; opacity: 0.75;
134 }
135 .hero {
136 min-height: 100svh;
137 position: relative;
138 display: flex; align-items: flex-end;
139 padding: 100px 0 34px;
140 overflow: hidden;
141 }
142 .hero::before {
143 content: "";
144 position: absolute; inset: 0;
145 background:
146 linear-gradient(90deg, rgba(5,7,11,0.62), rgba(5,7,11,0.16) 48%, rgba(5,7,11,0.48)),
147 linear-gradient(180deg, rgba(5,7,11,0.2), rgba(5,7,11,0.2) 50%, var(--bg-main) 100%),
148 url('/image/10.png') center / cover no-repeat;
149 transform: scale(1.03);
150 z-index: -3;
151 }
152 body:not(.dark-mode) .hero::before {
153 background:
154 linear-gradient(90deg, rgba(255,255,255,0.76), rgba(255,255,255,0.2) 48%, rgba(255,255,255,0.72)),
155 linear-gradient(180deg, rgba(248,251,255,0.05), rgba(248,251,255,0.18) 54%, var(--bg-main) 100%),
156 url('/image/10.png') center / cover no-repeat;
157 }
158 .hero::after {
159 content: "";
160 position: absolute; inset: 0; z-index: -2;
161 background:
162 linear-gradient(90deg, rgba(18,152,232,0.11) 1px, transparent 1px),
163 linear-gradient(180deg, rgba(18,185,129,0.08) 1px, transparent 1px);
164 background-size: 48px 48px;
165 mask-image: linear-gradient(180deg, rgba(0,0,0,0.74), transparent 86%);
166 }
167 .hero-inner {
168 width: min(1220px, calc(100% - 36px));
169 margin: 0 auto;
170 display: grid; grid-template-columns: minmax(0, 1fr) minmax(310px, 450px);
171 gap: clamp(24px, 5vw, 70px);
172 align-items: end;
173 position: relative;
174 z-index: 2;
175 }
176 .hero-copy { padding-bottom: clamp(18px, 4vw, 46px); }
177 .eyebrow {
178 display: inline-flex; align-items: center; gap: 8px;
179 color: var(--gold); font-size: 0.78rem; font-weight: 950;
180 letter-spacing: 0.16em; text-transform: uppercase;
181 margin-bottom: 16px;
182 }
183 .signal {
184 width: 9px; height: 9px; border-radius: 3px; background: var(--mint);
185 box-shadow: 0 0 0 6px rgba(18,185,129,0.16);
186 animation: pulse 1.7s ease-in-out infinite;
187 }
188 .hero-title {
189 margin: 0;
190 max-width: 860px;
191 font-size: clamp(3.1rem, 9.6vw, 8.8rem);
192 line-height: 0.9;
193 letter-spacing: 0;
194 font-weight: 950;
195 }
196 .hero-title span { display: block; }
197 .hero-title .accent {
198 color: transparent;
199 background: linear-gradient(90deg, var(--accent), var(--mint), var(--gold), var(--rose));
200 background-size: 240% 100%;
201 -webkit-background-clip: text; background-clip: text;
202 animation: sweep 7s ease-in-out infinite;
203 }
204 .hero-lead {
205 max-width: 670px;
206 margin: 24px 0 0;
207 color: var(--text-secondary);
208 font-size: clamp(1rem, 1.8vw, 1.2rem);
209 line-height: 1.82;
210 }
211 .hero-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 28px; }
212 .action-btn {
213 min-height: 44px; display: inline-flex; align-items: center; gap: 8px;
214 padding: 0 16px; border-radius: 999px;
215 border: 1px solid var(--border); background: var(--bg-soft);
216 color: var(--text-primary); font-weight: 900; font-size: 0.9rem;
217 backdrop-filter: blur(14px);
218 box-shadow: 0 1px 0 rgba(255,255,255,0.45) inset;
219 transition: transform 0.18s, border-color 0.18s, color 0.18s;
220 }
221 .action-btn.primary { background: #0f1419; color: #fff; border-color: #0f1419; }
222 body.dark-mode .action-btn.primary { background: #f6f8fb; color: #101828; border-color: #f6f8fb; }
223 .action-btn:hover { transform: translateY(-2px); border-color: rgba(18,152,232,0.48); color: var(--accent); }
224
225 .hero-panel {
226 position: relative;
227 min-height: 520px;
228 display: grid; place-items: end center;
229 }
230 .character {
231 position: relative; z-index: 2;
232 width: min(420px, 92vw);
233 filter: drop-shadow(0 30px 35px rgba(0,0,0,0.28));
234 animation: float 5.6s ease-in-out infinite;
235 }
236 .orbit-card {
237 position: absolute; z-index: 3;
238 display: inline-flex; align-items: center; gap: 8px;
239 padding: 9px 11px; border-radius: 8px;
240 border: 1px solid var(--border); background: var(--bg-soft);
241 color: var(--text-primary); font-weight: 950; font-size: 0.78rem;
242 box-shadow: var(--shadow);
243 backdrop-filter: blur(18px);
244 }
245 .orbit-a { top: 17%; left: 0; animation: chip 4.2s ease-in-out infinite; }
246 .orbit-b { top: 43%; right: 2%; animation: chip 4.8s ease-in-out infinite reverse; }
247 .orbit-c { bottom: 5%; left: 11%; animation: chip 5.2s ease-in-out infinite; }
248 .memory-float {
249 position: absolute;
250 z-index: 1;
251 width: var(--memory-size);
252 height: var(--memory-size);
253 left: var(--memory-left);
254 top: var(--memory-top);
255 background: var(--memory-image) center / cover no-repeat;
256 border-radius: 8px;
257 opacity: 0.34;
258 filter: blur(2px) saturate(1.05);
259 box-shadow: 0 18px 44px rgba(15, 23, 42, 0.18);
260 transform: rotate(var(--memory-rotate));
261 animation: memoryDrift var(--memory-duration) ease-in-out infinite;
262 pointer-events: none;
263 }
264 .scan-frame {
265 position: absolute; inset: 12% 6% 5%;
266 border: 1px solid rgba(56,189,248,0.42);
267 border-radius: 8px;
268 overflow: hidden; z-index: 1;
269 }
270 .scan-frame::before {
271 content: ""; position: absolute; left: 0; right: 0; top: -25%;
272 height: 22%; background: linear-gradient(180deg, transparent, rgba(56,189,248,0.28), transparent);
273 animation: scan 4.7s linear infinite;
274 }
275
276 .quick-launch {
277 width: min(1220px, calc(100% - 36px));
278 margin: -26px auto 0;
279 position: relative; z-index: 5;
280 display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
281 gap: 12px;
282 }
283 .launch-card {
284 min-height: 130px; padding: 18px;
285 border: 1px solid var(--border); border-radius: 8px;
286 background: var(--bg-soft);
287 box-shadow: var(--shadow);
288 backdrop-filter: blur(18px);
289 display: flex; flex-direction: column; justify-content: space-between;
290 overflow: hidden;
291 transition: transform 0.18s, border-color 0.18s, background 0.18s;
292 }
293 .launch-card:hover { transform: translateY(-4px); border-color: rgba(18,152,232,0.46); background: var(--bg-solid); }
294 .launch-top { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
295 .launch-icon {
296 width: 38px; height: 38px; border-radius: 8px;
297 display: inline-flex; align-items: center; justify-content: center;
298 background: rgba(18,152,232,0.12); color: var(--accent); font-size: 1.2rem;
299 }
300 .launch-arrow { color: var(--text-secondary); }
301 .launch-title { margin-top: 20px; font-size: 1.12rem; font-weight: 950; }
302 .launch-desc { margin-top: 5px; color: var(--text-secondary); font-size: 0.8rem; line-height: 1.55; }
303
304 .section {
305 width: min(1220px, calc(100% - 36px));
306 margin: 0 auto;
307 padding: clamp(64px, 9vw, 110px) 0;
308 }
309 .section-head {
310 display: flex; align-items: end; justify-content: space-between; gap: 24px;
311 margin-bottom: 24px;
312 }
313 .section-title {
314 margin: 0;
315 font-size: clamp(1.9rem, 4vw, 3.3rem);
316 font-weight: 950;
317 letter-spacing: 0;
318 }
319 .section-copy { margin: 0; max-width: 560px; color: var(--text-secondary); line-height: 1.75; }
320 .feature-grid {
321 display: grid; grid-template-columns: 1.15fr 0.85fr; gap: 14px;
322 }
323 .feature-panel {
324 min-height: 360px;
325 border: 1px solid var(--border); border-radius: 8px;
326 background: var(--bg-soft);
327 box-shadow: 0 1px 0 rgba(255,255,255,0.42) inset;
328 backdrop-filter: blur(18px);
329 overflow: hidden;
330 }
331 .feature-image {
332 position: relative;
333 background: url('/image/9.png') center / cover no-repeat;
334 }
335 .feature-image::after {
336 content: ""; position: absolute; inset: 0;
337 background: linear-gradient(180deg, transparent 38%, rgba(0,0,0,0.62));
338 }
339 .feature-caption {
340 position: absolute; left: 22px; right: 22px; bottom: 20px; z-index: 2;
341 color: #fff;
342 }
343 .feature-caption h3 { margin: 0; font-size: 1.65rem; font-weight: 950; }
344 .feature-caption p { margin: 8px 0 0; color: rgba(255,255,255,0.8); line-height: 1.65; }
345 .feature-list { padding: 18px; display: grid; gap: 10px; }
346 .mini-link {
347 padding: 14px; border-radius: 8px; border: 1px solid var(--border);
348 background: var(--bg-solid);
349 display: flex; align-items: center; justify-content: space-between; gap: 12px;
350 transition: transform 0.18s, color 0.18s;
351 }
352 .mini-link:hover { transform: translateX(3px); color: var(--accent); }
353 .mini-title { font-weight: 950; }
354 .mini-desc { margin-top: 3px; color: var(--text-secondary); font-size: 0.78rem; }
355
356 .site-footer {
357 width: min(1220px, calc(100% - 36px));
358 margin: 0 auto 38px; padding-top: 26px;
359 border-top: 1px solid var(--border);
360 color: var(--text-secondary);
361 display: flex; align-items: center; justify-content: space-between; gap: 20px;
362 font-size: 0.78rem;
363 }
364 .footer-brand { display: flex; align-items: center; gap: 12px; min-width: 0; }
365 .footer-logo { width: 44px; height: 44px; object-fit: contain; opacity: 0.74; mix-blend-mode: multiply; }
366 body.dark-mode .footer-logo { mix-blend-mode: screen; opacity: 0.9; }
367 .footer-title { color: var(--text-primary); font-weight: 950; font-size: 0.9rem; }
368 .footer-links { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; justify-content: flex-end; }
369 .footer-links a { color: var(--text-secondary); font-weight: 850; }
370 .footer-links a:hover { color: var(--accent); }
371 .footer-copy { margin-top: 4px; text-align: right; font-size: 0.72rem; }
372 .footer-version {
373 display: inline-flex; align-items: center; min-height: 20px;
374 padding: 0 8px; margin-right: 8px; border: 1px solid var(--line);
375 border-radius: 999px; color: var(--accent); font-weight: 850;
376 background: var(--bg-panel);
377 }
378
379 .reveal { opacity: 0; transform: translateY(22px); transition: opacity 0.7s ease, transform 0.7s ease; }
380 .reveal.is-visible { opacity: 1; transform: translateY(0); }
381
382 .classic-home {
383 min-height: calc(100svh - 62px);
384 padding: 112px 18px 56px;
385 display: flex;
386 flex-direction: column;
387 align-items: center;
388 justify-content: center;
389 position: relative;
390 overflow: hidden;
391 background:
392 linear-gradient(180deg, rgba(248,251,255,0.86), var(--bg-main)),
393 radial-gradient(circle at 50% 20%, rgba(18,152,232,0.14), transparent 34%);
394 }
395 body.dark-mode .classic-home {
396 background:
397 linear-gradient(180deg, rgba(5,7,11,0.82), var(--bg-main)),
398 radial-gradient(circle at 50% 20%, rgba(56,189,248,0.13), transparent 34%);
399 }
400 .classic-title {
401 margin: 0 0 46px;
402 color: var(--text-primary);
403 font-size: clamp(2.2rem, 6vw, 4.8rem);
404 line-height: 1;
405 font-weight: 950;
406 letter-spacing: 0;
407 text-align: center;
408 }
409 .classic-books {
410 display: flex;
411 justify-content: center;
412 align-items: center;
413 gap: clamp(18px, 4vw, 46px);
414 flex-wrap: wrap;
415 position: relative;
416 z-index: 2;
417 }
418 .classic-book {
419 position: relative;
420 width: 220px;
421 height: 300px;
422 border-radius: 3px 8px 8px 3px;
423 overflow: hidden;
424 display: flex;
425 align-items: center;
426 justify-content: center;
427 background: var(--bg-solid);
428 box-shadow: 12px 18px 38px rgba(15, 23, 42, 0.2);
429 transform: rotate(var(--book-tilt, 0deg));
430 transition: transform 0.22s ease, box-shadow 0.22s ease;
431 }
432 .classic-book:hover {
433 transform: rotate(var(--book-tilt, 0deg)) translateY(-7px) scale(1.03);
434 box-shadow: 16px 26px 54px rgba(15, 23, 42, 0.25);
435 }
436 .classic-book::before {
437 content: "";
438 position: absolute;
439 top: 0; left: 0;
440 width: 28px; height: 100%;
441 background: linear-gradient(180deg, #9f2f2f 0 11%, #f0ca55 11% 15%, #9f2f2f 15% 22%, #f0ca55 22% 26%, #9f2f2f 26% 100%);
442 z-index: 3;
443 }
444 .classic-book::after {
445 content: "";
446 position: absolute;
447 left: 28px; right: 0; bottom: 0;
448 height: 17px;
449 background: repeating-linear-gradient(90deg, #e7e7e7 0 1px, #fafafa 1px 3px);
450 border-top: 1px solid rgba(0,0,0,0.16);
451 z-index: 3;
452 }
453 .classic-book-image {
454 position: absolute; inset: 0;
455 width: 100%; height: 100%;
456 object-fit: cover;
457 filter: saturate(1.04) contrast(1.02);
458 transform: scale(1.04);
459 }
460 .classic-book-shade {
461 position: absolute; inset: 0;
462 background: linear-gradient(180deg, rgba(0,0,0,0.18), rgba(0,0,0,0.52));
463 z-index: 1;
464 }
465 .classic-book-title {
466 position: relative;
467 z-index: 4;
468 color: #fff;
469 text-align: center;
470 font-size: 1.42rem;
471 font-weight: 950;
472 line-height: 1.25;
473 padding: 28px;
474 text-shadow: 0 2px 12px rgba(0,0,0,0.72);
475 }
476 .classic-leaf {
477 position: absolute;
478 top: -180px;
479 width: var(--leaf-size);
480 height: var(--leaf-size);
481 background: var(--leaf-image) center / contain no-repeat;
482 filter: blur(4px);
483 opacity: 0;
484 animation: classicFall var(--leaf-duration) linear infinite;
485 animation-delay: var(--leaf-delay);
486 pointer-events: none;
487 }
488
489 @keyframes sweep { 0%, 100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } }
490 @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(0.72); } }
491 @keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-13px); } }
492 @keyframes chip { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-9px); } }
493 @keyframes scan { from { transform: translateY(-10%); } to { transform: translateY(610%); } }
494 @keyframes memoryDrift {
495 0%, 100% { transform: translate3d(0, 0, 0) rotate(var(--memory-rotate)); }
496 50% { transform: translate3d(0, -18px, 0) rotate(calc(var(--memory-rotate) + 4deg)); }
497 }
498 @keyframes classicFall {
499 0% { transform: translate3d(0, -18vh, 0) rotate(-10deg); opacity: 0; }
500 12% { opacity: 0.65; }
501 88% { opacity: 0.42; }
502 100% { transform: translate3d(8vw, 124vh, 0) rotate(14deg); opacity: 0; }
503 }
504
505 @media (max-width: 980px) {
506 .global-header { position: sticky; flex-wrap: wrap; height: auto; min-height: 62px; padding: 10px 16px; }
507 .header-nav {
508 position: static; transform: none; order: 3; width: 100%;
509 justify-content: center; overflow-x: auto; padding-top: 10px;
510 border-top: 1px solid var(--border);
511 }
512 .hero { min-height: auto; padding-top: 36px; }
513 .hero-inner { grid-template-columns: 1fr; }
514 .hero-panel { order: -1; min-height: 430px; }
515 .character { width: min(330px, 84vw); }
516 .quick-launch { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 18px; }
517 .section-head { display: block; }
518 .section-copy { margin-top: 10px; }
519 .feature-grid { grid-template-columns: 1fr; }
520 .home-mode-switch { top: auto; right: 14px; bottom: 14px; }
521 }
522 @media (max-width: 620px) {
523 .hero-inner, .quick-launch, .section, .site-footer { width: min(100% - 24px, 1220px); }
524 .hero-title { font-size: clamp(3rem, 18vw, 5rem); }
525 .hero-panel { min-height: 360px; }
526 .orbit-card { font-size: 0.68rem; padding: 7px 8px; }
527 .orbit-a { left: 3%; top: 10%; }
528 .orbit-b { right: 1%; top: 40%; }
529 .orbit-c { left: 7%; bottom: 1%; }
530 .memory-float { opacity: 0.2; filter: blur(3px) saturate(1.02); }
531 .quick-launch { grid-template-columns: 1fr; }
532 .launch-card { min-height: 112px; }
533 .feature-panel { min-height: 300px; }
534 .classic-book { width: 180px; height: 250px; }
535 .site-footer { flex-direction: column; align-items: flex-start; }
536 .footer-links { justify-content: flex-start; }
537 .footer-copy { text-align: left; }
538 }
539 @media (prefers-reduced-motion: reduce) {
540 *, *::before, *::after {
541 animation-duration: 0.01ms !important;
542 animation-iteration-count: 1 !important;
543 scroll-behavior: auto !important;
544 transition-duration: 0.01ms !important;
545 }
546 #home-canvas { display: none; }
547 }
548 </style>
549 </head>
550 <body class="<%= (typeof theme !== 'undefined' && theme === 'dark') ? 'dark-mode' : '' %>">
551 <header class="global-header">
552 <a href="/hinana/index" aria-label="비나래 아카이브">
553 <img src="/image/<%= (typeof theme !== 'undefined' && theme === 'dark') ? 'archive1.png' : 'archive.png' %>" alt="비나래 아카이브" class="header-logo">
554 </a>
555 <nav class="header-nav">
556 <a class="nav-link" href="/hinana/index">Archive</a>
557 <a class="nav-link" href="/hinana/info">Info</a>
558 <a class="nav-link" href="/hinana/blog">Blog</a>
559 <a class="nav-link" href="/hinana/lounge">Lounge</a>
560 </nav>
561 <div class="header-actions">
562 <% if(username) { %>
563 <a class="login-pill" href="/hinana/userInfo"><i class="bi bi-person-circle"></i><%= username %></a>
564 <% } else { %>
565 <a class="login-pill" href="/login"><i class="bi bi-box-arrow-in-right"></i>Login</a>
566 <% } %>
567 <form action="/toggle-theme" method="POST" style="margin:0;">
568 <button type="submit" class="round-btn" title="테마 변경">
569 <i class="bi <%= (typeof theme !== 'undefined' && theme==='dark') ? 'bi-moon-stars-fill':'bi-sun-fill' %>"></i>
570 </button>
571 </form>
572 </div>
573 </header>
574
575 <% if (typeof classicHome !== 'undefined' && classicHome) { %>
576 <a class="home-mode-switch" href="/"><i class="bi bi-stars"></i>새 현관 보기</a>
577 <% } else { %>
578 <a class="home-mode-switch" href="/classic"><i class="bi bi-clock-history"></i>이전 현관 보기</a>
579 <% } %>
580
581 <% if (typeof classicHome !== 'undefined' && classicHome) { %>
582 <main class="classic-home">
583 <h1 class="classic-title">환영합니다!</h1>
584 <div class="classic-books" aria-label="이전 현관">
585 <a class="classic-book" href="/hinana/index" style="--book-tilt:-4deg;">
586 <img class="classic-book-image" src="/image/1.png" alt="">
587 <span class="classic-book-shade"></span>
588 <span class="classic-book-title">비나래 아카이브</span>
589 </a>
590 <a class="classic-book" href="/hinana/info" style="--book-tilt:2deg;">
591 <img class="classic-book-image" src="/image/2.png" alt="">
592 <span class="classic-book-shade"></span>
593 <span class="classic-book-title">비나래씨는요?</span>
594 </a>
595 <a class="classic-book" href="/hinana/lounge" style="--book-tilt:4deg;">
596 <img class="classic-book-image" src="/image/3.png" alt="">
597 <span class="classic-book-shade"></span>
598 <span class="classic-book-title">비나래 라운지</span>
599 </a>
600 </div>
601 <span class="classic-leaf" style="left:8vw; --leaf-size:220px; --leaf-duration:21s; --leaf-delay:-2s; --leaf-image:url('/image/4.png');"></span>
602 <span class="classic-leaf" style="left:28vw; --leaf-size:250px; --leaf-duration:24s; --leaf-delay:-7s; --leaf-image:url('/image/5.png');"></span>
603 <span class="classic-leaf" style="left:48vw; --leaf-size:210px; --leaf-duration:19s; --leaf-delay:-11s; --leaf-image:url('/image/6.png');"></span>
604 <span class="classic-leaf" style="left:68vw; --leaf-size:240px; --leaf-duration:23s; --leaf-delay:-4s; --leaf-image:url('/image/7.png');"></span>
605 <span class="classic-leaf" style="left:88vw; --leaf-size:280px; --leaf-duration:26s; --leaf-delay:-9s; --leaf-image:url('/image/8.png');"></span>
606 </main>
607 <% } else { %>
608 <main class="home-shell">
609 <canvas id="home-canvas" aria-hidden="true"></canvas>
610
611 <section class="hero">
612 <span class="memory-float" style="--memory-image:url('/image/1.png'); --memory-size:180px; --memory-left:7%; --memory-top:16%; --memory-rotate:-8deg; --memory-duration:7s;"></span>
613 <span class="memory-float" style="--memory-image:url('/image/2.png'); --memory-size:150px; --memory-left:78%; --memory-top:12%; --memory-rotate:9deg; --memory-duration:8s;"></span>
614 <span class="memory-float" style="--memory-image:url('/image/3.png'); --memory-size:210px; --memory-left:66%; --memory-top:66%; --memory-rotate:-5deg; --memory-duration:9s;"></span>
615 <span class="memory-float" style="--memory-image:url('/image/4.png'); --memory-size:130px; --memory-left:18%; --memory-top:70%; --memory-rotate:11deg; --memory-duration:7.5s;"></span>
616 <span class="memory-float" style="--memory-image:url('/image/5.png'); --memory-size:160px; --memory-left:44%; --memory-top:22%; --memory-rotate:4deg; --memory-duration:8.6s;"></span>
617 <div class="hero-inner">
618 <div class="hero-copy reveal">
619 <div class="eyebrow"><span class="signal"></span> Hinana Memory Gateway</div>
620 <h1 class="hero-title">
621 <span>비나래</span>
622 <span class="accent">아카이브</span>
623 </h1>
624 <p class="hero-lead">
625 기록, 블로그, 라운지, 작은 실험들이 한곳에 모여 있어요.
626 오늘은 어디부터 열어볼까요?
627 </p>
628 <div class="hero-actions">
629 <a class="action-btn primary" href="/hinana/index"><i class="bi bi-archive"></i> Archive 열기</a>
630 <a class="action-btn" href="/hinana/info"><i class="bi bi-person-lines-fill"></i> 비나래 소개</a>
631 <a class="action-btn" href="/hinana/blog"><i class="bi bi-journal-text"></i> Blog</a>
632 </div>
633 </div>
634
635 <div class="hero-panel reveal" data-tilt-area>
636 <div class="scan-frame"></div>
637 <img src="/image/hinana_train.png" alt="비나래 아카이브" class="character" data-tilt-layer="0.12">
638 <div class="orbit-card orbit-a"><i class="bi bi-broadcast-pin"></i> archive online</div>
639 <div class="orbit-card orbit-b"><i class="bi bi-stars"></i> fresh signal</div>
640 <div class="orbit-card orbit-c"><i class="bi bi-gem"></i> memory engine</div>
641 </div>
642 </div>
643 </section>
644
645 <section class="quick-launch" aria-label="빠른 이동">
646 <a class="launch-card reveal" href="/hinana/index">
647 <div class="launch-top">
648 <span class="launch-icon"><i class="bi bi-archive"></i></span>
649 <i class="bi bi-arrow-up-right launch-arrow"></i>
650 </div>
651 <div>
652 <div class="launch-title">Archive</div>
653 <div class="launch-desc">짧은 기록과 댓글이 쌓이는 메인 아카이브예요.</div>
654 </div>
655 </a>
656 <a class="launch-card reveal" href="/hinana/blog">
657 <div class="launch-top">
658 <span class="launch-icon"><i class="bi bi-journal-richtext"></i></span>
659 <i class="bi bi-arrow-up-right launch-arrow"></i>
660 </div>
661 <div>
662 <div class="launch-title">Blog</div>
663 <div class="launch-desc">조금 더 긴 글과 도서관 같은 기록을 볼 수 있어요.</div>
664 </div>
665 </a>
666 <a class="launch-card reveal" href="/hinana/lounge">
667 <div class="launch-top">
668 <span class="launch-icon"><i class="bi bi-moon-stars"></i></span>
669 <i class="bi bi-arrow-up-right launch-arrow"></i>
670 </div>
671 <div>
672 <div class="launch-title">Lounge</div>
673 <div class="launch-desc">라운지와 갤러리 쪽으로 이어지는 공간이에요.</div>
674 </div>
675 </a>
676 <a class="launch-card reveal" href="/hinana/gallery">
677 <div class="launch-top">
678 <span class="launch-icon"><i class="bi bi-images"></i></span>
679 <i class="bi bi-arrow-up-right launch-arrow"></i>
680 </div>
681 <div>
682 <div class="launch-title">Gallery</div>
683 <div class="launch-desc">이미지와 라운지 쪽 시각 자료를 둘러볼 수 있어요.</div>
684 </div>
685 </a>
686 </section>
687
688 <section class="section">
689 <div class="section-head reveal">
690 <h2 class="section-title">아카이브 지도</h2>
691 <p class="section-copy">
692 글과 이미지, 작은 도구들이 각자의 방처럼 이어져 있어요.
693 마음이 가는 문을 열고 천천히 둘러보세요.
694 </p>
695 </div>
696 <div class="feature-grid">
697 <a class="feature-panel feature-image reveal" href="/hinana/gallery">
698 <div class="feature-caption">
699 <h3>Gallery</h3>
700 <p>이미지와 브랜드 자산, 라운지 계열의 시각 자료를 둘러볼 수 있어요.</p>
701 </div>
702 </a>
703 <div class="feature-panel feature-list reveal">
704 <a class="mini-link" href="/hinana/archive-search">
705 <div>
706 <div class="mini-title">Archive Search</div>
707 <div class="mini-desc">블로그와 일본어 라운지 기록을 한 번에 검색해요.</div>
708 </div>
709 <i class="bi bi-arrow-right"></i>
710 </a>
711 <a class="mini-link" href="/hinana/developer">
712 <div>
713 <div class="mini-title">Developer</div>
714 <div class="mini-desc">검열된 공개용 코드 뷰어로 프로젝트 구조를 볼 수 있어요.</div>
715 </div>
716 <i class="bi bi-arrow-right"></i>
717 </a>
718 <a class="mini-link" href="/hinana/subway">
719 <div>
720 <div class="mini-title">Subway</div>
721 <div class="mini-desc">지하철 도착/열차 추적 도구로 이동해요.</div>
722 </div>
723 <i class="bi bi-arrow-right"></i>
724 </a>
725 <a class="mini-link" href="/hinana/tetris">
726 <div>
727 <div class="mini-title">Tetris</div>
728 <div class="mini-desc">가볍게 점수를 쌓고 책갈피를 받을 수 있어요.</div>
729 </div>
730 <i class="bi bi-arrow-right"></i>
731 </a>
732 <a class="mini-link" href="/hinana/train">
733 <div>
734 <div class="mini-title">Train</div>
735 <div class="mini-desc">기차 화면을 꾸미는 작은 장난감이에요.</div>
736 </div>
737 <i class="bi bi-arrow-right"></i>
738 </a>
739 </div>
740 </div>
741 </section>
742 </main>
743 <% } %>
744
745 <footer class="site-footer">
746 <div class="footer-brand">
747 <img src="/image/sign.png" alt="비나래" class="footer-logo">
748 <div>
749 <div class="footer-title">비나래 아카이브</div>
750 <div>Archive My Memory</div>
751 </div>
752 </div>
753 <div>
754 <div class="footer-links">
755 <a href="/hinana/index">Archive</a>
756 <a href="/hinana/info">Info</a>
757 <a href="/hinana/blog">Blog</a>
758 <a href="/hinana/lounge">Lounge</a>
759 <a href="/hinana/developer">Developer</a>
760 <a href="https://x.com/NoctchillHinana" target="_blank" rel="noreferrer">X</a>
761 </div>
762 <div class="footer-copy"><span class="footer-version">Ver. 8.0.3.2-Utsumi Aoba</span>ⓒ 2024~2026. 비나래 | hinana.moe</div>
763 </div>
764 </footer>
765
766 <script>
767 if ("serviceWorker" in navigator) { navigator.serviceWorker.register("/sw.js"); }
768
769 (function() {
770 var reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
771 var revealEls = document.querySelectorAll('.reveal');
772 if ('IntersectionObserver' in window) {
773 var observer = new IntersectionObserver(function(entries) {
774 entries.forEach(function(entry) {
775 if (entry.isIntersecting) {
776 entry.target.classList.add('is-visible');
777 observer.unobserve(entry.target);
778 }
779 });
780 }, { threshold: 0.14 });
781 revealEls.forEach(function(el) { observer.observe(el); });
782 } else {
783 revealEls.forEach(function(el) { el.classList.add('is-visible'); });
784 }
785
786 var tiltArea = document.querySelector('[data-tilt-area]');
787 if (tiltArea && !reduceMotion) {
788 tiltArea.addEventListener('mousemove', function(event) {
789 var rect = tiltArea.getBoundingClientRect();
790 var x = (event.clientX - rect.left) / rect.width - 0.5;
791 var y = (event.clientY - rect.top) / rect.height - 0.5;
792 tiltArea.querySelectorAll('[data-tilt-layer]').forEach(function(layer) {
793 var depth = Number(layer.getAttribute('data-tilt-layer') || 0);
794 layer.style.transform = 'translate3d(' + (x * depth * 110) + 'px,' + (y * depth * 110) + 'px,0)';
795 });
796 });
797 tiltArea.addEventListener('mouseleave', function() {
798 tiltArea.querySelectorAll('[data-tilt-layer]').forEach(function(layer) {
799 layer.style.transform = '';
800 });
801 });
802 }
803
804 var canvas = document.getElementById('home-canvas');
805 if (!canvas || reduceMotion) return;
806 var ctx = canvas.getContext('2d');
807 var particles = [];
808 var w = 0;
809 var h = 0;
810 var dpr = Math.min(window.devicePixelRatio || 1, 2);
811
812 function resize() {
813 w = window.innerWidth;
814 h = window.innerHeight;
815 canvas.width = Math.floor(w * dpr);
816 canvas.height = Math.floor(h * dpr);
817 canvas.style.width = w + 'px';
818 canvas.style.height = h + 'px';
819 ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
820 var count = Math.min(72, Math.max(28, Math.floor(w * h / 21000)));
821 particles = Array.from({ length: count }, function() {
822 return {
823 x: Math.random() * w,
824 y: Math.random() * h,
825 vx: (Math.random() - 0.5) * 0.25,
826 vy: (Math.random() - 0.5) * 0.25,
827 size: Math.random() * 2 + 1,
828 hue: Math.random() > 0.5 ? 198 : 157
829 };
830 });
831 }
832
833 function draw() {
834 ctx.clearRect(0, 0, w, h);
835 particles.forEach(function(p, index) {
836 p.x += p.vx;
837 p.y += p.vy;
838 if (p.x < -12) p.x = w + 12;
839 if (p.x > w + 12) p.x = -12;
840 if (p.y < -12) p.y = h + 12;
841 if (p.y > h + 12) p.y = -12;
842 ctx.fillStyle = 'hsla(' + p.hue + ', 86%, 56%, 0.42)';
843 ctx.fillRect(p.x, p.y, p.size, p.size);
844
845 for (var j = index + 1; j < particles.length; j++) {
846 var q = particles[j];
847 var dx = p.x - q.x;
848 var dy = p.y - q.y;
849 var dist = Math.sqrt(dx * dx + dy * dy);
850 if (dist < 88) {
851 ctx.strokeStyle = 'rgba(18, 152, 232,' + (0.11 * (1 - dist / 88)) + ')';
852 ctx.lineWidth = 1;
853 ctx.beginPath();
854 ctx.moveTo(p.x, p.y);
855 ctx.lineTo(q.x, q.y);
856 ctx.stroke();
857 }
858 }
859 });
860 requestAnimationFrame(draw);
861 }
862
863 resize();
864 window.addEventListener('resize', resize);
865 draw();
866 })();
867 </script>
868 </body>
869 </html>
870