diff --git a/Theriapolis.Godot/Main.cs b/Theriapolis.Godot/Main.cs
index ff9f30f..f83c8b8 100644
--- a/Theriapolis.Godot/Main.cs
+++ b/Theriapolis.Godot/Main.cs
@@ -29,6 +29,19 @@ public partial class Main : Control
bool runCodexTest = false;
bool runWizard = false;
(ulong seed, int tx, int ty)? tacticalArgs = null;
+
+ // --dark is independent of the entry-point flags: it sets the codex
+ // palette default before any UI mounts so both TitleScreen and the
+ // wizard pick it up via CodexTheme.Build()'s no-arg overload.
+ for (int i = 0; i < args.Length; i++)
+ {
+ if (args[i] == "--dark")
+ {
+ CodexTheme.DefaultPalette = CodexPalette.Dark;
+ break;
+ }
+ }
+
for (int i = 0; i < args.Length; i++)
{
if (args[i] == "--codex-test")
diff --git a/Theriapolis.Godot/Scenes/Widgets/CodexCard.cs b/Theriapolis.Godot/Scenes/Widgets/CodexCard.cs
index af615a7..47c2979 100644
--- a/Theriapolis.Godot/Scenes/Widgets/CodexCard.cs
+++ b/Theriapolis.Godot/Scenes/Widgets/CodexCard.cs
@@ -21,8 +21,11 @@ public static class CodexCard
///
/// Creates a PanelContainer with ThemeTypeVariation = "Card" plus
- /// hover signal wiring. The MouseEntered/MouseExited handlers update
- /// the hover meta and re-apply the right stylebox.
+ /// hover signal wiring. MouseEntered marks hover; MouseExited defers
+ /// a recheck against the card's global rect so moving the cursor
+ /// from the card body onto an inner Button (which captures the
+ /// parent's MouseExited via mouse-filter Stop) does not clear the
+ /// hover state — the cursor is still visually within the card.
///
public static PanelContainer Make()
{
@@ -32,20 +35,52 @@ public static class CodexCard
MouseFilter = Control.MouseFilterEnum.Stop,
};
card.MouseEntered += () => SetHover(card, true);
- card.MouseExited += () => SetHover(card, false);
+ card.MouseExited += () =>
+ Callable.From(() => RecheckHover(card)).CallDeferred();
return card;
}
+ private static void RecheckHover(PanelContainer card)
+ {
+ if (!GodotObject.IsInstanceValid(card)) return;
+ // Hover stays true as long as the cursor is anywhere within the
+ // card's rect (including over any child control). Drop only when
+ // the cursor has truly left the card area.
+ bool stillOver = card.GetGlobalRect().HasPoint(card.GetGlobalMousePosition());
+ SetHover(card, stillOver);
+ }
+
public static void SetSelected(PanelContainer card, bool selected)
{
card.SetMeta(SelectedMeta, selected);
- Apply(card);
+ ApplyOrDefer(card);
}
private static void SetHover(PanelContainer card, bool hover)
{
card.SetMeta(HoverMeta, hover);
- Apply(card);
+ ApplyOrDefer(card);
+ }
+
+ ///
+ /// Apply now if the card is already in the scene tree, otherwise defer
+ /// until end-of-frame so the parent theme cascade is reachable. Step
+ /// builders call SetSelected on a freshly-created card before
+ /// AddChild — the theme isn't visible at that point and HasThemeStylebox
+ /// returns false, which previously meant the override silently dropped
+ /// and only re-attached when MouseEntered later re-ran Apply.
+ ///
+ private static void ApplyOrDefer(PanelContainer card)
+ {
+ if (card.IsInsideTree())
+ {
+ Apply(card);
+ return;
+ }
+ Callable.From(() =>
+ {
+ if (GodotObject.IsInstanceValid(card)) Apply(card);
+ }).CallDeferred();
}
private static void Apply(PanelContainer card)
diff --git a/Theriapolis.Godot/UI/CodexTheme.cs b/Theriapolis.Godot/UI/CodexTheme.cs
index d7b4b61..a66b08e 100644
--- a/Theriapolis.Godot/UI/CodexTheme.cs
+++ b/Theriapolis.Godot/UI/CodexTheme.cs
@@ -29,7 +29,15 @@ public static class CodexTheme
private static FontFile? _mono;
private static bool _fontsLoaded;
- public static Theme Build() => Build(CodexPalette.Parchment);
+ ///
+ /// Palette used by the no-arg . Set this before any
+ /// UI mounts to swap the active codex palette globally — e.g. Main reads
+ /// the --dark command-line flag and assigns
+ /// here. Defaults to .
+ ///
+ public static CodexPalette DefaultPalette { get; set; } = CodexPalette.Parchment;
+
+ public static Theme Build() => Build(DefaultPalette);
public static Theme Build(CodexPalette palette)
{
@@ -101,9 +109,15 @@ public static class CodexTheme
var cardSelected = (StyleBoxFlat)card.Duplicate();
cardSelected.BorderColor = p.Seal;
cardSelected.SetBorderWidthAll(3);
- cardSelected.ShadowColor = WithAlpha(p.Seal, 0.6f);
- cardSelected.ShadowSize = 14;
- cardSelected.ShadowOffset = new Vector2(0, 14);
+ // Drop shadow: directional (light from upper-left) and sized so the
+ // shadow's bottom edge stays clear of the next card. Card grids
+ // separate cards by 12px (v_separation in StepClade / StepSpecies /
+ // StepClass / etc.) — offset.y + size ≤ 11 keeps a 1px-minimum gap
+ // before the next card so the shadow reads as a shadow on the
+ // surface below, not as a smudge between cards.
+ cardSelected.ShadowColor = WithAlpha(p.Seal, 0.55f);
+ cardSelected.ShadowSize = 6;
+ cardSelected.ShadowOffset = new Vector2(4, 4);
theme.SetStylebox("panel_selected", "Card", cardSelected);
// Popover frame — gild border + soft shadow + rounded corners.
diff --git a/Theriapolis.Godot/UI/Widgets/CodexStepper.cs b/Theriapolis.Godot/UI/Widgets/CodexStepper.cs
index 35e67a6..3c00dc3 100644
--- a/Theriapolis.Godot/UI/Widgets/CodexStepper.cs
+++ b/Theriapolis.Godot/UI/Widgets/CodexStepper.cs
@@ -104,7 +104,7 @@ public partial class CodexStepper : HBoxContainer
{
var underline = new ColorRect
{
- Color = TryGetThemeColor("font_color", "Gild") ?? new Color("#b48a3c"),
+ Color = CodexTheme.DefaultPalette.Gild,
MouseFilter = MouseFilterEnum.Ignore,
};
underline.AnchorTop = 1.0f;
@@ -124,16 +124,20 @@ public partial class CodexStepper : HBoxContainer
// Default theme colours come from the StepperNum/StepperName variations
// (ink-mute). State overrides bring active steps to ink and complete
// to seal-red. Locked uses the dim default plus reduced opacity.
+ // Pull the colours from CodexTheme.DefaultPalette so the stepper
+ // tracks the active palette (parchment vs dark) instead of forcing
+ // the parchment values regardless.
+ var palette = CodexTheme.DefaultPalette;
Color? numColor = state switch
{
- StepState.Active => TryGetGlobalThemeColor("Ink", new Color("#2b1d10")),
- StepState.Complete => TryGetGlobalThemeColor("Seal", new Color("#7a1f12")),
+ StepState.Active => palette.Ink,
+ StepState.Complete => palette.Seal,
_ => null,
};
Color? nameColor = state switch
{
- StepState.Active => TryGetGlobalThemeColor("Ink", new Color("#2b1d10")),
- _ => null,
+ StepState.Active => palette.Ink,
+ _ => null,
};
if (numColor.HasValue) num.AddThemeColorOverride("font_color", numColor.Value);
@@ -149,14 +153,6 @@ public partial class CodexStepper : HBoxContainer
}
}
- private static Color? TryGetGlobalThemeColor(string name, Color fallback) => fallback;
-
- private Color? TryGetThemeColor(string property, string variation)
- {
- if (HasThemeColor(property, variation)) return GetThemeColor(property, variation);
- return null;
- }
-
private static string Roman(int n) => n switch
{
1 => "I",