feat(title): warm shader background, pulsing glow, drifting embers

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-11 10:54:22 -05:00
parent a77bf03ee4
commit f07139256b
5 changed files with 86 additions and 1 deletions

View File

@@ -0,0 +1,15 @@
shader_type canvas_item;
// Warm radial for the Title screen (mock: radial-gradient warm centre -> near
// black edge, centred at 70% 20%). Colours are Palette uniforms set by the title
// script (the DarkBay precedent), so no hex lives in the shader.
uniform vec4 warm : source_color;
uniform vec4 mid : source_color;
uniform vec4 edge : source_color;
uniform vec2 center = vec2(0.7, 0.2);
void fragment() {
float d = distance(UV, center);
vec3 c = mix(warm.rgb, mid.rgb, clamp(d / 0.45, 0.0, 1.0));
c = mix(c, edge.rgb, clamp((d - 0.45) / 0.55, 0.0, 1.0));
COLOR = vec4(c, 1.0);
}

View File

@@ -0,0 +1 @@
uid://cfrs0nt60f2mb

View File

@@ -18,6 +18,21 @@ anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
[node name="Glow" type="ColorRect" parent="."]
layout_mode = 1
anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = -420.0
mouse_filter = 2
[node name="Embers" type="GPUParticles2D" parent="."]
position = Vector2(960, 1080)
amount = 24
lifetime = 9.0
preprocess = 4.0
[node name="KeyArt" type="Label" parent="."]
layout_mode = 1
anchors_preset = 6

View File

@@ -19,6 +19,10 @@ const BORDER_3 := Color("4a4238")
const BORDER_4 := Color("3a352e")
const BORDER_5 := Color("1a1512")
# --- Title-screen ambiance (warm radial background) ---
const TITLE_WARM := Color("2a2018") # radial centre
const TITLE_MID := Color("160f0a") # radial midpoint (STAGE_1 is the edge)
# --- Parchment ---
const SHEET_TOP := Color("ece2ca")
const SHEET_BOTTOM := Color("e4d8bd")

View File

@@ -3,7 +3,9 @@ extends Control
## The entry-point screen (mock Title Screen). Editor-first (ADR 0001): the whole
## composition lives in TitleScreen.tscn; this script drives selection, input, and
## dispatches the menu. §2: presentation/state — no AI here (the DM enters at the
## shell). The atmosphere (bg shader, glow, embers) is layered on in a later task.
## shell). The atmosphere (warm bg shader, pulsing glow, drifting embers) is
## configured in code from Palette (the DarkBay precedent) — see _apply_background,
## _configure_embers, _pulse_glow.
signal menu_activated(id: StringName)
@@ -15,6 +17,8 @@ var _sel: int = 0
@onready var _menu: VBoxContainer = $Content/Menu
@onready var _footer_left: Label = $FooterLeft
@onready var _background: ColorRect = $Background
@onready var _glow: ColorRect = $Glow
@onready var _embers: GPUParticles2D = $Embers
@onready var _rule: ColorRect = $Content/Rule
@@ -25,6 +29,10 @@ func _ready() -> void:
_background.color = Palette.STAGE_1
_rule.color = Palette.GOLD
_glow.color = Palette.BLOOD
_apply_background()
_configure_embers()
_pulse_glow()
_rows = []
for row in _menu.get_children():
@@ -42,6 +50,48 @@ func _fit_to_viewport() -> void:
size = get_viewport_rect().size
func _apply_background() -> void:
# Palette-driven shader material set in code (the DarkBay precedent) so no hex
# lives in the shader/scene; the editor shows the ColorRect fallback.
var mat := ShaderMaterial.new()
mat.shader = load("res://assets/theme/shaders/title_background.gdshader")
mat.set_shader_parameter("warm", Palette.TITLE_WARM)
mat.set_shader_parameter("mid", Palette.TITLE_MID)
mat.set_shader_parameter("edge", Palette.STAGE_1)
_background.material = mat
func _configure_embers() -> void:
var pm := ParticleProcessMaterial.new()
pm.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_BOX
pm.emission_box_extents = Vector3(760, 4, 0) # a wide strip along the bottom
pm.direction = Vector3(0, -1, 0)
pm.spread = 12.0
pm.gravity = Vector3(0, -18, 0)
pm.initial_velocity_min = 24.0
pm.initial_velocity_max = 52.0
pm.scale_min = 0.4
pm.scale_max = 1.0
pm.color = Palette.GOLD_BRIGHT
# fade out over life
var ramp := Gradient.new()
ramp.set_color(0, Color(Palette.GOLD_BRIGHT, 0.0))
ramp.set_color(1, Color(Palette.GOLD_BRIGHT, 0.0))
ramp.add_point(0.15, Palette.GOLD_BRIGHT)
var tex := GradientTexture1D.new()
tex.gradient = ramp
pm.color_ramp = tex
_embers.process_material = pm
func _pulse_glow() -> void:
# A slow warm pulse (the off-screen fire). A code tween, not an AnimationPlayer:
# a 2-keyframe modulate loop is trivial in code and motion is runtime-only anyway.
var tween := create_tween().set_loops().set_trans(Tween.TRANS_SINE)
tween.tween_property(_glow, "modulate:a", 0.8, 3.0)
tween.tween_property(_glow, "modulate:a", 0.5, 3.0)
func _wire_row_mouse() -> void:
for i in range(_rows.size()):
var idx := i # fresh per-iteration binding for the closures