@livepeer-frameworks/streamcrafter-wc 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/cjs/components/fw-sc-advanced.js +198 -0
  2. package/dist/cjs/components/fw-sc-advanced.js.map +1 -0
  3. package/dist/cjs/components/fw-sc-compositor.js +116 -0
  4. package/dist/cjs/components/fw-sc-compositor.js.map +1 -0
  5. package/dist/cjs/components/fw-sc-layer-list.js +253 -0
  6. package/dist/cjs/components/fw-sc-layer-list.js.map +1 -0
  7. package/dist/cjs/components/fw-sc-scene-switcher.js +164 -0
  8. package/dist/cjs/components/fw-sc-scene-switcher.js.map +1 -0
  9. package/dist/cjs/components/fw-sc-volume.js +183 -0
  10. package/dist/cjs/components/fw-sc-volume.js.map +1 -0
  11. package/dist/cjs/components/fw-streamcrafter.js +508 -0
  12. package/dist/cjs/components/fw-streamcrafter.js.map +1 -0
  13. package/dist/cjs/controllers/ingest-controller-host.js +236 -0
  14. package/dist/cjs/controllers/ingest-controller-host.js.map +1 -0
  15. package/dist/cjs/define.js +25 -0
  16. package/dist/cjs/define.js.map +1 -0
  17. package/dist/cjs/icons/index.js +283 -0
  18. package/dist/cjs/icons/index.js.map +1 -0
  19. package/dist/cjs/index.js +38 -0
  20. package/dist/cjs/index.js.map +1 -0
  21. package/dist/cjs/node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js +33 -0
  22. package/dist/cjs/node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js.map +1 -0
  23. package/dist/cjs/styles/shared-styles.js +2019 -0
  24. package/dist/cjs/styles/shared-styles.js.map +1 -0
  25. package/dist/cjs/styles/utility-styles.js +182 -0
  26. package/dist/cjs/styles/utility-styles.js.map +1 -0
  27. package/dist/esm/components/fw-sc-advanced.js +198 -0
  28. package/dist/esm/components/fw-sc-advanced.js.map +1 -0
  29. package/dist/esm/components/fw-sc-compositor.js +116 -0
  30. package/dist/esm/components/fw-sc-compositor.js.map +1 -0
  31. package/dist/esm/components/fw-sc-layer-list.js +253 -0
  32. package/dist/esm/components/fw-sc-layer-list.js.map +1 -0
  33. package/dist/esm/components/fw-sc-scene-switcher.js +164 -0
  34. package/dist/esm/components/fw-sc-scene-switcher.js.map +1 -0
  35. package/dist/esm/components/fw-sc-volume.js +183 -0
  36. package/dist/esm/components/fw-sc-volume.js.map +1 -0
  37. package/dist/esm/components/fw-streamcrafter.js +508 -0
  38. package/dist/esm/components/fw-streamcrafter.js.map +1 -0
  39. package/dist/esm/controllers/ingest-controller-host.js +234 -0
  40. package/dist/esm/controllers/ingest-controller-host.js.map +1 -0
  41. package/dist/esm/define.js +23 -0
  42. package/dist/esm/define.js.map +1 -0
  43. package/dist/esm/icons/index.js +253 -0
  44. package/dist/esm/icons/index.js.map +1 -0
  45. package/dist/esm/index.js +8 -0
  46. package/dist/esm/index.js.map +1 -0
  47. package/dist/esm/node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js +31 -0
  48. package/dist/esm/node_modules/.pnpm/@rollup_plugin-typescript@12.3.0_rollup@4.57.1_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js.map +1 -0
  49. package/dist/esm/styles/shared-styles.js +2017 -0
  50. package/dist/esm/styles/shared-styles.js.map +1 -0
  51. package/dist/esm/styles/utility-styles.js +180 -0
  52. package/dist/esm/styles/utility-styles.js.map +1 -0
  53. package/dist/fw-streamcrafter.iife.js +3121 -0
  54. package/dist/fw-streamcrafter.iife.js.map +1 -0
  55. package/dist/types/components/fw-sc-advanced.d.ts +20 -0
  56. package/dist/types/components/fw-sc-compositor.d.ts +19 -0
  57. package/dist/types/components/fw-sc-layer-list.d.ts +30 -0
  58. package/dist/types/components/fw-sc-scene-switcher.d.ts +23 -0
  59. package/dist/types/components/fw-sc-volume.d.ts +30 -0
  60. package/dist/types/components/fw-streamcrafter.d.ts +49 -0
  61. package/dist/types/controllers/ingest-controller-host.d.ts +77 -0
  62. package/dist/types/define.d.ts +1 -0
  63. package/dist/types/icons/index.d.ts +29 -0
  64. package/dist/types/iife-entry.d.ts +11 -0
  65. package/dist/types/index.d.ts +12 -0
  66. package/dist/types/styles/shared-styles.d.ts +1 -0
  67. package/dist/types/styles/utility-styles.d.ts +1 -0
  68. package/package.json +55 -0
  69. package/src/components/fw-sc-advanced.ts +221 -0
  70. package/src/components/fw-sc-compositor.ts +162 -0
  71. package/src/components/fw-sc-layer-list.ts +251 -0
  72. package/src/components/fw-sc-scene-switcher.ts +163 -0
  73. package/src/components/fw-sc-volume.ts +171 -0
  74. package/src/components/fw-streamcrafter.ts +515 -0
  75. package/src/controllers/ingest-controller-host.ts +358 -0
  76. package/src/define.ts +23 -0
  77. package/src/icons/index.ts +291 -0
  78. package/src/iife-entry.ts +11 -0
  79. package/src/index.ts +15 -0
  80. package/src/styles/shared-styles.ts +2014 -0
  81. package/src/styles/utility-styles.ts +177 -0
@@ -0,0 +1,3121 @@
1
+ var FwStreamCrafter=function(e){"use strict";var t="undefined"!=typeof document?document.currentScript:null;function r(e,t,r,i){var s,o=arguments.length,n=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,r):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,r,i);else for(var a=e.length-1;a>=0;a--)(s=e[a])&&(n=(o<3?s(n):o>3?s(t,r,n):s(t,r))||n);return o>3&&n&&Object.defineProperty(t,r,n),n}"function"==typeof SuppressedError&&SuppressedError;
2
+ /**
3
+ * @license
4
+ * Copyright 2019 Google LLC
5
+ * SPDX-License-Identifier: BSD-3-Clause
6
+ */
7
+ const i=globalThis,s=i.ShadowRoot&&(void 0===i.ShadyCSS||i.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,o=Symbol(),n=new WeakMap;let a=class{constructor(e,t,r){if(this._$cssResult$=!0,r!==o)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o;const t=this.t;if(s&&void 0===e){const r=void 0!==t&&1===t.length;r&&(e=n.get(t)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),r&&n.set(t,e))}return e}toString(){return this.cssText}};const c=(e,...t)=>{const r=1===e.length?e[0]:t.reduce((t,r,i)=>t+(e=>{if(!0===e._$cssResult$)return e.cssText;if("number"==typeof e)return e;throw Error("Value passed to 'css' function must be a 'css' function result: "+e+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(r)+e[i+1],e[0]);return new a(r,e,o)},l=s?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t="";for(const r of e.cssRules)t+=r.cssText;return(e=>new a("string"==typeof e?e:e+"",void 0,o))(t)})(e):e,{is:d,defineProperty:h,getOwnPropertyDescriptor:u,getOwnPropertyNames:p,getOwnPropertySymbols:g,getPrototypeOf:f}=Object,m=globalThis,w=m.trustedTypes,v=w?w.emptyScript:"",y=m.reactiveElementPolyfillSupport,b=(e,t)=>e,x={toAttribute(e,t){switch(t){case Boolean:e=e?v:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){let r=e;switch(t){case Boolean:r=null!==e;break;case Number:r=null===e?null:Number(e);break;case Object:case Array:try{r=JSON.parse(e)}catch(e){r=null}}return r}},S=(e,t)=>!d(e,t),k={attribute:!0,type:String,converter:x,reflect:!1,useDefault:!1,hasChanged:S};
8
+ /**
9
+ * @license
10
+ * Copyright 2017 Google LLC
11
+ * SPDX-License-Identifier: BSD-3-Clause
12
+ */Symbol.metadata??=Symbol("metadata"),m.litPropertyMetadata??=new WeakMap;let C=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=k){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){const r=Symbol(),i=this.getPropertyDescriptor(e,r,t);void 0!==i&&h(this.prototype,e,i)}}static getPropertyDescriptor(e,t,r){const{get:i,set:s}=u(this.prototype,e)??{get(){return this[t]},set(e){this[t]=e}};return{get:i,set(t){const o=i?.call(this);s?.call(this,t),this.requestUpdate(e,o,r)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??k}static _$Ei(){if(this.hasOwnProperty(b("elementProperties")))return;const e=f(this);e.finalize(),void 0!==e.l&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(b("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(b("properties"))){const e=this.properties,t=[...p(e),...g(e)];for(const r of t)this.createProperty(r,e[r])}const e=this[Symbol.metadata];if(null!==e){const t=litPropertyMetadata.get(e);if(void 0!==t)for(const[e,r]of t)this.elementProperties.set(e,r)}this._$Eh=new Map;for(const[e,t]of this.elementProperties){const r=this._$Eu(e,t);void 0!==r&&this._$Eh.set(r,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){const t=[];if(Array.isArray(e)){const r=new Set(e.flat(1/0).reverse());for(const e of r)t.unshift(l(e))}else void 0!==e&&t.push(l(e));return t}static _$Eu(e,t){const r=t.attribute;return!1===r?void 0:"string"==typeof r?r:"string"==typeof e?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),void 0!==this.renderRoot&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){const e=new Map,t=this.constructor.elementProperties;for(const r of t.keys())this.hasOwnProperty(r)&&(e.set(r,this[r]),delete this[r]);e.size>0&&(this._$Ep=e)}createRenderRoot(){const e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((e,t)=>{if(s)e.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const r of t){const t=document.createElement("style"),s=i.litNonce;void 0!==s&&t.setAttribute("nonce",s),t.textContent=r.cssText,e.appendChild(t)}})(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,r){this._$AK(e,r)}_$ET(e,t){const r=this.constructor.elementProperties.get(e),i=this.constructor._$Eu(e,r);if(void 0!==i&&!0===r.reflect){const s=(void 0!==r.converter?.toAttribute?r.converter:x).toAttribute(t,r.type);this._$Em=e,null==s?this.removeAttribute(i):this.setAttribute(i,s),this._$Em=null}}_$AK(e,t){const r=this.constructor,i=r._$Eh.get(e);if(void 0!==i&&this._$Em!==i){const e=r.getPropertyOptions(i),s="function"==typeof e.converter?{fromAttribute:e.converter}:void 0!==e.converter?.fromAttribute?e.converter:x;this._$Em=i;const o=s.fromAttribute(t,e.type);this[i]=o??this._$Ej?.get(i)??o,this._$Em=null}}requestUpdate(e,t,r,i=!1,s){if(void 0!==e){const o=this.constructor;if(!1===i&&(s=this[e]),r??=o.getPropertyOptions(e),!((r.hasChanged??S)(s,t)||r.useDefault&&r.reflect&&s===this._$Ej?.get(e)&&!this.hasAttribute(o._$Eu(e,r))))return;this.C(e,t,r)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(e,t,{useDefault:r,reflect:i,wrapped:s},o){r&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,o??t??this[e]),!0!==s||void 0!==o)||(this._$AL.has(e)||(this.hasUpdated||r||(t=void 0),this._$AL.set(e,t)),!0===i&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const e=this.scheduleUpdate();return null!=e&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[e,t]of this._$Ep)this[e]=t;this._$Ep=void 0}const e=this.constructor.elementProperties;if(e.size>0)for(const[t,r]of e){const{wrapped:e}=r,i=this[t];!0!==e||this._$AL.has(t)||void 0===i||this.C(t,void 0,r,i)}}let e=!1;const t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(e=>e.hostUpdate?.()),this.update(t)):this._$EM()}catch(t){throw e=!1,this._$EM(),t}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(e){}firstUpdated(e){}};C.elementStyles=[],C.shadowRootOptions={mode:"open"},C[b("elementProperties")]=new Map,C[b("finalized")]=new Map,y?.({ReactiveElement:C}),(m.reactiveElementVersions??=[]).push("2.1.2");
13
+ /**
14
+ * @license
15
+ * Copyright 2017 Google LLC
16
+ * SPDX-License-Identifier: BSD-3-Clause
17
+ */
18
+ const $=globalThis,M=e=>e,T=$.trustedTypes,A=T?T.createPolicy("lit-html",{createHTML:e=>e}):void 0,_="$lit$",E=`lit$${Math.random().toFixed(9).slice(2)}$`,I="?"+E,P=`<${I}>`,R=document,W=()=>R.createComment(""),z=e=>null===e||"object"!=typeof e&&"function"!=typeof e,U=Array.isArray,L="[ \t\n\f\r]",D=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,V=/-->/g,O=/>/g,j=RegExp(`>|${L}(?:([^\\s"'>=/]+)(${L}*=${L}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),N=/'/g,F=/"/g,H=/^(?:script|style|textarea|title)$/i,q=(e=>(t,...r)=>({_$litType$:e,strings:t,values:r}))(1),B=Symbol.for("lit-noChange"),G=Symbol.for("lit-nothing"),Q=new WeakMap,X=R.createTreeWalker(R,129);function K(e,t){if(!U(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==A?A.createHTML(t):t}const J=(e,t)=>{const r=e.length-1,i=[];let s,o=2===t?"<svg>":3===t?"<math>":"",n=D;for(let t=0;t<r;t++){const r=e[t];let a,c,l=-1,d=0;for(;d<r.length&&(n.lastIndex=d,c=n.exec(r),null!==c);)d=n.lastIndex,n===D?"!--"===c[1]?n=V:void 0!==c[1]?n=O:void 0!==c[2]?(H.test(c[2])&&(s=RegExp("</"+c[2],"g")),n=j):void 0!==c[3]&&(n=j):n===j?">"===c[0]?(n=s??D,l=-1):void 0===c[1]?l=-2:(l=n.lastIndex-c[2].length,a=c[1],n=void 0===c[3]?j:'"'===c[3]?F:N):n===F||n===N?n=j:n===V||n===O?n=D:(n=j,s=void 0);const h=n===j&&e[t+1].startsWith("/>")?" ":"";o+=n===D?r+P:l>=0?(i.push(a),r.slice(0,l)+_+r.slice(l)+E+h):r+E+(-2===l?t:h)}return[K(e,o+(e[r]||"<?>")+(2===t?"</svg>":3===t?"</math>":"")),i]};class Z{constructor({strings:e,_$litType$:t},r){let i;this.parts=[];let s=0,o=0;const n=e.length-1,a=this.parts,[c,l]=J(e,t);if(this.el=Z.createElement(c,r),X.currentNode=this.el.content,2===t||3===t){const e=this.el.content.firstChild;e.replaceWith(...e.childNodes)}for(;null!==(i=X.nextNode())&&a.length<n;){if(1===i.nodeType){if(i.hasAttributes())for(const e of i.getAttributeNames())if(e.endsWith(_)){const t=l[o++],r=i.getAttribute(e).split(E),n=/([.?@])?(.*)/.exec(t);a.push({type:1,index:s,name:n[2],strings:r,ctor:"."===n[1]?ie:"?"===n[1]?se:"@"===n[1]?oe:re}),i.removeAttribute(e)}else e.startsWith(E)&&(a.push({type:6,index:s}),i.removeAttribute(e));if(H.test(i.tagName)){const e=i.textContent.split(E),t=e.length-1;if(t>0){i.textContent=T?T.emptyScript:"";for(let r=0;r<t;r++)i.append(e[r],W()),X.nextNode(),a.push({type:2,index:++s});i.append(e[t],W())}}}else if(8===i.nodeType)if(i.data===I)a.push({type:2,index:s});else{let e=-1;for(;-1!==(e=i.data.indexOf(E,e+1));)a.push({type:7,index:s}),e+=E.length-1}s++}}static createElement(e,t){const r=R.createElement("template");return r.innerHTML=e,r}}function Y(e,t,r=e,i){if(t===B)return t;let s=void 0!==i?r._$Co?.[i]:r._$Cl;const o=z(t)?void 0:t._$litDirective$;return s?.constructor!==o&&(s?._$AO?.(!1),void 0===o?s=void 0:(s=new o(e),s._$AT(e,r,i)),void 0!==i?(r._$Co??=[])[i]=s:r._$Cl=s),void 0!==s&&(t=Y(e,s._$AS(e,t.values),s,i)),t}class ee{constructor(e,t){this._$AV=[],this._$AN=void 0,this._$AD=e,this._$AM=t}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(e){const{el:{content:t},parts:r}=this._$AD,i=(e?.creationScope??R).importNode(t,!0);X.currentNode=i;let s=X.nextNode(),o=0,n=0,a=r[0];for(;void 0!==a;){if(o===a.index){let t;2===a.type?t=new te(s,s.nextSibling,this,e):1===a.type?t=new a.ctor(s,a.name,a.strings,this,e):6===a.type&&(t=new ne(s,this,e)),this._$AV.push(t),a=r[++n]}o!==a?.index&&(s=X.nextNode(),o++)}return X.currentNode=R,i}p(e){let t=0;for(const r of this._$AV)void 0!==r&&(void 0!==r.strings?(r._$AI(e,r,t),t+=r.strings.length-2):r._$AI(e[t])),t++}}class te{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(e,t,r,i){this.type=2,this._$AH=G,this._$AN=void 0,this._$AA=e,this._$AB=t,this._$AM=r,this.options=i,this._$Cv=i?.isConnected??!0}get parentNode(){let e=this._$AA.parentNode;const t=this._$AM;return void 0!==t&&11===e?.nodeType&&(e=t.parentNode),e}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(e,t=this){e=Y(this,e,t),z(e)?e===G||null==e||""===e?(this._$AH!==G&&this._$AR(),this._$AH=G):e!==this._$AH&&e!==B&&this._(e):void 0!==e._$litType$?this.$(e):void 0!==e.nodeType?this.T(e):(e=>U(e)||"function"==typeof e?.[Symbol.iterator])(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==G&&z(this._$AH)?this._$AA.nextSibling.data=e:this.T(R.createTextNode(e)),this._$AH=e}$(e){const{values:t,_$litType$:r}=e,i="number"==typeof r?this._$AC(e):(void 0===r.el&&(r.el=Z.createElement(K(r.h,r.h[0]),this.options)),r);if(this._$AH?._$AD===i)this._$AH.p(t);else{const e=new ee(i,this),r=e.u(this.options);e.p(t),this.T(r),this._$AH=e}}_$AC(e){let t=Q.get(e.strings);return void 0===t&&Q.set(e.strings,t=new Z(e)),t}k(e){U(this._$AH)||(this._$AH=[],this._$AR());const t=this._$AH;let r,i=0;for(const s of e)i===t.length?t.push(r=new te(this.O(W()),this.O(W()),this,this.options)):r=t[i],r._$AI(s),i++;i<t.length&&(this._$AR(r&&r._$AB.nextSibling,i),t.length=i)}_$AR(e=this._$AA.nextSibling,t){for(this._$AP?.(!1,!0,t);e!==this._$AB;){const t=M(e).nextSibling;M(e).remove(),e=t}}setConnected(e){void 0===this._$AM&&(this._$Cv=e,this._$AP?.(e))}}class re{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(e,t,r,i,s){this.type=1,this._$AH=G,this._$AN=void 0,this.element=e,this.name=t,this._$AM=i,this.options=s,r.length>2||""!==r[0]||""!==r[1]?(this._$AH=Array(r.length-1).fill(new String),this.strings=r):this._$AH=G}_$AI(e,t=this,r,i){const s=this.strings;let o=!1;if(void 0===s)e=Y(this,e,t,0),o=!z(e)||e!==this._$AH&&e!==B,o&&(this._$AH=e);else{const i=e;let n,a;for(e=s[0],n=0;n<s.length-1;n++)a=Y(this,i[r+n],t,n),a===B&&(a=this._$AH[n]),o||=!z(a)||a!==this._$AH[n],a===G?e=G:e!==G&&(e+=(a??"")+s[n+1]),this._$AH[n]=a}o&&!i&&this.j(e)}j(e){e===G?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,e??"")}}class ie extends re{constructor(){super(...arguments),this.type=3}j(e){this.element[this.name]=e===G?void 0:e}}class se extends re{constructor(){super(...arguments),this.type=4}j(e){this.element.toggleAttribute(this.name,!!e&&e!==G)}}class oe extends re{constructor(e,t,r,i,s){super(e,t,r,i,s),this.type=5}_$AI(e,t=this){if((e=Y(this,e,t,0)??G)===B)return;const r=this._$AH,i=e===G&&r!==G||e.capture!==r.capture||e.once!==r.once||e.passive!==r.passive,s=e!==G&&(r===G||i);i&&this.element.removeEventListener(this.name,this,r),s&&this.element.addEventListener(this.name,this,e),this._$AH=e}handleEvent(e){"function"==typeof this._$AH?this._$AH.call(this.options?.host??this.element,e):this._$AH.handleEvent(e)}}class ne{constructor(e,t,r){this.element=e,this.type=6,this._$AN=void 0,this._$AM=t,this.options=r}get _$AU(){return this._$AM._$AU}_$AI(e){Y(this,e)}}const ae=$.litHtmlPolyfillSupport;ae?.(Z,te),($.litHtmlVersions??=[]).push("3.3.2");const ce=globalThis;
19
+ /**
20
+ * @license
21
+ * Copyright 2017 Google LLC
22
+ * SPDX-License-Identifier: BSD-3-Clause
23
+ */let le=class extends C{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const e=super.createRenderRoot();return this.renderOptions.renderBefore??=e.firstChild,e}update(e){const t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=((e,t,r)=>{const i=r?.renderBefore??t;let s=i._$litPart$;if(void 0===s){const e=r?.renderBefore??null;i._$litPart$=s=new te(t.insertBefore(W(),e),e,void 0,r??{})}return s._$AI(e),s})(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return B}};le._$litElement$=!0,le.finalized=!0,ce.litElementHydrateSupport?.({LitElement:le});const de=ce.litElementPolyfillSupport;de?.({LitElement:le}),(ce.litElementVersions??=[]).push("4.2.2");
24
+ /**
25
+ * @license
26
+ * Copyright 2017 Google LLC
27
+ * SPDX-License-Identifier: BSD-3-Clause
28
+ */
29
+ const he=e=>(t,r)=>{void 0!==r?r.addInitializer(()=>{customElements.define(e,t)}):customElements.define(e,t)},ue={attribute:!0,type:String,converter:x,reflect:!1,hasChanged:S},pe=(e=ue,t,r)=>{const{kind:i,metadata:s}=r;let o=globalThis.litPropertyMetadata.get(s);if(void 0===o&&globalThis.litPropertyMetadata.set(s,o=new Map),"setter"===i&&((e=Object.create(e)).wrapped=!0),o.set(r.name,e),"accessor"===i){const{name:i}=r;return{set(r){const s=t.get.call(this);t.set.call(this,r),this.requestUpdate(i,s,e,!0,r)},init(t){return void 0!==t&&this.C(i,void 0,e,t),t}}}if("setter"===i){const{name:i}=r;return function(r){const s=this[i];t.call(this,r),this.requestUpdate(i,s,e,!0,r)}}throw Error("Unsupported decorator location: "+i)};
30
+ /**
31
+ * @license
32
+ * Copyright 2017 Google LLC
33
+ * SPDX-License-Identifier: BSD-3-Clause
34
+ */function ge(e){return(t,r)=>"object"==typeof r?pe(e,t,r):((e,t,r)=>{const i=t.hasOwnProperty(r);return t.constructor.createProperty(r,e),i?Object.getOwnPropertyDescriptor(t,r):void 0})(e,t,r)}
35
+ /**
36
+ * @license
37
+ * Copyright 2017 Google LLC
38
+ * SPDX-License-Identifier: BSD-3-Clause
39
+ */function fe(e){return ge({...e,state:!0,attribute:!1})}
40
+ /**
41
+ * @license
42
+ * Copyright 2017 Google LLC
43
+ * SPDX-License-Identifier: BSD-3-Clause
44
+ */
45
+ /**
46
+ * @license
47
+ * Copyright 2017 Google LLC
48
+ * SPDX-License-Identifier: BSD-3-Clause
49
+ */
50
+ function me(e,t){return(t,r,i)=>((e,t,r)=>(r.configurable=!0,r.enumerable=!0,Reflect.decorate&&"object"!=typeof t&&Object.defineProperty(e,t,r),r))(t,r,{get(){return(t=>t.renderRoot?.querySelector(e)??null)(this)}})}
51
+ /**
52
+ * @license
53
+ * Copyright 2017 Google LLC
54
+ * SPDX-License-Identifier: BSD-3-Clause
55
+ */const we=1;class ve{constructor(e){}get _$AU(){return this._$AM._$AU}_$AT(e,t,r){this._$Ct=e,this._$AM=t,this._$Ci=r}_$AS(e,t){return this.update(e,t)}update(e,t){return this.render(...t)}}
56
+ /**
57
+ * @license
58
+ * Copyright 2018 Google LLC
59
+ * SPDX-License-Identifier: BSD-3-Clause
60
+ */const ye=(e=>(...t)=>({_$litDirective$:e,values:t}))(class extends ve{constructor(e){if(super(e),e.type!==we||"class"!==e.name||e.strings?.length>2)throw Error("`classMap()` can only be used in the `class` attribute and must be the only part in the attribute.")}render(e){return" "+Object.keys(e).filter(t=>e[t]).join(" ")+" "}update(e,[t]){if(void 0===this.st){this.st=new Set,void 0!==e.strings&&(this.nt=new Set(e.strings.join(" ").split(/\s/).filter(e=>""!==e)));for(const e in t)t[e]&&!this.nt?.has(e)&&this.st.add(e);return this.render(t)}const r=e.element.classList;for(const e of this.st)e in t||(r.remove(e),this.st.delete(e));for(const e in t){const i=!!t[e];i===this.st.has(e)||this.nt?.has(e)||(i?(r.add(e),this.st.add(e)):(r.remove(e),this.st.delete(e)))}return B}}),be=c`
61
+ /**
62
+ * StreamCrafter CSS
63
+ * Wrapped in @layer fw-streamcrafter for cascade isolation.
64
+ * Host app unlayered styles will always take precedence.
65
+ *
66
+ * Import this file in your application:
67
+ * import '@livepeer-frameworks/streamcrafter-core/styles/streamcrafter.css';
68
+ */
69
+
70
+ /* Declare layer upfront for lowest priority */
71
+ @layer fw-streamcrafter;
72
+
73
+ @layer fw-streamcrafter {
74
+ /* =============================================
75
+ Root Container (with scoped CSS variables)
76
+ ============================================= */
77
+ .fw-sc-root {
78
+ /* Tokyo Night color tokens - scoped to component */
79
+ --tn-bg-dark: 240 15% 10%; /* #16161e - Darkest surface */
80
+ --tn-bg: 235 19% 13%; /* #1a1b26 - Main background */
81
+ --tn-bg-highlight: 229 24% 19%; /* #24283b - Elevated surfaces */
82
+
83
+ --tn-fg: 229 73% 86%; /* #c0caf5 - Primary text */
84
+ --tn-fg-dark: 229 35% 75%; /* #a9b1d6 - Secondary text */
85
+ --tn-fg-gutter: 229 24% 31%; /* #3b4261 - Borders, seams */
86
+ --tn-comment: 229 23% 44%; /* #565f89 - Comments, muted */
87
+
88
+ --tn-blue: 225 86% 70%; /* #7aa2f7 - Primary actions */
89
+ --tn-green: 89 51% 61%; /* #9ece6a - Success, live */
90
+ --tn-red: 349 89% 72%; /* #f7768e - Destructive, errors */
91
+ --tn-yellow: 36 66% 64%; /* #e0af68 - Warnings */
92
+ --tn-purple: 264 85% 74%; /* #bb9af7 - Special */
93
+ --tn-cyan: 197 95% 74%; /* #7dcfff - Info */
94
+
95
+ /* Component styles */
96
+ background: hsl(var(--tn-bg-dark));
97
+ border: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
98
+ font-family:
99
+ system-ui,
100
+ -apple-system,
101
+ BlinkMacSystemFont,
102
+ "Segoe UI",
103
+ Roboto,
104
+ sans-serif;
105
+ font-size: 14px;
106
+ line-height: 1.5;
107
+ color: hsl(var(--tn-fg));
108
+ /* Allow root to take full height of parent and manage its children */
109
+ height: 100%;
110
+ min-height: 500px;
111
+ display: flex;
112
+ flex-direction: column;
113
+
114
+ overflow: hidden; /* Prevent content from overflowing the root container */
115
+
116
+ /* Container query support for responsive layout */
117
+ container-type: inline-size;
118
+ container-name: streamcrafter;
119
+ }
120
+
121
+ /* Dev mode - flex layout for side panel */
122
+ .fw-sc-root--devmode {
123
+ display: flex;
124
+ flex-direction: row;
125
+ }
126
+
127
+ /* Main content wrapper */
128
+ .fw-sc-main {
129
+ display: flex;
130
+ flex-direction: column;
131
+ min-width: 0;
132
+ /* Occupy remaining vertical space in root */
133
+ flex: 1;
134
+ overflow: hidden; /* Manage internal overflow */
135
+ }
136
+
137
+ /* Content area (preview + mixer) - responsive layout */
138
+ .fw-sc-content {
139
+ display: flex;
140
+ flex-direction: column;
141
+ flex: 1; /* Occupy remaining vertical space in fw-sc-main */
142
+ min-height: 0; /* Allow flex item to shrink below content size */
143
+ }
144
+
145
+ /* Preview wrapper for flex sizing */
146
+ .fw-sc-preview-wrapper {
147
+ display: flex;
148
+ flex-direction: column;
149
+ }
150
+
151
+ /* Mixer panel class for responsive targeting */
152
+ .fw-sc-mixer {
153
+ /* Default: takes full width below preview */
154
+ }
155
+
156
+ /* =============================================
157
+ Responsive Layout: Mixer on Right (Wide Screens)
158
+ Uses container queries for container-aware layout
159
+ ============================================= */
160
+ @container streamcrafter (min-width: 600px) {
161
+ .fw-sc-content {
162
+ flex-direction: row;
163
+ align-items: stretch; /* Ensure full height */
164
+ }
165
+
166
+ .fw-sc-preview-wrapper {
167
+ flex: 1;
168
+ min-width: 0;
169
+ /* Center preview vertically if needed */
170
+ display: flex;
171
+ flex-direction: column;
172
+ justify-content: center;
173
+ background: black;
174
+ }
175
+
176
+ .fw-sc-mixer {
177
+ width: 320px; /* Slightly wider for better ergonomics */
178
+ flex-shrink: 0;
179
+ border-left: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
180
+ border-bottom: none;
181
+ background: hsl(var(--tn-bg));
182
+ display: flex;
183
+ flex-direction: column;
184
+ max-height: none;
185
+ overflow-y: auto;
186
+ transition: width 0.2s ease-out; /* Smooth collapse transition */
187
+ }
188
+
189
+ /* Collapsed state for sidebar mixer */
190
+ .fw-sc-mixer.fw-sc-section--collapsed {
191
+ width: 48px; /* Collapsed width */
192
+ overflow-x: hidden; /* Hide overflowing content */
193
+ }
194
+
195
+ .fw-sc-mixer.fw-sc-section--collapsed .fw-sc-section-header {
196
+ justify-content: center; /* Center icon */
197
+ }
198
+
199
+ .fw-sc-mixer.fw-sc-section--collapsed .fw-sc-section-header span {
200
+ display: none; /* Hide text */
201
+ }
202
+
203
+ .fw-sc-mixer.fw-sc-section--collapsed .fw-sc-section-header svg {
204
+ transform: rotate(0deg) !important; /* Reset chevron rotation */
205
+ }
206
+
207
+ .fw-sc-mixer.fw-sc-section--collapsed .fw-sc-sources {
208
+ display: none; /* Hide source list content */
209
+ }
210
+
211
+ /* Redesign Source Items for Sidebar Context */
212
+ .fw-sc-mixer .fw-sc-source {
213
+ display: flex;
214
+ align-items: center;
215
+ gap: 0.5rem;
216
+ padding: 0.375rem 0.5rem;
217
+ background: hsl(var(--tn-bg-highlight) / 0.1);
218
+ border-bottom: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
219
+ transition: background 0.2s;
220
+ height: 48px;
221
+ }
222
+
223
+ .fw-sc-mixer .fw-sc-source:hover {
224
+ background: hsl(var(--tn-bg-highlight) / 0.3);
225
+ }
226
+
227
+ /* Icon */
228
+ .fw-sc-mixer .fw-sc-source-icon {
229
+ flex: 0 0 auto;
230
+ }
231
+
232
+ /* Name + Type */
233
+ .fw-sc-mixer .fw-sc-source-info {
234
+ flex: 1;
235
+ min-width: 0; /* Enable truncation */
236
+ display: flex;
237
+ flex-direction: column;
238
+ gap: 0;
239
+ }
240
+
241
+ .fw-sc-mixer .fw-sc-source-label {
242
+ font-size: 0.8rem;
243
+ font-weight: 600;
244
+ }
245
+
246
+ /* Hide the source type in sidebar to save space */
247
+ .fw-sc-mixer .fw-sc-source-type {
248
+ display: none;
249
+ }
250
+
251
+ /* Controls */
252
+ .fw-sc-mixer .fw-sc-source-controls {
253
+ display: flex;
254
+ align-items: center;
255
+ gap: 0.25rem;
256
+ margin: 0;
257
+ width: auto;
258
+ }
259
+
260
+ /* Volume Slider fixed width in compact mode */
261
+ .fw-sc-mixer .fw-sc-volume-slider {
262
+ width: 60px;
263
+ flex: 0 0 auto;
264
+ height: 6px;
265
+ }
266
+
267
+ /* Hide the percentage text in this compact view */
268
+ .fw-sc-mixer .fw-sc-volume-label {
269
+ display: none;
270
+ }
271
+ }
272
+
273
+ /* Advanced Panel styling - matches Player DevModePanel */
274
+ .fw-sc-advanced-panel {
275
+ background: hsl(var(--tn-bg));
276
+ border-left: 1px solid hsl(var(--tn-fg-gutter) / 0.5);
277
+ width: 280px;
278
+ flex-shrink: 0;
279
+ display: flex;
280
+ flex-direction: column;
281
+ height: 100%;
282
+ overflow: hidden;
283
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
284
+ font-size: 12px;
285
+ z-index: 40;
286
+ }
287
+
288
+ /* =============================================
289
+ Header Zone
290
+ ============================================= */
291
+ .fw-sc-header {
292
+ display: flex;
293
+ align-items: center;
294
+ justify-content: space-between;
295
+ padding: 0.5rem 1rem;
296
+ border-bottom: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
297
+ background: hsl(var(--tn-bg-highlight) / 0.5);
298
+ }
299
+
300
+ .fw-sc-header-title {
301
+ font-weight: 600;
302
+ text-transform: uppercase;
303
+ letter-spacing: 0.05em;
304
+ font-size: 0.75rem;
305
+ color: hsl(var(--tn-fg-dark));
306
+ }
307
+
308
+ .fw-sc-header-status {
309
+ display: flex;
310
+ align-items: center;
311
+ gap: 0.5rem;
312
+ }
313
+
314
+ .fw-sc-header-actions {
315
+ display: flex;
316
+ align-items: center;
317
+ gap: 0.25rem;
318
+ }
319
+
320
+ /* Header Button - visible gear/wrench like Player */
321
+ .fw-sc-header-btn {
322
+ display: flex;
323
+ align-items: center;
324
+ justify-content: center;
325
+ width: 28px;
326
+ height: 28px;
327
+ padding: 0;
328
+ border: none;
329
+ border-radius: 4px;
330
+ background: transparent;
331
+ color: hsl(var(--tn-comment));
332
+ cursor: pointer;
333
+ transition: all 0.15s ease;
334
+ }
335
+
336
+ .fw-sc-header-btn:hover {
337
+ background: hsl(var(--tn-bg-highlight));
338
+ color: hsl(var(--tn-fg));
339
+ }
340
+
341
+ .fw-sc-header-btn--active {
342
+ background: hsl(var(--tn-blue) / 0.2);
343
+ color: hsl(var(--tn-blue));
344
+ }
345
+
346
+ .fw-sc-header-btn--active:hover {
347
+ background: hsl(var(--tn-blue) / 0.3);
348
+ color: hsl(var(--tn-blue));
349
+ }
350
+
351
+ /* =============================================
352
+ Video Preview (Flush - No Padding)
353
+ ============================================= */
354
+ .fw-sc-preview {
355
+ position: relative;
356
+ /* Use flex-grow instead of aspect-ratio to fill available height */
357
+ flex: 1;
358
+ background: black;
359
+ overflow: hidden;
360
+ display: flex; /* Make it a flex container for the video */
361
+ align-items: center;
362
+ justify-content: center;
363
+ }
364
+
365
+ .fw-sc-preview video {
366
+ width: 100%;
367
+ height: 100%;
368
+ object-fit: contain; /* Ensure video fits within the preview area */
369
+ }
370
+
371
+ .fw-sc-preview-placeholder {
372
+ position: absolute;
373
+ inset: 0;
374
+ display: flex;
375
+ flex-direction: column;
376
+ align-items: center;
377
+ justify-content: center;
378
+ color: hsl(var(--tn-comment));
379
+ gap: 0.5rem;
380
+ }
381
+
382
+ .fw-sc-preview-placeholder svg {
383
+ width: 48px;
384
+ height: 48px;
385
+ opacity: 0.5;
386
+ }
387
+
388
+ /* =============================================
389
+ Live Badge Overlay
390
+ ============================================= */
391
+ .fw-sc-live-badge {
392
+ position: absolute;
393
+ top: 1rem;
394
+ right: 1rem;
395
+ display: flex;
396
+ align-items: center;
397
+ gap: 0.375rem;
398
+ padding: 0.25rem 0.5rem;
399
+ background: hsl(var(--tn-red));
400
+ color: white;
401
+ font-size: 0.7rem;
402
+ font-weight: 700;
403
+ text-transform: uppercase;
404
+ letter-spacing: 0.05em;
405
+ }
406
+
407
+ .fw-sc-live-badge::before {
408
+ content: "";
409
+ width: 0.5rem;
410
+ height: 0.5rem;
411
+ background: white;
412
+ border-radius: 50%;
413
+ animation: fw-sc-pulse 1.5s infinite;
414
+ }
415
+
416
+ @keyframes fw-sc-pulse {
417
+ 0%,
418
+ 100% {
419
+ opacity: 1;
420
+ }
421
+ 50% {
422
+ opacity: 0.5;
423
+ }
424
+ }
425
+
426
+ /* =============================================
427
+ Status Overlay
428
+ ============================================= */
429
+ .fw-sc-status-overlay {
430
+ position: absolute;
431
+ inset: 0;
432
+ display: flex;
433
+ flex-direction: column;
434
+ align-items: center;
435
+ justify-content: center;
436
+ background: hsl(var(--tn-bg-dark) / 0.9);
437
+ gap: 1rem;
438
+ }
439
+
440
+ .fw-sc-status-spinner {
441
+ width: 32px;
442
+ height: 32px;
443
+ border: 3px solid hsl(var(--tn-fg-gutter) / 0.3);
444
+ border-top-color: hsl(var(--tn-blue));
445
+ border-radius: 50%;
446
+ animation: fw-sc-spin 1s linear infinite;
447
+ }
448
+
449
+ @keyframes fw-sc-spin {
450
+ to {
451
+ transform: rotate(360deg);
452
+ }
453
+ }
454
+
455
+ .fw-sc-status-text {
456
+ font-size: 0.875rem;
457
+ color: hsl(var(--tn-fg-dark));
458
+ }
459
+
460
+ /* =============================================
461
+ VU Meter
462
+ ============================================= */
463
+ .fw-sc-vu-meter {
464
+ height: 8px;
465
+ background: hsl(var(--tn-bg-highlight));
466
+ position: relative;
467
+ overflow: hidden;
468
+ }
469
+
470
+ .fw-sc-vu-meter-fill {
471
+ height: 100%;
472
+ background: linear-gradient(
473
+ to right,
474
+ hsl(var(--tn-green)) 0%,
475
+ hsl(var(--tn-green)) 60%,
476
+ hsl(var(--tn-yellow)) 80%,
477
+ hsl(var(--tn-red)) 100%
478
+ );
479
+ transition: width 50ms ease-out;
480
+ }
481
+
482
+ .fw-sc-vu-meter-peak {
483
+ position: absolute;
484
+ top: 0;
485
+ height: 100%;
486
+ width: 2px;
487
+ background: hsl(var(--tn-fg));
488
+ transition: left 50ms ease-out;
489
+ }
490
+
491
+ /* Vertical VU Meter variant */
492
+ .fw-sc-vu-meter--vertical {
493
+ width: 4px;
494
+ height: 100%;
495
+ background: hsl(var(--tn-bg-highlight));
496
+ }
497
+
498
+ .fw-sc-vu-meter--vertical .fw-sc-vu-meter-fill {
499
+ width: 100%;
500
+ background: linear-gradient(
501
+ to top,
502
+ hsl(var(--tn-green)) 0%,
503
+ hsl(var(--tn-green)) 60%,
504
+ hsl(var(--tn-yellow)) 80%,
505
+ hsl(var(--tn-red)) 100%
506
+ );
507
+ transition: height 50ms ease-out;
508
+ }
509
+
510
+ /* =============================================
511
+ Section (Collapsible Areas)
512
+ ============================================= */
513
+ .fw-sc-section {
514
+ border-bottom: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
515
+ }
516
+
517
+ .fw-sc-section:last-child {
518
+ border-bottom: none;
519
+ }
520
+
521
+ .fw-sc-section-header {
522
+ display: flex;
523
+ align-items: center;
524
+ justify-content: space-between;
525
+ padding: 0.5rem 1rem;
526
+ font-size: 0.7rem;
527
+ font-weight: 600;
528
+ text-transform: uppercase;
529
+ letter-spacing: 0.05em;
530
+ color: hsl(var(--tn-comment));
531
+ cursor: pointer;
532
+ user-select: none;
533
+ background: hsl(var(--tn-bg-highlight) / 0.3);
534
+ }
535
+
536
+ .fw-sc-section-header:hover {
537
+ background: hsl(var(--tn-bg-highlight) / 0.5);
538
+ }
539
+
540
+ .fw-sc-section-header svg {
541
+ width: 14px;
542
+ height: 14px;
543
+ transition: transform 0.2s;
544
+ }
545
+
546
+ .fw-sc-section--collapsed .fw-sc-section-header svg {
547
+ transform: rotate(-90deg);
548
+ }
549
+
550
+ .fw-sc-section-body {
551
+ padding: 0.75rem 1rem;
552
+ }
553
+
554
+ .fw-sc-section-body--flush {
555
+ padding: 0;
556
+ }
557
+
558
+ /* =============================================
559
+ Source List
560
+ ============================================= */
561
+ .fw-sc-sources {
562
+ display: flex;
563
+ flex-direction: column;
564
+ }
565
+
566
+ .fw-sc-source {
567
+ display: flex;
568
+ align-items: center;
569
+ padding: 0.5rem 1rem;
570
+ gap: 0.5rem;
571
+ border-bottom: 1px solid hsl(var(--tn-fg-gutter) / 0.2);
572
+ }
573
+
574
+ .fw-sc-source:last-child {
575
+ border-bottom: none;
576
+ }
577
+
578
+ .fw-sc-source--hidden {
579
+ opacity: 0.5;
580
+ background: hsl(var(--tn-bg-dark) / 0.3);
581
+ }
582
+
583
+ .fw-sc-source-icon {
584
+ width: 24px;
585
+ height: 24px;
586
+ display: flex;
587
+ align-items: center;
588
+ justify-content: center;
589
+ color: hsl(var(--tn-comment));
590
+ }
591
+
592
+ .fw-sc-source-info {
593
+ flex: 1;
594
+ min-width: 0;
595
+ }
596
+
597
+ .fw-sc-source-label {
598
+ font-size: 0.875rem;
599
+ font-weight: 500;
600
+ overflow: hidden;
601
+ text-overflow: ellipsis;
602
+ white-space: nowrap;
603
+ display: flex;
604
+ align-items: center;
605
+ gap: 0.5rem;
606
+ }
607
+
608
+ .fw-sc-primary-badge {
609
+ font-size: 0.55rem;
610
+ font-weight: 700;
611
+ text-transform: uppercase;
612
+ letter-spacing: 0.05em;
613
+ padding: 0.125rem 0.375rem;
614
+ background: hsl(var(--tn-green) / 0.2);
615
+ color: hsl(var(--tn-green));
616
+ border-radius: 2px;
617
+ }
618
+
619
+ .fw-sc-source-type {
620
+ font-size: 0.7rem;
621
+ color: hsl(var(--tn-comment));
622
+ text-transform: uppercase;
623
+ }
624
+
625
+ .fw-sc-source-controls {
626
+ display: flex;
627
+ align-items: center;
628
+ gap: 0.25rem;
629
+ flex-wrap: wrap; /* Allow items to wrap */
630
+ justify-content: flex-end; /* Align to the right when wrapped */
631
+ }
632
+
633
+ /* =============================================
634
+ Icon Buttons (Small Controls)
635
+ ============================================= */
636
+ .fw-sc-icon-btn {
637
+ width: 28px;
638
+ height: 28px;
639
+ display: flex;
640
+ align-items: center;
641
+ justify-content: center;
642
+ padding: 0;
643
+ border: none;
644
+ border-radius: 4px;
645
+ background: transparent;
646
+ color: hsl(var(--tn-comment));
647
+ cursor: pointer;
648
+ transition:
649
+ background 0.15s,
650
+ color 0.15s;
651
+ }
652
+
653
+ .fw-sc-icon-btn:hover {
654
+ background: hsl(var(--tn-bg-highlight) / 0.5);
655
+ color: hsl(var(--tn-fg));
656
+ }
657
+
658
+ .fw-sc-icon-btn:disabled {
659
+ opacity: 0.5;
660
+ cursor: not-allowed;
661
+ }
662
+
663
+ .fw-sc-icon-btn--active {
664
+ color: hsl(var(--tn-blue));
665
+ }
666
+
667
+ .fw-sc-icon-btn--inactive {
668
+ opacity: 0.45;
669
+ color: hsl(var(--tn-comment));
670
+ }
671
+
672
+ .fw-sc-icon-btn--primary {
673
+ color: hsl(var(--tn-green));
674
+ }
675
+
676
+ .fw-sc-icon-btn--primary:disabled {
677
+ color: hsl(var(--tn-green));
678
+ opacity: 1;
679
+ }
680
+
681
+ .fw-sc-icon-btn--destructive:hover {
682
+ color: hsl(var(--tn-red));
683
+ }
684
+
685
+ .fw-sc-icon-btn--muted {
686
+ color: hsl(var(--tn-comment) / 0.5);
687
+ }
688
+
689
+ .fw-sc-icon-btn svg {
690
+ width: 16px;
691
+ height: 16px;
692
+ }
693
+
694
+ /* =============================================
695
+ Settings Panel
696
+ ============================================= */
697
+ .fw-sc-settings {
698
+ display: flex;
699
+ flex-direction: column;
700
+ gap: 0.5rem;
701
+ }
702
+
703
+ .fw-sc-setting-row {
704
+ display: flex;
705
+ align-items: center;
706
+ justify-content: space-between;
707
+ gap: 1rem;
708
+ }
709
+
710
+ .fw-sc-setting-label {
711
+ font-size: 0.875rem;
712
+ color: hsl(var(--tn-fg-dark));
713
+ }
714
+
715
+ .fw-sc-select {
716
+ padding: 0.25rem 0.5rem;
717
+ border: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
718
+ border-radius: 4px;
719
+ background: hsl(var(--tn-bg-highlight));
720
+ color: hsl(var(--tn-fg));
721
+ font-size: 0.875rem;
722
+ cursor: pointer;
723
+ }
724
+
725
+ .fw-sc-select:focus {
726
+ outline: none;
727
+ border-color: hsl(var(--tn-blue));
728
+ }
729
+
730
+ /* =============================================
731
+ Action Bar (Bottom Controls)
732
+ ============================================= */
733
+ .fw-sc-actions {
734
+ display: flex;
735
+ border-top: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
736
+ }
737
+
738
+ .fw-sc-actions button {
739
+ padding: 1rem;
740
+ border: none;
741
+ border-radius: 0;
742
+ background: transparent;
743
+ color: hsl(var(--tn-fg));
744
+ font-size: 0.875rem;
745
+ font-weight: 500;
746
+ cursor: pointer;
747
+ transition: background 0.15s;
748
+ border-right: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
749
+ display: flex;
750
+ align-items: center;
751
+ justify-content: center;
752
+ gap: 0.25rem;
753
+ }
754
+
755
+ .fw-sc-actions button:last-child {
756
+ border-right: none;
757
+ }
758
+
759
+ .fw-sc-actions button:hover:not(:disabled) {
760
+ background: hsl(var(--tn-bg-highlight) / 0.5);
761
+ }
762
+
763
+ .fw-sc-actions button:disabled {
764
+ color: hsl(var(--tn-comment));
765
+ cursor: not-allowed;
766
+ }
767
+
768
+ .fw-sc-actions button svg {
769
+ width: 18px;
770
+ height: 18px;
771
+ }
772
+
773
+ /* Secondary actions (Camera, Screen, Settings) - smaller */
774
+ .fw-sc-action-secondary {
775
+ flex: 0 0 auto;
776
+ }
777
+
778
+ .fw-sc-action-secondary--active {
779
+ background: hsl(var(--tn-blue) / 0.2) !important;
780
+ color: hsl(var(--tn-blue)) !important;
781
+ }
782
+
783
+ /* Settings icon rotation on hover */
784
+ .fw-sc-action-secondary .settings-icon-wrapper {
785
+ display: inline-flex;
786
+ transition: transform 0.2s ease;
787
+ }
788
+
789
+ .fw-sc-action-secondary:hover .settings-icon-wrapper {
790
+ transform: rotate(90deg);
791
+ }
792
+
793
+ /* Primary action (Go Live) - takes remaining space */
794
+ .fw-sc-action-primary {
795
+ flex: 1;
796
+ font-weight: 600 !important;
797
+ background: hsl(var(--tn-red)) !important;
798
+ color: white !important;
799
+ }
800
+
801
+ .fw-sc-action-primary:hover:not(:disabled) {
802
+ background: hsl(var(--tn-red) / 0.8) !important;
803
+ }
804
+
805
+ .fw-sc-action-primary:disabled {
806
+ background: hsl(var(--tn-bg-highlight)) !important;
807
+ color: hsl(var(--tn-comment)) !important;
808
+ }
809
+
810
+ /* Stop action (when streaming) */
811
+ .fw-sc-action-stop {
812
+ background: hsl(var(--tn-bg-highlight)) !important;
813
+ }
814
+
815
+ .fw-sc-action-stop:hover:not(:disabled) {
816
+ background: hsl(var(--tn-red) / 0.3) !important;
817
+ }
818
+
819
+ /* =============================================
820
+ Status Badge
821
+ ============================================= */
822
+ .fw-sc-badge {
823
+ display: inline-flex;
824
+ align-items: center;
825
+ gap: 0.25rem;
826
+ padding: 0.125rem 0.5rem;
827
+ font-size: 0.7rem;
828
+ font-weight: 600;
829
+ text-transform: uppercase;
830
+ letter-spacing: 0.025em;
831
+ border-radius: 2px;
832
+ }
833
+
834
+ .fw-sc-badge--idle {
835
+ background: hsl(var(--tn-bg-highlight));
836
+ color: hsl(var(--tn-comment));
837
+ }
838
+
839
+ .fw-sc-badge--ready {
840
+ background: hsl(var(--tn-blue) / 0.2);
841
+ color: hsl(var(--tn-blue));
842
+ }
843
+
844
+ .fw-sc-badge--live {
845
+ background: hsl(var(--tn-red));
846
+ color: white;
847
+ }
848
+
849
+ .fw-sc-badge--connecting {
850
+ background: hsl(var(--tn-yellow) / 0.2);
851
+ color: hsl(var(--tn-yellow));
852
+ }
853
+
854
+ .fw-sc-badge--error {
855
+ background: hsl(var(--tn-red) / 0.2);
856
+ color: hsl(var(--tn-red));
857
+ }
858
+
859
+ /* =============================================
860
+ Volume Slider
861
+ ============================================= */
862
+ .fw-sc-volume-label {
863
+ font-size: 0.65rem;
864
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
865
+ color: hsl(var(--tn-fg-dark));
866
+ min-width: 32px;
867
+ text-align: right;
868
+ font-variant-numeric: tabular-nums;
869
+ }
870
+
871
+ .fw-sc-volume-slider {
872
+ width: 80px; /* Slightly wider by default */
873
+ height: 6px;
874
+ -webkit-appearance: none;
875
+ appearance: none;
876
+ background: hsl(var(--tn-bg-highlight));
877
+ border-radius: 3px;
878
+ cursor: pointer;
879
+ position: relative;
880
+ transition: opacity 0.2s;
881
+ }
882
+
883
+ .fw-sc-volume-slider:hover {
884
+ opacity: 0.9;
885
+ }
886
+
887
+ /* Track fill simulation (Note: WebKit requires JS to update background-size for true fill before thumb,
888
+ but we can use a gradient trick if value is known, or just keep the clean track look) */
889
+
890
+ .fw-sc-volume-slider::-webkit-slider-thumb {
891
+ -webkit-appearance: none;
892
+ width: 14px;
893
+ height: 14px;
894
+ border-radius: 50%;
895
+ background: hsl(var(--tn-fg));
896
+ border: 2px solid hsl(var(--tn-bg)); /* Ring effect */
897
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
898
+ cursor: pointer;
899
+ margin-top: -4px; /* Center thumb on 6px track */
900
+ transition: transform 0.1s;
901
+ }
902
+
903
+ .fw-sc-volume-slider:hover::-webkit-slider-thumb {
904
+ transform: scale(1.1);
905
+ background: white;
906
+ }
907
+
908
+ .fw-sc-volume-slider::-moz-range-thumb {
909
+ width: 14px;
910
+ height: 14px;
911
+ border-radius: 50%;
912
+ background: hsl(var(--tn-fg));
913
+ border: 2px solid hsl(var(--tn-bg));
914
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
915
+ cursor: pointer;
916
+ transition: transform 0.1s;
917
+ }
918
+
919
+ .fw-sc-volume-slider:hover::-moz-range-thumb {
920
+ transform: scale(1.1);
921
+ background: white;
922
+ }
923
+
924
+ /* Boosted state (>100%) - Add a glow */
925
+ .fw-sc-volume-slider--boosted::-webkit-slider-thumb {
926
+ background: hsl(var(--tn-yellow));
927
+ box-shadow: 0 0 5px hsl(var(--tn-yellow) / 0.5);
928
+ }
929
+
930
+ .fw-sc-volume-slider--boosted::-moz-range-thumb {
931
+ background: hsl(var(--tn-yellow));
932
+ box-shadow: 0 0 5px hsl(var(--tn-yellow) / 0.5);
933
+ }
934
+
935
+ /* =============================================
936
+ Error State
937
+ ============================================= */
938
+ .fw-sc-error {
939
+ padding: 1rem;
940
+ background: hsl(var(--tn-red) / 0.1);
941
+ border-left: 3px solid hsl(var(--tn-red));
942
+ }
943
+
944
+ .fw-sc-error-title {
945
+ font-weight: 600;
946
+ color: hsl(var(--tn-red));
947
+ margin-bottom: 0.25rem;
948
+ }
949
+
950
+ .fw-sc-error-message {
951
+ font-size: 0.875rem;
952
+ color: hsl(var(--tn-fg-dark));
953
+ }
954
+
955
+ /* =============================================
956
+ Utility Classes
957
+ ============================================= */
958
+ .fw-sc-hidden {
959
+ display: none !important;
960
+ }
961
+
962
+ .fw-sc-sr-only {
963
+ position: absolute;
964
+ width: 1px;
965
+ height: 1px;
966
+ padding: 0;
967
+ margin: -1px;
968
+ overflow: hidden;
969
+ clip: rect(0, 0, 0, 0);
970
+ white-space: nowrap;
971
+ border: 0;
972
+ }
973
+
974
+ /* =============================================
975
+ Settings Dropdown
976
+ ============================================= */
977
+ .fw-sc-settings-dropdown {
978
+ position: absolute;
979
+ top: calc(100% + 4px);
980
+ right: 0;
981
+ min-width: 200px;
982
+ background: hsl(var(--tn-bg-dark));
983
+ border: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
984
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
985
+ z-index: 50;
986
+ overflow: hidden;
987
+ }
988
+
989
+ .fw-sc-dropdown-section {
990
+ border-bottom: 1px solid hsl(var(--tn-fg-gutter) / 0.2);
991
+ }
992
+
993
+ .fw-sc-dropdown-section:last-child {
994
+ border-bottom: none;
995
+ }
996
+
997
+ .fw-sc-dropdown-label {
998
+ padding: 0.5rem 0.75rem 0.25rem;
999
+ font-size: 0.65rem;
1000
+ font-weight: 600;
1001
+ text-transform: uppercase;
1002
+ letter-spacing: 0.05em;
1003
+ color: hsl(var(--tn-comment));
1004
+ }
1005
+
1006
+ .fw-sc-dropdown-options {
1007
+ display: flex;
1008
+ flex-direction: column;
1009
+ padding: 0 0.25rem 0.5rem;
1010
+ }
1011
+
1012
+ .fw-sc-dropdown-option {
1013
+ display: flex;
1014
+ flex-direction: column;
1015
+ align-items: flex-start;
1016
+ gap: 0;
1017
+ padding: 0.375rem 0.5rem;
1018
+ margin: 0;
1019
+ border: none;
1020
+ border-radius: 4px;
1021
+ background: transparent;
1022
+ color: hsl(var(--tn-fg));
1023
+ cursor: pointer;
1024
+ text-align: left;
1025
+ width: 100%;
1026
+ transition: background 0.15s;
1027
+ }
1028
+
1029
+ .fw-sc-dropdown-option:hover:not(:disabled) {
1030
+ background: hsl(var(--tn-bg-highlight) / 0.5);
1031
+ }
1032
+
1033
+ .fw-sc-dropdown-option:disabled {
1034
+ opacity: 0.5;
1035
+ cursor: not-allowed;
1036
+ }
1037
+
1038
+ .fw-sc-dropdown-option--active {
1039
+ background: hsl(var(--tn-blue) / 0.2);
1040
+ }
1041
+
1042
+ .fw-sc-dropdown-option--active:hover:not(:disabled) {
1043
+ background: hsl(var(--tn-blue) / 0.3);
1044
+ }
1045
+
1046
+ .fw-sc-dropdown-option-label {
1047
+ font-size: 0.875rem;
1048
+ font-weight: 500;
1049
+ }
1050
+
1051
+ .fw-sc-dropdown-option-desc {
1052
+ font-size: 0.7rem;
1053
+ color: hsl(var(--tn-comment));
1054
+ }
1055
+
1056
+ .fw-sc-dropdown-info {
1057
+ padding: 0.25rem 0.75rem 0.5rem;
1058
+ }
1059
+
1060
+ .fw-sc-dropdown-info-row {
1061
+ display: flex;
1062
+ justify-content: space-between;
1063
+ align-items: center;
1064
+ padding: 0.25rem 0;
1065
+ font-size: 0.75rem;
1066
+ color: hsl(var(--tn-fg-dark));
1067
+ }
1068
+
1069
+ .fw-sc-dropdown-info-value {
1070
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
1071
+ color: hsl(var(--tn-fg));
1072
+ }
1073
+
1074
+ /* =============================================
1075
+ Context Menu
1076
+ ============================================= */
1077
+ .fw-sc-context-menu {
1078
+ position: fixed;
1079
+ min-width: 160px;
1080
+ background: hsl(var(--tn-bg-dark));
1081
+ border: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
1082
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
1083
+ z-index: 100;
1084
+ overflow: hidden;
1085
+ }
1086
+
1087
+ .fw-sc-context-menu-item {
1088
+ display: flex;
1089
+ align-items: center;
1090
+ gap: 0.5rem;
1091
+ padding: 0.5rem 0.75rem;
1092
+ font-size: 0.875rem;
1093
+ color: hsl(var(--tn-fg));
1094
+ cursor: pointer;
1095
+ background: transparent;
1096
+ border: none;
1097
+ width: 100%;
1098
+ text-align: left;
1099
+ transition:
1100
+ background 0.15s,
1101
+ color 0.15s;
1102
+ }
1103
+
1104
+ .fw-sc-context-menu-item:hover {
1105
+ background: hsl(var(--tn-bg-highlight) / 0.7);
1106
+ color: hsl(var(--tn-fg));
1107
+ }
1108
+
1109
+ .fw-sc-context-menu-item--destructive {
1110
+ color: hsl(var(--tn-red));
1111
+ }
1112
+
1113
+ .fw-sc-context-menu-item--destructive:hover {
1114
+ background: hsl(var(--tn-red) / 0.15);
1115
+ color: hsl(var(--tn-red));
1116
+ }
1117
+
1118
+ .fw-sc-context-menu-separator {
1119
+ height: 1px;
1120
+ background: hsl(var(--tn-fg-gutter) / 0.3);
1121
+ margin: 0.25rem 0;
1122
+ }
1123
+
1124
+ .fw-sc-context-menu-label {
1125
+ padding: 0.375rem 0.75rem;
1126
+ font-size: 0.65rem;
1127
+ font-weight: 600;
1128
+ text-transform: uppercase;
1129
+ letter-spacing: 0.05em;
1130
+ color: hsl(var(--tn-comment));
1131
+ }
1132
+
1133
+ /* =============================================
1134
+ Compositor Controls (Phase 3)
1135
+ ============================================= */
1136
+ .fw-sc-compositor-controls {
1137
+ display: flex;
1138
+ flex-direction: column;
1139
+ border-bottom: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
1140
+ }
1141
+
1142
+ .fw-sc-compositor-controls--disabled,
1143
+ .fw-sc-compositor-controls--loading {
1144
+ padding: 1rem;
1145
+ }
1146
+
1147
+ .fw-sc-compositor-enable {
1148
+ text-align: center;
1149
+ }
1150
+
1151
+ .fw-sc-compositor-enable p {
1152
+ font-size: 0.875rem;
1153
+ color: hsl(var(--tn-fg-dark));
1154
+ margin-bottom: 0.75rem;
1155
+ }
1156
+
1157
+ .fw-sc-compositor-enable-btn {
1158
+ padding: 0.5rem 1rem;
1159
+ border: 1px solid hsl(var(--tn-blue));
1160
+ border-radius: 4px;
1161
+ background: hsl(var(--tn-blue) / 0.1);
1162
+ color: hsl(var(--tn-blue));
1163
+ font-size: 0.875rem;
1164
+ font-weight: 500;
1165
+ cursor: pointer;
1166
+ transition: background 0.15s;
1167
+ }
1168
+
1169
+ .fw-sc-compositor-enable-btn:hover {
1170
+ background: hsl(var(--tn-blue) / 0.2);
1171
+ }
1172
+
1173
+ .fw-sc-compositor-loading {
1174
+ text-align: center;
1175
+ color: hsl(var(--tn-comment));
1176
+ font-size: 0.875rem;
1177
+ }
1178
+
1179
+ .fw-sc-compositor-header {
1180
+ display: flex;
1181
+ align-items: center;
1182
+ justify-content: space-between;
1183
+ padding: 0.5rem 1rem;
1184
+ border-bottom: 1px solid hsl(var(--tn-fg-gutter) / 0.2);
1185
+ background: hsl(var(--tn-bg-highlight) / 0.3);
1186
+ }
1187
+
1188
+ .fw-sc-compositor-info {
1189
+ display: flex;
1190
+ align-items: center;
1191
+ gap: 0.75rem;
1192
+ }
1193
+
1194
+ .fw-sc-compositor-title {
1195
+ font-size: 0.7rem;
1196
+ font-weight: 600;
1197
+ text-transform: uppercase;
1198
+ letter-spacing: 0.05em;
1199
+ color: hsl(var(--tn-comment));
1200
+ }
1201
+
1202
+ .fw-sc-compositor-renderer {
1203
+ font-size: 0.7rem;
1204
+ color: hsl(var(--tn-fg-dark));
1205
+ }
1206
+
1207
+ .fw-sc-compositor-stats-inline {
1208
+ font-size: 0.65rem;
1209
+ color: hsl(var(--tn-fg-gutter));
1210
+ font-family:
1211
+ ui-monospace,
1212
+ SFMono-Regular,
1213
+ SF Mono,
1214
+ Menlo,
1215
+ Consolas,
1216
+ monospace;
1217
+ margin-left: 0.5rem;
1218
+ }
1219
+
1220
+ .fw-sc-compositor-disable-btn {
1221
+ width: 24px;
1222
+ height: 24px;
1223
+ display: flex;
1224
+ align-items: center;
1225
+ justify-content: center;
1226
+ padding: 0;
1227
+ border: none;
1228
+ border-radius: 4px;
1229
+ background: transparent;
1230
+ color: hsl(var(--tn-comment));
1231
+ cursor: pointer;
1232
+ transition: all 0.15s;
1233
+ }
1234
+
1235
+ .fw-sc-compositor-disable-btn:hover {
1236
+ background: hsl(var(--tn-red) / 0.2);
1237
+ color: hsl(var(--tn-red));
1238
+ }
1239
+
1240
+ .fw-sc-compositor-stats {
1241
+ display: flex;
1242
+ align-items: center;
1243
+ gap: 1rem;
1244
+ padding: 0.25rem 1rem;
1245
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
1246
+ font-size: 0.7rem;
1247
+ color: hsl(var(--tn-fg-dark));
1248
+ background: hsl(var(--tn-bg-dark) / 0.5);
1249
+ }
1250
+
1251
+ .fw-sc-stat {
1252
+ color: hsl(var(--tn-cyan));
1253
+ }
1254
+
1255
+ /* =============================================
1256
+ Compositor Actions (Header Right Side)
1257
+ ============================================= */
1258
+ .fw-sc-compositor-actions {
1259
+ display: flex;
1260
+ align-items: center;
1261
+ gap: 0.5rem;
1262
+ }
1263
+
1264
+ .fw-sc-scaling-mode-select {
1265
+ padding: 0.25rem 0.5rem;
1266
+ font-size: 0.7rem;
1267
+ background: hsl(var(--tn-bg-dark));
1268
+ border: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
1269
+ border-radius: 4px;
1270
+ color: hsl(var(--tn-fg));
1271
+ cursor: pointer;
1272
+ }
1273
+
1274
+ .fw-sc-scaling-mode-select:hover {
1275
+ border-color: hsl(var(--tn-fg-gutter) / 0.5);
1276
+ }
1277
+
1278
+ /* =============================================
1279
+ Compositor Sources (Simplified)
1280
+ ============================================= */
1281
+ .fw-sc-compositor-sources {
1282
+ padding: 0.5rem;
1283
+ border-bottom: 1px solid hsl(var(--tn-fg-gutter) / 0.2);
1284
+ }
1285
+
1286
+ .fw-sc-section-label {
1287
+ font-size: 0.65rem;
1288
+ font-weight: 600;
1289
+ text-transform: uppercase;
1290
+ letter-spacing: 0.05em;
1291
+ color: hsl(var(--tn-comment));
1292
+ margin-bottom: 0.5rem;
1293
+ padding-left: 0.5rem;
1294
+ }
1295
+
1296
+ .fw-sc-source-list {
1297
+ display: flex;
1298
+ flex-direction: column;
1299
+ gap: 0.25rem;
1300
+ }
1301
+
1302
+ .fw-sc-source-row {
1303
+ display: flex;
1304
+ align-items: center;
1305
+ gap: 0.5rem;
1306
+ padding: 0.375rem 0.5rem;
1307
+ background: hsl(var(--tn-bg-highlight) / 0.3);
1308
+ border-radius: 4px;
1309
+ transition: opacity 0.15s;
1310
+ }
1311
+
1312
+ .fw-sc-source-row--hidden {
1313
+ opacity: 0.4;
1314
+ }
1315
+
1316
+ .fw-sc-visibility-btn {
1317
+ width: 24px;
1318
+ height: 24px;
1319
+ display: flex;
1320
+ align-items: center;
1321
+ justify-content: center;
1322
+ padding: 0;
1323
+ border: none;
1324
+ border-radius: 4px;
1325
+ background: transparent;
1326
+ color: hsl(var(--tn-fg-dark));
1327
+ cursor: pointer;
1328
+ transition: all 0.15s;
1329
+ }
1330
+
1331
+ .fw-sc-visibility-btn:hover {
1332
+ background: hsl(var(--tn-bg-highlight));
1333
+ color: hsl(var(--tn-fg));
1334
+ }
1335
+
1336
+ .fw-sc-source-icon {
1337
+ display: flex;
1338
+ align-items: center;
1339
+ color: hsl(var(--tn-comment));
1340
+ }
1341
+
1342
+ .fw-sc-source-label {
1343
+ flex: 1;
1344
+ font-size: 0.8rem;
1345
+ color: hsl(var(--tn-fg));
1346
+ overflow: hidden;
1347
+ text-overflow: ellipsis;
1348
+ white-space: nowrap;
1349
+ }
1350
+
1351
+ /* =============================================
1352
+ Layout Presets (Grid)
1353
+ ============================================= */
1354
+ .fw-sc-layout-presets {
1355
+ padding: 0.5rem;
1356
+ border-bottom: 1px solid hsl(var(--tn-fg-gutter) / 0.2);
1357
+ }
1358
+
1359
+ .fw-sc-layout-grid {
1360
+ display: grid;
1361
+ grid-template-columns: repeat(6, 1fr);
1362
+ gap: 0.25rem;
1363
+ }
1364
+
1365
+ .fw-sc-layout-btn {
1366
+ aspect-ratio: 1;
1367
+ display: flex;
1368
+ align-items: center;
1369
+ justify-content: center;
1370
+ padding: 0;
1371
+ border: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
1372
+ border-radius: 4px;
1373
+ background: hsl(var(--tn-bg-highlight) / 0.3);
1374
+ color: hsl(var(--tn-fg-dark));
1375
+ cursor: pointer;
1376
+ transition: all 0.15s;
1377
+ }
1378
+
1379
+ .fw-sc-layout-btn:hover {
1380
+ background: hsl(var(--tn-bg-highlight));
1381
+ border-color: hsl(var(--tn-fg-gutter) / 0.5);
1382
+ }
1383
+
1384
+ .fw-sc-layout-btn--active {
1385
+ background: hsl(var(--tn-blue) / 0.2);
1386
+ border-color: hsl(var(--tn-blue) / 0.5);
1387
+ color: hsl(var(--tn-blue));
1388
+ }
1389
+
1390
+ .fw-sc-layout-btn--disabled,
1391
+ .fw-sc-layout-btn:disabled {
1392
+ opacity: 0.3;
1393
+ cursor: not-allowed;
1394
+ }
1395
+
1396
+ .fw-sc-layout-btn--disabled:hover,
1397
+ .fw-sc-layout-btn:disabled:hover {
1398
+ background: hsl(var(--tn-bg-highlight) / 0.3);
1399
+ border-color: hsl(var(--tn-fg-gutter) / 0.3);
1400
+ }
1401
+
1402
+ /* Legacy layout preset buttons (for backwards compat) */
1403
+ .fw-sc-layout-presets-label {
1404
+ font-size: 0.7rem;
1405
+ font-weight: 600;
1406
+ text-transform: uppercase;
1407
+ letter-spacing: 0.05em;
1408
+ color: hsl(var(--tn-comment));
1409
+ }
1410
+
1411
+ .fw-sc-layout-preset-buttons {
1412
+ display: flex;
1413
+ gap: 0.25rem;
1414
+ }
1415
+
1416
+ .fw-sc-layout-preset-btn {
1417
+ width: 32px;
1418
+ height: 28px;
1419
+ display: flex;
1420
+ align-items: center;
1421
+ justify-content: center;
1422
+ padding: 0;
1423
+ border: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
1424
+ border-radius: 4px;
1425
+ background: hsl(var(--tn-bg-highlight) / 0.3);
1426
+ color: hsl(var(--tn-fg-dark));
1427
+ font-size: 0.75rem;
1428
+ cursor: pointer;
1429
+ transition: all 0.15s;
1430
+ }
1431
+
1432
+ .fw-sc-layout-preset-btn:hover {
1433
+ background: hsl(var(--tn-bg-highlight));
1434
+ border-color: hsl(var(--tn-fg-gutter) / 0.5);
1435
+ }
1436
+
1437
+ .fw-sc-layout-preset-btn--active {
1438
+ background: hsl(var(--tn-blue) / 0.2);
1439
+ border-color: hsl(var(--tn-blue) / 0.5);
1440
+ color: hsl(var(--tn-blue));
1441
+ }
1442
+
1443
+ .fw-sc-layout-preset-btn--disabled,
1444
+ .fw-sc-layout-preset-btn:disabled {
1445
+ opacity: 0.3;
1446
+ cursor: not-allowed;
1447
+ }
1448
+
1449
+ .fw-sc-layout-preset-btn--disabled:hover,
1450
+ .fw-sc-layout-preset-btn:disabled:hover {
1451
+ background: transparent;
1452
+ border-color: hsl(var(--tn-fg-gutter) / 0.3);
1453
+ }
1454
+
1455
+ /* =============================================
1456
+ Scene Switcher
1457
+ ============================================= */
1458
+ .fw-sc-scene-switcher {
1459
+ border-bottom: 1px solid hsl(var(--tn-fg-gutter) / 0.2);
1460
+ }
1461
+
1462
+ .fw-sc-scene-switcher-header {
1463
+ display: flex;
1464
+ align-items: center;
1465
+ justify-content: space-between;
1466
+ padding: 0.5rem 1rem;
1467
+ }
1468
+
1469
+ .fw-sc-scene-switcher-title {
1470
+ font-size: 0.7rem;
1471
+ font-weight: 600;
1472
+ text-transform: uppercase;
1473
+ letter-spacing: 0.05em;
1474
+ color: hsl(var(--tn-comment));
1475
+ }
1476
+
1477
+ .fw-sc-transition-controls {
1478
+ display: flex;
1479
+ align-items: center;
1480
+ gap: 0.5rem;
1481
+ }
1482
+
1483
+ .fw-sc-transition-select {
1484
+ padding: 0.25rem 0.5rem;
1485
+ border: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
1486
+ border-radius: 4px;
1487
+ background: hsl(var(--tn-bg-highlight));
1488
+ color: hsl(var(--tn-fg));
1489
+ font-size: 0.75rem;
1490
+ cursor: pointer;
1491
+ }
1492
+
1493
+ .fw-sc-transition-select:focus {
1494
+ outline: none;
1495
+ border-color: hsl(var(--tn-blue));
1496
+ }
1497
+
1498
+ .fw-sc-transition-duration {
1499
+ width: 60px;
1500
+ padding: 0.25rem 0.5rem;
1501
+ border: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
1502
+ border-radius: 4px;
1503
+ background: hsl(var(--tn-bg-highlight));
1504
+ color: hsl(var(--tn-fg));
1505
+ font-size: 0.75rem;
1506
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
1507
+ }
1508
+
1509
+ .fw-sc-transition-duration:focus {
1510
+ outline: none;
1511
+ border-color: hsl(var(--tn-blue));
1512
+ }
1513
+
1514
+ .fw-sc-transition-unit {
1515
+ font-size: 0.7rem;
1516
+ color: hsl(var(--tn-comment));
1517
+ }
1518
+
1519
+ .fw-sc-scene-list {
1520
+ display: flex;
1521
+ gap: 0.5rem;
1522
+ padding: 0 1rem 0.75rem;
1523
+ overflow-x: auto;
1524
+ }
1525
+
1526
+ .fw-sc-scene-item {
1527
+ position: relative;
1528
+ display: flex;
1529
+ flex-direction: column;
1530
+ align-items: flex-start;
1531
+ padding: 0.5rem 0.75rem;
1532
+ min-width: 100px;
1533
+ border: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
1534
+ border-radius: 4px;
1535
+ background: hsl(var(--tn-bg-highlight));
1536
+ color: hsl(var(--tn-fg));
1537
+ cursor: pointer;
1538
+ transition: all 0.15s;
1539
+ }
1540
+
1541
+ .fw-sc-scene-item:hover {
1542
+ border-color: hsl(var(--tn-fg-gutter) / 0.5);
1543
+ }
1544
+
1545
+ .fw-sc-scene-item--active {
1546
+ border-color: hsl(var(--tn-green));
1547
+ box-shadow: 0 0 0 1px hsl(var(--tn-green) / 0.3);
1548
+ }
1549
+
1550
+ .fw-sc-scene-item--transitioning {
1551
+ opacity: 0.7;
1552
+ pointer-events: none;
1553
+ }
1554
+
1555
+ .fw-sc-scene-name {
1556
+ font-size: 0.875rem;
1557
+ font-weight: 500;
1558
+ }
1559
+
1560
+ .fw-sc-scene-layer-count {
1561
+ font-size: 0.7rem;
1562
+ color: hsl(var(--tn-comment));
1563
+ }
1564
+
1565
+ .fw-sc-scene-delete {
1566
+ position: absolute;
1567
+ top: 2px;
1568
+ right: 2px;
1569
+ width: 18px;
1570
+ height: 18px;
1571
+ display: flex;
1572
+ align-items: center;
1573
+ justify-content: center;
1574
+ padding: 0;
1575
+ border: none;
1576
+ border-radius: 2px;
1577
+ background: transparent;
1578
+ color: hsl(var(--tn-comment));
1579
+ font-size: 14px;
1580
+ cursor: pointer;
1581
+ opacity: 0;
1582
+ transition: all 0.15s;
1583
+ }
1584
+
1585
+ .fw-sc-scene-item:hover .fw-sc-scene-delete {
1586
+ opacity: 1;
1587
+ }
1588
+
1589
+ .fw-sc-scene-delete:hover {
1590
+ background: hsl(var(--tn-red) / 0.2);
1591
+ color: hsl(var(--tn-red));
1592
+ }
1593
+
1594
+ .fw-sc-scene-add {
1595
+ display: flex;
1596
+ align-items: center;
1597
+ justify-content: center;
1598
+ min-width: 40px;
1599
+ padding: 0.5rem;
1600
+ border: 1px dashed hsl(var(--tn-fg-gutter) / 0.5);
1601
+ border-radius: 4px;
1602
+ background: transparent;
1603
+ color: hsl(var(--tn-comment));
1604
+ font-size: 1.25rem;
1605
+ cursor: pointer;
1606
+ transition: all 0.15s;
1607
+ }
1608
+
1609
+ .fw-sc-scene-add:hover {
1610
+ border-color: hsl(var(--tn-blue));
1611
+ color: hsl(var(--tn-blue));
1612
+ background: hsl(var(--tn-blue) / 0.1);
1613
+ }
1614
+
1615
+ /* New Scene Input */
1616
+ .fw-sc-new-scene-input {
1617
+ display: flex;
1618
+ gap: 0.5rem;
1619
+ padding: 0.5rem 1rem;
1620
+ background: hsl(var(--tn-bg-dark) / 0.5);
1621
+ }
1622
+
1623
+ .fw-sc-new-scene-input input {
1624
+ flex: 1;
1625
+ padding: 0.375rem 0.5rem;
1626
+ border: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
1627
+ border-radius: 4px;
1628
+ background: hsl(var(--tn-bg-highlight));
1629
+ color: hsl(var(--tn-fg));
1630
+ font-size: 0.875rem;
1631
+ }
1632
+
1633
+ .fw-sc-new-scene-input input:focus {
1634
+ outline: none;
1635
+ border-color: hsl(var(--tn-blue));
1636
+ }
1637
+
1638
+ .fw-sc-new-scene-input button {
1639
+ padding: 0.375rem 0.75rem;
1640
+ border: none;
1641
+ border-radius: 4px;
1642
+ background: hsl(var(--tn-blue));
1643
+ color: white;
1644
+ font-size: 0.75rem;
1645
+ font-weight: 500;
1646
+ cursor: pointer;
1647
+ transition: background 0.15s;
1648
+ }
1649
+
1650
+ .fw-sc-new-scene-input button:hover {
1651
+ background: hsl(var(--tn-blue) / 0.8);
1652
+ }
1653
+
1654
+ .fw-sc-new-scene-input button:last-child {
1655
+ background: hsl(var(--tn-bg-highlight));
1656
+ color: hsl(var(--tn-fg-dark));
1657
+ }
1658
+
1659
+ .fw-sc-new-scene-input button:last-child:hover {
1660
+ background: hsl(var(--tn-bg-highlight) / 0.8);
1661
+ }
1662
+
1663
+ /* =============================================
1664
+ Layer List
1665
+ ============================================= */
1666
+ .fw-sc-layer-list {
1667
+ border-bottom: 1px solid hsl(var(--tn-fg-gutter) / 0.2);
1668
+ }
1669
+
1670
+ .fw-sc-layer-list-header {
1671
+ display: flex;
1672
+ align-items: center;
1673
+ justify-content: space-between;
1674
+ padding: 0.5rem 1rem;
1675
+ background: hsl(var(--tn-bg-highlight) / 0.3);
1676
+ }
1677
+
1678
+ .fw-sc-layer-list-title {
1679
+ font-size: 0.7rem;
1680
+ font-weight: 600;
1681
+ text-transform: uppercase;
1682
+ letter-spacing: 0.05em;
1683
+ color: hsl(var(--tn-comment));
1684
+ }
1685
+
1686
+ .fw-sc-layer-count {
1687
+ font-size: 0.7rem;
1688
+ color: hsl(var(--tn-fg-dark));
1689
+ background: hsl(var(--tn-bg-highlight));
1690
+ padding: 0.125rem 0.375rem;
1691
+ border-radius: 8px;
1692
+ }
1693
+
1694
+ .fw-sc-layer-items {
1695
+ display: flex;
1696
+ flex-direction: column;
1697
+ }
1698
+
1699
+ .fw-sc-layer-empty {
1700
+ padding: 1rem;
1701
+ text-align: center;
1702
+ color: hsl(var(--tn-comment));
1703
+ font-size: 0.875rem;
1704
+ }
1705
+
1706
+ .fw-sc-layer-item {
1707
+ display: flex;
1708
+ align-items: center;
1709
+ gap: 0.5rem;
1710
+ padding: 0.5rem 1rem;
1711
+ border-bottom: 1px solid hsl(var(--tn-fg-gutter) / 0.15);
1712
+ cursor: pointer;
1713
+ transition: background 0.15s;
1714
+ }
1715
+
1716
+ .fw-sc-layer-item:last-child {
1717
+ border-bottom: none;
1718
+ }
1719
+
1720
+ .fw-sc-layer-item:hover {
1721
+ background: hsl(var(--tn-bg-highlight) / 0.3);
1722
+ }
1723
+
1724
+ .fw-sc-layer-item--selected {
1725
+ background: hsl(var(--tn-blue) / 0.1);
1726
+ }
1727
+
1728
+ .fw-sc-layer-item--selected:hover {
1729
+ background: hsl(var(--tn-blue) / 0.15);
1730
+ }
1731
+
1732
+ .fw-sc-layer-item--dragging {
1733
+ opacity: 0.5;
1734
+ }
1735
+
1736
+ .fw-sc-layer-item--drag-over {
1737
+ border-top: 2px solid hsl(var(--tn-blue));
1738
+ }
1739
+
1740
+ .fw-sc-layer-item--hidden {
1741
+ opacity: 0.5;
1742
+ }
1743
+
1744
+ .fw-sc-layer-visibility {
1745
+ width: 24px;
1746
+ height: 24px;
1747
+ display: flex;
1748
+ align-items: center;
1749
+ justify-content: center;
1750
+ padding: 0;
1751
+ border: none;
1752
+ border-radius: 4px;
1753
+ background: transparent;
1754
+ color: hsl(var(--tn-comment));
1755
+ cursor: pointer;
1756
+ transition: all 0.15s;
1757
+ }
1758
+
1759
+ .fw-sc-layer-visibility:hover {
1760
+ background: hsl(var(--tn-bg-highlight));
1761
+ }
1762
+
1763
+ .fw-sc-layer-visibility--visible {
1764
+ color: hsl(var(--tn-fg));
1765
+ }
1766
+
1767
+ .fw-sc-layer-icon {
1768
+ font-size: 1rem;
1769
+ }
1770
+
1771
+ .fw-sc-layer-name {
1772
+ flex: 1;
1773
+ font-size: 0.875rem;
1774
+ overflow: hidden;
1775
+ text-overflow: ellipsis;
1776
+ white-space: nowrap;
1777
+ }
1778
+
1779
+ .fw-sc-layer-opacity {
1780
+ display: flex;
1781
+ align-items: center;
1782
+ gap: 0.5rem;
1783
+ margin-right: 0.5rem;
1784
+ }
1785
+
1786
+ .fw-sc-layer-opacity input[type="range"] {
1787
+ width: 60px;
1788
+ height: 4px;
1789
+ -webkit-appearance: none;
1790
+ appearance: none;
1791
+ background: hsl(var(--tn-bg-highlight));
1792
+ border-radius: 2px;
1793
+ cursor: pointer;
1794
+ }
1795
+
1796
+ .fw-sc-layer-opacity input[type="range"]::-webkit-slider-thumb {
1797
+ -webkit-appearance: none;
1798
+ width: 12px;
1799
+ height: 12px;
1800
+ border-radius: 50%;
1801
+ background: hsl(var(--tn-fg));
1802
+ cursor: pointer;
1803
+ }
1804
+
1805
+ .fw-sc-layer-opacity input[type="range"]::-moz-range-thumb {
1806
+ width: 12px;
1807
+ height: 12px;
1808
+ border-radius: 50%;
1809
+ background: hsl(var(--tn-fg));
1810
+ border: none;
1811
+ cursor: pointer;
1812
+ }
1813
+
1814
+ .fw-sc-layer-opacity span {
1815
+ font-size: 0.7rem;
1816
+ color: hsl(var(--tn-fg-dark));
1817
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
1818
+ min-width: 32px;
1819
+ }
1820
+
1821
+ .fw-sc-layer-controls {
1822
+ display: flex;
1823
+ align-items: center;
1824
+ gap: 0.125rem;
1825
+ }
1826
+
1827
+ .fw-sc-layer-btn {
1828
+ width: 24px;
1829
+ height: 24px;
1830
+ display: flex;
1831
+ align-items: center;
1832
+ justify-content: center;
1833
+ padding: 0;
1834
+ border: none;
1835
+ border-radius: 4px;
1836
+ background: transparent;
1837
+ color: hsl(var(--tn-comment));
1838
+ font-size: 0.875rem;
1839
+ cursor: pointer;
1840
+ transition: all 0.15s;
1841
+ }
1842
+
1843
+ .fw-sc-layer-btn:hover:not(:disabled) {
1844
+ background: hsl(var(--tn-bg-highlight));
1845
+ color: hsl(var(--tn-fg));
1846
+ }
1847
+
1848
+ .fw-sc-layer-btn:disabled {
1849
+ opacity: 0.3;
1850
+ cursor: not-allowed;
1851
+ }
1852
+
1853
+ .fw-sc-layer-btn--active {
1854
+ background: hsl(var(--tn-blue) / 0.2);
1855
+ color: hsl(var(--tn-blue));
1856
+ }
1857
+
1858
+ .fw-sc-layer-btn--danger:hover:not(:disabled) {
1859
+ background: hsl(var(--tn-red) / 0.2);
1860
+ color: hsl(var(--tn-red));
1861
+ }
1862
+
1863
+ /* ============================================================================
1864
+ * Compact Layout Overlay
1865
+ * ============================================================================ */
1866
+
1867
+ .fw-sc-layout-overlay {
1868
+ position: absolute;
1869
+ bottom: 8px;
1870
+ left: 50%;
1871
+ transform: translateX(-50%);
1872
+ z-index: 20;
1873
+ display: flex;
1874
+ flex-direction: column;
1875
+ align-items: center;
1876
+ gap: 4px;
1877
+ opacity: 0.7;
1878
+ transition: opacity 0.2s ease;
1879
+ }
1880
+
1881
+ .fw-sc-layout-overlay:hover,
1882
+ .fw-sc-layout-overlay--expanded {
1883
+ opacity: 1;
1884
+ }
1885
+
1886
+ .fw-sc-layout-bar {
1887
+ display: flex;
1888
+ align-items: center;
1889
+ gap: 2px;
1890
+ padding: 4px 6px;
1891
+ background: hsl(var(--tn-bg-dark) / 0.9);
1892
+ backdrop-filter: blur(8px);
1893
+ border-radius: 6px;
1894
+ border: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
1895
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
1896
+ }
1897
+
1898
+ .fw-sc-layout-icons,
1899
+ .fw-sc-scaling-icons {
1900
+ display: flex;
1901
+ align-items: center;
1902
+ gap: 1px;
1903
+ }
1904
+
1905
+ .fw-sc-layout-icon {
1906
+ width: 22px;
1907
+ height: 22px;
1908
+ display: flex;
1909
+ align-items: center;
1910
+ justify-content: center;
1911
+ padding: 0;
1912
+ border: none;
1913
+ border-radius: 4px;
1914
+ background: transparent;
1915
+ color: hsl(var(--tn-fg-dark));
1916
+ cursor: pointer;
1917
+ transition: all 0.15s ease;
1918
+ }
1919
+
1920
+ .fw-sc-layout-icon:hover {
1921
+ background: hsl(var(--tn-bg-highlight));
1922
+ color: hsl(var(--tn-fg));
1923
+ }
1924
+
1925
+ .fw-sc-layout-icon--active {
1926
+ background: hsl(var(--tn-blue) / 0.2);
1927
+ color: hsl(var(--tn-blue));
1928
+ }
1929
+
1930
+ .fw-sc-layout-icon--active:hover {
1931
+ background: hsl(var(--tn-blue) / 0.3);
1932
+ }
1933
+
1934
+ .fw-sc-layout-separator {
1935
+ width: 1px;
1936
+ height: 14px;
1937
+ background: hsl(var(--tn-fg-gutter) / 0.5);
1938
+ margin: 0 4px;
1939
+ }
1940
+
1941
+ .fw-sc-layout-stats {
1942
+ font-size: 0.65rem;
1943
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
1944
+ color: hsl(var(--tn-fg-dark));
1945
+ white-space: nowrap;
1946
+ padding: 0 2px;
1947
+ }
1948
+
1949
+ /* Source chips (expanded on hover) */
1950
+ .fw-sc-layout-sources {
1951
+ display: flex;
1952
+ align-items: center;
1953
+ gap: 4px;
1954
+ padding: 4px 6px;
1955
+ background: hsl(var(--tn-bg-dark) / 0.9);
1956
+ backdrop-filter: blur(8px);
1957
+ border-radius: 6px;
1958
+ border: 1px solid hsl(var(--tn-fg-gutter) / 0.3);
1959
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
1960
+ }
1961
+
1962
+ .fw-sc-source-chip {
1963
+ display: flex;
1964
+ align-items: center;
1965
+ gap: 4px;
1966
+ padding: 3px 8px;
1967
+ border: none;
1968
+ border-radius: 4px;
1969
+ background: hsl(var(--tn-bg-highlight));
1970
+ color: hsl(var(--tn-fg));
1971
+ font-size: 0.7rem;
1972
+ cursor: pointer;
1973
+ transition: all 0.15s ease;
1974
+ }
1975
+
1976
+ .fw-sc-source-chip:hover {
1977
+ background: hsl(var(--tn-bg-highlight) / 1.5);
1978
+ }
1979
+
1980
+ .fw-sc-source-chip--hidden {
1981
+ background: hsl(var(--tn-bg-dark));
1982
+ color: hsl(var(--tn-comment));
1983
+ opacity: 0.6;
1984
+ }
1985
+
1986
+ .fw-sc-source-chip--hidden:hover {
1987
+ opacity: 0.9;
1988
+ }
1989
+
1990
+ .fw-sc-source-chip-icon {
1991
+ font-size: 0.75rem;
1992
+ }
1993
+
1994
+ .fw-sc-source-chip-label {
1995
+ max-width: 80px;
1996
+ overflow: hidden;
1997
+ text-overflow: ellipsis;
1998
+ white-space: nowrap;
1999
+ }
2000
+
2001
+ /* ============================================================================
2002
+ * Layout Bar Sections & Labels
2003
+ * ============================================================================ */
2004
+
2005
+ .fw-sc-layout-section {
2006
+ display: flex;
2007
+ align-items: center;
2008
+ gap: 4px;
2009
+ }
2010
+
2011
+ .fw-sc-layout-label {
2012
+ font-size: 0.6rem;
2013
+ font-weight: 500;
2014
+ text-transform: uppercase;
2015
+ letter-spacing: 0.05em;
2016
+ color: hsl(var(--tn-comment));
2017
+ padding: 0 4px;
2018
+ }
2019
+
2020
+ /* ============================================================================
2021
+ * Custom Instant Tooltips
2022
+ * ============================================================================ */
2023
+
2024
+ .fw-sc-tooltip-wrapper {
2025
+ position: relative;
2026
+ display: inline-flex;
2027
+ }
2028
+
2029
+ .fw-sc-tooltip {
2030
+ position: absolute;
2031
+ bottom: calc(100% + 8px);
2032
+ left: 50%;
2033
+ transform: translateX(-50%);
2034
+ padding: 4px 8px;
2035
+ background: hsl(var(--tn-bg-dark));
2036
+ border: 1px solid hsl(var(--tn-fg-gutter) / 0.5);
2037
+ border-radius: 4px;
2038
+ font-size: 0.7rem;
2039
+ font-weight: 500;
2040
+ color: hsl(var(--tn-fg));
2041
+ white-space: nowrap;
2042
+ z-index: 100;
2043
+ pointer-events: none;
2044
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
2045
+ }
2046
+
2047
+ .fw-sc-tooltip::after {
2048
+ content: "";
2049
+ position: absolute;
2050
+ top: 100%;
2051
+ left: 50%;
2052
+ transform: translateX(-50%);
2053
+ border: 5px solid transparent;
2054
+ border-top-color: hsl(var(--tn-fg-gutter) / 0.5);
2055
+ }
2056
+
2057
+ .fw-sc-tooltip::before {
2058
+ content: "";
2059
+ position: absolute;
2060
+ top: 100%;
2061
+ left: 50%;
2062
+ transform: translateX(-50%);
2063
+ border: 4px solid transparent;
2064
+ border-top-color: hsl(var(--tn-bg-dark));
2065
+ margin-top: -1px;
2066
+ }
2067
+ } /* End @layer fw-streamcrafter */
2068
+
2069
+ `,xe=c`
2070
+ .flex {
2071
+ display: flex;
2072
+ }
2073
+ .inline-flex {
2074
+ display: inline-flex;
2075
+ }
2076
+ .block {
2077
+ display: block;
2078
+ }
2079
+ .hidden {
2080
+ display: none;
2081
+ }
2082
+ .contents {
2083
+ display: contents;
2084
+ }
2085
+ .flex-col {
2086
+ flex-direction: column;
2087
+ }
2088
+ .flex-row {
2089
+ flex-direction: row;
2090
+ }
2091
+ .flex-1 {
2092
+ flex: 1 1 0%;
2093
+ }
2094
+ .flex-none {
2095
+ flex: none;
2096
+ }
2097
+ .flex-wrap {
2098
+ flex-wrap: wrap;
2099
+ }
2100
+ .items-center {
2101
+ align-items: center;
2102
+ }
2103
+ .items-start {
2104
+ align-items: flex-start;
2105
+ }
2106
+ .justify-center {
2107
+ justify-content: center;
2108
+ }
2109
+ .justify-between {
2110
+ justify-content: space-between;
2111
+ }
2112
+ .gap-0\\.5 {
2113
+ gap: 0.125rem;
2114
+ }
2115
+ .gap-1 {
2116
+ gap: 0.25rem;
2117
+ }
2118
+ .gap-2 {
2119
+ gap: 0.5rem;
2120
+ }
2121
+ .gap-3 {
2122
+ gap: 0.75rem;
2123
+ }
2124
+ .gap-4 {
2125
+ gap: 1rem;
2126
+ }
2127
+ .w-full {
2128
+ width: 100%;
2129
+ }
2130
+ .h-full {
2131
+ height: 100%;
2132
+ }
2133
+ .min-w-0 {
2134
+ min-width: 0;
2135
+ }
2136
+ .relative {
2137
+ position: relative;
2138
+ }
2139
+ .absolute {
2140
+ position: absolute;
2141
+ }
2142
+ .inset-0 {
2143
+ inset: 0;
2144
+ }
2145
+ .overflow-hidden {
2146
+ overflow: hidden;
2147
+ }
2148
+ .overflow-auto {
2149
+ overflow: auto;
2150
+ }
2151
+ .text-xs {
2152
+ font-size: 0.75rem;
2153
+ line-height: 1rem;
2154
+ }
2155
+ .text-sm {
2156
+ font-size: 0.875rem;
2157
+ line-height: 1.25rem;
2158
+ }
2159
+ .font-mono {
2160
+ font-family:
2161
+ ui-monospace,
2162
+ SFMono-Regular,
2163
+ SF Mono,
2164
+ Menlo,
2165
+ Consolas,
2166
+ monospace;
2167
+ }
2168
+ .font-medium {
2169
+ font-weight: 500;
2170
+ }
2171
+ .font-semibold {
2172
+ font-weight: 600;
2173
+ }
2174
+ .uppercase {
2175
+ text-transform: uppercase;
2176
+ }
2177
+ .tracking-wider {
2178
+ letter-spacing: 0.05em;
2179
+ }
2180
+ .whitespace-nowrap {
2181
+ white-space: nowrap;
2182
+ }
2183
+ .tabular-nums {
2184
+ font-variant-numeric: tabular-nums;
2185
+ }
2186
+ .rounded {
2187
+ border-radius: 0.25rem;
2188
+ }
2189
+ .rounded-md {
2190
+ border-radius: 0.375rem;
2191
+ }
2192
+ .rounded-lg {
2193
+ border-radius: 0.5rem;
2194
+ }
2195
+ .border-none {
2196
+ border: none;
2197
+ }
2198
+ .cursor-pointer {
2199
+ cursor: pointer;
2200
+ }
2201
+ .cursor-not-allowed {
2202
+ cursor: not-allowed;
2203
+ }
2204
+ .pointer-events-none {
2205
+ pointer-events: none;
2206
+ }
2207
+ .select-none {
2208
+ user-select: none;
2209
+ }
2210
+ .opacity-50 {
2211
+ opacity: 0.5;
2212
+ }
2213
+ .opacity-70 {
2214
+ opacity: 0.7;
2215
+ }
2216
+ .transition {
2217
+ transition: all 150ms ease;
2218
+ }
2219
+ .p-0 {
2220
+ padding: 0;
2221
+ }
2222
+ .p-2 {
2223
+ padding: 0.5rem;
2224
+ }
2225
+ .px-2 {
2226
+ padding-left: 0.5rem;
2227
+ padding-right: 0.5rem;
2228
+ }
2229
+ .py-1 {
2230
+ padding-top: 0.25rem;
2231
+ padding-bottom: 0.25rem;
2232
+ }
2233
+ .m-0 {
2234
+ margin: 0;
2235
+ }
2236
+ .bg-none {
2237
+ background: none;
2238
+ }
2239
+ `,Se=(e=18)=>q` <svg
2240
+ width="${e}"
2241
+ height="${e}"
2242
+ viewBox="0 0 24 24"
2243
+ fill="none"
2244
+ stroke="currentColor"
2245
+ stroke-width="2"
2246
+ stroke-linecap="round"
2247
+ stroke-linejoin="round"
2248
+ >
2249
+ <path d="M23 7l-7 5 7 5V7z" />
2250
+ <rect x="1" y="5" width="15" height="14" rx="2" ry="2" />
2251
+ </svg>`,ke=(e=18)=>q` <svg
2252
+ width="${e}"
2253
+ height="${e}"
2254
+ viewBox="0 0 24 24"
2255
+ fill="none"
2256
+ stroke="currentColor"
2257
+ stroke-width="2"
2258
+ stroke-linecap="round"
2259
+ stroke-linejoin="round"
2260
+ >
2261
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
2262
+ <line x1="8" y1="21" x2="16" y2="21" />
2263
+ <line x1="12" y1="17" x2="12" y2="21" />
2264
+ </svg>`,Ce=(e=14)=>q` <svg
2265
+ width="${e}"
2266
+ height="${e}"
2267
+ viewBox="0 0 24 24"
2268
+ fill="none"
2269
+ stroke="currentColor"
2270
+ stroke-width="2"
2271
+ stroke-linecap="round"
2272
+ stroke-linejoin="round"
2273
+ >
2274
+ <line x1="18" y1="6" x2="6" y2="18" />
2275
+ <line x1="6" y1="6" x2="18" y2="18" />
2276
+ </svg>`,$e=(e=14)=>q` <svg
2277
+ width="${e}"
2278
+ height="${e}"
2279
+ viewBox="0 0 24 24"
2280
+ fill="none"
2281
+ stroke="currentColor"
2282
+ stroke-width="2"
2283
+ stroke-linecap="round"
2284
+ stroke-linejoin="round"
2285
+ >
2286
+ <polygon points="23 7 16 12 23 17 23 7" />
2287
+ <rect x="1" y="5" width="15" height="14" rx="2" ry="2" />
2288
+ </svg>`,Me={enabled:!1,width:1920,height:1080,frameRate:30,renderer:"auto",defaultTransition:{type:"fade",durationMs:500,easing:"ease-in-out"}},Te={x:0,y:0,width:1,height:1,opacity:1,rotation:0,borderRadius:0,crop:{top:0,right:0,bottom:0,left:0}};class Ae{constructor(){this.listeners=new Map}on(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),()=>this.off(e,t)}once(e,t){const r=i=>{this.off(e,r),t(i)};return this.on(e,r)}off(e,t){const r=this.listeners.get(e);r&&(r.delete(t),0===r.size&&this.listeners.delete(e))}emit(e,t){const r=this.listeners.get(e);r&&r.forEach(r=>{try{r(t)}catch(t){console.error(`Error in event handler for "${String(e)}":`,t)}})}removeAllListeners(e){e?this.listeners.delete(e):this.listeners.clear()}listenerCount(e){return this.listeners.get(e)?.size??0}}function _e(e="professional"){const t={professional:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30}},broadcast:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30}},conference:{width:{ideal:1280},height:{ideal:720},frameRate:{ideal:24}},auto:{width:{ideal:1920},height:{ideal:1080},frameRate:{ideal:30}}};return t[e]||t.professional}function Ee(e,t){const r=function(e="professional"){const t={professional:{echoCancellation:!1,noiseSuppression:!1,autoGainControl:!1,sampleRate:48e3,channelCount:2,latency:.01},broadcast:{echoCancellation:!1,noiseSuppression:!0,autoGainControl:!1,sampleRate:48e3,channelCount:2,latency:.02},conference:{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0,sampleRate:44100,channelCount:1,latency:.05},auto:{echoCancellation:!1,noiseSuppression:!1,autoGainControl:!1,sampleRate:48e3,channelCount:2}};return t[e]||t.professional}(e),i=_e(e);return{audio:{echoCancellation:r.echoCancellation,noiseSuppression:r.noiseSuppression,autoGainControl:r.autoGainControl,sampleRate:r.sampleRate,channelCount:r.channelCount,...t?.audioDeviceId&&{deviceId:{exact:t.audioDeviceId}}},video:{width:i.width,height:i.height,frameRate:i.frameRate,...t?.videoDeviceId&&{deviceId:{exact:t.videoDeviceId}},...t?.facingMode&&{facingMode:t.facingMode}}}}class Ie extends Ae{constructor(){super(),this.devices=[],this.currentStream=null,this.permissionStatus={video:!1,audio:!1},this.setupDeviceChangeListener()}setupDeviceChangeListener(){"undefined"!=typeof navigator&&navigator.mediaDevices&&navigator.mediaDevices.addEventListener("devicechange",async()=>{await this.enumerateDevices(),this.emit("devicesChanged",{devices:this.devices})})}async enumerateDevices(){if(!navigator.mediaDevices?.enumerateDevices)throw new Error("enumerateDevices not supported");const e=await navigator.mediaDevices.enumerateDevices();return this.devices=e.filter(e=>"audioinput"===e.kind||"videoinput"===e.kind||"audiooutput"===e.kind).map(e=>({deviceId:e.deviceId,kind:e.kind,label:e.label||`${e.kind} (${e.deviceId.slice(0,8)}...)`,groupId:e.groupId})),this.devices}async getVideoInputs(){return await this.enumerateDevices(),this.devices.filter(e=>"videoinput"===e.kind)}async getAudioInputs(){return await this.enumerateDevices(),this.devices.filter(e=>"audioinput"===e.kind)}async getAudioOutputs(){return await this.enumerateDevices(),this.devices.filter(e=>"audiooutput"===e.kind)}async requestPermissions(e={video:!0,audio:!0}){try{return(await navigator.mediaDevices.getUserMedia({video:e.video,audio:e.audio})).getTracks().forEach(e=>e.stop()),e.video&&(this.permissionStatus.video=!0),e.audio&&(this.permissionStatus.audio=!0),await this.enumerateDevices(),this.emit("permissionChanged",{granted:!0,denied:!1}),this.permissionStatus}catch(e){const t=e instanceof Error?e:new Error(String(e));throw"NotAllowedError"!==t.name&&"PermissionDeniedError"!==t.name||this.emit("permissionChanged",{granted:!1,denied:!0}),this.emit("error",{message:`Permission request failed: ${t.message}`,error:t}),e}}hasPermission(e){return this.permissionStatus[e]}async getUserMedia(e={}){const t=e.profile||"professional";let r;r=e.customConstraints?e.customConstraints:Ee(t,{videoDeviceId:e.videoDeviceId,audioDeviceId:e.audioDeviceId,facingMode:e.facingMode});try{const e=await navigator.mediaDevices.getUserMedia(r);return this.currentStream=e,e.getVideoTracks().length>0&&(this.permissionStatus.video=!0),e.getAudioTracks().length>0&&(this.permissionStatus.audio=!0),e}catch(e){const t=e instanceof Error?e:new Error(String(e));if("OverconstrainedError"===t.name){const e={video:!!r.video,audio:!!r.audio},t=await navigator.mediaDevices.getUserMedia(e);return this.currentStream=t,t}throw this.emit("error",{message:`getUserMedia failed: ${t.message}`,error:t}),e}}getStream(){return this.currentStream}stopAllTracks(){this.currentStream&&(this.currentStream.getTracks().forEach(e=>{e.stop()}),this.currentStream=null)}async replaceVideoTrack(e,t="professional"){if(!this.currentStream)throw new Error("No active stream to replace track in");const r=this.currentStream.getVideoTracks()[0];r&&(r.stop(),this.currentStream.removeTrack(r));const i=Ee(t,{videoDeviceId:e}),s=(await navigator.mediaDevices.getUserMedia({video:i.video,audio:!1})).getVideoTracks()[0];return s&&this.currentStream.addTrack(s),s||null}async replaceAudioTrack(e,t="professional"){if(!this.currentStream)throw new Error("No active stream to replace track in");const r=this.currentStream.getAudioTracks()[0];r&&(r.stop(),this.currentStream.removeTrack(r));const i=Ee(t,{audioDeviceId:e}),s=(await navigator.mediaDevices.getUserMedia({video:!1,audio:i.audio})).getAudioTracks()[0];return s&&this.currentStream.addTrack(s),s||null}getAllDevices(){return[...this.devices]}destroy(){this.stopAllTracks(),this.removeAllListeners()}}class Pe extends Ae{constructor(){super(...arguments),this.captures=new Map,this.captureCounter=0}async start(e={}){try{let t;if(!1===e.video)t=!1;else if("object"==typeof e.video){t={frameRate:{ideal:30,max:60},width:{ideal:1920},height:{ideal:1080},...e.video,...void 0!==e.cursor?{cursor:e.cursor}:{}}}else t={frameRate:{ideal:30,max:60},width:{ideal:1920},height:{ideal:1080},...void 0!==e.cursor?{cursor:e.cursor}:{}};const r={video:t,audio:e.audio??!1};e.preferCurrentTab&&"preferCurrentTab"in r&&(r.preferCurrentTab=!0),e.surfaceSwitching&&(r.surfaceSwitching="include"),void 0!==e.selfBrowserSurface&&(r.selfBrowserSurface=e.selfBrowserSurface),e.monitorTypeSurfaces&&(r.monitorTypeSurfaces=e.monitorTypeSurfaces),e.systemAudio&&r.audio&&"object"==typeof r.audio&&(r.audio.systemAudio=e.systemAudio);const i=await navigator.mediaDevices.getDisplayMedia(r),s=`screen-${++this.captureCounter}-${Date.now()}`,o=i.getVideoTracks()[0],n=o?.label||`Screen ${this.captureCounter}`;return this.captures.set(s,{stream:i,label:n}),i.getTracks().forEach(e=>{e.addEventListener("ended",()=>{this.handleTrackEnded(s,i)})}),this.emit("started",{stream:i,captureId:s}),i}catch(e){const t=e instanceof Error?e:new Error(String(e));if("AbortError"===t.name||"NotAllowedError"===t.name)return this.emit("ended",{captureId:"",stream:null,reason:"cancelled"}),null;throw this.emit("error",{message:`Screen capture failed: ${t.message}`,error:t}),e}}handleTrackEnded(e,t){const r=t.getTracks().filter(e=>"live"===e.readyState);0===r.length&&(this.captures.delete(e),this.emit("ended",{captureId:e,stream:t,reason:"user_stopped"}))}stopByStream(e){for(const[t,r]of this.captures)if(r.stream===e)return r.stream.getTracks().forEach(e=>e.stop()),this.captures.delete(t),void this.emit("ended",{captureId:t,stream:e,reason:"stopped"})}stop(){for(const[e,t]of this.captures)t.stream.getTracks().forEach(e=>e.stop()),this.emit("ended",{captureId:e,stream:t.stream,reason:"stopped"});this.captures.clear()}getCaptures(){return Array.from(this.captures.entries()).map(([e,t])=>({captureId:e,stream:t.stream,label:t.label}))}isActive(){return this.captures.size>0}getCaptureCount(){return this.captures.size}getStream(){const e=this.captures.values().next().value;return e?.stream??null}getVideoTrack(){const e=this.getStream();return e?.getVideoTracks()[0]??null}getAudioTrack(){const e=this.getStream();return e?.getAudioTracks()[0]??null}hasAudio(){for(const[,e]of this.captures)if(e.stream.getAudioTracks().length>0)return!0;return!1}destroy(){this.stop(),this.removeAllListeners()}}class Re extends Ae{constructor(e){super(),this.peerConnection=null,this.videoTrackGenerator=null,this.audioTrackGenerator=null,this.state="disconnected",this.videoWriter=null,this.audioWriter=null,this.videoWriteQueue=[],this.audioWriteQueue=[],this.isProcessingVideoQueue=!1,this.isProcessingAudioQueue=!1,this.videoTransformWorker=null,this.audioTransformWorker=null,this.encoderListenerCleanup=null,this.negotiatedVideoCodec=null,this.negotiatedAudioCodec=null,this.resourceUrl=null,this.config=e}log(e,t){this.config.debug&&console.log(`[WHIP] ${e}`,t??"")}logError(e,t){console.error(`[WHIP ERROR] ${e}`,t??""),this.emit("error",{message:e,error:t})}setState(e){const t=this.state;this.state=e,this.emit("stateChange",{state:e,previousState:t})}preferCodecs(e){const t=e.getTransceivers();for(const e of t){if(!e.setCodecPreferences)continue;const t=e.sender.track?.kind;if("video"===t){const t=RTCRtpSender.getCapabilities("video");if(!t?.codecs)continue;const r=t.codecs.filter(e=>"video/VP9"===e.mimeType||"video/H264"===e.mimeType).sort((e,t)=>"video/VP9"===e.mimeType&&"video/VP9"!==t.mimeType?-1:"video/VP9"!==e.mimeType&&"video/VP9"===t.mimeType?1:0);if(r.length>0)try{e.setCodecPreferences(r),this.log("Set video codec preferences",r.map(e=>e.mimeType))}catch(e){this.log("Failed to set video codec preferences",e)}}if("audio"===t){const t=RTCRtpSender.getCapabilities("audio");if(!t?.codecs)continue;const r=t.codecs.filter(e=>"audio/opus"===e.mimeType);if(r.length>0)try{e.setCodecPreferences(r),this.log("Set audio codec preferences",r.map(e=>e.mimeType))}catch(e){this.log("Failed to set audio codec preferences",e)}}}}verifyCodecAlignment(){if(!this.peerConnection)return;const e=this.peerConnection.getSenders();for(const t of e){const e=t.getParameters(),r=e.codecs?.[0];"video"===t.track?.kind&&r?.mimeType&&(this.negotiatedVideoCodec=r.mimeType,this.log("Negotiated video codec",r.mimeType)),"audio"===t.track?.kind&&r?.mimeType&&(this.negotiatedAudioCodec=r.mimeType,this.log("Negotiated audio codec",r.mimeType))}}canUseEncodedInsertion(){if(!this.peerConnection||"connected"!==this.state)return this.log("canUseEncodedInsertion: no connection",{hasPC:!!this.peerConnection,state:this.state}),!1;if("undefined"==typeof RTCRtpScriptTransform)return this.log("canUseEncodedInsertion: RTCRtpScriptTransform not supported"),!1;const e=this.peerConnection.getSenders().some(e=>"transform"in e);if(!e)return this.log("Sender transform not supported"),!1;if(this.negotiatedVideoCodec){if(!("video/VP9"===this.negotiatedVideoCodec||"video/H264"===this.negotiatedVideoCodec))return this.log("Video codec not compatible with WebCodecs",this.negotiatedVideoCodec),!1}if(this.negotiatedAudioCodec){if(!("audio/opus"===this.negotiatedAudioCodec))return this.log("Audio codec not compatible with WebCodecs",this.negotiatedAudioCodec),!1}return!0}getNegotiatedVideoCodec(){return this.negotiatedVideoCodec}getNegotiatedAudioCodec(){return this.negotiatedAudioCodec}get isConnected(){return"connected"===this.state}async connect(e){try{if(this.log("Starting WHIP connection"),this.setState("connecting"),!this.config.whipUrl)throw new Error("WHIP URL is required");const t={iceServers:this.config.iceServers||[]};this.log("Creating RTCPeerConnection",t);const r=new RTCPeerConnection(t);r.onconnectionstatechange=()=>{const e=r.connectionState;switch(this.log(`Connection state changed: ${e}`),e){case"connected":this.log("WHIP streaming connected successfully"),this.setState("connected");break;case"disconnected":this.setState("disconnected");break;case"failed":this.setState("failed");break;case"closed":this.setState("closed")}},r.oniceconnectionstatechange=()=>{this.log(`ICE connection state: ${r.iceConnectionState}`)},r.onicegatheringstatechange=()=>{this.log(`ICE gathering state: ${r.iceGatheringState}`)},r.onicecandidate=e=>{this.emit("iceCandidate",{candidate:e.candidate}),e.candidate?this.log("ICE candidate generated",e.candidate.candidate):this.log("ICE candidate gathering complete")},this.log("Adding tracks to peer connection"),e.getTracks().forEach((t,i)=>{this.log(`Adding ${t.kind} track ${i}`,{id:t.id,kind:t.kind,enabled:t.enabled,readyState:t.readyState}),r.addTrack(t,e)}),this.peerConnection=r,this.preferCodecs(r),this.log("Creating offer");const i=await r.createOffer({offerToReceiveAudio:!1,offerToReceiveVideo:!1});this.log("Setting local description"),await r.setLocalDescription(i),this.log("Local SDP offer created",{type:i.type,sdpLength:i.sdp?.length}),this.log(`Sending offer to WHIP endpoint: ${this.config.whipUrl}`);const s=await fetch(this.config.whipUrl,{method:"POST",headers:{"Content-Type":"application/sdp",Accept:"application/sdp"},body:i.sdp});if(this.log(`WHIP response status: ${s.status} ${s.statusText}`),!s.ok){const e=await s.text().catch(()=>"Unknown error");throw new Error(`WHIP request failed: ${s.status} ${s.statusText} - ${e}`)}this.resourceUrl=s.headers.get("Location"),this.resourceUrl&&this.log("WHIP resource URL:",this.resourceUrl);const o=await s.text();this.log("Received SDP answer",{length:o.length}),await r.setRemoteDescription({type:"answer",sdp:o}),this.log("Remote description set successfully"),this.verifyCodecAlignment(),this.log("WHIP connection established, waiting for ICE connection...")}catch(e){throw this.logError("Failed to connect",e instanceof Error?e:new Error(String(e))),this.setState("failed"),this.cleanup(),e}}async connectWithGenerators(){try{if(this.log("Starting WHIP connection with track generators"),this.setState("connecting"),!this.config.whipUrl)throw new Error("WHIP URL is required");this.log("Creating MediaStreamTrackGenerators");const e=new MediaStreamTrackGenerator({kind:"video"}),t=new MediaStreamTrackGenerator({kind:"audio"});this.videoTrackGenerator=e,this.audioTrackGenerator=t,this.log("Track generators created successfully");const r={iceServers:this.config.iceServers||[]};this.log("Creating RTCPeerConnection",r);const i=new RTCPeerConnection(r);i.onconnectionstatechange=()=>{const e=i.connectionState;switch(this.log(`Connection state changed: ${e}`),e){case"connected":this.log("WHIP streaming connected successfully"),this.setState("connected");break;case"disconnected":this.setState("disconnected");break;case"failed":this.setState("failed");break;case"closed":this.setState("closed")}},i.oniceconnectionstatechange=()=>{this.log(`ICE connection state: ${i.iceConnectionState}`)},i.onicegatheringstatechange=()=>{this.log(`ICE gathering state: ${i.iceGatheringState}`)},i.onicecandidate=e=>{this.emit("iceCandidate",{candidate:e.candidate}),e.candidate?this.log("ICE candidate generated",e.candidate.candidate):this.log("ICE candidate gathering complete")};const s=new MediaStream([e,t]);this.log("Adding tracks to peer connection"),s.getTracks().forEach((e,t)=>{this.log(`Adding ${e.kind} track ${t}`,{id:e.id,kind:e.kind,enabled:e.enabled,readyState:e.readyState}),i.addTrack(e,s)}),this.peerConnection=i,this.preferCodecs(i),this.log("Creating offer");const o=await i.createOffer({offerToReceiveAudio:!1,offerToReceiveVideo:!1});this.log("Setting local description"),await i.setLocalDescription(o),this.log(`Sending offer to WHIP endpoint: ${this.config.whipUrl}`);const n=await fetch(this.config.whipUrl,{method:"POST",headers:{"Content-Type":"application/sdp",Accept:"application/sdp"},body:o.sdp});if(this.log(`WHIP response status: ${n.status} ${n.statusText}`),!n.ok){const e=await n.text().catch(()=>"Unknown error");throw new Error(`WHIP request failed: ${n.status} ${n.statusText} - ${e}`)}this.resourceUrl=n.headers.get("Location"),this.resourceUrl&&this.log("WHIP resource URL:",this.resourceUrl);const a=await n.text();return this.log("Received SDP answer",{length:a.length}),await i.setRemoteDescription({type:"answer",sdp:a}),this.log("Remote description set successfully"),this.verifyCodecAlignment(),this.log("WHIP connection established with generators"),{videoGenerator:e,audioGenerator:t}}catch(e){throw this.logError("Failed to connect with generators",e instanceof Error?e:new Error(String(e))),this.setState("failed"),this.cleanup(),e}}async processVideoQueue(){if(!this.isProcessingVideoQueue&&0!==this.videoWriteQueue.length){this.isProcessingVideoQueue=!0;try{for(;this.videoWriteQueue.length>0;){const e=this.videoWriteQueue.shift();if(!e)continue;const{frame:t,resolve:r,reject:i}=e;if(this.videoTrackGenerator)try{this.videoWriter||(this.videoWriter=this.videoTrackGenerator.writable.getWriter()),await this.videoWriter.write(t),r(!0)}catch(e){if(this.videoWriter){try{this.videoWriter.releaseLock()}catch{}this.videoWriter=null}i(e instanceof Error?e:new Error(String(e)))}else i(new Error("Video track generator not available"))}}finally{this.isProcessingVideoQueue=!1}}}async processAudioQueue(){if(!this.isProcessingAudioQueue&&0!==this.audioWriteQueue.length){this.isProcessingAudioQueue=!0;try{for(;this.audioWriteQueue.length>0;){const e=this.audioWriteQueue.shift();if(!e)continue;const{audioData:t,resolve:r,reject:i}=e;if(this.audioTrackGenerator)try{this.audioWriter||(this.audioWriter=this.audioTrackGenerator.writable.getWriter()),await this.audioWriter.write(t),r(!0)}catch(e){if(this.audioWriter){try{this.audioWriter.releaseLock()}catch{}this.audioWriter=null}i(e instanceof Error?e:new Error(String(e)))}else i(new Error("Audio track generator not available"))}}finally{this.isProcessingAudioQueue=!1}}}async sendVideoFrame(e){if(!this.videoTrackGenerator){if(e)try{e.close()}catch{}return!1}return new Promise((t,r)=>{this.videoWriteQueue.push({frame:e,resolve:t,reject:r}),this.processVideoQueue()})}async sendAudioData(e){if(!this.audioTrackGenerator){if(e)try{e.close()}catch{}return!1}return new Promise((t,r)=>{this.audioWriteQueue.push({audioData:e,resolve:t,reject:r}),this.processAudioQueue()})}async replaceTrack(e,t){if(!this.peerConnection)throw new Error("No peer connection");const r=this.peerConnection.getSenders().find(t=>t.track?.kind===e.kind);if(!r)throw new Error(`No sender found for ${e.kind} track`);await r.replaceTrack(t),this.log(`Replaced ${e.kind} track`)}async addTrack(e,t){if(!this.peerConnection)throw new Error("No peer connection");this.peerConnection.addTrack(e,t||new MediaStream([e])),this.log(`Added ${e.kind} track`)}async getStats(){if(!this.peerConnection)return null;try{return await this.peerConnection.getStats()}catch(e){return this.logError("Failed to get connection stats",e instanceof Error?e:new Error(String(e))),null}}getState(){return this.state}getPeerConnection(){return this.peerConnection}attachEncoderTransform(e,r){if(!this.peerConnection)throw new Error("No peer connection - call connect() first");if("undefined"==typeof RTCRtpScriptTransform)return void this.log("RTCRtpScriptTransform not supported, skipping encoder transform");this.log("Attaching encoder transform");const i=()=>{if(r)return new Worker(r,{type:"module"});try{const e=new URL("../workers/rtcTransform.worker.js",t&&"SCRIPT"===t.tagName.toUpperCase()&&t.src||new URL("fw-streamcrafter.iife.js",document.baseURI).href);return new Worker(e,{type:"module"})}catch(e){this.log("Packaged worker URL failed, trying fallback paths",e)}const e=["/workers/rtcTransform.worker.js","./workers/rtcTransform.worker.js","/node_modules/@livepeer-frameworks/streamcrafter-core/dist/workers/rtcTransform.worker.js"];for(const t of e)try{return new Worker(t,{type:"module"})}catch{try{return new Worker(t)}catch{}}return null},s=this.peerConnection.getSenders(),o=s.find(e=>"video"===e.track?.kind),n=s.find(e=>"audio"===e.track?.kind);o&&"transform"in o&&(this.log("Creating video transform worker"),this.videoTransformWorker=i(),this.videoTransformWorker?(this.videoTransformWorker.postMessage({type:"configure",config:{debug:this.config.debug,maxQueueSize:30}}),o.transform=new RTCRtpScriptTransform(this.videoTransformWorker,{kind:"video"}),this.log("Video transform attached")):this.logError("Failed to create video transform worker")),n&&"transform"in n&&(this.log("Creating audio transform worker"),this.audioTransformWorker=i(),this.audioTransformWorker?(this.audioTransformWorker.postMessage({type:"configure",config:{debug:this.config.debug,maxQueueSize:50}}),n.transform=new RTCRtpScriptTransform(this.audioTransformWorker,{kind:"audio"}),this.log("Audio transform attached")):this.logError("Failed to create audio transform worker"));const a=e=>{this.videoTransformWorker&&this.videoTransformWorker.postMessage({type:"videoChunk",data:e},[e.data])},c=e=>{this.audioTransformWorker&&this.audioTransformWorker.postMessage({type:"audioChunk",data:e},[e.data])};e.on("videoChunk",a),e.on("audioChunk",c),this.encoderListenerCleanup=()=>{e.off("videoChunk",a),e.off("audioChunk",c)},this.log("Encoder transform attached successfully")}hasEncoderTransform(){return null!==this.videoTransformWorker||null!==this.audioTransformWorker}detachEncoderTransform(){this.encoderListenerCleanup&&(this.encoderListenerCleanup(),this.encoderListenerCleanup=null),this.videoTransformWorker&&(this.videoTransformWorker.postMessage({type:"stop"}),this.videoTransformWorker.terminate(),this.videoTransformWorker=null),this.audioTransformWorker&&(this.audioTransformWorker.postMessage({type:"stop"}),this.audioTransformWorker.terminate(),this.audioTransformWorker=null),this.log("Encoder transform detached")}cleanupWriters(){if(this.videoWriter){try{this.videoWriter.releaseLock()}catch{}this.videoWriter=null}if(this.audioWriter){try{this.audioWriter.releaseLock()}catch{}this.audioWriter=null}this.videoWriteQueue=[],this.audioWriteQueue=[],this.isProcessingVideoQueue=!1,this.isProcessingAudioQueue=!1}cleanup(){this.cleanupWriters(),this.detachEncoderTransform(),this.videoTrackGenerator&&(this.videoTrackGenerator.stop(),this.videoTrackGenerator=null),this.audioTrackGenerator&&(this.audioTrackGenerator.stop(),this.audioTrackGenerator=null),this.peerConnection&&(this.peerConnection.close(),this.peerConnection=null),this.resourceUrl=null}async disconnect(){if(this.log("Disconnecting WHIP"),this.resourceUrl){try{this.log("Sending DELETE to WHIP resource:",this.resourceUrl),await fetch(this.resourceUrl,{method:"DELETE"})}catch(e){this.log("Failed to delete WHIP resource (non-fatal)",e)}this.resourceUrl=null}this.cleanup(),this.setState("disconnected")}destroy(){this.cleanup(),this.removeAllListeners()}}class We extends Ae{constructor(e={}){super(),this.audioContext=null,this.destination=null,this.masterGain=null,this.compressor=null,this.limiter=null,this.analyzer=null,this.sources=new Map,this.outputStream=null,this.levelMonitoringActive=!1,this.peakLevel=0,this.peakDecayRate=.95,this.config={sampleRate:e.sampleRate??48e3,channelCount:e.channelCount??2}}async initialize(){if(!this.audioContext)try{this.audioContext=new AudioContext({sampleRate:this.config.sampleRate}),this.destination=this.audioContext.createMediaStreamDestination(),this.destination.channelCount=this.config.channelCount,this.masterGain=this.audioContext.createGain(),this.compressor=this.audioContext.createDynamicsCompressor(),this.compressor.threshold.value=-24,this.compressor.knee.value=30,this.compressor.ratio.value=4,this.compressor.attack.value=.003,this.compressor.release.value=.25,this.limiter=this.audioContext.createDynamicsCompressor(),this.limiter.threshold.value=-1,this.limiter.knee.value=0,this.limiter.ratio.value=20,this.limiter.attack.value=.001,this.limiter.release.value=.1,this.analyzer=this.audioContext.createAnalyser(),this.analyzer.fftSize=256,this.analyzer.smoothingTimeConstant=.3,this.masterGain.connect(this.compressor),this.compressor.connect(this.analyzer),this.analyzer.connect(this.limiter),this.limiter.connect(this.destination),this.outputStream=this.destination.stream,console.log("[AudioMixer] Initialized with compressor/limiter chain",{sampleRate:this.audioContext.sampleRate,channelCount:this.config.channelCount})}catch(e){const t=e instanceof Error?e:new Error(String(e));throw console.error("[AudioMixer] Failed to initialize:",t),this.emit("error",{message:t.message,error:t}),t}}addSource(e,t,r={}){if(!this.audioContext||!this.masterGain)throw new Error("AudioMixer not initialized. Call initialize() first.");if("audio"!==t.kind)throw new Error("Track must be an audio track");this.sources.has(e)&&this.removeSource(e);try{const i=new MediaStream([t]),s=this.audioContext.createMediaStreamSource(i),o=this.audioContext.createGain();o.gain.value=r.muted?0:r.volume??1;const n=this.audioContext.createStereoPanner();n.pan.value=r.pan??0,s.connect(o),o.connect(n),n.connect(this.masterGain);const a={id:e,sourceNode:s,gainNode:o,panNode:n,track:t,options:{volume:r.volume??1,muted:r.muted??!1,pan:r.pan??0}};this.sources.set(e,a),console.log("[AudioMixer] Added source:",e),this.emit("sourceAdded",{sourceId:e})}catch(e){const t=e instanceof Error?e:new Error(String(e));throw console.error("[AudioMixer] Failed to add source:",t),this.emit("error",{message:`Failed to add source: ${t.message}`,error:t}),t}}removeSource(e){const t=this.sources.get(e);if(t)try{t.sourceNode.disconnect(),t.gainNode.disconnect(),t.panNode.disconnect(),this.sources.delete(e),console.log("[AudioMixer] Removed source:",e),this.emit("sourceRemoved",{sourceId:e})}catch(e){console.error("[AudioMixer] Error removing source:",e)}}updateSource(e,t){const r=this.sources.get(e);r?(void 0!==t.volume&&(r.options.volume=t.volume,r.options.muted||r.gainNode.gain.setTargetAtTime(t.volume,this.audioContext?.currentTime??0,.01)),void 0!==t.muted&&(r.options.muted=t.muted,r.gainNode.gain.setTargetAtTime(t.muted?0:r.options.volume,this.audioContext?.currentTime??0,.01)),void 0!==t.pan&&(r.options.pan=t.pan,r.panNode.pan.setTargetAtTime(t.pan,this.audioContext?.currentTime??0,.01))):console.warn("[AudioMixer] Source not found:",e)}setVolume(e,t){this.updateSource(e,{volume:Math.max(0,Math.min(2,t))})}mute(e){this.updateSource(e,{muted:!0})}unmute(e){this.updateSource(e,{muted:!1})}toggleMute(e){const t=this.sources.get(e);if(!t)return!1;const r=!t.options.muted;return this.updateSource(e,{muted:r}),r}setPan(e,t){this.updateSource(e,{pan:Math.max(-1,Math.min(1,t))})}setMasterVolume(e){this.masterGain&&this.masterGain.gain.setTargetAtTime(Math.max(0,Math.min(2,e)),this.audioContext?.currentTime??0,.01)}getMasterVolume(){return this.masterGain?.gain.value??1}getOutputStream(){return this.outputStream}getOutputTrack(){return this.outputStream?.getAudioTracks()[0]??null}getSourceIds(){return Array.from(this.sources.keys())}getSourceOptions(e){const t=this.sources.get(e);return t?{...t.options}:null}hasSource(e){return this.sources.has(e)}getSourceCount(){return this.sources.size}async resume(){this.audioContext&&"suspended"===this.audioContext.state&&await this.audioContext.resume()}async suspend(){this.audioContext&&"running"===this.audioContext.state&&await this.audioContext.suspend()}getState(){return this.audioContext?.state??null}getLevel(){if(!this.analyzer)return 0;const e=new Uint8Array(this.analyzer.frequencyBinCount);this.analyzer.getByteTimeDomainData(e);let t=0;for(let r=0;r<e.length;r++){const i=Math.abs(e[r]-128)/128;i>t&&(t=i)}if(t<1e-4)return 0;const r=(20*Math.log10(t)- -60)/60;return Math.max(0,Math.min(1,r))}getLevels(){const e=this.getLevel();return e>this.peakLevel?this.peakLevel=e:this.peakLevel*=this.peakDecayRate,{level:e,peakLevel:this.peakLevel}}startLevelMonitoring(){if(this.levelMonitoringActive)return;this.levelMonitoringActive=!0;const e=()=>{if(!this.levelMonitoringActive||"running"!==this.audioContext?.state)return;const{level:t,peakLevel:r}=this.getLevels();this.emit("levelUpdate",{level:t,peakLevel:r}),requestAnimationFrame(e)};requestAnimationFrame(e),console.log("[AudioMixer] Level monitoring started")}stopLevelMonitoring(){this.levelMonitoringActive=!1,this.peakLevel=0,console.log("[AudioMixer] Level monitoring stopped")}isMonitoringLevels(){return this.levelMonitoringActive}destroy(){this.stopLevelMonitoring();for(const e of this.sources.keys())this.removeSource(e);this.compressor&&(this.compressor.disconnect(),this.compressor=null),this.limiter&&(this.limiter.disconnect(),this.limiter=null),this.analyzer&&(this.analyzer.disconnect(),this.analyzer=null),this.audioContext&&(this.audioContext.close().catch(()=>{}),this.audioContext=null),this.destination=null,this.masterGain=null,this.outputStream=null,this.removeAllListeners()}}const ze={enabled:!0,maxAttempts:5,baseDelay:1e3,maxDelay:3e4,backoffMultiplier:2};class Ue extends Ae{constructor(e={}){super(),this.reconnectTimeout=null,this.countdownInterval=null,this.reconnectCallback=null,this.config={...ze,...e},this.state={isReconnecting:!1,attemptNumber:0,nextAttemptIn:null,lastError:null}}calculateDelay(e){const t=this.config.baseDelay*Math.pow(this.config.backoffMultiplier,e-1),r=.1*t*(2*Math.random()-1);return Math.min(t+r,this.config.maxDelay)}start(e){this.config.enabled?this.state.isReconnecting?console.log("[ReconnectionManager] Already reconnecting"):(this.reconnectCallback=e,this.state={isReconnecting:!0,attemptNumber:0,nextAttemptIn:null,lastError:null},this.scheduleNextAttempt()):console.log("[ReconnectionManager] Reconnection disabled")}scheduleNextAttempt(){if(!this.state.isReconnecting)return;if(this.state.attemptNumber++,this.state.attemptNumber>this.config.maxAttempts)return void this.handleExhausted();const e=this.calculateDelay(this.state.attemptNumber);this.state.nextAttemptIn=e,console.log(`[ReconnectionManager] Scheduling attempt ${this.state.attemptNumber}/${this.config.maxAttempts} in ${e}ms`),this.emit("attemptStart",{attempt:this.state.attemptNumber,delay:e}),this.startCountdown(e),this.reconnectTimeout=setTimeout(()=>{this.executeAttempt()},e)}startCountdown(e){this.stopCountdown();const t=Date.now();this.countdownInterval=setInterval(()=>{const r=Date.now()-t;this.state.nextAttemptIn=Math.max(0,e-r)},100)}stopCountdown(){this.countdownInterval&&(clearInterval(this.countdownInterval),this.countdownInterval=null)}async executeAttempt(){if(this.stopCountdown(),this.state.nextAttemptIn=null,this.reconnectCallback){console.log(`[ReconnectionManager] Executing attempt ${this.state.attemptNumber}`);try{await this.reconnectCallback(),this.handleSuccess()}catch(e){const t=e instanceof Error?e.message:String(e);this.handleFailure(t)}}else console.error("[ReconnectionManager] No reconnect callback set")}handleSuccess(){console.log("[ReconnectionManager] Reconnection successful"),this.state={isReconnecting:!1,attemptNumber:0,nextAttemptIn:null,lastError:null},this.cleanup(),this.emit("attemptSuccess",void 0)}handleFailure(e){console.log(`[ReconnectionManager] Attempt ${this.state.attemptNumber} failed:`,e),this.state.lastError=e,this.emit("attemptFailed",{attempt:this.state.attemptNumber,error:e}),this.scheduleNextAttempt()}handleExhausted(){console.log("[ReconnectionManager] All reconnection attempts exhausted"),this.emit("exhausted",{totalAttempts:this.config.maxAttempts}),this.stop()}stop(){console.log("[ReconnectionManager] Stopping reconnection"),this.cleanup(),this.state={isReconnecting:!1,attemptNumber:0,nextAttemptIn:null,lastError:this.state.lastError}}cleanup(){this.reconnectTimeout&&(clearTimeout(this.reconnectTimeout),this.reconnectTimeout=null),this.stopCountdown(),this.reconnectCallback=null}reset(){this.stop(),this.state.lastError=null}getState(){return{...this.state}}isReconnecting(){return this.state.isReconnecting}getAttemptNumber(){return this.state.attemptNumber}getMaxAttempts(){return this.config.maxAttempts}updateConfig(e){this.config={...this.config,...e}}destroy(){this.cleanup(),this.removeAllListeners()}}const Le=[{mode:"solo",label:"Solo",icon:"⬜",minSources:1,maxSources:1},{mode:"pip-br",label:"PiP ↘",icon:"◳",minSources:2,maxSources:2},{mode:"pip-bl",label:"PiP ↙",icon:"◲",minSources:2,maxSources:2},{mode:"pip-tr",label:"PiP ↗",icon:"◱",minSources:2,maxSources:2},{mode:"pip-tl",label:"PiP ↖",icon:"◰",minSources:2,maxSources:2},{mode:"split-h",label:"Split ⬌",icon:"▥",minSources:2,maxSources:2},{mode:"split-v",label:"Split ⬍",icon:"▤",minSources:2,maxSources:2},{mode:"focus-l",label:"Focus ◀",icon:"◧",minSources:2,maxSources:2},{mode:"focus-r",label:"Focus ▶",icon:"◨",minSources:2,maxSources:2},{mode:"pip-dual-br",label:"Main+2 PiP",icon:"⊞",minSources:3,maxSources:3},{mode:"pip-dual-bl",label:"Main+2 PiP ↙",icon:"⊟",minSources:3,maxSources:3},{mode:"split-pip-l",label:"Split+PiP",icon:"⊠",minSources:3,maxSources:3},{mode:"split-pip-r",label:"Split+PiP ▶",icon:"⊡",minSources:3,maxSources:3},{mode:"featured",label:"Featured",icon:"⬒",minSources:3,maxSources:99},{mode:"featured-r",label:"Featured ▶",icon:"⬓",minSources:3,maxSources:99},{mode:"grid",label:"Grid",icon:"▦",minSources:2,maxSources:99},{mode:"stack",label:"Stack",icon:"☰",minSources:2,maxSources:99}];let De="letterbox";function Ve(e,t,r,i=De){return{id:`layer-${t}-${e}`,sourceId:e,visible:!0,locked:!1,zIndex:t,transform:{...Te,...r},scalingMode:i}}const Oe=.25,je=.02,Ne=.005;function Fe(e){return[Ve(e[0],0,{x:0,y:0,width:1,height:1})]}function He(e,t,r=.25){if(e.length<2)return Fe(e);const i={tl:{x:je,y:je},tr:{x:1-r-je,y:je},bl:{x:je,y:1-r-je},br:{x:1-r-je,y:1-r-je}}[t];return[Ve(e[0],0,{x:0,y:0,width:1,height:1}),Ve(e[1],1,{x:i.x,y:i.y,width:r,height:r,borderRadius:8})]}function qe(e,t,r=.5){if(e.length<2)return Fe(e);const i=.0025;return"h"===t?[Ve(e[0],0,{x:0,y:0,width:r-i,height:1}),Ve(e[1],0,{x:r+i,y:0,width:1-r-i,height:1})]:[Ve(e[0],0,{x:0,y:0,width:1,height:r-i}),Ve(e[1],0,{x:0,y:r+i,width:1,height:1-r-i})]}function Be(e,t){if(e.length<3)return He(e,"br"===t?"br":"bl");const r=Oe,i=[Ve(e[0],0,{x:0,y:0,width:1,height:1})];return"br"===t?i.push(Ve(e[1],1,{x:.73,y:.47,width:r,height:r,borderRadius:8}),Ve(e[2],2,{x:.73,y:.73,width:r,height:r,borderRadius:8})):i.push(Ve(e[1],1,{x:je,y:.47,width:r,height:r,borderRadius:8}),Ve(e[2],2,{x:je,y:.73,width:r,height:r,borderRadius:8})),i}function Ge(e,t){if(e.length<3)return qe(e,"h");const r=Oe,i=[Ve(e[0],0,{x:0,y:0,width:.4975,height:1}),Ve(e[1],0,{x:.5025,y:0,width:.4975,height:1})];return"l"===t?i.push(Ve(e[2],1,{x:.2275,y:.73,width:r,height:r,borderRadius:8})):i.push(Ve(e[2],1,{x:.73,y:.73,width:r,height:r,borderRadius:8})),i}function Qe(e,t){const r=e.length;if(r<=2)return qe(e,"bottom"===t?"v":"h",.75);const i=Ne,s=.795,o=[];if("bottom"===t){o.push(Ve(e[0],0,{x:0,y:0,width:1,height:s}));const t=r-1,n=(1-i*(t-1))/t;for(let t=1;t<r;t++)o.push(Ve(e[t],0,{x:(t-1)*(n+i),y:.8,width:n,height:.2}))}else{o.push(Ve(e[0],0,{x:0,y:0,width:s,height:1}));const t=r-1,n=(1-i*(t-1))/t;for(let t=1;t<r;t++)o.push(Ve(e[t],0,{x:.8,y:(t-1)*(n+i),width:.2,height:n}))}return o}function Xe(e,t){if(0===t.length)return[];De=e.scalingMode??"letterbox";const r=e.pipScale??Oe,i=e.splitRatio??.5,s=Math.min(Math.max(i,.0025),.9975);switch(e.mode){case"solo":case"fullscreen":default:return Fe(t);case"pip-br":case"pip":return He(t,"br",r);case"pip-bl":return He(t,"bl",r);case"pip-tr":return He(t,"tr",r);case"pip-tl":return He(t,"tl",r);case"split-h":case"side-by-side":return qe(t,"h",s);case"split-v":return qe(t,"v",s);case"focus-l":return qe(t,"h",.7);case"focus-r":return qe(t,"h",.3);case"pip-dual-br":return Be(t,"br");case"pip-dual-bl":return Be(t,"bl");case"split-pip-l":return Ge(t,"l");case"split-pip-r":return Ge(t,"r");case"featured":return Qe(t,"bottom");case"featured-r":return Qe(t,"right");case"grid":return function(e){const t=e.length;if(0===t)return[];if(1===t)return Fe(e);if(2===t)return qe(e,"h");const r=Ne,i=Math.ceil(Math.sqrt(t)),s=Math.ceil(t/i),o=(1-r*(i-1))/i,n=(1-r*(s-1))/s,a=[];for(let c=0;c<t;c++){const l=c%i,d=Math.floor(c/i),h=d===s-1,u=h?(t-1)%i+1:i;let p=0;h&&u<i&&(p=(1-(u*o+(u-1)*r))/2);const g=p+l*(o+r),f=d*(n+r);a.push(Ve(e[c],c,{x:g,y:f,width:o,height:n}))}return a}(t);case"stack":return function(e){const t=e.length;if(0===t)return[];if(1===t)return Fe(e);const r=Ne,i=(1-r*(t-1))/t;return e.map((e,t)=>Ve(e,0,{x:0,y:t*(i+r),width:1,height:i}))}(t)}}const Ke={durationMs:300,easing:"ease-out"};class Je extends Ae{constructor(e){super(),this.scenes=new Map,this.activeSceneId=null,this.defaultLayoutTransition=Ke,this.isAnimating=!1,this.worker=null,this.workerReady=!1,this.pendingMessages=[],this.frameProcessors=new Map,this.frameReaders=new Map,this.outputCanvas=null,this.outputStream=null,this.lastStats={fps:0,frameTimeMs:0},this.config={...Me,...e},this.defaultTransition=this.config.defaultTransition||{type:"fade",durationMs:500,easing:"ease-in-out"},this.currentLayout={mode:"solo",scalingMode:"letterbox"}}async initializeWorker(){let e=null;const r=[new URL("../workers/compositor.worker.js",t&&"SCRIPT"===t.tagName.toUpperCase()&&t.src||new URL("fw-streamcrafter.iife.js",document.baseURI).href).href,"/node_modules/@livepeer-frameworks/streamcrafter-core/dist/workers/compositor.worker.js","/workers/compositor.worker.js","./workers/compositor.worker.js"];console.log("[SceneManager] Trying fallback worker paths:",r);for(const t of r){if(this.worker)break;try{console.log(`[SceneManager] Trying worker path: ${t}`);const e=(()=>{try{return new Worker(t,{type:"module"})}catch{return new Worker(t)}})();if(await new Promise(t=>{const r=setTimeout(()=>{e.terminate(),t(!1)},2e3);e.onerror=()=>{clearTimeout(r),e.terminate(),t(!1)},e.onmessage=()=>{clearTimeout(r),t(!0)},setTimeout(()=>{clearTimeout(r),t(!0)},500)})){e.terminate();try{this.worker=new Worker(t,{type:"module"})}catch{this.worker=new Worker(t)}console.log(`[SceneManager] Worker loaded from: ${t}`);break}console.warn(`[SceneManager] Worker failed to load from: ${t}`)}catch(r){e=r instanceof Error?r:new Error(String(r)),console.warn(`[SceneManager] Failed to load worker from ${t}:`,e.message),this.worker=null}}if(!this.worker)throw new Error(`Failed to initialize compositor worker. Make sure the worker is bundled correctly. Last error: ${e?.message??"unknown"}`)}async initialize(){if(console.log("[SceneManager] initialize() called"),this.worker)throw new Error("SceneManager already initialized");console.log("[SceneManager] Creating output canvas",{width:this.config.width,height:this.config.height}),this.outputCanvas=document.createElement("canvas"),this.outputCanvas.width=this.config.width,this.outputCanvas.height=this.config.height;const e=this.outputCanvas.transferControlToOffscreen();console.log("[SceneManager] Created OffscreenCanvas"),console.log("[SceneManager] Initializing worker..."),await this.initializeWorker(),console.log("[SceneManager] Worker initialized, waiting for ready...");const t=new Promise((e,t)=>{const r=setTimeout(()=>{console.error("[SceneManager] Worker initialization timeout"),t(new Error("Compositor worker initialization timeout"))},1e4);this.worker.onmessage=t=>{console.log("[SceneManager] Worker message:",t.data.type),"ready"===t.data.type&&(clearTimeout(r),e()),this.handleWorkerMessage(t.data)},this.worker.onerror=e=>{console.error("[SceneManager] Worker error:",e.message),clearTimeout(r),t(new Error(e.message))}});console.log("[SceneManager] Sending init message to worker"),this.worker.postMessage({type:"init",config:this.config,canvas:e},[e]),await t,console.log("[SceneManager] Worker is ready"),console.log("[SceneManager] Creating default scene");const r=this.createScene("Default");console.log("[SceneManager] Setting active scene:",r.id),this.setActiveScene(r.id),console.log("[SceneManager] Initialize complete")}handleWorkerMessage(e){switch(e.type){case"ready":this.workerReady=!0;for(const e of this.pendingMessages)this.worker?.postMessage(e);this.pendingMessages=[];break;case"stats":this.lastStats=e.stats,this.emit("statsUpdate",{stats:e.stats});break;case"transitionComplete":this.emit("transitionCompleted",{sceneId:e.sceneId});break;case"layoutAnimationComplete":{this.isAnimating=!1;const e=this.getActiveScene();if(e){const t=e.layers.filter(e=>e.visible).map(e=>e.sourceId);e.layers=Xe(this.currentLayout,t)}this.emit("layoutAnimationCompleted",{layout:this.currentLayout});break}case"rendererChanged":this.config.renderer=e.renderer,this.emit("rendererChanged",{renderer:e.renderer});break;case"error":this.emit("error",{message:e.message})}}sendToWorker(e,t){this.workerReady&&this.worker?this.worker.postMessage(e,t||[]):this.pendingMessages.push(e)}createScene(e,t="#000000"){const r=`scene-${Date.now()}-${Math.random().toString(36).slice(2,9)}`,i={id:r,name:e,layers:[],backgroundColor:t};return this.scenes.set(r,i),this.emit("sceneCreated",{scene:i}),i}deleteScene(e){if(!this.scenes.has(e))throw new Error(`Scene not found: ${e}`);if(this.activeSceneId===e)throw new Error("Cannot delete the active scene");this.scenes.delete(e),this.emit("sceneDeleted",{sceneId:e})}getScene(e){return this.scenes.get(e)}getAllScenes(){return Array.from(this.scenes.values())}getActiveScene(){return this.activeSceneId?this.scenes.get(this.activeSceneId):void 0}setActiveScene(e){const t=this.scenes.get(e);if(!t)throw new Error(`Scene not found: ${e}`);const r=this.activeSceneId;this.activeSceneId=e,this.sendToWorker({type:"updateScene",scene:t}),this.emit("sceneActivated",{scene:t,previousSceneId:r})}async transitionTo(e,t){const r=this.scenes.get(e);if(!r)throw new Error(`Scene not found: ${e}`);const i=t||this.defaultTransition;this.sendToWorker({type:"updateScene",scene:r}),this.sendToWorker({type:"startTransition",transition:i,toSceneId:e});const s=this.activeSceneId;this.emit("transitionStarted",{fromSceneId:s||"",toSceneId:e,transition:i}),this.activeSceneId=e}addLayer(e,t,r){const i=this.scenes.get(e);if(!i)throw new Error(`Scene not found: ${e}`);const s=`layer-${Date.now()}-${Math.random().toString(36).slice(2,9)}`,o=i.layers.reduce((e,t)=>Math.max(e,t.zIndex),-1),n={id:s,sourceId:t,visible:!0,locked:!1,zIndex:o+1,transform:{...Te,...r},scalingMode:this.currentLayout.scalingMode??"letterbox"};return i.layers.push(n),this.updateSceneInWorker(i),this.emit("layerAdded",{sceneId:e,layer:n}),n}removeLayer(e,t){const r=this.scenes.get(e);if(!r)throw new Error(`Scene not found: ${e}`);const i=r.layers.findIndex(e=>e.id===t);if(-1===i)throw new Error(`Layer not found: ${t}`);r.layers.splice(i,1),this.updateSceneInWorker(r),this.emit("layerRemoved",{sceneId:e,layerId:t})}updateLayerTransform(e,t,r){const i=this.scenes.get(e);if(!i)throw new Error(`Scene not found: ${e}`);const s=i.layers.find(e=>e.id===t);if(!s)throw new Error(`Layer not found: ${t}`);s.transform={...s.transform,...r},this.updateSceneInWorker(i),this.emit("layerUpdated",{sceneId:e,layer:s})}setLayerVisibility(e,t,r){const i=this.scenes.get(e);if(!i)throw new Error(`Scene not found: ${e}`);const s=i.layers.find(e=>e.id===t);if(!s)throw new Error(`Layer not found: ${t}`);s.visible=r,this.updateSceneInWorker(i),this.emit("layerUpdated",{sceneId:e,layer:s})}reorderLayers(e,t){const r=this.scenes.get(e);if(!r)throw new Error(`Scene not found: ${e}`);t.forEach((e,t)=>{const i=r.layers.find(t=>t.id===e);i&&(i.zIndex=t)}),this.updateSceneInWorker(r)}cycleSourceOrder(e="forward"){const t=this.getActiveScene();if(!t||t.layers.length<2)return void console.warn("[SceneManager] cycleSourceOrder: Need at least 2 layers");console.log("[SceneManager] cycleSourceOrder BEFORE:",{direction:e,layers:t.layers.map(e=>({id:e.id,sourceId:e.sourceId,zIndex:e.zIndex}))});const r=[...t.layers].sort((e,t)=>e.zIndex-t.zIndex),i=r.map(e=>e.id);if(console.log("[SceneManager] sorted layerIds before rotate:",[...i]),"forward"===e){const e=i.shift();e&&i.push(e)}else{const e=i.pop();e&&i.unshift(e)}console.log("[SceneManager] layerIds after rotate:",[...i]),this.reorderLayers(t.id,i),console.log("[SceneManager] after reorderLayers:",{layers:t.layers.map(e=>({id:e.id,sourceId:e.sourceId,zIndex:e.zIndex}))}),this.currentLayout&&this.applyLayout(this.currentLayout,!0,{durationMs:200,easing:"ease-out"}),console.log("[SceneManager] cycleSourceOrder AFTER applyLayout:",{layers:t.layers.map(e=>({id:e.id,sourceId:e.sourceId,zIndex:e.zIndex}))}),this.emit("layerUpdated",{sceneId:t.id,layer:t.layers[0]})}updateSceneInWorker(e){this.activeSceneId===e.id&&this.sendToWorker({type:"updateScene",scene:e})}applyLayout(e,t=!0,r){const i=this.getActiveScene();if(!i)return void console.warn("[SceneManager] applyLayout: No active scene");this.currentLayout=e;const s=[...i.layers].filter(e=>e.visible).sort((e,t)=>e.zIndex-t.zIndex).map(e=>e.sourceId);console.log("[SceneManager] applyLayout",{mode:e.mode,sourceIds:s,currentLayerCount:i.layers.length,animate:t});const o=Xe(e,s),n={...i,layers:o};if(i.layers=o,t&&o.length>0){const t={...this.defaultLayoutTransition,...r};this.isAnimating=!0,this.emit("layoutAnimationStarted",{layout:e}),this.sendToWorker({type:"animateLayout",targetScene:n,transition:t})}else this.updateSceneInWorker(i);this.sendToWorker({type:"updateLayout",layout:e})}setDefaultLayoutTransition(e){this.defaultLayoutTransition={...this.defaultLayoutTransition,...e}}isLayoutAnimating(){return this.isAnimating}getCurrentLayout(){return this.currentLayout}bindSource(e,t){const r=t.getVideoTracks()[0];if(!r)throw new Error("No video track in stream");const i=globalThis.MediaStreamTrackProcessor;if(!i)return void console.warn("[SceneManager] MediaStreamTrackProcessor not available, compositor will not work");const s=new i({track:r});this.frameProcessors.set(e,s);const o=s.readable.getReader();this.frameReaders.set(e,o);const n=async()=>{try{const{done:t,value:r}=await o.read();if(t||!r)return;const i=r;this.sendToWorker({type:"sourceFrame",sourceId:e,frame:i},[i]),n()}catch(e){"AbortError"!==e?.name&&console.error("[SceneManager] Frame read error:",e)}};n()}unbindSource(e){const t=this.frameReaders.get(e);t&&(t.cancel().catch(()=>{}),this.frameReaders.delete(e));this.frameProcessors.get(e)&&this.frameProcessors.delete(e)}async bindImageSource(e,t){const r=await fetch(t),i=await r.blob(),s=await createImageBitmap(i);this.sendToWorker({type:"sourceImage",sourceId:e,bitmap:s},[s])}applyFilter(e,t){this.sendToWorker({type:"applyFilter",layerId:e,filter:t})}getOutputTrack(){return this.outputCanvas?(this.outputStream||(this.outputStream=this.outputCanvas.captureStream(this.config.frameRate)),this.outputStream.getVideoTracks()[0]||null):null}getOutputStream(){return this.outputCanvas?(this.outputStream||(this.outputStream=this.outputCanvas.captureStream(this.config.frameRate)),this.outputStream):null}getRendererType(){return this.config.renderer}setRenderer(e){this.config.renderer=e,this.sendToWorker({type:"setRenderer",renderer:e})}getStats(){return this.lastStats}getConfig(){return{...this.config}}updateOutputConfig(e){const t=e.width??this.config.width,r=e.height??this.config.height,i=e.frameRate??this.config.frameRate;return(t!==this.config.width||r!==this.config.height||i!==this.config.frameRate)&&(this.config.width=t,this.config.height=r,this.config.frameRate=i,this.sendToWorker({type:"resize",width:t,height:r,frameRate:i}),!0)}isInitialized(){return this.workerReady}destroy(){for(const[e]of this.frameProcessors)this.unbindSource(e);this.frameProcessors.clear(),this.frameReaders.clear(),this.worker&&(this.sendToWorker({type:"destroy"}),this.worker.terminate(),this.worker=null),this.scenes.clear(),this.activeSceneId=null,this.outputStream=null,this.outputCanvas=null,this.workerReady=!1,this.removeAllListeners()}}const Ze={professional:{codec:"avc1.4d0032",width:1920,height:1080,bitrate:8e6,framerate:30},broadcast:{codec:"avc1.4d0028",width:1920,height:1080,bitrate:45e5,framerate:30},conference:{codec:"avc1.4d001f",width:1280,height:720,bitrate:25e5,framerate:30},low:{codec:"avc1.42001e",width:640,height:480,bitrate:1e6,framerate:24}},Ye={codec:"opus",sampleRate:48e3,numberOfChannels:2,bitrate:128e3};class et extends Ae{constructor(e={}){super(),this.worker=null,this.videoProcessor=null,this.audioProcessor=null,this.videoReader=null,this.audioReader=null,this.isRunning=!1,this.isInitialized=!1,this.stats=null,this.config=null,this.pendingRequests=new Map,this.requestCounter=0,this.providedWorker=null,this.options={workerUrl:e.workerUrl??"",debug:e.debug??!1,timeout:e.timeout??1e4},this.providedWorker=e.worker??null}log(e,t){this.options.debug&&console.log(`[EncoderManager] ${e}`,t??"")}generateRequestId(){return`req_${++this.requestCounter}_${Date.now()}`}sendRequest(e){return new Promise((t,r)=>{if(!this.worker)return void r(new Error("Worker not created"));const i=e.requestId;if(!i)return this.worker.postMessage(e),void t(void 0);const s=setTimeout(()=>{this.pendingRequests.delete(i),r(new Error(`Request ${i} timed out`))},this.options.timeout);this.pendingRequests.set(i,{resolve:t,reject:r,timer:s}),this.worker.postMessage(e)})}handleResponse(e,t,r){const i=this.pendingRequests.get(e);i&&(clearTimeout(i.timer),this.pendingRequests.delete(e),t?i.resolve():i.reject(new Error(r??"Unknown error")))}tryCreateWorker(e,t=!0){return new Promise(r=>{try{const i=new Worker(e,t?{type:"module"}:void 0);let s=!1;const o=()=>{i.removeEventListener("message",n),i.removeEventListener("error",a)},n=e=>{s||(s=!0,o(),r(i))},a=t=>{s||(s=!0,o(),this.log("Worker failed to load from: "+e.toString(),t.message),i.terminate(),r(null))};i.addEventListener("message",n),i.addEventListener("error",a),setTimeout(()=>{s||(s=!0,o(),r(i))},2e3)}catch(t){this.log("Failed to create worker: "+e.toString(),t),r(null)}})}async createWorkerAsync(){if(this.providedWorker)return this.log("Using provided worker instance"),this.providedWorker;if(this.options.workerUrl){this.log("Creating worker from URL:",this.options.workerUrl);const e=await this.tryCreateWorker(this.options.workerUrl);if(e)return e}const e=[];try{const r=new URL("../workers/encoder.worker.js",t&&"SCRIPT"===t.tagName.toUpperCase()&&t.src||new URL("fw-streamcrafter.iife.js",document.baseURI).href);e.push({url:r,description:"import.meta.url relative"})}catch{}e.push({url:"/workers/encoder.worker.js",description:"Vite dev server"},{url:"/node_modules/@livepeer-frameworks/streamcrafter-core/dist/workers/encoder.worker.js",description:"node_modules"},{url:"./workers/encoder.worker.js",description:"relative path"});for(const{url:t,description:r}of e){this.log(`Trying worker path: ${t.toString()} (${r})`);const e=await this.tryCreateWorker(t);if(e)return this.log("Worker loaded from:",t.toString()),e}throw new Error("Failed to create encoder worker. Provide a worker URL via options.workerUrl or ensure workers are served correctly.")}createWorker(){if(this.providedWorker)return this.providedWorker;if(this.options.workerUrl)return new Worker(this.options.workerUrl,{type:"module"});try{const e=new URL("../workers/encoder.worker.js",t&&"SCRIPT"===t.tagName.toUpperCase()&&t.src||new URL("fw-streamcrafter.iife.js",document.baseURI).href);return new Worker(e,{type:"module"})}catch{}return new Worker("/workers/encoder.worker.js",{type:"module"})}handleWorkerMessage(e){switch(e.type){case"ready":this.log("Worker ready"),e.requestId&&this.handleResponse(e.requestId,!0),this.emit("ready",void 0);break;case"started":this.log("Encoding started"),e.requestId&&this.handleResponse(e.requestId,!0),this.emit("started",void 0);break;case"stopped":this.log("Encoding stopped"),e.requestId&&this.handleResponse(e.requestId,!0),this.emit("stopped",void 0);break;case"flushed":this.log("Encoder flushed"),e.requestId&&this.handleResponse(e.requestId,!0);break;case"stats":this.stats=e.data,this.emit("stats",this.stats);break;case"error":{const t=e.data;console.error("[EncoderManager] Worker error:",t),e.requestId&&this.handleResponse(e.requestId,!1,t.message),this.emit("error",t);break}case"encodedVideoChunk":this.emit("videoChunk",e.data);break;case"encodedAudioChunk":this.emit("audioChunk",e.data);break;default:this.log("Unknown message from worker",e)}}async initialize(e,t){if(this.log("Initializing encoder",t),this.config=t,"undefined"==typeof VideoEncoder||"undefined"==typeof AudioEncoder)throw new Error("WebCodecs not supported in this browser");if("undefined"==typeof MediaStreamTrackProcessor)throw new Error("MediaStreamTrackProcessor not supported in this browser");const r=e.getVideoTracks()[0],i=e.getAudioTracks()[0];r&&(this.videoProcessor=new MediaStreamTrackProcessor({track:r})),i&&(this.audioProcessor=new MediaStreamTrackProcessor({track:i})),this.worker=await this.createWorkerAsync(),this.worker.onmessage=e=>{this.handleWorkerMessage(e.data)},this.worker.onerror=e=>{console.error("[EncoderManager] Worker error:",e),this.emit("error",{message:e.message,fatal:!0})};const s=this.generateRequestId();this.log("Sending initialize to worker",{requestId:s}),await this.sendRequest({type:"initialize",requestId:s,data:{config:t}}),this.isInitialized=!0,this.log("Worker initialized and ready")}async start(){if(!this.isInitialized)throw new Error("EncoderManager not initialized");if(this.isRunning)return;this.log("Starting encoder");const e=this.generateRequestId();await this.sendRequest({type:"start",requestId:e}),this.isRunning=!0,this.videoProcessor&&this.startVideoProcessing(),this.audioProcessor&&this.startAudioProcessing(),this.log("Encoder started, frame processing active")}async startVideoProcessing(){if(this.videoProcessor&&this.worker)try{this.videoReader=this.videoProcessor.readable.getReader();const e=this.videoReader;for(;this.isRunning;){const{value:t,done:r}=await e.read();if(r||!t)break;try{this.worker.postMessage({type:"videoFrame",data:t},[t])}catch(e){console.error("[EncoderManager] Error sending video frame:",e);try{t.close()}catch{}}}}catch(e){this.isRunning&&console.error("[EncoderManager] Video processing error:",e)}}async startAudioProcessing(){if(this.audioProcessor&&this.worker)try{this.audioReader=this.audioProcessor.readable.getReader();const e=this.audioReader;for(;this.isRunning;){const{value:t,done:r}=await e.read();if(r||!t)break;try{this.worker.postMessage({type:"audioData",data:t},[t])}catch(e){console.error("[EncoderManager] Error sending audio data:",e);try{t.close()}catch{}}}}catch(e){this.isRunning&&console.error("[EncoderManager] Audio processing error:",e)}}async stop(){if(this.isRunning){if(this.isRunning=!1,this.log("Stopping encoder"),this.videoReader){try{await this.videoReader.cancel(),this.videoReader.releaseLock()}catch{}this.videoReader=null}if(this.audioReader){try{await this.audioReader.cancel(),this.audioReader.releaseLock()}catch{}this.audioReader=null}if(this.worker&&this.isInitialized){const e=this.generateRequestId();try{await this.sendRequest({type:"stop",requestId:e})}catch(e){this.log("Stop request failed (may be expected)",e)}}this.emit("stopped",void 0)}}async updateConfig(e){if(!this.worker||!this.isInitialized)throw new Error("EncoderManager not initialized");const t=this.generateRequestId();await this.sendRequest({type:"updateConfig",requestId:t,data:e}),this.config&&(e.video&&(this.config.video={...this.config.video,...e.video}),e.audio&&(this.config.audio={...this.config.audio,...e.audio}))}async updateInputStream(e){if(!this.isInitialized||!this.worker)throw new Error("EncoderManager not initialized");const t=this.isRunning;if(this.log("Updating input stream (hot-swap)",{wasRunning:t}),this.videoReader){try{await this.videoReader.cancel(),this.videoReader.releaseLock()}catch{}this.videoReader=null}if(this.audioReader){try{await this.audioReader.cancel(),this.audioReader.releaseLock()}catch{}this.audioReader=null}const r=e.getVideoTracks()[0],i=e.getAudioTracks()[0];this.videoProcessor=r?new MediaStreamTrackProcessor({track:r}):null,this.audioProcessor=i?new MediaStreamTrackProcessor({track:i}):null,t&&(this.videoProcessor&&this.startVideoProcessing(),this.audioProcessor&&this.startAudioProcessing(),this.log("Input stream updated, processing restarted"))}async flush(){if(!this.worker||!this.isInitialized)return;const e=this.generateRequestId();await this.sendRequest({type:"flush",requestId:e})}getStats(){return this.stats}getConfig(){return this.config}getIsInitialized(){return this.isInitialized}getIsRunning(){return this.isRunning}destroy(){this.stop(),this.isInitialized=!1,this.videoProcessor=null,this.audioProcessor=null;for(const[,e]of this.pendingRequests)clearTimeout(e.timer),e.reject(new Error("EncoderManager destroyed"));this.pendingRequests.clear(),this.worker&&(this.worker.terminate(),this.worker=null),this.removeAllListeners()}}function tt(){const e={videoEncoder:"undefined"!=typeof VideoEncoder,audioEncoder:"undefined"!=typeof AudioEncoder,mediaStreamTrackProcessor:"undefined"!=typeof MediaStreamTrackProcessor,mediaStreamTrackGenerator:"undefined"!=typeof MediaStreamTrackGenerator};return{webcodecs:e,webrtc:{peerConnection:"undefined"!=typeof RTCPeerConnection,replaceTrack:"undefined"!=typeof RTCRtpSender&&"replaceTrack"in RTCRtpSender.prototype,insertableStreams:"undefined"!=typeof RTCRtpSender&&"createEncodedStreams"in RTCRtpSender.prototype,scriptTransform:"undefined"!=typeof RTCRtpScriptTransform},mediaDevices:{getUserMedia:"undefined"!=typeof navigator&&void 0!==navigator.mediaDevices&&"function"==typeof navigator.mediaDevices.getUserMedia,getDisplayMedia:"undefined"!=typeof navigator&&void 0!==navigator.mediaDevices&&"function"==typeof navigator.mediaDevices.getDisplayMedia,enumerateDevices:"undefined"!=typeof navigator&&void 0!==navigator.mediaDevices&&"function"==typeof navigator.mediaDevices.enumerateDevices},recommended:e.videoEncoder&&e.audioEncoder&&e.mediaStreamTrackProcessor&&e.mediaStreamTrackGenerator?"webcodecs":"mediastream"}}function rt(){return"undefined"!=typeof RTCRtpScriptTransform}function it(){return"undefined"!=typeof VideoEncoder&&"undefined"!=typeof AudioEncoder&&"undefined"!=typeof MediaStreamTrackProcessor&&"undefined"!=typeof MediaStreamTrackGenerator&&rt()}let st=0;class ot extends Ae{constructor(e){super(),this.whipClient=null,this.whipEndpoints=[],this.currentEndpointIndex=0,this.isStoppingIntentionally=!1,this.state="idle",this.stateContext={},this.sources=new Map,this.outputStream=null,this.statsInterval=null,this.lastStats=null,this.statsInFlight=!1,this.sceneManager=null,this.compositorBaseConfig=null,this.encoderManager=null,this.encoderOverrides={},this.config=e,this.currentProfile=e.profile||"broadcast",this.whipEndpoints=this.buildWhipEndpoints(e),this.deviceManager=new Ie,this.screenCapture=new Pe,this.audioMixer=new We,this.reconnectionManager=new Ue(e.reconnection);const t=tt();this.useWebCodecs=e.useWebCodecs??"webcodecs"===t.recommended,this.setupEventForwarding(),this.log("IngestControllerV2 initialized",{useWebCodecs:this.useWebCodecs,profile:this.currentProfile,audioMixing:e.audioMixing??!1})}buildWhipEndpoints(e){if(e.whipUrls&&e.whipUrls.length>0){return[e.whipUrl,...e.whipUrls].filter((e,t,r)=>r.indexOf(e)===t)}return[e.whipUrl]}getCurrentWhipUrl(){return this.whipEndpoints[this.currentEndpointIndex]??this.config.whipUrl}getNextWhipUrl(){return this.whipEndpoints.length>1&&(this.currentEndpointIndex=(this.currentEndpointIndex+1)%this.whipEndpoints.length),this.getCurrentWhipUrl()}log(e,t){this.config.debug&&console.log(`[IngestControllerV2] ${e}`,t??"")}setupEventForwarding(){this.deviceManager.on("devicesChanged",e=>{this.emit("deviceChange",e)}),this.deviceManager.on("error",e=>{this.emit("error",{error:e.message,recoverable:!0})}),this.screenCapture.on("ended",e=>{if(this.log("Screen capture ended",e),e.stream)for(const[t,r]of this.sources)if("screen"===r.type&&r.stream===e.stream){this.removeSource(t);break}}),this.screenCapture.on("error",e=>{this.emit("error",{error:e.message,recoverable:!0})}),this.reconnectionManager.on("attemptStart",e=>{this.emit("reconnectionAttempt",{attempt:e.attempt,maxAttempts:this.reconnectionManager.getMaxAttempts()})}),this.reconnectionManager.on("attemptSuccess",()=>{this.emit("reconnectionSuccess",void 0),this.setState("streaming")}),this.reconnectionManager.on("attemptFailed",e=>{this.log("Reconnection attempt failed",e)}),this.reconnectionManager.on("exhausted",()=>{this.emit("reconnectionFailed",{error:"All reconnection attempts exhausted"}),this.setState("error",{error:"Connection lost - reconnection failed"})})}setState(e,t){this.state=e,t&&(this.stateContext={...this.stateContext,...t}),this.stateContext.sources=Array.from(this.sources.values()),this.stateContext.activeProfile=this.currentProfile,this.stateContext.reconnection=this.reconnectionManager.getState(),this.emit("stateChange",{state:this.state,context:this.stateContext})}async ensureAudioMixer(){this.config.audioMixing&&null===this.audioMixer.getState()&&await this.audioMixer.initialize()}addMediaSource(e,t,r){const i=function(e){return`${e}-${++st}-${Date.now()}`}(e),s=t.getVideoTracks().length>0,o=Array.from(this.sources.values()).filter(e=>e.stream.getVideoTracks().length>0),n=s&&0===o.length,a={id:i,type:e,stream:t,label:r,active:!0,muted:!1,volume:1,primaryVideo:n};if(this.sources.set(i,a),this.log(`Added source: ${i} (${e})`,{label:r,tracks:t.getTracks().length,primaryVideo:n}),this.config.audioMixing){const e=t.getAudioTracks()[0];e&&this.audioMixer.addSource(i,e,{volume:1})}if(this.sceneManager&&this.sceneManager.isInitialized()){this.log("Binding source to compositor",{sourceId:i}),this.sceneManager.bindSource(i,t);const e=this.sceneManager.getActiveScene();this.log("Adding layer to scene",{sourceId:i,activeSceneId:e?.id,sceneLayers:e?.layers.length}),e&&(this.sceneManager.addLayer(e.id,i),this.log("Layer added",{sourceId:i,layerCount:e.layers.length}))}else this.log("Compositor not ready when adding source",{sourceId:i,hasSceneManager:!!this.sceneManager,isInitialized:this.sceneManager?.isInitialized()??!1});return this.emit("sourceAdded",{source:a}),this.updateOutputStreamFromSources(),a}removeSource(e){const t=this.sources.get(e);if(!t)return;const r=t.primaryVideo;if(t.stream.getTracks().forEach(e=>e.stop()),this.config.audioMixing&&this.audioMixer.removeSource(e),this.sceneManager){this.sceneManager.unbindSource(e);const t=this.sceneManager.getActiveScene();if(t){const r=t.layers.find(t=>t.sourceId===e);r&&this.sceneManager.removeLayer(t.id,r.id)}}if(this.sources.delete(e),this.log(`Removed source: ${e}`),r){const e=Array.from(this.sources.values()).filter(e=>e.stream.getVideoTracks().length>0);e.length>0&&(e[0].primaryVideo=!0,this.sources.set(e[0].id,e[0]),this.log(`Reassigned primary video to: ${e[0].id}`))}this.emit("sourceRemoved",{sourceId:e}),this.updateOutputStreamFromSources()}setPrimaryVideoSource(e){const t=this.sources.get(e);if(t)if(0!==t.stream.getVideoTracks().length){for(const[e,t]of this.sources)t.primaryVideo&&(t.primaryVideo=!1,this.sources.set(e,t));t.primaryVideo=!0,this.sources.set(e,t),this.log(`Set primary video source: ${e}`),this.emit("sourceUpdated",{source:t,changes:{primaryVideo:!0}}),this.updateOutputStreamFromSources()}else this.log(`Cannot set source ${e} as primary - no video track`)}getPrimaryVideoSource(){for(const e of this.sources.values())if(e.primaryVideo)return e;return null}updateOutputStreamFromSources(){const e=Array.from(this.sources.values()).filter(e=>e.active);if(0===e.length)return void(this.outputStream=null);const t=[];if(this.sceneManager&&this.sceneManager.isInitialized()){const e=this.sceneManager.getOutputTrack();e&&t.push(e)}else{const r=e.filter(e=>e.stream.getVideoTracks().length>0),i=r.find(e=>e.primaryVideo)||r[0];if(i){const e=i.stream.getVideoTracks()[0];e&&t.push(e)}}if(this.config.audioMixing&&"running"===this.audioMixer.getState()){const e=this.audioMixer.getOutputTrack();e&&t.push(e)}else for(const r of e){const e=r.stream.getAudioTracks()[0];if(e&&!r.muted){t.push(e);break}}this.outputStream=t.length>0?new MediaStream(t):null,this.whipClient&&"streaming"===this.state&&this.updateWhipTracks(),this.encoderManager&&this.outputStream&&this.encoderManager.updateInputStream(this.outputStream).catch(e=>{this.log("Failed to update encoder input stream",e)}),this.log("Output stream updated",{videoTracks:this.outputStream?.getVideoTracks().length??0,audioTracks:this.outputStream?.getAudioTracks().length??0,usingCompositor:!!this.sceneManager})}async updateWhipTracks(){if(this.whipClient&&this.outputStream)try{const e=this.whipClient.getPeerConnection();if(!e)return;const t=e.getSenders(),r=this.outputStream.getVideoTracks()[0],i=t.find(e=>"video"===e.track?.kind);i&&await i.replaceTrack(r??null);const s=this.outputStream.getAudioTracks()[0],o=t.find(e=>"audio"===e.track?.kind);o&&await o.replaceTrack(s??null)}catch(e){this.log("Error updating WHIP tracks",e)}}async startCamera(e={}){this.log("Starting camera capture",e),this.setState("requesting_permissions");try{await this.ensureAudioMixer();const t=e.profile||this.currentProfile,r={...e,profile:t};if(this.encoderOverrides?.video){const e=this.encoderOverrides.video,i=_e(t);r.customConstraints={video:{...i,...e.width&&{width:{ideal:e.width}},...e.height&&{height:{ideal:e.height}},...e.framerate&&{frameRate:{ideal:e.framerate}}},audio:!0},this.log("Using encoder overrides for capture constraints:",r.customConstraints)}const i=await this.deviceManager.getUserMedia(r),s=await this.getCameraLabel(i),o=this.addMediaSource("camera",i,s);return this.setState("capturing",{hasVideo:i.getVideoTracks().length>0,hasAudio:i.getAudioTracks().length>0}),o}catch(e){throw this.setState("error",{error:e instanceof Error?e.message:String(e)}),e}}async getCameraLabel(e){const t=e.getVideoTracks()[0];return t&&t.label||"Camera"}async startScreenShare(e={}){this.log("Starting screen share",e),this.setState("requesting_permissions");try{await this.ensureAudioMixer();const t={...e};if(this.encoderOverrides?.video){const e=this.encoderOverrides.video;t.video={...e.width&&{width:{ideal:e.width}},...e.height&&{height:{ideal:e.height}},...e.framerate&&{frameRate:{ideal:e.framerate}}},this.log("Using encoder overrides for screen capture constraints:",t.video)}const r=await this.screenCapture.start(t);if(r){const e=r.getVideoTracks()[0],t=e?.label||`Screen ${this.screenCapture.getCaptureCount()}`,i=this.addMediaSource("screen",r,t);return this.setState("capturing",{hasVideo:!0,isScreenShare:!0}),i}return this.sources.size>0?this.setState("capturing"):this.setState("idle"),null}catch(e){throw this.setState("error",{error:e instanceof Error?e.message:String(e)}),e}}addCustomSource(e,t){return this.addMediaSource("custom",e,t)}setSourceVolume(e,t){const r=this.sources.get(e);r&&(r.volume=Math.max(0,Math.min(2,t)),this.sources.set(e,r),this.config.audioMixing&&this.audioMixer.setVolume(e,r.volume),this.emit("sourceUpdated",{source:r,changes:{volume:r.volume}}))}setSourceMuted(e,t){const r=this.sources.get(e);r&&(r.muted=t,this.sources.set(e,r),this.config.audioMixing?t?this.audioMixer.mute(e):this.audioMixer.unmute(e):r.stream.getAudioTracks().forEach(e=>{e.enabled=!t}),this.emit("sourceUpdated",{source:r,changes:{muted:t}}),this.updateOutputStreamFromSources())}setSourceActive(e,t){const r=this.sources.get(e);r&&(r.active=t,this.sources.set(e,r),this.emit("sourceUpdated",{source:r,changes:{active:t}}),this.updateOutputStreamFromSources())}setMasterVolume(e){this.config.audioMixing&&this.audioMixer.setMasterVolume(e)}getMasterVolume(){return this.config.audioMixing?this.audioMixer.getMasterVolume():1}async stopCapture(){this.log("Stopping all capture");for(const e of Array.from(this.sources.keys()))this.removeSource(e);this.deviceManager.stopAllTracks(),this.screenCapture.stop(),this.outputStream=null,"streaming"!==this.state&&this.setState("idle",{hasVideo:!1,hasAudio:!1,isScreenShare:!1})}async setQualityProfile(e){if(e===this.currentProfile)return;const t=this.currentProfile;this.currentProfile=e,this.log(`Changing quality profile: ${t} -> ${e}`);for(const[t,r]of this.sources)if("camera"===r.type){const t=r.stream.getVideoTracks()[0];if(t)try{const r=_e(e);await t.applyConstraints(r)}catch(e){this.log("Failed to apply new constraints",e)}}this.emit("qualityChanged",{profile:e,previousProfile:t}),this.setState(this.state,{activeProfile:e})}setupWhipClientHandlers(){this.whipClient&&(this.whipClient.on("stateChange",e=>{if(this.log("WHIP state changed",e),this.stateContext={...this.stateContext,connectionState:e.state},"connected"===e.state){if(this.setState("streaming"),this.startStatsPolling(),this.reconnectionManager.reset(),this.useWebCodecs&&this.encoderManager&&this.whipClient){this.log("Attempting to attach WebCodecs encoder transform");const e=this.whipClient.canUseEncodedInsertion();if(this.log("canUseEncodedInsertion result:",e),e)try{this.whipClient.attachEncoderTransform(this.encoderManager),this.encoderManager.start(),this.log("WebCodecs encoder transform attached",{videoCodec:this.whipClient.getNegotiatedVideoCodec(),audioCodec:this.whipClient.getNegotiatedAudioCodec()}),this.emit("webCodecsActive",{active:!0})}catch(e){this.log("Failed to attach encoder transform, continuing with browser encoding",e),this.encoderManager&&(this.encoderManager.destroy(),this.encoderManager=null)}else this.log("Codec alignment check failed, using browser encoding",{videoCodec:this.whipClient.getNegotiatedVideoCodec(),audioCodec:this.whipClient.getNegotiatedAudioCodec()}),this.encoderManager&&(this.encoderManager.destroy(),this.encoderManager=null)}}else if("failed"===e.state||"disconnected"===e.state){if(this.isStoppingIntentionally)return;"streaming"===this.state&&!1!==this.config.reconnection?.enabled?this.handleConnectionLost():(this.setState("error",{error:"failed"===e.state?"Connection failed":"Connection lost"}),this.stopStatsPolling())}}),this.whipClient.on("error",e=>{this.isStoppingIntentionally||this.emit("error",{error:e.message,recoverable:!1})}))}async startStreaming(){if(!this.outputStream)throw new Error("No media source available. Add a camera or screen share first.");this.log("Starting streaming"),this.currentEndpointIndex=0,this.setState("connecting");try{if(this.whipClient=new Re({whipUrl:this.getCurrentWhipUrl(),iceServers:this.config.iceServers,debug:this.config.debug}),this.setupWhipClientHandlers(),this.config.audioMixing&&await this.audioMixer.resume(),this.useWebCodecs&&rt()){this.log("Initializing WebCodecs encoder (Path C: RTCRtpScriptTransform)");try{this.encoderManager=new et({debug:this.config.debug}),this.encoderManager.on("error",e=>{this.emit("error",{error:e.message,recoverable:!e.fatal}),e.fatal&&"streaming"===this.state&&(this.log("Fatal encoder error, reconnecting without WebCodecs"),this.handleEncoderFailure())}),this.encoderManager.on("stats",e=>{this.log("Encoder stats",e)});const e=function(e="broadcast",t){const r=Ze[e],i=Ye,s=t?.video?.width??r.width,o=t?.video?.height??r.height,n=t?.video?.framerate??r.framerate,a=function(e,t,r){const i=e*t;return i>=8294400?r>30?"avc1.640034":"avc1.640033":i>=3686400?r>30?"avc1.640033":"avc1.640032":i>=2073600?r>30?"avc1.640032":"avc1.64002a":i>=921600?r>30?"avc1.64002a":"avc1.640028":"avc1.64001f"}(s,o,n);return{video:{...r,codec:a,width:s,height:o,framerate:n,...void 0!==t?.video?.bitrate&&{bitrate:t.video.bitrate}},audio:{...i,...void 0!==t?.audio?.bitrate&&{bitrate:t.audio.bitrate},...void 0!==t?.audio?.sampleRate&&{sampleRate:t.audio.sampleRate},...void 0!==t?.audio?.numberOfChannels&&{numberOfChannels:t.audio.numberOfChannels}}}}("auto"===this.currentProfile?"broadcast":this.currentProfile,this.encoderOverrides);this.log("Encoder config with overrides:",e),await this.encoderManager.initialize(this.outputStream,e),this.log("WebCodecs encoder initialized")}catch(e){this.log("WebCodecs encoder initialization failed, falling back to browser encoding",e),this.encoderManager&&(this.encoderManager.destroy(),this.encoderManager=null)}}else this.useWebCodecs&&this.log("WebCodecs requested but RTCRtpScriptTransform not supported, using browser encoding");await this.whipClient.connect(this.outputStream)}catch(e){throw this.encoderManager&&(this.encoderManager.destroy(),this.encoderManager=null),this.setState("error",{error:e instanceof Error?e.message:String(e)}),e}}async handleEncoderFailure(){if(this.log("Handling encoder failure - reconnecting without WebCodecs"),this.setState("reconnecting"),this.stopStatsPolling(),this.encoderManager&&(this.encoderManager.destroy(),this.encoderManager=null),this.useWebCodecs=!1,this.whipClient)try{await this.whipClient.disconnect()}finally{this.whipClient.destroy(),this.whipClient=null}if(this.outputStream)try{this.whipClient=new Re({whipUrl:this.getNextWhipUrl(),iceServers:this.config.iceServers,debug:this.config.debug}),this.setupWhipClientHandlers(),await this.whipClient.connect(this.outputStream)}catch(e){this.setState("error",{error:`Reconnection failed: ${e instanceof Error?e.message:String(e)}`})}else this.setState("error",{error:"No output stream available for reconnection"})}handleConnectionLost(){this.log("Connection lost, starting reconnection"),this.setState("reconnecting"),this.stopStatsPolling(),this.reconnectionManager.start(async()=>{if(this.whipClient)try{await this.whipClient.disconnect()}finally{this.whipClient.destroy(),this.whipClient=null}if(!this.outputStream)throw new Error("No output stream available");this.whipClient=new Re({whipUrl:this.getNextWhipUrl(),iceServers:this.config.iceServers,debug:this.config.debug}),this.setupWhipClientHandlers(),await new Promise((e,t)=>{const r=setTimeout(()=>{t(new Error("Connection timeout"))},3e4),i=s=>{"connected"===s.state?(clearTimeout(r),this.whipClient?.off("stateChange",i),e()):"failed"===s.state&&(clearTimeout(r),this.whipClient?.off("stateChange",i),t(new Error("Connection failed")))};this.whipClient.on("stateChange",i),this.whipClient.connect(this.outputStream).catch(t)})})}async stopStreaming(){this.log("Stopping streaming"),this.isStoppingIntentionally=!0;try{this.stopStatsPolling(),this.reconnectionManager.stop(),this.encoderManager&&(await this.encoderManager.stop(),this.encoderManager.destroy(),this.encoderManager=null),this.whipClient&&(await this.whipClient.disconnect(),this.whipClient.destroy(),this.whipClient=null),this.sources.size>0?this.setState("capturing"):this.setState("idle"),this.stateContext={...this.stateContext,connectionState:"disconnected"}}finally{this.isStoppingIntentionally=!1}}async switchVideoDevice(e){const t=await this.deviceManager.replaceVideoTrack(e,this.currentProfile);if(t&&this.whipClient){const e=this.whipClient.getPeerConnection();if(e){const r=e.getSenders().find(e=>"video"===e.track?.kind);r&&await r.replaceTrack(t)}}}async switchAudioDevice(e){const t=await this.deviceManager.replaceAudioTrack(e,this.currentProfile);if(t&&this.whipClient){const e=this.whipClient.getPeerConnection();if(e){const r=e.getSenders().find(e=>"audio"===e.track?.kind);r&&await r.replaceTrack(t)}}}startStatsPolling(){this.statsInterval||(this.statsInterval=setInterval(async()=>{if(!this.statsInFlight){this.statsInFlight=!0;try{const e=await this.getStats();e&&(this.lastStats=e,this.emit("statsUpdate",e))}finally{this.statsInFlight=!1}}},1e3))}stopStatsPolling(){this.statsInterval&&(clearInterval(this.statsInterval),this.statsInterval=null)}async getStats(){if(!this.whipClient)return null;const e=await this.whipClient.getStats();if(!e)return null;const t={video:{bytesSent:0,packetsSent:0,packetsLost:0,framesEncoded:0,framesPerSecond:0,bitrate:0},audio:{bytesSent:0,packetsSent:0,packetsLost:0,bitrate:0},connection:{rtt:0,state:this.whipClient.getPeerConnection()?.connectionState??"new",iceState:this.whipClient.getPeerConnection()?.iceConnectionState??"new"},timestamp:Date.now()},r=this.lastStats;return e.forEach(e=>{if("outbound-rtp"===e.type){const i=e;if("video"===i.kind){if(t.video.bytesSent=i.bytesSent??0,t.video.packetsSent=i.packetsSent??0,t.video.framesEncoded=i.framesEncoded??0,t.video.framesPerSecond=i.framesPerSecond??0,r){const e=(t.timestamp-r.timestamp)/1e3,i=t.video.bytesSent-r.video.bytesSent;t.video.bitrate=Math.round(8*i/e)}}else if("audio"===i.kind&&(t.audio.bytesSent=i.bytesSent??0,t.audio.packetsSent=i.packetsSent??0,r)){const e=(t.timestamp-r.timestamp)/1e3,i=t.audio.bytesSent-r.audio.bytesSent;t.audio.bitrate=Math.round(8*i/e)}}else"candidate-pair"===e.type&&"succeeded"===e.state&&(t.connection.rtt=e.currentRoundTripTime??0)}),t}async enableCompositor(e){if(this.log("enableCompositor called",{alreadyEnabled:!!this.sceneManager}),this.sceneManager)return void this.log("Compositor already enabled");const t={...Me,...this.config.compositor,...e};this.log("Creating SceneManager with config",t),this.sceneManager=new Je(t),this.compositorBaseConfig=t;try{this.log("Initializing SceneManager..."),await this.sceneManager.initialize(),this.log("SceneManager initialized successfully")}catch(e){this.sceneManager=null;const t=e instanceof Error?e.message:String(e);throw this.log("Compositor initialization failed:",t),new Error(`Compositor initialization failed: ${t}`)}if(!this.sceneManager)throw this.log("ERROR: SceneManager was unexpectedly null after initialization"),new Error("SceneManager was unexpectedly null after initialization");this.log("SceneManager is valid, getting active scene...");const r=this.sceneManager.getActiveScene();this.log("Compositor active scene:",r?.id??"none");for(const[e,t]of this.sources){if(!this.sceneManager)break;this.sceneManager.bindSource(e,t.stream),r&&this.sceneManager.addLayer(r.id,e)}this.sceneManager&&this.sceneManager.on("error",e=>{this.emit("error",{error:e.message,recoverable:!0})}),this.log("Compositor enabled",t),this.updateOutputStreamFromSources()}disableCompositor(){this.sceneManager&&(this.sceneManager.destroy(),this.sceneManager=null,this.log("Compositor disabled"),this.updateOutputStreamFromSources())}getSceneManager(){return this.sceneManager}isCompositorEnabled(){return null!==this.sceneManager&&this.sceneManager.isInitialized()}getState(){return this.state}getStateContext(){return{...this.stateContext}}getMediaStream(){return this.outputStream}getSources(){return Array.from(this.sources.values())}getSource(e){return this.sources.get(e)}getQualityProfile(){return this.currentProfile}getDeviceManager(){return this.deviceManager}getScreenCapture(){return this.screenCapture}getAudioMixer(){return this.audioMixer}getReconnectionManager(){return this.reconnectionManager}async getDevices(){return this.deviceManager.enumerateDevices()}isStreaming(){return"streaming"===this.state}isCapturing(){return"capturing"===this.state||"streaming"===this.state}isReconnecting(){return"reconnecting"===this.state}setUseWebCodecs(e){"streaming"!==this.state?(this.useWebCodecs=e,this.log("useWebCodecs set to",e)):this.log("Cannot change useWebCodecs while streaming")}setEncoderOverrides(e){if("streaming"!==this.state){if(this.encoderOverrides=e,this.log("Encoder overrides set:",e),this.sceneManager){const t=this.compositorBaseConfig??this.sceneManager.getConfig(),r=e.video?.width??t.width,i=e.video?.height??t.height,s=e.video?.framerate??t.frameRate;this.sceneManager.updateOutputConfig({width:r,height:i,frameRate:s})&&this.updateOutputStreamFromSources()}}else this.log("Cannot change encoder overrides while streaming")}getEncoderOverrides(){return this.encoderOverrides}getUseWebCodecs(){return this.useWebCodecs}getEncoderManager(){return this.encoderManager}isWebCodecsActive(){return null!==this.encoderManager&&!0===this.whipClient?.hasEncoderTransform()}destroy(){this.log("Destroying IngestControllerV2"),this.stopStatsPolling(),this.reconnectionManager.destroy(),this.encoderManager&&(this.encoderManager.destroy(),this.encoderManager=null),this.whipClient&&(this.whipClient.destroy(),this.whipClient=null),this.sceneManager&&(this.sceneManager.destroy(),this.sceneManager=null);for(const e of Array.from(this.sources.keys()))this.removeSource(e);this.deviceManager.destroy(),this.screenCapture.destroy(),this.audioMixer.destroy(),this.removeAllListeners(),this.setState("destroyed")}}class nt{constructor(e,t="broadcast"){this.controller=null,this.unsubs=[],this.encoderStatsCleanup=null,this.host=e,e.addController(this);const r=tt();this.s={state:"idle",stateContext:{},isStreaming:!1,isCapturing:!1,isReconnecting:!1,error:null,mediaStream:null,sources:[],qualityProfile:t,reconnectionState:null,stats:null,useWebCodecs:"webcodecs"===r.recommended,isWebCodecsActive:!1,isWebCodecsAvailable:it(),encoderStats:null}}initialize(e){this.teardown();const t=new ot({...e,useWebCodecs:this.s.useWebCodecs});this.controller=t,this.subscribeToEvents(t)}hostConnected(){}hostDisconnected(){this.teardown()}teardown(){this.unsubs.forEach(e=>e()),this.unsubs=[],this.encoderStatsCleanup&&(this.encoderStatsCleanup(),this.encoderStatsCleanup=null),this.controller?.destroy(),this.controller=null}update(e){Object.assign(this.s,e),this.host.requestUpdate()}subscribeToEvents(e){const t=this.unsubs;t.push(e.on("stateChange",t=>{const r=t.state,i=t.context??{};this.update({state:r,stateContext:i,isStreaming:"streaming"===r,isCapturing:"capturing"===r||"streaming"===r,isReconnecting:"reconnecting"===r,mediaStream:e.getMediaStream(),sources:e.getSources(),reconnectionState:i.reconnection??this.s.reconnectionState}),this.dispatchEvent("fw-sc-state-change",{state:r,context:i})})),t.push(e.on("statsUpdate",e=>{this.update({stats:e})})),t.push(e.on("error",e=>{this.update({error:e.error}),this.dispatchEvent("fw-sc-error",{error:e.error})})),t.push(e.on("sourceAdded",()=>{this.update({sources:e.getSources(),mediaStream:e.getMediaStream()})})),t.push(e.on("sourceRemoved",()=>{this.update({sources:e.getSources(),mediaStream:e.getMediaStream()})})),t.push(e.on("sourceUpdated",()=>{this.update({sources:e.getSources(),mediaStream:e.getMediaStream()})})),t.push(e.on("qualityChanged",e=>{this.update({qualityProfile:e.profile})})),t.push(e.on("reconnectionAttempt",()=>{this.update({reconnectionState:e.getReconnectionManager().getState()})})),t.push(e.on("webCodecsActive",e=>{this.update({isWebCodecsActive:e.active}),e.active&&this.setupEncoderStatsListener()})),t.push(e.on("stateChange",t=>{"streaming"===t.state?setTimeout(()=>{this.update({isWebCodecsActive:e.isWebCodecsActive()}),e.isWebCodecsActive()&&!this.encoderStatsCleanup&&this.setupEncoderStatsListener()},200):"idle"!==t.state&&"capturing"!==t.state||(this.update({isWebCodecsActive:!1,encoderStats:null}),this.encoderStatsCleanup&&(this.encoderStatsCleanup(),this.encoderStatsCleanup=null))}))}setupEncoderStatsListener(){if(!this.controller)return;const e=this.controller.getEncoderManager();e&&(this.encoderStatsCleanup=e.on("stats",e=>{this.update({encoderStats:e})}))}dispatchEvent(e,t){this.host.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0}))}async startCamera(e){if(!this.controller)throw new Error("Controller not initialized");return this.update({error:null}),this.controller.startCamera(e)}async startScreenShare(e){if(!this.controller)throw new Error("Controller not initialized");return this.update({error:null}),this.controller.startScreenShare(e)}addCustomSource(e,t){if(!this.controller)throw new Error("Controller not initialized");return this.controller.addCustomSource(e,t)}removeSource(e){this.controller?.removeSource(e)}async stopCapture(){await(this.controller?.stopCapture())}setSourceVolume(e,t){this.controller?.setSourceVolume(e,t)}setSourceMuted(e,t){this.controller?.setSourceMuted(e,t)}setSourceActive(e,t){this.controller?.setSourceActive(e,t)}setPrimaryVideoSource(e){this.controller?.setPrimaryVideoSource(e)}setMasterVolume(e){this.controller?.setMasterVolume(e)}getMasterVolume(){return this.controller?.getMasterVolume()??1}async setQualityProfile(e){await(this.controller?.setQualityProfile(e))}async startStreaming(){if(!this.controller)throw new Error("Controller not initialized");this.update({error:null}),await this.controller.startStreaming()}async stopStreaming(){await(this.controller?.stopStreaming())}async getDevices(){return this.controller?.getDevices()??[]}async switchVideoDevice(e){await(this.controller?.switchVideoDevice(e))}async switchAudioDevice(e){await(this.controller?.switchAudioDevice(e))}async getStats(){return this.controller?.getStats()??null}setUseWebCodecs(e){this.update({useWebCodecs:e}),this.controller?.setUseWebCodecs(e)}setEncoderOverrides(e){this.controller?.setEncoderOverrides(e)}getController(){return this.controller}}const at=[{id:"professional",label:"Professional",description:"1080p @ 8 Mbps"},{id:"broadcast",label:"Broadcast",description:"1080p @ 4.5 Mbps"},{id:"conference",label:"Conference",description:"720p @ 2.5 Mbps"}];e.FwStreamCrafter=class extends le{constructor(){super(),this.whipUrl="",this.gatewayUrl="",this.streamKey="",this.initialProfile="broadcast",this.autoStartCamera=!1,this.devMode=!1,this.debug=!1,this.enableCompositor=!1,this._showSettings=!1,this._showSources=!0,this._isAdvancedPanelOpen=!1,this.pc=new nt(this,this.initialProfile)}connectedCallback(){super.connectedCallback(),this._initController()}willUpdate(e){(e.has("whipUrl")||e.has("initialProfile")||e.has("debug"))&&this._initController()}updated(e){e.has("_showSources")||e.has("_showSettings"),this._syncVideoPreview()}_initController(){this.whipUrl&&(this.pc.initialize({whipUrl:this.whipUrl,profile:this.initialProfile,debug:this.debug,reconnection:{enabled:!0,maxAttempts:5},audioMixing:!0}),this.autoStartCamera&&"idle"===this.pc.s.state&&this.pc.startCamera().catch(console.error))}_syncVideoPreview(){const e=this._videoEl,t=this.pc.s.mediaStream;e&&t&&e.srcObject!==t?(e.srcObject=t,e.play().catch(()=>{})):e&&!t&&(e.srcObject=null)}async startCamera(e){return this.pc.startCamera(e)}async startScreenShare(e){return this.pc.startScreenShare(e)}async startStreaming(){return this.pc.startStreaming()}async stopStreaming(){return this.pc.stopStreaming()}async stopCapture(){return this.pc.stopCapture()}removeSource(e){this.pc.removeSource(e)}setSourceVolume(e,t){this.pc.setSourceVolume(e,t)}setSourceMuted(e,t){this.pc.setSourceMuted(e,t)}setPrimaryVideoSource(e){this.pc.setPrimaryVideoSource(e)}setMasterVolume(e){this.pc.setMasterVolume(e)}async setQualityProfile(e){return this.pc.setQualityProfile(e)}destroy(){this.pc.getController()?.destroy()}render(){const e=this.pc.s,t=function(e,t){if(t?.isReconnecting)return`Reconnecting (${t.attemptNumber}/5)...`;switch(e){case"idle":return"Idle";case"requesting_permissions":return"Permissions...";case"capturing":return"Ready";case"connecting":return"Connecting...";case"streaming":return"Live";case"reconnecting":return"Reconnecting...";case"error":return"Error";case"destroyed":return"Destroyed";default:return e}}(e.state,e.reconnectionState),r=(i=e.state,s=e.isReconnecting,"streaming"===i?"fw-sc-badge fw-sc-badge--live":s?"fw-sc-badge fw-sc-badge--connecting":"error"===i?"fw-sc-badge fw-sc-badge--error":"capturing"===i?"fw-sc-badge fw-sc-badge--ready":"fw-sc-badge fw-sc-badge--idle");var i,s;const o="destroyed"!==e.state&&"error"!==e.state,n=e.isCapturing&&!e.isStreaming&&!!this.whipUrl,a=e.sources.some(e=>"camera"===e.type);return q`
2289
+ <div
2290
+ class=${ye({root:!0,"fw-sc-root":!0,"fw-sc-root--devmode":this.devMode})}
2291
+ >
2292
+ <div class="main fw-sc-main">
2293
+ <!-- Header -->
2294
+ <div class="fw-sc-header">
2295
+ <span class="fw-sc-header-title">StreamCrafter</span>
2296
+ <div class="fw-sc-header-status">
2297
+ <span class=${r}>${t}</span>
2298
+ </div>
2299
+ </div>
2300
+
2301
+ <!-- Content -->
2302
+ <div class="fw-sc-content">
2303
+ <div class="fw-sc-preview-wrapper">
2304
+ <div class="fw-sc-preview">
2305
+ <video playsinline muted autoplay aria-label="Stream preview"></video>
2306
+
2307
+ ${e.mediaStream?G:q`
2308
+ <div class="fw-sc-preview-placeholder">
2309
+ ${Se(48)}
2310
+ <span>Add a camera or screen to preview</span>
2311
+ </div>
2312
+ `}
2313
+ ${"connecting"===e.state||"reconnecting"===e.state?q`
2314
+ <div class="fw-sc-status-overlay">
2315
+ <div class="fw-sc-status-spinner"></div>
2316
+ <span class="fw-sc-status-text">${t}</span>
2317
+ </div>
2318
+ `:G}
2319
+ ${e.isStreaming?q`<div class="fw-sc-live-badge">Live</div>`:G}
2320
+ ${this.enableCompositor?q` <fw-sc-compositor .ic=${this.pc}></fw-sc-compositor> `:G}
2321
+ </div>
2322
+ </div>
2323
+
2324
+ <!-- Sources Mixer -->
2325
+ ${e.sources.length>0?q`
2326
+ <div
2327
+ class=${ye({"fw-sc-section":!0,"fw-sc-mixer":!0,"fw-sc-section--collapsed":!this._showSources})}
2328
+ >
2329
+ <div
2330
+ class="fw-sc-section-header"
2331
+ @click=${()=>{this._showSources=!this._showSources}}
2332
+ >
2333
+ <span>Mixer (${e.sources.length})</span>
2334
+ ${this._showSources?((e=14)=>q` <svg
2335
+ width="${e}"
2336
+ height="${e}"
2337
+ viewBox="0 0 24 24"
2338
+ fill="none"
2339
+ stroke="currentColor"
2340
+ stroke-width="2"
2341
+ stroke-linecap="round"
2342
+ stroke-linejoin="round"
2343
+ >
2344
+ <polyline points="13 17 18 12 13 7" />
2345
+ <polyline points="6 17 11 12 6 7" />
2346
+ </svg>`)(14):((e=14)=>q` <svg
2347
+ width="${e}"
2348
+ height="${e}"
2349
+ viewBox="0 0 24 24"
2350
+ fill="none"
2351
+ stroke="currentColor"
2352
+ stroke-width="2"
2353
+ stroke-linecap="round"
2354
+ stroke-linejoin="round"
2355
+ >
2356
+ <polyline points="11 17 6 12 11 7" />
2357
+ <polyline points="18 17 13 12 18 7" />
2358
+ </svg>`)(14)}
2359
+ </div>
2360
+ ${this._showSources?q`
2361
+ <div class="fw-sc-section-body--flush">
2362
+ <div class="fw-sc-sources">
2363
+ ${e.sources.map(e=>this._renderSourceRow(e))}
2364
+ </div>
2365
+ </div>
2366
+ `:G}
2367
+ </div>
2368
+ `:G}
2369
+ </div>
2370
+
2371
+ <!-- Error -->
2372
+ ${e.error?q`
2373
+ <div class="fw-sc-error">
2374
+ <div class="fw-sc-error-title">Error</div>
2375
+ <div class="fw-sc-error-message">${e.error}</div>
2376
+ </div>
2377
+ `:G}
2378
+ ${this.whipUrl||e.error?G:q`
2379
+ <div class="fw-sc-error" style="border-left-color: hsl(40 80% 65%)">
2380
+ <div class="fw-sc-error-title" style="color: hsl(40 80% 65%)">Warning</div>
2381
+ <div class="fw-sc-error-message">Configure WHIP endpoint to stream</div>
2382
+ </div>
2383
+ `}
2384
+
2385
+ <!-- Action Bar -->
2386
+ <div class="fw-sc-actions">
2387
+ <button
2388
+ type="button"
2389
+ class="fw-sc-action-secondary"
2390
+ @click=${()=>this.pc.startCamera().catch(console.error)}
2391
+ ?disabled=${!o||a}
2392
+ title=${a?"Camera active":"Add Camera"}
2393
+ >
2394
+ ${Se(18)}
2395
+ </button>
2396
+ <button
2397
+ type="button"
2398
+ class="fw-sc-action-secondary"
2399
+ @click=${()=>this.pc.startScreenShare({audio:!0}).catch(console.error)}
2400
+ ?disabled=${!o}
2401
+ title="Share Screen"
2402
+ >
2403
+ ${ke(18)}
2404
+ </button>
2405
+
2406
+ <!-- Settings -->
2407
+ <div style="position:relative">
2408
+ <button
2409
+ type="button"
2410
+ class=${ye({"fw-sc-action-secondary":!0,"fw-sc-action-secondary--active":this._showSettings})}
2411
+ @click=${()=>{this._showSettings=!this._showSettings}}
2412
+ title="Settings"
2413
+ >
2414
+ ${((e=16)=>q` <svg
2415
+ width="${e}"
2416
+ height="${e}"
2417
+ viewBox="0 0 24 24"
2418
+ fill="none"
2419
+ stroke="currentColor"
2420
+ stroke-width="2"
2421
+ stroke-linecap="round"
2422
+ stroke-linejoin="round"
2423
+ >
2424
+ <circle cx="12" cy="12" r="3" />
2425
+ <path
2426
+ d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
2427
+ />
2428
+ </svg>`)(16)}
2429
+ </button>
2430
+ ${this._showSettings?this._renderSettingsPopup():G}
2431
+ </div>
2432
+
2433
+ <!-- Go Live / Stop -->
2434
+ ${e.isStreaming?q`
2435
+ <button
2436
+ type="button"
2437
+ class="fw-sc-action-primary fw-sc-action-stop"
2438
+ @click=${()=>this.pc.stopStreaming().catch(console.error)}
2439
+ >
2440
+ Stop Streaming
2441
+ </button>
2442
+ `:q`
2443
+ <button
2444
+ type="button"
2445
+ class="fw-sc-action-primary"
2446
+ @click=${()=>this.pc.startStreaming().catch(console.error)}
2447
+ ?disabled=${!n}
2448
+ >
2449
+ ${"connecting"===e.state?"Connecting...":"Go Live"}
2450
+ </button>
2451
+ `}
2452
+ </div>
2453
+ </div>
2454
+
2455
+ <!-- Advanced Panel -->
2456
+ ${this.devMode&&this._isAdvancedPanelOpen?q`
2457
+ <fw-sc-advanced
2458
+ .ic=${this.pc}
2459
+ @fw-close=${()=>{this._isAdvancedPanelOpen=!1}}
2460
+ ></fw-sc-advanced>
2461
+ `:G}
2462
+ </div>
2463
+ `}_renderSourceRow(e){const t=this.pc.s,r=e.stream.getVideoTracks().length>0;return q`
2464
+ <div class=${ye({"fw-sc-source":!0})}>
2465
+ <div class="fw-sc-source-icon">
2466
+ ${"camera"===e.type?Se(16):ke(16)}
2467
+ </div>
2468
+ <div class="fw-sc-source-info">
2469
+ <div class="fw-sc-source-label">
2470
+ ${e.label}
2471
+ ${e.primaryVideo&&!this.enableCompositor?q`<span class="fw-sc-primary-badge">PRIMARY</span>`:G}
2472
+ </div>
2473
+ <div class="fw-sc-source-type">${e.type}</div>
2474
+ </div>
2475
+ <div class="fw-sc-source-controls">
2476
+ ${r&&!this.enableCompositor?q`
2477
+ <button
2478
+ type="button"
2479
+ class=${ye({"fw-sc-icon-btn":!0,"fw-sc-icon-btn--primary":!!e.primaryVideo})}
2480
+ @click=${()=>this.pc.setPrimaryVideoSource(e.id)}
2481
+ ?disabled=${e.primaryVideo}
2482
+ title=${e.primaryVideo?"Primary video source":"Set as primary video"}
2483
+ >
2484
+ ${$e(14)}
2485
+ </button>
2486
+ `:G}
2487
+ <span class="fw-sc-volume-label">${Math.round(100*e.volume)}%</span>
2488
+ <fw-sc-volume
2489
+ .value=${e.volume}
2490
+ @fw-sc-volume-change=${t=>this.pc.setSourceVolume(e.id,t.detail.value)}
2491
+ compact
2492
+ ></fw-sc-volume>
2493
+ <button
2494
+ type="button"
2495
+ class=${ye({"fw-sc-icon-btn":!0,"fw-sc-icon-btn--active":e.muted})}
2496
+ @click=${()=>this.pc.setSourceMuted(e.id,!e.muted)}
2497
+ title=${e.muted?"Unmute":"Mute"}
2498
+ >
2499
+ ${e.muted?((e=16)=>q` <svg
2500
+ width="${e}"
2501
+ height="${e}"
2502
+ viewBox="0 0 24 24"
2503
+ fill="none"
2504
+ stroke="currentColor"
2505
+ stroke-width="2"
2506
+ stroke-linecap="round"
2507
+ stroke-linejoin="round"
2508
+ >
2509
+ <line x1="1" y1="1" x2="23" y2="23" />
2510
+ <path d="M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6" />
2511
+ <path d="M17 16.95A7 7 0 0 1 5 12v-2m14 0v2a7 7 0 0 1-.11 1.23" />
2512
+ <line x1="12" y1="19" x2="12" y2="23" />
2513
+ <line x1="8" y1="23" x2="16" y2="23" />
2514
+ </svg>`)(14):((e=16)=>q` <svg
2515
+ width="${e}"
2516
+ height="${e}"
2517
+ viewBox="0 0 24 24"
2518
+ fill="none"
2519
+ stroke="currentColor"
2520
+ stroke-width="2"
2521
+ stroke-linecap="round"
2522
+ stroke-linejoin="round"
2523
+ >
2524
+ <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
2525
+ <path d="M19 10v2a7 7 0 0 1-14 0v-2" />
2526
+ <line x1="12" y1="19" x2="12" y2="23" />
2527
+ <line x1="8" y1="23" x2="16" y2="23" />
2528
+ </svg>`)(14)}
2529
+ </button>
2530
+ <button
2531
+ type="button"
2532
+ class="fw-sc-icon-btn fw-sc-icon-btn--destructive"
2533
+ @click=${()=>this.pc.removeSource(e.id)}
2534
+ ?disabled=${t.isStreaming}
2535
+ title=${t.isStreaming?"Cannot remove source while streaming":"Remove source"}
2536
+ >
2537
+ ${Ce(14)}
2538
+ </button>
2539
+ </div>
2540
+ </div>
2541
+ `}_renderSettingsPopup(){const e=this.pc.s;return q`
2542
+ <div
2543
+ class="fw-sc-settings-popup"
2544
+ style="position:absolute;bottom:100%;left:0;margin-bottom:8px;width:192px;background:#1a1b26;border:1px solid rgba(90,96,127,0.3);box-shadow:0 4px 12px rgba(0,0,0,0.4);border-radius:4px;overflow:hidden;z-index:50"
2545
+ >
2546
+ <div style="padding:8px;border-bottom:1px solid rgba(90,96,127,0.3)">
2547
+ <div
2548
+ style="font-size:10px;color:#565f89;text-transform:uppercase;font-weight:600;margin-bottom:4px;padding-left:4px"
2549
+ >
2550
+ Quality
2551
+ </div>
2552
+ <div style="display:flex;flex-direction:column;gap:2px">
2553
+ ${at.map(t=>q`
2554
+ <button
2555
+ type="button"
2556
+ @click=${()=>{e.isStreaming||(this.pc.setQualityProfile(t.id),this.devMode||(this._showSettings=!1))}}
2557
+ ?disabled=${e.isStreaming}
2558
+ style="width:100%;padding:6px 8px;text-align:left;font-size:12px;border-radius:4px;border:none;cursor:${e.isStreaming?"not-allowed":"pointer"};opacity:${e.isStreaming?"0.5":"1"};background:${e.qualityProfile===t.id?"rgba(122,162,247,0.2)":"transparent"};color:${e.qualityProfile===t.id?"#7aa2f7":"#a9b1d6"}"
2559
+ >
2560
+ <div style="font-weight:500">${t.label}</div>
2561
+ <div style="font-size:10px;color:#565f89">${t.description}</div>
2562
+ </button>
2563
+ `)}
2564
+ </div>
2565
+ </div>
2566
+ ${this.devMode?q`
2567
+ <div style="padding:8px">
2568
+ <div
2569
+ style="font-size:10px;color:#565f89;text-transform:uppercase;font-weight:600;margin-bottom:4px;padding-left:4px"
2570
+ >
2571
+ Debug
2572
+ </div>
2573
+ <div
2574
+ style="display:flex;flex-direction:column;gap:4px;padding-left:4px;font-size:12px;font-family:ui-monospace,monospace"
2575
+ >
2576
+ <div style="display:flex;justify-content:space-between">
2577
+ <span style="color:#565f89">State</span
2578
+ ><span style="color:#c0caf5">${e.state}</span>
2579
+ </div>
2580
+ <div style="display:flex;justify-content:space-between">
2581
+ <span style="color:#565f89">WHIP</span
2582
+ ><span style="color:${this.whipUrl?"#9ece6a":"#f7768e"}"
2583
+ >${this.whipUrl?"OK":"Not set"}</span
2584
+ >
2585
+ </div>
2586
+ </div>
2587
+ </div>
2588
+ `:G}
2589
+ </div>
2590
+ `}},e.FwStreamCrafter.styles=[be,xe,c`
2591
+ :host {
2592
+ display: block;
2593
+ }
2594
+ .root {
2595
+ display: flex;
2596
+ height: 100%;
2597
+ }
2598
+ .main {
2599
+ display: flex;
2600
+ flex-direction: column;
2601
+ flex: 1;
2602
+ min-width: 0;
2603
+ }
2604
+ `],r([ge({type:String,attribute:"whip-url"})],e.FwStreamCrafter.prototype,"whipUrl",void 0),r([ge({type:String,attribute:"gateway-url"})],e.FwStreamCrafter.prototype,"gatewayUrl",void 0),r([ge({type:String,attribute:"stream-key"})],e.FwStreamCrafter.prototype,"streamKey",void 0),r([ge({type:String,attribute:"initial-profile"})],e.FwStreamCrafter.prototype,"initialProfile",void 0),r([ge({type:Boolean,attribute:"auto-start-camera"})],e.FwStreamCrafter.prototype,"autoStartCamera",void 0),r([ge({type:Boolean,attribute:"dev-mode"})],e.FwStreamCrafter.prototype,"devMode",void 0),r([ge({type:Boolean})],e.FwStreamCrafter.prototype,"debug",void 0),r([ge({type:Boolean,attribute:"enable-compositor"})],e.FwStreamCrafter.prototype,"enableCompositor",void 0),r([fe()],e.FwStreamCrafter.prototype,"_showSettings",void 0),r([fe()],e.FwStreamCrafter.prototype,"_showSources",void 0),r([fe()],e.FwStreamCrafter.prototype,"_isAdvancedPanelOpen",void 0),r([me(".fw-sc-preview video")],e.FwStreamCrafter.prototype,"_videoEl",void 0),e.FwStreamCrafter=r([he("fw-streamcrafter")],e.FwStreamCrafter);const ct=[{mode:"solo",label:"Solo",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2605
+ <rect x="1" y="1" width="10" height="10" rx="1" />
2606
+ </svg>`,minSources:1},{mode:"pip-br",label:"PiP ↘",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2607
+ <rect x="1" y="1" width="10" height="10" rx="1" fill-opacity="0.3" />
2608
+ <rect x="6.5" y="6.5" width="4" height="3" rx="0.5" />
2609
+ </svg>`,minSources:2},{mode:"pip-bl",label:"PiP ↙",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2610
+ <rect x="1" y="1" width="10" height="10" rx="1" fill-opacity="0.3" />
2611
+ <rect x="1.5" y="6.5" width="4" height="3" rx="0.5" />
2612
+ </svg>`,minSources:2},{mode:"pip-tr",label:"PiP ↗",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2613
+ <rect x="1" y="1" width="10" height="10" rx="1" fill-opacity="0.3" />
2614
+ <rect x="6.5" y="2.5" width="4" height="3" rx="0.5" />
2615
+ </svg>`,minSources:2},{mode:"pip-tl",label:"PiP ↖",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2616
+ <rect x="1" y="1" width="10" height="10" rx="1" fill-opacity="0.3" />
2617
+ <rect x="1.5" y="2.5" width="4" height="3" rx="0.5" />
2618
+ </svg>`,minSources:2},{mode:"split-h",label:"Split ⬌",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2619
+ <rect x="1" y="1" width="4.5" height="10" rx="1" />
2620
+ <rect x="6.5" y="1" width="4.5" height="10" rx="1" />
2621
+ </svg>`,minSources:2},{mode:"split-v",label:"Split ⬍",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2622
+ <rect x="1" y="1" width="10" height="4.5" rx="1" />
2623
+ <rect x="1" y="6.5" width="10" height="4.5" rx="1" />
2624
+ </svg>`,minSources:2},{mode:"focus-l",label:"Focus ◀",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2625
+ <rect x="1" y="1" width="7" height="10" rx="1" />
2626
+ <rect x="8.5" y="1" width="2.5" height="10" rx="1" fill-opacity="0.5" />
2627
+ </svg>`,minSources:2},{mode:"focus-r",label:"Focus ▶",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2628
+ <rect x="1" y="1" width="2.5" height="10" rx="1" fill-opacity="0.5" />
2629
+ <rect x="4" y="1" width="7" height="10" rx="1" />
2630
+ </svg>`,minSources:2},{mode:"pip-dual-br",label:"Main+2 PiP",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2631
+ <rect x="1" y="1" width="10" height="10" rx="1" fill-opacity="0.3" />
2632
+ <rect x="7" y="4" width="3.5" height="2.5" rx="0.5" />
2633
+ <rect x="7" y="7" width="3.5" height="2.5" rx="0.5" />
2634
+ </svg>`,minSources:3},{mode:"split-pip-r",label:"Split+PiP",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2635
+ <rect x="1" y="1" width="4.5" height="10" rx="1" />
2636
+ <rect x="6.5" y="1" width="4.5" height="10" rx="1" fill-opacity="0.5" />
2637
+ <rect x="7.5" y="7" width="2.5" height="2.5" rx="0.5" />
2638
+ </svg>`,minSources:3},{mode:"featured",label:"Featured",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2639
+ <rect x="1" y="1" width="10" height="7.5" rx="1" />
2640
+ <rect x="1" y="9" width="3" height="2" rx="0.5" fill-opacity="0.5" />
2641
+ <rect x="4.5" y="9" width="3" height="2" rx="0.5" fill-opacity="0.5" />
2642
+ <rect x="8" y="9" width="3" height="2" rx="0.5" fill-opacity="0.5" />
2643
+ </svg>`,minSources:3},{mode:"featured-r",label:"Featured ▶",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2644
+ <rect x="1" y="1" width="8" height="10" rx="1" />
2645
+ <rect x="9.5" y="1" width="1.5" height="3" rx="0.5" fill-opacity="0.5" />
2646
+ <rect x="9.5" y="4.5" width="1.5" height="3" rx="0.5" fill-opacity="0.5" />
2647
+ <rect x="9.5" y="8" width="1.5" height="3" rx="0.5" fill-opacity="0.5" />
2648
+ </svg>`,minSources:3},{mode:"grid",label:"Grid",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2649
+ <rect x="1" y="1" width="4.5" height="4.5" rx="1" />
2650
+ <rect x="6.5" y="1" width="4.5" height="4.5" rx="1" />
2651
+ <rect x="1" y="6.5" width="4.5" height="4.5" rx="1" />
2652
+ <rect x="6.5" y="6.5" width="4.5" height="4.5" rx="1" />
2653
+ </svg>`,minSources:2},{mode:"stack",label:"Stack",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2654
+ <rect x="1" y="1" width="10" height="2.8" rx="0.5" />
2655
+ <rect x="1" y="4.6" width="10" height="2.8" rx="0.5" />
2656
+ <rect x="1" y="8.2" width="10" height="2.8" rx="0.5" />
2657
+ </svg>`,minSources:2}],lt=[{mode:"letterbox",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2658
+ <rect x="1" y="3" width="10" height="6" rx="1" />
2659
+ <rect x="0" y="1" width="12" height="1.5" fill-opacity="0.3" />
2660
+ <rect x="0" y="9.5" width="12" height="1.5" fill-opacity="0.3" />
2661
+ </svg>`,label:"Letterbox (fit)"},{mode:"crop",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2662
+ <rect x="0" y="0" width="12" height="12" rx="1" />
2663
+ <path
2664
+ d="M2 0v2H0v1h3V0H2zM10 0v3h2V2h-2V0H9v3h3V2h-2V0h1zM0 9v1h2v2h1V9H0zM12 9H9v3h1v-2h2v-1z"
2665
+ fill-opacity="0.5"
2666
+ />
2667
+ </svg>`,label:"Crop (fill)"},{mode:"stretch",icon:()=>q`<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
2668
+ <rect x="1" y="1" width="10" height="10" rx="1" fill-opacity="0.3" />
2669
+ <path
2670
+ d="M3 5.5h6M3 5l-1.5 1L3 7M9 5l1.5 1L9 7M5.5 3v6M5 3L6 1.5 7 3M5 9l1 1.5 1-1.5"
2671
+ stroke="currentColor"
2672
+ stroke-width="1"
2673
+ fill="none"
2674
+ />
2675
+ </svg>`,label:"Stretch"}];let dt=class extends le{constructor(){super(...arguments),this._tooltipText="",this._tooltipTarget=null}render(){const e=this.ic.s.sources.length,t=ct.filter(t=>function(e,t){const r=Le.find(t=>t.mode===e);return!!r&&t>=r.minSources&&t<=r.maxSources}(t.mode,e));return q`
2676
+ <div class="fw-sc-layout-overlay">
2677
+ <div class="fw-sc-layout-bar">
2678
+ <div class="fw-sc-layout-section">
2679
+ <span class="fw-sc-layout-label">Layout</span>
2680
+ <div class="fw-sc-layout-icons">
2681
+ ${t.map(e=>q`
2682
+ <button
2683
+ type="button"
2684
+ class="fw-sc-layout-icon"
2685
+ @click=${t=>{t.stopPropagation(),this._handleLayoutSelect(e.mode)}}
2686
+ title=${e.label}
2687
+ >
2688
+ ${e.icon()}
2689
+ </button>
2690
+ `)}
2691
+ </div>
2692
+ </div>
2693
+ <div class="fw-sc-layout-separator"></div>
2694
+ <div class="fw-sc-layout-section">
2695
+ <span class="fw-sc-layout-label">Display</span>
2696
+ <div class="fw-sc-scaling-icons">
2697
+ ${lt.map(e=>q`
2698
+ <button
2699
+ type="button"
2700
+ class="fw-sc-layout-icon"
2701
+ @click=${e=>{e.stopPropagation()}}
2702
+ title=${e.label}
2703
+ >
2704
+ ${e.icon()}
2705
+ </button>
2706
+ `)}
2707
+ </div>
2708
+ </div>
2709
+ </div>
2710
+ </div>
2711
+ `}_handleLayoutSelect(e){this.dispatchEvent(new CustomEvent("fw-sc-layout-select",{detail:{mode:e},bubbles:!0,composed:!0}))}};dt.styles=[be,xe,c`
2712
+ :host {
2713
+ display: contents;
2714
+ }
2715
+ `],r([ge({attribute:!1})],dt.prototype,"ic",void 0),r([fe()],dt.prototype,"_tooltipText",void 0),r([fe()],dt.prototype,"_tooltipTarget",void 0),dt=r([he("fw-sc-compositor")],dt);let ht=class extends le{constructor(){super(...arguments),this.scenes=[],this.activeSceneId=null,this.showTransitionControls=!0,this._selectedTransition="fade",this._transitionDuration=500,this._isTransitioning=!1}render(){return q`
2716
+ <div class="fw-sc-scene-switcher">
2717
+ <div class="fw-sc-scene-switcher-header">
2718
+ <span class="fw-sc-scene-switcher-title">Scenes</span>
2719
+ ${this.showTransitionControls?q`
2720
+ <div class="fw-sc-transition-controls">
2721
+ <select
2722
+ class="fw-sc-transition-select"
2723
+ .value=${this._selectedTransition}
2724
+ @change=${e=>{this._selectedTransition=e.target.value}}
2725
+ >
2726
+ <option value="cut">Cut</option>
2727
+ <option value="fade">Fade</option>
2728
+ <option value="slide-left">Slide Left</option>
2729
+ <option value="slide-right">Slide Right</option>
2730
+ <option value="slide-up">Slide Up</option>
2731
+ <option value="slide-down">Slide Down</option>
2732
+ </select>
2733
+ <input
2734
+ type="number"
2735
+ class="fw-sc-transition-duration"
2736
+ .value=${String(this._transitionDuration)}
2737
+ @change=${e=>{this._transitionDuration=Number(e.target.value)}}
2738
+ min="0"
2739
+ max="3000"
2740
+ step="100"
2741
+ title="Transition duration (ms)"
2742
+ />
2743
+ <span class="fw-sc-transition-unit">ms</span>
2744
+ </div>
2745
+ `:G}
2746
+ </div>
2747
+
2748
+ <div class="fw-sc-scene-list">
2749
+ ${this.scenes.map(e=>q`
2750
+ <div
2751
+ class=${ye({"fw-sc-scene-item":!0,"fw-sc-scene-item--active":e.id===this.activeSceneId,"fw-sc-scene-item--transitioning":this._isTransitioning})}
2752
+ @click=${()=>this._handleSceneClick(e.id)}
2753
+ style="background-color:${e.backgroundColor}"
2754
+ >
2755
+ <span class="fw-sc-scene-name">${e.name}</span>
2756
+ <span class="fw-sc-scene-layer-count">${e.layers.length} layers</span>
2757
+ ${this.scenes.length>1&&e.id!==this.activeSceneId?q`
2758
+ <button
2759
+ class="fw-sc-scene-delete"
2760
+ @click=${t=>{t.stopPropagation(),this._handleDelete(e.id)}}
2761
+ title="Delete scene"
2762
+ >
2763
+ ×
2764
+ </button>
2765
+ `:G}
2766
+ </div>
2767
+ `)}
2768
+
2769
+ <button
2770
+ class="fw-sc-scene-add"
2771
+ @click=${()=>this.dispatchEvent(new CustomEvent("fw-sc-scene-create",{bubbles:!0,composed:!0}))}
2772
+ title="Create new scene"
2773
+ >
2774
+ +
2775
+ </button>
2776
+ </div>
2777
+ </div>
2778
+ `}async _handleSceneClick(e){if(e!==this.activeSceneId&&!this._isTransitioning){this._isTransitioning=!0;try{this.dispatchEvent(new CustomEvent("fw-sc-scene-select",{detail:{sceneId:e,transition:{type:this._selectedTransition,durationMs:this._transitionDuration,easing:"ease-in-out"}},bubbles:!0,composed:!0}))}finally{this._isTransitioning=!1}}}_handleDelete(e){this.scenes.length<=1||this.dispatchEvent(new CustomEvent("fw-sc-scene-delete",{detail:{sceneId:e},bubbles:!0,composed:!0}))}};ht.styles=[be,xe,c`
2779
+ :host {
2780
+ display: block;
2781
+ }
2782
+ `],r([ge({attribute:!1})],ht.prototype,"scenes",void 0),r([ge({type:String,attribute:"active-scene-id"})],ht.prototype,"activeSceneId",void 0),r([ge({type:Boolean,attribute:"show-transition-controls"})],ht.prototype,"showTransitionControls",void 0),r([fe()],ht.prototype,"_selectedTransition",void 0),r([fe()],ht.prototype,"_transitionDuration",void 0),r([fe()],ht.prototype,"_isTransitioning",void 0),ht=r([he("fw-sc-scene-switcher")],ht);let ut=class extends le{constructor(){super(...arguments),this.layers=[],this.sources=[],this.selectedLayerId=null,this._draggedId=null,this._dragOverId=null,this._editingLayerId=null}get _sortedLayers(){return[...this.layers].sort((e,t)=>t.zIndex-e.zIndex)}_getSourceLabel(e){const t=this.sources.find(t=>t.id===e);return t?.label||e}_getSourceIcon(e){const t=this.sources.find(t=>t.id===e);switch(t?.type){case"camera":return Se(14);case"screen":return ke(14);default:return $e(14)}}render(){const e=this._sortedLayers;return q`
2783
+ <div class="fw-sc-layer-list">
2784
+ <div class="fw-sc-layer-list-header">
2785
+ <span class="fw-sc-layer-list-title">Layers</span>
2786
+ <span class="fw-sc-layer-count">${this.layers.length}</span>
2787
+ </div>
2788
+
2789
+ <div class="fw-sc-layer-items">
2790
+ ${0===e.length?q` <div class="fw-sc-layer-empty">No layers. Add a source to get started.</div> `:e.map((t,r)=>q`
2791
+ <div
2792
+ class=${ye({"fw-sc-layer-item":!0,"fw-sc-layer-item--selected":t.id===this.selectedLayerId,"fw-sc-layer-item--dragging":t.id===this._draggedId,"fw-sc-layer-item--drag-over":t.id===this._dragOverId,"fw-sc-layer-item--hidden":!t.visible})}
2793
+ draggable="true"
2794
+ @dragstart=${e=>this._handleDragStart(e,t.id)}
2795
+ @dragover=${e=>this._handleDragOver(e,t.id)}
2796
+ @dragleave=${()=>{this._dragOverId=null}}
2797
+ @drop=${e=>this._handleDrop(e,t.id)}
2798
+ @dragend=${()=>{this._draggedId=null,this._dragOverId=null}}
2799
+ @click=${()=>this._dispatch("fw-sc-layer-select",{layerId:t.id===this.selectedLayerId?null:t.id})}
2800
+ >
2801
+ <button
2802
+ class=${ye({"fw-sc-layer-visibility":!0,"fw-sc-layer-visibility--visible":t.visible})}
2803
+ @click=${e=>{e.stopPropagation(),this._dispatch("fw-sc-visibility-toggle",{layerId:t.id,visible:!t.visible})}}
2804
+ title=${t.visible?"Hide layer":"Show layer"}
2805
+ >
2806
+ ${t.visible?((e=14)=>q` <svg
2807
+ width="${e}"
2808
+ height="${e}"
2809
+ viewBox="0 0 24 24"
2810
+ fill="none"
2811
+ stroke="currentColor"
2812
+ stroke-width="2"
2813
+ stroke-linecap="round"
2814
+ stroke-linejoin="round"
2815
+ >
2816
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
2817
+ <circle cx="12" cy="12" r="3" />
2818
+ </svg>`)(14):((e=14)=>q` <svg
2819
+ width="${e}"
2820
+ height="${e}"
2821
+ viewBox="0 0 24 24"
2822
+ fill="none"
2823
+ stroke="currentColor"
2824
+ stroke-width="2"
2825
+ stroke-linecap="round"
2826
+ stroke-linejoin="round"
2827
+ >
2828
+ <path
2829
+ d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"
2830
+ />
2831
+ <line x1="1" y1="1" x2="23" y2="23" />
2832
+ </svg>`)(14)}
2833
+ </button>
2834
+ <span class="fw-sc-layer-icon">${this._getSourceIcon(t.sourceId)}</span>
2835
+ <span class="fw-sc-layer-name">${this._getSourceLabel(t.sourceId)}</span>
2836
+
2837
+ ${this._editingLayerId===t.id?q`
2838
+ <div class="fw-sc-layer-opacity">
2839
+ <input
2840
+ type="range"
2841
+ min="0"
2842
+ max="1"
2843
+ step="0.1"
2844
+ .value=${String(t.transform.opacity)}
2845
+ @input=${e=>this._dispatch("fw-sc-transform-edit",{layerId:t.id,transform:{opacity:Number(e.target.value)}})}
2846
+ @click=${e=>e.stopPropagation()}
2847
+ />
2848
+ <span>${Math.round(100*t.transform.opacity)}%</span>
2849
+ </div>
2850
+ `:G}
2851
+
2852
+ <div class="fw-sc-layer-controls">
2853
+ <button
2854
+ class="fw-sc-layer-btn"
2855
+ @click=${e=>{e.stopPropagation(),this._moveUp(t.id)}}
2856
+ ?disabled=${0===r}
2857
+ title="Move up"
2858
+ >
2859
+
2860
+ </button>
2861
+ <button
2862
+ class="fw-sc-layer-btn"
2863
+ @click=${e=>{e.stopPropagation(),this._moveDown(t.id)}}
2864
+ ?disabled=${r===e.length-1}
2865
+ title="Move down"
2866
+ >
2867
+
2868
+ </button>
2869
+ <button
2870
+ class=${ye({"fw-sc-layer-btn":!0,"fw-sc-layer-btn--active":this._editingLayerId===t.id})}
2871
+ @click=${e=>{e.stopPropagation(),this._editingLayerId=this._editingLayerId===t.id?null:t.id}}
2872
+ title="Edit opacity"
2873
+ >
2874
+
2875
+ </button>
2876
+ <button
2877
+ class="fw-sc-layer-btn fw-sc-layer-btn--danger"
2878
+ @click=${e=>{e.stopPropagation(),this._dispatch("fw-sc-layer-remove",{layerId:t.id})}}
2879
+ title="Remove layer"
2880
+ >
2881
+ ×
2882
+ </button>
2883
+ </div>
2884
+ </div>
2885
+ `)}
2886
+ </div>
2887
+ </div>
2888
+ `}_dispatch(e,t){this.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0}))}_handleDragStart(e,t){this._draggedId=t,e.dataTransfer.effectAllowed="move",e.dataTransfer.setData("text/plain",t)}_handleDragOver(e,t){e.preventDefault(),e.dataTransfer.dropEffect="move",this._dragOverId=t}_handleDrop(e,t){if(e.preventDefault(),this._dragOverId=null,!this._draggedId||this._draggedId===t)return void(this._draggedId=null);const r=this._sortedLayers.map(e=>e.id),i=r.indexOf(this._draggedId),s=r.indexOf(t);if(-1===i||-1===s)return void(this._draggedId=null);const o=[...r];o.splice(i,1),o.splice(s,0,this._draggedId),this._dispatch("fw-sc-reorder",{layerIds:o}),this._draggedId=null}_moveUp(e){const t=this._sortedLayers.map(e=>e.id),r=t.indexOf(e);r<=0||([t[r-1],t[r]]=[t[r],t[r-1]],this._dispatch("fw-sc-reorder",{layerIds:t}))}_moveDown(e){const t=this._sortedLayers.map(e=>e.id),r=t.indexOf(e);r>=t.length-1||([t[r],t[r+1]]=[t[r+1],t[r]],this._dispatch("fw-sc-reorder",{layerIds:t}))}};ut.styles=[be,xe,c`
2889
+ :host {
2890
+ display: block;
2891
+ }
2892
+ `],r([ge({attribute:!1})],ut.prototype,"layers",void 0),r([ge({attribute:!1})],ut.prototype,"sources",void 0),r([ge({type:String,attribute:"selected-layer-id"})],ut.prototype,"selectedLayerId",void 0),r([fe()],ut.prototype,"_draggedId",void 0),r([fe()],ut.prototype,"_dragOverId",void 0),r([fe()],ut.prototype,"_editingLayerId",void 0),ut=r([he("fw-sc-layer-list")],ut);let pt=class extends le{constructor(){super(...arguments),this.value=1,this.min=0,this.max=2,this.snapThreshold=.05,this.compact=!1,this._isDragging=!1,this._popupPosition=0}get _displayValue(){return Math.round(100*this.value)}get _isBoost(){return this.value>1}get _isDefault(){return 1===this.value}get _accentColor(){return this._isBoost?"#e0af68":this._isDefault?"#9ece6a":"#7aa2f7"}_handleChange(e){let t=parseInt(e.target.value,10)/100;Math.abs(t-1)<=this.snapThreshold&&(t=1),this.dispatchEvent(new CustomEvent("fw-sc-volume-change",{detail:{value:t},bubbles:!0,composed:!0})),this._updatePopupPosition(t)}_handleMouseDown(){this._isDragging=!0,this._updatePopupPosition(this.value)}_handleMouseUp(){this._isDragging=!1}_updatePopupPosition(e){if(this._slider){const t=this._slider.getBoundingClientRect(),r=(e-this.min)/(this.max-this.min);this._popupPosition=r*t.width}}render(){const e=1/this.max*100+"%",t=this._isDefault?"#9ece6a":"rgba(158, 206, 106, 0.3)";return q`
2893
+ ${this._isDragging?q`
2894
+ <div
2895
+ class="popup"
2896
+ style="left:${this._popupPosition}px;background:${this._accentColor}"
2897
+ >
2898
+ ${this._displayValue}%${this._isDefault?" (default)":""}
2899
+ <div class="popup-arrow" style="border-top:6px solid ${this._accentColor}"></div>
2900
+ </div>
2901
+ `:""}
2902
+ <div class="track">
2903
+ <div class="marker" style="left:${e};background:${t}"></div>
2904
+ <input
2905
+ type="range"
2906
+ .min=${String(100*this.min)}
2907
+ .max=${String(100*this.max)}
2908
+ .value=${String(Math.round(100*this.value))}
2909
+ @input=${this._handleChange}
2910
+ @mousedown=${this._handleMouseDown}
2911
+ @mouseup=${this._handleMouseUp}
2912
+ @mouseleave=${this._handleMouseUp}
2913
+ @touchstart=${this._handleMouseDown}
2914
+ @touchend=${this._handleMouseUp}
2915
+ style="accent-color:${this._accentColor}"
2916
+ />
2917
+ </div>
2918
+ `}};pt.styles=[be,c`
2919
+ :host {
2920
+ display: inline-flex;
2921
+ position: relative;
2922
+ flex: 1;
2923
+ }
2924
+ :host([compact]) {
2925
+ min-width: 60px;
2926
+ }
2927
+ :host(:not([compact])) {
2928
+ min-width: 100px;
2929
+ }
2930
+ .track {
2931
+ position: relative;
2932
+ width: 100%;
2933
+ }
2934
+ .marker {
2935
+ position: absolute;
2936
+ top: 0;
2937
+ bottom: 0;
2938
+ width: 2px;
2939
+ border-radius: 1px;
2940
+ z-index: 1;
2941
+ pointer-events: none;
2942
+ transform: translateX(-50%);
2943
+ }
2944
+ input[type="range"] {
2945
+ width: 100%;
2946
+ height: 6px;
2947
+ border-radius: 3px;
2948
+ cursor: pointer;
2949
+ }
2950
+ .popup {
2951
+ position: absolute;
2952
+ bottom: 100%;
2953
+ transform: translateX(-50%);
2954
+ margin-bottom: 8px;
2955
+ padding: 4px 8px;
2956
+ color: #1a1b26;
2957
+ border-radius: 4px;
2958
+ font-size: 12px;
2959
+ font-weight: 600;
2960
+ font-family: monospace;
2961
+ white-space: nowrap;
2962
+ pointer-events: none;
2963
+ z-index: 100;
2964
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
2965
+ }
2966
+ .popup-arrow {
2967
+ position: absolute;
2968
+ top: 100%;
2969
+ left: 50%;
2970
+ transform: translateX(-50%);
2971
+ width: 0;
2972
+ height: 0;
2973
+ border-left: 6px solid transparent;
2974
+ border-right: 6px solid transparent;
2975
+ }
2976
+ `],r([ge({type:Number})],pt.prototype,"value",void 0),r([ge({type:Number})],pt.prototype,"min",void 0),r([ge({type:Number})],pt.prototype,"max",void 0),r([ge({type:Number,attribute:"snap-threshold"})],pt.prototype,"snapThreshold",void 0),r([ge({type:Boolean})],pt.prototype,"compact",void 0),r([fe()],pt.prototype,"_isDragging",void 0),r([fe()],pt.prototype,"_popupPosition",void 0),r([me("input[type=range]")],pt.prototype,"_slider",void 0),pt=r([he("fw-sc-volume")],pt);let gt=class extends le{constructor(){super(...arguments),this._activeTab="stats"}render(){return q`
2977
+ <div class="panel">
2978
+ <div class="header">
2979
+ <div class="tabs">
2980
+ <button
2981
+ class=${ye({tab:!0,"tab--active":"stats"===this._activeTab})}
2982
+ @click=${()=>{this._activeTab="stats"}}
2983
+ >
2984
+ Stats
2985
+ </button>
2986
+ <button
2987
+ class=${ye({tab:!0,"tab--active":"info"===this._activeTab})}
2988
+ @click=${()=>{this._activeTab="info"}}
2989
+ >
2990
+ Info
2991
+ </button>
2992
+ </div>
2993
+ <button
2994
+ class="close"
2995
+ @click=${()=>this.dispatchEvent(new CustomEvent("fw-close",{bubbles:!0,composed:!0}))}
2996
+ aria-label="Close panel"
2997
+ >
2998
+ ${Ce(14)}
2999
+ </button>
3000
+ </div>
3001
+ <div class="body">
3002
+ ${"stats"===this._activeTab?this._renderStats():this._renderInfo()}
3003
+ </div>
3004
+ </div>
3005
+ `}_renderStats(){const e=this.ic.s,t=e.stats;return q`
3006
+ <div class="section">
3007
+ <div class="label">Connection</div>
3008
+ ${this._row("State",e.state)}
3009
+ ${this._row("WebCodecs",e.isWebCodecsActive?"Active":e.useWebCodecs?"Pending":"Off")}
3010
+ </div>
3011
+ ${t?q`
3012
+ <div class="section">
3013
+ <div class="label">WebRTC Stats</div>
3014
+ ${this._row("Video bitrate",t.video.bitrate?`${Math.round(t.video.bitrate/1e3)} kbps`:"—")}
3015
+ ${this._row("Audio bitrate",t.audio.bitrate?`${Math.round(t.audio.bitrate/1e3)} kbps`:"—")}
3016
+ ${this._row("RTT",t.connection.rtt?`${(1e3*t.connection.rtt).toFixed(0)} ms`:"—")}
3017
+ ${this._row("FPS",t.video.framesPerSecond?String(t.video.framesPerSecond):"—")}
3018
+ ${this._row("Packets sent",String(t.video.packetsSent))}
3019
+ ${this._row("Packets lost",String(t.video.packetsLost))}
3020
+ </div>
3021
+ `:G}
3022
+ ${e.encoderStats?q`
3023
+ <div class="section">
3024
+ <div class="label">Encoder</div>
3025
+ ${this._row("Video frames",String(e.encoderStats.video.framesEncoded))}
3026
+ ${this._row("Video pending",String(e.encoderStats.video.framesPending))}
3027
+ ${this._row("Audio samples",String(e.encoderStats.audio.samplesEncoded))}
3028
+ </div>
3029
+ `:G}
3030
+ `}_renderInfo(){const e=this.ic.s;return q`
3031
+ <div class="section">
3032
+ <div class="label">Configuration</div>
3033
+ ${this._row("Profile",e.qualityProfile)} ${this._row("Sources",String(e.sources.length))}
3034
+ ${this._row("WebCodecs Available",e.isWebCodecsAvailable?"Yes":"No")}
3035
+ </div>
3036
+ <div class="section">
3037
+ <div class="label">Sources</div>
3038
+ ${e.sources.map(e=>q`
3039
+ ${this._row(e.label,`${e.type} ${e.muted?"(muted)":""}`)}
3040
+ `)}
3041
+ </div>
3042
+ `}_row(e,t){return q`<div class="row">
3043
+ <span class="row-label">${e}</span><span class="row-value">${t}</span>
3044
+ </div>`}};function ft(e,t){customElements.get(e)||customElements.define(e,t)}return gt.styles=[be,xe,c`
3045
+ :host {
3046
+ display: block;
3047
+ }
3048
+ .panel {
3049
+ width: 320px;
3050
+ height: 100%;
3051
+ border-left: 1px solid rgba(90, 96, 127, 0.3);
3052
+ background: #1a1b26;
3053
+ overflow: auto;
3054
+ font-size: 0.75rem;
3055
+ color: #a9b1d6;
3056
+ }
3057
+ .header {
3058
+ display: flex;
3059
+ align-items: center;
3060
+ justify-content: space-between;
3061
+ padding: 0.5rem 0.75rem;
3062
+ border-bottom: 1px solid rgba(90, 96, 127, 0.3);
3063
+ }
3064
+ .tabs {
3065
+ display: flex;
3066
+ gap: 0.5rem;
3067
+ }
3068
+ .tab {
3069
+ padding: 0.25rem 0.5rem;
3070
+ border: none;
3071
+ background: none;
3072
+ color: #565f89;
3073
+ font-size: 0.6875rem;
3074
+ font-weight: 600;
3075
+ cursor: pointer;
3076
+ border-radius: 0.25rem;
3077
+ }
3078
+ .tab--active {
3079
+ color: #c0caf5;
3080
+ background: rgba(90, 96, 127, 0.2);
3081
+ }
3082
+ .close {
3083
+ display: flex;
3084
+ background: none;
3085
+ border: none;
3086
+ color: #565f89;
3087
+ cursor: pointer;
3088
+ padding: 0;
3089
+ }
3090
+ .close:hover {
3091
+ color: #c0caf5;
3092
+ }
3093
+ .body {
3094
+ padding: 0.75rem;
3095
+ }
3096
+ .section {
3097
+ margin-bottom: 0.75rem;
3098
+ }
3099
+ .label {
3100
+ font-size: 0.625rem;
3101
+ font-weight: 600;
3102
+ text-transform: uppercase;
3103
+ letter-spacing: 0.05em;
3104
+ color: #565f89;
3105
+ margin-bottom: 0.375rem;
3106
+ }
3107
+ .row {
3108
+ display: flex;
3109
+ justify-content: space-between;
3110
+ padding: 0.125rem 0;
3111
+ }
3112
+ .row-label {
3113
+ color: #565f89;
3114
+ }
3115
+ .row-value {
3116
+ color: #c0caf5;
3117
+ font-family: ui-monospace, monospace;
3118
+ font-variant-numeric: tabular-nums;
3119
+ }
3120
+ `],r([ge({attribute:!1})],gt.prototype,"ic",void 0),r([fe()],gt.prototype,"_activeTab",void 0),gt=r([he("fw-sc-advanced")],gt),ft("fw-streamcrafter",e.FwStreamCrafter),ft("fw-sc-compositor",dt),ft("fw-sc-scene-switcher",ht),ft("fw-sc-layer-list",ut),ft("fw-sc-volume",pt),ft("fw-sc-advanced",gt),e.IngestControllerHost=nt,e}({});
3121
+ //# sourceMappingURL=fw-streamcrafter.iife.js.map