1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js"; 2import { a as api } from "./index-w5lXls15.js"; 3import { g as getColorsFromHash, n as getOptimizedImageUrl, s as slugify } from "./utils-CEVb7nN1.js"; 4import { g as getBilingualTitle, p as parseMultilingualData } from "./bilingual-CBk-npe4.js"; 5import { u as useTranslation } from "./vendor-i18n-BC7ZMXWQ.js"; 6import { ck as Layers, cM as RefreshCw, bi as Sparkles, bf as Search, aW as X, cA as SlidersHorizontal, cw as LayoutGrid, cx as List, bg as Music, bC as Gamepad2, dX as Film, de as Flame, cI as Disc, bn as Globe, e9 as Radio, cB as ChevronUp, cC as ChevronDown, bO as Library, bb as User, cn as ArrowRight, dO as Calendar } from "./vendor-D0P4XgNj.js"; 7const CATEGORIES = [ 8 { id: "all", labelKey: "collections.allCategories", defaultLabel: "All", icon: Sparkles }, 9 { id: "classical", labelKey: "collections.catClassical", defaultLabel: "Classical & Masters", icon: Music }, 10 { id: "games_anime", labelKey: "collections.catGamesAnime", defaultLabel: "Video Games & Anime", icon: Gamepad2 }, 11 { id: "stage_cinema", labelKey: "collections.catStageScreen", defaultLabel: "Stage & Screen", icon: Film }, 12 { id: "pop_rock", labelKey: "collections.catPopRock", defaultLabel: "Pop, Rock & Metal", icon: Flame }, 13 { id: "jazz_blues", labelKey: "collections.catJazzBlues", defaultLabel: "Jazz, Blues & Soul", icon: Disc }, 14 { id: "folk_world", labelKey: "collections.catFolkWorld", defaultLabel: "Folk & Traditional", icon: Globe }, 15 { id: "electronic", labelKey: "collections.catElectronic", defaultLabel: "Electronic & Dance", icon: Radio } 16]; 17function getCollectionCategory(c) { 18 const slug = (c.slug || "").toLowerCase(); 19 const title = (c.title || "").toLowerCase(); 20 const combined = `${slug} ${title}`; 21 if (/classical|baroque|chopin|bach|mozart|beethoven|cello|flute|woodwind|organ|violin|etude|exam-piece|encore|urtext|fugue|counterpoint/.test(combined)) { 22 return "classical"; 23 } 24 if (/game|touhou|ghibli|anime|visual-novel|demoscene|tracker/.test(combined)) { 25 return "games_anime"; 26 } 27 if (/broadway|musical|movie|soundtrack|disney|trailer|zimmer|morricone|tv-show|barbershop|circus|vaudeville/.test(combined)) { 28 return "stage_cinema"; 29 } 30 if (/beatles|pop|rock|metal|disco|grunge|punk|ballad|k-pop|j-pop/.test(combined)) { 31 return "pop_rock"; 32 } 33 if (/jazz|blues|swing|bebop|soul|motown|ragtime|bossa|r&b/.test(combined)) { 34 return "jazz_blues"; 35 } 36 if (/electronic|dance|dubstep|synthwave|retrowave|trance|techno|house|lo-fi|edm|ambient|drone|chillout/.test(combined)) { 37 return "electronic"; 38 } 39 return "folk_world"; 40} 41const CollectionIndexPage = ({ onNavigate }) => { 42 const { t, i18n } = useTranslation(); 43 const [collections, setCollections] = reactExports.useState([]); 44 const [isLoading, setIsLoading] = reactExports.useState(true); 45 const [error, setError] = reactExports.useState(null); 46 const [imageErrors, setImageErrors] = reactExports.useState({}); 47 const [searchQuery, setSearchQuery] = reactExports.useState(""); 48 const [debouncedQuery, setDebouncedQuery] = reactExports.useState(""); 49 const [selectedCategory, setSelectedCategory] = reactExports.useState("all"); 50 const [sortBy, setSortBy] = reactExports.useState("curated"); 51 const [viewMode, setViewMode] = reactExports.useState(() => { 52 return typeof window !== "undefined" && localStorage.getItem("hamie_collection_index_view_mode") || "grid"; 53 }); 54 const [matchingSections, setMatchingSections] = reactExports.useState([]); 55 const [isSearchingSections, setIsSearchingSections] = reactExports.useState(false); 56 const [showAllSections, setShowAllSections] = reactExports.useState(false); 57 const searchInputRef = reactExports.useRef(null); 58 reactExports.useEffect(() => { 59 if (typeof window !== "undefined") { 60 localStorage.setItem("hamie_collection_index_view_mode", viewMode); 61 } 62 }, [viewMode]); 63 reactExports.useEffect(() => { 64 const handleKeyDown = (e) => { 65 var _a, _b; 66 if (e.key === "/" && document.activeElement !== searchInputRef.current && !(document.activeElement instanceof HTMLInputElement || document.activeElement instanceof HTMLTextAreaElement)) { 67 e.preventDefault(); 68 (_a = searchInputRef.current) == null ? void 0 : _a.focus(); 69 } else if (e.key === "Escape" && document.activeElement === searchInputRef.current) { 70 setSearchQuery(""); 71 (_b = searchInputRef.current) == null ? void 0 : _b.blur(); 72 } 73 }; 74 window.addEventListener("keydown", handleKeyDown); 75 return () => window.removeEventListener("keydown", handleKeyDown); 76 }, []); 77 reactExports.useEffect(() => { 78 const timer = setTimeout(() => { 79 setDebouncedQuery(searchQuery.trim()); 80 }, 250); 81 return () => clearTimeout(timer); 82 }, [searchQuery]); 83 reactExports.useEffect(() => { 84 if (debouncedQuery.length < 2) { 85 setMatchingSections([]); 86 setIsSearchingSections(false); 87 return; 88 } 89 let isMounted = true; 90 setIsSearchingSections(true); 91 api.searchCollectionSections(debouncedQuery, 24).then((res) => { 92 if (isMounted) { 93 setMatchingSections(res); 94 setIsSearchingSections(false); 95 } 96 }).catch(() => { 97 if (isMounted) { 98 setMatchingSections([]); 99 setIsSearchingSections(false); 100 } 101 }); 102 return () => { 103 isMounted = false;
104 }; 105 }, [debouncedQuery]); 106 reactExports.useEffect(() => { 107 document.title = t("collections.pageTitle", "Browse Curated MIDI Collections | HamieNET"); 108 const metaDesc = document.querySelector('meta[name="description"]'); 109 if (metaDesc) { 110 metaDesc.setAttribute( 111 "content", 112 "Explore hand-crafted collections of MIDI files organized by theme, composer, style, and musical eras. Discover lists like Broadway Musicals, Classical Masters, and more." 113 ); 114 } 115 const fetchCollections = async () => { 116 try { 117 setIsLoading(true); 118 const data = await api.getCollections(); 119 setCollections(data); 120 setError(null); 121 } catch (err) { 122 console.error("[CollectionIndexPage Load Error]", err); 123 const errMsg = err instanceof Error ? err.message : "Failed to load collections. Please try again later."; 124 setError(errMsg); 125 } finally { 126 setIsLoading(false); 127 } 128 }; 129 fetchCollections(); 130 }, [t]); 131 const categoryCounts = reactExports.useMemo(() => { 132 const counts = { all: collections.length }; 133 for (const c of collections) { 134 const cat = getCollectionCategory(c); 135 counts[cat] = (counts[cat] || 0) + 1; 136 } 137 return counts; 138 }, [collections]); 139 const filteredCollections = reactExports.useMemo(() => { 140 let result = [...collections]; 141 if (selectedCategory !== "all") { 142 result = result.filter((c) => getCollectionCategory(c) === selectedCategory); 143 } 144 const q = searchQuery.trim().toLowerCase(); 145 if (q) { 146 result = result.filter((c) => { 147 const titleMatch = c.title.toLowerCase().includes(q); 148 const slugMatch = (c.slug || "").toLowerCase().includes(q); 149 const subMatch = c.subtitle && c.subtitle.toLowerCase().includes(q); 150 const descMatch = c.description && c.description.toLowerCase().includes(q); 151 return titleMatch || slugMatch || subMatch || descMatch; 152 }); 153 } 154 if (sortBy === "name_asc") { 155 result.sort((a, b) => a.title.localeCompare(b.title)); 156 } else if (sortBy === "name_desc") { 157 result.sort((a, b) => b.title.localeCompare(a.title)); 158 } else if (sortBy === "sections_desc") { 159 result.sort((a, b) => (b.sectionCount || 0) - (a.sectionCount || 0)); 160 } else if (sortBy === "newest") { 161 result.sort((a, b) => new Date(b.createdAt || 0).getTime() - new Date(a.createdAt || 0).getTime()); 162 } else { 163 result.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0)); 164 } 165 return result; 166 }, [collections, selectedCategory, searchQuery, sortBy]); 167 const hasActiveFilters = searchQuery.trim().length > 0 || selectedCategory !== "all"; 168 const handleClearFilters = () => { 169 setSearchQuery(""); 170 setSelectedCategory("all"); 171 setSortBy("curated"); 172 }; 173 if (isLoading) { 174 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center min-h-[60vh] bg-zinc-50 dark:bg-[#0a0a0a]", children: [
175 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-12 h-12 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin mb-4" }), 176 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-500 dark:text-zinc-400 text-sm font-semibold animate-pulse", children: t("collections.loading", "Loading themed collections...") }) 177 ] }); 178 } 179 if (error) { 180 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-md mx-auto my-20 p-8 rounded-2xl bg-red-50 dark:bg-red-950/20 border border-red-200 dark:border-red-900/30 text-center animate-in fade-in", children: [ 181 /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { className: "mx-auto text-red-500 dark:text-red-400 mb-4", size: 40 }), 182 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-bold text-red-800 dark:text-red-300 mb-2", children: t("collections.errorTitle", "Error Loading Collections") }), 183 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-red-600 dark:text-red-400 mb-6", children: error }),
184 /* @__PURE__ */ jsxRuntimeExports.jsxs( 185 "button", 186 { 187 onClick: () => window.location.reload(), 188 className: "inline-flex items-center gap-2 bg-red-600 hover:bg-red-700 text-white font-bold text-sm px-6 py-2.5 rounded-xl transition shadow-lg shadow-red-600/20", 189 children: [ 190 /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 14 }), 191 t("collections.retry", "Retry") 192 ] 193 } 194 ) 195 ] }); 196 } 197 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 sm:p-6 md:p-10 max-w-[2000px] mx-auto animate-in fade-in duration-700 bg-zinc-50 dark:bg-[#0a0a0a] min-h-full", children: [ 198 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-4xl mb-8", children: [ 199 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "inline-flex items-center gap-2 text-indigo-500 font-black text-xs uppercase tracking-widest mb-3 bg-indigo-50 dark:bg-indigo-950/30 px-3 py-1 rounded-full border border-indigo-100 dark:border-indigo-900/40", children: [
200 /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 14 }), 201 t("collections.curatedSoundscapes", "Curated Soundscapes") 202 ] }), 203 /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-3xl sm:text-4xl md:text-5xl font-black tracking-tight text-zinc-900 dark:text-white mb-4", children: t("collections.browseCollections", "Browse Collections") }), 204 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-600 dark:text-zinc-400 text-base md:text-lg font-medium leading-relaxed", children: t( 205 "collections.browseDescription", 206 "Discover hand-crafted selections of MIDI tracks grouped by themes, stage shows, composers, and styles. Each collection is carefully arranged to guide you through different musical journeys." 207 ) }) 208 ] }), 209 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-6 space-y-4", children: [ 210 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col md:flex-row gap-3 items-stretch md:items-center justify-between", children: [
211 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative flex-1 max-w-2xl", children: [ 212 /* @__PURE__ */ jsxRuntimeExports.jsx(Search, { className: "absolute left-4 top-1/2 -translate-y-1/2 text-zinc-400", size: 18 }), 213 /* @__PURE__ */ jsxRuntimeExports.jsx( 214 "input", 215 { 216 ref: searchInputRef, 217 type: "text", 218 value: searchQuery, 219 onChange: (e) => setSearchQuery(e.target.value), 220 placeholder: t("collections.searchPlaceholder", "Search collections, sections, themes, or composers..."), 221 className: "w-full pl-11 pr-24 py-3 rounded-2xl bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 text-sm font-semibold text-zinc-900 dark:text-white placeholder:text-zinc-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 shadow-sm transition" 222 } 223 ), 224 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1.5", children: searchQuery ? /* @__PURE__ */ jsxRuntimeExports.jsx( 225 "button", 226 { 227 onClick: () => setSearchQuery(""), 228 className: "p-1 rounded-lg text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition", 229 title: t("collections.clearSearch", "Clear Search"), 230 children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 16 }) 231 } 232 ) : /* @__PURE__ */ jsxRuntimeExports.jsx("kbd", { className: "hidden sm:inline-flex items-center gap-0.5 text-[10px] font-bold text-zinc-400 bg-zinc-100 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 px-1.5 py-0.5 rounded shadow-sm", children: "/" }) }) 233 ] }),
234 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 self-end md:self-auto shrink-0", children: [ 235 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-2xl px-3 py-1.5 shadow-sm", children: [ 236 /* @__PURE__ */ jsxRuntimeExports.jsx(SlidersHorizontal, { size: 14, className: "text-zinc-400 shrink-0" }), 237 /* @__PURE__ */ jsxRuntimeExports.jsxs( 238 "select", 239 { 240 value: sortBy, 241 onChange: (e) => setSortBy(e.target.value), 242 "aria-label": t("collections.sortBy", "Sort By"), 243 className: "text-xs font-bold text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-900 border-none focus:outline-none cursor-pointer py-1", 244 children: [ 245 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "curated", className: "text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-900", children: t("collections.sortCurated", "Curated Order") }),
246 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "name_asc", className: "text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-900", children: t("collections.sortNameAsc", "Title (AâZ)") }), 247 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "name_desc", className: "text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-900", children: t("collections.sortNameDesc", "Title (ZâA)") }), 248 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "sections_desc", className: "text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-900", children: t("collections.sortSections", "Most Sections") }), 249 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "newest", className: "text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-900", children: t("collections.sortNewest", "Recently Added") }) 250 ] 251 } 252 ) 253 ] }), 254 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-2xl p-1 shadow-sm", children: [
255 /* @__PURE__ */ jsxRuntimeExports.jsx( 256 "button", 257 { 258 onClick: () => setViewMode("grid"), 259 className: `p-1.5 rounded-xl transition ${viewMode === "grid" ? "bg-indigo-600 text-white shadow-sm" : "text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200"}`, 260 title: "Grid View", 261 children: /* @__PURE__ */ jsxRuntimeExports.jsx(LayoutGrid, { size: 16 }) 262 } 263 ), 264 /* @__PURE__ */ jsxRuntimeExports.jsx( 265 "button", 266 { 267 onClick: () => setViewMode("list"), 268 className: `p-1.5 rounded-xl transition ${viewMode === "list" ? "bg-indigo-600 text-white shadow-sm" : "text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200"}`, 269 title: "List View", 270 children: /* @__PURE__ */ jsxRuntimeExports.jsx(List, { size: 16 }) 271 } 272 ) 273 ] }) 274 ] }) 275 ] }), 276 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-2 overflow-x-auto no-scrollbar py-1", children: CATEGORIES.map((cat) => { 277 const Icon = cat.icon; 278 const count = categoryCounts[cat.id] || 0; 279 const isSelected = selectedCategory === cat.id; 280 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 281 "button", 282 { 283 onClick: () => setSelectedCategory(cat.id), 284 className: `inline-flex items-center gap-2 px-3.5 py-1.5 rounded-xl text-xs font-bold whitespace-nowrap transition-all duration-200 shrink-0 ${isSelected ? "bg-indigo-600 text-white shadow-md shadow-indigo-600/20" : "bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 text-zinc-600 dark:text-zinc-400 hover:border-zinc-300 dark:hover:border-zinc-700 hover:text-zinc-900 dark:hover:text-white shadow-sm"}`, 285 children: [
286 /* @__PURE__ */ jsxRuntimeExports.jsx(Icon, { size: 14, className: isSelected ? "text-white" : "text-zinc-400" }), 287 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t(cat.labelKey, cat.defaultLabel) }), 288 /* @__PURE__ */ jsxRuntimeExports.jsx( 289 "span", 290 { 291 className: `text-[10px] font-black px-1.5 py-0.5 rounded-md ${isSelected ? "bg-indigo-700/60 text-white" : "bg-zinc-100 dark:bg-zinc-800 text-zinc-500"}`, 292 children: count 293 } 294 ) 295 ] 296 }, 297 cat.id 298 ); 299 }) }) 300 ] }), 301 debouncedQuery.length >= 2 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-10 animate-in fade-in slide-in-from-top-4 duration-500", children: [ 302 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-4", children: [ 303 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2.5", children: [
304 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-2 rounded-xl bg-indigo-50 dark:bg-indigo-950/40 text-indigo-600 dark:text-indigo-400", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 18 }) }), 305 /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-xl font-black text-zinc-900 dark:text-white", children: t("collections.matchingSections", "Matching Sections") }), 306 isSearchingSections ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-4 h-4 border-2 border-indigo-500 border-t-transparent rounded-full animate-spin ml-1" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-black px-2.5 py-0.5 rounded-full bg-indigo-100 dark:bg-indigo-950/60 text-indigo-700 dark:text-indigo-300 border border-indigo-200 dark:border-indigo-800/50", children: matchingSections.length }) 307 ] }), 308 matchingSections.length > 6 && /* @__PURE__ */ jsxRuntimeExports.jsxs( 309 "button", 310 { 311 onClick: () => setShowAllSections(!showAllSections), 312 className: "text-xs font-bold text-indigo-600 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300 flex items-center gap-1 transition", 313 children: [ 314 showAllSections ? t("collections.showLessSections", "Show fewer sections") : t("collections.viewAllSections", "View all {{count}} sections", { count: matchingSections.length }), 315 showAllSections ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 14 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 14 }) 316 ] 317 } 318 ) 319 ] }), 320 matchingSections.length === 0 && !isSearchingSections ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-6 rounded-2xl border border-zinc-200 dark:border-zinc-800/60 bg-white/50 dark:bg-zinc-900/30 text-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-400", children: t("collections.noSectionsMatch", 'No individual sections directly matched "{{query}}".', { query: debouncedQuery }) }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-4", children: (showAllSections ? matchingSections : matchingSections.slice(0, 6)).map((sec) => { 321 const bilingual = getBilingualTitle(sec.title, sec.subtitle, i18n.language); 322 const colBilingual = getBilingualTitle(sec.collection.title, sec.collection.subtitle, i18n.language); 323 const colors = getColorsFromHash(sec.title + sec.id); 324 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 325 "a", 326 { 327 href: sec.url, 328 onClick: (e) => { 329 if (!e.ctrlKey && !e.metaKey && !e.shiftKey && e.button === 0) { 330 e.preventDefault(); 331 const secSlug = sec.url.split("/").pop() || `${slugify(sec.title)}-${sec.id}`; 332 onNavigate("collection-section", `${sec.collection.slug}:${secSlug}`); 333 } 334 }, 335 className: "group relative flex flex-col rounded-2xl overflow-hidden bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800/80 shadow-sm hover:shadow-xl hover:shadow-indigo-500/10 hover:border-indigo-500/40 transition-all duration-300 hover:-translate-y-1 p-3 no-underline text-inherit", 336 title: bilingual.hoverTooltip, 337 children: [
338 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative h-28 w-full rounded-xl overflow-hidden shrink-0 select-none mb-3", children: [ 339 sec.bannerImage ? /* @__PURE__ */ jsxRuntimeExports.jsx( 340 "img", 341 { 342 src: getOptimizedImageUrl(sec.bannerImage, "card"), 343 alt: bilingual.altText, 344 className: "w-full h-full object-cover transition-transform duration-500 group-hover:scale-105", 345 loading: "lazy" 346 } 347 ) : /* @__PURE__ */ jsxRuntimeExports.jsx( 348 "div", 349 { 350 className: "w-full h-full transition-transform duration-500 group-hover:scale-105", 351 style: { background: colors.gradient } 352 } 353 ), 354 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent" }),
355 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "absolute top-2 left-2 max-w-[85%] truncate bg-black/60 backdrop-blur-md text-white font-extrabold text-[9px] uppercase tracking-wider px-2 py-0.5 rounded-md border border-white/10 flex items-center gap-1", children: [ 356 /* @__PURE__ */ jsxRuntimeExports.jsx(Library, { size: 10, className: "shrink-0" }), 357 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: colBilingual.mainTitle }) 358 ] }) 359 ] }), 360 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col justify-between", children: [ 361 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 362 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-sm font-extrabold text-zinc-900 dark:text-white line-clamp-2 leading-tight group-hover:text-indigo-500 transition-colors", children: bilingual.mainTitle }), 363 bilingual.subTitle && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] font-semibold text-zinc-400 dark:text-zinc-500 truncate mt-0.5", children: bilingual.subTitle }), 364 sec.composer && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] font-bold text-indigo-500 dark:text-indigo-400 mt-1 flex items-center gap-1 truncate", children: [
365 /* @__PURE__ */ jsxRuntimeExports.jsx(User, { size: 10, className: "shrink-0" }), 366 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: sec.composer }) 367 ] }) 368 ] }), 369 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-2.5 mt-2 border-t border-zinc-100 dark:border-zinc-800/80 flex items-center justify-between text-[11px] font-black text-indigo-500", children: [ 370 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("collections.explore", "Explore") }), 371 /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowRight, { size: 12, className: "transform transition-transform duration-200 group-hover:translate-x-1" }) 372 ] }) 373 ] }) 374 ] 375 }, 376 sec.id 377 ); 378 }) }) 379 ] }), 380 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-6", children: [ 381 /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-xl font-black text-zinc-900 dark:text-white flex items-center gap-2", children: [
382 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t("collections.matchingCollections", "Collections") }), 383 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs font-bold text-zinc-400", children: [ 384 "(", 385 t("collections.showingResults", "Showing {{shown}} of {{total}}", { 386 shown: filteredCollections.length, 387 total: collections.length 388 }), 389 ")" 390 ] }) 391 ] }), 392 hasActiveFilters && /* @__PURE__ */ jsxRuntimeExports.jsx( 393 "button", 394 { 395 onClick: handleClearFilters, 396 className: "text-xs font-bold text-indigo-600 dark:text-indigo-400 hover:underline", 397 children: t("collections.clearFilters", "Reset Filters") 398 } 399 ) 400 ] }), 401 filteredCollections.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-12 text-center rounded-3xl border-2 border-dashed border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900/30 max-w-xl mx-auto my-12 animate-in fade-in", children: [
402 /* @__PURE__ */ jsxRuntimeExports.jsx(Library, { className: "mx-auto text-zinc-400 dark:text-zinc-600 mb-4", size: 48 }), 403 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-lg font-bold text-zinc-700 dark:text-zinc-300 mb-1", children: t("collections.noResults", "No collections or sections found") }), 404 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-zinc-500 dark:text-zinc-400 mb-6", children: t( 405 "collections.noResultsDesc", 406 "Try searching with a different title, composer, or keyword, or reset your filters." 407 ) }), 408 /* @__PURE__ */ jsxRuntimeExports.jsxs( 409 "button", 410 { 411 onClick: handleClearFilters, 412 className: "inline-flex items-center gap-2 bg-indigo-600 hover:bg-indigo-700 text-white font-bold text-sm px-6 py-2.5 rounded-xl transition shadow-lg shadow-indigo-600/20", 413 children: [ 414 /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { size: 14 }), 415 t("collections.clearFilters", "Reset Filters") 416 ] 417 } 418 ) 419 ] }) : viewMode === "grid" ? ( 420 /* Grid View */
421 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-8", children: filteredCollections.map((collection, idx) => { 422 const colors = getColorsFromHash(collection.title + collection.slug); 423 const formattedDate = collection.createdAt ? new Date(collection.createdAt).toLocaleDateString(void 0, { 424 year: "numeric", 425 month: "short" 426 }) : null; 427 const bilingual = getBilingualTitle(collection.title, collection.subtitle, i18n.language); 428 const descDict = parseMultilingualData(collection.description); 429 const lang = (i18n.language || "en").toLowerCase(); 430 const langPrefix = lang.split("-")[0]; 431 const localizedDescription = descDict && (descDict[lang] || descDict[langPrefix] || descDict.en || Object.values(descDict)[0]) || (typeof collection.description === "string" && !collection.description.trim().startsWith("{") && !collection.description.trim().startsWith("[") ? collection.description : null); 432 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 433 "a", 434 { 435 href: `/collections/${collection.slug}`, 436 onClick: (e) => { 437 if (!e.ctrlKey && !e.metaKey && !e.shiftKey && e.button === 0) { 438 e.preventDefault(); 439 onNavigate("collection", collection.slug); 440 } 441 }, 442 className: "group relative rounded-3xl overflow-hidden bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800/80 shadow-md hover:shadow-2xl hover:shadow-indigo-500/10 transition-all duration-500 hover:-translate-y-2 cursor-pointer flex flex-col h-[400px] animate-in fade-in slide-in-from-bottom-8 no-underline text-inherit", 443 style: { animationDelay: `${Math.min(idx * 40, 400)}ms` }, 444 title: bilingual.hoverTooltip, 445 children: [
446 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative h-48 w-full overflow-hidden shrink-0 select-none", children: [ 447 collection.heroImage && !imageErrors[collection.id] ? /* @__PURE__ */ jsxRuntimeExports.jsx( 448 "img", 449 { 450 src: getOptimizedImageUrl(collection.heroImage, "card"), 451 alt: bilingual.altText, 452 title: bilingual.hoverTooltip, 453 className: "w-full h-full object-cover transition-transform duration-700 group-hover:scale-110", 454 loading: "lazy", 455 onError: () => setImageErrors((prev) => ({ ...prev, [collection.id]: true })) 456 } 457 ) : /* @__PURE__ */ jsxRuntimeExports.jsx( 458 "div", 459 { 460 className: "w-full h-full transition-transform duration-700 group-hover:scale-110", 461 style: { background: colors.gradient }, 462 title: bilingual.hoverTooltip 463 } 464 ),
465 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/80 via-black/30 to-transparent" }), 466 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute top-4 right-4 flex items-center gap-1.5", children: [ 467 bilingual.nativeBadge && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bg-emerald-600/90 text-white font-black text-[10px] tracking-wider px-2 py-1 rounded-lg backdrop-blur-sm shadow border border-emerald-400/30", children: bilingual.nativeBadge }), 468 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bg-indigo-600/90 text-white font-black text-[10px] uppercase tracking-wider px-3 py-1 rounded-xl backdrop-blur-sm shadow-lg border border-indigo-400/20", children: collection.sectionCount === 1 ? t("collections.sectionCount_one", "1 Section", { count: 1 }) : t("collections.sectionCount_other", "{{count}} Sections", { 469 count: collection.sectionCount || 0 470 }) }) 471 ] }) 472 ] }),
473 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-6 flex-1 flex flex-col justify-between", children: [ 474 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2", children: [ 475 /* @__PURE__ */ jsxRuntimeExports.jsx( 476 "h3", 477 { 478 className: "text-xl font-extrabold text-zinc-900 dark:text-white line-clamp-2 group-hover:text-indigo-500 transition-colors leading-tight", 479 title: bilingual.hoverTooltip, 480 children: bilingual.mainTitle 481 } 482 ), 483 bilingual.subTitle && /* @__PURE__ */ jsxRuntimeExports.jsx( 484 "p", 485 { 486 className: "text-zinc-500 dark:text-zinc-400 text-xs font-semibold uppercase tracking-wider line-clamp-1", 487 title: bilingual.hoverTooltip, 488 children: bilingual.subTitle 489 } 490 ),
491 localizedDescription && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-500 dark:text-zinc-400 text-sm line-clamp-2 leading-relaxed pt-1", children: localizedDescription }) 492 ] }), 493 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between pt-4 border-t border-zinc-100 dark:border-zinc-800/80 mt-4 shrink-0", children: [ 494 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-1 text-[11px] text-zinc-400 dark:text-zinc-500 font-bold uppercase", children: formattedDate && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 495 /* @__PURE__ */ jsxRuntimeExports.jsx(Calendar, { size: 12 }), 496 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: formattedDate }) 497 ] }) }), 498 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-[11px] font-black uppercase tracking-widest text-indigo-500 group-hover:text-indigo-400 transition-colors", children: [ 499 t("collections.explore", "Explore"),
500 /* @__PURE__ */ jsxRuntimeExports.jsx( 501 ArrowRight, 502 { 503 size: 12, 504 className: "transform transition-transform duration-300 group-hover:translate-x-1" 505 } 506 ) 507 ] }) 508 ] }) 509 ] }) 510 ] 511 }, 512 collection.id 513 ); 514 }) }) 515 ) : ( 516 /* Compact List View */ 517 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3", children: filteredCollections.map((collection, idx) => { 518 const colors = getColorsFromHash(collection.title + collection.slug); 519 const bilingual = getBilingualTitle(collection.title, collection.subtitle, i18n.language); 520 const descDict = parseMultilingualData(collection.description); 521 const lang = (i18n.language || "en").toLowerCase(); 522 const langPrefix = lang.split("-")[0]; 523 const localizedDescription = descDict && (descDict[lang] || descDict[langPrefix] || descDict.en || Object.values(descDict)[0]) || (typeof collection.description === "string" && !collection.description.trim().startsWith("{") && !collection.description.trim().startsWith("[") ? collection.description : null); 524 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 525 "a", 526 { 527 href: `/collections/${collection.slug}`, 528 onClick: (e) => { 529 if (!e.ctrlKey && !e.metaKey && !e.shiftKey && e.button === 0) { 530 e.preventDefault(); 531 onNavigate("collection", collection.slug); 532 } 533 }, 534 className: "group flex items-center justify-between p-4 rounded-2xl bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800/80 shadow-sm hover:shadow-lg hover:border-indigo-500/40 transition-all duration-300 no-underline text-inherit", 535 style: { animationDelay: `${Math.min(idx * 20, 300)}ms` }, 536 children: [
537 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4 min-w-0", children: [ 538 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "relative w-16 h-16 sm:w-20 sm:h-20 rounded-xl overflow-hidden shrink-0 select-none", children: collection.heroImage && !imageErrors[collection.id] ? /* @__PURE__ */ jsxRuntimeExports.jsx( 539 "img", 540 { 541 src: getOptimizedImageUrl(collection.heroImage, "card"), 542 alt: bilingual.altText, 543 className: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300", 544 loading: "lazy", 545 onError: () => setImageErrors((prev) => ({ ...prev, [collection.id]: true })) 546 } 547 ) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-full h-full", style: { background: colors.gradient } }) }), 548 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
549 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap mb-1", children: [ 550 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "text-base sm:text-lg font-extrabold text-zinc-900 dark:text-white truncate group-hover:text-indigo-500 transition-colors", children: bilingual.mainTitle }), 551 bilingual.nativeBadge && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bg-emerald-600/90 text-white font-black text-[9px] px-1.5 py-0.5 rounded", children: bilingual.nativeBadge }) 552 ] }), 553 bilingual.subTitle && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs font-bold text-zinc-400 truncate mb-1", children: bilingual.subTitle }), 554 localizedDescription && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400 line-clamp-1 hidden sm:block", children: localizedDescription }) 555 ] }) 556 ] }),
557 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4 shrink-0 pl-4", children: [ 558 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold px-3 py-1 rounded-xl bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-300", children: collection.sectionCount === 1 ? t("collections.sectionCount_one", "1 Section", { count: 1 }) : t("collections.sectionCount_other", "{{count}} Sections", { 559 count: collection.sectionCount || 0 560 }) }), 561 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-8 h-8 rounded-full bg-indigo-50 dark:bg-indigo-950/40 text-indigo-600 dark:text-indigo-400 flex items-center justify-center group-hover:bg-indigo-600 group-hover:text-white transition", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowRight, { size: 14, className: "transform transition-transform duration-200 group-hover:translate-x-0.5" }) }) 562 ] }) 563 ] 564 }, 565 collection.id 566 ); 567 }) }) 568 ) 569 ] }); 570}; 571export { 572 CollectionIndexPage as default 573};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.