@lumiastream/ui 0.2.8-alpha.1 → 0.2.8-alpha.11

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.js CHANGED
@@ -508,7 +508,7 @@ import FormControlLabel from "@mui/material/FormControlLabel";
508
508
  import { forwardRef as forwardRef2 } from "react";
509
509
  import { useController, useFormContext } from "react-hook-form";
510
510
  import classNames4 from "classnames";
511
- import { jsx as jsx5 } from "react/jsx-runtime";
511
+ import { Fragment, jsx as jsx5 } from "react/jsx-runtime";
512
512
  var LSCheckbox = forwardRef2(({ className = "", checked, inputRef, slotProps, ...props }, ref) => {
513
513
  const CheckboxComponent = Checkbox;
514
514
  return /* @__PURE__ */ jsx5(
@@ -539,8 +539,31 @@ function LSCheckboxField({
539
539
  checkboxProps
540
540
  }) {
541
541
  const formContext = useFormContext();
542
- const resolvedControl = control ?? formContext.control;
543
- const { field } = useController({ name, control: resolvedControl });
542
+ const resolvedControl = control ?? formContext?.control;
543
+ if (!resolvedControl) return /* @__PURE__ */ jsx5(Fragment, {});
544
+ return /* @__PURE__ */ jsx5(
545
+ LSCheckboxFieldInner,
546
+ {
547
+ name,
548
+ label,
549
+ control: resolvedControl,
550
+ className,
551
+ disabled,
552
+ labelPlacement,
553
+ checkboxProps
554
+ }
555
+ );
556
+ }
557
+ function LSCheckboxFieldInner({
558
+ name,
559
+ label,
560
+ control,
561
+ className = "",
562
+ disabled,
563
+ labelPlacement = "end",
564
+ checkboxProps
565
+ }) {
566
+ const { field } = useController({ name, control });
544
567
  return /* @__PURE__ */ jsx5(
545
568
  FormControlLabel,
546
569
  {
@@ -1045,7 +1068,7 @@ import Search from "@mui/icons-material/Search";
1045
1068
  import KeyboardArrowDown2 from "@mui/icons-material/KeyboardArrowDown";
1046
1069
  import Tooltip from "@mui/material/Tooltip";
1047
1070
  import {
1048
- Fragment,
1071
+ Fragment as Fragment2,
1049
1072
  createContext,
1050
1073
  forwardRef as forwardRef4,
1051
1074
  useCallback as useCallback2,
@@ -1055,7 +1078,7 @@ import {
1055
1078
  useRef as useRef3,
1056
1079
  useState as useState4
1057
1080
  } from "react";
1058
- import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
1081
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
1059
1082
  import { createElement } from "react";
1060
1083
  var LSVariableInputContext = createContext({});
1061
1084
  var LSVariableInputProvider = ({
@@ -1799,7 +1822,7 @@ var LSVariableInputField = forwardRef4((props, ref) => {
1799
1822
  const fullToken = `${rootToken}.${node.relativePath}`;
1800
1823
  const expanded = node.isExpandable && isPathExpanded(fullToken);
1801
1824
  const { displayValue, fullValue } = getVariableValuePreview(node.value);
1802
- return /* @__PURE__ */ jsxs5(Fragment, { children: [
1825
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
1803
1826
  /* @__PURE__ */ jsxs5(
1804
1827
  "div",
1805
1828
  {
@@ -1938,7 +1961,7 @@ var LSVariableInputField = forwardRef4((props, ref) => {
1938
1961
  const expanded = expandable && isPathExpanded(definition.name);
1939
1962
  const isLast = idx === filteredAllowedVariables.length - 1;
1940
1963
  const rowCls = `ls-variable-picker__row${isLast && !expanded ? " ls-variable-picker__row--no-border" : ""}`;
1941
- return /* @__PURE__ */ jsxs5(Fragment, { children: [
1964
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
1942
1965
  /* @__PURE__ */ jsxs5(
1943
1966
  "div",
1944
1967
  {
@@ -1978,7 +2001,7 @@ var LSVariableInputField = forwardRef4((props, ref) => {
1978
2001
  const tree = systemVariableValueTrees.get(variable.name);
1979
2002
  const expandable = !!tree && tree.length > 0;
1980
2003
  const expanded = expandable && isPathExpanded(rootToken);
1981
- return /* @__PURE__ */ jsxs5(Fragment, { children: [
2004
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
1982
2005
  /* @__PURE__ */ jsxs5(
1983
2006
  "div",
1984
2007
  {
@@ -2028,7 +2051,7 @@ var LSVariableInputField = forwardRef4((props, ref) => {
2028
2051
  ref
2029
2052
  }
2030
2053
  );
2031
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
2054
+ return /* @__PURE__ */ jsxs5(Fragment3, { children: [
2032
2055
  /* @__PURE__ */ jsx11(
2033
2056
  Popover,
2034
2057
  {
@@ -2212,7 +2235,7 @@ var VariableInputTextField = forwardRef4(
2212
2235
  ...paramsSlotInputProps,
2213
2236
  ...paramsInputProps,
2214
2237
  startAdornment,
2215
- endAdornment: /* @__PURE__ */ jsxs5(Fragment2, { children: [
2238
+ endAdornment: /* @__PURE__ */ jsxs5(Fragment3, { children: [
2216
2239
  endAdornment ?? null,
2217
2240
  showVariableIcon ? /* @__PURE__ */ jsx11(
2218
2241
  Tooltip,
@@ -2269,7 +2292,7 @@ function variableCompletionOptions(context, variables) {
2269
2292
  }
2270
2293
 
2271
2294
  // src/utils/codeMirrorlinterOptions.ts
2272
- import * as globals from "globals";
2295
+ import globals from "globals";
2273
2296
  var browserGlobals = Object.fromEntries(
2274
2297
  Object.entries(globals.browser).map(([key, value]) => [
2275
2298
  key,
@@ -3126,7 +3149,11 @@ var SE_WIDGET_TYPE_LABELS = {
3126
3149
  "se-widget-hype-cup": "Hype Cup",
3127
3150
  "se-widget-custom-event-list": "Custom widget",
3128
3151
  "se-widget-credit-roll": "Credits",
3129
- "se-widget-media-share": "Media share"
3152
+ "se-widget-media-share": "Media share",
3153
+ // Imports as an empty slideshow — Lumia has no Fourthwall catalog API, so
3154
+ // the streamer has to add product images by hand. The friendly title makes
3155
+ // that obvious in the layers panel; see REVIEW_REASONS for the full prompt.
3156
+ "se-widget-merch-products-rotator": "Merch products (add images manually)"
3130
3157
  };
3131
3158
  function defaultLabelForSEWidget(widget) {
3132
3159
  const trimmed = typeof widget.name === "string" ? widget.name.trim() : "";
@@ -3210,11 +3237,21 @@ function listenerToLumiaVariable(listener) {
3210
3237
  return m[listener] ?? listener.replace(/-/g, "_");
3211
3238
  }
3212
3239
  function seCssToBounds(css, defaults = { width: 600, height: 200 }, canvas) {
3240
+ let widthIsAuto = false;
3241
+ let heightIsAuto = false;
3213
3242
  const toPx = (v, fallback, axis) => {
3214
- if (v == null) return fallback;
3243
+ if (v == null) {
3244
+ if (axis === "width") widthIsAuto = true;
3245
+ else if (axis === "height") heightIsAuto = true;
3246
+ return fallback;
3247
+ }
3215
3248
  if (typeof v === "number") return v;
3216
3249
  const s = v.trim();
3217
- if (s === "auto" || s === "") return fallback;
3250
+ if (s === "auto" || s === "") {
3251
+ if (axis === "width") widthIsAuto = true;
3252
+ else if (axis === "height") heightIsAuto = true;
3253
+ return fallback;
3254
+ }
3218
3255
  if (s.endsWith("%")) {
3219
3256
  const pct = parseFloat(s);
3220
3257
  if (canvas && axis && Number.isFinite(pct)) {
@@ -3228,6 +3265,8 @@ function seCssToBounds(css, defaults = { width: 600, height: 200 }, canvas) {
3228
3265
  };
3229
3266
  const width = toPx(css.width, defaults.width, "width");
3230
3267
  const height = toPx(css.height, defaults.height, "height");
3268
+ const autoWidth = widthIsAuto;
3269
+ const autoHeight = heightIsAuto;
3231
3270
  let x = toPx(css.left, 0, "width");
3232
3271
  let y = toPx(css.top, 0, "height");
3233
3272
  if (typeof css.transform === "string" && css.transform.includes("translate")) {
@@ -3253,7 +3292,9 @@ function seCssToBounds(css, defaults = { width: 600, height: 200 }, canvas) {
3253
3292
  width,
3254
3293
  height,
3255
3294
  opacity: css.opacity ?? 1,
3256
- zIndex: css["z-index"] ?? 0
3295
+ zIndex: css["z-index"] ?? 0,
3296
+ autoWidth,
3297
+ autoHeight
3257
3298
  };
3258
3299
  }
3259
3300
  var LUMIA_DEFAULT_SIZES = {
@@ -3263,7 +3304,11 @@ var LUMIA_DEFAULT_SIZES = {
3263
3304
  audio: { width: 200, height: 200 },
3264
3305
  slideshow: { width: 400, height: 220 },
3265
3306
  alert: { width: 700, height: 600 },
3266
- goal: { width: 740, height: 80 },
3307
+ // 80 was too short for the SE-style slim goal layout (label + bar + "Ends
3308
+ // in" sublabel needs ~125 px of vertical space). Bumped so editor-canvas
3309
+ // rendering (which doesn't honor autoHeight) doesn't clip the sublabel
3310
+ // after import.
3311
+ goal: { width: 740, height: 130 },
3267
3312
  timer: { width: 740, height: 80 },
3268
3313
  chatbox: { width: 375, height: 470 },
3269
3314
  eventlist: { width: 375, height: 470 },
@@ -3273,7 +3318,22 @@ var LUMIA_DEFAULT_SIZES = {
3273
3318
  emotebox: { width: 400, height: 400 },
3274
3319
  nowplaying: { width: 600, height: 250 },
3275
3320
  spotify: { width: 600, height: 250 },
3276
- youtubemusic: { width: 600, height: 250 }
3321
+ youtubemusic: { width: 600, height: 250 },
3322
+ tipjar: { width: 600, height: 700 },
3323
+ // Hype Train: wide-and-short by default to fit a horizontal progress bar.
3324
+ // All four built-in themes (subtle / train / rocket / inferno) render
3325
+ // fine at this aspect — rocket internally uses a vertical gauge but
3326
+ // still works at this footprint. Users can resize per their overlay.
3327
+ hypetrain: { width: 740, height: 140 },
3328
+ // Stream Boss: matches SE's bit-boss default (440×86) for a 1:1 import
3329
+ // experience, lightly padded so Lumia's Card-theme avatar fits without
3330
+ // resizing. Users can swap to Bar-Only and shrink height further.
3331
+ streamboss: { width: 480, height: 100 },
3332
+ // Song Request: SE's media-share default is 480×292. Lumia's now-playing
3333
+ // card + 5-item queue list needs a bit more vertical room, so we add ~80px
3334
+ // for the queue list. Users can hide the queue list and shrink back to
3335
+ // just the card if they want SE's exact footprint.
3336
+ songrequest: { width: 480, height: 380 }
3277
3337
  };
3278
3338
  function translateVariationCondition(seType, seCondition, requirement) {
3279
3339
  if (seType === "gift") return { conditionType: "IS_GIFT", condition: 1 };
@@ -3282,7 +3342,7 @@ function translateVariationCondition(seType, seCondition, requirement) {
3282
3342
  if (seCondition === "ATLEAST" && requirement != null) {
3283
3343
  return { conditionType: "GREATER_NUMBER", condition: requirement };
3284
3344
  }
3285
- return null;
3345
+ return { conditionType: "RANDOM", condition: 100 };
3286
3346
  }
3287
3347
  var LUMIA_LAYOUTS = /* @__PURE__ */ new Set(["column", "row", "onlyImage", "onlyText", "imageOver", "textOver"]);
3288
3348
  function translateLayout(seLayout) {
@@ -3310,7 +3370,13 @@ function buildUnit(widget, lumiaType, moduleExtras) {
3310
3370
  height: bounds.height,
3311
3371
  scale: [1, 1],
3312
3372
  opacity: bounds.opacity,
3313
- zIndex: bounds.zIndex
3373
+ zIndex: bounds.zIndex,
3374
+ // Per-axis flags — only the SE axis that was actually `auto`
3375
+ // collapses in render. The SE cheer goal is the canonical case:
3376
+ // width: "700px" + height: "auto" → numeric width stays at 700,
3377
+ // height collapses to content.
3378
+ ...bounds.autoWidth ? { autoWidth: true } : {},
3379
+ ...bounds.autoHeight ? { autoHeight: true } : {}
3314
3380
  }
3315
3381
  };
3316
3382
  const module = {
@@ -3332,6 +3398,19 @@ function buildUnit(widget, lumiaType, moduleExtras) {
3332
3398
  }
3333
3399
 
3334
3400
  // src/se-import/mappers/basic.ts
3401
+ function seExtraTextCss(seCss, scrolling) {
3402
+ const extra = {};
3403
+ if (typeof seCss["text-decoration"] === "string") extra.textDecoration = seCss["text-decoration"];
3404
+ if (typeof seCss["text-transform"] === "string") extra.textTransform = seCss["text-transform"];
3405
+ if (seCss["-webkit-text-stroke-color"]) extra["-webkit-text-stroke-color"] = seCss["-webkit-text-stroke-color"];
3406
+ if (seCss["-webkit-text-stroke-width"] != null) extra["-webkit-text-stroke-width"] = seCss["-webkit-text-stroke-width"];
3407
+ if (scrolling?.enabled) {
3408
+ extra.scroll = true;
3409
+ const speed = Number(scrolling.speed);
3410
+ if (Number.isFinite(speed) && speed > 0) extra.scrollSpeed = speed * 600;
3411
+ }
3412
+ return extra;
3413
+ }
3335
3414
  function mapText(widget) {
3336
3415
  const value = translateSeText(widget.text?.value ?? "");
3337
3416
  const seCss = widget.text?.css ?? {};
@@ -3348,7 +3427,8 @@ function mapText(widget) {
3348
3427
  color: seCss["color"] ?? "#ffffff",
3349
3428
  textShadow: seCss["text-shadow"] ?? "rgb(0, 0, 0) 1px 1px 1px",
3350
3429
  lineHeight: seCss["line-height"] ?? 1,
3351
- background: "transparent"
3430
+ background: "transparent",
3431
+ ...seExtraTextCss(seCss, widget.text?.scrolling)
3352
3432
  }
3353
3433
  });
3354
3434
  }
@@ -3370,6 +3450,17 @@ function mapVideo(widget) {
3370
3450
  }
3371
3451
  });
3372
3452
  }
3453
+ var LUMIA_SNOW_WEBM = "https://storage.lumiastream.com/overlays/global/seasonal/snow.webm";
3454
+ function mapSnow(widget) {
3455
+ return buildUnit(widget, "video", {
3456
+ content: {
3457
+ src: LUMIA_SNOW_WEBM,
3458
+ volume: 0,
3459
+ loop: true,
3460
+ muted: true
3461
+ }
3462
+ });
3463
+ }
3373
3464
  function mapReadout(widget, fallbackVar) {
3374
3465
  const explicitListener = widget.variables?.listener;
3375
3466
  const explicit = typeof explicitListener === "string" ? explicitListener : null;
@@ -3390,7 +3481,8 @@ function mapReadout(widget, fallbackVar) {
3390
3481
  color: seCss["color"] ?? "#ffffff",
3391
3482
  textShadow: seCss["text-shadow"] ?? "rgb(0, 0, 0) 1px 1px 1px",
3392
3483
  lineHeight: seCss["line-height"] ?? 1,
3393
- background: "transparent"
3484
+ background: "transparent",
3485
+ ...seExtraTextCss(seCss, widget.text?.scrolling)
3394
3486
  }
3395
3487
  });
3396
3488
  }
@@ -3408,9 +3500,20 @@ var SE_EVENT_TO_LUMIA_ALERT = {
3408
3500
  // LumiaAlertValues / activity.types.ts.
3409
3501
  merch: "fourthwall-shopOrder",
3410
3502
  purchase: "fourthwall-shopOrder",
3411
- charityCampaignDonation: "twitch-charityDonation",
3412
- host: "twitch-raid",
3413
- redemption: "twitch-redemption"
3503
+ charityCampaignDonation: "twitch-charityDonation"
3504
+ // NOTE — two SE events intentionally NOT mapped:
3505
+ // • `host` → previously `twitch-raid`. Twitch deprecated hosting in
3506
+ // late 2022 so the event never fires on the platform anymore. Even
3507
+ // if it did, host payloads don't carry a viewer count (raids do),
3508
+ // so the rendered alert would read "{name} raided with ?? viewers".
3509
+ // Drop the mapping; SE widgets configured with a `host` block fall
3510
+ // through to the unsupported placeholder rather than producing
3511
+ // misleading raid alerts.
3512
+ // • `redemption` → SE's `redemption` event is the legacy SE Store
3513
+ // redemption (their own loyalty-point store purchases), NOT Twitch
3514
+ // channel-point redemptions. Different payload, different semantics.
3515
+ // SE Store is mostly deprecated and seldom used; dropping the
3516
+ // mapping prevents misleading "channel-point redemption" alerts.
3414
3517
  };
3415
3518
  function pickGraphics(g) {
3416
3519
  if (!g) return { type: "image", content: { src: "", loop: false, volume: 1 } };
@@ -3429,25 +3532,48 @@ var SE_DEFAULT_DURATION_BY_EVENT = {
3429
3532
  tip: 5,
3430
3533
  cheer: 5,
3431
3534
  raid: 8,
3432
- host: 8,
3433
3535
  merch: 8,
3434
3536
  purchase: 8,
3435
- charityCampaignDonation: 8,
3436
- redemption: 5
3537
+ charityCampaignDonation: 8
3538
+ // `redemption` intentionally omitted — see SE_EVENT_TO_LUMIA_ALERT above
3539
+ // for why SE Store redemptions don't map to Twitch redemptions.
3437
3540
  };
3541
+ function cssKebabToCamel(input) {
3542
+ const out = {};
3543
+ for (const [k, v] of Object.entries(input)) {
3544
+ const camel = k.replace(/-([a-z])/g, (_m, c) => c.toUpperCase());
3545
+ out[camel] = v;
3546
+ }
3547
+ return out;
3548
+ }
3438
3549
  function buildAlertEvent(seEvent, seEventType) {
3439
3550
  if (!seEvent) {
3440
3551
  return { on: false, variations: [] };
3441
3552
  }
3442
3553
  const text = seEvent.text ?? {};
3443
3554
  const css = text.css ?? {};
3444
- const messageCss = css.message ?? {};
3555
+ const rawMessageCss = css.message ?? {};
3556
+ const messageCss = cssKebabToCamel(rawMessageCss);
3445
3557
  const highlight = css.highlights ?? {};
3446
- const variations = (seEvent.variations ?? []).map((variation) => buildAlertVariation(variation, seEventType)).filter((v) => v != null);
3558
+ const shadowEnabled = text.enableShadow !== false;
3559
+ const importedTextShadow = css["text-shadow"] ?? "rgba(0, 0, 0, 0.8) 1px 1px 1px";
3560
+ const textShadow = shadowEnabled ? importedTextShadow : "none";
3561
+ const marginTopRaw = Number(css["margin-top"]);
3562
+ const pushTextUp = Number.isFinite(marginTopRaw) ? -marginTopRaw : 0;
3563
+ const variations = (seEvent.variations ?? []).map((variation) => buildAlertVariation(variation, seEventType, seEvent)).filter((v) => v != null);
3447
3564
  const defaultDuration = (seEventType && SE_DEFAULT_DURATION_BY_EVENT[seEventType]) ?? 8;
3448
3565
  return {
3449
3566
  on: seEvent.enabled !== false,
3450
- theme: "CUSTOM",
3567
+ // CAREFUL: Lumia's `AlertThemes.CUSTOM` enum value is lowercase
3568
+ // `'custom'` (Overlay-UI/.../Alert.types.ts). The AlertBoxHandler
3569
+ // runtime does a strict equality check — `copiedAlert.theme ===
3570
+ // AlertThemes.CUSTOM` — so uppercase `'CUSTOM'` falls through to the
3571
+ // templated (Branded / Card / Simple) code path. That path calls
3572
+ // `replaceModuleVariables` WITHOUT the wrapVariablesWithHtml argument
3573
+ // and the variable highlight span never gets rendered, which is
3574
+ // exactly the "everything is white" bug imported alerts hit. The
3575
+ // lowercase value is load-bearing.
3576
+ theme: "custom",
3451
3577
  themeSettings: {
3452
3578
  primaryColor: null,
3453
3579
  accentColor: null,
@@ -3460,14 +3586,14 @@ function buildAlertEvent(seEvent, seEventType) {
3460
3586
  showBrandIcon: true
3461
3587
  },
3462
3588
  settings: { duration: (seEvent.duration ?? defaultDuration) * 1e3 },
3463
- layout: { value: translateLayout(seEvent.layout), css: {}, customCss: "" },
3589
+ layout: { value: translateLayout(seEvent.layout), css: {}, customCss: "", pushTextUp, pushTextLeft: 0 },
3464
3590
  text: {
3465
3591
  css: {
3466
3592
  fontFamily: css["font-family"] ?? "Roboto",
3467
3593
  fontSize: css["font-size"] ?? 30,
3468
3594
  color: css.color ?? "#ffffff",
3469
3595
  fontWeight: css["font-weight"] ?? "bold",
3470
- textShadow: css["text-shadow"] ?? "rgba(0, 0, 0, 0.8) 1px 1px 1px",
3596
+ textShadow,
3471
3597
  textAlign: css["text-align"] ?? "center"
3472
3598
  },
3473
3599
  messageCss,
@@ -3482,11 +3608,45 @@ function buildAlertEvent(seEvent, seEventType) {
3482
3608
  },
3483
3609
  animations: {
3484
3610
  alertEnterAnimation: seEvent.animation?.in ?? "fadeIn",
3611
+ alertEnterAnimationDelay: 0,
3485
3612
  alertEnterAnimationDuration: (seEvent.animation?.inDuration ?? 1) * 1e3,
3486
3613
  alertExitAnimation: seEvent.animation?.out ?? "fadeOut",
3614
+ alertExitAnimationDelay: 0,
3487
3615
  alertExitAnimationDuration: (seEvent.animation?.outDuration ?? 1) * 1e3,
3488
3616
  textEnterAnimation: text.animation ?? "",
3489
- textEnterAnimationDuration: 1e3
3617
+ textEnterDelay: 0,
3618
+ textEnterAnimationDuration: 1e3,
3619
+ textEnterRepeat: 1,
3620
+ // SE has no separate "text exit" semantics — the text fades out with
3621
+ // the alert. Lumia's AlertBoxHandler reads these fields directly to
3622
+ // schedule `setTextVisible(false)` via
3623
+ // duration - textExitAnimationDuration - textExitOffset
3624
+ // If either is undefined the math evaluates to NaN, setTimeout
3625
+ // fires immediately, and the text becomes visible for one frame
3626
+ // before being hidden — which is exactly the bug that made
3627
+ // imported alerts look like they had "no text at all". Defaulting
3628
+ // to a 1s fade-out that starts 1s before the alert ends matches
3629
+ // SE's perceived behaviour.
3630
+ textExitAnimation: "fadeOut",
3631
+ textExitAnimationDuration: 1e3,
3632
+ textExitOffset: 1e3,
3633
+ // SE wraps variables (`{name}`, `{amount}`, etc.) in a span styled by
3634
+ // `text.css.highlights`. Color comes via `themeSettings.highlightColor`;
3635
+ // the animation lives here. When SE *doesn't* set
3636
+ // `highlights.animation` explicitly, variables visually inherit the
3637
+ // text-level `text.animation` (`pulse`, `bounce`, etc.) because
3638
+ // they're nested inside the animating text. We mirror that fallback so
3639
+ // imports look the same as on SE — without it, common imports (where
3640
+ // only `text.animation` is set) would have a static highlight color
3641
+ // with no pulse, which reads as "the highlight animation didn't
3642
+ // import" even though the color did.
3643
+ highlightTextEnterAnimation: highlight.animation || text.animation || "",
3644
+ highlightTextEnterDelay: 0,
3645
+ highlightTextEnterAnimationDuration: 1e3,
3646
+ highlightTextEnterRepeat: 1,
3647
+ highlightTextExitAnimation: "",
3648
+ highlightTextExitAnimationDuration: 0,
3649
+ highlightTextExitOffset: 0
3490
3650
  },
3491
3651
  tts: {
3492
3652
  enabled: !!seEvent.tts?.enabled,
@@ -3497,10 +3657,12 @@ function buildAlertEvent(seEvent, seEventType) {
3497
3657
  variations
3498
3658
  };
3499
3659
  }
3500
- function buildAlertVariation(v, seEventType) {
3660
+ function buildAlertVariation(v, seEventType, parentSettings) {
3501
3661
  const translated = translateVariationCondition(v.type ?? null, v.condition ?? null, v.requirement ?? null);
3502
3662
  if (!translated) return null;
3503
- const base = buildAlertEvent(v.settings, seEventType);
3663
+ const { variations: _parentVariations, ...parentInheritable } = parentSettings ?? {};
3664
+ const mergedSettings = deepMergeSeSettings(parentSettings ? parentInheritable : void 0, v.settings);
3665
+ const base = buildAlertEvent(mergedSettings, seEventType);
3504
3666
  return {
3505
3667
  name: v.name ?? "Variation",
3506
3668
  on: v.enabled !== false,
@@ -3511,6 +3673,22 @@ function buildAlertVariation(v, seEventType) {
3511
3673
  ...base
3512
3674
  };
3513
3675
  }
3676
+ function deepMergeSeSettings(parent, variation) {
3677
+ if (parent == null) return variation;
3678
+ if (variation == null) return parent;
3679
+ if (typeof parent !== "object" || typeof variation !== "object") return variation;
3680
+ if (Array.isArray(parent) || Array.isArray(variation)) return variation;
3681
+ const out = { ...parent };
3682
+ for (const [k, vv] of Object.entries(variation)) {
3683
+ if (vv === void 0) continue;
3684
+ if (vv && typeof vv === "object" && !Array.isArray(vv) && parent[k] && typeof parent[k] === "object" && !Array.isArray(parent[k])) {
3685
+ out[k] = deepMergeSeSettings(parent[k], vv);
3686
+ } else {
3687
+ out[k] = vv;
3688
+ }
3689
+ }
3690
+ return out;
3691
+ }
3514
3692
  function mapAlertBox(widget, opts = {}) {
3515
3693
  const v = widget.variables ?? {};
3516
3694
  const events = {};
@@ -3539,6 +3717,9 @@ function currentForListener(listener) {
3539
3717
  if (listener.startsWith("subscriber-")) return "{{twitch_total_subscriber_count}}";
3540
3718
  if (listener.startsWith("cheer-")) return "{{twitch_total_bits_count}}";
3541
3719
  if (listener.startsWith("tip-")) return "{{total_donation_amount}}";
3720
+ if (listener === "merch-goal-orders") return "{{fourthwall_total_order_count}}";
3721
+ if (listener === "merch-goal-items") return "{{fourthwall_total_items_count}}";
3722
+ if (listener === "merch-goal-total") return "{{fourthwall_total_order_amount}}";
3542
3723
  if (listener.startsWith("merch-")) return "{{fourthwall_total_order_count}}";
3543
3724
  return "0";
3544
3725
  }
@@ -3548,8 +3729,11 @@ function mapGoal(widget) {
3548
3729
  return buildUnit(widget, "goal", {
3549
3730
  content: {
3550
3731
  version: 1,
3551
- // Matches GoalTypes enum in components/Modules/Views/Goal/types.ts.
3552
- type: "bar",
3732
+ // `slim` reproduces SE's default goal look — label above a thin
3733
+ // track, with `0` / `goalAmount` anchoring the bar's edges and the
3734
+ // live `current` floating at the percentage position. See
3735
+ // components/Modules/Views/Goal/types.ts:GoalTypes.SLIM.
3736
+ type: "slim",
3553
3737
  label: translateSeText(v.title ?? "Goal"),
3554
3738
  subLabel: "",
3555
3739
  // The Goal module renders this template literal with {{current}} + {{goal}} substituted.
@@ -3577,10 +3761,13 @@ function mapGoal(widget) {
3577
3761
  se_simple_design: !!v.simpleDesign
3578
3762
  },
3579
3763
  css: {
3580
- borderRadius: "0px",
3581
- borderStyle: "solid",
3582
- borderWidth: "0px",
3583
- borderColor: "transparent",
3764
+ // `borderRadius` / `borderStyle` / `borderWidth` / `borderColor`
3765
+ // used to be set here too, but the Goal renderer applies its own
3766
+ // border + borderRadius via inline `style={…}` on the .goal-bar
3767
+ // div (see Goal/index.tsx:123,127,155) sourced from
3768
+ // `content.border` and `content.borderRadius`. Writing them to
3769
+ // module.css just plopped useless transparent-0px rules on the
3770
+ // outer .overlay-module wrapper.
3584
3771
  lineHeight: 1,
3585
3772
  fontSize: 30,
3586
3773
  textAlign: "center",
@@ -3639,97 +3826,115 @@ function mapCustom(widget) {
3639
3826
  }
3640
3827
  });
3641
3828
  }
3642
- var SEASONAL_TEMPLATE_HTML = '<canvas id="particles"></canvas>';
3643
- var SEASONAL_TEMPLATE_CSS = `html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: transparent; overflow: hidden; }
3644
- #particles { position: fixed; inset: 0; width: 100%; height: 100%; pointer-events: none; }`;
3645
- var SEASONAL_TEMPLATE_JS = `const SEASONS = {
3646
- snow: { emoji: '\\u2744', count: 80, sizeMin: 14, sizeMax: 28, gravity: 1.0, drift: 0.6, sway: 1.2 },
3647
- halloween: { emoji: '\\ud83d\\udc7b', count: 25, sizeMin: 28, sizeMax: 44, gravity: 0.6, drift: 0.4, sway: 0.8 },
3648
- xmas: { emoji: '\\u2744', count: 120, sizeMin: 16, sizeMax: 32, gravity: 1.1, drift: 0.7, sway: 1.3 },
3649
- valentine: { emoji: '\\u2764\\ufe0f', count: 50, sizeMin: 18, sizeMax: 34, gravity: 0.8, drift: 0.5, sway: 1.0 },
3650
- easter: { emoji: '\\ud83e\\udd5a', count: 30, sizeMin: 22, sizeMax: 38, gravity: 0.9, drift: 0.3, sway: 0.4 },
3651
- worldcup: { emoji: '\\u26bd', count: 25, sizeMin: 24, sizeMax: 40, gravity: 1.1, drift: 0.4, sway: 0.5 },
3652
- };
3653
- const cfg = SEASONS[(window.DATA && window.DATA.season) || 'snow'] || SEASONS.snow;
3654
- const canvas = document.getElementById('particles');
3655
- const ctx = canvas.getContext('2d');
3656
- const dpr = window.devicePixelRatio || 1;
3657
- function resize() {
3658
- canvas.width = window.innerWidth * dpr;
3659
- canvas.height = window.innerHeight * dpr;
3660
- canvas.style.width = window.innerWidth + 'px';
3661
- canvas.style.height = window.innerHeight + 'px';
3662
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
3663
- }
3664
- resize();
3665
- window.addEventListener('resize', resize);
3666
- const particles = [];
3667
- for (let i = 0; i < cfg.count; i++) {
3668
- particles.push({
3669
- x: Math.random() * window.innerWidth,
3670
- y: Math.random() * window.innerHeight - window.innerHeight,
3671
- size: cfg.sizeMin + Math.random() * (cfg.sizeMax - cfg.sizeMin),
3672
- speed: 0.5 + Math.random() * 1.5,
3673
- phase: Math.random() * Math.PI * 2,
3674
- });
3675
- }
3676
- function tick(now) {
3677
- ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
3678
- const t = now / 1000;
3679
- for (const p of particles) {
3680
- p.y += p.speed * cfg.gravity;
3681
- const sway = Math.sin(t + p.phase) * cfg.sway;
3682
- if (p.y > window.innerHeight + 40) {
3683
- p.y = -40;
3684
- p.x = Math.random() * window.innerWidth;
3685
- }
3686
- ctx.font = p.size + 'px serif';
3687
- ctx.fillText(cfg.emoji, p.x + sway * 20, p.y);
3688
- }
3689
- requestAnimationFrame(tick);
3690
- }
3691
- requestAnimationFrame(tick);`;
3692
- function mapSeasonal(widget, season) {
3693
- return buildUnit(widget, "custom", {
3694
- content: {
3695
- codeId: `seasonal-${season}-${widget.id}`,
3696
- html: SEASONAL_TEMPLATE_HTML,
3697
- css: SEASONAL_TEMPLATE_CSS,
3698
- js: SEASONAL_TEMPLATE_JS,
3699
- configs: [
3700
- {
3701
- key: "season",
3702
- label: "Season",
3703
- type: "dropdown",
3704
- value: season,
3705
- options: { snow: "Snow", halloween: "Halloween", xmas: "Christmas", valentine: "Valentine's", easter: "Easter", worldcup: "World Cup" }
3706
- }
3707
- ],
3708
- data: { season },
3709
- flavor: "lumia-seasonal"
3710
- }
3711
- });
3712
- }
3713
3829
 
3714
3830
  // src/se-import/mappers/misc.ts
3831
+ function mapChatboxTheme(seTheme) {
3832
+ const t = (seTheme ?? "").toString().toLowerCase();
3833
+ if (!t || t === "default") return "simple";
3834
+ if (t.includes("flipin") || t.includes("bubble") || t.includes("bubbly")) return "bubbly";
3835
+ if (t.includes("glass") || t.includes("transparent")) return "glass";
3836
+ if (t.includes("boxy") || t.includes("checker")) return "boxy";
3837
+ if (t.includes("basic")) return "basic";
3838
+ return "simple";
3839
+ }
3840
+ function paddingShorthand(message) {
3841
+ const pick = (key) => {
3842
+ const raw = message?.[key];
3843
+ if (raw == null || raw === "") return null;
3844
+ const n = typeof raw === "number" ? raw : parseFloat(String(raw));
3845
+ return Number.isFinite(n) ? n : null;
3846
+ };
3847
+ const t = pick("padding-top");
3848
+ const r = pick("padding-right");
3849
+ const b = pick("padding-bottom");
3850
+ const l = pick("padding-left");
3851
+ if (t == null && r == null && b == null && l == null) return void 0;
3852
+ const px = (n) => `${n ?? 0}px`;
3853
+ return `${px(t)} ${px(r)} ${px(b)} ${px(l)}`;
3854
+ }
3715
3855
  function mapChatbox(widget) {
3716
3856
  const v = widget.variables ?? {};
3717
- const fadeOutSeconds = typeof v.fadeOut === "number" ? v.fadeOut : 0;
3857
+ const textCss = widget.text?.css ?? {};
3858
+ const messageCss = textCss.message ?? {};
3859
+ const fadeOutSeconds = typeof v.fadeOut === "number" ? v.fadeOut : v.fadeOut === true ? 30 : 0;
3860
+ const messagePadding = paddingShorthand(messageCss);
3861
+ const DEFAULT_CHATBOX_FONT_SIZE = 14;
3862
+ const moduleCss = {};
3863
+ if (textCss["font-family"] != null) moduleCss.fontFamily = textCss["font-family"];
3864
+ moduleCss.fontSize = textCss["font-size"] != null ? textCss["font-size"] : DEFAULT_CHATBOX_FONT_SIZE;
3865
+ if (textCss["color"] != null) moduleCss.color = textCss["color"];
3866
+ if (textCss["font-weight"] != null) moduleCss.fontWeight = textCss["font-weight"];
3867
+ if (textCss["text-shadow"] != null) moduleCss.textShadow = textCss["text-shadow"];
3868
+ if (textCss["text-align"] != null) moduleCss.textAlign = textCss["text-align"];
3869
+ if (textCss["line-height"] != null) moduleCss.lineHeight = textCss["line-height"];
3870
+ if (messagePadding) moduleCss.padding = messagePadding;
3871
+ const DEFAULT_CHATBOX_BG = "#1e1e1e";
3872
+ const messageBackground = typeof messageCss["background"] === "string" && messageCss["background"].length > 0 ? messageCss["background"] : DEFAULT_CHATBOX_BG;
3873
+ moduleCss.background = messageBackground;
3874
+ const messageFontFamily = typeof messageCss["font-family"] === "string" && messageCss["font-family"].length > 0 ? messageCss["font-family"] : void 0;
3875
+ const highlightBackground = v.highlight && typeof v.highlight === "object" && typeof v.highlight["background"] === "string" ? v.highlight["background"] : void 0;
3876
+ const accentColor = highlightBackground && highlightBackground.length > 0 ? highlightBackground : void 0;
3718
3877
  return buildUnit(widget, "chatbox", {
3878
+ // Visual styling (font / color / padding etc) lives on module.css, not
3879
+ // content. Lumia's TextStyle settings panel + the chatbox renderer both
3880
+ // read from here.
3881
+ css: moduleCss,
3719
3882
  content: {
3720
- theme: v.theme ?? "default",
3883
+ theme: mapChatboxTheme(v.theme),
3721
3884
  fadeOutAfterDelay: fadeOutSeconds > 0,
3722
3885
  fadeOutDelayTime: fadeOutSeconds,
3886
+ // Defaults Lumia's chatbox renderer + UI expect on every module.
3887
+ // Without these the chatbox silently dropped messages (the
3888
+ // `ignoredStreamingSites` truthy gate), rendered in the wrong
3889
+ // theme (no theme → blank list), or showed missing controls in
3890
+ // settings. `ignoredList` carries SE's per-user ignore list across.
3891
+ ignoredList: Array.isArray(v.ignored) ? v.ignored : [],
3892
+ ignoredStreamingSites: [],
3893
+ horizontal: false,
3894
+ reverseFlow: false,
3895
+ hideAlerts: false,
3896
+ hideAlertMessage: false,
3723
3897
  showAvatar: true,
3724
- // SE `messageDelay` (between-message delay) and `delay` (initial delay) have no
3725
- // Lumia equivalent today — kept as provenance so users can wire them later if
3726
- // Lumia adds the feature.
3898
+ showBadges: true,
3899
+ showEmotes: true,
3900
+ showSiteIcon: true,
3901
+ showTimestamp: false,
3902
+ breakLine: false,
3903
+ hyperClickableLinks: true,
3904
+ clickableChatterProfiles: true,
3905
+ previewMediaInChat: true,
3906
+ maxItemsToShow: 20,
3907
+ removeAfter: 3e4,
3908
+ itemGap: 8,
3909
+ borderRadius: "10px",
3910
+ bgOpacity: 0.4,
3911
+ animations: {
3912
+ enterAnimation: "fadeIn",
3913
+ exitAnimation: "fadeOut",
3914
+ enterAnimationDuration: 1e3,
3915
+ exitAnimationDuration: 1e3,
3916
+ enterAnimationDelay: 0,
3917
+ exitAnimationDelay: 0
3918
+ },
3919
+ // Carry the SE message-level font override and the streamer's
3920
+ // accent color across so the imported chatbox looks right at
3921
+ // first paint. Background lives on `module.css.background`
3922
+ // above — `content.background` was a dead write the renderer
3923
+ // never read.
3924
+ ...messageFontFamily ? { messageFontFamily } : {},
3925
+ ...accentColor ? {
3926
+ chatboxStreamingSite: {
3927
+ twitch: { themeConfig: { primaryColor: accentColor, showUsernameColors: true } }
3928
+ }
3929
+ } : {},
3930
+ // SE-only fields preserved as provenance — no native Lumia equivalent.
3931
+ // `delay` / `messageDelay` (initial + per-message hold), and the
3932
+ // raw highlight block in case a future Lumia feature wants the
3933
+ // fine-grained styling.
3727
3934
  se_message_delay: v.messageDelay,
3728
3935
  se_initial_delay: v.delay,
3729
- // SE `ignored` (per-user ignore) and `highlight` (per-user emphasis) likewise
3730
- // have no Lumia equivalent — preserved as provenance only.
3731
- se_ignored_users: v.ignored,
3732
- se_highlight_users: v.highlight
3936
+ se_highlight_users: v.highlight,
3937
+ se_text_css: textCss
3733
3938
  }
3734
3939
  });
3735
3940
  }
@@ -3858,7 +4063,7 @@ var CREDITS_DEFAULT_EVENTS_TO_SHOW = {
3858
4063
  };
3859
4064
  function mapCredits(widget) {
3860
4065
  const v = widget.variables ?? {};
3861
- const itemGap = typeof v.padding === "number" ? v.padding : void 0;
4066
+ const itemGap = typeof v.padding === "number" ? v.padding : 8;
3862
4067
  const reverseFlow = v.direction === "bottom";
3863
4068
  return buildUnit(widget, "credits", {
3864
4069
  content: {
@@ -3879,13 +4084,38 @@ function mapCredits(widget) {
3879
4084
  }
3880
4085
  });
3881
4086
  }
4087
+ function inferSlideshowMediaType(mime, url) {
4088
+ if (typeof mime === "string") {
4089
+ if (mime.startsWith("video/")) return "video";
4090
+ if (mime.startsWith("image/")) return "image";
4091
+ }
4092
+ const ext = url.split("?")[0].split("#")[0].split(".").pop()?.toLowerCase() ?? "";
4093
+ if (["mp4", "webm", "mov", "m4v", "ogv"].includes(ext)) return "video";
4094
+ return "image";
4095
+ }
4096
+ function nameFromUrl(url) {
4097
+ try {
4098
+ const path = url.split("?")[0].split("#")[0];
4099
+ return decodeURIComponent(path.split("/").pop() ?? url);
4100
+ } catch {
4101
+ return url;
4102
+ }
4103
+ }
3882
4104
  function mapSlideshow(widget) {
3883
4105
  const v = widget.variables ?? {};
3884
- const sources = (v.images ?? []).map((i) => typeof i === "string" ? i : i.src ?? "").filter(Boolean);
4106
+ const items = (v.images ?? []).map((raw) => {
4107
+ const url = typeof raw === "string" ? raw : raw.url ?? raw.src ?? "";
4108
+ if (!url) return null;
4109
+ return {
4110
+ type: inferSlideshowMediaType(typeof raw === "object" ? raw?.type : void 0, url),
4111
+ src: url,
4112
+ name: nameFromUrl(url)
4113
+ };
4114
+ }).filter((item) => item !== null);
3885
4115
  const seAnim = typeof v.animation === "object" && v.animation !== null ? v.animation : {};
3886
4116
  return buildUnit(widget, "slideshow", {
3887
4117
  content: {
3888
- items: sources.map((src) => ({ src })),
4118
+ src: items,
3889
4119
  // `interval` of 0 = "rapid cycle" in SE (no delay between images); Lumia's
3890
4120
  // `imageDuration` is the total ms per image. Distinguish three cases:
3891
4121
  // - undefined → use 5000ms default
@@ -3896,7 +4126,10 @@ function mapSlideshow(widget) {
3896
4126
  loopDelay: 0,
3897
4127
  delay: 0,
3898
4128
  playAudio: !!v.playAudio,
3899
- random: !!v.random,
4129
+ // Lumia's renderer reads `content.shuffle` (Slideshow/index.tsx:21);
4130
+ // mirroring SE's `random` to that name keeps "play in random order"
4131
+ // working out of the box.
4132
+ shuffle: !!v.random,
3900
4133
  animation: {
3901
4134
  enterAnimation: typeof v.animation === "string" ? v.animation : seAnim.in ?? "fadeIn",
3902
4135
  exitAnimation: typeof v.animation === "string" ? v.animation : seAnim.out ?? "fadeOut",
@@ -4012,15 +4245,69 @@ function mapNowPlaying(widget) {
4012
4245
  }
4013
4246
  });
4014
4247
  }
4248
+ function mapMediaShare(widget) {
4249
+ const v = widget.variables ?? {};
4250
+ return buildUnit(widget, "songrequest", {
4251
+ content: {
4252
+ version: 1,
4253
+ // SE always shows the now-playing card; honour its cosmetic toggles
4254
+ // for the rest. `showTitle` defaults true; the others default to
4255
+ // SE's documented behavior (tipper visible, stats hidden).
4256
+ showNowPlayingCard: true,
4257
+ showQueueList: true,
4258
+ showSkipButton: true,
4259
+ showPauseButton: true,
4260
+ showTitle: v.showTitle !== false,
4261
+ showTipper: v.showTipper !== false,
4262
+ showAmount: v.showAmount === true,
4263
+ showStats: v.showStats === true,
4264
+ // SE doesn't have a queue-size knob — pick a sensible default
4265
+ // matching what fits in the SE widget's ~292px tall footprint.
4266
+ maxQueueItemsVisible: 5,
4267
+ // SE color knobs translate directly. Fall back to Lumia's defaults
4268
+ // (purple accent, near-black card background) if SE values are
4269
+ // missing — matches what the module ships with for native creates.
4270
+ accentColor: v.accentColor ?? "#9146ff",
4271
+ primaryColor: v.primaryColor ?? "#1a1a1a",
4272
+ // SE's widget hides the YT player by default; mirror that for
4273
+ // audio-only overlay use.
4274
+ playerVisible: false,
4275
+ defaultVolume: 70,
4276
+ autoAdvanceOnEnd: true,
4277
+ // SE-only knob preserved for round-trip provenance; Lumia ignores
4278
+ // it (LumiaStream auto-advance fires on `durationSeconds + grace`).
4279
+ se_timeLeft: v.timeLeft === true
4280
+ },
4281
+ css: {
4282
+ fontFamily: "Roboto",
4283
+ fontWeight: "bold",
4284
+ color: "#ffffff",
4285
+ textShadow: "rgb(0, 0, 0) 1px 1px 1px",
4286
+ background: "transparent",
4287
+ fontSize: 18
4288
+ }
4289
+ });
4290
+ }
4015
4291
  var SE_KAPPAGEN_EVENT_TO_LUMIA_ALERT = {
4016
4292
  follower: "twitch-follower",
4017
4293
  subscriber: "twitch-subscriber",
4018
4294
  cheer: "twitch-bits",
4019
4295
  raid: "twitch-raid",
4020
- host: "twitch-host",
4021
- tip: "streamelements-donation",
4022
- purchase: "streamlabs-merch",
4023
- embers: "youtube-superchat"
4296
+ tip: "streamelements-donation"
4297
+ // NOTE — three SE events intentionally NOT mapped:
4298
+ // • `host` → previously `twitch-host`, which doesn't exist in
4299
+ // LumiaAlertValues (verified). Twitch deprecated hosting
4300
+ // in 2022 so the event never fires anyway.
4301
+ // • `purchase` → previously `streamlabs-merch`, which is deprecated. The
4302
+ // main Alert Box mapper routes merch/purchase to
4303
+ // `fourthwall-shopOrder`; keep Kappagen consistent by
4304
+ // dropping the legacy SL path rather than splitting
4305
+ // emoji emotion between two competing alert keys.
4306
+ // • `embers` → previously `youtube-superchat`, which is wrong on both
4307
+ // ends: embers were a deprecated Twitch paid-emote
4308
+ // feature unrelated to YouTube super chats.
4309
+ // All three drop silently; the rest of the widget (size, spawn zone,
4310
+ // per-event flags for the remaining events) still imports.
4024
4311
  };
4025
4312
  function mapKappagen(widget) {
4026
4313
  const v = widget.variables ?? {};
@@ -4155,6 +4442,75 @@ function mapGiveaway(widget) {
4155
4442
  }
4156
4443
  });
4157
4444
  }
4445
+ var HYPETRAIN_LISTENER_KEYS = /* @__PURE__ */ new Set([
4446
+ "hypetrain-latest",
4447
+ "hypetrain-level-progress",
4448
+ "hypetrain-level-goal",
4449
+ "hypetrain-total",
4450
+ "hypetrain-latest-top-contributors",
4451
+ // SE alert-name variants some community widgets subscribe to as well.
4452
+ "hypetrain-start",
4453
+ "hypetrain-end",
4454
+ "hypetrain-cooldown-start",
4455
+ "hypetrain-cooldown-end"
4456
+ ]);
4457
+ function isHypetrainCustomWidget(widget) {
4458
+ const enabled = [];
4459
+ const listeners = widget.listeners;
4460
+ if (listeners && typeof listeners === "object" && !Array.isArray(listeners)) {
4461
+ for (const [k, v] of Object.entries(listeners)) {
4462
+ if (v) enabled.push(k);
4463
+ }
4464
+ } else if (Array.isArray(listeners)) {
4465
+ enabled.push(...listeners);
4466
+ }
4467
+ const explicit = widget.listener;
4468
+ if (typeof explicit === "string" && explicit && !enabled.includes(explicit)) {
4469
+ enabled.push(explicit);
4470
+ }
4471
+ if (enabled.length === 0) return false;
4472
+ return enabled.every((listener) => HYPETRAIN_LISTENER_KEYS.has(listener));
4473
+ }
4474
+ function mapHypetrain(widget) {
4475
+ const v = widget.variables ?? {};
4476
+ return buildUnit(widget, "hypetrain", {
4477
+ content: {
4478
+ // Twitch-native is the right default — SE hype-train widgets always
4479
+ // reflected the Twitch native train. Users can flip to 'synthetic'
4480
+ // or 'hybrid' from Settings if they want cross-platform pooling.
4481
+ sourceMode: "twitch-native",
4482
+ sources: null,
4483
+ // default money-only whitelist; ignored in twitch-native anyway
4484
+ customValues: {},
4485
+ pretrainThreshold: 5,
4486
+ pretrainDurationMs: 5 * 60 * 1e3,
4487
+ levelDurationMs: 5 * 60 * 1e3,
4488
+ levelStep: 5,
4489
+ levelThresholds: [],
4490
+ // Pick a moderately energetic default theme — SE hype-train widgets
4491
+ // were never subtle. Users can switch in Settings.
4492
+ theme: "train",
4493
+ primaryColor: typeof v.primaryColor === "string" ? v.primaryColor : "#ff6b00",
4494
+ accentColor: typeof v.accentColor === "string" ? v.accentColor : "#ffd84d",
4495
+ textColor: typeof v.textColor === "string" ? v.textColor : "#ffffff",
4496
+ showLevel: true,
4497
+ showTotal: true,
4498
+ showTimer: true,
4499
+ showTopContributor: true,
4500
+ showLeaderboard: true,
4501
+ leaderboardLimit: 5,
4502
+ leaderboardDurationMs: 30 * 1e3,
4503
+ levelUpSoundUrl: "https://storage.lumiastream.com/overlays/lumia/audio/crowdClap.mp3",
4504
+ trainEndSoundUrl: "https://storage.lumiastream.com/overlays/lumia/audio/crowdClap.mp3",
4505
+ audioVolume: 0.5,
4506
+ // SE config preserved verbatim as provenance — no Lumia equivalent for
4507
+ // most of these knobs (per-widget animation tunables, sound URLs the
4508
+ // streamer might want to migrate manually, etc.). Users can inspect
4509
+ // these via the overlay JSON if they want to recreate a specific look.
4510
+ se_variables: v
4511
+ }
4512
+ });
4513
+ }
4158
4514
  function mapUnsupportedAsText(widget) {
4159
4515
  return buildUnit(widget, "text", {
4160
4516
  content: {
@@ -4172,44 +4528,293 @@ function mapUnsupportedAsText(widget) {
4172
4528
  });
4173
4529
  }
4174
4530
 
4531
+ // src/se-import/mappers/hypecup.ts
4532
+ var SE_TYPE_TO_CATEGORY = {
4533
+ cheer: "bits",
4534
+ sub: "subs",
4535
+ follower: "follows",
4536
+ raid: "raids",
4537
+ tip: "tips"
4538
+ };
4539
+ function liftSESpritesIntoTiers(types) {
4540
+ const out = {};
4541
+ if (!types) return out;
4542
+ for (const [seKey, block] of Object.entries(types)) {
4543
+ const cat = SE_TYPE_TO_CATEGORY[seKey];
4544
+ if (!cat) continue;
4545
+ const variations = block?.variations ?? [];
4546
+ const usable = variations.filter((v) => typeof v?.src === "string" && v.src.length > 0);
4547
+ const flagged = usable.filter((v) => v.default === true);
4548
+ const picked = flagged.length > 0 ? flagged : usable;
4549
+ const seenAmounts = /* @__PURE__ */ new Set();
4550
+ const tiers = [];
4551
+ for (const v of picked) {
4552
+ const amount = typeof v.amount === "number" && v.amount > 0 ? v.amount : 1;
4553
+ if (seenAmounts.has(amount)) continue;
4554
+ seenAmounts.add(amount);
4555
+ tiers.push({
4556
+ minAmount: amount,
4557
+ imageUrl: v.src,
4558
+ scale: typeof v.cheerSize === "number" && v.cheerSize > 0 ? v.cheerSize : 1
4559
+ });
4560
+ }
4561
+ tiers.sort((a, b) => a.minAmount - b.minAmount);
4562
+ if (tiers.length > 0) out[cat] = tiers;
4563
+ }
4564
+ return out;
4565
+ }
4566
+ var LISTENER_TO_CATEGORY = {
4567
+ "follower-latest": "follows",
4568
+ "subscriber-latest": "subs",
4569
+ "tip-latest": "tips",
4570
+ "cheer-latest": "bits",
4571
+ "raid-latest": "raids"
4572
+ };
4573
+ function categoriesFromListeners(listeners) {
4574
+ const events = {
4575
+ tips: false,
4576
+ bits: false,
4577
+ subs: false,
4578
+ resubs: false,
4579
+ gifts: false,
4580
+ superchats: false,
4581
+ superstickers: false,
4582
+ kicks: false,
4583
+ charity: false,
4584
+ follows: false,
4585
+ raids: false,
4586
+ redemptions: false,
4587
+ chat: false
4588
+ };
4589
+ if (!listeners) return events;
4590
+ const onListeners = Array.isArray(listeners) ? listeners : Object.entries(listeners).filter(([, on]) => on === true).map(([k]) => k);
4591
+ for (const l of onListeners) {
4592
+ const cat = LISTENER_TO_CATEGORY[l];
4593
+ if (cat) events[cat] = true;
4594
+ }
4595
+ if (events.subs) events.resubs = true;
4596
+ return events;
4597
+ }
4598
+ function mapHypeCup(widget) {
4599
+ const v = widget.variables ?? {};
4600
+ const widgetWidth = typeof v.width === "number" && v.width > 0 ? v.width : parsePx(widget.css?.width);
4601
+ const widgetHeight = typeof v.height === "number" && v.height > 0 ? v.height : parsePx(widget.css?.height);
4602
+ const cupX = positionToFraction(v.cupPosition?.x, widgetWidth, 0.5);
4603
+ const cupY = positionToFraction(v.cupPosition?.y, widgetHeight, 0.7);
4604
+ const bounce = typeof v.bounce === "number" ? Math.min(1, Math.max(0, v.bounce)) : 0.3;
4605
+ const maxBodies = typeof v.recentEvents === "number" && v.recentEvents > 0 ? Math.min(300, Math.max(10, v.recentEvents)) : 80;
4606
+ const duration = typeof v.duration === "number" && v.duration > 0 ? Math.min(15, Math.max(1, v.duration)) : 3;
4607
+ const sprites = liftSESpritesIntoTiers(v.types);
4608
+ return buildUnit(widget, "tipjar", {
4609
+ content: {
4610
+ version: 1,
4611
+ // Phase 1 ships a single preset; SE's per-cup polygon set isn't lifted
4612
+ // yet (it'd require multi-cup support which we deliberately deferred).
4613
+ // The streamer can swap a custom cup image + wireframe via Settings.
4614
+ preset: "glass-mug",
4615
+ customCup: null,
4616
+ cup: {
4617
+ x: cupX,
4618
+ y: cupY,
4619
+ scale: 1,
4620
+ opacity: 1
4621
+ },
4622
+ physics: {
4623
+ gravity: 1,
4624
+ bounce,
4625
+ friction: 0.06,
4626
+ maxBodies
4627
+ },
4628
+ tokens: {
4629
+ defaultRadius: 26
4630
+ },
4631
+ events: categoriesFromListeners(widget.listeners),
4632
+ chat: {
4633
+ useViewerAvatars: true
4634
+ },
4635
+ display: {
4636
+ showWireframes: !!v.showWireframes,
4637
+ showEventMessages: !!v.textEnabled,
4638
+ messageDuration: duration
4639
+ },
4640
+ sprites,
4641
+ // Phase 2 defaults for goal/break — disabled by import. Streamer
4642
+ // can enable in Settings; SE has no equivalent so nothing to lift.
4643
+ goal: { active: false, currency: "tips", target: 100, current: 0, readoutTemplate: "{{current}} / {{target}}", textColor: "#ffffff", hitAt: null },
4644
+ break: { enabled: false, command: "!breakjar", rebuildDelay: 5e3 },
4645
+ // SE-only knobs that don't have a Lumia equivalent — preserved as
4646
+ // provenance for future phases (per-cup polygons + enter/movement
4647
+ // physics tweaks). `se_types` removed — now consumed into sprites above.
4648
+ se_enter_from: v.enterFrom ?? null,
4649
+ se_prevent_movement: !!v.preventMovement,
4650
+ se_fullscreen: !!v.fullscreen,
4651
+ se_cups: v.cups ?? null
4652
+ },
4653
+ css: {
4654
+ background: "transparent"
4655
+ }
4656
+ });
4657
+ }
4658
+ function clamp01(n) {
4659
+ if (!Number.isFinite(n)) return 0.5;
4660
+ return Math.min(1, Math.max(0, n));
4661
+ }
4662
+ function positionToFraction(raw, widgetDim, fallback) {
4663
+ if (typeof raw !== "number" || !Number.isFinite(raw)) return fallback;
4664
+ if (raw <= 1) return clamp01(raw);
4665
+ if (typeof widgetDim !== "number" || widgetDim <= 0) return fallback;
4666
+ return clamp01(raw / widgetDim);
4667
+ }
4668
+ function parsePx(raw) {
4669
+ if (typeof raw === "number" && Number.isFinite(raw)) return raw;
4670
+ if (typeof raw !== "string") return void 0;
4671
+ const match = raw.match(/^(-?\d+(?:\.\d+)?)/);
4672
+ if (!match) return void 0;
4673
+ const n = Number(match[1]);
4674
+ return Number.isFinite(n) ? n : void 0;
4675
+ }
4676
+
4677
+ // src/se-import/mappers/streamboss.ts
4678
+ var SE_LISTENER_TO_ALERT = {
4679
+ "follower-latest": "twitch-follower",
4680
+ "subscriber-latest": "twitch-subscriber",
4681
+ "tip-latest": "lumiastream-donation",
4682
+ "cheer-latest": "twitch-bits",
4683
+ "raid-latest": "twitch-raid"
4684
+ // `purchase-latest` and `subscriber-gifted-latest` are intentionally absent.
4685
+ // SE rolled gifted subs into `subscriber-latest`; Lumia splits them via
4686
+ // extraSettings.isGift, which the runtime already handles consistently.
4687
+ };
4688
+ var SE_LISTENER_MODE = {
4689
+ "follower-latest": "flat",
4690
+ "subscriber-latest": "flat",
4691
+ "tip-latest": "multiplier",
4692
+ // 100 means "100 dmg per $"
4693
+ "cheer-latest": "multiplier",
4694
+ // 1 means "1 dmg per bit"
4695
+ "raid-latest": "multiplier"
4696
+ // 10 means "10 dmg per viewer"
4697
+ };
4698
+ function listenerIsOn(listeners, key) {
4699
+ if (!listeners) return false;
4700
+ if (Array.isArray(listeners)) return listeners.includes(key);
4701
+ return listeners[key] === true;
4702
+ }
4703
+ function mapStreamBoss(widget) {
4704
+ const v = widget.variables ?? {};
4705
+ const baseHp = typeof v.baseHP === "number" && v.baseHP > 0 ? Math.round(v.baseHP) : 1e3;
4706
+ const seDamage = v.damage ?? {};
4707
+ const damageMap = {};
4708
+ const selectedEvents = [];
4709
+ for (const [seListener, alertKey] of Object.entries(SE_LISTENER_TO_ALERT)) {
4710
+ if (!listenerIsOn(widget.listeners, seListener)) continue;
4711
+ const raw = seDamage[seListener];
4712
+ const value = typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : null;
4713
+ if (value === null) continue;
4714
+ damageMap[alertKey] = { value, mode: SE_LISTENER_MODE[seListener] };
4715
+ selectedEvents.push(alertKey);
4716
+ }
4717
+ const barColor = typeof v.colors?.frame === "string" && v.colors.frame ? v.colors.frame : "#2bff00";
4718
+ const widgetHeight = parsePx2(widget.css?.height);
4719
+ const tooShortForCard = typeof widgetHeight === "number" && widgetHeight < 140;
4720
+ const theme = v.showImage === false || tooShortForCard ? "bar-only" : "card";
4721
+ return buildUnit(widget, "streamboss", {
4722
+ content: {
4723
+ version: 1,
4724
+ theme,
4725
+ initialBossName: "Stream Boss",
4726
+ // SE has no stage cycling — every takeover resets to baseHP. We
4727
+ // honor that by shipping a single-element healthStages array; the
4728
+ // runtime cycles modulo length so a one-element array stays put.
4729
+ healthStages: [baseHp],
4730
+ currentBoss: "Stream Boss",
4731
+ currentBossAvatar: "",
4732
+ currentHP: baseHp,
4733
+ maxHP: baseHp,
4734
+ currentStageIndex: 0,
4735
+ damageMap,
4736
+ // Concrete selectedEvents means "user has opted in to exactly these
4737
+ // events". Empty arrays would disable everything — only happens if
4738
+ // SE had all listeners off, which mirrors what the imported overlay
4739
+ // should do.
4740
+ selectedEvents,
4741
+ bossHealsOnEventTrigger: v.selfHeal !== false,
4742
+ newBossCommand: "!newboss",
4743
+ eventQueueDelayMs: 3e3,
4744
+ defeatedDelayMs: 3e3,
4745
+ showAvatar: v.showImage !== false,
4746
+ showFloatingNumbers: true,
4747
+ showEventMessages: v.showText !== false,
4748
+ barColor,
4749
+ barBackgroundColor: "#333333",
4750
+ attackColor: "#ff007f",
4751
+ healColor: "#00ff00",
4752
+ // Phase 2 defaults — off by default so imports stay close to
4753
+ // SE's behaviour and don't surprise the streamer with new effects.
4754
+ lastResetAt: 0,
4755
+ playSounds: true,
4756
+ audioVolume: 50,
4757
+ damageSounds: [],
4758
+ healSound: "",
4759
+ newBossSound: "",
4760
+ defeatSound: "",
4761
+ megaBossChance: 0,
4762
+ particleScatter: true,
4763
+ showCrown: true,
4764
+ matchBossChatColor: true,
4765
+ bossNameColor: "",
4766
+ botChat: "off",
4767
+ chatAsSelf: false,
4768
+ // SE-only knobs preserved as provenance for future passes. None
4769
+ // have direct Lumia analogs today:
4770
+ // - `right` anchors the boss to the right edge of its bounds;
4771
+ // Lumia uses layer position instead, so this is informational.
4772
+ // - `bossName` controls vertical name position in SE; our themes
4773
+ // don't expose that level of fine-grained tweaking yet.
4774
+ // - `mode` SE's gameplay mode hint ('overkill', etc.).
4775
+ se_right: !!v.right,
4776
+ se_bossNamePosition: typeof v.bossName === "string" ? v.bossName : null,
4777
+ se_mode: typeof v.mode === "string" ? v.mode : null,
4778
+ se_showName: v.showName !== false
4779
+ },
4780
+ css: {
4781
+ background: "transparent"
4782
+ }
4783
+ });
4784
+ }
4785
+ function parsePx2(raw) {
4786
+ if (typeof raw === "number" && Number.isFinite(raw)) return raw;
4787
+ if (typeof raw !== "string") return void 0;
4788
+ const match = raw.match(/^(-?\d+(?:\.\d+)?)/);
4789
+ if (!match) return void 0;
4790
+ const n = Number(match[1]);
4791
+ return Number.isFinite(n) ? n : void 0;
4792
+ }
4793
+
4175
4794
  // src/se-import/feature-flags.ts
4176
4795
  var SE_IMPORT_FLAGS = {
4177
4796
  // (Removed) IMPORT_CUSTOM_EMBED: custom-event-list now routes to native eventlist
4178
4797
  // by default with a one-click "switch to custom HTML" option in the review step.
4179
4798
  // (Removed) IMPORT_MERCH_GOAL: Fourthwall integration provides session/total order
4180
4799
  // counters now (see fourthwall.manager.ts ORDER_PLACED handler); merch goal maps
4181
- // to {{fourthwall_total_order_count}} via the goal mapper.
4182
- // Merch products rotator Lumia has no Fourthwall product-list API yet. The
4183
- // Fourthwall integration is webhook-driven (events flow in) but doesn't fetch
4184
- // the user's shop catalog. Adding it requires a separate Fourthwall API auth
4185
- // flow + product-fetch endpoint + periodic refresh. Until then a static stub
4186
- // is unhelpful, so users are routed to review for AI/skip/marketplace-when-
4187
- // available replacements.
4188
- IMPORT_MERCH_PRODUCTS: false,
4189
- // Seasonal effects (snow/halloween/xmas/valentine/easter/worldcup). The
4190
- // generic canvas-particle template is approximate, not pixel-accurate vs
4191
- // SE's polished versions. Flagging off so users get the option to grab a
4192
- // AI-generated replacement or a curated marketplace seasonal overlay when one
4193
- // is configured.
4194
- IMPORT_SEASONAL: false
4800
+ // to {{fourthwall_total_*}} via the goal mapper (per-listener: orders → order_count,
4801
+ // items items_count, total order_amount).
4802
+ // (Removed) IMPORT_SEASONAL: seasonal widgets now route per-widget into existing
4803
+ // modules (image / slideshow / video) rather than a single approximate canvas-
4804
+ // particle template. See dispatcher.ts SEASONAL_IMAGE_TYPES comment for the
4805
+ // rationale and /SE/05-widget-to-module-mapping.md §D for the current mapping.
4806
+ // (Removed) IMPORT_MERCH_PRODUCTS: the merch-products-rotator now imports as an
4807
+ // empty slideshow with a friendly layer title prompting the user to add product
4808
+ // images manually. This is a degraded but honest UX — better than a placeholder
4809
+ // text layer for the (rare) streamer who uses SE's merch rotator. A Fourthwall
4810
+ // catalog fetcher would unlock fully-automatic import, but that's a multi-day
4811
+ // project gated on a new Fourthwall API auth scope, an endpoint, and a refresh
4812
+ // cadence.
4195
4813
  };
4196
- function isWidgetFlaggedOff(seType) {
4197
- if (seType === "se-widget-merch-products-rotator") return !SE_IMPORT_FLAGS.IMPORT_MERCH_PRODUCTS;
4198
- if (seType === "se-widget-snow" || seType === "se-widget-halloween" || seType === "se-widget-halloween-2019" || seType === "se-widget-xmas" || seType === "se-widget-valentine" || seType === "se-widget-easter" || seType === "se-widget-world-cup") {
4199
- return !SE_IMPORT_FLAGS.IMPORT_SEASONAL;
4200
- }
4814
+ function isWidgetFlaggedOff(_seType) {
4201
4815
  return false;
4202
4816
  }
4203
- var FLAG_OFF_REASONS = {
4204
- "se-widget-merch-products-rotator": "Merch products rotator \u2014 Lumia tracks Fourthwall orders but doesn't fetch the full product catalog yet. Generate a replacement with AI, skip it, or use Marketplace when a curated replacement is configured.",
4205
- "se-widget-snow": "Seasonal effect \u2014 the generic auto-import template is approximate. Generate a replacement with AI, skip it, or use Marketplace when a curated version is configured.",
4206
- "se-widget-halloween": "Seasonal effect \u2014 the generic auto-import template is approximate. Generate a replacement with AI, skip it, or use Marketplace when a curated version is configured.",
4207
- "se-widget-halloween-2019": "Seasonal effect \u2014 the generic auto-import template is approximate. Generate a replacement with AI, skip it, or use Marketplace when a curated version is configured.",
4208
- "se-widget-xmas": "Seasonal effect \u2014 the generic auto-import template is approximate. Generate a replacement with AI, skip it, or use Marketplace when a curated version is configured.",
4209
- "se-widget-valentine": "Seasonal effect \u2014 the generic auto-import template is approximate. Generate a replacement with AI, skip it, or use Marketplace when a curated version is configured.",
4210
- "se-widget-easter": "Seasonal effect \u2014 the generic auto-import template is approximate. Generate a replacement with AI, skip it, or use Marketplace when a curated version is configured.",
4211
- "se-widget-world-cup": "Seasonal effect \u2014 the generic auto-import template is approximate. Generate a replacement with AI, skip it, or use Marketplace when a curated version is configured."
4212
- };
4817
+ var FLAG_OFF_REASONS = {};
4213
4818
 
4214
4819
  // src/se-import/dispatcher.ts
4215
4820
  var READOUT_TYPES = {
@@ -4238,15 +4843,7 @@ var READOUT_TYPES = {
4238
4843
  // twitch_session_chat_count, so we render it as a text layer with the variable inline.
4239
4844
  "se-widget-botcounter": "twitch_session_chat_count"
4240
4845
  };
4241
- var SEASONAL_TYPES = {
4242
- "se-widget-snow": "snow",
4243
- "se-widget-halloween": "halloween",
4244
- "se-widget-halloween-2019": "halloween",
4245
- "se-widget-xmas": "xmas",
4246
- "se-widget-valentine": "valentine",
4247
- "se-widget-easter": "easter",
4248
- "se-widget-world-cup": "worldcup"
4249
- };
4846
+ var SEASONAL_IMAGE_TYPES = /* @__PURE__ */ new Set(["se-widget-halloween", "se-widget-xmas", "se-widget-valentine", "se-widget-easter", "se-widget-world-cup"]);
4250
4847
  var GOAL_TYPES = /* @__PURE__ */ new Set([
4251
4848
  "se-widget-follower-goal",
4252
4849
  "se-widget-subscriber-goal",
@@ -4272,7 +4869,6 @@ function dispatch(widget) {
4272
4869
  if (GOAL_TYPES.has(t)) return { unit: mapGoal(widget), status: "direct", lumiaType: "goal" };
4273
4870
  if (t === "se-widget-alert-box") return { unit: mapAlertBox(widget), status: "direct", lumiaType: "alert" };
4274
4871
  if (t === "se-widget-alert-box-merch") return { unit: mapAlertBox(widget, { onlyEvents: ["merch"] }), status: "direct", lumiaType: "alert" };
4275
- if (t === "se-widget-store-redemptions") return { unit: mapSingleAlert(widget, "twitch-redemption"), status: "direct", lumiaType: "alert" };
4276
4872
  if (t === "se-widget-donor-drive-alert") return { unit: mapSingleAlert(widget, "donordrive-donation"), status: "direct", lumiaType: "alert" };
4277
4873
  if (t === "se-widget-extralife-alert") return { unit: mapSingleAlert(widget, "extralife-donation"), status: "direct", lumiaType: "alert" };
4278
4874
  if (t === "se-widget-tiltify-alert") return { unit: mapSingleAlert(widget, "tiltify-campaignDonation"), status: "direct", lumiaType: "alert" };
@@ -4287,10 +4883,17 @@ function dispatch(widget) {
4287
4883
  if (t === "se-widget-current-song") return { unit: mapNowPlaying(widget), status: "partial", lumiaType: "nowplaying" };
4288
4884
  if (t === "se-widget-kappagen") return { unit: mapKappagen(widget), status: "partial", lumiaType: "emotealert" };
4289
4885
  if (t === "se-widget-giveaway") return { unit: mapGiveaway(widget), status: "direct", lumiaType: "raffle" };
4290
- if (t === "se-widget-custom-event-list") return { unit: mapCustom(widget), status: "partial", lumiaType: "custom" };
4291
- if (t in SEASONAL_TYPES) {
4292
- return { unit: mapSeasonal(widget, SEASONAL_TYPES[t]), status: "direct", lumiaType: "custom" };
4886
+ if (t === "se-widget-media-share") return { unit: mapMediaShare(widget), status: "partial", lumiaType: "songrequest" };
4887
+ if (t === "se-widget-hype-cup") return { unit: mapHypeCup(widget), status: "partial", lumiaType: "tipjar" };
4888
+ if (t === "se-widget-bit-boss") return { unit: mapStreamBoss(widget), status: "partial", lumiaType: "streamboss" };
4889
+ if (t === "se-widget-hype-train") return { unit: mapHypetrain(widget), status: "partial", lumiaType: "hypetrain" };
4890
+ if (t === "se-widget-custom-event-list" && isHypetrainCustomWidget(widget)) {
4891
+ return { unit: mapHypetrain(widget), status: "partial", lumiaType: "hypetrain" };
4293
4892
  }
4893
+ if (t === "se-widget-custom-event-list") return { unit: mapCustom(widget), status: "partial", lumiaType: "custom" };
4894
+ if (t === "se-widget-snow") return { unit: mapSnow(widget), status: "direct", lumiaType: "video" };
4895
+ if (t === "se-widget-halloween-2019") return { unit: mapSlideshow(widget), status: "direct", lumiaType: "slideshow" };
4896
+ if (SEASONAL_IMAGE_TYPES.has(t)) return { unit: mapImage(widget), status: "direct", lumiaType: "image" };
4294
4897
  return { unit: mapUnsupportedAsText(widget), status: "placeholder", lumiaType: "text" };
4295
4898
  }
4296
4899
  function recordCoverage(coverage, result, seType) {
@@ -4477,7 +5080,10 @@ var SE_WIDGET_TO_MARKETPLACE_CANDIDATES = {
4477
5080
  // value for marketplace replacements since AI generation rarely produces
4478
5081
  // satisfying results for these.
4479
5082
  "se-widget-hype-cup": [],
4480
- "se-widget-bit-boss": [],
5083
+ // Note: `se-widget-bit-boss` is auto-mapped onto the native `streamboss`
5084
+ // module by the dispatcher and no longer needs a marketplace fallback.
5085
+ // Re-add a key here if a community curated boss overlay later supersedes
5086
+ // the native module for specific use cases.
4481
5087
  "se-widget-train": [],
4482
5088
  // Medium-difficulty widgets — possible to AI-generate but a curated overlay
4483
5089
  // will usually look better.
@@ -4487,6 +5093,15 @@ var SE_WIDGET_TO_MARKETPLACE_CANDIDATES = {
4487
5093
  // have curated replacements for.
4488
5094
  "se-widget-custom-event-list": [],
4489
5095
  "se-widget-botcounter": [],
5096
+ // Hype Train — the dispatcher already auto-routes `se-widget-hype-train` and
5097
+ // hypetrain-only custom-event-lists onto the native `hypetrain` module. This
5098
+ // entry stays here for two cases the auto-router can't catch:
5099
+ // - Heavily customized hype-train custom widgets where the native module
5100
+ // would lose too much visual fidelity (custom locomotive art, themed
5101
+ // particles, etc.) and the streamer would prefer a curated alternative.
5102
+ // - Community curated overlays that pair the native hypetrain with extra
5103
+ // decorative layers (e.g. a "depot" background, station signage).
5104
+ "se-widget-hype-train": [],
4490
5105
  // Flagged-off in feature-flags.ts — the auto-import is disabled because
4491
5106
  // Lumia has no merch data source. Marketplace overlays here would be
4492
5107
  // curated static merch-style goals/sliders that don't depend on a feed.
@@ -4517,6 +5132,7 @@ var SEAuthError = class extends Error {
4517
5132
  this.code = code;
4518
5133
  this.name = "SEAuthError";
4519
5134
  }
5135
+ code;
4520
5136
  };
4521
5137
  function decodeBase64Url(s) {
4522
5138
  const b64 = s.replace(/-/g, "+").replace(/_/g, "/");
@@ -4558,6 +5174,7 @@ var SEClient = class _SEClient {
4558
5174
  constructor(claims) {
4559
5175
  this.claims = claims;
4560
5176
  }
5177
+ claims;
4561
5178
  static fromJwt(jwt) {
4562
5179
  return new _SEClient(decodeJwtPayload(jwt));
4563
5180
  }
@@ -4619,6 +5236,13 @@ async function fetchUsableChannels(client) {
4619
5236
  const me = await client.get("/kappa/v2/users/current");
4620
5237
  return (me.channels ?? []).filter((c) => c.authorized !== false && c.inactive !== true);
4621
5238
  }
5239
+ async function fetchSEOverlays(client, count = 200) {
5240
+ const res = await client.get(`/kappa/v2/overlays/${client.channelId}?count=${count}`);
5241
+ return res.docs ?? [];
5242
+ }
5243
+ async function fetchSEBootstrapForOverlay(client, overlayId) {
5244
+ return client.get(`/kappa/v2/overlays/${overlayId}/bootstrap?isEditor=false&isMobile=false&isObs=false&isObsLive=false&isXsplit=false`);
5245
+ }
4622
5246
 
4623
5247
  // src/se-import/ui/SEImportWizard.tsx
4624
5248
  import { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo5, useRef as useRef4, useState as useState6 } from "react";
@@ -4763,23 +5387,37 @@ function ActiveLayerPreview({ overlay, layerId, CustomEmbed }) {
4763
5387
  }
4764
5388
 
4765
5389
  // src/se-import/ui/SEImportWizard.css
4766
- styleInject('.se-import,\n.se-import * {\n box-sizing: border-box;\n}\n.se-import {\n --se-bg: #17162a;\n --se-surface: #27264a;\n --se-panel: #171b38;\n --se-panel-2: #202449;\n --se-border: rgba(116, 124, 211, 0.24);\n --se-border-strong: rgba(126, 128, 255, 0.58);\n --se-text: #f5f4ff;\n --se-muted: #bab8d2;\n --se-primary: #ff4076;\n --se-purple: #393853;\n --se-green: #06c96f;\n --se-warn: #f7a42b;\n width: 100%;\n min-width: min(680px, 100%);\n color: var(--se-text);\n font-family: inherit;\n}\n.se-import__shell {\n border-radius: 0;\n padding: 28px 48px 24px;\n}\n.se-import__header {\n display: grid;\n justify-items: center;\n gap: 42px;\n}\n.se-import__title {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n font-size: 20px;\n font-weight: 500;\n}\n.se-import-stepper {\n display: grid;\n width: min(760px, 100%);\n grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));\n}\n.se-import-step {\n position: relative;\n display: grid;\n justify-items: center;\n gap: 12px;\n min-width: 0;\n color: rgba(255, 255, 255, 0.72);\n}\n.se-import-step__label {\n color: var(--se-text);\n font-size: 18px;\n line-height: 1.1;\n}\n.se-import-step__line {\n position: absolute;\n top: 42px;\n left: 50%;\n width: 100%;\n height: 2px;\n background: rgba(255, 255, 255, 0.16);\n}\n.se-import-step:last-child .se-import-step__line {\n display: none;\n}\n.se-import-step__dot {\n z-index: 1;\n display: grid;\n width: 22px;\n height: 22px;\n place-items: center;\n border-radius: 50%;\n background: #47485e;\n color: #fff;\n font-size: 13px;\n font-weight: 700;\n line-height: 1;\n}\n.se-import-step--active .se-import-step__dot {\n background: var(--se-primary);\n}\n.se-import-step--done .se-import-step__dot {\n background: var(--se-green);\n}\n.se-import-step--done .se-import-step__line {\n background: var(--se-green);\n}\n.se-import-step--done + .se-import-step--active .se-import-step__line {\n background:\n linear-gradient(\n 90deg,\n var(--se-green),\n var(--se-primary));\n}\n.se-import__content {\n display: grid;\n min-height: 470px;\n place-items: center;\n padding: 64px 0 28px;\n}\n.se-import-panel {\n width: min(100%, 1080px);\n padding: 28px;\n}\n.se-import-panel--narrow {\n width: min(100%, 1140px);\n}\n.se-import-panel--medium {\n width: min(100%, 880px);\n}\n.se-import-panel--wide {\n width: min(100%, 1000px);\n}\n.se-import-panel--confirm {\n width: min(100%, 800px);\n}\n.se-import-panel h2,\n.se-import-panel h3,\n.se-import-panel p {\n margin: 0;\n}\n.se-import-panel h2 {\n font-size: 26px;\n font-weight: 700;\n line-height: 1.15;\n}\n.se-import-panel > p {\n margin-top: 14px;\n color: var(--se-muted);\n font-size: 16px;\n line-height: 1.45;\n}\n.se-import-field {\n margin-top: 34px;\n}\n.se-import-field .mui-ls-input {\n --ls-control-height: 66px;\n}\n.se-import-field .MuiInputLabel-root {\n color: var(--se-text);\n font-weight: 700;\n}\n.se-import-field .MuiInputLabel-root.Mui-focused {\n color: #dfe1ff;\n}\n.se-import-field .MuiInputBase-root {\n background: rgba(28, 32, 66, 0.85);\n box-shadow: inset 0 0 0 3px rgba(255, 255, 255, 0.04);\n font-size: 18px;\n}\n.se-import-field .MuiOutlinedInput-notchedOutline {\n border-color: var(--se-border-strong) !important;\n}\n.se-import-help {\n display: flex;\n gap: 18px;\n margin-top: 30px;\n border: 1px solid var(--se-purple);\n border-radius: 12px;\n padding: 24px;\n color: var(--white2);\n}\n.se-import-help__icon {\n display: grid;\n width: 30px;\n height: 30px;\n flex: 0 0 auto;\n place-items: center;\n border: 1px solid var(--se-purple);\n border-radius: 50%;\n color: var(--white2);\n font-weight: 800;\n}\n.se-import-help__title {\n color: var(--se-text);\n font-size: 18px;\n font-weight: 700;\n}\n.se-import-help ol {\n margin: 16px 0 0;\n padding-left: 18px;\n font-size: 15px;\n line-height: 1.7;\n}\n.se-import-help a {\n color: var(--primary);\n font-weight: 700;\n}\n.se-import-help--info {\n align-items: center;\n}\n.se-import-section-title {\n display: flex;\n gap: 1rem;\n margin-bottom: 1.5rem;\n}\n.se-import-section-title__num {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 46px;\n height: 46px;\n border: 1px solid var(--se-purple);\n border-radius: 50%;\n font-size: 1.5rem;\n font-weight: 800;\n}\n.se-import-section-title__content {\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n.se-import-section-title h3 {\n font-size: 18px;\n font-weight: 800;\n}\n.se-import-section-title p {\n margin-top: 4px;\n color: var(--se-muted);\n font-size: 14px;\n}\n.se-import-grid {\n display: grid;\n gap: 8px;\n margin-top: 10px;\n}\n.se-import-grid--four {\n grid-template-columns: repeat(4, minmax(0, 1fr));\n}\n.se-import-grid--two {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n.se-import-stat {\n min-height: 116px;\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 8px;\n background:\n linear-gradient(\n 135deg,\n rgba(42, 47, 92, 0.9),\n rgba(33, 38, 78, 0.9));\n padding: 24px 22px;\n}\n.se-import-stat--success {\n background:\n linear-gradient(\n 135deg,\n rgba(12, 91, 86, 0.72),\n rgba(32, 65, 76, 0.8));\n border-color: rgba(19, 201, 126, 0.25);\n}\n.se-import-stat--info {\n border-color: rgba(116, 104, 255, 0.8);\n box-shadow: inset 3px 0 0 #6e76ff;\n}\n.se-import-stat--warn,\n.se-import-stat--muted {\n box-shadow: inset 3px 0 0 var(--se-warn);\n}\n.se-import-stat__label {\n color: var(--se-muted);\n font-size: 12px;\n font-weight: 800;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n.se-import-stat__value {\n margin-top: 12px;\n color: #fff;\n font-size: 30px;\n font-weight: 800;\n line-height: 1;\n}\n.se-import-stat__sub {\n margin-top: 8px;\n color: var(--se-muted);\n font-size: 13px;\n}\n.se-import-option {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto auto;\n align-items: center;\n gap: 18px;\n border: 1px solid var(--se-purple);\n border-radius: 12px;\n background:\n linear-gradient(\n 135deg,\n rgba(35, 39, 81, 0.95),\n rgba(31, 35, 73, 0.95));\n box-shadow: inset 3px 0 0 var(--se-primary);\n cursor: pointer;\n padding: 22px;\n margin-bottom: 1rem;\n}\n.se-import-option--disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n.se-import-option__icon {\n display: grid;\n width: 26px;\n height: 26px;\n place-items: center;\n border-radius: 50%;\n background: rgba(255, 255, 255, 0.22);\n color: #d8dbff;\n font-weight: 800;\n}\n.se-import-option__body {\n display: grid;\n gap: 8px;\n min-width: 0;\n}\n.se-import-option__title {\n font-size: 17px;\n font-weight: 800;\n}\n.se-import-option__copy {\n color: var(--se-muted);\n font-size: 14px;\n line-height: 1.45;\n}\n.se-import-option__count {\n color: #fff;\n font-size: 16px;\n}\n.se-import-option input {\n position: absolute;\n opacity: 0;\n pointer-events: none;\n}\n.se-import-toggle {\n position: relative;\n width: 48px;\n height: 28px;\n border-radius: 999px;\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.14);\n}\n.se-import-toggle::after {\n position: absolute;\n top: 4px;\n left: 4px;\n width: 20px;\n height: 20px;\n border-radius: 50%;\n background: #fff;\n content: "";\n transition: transform 160ms ease;\n}\n.se-import-option input:checked + .se-import-toggle {\n background: var(--primary);\n}\n.se-import-option input:checked + .se-import-toggle::after {\n transform: translateX(20px);\n}\n.se-import-advanced {\n margin-top: 26px;\n border-radius: 10px;\n background: rgba(32, 36, 73, 0.7);\n padding: 24px;\n}\n.se-import-advanced summary {\n cursor: pointer;\n font-size: 18px;\n font-weight: 700;\n}\n.se-import-advanced p {\n margin-top: 10px;\n color: var(--se-muted);\n}\n.se-import-transfer {\n position: relative;\n display: grid;\n width: min(720px, 100%);\n min-height: 190px;\n grid-template-columns: 180px minmax(260px, 1fr) 180px;\n align-items: center;\n gap: 28px;\n margin: 0 auto 60px;\n}\n.se-import-transfer__source {\n position: relative;\n display: grid;\n width: 160px;\n height: 160px;\n place-items: center;\n animation: se-import-source-bounce 1.8s ease-in-out infinite;\n}\n.se-import-transfer__source::before {\n top: -13px;\n left: -13px;\n box-shadow: 155px 0 0 var(--se-primary);\n}\n.se-import-transfer__source::after {\n bottom: -13px;\n left: -13px;\n box-shadow: 155px 0 0 var(--se-primary);\n}\n.se-import-transfer__source img {\n width: 192px;\n height: 192px;\n object-fit: contain;\n}\n.se-import-transfer__source--lumia {\n animation-delay: 0.22s;\n}\n.se-import-transfer__files {\n position: relative;\n height: 130px;\n}\n.se-import-transfer__file {\n position: absolute;\n top: 50%;\n left: -18px;\n width: 56px;\n height: 66px;\n object-fit: contain;\n transform: translateY(-50%);\n filter: drop-shadow(0 10px 12px rgba(8, 9, 25, 0.25));\n animation: se-import-file-fly 2.4s cubic-bezier(0.35, 0, 0.25, 1) infinite;\n}\n.se-import-transfer__file--one {\n animation-delay: 0s;\n}\n.se-import-transfer__file--two {\n animation-delay: 0.38s;\n}\n.se-import-transfer__file--three {\n animation-delay: 0.76s;\n}\n.se-import-transfer__file--four {\n animation-delay: 1.14s;\n}\n@keyframes se-import-source-bounce {\n 0%, 100% {\n transform: translateY(0) scale(1);\n }\n 50% {\n transform: translateY(-8px) scale(1.025);\n }\n}\n@keyframes se-import-file-fly {\n 0% {\n opacity: 0;\n transform: translate(-10px, -50%) scale(0.82) rotate(-8deg);\n }\n 12% {\n opacity: 1;\n }\n 45% {\n transform: translate(145px, calc(-50% - 18px)) scale(1.08) rotate(5deg);\n }\n 82% {\n opacity: 1;\n }\n 100% {\n opacity: 0;\n transform: translate(330px, -50%) scale(0.9) rotate(10deg);\n }\n}\n.se-import-mirror-head {\n display: flex;\n align-items: end;\n justify-content: space-between;\n gap: 20px;\n}\n.se-import-mirror-head p {\n margin-top: 14px;\n color: var(--se-muted);\n font-size: 16px;\n}\n.se-import-mirror-head span {\n flex: 0 0 auto;\n color: var(--se-muted);\n font-weight: 700;\n}\n.se-import-progress {\n width: 100%;\n height: 14px;\n margin-top: 28px;\n overflow: hidden;\n border: 0;\n border-radius: 999px;\n background: rgba(255, 64, 118, 0.1);\n}\n.se-import-progress::-webkit-progress-bar {\n background: rgba(255, 64, 118, 0.1);\n}\n.se-import-progress::-webkit-progress-value {\n border-radius: 999px;\n background:\n linear-gradient(\n 90deg,\n #ff4076,\n #e7295f);\n}\n.se-import-progress::-moz-progress-bar {\n border-radius: 999px;\n background:\n linear-gradient(\n 90deg,\n #ff4076,\n #e7295f);\n}\n.se-import-asset-list {\n max-height: 360px;\n margin-top: 22px;\n overflow-y: auto;\n border-top: 1px solid rgba(255, 255, 255, 0.08);\n}\n.se-import-asset-row {\n display: grid;\n grid-template-columns: 32px minmax(0, 1fr) auto auto;\n align-items: center;\n gap: 14px;\n min-height: 42px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n color: var(--se-muted);\n font-size: 13px;\n}\n.se-import-asset-row__icon {\n width: 24px;\n height: 28px;\n justify-self: end;\n object-fit: contain;\n}\n.se-import-asset-row__url {\n overflow: hidden;\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.se-import-asset-row__state {\n color: #9b8cff;\n font-weight: 800;\n}\n.se-import-asset-row--done .se-import-asset-row__state,\n.se-import-asset-row--reused .se-import-asset-row__state {\n color: var(--se-green);\n}\n.se-import-asset-row--failed .se-import-asset-row__state,\n.se-import-asset-row__error {\n color: #ff8080;\n}\n.se-import-link-button {\n border: 0;\n background: transparent;\n color: #ff9ab6;\n cursor: pointer;\n font: inherit;\n font-weight: 800;\n}\n.se-import-overview {\n margin-top: 28px;\n border: 1px solid var(--se-border);\n border-radius: 12px;\n background: rgba(26, 30, 61, 0.62);\n padding: 28px;\n}\n.se-import-overview h3 {\n margin-bottom: 18px;\n font-size: 18px;\n font-weight: 800;\n}\n.se-import-row {\n display: grid;\n grid-template-columns: 190px minmax(0, 1fr) auto;\n gap: 18px;\n align-items: center;\n min-height: 48px;\n border-top: 1px solid rgba(255, 255, 255, 0.08);\n color: var(--se-muted);\n}\n.se-import-row strong {\n min-width: 0;\n overflow: hidden;\n color: var(--se-text);\n font-weight: 600;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.se-import-row em {\n color: var(--se-muted);\n font-style: normal;\n}\n.se-import-review-card {\n display: flex;\n justify-content: space-between;\n gap: 18px;\n border: 1px solid var(--se-border);\n border-radius: 10px;\n background: rgba(30, 34, 71, 0.8);\n padding: 20px;\n}\n.se-import-review-card__type {\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n font-weight: 800;\n}\n.se-import-review-card p {\n margin-top: 8px;\n color: var(--se-muted);\n line-height: 1.45;\n}\n.se-import-review-card__status {\n flex: 0 0 auto;\n color: #facc15;\n font-size: 11px;\n font-weight: 800;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n.se-import-review-bulk,\n.se-import-actions--inline {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: 10px;\n margin-top: 16px;\n}\n.se-import-muted {\n margin-top: 16px;\n color: var(--se-muted);\n font-size: 13px;\n}\n.se-import-generated {\n display: grid;\n gap: 12px;\n margin-top: 18px;\n}\n.se-import-generated__title {\n font-weight: 800;\n}\n.se-import-preview {\n width: 100%;\n height: 260px;\n border: 1px solid var(--se-border);\n border-radius: 8px;\n background: repeating-conic-gradient(#1a1c20 0% 25%, #1d1f24 0% 50%) 50% / 20px 20px;\n}\n.se-import-code {\n margin-top: 16px;\n}\n.se-import-code summary {\n cursor: pointer;\n color: var(--se-muted);\n font-size: 13px;\n}\n.se-import-code pre {\n max-height: 220px;\n margin: 10px 0 0;\n overflow: auto;\n border-radius: 8px;\n background: rgba(0, 0, 0, 0.28);\n padding: 12px;\n color: #d8dcff;\n font-size: 12px;\n}\n.se-import-error {\n margin-top: 18px;\n border: 1px solid rgba(239, 68, 68, 0.45);\n border-radius: 8px;\n background: rgba(220, 38, 38, 0.12);\n padding: 12px;\n color: #fca5a5;\n}\n.se-import-error__title {\n font-weight: 800;\n}\n.se-import-error__hint {\n margin-top: 6px;\n white-space: pre-wrap;\n}\n.se-import-actions {\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n margin-top: 8px;\n}\n@media (max-width: 900px) {\n .se-import__shell {\n min-height: 0;\n padding: 24px 18px;\n }\n .se-import__content {\n min-height: 0;\n padding-top: 32px;\n }\n .se-import-stepper {\n grid-template-columns: repeat(auto-fit, minmax(70px, 1fr));\n }\n .se-import-step__label {\n font-size: 13px;\n }\n .se-import-grid--four,\n .se-import-grid--two,\n .se-import-transfer {\n grid-template-columns: 1fr;\n }\n .se-import-transfer {\n justify-items: center;\n }\n .se-import-transfer__files {\n width: min(360px, 100%);\n }\n .se-import-option,\n .se-import-row,\n .se-import-asset-row {\n grid-template-columns: 1fr;\n }\n .se-import-mirror-head,\n .se-import-review-card {\n align-items: flex-start;\n flex-direction: column;\n }\n}\n');
5390
+ styleInject('.se-import,\n.se-import * {\n box-sizing: border-box;\n}\n.se-import {\n --se-bg: #17162a;\n --se-surface: #27264a;\n --se-panel: #171b38;\n --se-panel-2: #202449;\n --se-border: rgba(116, 124, 211, 0.24);\n --se-border-strong: rgba(126, 128, 255, 0.58);\n --se-text: #f5f4ff;\n --se-muted: #bab8d2;\n --se-primary: #ff4076;\n --se-purple: #393853;\n --se-green: #06c96f;\n --se-warn: #f7a42b;\n width: 100%;\n height: 100%;\n min-width: min(680px, 100%);\n color: var(--se-text);\n font-family: inherit;\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n.se-import__shell {\n border-radius: 0;\n padding: 28px 48px 24px;\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n}\n.se-import__header {\n flex: 0 0 auto;\n}\nfooter.se-import-actions {\n flex: 0 0 auto;\n}\n.se-import__header {\n display: grid;\n justify-items: center;\n gap: 42px;\n}\n.se-import__title {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n font-size: 20px;\n font-weight: 500;\n}\n.se-import-stepper {\n display: grid;\n width: min(760px, 100%);\n grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));\n}\n.se-import-step {\n position: relative;\n display: grid;\n justify-items: center;\n gap: 12px;\n min-width: 0;\n color: rgba(255, 255, 255, 0.72);\n}\n.se-import-step__label {\n color: var(--se-text);\n font-size: 18px;\n line-height: 1.1;\n}\n.se-import-step__line {\n position: absolute;\n top: 42px;\n left: 50%;\n width: 100%;\n height: 2px;\n background: rgba(255, 255, 255, 0.16);\n}\n.se-import-step:last-child .se-import-step__line {\n display: none;\n}\n.se-import-step__dot {\n z-index: 1;\n display: grid;\n width: 22px;\n height: 22px;\n place-items: center;\n border-radius: 50%;\n background: #47485e;\n color: #fff;\n font-size: 13px;\n font-weight: 700;\n line-height: 1;\n}\n.se-import-step--active .se-import-step__dot {\n background: var(--se-primary);\n}\n.se-import-step--done .se-import-step__dot {\n background: var(--se-green);\n}\n.se-import-step--done .se-import-step__line {\n background: var(--se-green);\n}\n.se-import-step--done + .se-import-step--active .se-import-step__line {\n background:\n linear-gradient(\n 90deg,\n var(--se-green),\n var(--se-primary));\n}\n.se-import__content {\n display: grid;\n place-items: center;\n padding: 64px 0 28px;\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n}\n@media (max-height: 760px) {\n .se-import__shell {\n padding: 18px 32px 16px;\n }\n .se-import__content {\n padding: 32px 0 16px;\n }\n}\n.se-import-panel {\n width: min(100%, 1080px);\n padding: 28px;\n}\n.se-import-panel--narrow {\n width: min(100%, 1140px);\n}\n.se-import-panel--medium {\n width: min(100%, 880px);\n}\n.se-import-panel--wide {\n width: min(100%, 1000px);\n}\n.se-import-panel--confirm {\n width: min(100%, 800px);\n}\n.se-import-panel h2,\n.se-import-panel h3,\n.se-import-panel p {\n margin: 0;\n}\n.se-import-panel h2 {\n font-size: 26px;\n font-weight: 700;\n line-height: 1.15;\n}\n.se-import-panel > p {\n margin-top: 14px;\n color: var(--se-muted);\n font-size: 16px;\n line-height: 1.45;\n}\n.se-import-field {\n margin-top: 34px;\n}\n.se-import-field .mui-ls-input {\n --ls-control-height: 66px;\n}\n.se-import-field .MuiInputLabel-root {\n color: var(--se-text);\n font-weight: 700;\n}\n.se-import-field .MuiInputLabel-root.Mui-focused {\n color: #dfe1ff;\n}\n.se-import-field .MuiInputBase-root {\n background: rgba(28, 32, 66, 0.85);\n box-shadow: inset 0 0 0 3px rgba(255, 255, 255, 0.04);\n font-size: 18px;\n}\n.se-import-field .MuiOutlinedInput-notchedOutline {\n border-color: var(--se-border-strong) !important;\n}\n.se-import-help {\n display: flex;\n gap: 18px;\n margin-top: 30px;\n border: 1px solid var(--se-purple);\n border-radius: 12px;\n padding: 24px;\n color: var(--white2);\n}\n.se-import-help__icon {\n display: grid;\n width: 30px;\n height: 30px;\n flex: 0 0 auto;\n place-items: center;\n border: 1px solid var(--se-purple);\n border-radius: 50%;\n color: var(--white2);\n font-weight: 800;\n}\n.se-import-help__title {\n color: var(--se-text);\n font-size: 18px;\n font-weight: 700;\n}\n.se-import-help ol {\n margin: 16px 0 0;\n padding-left: 18px;\n font-size: 15px;\n line-height: 1.7;\n}\n.se-import-help a {\n color: var(--primary);\n font-weight: 700;\n}\n.se-import-help--info {\n align-items: center;\n}\n.se-import-section-title {\n display: flex;\n gap: 1rem;\n margin-bottom: 1.5rem;\n}\n.se-import-section-title__num {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 46px;\n height: 46px;\n border: 1px solid var(--se-purple);\n border-radius: 50%;\n font-size: 1.5rem;\n font-weight: 800;\n}\n.se-import-section-title__content {\n display: flex;\n flex-direction: column;\n justify-content: center;\n}\n.se-import-section-title h3 {\n font-size: 18px;\n font-weight: 800;\n}\n.se-import-section-title p {\n margin-top: 4px;\n color: var(--se-muted);\n font-size: 14px;\n}\n.se-import-grid {\n display: grid;\n gap: 8px;\n margin-top: 10px;\n}\n.se-import-grid--four {\n grid-template-columns: repeat(4, minmax(0, 1fr));\n}\n.se-import-grid--two {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n.se-import-stat {\n min-height: 116px;\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 8px;\n background:\n linear-gradient(\n 135deg,\n rgba(42, 47, 92, 0.9),\n rgba(33, 38, 78, 0.9));\n padding: 24px 22px;\n}\n.se-import-stat--success {\n background:\n linear-gradient(\n 135deg,\n rgba(12, 91, 86, 0.72),\n rgba(32, 65, 76, 0.8));\n border-color: rgba(19, 201, 126, 0.25);\n}\n.se-import-stat--info {\n border-color: rgba(116, 104, 255, 0.8);\n box-shadow: inset 3px 0 0 #6e76ff;\n}\n.se-import-stat--warn,\n.se-import-stat--muted {\n box-shadow: inset 3px 0 0 var(--se-warn);\n}\n.se-import-stat__label {\n color: var(--se-muted);\n font-size: 12px;\n font-weight: 800;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n.se-import-stat__value {\n margin-top: 12px;\n color: #fff;\n font-size: 30px;\n font-weight: 800;\n line-height: 1;\n}\n.se-import-stat__sub {\n margin-top: 8px;\n color: var(--se-muted);\n font-size: 13px;\n}\n.se-import-option {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr) auto auto;\n align-items: center;\n gap: 18px;\n border: 1px solid var(--se-purple);\n border-radius: 12px;\n background:\n linear-gradient(\n 135deg,\n rgba(35, 39, 81, 0.95),\n rgba(31, 35, 73, 0.95));\n box-shadow: inset 3px 0 0 var(--se-primary);\n cursor: pointer;\n padding: 22px;\n margin-bottom: 1rem;\n}\n.se-import-option--disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n.se-import-option__icon {\n display: grid;\n width: 26px;\n height: 26px;\n place-items: center;\n border-radius: 50%;\n background: rgba(255, 255, 255, 0.22);\n color: #d8dbff;\n font-weight: 800;\n}\n.se-import-option__body {\n display: grid;\n gap: 8px;\n min-width: 0;\n}\n.se-import-option__title {\n font-size: 17px;\n font-weight: 800;\n}\n.se-import-option__copy {\n color: var(--se-muted);\n font-size: 14px;\n line-height: 1.45;\n}\n.se-import-option__count {\n color: #fff;\n font-size: 16px;\n}\n.se-import-option input {\n position: absolute;\n opacity: 0;\n pointer-events: none;\n}\n.se-import-toggle {\n position: relative;\n width: 48px;\n height: 28px;\n border-radius: 999px;\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.14);\n}\n.se-import-toggle::after {\n position: absolute;\n top: 4px;\n left: 4px;\n width: 20px;\n height: 20px;\n border-radius: 50%;\n background: #fff;\n content: "";\n transition: transform 160ms ease;\n}\n.se-import-option input:checked + .se-import-toggle {\n background: var(--primary);\n}\n.se-import-option input:checked + .se-import-toggle::after {\n transform: translateX(20px);\n}\n.se-import-advanced {\n margin-top: 26px;\n border-radius: 10px;\n background: rgba(32, 36, 73, 0.7);\n padding: 24px;\n}\n.se-import-advanced summary {\n cursor: pointer;\n font-size: 18px;\n font-weight: 700;\n}\n.se-import-advanced p {\n margin-top: 10px;\n color: var(--se-muted);\n}\n.se-import-transfer {\n position: relative;\n display: grid;\n width: min(720px, 100%);\n min-height: 190px;\n grid-template-columns: 180px minmax(260px, 1fr) 180px;\n align-items: center;\n gap: 28px;\n margin: 0 auto 60px;\n}\n.se-import-transfer__source {\n position: relative;\n display: grid;\n width: 160px;\n height: 160px;\n place-items: center;\n animation: se-import-source-bounce 1.8s ease-in-out infinite;\n}\n.se-import-transfer__source::before {\n top: -13px;\n left: -13px;\n box-shadow: 155px 0 0 var(--se-primary);\n}\n.se-import-transfer__source::after {\n bottom: -13px;\n left: -13px;\n box-shadow: 155px 0 0 var(--se-primary);\n}\n.se-import-transfer__source img {\n width: 192px;\n height: 192px;\n object-fit: contain;\n}\n.se-import-transfer__source--lumia {\n animation-delay: 0.22s;\n}\n.se-import-transfer__files {\n position: relative;\n height: 130px;\n}\n.se-import-transfer__file {\n position: absolute;\n top: 50%;\n left: -18px;\n width: 56px;\n height: 66px;\n object-fit: contain;\n transform: translateY(-50%);\n filter: drop-shadow(0 10px 12px rgba(8, 9, 25, 0.25));\n animation: se-import-file-fly 2.4s cubic-bezier(0.35, 0, 0.25, 1) infinite;\n}\n.se-import-transfer__file--one {\n animation-delay: 0s;\n}\n.se-import-transfer__file--two {\n animation-delay: 0.38s;\n}\n.se-import-transfer__file--three {\n animation-delay: 0.76s;\n}\n.se-import-transfer__file--four {\n animation-delay: 1.14s;\n}\n@keyframes se-import-source-bounce {\n 0%, 100% {\n transform: translateY(0) scale(1);\n }\n 50% {\n transform: translateY(-8px) scale(1.025);\n }\n}\n@keyframes se-import-file-fly {\n 0% {\n opacity: 0;\n transform: translate(-10px, -50%) scale(0.82) rotate(-8deg);\n }\n 12% {\n opacity: 1;\n }\n 45% {\n transform: translate(145px, calc(-50% - 18px)) scale(1.08) rotate(5deg);\n }\n 82% {\n opacity: 1;\n }\n 100% {\n opacity: 0;\n transform: translate(330px, -50%) scale(0.9) rotate(10deg);\n }\n}\n.se-import-mirror-head {\n display: flex;\n align-items: end;\n justify-content: space-between;\n gap: 20px;\n}\n.se-import-mirror-head p {\n margin-top: 14px;\n color: var(--se-muted);\n font-size: 16px;\n}\n.se-import-mirror-head span {\n flex: 0 0 auto;\n color: var(--se-muted);\n font-weight: 700;\n}\n.se-import-progress {\n width: 100%;\n height: 14px;\n margin-top: 28px;\n overflow: hidden;\n border: 0;\n border-radius: 999px;\n background: rgba(255, 64, 118, 0.1);\n}\n.se-import-progress::-webkit-progress-bar {\n background: rgba(255, 64, 118, 0.1);\n}\n.se-import-progress::-webkit-progress-value {\n border-radius: 999px;\n background:\n linear-gradient(\n 90deg,\n #ff4076,\n #e7295f);\n}\n.se-import-progress::-moz-progress-bar {\n border-radius: 999px;\n background:\n linear-gradient(\n 90deg,\n #ff4076,\n #e7295f);\n}\n.se-import-asset-list {\n max-height: 360px;\n margin-top: 22px;\n overflow-y: auto;\n border-top: 1px solid rgba(255, 255, 255, 0.08);\n}\n.se-import-asset-row {\n display: grid;\n grid-template-columns: 32px minmax(0, 1fr) auto auto;\n align-items: center;\n gap: 14px;\n min-height: 42px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.08);\n color: var(--se-muted);\n font-size: 13px;\n}\n.se-import-asset-row__icon {\n width: 24px;\n height: 28px;\n justify-self: end;\n object-fit: contain;\n}\n.se-import-asset-row__url {\n overflow: hidden;\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.se-import-asset-row__state {\n color: #9b8cff;\n font-weight: 800;\n}\n.se-import-asset-row--done .se-import-asset-row__state,\n.se-import-asset-row--reused .se-import-asset-row__state {\n color: var(--se-green);\n}\n.se-import-asset-row--failed .se-import-asset-row__state,\n.se-import-asset-row__error {\n color: #ff8080;\n}\n.se-import-link-button {\n border: 0;\n background: transparent;\n color: #ff9ab6;\n cursor: pointer;\n font: inherit;\n font-weight: 800;\n}\n.se-import-overview {\n margin-top: 28px;\n border: 1px solid var(--se-border);\n border-radius: 12px;\n background: rgba(26, 30, 61, 0.62);\n padding: 28px;\n}\n.se-import-overview h3 {\n margin-bottom: 18px;\n font-size: 18px;\n font-weight: 800;\n}\n.se-import-row {\n display: grid;\n grid-template-columns: 190px minmax(0, 1fr) auto;\n gap: 18px;\n align-items: center;\n min-height: 48px;\n border-top: 1px solid rgba(255, 255, 255, 0.08);\n color: var(--se-muted);\n}\n.se-import-row strong {\n min-width: 0;\n overflow: hidden;\n color: var(--se-text);\n font-weight: 600;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.se-import-row em {\n color: var(--se-muted);\n font-style: normal;\n}\n.se-import-review-card {\n display: flex;\n justify-content: space-between;\n gap: 18px;\n border: 1px solid var(--se-border);\n border-radius: 10px;\n background: rgba(30, 34, 71, 0.8);\n padding: 20px;\n}\n.se-import-review-card__type {\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n font-weight: 800;\n}\n.se-import-review-card p {\n margin-top: 8px;\n color: var(--se-muted);\n line-height: 1.45;\n}\n.se-import-review-card__status {\n flex: 0 0 auto;\n color: #facc15;\n font-size: 11px;\n font-weight: 800;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n.se-import-review-bulk,\n.se-import-actions--inline {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: 10px;\n margin-top: 16px;\n}\n.se-import-muted {\n margin-top: 16px;\n color: var(--se-muted);\n font-size: 13px;\n}\n.se-import-generated {\n display: grid;\n gap: 12px;\n margin-top: 18px;\n}\n.se-import-generated__title {\n font-weight: 800;\n}\n.se-import-preview {\n width: 100%;\n height: 260px;\n border: 1px solid var(--se-border);\n border-radius: 8px;\n background: repeating-conic-gradient(#1a1c20 0% 25%, #1d1f24 0% 50%) 50% / 20px 20px;\n}\n.se-import-code {\n margin-top: 16px;\n}\n.se-import-code summary {\n cursor: pointer;\n color: var(--se-muted);\n font-size: 13px;\n}\n.se-import-code pre {\n max-height: 220px;\n margin: 10px 0 0;\n overflow: auto;\n border-radius: 8px;\n background: rgba(0, 0, 0, 0.28);\n padding: 12px;\n color: #d8dcff;\n font-size: 12px;\n}\n.se-import-error {\n margin-top: 18px;\n border: 1px solid rgba(239, 68, 68, 0.45);\n border-radius: 8px;\n background: rgba(220, 38, 38, 0.12);\n padding: 12px;\n color: #fca5a5;\n}\n.se-import-error__title {\n font-weight: 800;\n}\n.se-import-error__hint {\n margin-top: 6px;\n white-space: pre-wrap;\n}\n.se-import-actions {\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n margin-top: 8px;\n}\nfooter.se-import-actions:not(.se-import-actions--inline) {\n background: var(--se-bg, #17162a);\n border-top: 1px solid rgba(255, 255, 255, 0.06);\n margin: 0 -48px -24px;\n padding: 16px 48px;\n}\n@media (max-height: 760px) {\n footer.se-import-actions:not(.se-import-actions--inline) {\n margin: 0 -32px -16px;\n padding: 12px 32px;\n }\n}\n@media (max-width: 900px) {\n .se-import__shell {\n min-height: 0;\n padding: 24px 18px;\n }\n .se-import__content {\n min-height: 0;\n padding-top: 32px;\n }\n .se-import-stepper {\n grid-template-columns: repeat(auto-fit, minmax(70px, 1fr));\n }\n .se-import-step__label {\n font-size: 13px;\n }\n .se-import-grid--four,\n .se-import-grid--two,\n .se-import-transfer {\n grid-template-columns: 1fr;\n }\n .se-import-transfer {\n justify-items: center;\n }\n .se-import-transfer__files {\n width: min(360px, 100%);\n }\n .se-import-option,\n .se-import-row,\n .se-import-asset-row {\n grid-template-columns: 1fr;\n }\n .se-import-mirror-head,\n .se-import-review-card {\n align-items: flex-start;\n flex-direction: column;\n }\n}\n');
5391
+
5392
+ // src/assets/source_se.svg
5393
+ var source_se_default = 'data:image/svg+xml,<svg width="157" height="164" viewBox="0 0 157 164" fill="none" xmlns="http://www.w3.org/2000/svg">%0A<rect x="8.5" y="9.5" width="139" height="145" fill="%23242444" stroke="%23FF4076" stroke-width="3"/>%0A<path d="M87.1229 44.9224L89.0788 48.7235C102.041 53.3686 110.947 65.8756 110.947 79.7241C110.947 97.8858 96.1663 112.658 77.9929 112.658C59.8196 112.658 45.0457 97.8858 45.0457 79.7241C45.0457 65.8684 53.9519 53.3686 66.9143 48.7235L68.8702 44.9296C53.1725 49.0337 42 63.3222 42 79.7241C42 99.5592 58.1524 115.701 78.0001 115.701C97.8479 115.701 114 99.5592 114 79.7241C114 63.3222 102.828 49.0265 87.1229 44.9224Z" fill="url(%23paint0_linear_1424_495)"/>%0A<mask id="mask0_1424_495" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="47" y="48" width="62" height="63">%0A<path d="M77.9986 110.386C94.991 110.386 108.766 96.6196 108.766 79.638C108.766 62.6563 94.991 48.8899 77.9986 48.8899C61.0061 48.8899 47.231 62.6563 47.231 79.638C47.231 96.6196 61.0061 110.386 77.9986 110.386Z" fill="white"/>%0A</mask>%0A<g mask="url(%23mask0_1424_495)">%0A<path d="M108.825 78.9093C108.651 81.2463 105.036 90.9619 100.59 90.9619C98.7853 90.9619 98.3812 88.322 98.0853 86.3602C97.2841 89.4905 91.7629 96.2563 87.6706 96.2563C80.2295 96.2563 78.4974 80.5683 77.9921 78.4766C77.4869 80.561 75.7548 96.2563 68.3137 96.2563C64.2215 96.2563 58.7002 89.4977 57.8991 86.3602C57.6031 88.322 57.2062 90.9619 55.3947 90.9619C50.9487 90.9619 47.3328 81.2463 47.1596 78.9093C46.207 80.3158 46.2214 85.9995 47.636 89.6419C52.3561 101.795 64.1782 110.429 77.9849 110.429C91.7917 110.429 103.614 101.795 108.334 89.6419C109.749 85.9995 109.763 80.3231 108.81 78.9093H108.825Z" fill="url(%23paint1_linear_1424_495)"/>%0A<path d="M77.9988 96.2998C79.2258 98.5497 81.8961 101.918 86.4719 101.918C91.0478 101.918 94.6059 97.2014 95.7968 95.2105C95.4143 96.6097 94.642 99.6032 97.132 99.6032C102.841 99.6032 106.969 90.5798 106.969 90.5798C102.466 102.178 91.1777 110.422 77.9988 110.422C64.8199 110.422 53.532 102.178 49.0283 90.5798C49.0283 90.5798 53.1567 99.6032 58.8656 99.6032C61.3628 99.6032 60.5833 96.6021 60.2008 95.2105C61.3917 97.2014 64.8343 101.918 69.5257 101.918C74.1014 101.918 76.7646 98.5428 77.9988 96.2998Z" fill="url(%23paint2_linear_1424_495)"/>%0A<path d="M85.2523 106.419C82.4448 106.419 80.2579 105.546 77.9989 104.14C75.7399 105.539 73.553 106.419 70.7455 106.419C66.4656 106.419 65.1664 105.157 63.6147 104.003C63.658 105.474 65.1592 107.552 66.7109 108.301C70.1537 109.817 74.0221 110.422 77.9989 110.422C81.9756 110.422 85.8442 109.809 89.2868 108.301C90.8385 107.544 92.3326 105.474 92.3831 104.003C90.8313 105.157 89.5323 106.419 85.2523 106.419Z" fill="url(%23paint3_linear_1424_495)"/>%0A</g>%0A<path d="M73.8428 64.7073L69.4257 74.0622C69.2453 74.4446 69.1804 74.8773 69.2525 75.3029L70.6527 83.7562C70.696 84.0303 71.1002 84.0303 71.1435 83.7562L71.9013 79.1473C72.2116 77.2359 73.2292 75.5048 74.7522 74.3003L73.8428 64.7001V64.7073Z" fill="%23FF4800"/>%0A<path d="M82.1584 64.7073L86.5755 74.0622C86.7559 74.4446 86.8208 74.8773 86.7487 75.3029L85.3485 83.7634C85.3052 84.0376 84.9082 84.0376 84.8577 83.7634L84.0999 79.1473C83.7896 77.2359 82.7719 75.5048 81.249 74.3003L82.1584 64.7001V64.7073Z" fill="%23FF4800"/>%0A<path d="M84.5241 50.0291L78.4687 38.2867C78.2739 37.9044 77.7326 37.9044 77.5305 38.2867L71.4751 50.0219C71.2226 50.5052 71.1287 51.0606 71.2081 51.5943L74.261 73.0451L73.8063 75.0503L75.4158 79.4645C75.4519 79.5655 75.5457 79.6304 75.6468 79.6304H80.3308C80.4319 79.6304 80.5257 79.5655 80.5618 79.4645L82.1712 75.0503L81.7165 73.0451L84.7695 51.5943C84.8489 51.0534 84.7551 50.5052 84.5024 50.0219L84.5241 50.0291Z" fill="url(%23paint4_linear_1424_495)"/>%0A<path d="M77.7905 69.5107L75.6397 74.8265C75.3077 75.6488 75.2427 76.5576 75.4593 77.4159L77.7544 86.605C77.8193 86.8575 78.1802 86.8575 78.2379 86.605L80.5331 77.4159C80.7495 76.5576 80.6846 75.6488 80.3526 74.8265L78.2019 69.5107C78.1225 69.3232 77.8554 69.3232 77.7832 69.5107H77.7905Z" fill="%23FF4800"/>%0A<rect width="15" height="16" fill="%23FF4076"/>%0A<rect y="148" width="15" height="16" fill="%23FF4076"/>%0A<rect x="142" width="15" height="16" fill="%23FF4076"/>%0A<rect x="142" y="148" width="15" height="16" fill="%23FF4076"/>%0A<defs>%0A<linearGradient id="paint0_linear_1424_495" x1="78.0001" y1="24.4742" x2="78.0001" y2="138.609" gradientUnits="userSpaceOnUse">%0A<stop offset="0.3" stop-color="%2300ADFF"/>%0A<stop offset="1" stop-color="%231542FF"/>%0A</linearGradient>%0A<linearGradient id="paint1_linear_1424_495" x1="77.9993" y1="79.7965" x2="77.9993" y2="120.816" gradientUnits="userSpaceOnUse">%0A<stop stop-color="%23FCE3AD"/>%0A<stop offset="0.07" stop-color="%23FCE2A4"/>%0A<stop offset="0.19" stop-color="%23FCE08B"/>%0A<stop offset="0.33" stop-color="%23FCDD64"/>%0A<stop offset="0.48" stop-color="%23FCDA32"/>%0A<stop offset="0.66" stop-color="%23F9BC22"/>%0A<stop offset="1" stop-color="%23F57700"/>%0A</linearGradient>%0A<linearGradient id="paint2_linear_1424_495" x1="77.9988" y1="90.2913" x2="77.9988" y2="113.603" gradientUnits="userSpaceOnUse">%0A<stop stop-color="%23FCD619"/>%0A<stop offset="0.35" stop-color="%23F9B811"/>%0A<stop offset="1" stop-color="%23F57700"/>%0A</linearGradient>%0A<linearGradient id="paint3_linear_1424_495" x1="77.9989" y1="101.947" x2="77.9989" y2="112.694" gradientUnits="userSpaceOnUse">%0A<stop stop-color="%23F57700"/>%0A<stop offset="1" stop-color="%23FF4800"/>%0A</linearGradient>%0A<linearGradient id="paint4_linear_1424_495" x1="77.9996" y1="38.0054" x2="77.9996" y2="92.2167" gradientUnits="userSpaceOnUse">%0A<stop stop-color="%23E4E4E4"/>%0A<stop offset="0.88" stop-color="%23B6B8B8"/>%0A</linearGradient>%0A</defs>%0A</svg>%0A';
5394
+
5395
+ // src/assets/source_lumia.svg
5396
+ var source_lumia_default = 'data:image/svg+xml,<svg width="157" height="164" viewBox="0 0 157 164" fill="none" xmlns="http://www.w3.org/2000/svg">%0A<rect x="8.5" y="9.5" width="139" height="145" fill="%23242444" stroke="%23FF4076" stroke-width="3"/>%0A<path d="M81.877 128.851C81.877 131.843 79.5456 132.572 76.6689 132.572C73.7923 132.572 71.4609 131.843 71.4609 128.851H81.877Z" fill="%23FF4076"/>%0A<path d="M77.3916 32C84.8793 32 92.6433 34.2919 98.5977 39.2305C104.635 44.2381 108.604 51.8052 108.604 61.8105C108.603 73.1268 103.496 81.0627 98.6982 88.5176C98.5049 88.8177 98.3117 89.1179 98.1201 89.417C93.1275 97.1931 88.4496 104.881 88.0957 116.606C88.0422 118.373 86.5954 119.776 84.8281 119.776H73.832L74.0488 120.9H82.7236C84.5288 120.901 85.9921 122.364 85.9922 124.169C85.9922 125.975 84.5288 127.438 82.7236 127.438H71.3496C69.7828 127.438 68.4363 126.326 68.1396 124.788L66.6631 117.126C66.4786 116.168 66.7307 115.178 67.3516 114.426C67.9726 113.674 68.8977 113.238 69.873 113.238H81.7549C82.8573 101.346 87.9524 93.1519 92.6182 85.8848C92.7267 85.7157 92.8346 85.5466 92.9424 85.3789C97.9058 77.6522 102.065 71.1771 102.065 61.8105C102.065 53.7021 98.9254 47.9983 94.4229 44.2637C89.8371 40.46 83.6294 38.5381 77.3916 38.5381C65.8229 38.5382 53.5382 46.8207 53.5381 62.1387C53.5381 70.5737 57.3337 78.2378 61.7773 85.8975C62.3932 86.9591 63.0286 88.0311 63.665 89.1064C65.2574 91.7971 66.8614 94.5084 68.2139 97.1221C68.5532 97.7775 68.8826 98.4382 69.1982 99.1035C69.7167 98.1764 70.2716 97.2596 70.8477 96.3564C73.0295 92.9368 75.7271 89.4027 78.3623 85.9502C78.789 85.3914 79.2138 84.8343 79.6348 84.2803C82.7122 80.2309 85.6068 76.2986 87.7383 72.4648C89.8803 68.6123 91.0742 65.165 91.0742 62.084C91.0742 58.1367 89.6207 55.1129 87.3291 53.0479C85.0029 50.9516 81.5869 49.6475 77.3916 49.6475C70.0529 49.6475 64.5294 55.5289 64.5293 62.084C64.5293 64.4095 65.0263 68.0042 66.2227 70.8975C67.4926 73.9687 68.9334 74.959 70.2285 74.959C71.187 74.9589 71.7005 74.6624 72.085 74.2744C72.5587 73.7963 73.0139 72.9773 73.3662 71.7422C74.0688 69.2783 74.0688 66.3016 74.0684 64.2012V64.0537C74.0686 62.2485 75.5317 60.7852 77.3369 60.7852C79.1422 60.7852 80.6062 62.2485 80.6064 64.0537V65.2021C80.5968 67.3108 80.4956 70.5813 79.6533 73.5352C79.1375 75.3439 78.2704 77.3208 76.7295 78.876C75.0993 80.5211 72.9061 81.497 70.2285 81.4971C64.7429 81.4971 61.6996 77.0691 60.1807 73.3955C58.5882 69.5443 57.9912 65.0667 57.9912 62.084C57.9913 51.8935 66.4665 43.1094 77.3916 43.1094C82.9298 43.1094 87.9897 44.8418 91.7061 48.1904C95.4567 51.5704 97.6123 56.3999 97.6123 62.084C97.6123 66.7738 95.8263 71.3721 93.4521 75.6426C91.0671 79.9317 87.9082 84.2005 84.8408 88.2363C84.4031 88.8123 83.9677 89.3835 83.5361 89.9492C80.8874 93.4208 78.3814 96.705 76.3604 99.873C73.9874 103.593 72.5611 106.741 72.2754 109.389C72.0981 111.031 70.7238 112.283 69.0723 112.307C67.4206 112.33 66.0104 111.118 65.7861 109.481C65.3962 106.635 64.1894 103.57 62.4072 100.126C61.1449 97.6868 59.6908 95.2302 58.1406 92.6113C57.4828 91.4997 56.8073 90.3588 56.1221 89.1777C51.6525 81.4736 47 72.474 47 62.1387C47.0001 42.4332 63.0409 32.0001 77.3916 32Z" fill="%23FF4076"/>%0A<path d="M71.6338 65.5146C72.1863 65.262 72.839 65.5052 73.0918 66.0576C73.3445 66.6102 73.1013 67.2627 72.5488 67.5156L68.7217 69.2666C68.1692 69.5191 67.5155 69.2768 67.2627 68.7246C67.0099 68.1719 67.2531 67.5186 67.8057 67.2656L71.6338 65.5146Z" fill="%23FF4076"/>%0A<path d="M82.208 65.5684C82.4316 65.0036 83.0708 64.7267 83.6357 64.9502L87.5498 66.5C88.1146 66.7236 88.3914 67.3628 88.168 67.9277C87.9445 68.4926 87.3052 68.7691 86.7402 68.5459L82.8262 66.9961C82.2611 66.7726 81.9845 66.1333 82.208 65.5684Z" fill="%23FF4076"/>%0A<path d="M66.9834 60.3623C67.1772 59.7865 67.8011 59.4771 68.377 59.6709L72.3662 61.0127C72.942 61.2065 73.2522 61.8304 73.0586 62.4062C72.8649 62.982 72.2408 63.2921 71.665 63.0986L67.6758 61.7559C67.0999 61.5621 66.7896 60.9382 66.9834 60.3623Z" fill="%23FF4076"/>%0A<path d="M86.4482 58.8301C86.9968 58.5686 87.6533 58.8014 87.915 59.3496C88.1767 59.8979 87.9437 60.5547 87.3955 60.8164L83.5977 62.6299C83.049 62.8917 82.3923 62.6587 82.1309 62.1104C81.8692 61.562 82.1012 60.9053 82.6494 60.6436L86.4482 58.8301Z" fill="%23FF4076"/>%0A<path d="M73.084 53.8389C73.6539 53.6282 74.2874 53.9193 74.498 54.4893L75.958 58.4395C76.1686 59.0093 75.8775 59.6428 75.3076 59.8535C74.7377 60.0642 74.1042 59.7721 73.8936 59.2021L72.4336 55.252C72.2231 54.6822 72.5143 54.0496 73.084 53.8389Z" fill="%23FF4076"/>%0A<path d="M80.2275 54.2715C80.4366 53.701 81.0691 53.4072 81.6396 53.6162C82.2102 53.8253 82.5031 54.4578 82.2939 55.0283L80.8447 58.9824C80.6357 59.5529 80.0041 59.8458 79.4336 59.6367C78.8631 59.4277 78.5702 58.7961 78.7793 58.2256L80.2275 54.2715Z" fill="%23FF4076"/>%0A<rect width="15" height="16" fill="%23FF4076"/>%0A<rect y="148" width="15" height="16" fill="%23FF4076"/>%0A<rect x="142" width="15" height="16" fill="%23FF4076"/>%0A<rect x="142" y="148" width="15" height="16" fill="%23FF4076"/>%0A</svg>%0A';
5397
+
5398
+ // src/assets/audio_file.svg
5399
+ var audio_file_default = 'data:image/svg+xml,<svg width="23" height="29" viewBox="0 0 23 29" fill="none" xmlns="http://www.w3.org/2000/svg">%0A<path fill-rule="evenodd" clip-rule="evenodd" d="M22.7675 8.5178V25.5534C22.7675 26.3064 22.4678 27.0287 21.9341 27.5611C21.4004 28.0936 20.6765 28.3925 19.9217 28.3926H2.84579C2.09106 28.3925 1.36706 28.0936 0.833383 27.5611C0.299704 27.0287 0 26.3064 0 25.5534V2.83914C3.46516e-05 2.08616 0.299694 1.36386 0.833383 0.831425C1.36706 0.298996 2.09106 4.12048e-05 2.84579 0H14.2297L22.7675 8.5178ZM15.5614 14.0434C15.4739 14.0241 15.383 14.0247 15.2956 14.045L9.28308 15.4475C9.1509 15.4784 9.03315 15.5534 8.94878 15.6597C8.86444 15.766 8.81842 15.8978 8.81843 16.0335V21.3474C8.45759 21.2198 8.06549 21.2104 7.6989 21.3203C7.33226 21.4304 7.01003 21.6543 6.77901 21.9596C6.54799 22.2648 6.42011 22.6357 6.4138 23.0184C6.4075 23.4012 6.52298 23.776 6.74378 24.0886C6.96461 24.4013 7.27932 24.6355 7.64215 24.7576C8.00495 24.8797 8.39716 24.8834 8.76206 24.7678C9.12698 24.6522 9.44546 24.4232 9.67178 24.1145C9.8981 23.8057 10.0205 23.433 10.0209 23.0501V18.9157L14.831 17.7934V20.1445C14.47 20.0168 14.0778 20.0074 13.7111 20.1174C13.3444 20.2275 13.0222 20.4514 12.7912 20.7567C12.5603 21.0619 12.4326 21.4328 12.4264 21.8155C12.4201 22.1983 12.5358 22.5734 12.7567 22.8861C12.9777 23.1987 13.2923 23.4328 13.6551 23.5547C14.018 23.6766 14.4105 23.6799 14.7754 23.5641C15.1403 23.4483 15.459 23.2193 15.6851 22.9104C15.9112 22.6015 16.0333 22.2288 16.0335 21.8461C16.0335 21.8032 16.032 21.7605 16.0292 21.7184C16.032 21.694 16.0335 21.6697 16.0335 21.6456V14.6306C16.0335 14.5401 16.013 14.4506 15.9736 14.3691C15.9343 14.2876 15.8769 14.216 15.8061 14.1597L15.8022 14.1569C15.7315 14.1017 15.649 14.0628 15.5614 14.0434ZM12.8068 9.93757H20.633L12.8068 2.12945V9.93757Z" fill="white"/>%0A</svg>%0A';
5400
+
5401
+ // src/assets/img_file.svg
5402
+ var img_file_default = 'data:image/svg+xml,<svg width="23" height="29" viewBox="0 0 23 29" fill="none" xmlns="http://www.w3.org/2000/svg">%0A<path fill-rule="evenodd" clip-rule="evenodd" d="M11.7445 13.2276C13.0784 13.2276 14.0388 13.2283 14.7699 13.3267C15.4894 13.4235 15.9266 13.6082 16.2504 13.9322C16.5743 14.2565 16.7591 14.694 16.8559 15.4135C16.9544 16.1445 16.9554 17.1047 16.9554 18.4385V19.8415C16.9554 21.1754 16.9547 22.1358 16.8563 22.8669C16.7595 23.5864 16.5748 24.0236 16.2508 24.3474C15.9265 24.6713 15.489 24.8561 14.7695 24.9529C14.0385 25.0514 13.0784 25.0524 11.7445 25.0524H10.3415C9.00759 25.0524 8.04722 25.0517 7.31607 24.9533C6.59659 24.8565 6.1594 24.6718 5.83564 24.3478C5.51168 24.0235 5.32694 23.586 5.23007 22.8665C5.13166 22.1355 5.13065 21.1754 5.13065 19.8415V18.4385C5.13065 17.1046 5.13135 16.1442 5.22968 15.4131C5.32646 14.6936 5.51124 14.2564 5.83524 13.9326C6.15948 13.6087 6.597 13.4239 7.31646 13.3271C8.04751 13.2287 9.00765 13.2276 10.3415 13.2276H11.7445ZM8.28842 17.3961C7.82545 17.4347 7.44577 17.667 7.10626 17.9398C6.82149 18.1692 6.50307 18.4799 6.13274 18.8496V19.8415C6.13274 20.2034 6.13329 20.5335 6.1347 20.8314L6.91641 20.0497C7.40027 19.5659 7.72283 19.2453 7.98583 19.0328C8.24538 18.8237 8.35792 18.7979 8.40703 18.7944C8.53557 18.7835 8.66468 18.8079 8.78007 18.8656C8.82286 18.8874 8.91773 18.9527 9.08187 19.2445C9.2474 19.5391 9.42752 19.9572 9.69683 20.5856L9.73402 20.6729L9.74615 20.6995C9.85061 20.9435 9.94729 21.1693 10.044 21.3474C10.1458 21.5332 10.2932 21.7566 10.5416 21.9103C10.8341 22.0915 11.1831 22.1586 11.5221 22.0993C11.8097 22.0481 12.0292 21.8962 12.1927 21.7615C12.3484 21.6324 12.5226 21.4582 12.7106 21.2702L12.7313 21.2495C13.0294 20.9514 13.2231 20.7583 13.3823 20.6244C13.5351 20.4954 13.6068 20.4645 13.6461 20.4525C13.7783 20.4126 13.9194 20.4126 14.0516 20.4525C14.0909 20.4645 14.1626 20.4954 14.3155 20.6244C14.4747 20.7583 14.6682 20.9512 14.9668 21.2499L15.9141 22.1972C15.9464 21.6935 15.9519 21.0675 15.9533 20.2517L15.9392 20.238C15.6656 19.9645 15.4301 19.7285 15.2197 19.551C14.9973 19.3637 14.7552 19.1989 14.4564 19.1091C14.0602 18.9896 13.6375 18.9896 13.2413 19.1091C12.9425 19.1996 12.7011 19.3637 12.478 19.551C12.2676 19.7285 12.0321 19.9649 11.7593 20.2384L11.739 20.258C11.5237 20.4726 11.3971 20.5989 11.2982 20.6796L11.2841 20.6917L11.2759 20.6756C11.2149 20.5634 11.1434 20.3997 11.0234 20.1198L10.9862 20.0329L10.9706 19.9965C10.7209 19.4143 10.5113 18.9245 10.3044 18.5556C10.0904 18.1761 9.82392 17.819 9.40873 17.6114C9.06229 17.4379 8.67451 17.3634 8.28842 17.3961ZM14.1996 14.9312C13.6185 14.9312 13.1474 15.4023 13.1474 15.9834C13.1474 16.5645 13.6185 17.0356 14.1996 17.0356C14.7807 17.0356 15.2518 16.5645 15.2518 15.9834C15.2518 15.4023 14.7807 14.9312 14.1996 14.9312Z" fill="white"/>%0A<path fill-rule="evenodd" clip-rule="evenodd" d="M22.7675 8.5178V25.5534C22.7675 26.3064 22.4678 27.0287 21.9341 27.5611C21.4004 28.0936 20.6765 28.3925 19.9217 28.3926H2.84579C2.09106 28.3925 1.36706 28.0936 0.833383 27.5611C0.299704 27.0287 0 26.3064 0 25.5534V2.83914C3.46516e-05 2.08616 0.299694 1.36386 0.833383 0.831425C1.36706 0.298996 2.09106 4.12048e-05 2.84579 0H14.2297L22.7675 8.5178ZM10.3415 12.426C9.03021 12.426 8.00652 12.4252 7.2096 12.5324C6.40115 12.6413 5.76631 12.868 5.26843 13.3654C4.77051 13.863 4.54419 14.4978 4.43544 15.3062C4.32825 16.1032 4.32897 17.1273 4.32897 18.4385V19.8415C4.32897 21.1528 4.32816 22.1765 4.43544 22.9734C4.54428 23.7819 4.77099 24.4167 5.26843 24.9146C5.766 25.4125 6.40083 25.6388 7.20921 25.7476C8.00621 25.8548 9.03026 25.854 10.3415 25.854H11.7445C13.0558 25.854 14.0795 25.8549 14.8764 25.7476C15.6849 25.6387 16.3197 25.412 16.8176 24.9146C17.3155 24.417 17.5418 23.7822 17.6506 22.9738C17.7578 22.1768 17.757 21.1528 17.757 19.8415V18.4385C17.757 17.1272 17.7578 16.1035 17.6506 15.3066C17.5417 14.4982 17.315 13.8633 16.8176 13.3654C16.32 12.8675 15.6852 12.6412 14.8768 12.5324C14.0798 12.4253 13.0557 12.426 11.7445 12.426H10.3415ZM12.8068 9.93757H20.633L12.8068 2.12945V9.93757Z" fill="white"/>%0A</svg>%0A';
5403
+
5404
+ // src/assets/video_file.svg
5405
+ var video_file_default = 'data:image/svg+xml,<svg width="23" height="29" viewBox="0 0 23 29" fill="none" xmlns="http://www.w3.org/2000/svg">%0A<path fill-rule="evenodd" clip-rule="evenodd" d="M22.7675 8.5178V25.5534C22.7675 26.3064 22.4678 27.0287 21.9341 27.5611C21.4004 28.0936 20.6765 28.3925 19.9217 28.3926H2.84579C2.09106 28.3925 1.36706 28.0936 0.833383 27.5611C0.299704 27.0287 0 26.3064 0 25.5534V2.83914C3.46516e-05 2.08616 0.299694 1.36386 0.833383 0.831425C1.36706 0.298996 2.09106 4.12048e-05 2.84579 0H14.2297L22.7675 8.5178ZM7.89972 14.6224C7.44912 14.6224 7.01703 14.8013 6.69838 15.1199C6.37968 15.4386 6.20046 15.8709 6.20046 16.3216V20.9563C6.20046 21.407 6.37968 21.8393 6.69838 22.158C7.01703 22.4766 7.44912 22.6555 7.89972 22.6556H13.1525C13.6032 22.6556 14.0355 22.4767 14.3542 22.158C14.6729 21.8393 14.8517 21.407 14.8517 20.9563V19.9714L16.9648 21.8531C17.4622 22.296 18.2506 21.9425 18.2506 21.2757V15.7748C18.2506 15.1074 17.4622 14.7545 16.9648 15.1974L14.8517 17.0791V16.3216C14.8517 15.8709 14.6729 15.4386 14.3542 15.1199C14.0355 14.8012 13.6032 14.6224 13.1525 14.6224H7.89972ZM12.8068 9.93757H20.633L12.8068 2.12945V9.93757Z" fill="white"/>%0A</svg>%0A';
5406
+
5407
+ // src/assets/sound.svg
5408
+ var sound_default = 'data:image/svg+xml,<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">%0A<path d="M18.622 3.21699C18.7399 3.31063 18.8351 3.42968 18.9005 3.56526C18.966 3.70084 19 3.84944 19 3.99999V15.667C19 15.707 18.9977 15.7473 18.993 15.788C18.9977 15.858 19 15.9287 19 16C18.9997 16.6368 18.7967 17.2569 18.4206 17.7707C18.0444 18.2844 17.5145 18.6652 16.9076 18.8578C16.3007 19.0505 15.6482 19.045 15.0446 18.8422C14.441 18.6394 13.9176 18.2498 13.5501 17.7298C13.1826 17.2098 12.9901 16.5864 13.0004 15.9497C13.0107 15.313 13.2234 14.6961 13.6076 14.1883C13.9918 13.6805 14.5276 13.3082 15.1375 13.1251C15.7473 12.942 16.3997 12.9577 17 13.17V9.25999L9 11.127V18.003C8.99924 18.6397 8.79595 19.2596 8.41953 19.7731C8.04312 20.2866 7.5131 20.667 6.90616 20.8593C6.29923 21.0516 5.64684 21.0458 5.0434 20.8428C4.43996 20.6398 3.91676 20.2501 3.54948 19.73C3.1822 19.2099 2.98991 18.5865 3.00041 17.9499C3.01091 17.3133 3.22367 16.6966 3.60789 16.1889C3.99212 15.6813 4.5279 15.309 5.13771 15.126C5.74752 14.943 6.39973 14.9588 7 15.171V6.33299C6.99997 6.10722 7.07635 5.88808 7.2167 5.71124C7.35705 5.53439 7.55312 5.41024 7.773 5.35899L17.773 3.02599C17.9183 2.99221 18.0694 2.99139 18.2151 3.02357C18.3608 3.05576 18.4974 3.12013 18.615 3.21199L18.622 3.21699Z" fill="white"/>%0A</svg>%0A';
5409
+
5410
+ // src/assets/img.svg
5411
+ var img_default = 'data:image/svg+xml,<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">%0A<path fill-rule="evenodd" clip-rule="evenodd" d="M3.172 3.17199C2 4.34299 2 6.22899 2 9.99999V14C2 17.771 2 19.657 3.172 20.828C4.344 21.999 6.229 22 10 22H14C17.771 22 19.657 22 20.828 20.828C21.999 19.656 22 17.771 22 14V9.99999C22 7.15999 22 5.38999 21.5 4.18899V17C21.0246 17.0001 20.5538 16.9065 20.1146 16.7246C19.6753 16.5427 19.2762 16.2761 18.94 15.94L18.188 15.188C17.466 14.466 17.106 14.106 16.697 13.954C16.2474 13.7868 15.7526 13.7868 15.303 13.954C14.894 14.106 14.533 14.466 13.813 15.188L13.699 15.301C13.114 15.886 12.821 16.179 12.51 16.233C12.2685 16.2756 12.0197 16.2279 11.811 16.099C11.543 15.933 11.38 15.552 11.053 14.791L11 14.667C10.25 12.917 9.876 12.043 9.222 11.715C8.89266 11.5499 8.52411 11.4789 8.157 11.51C7.428 11.572 6.756 12.245 5.41 13.59L3.5 15.5V2.88699C3.384 2.97299 3.27467 3.06799 3.172 3.17199Z" fill="white"/>%0A<mask id="mask0_1424_377" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="2" y="2" width="20" height="20">%0A<path d="M2 10C2 6.229 2 4.343 3.172 3.172C4.344 2.001 6.229 2 10 2H14C17.771 2 19.657 2 20.828 3.172C21.999 4.344 22 6.229 22 10V14C22 17.771 22 19.657 20.828 20.828C19.656 21.999 17.771 22 14 22H10C6.229 22 4.343 22 3.172 20.828C2.001 19.656 2 17.771 2 14V10Z" fill="white"/>%0A</mask>%0A<g mask="url(%23mask0_1424_377)">%0A<path d="M2 10C2 6.229 2 4.343 3.172 3.172C4.344 2.001 6.229 2 10 2H14C17.771 2 19.657 2 20.828 3.172C21.999 4.344 22 6.229 22 10V14C22 17.771 22 19.657 20.828 20.828C19.656 21.999 17.771 22 14 22H10C6.229 22 4.343 22 3.172 20.828C2.001 19.656 2 17.771 2 14V10Z" stroke="white" stroke-width="4"/>%0A</g>%0A<path d="M15 11C16.1046 11 17 10.1046 17 9C17 7.89543 16.1046 7 15 7C13.8954 7 13 7.89543 13 9C13 10.1046 13.8954 11 15 11Z" fill="white"/>%0A</svg>%0A';
5412
+
5413
+ // src/assets/video.svg
5414
+ var video_default = 'data:image/svg+xml,<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">%0A<path d="M5 5.5C4.27065 5.5 3.57118 5.78973 3.05546 6.30546C2.53973 6.82118 2.25 7.52065 2.25 8.25V15.75C2.25 16.4793 2.53973 17.1788 3.05546 17.6945C3.57118 18.2103 4.27065 18.5 5 18.5H13.5C14.2293 18.5 14.9288 18.2103 15.4445 17.6945C15.9603 17.1788 16.25 16.4793 16.25 15.75V14.156L19.669 17.201C20.474 17.918 21.75 17.346 21.75 16.267V7.365C21.75 6.285 20.474 5.714 19.669 6.431L16.25 9.476V8.25C16.25 7.52065 15.9603 6.82118 15.4445 6.30546C14.9288 5.78973 14.2293 5.5 13.5 5.5H5Z" fill="white"/>%0A</svg>%0A';
4767
5415
 
4768
5416
  // src/se-import/ui/SEImportWizard.tsx
4769
- import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
4770
- var sourceSEIcon = new URL("../../assets/source_se.svg", import.meta.url).href;
4771
- var sourceLumiaIcon = new URL(
4772
- "../../assets/source_lumia.svg",
4773
- import.meta.url
4774
- ).href;
4775
- var audioFileIcon = new URL("../../assets/audio_file.svg", import.meta.url).href;
4776
- var imageFileIcon = new URL("../../assets/img_file.svg", import.meta.url).href;
4777
- var videoFileIcon = new URL("../../assets/video_file.svg", import.meta.url).href;
4778
- var audioIcon = new URL("../../assets/sound.svg", import.meta.url).href;
4779
- var imageIcon = new URL("../../assets/img.svg", import.meta.url).href;
4780
- var videoIcon = new URL("../../assets/video.svg", import.meta.url).href;
5417
+ import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
4781
5418
  var CORE_STEPS = [
4782
5419
  { key: "connect", label: "Connect" },
5420
+ { key: "pick", label: "Overlays" },
4783
5421
  { key: "url", label: "Load" },
4784
5422
  { key: "discovery", label: "Discovery" },
4785
5423
  { key: "options", label: "Options" },
@@ -4932,6 +5570,136 @@ function StepHeader({
4932
5570
  ] })
4933
5571
  ] });
4934
5572
  }
5573
+ var JWT_FEATURES = [
5574
+ "All your overlays \u2014 pick one, many, or all",
5575
+ "Live counters (follower / sub / tip / bits goals)",
5576
+ "Chat bot commands",
5577
+ "Loyalty points + watch time",
5578
+ "Recent activity replay",
5579
+ "Bits leaderboard"
5580
+ ];
5581
+ function ModeCard({
5582
+ primary,
5583
+ title,
5584
+ body,
5585
+ caveat,
5586
+ onClick
5587
+ }) {
5588
+ return /* @__PURE__ */ jsxs7(
5589
+ "button",
5590
+ {
5591
+ type: "button",
5592
+ onClick,
5593
+ style: {
5594
+ display: "flex",
5595
+ flexDirection: "column",
5596
+ alignItems: "flex-start",
5597
+ gap: 14,
5598
+ padding: 24,
5599
+ minHeight: 280,
5600
+ borderRadius: 14,
5601
+ border: primary ? "2px solid var(--se-primary)" : "1px solid var(--se-border)",
5602
+ background: primary ? "rgba(255, 64, 118, 0.08)" : "transparent",
5603
+ color: "var(--se-text)",
5604
+ textAlign: "left",
5605
+ cursor: "pointer",
5606
+ transition: "background 120ms, border-color 120ms, transform 120ms"
5607
+ },
5608
+ onMouseEnter: (e) => {
5609
+ e.currentTarget.style.background = primary ? "rgba(255, 64, 118, 0.14)" : "rgba(255, 255, 255, 0.04)";
5610
+ if (!primary)
5611
+ e.currentTarget.style.borderColor = "var(--se-border-strong)";
5612
+ e.currentTarget.style.transform = "translateY(-2px)";
5613
+ },
5614
+ onMouseLeave: (e) => {
5615
+ e.currentTarget.style.background = primary ? "rgba(255, 64, 118, 0.08)" : "transparent";
5616
+ if (!primary)
5617
+ e.currentTarget.style.borderColor = "var(--se-border)";
5618
+ e.currentTarget.style.transform = "translateY(0)";
5619
+ },
5620
+ children: [
5621
+ /* @__PURE__ */ jsx13(
5622
+ "div",
5623
+ {
5624
+ style: {
5625
+ fontSize: 18,
5626
+ fontWeight: 800,
5627
+ color: primary ? "var(--se-primary)" : "var(--se-text)"
5628
+ },
5629
+ children: title
5630
+ }
5631
+ ),
5632
+ /* @__PURE__ */ jsx13("div", { style: { fontSize: 13.5, lineHeight: 1.55, color: "var(--se-text)", width: "100%" }, children: body }),
5633
+ /* @__PURE__ */ jsx13("div", { style: { fontSize: 12, color: "var(--se-muted)", marginTop: "auto" }, children: caveat })
5634
+ ]
5635
+ }
5636
+ );
5637
+ }
5638
+ function StepModePicker({
5639
+ onChooseJwt,
5640
+ onChooseUrl
5641
+ }) {
5642
+ return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--wide"), children: [
5643
+ /* @__PURE__ */ jsx13(
5644
+ StepHeader,
5645
+ {
5646
+ number: 1,
5647
+ title: "What would you like to import?",
5648
+ subtitle: "Pick a starting point. You can change your mind from the Back button on the next step."
5649
+ }
5650
+ ),
5651
+ /* @__PURE__ */ jsxs7(
5652
+ "div",
5653
+ {
5654
+ style: {
5655
+ display: "grid",
5656
+ gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))",
5657
+ gap: 18,
5658
+ marginTop: 8
5659
+ },
5660
+ children: [
5661
+ /* @__PURE__ */ jsx13(
5662
+ ModeCard,
5663
+ {
5664
+ primary: true,
5665
+ title: "Import everything from StreamElements",
5666
+ body: /* @__PURE__ */ jsxs7(Fragment4, { children: [
5667
+ /* @__PURE__ */ jsx13("div", { style: { marginBottom: 10 }, children: "Connect once and bring over your entire SE setup:" }),
5668
+ /* @__PURE__ */ jsx13(
5669
+ "ul",
5670
+ {
5671
+ style: {
5672
+ margin: 0,
5673
+ paddingLeft: 18,
5674
+ display: "grid",
5675
+ gap: 6,
5676
+ fontSize: 13
5677
+ },
5678
+ children: JWT_FEATURES.map((feat) => /* @__PURE__ */ jsx13("li", { children: feat }, feat))
5679
+ }
5680
+ )
5681
+ ] }),
5682
+ caveat: "Requires pasting your StreamElements JWT. Stored only in memory \u2014 never sent to Lumia.",
5683
+ onClick: onChooseJwt
5684
+ }
5685
+ ),
5686
+ /* @__PURE__ */ jsx13(
5687
+ ModeCard,
5688
+ {
5689
+ title: "Import a single overlay",
5690
+ body: /* @__PURE__ */ jsxs7(Fragment4, { children: [
5691
+ "Paste a single overlay's public preview URL to bring just that overlay over.",
5692
+ /* @__PURE__ */ jsx13("div", { style: { marginTop: 10, color: "var(--se-muted)" }, children: "No login required \u2014 perfect for trying one overlay before deciding whether to migrate your whole account." })
5693
+ ] }),
5694
+ caveat: "The URL contains the access token; treat it like a password.",
5695
+ onClick: onChooseUrl
5696
+ }
5697
+ )
5698
+ ]
5699
+ }
5700
+ )
5701
+ ] });
5702
+ }
4935
5703
  function StepConnect({
4936
5704
  jwt,
4937
5705
  setJwt,
@@ -5057,6 +5825,140 @@ function StepConnect({
5057
5825
  error && /* @__PURE__ */ jsx13(ErrorPanel, { message: error.message, hint: error.hint })
5058
5826
  ] });
5059
5827
  }
5828
+ function StepPickOverlay({
5829
+ overlays,
5830
+ loading,
5831
+ error,
5832
+ selectedIds,
5833
+ toggleId,
5834
+ selectAll,
5835
+ clearAll,
5836
+ onRetry
5837
+ }) {
5838
+ const selectedCount = selectedIds.size;
5839
+ const total = overlays?.length ?? 0;
5840
+ const allSelected = total > 0 && selectedCount === total;
5841
+ const hasAnySelected = selectedCount > 0;
5842
+ return /* @__PURE__ */ jsxs7("div", { className: panelClass("se-import-panel--wide"), children: [
5843
+ /* @__PURE__ */ jsx13(
5844
+ StepHeader,
5845
+ {
5846
+ number: 2,
5847
+ title: "Pick the overlays to import",
5848
+ subtitle: `Select one or more StreamElements overlays from your account. We'll run each one through the discovery \u2192 review \u2192 import pipeline in sequence. ${total > 0 ? `${total} overlay${total === 1 ? "" : "s"} on this channel.` : ""}`
5849
+ }
5850
+ ),
5851
+ loading && /* @__PURE__ */ jsxs7("div", { className: "se-import-help se-import-help--info", children: [
5852
+ /* @__PURE__ */ jsx13("div", { className: "se-import-help__icon", children: "\u2026" }),
5853
+ /* @__PURE__ */ jsx13("div", { children: "Fetching your StreamElements overlays." })
5854
+ ] }),
5855
+ error && /* @__PURE__ */ jsxs7(Fragment4, { children: [
5856
+ /* @__PURE__ */ jsx13(ErrorPanel, { message: error.message, hint: error.hint }),
5857
+ /* @__PURE__ */ jsx13("div", { style: { marginTop: 12 }, children: /* @__PURE__ */ jsx13(LSButton, { type: "button", color: "secondary", onClick: onRetry, label: "Retry" }) })
5858
+ ] }),
5859
+ !loading && !error && overlays && overlays.length === 0 && /* @__PURE__ */ jsxs7("div", { className: "se-import-help se-import-help--info", children: [
5860
+ /* @__PURE__ */ jsx13("div", { className: "se-import-help__icon", children: "i" }),
5861
+ /* @__PURE__ */ jsxs7("div", { children: [
5862
+ /* @__PURE__ */ jsx13("div", { className: "se-import-help__title", children: "No overlays found" }),
5863
+ /* @__PURE__ */ jsx13("p", { children: "This StreamElements account doesn't have any overlays yet. Create one in the SE dashboard, or use the legacy preview URL flow from the Connect step." })
5864
+ ] })
5865
+ ] }),
5866
+ !loading && !error && overlays && overlays.length > 0 && /* @__PURE__ */ jsxs7(Fragment4, { children: [
5867
+ /* @__PURE__ */ jsxs7("div", { className: "se-import-actions se-import-actions--inline", style: { marginTop: 24, marginBottom: 16, justifyContent: "space-between", alignItems: "center", display: "flex" }, children: [
5868
+ /* @__PURE__ */ jsxs7("label", { style: { display: "inline-flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 14, fontWeight: 600 }, children: [
5869
+ /* @__PURE__ */ jsx13(
5870
+ "input",
5871
+ {
5872
+ type: "checkbox",
5873
+ checked: allSelected,
5874
+ ref: (el) => {
5875
+ if (el) el.indeterminate = !allSelected && hasAnySelected;
5876
+ },
5877
+ onChange: () => allSelected ? clearAll() : selectAll()
5878
+ }
5879
+ ),
5880
+ /* @__PURE__ */ jsxs7("span", { children: [
5881
+ allSelected ? "Deselect all" : "Select all",
5882
+ " (",
5883
+ selectedCount,
5884
+ "/",
5885
+ total,
5886
+ ")"
5887
+ ] })
5888
+ ] }),
5889
+ /* @__PURE__ */ jsx13("span", { style: { fontSize: 13, color: "var(--se-muted)" }, children: hasAnySelected ? `${selectedCount} overlay${selectedCount === 1 ? "" : "s"} queued. Each runs through the full import flow in sequence.` : "Click overlays below to select them." })
5890
+ ] }),
5891
+ /* @__PURE__ */ jsx13(
5892
+ "div",
5893
+ {
5894
+ className: "se-import-grid",
5895
+ style: {
5896
+ gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))",
5897
+ gap: 12,
5898
+ maxHeight: 480,
5899
+ overflowY: "auto",
5900
+ padding: 4
5901
+ },
5902
+ children: overlays.map((o) => {
5903
+ const selected = selectedIds.has(o._id);
5904
+ return /* @__PURE__ */ jsxs7(
5905
+ "label",
5906
+ {
5907
+ style: {
5908
+ position: "relative",
5909
+ display: "flex",
5910
+ flexDirection: "column",
5911
+ border: selected ? "2px solid var(--se-primary)" : "1px solid var(--se-border)",
5912
+ borderRadius: 10,
5913
+ padding: 10,
5914
+ background: selected ? "rgba(255, 64, 118, 0.08)" : "rgba(28, 32, 66, 0.45)",
5915
+ cursor: "pointer",
5916
+ transition: "border-color 120ms, background 120ms"
5917
+ },
5918
+ children: [
5919
+ /* @__PURE__ */ jsx13(
5920
+ "input",
5921
+ {
5922
+ type: "checkbox",
5923
+ checked: selected,
5924
+ onChange: () => toggleId(o._id),
5925
+ style: { position: "absolute", top: 10, right: 10, accentColor: "var(--se-primary)", zIndex: 1 }
5926
+ }
5927
+ ),
5928
+ o.preview ? /* @__PURE__ */ jsx13(
5929
+ "img",
5930
+ {
5931
+ src: o.preview,
5932
+ alt: "",
5933
+ style: {
5934
+ width: "100%",
5935
+ height: 110,
5936
+ objectFit: "cover",
5937
+ borderRadius: 6,
5938
+ background: "var(--se-panel-2)"
5939
+ },
5940
+ loading: "lazy"
5941
+ }
5942
+ ) : /* @__PURE__ */ jsx13("div", { style: { width: "100%", height: 110, borderRadius: 6, background: "var(--se-panel-2)" } }),
5943
+ /* @__PURE__ */ jsx13("div", { style: { marginTop: 8, fontSize: 14, fontWeight: 700, color: "var(--se-text)", paddingRight: 28 }, children: o.name ?? "Unnamed overlay" }),
5944
+ /* @__PURE__ */ jsxs7("div", { style: { marginTop: 4, fontSize: 11, color: "var(--se-muted)" }, children: [
5945
+ o.type ?? "regular",
5946
+ o.settings?.width && o.settings?.height ? ` \xB7 ${o.settings.width}\xD7${o.settings.height}` : ""
5947
+ ] }),
5948
+ o.updatedAt && /* @__PURE__ */ jsxs7("div", { style: { marginTop: 2, fontSize: 11, color: "var(--se-muted)" }, children: [
5949
+ "Updated ",
5950
+ new Date(o.updatedAt).toLocaleDateString()
5951
+ ] })
5952
+ ]
5953
+ },
5954
+ o._id
5955
+ );
5956
+ })
5957
+ }
5958
+ )
5959
+ ] })
5960
+ ] });
5961
+ }
5060
5962
  function StepURL({
5061
5963
  url,
5062
5964
  setUrl,
@@ -5077,7 +5979,7 @@ function StepURL({
5077
5979
  {
5078
5980
  label: "Overlay URL",
5079
5981
  value: url,
5080
- onChange: setUrl,
5982
+ onChange: (_, next) => setUrl(String(next ?? "")),
5081
5983
  placeholder: "https://streamelements.com/overlay/<id>/<token>",
5082
5984
  disabled: busy
5083
5985
  }
@@ -5208,19 +6110,19 @@ function mirrorLabel(state) {
5208
6110
  }
5209
6111
  function fileTypeIcon(url) {
5210
6112
  const ext = filenameFromURL(url).split(".").pop()?.toLowerCase();
5211
- if (ext === "mp3" || ext === "wav" || ext === "ogg") return audioIcon;
5212
- if (ext === "webm" || ext === "mp4" || ext === "mov") return videoIcon;
5213
- return imageIcon;
6113
+ if (ext === "mp3" || ext === "wav" || ext === "ogg") return sound_default;
6114
+ if (ext === "webm" || ext === "mp4" || ext === "mov") return video_default;
6115
+ return img_default;
5214
6116
  }
5215
6117
  function AssetTransferAnimation() {
5216
6118
  return /* @__PURE__ */ jsxs7("div", { className: "se-import-transfer", "aria-hidden": "true", children: [
5217
- /* @__PURE__ */ jsx13("div", { className: "se-import-transfer__source se-import-transfer__source--se", children: /* @__PURE__ */ jsx13("img", { src: sourceSEIcon, alt: "" }) }),
6119
+ /* @__PURE__ */ jsx13("div", { className: "se-import-transfer__source se-import-transfer__source--se", children: /* @__PURE__ */ jsx13("img", { src: source_se_default, alt: "" }) }),
5218
6120
  /* @__PURE__ */ jsxs7("div", { className: "se-import-transfer__files", children: [
5219
6121
  /* @__PURE__ */ jsx13(
5220
6122
  "img",
5221
6123
  {
5222
6124
  className: "se-import-transfer__file se-import-transfer__file--one",
5223
- src: imageFileIcon,
6125
+ src: img_file_default,
5224
6126
  alt: ""
5225
6127
  }
5226
6128
  ),
@@ -5228,7 +6130,7 @@ function AssetTransferAnimation() {
5228
6130
  "img",
5229
6131
  {
5230
6132
  className: "se-import-transfer__file se-import-transfer__file--two",
5231
- src: audioFileIcon,
6133
+ src: audio_file_default,
5232
6134
  alt: ""
5233
6135
  }
5234
6136
  ),
@@ -5236,7 +6138,7 @@ function AssetTransferAnimation() {
5236
6138
  "img",
5237
6139
  {
5238
6140
  className: "se-import-transfer__file se-import-transfer__file--three",
5239
- src: imageFileIcon,
6141
+ src: img_file_default,
5240
6142
  alt: ""
5241
6143
  }
5242
6144
  ),
@@ -5244,12 +6146,12 @@ function AssetTransferAnimation() {
5244
6146
  "img",
5245
6147
  {
5246
6148
  className: "se-import-transfer__file se-import-transfer__file--four",
5247
- src: videoFileIcon,
6149
+ src: video_file_default,
5248
6150
  alt: ""
5249
6151
  }
5250
6152
  )
5251
6153
  ] }),
5252
- /* @__PURE__ */ jsx13("div", { className: "se-import-transfer__source se-import-transfer__source--lumia", children: /* @__PURE__ */ jsx13("img", { src: sourceLumiaIcon, alt: "" }) })
6154
+ /* @__PURE__ */ jsx13("div", { className: "se-import-transfer__source se-import-transfer__source--lumia", children: /* @__PURE__ */ jsx13("img", { src: source_lumia_default, alt: "" }) })
5253
6155
  ] });
5254
6156
  }
5255
6157
  function StepMirror({
@@ -5400,7 +6302,7 @@ function StepReview({
5400
6302
  /* @__PURE__ */ jsx13("summary", { children: "Original StreamElements config" }),
5401
6303
  /* @__PURE__ */ jsx13("pre", { children: JSON.stringify(item.seWidget.variables ?? {}, null, 2) })
5402
6304
  ] }),
5403
- /* @__PURE__ */ jsx13("div", { className: "se-import-actions se-import-actions--inline", children: state === "generated" ? /* @__PURE__ */ jsxs7(Fragment3, { children: [
6305
+ /* @__PURE__ */ jsx13("div", { className: "se-import-actions se-import-actions--inline", children: state === "generated" ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
5404
6306
  /* @__PURE__ */ jsx13(
5405
6307
  LSButton,
5406
6308
  {
@@ -5428,7 +6330,7 @@ function StepReview({
5428
6330
  label: "Use this and continue"
5429
6331
  }
5430
6332
  )
5431
- ] }) : /* @__PURE__ */ jsxs7(Fragment3, { children: [
6333
+ ] }) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
5432
6334
  !item.flaggedOff && /* @__PURE__ */ jsx13(
5433
6335
  LSButton,
5434
6336
  {
@@ -5554,13 +6456,23 @@ function SEImportWizard({
5554
6456
  t
5555
6457
  } = bindings;
5556
6458
  const translate = (key, fallback) => t ? t(key) ?? fallback : fallback;
5557
- const [step, setStep] = useState6("connect");
6459
+ const [step, setStep] = useState6(initialUrl ? "url" : "mode");
6460
+ const [entryMode, setEntryMode] = useState6(
6461
+ initialUrl ? "url" : null
6462
+ );
5558
6463
  const [jwt, setJwt] = useState6("");
5559
6464
  const [seClient, setSeClient] = useState6(null);
5560
6465
  const [channels, setChannels] = useState6(null);
5561
6466
  const [selectedChannelId, setSelectedChannelId] = useState6(null);
5562
6467
  const [connectError, setConnectError] = useState6(null);
5563
6468
  const [connecting, setConnecting] = useState6(false);
6469
+ const [overlayList, setOverlayList] = useState6(null);
6470
+ const [overlaysLoading, setOverlaysLoading] = useState6(false);
6471
+ const [overlaysError, setOverlaysError] = useState6(null);
6472
+ const [selectedOverlayIds, setSelectedOverlayIds] = useState6(/* @__PURE__ */ new Set());
6473
+ const [overlayQueue, setOverlayQueue] = useState6([]);
6474
+ const [currentOverlayIndex, setCurrentOverlayIndex] = useState6(0);
6475
+ const [totalOverlaysToImport, setTotalOverlaysToImport] = useState6(0);
5564
6476
  const [url, setUrl] = useState6(initialUrl);
5565
6477
  const [loadError, setLoadError] = useState6(null);
5566
6478
  const [loading, setLoading] = useState6(false);
@@ -5630,6 +6542,73 @@ function SEImportWizard({
5630
6542
  setConnecting(false);
5631
6543
  }
5632
6544
  }, [bindings, jwt]);
6545
+ const loadOverlayList = useCallback3(async (client) => {
6546
+ setOverlaysError(null);
6547
+ setOverlaysLoading(true);
6548
+ try {
6549
+ const list = await fetchSEOverlays(client);
6550
+ setOverlayList(list);
6551
+ } catch (e) {
6552
+ if (e instanceof SEAuthError) {
6553
+ setOverlaysError({ message: e.message, hint: e.code === "unauthorized" ? "Generate a new JWT on streamelements.com/dashboard/account/channels." : void 0 });
6554
+ bindings.onAuthError?.(e);
6555
+ } else {
6556
+ setOverlaysError({ message: e instanceof Error ? e.message : "Could not load your StreamElements overlays." });
6557
+ }
6558
+ } finally {
6559
+ setOverlaysLoading(false);
6560
+ }
6561
+ }, [bindings]);
6562
+ const toggleOverlaySelection = useCallback3((id) => {
6563
+ setSelectedOverlayIds((prev) => {
6564
+ const next = new Set(prev);
6565
+ if (next.has(id)) next.delete(id);
6566
+ else next.add(id);
6567
+ return next;
6568
+ });
6569
+ }, []);
6570
+ const selectAllOverlays = useCallback3(() => {
6571
+ setSelectedOverlayIds(new Set((overlayList ?? []).map((o) => o._id)));
6572
+ }, [overlayList]);
6573
+ const clearAllOverlays = useCallback3(() => {
6574
+ setSelectedOverlayIds(/* @__PURE__ */ new Set());
6575
+ }, []);
6576
+ const loadBootstrapAndKickoff = useCallback3(async (client, overlayId) => {
6577
+ setLoadError(null);
6578
+ setLoading(true);
6579
+ try {
6580
+ const bootstrap = await fetchSEBootstrapForOverlay(client, overlayId);
6581
+ if (!isSEBootstrap(bootstrap)) {
6582
+ throw new Error("StreamElements returned a payload that doesn't look like an overlay bootstrap.");
6583
+ }
6584
+ const imported = importSEBootstrap(bootstrap);
6585
+ setResult(imported);
6586
+ setOptions((o) => ({ ...o, name: imported.overlay.name }));
6587
+ setOriginalReviewItems(imported.reviewItems);
6588
+ setRowStates(Object.fromEntries(imported.reviewItems.map((r) => [r.moduleId, { state: "pending" }])));
6589
+ setReviewIndex(0);
6590
+ setMirrorRows([]);
6591
+ mirrorStartedRef.current = false;
6592
+ setStep("discovery");
6593
+ } catch (e) {
6594
+ setLoadError({
6595
+ message: "Could not load the overlay.",
6596
+ hint: e?.message ?? String(e)
6597
+ });
6598
+ } finally {
6599
+ setLoading(false);
6600
+ }
6601
+ }, []);
6602
+ const handlePickConfirm = useCallback3(async () => {
6603
+ if (!seClient) return;
6604
+ const ids = Array.from(selectedOverlayIds);
6605
+ if (ids.length === 0) return;
6606
+ const [first, ...rest] = ids;
6607
+ setOverlayQueue(rest);
6608
+ setTotalOverlaysToImport(ids.length);
6609
+ setCurrentOverlayIndex(1);
6610
+ await loadBootstrapAndKickoff(seClient, first);
6611
+ }, [seClient, selectedOverlayIds, loadBootstrapAndKickoff]);
5633
6612
  const handleLoad = useCallback3(async () => {
5634
6613
  setLoadError(null);
5635
6614
  const parts = extractSEPreviewParts(url);
@@ -5765,6 +6744,11 @@ function SEImportWizard({
5765
6744
  void runMirror();
5766
6745
  }
5767
6746
  }, [step, runMirror]);
6747
+ useEffect6(() => {
6748
+ if (step === "pick" && seClient && overlayList === null && !overlaysLoading) {
6749
+ void loadOverlayList(seClient);
6750
+ }
6751
+ }, [step, seClient, overlayList, overlaysLoading, loadOverlayList]);
5768
6752
  const advanceReview = useCallback3(() => {
5769
6753
  if (reviewIndex + 1 < originalReviewItems.length)
5770
6754
  setReviewIndex(reviewIndex + 1);
@@ -5898,7 +6882,23 @@ function SEImportWizard({
5898
6882
  setStep("confirm");
5899
6883
  };
5900
6884
  const handleFinalImport = useCallback3(async () => {
5901
- if (!result) return;
6885
+ if (!result || !seClient) {
6886
+ if (!result) return;
6887
+ setImporting(true);
6888
+ try {
6889
+ const body = { name: options.name.trim() || result.overlay.name, description: result.overlay.description, settings: result.overlay.settings };
6890
+ const { uuid } = await saveOverlay(body);
6891
+ notify.success("Imported overlay from StreamElements.");
6892
+ mirrorAbortRef.current?.abort();
6893
+ onClose();
6894
+ if (uuid) onComplete({ overlayId: uuid });
6895
+ } catch (error) {
6896
+ notify.error(`Import failed: ${extractErrorMessage(error)}`);
6897
+ } finally {
6898
+ setImporting(false);
6899
+ }
6900
+ return;
6901
+ }
5902
6902
  setImporting(true);
5903
6903
  try {
5904
6904
  const body = {
@@ -5907,7 +6907,19 @@ function SEImportWizard({
5907
6907
  settings: result.overlay.settings
5908
6908
  };
5909
6909
  const { uuid } = await saveOverlay(body);
5910
- notify.success("Imported overlay from StreamElements.");
6910
+ const hasMore = overlayQueue.length > 0;
6911
+ if (hasMore) {
6912
+ notify.success(`Imported ${currentOverlayIndex} of ${totalOverlaysToImport} overlays from StreamElements.`);
6913
+ const [nextId, ...rest] = overlayQueue;
6914
+ setOverlayQueue(rest);
6915
+ setCurrentOverlayIndex(currentOverlayIndex + 1);
6916
+ setImporting(false);
6917
+ await loadBootstrapAndKickoff(seClient, nextId);
6918
+ return;
6919
+ }
6920
+ notify.success(
6921
+ totalOverlaysToImport > 1 ? `Imported all ${totalOverlaysToImport} overlays from StreamElements.` : "Imported overlay from StreamElements."
6922
+ );
5911
6923
  mirrorAbortRef.current?.abort();
5912
6924
  onClose();
5913
6925
  if (uuid) onComplete({ overlayId: uuid });
@@ -5916,12 +6928,32 @@ function SEImportWizard({
5916
6928
  } finally {
5917
6929
  setImporting(false);
5918
6930
  }
5919
- }, [notify, onClose, onComplete, options.name, result, saveOverlay]);
6931
+ }, [
6932
+ notify,
6933
+ onClose,
6934
+ onComplete,
6935
+ options.name,
6936
+ result,
6937
+ saveOverlay,
6938
+ seClient,
6939
+ overlayQueue,
6940
+ currentOverlayIndex,
6941
+ totalOverlaysToImport,
6942
+ loadBootstrapAndKickoff
6943
+ ]);
5920
6944
  const visibleSteps = useMemo5(() => {
5921
6945
  const showMirrorStep = options.mirrorAssets && (assetUrls.length > 0 || mirrorRows.length > 0 || step === "mirror");
5922
- const steps = CORE_STEPS.filter(
5923
- (item) => item.key !== "mirror" || showMirrorStep
5924
- );
6946
+ if (step === "mode") return [];
6947
+ const onUrlFlow = entryMode === "url" || step === "url";
6948
+ const onJwtFlow = !onUrlFlow;
6949
+ const steps = CORE_STEPS.filter((item) => {
6950
+ if (item.key === "mode") return false;
6951
+ if (item.key === "mirror" && !showMirrorStep) return false;
6952
+ if (item.key === "connect" && !onJwtFlow) return false;
6953
+ if (item.key === "pick" && !onJwtFlow) return false;
6954
+ if (item.key === "url" && !onUrlFlow) return false;
6955
+ return true;
6956
+ });
5925
6957
  if (originalReviewItems.length > 0)
5926
6958
  steps.splice(Math.max(steps.length - 1, 0), 0, {
5927
6959
  key: "review",
@@ -5930,9 +6962,11 @@ function SEImportWizard({
5930
6962
  return steps;
5931
6963
  }, [
5932
6964
  assetUrls.length,
6965
+ entryMode,
5933
6966
  mirrorRows.length,
5934
6967
  options.mirrorAssets,
5935
6968
  originalReviewItems.length,
6969
+ seClient,
5936
6970
  step
5937
6971
  ]);
5938
6972
  const goBack = () => {
@@ -5941,14 +6975,22 @@ function SEImportWizard({
5941
6975
  setReviewIndex(reviewIndex - 1);
5942
6976
  return;
5943
6977
  }
6978
+ if (currentIndex === 0 && (step === "connect" || step === "url")) {
6979
+ setStep("mode");
6980
+ setEntryMode(null);
6981
+ return;
6982
+ }
5944
6983
  if (currentIndex > 0) setStep(visibleSteps[currentIndex - 1].key);
5945
6984
  };
5946
6985
  const goNext = () => {
5947
6986
  if (step === "connect") {
5948
6987
  if (!seClient) return void handleConnect();
5949
- setStep("url");
6988
+ setStep("pick");
5950
6989
  return;
5951
6990
  }
6991
+ if (step === "pick") {
6992
+ return void handlePickConfirm();
6993
+ }
5952
6994
  if (step === "url") return void handleLoad();
5953
6995
  if (step === "review") {
5954
6996
  if (!currentItem) return;
@@ -5971,6 +7013,11 @@ function SEImportWizard({
5971
7013
  const nextLabel = (() => {
5972
7014
  if (step === "connect")
5973
7015
  return connecting ? "Connecting..." : seClient ? "Continue" : "Connect";
7016
+ if (step === "pick") {
7017
+ if (loading) return "Loading overlay...";
7018
+ const n = selectedOverlayIds.size;
7019
+ return n > 1 ? `Import ${n} overlays` : "Import overlay";
7020
+ }
5974
7021
  if (step === "url") return loading ? "Loading..." : "Load overlay";
5975
7022
  if (step === "mirror") return mirrorRunning ? "Working..." : "Continue";
5976
7023
  if (step === "review")
@@ -5982,6 +7029,8 @@ function SEImportWizard({
5982
7029
  const nextDisabled = (() => {
5983
7030
  if (step === "connect")
5984
7031
  return connecting || !seClient && !jwt.trim();
7032
+ if (step === "pick")
7033
+ return loading || overlaysLoading || selectedOverlayIds.size === 0;
5985
7034
  if (step === "url") return loading || !url.trim();
5986
7035
  if (step === "mirror") return mirrorRunning;
5987
7036
  if (step === "review")
@@ -5990,8 +7039,34 @@ function SEImportWizard({
5990
7039
  return false;
5991
7040
  })();
5992
7041
  const activeIndex = visibleSteps.findIndex((item) => item.key === step);
7042
+ const batchActive = totalOverlaysToImport > 1 && step !== "mode" && step !== "pick" && step !== "connect" && step !== "url";
7043
+ const currentOverlayName = overlayList?.find((o) => o._id === Array.from(selectedOverlayIds).filter((id) => !overlayQueue.includes(id))[currentOverlayIndex - 1])?.name;
5993
7044
  return /* @__PURE__ */ jsx13("div", { className: "se-import", children: /* @__PURE__ */ jsxs7("div", { className: "se-import__shell", children: [
5994
- /* @__PURE__ */ jsx13("header", { className: "se-import__header", children: /* @__PURE__ */ jsx13("div", { className: "se-import-stepper", children: visibleSteps.map((item, index) => /* @__PURE__ */ jsxs7(
7045
+ batchActive && /* @__PURE__ */ jsxs7(
7046
+ "div",
7047
+ {
7048
+ style: {
7049
+ marginBottom: 16,
7050
+ padding: "10px 16px",
7051
+ border: "1px solid var(--se-border-strong)",
7052
+ borderRadius: 8,
7053
+ background: "rgba(255, 64, 118, 0.08)",
7054
+ color: "var(--se-text)",
7055
+ fontSize: 13,
7056
+ fontWeight: 600,
7057
+ textAlign: "center"
7058
+ },
7059
+ children: [
7060
+ "Importing overlay ",
7061
+ currentOverlayIndex,
7062
+ " of ",
7063
+ totalOverlaysToImport,
7064
+ currentOverlayName ? ` \xB7 ${currentOverlayName}` : "",
7065
+ overlayQueue.length > 0 ? ` \xB7 ${overlayQueue.length} more queued` : ""
7066
+ ]
7067
+ }
7068
+ ),
7069
+ visibleSteps.length > 0 && /* @__PURE__ */ jsx13("header", { className: "se-import__header", children: /* @__PURE__ */ jsx13("div", { className: "se-import-stepper", children: visibleSteps.map((item, index) => /* @__PURE__ */ jsxs7(
5995
7070
  "div",
5996
7071
  {
5997
7072
  className: stepClass(
@@ -6007,6 +7082,19 @@ function SEImportWizard({
6007
7082
  item.key
6008
7083
  )) }) }),
6009
7084
  /* @__PURE__ */ jsxs7("main", { className: "se-import__content", children: [
7085
+ step === "mode" && /* @__PURE__ */ jsx13(
7086
+ StepModePicker,
7087
+ {
7088
+ onChooseJwt: () => {
7089
+ setEntryMode("jwt");
7090
+ setStep("connect");
7091
+ },
7092
+ onChooseUrl: () => {
7093
+ setEntryMode("url");
7094
+ setStep("url");
7095
+ }
7096
+ }
7097
+ ),
6010
7098
  step === "connect" && /* @__PURE__ */ jsx13(
6011
7099
  StepConnect,
6012
7100
  {
@@ -6017,7 +7105,23 @@ function SEImportWizard({
6017
7105
  channels,
6018
7106
  selectedChannelId,
6019
7107
  setSelectedChannelId,
6020
- onUseUrl: () => setStep("url")
7108
+ onUseUrl: () => {
7109
+ setEntryMode("url");
7110
+ setStep("url");
7111
+ }
7112
+ }
7113
+ ),
7114
+ step === "pick" && /* @__PURE__ */ jsx13(
7115
+ StepPickOverlay,
7116
+ {
7117
+ overlays: overlayList,
7118
+ loading: overlaysLoading,
7119
+ error: overlaysError,
7120
+ selectedIds: selectedOverlayIds,
7121
+ toggleId: toggleOverlaySelection,
7122
+ selectAll: selectAllOverlays,
7123
+ clearAll: clearAllOverlays,
7124
+ onRetry: () => seClient && void loadOverlayList(seClient)
6021
7125
  }
6022
7126
  ),
6023
7127
  step === "url" && /* @__PURE__ */ jsx13(
@@ -6106,10 +7210,10 @@ function SEImportWizard({
6106
7210
  onClose();
6107
7211
  },
6108
7212
  disabled: loading || importing,
6109
- label: translate("action.cancel", "Cancel")
7213
+ label: translate("common.cancel", "Cancel")
6110
7214
  }
6111
7215
  ),
6112
- activeIndex > 0 && /* @__PURE__ */ jsx13(
7216
+ step !== "mode" && /* @__PURE__ */ jsx13(
6113
7217
  LSButton,
6114
7218
  {
6115
7219
  type: "button",
@@ -6119,7 +7223,7 @@ function SEImportWizard({
6119
7223
  label: "Back"
6120
7224
  }
6121
7225
  ),
6122
- /* @__PURE__ */ jsx13(
7226
+ step !== "mode" && /* @__PURE__ */ jsx13(
6123
7227
  LSButton,
6124
7228
  {
6125
7229
  type: "button",
@@ -6141,7 +7245,10 @@ function extractErrorMessage(err) {
6141
7245
  // src/se-import/index.ts
6142
7246
  var REVIEW_REASONS = {
6143
7247
  "se-widget-bit-boss": "No native Lumia equivalent \u2014 bit boss fight bar with HP/damage states.",
6144
- "se-widget-hype-cup": "No native Lumia equivalent \u2014 Box2D physics stacking animation.",
7248
+ // `se-widget-hype-cup` intentionally omitted it now routes to the native
7249
+ // `tipjar` module (see dispatcher.ts) and lands with `partial` status, so it
7250
+ // never reaches the placeholder-reason fallback. If a future feature flag
7251
+ // disables tipjar auto-import, add an entry to FLAG_OFF_REASONS instead.
6145
7252
  "se-widget-train": "No native Lumia equivalent \u2014 animated subscriber/event train.",
6146
7253
  "se-widget-media-share": "No native Lumia equivalent \u2014 viewer-submitted media player.",
6147
7254
  "se-widget-contest": "No native Lumia equivalent \u2014 SE-specific contest widget.",
@@ -6150,13 +7257,17 @@ var REVIEW_REASONS = {
6150
7257
  "se-widget-top-cheerers-list": 'Lumia loyaltyleaderboard needs a "source" extension before it can render cheer rankings.',
6151
7258
  "se-widget-top-gifters-list": 'Lumia loyaltyleaderboard needs a "source" extension before it can render gifter rankings.',
6152
7259
  "se-widget-top-tippers-list": 'Lumia loyaltyleaderboard needs a "source" extension before it can render tipper rankings.',
6153
- "se-widget-snow": "Seasonal effect \u2014 needs a particle template in Lumia.",
6154
- "se-widget-halloween": "Seasonal effect \u2014 needs a Lumia template.",
6155
- "se-widget-halloween-2019": "Seasonal effect \u2014 needs a Lumia template.",
6156
- "se-widget-xmas": "Seasonal effect \u2014 needs a Lumia template.",
6157
- "se-widget-valentine": "Seasonal effect \u2014 needs a Lumia template.",
6158
- "se-widget-easter": "Seasonal effect \u2014 needs a Lumia template.",
6159
- "se-widget-world-cup": "Seasonal effect \u2014 needs a Lumia template."
7260
+ // Merch products rotator imports as an empty slideshow with the title
7261
+ // "Merch products (add images manually)". Lumia's Fourthwall integration is
7262
+ // webhook-driven (events flow in) but doesn't fetch the user's shop catalog,
7263
+ // so we can't pre-populate the slideshow items. The streamer adds their
7264
+ // product images by hand in the slideshow editor. Status = 'partial' (see
7265
+ // dispatcher.ts), so this string surfaces in the review step.
7266
+ "se-widget-merch-products-rotator": "Imported as an empty slideshow. Lumia doesn't fetch your Fourthwall product catalog yet, so add your product images manually in the slideshow editor \u2014 the layer is pre-positioned and styled to match your SE widget."
7267
+ // Seasonal widgets (snow / halloween / halloween-2019 / xmas / valentine / easter
7268
+ // / world-cup) intentionally omitted — they route directly to image/slideshow/
7269
+ // video modules now (see dispatcher.ts SEASONAL_IMAGE_TYPES), so they always
7270
+ // produce a `direct` status and never need a fallback review reason.
6160
7271
  };
6161
7272
  function reasonFor(seType, status, flaggedOff) {
6162
7273
  if (flaggedOff && FLAG_OFF_REASONS[seType]) return FLAG_OFF_REASONS[seType];