From 0d2ee634209bb2563ff34d3acaa048d90b8e8aae Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Thu, 7 May 2026 15:28:47 +0200 Subject: [PATCH 1/7] perf(themes): add pre-computed ABGR cache on theme records --- HellionChat/Themes/Theme.cs | 45 +++++++++++++++++++++++++++++ HellionChat/Themes/ThemeRegistry.cs | 6 ++++ 2 files changed, 51 insertions(+) diff --git a/HellionChat/Themes/Theme.cs b/HellionChat/Themes/Theme.cs index 18eee74..738a981 100644 --- a/HellionChat/Themes/Theme.cs +++ b/HellionChat/Themes/Theme.cs @@ -1,3 +1,5 @@ +using HellionChat.Util; + namespace HellionChat.Themes; public sealed record Theme( @@ -10,4 +12,47 @@ public sealed record Theme( ThemeTypography Typography, bool IsBuiltIn, ThemeChatColors? ChatColors = null +) +{ + // Pre-computed ABGR mirror of ThemeColors so PushGlobal can skip the + // RgbaToAbgr conversion per slot per frame. + public ThemeAbgrCache AbgrCache { get; private set; } + + public void RecomputeAbgrCache() + { + AbgrCache = new ThemeAbgrCache( + PrimaryDark: ColourUtil.RgbaToAbgr(Colors.PrimaryDark), + Primary: ColourUtil.RgbaToAbgr(Colors.Primary), + PrimaryLight: ColourUtil.RgbaToAbgr(Colors.PrimaryLight), + PrimaryGlow: ColourUtil.RgbaToAbgr(Colors.PrimaryGlow), + AccentDark: ColourUtil.RgbaToAbgr(Colors.AccentDark), + Accent: ColourUtil.RgbaToAbgr(Colors.Accent), + AccentLight: ColourUtil.RgbaToAbgr(Colors.AccentLight), + Identity: ColourUtil.RgbaToAbgr(Colors.Identity), + WindowBg: ColourUtil.RgbaToAbgr(Colors.WindowBg), + ChildBg: ColourUtil.RgbaToAbgr(Colors.ChildBg), + FrameBg: ColourUtil.RgbaToAbgr(Colors.FrameBg), + Surface: ColourUtil.RgbaToAbgr(Colors.Surface), + SurfaceHover: ColourUtil.RgbaToAbgr(Colors.SurfaceHover), + Border: ColourUtil.RgbaToAbgr(Colors.Border), + TextPrimary: ColourUtil.RgbaToAbgr(Colors.TextPrimary), + TextMuted: ColourUtil.RgbaToAbgr(Colors.TextMuted), + TextDim: ColourUtil.RgbaToAbgr(Colors.TextDim), + StatusSuccess: ColourUtil.RgbaToAbgr(Colors.StatusSuccess), + StatusDanger: ColourUtil.RgbaToAbgr(Colors.StatusDanger), + StatusWarning: ColourUtil.RgbaToAbgr(Colors.StatusWarning), + StatusInfo: ColourUtil.RgbaToAbgr(Colors.StatusInfo)); + } +} + +// Mirrors ThemeColors slot-for-slot. The FillsAll21Slots test pins the +// contract — a new slot without its mirror fails the build. +public readonly record struct ThemeAbgrCache( + uint PrimaryDark, uint Primary, uint PrimaryLight, uint PrimaryGlow, + uint AccentDark, uint Accent, uint AccentLight, + uint Identity, + uint WindowBg, uint ChildBg, uint FrameBg, + uint Surface, uint SurfaceHover, uint Border, + uint TextPrimary, uint TextMuted, uint TextDim, + uint StatusSuccess, uint StatusDanger, uint StatusWarning, uint StatusInfo ); diff --git a/HellionChat/Themes/ThemeRegistry.cs b/HellionChat/Themes/ThemeRegistry.cs index 62c0f8a..5b3ce23 100644 --- a/HellionChat/Themes/ThemeRegistry.cs +++ b/HellionChat/Themes/ThemeRegistry.cs @@ -25,6 +25,11 @@ public sealed class ThemeRegistry { ForgeMerchantman.Slug, ForgeMerchantman.Build() }, { MintGrove.Slug, MintGrove.Build() }, }; + + // Centralised so the nine .Build() factories stay free of cache plumbing. + foreach (var theme in _builtIns.Values) + theme.RecomputeAbgrCache(); + _active = _builtIns[DefaultSlug]; _customThemesDir = customThemesDir; } @@ -81,6 +86,7 @@ public sealed class ThemeRegistry try { theme = ThemeJsonLoader.LoadFromFile(path); + theme.RecomputeAbgrCache(); _customCache[key] = (theme, stamp); } catch (Exception ex) From aff2528a6f7eb9d6f414de870a66145662efec12 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Thu, 7 May 2026 15:28:52 +0200 Subject: [PATCH 2/7] perf(themes): read abgr from theme cache in PushGlobal and Push --- HellionChat/Ui/HellionStyle.cs | 105 +++++++++++++++++---------------- 1 file changed, 55 insertions(+), 50 deletions(-) diff --git a/HellionChat/Ui/HellionStyle.cs b/HellionChat/Ui/HellionStyle.cs index 77ce083..a9828d2 100644 --- a/HellionChat/Ui/HellionStyle.cs +++ b/HellionChat/Ui/HellionStyle.cs @@ -19,21 +19,21 @@ internal static class HellionStyle /// internal static IDisposable Push(Theme theme) { - var c = theme.Colors; + var a = theme.AbgrCache; var stack = new StackHandle(); - stack.PushColor(ImGuiCol.Button, c.Primary); - stack.PushColor(ImGuiCol.ButtonHovered, c.PrimaryLight); - stack.PushColor(ImGuiCol.ButtonActive, c.PrimaryDark); - stack.PushColor(ImGuiCol.FrameBg, c.FrameBg); - stack.PushColor(ImGuiCol.FrameBgHovered, c.SurfaceHover); - stack.PushColor(ImGuiCol.FrameBgActive, c.Surface); - stack.PushColor(ImGuiCol.Border, c.Border); - stack.PushColor(ImGuiCol.Header, c.Surface); - stack.PushColor(ImGuiCol.HeaderHovered, c.SurfaceHover); - stack.PushColor(ImGuiCol.HeaderActive, c.Identity); - stack.PushColor(ImGuiCol.CheckMark, c.Primary); - stack.PushColor(ImGuiCol.SliderGrab, c.Primary); - stack.PushColor(ImGuiCol.SliderGrabActive, c.PrimaryLight); + stack.PushColorAbgr(ImGuiCol.Button, a.Primary); + stack.PushColorAbgr(ImGuiCol.ButtonHovered, a.PrimaryLight); + stack.PushColorAbgr(ImGuiCol.ButtonActive, a.PrimaryDark); + stack.PushColorAbgr(ImGuiCol.FrameBg, a.FrameBg); + stack.PushColorAbgr(ImGuiCol.FrameBgHovered, a.SurfaceHover); + stack.PushColorAbgr(ImGuiCol.FrameBgActive, a.Surface); + stack.PushColorAbgr(ImGuiCol.Border, a.Border); + stack.PushColorAbgr(ImGuiCol.Header, a.Surface); + stack.PushColorAbgr(ImGuiCol.HeaderHovered, a.SurfaceHover); + stack.PushColorAbgr(ImGuiCol.HeaderActive, a.Identity); + stack.PushColorAbgr(ImGuiCol.CheckMark, a.Primary); + stack.PushColorAbgr(ImGuiCol.SliderGrab, a.Primary); + stack.PushColorAbgr(ImGuiCol.SliderGrabActive, a.PrimaryLight); return stack; } @@ -48,6 +48,7 @@ internal static class HellionStyle { var c = theme.Colors; var l = theme.Layout; + var a = theme.AbgrCache; var stack = new StackHandle(); var alphaByte = (uint)Math.Clamp((int)(windowOpacity * 255f), 0x55, 0xFF); @@ -76,60 +77,61 @@ internal static class HellionStyle stack.PushStyleVar(ImGuiStyleVar.WindowBorderSize, l.WindowBorderSize); stack.PushStyleVar(ImGuiStyleVar.FrameBorderSize, l.FrameBorderSize); - // Surfaces - stack.PushColor(ImGuiCol.WindowBg, windowBgWithAlpha); - stack.PushColor(ImGuiCol.ChildBg, childBgWithAlpha); - stack.PushColor(ImGuiCol.PopupBg, c.ChildBg); - stack.PushColor(ImGuiCol.Border, c.Border); - stack.PushColor(ImGuiCol.BorderShadow, 0u); + // Surfaces — WindowBg/ChildBg use the per-push opacity-modulated value, + // so they go through the RGBA path; everything else reads from cache. + stack.PushColor(ImGuiCol.WindowBg, windowBgWithAlpha); + stack.PushColor(ImGuiCol.ChildBg, childBgWithAlpha); + stack.PushColorAbgr(ImGuiCol.PopupBg, a.ChildBg); + stack.PushColorAbgr(ImGuiCol.Border, a.Border); + stack.PushColorAbgr(ImGuiCol.BorderShadow, 0u); // Frames - stack.PushColor(ImGuiCol.FrameBg, c.FrameBg); - stack.PushColor(ImGuiCol.FrameBgHovered, c.SurfaceHover); - stack.PushColor(ImGuiCol.FrameBgActive, c.Surface); + stack.PushColorAbgr(ImGuiCol.FrameBg, a.FrameBg); + stack.PushColorAbgr(ImGuiCol.FrameBgHovered, a.SurfaceHover); + stack.PushColorAbgr(ImGuiCol.FrameBgActive, a.Surface); // Title bars - stack.PushColor(ImGuiCol.TitleBg, c.WindowBg); - stack.PushColor(ImGuiCol.TitleBgActive, c.Identity); - stack.PushColor(ImGuiCol.TitleBgCollapsed, c.WindowBg); + stack.PushColorAbgr(ImGuiCol.TitleBg, a.WindowBg); + stack.PushColorAbgr(ImGuiCol.TitleBgActive, a.Identity); + stack.PushColorAbgr(ImGuiCol.TitleBgCollapsed, a.WindowBg); // Buttons - stack.PushColor(ImGuiCol.Button, c.Primary); - stack.PushColor(ImGuiCol.ButtonHovered, c.PrimaryLight); - stack.PushColor(ImGuiCol.ButtonActive, c.PrimaryDark); + stack.PushColorAbgr(ImGuiCol.Button, a.Primary); + stack.PushColorAbgr(ImGuiCol.ButtonHovered, a.PrimaryLight); + stack.PushColorAbgr(ImGuiCol.ButtonActive, a.PrimaryDark); // Headers / selectables - stack.PushColor(ImGuiCol.Header, c.Surface); - stack.PushColor(ImGuiCol.HeaderHovered, c.SurfaceHover); - stack.PushColor(ImGuiCol.HeaderActive, c.Identity); + stack.PushColorAbgr(ImGuiCol.Header, a.Surface); + stack.PushColorAbgr(ImGuiCol.HeaderHovered, a.SurfaceHover); + stack.PushColorAbgr(ImGuiCol.HeaderActive, a.Identity); // Tabs - stack.PushColor(ImGuiCol.Tab, c.FrameBg); - stack.PushColor(ImGuiCol.TabHovered, c.PrimaryLight); - stack.PushColor(ImGuiCol.TabActive, c.Identity); - stack.PushColor(ImGuiCol.TabUnfocused, c.ChildBg); - stack.PushColor(ImGuiCol.TabUnfocusedActive, c.PrimaryDark); + stack.PushColorAbgr(ImGuiCol.Tab, a.FrameBg); + stack.PushColorAbgr(ImGuiCol.TabHovered, a.PrimaryLight); + stack.PushColorAbgr(ImGuiCol.TabActive, a.Identity); + stack.PushColorAbgr(ImGuiCol.TabUnfocused, a.ChildBg); + stack.PushColorAbgr(ImGuiCol.TabUnfocusedActive, a.PrimaryDark); // Scrollbar - stack.PushColor(ImGuiCol.ScrollbarBg, c.WindowBg); - stack.PushColor(ImGuiCol.ScrollbarGrab, c.Surface); - stack.PushColor(ImGuiCol.ScrollbarGrabHovered, c.AccentLight); - stack.PushColor(ImGuiCol.ScrollbarGrabActive, c.Accent); + stack.PushColorAbgr(ImGuiCol.ScrollbarBg, a.WindowBg); + stack.PushColorAbgr(ImGuiCol.ScrollbarGrab, a.Surface); + stack.PushColorAbgr(ImGuiCol.ScrollbarGrabHovered, a.AccentLight); + stack.PushColorAbgr(ImGuiCol.ScrollbarGrabActive, a.Accent); // Resize grip - stack.PushColor(ImGuiCol.ResizeGrip, c.FrameBg); - stack.PushColor(ImGuiCol.ResizeGripHovered, c.AccentLight); - stack.PushColor(ImGuiCol.ResizeGripActive, c.Accent); + stack.PushColorAbgr(ImGuiCol.ResizeGrip, a.FrameBg); + stack.PushColorAbgr(ImGuiCol.ResizeGripHovered, a.AccentLight); + stack.PushColorAbgr(ImGuiCol.ResizeGripActive, a.Accent); // Check mark + slider grab - stack.PushColor(ImGuiCol.CheckMark, c.Primary); - stack.PushColor(ImGuiCol.SliderGrab, c.Primary); - stack.PushColor(ImGuiCol.SliderGrabActive, c.PrimaryLight); + stack.PushColorAbgr(ImGuiCol.CheckMark, a.Primary); + stack.PushColorAbgr(ImGuiCol.SliderGrab, a.Primary); + stack.PushColorAbgr(ImGuiCol.SliderGrabActive, a.PrimaryLight); // Separator - stack.PushColor(ImGuiCol.Separator, c.Border); - stack.PushColor(ImGuiCol.SeparatorHovered, c.PrimaryLight); - stack.PushColor(ImGuiCol.SeparatorActive, c.Primary); + stack.PushColorAbgr(ImGuiCol.Separator, a.Border); + stack.PushColorAbgr(ImGuiCol.SeparatorHovered, a.PrimaryLight); + stack.PushColorAbgr(ImGuiCol.SeparatorActive, a.Primary); return stack; } @@ -141,6 +143,9 @@ internal static class HellionStyle internal void PushColor(ImGuiCol slot, uint rgba) => _items.Add(ImRaii.PushColor(slot, ColourUtil.RgbaToAbgr(rgba))); + internal void PushColorAbgr(ImGuiCol slot, uint abgr) + => _items.Add(ImRaii.PushColor(slot, abgr)); + internal void PushStyleVar(ImGuiStyleVar var, float value) => _items.Add(ImRaii.PushStyle(var, value)); From e4ee7aaafa5b830c7c9a1260fa432130124328d8 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Thu, 7 May 2026 15:29:05 +0200 Subject: [PATCH 3/7] fix(themes): keep last-known-good custom theme on transient file-lock --- HellionChat/Themes/ThemeJsonLoader.cs | 6 +++++- HellionChat/Themes/ThemeRegistry.cs | 24 +++++++++++++++++++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/HellionChat/Themes/ThemeJsonLoader.cs b/HellionChat/Themes/ThemeJsonLoader.cs index e340165..79b69dc 100644 --- a/HellionChat/Themes/ThemeJsonLoader.cs +++ b/HellionChat/Themes/ThemeJsonLoader.cs @@ -63,7 +63,11 @@ internal static class ThemeJsonLoader public static Theme LoadFromFile(string path) { - var json = File.ReadAllText(path); + // FileShare.Read lets concurrent readers and well-behaved editors share + // the handle; atomic-replace editors still raise IOException, caught upstream. + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + using var reader = new StreamReader(stream); + var json = reader.ReadToEnd(); return LoadFromString(json); } diff --git a/HellionChat/Themes/ThemeRegistry.cs b/HellionChat/Themes/ThemeRegistry.cs index 5b3ce23..82ea605 100644 --- a/HellionChat/Themes/ThemeRegistry.cs +++ b/HellionChat/Themes/ThemeRegistry.cs @@ -52,6 +52,16 @@ public sealed class ThemeRegistry public void Switch(string slug) => _active = Get(slug); + // 0x80070020 = SHARING_VIOLATION, 0x80070021 = LOCK_VIOLATION. Other + // IO failures are permanent and get the theme dropped instead of retried. + internal static bool IsRecoverableFileLock(Exception? ex) + { + if (ex is not IOException io) + return false; + var code = (uint)io.HResult; + return code == 0x80070020u || code == 0x80070021u; + } + // Custom-Themes werden lazy aus dem Verzeichnis geladen, Cache mit // LastWriteTime-Token. Eine geänderte JSON wird beim nächsten Lookup // neu eingelesen. @@ -89,12 +99,16 @@ public sealed class ThemeRegistry theme.RecomputeAbgrCache(); _customCache[key] = (theme, stamp); } - catch (Exception ex) + catch (Exception ex) when (IsRecoverableFileLock(ex)) + { + // Editor mid-save: keep the cached snapshot, leave the stamp + // alone so the next refresh retries automatically. + Plugin.Log.Debug($"Custom theme {Path.GetFileName(path)} is locked, keeping last known good"); + if (cached.Theme is not null) + theme = cached.Theme; + } + catch (Exception) { - // Logging passiert in Plugin.cs durch den Aufrufer; hier still - // ignorieren, damit ein einzelnes kaputtes JSON nicht alle - // Custom-Themes blockt. - _ = ex; continue; } } From 3d7883ee0162e59a4eb21f8f857b94130b1e16a1 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Thu, 7 May 2026 15:42:58 +0200 Subject: [PATCH 4/7] fix(themes): refresh abgr cache defensively on theme switch --- HellionChat/Themes/ThemeRegistry.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/HellionChat/Themes/ThemeRegistry.cs b/HellionChat/Themes/ThemeRegistry.cs index 82ea605..59eb2c5 100644 --- a/HellionChat/Themes/ThemeRegistry.cs +++ b/HellionChat/Themes/ThemeRegistry.cs @@ -50,7 +50,14 @@ public sealed class ThemeRegistry public IEnumerable AllCustom() => RefreshCustomCache(); - public void Switch(string slug) => _active = Get(slug); + public void Switch(string slug) + { + var theme = Get(slug); + // Defensive — idempotent and cheap, so any future theme source + // that forgets the cache fill still ends up with a populated one. + theme.RecomputeAbgrCache(); + _active = theme; + } // 0x80070020 = SHARING_VIOLATION, 0x80070021 = LOCK_VIOLATION. Other // IO failures are permanent and get the theme dropped instead of retried. From 5f83c7029247866f6b5894685f4af5d1f0f90051 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Thu, 7 May 2026 19:48:15 +0200 Subject: [PATCH 5/7] feat(themes): add Synthwave Sunset built-in, refresh author credits --- HellionChat/Themes/Builtin/EventHorizon.cs | 2 +- .../Themes/Builtin/ForgeMerchantman.cs | 2 +- HellionChat/Themes/Builtin/HellionArctic.cs | 2 +- HellionChat/Themes/Builtin/HellionSpectrum.cs | 2 +- HellionChat/Themes/Builtin/MintGrove.cs | 2 +- HellionChat/Themes/Builtin/MoonlitBloom.cs | 2 +- HellionChat/Themes/Builtin/SynthwaveSunset.cs | 76 +++++++++++++++++++ HellionChat/Themes/ThemeRegistry.cs | 3 +- 8 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 HellionChat/Themes/Builtin/SynthwaveSunset.cs diff --git a/HellionChat/Themes/Builtin/EventHorizon.cs b/HellionChat/Themes/Builtin/EventHorizon.cs index c436ec6..bfc086b 100644 --- a/HellionChat/Themes/Builtin/EventHorizon.cs +++ b/HellionChat/Themes/Builtin/EventHorizon.cs @@ -9,7 +9,7 @@ internal static class EventHorizon public static Theme Build() => new( Slug: Slug, Name: "Event Horizon", - Author: "Hellion Online Media", + Author: "Hellion Forge", Description: "Cosmic Purple auf Near-Black. Deep-Space-Stimmung.", Colors: new ThemeColors( PrimaryDark: ColourUtil.HexToRgba("#7B3FCF"), diff --git a/HellionChat/Themes/Builtin/ForgeMerchantman.cs b/HellionChat/Themes/Builtin/ForgeMerchantman.cs index b18c724..bda06b0 100644 --- a/HellionChat/Themes/Builtin/ForgeMerchantman.cs +++ b/HellionChat/Themes/Builtin/ForgeMerchantman.cs @@ -9,7 +9,7 @@ internal static class ForgeMerchantman public static Theme Build() => new( Slug: Slug, Name: "Forge Merchantman", - Author: "Hellion Online Media", + Author: "Carla Beleandis", Description: "Patina Bronze auf Workshop-Slate — Hellion Forge im Plugin.", Colors: new ThemeColors( PrimaryDark: ColourUtil.HexToRgba("#1F8A82"), diff --git a/HellionChat/Themes/Builtin/HellionArctic.cs b/HellionChat/Themes/Builtin/HellionArctic.cs index 481084f..4add46a 100644 --- a/HellionChat/Themes/Builtin/HellionArctic.cs +++ b/HellionChat/Themes/Builtin/HellionArctic.cs @@ -9,7 +9,7 @@ internal static class HellionArctic public static Theme Build() => new( Slug: Slug, Name: "Hellion Arctic", - Author: "Hellion Online Media", + Author: "Hellion Forge", Description: "Arctic Cyan + Ember Glow on industrial slate. Plugin default.", Colors: new ThemeColors( PrimaryDark: ColourUtil.HexToRgba("#0097A7"), diff --git a/HellionChat/Themes/Builtin/HellionSpectrum.cs b/HellionChat/Themes/Builtin/HellionSpectrum.cs index 5175bcd..22f630f 100644 --- a/HellionChat/Themes/Builtin/HellionSpectrum.cs +++ b/HellionChat/Themes/Builtin/HellionSpectrum.cs @@ -15,7 +15,7 @@ internal static class HellionSpectrum public static Theme Build() => new( Slug: Slug, Name: "Hellion Spectrum", - Author: "Hellion Online Media", + Author: "Hellion Forge", Description: "Deuteran/Protan-safe channels — Wong palette tones, channel identity preserved.", Colors: new ThemeColors( PrimaryDark: ColourUtil.HexToRgba("#005983"), diff --git a/HellionChat/Themes/Builtin/MintGrove.cs b/HellionChat/Themes/Builtin/MintGrove.cs index eab1f5c..d9024f6 100644 --- a/HellionChat/Themes/Builtin/MintGrove.cs +++ b/HellionChat/Themes/Builtin/MintGrove.cs @@ -9,7 +9,7 @@ internal static class MintGrove public static Theme Build() => new( Slug: Slug, Name: "Mint Grove", - Author: "Hellion Online Media", + Author: "Carla Beleandis", Description: "Mint Green + Honey Amber auf Deep Forest. Naturthemen-tauglich.", Colors: new ThemeColors( PrimaryDark: ColourUtil.HexToRgba("#3CB371"), diff --git a/HellionChat/Themes/Builtin/MoonlitBloom.cs b/HellionChat/Themes/Builtin/MoonlitBloom.cs index 3da16b3..f818c4a 100644 --- a/HellionChat/Themes/Builtin/MoonlitBloom.cs +++ b/HellionChat/Themes/Builtin/MoonlitBloom.cs @@ -9,7 +9,7 @@ internal static class MoonlitBloom public static Theme Build() => new( Slug: Slug, Name: "Moonlit Bloom", - Author: "Hellion Online Media", + Author: "Hellion Forge", Description: "Bloom Magenta + Soft Sage auf Deep Violet Night.", Colors: new ThemeColors( PrimaryDark: ColourUtil.HexToRgba("#C957D0"), diff --git a/HellionChat/Themes/Builtin/SynthwaveSunset.cs b/HellionChat/Themes/Builtin/SynthwaveSunset.cs new file mode 100644 index 0000000..0928c11 --- /dev/null +++ b/HellionChat/Themes/Builtin/SynthwaveSunset.cs @@ -0,0 +1,76 @@ +using HellionChat.Util; + +namespace HellionChat.Themes.Builtin; + +internal static class SynthwaveSunset +{ + public const string Slug = "synthwave-sunset"; + + public static Theme Build() => new( + Slug: Slug, + Name: "Synthwave Sunset", + Author: "Hellion Forge", + Description: "Hot Magenta + Cyan on midnight violet. 80s neon-grid vibes for late-night raids.", + Colors: new ThemeColors( + PrimaryDark: ColourUtil.HexToRgba("#C71585"), + Primary: ColourUtil.HexToRgba("#FF2D95"), + PrimaryLight: ColourUtil.HexToRgba("#FF6BB6"), + PrimaryGlow: ColourUtil.HexToRgba("#FF2D9599"), + + AccentDark: ColourUtil.HexToRgba("#0098B8"), + Accent: ColourUtil.HexToRgba("#00F0FF"), + AccentLight: ColourUtil.HexToRgba("#5CFFFE"), + + Identity: ColourUtil.HexToRgba("#FF2D95"), + + WindowBg: ColourUtil.HexToRgba("#13041F"), + ChildBg: ColourUtil.HexToRgba("#1E0A35"), + FrameBg: ColourUtil.HexToRgba("#2A1247"), + Surface: ColourUtil.HexToRgba("#3A1860"), + SurfaceHover: ColourUtil.HexToRgba("#4A2475"), + Border: ColourUtil.HexToRgba("#FF2D9566"), + + TextPrimary: ColourUtil.HexToRgba("#F0DFFF"), + TextMuted: ColourUtil.HexToRgba("#A88BC4"), + TextDim: ColourUtil.HexToRgba("#6F4D8E"), + + StatusSuccess: ColourUtil.HexToRgba("#39FF14"), + StatusDanger: ColourUtil.HexToRgba("#FF3838"), + StatusWarning: ColourUtil.HexToRgba("#FFD700"), + StatusInfo: ColourUtil.HexToRgba("#00F0FF") + ), + Layout: new ThemeLayout( + WindowRounding: 5f, ChildRounding: 4f, PopupRounding: 4f, + FrameRounding: 3f, GrabRounding: 3f, TabRounding: 3f, + ScrollbarRounding: 3f, WindowBorderSize: 1f, FrameBorderSize: 1f + ), + Typography: new ThemeTypography(), + IsBuiltIn: true, + ChatColors: new ThemeChatColors(new Dictionary + { + // Synthwave Sunset — Magenta dominiert die warmen Channels (Yell/Shout/FC), + // Cyan dominiert die kühlen (Tell/Party). Neon-Akzente für Status-nahe Channels. + [HellionChat.Code.ChatType.Say] = ColourUtil.HexToRgba("#F0DFFF"), + [HellionChat.Code.ChatType.Yell] = ColourUtil.HexToRgba("#FF2D95"), + [HellionChat.Code.ChatType.Shout] = ColourUtil.HexToRgba("#FF6BB6"), + [HellionChat.Code.ChatType.TellIncoming] = ColourUtil.HexToRgba("#00F0FF"), + [HellionChat.Code.ChatType.TellOutgoing] = ColourUtil.HexToRgba("#5CFFFE"), + [HellionChat.Code.ChatType.Party] = ColourUtil.HexToRgba("#5CFFFE"), + [HellionChat.Code.ChatType.Alliance] = ColourUtil.HexToRgba("#FF8C00"), + [HellionChat.Code.ChatType.FreeCompany] = ColourUtil.HexToRgba("#FF2D95"), + [HellionChat.Code.ChatType.NoviceNetwork] = ColourUtil.HexToRgba("#39FF14"), + [HellionChat.Code.ChatType.CrossParty] = ColourUtil.HexToRgba("#5CFFFE"), + [HellionChat.Code.ChatType.Linkshell1] = ColourUtil.HexToRgba("#39FF14"), + [HellionChat.Code.ChatType.Linkshell2] = ColourUtil.HexToRgba("#FF8C00"), + [HellionChat.Code.ChatType.Linkshell3] = ColourUtil.HexToRgba("#FFD700"), + [HellionChat.Code.ChatType.Linkshell4] = ColourUtil.HexToRgba("#00F0FF"), + [HellionChat.Code.ChatType.Linkshell5] = ColourUtil.HexToRgba("#FF6BB6"), + [HellionChat.Code.ChatType.Linkshell6] = ColourUtil.HexToRgba("#FF2D95"), + [HellionChat.Code.ChatType.Linkshell7] = ColourUtil.HexToRgba("#A88BC4"), + [HellionChat.Code.ChatType.Linkshell8] = ColourUtil.HexToRgba("#5CFFFE"), + [HellionChat.Code.ChatType.CustomEmote] = ColourUtil.HexToRgba("#FF6BB6"), + [HellionChat.Code.ChatType.StandardEmote] = ColourUtil.HexToRgba("#A88BC4"), + [HellionChat.Code.ChatType.Echo] = ColourUtil.HexToRgba("#A88BC4"), + }) + ); +} diff --git a/HellionChat/Themes/ThemeRegistry.cs b/HellionChat/Themes/ThemeRegistry.cs index 59eb2c5..3e2b17e 100644 --- a/HellionChat/Themes/ThemeRegistry.cs +++ b/HellionChat/Themes/ThemeRegistry.cs @@ -24,9 +24,10 @@ public sealed class ThemeRegistry { IndigoViolet.Slug, IndigoViolet.Build() }, { ForgeMerchantman.Slug, ForgeMerchantman.Build() }, { MintGrove.Slug, MintGrove.Build() }, + { SynthwaveSunset.Slug, SynthwaveSunset.Build() }, }; - // Centralised so the nine .Build() factories stay free of cache plumbing. + // Centralised so the ten .Build() factories stay free of cache plumbing. foreach (var theme in _builtIns.Values) theme.RecomputeAbgrCache(); From 54bfeb0f6f40df37cbcf365204d1f247c4d86403 Mon Sep 17 00:00:00 2001 From: JonKazama-Hellion Date: Thu, 7 May 2026 19:58:50 +0200 Subject: [PATCH 6/7] chore: bump version to 1.4.1 and document Theme Engine Performance --- HellionChat/HellionChat.csproj | 2 +- HellionChat/HellionChat.yaml | 50 ++++++++++++++++++++++++++++++---- README.md | 9 +++--- docs/CHANGELOG.md | 39 ++++++++++++++++++++++++++ docs/ROADMAP.md | 27 +++++++++++++----- repo.json | 14 +++++----- 6 files changed, 117 insertions(+), 24 deletions(-) diff --git a/HellionChat/HellionChat.csproj b/HellionChat/HellionChat.csproj index 98f58fa..bb6edd7 100644 --- a/HellionChat/HellionChat.csproj +++ b/HellionChat/HellionChat.csproj @@ -4,7 +4,7 @@ 0.1.0 is our bootstrap release; the underlying Chat 2 base is called out in the yaml changelog so users can see what it derives from. --> - 1.4.0 + 1.4.1 enable enable