diff --git a/Assets/llamacasty text.jpg b/Assets/llamacasty text.jpg
new file mode 100644
index 0000000..3e2b1ac
Binary files /dev/null and b/Assets/llamacasty text.jpg differ
diff --git a/Assets/llamacasty text.png b/Assets/llamacasty text.png
new file mode 100644
index 0000000..b96b047
Binary files /dev/null and b/Assets/llamacasty text.png differ
diff --git a/Assets/llamacasty-text-nobg-2.png b/Assets/llamacasty-text-nobg-2.png
new file mode 100644
index 0000000..cf1f677
Binary files /dev/null and b/Assets/llamacasty-text-nobg-2.png differ
diff --git a/Assets/llamacasty-text-nobg.png b/Assets/llamacasty-text-nobg.png
new file mode 100644
index 0000000..97290c7
Binary files /dev/null and b/Assets/llamacasty-text-nobg.png differ
diff --git a/Assets/thin logo wordmark.png b/Assets/thin logo wordmark.png
new file mode 100644
index 0000000..0e789ee
Binary files /dev/null and b/Assets/thin logo wordmark.png differ
diff --git a/Controls/LeftPanel.xaml b/Controls/LeftPanel.xaml
index b8c1312..59ef205 100644
--- a/Controls/LeftPanel.xaml
+++ b/Controls/LeftPanel.xaml
@@ -596,8 +596,18 @@
HorizontalAlignment="Center" TextAlignment="Center" MaxWidth="240"/>
+
+
+
+
+
+
+
+
- : ICommand
+{
+ private readonly Action _execute;
+ private readonly Predicate? _canExecute;
+
+ public RelayCommand(Action execute, Predicate? canExecute = null)
+ {
+ _execute = execute;
+ _canExecute = canExecute;
+ }
+
+ public event EventHandler? CanExecuteChanged
+ {
+ add => CommandManager.RequerySuggested += value;
+ remove => CommandManager.RequerySuggested -= value;
+ }
+
+ public bool CanExecute(object? parameter)
+ {
+ if (parameter is T t) return _canExecute?.Invoke(t) ?? true;
+ return _canExecute?.Invoke(default) ?? true;
+ }
+
+ public void Execute(object? parameter)
+ {
+ if (parameter is T t) _execute(t);
+ else _execute(default);
+ }
+}
diff --git a/Models/YouTube.cs b/Models/YouTube.cs
index da7c6ea..5a32b0e 100644
--- a/Models/YouTube.cs
+++ b/Models/YouTube.cs
@@ -70,4 +70,5 @@ public class ChatMessage
/// subscriber events locally (YouTube's insert API only supports text), so the
/// buffer can label them honestly — simulated events never reach YouTube.
public bool IsSimulated { get; set; }
+ public bool IsPinned { get; set; }
}
diff --git a/ViewModels/MainViewModel.Chat.cs b/ViewModels/MainViewModel.Chat.cs
index 496671d..db806a9 100644
--- a/ViewModels/MainViewModel.Chat.cs
+++ b/ViewModels/MainViewModel.Chat.cs
@@ -22,6 +22,16 @@ public partial class MainViewModel : ViewModelBase
/// left panel can subscribe to its changes.
public ObservableCollection ChatMessages => _chatLayer.Messages;
+ private ChatMessage? _pinnedChatMessage;
+ public ChatMessage? PinnedChatMessage
+ {
+ get => _pinnedChatMessage;
+ set => SetProperty(ref _pinnedChatMessage, value);
+ }
+
+ public RelayCommand PinChatMessageCommand => new((m) => { PinnedChatMessage = m; });
+ public RelayCommand ClearPinnedChatMessageCommand => new(_ => { PinnedChatMessage = null; });
+
/// The Add → YouTube Chat menu item: enabled only while no chat layer exists
/// anywhere — one chat overlay per layout.
public bool CanAddYouTubeChat