ui(properties): one typography hierarchy, pill switches, and tokens instead of hex

Creator directive 2026-11-01: the properties panels had 14 hand-rolled heading styles, 37
hand-rolled labels, four literal grey hex values, and 14 square checkboxes.

- PropertyHeader / PropertySubHeader / PropertyFieldLabel / PropertyHint in Themes/Controls.xaml
  now carry the whole hierarchy: category bright and 11px, labels and hints dim, size and weight
  doing the level. Colour groups the panel — bright for what you edit, dim for its labels and
  help — and no new palette entry was needed; DESIGN.md stays the only colour source.
- Every checkbox in the panel is a pill switch (PropertyPillCheck, 32x16 animated thumb).
  IsChecked semantics unchanged, so no binding moved.
- Literal #888/#666/#555 became OnSurfaceVariant / OnSurfaceDisabled.
- 13 headings, 2 sub-headers and 31 in-row labels now reference a style instead of repeating
  colour + size + alignment inline.

Controls audited for backing: every Button/Slider/ComboBox/CheckBox/ToggleButton/TextBox in the
panel has a Command or Click, a Value binding, an ItemsSource or Selected*, IsChecked, or Text —
no dead controls. Release 0 warnings; typography and token tests pass.
This commit is contained in:
2026-11-01 15:51:33 -08:00
parent 82960d0ca3
commit 8221bd6478
3 changed files with 174 additions and 69 deletions
+64 -69
View File
@@ -277,7 +277,7 @@
</ListBox.ItemTemplate> </ListBox.ItemTemplate>
</Helpers:FocusPreservingListBox> </Helpers:FocusPreservingListBox>
<TextBlock Grid.Row="2" Text="No layers yet" Foreground="#555" FontSize="12" <TextBlock Grid.Row="2" Text="No layers yet" Foreground="{StaticResource Brush.OnSurfaceDisabled}" FontSize="12"
HorizontalAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Center"
Visibility="{Binding ShowSourcesEmptyHint, Converter={StaticResource BoolToVis}}"/> Visibility="{Binding ShowSourcesEmptyHint, Converter={StaticResource BoolToVis}}"/>
@@ -330,7 +330,7 @@
is not offered in the vertical Chat layout, so that option is not offered in the vertical Chat layout, so that option
is not even shown there (CanToggleClipShape). --> is not even shown there (CanToggleClipShape). -->
<StackPanel Visibility="{Binding IsWebcamSelected, Converter={StaticResource BoolToVis}}"> <StackPanel Visibility="{Binding IsWebcamSelected, Converter={StaticResource BoolToVis}}">
<TextBlock Text="Shape" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,4"/> <TextBlock Text="Shape" Style="{StaticResource PropertyHeader}"/>
<StackPanel Orientation="Horizontal" Margin="10,0,0,4"> <StackPanel Orientation="Horizontal" Margin="10,0,0,4">
<Button Content="{Binding SelectedElement.MirrorButtonText}" <Button Content="{Binding SelectedElement.MirrorButtonText}"
Style="{StaticResource YtButtonSecondary}" Padding="10,4" Style="{StaticResource YtButtonSecondary}" Padding="10,4"
@@ -350,7 +350,7 @@
strength slider, on the SAME 0–1 scale and default as the strength slider, on the SAME 0–1 scale and default as the
webcam's background blur — one radius curve, shared. --> webcam's background blur — one radius curve, shared. -->
<StackPanel Visibility="{Binding IsBlurSelected, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8"> <StackPanel Visibility="{Binding IsBlurSelected, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
<TextBlock Text="Blur" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,4"/> <TextBlock Text="Blur" Style="{StaticResource PropertyHeader}"/>
<StackPanel Orientation="Horizontal" Margin="10,0,0,6"> <StackPanel Orientation="Horizontal" Margin="10,0,0,6">
<ToggleButton Style="{StaticResource SegmentToggle}" Padding="10,4" <ToggleButton Style="{StaticResource SegmentToggle}" Padding="10,4"
IsChecked="{Binding SelectedElement.IsRectShape, Mode=TwoWay}"> IsChecked="{Binding SelectedElement.IsRectShape, Mode=TwoWay}">
@@ -362,7 +362,7 @@
</ToggleButton> </ToggleButton>
</StackPanel> </StackPanel>
<DockPanel Margin="10,0,0,4"> <DockPanel Margin="10,0,0,4">
<TextBlock DockPanel.Dock="Right" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Margin="8,0,0,0" Width="34"> <TextBlock DockPanel.Dock="Right" Style="{StaticResource PropertyFieldLabel}" Margin="8,0,0,0" Width="34">
<Run Text="{Binding SelectedElement.BlurAmount, Mode=OneWay, StringFormat={}{0:P0}}"/> <Run Text="{Binding SelectedElement.BlurAmount, Mode=OneWay, StringFormat={}{0:P0}}"/>
</TextBlock> </TextBlock>
<Slider Minimum="0" Maximum="1" SmallChange="0.05" LargeChange="0.1" <Slider Minimum="0" Maximum="1" SmallChange="0.05" LargeChange="0.1"
@@ -386,23 +386,23 @@
above) so the webcam's border/background section stays the above) so the webcam's border/background section stays the
one panel bound to SelectedElement.IsWebcam. --> one panel bound to SelectedElement.IsWebcam. -->
<StackPanel Visibility="{Binding SelectedElement.IsDraggable, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8"> <StackPanel Visibility="{Binding SelectedElement.IsDraggable, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
<TextBlock Text="Geometry" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,8,0,4"/> <TextBlock Text="Geometry" Style="{StaticResource PropertyHeader}"/>
<!-- TASK 61: the four fields share ONE fixed width — wide enough <!-- TASK 61: the four fields share ONE fixed width — wide enough
for the largest geometry value (1920, plus a typed sign) and for the largest geometry value (1920, plus a typed sign) and
no wider, so Y/H no longer stretch to fill the row. --> no wider, so Y/H no longer stretch to fill the row. -->
<DockPanel Margin="10,0,0,4" LastChildFill="False"> <DockPanel Margin="10,0,0,4" LastChildFill="False">
<TextBlock DockPanel.Dock="Left" Text="X" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="16"/> <TextBlock DockPanel.Dock="Left" Text="X" Style="{StaticResource PropertyFieldLabel}" Width="16"/>
<TextBox Text="{Binding SelectedElement.X, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}" <TextBox Text="{Binding SelectedElement.X, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
Style="{StaticResource YtTextBoxCompact}" Width="44"/> Style="{StaticResource YtTextBoxCompact}" Width="44"/>
<TextBlock DockPanel.Dock="Left" Text=" Y" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="20" Margin="8,0,0,0"/> <TextBlock DockPanel.Dock="Left" Text=" Y" Style="{StaticResource PropertyFieldLabel}" Width="20" Margin="8,0,0,0"/>
<TextBox Text="{Binding SelectedElement.Y, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}" <TextBox Text="{Binding SelectedElement.Y, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
Style="{StaticResource YtTextBoxCompact}" Width="44"/> Style="{StaticResource YtTextBoxCompact}" Width="44"/>
</DockPanel> </DockPanel>
<DockPanel Margin="10,0,0,4" LastChildFill="False"> <DockPanel Margin="10,0,0,4" LastChildFill="False">
<TextBlock DockPanel.Dock="Left" Text="W" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="16"/> <TextBlock DockPanel.Dock="Left" Text="W" Style="{StaticResource PropertyFieldLabel}" Width="16"/>
<TextBox Text="{Binding SelectedElement.Width, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}" <TextBox Text="{Binding SelectedElement.Width, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
Style="{StaticResource YtTextBoxCompact}" Width="44"/> Style="{StaticResource YtTextBoxCompact}" Width="44"/>
<TextBlock DockPanel.Dock="Left" Text=" H" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="20" Margin="8,0,0,0"/> <TextBlock DockPanel.Dock="Left" Text=" H" Style="{StaticResource PropertyFieldLabel}" Width="20" Margin="8,0,0,0"/>
<TextBox Text="{Binding SelectedElement.Height, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}" <TextBox Text="{Binding SelectedElement.Height, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
Style="{StaticResource YtTextBoxCompact}" Width="44"/> Style="{StaticResource YtTextBoxCompact}" Width="44"/>
</DockPanel> </DockPanel>
@@ -410,7 +410,7 @@
<!-- Webcam border config --> <!-- Webcam border config -->
<StackPanel Visibility="{Binding SelectedElement.IsWebcam, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8"> <StackPanel Visibility="{Binding SelectedElement.IsWebcam, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
<TextBlock Text="Border Color" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,8,0,2"/> <TextBlock Text="Border Color" Style="{StaticResource PropertyHeader}"/>
<!-- TASK 63: the border colour is picked with the app's OWN <!-- TASK 63: the border colour is picked with the app's OWN
drop-down (YtComboBox) — the same widget the Display drop-down (YtComboBox) — the same widget the Display
and Font pickers use — not a hand-rolled menu. The hex and Font pickers use — not a hand-rolled menu. The hex
@@ -429,7 +429,7 @@
ToolTip="Border colour" MinWidth="100" ToolTip="Border colour" MinWidth="100"
Margin="8,0,0,0" VerticalAlignment="Center"/> Margin="8,0,0,0" VerticalAlignment="Center"/>
</DockPanel> </DockPanel>
<TextBlock Text="Border Thickness" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,8,0,2"/> <TextBlock Text="Border Thickness" Style="{StaticResource PropertyHeader}"/>
<DockPanel Margin="10,0,0,6"> <DockPanel Margin="10,0,0,6">
<Button DockPanel.Dock="Right" Style="{StaticResource IconButton}" <Button DockPanel.Dock="Right" Style="{StaticResource IconButton}"
Width="22" Height="22" Margin="4,0,0,0" Width="22" Height="22" Margin="4,0,0,0"
@@ -446,17 +446,16 @@
<Slider DockPanel.Dock="Left" Width="120" Minimum="0" Maximum="10" <Slider DockPanel.Dock="Left" Width="120" Minimum="0" Maximum="10"
Value="{Binding SelectedElement.BorderWidth, Mode=TwoWay}" Value="{Binding SelectedElement.BorderWidth, Mode=TwoWay}"
VerticalAlignment="Center" TickFrequency="1" IsSnapToTickEnabled="True"/> VerticalAlignment="Center" TickFrequency="1" IsSnapToTickEnabled="True"/>
<TextBlock Text="{Binding SelectedElement.BorderWidth}" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" <TextBlock Text="{Binding SelectedElement.BorderWidth}" Style="{StaticResource PropertyFieldLabel}" Margin="8,0,0,0" Width="20"/>
VerticalAlignment="Center" Margin="8,0,0,0" Width="20"/>
</DockPanel> </DockPanel>
<!-- TASK 51: background effects — two pills acting as radio buttons <!-- TASK 51: background effects — two pills acting as radio buttons
(one on turns the other off; both off = no effect). TASK 59: (one on turns the other off; both off = no effect). TASK 59:
the pills are indented under the Background title, and the the pills are indented under the Background title, and the
strength row one level deeper, under Blur. --> strength row one level deeper, under Blur. -->
<TextBlock Text="Background" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,8,0,6"/> <TextBlock Text="Background" Style="{StaticResource PropertyHeader}"/>
<DockPanel Margin="10,0,0,4"> <DockPanel Margin="10,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="Remove" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="84"/> <TextBlock DockPanel.Dock="Left" Text="Remove" Style="{StaticResource PropertyFieldLabel}" Width="84"/>
<ToggleButton x:Name="RemoveBackgroundPill" Style="{StaticResource PillToggle}" <ToggleButton x:Name="RemoveBackgroundPill" Style="{StaticResource PillToggle}"
IsChecked="{Binding SelectedElement.RemoveBackground, Mode=TwoWay}" IsChecked="{Binding SelectedElement.RemoveBackground, Mode=TwoWay}"
AutomationProperties.Name="Remove background" AutomationProperties.Name="Remove background"
@@ -464,7 +463,7 @@
HorizontalAlignment="Left"/> HorizontalAlignment="Left"/>
</DockPanel> </DockPanel>
<DockPanel Margin="10,0,0,4"> <DockPanel Margin="10,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="Blur" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="84"/> <TextBlock DockPanel.Dock="Left" Text="Blur" Style="{StaticResource PropertyFieldLabel}" Width="84"/>
<ToggleButton x:Name="BlurBackgroundPill" Style="{StaticResource PillToggle}" <ToggleButton x:Name="BlurBackgroundPill" Style="{StaticResource PillToggle}"
IsChecked="{Binding SelectedElement.BlurBackground, Mode=TwoWay}" IsChecked="{Binding SelectedElement.BlurBackground, Mode=TwoWay}"
AutomationProperties.Name="Blur background" AutomationProperties.Name="Blur background"
@@ -473,9 +472,9 @@
</DockPanel> </DockPanel>
<DockPanel Margin="20,0,0,6" <DockPanel Margin="20,0,0,6"
Visibility="{Binding SelectedElement.HasBackgroundEffect, Converter={StaticResource BoolToVis}}"> Visibility="{Binding SelectedElement.HasBackgroundEffect, Converter={StaticResource BoolToVis}}">
<TextBlock DockPanel.Dock="Left" Text="Strength" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="84"/> <TextBlock DockPanel.Dock="Left" Text="Strength" Style="{StaticResource PropertyFieldLabel}" Width="84"/>
<TextBlock DockPanel.Dock="Right" Text="{Binding SelectedElement.BackgroundStrength, StringFormat={}{0:P0}}" <TextBlock DockPanel.Dock="Right" Text="{Binding SelectedElement.BackgroundStrength, StringFormat={}{0:P0}}"
Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Margin="8,0,0,0" Width="34"/> Style="{StaticResource PropertyFieldLabel}" Margin="8,0,0,0" Width="34"/>
<Slider x:Name="BackgroundStrengthSlider" Minimum="0" Maximum="1" <Slider x:Name="BackgroundStrengthSlider" Minimum="0" Maximum="1"
Value="{Binding SelectedElement.BackgroundStrength, Mode=TwoWay}" Value="{Binding SelectedElement.BackgroundStrength, Mode=TwoWay}"
TickFrequency="0.05" IsSnapToTickEnabled="True" VerticalAlignment="Center" TickFrequency="0.05" IsSnapToTickEnabled="True" VerticalAlignment="Center"
@@ -496,7 +495,7 @@
Visibility="{Binding CanUseScoreboard, Converter={StaticResource InverseBoolToVis}}"/> Visibility="{Binding CanUseScoreboard, Converter={StaticResource InverseBoolToVis}}"/>
<!-- DEBUG builds only: prove the strip in the preview before a real <!-- DEBUG builds only: prove the strip in the preview before a real
stream. Absent from Release, not merely hidden by a flag. --> stream. Absent from Release, not merely hidden by a flag. -->
<CheckBox Style="{StaticResource YtCheckBox}" Margin="0,0,0,4" <CheckBox Style="{StaticResource PropertyPillCheck}" Margin="0,0,0,4"
IsChecked="{Binding ScoreboardSimulate, Mode=TwoWay}" IsChecked="{Binding ScoreboardSimulate, Mode=TwoWay}"
ToolTip="Push sample events so the strip appears in the preview." ToolTip="Push sample events so the strip appears in the preview."
Visibility="{Binding IsDevelopmentBuild, Converter={StaticResource BoolToVis}}"> Visibility="{Binding IsDevelopmentBuild, Converter={StaticResource BoolToVis}}">
@@ -504,11 +503,10 @@
be mistaken for product. --> be mistaken for product. -->
<TextBlock Text="Simulate (dev)" Foreground="{StaticResource Brush.Attention}" FontSize="12"/> <TextBlock Text="Simulate (dev)" Foreground="{StaticResource Brush.Attention}" FontSize="12"/>
</CheckBox> </CheckBox>
<CheckBox Style="{StaticResource YtCheckBox}" Margin="0,0,0,4" <CheckBox Style="{StaticResource PropertyPillCheck}" Margin="0,0,0,4"
Content="Show the scoreboard" IsChecked="{Binding ScoreboardEnabled, Mode=TwoWay}" Content="Show the scoreboard" IsChecked="{Binding ScoreboardEnabled, Mode=TwoWay}"
ToolTip="Off hides the strip in every scene AND stops the channel polling."/> ToolTip="Off hides the strip in every scene AND stops the channel polling."/>
<TextBlock Text="Edge (socials take the other)" Foreground="{StaticResource Brush.OnSurfaceVariant}" <TextBlock Text="Edge (socials take the other)" Style="{StaticResource PropertySubHeader}"/>
FontSize="11" Margin="10,0,0,2"/>
<ComboBox Style="{StaticResource YtComboBox}" Height="24" FontSize="11" Margin="10,0,0,6" <ComboBox Style="{StaticResource YtComboBox}" Height="24" FontSize="11" Margin="10,0,0,6"
ItemsSource="{Binding ScoreboardEdgeOptions}" ItemsSource="{Binding ScoreboardEdgeOptions}"
SelectedItem="{Binding ScoreboardEdge, Mode=TwoWay}"/> SelectedItem="{Binding ScoreboardEdge, Mode=TwoWay}"/>
@@ -523,32 +521,32 @@
Value="{Binding ScoreboardScrollSpeed, Mode=TwoWay}" VerticalAlignment="Center"/> Value="{Binding ScoreboardScrollSpeed, Mode=TwoWay}" VerticalAlignment="Center"/>
</DockPanel> </DockPanel>
<TextBlock Text="Facts" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,4"/> <TextBlock Text="Facts" Style="{StaticResource PropertyHeader}"/>
<WrapPanel Margin="10,0,0,0"> <WrapPanel Margin="10,0,0,0">
<CheckBox Style="{StaticResource YtCheckBox}" Content="Subscribers" Margin="0,0,10,4" <CheckBox Style="{StaticResource PropertyPillCheck}" Content="Subscribers" Margin="0,0,10,4"
IsChecked="{Binding ScoreboardShowSubscribers, Mode=TwoWay}"/> IsChecked="{Binding ScoreboardShowSubscribers, Mode=TwoWay}"/>
<CheckBox Style="{StaticResource YtCheckBox}" Content="Watch hours" Margin="0,0,10,4" <CheckBox Style="{StaticResource PropertyPillCheck}" Content="Watch hours" Margin="0,0,10,4"
IsChecked="{Binding ScoreboardShowWatchHours, Mode=TwoWay}"/> IsChecked="{Binding ScoreboardShowWatchHours, Mode=TwoWay}"/>
<CheckBox Style="{StaticResource YtCheckBox}" Content="Super Chats" Margin="0,0,10,4" <CheckBox Style="{StaticResource PropertyPillCheck}" Content="Super Chats" Margin="0,0,10,4"
IsChecked="{Binding ScoreboardShowSuperChats, Mode=TwoWay}"/> IsChecked="{Binding ScoreboardShowSuperChats, Mode=TwoWay}"/>
<CheckBox Style="{StaticResource YtCheckBox}" Content="New members" Margin="0,0,10,4" <CheckBox Style="{StaticResource PropertyPillCheck}" Content="New members" Margin="0,0,10,4"
IsChecked="{Binding ScoreboardShowNewMembers, Mode=TwoWay}"/> IsChecked="{Binding ScoreboardShowNewMembers, Mode=TwoWay}"/>
<CheckBox Style="{StaticResource YtCheckBox}" Content="Milestones" Margin="0,0,10,4" <CheckBox Style="{StaticResource PropertyPillCheck}" Content="Milestones" Margin="0,0,10,4"
IsChecked="{Binding ScoreboardShowMilestones, Mode=TwoWay}"/> IsChecked="{Binding ScoreboardShowMilestones, Mode=TwoWay}"/>
<CheckBox Style="{StaticResource YtCheckBox}" Content="Last member" Margin="0,0,10,4" <CheckBox Style="{StaticResource PropertyPillCheck}" Content="Last member" Margin="0,0,10,4"
IsChecked="{Binding ScoreboardShowLastMember, Mode=TwoWay}"/> IsChecked="{Binding ScoreboardShowLastMember, Mode=TwoWay}"/>
<CheckBox Style="{StaticResource YtCheckBox}" Content="Last Super Chat" Margin="0,0,10,4" <CheckBox Style="{StaticResource PropertyPillCheck}" Content="Last Super Chat" Margin="0,0,10,4"
IsChecked="{Binding ScoreboardShowLastSuperChat, Mode=TwoWay}"/> IsChecked="{Binding ScoreboardShowLastSuperChat, Mode=TwoWay}"/>
<CheckBox Style="{StaticResource YtCheckBox}" Content="Last gift" Margin="0,0,10,4" <CheckBox Style="{StaticResource PropertyPillCheck}" Content="Last gift" Margin="0,0,10,4"
IsChecked="{Binding ScoreboardShowLastGift, Mode=TwoWay}"/> IsChecked="{Binding ScoreboardShowLastGift, Mode=TwoWay}"/>
</WrapPanel> </WrapPanel>
<TextBlock Text="Text colour" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" Margin="10,6,0,2"/> <TextBlock Text="Text colour" Style="{StaticResource PropertySubHeader}"/>
<ComboBox Style="{StaticResource YtComboBox}" Height="24" FontSize="11" Margin="10,0,0,6" <ComboBox Style="{StaticResource YtComboBox}" Height="24" FontSize="11" Margin="10,0,0,6"
ItemsSource="{Binding ScoreboardColorOptions}" DisplayMemberPath="Name" ItemsSource="{Binding ScoreboardColorOptions}" DisplayMemberPath="Name"
SelectedValuePath="Hex" SelectedValue="{Binding ScoreboardTextColor, Mode=TwoWay}"/> SelectedValuePath="Hex" SelectedValue="{Binding ScoreboardTextColor, Mode=TwoWay}"/>
<CheckBox Style="{StaticResource YtCheckBox}" Content="Background" Margin="10,0,0,4" <CheckBox Style="{StaticResource PropertyPillCheck}" Content="Background" Margin="10,0,0,4"
IsChecked="{Binding ScoreboardBackgroundEnabled, Mode=TwoWay}"/> IsChecked="{Binding ScoreboardBackgroundEnabled, Mode=TwoWay}"/>
<ComboBox Style="{StaticResource YtComboBox}" Height="24" FontSize="11" Margin="10,0,0,6" <ComboBox Style="{StaticResource YtComboBox}" Height="24" FontSize="11" Margin="10,0,0,6"
ItemsSource="{Binding ScoreboardColorOptions}" DisplayMemberPath="Name" ItemsSource="{Binding ScoreboardColorOptions}" DisplayMemberPath="Name"
@@ -593,27 +591,26 @@
<Slider DockPanel.Dock="Left" Width="120" Minimum="1" Maximum="60" <Slider DockPanel.Dock="Left" Width="120" Minimum="1" Maximum="60"
Value="{Binding SelectedElement.TimerMinutes, Mode=TwoWay}" Value="{Binding SelectedElement.TimerMinutes, Mode=TwoWay}"
VerticalAlignment="Center" TickFrequency="1" IsSnapToTickEnabled="True"/> VerticalAlignment="Center" TickFrequency="1" IsSnapToTickEnabled="True"/>
<TextBlock Text="{Binding SelectedElement.TimerMinutes}" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" <TextBlock Text="{Binding SelectedElement.TimerMinutes}" Style="{StaticResource PropertyFieldLabel}" Margin="8,0,0,0" Width="20"/>
VerticalAlignment="Center" Margin="8,0,0,0" Width="20"/>
</DockPanel> </DockPanel>
</StackPanel> </StackPanel>
<!-- Web source config --> <!-- Web source config -->
<StackPanel Visibility="{Binding SelectedElement.IsWebSource, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8"> <StackPanel Visibility="{Binding SelectedElement.IsWebSource, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
<TextBlock Text="Geometry" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,4"/> <TextBlock Text="Geometry" Style="{StaticResource PropertyHeader}"/>
<DockPanel Margin="0,0,0,4"> <DockPanel Margin="0,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="X" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="16"/> <TextBlock DockPanel.Dock="Left" Text="X" Style="{StaticResource PropertyFieldLabel}" Width="16"/>
<TextBox Text="{Binding SelectedElement.X, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}" <TextBox Text="{Binding SelectedElement.X, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
Style="{StaticResource YtTextBoxCompact}"/> Style="{StaticResource YtTextBoxCompact}"/>
<TextBlock DockPanel.Dock="Left" Text=" Y" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="20" Margin="8,0,0,0"/> <TextBlock DockPanel.Dock="Left" Text=" Y" Style="{StaticResource PropertyFieldLabel}" Width="20" Margin="8,0,0,0"/>
<TextBox Text="{Binding SelectedElement.Y, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}" <TextBox Text="{Binding SelectedElement.Y, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
Style="{StaticResource YtTextBoxCompact}"/> Style="{StaticResource YtTextBoxCompact}"/>
</DockPanel> </DockPanel>
<DockPanel Margin="0,0,0,4"> <DockPanel Margin="0,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="W" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="16"/> <TextBlock DockPanel.Dock="Left" Text="W" Style="{StaticResource PropertyFieldLabel}" Width="16"/>
<TextBox Text="{Binding SelectedElement.Width, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}" <TextBox Text="{Binding SelectedElement.Width, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
Style="{StaticResource YtTextBoxCompact}"/> Style="{StaticResource YtTextBoxCompact}"/>
<TextBlock DockPanel.Dock="Left" Text=" H" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="20" Margin="8,0,0,0"/> <TextBlock DockPanel.Dock="Left" Text=" H" Style="{StaticResource PropertyFieldLabel}" Width="20" Margin="8,0,0,0"/>
<TextBox Text="{Binding SelectedElement.Height, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}" <TextBox Text="{Binding SelectedElement.Height, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
Style="{StaticResource YtTextBoxCompact}"/> Style="{StaticResource YtTextBoxCompact}"/>
</DockPanel> </DockPanel>
@@ -639,10 +636,10 @@
<!-- Chat box config --> <!-- Chat box config -->
<StackPanel Visibility="{Binding SelectedElement.IsChatBox, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8"> <StackPanel Visibility="{Binding SelectedElement.IsChatBox, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
<TextBlock Text="Chat Overlay" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,6"/> <TextBlock Text="Chat Overlay" Style="{StaticResource PropertyHeader}"/>
<!-- Border (same controls as the webcam's border) --> <!-- Border (same controls as the webcam's border) -->
<TextBlock Text="Border Color" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,2"/> <TextBlock Text="Border Color" Style="{StaticResource PropertyHeader}"/>
<DockPanel Margin="10,0,0,6"> <DockPanel Margin="10,0,0,6">
<TextBox DockPanel.Dock="Left" <TextBox DockPanel.Dock="Left"
Text="{Binding SelectedElement.BorderColor, UpdateSourceTrigger=PropertyChanged}" Text="{Binding SelectedElement.BorderColor, UpdateSourceTrigger=PropertyChanged}"
@@ -656,26 +653,24 @@
ToolTip="Border colour" MinWidth="100" ToolTip="Border colour" MinWidth="100"
Margin="8,0,0,0" VerticalAlignment="Center"/> Margin="8,0,0,0" VerticalAlignment="Center"/>
</DockPanel> </DockPanel>
<TextBlock Text="Border Thickness" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,2"/> <TextBlock Text="Border Thickness" Style="{StaticResource PropertyHeader}"/>
<DockPanel Margin="10,0,0,6"> <DockPanel Margin="10,0,0,6">
<Slider DockPanel.Dock="Left" Width="120" Minimum="0" Maximum="10" <Slider DockPanel.Dock="Left" Width="120" Minimum="0" Maximum="10"
Value="{Binding SelectedElement.BorderWidth, Mode=TwoWay}" Value="{Binding SelectedElement.BorderWidth, Mode=TwoWay}"
VerticalAlignment="Center" TickFrequency="1" IsSnapToTickEnabled="True"/> VerticalAlignment="Center" TickFrequency="1" IsSnapToTickEnabled="True"/>
<TextBlock Text="{Binding SelectedElement.BorderWidth}" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" <TextBlock Text="{Binding SelectedElement.BorderWidth}" Style="{StaticResource PropertyFieldLabel}" Margin="8,0,0,0" Width="20"/>
VerticalAlignment="Center" Margin="8,0,0,0" Width="20"/>
</DockPanel> </DockPanel>
<TextBlock Text="Border Opacity" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,2"/> <TextBlock Text="Border Opacity" Style="{StaticResource PropertyHeader}"/>
<DockPanel Margin="10,0,0,8"> <DockPanel Margin="10,0,0,8">
<Slider DockPanel.Dock="Left" Width="120" Minimum="0" Maximum="1" <Slider DockPanel.Dock="Left" Width="120" Minimum="0" Maximum="1"
Value="{Binding SelectedElement.BorderOpacity, Mode=TwoWay}" Value="{Binding SelectedElement.BorderOpacity, Mode=TwoWay}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
<TextBlock Text="{Binding SelectedElement.BorderOpacity, StringFormat={}{0:P0}}" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" <TextBlock Text="{Binding SelectedElement.BorderOpacity, StringFormat={}{0:P0}}" Style="{StaticResource PropertyFieldLabel}" Margin="8,0,0,0" Width="34"/>
VerticalAlignment="Center" Margin="8,0,0,0" Width="34"/>
</DockPanel> </DockPanel>
<!-- Font Size --> <!-- Font Size -->
<DockPanel Margin="0,0,0,4"> <DockPanel Margin="0,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="Font Size" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="70"/> <TextBlock DockPanel.Dock="Left" Text="Font Size" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
<Slider x:Name="ChatFontSizeSlider" Width="120" Minimum="8" Maximum="72" TickFrequency="2" IsSnapToTickEnabled="True" <Slider x:Name="ChatFontSizeSlider" Width="120" Minimum="8" Maximum="72" TickFrequency="2" IsSnapToTickEnabled="True"
Value="{Binding SelectedElement.ChatFontSize, Mode=TwoWay}" Value="{Binding SelectedElement.ChatFontSize, Mode=TwoWay}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
@@ -684,7 +679,7 @@
<!-- Font Family --> <!-- Font Family -->
<DockPanel Margin="0,0,0,4"> <DockPanel Margin="0,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="Font" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="70"/> <TextBlock DockPanel.Dock="Left" Text="Font" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
<ComboBox SelectedValue="{Binding SelectedElement.ChatFontFamily, Mode=TwoWay}" <ComboBox SelectedValue="{Binding SelectedElement.ChatFontFamily, Mode=TwoWay}"
SelectedValuePath="Content" SelectedValuePath="Content"
Style="{StaticResource YtComboBox}" Style="{StaticResource YtComboBox}"
@@ -700,28 +695,28 @@
<!-- Font Color --> <!-- Font Color -->
<DockPanel Margin="0,0,0,4"> <DockPanel Margin="0,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="Font Color" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="70"/> <TextBlock DockPanel.Dock="Left" Text="Font Color" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
<TextBox Text="{Binding SelectedElement.ChatFontColor, UpdateSourceTrigger=PropertyChanged}" <TextBox Text="{Binding SelectedElement.ChatFontColor, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource YtTextBoxCompact}" Width="80"/> Style="{StaticResource YtTextBoxCompact}" Width="80"/>
</DockPanel> </DockPanel>
<!-- Member Color --> <!-- Member Color -->
<DockPanel Margin="0,0,0,4"> <DockPanel Margin="0,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="Member" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="70"/> <TextBlock DockPanel.Dock="Left" Text="Member" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
<TextBox Text="{Binding SelectedElement.ChatMemberColor, UpdateSourceTrigger=PropertyChanged}" <TextBox Text="{Binding SelectedElement.ChatMemberColor, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource YtTextBoxCompact}" Width="80"/> Style="{StaticResource YtTextBoxCompact}" Width="80"/>
</DockPanel> </DockPanel>
<!-- Super Chat Color --> <!-- Super Chat Color -->
<DockPanel Margin="0,0,0,4"> <DockPanel Margin="0,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="Super Chat" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="70"/> <TextBlock DockPanel.Dock="Left" Text="Super Chat" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
<TextBox Text="{Binding SelectedElement.ChatSuperChatColor, UpdateSourceTrigger=PropertyChanged}" <TextBox Text="{Binding SelectedElement.ChatSuperChatColor, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource YtTextBoxCompact}" Width="80"/> Style="{StaticResource YtTextBoxCompact}" Width="80"/>
</DockPanel> </DockPanel>
<!-- Max Messages --> <!-- Max Messages -->
<DockPanel Margin="0,0,0,4"> <DockPanel Margin="0,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="Max Msgs" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="70"/> <TextBlock DockPanel.Dock="Left" Text="Max Msgs" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
<Slider x:Name="ChatMaxMsgsSlider" Width="120" Minimum="1" Maximum="20" TickFrequency="1" IsSnapToTickEnabled="True" <Slider x:Name="ChatMaxMsgsSlider" Width="120" Minimum="1" Maximum="20" TickFrequency="1" IsSnapToTickEnabled="True"
Value="{Binding SelectedElement.ChatMaxMessages, Mode=TwoWay}" Value="{Binding SelectedElement.ChatMaxMessages, Mode=TwoWay}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
@@ -730,14 +725,14 @@
<!-- Background Color --> <!-- Background Color -->
<DockPanel Margin="0,0,0,4"> <DockPanel Margin="0,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="Background" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="70"/> <TextBlock DockPanel.Dock="Left" Text="Background" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
<TextBox Text="{Binding SelectedElement.ChatBackgroundColor, UpdateSourceTrigger=PropertyChanged}" <TextBox Text="{Binding SelectedElement.ChatBackgroundColor, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource YtTextBoxCompact}" Width="80"/> Style="{StaticResource YtTextBoxCompact}" Width="80"/>
</DockPanel> </DockPanel>
<!-- Background Opacity --> <!-- Background Opacity -->
<DockPanel Margin="0,0,0,4"> <DockPanel Margin="0,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="Bg Opacity" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="70"/> <TextBlock DockPanel.Dock="Left" Text="Bg Opacity" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
<Slider x:Name="ChatBgOpacitySlider" Width="120" Minimum="0" Maximum="1" <Slider x:Name="ChatBgOpacitySlider" Width="120" Minimum="0" Maximum="1"
Value="{Binding SelectedElement.ChatBackgroundOpacity, Mode=TwoWay}" Value="{Binding SelectedElement.ChatBackgroundOpacity, Mode=TwoWay}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
@@ -746,7 +741,7 @@
<!-- Fade After (seconds) --> <!-- Fade After (seconds) -->
<DockPanel Margin="0,0,0,4"> <DockPanel Margin="0,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="Fade After" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="70"/> <TextBlock DockPanel.Dock="Left" Text="Fade After" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
<Slider x:Name="ChatFadeSlider" Width="120" Minimum="0" Maximum="60" TickFrequency="5" IsSnapToTickEnabled="True" <Slider x:Name="ChatFadeSlider" Width="120" Minimum="0" Maximum="60" TickFrequency="5" IsSnapToTickEnabled="True"
Value="{Binding SelectedElement.ChatFadeSeconds, Mode=TwoWay}" Value="{Binding SelectedElement.ChatFadeSeconds, Mode=TwoWay}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
@@ -754,17 +749,17 @@
</DockPanel> </DockPanel>
<!-- Toggles --> <!-- Toggles -->
<CheckBox Content="Show timestamps" Style="{StaticResource YtCheckBox}" Margin="0,4,0,2" <CheckBox Content="Show timestamps" Style="{StaticResource PropertyPillCheck}" Margin="0,4,0,2"
IsChecked="{Binding SelectedElement.ChatShowTimestamps, Mode=TwoWay}"/> IsChecked="{Binding SelectedElement.ChatShowTimestamps, Mode=TwoWay}"/>
<CheckBox Content="Show badges" Style="{StaticResource YtCheckBox}" Margin="0,0,0,2" <CheckBox Content="Show badges" Style="{StaticResource PropertyPillCheck}" Margin="0,0,0,2"
IsChecked="{Binding SelectedElement.ChatShowBadges, Mode=TwoWay}"/> IsChecked="{Binding SelectedElement.ChatShowBadges, Mode=TwoWay}"/>
</StackPanel> </StackPanel>
<!-- Stream Alerts config --> <!-- Stream Alerts config -->
<StackPanel Visibility="{Binding SelectedElement.IsAlertBox, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8"> <StackPanel Visibility="{Binding SelectedElement.IsAlertBox, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
<TextBlock Text="Stream Alerts" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,6"/> <TextBlock Text="Stream Alerts" Style="{StaticResource PropertyHeader}"/>
<TextBlock Text="The built-in clip plays on every alert (Super Chat, membership…) unless a custom video is picked." <TextBlock Text="The built-in clip plays on every alert (Super Chat, membership…) unless a custom video is picked."
Foreground="#888" FontSize="10" TextWrapping="Wrap" Margin="0,0,0,6"/> Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="10" TextWrapping="Wrap" Margin="0,0,0,6"/>
<DockPanel Margin="0,0,0,4"> <DockPanel Margin="0,0,0,4">
<!-- Test alert (creator ruling 2026-11-01): plays the clip this box <!-- Test alert (creator ruling 2026-11-01): plays the clip this box
@@ -774,7 +769,7 @@
Style="{StaticResource YtButtonSecondary}" Style="{StaticResource YtButtonSecondary}"
Padding="8,2" FontSize="11" VerticalAlignment="Center" Padding="8,2" FontSize="11" VerticalAlignment="Center"
ToolTip="Play the alert here, exactly as a viewer would trigger it"/> ToolTip="Play the alert here, exactly as a viewer would trigger it"/>
<TextBlock DockPanel.Dock="Left" Text="Built-in video" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="84"/> <TextBlock DockPanel.Dock="Left" Text="Built-in video" Style="{StaticResource PropertyFieldLabel}" Width="84"/>
<ToggleButton Style="{StaticResource PillToggle}" <ToggleButton Style="{StaticResource PillToggle}"
IsChecked="{Binding SelectedElement.AlertUseDefaultVideo, Mode=TwoWay}" IsChecked="{Binding SelectedElement.AlertUseDefaultVideo, Mode=TwoWay}"
HorizontalAlignment="Left"/> HorizontalAlignment="Left"/>
@@ -797,7 +792,7 @@
Style="{StaticResource YtTextBoxCompact}" IsReadOnly="True" Style="{StaticResource YtTextBoxCompact}" IsReadOnly="True"
Foreground="{StaticResource Brush.OnSurfaceVariant}" VerticalAlignment="Center"/> Foreground="{StaticResource Brush.OnSurfaceVariant}" VerticalAlignment="Center"/>
</DockPanel> </DockPanel>
<CheckBox Content="Loop the sound until the video ends" Style="{StaticResource YtCheckBox}" Margin="0,2,0,6" <CheckBox Content="Loop the sound until the video ends" Style="{StaticResource PropertyPillCheck}" Margin="0,2,0,6"
IsChecked="{Binding SelectedElement.AlertAudioLoop, Mode=TwoWay}" IsChecked="{Binding SelectedElement.AlertAudioLoop, Mode=TwoWay}"
IsEnabled="{Binding SelectedElement.HasAlertAudio}"/> IsEnabled="{Binding SelectedElement.HasAlertAudio}"/>
@@ -819,10 +814,10 @@
CommandParameter="{Binding SelectedElement}" Style="{StaticResource YtButtonSecondary}" CommandParameter="{Binding SelectedElement}" Style="{StaticResource YtButtonSecondary}"
HorizontalAlignment="Left" Padding="8,2" Margin="0,0,4,0"/> HorizontalAlignment="Left" Padding="8,2" Margin="0,0,4,0"/>
<TextBlock Text="Custom clips are referenced from disk — never stored in the layout." <TextBlock Text="Custom clips are referenced from disk — never stored in the layout."
Foreground="#888" FontSize="10" TextWrapping="Wrap" Margin="0,4,0,6"/> Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="10" TextWrapping="Wrap" Margin="0,4,0,6"/>
<DockPanel Margin="0,0,0,4"> <DockPanel Margin="0,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="Message ticker" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="84"/> <TextBlock DockPanel.Dock="Left" Text="Message ticker" Style="{StaticResource PropertyFieldLabel}" Width="84"/>
<ToggleButton Style="{StaticResource PillToggle}" <ToggleButton Style="{StaticResource PillToggle}"
IsChecked="{Binding SelectedElement.AlertShowTicker, Mode=TwoWay}" IsChecked="{Binding SelectedElement.AlertShowTicker, Mode=TwoWay}"
HorizontalAlignment="Left"/> HorizontalAlignment="Left"/>
@@ -832,7 +827,7 @@
solid (still). Inline items, not a bound ItemsSource — solid (still). Inline items, not a bound ItemsSource —
same shape as the chat Font selector above. --> same shape as the chat Font selector above. -->
<DockPanel Margin="0,0,0,4"> <DockPanel Margin="0,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="Display" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="84"/> <TextBlock DockPanel.Dock="Left" Text="Display" Style="{StaticResource PropertyFieldLabel}" Width="84"/>
<ComboBox SelectedValue="{Binding SelectedElement.AlertDisplayMethod, Mode=TwoWay}" <ComboBox SelectedValue="{Binding SelectedElement.AlertDisplayMethod, Mode=TwoWay}"
SelectedValuePath="Tag" SelectedValuePath="Tag"
Style="{StaticResource YtComboBox}" Style="{StaticResource YtComboBox}"
@@ -844,7 +839,7 @@
</DockPanel> </DockPanel>
<DockPanel Margin="0,0,0,4"> <DockPanel Margin="0,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="Volume" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Width="84"/> <TextBlock DockPanel.Dock="Left" Text="Volume" Style="{StaticResource PropertyFieldLabel}" Width="84"/>
<Slider Width="120" Minimum="0" Maximum="1" <Slider Width="120" Minimum="0" Maximum="1"
Value="{Binding SelectedElement.AlertVideoVolume, Mode=TwoWay}" Value="{Binding SelectedElement.AlertVideoVolume, Mode=TwoWay}"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
@@ -858,7 +853,7 @@
<TextBlock Text="Background" Style="{StaticResource SubHeader}"/> <TextBlock Text="Background" Style="{StaticResource SubHeader}"/>
<StackPanel Margin="4,0,0,0"> <StackPanel Margin="4,0,0,0">
<StackPanel Orientation="Horizontal" Margin="0,0,0,8"> <StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<TextBlock Text="Use default background:" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center"/> <TextBlock Text="Use default background:" Style="{StaticResource PropertyFieldLabel}"/>
<ToggleButton Style="{StaticResource PillToggle}" <ToggleButton Style="{StaticResource PillToggle}"
IsChecked="{Binding SelectedElement.UseDefaultBackground, Mode=TwoWay}" IsChecked="{Binding SelectedElement.UseDefaultBackground, Mode=TwoWay}"
Margin="8,0,0,0"/> Margin="8,0,0,0"/>
@@ -900,7 +895,7 @@
<TextBlock Text="Chat is inactive" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="13" <TextBlock Text="Chat is inactive" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="13"
HorizontalAlignment="Center" Margin="0,0,0,4"/> HorizontalAlignment="Center" Margin="0,0,0,4"/>
<TextBlock Text="Chat becomes active once LlamaCasty is connected and streaming on YouTube." <TextBlock Text="Chat becomes active once LlamaCasty is connected and streaming on YouTube."
Foreground="#666" FontSize="11" TextWrapping="Wrap" Foreground="{StaticResource Brush.OnSurfaceDisabled}" FontSize="11" TextWrapping="Wrap"
HorizontalAlignment="Center" TextAlignment="Center" MaxWidth="240"/> HorizontalAlignment="Center" TextAlignment="Center" MaxWidth="240"/>
</StackPanel> </StackPanel>
+99
View File
@@ -145,6 +145,105 @@
<!-- Dark check box. The default WPF template is the light system one — it <!-- Dark check box. The default WPF template is the light system one — it
sticks out of the dark panels, so every check box carries this style sticks out of the dark panels, so every check box carries this style
(the guard test in ytLive.Tests/PropertiesStyleTests.cs enforces it). --> (the guard test in ytLive.Tests/PropertiesStyleTests.cs enforces it). -->
<!-- ═══ Property-panel typography (2026-11-01) ═══
One hierarchy for every properties section, so no panel invents its own:
PropertyHeader — the category (bright, 11px, semibold)
PropertySubHeader — a field label inside it (dim, 10px, indented)
PropertyHint — the explanatory sentence under a control (dim, 10px, wrapped)
Colour does the grouping: bright for the thing you are editing, dim for its labels and
help. No new palette entries; these are the existing OnSurface / OnSurfaceVariant tokens. -->
<Style x:Key="PropertyHeader" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource Brush.OnSurface}"/>
<Setter Property="FontSize" Value="11"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Margin" Value="0,0,0,6"/>
</Style>
<Style x:Key="PropertySubHeader" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource Brush.OnSurfaceVariant}"/>
<Setter Property="FontSize" Value="10"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Margin" Value="10,6,0,3"/>
</Style>
<!-- A field label that sits in a row with its control (DockPanel.Dock="Left", fixed Width).
Same typography as PropertySubHeader, but vertically centred and marginless so the rows
stay on a grid. -->
<Style x:Key="PropertyFieldLabel" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource Brush.OnSurfaceVariant}"/>
<Setter Property="FontSize" Value="10"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="PropertyHint" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource Brush.OnSurfaceVariant}"/>
<Setter Property="FontSize" Value="10"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="Margin" Value="10,0,0,6"/>
</Style>
<!-- Checkboxes are pill switches (creator ruling 2026-11-01): a 14px box in a properties panel
reads as a form, and a switch reads as a setting. IsChecked semantics are untouched, so
every existing binding still works. -->
<Style x:Key="PropertyPillCheck" TargetType="CheckBox">
<Setter Property="Foreground" Value="{StaticResource Brush.OnSurface}"/>
<Setter Property="FontSize" Value="11"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CheckBox">
<Grid Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border x:Name="Track" Grid.Column="0" Width="32" Height="16" CornerRadius="8"
Background="{StaticResource Brush.SurfaceContainerHighest}"
BorderBrush="{StaticResource Brush.Outline}" BorderThickness="1"
VerticalAlignment="Center">
<Ellipse x:Name="Thumb" Width="10" Height="10" Fill="{StaticResource Brush.OnSurfaceVariant}"
HorizontalAlignment="Left" Margin="2,0,0,0">
<Ellipse.RenderTransform>
<TranslateTransform x:Name="ThumbShift" X="0"/>
</Ellipse.RenderTransform>
</Ellipse>
</Border>
<ContentPresenter Grid.Column="1" Margin="8,0,0,0" VerticalAlignment="Center"
RecognizesAccessKey="True"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="Track" Property="Background" Value="{StaticResource Brush.Tertiary}"/>
<Setter TargetName="Track" Property="BorderBrush" Value="{StaticResource Brush.Tertiary}"/>
<Setter TargetName="Thumb" Property="Fill" Value="{StaticResource Brush.OnAccent}"/>
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ThumbShift" Storyboard.TargetProperty="X"
To="16" Duration="0:0:0.12"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ThumbShift" Storyboard.TargetProperty="X"
To="0" Duration="0:0:0.12"/>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="{StaticResource Brush.OnSurfaceDisabled}"/>
<Setter TargetName="Track" Property="Opacity" Value="0.5"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="YtCheckBox" TargetType="CheckBox"> <Style x:Key="YtCheckBox" TargetType="CheckBox">
<Setter Property="Foreground" Value="{StaticResource Brush.OnSurface}"/> <Setter Property="Foreground" Value="{StaticResource Brush.OnSurface}"/>
<Setter Property="FontSize" Value="11"/> <Setter Property="FontSize" Value="11"/>
+11
View File
@@ -185,3 +185,14 @@ skip (would spam every launch). Still modal by design: SocialsDialog's slot-dele
- The image lives in the asset table; the settings table keeps its asset id and file name - The image lives in the asset table; the settings table keeps its asset id and file name
(`BrandLogoAssetId` / `BrandLogoFileName`). Eye visibility is the layer's ordinary per-scene `IsVisible`. (`BrandLogoAssetId` / `BrandLogoFileName`). Eye visibility is the layer's ordinary per-scene `IsVisible`.
- Default placement: top-left (32,32), 240x120 master px. - Default placement: top-left (32,32), 240x120 master px.
- **Properties panel typography (2026-11-01):** one hierarchy for every section, defined in
`Themes/Controls.xaml` — `PropertyHeader` (category: 11px semibold, `Brush.OnSurface`, the
brightest text), `PropertySubHeader` (a field label inside it: 10px semibold, indented 10,
`OnSurfaceVariant`), `PropertyFieldLabel` (a label sharing a row with its control: same
typography, vertically centred, no margin), `PropertyHint` (the explanatory sentence: 10px,
wrapped, dim). Colour carries the grouping — bright for the thing being edited, dim for its
labels and help — and no new palette entries were needed. ⛔ No literal hex in the panel: the
four `#888`/`#666`/`#555` values that had crept in are now `OnSurfaceVariant` /
`OnSurfaceDisabled`. ⛔ **Every checkbox in the properties panel is a pill switch**
(`PropertyPillCheck`, 32×16 track with an animated thumb) — a 14px box reads as a form, a switch
reads as a setting; `IsChecked` semantics are untouched so all bindings still work.