@chiselandco/nexus 2.2.7 → 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.
Files changed (40) hide show
  1. package/README.md +274 -286
  2. package/dist/FilterSidebar.d.ts +20 -0
  3. package/dist/FilterSidebar.d.ts.map +1 -0
  4. package/dist/FilterSidebar.js +266 -0
  5. package/dist/FilteredPortfolio.d.ts +45 -0
  6. package/dist/FilteredPortfolio.d.ts.map +1 -0
  7. package/dist/FilteredPortfolio.js +134 -0
  8. package/dist/GalleryCarousel.d.ts +9 -2
  9. package/dist/GalleryCarousel.d.ts.map +1 -1
  10. package/dist/GalleryCarousel.js +363 -63
  11. package/dist/ProjectDetail.d.ts +3 -1
  12. package/dist/ProjectDetail.d.ts.map +1 -1
  13. package/dist/ProjectDetail.js +33 -18
  14. package/dist/ProjectMenu.d.ts +8 -3
  15. package/dist/ProjectMenu.d.ts.map +1 -1
  16. package/dist/ProjectMenu.js +12 -16
  17. package/dist/ProjectMenuClient.d.ts +4 -2
  18. package/dist/ProjectMenuClient.d.ts.map +1 -1
  19. package/dist/ProjectMenuClient.js +6 -7
  20. package/dist/ProjectPortfolio.d.ts +3 -1
  21. package/dist/ProjectPortfolio.d.ts.map +1 -1
  22. package/dist/ProjectPortfolio.js +4 -4
  23. package/dist/ProjectPortfolioClient.d.ts +3 -1
  24. package/dist/ProjectPortfolioClient.d.ts.map +1 -1
  25. package/dist/ProjectPortfolioClient.js +4 -6
  26. package/dist/SimilarProjects.d.ts +3 -1
  27. package/dist/SimilarProjects.d.ts.map +1 -1
  28. package/dist/SimilarProjects.js +11 -9
  29. package/dist/index.d.ts +4 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +2 -0
  32. package/dist/types.d.ts +2 -0
  33. package/dist/types.d.ts.map +1 -1
  34. package/package.json +3 -2
  35. package/dist/ProjectFilters.d.ts +0 -11
  36. package/dist/ProjectFilters.d.ts.map +0 -1
  37. package/dist/ProjectFilters.js +0 -49
  38. package/dist/ProjectGrid.d.ts +0 -10
  39. package/dist/ProjectGrid.d.ts.map +0 -1
  40. package/dist/ProjectGrid.js +0 -8
@@ -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
- const font = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
5
- export function GalleryCarousel({ images, projectTitle, }) {
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
- // caption may come back as `caption`, `description`, or `alt` depending on the API
10
- const active = images[current];
11
- const caption = active.caption || active.description || null;
12
- const total = images.length;
13
- function prev() {
14
- setCurrent((c) => (c - 1 + total) % total);
15
- }
16
- function next() {
17
- setCurrent((c) => (c + 1) % total);
18
- }
19
- return (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "12px", fontFamily: font }, children: [_jsxs("div", { style: { position: "relative", width: "100%", aspectRatio: "16/9", backgroundColor: "#f4f4f5", 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" } }), total > 1 && (_jsx("button", { onClick: prev, "aria-label": "Previous image", style: {
20
- position: "absolute",
21
- left: "16px",
22
- top: "50%",
23
- transform: "translateY(-50%)",
24
- width: "40px",
25
- height: "40px",
26
- borderRadius: "50%",
27
- backgroundColor: "rgba(255,255,255,0.92)",
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
- alignItems: "center",
32
- justifyContent: "center",
33
- boxShadow: "0 1px 4px rgba(0,0,0,0.18)",
34
- }, children: _jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "#18181b", strokeWidth: "2.5", 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: {
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: "center",
47
- justifyContent: "center",
48
- boxShadow: "0 1px 4px rgba(0,0,0,0.18)",
49
- }, children: _jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "#18181b", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M9 18l6-6-6-6" }) }) })), total > 1 && (_jsxs("div", { style: {
50
- position: "absolute",
51
- bottom: "14px",
52
- right: "14px",
53
- backgroundColor: "rgba(0,0,0,0.65)",
54
- color: "#fff",
55
- fontSize: "13px",
56
- fontWeight: 600,
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
- color: "#6b7280",
65
- margin: 0,
66
- padding: "0 1rem",
67
- fontFamily: font,
68
- }, children: caption })), total > 1 && (_jsx("div", { style: { display: "flex", gap: "8px", overflowX: "auto", WebkitOverflowScrolling: "touch", paddingBottom: "4px" }, children: images.map((img, i) => {
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
- width: "88px",
348
+ position: "relative",
349
+ width: "72px",
72
350
  aspectRatio: "16/9",
73
351
  padding: 0,
74
- border: i === current ? "2px solid #f18a00" : "2px solid transparent",
75
- borderRadius: "2px",
352
+ border: "none",
353
+ borderRadius: "1px",
76
354
  overflow: "hidden",
77
355
  cursor: "pointer",
78
356
  flexShrink: 0,
79
- backgroundColor: "#f4f4f5",
80
- opacity: i === current ? 1 : 0.65,
81
- transition: "opacity 0.15s, border-color 0.15s",
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
+ }
@@ -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":"AAKA,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,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;AAoED,wBAAsB,aAAa,CAAC,EAClC,IAAI,EACJ,UAAU,EACV,OAAO,EACP,QAAsB,EACtB,SAA0B,EAC1B,UAAe,EACf,OAAe,GAChB,EAAE,kBAAkB,oDAgRpB"}
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"}
@@ -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
- const fetchProjectDetail = cache(async (apiBase, clientSlug, slug, revalidate, noCache) => {
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
- // Single call /projects/{slug} returns the project AND client.custom_fields_schema
32
- // with full options embedded. No need for a separate /fields call.
33
- const projectRes = await fetch(`${apiBase}/api/v1/clients/${clientSlug}/projects/${slug}?api_key=pk_live_crmsuTIm7NNfb9uEWBCyv88F6kj2YQUR`, fetchOpts);
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, _j, _k;
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
- const galleryImages = project.image_url
64
- ? ((_e = project.media) !== null && _e !== void 0 ? _e : [])
65
- : ((_g = (_f = project.media) === null || _f === void 0 ? void 0 : _f.slice(1)) !== null && _g !== void 0 ? _g : []);
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
- ? ((_h = parseMultiValue(project.custom_field_values[badgeField.key])[0]) !== null && _h !== void 0 ? _h : null)
84
+ ? ((_f = parseMultiValue(project.custom_field_values[badgeField.key])[0]) !== null && _f !== void 0 ? _f : null)
70
85
  : null;
71
- const badgeOptMap = badgeField ? ((_j = fieldOptionsMap[badgeField.key]) !== null && _j !== void 0 ? _j : {}) : {};
72
- const badgeValue = badgeRaw ? ((_k = badgeOptMap[badgeRaw]) !== null && _k !== void 0 ? _k : badgeRaw) : null;
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;
@@ -184,7 +199,7 @@ export async function ProjectDetail({ slug, clientSlug, apiBase, backPath = "/pr
184
199
  borderRadius: "2px",
185
200
  lineHeight: 1.5,
186
201
  }, children: val }, val))) })] }, field.key));
187
- }) }) }))] })] })), _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 })) : (
188
203
  /* Placeholder */
189
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))) }))] })] })] }));
190
205
  }
@@ -4,6 +4,8 @@ export interface ProjectMenuProps {
4
4
  clientSlug: string;
5
5
  /** API base URL e.g. "https://nexus.chiselandco.com" */
6
6
  apiBase: string;
7
+ /** Client API key — pass via environment variable, never hardcode */
8
+ apiKey: string;
7
9
  /**
8
10
  * Optional menu ID to fetch a specific curated menu instead of all projects.
9
11
  * When provided, fetches from /api/v1/clients/{clientSlug}/menus/{menuId}
@@ -51,16 +53,19 @@ export interface ProjectMenuProps {
51
53
  * import { revalidateTag } from "next/cache"
52
54
  * revalidateTag("chisel-menu-my-client")
53
55
  */
54
- export declare function createMenuHandler({ clientSlug, apiBase, menuId, revalidate, }: {
56
+ export declare function createMenuHandler({ clientSlug, apiBase, apiKey, menuId, revalidate, }: {
55
57
  clientSlug: string;
56
58
  apiBase: string;
59
+ /** Client API key — pass via environment variable, never hardcode */
60
+ apiKey: string;
57
61
  /** Optional menu ID to fetch a specific curated menu instead of all projects */
58
62
  menuId?: string;
59
63
  revalidate?: number;
60
64
  }): (request: Request) => Promise<Response>;
61
- export declare function fetchProjectMenuData({ apiBase, clientSlug, menuId, revalidate, noCache, }: {
65
+ export declare function fetchProjectMenuData({ apiBase, clientSlug, apiKey, menuId, revalidate, noCache, }: {
62
66
  apiBase: string;
63
67
  clientSlug: string;
68
+ apiKey: string;
64
69
  menuId?: string;
65
70
  revalidate?: number;
66
71
  noCache?: boolean;
@@ -75,5 +80,5 @@ export declare function fetchProjectMenuData({ apiBase, clientSlug, menuId, reva
75
80
  filterFieldName: string;
76
81
  fieldOptionsMap: Record<string, Record<string, string>>;
77
82
  }>;
78
- export declare function ProjectMenu({ clientSlug, apiBase, menuId, basePath, viewAllPath, subtitle, font, maxProjects, revalidate, noCache, }: ProjectMenuProps): Promise<import("react/jsx-runtime").JSX.Element>;
83
+ export declare function ProjectMenu({ clientSlug, apiBase, apiKey, menuId, basePath, viewAllPath, subtitle, font, maxProjects, revalidate, noCache, }: ProjectMenuProps): Promise<import("react/jsx-runtime").JSX.Element>;
79
84
  //# sourceMappingURL=ProjectMenu.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectMenu.d.ts","sourceRoot":"","sources":["../src/ProjectMenu.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAKzD,MAAM,WAAW,gBAAgB;IAC/B,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAA;IAClB,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,UAAU,EACV,OAAO,EACP,MAAM,EACN,UAAkB,GACnB,EAAE;IACD,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,aACoC,OAAO,uBAmE3C;AAED,wBAAsB,oBAAoB,CAAC,EACzC,OAAO,EACP,UAAU,EACV,MAAM,EACN,UAAkB,EAClB,OAAe,GAChB,EAAE;IACD,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,GAAG,OAAO,CAAC;IACV,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,MAAM,EAAE,iBAAiB,EAAE,CAAA;IAC3B,aAAa,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC9C,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CACxD,CAAC,CAqBD;AA0DD,wBAAsB,WAAW,CAAC,EAChC,UAAU,EACV,OAAO,EACP,MAAM,EACN,QAAsB,EACtB,WAAW,EACX,QAAQ,EACR,IAA0E,EAC1E,WAAe,EACf,UAAkB,EAClB,OAAe,GAChB,EAAE,gBAAgB,oDAiClB"}
1
+ {"version":3,"file":"ProjectMenu.d.ts","sourceRoot":"","sources":["../src/ProjectMenu.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAGzD,MAAM,WAAW,gBAAgB;IAC/B,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAA;IAClB,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAA;IACf,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,UAAU,EACV,OAAO,EACP,MAAM,EACN,MAAM,EACN,UAAkB,GACnB,EAAE;IACD,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAA;IACd,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,aACoC,OAAO,uBAmE3C;AAED,wBAAsB,oBAAoB,CAAC,EACzC,OAAO,EACP,UAAU,EACV,MAAM,EACN,MAAM,EACN,UAAkB,EAClB,OAAe,GAChB,EAAE;IACD,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,GAAG,OAAO,CAAC;IACV,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,MAAM,EAAE,iBAAiB,EAAE,CAAA;IAC3B,aAAa,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC9C,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,eAAe,EAAE,MAAM,CAAA;IACvB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CACxD,CAAC,CAqBD;AAwDD,wBAAsB,WAAW,CAAC,EAChC,UAAU,EACV,OAAO,EACP,MAAM,EACN,MAAM,EACN,QAAsB,EACtB,WAAW,EACX,QAAQ,EACR,IAA0E,EAC1E,WAAe,EACf,UAAkB,EAClB,OAAe,GAChB,EAAE,gBAAgB,oDAiClB"}