extends VBoxContainer const Brand := preload("res://scripts/ui/medmind_style.gd") var main: Control var card: PanelContainer var eyebrow: Label var title: Label var detail: Label var start_button: Button var abandon_button: Button var error_label: Label var note: Label var abandon_armed := false func _ready() -> void: set_anchors_preset(Control.PRESET_FULL_RECT) add_theme_constant_override("separation", 16) if not ExamSimulationService.state_changed.is_connected(_on_state_changed): ExamSimulationService.state_changed.connect(_on_state_changed) if not ExamSimulationService.request_failed.is_connected(_on_failed): ExamSimulationService.request_failed.connect(_on_failed) card = PanelContainer.new() add_child(card) var card_margin := MarginContainer.new() card_margin.add_theme_constant_override("margin_left", 24) card_margin.add_theme_constant_override("margin_right", 24) card_margin.add_theme_constant_override("margin_top", 22) card_margin.add_theme_constant_override("margin_bottom", 22) card.add_child(card_margin) var card_content := VBoxContainer.new() card_content.add_theme_constant_override("separation", 10) card_margin.add_child(card_content) eyebrow = Label.new() eyebrow.text = "STEP 3 DAY 1 SIMULATION" eyebrow.add_theme_font_override("font", Brand.font) eyebrow.add_theme_font_size_override("font_size", 13) card_content.add_child(eyebrow) title = Label.new() title.add_theme_font_override("font", Brand.font) title.add_theme_font_size_override("font_size", 26) card_content.add_child(title) detail = Label.new() detail.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART detail.add_theme_font_override("font", Brand.font) detail.add_theme_font_size_override("font_size", 15) card_content.add_child(detail) var actions := HBoxContainer.new() actions.add_theme_constant_override("separation", 12) card_content.add_child(actions) start_button = Button.new() start_button.custom_minimum_size = Vector2(220, 52) start_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN _style_gradient_button(start_button) start_button.pressed.connect(_start_block) actions.add_child(start_button) abandon_button = Button.new() abandon_button.text = "Abandon simulation" abandon_button.custom_minimum_size = Vector2(200, 52) abandon_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN abandon_button.pressed.connect(_abandon) actions.add_child(abandon_button) error_label = Label.new() error_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART error_label.add_theme_font_override("font", Brand.font) error_label.add_theme_font_size_override("font_size", 13) error_label.add_theme_color_override("font_color", Brand.MAGENTA) add_child(error_label) note = Label.new() note.text = "Each block runs for 30 minutes. You may review and change answers within the active block, but closed blocks cannot be reopened. Leaving during a block does not stop its server timer." note.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART note.add_theme_font_override("font", Brand.font) note.add_theme_font_size_override("font_size", 12) add_child(note) var spacer := Control.new() spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL add_child(spacer) UIThemeService.theme_changed.connect(func(_dark: bool): refresh_theme_controls()) refresh_theme_controls() func refresh_theme_controls() -> void: var dark := UIThemeService.is_dark_mode() card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, true, Brand.ROYAL_BLUE)) eyebrow.add_theme_color_override("font_color", Brand.SKY_BLUE) title.add_theme_color_override("font_color", Brand.text_color(dark)) detail.add_theme_color_override("font_color", Brand.muted_color(dark)) note.add_theme_color_override("font_color", Brand.muted_color(dark)) _style_outline_button(abandon_button, Brand.MAGENTA, dark) func on_show(_payload: Variant = null) -> void: abandon_armed = false _refresh() func _refresh() -> void: var block := int(ExamSimulationService.simulation.get("currentBlock", -1)) var break_seconds := int(ExamSimulationService.simulation.get("breakSecondsRemaining", 3300)) if block < 0: title.text = "Foundations of Independent Practice" detail.text = "232 questions across 12 timed blocks. Results and explanations remain hidden until the simulation is complete. Estimated break pool: %d minutes." % int(break_seconds / 60.0) else: title.text = "Block %d complete" % (block + 1) detail.text = "%d of 12 blocks complete. Your progress is saved. Break pool: approximately %d minutes." % [block + 1, int(break_seconds / 60.0)] start_button.text = "Start Block %d" % (block + 2) start_button.disabled = ExamSimulationService.is_loading abandon_button.text = "Confirm abandon" if abandon_armed else "Abandon simulation" abandon_button.disabled = ExamSimulationService.is_loading error_label.text = "" func _start_block() -> void: start_button.disabled = true start_button.text = "Preparing..." ExamSimulationService.start_next_block() func _abandon() -> void: if not abandon_armed: abandon_armed = true abandon_button.text = "Confirm abandon" error_label.text = "This permanently closes the current simulation. Select Confirm abandon to continue." return abandon_button.disabled = true ExamSimulationService.abandon() func _on_state_changed() -> void: if not visible: return if ExamSimulationService.simulation.is_empty(): main.show_screen("selection", "step3") return if str(ExamSimulationService.simulation.get("state", "")) != "block_active": _refresh() return 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") func _on_failed(message: String) -> void: if not visible: return error_label.text = message start_button.disabled = false _refresh() 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 := StyleBoxFlat.new() normal.bg_color = Color(0, 0, 0, 0) normal.border_color = accent normal.set_border_width_all(1) normal.set_corner_radius_all(10) var hover := StyleBoxFlat.new() hover.bg_color = Color(accent, 0.12) hover.border_color = accent hover.set_border_width_all(1) hover.set_corner_radius_all(10) 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)