(behavior.name = (event.currentTarget as HTMLInputElement).value)} />
(behavior.enabled = checked === true)} />
(behavior.once = checked === true)} />
setTarget(String(value))} > {targetLabel} {#each groupOptions as option (option.value)} {/each} {#each elements as element (element.id)} {/each}
{#if themeVariables.length > 0}
Variables {#each themeVariables as variable (variable)} {variable} {/each}
{/if}
{#if activeSection === 'when'}
setTriggerType(String(value))} > {triggerOptions.find((option) => option.value === behavior.trigger.type)?.label ?? 'Theme loads'} {#each triggerOptions as option (option.value)} {/each} {#if behavior.trigger.type === 'screen'}
updateTrigger({ screen: value as ThemeV2ScriptScreen })} > {screens.find((screen) => screen.value === triggerScreen)?.label ?? 'Home'} {#each screens as screen (screen.value)} {/each}
{:else if pointerTrigger}
{#if behavior.target}

{#if behavior.target.type === 'group'} Fires for any interactive layer inside {targetName}. {:else} Fires for {targetName}. {/if}

{#if targetNeedsInteractive}

{#if behavior.target.type === 'group'} Mark at least one layer inside this group as Interactive so this trigger can fire. {:else} This linked layer is not Interactive, so this trigger cannot fire. {/if}

{/if} {:else} updateTrigger({ elementId: value === '_any' ? undefined : String(value) })} > {elementOptions.find((option) => option.value === triggerElementId)?.label ?? 'Any interactive layer'} {#each elementOptions as option (option.value)} {/each}

Enable Interactive in the layer inspector to make this layer receive pointer events.

{/if}
{:else if behavior.trigger.type === 'timer'}
updateTrigger({ interval: Math.max( 0.5, Number((event.currentTarget as HTMLInputElement).value) || 5 ), })} />
{:else}

Runs once when the theme is applied to the launcher.

{/if}
{:else if activeSection === 'if'}

Match

(behavior.conditionMode = value === 'any' ? 'any' : 'all')} > {behavior.conditionMode === 'any' ? 'Any' : 'All'}
{#if (behavior.conditions ?? []).length === 0}

No conditions — this rule always runs when the trigger fires.

{:else} {#each behavior.conditions ?? [] as condition, index (index)}
replaceCondition(index, String(value) as ThemeV2BehaviorCondition['type'])} > {conditionOptions.find((option) => option.value === condition.type)?.label ?? 'Condition'} {#each conditionOptions as option (option.value)} {/each}
{#if condition.type === 'variable'}
(condition.variable = (event.currentTarget as HTMLInputElement).value)} /> (condition.operator = value as ThemeV2BehaviorOperator)} > {operators.find((operator) => operator.value === condition.operator)?.label ?? 'equals'} {#each operators as operator (operator.value)} {/each}
(condition.value = parseValue((event.currentTarget as HTMLInputElement).value))} />
Clock {#each clockConditionNames as clock (clock)} {/each}
{:else if condition.type === 'visible'} setElementField(condition, String(value))} > {elementOptions.find((option) => option.value === condition.elementId)?.label ?? 'Choose a layer'} {#each elementOptions as option (option.value)} {/each} (condition.value = value === 'visible')} > {condition.value ? 'Is visible' : 'Is hidden'} {:else if condition.type === 'groupVisible'} setGroupField(condition, String(value))} > {groupOptions.find((option) => option.value === condition.groupId)?.label ?? 'Choose a group'} {#each groupOptions as option (option.value)} {/each} (condition.value = value === 'visible')} > {condition.value ? 'Is visible' : 'Is hidden'} {:else if condition.type === 'screen'} (condition.screen = value as ThemeV2ScriptScreen)} > {screens.find((screen) => screen.value === condition.screen)?.label ?? 'Home'} {#each screens as screen (screen.value)} {/each} {:else}
Chance {Math.round(condition.percent)}%
(condition.percent = Math.min( 100, Math.max(0, Number((event.currentTarget as HTMLInputElement).value) || 0) ))} />
{/if}
{/each} {/if} {#snippet child({ props })} {/snippet} {#each availableConditionOptions as option (option.value)} addCondition(option.value as ThemeV2BehaviorCondition['type'])} > {option.label} {/each}
{:else}

{scriptMode ? 'Script' : 'Actions'}

{#if scriptMode}
(behavior.script = next)} />
Builder actions are kept but ignored.
{#if scriptError}

{formatThemeScriptError(scriptError)}

{:else if !(behavior.script ?? '').trim()}

This script is empty, so the rule does nothing.

{:else}

Sandboxed JavaScript syntax — no browser APIs or eval. Try clicks += 1;, if (clicks >= 3) { ... }, for (let i = 0; i < 3; i++) { ... }, showLayer("id");, goTo("themes");.

{/if}
{:else} {#if behavior.actions.length === 0}

Add at least one action for this rule to do something.

{:else} {#each behavior.actions as action, index (index)}
replaceAction(index, String(value) as ThemeV2BehaviorAction['type'])} > {actionOptions.find((option) => option.value === action.type)?.label ?? 'Action'} {#each actionOptions as option (option.value)} {/each}
{#if action.type === 'setVariable'}
(action.variable = (event.currentTarget as HTMLInputElement).value)} /> (action.value = parseValue((event.currentTarget as HTMLInputElement).value))} />
{:else if action.type === 'changeVariable'}
(action.variable = (event.currentTarget as HTMLInputElement).value)} /> (action.amount = Number((event.currentTarget as HTMLInputElement).value) || 0)} />
{:else if action.type === 'toggleVariable'} (action.variable = (event.currentTarget as HTMLInputElement).value)} /> {:else if action.type === 'showElement' || action.type === 'hideElement' || action.type === 'toggleElement'} setElementField(action, String(value))} > {elementOptions.find((option) => option.value === action.elementId)?.label ?? 'Choose a layer'} {#each elementOptions as option (option.value)} {/each} {:else if action.type === 'showGroup' || action.type === 'hideGroup' || action.type === 'toggleGroup'} setGroupField(action, String(value))} > {groupOptions.find((option) => option.value === action.groupId)?.label ?? 'Choose a group'} {#each groupOptions as option (option.value)} {/each} {:else if action.type === 'navigate'} (action.screen = value as ThemeV2ScriptScreen)} > {screens.find((screen) => screen.value === action.screen)?.label ?? 'Home'} {#each screens as screen (screen.value)} {/each} {:else if action.type === 'openUrl'} (action.url = (event.currentTarget as HTMLInputElement).value)} /> {:else}

Starts the normal osu! launch flow. Only use this on an explicit click.

{/if}
{/each} {/if} {#snippet child({ props })} {/snippet} {#each availableActionOptions as option (option.value)} addAction(option.value as ThemeV2BehaviorAction['type'])} > {option.label} {/each} {/if}
{/if} {#if scriptMode ? scriptCanLeave : behavior.actions.some((action) => action.type === 'launch' || action.type === 'openUrl')}

This rule can leave the launcher. URL actions show the full destination and ask for confirmation first; launch actions start the normal osu! flow. Prefer a click trigger and tell users what will happen.

{/if}