extends VBoxContainer const Style := preload("res://scripts/ui/ui_style.gd") const Brand := preload("res://scripts/ui/medmind_style.gd") const CLOCK_ICON := preload("res://assets/icons/lucide/clock-3.svg") const REVIEW_ICON := preload("res://assets/icons/lucide/chart-no-axes-column-increasing.svg") var main: Control var page: VBoxContainer var active_section: PanelContainer var active_content: VBoxContainer var history_section: PanelContainer var history_header: BoxContainer var table_header: HBoxContainer var history_list: VBoxContainer var status_label: Label var load_more_button: Button var filter_option: OptionButton var sort_option: OptionButton var abandon_dialog: ConfirmationDialog var pending_simulation_id := "" var pending_launch := false var pending_abandon := false var history_status := "all" var history_sort := "newest" var compact_layout := false func _ready() -> void: set_anchors_preset(Control.PRESET_FULL_RECT) size_flags_horizontal = Control.SIZE_EXPAND_FILL size_flags_vertical = Control.SIZE_EXPAND_FILL add_theme_constant_override("separation", 0) _build_ui() _connect_services() resized.connect(_update_responsive_layout) UIThemeService.theme_changed.connect(func(_dark: bool): refresh_theme_controls()) call_deferred("_update_responsive_layout") func _build_ui() -> void: var scroll := ScrollContainer.new() scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED add_child(scroll) page = VBoxContainer.new() page.size_flags_horizontal = Control.SIZE_EXPAND_FILL page.add_theme_constant_override("separation", 18) scroll.add_child(page) active_section = PanelContainer.new() active_section.name = "ActiveSimulationSection" page.add_child(active_section) active_content = VBoxContainer.new() active_content.add_theme_constant_override("separation", 10) active_section.add_child(active_content) history_section = PanelContainer.new() history_section.name = "SimulationHistorySection" history_section.size_flags_vertical = Control.SIZE_EXPAND_FILL page.add_child(history_section) var history_content := VBoxContainer.new() history_content.add_theme_constant_override("separation", 12) history_section.add_child(history_content) history_header = BoxContainer.new() history_header.vertical = false history_header.add_theme_constant_override("separation", 10) history_content.add_child(history_header) var heading := Label.new() heading.text = "Simulation History" heading.size_flags_horizontal = Control.SIZE_EXPAND_FILL Brand.apply_font(heading) heading.add_theme_font_size_override("font_size", 21) history_header.add_child(heading) filter_option = OptionButton.new() filter_option.name = "HistoryFilter" filter_option.custom_minimum_size = Vector2(190, 42) filter_option.add_item("All Simulations") filter_option.set_item_metadata(0, "all") filter_option.add_item("Completed") filter_option.set_item_metadata(1, "completed") filter_option.add_item("Abandoned") filter_option.set_item_metadata(2, "abandoned") filter_option.item_selected.connect(_on_filter_selected) history_header.add_child(filter_option) sort_option = OptionButton.new() sort_option.name = "HistorySort" sort_option.custom_minimum_size = Vector2(170, 42) sort_option.add_item("Newest First") sort_option.set_item_metadata(0, "newest") sort_option.add_item("Oldest First") sort_option.set_item_metadata(1, "oldest") sort_option.item_selected.connect(_on_sort_selected) history_header.add_child(sort_option) status_label = Label.new() status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART Brand.apply_font(status_label) status_label.add_theme_font_size_override("font_size", 15) history_content.add_child(status_label) table_header = _build_table_header() history_content.add_child(table_header) history_list = VBoxContainer.new() history_list.add_theme_constant_override("separation", 0) history_content.add_child(history_list) load_more_button = Button.new() load_more_button.text = "Show more" load_more_button.visible = false load_more_button.custom_minimum_size = Vector2(150, 42) load_more_button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER load_more_button.pressed.connect(_load_more) history_content.add_child(load_more_button) abandon_dialog = ConfirmationDialog.new() abandon_dialog.title = "Abandon simulation?" abandon_dialog.dialog_text = "This attempt cannot be resumed and will not receive a predicted score. Completed blocks will remain visible in your history." abandon_dialog.ok_button_text = "Abandon Simulation" abandon_dialog.cancel_button_text = "Keep Simulation" abandon_dialog.confirmed.connect(_confirm_abandon) add_child(abandon_dialog) refresh_theme_controls() func _connect_services() -> void: if not ExamSimulationService.history_changed.is_connected(_render_history): ExamSimulationService.history_changed.connect(_render_history) if not ExamSimulationService.readiness_changed.is_connected(_render_active_section): ExamSimulationService.readiness_changed.connect(_render_active_section) if not ExamSimulationService.state_changed.is_connected(_on_simulation_loaded): ExamSimulationService.state_changed.connect(_on_simulation_loaded) if not ExamSimulationService.request_failed.is_connected(_on_request_failed): ExamSimulationService.request_failed.connect(_on_request_failed) if not ExamSimulationService.exam_token_required.is_connected(_on_exam_token_required): ExamSimulationService.exam_token_required.connect(_on_exam_token_required) func on_show(_payload: Variant = null) -> void: pending_simulation_id = "" pending_launch = false pending_abandon = false status_label.text = "Loading simulation history..." load_more_button.disabled = true ExamSimulationService.load_readiness() ExamSimulationService.load_active() _load_history_page() func refresh_theme_controls() -> void: if page == null: return var dark := UIThemeService.is_dark_mode() active_section.add_theme_stylebox_override("panel", _section_box(dark)) history_section.add_theme_stylebox_override("panel", _section_box(dark)) status_label.add_theme_color_override("font_color", Brand.muted_color(dark)) _style_option(filter_option, dark) _style_option(sort_option, dark) _style_outline_button(load_more_button, Brand.PURPLE, dark) _render_active_section() _render_history() func _render_active_section() -> void: if active_content == null: return _clear(active_content) var dark := UIThemeService.is_dark_mode() var section_title := Label.new() section_title.text = "Active Simulation" if _has_active_simulation() else "Step 3 Simulation" Brand.apply_font(section_title) section_title.add_theme_font_size_override("font_size", 18) section_title.add_theme_color_override("font_color", Brand.PURPLE if not dark else Color("9b73ff")) active_content.add_child(section_title) var card := PanelContainer.new() card.add_theme_stylebox_override("panel", _active_card_box(dark)) active_content.add_child(card) var card_layout := BoxContainer.new() card_layout.name = "ActiveCardLayout" card_layout.vertical = compact_layout card_layout.add_theme_constant_override("separation", 18) card.add_child(card_layout) var icon_panel := PanelContainer.new() icon_panel.custom_minimum_size = Vector2(94, 94) icon_panel.size_flags_vertical = Control.SIZE_SHRINK_CENTER icon_panel.add_theme_stylebox_override("panel", Brand.icon_circle_box(dark, Brand.PURPLE, 8)) card_layout.add_child(icon_panel) var clock := TextureRect.new() clock.texture = CLOCK_ICON clock.custom_minimum_size = Vector2(44, 44) clock.expand_mode = TextureRect.EXPAND_IGNORE_SIZE clock.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED clock.modulate = Brand.PURPLE if not dark else Color("9668ff") icon_panel.add_child(clock) var details := VBoxContainer.new() details.size_flags_horizontal = Control.SIZE_EXPAND_FILL details.add_theme_constant_override("separation", 5) card_layout.add_child(details) var active := _has_active_simulation() var heading := Label.new() heading.text = "Step 3 Day 1 Simulation" Brand.apply_font(heading) heading.add_theme_font_size_override("font_size", 19) heading.add_theme_color_override("font_color", Brand.text_color(dark)) details.add_child(heading) if active: _build_active_details(details, dark) else: _build_start_details(details, dark) var actions := VBoxContainer.new() actions.name = "ActiveActions" actions.custom_minimum_size = Vector2(265, 0) if not compact_layout else Vector2.ZERO actions.size_flags_vertical = Control.SIZE_SHRINK_CENTER actions.add_theme_constant_override("separation", 10) card_layout.add_child(actions) var primary := Button.new() primary.name = "PrimarySimulationAction" primary.text = "Resume Simulation" if active else "Start Simulation" primary.custom_minimum_size = Vector2(0, 48) primary.disabled = pending_launch or (not active and not bool(ExamSimulationService.readiness.get("ready", false))) _style_gradient_button(primary) primary.pressed.connect(_launch_or_resume) actions.add_child(primary) if active: var abandon := Button.new() abandon.name = "AbandonSimulation" abandon.text = "Abandon Simulation" abandon.custom_minimum_size = Vector2(0, 46) _style_outline_button(abandon, Color("ff5f76"), dark) abandon.pressed.connect(func(): abandon_dialog.popup_centered(Vector2i(520, 220))) actions.add_child(abandon) var notice := PanelContainer.new() notice.add_theme_stylebox_override("panel", _notice_box(dark)) active_content.add_child(notice) var notice_label := Label.new() notice_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART notice_label.text = "Abandoning will end your active simulation; completed blocks remain in history, but no score will be generated." if active else _readiness_message() Brand.apply_font(notice_label) notice_label.add_theme_font_size_override("font_size", 14) notice_label.add_theme_color_override("font_color", Brand.muted_color(dark)) notice.add_child(notice_label) func _build_active_details(details: VBoxContainer, dark: bool) -> void: var simulation := ExamSimulationService.simulation var summary := _active_history_summary() var started := str(summary.get("createdUtc", "")) var started_label := Label.new() started_label.text = "Started: %s" % _format_local_datetime(started) _style_muted_label(started_label, dark, 14) details.add_child(started_label) var progress_title := Label.new() progress_title.text = "Progress" _style_muted_label(progress_title, dark, 13) details.add_child(progress_title) var completed := completed_blocks(simulation if not simulation.is_empty() else summary) var total := int((simulation if not simulation.is_empty() else summary).get("totalBlocks", 12)) var progress_row := HBoxContainer.new() progress_row.add_theme_constant_override("separation", 12) details.add_child(progress_row) var bar := ProgressBar.new() bar.show_percentage = false bar.max_value = max(1, total) bar.value = completed bar.custom_minimum_size = Vector2(120, 8) bar.size_flags_horizontal = Control.SIZE_EXPAND_FILL bar.size_flags_vertical = Control.SIZE_SHRINK_CENTER bar.add_theme_stylebox_override("background", _progress_box(Color("28344f") if dark else Color("e2e8f0"))) bar.add_theme_stylebox_override("fill", _progress_box(Brand.PURPLE)) progress_row.add_child(bar) var percent := Label.new() percent.text = "%d%%" % progress_percent(completed, total) _style_muted_label(percent, dark, 13) progress_row.add_child(percent) var progress_copy := Label.new() progress_copy.text = "%d of %d blocks complete • %s" % [completed, total, remaining_time_text(simulation if not simulation.is_empty() else summary)] _style_muted_label(progress_copy, dark, 14) details.add_child(progress_copy) func _build_start_details(details: VBoxContainer, dark: bool) -> void: var copy := Label.new() copy.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART copy.text = "A full-length, 12-block simulation with a provisional predicted Step 3 score. Progress is saved between blocks." _style_muted_label(copy, dark, 15) details.add_child(copy) func _render_history() -> void: if history_list == null or not visible: return _clear(history_list) var items: Array = ExamSimulationService.history if items.is_empty(): status_label.text = "No simulations match this filter." else: status_label.text = "" for raw_summary in items: var summary: Dictionary = raw_summary if str(summary.get("state", "")) in ["block_active", "between_blocks"]: continue history_list.add_child(_build_history_row(summary)) load_more_button.visible = not ExamSimulationService.history_next_cursor.is_empty() load_more_button.disabled = false table_header.visible = not compact_layout and not items.is_empty() func _build_table_header() -> HBoxContainer: var dark := UIThemeService.is_dark_mode() var row := HBoxContainer.new() row.add_theme_constant_override("separation", 10) for definition in [["Simulation", 2.2], ["Date", 1.15], ["Status", 1.0], ["Blocks", 0.8], ["Score", 1.15], ["Pass chance", 1.0], ["Actions", 1.0]]: var label := Label.new() label.text = definition[0] label.size_flags_horizontal = Control.SIZE_EXPAND_FILL label.size_flags_stretch_ratio = definition[1] Brand.apply_font(label) label.add_theme_font_size_override("font_size", 13) label.add_theme_color_override("font_color", Brand.muted_color(dark)) row.add_child(label) return row func _build_history_row(summary: Dictionary) -> Control: var dark := UIThemeService.is_dark_mode() var panel := PanelContainer.new() panel.add_theme_stylebox_override("panel", _row_box(dark)) var state := str(summary.get("state", "")) if compact_layout: var card := VBoxContainer.new() card.add_theme_constant_override("separation", 8) panel.add_child(card) var top := HBoxContainer.new() card.add_child(top) top.add_child(_simulation_cell(summary, dark)) var badge := _status_badge(state, dark) top.add_child(badge) var metrics := Label.new() metrics.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART metrics.text = "%s • %d/%d blocks\n%s • %s" % [_format_local_datetime(str(summary.get("createdUtc", ""))), completed_blocks(summary), int(summary.get("totalBlocks", 12)), _score_text(summary), _pass_chance_text(summary)] _style_muted_label(metrics, dark, 14) card.add_child(metrics) if state == "completed": card.add_child(_review_button(summary, dark)) return panel var row := HBoxContainer.new() row.custom_minimum_size = Vector2(0, 66) row.add_theme_constant_override("separation", 10) panel.add_child(row) _add_table_cell(row, _simulation_cell(summary, dark), 2.2) _add_table_cell(row, _plain_cell(_format_local_datetime(str(summary.get("createdUtc", ""))), dark), 1.15) _add_table_cell(row, _status_badge(state, dark), 1.0) _add_table_cell(row, _plain_cell("%d / %d" % [completed_blocks(summary), int(summary.get("totalBlocks", 12))], dark), 0.8) _add_table_cell(row, _plain_cell(_score_text(summary), dark), 1.15) _add_table_cell(row, _plain_cell(_pass_chance_text(summary), dark), 1.0) var action: Control if state == "completed": action = _review_button(summary, dark) else: action = _plain_cell("-", dark) _add_table_cell(row, action, 1.0) return panel func _simulation_cell(summary: Dictionary, dark: bool) -> Control: var column := VBoxContainer.new() column.size_flags_horizontal = Control.SIZE_EXPAND_FILL var name_label := Label.new() name_label.text = "Step 3 Day 1 Simulation%s" % (" (Sample)" if bool(summary.get("isSample", false)) else "") Brand.apply_font(name_label) name_label.add_theme_font_size_override("font_size", 15) name_label.add_theme_color_override("font_color", Brand.text_color(dark)) column.add_child(name_label) var blocks := Label.new() blocks.text = "%d blocks" % int(summary.get("totalBlocks", 12)) _style_muted_label(blocks, dark, 12) column.add_child(blocks) return column func _plain_cell(text_value: String, dark: bool) -> Label: var label := Label.new() label.text = text_value label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART Brand.apply_font(label) label.add_theme_font_size_override("font_size", 14) label.add_theme_color_override("font_color", Brand.text_color(dark)) return label func _status_badge(state: String, dark: bool) -> Label: var label := Label.new() var accent := Brand.SUCCESS if state == "completed" else Brand.SLATE label.text = "Completed" if state == "completed" else "Abandoned" label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER label.size_flags_vertical = Control.SIZE_SHRINK_CENTER Brand.apply_font(label) label.add_theme_font_size_override("font_size", 12) label.add_theme_color_override("font_color", accent if dark else accent.darkened(0.2)) label.add_theme_stylebox_override("normal", Brand.badge_box(dark, accent)) return label func _review_button(summary: Dictionary, dark: bool) -> Button: var button := Button.new() button.text = "Review" button.icon = REVIEW_ICON button.expand_icon = true button.add_theme_constant_override("icon_max_width", 17) button.custom_minimum_size = Vector2(105, 38) button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN _style_outline_button(button, Brand.PURPLE, dark) button.pressed.connect(_open_simulation.bind(str(summary.get("id", "")))) return button func _add_table_cell(row: HBoxContainer, control: Control, ratio: float) -> void: control.size_flags_horizontal = Control.SIZE_EXPAND_FILL control.size_flags_vertical = Control.SIZE_SHRINK_CENTER control.size_flags_stretch_ratio = ratio row.add_child(control) func _open_simulation(simulation_id: String) -> void: pending_launch = false pending_simulation_id = simulation_id status_label.text = "Opening simulation results..." ExamSimulationService.load_simulation(simulation_id) func _on_simulation_loaded() -> void: if not visible: return if pending_abandon: if ExamSimulationService.simulation.is_empty(): pending_abandon = false ExamSimulationService.load_readiness() ExamSimulationService.load_active() _load_history_page() _render_active_section() return if pending_launch: pending_launch = false _route_loaded_simulation() return if not pending_simulation_id.is_empty(): pending_simulation_id = "" _route_loaded_simulation() return _render_active_section() func _route_loaded_simulation() -> void: if ExamSimulationService.simulation.is_empty(): _on_request_failed("The simulation is no longer available.") return var state := str(ExamSimulationService.simulation.get("state", "")) if state == "completed": main.show_screen("simulation_results", {"simulation": ExamSimulationService.simulation, "return_screen": "simulation_history"}) elif state == "block_active": BlockSessionService.start_simulation_block( ExamSimulationService.get_current_questions(), int(ExamSimulationService.simulation.get("currentBlock", 0)), int(ExamSimulationService.simulation.get("totalBlocks", 12)), ExamSimulationService.get_block_deadline_unix()) BlockSessionService.restore_simulation_responses(ExamSimulationService.simulation.get("responses", [])) main.show_screen("player") elif state == "between_blocks": main.show_screen("simulation_break") else: _on_request_failed("This simulation can no longer be resumed.") func _launch_or_resume() -> void: pending_launch = true pending_simulation_id = "" _render_active_section() if _has_active_simulation(): if not ExamSimulationService.simulation.is_empty(): pending_launch = false _route_loaded_simulation() else: ExamSimulationService.load_active() else: ExamSimulationService.create_simulation() func _confirm_abandon() -> void: if not _has_active_simulation() or ExamSimulationService.simulation.is_empty(): return pending_abandon = true ExamSimulationService.abandon() _render_active_section() func _on_filter_selected(index: int) -> void: history_status = str(filter_option.get_item_metadata(index)) _load_history_page() func _on_sort_selected(index: int) -> void: history_sort = str(sort_option.get_item_metadata(index)) _load_history_page() func _load_history_page() -> void: status_label.text = "Loading simulation history..." load_more_button.disabled = true ExamSimulationService.load_history("", history_status, history_sort) func _load_more() -> void: if ExamSimulationService.history_next_cursor.is_empty(): return load_more_button.disabled = true ExamSimulationService.load_history(ExamSimulationService.history_next_cursor, history_status, history_sort) func _on_request_failed(message: String) -> void: if not visible: return pending_launch = false pending_simulation_id = "" pending_abandon = false status_label.text = message load_more_button.disabled = false _render_active_section() func _on_exam_token_required() -> void: pending_launch = false main.show_screen("rz_store") func _update_responsive_layout() -> void: var use_compact := size.x < 940 if compact_layout == use_compact: return compact_layout = use_compact history_header.vertical = compact_layout filter_option.size_flags_horizontal = Control.SIZE_EXPAND_FILL if compact_layout else Control.SIZE_SHRINK_END sort_option.size_flags_horizontal = Control.SIZE_EXPAND_FILL if compact_layout else Control.SIZE_SHRINK_END _render_active_section() _render_history() func _has_active_simulation() -> bool: return has_active_simulation(ExamSimulationService.readiness, ExamSimulationService.simulation) func _active_history_summary() -> Dictionary: for raw_summary in ExamSimulationService.history: var summary: Dictionary = raw_summary if str(summary.get("state", "")) in ["block_active", "between_blocks"]: return summary return ExamSimulationService.simulation func _readiness_message() -> String: if bool(ExamSimulationService.readiness.get("ready", false)): return "The question bank is ready. Start when you have enough uninterrupted time for a full simulation." var available := int(ExamSimulationService.readiness.get("availableQuestions", 0)) var required := int(ExamSimulationService.readiness.get("requiredQuestions", 232)) return "Simulation unlocks when %d published questions are available. Currently %d/%d." % [required, available, required] func _score_text(summary: Dictionary) -> String: if summary.get("predictedScore", null) == null: return "—" return "%d (%d–%d)" % [int(summary.get("predictedScore", 0)), int(summary.get("rangeLow", 0)), int(summary.get("rangeHigh", 0))] func _pass_chance_text(summary: Dictionary) -> String: if summary.get("passProbability", null) == null: return "—" return "%d%%" % int(round(float(summary.get("passProbability", 0.0)) * 100.0)) func _format_local_datetime(value: String) -> String: if value.is_empty(): return "—" var unix := Time.get_unix_time_from_datetime_string(value) var timezone: Dictionary = Time.get_time_zone_from_system() var local := Time.get_datetime_dict_from_unix_time(int(unix) + int(timezone.get("bias", 0)) * 60) return format_datetime_parts(local) static func format_datetime_parts(parts: Dictionary) -> String: const MONTHS := ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] var month_index: int = clamp(int(parts.get("month", 1)) - 1, 0, 11) var hour_24 := int(parts.get("hour", 0)) var suffix := "AM" if hour_24 < 12 else "PM" var hour_12 := hour_24 % 12 if hour_12 == 0: hour_12 = 12 return "%s %d, %d\n%d:%02d %s" % [MONTHS[month_index], int(parts.get("day", 1)), int(parts.get("year", 1970)), hour_12, int(parts.get("minute", 0)), suffix] static func completed_blocks(summary: Dictionary) -> int: var state := str(summary.get("state", "")) var current := int(summary.get("currentBlock", -1)) var total: int = max(0, int(summary.get("totalBlocks", 12))) if state == "completed": return total if state == "between_blocks": return clamp(current + 1, 0, total) if state == "block_active": return clamp(current, 0, total) return clamp(current + 1, 0, total) static func progress_percent(completed: int, total: int) -> int: if total <= 0: return 0 return int(round(float(completed) * 100.0 / float(total))) static func remaining_time_text(summary: Dictionary, now_unix: float = -1.0) -> String: var total: int = max(0, int(summary.get("totalBlocks", 12))) var current := int(summary.get("currentBlock", -1)) var state := str(summary.get("state", "")) var seconds := 0 if state == "block_active": var future_blocks: int = max(0, total - current - 1) seconds = future_blocks * 1800 var deadline := str(summary.get("blockDeadlineUtc", "")) if not deadline.is_empty(): var current_unix := Time.get_unix_time_from_system() if now_unix < 0.0 else now_unix seconds += max(0, int(Time.get_unix_time_from_datetime_string(deadline) - current_unix)) else: seconds += 1800 else: seconds = max(0, total - completed_blocks(summary)) * 1800 var hours := int(floor(float(seconds) / 3600.0)) var minutes := int(floor(float(seconds % 3600) / 60.0)) if hours > 0 and minutes > 0: return "~%dh %dm remaining" % [hours, minutes] if hours > 0: return "~%dh remaining" % hours return "~%dm remaining" % minutes static func has_active_simulation(readiness: Dictionary, simulation: Dictionary) -> bool: if bool(readiness.get("hasActiveSimulation", false)): return true if simulation.is_empty(): return false return str(simulation.get("state", "")) in ["between_blocks", "block_active"] static func _action_for_state(state: String) -> String: if state == "completed": return "results" if state == "block_active" or state == "between_blocks": return "resume" return "none" func _style_muted_label(label: Label, dark: bool, font_size: int) -> void: Brand.apply_font(label) label.add_theme_font_size_override("font_size", font_size) label.add_theme_color_override("font_color", Brand.muted_color(dark)) func _style_option(option: OptionButton, dark: bool) -> void: Brand.apply_font(option) option.add_theme_font_size_override("font_size", 14) option.add_theme_color_override("font_color", Brand.text_color(dark)) for state in ["normal", "hover", "pressed"]: option.add_theme_stylebox_override(state, Brand.secondary_button_box(dark, state != "normal")) func _style_gradient_button(button: Button) -> void: Brand.apply_font(button) button.add_theme_font_size_override("font_size", 15) button.add_theme_stylebox_override("normal", Brand.gradient_button_box()) button.add_theme_stylebox_override("hover", Brand.gradient_button_box()) button.add_theme_stylebox_override("pressed", Brand.gradient_button_box()) button.add_theme_color_override("font_color", Color.WHITE) button.add_theme_color_override("font_hover_color", Color.WHITE) func _style_outline_button(button: Button, accent: Color, dark: bool) -> void: Brand.apply_font(button) button.add_theme_font_size_override("font_size", 14) var normal := Style.button_box(Color(0, 0, 0, 0), accent) var hover := Style.button_box(Color(accent, 0.12), accent) button.add_theme_stylebox_override("normal", normal) button.add_theme_stylebox_override("hover", hover) button.add_theme_stylebox_override("pressed", hover) button.add_theme_color_override("font_color", accent if dark else accent.darkened(0.08)) button.add_theme_color_override("font_hover_color", accent) button.add_theme_color_override("icon_normal_color", accent) func _section_box(dark: bool) -> StyleBoxFlat: var box := Brand.content_card_box(dark, false) box.content_margin_left = 16 box.content_margin_right = 16 box.content_margin_top = 14 box.content_margin_bottom = 14 return box func _active_card_box(dark: bool) -> StyleBoxFlat: var box := Brand.content_card_box(dark, true, Color("7455ff")) box.bg_color = Color("101a38") if dark else Color("f8f7ff") box.border_color = Color("3d326f") if dark else Color("d9d1ff") box.content_margin_left = 16 box.content_margin_right = 16 box.content_margin_top = 16 box.content_margin_bottom = 16 return box func _notice_box(dark: bool) -> StyleBoxFlat: var box := StyleBoxFlat.new() box.bg_color = Color("101a32") if dark else Color("f5f7ff") box.border_color = Color("29365d") if dark else Color("dbe3f4") box.set_border_width_all(1) box.set_corner_radius_all(7) box.content_margin_left = 12 box.content_margin_right = 12 box.content_margin_top = 9 box.content_margin_bottom = 9 return box func _row_box(dark: bool) -> StyleBoxFlat: var box := StyleBoxFlat.new() box.bg_color = Color("0c162b") if dark else Color("ffffff") box.border_color = Color("273450") if dark else Color("e3e8f1") box.border_width_top = 1 box.content_margin_left = 12 box.content_margin_right = 12 box.content_margin_top = 10 box.content_margin_bottom = 10 return box func _progress_box(color: Color) -> StyleBoxFlat: var box := StyleBoxFlat.new() box.bg_color = color box.set_corner_radius_all(4) return box func _clear(container: Node) -> void: for child in container.get_children(): container.remove_child(child) child.queue_free()