@asteby/storefront-react 0.2.0 → 0.4.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.
@@ -0,0 +1,567 @@
1
+ 'use strict';
2
+
3
+ var mockup = require('@asteby/storefront/mockup');
4
+ var react = require('react');
5
+ require('@asteby/storefront');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+
8
+ // src/mockups/product-image.tsx
9
+ var DEFAULT_IMAGE_WIDTHS = [320, 480, 640, 768, 1024, 1280, 1600];
10
+ var defaultImageLoader = (src, width, height) => {
11
+ try {
12
+ const isAbsolute = /^[a-z][a-z0-9+.-]*:/i.test(src);
13
+ const url = new URL(src, "http://relative.invalid");
14
+ url.searchParams.set("width", String(width));
15
+ if (height) url.searchParams.set("height", String(height));
16
+ return isAbsolute ? url.toString() : url.pathname + url.search + url.hash;
17
+ } catch {
18
+ return src;
19
+ }
20
+ };
21
+ function parseRatio(ratio) {
22
+ if (!ratio) return null;
23
+ const [w, h] = ratio.split(/[/:]/).map((n) => Number.parseFloat(n));
24
+ return w && h ? w / h : null;
25
+ }
26
+ function Image({
27
+ data,
28
+ sizes = "100vw",
29
+ widths = DEFAULT_IMAGE_WIDTHS,
30
+ loader = defaultImageLoader,
31
+ priority = false,
32
+ aspectRatio,
33
+ alt,
34
+ loading,
35
+ decoding,
36
+ ...rest
37
+ }) {
38
+ const ratio = parseRatio(aspectRatio) ?? (data.width && data.height ? data.width / data.height : null);
39
+ let candidates = [...widths].sort((a, b) => a - b);
40
+ if (data.width) {
41
+ const capped = candidates.filter((w) => w <= data.width);
42
+ candidates = capped.length ? capped : [data.width];
43
+ }
44
+ const srcSet = loader ? candidates.map((w) => `${loader(data.url, w, ratio ? Math.round(w / ratio) : void 0)} ${w}w`).join(", ") : void 0;
45
+ const width = data.width ?? candidates[candidates.length - 1];
46
+ const height = data.height ?? (ratio && width ? Math.round(width / ratio) : void 0);
47
+ return /* @__PURE__ */ jsxRuntime.jsx(
48
+ "img",
49
+ {
50
+ src: data.url,
51
+ alt: alt ?? data.alt ?? "",
52
+ ...srcSet ? { srcSet, sizes } : {},
53
+ ...width ? { width } : {},
54
+ ...height ? { height } : {},
55
+ loading: loading ?? (priority ? "eager" : "lazy"),
56
+ decoding: decoding ?? (priority ? "auto" : "async"),
57
+ ...priority ? { fetchPriority: "high" } : {},
58
+ ...rest
59
+ }
60
+ );
61
+ }
62
+ var MOCKUP_SIZE = 400;
63
+ var MOCKUP_TOKENS = {
64
+ bg: "var(--mockup-bg, var(--color-muted, light-dark(#f4f4f5, #27272a)))",
65
+ surface: "var(--mockup-surface, light-dark(#3f3f46, #0a0a0a))",
66
+ "surface-ink": "var(--mockup-surface-ink, light-dark(#e4e4e7, #a1a1aa))",
67
+ label: "var(--mockup-label, var(--color-background, light-dark(#ffffff, #18181b)))",
68
+ "label-ink": "var(--mockup-label-ink, var(--color-foreground, light-dark(#18181b, #fafafa)))",
69
+ ink: "var(--mockup-ink, var(--color-foreground, light-dark(#18181b, #fafafa)))",
70
+ muted: "var(--mockup-muted, var(--color-muted-foreground, light-dark(#71717a, #a1a1aa)))",
71
+ accent: "var(--mockup-accent, var(--color-primary, light-dark(#52525b, #d4d4d8)))",
72
+ "accent-ink": "var(--mockup-accent-ink, var(--color-primary-foreground, light-dark(#ffffff, #18181b)))"
73
+ };
74
+ var ROOT_STYLE = {
75
+ ...Object.fromEntries(Object.entries(MOCKUP_TOKENS).map(([k, v]) => [`--mk-${k}`, v])),
76
+ fontFamily: "var(--mockup-font, var(--font-heading, var(--font-sans, ui-sans-serif, system-ui, sans-serif)))",
77
+ textTransform: "var(--mockup-text-transform, none)"
78
+ };
79
+ function tone(t, prop = "fill") {
80
+ return { [prop]: `var(--mk-${t})` };
81
+ }
82
+ var MOCKUP_MONO = "var(--mockup-font-mono, var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace))";
83
+ function wrapMockupText(text, maxChars, maxLines, wordMax = maxChars) {
84
+ if (!text || maxLines < 1) return [];
85
+ const cap = Math.max(maxChars, wordMax);
86
+ const words = text.split(" ").filter(Boolean);
87
+ const lines = [];
88
+ let current = "";
89
+ let cut = false;
90
+ for (let i = 0; i < words.length; i++) {
91
+ let word = words[i];
92
+ if (Array.from(word).length > cap) word = Array.from(word).slice(0, cap - 1).join("") + "\u2026";
93
+ const next = current ? `${current} ${word}` : word;
94
+ if (Array.from(next).length <= maxChars || !current) {
95
+ current = next;
96
+ continue;
97
+ }
98
+ lines.push(current);
99
+ current = word;
100
+ if (lines.length === maxLines) {
101
+ cut = true;
102
+ current = "";
103
+ break;
104
+ }
105
+ }
106
+ if (current) lines.push(current);
107
+ if (cut || lines.length > maxLines) {
108
+ const kept = lines.slice(0, maxLines);
109
+ const last = Array.from(kept[maxLines - 1] ?? "");
110
+ const limit = Math.max(maxChars, Math.min(cap, last.length));
111
+ kept[maxLines - 1] = (last.length >= limit ? last.slice(0, limit - 1).join("") : last.join("")).replace(/[\s.,;:…-]+$/, "") + "\u2026";
112
+ return kept;
113
+ }
114
+ return lines;
115
+ }
116
+ function mockupWordMax(width, min, ratio = 0.62) {
117
+ return Math.max(1, Math.floor(width / (min * ratio)));
118
+ }
119
+ function fitMockupFontSize(lines, width, max, min = 10, ratio = 0.62) {
120
+ const longest = Math.max(1, ...lines.map((l) => Array.from(l).length));
121
+ return Math.round(Math.max(min, Math.min(max, width / (longest * ratio))));
122
+ }
123
+ function MockupText({ lines, x, y, size, lineHeight = 1.12, tone: t = "ink", weight = 700, anchor = "middle", mono, style, ...rest }) {
124
+ if (!lines.length) return null;
125
+ return /* @__PURE__ */ jsxRuntime.jsx(
126
+ "text",
127
+ {
128
+ x,
129
+ y,
130
+ fontSize: size,
131
+ fontWeight: weight,
132
+ textAnchor: anchor,
133
+ style: { ...tone(t), ...mono ? { fontFamily: MOCKUP_MONO } : null, ...style },
134
+ ...rest,
135
+ children: lines.length === 1 ? lines[0] : lines.map((line, i) => /* @__PURE__ */ jsxRuntime.jsx("tspan", { x, dy: i === 0 ? 0 : size * lineHeight, children: line }, i))
136
+ }
137
+ );
138
+ }
139
+ function MockupFrame({ label, decorative = false, kind, children, style, width = MOCKUP_SIZE, height = MOCKUP_SIZE, ...rest }) {
140
+ const a11y = decorative ? { "aria-hidden": true } : { role: "img", "aria-label": label };
141
+ return /* @__PURE__ */ jsxRuntime.jsxs(
142
+ "svg",
143
+ {
144
+ xmlns: "http://www.w3.org/2000/svg",
145
+ viewBox: `0 0 ${MOCKUP_SIZE} ${MOCKUP_SIZE}`,
146
+ width,
147
+ height,
148
+ focusable: "false",
149
+ "data-mockup": kind,
150
+ ...a11y,
151
+ style: { ...ROOT_STYLE, ...style },
152
+ ...rest,
153
+ children: [
154
+ decorative ? null : /* @__PURE__ */ jsxRuntime.jsx("title", { children: label }),
155
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: MOCKUP_SIZE, height: MOCKUP_SIZE, style: tone("bg") }),
156
+ children
157
+ ]
158
+ }
159
+ );
160
+ }
161
+ function GenericMockup({ data, label, labels: _labels, ...rest }) {
162
+ const titleLines = wrapMockupText(data.title, 28, 2);
163
+ const titleFont = fitMockupFontSize(titleLines, 330, 22, 13);
164
+ const titleY = titleLines.length > 1 ? 340 : 352;
165
+ const eyebrow = data.category ? wrapMockupText(data.category, 30, 1) : [];
166
+ return /* @__PURE__ */ jsxRuntime.jsxs(MockupFrame, { kind: "generic", label, ...rest, children: [
167
+ /* @__PURE__ */ jsxRuntime.jsx("ellipse", { cx: 200, cy: 300, rx: 130, ry: 8, style: { ...tone("ink"), opacity: 0.08 } }),
168
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M90 82L114 64H330L306 82Z", style: tone("accent") }),
169
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M306 82L330 64V280L306 298Z", style: tone("surface") }),
170
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M306 82L330 64V280L306 298Z", style: { fill: "#000", opacity: 0.22 } }),
171
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: 90, y: 82, width: 216, height: 216, style: tone("surface") }),
172
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: 186, y: 82, width: 24, height: 46, style: { ...tone("accent"), opacity: 0.9 } }),
173
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: 198, cy: 186, r: 50, style: tone("label") }),
174
+ data.monogram ? /* @__PURE__ */ jsxRuntime.jsx(MockupText, { lines: [data.monogram], x: 198, y: 202, size: 44, weight: 900, tone: "label-ink", letterSpacing: 1 }) : null,
175
+ data.brand ? /* @__PURE__ */ jsxRuntime.jsx(MockupText, { lines: wrapMockupText(data.brand, 20, 1), x: 198, y: 272, size: 13, weight: 700, tone: "surface-ink", letterSpacing: 2 }) : null,
176
+ eyebrow.length > 0 && titleLines.length < 2 ? /* @__PURE__ */ jsxRuntime.jsx(MockupText, { lines: eyebrow, x: 200, y: 326, size: 12, weight: 600, tone: "muted", letterSpacing: 1 }) : null,
177
+ /* @__PURE__ */ jsxRuntime.jsx(MockupText, { lines: titleLines, x: 200, y: titleY, size: titleFont, lineHeight: 1.2, weight: 700 })
178
+ ] });
179
+ }
180
+
181
+ // src/mockups/labels.ts
182
+ var DEFAULT_MOCKUP_LABELS = {
183
+ illustration: "Ilustraci\xF3n de referencia",
184
+ size: "medida",
185
+ partNumber: "N.\xBA de parte",
186
+ oem: "OEM",
187
+ fits: "Compatible con",
188
+ flavor: "sabor",
189
+ servings: "porciones",
190
+ lightTruck: "LT",
191
+ runFlat: "Run flat"
192
+ };
193
+ var has = (haystack, needle) => haystack.toLowerCase().includes(needle.toLowerCase());
194
+ function describeMockup(data, labels = DEFAULT_MOCKUP_LABELS) {
195
+ const title = data.title ?? "";
196
+ const parts = [title];
197
+ switch (data.kind) {
198
+ case "tire":
199
+ if (data.size && !has(title, data.size)) parts.push(`${labels.size} ${data.size}`);
200
+ if (data.service && !has(title, data.service)) parts.push(data.service);
201
+ break;
202
+ case "part":
203
+ if (data.partNumber && !has(title, data.partNumber)) parts.push(`${labels.partNumber} ${data.partNumber}`);
204
+ break;
205
+ case "supplement":
206
+ if (data.flavor && !has(title, data.flavor)) parts.push(`${labels.flavor} ${data.flavor}`);
207
+ if (data.presentation && !has(title, data.presentation)) parts.push(data.presentation);
208
+ break;
209
+ }
210
+ const detail = parts.filter((p) => !!p).join(", ");
211
+ return detail ? `${labels.illustration}: ${detail}` : labels.illustration;
212
+ }
213
+ var X = 48;
214
+ var W = 304;
215
+ var PAD = 24;
216
+ var INNER = W - PAD * 2;
217
+ var SECONDARY = { opacity: 0.68 };
218
+ function PartMockup({ data, label, labels, ...rest }) {
219
+ const number = data.partNumber ?? data.oemNumber;
220
+ const numberCaption = data.partNumber ? labels.partNumber : labels.oem;
221
+ const bandLines = number ? [number] : wrapMockupText(data.title, 22, 2);
222
+ const bandFont = number ? fitMockupFontSize(bandLines, INNER, 34, 14, 0.62) : fitMockupFontSize(bandLines, INNER, 24, 14);
223
+ const header = data.brand ?? data.category;
224
+ const headerAside = data.brand ? data.position ?? data.category : data.position;
225
+ const body = [];
226
+ if (number) body.push({ lines: wrapMockupText(data.title, 26, 2), size: 17, weight: 700 });
227
+ if (data.fitment) body.push({ lines: wrapMockupText(`${labels.fits}: ${data.fitment}`, 34, 2), size: 13, secondary: true, weight: 500 });
228
+ if (data.partNumber && data.oemNumber) body.push({ lines: [`${labels.oem} ${data.oemNumber}`], size: 13, secondary: true, weight: 600, mono: true });
229
+ let y = 248;
230
+ const placed = [];
231
+ for (const b of body) {
232
+ const at = y + b.size;
233
+ const room = Math.floor((330 - at) / (b.size * 1.2)) + 1;
234
+ if (room < 1 || !b.lines.length) continue;
235
+ const lines = b.lines.length > room ? [...b.lines.slice(0, room - 1), b.lines[room - 1].replace(/[\s.,;:…-]*$/, "\u2026")] : b.lines;
236
+ placed.push({ ...b, lines, y: at });
237
+ y = at + (lines.length - 1) * b.size * 1.2 + 10;
238
+ }
239
+ return /* @__PURE__ */ jsxRuntime.jsxs(MockupFrame, { kind: "part", label, ...rest, children: [
240
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M78 98 C60 52 36 46 14 30", fill: "none", strokeWidth: 2, strokeLinecap: "round", style: tone("muted", "stroke") }),
241
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: X, y: 66, width: W, height: 276, rx: 18, style: tone("label") }),
242
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: X + 0.75, y: 66.75, width: W - 1.5, height: 274.5, rx: 17.25, fill: "none", strokeWidth: 1.5, style: { ...tone("muted", "stroke"), opacity: 0.3 } }),
243
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: 80, cy: 98, r: 9, style: tone("bg") }),
244
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: 80, cy: 98, r: 9, fill: "none", strokeWidth: 1.5, style: { ...tone("muted", "stroke"), opacity: 0.5 } }),
245
+ header ? /* @__PURE__ */ jsxRuntime.jsx(MockupText, { lines: wrapMockupText(header, headerAside ? 14 : 22, 1), x: 104, y: 104, size: 16, weight: 800, tone: "label-ink", anchor: "start", letterSpacing: 0.5 }) : null,
246
+ headerAside ? /* @__PURE__ */ jsxRuntime.jsx(MockupText, { lines: wrapMockupText(headerAside, header ? 14 : 22, 1), x: X + W - PAD, y: 104, size: 12, weight: 600, tone: "label-ink", anchor: "end", style: SECONDARY }) : null,
247
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: X, y: 128, width: W, height: 92, style: tone("accent") }),
248
+ number ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
249
+ /* @__PURE__ */ jsxRuntime.jsx(MockupText, { lines: [numberCaption], x: X + PAD, y: 154, size: 12, weight: 600, tone: "accent-ink", anchor: "start", letterSpacing: 1, style: { opacity: 0.85 } }),
250
+ /* @__PURE__ */ jsxRuntime.jsx(MockupText, { lines: bandLines, x: X + PAD, y: 198, size: bandFont, weight: 800, tone: "accent-ink", anchor: "start", mono: true })
251
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(MockupText, { lines: bandLines, x: X + PAD, y: bandLines.length > 1 ? 168 : 182, size: bandFont, weight: 800, tone: "accent-ink", anchor: "start" }),
252
+ placed.map((b, i) => /* @__PURE__ */ jsxRuntime.jsx(MockupText, { lines: b.lines, x: X + PAD, y: b.y, size: b.size, lineHeight: 1.2, weight: b.weight, tone: "label-ink", anchor: "start", mono: b.mono, style: b.secondary ? SECONDARY : void 0 }, i))
253
+ ] });
254
+ }
255
+ var SHAPES = {
256
+ tub: {
257
+ label: { x: 131, y: 128, w: 138, h: 200 },
258
+ draw: () => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
259
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: 120, y: 44, width: 160, height: 54, rx: 8, style: tone("surface") }),
260
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: 128, y1: 71, x2: 272, y2: 71, strokeWidth: 42, strokeDasharray: "1.5 7.5", style: { ...tone("surface-ink", "stroke"), opacity: 0.18 } }),
261
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: 117, y: 95, width: 166, height: 9, rx: 2, style: tone("accent") }),
262
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M122 104H278L283 344Q283 362 265 362H135Q117 362 117 344Z", style: tone("surface") })
263
+ ] })
264
+ },
265
+ bottle: {
266
+ label: { x: 131, y: 148, w: 138, h: 184 },
267
+ draw: () => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
268
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: 158, y: 46, width: 84, height: 40, rx: 7, style: tone("surface") }),
269
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: 164, y1: 66, x2: 236, y2: 66, strokeWidth: 30, strokeDasharray: "1.5 6.5", style: { ...tone("surface-ink", "stroke"), opacity: 0.18 } }),
270
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: 168, y: 84, width: 64, height: 12, style: tone("accent") }),
271
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M124 116Q124 96 148 96H252Q276 96 276 116L281 344Q281 362 263 362H137Q119 362 119 344Z", style: tone("surface") })
272
+ ] })
273
+ },
274
+ bag: {
275
+ label: { x: 133, y: 118, w: 134, h: 196 },
276
+ draw: () => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
277
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M118 64Q200 50 282 64L297 344Q200 360 103 344Z", style: tone("surface") }),
278
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M118 64Q200 50 282 64L280 84Q200 70 120 84Z", style: tone("accent") }),
279
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M124 92Q200 79 276 92", fill: "none", strokeWidth: 2, strokeDasharray: "4 4", style: { ...tone("surface-ink", "stroke"), opacity: 0.4 } })
280
+ ] })
281
+ }
282
+ };
283
+ function SupplementMockup({ data, label, labels, ...rest }) {
284
+ const shape = SHAPES[data.shape] ?? SHAPES.tub;
285
+ const box = shape.label;
286
+ const cx = box.x + box.w / 2;
287
+ const inner = box.w - 20;
288
+ const amount = data.presentation ?? (data.servings ? `${data.servings} ${labels.servings}` : null);
289
+ const nameLines = wrapMockupText(data.title, 12, 3, mockupWordMax(inner, 12, 0.64));
290
+ const nameFont = fitMockupFontSize(nameLines, inner, 26, 12, 0.64);
291
+ const flavor = data.flavor ? wrapMockupText(data.flavor, 18, 1)[0] : null;
292
+ const flavorW = flavor ? Math.min(inner, Array.from(flavor).length * 6.6 + 20) : 0;
293
+ const amountText = amount ? wrapMockupText(amount, 16, 1)[0] : null;
294
+ const amountW = amountText ? Math.min(inner, Array.from(amountText).length * 7.4 + 24) : 0;
295
+ const top = box.y + (data.brand ? 46 : 30);
296
+ const nameY = top + nameFont * 0.8;
297
+ const nameBottom = nameY + (nameLines.length - 1) * nameFont * 1.08;
298
+ const flavorY = Math.max(nameBottom + 14, box.y + box.h - 82);
299
+ return /* @__PURE__ */ jsxRuntime.jsxs(MockupFrame, { kind: "supplement", label, ...rest, children: [
300
+ /* @__PURE__ */ jsxRuntime.jsx("ellipse", { cx: 200, cy: 364, rx: 110, ry: 8, style: { ...tone("ink"), opacity: 0.1 } }),
301
+ shape.draw(),
302
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: box.x, y: box.y, width: box.w, height: box.h, rx: 6, style: tone("label") }),
303
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: box.x, y: box.y, width: box.w, height: 6, rx: 3, style: tone("accent") }),
304
+ data.brand ? /* @__PURE__ */ jsxRuntime.jsx(MockupText, { lines: wrapMockupText(data.brand, 16, 1), x: cx, y: box.y + 28, size: 12, weight: 700, tone: "label-ink", letterSpacing: 1.5, style: { opacity: 0.68 } }) : null,
305
+ /* @__PURE__ */ jsxRuntime.jsx(MockupText, { lines: nameLines, x: cx, y: nameY, size: nameFont, lineHeight: 1.08, weight: 900, tone: "label-ink" }),
306
+ flavor ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
307
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: cx - flavorW / 2, y: flavorY, width: flavorW, height: 22, rx: 11, style: tone("accent") }),
308
+ /* @__PURE__ */ jsxRuntime.jsx(MockupText, { lines: [flavor], x: cx, y: flavorY + 15, size: 11.5, weight: 700, tone: "accent-ink" })
309
+ ] }) : null,
310
+ amountText ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
311
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: cx - amountW / 2, y: box.y + box.h - 40, width: amountW, height: 26, rx: 13, style: tone("surface") }),
312
+ /* @__PURE__ */ jsxRuntime.jsx(MockupText, { lines: [amountText], x: cx, y: box.y + box.h - 22.5, size: 12.5, weight: 700, tone: "surface-ink", letterSpacing: 1 })
313
+ ] }) : null
314
+ ] });
315
+ }
316
+ var CX = 200;
317
+ var CY = 146;
318
+ function TireMockup({ data, label, labels, ...rest }) {
319
+ const split = data.width && data.aspect && data.rim;
320
+ const headline = split ? `${data.width}/${data.aspect} R${data.rim}` : data.size;
321
+ const sizeFont = headline ? fitMockupFontSize([headline], 330, 46, 22, 0.6) : 0;
322
+ const titleLines = headline ? [] : wrapMockupText(data.model ?? data.title, 24, 2);
323
+ const titleFont = fitMockupFontSize(titleLines, 320, 26, 14);
324
+ const extras = [data.lightTruck && !(headline ?? "").toUpperCase().startsWith(labels.lightTruck.toUpperCase()) ? labels.lightTruck : null, data.runFlat ? labels.runFlat : null].filter(Boolean).join(" \xB7 ");
325
+ const footer = [headline ? data.model : null, extras || null].filter(Boolean).join(" \xB7 ");
326
+ return /* @__PURE__ */ jsxRuntime.jsxs(MockupFrame, { kind: "tire", label, ...rest, children: [
327
+ /* @__PURE__ */ jsxRuntime.jsx("ellipse", { cx: CX, cy: CY + 128, rx: 120, ry: 8, style: { ...tone("ink"), opacity: 0.08 } }),
328
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: CX, cy: CY, r: 102, fill: "none", strokeWidth: 52, style: tone("surface", "stroke") }),
329
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: CX, cy: CY, r: 124, fill: "none", strokeWidth: 8, strokeDasharray: "8 9", style: tone("bg", "stroke") }),
330
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: CX, cy: CY, r: 111, fill: "none", strokeWidth: 1.5, strokeDasharray: "3 5", style: { ...tone("surface-ink", "stroke"), opacity: 0.35 } }),
331
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: CX, cy: CY, r: 86, fill: "none", strokeWidth: 1.5, style: { ...tone("surface-ink", "stroke"), opacity: 0.25 } }),
332
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: CX, cy: CY, r: 74, style: tone("muted") }),
333
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: CX, cy: CY, r: 45, fill: "none", strokeWidth: 36, strokeDasharray: "36 20.5", style: tone("surface", "stroke") }),
334
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: CX, cy: CY, r: 20, style: tone("label") }),
335
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: CX, cy: CY, r: 11, fill: "none", strokeWidth: 4, strokeDasharray: "2.5 11.3", style: tone("muted", "stroke") }),
336
+ data.brand ? /* @__PURE__ */ jsxRuntime.jsx(MockupText, { lines: [data.brand], x: CX, y: 302, size: 15, weight: 700, tone: "muted", letterSpacing: 2 }) : null,
337
+ headline ? /* @__PURE__ */ jsxRuntime.jsx("text", { x: CX, y: 346, fontSize: sizeFont, fontWeight: 800, textAnchor: "middle", style: tone("ink"), children: split ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
338
+ `${data.width}/${data.aspect} `,
339
+ /* @__PURE__ */ jsxRuntime.jsx("tspan", { style: tone("accent"), children: `R${data.rim}` })
340
+ ] }) : headline }) : /* @__PURE__ */ jsxRuntime.jsx(MockupText, { lines: titleLines, x: CX, y: titleLines.length > 1 ? 330 : 342, size: titleFont, weight: 700 }),
341
+ footer || data.service ? /* @__PURE__ */ jsxRuntime.jsxs("text", { x: CX, y: 380, fontSize: 15, fontWeight: 600, textAnchor: "middle", style: tone("muted"), children: [
342
+ data.service ? /* @__PURE__ */ jsxRuntime.jsx("tspan", { fontWeight: 800, style: tone("accent"), children: data.service }) : null,
343
+ data.service && footer ? " \xB7 " : null,
344
+ wrapMockupText(footer, data.service ? 34 : 40, 1)[0]
345
+ ] }) : null
346
+ ] });
347
+ }
348
+ var KIND_PATTERN = /^[a-z][a-z0-9_-]{0,31}$/;
349
+ function normalize(kind, input) {
350
+ const r = typeof input === "function" ? { component: input } : input;
351
+ if (!r || typeof r.component !== "function") throw new TypeError(`Mockup "${kind}": component is required`);
352
+ return {
353
+ component: r.component,
354
+ fromProduct: r.fromProduct ?? ((product, variant) => mockup.mockupDataFromProduct(kind, product, variant)),
355
+ describe: r.describe ?? ((data, labels) => describeMockup(data, labels))
356
+ };
357
+ }
358
+ function createMockupRegistry(initial = {}) {
359
+ const map = /* @__PURE__ */ new Map();
360
+ const registry = {
361
+ register(kind, renderer) {
362
+ if (!KIND_PATTERN.test(kind)) throw new TypeError(`Invalid mockup kind "${kind}" (use a-z, 0-9, - and _)`);
363
+ map.set(kind, normalize(kind, renderer));
364
+ return registry;
365
+ },
366
+ get: (kind) => map.get(kind),
367
+ has: (kind) => map.has(kind),
368
+ kinds: () => [...map.keys()],
369
+ extend(overrides = {}) {
370
+ const next = createMockupRegistry();
371
+ for (const [k, r] of map) next.register(k, r);
372
+ for (const [k, r] of Object.entries(overrides)) if (r) next.register(k, r);
373
+ return next;
374
+ }
375
+ };
376
+ for (const [k, r] of Object.entries(initial)) if (r) registry.register(k, r);
377
+ return registry;
378
+ }
379
+ var BUILTIN_MOCKUPS = {
380
+ tire: TireMockup,
381
+ part: PartMockup,
382
+ supplement: SupplementMockup,
383
+ generic: GenericMockup
384
+ };
385
+ var defaultMockupRegistry = createMockupRegistry(BUILTIN_MOCKUPS);
386
+ function registerMockup(kind, renderer) {
387
+ return defaultMockupRegistry.register(kind, renderer);
388
+ }
389
+ var MockupContext = react.createContext(null);
390
+ function MockupProvider({ registry, resolveKind, labels, children }) {
391
+ const parent = react.useContext(MockupContext);
392
+ const value = {
393
+ registry: registry ?? parent?.registry ?? defaultMockupRegistry,
394
+ resolveKind: resolveKind ?? parent?.resolveKind,
395
+ labels: { ...parent?.labels ?? DEFAULT_MOCKUP_LABELS, ...labels }
396
+ };
397
+ return /* @__PURE__ */ jsxRuntime.jsx(MockupContext.Provider, { value, children });
398
+ }
399
+ function useMockupConfig() {
400
+ return react.useContext(MockupContext) ?? { registry: defaultMockupRegistry, labels: DEFAULT_MOCKUP_LABELS };
401
+ }
402
+ function ProductMockup({ kind, data, label, labels: labelOverrides, registry: registryProp, ...svg }) {
403
+ const config = useMockupConfig();
404
+ const registry = registryProp ?? config.registry;
405
+ const renderer = registry.get(kind) ?? registry.get("generic");
406
+ if (!renderer) return null;
407
+ const labels = labelOverrides ? { ...config.labels, ...labelOverrides } : config.labels;
408
+ const name = label ? `${labels.illustration}: ${label}` : renderer.describe(data, labels);
409
+ const Component = renderer.component;
410
+ return /* @__PURE__ */ jsxRuntime.jsx(Component, { data, label: name, labels, ...svg });
411
+ }
412
+ function ProductImage({
413
+ product,
414
+ variant,
415
+ image,
416
+ kind: kindProp,
417
+ resolveKind: resolveProp,
418
+ registry: registryProp,
419
+ labels: labelOverrides,
420
+ alt,
421
+ className,
422
+ style,
423
+ id,
424
+ ...imageProps
425
+ }) {
426
+ const config = useMockupConfig();
427
+ const img = image !== void 0 ? image : variant?.image ?? product.featured_image ?? product.images?.[0] ?? null;
428
+ if (img?.url) {
429
+ return /* @__PURE__ */ jsxRuntime.jsx(Image, { data: img, alt: alt ?? img.alt ?? product.title, className, style, id, ...imageProps });
430
+ }
431
+ const registry = registryProp ?? config.registry;
432
+ let kind = kindProp;
433
+ if (!kind) {
434
+ const guess = mockup.resolveMockupKind(product, { kinds: registry.kinds() });
435
+ kind = (resolveProp ?? config.resolveKind)?.(product, guess) ?? guess;
436
+ }
437
+ const renderer = registry.get(kind) ?? registry.get("generic");
438
+ if (!renderer) return null;
439
+ const labels = labelOverrides ? { ...config.labels, ...labelOverrides } : config.labels;
440
+ const data = renderer.fromProduct(product, variant);
441
+ const decorative = alt === "";
442
+ const label = alt ? `${labels.illustration}: ${alt}` : renderer.describe(data, labels);
443
+ const Component = renderer.component;
444
+ return /* @__PURE__ */ jsxRuntime.jsx(
445
+ Component,
446
+ {
447
+ data,
448
+ label,
449
+ labels,
450
+ decorative,
451
+ className,
452
+ style,
453
+ id,
454
+ "data-mockup-fallback": ""
455
+ }
456
+ );
457
+ }
458
+
459
+ // src/mockups/define.tsx
460
+ var KIND_PATTERN2 = /^[a-z][a-z0-9_-]{0,31}$/;
461
+ function defineMockup(definition) {
462
+ if (!definition || typeof definition.kind !== "string" || !KIND_PATTERN2.test(definition.kind)) {
463
+ throw new TypeError(`defineMockup: invalid kind "${String(definition?.kind)}" (use a-z, 0-9, - and _)`);
464
+ }
465
+ if (typeof definition.render !== "function") throw new TypeError(`defineMockup("${definition.kind}"): render is required`);
466
+ if (definition.match !== void 0 && typeof definition.match !== "function") throw new TypeError(`defineMockup("${definition.kind}"): match must be a function`);
467
+ return Object.freeze({ ...definition });
468
+ }
469
+ function createStoreMockups(definitions, options = {}) {
470
+ const base = options.base ?? "builtin";
471
+ const registry = base === "builtin" ? createMockupRegistry(BUILTIN_MOCKUPS) : base === "none" ? createMockupRegistry() : base.extend();
472
+ const seen = /* @__PURE__ */ new Set();
473
+ for (const d of definitions) {
474
+ const def = defineMockup(d);
475
+ if (seen.has(def.kind)) throw new TypeError(`createStoreMockups: kind "${def.kind}" is defined twice`);
476
+ seen.add(def.kind);
477
+ registry.register(def.kind, {
478
+ component: def.render,
479
+ ...def.data ? { fromProduct: def.data } : null,
480
+ ...def.describe ? { describe: def.describe } : null
481
+ });
482
+ }
483
+ const matchers = definitions.filter((d) => typeof d.match === "function");
484
+ const resolveKind = (product, guess) => {
485
+ for (const d of matchers) if (d.match(product, guess)) return d.kind;
486
+ return guess;
487
+ };
488
+ const out = { registry, resolveKind, kinds: [...seen] };
489
+ if (options.labels) out.labels = options.labels;
490
+ return out;
491
+ }
492
+
493
+ Object.defineProperty(exports, "BUILTIN_MOCKUP_KINDS", {
494
+ enumerable: true,
495
+ get: function () { return mockup.BUILTIN_MOCKUP_KINDS; }
496
+ });
497
+ Object.defineProperty(exports, "DEFAULT_MOCKUP_KIND_RULES", {
498
+ enumerable: true,
499
+ get: function () { return mockup.DEFAULT_MOCKUP_KIND_RULES; }
500
+ });
501
+ Object.defineProperty(exports, "MOCKUP_KIND_ATTRIBUTES", {
502
+ enumerable: true,
503
+ get: function () { return mockup.MOCKUP_KIND_ATTRIBUTES; }
504
+ });
505
+ Object.defineProperty(exports, "MOCKUP_TEXT_LIMITS", {
506
+ enumerable: true,
507
+ get: function () { return mockup.MOCKUP_TEXT_LIMITS; }
508
+ });
509
+ Object.defineProperty(exports, "cleanMockupText", {
510
+ enumerable: true,
511
+ get: function () { return mockup.cleanMockupText; }
512
+ });
513
+ Object.defineProperty(exports, "genericMockupData", {
514
+ enumerable: true,
515
+ get: function () { return mockup.genericMockupData; }
516
+ });
517
+ Object.defineProperty(exports, "mockupDataFromProduct", {
518
+ enumerable: true,
519
+ get: function () { return mockup.mockupDataFromProduct; }
520
+ });
521
+ Object.defineProperty(exports, "mockupMonogram", {
522
+ enumerable: true,
523
+ get: function () { return mockup.mockupMonogram; }
524
+ });
525
+ Object.defineProperty(exports, "partMockupData", {
526
+ enumerable: true,
527
+ get: function () { return mockup.partMockupData; }
528
+ });
529
+ Object.defineProperty(exports, "resolveMockupKind", {
530
+ enumerable: true,
531
+ get: function () { return mockup.resolveMockupKind; }
532
+ });
533
+ Object.defineProperty(exports, "supplementMockupData", {
534
+ enumerable: true,
535
+ get: function () { return mockup.supplementMockupData; }
536
+ });
537
+ Object.defineProperty(exports, "tireMockupData", {
538
+ enumerable: true,
539
+ get: function () { return mockup.tireMockupData; }
540
+ });
541
+ exports.BUILTIN_MOCKUPS = BUILTIN_MOCKUPS;
542
+ exports.DEFAULT_MOCKUP_LABELS = DEFAULT_MOCKUP_LABELS;
543
+ exports.GenericMockup = GenericMockup;
544
+ exports.MOCKUP_MONO = MOCKUP_MONO;
545
+ exports.MOCKUP_SIZE = MOCKUP_SIZE;
546
+ exports.MOCKUP_TOKENS = MOCKUP_TOKENS;
547
+ exports.MockupFrame = MockupFrame;
548
+ exports.MockupProvider = MockupProvider;
549
+ exports.MockupText = MockupText;
550
+ exports.PartMockup = PartMockup;
551
+ exports.ProductImage = ProductImage;
552
+ exports.ProductMockup = ProductMockup;
553
+ exports.SupplementMockup = SupplementMockup;
554
+ exports.TireMockup = TireMockup;
555
+ exports.createMockupRegistry = createMockupRegistry;
556
+ exports.createStoreMockups = createStoreMockups;
557
+ exports.defaultMockupRegistry = defaultMockupRegistry;
558
+ exports.defineMockup = defineMockup;
559
+ exports.describeMockup = describeMockup;
560
+ exports.fitMockupFontSize = fitMockupFontSize;
561
+ exports.mockupWordMax = mockupWordMax;
562
+ exports.registerMockup = registerMockup;
563
+ exports.tone = tone;
564
+ exports.useMockupConfig = useMockupConfig;
565
+ exports.wrapMockupText = wrapMockupText;
566
+ //# sourceMappingURL=mockups.cjs.map
567
+ //# sourceMappingURL=mockups.cjs.map