@chiselandco/nexus 2.2.6 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +286 -298
- package/dist/FilterSidebar.d.ts +20 -0
- package/dist/FilterSidebar.d.ts.map +1 -0
- package/dist/FilterSidebar.js +266 -0
- package/dist/FilteredPortfolio.d.ts +45 -0
- package/dist/FilteredPortfolio.d.ts.map +1 -0
- package/dist/FilteredPortfolio.js +134 -0
- package/dist/GalleryCarousel.d.ts +9 -2
- package/dist/GalleryCarousel.d.ts.map +1 -1
- package/dist/GalleryCarousel.js +363 -63
- package/dist/ProjectDetail.d.ts +3 -1
- package/dist/ProjectDetail.d.ts.map +1 -1
- package/dist/ProjectDetail.js +46 -20
- package/dist/ProjectMenu.d.ts +9 -4
- package/dist/ProjectMenu.d.ts.map +1 -1
- package/dist/ProjectMenu.js +13 -17
- package/dist/ProjectMenuClient.d.ts +4 -2
- package/dist/ProjectMenuClient.d.ts.map +1 -1
- package/dist/ProjectMenuClient.js +6 -7
- package/dist/ProjectPortfolio.d.ts +4 -2
- package/dist/ProjectPortfolio.d.ts.map +1 -1
- package/dist/ProjectPortfolio.js +5 -5
- package/dist/ProjectPortfolioClient.d.ts +3 -1
- package/dist/ProjectPortfolioClient.d.ts.map +1 -1
- package/dist/ProjectPortfolioClient.js +4 -6
- package/dist/SimilarProjects.d.ts +3 -1
- package/dist/SimilarProjects.d.ts.map +1 -1
- package/dist/SimilarProjects.js +11 -9
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -6
- package/dist/ProjectFilters.d.ts +0 -11
- package/dist/ProjectFilters.d.ts.map +0 -1
- package/dist/ProjectFilters.js +0 -49
- package/dist/ProjectGrid.d.ts +0 -10
- package/dist/ProjectGrid.d.ts.map +0 -1
- package/dist/ProjectGrid.js +0 -8
package/dist/GalleryCarousel.js
CHANGED
|
@@ -1,84 +1,384 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
|
|
4
|
+
import { useRouter, useSearchParams, usePathname } from "next/navigation";
|
|
5
|
+
const FONT = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
|
|
6
|
+
const ACCENT = "#C8872A";
|
|
7
|
+
const INK = "#0f0f0f";
|
|
8
|
+
const MIST = "#f7f6f4";
|
|
9
|
+
const BORDER = "#e2e0dc";
|
|
10
|
+
const LABEL = "#8a8680";
|
|
11
|
+
function resolveLabel(slug, field) {
|
|
12
|
+
var _a;
|
|
13
|
+
for (const opt of (_a = field.options) !== null && _a !== void 0 ? _a : []) {
|
|
14
|
+
if (typeof opt === "object" && opt.id === slug) {
|
|
15
|
+
return opt.label;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return slug.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
19
|
+
}
|
|
20
|
+
function getTagsForImage(image, schema) {
|
|
21
|
+
const cfv = image.custom_field_values;
|
|
22
|
+
if (!cfv || typeof cfv !== "object")
|
|
23
|
+
return [];
|
|
24
|
+
const tags = [];
|
|
25
|
+
for (const field of schema) {
|
|
26
|
+
const raw = cfv[field.key];
|
|
27
|
+
if (raw === null || raw === undefined)
|
|
28
|
+
continue;
|
|
29
|
+
const slugs = Array.isArray(raw) ? raw : typeof raw === "string" ? [raw] : [];
|
|
30
|
+
for (const slug of slugs) {
|
|
31
|
+
if (slug)
|
|
32
|
+
tags.push({ label: resolveLabel(slug, field), fieldName: field.name, fieldKey: field.key, valueSlug: slug });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return tags;
|
|
36
|
+
}
|
|
37
|
+
function buildFilterOptions(images, schema) {
|
|
38
|
+
const fieldValueMap = new Map();
|
|
39
|
+
for (const img of images) {
|
|
40
|
+
const tags = getTagsForImage(img, schema);
|
|
41
|
+
for (const tag of tags) {
|
|
42
|
+
if (!fieldValueMap.has(tag.fieldKey))
|
|
43
|
+
fieldValueMap.set(tag.fieldKey, new Set());
|
|
44
|
+
fieldValueMap.get(tag.fieldKey).add(tag.valueSlug);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const result = [];
|
|
48
|
+
for (const field of schema) {
|
|
49
|
+
const slugSet = fieldValueMap.get(field.key);
|
|
50
|
+
if (!slugSet || slugSet.size === 0)
|
|
51
|
+
continue;
|
|
52
|
+
result.push({
|
|
53
|
+
field,
|
|
54
|
+
values: Array.from(slugSet).map((slug) => ({ slug, label: resolveLabel(slug, field) })),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
function readFiltersFromSearch(params) {
|
|
60
|
+
const filters = {};
|
|
61
|
+
params.forEach((value, key) => {
|
|
62
|
+
const match = key.match(/^filter\[(.+)\]$/);
|
|
63
|
+
if (match)
|
|
64
|
+
filters[match[1]] = value;
|
|
65
|
+
});
|
|
66
|
+
return filters;
|
|
67
|
+
}
|
|
68
|
+
function labelToSlug(label, field) {
|
|
69
|
+
var _a;
|
|
70
|
+
for (const opt of (_a = field.options) !== null && _a !== void 0 ? _a : []) {
|
|
71
|
+
if (typeof opt === "object" && opt.label === label) {
|
|
72
|
+
return opt.id;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
// ─── Dropdown trigger button ───────────────────────────────────────────────
|
|
78
|
+
function FilterDropdown({ field, values, activeSlug, onSelect, }) {
|
|
79
|
+
var _a;
|
|
80
|
+
const [open, setOpen] = useState(false);
|
|
81
|
+
const ref = useRef(null);
|
|
82
|
+
const activeLabel = activeSlug ? (_a = values.find(v => v.slug === activeSlug)) === null || _a === void 0 ? void 0 : _a.label : undefined;
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!open)
|
|
85
|
+
return;
|
|
86
|
+
function onDown(e) {
|
|
87
|
+
if (ref.current && !ref.current.contains(e.target))
|
|
88
|
+
setOpen(false);
|
|
89
|
+
}
|
|
90
|
+
document.addEventListener("mousedown", onDown);
|
|
91
|
+
return () => document.removeEventListener("mousedown", onDown);
|
|
92
|
+
}, [open]);
|
|
93
|
+
const isActive = !!activeSlug;
|
|
94
|
+
return (_jsxs("div", { ref: ref, style: { position: "relative" }, children: [_jsxs("button", { onClick: () => setOpen(o => !o), style: {
|
|
95
|
+
display: "inline-flex",
|
|
96
|
+
alignItems: "center",
|
|
97
|
+
gap: "6px",
|
|
98
|
+
height: "32px",
|
|
99
|
+
padding: "0 12px",
|
|
100
|
+
fontFamily: FONT,
|
|
101
|
+
fontSize: "12px",
|
|
102
|
+
fontWeight: isActive ? 600 : 400,
|
|
103
|
+
letterSpacing: "0.01em",
|
|
104
|
+
color: isActive ? "#fff" : INK,
|
|
105
|
+
backgroundColor: isActive ? INK : "#fff",
|
|
106
|
+
border: `1px solid ${isActive ? INK : BORDER}`,
|
|
107
|
+
borderRadius: "4px",
|
|
108
|
+
cursor: "pointer",
|
|
109
|
+
whiteSpace: "nowrap",
|
|
110
|
+
transition: "background 0.1s, border-color 0.1s, color 0.1s",
|
|
111
|
+
}, onMouseEnter: e => {
|
|
112
|
+
if (!isActive)
|
|
113
|
+
e.currentTarget.style.borderColor = "#6b6860";
|
|
114
|
+
}, onMouseLeave: e => {
|
|
115
|
+
if (!isActive)
|
|
116
|
+
e.currentTarget.style.borderColor = BORDER;
|
|
117
|
+
}, children: [_jsx("span", { style: {
|
|
118
|
+
fontSize: "9px",
|
|
119
|
+
fontWeight: 700,
|
|
120
|
+
letterSpacing: "0.1em",
|
|
121
|
+
textTransform: "uppercase",
|
|
122
|
+
color: isActive ? "rgba(255,255,255,0.6)" : LABEL,
|
|
123
|
+
}, children: field.name }), activeLabel && (_jsxs(_Fragment, { children: [_jsx("span", { style: { color: isActive ? "rgba(255,255,255,0.4)" : BORDER }, children: "\u00B7" }), _jsx("span", { children: activeLabel })] })), _jsx("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", style: {
|
|
124
|
+
marginLeft: "2px",
|
|
125
|
+
transform: open ? "rotate(180deg)" : "rotate(0deg)",
|
|
126
|
+
transition: "transform 0.15s",
|
|
127
|
+
opacity: 0.5,
|
|
128
|
+
flexShrink: 0,
|
|
129
|
+
}, children: _jsx("path", { d: "M2 3.5l3 3 3-3" }) })] }), open && (_jsx("div", { style: {
|
|
130
|
+
position: "absolute",
|
|
131
|
+
top: "calc(100% + 5px)",
|
|
132
|
+
left: 0,
|
|
133
|
+
zIndex: 50,
|
|
134
|
+
minWidth: "180px",
|
|
135
|
+
backgroundColor: "#fff",
|
|
136
|
+
border: `1px solid ${BORDER}`,
|
|
137
|
+
borderRadius: "5px",
|
|
138
|
+
boxShadow: "0 4px 20px rgba(0,0,0,0.10), 0 1px 4px rgba(0,0,0,0.06)",
|
|
139
|
+
overflow: "hidden",
|
|
140
|
+
}, children: values.map(({ slug, label }) => {
|
|
141
|
+
const selected = activeSlug === slug;
|
|
142
|
+
return (_jsxs("button", { onClick: () => {
|
|
143
|
+
onSelect(slug, label);
|
|
144
|
+
setOpen(false);
|
|
145
|
+
}, style: {
|
|
146
|
+
display: "flex",
|
|
147
|
+
alignItems: "center",
|
|
148
|
+
justifyContent: "space-between",
|
|
149
|
+
width: "100%",
|
|
150
|
+
padding: "9px 14px",
|
|
151
|
+
fontFamily: FONT,
|
|
152
|
+
fontSize: "13px",
|
|
153
|
+
fontWeight: selected ? 600 : 400,
|
|
154
|
+
color: selected ? INK : "#3d3b38",
|
|
155
|
+
backgroundColor: selected ? MIST : "#fff",
|
|
156
|
+
border: "none",
|
|
157
|
+
borderBottom: `1px solid ${BORDER}`,
|
|
158
|
+
cursor: "pointer",
|
|
159
|
+
textAlign: "left",
|
|
160
|
+
transition: "background 0.08s",
|
|
161
|
+
letterSpacing: "0.005em",
|
|
162
|
+
}, onMouseEnter: e => { if (!selected)
|
|
163
|
+
e.currentTarget.style.backgroundColor = MIST; }, onMouseLeave: e => { if (!selected)
|
|
164
|
+
e.currentTarget.style.backgroundColor = "#fff"; }, children: [_jsx("span", { children: label }), selected && (_jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", stroke: ACCENT, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M2 6l3 3 5-5" }) }))] }, slug));
|
|
165
|
+
}) }))] }));
|
|
166
|
+
}
|
|
167
|
+
// ─── Main component ────────────────────────────────────────────────────────
|
|
168
|
+
export function GalleryCarousel({ images, projectTitle, schema = [] }) {
|
|
169
|
+
var _a;
|
|
170
|
+
const router = useRouter();
|
|
171
|
+
const pathname = usePathname();
|
|
172
|
+
const searchParams = useSearchParams();
|
|
173
|
+
const filterOptions = useMemo(() => buildFilterOptions(images, schema), [images, schema]);
|
|
174
|
+
const activeFilters = useMemo(() => {
|
|
175
|
+
var _a;
|
|
176
|
+
const fromUrl = readFiltersFromSearch(searchParams);
|
|
177
|
+
const resolved = {};
|
|
178
|
+
for (const [fieldKey, label] of Object.entries(fromUrl)) {
|
|
179
|
+
const fieldDef = schema.find((f) => f.key === fieldKey);
|
|
180
|
+
if (!fieldDef)
|
|
181
|
+
continue;
|
|
182
|
+
const slug = (_a = labelToSlug(label, fieldDef)) !== null && _a !== void 0 ? _a : label;
|
|
183
|
+
resolved[fieldKey] = slug;
|
|
184
|
+
}
|
|
185
|
+
return resolved;
|
|
186
|
+
}, [searchParams, schema]);
|
|
6
187
|
const [current, setCurrent] = useState(0);
|
|
188
|
+
const filteredImages = useMemo(() => {
|
|
189
|
+
const activeEntries = Object.entries(activeFilters);
|
|
190
|
+
if (activeEntries.length === 0)
|
|
191
|
+
return images;
|
|
192
|
+
return images.filter((img) => {
|
|
193
|
+
const cfv = img.custom_field_values;
|
|
194
|
+
if (!cfv)
|
|
195
|
+
return false;
|
|
196
|
+
return activeEntries.every(([key, slug]) => {
|
|
197
|
+
const raw = cfv[key];
|
|
198
|
+
if (raw === null || raw === undefined)
|
|
199
|
+
return false;
|
|
200
|
+
const slugs = Array.isArray(raw) ? raw : [raw];
|
|
201
|
+
return slugs.includes(slug);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
}, [images, activeFilters]);
|
|
205
|
+
useEffect(() => { setCurrent(0); }, [filteredImages]);
|
|
206
|
+
const safeIndex = Math.min(current, Math.max(filteredImages.length - 1, 0));
|
|
207
|
+
const active = (_a = filteredImages[safeIndex]) !== null && _a !== void 0 ? _a : null;
|
|
208
|
+
const total = filteredImages.length;
|
|
209
|
+
function prev() { setCurrent((c) => (c - 1 + total) % total); }
|
|
210
|
+
function next() { setCurrent((c) => (c + 1) % total); }
|
|
211
|
+
const toggleFilter = useCallback((fieldKey, valueSlug, valueLabel) => {
|
|
212
|
+
const params = new URLSearchParams(searchParams.toString());
|
|
213
|
+
const paramKey = `filter[${fieldKey}]`;
|
|
214
|
+
const existing = params.get(paramKey);
|
|
215
|
+
if (existing === valueLabel) {
|
|
216
|
+
params.delete(paramKey);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
params.set(paramKey, valueLabel);
|
|
220
|
+
}
|
|
221
|
+
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
|
|
222
|
+
}, [searchParams, pathname, router]);
|
|
223
|
+
const clearFilters = useCallback(() => {
|
|
224
|
+
const params = new URLSearchParams(searchParams.toString());
|
|
225
|
+
Array.from(params.keys()).forEach((key) => {
|
|
226
|
+
if (key.startsWith("filter["))
|
|
227
|
+
params.delete(key);
|
|
228
|
+
});
|
|
229
|
+
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
|
|
230
|
+
}, [searchParams, pathname, router]);
|
|
231
|
+
const hasActiveFilter = Object.keys(activeFilters).length > 0;
|
|
232
|
+
const activeFilterCount = Object.keys(activeFilters).length;
|
|
7
233
|
if (images.length === 0)
|
|
8
234
|
return null;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
235
|
+
const activeTags = active ? getTagsForImage(active, schema) : [];
|
|
236
|
+
return (_jsxs("div", { style: { display: "flex", flexDirection: "column", fontFamily: FONT }, children: [filterOptions.length > 0 && (_jsx("div", { style: { marginBottom: "14px" }, children: _jsxs("div", { style: {
|
|
237
|
+
display: "flex",
|
|
238
|
+
alignItems: "center",
|
|
239
|
+
gap: "6px",
|
|
240
|
+
flexWrap: "wrap",
|
|
241
|
+
}, children: [filterOptions.map(({ field, values }) => (_jsx(FilterDropdown, { field: field, values: values, activeSlug: activeFilters[field.key], onSelect: (slug, label) => toggleFilter(field.key, slug, label) }, field.key))), hasActiveFilter && (_jsxs(_Fragment, { children: [_jsx("div", { style: { width: "1px", height: "18px", backgroundColor: BORDER, margin: "0 2px" } }), _jsxs("button", { onClick: clearFilters, style: {
|
|
242
|
+
display: "inline-flex",
|
|
243
|
+
alignItems: "center",
|
|
244
|
+
gap: "5px",
|
|
245
|
+
height: "32px",
|
|
246
|
+
padding: "0 10px",
|
|
247
|
+
fontFamily: FONT,
|
|
248
|
+
fontSize: "12px",
|
|
249
|
+
fontWeight: 400,
|
|
250
|
+
color: LABEL,
|
|
251
|
+
background: "none",
|
|
252
|
+
border: "none",
|
|
253
|
+
cursor: "pointer",
|
|
254
|
+
borderRadius: "4px",
|
|
255
|
+
transition: "color 0.1s, background 0.1s",
|
|
256
|
+
}, onMouseEnter: e => {
|
|
257
|
+
e.currentTarget.style.color = INK;
|
|
258
|
+
e.currentTarget.style.backgroundColor = MIST;
|
|
259
|
+
}, onMouseLeave: e => {
|
|
260
|
+
e.currentTarget.style.color = LABEL;
|
|
261
|
+
e.currentTarget.style.backgroundColor = "transparent";
|
|
262
|
+
}, children: [_jsx("svg", { width: "9", height: "9", viewBox: "0 0 10 10", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", children: _jsx("path", { d: "M1 1l8 8M9 1L1 9" }) }), "Clear ", activeFilterCount > 1 ? `${activeFilterCount} filters` : "filter"] })] })), _jsx("span", { style: {
|
|
263
|
+
marginLeft: "auto",
|
|
264
|
+
fontSize: "11px",
|
|
265
|
+
color: LABEL,
|
|
266
|
+
letterSpacing: "0.01em",
|
|
267
|
+
whiteSpace: "nowrap",
|
|
268
|
+
}, children: hasActiveFilter ? (_jsxs(_Fragment, { children: [_jsx("strong", { style: { color: INK, fontWeight: 600 }, children: total }), " / ", images.length] })) : (_jsxs(_Fragment, { children: [images.length, " image", images.length !== 1 ? "s" : ""] })) })] }) })), total === 0 && (_jsxs("div", { style: {
|
|
269
|
+
width: "100%",
|
|
270
|
+
aspectRatio: "16/9",
|
|
271
|
+
backgroundColor: MIST,
|
|
272
|
+
border: `1px solid ${BORDER}`,
|
|
273
|
+
display: "flex",
|
|
274
|
+
flexDirection: "column",
|
|
275
|
+
alignItems: "center",
|
|
276
|
+
justifyContent: "center",
|
|
277
|
+
gap: "8px",
|
|
278
|
+
}, children: [_jsx("span", { style: { fontSize: "11px", letterSpacing: "0.1em", textTransform: "uppercase", color: LABEL, fontWeight: 600 }, children: "No images match" }), _jsx("button", { onClick: clearFilters, style: {
|
|
279
|
+
fontSize: "11px",
|
|
280
|
+
fontFamily: FONT,
|
|
281
|
+
color: ACCENT,
|
|
282
|
+
background: "none",
|
|
28
283
|
border: "none",
|
|
29
284
|
cursor: "pointer",
|
|
285
|
+
letterSpacing: "0.04em",
|
|
286
|
+
textDecoration: "underline",
|
|
287
|
+
}, children: "Clear filters" })] })), active && (_jsxs("div", { style: { position: "relative", width: "100%", aspectRatio: "16/9", backgroundColor: "#1a1916", overflow: "hidden" }, children: [_jsx("img", { src: active.url, alt: active.alt || projectTitle, style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: "block" } }), _jsx("div", { style: {
|
|
288
|
+
position: "absolute",
|
|
289
|
+
inset: "50% 0 0 0",
|
|
290
|
+
background: "linear-gradient(to bottom, transparent, rgba(0,0,0,0.52))",
|
|
291
|
+
pointerEvents: "none",
|
|
292
|
+
} }), total > 1 && (_jsx("button", { onClick: prev, "aria-label": "Previous image", style: arrowBtn("left"), children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M15 18l-6-6 6-6" }) }) })), total > 1 && (_jsx("button", { onClick: next, "aria-label": "Next image", style: arrowBtn("right"), children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M9 18l6-6-6-6" }) }) })), activeTags.length > 0 && (_jsx("div", { style: {
|
|
293
|
+
position: "absolute",
|
|
294
|
+
bottom: "16px",
|
|
295
|
+
left: "16px",
|
|
30
296
|
display: "flex",
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}, children:
|
|
297
|
+
flexWrap: "wrap",
|
|
298
|
+
gap: "5px",
|
|
299
|
+
maxWidth: "calc(100% - 100px)",
|
|
300
|
+
}, children: activeTags.map((tag, i) => (_jsxs("span", { style: {
|
|
301
|
+
display: "inline-flex",
|
|
302
|
+
alignItems: "baseline",
|
|
303
|
+
gap: "5px",
|
|
304
|
+
padding: "5px 10px",
|
|
305
|
+
backgroundColor: "rgba(15,15,15,0.72)",
|
|
306
|
+
backdropFilter: "blur(8px)",
|
|
307
|
+
WebkitBackdropFilter: "blur(8px)",
|
|
308
|
+
borderLeft: `2px solid ${ACCENT}`,
|
|
309
|
+
color: "#fff",
|
|
310
|
+
fontSize: "11px",
|
|
311
|
+
fontFamily: FONT,
|
|
312
|
+
lineHeight: 1.2,
|
|
313
|
+
whiteSpace: "nowrap",
|
|
314
|
+
}, children: [_jsx("span", { style: {
|
|
315
|
+
fontSize: "9px",
|
|
316
|
+
fontWeight: 700,
|
|
317
|
+
letterSpacing: "0.1em",
|
|
318
|
+
textTransform: "uppercase",
|
|
319
|
+
color: ACCENT,
|
|
320
|
+
}, children: tag.fieldName }), _jsx("span", { style: { fontWeight: 500, color: "#f0ede8" }, children: tag.label })] }, i))) })), total > 1 && (_jsxs("div", { style: {
|
|
35
321
|
position: "absolute",
|
|
322
|
+
bottom: "16px",
|
|
36
323
|
right: "16px",
|
|
37
|
-
top: "50%",
|
|
38
|
-
transform: "translateY(-50%)",
|
|
39
|
-
width: "40px",
|
|
40
|
-
height: "40px",
|
|
41
|
-
borderRadius: "50%",
|
|
42
|
-
backgroundColor: "rgba(255,255,255,0.92)",
|
|
43
|
-
border: "none",
|
|
44
|
-
cursor: "pointer",
|
|
45
324
|
display: "flex",
|
|
46
|
-
alignItems: "
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
padding: "4px 10px",
|
|
58
|
-
borderRadius: "9999px",
|
|
59
|
-
fontFamily: font,
|
|
60
|
-
}, children: [current + 1, " / ", total] }))] }), caption && (_jsx("p", { style: {
|
|
61
|
-
textAlign: "center",
|
|
62
|
-
fontSize: "14px",
|
|
325
|
+
alignItems: "baseline",
|
|
326
|
+
gap: "2px",
|
|
327
|
+
color: "rgba(255,255,255,0.6)",
|
|
328
|
+
fontSize: "11px",
|
|
329
|
+
fontFamily: FONT,
|
|
330
|
+
letterSpacing: "0.04em",
|
|
331
|
+
}, children: [_jsx("span", { style: { color: "#fff", fontWeight: 600, fontSize: "13px" }, children: safeIndex + 1 }), _jsx("span", { style: { margin: "0 2px" }, children: "/" }), _jsx("span", { children: total })] }))] })), active && (active.caption || active.description) && (_jsx("p", { style: {
|
|
332
|
+
fontSize: "12px",
|
|
333
|
+
color: LABEL,
|
|
334
|
+
letterSpacing: "0.02em",
|
|
335
|
+
margin: "10px 0 0",
|
|
63
336
|
fontStyle: "italic",
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
337
|
+
lineHeight: 1.5,
|
|
338
|
+
}, children: active.caption || active.description })), total > 1 && (_jsx("div", { style: {
|
|
339
|
+
display: "flex",
|
|
340
|
+
gap: "4px",
|
|
341
|
+
overflowX: "auto",
|
|
342
|
+
marginTop: "6px",
|
|
343
|
+
paddingBottom: "2px",
|
|
344
|
+
}, children: filteredImages.map((img, i) => {
|
|
69
345
|
var _a;
|
|
346
|
+
const isActive = i === safeIndex;
|
|
70
347
|
return (_jsx("button", { onClick: () => setCurrent(i), "aria-label": `View image ${i + 1}`, style: {
|
|
71
|
-
|
|
348
|
+
position: "relative",
|
|
349
|
+
width: "72px",
|
|
72
350
|
aspectRatio: "16/9",
|
|
73
351
|
padding: 0,
|
|
74
|
-
border:
|
|
75
|
-
borderRadius: "
|
|
352
|
+
border: "none",
|
|
353
|
+
borderRadius: "1px",
|
|
76
354
|
overflow: "hidden",
|
|
77
355
|
cursor: "pointer",
|
|
78
356
|
flexShrink: 0,
|
|
79
|
-
backgroundColor: "#
|
|
80
|
-
|
|
81
|
-
|
|
357
|
+
backgroundColor: "#1a1916",
|
|
358
|
+
outline: isActive ? `2px solid ${ACCENT}` : `1px solid ${BORDER}`,
|
|
359
|
+
outlineOffset: isActive ? "1px" : "0",
|
|
360
|
+
transition: "outline 0.12s, opacity 0.12s",
|
|
361
|
+
opacity: isActive ? 1 : 0.55,
|
|
82
362
|
}, children: _jsx("img", { src: img.url, alt: img.alt || `Image ${i + 1}`, style: { width: "100%", height: "100%", objectFit: "cover", display: "block" } }) }, (_a = img.id) !== null && _a !== void 0 ? _a : i));
|
|
83
363
|
}) }))] }));
|
|
84
364
|
}
|
|
365
|
+
function arrowBtn(side) {
|
|
366
|
+
return {
|
|
367
|
+
position: "absolute",
|
|
368
|
+
[side]: "14px",
|
|
369
|
+
top: "50%",
|
|
370
|
+
transform: "translateY(-50%)",
|
|
371
|
+
width: "36px",
|
|
372
|
+
height: "36px",
|
|
373
|
+
borderRadius: "2px",
|
|
374
|
+
backgroundColor: "rgba(255,255,255,0.96)",
|
|
375
|
+
border: "none",
|
|
376
|
+
cursor: "pointer",
|
|
377
|
+
display: "flex",
|
|
378
|
+
alignItems: "center",
|
|
379
|
+
justifyContent: "center",
|
|
380
|
+
color: INK,
|
|
381
|
+
boxShadow: "0 1px 6px rgba(0,0,0,0.22)",
|
|
382
|
+
transition: "background 0.1s",
|
|
383
|
+
};
|
|
384
|
+
}
|
package/dist/ProjectDetail.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export interface ProjectDetailProps {
|
|
|
5
5
|
clientSlug: string;
|
|
6
6
|
/** Base URL of the projects API */
|
|
7
7
|
apiBase: string;
|
|
8
|
+
/** Client API key — pass via environment variable, never hardcode */
|
|
9
|
+
apiKey: string;
|
|
8
10
|
/** Base path for the "back" link and "View All" link. Defaults to "/projects" */
|
|
9
11
|
backPath?: string;
|
|
10
12
|
/** Label for the "back" link. Defaults to "All Projects" */
|
|
@@ -21,5 +23,5 @@ export interface ProjectDetailProps {
|
|
|
21
23
|
*/
|
|
22
24
|
noCache?: boolean;
|
|
23
25
|
}
|
|
24
|
-
export declare function ProjectDetail({ slug, clientSlug, apiBase, backPath, backLabel, revalidate, noCache, }: ProjectDetailProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
26
|
+
export declare function ProjectDetail({ slug, clientSlug, apiBase, apiKey, backPath, backLabel, revalidate, noCache, }: ProjectDetailProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
25
27
|
//# sourceMappingURL=ProjectDetail.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectDetail.d.ts","sourceRoot":"","sources":["../src/ProjectDetail.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProjectDetail.d.ts","sourceRoot":"","sources":["../src/ProjectDetail.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,kBAAkB;IACjC,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAA;IAClB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAA;IACd,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAqFD,wBAAsB,aAAa,CAAC,EAClC,IAAI,EACJ,UAAU,EACV,OAAO,EACP,MAAM,EACN,QAAsB,EACtB,SAA0B,EAC1B,UAAe,EACf,OAAe,GAChB,EAAE,kBAAkB,oDAgRpB"}
|
package/dist/ProjectDetail.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { cache } from "react";
|
|
3
|
-
// LocationValue is used in locationValue cast below
|
|
4
2
|
import { GalleryCarousel } from "./GalleryCarousel";
|
|
5
3
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
6
4
|
function parseMultiValue(raw) {
|
|
@@ -20,21 +18,38 @@ function dedupeByKey(arr) {
|
|
|
20
18
|
});
|
|
21
19
|
}
|
|
22
20
|
// ─── Data fetching ───────────────────────────────────────────────────────────
|
|
23
|
-
|
|
21
|
+
async function fetchProjectDetail(apiBase, clientSlug, slug, apiKey, revalidate, noCache) {
|
|
24
22
|
var _a, _b, _c, _d;
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
24
|
const fetchOpts = noCache
|
|
26
25
|
? { cache: "no-store" }
|
|
27
26
|
: revalidate > 0
|
|
28
27
|
? { next: { revalidate } }
|
|
29
28
|
: {};
|
|
30
29
|
try {
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
|
|
30
|
+
// Fetch both in parallel:
|
|
31
|
+
// 1. Single project for detail data + schema
|
|
32
|
+
// 2. List endpoint for enriched media (custom_field_values only available there)
|
|
33
|
+
const [projectRes, listRes] = await Promise.all([
|
|
34
|
+
fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects/${slug}?api_key=${apiKey}`, fetchOpts),
|
|
35
|
+
fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects?api_key=${apiKey}`, fetchOpts),
|
|
36
|
+
]);
|
|
34
37
|
const projectJson = projectRes.ok ? await projectRes.json() : null;
|
|
38
|
+
const listJson = listRes.ok ? await listRes.json() : null;
|
|
35
39
|
const project = (_a = projectJson === null || projectJson === void 0 ? void 0 : projectJson.data) !== null && _a !== void 0 ? _a : null;
|
|
40
|
+
// Merge custom_field_values from list media onto project media by id
|
|
41
|
+
if ((project === null || project === void 0 ? void 0 : project.media) && (listJson === null || listJson === void 0 ? void 0 : listJson.data)) {
|
|
42
|
+
const listProject = listJson.data.find((p) => p.slug === slug);
|
|
43
|
+
if (listProject === null || listProject === void 0 ? void 0 : listProject.media) {
|
|
44
|
+
const listMediaMap = new Map(listProject.media.map((m) => [m.id, m]));
|
|
45
|
+
project.media = project.media.map((m) => {
|
|
46
|
+
const enriched = listMediaMap.get(m.id);
|
|
47
|
+
return (enriched === null || enriched === void 0 ? void 0 : enriched.custom_field_values)
|
|
48
|
+
? Object.assign(Object.assign({}, m), { custom_field_values: enriched.custom_field_values }) : m;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
36
52
|
const schema = dedupeByKey((_c = (_b = projectJson === null || projectJson === void 0 ? void 0 : projectJson.client) === null || _b === void 0 ? void 0 : _b.custom_fields_schema) !== null && _c !== void 0 ? _c : []);
|
|
37
|
-
// Build fieldOptionsMap directly from schema options — identical data to /fields
|
|
38
53
|
const fieldOptionsMap = {};
|
|
39
54
|
for (const field of schema) {
|
|
40
55
|
const map = {};
|
|
@@ -51,25 +66,25 @@ const fetchProjectDetail = cache(async (apiBase, clientSlug, slug, revalidate, n
|
|
|
51
66
|
catch (_e) {
|
|
52
67
|
return { project: null, schema: [], fieldOptionsMap: {} };
|
|
53
68
|
}
|
|
54
|
-
}
|
|
69
|
+
}
|
|
55
70
|
// ─── Component ───────────────────────────────────────────────────────────────
|
|
56
|
-
export async function ProjectDetail({ slug, clientSlug, apiBase, backPath = "/projects", backLabel = "All Projects", revalidate = 60, noCache = false, }) {
|
|
57
|
-
var _a, _b, _c, _d, _e, _f, _g, _h
|
|
58
|
-
const { project, schema, fieldOptionsMap } = await fetchProjectDetail(apiBase, clientSlug, slug, revalidate, noCache);
|
|
71
|
+
export async function ProjectDetail({ slug, clientSlug, apiBase, apiKey, backPath = "/projects", backLabel = "All Projects", revalidate = 60, noCache = false, }) {
|
|
72
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
73
|
+
const { project, schema, fieldOptionsMap } = await fetchProjectDetail(apiBase, clientSlug, slug, apiKey, revalidate, noCache);
|
|
59
74
|
if (!project) {
|
|
60
75
|
return (_jsx("div", { style: { textAlign: "center", padding: "6rem 1.5rem", fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" }, children: _jsx("p", { style: { color: "#71717a", fontSize: "18px" }, children: "Project not found." }) }));
|
|
61
76
|
}
|
|
62
77
|
const imageUrl = (_d = (_a = project.image_url) !== null && _a !== void 0 ? _a : (_c = (_b = project.media) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.url) !== null && _d !== void 0 ? _d : null;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
78
|
+
// Always use all media items for the gallery — never exclude media[0] even if image_url is set,
|
|
79
|
+
// since media items carry custom_field_values (tags) that must be preserved.
|
|
80
|
+
const galleryImages = (_e = project.media) !== null && _e !== void 0 ? _e : [];
|
|
66
81
|
const badgeField = schema.find((f) => f.display_position === "badge_overlay");
|
|
67
82
|
const locationField = schema.find((f) => f.type === "location");
|
|
68
83
|
const badgeRaw = badgeField
|
|
69
|
-
? ((
|
|
84
|
+
? ((_f = parseMultiValue(project.custom_field_values[badgeField.key])[0]) !== null && _f !== void 0 ? _f : null)
|
|
70
85
|
: null;
|
|
71
|
-
const badgeOptMap = badgeField ? ((
|
|
72
|
-
const badgeValue = badgeRaw ? ((
|
|
86
|
+
const badgeOptMap = badgeField ? ((_g = fieldOptionsMap[badgeField.key]) !== null && _g !== void 0 ? _g : {}) : {};
|
|
87
|
+
const badgeValue = badgeRaw ? ((_h = badgeOptMap[badgeRaw]) !== null && _h !== void 0 ? _h : badgeRaw) : null;
|
|
73
88
|
const locationValue = locationField
|
|
74
89
|
? project.custom_field_values[locationField.key]
|
|
75
90
|
: null;
|
|
@@ -133,7 +148,18 @@ export async function ProjectDetail({ slug, clientSlug, apiBase, backPath = "/pr
|
|
|
133
148
|
.map((key) => schema.find((f) => f.key === key))
|
|
134
149
|
.filter((f) => f !== undefined);
|
|
135
150
|
const font = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
|
|
136
|
-
return (_jsxs("main", { style: { minHeight: "100vh", backgroundColor: "#fff", fontFamily: font }, children: [_jsxs("section", { className: "chisel-hero-img", style: { position: "relative", width: "100%", overflow: "hidden" }, children: [imageUrl ? (_jsx("img", { src: imageUrl, alt: project.title, style: { width: "100%", height: "100%", objectFit: "cover", display: "block" } })) : (_jsx("div", { style: { position: "absolute", inset: 0, backgroundColor: "#27272a" } })), _jsx("div", { style: { position: "absolute", inset: 0, background: "linear-gradient(to
|
|
151
|
+
return (_jsxs("main", { style: { minHeight: "100vh", backgroundColor: "#fff", fontFamily: font }, children: [_jsxs("section", { className: "chisel-hero-img", style: { position: "relative", width: "100%", overflow: "hidden" }, children: [imageUrl ? (_jsx("img", { src: imageUrl, alt: project.title, style: { position: "absolute", inset: 0, width: "100%", height: "100%", objectFit: "cover", display: "block" } })) : (_jsx("div", { style: { position: "absolute", inset: 0, backgroundColor: "#27272a" } })), _jsx("div", { style: { position: "absolute", inset: 0, background: "linear-gradient(to right, rgba(0,0,0,0.82) 0%, rgba(0,0,0,0.55) 45%, rgba(0,0,0,0.15) 100%)" } }), _jsx("div", { style: { position: "absolute", inset: 0, background: "linear-gradient(to top, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0) 60%)" } }), _jsxs("div", { style: { position: "absolute", bottom: 0, left: 0, right: 0, padding: "0 clamp(1.5rem, 5vw, 5rem) clamp(2rem, 4vw, 3.5rem)", maxWidth: "760px", boxSizing: "border-box" }, children: [badgeValue && (_jsx("span", { style: {
|
|
152
|
+
display: "inline-block",
|
|
153
|
+
backgroundColor: "#f18a00",
|
|
154
|
+
color: "#fff",
|
|
155
|
+
fontSize: "11px",
|
|
156
|
+
fontWeight: 700,
|
|
157
|
+
textTransform: "uppercase",
|
|
158
|
+
letterSpacing: "0.1em",
|
|
159
|
+
padding: "5px 14px",
|
|
160
|
+
borderRadius: "4px",
|
|
161
|
+
marginBottom: "16px",
|
|
162
|
+
}, children: badgeValue })), _jsx("h1", { style: { color: "#fff", fontWeight: 700, fontSize: "clamp(28px, 4.5vw, 56px)", lineHeight: 1.05, margin: "0 0 16px 0", fontFamily: font, letterSpacing: "-0.02em" }, children: project.title }), locationString && (_jsxs("p", { style: { display: "flex", alignItems: "center", gap: "6px", color: "rgba(255,255,255,0.72)", fontSize: "15px", margin: 0 }, children: [_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { flexShrink: 0 }, children: [_jsx("path", { d: "M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" }), _jsx("circle", { cx: "12", cy: "10", r: "3" })] }), locationString] }))] })] }), metadataStats.length > 0 && (_jsxs("section", { style: { borderBottom: "1px solid #e4e4e7", backgroundColor: "#fff" }, children: [_jsx("style", { children: `
|
|
137
163
|
.chisel-stats-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0; }
|
|
138
164
|
@media (min-width: 640px) { .chisel-stats-grid { grid-template-columns: repeat(3, 1fr); } }
|
|
139
165
|
@media (min-width: 1024px) { .chisel-stats-grid { grid-template-columns: repeat(${Math.min(metadataStats.length, 6)}, 1fr); } }
|
|
@@ -141,7 +167,7 @@ export async function ProjectDetail({ slug, clientSlug, apiBase, backPath = "/pr
|
|
|
141
167
|
.chisel-stat-item:last-child { border-right: none; }
|
|
142
168
|
.chisel-gallery-placeholder { display: grid; grid-template-columns: 1fr; gap: 12px; }
|
|
143
169
|
@media (min-width: 640px) { .chisel-gallery-placeholder { grid-template-columns: repeat(3, 1fr); } }
|
|
144
|
-
.chisel-hero-img { height:
|
|
170
|
+
.chisel-hero-img { height: 56vw; min-height: 340px; max-height: 680px; }
|
|
145
171
|
.chisel-overview-grid { display: grid; grid-template-columns: 1fr; gap: 2.5rem; }
|
|
146
172
|
@media (min-width: 1024px) { .chisel-overview-grid { grid-template-columns: 1fr 300px; gap: 4rem; align-items: start; } }
|
|
147
173
|
` }), _jsx("div", { style: { maxWidth: "1280px", margin: "0 auto", boxSizing: "border-box", padding: "0 0 0 0" }, className: "chisel-stats-grid", children: metadataStats.map(({ label, value }) => (_jsxs("div", { className: "chisel-stat-item", children: [_jsx("p", { style: { fontSize: "10px", fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.12em", color: "#a1a1aa", margin: "0 0 8px 0" }, children: label }), _jsx("p", { style: { color: "#18181b", fontWeight: 600, fontSize: "15px", margin: 0, lineHeight: 1.4 }, children: value })] }, label))) })] })), _jsxs("article", { style: { maxWidth: "1280px", margin: "0 auto", padding: "3rem 1.5rem", boxSizing: "border-box" }, children: [(project.blurb || project.description || specFields.length > 0) && (_jsxs("section", { style: { marginBottom: "3rem" }, children: [_jsx("div", { style: { borderBottom: "1px solid #e4e4e7", marginBottom: "2rem", paddingBottom: "1rem", display: "flex", alignItems: "baseline", justifyContent: "space-between" }, children: _jsx("h2", { style: { color: "#18181b", fontWeight: 700, fontSize: "20px", margin: 0, letterSpacing: "-0.01em", fontFamily: font }, children: "Project Overview" }) }), _jsxs("div", { className: "chisel-overview-grid", children: [_jsxs("div", { children: [project.blurb && (_jsx("p", { style: { color: "#3f3f46", fontSize: "16px", fontWeight: 400, lineHeight: 1.85, margin: "0 0 20px 0" }, children: project.blurb })), project.description && project.description !== project.blurb && (_jsx("p", { style: { color: "#3f3f46", fontSize: "16px", fontWeight: 400, lineHeight: 1.85, margin: 0 }, children: project.description }))] }), specFields.length > 0 && (_jsx("aside", { style: { borderLeft: "3px solid #f18a00", paddingLeft: "2rem" }, children: _jsx("div", { style: { display: "flex", flexDirection: "column", gap: "2rem" }, children: specFields.map((field) => {
|
|
@@ -173,7 +199,7 @@ export async function ProjectDetail({ slug, clientSlug, apiBase, backPath = "/pr
|
|
|
173
199
|
borderRadius: "2px",
|
|
174
200
|
lineHeight: 1.5,
|
|
175
201
|
}, children: val }, val))) })] }, field.key));
|
|
176
|
-
}) }) }))] })] })), _jsxs("section", { children: [_jsx("div", { style: { borderBottom: "1px solid #e4e4e7", marginBottom: "2rem", paddingBottom: "1rem" }, children: _jsx("h2", { style: { color: "#18181b", fontWeight: 700, fontSize: "20px", margin: 0, letterSpacing: "-0.01em", fontFamily: font }, children: "Project Gallery" }) }), galleryImages.length > 0 ? (_jsx(GalleryCarousel, { images: galleryImages, projectTitle: project.title })) : (
|
|
202
|
+
}) }) }))] })] })), _jsxs("section", { children: [_jsx("div", { style: { borderBottom: "1px solid #e4e4e7", marginBottom: "2rem", paddingBottom: "1rem" }, children: _jsx("h2", { style: { color: "#18181b", fontWeight: 700, fontSize: "20px", margin: 0, letterSpacing: "-0.01em", fontFamily: font }, children: "Project Gallery" }) }), galleryImages.length > 0 ? (_jsx(GalleryCarousel, { images: galleryImages, projectTitle: project.title, schema: schema })) : (
|
|
177
203
|
/* Placeholder */
|
|
178
204
|
_jsx("div", { className: "chisel-gallery-placeholder", children: [0, 1, 2].map((i) => (_jsxs("div", { style: { aspectRatio: "16/9", borderRadius: "4px", backgroundColor: "#f9f9f9", border: "1px solid #e4e4e7", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "8px" }, children: [_jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#a1a1aa", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909M3.75 19.5h16.5" }) }), _jsx("p", { style: { color: "#a1a1aa", fontSize: "12px", margin: 0 }, children: "Photos coming soon" })] }, i))) }))] })] })] }));
|
|
179
205
|
}
|