{label}
- )} -diff --git a/src/pages/travel/Travel.css b/src/pages/travel/Travel.css
index 4782a25..40600b2 100644
--- a/src/pages/travel/Travel.css
+++ b/src/pages/travel/Travel.css
@@ -166,95 +166,16 @@
}
/* ═══════════════════════════════════════════════════
- MAP SECTION
+ ALBUMS SECTION — card grid
═══════════════════════════════════════════════════ */
-.tv-map-section {
+.tv-albums {
+ min-height: 120px;
+}
+
+.tv-albums__grid {
display: grid;
- gap: 28px;
- transition: opacity 0.35s ease;
-}
-
-.tv-map-section.is-dimmed {
- opacity: 0.3;
- pointer-events: none;
-}
-
-.tv-map-wrap {
- position: relative;
- border-radius: var(--tv-r-lg);
- overflow: hidden;
- border: 1px solid var(--tv-line-bright);
- box-shadow: 0 24px 64px rgba(0, 0, 0, 0.6);
-}
-
-.tv-map {
- width: 100%;
- height: 480px;
-}
-
-@media (max-width: 768px) {
- .tv-header {
- grid-template-columns: 1fr;
- }
-
- .tv-map {
- height: 300px;
- }
-
- /* 지도 높이 축소 */
- .tv-map {
- height: 35vh !important;
- }
-
- /* 라이트박스 풀스크린 */
- .lightbox {
- border-radius: 0;
- }
-
- .lightbox__inner {
- max-width: 100vw;
- max-height: 100vh;
- width: 100vw;
- height: 100vh;
- border-radius: 0;
- }
-}
-
-/* Leaflet map tooltip override */
-.map-tooltip {
- font-family: var(--tv-mono) !important;
- font-size: 10px !important;
- letter-spacing: 0.12em !important;
- text-transform: uppercase !important;
- background: rgba(15, 12, 9, 0.92) !important;
- border: 1px solid rgba(232, 221, 208, 0.2) !important;
- border-radius: 6px !important;
- color: #e8ddd0 !important;
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5) !important;
-}
-
-.map-tooltip::before {
- border-top-color: rgba(232, 221, 208, 0.15) !important;
-}
-
-/* Map overlay hint */
-.tv-map__overlay-hint {
- position: absolute;
- bottom: 20px;
- left: 50%;
- transform: translateX(-50%);
- background: rgba(15, 12, 9, 0.85);
- border: 1px solid rgba(232, 221, 208, 0.2);
- border-radius: 999px;
- padding: 7px 18px;
- pointer-events: none;
-}
-
-.tv-map__overlay-hint span {
- font-family: var(--tv-mono);
- font-size: 9px;
- letter-spacing: 0.24em;
- color: var(--tv-muted);
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
+ gap: 20px;
}
/* ── Loading / Error states ──────────────────────── */
@@ -308,686 +229,15 @@
letter-spacing: 0.08em;
}
-/* ═══════════════════════════════════════════════════
- ALBUM HEADER
-═══════════════════════════════════════════════════ */
-.tv-album-header {
- display: flex;
- justify-content: space-between;
- align-items: baseline;
- gap: 16px;
- padding: 12px 0;
- border-bottom: 1px solid var(--tv-line);
-}
-
-.tv-album-header__left {
- display: flex;
- flex-wrap: wrap;
- align-items: baseline;
- gap: 14px;
-}
-
-.tv-album-header__region {
- font-family: var(--tv-serif);
- font-size: 24px;
- font-weight: 600;
- letter-spacing: -0.01em;
-}
-
-.tv-album-header__albums {
- font-family: var(--tv-mono);
- font-size: 10px;
- color: var(--tv-muted);
- letter-spacing: 0.14em;
- text-transform: uppercase;
-}
-
-.tv-album-header__count {
- font-family: var(--tv-mono);
- font-size: 11px;
- color: var(--tv-dim);
- letter-spacing: 0.12em;
- flex-shrink: 0;
-}
-
-/* ═══════════════════════════════════════════════════
- PHOTO MOSAIC — 4-column editorial grid
-═══════════════════════════════════════════════════ */
-.photo-mosaic {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- grid-auto-rows: 240px;
- grid-auto-flow: dense;
- gap: 6px;
-}
-
-@media (max-width: 1024px) {
- .photo-mosaic {
- grid-template-columns: repeat(3, 1fr);
- grid-auto-rows: 200px;
- }
-}
-
-@media (max-width: 480px) {
- .photo-mosaic {
- grid-template-columns: repeat(2, 1fr);
- grid-auto-rows: 180px;
- gap: 4px;
- }
-}
-
-/* ═══════════════════════════════════════════════════
- PHOTO CARD
-═══════════════════════════════════════════════════ */
-.photo-card {
- position: relative;
- overflow: hidden;
- border-radius: var(--tv-r-sm);
- cursor: pointer;
- background: var(--tv-surface);
-
- /* Scroll-reveal */
- opacity: 0;
- transform: scale(0.97) translateY(10px);
- transition:
- opacity 0.5s ease,
- transform 0.5s ease,
- box-shadow 0.25s ease;
- transition-delay: var(--reveal-delay, 0ms);
-}
-
-.photo-card[data-revealed='true'] {
- opacity: 1;
- transform: scale(1) translateY(0);
-}
-
-/* Layout variants */
-.photo-card--hero {
- grid-column: span 2;
- grid-row: span 2;
-}
-
-.photo-card--tall {
- grid-row: span 2;
-}
-
-.photo-card--wide {
- grid-column: span 2;
-}
-
-/* Image */
-.photo-card img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- display: block;
- transition: transform 0.6s cubic-bezier(0.25, 0, 0, 1), filter 0.4s ease;
- filter: saturate(0.85) brightness(0.92);
-}
-
-.photo-card:hover img {
- transform: scale(1.04);
- filter: saturate(1) brightness(1);
-}
-
-/* Hover overlay */
-.photo-card__overlay {
- position: absolute;
- inset: 0;
- background: linear-gradient(
- 160deg,
- rgba(15, 12, 9, 0) 40%,
- rgba(15, 12, 9, 0.75) 100%
- );
- opacity: 0;
- transition: opacity 0.3s ease;
- display: flex;
- flex-direction: column;
- justify-content: flex-end;
- padding: 14px;
-}
-
-.photo-card:hover .photo-card__overlay {
- opacity: 1;
-}
-
-.photo-card__overlay-inner {
- display: flex;
- flex-direction: column;
- gap: 3px;
-}
-
-.photo-card__index {
- font-family: var(--tv-mono);
- font-size: 9px;
- letter-spacing: 0.2em;
- color: var(--accent, var(--tv-accent));
-}
-
-.photo-card__label {
- font-family: var(--tv-mono);
- font-size: 10px;
- color: rgba(232, 221, 208, 0.85);
- margin: 0;
- letter-spacing: 0.06em;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 100%;
-}
-
-/* Decorative print-border effect */
-.photo-card__frame {
- position: absolute;
- inset: 0;
- border-radius: var(--tv-r-sm);
- box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.06);
- pointer-events: none;
- transition: box-shadow 0.3s ease;
-}
-
-.photo-card:hover .photo-card__frame {
- box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16);
-}
-
-.photo-card:focus-visible {
- outline: 2px solid var(--tv-accent);
- outline-offset: 2px;
-}
-
-/* ═══════════════════════════════════════════════════
- MOSAIC FOOTER — sentinel + end message
-═══════════════════════════════════════════════════ */
-.mosaic-footer {
- display: flex;
- justify-content: center;
- align-items: center;
- padding: 24px 0 8px;
- min-height: 48px;
- grid-column: 1 / -1;
-}
-
-.mosaic-loading {
- display: flex;
- gap: 8px;
-}
-
-.mosaic-loading__dot {
- width: 5px;
- height: 5px;
- border-radius: 50%;
- background: var(--tv-accent);
- animation: tv-pulse 1.2s ease-in-out infinite;
-}
-
-.mosaic-loading__dot:nth-child(2) { animation-delay: 0.2s; }
-.mosaic-loading__dot:nth-child(3) { animation-delay: 0.4s; }
-
-.mosaic-end {
- font-family: var(--tv-mono);
- font-size: 10px;
- letter-spacing: 0.22em;
- color: var(--tv-dim);
- text-transform: uppercase;
- margin: 0;
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-.mosaic-end span {
- color: var(--tv-line-bright);
-}
-
-/* ═══════════════════════════════════════════════════
- FILM STRIP — thumbnail rail
-═══════════════════════════════════════════════════ */
-.filmstrip {
- display: grid;
- grid-template-columns: auto minmax(0, 1fr) auto;
- align-items: stretch;
- gap: 0;
- background: #0a0806;
- border-radius: 6px;
- overflow: hidden;
- border: 1px solid var(--tv-line);
-}
-
-.filmstrip__nav {
- width: 32px;
- background: rgba(15, 12, 9, 0.9);
- border: none;
- color: var(--tv-muted);
- font-size: 22px;
- cursor: pointer;
- transition: color 0.2s ease, background 0.2s ease;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
-}
-
-.filmstrip__nav:hover {
- color: var(--tv-text);
- background: rgba(15, 12, 9, 0.6);
-}
-
-.filmstrip__rail {
- display: flex;
- flex-direction: column;
- overflow: hidden;
- position: relative;
-}
-
-/* Perforation strip */
-.filmstrip__holes {
- display: flex;
- flex-direction: row;
- gap: 0;
- padding: 5px 8px;
- background: #0a0806;
- border-bottom: 1px solid rgba(255, 255, 255, 0.06);
- overflow: hidden;
-}
-
-.filmstrip__hole {
- width: 10px;
- height: 8px;
- flex-shrink: 0;
- margin-right: 14px;
- border-radius: 2px;
- background: var(--tv-surface);
- border: 1px solid rgba(255, 255, 255, 0.08);
- box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.6);
-}
-
-/* Thumbnail frames */
-.filmstrip__frames {
- display: flex;
- gap: 3px;
- padding: 5px 8px;
- overflow-x: auto;
- scroll-behavior: smooth;
- scrollbar-width: none;
-}
-
-.filmstrip__frames::-webkit-scrollbar {
- display: none;
-}
-
-.filmstrip__frame {
- position: relative;
- width: 68px;
- height: 52px;
- border-radius: 4px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- background: var(--tv-surface-2);
- padding: 0;
- cursor: pointer;
- flex-shrink: 0;
- overflow: hidden;
- transition: border-color 0.2s ease, transform 0.2s ease;
-}
-
-.filmstrip__frame img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- display: block;
- filter: saturate(0.7);
- transition: filter 0.2s ease;
-}
-
-.filmstrip__frame:hover img,
-.filmstrip__frame.is-active img {
- filter: saturate(1);
-}
-
-.filmstrip__frame:hover {
- transform: scale(1.06);
- border-color: rgba(255, 255, 255, 0.4);
-}
-
-.filmstrip__frame.is-active {
- border-color: var(--tv-accent);
- box-shadow: 0 0 0 1px var(--tv-accent);
-}
-
-.filmstrip__frame-num {
- position: absolute;
- bottom: 2px;
- right: 3px;
- font-family: var(--tv-mono);
- font-size: 7px;
- color: rgba(232, 221, 208, 0.6);
- letter-spacing: 0.06em;
- pointer-events: none;
- line-height: 1;
- text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
-}
-
-/* ═══════════════════════════════════════════════════
- LIGHTBOX — cinematic full-screen viewer
-═══════════════════════════════════════════════════ */
-.lightbox {
- position: fixed;
- inset: 0;
- background: rgba(10, 8, 6, 0.9);
- backdrop-filter: blur(var(--lb-blur, 6px));
- -webkit-backdrop-filter: blur(var(--lb-blur, 6px));
- z-index: 3000;
- display: grid;
- place-items: center;
-}
-
-.lightbox__inner {
- width: min(1280px, 98vw);
- max-height: 100dvh;
- display: grid;
- grid-template-rows: auto 1fr auto auto auto;
- gap: 0;
- overflow: hidden;
-}
-
-/* ── Top bar ──────────────────────────────────────── */
-.lightbox__topbar {
- display: grid;
- grid-template-columns: auto 1fr auto auto;
- align-items: center;
- gap: 16px;
- padding: 14px 20px;
- border-bottom: 1px solid var(--tv-line);
- background: rgba(10, 8, 6, 0.7);
-}
-
-.lightbox__counter {
- display: flex;
- align-items: baseline;
- gap: 4px;
- font-family: var(--tv-mono);
-}
-
-.lightbox__counter-current {
- font-size: 22px;
- font-weight: 400;
- line-height: 1;
-}
-
-.lightbox__counter-sep {
- font-size: 12px;
- color: var(--tv-line-bright);
-}
-
-.lightbox__counter-total {
- font-size: 12px;
- color: var(--tv-muted);
-}
-
-.lightbox__region {
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-.lightbox__region-dot {
- width: 6px;
- height: 6px;
- border-radius: 50%;
- background: var(--accent, var(--tv-accent));
- flex-shrink: 0;
-}
-
-.lightbox__region-name {
- font-family: var(--tv-serif);
- font-size: 15px;
- font-weight: 600;
- color: var(--tv-text);
- letter-spacing: 0.02em;
-}
-
-.lightbox__album {
- font-family: var(--tv-mono);
- font-size: 9px;
- text-transform: uppercase;
- letter-spacing: 0.18em;
- color: var(--tv-muted);
- padding-left: 10px;
- border-left: 1px solid var(--tv-line-bright);
- margin-left: 2px;
-}
-
-.lightbox__controls {
- display: flex;
- align-items: center;
- gap: 12px;
-}
-
-.lb-control {
- display: flex;
- align-items: center;
- gap: 7px;
- font-family: var(--tv-mono);
- font-size: 9px;
- text-transform: uppercase;
- letter-spacing: 0.18em;
- color: var(--tv-muted);
- cursor: pointer;
-}
-
-.lb-control input[type='range'] {
- appearance: none;
- -webkit-appearance: none;
- width: 100px;
- height: 3px;
- background: rgba(232, 221, 208, 0.15);
- border-radius: 999px;
- outline: none;
-}
-
-.lb-control input[type='range']::-webkit-slider-thumb {
- appearance: none;
- -webkit-appearance: none;
- width: 12px;
- height: 12px;
- border-radius: 50%;
- background: var(--tv-text);
- cursor: pointer;
- border: 1px solid rgba(255, 255, 255, 0.4);
-}
-
-.lb-control input[type='range']::-moz-range-thumb {
- width: 12px;
- height: 12px;
- border-radius: 50%;
- background: var(--tv-text);
- cursor: pointer;
- border: 1px solid rgba(255, 255, 255, 0.4);
-}
-
-.lb-control__val {
- font-size: 9px;
- min-width: 16px;
- text-align: right;
-}
-
-.lightbox__close {
- width: 32px;
- height: 32px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 50%;
- border: 1px solid rgba(232, 221, 208, 0.18);
- background: rgba(15, 12, 9, 0.8);
- color: var(--tv-text);
- cursor: pointer;
- transition: border-color 0.2s ease, background 0.2s ease;
- flex-shrink: 0;
-}
-
-.lightbox__close:hover {
- border-color: rgba(232, 221, 208, 0.5);
- background: rgba(232, 221, 208, 0.08);
-}
-
-/* ── Photo stage ──────────────────────────────────── */
-.lightbox__stage {
- display: grid;
- grid-template-columns: 56px 1fr 56px;
- align-items: center;
- gap: 0;
- min-height: 0;
- padding: 12px 0;
-}
-
-.lightbox__frame {
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
- height: clamp(300px, 58vh, 700px);
- overflow: hidden;
-}
-
-.lightbox__photo {
- max-width: 100%;
- max-height: 100%;
- object-fit: contain;
- border-radius: 4px;
- display: block;
-}
-
-.lightbox__photo.slide-next {
- animation: lb-slide-in-right 280ms cubic-bezier(0.25, 0, 0.25, 1) forwards;
-}
-
-.lightbox__photo.slide-prev {
- animation: lb-slide-in-left 280ms cubic-bezier(0.25, 0, 0.25, 1) forwards;
-}
-
-@keyframes lb-slide-in-right {
- from { opacity: 0; transform: translateX(24px) scale(0.98); }
- to { opacity: 1; transform: translateX(0) scale(1); }
-}
-
-@keyframes lb-slide-in-left {
- from { opacity: 0; transform: translateX(-24px) scale(0.98); }
- to { opacity: 1; transform: translateX(0) scale(1); }
-}
-
-/* Decorative film frame border */
-.lightbox__photo-frame {
- position: absolute;
- inset: 0;
- pointer-events: none;
- border-radius: 4px;
- box-shadow:
- inset 0 0 0 1px rgba(255, 255, 255, 0.06),
- 0 2px 24px rgba(0, 0, 0, 0.5);
-}
-
-/* Navigation arrows */
-.lightbox__arrow {
- width: 44px;
- height: 44px;
- border-radius: 12px;
- border: 1px solid rgba(232, 221, 208, 0.18);
- background: rgba(15, 12, 9, 0.85);
- color: var(--tv-text);
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- justify-self: center;
- position: relative;
- transition: border-color 0.2s ease, transform 0.2s ease, background 0.2s ease;
-}
-
-.lightbox__arrow:hover {
- border-color: rgba(232, 221, 208, 0.45);
- background: rgba(232, 221, 208, 0.06);
- transform: scale(1.05);
-}
-
-.lightbox__arrow:disabled {
- opacity: 0.25;
- cursor: not-allowed;
- transform: none;
-}
-
-.lightbox__arrow.is-loading {
- pointer-events: none;
-}
-
-.lightbox__spinner {
- width: 18px;
- height: 18px;
- border-radius: 50%;
- border: 2px solid rgba(232, 221, 208, 0.25);
- border-top-color: var(--tv-accent);
- animation: tv-spin 0.7s linear infinite;
-}
-
-@keyframes tv-spin {
- to { transform: rotate(360deg); }
-}
-
-/* Photo meta */
-.lightbox__meta {
- padding: 6px 20px;
- font-family: var(--tv-mono);
- font-size: 9px;
- text-transform: uppercase;
- letter-spacing: 0.18em;
- color: var(--tv-muted);
- margin: 0;
- border-top: 1px solid var(--tv-line);
-}
-
-.lightbox__meta span {
- color: var(--tv-dim);
-}
-
-/* Toast */
-.lightbox__toast {
- position: absolute;
- left: 20px;
- bottom: 16px;
- background: rgba(15, 12, 9, 0.92);
- border: 1px solid rgba(232, 221, 208, 0.2);
- border-radius: 999px;
- padding: 7px 14px;
- font-family: var(--tv-mono);
- font-size: 10px;
- letter-spacing: 0.12em;
- color: var(--tv-text);
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
- pointer-events: none;
- animation: lb-toast-in 0.22s ease;
-}
-
-@keyframes lb-toast-in {
- from { opacity: 0; transform: translateY(6px); }
- to { opacity: 1; transform: translateY(0); }
-}
-
-/* ═══════════════════════════════════════════════════
- SCROLL REVEAL
-═══════════════════════════════════════════════════ */
-[data-reveal] {
- opacity: 0;
- transform: translateY(20px);
- transition: opacity 0.6s ease, transform 0.6s ease;
-}
-
-[data-reveal][data-revealed='true'] {
- opacity: 1;
- transform: translateY(0);
-}
-
/* ═══════════════════════════════════════════════════
RESPONSIVE
═══════════════════════════════════════════════════ */
+@media (max-width: 768px) {
+ .tv-header {
+ grid-template-columns: 1fr;
+ }
+}
+
@media (max-width: 480px) {
.travel {
gap: 28px;
@@ -1002,45 +252,9 @@
font-size: clamp(40px, 12vw, 60px);
}
- .lightbox__topbar {
- grid-template-columns: auto 1fr auto;
- gap: 8px;
- padding: 10px 12px;
- }
-
- .lightbox__controls {
- display: none;
- }
-
- .lightbox__stage {
- grid-template-columns: 44px 1fr 44px;
- padding: 6px 0;
- }
-
- .lightbox__frame {
- height: clamp(240px, 50vh, 480px);
- }
-
- .filmstrip__frame {
- width: 56px;
- height: 44px;
- }
-
- .photo-mosaic {
- grid-template-columns: repeat(2, 1fr);
- }
-
- .photo-card--hero {
- grid-column: span 2;
- grid-row: span 1;
- }
-
- .photo-card--wide {
- grid-column: span 2;
- }
-
- .photo-mosaic {
+ .tv-albums__grid {
grid-template-columns: 1fr;
+ gap: 14px;
}
}
@@ -1048,19 +262,8 @@
REDUCED MOTION
═══════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
- .photo-card,
- [data-reveal] {
- opacity: 1 !important;
- transform: none !important;
- transition: none !important;
- }
-
- .lightbox__photo.slide-next,
- .lightbox__photo.slide-prev {
+ .tv-state__loader span {
animation: none !important;
- }
-
- .photo-card img {
- transition: none !important;
+ opacity: 1 !important;
}
}
diff --git a/src/pages/travel/Travel.jsx b/src/pages/travel/Travel.jsx
index 14a5b29..47c507f 100644
--- a/src/pages/travel/Travel.jsx
+++ b/src/pages/travel/Travel.jsx
@@ -1,865 +1,83 @@
-import React, { useCallback, useEffect, useRef, useState } from 'react';
-import { GeoJSON, MapContainer, TileLayer, useMap } from 'react-leaflet';
-import 'leaflet/dist/leaflet.css';
+import React, { useState, useCallback } from 'react';
+import useTravelData from './useTravelData';
+import MiniMap, { getRegionAccent } from './MiniMap';
+import AlbumCard from './AlbumCard';
+import AlbumDetail from './AlbumDetail';
import './Travel.css';
-import { useIsMobile } from '../../hooks/useIsMobile';
-import PullToRefresh from '../../components/PullToRefresh';
-
-/* ─────────────────────────────────────────────
- Constants
-───────────────────────────────────────────── */
-const PAGE_SIZE = 20;
-const THUMB_STRIP_LIMIT = 36;
-
-/* ─────────────────────────────────────────────
- Region accent palette — each destination
- gets its own identity color
-───────────────────────────────────────────── */
-const REGION_PALETTE = {
- japan: '#e05c4b', // vermillion
- korea: '#d64f6e', // rose
- china: '#c84b3a', // crimson
- europe: '#5b8fc4', // cobalt
- france: '#6f8fc4', // slate blue
- italy: '#78a46e', // olive
- spain: '#c4844a', // terracotta
- sea: '#4aad8b', // jade
- thailand: '#4aad8b',
- vietnam: '#5faa78',
- bali: '#7aac5a',
- indonesia: '#8aaa4a',
- america: '#b4885c', // desert
- usa: '#b4885c',
- canada: '#6a9890', // glacier
- africa: '#c47c3c', // ochre
- middle: '#c4a24a', // sand gold
- dubai: '#c4a24a',
- default: '#c8905e', // warm amber
-};
-
-const getRegionAccent = (regionId = '') => {
- const id = regionId.toLowerCase();
- for (const [key, color] of Object.entries(REGION_PALETTE)) {
- if (key !== 'default' && id.includes(key)) return color;
- }
- return REGION_PALETTE.default;
-};
-
-/* ─────────────────────────────────────────────
- Editorial card layout pattern
- 4-column grid, repeating every 7 cards
-───────────────────────────────────────────── */
-const LAYOUT_SEQ = ['hero', '', 'tall', '', '', 'wide', ''];
-const getCardLayout = (index) => LAYOUT_SEQ[index % LAYOUT_SEQ.length];
-
-/* ─────────────────────────────────────────────
- Utility functions
-───────────────────────────────────────────── */
-const normalizePhotos = (items = []) =>
- items
- .map((item) => {
- if (typeof item === 'string') return { src: item, title: '' };
- if (!item) return null;
- return {
- src: item.thumb || item.url || item.path || item.src || '',
- title: item.title || item.name || item.file || '',
- original: item.url || item.path || item.src || '',
- file: item.file || '',
- album: item.album || '',
- };
- })
- .filter((item) => item && item.src);
-
-const hasSummaryInfo = (payload) =>
- payload &&
- (Object.prototype.hasOwnProperty.call(payload, 'total') ||
- Object.prototype.hasOwnProperty.call(payload, 'matched_albums'));
-
-const getPhotoLabel = (photo) => {
- if (!photo) return '';
- if (photo.title) return photo.title;
- if (photo.file) return photo.file;
- if (!photo.src) return '';
- const parts = photo.src.split('/');
- return parts[parts.length - 1];
-};
-
-const getStripRange = (length, center) => {
- if (length <= THUMB_STRIP_LIMIT) return [0, length];
- const half = Math.floor(THUMB_STRIP_LIMIT / 2);
- let start = Math.max(0, center - half);
- let end = start + THUMB_STRIP_LIMIT;
- if (end > length) {
- end = length;
- start = end - THUMB_STRIP_LIMIT;
- }
- return [start, end];
-};
-
-/* ─────────────────────────────────────────────
- PhotoCard — single photo tile
-───────────────────────────────────────────── */
-const PhotoCard = ({ photo, index, onSelect, regionAccent }) => {
- const label = getPhotoLabel(photo);
- const layout = getCardLayout(index);
- const isEager = index < 4;
-
- return (
- {label} {
- if (photo.original && e.currentTarget.src !== photo.original) {
- e.currentTarget.src = photo.original;
- }
- }}
- />
-
- {/* Hover overlay */}
-
- — - {photos.length} frames developed - — -
- )} -- {photo.album} - {photo.file ? · {photo.file} : null} -
- )} - - {/* ── Film strip ───────────────────────── */} -Currently viewing
-{selectedRegion.name}
- {photoSummary && ( -- {photoSummary.total ?? photos.length} photos -
- )} +{selectedRegion}
Developing film…
+Loading albums…
{error}
} - {!loading && !error && selectedRegion && photos.length === 0 && ( + + {!loadingAlbums && filteredAlbums.length === 0 && (- 이 지역에는 아직 사진이 없습니다. + {selectedRegion + ? '이 지역에는 아직 앨범이 없습니다.' + : '지도에서 지역을 선택하거나 전체 앨범을 둘러보세요.'}
)} - {/* ── Photo Mosaic ─────────────────── */} - {!loading && !error && selectedRegion && photos.length > 0 && ( -