@lumiastream/ui 0.5.3 → 0.5.4

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
@@ -21,7 +21,7 @@ function styleInject(css, { insertAt } = {}) {
21
21
  }
22
22
 
23
23
  // src/lumia-ui.css
24
- styleInject(".ls-color-picker,\n.ls-color-picker *,\n.mui-multi-select,\n.mui-multi-select *,\n.ls-multi-select-value,\n.ls-multi-select-value *,\n.ls-variable-picker,\n.ls-variable-picker *,\n.ls-variable-token,\n.ls-variable-token *,\n.ls-font-picker,\n.ls-font-picker *,\n.mui-ls-input,\n.mui-ls-input * {\n box-sizing: border-box;\n}\n.ls-color-picker button,\n.mui-multi-select button {\n font: inherit;\n letter-spacing: inherit;\n color: inherit;\n margin: 0;\n}\n.ls-color-picker__preview {\n appearance: none;\n -webkit-appearance: none;\n background-color: transparent;\n background-image: none;\n cursor: pointer;\n text-transform: uppercase;\n user-select: none;\n}\n.mui-ls-button.Mui-disabled {\n color: var(--neutralLight2, var(--white2, #cac9d5));\n opacity: 0.45;\n background-color: var(--separator, #0a0821);\n cursor: not-allowed;\n}\n.mui-ls-button {\n border-radius: var(--radius, 1rem);\n padding: 0.375rem 1.5rem;\n text-transform: uppercase;\n}\n.mui-ls-button.MuiButton-sizeSmall {\n padding: 0.25rem 1rem;\n}\n.mui-ls-button.MuiButton-sizeLarge {\n padding: 0.625rem 2rem;\n}\n.mui-ls-button--extended {\n padding-inline: 3rem;\n}\n.mui-ls-button.MuiButton-containedPrimary,\n.mui-ls-button.Mui-disabled.MuiButton-containedPrimary {\n background: var(--primary, #ff4076);\n color: var(--alwayswhite, #fff) !important;\n}\n.mui-ls-button.MuiButton-containedSecondary {\n background-color: var(--secondary, #535395);\n color: var(--alwayswhite, #fff) !important;\n}\n.mui-ls-button.Mui-disabled.MuiButton-containedSecondary {\n background-color: var(--secondary, #535395);\n color: var(--neutralLight2, var(--white2, #cac9d5));\n opacity: 0.4;\n}\n.mui-ls-button.MuiButton-outlinedPrimary {\n color: var(--primary, #ff4076);\n border-color: var(--primary, #ff4076);\n}\n.mui-ls-button.MuiButton-outlinedPrimary:hover {\n border-color: var(--primary, #ff4076);\n background-color: color-mix(in srgb, var(--primary, #ff4076) 12%, transparent);\n}\n.mui-ls-button.MuiButton-outlinedSecondary {\n color: var(--neutralLight1, var(--white, #fff));\n border-color: var(--secondary, #535395);\n}\n.mui-ls-button.MuiButton-outlinedSecondary.Mui-disabled {\n color: var(--neutralLight2, var(--white2, #cac9d5));\n border-color: var(--neutralDark4, var(--cardborder, #393853));\n background-color: var(--transwhite, rgba(255, 255, 255, 0.05));\n opacity: 0.5;\n}\n.mui-ls-button.MuiButton-outlinedSecondary:hover {\n border-color: var(--secondary, #535395);\n background-color: var(--transwhite, rgba(255, 255, 255, 0.05));\n}\n.mui-ls-button.MuiButton-containedError {\n color: var(--alwayswhite, #fff) !important;\n}\n.mui-ls-button.MuiButton-outlinedError {\n color: var(--error, #fd5454);\n border-color: var(--error, #fd5454);\n}\n.mui-ls-button.MuiButton-outlinedError:hover {\n border-color: var(--error, #fd5454);\n background-color: color-mix(in srgb, var(--error, #fd5454) 12%, transparent);\n}\n.mui-ls-button--neutral.mui-ls-button--contained {\n background-color: var(--neutralDark4, var(--cardborder, #393853));\n color: var(--neutralLight1, var(--white, #fff)) !important;\n}\n.mui-ls-button--neutral.mui-ls-button--contained:hover {\n background-color: color-mix(in srgb, var(--neutralDark4, var(--cardborder, #393853)) 82%, var(--neutralLight1, #fff));\n}\n.mui-ls-button--neutral.mui-ls-button--outlined {\n color: var(--neutralLight1, var(--white, #fff));\n border-color: var(--neutralDark4, var(--cardborder, #393853));\n background-color: transparent;\n}\n.mui-ls-button--neutral.mui-ls-button--outlined:hover {\n border-color: var(--neutralLight3, var(--grey, #9392a1));\n background-color: var(--transwhite, rgba(255, 255, 255, 0.05));\n}\n.mui-ls-button--neutral.mui-ls-button--text {\n color: var(--neutralLight1, var(--white, #fff));\n}\n.mui-ls-button--neutral.mui-ls-button--text:hover {\n background-color: var(--transwhite, rgba(255, 255, 255, 0.05));\n}\n.mui-ls-button svg {\n max-height: 24px;\n max-width: 24px;\n}\n.mui-ls-input,\n.ls-font-picker,\n.ls-color-picker__input {\n width: 100%;\n}\n.mui-ls-input,\n.mui-ls-select-form-control,\n.mui-multi-select,\n.ls-font-picker,\n.mui-ls-slider-input-root {\n --ls-control-height: 55px;\n}\n.mui-ls-input .MuiOutlinedInput-root,\n.mui-ls-input .MuiInputBase-root,\n.mui-ls-input .MuiOutlinedInput-notchedOutline,\n.mui-ls-select.MuiOutlinedInput-root,\n.mui-ls-select .MuiOutlinedInput-notchedOutline,\n.mui-multi-select .mui-ls-select.MuiOutlinedInput-root,\n.mui-multi-select .mui-ls-select .MuiOutlinedInput-notchedOutline,\n.ls-font-picker .MuiOutlinedInput-root,\n.ls-font-picker .MuiInputBase-root,\n.ls-font-picker .MuiOutlinedInput-notchedOutline,\n.ls-color-picker__input .MuiOutlinedInput-root,\n.ls-color-picker__input .MuiInputBase-root,\n.ls-color-picker__input .MuiOutlinedInput-notchedOutline {\n border-radius: var(--radius) !important;\n}\n.mui-ls-input .MuiInputBase-root,\n.mui-ls-select.MuiInputBase-root,\n.mui-multi-select .mui-ls-select.MuiInputBase-root,\n.ls-font-picker .MuiInputBase-root {\n min-height: var(--ls-control-height);\n}\n.mui-ls-input.small .MuiInputBase-root {\n min-height: 25px;\n}\n.mui-ls-input.medium .MuiInputBase-root {\n min-height: 35px;\n}\n.mui-ls-input.noMinHeight .MuiInputBase-root {\n min-height: unset;\n}\n.mui-ls-date-picker input[type=datetime-local]::-webkit-calendar-picker-indicator {\n cursor: pointer;\n opacity: 0.9;\n filter: invert(1);\n}\n.mui-ls-select .MuiSelect-select,\n.mui-multi-select .MuiSelect-select {\n display: flex;\n min-height: calc(var(--ls-control-height) - 2px);\n align-items: center;\n box-sizing: border-box;\n border-radius: var(--radius);\n}\n.mui-multi-select .MuiSelect-select {\n min-width: 0;\n}\n.MuiSvgIcon-root svg {\n color: var(--neutralLight1, var(--white, #fff)) !important;\n}\n.mui-ls-input .MuiSvgIcon-root svg path {\n fill: currentcolor;\n stroke: currentcolor;\n}\n.mui-ls-input input::placeholder,\n.mui-ls-input textarea::placeholder {\n color: var(--neutralLight3, var(--grey, #9392a1));\n opacity: 1;\n}\n.mui-ls-input .Mui-focused .MuiOutlinedInput-notchedOutline,\n.mui-ls-input .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline {\n border-color: var(--primary, #ff4076) !important;\n}\n.ls-variable-autocomplete-popper .MuiAutocomplete-paper,\n.ls-variable-autocomplete-paper {\n border: 1px solid var(--neutralDark4, var(--cardborder, #393853)) !important;\n border-radius: var(--radius, 1rem) !important;\n background: var(--neutralDark2, var(--cardbackground, #211f45)) !important;\n color: var(--neutralLight1, var(--white, #fff)) !important;\n box-shadow: 0 16px 32px rgba(0, 0, 0, 0.32) !important;\n overflow: hidden !important;\n}\n.ls-variable-autocomplete-popper .MuiAutocomplete-listbox,\n.ls-variable-autocomplete-listbox {\n padding: 0.25rem 0 !important;\n background: var(--neutralDark2, var(--cardbackground, #211f45)) !important;\n color: var(--neutralLight1, var(--white, #fff)) !important;\n}\n.ls-variable-autocomplete-popper .MuiAutocomplete-option,\n.ls-variable-autocomplete-listbox .MuiAutocomplete-option {\n display: flex !important;\n min-height: 44px !important;\n align-items: center !important;\n padding: 0.625rem 1rem !important;\n color: var(--neutralLight1, var(--white, #fff)) !important;\n font-size: 1rem !important;\n line-height: 1.35 !important;\n cursor: pointer !important;\n transition: background-color 120ms ease, color 120ms ease !important;\n}\n.ls-variable-autocomplete-popper .MuiAutocomplete-option > *,\n.ls-variable-autocomplete-listbox .MuiAutocomplete-option > * {\n min-width: 0;\n}\n.ls-variable-autocomplete-popper .MuiAutocomplete-option:hover,\n.ls-variable-autocomplete-popper .MuiAutocomplete-option.Mui-focused,\n.ls-variable-autocomplete-popper .MuiAutocomplete-option[data-focus=true],\n.ls-variable-autocomplete-popper .MuiAutocomplete-option[aria-selected=true],\n.ls-variable-autocomplete-listbox .MuiAutocomplete-option:hover,\n.ls-variable-autocomplete-listbox .MuiAutocomplete-option.Mui-focused,\n.ls-variable-autocomplete-listbox .MuiAutocomplete-option[data-focus=true],\n.ls-variable-autocomplete-listbox .MuiAutocomplete-option[aria-selected=true] {\n background: var( --cardHover, var(--transwhite, rgba(255, 255, 255, 0.05)) ) !important;\n color: var(--neutralLight1, var(--white, #fff)) !important;\n}\n.ls-variable-option {\n gap: 0.75rem;\n}\n.ls-variable-option__thumb {\n width: 40px;\n height: 40px;\n flex: 0 0 40px;\n border-radius: var(--radius, 1rem);\n object-fit: cover;\n}\n.ls-variable-option__thumb--placeholder {\n background: var(--neutralDark4, var(--cardborder, #393853));\n}\n.ls-variable-option__body {\n min-width: 0;\n}\n.ls-variable-option__label {\n color: var(--neutralLight1, var(--white, #fff));\n font-weight: 500;\n line-height: 1.25;\n}\n.ls-variable-option__helper {\n margin-top: 0.125rem;\n color: var(--neutralLight3, var(--grey, #9392a1));\n font-size: 0.875rem;\n line-height: 1.25;\n}\n.ls-variable-autocomplete-popup-indicator,\n.ls-variable-autocomplete-popup-indicator.MuiIconButton-root,\n.ls-variable-autocomplete-popup-indicator svg {\n color: var(--neutralLight1, var(--white, #fff)) !important;\n}\n.ls-variable-autocomplete-popup-indicator svg path {\n fill: currentcolor !important;\n stroke: currentcolor !important;\n}\n.ls-variable-input-adornment,\n.ls-variable-input-adornment.MuiInputAdornment-root {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 24px;\n width: 24px;\n min-width: 24px;\n margin-left: 0.35rem;\n border-radius: var(--radius, 1rem);\n background: var(--primary, #ff4076) !important;\n color: var(--alwayswhite, var(--white, #fff)) !important;\n font-weight: 800;\n line-height: 1;\n letter-spacing: 0;\n cursor: pointer;\n user-select: none;\n}\n.ls-variable-input-adornment svg,\n.ls-variable-input-adornment path {\n color: currentcolor !important;\n fill: currentcolor !important;\n stroke: currentcolor !important;\n}\n.ls-variable-input-adornment:hover,\n.ls-variable-input-adornment.MuiInputAdornment-root:hover {\n filter: brightness(1.08);\n}\n.ls-variable-input-adornment:active,\n.ls-variable-input-adornment.MuiInputAdornment-root:active {\n filter: brightness(0.96);\n}\n.mui-ls-slider-input-root {\n width: 100%;\n}\n.mui-ls-slider-input-fieldset {\n position: relative;\n min-width: 0;\n width: 100%;\n min-height: var(--ls-control-height);\n margin: 0;\n border: 2px solid var(--neutralDark4, var(--cardborder, #393853));\n border-radius: var(--radius, 1rem);\n padding: 0 0.875rem 0.5rem 0.875rem;\n background: transparent;\n color: var(--neutralLight1, var(--white, #fff));\n}\n.mui-ls-slider-input-fieldset:hover {\n border-color: var(--primary, #ff4076);\n}\n.mui-ls-slider-input-legend {\n display: block;\n width: auto;\n max-width: calc(100% - 1rem);\n margin: 0;\n padding: 0 0.25rem;\n color: var(--neutralLight1, var(--white, #fff));\n font-size: 0.875rem;\n line-height: 1.1;\n white-space: nowrap;\n}\n.mui-ls-slider-input-row {\n display: flex;\n min-width: 0;\n min-height: calc(var(--ls-control-height) - 14px);\n align-items: center;\n gap: 1rem;\n}\n.mui-ls-slider-input-row--slider {\n flex-wrap: nowrap;\n}\n.mui-ls-slider-input-row--input {\n justify-content: stretch;\n}\n.mui-ls-slider-input-value.mui-ls-input {\n margin: 0;\n}\n.mui-ls-slider-input-value.mui-ls-input .MuiFormControl-root,\n.mui-ls-slider-input-value.mui-ls-input .MuiTextField-root {\n margin: 0;\n}\n.mui-ls-slider-input-value.mui-ls-input .MuiInputBase-root {\n min-height: 36px;\n}\n.mui-ls-slider-input-value.mui-ls-input .MuiOutlinedInput-notchedOutline {\n border-color: var(--neutralDark4, var(--cardborder, #393853));\n}\n.mui-ls-slider-input-helper {\n display: block;\n margin-top: 0.25rem;\n color: var(--neutralLight3, var(--grey, #9392a1));\n font-size: 0.75rem;\n line-height: 1.25;\n}\n.ls-multi-select-value {\n display: flex;\n width: 100%;\n min-width: 0;\n flex-wrap: nowrap;\n align-items: center;\n justify-content: space-between;\n gap: 0.5rem;\n overflow: hidden;\n white-space: nowrap;\n}\n.ls-multi-select-value__label {\n min-width: 0;\n flex: 1 1 0%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ls-multi-select-value__count {\n flex-shrink: 0;\n white-space: nowrap;\n}\n.ls-color-picker__preview {\n position: relative;\n display: inline-block;\n width: 1.5rem;\n height: 1.5rem;\n flex-shrink: 0;\n padding: 0;\n border: 1px solid color-mix(in srgb, #fff 25%, transparent);\n border-radius: var(--radius, 1rem);\n background-size: cover;\n background-position: center;\n background-repeat: no-repeat;\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25);\n color: transparent;\n line-height: 0;\n}\n.ls-color-picker__preview:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n.ls-color-picker__preview:focus {\n outline: none;\n}\n.ls-color-picker__preview:focus-visible {\n box-shadow:\n 0 0 0 2px var(--primary, #ff4076),\n 0 0 0 4px var(--neutralDark1, #1b1a28),\n 0 0 0 1px rgba(0, 0, 0, 0.25);\n}\n.ls-color-picker__gradient circle,\n.ls-color-picker__gradient polyline {\n stroke: #fff;\n}\n.ls-variable-picker {\n --variable-custom-color: var(--customVariables, #69ffd2);\n --variable-function-color: var( --functionVariables, var(--semanticPurple, #b489ff) );\n --variable-normal-color: var(--normalVariables, var(--semanticBlue, #65b8ff));\n display: flex;\n width: 92vw;\n max-width: 1100px;\n min-height: 50vh;\n max-height: 55vh;\n flex-direction: column;\n overflow: hidden;\n padding: 1rem;\n}\n.ls-variable-picker__field {\n margin: 0 0 0.75rem;\n border: 1px solid color-mix(in srgb, var(--primary, #ff4076) 42%, var(--neutralDark4, #393853));\n border-radius: var(--radius, 1rem);\n padding: 0.5rem;\n}\n.ls-variable-picker__legend {\n padding-inline: 0.375rem;\n color: var(--primary, #ff4076);\n font-size: 0.75rem;\n font-weight: 700;\n}\n.ls-variable-picker__intro {\n margin-bottom: 1rem;\n color: var(--neutralLight2, #cac9d5);\n}\n.ls-variable-picker__header,\n.ls-variable-picker__row {\n display: grid;\n grid-template-columns: minmax(300px, 1.5fr) 1fr 1fr;\n gap: 2rem;\n}\n.ls-variable-picker__header {\n margin-bottom: 0.25rem;\n padding-inline: 0.25rem;\n padding-right: 1.7rem;\n color: var(--neutralLight3, #9392a1);\n}\n.ls-variable-picker__list {\n height: 100%;\n overflow: auto;\n padding-right: 1rem;\n}\n.ls-variable-picker__row {\n cursor: pointer;\n align-items: start;\n border-bottom: 1px solid color-mix(in srgb, var(--neutralDark4, #393853) 70%, transparent);\n padding: 1rem 0.25rem;\n}\n.ls-variable-picker__row--no-border {\n border-bottom: 0;\n}\n.ls-variable-picker__token-cell {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: 0.375rem;\n}\n.ls-variable-picker__expand-toggle,\n.ls-variable-picker__expand-spacer {\n width: 24px;\n height: 24px;\n flex: 0 0 24px;\n}\n.ls-variable-picker__expand-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 1px solid color-mix(in srgb, var(--neutralDark4, #393853) 75%, transparent);\n border-radius: var(--radius, 1rem);\n padding: 0;\n background: var(--neutralDark3, var(--containerbackground, #1f1f3a));\n color: var(--neutralLight2, #cac9d5);\n transition:\n background-color 140ms ease,\n border-color 140ms ease,\n color 140ms ease,\n transform 140ms ease;\n}\n.ls-variable-picker__expand-toggle:hover {\n border-color: var(--primary, #ff4076);\n background: color-mix(in srgb, var(--primary, #ff4076) 16%, var(--neutralDark3, #1f1f3a));\n color: var(--neutralLight1, #fff);\n}\n.ls-variable-picker__expand-toggle svg {\n transform: rotate(-90deg);\n transition: transform 140ms ease;\n}\n.ls-variable-picker__expand-toggle--expanded svg {\n transform: rotate(0deg);\n}\n.ls-variable-picker__row:active {\n opacity: 0.8;\n}\n.ls-variable-picker__description {\n max-width: 600px;\n overflow-wrap: break-word;\n line-height: 1.25;\n word-break: break-all;\n}\n.ls-variable-picker__value {\n min-width: 0;\n}\n.ls-variable-picker__value-input {\n width: 100%;\n min-width: 0;\n cursor: text;\n overflow: hidden;\n border: 0;\n background: transparent;\n color: var(--neutralLight1, var(--white, #fff));\n caret-color: transparent;\n font: inherit;\n font-size: 1rem;\n line-height: 1.25;\n outline: 0;\n padding: 0;\n text-overflow: ellipsis;\n}\n.ls-variable-picker__value-input:focus {\n outline: 0;\n}\n.ls-variable-picker__value-input::selection {\n background: color-mix(in srgb, var(--primary, #ff4076) 45%, transparent);\n color: var(--alwayswhite, var(--white, #fff));\n}\n.ls-variable-token {\n display: flex;\n min-width: 0;\n align-items: center;\n}\n.ls-variable-token--suggested {\n color: var(--suggestedVariables, #ffb469);\n}\n.ls-variable-token--custom {\n color: var(--customVariables, #69ffd2);\n}\n.ls-variable-token--function {\n color: var(--functionVariables, var(--semanticPurple, #b489ff));\n}\n.ls-variable-token--system {\n color: var(--normalVariables, var(--semanticBlue, #65b8ff));\n}\n.ls-variable-token__name {\n min-width: 0;\n word-break: break-all;\n}\n.ls-variable-token__badge {\n display: inline-flex;\n margin-left: 0.5rem;\n flex-shrink: 0;\n align-items: center;\n border: 1px solid currentcolor;\n border-radius: var(--radius, 1rem);\n padding-inline: 0.25rem;\n padding-block: 0;\n font-size: 10px;\n font-weight: 600;\n line-height: 1;\n text-transform: uppercase;\n}\n");
24
+ styleInject(".ls-color-picker,\n.ls-color-picker *,\n.mui-multi-select,\n.mui-multi-select *,\n.ls-multi-select-value,\n.ls-multi-select-value *,\n.ls-variable-picker,\n.ls-variable-picker *,\n.ls-variable-token,\n.ls-variable-token *,\n.ls-font-picker,\n.ls-font-picker *,\n.mui-ls-input,\n.mui-ls-input * {\n box-sizing: border-box;\n}\n.ls-color-picker button,\n.mui-multi-select button {\n font: inherit;\n letter-spacing: inherit;\n color: inherit;\n margin: 0;\n}\n.ls-color-picker__preview {\n appearance: none;\n -webkit-appearance: none;\n background-color: transparent;\n background-image: none;\n cursor: pointer;\n text-transform: uppercase;\n user-select: none;\n}\n.mui-ls-button.Mui-disabled {\n color: var(--neutralLight2, var(--white2, #cac9d5));\n opacity: 0.45;\n background-color: var(--separator, #0a0821);\n cursor: not-allowed;\n}\n.mui-ls-button {\n border-radius: var(--radius, 1rem);\n padding: 0.375rem 1.5rem;\n text-transform: uppercase;\n}\n.mui-ls-button.MuiButton-sizeSmall {\n padding: 0.25rem 1rem;\n}\n.mui-ls-button.MuiButton-sizeLarge {\n padding: 0.625rem 2rem;\n}\n.mui-ls-button--extended {\n padding-inline: 3rem;\n}\n.mui-ls-button.MuiButton-containedPrimary,\n.mui-ls-button.Mui-disabled.MuiButton-containedPrimary {\n background: var(--primary, #ff4076);\n color: var(--alwayswhite, #fff) !important;\n}\n.mui-ls-button.MuiButton-containedSecondary {\n background-color: var(--secondary, #535395);\n color: var(--alwayswhite, #fff) !important;\n}\n.mui-ls-button.Mui-disabled.MuiButton-containedSecondary {\n background-color: var(--secondary, #535395);\n color: var(--neutralLight2, var(--white2, #cac9d5));\n opacity: 0.4;\n}\n.mui-ls-button.MuiButton-outlinedPrimary {\n color: var(--primary, #ff4076);\n border-color: var(--primary, #ff4076);\n}\n.mui-ls-button.MuiButton-outlinedPrimary:hover {\n border-color: var(--primary, #ff4076);\n background-color: color-mix(in srgb, var(--primary, #ff4076) 12%, transparent);\n}\n.mui-ls-button.MuiButton-outlinedSecondary {\n color: var(--neutralLight1, var(--white, #fff));\n border-color: var(--secondary, #535395);\n}\n.mui-ls-button.MuiButton-outlinedSecondary.Mui-disabled {\n color: var(--neutralLight2, var(--white2, #cac9d5));\n border-color: var(--neutralDark4, var(--cardborder, #393853));\n background-color: var(--transwhite, rgba(255, 255, 255, 0.05));\n opacity: 0.5;\n}\n.mui-ls-button.MuiButton-outlinedSecondary:hover {\n border-color: var(--secondary, #535395);\n background-color: var(--transwhite, rgba(255, 255, 255, 0.05));\n}\n.mui-ls-button.MuiButton-containedError {\n color: var(--alwayswhite, #fff) !important;\n}\n.mui-ls-button.MuiButton-outlinedError {\n color: var(--error, #fd5454);\n border-color: var(--error, #fd5454);\n}\n.mui-ls-button.MuiButton-outlinedError:hover {\n border-color: var(--error, #fd5454);\n background-color: color-mix(in srgb, var(--error, #fd5454) 12%, transparent);\n}\n.mui-ls-button--neutral.mui-ls-button--contained {\n background-color: var(--neutralDark4, var(--cardborder, #393853));\n color: var(--neutralLight1, var(--white, #fff)) !important;\n}\n.mui-ls-button--neutral.mui-ls-button--contained:hover {\n background-color: color-mix(in srgb, var(--neutralDark4, var(--cardborder, #393853)) 82%, var(--neutralLight1, #fff));\n}\n.mui-ls-button--neutral.mui-ls-button--outlined {\n color: var(--neutralLight1, var(--white, #fff));\n border-color: var(--neutralDark4, var(--cardborder, #393853));\n background-color: transparent;\n}\n.mui-ls-button--neutral.mui-ls-button--outlined:hover {\n border-color: var(--neutralLight3, var(--grey, #9392a1));\n background-color: var(--transwhite, rgba(255, 255, 255, 0.05));\n}\n.mui-ls-button--neutral.mui-ls-button--text {\n color: var(--neutralLight1, var(--white, #fff));\n}\n.mui-ls-button--neutral.mui-ls-button--text:hover {\n background-color: var(--transwhite, rgba(255, 255, 255, 0.05));\n}\n.mui-ls-button svg {\n max-height: 24px;\n max-width: 24px;\n}\n.mui-ls-input,\n.ls-font-picker,\n.ls-color-picker__input {\n width: 100%;\n}\n.mui-ls-input,\n.mui-ls-select-form-control,\n.mui-multi-select,\n.ls-font-picker,\n.mui-ls-slider-input-root {\n --ls-control-height: 55px;\n}\n.mui-ls-input .MuiOutlinedInput-root,\n.mui-ls-input .MuiInputBase-root,\n.mui-ls-input .MuiOutlinedInput-notchedOutline,\n.mui-ls-select.MuiOutlinedInput-root,\n.mui-ls-select .MuiOutlinedInput-notchedOutline,\n.mui-multi-select .mui-ls-select.MuiOutlinedInput-root,\n.mui-multi-select .mui-ls-select .MuiOutlinedInput-notchedOutline,\n.ls-font-picker .MuiOutlinedInput-root,\n.ls-font-picker .MuiInputBase-root,\n.ls-font-picker .MuiOutlinedInput-notchedOutline,\n.ls-color-picker__input .MuiOutlinedInput-root,\n.ls-color-picker__input .MuiInputBase-root,\n.ls-color-picker__input .MuiOutlinedInput-notchedOutline {\n border-radius: var(--radius) !important;\n}\n.mui-ls-input .MuiInputBase-root,\n.mui-ls-select.MuiInputBase-root,\n.mui-multi-select .mui-ls-select.MuiInputBase-root,\n.ls-font-picker .MuiInputBase-root {\n min-height: var(--ls-control-height);\n}\n.mui-ls-input.small .MuiInputBase-root {\n min-height: 25px;\n}\n.mui-ls-input.medium .MuiInputBase-root {\n min-height: 35px;\n}\n.mui-ls-input.noMinHeight .MuiInputBase-root {\n min-height: unset;\n}\n.mui-ls-date-picker input[type=datetime-local]::-webkit-calendar-picker-indicator {\n cursor: pointer;\n opacity: 0.9;\n filter: invert(1);\n}\n.mui-ls-select .MuiSelect-select,\n.mui-multi-select .MuiSelect-select {\n display: flex;\n min-height: calc(var(--ls-control-height) - 2px);\n align-items: center;\n box-sizing: border-box;\n border-radius: var(--radius);\n}\n.mui-multi-select .MuiSelect-select {\n min-width: 0;\n}\n.MuiSvgIcon-root svg {\n color: var(--neutralLight1, var(--white, #fff)) !important;\n}\n.mui-ls-input .MuiSvgIcon-root svg path {\n fill: currentcolor;\n stroke: currentcolor;\n}\n.mui-ls-input input::placeholder,\n.mui-ls-input textarea::placeholder {\n color: var(--neutralLight3, var(--grey, #9392a1));\n opacity: 0.55;\n font-style: italic;\n}\n.mui-ls-input .MuiInputBase-input,\n.mui-ls-input .MuiInputAdornment-root,\n.mui-ls-input .MuiSvgIcon-root,\n.mui-ls-input .MuiFormHelperText-root {\n color: var(--neutralLight1, var(--white, #fff));\n opacity: 1;\n}\n.mui-ls-input .Mui-focused .MuiOutlinedInput-notchedOutline,\n.mui-ls-input .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline {\n border-color: var(--primary, #ff4076) !important;\n}\n.ls-variable-autocomplete-popper .MuiAutocomplete-paper,\n.ls-variable-autocomplete-paper {\n border: 1px solid var(--neutralDark4, var(--cardborder, #393853)) !important;\n border-radius: var(--radius, 1rem) !important;\n background: var(--neutralDark2, var(--cardbackground, #211f45)) !important;\n color: var(--neutralLight1, var(--white, #fff)) !important;\n box-shadow: 0 16px 32px rgba(0, 0, 0, 0.32) !important;\n overflow: hidden !important;\n}\n.ls-variable-autocomplete-popper .MuiAutocomplete-listbox,\n.ls-variable-autocomplete-listbox {\n padding: 0.25rem 0 !important;\n background: var(--neutralDark2, var(--cardbackground, #211f45)) !important;\n color: var(--neutralLight1, var(--white, #fff)) !important;\n}\n.ls-variable-autocomplete-popper .MuiAutocomplete-option,\n.ls-variable-autocomplete-listbox .MuiAutocomplete-option {\n display: flex !important;\n min-height: 44px !important;\n align-items: center !important;\n padding: 0.625rem 1rem !important;\n color: var(--neutralLight1, var(--white, #fff)) !important;\n font-size: 1rem !important;\n line-height: 1.35 !important;\n cursor: pointer !important;\n transition: background-color 120ms ease, color 120ms ease !important;\n}\n.ls-variable-autocomplete-popper .MuiAutocomplete-option > *,\n.ls-variable-autocomplete-listbox .MuiAutocomplete-option > * {\n min-width: 0;\n}\n.ls-variable-autocomplete-popper .MuiAutocomplete-option:hover,\n.ls-variable-autocomplete-popper .MuiAutocomplete-option.Mui-focused,\n.ls-variable-autocomplete-popper .MuiAutocomplete-option[data-focus=true],\n.ls-variable-autocomplete-popper .MuiAutocomplete-option[aria-selected=true],\n.ls-variable-autocomplete-listbox .MuiAutocomplete-option:hover,\n.ls-variable-autocomplete-listbox .MuiAutocomplete-option.Mui-focused,\n.ls-variable-autocomplete-listbox .MuiAutocomplete-option[data-focus=true],\n.ls-variable-autocomplete-listbox .MuiAutocomplete-option[aria-selected=true] {\n background: var( --cardHover, var(--transwhite, rgba(255, 255, 255, 0.05)) ) !important;\n color: var(--neutralLight1, var(--white, #fff)) !important;\n}\n.ls-variable-option {\n gap: 0.75rem;\n}\n.ls-variable-option__thumb {\n width: 40px;\n height: 40px;\n flex: 0 0 40px;\n border-radius: var(--radius, 1rem);\n object-fit: cover;\n}\n.ls-variable-option__thumb--placeholder {\n background: var(--neutralDark4, var(--cardborder, #393853));\n}\n.ls-variable-option__body {\n min-width: 0;\n}\n.ls-variable-option__label {\n color: var(--neutralLight1, var(--white, #fff));\n font-weight: 500;\n line-height: 1.25;\n}\n.ls-variable-option__helper {\n margin-top: 0.125rem;\n color: var(--neutralLight3, var(--grey, #9392a1));\n font-size: 0.875rem;\n line-height: 1.25;\n}\n.ls-variable-autocomplete-popup-indicator,\n.ls-variable-autocomplete-popup-indicator.MuiIconButton-root,\n.ls-variable-autocomplete-popup-indicator svg {\n color: var(--neutralLight1, var(--white, #fff)) !important;\n}\n.ls-variable-autocomplete-popup-indicator svg path {\n fill: currentcolor !important;\n stroke: currentcolor !important;\n}\n.ls-variable-input-adornment,\n.ls-variable-input-adornment.MuiInputAdornment-root {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 24px;\n width: 24px;\n min-width: 24px;\n margin-left: 0.35rem;\n border-radius: var(--radius, 1rem);\n background: var(--primary, #ff4076) !important;\n color: var(--alwayswhite, var(--white, #fff)) !important;\n font-weight: 800;\n line-height: 1;\n letter-spacing: 0;\n cursor: pointer;\n user-select: none;\n}\n.ls-variable-input-adornment svg,\n.ls-variable-input-adornment path {\n color: currentcolor !important;\n fill: currentcolor !important;\n stroke: currentcolor !important;\n}\n.ls-variable-input-adornment:hover,\n.ls-variable-input-adornment.MuiInputAdornment-root:hover {\n filter: brightness(1.08);\n}\n.ls-variable-input-adornment:active,\n.ls-variable-input-adornment.MuiInputAdornment-root:active {\n filter: brightness(0.96);\n}\n.mui-ls-slider-input-root {\n width: 100%;\n}\n.mui-ls-slider-input-fieldset {\n position: relative;\n min-width: 0;\n width: 100%;\n min-height: var(--ls-control-height);\n margin: 0;\n border: 2px solid var(--neutralDark4, var(--cardborder, #393853));\n border-radius: var(--radius, 1rem);\n padding: 0 0.875rem 0.5rem 0.875rem;\n background: transparent;\n color: var(--neutralLight1, var(--white, #fff));\n}\n.mui-ls-slider-input-fieldset:hover {\n border-color: var(--primary, #ff4076);\n}\n.mui-ls-slider-input-legend {\n display: block;\n width: auto;\n max-width: calc(100% - 1rem);\n margin: 0;\n padding: 0 0.25rem;\n color: var(--neutralLight1, var(--white, #fff));\n font-size: 0.875rem;\n line-height: 1.1;\n white-space: nowrap;\n}\n.mui-ls-slider-input-row {\n display: flex;\n min-width: 0;\n min-height: calc(var(--ls-control-height) - 14px);\n align-items: center;\n gap: 1rem;\n}\n.mui-ls-slider-input-row--slider {\n flex-wrap: nowrap;\n}\n.mui-ls-slider-input-row--input {\n justify-content: stretch;\n}\n.mui-ls-slider-input-value.mui-ls-input {\n margin: 0;\n}\n.mui-ls-slider-input-value.mui-ls-input .MuiFormControl-root,\n.mui-ls-slider-input-value.mui-ls-input .MuiTextField-root {\n margin: 0;\n}\n.mui-ls-slider-input-value.mui-ls-input .MuiInputBase-root {\n min-height: 36px;\n}\n.mui-ls-slider-input-value.mui-ls-input .MuiOutlinedInput-notchedOutline {\n border-color: var(--neutralDark4, var(--cardborder, #393853));\n}\n.mui-ls-slider-input-helper {\n display: block;\n margin-top: 0.25rem;\n color: var(--neutralLight3, var(--grey, #9392a1));\n font-size: 0.75rem;\n line-height: 1.25;\n}\n.ls-multi-select-value {\n display: flex;\n width: 100%;\n min-width: 0;\n flex-wrap: nowrap;\n align-items: center;\n justify-content: space-between;\n gap: 0.5rem;\n overflow: hidden;\n white-space: nowrap;\n}\n.ls-multi-select-value__label {\n min-width: 0;\n flex: 1 1 0%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ls-multi-select-value__count {\n flex-shrink: 0;\n white-space: nowrap;\n}\n.ls-color-picker__preview {\n position: relative;\n display: inline-block;\n width: 1.5rem;\n height: 1.5rem;\n flex-shrink: 0;\n padding: 0;\n border: 1px solid color-mix(in srgb, #fff 25%, transparent);\n border-radius: var(--radius, 1rem);\n background-size: cover;\n background-position: center;\n background-repeat: no-repeat;\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25);\n color: transparent;\n line-height: 0;\n}\n.ls-color-picker__preview:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n.ls-color-picker__preview:focus {\n outline: none;\n}\n.ls-color-picker__preview:focus-visible {\n box-shadow:\n 0 0 0 2px var(--primary, #ff4076),\n 0 0 0 4px var(--neutralDark1, #1b1a28),\n 0 0 0 1px rgba(0, 0, 0, 0.25);\n}\n.ls-color-picker__gradient circle,\n.ls-color-picker__gradient polyline {\n stroke: #fff;\n}\n.ls-variable-picker {\n --variable-custom-color: var(--customVariables, #69ffd2);\n --variable-function-color: var( --functionVariables, var(--semanticPurple, #b489ff) );\n --variable-normal-color: var(--normalVariables, var(--semanticBlue, #65b8ff));\n display: flex;\n width: 92vw;\n max-width: 1100px;\n min-height: 50vh;\n max-height: 55vh;\n flex-direction: column;\n overflow: hidden;\n padding: 1rem;\n}\n.ls-variable-picker__field {\n margin: 0 0 0.75rem;\n border: 1px solid color-mix(in srgb, var(--primary, #ff4076) 42%, var(--neutralDark4, #393853));\n border-radius: var(--radius, 1rem);\n padding: 0.5rem;\n}\n.ls-variable-picker__legend {\n padding-inline: 0.375rem;\n color: var(--primary, #ff4076);\n font-size: 0.75rem;\n font-weight: 700;\n}\n.ls-variable-picker__intro {\n margin-bottom: 1rem;\n color: var(--neutralLight2, #cac9d5);\n}\n.ls-variable-picker__header,\n.ls-variable-picker__row {\n display: grid;\n grid-template-columns: minmax(300px, 1.5fr) 1fr 1fr;\n gap: 2rem;\n}\n.ls-variable-picker__header {\n margin-bottom: 0.25rem;\n padding-inline: 0.25rem;\n padding-right: 1.7rem;\n color: var(--neutralLight3, #9392a1);\n}\n.ls-variable-picker__list {\n height: 100%;\n overflow: auto;\n padding-right: 1rem;\n}\n.ls-variable-picker__row {\n cursor: pointer;\n align-items: start;\n border-bottom: 1px solid color-mix(in srgb, var(--neutralDark4, #393853) 70%, transparent);\n padding: 1rem 0.25rem;\n}\n.ls-variable-picker__row--no-border {\n border-bottom: 0;\n}\n.ls-variable-picker__token-cell {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: 0.375rem;\n}\n.ls-variable-picker__expand-toggle,\n.ls-variable-picker__expand-spacer {\n width: 24px;\n height: 24px;\n flex: 0 0 24px;\n}\n.ls-variable-picker__expand-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 1px solid color-mix(in srgb, var(--neutralDark4, #393853) 75%, transparent);\n border-radius: var(--radius, 1rem);\n padding: 0;\n background: var(--neutralDark3, var(--containerbackground, #1f1f3a));\n color: var(--neutralLight2, #cac9d5);\n transition:\n background-color 140ms ease,\n border-color 140ms ease,\n color 140ms ease,\n transform 140ms ease;\n}\n.ls-variable-picker__expand-toggle:hover {\n border-color: var(--primary, #ff4076);\n background: color-mix(in srgb, var(--primary, #ff4076) 16%, var(--neutralDark3, #1f1f3a));\n color: var(--neutralLight1, #fff);\n}\n.ls-variable-picker__expand-toggle svg {\n transform: rotate(-90deg);\n transition: transform 140ms ease;\n}\n.ls-variable-picker__expand-toggle--expanded svg {\n transform: rotate(0deg);\n}\n.ls-variable-picker__row:active {\n opacity: 0.8;\n}\n.ls-variable-picker__description {\n max-width: 600px;\n overflow-wrap: break-word;\n line-height: 1.25;\n word-break: break-all;\n}\n.ls-variable-picker__value {\n min-width: 0;\n}\n.ls-variable-picker__value-input {\n width: 100%;\n min-width: 0;\n cursor: text;\n overflow: hidden;\n border: 0;\n background: transparent;\n color: var(--neutralLight1, var(--white, #fff));\n caret-color: transparent;\n font: inherit;\n font-size: 1rem;\n line-height: 1.25;\n outline: 0;\n padding: 0;\n text-overflow: ellipsis;\n}\n.ls-variable-picker__value-input:focus {\n outline: 0;\n}\n.ls-variable-picker__value-input::selection {\n background: color-mix(in srgb, var(--primary, #ff4076) 45%, transparent);\n color: var(--alwayswhite, var(--white, #fff));\n}\n.ls-variable-token {\n display: flex;\n min-width: 0;\n align-items: center;\n}\n.ls-variable-token--suggested {\n color: var(--suggestedVariables, #ffb469);\n}\n.ls-variable-token--custom {\n color: var(--customVariables, #69ffd2);\n}\n.ls-variable-token--function {\n color: var(--functionVariables, var(--semanticPurple, #b489ff));\n}\n.ls-variable-token--system {\n color: var(--normalVariables, var(--semanticBlue, #65b8ff));\n}\n.ls-variable-token__name {\n min-width: 0;\n word-break: break-all;\n}\n.ls-variable-token__badge {\n display: inline-flex;\n margin-left: 0.5rem;\n flex-shrink: 0;\n align-items: center;\n border: 1px solid currentcolor;\n border-radius: var(--radius, 1rem);\n padding-inline: 0.25rem;\n padding-block: 0;\n font-size: 10px;\n font-weight: 600;\n line-height: 1;\n text-transform: uppercase;\n}\n");
25
25
 
26
26
  // src/components/LSButton/LSButton.tsx
27
27
  import Button from "@mui/material/Button";
@@ -3133,7 +3133,7 @@ var resolveChatterProfileUrlWithResolvers = async ({
3133
3133
  };
3134
3134
 
3135
3135
  // src/se-import/index.ts
3136
- import { nanoid as nanoid10 } from "nanoid";
3136
+ import { nanoid as nanoid8 } from "nanoid";
3137
3137
 
3138
3138
  // src/se-import/translate.ts
3139
3139
  var SE_TO_LUMIA_PLACEHOLDER = {
@@ -4198,12 +4198,26 @@ function mapImage(widget, ctx) {
4198
4198
  );
4199
4199
  const innerWidth = widget.image?.css?.width;
4200
4200
  const innerHeight = widget.image?.css?.height;
4201
+ const outerWidthExplicit = isExplicitPxSize(widget.css?.width);
4202
+ const outerHeightExplicit = isExplicitPxSize(widget.css?.height);
4201
4203
  const bounds = unit.layer.bounds;
4202
- if (innerWidth === "auto") bounds.autoWidth = true;
4203
- if (innerHeight === "auto") bounds.autoHeight = true;
4204
+ if (innerWidth === "auto" && !outerWidthExplicit) bounds.autoWidth = true;
4205
+ if (innerHeight === "auto" && !outerHeightExplicit) bounds.autoHeight = true;
4204
4206
  return unit;
4205
4207
  }
4208
+ function isExplicitPxSize(value) {
4209
+ if (value == null) return false;
4210
+ if (typeof value === "number") return Number.isFinite(value) && value > 0;
4211
+ if (typeof value !== "string") return false;
4212
+ const s = value.trim().toLowerCase();
4213
+ if (s === "" || s === "auto") return false;
4214
+ if (s.endsWith("%")) return false;
4215
+ const parsed = parseFloat(s);
4216
+ return Number.isFinite(parsed) && parsed > 0;
4217
+ }
4206
4218
  function mapVideo(widget, ctx) {
4219
+ const innerWidth = widget.video?.css?.width;
4220
+ const isInnerAutoWidth = innerWidth === "auto";
4207
4221
  const unit = buildUnit(
4208
4222
  widget,
4209
4223
  "video",
@@ -4212,12 +4226,19 @@ function mapVideo(widget, ctx) {
4212
4226
  src: widget.video?.src ?? "",
4213
4227
  volume: widget.video?.volume ?? 0.5,
4214
4228
  loop: true,
4215
- muted: false
4229
+ muted: false,
4230
+ ...isInnerAutoWidth ? { objectFit: "contain" } : {}
4216
4231
  }
4217
4232
  },
4218
4233
  ctx
4219
4234
  );
4220
- unit.layer.bounds.autoHeight = true;
4235
+ const bounds = unit.layer.bounds;
4236
+ if (isInnerAutoWidth) {
4237
+ bounds.autoWidth = true;
4238
+ bounds.autoHeight = true;
4239
+ } else {
4240
+ bounds.autoHeight = true;
4241
+ }
4221
4242
  return unit;
4222
4243
  }
4223
4244
  var LUMIA_SNOW_WEBM = "https://storage.lumiastream.com/overlays/global/seasonal/snow.webm";
@@ -4276,118 +4297,157 @@ function mapReadout(widget, fallbackVar, ctx) {
4276
4297
 
4277
4298
  // src/se-import/mappers/alert.ts
4278
4299
  import { LumiaAlertConfigs } from "@lumiastream/lumia-types";
4300
+ import { nanoid as nanoid2 } from "nanoid";
4301
+
4302
+ // src/se-import/import-utils.ts
4303
+ function pxToNumber(raw, fallback) {
4304
+ if (typeof raw === "number" && Number.isFinite(raw)) return raw;
4305
+ if (typeof raw !== "string") return fallback;
4306
+ const trimmed = raw.trim();
4307
+ if (!trimmed) return fallback;
4308
+ if (trimmed.endsWith("px")) {
4309
+ const n2 = parseFloat(trimmed.slice(0, -2));
4310
+ return Number.isFinite(n2) ? n2 : fallback;
4311
+ }
4312
+ const n = parseFloat(trimmed);
4313
+ return Number.isFinite(n) ? n : fallback;
4314
+ }
4315
+ function parseDurationMs(raw, fallbackMs) {
4316
+ if (typeof raw === "number" && Number.isFinite(raw)) return raw;
4317
+ if (typeof raw !== "string") return fallbackMs;
4318
+ const trimmed = raw.trim();
4319
+ if (!trimmed) return fallbackMs;
4320
+ if (trimmed.endsWith("ms")) {
4321
+ const n2 = parseFloat(trimmed.slice(0, -2));
4322
+ return Number.isFinite(n2) ? n2 : fallbackMs;
4323
+ }
4324
+ if (trimmed.endsWith("s")) {
4325
+ const n2 = parseFloat(trimmed.slice(0, -1));
4326
+ return Number.isFinite(n2) ? n2 * 1e3 : fallbackMs;
4327
+ }
4328
+ const n = parseFloat(trimmed);
4329
+ return Number.isFinite(n) ? n : fallbackMs;
4330
+ }
4331
+ function buildImportDescription(head, sourceUrl) {
4332
+ if (typeof sourceUrl === "string" && sourceUrl.trim().length > 0) {
4333
+ return `${head}
4334
+ Source: ${sourceUrl.trim()}`;
4335
+ }
4336
+ return head;
4337
+ }
4279
4338
 
4280
4339
  // src/se-import/mappers/provider-alerts.ts
4340
+ import { LumiaAlertValues } from "@lumiastream/lumia-types";
4281
4341
  var ALERT_BOX_KEYS = {
4282
4342
  twitch: {
4283
- follower: ["twitch-follower"],
4284
- subscriber: ["twitch-subscriber"],
4285
- tip: ["lumiastream-donation", "streamelements-donation"],
4286
- cheer: ["twitch-bits"],
4287
- raid: ["twitch-raid"],
4288
- merch: ["fourthwall-shopOrder"],
4289
- purchase: ["fourthwall-shopOrder"],
4290
- charityCampaignDonation: ["twitch-charityDonation"]
4343
+ follower: [LumiaAlertValues.TWITCH_FOLLOWER],
4344
+ subscriber: [LumiaAlertValues.TWITCH_SUBSCRIBER],
4345
+ tip: [LumiaAlertValues.LUMIASTREAM_DONATION, LumiaAlertValues.STREAMELEMENTS_DONATION],
4346
+ cheer: [LumiaAlertValues.TWITCH_BITS],
4347
+ raid: [LumiaAlertValues.TWITCH_RAID],
4348
+ merch: [LumiaAlertValues.FOURTHWALL_SHOPORDER],
4349
+ purchase: [LumiaAlertValues.FOURTHWALL_SHOPORDER],
4350
+ charityCampaignDonation: [LumiaAlertValues.TWITCH_CHARITY_DONATION]
4291
4351
  },
4292
4352
  kick: {
4293
- follower: ["kick-follower"],
4294
- subscriber: ["kick-subscriber"],
4295
- tip: ["lumiastream-donation", "streamelements-donation"],
4296
- cheer: ["kick-kicks"],
4297
- raid: ["kick-host"],
4298
- host: ["kick-host"],
4299
- merch: ["fourthwall-shopOrder"],
4300
- purchase: ["fourthwall-shopOrder"]
4353
+ follower: [LumiaAlertValues.KICK_FOLLOWER],
4354
+ subscriber: [LumiaAlertValues.KICK_SUBSCRIBER],
4355
+ tip: [LumiaAlertValues.LUMIASTREAM_DONATION, LumiaAlertValues.STREAMELEMENTS_DONATION],
4356
+ cheer: [LumiaAlertValues.KICK_KICKS],
4357
+ raid: [LumiaAlertValues.KICK_HOST],
4358
+ host: [LumiaAlertValues.KICK_HOST],
4359
+ merch: [LumiaAlertValues.FOURTHWALL_SHOPORDER],
4360
+ purchase: [LumiaAlertValues.FOURTHWALL_SHOPORDER]
4301
4361
  },
4302
4362
  youtube: {
4303
- follower: ["youtube-subscriber"],
4304
- subscriber: ["youtube-member"],
4305
- tip: ["lumiastream-donation", "streamelements-donation"],
4306
- cheer: ["youtube-superchat"],
4307
- merch: ["fourthwall-shopOrder"],
4308
- purchase: ["fourthwall-shopOrder"]
4363
+ follower: [LumiaAlertValues.YOUTUBE_SUBSCRIBER],
4364
+ subscriber: [LumiaAlertValues.YOUTUBE_MEMBER],
4365
+ tip: [LumiaAlertValues.LUMIASTREAM_DONATION, LumiaAlertValues.STREAMELEMENTS_DONATION],
4366
+ cheer: [LumiaAlertValues.YOUTUBE_SUPERCHAT],
4367
+ merch: [LumiaAlertValues.FOURTHWALL_SHOPORDER],
4368
+ purchase: [LumiaAlertValues.FOURTHWALL_SHOPORDER]
4309
4369
  }
4310
4370
  };
4311
4371
  var KAPPAGEN_KEYS = {
4312
4372
  twitch: {
4313
- follower: "twitch-follower",
4314
- subscriber: "twitch-subscriber",
4315
- cheer: "twitch-bits",
4316
- raid: "twitch-raid",
4317
- tip: "lumiastream-donation"
4373
+ follower: LumiaAlertValues.TWITCH_FOLLOWER,
4374
+ subscriber: LumiaAlertValues.TWITCH_SUBSCRIBER,
4375
+ cheer: LumiaAlertValues.TWITCH_BITS,
4376
+ raid: LumiaAlertValues.TWITCH_RAID,
4377
+ tip: LumiaAlertValues.LUMIASTREAM_DONATION
4318
4378
  },
4319
4379
  kick: {
4320
- follower: "kick-follower",
4321
- subscriber: "kick-subscriber",
4322
- cheer: "kick-kicks",
4323
- raid: "kick-host",
4324
- tip: "lumiastream-donation"
4380
+ follower: LumiaAlertValues.KICK_FOLLOWER,
4381
+ subscriber: LumiaAlertValues.KICK_SUBSCRIBER,
4382
+ cheer: LumiaAlertValues.KICK_KICKS,
4383
+ raid: LumiaAlertValues.KICK_HOST,
4384
+ tip: LumiaAlertValues.LUMIASTREAM_DONATION
4325
4385
  },
4326
4386
  youtube: {
4327
- follower: "youtube-subscriber",
4328
- subscriber: "youtube-member",
4329
- cheer: "youtube-superchat",
4330
- tip: "lumiastream-donation"
4387
+ follower: LumiaAlertValues.YOUTUBE_SUBSCRIBER,
4388
+ subscriber: LumiaAlertValues.YOUTUBE_MEMBER,
4389
+ cheer: LumiaAlertValues.YOUTUBE_SUPERCHAT,
4390
+ tip: LumiaAlertValues.LUMIASTREAM_DONATION
4331
4391
  }
4332
4392
  };
4333
4393
  var STREAMBOSS_LISTENER_KEYS = {
4334
4394
  twitch: {
4335
- "follower-latest": ["twitch-follower"],
4336
- "subscriber-latest": ["twitch-subscriber"],
4337
- "tip-latest": ["lumiastream-donation", "streamelements-donation"],
4338
- "cheer-latest": ["twitch-bits"],
4339
- "raid-latest": ["twitch-raid"]
4395
+ "follower-latest": [LumiaAlertValues.TWITCH_FOLLOWER],
4396
+ "subscriber-latest": [LumiaAlertValues.TWITCH_SUBSCRIBER],
4397
+ "tip-latest": [LumiaAlertValues.LUMIASTREAM_DONATION, LumiaAlertValues.STREAMELEMENTS_DONATION],
4398
+ "cheer-latest": [LumiaAlertValues.TWITCH_BITS],
4399
+ "raid-latest": [LumiaAlertValues.TWITCH_RAID]
4340
4400
  },
4341
4401
  kick: {
4342
- "follower-latest": ["kick-follower"],
4343
- "subscriber-latest": ["kick-subscriber"],
4344
- "tip-latest": ["lumiastream-donation", "streamelements-donation"],
4345
- "cheer-latest": ["kick-kicks"],
4346
- "raid-latest": ["kick-host"]
4402
+ "follower-latest": [LumiaAlertValues.KICK_FOLLOWER],
4403
+ "subscriber-latest": [LumiaAlertValues.KICK_SUBSCRIBER],
4404
+ "tip-latest": [LumiaAlertValues.LUMIASTREAM_DONATION, LumiaAlertValues.STREAMELEMENTS_DONATION],
4405
+ "cheer-latest": [LumiaAlertValues.KICK_KICKS],
4406
+ "raid-latest": [LumiaAlertValues.KICK_HOST]
4347
4407
  },
4348
4408
  youtube: {
4349
- "follower-latest": ["youtube-subscriber"],
4350
- "subscriber-latest": ["youtube-member"],
4351
- "tip-latest": ["lumiastream-donation", "streamelements-donation"],
4352
- "cheer-latest": ["youtube-superchat"]
4409
+ "follower-latest": [LumiaAlertValues.YOUTUBE_SUBSCRIBER],
4410
+ "subscriber-latest": [LumiaAlertValues.YOUTUBE_MEMBER],
4411
+ "tip-latest": [LumiaAlertValues.LUMIASTREAM_DONATION, LumiaAlertValues.STREAMELEMENTS_DONATION],
4412
+ "cheer-latest": [LumiaAlertValues.YOUTUBE_SUPERCHAT]
4353
4413
  }
4354
4414
  };
4355
4415
  var CURATED_ALERTS = {
4356
4416
  twitch: [
4357
- "twitch-follower",
4358
- "twitch-subscriber",
4359
- "twitch-giftSubscription",
4360
- "twitch-bits",
4361
- "twitch-bitsCombo",
4362
- "twitch-raid",
4363
- "twitch-points",
4364
- "twitch-redemption",
4365
- "twitch-charityDonation",
4366
- "twitch-hypetrainStarted",
4367
- "twitch-clip",
4368
- "twitch-shoutoutReceive",
4369
- "twitch-watchStreak",
4370
- "twitch-firstChatter",
4371
- "twitch-extension"
4417
+ LumiaAlertValues.TWITCH_FOLLOWER,
4418
+ LumiaAlertValues.TWITCH_SUBSCRIBER,
4419
+ LumiaAlertValues.TWITCH_GIFT_SUBSCRIPTION,
4420
+ LumiaAlertValues.TWITCH_BITS,
4421
+ LumiaAlertValues.TWITCH_BITS_COMBO,
4422
+ LumiaAlertValues.TWITCH_RAID,
4423
+ LumiaAlertValues.TWITCH_POINTS,
4424
+ LumiaAlertValues.TWITCH_REDEMPTION,
4425
+ LumiaAlertValues.TWITCH_CHARITY_DONATION,
4426
+ LumiaAlertValues.TWITCH_HYPETRAIN_STARTED,
4427
+ LumiaAlertValues.TWITCH_CLIP,
4428
+ LumiaAlertValues.TWITCH_SHOUTOUT_RECEIVE,
4429
+ LumiaAlertValues.TWITCH_WATCH_STREAK,
4430
+ LumiaAlertValues.TWITCH_FIRST_CHATTER,
4431
+ LumiaAlertValues.TWITCH_EXTENSION
4372
4432
  ],
4373
4433
  kick: [
4374
- "kick-follower",
4375
- "kick-subscriber",
4376
- "kick-subscriptionGift",
4377
- "kick-kicks",
4378
- "kick-host",
4379
- "kick-points",
4380
- "kick-firstChatter"
4434
+ LumiaAlertValues.KICK_FOLLOWER,
4435
+ LumiaAlertValues.KICK_SUBSCRIBER,
4436
+ LumiaAlertValues.KICK_GIFT_SUBSCRIPTION,
4437
+ LumiaAlertValues.KICK_KICKS,
4438
+ LumiaAlertValues.KICK_HOST,
4439
+ LumiaAlertValues.KICK_POINTS,
4440
+ LumiaAlertValues.KICK_FIRST_CHATTER
4381
4441
  ],
4382
4442
  youtube: [
4383
- "youtube-subscriber",
4384
- "youtube-member",
4385
- "youtube-giftMembers",
4386
- "youtube-superchat",
4387
- "youtube-supersticker",
4388
- "youtube-gifts",
4389
- "youtube-like",
4390
- "youtube-firstChatter"
4443
+ LumiaAlertValues.YOUTUBE_SUBSCRIBER,
4444
+ LumiaAlertValues.YOUTUBE_MEMBER,
4445
+ LumiaAlertValues.YOUTUBE_GIFT_MEMBERS,
4446
+ LumiaAlertValues.YOUTUBE_SUPERCHAT,
4447
+ LumiaAlertValues.YOUTUBE_SUPERSTICKER,
4448
+ LumiaAlertValues.YOUTUBE_GIFTS,
4449
+ LumiaAlertValues.YOUTUBE_LIKE,
4450
+ LumiaAlertValues.YOUTUBE_FIRST_CHATTER
4391
4451
  ]
4392
4452
  };
4393
4453
  function curatedAlertsFor(provider = "twitch") {
@@ -4455,8 +4515,12 @@ function buildAlertEvent(seEvent, seEventType) {
4455
4515
  const shadowEnabled = text.enableShadow !== false;
4456
4516
  const importedTextShadow = css["text-shadow"] ?? "rgba(0, 0, 0, 0.8) 1px 1px 1px";
4457
4517
  const textShadow = shadowEnabled ? importedTextShadow : "none";
4458
- const marginTopRaw = Number(css["margin-top"]);
4459
- const pushTextUp = seLayoutUsesPushText(seEvent.layout) && Number.isFinite(marginTopRaw) ? -marginTopRaw : 0;
4518
+ const allowPush = seLayoutUsesPushText(seEvent.layout);
4519
+ const pushTextUp = allowPush ? pxToNumber(css["margin-top"], 0) : 0;
4520
+ const pushTextLeft = allowPush ? pxToNumber(css["margin-left"], 0) : 0;
4521
+ const usesColumnLayout = !seEvent.layout || seEvent.layout === "column";
4522
+ const usesRowLayout = seEvent.layout === "row";
4523
+ const tightLayout = allowPush && (usesColumnLayout || usesRowLayout);
4460
4524
  const variations = (seEvent.variations ?? []).map((variation) => buildAlertVariation(variation, seEventType, seEvent)).filter((v) => v != null);
4461
4525
  const defaultDuration = (seEventType && SE_DEFAULT_DURATION_BY_EVENT[seEventType]) ?? 8;
4462
4526
  return {
@@ -4483,7 +4547,7 @@ function buildAlertEvent(seEvent, seEventType) {
4483
4547
  showBrandIcon: true
4484
4548
  },
4485
4549
  settings: { duration: (seEvent.duration ?? defaultDuration) * 1e3 },
4486
- layout: { value: translateLayout(seEvent.layout), css: {}, customCss: "", pushTextUp, pushTextLeft: 0 },
4550
+ layout: { value: translateLayout(seEvent.layout), css: {}, customCss: "", pushTextUp, pushTextLeft, tight: tightLayout },
4487
4551
  text: {
4488
4552
  css: {
4489
4553
  fontFamily: css["font-family"] ?? "Roboto",
@@ -4492,6 +4556,10 @@ function buildAlertEvent(seEvent, seEventType) {
4492
4556
  fontWeight: css["font-weight"] ?? "bold",
4493
4557
  textShadow,
4494
4558
  textAlign: css["text-align"] ?? "center"
4559
+ // SE's per-event `margin-top` / `margin-left` are routed to
4560
+ // `layout.pushTextUp` / `layout.pushTextLeft` above (not
4561
+ // stamped on `text.css`) so they land in the alert editor's
4562
+ // dedicated sliders. See the long comment near `pushTextUp`.
4495
4563
  },
4496
4564
  messageCss,
4497
4565
  content: { value: translateSeText(text.message ?? "") }
@@ -4561,6 +4629,15 @@ function buildAlertVariation(v, seEventType, parentSettings) {
4561
4629
  const mergedSettings = deepMergeSeSettings(parentSettings ? parentInheritable : void 0, v.settings);
4562
4630
  const base = buildAlertEvent(mergedSettings, seEventType);
4563
4631
  return {
4632
+ // Stable per-variation ID. Lumia's alert editor (Settings.tsx) auto-
4633
+ // stamps a nanoid on first open for any variation missing an id, but
4634
+ // that leaves a window where the imported overlay's variations have
4635
+ // no stable key — list components rendering them key on
4636
+ // `variation.id || variation.name || idx` and adjacent variations
4637
+ // with the same name collide. Stamping at import time gives every
4638
+ // variation a stable identity from save #1 without waiting for the
4639
+ // editor's first mount.
4640
+ id: nanoid2(),
4564
4641
  name: v.name ?? "Variation",
4565
4642
  on: v.enabled !== false,
4566
4643
  randomChance: v.chance ?? 100,
@@ -4644,7 +4721,7 @@ function mapSingleAlert(widget, lumiaAlertKey, ctx) {
4644
4721
  css: { ...topCss, ...innerCss }
4645
4722
  };
4646
4723
  }
4647
- const built = buildAlertEvent(merged);
4724
+ const built = buildAlertEvent(merged, void 0);
4648
4725
  const events = {
4649
4726
  [lumiaAlertKey]: built
4650
4727
  };
@@ -4790,7 +4867,7 @@ function mapGoal(widget, ctx) {
4790
4867
  }
4791
4868
 
4792
4869
  // src/se-import/mappers/custom.ts
4793
- import { nanoid as nanoid2 } from "nanoid";
4870
+ import { nanoid as nanoid3 } from "nanoid";
4794
4871
  var SOURCE_TO_LUMIA_FIELD_TYPE = {
4795
4872
  text: "input",
4796
4873
  number: "number",
@@ -4880,7 +4957,7 @@ function mapCustom(widget, ctx) {
4880
4957
  // imports — codeId is the scoping key for variables, storage, and
4881
4958
  // event listeners. Mint a fresh nanoid so every imported widget
4882
4959
  // has its own scope.
4883
- codeId: nanoid2(),
4960
+ codeId: nanoid3(),
4884
4961
  html: v.html ?? "",
4885
4962
  css: v.css ?? "",
4886
4963
  js: v.js ?? "",
@@ -5017,6 +5094,63 @@ var TIPJAR_DEFAULT_MODULE = {
5017
5094
  version: 1,
5018
5095
  content: TIPJAR_DEFAULT_CONTENT_FIELDS
5019
5096
  };
5097
+ var EVENTLIST_DEFAULT_CONTENT_FIELDS = {
5098
+ version: 2,
5099
+ eventListType: {},
5100
+ theme: "basic",
5101
+ themeConfig: {},
5102
+ borderRadius: "10px",
5103
+ condensedText: true,
5104
+ src: "",
5105
+ // Category blacklist — events whose Lumia type appears here are
5106
+ // hidden. SL `show_<type>: false` maps to inserting the equivalent
5107
+ // Lumia category here.
5108
+ filters: [],
5109
+ sites: [],
5110
+ maxItemsToShow: 20,
5111
+ removeAfter: 0,
5112
+ reverse: false,
5113
+ horizontal: false,
5114
+ animations: {
5115
+ enterAnimation: "fadeIn",
5116
+ exitAnimation: "fadeOut",
5117
+ enterAnimationDuration: 1e3,
5118
+ exitAnimationDuration: 1e3,
5119
+ enterAnimationDelay: 0,
5120
+ exitAnimationDelay: 0
5121
+ },
5122
+ showDetailedText: true,
5123
+ hideAlertMessage: false,
5124
+ showAlertIcon: true,
5125
+ showSiteIcon: false,
5126
+ showUserAvatar: false,
5127
+ fadeOutAfterDelay: false,
5128
+ fadeOutDelayTime: 5,
5129
+ image: "",
5130
+ itemGap: 16,
5131
+ audio: {
5132
+ enter: "",
5133
+ volume: 1
5134
+ }
5135
+ };
5136
+ var EVENTLIST_DEFAULT_MODULE = {
5137
+ version: 2,
5138
+ content: EVENTLIST_DEFAULT_CONTENT_FIELDS,
5139
+ css: {
5140
+ borderRadius: "0px",
5141
+ borderStyle: "solid",
5142
+ borderWidth: "0px",
5143
+ borderColor: "transparent",
5144
+ fontSize: 16,
5145
+ lineHeight: 1.2,
5146
+ textAlign: "left",
5147
+ fontFamily: "Roboto",
5148
+ fontWeight: "normal",
5149
+ color: "#ffffff",
5150
+ textShadow: "",
5151
+ background: "transparent"
5152
+ }
5153
+ };
5020
5154
 
5021
5155
  // src/se-import/mappers/misc.ts
5022
5156
  function mapChatboxTheme(seTheme) {
@@ -6966,11 +7100,6 @@ function rewriteAssetURLs(overlay, mapping) {
6966
7100
  };
6967
7101
  }
6968
7102
  var MAX_MIRROR_ASSET_BYTES = 100 * 1024 * 1024;
6969
- function looksLikeCorsError2(err) {
6970
- if (!(err instanceof TypeError)) return false;
6971
- const msg = (err.message ?? "").toLowerCase();
6972
- return msg.includes("failed to fetch") || msg.includes("networkerror") || msg.includes("network error") || msg.includes("cors") || msg.includes("access-control") || err.message.length > 0;
6973
- }
6974
7103
  async function mirrorOneAsset(url, upload, signal, proxyAssetFetch) {
6975
7104
  const blob = await fetchAssetBlob(url, signal, proxyAssetFetch);
6976
7105
  if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
@@ -7002,7 +7131,7 @@ async function fetchAssetBlob(url, signal, proxyAssetFetch) {
7002
7131
  return res.blob();
7003
7132
  }
7004
7133
  if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
7005
- if (proxyAssetFetch && looksLikeCorsError2(directErr)) {
7134
+ if (proxyAssetFetch && looksLikeCorsError(directErr)) {
7006
7135
  try {
7007
7136
  return await proxyAssetFetch(url);
7008
7137
  } catch (proxyErr) {
@@ -7053,7 +7182,7 @@ function guessMime(name) {
7053
7182
  }
7054
7183
 
7055
7184
  // src/se-import/transplant.ts
7056
- import { nanoid as nanoid3 } from "nanoid";
7185
+ import { nanoid as nanoid4 } from "nanoid";
7057
7186
  function cloneModuleForTransplant(srcModule, newId) {
7058
7187
  const clonedContent = srcModule.content ? structuredClone(srcModule.content) : srcModule.content;
7059
7188
  if (srcModule.settings?.type === "custom" && clonedContent) {
@@ -7071,7 +7200,7 @@ function transplantLayer(source, sourceLayerId) {
7071
7200
  const srcLayer = layers.find((l) => l.id === sourceLayerId);
7072
7201
  const srcModule = modules[sourceLayerId];
7073
7202
  if (!srcLayer || !srcModule) return null;
7074
- const rootNewId = nanoid3();
7203
+ const rootNewId = nanoid4();
7075
7204
  if (srcLayer.type !== "group") {
7076
7205
  const layer = { ...srcLayer, id: rootNewId };
7077
7206
  const module = cloneModuleForTransplant(srcModule, rootNewId);
@@ -7089,7 +7218,7 @@ function transplantLayer(source, sourceLayerId) {
7089
7218
  if (seen.has(child.id)) continue;
7090
7219
  seen.add(child.id);
7091
7220
  descendants.push(child);
7092
- idRemap.set(child.id, nanoid3());
7221
+ idRemap.set(child.id, nanoid4());
7093
7222
  if (child.type === "group") queue.push(child.id);
7094
7223
  }
7095
7224
  }
@@ -7593,7 +7722,74 @@ function readSLProvider(response) {
7593
7722
  }
7594
7723
 
7595
7724
  // src/sl-import/mappers/alert-box.ts
7596
- import { nanoid as nanoid4 } from "nanoid";
7725
+ import { nanoid as nanoid6 } from "nanoid";
7726
+ import { LumiaAlertValues as LumiaAlertValues2 } from "@lumiastream/lumia-types";
7727
+
7728
+ // src/se-import/import-envelope.ts
7729
+ import { nanoid as nanoid5 } from "nanoid";
7730
+ function buildModuleEnvelope(opts) {
7731
+ const id = opts.id ?? nanoid5();
7732
+ const locked = opts.locked ?? false;
7733
+ const visible = opts.visible ?? true;
7734
+ const layer = {
7735
+ id,
7736
+ ...opts.parentGroupId !== void 0 ? { group: opts.parentGroupId } : {},
7737
+ ...opts.type_layer ? { type: opts.type_layer } : {},
7738
+ state: {
7739
+ visible,
7740
+ locked,
7741
+ ...opts.expanded !== void 0 ? { expanded: opts.expanded } : {}
7742
+ },
7743
+ bounds: {
7744
+ x: opts.bounds.x,
7745
+ y: opts.bounds.y,
7746
+ width: opts.bounds.width,
7747
+ height: opts.bounds.height,
7748
+ scale: [1, 1],
7749
+ ...opts.zIndex !== void 0 ? { zIndex: opts.zIndex } : {}
7750
+ }
7751
+ };
7752
+ const module = {
7753
+ id,
7754
+ version: opts.version ?? 1,
7755
+ loaded: true,
7756
+ settings: { type: opts.type, title: opts.title, locked },
7757
+ lights: opts.lights ?? [],
7758
+ css: opts.css ?? {},
7759
+ content: opts.content ?? {},
7760
+ variables: opts.variables ?? {},
7761
+ events: opts.events ?? {},
7762
+ ...opts.alert !== void 0 ? { alert: opts.alert } : {}
7763
+ };
7764
+ return { id, layer, module };
7765
+ }
7766
+ var IMPORT_META_KEY = "importMeta";
7767
+ function stampImportMetaMerge(module, meta) {
7768
+ const existing = module.content?.[IMPORT_META_KEY] ?? {};
7769
+ module.content = {
7770
+ ...module.content ?? {},
7771
+ [IMPORT_META_KEY]: {
7772
+ importedAt: (/* @__PURE__ */ new Date()).toISOString(),
7773
+ ...existing,
7774
+ ...meta
7775
+ }
7776
+ };
7777
+ }
7778
+ function customHtmlReviewItem(moduleId, importSourceType, settings, reason) {
7779
+ return {
7780
+ moduleId,
7781
+ // ReviewItem's `seWidget` is typed against SEWidget but the review
7782
+ // UI just reads `.type` and `.settings`. Cast through unknown is
7783
+ // the only place we touch the shape mismatch; widening the
7784
+ // ReviewItem type is a future cleanup.
7785
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7786
+ seWidget: { type: importSourceType, settings },
7787
+ status: "template",
7788
+ reason
7789
+ };
7790
+ }
7791
+
7792
+ // src/sl-import/mappers/alert-box.ts
7597
7793
  var SL_EVENT_ROUTING = {
7598
7794
  // Platform-native — provider routes (Twitch / Kick / YouTube).
7599
7795
  follow: { pickProviderKeys: "follower" },
@@ -7605,47 +7801,35 @@ var SL_EVENT_ROUTING = {
7605
7801
  // `subscriber` here is YouTube subscribe (free); `sponsor` is YouTube
7606
7802
  // channel membership; `fanfunding` is YouTube superchat. SL ships these
7607
7803
  // even on Twitch-primary accounts so we always map them.
7608
- subscriber: { keys: ["youtube-subscriber"] },
7609
- sponsor: { keys: ["youtube-member"] },
7610
- fanfunding: { keys: ["youtube-superchat"] },
7611
- super_sticker: { keys: ["youtube-supersticker"] },
7804
+ subscriber: { keys: [LumiaAlertValues2.YOUTUBE_SUBSCRIBER] },
7805
+ sponsor: { keys: [LumiaAlertValues2.YOUTUBE_MEMBER] },
7806
+ fanfunding: { keys: [LumiaAlertValues2.YOUTUBE_SUPERCHAT] },
7807
+ super_sticker: { keys: [LumiaAlertValues2.YOUTUBE_SUPERSTICKER] },
7612
7808
  // Donation / tip channels — Lumia ships dedicated alert keys for each
7613
7809
  // integration. `donation` itself fans out to `streamlabs-donation` AND
7614
7810
  // `lumiastream-donation` so the imported alert fires regardless of which
7615
7811
  // channel the donation actually came in on.
7616
- donation: { keys: ["streamlabs-donation", "lumiastream-donation"] },
7617
- eldonation: { keys: ["extralife-donation"] },
7618
- tiltifydonation: { keys: ["tiltify-campaignDonation"] },
7619
- donordrivedonation: { keys: ["donordrive-donation"] },
7620
- justgivingdonation: { keys: ["streamlabs-donation"] },
7621
- pledge: { keys: ["patreon-campaignPledge"] },
7812
+ donation: { keys: [LumiaAlertValues2.STREAMLABS_DONATION, LumiaAlertValues2.LUMIASTREAM_DONATION] },
7813
+ eldonation: { keys: [LumiaAlertValues2.EXTRALIFE_DONATION] },
7814
+ tiltifydonation: { keys: [LumiaAlertValues2.TILTIFY_DONATION] },
7815
+ donordrivedonation: { keys: [LumiaAlertValues2.DONORDRIVE_DONATION] },
7816
+ justgivingdonation: { keys: [LumiaAlertValues2.STREAMLABS_DONATION] },
7817
+ pledge: { keys: [LumiaAlertValues2.PATREON_PLEDGE] },
7622
7818
  // Charity / merch / loyalty / prime gifting — SL-flavored channels with
7623
7819
  // matching Lumia keys.
7624
- streamlabscharitydonation: { keys: ["streamlabs-charity"] },
7625
- twitchcharitydonation: { keys: ["twitch-charityDonation"] },
7626
- merch: { keys: ["streamlabs-merch"] },
7627
- loyalty_store_redemption: { keys: ["streamlabs-redemption"] },
7628
- prime_sub_gift: { keys: ["streamlabs-primegift"] },
7629
- treat: { keys: ["treatstream-treat"] },
7820
+ streamlabscharitydonation: { keys: [LumiaAlertValues2.STREAMLABS_CHARITY] },
7821
+ twitchcharitydonation: { keys: [LumiaAlertValues2.TWITCH_CHARITY_DONATION] },
7822
+ merch: { keys: [LumiaAlertValues2.STREAMLABS_MERCH] },
7823
+ loyalty_store_redemption: { keys: [LumiaAlertValues2.STREAMLABS_REDEMPTION] },
7824
+ prime_sub_gift: { keys: [LumiaAlertValues2.STREAMLABS_PRIMEGIFT] },
7825
+ treat: { keys: [LumiaAlertValues2.TREATSTREAM_TREAT] },
7630
7826
  // TikTok-flavored (jewel gift is TikTok-ish in concept).
7631
- jewel_gift: { keys: ["tiktok-gift"] }
7827
+ jewel_gift: { keys: [LumiaAlertValues2.TIKTOK_GIFT] }
7632
7828
  };
7633
7829
  function getField(settings, prefix, field) {
7634
7830
  const key = `${prefix}_${field}`;
7635
7831
  return settings[key];
7636
7832
  }
7637
- function pxToNumber(raw, fallback) {
7638
- if (typeof raw === "number" && Number.isFinite(raw)) return raw;
7639
- if (typeof raw !== "string") return fallback;
7640
- const trimmed = raw.trim();
7641
- if (!trimmed) return fallback;
7642
- if (trimmed.endsWith("px")) {
7643
- const n2 = parseFloat(trimmed.slice(0, -2));
7644
- return Number.isFinite(n2) ? n2 : fallback;
7645
- }
7646
- const n = parseFloat(trimmed);
7647
- return Number.isFinite(n) ? n : fallback;
7648
- }
7649
7833
  function translateLayout2(slLayout) {
7650
7834
  switch (slLayout) {
7651
7835
  case "above":
@@ -7755,9 +7939,7 @@ function buildBaseAlertEvent(settings, prefix, defaultMessage) {
7755
7939
  tts: {
7756
7940
  enabled: getField(settings, prefix, "tts_enabled") ?? false,
7757
7941
  voice: getField(settings, prefix, "tts_language") ?? "Brian",
7758
- volume: clampVolume(
7759
- getField(settings, prefix, "tts_volume") ?? 75
7760
- ),
7942
+ volume: clampVolume(getField(settings, prefix, "tts_volume") ?? 75),
7761
7943
  minAmount: getField(settings, prefix, "tts_min_amount") ?? 0
7762
7944
  },
7763
7945
  variations: []
@@ -7772,10 +7954,7 @@ function readSecondaryMessageConfig(settings, prefix, primaryFontFamily) {
7772
7954
  const enabled = settings[showKey];
7773
7955
  if (enabled !== true) return null;
7774
7956
  const fontFamily = getField(settings, secondaryPrefix, "message_font") ?? primaryFontFamily;
7775
- const fontSize = pxToNumber(
7776
- getField(settings, secondaryPrefix, "message_font_size"),
7777
- 20
7778
- );
7957
+ const fontSize = pxToNumber(getField(settings, secondaryPrefix, "message_font_size"), 20);
7779
7958
  const fontWeight = getField(settings, secondaryPrefix, "message_font_weight") ?? 400;
7780
7959
  const fontColor = getField(settings, secondaryPrefix, "message_font_color") ?? "#FFFFFF";
7781
7960
  return {
@@ -7802,11 +7981,20 @@ function translateSLVariationCondition(v) {
7802
7981
  const numericData = typeof data === "number" ? data : Number(data);
7803
7982
  switch (v.condition) {
7804
7983
  case "RANDOM":
7805
- return { conditionType: "RANDOM", condition: Number.isFinite(numericData) ? numericData : 100 };
7984
+ return {
7985
+ conditionType: "RANDOM",
7986
+ condition: Number.isFinite(numericData) ? numericData : 100
7987
+ };
7806
7988
  case "MIN_BITS_USED":
7807
- return { conditionType: "GREATER_NUMBER", condition: Number.isFinite(numericData) ? numericData : 0 };
7989
+ return {
7990
+ conditionType: "GREATER_NUMBER",
7991
+ condition: Number.isFinite(numericData) ? numericData : 0
7992
+ };
7808
7993
  case "EXACT_BITS_USED":
7809
- return { conditionType: "EQUAL_NUMBER", condition: Number.isFinite(numericData) ? numericData : 0 };
7994
+ return {
7995
+ conditionType: "EQUAL_NUMBER",
7996
+ condition: Number.isFinite(numericData) ? numericData : 0
7997
+ };
7810
7998
  case "MIN_MONTHS_SUBSCRIBED":
7811
7999
  return {
7812
8000
  conditionType: "SUBSCRIBED_MONTHS_GREATER",
@@ -7871,6 +8059,14 @@ function buildVariationEvent(v, parentBase) {
7871
8059
  if (s.hideAnimation) inherited.animations.alertExitAnimation = s.hideAnimation;
7872
8060
  if (s.layout) inherited.layout.value = translateLayout2(s.layout);
7873
8061
  return {
8062
+ // Reuse SL's own `uuid` when present so re-importing the same SL
8063
+ // alert-box keeps the variation identity stable across import
8064
+ // runs. SL always emits a uuid in their config (every fixture we've
8065
+ // seen has one); fall back to nanoid for the edge case of a
8066
+ // hand-crafted / older payload that omits it. Matches the SE
8067
+ // alert mapper's variation-id pattern — see Settings.tsx auto-fill
8068
+ // note there for why a stable id matters from save #1.
8069
+ id: typeof v.uuid === "string" && v.uuid.length > 0 ? v.uuid : nanoid6(),
7874
8070
  name: v.name ?? "Variation",
7875
8071
  on: v.paused !== true,
7876
8072
  randomChance: 100,
@@ -7936,11 +8132,7 @@ function buildSLAlertBox(settings, options = {}) {
7936
8132
  for (const [prefix, routing] of Object.entries(SL_EVENT_ROUTING)) {
7937
8133
  const lumiaKeys = routing.pickProviderKeys ? alertBoxKeysFor(routing.pickProviderKeys, provider) : routing.keys ?? [];
7938
8134
  if (lumiaKeys.length === 0) continue;
7939
- const built = buildBaseAlertEvent(
7940
- settings,
7941
- prefix,
7942
- DEFAULT_MESSAGES[prefix] ?? "{name}"
7943
- );
8135
+ const built = buildBaseAlertEvent(settings, prefix, DEFAULT_MESSAGES[prefix] ?? "{name}");
7944
8136
  if (!built) continue;
7945
8137
  const variationKey = VARIATION_KEY_FOR_PREFIX[prefix];
7946
8138
  const variations = variationKey ? settings[variationKey] : void 0;
@@ -7957,53 +8149,32 @@ function buildSLAlertBox(settings, options = {}) {
7957
8149
  channelsImported.push(prefix);
7958
8150
  if (firstEvent == null) firstEvent = built;
7959
8151
  }
7960
- const id = nanoid4();
7961
- const layer = {
7962
- id,
7963
- state: { visible: true, locked: false },
7964
- bounds: { x: 100, y: 100, width: 700, height: 600, scale: [1, 1] }
7965
- };
7966
8152
  const baseAlert = firstEvent ? stripEventOnlyFields(firstEvent) : {};
7967
- const module = {
7968
- id,
7969
- version: 1,
7970
- loaded: true,
7971
- settings: { type: "alert", title: "Streamlabs Alerts", locked: false },
7972
- lights: [],
7973
- css: {},
7974
- content: {},
7975
- variables: {},
7976
- events: {},
8153
+ const { layer, module } = buildModuleEnvelope({
8154
+ type: "alert",
8155
+ title: "Streamlabs Alerts",
8156
+ bounds: { x: 100, y: 100, width: 700, height: 600 },
7977
8157
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
7978
8158
  alert: { ...baseAlert, events }
7979
- };
8159
+ });
7980
8160
  return { layer, module, channelsImported };
7981
8161
  }
7982
8162
  function stripEventOnlyFields(event) {
7983
- const { on: _on, variations: _variations, matchEmptyCondition: _mec, ...base } = event;
8163
+ const {
8164
+ on: _on,
8165
+ variations: _variations,
8166
+ matchEmptyCondition: _mec,
8167
+ ...base
8168
+ } = event;
7984
8169
  return base;
7985
8170
  }
7986
8171
 
7987
8172
  // src/sl-import/mappers/tip-jar.ts
7988
- import { nanoid as nanoid5 } from "nanoid";
7989
- function pxToNumber2(raw, fallback) {
7990
- if (typeof raw === "number" && Number.isFinite(raw)) return raw;
7991
- if (typeof raw !== "string") return fallback;
7992
- const trimmed = raw.trim();
7993
- if (!trimmed) return fallback;
7994
- if (trimmed.endsWith("px")) {
7995
- const n2 = parseFloat(trimmed.slice(0, -2));
7996
- return Number.isFinite(n2) ? n2 : fallback;
7997
- }
7998
- const n = parseFloat(trimmed);
7999
- return Number.isFinite(n) ? n : fallback;
8000
- }
8001
8173
  function buildSLTipJar(settings) {
8002
- const id = nanoid5();
8003
8174
  const backgroundColor = settings.background?.color ?? "transparent";
8004
8175
  const textColor = settings.text?.color ?? "#FFFFFF";
8005
8176
  const fontFamily = settings.text?.font ?? "Open Sans";
8006
- const fontSize = pxToNumber2(settings.text?.size, 32);
8177
+ const fontSize = pxToNumber(settings.text?.size, 32);
8007
8178
  const types = settings.types ?? {};
8008
8179
  const tipsConfig = types.tips ?? {};
8009
8180
  const slEvents = {
@@ -8019,17 +8190,11 @@ function buildSLTipJar(settings) {
8019
8190
  ...TIPJAR_DEFAULT_MODULE.content.display,
8020
8191
  showEventMessages: settings.text?.show !== false
8021
8192
  };
8022
- const layer = {
8023
- id,
8024
- state: { visible: true, locked: false },
8025
- bounds: { x: 100, y: 100, width: 600, height: 700, scale: [1, 1] }
8026
- };
8027
- const module = {
8028
- id,
8193
+ const { layer, module } = buildModuleEnvelope({
8194
+ type: "tipjar",
8195
+ title: "Streamlabs Tip Jar",
8196
+ bounds: { x: 100, y: 100, width: 600, height: 700 },
8029
8197
  version: TIPJAR_DEFAULT_MODULE.version,
8030
- loaded: true,
8031
- settings: { type: "tipjar", title: "Streamlabs Tip Jar", locked: false },
8032
- lights: [],
8033
8198
  css: {
8034
8199
  fontFamily,
8035
8200
  fontSize,
@@ -8050,12 +8215,7 @@ function buildSLTipJar(settings) {
8050
8215
  source: "streamlabs",
8051
8216
  widgetType: "tip_jar",
8052
8217
  importedAt: (/* @__PURE__ */ new Date()).toISOString(),
8053
- // SL jar type doesn't have a 1:1 mapping to Lumia's preset
8054
- // vocabulary (cup-01, cup-02, …). Stashed for review.
8055
8218
  slJarType: settings.jar?.type ?? null,
8056
- // SL tier coin tiers — Lumia's sprite tiers are per-event and
8057
- // configured in the sprite editor; the user re-creates these
8058
- // manually. Stored here so they're not lost.
8059
8219
  slTipTiers: Array.isArray(tipsConfig.tiers) ? tipsConfig.tiers.map((t) => ({
8060
8220
  minimumAmount: t.minimum_amount,
8061
8221
  imageSrc: t.image_src ?? null
@@ -8063,15 +8223,12 @@ function buildSLTipJar(settings) {
8063
8223
  slMinimumTipAmount: tipsConfig.minimum_amount ?? null,
8064
8224
  slHadCustomHtml: settings.custom_html_enabled === true
8065
8225
  }
8066
- },
8067
- variables: {},
8068
- events: {}
8069
- };
8226
+ }
8227
+ });
8070
8228
  return { layer, module };
8071
8229
  }
8072
8230
 
8073
8231
  // src/sl-import/mappers/goal.ts
8074
- import { nanoid as nanoid6 } from "nanoid";
8075
8232
  var GOAL_SOURCE_MAP = {
8076
8233
  follower_goal: "follower",
8077
8234
  sub_goal: "subscriber",
@@ -8084,69 +8241,262 @@ var GOAL_TITLE_MAP = {
8084
8241
  donation_goal: "Donation Goal",
8085
8242
  bit_goal: "Bits Goal"
8086
8243
  };
8087
- function pxToNumber3(raw, fallback) {
8088
- if (typeof raw === "number" && Number.isFinite(raw)) return raw;
8089
- if (typeof raw !== "string") return fallback;
8090
- const trimmed = raw.trim();
8091
- if (!trimmed) return fallback;
8092
- if (trimmed.endsWith("px")) {
8093
- const n2 = parseFloat(trimmed.slice(0, -2));
8094
- return Number.isFinite(n2) ? n2 : fallback;
8095
- }
8096
- const n = parseFloat(trimmed);
8097
- return Number.isFinite(n) ? n : fallback;
8098
- }
8244
+ var GOAL_CURRENT_TEMPLATE = {
8245
+ follower_goal: "{{twitch_total_follower_count}}",
8246
+ sub_goal: "{{twitch_total_subscriber_count}}",
8247
+ donation_goal: "{{total_donation_amount}}",
8248
+ bit_goal: "{{twitch_total_bits_count}}"
8249
+ };
8099
8250
  function buildSLGoal(settings, widgetType) {
8100
- const id = nanoid6();
8101
8251
  const goalSource = GOAL_SOURCE_MAP[widgetType] ?? "follower";
8102
8252
  const defaultTitle = GOAL_TITLE_MAP[widgetType] ?? "Goal";
8253
+ const currentTemplate = GOAL_CURRENT_TEMPLATE[widgetType] ?? "";
8254
+ const requiresReview = settings.custom_enabled === true;
8103
8255
  const title = typeof settings.title === "string" ? settings.title : defaultTitle;
8104
8256
  const goalAmount = typeof settings.goal === "number" ? settings.goal : 100;
8105
8257
  const currentAmount = typeof settings.current === "number" ? settings.current : 0;
8106
8258
  const startingAmount = typeof settings.starting_amount === "number" ? settings.starting_amount : 0;
8107
- const endsAt = typeof settings.ends_at === "string" ? settings.ends_at : null;
8259
+ const endsAt = typeof settings.ends_at === "string" ? settings.ends_at : "";
8108
8260
  const barColor = settings.bar_color ?? "#32C3A6";
8109
- const barBackgroundColor = settings.bar_background_color ?? "#181818";
8261
+ const barBgColor = settings.bar_bg_color ?? "#181818";
8262
+ const widgetBgColor = settings.background_color ?? "transparent";
8110
8263
  const textColor = settings.text_color ?? "#FFFFFF";
8111
8264
  const barTextColor = settings.bar_text_color ?? textColor;
8112
8265
  const fontFamily = settings.font ?? "Open Sans";
8113
- const layer = {
8114
- id,
8115
- state: { visible: true, locked: false },
8116
- bounds: { x: 100, y: 100, width: 740, height: 90, scale: [1, 1] }
8117
- };
8118
- const module = {
8119
- id,
8120
- version: 1,
8121
- loaded: true,
8122
- settings: { type: "goal", title, locked: false },
8123
- lights: [],
8266
+ const fontSize = pxToNumber(settings.font_size, 24);
8267
+ const barThickness = pxToNumber(settings.bar_thickness, 48);
8268
+ const layerHeight = Math.max(60, barThickness + 32);
8269
+ const { layer, module } = buildModuleEnvelope({
8270
+ type: "goal",
8271
+ title,
8272
+ bounds: { x: 100, y: 100, width: 740, height: layerHeight },
8124
8273
  css: {
8125
8274
  fontFamily,
8126
8275
  color: textColor,
8127
- background: barBackgroundColor
8276
+ background: widgetBgColor,
8277
+ fontSize
8128
8278
  },
8279
+ // Use Lumia's canonical goal field names — `targetGoal` (string),
8280
+ // `current` (template string), `display`, `filledColor` /
8281
+ // `unfilledColor`. The prior schema used invented field names
8282
+ // (`goalAmount`, `barColor`) that the renderer ignored.
8129
8283
  content: {
8130
- title,
8131
- goalSource,
8132
- goalAmount,
8133
- currentAmount,
8134
- startingAmount,
8135
- endsAt,
8136
- barColor,
8137
- barBackgroundColor,
8138
- barTextColor,
8139
- fontFamily,
8140
- fontSize: pxToNumber3(settings.font_size, 24)
8284
+ version: 1,
8285
+ type: "bar",
8286
+ label: title,
8287
+ subLabel: "",
8288
+ display: "{{current}} / {{goal}}",
8289
+ current: currentAmount > 0 ? String(currentAmount) : currentTemplate,
8290
+ targetGoal: String(goalAmount),
8291
+ image: "",
8292
+ audio: {
8293
+ decrement: "",
8294
+ increment: "",
8295
+ goal: "",
8296
+ goalFailed: "",
8297
+ volume: 1
8298
+ },
8299
+ endDate: endsAt,
8300
+ goalAnimation: "",
8301
+ goalAnimationLoop: true,
8302
+ unfilledColor: barBgColor,
8303
+ filledColor: barColor,
8304
+ alignItemsToEdges: false,
8305
+ border: "solid 1px transparent",
8306
+ borderRadius: "40px",
8307
+ highlightColor: "inherit",
8308
+ importMeta: {
8309
+ source: "streamlabs",
8310
+ widgetType,
8311
+ importedAt: (/* @__PURE__ */ new Date()).toISOString(),
8312
+ goalSource,
8313
+ slLayout: settings.layout ?? null,
8314
+ slBarTextColor: barTextColor,
8315
+ slBarThickness: barThickness,
8316
+ slStartingAmount: startingAmount,
8317
+ slHadCustomHtml: settings.custom_enabled === true
8318
+ }
8319
+ }
8320
+ });
8321
+ return { layer, module, requiresReview };
8322
+ }
8323
+
8324
+ // src/sl-import/mappers/chat-box.ts
8325
+ function buildSitesArray(settings) {
8326
+ const defaultSites = [...CHATBOX_DEFAULT_MODULE.content.sites];
8327
+ const drop = /* @__PURE__ */ new Set();
8328
+ if (settings.show_bttv_emotes === false) drop.add("bttv");
8329
+ if (settings.show_franker_emotes === false) drop.add("ffz");
8330
+ if (settings.show_7tv_emotes === false) drop.add("seventv");
8331
+ if (settings.twitch_enabled === false) drop.add("twitch");
8332
+ if (settings.youtube_enabled === false) drop.add("youtube");
8333
+ if (settings.facebook_enabled === false) drop.add("facebook");
8334
+ if (settings.tiktok_enabled === false) drop.add("tiktok");
8335
+ if (settings.kick_enabled === false) drop.add("kick");
8336
+ if (drop.size === 0) return defaultSites;
8337
+ return defaultSites.filter((s) => !drop.has(s));
8338
+ }
8339
+ function buildSLChatBox(settings) {
8340
+ const requiresReview = settings.custom_enabled === true;
8341
+ const backgroundColor = settings.background_color ?? "transparent";
8342
+ const textColor = settings.text_color ?? "#FFFFFF";
8343
+ const textSize = pxToNumber(settings.text_size, 16);
8344
+ const fadeOutAfterDelay = settings.always_show_messages !== true;
8345
+ const fadeOutDelayTime = fadeOutAfterDelay ? Math.max(1, Math.round((settings.message_hide_delay ?? 15e3) / 1e3)) : 0;
8346
+ const sites = buildSitesArray(settings);
8347
+ const { layer, module } = buildModuleEnvelope({
8348
+ type: "chatbox",
8349
+ title: "Streamlabs Chat Box",
8350
+ bounds: { x: 100, y: 100, width: 375, height: 470 },
8351
+ version: CHATBOX_DEFAULT_MODULE.version,
8352
+ css: {
8353
+ background: backgroundColor,
8354
+ color: textColor,
8355
+ fontSize: textSize
8141
8356
  },
8142
- variables: {},
8143
- events: {}
8144
- };
8145
- return { layer, module };
8357
+ content: {
8358
+ // Spread canonical defaults FIRST so the renderer's dereferences
8359
+ // (chatboxStreamingSite.<platform>.themeConfig, animations,
8360
+ // chatboxStreamingSite.<platform>.message, etc.) all exist.
8361
+ ...CHATBOX_DEFAULT_MODULE.content,
8362
+ // Then override the bits SL controls.
8363
+ sites,
8364
+ fadeOutAfterDelay,
8365
+ fadeOutDelayTime,
8366
+ // SL keeps history forever by default — `always_show_messages`
8367
+ // implies the streamer wants a permanent scrollback. Lumia
8368
+ // exposes that as `permanent: true`.
8369
+ permanent: settings.always_show_messages === true,
8370
+ // Preserve the SL-specific provenance for the review step.
8371
+ importMeta: {
8372
+ source: "streamlabs",
8373
+ widgetType: "chat_box",
8374
+ importedAt: (/* @__PURE__ */ new Date()).toISOString(),
8375
+ slTheme: settings.theme ?? null,
8376
+ slHadCustomHtml: settings.custom_enabled === true,
8377
+ slHidesCommonBots: settings.hide_common_chat_bots === true,
8378
+ slHidesCommands: settings.hide_commands === true,
8379
+ slMutedChatters: settings.muted_chatters ?? "",
8380
+ slMessageShowDelayMs: settings.message_show_delay ?? 0
8381
+ }
8382
+ }
8383
+ });
8384
+ return { layer, module, requiresReview };
8385
+ }
8386
+
8387
+ // src/sl-import/mappers/event-list.ts
8388
+ var SL_SHOW_FLAG_TO_LUMIA_FILTER = {
8389
+ show_follows: "follower",
8390
+ show_subscriptions: "subscribers",
8391
+ show_resubs: "subscribers",
8392
+ show_sub_gifts: "gifts",
8393
+ show_subscribers: "subscribers",
8394
+ show_sponsors: "subscribers",
8395
+ // YouTube channel members
8396
+ show_sponsor_gifts: "gifts",
8397
+ show_bits: "bits",
8398
+ show_donations: "donation",
8399
+ show_pledges: "donation",
8400
+ // Patreon
8401
+ show_eldonations: "donation",
8402
+ show_tiltifydonations: "donation",
8403
+ show_donordrivedonations: "donation",
8404
+ show_justgivingdonations: "donation",
8405
+ show_streamlabscharitydonations: "donation",
8406
+ show_twitchcharitydonations: "donation",
8407
+ show_twitch_charity: "donation",
8408
+ show_treats: "donation",
8409
+ // TreatStream
8410
+ show_merch: "purchases",
8411
+ show_raids: "raids",
8412
+ show_hosts: "raids",
8413
+ show_redemptions: "redemption",
8414
+ show_fanfundings: "superchats",
8415
+ // YouTube Super Chat
8416
+ show_super_stickers: "superstickers",
8417
+ show_jewel_gifts: "stars"
8418
+ // TikTok-flavored, closest analogue
8419
+ };
8420
+ function buildFilters(settings) {
8421
+ const drop = /* @__PURE__ */ new Set();
8422
+ for (const [slFlag, lumiaCategory] of Object.entries(SL_SHOW_FLAG_TO_LUMIA_FILTER)) {
8423
+ if (settings[slFlag] === false) {
8424
+ drop.add(lumiaCategory);
8425
+ }
8426
+ }
8427
+ return Array.from(drop);
8428
+ }
8429
+ function buildSLEventList(settings) {
8430
+ const requiresReview = settings.custom_enabled === true;
8431
+ const backgroundColor = settings.background_color ?? "transparent";
8432
+ const textColor = settings.text_color ?? "#FFFFFF";
8433
+ const textSize = pxToNumber(settings.text_size, 16);
8434
+ const fontFamily = settings.font_family ?? "Roboto";
8435
+ const maxEvents = typeof settings.max_events === "number" && settings.max_events > 0 ? settings.max_events : EVENTLIST_DEFAULT_MODULE.content.maxItemsToShow;
8436
+ const enterAnimation = settings.show_animation ?? "fadeIn";
8437
+ const exitAnimation = settings.hide_animation ?? "fadeOut";
8438
+ const animationDuration = typeof settings.animation_speed === "number" ? settings.animation_speed : 1e3;
8439
+ const filters = buildFilters(settings);
8440
+ const { layer, module } = buildModuleEnvelope({
8441
+ type: "eventlist",
8442
+ title: "Streamlabs Event List",
8443
+ bounds: { x: 100, y: 100, width: 375, height: 470 },
8444
+ version: EVENTLIST_DEFAULT_MODULE.version,
8445
+ css: {
8446
+ ...EVENTLIST_DEFAULT_MODULE.css,
8447
+ background: backgroundColor,
8448
+ color: textColor,
8449
+ fontSize: textSize,
8450
+ fontFamily
8451
+ },
8452
+ content: {
8453
+ // Spread canonical defaults FIRST so the renderer's dereferences
8454
+ // don't crash on missing nested objects.
8455
+ ...EVENTLIST_DEFAULT_MODULE.content,
8456
+ maxItemsToShow: maxEvents,
8457
+ // SL's `flip_y` corresponds to Lumia's `reverse` (newest item
8458
+ // at the bottom vs the top). SL's `flip_x` flips the alignment
8459
+ // to right; Lumia covers that via css.textAlign but doesn't
8460
+ // have a per-module horizontal flip — stash as importMeta for
8461
+ // the review step.
8462
+ reverse: settings.flip_y === true,
8463
+ // SL's `keep_history: false` means events disappear after the
8464
+ // fade time; map to `fadeOutAfterDelay: true` with the fade
8465
+ // time SL configured.
8466
+ fadeOutAfterDelay: settings.keep_history === false,
8467
+ fadeOutDelayTime: typeof settings.fade_time === "number" && settings.fade_time > 0 ? Math.max(1, Math.round(settings.fade_time / 1e3)) : EVENTLIST_DEFAULT_MODULE.content.fadeOutDelayTime,
8468
+ animations: {
8469
+ ...EVENTLIST_DEFAULT_MODULE.content.animations,
8470
+ enterAnimation,
8471
+ exitAnimation,
8472
+ enterAnimationDuration: animationDuration,
8473
+ exitAnimationDuration: animationDuration
8474
+ },
8475
+ filters,
8476
+ importMeta: {
8477
+ source: "streamlabs",
8478
+ widgetType: "event_list",
8479
+ importedAt: (/* @__PURE__ */ new Date()).toISOString(),
8480
+ slTheme: settings.theme ?? null,
8481
+ slThemeColor: settings.theme_color ?? null,
8482
+ slFlipX: settings.flip_x === true,
8483
+ slHadCustomHtml: settings.custom_enabled === true,
8484
+ // Per-event-type minimums SL filters by — Lumia's eventlist
8485
+ // has no equivalent thresholding; preserve so the streamer
8486
+ // can recreate via the alert module's variation conditions.
8487
+ slHostViewerMinimum: settings.host_viewer_minimum ?? 0,
8488
+ slBitsMinimum: settings.bits_minimum ?? 0,
8489
+ slRaidRaiderMinimum: settings.raid_raider_minimum ?? 0
8490
+ }
8491
+ }
8492
+ });
8493
+ return { layer, module, requiresReview };
8146
8494
  }
8147
8495
 
8148
8496
  // src/sl-import/sl-dispatch.ts
8149
- var IMPORT_META_KEY = "importMeta";
8497
+ function buildSLDescription(widgetType, sourceUrl) {
8498
+ return buildImportDescription(`Imported from Streamlabs ${widgetType}`, sourceUrl);
8499
+ }
8150
8500
  var GOAL_WIDGET_TYPES = /* @__PURE__ */ new Set([
8151
8501
  "follower_goal",
8152
8502
  "sub_goal",
@@ -8154,8 +8504,6 @@ var GOAL_WIDGET_TYPES = /* @__PURE__ */ new Set([
8154
8504
  "bit_goal"
8155
8505
  ]);
8156
8506
  var PLACEHOLDER_WIDGET_TYPES = /* @__PURE__ */ new Set([
8157
- "chat_box",
8158
- "event_list",
8159
8507
  "donation_ticker",
8160
8508
  "stream_boss",
8161
8509
  "credits",
@@ -8165,8 +8513,6 @@ var PLACEHOLDER_WIDGET_TYPES = /* @__PURE__ */ new Set([
8165
8513
  "streamlabels"
8166
8514
  ]);
8167
8515
  var PLACEHOLDER_REASONS = {
8168
- chat_box: "Lumia has a native chatbox module \u2014 set it up directly rather than importing the SL config.",
8169
- event_list: "Lumia has a native eventlist module \u2014 set it up directly rather than importing the SL config.",
8170
8516
  donation_ticker: "No native Lumia equivalent yet \u2014 imported as a placeholder text layer.",
8171
8517
  stream_boss: "Lumia has a native streamboss module \u2014 set it up directly rather than importing the SL config.",
8172
8518
  credits: "Lumia has a native credits module \u2014 set it up directly rather than importing the SL config.",
@@ -8175,19 +8521,14 @@ var PLACEHOLDER_REASONS = {
8175
8521
  sub_train: "No native Lumia equivalent yet \u2014 imported as a placeholder text layer.",
8176
8522
  streamlabels: "Use Lumia template variables (e.g. {{twitch_last_follower}}) inside a Text module instead of importing the SL streamlabels config."
8177
8523
  };
8524
+ var CUSTOM_HTML_REVIEW_REASON = "The Streamlabs widget used custom HTML/CSS/JS that Lumia cannot host. The native Lumia module is the right starting point \u2014 recreate the styling there. The original SL settings are stashed under module.content.importMeta for reference.";
8178
8525
  function stampImportMeta(module, widgetType, role, extra = {}) {
8179
- const existing = module.content?.[IMPORT_META_KEY] ?? {};
8180
- module.content = {
8181
- ...module.content ?? {},
8182
- [IMPORT_META_KEY]: {
8183
- source: "streamlabs",
8184
- widgetType,
8185
- role,
8186
- importedAt: (/* @__PURE__ */ new Date()).toISOString(),
8187
- ...existing,
8188
- ...extra
8189
- }
8190
- };
8526
+ stampImportMetaMerge(module, {
8527
+ source: "streamlabs",
8528
+ widgetType,
8529
+ role,
8530
+ ...extra
8531
+ });
8191
8532
  }
8192
8533
  function buildPlaceholder(widgetType) {
8193
8534
  const id = nanoid7();
@@ -8219,7 +8560,7 @@ function buildPlaceholder(widgetType) {
8219
8560
  };
8220
8561
  return { layer, module };
8221
8562
  }
8222
- function importStreamlabsWidget(response, widgetType) {
8563
+ function importStreamlabsWidget(response, widgetType, options = {}) {
8223
8564
  if (!isSLWidgetConfigResponse(response)) {
8224
8565
  throw new Error(
8225
8566
  "Streamlabs response is missing the expected `settings` envelope. The widget may be private or the response format may have changed."
@@ -8269,9 +8610,61 @@ function importStreamlabsWidget(response, widgetType) {
8269
8610
  coverage.mappings.push({
8270
8611
  seType: `streamlabs:${widgetType}`,
8271
8612
  lumiaType: "goal",
8272
- status: "direct",
8613
+ status: built.requiresReview ? "partial" : "direct",
8273
8614
  count: 1
8274
8615
  });
8616
+ if (built.requiresReview) {
8617
+ reviewItems.push(
8618
+ customHtmlReviewItem(
8619
+ built.module.id,
8620
+ `streamlabs-${widgetType}`,
8621
+ response.settings,
8622
+ CUSTOM_HTML_REVIEW_REASON
8623
+ )
8624
+ );
8625
+ }
8626
+ } else if (widgetType === "chat_box") {
8627
+ const built = buildSLChatBox(response.settings);
8628
+ stampImportMeta(built.module, widgetType, "chatbox");
8629
+ layers.push(built.layer);
8630
+ modules[built.layer.id] = built.module;
8631
+ coverage.mappings.push({
8632
+ seType: "streamlabs:chat_box",
8633
+ lumiaType: "chatbox",
8634
+ status: built.requiresReview ? "partial" : "direct",
8635
+ count: 1
8636
+ });
8637
+ if (built.requiresReview) {
8638
+ reviewItems.push(
8639
+ customHtmlReviewItem(
8640
+ built.module.id,
8641
+ "streamlabs-chat_box",
8642
+ response.settings,
8643
+ CUSTOM_HTML_REVIEW_REASON
8644
+ )
8645
+ );
8646
+ }
8647
+ } else if (widgetType === "event_list") {
8648
+ const built = buildSLEventList(response.settings);
8649
+ stampImportMeta(built.module, widgetType, "eventlist");
8650
+ layers.push(built.layer);
8651
+ modules[built.layer.id] = built.module;
8652
+ coverage.mappings.push({
8653
+ seType: "streamlabs:event_list",
8654
+ lumiaType: "eventlist",
8655
+ status: built.requiresReview ? "partial" : "direct",
8656
+ count: 1
8657
+ });
8658
+ if (built.requiresReview) {
8659
+ reviewItems.push(
8660
+ customHtmlReviewItem(
8661
+ built.module.id,
8662
+ "streamlabs-event_list",
8663
+ response.settings,
8664
+ CUSTOM_HTML_REVIEW_REASON
8665
+ )
8666
+ );
8667
+ }
8275
8668
  } else if (PLACEHOLDER_WIDGET_TYPES.has(widgetType)) {
8276
8669
  const placeholder = buildPlaceholder(widgetType);
8277
8670
  const reason = PLACEHOLDER_REASONS[widgetType] ?? "No native Lumia mapping yet.";
@@ -8320,7 +8713,7 @@ function importStreamlabsWidget(response, widgetType) {
8320
8713
  uuid: "",
8321
8714
  listen_id: "",
8322
8715
  name: overlayName,
8323
- description: `Imported from Streamlabs ${widgetType}`,
8716
+ description: buildSLDescription(widgetType, options.sourceUrl),
8324
8717
  settings: {
8325
8718
  metadata: { width: 1920, height: 1080 },
8326
8719
  layers,
@@ -9882,7 +10275,12 @@ function SEImportWizard({
9882
10275
  );
9883
10276
  }
9884
10277
  importedOverlays.push({
9885
- result: importSEBootstrap(bootstrap),
10278
+ result: importSEBootstrap(bootstrap, {
10279
+ // JWT-flow imports don't come from a pasted URL; synthesize
10280
+ // the SE dashboard URL so the description still carries a
10281
+ // useful traceback. Same shape SE editor links use.
10282
+ sourceUrl: `https://streamelements.com/dashboard/overlays/${overlayId}/editor`
10283
+ }),
9886
10284
  summary
9887
10285
  });
9888
10286
  }
@@ -9927,7 +10325,9 @@ function SEImportWizard({
9927
10325
  "StreamElements returned a payload that doesn't look like an overlay bootstrap."
9928
10326
  );
9929
10327
  }
9930
- const imported = importSEBootstrap(bootstrap);
10328
+ const imported = importSEBootstrap(bootstrap, {
10329
+ sourceUrl: `https://streamelements.com/dashboard/overlays/${overlayId}/editor`
10330
+ });
9931
10331
  setBatchImports([{ result: imported, summary }]);
9932
10332
  setResult(imported);
9933
10333
  setAssetUrls(findSEAssetURLs(imported.overlay));
@@ -10015,7 +10415,9 @@ function SEImportWizard({
10015
10415
  "Streamlabs returned a response that does not look like a widget config payload."
10016
10416
  );
10017
10417
  }
10018
- const imported = importStreamlabsWidget(raw, slParts.widgetType);
10418
+ const imported = importStreamlabsWidget(raw, slParts.widgetType, {
10419
+ sourceUrl: url
10420
+ });
10019
10421
  setBatchImports([]);
10020
10422
  setResult(imported);
10021
10423
  setAssetUrls(findSEAssetURLs(imported.overlay));
@@ -10056,7 +10458,7 @@ function SEImportWizard({
10056
10458
  "StreamElements returned a response that does not look like an Element widget payload."
10057
10459
  );
10058
10460
  }
10059
- const imported = importElementOverlay(raw);
10461
+ const imported = importElementOverlay(raw, { sourceUrl: url });
10060
10462
  setBatchImports([]);
10061
10463
  setResult(imported);
10062
10464
  setAssetUrls(findSEAssetURLs(imported.overlay));
@@ -10104,7 +10506,7 @@ function SEImportWizard({
10104
10506
  ...parts,
10105
10507
  proxyFetch: bindings.proxyFetch
10106
10508
  });
10107
- const imported = importSEBootstrap(bootstrap);
10509
+ const imported = importSEBootstrap(bootstrap, { sourceUrl: url });
10108
10510
  setBatchImports([]);
10109
10511
  setResult(imported);
10110
10512
  setAssetUrls(findSEAssetURLs(imported.overlay));
@@ -10763,7 +11165,7 @@ function extractErrorMessage(err) {
10763
11165
  }
10764
11166
 
10765
11167
  // src/se-import/element-dispatch.ts
10766
- import { nanoid as nanoid9 } from "nanoid";
11168
+ import { LumiaAlertValues as LumiaAlertValues3 } from "@lumiastream/lumia-types";
10767
11169
 
10768
11170
  // src/se-import/element-types.ts
10769
11171
  function isSEElementResponse(value) {
@@ -11007,34 +11409,8 @@ function translateAnimationName(name, fallback) {
11007
11409
  if (PINNED_ANIMATION_MAP[name]) return PINNED_ANIMATION_MAP[name];
11008
11410
  return name.replace(/-([a-z])/g, (_m, c) => c.toUpperCase());
11009
11411
  }
11010
- function parseAnimationDurationMs(raw, fallbackMs) {
11011
- if (typeof raw === "number" && Number.isFinite(raw)) return raw;
11012
- if (typeof raw !== "string") return fallbackMs;
11013
- const trimmed = raw.trim();
11014
- if (!trimmed) return fallbackMs;
11015
- if (trimmed.endsWith("ms")) {
11016
- const n2 = parseFloat(trimmed.slice(0, -2));
11017
- return Number.isFinite(n2) ? n2 : fallbackMs;
11018
- }
11019
- if (trimmed.endsWith("s")) {
11020
- const n2 = parseFloat(trimmed.slice(0, -1));
11021
- return Number.isFinite(n2) ? n2 * 1e3 : fallbackMs;
11022
- }
11023
- const n = parseFloat(trimmed);
11024
- return Number.isFinite(n) ? n : fallbackMs;
11025
- }
11026
- function parsePx2(raw) {
11027
- if (typeof raw === "number" && Number.isFinite(raw)) return raw;
11028
- if (typeof raw !== "string") return NaN;
11029
- const trimmed = raw.trim();
11030
- if (!trimmed) return NaN;
11031
- if (trimmed.endsWith("px")) {
11032
- const n2 = parseFloat(trimmed.slice(0, -2));
11033
- return Number.isFinite(n2) ? n2 : NaN;
11034
- }
11035
- const n = parseFloat(trimmed);
11036
- return Number.isFinite(n) ? n : NaN;
11037
- }
11412
+ var parseAnimationDurationMs = parseDurationMs;
11413
+ var parsePx2 = (raw) => pxToNumber(raw, NaN);
11038
11414
  function findFieldByType(fields, type) {
11039
11415
  if (!fields) return void 0;
11040
11416
  for (const f of Object.values(fields)) {
@@ -11110,6 +11486,47 @@ function buildAnimations(title) {
11110
11486
  const visibleDurationMs = enterDelay + enterDuration + staticDuration + exitDuration;
11111
11487
  return { animations, visibleDurationMs: visibleDurationMs || DEFAULT_DURATION_MS };
11112
11488
  }
11489
+ function resolveFieldBoundsInCanvas(style, canvas, textContent) {
11490
+ if (!style) return null;
11491
+ const x = parsePx2(style.left);
11492
+ const y = parsePx2(style.top);
11493
+ const hasXY = Number.isFinite(x) || Number.isFinite(y);
11494
+ if (!hasXY && style.width == null && style.height == null) return null;
11495
+ const sx = Number.isFinite(x) ? x : 0;
11496
+ const sy = Number.isFinite(y) ? y : 0;
11497
+ let width = parsePx2(style.width);
11498
+ let height = parsePx2(style.height);
11499
+ if (!Number.isFinite(width)) width = Math.max(1, canvas.width - sx);
11500
+ if (!Number.isFinite(height)) {
11501
+ if (typeof textContent === "string") {
11502
+ const lineCount = (textContent.match(/\n/g)?.length ?? 0) + 1;
11503
+ const lhPx = parsePx2(style.lineHeight) || parsePx2(style["line-height"]);
11504
+ const fsPx = parsePx2(style.fontSize);
11505
+ let effectiveLh = lhPx;
11506
+ if (!Number.isFinite(effectiveLh) || effectiveLh < 8) {
11507
+ effectiveLh = Number.isFinite(fsPx) ? fsPx * 1.3 : 24 * 1.3;
11508
+ }
11509
+ height = Math.max(1, lineCount * effectiveLh);
11510
+ } else {
11511
+ height = Math.max(1, canvas.height - sy);
11512
+ }
11513
+ }
11514
+ return { x: sx, y: sy, width, height };
11515
+ }
11516
+ function unionBounds(boxes) {
11517
+ if (boxes.length === 0) return null;
11518
+ let minX = Infinity;
11519
+ let minY = Infinity;
11520
+ let maxX = -Infinity;
11521
+ let maxY = -Infinity;
11522
+ for (const b of boxes) {
11523
+ minX = Math.min(minX, b.x);
11524
+ minY = Math.min(minY, b.y);
11525
+ maxX = Math.max(maxX, b.x + b.width);
11526
+ maxY = Math.max(maxY, b.y + b.height);
11527
+ }
11528
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
11529
+ }
11113
11530
  function buildElementAlertEvent(state, options = {}) {
11114
11531
  const compositeFields = state.settings?.compositeFields;
11115
11532
  const titleField = findFieldByType(compositeFields, "text");
@@ -11127,6 +11544,31 @@ function buildElementAlertEvent(state, options = {}) {
11127
11544
  const titleStyle = titleField?.style;
11128
11545
  const highlightColor = titleField?.highlightedStyle?.color ?? null;
11129
11546
  const { animations, visibleDurationMs } = buildAnimations(titleField);
11547
+ const canvas = options.canvas ?? { width: 1920, height: 1080 };
11548
+ const mediaBounds = resolveFieldBoundsInCanvas(mainAsset?.style, canvas);
11549
+ const textBounds = resolveFieldBoundsInCanvas(titleStyle, canvas, rawMessage);
11550
+ const collected = [];
11551
+ if (mediaBounds) collected.push(mediaBounds);
11552
+ if (textBounds) collected.push(textBounds);
11553
+ let layerBounds = unionBounds(collected);
11554
+ if (!layerBounds) {
11555
+ layerBounds = {
11556
+ x: Math.round(canvas.width * 0.6),
11557
+ y: Math.round(canvas.height * 0.15),
11558
+ width: 400,
11559
+ height: 300
11560
+ };
11561
+ }
11562
+ let pushTextUp = 0;
11563
+ let pushTextLeft = 0;
11564
+ if (textBounds) {
11565
+ const desiredTextTopInLayer = textBounds.y - layerBounds.y;
11566
+ const naturalTextTopInLayer = Math.max(0, (layerBounds.height - textBounds.height) / 2);
11567
+ pushTextUp = Math.round(desiredTextTopInLayer - naturalTextTopInLayer);
11568
+ const desiredTextLeftInLayer = textBounds.x - layerBounds.x;
11569
+ const naturalTextLeftInLayer = Math.max(0, (layerBounds.width - textBounds.width) / 2);
11570
+ pushTextLeft = Math.round(desiredTextLeftInLayer - naturalTextLeftInLayer);
11571
+ }
11130
11572
  const event = {
11131
11573
  on: true,
11132
11574
  // `'custom'` lowercase is load-bearing in Lumia's AlertBoxHandler —
@@ -11145,7 +11587,11 @@ function buildElementAlertEvent(state, options = {}) {
11145
11587
  showBrandIcon: true
11146
11588
  },
11147
11589
  settings: { duration: visibleDurationMs },
11148
- layout: { value: "column", css: {}, customCss: "", pushTextUp: 0, pushTextLeft: 0 },
11590
+ // `textOver` matches SE Element's text-on-video render pattern.
11591
+ // `imageOver` / `column` / `row` would all stack the text outside
11592
+ // the media, which is the bug this layout fixes (text rendered
11593
+ // below the alert graphic instead of overlaid on it).
11594
+ layout: { value: "textOver", css: {}, customCss: "", pushTextUp, pushTextLeft },
11149
11595
  text: {
11150
11596
  css: titleCss(titleStyle),
11151
11597
  messageCss: {},
@@ -11167,44 +11613,13 @@ function buildElementAlertEvent(state, options = {}) {
11167
11613
  },
11168
11614
  variations: []
11169
11615
  };
11170
- const style = mainAsset?.style;
11171
- let bounds;
11172
- if (style) {
11173
- const x = parsePx2(style.left);
11174
- const y = parsePx2(style.top);
11175
- const width = parsePx2(style.width);
11176
- const height = parsePx2(style.height);
11177
- if (Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(width)) {
11178
- bounds = {
11179
- x,
11180
- y,
11181
- width,
11182
- // Height isn't always present on video Main Assets (the video
11183
- // aspect-ratios itself from the source). Fall back to a
11184
- // reasonable default that matches SE's preview row.
11185
- height: Number.isFinite(height) ? height : Math.round(width * 0.75)
11186
- };
11187
- }
11188
- }
11189
- return { event, bounds };
11616
+ return { event, bounds: layerBounds };
11190
11617
  }
11191
11618
 
11192
11619
  // src/se-import/mappers/element-scene.ts
11193
- import { nanoid as nanoid8 } from "nanoid";
11194
11620
  var TEXT_FALLBACK = { width: 400, height: 50 };
11195
11621
  var IMAGE_FALLBACK = { width: 300, height: 100 };
11196
- function parsePx3(raw) {
11197
- if (typeof raw === "number" && Number.isFinite(raw)) return raw;
11198
- if (typeof raw !== "string") return NaN;
11199
- const trimmed = raw.trim();
11200
- if (!trimmed) return NaN;
11201
- if (trimmed.endsWith("px")) {
11202
- const n2 = parseFloat(trimmed.slice(0, -2));
11203
- return Number.isFinite(n2) ? n2 : NaN;
11204
- }
11205
- const n = parseFloat(trimmed);
11206
- return Number.isFinite(n) ? n : NaN;
11207
- }
11622
+ var parsePx3 = (raw) => pxToNumber(raw, NaN);
11208
11623
  function styleToBounds(style, type, canvas) {
11209
11624
  const x = Number.isFinite(parsePx3(style?.left)) ? parsePx3(style?.left) : 0;
11210
11625
  const y = Number.isFinite(parsePx3(style?.top)) ? parsePx3(style?.top) : 0;
@@ -11240,33 +11655,17 @@ function styleToBounds(style, type, canvas) {
11240
11655
  }
11241
11656
  return { x, y, width, height };
11242
11657
  }
11243
- function buildLayer(id, groupId, bounds, zIndex) {
11244
- return {
11245
- id,
11246
- group: groupId,
11247
- state: { visible: true, locked: false },
11248
- bounds: {
11249
- x: bounds.x,
11250
- y: bounds.y,
11251
- width: bounds.width,
11252
- height: bounds.height,
11253
- scale: [1, 1],
11254
- zIndex
11255
- }
11256
- };
11257
- }
11258
- function buildModule(id, type, title, content, css = {}) {
11259
- return {
11260
- id,
11261
- version: 1,
11262
- loaded: true,
11263
- settings: { type, title, locked: false },
11264
- lights: [],
11265
- css,
11658
+ function buildChild(groupId, zIndex, bounds, type, title, content, css = {}) {
11659
+ const { layer, module } = buildModuleEnvelope({
11660
+ type,
11661
+ title,
11662
+ bounds,
11266
11663
  content,
11267
- variables: {},
11268
- events: {}
11269
- };
11664
+ css,
11665
+ parentGroupId: groupId,
11666
+ zIndex
11667
+ });
11668
+ return { layer, module };
11270
11669
  }
11271
11670
  function textStyleToCss(style) {
11272
11671
  const s = style ?? {};
@@ -11281,80 +11680,69 @@ function textStyleToCss(style) {
11281
11680
  return out;
11282
11681
  }
11283
11682
  function mapCompositeField(name, field, groupId, zIndex, provider, canvas) {
11284
- const id = nanoid8();
11285
11683
  switch (field.type) {
11286
11684
  case "video": {
11287
- const bounds = styleToBounds(field.style, "video", canvas);
11288
- const layer = buildLayer(id, groupId, bounds, zIndex);
11289
11685
  const src = pickFieldMediaHref(field.media, "video/");
11290
11686
  const volume = typeof field.volume === "number" ? field.volume : 1;
11291
- const module = buildModule(id, "video", name, {
11292
- src,
11293
- volume,
11294
- loop: true,
11295
- autoplay: true,
11296
- muted: volume === 0
11297
- });
11298
- return { layer, module };
11687
+ return buildChild(
11688
+ groupId,
11689
+ zIndex,
11690
+ styleToBounds(field.style, "video", canvas),
11691
+ "video",
11692
+ name,
11693
+ { src, volume, loop: true, autoplay: true, muted: volume === 0 }
11694
+ );
11299
11695
  }
11300
11696
  case "image": {
11301
- const bounds = styleToBounds(field.style, "image", canvas);
11302
- const layer = buildLayer(id, groupId, bounds, zIndex);
11303
11697
  const src = pickFieldMediaHref(field.media, "image/");
11304
- const module = buildModule(id, "image", name, { src });
11305
- return { layer, module };
11698
+ return buildChild(
11699
+ groupId,
11700
+ zIndex,
11701
+ styleToBounds(field.style, "image", canvas),
11702
+ "image",
11703
+ name,
11704
+ { src }
11705
+ );
11306
11706
  }
11307
11707
  case "text": {
11308
- const bounds = styleToBounds(field.style, "text", canvas);
11309
- const layer = buildLayer(id, groupId, bounds, zIndex);
11310
11708
  const raw = field.text?.[0]?.content ?? "";
11311
11709
  const value = substituteElementTokens(raw, { provider });
11312
- const module = buildModule(
11313
- id,
11710
+ return buildChild(
11711
+ groupId,
11712
+ zIndex,
11713
+ styleToBounds(field.style, "text", canvas),
11314
11714
  "text",
11315
11715
  name,
11316
11716
  { value, highlightColor: "inherit" },
11317
11717
  textStyleToCss(field.style)
11318
11718
  );
11319
- return { layer, module };
11320
11719
  }
11321
11720
  case "audio": {
11322
- const layer = buildLayer(id, groupId, { x: 0, y: 0, width: 1, height: 1 }, zIndex);
11323
11721
  const src = pickFieldMediaHref(field.media, "audio/");
11324
11722
  const volume = typeof field.volume === "number" ? field.volume : 0.5;
11325
- const module = buildModule(id, "audio", name, { src, volume, loop: false });
11326
- return { layer, module };
11723
+ return buildChild(
11724
+ groupId,
11725
+ zIndex,
11726
+ { x: 0, y: 0, width: 1, height: 1 },
11727
+ "audio",
11728
+ name,
11729
+ { src, volume, loop: false }
11730
+ );
11327
11731
  }
11328
11732
  default:
11329
11733
  return null;
11330
11734
  }
11331
11735
  }
11332
11736
  function buildElementScene(state, options) {
11333
- const groupId = nanoid8();
11334
11737
  const groupTitle = typeof state.displayName === "string" && state.displayName || state.referenceId || "Scene";
11335
- const groupLayer = {
11336
- id: groupId,
11738
+ const { id: groupId, layer: groupLayer, module: groupModule } = buildModuleEnvelope({
11337
11739
  type: "group",
11338
- group: null,
11339
- bounds: {
11340
- x: 0,
11341
- y: 0,
11342
- width: options.canvas.width,
11343
- height: options.canvas.height,
11344
- scale: [1, 1]
11345
- },
11346
- state: { visible: true, locked: false, expanded: false }
11347
- };
11348
- const groupModule = {
11349
- id: groupId,
11350
- loaded: true,
11351
- settings: { type: "group", title: groupTitle },
11352
- lights: [],
11353
- css: {},
11354
- content: {},
11355
- variables: {},
11356
- events: {}
11357
- };
11740
+ title: groupTitle,
11741
+ bounds: { x: 0, y: 0, width: options.canvas.width, height: options.canvas.height },
11742
+ type_layer: "group",
11743
+ parentGroupId: null,
11744
+ expanded: false
11745
+ });
11358
11746
  const children = [];
11359
11747
  const fields = state.settings?.compositeFields ?? {};
11360
11748
  let zIndex = 1;
@@ -11373,7 +11761,12 @@ function buildElementScene(state, options) {
11373
11761
  }
11374
11762
 
11375
11763
  // src/se-import/element-dispatch.ts
11376
- var IMPORT_META_KEY2 = "importMeta";
11764
+ function buildElementDescription(widgetInstanceId, sourceUrl) {
11765
+ return buildImportDescription(
11766
+ `Imported from StreamElements Element widget ${widgetInstanceId}`,
11767
+ sourceUrl
11768
+ );
11769
+ }
11377
11770
  function elementAlertKindToLumiaKeys(kind, provider) {
11378
11771
  switch (kind) {
11379
11772
  case "follower":
@@ -11392,9 +11785,9 @@ function elementAlertKindToLumiaKeys(kind, provider) {
11392
11785
  return alertBoxKeysFor("charityCampaignDonation", provider);
11393
11786
  case "subscriberGift":
11394
11787
  case "communityGiftPurchase": {
11395
- if (provider === "twitch") return ["twitch-giftSubscription"];
11396
- if (provider === "kick") return ["kick-subscriptionGift"];
11397
- if (provider === "youtube") return ["youtube-giftMembers"];
11788
+ if (provider === "twitch") return [LumiaAlertValues3.TWITCH_GIFT_SUBSCRIPTION];
11789
+ if (provider === "kick") return [LumiaAlertValues3.KICK_GIFT_SUBSCRIPTION];
11790
+ if (provider === "youtube") return [LumiaAlertValues3.YOUTUBE_GIFT_MEMBERS];
11398
11791
  return alertBoxKeysFor("subscriber", provider);
11399
11792
  }
11400
11793
  default:
@@ -11409,7 +11802,7 @@ function buildCombinedAlertModule(alertStates, provider, canvas) {
11409
11802
  for (const state of alertStates) {
11410
11803
  const cls = classifyConfigState(state);
11411
11804
  if (cls.kind !== "alert") continue;
11412
- const { event, bounds: bounds2 } = buildElementAlertEvent(state, { provider });
11805
+ const { event, bounds: bounds2 } = buildElementAlertEvent(state, { provider, canvas });
11413
11806
  const keys = elementAlertKindToLumiaKeys(cls.alertKind, provider);
11414
11807
  if (keys.length === 0) continue;
11415
11808
  for (const key of keys) {
@@ -11421,61 +11814,42 @@ function buildCombinedAlertModule(alertStates, provider, canvas) {
11421
11814
  }
11422
11815
  }
11423
11816
  if (Object.keys(events).length === 0) return null;
11424
- const id = nanoid9();
11425
11817
  const bounds = firstBounds ?? {
11426
11818
  x: Math.round(canvas.width * 0.6),
11427
11819
  y: Math.round(canvas.height * 0.15),
11428
11820
  width: 400,
11429
11821
  height: 300
11430
11822
  };
11431
- const layer = {
11432
- id,
11433
- state: { visible: true, locked: false },
11434
- bounds: {
11435
- x: bounds.x,
11436
- y: bounds.y,
11437
- width: bounds.width,
11438
- height: bounds.height,
11439
- scale: [1, 1]
11440
- }
11441
- };
11442
11823
  const baseAlert = firstEvent ? stripEventOnlyFields2(firstEvent) : {};
11443
- const module = {
11444
- id,
11445
- version: 1,
11446
- loaded: true,
11447
- settings: { type: "alert", title: "Alerts (Element import)", locked: false },
11448
- lights: [],
11449
- css: {},
11450
- content: {},
11451
- variables: {},
11452
- events: {},
11824
+ const { layer, module } = buildModuleEnvelope({
11825
+ type: "alert",
11826
+ title: "Alerts (Element import)",
11827
+ bounds,
11453
11828
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
11454
11829
  alert: { ...baseAlert, events }
11455
- };
11830
+ });
11456
11831
  return { layer, module };
11457
11832
  }
11458
11833
  function stripEventOnlyFields2(event) {
11459
- const { on: _on, variations: _variations, matchEmptyCondition: _mec, ...base } = event;
11834
+ const {
11835
+ on: _on,
11836
+ variations: _variations,
11837
+ matchEmptyCondition: _mec,
11838
+ ...base
11839
+ } = event;
11460
11840
  return base;
11461
11841
  }
11462
11842
  function stampImportMeta2(module, state, role) {
11463
- module.content = {
11464
- ...module.content ?? {},
11465
- [IMPORT_META_KEY2]: {
11466
- source: "streamelements-element",
11467
- role,
11468
- referenceId: state?.referenceId,
11469
- categoryId: state?.categoryId,
11470
- displayName: state?.displayName,
11471
- importedAt: (/* @__PURE__ */ new Date()).toISOString()
11472
- }
11473
- };
11843
+ stampImportMetaMerge(module, {
11844
+ source: "streamelements-element",
11845
+ role,
11846
+ referenceId: state?.referenceId,
11847
+ categoryId: state?.categoryId,
11848
+ displayName: state?.displayName
11849
+ });
11474
11850
  }
11475
- function importElementOverlay(response) {
11476
- const configData = parseElementConfigData(
11477
- response.widgetInstanceConfigVersion.configData
11478
- );
11851
+ function importElementOverlay(response, options = {}) {
11852
+ const configData = parseElementConfigData(response.widgetInstanceConfigVersion.configData);
11479
11853
  if (!configData) {
11480
11854
  throw new Error(
11481
11855
  "StreamElements Element response carries an invalid configData payload. The widget may be corrupted or the response format may have changed."
@@ -11545,7 +11919,10 @@ function importElementOverlay(response) {
11545
11919
  uuid: "",
11546
11920
  listen_id: "",
11547
11921
  name: response.widgetInstance.displayName ? `[SE Element] ${response.widgetInstance.displayName}` : `Imported from StreamElements Element (${response.widgetInstance.widgetInstanceId})`,
11548
- description: `Imported from StreamElements Element widget ${response.widgetInstance.widgetInstanceId}`,
11922
+ description: buildElementDescription(
11923
+ response.widgetInstance.widgetInstanceId,
11924
+ options.sourceUrl
11925
+ ),
11549
11926
  settings: {
11550
11927
  metadata: { width: canvas.width, height: canvas.height },
11551
11928
  layers,
@@ -11559,7 +11936,7 @@ function importElementOverlay(response) {
11559
11936
  }
11560
11937
 
11561
11938
  // src/se-import/index.ts
11562
- var IMPORT_META_KEY3 = "importMeta";
11939
+ var IMPORT_META_KEY2 = "importMeta";
11563
11940
  var REVIEW_REASONS = {
11564
11941
  "se-widget-bit-boss": "No native Lumia equivalent \u2014 bit boss fight bar with HP/damage states.",
11565
11942
  // `se-widget-hype-cup` intentionally omitted — it now routes to the native
@@ -11589,6 +11966,10 @@ function reasonFor(seType, status, flaggedOff) {
11589
11966
  if (flaggedOff && FLAG_OFF_REASONS[seType]) return FLAG_OFF_REASONS[seType];
11590
11967
  return REVIEW_REASONS[seType] ?? (status === "template" ? "Imported as a generic template stub." : "No native mapping yet \u2014 imported as a labelled text placeholder.");
11591
11968
  }
11969
+ function buildSEBootstrapDescription(overlayId, sourceUrl) {
11970
+ const head = overlayId ? `Imported from StreamElements overlay ${overlayId}` : "Imported from StreamElements";
11971
+ return buildImportDescription(head, sourceUrl);
11972
+ }
11592
11973
  function extractSEOverlayId(input) {
11593
11974
  const trimmed = input.trim();
11594
11975
  const m = trimmed.match(/[0-9a-f]{24}/i);
@@ -11643,7 +12024,7 @@ function isSEBootstrap(value) {
11643
12024
  const o = v.overlay;
11644
12025
  return Array.isArray(o.widgets);
11645
12026
  }
11646
- function importSEBootstrap(bootstrap) {
12027
+ function importSEBootstrap(bootstrap, options = {}) {
11647
12028
  const seOverlay = bootstrap.overlay;
11648
12029
  const widgets = seOverlay?.widgets ?? [];
11649
12030
  const coverage = {
@@ -11666,7 +12047,7 @@ function importSEBootstrap(bootstrap) {
11666
12047
  if (w.type !== "se-widget-group") continue;
11667
12048
  const uid = w.variables?.uid;
11668
12049
  if (typeof uid !== "string") continue;
11669
- const lumiaGroupId = nanoid10();
12050
+ const lumiaGroupId = nanoid8();
11670
12051
  seUidToLumiaGroupId[uid] = lumiaGroupId;
11671
12052
  groupWidgetByUid[uid] = w;
11672
12053
  }
@@ -11705,7 +12086,7 @@ function importSEBootstrap(bootstrap) {
11705
12086
  lights: [],
11706
12087
  css: {},
11707
12088
  content: {
11708
- [IMPORT_META_KEY3]: {
12089
+ [IMPORT_META_KEY2]: {
11709
12090
  source: "streamelements",
11710
12091
  widgetType: w.type,
11711
12092
  importedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -11743,7 +12124,7 @@ function importSEBootstrap(bootstrap) {
11743
12124
  if (result.status === "placeholder" || result.status === "template") {
11744
12125
  module.content = {
11745
12126
  ...module.content ?? {},
11746
- [IMPORT_META_KEY3]: {
12127
+ [IMPORT_META_KEY2]: {
11747
12128
  source: "streamelements",
11748
12129
  widgetType: w.type,
11749
12130
  importedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -11783,7 +12164,7 @@ function importSEBootstrap(bootstrap) {
11783
12164
  uuid: "",
11784
12165
  listen_id: "",
11785
12166
  name: seOverlay?.name ? `[SE] ${seOverlay.name}` : `Imported from StreamElements (${seOverlay?._id ?? ""})`,
11786
- description: seOverlay?._id ? `Imported from StreamElements overlay ${seOverlay._id}` : "Imported from StreamElements",
12167
+ description: buildSEBootstrapDescription(seOverlay?._id, options.sourceUrl),
11787
12168
  settings: {
11788
12169
  metadata: {
11789
12170
  width: seOverlay?.settings?.width ?? 1920,
@@ -11831,7 +12212,7 @@ function applyReviewAction(result, moduleId, action, payload) {
11831
12212
  bounds: existingLayer.bounds,
11832
12213
  state: existingLayer.state
11833
12214
  };
11834
- const importMeta2 = existing2.content?.[IMPORT_META_KEY3];
12215
+ const importMeta2 = existing2.content?.[IMPORT_META_KEY2];
11835
12216
  const mergedLights = (existing2.lights?.length ?? 0) > 0 ? existing2.lights : transplant.module.lights;
11836
12217
  const mergedEvents = existing2.events && Object.keys(existing2.events).length > 0 ? { ...transplant.module.events, ...existing2.events } : transplant.module.events;
11837
12218
  const mergedVariables = existing2.variables && Object.keys(existing2.variables).length > 0 ? { ...transplant.module.variables, ...existing2.variables } : transplant.module.variables;
@@ -11843,7 +12224,7 @@ function applyReviewAction(result, moduleId, action, payload) {
11843
12224
  variables: mergedVariables,
11844
12225
  content: {
11845
12226
  ...transplant.module.content ?? {},
11846
- [IMPORT_META_KEY3]: importMeta2
12227
+ [IMPORT_META_KEY2]: importMeta2
11847
12228
  }
11848
12229
  };
11849
12230
  const rootOldNewId = transplant.layer.id;
@@ -11871,7 +12252,7 @@ function applyReviewAction(result, moduleId, action, payload) {
11871
12252
  const existing = modules[moduleId];
11872
12253
  const generated = payload;
11873
12254
  if (!existing || !generated) return result;
11874
- const importMeta = existing.content?.[IMPORT_META_KEY3];
12255
+ const importMeta = existing.content?.[IMPORT_META_KEY2];
11875
12256
  const replaced = {
11876
12257
  ...existing,
11877
12258
  settings: { ...existing.settings ?? {}, type: "custom" },
@@ -11885,7 +12266,7 @@ function applyReviewAction(result, moduleId, action, payload) {
11885
12266
  data: parseLooseJson(generated.data) ?? {},
11886
12267
  configs: parseLooseJson(generated.configs) ?? [],
11887
12268
  flavor: "ai-generated",
11888
- [IMPORT_META_KEY3]: importMeta
12269
+ [IMPORT_META_KEY2]: importMeta
11889
12270
  }
11890
12271
  };
11891
12272
  modules[moduleId] = replaced;