extends RefCounted const DEEP_NAVY := Color("0b1324") const ROYAL_BLUE := Color("2563eb") const SKY_BLUE := Color("38bdf8") const PURPLE := Color("7c3aed") const MAGENTA := Color("d946ef") const TEAL := Color("14b8a6") const SUCCESS := Color("22c55e") const AMBER := Color("f59e0b") const SUN_ICON := preload("res://assets/icons/lucide/sun.svg") const MOON_ICON := preload("res://assets/icons/lucide/moon.svg") const SLATE := Color("94a3b8") const DARK_TEXT := Color("f8fafc") const LIGHT_TEXT := Color("0f172a") const LIGHT_BACKGROUND := Color("f1f5f9") const LIGHT_SURFACE := Color("ffffff") const LIGHT_SURFACE_SUBTLE := Color("f8fafc") const LIGHT_BORDER := Color("dbe3ef") const LIGHT_MUTED := Color("64748b") static var font: Font = preload("res://assets/fonts/Inter-Variable.ttf") static func text_color(dark: bool) -> Color: return DARK_TEXT if dark else LIGHT_TEXT static func muted_color(dark: bool) -> Color: return Color("a9b5ca") if dark else LIGHT_MUTED static func background_color(dark: bool) -> Color: return DEEP_NAVY if dark else LIGHT_BACKGROUND static func background_material(dark: bool) -> ShaderMaterial: var shader := Shader.new() shader.code = """ shader_type canvas_item; uniform bool dark_mode = true; void fragment() { vec2 uv = UV; vec3 base = dark_mode ? vec3(0.018, 0.043, 0.090) : vec3(0.965, 0.975, 0.990); float left_glow = 1.0 - smoothstep(0.0, 0.58, distance(uv, vec2(-0.02, 0.12))); float right_glow = 1.0 - smoothstep(0.0, 0.58, distance(uv, vec2(1.02, 0.92))); vec3 blue = dark_mode ? vec3(0.075, 0.055, 0.35) : vec3(0.70, 0.80, 1.0); vec3 purple = dark_mode ? vec3(0.18, 0.035, 0.52) : vec3(0.86, 0.78, 1.0); vec3 color = base + blue * left_glow * (dark_mode ? 0.55 : 0.28); color += purple * right_glow * (dark_mode ? 0.62 : 0.12); COLOR = vec4(color, 1.0); } """ var material := ShaderMaterial.new() material.shader = shader material.set_shader_parameter("dark_mode", dark) return material static func logo_cutout_material() -> ShaderMaterial: var shader := Shader.new() shader.code = """ shader_type canvas_item; void fragment() { vec4 source = texture(TEXTURE, UV); float brightness = max(source.r, max(source.g, source.b)); float alpha = smoothstep(0.10, 0.24, brightness); COLOR = vec4(source.rgb, source.a * alpha); } """ var material := ShaderMaterial.new() material.shader = shader return material static func panel_box(dark: bool) -> StyleBoxFlat: var box := StyleBoxFlat.new() box.bg_color = Color("0b1427cc") if dark else LIGHT_SURFACE box.border_color = Color("33415d") if dark else LIGHT_BORDER box.set_border_width_all(1) box.set_corner_radius_all(26) if not dark: box.shadow_color = Color(0.15, 0.23, 0.38, 0.12) box.shadow_size = 12 box.content_margin_left = 64 box.content_margin_right = 64 box.content_margin_top = 62 box.content_margin_bottom = 48 return box static func field_box(dark: bool, focused: bool = false) -> StyleBoxFlat: var box := StyleBoxFlat.new() box.bg_color = Color("0d172a") if dark else LIGHT_SURFACE box.border_color = ROYAL_BLUE if focused else (Color("33415d") if dark else LIGHT_BORDER) box.set_border_width_all(1) box.set_corner_radius_all(10) box.content_margin_left = 22 box.content_margin_right = 22 box.content_margin_top = 18 box.content_margin_bottom = 18 return box static func secondary_button_box(dark: bool, hover: bool = false) -> StyleBoxFlat: var box := field_box(dark, hover) box.bg_color = Color("111b30") if dark else Color("ffffff") return box static func shell_box(dark: bool, edge: String) -> StyleBoxFlat: var box := StyleBoxFlat.new() box.bg_color = Color("071224") if dark else LIGHT_SURFACE box.border_color = Color("1c2b49") if dark else LIGHT_BORDER if edge == "right": box.border_width_right = 1 elif edge == "bottom": box.border_width_bottom = 1 return box static func content_card_box(dark: bool, active: bool, accent: Color = ROYAL_BLUE) -> StyleBoxFlat: var box := StyleBoxFlat.new() box.bg_color = (Color("07192c") if active else Color("11182e")) if dark else LIGHT_SURFACE box.border_color = accent if active else (Color("2a365a") if dark else LIGHT_BORDER) box.set_border_width_all(1) box.set_corner_radius_all(12) if not dark: box.shadow_color = Color(0.15, 0.23, 0.38, 0.06) box.shadow_size = 5 return box static func icon_circle_box(dark: bool, accent: Color, radius: int, filled: bool = false) -> StyleBoxFlat: var box := StyleBoxFlat.new() box.bg_color = Color(accent, 0.62 if filled else (0.06 if dark else 0.045)) box.border_color = Color(accent, 0.48 if dark else 0.18) box.set_border_width_all(1) box.set_corner_radius_all(radius) return box static func badge_box(dark: bool, accent: Color) -> StyleBoxFlat: var box := StyleBoxFlat.new() box.bg_color = Color(accent, 0.12 if dark else 0.09) box.set_corner_radius_all(7) box.content_margin_left = 10 box.content_margin_right = 10 box.content_margin_top = 5 box.content_margin_bottom = 5 return box static func gradient_button_box() -> StyleBoxTexture: var gradient := Gradient.new() gradient.colors = PackedColorArray([Color("1677ff"), Color("8f3ff4")]) var texture := GradientTexture2D.new() texture.gradient = gradient texture.width = 512 texture.height = 64 texture.fill_from = Vector2(0, 0.5) texture.fill_to = Vector2(1, 0.5) var box := StyleBoxTexture.new() box.texture = texture box.content_margin_top = 18 box.content_margin_bottom = 18 return box static func apply_font(control: Control) -> void: control.add_theme_font_override("font", font) static func set_theme_toggle_icon(button: Button, dark: bool) -> void: button.text = "" button.icon = SUN_ICON if dark else MOON_ICON button.expand_icon = false button.add_theme_constant_override("icon_max_width", 22) for color_name in ["icon_normal_color", "icon_hover_color", "icon_pressed_color", "icon_focus_color"]: button.add_theme_color_override(color_name, AMBER)