टेम्पलेट के प्रोसेसिंग के समय त्रुटि हुई है.
The following has evaluated to null or missing:
==> document.selectSingleNode(
"//dynamic-element[@field-reference='name']/dynamic-content[@language-id='${languageId}']"
) [in template "20662264241477#20120#102636" at line 300, column 25]
----
Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----
----
FTL stack trace ("~" means nesting-related):
- Failed at: #assign nameNode = document.selectSin... [in template "20662264241477#20120#102636" at line 300, column 5]
----
1<div class="gallery-wrapper">
2
3 <!-- =========================
4 HEADER
5 ========================== -->
6
7 <div class="gallery-header">
8
9 <h1>
10 <i class="bi bi-camera-fill"></i>
11${languageUtil.get(locale, "photo_gallery")}
12 </h1>
13
14
15
16
17 </div>
18
19
20
21<!-- =========================
22 TOOLBAR
23========================== -->
24
25<div class="gallery-toolbar">
26
27 <div class="gallery-filter-row">
28
29 <!-- CATEGORY -->
30 <div class="filter-group category-group">
31
32 <label for="categoryFilter">
33 <i class="bi bi-grid-3x3-gap-fill"></i>
34 Category
35 </label>
36
37 <div class="select-wrapper">
38
39 <select
40 id="categoryFilter"
41 class="gallery-select">
42
43 <option value="all">
44 All Categories
45 </option>
46
47 </select>
48
49 <i class="bi bi-chevron-down select-icon"></i>
50
51 </div>
52
53 </div>
54
55
56 <!-- SEARCH -->
57 <div class="filter-group search-group">
58
59 <label for="searchInput">
60 <i class="bi bi-search"></i>
61 Search
62 </label>
63
64 <div class="search-wrapper">
65
66 <i class="bi bi-search search-icon"></i>
67
68 <input
69 type="text"
70 id="searchInput"
71 class="gallery-search"
72 placeholder="Search photos..."
73 autocomplete="off"
74 />
75
76 <button
77 type="button"
78 id="clearSearch"
79 class="clear-search"
80 title="Clear search">
81
82 <i class="bi bi-x"></i>
83
84 </button>
85
86 </div>
87
88 </div>
89
90
91 <!-- TOTAL -->
92 <div class="photo-count-box">
93
94 <div class="count-icon">
95
96 <i class="bi bi-images"></i>
97
98 </div>
99
100 <div class="count-content">
101
102 <span class="count-label">
103 Total Photos
104 </span>
105
106 <strong id="totalPhotos">
107 0
108 </strong>
109
110 </div>
111
112 </div>
113
114 </div>
115
116</div>
117
118 <!-- =========================
119 GALLERY GRID
120========================== -->
121
122<div class="gallery-grid">
123
124 <div
125 id="galleryContainer"
126 class="row g-4">
127
128 </div>
129
130</div>
131
132 <!-- =========================
133 PAGINATION
134 ========================== -->
135
136 <div class="pagination-wrapper">
137
138 <nav aria-label="Photo pagination">
139
140 <ul
141 id="pagination"
142 class="pagination justify-content-center mb-0"
143 >
144 </ul>
145
146 </nav>
147
148 </div>
149
150</div>
151
152
153
154<!-- =========================
155 LIGHTBOX
156========================== -->
157
158<div
159 id="lightbox"
160 class="lightbox"
161>
162
163 <div class="lightbox-content">
164
165
166 <!-- CLOSE -->
167
168 <button
169 class="lightbox-close"
170 id="closeLightbox"
171 title="Close"
172 >
173 <i class="bi bi-x-lg"></i>
174 </button>
175
176
177 <!-- IMAGE AREA -->
178
179 <div class="lightbox-image-container">
180
181 <!-- PREVIOUS -->
182
183 <button
184 id="prevPhoto"
185 class="lightbox-prev"
186 >
187 <i class="bi bi-chevron-left"></i>
188 </button>
189
190
191 <!-- IMAGE -->
192
193 <img
194 id="lightboxImage"
195 src=""
196 alt="Photo"
197 >
198
199
200 <!-- NEXT -->
201
202 <button
203 id="nextPhoto"
204 class="lightbox-next"
205 >
206 <i class="bi bi-chevron-right"></i>
207 </button>
208
209 </div>
210
211
212 <!-- FOOTER -->
213
214 <div class="lightbox-footer">
215
216 <div>
217
218 <div
219 id="lightboxTitle"
220 class="lightbox-title"
221 >
222 Photo
223 </div>
224
225 <div
226 id="lightboxCounter"
227 class="lightbox-counter"
228 >
229 1 / 1
230 </div>
231
232 </div>
233
234
235 <!-- ACTION BUTTONS -->
236
237 <div class="lightbox-actions">
238
239 <button
240 id="zoomOut"
241 title="Zoom Out"
242 >
243 <i class="bi bi-zoom-out"></i>
244 </button>
245
246 <button
247 id="zoomIn"
248 title="Zoom In"
249 >
250 <i class="bi bi-zoom-in"></i>
251 </button>
252
253 <button
254 id="resetZoom"
255 title="Reset Zoom"
256 >
257 <i class="bi bi-arrow-counterclockwise"></i>
258 </button>
259
260 <button
261 id="downloadPhoto"
262 title="Download"
263 >
264 <i class="bi bi-download"></i>
265 </button>
266
267 </div>
268
269 </div>
270
271 </div>
272
273</div>
274
275
276
277<!-- =========================
278 JAVASCRIPT
279========================== -->
280
281<script>
282
283 /* =====================================
284 PHOTO DATA
285 ====================================== */
286
287 const photos = [
288
289<#list entries as entry>
290
291 <#assign
292 renderer = entry.getAssetRenderer()
293 article = renderer.getArticle()
294 document = saxReaderUtil.read(article.getContent())
295 languageId = locale.toString()
296 srNo = entry?index + 1
297 />
298
299 <#-- NAME -->
300 <#assign nameNode = document.selectSingleNode(
301 "//dynamic-element[@field-reference='name']/dynamic-content[@language-id='${languageId}']"
302 ) />
303
304 <#assign photoName = "" />
305
306 <#if nameNode??>
307 <#assign photoName = nameNode.getText() />
308 </#if>
309
310
311 <#-- CATEGORY -->
312 <#assign categoryNode = document.selectSingleNode(
313 "//dynamic-element[@field-reference='category']/dynamic-content[@language-id='${languageId}']"
314 ) />
315
316 <#assign photoCategory = "" />
317
318 <#if categoryNode??>
319 <#assign photoCategory = categoryNode.getText() />
320
321 </#if>
322
323
324 <#-- IMAGE -->
325 <#assign imageNode = document.selectSingleNode(
326 "//dynamic-element[@field-reference='image']/dynamic-content[@language-id='${languageId}']"
327 ) />
328
329 <#assign imageUrl = "" />
330
331 <#if imageNode??>
332
333 <#assign imageData = imageNode.getText() />
334
335 <#if imageData?has_content>
336
337 <#assign imageJson = jsonFactoryUtil.createJSONObject(imageData) />
338
339 <#if imageJson.has("url")>
340 <#assign imageUrl = imageJson.getString("url") />
341 </#if>
342
343 </#if>
344
345 </#if>
346
347
348 {
349 id: ${srNo},
350 title: "${photoName?js_string}",
351 category: "${photoCategory?js_string}",
352 image: "${imageUrl?js_string}"
353 }<#if entry?has_next>,</#if>
354
355</#list>
356
357];
358
359
360 /* =====================================
361 VARIABLES
362 ====================================== */
363
364 const photosPerPage = 6;
365
366 let currentPage = 1;
367
368 let filteredPhotos = [...photos];
369
370 let currentLightboxIndex = 0;
371
372 let zoomLevel = 1;
373
374
375 /* =====================================
376 DOM ELEMENTS
377 ====================================== */
378
379 const galleryContainer =
380 document.getElementById("galleryContainer");
381
382 const pagination =
383 document.getElementById("pagination");
384
385 const totalPhotos =
386 document.getElementById("totalPhotos");
387
388 const categoryFilter =
389 document.getElementById("categoryFilter");
390
391 const searchInput =
392 document.getElementById("searchInput");
393
394 const lightbox =
395 document.getElementById("lightbox");
396
397 const lightboxImage =
398 document.getElementById("lightboxImage");
399
400 const lightboxTitle =
401 document.getElementById("lightboxTitle");
402
403 const lightboxCounter =
404 document.getElementById("lightboxCounter");
405/* =====================================
406 LOAD DYNAMIC CATEGORIES
407====================================== */
408
409function loadCategories() {
410
411 const categorySet = new Set();
412
413 photos.forEach(function(photo) {
414
415 if (
416 photo.category &&
417 photo.category.trim() !== ""
418 ) {
419
420 categorySet.add(
421 photo.category.trim()
422 );
423
424 }
425
426 });
427
428
429 const categories =
430 Array.from(categorySet).sort();
431
432
433 console.log(
434 "Dynamic Categories:",
435 categories
436 );
437
438
439 categoryFilter.innerHTML =
440 '<option value="all">' +
441 'All Categories' +
442 '</option>';
443
444
445 categories.forEach(function(category) {
446
447 const option =
448 document.createElement("option");
449
450
451 option.value =
452 category;
453
454
455 option.textContent =
456 category;
457
458
459 categoryFilter.appendChild(
460 option
461 );
462
463 });
464
465}
466
467 /* =====================================
468 DISPLAY GALLERY
469 ====================================== */
470
471 function displayGallery() {
472
473 galleryContainer.innerHTML = "";
474
475 const start =
476 (currentPage - 1) * photosPerPage;
477
478 const end =
479 start + photosPerPage;
480
481 const pagePhotos =
482 filteredPhotos.slice(start, end);
483
484
485 if (pagePhotos.length === 0) {
486
487 galleryContainer.innerHTML = `
488 <div class="col-12 text-center py-5">
489 <i class="bi bi-image fs-1 text-muted"></i>
490 <h5 class="mt-3 text-muted">
491 No photos found
492 </h5>
493 </div>
494 `;
495
496 return;
497 }
498
499
500 pagePhotos.forEach((photo) => {
501
502 const originalIndex = filteredPhotos.indexOf(photo);
503
504
505 const column = document.createElement("div");
506
507
508 column.className = "col-12 col-sm-6 col-md-4 col-lg-4 mt-3";
509
510
511
512 column.innerHTML =
513 '<div class="photo-card mt-3">' +
514
515 '<div ' +
516 'class="photo-image-wrapper" ' +
517 'onclick="openLightbox(' + originalIndex + ')">' +
518
519 '<img ' +
520 'src="' + photo.image + '" ' +
521 'alt="' + photo.title + '" ' +
522 'class="photo-image" ' +
523 'loading="lazy">' +
524
525 '<span class="zoom-icon">' +
526 '<i class="bi bi-search"></i>' +
527 '</span>' +
528
529 '</div>' +
530
531 '<div class="photo-title">' +
532 photo.title +
533 '</div>' +
534
535 '</div>';
536
537
538 galleryContainer.appendChild(column);
539
540 });
541
542
543 totalPhotos.textContent = filteredPhotos.length;
544
545 }
546
547
548 /* =====================================
549 PAGINATION
550 ====================================== */
551
552 function createPagination() {
553
554 pagination.innerHTML = "";
555 const totalPages = Math.ceil(
556 filteredPhotos.length / photosPerPage
557 );
558
559
560 if (totalPages <= 1) {
561 return;
562 }
563
564
565 /* PREVIOUS */
566
567 const previous = document.createElement("li");
568
569
570 previous.className =
571 "page-item " + (currentPage === 1 ? "disabled" : "");
572
573 previous.innerHTML = `
574 <a class="page-link" href="#">
575 Previous
576 </a>
577 `;
578
579 previous.onclick = function(e) {
580
581 e.preventDefault();
582
583 if (currentPage > 1) {
584
585 currentPage--;
586
587
588 displayGallery();
589
590 createPagination();
591
592 window.scrollTo({
593 top: 0,
594 behavior: "smooth"
595 });
596 }
597
598 };
599
600 pagination.appendChild(previous);
601
602
603 /* PAGE NUMBERS */
604
605 for (
606 let i = 1;
607 i <= totalPages;
608 i++
609 ) {
610
611 const page =
612 document.createElement("li");
613
614 page.className =
615 "page-item " + (i === currentPage ? "active" : "");
616
617 page.innerHTML =
618 '<a class="page-link" href="#">' +
619 i +
620 '</a>';
621
622
623 page.onclick = function(e) {
624
625 e.preventDefault();
626
627 currentPage = i;
628
629
630 displayGallery();
631
632 createPagination();
633
634 window.scrollTo({
635 top: 0,
636 behavior: "smooth"
637 });
638
639 };
640
641
642 pagination.appendChild(page);
643
644 }
645
646
647 /* NEXT */
648
649 const next =
650 document.createElement("li");
651
652 next.className =
653 "page-item " + (currentPage === totalPages ? "disabled" : "");
654
655 next.innerHTML = `
656 <a class="page-link" href="#">
657 Next
658 </a>
659 `;
660
661
662 next.onclick = function(e) {
663
664 e.preventDefault();
665
666 if (currentPage < totalPages) {
667
668 currentPage++;
669
670
671 displayGallery();
672
673 createPagination();
674
675 window.scrollTo({
676 top: 0,
677 behavior: "smooth"
678 });
679
680 }
681
682 };
683
684
685 pagination.appendChild(next);
686
687 }
688
689
690
691/* =====================================
692 FILTER PHOTOS
693====================================== */
694
695function filterPhotos() {
696
697 const selectedCategory =
698 categoryFilter.value.trim();
699
700
701 const searchText =
702 searchInput.value
703 .toLowerCase()
704 .trim();
705
706
707 console.log(
708 "Selected Category:",
709 selectedCategory
710 );
711
712
713 filteredPhotos =
714 photos.filter(function(photo) {
715
716
717 const photoCategory =
718 (
719 photo.category || ""
720 ).trim();
721
722
723 const photoTitle =
724 (
725 photo.title || ""
726 )
727 .toLowerCase();
728
729
730 /*
731 * CATEGORY FILTER
732 */
733
734 const categoryMatch =
735
736 selectedCategory === "all" ||
737
738 photoCategory ===
739 selectedCategory;
740
741
742 /*
743 * SEARCH FILTER
744 */
745
746 const searchMatch =
747
748 photoTitle.includes(
749 searchText
750 );
751
752
753 return (
754 categoryMatch &&
755 searchMatch
756 );
757
758 });
759
760
761 console.log(
762 "Filtered Photos:",
763 filteredPhotos
764 );
765
766
767 currentPage = 1;
768
769
770 displayGallery();
771
772 createPagination();
773
774}
775
776
777 categoryFilter.addEventListener(
778 "change",
779 filterPhotos
780 );
781
782
783 searchInput.addEventListener(
784 "input",
785 filterPhotos
786 );
787 const clearSearch =
788 document.getElementById("clearSearch");
789
790
791searchInput.addEventListener(
792 "input",
793 function() {
794
795 if (searchInput.value.trim() !== "") {
796
797 clearSearch.style.display =
798 "flex";
799
800 } else {
801
802 clearSearch.style.display =
803 "none";
804
805 }
806
807 }
808);
809
810
811clearSearch.addEventListener(
812 "click",
813 function() {
814
815 searchInput.value = "";
816
817 clearSearch.style.display =
818 "none";
819
820 filterPhotos();
821
822 searchInput.focus();
823
824 }
825);
826
827
828 /* =====================================
829 OPEN LIGHTBOX
830 ====================================== */
831
832 function openLightbox(index) {
833
834 currentLightboxIndex = index;
835
836 zoomLevel = 1;
837
838 updateLightbox();
839
840 lightbox.classList.add("show");
841
842 document.body.style.overflow = "hidden";
843
844 }
845
846
847 /* =====================================
848 UPDATE LIGHTBOX
849 ====================================== */
850
851 function updateLightbox() {
852
853 const photo =
854 filteredPhotos[currentLightboxIndex];
855
856
857 if (!photo) {
858 return;
859 }
860
861
862 lightboxImage.src =
863 photo.image;
864
865 lightboxImage.alt =
866 photo.title;
867
868 lightboxTitle.textContent =
869 photo.title;
870
871 lightboxCounter.textContent =
872 (currentLightboxIndex + 1) + " / " + filteredPhotos.length;
873
874 applyZoom();
875
876 }
877
878
879 /* =====================================
880 CLOSE LIGHTBOX
881 ====================================== */
882
883 function closeLightbox() {
884
885 lightbox.classList.remove("show");
886
887 document.body.style.overflow = "";
888
889 }
890
891
892 document
893 .getElementById("closeLightbox")
894 .addEventListener(
895 "click",
896 closeLightbox
897 );
898
899
900 /* =====================================
901 NEXT PHOTO
902 ====================================== */
903
904 function nextPhoto() {
905
906 if (
907 currentLightboxIndex <
908 filteredPhotos.length - 1
909 ) {
910
911 currentLightboxIndex++;
912
913 } else {
914
915 currentLightboxIndex = 0;
916
917 }
918
919
920 zoomLevel = 1;
921
922 updateLightbox();
923
924 }
925
926
927 document
928 .getElementById("nextPhoto")
929 .addEventListener(
930 "click",
931 nextPhoto
932 );
933
934
935 /* =====================================
936 PREVIOUS PHOTO
937 ====================================== */
938
939 function previousPhoto() {
940
941 if (
942 currentLightboxIndex > 0
943 ) {
944
945 currentLightboxIndex--;
946
947 } else {
948
949 currentLightboxIndex =
950 filteredPhotos.length - 1;
951
952 }
953
954
955 zoomLevel = 1;
956
957 updateLightbox();
958
959 }
960
961
962 document
963 .getElementById("prevPhoto")
964 .addEventListener(
965 "click",
966 previousPhoto
967 );
968
969
970 /* =====================================
971 ZOOM
972 ====================================== */
973
974 function applyZoom() {
975
976 lightboxImage.style.transform =
977 "scale(" + zoomLevel + ")";
978
979}
980
981
982 document
983 .getElementById("zoomIn")
984 .addEventListener(
985 "click",
986 function() {
987
988 zoomLevel += 0.2;
989
990 if (zoomLevel > 3) {
991 zoomLevel = 3;
992 }
993
994 applyZoom();
995
996 }
997 );
998
999
1000 document
1001 .getElementById("zoomOut")
1002 .addEventListener(
1003 "click",
1004 function() {
1005
1006 zoomLevel -= 0.2;
1007
1008 if (zoomLevel < 0.5) {
1009 zoomLevel = 0.5;
1010 }
1011
1012 applyZoom();
1013
1014 }
1015 );
1016
1017
1018 document
1019 .getElementById("resetZoom")
1020 .addEventListener(
1021 "click",
1022 function() {
1023
1024 zoomLevel = 1;
1025
1026 applyZoom();
1027
1028 }
1029 );
1030
1031
1032 /* =====================================
1033 DOWNLOAD
1034 ====================================== */
1035
1036 document
1037 .getElementById("downloadPhoto")
1038 .addEventListener(
1039 "click",
1040 function() {
1041
1042 const photo =
1043 filteredPhotos[currentLightboxIndex];
1044
1045
1046 const link =
1047 document.createElement("a");
1048
1049 link.href =
1050 photo.image;
1051
1052 link.download =
1053 photo.title + ".jpg";
1054
1055 link.target =
1056 "_blank";
1057
1058 document.body.appendChild(link);
1059
1060 link.click();
1061
1062 document.body.removeChild(link);
1063
1064 }
1065 );
1066
1067
1068 /* =====================================
1069 KEYBOARD CONTROLS
1070 ====================================== */
1071
1072 document.addEventListener(
1073 "keydown",
1074 function(e) {
1075
1076 if (!lightbox.classList.contains("show")) {
1077 return;
1078 }
1079
1080
1081 if (e.key === "Escape") {
1082
1083 closeLightbox();
1084
1085 }
1086
1087
1088 if (e.key === "ArrowRight") {
1089
1090 nextPhoto();
1091
1092 }
1093
1094
1095 if (e.key === "ArrowLeft") {
1096
1097 previousPhoto();
1098
1099 }
1100
1101
1102 if (e.key === "+") {
1103
1104 zoomLevel += 0.2;
1105
1106 if (zoomLevel > 3) {
1107 zoomLevel = 3;
1108 }
1109
1110 applyZoom();
1111
1112 }
1113
1114
1115 if (e.key === "-") {
1116
1117 zoomLevel -= 0.2;
1118
1119 if (zoomLevel < 0.5) {
1120 zoomLevel = 0.5;
1121 }
1122
1123 applyZoom();
1124
1125 }
1126
1127 }
1128 );
1129
1130
1131 /* =====================================
1132 CLICK OUTSIDE IMAGE
1133 ====================================== */
1134
1135 lightbox.addEventListener(
1136 "click",
1137 function(e) {
1138
1139 if (e.target === lightbox) {
1140
1141 closeLightbox();
1142
1143 }
1144
1145 }
1146 );
1147
1148
1149 /* =====================================
1150 INITIAL LOAD
1151 ====================================== */
1152loadCategories();
1153 displayGallery();
1154
1155 createPagination();
1156
1157</script>
1158
1159<link
1160 rel="stylesheet"
1161 href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
1162 >
1163
1164 <style>
1165
1166 /* =========================
1167 GENERAL
1168 ========================= */
1169
1170 body {
1171 background: #f5f7fa;
1172 font-family: Arial, Helvetica, sans-serif;
1173 }
1174
1175 .gallery-wrapper {
1176 max-width: 1400px;
1177 margin: 40px auto;
1178 padding: 0 20px;
1179 }
1180
1181
1182 /* =========================
1183 HEADER
1184 ========================= */
1185
1186 .gallery-header {
1187 background: linear-gradient(135deg, #073b70, #0d5ca8);
1188 color: #ffffff;
1189 padding: 30px 20px;
1190 border-radius: 10px 10px 0 0;
1191 text-align: center;
1192 }
1193
1194 .gallery-header h1 {
1195 font-size: 32px;
1196 font-weight: 700;
1197 margin-bottom: 8px;
1198 }
1199
1200 .gallery-header p {
1201 margin: 0;
1202 font-size: 16px;
1203 opacity: 0.9;
1204 }
1205
1206
1207 /* =========================
1208 FILTER BAR
1209 ========================= */
1210
1211 .gallery-toolbar {
1212 background: #ffffff;
1213 padding: 20px;
1214 border-left: 1px solid #ddd;
1215 border-right: 1px solid #ddd;
1216 }
1217
1218 .photo-count {
1219 font-weight: 600;
1220 color: #555;
1221 }
1222
1223
1224 /* =========================
1225 GALLERY
1226 ========================= */
1227
1228 .gallery-grid {
1229 background: #ffffff;
1230 padding: 20px;
1231 border-left: 1px solid #ddd;
1232 border-right: 1px solid #ddd;
1233 }
1234
1235 .photo-card {
1236 background: #ffffff;
1237 border: 1px solid #dee2e6;
1238 border-radius: 8px;
1239 overflow: hidden;
1240 height: 100%;
1241 transition: all 0.3s ease;
1242 }
1243
1244 .photo-card:hover {
1245 transform: translateY(-5px);
1246 box-shadow: 0 8px 20px rgba(0,0,0,0.12);
1247 }
1248
1249 .photo-image-wrapper {
1250 position: relative;
1251 height: 220px;
1252 overflow: hidden;
1253 cursor: pointer;
1254 background: #eee;
1255 }
1256
1257 .photo-image {
1258 width: 100%;
1259 height: 100%;
1260 object-fit: cover;
1261 transition: transform 0.4s ease;
1262 }
1263
1264 .photo-card:hover .photo-image {
1265 transform: scale(1.06);
1266 }
1267
1268 .zoom-icon {
1269 position: absolute;
1270 right: 10px;
1271 top: 10px;
1272
1273 width: 38px;
1274 height: 38px;
1275
1276 border-radius: 50%;
1277
1278 display: flex;
1279 align-items: center;
1280 justify-content: center;
1281
1282 background: rgba(0,0,0,0.65);
1283 color: white;
1284
1285 font-size: 18px;
1286 }
1287
1288 .photo-title {
1289 padding: 14px;
1290 text-align: center;
1291 font-size: 16px;
1292 font-weight: 600;
1293 color: #333;
1294 }
1295
1296
1297 /* =========================
1298 PAGINATION
1299 ========================= */
1300
1301 .pagination-wrapper {
1302 background: #ffffff;
1303 padding: 20px;
1304 border: 1px solid #ddd;
1305 border-top: 0;
1306 border-radius: 0 0 10px 10px;
1307 }
1308
1309 .pagination .page-link {
1310 color: #0d5ca8;
1311 margin: 0 3px;
1312 border-radius: 6px;
1313 }
1314
1315 .pagination .page-item.active .page-link {
1316 background: #0d5ca8;
1317 border-color: #0d5ca8;
1318 color: white;
1319 }
1320
1321
1322 /* =========================
1323 LIGHTBOX
1324 ========================= */
1325
1326 .lightbox {
1327 position: fixed;
1328 inset: 0;
1329
1330 background: rgba(0,0,0,0.92);
1331
1332 z-index: 9999;
1333
1334 display: none;
1335
1336 align-items: center;
1337 justify-content: center;
1338
1339 padding: 20px;
1340 }
1341
1342 .lightbox.show {
1343 display: flex;
1344 }
1345
1346 .lightbox-content {
1347 position: relative;
1348
1349 width: 100%;
1350 max-width: 1100px;
1351
1352 max-height: 95vh;
1353
1354 display: flex;
1355 flex-direction: column;
1356
1357 background: #111;
1358
1359 border-radius: 8px;
1360
1361 overflow: hidden;
1362 }
1363
1364
1365 /* =========================
1366 LIGHTBOX IMAGE
1367 ========================= */
1368
1369 .lightbox-image-container {
1370 position: relative;
1371
1372 width: 100%;
1373 height: 75vh;
1374
1375 display: flex;
1376 align-items: center;
1377 justify-content: center;
1378
1379 background: #000;
1380
1381 overflow: hidden;
1382 }
1383
1384 #lightboxImage {
1385 max-width: 100%;
1386 max-height: 100%;
1387
1388 object-fit: contain;
1389
1390 transition: transform 0.3s ease;
1391 }
1392
1393
1394 /* =========================
1395 CLOSE BUTTON
1396 ========================= */
1397
1398 .lightbox-close {
1399 position: absolute;
1400
1401 top: 15px;
1402 right: 15px;
1403
1404 width: 42px;
1405 height: 42px;
1406
1407 border: 0;
1408 border-radius: 50%;
1409
1410 background: rgba(0,0,0,0.65);
1411 color: white;
1412
1413 font-size: 25px;
1414
1415 z-index: 10;
1416 }
1417
1418 .lightbox-close:hover {
1419 background: rgba(255,255,255,0.25);
1420 }
1421
1422
1423 /* =========================
1424 PREVIOUS / NEXT
1425 ========================= */
1426
1427 .lightbox-prev,
1428 .lightbox-next {
1429 position: absolute;
1430
1431 top: 50%;
1432 transform: translateY(-50%);
1433
1434 width: 50px;
1435 height: 60px;
1436
1437 border: none;
1438
1439 background: rgba(0,0,0,0.65);
1440 color: white;
1441
1442 font-size: 30px;
1443
1444 z-index: 5;
1445 }
1446
1447 .lightbox-prev {
1448 left: 15px;
1449 border-radius: 8px;
1450 }
1451
1452 .lightbox-next {
1453 right: 15px;
1454 border-radius: 8px;
1455 }
1456
1457 .lightbox-prev:hover,
1458 .lightbox-next:hover {
1459 background: rgba(255,255,255,0.2);
1460 }
1461
1462
1463 /* =========================
1464 LIGHTBOX FOOTER
1465 ========================= */
1466
1467 .lightbox-footer {
1468 min-height: 65px;
1469
1470 padding: 12px 20px;
1471
1472 display: flex;
1473
1474 align-items: center;
1475
1476 justify-content: space-between;
1477
1478 color: white;
1479
1480 background: #151515;
1481 }
1482
1483 .lightbox-title {
1484 font-weight: 600;
1485 font-size: 17px;
1486 }
1487
1488 .lightbox-counter {
1489 font-size: 14px;
1490 color: #ccc;
1491 }
1492
1493 .lightbox-actions {
1494 display: flex;
1495 gap: 8px;
1496 }
1497
1498 .lightbox-actions button {
1499 width: 38px;
1500 height: 38px;
1501
1502 border: 1px solid #555;
1503
1504 border-radius: 5px;
1505
1506 background: #222;
1507 color: white;
1508 }
1509
1510 .lightbox-actions button:hover {
1511 background: #444;
1512 }
1513
1514
1515 /* =========================
1516 RESPONSIVE
1517 ========================= */
1518
1519 @media (max-width: 768px) {
1520
1521 .gallery-wrapper {
1522 margin: 15px auto;
1523 padding: 0 10px;
1524 }
1525
1526 .gallery-header h1 {
1527 font-size: 24px;
1528 }
1529
1530 .photo-image-wrapper {
1531 height: 180px;
1532 }
1533
1534 .lightbox {
1535 padding: 5px;
1536 }
1537
1538 .lightbox-image-container {
1539 height: 75vh;
1540 }
1541
1542 .lightbox-footer {
1543 flex-wrap: wrap;
1544 gap: 10px;
1545 }
1546
1547 .lightbox-prev,
1548 .lightbox-next {
1549 width: 40px;
1550 height: 50px;
1551 font-size: 24px;
1552 }
1553
1554 .lightbox-prev {
1555 left: 5px;
1556 }
1557
1558 .lightbox-next {
1559 right: 5px;
1560 }
1561 }
1562
1563
1564
1565
1566
1567
1568 /* =====================================
1569 MODERN GALLERY TOOLBAR
1570===================================== */
1571
1572.gallery-toolbar {
1573
1574 background: #ffffff;
1575
1576 padding: 24px 30px;
1577
1578 border-left: 1px solid #e1e5eb;
1579
1580 border-right: 1px solid #e1e5eb;
1581
1582 border-bottom: 1px solid #e1e5eb;
1583
1584}
1585
1586
1587/* =====================================
1588 FILTER ROW
1589===================================== */
1590
1591.gallery-filter-row {
1592
1593 display: flex;
1594
1595 align-items: flex-end;
1596
1597 gap: 24px;
1598
1599}
1600
1601
1602/* =====================================
1603 FILTER GROUP
1604===================================== */
1605
1606.filter-group {
1607
1608 display: flex;
1609
1610 flex-direction: column;
1611
1612 gap: 8px;
1613
1614}
1615
1616
1617.filter-group label {
1618
1619 font-size: 13px;
1620
1621 font-weight: 600;
1622
1623 color: #344054;
1624
1625 margin: 0;
1626
1627}
1628
1629
1630.filter-group label i {
1631
1632 margin-right: 5px;
1633
1634 color: #145a96;
1635
1636}
1637
1638
1639/* =====================================
1640 CATEGORY
1641===================================== */
1642
1643.category-group {
1644
1645 width: 250px;
1646
1647}
1648
1649
1650.select-wrapper {
1651
1652 position: relative;
1653
1654}
1655
1656
1657.gallery-select {
1658
1659 width: 100%;
1660
1661 height: 48px;
1662
1663 padding: 0 42px 0 15px;
1664
1665 border: 1px solid #d0d5dd;
1666
1667 border-radius: 8px;
1668
1669 background: #ffffff;
1670
1671 color: #344054;
1672
1673 font-size: 15px;
1674
1675 cursor: pointer;
1676
1677 appearance: none;
1678
1679 -webkit-appearance: none;
1680
1681 transition: all 0.2s ease;
1682
1683}
1684
1685
1686.gallery-select:hover {
1687
1688 border-color: #145a96;
1689
1690}
1691
1692
1693.gallery-select:focus {
1694
1695 outline: none;
1696
1697 border-color: #145a96;
1698
1699 box-shadow:
1700 0 0 0 3px
1701 rgba(20, 90, 150, 0.12);
1702
1703}
1704
1705
1706.select-icon {
1707
1708 position: absolute;
1709
1710 right: 15px;
1711
1712 top: 50%;
1713
1714 transform: translateY(-50%);
1715
1716 pointer-events: none;
1717
1718 color: #667085;
1719
1720}
1721
1722
1723/* =====================================
1724 SEARCH
1725===================================== */
1726
1727.search-group {
1728
1729 flex: 1;
1730
1731 max-width: 500px;
1732
1733}
1734
1735
1736.search-wrapper {
1737
1738 position: relative;
1739
1740}
1741
1742
1743.gallery-search {
1744
1745 width: 100%;
1746
1747 height: 48px;
1748
1749 padding:
1750 0 45px
1751 0 42px;
1752
1753 border: 1px solid #d0d5dd;
1754
1755 border-radius: 8px;
1756
1757 background: #ffffff;
1758
1759 font-size: 15px;
1760
1761 color: #344054;
1762
1763 transition: all 0.2s ease;
1764
1765}
1766
1767
1768.gallery-search::placeholder {
1769
1770 color: #98a2b3;
1771
1772}
1773
1774
1775.gallery-search:hover {
1776
1777 border-color: #145a96;
1778
1779}
1780
1781
1782.gallery-search:focus {
1783
1784 outline: none;
1785
1786 border-color: #145a96;
1787
1788 box-shadow:
1789 0 0 0 3px
1790 rgba(20, 90, 150, 0.12);
1791
1792}
1793
1794
1795.search-icon {
1796
1797 position: absolute;
1798
1799 left: 15px;
1800
1801 top: 50%;
1802
1803 transform: translateY(-50%);
1804
1805 color: #667085;
1806
1807 pointer-events: none;
1808
1809}
1810
1811
1812.clear-search {
1813
1814 position: absolute;
1815
1816 right: 8px;
1817
1818 top: 50%;
1819
1820 transform: translateY(-50%);
1821
1822 width: 32px;
1823
1824 height: 32px;
1825
1826 border: none;
1827
1828 border-radius: 6px;
1829
1830 background: transparent;
1831
1832 color: #667085;
1833
1834 display: none;
1835
1836 align-items: center;
1837
1838 justify-content: center;
1839
1840 cursor: pointer;
1841
1842}
1843
1844
1845.clear-search:hover {
1846
1847 background: #f2f4f7;
1848
1849 color: #344054;
1850
1851}
1852
1853
1854/* =====================================
1855 PHOTO COUNT
1856===================================== */
1857
1858.photo-count-box {
1859
1860 min-width: 190px;
1861
1862 height: 48px;
1863
1864 padding: 6px 14px;
1865
1866 border-radius: 8px;
1867
1868 background: #f4f8fc;
1869
1870 border: 1px solid #dce8f3;
1871
1872 display: flex;
1873
1874 align-items: center;
1875
1876 gap: 11px;
1877
1878}
1879
1880
1881.count-icon {
1882
1883 width: 36px;
1884
1885 height: 36px;
1886
1887 border-radius: 7px;
1888
1889 display: flex;
1890
1891 align-items: center;
1892
1893 justify-content: center;
1894
1895 background: #145a96;
1896
1897 color: #ffffff;
1898
1899 font-size: 17px;
1900
1901}
1902
1903
1904.count-content {
1905
1906 display: flex;
1907
1908 align-items: center;
1909
1910 gap: 8px;
1911
1912}
1913
1914
1915.count-label {
1916
1917 font-size: 13px;
1918
1919 color: #667085;
1920
1921 white-space: nowrap;
1922
1923}
1924
1925
1926.count-content strong {
1927
1928 font-size: 20px;
1929
1930 color: #145a96;
1931
1932 line-height: 1;
1933
1934}
1935
1936
1937/* =====================================
1938 RESPONSIVE
1939===================================== */
1940
1941@media (max-width: 900px) {
1942
1943 .gallery-filter-row {
1944
1945 flex-wrap: wrap;
1946
1947 }
1948
1949 .category-group {
1950
1951 flex: 1;
1952
1953 min-width: 220px;
1954
1955 }
1956
1957 .search-group {
1958
1959 min-width: 280px;
1960
1961 }
1962
1963 .photo-count-box {
1964
1965 min-width: 180px;
1966
1967 }
1968
1969}
1970
1971
1972@media (max-width: 600px) {
1973
1974 .gallery-toolbar {
1975
1976 padding: 18px;
1977
1978 }
1979
1980 .gallery-filter-row {
1981
1982 flex-direction: column;
1983
1984 align-items: stretch;
1985
1986 gap: 16px;
1987
1988 }
1989
1990 .category-group,
1991 .search-group {
1992
1993 width: 100%;
1994
1995 max-width: none;
1996
1997 }
1998
1999 .photo-count-box {
2000
2001 width: 100%;
2002
2003 }
2004
2005}
2006
2007
2008
2009 </style>