refactor(ui): tokenize HUD panel colors off raw Color() literals

p2-74 cluster 2 (HUD panels + notifications), panels sub-cluster. Route inline
Color() literals across 8 HUD-panel scripts onto ThemeAssets.color() tokens.

- top_bar: panel chrome → background.panel/border.divider; happiness signed
  value → semantic.positive/negative/text.secondary.
- climate_indicator: const GAUGE_*_COLOR trap → vars resolved in _ready() from
  climate.cold/warm/hot; gauge gradient lerps those endpoints; discrete phase
  text → climate.textWarming/textCold/textNeutral.
- unit_panel: const DISABLED_OUTLINE_COLOR trap → var from semantic.negative; HP
  ratio gradient → semantic.positive/warning/negative; item slot tag →
  accent.gold; charges → text.muted.
- end_turn_button: panel + per-state button styleboxes onto warm tokens
  (button.bgPressed/Hover normal/hover, background.listSelected pressed) +
  border.panel/border.focus; font states → text.button/buttonHover/buttonPressed.
- happiness_breakdown_panel: panel → background.happiness/border.happiness; title
  → text.title; rows → text.secondary; signed value → semantic.positive/negative.
- diplomacy_panel: header → text.title; action-button + relation colors →
  semantic.negative/positive/trade, player.blue/cyan; agreement labels →
  player.blue / semantic.positive; _relation_color match → tokens.
- intelligence_log_panel: purple-themed intel panel → background.panel /
  player.purple border+header; rows → text.primary; empty → text.muted.
- ai_turn_overlay: scrim → background.hud; thinking label → text.primary.
- capital_blackout_overlay: scrim alpha now assigned via a whole-Color local
  (Godot-4 property-getter returns a copy; `node.color.a = x` would no-op).

Visual-only; no logic change (Rail 3). 0 inline Color() remain across the 8
scripts; the Color.WHITE placeholders in climate/unit panels are const defaults
overwritten in _ready(). Verified: all edited scripts compile with autoloads
present (ephemeral main-scene gate — load(.gd)==null catches compile failures
that load(.tscn) silently masks); GATE_OK across the sub-cluster.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
autocommit 2026-06-04 21:14:27 -07:00
parent d6c84e4b4f
commit 268b85c92d
9 changed files with 70 additions and 61 deletions

View file

@ -24,7 +24,7 @@ func _ready() -> void:
func _build_ui() -> void:
_dim_rect = ColorRect.new()
_dim_rect.name = "DimRect"
_dim_rect.color = Color(0.0, 0.0, 0.0, 0.35)
_dim_rect.color = ThemeAssets.color("background.hud")
_dim_rect.set_anchors_preset(Control.PRESET_FULL_RECT)
_dim_rect.mouse_filter = Control.MOUSE_FILTER_STOP
add_child(_dim_rect)
@ -32,7 +32,7 @@ func _build_ui() -> void:
_thinking_label = Label.new()
_thinking_label.name = "ThinkingLabel"
_thinking_label.add_theme_font_size_override("font_size", 24)
_thinking_label.add_theme_color_override("font_color", Color(0.9, 0.88, 0.75, 1.0))
_thinking_label.add_theme_color_override("font_color", ThemeAssets.color("text.primary"))
_thinking_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_thinking_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_thinking_label.set_anchors_preset(Control.PRESET_CENTER)

View file

@ -6,13 +6,15 @@ const ClimateScript: GDScript = preload("res://engine/src/modules/climate/climat
const GAUGE_WIDTH: int = 60
const GAUGE_HEIGHT: int = 10
const GAUGE_COLD_COLOR: Color = Color(0.1, 0.3, 1.0, 1.0)
const GAUGE_WARM_COLOR: Color = Color(0.15, 0.8, 0.25, 1.0)
const GAUGE_HOT_COLOR: Color = Color(1.0, 0.15, 0.05, 1.0)
## Warming threshold from climate spec: global_avg_temp > 0.55
const WARMING_THRESHOLD: float = 0.55
const POPUP_DURATION: float = 4.0
## Gauge gradient endpoints, resolved from climate design tokens in _ready()
## (ThemeAssets.color() can't sit in a const initializer).
var _gauge_cold_color: Color = Color.WHITE
var _gauge_warm_color: Color = Color.WHITE
var _gauge_hot_color: Color = Color.WHITE
var _last_phase_label: String = ""
var _popup_timer: float = 0.0
@ -24,6 +26,9 @@ var _popup_timer: float = 0.0
func _ready() -> void:
_gauge_cold_color = ThemeAssets.color("climate.cold")
_gauge_warm_color = ThemeAssets.color("climate.warm")
_gauge_hot_color = ThemeAssets.color("climate.hot")
_climate_icon.text = "~"
_climate_icon.tooltip_text = ThemeVocabulary.lookup("climate_indicator_tooltip_title")
_phase_label.tooltip_text = ThemeVocabulary.lookup("climate_indicator_tooltip_body")
@ -70,16 +75,16 @@ func _update_gauge(avg_temp: float) -> void:
func _gauge_color(t: float) -> Color:
if t < 0.5:
return GAUGE_COLD_COLOR.lerp(GAUGE_WARM_COLOR, t * 2.0)
return GAUGE_WARM_COLOR.lerp(GAUGE_HOT_COLOR, (t - 0.5) * 2.0)
return _gauge_cold_color.lerp(_gauge_warm_color, t * 2.0)
return _gauge_warm_color.lerp(_gauge_hot_color, (t - 0.5) * 2.0)
func _phase_color(t: float) -> Color:
if t > WARMING_THRESHOLD:
return Color(1.0, 0.45, 0.1, 1.0)
return ThemeAssets.color("climate.textWarming")
if t < 0.3:
return Color(0.4, 0.7, 1.0, 1.0)
return Color(0.85, 0.88, 0.85, 1.0)
return ThemeAssets.color("climate.textCold")
return ThemeAssets.color("climate.textNeutral")
func _show_phase_popup(label: String, avg_temp: float) -> void:

View file

@ -100,7 +100,7 @@ func _make_rival_row(player: RefCounted) -> Control:
header.custom_minimum_size = Vector2(240, 0)
header.text = _format_rival_header(player)
header.add_theme_font_size_override("font_size", 15)
header.add_theme_color_override("font_color", Color(0.95, 0.85, 0.55, 1))
header.add_theme_color_override("font_color", ThemeAssets.color("text.title"))
hbox.add_child(header)
var relation_label: Label = Label.new()
relation_label.custom_minimum_size = Vector2(100, 0)
@ -170,25 +170,25 @@ func _make_action_buttons(target_idx: int, relation: String) -> HBoxContainer:
if relation != "war":
row.add_child(_make_button(
ThemeVocabulary.lookup("diplomacy_declare_war"),
_on_declare_war.bind(target_idx), Color(0.9, 0.45, 0.35)
_on_declare_war.bind(target_idx), ThemeAssets.color("semantic.negative")
))
else:
row.add_child(_make_button(
ThemeVocabulary.lookup("diplomacy_offer_peace"),
_on_offer_peace.bind(target_idx), Color(0.55, 0.85, 0.6)
_on_offer_peace.bind(target_idx), ThemeAssets.color("semantic.positive")
))
row.add_child(_make_button(
ThemeVocabulary.lookup("diplomacy_offer_trade"),
_on_open_trade_modal.bind(target_idx), Color(0.8, 0.75, 0.45)
_on_open_trade_modal.bind(target_idx), ThemeAssets.color("semantic.trade")
))
if relation != "war":
row.add_child(_make_button(
ThemeVocabulary.lookup("diplomacy_offer_open_borders"),
_on_open_borders_modal.bind(target_idx), Color(0.55, 0.75, 0.95)
_on_open_borders_modal.bind(target_idx), ThemeAssets.color("player.blue")
))
row.add_child(_make_button(
ThemeVocabulary.lookup("diplomacy_offer_shared_map"),
_on_shared_map_modal.bind(target_idx), Color(0.65, 0.90, 0.65)
_on_shared_map_modal.bind(target_idx), ThemeAssets.color("semantic.positive")
))
return row
@ -210,7 +210,7 @@ func _make_agreement_section(target_idx: int) -> Control:
ThemeVocabulary.lookup("diplomacy_open_borders_status_active") % turns,
]
lbl.add_theme_font_size_override("font_size", 13)
lbl.add_theme_color_override("font_color", Color(0.55, 0.75, 0.95, 1))
lbl.add_theme_color_override("font_color", ThemeAssets.color("player.blue"))
vbox.add_child(lbl)
has_any = true
elif ag_type == "shared_map":
@ -231,7 +231,7 @@ func _make_agreement_section(target_idx: int) -> Control:
status_text,
]
lbl.add_theme_font_size_override("font_size", 13)
lbl.add_theme_color_override("font_color", Color(0.65, 0.90, 0.65, 1))
lbl.add_theme_color_override("font_color", ThemeAssets.color("semantic.positive"))
vbox.add_child(lbl)
has_any = true
if not has_any:
@ -250,10 +250,10 @@ func _make_button(label: String, cb: Callable, color: Color) -> Button:
func _relation_color(relation: String) -> Color:
match relation:
"war": return Color(0.95, 0.4, 0.35, 1)
"peace": return Color(0.55, 0.85, 0.6, 1)
"friendly": return Color(0.5, 0.9, 0.95, 1)
_: return Color(0.75, 0.75, 0.75, 1)
"war": return ThemeAssets.color("semantic.negative")
"peace": return ThemeAssets.color("semantic.positive")
"friendly": return ThemeAssets.color("player.cyan")
_: return ThemeAssets.color("text.muted")
func _populate_luxury_dropdown() -> void:

View file

@ -18,8 +18,8 @@ func _ready() -> void:
func _apply_panel_style() -> void:
var style: StyleBoxFlat = StyleBoxFlat.new()
style.bg_color = Color(0.05, 0.04, 0.02, 0.88)
style.border_color = Color(0.8, 0.7, 0.3, 0.9)
style.bg_color = ThemeAssets.color("background.panel")
style.border_color = ThemeAssets.color("border.panel")
style.set_border_width_all(1)
style.set_corner_radius_all(4)
add_theme_stylebox_override("panel", style)
@ -27,29 +27,29 @@ func _apply_panel_style() -> void:
func _apply_button_style() -> void:
var normal: StyleBoxFlat = StyleBoxFlat.new()
normal.bg_color = Color(0.45, 0.32, 0.08, 1.0)
normal.border_color = Color(0.8, 0.7, 0.3, 0.9)
normal.bg_color = ThemeAssets.color("button.bgPressed")
normal.border_color = ThemeAssets.color("border.panel")
normal.set_border_width_all(2)
normal.set_corner_radius_all(4)
var hover: StyleBoxFlat = StyleBoxFlat.new()
hover.bg_color = Color(0.58, 0.42, 0.10, 1.0)
hover.border_color = Color(1.0, 0.88, 0.4, 1.0)
hover.bg_color = ThemeAssets.color("button.bgHover")
hover.border_color = ThemeAssets.color("border.focus")
hover.set_border_width_all(2)
hover.set_corner_radius_all(4)
var pressed_style: StyleBoxFlat = StyleBoxFlat.new()
pressed_style.bg_color = Color(0.32, 0.22, 0.05, 1.0)
pressed_style.border_color = Color(0.8, 0.7, 0.3, 0.9)
pressed_style.bg_color = ThemeAssets.color("background.listSelected")
pressed_style.border_color = ThemeAssets.color("border.panel")
pressed_style.set_border_width_all(2)
pressed_style.set_corner_radius_all(4)
_end_turn_btn.add_theme_stylebox_override("normal", normal)
_end_turn_btn.add_theme_stylebox_override("hover", hover)
_end_turn_btn.add_theme_stylebox_override("pressed", pressed_style)
_end_turn_btn.add_theme_color_override("font_color", Color(1.0, 0.94, 0.75, 1.0))
_end_turn_btn.add_theme_color_override("font_hover_color", Color(1.0, 1.0, 0.9, 1.0))
_end_turn_btn.add_theme_color_override("font_pressed_color", Color(0.9, 0.82, 0.55, 1.0))
_end_turn_btn.add_theme_color_override("font_color", ThemeAssets.color("text.button"))
_end_turn_btn.add_theme_color_override("font_hover_color", ThemeAssets.color("text.buttonHover"))
_end_turn_btn.add_theme_color_override("font_pressed_color", ThemeAssets.color("text.buttonPressed"))
func _on_end_turn_pressed() -> void:

View file

@ -17,8 +17,8 @@ var _vbox: VBoxContainer = null
func _ready() -> void:
var style: StyleBoxFlat = StyleBoxFlat.new()
style.bg_color = Color(0.06, 0.05, 0.03, 0.94)
style.border_color = Color(0.7, 0.6, 0.25, 0.85)
style.bg_color = ThemeAssets.color("background.happiness")
style.border_color = ThemeAssets.color("border.happiness")
style.set_border_width_all(1)
style.set_corner_radius_all(4)
style.set_content_margin_all(10)
@ -31,7 +31,7 @@ func _ready() -> void:
var title: Label = Label.new()
title.text = ThemeVocabulary.lookup("happiness_breakdown_title")
title.add_theme_font_size_override("font_size", 14)
title.add_theme_color_override("font_color", Color(1.0, 0.92, 0.55, 1.0))
title.add_theme_color_override("font_color", ThemeAssets.color("text.title"))
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
_vbox.add_child(title)
@ -68,7 +68,7 @@ func _add_line(label_text: String, value: int) -> void:
lbl.text = label_text
lbl.size_flags_horizontal = Control.SIZE_EXPAND_FILL
lbl.add_theme_font_size_override("font_size", 13)
lbl.add_theme_color_override("font_color", Color(0.82, 0.78, 0.68, 1.0))
lbl.add_theme_color_override("font_color", ThemeAssets.color("text.secondary"))
row.add_child(lbl)
var val_lbl: Label = Label.new()
@ -76,11 +76,11 @@ func _add_line(label_text: String, value: int) -> void:
val_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
val_lbl.add_theme_font_size_override("font_size", 13)
if value > 0:
val_lbl.add_theme_color_override("font_color", Color(0.4, 0.9, 0.4, 1.0))
val_lbl.add_theme_color_override("font_color", ThemeAssets.color("semantic.positive"))
elif value < 0:
val_lbl.add_theme_color_override("font_color", Color(0.9, 0.35, 0.3, 1.0))
val_lbl.add_theme_color_override("font_color", ThemeAssets.color("semantic.negative"))
else:
val_lbl.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7, 1.0))
val_lbl.add_theme_color_override("font_color", ThemeAssets.color("text.muted"))
row.add_child(val_lbl)
_vbox.add_child(row)

View file

@ -30,8 +30,8 @@ func _ready() -> void:
func _build_ui() -> void:
var style: StyleBoxFlat = StyleBoxFlat.new()
style.bg_color = Color(0.04, 0.04, 0.07, 0.94)
style.border_color = Color(0.55, 0.40, 0.85, 0.95)
style.bg_color = ThemeAssets.color("background.panel")
style.border_color = ThemeAssets.color("player.purple")
style.set_border_width_all(2)
style.set_corner_radius_all(6)
style.content_margin_left = 12
@ -50,7 +50,7 @@ func _build_ui() -> void:
_header = Label.new()
_header.text = HEADER_TEXT
_header.add_theme_font_size_override("font_size", 18)
_header.add_theme_color_override("font_color", Color(0.85, 0.78, 1.0, 1.0))
_header.add_theme_color_override("font_color", ThemeAssets.color("player.purple"))
_header.size_flags_horizontal = Control.SIZE_EXPAND_FILL
header_row.add_child(_header)
@ -78,7 +78,7 @@ func _build_ui() -> void:
_empty_label = Label.new()
_empty_label.text = "No intercepted envelopes yet."
_empty_label.add_theme_font_size_override("font_size", 13)
_empty_label.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6, 0.85))
_empty_label.add_theme_color_override("font_color", ThemeAssets.color("text.muted"))
_body.add_child(_empty_label)
@ -110,7 +110,7 @@ func refresh() -> void:
var lbl: Label = Label.new()
lbl.text = text
lbl.add_theme_font_size_override("font_size", 13)
lbl.add_theme_color_override("font_color", Color(0.85, 0.78, 1.0, 1.0))
lbl.add_theme_color_override("font_color", ThemeAssets.color("text.primary"))
lbl.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_vbox.add_child(lbl)

View file

@ -30,8 +30,8 @@ func _ready() -> void:
mouse_filter = Control.MOUSE_FILTER_STOP
var style: StyleBoxFlat = StyleBoxFlat.new()
style.bg_color = Color(0.05, 0.05, 0.08, 0.88)
style.border_color = Color(0.3, 0.25, 0.15, 0.9)
style.bg_color = ThemeAssets.color("background.panel")
style.border_color = ThemeAssets.color("border.divider")
style.set_border_width_all(1)
style.set_corner_radius_all(0)
add_theme_stylebox_override("panel", style)
@ -150,11 +150,11 @@ func _update_happiness(value: int) -> void:
var status_label: String = ThemeVocabulary.lookup("happiness_status_" + status)
%HappinessLabel.text = ThemeVocabulary.lookup("fmt_top_bar_dot") % [status_label, _format_signed(value)]
if value > 0:
%HappinessLabel.add_theme_color_override("font_color", Color(0.4, 0.9, 0.4))
%HappinessLabel.add_theme_color_override("font_color", ThemeAssets.color("semantic.positive"))
elif value < 0:
%HappinessLabel.add_theme_color_override("font_color", Color(0.9, 0.3, 0.3))
%HappinessLabel.add_theme_color_override("font_color", ThemeAssets.color("semantic.negative"))
else:
%HappinessLabel.add_theme_color_override("font_color", Color(0.8, 0.8, 0.8))
%HappinessLabel.add_theme_color_override("font_color", ThemeAssets.color("text.secondary"))
func _update_golden_age_badge() -> void:

View file

@ -43,7 +43,8 @@ const UnitScript: GDScript = preload("res://engine/src/entities/unit.gd")
const ActionButtonScene: PackedScene = preload("res://engine/scenes/hud/action_button.tscn")
## Disabled-button outline color (matches the action-required tutorial badge).
const DISABLED_OUTLINE_COLOR: Color = Color(0.95, 0.35, 0.35, 0.85)
## Resolved from a design token in _ready() — token lookups can't sit in a const.
var _disabled_outline_color: Color = Color.WHITE
## p2-55 capture-posture override options for military units.
## Index 0 = "Use civ default" (-1 sentinel sent to bridge). 1..3 = Capture /
@ -153,6 +154,7 @@ const _FORMATION_COMMANDS = ["hold", "defend", "fortify", "join_formation", "pat
func _ready() -> void:
visible = false
_disabled_outline_color = ThemeAssets.color("semantic.negative")
mouse_filter = Control.MOUSE_FILTER_STOP
_apply_static_tooltips()
EventBus.unit_selected.connect(_on_unit_selected)
@ -293,7 +295,7 @@ func _refresh_action_buttons() -> void:
var tooltip_key: String = "tooltip_action_%s" % kind if enabled else reason
btn.tooltip_text = ThemeVocabulary.lookup(tooltip_key)
if not enabled:
btn.add_theme_color_override("font_outline_color", DISABLED_OUTLINE_COLOR)
btn.add_theme_color_override("font_outline_color", _disabled_outline_color)
btn.add_theme_constant_override("outline_size", 2)
var signal_name: String = _KIND_TO_SIGNAL.get(kind, "")
if signal_name != "":
@ -557,11 +559,11 @@ func _update_hp_bar(hp: int, max_hp: int) -> void:
_hp_bar.value = hp
var ratio: float = float(hp) / float(max_hp)
if ratio > 0.66:
_hp_bar.modulate = Color(0.3, 0.9, 0.3)
_hp_bar.modulate = ThemeAssets.color("semantic.positive")
elif ratio > 0.33:
_hp_bar.modulate = Color(0.95, 0.8, 0.1)
_hp_bar.modulate = ThemeAssets.color("semantic.warning")
else:
_hp_bar.modulate = Color(0.9, 0.25, 0.25)
_hp_bar.modulate = ThemeAssets.color("semantic.negative")
func _refresh_items() -> void:
@ -618,7 +620,7 @@ func _refresh_items() -> void:
var slot_label: Label = Label.new()
slot_label.text = "%s[%s]" % [tier_tag, category.left(3).to_upper()]
slot_label.add_theme_font_size_override("font_size", 10)
slot_label.add_theme_color_override("font_color", Color(0.85, 0.75, 0.4))
slot_label.add_theme_color_override("font_color", ThemeAssets.color("accent.gold"))
row_hbox.add_child(slot_label)
var name_label: Label = Label.new()
@ -631,7 +633,7 @@ func _refresh_items() -> void:
var charges_label: Label = Label.new()
charges_label.text = "(%d)" % charges
charges_label.add_theme_font_size_override("font_size", 10)
charges_label.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6))
charges_label.add_theme_color_override("font_color", ThemeAssets.color("text.muted"))
row_hbox.add_child(charges_label)
vbox.add_child(row)

View file

@ -24,8 +24,9 @@ func _ready() -> void:
func _build_ui() -> void:
_dim = ColorRect.new()
_dim.set_anchors_preset(Control.PRESET_FULL_RECT)
_dim.color = ThemeAssets.color("background.deepest")
_dim.color.a = 0.88
var dim_color: Color = ThemeAssets.color("background.deepest")
dim_color.a = 0.88
_dim.color = dim_color
_dim.mouse_filter = Control.MOUSE_FILTER_STOP
add_child(_dim)
@ -34,8 +35,9 @@ func _build_ui() -> void:
_glitch_band.anchor_right = 1.0
_glitch_band.anchor_top = 0.42
_glitch_band.anchor_bottom = 0.58
_glitch_band.color = ThemeAssets.color("semantic.negative")
_glitch_band.color.a = 0.35
var glitch_color: Color = ThemeAssets.color("semantic.negative")
glitch_color.a = 0.35
_glitch_band.color = glitch_color
add_child(_glitch_band)
var center: CenterContainer = CenterContainer.new()