@tangle-network/sandbox-ui 0.131.1 → 0.131.3

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/dist/assets.d.ts CHANGED
@@ -237,6 +237,8 @@ interface CopyPreviewProps {
237
237
  content: CopyContent;
238
238
  className?: string;
239
239
  }
240
+ export declare const COPY_PLATFORM_LABEL: Record<CopyPlatform, string>;
241
+ /** Copy as it will read: the channel and its length budget, the headline, the body and its hashtags. */
240
242
  export declare function CopyPreview({ content, className }: CopyPreviewProps): import("react").JSX.Element;
241
243
  //#endregion
242
244
  //#region src/assets/preview/email-preview.d.ts
@@ -246,7 +248,25 @@ interface EmailPreviewProps {
246
248
  previewUrl?: string;
247
249
  className?: string;
248
250
  }
249
- export declare function EmailPreview({ content, brand, previewUrl, className }: EmailPreviewProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
251
+ /**
252
+ * The colours an email body renders in. A brand whose text colour is light
253
+ * writes on a dark page; any brand colour that would not read on that page
254
+ * falls back to the page's own ink.
255
+ */
256
+ export declare function emailPalette(brand: BrandTokens): {
257
+ scheme: string;
258
+ paper: "#111218" | "#ffffff";
259
+ ink: string;
260
+ accent: string;
261
+ button: string;
262
+ buttonText: string;
263
+ quote: string;
264
+ };
265
+ /**
266
+ * An email as a reader meets it: who it is from, the subject and the inbox
267
+ * preview line, then the body on the brand's page and in its typeface.
268
+ */
269
+ export declare function EmailPreview({ content, brand, previewUrl, className }: EmailPreviewProps): import("react").JSX.Element;
250
270
  //#endregion
251
271
  //#region src/assets/preview/image-preview.d.ts
252
272
  type ImageFormat = "feed" | "story" | "carousel";
package/dist/assets.js CHANGED
@@ -118,6 +118,283 @@ function ImagePreview({ content, brand, format = "feed", className }) {
118
118
  });
119
119
  }
120
120
  //#endregion
121
+ //#region src/assets/preview/email-preview.tsx
122
+ /** sRGB relative luminance of a #rgb or #rrggbb colour; null for anything else. */
123
+ function luminance(color) {
124
+ const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color.trim())?.[1];
125
+ if (!hex) return null;
126
+ const full = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
127
+ const [r, g, b] = [
128
+ 0,
129
+ 2,
130
+ 4
131
+ ].map((i) => {
132
+ const channel = parseInt(full.slice(i, i + 2), 16) / 255;
133
+ return channel <= .03928 ? channel / 12.92 : ((channel + .055) / 1.055) ** 2.4;
134
+ });
135
+ return .2126 * r + .7152 * g + .0722 * b;
136
+ }
137
+ function contrast(a, b) {
138
+ return (Math.max(a, b) + .05) / (Math.min(a, b) + .05);
139
+ }
140
+ const PAPER = {
141
+ light: "#ffffff",
142
+ dark: "#111218"
143
+ };
144
+ const INK = {
145
+ light: "#16171d",
146
+ dark: "#f4f4f6"
147
+ };
148
+ /**
149
+ * The colours an email body renders in. A brand whose text colour is light
150
+ * writes on a dark page; any brand colour that would not read on that page
151
+ * falls back to the page's own ink.
152
+ */
153
+ function emailPalette(brand) {
154
+ const text = luminance(brand.textColor);
155
+ const scheme = text !== null && text > .5 ? "dark" : "light";
156
+ const paper = PAPER[scheme];
157
+ const paperLum = luminance(paper);
158
+ const readable = (color, minimum) => {
159
+ const lum = luminance(color);
160
+ return lum !== null && contrast(lum, paperLum) >= minimum;
161
+ };
162
+ const ink = readable(brand.textColor, 4.5) ? brand.textColor : INK[scheme];
163
+ const accent = readable(brand.primaryColor, 3) ? brand.primaryColor : ink;
164
+ const primary = luminance(brand.primaryColor);
165
+ const buttonText = primary !== null && contrast(primary, 1) < contrast(primary, 0) ? "#111111" : "#ffffff";
166
+ return {
167
+ scheme,
168
+ paper,
169
+ ink,
170
+ accent,
171
+ button: brand.primaryColor,
172
+ buttonText,
173
+ quote: brand.accentColor
174
+ };
175
+ }
176
+ function Button$1({ href, label, palette }) {
177
+ return /* @__PURE__ */ jsx("a", {
178
+ href,
179
+ onClick: (event) => event.preventDefault(),
180
+ className: "inline-block rounded-md px-5 py-2.5 text-sm font-semibold no-underline",
181
+ style: {
182
+ background: palette.button,
183
+ color: palette.buttonText
184
+ },
185
+ children: label
186
+ });
187
+ }
188
+ function Section({ section, palette }) {
189
+ const muted = {
190
+ color: palette.ink,
191
+ opacity: .72
192
+ };
193
+ switch (section.type) {
194
+ case "hero": return /* @__PURE__ */ jsxs("div", {
195
+ className: "flex flex-col items-center gap-3 py-2 text-center",
196
+ children: [
197
+ section.imageUrl && /* @__PURE__ */ jsx("img", {
198
+ src: section.imageUrl,
199
+ alt: "",
200
+ className: "w-full rounded-md object-cover"
201
+ }),
202
+ /* @__PURE__ */ jsx("h2", {
203
+ className: "text-2xl font-bold leading-tight tracking-tight",
204
+ style: { color: palette.accent },
205
+ children: section.headline
206
+ }),
207
+ section.subheadline && /* @__PURE__ */ jsx("p", {
208
+ className: "text-base leading-relaxed",
209
+ style: muted,
210
+ children: section.subheadline
211
+ }),
212
+ section.ctaLabel && /* @__PURE__ */ jsx("div", {
213
+ className: "pt-1",
214
+ children: /* @__PURE__ */ jsx(Button$1, {
215
+ href: section.ctaUrl ?? "#",
216
+ label: section.ctaLabel,
217
+ palette
218
+ })
219
+ })
220
+ ]
221
+ });
222
+ case "body": return /* @__PURE__ */ jsx("p", {
223
+ className: "text-base leading-7 whitespace-pre-wrap",
224
+ children: section.text
225
+ });
226
+ case "feature": return /* @__PURE__ */ jsxs("div", {
227
+ className: "flex items-start gap-4",
228
+ children: [section.imageUrl && /* @__PURE__ */ jsx("img", {
229
+ src: section.imageUrl,
230
+ alt: "",
231
+ className: "size-16 shrink-0 rounded-md object-cover"
232
+ }), /* @__PURE__ */ jsxs("div", {
233
+ className: "flex flex-col gap-1",
234
+ children: [/* @__PURE__ */ jsx("div", {
235
+ className: "text-base font-semibold",
236
+ children: section.headline
237
+ }), /* @__PURE__ */ jsx("div", {
238
+ className: "text-sm leading-relaxed",
239
+ style: muted,
240
+ children: section.description
241
+ })]
242
+ })]
243
+ });
244
+ case "testimonial": return /* @__PURE__ */ jsxs("blockquote", {
245
+ className: "border-l-4 pl-4",
246
+ style: { borderColor: palette.quote },
247
+ children: [/* @__PURE__ */ jsxs("p", {
248
+ className: "text-base italic leading-relaxed",
249
+ children: [
250
+ "“",
251
+ section.quote,
252
+ "”"
253
+ ]
254
+ }), /* @__PURE__ */ jsxs("footer", {
255
+ className: "mt-2 text-sm font-medium",
256
+ style: muted,
257
+ children: [section.author, section.role ? `, ${section.role}` : ""]
258
+ })]
259
+ });
260
+ case "cta": return /* @__PURE__ */ jsxs("div", {
261
+ className: "flex flex-col items-center gap-2 py-2 text-center",
262
+ children: [/* @__PURE__ */ jsx(Button$1, {
263
+ href: section.url,
264
+ label: section.label,
265
+ palette
266
+ }), section.subtext && /* @__PURE__ */ jsx("p", {
267
+ className: "text-sm",
268
+ style: muted,
269
+ children: section.subtext
270
+ })]
271
+ });
272
+ case "divider": return /* @__PURE__ */ jsx("hr", {
273
+ className: "border-0 border-t",
274
+ style: {
275
+ borderColor: palette.ink,
276
+ opacity: .15
277
+ }
278
+ });
279
+ default: return null;
280
+ }
281
+ }
282
+ /**
283
+ * An email as a reader meets it: who it is from, the subject and the inbox
284
+ * preview line, then the body on the brand's page and in its typeface.
285
+ */
286
+ function EmailPreview({ content, brand, previewUrl, className }) {
287
+ const palette = emailPalette(brand);
288
+ return /* @__PURE__ */ jsxs("article", {
289
+ className: cn("overflow-hidden rounded-lg border border-border bg-card shadow-sm", className),
290
+ children: [/* @__PURE__ */ jsxs("header", {
291
+ className: "flex flex-col gap-1 border-b border-border px-5 py-4",
292
+ children: [
293
+ /* @__PURE__ */ jsxs("div", {
294
+ className: "flex items-center gap-2 text-sm",
295
+ children: [/* @__PURE__ */ jsx("span", {
296
+ "aria-hidden": true,
297
+ className: "flex size-7 shrink-0 items-center justify-center rounded-full text-xs font-semibold",
298
+ style: {
299
+ background: palette.button,
300
+ color: palette.buttonText
301
+ },
302
+ children: brand.businessName.trim().charAt(0).toUpperCase() || "?"
303
+ }), /* @__PURE__ */ jsx("span", {
304
+ className: "font-medium",
305
+ children: brand.businessName
306
+ })]
307
+ }),
308
+ /* @__PURE__ */ jsx("h3", {
309
+ className: "text-base font-semibold leading-snug",
310
+ children: content.subject
311
+ }),
312
+ content.preheader && /* @__PURE__ */ jsx("p", {
313
+ className: "text-sm text-muted-foreground",
314
+ children: content.preheader
315
+ })
316
+ ]
317
+ }), previewUrl ? /* @__PURE__ */ jsx("iframe", {
318
+ src: previewUrl,
319
+ title: "Email preview",
320
+ sandbox: "allow-same-origin",
321
+ className: "block h-[480px] w-full bg-white"
322
+ }) : /* @__PURE__ */ jsx("div", {
323
+ className: "px-5 py-8 sm:px-8",
324
+ style: {
325
+ background: palette.paper,
326
+ color: palette.ink,
327
+ fontFamily: brand.fontFamily
328
+ },
329
+ children: /* @__PURE__ */ jsx("div", {
330
+ className: "mx-auto flex max-w-[560px] flex-col gap-5",
331
+ children: content.sections.map((section, index) => /* @__PURE__ */ jsx(Section, {
332
+ section,
333
+ palette
334
+ }, index))
335
+ })
336
+ })]
337
+ });
338
+ }
339
+ //#endregion
340
+ //#region src/assets/preview/copy-preview.tsx
341
+ const PLATFORM_LIMITS = {
342
+ instagram: 2200,
343
+ tiktok: 2200,
344
+ x: 280,
345
+ linkedin: 3e3,
346
+ sms: 160,
347
+ "email-subject": 60
348
+ };
349
+ const COPY_PLATFORM_LABEL = {
350
+ instagram: "Instagram",
351
+ tiktok: "TikTok",
352
+ x: "X",
353
+ linkedin: "LinkedIn",
354
+ sms: "SMS",
355
+ "email-subject": "Email subject"
356
+ };
357
+ /** Copy as it will read: the channel and its length budget, the headline, the body and its hashtags. */
358
+ function CopyPreview({ content, className }) {
359
+ const limit = PLATFORM_LIMITS[content.platform];
360
+ const length = content.body.length;
361
+ const over = limit !== null && length > limit;
362
+ const near = !over && limit !== null && length >= limit * .9;
363
+ return /* @__PURE__ */ jsxs("article", {
364
+ className: cn("flex flex-col gap-4 rounded-xl border border-border bg-card p-6 shadow-sm", className),
365
+ children: [
366
+ /* @__PURE__ */ jsxs("div", {
367
+ className: "flex items-center justify-between gap-3",
368
+ children: [/* @__PURE__ */ jsx(Badge, {
369
+ variant: "secondary",
370
+ className: "text-xs",
371
+ children: COPY_PLATFORM_LABEL[content.platform] ?? content.platform
372
+ }), limit !== null && /* @__PURE__ */ jsxs("span", {
373
+ className: cn("text-xs tabular-nums", over ? "font-medium text-destructive" : near ? "text-warning" : "text-muted-foreground"),
374
+ children: [
375
+ length,
376
+ " / ",
377
+ limit,
378
+ " characters"
379
+ ]
380
+ })]
381
+ }),
382
+ content.headline && /* @__PURE__ */ jsx("h3", {
383
+ className: "text-xl font-semibold leading-snug tracking-tight",
384
+ children: content.headline
385
+ }),
386
+ /* @__PURE__ */ jsx("p", {
387
+ className: "text-base leading-7 whitespace-pre-wrap [overflow-wrap:anywhere]",
388
+ children: content.body
389
+ }),
390
+ content.hashtags && content.hashtags.length > 0 && /* @__PURE__ */ jsx("div", {
391
+ className: "flex flex-wrap gap-x-2 gap-y-1 text-sm text-primary",
392
+ children: content.hashtags.map((tag) => /* @__PURE__ */ jsxs("span", { children: ["#", tag.replace(/^#/, "")] }, tag))
393
+ })
394
+ ]
395
+ });
396
+ }
397
+ //#endregion
121
398
  //#region src/assets/labels.ts
122
399
  /** Reader-facing name for each asset format. */
123
400
  const ASSET_FORMAT_LABEL = {
@@ -163,6 +440,7 @@ function videoPosterUrl(content) {
163
440
  }
164
441
  return null;
165
442
  }
443
+ const FADE_OUT = "[mask-image:linear-gradient(to_bottom,black_75%,transparent)]";
166
444
  function formatDuration(seconds) {
167
445
  const whole = Math.max(0, Math.round(seconds));
168
446
  return `${Math.floor(whole / 60)}:${String(whole % 60).padStart(2, "0")}`;
@@ -177,15 +455,46 @@ function Thumbnail({ spec }) {
177
455
  const { format, brand, content } = spec;
178
456
  if (format === "email") {
179
457
  const ec = content;
180
- return /* @__PURE__ */ jsxs("div", {
181
- className: "flex h-full flex-col gap-1 p-3 text-sm",
182
- children: [/* @__PURE__ */ jsx("div", {
183
- className: "line-clamp-3 font-medium",
184
- children: ec.subject
185
- }), /* @__PURE__ */ jsxs("div", {
186
- className: "text-xs text-muted-foreground",
187
- children: [ec.sections.length, " sections"]
188
- })]
458
+ const palette = emailPalette(brand);
459
+ const hero = ec.sections.find((section) => section.type === "hero");
460
+ const body = ec.sections.find((section) => section.type === "body");
461
+ return /* @__PURE__ */ jsx("div", {
462
+ className: "flex h-full flex-col p-3",
463
+ children: /* @__PURE__ */ jsxs("div", {
464
+ className: "flex h-full flex-col overflow-hidden rounded-md border border-border bg-card shadow-sm",
465
+ children: [/* @__PURE__ */ jsxs("div", {
466
+ className: "flex shrink-0 flex-col gap-0.5 border-b border-border px-3 py-2",
467
+ children: [
468
+ /* @__PURE__ */ jsx("div", {
469
+ className: "truncate text-xs text-muted-foreground",
470
+ children: brand.businessName
471
+ }),
472
+ /* @__PURE__ */ jsx("div", {
473
+ className: "line-clamp-2 text-sm font-semibold leading-snug",
474
+ children: ec.subject
475
+ }),
476
+ ec.preheader && /* @__PURE__ */ jsx("div", {
477
+ className: "truncate text-xs text-muted-foreground",
478
+ children: ec.preheader
479
+ })
480
+ ]
481
+ }), /* @__PURE__ */ jsxs("div", {
482
+ className: cn("flex min-h-0 flex-1 flex-col gap-1 overflow-hidden px-3 py-2", FADE_OUT),
483
+ style: {
484
+ background: palette.paper,
485
+ color: palette.ink,
486
+ fontFamily: brand.fontFamily
487
+ },
488
+ children: [hero?.type === "hero" && /* @__PURE__ */ jsx("div", {
489
+ className: "line-clamp-2 shrink-0 text-sm font-bold leading-snug",
490
+ style: { color: palette.accent },
491
+ children: hero.headline
492
+ }), body?.type === "body" && /* @__PURE__ */ jsx("div", {
493
+ className: "text-xs leading-relaxed opacity-75",
494
+ children: body.text
495
+ })]
496
+ })]
497
+ })
189
498
  });
190
499
  }
191
500
  if (format.startsWith("image:")) {
@@ -196,7 +505,7 @@ function Thumbnail({ spec }) {
196
505
  alt: "",
197
506
  loading: "lazy",
198
507
  decoding: "async",
199
- className: "h-full w-full object-contain"
508
+ className: "h-full w-full object-contain p-2"
200
509
  });
201
510
  return /* @__PURE__ */ jsx("div", {
202
511
  className: "h-full overflow-hidden",
@@ -218,7 +527,7 @@ function Thumbnail({ spec }) {
218
527
  alt: "",
219
528
  loading: "lazy",
220
529
  decoding: "async",
221
- className: "h-full w-full object-contain"
530
+ className: "h-full w-full object-contain p-2"
222
531
  }) : /* @__PURE__ */ jsx("div", {
223
532
  className: "flex h-full flex-col gap-1 p-3 text-sm",
224
533
  children: /* @__PURE__ */ jsxs("div", {
@@ -244,14 +553,21 @@ function Thumbnail({ spec }) {
244
553
  if (format.startsWith("copy:")) {
245
554
  const cc = content;
246
555
  return /* @__PURE__ */ jsxs("div", {
247
- className: "flex h-full flex-col gap-1 p-3 text-sm",
248
- children: [/* @__PURE__ */ jsx("div", {
249
- className: "line-clamp-2 font-medium",
250
- children: cc.headline
251
- }), /* @__PURE__ */ jsx("div", {
252
- className: "line-clamp-4 text-xs text-muted-foreground",
253
- children: cc.body
254
- })]
556
+ className: "flex h-full flex-col gap-2 overflow-hidden p-4",
557
+ children: [
558
+ /* @__PURE__ */ jsx("span", {
559
+ className: "shrink-0 text-xs font-medium text-muted-foreground",
560
+ children: COPY_PLATFORM_LABEL[cc.platform] ?? cc.platform
561
+ }),
562
+ cc.headline && /* @__PURE__ */ jsx("div", {
563
+ className: "line-clamp-2 shrink-0 text-base font-semibold leading-snug sm:line-clamp-3",
564
+ children: cc.headline
565
+ }),
566
+ /* @__PURE__ */ jsx("div", {
567
+ className: cn("min-h-0 flex-1 overflow-hidden text-sm leading-relaxed text-muted-foreground [overflow-wrap:anywhere]", FADE_OUT),
568
+ children: cc.body
569
+ })
570
+ ]
255
571
  });
256
572
  }
257
573
  return null;
@@ -281,10 +597,10 @@ function AssetCard({ spec, variantCount, onApprove, onReject, onEdit, onOpen, cl
281
597
  })]
282
598
  }),
283
599
  /* @__PURE__ */ jsxs("div", {
284
- className: "flex items-center gap-2 px-2.5 py-2",
600
+ className: "flex flex-wrap items-center justify-between gap-x-2 gap-y-1 px-2.5 py-2",
285
601
  children: [
286
602
  /* @__PURE__ */ jsx("span", {
287
- className: "min-w-0 flex-1 truncate text-xs text-muted-foreground",
603
+ className: "min-w-0 truncate text-xs text-muted-foreground",
288
604
  children: formatLabel
289
605
  }),
290
606
  /* @__PURE__ */ jsx(Badge, {
@@ -325,125 +641,6 @@ function AssetCard({ spec, variantCount, onApprove, onReject, onEdit, onOpen, cl
325
641
  });
326
642
  }
327
643
  //#endregion
328
- //#region src/assets/preview/email-preview.tsx
329
- function EmailPreview({ content, brand, previewUrl, className }) {
330
- if (previewUrl) return jsxs("div", {
331
- className: cn("flex flex-col gap-1", className),
332
- children: [
333
- jsx("div", {
334
- className: "text-xs text-muted-foreground font-medium truncate",
335
- children: content.subject
336
- }),
337
- content.preheader && jsx("div", {
338
- className: "text-xs text-muted-foreground/60 truncate",
339
- children: content.preheader
340
- }),
341
- jsx("iframe", {
342
- src: previewUrl,
343
- className: "w-full rounded border border-border",
344
- style: {
345
- height: 480,
346
- background: "#fff"
347
- },
348
- title: "Email preview",
349
- sandbox: "allow-same-origin"
350
- })
351
- ]
352
- });
353
- return jsxs("div", {
354
- className: cn("flex flex-col gap-2", className),
355
- children: [
356
- jsx("div", {
357
- className: "text-sm font-semibold truncate",
358
- children: content.subject
359
- }),
360
- content.preheader && jsx("div", {
361
- className: "text-xs text-muted-foreground truncate",
362
- children: content.preheader
363
- }),
364
- jsx("div", {
365
- className: "rounded border border-border p-4 space-y-3 overflow-y-auto",
366
- style: {
367
- maxHeight: 480,
368
- fontFamily: brand.fontFamily,
369
- color: brand.textColor
370
- },
371
- children: content.sections.map((section, i) => {
372
- if (section.type === "hero") return jsxs("div", {
373
- className: "text-center py-4 space-y-2",
374
- children: [
375
- section.imageUrl && jsx("img", {
376
- src: section.imageUrl,
377
- alt: "",
378
- className: "mx-auto max-h-40 object-cover rounded"
379
- }),
380
- jsx("div", {
381
- className: "text-xl font-bold",
382
- style: { color: brand.primaryColor },
383
- children: section.headline
384
- }),
385
- section.subheadline && jsx("div", {
386
- className: "text-sm text-muted-foreground",
387
- children: section.subheadline
388
- }),
389
- section.ctaLabel && jsx("a", {
390
- href: section.ctaUrl ?? "#",
391
- className: "inline-block px-4 py-2 rounded text-sm font-medium text-white",
392
- style: { background: brand.primaryColor },
393
- children: section.ctaLabel
394
- })
395
- ]
396
- }, i);
397
- if (section.type === "body") return jsx("p", {
398
- className: "text-sm leading-relaxed whitespace-pre-wrap",
399
- children: section.text
400
- }, i);
401
- if (section.type === "feature") return jsxs("div", {
402
- className: "flex gap-3 items-start",
403
- children: [section.imageUrl && jsx("img", {
404
- src: section.imageUrl,
405
- alt: "",
406
- className: "w-16 h-16 object-cover rounded shrink-0"
407
- }), jsxs("div", { children: [jsx("div", {
408
- className: "text-sm font-semibold",
409
- children: section.headline
410
- }), jsx("div", {
411
- className: "text-xs text-muted-foreground mt-0.5",
412
- children: section.description
413
- })] })]
414
- }, i);
415
- if (section.type === "testimonial") return jsxs("blockquote", {
416
- className: "border-l-2 pl-3 italic text-sm text-muted-foreground",
417
- style: { borderColor: brand.accentColor },
418
- children: [jsxs("p", { children: [
419
- "\"",
420
- section.quote,
421
- "\""
422
- ] }), jsxs("footer", {
423
- className: "mt-1 text-xs not-italic font-medium",
424
- children: [section.author, section.role ? `, ${section.role}` : ""]
425
- })]
426
- }, i);
427
- if (section.type === "cta") return jsxs("div", {
428
- className: "text-center py-3 space-y-1",
429
- children: [jsx("a", {
430
- href: section.url,
431
- className: "inline-block px-5 py-2.5 rounded font-medium text-white text-sm",
432
- style: { background: brand.primaryColor },
433
- children: section.label
434
- }), section.subtext && jsx("p", {
435
- className: "text-xs text-muted-foreground",
436
- children: section.subtext
437
- })]
438
- }, i);
439
- if (section.type === "divider") return jsx("hr", { className: "border-border" }, i);
440
- return null;
441
- })
442
- })
443
- ]
444
- });
445
- }
446
- //#endregion
447
644
  //#region src/assets/preview/video-preview.tsx
448
645
  function VideoPreview({ content, brand, onRenderRequest, isRendering, className }) {
449
646
  if (content.renderedUrl) return /* @__PURE__ */ jsx("div", {
@@ -534,64 +731,6 @@ function VideoPreview({ content, brand, onRenderRequest, isRendering, className
534
731
  });
535
732
  }
536
733
  //#endregion
537
- //#region src/assets/preview/copy-preview.tsx
538
- const PLATFORM_LIMITS = {
539
- instagram: 2200,
540
- tiktok: 2200,
541
- x: 280,
542
- linkedin: 3e3,
543
- sms: 160,
544
- "email-subject": 60
545
- };
546
- const PLATFORM_LABELS = {
547
- instagram: "Instagram",
548
- tiktok: "TikTok",
549
- x: "X / Twitter",
550
- linkedin: "LinkedIn",
551
- sms: "SMS",
552
- "email-subject": "Email Subject"
553
- };
554
- function CopyPreview({ content, className }) {
555
- const limit = PLATFORM_LIMITS[content.platform];
556
- const bodyLen = content.body.length;
557
- const isOverLimit = limit !== null && bodyLen > limit;
558
- const warningThreshold = limit !== null ? limit * .9 : null;
559
- const isNearLimit = !isOverLimit && warningThreshold !== null && bodyLen >= warningThreshold;
560
- return /* @__PURE__ */ jsxs("div", {
561
- className: cn("flex flex-col gap-2", className),
562
- children: [/* @__PURE__ */ jsxs("div", {
563
- className: "flex items-center justify-between",
564
- children: [/* @__PURE__ */ jsx("span", {
565
- className: "text-xs font-medium text-muted-foreground",
566
- children: PLATFORM_LABELS[content.platform]
567
- }), limit !== null && /* @__PURE__ */ jsxs("span", {
568
- className: cn("text-xs tabular-nums", isOverLimit ? "text-destructive font-medium" : isNearLimit ? "text-warning" : "text-muted-foreground"),
569
- children: [
570
- bodyLen,
571
- " / ",
572
- limit
573
- ]
574
- })]
575
- }), /* @__PURE__ */ jsxs("div", {
576
- className: "rounded border border-border p-3 space-y-2 bg-card",
577
- children: [
578
- content.headline && /* @__PURE__ */ jsx("div", {
579
- className: "text-sm font-semibold",
580
- children: content.headline
581
- }),
582
- /* @__PURE__ */ jsx("p", {
583
- className: "text-sm whitespace-pre-wrap leading-relaxed",
584
- children: content.body
585
- }),
586
- content.hashtags && content.hashtags.length > 0 && /* @__PURE__ */ jsx("div", {
587
- className: "text-sm text-blue-500 flex flex-wrap gap-1",
588
- children: content.hashtags.map((tag) => /* @__PURE__ */ jsxs("span", { children: ["#", tag] }, tag))
589
- })
590
- ]
591
- })]
592
- });
593
- }
594
- //#endregion
595
734
  //#region src/assets/asset-editor.tsx
596
735
  function BrandPanel({ brand, onChange }) {
597
736
  const [open, setOpen] = React$1.useState(false);
@@ -1126,4 +1265,4 @@ function ApprovalQueue({ assets, variantCounts = {}, onApprove, onReject, onEdit
1126
1265
  });
1127
1266
  }
1128
1267
  //#endregion
1129
- export { ASSET_FORMAT_LABEL, ASSET_STATUS_LABEL, ApprovalQueue, AssetCard, AssetEditor, CopyPreview, EmailPreview, ImagePreview, VideoPreview, imageOnlyUrl, videoPosterUrl };
1268
+ export { ASSET_FORMAT_LABEL, ASSET_STATUS_LABEL, ApprovalQueue, AssetCard, AssetEditor, COPY_PLATFORM_LABEL, CopyPreview, EmailPreview, ImagePreview, VideoPreview, emailPalette, imageOnlyUrl, videoPosterUrl };
package/dist/globals.css CHANGED
@@ -1007,9 +1007,6 @@
1007
1007
  .h-screen {
1008
1008
  height: 100vh;
1009
1009
  }
1010
- .max-h-40 {
1011
- max-height: calc(var(--spacing) * 40);
1012
- }
1013
1010
  .max-h-64 {
1014
1011
  max-height: calc(var(--spacing) * 64);
1015
1012
  }
@@ -2184,9 +2181,9 @@
2184
2181
  border-left-style: var(--tw-border-style);
2185
2182
  border-left-width: 0px;
2186
2183
  }
2187
- .border-l-2 {
2184
+ .border-l-4 {
2188
2185
  border-left-style: var(--tw-border-style);
2189
- border-left-width: 2px;
2186
+ border-left-width: 4px;
2190
2187
  }
2191
2188
  .border-dashed {
2192
2189
  --tw-border-style: dashed;
@@ -3002,6 +2999,9 @@
3002
2999
  --tw-gradient-to: transparent;
3003
3000
  --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
3004
3001
  }
3002
+ .\[mask-image\:linear-gradient\(to_bottom\,black_75\%\,transparent\)\] {
3003
+ mask-image: linear-gradient(to bottom,black 75%,transparent);
3004
+ }
3005
3005
  .fill-current {
3006
3006
  fill: currentcolor;
3007
3007
  }
@@ -3747,9 +3747,6 @@
3747
3747
  .text-blue-400 {
3748
3748
  color: var(--color-blue-400);
3749
3749
  }
3750
- .text-blue-500 {
3751
- color: var(--color-blue-500);
3752
- }
3753
3750
  .text-border {
3754
3751
  color: hsl(var(--border));
3755
3752
  }
@@ -3894,13 +3891,13 @@
3894
3891
  .italic {
3895
3892
  font-style: italic;
3896
3893
  }
3897
- .not-italic {
3898
- font-style: normal;
3899
- }
3900
3894
  .tabular-nums {
3901
3895
  --tw-numeric-spacing: tabular-nums;
3902
3896
  font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
3903
3897
  }
3898
+ .no-underline {
3899
+ text-decoration-line: none;
3900
+ }
3904
3901
  .underline {
3905
3902
  text-decoration-line: underline;
3906
3903
  }
@@ -5276,6 +5273,12 @@
5276
5273
  -webkit-box-orient: vertical;
5277
5274
  -webkit-line-clamp: 1;
5278
5275
  }
5276
+ .sm\:line-clamp-3 {
5277
+ overflow: hidden;
5278
+ display: -webkit-box;
5279
+ -webkit-box-orient: vertical;
5280
+ -webkit-line-clamp: 3;
5281
+ }
5279
5282
  .sm\:grid {
5280
5283
  display: grid;
5281
5284
  }
@@ -5376,6 +5379,9 @@
5376
5379
  .sm\:px-6 {
5377
5380
  padding-inline: calc(var(--spacing) * 6);
5378
5381
  }
5382
+ .sm\:px-8 {
5383
+ padding-inline: calc(var(--spacing) * 8);
5384
+ }
5379
5385
  .sm\:py-1 {
5380
5386
  padding-block: var(--spacing);
5381
5387
  }
package/dist/styles.css CHANGED
@@ -1007,9 +1007,6 @@
1007
1007
  .h-screen {
1008
1008
  height: 100vh;
1009
1009
  }
1010
- .max-h-40 {
1011
- max-height: calc(var(--spacing) * 40);
1012
- }
1013
1010
  .max-h-64 {
1014
1011
  max-height: calc(var(--spacing) * 64);
1015
1012
  }
@@ -2184,9 +2181,9 @@
2184
2181
  border-left-style: var(--tw-border-style);
2185
2182
  border-left-width: 0px;
2186
2183
  }
2187
- .border-l-2 {
2184
+ .border-l-4 {
2188
2185
  border-left-style: var(--tw-border-style);
2189
- border-left-width: 2px;
2186
+ border-left-width: 4px;
2190
2187
  }
2191
2188
  .border-dashed {
2192
2189
  --tw-border-style: dashed;
@@ -3002,6 +2999,9 @@
3002
2999
  --tw-gradient-to: transparent;
3003
3000
  --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
3004
3001
  }
3002
+ .\[mask-image\:linear-gradient\(to_bottom\,black_75\%\,transparent\)\] {
3003
+ mask-image: linear-gradient(to bottom,black 75%,transparent);
3004
+ }
3005
3005
  .fill-current {
3006
3006
  fill: currentcolor;
3007
3007
  }
@@ -3747,9 +3747,6 @@
3747
3747
  .text-blue-400 {
3748
3748
  color: var(--color-blue-400);
3749
3749
  }
3750
- .text-blue-500 {
3751
- color: var(--color-blue-500);
3752
- }
3753
3750
  .text-border {
3754
3751
  color: hsl(var(--border));
3755
3752
  }
@@ -3894,13 +3891,13 @@
3894
3891
  .italic {
3895
3892
  font-style: italic;
3896
3893
  }
3897
- .not-italic {
3898
- font-style: normal;
3899
- }
3900
3894
  .tabular-nums {
3901
3895
  --tw-numeric-spacing: tabular-nums;
3902
3896
  font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
3903
3897
  }
3898
+ .no-underline {
3899
+ text-decoration-line: none;
3900
+ }
3904
3901
  .underline {
3905
3902
  text-decoration-line: underline;
3906
3903
  }
@@ -5276,6 +5273,12 @@
5276
5273
  -webkit-box-orient: vertical;
5277
5274
  -webkit-line-clamp: 1;
5278
5275
  }
5276
+ .sm\:line-clamp-3 {
5277
+ overflow: hidden;
5278
+ display: -webkit-box;
5279
+ -webkit-box-orient: vertical;
5280
+ -webkit-line-clamp: 3;
5281
+ }
5279
5282
  .sm\:grid {
5280
5283
  display: grid;
5281
5284
  }
@@ -5376,6 +5379,9 @@
5376
5379
  .sm\:px-6 {
5377
5380
  padding-inline: calc(var(--spacing) * 6);
5378
5381
  }
5382
+ .sm\:px-8 {
5383
+ padding-inline: calc(var(--spacing) * 8);
5384
+ }
5379
5385
  .sm\:py-1 {
5380
5386
  padding-block: var(--spacing);
5381
5387
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/sandbox-ui",
3
- "version": "0.131.1",
3
+ "version": "0.131.3",
4
4
  "license": "MIT",
5
5
  "description": "Unified UI component library for Tangle Sandbox — primitives, chat, dashboard, terminal, editor, and workspace components",
6
6
  "repository": {