(behavior.name = (event.currentTarget as HTMLInputElement).value)}
/>
(behavior.enabled = checked === true)}
/>
Enabled
(behavior.once = checked === true)}
/>
Once per session
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}
(activeSection = 'when')}
>
When
(activeSection = 'if')}
>
If
{#if conditionCount > 0}
{conditionCount}
{/if}
(activeSection = 'then')}
>
Then
{#if thenCount > 0}
{thenCount}
{/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'}
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}
Layer
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'}
Every (seconds)
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}
removeCondition(index)}
>
{#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)}
(condition.variable = 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}
{/if}
{/each}
{/if}
{#snippet child({ props })}
{canAddCondition ? 'Add condition' : 'Condition limit reached'}
{/snippet}
{#each availableConditionOptions as option (option.value)}
addCondition(option.value as ThemeV2BehaviorCondition['type'])}
>
{option.label}
{/each}
{:else}
{scriptMode ? 'Script' : 'Actions'}
setScriptMode(false)}
>
Builder
setScriptMode(true)}
>
Script
{#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)}
{/each}
{/if}
{#snippet child({ props })}
{canAddAction ? 'Add action' : 'Action limit reached'}
{/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}