diff --git a/frontend/static/css/common/dino-loader.css b/frontend/static/css/common/dino-loader.css new file mode 100644 index 0000000..6a9c82c --- /dev/null +++ b/frontend/static/css/common/dino-loader.css @@ -0,0 +1,71 @@ +/* 귀여운 아기 공룡 로딩 애니메이션 (개별 4장 즉시 교체) + * + * 프레임 이미지 (dino_frame_01.png 제외, 가로 정렬 순서대로 4장): + * frontend/static/img/dino_frame_02.png ~ dino_frame_05.png (각 248 x 263, 투명 배경) + * + * 4장을 겹쳐 두고 매 순간 한 장만 즉시 켜고 끕니다(opacity 0/1 즉시 전환). + * 따라서 프레임이 바뀔 때 흐릿한 페이드가 전혀 없습니다. + * + * div 로 그렸던 이전(CSS) 버전은 dino-loader.v1-backup.css 에 백업되어 있습니다. + */ +.dino-loader { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1.25rem; + padding: 3.5rem 1rem; + user-select: none; +} + +/* 5장을 같은 자리에 겹쳐 둠 */ +.dino-sprite { + position: relative; + width: 150px; + height: 128px; +} +.dino-frame { + position: absolute; + inset: 0; + background-repeat: no-repeat; + background-position: center bottom; + background-size: auto 122px; + image-rendering: auto; + opacity: 0; /* 기본은 숨김, 자기 차례에만 켜짐 */ + /* 0.4초에 4장 = 프레임당 0.1초. steps 가 아닌 즉시 on/off 키프레임 사용 */ + animation: dino-flip 0.4s linear infinite; +} +.dino-frame.f2 { background-image: url('/static/img/dino_frame_02.png'); animation-delay: 0s; } +.dino-frame.f3 { background-image: url('/static/img/dino_frame_03.png'); animation-delay: 0.1s; } +.dino-frame.f4 { background-image: url('/static/img/dino_frame_04.png'); animation-delay: 0.2s; } +.dino-frame.f5 { background-image: url('/static/img/dino_frame_05.png'); animation-delay: 0.3s; } + +/* 로딩 텍스트 */ +.dino-loading-text { + font-size: 0.95rem; + font-weight: 600; + color: #566168; + letter-spacing: 0.01em; +} +.dino-loading-text .dot { + animation: dino-dot 1.4s infinite; +} +.dino-loading-text .dot:nth-child(2) { animation-delay: 0.2s; } +.dino-loading-text .dot:nth-child(3) { animation-delay: 0.4s; } + +/* 각 프레임: 자기 차례(전체의 1/4 = 0~25%)에만 불투명, 나머지는 즉시 투명. + * 24.999%->25% 사이에서 opacity 1->0 으로 사실상 즉시 꺼져 페이드가 없습니다. */ +@keyframes dino-flip { + 0%, 24.999% { opacity: 1; } + 25%, 100% { opacity: 0; } +} + +@keyframes dino-dot { + 0%, 60%, 100% { opacity: 0.2; } + 30% { opacity: 1; } +} + +@media (prefers-reduced-motion: reduce) { + .dino-frame { animation: none; } + .dino-frame.f2 { opacity: 1; } /* 정지 시 첫 프레임만 표시 */ +} diff --git a/frontend/static/css/common/dino-loader.v1-backup.css b/frontend/static/css/common/dino-loader.v1-backup.css new file mode 100644 index 0000000..f66ae40 --- /dev/null +++ b/frontend/static/css/common/dino-loader.v1-backup.css @@ -0,0 +1,199 @@ +/* 귀여운 공룡 로딩 애니메이션 */ +.dino-loader { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1.25rem; + padding: 3.5rem 1rem; + user-select: none; +} + +.dino-scene { + position: relative; + width: 220px; + height: 130px; +} + +/* 땅 (스크롤되는 줄무늬) */ +.dino-ground { + position: absolute; + left: 0; + bottom: 0; + width: 100%; + height: 8px; + border-radius: 999px; + background: repeating-linear-gradient( + 90deg, + #c7d1ee 0, + #c7d1ee 14px, + transparent 14px, + transparent 28px + ); + background-size: 28px 100%; + animation: dino-ground-scroll 0.5s linear infinite; +} + +/* 달려가는 공룡 (위아래로 깡총) */ +.dino { + position: absolute; + left: 50%; + bottom: 8px; + width: 78px; + height: 84px; + transform: translateX(-50%); + transform-origin: bottom center; + animation: dino-hop 0.5s ease-in-out infinite; +} + +/* 몸통 */ +.dino-body { + position: absolute; + left: 8px; + bottom: 14px; + width: 50px; + height: 44px; + background: #6cc24a; + border-radius: 18px 22px 16px 14px; + box-shadow: inset -4px -4px 0 rgba(0, 0, 0, 0.08); +} + +/* 머리 */ +.dino-head { + position: absolute; + right: 2px; + top: 0; + width: 40px; + height: 36px; + background: #6cc24a; + border-radius: 16px 18px 6px 16px; + box-shadow: inset -4px -3px 0 rgba(0, 0, 0, 0.08); +} + +/* 주둥이 */ +.dino-snout { + position: absolute; + right: -8px; + top: 14px; + width: 16px; + height: 16px; + background: #5cb13e; + border-radius: 6px 8px 8px 4px; +} + +/* 눈 */ +.dino-eye { + position: absolute; + right: 8px; + top: 9px; + width: 11px; + height: 11px; + background: #ffffff; + border-radius: 50%; +} +.dino-eye::after { + content: ""; + position: absolute; + right: 1px; + top: 2px; + width: 5px; + height: 5px; + background: #1f2937; + border-radius: 50%; + animation: dino-blink 2.4s ease-in-out infinite; +} + +/* 등 가시 */ +.dino-spike { + position: absolute; + top: -6px; + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 9px solid #4f9e34; +} +.dino-spike.s1 { left: 12px; } +.dino-spike.s2 { left: 24px; } +.dino-spike.s3 { left: 36px; } + +/* 꼬리 */ +.dino-tail { + position: absolute; + left: -10px; + bottom: 22px; + width: 0; + height: 0; + border-top: 12px solid transparent; + border-bottom: 12px solid transparent; + border-right: 18px solid #6cc24a; +} + +/* 다리 (번갈아 달리기) */ +.dino-leg { + position: absolute; + bottom: 0; + width: 12px; + height: 18px; + background: #4f9e34; + border-radius: 0 0 5px 5px; +} +.dino-leg.front { + left: 30px; + animation: dino-run-front 0.5s ease-in-out infinite; +} +.dino-leg.back { + left: 14px; + animation: dino-run-back 0.5s ease-in-out infinite; +} + +/* 로딩 텍스트 */ +.dino-loading-text { + font-size: 0.95rem; + font-weight: 600; + color: #566168; + letter-spacing: 0.01em; +} +.dino-loading-text .dot { + animation: dino-dot 1.4s infinite; +} +.dino-loading-text .dot:nth-child(2) { animation-delay: 0.2s; } +.dino-loading-text .dot:nth-child(3) { animation-delay: 0.4s; } + +@keyframes dino-hop { + 0%, 100% { transform: translateX(-50%) translateY(0); } + 50% { transform: translateX(-50%) translateY(-12px); } +} + +@keyframes dino-ground-scroll { + from { background-position: 0 0; } + to { background-position: -28px 0; } +} + +@keyframes dino-run-front { + 0%, 100% { transform: translateY(0) rotate(0deg); } + 50% { transform: translateY(-6px) rotate(-18deg); } +} + +@keyframes dino-run-back { + 0%, 100% { transform: translateY(-6px) rotate(18deg); } + 50% { transform: translateY(0) rotate(0deg); } +} + +@keyframes dino-blink { + 0%, 92%, 100% { transform: scaleY(1); } + 96% { transform: scaleY(0.1); } +} + +@keyframes dino-dot { + 0%, 60%, 100% { opacity: 0.2; } + 30% { opacity: 1; } +} + +/* 깡총 멈추고 싶을 때를 위한 접근성 배려 */ +@media (prefers-reduced-motion: reduce) { + .dino, + .dino-ground, + .dino-leg, + .dino-eye::after { animation: none; } +} diff --git a/frontend/static/img/dino_frame_02.png b/frontend/static/img/dino_frame_02.png new file mode 100644 index 0000000..e046eb1 Binary files /dev/null and b/frontend/static/img/dino_frame_02.png differ diff --git a/frontend/static/img/dino_frame_03.png b/frontend/static/img/dino_frame_03.png new file mode 100644 index 0000000..450f5e7 Binary files /dev/null and b/frontend/static/img/dino_frame_03.png differ diff --git a/frontend/static/img/dino_frame_04.png b/frontend/static/img/dino_frame_04.png new file mode 100644 index 0000000..1dcd63f Binary files /dev/null and b/frontend/static/img/dino_frame_04.png differ diff --git a/frontend/static/img/dino_frame_05.png b/frontend/static/img/dino_frame_05.png new file mode 100644 index 0000000..2f06503 Binary files /dev/null and b/frontend/static/img/dino_frame_05.png differ diff --git a/frontend/static/js/search/list.js b/frontend/static/js/search/list.js index f7878e6..46c376a 100644 --- a/frontend/static/js/search/list.js +++ b/frontend/static/js/search/list.js @@ -62,6 +62,8 @@ document.addEventListener('DOMContentLoaded', async () => { renderCurrentPage(); } catch (error) { console.error('Fetch error:', error); + results = []; + renderCurrentPage(); alert('검색 결과를 불러오지 못했습니다.'); } diff --git a/frontend/templates/search/list.html b/frontend/templates/search/list.html index 9063c0f..713ba7f 100644 --- a/frontend/templates/search/list.html +++ b/frontend/templates/search/list.html @@ -89,6 +89,7 @@ .no-scrollbar::-webkit-scrollbar { display: none; } + @@ -187,9 +188,18 @@

-
- sync -

검색 결과를 불러오는 중입니다...

+
+
+
+ + + + +
+

+ 웹스토어에서 열심히 찾아오는 중... +

+