@typecaast/skin-kit 0.3.2 → 0.4.1
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/index.cjs +233 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +234 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
var react = require('react');
|
|
5
5
|
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var reactDom = require('react-dom');
|
|
6
7
|
|
|
7
8
|
// src/define-skin.ts
|
|
8
9
|
function defineSkin(skin) {
|
|
@@ -64,6 +65,7 @@ function fadeSlideIn(progress, options = {}) {
|
|
|
64
65
|
const eased = (options.easing ?? easeOutCubic)(clamp01(progress));
|
|
65
66
|
const distance = options.distance ?? 8;
|
|
66
67
|
const offset = (1 - eased) * distance;
|
|
68
|
+
if (offset === 0) return { opacity: eased, transform: "none" };
|
|
67
69
|
const translate = options.axis === "x" ? `translateX(${offset}px)` : `translateY(${offset}px)`;
|
|
68
70
|
return { opacity: eased, transform: translate };
|
|
69
71
|
}
|
|
@@ -302,6 +304,236 @@ function TypecaastStage({
|
|
|
302
304
|
] })
|
|
303
305
|
] });
|
|
304
306
|
}
|
|
307
|
+
var SLOT_ATTR = "data-tc-slot";
|
|
308
|
+
function contentToText(content) {
|
|
309
|
+
const out = [];
|
|
310
|
+
for (const node of content) {
|
|
311
|
+
if (node.type === "text" && Array.isArray(node.spans)) {
|
|
312
|
+
for (const span of node.spans) {
|
|
313
|
+
out.push(span.value ?? span.label ?? "");
|
|
314
|
+
}
|
|
315
|
+
} else if (node.type === "image") {
|
|
316
|
+
out.push(node.alt ?? "\u{1F5BC}");
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return out.join("");
|
|
320
|
+
}
|
|
321
|
+
function initials(name) {
|
|
322
|
+
return name.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0]?.toUpperCase() ?? "").join("");
|
|
323
|
+
}
|
|
324
|
+
function fmtTime(atMs) {
|
|
325
|
+
const total = Math.floor(atMs / 1e3);
|
|
326
|
+
const m = Math.floor(total / 60);
|
|
327
|
+
const s = total % 60;
|
|
328
|
+
return `${m}:${String(s).padStart(2, "0")}`;
|
|
329
|
+
}
|
|
330
|
+
function styleText(tokens, css, capturedAt, slotMarkers) {
|
|
331
|
+
const vars = Object.entries(tokens.colors ?? {}).map(([k, v]) => `--${k}: ${v};`).join(" ");
|
|
332
|
+
const cv = capturedAt?.viewportWidth ? `--captured-viewport-width: ${capturedAt.viewportWidth}px;` : "";
|
|
333
|
+
const reset = `
|
|
334
|
+
:host { all: initial; display: block; width: 100%; height: 100%; ${vars} ${cv} }
|
|
335
|
+
* { box-sizing: border-box; }
|
|
336
|
+
/* Normalise the captured root: drop desktop-only margins / max-widths
|
|
337
|
+
so the layout re-centers cleanly in any canvas. */
|
|
338
|
+
.tc-slot-root > :first-child {
|
|
339
|
+
margin-left: auto !important;
|
|
340
|
+
margin-right: auto !important;
|
|
341
|
+
max-width: 100% !important;
|
|
342
|
+
}`;
|
|
343
|
+
const markers = slotMarkers ? `
|
|
344
|
+
[data-tc-slot] {
|
|
345
|
+
outline: 1px dashed rgba(99, 102, 241, 0.6);
|
|
346
|
+
outline-offset: -1px;
|
|
347
|
+
position: relative;
|
|
348
|
+
}
|
|
349
|
+
[data-tc-slot]::before {
|
|
350
|
+
content: attr(data-tc-slot);
|
|
351
|
+
position: absolute;
|
|
352
|
+
top: 0;
|
|
353
|
+
left: 0;
|
|
354
|
+
transform: translateY(-100%);
|
|
355
|
+
font: 600 9px/1 -apple-system, system-ui, sans-serif;
|
|
356
|
+
padding: 1px 4px;
|
|
357
|
+
background: rgba(99, 102, 241, 0.95);
|
|
358
|
+
color: white;
|
|
359
|
+
border-radius: 2px;
|
|
360
|
+
pointer-events: none;
|
|
361
|
+
z-index: 10;
|
|
362
|
+
}` : "";
|
|
363
|
+
return `${reset}
|
|
364
|
+
${css}
|
|
365
|
+
${markers}`;
|
|
366
|
+
}
|
|
367
|
+
function fillInto(host, templateHtml, values) {
|
|
368
|
+
host.innerHTML = templateHtml;
|
|
369
|
+
for (const node of host.querySelectorAll(`[${SLOT_ATTR}]`)) {
|
|
370
|
+
const slot = node.getAttribute(SLOT_ATTR);
|
|
371
|
+
if (slot && slot in values) node.textContent = values[slot] ?? "";
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function revealStyle(progress) {
|
|
375
|
+
const p = Math.max(0, Math.min(1, progress));
|
|
376
|
+
return {
|
|
377
|
+
opacity: p,
|
|
378
|
+
transform: `translateY(${(1 - p) * 6}px)`,
|
|
379
|
+
willChange: "opacity, transform"
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function slotSkinFromDraft(draft, opts) {
|
|
383
|
+
const lightTokens = { colors: draft.tokens.colors ?? {} };
|
|
384
|
+
const darkTokens = draft.darkTokens ? { colors: draft.darkTokens.colors ?? {} } : lightTokens;
|
|
385
|
+
const slotMarkers = opts.slotMarkers ?? false;
|
|
386
|
+
const cssByTheme = {
|
|
387
|
+
light: styleText(
|
|
388
|
+
lightTokens,
|
|
389
|
+
draft.css ?? "",
|
|
390
|
+
draft.meta.capturedAt,
|
|
391
|
+
slotMarkers
|
|
392
|
+
),
|
|
393
|
+
dark: styleText(
|
|
394
|
+
darkTokens,
|
|
395
|
+
draft.css ?? "",
|
|
396
|
+
draft.meta.capturedAt,
|
|
397
|
+
slotMarkers
|
|
398
|
+
)
|
|
399
|
+
};
|
|
400
|
+
const supportsThemes = draft.darkTokens ? ["light", "dark"] : [draft.meta.theme ?? "light"];
|
|
401
|
+
const frameHtml = draft.slots.frame;
|
|
402
|
+
const messageHtml = draft.slots.message;
|
|
403
|
+
const composerHtml = draft.slots.composer;
|
|
404
|
+
const systemHtml = draft.slots.system;
|
|
405
|
+
const typingHtml = draft.slots.typing;
|
|
406
|
+
const Frame = ({
|
|
407
|
+
theme,
|
|
408
|
+
children
|
|
409
|
+
}) => {
|
|
410
|
+
const hostRef = react.useRef(null);
|
|
411
|
+
const [mount, setMount] = react.useState(null);
|
|
412
|
+
react.useLayoutEffect(() => {
|
|
413
|
+
const host = hostRef.current;
|
|
414
|
+
if (!host) return;
|
|
415
|
+
const shadow = host.shadowRoot ?? host.attachShadow({ mode: "open" });
|
|
416
|
+
shadow.innerHTML = "";
|
|
417
|
+
const style = host.ownerDocument.createElement("style");
|
|
418
|
+
style.textContent = theme === "dark" ? cssByTheme.dark : cssByTheme.light;
|
|
419
|
+
shadow.appendChild(style);
|
|
420
|
+
const wrapper = host.ownerDocument.createElement("div");
|
|
421
|
+
wrapper.className = "tc-slot-root";
|
|
422
|
+
wrapper.style.width = "100%";
|
|
423
|
+
wrapper.style.height = "100%";
|
|
424
|
+
wrapper.style.display = "flex";
|
|
425
|
+
wrapper.style.flexDirection = "column";
|
|
426
|
+
wrapper.style.overflow = "hidden";
|
|
427
|
+
wrapper.innerHTML = frameHtml ?? `<div ${SLOT_ATTR}="messages"></div>`;
|
|
428
|
+
shadow.appendChild(wrapper);
|
|
429
|
+
const slot = wrapper.querySelector(`[${SLOT_ATTR}="messages"]`) ?? wrapper;
|
|
430
|
+
slot.textContent = "";
|
|
431
|
+
setMount(slot);
|
|
432
|
+
}, [theme]);
|
|
433
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: hostRef, style: { width: "100%", height: "100%" }, children: mount ? reactDom.createPortal(children, mount) : null });
|
|
434
|
+
};
|
|
435
|
+
const Message = ({ message, author }) => {
|
|
436
|
+
const ref = react.useRef(null);
|
|
437
|
+
react.useLayoutEffect(() => {
|
|
438
|
+
const el = ref.current;
|
|
439
|
+
if (!el || !messageHtml) return;
|
|
440
|
+
fillInto(el, messageHtml, {
|
|
441
|
+
author: author.name,
|
|
442
|
+
avatar: initials(author.name),
|
|
443
|
+
body: contentToText(message.content),
|
|
444
|
+
time: fmtTime(message.atMs)
|
|
445
|
+
});
|
|
446
|
+
}, [message, author]);
|
|
447
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, style: revealStyle(message.revealProgress) });
|
|
448
|
+
};
|
|
449
|
+
const SystemMessage = ({ message }) => {
|
|
450
|
+
const ref = react.useRef(null);
|
|
451
|
+
react.useLayoutEffect(() => {
|
|
452
|
+
const el = ref.current;
|
|
453
|
+
if (!el || !systemHtml) return;
|
|
454
|
+
fillInto(el, systemHtml, { body: contentToText(message.content) });
|
|
455
|
+
}, [message]);
|
|
456
|
+
if (systemHtml) {
|
|
457
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, style: revealStyle(message.revealProgress) });
|
|
458
|
+
}
|
|
459
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
460
|
+
"div",
|
|
461
|
+
{
|
|
462
|
+
style: {
|
|
463
|
+
...revealStyle(message.revealProgress),
|
|
464
|
+
textAlign: "center"
|
|
465
|
+
},
|
|
466
|
+
children: contentToText(message.content)
|
|
467
|
+
}
|
|
468
|
+
);
|
|
469
|
+
};
|
|
470
|
+
const TypingIndicator = ({ author }) => {
|
|
471
|
+
const ref = react.useRef(null);
|
|
472
|
+
react.useLayoutEffect(() => {
|
|
473
|
+
const el = ref.current;
|
|
474
|
+
if (!el || !typingHtml) return;
|
|
475
|
+
fillInto(el, typingHtml, { author: author.name });
|
|
476
|
+
}, [author]);
|
|
477
|
+
if (typingHtml) {
|
|
478
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref });
|
|
479
|
+
}
|
|
480
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { opacity: 0.7, fontStyle: "italic" }, children: [
|
|
481
|
+
author.name,
|
|
482
|
+
" is typing\u2026"
|
|
483
|
+
] });
|
|
484
|
+
};
|
|
485
|
+
const Composer = ({ composer }) => {
|
|
486
|
+
const ref = react.useRef(null);
|
|
487
|
+
react.useLayoutEffect(() => {
|
|
488
|
+
const el = ref.current;
|
|
489
|
+
if (!el) return;
|
|
490
|
+
if (composerHtml) {
|
|
491
|
+
fillInto(el, composerHtml, { composer: composer.text });
|
|
492
|
+
} else {
|
|
493
|
+
el.textContent = composer.text;
|
|
494
|
+
}
|
|
495
|
+
}, [composer]);
|
|
496
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref });
|
|
497
|
+
};
|
|
498
|
+
const Avatar = ({
|
|
499
|
+
participant,
|
|
500
|
+
size = 36
|
|
501
|
+
}) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
502
|
+
"div",
|
|
503
|
+
{
|
|
504
|
+
style: {
|
|
505
|
+
width: size,
|
|
506
|
+
height: size,
|
|
507
|
+
borderRadius: "50%",
|
|
508
|
+
display: "grid",
|
|
509
|
+
placeItems: "center",
|
|
510
|
+
background: "var(--color-1, #ccc)",
|
|
511
|
+
fontSize: size * 0.4
|
|
512
|
+
},
|
|
513
|
+
children: initials(participant.name)
|
|
514
|
+
}
|
|
515
|
+
);
|
|
516
|
+
const components = {
|
|
517
|
+
Frame,
|
|
518
|
+
Message,
|
|
519
|
+
SystemMessage,
|
|
520
|
+
TypingIndicator,
|
|
521
|
+
Reaction: () => null,
|
|
522
|
+
Composer,
|
|
523
|
+
Avatar
|
|
524
|
+
};
|
|
525
|
+
return {
|
|
526
|
+
id: opts.id,
|
|
527
|
+
meta: {
|
|
528
|
+
name: draft.meta.name,
|
|
529
|
+
defaultCanvas: draft.meta.canvas ?? { width: 420, height: 720 },
|
|
530
|
+
supportsThemes,
|
|
531
|
+
capabilities: opts.capabilities
|
|
532
|
+
},
|
|
533
|
+
components,
|
|
534
|
+
tokens: { light: lightTokens, dark: darkTokens }
|
|
535
|
+
};
|
|
536
|
+
}
|
|
305
537
|
|
|
306
538
|
exports.MessageContent = MessageContent;
|
|
307
539
|
exports.ThemeProvider = ThemeProvider;
|
|
@@ -314,6 +546,7 @@ exports.easeOutCubic = easeOutCubic;
|
|
|
314
546
|
exports.fadeSlideIn = fadeSlideIn;
|
|
315
547
|
exports.loadSkinFonts = loadSkinFonts;
|
|
316
548
|
exports.popIn = popIn;
|
|
549
|
+
exports.slotSkinFromDraft = slotSkinFromDraft;
|
|
317
550
|
exports.useTheme = useTheme;
|
|
318
551
|
exports.useTokens = useTokens;
|
|
319
552
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/define-skin.ts","../src/theme.tsx","../src/fonts.ts","../src/animation.tsx","../src/content.tsx","../src/stage.tsx"],"names":["createContext","jsx","useContext","Fragment","useMemo","jsxs"],"mappings":";;;;;;AAOO,SAAS,WAAW,IAAA,EAAkB;AAC3C,EAAA,OAAO,IAAA;AACT;ACKA,IAAM,YAAA,GAAeA,mBAAA,CAAiC,EAAE,KAAA,EAAO,SAAS,CAAA;AAUjE,SAAS,aAAA,CAAc;AAAA,EAC5B,KAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAAqC;AACnC,EAAA,uBACEC,cAAA,CAAC,aAAa,QAAA,EAAb,EAAsB,OAAO,EAAE,KAAA,EAAO,MAAA,EAAO,EAC3C,QAAA,EACH,CAAA;AAEJ;AAGO,SAAS,QAAA,GAA0B;AACxC,EAAA,OAAOC,gBAAA,CAAW,YAAY,CAAA,CAAE,KAAA;AAClC;AAGO,SAAS,SAAA,GAAoC;AAClD,EAAA,OAAOA,gBAAA,CAAW,YAAY,CAAA,CAAE,MAAA;AAClC;;;ACzCA,SAAS,SAAS,IAAA,EAA+B;AAC/C,EAAA,OAAO,IAAA,CAAK,OAAA,CACT,GAAA,CAAI,CAAC,CAAA,KAAM;AACV,IAAA,MAAM,SAAS,CAAA,CAAE,MAAA,GAAS,CAAA,SAAA,EAAY,CAAA,CAAE,MAAM,CAAA,EAAA,CAAA,GAAO,EAAA;AACrD,IAAA,OAAO,CAAA,KAAA,EAAQ,CAAA,CAAE,GAAG,CAAA,EAAA,EAAK,MAAM,CAAA,CAAA;AAAA,EACjC,CAAC,CAAA,CACA,IAAA,CAAK,IAAI,CAAA;AACd;AASA,eAAsB,cACpB,KAAA,EACe;AACf,EAAA,IAAI,CAAC,KAAA,IAAS,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG;AAClC,EAAA,IACE,OAAO,aAAa,WAAA,IACpB,OAAO,aAAa,WAAA,IACpB,CAAC,SAAS,KAAA,EACV;AACA,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,UAA8B,EAAC;AACrC,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,KAAA,MAAW,MAAA,IAAU,KAAK,OAAA,EAAS;AACjC,MAAA,MAAM,cAAmC,EAAC;AAC1C,MAAA,IAAI,OAAO,MAAA,KAAW,MAAA;AACpB,QAAA,WAAA,CAAY,MAAA,GAAS,MAAA,CAAO,MAAA,CAAO,MAAM,CAAA;AAC3C,MAAA,IAAI,MAAA,CAAO,KAAA,KAAU,MAAA,EAAW,WAAA,CAAY,QAAQ,MAAA,CAAO,KAAA;AAE3D,MAAA,MAAM,SAA0B,EAAE,GAAG,MAAM,OAAA,EAAS,CAAC,MAAM,CAAA,EAAE;AAC7D,MAAA,MAAM,IAAA,GAAO,IAAI,QAAA,CAAS,IAAA,CAAK,QAAQ,QAAA,CAAS,MAAM,GAAG,WAAW,CAAA;AAGpE,MAAA,MAAM,OAAA,GAAU,CAAC,GAAG,QAAA,CAAS,KAAK,CAAA,CAAE,IAAA;AAAA,QAClC,CAAC,CAAA,KACC,CAAA,CAAE,MAAA,KAAW,KAAK,MAAA,IAClB,CAAA,CAAE,MAAA,MAAY,WAAA,CAAY,MAAA,IAAU,QAAA,CAAA,IACpC,CAAA,CAAE,KAAA,MAAW,YAAY,KAAA,IAAS,QAAA;AAAA,OACtC;AACA,MAAA,IAAI,OAAA,EAAS;AAEb,MAAA,QAAA,CAAS,KAAA,CAAM,IAAI,IAAI,CAAA;AACvB,MAAA,OAAA,CAAQ,IAAA,CAAK,IAAA,CAAK,IAAA,EAAM,CAAA;AAAA,IAC1B;AAAA,EACF;AACA,EAAA,MAAM,OAAA,CAAQ,IAAI,OAAO,CAAA;AAC3B;AC/CO,IAAM,OAAA,GAAU,CAAC,CAAA,KAAuB,CAAA,GAAI,IAAI,CAAA,GAAI,CAAA,GAAI,IAAI,CAAA,GAAI;AAGhE,IAAM,YAAA,GAAe,CAAC,CAAA,KAAsB,CAAA,GAAI,KAAK,GAAA,CAAI,CAAA,GAAI,GAAG,CAAC;AAGjE,SAAS,WAAA,CAAY,CAAA,EAAW,OAAA,GAAU,GAAA,EAAa;AAC5D,EAAA,MAAM,KAAK,OAAA,GAAU,CAAA;AACrB,EAAA,OAAO,CAAA,GAAI,EAAA,GAAK,IAAA,CAAK,GAAA,CAAI,CAAA,GAAI,CAAA,EAAG,CAAC,CAAA,GAAI,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,CAAA,GAAI,GAAG,CAAC,CAAA;AAClE;AAWO,SAAS,WAAA,CACd,QAAA,EACA,OAAA,GAA4B,EAAC,EACd;AACf,EAAA,MAAM,SAAS,OAAA,CAAQ,MAAA,IAAU,YAAA,EAAc,OAAA,CAAQ,QAAQ,CAAC,CAAA;AAChE,EAAA,MAAM,QAAA,GAAW,QAAQ,QAAA,IAAY,CAAA;AACrC,EAAA,MAAM,MAAA,GAAA,CAAU,IAAI,KAAA,IAAS,QAAA;AAC7B,EAAA,MAAM,SAAA,GACJ,QAAQ,IAAA,KAAS,GAAA,GACb,cAAc,MAAM,CAAA,GAAA,CAAA,GACpB,cAAc,MAAM,CAAA,GAAA,CAAA;AAC1B,EAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,SAAA,EAAW,SAAA,EAAU;AAChD;AAQO,SAAS,KAAA,CACd,QAAA,EACA,OAAA,GAAsB,EAAC,EACR;AACf,EAAA,MAAM,CAAA,GAAI,QAAQ,QAAQ,CAAA;AAC1B,EAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,CAAA,EAAG,OAAA,CAAQ,WAAW,GAAG,CAAA;AACnD,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,OAAA,CAAQ,CAAA,GAAI,CAAC,CAAA;AAAA,IACtB,SAAA,EAAW,SAAS,KAAK,CAAA,CAAA,CAAA;AAAA,IACzB,eAAA,EAAiB;AAAA,GACnB;AACF;AAoBO,SAAS,UAAA,CAAW;AAAA,EACzB,QAAA,GAAW,CAAA;AAAA,EACX,KAAA,GAAQ,CAAA;AAAA,EACR,MAAA,GAAS,CAAA;AAAA,EACT,KAAA,GAAQ,cAAA;AAAA,EACR,IAAA,GAAO,CAAA;AAAA,EACP,GAAA,GAAM;AACR,CAAA,EAAkC;AAChC,EAAA,MAAM,QAAQ,OAAA,CAAQ,QAAQ,CAAA,GAAI,MAAA,GAAS,KAAK,EAAA,GAAK,CAAA;AACrD,EAAA,MAAM,OAAoB,EAAC;AAC3B,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,EAAO,CAAA,EAAA,EAAK;AAC9B,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,KAAA,GAAQ,IAAI,GAAG,CAAA;AACrC,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAI,CAAA,GAAI,CAAA;AACjC,IAAA,MAAM,UAAU,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAI,CAAA,GAAI,GAAA;AAC1C,IAAA,IAAA,CAAK,IAAA;AAAA,sBACHD,cAAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UAEC,KAAA,EAAO;AAAA,YACL,KAAA,EAAO,IAAA;AAAA,YACP,MAAA,EAAQ,IAAA;AAAA,YACR,YAAA,EAAc,KAAA;AAAA,YACd,UAAA,EAAY,KAAA;AAAA,YACZ,SAAA,EAAW,CAAA,WAAA,EAAc,CAAC,IAAI,CAAA,GAAA,CAAA;AAAA,YAC9B;AAAA;AACF,SAAA;AAAA,QARK;AAAA;AASP,KACF;AAAA,EACF;AACA,EAAA,uBACEA,cAAAA,CAAC,MAAA,EAAA,EAAK,KAAA,EAAO,EAAE,OAAA,EAAS,aAAA,EAAe,UAAA,EAAY,UAAA,EAAY,GAAA,EAAI,EAChE,QAAA,EAAA,IAAA,EACH,CAAA;AAEJ;AC7EA,SAAS,YAAA,CACP,IAAA,EACA,GAAA,EACA,EAAA,EACA,EAAA,EACW;AACX,EAAA,QAAQ,KAAK,IAAA;AAAM,IACjB,KAAK,MAAA;AACH,MAAA,OAAO,IAAA,CAAK,KAAA;AAAA,IACd,KAAK,MAAA;AACH,MAAA,uBACEA,cAAAA,CAAC,MAAA,EAAA,EAAe,cAAA,EAAa,MAAA,EAAO,SAAA,EAAW,EAAA,CAAG,IAAA,EAAM,KAAA,EAAO,EAAA,CAAG,IAAA,EAC/D,QAAA,EAAA,IAAA,CAAK,SADG,GAEX,CAAA;AAAA,IAEJ,KAAK,MAAA;AACH,MAAA,uBACEA,cAAAA;AAAA,QAAC,GAAA;AAAA,QAAA;AAAA,UAEC,cAAA,EAAa,MAAA;AAAA,UACb,WAAW,EAAA,CAAG,IAAA;AAAA,UACd,OAAO,EAAA,CAAG,IAAA;AAAA,UACV,MAAM,IAAA,CAAK,IAAA;AAAA,UACX,GAAA,EAAI,YAAA;AAAA,UAEH,QAAA,EAAA,IAAA,CAAK,SAAS,IAAA,CAAK;AAAA,SAAA;AAAA,QAPf;AAAA,OAQP;AAAA,IAEJ,KAAK,SAAA;AACH,MAAA,uBACEA,cAAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UAEC,cAAA,EAAa,SAAA;AAAA,UACb,WAAW,EAAA,CAAG,OAAA;AAAA,UACd,OAAO,EAAA,CAAG,OAAA;AAAA,UAET,QAAA,EAAA,IAAA,CAAK;AAAA,SAAA;AAAA,QALD;AAAA,OAMP;AAAA,IAEJ,KAAK,OAAA;AACH,MAAA,uBACEA,cAAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UAEC,cAAA,EAAa,OAAA;AAAA,UACb,WAAW,EAAA,CAAG,KAAA;AAAA,UACd,OAAO,EAAA,CAAG,KAAA;AAAA,UAET,QAAA,EAAA,IAAA,CAAK;AAAA,SAAA;AAAA,QALD;AAAA,OAMP;AAAA;AAGR;AAEA,SAAS,WAAA,CACP,IAAA,EACA,GAAA,EACA,EAAA,EACA,UAAA,EACW;AACX,EAAA,uBACEA,cAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MAEC,cAAA,EAAa,OAAA;AAAA,MACb,WAAW,EAAA,CAAG,KAAA;AAAA,MACd,KAAK,IAAA,CAAK,GAAA;AAAA,MACV,GAAA,EAAK,KAAK,GAAA,IAAO,EAAA;AAAA,MACjB,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,OAAO,EAAE,QAAA,EAAU,QAAQ,OAAA,EAAS,OAAA,EAAS,GAAG,UAAA;AAAW,KAAA;AAAA,IAPtD;AAAA,GAQP;AAEJ;AAQO,SAAS,cAAA,CAAe;AAAA,EAC7B,KAAA;AAAA,EACA,aAAa,EAAC;AAAA,EACd,SAAS,EAAC;AAAA,EACV;AACF,CAAA,EAAsC;AACpC,EAAA,uBACEA,cAAAA,CAAAE,mBAAA,EAAA,EACG,gBAAM,GAAA,CAAI,CAAC,MAAM,CAAA,KAAM;AACtB,IAAA,IAAI,IAAA,CAAK,SAAS,MAAA,EAAQ;AACxB,MAAA,MAAM,IAAA,GAAO,IAAA;AACb,MAAA,uBACEF,cAAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UAEC,cAAA,EAAa,MAAA;AAAA,UACb,WAAW,UAAA,CAAW,IAAA;AAAA,UACtB,OAAO,MAAA,CAAO,IAAA;AAAA,UAEb,eAAK,KAAA,CAAM,GAAA;AAAA,YAAI,CAAC,IAAA,EAAM,CAAA,KACrB,aAAa,IAAA,EAAM,CAAA,EAAG,YAAY,MAAM;AAAA;AAC1C,SAAA;AAAA,QAPK;AAAA,OAQP;AAAA,IAEJ;AACA,IAAA,IAAI,IAAA,CAAK,SAAS,OAAA,EAAS;AACzB,MAAA,OAAO,WAAA,CAAY,IAAA,EAAmB,CAAA,EAAG,UAAA,EAAY,UAAU,CAAA;AAAA,IACjE;AACA,IAAA,OAAO,IAAA;AAAA,EACT,CAAC,CAAA,EACH,CAAA;AAEJ;ACpIA,IAAM,oBAAA,GAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AA8BtB,SAAS,cAAA,CAAe;AAAA,EAC7B,KAAA;AAAA,EACA,IAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA,GAAW;AACb,CAAA,EAAsC;AACpC,EAAA,MAAM,QAAQ,KAAA,CAAM,KAAA;AACpB,EAAA,MAAM,IAAA,GAAOG,cAAQ,MAAM;AACzB,IAAA,MAAM,GAAA,uBAAU,GAAA,EAAyB;AACzC,IAAA,KAAA,MAAW,KAAK,YAAA,EAAc,GAAA,CAAI,GAAA,CAAI,CAAA,CAAE,IAAI,CAAC,CAAA;AAC7C,IAAA,OAAO,GAAA;AAAA,EACT,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAEjB,EAAA,MAAM,EAAE,KAAA,EAAO,OAAA,EAAS,eAAe,eAAA,EAAiB,QAAA,KACtD,IAAA,CAAK,UAAA;AACP,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,GAAS,KAAK,CAAA;AAGlC,EAAA,MAAM,eAAA,GAAkBA,aAAA;AAAA,IACtB,MAAM,YAAA,CAAa,IAAA,CAAK,CAAC,CAAA,KAAM,EAAE,MAAM,CAAA;AAAA,IACvC,CAAC,YAAY;AAAA,GACf;AACA,EAAA,MAAM,cAAA,GAAiB,KAAA,CAAM,QAAA,CAAS,IAAA,GAClC,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,QAAA,CAAS,IAAI,CAAA,GAC5B,QAAA,KAAa,QAAA,GACX,eAAA,GACA,MAAA;AACN,EAAA,MAAM,YAAA,GAAe,QAAA,KAAa,OAAA,IAAW,cAAA,KAAmB,MAAA;AAKhE,EAAA,MAAM,eAAA,GAAkB,IAAA,CAAK,IAAA,CAAK,eAAA,IAAmB,QAAA;AACrD,EAAA,MAAM,cAAc,KAAA,CAAM,gBAAA,CACvB,GAAA,CAAI,CAAC,QAAQ,CAAA,KAAM;AAClB,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA;AACnC,IAAA,IAAI,CAAC,MAAA,IAAU,MAAA,CAAO,MAAA,EAAQ,OAAO,IAAA;AACrC,IAAA,uBACEH,cAAAA;AAAA,MAAC,eAAA;AAAA,MAAA;AAAA,QAEC,KAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA,OAAA;AAAA,MAHK,CAAA,OAAA,EAAU,MAAA,CAAO,IAAI,CAAA,CAAA,EAAI,CAAC,CAAA;AAAA,KAIjC;AAAA,EAEJ,CAAC,CAAA,CACA,MAAA,CAAO,OAAO,CAAA;AAEjB,EAAA,uBACEI,eAAA,CAAC,aAAA,EAAA,EAAc,KAAA,EAAc,MAAA,EAC3B,QAAA,EAAA;AAAA,oBAAAJ,cAAAA,CAAC,WAAO,QAAA,EAAA,oBAAA,EAAqB,CAAA;AAAA,oBAC7BI,eAAA,CAAC,KAAA,EAAA,EAAM,KAAA,EAAc,OAAA,EAAkB,QAAA,EAYrC,QAAA,EAAA;AAAA,sBAAAA,eAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,uBAAA,EAAsB,EAAA;AAAA,UACtB,KAAA,EAAO;AAAA,YACL,OAAA,EAAS,MAAA;AAAA,YACT,aAAA,EAAe,gBAAA;AAAA,YACf,IAAA,EAAM,UAAA;AAAA,YACN,SAAA,EAAW,CAAA;AAAA,YACX,SAAA,EAAW,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAKX,aAAA,EAAe;AAAA,WACjB;AAAA,UAEC,QAAA,EAAA;AAAA,YAAA,eAAA,KAAoB,WAAW,WAAA,GAAc,IAAA;AAAA,YAC7C,KAAA,CAAM,QAAA,CACJ,GAAA,CAAI,CAAC,SAAS,CAAA,KAAM;AACnB,cAAA,MAAM,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA;AACpC,cAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AAGpB,cAAA,MAAM,GAAA,GAAM,CAAA,EAAG,OAAA,CAAQ,EAAE,IAAI,CAAC,CAAA,CAAA;AAC9B,cAAA,IAAI,OAAA,CAAQ,YAAY,QAAA,EAAU;AAChC,gBAAA,uBACEJ,cAAAA;AAAA,kBAAC,aAAA;AAAA,kBAAA;AAAA,oBAEC,KAAA;AAAA,oBACA,OAAA;AAAA,oBACA;AAAA,mBAAA;AAAA,kBAHK;AAAA,iBAIP;AAAA,cAEJ;AAGA,cAAA,MAAM,IAAA,GAAO,KAAA,CAAM,QAAA,CAAS,CAAA,GAAI,CAAC,CAAA;AACjC,cAAA,MAAM,iBAAiB,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,GAAI,MAAA;AACpD,cAAA,uBACEA,cAAAA;AAAA,gBAAC,OAAA;AAAA,gBAAA;AAAA,kBAEC,KAAA;AAAA,kBACA,OAAA;AAAA,kBACA,MAAA;AAAA,kBACA;AAAA,iBAAA;AAAA,gBAJK;AAAA,eAKP;AAAA,YAEJ,CAAC,EACA,OAAA;AAAQ;AAAA;AAAA,OACb;AAAA,MACC,+BACCA,cAAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,KAAA;AAAA,UACA,UAAU,KAAA,CAAM,QAAA;AAAA,UAChB,MAAA,EAAQ,cAAA;AAAA,UACR;AAAA;AAAA,OACF,GACE,IAAA;AAAA,MACH,eAAA,KAAoB,mBAAmB,WAAA,GAAc;AAAA,KAAA,EACxD;AAAA,GAAA,EACF,CAAA;AAEJ","file":"index.cjs","sourcesContent":["import type { Skin } from \"./types.js\";\n\n/**\n * Identity helper for type-safety and a single registration point when\n * authoring a skin. Keeping it a function (rather than a bare object) lets us\n * add validation/registration later without changing skin call sites.\n */\nexport function defineSkin(skin: Skin): Skin {\n return skin;\n}\n","import {\n createContext,\n useContext,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\nimport type { SkinTokens } from \"./types.js\";\n\ninterface ThemeContextValue {\n theme: ResolvedTheme;\n tokens?: SkinTokens;\n}\n\nconst ThemeContext = createContext<ThemeContextValue>({ theme: \"light\" });\n\nexport interface ThemeProviderProps {\n theme: ResolvedTheme;\n /** Resolved per-theme tokens for the active skin. */\n tokens?: SkinTokens;\n children?: ReactNode;\n}\n\n/** Provides the resolved theme + tokens to every skin component below it. */\nexport function ThemeProvider({\n theme,\n tokens,\n children,\n}: ThemeProviderProps): ReactElement {\n return (\n <ThemeContext.Provider value={{ theme, tokens }}>\n {children}\n </ThemeContext.Provider>\n );\n}\n\n/** The resolved theme (`\"light\" | \"dark\"`) for the current subtree. */\nexport function useTheme(): ResolvedTheme {\n return useContext(ThemeContext).theme;\n}\n\n/** The resolved design tokens for the current subtree, if provided. */\nexport function useTokens(): SkinTokens | undefined {\n return useContext(ThemeContext).tokens;\n}\n","import type { FontDeclaration } from \"./types.js\";\n\n/** Build a CSS `src:` value from a font's sources. */\nfunction srcValue(decl: FontDeclaration): string {\n return decl.sources\n .map((s) => {\n const format = s.format ? ` format(\"${s.format}\")` : \"\";\n return `url(\"${s.url}\")${format}`;\n })\n .join(\", \");\n}\n\n/**\n * Load a skin's declared web fonts so live preview matches the platform (not\n * just video export). SSR-safe: a no-op when `document`/`FontFace` are absent\n * (server render, Remotion Node). Idempotent per family+weight+style.\n *\n * Returns once every face has loaded (or immediately, off the main document).\n */\nexport async function loadSkinFonts(\n fonts: FontDeclaration[] | undefined,\n): Promise<void> {\n if (!fonts || fonts.length === 0) return;\n if (\n typeof document === \"undefined\" ||\n typeof FontFace === \"undefined\" ||\n !document.fonts\n ) {\n return;\n }\n\n const pending: Promise<unknown>[] = [];\n for (const decl of fonts) {\n for (const source of decl.sources) {\n const descriptors: FontFaceDescriptors = {};\n if (source.weight !== undefined)\n descriptors.weight = String(source.weight);\n if (source.style !== undefined) descriptors.style = source.style;\n\n const single: FontDeclaration = { ...decl, sources: [source] };\n const face = new FontFace(decl.family, srcValue(single), descriptors);\n\n // Skip if an identical face is already registered.\n const already = [...document.fonts].some(\n (f) =>\n f.family === decl.family &&\n f.weight === (descriptors.weight ?? \"normal\") &&\n f.style === (descriptors.style ?? \"normal\"),\n );\n if (already) continue;\n\n document.fonts.add(face);\n pending.push(face.load());\n }\n }\n await Promise.all(pending);\n}\n","import type { CSSProperties, ReactElement, ReactNode } from \"react\";\n\n/**\n * Animation primitives are **pure functions of progress** (0..1), not CSS\n * transitions or JS timers — so the React preview and the Remotion render\n * animate identically frame-for-frame (PLAN §7). Skins call these driven by\n * `revealProgress` / typing `progress` from `SimState`.\n */\n\nexport const clamp01 = (x: number): number => (x < 0 ? 0 : x > 1 ? 1 : x);\n\n/** Decelerating ease, good for reveals. */\nexport const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3);\n\n/** Back-ease-out with overshoot, good for pops. Lands exactly on 1 at t=1. */\nexport function backEaseOut(t: number, tension = 2.2): number {\n const c3 = tension + 1;\n return 1 + c3 * Math.pow(t - 1, 3) + tension * Math.pow(t - 1, 2);\n}\n\nexport interface FadeSlideOptions {\n /** Slide distance in px at progress 0 (default 8). */\n distance?: number;\n /** Axis to slide along (default \"y\"). */\n axis?: \"x\" | \"y\";\n easing?: (t: number) => number;\n}\n\n/** Fade + slide-in reveal. `progress` 0 → hidden+offset, 1 → shown+settled. */\nexport function fadeSlideIn(\n progress: number,\n options: FadeSlideOptions = {},\n): CSSProperties {\n const eased = (options.easing ?? easeOutCubic)(clamp01(progress));\n const distance = options.distance ?? 8;\n const offset = (1 - eased) * distance;\n const translate =\n options.axis === \"x\"\n ? `translateX(${offset}px)`\n : `translateY(${offset}px)`;\n return { opacity: eased, transform: translate };\n}\n\nexport interface PopOptions {\n /** Overshoot tension (default 2.2 ≈ ~10% overshoot). */\n tension?: number;\n}\n\n/** Scale pop-in (with a little overshoot), good for reactions landing. */\nexport function popIn(\n progress: number,\n options: PopOptions = {},\n): CSSProperties {\n const p = clamp01(progress);\n const scale = backEaseOut(p, options.tension ?? 2.2);\n return {\n opacity: clamp01(p * 3),\n transform: `scale(${scale})`,\n transformOrigin: \"center\",\n };\n}\n\nexport interface TypingDotsProps {\n /** 0..1 progress through the indicator's shown duration. */\n progress?: number;\n count?: number;\n /** Bounce cycles across the full progress (default 4). */\n cycles?: number;\n /** Dot color (default `currentColor`). */\n color?: string;\n /** Dot diameter in px (default 6). */\n size?: number;\n /** Gap between dots in px (default 4). */\n gap?: number;\n}\n\n/**\n * The three-dot bouncing typing indicator, animated purely from `progress`\n * (deterministic per frame). Skins style it via props or wrap it.\n */\nexport function TypingDots({\n progress = 0,\n count = 3,\n cycles = 4,\n color = \"currentColor\",\n size = 6,\n gap = 4,\n}: TypingDotsProps): ReactElement {\n const phase = clamp01(progress) * cycles * Math.PI * 2;\n const dots: ReactNode[] = [];\n for (let i = 0; i < count; i++) {\n const wave = Math.sin(phase - i * 0.9);\n const lift = Math.max(0, wave) * 4;\n const opacity = 0.4 + Math.max(0, wave) * 0.6;\n dots.push(\n <span\n key={i}\n style={{\n width: size,\n height: size,\n borderRadius: \"50%\",\n background: color,\n transform: `translateY(${-lift}px)`,\n opacity,\n }}\n />,\n );\n }\n return (\n <span style={{ display: \"inline-flex\", alignItems: \"flex-end\", gap }}>\n {dots}\n </span>\n );\n}\n","import type { CSSProperties, ReactElement, ReactNode } from \"react\";\nimport type {\n ContentNode,\n ImageNode,\n InlineNode,\n TextNode,\n} from \"@typecaast/schema\";\n\nexport interface ContentClassNames {\n text?: string;\n link?: string;\n mention?: string;\n code?: string;\n emoji?: string;\n image?: string;\n}\n\n/** Per-mark inline styles, so skins can theme marks without a CSS file. */\nexport interface ContentStyles {\n text?: CSSProperties;\n link?: CSSProperties;\n mention?: CSSProperties;\n code?: CSSProperties;\n emoji?: CSSProperties;\n}\n\nexport interface MessageContentProps {\n nodes: ContentNode[];\n /** Per-mark class names so skins style marks with their own CSS. */\n classNames?: ContentClassNames;\n /** Per-mark inline styles (merged with the defaults). */\n styles?: ContentStyles;\n /** Extra style for in-message images (skins set radius, max size, etc.). */\n imageStyle?: CSSProperties;\n}\n\nfunction renderInline(\n span: InlineNode,\n key: number,\n cn: ContentClassNames,\n st: ContentStyles,\n): ReactNode {\n switch (span.type) {\n case \"text\":\n return span.value;\n case \"code\":\n return (\n <code key={key} data-tc-mark=\"code\" className={cn.code} style={st.code}>\n {span.value}\n </code>\n );\n case \"link\":\n return (\n <a\n key={key}\n data-tc-mark=\"link\"\n className={cn.link}\n style={st.link}\n href={span.href}\n rel=\"noreferrer\"\n >\n {span.label ?? span.href}\n </a>\n );\n case \"mention\":\n return (\n <span\n key={key}\n data-tc-mark=\"mention\"\n className={cn.mention}\n style={st.mention}\n >\n {span.label}\n </span>\n );\n case \"emoji\":\n return (\n <span\n key={key}\n data-tc-mark=\"emoji\"\n className={cn.emoji}\n style={st.emoji}\n >\n {span.value}\n </span>\n );\n }\n}\n\nfunction renderImage(\n node: ImageNode,\n key: number,\n cn: ContentClassNames,\n imageStyle?: CSSProperties,\n): ReactNode {\n return (\n <img\n key={key}\n data-tc-node=\"image\"\n className={cn.image}\n src={node.src}\n alt={node.alt ?? \"\"}\n width={node.width}\n height={node.height}\n style={{ maxWidth: \"100%\", display: \"block\", ...imageStyle }}\n />\n );\n}\n\n/**\n * Render a message body (`ContentNode[]`) to React: text nodes with inline\n * marks (code/link/mention/emoji) and in-message images. Unknown node types are\n * skipped (forward-compatible — PLAN §6). SSR-safe, so it renders identically\n * in the browser and in Remotion's Node renderer. Skins style via `classNames`.\n */\nexport function MessageContent({\n nodes,\n classNames = {},\n styles = {},\n imageStyle,\n}: MessageContentProps): ReactElement {\n return (\n <>\n {nodes.map((node, i) => {\n if (node.type === \"text\") {\n const text = node as TextNode;\n return (\n <span\n key={i}\n data-tc-node=\"text\"\n className={classNames.text}\n style={styles.text}\n >\n {text.spans.map((span, j) =>\n renderInline(span, j, classNames, styles),\n )}\n </span>\n );\n }\n if (node.type === \"image\") {\n return renderImage(node as ImageNode, i, classNames, imageStyle);\n }\n return null; // unknown future node type — skipped\n })}\n </>\n );\n}\n","import { useMemo, type ReactElement } from \"react\";\nimport type { ComposerMode, Participant } from \"@typecaast/schema\";\nimport type { SimState } from \"@typecaast/core\";\nimport { ThemeProvider } from \"./theme.js\";\nimport type { Skin } from \"./types.js\";\n\nexport type { ComposerMode };\n\n// A subtle, native-feeling scrollbar for the scrollable thread, scoped to the\n// thread viewport and injected inline so the embed needs no external stylesheet.\n// A neutral translucent grey reads on both light and dark skins and deepens on\n// hover; WebKit/Blink get the thin overlay-style thumb, Firefox uses the\n// standard `scrollbar-*` props. The selector is global on purpose — identical\n// rules across multiple embeds on a page are harmless and keep them consistent.\nconst THREAD_SCROLLBAR_CSS = `\n[data-typecaast-thread]{scrollbar-width:thin;scrollbar-color:rgba(128,128,128,.4) transparent}\n[data-typecaast-thread]::-webkit-scrollbar{width:8px;height:8px}\n[data-typecaast-thread]::-webkit-scrollbar-track{background:transparent}\n[data-typecaast-thread]::-webkit-scrollbar-thumb{background-color:rgba(128,128,128,.4);border-radius:8px;border:2px solid transparent;background-clip:padding-box}\n[data-typecaast-thread]:hover::-webkit-scrollbar-thumb{background-color:rgba(128,128,128,.6)}\n`;\n\nexport interface TypecaastStageProps {\n state: SimState;\n skin: Skin;\n participants: Participant[];\n /** Skin-specific options from `meta.skin.options`. */\n options?: Record<string, unknown>;\n /**\n * Composer visibility: `auto` (default) shows it only while someone is\n * typing/sending; `always` keeps the reply box visible (idle = placeholder);\n * `never` hides it.\n */\n composer?: ComposerMode;\n}\n\n/**\n * Maps a `SimState` onto a skin's components: a `Frame` wrapping the thread\n * items (Message / SystemMessage), the typing indicators, and the composer.\n * Reactions render inside the skin's `Message` (it reads `message.reactions`).\n *\n * Lives in `skin-kit` (the contract layer) so both `@typecaast/react` and the\n * skins' own stories can render a frame without depending on the React player.\n */\nexport function TypecaastStage({\n state,\n skin,\n participants,\n options,\n composer = \"auto\",\n}: TypecaastStageProps): ReactElement {\n const theme = state.theme;\n const byId = useMemo(() => {\n const map = new Map<string, Participant>();\n for (const p of participants) map.set(p.id, p);\n return map;\n }, [participants]);\n\n const { Frame, Message, SystemMessage, TypingIndicator, Composer } =\n skin.components;\n const tokens = skin.tokens?.[theme];\n // `always` keeps the reply box mounted even when idle (falls back to the self\n // participant so a placeholder shows); `auto` only shows it while composing.\n const selfParticipant = useMemo(\n () => participants.find((p) => p.isSelf),\n [participants],\n );\n const composerAuthor = state.composer.from\n ? byId.get(state.composer.from)\n : composer === \"always\"\n ? selfParticipant\n : undefined;\n const showComposer = composer !== \"never\" && composerAuthor !== undefined;\n\n // \"X is typing…\" indicators, minus the viewer's own (you never see yourself\n // typing — that's what the composer shows). Placement is skin-driven: inline\n // in the thread by default, or below the composer (Slack).\n const typingPlacement = skin.meta.typingPlacement ?? \"thread\";\n const typingNodes = state.typingIndicators\n .map((typing, i) => {\n const author = byId.get(typing.from);\n if (!author || author.isSelf) return null;\n return (\n <TypingIndicator\n key={`typing-${typing.from}-${i}`}\n theme={theme}\n typing={typing}\n author={author}\n />\n );\n })\n .filter(Boolean);\n\n return (\n <ThemeProvider theme={theme} tokens={tokens}>\n <style>{THREAD_SCROLLBAR_CSS}</style>\n <Frame theme={theme} options={options} composer={composer}>\n {/* Thread viewport. `column-reverse` pins the conversation to the\n bottom (newest message + composer sit at the bottom, older items\n \"shift up\", PLAN §7) and, once the thread outgrows the height, makes\n it scroll from the bottom with the top reachable — entirely in CSS,\n so it renders identically in a live embed, an SSR page, and a video\n frame (no scroll-to-bottom effect, which wouldn't run before a\n Remotion screenshot). Because `column-reverse` lays the first child\n out at the bottom, children render newest-first: the typing\n indicator (most recent activity) first, then messages reversed. The\n engine's scroll.targetOffset is honored here once real layout\n measurement lands. */}\n <div\n data-typecaast-thread=\"\"\n style={{\n display: \"flex\",\n flexDirection: \"column-reverse\",\n flex: \"1 1 auto\",\n minHeight: 0,\n overflowY: \"auto\",\n // Scrollbar styling lives in THREAD_SCROLLBAR_CSS (the `<style>`\n // above) so it can reach the WebKit pseudo-elements.\n // Breathing room beneath the last message — keeps it off the\n // composer (when shown) and the Frame's bottom edge (when hidden).\n paddingBottom: 16,\n }}\n >\n {typingPlacement === \"thread\" ? typingNodes : null}\n {state.messages\n .map((message, i) => {\n const author = byId.get(message.from);\n if (!author) return null;\n // Index-disambiguated so a config with duplicate message ids can't\n // collide React keys (the builder can produce them transiently).\n const key = `${message.id}-${i}`;\n if (message.variant === \"system\") {\n return (\n <SystemMessage\n key={key}\n theme={theme}\n message={message}\n author={author}\n />\n );\n }\n // Grouping looks at the chronological predecessor — computed\n // before the reverse below, so author-collapsing is unaffected.\n const prev = state.messages[i - 1];\n const previousAuthor = prev ? byId.get(prev.from) : undefined;\n return (\n <Message\n key={key}\n theme={theme}\n message={message}\n author={author}\n previousAuthor={previousAuthor}\n />\n );\n })\n .reverse()}\n </div>\n {showComposer ? (\n <Composer\n theme={theme}\n composer={state.composer}\n author={composerAuthor}\n options={options}\n />\n ) : null}\n {typingPlacement === \"below-composer\" ? typingNodes : null}\n </Frame>\n </ThemeProvider>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/define-skin.ts","../src/theme.tsx","../src/fonts.ts","../src/animation.tsx","../src/content.tsx","../src/stage.tsx","../src/slot-skin.tsx"],"names":["createContext","jsx","useContext","Fragment","useMemo","jsxs","useRef","useState","useLayoutEffect","createPortal"],"mappings":";;;;;;;AAOO,SAAS,WAAW,IAAA,EAAkB;AAC3C,EAAA,OAAO,IAAA;AACT;ACKA,IAAM,YAAA,GAAeA,mBAAA,CAAiC,EAAE,KAAA,EAAO,SAAS,CAAA;AAUjE,SAAS,aAAA,CAAc;AAAA,EAC5B,KAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAAqC;AACnC,EAAA,uBACEC,cAAA,CAAC,aAAa,QAAA,EAAb,EAAsB,OAAO,EAAE,KAAA,EAAO,MAAA,EAAO,EAC3C,QAAA,EACH,CAAA;AAEJ;AAGO,SAAS,QAAA,GAA0B;AACxC,EAAA,OAAOC,gBAAA,CAAW,YAAY,CAAA,CAAE,KAAA;AAClC;AAGO,SAAS,SAAA,GAAoC;AAClD,EAAA,OAAOA,gBAAA,CAAW,YAAY,CAAA,CAAE,MAAA;AAClC;;;ACzCA,SAAS,SAAS,IAAA,EAA+B;AAC/C,EAAA,OAAO,IAAA,CAAK,OAAA,CACT,GAAA,CAAI,CAAC,CAAA,KAAM;AACV,IAAA,MAAM,SAAS,CAAA,CAAE,MAAA,GAAS,CAAA,SAAA,EAAY,CAAA,CAAE,MAAM,CAAA,EAAA,CAAA,GAAO,EAAA;AACrD,IAAA,OAAO,CAAA,KAAA,EAAQ,CAAA,CAAE,GAAG,CAAA,EAAA,EAAK,MAAM,CAAA,CAAA;AAAA,EACjC,CAAC,CAAA,CACA,IAAA,CAAK,IAAI,CAAA;AACd;AASA,eAAsB,cACpB,KAAA,EACe;AACf,EAAA,IAAI,CAAC,KAAA,IAAS,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG;AAClC,EAAA,IACE,OAAO,aAAa,WAAA,IACpB,OAAO,aAAa,WAAA,IACpB,CAAC,SAAS,KAAA,EACV;AACA,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,UAA8B,EAAC;AACrC,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,KAAA,MAAW,MAAA,IAAU,KAAK,OAAA,EAAS;AACjC,MAAA,MAAM,cAAmC,EAAC;AAC1C,MAAA,IAAI,OAAO,MAAA,KAAW,MAAA;AACpB,QAAA,WAAA,CAAY,MAAA,GAAS,MAAA,CAAO,MAAA,CAAO,MAAM,CAAA;AAC3C,MAAA,IAAI,MAAA,CAAO,KAAA,KAAU,MAAA,EAAW,WAAA,CAAY,QAAQ,MAAA,CAAO,KAAA;AAE3D,MAAA,MAAM,SAA0B,EAAE,GAAG,MAAM,OAAA,EAAS,CAAC,MAAM,CAAA,EAAE;AAC7D,MAAA,MAAM,IAAA,GAAO,IAAI,QAAA,CAAS,IAAA,CAAK,QAAQ,QAAA,CAAS,MAAM,GAAG,WAAW,CAAA;AAGpE,MAAA,MAAM,OAAA,GAAU,CAAC,GAAG,QAAA,CAAS,KAAK,CAAA,CAAE,IAAA;AAAA,QAClC,CAAC,CAAA,KACC,CAAA,CAAE,MAAA,KAAW,KAAK,MAAA,IAClB,CAAA,CAAE,MAAA,MAAY,WAAA,CAAY,MAAA,IAAU,QAAA,CAAA,IACpC,CAAA,CAAE,KAAA,MAAW,YAAY,KAAA,IAAS,QAAA;AAAA,OACtC;AACA,MAAA,IAAI,OAAA,EAAS;AAEb,MAAA,QAAA,CAAS,KAAA,CAAM,IAAI,IAAI,CAAA;AACvB,MAAA,OAAA,CAAQ,IAAA,CAAK,IAAA,CAAK,IAAA,EAAM,CAAA;AAAA,IAC1B;AAAA,EACF;AACA,EAAA,MAAM,OAAA,CAAQ,IAAI,OAAO,CAAA;AAC3B;AC/CO,IAAM,OAAA,GAAU,CAAC,CAAA,KAAuB,CAAA,GAAI,IAAI,CAAA,GAAI,CAAA,GAAI,IAAI,CAAA,GAAI;AAGhE,IAAM,YAAA,GAAe,CAAC,CAAA,KAAsB,CAAA,GAAI,KAAK,GAAA,CAAI,CAAA,GAAI,GAAG,CAAC;AAGjE,SAAS,WAAA,CAAY,CAAA,EAAW,OAAA,GAAU,GAAA,EAAa;AAC5D,EAAA,MAAM,KAAK,OAAA,GAAU,CAAA;AACrB,EAAA,OAAO,CAAA,GAAI,EAAA,GAAK,IAAA,CAAK,GAAA,CAAI,CAAA,GAAI,CAAA,EAAG,CAAC,CAAA,GAAI,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,CAAA,GAAI,GAAG,CAAC,CAAA;AAClE;AAWO,SAAS,WAAA,CACd,QAAA,EACA,OAAA,GAA4B,EAAC,EACd;AACf,EAAA,MAAM,SAAS,OAAA,CAAQ,MAAA,IAAU,YAAA,EAAc,OAAA,CAAQ,QAAQ,CAAC,CAAA;AAChE,EAAA,MAAM,QAAA,GAAW,QAAQ,QAAA,IAAY,CAAA;AACrC,EAAA,MAAM,MAAA,GAAA,CAAU,IAAI,KAAA,IAAS,QAAA;AAM7B,EAAA,IAAI,WAAW,CAAA,EAAG,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,WAAW,MAAA,EAAO;AAC7D,EAAA,MAAM,SAAA,GACJ,QAAQ,IAAA,KAAS,GAAA,GACb,cAAc,MAAM,CAAA,GAAA,CAAA,GACpB,cAAc,MAAM,CAAA,GAAA,CAAA;AAC1B,EAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,SAAA,EAAW,SAAA,EAAU;AAChD;AAQO,SAAS,KAAA,CACd,QAAA,EACA,OAAA,GAAsB,EAAC,EACR;AACf,EAAA,MAAM,CAAA,GAAI,QAAQ,QAAQ,CAAA;AAC1B,EAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,CAAA,EAAG,OAAA,CAAQ,WAAW,GAAG,CAAA;AACnD,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,OAAA,CAAQ,CAAA,GAAI,CAAC,CAAA;AAAA,IACtB,SAAA,EAAW,SAAS,KAAK,CAAA,CAAA,CAAA;AAAA,IACzB,eAAA,EAAiB;AAAA,GACnB;AACF;AAoBO,SAAS,UAAA,CAAW;AAAA,EACzB,QAAA,GAAW,CAAA;AAAA,EACX,KAAA,GAAQ,CAAA;AAAA,EACR,MAAA,GAAS,CAAA;AAAA,EACT,KAAA,GAAQ,cAAA;AAAA,EACR,IAAA,GAAO,CAAA;AAAA,EACP,GAAA,GAAM;AACR,CAAA,EAAkC;AAChC,EAAA,MAAM,QAAQ,OAAA,CAAQ,QAAQ,CAAA,GAAI,MAAA,GAAS,KAAK,EAAA,GAAK,CAAA;AACrD,EAAA,MAAM,OAAoB,EAAC;AAC3B,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,EAAO,CAAA,EAAA,EAAK;AAC9B,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,KAAA,GAAQ,IAAI,GAAG,CAAA;AACrC,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAI,CAAA,GAAI,CAAA;AACjC,IAAA,MAAM,UAAU,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAI,CAAA,GAAI,GAAA;AAC1C,IAAA,IAAA,CAAK,IAAA;AAAA,sBACHD,cAAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UAEC,KAAA,EAAO;AAAA,YACL,KAAA,EAAO,IAAA;AAAA,YACP,MAAA,EAAQ,IAAA;AAAA,YACR,YAAA,EAAc,KAAA;AAAA,YACd,UAAA,EAAY,KAAA;AAAA,YACZ,SAAA,EAAW,CAAA,WAAA,EAAc,CAAC,IAAI,CAAA,GAAA,CAAA;AAAA,YAC9B;AAAA;AACF,SAAA;AAAA,QARK;AAAA;AASP,KACF;AAAA,EACF;AACA,EAAA,uBACEA,cAAAA,CAAC,MAAA,EAAA,EAAK,KAAA,EAAO,EAAE,OAAA,EAAS,aAAA,EAAe,UAAA,EAAY,UAAA,EAAY,GAAA,EAAI,EAChE,QAAA,EAAA,IAAA,EACH,CAAA;AAEJ;ACnFA,SAAS,YAAA,CACP,IAAA,EACA,GAAA,EACA,EAAA,EACA,EAAA,EACW;AACX,EAAA,QAAQ,KAAK,IAAA;AAAM,IACjB,KAAK,MAAA;AACH,MAAA,OAAO,IAAA,CAAK,KAAA;AAAA,IACd,KAAK,MAAA;AACH,MAAA,uBACEA,cAAAA,CAAC,MAAA,EAAA,EAAe,cAAA,EAAa,MAAA,EAAO,SAAA,EAAW,EAAA,CAAG,IAAA,EAAM,KAAA,EAAO,EAAA,CAAG,IAAA,EAC/D,QAAA,EAAA,IAAA,CAAK,SADG,GAEX,CAAA;AAAA,IAEJ,KAAK,MAAA;AACH,MAAA,uBACEA,cAAAA;AAAA,QAAC,GAAA;AAAA,QAAA;AAAA,UAEC,cAAA,EAAa,MAAA;AAAA,UACb,WAAW,EAAA,CAAG,IAAA;AAAA,UACd,OAAO,EAAA,CAAG,IAAA;AAAA,UACV,MAAM,IAAA,CAAK,IAAA;AAAA,UACX,GAAA,EAAI,YAAA;AAAA,UAEH,QAAA,EAAA,IAAA,CAAK,SAAS,IAAA,CAAK;AAAA,SAAA;AAAA,QAPf;AAAA,OAQP;AAAA,IAEJ,KAAK,SAAA;AACH,MAAA,uBACEA,cAAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UAEC,cAAA,EAAa,SAAA;AAAA,UACb,WAAW,EAAA,CAAG,OAAA;AAAA,UACd,OAAO,EAAA,CAAG,OAAA;AAAA,UAET,QAAA,EAAA,IAAA,CAAK;AAAA,SAAA;AAAA,QALD;AAAA,OAMP;AAAA,IAEJ,KAAK,OAAA;AACH,MAAA,uBACEA,cAAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UAEC,cAAA,EAAa,OAAA;AAAA,UACb,WAAW,EAAA,CAAG,KAAA;AAAA,UACd,OAAO,EAAA,CAAG,KAAA;AAAA,UAET,QAAA,EAAA,IAAA,CAAK;AAAA,SAAA;AAAA,QALD;AAAA,OAMP;AAAA;AAGR;AAEA,SAAS,WAAA,CACP,IAAA,EACA,GAAA,EACA,EAAA,EACA,UAAA,EACW;AACX,EAAA,uBACEA,cAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MAEC,cAAA,EAAa,OAAA;AAAA,MACb,WAAW,EAAA,CAAG,KAAA;AAAA,MACd,KAAK,IAAA,CAAK,GAAA;AAAA,MACV,GAAA,EAAK,KAAK,GAAA,IAAO,EAAA;AAAA,MACjB,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,OAAO,EAAE,QAAA,EAAU,QAAQ,OAAA,EAAS,OAAA,EAAS,GAAG,UAAA;AAAW,KAAA;AAAA,IAPtD;AAAA,GAQP;AAEJ;AAQO,SAAS,cAAA,CAAe;AAAA,EAC7B,KAAA;AAAA,EACA,aAAa,EAAC;AAAA,EACd,SAAS,EAAC;AAAA,EACV;AACF,CAAA,EAAsC;AACpC,EAAA,uBACEA,cAAAA,CAAAE,mBAAA,EAAA,EACG,gBAAM,GAAA,CAAI,CAAC,MAAM,CAAA,KAAM;AACtB,IAAA,IAAI,IAAA,CAAK,SAAS,MAAA,EAAQ;AACxB,MAAA,MAAM,IAAA,GAAO,IAAA;AACb,MAAA,uBACEF,cAAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UAEC,cAAA,EAAa,MAAA;AAAA,UACb,WAAW,UAAA,CAAW,IAAA;AAAA,UACtB,OAAO,MAAA,CAAO,IAAA;AAAA,UAEb,eAAK,KAAA,CAAM,GAAA;AAAA,YAAI,CAAC,IAAA,EAAM,CAAA,KACrB,aAAa,IAAA,EAAM,CAAA,EAAG,YAAY,MAAM;AAAA;AAC1C,SAAA;AAAA,QAPK;AAAA,OAQP;AAAA,IAEJ;AACA,IAAA,IAAI,IAAA,CAAK,SAAS,OAAA,EAAS;AACzB,MAAA,OAAO,WAAA,CAAY,IAAA,EAAmB,CAAA,EAAG,UAAA,EAAY,UAAU,CAAA;AAAA,IACjE;AACA,IAAA,OAAO,IAAA;AAAA,EACT,CAAC,CAAA,EACH,CAAA;AAEJ;ACpIA,IAAM,oBAAA,GAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AA8BtB,SAAS,cAAA,CAAe;AAAA,EAC7B,KAAA;AAAA,EACA,IAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA,GAAW;AACb,CAAA,EAAsC;AACpC,EAAA,MAAM,QAAQ,KAAA,CAAM,KAAA;AACpB,EAAA,MAAM,IAAA,GAAOG,cAAQ,MAAM;AACzB,IAAA,MAAM,GAAA,uBAAU,GAAA,EAAyB;AACzC,IAAA,KAAA,MAAW,KAAK,YAAA,EAAc,GAAA,CAAI,GAAA,CAAI,CAAA,CAAE,IAAI,CAAC,CAAA;AAC7C,IAAA,OAAO,GAAA;AAAA,EACT,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAEjB,EAAA,MAAM,EAAE,KAAA,EAAO,OAAA,EAAS,eAAe,eAAA,EAAiB,QAAA,KACtD,IAAA,CAAK,UAAA;AACP,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,GAAS,KAAK,CAAA;AAGlC,EAAA,MAAM,eAAA,GAAkBA,aAAA;AAAA,IACtB,MAAM,YAAA,CAAa,IAAA,CAAK,CAAC,CAAA,KAAM,EAAE,MAAM,CAAA;AAAA,IACvC,CAAC,YAAY;AAAA,GACf;AACA,EAAA,MAAM,cAAA,GAAiB,KAAA,CAAM,QAAA,CAAS,IAAA,GAClC,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,QAAA,CAAS,IAAI,CAAA,GAC5B,QAAA,KAAa,QAAA,GACX,eAAA,GACA,MAAA;AACN,EAAA,MAAM,YAAA,GAAe,QAAA,KAAa,OAAA,IAAW,cAAA,KAAmB,MAAA;AAKhE,EAAA,MAAM,eAAA,GAAkB,IAAA,CAAK,IAAA,CAAK,eAAA,IAAmB,QAAA;AACrD,EAAA,MAAM,cAAc,KAAA,CAAM,gBAAA,CACvB,GAAA,CAAI,CAAC,QAAQ,CAAA,KAAM;AAClB,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA;AACnC,IAAA,IAAI,CAAC,MAAA,IAAU,MAAA,CAAO,MAAA,EAAQ,OAAO,IAAA;AACrC,IAAA,uBACEH,cAAAA;AAAA,MAAC,eAAA;AAAA,MAAA;AAAA,QAEC,KAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA,OAAA;AAAA,MAHK,CAAA,OAAA,EAAU,MAAA,CAAO,IAAI,CAAA,CAAA,EAAI,CAAC,CAAA;AAAA,KAIjC;AAAA,EAEJ,CAAC,CAAA,CACA,MAAA,CAAO,OAAO,CAAA;AAEjB,EAAA,uBACEI,eAAA,CAAC,aAAA,EAAA,EAAc,KAAA,EAAc,MAAA,EAC3B,QAAA,EAAA;AAAA,oBAAAJ,cAAAA,CAAC,WAAO,QAAA,EAAA,oBAAA,EAAqB,CAAA;AAAA,oBAC7BI,eAAA,CAAC,KAAA,EAAA,EAAM,KAAA,EAAc,OAAA,EAAkB,QAAA,EAYrC,QAAA,EAAA;AAAA,sBAAAA,eAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,uBAAA,EAAsB,EAAA;AAAA,UACtB,KAAA,EAAO;AAAA,YACL,OAAA,EAAS,MAAA;AAAA,YACT,aAAA,EAAe,gBAAA;AAAA,YACf,IAAA,EAAM,UAAA;AAAA,YACN,SAAA,EAAW,CAAA;AAAA,YACX,SAAA,EAAW,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAKX,aAAA,EAAe;AAAA,WACjB;AAAA,UAEC,QAAA,EAAA;AAAA,YAAA,eAAA,KAAoB,WAAW,WAAA,GAAc,IAAA;AAAA,YAC7C,KAAA,CAAM,QAAA,CACJ,GAAA,CAAI,CAAC,SAAS,CAAA,KAAM;AACnB,cAAA,MAAM,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA;AACpC,cAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AAGpB,cAAA,MAAM,GAAA,GAAM,CAAA,EAAG,OAAA,CAAQ,EAAE,IAAI,CAAC,CAAA,CAAA;AAC9B,cAAA,IAAI,OAAA,CAAQ,YAAY,QAAA,EAAU;AAChC,gBAAA,uBACEJ,cAAAA;AAAA,kBAAC,aAAA;AAAA,kBAAA;AAAA,oBAEC,KAAA;AAAA,oBACA,OAAA;AAAA,oBACA;AAAA,mBAAA;AAAA,kBAHK;AAAA,iBAIP;AAAA,cAEJ;AAGA,cAAA,MAAM,IAAA,GAAO,KAAA,CAAM,QAAA,CAAS,CAAA,GAAI,CAAC,CAAA;AACjC,cAAA,MAAM,iBAAiB,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,GAAI,MAAA;AACpD,cAAA,uBACEA,cAAAA;AAAA,gBAAC,OAAA;AAAA,gBAAA;AAAA,kBAEC,KAAA;AAAA,kBACA,OAAA;AAAA,kBACA,MAAA;AAAA,kBACA;AAAA,iBAAA;AAAA,gBAJK;AAAA,eAKP;AAAA,YAEJ,CAAC,EACA,OAAA;AAAQ;AAAA;AAAA,OACb;AAAA,MACC,+BACCA,cAAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,KAAA;AAAA,UACA,UAAU,KAAA,CAAM,QAAA;AAAA,UAChB,MAAA,EAAQ,cAAA;AAAA,UACR;AAAA;AAAA,OACF,GACE,IAAA;AAAA,MACH,eAAA,KAAoB,mBAAmB,WAAA,GAAc;AAAA,KAAA,EACxD;AAAA,GAAA,EACF,CAAA;AAEJ;AC7HA,IAAM,SAAA,GAAY,cAAA;AAoClB,SAAS,cAAc,OAAA,EAAgC;AACrD,EAAA,MAAM,MAAgB,EAAC;AACvB,EAAA,KAAA,MAAW,QAAQ,OAAA,EAAS;AAC1B,IAAA,IACE,KAAK,IAAA,KAAS,MAAA,IACd,MAAM,OAAA,CAAS,IAAA,CAA6B,KAAK,CAAA,EACjD;AACA,MAAA,KAAA,MAAW,IAAA,IACT,KACA,KAAA,EAAO;AACP,QAAA,GAAA,CAAI,IAAA,CAAK,IAAA,CAAK,KAAA,IAAS,IAAA,CAAK,SAAS,EAAE,CAAA;AAAA,MACzC;AAAA,IACF,CAAA,MAAA,IAAW,IAAA,CAAK,IAAA,KAAS,OAAA,EAAS;AAChC,MAAA,GAAA,CAAI,IAAA,CAAM,IAAA,CAA0B,GAAA,IAAO,WAAI,CAAA;AAAA,IACjD;AAAA,EACF;AACA,EAAA,OAAO,GAAA,CAAI,KAAK,EAAE,CAAA;AACpB;AAEA,SAAS,SAAS,IAAA,EAAsB;AACtC,EAAA,OAAO,IAAA,CACJ,MAAM,KAAK,CAAA,CACX,OAAO,OAAO,CAAA,CACd,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA,CACV,IAAI,CAAC,CAAA,KAAM,EAAE,CAAC,CAAA,EAAG,aAAY,IAAK,EAAE,CAAA,CACpC,IAAA,CAAK,EAAE,CAAA;AACZ;AAEA,SAAS,QAAQ,IAAA,EAAsB;AACrC,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,IAAA,GAAO,GAAI,CAAA;AACpC,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,EAAE,CAAA;AAC/B,EAAA,MAAM,IAAI,KAAA,GAAQ,EAAA;AAClB,EAAA,OAAO,CAAA,EAAG,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,CAAC,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA;AAC3C;AAWA,SAAS,SAAA,CACP,MAAA,EACA,GAAA,EACA,UAAA,EACA,WAAA,EACQ;AACR,EAAA,MAAM,IAAA,GAAO,OAAO,OAAA,CAAQ,MAAA,CAAO,UAAU,EAAE,EAC5C,GAAA,CAAI,CAAC,CAAC,CAAA,EAAG,CAAC,MAAM,CAAA,EAAA,EAAK,CAAC,KAAK,CAAC,CAAA,CAAA,CAAG,CAAA,CAC/B,IAAA,CAAK,GAAG,CAAA;AACX,EAAA,MAAM,KAAK,UAAA,EAAY,aAAA,GACnB,CAAA,2BAAA,EAA8B,UAAA,CAAW,aAAa,CAAA,GAAA,CAAA,GACtD,EAAA;AACJ,EAAA,MAAM,KAAA,GAAQ;AAAA,mEAAA,EACqD,IAAI,IAAI,EAAE,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAAA,CAAA;AAS7E,EAAA,MAAM,UAAU,WAAA,GACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAAA,CAAA,GAoBA,EAAA;AACJ,EAAA,OAAO,GAAG,KAAK;AAAA,EAAK,GAAG;AAAA,EAAK,OAAO,CAAA,CAAA;AACrC;AAEA,SAAS,QAAA,CACP,IAAA,EACA,YAAA,EACA,MAAA,EACM;AACN,EAAA,IAAA,CAAK,SAAA,GAAY,YAAA;AACjB,EAAA,KAAA,MAAW,QAAQ,IAAA,CAAK,gBAAA,CAAiB,CAAA,CAAA,EAAI,SAAS,GAAG,CAAA,EAAG;AAC1D,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,YAAA,CAAa,SAAS,CAAA;AACxC,IAAA,IAAI,QAAQ,IAAA,IAAQ,MAAA,OAAa,WAAA,GAAc,MAAA,CAAO,IAAI,CAAA,IAAK,EAAA;AAAA,EACjE;AACF;AAEA,SAAS,YAAY,QAAA,EAAiC;AACpD,EAAA,MAAM,CAAA,GAAI,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,CAAA,EAAG,QAAQ,CAAC,CAAA;AAC3C,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,CAAA;AAAA,IACT,SAAA,EAAW,CAAA,WAAA,EAAA,CAAe,CAAA,GAAI,CAAA,IAAK,CAAC,CAAA,GAAA,CAAA;AAAA,IACpC,UAAA,EAAY;AAAA,GACd;AACF;AAEO,SAAS,iBAAA,CACd,OACA,IAAA,EACM;AACN,EAAA,MAAM,cAA0B,EAAE,MAAA,EAAQ,MAAM,MAAA,CAAO,MAAA,IAAU,EAAC,EAAE;AACpE,EAAA,MAAM,UAAA,GAAyB,KAAA,CAAM,UAAA,GACjC,EAAE,MAAA,EAAQ,MAAM,UAAA,CAAW,MAAA,IAAU,EAAC,EAAE,GACxC,WAAA;AACJ,EAAA,MAAM,WAAA,GAAc,KAAK,WAAA,IAAe,KAAA;AACxC,EAAA,MAAM,UAAA,GAAa;AAAA,IACjB,KAAA,EAAO,SAAA;AAAA,MACL,WAAA;AAAA,MACA,MAAM,GAAA,IAAO,EAAA;AAAA,MACb,MAAM,IAAA,CAAK,UAAA;AAAA,MACX;AAAA,KACF;AAAA,IACA,IAAA,EAAM,SAAA;AAAA,MACJ,UAAA;AAAA,MACA,MAAM,GAAA,IAAO,EAAA;AAAA,MACb,MAAM,IAAA,CAAK,UAAA;AAAA,MACX;AAAA;AACF,GACF;AACA,EAAA,MAAM,cAAA,GAAuC,KAAA,CAAM,UAAA,GAC/C,CAAC,OAAA,EAAS,MAAM,CAAA,GAChB,CAAC,KAAA,CAAM,IAAA,CAAK,KAAA,IAAS,OAAO,CAAA;AAEhC,EAAA,MAAM,SAAA,GAAY,MAAM,KAAA,CAAM,KAAA;AAC9B,EAAA,MAAM,WAAA,GAAc,MAAM,KAAA,CAAM,OAAA;AAChC,EAAA,MAAM,YAAA,GAAe,MAAM,KAAA,CAAM,QAAA;AACjC,EAAA,MAAM,UAAA,GAAa,MAAM,KAAA,CAAM,MAAA;AAC/B,EAAA,MAAM,UAAA,GAAa,MAAM,KAAA,CAAM,MAAA;AAE/B,EAAA,MAAM,QAAmD,CAAC;AAAA,IACxD,KAAA;AAAA,IACA;AAAA,GACF,KAAM;AACJ,IAAA,MAAM,OAAA,GAAUK,aAAuB,IAAI,CAAA;AAC3C,IAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIC,eAA6B,IAAI,CAAA;AAC3D,IAAAC,qBAAA,CAAgB,MAAM;AACpB,MAAA,MAAM,OAAO,OAAA,CAAQ,OAAA;AACrB,MAAA,IAAI,CAAC,IAAA,EAAM;AACX,MAAA,MAAM,MAAA,GAAS,KAAK,UAAA,IAAc,IAAA,CAAK,aAAa,EAAE,IAAA,EAAM,QAAQ,CAAA;AACpE,MAAA,MAAA,CAAO,SAAA,GAAY,EAAA;AACnB,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,aAAA,CAAc,aAAA,CAAc,OAAO,CAAA;AACtD,MAAA,KAAA,CAAM,WAAA,GAAc,KAAA,KAAU,MAAA,GAAS,UAAA,CAAW,OAAO,UAAA,CAAW,KAAA;AACpE,MAAA,MAAA,CAAO,YAAY,KAAK,CAAA;AAIxB,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,aAAA,CAAc,aAAA,CAAc,KAAK,CAAA;AACtD,MAAA,OAAA,CAAQ,SAAA,GAAY,cAAA;AACpB,MAAA,OAAA,CAAQ,MAAM,KAAA,GAAQ,MAAA;AACtB,MAAA,OAAA,CAAQ,MAAM,MAAA,GAAS,MAAA;AACvB,MAAA,OAAA,CAAQ,MAAM,OAAA,GAAU,MAAA;AACxB,MAAA,OAAA,CAAQ,MAAM,aAAA,GAAgB,QAAA;AAC9B,MAAA,OAAA,CAAQ,MAAM,QAAA,GAAW,QAAA;AACzB,MAAA,OAAA,CAAQ,SAAA,GAAY,SAAA,IAAa,CAAA,KAAA,EAAQ,SAAS,CAAA,kBAAA,CAAA;AAClD,MAAA,MAAA,CAAO,YAAY,OAAO,CAAA;AAC1B,MAAA,MAAM,OACJ,OAAA,CAAQ,aAAA,CAA2B,CAAA,CAAA,EAAI,SAAS,cAAc,CAAA,IAC9D,OAAA;AACF,MAAA,IAAA,CAAK,WAAA,GAAc,EAAA;AACnB,MAAA,QAAA,CAAS,IAAI,CAAA;AAAA,IACf,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AACV,IAAA,uBACEP,cAAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAK,OAAA,EAAS,OAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,MAAA,EAAQ,QAAO,EACvD,QAAA,EAAA,KAAA,GAAQQ,sBAAa,QAAA,EAAU,KAAK,IAAI,IAAA,EAC3C,CAAA;AAAA,EAEJ,CAAA;AAEA,EAAA,MAAM,OAAA,GAA4B,CAAC,EAAE,OAAA,EAAS,QAAO,KAAM;AACzD,IAAA,MAAM,GAAA,GAAMH,aAAuB,IAAI,CAAA;AACvC,IAAAE,qBAAA,CAAgB,MAAM;AACpB,MAAA,MAAM,KAAK,GAAA,CAAI,OAAA;AACf,MAAA,IAAI,CAAC,EAAA,IAAM,CAAC,WAAA,EAAa;AACzB,MAAA,QAAA,CAAS,IAAI,WAAA,EAAa;AAAA,QACxB,QAAQ,MAAA,CAAO,IAAA;AAAA,QACf,MAAA,EAAQ,QAAA,CAAS,MAAA,CAAO,IAAI,CAAA;AAAA,QAC5B,IAAA,EAAM,aAAA,CAAc,OAAA,CAAQ,OAAO,CAAA;AAAA,QACnC,IAAA,EAAM,OAAA,CAAQ,OAAA,CAAQ,IAAI;AAAA,OAC3B,CAAA;AAAA,IACH,CAAA,EAAG,CAAC,OAAA,EAAS,MAAM,CAAC,CAAA;AACpB,IAAA,uBAAOP,eAAC,KAAA,EAAA,EAAI,GAAA,EAAU,OAAO,WAAA,CAAY,OAAA,CAAQ,cAAc,CAAA,EAAG,CAAA;AAAA,EACpE,CAAA;AAEA,EAAA,MAAM,aAAA,GAAiC,CAAC,EAAE,OAAA,EAAQ,KAAM;AACtD,IAAA,MAAM,GAAA,GAAMK,aAAuB,IAAI,CAAA;AACvC,IAAAE,qBAAA,CAAgB,MAAM;AACpB,MAAA,MAAM,KAAK,GAAA,CAAI,OAAA;AACf,MAAA,IAAI,CAAC,EAAA,IAAM,CAAC,UAAA,EAAY;AACxB,MAAA,QAAA,CAAS,EAAA,EAAI,YAAY,EAAE,IAAA,EAAM,cAAc,OAAA,CAAQ,OAAO,GAAG,CAAA;AAAA,IACnE,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AACZ,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,uBAAOP,eAAC,KAAA,EAAA,EAAI,GAAA,EAAU,OAAO,WAAA,CAAY,OAAA,CAAQ,cAAc,CAAA,EAAG,CAAA;AAAA,IACpE;AACA,IAAA,uBACEA,cAAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAO;AAAA,UACL,GAAG,WAAA,CAAY,OAAA,CAAQ,cAAc,CAAA;AAAA,UACrC,SAAA,EAAW;AAAA,SACb;AAAA,QAEC,QAAA,EAAA,aAAA,CAAc,QAAQ,OAAO;AAAA;AAAA,KAChC;AAAA,EAEJ,CAAA;AAEA,EAAA,MAAM,eAAA,GAAmC,CAAC,EAAE,MAAA,EAAO,KAAM;AACvD,IAAA,MAAM,GAAA,GAAMK,aAAuB,IAAI,CAAA;AACvC,IAAAE,qBAAA,CAAgB,MAAM;AACpB,MAAA,MAAM,KAAK,GAAA,CAAI,OAAA;AACf,MAAA,IAAI,CAAC,EAAA,IAAM,CAAC,UAAA,EAAY;AACxB,MAAA,QAAA,CAAS,IAAI,UAAA,EAAY,EAAE,MAAA,EAAQ,MAAA,CAAO,MAAM,CAAA;AAAA,IAClD,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AACX,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,uBAAOP,cAAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAU,CAAA;AAAA,IACxB;AACA,IAAA,uBACEI,gBAAC,KAAA,EAAA,EAAI,KAAA,EAAO,EAAE,OAAA,EAAS,GAAA,EAAK,SAAA,EAAW,QAAA,EAAS,EAC7C,QAAA,EAAA;AAAA,MAAA,MAAA,CAAO,IAAA;AAAA,MAAK;AAAA,KAAA,EACf,CAAA;AAAA,EAEJ,CAAA;AAEA,EAAA,MAAM,QAAA,GAA8B,CAAC,EAAE,QAAA,EAAS,KAAM;AACpD,IAAA,MAAM,GAAA,GAAMC,aAAuB,IAAI,CAAA;AACvC,IAAAE,qBAAA,CAAgB,MAAM;AACpB,MAAA,MAAM,KAAK,GAAA,CAAI,OAAA;AACf,MAAA,IAAI,CAAC,EAAA,EAAI;AACT,MAAA,IAAI,YAAA,EAAc;AAChB,QAAA,QAAA,CAAS,IAAI,YAAA,EAAc,EAAE,QAAA,EAAU,QAAA,CAAS,MAAM,CAAA;AAAA,MACxD,CAAA,MAAO;AACL,QAAA,EAAA,CAAG,cAAc,QAAA,CAAS,IAAA;AAAA,MAC5B;AAAA,IACF,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AACb,IAAA,uBAAOP,cAAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAU,CAAA;AAAA,EACxB,CAAA;AAEA,EAAA,MAAM,SAA0D,CAAC;AAAA,IAC/D,WAAA;AAAA,IACA,IAAA,GAAO;AAAA,wBAEPA,cAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO;AAAA,QACL,KAAA,EAAO,IAAA;AAAA,QACP,MAAA,EAAQ,IAAA;AAAA,QACR,YAAA,EAAc,KAAA;AAAA,QACd,OAAA,EAAS,MAAA;AAAA,QACT,UAAA,EAAY,QAAA;AAAA,QACZ,UAAA,EAAY,sBAAA;AAAA,QACZ,UAAU,IAAA,GAAO;AAAA,OACnB;AAAA,MAEC,QAAA,EAAA,QAAA,CAAS,YAAY,IAAI;AAAA;AAAA,GAC5B;AAGF,EAAA,MAAM,UAAA,GAA6B;AAAA,IACjC,KAAA;AAAA,IACA,OAAA;AAAA,IACA,aAAA;AAAA,IACA,eAAA;AAAA,IACA,UAAU,MAAM,IAAA;AAAA,IAChB,QAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,OAAO;AAAA,IACL,IAAI,IAAA,CAAK,EAAA;AAAA,IACT,IAAA,EAAM;AAAA,MACJ,IAAA,EAAM,MAAM,IAAA,CAAK,IAAA;AAAA,MACjB,aAAA,EAAe,MAAM,IAAA,CAAK,MAAA,IAAU,EAAE,KAAA,EAAO,GAAA,EAAK,QAAQ,GAAA,EAAI;AAAA,MAC9D,cAAA;AAAA,MACA,cAAc,IAAA,CAAK;AAAA,KACrB;AAAA,IACA,UAAA;AAAA,IACA,MAAA,EAAQ,EAAE,KAAA,EAAO,WAAA,EAAa,MAAM,UAAA;AAAW,GACjD;AACF","file":"index.cjs","sourcesContent":["import type { Skin } from \"./types.js\";\n\n/**\n * Identity helper for type-safety and a single registration point when\n * authoring a skin. Keeping it a function (rather than a bare object) lets us\n * add validation/registration later without changing skin call sites.\n */\nexport function defineSkin(skin: Skin): Skin {\n return skin;\n}\n","import {\n createContext,\n useContext,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\nimport type { SkinTokens } from \"./types.js\";\n\ninterface ThemeContextValue {\n theme: ResolvedTheme;\n tokens?: SkinTokens;\n}\n\nconst ThemeContext = createContext<ThemeContextValue>({ theme: \"light\" });\n\nexport interface ThemeProviderProps {\n theme: ResolvedTheme;\n /** Resolved per-theme tokens for the active skin. */\n tokens?: SkinTokens;\n children?: ReactNode;\n}\n\n/** Provides the resolved theme + tokens to every skin component below it. */\nexport function ThemeProvider({\n theme,\n tokens,\n children,\n}: ThemeProviderProps): ReactElement {\n return (\n <ThemeContext.Provider value={{ theme, tokens }}>\n {children}\n </ThemeContext.Provider>\n );\n}\n\n/** The resolved theme (`\"light\" | \"dark\"`) for the current subtree. */\nexport function useTheme(): ResolvedTheme {\n return useContext(ThemeContext).theme;\n}\n\n/** The resolved design tokens for the current subtree, if provided. */\nexport function useTokens(): SkinTokens | undefined {\n return useContext(ThemeContext).tokens;\n}\n","import type { FontDeclaration } from \"./types.js\";\n\n/** Build a CSS `src:` value from a font's sources. */\nfunction srcValue(decl: FontDeclaration): string {\n return decl.sources\n .map((s) => {\n const format = s.format ? ` format(\"${s.format}\")` : \"\";\n return `url(\"${s.url}\")${format}`;\n })\n .join(\", \");\n}\n\n/**\n * Load a skin's declared web fonts so live preview matches the platform (not\n * just video export). SSR-safe: a no-op when `document`/`FontFace` are absent\n * (server render, Remotion Node). Idempotent per family+weight+style.\n *\n * Returns once every face has loaded (or immediately, off the main document).\n */\nexport async function loadSkinFonts(\n fonts: FontDeclaration[] | undefined,\n): Promise<void> {\n if (!fonts || fonts.length === 0) return;\n if (\n typeof document === \"undefined\" ||\n typeof FontFace === \"undefined\" ||\n !document.fonts\n ) {\n return;\n }\n\n const pending: Promise<unknown>[] = [];\n for (const decl of fonts) {\n for (const source of decl.sources) {\n const descriptors: FontFaceDescriptors = {};\n if (source.weight !== undefined)\n descriptors.weight = String(source.weight);\n if (source.style !== undefined) descriptors.style = source.style;\n\n const single: FontDeclaration = { ...decl, sources: [source] };\n const face = new FontFace(decl.family, srcValue(single), descriptors);\n\n // Skip if an identical face is already registered.\n const already = [...document.fonts].some(\n (f) =>\n f.family === decl.family &&\n f.weight === (descriptors.weight ?? \"normal\") &&\n f.style === (descriptors.style ?? \"normal\"),\n );\n if (already) continue;\n\n document.fonts.add(face);\n pending.push(face.load());\n }\n }\n await Promise.all(pending);\n}\n","import type { CSSProperties, ReactElement, ReactNode } from \"react\";\n\n/**\n * Animation primitives are **pure functions of progress** (0..1), not CSS\n * transitions or JS timers — so the React preview and the Remotion render\n * animate identically frame-for-frame (PLAN §7). Skins call these driven by\n * `revealProgress` / typing `progress` from `SimState`.\n */\n\nexport const clamp01 = (x: number): number => (x < 0 ? 0 : x > 1 ? 1 : x);\n\n/** Decelerating ease, good for reveals. */\nexport const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3);\n\n/** Back-ease-out with overshoot, good for pops. Lands exactly on 1 at t=1. */\nexport function backEaseOut(t: number, tension = 2.2): number {\n const c3 = tension + 1;\n return 1 + c3 * Math.pow(t - 1, 3) + tension * Math.pow(t - 1, 2);\n}\n\nexport interface FadeSlideOptions {\n /** Slide distance in px at progress 0 (default 8). */\n distance?: number;\n /** Axis to slide along (default \"y\"). */\n axis?: \"x\" | \"y\";\n easing?: (t: number) => number;\n}\n\n/** Fade + slide-in reveal. `progress` 0 → hidden+offset, 1 → shown+settled. */\nexport function fadeSlideIn(\n progress: number,\n options: FadeSlideOptions = {},\n): CSSProperties {\n const eased = (options.easing ?? easeOutCubic)(clamp01(progress));\n const distance = options.distance ?? 8;\n const offset = (1 - eased) * distance;\n // Once settled, drop the transform entirely. Any non-`none` transform — even\n // `translateY(0px)` — establishes a stacking context, which would trap a\n // descendant overlay (e.g. a reaction's hover tooltip) *below* later sibling\n // messages, so neighbouring text paints over the opaque tooltip and it reads\n // as transparent. `none` at rest lets overlays layer above adjacent content.\n if (offset === 0) return { opacity: eased, transform: \"none\" };\n const translate =\n options.axis === \"x\"\n ? `translateX(${offset}px)`\n : `translateY(${offset}px)`;\n return { opacity: eased, transform: translate };\n}\n\nexport interface PopOptions {\n /** Overshoot tension (default 2.2 ≈ ~10% overshoot). */\n tension?: number;\n}\n\n/** Scale pop-in (with a little overshoot), good for reactions landing. */\nexport function popIn(\n progress: number,\n options: PopOptions = {},\n): CSSProperties {\n const p = clamp01(progress);\n const scale = backEaseOut(p, options.tension ?? 2.2);\n return {\n opacity: clamp01(p * 3),\n transform: `scale(${scale})`,\n transformOrigin: \"center\",\n };\n}\n\nexport interface TypingDotsProps {\n /** 0..1 progress through the indicator's shown duration. */\n progress?: number;\n count?: number;\n /** Bounce cycles across the full progress (default 4). */\n cycles?: number;\n /** Dot color (default `currentColor`). */\n color?: string;\n /** Dot diameter in px (default 6). */\n size?: number;\n /** Gap between dots in px (default 4). */\n gap?: number;\n}\n\n/**\n * The three-dot bouncing typing indicator, animated purely from `progress`\n * (deterministic per frame). Skins style it via props or wrap it.\n */\nexport function TypingDots({\n progress = 0,\n count = 3,\n cycles = 4,\n color = \"currentColor\",\n size = 6,\n gap = 4,\n}: TypingDotsProps): ReactElement {\n const phase = clamp01(progress) * cycles * Math.PI * 2;\n const dots: ReactNode[] = [];\n for (let i = 0; i < count; i++) {\n const wave = Math.sin(phase - i * 0.9);\n const lift = Math.max(0, wave) * 4;\n const opacity = 0.4 + Math.max(0, wave) * 0.6;\n dots.push(\n <span\n key={i}\n style={{\n width: size,\n height: size,\n borderRadius: \"50%\",\n background: color,\n transform: `translateY(${-lift}px)`,\n opacity,\n }}\n />,\n );\n }\n return (\n <span style={{ display: \"inline-flex\", alignItems: \"flex-end\", gap }}>\n {dots}\n </span>\n );\n}\n","import type { CSSProperties, ReactElement, ReactNode } from \"react\";\nimport type {\n ContentNode,\n ImageNode,\n InlineNode,\n TextNode,\n} from \"@typecaast/schema\";\n\nexport interface ContentClassNames {\n text?: string;\n link?: string;\n mention?: string;\n code?: string;\n emoji?: string;\n image?: string;\n}\n\n/** Per-mark inline styles, so skins can theme marks without a CSS file. */\nexport interface ContentStyles {\n text?: CSSProperties;\n link?: CSSProperties;\n mention?: CSSProperties;\n code?: CSSProperties;\n emoji?: CSSProperties;\n}\n\nexport interface MessageContentProps {\n nodes: ContentNode[];\n /** Per-mark class names so skins style marks with their own CSS. */\n classNames?: ContentClassNames;\n /** Per-mark inline styles (merged with the defaults). */\n styles?: ContentStyles;\n /** Extra style for in-message images (skins set radius, max size, etc.). */\n imageStyle?: CSSProperties;\n}\n\nfunction renderInline(\n span: InlineNode,\n key: number,\n cn: ContentClassNames,\n st: ContentStyles,\n): ReactNode {\n switch (span.type) {\n case \"text\":\n return span.value;\n case \"code\":\n return (\n <code key={key} data-tc-mark=\"code\" className={cn.code} style={st.code}>\n {span.value}\n </code>\n );\n case \"link\":\n return (\n <a\n key={key}\n data-tc-mark=\"link\"\n className={cn.link}\n style={st.link}\n href={span.href}\n rel=\"noreferrer\"\n >\n {span.label ?? span.href}\n </a>\n );\n case \"mention\":\n return (\n <span\n key={key}\n data-tc-mark=\"mention\"\n className={cn.mention}\n style={st.mention}\n >\n {span.label}\n </span>\n );\n case \"emoji\":\n return (\n <span\n key={key}\n data-tc-mark=\"emoji\"\n className={cn.emoji}\n style={st.emoji}\n >\n {span.value}\n </span>\n );\n }\n}\n\nfunction renderImage(\n node: ImageNode,\n key: number,\n cn: ContentClassNames,\n imageStyle?: CSSProperties,\n): ReactNode {\n return (\n <img\n key={key}\n data-tc-node=\"image\"\n className={cn.image}\n src={node.src}\n alt={node.alt ?? \"\"}\n width={node.width}\n height={node.height}\n style={{ maxWidth: \"100%\", display: \"block\", ...imageStyle }}\n />\n );\n}\n\n/**\n * Render a message body (`ContentNode[]`) to React: text nodes with inline\n * marks (code/link/mention/emoji) and in-message images. Unknown node types are\n * skipped (forward-compatible — PLAN §6). SSR-safe, so it renders identically\n * in the browser and in Remotion's Node renderer. Skins style via `classNames`.\n */\nexport function MessageContent({\n nodes,\n classNames = {},\n styles = {},\n imageStyle,\n}: MessageContentProps): ReactElement {\n return (\n <>\n {nodes.map((node, i) => {\n if (node.type === \"text\") {\n const text = node as TextNode;\n return (\n <span\n key={i}\n data-tc-node=\"text\"\n className={classNames.text}\n style={styles.text}\n >\n {text.spans.map((span, j) =>\n renderInline(span, j, classNames, styles),\n )}\n </span>\n );\n }\n if (node.type === \"image\") {\n return renderImage(node as ImageNode, i, classNames, imageStyle);\n }\n return null; // unknown future node type — skipped\n })}\n </>\n );\n}\n","import { useMemo, type ReactElement } from \"react\";\nimport type { ComposerMode, Participant } from \"@typecaast/schema\";\nimport type { SimState } from \"@typecaast/core\";\nimport { ThemeProvider } from \"./theme.js\";\nimport type { Skin } from \"./types.js\";\n\nexport type { ComposerMode };\n\n// A subtle, native-feeling scrollbar for the scrollable thread, scoped to the\n// thread viewport and injected inline so the embed needs no external stylesheet.\n// A neutral translucent grey reads on both light and dark skins and deepens on\n// hover; WebKit/Blink get the thin overlay-style thumb, Firefox uses the\n// standard `scrollbar-*` props. The selector is global on purpose — identical\n// rules across multiple embeds on a page are harmless and keep them consistent.\nconst THREAD_SCROLLBAR_CSS = `\n[data-typecaast-thread]{scrollbar-width:thin;scrollbar-color:rgba(128,128,128,.4) transparent}\n[data-typecaast-thread]::-webkit-scrollbar{width:8px;height:8px}\n[data-typecaast-thread]::-webkit-scrollbar-track{background:transparent}\n[data-typecaast-thread]::-webkit-scrollbar-thumb{background-color:rgba(128,128,128,.4);border-radius:8px;border:2px solid transparent;background-clip:padding-box}\n[data-typecaast-thread]:hover::-webkit-scrollbar-thumb{background-color:rgba(128,128,128,.6)}\n`;\n\nexport interface TypecaastStageProps {\n state: SimState;\n skin: Skin;\n participants: Participant[];\n /** Skin-specific options from `meta.skin.options`. */\n options?: Record<string, unknown>;\n /**\n * Composer visibility: `auto` (default) shows it only while someone is\n * typing/sending; `always` keeps the reply box visible (idle = placeholder);\n * `never` hides it.\n */\n composer?: ComposerMode;\n}\n\n/**\n * Maps a `SimState` onto a skin's components: a `Frame` wrapping the thread\n * items (Message / SystemMessage), the typing indicators, and the composer.\n * Reactions render inside the skin's `Message` (it reads `message.reactions`).\n *\n * Lives in `skin-kit` (the contract layer) so both `@typecaast/react` and the\n * skins' own stories can render a frame without depending on the React player.\n */\nexport function TypecaastStage({\n state,\n skin,\n participants,\n options,\n composer = \"auto\",\n}: TypecaastStageProps): ReactElement {\n const theme = state.theme;\n const byId = useMemo(() => {\n const map = new Map<string, Participant>();\n for (const p of participants) map.set(p.id, p);\n return map;\n }, [participants]);\n\n const { Frame, Message, SystemMessage, TypingIndicator, Composer } =\n skin.components;\n const tokens = skin.tokens?.[theme];\n // `always` keeps the reply box mounted even when idle (falls back to the self\n // participant so a placeholder shows); `auto` only shows it while composing.\n const selfParticipant = useMemo(\n () => participants.find((p) => p.isSelf),\n [participants],\n );\n const composerAuthor = state.composer.from\n ? byId.get(state.composer.from)\n : composer === \"always\"\n ? selfParticipant\n : undefined;\n const showComposer = composer !== \"never\" && composerAuthor !== undefined;\n\n // \"X is typing…\" indicators, minus the viewer's own (you never see yourself\n // typing — that's what the composer shows). Placement is skin-driven: inline\n // in the thread by default, or below the composer (Slack).\n const typingPlacement = skin.meta.typingPlacement ?? \"thread\";\n const typingNodes = state.typingIndicators\n .map((typing, i) => {\n const author = byId.get(typing.from);\n if (!author || author.isSelf) return null;\n return (\n <TypingIndicator\n key={`typing-${typing.from}-${i}`}\n theme={theme}\n typing={typing}\n author={author}\n />\n );\n })\n .filter(Boolean);\n\n return (\n <ThemeProvider theme={theme} tokens={tokens}>\n <style>{THREAD_SCROLLBAR_CSS}</style>\n <Frame theme={theme} options={options} composer={composer}>\n {/* Thread viewport. `column-reverse` pins the conversation to the\n bottom (newest message + composer sit at the bottom, older items\n \"shift up\", PLAN §7) and, once the thread outgrows the height, makes\n it scroll from the bottom with the top reachable — entirely in CSS,\n so it renders identically in a live embed, an SSR page, and a video\n frame (no scroll-to-bottom effect, which wouldn't run before a\n Remotion screenshot). Because `column-reverse` lays the first child\n out at the bottom, children render newest-first: the typing\n indicator (most recent activity) first, then messages reversed. The\n engine's scroll.targetOffset is honored here once real layout\n measurement lands. */}\n <div\n data-typecaast-thread=\"\"\n style={{\n display: \"flex\",\n flexDirection: \"column-reverse\",\n flex: \"1 1 auto\",\n minHeight: 0,\n overflowY: \"auto\",\n // Scrollbar styling lives in THREAD_SCROLLBAR_CSS (the `<style>`\n // above) so it can reach the WebKit pseudo-elements.\n // Breathing room beneath the last message — keeps it off the\n // composer (when shown) and the Frame's bottom edge (when hidden).\n paddingBottom: 16,\n }}\n >\n {typingPlacement === \"thread\" ? typingNodes : null}\n {state.messages\n .map((message, i) => {\n const author = byId.get(message.from);\n if (!author) return null;\n // Index-disambiguated so a config with duplicate message ids can't\n // collide React keys (the builder can produce them transiently).\n const key = `${message.id}-${i}`;\n if (message.variant === \"system\") {\n return (\n <SystemMessage\n key={key}\n theme={theme}\n message={message}\n author={author}\n />\n );\n }\n // Grouping looks at the chronological predecessor — computed\n // before the reverse below, so author-collapsing is unaffected.\n const prev = state.messages[i - 1];\n const previousAuthor = prev ? byId.get(prev.from) : undefined;\n return (\n <Message\n key={key}\n theme={theme}\n message={message}\n author={author}\n previousAuthor={previousAuthor}\n />\n );\n })\n .reverse()}\n </div>\n {showComposer ? (\n <Composer\n theme={theme}\n composer={state.composer}\n author={composerAuthor}\n options={options}\n />\n ) : null}\n {typingPlacement === \"below-composer\" ? typingNodes : null}\n </Frame>\n </ThemeProvider>\n );\n}\n","/**\n * `slotSkinFromDraft` — build a `Skin` from a slotted HTML draft (the shape\n * the capture pipeline emits). The frame/message/composer HTML strings are\n * injected into a shadow root with the draft's CSS, and `{{slot}}` markers\n * are substituted with per-message text at render time.\n *\n * Lives in `@typecaast/skin-kit` so both `@typecaast/capture` (the runtime\n * untrusted-template path) and `@typecaast/skins` (built-in captured skins\n * like PostHog) can share one implementation without forming a build cycle\n * with `@typecaast/react`. The capture-runtime caller layers `sanitizeHtml`\n * around it; built-ins skip sanitize because their draft.json is\n * version-controlled.\n *\n * Responsive behaviour: the captured DOM is typically taken on a desktop\n * viewport (1000-1500px wide) but the skin renders into a small canvas\n * (often 480×640). We normalise that by:\n * - wrapping the frame slot in a container that is `width:100%; height:100%`\n * with `overflow:hidden`, so the captured outer chrome can't extend\n * past the canvas;\n * - resetting the captured root element's `margin`/`max-width` so a\n * `margin: 0px 234px` baked in at desktop width becomes `margin: 0 auto`;\n * - exposing the captured viewport width as `--captured-viewport-width`\n * for authored CSS to ratio-scale against if it wants.\n */\nimport {\n useLayoutEffect,\n useRef,\n useState,\n type CSSProperties,\n type FC,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { ContentNode, Participant } from \"@typecaast/schema\";\nimport type {\n Capabilities,\n ComposerProps,\n FrameProps,\n MessageProps,\n SystemProps,\n TypingProps,\n} from \"@typecaast/core\";\nimport type { Skin, SkinComponents, SkinTokens } from \"./types.js\";\n\nconst SLOT_ATTR = \"data-tc-slot\";\n\ninterface TokenSet {\n colors?: Record<string, string>;\n}\n\nexport interface SlotSkinDraft {\n meta: {\n name: string;\n theme?: \"light\" | \"dark\";\n canvas?: { width: number; height: number };\n capturedAt?: { viewportWidth?: number; pixelRatio?: number };\n };\n slots: {\n frame?: string;\n message?: string;\n composer?: string;\n typing?: string;\n system?: string;\n };\n css?: string;\n tokens: TokenSet;\n darkTokens?: TokenSet;\n}\n\nexport interface SlotSkinOptions {\n id: string;\n capabilities: Capabilities;\n /**\n * When true, every slot mount in the shadow root gets a faint dashed\n * outline + a corner badge naming the slot. Used by the `/create-skin`\n * editor to show authors where their `{{body}}` lands.\n */\n slotMarkers?: boolean;\n}\n\nfunction contentToText(content: ContentNode[]): string {\n const out: string[] = [];\n for (const node of content) {\n if (\n node.type === \"text\" &&\n Array.isArray((node as { spans?: unknown }).spans)\n ) {\n for (const span of (\n node as { spans: { value?: string; label?: string }[] }\n ).spans) {\n out.push(span.value ?? span.label ?? \"\");\n }\n } else if (node.type === \"image\") {\n out.push((node as { alt?: string }).alt ?? \"🖼\");\n }\n }\n return out.join(\"\");\n}\n\nfunction initials(name: string): string {\n return name\n .split(/\\s+/)\n .filter(Boolean)\n .slice(0, 2)\n .map((w) => w[0]?.toUpperCase() ?? \"\")\n .join(\"\");\n}\n\nfunction fmtTime(atMs: number): string {\n const total = Math.floor(atMs / 1000);\n const m = Math.floor(total / 60);\n const s = total % 60;\n return `${m}:${String(s).padStart(2, \"0\")}`;\n}\n\n/**\n * Build the `<style>` text for the shadow root. Order matters:\n * 1. `:host` declarations (tokens as CSS vars, captured viewport width).\n * 2. A reset that normalises the captured root element back to fluid\n * layout — desktop-only `margin: 0 200px`/`max-width: 1200px` would\n * otherwise squeeze content in a small canvas.\n * 3. Author / captured CSS, which can override anything above.\n * 4. Slot-marker overlay if requested.\n */\nfunction styleText(\n tokens: TokenSet,\n css: string,\n capturedAt: SlotSkinDraft[\"meta\"][\"capturedAt\"],\n slotMarkers: boolean,\n): string {\n const vars = Object.entries(tokens.colors ?? {})\n .map(([k, v]) => `--${k}: ${v};`)\n .join(\" \");\n const cv = capturedAt?.viewportWidth\n ? `--captured-viewport-width: ${capturedAt.viewportWidth}px;`\n : \"\";\n const reset = `\n :host { all: initial; display: block; width: 100%; height: 100%; ${vars} ${cv} }\n * { box-sizing: border-box; }\n /* Normalise the captured root: drop desktop-only margins / max-widths\n so the layout re-centers cleanly in any canvas. */\n .tc-slot-root > :first-child {\n margin-left: auto !important;\n margin-right: auto !important;\n max-width: 100% !important;\n }`;\n const markers = slotMarkers\n ? `\n [data-tc-slot] {\n outline: 1px dashed rgba(99, 102, 241, 0.6);\n outline-offset: -1px;\n position: relative;\n }\n [data-tc-slot]::before {\n content: attr(data-tc-slot);\n position: absolute;\n top: 0;\n left: 0;\n transform: translateY(-100%);\n font: 600 9px/1 -apple-system, system-ui, sans-serif;\n padding: 1px 4px;\n background: rgba(99, 102, 241, 0.95);\n color: white;\n border-radius: 2px;\n pointer-events: none;\n z-index: 10;\n }`\n : \"\";\n return `${reset}\\n${css}\\n${markers}`;\n}\n\nfunction fillInto(\n host: HTMLElement,\n templateHtml: string,\n values: Record<string, string>,\n): void {\n host.innerHTML = templateHtml;\n for (const node of host.querySelectorAll(`[${SLOT_ATTR}]`)) {\n const slot = node.getAttribute(SLOT_ATTR);\n if (slot && slot in values) node.textContent = values[slot] ?? \"\";\n }\n}\n\nfunction revealStyle(progress: number): CSSProperties {\n const p = Math.max(0, Math.min(1, progress));\n return {\n opacity: p,\n transform: `translateY(${(1 - p) * 6}px)`,\n willChange: \"opacity, transform\",\n };\n}\n\nexport function slotSkinFromDraft(\n draft: SlotSkinDraft,\n opts: SlotSkinOptions,\n): Skin {\n const lightTokens: SkinTokens = { colors: draft.tokens.colors ?? {} };\n const darkTokens: SkinTokens = draft.darkTokens\n ? { colors: draft.darkTokens.colors ?? {} }\n : lightTokens;\n const slotMarkers = opts.slotMarkers ?? false;\n const cssByTheme = {\n light: styleText(\n lightTokens,\n draft.css ?? \"\",\n draft.meta.capturedAt,\n slotMarkers,\n ),\n dark: styleText(\n darkTokens,\n draft.css ?? \"\",\n draft.meta.capturedAt,\n slotMarkers,\n ),\n };\n const supportsThemes: (\"light\" | \"dark\")[] = draft.darkTokens\n ? [\"light\", \"dark\"]\n : [draft.meta.theme ?? \"light\"];\n\n const frameHtml = draft.slots.frame;\n const messageHtml = draft.slots.message;\n const composerHtml = draft.slots.composer;\n const systemHtml = draft.slots.system;\n const typingHtml = draft.slots.typing;\n\n const Frame: FC<FrameProps & { children?: ReactNode }> = ({\n theme,\n children,\n }) => {\n const hostRef = useRef<HTMLDivElement>(null);\n const [mount, setMount] = useState<HTMLElement | null>(null);\n useLayoutEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const shadow = host.shadowRoot ?? host.attachShadow({ mode: \"open\" });\n shadow.innerHTML = \"\";\n const style = host.ownerDocument.createElement(\"style\");\n style.textContent = theme === \"dark\" ? cssByTheme.dark : cssByTheme.light;\n shadow.appendChild(style);\n // Wrap the captured frame in a fluid 100%×100% container so any\n // desktop-fixed widths/margins on the captured root get neutralised\n // by the `.tc-slot-root > :first-child` reset.\n const wrapper = host.ownerDocument.createElement(\"div\");\n wrapper.className = \"tc-slot-root\";\n wrapper.style.width = \"100%\";\n wrapper.style.height = \"100%\";\n wrapper.style.display = \"flex\";\n wrapper.style.flexDirection = \"column\";\n wrapper.style.overflow = \"hidden\";\n wrapper.innerHTML = frameHtml ?? `<div ${SLOT_ATTR}=\"messages\"></div>`;\n shadow.appendChild(wrapper);\n const slot =\n wrapper.querySelector<HTMLElement>(`[${SLOT_ATTR}=\"messages\"]`) ??\n wrapper;\n slot.textContent = \"\";\n setMount(slot);\n }, [theme]);\n return (\n <div ref={hostRef} style={{ width: \"100%\", height: \"100%\" }}>\n {mount ? createPortal(children, mount) : null}\n </div>\n );\n };\n\n const Message: FC<MessageProps> = ({ message, author }) => {\n const ref = useRef<HTMLDivElement>(null);\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || !messageHtml) return;\n fillInto(el, messageHtml, {\n author: author.name,\n avatar: initials(author.name),\n body: contentToText(message.content),\n time: fmtTime(message.atMs),\n });\n }, [message, author]);\n return <div ref={ref} style={revealStyle(message.revealProgress)} />;\n };\n\n const SystemMessage: FC<SystemProps> = ({ message }) => {\n const ref = useRef<HTMLDivElement>(null);\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || !systemHtml) return;\n fillInto(el, systemHtml, { body: contentToText(message.content) });\n }, [message]);\n if (systemHtml) {\n return <div ref={ref} style={revealStyle(message.revealProgress)} />;\n }\n return (\n <div\n style={{\n ...revealStyle(message.revealProgress),\n textAlign: \"center\",\n }}\n >\n {contentToText(message.content)}\n </div>\n );\n };\n\n const TypingIndicator: FC<TypingProps> = ({ author }) => {\n const ref = useRef<HTMLDivElement>(null);\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || !typingHtml) return;\n fillInto(el, typingHtml, { author: author.name });\n }, [author]);\n if (typingHtml) {\n return <div ref={ref} />;\n }\n return (\n <div style={{ opacity: 0.7, fontStyle: \"italic\" }}>\n {author.name} is typing…\n </div>\n );\n };\n\n const Composer: FC<ComposerProps> = ({ composer }) => {\n const ref = useRef<HTMLDivElement>(null);\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el) return;\n if (composerHtml) {\n fillInto(el, composerHtml, { composer: composer.text });\n } else {\n el.textContent = composer.text;\n }\n }, [composer]);\n return <div ref={ref} />;\n };\n\n const Avatar: FC<{ participant: Participant; size?: number }> = ({\n participant,\n size = 36,\n }) => (\n <div\n style={{\n width: size,\n height: size,\n borderRadius: \"50%\",\n display: \"grid\",\n placeItems: \"center\",\n background: \"var(--color-1, #ccc)\",\n fontSize: size * 0.4,\n }}\n >\n {initials(participant.name)}\n </div>\n );\n\n const components: SkinComponents = {\n Frame,\n Message,\n SystemMessage,\n TypingIndicator,\n Reaction: () => null,\n Composer,\n Avatar,\n };\n\n return {\n id: opts.id,\n meta: {\n name: draft.meta.name,\n defaultCanvas: draft.meta.canvas ?? { width: 420, height: 720 },\n supportsThemes,\n capabilities: opts.capabilities,\n },\n components,\n tokens: { light: lightTokens, dark: darkTokens },\n };\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -209,4 +209,43 @@ interface TypecaastStageProps {
|
|
|
209
209
|
*/
|
|
210
210
|
declare function TypecaastStage({ state, skin, participants, options, composer, }: TypecaastStageProps): ReactElement;
|
|
211
211
|
|
|
212
|
-
|
|
212
|
+
interface TokenSet {
|
|
213
|
+
colors?: Record<string, string>;
|
|
214
|
+
}
|
|
215
|
+
interface SlotSkinDraft {
|
|
216
|
+
meta: {
|
|
217
|
+
name: string;
|
|
218
|
+
theme?: "light" | "dark";
|
|
219
|
+
canvas?: {
|
|
220
|
+
width: number;
|
|
221
|
+
height: number;
|
|
222
|
+
};
|
|
223
|
+
capturedAt?: {
|
|
224
|
+
viewportWidth?: number;
|
|
225
|
+
pixelRatio?: number;
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
slots: {
|
|
229
|
+
frame?: string;
|
|
230
|
+
message?: string;
|
|
231
|
+
composer?: string;
|
|
232
|
+
typing?: string;
|
|
233
|
+
system?: string;
|
|
234
|
+
};
|
|
235
|
+
css?: string;
|
|
236
|
+
tokens: TokenSet;
|
|
237
|
+
darkTokens?: TokenSet;
|
|
238
|
+
}
|
|
239
|
+
interface SlotSkinOptions {
|
|
240
|
+
id: string;
|
|
241
|
+
capabilities: Capabilities;
|
|
242
|
+
/**
|
|
243
|
+
* When true, every slot mount in the shadow root gets a faint dashed
|
|
244
|
+
* outline + a corner badge naming the slot. Used by the `/create-skin`
|
|
245
|
+
* editor to show authors where their `{{body}}` lands.
|
|
246
|
+
*/
|
|
247
|
+
slotMarkers?: boolean;
|
|
248
|
+
}
|
|
249
|
+
declare function slotSkinFromDraft(draft: SlotSkinDraft, opts: SlotSkinOptions): Skin;
|
|
250
|
+
|
|
251
|
+
export { type ContentClassNames, type ContentStyles, type FadeSlideOptions, type FontDeclaration, type FontSource, MessageContent, type MessageContentProps, type PopOptions, type Skin, type SkinComponents, type SkinMeta, type SkinTokens, type SlotSkinDraft, type SlotSkinOptions, ThemeProvider, type ThemeProviderProps, TypecaastStage, type TypecaastStageProps, TypingDots, type TypingDotsProps, backEaseOut, clamp01, defineSkin, easeOutCubic, fadeSlideIn, loadSkinFonts, popIn, slotSkinFromDraft, useTheme, useTokens };
|
package/dist/index.d.ts
CHANGED
|
@@ -209,4 +209,43 @@ interface TypecaastStageProps {
|
|
|
209
209
|
*/
|
|
210
210
|
declare function TypecaastStage({ state, skin, participants, options, composer, }: TypecaastStageProps): ReactElement;
|
|
211
211
|
|
|
212
|
-
|
|
212
|
+
interface TokenSet {
|
|
213
|
+
colors?: Record<string, string>;
|
|
214
|
+
}
|
|
215
|
+
interface SlotSkinDraft {
|
|
216
|
+
meta: {
|
|
217
|
+
name: string;
|
|
218
|
+
theme?: "light" | "dark";
|
|
219
|
+
canvas?: {
|
|
220
|
+
width: number;
|
|
221
|
+
height: number;
|
|
222
|
+
};
|
|
223
|
+
capturedAt?: {
|
|
224
|
+
viewportWidth?: number;
|
|
225
|
+
pixelRatio?: number;
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
slots: {
|
|
229
|
+
frame?: string;
|
|
230
|
+
message?: string;
|
|
231
|
+
composer?: string;
|
|
232
|
+
typing?: string;
|
|
233
|
+
system?: string;
|
|
234
|
+
};
|
|
235
|
+
css?: string;
|
|
236
|
+
tokens: TokenSet;
|
|
237
|
+
darkTokens?: TokenSet;
|
|
238
|
+
}
|
|
239
|
+
interface SlotSkinOptions {
|
|
240
|
+
id: string;
|
|
241
|
+
capabilities: Capabilities;
|
|
242
|
+
/**
|
|
243
|
+
* When true, every slot mount in the shadow root gets a faint dashed
|
|
244
|
+
* outline + a corner badge naming the slot. Used by the `/create-skin`
|
|
245
|
+
* editor to show authors where their `{{body}}` lands.
|
|
246
|
+
*/
|
|
247
|
+
slotMarkers?: boolean;
|
|
248
|
+
}
|
|
249
|
+
declare function slotSkinFromDraft(draft: SlotSkinDraft, opts: SlotSkinOptions): Skin;
|
|
250
|
+
|
|
251
|
+
export { type ContentClassNames, type ContentStyles, type FadeSlideOptions, type FontDeclaration, type FontSource, MessageContent, type MessageContentProps, type PopOptions, type Skin, type SkinComponents, type SkinMeta, type SkinTokens, type SlotSkinDraft, type SlotSkinOptions, ThemeProvider, type ThemeProviderProps, TypecaastStage, type TypecaastStageProps, TypingDots, type TypingDotsProps, backEaseOut, clamp01, defineSkin, easeOutCubic, fadeSlideIn, loadSkinFonts, popIn, slotSkinFromDraft, useTheme, useTokens };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { createContext, useContext, useMemo } from 'react';
|
|
2
|
+
import { createContext, useContext, useMemo, useRef, useState, useLayoutEffect } from 'react';
|
|
3
3
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
4
5
|
|
|
5
6
|
// src/define-skin.ts
|
|
6
7
|
function defineSkin(skin) {
|
|
@@ -62,6 +63,7 @@ function fadeSlideIn(progress, options = {}) {
|
|
|
62
63
|
const eased = (options.easing ?? easeOutCubic)(clamp01(progress));
|
|
63
64
|
const distance = options.distance ?? 8;
|
|
64
65
|
const offset = (1 - eased) * distance;
|
|
66
|
+
if (offset === 0) return { opacity: eased, transform: "none" };
|
|
65
67
|
const translate = options.axis === "x" ? `translateX(${offset}px)` : `translateY(${offset}px)`;
|
|
66
68
|
return { opacity: eased, transform: translate };
|
|
67
69
|
}
|
|
@@ -300,7 +302,237 @@ function TypecaastStage({
|
|
|
300
302
|
] })
|
|
301
303
|
] });
|
|
302
304
|
}
|
|
305
|
+
var SLOT_ATTR = "data-tc-slot";
|
|
306
|
+
function contentToText(content) {
|
|
307
|
+
const out = [];
|
|
308
|
+
for (const node of content) {
|
|
309
|
+
if (node.type === "text" && Array.isArray(node.spans)) {
|
|
310
|
+
for (const span of node.spans) {
|
|
311
|
+
out.push(span.value ?? span.label ?? "");
|
|
312
|
+
}
|
|
313
|
+
} else if (node.type === "image") {
|
|
314
|
+
out.push(node.alt ?? "\u{1F5BC}");
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return out.join("");
|
|
318
|
+
}
|
|
319
|
+
function initials(name) {
|
|
320
|
+
return name.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0]?.toUpperCase() ?? "").join("");
|
|
321
|
+
}
|
|
322
|
+
function fmtTime(atMs) {
|
|
323
|
+
const total = Math.floor(atMs / 1e3);
|
|
324
|
+
const m = Math.floor(total / 60);
|
|
325
|
+
const s = total % 60;
|
|
326
|
+
return `${m}:${String(s).padStart(2, "0")}`;
|
|
327
|
+
}
|
|
328
|
+
function styleText(tokens, css, capturedAt, slotMarkers) {
|
|
329
|
+
const vars = Object.entries(tokens.colors ?? {}).map(([k, v]) => `--${k}: ${v};`).join(" ");
|
|
330
|
+
const cv = capturedAt?.viewportWidth ? `--captured-viewport-width: ${capturedAt.viewportWidth}px;` : "";
|
|
331
|
+
const reset = `
|
|
332
|
+
:host { all: initial; display: block; width: 100%; height: 100%; ${vars} ${cv} }
|
|
333
|
+
* { box-sizing: border-box; }
|
|
334
|
+
/* Normalise the captured root: drop desktop-only margins / max-widths
|
|
335
|
+
so the layout re-centers cleanly in any canvas. */
|
|
336
|
+
.tc-slot-root > :first-child {
|
|
337
|
+
margin-left: auto !important;
|
|
338
|
+
margin-right: auto !important;
|
|
339
|
+
max-width: 100% !important;
|
|
340
|
+
}`;
|
|
341
|
+
const markers = slotMarkers ? `
|
|
342
|
+
[data-tc-slot] {
|
|
343
|
+
outline: 1px dashed rgba(99, 102, 241, 0.6);
|
|
344
|
+
outline-offset: -1px;
|
|
345
|
+
position: relative;
|
|
346
|
+
}
|
|
347
|
+
[data-tc-slot]::before {
|
|
348
|
+
content: attr(data-tc-slot);
|
|
349
|
+
position: absolute;
|
|
350
|
+
top: 0;
|
|
351
|
+
left: 0;
|
|
352
|
+
transform: translateY(-100%);
|
|
353
|
+
font: 600 9px/1 -apple-system, system-ui, sans-serif;
|
|
354
|
+
padding: 1px 4px;
|
|
355
|
+
background: rgba(99, 102, 241, 0.95);
|
|
356
|
+
color: white;
|
|
357
|
+
border-radius: 2px;
|
|
358
|
+
pointer-events: none;
|
|
359
|
+
z-index: 10;
|
|
360
|
+
}` : "";
|
|
361
|
+
return `${reset}
|
|
362
|
+
${css}
|
|
363
|
+
${markers}`;
|
|
364
|
+
}
|
|
365
|
+
function fillInto(host, templateHtml, values) {
|
|
366
|
+
host.innerHTML = templateHtml;
|
|
367
|
+
for (const node of host.querySelectorAll(`[${SLOT_ATTR}]`)) {
|
|
368
|
+
const slot = node.getAttribute(SLOT_ATTR);
|
|
369
|
+
if (slot && slot in values) node.textContent = values[slot] ?? "";
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function revealStyle(progress) {
|
|
373
|
+
const p = Math.max(0, Math.min(1, progress));
|
|
374
|
+
return {
|
|
375
|
+
opacity: p,
|
|
376
|
+
transform: `translateY(${(1 - p) * 6}px)`,
|
|
377
|
+
willChange: "opacity, transform"
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
function slotSkinFromDraft(draft, opts) {
|
|
381
|
+
const lightTokens = { colors: draft.tokens.colors ?? {} };
|
|
382
|
+
const darkTokens = draft.darkTokens ? { colors: draft.darkTokens.colors ?? {} } : lightTokens;
|
|
383
|
+
const slotMarkers = opts.slotMarkers ?? false;
|
|
384
|
+
const cssByTheme = {
|
|
385
|
+
light: styleText(
|
|
386
|
+
lightTokens,
|
|
387
|
+
draft.css ?? "",
|
|
388
|
+
draft.meta.capturedAt,
|
|
389
|
+
slotMarkers
|
|
390
|
+
),
|
|
391
|
+
dark: styleText(
|
|
392
|
+
darkTokens,
|
|
393
|
+
draft.css ?? "",
|
|
394
|
+
draft.meta.capturedAt,
|
|
395
|
+
slotMarkers
|
|
396
|
+
)
|
|
397
|
+
};
|
|
398
|
+
const supportsThemes = draft.darkTokens ? ["light", "dark"] : [draft.meta.theme ?? "light"];
|
|
399
|
+
const frameHtml = draft.slots.frame;
|
|
400
|
+
const messageHtml = draft.slots.message;
|
|
401
|
+
const composerHtml = draft.slots.composer;
|
|
402
|
+
const systemHtml = draft.slots.system;
|
|
403
|
+
const typingHtml = draft.slots.typing;
|
|
404
|
+
const Frame = ({
|
|
405
|
+
theme,
|
|
406
|
+
children
|
|
407
|
+
}) => {
|
|
408
|
+
const hostRef = useRef(null);
|
|
409
|
+
const [mount, setMount] = useState(null);
|
|
410
|
+
useLayoutEffect(() => {
|
|
411
|
+
const host = hostRef.current;
|
|
412
|
+
if (!host) return;
|
|
413
|
+
const shadow = host.shadowRoot ?? host.attachShadow({ mode: "open" });
|
|
414
|
+
shadow.innerHTML = "";
|
|
415
|
+
const style = host.ownerDocument.createElement("style");
|
|
416
|
+
style.textContent = theme === "dark" ? cssByTheme.dark : cssByTheme.light;
|
|
417
|
+
shadow.appendChild(style);
|
|
418
|
+
const wrapper = host.ownerDocument.createElement("div");
|
|
419
|
+
wrapper.className = "tc-slot-root";
|
|
420
|
+
wrapper.style.width = "100%";
|
|
421
|
+
wrapper.style.height = "100%";
|
|
422
|
+
wrapper.style.display = "flex";
|
|
423
|
+
wrapper.style.flexDirection = "column";
|
|
424
|
+
wrapper.style.overflow = "hidden";
|
|
425
|
+
wrapper.innerHTML = frameHtml ?? `<div ${SLOT_ATTR}="messages"></div>`;
|
|
426
|
+
shadow.appendChild(wrapper);
|
|
427
|
+
const slot = wrapper.querySelector(`[${SLOT_ATTR}="messages"]`) ?? wrapper;
|
|
428
|
+
slot.textContent = "";
|
|
429
|
+
setMount(slot);
|
|
430
|
+
}, [theme]);
|
|
431
|
+
return /* @__PURE__ */ jsx("div", { ref: hostRef, style: { width: "100%", height: "100%" }, children: mount ? createPortal(children, mount) : null });
|
|
432
|
+
};
|
|
433
|
+
const Message = ({ message, author }) => {
|
|
434
|
+
const ref = useRef(null);
|
|
435
|
+
useLayoutEffect(() => {
|
|
436
|
+
const el = ref.current;
|
|
437
|
+
if (!el || !messageHtml) return;
|
|
438
|
+
fillInto(el, messageHtml, {
|
|
439
|
+
author: author.name,
|
|
440
|
+
avatar: initials(author.name),
|
|
441
|
+
body: contentToText(message.content),
|
|
442
|
+
time: fmtTime(message.atMs)
|
|
443
|
+
});
|
|
444
|
+
}, [message, author]);
|
|
445
|
+
return /* @__PURE__ */ jsx("div", { ref, style: revealStyle(message.revealProgress) });
|
|
446
|
+
};
|
|
447
|
+
const SystemMessage = ({ message }) => {
|
|
448
|
+
const ref = useRef(null);
|
|
449
|
+
useLayoutEffect(() => {
|
|
450
|
+
const el = ref.current;
|
|
451
|
+
if (!el || !systemHtml) return;
|
|
452
|
+
fillInto(el, systemHtml, { body: contentToText(message.content) });
|
|
453
|
+
}, [message]);
|
|
454
|
+
if (systemHtml) {
|
|
455
|
+
return /* @__PURE__ */ jsx("div", { ref, style: revealStyle(message.revealProgress) });
|
|
456
|
+
}
|
|
457
|
+
return /* @__PURE__ */ jsx(
|
|
458
|
+
"div",
|
|
459
|
+
{
|
|
460
|
+
style: {
|
|
461
|
+
...revealStyle(message.revealProgress),
|
|
462
|
+
textAlign: "center"
|
|
463
|
+
},
|
|
464
|
+
children: contentToText(message.content)
|
|
465
|
+
}
|
|
466
|
+
);
|
|
467
|
+
};
|
|
468
|
+
const TypingIndicator = ({ author }) => {
|
|
469
|
+
const ref = useRef(null);
|
|
470
|
+
useLayoutEffect(() => {
|
|
471
|
+
const el = ref.current;
|
|
472
|
+
if (!el || !typingHtml) return;
|
|
473
|
+
fillInto(el, typingHtml, { author: author.name });
|
|
474
|
+
}, [author]);
|
|
475
|
+
if (typingHtml) {
|
|
476
|
+
return /* @__PURE__ */ jsx("div", { ref });
|
|
477
|
+
}
|
|
478
|
+
return /* @__PURE__ */ jsxs("div", { style: { opacity: 0.7, fontStyle: "italic" }, children: [
|
|
479
|
+
author.name,
|
|
480
|
+
" is typing\u2026"
|
|
481
|
+
] });
|
|
482
|
+
};
|
|
483
|
+
const Composer = ({ composer }) => {
|
|
484
|
+
const ref = useRef(null);
|
|
485
|
+
useLayoutEffect(() => {
|
|
486
|
+
const el = ref.current;
|
|
487
|
+
if (!el) return;
|
|
488
|
+
if (composerHtml) {
|
|
489
|
+
fillInto(el, composerHtml, { composer: composer.text });
|
|
490
|
+
} else {
|
|
491
|
+
el.textContent = composer.text;
|
|
492
|
+
}
|
|
493
|
+
}, [composer]);
|
|
494
|
+
return /* @__PURE__ */ jsx("div", { ref });
|
|
495
|
+
};
|
|
496
|
+
const Avatar = ({
|
|
497
|
+
participant,
|
|
498
|
+
size = 36
|
|
499
|
+
}) => /* @__PURE__ */ jsx(
|
|
500
|
+
"div",
|
|
501
|
+
{
|
|
502
|
+
style: {
|
|
503
|
+
width: size,
|
|
504
|
+
height: size,
|
|
505
|
+
borderRadius: "50%",
|
|
506
|
+
display: "grid",
|
|
507
|
+
placeItems: "center",
|
|
508
|
+
background: "var(--color-1, #ccc)",
|
|
509
|
+
fontSize: size * 0.4
|
|
510
|
+
},
|
|
511
|
+
children: initials(participant.name)
|
|
512
|
+
}
|
|
513
|
+
);
|
|
514
|
+
const components = {
|
|
515
|
+
Frame,
|
|
516
|
+
Message,
|
|
517
|
+
SystemMessage,
|
|
518
|
+
TypingIndicator,
|
|
519
|
+
Reaction: () => null,
|
|
520
|
+
Composer,
|
|
521
|
+
Avatar
|
|
522
|
+
};
|
|
523
|
+
return {
|
|
524
|
+
id: opts.id,
|
|
525
|
+
meta: {
|
|
526
|
+
name: draft.meta.name,
|
|
527
|
+
defaultCanvas: draft.meta.canvas ?? { width: 420, height: 720 },
|
|
528
|
+
supportsThemes,
|
|
529
|
+
capabilities: opts.capabilities
|
|
530
|
+
},
|
|
531
|
+
components,
|
|
532
|
+
tokens: { light: lightTokens, dark: darkTokens }
|
|
533
|
+
};
|
|
534
|
+
}
|
|
303
535
|
|
|
304
|
-
export { MessageContent, ThemeProvider, TypecaastStage, TypingDots, backEaseOut, clamp01, defineSkin, easeOutCubic, fadeSlideIn, loadSkinFonts, popIn, useTheme, useTokens };
|
|
536
|
+
export { MessageContent, ThemeProvider, TypecaastStage, TypingDots, backEaseOut, clamp01, defineSkin, easeOutCubic, fadeSlideIn, loadSkinFonts, popIn, slotSkinFromDraft, useTheme, useTokens };
|
|
305
537
|
//# sourceMappingURL=index.js.map
|
|
306
538
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/define-skin.ts","../src/theme.tsx","../src/fonts.ts","../src/animation.tsx","../src/content.tsx","../src/stage.tsx"],"names":["jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { Skin } from \"./types.js\";\n\n/**\n * Identity helper for type-safety and a single registration point when\n * authoring a skin. Keeping it a function (rather than a bare object) lets us\n * add validation/registration later without changing skin call sites.\n */\nexport function defineSkin(skin: Skin): Skin {\n return skin;\n}\n","import {\n createContext,\n useContext,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\nimport type { SkinTokens } from \"./types.js\";\n\ninterface ThemeContextValue {\n theme: ResolvedTheme;\n tokens?: SkinTokens;\n}\n\nconst ThemeContext = createContext<ThemeContextValue>({ theme: \"light\" });\n\nexport interface ThemeProviderProps {\n theme: ResolvedTheme;\n /** Resolved per-theme tokens for the active skin. */\n tokens?: SkinTokens;\n children?: ReactNode;\n}\n\n/** Provides the resolved theme + tokens to every skin component below it. */\nexport function ThemeProvider({\n theme,\n tokens,\n children,\n}: ThemeProviderProps): ReactElement {\n return (\n <ThemeContext.Provider value={{ theme, tokens }}>\n {children}\n </ThemeContext.Provider>\n );\n}\n\n/** The resolved theme (`\"light\" | \"dark\"`) for the current subtree. */\nexport function useTheme(): ResolvedTheme {\n return useContext(ThemeContext).theme;\n}\n\n/** The resolved design tokens for the current subtree, if provided. */\nexport function useTokens(): SkinTokens | undefined {\n return useContext(ThemeContext).tokens;\n}\n","import type { FontDeclaration } from \"./types.js\";\n\n/** Build a CSS `src:` value from a font's sources. */\nfunction srcValue(decl: FontDeclaration): string {\n return decl.sources\n .map((s) => {\n const format = s.format ? ` format(\"${s.format}\")` : \"\";\n return `url(\"${s.url}\")${format}`;\n })\n .join(\", \");\n}\n\n/**\n * Load a skin's declared web fonts so live preview matches the platform (not\n * just video export). SSR-safe: a no-op when `document`/`FontFace` are absent\n * (server render, Remotion Node). Idempotent per family+weight+style.\n *\n * Returns once every face has loaded (or immediately, off the main document).\n */\nexport async function loadSkinFonts(\n fonts: FontDeclaration[] | undefined,\n): Promise<void> {\n if (!fonts || fonts.length === 0) return;\n if (\n typeof document === \"undefined\" ||\n typeof FontFace === \"undefined\" ||\n !document.fonts\n ) {\n return;\n }\n\n const pending: Promise<unknown>[] = [];\n for (const decl of fonts) {\n for (const source of decl.sources) {\n const descriptors: FontFaceDescriptors = {};\n if (source.weight !== undefined)\n descriptors.weight = String(source.weight);\n if (source.style !== undefined) descriptors.style = source.style;\n\n const single: FontDeclaration = { ...decl, sources: [source] };\n const face = new FontFace(decl.family, srcValue(single), descriptors);\n\n // Skip if an identical face is already registered.\n const already = [...document.fonts].some(\n (f) =>\n f.family === decl.family &&\n f.weight === (descriptors.weight ?? \"normal\") &&\n f.style === (descriptors.style ?? \"normal\"),\n );\n if (already) continue;\n\n document.fonts.add(face);\n pending.push(face.load());\n }\n }\n await Promise.all(pending);\n}\n","import type { CSSProperties, ReactElement, ReactNode } from \"react\";\n\n/**\n * Animation primitives are **pure functions of progress** (0..1), not CSS\n * transitions or JS timers — so the React preview and the Remotion render\n * animate identically frame-for-frame (PLAN §7). Skins call these driven by\n * `revealProgress` / typing `progress` from `SimState`.\n */\n\nexport const clamp01 = (x: number): number => (x < 0 ? 0 : x > 1 ? 1 : x);\n\n/** Decelerating ease, good for reveals. */\nexport const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3);\n\n/** Back-ease-out with overshoot, good for pops. Lands exactly on 1 at t=1. */\nexport function backEaseOut(t: number, tension = 2.2): number {\n const c3 = tension + 1;\n return 1 + c3 * Math.pow(t - 1, 3) + tension * Math.pow(t - 1, 2);\n}\n\nexport interface FadeSlideOptions {\n /** Slide distance in px at progress 0 (default 8). */\n distance?: number;\n /** Axis to slide along (default \"y\"). */\n axis?: \"x\" | \"y\";\n easing?: (t: number) => number;\n}\n\n/** Fade + slide-in reveal. `progress` 0 → hidden+offset, 1 → shown+settled. */\nexport function fadeSlideIn(\n progress: number,\n options: FadeSlideOptions = {},\n): CSSProperties {\n const eased = (options.easing ?? easeOutCubic)(clamp01(progress));\n const distance = options.distance ?? 8;\n const offset = (1 - eased) * distance;\n const translate =\n options.axis === \"x\"\n ? `translateX(${offset}px)`\n : `translateY(${offset}px)`;\n return { opacity: eased, transform: translate };\n}\n\nexport interface PopOptions {\n /** Overshoot tension (default 2.2 ≈ ~10% overshoot). */\n tension?: number;\n}\n\n/** Scale pop-in (with a little overshoot), good for reactions landing. */\nexport function popIn(\n progress: number,\n options: PopOptions = {},\n): CSSProperties {\n const p = clamp01(progress);\n const scale = backEaseOut(p, options.tension ?? 2.2);\n return {\n opacity: clamp01(p * 3),\n transform: `scale(${scale})`,\n transformOrigin: \"center\",\n };\n}\n\nexport interface TypingDotsProps {\n /** 0..1 progress through the indicator's shown duration. */\n progress?: number;\n count?: number;\n /** Bounce cycles across the full progress (default 4). */\n cycles?: number;\n /** Dot color (default `currentColor`). */\n color?: string;\n /** Dot diameter in px (default 6). */\n size?: number;\n /** Gap between dots in px (default 4). */\n gap?: number;\n}\n\n/**\n * The three-dot bouncing typing indicator, animated purely from `progress`\n * (deterministic per frame). Skins style it via props or wrap it.\n */\nexport function TypingDots({\n progress = 0,\n count = 3,\n cycles = 4,\n color = \"currentColor\",\n size = 6,\n gap = 4,\n}: TypingDotsProps): ReactElement {\n const phase = clamp01(progress) * cycles * Math.PI * 2;\n const dots: ReactNode[] = [];\n for (let i = 0; i < count; i++) {\n const wave = Math.sin(phase - i * 0.9);\n const lift = Math.max(0, wave) * 4;\n const opacity = 0.4 + Math.max(0, wave) * 0.6;\n dots.push(\n <span\n key={i}\n style={{\n width: size,\n height: size,\n borderRadius: \"50%\",\n background: color,\n transform: `translateY(${-lift}px)`,\n opacity,\n }}\n />,\n );\n }\n return (\n <span style={{ display: \"inline-flex\", alignItems: \"flex-end\", gap }}>\n {dots}\n </span>\n );\n}\n","import type { CSSProperties, ReactElement, ReactNode } from \"react\";\nimport type {\n ContentNode,\n ImageNode,\n InlineNode,\n TextNode,\n} from \"@typecaast/schema\";\n\nexport interface ContentClassNames {\n text?: string;\n link?: string;\n mention?: string;\n code?: string;\n emoji?: string;\n image?: string;\n}\n\n/** Per-mark inline styles, so skins can theme marks without a CSS file. */\nexport interface ContentStyles {\n text?: CSSProperties;\n link?: CSSProperties;\n mention?: CSSProperties;\n code?: CSSProperties;\n emoji?: CSSProperties;\n}\n\nexport interface MessageContentProps {\n nodes: ContentNode[];\n /** Per-mark class names so skins style marks with their own CSS. */\n classNames?: ContentClassNames;\n /** Per-mark inline styles (merged with the defaults). */\n styles?: ContentStyles;\n /** Extra style for in-message images (skins set radius, max size, etc.). */\n imageStyle?: CSSProperties;\n}\n\nfunction renderInline(\n span: InlineNode,\n key: number,\n cn: ContentClassNames,\n st: ContentStyles,\n): ReactNode {\n switch (span.type) {\n case \"text\":\n return span.value;\n case \"code\":\n return (\n <code key={key} data-tc-mark=\"code\" className={cn.code} style={st.code}>\n {span.value}\n </code>\n );\n case \"link\":\n return (\n <a\n key={key}\n data-tc-mark=\"link\"\n className={cn.link}\n style={st.link}\n href={span.href}\n rel=\"noreferrer\"\n >\n {span.label ?? span.href}\n </a>\n );\n case \"mention\":\n return (\n <span\n key={key}\n data-tc-mark=\"mention\"\n className={cn.mention}\n style={st.mention}\n >\n {span.label}\n </span>\n );\n case \"emoji\":\n return (\n <span\n key={key}\n data-tc-mark=\"emoji\"\n className={cn.emoji}\n style={st.emoji}\n >\n {span.value}\n </span>\n );\n }\n}\n\nfunction renderImage(\n node: ImageNode,\n key: number,\n cn: ContentClassNames,\n imageStyle?: CSSProperties,\n): ReactNode {\n return (\n <img\n key={key}\n data-tc-node=\"image\"\n className={cn.image}\n src={node.src}\n alt={node.alt ?? \"\"}\n width={node.width}\n height={node.height}\n style={{ maxWidth: \"100%\", display: \"block\", ...imageStyle }}\n />\n );\n}\n\n/**\n * Render a message body (`ContentNode[]`) to React: text nodes with inline\n * marks (code/link/mention/emoji) and in-message images. Unknown node types are\n * skipped (forward-compatible — PLAN §6). SSR-safe, so it renders identically\n * in the browser and in Remotion's Node renderer. Skins style via `classNames`.\n */\nexport function MessageContent({\n nodes,\n classNames = {},\n styles = {},\n imageStyle,\n}: MessageContentProps): ReactElement {\n return (\n <>\n {nodes.map((node, i) => {\n if (node.type === \"text\") {\n const text = node as TextNode;\n return (\n <span\n key={i}\n data-tc-node=\"text\"\n className={classNames.text}\n style={styles.text}\n >\n {text.spans.map((span, j) =>\n renderInline(span, j, classNames, styles),\n )}\n </span>\n );\n }\n if (node.type === \"image\") {\n return renderImage(node as ImageNode, i, classNames, imageStyle);\n }\n return null; // unknown future node type — skipped\n })}\n </>\n );\n}\n","import { useMemo, type ReactElement } from \"react\";\nimport type { ComposerMode, Participant } from \"@typecaast/schema\";\nimport type { SimState } from \"@typecaast/core\";\nimport { ThemeProvider } from \"./theme.js\";\nimport type { Skin } from \"./types.js\";\n\nexport type { ComposerMode };\n\n// A subtle, native-feeling scrollbar for the scrollable thread, scoped to the\n// thread viewport and injected inline so the embed needs no external stylesheet.\n// A neutral translucent grey reads on both light and dark skins and deepens on\n// hover; WebKit/Blink get the thin overlay-style thumb, Firefox uses the\n// standard `scrollbar-*` props. The selector is global on purpose — identical\n// rules across multiple embeds on a page are harmless and keep them consistent.\nconst THREAD_SCROLLBAR_CSS = `\n[data-typecaast-thread]{scrollbar-width:thin;scrollbar-color:rgba(128,128,128,.4) transparent}\n[data-typecaast-thread]::-webkit-scrollbar{width:8px;height:8px}\n[data-typecaast-thread]::-webkit-scrollbar-track{background:transparent}\n[data-typecaast-thread]::-webkit-scrollbar-thumb{background-color:rgba(128,128,128,.4);border-radius:8px;border:2px solid transparent;background-clip:padding-box}\n[data-typecaast-thread]:hover::-webkit-scrollbar-thumb{background-color:rgba(128,128,128,.6)}\n`;\n\nexport interface TypecaastStageProps {\n state: SimState;\n skin: Skin;\n participants: Participant[];\n /** Skin-specific options from `meta.skin.options`. */\n options?: Record<string, unknown>;\n /**\n * Composer visibility: `auto` (default) shows it only while someone is\n * typing/sending; `always` keeps the reply box visible (idle = placeholder);\n * `never` hides it.\n */\n composer?: ComposerMode;\n}\n\n/**\n * Maps a `SimState` onto a skin's components: a `Frame` wrapping the thread\n * items (Message / SystemMessage), the typing indicators, and the composer.\n * Reactions render inside the skin's `Message` (it reads `message.reactions`).\n *\n * Lives in `skin-kit` (the contract layer) so both `@typecaast/react` and the\n * skins' own stories can render a frame without depending on the React player.\n */\nexport function TypecaastStage({\n state,\n skin,\n participants,\n options,\n composer = \"auto\",\n}: TypecaastStageProps): ReactElement {\n const theme = state.theme;\n const byId = useMemo(() => {\n const map = new Map<string, Participant>();\n for (const p of participants) map.set(p.id, p);\n return map;\n }, [participants]);\n\n const { Frame, Message, SystemMessage, TypingIndicator, Composer } =\n skin.components;\n const tokens = skin.tokens?.[theme];\n // `always` keeps the reply box mounted even when idle (falls back to the self\n // participant so a placeholder shows); `auto` only shows it while composing.\n const selfParticipant = useMemo(\n () => participants.find((p) => p.isSelf),\n [participants],\n );\n const composerAuthor = state.composer.from\n ? byId.get(state.composer.from)\n : composer === \"always\"\n ? selfParticipant\n : undefined;\n const showComposer = composer !== \"never\" && composerAuthor !== undefined;\n\n // \"X is typing…\" indicators, minus the viewer's own (you never see yourself\n // typing — that's what the composer shows). Placement is skin-driven: inline\n // in the thread by default, or below the composer (Slack).\n const typingPlacement = skin.meta.typingPlacement ?? \"thread\";\n const typingNodes = state.typingIndicators\n .map((typing, i) => {\n const author = byId.get(typing.from);\n if (!author || author.isSelf) return null;\n return (\n <TypingIndicator\n key={`typing-${typing.from}-${i}`}\n theme={theme}\n typing={typing}\n author={author}\n />\n );\n })\n .filter(Boolean);\n\n return (\n <ThemeProvider theme={theme} tokens={tokens}>\n <style>{THREAD_SCROLLBAR_CSS}</style>\n <Frame theme={theme} options={options} composer={composer}>\n {/* Thread viewport. `column-reverse` pins the conversation to the\n bottom (newest message + composer sit at the bottom, older items\n \"shift up\", PLAN §7) and, once the thread outgrows the height, makes\n it scroll from the bottom with the top reachable — entirely in CSS,\n so it renders identically in a live embed, an SSR page, and a video\n frame (no scroll-to-bottom effect, which wouldn't run before a\n Remotion screenshot). Because `column-reverse` lays the first child\n out at the bottom, children render newest-first: the typing\n indicator (most recent activity) first, then messages reversed. The\n engine's scroll.targetOffset is honored here once real layout\n measurement lands. */}\n <div\n data-typecaast-thread=\"\"\n style={{\n display: \"flex\",\n flexDirection: \"column-reverse\",\n flex: \"1 1 auto\",\n minHeight: 0,\n overflowY: \"auto\",\n // Scrollbar styling lives in THREAD_SCROLLBAR_CSS (the `<style>`\n // above) so it can reach the WebKit pseudo-elements.\n // Breathing room beneath the last message — keeps it off the\n // composer (when shown) and the Frame's bottom edge (when hidden).\n paddingBottom: 16,\n }}\n >\n {typingPlacement === \"thread\" ? typingNodes : null}\n {state.messages\n .map((message, i) => {\n const author = byId.get(message.from);\n if (!author) return null;\n // Index-disambiguated so a config with duplicate message ids can't\n // collide React keys (the builder can produce them transiently).\n const key = `${message.id}-${i}`;\n if (message.variant === \"system\") {\n return (\n <SystemMessage\n key={key}\n theme={theme}\n message={message}\n author={author}\n />\n );\n }\n // Grouping looks at the chronological predecessor — computed\n // before the reverse below, so author-collapsing is unaffected.\n const prev = state.messages[i - 1];\n const previousAuthor = prev ? byId.get(prev.from) : undefined;\n return (\n <Message\n key={key}\n theme={theme}\n message={message}\n author={author}\n previousAuthor={previousAuthor}\n />\n );\n })\n .reverse()}\n </div>\n {showComposer ? (\n <Composer\n theme={theme}\n composer={state.composer}\n author={composerAuthor}\n options={options}\n />\n ) : null}\n {typingPlacement === \"below-composer\" ? typingNodes : null}\n </Frame>\n </ThemeProvider>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/define-skin.ts","../src/theme.tsx","../src/fonts.ts","../src/animation.tsx","../src/content.tsx","../src/stage.tsx","../src/slot-skin.tsx"],"names":["jsx","jsxs"],"mappings":";;;;;AAOO,SAAS,WAAW,IAAA,EAAkB;AAC3C,EAAA,OAAO,IAAA;AACT;ACKA,IAAM,YAAA,GAAe,aAAA,CAAiC,EAAE,KAAA,EAAO,SAAS,CAAA;AAUjE,SAAS,aAAA,CAAc;AAAA,EAC5B,KAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAAqC;AACnC,EAAA,uBACE,GAAA,CAAC,aAAa,QAAA,EAAb,EAAsB,OAAO,EAAE,KAAA,EAAO,MAAA,EAAO,EAC3C,QAAA,EACH,CAAA;AAEJ;AAGO,SAAS,QAAA,GAA0B;AACxC,EAAA,OAAO,UAAA,CAAW,YAAY,CAAA,CAAE,KAAA;AAClC;AAGO,SAAS,SAAA,GAAoC;AAClD,EAAA,OAAO,UAAA,CAAW,YAAY,CAAA,CAAE,MAAA;AAClC;;;ACzCA,SAAS,SAAS,IAAA,EAA+B;AAC/C,EAAA,OAAO,IAAA,CAAK,OAAA,CACT,GAAA,CAAI,CAAC,CAAA,KAAM;AACV,IAAA,MAAM,SAAS,CAAA,CAAE,MAAA,GAAS,CAAA,SAAA,EAAY,CAAA,CAAE,MAAM,CAAA,EAAA,CAAA,GAAO,EAAA;AACrD,IAAA,OAAO,CAAA,KAAA,EAAQ,CAAA,CAAE,GAAG,CAAA,EAAA,EAAK,MAAM,CAAA,CAAA;AAAA,EACjC,CAAC,CAAA,CACA,IAAA,CAAK,IAAI,CAAA;AACd;AASA,eAAsB,cACpB,KAAA,EACe;AACf,EAAA,IAAI,CAAC,KAAA,IAAS,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG;AAClC,EAAA,IACE,OAAO,aAAa,WAAA,IACpB,OAAO,aAAa,WAAA,IACpB,CAAC,SAAS,KAAA,EACV;AACA,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,UAA8B,EAAC;AACrC,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,KAAA,MAAW,MAAA,IAAU,KAAK,OAAA,EAAS;AACjC,MAAA,MAAM,cAAmC,EAAC;AAC1C,MAAA,IAAI,OAAO,MAAA,KAAW,MAAA;AACpB,QAAA,WAAA,CAAY,MAAA,GAAS,MAAA,CAAO,MAAA,CAAO,MAAM,CAAA;AAC3C,MAAA,IAAI,MAAA,CAAO,KAAA,KAAU,MAAA,EAAW,WAAA,CAAY,QAAQ,MAAA,CAAO,KAAA;AAE3D,MAAA,MAAM,SAA0B,EAAE,GAAG,MAAM,OAAA,EAAS,CAAC,MAAM,CAAA,EAAE;AAC7D,MAAA,MAAM,IAAA,GAAO,IAAI,QAAA,CAAS,IAAA,CAAK,QAAQ,QAAA,CAAS,MAAM,GAAG,WAAW,CAAA;AAGpE,MAAA,MAAM,OAAA,GAAU,CAAC,GAAG,QAAA,CAAS,KAAK,CAAA,CAAE,IAAA;AAAA,QAClC,CAAC,CAAA,KACC,CAAA,CAAE,MAAA,KAAW,KAAK,MAAA,IAClB,CAAA,CAAE,MAAA,MAAY,WAAA,CAAY,MAAA,IAAU,QAAA,CAAA,IACpC,CAAA,CAAE,KAAA,MAAW,YAAY,KAAA,IAAS,QAAA;AAAA,OACtC;AACA,MAAA,IAAI,OAAA,EAAS;AAEb,MAAA,QAAA,CAAS,KAAA,CAAM,IAAI,IAAI,CAAA;AACvB,MAAA,OAAA,CAAQ,IAAA,CAAK,IAAA,CAAK,IAAA,EAAM,CAAA;AAAA,IAC1B;AAAA,EACF;AACA,EAAA,MAAM,OAAA,CAAQ,IAAI,OAAO,CAAA;AAC3B;AC/CO,IAAM,OAAA,GAAU,CAAC,CAAA,KAAuB,CAAA,GAAI,IAAI,CAAA,GAAI,CAAA,GAAI,IAAI,CAAA,GAAI;AAGhE,IAAM,YAAA,GAAe,CAAC,CAAA,KAAsB,CAAA,GAAI,KAAK,GAAA,CAAI,CAAA,GAAI,GAAG,CAAC;AAGjE,SAAS,WAAA,CAAY,CAAA,EAAW,OAAA,GAAU,GAAA,EAAa;AAC5D,EAAA,MAAM,KAAK,OAAA,GAAU,CAAA;AACrB,EAAA,OAAO,CAAA,GAAI,EAAA,GAAK,IAAA,CAAK,GAAA,CAAI,CAAA,GAAI,CAAA,EAAG,CAAC,CAAA,GAAI,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,CAAA,GAAI,GAAG,CAAC,CAAA;AAClE;AAWO,SAAS,WAAA,CACd,QAAA,EACA,OAAA,GAA4B,EAAC,EACd;AACf,EAAA,MAAM,SAAS,OAAA,CAAQ,MAAA,IAAU,YAAA,EAAc,OAAA,CAAQ,QAAQ,CAAC,CAAA;AAChE,EAAA,MAAM,QAAA,GAAW,QAAQ,QAAA,IAAY,CAAA;AACrC,EAAA,MAAM,MAAA,GAAA,CAAU,IAAI,KAAA,IAAS,QAAA;AAM7B,EAAA,IAAI,WAAW,CAAA,EAAG,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,WAAW,MAAA,EAAO;AAC7D,EAAA,MAAM,SAAA,GACJ,QAAQ,IAAA,KAAS,GAAA,GACb,cAAc,MAAM,CAAA,GAAA,CAAA,GACpB,cAAc,MAAM,CAAA,GAAA,CAAA;AAC1B,EAAA,OAAO,EAAE,OAAA,EAAS,KAAA,EAAO,SAAA,EAAW,SAAA,EAAU;AAChD;AAQO,SAAS,KAAA,CACd,QAAA,EACA,OAAA,GAAsB,EAAC,EACR;AACf,EAAA,MAAM,CAAA,GAAI,QAAQ,QAAQ,CAAA;AAC1B,EAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,CAAA,EAAG,OAAA,CAAQ,WAAW,GAAG,CAAA;AACnD,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,OAAA,CAAQ,CAAA,GAAI,CAAC,CAAA;AAAA,IACtB,SAAA,EAAW,SAAS,KAAK,CAAA,CAAA,CAAA;AAAA,IACzB,eAAA,EAAiB;AAAA,GACnB;AACF;AAoBO,SAAS,UAAA,CAAW;AAAA,EACzB,QAAA,GAAW,CAAA;AAAA,EACX,KAAA,GAAQ,CAAA;AAAA,EACR,MAAA,GAAS,CAAA;AAAA,EACT,KAAA,GAAQ,cAAA;AAAA,EACR,IAAA,GAAO,CAAA;AAAA,EACP,GAAA,GAAM;AACR,CAAA,EAAkC;AAChC,EAAA,MAAM,QAAQ,OAAA,CAAQ,QAAQ,CAAA,GAAI,MAAA,GAAS,KAAK,EAAA,GAAK,CAAA;AACrD,EAAA,MAAM,OAAoB,EAAC;AAC3B,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,EAAO,CAAA,EAAA,EAAK;AAC9B,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,KAAA,GAAQ,IAAI,GAAG,CAAA;AACrC,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAI,CAAA,GAAI,CAAA;AACjC,IAAA,MAAM,UAAU,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAI,CAAA,GAAI,GAAA;AAC1C,IAAA,IAAA,CAAK,IAAA;AAAA,sBACHA,GAAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UAEC,KAAA,EAAO;AAAA,YACL,KAAA,EAAO,IAAA;AAAA,YACP,MAAA,EAAQ,IAAA;AAAA,YACR,YAAA,EAAc,KAAA;AAAA,YACd,UAAA,EAAY,KAAA;AAAA,YACZ,SAAA,EAAW,CAAA,WAAA,EAAc,CAAC,IAAI,CAAA,GAAA,CAAA;AAAA,YAC9B;AAAA;AACF,SAAA;AAAA,QARK;AAAA;AASP,KACF;AAAA,EACF;AACA,EAAA,uBACEA,GAAAA,CAAC,MAAA,EAAA,EAAK,KAAA,EAAO,EAAE,OAAA,EAAS,aAAA,EAAe,UAAA,EAAY,UAAA,EAAY,GAAA,EAAI,EAChE,QAAA,EAAA,IAAA,EACH,CAAA;AAEJ;ACnFA,SAAS,YAAA,CACP,IAAA,EACA,GAAA,EACA,EAAA,EACA,EAAA,EACW;AACX,EAAA,QAAQ,KAAK,IAAA;AAAM,IACjB,KAAK,MAAA;AACH,MAAA,OAAO,IAAA,CAAK,KAAA;AAAA,IACd,KAAK,MAAA;AACH,MAAA,uBACEA,GAAAA,CAAC,MAAA,EAAA,EAAe,cAAA,EAAa,MAAA,EAAO,SAAA,EAAW,EAAA,CAAG,IAAA,EAAM,KAAA,EAAO,EAAA,CAAG,IAAA,EAC/D,QAAA,EAAA,IAAA,CAAK,SADG,GAEX,CAAA;AAAA,IAEJ,KAAK,MAAA;AACH,MAAA,uBACEA,GAAAA;AAAA,QAAC,GAAA;AAAA,QAAA;AAAA,UAEC,cAAA,EAAa,MAAA;AAAA,UACb,WAAW,EAAA,CAAG,IAAA;AAAA,UACd,OAAO,EAAA,CAAG,IAAA;AAAA,UACV,MAAM,IAAA,CAAK,IAAA;AAAA,UACX,GAAA,EAAI,YAAA;AAAA,UAEH,QAAA,EAAA,IAAA,CAAK,SAAS,IAAA,CAAK;AAAA,SAAA;AAAA,QAPf;AAAA,OAQP;AAAA,IAEJ,KAAK,SAAA;AACH,MAAA,uBACEA,GAAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UAEC,cAAA,EAAa,SAAA;AAAA,UACb,WAAW,EAAA,CAAG,OAAA;AAAA,UACd,OAAO,EAAA,CAAG,OAAA;AAAA,UAET,QAAA,EAAA,IAAA,CAAK;AAAA,SAAA;AAAA,QALD;AAAA,OAMP;AAAA,IAEJ,KAAK,OAAA;AACH,MAAA,uBACEA,GAAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UAEC,cAAA,EAAa,OAAA;AAAA,UACb,WAAW,EAAA,CAAG,KAAA;AAAA,UACd,OAAO,EAAA,CAAG,KAAA;AAAA,UAET,QAAA,EAAA,IAAA,CAAK;AAAA,SAAA;AAAA,QALD;AAAA,OAMP;AAAA;AAGR;AAEA,SAAS,WAAA,CACP,IAAA,EACA,GAAA,EACA,EAAA,EACA,UAAA,EACW;AACX,EAAA,uBACEA,GAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MAEC,cAAA,EAAa,OAAA;AAAA,MACb,WAAW,EAAA,CAAG,KAAA;AAAA,MACd,KAAK,IAAA,CAAK,GAAA;AAAA,MACV,GAAA,EAAK,KAAK,GAAA,IAAO,EAAA;AAAA,MACjB,OAAO,IAAA,CAAK,KAAA;AAAA,MACZ,QAAQ,IAAA,CAAK,MAAA;AAAA,MACb,OAAO,EAAE,QAAA,EAAU,QAAQ,OAAA,EAAS,OAAA,EAAS,GAAG,UAAA;AAAW,KAAA;AAAA,IAPtD;AAAA,GAQP;AAEJ;AAQO,SAAS,cAAA,CAAe;AAAA,EAC7B,KAAA;AAAA,EACA,aAAa,EAAC;AAAA,EACd,SAAS,EAAC;AAAA,EACV;AACF,CAAA,EAAsC;AACpC,EAAA,uBACEA,GAAAA,CAAA,QAAA,EAAA,EACG,gBAAM,GAAA,CAAI,CAAC,MAAM,CAAA,KAAM;AACtB,IAAA,IAAI,IAAA,CAAK,SAAS,MAAA,EAAQ;AACxB,MAAA,MAAM,IAAA,GAAO,IAAA;AACb,MAAA,uBACEA,GAAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UAEC,cAAA,EAAa,MAAA;AAAA,UACb,WAAW,UAAA,CAAW,IAAA;AAAA,UACtB,OAAO,MAAA,CAAO,IAAA;AAAA,UAEb,eAAK,KAAA,CAAM,GAAA;AAAA,YAAI,CAAC,IAAA,EAAM,CAAA,KACrB,aAAa,IAAA,EAAM,CAAA,EAAG,YAAY,MAAM;AAAA;AAC1C,SAAA;AAAA,QAPK;AAAA,OAQP;AAAA,IAEJ;AACA,IAAA,IAAI,IAAA,CAAK,SAAS,OAAA,EAAS;AACzB,MAAA,OAAO,WAAA,CAAY,IAAA,EAAmB,CAAA,EAAG,UAAA,EAAY,UAAU,CAAA;AAAA,IACjE;AACA,IAAA,OAAO,IAAA;AAAA,EACT,CAAC,CAAA,EACH,CAAA;AAEJ;ACpIA,IAAM,oBAAA,GAAuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AA8BtB,SAAS,cAAA,CAAe;AAAA,EAC7B,KAAA;AAAA,EACA,IAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA,GAAW;AACb,CAAA,EAAsC;AACpC,EAAA,MAAM,QAAQ,KAAA,CAAM,KAAA;AACpB,EAAA,MAAM,IAAA,GAAO,QAAQ,MAAM;AACzB,IAAA,MAAM,GAAA,uBAAU,GAAA,EAAyB;AACzC,IAAA,KAAA,MAAW,KAAK,YAAA,EAAc,GAAA,CAAI,GAAA,CAAI,CAAA,CAAE,IAAI,CAAC,CAAA;AAC7C,IAAA,OAAO,GAAA;AAAA,EACT,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAEjB,EAAA,MAAM,EAAE,KAAA,EAAO,OAAA,EAAS,eAAe,eAAA,EAAiB,QAAA,KACtD,IAAA,CAAK,UAAA;AACP,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,GAAS,KAAK,CAAA;AAGlC,EAAA,MAAM,eAAA,GAAkB,OAAA;AAAA,IACtB,MAAM,YAAA,CAAa,IAAA,CAAK,CAAC,CAAA,KAAM,EAAE,MAAM,CAAA;AAAA,IACvC,CAAC,YAAY;AAAA,GACf;AACA,EAAA,MAAM,cAAA,GAAiB,KAAA,CAAM,QAAA,CAAS,IAAA,GAClC,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,QAAA,CAAS,IAAI,CAAA,GAC5B,QAAA,KAAa,QAAA,GACX,eAAA,GACA,MAAA;AACN,EAAA,MAAM,YAAA,GAAe,QAAA,KAAa,OAAA,IAAW,cAAA,KAAmB,MAAA;AAKhE,EAAA,MAAM,eAAA,GAAkB,IAAA,CAAK,IAAA,CAAK,eAAA,IAAmB,QAAA;AACrD,EAAA,MAAM,cAAc,KAAA,CAAM,gBAAA,CACvB,GAAA,CAAI,CAAC,QAAQ,CAAA,KAAM;AAClB,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA;AACnC,IAAA,IAAI,CAAC,MAAA,IAAU,MAAA,CAAO,MAAA,EAAQ,OAAO,IAAA;AACrC,IAAA,uBACEA,GAAAA;AAAA,MAAC,eAAA;AAAA,MAAA;AAAA,QAEC,KAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA,OAAA;AAAA,MAHK,CAAA,OAAA,EAAU,MAAA,CAAO,IAAI,CAAA,CAAA,EAAI,CAAC,CAAA;AAAA,KAIjC;AAAA,EAEJ,CAAC,CAAA,CACA,MAAA,CAAO,OAAO,CAAA;AAEjB,EAAA,uBACE,IAAA,CAAC,aAAA,EAAA,EAAc,KAAA,EAAc,MAAA,EAC3B,QAAA,EAAA;AAAA,oBAAAA,GAAAA,CAAC,WAAO,QAAA,EAAA,oBAAA,EAAqB,CAAA;AAAA,oBAC7B,IAAA,CAAC,KAAA,EAAA,EAAM,KAAA,EAAc,OAAA,EAAkB,QAAA,EAYrC,QAAA,EAAA;AAAA,sBAAA,IAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,uBAAA,EAAsB,EAAA;AAAA,UACtB,KAAA,EAAO;AAAA,YACL,OAAA,EAAS,MAAA;AAAA,YACT,aAAA,EAAe,gBAAA;AAAA,YACf,IAAA,EAAM,UAAA;AAAA,YACN,SAAA,EAAW,CAAA;AAAA,YACX,SAAA,EAAW,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAKX,aAAA,EAAe;AAAA,WACjB;AAAA,UAEC,QAAA,EAAA;AAAA,YAAA,eAAA,KAAoB,WAAW,WAAA,GAAc,IAAA;AAAA,YAC7C,KAAA,CAAM,QAAA,CACJ,GAAA,CAAI,CAAC,SAAS,CAAA,KAAM;AACnB,cAAA,MAAM,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA;AACpC,cAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AAGpB,cAAA,MAAM,GAAA,GAAM,CAAA,EAAG,OAAA,CAAQ,EAAE,IAAI,CAAC,CAAA,CAAA;AAC9B,cAAA,IAAI,OAAA,CAAQ,YAAY,QAAA,EAAU;AAChC,gBAAA,uBACEA,GAAAA;AAAA,kBAAC,aAAA;AAAA,kBAAA;AAAA,oBAEC,KAAA;AAAA,oBACA,OAAA;AAAA,oBACA;AAAA,mBAAA;AAAA,kBAHK;AAAA,iBAIP;AAAA,cAEJ;AAGA,cAAA,MAAM,IAAA,GAAO,KAAA,CAAM,QAAA,CAAS,CAAA,GAAI,CAAC,CAAA;AACjC,cAAA,MAAM,iBAAiB,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,GAAI,MAAA;AACpD,cAAA,uBACEA,GAAAA;AAAA,gBAAC,OAAA;AAAA,gBAAA;AAAA,kBAEC,KAAA;AAAA,kBACA,OAAA;AAAA,kBACA,MAAA;AAAA,kBACA;AAAA,iBAAA;AAAA,gBAJK;AAAA,eAKP;AAAA,YAEJ,CAAC,EACA,OAAA;AAAQ;AAAA;AAAA,OACb;AAAA,MACC,+BACCA,GAAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,KAAA;AAAA,UACA,UAAU,KAAA,CAAM,QAAA;AAAA,UAChB,MAAA,EAAQ,cAAA;AAAA,UACR;AAAA;AAAA,OACF,GACE,IAAA;AAAA,MACH,eAAA,KAAoB,mBAAmB,WAAA,GAAc;AAAA,KAAA,EACxD;AAAA,GAAA,EACF,CAAA;AAEJ;AC7HA,IAAM,SAAA,GAAY,cAAA;AAoClB,SAAS,cAAc,OAAA,EAAgC;AACrD,EAAA,MAAM,MAAgB,EAAC;AACvB,EAAA,KAAA,MAAW,QAAQ,OAAA,EAAS;AAC1B,IAAA,IACE,KAAK,IAAA,KAAS,MAAA,IACd,MAAM,OAAA,CAAS,IAAA,CAA6B,KAAK,CAAA,EACjD;AACA,MAAA,KAAA,MAAW,IAAA,IACT,KACA,KAAA,EAAO;AACP,QAAA,GAAA,CAAI,IAAA,CAAK,IAAA,CAAK,KAAA,IAAS,IAAA,CAAK,SAAS,EAAE,CAAA;AAAA,MACzC;AAAA,IACF,CAAA,MAAA,IAAW,IAAA,CAAK,IAAA,KAAS,OAAA,EAAS;AAChC,MAAA,GAAA,CAAI,IAAA,CAAM,IAAA,CAA0B,GAAA,IAAO,WAAI,CAAA;AAAA,IACjD;AAAA,EACF;AACA,EAAA,OAAO,GAAA,CAAI,KAAK,EAAE,CAAA;AACpB;AAEA,SAAS,SAAS,IAAA,EAAsB;AACtC,EAAA,OAAO,IAAA,CACJ,MAAM,KAAK,CAAA,CACX,OAAO,OAAO,CAAA,CACd,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA,CACV,IAAI,CAAC,CAAA,KAAM,EAAE,CAAC,CAAA,EAAG,aAAY,IAAK,EAAE,CAAA,CACpC,IAAA,CAAK,EAAE,CAAA;AACZ;AAEA,SAAS,QAAQ,IAAA,EAAsB;AACrC,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,IAAA,GAAO,GAAI,CAAA;AACpC,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,EAAE,CAAA;AAC/B,EAAA,MAAM,IAAI,KAAA,GAAQ,EAAA;AAClB,EAAA,OAAO,CAAA,EAAG,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,CAAC,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA;AAC3C;AAWA,SAAS,SAAA,CACP,MAAA,EACA,GAAA,EACA,UAAA,EACA,WAAA,EACQ;AACR,EAAA,MAAM,IAAA,GAAO,OAAO,OAAA,CAAQ,MAAA,CAAO,UAAU,EAAE,EAC5C,GAAA,CAAI,CAAC,CAAC,CAAA,EAAG,CAAC,MAAM,CAAA,EAAA,EAAK,CAAC,KAAK,CAAC,CAAA,CAAA,CAAG,CAAA,CAC/B,IAAA,CAAK,GAAG,CAAA;AACX,EAAA,MAAM,KAAK,UAAA,EAAY,aAAA,GACnB,CAAA,2BAAA,EAA8B,UAAA,CAAW,aAAa,CAAA,GAAA,CAAA,GACtD,EAAA;AACJ,EAAA,MAAM,KAAA,GAAQ;AAAA,mEAAA,EACqD,IAAI,IAAI,EAAE,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAAA,CAAA;AAS7E,EAAA,MAAM,UAAU,WAAA,GACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAAA,CAAA,GAoBA,EAAA;AACJ,EAAA,OAAO,GAAG,KAAK;AAAA,EAAK,GAAG;AAAA,EAAK,OAAO,CAAA,CAAA;AACrC;AAEA,SAAS,QAAA,CACP,IAAA,EACA,YAAA,EACA,MAAA,EACM;AACN,EAAA,IAAA,CAAK,SAAA,GAAY,YAAA;AACjB,EAAA,KAAA,MAAW,QAAQ,IAAA,CAAK,gBAAA,CAAiB,CAAA,CAAA,EAAI,SAAS,GAAG,CAAA,EAAG;AAC1D,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,YAAA,CAAa,SAAS,CAAA;AACxC,IAAA,IAAI,QAAQ,IAAA,IAAQ,MAAA,OAAa,WAAA,GAAc,MAAA,CAAO,IAAI,CAAA,IAAK,EAAA;AAAA,EACjE;AACF;AAEA,SAAS,YAAY,QAAA,EAAiC;AACpD,EAAA,MAAM,CAAA,GAAI,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,CAAA,EAAG,QAAQ,CAAC,CAAA;AAC3C,EAAA,OAAO;AAAA,IACL,OAAA,EAAS,CAAA;AAAA,IACT,SAAA,EAAW,CAAA,WAAA,EAAA,CAAe,CAAA,GAAI,CAAA,IAAK,CAAC,CAAA,GAAA,CAAA;AAAA,IACpC,UAAA,EAAY;AAAA,GACd;AACF;AAEO,SAAS,iBAAA,CACd,OACA,IAAA,EACM;AACN,EAAA,MAAM,cAA0B,EAAE,MAAA,EAAQ,MAAM,MAAA,CAAO,MAAA,IAAU,EAAC,EAAE;AACpE,EAAA,MAAM,UAAA,GAAyB,KAAA,CAAM,UAAA,GACjC,EAAE,MAAA,EAAQ,MAAM,UAAA,CAAW,MAAA,IAAU,EAAC,EAAE,GACxC,WAAA;AACJ,EAAA,MAAM,WAAA,GAAc,KAAK,WAAA,IAAe,KAAA;AACxC,EAAA,MAAM,UAAA,GAAa;AAAA,IACjB,KAAA,EAAO,SAAA;AAAA,MACL,WAAA;AAAA,MACA,MAAM,GAAA,IAAO,EAAA;AAAA,MACb,MAAM,IAAA,CAAK,UAAA;AAAA,MACX;AAAA,KACF;AAAA,IACA,IAAA,EAAM,SAAA;AAAA,MACJ,UAAA;AAAA,MACA,MAAM,GAAA,IAAO,EAAA;AAAA,MACb,MAAM,IAAA,CAAK,UAAA;AAAA,MACX;AAAA;AACF,GACF;AACA,EAAA,MAAM,cAAA,GAAuC,KAAA,CAAM,UAAA,GAC/C,CAAC,OAAA,EAAS,MAAM,CAAA,GAChB,CAAC,KAAA,CAAM,IAAA,CAAK,KAAA,IAAS,OAAO,CAAA;AAEhC,EAAA,MAAM,SAAA,GAAY,MAAM,KAAA,CAAM,KAAA;AAC9B,EAAA,MAAM,WAAA,GAAc,MAAM,KAAA,CAAM,OAAA;AAChC,EAAA,MAAM,YAAA,GAAe,MAAM,KAAA,CAAM,QAAA;AACjC,EAAA,MAAM,UAAA,GAAa,MAAM,KAAA,CAAM,MAAA;AAC/B,EAAA,MAAM,UAAA,GAAa,MAAM,KAAA,CAAM,MAAA;AAE/B,EAAA,MAAM,QAAmD,CAAC;AAAA,IACxD,KAAA;AAAA,IACA;AAAA,GACF,KAAM;AACJ,IAAA,MAAM,OAAA,GAAU,OAAuB,IAAI,CAAA;AAC3C,IAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAA6B,IAAI,CAAA;AAC3D,IAAA,eAAA,CAAgB,MAAM;AACpB,MAAA,MAAM,OAAO,OAAA,CAAQ,OAAA;AACrB,MAAA,IAAI,CAAC,IAAA,EAAM;AACX,MAAA,MAAM,MAAA,GAAS,KAAK,UAAA,IAAc,IAAA,CAAK,aAAa,EAAE,IAAA,EAAM,QAAQ,CAAA;AACpE,MAAA,MAAA,CAAO,SAAA,GAAY,EAAA;AACnB,MAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,aAAA,CAAc,aAAA,CAAc,OAAO,CAAA;AACtD,MAAA,KAAA,CAAM,WAAA,GAAc,KAAA,KAAU,MAAA,GAAS,UAAA,CAAW,OAAO,UAAA,CAAW,KAAA;AACpE,MAAA,MAAA,CAAO,YAAY,KAAK,CAAA;AAIxB,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,aAAA,CAAc,aAAA,CAAc,KAAK,CAAA;AACtD,MAAA,OAAA,CAAQ,SAAA,GAAY,cAAA;AACpB,MAAA,OAAA,CAAQ,MAAM,KAAA,GAAQ,MAAA;AACtB,MAAA,OAAA,CAAQ,MAAM,MAAA,GAAS,MAAA;AACvB,MAAA,OAAA,CAAQ,MAAM,OAAA,GAAU,MAAA;AACxB,MAAA,OAAA,CAAQ,MAAM,aAAA,GAAgB,QAAA;AAC9B,MAAA,OAAA,CAAQ,MAAM,QAAA,GAAW,QAAA;AACzB,MAAA,OAAA,CAAQ,SAAA,GAAY,SAAA,IAAa,CAAA,KAAA,EAAQ,SAAS,CAAA,kBAAA,CAAA;AAClD,MAAA,MAAA,CAAO,YAAY,OAAO,CAAA;AAC1B,MAAA,MAAM,OACJ,OAAA,CAAQ,aAAA,CAA2B,CAAA,CAAA,EAAI,SAAS,cAAc,CAAA,IAC9D,OAAA;AACF,MAAA,IAAA,CAAK,WAAA,GAAc,EAAA;AACnB,MAAA,QAAA,CAAS,IAAI,CAAA;AAAA,IACf,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AACV,IAAA,uBACEA,GAAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAK,OAAA,EAAS,OAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,MAAA,EAAQ,QAAO,EACvD,QAAA,EAAA,KAAA,GAAQ,aAAa,QAAA,EAAU,KAAK,IAAI,IAAA,EAC3C,CAAA;AAAA,EAEJ,CAAA;AAEA,EAAA,MAAM,OAAA,GAA4B,CAAC,EAAE,OAAA,EAAS,QAAO,KAAM;AACzD,IAAA,MAAM,GAAA,GAAM,OAAuB,IAAI,CAAA;AACvC,IAAA,eAAA,CAAgB,MAAM;AACpB,MAAA,MAAM,KAAK,GAAA,CAAI,OAAA;AACf,MAAA,IAAI,CAAC,EAAA,IAAM,CAAC,WAAA,EAAa;AACzB,MAAA,QAAA,CAAS,IAAI,WAAA,EAAa;AAAA,QACxB,QAAQ,MAAA,CAAO,IAAA;AAAA,QACf,MAAA,EAAQ,QAAA,CAAS,MAAA,CAAO,IAAI,CAAA;AAAA,QAC5B,IAAA,EAAM,aAAA,CAAc,OAAA,CAAQ,OAAO,CAAA;AAAA,QACnC,IAAA,EAAM,OAAA,CAAQ,OAAA,CAAQ,IAAI;AAAA,OAC3B,CAAA;AAAA,IACH,CAAA,EAAG,CAAC,OAAA,EAAS,MAAM,CAAC,CAAA;AACpB,IAAA,uBAAOA,IAAC,KAAA,EAAA,EAAI,GAAA,EAAU,OAAO,WAAA,CAAY,OAAA,CAAQ,cAAc,CAAA,EAAG,CAAA;AAAA,EACpE,CAAA;AAEA,EAAA,MAAM,aAAA,GAAiC,CAAC,EAAE,OAAA,EAAQ,KAAM;AACtD,IAAA,MAAM,GAAA,GAAM,OAAuB,IAAI,CAAA;AACvC,IAAA,eAAA,CAAgB,MAAM;AACpB,MAAA,MAAM,KAAK,GAAA,CAAI,OAAA;AACf,MAAA,IAAI,CAAC,EAAA,IAAM,CAAC,UAAA,EAAY;AACxB,MAAA,QAAA,CAAS,EAAA,EAAI,YAAY,EAAE,IAAA,EAAM,cAAc,OAAA,CAAQ,OAAO,GAAG,CAAA;AAAA,IACnE,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AACZ,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,uBAAOA,IAAC,KAAA,EAAA,EAAI,GAAA,EAAU,OAAO,WAAA,CAAY,OAAA,CAAQ,cAAc,CAAA,EAAG,CAAA;AAAA,IACpE;AACA,IAAA,uBACEA,GAAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAO;AAAA,UACL,GAAG,WAAA,CAAY,OAAA,CAAQ,cAAc,CAAA;AAAA,UACrC,SAAA,EAAW;AAAA,SACb;AAAA,QAEC,QAAA,EAAA,aAAA,CAAc,QAAQ,OAAO;AAAA;AAAA,KAChC;AAAA,EAEJ,CAAA;AAEA,EAAA,MAAM,eAAA,GAAmC,CAAC,EAAE,MAAA,EAAO,KAAM;AACvD,IAAA,MAAM,GAAA,GAAM,OAAuB,IAAI,CAAA;AACvC,IAAA,eAAA,CAAgB,MAAM;AACpB,MAAA,MAAM,KAAK,GAAA,CAAI,OAAA;AACf,MAAA,IAAI,CAAC,EAAA,IAAM,CAAC,UAAA,EAAY;AACxB,MAAA,QAAA,CAAS,IAAI,UAAA,EAAY,EAAE,MAAA,EAAQ,MAAA,CAAO,MAAM,CAAA;AAAA,IAClD,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AACX,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,uBAAOA,GAAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAU,CAAA;AAAA,IACxB;AACA,IAAA,uBACEC,KAAC,KAAA,EAAA,EAAI,KAAA,EAAO,EAAE,OAAA,EAAS,GAAA,EAAK,SAAA,EAAW,QAAA,EAAS,EAC7C,QAAA,EAAA;AAAA,MAAA,MAAA,CAAO,IAAA;AAAA,MAAK;AAAA,KAAA,EACf,CAAA;AAAA,EAEJ,CAAA;AAEA,EAAA,MAAM,QAAA,GAA8B,CAAC,EAAE,QAAA,EAAS,KAAM;AACpD,IAAA,MAAM,GAAA,GAAM,OAAuB,IAAI,CAAA;AACvC,IAAA,eAAA,CAAgB,MAAM;AACpB,MAAA,MAAM,KAAK,GAAA,CAAI,OAAA;AACf,MAAA,IAAI,CAAC,EAAA,EAAI;AACT,MAAA,IAAI,YAAA,EAAc;AAChB,QAAA,QAAA,CAAS,IAAI,YAAA,EAAc,EAAE,QAAA,EAAU,QAAA,CAAS,MAAM,CAAA;AAAA,MACxD,CAAA,MAAO;AACL,QAAA,EAAA,CAAG,cAAc,QAAA,CAAS,IAAA;AAAA,MAC5B;AAAA,IACF,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AACb,IAAA,uBAAOD,GAAAA,CAAC,KAAA,EAAA,EAAI,GAAA,EAAU,CAAA;AAAA,EACxB,CAAA;AAEA,EAAA,MAAM,SAA0D,CAAC;AAAA,IAC/D,WAAA;AAAA,IACA,IAAA,GAAO;AAAA,wBAEPA,GAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,KAAA,EAAO;AAAA,QACL,KAAA,EAAO,IAAA;AAAA,QACP,MAAA,EAAQ,IAAA;AAAA,QACR,YAAA,EAAc,KAAA;AAAA,QACd,OAAA,EAAS,MAAA;AAAA,QACT,UAAA,EAAY,QAAA;AAAA,QACZ,UAAA,EAAY,sBAAA;AAAA,QACZ,UAAU,IAAA,GAAO;AAAA,OACnB;AAAA,MAEC,QAAA,EAAA,QAAA,CAAS,YAAY,IAAI;AAAA;AAAA,GAC5B;AAGF,EAAA,MAAM,UAAA,GAA6B;AAAA,IACjC,KAAA;AAAA,IACA,OAAA;AAAA,IACA,aAAA;AAAA,IACA,eAAA;AAAA,IACA,UAAU,MAAM,IAAA;AAAA,IAChB,QAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,OAAO;AAAA,IACL,IAAI,IAAA,CAAK,EAAA;AAAA,IACT,IAAA,EAAM;AAAA,MACJ,IAAA,EAAM,MAAM,IAAA,CAAK,IAAA;AAAA,MACjB,aAAA,EAAe,MAAM,IAAA,CAAK,MAAA,IAAU,EAAE,KAAA,EAAO,GAAA,EAAK,QAAQ,GAAA,EAAI;AAAA,MAC9D,cAAA;AAAA,MACA,cAAc,IAAA,CAAK;AAAA,KACrB;AAAA,IACA,UAAA;AAAA,IACA,MAAA,EAAQ,EAAE,KAAA,EAAO,WAAA,EAAa,MAAM,UAAA;AAAW,GACjD;AACF","file":"index.js","sourcesContent":["import type { Skin } from \"./types.js\";\n\n/**\n * Identity helper for type-safety and a single registration point when\n * authoring a skin. Keeping it a function (rather than a bare object) lets us\n * add validation/registration later without changing skin call sites.\n */\nexport function defineSkin(skin: Skin): Skin {\n return skin;\n}\n","import {\n createContext,\n useContext,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport type { ResolvedTheme } from \"@typecaast/core\";\nimport type { SkinTokens } from \"./types.js\";\n\ninterface ThemeContextValue {\n theme: ResolvedTheme;\n tokens?: SkinTokens;\n}\n\nconst ThemeContext = createContext<ThemeContextValue>({ theme: \"light\" });\n\nexport interface ThemeProviderProps {\n theme: ResolvedTheme;\n /** Resolved per-theme tokens for the active skin. */\n tokens?: SkinTokens;\n children?: ReactNode;\n}\n\n/** Provides the resolved theme + tokens to every skin component below it. */\nexport function ThemeProvider({\n theme,\n tokens,\n children,\n}: ThemeProviderProps): ReactElement {\n return (\n <ThemeContext.Provider value={{ theme, tokens }}>\n {children}\n </ThemeContext.Provider>\n );\n}\n\n/** The resolved theme (`\"light\" | \"dark\"`) for the current subtree. */\nexport function useTheme(): ResolvedTheme {\n return useContext(ThemeContext).theme;\n}\n\n/** The resolved design tokens for the current subtree, if provided. */\nexport function useTokens(): SkinTokens | undefined {\n return useContext(ThemeContext).tokens;\n}\n","import type { FontDeclaration } from \"./types.js\";\n\n/** Build a CSS `src:` value from a font's sources. */\nfunction srcValue(decl: FontDeclaration): string {\n return decl.sources\n .map((s) => {\n const format = s.format ? ` format(\"${s.format}\")` : \"\";\n return `url(\"${s.url}\")${format}`;\n })\n .join(\", \");\n}\n\n/**\n * Load a skin's declared web fonts so live preview matches the platform (not\n * just video export). SSR-safe: a no-op when `document`/`FontFace` are absent\n * (server render, Remotion Node). Idempotent per family+weight+style.\n *\n * Returns once every face has loaded (or immediately, off the main document).\n */\nexport async function loadSkinFonts(\n fonts: FontDeclaration[] | undefined,\n): Promise<void> {\n if (!fonts || fonts.length === 0) return;\n if (\n typeof document === \"undefined\" ||\n typeof FontFace === \"undefined\" ||\n !document.fonts\n ) {\n return;\n }\n\n const pending: Promise<unknown>[] = [];\n for (const decl of fonts) {\n for (const source of decl.sources) {\n const descriptors: FontFaceDescriptors = {};\n if (source.weight !== undefined)\n descriptors.weight = String(source.weight);\n if (source.style !== undefined) descriptors.style = source.style;\n\n const single: FontDeclaration = { ...decl, sources: [source] };\n const face = new FontFace(decl.family, srcValue(single), descriptors);\n\n // Skip if an identical face is already registered.\n const already = [...document.fonts].some(\n (f) =>\n f.family === decl.family &&\n f.weight === (descriptors.weight ?? \"normal\") &&\n f.style === (descriptors.style ?? \"normal\"),\n );\n if (already) continue;\n\n document.fonts.add(face);\n pending.push(face.load());\n }\n }\n await Promise.all(pending);\n}\n","import type { CSSProperties, ReactElement, ReactNode } from \"react\";\n\n/**\n * Animation primitives are **pure functions of progress** (0..1), not CSS\n * transitions or JS timers — so the React preview and the Remotion render\n * animate identically frame-for-frame (PLAN §7). Skins call these driven by\n * `revealProgress` / typing `progress` from `SimState`.\n */\n\nexport const clamp01 = (x: number): number => (x < 0 ? 0 : x > 1 ? 1 : x);\n\n/** Decelerating ease, good for reveals. */\nexport const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3);\n\n/** Back-ease-out with overshoot, good for pops. Lands exactly on 1 at t=1. */\nexport function backEaseOut(t: number, tension = 2.2): number {\n const c3 = tension + 1;\n return 1 + c3 * Math.pow(t - 1, 3) + tension * Math.pow(t - 1, 2);\n}\n\nexport interface FadeSlideOptions {\n /** Slide distance in px at progress 0 (default 8). */\n distance?: number;\n /** Axis to slide along (default \"y\"). */\n axis?: \"x\" | \"y\";\n easing?: (t: number) => number;\n}\n\n/** Fade + slide-in reveal. `progress` 0 → hidden+offset, 1 → shown+settled. */\nexport function fadeSlideIn(\n progress: number,\n options: FadeSlideOptions = {},\n): CSSProperties {\n const eased = (options.easing ?? easeOutCubic)(clamp01(progress));\n const distance = options.distance ?? 8;\n const offset = (1 - eased) * distance;\n // Once settled, drop the transform entirely. Any non-`none` transform — even\n // `translateY(0px)` — establishes a stacking context, which would trap a\n // descendant overlay (e.g. a reaction's hover tooltip) *below* later sibling\n // messages, so neighbouring text paints over the opaque tooltip and it reads\n // as transparent. `none` at rest lets overlays layer above adjacent content.\n if (offset === 0) return { opacity: eased, transform: \"none\" };\n const translate =\n options.axis === \"x\"\n ? `translateX(${offset}px)`\n : `translateY(${offset}px)`;\n return { opacity: eased, transform: translate };\n}\n\nexport interface PopOptions {\n /** Overshoot tension (default 2.2 ≈ ~10% overshoot). */\n tension?: number;\n}\n\n/** Scale pop-in (with a little overshoot), good for reactions landing. */\nexport function popIn(\n progress: number,\n options: PopOptions = {},\n): CSSProperties {\n const p = clamp01(progress);\n const scale = backEaseOut(p, options.tension ?? 2.2);\n return {\n opacity: clamp01(p * 3),\n transform: `scale(${scale})`,\n transformOrigin: \"center\",\n };\n}\n\nexport interface TypingDotsProps {\n /** 0..1 progress through the indicator's shown duration. */\n progress?: number;\n count?: number;\n /** Bounce cycles across the full progress (default 4). */\n cycles?: number;\n /** Dot color (default `currentColor`). */\n color?: string;\n /** Dot diameter in px (default 6). */\n size?: number;\n /** Gap between dots in px (default 4). */\n gap?: number;\n}\n\n/**\n * The three-dot bouncing typing indicator, animated purely from `progress`\n * (deterministic per frame). Skins style it via props or wrap it.\n */\nexport function TypingDots({\n progress = 0,\n count = 3,\n cycles = 4,\n color = \"currentColor\",\n size = 6,\n gap = 4,\n}: TypingDotsProps): ReactElement {\n const phase = clamp01(progress) * cycles * Math.PI * 2;\n const dots: ReactNode[] = [];\n for (let i = 0; i < count; i++) {\n const wave = Math.sin(phase - i * 0.9);\n const lift = Math.max(0, wave) * 4;\n const opacity = 0.4 + Math.max(0, wave) * 0.6;\n dots.push(\n <span\n key={i}\n style={{\n width: size,\n height: size,\n borderRadius: \"50%\",\n background: color,\n transform: `translateY(${-lift}px)`,\n opacity,\n }}\n />,\n );\n }\n return (\n <span style={{ display: \"inline-flex\", alignItems: \"flex-end\", gap }}>\n {dots}\n </span>\n );\n}\n","import type { CSSProperties, ReactElement, ReactNode } from \"react\";\nimport type {\n ContentNode,\n ImageNode,\n InlineNode,\n TextNode,\n} from \"@typecaast/schema\";\n\nexport interface ContentClassNames {\n text?: string;\n link?: string;\n mention?: string;\n code?: string;\n emoji?: string;\n image?: string;\n}\n\n/** Per-mark inline styles, so skins can theme marks without a CSS file. */\nexport interface ContentStyles {\n text?: CSSProperties;\n link?: CSSProperties;\n mention?: CSSProperties;\n code?: CSSProperties;\n emoji?: CSSProperties;\n}\n\nexport interface MessageContentProps {\n nodes: ContentNode[];\n /** Per-mark class names so skins style marks with their own CSS. */\n classNames?: ContentClassNames;\n /** Per-mark inline styles (merged with the defaults). */\n styles?: ContentStyles;\n /** Extra style for in-message images (skins set radius, max size, etc.). */\n imageStyle?: CSSProperties;\n}\n\nfunction renderInline(\n span: InlineNode,\n key: number,\n cn: ContentClassNames,\n st: ContentStyles,\n): ReactNode {\n switch (span.type) {\n case \"text\":\n return span.value;\n case \"code\":\n return (\n <code key={key} data-tc-mark=\"code\" className={cn.code} style={st.code}>\n {span.value}\n </code>\n );\n case \"link\":\n return (\n <a\n key={key}\n data-tc-mark=\"link\"\n className={cn.link}\n style={st.link}\n href={span.href}\n rel=\"noreferrer\"\n >\n {span.label ?? span.href}\n </a>\n );\n case \"mention\":\n return (\n <span\n key={key}\n data-tc-mark=\"mention\"\n className={cn.mention}\n style={st.mention}\n >\n {span.label}\n </span>\n );\n case \"emoji\":\n return (\n <span\n key={key}\n data-tc-mark=\"emoji\"\n className={cn.emoji}\n style={st.emoji}\n >\n {span.value}\n </span>\n );\n }\n}\n\nfunction renderImage(\n node: ImageNode,\n key: number,\n cn: ContentClassNames,\n imageStyle?: CSSProperties,\n): ReactNode {\n return (\n <img\n key={key}\n data-tc-node=\"image\"\n className={cn.image}\n src={node.src}\n alt={node.alt ?? \"\"}\n width={node.width}\n height={node.height}\n style={{ maxWidth: \"100%\", display: \"block\", ...imageStyle }}\n />\n );\n}\n\n/**\n * Render a message body (`ContentNode[]`) to React: text nodes with inline\n * marks (code/link/mention/emoji) and in-message images. Unknown node types are\n * skipped (forward-compatible — PLAN §6). SSR-safe, so it renders identically\n * in the browser and in Remotion's Node renderer. Skins style via `classNames`.\n */\nexport function MessageContent({\n nodes,\n classNames = {},\n styles = {},\n imageStyle,\n}: MessageContentProps): ReactElement {\n return (\n <>\n {nodes.map((node, i) => {\n if (node.type === \"text\") {\n const text = node as TextNode;\n return (\n <span\n key={i}\n data-tc-node=\"text\"\n className={classNames.text}\n style={styles.text}\n >\n {text.spans.map((span, j) =>\n renderInline(span, j, classNames, styles),\n )}\n </span>\n );\n }\n if (node.type === \"image\") {\n return renderImage(node as ImageNode, i, classNames, imageStyle);\n }\n return null; // unknown future node type — skipped\n })}\n </>\n );\n}\n","import { useMemo, type ReactElement } from \"react\";\nimport type { ComposerMode, Participant } from \"@typecaast/schema\";\nimport type { SimState } from \"@typecaast/core\";\nimport { ThemeProvider } from \"./theme.js\";\nimport type { Skin } from \"./types.js\";\n\nexport type { ComposerMode };\n\n// A subtle, native-feeling scrollbar for the scrollable thread, scoped to the\n// thread viewport and injected inline so the embed needs no external stylesheet.\n// A neutral translucent grey reads on both light and dark skins and deepens on\n// hover; WebKit/Blink get the thin overlay-style thumb, Firefox uses the\n// standard `scrollbar-*` props. The selector is global on purpose — identical\n// rules across multiple embeds on a page are harmless and keep them consistent.\nconst THREAD_SCROLLBAR_CSS = `\n[data-typecaast-thread]{scrollbar-width:thin;scrollbar-color:rgba(128,128,128,.4) transparent}\n[data-typecaast-thread]::-webkit-scrollbar{width:8px;height:8px}\n[data-typecaast-thread]::-webkit-scrollbar-track{background:transparent}\n[data-typecaast-thread]::-webkit-scrollbar-thumb{background-color:rgba(128,128,128,.4);border-radius:8px;border:2px solid transparent;background-clip:padding-box}\n[data-typecaast-thread]:hover::-webkit-scrollbar-thumb{background-color:rgba(128,128,128,.6)}\n`;\n\nexport interface TypecaastStageProps {\n state: SimState;\n skin: Skin;\n participants: Participant[];\n /** Skin-specific options from `meta.skin.options`. */\n options?: Record<string, unknown>;\n /**\n * Composer visibility: `auto` (default) shows it only while someone is\n * typing/sending; `always` keeps the reply box visible (idle = placeholder);\n * `never` hides it.\n */\n composer?: ComposerMode;\n}\n\n/**\n * Maps a `SimState` onto a skin's components: a `Frame` wrapping the thread\n * items (Message / SystemMessage), the typing indicators, and the composer.\n * Reactions render inside the skin's `Message` (it reads `message.reactions`).\n *\n * Lives in `skin-kit` (the contract layer) so both `@typecaast/react` and the\n * skins' own stories can render a frame without depending on the React player.\n */\nexport function TypecaastStage({\n state,\n skin,\n participants,\n options,\n composer = \"auto\",\n}: TypecaastStageProps): ReactElement {\n const theme = state.theme;\n const byId = useMemo(() => {\n const map = new Map<string, Participant>();\n for (const p of participants) map.set(p.id, p);\n return map;\n }, [participants]);\n\n const { Frame, Message, SystemMessage, TypingIndicator, Composer } =\n skin.components;\n const tokens = skin.tokens?.[theme];\n // `always` keeps the reply box mounted even when idle (falls back to the self\n // participant so a placeholder shows); `auto` only shows it while composing.\n const selfParticipant = useMemo(\n () => participants.find((p) => p.isSelf),\n [participants],\n );\n const composerAuthor = state.composer.from\n ? byId.get(state.composer.from)\n : composer === \"always\"\n ? selfParticipant\n : undefined;\n const showComposer = composer !== \"never\" && composerAuthor !== undefined;\n\n // \"X is typing…\" indicators, minus the viewer's own (you never see yourself\n // typing — that's what the composer shows). Placement is skin-driven: inline\n // in the thread by default, or below the composer (Slack).\n const typingPlacement = skin.meta.typingPlacement ?? \"thread\";\n const typingNodes = state.typingIndicators\n .map((typing, i) => {\n const author = byId.get(typing.from);\n if (!author || author.isSelf) return null;\n return (\n <TypingIndicator\n key={`typing-${typing.from}-${i}`}\n theme={theme}\n typing={typing}\n author={author}\n />\n );\n })\n .filter(Boolean);\n\n return (\n <ThemeProvider theme={theme} tokens={tokens}>\n <style>{THREAD_SCROLLBAR_CSS}</style>\n <Frame theme={theme} options={options} composer={composer}>\n {/* Thread viewport. `column-reverse` pins the conversation to the\n bottom (newest message + composer sit at the bottom, older items\n \"shift up\", PLAN §7) and, once the thread outgrows the height, makes\n it scroll from the bottom with the top reachable — entirely in CSS,\n so it renders identically in a live embed, an SSR page, and a video\n frame (no scroll-to-bottom effect, which wouldn't run before a\n Remotion screenshot). Because `column-reverse` lays the first child\n out at the bottom, children render newest-first: the typing\n indicator (most recent activity) first, then messages reversed. The\n engine's scroll.targetOffset is honored here once real layout\n measurement lands. */}\n <div\n data-typecaast-thread=\"\"\n style={{\n display: \"flex\",\n flexDirection: \"column-reverse\",\n flex: \"1 1 auto\",\n minHeight: 0,\n overflowY: \"auto\",\n // Scrollbar styling lives in THREAD_SCROLLBAR_CSS (the `<style>`\n // above) so it can reach the WebKit pseudo-elements.\n // Breathing room beneath the last message — keeps it off the\n // composer (when shown) and the Frame's bottom edge (when hidden).\n paddingBottom: 16,\n }}\n >\n {typingPlacement === \"thread\" ? typingNodes : null}\n {state.messages\n .map((message, i) => {\n const author = byId.get(message.from);\n if (!author) return null;\n // Index-disambiguated so a config with duplicate message ids can't\n // collide React keys (the builder can produce them transiently).\n const key = `${message.id}-${i}`;\n if (message.variant === \"system\") {\n return (\n <SystemMessage\n key={key}\n theme={theme}\n message={message}\n author={author}\n />\n );\n }\n // Grouping looks at the chronological predecessor — computed\n // before the reverse below, so author-collapsing is unaffected.\n const prev = state.messages[i - 1];\n const previousAuthor = prev ? byId.get(prev.from) : undefined;\n return (\n <Message\n key={key}\n theme={theme}\n message={message}\n author={author}\n previousAuthor={previousAuthor}\n />\n );\n })\n .reverse()}\n </div>\n {showComposer ? (\n <Composer\n theme={theme}\n composer={state.composer}\n author={composerAuthor}\n options={options}\n />\n ) : null}\n {typingPlacement === \"below-composer\" ? typingNodes : null}\n </Frame>\n </ThemeProvider>\n );\n}\n","/**\n * `slotSkinFromDraft` — build a `Skin` from a slotted HTML draft (the shape\n * the capture pipeline emits). The frame/message/composer HTML strings are\n * injected into a shadow root with the draft's CSS, and `{{slot}}` markers\n * are substituted with per-message text at render time.\n *\n * Lives in `@typecaast/skin-kit` so both `@typecaast/capture` (the runtime\n * untrusted-template path) and `@typecaast/skins` (built-in captured skins\n * like PostHog) can share one implementation without forming a build cycle\n * with `@typecaast/react`. The capture-runtime caller layers `sanitizeHtml`\n * around it; built-ins skip sanitize because their draft.json is\n * version-controlled.\n *\n * Responsive behaviour: the captured DOM is typically taken on a desktop\n * viewport (1000-1500px wide) but the skin renders into a small canvas\n * (often 480×640). We normalise that by:\n * - wrapping the frame slot in a container that is `width:100%; height:100%`\n * with `overflow:hidden`, so the captured outer chrome can't extend\n * past the canvas;\n * - resetting the captured root element's `margin`/`max-width` so a\n * `margin: 0px 234px` baked in at desktop width becomes `margin: 0 auto`;\n * - exposing the captured viewport width as `--captured-viewport-width`\n * for authored CSS to ratio-scale against if it wants.\n */\nimport {\n useLayoutEffect,\n useRef,\n useState,\n type CSSProperties,\n type FC,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { ContentNode, Participant } from \"@typecaast/schema\";\nimport type {\n Capabilities,\n ComposerProps,\n FrameProps,\n MessageProps,\n SystemProps,\n TypingProps,\n} from \"@typecaast/core\";\nimport type { Skin, SkinComponents, SkinTokens } from \"./types.js\";\n\nconst SLOT_ATTR = \"data-tc-slot\";\n\ninterface TokenSet {\n colors?: Record<string, string>;\n}\n\nexport interface SlotSkinDraft {\n meta: {\n name: string;\n theme?: \"light\" | \"dark\";\n canvas?: { width: number; height: number };\n capturedAt?: { viewportWidth?: number; pixelRatio?: number };\n };\n slots: {\n frame?: string;\n message?: string;\n composer?: string;\n typing?: string;\n system?: string;\n };\n css?: string;\n tokens: TokenSet;\n darkTokens?: TokenSet;\n}\n\nexport interface SlotSkinOptions {\n id: string;\n capabilities: Capabilities;\n /**\n * When true, every slot mount in the shadow root gets a faint dashed\n * outline + a corner badge naming the slot. Used by the `/create-skin`\n * editor to show authors where their `{{body}}` lands.\n */\n slotMarkers?: boolean;\n}\n\nfunction contentToText(content: ContentNode[]): string {\n const out: string[] = [];\n for (const node of content) {\n if (\n node.type === \"text\" &&\n Array.isArray((node as { spans?: unknown }).spans)\n ) {\n for (const span of (\n node as { spans: { value?: string; label?: string }[] }\n ).spans) {\n out.push(span.value ?? span.label ?? \"\");\n }\n } else if (node.type === \"image\") {\n out.push((node as { alt?: string }).alt ?? \"🖼\");\n }\n }\n return out.join(\"\");\n}\n\nfunction initials(name: string): string {\n return name\n .split(/\\s+/)\n .filter(Boolean)\n .slice(0, 2)\n .map((w) => w[0]?.toUpperCase() ?? \"\")\n .join(\"\");\n}\n\nfunction fmtTime(atMs: number): string {\n const total = Math.floor(atMs / 1000);\n const m = Math.floor(total / 60);\n const s = total % 60;\n return `${m}:${String(s).padStart(2, \"0\")}`;\n}\n\n/**\n * Build the `<style>` text for the shadow root. Order matters:\n * 1. `:host` declarations (tokens as CSS vars, captured viewport width).\n * 2. A reset that normalises the captured root element back to fluid\n * layout — desktop-only `margin: 0 200px`/`max-width: 1200px` would\n * otherwise squeeze content in a small canvas.\n * 3. Author / captured CSS, which can override anything above.\n * 4. Slot-marker overlay if requested.\n */\nfunction styleText(\n tokens: TokenSet,\n css: string,\n capturedAt: SlotSkinDraft[\"meta\"][\"capturedAt\"],\n slotMarkers: boolean,\n): string {\n const vars = Object.entries(tokens.colors ?? {})\n .map(([k, v]) => `--${k}: ${v};`)\n .join(\" \");\n const cv = capturedAt?.viewportWidth\n ? `--captured-viewport-width: ${capturedAt.viewportWidth}px;`\n : \"\";\n const reset = `\n :host { all: initial; display: block; width: 100%; height: 100%; ${vars} ${cv} }\n * { box-sizing: border-box; }\n /* Normalise the captured root: drop desktop-only margins / max-widths\n so the layout re-centers cleanly in any canvas. */\n .tc-slot-root > :first-child {\n margin-left: auto !important;\n margin-right: auto !important;\n max-width: 100% !important;\n }`;\n const markers = slotMarkers\n ? `\n [data-tc-slot] {\n outline: 1px dashed rgba(99, 102, 241, 0.6);\n outline-offset: -1px;\n position: relative;\n }\n [data-tc-slot]::before {\n content: attr(data-tc-slot);\n position: absolute;\n top: 0;\n left: 0;\n transform: translateY(-100%);\n font: 600 9px/1 -apple-system, system-ui, sans-serif;\n padding: 1px 4px;\n background: rgba(99, 102, 241, 0.95);\n color: white;\n border-radius: 2px;\n pointer-events: none;\n z-index: 10;\n }`\n : \"\";\n return `${reset}\\n${css}\\n${markers}`;\n}\n\nfunction fillInto(\n host: HTMLElement,\n templateHtml: string,\n values: Record<string, string>,\n): void {\n host.innerHTML = templateHtml;\n for (const node of host.querySelectorAll(`[${SLOT_ATTR}]`)) {\n const slot = node.getAttribute(SLOT_ATTR);\n if (slot && slot in values) node.textContent = values[slot] ?? \"\";\n }\n}\n\nfunction revealStyle(progress: number): CSSProperties {\n const p = Math.max(0, Math.min(1, progress));\n return {\n opacity: p,\n transform: `translateY(${(1 - p) * 6}px)`,\n willChange: \"opacity, transform\",\n };\n}\n\nexport function slotSkinFromDraft(\n draft: SlotSkinDraft,\n opts: SlotSkinOptions,\n): Skin {\n const lightTokens: SkinTokens = { colors: draft.tokens.colors ?? {} };\n const darkTokens: SkinTokens = draft.darkTokens\n ? { colors: draft.darkTokens.colors ?? {} }\n : lightTokens;\n const slotMarkers = opts.slotMarkers ?? false;\n const cssByTheme = {\n light: styleText(\n lightTokens,\n draft.css ?? \"\",\n draft.meta.capturedAt,\n slotMarkers,\n ),\n dark: styleText(\n darkTokens,\n draft.css ?? \"\",\n draft.meta.capturedAt,\n slotMarkers,\n ),\n };\n const supportsThemes: (\"light\" | \"dark\")[] = draft.darkTokens\n ? [\"light\", \"dark\"]\n : [draft.meta.theme ?? \"light\"];\n\n const frameHtml = draft.slots.frame;\n const messageHtml = draft.slots.message;\n const composerHtml = draft.slots.composer;\n const systemHtml = draft.slots.system;\n const typingHtml = draft.slots.typing;\n\n const Frame: FC<FrameProps & { children?: ReactNode }> = ({\n theme,\n children,\n }) => {\n const hostRef = useRef<HTMLDivElement>(null);\n const [mount, setMount] = useState<HTMLElement | null>(null);\n useLayoutEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const shadow = host.shadowRoot ?? host.attachShadow({ mode: \"open\" });\n shadow.innerHTML = \"\";\n const style = host.ownerDocument.createElement(\"style\");\n style.textContent = theme === \"dark\" ? cssByTheme.dark : cssByTheme.light;\n shadow.appendChild(style);\n // Wrap the captured frame in a fluid 100%×100% container so any\n // desktop-fixed widths/margins on the captured root get neutralised\n // by the `.tc-slot-root > :first-child` reset.\n const wrapper = host.ownerDocument.createElement(\"div\");\n wrapper.className = \"tc-slot-root\";\n wrapper.style.width = \"100%\";\n wrapper.style.height = \"100%\";\n wrapper.style.display = \"flex\";\n wrapper.style.flexDirection = \"column\";\n wrapper.style.overflow = \"hidden\";\n wrapper.innerHTML = frameHtml ?? `<div ${SLOT_ATTR}=\"messages\"></div>`;\n shadow.appendChild(wrapper);\n const slot =\n wrapper.querySelector<HTMLElement>(`[${SLOT_ATTR}=\"messages\"]`) ??\n wrapper;\n slot.textContent = \"\";\n setMount(slot);\n }, [theme]);\n return (\n <div ref={hostRef} style={{ width: \"100%\", height: \"100%\" }}>\n {mount ? createPortal(children, mount) : null}\n </div>\n );\n };\n\n const Message: FC<MessageProps> = ({ message, author }) => {\n const ref = useRef<HTMLDivElement>(null);\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || !messageHtml) return;\n fillInto(el, messageHtml, {\n author: author.name,\n avatar: initials(author.name),\n body: contentToText(message.content),\n time: fmtTime(message.atMs),\n });\n }, [message, author]);\n return <div ref={ref} style={revealStyle(message.revealProgress)} />;\n };\n\n const SystemMessage: FC<SystemProps> = ({ message }) => {\n const ref = useRef<HTMLDivElement>(null);\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || !systemHtml) return;\n fillInto(el, systemHtml, { body: contentToText(message.content) });\n }, [message]);\n if (systemHtml) {\n return <div ref={ref} style={revealStyle(message.revealProgress)} />;\n }\n return (\n <div\n style={{\n ...revealStyle(message.revealProgress),\n textAlign: \"center\",\n }}\n >\n {contentToText(message.content)}\n </div>\n );\n };\n\n const TypingIndicator: FC<TypingProps> = ({ author }) => {\n const ref = useRef<HTMLDivElement>(null);\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el || !typingHtml) return;\n fillInto(el, typingHtml, { author: author.name });\n }, [author]);\n if (typingHtml) {\n return <div ref={ref} />;\n }\n return (\n <div style={{ opacity: 0.7, fontStyle: \"italic\" }}>\n {author.name} is typing…\n </div>\n );\n };\n\n const Composer: FC<ComposerProps> = ({ composer }) => {\n const ref = useRef<HTMLDivElement>(null);\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el) return;\n if (composerHtml) {\n fillInto(el, composerHtml, { composer: composer.text });\n } else {\n el.textContent = composer.text;\n }\n }, [composer]);\n return <div ref={ref} />;\n };\n\n const Avatar: FC<{ participant: Participant; size?: number }> = ({\n participant,\n size = 36,\n }) => (\n <div\n style={{\n width: size,\n height: size,\n borderRadius: \"50%\",\n display: \"grid\",\n placeItems: \"center\",\n background: \"var(--color-1, #ccc)\",\n fontSize: size * 0.4,\n }}\n >\n {initials(participant.name)}\n </div>\n );\n\n const components: SkinComponents = {\n Frame,\n Message,\n SystemMessage,\n TypingIndicator,\n Reaction: () => null,\n Composer,\n Avatar,\n };\n\n return {\n id: opts.id,\n meta: {\n name: draft.meta.name,\n defaultCanvas: draft.meta.canvas ?? { width: 420, height: 720 },\n supportsThemes,\n capabilities: opts.capabilities,\n },\n components,\n tokens: { light: lightTokens, dark: darkTokens },\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typecaast/skin-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Skin authoring kit: defineSkin, Skin/Capabilities/SkinTokens types, theme context, font loader.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"@typecaast/schema": "0.2.1"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"react": ">=18"
|
|
32
|
+
"react": ">=18",
|
|
33
|
+
"react-dom": ">=18"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"@types/react": "^19.0.0",
|