{"Document":{"ObjectName":"Scenario::ScenarioDocumentModel","id":1,"BaseScenario":{"ObjectName":"Scenario::BaseScenario","id":0,"Constraint":{"ObjectName":"Scenario::IntervalModel","id":0,"Metadata":{"ScriptingName":"http-llm","Comment":"","Color":"Transparent1","Label":"","Touched":true},"Inlet":{"uuid":"a1574bb0-cbd4-4c7d-9417-0c25cfd1187b","ObjectName":"Inlet","id":0,"Hidden":false,"Custom":"Audio In","Exposed":"audio in"},"Outlet":{"uuid":"a1d97535-18ac-444a-8417-0cbc1692d897","ObjectName":"Outlet","id":0,"Hidden":false,"Custom":"Audio Out","Exposed":"audio out","Address":"audio:/out/main","GainInlet":{"uuid":"9a13fb32-269a-47bf-99a9-930188c1f19c","ObjectName":"Inlet","id":10000,"Hidden":false,"Custom":"Gain","Exposed":"gain","Value":{},"Init":{},"Domain":{"Float":{"Min":0.0,"Max":1.0}}},"PanInlet":{"uuid":"9a13fb32-269a-47bf-99a9-930188c1f19c","ObjectName":"Inlet","id":10001,"Hidden":false,"Custom":"Pan","Exposed":"pan","Value":{},"Init":{},"Domain":{}},"Gain":1.0,"Pan":[1.0,1.0],"Propagate":true},"Processes":[{"uuid":"846a5de5-47f9-46c5-a898-013cb20951d0","ObjectName":"Javascript","id":1,"Metadata":{"ScriptingName":"Answer","Comment":"","Color":"Transparent1","Label":"","Touched":true},"Duration":10584000000,"Height":300.0,"StartOffset":0,"LoopDuration":10584000000,"Pos":[-108.3343462430674,-83.3153462430674],"Size":[278.0,135.0],"Loops":false,"FoldMode":0,"ProcessUIPlacement":"Side panel","Script":"import Score\nimport QtQuick\nimport \"TextRender.js\" as TextRender\nimport OssiaUI as S\n\nScript {\n    id: root\n\n    TextureInlet {\n        id: texFill\n        objectName: \"Fill Texture\"\n    }\n\n    ValueInlet {\n        id: textContent\n        objectName: \"Text\"\n    }\n    ValueInlet {\n        id: textStyleIn\n        objectName: \"Style\"\n    }\n    FloatSlider {\n        id: masterOpacity\n        objectName: \"Opacity\"\n        min: 0; max: 1; init: 1\n    }\n    FloatSlider {\n        id: writeOn\n        objectName: \"Write On\"\n        min: 0; max: 1; init: 1\n    }\n    HSVSlider {\n        id: colorOverride\n        objectName: \"Color\"\n    }\n\n    TextureOutlet {\n        objectName: \"Output\"\n        item: Item {\n            id: outputRoot\n            anchors.fill: parent\n\n            // Hidden container: parents the texture inlet item into the scene\n            // graph so ShaderEffectSource can render from it.\n            Item {\n                id: texFillContainer\n                anchors.fill: parent\n                visible: false\n\n                Component.onCompleted: {\n                    if (texFill.item) {\n                        texFill.item.parent = texFillContainer;\n                        texFill.item.width = Qt.binding(function() { return texFillContainer.width; });\n                        texFill.item.height = Qt.binding(function() { return texFillContainer.height; });\n                    }\n                }\n            }\n\n            // Bridge: ShaderEffectSource mirrors the inlet's texture as a\n            // proper QML item (with engine), so grabToImage() works on it.\n            ShaderEffectSource {\n                id: texBridge\n                anchors.fill: parent\n                sourceItem: texFill.item\n                visible: false\n                live: true\n            }\n\n            Canvas {\n                id: textCanvas\n                anchors.fill: parent\n                renderStrategy: Canvas.Cooperative\n\n                property int ver: root.stateVersion\n                onVerChanged: requestPaint()\n                property bool editing: inlineText.editing\n                onEditingChanged: requestPaint()\n                onImageLoaded: requestPaint()\n\n                onPaint: {\n                    var ctx = getContext(\"2d\");\n                    // Keep the Canvas and its backing texture alive while the\n                    // editor replaces the text; hiding it loses output resources.\n                    if (editing) {\n                        ctx.clearRect(0, 0, width, height);\n                        return;\n                    }\n                    // For texture fill, pass the grabbed image URL (Canvas-drawable)\n                    // instead of the QQuickRhiItem (which Canvas can't draw).\n                    var texItem = null;\n                    if (root.textState.fillType === \"texture\"\n                        && root.texGrabUrl\n                        && isImageLoaded(root.texGrabUrl))\n                    {\n                        texItem = root.texGrabUrl;\n                    }\n                    TextRender.paintText(ctx, width, height,\n                        root.textState, root.getInletValues(), texItem);\n                }\n            }\n\n            MouseArea {\n                anchors.fill: parent\n                acceptedButtons: Qt.LeftButton\n                onPressed: function(mouse) {\n                    if (inlineText.editing) inlineText.finish(true);\n                    if (!root.canEditText()) mouse.accepted = false;\n                }\n                onDoubleClicked: function(mouse) {\n                    if (!root.canEditText() || !textCanvas.available) return;\n                    var g = TextRender.editingGeometry(textCanvas.getContext(\"2d\"),\n                        outputRoot.width, outputRoot.height, root.textState, root.getInletValues());\n                    var det = g.a * g.d - g.b * g.c;\n                    if (Math.abs(det) < 0.000001) return;\n                    var dx = mouse.x - g.x, dy = mouse.y - g.y;\n                    var x = (g.d * dx - g.c * dy) / det;\n                    var y = (-g.b * dx + g.a * dy) / det;\n                    if (x < 0 || y < 0 || x > g.width || y > g.height) return;\n                    inlineText.geometry = g;\n                    inlineText.editStyle = TextRender.mergeState({}, root.textState);\n                    inlineText.begin(String(root.textState.text || \"\"));\n                }\n            }\n\n            S.SInlineTextEditor {\n                id: inlineText\n                // Hold the text block still while typing; the animation clock keeps running.\n                property var geometry: ({a: 1, b: 0, c: 0, d: 1, x: 0, y: 0, width: 1, height: 1})\n                property var editStyle: ({})\n                width: geometry.width\n                height: Math.max(geometry.height, contentHeight)\n                padding: 0\n                wrapMode: editStyle.wordWrap ? TextEdit.WordWrap : TextEdit.NoWrap\n                horizontalAlignment: editStyle.hAlign === \"left\" ? TextEdit.AlignLeft\n                    : (editStyle.hAlign === \"right\" ? TextEdit.AlignRight : TextEdit.AlignHCenter)\n                font.family: editStyle.fontFamily || \"Arial\"\n                font.pixelSize: editStyle.fontSize || 72\n                font.bold: editStyle.fontWeight === \"bold\"\n                font.italic: editStyle.fontStyle === \"italic\"\n                font.underline: !!editStyle.underline\n                font.strikeout: !!editStyle.strikethrough\n                font.letterSpacing: editStyle.tracking || 0\n                transform: Matrix4x4 {\n                    matrix: Qt.matrix4x4(inlineText.geometry.a, inlineText.geometry.c, 0, inlineText.geometry.x,\n                        inlineText.geometry.b, inlineText.geometry.d, 0, inlineText.geometry.y,\n                        0, 0, 1, 0, 0, 0, 0, 1)\n                }\n                onEdited: function(value) {\n                    root.textState.text = value;\n                    root.stateVersion++;\n                }\n                onFinished: function(value, accepted) {\n                    if (accepted && root.canEditText() && value !== String(editStyle.text || \"\")) {\n                        root.storedTextState.text = value;\n                        var serialized = JSON.stringify(root.storedTextState);\n                        root.pendingCommits.push(serialized);\n                        root.commitState(\"textState\", serialized);\n                        root.uiSend({type: \"textState\", style: serialized});\n                    }\n                    textCanvas.requestPaint();\n                }\n            }\n        }\n    }\n\n    property var textState: TextRender.defaultState()\n    property var storedTextState: TextRender.defaultState()\n    property var pendingCommits: []\n    property int stateVersion: 0\n    property real lastSentW: 0\n    property real lastSentH: 0\n    property real prevOpacity: -1\n    property real prevWriteOn: -1\n    property string prevText: \"\"\n    property real elapsed: 0\n    property real lastTickMs: 0\n    property bool needsContinuousRepaint: false\n\n    // Texture grab state for Canvas-based texture fill\n    property var texGrabResult: null\n    property string texGrabUrl: \"\"\n    property bool texGrabPending: false\n\n    function styleOverrides() {\n        try {\n            var value = textStyleIn.value;\n            var style = typeof value === \"string\" ? JSON.parse(value) : value;\n            return style && typeof style === \"object\" ? style : null;\n        } catch (error) { return null; }\n    }\n\n    function canEditText() {\n        // Keep the renderer's existing nonempty Text inlet precedence. A Style\n        // inlet that supplies text owns that value even if it is an empty string.\n        var value = textContent.value;\n        if (root.prevText || (value !== undefined && value !== null && String(value))) return false;\n        var style = root.styleOverrides();\n        return !style || !Object.prototype.hasOwnProperty.call(style, \"text\");\n    }\n\n    function applyStoredStyle(style) {\n        inlineText.finish(false);\n        root.storedTextState = TextRender.mergeState(TextRender.defaultState(), style);\n        root.textState = TextRender.mergeState(root.storedTextState, root.styleOverrides());\n        root.stateVersion++;\n    }\n\n    function getInletValues() {\n        var vals = {\n            opacity: masterOpacity.value,\n            writeOn: writeOn.value,\n            elapsed: root.elapsed\n        };\n        if (root.prevText)\n            vals.text = root.prevText;\n        if (colorOverride.value && colorOverride.value.w > 0)\n            vals.colorOverride = colorOverride.value;\n        return vals;\n    }\n\n    loadState: function(state) {\n        var s = null;\n        try {\n            if (state && state.textState)\n                s = typeof state.textState === \"string\" ? JSON.parse(state.textState) : state.textState;\n        } catch (error) {}\n        root.applyStoredStyle(s);\n    }\n\n    stateUpdated: function(k, v) {\n        if (k === \"textState\") {\n            var pending = root.pendingCommits.indexOf(v);\n            if (pending >= 0) { root.pendingCommits.splice(pending, 1); return; }\n            try {\n                var s = typeof v === \"string\" ? JSON.parse(v) : v;\n                root.applyStoredStyle(s);\n            } catch(e) { return; }\n        }\n    }\n\n    uiEvent: function(message) {\n        if (!message) return;\n        if (message.type === \"updateStyle\") {\n            root.applyStoredStyle(message.style || {});\n        } else if (message.type === \"retrigger\") {\n            root.elapsed = 0;\n            root.lastTickMs = Date.now();\n            root.stateVersion++;\n        }\n    }\n\n    tick: function(token, state) {\n        var changed = false;\n        if (inlineText.editing && !root.canEditText()) inlineText.finish(false);\n\n        // Accumulate elapsed time for animations\n        var now = Date.now();\n        if (root.lastTickMs > 0) {\n            root.elapsed += (now - root.lastTickMs) / 1000;\n        }\n        root.lastTickMs = now;\n\n        // Check if continuous repaint is needed (scroll or wave active)\n        var ts = root.textState;\n        root.needsContinuousRepaint =\n            (ts.scrollMode && ts.scrollMode !== \"none\")\n            || ts.charWaveEnabled\n            || ts.charAnimEnabled\n            || (writeOn.value < 1 && ts.writeOnMode === \"scramble\")\n            || ts.fillType === \"texture\";\n\n        if (root.needsContinuousRepaint) changed = true;\n\n        if (textContent.value !== undefined && textContent.value !== null) {\n            var t = String(textContent.value);\n            if (t !== root.prevText) {\n                root.prevText = t;\n                changed = true;\n            }\n        }\n        if (masterOpacity.value !== root.prevOpacity) {\n            root.prevOpacity = masterOpacity.value;\n            changed = true;\n        }\n        if (writeOn.value !== root.prevWriteOn) {\n            root.prevWriteOn = writeOn.value;\n            changed = true;\n        }\n        if (textStyleIn.value !== undefined && textStyleIn.value !== null) {\n            try {\n                var so = typeof textStyleIn.value === \"string\"\n                    ? JSON.parse(textStyleIn.value) : textStyleIn.value;\n                root.textState = TextRender.mergeState(root.textState, so);\n                changed = true;\n            } catch(e) {}\n        }\n\n        // Grab texture inlet content for Canvas use.\n        // Canvas drawImage() can't render QQuickRhiItem directly,\n        // so we snapshot it via a ShaderEffectSource bridge (which is a\n        // proper QML item with an engine) to an image URL Canvas can draw.\n        if (root.textState.fillType === \"texture\" && texFill.item && !root.texGrabPending) {\n            root.texGrabPending = true;\n            var oldUrl = root.texGrabUrl;\n            texBridge.grabToImage(function(result) {\n                if (oldUrl) textCanvas.unloadImage(oldUrl);\n                root.texGrabResult = result; // prevent GC\n                root.texGrabUrl = result.url;\n                root.texGrabPending = false;\n                textCanvas.loadImage(result.url);\n            });\n        }\n\n        if (changed) root.stateVersion++;\n\n        var cw = textCanvas.width, ch = textCanvas.height;\n        if (cw !== root.lastSentW || ch !== root.lastSentH) {\n            root.lastSentW = cw;\n            root.lastSentH = ch;\n            uiSend({ type: \"renderSize\", width: cw, height: ch });\n        }\n    }\n\n    start: function() {\n        root.elapsed = 0;\n        root.lastTickMs = 0;\n    }\n}\n","Ui":"import Score as Score\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport QtQuick.Dialogs\nimport \"TextRender.js\" as TextRender\nimport OssiaUI as S\n\n// Advanced Text — style editor.\n//\n// One scrolling column of collapsible sections, each a thin binding onto a key\n// of `style`. Widgets come from the shared OssiaUI kit, so this editor, the mapper and the\n// tracking-zones editor share one look; nothing here styles a control itself.\nScore.ScriptUI {\n    id: root\n    anchors.fill: parent\n    implicitWidth: 380\n    implicitHeight: 780\n\n    property var style: TextRender.defaultState()\n    property int stateVersion: 0\n    property real renderWidth: 0\n    property real renderHeight: 0\n    property bool applyingState: false\n\n    // ---------------- state plumbing ----------------\n\n    // Read a style key. Touching stateVersion inside makes every binding that\n    // calls this re-evaluate when the document changes (undo, preset load).\n    function sty(key, fallback) {\n        root.stateVersion;\n        var v = root.style[key];\n        return (v === undefined || v === null) ? fallback : v;\n    }\n\n    function sendLive() {\n        root.executionSend({ type: \"updateStyle\", style: root.style });\n    }\n    function saveStyle(action) {\n        root.beginUpdateState(action || \"Edit text\");\n        root.updateState(\"textState\", JSON.stringify(root.style));\n        root.endUpdateState();\n    }\n    // live preview only: no stateVersion bump, so a drag does not fight the\n    // binding that feeds the widget\n    function setLive(key, val) {\n        root.style[key] = val;\n        sendLive();\n    }\n    function setAndSave(key, val, action) {\n        root.style[key] = val;\n        root.stateVersion++;\n        sendLive();\n        saveStyle(action);\n    }\n\n    function applyStyle(style) {\n        root.applyingState = true;\n        textArea.dirty = false;\n        root.style = TextRender.mergeState(TextRender.defaultState(), style);\n        root.stateVersion++;\n        root.applyingState = false;\n    }\n\n    loadState: function(state) {\n        if (state && state.textState) {\n            try {\n                var s = typeof state.textState === \"string\"\n                    ? JSON.parse(state.textState) : state.textState;\n                root.applyStyle(s);\n            } catch(e) {\n                root.applyStyle(null);\n            }\n        }\n        if (!state || !state.textState) root.applyStyle(null);\n    }\n    stateUpdated: function(k, v) {\n        if (k === \"textState\") {\n            try {\n                var s = typeof v === \"string\" ? JSON.parse(v) : v;\n                root.applyStyle(s);\n            } catch(e) { return; }\n        }\n    }\n    executionEvent: function(message) {\n        if (message && message.type === \"renderSize\") {\n            root.renderWidth = message.width;\n            root.renderHeight = message.height;\n        }\n        else if (message && message.type === \"textState\") {\n            try {\n                root.applyStyle(typeof message.style === \"string\"\n                    ? JSON.parse(message.style) : message.style);\n            } catch (error) {}\n        }\n    }\n\n    // scripted UI actions, for the test harness and the Command inlet\n    function uiAction(m) {\n        switch (m.action) {\n        case \"grab\": root.grabToImage(function (res) { res.saveToFile(m.path); }); break;\n        case \"set\": root.setAndSave(m.key, m.value, \"Set \" + m.key); break;\n        case \"section\": {\n            for (var i = 0; i < sections.children.length; i++) {\n                var c = sections.children[i];\n                if (c.title === m.name) c.expanded = !!m.value;\n            }\n            break;\n        }\n        case \"dumpstate\":\n            Util.writeFile(m.path, JSON.stringify({\n                style: root.style,\n                renderWidth: root.renderWidth,\n                renderHeight: root.renderHeight\n            }, null, 1));\n            break;\n        }\n    }\n\n    // ---------------- layout ----------------\n\n    S.ThemedPage {\n        anchors.fill: parent\n\n        ColumnLayout {\n            anchors.fill: parent\n            anchors.margins: S.Theme.gap\n            spacing: S.Theme.gap\n\n            S.SPanel {\n                Layout.fillWidth: true\n                Layout.fillHeight: true\n\n                ScrollView {\n                    id: sv\n                    anchors.fill: parent\n                    anchors.margins: S.Theme.pad\n                    clip: true\n                    ScrollBar.horizontal.policy: ScrollBar.AlwaysOff\n                    contentWidth: availableWidth\n\n                    ColumnLayout {\n                        id: sections\n                        width: sv.availableWidth\n                        spacing: S.Theme.gapSm\n\n                        // ======================== CONTENT ========================\n                        S.SSection {\n                            title: \"Content\"\n\n                            ScrollView {\n                                Layout.fillWidth: true\n                                Layout.preferredHeight: 74\n                                clip: true\n\n                                TextArea {\n                                    id: textArea\n                                    property bool dirty: false\n                                    text: root.sty(\"text\", \"\")\n                                    wrapMode: TextEdit.Wrap\n                                    font.pixelSize: S.Theme.fontLg\n                                    color: S.Theme.text\n                                    placeholderText: \"Enter text\"\n                                    placeholderTextColor: S.Theme.textMuted\n                                    selectByMouse: true\n                                    selectionColor: S.Theme.accentFill\n                                    selectedTextColor: S.Theme.accentText\n                                    padding: S.Theme.gap\n                                    background: Rectangle {\n                                        color: S.Theme.control\n                                        radius: S.Theme.radius\n                                        border.width: 1\n                                        border.color: textArea.activeFocus ? S.Theme.accent : S.Theme.border\n                                    }\n                                    onTextChanged: {\n                                        if (activeFocus && !root.applyingState && root.style.text !== text) {\n                                            dirty = true;\n                                            root.style.text = text;\n                                            root.sendLive();\n                                        }\n                                    }\n                                    onActiveFocusChanged: {\n                                        if (!activeFocus && dirty) {\n                                            dirty = false;\n                                            root.saveStyle(\"Edit text\");\n                                        }\n                                    }\n                                }\n                            }\n                            S.SComboRow {\n                                label: \"Case\"\n                                options: [\"none\", \"uppercase\", \"lowercase\", \"titlecase\"]\n                                labels: [\"Unchanged\", \"Uppercase\", \"Lowercase\", \"Title case\"]\n                                value: root.sty(\"textTransform\", \"none\")\n                                onEdited: function (v) { root.setAndSave(\"textTransform\", v, \"Change transform\"); }\n                            }\n                        }\n\n                        // ========================= FONT =========================\n                        S.SSection {\n                            title: \"Font\"\n\n                            RowLayout {\n                                Layout.fillWidth: true\n                                spacing: S.Theme.gap\n                                S.SFieldLabel { text: \"Family\" }\n                                S.SCombo {\n                                    id: fontCombo\n                                    Layout.fillWidth: true\n                                    editable: true\n\n                                    property string family: root.sty(\"fontFamily\", \"\")\n                                    // Prepend the stored family when it is not installed on this\n                                    // machine, so the field shows what the document actually asks\n                                    // for (the render host may well have the font) instead of\n                                    // blanking on a currentIndex of -1.\n                                    model: {\n                                        var l = Qt.fontFamilies();\n                                        return (family.length && l.indexOf(family) < 0) ? [family].concat(l) : l;\n                                    }\n                                    currentIndex: Math.max(0, model.indexOf(family))\n                                    onActivated: root.setAndSave(\"fontFamily\", currentText, \"Change font\")\n                                    onAccepted: root.setAndSave(\"fontFamily\", editText, \"Change font\")\n                                }\n                            }\n                            S.SSliderRow {\n                                label: \"Size\"; from: 4; to: 500; stepSize: 1; decimals: 0; suffix: \" px\"\n                                defaultValue: 72\n                                value: root.sty(\"fontSize\", 72)\n                                onMoved: function (v) { root.setLive(\"fontSize\", v); }\n                                onCommitted: root.saveStyle(\"Change size\")\n                            }\n                            S.SComboRow {\n                                label: \"Weight\"\n                                options: [\"normal\", \"bold\"]\n                                value: root.sty(\"fontWeight\", \"normal\")\n                                onEdited: function (v) { root.setAndSave(\"fontWeight\", v, \"Change weight\"); }\n                            }\n                            S.SComboRow {\n                                label: \"Style\"\n                                options: [\"normal\", \"italic\"]\n                                value: root.sty(\"fontStyle\", \"normal\")\n                                onEdited: function (v) { root.setAndSave(\"fontStyle\", v, \"Change style\"); }\n                            }\n                            RowLayout {\n                                Layout.fillWidth: true\n                                spacing: S.Theme.gapLg\n                                S.SCheck {\n                                    text: \"Underline\"; font.pixelSize: S.Theme.fontMd\n                                    checked: root.sty(\"underline\", false)\n                                    onToggled: root.setAndSave(\"underline\", checked, \"Toggle underline\")\n                                }\n                                S.SCheck {\n                                    text: \"Strike\"; font.pixelSize: S.Theme.fontMd\n                                    checked: root.sty(\"strikethrough\", false)\n                                    onToggled: root.setAndSave(\"strikethrough\", checked, \"Toggle strikethrough\")\n                                }\n                                Item { Layout.fillWidth: true }\n                            }\n                        }\n\n                        // ========================= FILL =========================\n                        S.SSection {\n                            title: \"Fill\"\n\n                            RowLayout {\n                                Layout.fillWidth: true\n                                spacing: S.Theme.gap\n                                S.SCheck {\n                                    text: \"Enabled\"; font.pixelSize: S.Theme.fontMd\n                                    checked: root.sty(\"fillEnabled\", true) !== false\n                                    onToggled: root.setAndSave(\"fillEnabled\", checked, \"Toggle fill\")\n                                }\n                                S.SCombo {\n                                    Layout.fillWidth: true\n                                    readonly property var options: [\"solid\", \"linearGradient\", \"radialGradient\", \"texture\"]\n                                    model: [\"Solid\", \"Linear gradient\", \"Radial gradient\", \"Texture\"]\n                                    currentIndex: Math.max(0, options.indexOf(root.sty(\"fillType\", \"solid\")))\n                                    onActivated: root.setAndSave(\"fillType\", options[currentIndex], \"Change fill type\")\n                                    ToolTip.visible: hovered && currentIndex === 3\n                                    ToolTip.text: \"Texture uses the Fill Texture inlet.\"\n                                }\n                            }\n                            S.SColorRow {\n                                visible: root.sty(\"fillType\", \"solid\") === \"solid\"\n                                label: \"Colour\"; value: root.sty(\"fillColor\", \"#ffffff\")\n                                onEdited: function (v) { root.setAndSave(\"fillColor\", v, \"Change fill colour\"); }\n                            }\n\n                            ColumnLayout {\n                                visible: {\n                                    var t = root.sty(\"fillType\", \"solid\");\n                                    return t === \"linearGradient\" || t === \"radialGradient\";\n                                }\n                                Layout.fillWidth: true\n                                spacing: S.Theme.gap\n\n                                S.SColorRow {\n                                    label: \"Start\"; value: root.sty(\"gradStartColor\", \"#ffffff\")\n                                    onEdited: function (v) { root.setAndSave(\"gradStartColor\", v, \"Change gradient start\"); }\n                                }\n                                S.SColorRow {\n                                    label: \"End\"; value: root.sty(\"gradEndColor\", \"#000000\")\n                                    onEdited: function (v) { root.setAndSave(\"gradEndColor\", v, \"Change gradient end\"); }\n                                }\n                                S.SSliderRow {\n                                    visible: root.sty(\"fillType\", \"solid\") === \"linearGradient\"\n                                    label: \"Angle\"; from: 0; to: 360; stepSize: 1; decimals: 0; suffix: \"°\"\n                                    value: root.sty(\"gradAngle\", 0)\n                                    onMoved: function (v) { root.setLive(\"gradAngle\", v); }\n                                    onCommitted: root.saveStyle(\"Change angle\")\n                                }\n                                S.SComboRow {\n                                    label: \"Scope\"\n                                    options: [\"fullText\", \"perLine\", \"perWord\", \"perCharacter\"]\n                                    labels: [\"Whole text\", \"Each line\", \"Each word\", \"Each character\"]\n                                    value: root.sty(\"gradientScope\", \"fullText\")\n                                    onEdited: function (v) { root.setAndSave(\"gradientScope\", v, \"Change gradient scope\"); }\n                                }\n                            }\n\n                            ColumnLayout {\n                                visible: root.sty(\"fillType\", \"solid\") === \"texture\"\n                                Layout.fillWidth: true\n                                spacing: S.Theme.gap\n\n                                S.SSliderRow {\n                                    label: \"Scale X\"; from: 0.1; to: 5; stepSize: 0.05; defaultValue: 1\n                                    value: root.sty(\"texFillScaleX\", 1)\n                                    onMoved: function (v) { root.setLive(\"texFillScaleX\", v); }\n                                    onCommitted: root.saveStyle(\"Change texture scale\")\n                                }\n                                S.SSliderRow {\n                                    label: \"Scale Y\"; from: 0.1; to: 5; stepSize: 0.05; defaultValue: 1\n                                    value: root.sty(\"texFillScaleY\", 1)\n                                    onMoved: function (v) { root.setLive(\"texFillScaleY\", v); }\n                                    onCommitted: root.saveStyle(\"Change texture scale\")\n                                }\n                                S.SSliderRow {\n                                    label: \"Offset X\"; from: -1; to: 1; stepSize: 0.01\n                                    value: root.sty(\"texFillOffsetX\", 0)\n                                    onMoved: function (v) { root.setLive(\"texFillOffsetX\", v); }\n                                    onCommitted: root.saveStyle(\"Change texture offset\")\n                                }\n                                S.SSliderRow {\n                                    label: \"Offset Y\"; from: -1; to: 1; stepSize: 0.01\n                                    value: root.sty(\"texFillOffsetY\", 0)\n                                    onMoved: function (v) { root.setLive(\"texFillOffsetY\", v); }\n                                    onCommitted: root.saveStyle(\"Change texture offset\")\n                                }\n                            }\n                        }\n\n                        // ======================== STROKE ========================\n                        S.SSection {\n                            title: \"Stroke\"\n                            tip: \"Up to three stroke layers, drawn widest first.\"\n\n                            Repeater {\n                                model: [\n                                    { n: 1, en: \"strokeEnabled\",  col: \"strokeColor\",  w: \"strokeWidth\",  j: \"strokeJoin\",  max: 50,  def: 3 },\n                                    { n: 2, en: \"stroke2Enabled\", col: \"stroke2Color\", w: \"stroke2Width\", j: \"stroke2Join\", max: 80,  def: 8 },\n                                    { n: 3, en: \"stroke3Enabled\", col: \"stroke3Color\", w: \"stroke3Width\", j: \"stroke3Join\", max: 100, def: 14 }\n                                ]\n                                ColumnLayout {\n                                    id: strokeLayer\n                                    required property var modelData\n                                    Layout.fillWidth: true\n                                    spacing: S.Theme.gap\n\n                                    S.SSeparator { visible: strokeLayer.modelData.n > 1 }\n                                    S.SCheck {\n                                        text: \"Layer \" + strokeLayer.modelData.n\n                                        font.pixelSize: S.Theme.fontMd\n                                        checked: root.sty(strokeLayer.modelData.en, false)\n                                        onToggled: root.setAndSave(strokeLayer.modelData.en, checked,\n                                                                   \"Toggle stroke \" + strokeLayer.modelData.n)\n                                    }\n                                    S.SColorRow {\n                                        label: \"Colour\"; value: root.sty(strokeLayer.modelData.col, \"#000000\")\n                                        onEdited: function (v) { root.setAndSave(strokeLayer.modelData.col, v, \"Change stroke colour\"); }\n                                    }\n                                    S.SSliderRow {\n                                        label: \"Width\"; from: 0.5; to: strokeLayer.modelData.max\n                                        stepSize: 0.5; decimals: 1\n                                        defaultValue: strokeLayer.modelData.def\n                                        value: root.sty(strokeLayer.modelData.w, strokeLayer.modelData.def)\n                                        onMoved: function (v) { root.setLive(strokeLayer.modelData.w, v); }\n                                        onCommitted: root.saveStyle(\"Change stroke width\")\n                                    }\n                                    S.SComboRow {\n                                        label: \"Join\"\n                                        options: [\"round\", \"bevel\", \"miter\"]\n                                        value: root.sty(strokeLayer.modelData.j, \"round\")\n                                        onEdited: function (v) { root.setAndSave(strokeLayer.modelData.j, v, \"Change join\"); }\n                                    }\n                                }\n                            }\n                        }\n\n                        // ====================== BACKGROUND ======================\n                        S.SSection {\n                            title: \"Background\"\n\n                            S.SCheck {\n                                text: \"Enabled\"; font.pixelSize: S.Theme.fontMd\n                                checked: root.sty(\"bgEnabled\", false)\n                                onToggled: root.setAndSave(\"bgEnabled\", checked, \"Toggle background\")\n                            }\n                            S.SColorRow {\n                                label: \"Colour\"; value: root.sty(\"bgColor\", \"#000000\")\n                                onEdited: function (v) { root.setAndSave(\"bgColor\", v, \"Change background colour\"); }\n                            }\n                            S.SSliderRow {\n                                label: \"Opacity\"; from: 0; to: 1; stepSize: 0.05; defaultValue: 0.5\n                                value: root.sty(\"bgOpacity\", 0.5)\n                                onMoved: function (v) { root.setLive(\"bgOpacity\", v); }\n                                onCommitted: root.saveStyle(\"Change background opacity\")\n                            }\n                            S.SSliderRow {\n                                label: \"Pad H\"; from: 0; to: 200; stepSize: 1; decimals: 0; defaultValue: 20\n                                value: root.sty(\"bgPaddingH\", 20)\n                                onMoved: function (v) { root.setLive(\"bgPaddingH\", v); }\n                                onCommitted: root.saveStyle(\"Change padding\")\n                            }\n                            S.SSliderRow {\n                                label: \"Pad V\"; from: 0; to: 200; stepSize: 1; decimals: 0; defaultValue: 10\n                                value: root.sty(\"bgPaddingV\", 10)\n                                onMoved: function (v) { root.setLive(\"bgPaddingV\", v); }\n                                onCommitted: root.saveStyle(\"Change padding\")\n                            }\n                            S.SSliderRow {\n                                label: \"Radius\"; from: 0; to: 100; stepSize: 1; decimals: 0; defaultValue: 8\n                                value: root.sty(\"bgCornerRadius\", 8)\n                                onMoved: function (v) { root.setLive(\"bgCornerRadius\", v); }\n                                onCommitted: root.saveStyle(\"Change radius\")\n                            }\n                            S.SComboRow {\n                                label: \"Scope\"\n                                options: [\"fullText\", \"perLine\"]\n                                labels: [\"Whole text\", \"Each line\"]\n                                value: root.sty(\"bgScope\", \"fullText\")\n                                onEdited: function (v) { root.setAndSave(\"bgScope\", v, \"Change bg scope\"); }\n                            }\n                            S.SSeparator {}\n                            S.SCheck {\n                                text: \"Border\"; font.pixelSize: S.Theme.fontMd\n                                checked: root.sty(\"bgBorderEnabled\", false)\n                                onToggled: root.setAndSave(\"bgBorderEnabled\", checked, \"Toggle bg border\")\n                            }\n                            S.SColorRow {\n                                label: \"Border colour\"; value: root.sty(\"bgBorderColor\", \"#ffffff\")\n                                onEdited: function (v) { root.setAndSave(\"bgBorderColor\", v, \"Change border colour\"); }\n                            }\n                            S.SSliderRow {\n                                label: \"Border width\"; from: 0.5; to: 20; stepSize: 0.5; decimals: 1; defaultValue: 1\n                                value: root.sty(\"bgBorderWidth\", 1)\n                                onMoved: function (v) { root.setLive(\"bgBorderWidth\", v); }\n                                onCommitted: root.saveStyle(\"Change border width\")\n                            }\n                        }\n\n                        // ======================== LAYOUT ========================\n                        S.SSection {\n                            title: \"Layout\"\n\n                            S.SComboRow {\n                                label: \"H align\"\n                                options: [\"left\", \"center\", \"right\"]\n                                value: root.sty(\"hAlign\", \"center\")\n                                onEdited: function (v) { root.setAndSave(\"hAlign\", v, \"Change alignment\"); }\n                            }\n                            S.SComboRow {\n                                label: \"V align\"\n                                options: [\"top\", \"center\", \"bottom\"]\n                                value: root.sty(\"vAlign\", \"center\")\n                                onEdited: function (v) { root.setAndSave(\"vAlign\", v, \"Change alignment\"); }\n                            }\n                            S.SCheck {\n                                text: \"Word wrap\"; font.pixelSize: S.Theme.fontMd\n                                checked: root.sty(\"wordWrap\", false)\n                                onToggled: root.setAndSave(\"wordWrap\", checked, \"Toggle word wrap\")\n                            }\n                            S.SSliderRow {\n                                visible: root.sty(\"wordWrap\", false)\n                                label: \"Box width\"; from: 0.1; to: 1; stepSize: 0.01; defaultValue: 0.8\n                                value: root.sty(\"boundingBoxW\", 0.8)\n                                onMoved: function (v) { root.setLive(\"boundingBoxW\", v); }\n                                onCommitted: root.saveStyle(\"Change box width\")\n                            }\n                            S.SSliderRow {\n                                label: \"Line spacing\"; from: 0.5; to: 4; stepSize: 0.05; defaultValue: 1.3\n                                value: root.sty(\"lineSpacing\", 1.3)\n                                onMoved: function (v) { root.setLive(\"lineSpacing\", v); }\n                                onCommitted: root.saveStyle(\"Change line spacing\")\n                            }\n                            S.SSliderRow {\n                                label: \"Tracking\"; from: -20; to: 50; stepSize: 0.5; decimals: 1\n                                value: root.sty(\"tracking\", 0)\n                                onMoved: function (v) { root.setLive(\"tracking\", v); }\n                                onCommitted: root.saveStyle(\"Change tracking\")\n                            }\n                        }\n\n                        // ====================== TRANSFORM =======================\n                        S.SSection {\n                            title: \"Transform\"\n\n                            S.SSliderRow {\n                                label: \"Pos X\"; from: 0; to: 1; stepSize: 0.005; decimals: 3; defaultValue: 0.5\n                                value: root.sty(\"posX\", 0.5)\n                                onMoved: function (v) { root.setLive(\"posX\", v); }\n                                onCommitted: root.saveStyle(\"Change position\")\n                            }\n                            S.SSliderRow {\n                                label: \"Pos Y\"; from: 0; to: 1; stepSize: 0.005; decimals: 3; defaultValue: 0.5\n                                value: root.sty(\"posY\", 0.5)\n                                onMoved: function (v) { root.setLive(\"posY\", v); }\n                                onCommitted: root.saveStyle(\"Change position\")\n                            }\n                            S.SSliderRow {\n                                label: \"Rotation\"; from: -360; to: 360; stepSize: 1; decimals: 0; suffix: \"°\"\n                                value: root.sty(\"rotation\", 0)\n                                onMoved: function (v) { root.setLive(\"rotation\", v); }\n                                onCommitted: root.saveStyle(\"Change rotation\")\n                            }\n                            S.SSliderRow {\n                                label: \"Scale X\"; from: 0.1; to: 5; stepSize: 0.05; defaultValue: 1\n                                value: root.sty(\"scaleX\", 1)\n                                onMoved: function (v) { root.setLive(\"scaleX\", v); }\n                                onCommitted: root.saveStyle(\"Change scale\")\n                            }\n                            S.SSliderRow {\n                                label: \"Scale Y\"; from: 0.1; to: 5; stepSize: 0.05; defaultValue: 1\n                                value: root.sty(\"scaleY\", 1)\n                                onMoved: function (v) { root.setLive(\"scaleY\", v); }\n                                onCommitted: root.saveStyle(\"Change scale\")\n                            }\n                            S.SSliderRow {\n                                label: \"Skew X\"; from: -60; to: 60; stepSize: 1; decimals: 0; suffix: \"°\"\n                                value: root.sty(\"skewX\", 0)\n                                onMoved: function (v) { root.setLive(\"skewX\", v); }\n                                onCommitted: root.saveStyle(\"Change skew\")\n                            }\n                            S.SSliderRow {\n                                label: \"Skew Y\"; from: -60; to: 60; stepSize: 1; decimals: 0; suffix: \"°\"\n                                value: root.sty(\"skewY\", 0)\n                                onMoved: function (v) { root.setLive(\"skewY\", v); }\n                                onCommitted: root.saveStyle(\"Change skew\")\n                            }\n                            S.SComboRow {\n                                label: \"Anchor H\"\n                                options: [\"left\", \"center\", \"right\"]\n                                value: root.sty(\"anchorH\", \"center\")\n                                onEdited: function (v) { root.setAndSave(\"anchorH\", v, \"Change anchor\"); }\n                            }\n                            S.SComboRow {\n                                label: \"Anchor V\"\n                                options: [\"top\", \"center\", \"bottom\"]\n                                value: root.sty(\"anchorV\", \"center\")\n                                onEdited: function (v) { root.setAndSave(\"anchorV\", v, \"Change anchor\"); }\n                            }\n                            S.SSliderRow {\n                                label: \"Opacity\"; from: 0; to: 1; stepSize: 0.01; defaultValue: 1\n                                value: root.sty(\"opacity\", 1)\n                                onMoved: function (v) { root.setLive(\"opacity\", v); }\n                                onCommitted: root.saveStyle(\"Change opacity\")\n                            }\n                        }\n\n                        // ======================= WRITE-ON =======================\n                        S.SSection {\n                            title: \"Write-on\"\n                            expanded: false\n\n                            S.SComboRow {\n                                label: \"Mode\"\n                                options: [\"typewriter\", \"fade\", \"scale\", \"slide\", \"scramble\"]\n                                value: root.sty(\"writeOnMode\", \"typewriter\")\n                                onEdited: function (v) { root.setAndSave(\"writeOnMode\", v, \"Change write-on mode\"); }\n                            }\n                            S.SComboRow {\n                                label: \"Direction\"\n                                options: [\"forward\", \"reverse\", \"fromCenter\", \"random\"]\n                                labels: [\"Forward\", \"Reverse\", \"From center\", \"Random\"]\n                                value: root.sty(\"writeOnDirection\", \"forward\")\n                                onEdited: function (v) { root.setAndSave(\"writeOnDirection\", v, \"Change write-on direction\"); }\n                            }\n                            S.SComboRow {\n                                label: \"Scope\"\n                                options: [\"character\", \"word\", \"line\"]\n                                value: root.sty(\"writeOnScope\", \"character\")\n                                onEdited: function (v) { root.setAndSave(\"writeOnScope\", v, \"Change write-on scope\"); }\n                            }\n                            S.SSliderRow {\n                                label: \"Overlap\"; from: 1; to: 50; stepSize: 1; decimals: 0; defaultValue: 5\n                                value: root.sty(\"writeOnOverlap\", 5)\n                                onMoved: function (v) { root.setLive(\"writeOnOverlap\", v); }\n                                onCommitted: root.saveStyle(\"Change overlap\")\n                            }\n                            RowLayout {\n                                Layout.fillWidth: true\n                                spacing: S.Theme.gapLg\n                                S.SCheck {\n                                    text: \"Cursor\"; font.pixelSize: S.Theme.fontMd\n                                    checked: root.sty(\"writeOnCursor\", false)\n                                    onToggled: root.setAndSave(\"writeOnCursor\", checked, \"Toggle cursor\")\n                                }\n                                S.STextField {\n                                    Layout.preferredWidth: 44\n                                    horizontalAlignment: Text.AlignHCenter\n                                    text: root.sty(\"writeOnCursorChar\", \"▌\")\n                                    onEditingFinished: root.setAndSave(\"writeOnCursorChar\", text, \"Change cursor char\")\n                                }\n                                Item { Layout.fillWidth: true }\n                            }\n                        }\n\n                        // ====================== SCROLLING =======================\n                        S.SSection {\n                            title: \"Scrolling\"\n                            expanded: false\n\n                            S.SComboRow {\n                                label: \"Mode\"\n                                options: [\"none\", \"up\", \"down\", \"left\", \"right\"]\n                                value: root.sty(\"scrollMode\", \"none\")\n                                onEdited: function (v) { root.setAndSave(\"scrollMode\", v, \"Change scroll mode\"); }\n                            }\n                            S.SSliderRow {\n                                label: \"Speed\"; from: 1; to: 1000; stepSize: 1; decimals: 0; suffix: \" px/s\"\n                                defaultValue: 50\n                                value: root.sty(\"scrollSpeed\", 50)\n                                onMoved: function (v) { root.setLive(\"scrollSpeed\", v); }\n                                onCommitted: root.saveStyle(\"Change scroll speed\")\n                            }\n                            S.SCheck {\n                                text: \"Loop\"; font.pixelSize: S.Theme.fontMd\n                                checked: root.sty(\"scrollLoop\", true) !== false\n                                onToggled: root.setAndSave(\"scrollLoop\", checked, \"Toggle scroll loop\")\n                            }\n                            S.SSliderRow {\n                                label: \"Fade edge\"; from: 0; to: 0.5; stepSize: 0.01; defaultValue: 0.1\n                                value: root.sty(\"scrollFadeEdges\", 0.1)\n                                onMoved: function (v) { root.setLive(\"scrollFadeEdges\", v); }\n                                onCommitted: root.saveStyle(\"Change fade\")\n                            }\n                        }\n\n                        // ======================== WAVE ==========================\n                        S.SSection {\n                            title: \"Wave animation\"\n                            expanded: false\n\n                            S.SCheck {\n                                text: \"Enabled\"; font.pixelSize: S.Theme.fontMd\n                                checked: root.sty(\"charWaveEnabled\", false)\n                                onToggled: root.setAndSave(\"charWaveEnabled\", checked, \"Toggle wave\")\n                            }\n                            S.SSliderRow {\n                                label: \"Amplitude\"; from: 1; to: 100; stepSize: 1; decimals: 0; defaultValue: 10\n                                value: root.sty(\"charWaveAmplitude\", 10)\n                                onMoved: function (v) { root.setLive(\"charWaveAmplitude\", v); }\n                                onCommitted: root.saveStyle(\"Change amplitude\")\n                            }\n                            S.SSliderRow {\n                                label: \"Frequency\"; from: 0.1; to: 20; stepSize: 0.1; decimals: 1; suffix: \" Hz\"\n                                defaultValue: 2\n                                value: root.sty(\"charWaveFrequency\", 2)\n                                onMoved: function (v) { root.setLive(\"charWaveFrequency\", v); }\n                                onCommitted: root.saveStyle(\"Change frequency\")\n                            }\n                            S.SComboRow {\n                                label: \"Property\"\n                                options: [\"posY\", \"posX\", \"rotation\", \"scale\", \"opacity\"]\n                                labels: [\"Position Y\", \"Position X\", \"Rotation\", \"Scale\", \"Opacity\"]\n                                value: root.sty(\"charWaveProperty\", \"posY\")\n                                onEdited: function (v) { root.setAndSave(\"charWaveProperty\", v, \"Change wave property\"); }\n                            }\n                        }\n\n                        // =================== CHAR ANIMATION =====================\n                        S.SSection {\n                            title: \"Char animation\"\n                            expanded: false\n                            tip: \"Animate characters, words or lines in sequence.\"\n\n                            RowLayout {\n                                Layout.fillWidth: true\n                                spacing: S.Theme.gapLg\n                                S.SCheck {\n                                    text: \"Enabled\"; font.pixelSize: S.Theme.fontMd\n                                    checked: root.sty(\"charAnimEnabled\", false)\n                                    onToggled: root.setAndSave(\"charAnimEnabled\", checked, \"Toggle char anim\")\n                                }\n                                Item { Layout.fillWidth: true }\n                                S.SButton {\n                                    text: \"Retrigger\"\n                                    implicitHeight: S.Theme.rowH\n                                    onClicked: root.executionSend({ type: \"retrigger\" })\n                                }\n                            }\n                            S.SComboRow {\n                                label: \"Scope\"\n                                options: [\"character\", \"word\", \"line\"]\n                                value: root.sty(\"charAnimScope\", \"character\")\n                                onEdited: function (v) { root.setAndSave(\"charAnimScope\", v, \"Change anim scope\"); }\n                            }\n                            S.SComboRow {\n                                label: \"Order\"\n                                options: [\"forward\", \"reverse\", \"random\", \"fromCenter\", \"toCenter\"]\n                                labels: [\"Forward\", \"Reverse\", \"Random\", \"From center\", \"To center\"]\n                                value: root.sty(\"charAnimOrder\", \"forward\")\n                                onEdited: function (v) { root.setAndSave(\"charAnimOrder\", v, \"Change anim order\"); }\n                            }\n                            S.SSliderRow {\n                                label: \"Delay\"; from: 0.01; to: 2; stepSize: 0.01; suffix: \" s\"; defaultValue: 0.1\n                                value: root.sty(\"charAnimDelay\", 0.1)\n                                onMoved: function (v) { root.setLive(\"charAnimDelay\", v); }\n                                onCommitted: root.saveStyle(\"Change delay\")\n                            }\n                            S.SSliderRow {\n                                label: \"Seed\"; from: 1; to: 999; stepSize: 1; decimals: 0; defaultValue: 42\n                                value: root.sty(\"charAnimRandomSeed\", 42)\n                                onMoved: function (v) { root.setLive(\"charAnimRandomSeed\", v); }\n                                onCommitted: root.saveStyle(\"Change seed\")\n                            }\n\n                            S.SSeparator {}\n                            S.SSectionLabel { text: \"Offsets\" }\n\n                            S.SSliderRow {\n                                label: \"Pos X\"; from: -500; to: 500; stepSize: 1; decimals: 0\n                                value: root.sty(\"charAnimPosX\", 0)\n                                onMoved: function (v) { root.setLive(\"charAnimPosX\", v); }\n                                onCommitted: root.saveStyle(\"Change offset\")\n                            }\n                            S.SSliderRow {\n                                label: \"Pos Y\"; from: -500; to: 500; stepSize: 1; decimals: 0\n                                value: root.sty(\"charAnimPosY\", 0)\n                                onMoved: function (v) { root.setLive(\"charAnimPosY\", v); }\n                                onCommitted: root.saveStyle(\"Change offset\")\n                            }\n                            S.SSliderRow {\n                                label: \"Rotation\"; from: -360; to: 360; stepSize: 1; decimals: 0; suffix: \"°\"\n                                value: root.sty(\"charAnimRotation\", 0)\n                                onMoved: function (v) { root.setLive(\"charAnimRotation\", v); }\n                                onCommitted: root.saveStyle(\"Change rotation\")\n                            }\n                            S.SSliderRow {\n                                label: \"Scale\"; from: 0; to: 5; stepSize: 0.05; defaultValue: 1\n                                value: root.sty(\"charAnimScale\", 1)\n                                onMoved: function (v) { root.setLive(\"charAnimScale\", v); }\n                                onCommitted: root.saveStyle(\"Change scale\")\n                            }\n                            S.SSliderRow {\n                                label: \"Opacity\"; from: 0; to: 1; stepSize: 0.01; defaultValue: 1\n                                value: root.sty(\"charAnimOpacity\", 1)\n                                onMoved: function (v) { root.setLive(\"charAnimOpacity\", v); }\n                                onCommitted: root.saveStyle(\"Change opacity\")\n                            }\n                            S.SColorRow {\n                                label: \"Fill colour\"; value: root.sty(\"charAnimFillColor\", \"#ffffff\")\n                                onEdited: function (v) { root.setAndSave(\"charAnimFillColor\", v, \"Change anim colour\"); }\n                            }\n                            S.SSliderRow {\n                                label: \"Char offset\"; from: -50; to: 50; stepSize: 1; decimals: 0\n                                value: root.sty(\"charAnimCharOffset\", 0)\n                                onMoved: function (v) { root.setLive(\"charAnimCharOffset\", v); }\n                                onCommitted: root.saveStyle(\"Change char offset\")\n                            }\n                        }\n\n                        Item { Layout.fillHeight: true; Layout.minimumHeight: S.Theme.pad }\n                    }\n                }\n            }\n\n            S.SStatusBar {\n                visible: detail.length > 0\n                detail: root.renderWidth > 0\n                        ? (Math.round(root.renderWidth) + \"×\" + Math.round(root.renderHeight))\n                        : \"\"\n            }\n        }\n    }\n}\n","State":[["textState",{"String":"{\"text\":\"\",\"textTransform\":\"none\",\"fontFamily\":\"Monospace\",\"fontSize\":32,\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"underline\":false,\"strikethrough\":false,\"fillEnabled\":true,\"fillType\":\"solid\",\"fillColor\":\"#ffffff\",\"gradStartColor\":\"#ffffff\",\"gradEndColor\":\"#4466ff\",\"gradAngle\":90,\"strokeEnabled\":false,\"strokeColor\":\"#000000\",\"strokeWidth\":3,\"strokeJoin\":\"round\",\"shadowEnabled\":false,\"shadowColor\":\"#000000\",\"shadowOffsetX\":4,\"shadowOffsetY\":4,\"shadowBlur\":10,\"shadowOpacity\":0.7,\"bgEnabled\":false,\"bgColor\":\"#000000\",\"bgOpacity\":0.5,\"bgPaddingH\":20,\"bgPaddingV\":10,\"bgCornerRadius\":8,\"hAlign\":\"center\",\"vAlign\":\"center\",\"wordWrap\":true,\"boundingBoxW\":0.8,\"boundingBoxH\":0,\"lineSpacing\":1.3,\"tracking\":0,\"posX\":0.485,\"posY\":0.275,\"rotation\":0,\"scaleX\":1,\"scaleY\":1,\"skewX\":0,\"skewY\":0,\"anchorH\":\"center\",\"anchorV\":\"top\",\"opacity\":1,\"writeOnMode\":\"typewriter\",\"writeOnDirection\":\"forward\",\"writeOnScope\":\"character\",\"writeOnOverlap\":5,\"writeOnCursor\":true,\"writeOnCursorChar\":\"▌\",\"writeOnScrambleIter\":5,\"scrollMode\":\"none\",\"scrollSpeed\":50,\"scrollLoop\":true,\"scrollFadeEdges\":0.1,\"charWaveEnabled\":false,\"charWaveAmplitude\":10,\"charWaveFrequency\":2,\"charWaveProperty\":\"posY\",\"stroke2Enabled\":false,\"stroke2Color\":\"#333333\",\"stroke2Width\":8,\"stroke2Join\":\"round\",\"stroke3Enabled\":false,\"stroke3Color\":\"#666666\",\"stroke3Width\":14,\"stroke3Join\":\"round\",\"shadow2Enabled\":false,\"shadow2Color\":\"#000000\",\"shadow2OffsetX\":8,\"shadow2OffsetY\":8,\"shadow2Blur\":20,\"shadow2Opacity\":0.5,\"charAnimEnabled\":false,\"charAnimScope\":\"character\",\"charAnimOrder\":\"forward\",\"charAnimDelay\":0.1,\"charAnimRandomSeed\":42,\"charAnimPosX\":0,\"charAnimPosY\":50,\"charAnimRotation\":0,\"charAnimScale\":0.5,\"charAnimOpacity\":0,\"charAnimFillColor\":\"\",\"charAnimCharOffset\":0,\"texFillScaleX\":1,\"texFillScaleY\":1,\"texFillOffsetX\":0,\"texFillOffsetY\":0,\"gradientScope\":\"fullText\",\"bgScope\":\"fullText\",\"bgBorderEnabled\":false,\"bgBorderColor\":\"#ffffff\",\"bgBorderWidth\":1}"}]],"Root":"<LIBRARY>:packages/default/Presets/Javascript/advanced-text/advanced-text.qml","Inlets":[{"uuid":"5ac86198-2d03-4830-9e41-a6d529922d29","ObjectName":"Inlet","id":0,"Hidden":false,"Custom":"Fill Texture","Exposed":"fill texture","Format":0,"Filter":2,"AddressMode":1},{"uuid":"769dd38a-bfb3-4dc6-b52a-b6abb7afe2a3","ObjectName":"Inlet","id":1,"Hidden":false,"Custom":"Text","Exposed":"text","Address":"LLM:/chat/response"},{"uuid":"769dd38a-bfb3-4dc6-b52a-b6abb7afe2a3","ObjectName":"Inlet","id":2,"Hidden":false,"Custom":"Style","Exposed":"style"},{"uuid":"af2b4fc3-aecb-4c15-a5aa-1c573a239925","ObjectName":"Inlet","id":3,"Hidden":true,"Custom":"Opacity","Exposed":"opacity","Value":{"Float":1.0},"Init":{"Float":1.0},"Domain":{"Float":{"Min":0.0,"Max":1.0}}},{"uuid":"af2b4fc3-aecb-4c15-a5aa-1c573a239925","ObjectName":"Inlet","id":4,"Hidden":true,"Custom":"Write On","Exposed":"write on","Value":{"Float":0.8530502319335938},"Init":{"Float":1.0},"Domain":{"Float":{"Min":0.0,"Max":1.0}}},{"uuid":"8f38638e-9f9f-48b0-ae36-1cba86ef5703","ObjectName":"Inlet","id":5,"Hidden":true,"Custom":"Color","Exposed":"color","Value":{"Vec4f":[0.009277485311031342,1.0,0.08606088161468506,1.0]},"Init":{"Vec4f":[0.0,0.0,0.0,1.0]},"Domain":{}}],"Outlets":[{"uuid":"f1c71046-b754-49a5-8e66-d01374773dfc","ObjectName":"Outlet","id":0,"Hidden":false,"Custom":"Output","Exposed":"output"}]},{"uuid":"846a5de5-47f9-46c5-a898-013cb20951d0","ObjectName":"Javascript","id":2,"Metadata":{"ScriptingName":"Question","Comment":"","Color":"Transparent1","Label":"","Touched":true},"Duration":10584000000,"Height":300.0,"StartOffset":0,"LoopDuration":10584000000,"Pos":[-108.49089454860824,-250.89099999999996],"Size":[278.0,135.0],"Loops":false,"FoldMode":0,"ProcessUIPlacement":"Side panel","Script":"import Score\nimport QtQuick\nimport \"TextRender.js\" as TextRender\nimport OssiaUI as S\n\nScript {\n    id: root\n\n    TextureInlet {\n        id: texFill\n        objectName: \"Fill Texture\"\n    }\n\n    ValueInlet {\n        id: textContent\n        objectName: \"Text\"\n    }\n    ValueInlet {\n        id: textStyleIn\n        objectName: \"Style\"\n    }\n    FloatSlider {\n        id: masterOpacity\n        objectName: \"Opacity\"\n        min: 0; max: 1; init: 1\n    }\n    FloatSlider {\n        id: writeOn\n        objectName: \"Write On\"\n        min: 0; max: 1; init: 1\n    }\n    HSVSlider {\n        id: colorOverride\n        objectName: \"Color\"\n    }\n\n    TextureOutlet {\n        objectName: \"Output\"\n        item: Item {\n            id: outputRoot\n            anchors.fill: parent\n\n            // Hidden container: parents the texture inlet item into the scene\n            // graph so ShaderEffectSource can render from it.\n            Item {\n                id: texFillContainer\n                anchors.fill: parent\n                visible: false\n\n                Component.onCompleted: {\n                    if (texFill.item) {\n                        texFill.item.parent = texFillContainer;\n                        texFill.item.width = Qt.binding(function() { return texFillContainer.width; });\n                        texFill.item.height = Qt.binding(function() { return texFillContainer.height; });\n                    }\n                }\n            }\n\n            // Bridge: ShaderEffectSource mirrors the inlet's texture as a\n            // proper QML item (with engine), so grabToImage() works on it.\n            ShaderEffectSource {\n                id: texBridge\n                anchors.fill: parent\n                sourceItem: texFill.item\n                visible: false\n                live: true\n            }\n\n            Canvas {\n                id: textCanvas\n                anchors.fill: parent\n                renderStrategy: Canvas.Cooperative\n\n                property int ver: root.stateVersion\n                onVerChanged: requestPaint()\n                property bool editing: inlineText.editing\n                onEditingChanged: requestPaint()\n                onImageLoaded: requestPaint()\n\n                onPaint: {\n                    var ctx = getContext(\"2d\");\n                    // Keep the Canvas and its backing texture alive while the\n                    // editor replaces the text; hiding it loses output resources.\n                    if (editing) {\n                        ctx.clearRect(0, 0, width, height);\n                        return;\n                    }\n                    // For texture fill, pass the grabbed image URL (Canvas-drawable)\n                    // instead of the QQuickRhiItem (which Canvas can't draw).\n                    var texItem = null;\n                    if (root.textState.fillType === \"texture\"\n                        && root.texGrabUrl\n                        && isImageLoaded(root.texGrabUrl))\n                    {\n                        texItem = root.texGrabUrl;\n                    }\n                    TextRender.paintText(ctx, width, height,\n                        root.textState, root.getInletValues(), texItem);\n                }\n            }\n\n            MouseArea {\n                anchors.fill: parent\n                acceptedButtons: Qt.LeftButton\n                onPressed: function(mouse) {\n                    if (inlineText.editing) inlineText.finish(true);\n                    if (!root.canEditText()) mouse.accepted = false;\n                }\n                onDoubleClicked: function(mouse) {\n                    if (!root.canEditText() || !textCanvas.available) return;\n                    var g = TextRender.editingGeometry(textCanvas.getContext(\"2d\"),\n                        outputRoot.width, outputRoot.height, root.textState, root.getInletValues());\n                    var det = g.a * g.d - g.b * g.c;\n                    if (Math.abs(det) < 0.000001) return;\n                    var dx = mouse.x - g.x, dy = mouse.y - g.y;\n                    var x = (g.d * dx - g.c * dy) / det;\n                    var y = (-g.b * dx + g.a * dy) / det;\n                    if (x < 0 || y < 0 || x > g.width || y > g.height) return;\n                    inlineText.geometry = g;\n                    inlineText.editStyle = TextRender.mergeState({}, root.textState);\n                    inlineText.begin(String(root.textState.text || \"\"));\n                }\n            }\n\n            S.SInlineTextEditor {\n                id: inlineText\n                // Hold the text block still while typing; the animation clock keeps running.\n                property var geometry: ({a: 1, b: 0, c: 0, d: 1, x: 0, y: 0, width: 1, height: 1})\n                property var editStyle: ({})\n                width: geometry.width\n                height: Math.max(geometry.height, contentHeight)\n                padding: 0\n                wrapMode: editStyle.wordWrap ? TextEdit.WordWrap : TextEdit.NoWrap\n                horizontalAlignment: editStyle.hAlign === \"left\" ? TextEdit.AlignLeft\n                    : (editStyle.hAlign === \"right\" ? TextEdit.AlignRight : TextEdit.AlignHCenter)\n                font.family: editStyle.fontFamily || \"Arial\"\n                font.pixelSize: editStyle.fontSize || 72\n                font.bold: editStyle.fontWeight === \"bold\"\n                font.italic: editStyle.fontStyle === \"italic\"\n                font.underline: !!editStyle.underline\n                font.strikeout: !!editStyle.strikethrough\n                font.letterSpacing: editStyle.tracking || 0\n                transform: Matrix4x4 {\n                    matrix: Qt.matrix4x4(inlineText.geometry.a, inlineText.geometry.c, 0, inlineText.geometry.x,\n                        inlineText.geometry.b, inlineText.geometry.d, 0, inlineText.geometry.y,\n                        0, 0, 1, 0, 0, 0, 0, 1)\n                }\n                onEdited: function(value) {\n                    root.textState.text = value;\n                    root.stateVersion++;\n                }\n                onFinished: function(value, accepted) {\n                    if (accepted && root.canEditText() && value !== String(editStyle.text || \"\")) {\n                        root.storedTextState.text = value;\n                        var serialized = JSON.stringify(root.storedTextState);\n                        root.pendingCommits.push(serialized);\n                        root.commitState(\"textState\", serialized);\n                        root.uiSend({type: \"textState\", style: serialized});\n                    }\n                    textCanvas.requestPaint();\n                }\n            }\n        }\n    }\n\n    property var textState: TextRender.defaultState()\n    property var storedTextState: TextRender.defaultState()\n    property var pendingCommits: []\n    property int stateVersion: 0\n    property real lastSentW: 0\n    property real lastSentH: 0\n    property real prevOpacity: -1\n    property real prevWriteOn: -1\n    property string prevText: \"\"\n    property real elapsed: 0\n    property real lastTickMs: 0\n    property bool needsContinuousRepaint: false\n\n    // Texture grab state for Canvas-based texture fill\n    property var texGrabResult: null\n    property string texGrabUrl: \"\"\n    property bool texGrabPending: false\n\n    function styleOverrides() {\n        try {\n            var value = textStyleIn.value;\n            var style = typeof value === \"string\" ? JSON.parse(value) : value;\n            return style && typeof style === \"object\" ? style : null;\n        } catch (error) { return null; }\n    }\n\n    function canEditText() {\n        // Keep the renderer's existing nonempty Text inlet precedence. A Style\n        // inlet that supplies text owns that value even if it is an empty string.\n        var value = textContent.value;\n        if (root.prevText || (value !== undefined && value !== null && String(value))) return false;\n        var style = root.styleOverrides();\n        return !style || !Object.prototype.hasOwnProperty.call(style, \"text\");\n    }\n\n    function applyStoredStyle(style) {\n        inlineText.finish(false);\n        root.storedTextState = TextRender.mergeState(TextRender.defaultState(), style);\n        root.textState = TextRender.mergeState(root.storedTextState, root.styleOverrides());\n        root.stateVersion++;\n    }\n\n    function getInletValues() {\n        var vals = {\n            opacity: masterOpacity.value,\n            writeOn: writeOn.value,\n            elapsed: root.elapsed\n        };\n        if (root.prevText)\n            vals.text = root.prevText;\n        if (colorOverride.value && colorOverride.value.w > 0)\n            vals.colorOverride = colorOverride.value;\n        return vals;\n    }\n\n    loadState: function(state) {\n        var s = null;\n        try {\n            if (state && state.textState)\n                s = typeof state.textState === \"string\" ? JSON.parse(state.textState) : state.textState;\n        } catch (error) {}\n        root.applyStoredStyle(s);\n    }\n\n    stateUpdated: function(k, v) {\n        if (k === \"textState\") {\n            var pending = root.pendingCommits.indexOf(v);\n            if (pending >= 0) { root.pendingCommits.splice(pending, 1); return; }\n            try {\n                var s = typeof v === \"string\" ? JSON.parse(v) : v;\n                root.applyStoredStyle(s);\n            } catch(e) { return; }\n        }\n    }\n\n    uiEvent: function(message) {\n        if (!message) return;\n        if (message.type === \"updateStyle\") {\n            root.applyStoredStyle(message.style || {});\n        } else if (message.type === \"retrigger\") {\n            root.elapsed = 0;\n            root.lastTickMs = Date.now();\n            root.stateVersion++;\n        }\n    }\n\n    tick: function(token, state) {\n        var changed = false;\n        if (inlineText.editing && !root.canEditText()) inlineText.finish(false);\n\n        // Accumulate elapsed time for animations\n        var now = Date.now();\n        if (root.lastTickMs > 0) {\n            root.elapsed += (now - root.lastTickMs) / 1000;\n        }\n        root.lastTickMs = now;\n\n        // Check if continuous repaint is needed (scroll or wave active)\n        var ts = root.textState;\n        root.needsContinuousRepaint =\n            (ts.scrollMode && ts.scrollMode !== \"none\")\n            || ts.charWaveEnabled\n            || ts.charAnimEnabled\n            || (writeOn.value < 1 && ts.writeOnMode === \"scramble\")\n            || ts.fillType === \"texture\";\n\n        if (root.needsContinuousRepaint) changed = true;\n\n        if (textContent.value !== undefined && textContent.value !== null) {\n            var t = String(textContent.value);\n            if (t !== root.prevText) {\n                root.prevText = t;\n                changed = true;\n            }\n        }\n        if (masterOpacity.value !== root.prevOpacity) {\n            root.prevOpacity = masterOpacity.value;\n            changed = true;\n        }\n        if (writeOn.value !== root.prevWriteOn) {\n            root.prevWriteOn = writeOn.value;\n            changed = true;\n        }\n        if (textStyleIn.value !== undefined && textStyleIn.value !== null) {\n            try {\n                var so = typeof textStyleIn.value === \"string\"\n                    ? JSON.parse(textStyleIn.value) : textStyleIn.value;\n                root.textState = TextRender.mergeState(root.textState, so);\n                changed = true;\n            } catch(e) {}\n        }\n\n        // Grab texture inlet content for Canvas use.\n        // Canvas drawImage() can't render QQuickRhiItem directly,\n        // so we snapshot it via a ShaderEffectSource bridge (which is a\n        // proper QML item with an engine) to an image URL Canvas can draw.\n        if (root.textState.fillType === \"texture\" && texFill.item && !root.texGrabPending) {\n            root.texGrabPending = true;\n            var oldUrl = root.texGrabUrl;\n            texBridge.grabToImage(function(result) {\n                if (oldUrl) textCanvas.unloadImage(oldUrl);\n                root.texGrabResult = result; // prevent GC\n                root.texGrabUrl = result.url;\n                root.texGrabPending = false;\n                textCanvas.loadImage(result.url);\n            });\n        }\n\n        if (changed) root.stateVersion++;\n\n        var cw = textCanvas.width, ch = textCanvas.height;\n        if (cw !== root.lastSentW || ch !== root.lastSentH) {\n            root.lastSentW = cw;\n            root.lastSentH = ch;\n            uiSend({ type: \"renderSize\", width: cw, height: ch });\n        }\n    }\n\n    start: function() {\n        root.elapsed = 0;\n        root.lastTickMs = 0;\n    }\n}\n","Ui":"import Score as Score\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport QtQuick.Dialogs\nimport \"TextRender.js\" as TextRender\nimport OssiaUI as S\n\n// Advanced Text — style editor.\n//\n// One scrolling column of collapsible sections, each a thin binding onto a key\n// of `style`. Widgets come from the shared OssiaUI kit, so this editor, the mapper and the\n// tracking-zones editor share one look; nothing here styles a control itself.\nScore.ScriptUI {\n    id: root\n    anchors.fill: parent\n    implicitWidth: 380\n    implicitHeight: 780\n\n    property var style: TextRender.defaultState()\n    property int stateVersion: 0\n    property real renderWidth: 0\n    property real renderHeight: 0\n    property bool applyingState: false\n\n    // ---------------- state plumbing ----------------\n\n    // Read a style key. Touching stateVersion inside makes every binding that\n    // calls this re-evaluate when the document changes (undo, preset load).\n    function sty(key, fallback) {\n        root.stateVersion;\n        var v = root.style[key];\n        return (v === undefined || v === null) ? fallback : v;\n    }\n\n    function sendLive() {\n        root.executionSend({ type: \"updateStyle\", style: root.style });\n    }\n    function saveStyle(action) {\n        root.beginUpdateState(action || \"Edit text\");\n        root.updateState(\"textState\", JSON.stringify(root.style));\n        root.endUpdateState();\n    }\n    // live preview only: no stateVersion bump, so a drag does not fight the\n    // binding that feeds the widget\n    function setLive(key, val) {\n        root.style[key] = val;\n        sendLive();\n    }\n    function setAndSave(key, val, action) {\n        root.style[key] = val;\n        root.stateVersion++;\n        sendLive();\n        saveStyle(action);\n    }\n\n    function applyStyle(style) {\n        root.applyingState = true;\n        textArea.dirty = false;\n        root.style = TextRender.mergeState(TextRender.defaultState(), style);\n        root.stateVersion++;\n        root.applyingState = false;\n    }\n\n    loadState: function(state) {\n        if (state && state.textState) {\n            try {\n                var s = typeof state.textState === \"string\"\n                    ? JSON.parse(state.textState) : state.textState;\n                root.applyStyle(s);\n            } catch(e) {\n                root.applyStyle(null);\n            }\n        }\n        if (!state || !state.textState) root.applyStyle(null);\n    }\n    stateUpdated: function(k, v) {\n        if (k === \"textState\") {\n            try {\n                var s = typeof v === \"string\" ? JSON.parse(v) : v;\n                root.applyStyle(s);\n            } catch(e) { return; }\n        }\n    }\n    executionEvent: function(message) {\n        if (message && message.type === \"renderSize\") {\n            root.renderWidth = message.width;\n            root.renderHeight = message.height;\n        }\n        else if (message && message.type === \"textState\") {\n            try {\n                root.applyStyle(typeof message.style === \"string\"\n                    ? JSON.parse(message.style) : message.style);\n            } catch (error) {}\n        }\n    }\n\n    // scripted UI actions, for the test harness and the Command inlet\n    function uiAction(m) {\n        switch (m.action) {\n        case \"grab\": root.grabToImage(function (res) { res.saveToFile(m.path); }); break;\n        case \"set\": root.setAndSave(m.key, m.value, \"Set \" + m.key); break;\n        case \"section\": {\n            for (var i = 0; i < sections.children.length; i++) {\n                var c = sections.children[i];\n                if (c.title === m.name) c.expanded = !!m.value;\n            }\n            break;\n        }\n        case \"dumpstate\":\n            Util.writeFile(m.path, JSON.stringify({\n                style: root.style,\n                renderWidth: root.renderWidth,\n                renderHeight: root.renderHeight\n            }, null, 1));\n            break;\n        }\n    }\n\n    // ---------------- layout ----------------\n\n    S.ThemedPage {\n        anchors.fill: parent\n\n        ColumnLayout {\n            anchors.fill: parent\n            anchors.margins: S.Theme.gap\n            spacing: S.Theme.gap\n\n            S.SPanel {\n                Layout.fillWidth: true\n                Layout.fillHeight: true\n\n                ScrollView {\n                    id: sv\n                    anchors.fill: parent\n                    anchors.margins: S.Theme.pad\n                    clip: true\n                    ScrollBar.horizontal.policy: ScrollBar.AlwaysOff\n                    contentWidth: availableWidth\n\n                    ColumnLayout {\n                        id: sections\n                        width: sv.availableWidth\n                        spacing: S.Theme.gapSm\n\n                        // ======================== CONTENT ========================\n                        S.SSection {\n                            title: \"Content\"\n\n                            ScrollView {\n                                Layout.fillWidth: true\n                                Layout.preferredHeight: 74\n                                clip: true\n\n                                TextArea {\n                                    id: textArea\n                                    property bool dirty: false\n                                    text: root.sty(\"text\", \"\")\n                                    wrapMode: TextEdit.Wrap\n                                    font.pixelSize: S.Theme.fontLg\n                                    color: S.Theme.text\n                                    placeholderText: \"Enter text\"\n                                    placeholderTextColor: S.Theme.textMuted\n                                    selectByMouse: true\n                                    selectionColor: S.Theme.accentFill\n                                    selectedTextColor: S.Theme.accentText\n                                    padding: S.Theme.gap\n                                    background: Rectangle {\n                                        color: S.Theme.control\n                                        radius: S.Theme.radius\n                                        border.width: 1\n                                        border.color: textArea.activeFocus ? S.Theme.accent : S.Theme.border\n                                    }\n                                    onTextChanged: {\n                                        if (activeFocus && !root.applyingState && root.style.text !== text) {\n                                            dirty = true;\n                                            root.style.text = text;\n                                            root.sendLive();\n                                        }\n                                    }\n                                    onActiveFocusChanged: {\n                                        if (!activeFocus && dirty) {\n                                            dirty = false;\n                                            root.saveStyle(\"Edit text\");\n                                        }\n                                    }\n                                }\n                            }\n                            S.SComboRow {\n                                label: \"Case\"\n                                options: [\"none\", \"uppercase\", \"lowercase\", \"titlecase\"]\n                                labels: [\"Unchanged\", \"Uppercase\", \"Lowercase\", \"Title case\"]\n                                value: root.sty(\"textTransform\", \"none\")\n                                onEdited: function (v) { root.setAndSave(\"textTransform\", v, \"Change transform\"); }\n                            }\n                        }\n\n                        // ========================= FONT =========================\n                        S.SSection {\n                            title: \"Font\"\n\n                            RowLayout {\n                                Layout.fillWidth: true\n                                spacing: S.Theme.gap\n                                S.SFieldLabel { text: \"Family\" }\n                                S.SCombo {\n                                    id: fontCombo\n                                    Layout.fillWidth: true\n                                    editable: true\n\n                                    property string family: root.sty(\"fontFamily\", \"\")\n                                    // Prepend the stored family when it is not installed on this\n                                    // machine, so the field shows what the document actually asks\n                                    // for (the render host may well have the font) instead of\n                                    // blanking on a currentIndex of -1.\n                                    model: {\n                                        var l = Qt.fontFamilies();\n                                        return (family.length && l.indexOf(family) < 0) ? [family].concat(l) : l;\n                                    }\n                                    currentIndex: Math.max(0, model.indexOf(family))\n                                    onActivated: root.setAndSave(\"fontFamily\", currentText, \"Change font\")\n                                    onAccepted: root.setAndSave(\"fontFamily\", editText, \"Change font\")\n                                }\n                            }\n                            S.SSliderRow {\n                                label: \"Size\"; from: 4; to: 500; stepSize: 1; decimals: 0; suffix: \" px\"\n                                defaultValue: 72\n                                value: root.sty(\"fontSize\", 72)\n                                onMoved: function (v) { root.setLive(\"fontSize\", v); }\n                                onCommitted: root.saveStyle(\"Change size\")\n                            }\n                            S.SComboRow {\n                                label: \"Weight\"\n                                options: [\"normal\", \"bold\"]\n                                value: root.sty(\"fontWeight\", \"normal\")\n                                onEdited: function (v) { root.setAndSave(\"fontWeight\", v, \"Change weight\"); }\n                            }\n                            S.SComboRow {\n                                label: \"Style\"\n                                options: [\"normal\", \"italic\"]\n                                value: root.sty(\"fontStyle\", \"normal\")\n                                onEdited: function (v) { root.setAndSave(\"fontStyle\", v, \"Change style\"); }\n                            }\n                            RowLayout {\n                                Layout.fillWidth: true\n                                spacing: S.Theme.gapLg\n                                S.SCheck {\n                                    text: \"Underline\"; font.pixelSize: S.Theme.fontMd\n                                    checked: root.sty(\"underline\", false)\n                                    onToggled: root.setAndSave(\"underline\", checked, \"Toggle underline\")\n                                }\n                                S.SCheck {\n                                    text: \"Strike\"; font.pixelSize: S.Theme.fontMd\n                                    checked: root.sty(\"strikethrough\", false)\n                                    onToggled: root.setAndSave(\"strikethrough\", checked, \"Toggle strikethrough\")\n                                }\n                                Item { Layout.fillWidth: true }\n                            }\n                        }\n\n                        // ========================= FILL =========================\n                        S.SSection {\n                            title: \"Fill\"\n\n                            RowLayout {\n                                Layout.fillWidth: true\n                                spacing: S.Theme.gap\n                                S.SCheck {\n                                    text: \"Enabled\"; font.pixelSize: S.Theme.fontMd\n                                    checked: root.sty(\"fillEnabled\", true) !== false\n                                    onToggled: root.setAndSave(\"fillEnabled\", checked, \"Toggle fill\")\n                                }\n                                S.SCombo {\n                                    Layout.fillWidth: true\n                                    readonly property var options: [\"solid\", \"linearGradient\", \"radialGradient\", \"texture\"]\n                                    model: [\"Solid\", \"Linear gradient\", \"Radial gradient\", \"Texture\"]\n                                    currentIndex: Math.max(0, options.indexOf(root.sty(\"fillType\", \"solid\")))\n                                    onActivated: root.setAndSave(\"fillType\", options[currentIndex], \"Change fill type\")\n                                    ToolTip.visible: hovered && currentIndex === 3\n                                    ToolTip.text: \"Texture uses the Fill Texture inlet.\"\n                                }\n                            }\n                            S.SColorRow {\n                                visible: root.sty(\"fillType\", \"solid\") === \"solid\"\n                                label: \"Colour\"; value: root.sty(\"fillColor\", \"#ffffff\")\n                                onEdited: function (v) { root.setAndSave(\"fillColor\", v, \"Change fill colour\"); }\n                            }\n\n                            ColumnLayout {\n                                visible: {\n                                    var t = root.sty(\"fillType\", \"solid\");\n                                    return t === \"linearGradient\" || t === \"radialGradient\";\n                                }\n                                Layout.fillWidth: true\n                                spacing: S.Theme.gap\n\n                                S.SColorRow {\n                                    label: \"Start\"; value: root.sty(\"gradStartColor\", \"#ffffff\")\n                                    onEdited: function (v) { root.setAndSave(\"gradStartColor\", v, \"Change gradient start\"); }\n                                }\n                                S.SColorRow {\n                                    label: \"End\"; value: root.sty(\"gradEndColor\", \"#000000\")\n                                    onEdited: function (v) { root.setAndSave(\"gradEndColor\", v, \"Change gradient end\"); }\n                                }\n                                S.SSliderRow {\n                                    visible: root.sty(\"fillType\", \"solid\") === \"linearGradient\"\n                                    label: \"Angle\"; from: 0; to: 360; stepSize: 1; decimals: 0; suffix: \"°\"\n                                    value: root.sty(\"gradAngle\", 0)\n                                    onMoved: function (v) { root.setLive(\"gradAngle\", v); }\n                                    onCommitted: root.saveStyle(\"Change angle\")\n                                }\n                                S.SComboRow {\n                                    label: \"Scope\"\n                                    options: [\"fullText\", \"perLine\", \"perWord\", \"perCharacter\"]\n                                    labels: [\"Whole text\", \"Each line\", \"Each word\", \"Each character\"]\n                                    value: root.sty(\"gradientScope\", \"fullText\")\n                                    onEdited: function (v) { root.setAndSave(\"gradientScope\", v, \"Change gradient scope\"); }\n                                }\n                            }\n\n                            ColumnLayout {\n                                visible: root.sty(\"fillType\", \"solid\") === \"texture\"\n                                Layout.fillWidth: true\n                                spacing: S.Theme.gap\n\n                                S.SSliderRow {\n                                    label: \"Scale X\"; from: 0.1; to: 5; stepSize: 0.05; defaultValue: 1\n                                    value: root.sty(\"texFillScaleX\", 1)\n                                    onMoved: function (v) { root.setLive(\"texFillScaleX\", v); }\n                                    onCommitted: root.saveStyle(\"Change texture scale\")\n                                }\n                                S.SSliderRow {\n                                    label: \"Scale Y\"; from: 0.1; to: 5; stepSize: 0.05; defaultValue: 1\n                                    value: root.sty(\"texFillScaleY\", 1)\n                                    onMoved: function (v) { root.setLive(\"texFillScaleY\", v); }\n                                    onCommitted: root.saveStyle(\"Change texture scale\")\n                                }\n                                S.SSliderRow {\n                                    label: \"Offset X\"; from: -1; to: 1; stepSize: 0.01\n                                    value: root.sty(\"texFillOffsetX\", 0)\n                                    onMoved: function (v) { root.setLive(\"texFillOffsetX\", v); }\n                                    onCommitted: root.saveStyle(\"Change texture offset\")\n                                }\n                                S.SSliderRow {\n                                    label: \"Offset Y\"; from: -1; to: 1; stepSize: 0.01\n                                    value: root.sty(\"texFillOffsetY\", 0)\n                                    onMoved: function (v) { root.setLive(\"texFillOffsetY\", v); }\n                                    onCommitted: root.saveStyle(\"Change texture offset\")\n                                }\n                            }\n                        }\n\n                        // ======================== STROKE ========================\n                        S.SSection {\n                            title: \"Stroke\"\n                            tip: \"Up to three stroke layers, drawn widest first.\"\n\n                            Repeater {\n                                model: [\n                                    { n: 1, en: \"strokeEnabled\",  col: \"strokeColor\",  w: \"strokeWidth\",  j: \"strokeJoin\",  max: 50,  def: 3 },\n                                    { n: 2, en: \"stroke2Enabled\", col: \"stroke2Color\", w: \"stroke2Width\", j: \"stroke2Join\", max: 80,  def: 8 },\n                                    { n: 3, en: \"stroke3Enabled\", col: \"stroke3Color\", w: \"stroke3Width\", j: \"stroke3Join\", max: 100, def: 14 }\n                                ]\n                                ColumnLayout {\n                                    id: strokeLayer\n                                    required property var modelData\n                                    Layout.fillWidth: true\n                                    spacing: S.Theme.gap\n\n                                    S.SSeparator { visible: strokeLayer.modelData.n > 1 }\n                                    S.SCheck {\n                                        text: \"Layer \" + strokeLayer.modelData.n\n                                        font.pixelSize: S.Theme.fontMd\n                                        checked: root.sty(strokeLayer.modelData.en, false)\n                                        onToggled: root.setAndSave(strokeLayer.modelData.en, checked,\n                                                                   \"Toggle stroke \" + strokeLayer.modelData.n)\n                                    }\n                                    S.SColorRow {\n                                        label: \"Colour\"; value: root.sty(strokeLayer.modelData.col, \"#000000\")\n                                        onEdited: function (v) { root.setAndSave(strokeLayer.modelData.col, v, \"Change stroke colour\"); }\n                                    }\n                                    S.SSliderRow {\n                                        label: \"Width\"; from: 0.5; to: strokeLayer.modelData.max\n                                        stepSize: 0.5; decimals: 1\n                                        defaultValue: strokeLayer.modelData.def\n                                        value: root.sty(strokeLayer.modelData.w, strokeLayer.modelData.def)\n                                        onMoved: function (v) { root.setLive(strokeLayer.modelData.w, v); }\n                                        onCommitted: root.saveStyle(\"Change stroke width\")\n                                    }\n                                    S.SComboRow {\n                                        label: \"Join\"\n                                        options: [\"round\", \"bevel\", \"miter\"]\n                                        value: root.sty(strokeLayer.modelData.j, \"round\")\n                                        onEdited: function (v) { root.setAndSave(strokeLayer.modelData.j, v, \"Change join\"); }\n                                    }\n                                }\n                            }\n                        }\n\n                        // ====================== BACKGROUND ======================\n                        S.SSection {\n                            title: \"Background\"\n\n                            S.SCheck {\n                                text: \"Enabled\"; font.pixelSize: S.Theme.fontMd\n                                checked: root.sty(\"bgEnabled\", false)\n                                onToggled: root.setAndSave(\"bgEnabled\", checked, \"Toggle background\")\n                            }\n                            S.SColorRow {\n                                label: \"Colour\"; value: root.sty(\"bgColor\", \"#000000\")\n                                onEdited: function (v) { root.setAndSave(\"bgColor\", v, \"Change background colour\"); }\n                            }\n                            S.SSliderRow {\n                                label: \"Opacity\"; from: 0; to: 1; stepSize: 0.05; defaultValue: 0.5\n                                value: root.sty(\"bgOpacity\", 0.5)\n                                onMoved: function (v) { root.setLive(\"bgOpacity\", v); }\n                                onCommitted: root.saveStyle(\"Change background opacity\")\n                            }\n                            S.SSliderRow {\n                                label: \"Pad H\"; from: 0; to: 200; stepSize: 1; decimals: 0; defaultValue: 20\n                                value: root.sty(\"bgPaddingH\", 20)\n                                onMoved: function (v) { root.setLive(\"bgPaddingH\", v); }\n                                onCommitted: root.saveStyle(\"Change padding\")\n                            }\n                            S.SSliderRow {\n                                label: \"Pad V\"; from: 0; to: 200; stepSize: 1; decimals: 0; defaultValue: 10\n                                value: root.sty(\"bgPaddingV\", 10)\n                                onMoved: function (v) { root.setLive(\"bgPaddingV\", v); }\n                                onCommitted: root.saveStyle(\"Change padding\")\n                            }\n                            S.SSliderRow {\n                                label: \"Radius\"; from: 0; to: 100; stepSize: 1; decimals: 0; defaultValue: 8\n                                value: root.sty(\"bgCornerRadius\", 8)\n                                onMoved: function (v) { root.setLive(\"bgCornerRadius\", v); }\n                                onCommitted: root.saveStyle(\"Change radius\")\n                            }\n                            S.SComboRow {\n                                label: \"Scope\"\n                                options: [\"fullText\", \"perLine\"]\n                                labels: [\"Whole text\", \"Each line\"]\n                                value: root.sty(\"bgScope\", \"fullText\")\n                                onEdited: function (v) { root.setAndSave(\"bgScope\", v, \"Change bg scope\"); }\n                            }\n                            S.SSeparator {}\n                            S.SCheck {\n                                text: \"Border\"; font.pixelSize: S.Theme.fontMd\n                                checked: root.sty(\"bgBorderEnabled\", false)\n                                onToggled: root.setAndSave(\"bgBorderEnabled\", checked, \"Toggle bg border\")\n                            }\n                            S.SColorRow {\n                                label: \"Border colour\"; value: root.sty(\"bgBorderColor\", \"#ffffff\")\n                                onEdited: function (v) { root.setAndSave(\"bgBorderColor\", v, \"Change border colour\"); }\n                            }\n                            S.SSliderRow {\n                                label: \"Border width\"; from: 0.5; to: 20; stepSize: 0.5; decimals: 1; defaultValue: 1\n                                value: root.sty(\"bgBorderWidth\", 1)\n                                onMoved: function (v) { root.setLive(\"bgBorderWidth\", v); }\n                                onCommitted: root.saveStyle(\"Change border width\")\n                            }\n                        }\n\n                        // ======================== LAYOUT ========================\n                        S.SSection {\n                            title: \"Layout\"\n\n                            S.SComboRow {\n                                label: \"H align\"\n                                options: [\"left\", \"center\", \"right\"]\n                                value: root.sty(\"hAlign\", \"center\")\n                                onEdited: function (v) { root.setAndSave(\"hAlign\", v, \"Change alignment\"); }\n                            }\n                            S.SComboRow {\n                                label: \"V align\"\n                                options: [\"top\", \"center\", \"bottom\"]\n                                value: root.sty(\"vAlign\", \"center\")\n                                onEdited: function (v) { root.setAndSave(\"vAlign\", v, \"Change alignment\"); }\n                            }\n                            S.SCheck {\n                                text: \"Word wrap\"; font.pixelSize: S.Theme.fontMd\n                                checked: root.sty(\"wordWrap\", false)\n                                onToggled: root.setAndSave(\"wordWrap\", checked, \"Toggle word wrap\")\n                            }\n                            S.SSliderRow {\n                                visible: root.sty(\"wordWrap\", false)\n                                label: \"Box width\"; from: 0.1; to: 1; stepSize: 0.01; defaultValue: 0.8\n                                value: root.sty(\"boundingBoxW\", 0.8)\n                                onMoved: function (v) { root.setLive(\"boundingBoxW\", v); }\n                                onCommitted: root.saveStyle(\"Change box width\")\n                            }\n                            S.SSliderRow {\n                                label: \"Line spacing\"; from: 0.5; to: 4; stepSize: 0.05; defaultValue: 1.3\n                                value: root.sty(\"lineSpacing\", 1.3)\n                                onMoved: function (v) { root.setLive(\"lineSpacing\", v); }\n                                onCommitted: root.saveStyle(\"Change line spacing\")\n                            }\n                            S.SSliderRow {\n                                label: \"Tracking\"; from: -20; to: 50; stepSize: 0.5; decimals: 1\n                                value: root.sty(\"tracking\", 0)\n                                onMoved: function (v) { root.setLive(\"tracking\", v); }\n                                onCommitted: root.saveStyle(\"Change tracking\")\n                            }\n                        }\n\n                        // ====================== TRANSFORM =======================\n                        S.SSection {\n                            title: \"Transform\"\n\n                            S.SSliderRow {\n                                label: \"Pos X\"; from: 0; to: 1; stepSize: 0.005; decimals: 3; defaultValue: 0.5\n                                value: root.sty(\"posX\", 0.5)\n                                onMoved: function (v) { root.setLive(\"posX\", v); }\n                                onCommitted: root.saveStyle(\"Change position\")\n                            }\n                            S.SSliderRow {\n                                label: \"Pos Y\"; from: 0; to: 1; stepSize: 0.005; decimals: 3; defaultValue: 0.5\n                                value: root.sty(\"posY\", 0.5)\n                                onMoved: function (v) { root.setLive(\"posY\", v); }\n                                onCommitted: root.saveStyle(\"Change position\")\n                            }\n                            S.SSliderRow {\n                                label: \"Rotation\"; from: -360; to: 360; stepSize: 1; decimals: 0; suffix: \"°\"\n                                value: root.sty(\"rotation\", 0)\n                                onMoved: function (v) { root.setLive(\"rotation\", v); }\n                                onCommitted: root.saveStyle(\"Change rotation\")\n                            }\n                            S.SSliderRow {\n                                label: \"Scale X\"; from: 0.1; to: 5; stepSize: 0.05; defaultValue: 1\n                                value: root.sty(\"scaleX\", 1)\n                                onMoved: function (v) { root.setLive(\"scaleX\", v); }\n                                onCommitted: root.saveStyle(\"Change scale\")\n                            }\n                            S.SSliderRow {\n                                label: \"Scale Y\"; from: 0.1; to: 5; stepSize: 0.05; defaultValue: 1\n                                value: root.sty(\"scaleY\", 1)\n                                onMoved: function (v) { root.setLive(\"scaleY\", v); }\n                                onCommitted: root.saveStyle(\"Change scale\")\n                            }\n                            S.SSliderRow {\n                                label: \"Skew X\"; from: -60; to: 60; stepSize: 1; decimals: 0; suffix: \"°\"\n                                value: root.sty(\"skewX\", 0)\n                                onMoved: function (v) { root.setLive(\"skewX\", v); }\n                                onCommitted: root.saveStyle(\"Change skew\")\n                            }\n                            S.SSliderRow {\n                                label: \"Skew Y\"; from: -60; to: 60; stepSize: 1; decimals: 0; suffix: \"°\"\n                                value: root.sty(\"skewY\", 0)\n                                onMoved: function (v) { root.setLive(\"skewY\", v); }\n                                onCommitted: root.saveStyle(\"Change skew\")\n                            }\n                            S.SComboRow {\n                                label: \"Anchor H\"\n                                options: [\"left\", \"center\", \"right\"]\n                                value: root.sty(\"anchorH\", \"center\")\n                                onEdited: function (v) { root.setAndSave(\"anchorH\", v, \"Change anchor\"); }\n                            }\n                            S.SComboRow {\n                                label: \"Anchor V\"\n                                options: [\"top\", \"center\", \"bottom\"]\n                                value: root.sty(\"anchorV\", \"center\")\n                                onEdited: function (v) { root.setAndSave(\"anchorV\", v, \"Change anchor\"); }\n                            }\n                            S.SSliderRow {\n                                label: \"Opacity\"; from: 0; to: 1; stepSize: 0.01; defaultValue: 1\n                                value: root.sty(\"opacity\", 1)\n                                onMoved: function (v) { root.setLive(\"opacity\", v); }\n                                onCommitted: root.saveStyle(\"Change opacity\")\n                            }\n                        }\n\n                        // ======================= WRITE-ON =======================\n                        S.SSection {\n                            title: \"Write-on\"\n                            expanded: false\n\n                            S.SComboRow {\n                                label: \"Mode\"\n                                options: [\"typewriter\", \"fade\", \"scale\", \"slide\", \"scramble\"]\n                                value: root.sty(\"writeOnMode\", \"typewriter\")\n                                onEdited: function (v) { root.setAndSave(\"writeOnMode\", v, \"Change write-on mode\"); }\n                            }\n                            S.SComboRow {\n                                label: \"Direction\"\n                                options: [\"forward\", \"reverse\", \"fromCenter\", \"random\"]\n                                labels: [\"Forward\", \"Reverse\", \"From center\", \"Random\"]\n                                value: root.sty(\"writeOnDirection\", \"forward\")\n                                onEdited: function (v) { root.setAndSave(\"writeOnDirection\", v, \"Change write-on direction\"); }\n                            }\n                            S.SComboRow {\n                                label: \"Scope\"\n                                options: [\"character\", \"word\", \"line\"]\n                                value: root.sty(\"writeOnScope\", \"character\")\n                                onEdited: function (v) { root.setAndSave(\"writeOnScope\", v, \"Change write-on scope\"); }\n                            }\n                            S.SSliderRow {\n                                label: \"Overlap\"; from: 1; to: 50; stepSize: 1; decimals: 0; defaultValue: 5\n                                value: root.sty(\"writeOnOverlap\", 5)\n                                onMoved: function (v) { root.setLive(\"writeOnOverlap\", v); }\n                                onCommitted: root.saveStyle(\"Change overlap\")\n                            }\n                            RowLayout {\n                                Layout.fillWidth: true\n                                spacing: S.Theme.gapLg\n                                S.SCheck {\n                                    text: \"Cursor\"; font.pixelSize: S.Theme.fontMd\n                                    checked: root.sty(\"writeOnCursor\", false)\n                                    onToggled: root.setAndSave(\"writeOnCursor\", checked, \"Toggle cursor\")\n                                }\n                                S.STextField {\n                                    Layout.preferredWidth: 44\n                                    horizontalAlignment: Text.AlignHCenter\n                                    text: root.sty(\"writeOnCursorChar\", \"▌\")\n                                    onEditingFinished: root.setAndSave(\"writeOnCursorChar\", text, \"Change cursor char\")\n                                }\n                                Item { Layout.fillWidth: true }\n                            }\n                        }\n\n                        // ====================== SCROLLING =======================\n                        S.SSection {\n                            title: \"Scrolling\"\n                            expanded: false\n\n                            S.SComboRow {\n                                label: \"Mode\"\n                                options: [\"none\", \"up\", \"down\", \"left\", \"right\"]\n                                value: root.sty(\"scrollMode\", \"none\")\n                                onEdited: function (v) { root.setAndSave(\"scrollMode\", v, \"Change scroll mode\"); }\n                            }\n                            S.SSliderRow {\n                                label: \"Speed\"; from: 1; to: 1000; stepSize: 1; decimals: 0; suffix: \" px/s\"\n                                defaultValue: 50\n                                value: root.sty(\"scrollSpeed\", 50)\n                                onMoved: function (v) { root.setLive(\"scrollSpeed\", v); }\n                                onCommitted: root.saveStyle(\"Change scroll speed\")\n                            }\n                            S.SCheck {\n                                text: \"Loop\"; font.pixelSize: S.Theme.fontMd\n                                checked: root.sty(\"scrollLoop\", true) !== false\n                                onToggled: root.setAndSave(\"scrollLoop\", checked, \"Toggle scroll loop\")\n                            }\n                            S.SSliderRow {\n                                label: \"Fade edge\"; from: 0; to: 0.5; stepSize: 0.01; defaultValue: 0.1\n                                value: root.sty(\"scrollFadeEdges\", 0.1)\n                                onMoved: function (v) { root.setLive(\"scrollFadeEdges\", v); }\n                                onCommitted: root.saveStyle(\"Change fade\")\n                            }\n                        }\n\n                        // ======================== WAVE ==========================\n                        S.SSection {\n                            title: \"Wave animation\"\n                            expanded: false\n\n                            S.SCheck {\n                                text: \"Enabled\"; font.pixelSize: S.Theme.fontMd\n                                checked: root.sty(\"charWaveEnabled\", false)\n                                onToggled: root.setAndSave(\"charWaveEnabled\", checked, \"Toggle wave\")\n                            }\n                            S.SSliderRow {\n                                label: \"Amplitude\"; from: 1; to: 100; stepSize: 1; decimals: 0; defaultValue: 10\n                                value: root.sty(\"charWaveAmplitude\", 10)\n                                onMoved: function (v) { root.setLive(\"charWaveAmplitude\", v); }\n                                onCommitted: root.saveStyle(\"Change amplitude\")\n                            }\n                            S.SSliderRow {\n                                label: \"Frequency\"; from: 0.1; to: 20; stepSize: 0.1; decimals: 1; suffix: \" Hz\"\n                                defaultValue: 2\n                                value: root.sty(\"charWaveFrequency\", 2)\n                                onMoved: function (v) { root.setLive(\"charWaveFrequency\", v); }\n                                onCommitted: root.saveStyle(\"Change frequency\")\n                            }\n                            S.SComboRow {\n                                label: \"Property\"\n                                options: [\"posY\", \"posX\", \"rotation\", \"scale\", \"opacity\"]\n                                labels: [\"Position Y\", \"Position X\", \"Rotation\", \"Scale\", \"Opacity\"]\n                                value: root.sty(\"charWaveProperty\", \"posY\")\n                                onEdited: function (v) { root.setAndSave(\"charWaveProperty\", v, \"Change wave property\"); }\n                            }\n                        }\n\n                        // =================== CHAR ANIMATION =====================\n                        S.SSection {\n                            title: \"Char animation\"\n                            expanded: false\n                            tip: \"Animate characters, words or lines in sequence.\"\n\n                            RowLayout {\n                                Layout.fillWidth: true\n                                spacing: S.Theme.gapLg\n                                S.SCheck {\n                                    text: \"Enabled\"; font.pixelSize: S.Theme.fontMd\n                                    checked: root.sty(\"charAnimEnabled\", false)\n                                    onToggled: root.setAndSave(\"charAnimEnabled\", checked, \"Toggle char anim\")\n                                }\n                                Item { Layout.fillWidth: true }\n                                S.SButton {\n                                    text: \"Retrigger\"\n                                    implicitHeight: S.Theme.rowH\n                                    onClicked: root.executionSend({ type: \"retrigger\" })\n                                }\n                            }\n                            S.SComboRow {\n                                label: \"Scope\"\n                                options: [\"character\", \"word\", \"line\"]\n                                value: root.sty(\"charAnimScope\", \"character\")\n                                onEdited: function (v) { root.setAndSave(\"charAnimScope\", v, \"Change anim scope\"); }\n                            }\n                            S.SComboRow {\n                                label: \"Order\"\n                                options: [\"forward\", \"reverse\", \"random\", \"fromCenter\", \"toCenter\"]\n                                labels: [\"Forward\", \"Reverse\", \"Random\", \"From center\", \"To center\"]\n                                value: root.sty(\"charAnimOrder\", \"forward\")\n                                onEdited: function (v) { root.setAndSave(\"charAnimOrder\", v, \"Change anim order\"); }\n                            }\n                            S.SSliderRow {\n                                label: \"Delay\"; from: 0.01; to: 2; stepSize: 0.01; suffix: \" s\"; defaultValue: 0.1\n                                value: root.sty(\"charAnimDelay\", 0.1)\n                                onMoved: function (v) { root.setLive(\"charAnimDelay\", v); }\n                                onCommitted: root.saveStyle(\"Change delay\")\n                            }\n                            S.SSliderRow {\n                                label: \"Seed\"; from: 1; to: 999; stepSize: 1; decimals: 0; defaultValue: 42\n                                value: root.sty(\"charAnimRandomSeed\", 42)\n                                onMoved: function (v) { root.setLive(\"charAnimRandomSeed\", v); }\n                                onCommitted: root.saveStyle(\"Change seed\")\n                            }\n\n                            S.SSeparator {}\n                            S.SSectionLabel { text: \"Offsets\" }\n\n                            S.SSliderRow {\n                                label: \"Pos X\"; from: -500; to: 500; stepSize: 1; decimals: 0\n                                value: root.sty(\"charAnimPosX\", 0)\n                                onMoved: function (v) { root.setLive(\"charAnimPosX\", v); }\n                                onCommitted: root.saveStyle(\"Change offset\")\n                            }\n                            S.SSliderRow {\n                                label: \"Pos Y\"; from: -500; to: 500; stepSize: 1; decimals: 0\n                                value: root.sty(\"charAnimPosY\", 0)\n                                onMoved: function (v) { root.setLive(\"charAnimPosY\", v); }\n                                onCommitted: root.saveStyle(\"Change offset\")\n                            }\n                            S.SSliderRow {\n                                label: \"Rotation\"; from: -360; to: 360; stepSize: 1; decimals: 0; suffix: \"°\"\n                                value: root.sty(\"charAnimRotation\", 0)\n                                onMoved: function (v) { root.setLive(\"charAnimRotation\", v); }\n                                onCommitted: root.saveStyle(\"Change rotation\")\n                            }\n                            S.SSliderRow {\n                                label: \"Scale\"; from: 0; to: 5; stepSize: 0.05; defaultValue: 1\n                                value: root.sty(\"charAnimScale\", 1)\n                                onMoved: function (v) { root.setLive(\"charAnimScale\", v); }\n                                onCommitted: root.saveStyle(\"Change scale\")\n                            }\n                            S.SSliderRow {\n                                label: \"Opacity\"; from: 0; to: 1; stepSize: 0.01; defaultValue: 1\n                                value: root.sty(\"charAnimOpacity\", 1)\n                                onMoved: function (v) { root.setLive(\"charAnimOpacity\", v); }\n                                onCommitted: root.saveStyle(\"Change opacity\")\n                            }\n                            S.SColorRow {\n                                label: \"Fill colour\"; value: root.sty(\"charAnimFillColor\", \"#ffffff\")\n                                onEdited: function (v) { root.setAndSave(\"charAnimFillColor\", v, \"Change anim colour\"); }\n                            }\n                            S.SSliderRow {\n                                label: \"Char offset\"; from: -50; to: 50; stepSize: 1; decimals: 0\n                                value: root.sty(\"charAnimCharOffset\", 0)\n                                onMoved: function (v) { root.setLive(\"charAnimCharOffset\", v); }\n                                onCommitted: root.saveStyle(\"Change char offset\")\n                            }\n                        }\n\n                        Item { Layout.fillHeight: true; Layout.minimumHeight: S.Theme.pad }\n                    }\n                }\n            }\n\n            S.SStatusBar {\n                visible: detail.length > 0\n                detail: root.renderWidth > 0\n                        ? (Math.round(root.renderWidth) + \"×\" + Math.round(root.renderHeight))\n                        : \"\"\n            }\n        }\n    }\n}\n","State":[["textState",{"String":"{\"text\":\"\",\"textTransform\":\"none\",\"fontFamily\":\"Bitstream Vera Serif\",\"fontSize\":49,\"fontWeight\":\"normal\",\"fontStyle\":\"normal\",\"underline\":false,\"strikethrough\":false,\"fillEnabled\":true,\"fillType\":\"solid\",\"fillColor\":\"#ffffff\",\"gradStartColor\":\"#ffffff\",\"gradEndColor\":\"#4466ff\",\"gradAngle\":90,\"strokeEnabled\":false,\"strokeColor\":\"#000000\",\"strokeWidth\":3,\"strokeJoin\":\"round\",\"shadowEnabled\":false,\"shadowColor\":\"#000000\",\"shadowOffsetX\":4,\"shadowOffsetY\":4,\"shadowBlur\":10,\"shadowOpacity\":0.7,\"bgEnabled\":false,\"bgColor\":\"#000000\",\"bgOpacity\":0.5,\"bgPaddingH\":20,\"bgPaddingV\":10,\"bgCornerRadius\":8,\"hAlign\":\"center\",\"vAlign\":\"center\",\"wordWrap\":false,\"boundingBoxW\":0.8,\"boundingBoxH\":0,\"lineSpacing\":1.3,\"tracking\":0,\"posX\":0.5,\"posY\":0.12,\"rotation\":0,\"scaleX\":1,\"scaleY\":1,\"skewX\":0,\"skewY\":0,\"anchorH\":\"center\",\"anchorV\":\"center\",\"opacity\":1,\"writeOnMode\":\"typewriter\",\"writeOnDirection\":\"forward\",\"writeOnScope\":\"character\",\"writeOnOverlap\":5,\"writeOnCursor\":false,\"writeOnCursorChar\":\"▌\",\"writeOnScrambleIter\":5,\"scrollMode\":\"none\",\"scrollSpeed\":50,\"scrollLoop\":true,\"scrollFadeEdges\":0.1,\"charWaveEnabled\":false,\"charWaveAmplitude\":10,\"charWaveFrequency\":2,\"charWaveProperty\":\"posY\",\"stroke2Enabled\":false,\"stroke2Color\":\"#333333\",\"stroke2Width\":8,\"stroke2Join\":\"round\",\"stroke3Enabled\":false,\"stroke3Color\":\"#666666\",\"stroke3Width\":14,\"stroke3Join\":\"round\",\"shadow2Enabled\":false,\"shadow2Color\":\"#000000\",\"shadow2OffsetX\":8,\"shadow2OffsetY\":8,\"shadow2Blur\":20,\"shadow2Opacity\":0.5,\"charAnimEnabled\":false,\"charAnimScope\":\"character\",\"charAnimOrder\":\"forward\",\"charAnimDelay\":0.1,\"charAnimRandomSeed\":42,\"charAnimPosX\":0,\"charAnimPosY\":50,\"charAnimRotation\":0,\"charAnimScale\":0.5,\"charAnimOpacity\":0,\"charAnimFillColor\":\"\",\"charAnimCharOffset\":0,\"texFillScaleX\":1,\"texFillScaleY\":1,\"texFillOffsetX\":0,\"texFillOffsetY\":0,\"gradientScope\":\"fullText\",\"bgScope\":\"fullText\",\"bgBorderEnabled\":false,\"bgBorderColor\":\"#ffffff\",\"bgBorderWidth\":1}"}]],"Root":"<LIBRARY>:packages/default/Presets/Javascript/advanced-text/advanced-text.qml","Inlets":[{"uuid":"5ac86198-2d03-4830-9e41-a6d529922d29","ObjectName":"Inlet","id":0,"Hidden":false,"Custom":"Fill Texture","Exposed":"fill texture","Format":0,"Filter":2,"AddressMode":1},{"uuid":"769dd38a-bfb3-4dc6-b52a-b6abb7afe2a3","ObjectName":"Inlet","id":1,"Hidden":false,"Custom":"Text","Exposed":"text"},{"uuid":"769dd38a-bfb3-4dc6-b52a-b6abb7afe2a3","ObjectName":"Inlet","id":2,"Hidden":false,"Custom":"Style","Exposed":"style"},{"uuid":"af2b4fc3-aecb-4c15-a5aa-1c573a239925","ObjectName":"Inlet","id":3,"Hidden":true,"Custom":"Opacity","Exposed":"opacity","Value":{"Float":1.0},"Init":{"Float":1.0},"Domain":{"Float":{"Min":0.0,"Max":1.0}}},{"uuid":"af2b4fc3-aecb-4c15-a5aa-1c573a239925","ObjectName":"Inlet","id":4,"Hidden":true,"Custom":"Write On","Exposed":"write on","Value":{"Float":1.0},"Init":{"Float":1.0},"Domain":{"Float":{"Min":0.0,"Max":1.0}}},{"uuid":"8f38638e-9f9f-48b0-ae36-1cba86ef5703","ObjectName":"Inlet","id":5,"Hidden":true,"Custom":"Color","Exposed":"color","Value":{"Vec4f":[1.0,0.37036699056625366,0.09665064513683319,1.0]},"Init":{"Vec4f":[0.0,0.0,0.0,1.0]},"Domain":{}}],"Outlets":[{"uuid":"f1c71046-b754-49a5-8e66-d01374773dfc","ObjectName":"Outlet","id":0,"Hidden":false,"Custom":"Output","Exposed":"output"}]},{"uuid":"74ca45ff-92c9-44a0-8f1a-754dea05ee1b","ObjectName":"gfxProcess","id":3,"Metadata":{"ScriptingName":"Video Mixer","Comment":"","Color":"Transparent1","Label":"","Touched":true},"Duration":10584000000,"Height":300.0,"StartOffset":0,"LoopDuration":10584000000,"Pos":[199.59000000000003,-250.89099999999996],"Size":[387.0,175.0],"Loops":false,"FoldMode":0,"Vertex":"","Fragment":"/*{\n    \"CATEGORIES\": [\n        \"General\"\n    ],\n    \"CREDIT\": \"Jamie Owen, Jean-Michaël Celerier, Optimized by AI Assistant (Gemini)\",\n    \"DESCRIPTION\": \"8-channel video mixer (Optimized)\",\n    \"INPUTS\": [\n        { \"NAME\": \"t1\", \"LABEL\" : \"Texture 1\", \"TYPE\": \"image\" },\n        { \"NAME\": \"t2\", \"LABEL\" : \"Texture 2\", \"TYPE\": \"image\" },\n        { \"NAME\": \"t3\", \"LABEL\" : \"Texture 3\", \"TYPE\": \"image\" },\n        { \"NAME\": \"t4\", \"LABEL\" : \"Texture 4\", \"TYPE\": \"image\" },\n        { \"NAME\": \"t5\", \"LABEL\" : \"Texture 5\", \"TYPE\": \"image\" },\n        { \"NAME\": \"t6\", \"LABEL\" : \"Texture 6\", \"TYPE\": \"image\" },\n        { \"NAME\": \"t7\", \"LABEL\" : \"Texture 7\", \"TYPE\": \"image\" },\n        { \"NAME\": \"t8\", \"LABEL\" : \"Texture 8\", \"TYPE\": \"image\" },\n        { \"NAME\": \"alpha1\", \"LABEL\" : \"Alpha 1\", \"DEFAULT\": 1, \"MAX\": 1, \"MIN\": 0, \"TYPE\": \"float\" },\n        { \"NAME\": \"alpha2\", \"LABEL\" : \"Alpha 2\", \"DEFAULT\": 0, \"MAX\": 1, \"MIN\": 0, \"TYPE\": \"float\" },\n        { \"NAME\": \"alpha3\", \"LABEL\" : \"Alpha 3\", \"DEFAULT\": 0, \"MAX\": 1, \"MIN\": 0, \"TYPE\": \"float\" },\n        { \"NAME\": \"alpha4\", \"LABEL\" : \"Alpha 4\", \"DEFAULT\": 0, \"MAX\": 1, \"MIN\": 0, \"TYPE\": \"float\" },\n        { \"NAME\": \"alpha5\", \"LABEL\" : \"Alpha 5\", \"DEFAULT\": 0, \"MAX\": 1, \"MIN\": 0, \"TYPE\": \"float\" },\n        { \"NAME\": \"alpha6\", \"LABEL\" : \"Alpha 6\", \"DEFAULT\": 0, \"MAX\": 1, \"MIN\": 0, \"TYPE\": \"float\" },\n        { \"NAME\": \"alpha7\", \"LABEL\" : \"Alpha 7\", \"DEFAULT\": 0, \"MAX\": 1, \"MIN\": 0, \"TYPE\": \"float\" },\n        { \"NAME\": \"alpha8\", \"LABEL\" : \"Alpha 8\", \"DEFAULT\": 0, \"MAX\": 1, \"MIN\": 0, \"TYPE\": \"float\" },\n        { \"VALUES\" : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 ],\n           \"LABELS\" : [ \"Add\", \"Average\", \"Color Burn\", \"Color Dodge\", \"Darken\", \"Difference\", \n                        \"Exclusion\", \"Glow\", \"Hard Light\", \"Hard Mix\", \"Lighten\", \"Linear Burn\", \n                        \"Linear Dodge\", \"Linear Light\", \"Multiply\", \"Negation\", \"Normal\", \"Overlay\", \n                        \"Phoenix\", \"Pin Light\", \"Reflect\", \"Screen\", \"Soft Light\", \"Subtract\", \"Vivid Light\"],\n           \"IDENTITY\" : 1,\n           \"DEFAULT\" : 17,\n           \"LABEL\" : \"Mode 1\",\n           \"TYPE\" : \"long\",\n           \"NAME\" : \"mode1\"\n        },\n        { \"VALUES\" : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 ],\n           \"LABELS\" : [ \"Add\", \"Average\", \"Color Burn\", \"Color Dodge\", \"Darken\", \"Difference\", \n                        \"Exclusion\", \"Glow\", \"Hard Light\", \"Hard Mix\", \"Lighten\", \"Linear Burn\", \n                        \"Linear Dodge\", \"Linear Light\", \"Multiply\", \"Negation\", \"Normal\", \"Overlay\", \n                        \"Phoenix\", \"Pin Light\", \"Reflect\", \"Screen\", \"Soft Light\", \"Subtract\", \"Vivid Light\"],\n           \"IDENTITY\" : 1,\n           \"DEFAULT\" : 17,\n           \"LABEL\" : \"Mode 2\",\n           \"TYPE\" : \"long\",\n           \"NAME\" : \"mode2\"\n        },\n        { \"VALUES\" : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 ],\n           \"LABELS\" : [ \"Add\", \"Average\", \"Color Burn\", \"Color Dodge\", \"Darken\", \"Difference\", \n                        \"Exclusion\", \"Glow\", \"Hard Light\", \"Hard Mix\", \"Lighten\", \"Linear Burn\", \n                        \"Linear Dodge\", \"Linear Light\", \"Multiply\", \"Negation\", \"Normal\", \"Overlay\", \n                        \"Phoenix\", \"Pin Light\", \"Reflect\", \"Screen\", \"Soft Light\", \"Subtract\", \"Vivid Light\"],\n           \"IDENTITY\" : 1,\n           \"DEFAULT\" : 17,\n           \"LABEL\" : \"Mode 3\",\n           \"TYPE\" : \"long\",\n           \"NAME\" : \"mode3\"\n        },\n        { \"VALUES\" : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 ],\n           \"LABELS\" : [ \"Add\", \"Average\", \"Color Burn\", \"Color Dodge\", \"Darken\", \"Difference\", \n                        \"Exclusion\", \"Glow\", \"Hard Light\", \"Hard Mix\", \"Lighten\", \"Linear Burn\", \n                        \"Linear Dodge\", \"Linear Light\", \"Multiply\", \"Negation\", \"Normal\", \"Overlay\", \n                        \"Phoenix\", \"Pin Light\", \"Reflect\", \"Screen\", \"Soft Light\", \"Subtract\", \"Vivid Light\"],\n           \"IDENTITY\" : 1,\n           \"DEFAULT\" : 17,\n           \"LABEL\" : \"Mode 4\",\n           \"TYPE\" : \"long\",\n           \"NAME\" : \"mode4\"\n        },\n        { \"VALUES\" : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 ],\n           \"LABELS\" : [ \"Add\", \"Average\", \"Color Burn\", \"Color Dodge\", \"Darken\", \"Difference\", \n                        \"Exclusion\", \"Glow\", \"Hard Light\", \"Hard Mix\", \"Lighten\", \"Linear Burn\", \n                        \"Linear Dodge\", \"Linear Light\", \"Multiply\", \"Negation\", \"Normal\", \"Overlay\", \n                        \"Phoenix\", \"Pin Light\", \"Reflect\", \"Screen\", \"Soft Light\", \"Subtract\", \"Vivid Light\"],\n           \"IDENTITY\" : 1,\n           \"DEFAULT\" : 17,\n           \"LABEL\" : \"Mode 5\",\n           \"TYPE\" : \"long\",\n           \"NAME\" : \"mode5\"\n        },\n        { \"VALUES\" : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 ],\n           \"LABELS\" : [ \"Add\", \"Average\", \"Color Burn\", \"Color Dodge\", \"Darken\", \"Difference\", \n                        \"Exclusion\", \"Glow\", \"Hard Light\", \"Hard Mix\", \"Lighten\", \"Linear Burn\", \n                        \"Linear Dodge\", \"Linear Light\", \"Multiply\", \"Negation\", \"Normal\", \"Overlay\", \n                        \"Phoenix\", \"Pin Light\", \"Reflect\", \"Screen\", \"Soft Light\", \"Subtract\", \"Vivid Light\"],\n           \"IDENTITY\" : 1,\n           \"DEFAULT\" : 17,\n           \"LABEL\" : \"Mode 6\",\n           \"TYPE\" : \"long\",\n           \"NAME\" : \"mode6\"\n        },\n        { \"VALUES\" : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 ],\n           \"LABELS\" : [ \"Add\", \"Average\", \"Color Burn\", \"Color Dodge\", \"Darken\", \"Difference\", \n                        \"Exclusion\", \"Glow\", \"Hard Light\", \"Hard Mix\", \"Lighten\", \"Linear Burn\", \n                        \"Linear Dodge\", \"Linear Light\", \"Multiply\", \"Negation\", \"Normal\", \"Overlay\", \n                        \"Phoenix\", \"Pin Light\", \"Reflect\", \"Screen\", \"Soft Light\", \"Subtract\", \"Vivid Light\"],\n           \"IDENTITY\" : 1,\n           \"DEFAULT\" : 17,\n           \"LABEL\" : \"Mode 7\",\n           \"TYPE\" : \"long\",\n           \"NAME\" : \"mode7\"\n        }\n    ],\n    \"ISFVSN\": \"2\"\n}\n*/\n\n/* Blend mode implementations courtesy of Jamie Owen:\n   https://github.com/jamieowen/glsl-blend\n*/\n\nvec3 blendPhoenix(vec3 base, vec3 blend) {\n  return min(base, blend) - max(base, blend) + vec3(1.0);\n}\nfloat blendOverlay(float base, float blend) {\n  return base < 0.5 ? (2.0 * base * blend) : (1.0 - 2.0 * (1.0 - base) * (1.0 - blend));\n}\nvec3 blendOverlay(vec3 base, vec3 blend) {\n  return vec3(blendOverlay(base.r, blend.r), blendOverlay(base.g, blend.g), blendOverlay(base.b, blend.b));\n}\nvec3 blendNormal(vec3 base, vec3 blend) { return blend; }\nvec3 blendNegation(vec3 base, vec3 blend) {\n  return vec3(1.0) - abs(vec3(1.0) - base - blend);\n}\nvec3 blendMultiply(vec3 base, vec3 blend) { return base * blend; }\nfloat blendReflect(float base, float blend) {\n  return (blend == 1.0) ? blend : min(base * base / (1.0 - blend), 1.0);\n}\nvec3 blendReflect(vec3 base, vec3 blend) {\n  return vec3(blendReflect(base.r, blend.r), blendReflect(base.g, blend.g), blendReflect(base.b, blend.b));\n}\nvec3 blendAverage(vec3 base, vec3 blend) { return (base + blend) / 2.0; }\nfloat blendLinearBurn(float base, float blend) {\n  return max(base + blend - 1.0, 0.0);\n}\nvec3 blendLinearBurn(vec3 base, vec3 blend) {\n  return max(base + blend - vec3(1.0), vec3(0.0));\n}\nfloat blendLighten(float base, float blend) { return max(blend, base); }\nvec3 blendLighten(vec3 base, vec3 blend) {\n  return vec3(blendLighten(base.r, blend.r), blendLighten(base.g, blend.g), blendLighten(base.b, blend.b));\n}\nfloat blendScreen(float base, float blend) {\n  return 1.0 - ((1.0 - base) * (1.0 - blend));\n}\nvec3 blendScreen(vec3 base, vec3 blend) {\n  return vec3(blendScreen(base.r, blend.r), blendScreen(base.g, blend.g), blendScreen(base.b, blend.b));\n}\nfloat blendSoftLight(float base, float blend) {\n  return (blend < 0.5)\n             ? (2.0 * base * blend + base * base * (1.0 - 2.0 * blend))\n             : (sqrt(base) * (2.0 * blend - 1.0) + 2.0 * base * (1.0 - blend));\n}\nvec3 blendSoftLight(vec3 base, vec3 blend) {\n  return vec3(blendSoftLight(base.r, blend.r), blendSoftLight(base.g, blend.g), blendSoftLight(base.b, blend.b));\n}\nfloat blendSubtract(float base, float blend) {\n  return max(base + blend - 1.0, 0.0);\n}\nvec3 blendSubtract(vec3 base, vec3 blend) {\n  return max(base + blend - vec3(1.0), vec3(0.0));\n}\nvec3 blendExclusion(vec3 base, vec3 blend) {\n  return base + blend - 2.0 * base * blend;\n}\nvec3 blendDifference(vec3 base, vec3 blend) { return abs(base - blend); }\nfloat blendDarken(float base, float blend) { return min(blend, base); }\nvec3 blendDarken(vec3 base, vec3 blend) {\n  return vec3(blendDarken(base.r, blend.r), blendDarken(base.g, blend.g), blendDarken(base.b, blend.b));\n}\nfloat blendColorDodge(float base, float blend) {\n  return (blend == 1.0) ? blend : min(base / (1.0 - blend), 1.0);\n}\nvec3 blendColorDodge(vec3 base, vec3 blend) {\n  return vec3(blendColorDodge(base.r, blend.r), blendColorDodge(base.g, blend.g), blendColorDodge(base.b, blend.b));\n}\nfloat blendColorBurn(float base, float blend) {\n  return (blend == 0.0) ? blend : max((1.0 - ((1.0 - base) / blend)), 0.0);\n}\nvec3 blendColorBurn(vec3 base, vec3 blend) {\n  return vec3(blendColorBurn(base.r, blend.r), blendColorBurn(base.g, blend.g), blendColorBurn(base.b, blend.b));\n}\nfloat blendAdd(float base, float blend) { return min(base + blend, 1.0); }\nvec3 blendAdd(vec3 base, vec3 blend) { return min(base + blend, vec3(1.0)); }\nfloat blendLinearDodge(float base, float blend) {\n  return min(base + blend, 1.0);\n}\nvec3 blendLinearDodge(vec3 base, vec3 blend) {\n  return min(base + blend, vec3(1.0));\n}\nvec3 blendHardLight(vec3 base, vec3 blend) { return blendOverlay(blend, base); }\nvec3 blendGlow(vec3 base, vec3 blend) { return blendReflect(blend, base); }\nfloat blendVividLight(float base, float blend) {\n  return (blend < 0.5) ? blendColorBurn(base, (2.0 * blend)) : blendColorDodge(base, (2.0 * (blend - 0.5)));\n}\nvec3 blendVividLight(vec3 base, vec3 blend) {\n  return vec3(blendVividLight(base.r, blend.r), blendVividLight(base.g, blend.g), blendVividLight(base.b, blend.b));\n}\nfloat blendHardMix(float base, float blend) {\n  return (blendVividLight(base, blend) < 0.5) ? 0.0 : 1.0;\n}\nvec3 blendHardMix(vec3 base, vec3 blend) {\n  return vec3(blendHardMix(base.r, blend.r), blendHardMix(base.g, blend.g), blendHardMix(base.b, blend.b));\n}\nfloat blendLinearLight(float base, float blend) {\n  return blend < 0.5 ? blendLinearBurn(base, (2.0 * blend)) : blendLinearDodge(base, (2.0 * (blend - 0.5)));\n}\nvec3 blendLinearLight(vec3 base, vec3 blend) {\n  return vec3(blendLinearLight(base.r, blend.r), blendLinearLight(base.g, blend.g), blendLinearLight(base.b, blend.b));\n}\nfloat blendPinLight(float base, float blend) {\n  return (blend < 0.5) ? blendDarken(base, (2.0 * blend)) : blendLighten(base, (2.0 * (blend - 0.5)));\n}\nvec3 blendPinLight(vec3 base, vec3 blend) {\n  return vec3(blendPinLight(base.r, blend.r), blendPinLight(base.g, blend.g), blendPinLight(base.b, blend.b));\n}\n\n// 3-argument version of blendMode\nvec3 blendMode(int mode, vec3 base, vec3 blend) {\n  if (mode == 1) return blendAdd(base, blend);\n  else if (mode == 2) return blendAverage(base, blend);\n  else if (mode == 3) return blendColorBurn(base, blend);\n  else if (mode == 4) return blendColorDodge(base, blend);\n  else if (mode == 5) return blendDarken(base, blend);\n  else if (mode == 6) return blendDifference(base, blend);\n  else if (mode == 7) return blendExclusion(base, blend);\n  else if (mode == 8) return blendGlow(base, blend);\n  else if (mode == 9) return blendHardLight(base, blend);\n  else if (mode == 10) return blendHardMix(base, blend);\n  else if (mode == 11) return blendLighten(base, blend);\n  else if (mode == 12) return blendLinearBurn(base, blend);\n  else if (mode == 13) return blendLinearDodge(base, blend);\n  else if (mode == 14) return blendLinearLight(base, blend);\n  else if (mode == 15) return blendMultiply(base, blend);\n  else if (mode == 16) return blendNegation(base, blend);\n  else if (mode == 17) return blendNormal(base, blend);\n  else if (mode == 18) return blendOverlay(base, blend);\n  else if (mode == 19) return blendPhoenix(base, blend);\n  else if (mode == 20) return blendPinLight(base, blend);\n  else if (mode == 21) return blendReflect(base, blend);\n  else if (mode == 22) return blendScreen(base, blend);\n  else if (mode == 23) return blendSoftLight(base, blend);\n  else if (mode == 24) return blendSubtract(base, blend);\n  else if (mode == 25) return blendVividLight(base, blend);\n  else return vec3(0.,0.,0.);\n}\n\n// 4-argument version (handles the opacity mapping beautifully with mix!)\nvec3 blendMode(int mode, vec3 base, vec3 blend, float opacity) {\n    return mix(base, blendMode(mode, base, blend), opacity);\n}\n\nvoid main() {\n  // Base Layer (t1)\n  vec4 s1 = vec4(0.0);\n  if (alpha1 > 0.0) {\n      s1 = IMG_THIS_PIXEL(t1);\n  }\n  float a = s1.a * alpha1;\n  vec3 rgb = s1.rgb * a;\n  \n  // Layer 2\n  if (alpha2 > 0.0) {\n      vec4 s2 = IMG_THIS_PIXEL(t2);\n      float ea = s2.a * alpha2;\n      if (ea > 0.0) {\n          rgb = blendMode(mode1, rgb, s2.rgb, ea);\n          a = a + ea * (1.0 - a);\n      }\n  }\n\n  // Layer 3\n  if (alpha3 > 0.0) {\n      vec4 s3 = IMG_THIS_PIXEL(t3);\n      float ea = s3.a * alpha3;\n      if (ea > 0.0) {\n          rgb = blendMode(mode2, rgb, s3.rgb, ea);\n          a = a + ea * (1.0 - a);\n      }\n  }\n\n  // Layer 4\n  if (alpha4 > 0.0) {\n      vec4 s4 = IMG_THIS_PIXEL(t4);\n      float ea = s4.a * alpha4;\n      if (ea > 0.0) {\n          rgb = blendMode(mode3, rgb, s4.rgb, ea);\n          a = a + ea * (1.0 - a);\n      }\n  }\n\n  // Layer 5\n  if (alpha5 > 0.0) {\n      vec4 s5 = IMG_THIS_PIXEL(t5);\n      float ea = s5.a * alpha5;\n      if (ea > 0.0) {\n          rgb = blendMode(mode4, rgb, s5.rgb, ea);\n          a = a + ea * (1.0 - a);\n      }\n  }\n\n  // Layer 6\n  if (alpha6 > 0.0) {\n      vec4 s6 = IMG_THIS_PIXEL(t6);\n      float ea = s6.a * alpha6;\n      if (ea > 0.0) {\n          rgb = blendMode(mode5, rgb, s6.rgb, ea);\n          a = a + ea * (1.0 - a);\n      }\n  }\n\n  // Layer 7\n  if (alpha7 > 0.0) {\n      vec4 s7 = IMG_THIS_PIXEL(t7);\n      float ea = s7.a * alpha7;\n      if (ea > 0.0) {\n          rgb = blendMode(mode6, rgb, s7.rgb, ea);\n          a = a + ea * (1.0 - a);\n      }\n  }\n\n  // Layer 8\n  if (alpha8 > 0.0) {\n      vec4 s8 = IMG_THIS_PIXEL(t8);\n      float ea = s8.a * alpha8;\n      if (ea > 0.0) {\n          rgb = blendMode(mode7, rgb, s8.rgb, ea);\n          a = a + ea * (1.0 - a);\n      }\n  }\n\n  gl_FragColor = vec4(rgb, a);\n}","Root":"<LIBRARY>:packages/default/Presets/GLSL_shaders/utility/Video Mixer.fs","Inlets":[{"uuid":"5ac86198-2d03-4830-9e41-a6d529922d29","ObjectName":"Inlet","id":0,"Hidden":false,"Custom":"t1","Exposed":"t1","Format":0,"Filter":2,"AddressMode":1},{"uuid":"5ac86198-2d03-4830-9e41-a6d529922d29","ObjectName":"Inlet","id":1,"Hidden":false,"Custom":"t2","Exposed":"t2","Format":0,"Filter":2,"AddressMode":1},{"uuid":"5ac86198-2d03-4830-9e41-a6d529922d29","ObjectName":"Inlet","id":2,"Hidden":false,"Custom":"t3","Exposed":"t3","Format":0,"Filter":2,"AddressMode":1},{"uuid":"5ac86198-2d03-4830-9e41-a6d529922d29","ObjectName":"Inlet","id":3,"Hidden":false,"Custom":"t4","Exposed":"t4","Format":0,"Filter":2,"AddressMode":1},{"uuid":"5ac86198-2d03-4830-9e41-a6d529922d29","ObjectName":"Inlet","id":4,"Hidden":false,"Custom":"t5","Exposed":"t5","Format":0,"Filter":2,"AddressMode":1},{"uuid":"5ac86198-2d03-4830-9e41-a6d529922d29","ObjectName":"Inlet","id":5,"Hidden":false,"Custom":"t6","Exposed":"t6","Format":0,"Filter":2,"AddressMode":1},{"uuid":"5ac86198-2d03-4830-9e41-a6d529922d29","ObjectName":"Inlet","id":6,"Hidden":false,"Custom":"t7","Exposed":"t7","Format":0,"Filter":2,"AddressMode":1},{"uuid":"5ac86198-2d03-4830-9e41-a6d529922d29","ObjectName":"Inlet","id":7,"Hidden":false,"Custom":"t8","Exposed":"t8","Format":0,"Filter":2,"AddressMode":1},{"uuid":"af2b4fc3-aecb-4c15-a5aa-1c573a239925","ObjectName":"Inlet","id":8,"Hidden":true,"Custom":"alpha1","Exposed":"alpha1","Value":{"Float":1.0},"Init":{"Float":1.0},"Domain":{"Float":{"Min":0.0,"Max":1.0}}},{"uuid":"af2b4fc3-aecb-4c15-a5aa-1c573a239925","ObjectName":"Inlet","id":9,"Hidden":true,"Custom":"alpha2","Exposed":"alpha2","Value":{"Float":1.0},"Init":{"Float":0.0},"Domain":{"Float":{"Min":0.0,"Max":1.0}}},{"uuid":"af2b4fc3-aecb-4c15-a5aa-1c573a239925","ObjectName":"Inlet","id":10,"Hidden":true,"Custom":"alpha3","Exposed":"alpha3","Value":{"Float":0.0},"Init":{"Float":0.0},"Domain":{"Float":{"Min":0.0,"Max":1.0}}},{"uuid":"af2b4fc3-aecb-4c15-a5aa-1c573a239925","ObjectName":"Inlet","id":11,"Hidden":true,"Custom":"alpha4","Exposed":"alpha4","Value":{"Float":0.0},"Init":{"Float":0.0},"Domain":{"Float":{"Min":0.0,"Max":1.0}}},{"uuid":"af2b4fc3-aecb-4c15-a5aa-1c573a239925","ObjectName":"Inlet","id":12,"Hidden":true,"Custom":"alpha5","Exposed":"alpha5","Value":{"Float":0.0},"Init":{"Float":0.0},"Domain":{"Float":{"Min":0.0,"Max":1.0}}},{"uuid":"af2b4fc3-aecb-4c15-a5aa-1c573a239925","ObjectName":"Inlet","id":13,"Hidden":true,"Custom":"alpha6","Exposed":"alpha6","Value":{"Float":0.0},"Init":{"Float":0.0},"Domain":{"Float":{"Min":0.0,"Max":1.0}}},{"uuid":"af2b4fc3-aecb-4c15-a5aa-1c573a239925","ObjectName":"Inlet","id":14,"Hidden":true,"Custom":"alpha7","Exposed":"alpha7","Value":{"Float":0.0},"Init":{"Float":0.0},"Domain":{"Float":{"Min":0.0,"Max":1.0}}},{"uuid":"af2b4fc3-aecb-4c15-a5aa-1c573a239925","ObjectName":"Inlet","id":15,"Hidden":true,"Custom":"alpha8","Exposed":"alpha8","Value":{"Float":0.0},"Init":{"Float":0.0},"Domain":{"Float":{"Min":0.0,"Max":1.0}}},{"uuid":"485680cc-b8b9-4a01-acc7-3e8334bdc016","ObjectName":"Inlet","id":16,"Hidden":true,"Custom":"mode1","Exposed":"mode1","Value":{"Int":17},"Init":{"Int":18},"Domain":{"Int":{"Values":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]}},"Values":[["Add",{"Int":1}],["Average",{"Int":2}],["Color Burn",{"Int":3}],["Color Dodge",{"Int":4}],["Darken",{"Int":5}],["Difference",{"Int":6}],["Exclusion",{"Int":7}],["Glow",{"Int":8}],["Hard Light",{"Int":9}],["Hard Mix",{"Int":10}],["Lighten",{"Int":11}],["Linear Burn",{"Int":12}],["Linear Dodge",{"Int":13}],["Linear Light",{"Int":14}],["Multiply",{"Int":15}],["Negation",{"Int":16}],["Normal",{"Int":17}],["Overlay",{"Int":18}],["Phoenix",{"Int":19}],["Pin Light",{"Int":20}],["Reflect",{"Int":21}],["Screen",{"Int":22}],["Soft Light",{"Int":23}],["Subtract",{"Int":24}],["Vivid Light",{"Int":25}]],"FolderPort":"","FileExtensions":""},{"uuid":"485680cc-b8b9-4a01-acc7-3e8334bdc016","ObjectName":"Inlet","id":17,"Hidden":true,"Custom":"mode2","Exposed":"mode2","Value":{"Int":17},"Init":{"Int":18},"Domain":{"Int":{"Values":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]}},"Values":[["Add",{"Int":1}],["Average",{"Int":2}],["Color Burn",{"Int":3}],["Color Dodge",{"Int":4}],["Darken",{"Int":5}],["Difference",{"Int":6}],["Exclusion",{"Int":7}],["Glow",{"Int":8}],["Hard Light",{"Int":9}],["Hard Mix",{"Int":10}],["Lighten",{"Int":11}],["Linear Burn",{"Int":12}],["Linear Dodge",{"Int":13}],["Linear Light",{"Int":14}],["Multiply",{"Int":15}],["Negation",{"Int":16}],["Normal",{"Int":17}],["Overlay",{"Int":18}],["Phoenix",{"Int":19}],["Pin Light",{"Int":20}],["Reflect",{"Int":21}],["Screen",{"Int":22}],["Soft Light",{"Int":23}],["Subtract",{"Int":24}],["Vivid Light",{"Int":25}]],"FolderPort":"","FileExtensions":""},{"uuid":"485680cc-b8b9-4a01-acc7-3e8334bdc016","ObjectName":"Inlet","id":18,"Hidden":true,"Custom":"mode3","Exposed":"mode3","Value":{"Int":18},"Init":{"Int":18},"Domain":{"Int":{"Values":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]}},"Values":[["Add",{"Int":1}],["Average",{"Int":2}],["Color Burn",{"Int":3}],["Color Dodge",{"Int":4}],["Darken",{"Int":5}],["Difference",{"Int":6}],["Exclusion",{"Int":7}],["Glow",{"Int":8}],["Hard Light",{"Int":9}],["Hard Mix",{"Int":10}],["Lighten",{"Int":11}],["Linear Burn",{"Int":12}],["Linear Dodge",{"Int":13}],["Linear Light",{"Int":14}],["Multiply",{"Int":15}],["Negation",{"Int":16}],["Normal",{"Int":17}],["Overlay",{"Int":18}],["Phoenix",{"Int":19}],["Pin Light",{"Int":20}],["Reflect",{"Int":21}],["Screen",{"Int":22}],["Soft Light",{"Int":23}],["Subtract",{"Int":24}],["Vivid Light",{"Int":25}]],"FolderPort":"","FileExtensions":""},{"uuid":"485680cc-b8b9-4a01-acc7-3e8334bdc016","ObjectName":"Inlet","id":19,"Hidden":true,"Custom":"mode4","Exposed":"mode4","Value":{"Int":18},"Init":{"Int":18},"Domain":{"Int":{"Values":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]}},"Values":[["Add",{"Int":1}],["Average",{"Int":2}],["Color Burn",{"Int":3}],["Color Dodge",{"Int":4}],["Darken",{"Int":5}],["Difference",{"Int":6}],["Exclusion",{"Int":7}],["Glow",{"Int":8}],["Hard Light",{"Int":9}],["Hard Mix",{"Int":10}],["Lighten",{"Int":11}],["Linear Burn",{"Int":12}],["Linear Dodge",{"Int":13}],["Linear Light",{"Int":14}],["Multiply",{"Int":15}],["Negation",{"Int":16}],["Normal",{"Int":17}],["Overlay",{"Int":18}],["Phoenix",{"Int":19}],["Pin Light",{"Int":20}],["Reflect",{"Int":21}],["Screen",{"Int":22}],["Soft Light",{"Int":23}],["Subtract",{"Int":24}],["Vivid Light",{"Int":25}]],"FolderPort":"","FileExtensions":""},{"uuid":"485680cc-b8b9-4a01-acc7-3e8334bdc016","ObjectName":"Inlet","id":20,"Hidden":true,"Custom":"mode5","Exposed":"mode5","Value":{"Int":18},"Init":{"Int":18},"Domain":{"Int":{"Values":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]}},"Values":[["Add",{"Int":1}],["Average",{"Int":2}],["Color Burn",{"Int":3}],["Color Dodge",{"Int":4}],["Darken",{"Int":5}],["Difference",{"Int":6}],["Exclusion",{"Int":7}],["Glow",{"Int":8}],["Hard Light",{"Int":9}],["Hard Mix",{"Int":10}],["Lighten",{"Int":11}],["Linear Burn",{"Int":12}],["Linear Dodge",{"Int":13}],["Linear Light",{"Int":14}],["Multiply",{"Int":15}],["Negation",{"Int":16}],["Normal",{"Int":17}],["Overlay",{"Int":18}],["Phoenix",{"Int":19}],["Pin Light",{"Int":20}],["Reflect",{"Int":21}],["Screen",{"Int":22}],["Soft Light",{"Int":23}],["Subtract",{"Int":24}],["Vivid Light",{"Int":25}]],"FolderPort":"","FileExtensions":""},{"uuid":"485680cc-b8b9-4a01-acc7-3e8334bdc016","ObjectName":"Inlet","id":21,"Hidden":true,"Custom":"mode6","Exposed":"mode6","Value":{"Int":18},"Init":{"Int":18},"Domain":{"Int":{"Values":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]}},"Values":[["Add",{"Int":1}],["Average",{"Int":2}],["Color Burn",{"Int":3}],["Color Dodge",{"Int":4}],["Darken",{"Int":5}],["Difference",{"Int":6}],["Exclusion",{"Int":7}],["Glow",{"Int":8}],["Hard Light",{"Int":9}],["Hard Mix",{"Int":10}],["Lighten",{"Int":11}],["Linear Burn",{"Int":12}],["Linear Dodge",{"Int":13}],["Linear Light",{"Int":14}],["Multiply",{"Int":15}],["Negation",{"Int":16}],["Normal",{"Int":17}],["Overlay",{"Int":18}],["Phoenix",{"Int":19}],["Pin Light",{"Int":20}],["Reflect",{"Int":21}],["Screen",{"Int":22}],["Soft Light",{"Int":23}],["Subtract",{"Int":24}],["Vivid Light",{"Int":25}]],"FolderPort":"","FileExtensions":""},{"uuid":"485680cc-b8b9-4a01-acc7-3e8334bdc016","ObjectName":"Inlet","id":22,"Hidden":true,"Custom":"mode7","Exposed":"mode7","Value":{"Int":18},"Init":{"Int":18},"Domain":{"Int":{"Values":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]}},"Values":[["Add",{"Int":1}],["Average",{"Int":2}],["Color Burn",{"Int":3}],["Color Dodge",{"Int":4}],["Darken",{"Int":5}],["Difference",{"Int":6}],["Exclusion",{"Int":7}],["Glow",{"Int":8}],["Hard Light",{"Int":9}],["Hard Mix",{"Int":10}],["Lighten",{"Int":11}],["Linear Burn",{"Int":12}],["Linear Dodge",{"Int":13}],["Linear Light",{"Int":14}],["Multiply",{"Int":15}],["Negation",{"Int":16}],["Normal",{"Int":17}],["Overlay",{"Int":18}],["Phoenix",{"Int":19}],["Pin Light",{"Int":20}],["Reflect",{"Int":21}],["Screen",{"Int":22}],["Soft Light",{"Int":23}],["Subtract",{"Int":24}],["Vivid Light",{"Int":25}]],"FolderPort":"","FileExtensions":""}],"Outlets":[{"uuid":"f1c71046-b754-49a5-8e66-d01374773dfc","ObjectName":"Outlet","id":1,"Hidden":false,"Custom":"Texture Out","Exposed":"texture out","Address":"Window:/"}]},{"uuid":"ebae143d-ff33-422c-afdc-150c7738db1f","ObjectName":"string_join","id":4,"Metadata":{"ScriptingName":"Join strings","Comment":"","Color":"Transparent1","Label":"","Touched":true},"Duration":10584000000,"Height":300.0,"StartOffset":0,"LoopDuration":10584000000,"Pos":[-368.13869394177937,-233.41935743135372],"Size":[236.0,85.0],"Loops":false,"FoldMode":0,"Inlets":[{"uuid":"769dd38a-bfb3-4dc6-b52a-b6abb7afe2a3","ObjectName":"Inlet","id":0,"Hidden":false,"Custom":"Parts","Exposed":"parts"},{"uuid":"9ae797ea-d94c-4792-acec-9ec1932bae5d","ObjectName":"Inlet","id":1,"Hidden":true,"Custom":"Separator","Exposed":"separator","Value":{"String":""},"Init":{"String":","},"Domain":{}},{"uuid":"238399a0-7e81-47e3-896f-08e8856e2973","ObjectName":"Inlet","id":2,"Hidden":true,"Custom":"Max bytes","Exposed":"max bytes","Value":{"Int":1048576},"Init":{"Int":1048576},"Domain":{"Int":{"Min":1,"Max":1048576}}}],"Outlets":[{"uuid":"cff96158-cc72-46d7-99dc-b6038171375b","ObjectName":"Outlet","id":0,"Hidden":false,"Custom":"Text","Exposed":"text"},{"uuid":"cff96158-cc72-46d7-99dc-b6038171375b","ObjectName":"Outlet","id":1,"Hidden":false,"Custom":"Error","Exposed":"error"}]},{"uuid":"3bbf74cd-55c1-473f-a11c-25ffec5b5c71","ObjectName":"avnd_combine","id":5,"Metadata":{"ScriptingName":"Combine inlets","Comment":"","Color":"Transparent1","Label":"","Touched":true},"Duration":10584000000,"Height":300.0,"StartOffset":0,"LoopDuration":10584000000,"Pos":[-521.3337670243845,-234.52148025928602],"Size":[144.0,72.0],"Loops":false,"FoldMode":0,"Inlets":[{"uuid":"3abf74cd-55c1-473f-a11c-25ffec5b5c71","ObjectName":"Inlet","id":0,"Hidden":true,"Custom":"Input count","Exposed":"input count","Value":{"Int":2},"Init":{"Int":2},"Domain":{"Int":{"Min":0,"Max":1024}}},{"uuid":"769dd38a-bfb3-4dc6-b52a-b6abb7afe2a3","ObjectName":"Inlet","id":11000,"Hidden":false,"Custom":"Input 0","Exposed":"input 0"},{"uuid":"769dd38a-bfb3-4dc6-b52a-b6abb7afe2a3","ObjectName":"Inlet","id":11001,"Hidden":false,"Custom":"Input 1","Exposed":"input 1","Address":"LLM:/chat/prompt"}],"Outlets":[{"uuid":"cff96158-cc72-46d7-99dc-b6038171375b","ObjectName":"Outlet","id":0,"Hidden":false,"Custom":"Output","Exposed":"output"}],"DynamicInlets":[["unnamed",2]]},{"uuid":"4770e6cd-53a8-4943-b13d-7cb55af429b3","ObjectName":"avnd_string","id":6,"Metadata":{"ScriptingName":"String","Comment":"","Color":"Transparent1","Label":"","Touched":true},"Duration":10584000000,"Height":300.0,"StartOffset":0,"LoopDuration":10584000000,"Pos":[-766.0050348253808,-199.25354976544872],"Size":[230.0,38.0],"Loops":false,"FoldMode":0,"Inlets":[{"uuid":"9ae797ea-d94c-4792-acec-9ec1932bae5d","ObjectName":"Inlet","id":0,"Hidden":true,"Custom":"Value","Exposed":"value","Value":{"String":"Human: "},"Init":{"String":"text"},"Domain":{}}],"Outlets":[{"uuid":"cff96158-cc72-46d7-99dc-b6038171375b","ObjectName":"Outlet","id":0,"Hidden":false,"Custom":"Out","Exposed":"out"}]},{"uuid":"7be51631-fb4b-4152-9ca7-86fdafa8a989","ObjectName":"Display","id":7,"Metadata":{"ScriptingName":"Text Box","Comment":"","Color":"Transparent1","Label":"","Touched":true},"Duration":10584000000,"Height":300.0,"StartOffset":0,"LoopDuration":10584000000,"Pos":[-774.0398957039631,-409.51491572673956],"Size":[458.0,73.0],"Loops":false,"FoldMode":0,"Inlets":[{"uuid":"9ae797ea-d94c-4792-acec-9ec1932bae5d","ObjectName":"Inlet","id":0,"Hidden":true,"Custom":"in","Exposed":"in","Value":{"String":"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n<html><head><meta name=\"qrichtext\" content=\"1\" /><meta charset=\"utf-8\" /><style type=\"text/css\">\np, li { white-space: pre-wrap; }\nhr { height: 1px; border-width: 0; }\nli.unchecked::marker { content: \"\\2610\"; }\nli.checked::marker { content: \"\\2612\"; }\n</style></head><body style=\" font-family:'Ubuntu'; font-size:12px; font-weight:400; font-style:normal;\">\n<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">This example shows how to use a HTTP device to communicate over a <br />standard API. <br />In this case, this is the classic LLM OpenAI API, implemented by many tools.<br />You can for instance use llama.cpp and llama-server to host a compatible local LLM<br />such as Qwen, Gemma, etc.</p></body></html>"},"Init":{"String":"(Double-click to edit)"},"Domain":{}}],"Outlets":[]},{"uuid":"7be51631-fb4b-4152-9ca7-86fdafa8a989","ObjectName":"Display","id":8,"Metadata":{"ScriptingName":"Text Box.1","Comment":"","Color":"Transparent1","Label":"","Touched":true},"Duration":10584000000,"Height":300.0,"StartOffset":0,"LoopDuration":10584000000,"Pos":[-767.9677322713035,-284.24695419447494],"Size":[384.0,21.0],"Loops":false,"FoldMode":0,"Inlets":[{"uuid":"9ae797ea-d94c-4792-acec-9ec1932bae5d","ObjectName":"Inlet","id":0,"Hidden":true,"Custom":"in","Exposed":"in","Value":{"String":"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n<html><head><meta name=\"qrichtext\" content=\"1\" /><meta charset=\"utf-8\" /><style type=\"text/css\">\np, li { white-space: pre-wrap; }\nhr { height: 1px; border-width: 0; }\nli.unchecked::marker { content: \"\\2610\"; }\nli.checked::marker { content: \"\\2612\"; }\n</style></head><body style=\" font-family:'Ubuntu'; font-size:12px; font-weight:400; font-style:normal;\">\n<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Basic string utilities to create the string &quot;Human: &lt;what you typed&gt;&quot;</p></body></html>"},"Init":{"String":"(Double-click to edit)"},"Domain":{}}],"Outlets":[]},{"uuid":"7be51631-fb4b-4152-9ca7-86fdafa8a989","ObjectName":"Display","id":9,"Metadata":{"ScriptingName":"Text Box.2","Comment":"","Color":"Transparent1","Label":"","Touched":true},"Duration":10584000000,"Height":300.0,"StartOffset":0,"LoopDuration":10584000000,"Pos":[-111.57470297328652,-303.4339901397039],"Size":[77.0,21.0],"Loops":false,"FoldMode":0,"Inlets":[{"uuid":"9ae797ea-d94c-4792-acec-9ec1932bae5d","ObjectName":"Inlet","id":0,"Hidden":true,"Custom":"in","Exposed":"in","Value":{"String":"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n<html><head><meta name=\"qrichtext\" content=\"1\" /><meta charset=\"utf-8\" /><style type=\"text/css\">\np, li { white-space: pre-wrap; }\nhr { height: 1px; border-width: 0; }\nli.unchecked::marker { content: \"\\2610\"; }\nli.checked::marker { content: \"\\2612\"; }\n</style></head><body style=\" font-family:'Ubuntu'; font-size:12px; font-weight:400; font-style:normal;\">\n<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Render text</p></body></html>"},"Init":{"String":"(Double-click to edit)"},"Domain":{}}],"Outlets":[]},{"uuid":"7be51631-fb4b-4152-9ca7-86fdafa8a989","ObjectName":"Display","id":10,"Metadata":{"ScriptingName":"Text Box.3","Comment":"","Color":"Transparent1","Label":"","Touched":true},"Duration":10584000000,"Height":300.0,"StartOffset":0,"LoopDuration":10584000000,"Pos":[-114.85486806737487,-419.0438838786798],"Size":[415.0,99.0],"Loops":false,"FoldMode":0,"Inlets":[{"uuid":"9ae797ea-d94c-4792-acec-9ec1932bae5d","ObjectName":"Inlet","id":0,"Hidden":true,"Custom":"in","Exposed":"in","Value":{"String":"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n<html><head><meta name=\"qrichtext\" content=\"1\" /><meta charset=\"utf-8\" /><style type=\"text/css\">\np, li { white-space: pre-wrap; }\nhr { height: 1px; border-width: 0; }\nli.unchecked::marker { content: \"\\2610\"; }\nli.checked::marker { content: \"\\2612\"; }\n</style></head><body style=\" font-family:'Ubuntu'; font-size:12px; font-weight:400; font-style:normal;\">\n<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Once your local AI server is up and running, you can control the messages </p>\n<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">sent to it: <br />LLM:/config/host is the ip / port (likely http://127.0.0.1:8080), <br />LLM:/chat/prompt is the message you want to send, <br />LLM:/chat/response is the answer from the LLM. <br /><br />The context is preserved across calls, use &quot;clear&quot; to clear it.</p></body></html>"},"Init":{"String":"(Double-click to edit)"},"Domain":{}}],"Outlets":[]},{"uuid":"de035912-5b03-49a8-bc4d-b2cba68e21d9","ObjectName":"Scenario","id":12,"Metadata":{"ScriptingName":"Scenario","Comment":"","Color":"Transparent1","Label":"","Touched":true},"Duration":10584000000,"Height":300.0,"StartOffset":0,"LoopDuration":10584000000,"Pos":[-421.7591866952183,2.182995879078531],"Size":[218.07098311646718,276.8374776397145],"Loops":false,"FoldMode":2,"Inlet":{"uuid":"a1574bb0-cbd4-4c7d-9417-0c25cfd1187b","ObjectName":"Inlet","id":0,"Hidden":false,"Custom":"In","Exposed":"in"},"Outlet":{"uuid":"a1d97535-18ac-444a-8417-0cbc1692d897","ObjectName":"Outlet","id":0,"Hidden":false,"Custom":"Out","Exposed":"out","GainInlet":{"uuid":"9a13fb32-269a-47bf-99a9-930188c1f19c","ObjectName":"Inlet","id":10000,"Hidden":false,"Custom":"Gain","Exposed":"gain","Value":{},"Init":{},"Domain":{"Float":{"Min":0.0,"Max":1.0}}},"PanInlet":{"uuid":"9a13fb32-269a-47bf-99a9-930188c1f19c","ObjectName":"Inlet","id":10001,"Hidden":false,"Custom":"Pan","Exposed":"pan","Value":{},"Init":{},"Domain":{}},"Gain":1.0,"Pan":[1.0,1.0],"Propagate":true},"StartTimeNodeId":0,"StartEventId":0,"StartStateId":0,"Exclusive":false,"TimeNodes":[{"ObjectName":"Scenario::TimeSyncModel","id":0,"Metadata":{"ScriptingName":"Sync.start","Comment":"","Color":"Gray","Label":"","Touched":true},"Date":0,"Events":[0],"MusicalSync":-1.0,"AutoTrigger":false,"Start":true,"Active":false,"Expression":" { true == false } "},{"ObjectName":"Scenario::TimeSyncModel","id":1,"Metadata":{"ScriptingName":"Sync.1","Comment":"","Color":"Gray","Label":"","Touched":false},"Date":1571091272,"Events":[1],"MusicalSync":-1.0,"AutoTrigger":true,"Start":true,"Active":true,"Expression":" { %LLM:/chat/response% impulse } "},{"ObjectName":"Scenario::TimeSyncModel","id":2,"Metadata":{"ScriptingName":"Sync.atom13","Comment":"","Color":"Gray","Label":"","Touched":true},"Date":8627091272,"Events":[2],"MusicalSync":-1.0,"AutoTrigger":false,"Start":false,"Active":false,"Expression":" { true == false } "}],"Events":[{"ObjectName":"Scenario::EventModel","id":0,"Metadata":{"ScriptingName":"Event.start","Comment":"","Color":"Emphasis4","Label":"","Touched":true},"TimeNode":0,"States":[0],"Condition":"","Date":0,"Offset":0},{"ObjectName":"Scenario::EventModel","id":1,"Metadata":{"ScriptingName":"Event.mutt1","Comment":"","Color":"Emphasis4","Label":"","Touched":true},"TimeNode":1,"States":[1],"Condition":"","Date":1571091272,"Offset":0},{"ObjectName":"Scenario::EventModel","id":2,"Metadata":{"ScriptingName":"Event.eyes16","Comment":"","Color":"Emphasis4","Label":"","Touched":true},"TimeNode":2,"States":[2],"Condition":"","Date":8627091272,"Offset":0}],"States":[{"ObjectName":"Scenario::StateModel","id":0,"Metadata":{"ScriptingName":"State.start","Comment":"","Color":"Base1","Label":"","Touched":true},"Event":0,"PreviousConstraint":null,"NextConstraint":null,"HeightPercentage":0.02,"Messages":{"Name":"","Accessors":[],"Unit":"none","Previous":[],"Following":[],"User":null,"Priorities":[1,2,0]},"Controls":[],"StateProcesses":[]},{"ObjectName":"Scenario::StateModel","id":1,"Metadata":{"ScriptingName":"State.bias23","Comment":"","Color":"Base1","Label":"","Touched":true},"Event":1,"PreviousConstraint":null,"NextConstraint":1,"HeightPercentage":0.13619182580897976,"Messages":{"Name":"","Accessors":[],"Unit":"none","Previous":[],"Following":[],"User":null,"Priorities":[1,2,0]},"Controls":[],"StateProcesses":[]},{"ObjectName":"Scenario::StateModel","id":2,"Metadata":{"ScriptingName":"State.hill66","Comment":"","Color":"Base1","Label":"","Touched":true},"Event":2,"PreviousConstraint":1,"NextConstraint":null,"HeightPercentage":0.13619182580897976,"Messages":{"Name":"","Accessors":[],"Unit":"none","Previous":[],"Following":[],"User":null,"Priorities":[1,2,0]},"Controls":[],"StateProcesses":[]}],"Constraints":[{"ObjectName":"Scenario::IntervalModel","id":1,"Metadata":{"ScriptingName":"Ramp","Comment":"","Color":"Transparent1","Label":"","Touched":true},"Inlet":{"uuid":"a1574bb0-cbd4-4c7d-9417-0c25cfd1187b","ObjectName":"Inlet","id":0,"Hidden":false,"Custom":"Audio In","Exposed":"audio in"},"Outlet":{"uuid":"a1d97535-18ac-444a-8417-0cbc1692d897","ObjectName":"Outlet","id":0,"Hidden":false,"Custom":"Audio Out","Exposed":"audio out","GainInlet":{"uuid":"9a13fb32-269a-47bf-99a9-930188c1f19c","ObjectName":"Inlet","id":10000,"Hidden":false,"Custom":"Gain","Exposed":"gain","Value":{},"Init":{},"Domain":{"Float":{"Min":0.0,"Max":1.0}}},"PanInlet":{"uuid":"9a13fb32-269a-47bf-99a9-930188c1f19c","ObjectName":"Inlet","id":10001,"Hidden":false,"Custom":"Pan","Exposed":"pan","Value":{},"Init":{},"Domain":{}},"Gain":1.0,"Pan":[1.0,1.0],"Propagate":true},"Processes":[{"uuid":"d2a67bd8-5d3f-404e-b6e9-e350cf2a833f","ObjectName":"Automation","id":1,"Metadata":{"ScriptingName":"Automation (float)","Comment":"","Color":"Transparent1","Label":"","Touched":true},"Duration":7056000000,"Height":300.0,"StartOffset":0,"LoopDuration":7056000000,"Pos":[40.0,40.0],"Size":[200.0,100.0],"Loops":false,"FoldMode":0,"Outlet":{"uuid":"047e4cc2-4d99-4e8b-bf98-206018d02274","ObjectName":"Outlet","id":0,"Hidden":false,"Custom":"Out","Exposed":"out","MinInlet":{"uuid":"af2b4fc3-aecb-4c15-a5aa-1c573a239925","ObjectName":"Inlet","id":0,"Hidden":false,"Custom":"Min","Exposed":"min","Value":{"Float":0.0},"Init":{},"Domain":{}},"MaxInlet":{"uuid":"af2b4fc3-aecb-4c15-a5aa-1c573a239925","ObjectName":"Inlet","id":1,"Hidden":false,"Custom":"Max","Exposed":"max","Value":{"Float":1.0},"Init":{},"Domain":{}}},"Curve":{"ObjectName":"CurveModel","id":45345,"Segments":[{"uuid":"1e7cb83f-4e47-4b14-814d-2242a9c75991","ObjectName":"CurveSegmentModel","id":1,"Previous":null,"Following":null,"Start":[0.0,0.0],"End":[1.0,1.0],"Power":1.0}]},"Tween":false}],"SmallViewRack":[{"Processes":[1],"Process":1,"Height":200.0,"Nodal":false}],"FullViewRack":[{"Process":1,"Nodal":false}],"DefaultDuration":7056000000,"MinDuration":7056000000,"MaxDuration":7056000000,"GuiDuration":7761600000,"Speed":1.0,"Rigidity":true,"MinNull":false,"MaxInf":false,"Signatures":[],"StartState":1,"EndState":2,"StartDate":1571091272,"HeightPercentage":0.13619182580897976,"NodalSlotHeight":100.0,"QuantizationRate":-1.0,"Zoom":10080000.0,"Center":0,"ViewMode":0,"SmallViewShown":true,"HasSignature":false}],"Comments":[]}],"SmallViewRack":[{"Processes":[1,2,3,4,5,6,7,8,9,10],"Process":null,"Height":200.0,"Nodal":true},{"Processes":[12],"Process":12,"Height":200.0,"Nodal":false}],"FullViewRack":[{"Process":0,"Nodal":true},{"Process":12,"Nodal":false}],"DefaultDuration":10584000000,"MinDuration":10584000000,"MaxDuration":11113200000,"GuiDuration":11642400000,"Speed":1.0,"Rigidity":false,"MinNull":false,"MaxInf":true,"Signatures":[[0,[4,4]]],"StartState":0,"EndState":1,"StartDate":0,"HeightPercentage":0.0,"NodalSlotHeight":100.0,"QuantizationRate":-1.0,"Zoom":7510798.558199507,"Center":3368784774,"NodalCenter":[-592.933166473283,-165.51128062380096],"NodalScale":1.6459958624801252,"ViewMode":1,"SmallViewShown":true,"HasSignature":true},"StartTimeNode":{"ObjectName":"Scenario::TimeSyncModel","id":0,"Metadata":{"ScriptingName":"Sync.start","Comment":"","Color":"Gray","Label":"","Touched":true},"Date":0,"Events":[0],"MusicalSync":-1.0,"AutoTrigger":false,"Start":true,"Active":false,"Expression":" { true == false } "},"EndTimeNode":{"ObjectName":"Scenario::TimeSyncModel","id":1,"Metadata":{"ScriptingName":"Sync.end","Comment":"","Color":"Gray","Label":"","Touched":true},"Date":10584000000,"Events":[1],"MusicalSync":-1.0,"AutoTrigger":false,"Start":false,"Active":true,"Expression":" { true == false } "},"StartEvent":{"ObjectName":"Scenario::EventModel","id":0,"Metadata":{"ScriptingName":"Event.start","Comment":"","Color":"Emphasis4","Label":"","Touched":true},"TimeNode":0,"States":[0],"Condition":"","Date":0,"Offset":0},"EndEvent":{"ObjectName":"Scenario::EventModel","id":1,"Metadata":{"ScriptingName":"Event.end","Comment":"","Color":"Emphasis4","Label":"","Touched":true},"TimeNode":1,"States":[1],"Condition":"","Date":10584000000,"Offset":0},"StartState":{"ObjectName":"Scenario::StateModel","id":0,"Metadata":{"ScriptingName":"State.start","Comment":"","Color":"Base1","Label":"","Touched":true},"Event":0,"PreviousConstraint":null,"NextConstraint":0,"HeightPercentage":0.0,"Messages":{"Name":"","Accessors":[],"Unit":"none","Previous":[],"Following":[],"User":null,"Priorities":[1,2,0]},"Controls":[],"StateProcesses":[]},"EndState":{"ObjectName":"Scenario::StateModel","id":1,"Metadata":{"ScriptingName":"State.end","Comment":"","Color":"Base1","Label":"","Touched":true},"Event":1,"PreviousConstraint":0,"NextConstraint":null,"HeightPercentage":0.0,"Messages":{"Name":"","Accessors":[],"Unit":"none","Previous":[],"Following":[],"User":null,"Priorities":[1,2,0]},"Controls":[],"StateProcesses":[]}},"Speed":1.0,"Cables":[{"ObjectName":"Process::Cable","id":1,"Type":0,"Source":[{"ObjectName":"Scenario::ScenarioDocumentModel","ObjectId":1},{"ObjectName":"Scenario::BaseScenario","ObjectId":0},{"ObjectName":"Scenario::IntervalModel","ObjectId":0},{"ObjectName":"Javascript","ObjectId":2},{"ObjectName":"Outlet","ObjectId":0}],"Sink":[{"ObjectName":"Scenario::ScenarioDocumentModel","ObjectId":1},{"ObjectName":"Scenario::BaseScenario","ObjectId":0},{"ObjectName":"Scenario::IntervalModel","ObjectId":0},{"ObjectName":"gfxProcess","ObjectId":3},{"ObjectName":"Inlet","ObjectId":0}]},{"ObjectName":"Process::Cable","id":2,"Type":0,"Source":[{"ObjectName":"Scenario::ScenarioDocumentModel","ObjectId":1},{"ObjectName":"Scenario::BaseScenario","ObjectId":0},{"ObjectName":"Scenario::IntervalModel","ObjectId":0},{"ObjectName":"Javascript","ObjectId":1},{"ObjectName":"Outlet","ObjectId":0}],"Sink":[{"ObjectName":"Scenario::ScenarioDocumentModel","ObjectId":1},{"ObjectName":"Scenario::BaseScenario","ObjectId":0},{"ObjectName":"Scenario::IntervalModel","ObjectId":0},{"ObjectName":"gfxProcess","ObjectId":3},{"ObjectName":"Inlet","ObjectId":1}]},{"ObjectName":"Process::Cable","id":3,"Type":0,"Source":[{"ObjectName":"Scenario::ScenarioDocumentModel","ObjectId":1},{"ObjectName":"Scenario::BaseScenario","ObjectId":0},{"ObjectName":"Scenario::IntervalModel","ObjectId":0},{"ObjectName":"avnd_combine","ObjectId":5},{"ObjectName":"Outlet","ObjectId":0}],"Sink":[{"ObjectName":"Scenario::ScenarioDocumentModel","ObjectId":1},{"ObjectName":"Scenario::BaseScenario","ObjectId":0},{"ObjectName":"Scenario::IntervalModel","ObjectId":0},{"ObjectName":"string_join","ObjectId":4},{"ObjectName":"Inlet","ObjectId":0}]},{"ObjectName":"Process::Cable","id":4,"Type":0,"Source":[{"ObjectName":"Scenario::ScenarioDocumentModel","ObjectId":1},{"ObjectName":"Scenario::BaseScenario","ObjectId":0},{"ObjectName":"Scenario::IntervalModel","ObjectId":0},{"ObjectName":"string_join","ObjectId":4},{"ObjectName":"Outlet","ObjectId":0}],"Sink":[{"ObjectName":"Scenario::ScenarioDocumentModel","ObjectId":1},{"ObjectName":"Scenario::BaseScenario","ObjectId":0},{"ObjectName":"Scenario::IntervalModel","ObjectId":0},{"ObjectName":"Javascript","ObjectId":2},{"ObjectName":"Inlet","ObjectId":1}]},{"ObjectName":"Process::Cable","id":5,"Type":0,"Source":[{"ObjectName":"Scenario::ScenarioDocumentModel","ObjectId":1},{"ObjectName":"Scenario::BaseScenario","ObjectId":0},{"ObjectName":"Scenario::IntervalModel","ObjectId":0},{"ObjectName":"avnd_string","ObjectId":6},{"ObjectName":"Outlet","ObjectId":0}],"Sink":[{"ObjectName":"Scenario::ScenarioDocumentModel","ObjectId":1},{"ObjectName":"Scenario::BaseScenario","ObjectId":0},{"ObjectName":"Scenario::IntervalModel","ObjectId":0},{"ObjectName":"avnd_combine","ObjectId":5},{"ObjectName":"Inlet","ObjectId":11000}]},{"ObjectName":"Process::Cable","id":6,"Type":0,"Source":[{"ObjectName":"Scenario::ScenarioDocumentModel","ObjectId":1},{"ObjectName":"Scenario::BaseScenario","ObjectId":0},{"ObjectName":"Scenario::IntervalModel","ObjectId":0},{"ObjectName":"Scenario","ObjectId":12},{"ObjectName":"Scenario::IntervalModel","ObjectId":1},{"ObjectName":"Automation","ObjectId":1},{"ObjectName":"Outlet","ObjectId":0}],"Sink":[{"ObjectName":"Scenario::ScenarioDocumentModel","ObjectId":1},{"ObjectName":"Scenario::BaseScenario","ObjectId":0},{"ObjectName":"Scenario::IntervalModel","ObjectId":0},{"ObjectName":"Javascript","ObjectId":1},{"ObjectName":"Inlet","ObjectId":4}]}],"BusIntervals":[]},"Plugins":[{"uuid":"aac1402f-6851-4a1a-ab50-5f213acd1262","Name":"http llm","Author":"","Description":"Talks to an OpenAI-compatible LLM API over an HTTP device, assembling the request with string utilities.","Url":"","Created":"2026-09-08T17:40:34.082Z","LastSaved":"2026-09-08T18:05:55.889Z","Thumbnail":"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","AutomaticThumbnail":true},{"uuid":"1f923578-08c3-49be-9ba9-69c144ee2e32","Refresh":false,"Reconnect":false,"MidiRatio":1.0},{"uuid":"6e610e1f-9de2-4c36-90dd-0ef570002a21","RootNode":{},"Children":[{"Device":{"Name":"LLM","Protocol":"171095cd-6815-4930-be40-5ffe131eb775","Text":"import Ossia 1.0 as Ossia\n\n/**\n * OpenAI / llama.cpp server compatible LLM client for ossia score.\n *\n * Compatible with: llama.cpp server, Ollama, vLLM, LM Studio,\n * and any server exposing an OpenAI-compatible HTTP API.\n *\n * Setup:\n *   1. Start your LLM server (e.g. llama-server -m model.gguf)\n *   2. Set /config/host to your server URL (default: http://127.0.0.1:8080)\n *   3. Set /config/model to your model name if required\n *\n * Chat (with conversation history):\n *   - Optionally set /config/system_prompt\n *   - Write a message to /chat/prompt to get a response in /chat/response\n *   - Conversation history is maintained across messages\n *   - Send an impulse to /chat/clear to reset the conversation\n *\n * Text completion (single-shot, no history):\n *   - Write text to /completion/prompt to get a continuation in /completion/response\n *\n * Health check:\n *   - Send an impulse to /health/check to query server status\n *\n * Note: Custom HTTP headers (Authorization, Content-Type) are not set by this\n * device. This works out-of-the-box with local servers (llama.cpp, Ollama, etc.)\n * which do not enforce strict header checks. For cloud APIs requiring Bearer\n * tokens, configure a reverse proxy that injects the Authorization header.\n */\nOssia.Http\n{\n  property var chatHistory: []\n\n  function toStr(v) { var s = Device.toValue(v); return (s !== undefined && s !== null) ? \"\" + s : \"\"; }\n\n  function buildChatMessages(userPrompt)\n  {\n    var messages = [];\n    var sys = Device.read(\"/config/system_prompt\");\n    if (sys && sys.length > 0)\n      messages.push({ role: \"system\", content: \"\" + sys });\n\n    for (var i = 0; i < chatHistory.length; i++)\n      messages.push(chatHistory[i]);\n\n    messages.push({ role: \"user\", content: userPrompt });\n    return messages;\n  }\n\n  function samplingParams()\n  {\n    return {\n      temperature:       Device.read(\"/config/temperature\"),\n      max_tokens:        Device.read(\"/config/max_tokens\"),\n      top_p:             Device.read(\"/config/top_p\"),\n      frequency_penalty: Device.read(\"/config/frequency_penalty\"),\n      presence_penalty:  Device.read(\"/config/presence_penalty\"),\n      stream:            false\n    };\n  }\n\n  function createTree()\n  {\n    return [\n      // ── Configuration ─────────────────────────────────────────\n      {\n        name: \"config\",\n        children: [\n          {\n            name: \"host\",\n            type: Ossia.Type.String,\n            value: \"http://127.0.0.1:8080\",\n            description: \"LLM server base URL\"\n          },\n          {\n            name: \"model\",\n            type: Ossia.Type.String,\n            value: \"default\",\n            description: \"Model name or alias\"\n          },\n          {\n            name: \"system_prompt\",\n            type: Ossia.Type.String,\n            value: \"\",\n            description: \"System prompt prepended to chat messages\"\n          },\n          {\n            name: \"temperature\",\n            type: Ossia.Type.Float,\n            value: 0.8,\n            min: 0,\n            max: 2,\n            description: \"Sampling temperature (0 = deterministic, 2 = most random)\"\n          },\n          {\n            name: \"max_tokens\",\n            type: Ossia.Type.Int,\n            value: 256,\n            min: 1,\n            max: 16384,\n            description: \"Maximum number of tokens to generate\"\n          },\n          {\n            name: \"top_p\",\n            type: Ossia.Type.Float,\n            value: 0.95,\n            min: 0,\n            max: 1,\n            description: \"Nucleus sampling threshold\"\n          },\n          {\n            name: \"frequency_penalty\",\n            type: Ossia.Type.Float,\n            value: 0,\n            min: -2,\n            max: 2,\n            description: \"Penalize repeated tokens by frequency\"\n          },\n          {\n            name: \"presence_penalty\",\n            type: Ossia.Type.Float,\n            value: 0,\n            min: -2,\n            max: 2,\n            description: \"Penalize repeated tokens by presence\"\n          }\n        ]\n      },\n\n      // ── Chat completions (/v1/chat/completions) ───────────────\n      {\n        name: \"chat\",\n        children: [\n          {\n            name: \"prompt\",\n            type: Ossia.Type.String,\n            value: \"\",\n            description: \"Write a message here to chat with the LLM\",\n            method: \"post\",\n            request: function(v) {\n              return Device.read(\"/config/host\") + \"/v1/chat/completions\";\n            },\n            requestData: function(v) {\n              var prompt = toStr(v);\n              var body = samplingParams();\n              body.model    = Device.read(\"/config/model\") || \"default\";\n              body.messages = buildChatMessages(prompt);\n              return JSON.stringify(body);\n            },\n            answer: function(json, promptValue) {\n              try {\n                var obj = JSON.parse(json);\n                if (obj.error) {\n                  var msg = obj.error.message || JSON.stringify(obj.error);\n                  return [{ address: \"/chat/response\", value: \"Error: \" + msg }];\n                }\n\n                var choice = obj.choices[0];\n                var content = choice.message.content;\n                var usage = obj.usage || {};\n\n                chatHistory.push({ role: \"user\",      content: toStr(promptValue) });\n                chatHistory.push({ role: \"assistant\",  content: content });\n\n                return [\n                  { address: \"/chat/response\",                value: content },\n                  { address: \"/chat/finish_reason\",           value: choice.finish_reason || \"\" },\n                  { address: \"/chat/usage/prompt_tokens\",     value: usage.prompt_tokens || 0 },\n                  { address: \"/chat/usage/completion_tokens\", value: usage.completion_tokens || 0 },\n                  { address: \"/chat/usage/total_tokens\",      value: usage.total_tokens || 0 }\n                ];\n              } catch (e) {\n                console.log(\"LLM chat parse error: \" + e);\n                return [{ address: \"/chat/response\", value: \"Parse error: \" + e }];\n              }\n            }\n          },\n          {\n            name: \"response\",\n            type: Ossia.Type.String,\n            value: \"\",\n            description: \"Last assistant reply\",\n            access: Ossia.Access.Get\n          },\n          {\n            name: \"finish_reason\",\n            type: Ossia.Type.String,\n            value: \"\",\n            description: \"Why generation stopped (stop, length, ...)\",\n            access: Ossia.Access.Get\n          },\n          {\n            name: \"clear\",\n            type: Ossia.Type.Impulse,\n            description: \"Clear conversation history\",\n            request: function() {\n              chatHistory = [];\n              return Device.read(\"/config/host\") + \"/health\";\n            },\n            answer: function(json) {\n              return [\n                { address: \"/chat/response\",                value: \"\" },\n                { address: \"/chat/finish_reason\",           value: \"\" },\n                { address: \"/chat/usage/prompt_tokens\",     value: 0 },\n                { address: \"/chat/usage/completion_tokens\", value: 0 },\n                { address: \"/chat/usage/total_tokens\",      value: 0 }\n              ];\n            }\n          },\n          {\n            name: \"usage\",\n            children: [\n              { name: \"prompt_tokens\",     type: Ossia.Type.Int, value: 0, access: Ossia.Access.Get },\n              { name: \"completion_tokens\", type: Ossia.Type.Int, value: 0, access: Ossia.Access.Get },\n              { name: \"total_tokens\",      type: Ossia.Type.Int, value: 0, access: Ossia.Access.Get }\n            ]\n          }\n        ]\n      },\n\n      // ── Text completions (/v1/completions) ────────────────────\n      {\n        name: \"completion\",\n        children: [\n          {\n            name: \"prompt\",\n            type: Ossia.Type.String,\n            value: \"\",\n            description: \"Write text here for completion (no chat history)\",\n            method: \"post\",\n            request: function(v) {\n              return Device.read(\"/config/host\") + \"/v1/completions\";\n            },\n            requestData: function(v) {\n              var body = samplingParams();\n              body.model  = Device.read(\"/config/model\") || \"default\";\n              body.prompt = toStr(v);\n              return JSON.stringify(body);\n            },\n            answer: function(json) {\n              try {\n                var obj = JSON.parse(json);\n                if (obj.error) {\n                  var msg = obj.error.message || JSON.stringify(obj.error);\n                  return [{ address: \"/completion/response\", value: \"Error: \" + msg }];\n                }\n\n                var choice = obj.choices[0];\n                return [\n                  { address: \"/completion/response\",      value: choice.text || \"\" },\n                  { address: \"/completion/finish_reason\",  value: choice.finish_reason || \"\" }\n                ];\n              } catch (e) {\n                console.log(\"LLM completion parse error: \" + e);\n                return [{ address: \"/completion/response\", value: \"Parse error: \" + e }];\n              }\n            }\n          },\n          {\n            name: \"response\",\n            type: Ossia.Type.String,\n            value: \"\",\n            description: \"Completion result\",\n            access: Ossia.Access.Get\n          },\n          {\n            name: \"finish_reason\",\n            type: Ossia.Type.String,\n            value: \"\",\n            access: Ossia.Access.Get\n          }\n        ]\n      },\n\n      // ── Health check ──────────────────────────────────────────\n      {\n        name: \"health\",\n        children: [\n          {\n            name: \"check\",\n            type: Ossia.Type.Impulse,\n            description: \"Send impulse to check server health\",\n            request: function() {\n              return Device.read(\"/config/host\") + \"/health\";\n            },\n            answer: function(json) {\n              try {\n                var obj = JSON.parse(json);\n                return [{ address: \"/health/status\", value: obj.status || \"unknown\" }];\n              } catch (e) {\n                return [{ address: \"/health/status\", value: \"error\" }];\n              }\n            }\n          },\n          {\n            name: \"status\",\n            type: Ossia.Type.String,\n            value: \"unknown\",\n            description: \"Server health status\",\n            access: Ossia.Access.Get\n          }\n        ]\n      }\n    ];\n  }\n}\n"}},{"Device":{"Name":"Window","Protocol":"5a181207-7d40-4ad8-814e-879fcdf8cc31","Mode":1,"Outputs":[],"InputWidth":1920,"InputHeight":1080,"SwapchainFlag":0,"SwapchainFormat":0},"Children":[{"Address":{"ioType":"<->","ClipMode":"Free","RepetitionFilter":false,"Value":{"Vec2f":[1501.0,1355.0]},"Domain":{},"Name":"size"}},{"Address":{"ioType":"<->","ClipMode":"Free","RepetitionFilter":false,"Value":{"Vec2f":[0.0,0.0]},"Domain":{},"Name":"rendersize"}},{"Address":{"ClipMode":"Free","RepetitionFilter":false,"Value":{},"Domain":{},"Name":"cursor"},"Children":[{"Address":{"ioType":"<->","ClipMode":"Free","RepetitionFilter":false,"Value":{"Vec2f":[0.0,0.0]},"Domain":{"Float":{"Min":0.0,"Max":1.0}},"Name":"scaled"}},{"Address":{"ioType":"<->","ClipMode":"Free","RepetitionFilter":false,"Value":{"Vec2f":[0.0,0.0]},"Domain":{"Float":{"Min":0.0,"Max":1.0}},"Name":"gl"}},{"Address":{"ioType":"<->","ClipMode":"Free","RepetitionFilter":false,"Value":{"Vec2f":[998.0,134.0]},"Domain":{"Vec2f":{"Min":[0.0,0.0],"Max":[1501.0,1355.0],"Values":[[],[]]}},"Name":"absolute"}}]},{"Address":{"ClipMode":"Free","RepetitionFilter":false,"Value":{},"Domain":{},"Name":"tablet"},"Children":[{"Address":{"ioType":"<->","ClipMode":"Free","RepetitionFilter":false,"Value":{"Vec2f":[0.0,0.0]},"Domain":{"Float":{"Min":0.0,"Max":1.0}},"Name":"scaled"}},{"Address":{"ioType":"<->","ClipMode":"Free","RepetitionFilter":false,"Value":{"Vec2f":[0.0,0.0]},"Domain":{"Vec2f":{"Min":[0.0,0.0],"Max":[1501.0,1355.0],"Values":[[],[]]}},"Name":"absolute"}},{"Address":{"ioType":"<->","ClipMode":"Free","RepetitionFilter":false,"Value":{"Int":0},"Domain":{},"Name":"z"}},{"Address":{"ioType":"<->","ClipMode":"Free","RepetitionFilter":false,"Value":{"Float":0.0},"Domain":{},"Name":"pressure"}},{"Address":{"ioType":"<->","ClipMode":"Free","RepetitionFilter":false,"Value":{"Float":0.0},"Domain":{"Float":{"Min":-1.0,"Max":1.0}},"Name":"tangential"}},{"Address":{"ioType":"<->","ClipMode":"Free","Unit":"angle.degree","RepetitionFilter":false,"Value":{"Float":0.0},"Domain":{"Float":{"Min":-180.0,"Max":180.0}},"Name":"rotation"}},{"Address":{"ioType":"<->","ClipMode":"Free","Unit":"angle.degree","RepetitionFilter":false,"Value":{"Float":0.0},"Domain":{"Float":{"Min":-60.0,"Max":60.0}},"Name":"tilt_x"}},{"Address":{"ioType":"<->","ClipMode":"Free","Unit":"angle.degree","RepetitionFilter":false,"Value":{"Float":0.0},"Domain":{"Float":{"Min":-60.0,"Max":60.0}},"Name":"tilt_y"}}]},{"Address":{"ClipMode":"Free","RepetitionFilter":false,"Value":{},"Domain":{},"Name":"key"},"Children":[{"Address":{"ClipMode":"Free","RepetitionFilter":false,"Value":{},"Domain":{},"Name":"press"},"Children":[{"Address":{"ioType":"<->","ClipMode":"Free","RepetitionFilter":false,"Value":{"Int":0},"Domain":{},"Name":"code"}},{"Address":{"ioType":"<->","ClipMode":"Free","RepetitionFilter":false,"Value":{"String":""},"Domain":{},"Name":"text"}}]},{"Address":{"ClipMode":"Free","RepetitionFilter":false,"Value":{},"Domain":{},"Name":"release"},"Children":[{"Address":{"ioType":"<->","ClipMode":"Free","RepetitionFilter":false,"Value":{"Int":0},"Domain":{},"Name":"code"}},{"Address":{"ioType":"<->","ClipMode":"Free","RepetitionFilter":false,"Value":{"String":""},"Domain":{},"Name":"text"}}]}]}]}]},{"uuid":"05e72689-e02c-4c9d-a0bf-fe84c32d3d96","Data":""}],"Version":4,"Commit":"1d56cfd1f967adb450195c915818a98bf869c047","Tag":"3.8.2"}