diff --git a/client/scripts/theme/palette.gd b/client/scripts/theme/palette.gd new file mode 100644 index 0000000..401ef83 --- /dev/null +++ b/client/scripts/theme/palette.gd @@ -0,0 +1,89 @@ +class_name Palette +extends RefCounted +## The single source of truth for every colour token (mockups README, "Color +## tokens"). §2: presentation — owns no state. No hex literal may live anywhere +## else in the client; every script, shader, and the Theme builder reads here. + +# --- Dark backgrounds --- +const STAGE_1 := Color("0a0806") +const STAGE_2 := Color("0f0d0b") +const BAY_STRIPE_A := Color("26221c") +const BAY_STRIPE_B := Color("221e18") +const DARK_PANEL_70 := Color("120f0b", 0.7) +const DARK_PANEL_85 := Color("0e0b09", 0.85) +const DARK_PANEL_90 := Color("0a0806", 0.9) +# border ladder, light -> dark +const BORDER_1 := Color("6b6152") +const BORDER_2 := Color("5a5348") +const BORDER_3 := Color("4a4238") +const BORDER_4 := Color("3a352e") +const BORDER_5 := Color("1a1512") + +# --- Parchment --- +const SHEET_TOP := Color("ece2ca") +const SHEET_BOTTOM := Color("e4d8bd") +const LOG_TOP := Color("e7dcc4") +const LOG_BOTTOM := Color("ddd0b2") +const PARCHMENT_CARD := Color("f1e8d2") +const PARCHMENT_INSET := Color("e6d9b8") +const PARCHMENT_INSET_2 := Color("d9c9a2") +const PARCHMENT_BORDER := Color("bfa878") +const PARCHMENT_BORDER_2 := Color("cbb684") +const PARCHMENT_BORDER_3 := Color("a99464") +const TAB_INACTIVE_BG := Color("efe6cf") + +# --- Ink (text on parchment) --- +const INK_HEADING := Color("3a2f1c") +const INK_HEADING_2 := Color("4a3520") +const INK_BODY := Color("4a3f2c") +const INK_MUTED := Color("6a5a3a") +const INK_LABEL := Color("5a4326") +const INK_GOLD := Color("8f6a2a") + +# --- Text on dark --- +const CREAM := Color("e8ddc8") +const CREAM_BRIGHT := Color("f0e6d0") +const CREAM_SECONDARY := Color("c9bda3") +const MUTED_MONO := Color("9a8f78") +const MUTED_MONO_DIM := Color("7a7060") + +# --- Accents (muted for grit) --- +const BLOOD := Color("8f3a34") # core CTA / danger / selection / enemies +const BLOOD_BRIGHT := Color("b8524a") # hover +const BLOOD_DARK := Color("7a2f29") # CTA gradient bottom +const GOLD := Color("a9843f") # highlight / currency / current-turn +const GOLD_BRIGHT := Color("c8963f") +const STEEL := Color("5f8a9a") # allies / mana +const STEEL_BLUE := Color("4f7ab8") +const STEEL_DEEP := Color("243f6f") +const GREEN := Color("5f8a4f") # stamina / success / cleared + +# --- Stat bars --- +const HP_A := Color("b8524a") +const HP_B := Color("8f3a34") +const MP_A := Color("4f7ab8") +const MP_B := Color("243f6f") +const STAMINA := Color("8f9a4f") +const BAR_TRACK := Color("2a251f") +const BAR_BORDER := Color("4a4238") + +# --- Rarity (ordered common..legendary) --- +const RARITY: Array[Color] = [ + Color("8a8378"), # 0 common + Color("5f8a4f"), # 1 uncommon + Color("4f7ab8"), # 2 rare + Color("8a5fb8"), # 3 epic + Color("c8963f"), # 4 legendary +] + + +static func rarity_color(rarity: int) -> Color: + return RARITY[clampi(rarity, 0, RARITY.size() - 1)] + + +static func stat_bar(kind: StringName) -> Array: + match kind: + &"hp": return [HP_A, HP_B] + &"mp": return [MP_A, MP_B] + &"stamina": return [STAMINA, STAMINA] + _: return [MUTED_MONO, MUTED_MONO_DIM] diff --git a/client/tests/unit/test_palette.gd b/client/tests/unit/test_palette.gd new file mode 100644 index 0000000..8bddb80 --- /dev/null +++ b/client/tests/unit/test_palette.gd @@ -0,0 +1,45 @@ +extends "res://addons/gut/test.gd" + +const Palette = preload("res://scripts/theme/palette.gd") + + +func test_core_tokens_are_colors(): + assert_true(Palette.BLOOD is Color) + assert_true(Palette.GOLD is Color) + assert_true(Palette.PARCHMENT_CARD is Color) + assert_true(Palette.CREAM is Color) + + +func test_blood_core_matches_readme_hex(): + # #8f3a34 — the primary CTA / danger / selection red. + assert_eq(Palette.BLOOD, Color("8f3a34")) + + +func test_dark_panel_is_translucent(): + assert_almost_eq(Palette.DARK_PANEL_70.a, 0.7, 0.01) + + +func test_rarity_ladder_has_five_ordered_colors(): + assert_eq(Palette.RARITY.size(), 5) + assert_eq(Palette.rarity_color(0), Color("8a8378")) # common + assert_eq(Palette.rarity_color(4), Color("c8963f")) # legendary + + +func test_rarity_color_clamps_out_of_range(): + # Out-of-range must NOT index-error (would emit an engine error -> fail). + assert_eq(Palette.rarity_color(-3), Palette.RARITY[0]) + assert_eq(Palette.rarity_color(99), Palette.RARITY[4]) + + +func test_stat_bar_returns_from_to_pair(): + var hp = Palette.stat_bar(&"hp") + assert_eq(hp.size(), 2) + assert_eq(hp[0], Color("b8524a")) + assert_eq(hp[1], Color("8f3a34")) + + +func test_stat_bar_unknown_kind_is_safe(): + # Unknown kind returns a grey pair, never null / error. + var pair = Palette.stat_bar(&"nonsense") + assert_eq(pair.size(), 2) + assert_true(pair[0] is Color)