import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox, font
import customtkinter as ctk
import requests
import re
from urllib.parse import urlparse, parse_qs
import threading
import os
import sys
from pathlib import Path
import xml.etree.ElementTree as ET
from dotenv import load_dotenv
from PIL import Image, ImageTk, ImageDraw, ImageFilter
import time
import webbrowser
from update_checker import UpdateChecker
from update_config import get_update_config
import html
from ui_theme import (
    COLORS,
    FONT_FAMILY,
    create_striped_panel,
    create_striped_texture,
    load_cover_background,
    style_text_button,
    center_window,
)

# Load environment variables
load_dotenv()

# Set CustomTkinter appearance with new color scheme
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("dark-blue")

def get_resource_path(relative_path):
    """Get absolute path to resource, works for dev and for PyInstaller"""
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")
    
    return os.path.join(base_path, relative_path)

class LoadingScreen:
    def __init__(self, root, on_complete_callback):
        self.root = root
        self.on_complete_callback = on_complete_callback
        self.current_stage = 0
        self.expansion_check_results = {}
        
        # Load custom font first
        self.load_custom_font()
        
        # Create fullscreen loading window using CTkToplevel
        self.loading_window = ctk.CTkToplevel(root)
        self.loading_window.title("Progression: Loader")
        
        # Set window icon
        try:
            icon_path = get_resource_path(os.path.join("art", "Progression.ico"))
            if os.path.exists(icon_path):
                self.loading_window.iconbitmap(icon_path)
        except Exception as e:
            print(f"Could not load icon for loading window: {e}")
            
        self.loading_window.attributes('-topmost', True)
        
        # Make it fullscreen initially, but allow free resizing
        self.loading_window.state('zoomed')
        
        # Set minimum size for loading window too
        self.loading_window.minsize(800, 600)
        
        # Bind to enforce minimum size for loading window
        self.loading_window.bind('<Configure>', self._enforce_loading_minimum_size)
        
        self.loading_window.protocol("WM_DELETE_WINDOW", self.on_close)
        
        # Load images
        self.load_images()
        
        # Create UI elements
        self.create_loading_ui()
        
        # Start the loading sequence
        self.start_loading_sequence()
    
    def load_custom_font(self):
        """Load the RimWorld font using Windows AddFontResourceEx with private flag"""
        try:
            font_path = get_resource_path(os.path.join("art", "georgia.ttf"))
            if os.path.exists(font_path):
                abs_font_path = os.path.abspath(font_path)
                
                # Use the Stack Overflow method with AddFontResourceEx
                success = self._load_font_private(abs_font_path)
                
                if success:
                    # Use the actual font family name from the TTF file
                    self.custom_font_family = "Georgia"
                    self.custom_font_available = True
                    return
                    
                else:
                    self.custom_font_available = False
                    
            else:
                self.custom_font_available = False
                
        except Exception as e:
            self.custom_font_available = False
        
        # Initialize fallback values if font loading failed
        if not hasattr(self, 'custom_font_available'):
            self.custom_font_available = False
        if not hasattr(self, 'custom_font_family'):
            self.custom_font_family = None
    
    def _load_font_private(self, fontpath):
        """
        Load font privately using AddFontResourceEx
        Based on Stack Overflow solution by Felipe
        """
        try:
            from ctypes import windll, byref, create_unicode_buffer
            
            # Constants for AddFontResourceEx
            FR_PRIVATE = 0x10   # Font is private to this process
            FR_NOT_ENUM = 0x20  # Font won't appear in font enumeration
            
            # Create unicode buffer for the font path
            pathbuf = create_unicode_buffer(fontpath)
            
            # Use AddFontResourceExW for Unicode strings
            AddFontResourceEx = windll.gdi32.AddFontResourceExW
            
            # Set flags: private (unloaded when process dies) and not enumerable
            flags = FR_PRIVATE | FR_NOT_ENUM
            
            # Add the font resource
            numFontsAdded = AddFontResourceEx(byref(pathbuf), flags, 0)
            
            return bool(numFontsAdded)
            
        except Exception:
            return False
    
    def get_font(self, size=10, weight='normal', slant='roman'):
        """Get font tuple for UI elements"""
        if weight in {'italic', 'roman'}:
            slant = weight
            weight = 'normal'
        elif weight in {'bold italic', 'italic bold'}:
            weight = 'bold'
            slant = 'italic'

        if self.custom_font_available and self.custom_font_family:
            try:
                return font.Font(
                    family=self.custom_font_family,
                    size=size,
                    weight=weight,
                    slant=slant,
                )
            except Exception:
                pass
        
        # Fall back to system font
        return font.Font(family=FONT_FAMILY, size=size, weight=weight, slant=slant)
    
    def create_radial_glow_image(self, base_image):
        """Create a radial glow effect around the image with #ff8686 color"""
        # Get base image dimensions
        base_width, base_height = base_image.size
        
        # Create larger canvas for glow effect
        glow_padding = 15
        glow_width = base_width + (glow_padding * 2)
        glow_height = base_height + (glow_padding * 2)
        
        # Create glow background matching the label background
        glow_bg = Image.new('RGBA', (glow_width, glow_height), (43, 43, 43, 255))  # #2b2b2b background
        
        # Create radial glow mask
        glow_mask = Image.new('L', (glow_width, glow_height), 0)
        draw = ImageDraw.Draw(glow_mask)
        
        # Calculate center and create multiple circles for smooth gradient
        center_x, center_y = glow_width // 2, glow_height // 2
        
        # Create glow that extends beyond the original image
        max_radius = glow_padding + max(base_width, base_height) // 2
        
        # Create radial gradient by drawing concentric circles
        for i in range(max_radius, 0, -1):
            # Calculate alpha based on distance from center (stronger in center)
            # Make the glow more intense closer to the logo
            if i <= max(base_width, base_height) // 2:
                # Inner area (around the logo) - stronger glow
                alpha = int(255 * 0.8)
            else:
                # Outer area - fade out
                fade_factor = (max_radius - i) / (max_radius - max(base_width, base_height) // 2)
                alpha = int(255 * fade_factor * 0.6)
            
            if alpha > 0:
                draw.ellipse([center_x - i, center_y - i, center_x + i, center_y + i], fill=alpha)
        
        # Apply blur for smooth glow effect
        glow_mask = glow_mask.filter(ImageFilter.GaussianBlur(radius=6))
        
        # Create glow color layer (#ff8686)
        glow_color = Image.new('RGBA', (glow_width, glow_height), (255, 134, 134, 0))
        glow_layer = Image.new('RGBA', (glow_width, glow_height), (255, 134, 134, 255))
        
        # Apply the mask to create the glow effect
        glow_color.paste(glow_layer, mask=glow_mask)
        
        # Composite glow onto background
        glow_bg = Image.alpha_composite(glow_bg, glow_color)
        
        # Convert base image to RGBA if needed
        if base_image.mode != 'RGBA':
            base_image = base_image.convert('RGBA')
        
        # Paste the original image in the exact center to maintain position
        paste_x = (glow_width - base_width) // 2
        paste_y = (glow_height - base_height) // 2
        glow_bg.paste(base_image, (paste_x, paste_y), base_image)
        
        return ImageTk.PhotoImage(glow_bg)
    
    def load_background_image(self, width, height):
        """Load the Desolation art as the app backdrop."""
        try:
            background = load_cover_background(
                get_resource_path("art/Desolation.png"),
                width,
                height,
                overlay_color=COLORS['bg_primary'],
                overlay_alpha=130,
            )
            return ImageTk.PhotoImage(background)
        except Exception as e:
            print(f"Error loading background image: {e}")
            return None
    
    def load_images(self):
        """Load all required images"""
        try:
            # Load game title
            self.game_title_img = Image.open(get_resource_path("art/GameTitle.png"))
            self.game_title_tk = ImageTk.PhotoImage(self.game_title_img)
            # Create CTkImage version for use with CTkLabel
            self.game_title_ctk = ctk.CTkImage(light_image=self.game_title_img,
                                               dark_image=self.game_title_img,
                                               size=self.game_title_img.size)
            
            # Load HR Systems logo
            self.hr_logo_img = Image.open(get_resource_path("art/hudsonriggssystems.png"))
            # Resize HR logo to be smaller
            hr_width, hr_height = self.hr_logo_img.size
            new_hr_height = 60
            new_hr_width = int((new_hr_height / hr_height) * hr_width)
            self.hr_logo_img = self.hr_logo_img.resize((new_hr_width, new_hr_height), Image.Resampling.LANCZOS)
            self.hr_logo_tk = ImageTk.PhotoImage(self.hr_logo_img)
            # Create CTkImage version
            self.hr_logo_ctk = ctk.CTkImage(light_image=self.hr_logo_img,
                                            dark_image=self.hr_logo_img,
                                            size=(new_hr_width, new_hr_height))
            
            # Create radial glow effect version for loading screen
            self.hr_logo_glow_tk = self.create_radial_glow_image(self.hr_logo_img)
            
            # Load expansion images - dynamically find all NotOwned_ files
            self.expansion_images = {}
            art_dir = get_resource_path("art")
            
            if os.path.exists(art_dir):
                for file in os.listdir(art_dir):
                    if file.startswith("NotOwned_") and file.endswith(".png"):
                        try:
                            img = Image.open(os.path.join(art_dir, file))
                            # Resize expansion images
                            img_width, img_height = img.size
                            new_height = 150
                            new_width = int((new_height / img_height) * img_width)
                            img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
                            expansion_name = file.replace("NotOwned_", "").replace(".png", "")
                            self.expansion_images[expansion_name] = ImageTk.PhotoImage(img)
                        except Exception as e:
                            print(f"Error loading {file}: {e}")
                    
        except Exception as e:
            print(f"Error loading images: {e}")
            # Create placeholder images if loading fails
            self.game_title_tk = None
            self.game_title_ctk = None
            self.hr_logo_tk = None
            self.hr_logo_ctk = None
            self.expansion_images = {}
            # Create placeholder images if loading fails
            self.game_title_tk = None
            self.hr_logo_tk = None
            self.expansion_images = {}
    
    def create_loading_ui(self):
        """Create the loading screen UI with background image using Canvas for true transparency"""
        # Force window to update and get proper dimensions
        self.loading_window.update()
        width = self.loading_window.winfo_width()
        height = self.loading_window.winfo_height()
        
        # Fallback to screen size if window size not available
        if width < 100 or height < 100:
            width = self.loading_window.winfo_screenwidth()
            height = self.loading_window.winfo_screenheight()
        
        print(f"Loading UI with dimensions: {width}x{height}")
        
        # Store canvas dimensions for later use
        self.canvas_width = width
        self.canvas_height = height
        
        # Create canvas for background and all content
        self.bg_canvas = tk.Canvas(self.loading_window, highlightthickness=0)
        self.bg_canvas.pack(fill=tk.BOTH, expand=True)
        
        # Load and display background image
        self.bg_image_pil = self._load_and_scale_background(width, height)
        if self.bg_image_pil:
            print(f"Background image loaded: {self.bg_image_pil.size}")
            self.bg_image_tk = ImageTk.PhotoImage(self.bg_image_pil)
            self.bg_canvas.create_image(0, 0, image=self.bg_image_tk, anchor='nw', tags='bg')
        else:
            print("Failed to load background image!")
            self.bg_canvas.configure(bg=COLORS['bg_primary'])
        
        # Bind window state change events
        self.loading_window.bind('<Map>', self._on_window_map)
        self.loading_window.bind('<Unmap>', self._on_window_unmap)
        
        # Bind resize event to update background (minimum size enforcement is handled separately)
        # Note: _enforce_loading_minimum_size calls _on_loading_resize after size correction
        
        # Place widgets directly on the canvas
        # Game title (initially large and centered)
        if hasattr(self, 'game_title_tk') and self.game_title_tk:
            self.title_image_id = self.bg_canvas.create_image(width//2, height//2, 
                                                              image=self.game_title_tk, 
                                                              anchor='center', tags='title')
        else:
            self.title_text_id = self.bg_canvas.create_text(width//2, height//2,
                                                            text="Progression: Loader",
                                                            fill=COLORS['text_highlight'],
                                                            font=self.get_font(24, 'bold', 'italic'),
                                                            anchor='center', tags='title')
        
        # HR Systems logo (initially at bottom)
        if hasattr(self, 'hr_logo_tk') and self.hr_logo_tk:
            self.hr_logo_id = self.bg_canvas.create_image(width//2, int(height*0.95),
                                                          image=self.hr_logo_tk,
                                                          anchor='center', tags='hr_logo')
        else:
            self.hr_logo_id = self.bg_canvas.create_text(width//2, int(height*0.95),
                                                         text="Hudson Riggs Systems",
                                                         fill=COLORS['text_muted'],
                                                         font=self.get_font(12, 'normal', 'italic'),
                                                         anchor='center', tags='hr_logo')
        
        # Bind click events for HR logo
        self.bg_canvas.tag_bind('hr_logo', '<Button-1>', self.open_hr_website)
        self.bg_canvas.tag_bind('hr_logo', '<Enter>', self.on_loading_hr_enter)
        self.bg_canvas.tag_bind('hr_logo', '<Leave>', self.on_loading_hr_leave)
        
        # Create a reference for compatibility
        self.main_frame = self.loading_window
        
        # Store canvas dimensions for later use
        self.canvas_width = width
        self.canvas_height = height
    
    def _load_and_scale_background(self, width, height):
        """Load the Desolation art as the loading backdrop."""
        try:
            return load_cover_background(
                get_resource_path("art/Desolation.png"),
                width,
                height,
                overlay_color=COLORS['bg_primary'],
                overlay_alpha=135,
            )
        except Exception as e:
            print(f"Error loading background image: {e}")
            return None
    
    def _enforce_loading_minimum_size(self, event):
        """Enforce minimum window size for loading screen"""
        if event.widget == self.loading_window:
            width = self.loading_window.winfo_width()
            height = self.loading_window.winfo_height()
            
            # Check if window is smaller than minimum
            if width < 800 or height < 600:
                # Resize to minimum if too small
                new_width = max(width, 800)
                new_height = max(height, 600)
                self.loading_window.geometry(f"{new_width}x{new_height}")
                return  # Don't process resize if we're correcting size
            
            # Continue with normal resize handling
            self._on_loading_resize(event)
    
    def _on_loading_resize(self, event):
        """Handle window resize to update background image and reposition elements responsively"""
        if event.widget == self.loading_window and hasattr(self, 'bg_canvas'):
            width = event.width
            height = event.height
            
            # Skip if dimensions are too small (handled by minimum size enforcement)
            if width < 800 or height < 600:
                return
            
            # Update canvas dimensions for responsive scaling
            self.canvas_width = width
            self.canvas_height = height
            
            # Update background image with new dimensions
            self.bg_image_pil = self._load_and_scale_background(width, height)
            if self.bg_image_pil:
                self.bg_image_tk = ImageTk.PhotoImage(self.bg_image_pil)
                self.bg_canvas.delete('bg')
                self.bg_canvas.create_image(0, 0, image=self.bg_image_tk, anchor='nw', tags='bg')
                self.bg_canvas.tag_lower('bg')
            
            # Reposition elements responsively based on new dimensions
            self._reposition_loading_elements_responsive(width, height)
            
            # Ensure logo stays on top after repositioning
            self._bring_logo_to_front()
            
            # If we're in a stage, refresh the stage content to reposition elements
            if hasattr(self, 'current_stage_method'):
                # Add a small delay to avoid too frequent updates during resize
                if hasattr(self, '_refresh_timer'):
                    self.loading_window.after_cancel(self._refresh_timer)
                self._refresh_timer = self.loading_window.after(150, self._refresh_current_stage)
    
    def _reposition_loading_elements_responsive(self, width, height):
        """Reposition loading elements responsively based on window size"""
        # Calculate responsive positions as percentages of window size
        title_y = max(60, int(height * 0.08))  # 8% from top, minimum 60px
        hr_logo_y = max(height - 80, int(height * 0.95))  # 5% from bottom, minimum 80px from bottom
        
        # Reposition title to maintain relative position
        title_items = self.bg_canvas.find_withtag('title')
        for item in title_items:
            self.bg_canvas.coords(item, width//2, title_y)
        
        # Reposition HR logo to maintain relative position
        hr_items = self.bg_canvas.find_withtag('hr_logo')
        for item in hr_items:
            self.bg_canvas.coords(item, width//2, hr_logo_y)
    
    def _refresh_current_stage(self):
        """Refresh the current stage content after window resize"""
        # This will be called to reposition stage elements after resize
        if hasattr(self, 'current_stage_method') and self.current_stage_method:
            # Re-call the current stage method to reposition elements
            try:
                self.current_stage_method()
            except Exception as e:
                print(f"Error refreshing stage: {e}")
                # If refresh fails, just continue without crashing
    
    def _on_window_map(self, event):
        """Handle window map event (window becomes visible)"""
        pass
    
    def _on_window_unmap(self, event):
        """Handle window unmap event (window becomes hidden)"""
        pass
    
    def get_ctk_font(self, size=10, weight='normal'):
        """Get CTkFont for UI elements"""
        if self.custom_font_available and self.custom_font_family:
            return ctk.CTkFont(family=self.custom_font_family, size=size, 
                              weight=weight if weight != 'normal' else 'normal')
        return ctk.CTkFont(family=FONT_FAMILY, size=size, weight=weight if weight != 'normal' else 'normal')
    
    def create_modern_card_background(self, x, y, width, height, card_type='info', corner_radius=12):
        """Create a solid panel with a striped halo just outside its edges."""
        if card_type == 'error':
            card_img = create_striped_panel(
                width,
                height,
                panel_color=COLORS['bg_error'],
                stripe_color=COLORS['stripe'],
                stripe_width=18,
                stripe_gap=18,
                stripe_alpha=120,
                halo_padding=26,
                halo_alpha=150,
                panel_alpha=238,
                corner_radius=corner_radius,
                border_color=COLORS['accent_red'],
                border_width=2,
                border_alpha=190,
            )
        else:
            card_img = create_striped_panel(
                width,
                height,
                panel_color=COLORS['bg_card'],
                stripe_color=COLORS['stripe_soft'],
                stripe_width=18,
                stripe_gap=18,
                stripe_alpha=110,
                halo_padding=22,
                halo_alpha=135,
                panel_alpha=238,
                corner_radius=corner_radius,
                border_color=COLORS['border_light'],
                border_width=1,
                border_alpha=170,
            )
        
        # Convert to PhotoImage
        card_tk = ImageTk.PhotoImage(card_img)
        
        # Store reference to prevent garbage collection
        if not hasattr(self, '_modern_card_images'):
            self._modern_card_images = []
        self._modern_card_images.append(card_tk)
        
        # Create canvas image
        img_id = self.bg_canvas.create_image(x, y, image=card_tk, anchor='center', tags='stage_content')
        
        # Make sure it's behind text but above background
        self.bg_canvas.tag_lower(img_id, 'stage_content')
        
        # Ensure logo stays on top
        self._bring_logo_to_front()
        
        return img_id
    
    def _bring_logo_to_front(self):
        """Ensure the game logo stays on top of all other elements"""
        # Bring title/logo to the very front
        title_items = self.bg_canvas.find_withtag('title')
        for item in title_items:
            self.bg_canvas.tag_raise(item)
        
        # Also bring HR logo to front (but below title)
        hr_items = self.bg_canvas.find_withtag('hr_logo')
        for item in hr_items:
            self.bg_canvas.tag_raise(item)
        
        # Make sure title is above HR logo
        for item in title_items:
            self.bg_canvas.tag_raise(item)
    
    def start_loading_sequence(self):
        """Start the loading animation sequence"""
        # Stage 1: Show initial screen for 2 seconds
        self.loading_window.after(2000, self.stage_2_move_title)
    
    def stage_2_move_title(self):
        """Stage 2: Move title to top and show expansion check"""
        # Start the smooth animation to move title to top
        self.animate_title_to_top_smooth()
        
        # After animation completes, check subscriptions
        self.loading_window.after(1500, self.stage_3_check_subscriptions)
    
    def animate_title_to_top_smooth(self):
        """Smoothly animate the title moving to the top of the screen using canvas"""
        # Get current window dimensions
        window_width = self.canvas_width if hasattr(self, 'canvas_width') else self.loading_window.winfo_width()
        window_height = self.canvas_height if hasattr(self, 'canvas_height') else self.loading_window.winfo_height()
        
        # If window dimensions aren't available yet, wait and try again
        if window_width <= 1 or window_height <= 1:
            self.loading_window.after(100, self.animate_title_to_top_smooth)
            return
        
        # Get current title image dimensions
        if hasattr(self, 'game_title_img') and self.game_title_img:
            current_img = self.game_title_img
            current_width, current_height = current_img.size
            
            # Target size (smaller for header)
            target_height = 80
            target_width = int((target_height / current_height) * current_width)
            
            # Animation parameters (using pixel positions)
            start_y = window_height // 2
            target_y = int(window_height * 0.08)
            
            # Start the smooth animation
            self.animate_title_step(0, 30, start_y, target_y,
                                  current_width, current_height, target_width, target_height)
        else:
            # Fallback for text version
            self.animate_text_title_smooth()
    
    def animate_title_step(self, step, total_steps, start_y, target_y,
                          start_width, start_height, target_width, target_height):
        """Perform one step of the title animation using canvas"""
        if step <= total_steps:
            # Calculate progress (0.0 to 1.0)
            progress = step / total_steps
            # Use easing function for smooth animation (ease-out)
            eased_progress = 1 - (1 - progress) ** 3
            
            # Calculate current position
            current_y = start_y + (target_y - start_y) * eased_progress
            
            # Calculate current size
            current_width = start_width + (target_width - start_width) * eased_progress
            current_height = start_height + (target_height - start_height) * eased_progress
            
            # Resize image for current step
            if hasattr(self, 'game_title_img'):
                resized_img = self.game_title_img.resize(
                    (int(current_width), int(current_height)), 
                    Image.Resampling.LANCZOS
                )
                self.current_title_tk = ImageTk.PhotoImage(resized_img)
                
                # Update canvas image
                window_width = self.canvas_width if hasattr(self, 'canvas_width') else self.loading_window.winfo_width()
                self.bg_canvas.delete('title')
                self.bg_canvas.create_image(window_width//2, int(current_y), 
                                           image=self.current_title_tk, 
                                           anchor='center', tags='title')
            
            # Schedule next step
            self.loading_window.after(50, lambda: self.animate_title_step(
                step + 1, total_steps, start_y, target_y,
                start_width, start_height, target_width, target_height
            ))
        else:
            # Animation complete - finalize position
            self.finalize_title_position()
    
    def animate_text_title_smooth(self):
        """Animate text title if image is not available"""
        window_height = self.canvas_height if hasattr(self, 'canvas_height') else self.loading_window.winfo_height()
        start_y = window_height // 2
        target_y = int(window_height * 0.08)
        
        # Animate text title position and size
        self.animate_text_step(0, 30, start_y, target_y, 24, 16)
    
    def animate_text_step(self, step, total_steps, start_y, target_y, 
                         start_size, target_size):
        """Animate text title step by step using canvas"""
        if step <= total_steps:
            progress = step / total_steps
            eased_progress = 1 - (1 - progress) ** 3
            
            current_y = start_y + (target_y - start_y) * eased_progress
            current_size = int(start_size + (target_size - start_size) * eased_progress)
            
            # Update canvas text
            window_width = self.canvas_width if hasattr(self, 'canvas_width') else self.loading_window.winfo_width()
            self.bg_canvas.delete('title')
            self.bg_canvas.create_text(window_width//2, int(current_y),
                                      text="Progression: Loader",
                                      fill=COLORS['text_highlight'],
                                      font=self.get_font(current_size, 'bold', 'italic'),
                                      anchor='center', tags='title')
            
            self.loading_window.after(50, lambda: self.animate_text_step(
                step + 1, total_steps, start_y, target_y,
                start_size, target_size
            ))
        else:
            self.finalize_title_position()
    
    def finalize_title_position(self):
        """Finalize the title position after animation and show content"""
        window_width = self.canvas_width if hasattr(self, 'canvas_width') else self.loading_window.winfo_width()
        window_height = self.canvas_height if hasattr(self, 'canvas_height') else self.loading_window.winfo_height()
        
        # Set final image size
        if hasattr(self, 'game_title_img') and self.game_title_img:
            final_height = 80
            final_width = int((final_height / self.game_title_img.size[1]) * self.game_title_img.size[0])
            final_img = self.game_title_img.resize((final_width, final_height), Image.Resampling.LANCZOS)
            self.small_title_tk = ImageTk.PhotoImage(final_img)
            
            # Position title at top center
            self.bg_canvas.delete('title')
            self.bg_canvas.create_image(window_width//2, int(window_height * 0.08),
                                       image=self.small_title_tk,
                                       anchor='center', tags='title')
        else:
            self.bg_canvas.delete('title')
            self.bg_canvas.create_text(window_width//2, int(window_height * 0.08),
                                      text="Progression: Loader",
                                      fill=COLORS['text_highlight'],
                                      font=self.get_font(16, 'bold', 'italic'),
                                      anchor='center', tags='title')
    
    def stage_3_check_subscriptions(self):
        """Stage 3: Show Steam Workshop subscriptions directly"""
        # Skip expansion check and go directly to subscription stage
        self.show_subscription_stage()
    
    def check_expansions_thread(self):
        """Check which expansions are owned"""
        try:
            # Find ModsConfig.xml
            modsconfig_path = self.find_modsconfig_path()
            
            if not modsconfig_path or not os.path.exists(modsconfig_path):
                # If no ModsConfig found, get all NotOwned_ files and assume all are missing
                art_dir = get_resource_path("art")
                not_owned_files = []
                if os.path.exists(art_dir):
                    for file in os.listdir(art_dir):
                        if file.startswith("NotOwned_") and file.endswith(".png"):
                            expansion_name = file.replace("NotOwned_", "").replace(".png", "")
                            not_owned_files.append(expansion_name)
                
                self.expansion_check_results = {expansion: False for expansion in not_owned_files}
            else:
                # Parse ModsConfig.xml to check known expansions
                self.expansion_check_results = self.parse_known_expansions(modsconfig_path)
            
            # Update UI on main thread
            self.loading_window.after(0, self.show_expansion_results)
            
        except Exception as e:
            print(f"Error checking expansions: {e}")
            # Assume all missing on error - get from NotOwned_ files
            art_dir = get_resource_path("art")
            not_owned_files = []
            if os.path.exists(art_dir):
                for file in os.listdir(art_dir):
                    if file.startswith("NotOwned_") and file.endswith(".png"):
                        expansion_name = file.replace("NotOwned_", "").replace(".png", "")
                        not_owned_files.append(expansion_name)
            
            self.expansion_check_results = {expansion: False for expansion in not_owned_files}
            self.loading_window.after(0, self.show_expansion_results)
    
    def find_modsconfig_path(self):
        """Find ModsConfig.xml file"""
        try:
            path_template = os.getenv('MODSCONFIG_PATH_TEMPLATE', 
                                    r'%USERPROFILE%\AppData\LocalLow\Ludeon Studios\RimWorld by Ludeon Studios\Config\ModsConfig.xml')
            modsconfig_path = os.path.expandvars(path_template)
            
            if os.path.exists(modsconfig_path):
                return modsconfig_path
            return None
        except Exception:
            return None
    
    def parse_known_expansions(self, modsconfig_path):
        """Parse ModsConfig.xml to check which expansions are owned"""
        # Get all NotOwned_ PNG files from art directory
        art_dir = get_resource_path("art")
        not_owned_files = []
        if os.path.exists(art_dir):
            for file in os.listdir(art_dir):
                if file.startswith("NotOwned_") and file.endswith(".png"):
                    # Extract expansion name (remove NotOwned_ prefix and .png suffix)
                    expansion_name = file.replace("NotOwned_", "").replace(".png", "")
                    not_owned_files.append(expansion_name)
        
        # Initialize results - all expansions are missing by default
        results = {expansion: False for expansion in not_owned_files}
        
        try:
            tree = ET.parse(modsconfig_path)
            root = tree.getroot()
            
            # Get RimWorld Data folder path from the RimWorld game path
            rimworld_data_path = None
            if hasattr(self, 'rimworld_var') and self.rimworld_var.get().strip():
                rimworld_game_path = self.rimworld_var.get().strip()
                rimworld_data_path = os.path.join(rimworld_game_path, "Data")
            
            known_expansions = root.find('knownExpansions')
            if known_expansions is not None:
                for li in known_expansions.findall('li'):
                    expansion_id = li.text
                    if expansion_id:
                        # Get the real expansion name from RimWorld Data folder
                        real_name = self.get_expansion_real_name(expansion_id.strip(), rimworld_data_path)
                        
                        # Check if this real name matches any of our NotOwned_ files
                        for expansion in not_owned_files:
                            if real_name and expansion.lower() == real_name.lower():
                                results[expansion] = True
                                break
            
        except Exception as e:
            print(f"Error parsing ModsConfig.xml: {e}")
        
        return results
    
    def get_expansion_real_name(self, expansion_id, rimworld_data_path):
        """Get the real expansion name from RimWorld Data folder About.xml"""
        try:
            if not rimworld_data_path or not os.path.exists(rimworld_data_path):
                # Fallback to extracting from ID if no data path
                return self.extract_name_from_id(expansion_id)
            
            # Look for expansion folder in Data directory
            # Core expansions are usually in subfolders like Royalty, Ideology, etc.
            possible_folders = [
                expansion_id.replace('ludeon.rimworld.', '').capitalize(),
                expansion_id.split('.')[-1].capitalize() if '.' in expansion_id else expansion_id,
                expansion_id.replace('ludeon.rimworld.', ''),
                expansion_id.replace('ludeon.rimworld', 'Core')
            ]
            
            for folder_name in possible_folders:
                about_xml_path = os.path.join(rimworld_data_path, folder_name, "About", "About.xml")
                if os.path.exists(about_xml_path):
                    try:
                        tree = ET.parse(about_xml_path)
                        root = tree.getroot()
                        
                        name_element = root.find('name')
                        if name_element is not None and name_element.text:
                            return name_element.text.strip()
                    except ET.ParseError:
                        continue
            
            # If not found in Data folder, use fallback
            return self.extract_name_from_id(expansion_id)
            
        except Exception as e:
            return self.extract_name_from_id(expansion_id)
    
    def extract_name_from_id(self, expansion_id):
        """Extract expansion name from ID as fallback"""
        normalized_id = expansion_id.lower()

        if normalized_id == 'ludeon.rimworld':
            return 'Core'
        elif 'royalty' in normalized_id:
            return 'Royalty'
        elif 'ideology' in normalized_id:
            return 'Ideology'
        elif 'biotech' in normalized_id:
            return 'Biotech'
        elif 'anomaly' in normalized_id:
            return 'Anomaly'
        elif 'odyssey' in normalized_id:
            return 'Odyssey'
        else:
            # Extract the last part after the last dot and capitalize
            parts = expansion_id.split('.')
            if len(parts) > 1:
                return parts[-1].capitalize()
            return expansion_id
    
    def show_expansion_results(self):
        """Show the results of expansion checking using new styled cards"""
        # Clear previous stage content
        self.bg_canvas.delete('stage_content')
        
        # Track current stage for responsive updates
        self.current_stage_method = self.show_expansion_results
        
        window_width = self.canvas_width if hasattr(self, 'canvas_width') else self.loading_window.winfo_width()
        window_height = self.canvas_height if hasattr(self, 'canvas_height') else self.loading_window.winfo_height()
        
        missing_expansions = [name for name, owned in self.expansion_check_results.items() if not owned]
        
        if not missing_expansions:
            # All expansions owned - use clean info card
            card_width = max(400, min(int(window_width * 0.6), 700))
            card_height = max(150, min(int(window_height * 0.25), 200))
            
            self.create_modern_card_background(window_width//2, int(window_height * 0.4), 
                                             card_width, card_height, 'info')
            
            # Title in highlight color - bold italic
            title_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                                  size=20, weight='bold', slant='italic')
            self.bg_canvas.create_text(window_width//2, int(window_height * 0.4) - (card_height//2) + 40,
                                      text="Expansion Check Complete",
                                      fill=COLORS['text_highlight'],
                                      font=title_font,
                                      anchor='center', tags='stage_content')
            
            # Success message - bold
            success_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                                    size=16, weight='bold')
            self.bg_canvas.create_text(window_width//2, int(window_height * 0.4),
                                      text="✓ All RimWorld expansions detected!",
                                      fill=COLORS['accent_green'],
                                      font=success_font,
                                      anchor='center', tags='stage_content')
            
            # Body text - regular
            body_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                                 size=12)
            self.bg_canvas.create_text(window_width//2, int(window_height * 0.4) + 40,
                                      text="You can proceed with the complete Progression experience.",
                                      fill=COLORS['text_body'],
                                      font=body_font,
                                      anchor='center', tags='stage_content')
            
            self.loading_window.after(2000, self.stage_6_final_confirmation)
        else:
            # Missing expansions - use error card with diagonal lines
            card_width = max(600, min(int(window_width * 0.8), 900))
            card_height = max(400, min(int(window_height * 0.7), 600))
            
            self.create_modern_card_background(window_width//2, int(window_height * 0.45), 
                                             card_width, card_height, 'error')
            
            # Error title in highlight color - bold italic
            error_title_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                                        size=24, weight='bold', slant='italic')
            self.bg_canvas.create_text(window_width//2, int(window_height * 0.45) - (card_height//2) + 50,
                                      text="ERROR",
                                      fill=COLORS['text_highlight'],
                                      font=error_title_font,
                                      anchor='center', tags='stage_content')
            
            # Subtitle - bold
            subtitle_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                                     size=16, weight='bold')
            self.bg_canvas.create_text(window_width//2, int(window_height * 0.45) - (card_height//2) + 90,
                                      text="Missing Required Expansions",
                                      fill=COLORS['text_primary'],
                                      font=subtitle_font,
                                      anchor='center', tags='stage_content')
            
            # Body text explaining the issue - regular
            body_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                                 size=12)
            error_text = f"You don't own {', '.join(missing_expansions)}!\n\n"
            error_text += "These expansions are required for the complete Progression experience. "
            error_text += "Some mods may not function correctly without them. "
            error_text += "Please purchase the missing expansions from Steam before continuing."
            
            self.bg_canvas.create_text(window_width//2, int(window_height * 0.45),
                                      text=error_text,
                                      fill=COLORS['text_body'],
                                      font=body_font,
                                      anchor='center', 
                                      width=card_width - 80,  # Text wrapping
                                      tags='stage_content')
            
            # Show expansion images side by side
            img_y = int(window_height * 0.58)
            img_spacing = max(120, min(160, int(window_width * 0.08)))
            total_width = len(missing_expansions) * img_spacing
            start_x = window_width//2 - (total_width - img_spacing) // 2
            
            for i, expansion in enumerate(missing_expansions):
                img_x = start_x + i * img_spacing
                
                if expansion in self.expansion_images:
                    self.bg_canvas.create_image(img_x, img_y,
                                               image=self.expansion_images[expansion],
                                               anchor='center', tags='stage_content')
            
            # Exit button
            self.create_canvas_button(window_width//2, int(window_height * 0.7), "Exit Application", 
                                     self.exit_application, COLORS['accent_red'], width=200)
    
    def show_subscription_stage(self):
        """Show the subscription check stage using new styled cards matching the design"""
        # Clear previous stage content and background images
        self.bg_canvas.delete('stage_content')
        self._modern_card_images = []  # Clear old background images
        
        # Track current stage for responsive updates
        self.current_stage_method = self.show_subscription_stage
        
        window_width = self.canvas_width if hasattr(self, 'canvas_width') else self.loading_window.winfo_width()
        window_height = self.canvas_height if hasattr(self, 'canvas_height') else self.loading_window.winfo_height()
        
        # Calculate card width based on content + 10% padding on each side
        # Estimate text width for the longest line
        temp_font = self.get_font(12)
        max_text_width = max(
            temp_font.measure("I have subscribed to Cosmetics Collection"),
            temp_font.measure("Required for Complete Progression Experience"),
            temp_font.measure("Steam Workshop Collections")
        )
        card_width = int(max_text_width * 1.2)  # 10% padding on each side
        card_height = 320  # Increased height to accommodate title inside
        
        # Create modern card with fading edges
        self.create_modern_card_background(window_width//2, int(window_height * 0.4), card_width, card_height, 'info')
        
        # Title INSIDE the card at the top (highlight color) - bold italic
        title_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                              size=18, weight='bold', slant='italic')
        self.bg_canvas.create_text(window_width//2, int(window_height * 0.4) - (card_height//2) + 40,
                                  text="Steam Workshop Collections",
                                  fill=COLORS['text_highlight'],
                                  font=title_font,
                                  anchor='center', tags='stage_content')
        
        # Subtitle inside the card - regular
        subtitle_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                                 size=12)
        self.bg_canvas.create_text(window_width//2, int(window_height * 0.4) - (card_height//2) + 70,
                                  text="Required for Complete Progression Experience",
                                  fill=COLORS['text_body'],
                                  font=subtitle_font,
                                  anchor='center', tags='stage_content')
        
        # Collection checkboxes
        collections = [
            ("Core Collection", "https://steamcommunity.com/workshop/filedetails/?id=3521297585"),
            ("Content Collection", "https://steamcommunity.com/sharedfiles/filedetails/?id=3521319712"),
            ("Cosmetics Collection", "https://steamcommunity.com/sharedfiles/filedetails/?id=3637541646")
        ]
        
        self.subscription_vars = {}
        self.checkbox_ids = {}
        y_start = int(window_height * 0.4) - (card_height//2) + 110  # Start checkboxes inside card
        
        for i, (name, url) in enumerate(collections):
            var = tk.BooleanVar()
            self.subscription_vars[name] = var
            
            y_pos = y_start + (i * 35)
            
            # Create custom checkbox using canvas
            checkbox_x = window_width//2 - (card_width//2) + 40
            
            # Draw checkbox box
            box_id = self.bg_canvas.create_rectangle(
                checkbox_x - 8, y_pos - 8, checkbox_x + 8, y_pos + 8,
                outline=COLORS['text_primary'], width=2, fill='', tags=('stage_content', f'checkbox_{name}')
            )
            
            # Create invisible clickable area around checkbox
            click_area = self.bg_canvas.create_rectangle(
                checkbox_x - 15, y_pos - 15, checkbox_x + 15, y_pos + 15,
                outline='', fill='', tags=('stage_content', f'checkbox_{name}')
            )
            
            # Store checkbox info for click handling
            self.checkbox_ids[name] = {'box': box_id, 'check': None, 'var': var}
            
            # Bind click event to toggle checkbox
            self.bg_canvas.tag_bind(f'checkbox_{name}', '<Button-1>', 
                                   lambda e, n=name: self.toggle_checkbox(n))
            
            # Create text label next to checkbox
            text_x = checkbox_x + 20
            self.bg_canvas.create_text(text_x, y_pos,
                                      text="I have subscribed to ",
                                      fill=COLORS['text_body'],
                                      font=self.get_font(12),
                                      anchor='w', tags='stage_content')
            
            # Calculate the width of the "I have subscribed to " text to position the link correctly
            text_width = temp_font.measure("I have subscribed to ")
            
            # Create clickable link for collection name
            link_x = text_x + text_width
            link_id = self.bg_canvas.create_text(link_x, y_pos,
                                                text=name,
                                                fill=COLORS['accent_blue'],
                                                font=self.get_font(12, 'bold'),
                                                anchor='w', tags=('stage_content', f'link_{name}'))
            
            # Bind click and hover events to the text
            self.bg_canvas.tag_bind(f'link_{name}', '<Button-1>', lambda e, u=url: self.open_collection_url(u))
            self.bg_canvas.tag_bind(f'link_{name}', '<Enter>', lambda e, lid=link_id: self.bg_canvas.itemconfig(lid, fill=COLORS['text_highlight']))
            self.bg_canvas.tag_bind(f'link_{name}', '<Leave>', lambda e, lid=link_id: self.bg_canvas.itemconfig(lid, fill=COLORS['accent_blue']))
        
        # Continue button - text only, no background, bold AND italic in success color
        # Position it INSIDE the card at the bottom
        continue_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                                 size=16, weight='bold', slant='italic')
        continue_text_id = self.bg_canvas.create_text(window_width//2, int(window_height * 0.4) + (card_height//2) - 30,
                                                     text="Continue",
                                                     fill=COLORS['accent_green'],
                                                     font=continue_font,
                                                     anchor='center', tags=('stage_content', 'continue_btn'))
        
        # Make continue button clickable
        self.bg_canvas.tag_bind('continue_btn', '<Button-1>', lambda e: self.check_all_subscribed())
        self.bg_canvas.tag_bind('continue_btn', '<Enter>', lambda e: self.bg_canvas.itemconfig(continue_text_id, fill=COLORS['text_highlight']))
        self.bg_canvas.tag_bind('continue_btn', '<Leave>', lambda e: self.bg_canvas.itemconfig(continue_text_id, fill=COLORS['accent_green']))
        
        # Ensure logo stays on top
        self._bring_logo_to_front()
    
    def toggle_checkbox(self, name):
        """Toggle a checkbox state"""
        info = self.checkbox_ids[name]
        var = info['var']
        var.set(not var.get())
        
        # Get the checkbox position from the stored box coordinates
        box_coords = self.bg_canvas.coords(info['box'])
        checkbox_x = (box_coords[0] + box_coords[2]) / 2  # Center X of the box
        checkbox_y = (box_coords[1] + box_coords[3]) / 2  # Center Y of the box
        
        if var.get():
            # Draw checkmark INSIDE the checkbox
            if info['check'] is None:
                check_id = self.bg_canvas.create_text(checkbox_x, checkbox_y,
                                                     text="✓",
                                                     fill=COLORS['accent_green'],
                                                     font=self.get_font(12, 'bold'),
                                                     anchor='center', tags='stage_content')
                self.checkbox_ids[name]['check'] = check_id
        else:
            # Remove checkmark
            if info['check'] is not None:
                self.bg_canvas.delete(info['check'])
                self.checkbox_ids[name]['check'] = None
    
    def create_canvas_button(self, x, y, text, command, color, width=150, height=40):
        """Create a color-coded text action on the canvas."""
        safe_tag = f'btn_{text.replace(" ", "_").replace("!", "").replace("?", "").replace(",", "")}'

        text_id = self.bg_canvas.create_text(
            x,
            y,
            text=text,
            fill=color,
            font=self.get_font(14, 'bold', 'italic'),
            anchor='center',
            tags=('stage_content', safe_tag),
        )

        left, top, right, bottom = self.bg_canvas.bbox(text_id)
        hit_area_id = self.bg_canvas.create_rectangle(
            left - max(16, width // 6),
            top - max(8, height // 5),
            right + max(16, width // 6),
            bottom + max(8, height // 5),
            outline='',
            fill='',
            tags=('stage_content', safe_tag),
        )
        self.bg_canvas.tag_lower(hit_area_id, text_id)

        self.bg_canvas.tag_bind(safe_tag, '<Button-1>', lambda e: command())
        self.bg_canvas.tag_bind(
            safe_tag,
            '<Enter>',
            lambda e, item=text_id: self.bg_canvas.itemconfig(item, fill=COLORS['text_primary']),
        )
        self.bg_canvas.tag_bind(
            safe_tag,
            '<Leave>',
            lambda e, item=text_id, button_color=color: self.bg_canvas.itemconfig(item, fill=button_color),
        )

        return text_id

    def create_text_background(self, x, y, width, height, opacity=0.82, corner_radius=20):
        """Create a rounded dark panel with stripes extending just outside it."""
        alpha = max(0, min(255, int(255 * opacity)))
        panel_image = create_striped_panel(
            int(width * 1.05),
            int(height * 1.05),
            panel_color=COLORS['bg_card'],
            stripe_color=COLORS['stripe_soft'],
            stripe_width=18,
            stripe_gap=18,
            stripe_alpha=max(50, int(alpha * 0.3)),
            halo_padding=22,
            halo_alpha=max(90, int(alpha * 0.55)),
            panel_alpha=max(220, alpha),
            corner_radius=corner_radius,
            border_color=COLORS['border_light'],
            border_width=1,
            border_alpha=180,
        )

        bg_tk = ImageTk.PhotoImage(panel_image)

        if not hasattr(self, '_text_bg_images'):
            self._text_bg_images = []
        self._text_bg_images.append(bg_tk)

        img_id = self.bg_canvas.create_image(x, y, image=bg_tk, anchor='center', tags='stage_content')
        self.bg_canvas.tag_lower(img_id, 'stage_content')
        return img_id
    
    def check_all_subscribed(self):
        """Check if all collections are subscribed"""
        all_checked = all(var.get() for var in self.subscription_vars.values())
        
        if not all_checked:
            self.show_subscription_warning()
            return
        
        # All subscribed, proceed to RimWorld download check
        self.stage_4_rimworld_check()
    
    def stage_5_check_expansions(self):
        """Stage 5: Check RimWorld Expansions after RimWorld setup verification"""
        # Clear previous stage content
        self.bg_canvas.delete('stage_content')
        
        # Track current stage for responsive updates
        self.current_stage_method = self.stage_5_check_expansions
        
        window_width = self.canvas_width if hasattr(self, 'canvas_width') else self.loading_window.winfo_width()
        window_height = self.canvas_height if hasattr(self, 'canvas_height') else self.loading_window.winfo_height()
        
        # Create semi-transparent background panel for content - responsive sizing
        panel_width = max(400, min(int(window_width * 0.6), 800))  # 60% of width, min 400px, max 800px
        panel_height = max(200, min(int(window_height * 0.4), 400))  # 40% of height, min 200px, max 400px
        self.create_text_background(window_width//2, int(window_height * 0.4), panel_width, panel_height)
        
        # Create title as canvas text
        self.bg_canvas.create_text(window_width//2, int(window_height * 0.18),
                                  text="Checking RimWorld Expansions...",
                                  fill=COLORS['text_highlight'],
                                  font=self.get_font(18, 'bold'),
                                  anchor='center', tags='stage_content')
        
        # Check expansions in a separate thread
        threading.Thread(target=self.check_expansions_thread, daemon=True).start()
    
    def show_subscription_warning(self):
        """Show subscription warning in custom styled window"""
        warning_window = ctk.CTkToplevel(self.loading_window)
        warning_window.title("Progression: Loader - Subscription Required")
        warning_window.configure(fg_color=COLORS['bg_card'])
        
        # Set window icon
        try:
            icon_path = get_resource_path(os.path.join("art", "Progression.ico"))
            if os.path.exists(icon_path):
                warning_window.after(200, lambda: warning_window.iconbitmap(icon_path))
        except Exception:
            pass  # Ignore icon loading errors for popup windows
            
        warning_window.attributes('-topmost', True)
        
        # Warning text
        warning_text = """Steam Workshop Collections Required

You must subscribe to all three collections before continuing:

• Core Collection - Essential progression mods
• Content Collection - Expanded content and features  
• Cosmetics Collection - Visual enhancements and aesthetics

To subscribe to a collection:
1. Click on the collection name link
2. Click "Subscribe" on the Steam Workshop page
3. Wait for Steam to download the collection
4. Return here and check the subscription box
5. Repeat for all three collections

All collections are required for the complete Progression experience."""
        
        # Calculate window size
        window_width = 650
        window_height = 500
        
        center_window(warning_window, window_width, window_height, self.loading_window)
        
        # Title
        title_label = ctk.CTkLabel(warning_window,
                             text="Subscription Required",
                             text_color=COLORS['text_highlight'],
                             fg_color='transparent',
                             font=self.get_ctk_font(18, 'bold'))
        title_label.pack(pady=20)
        
        # Warning label with proper sizing
        warning_label = ctk.CTkLabel(warning_window,
                               text=warning_text,
                               text_color=COLORS['text_body'],
                               fg_color='transparent',
                               font=self.get_ctk_font(11),
                               justify='left',
                               wraplength=550)
        warning_label.pack(pady=20, padx=30, expand=True)
        
        close_btn = tk.Button(
            warning_window,
            text="Continue",
            command=warning_window.destroy,
            bg=COLORS['bg_card'],
            font=self.get_font(13, 'bold', 'italic'),
            padx=0,
            pady=6,
        )
        style_text_button(close_btn, COLORS['accent_green'], COLORS['bg_card'])
        close_btn.pack(pady=20)
    
    def stage_4_rimworld_check(self):
        """Stage 4: Check RimWorld download and launch status using new styled cards"""
        # Clear previous stage content
        self.bg_canvas.delete('stage_content')
        
        # Track current stage for responsive updates
        self.current_stage_method = self.stage_4_rimworld_check
        
        window_width = self.canvas_width if hasattr(self, 'canvas_width') else self.loading_window.winfo_width()
        window_height = self.canvas_height if hasattr(self, 'canvas_height') else self.loading_window.winfo_height()
        
        # Create styled card for RimWorld setup info
        card_width = max(500, min(int(window_width * 0.7), 800))
        card_height = max(350, min(int(window_height * 0.6), 550))
        self.create_modern_card_background(window_width//2, int(window_height * 0.4), card_width, card_height, 'info')
        
        # Title in highlight color - bold italic
        title_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                              size=20, weight='bold', slant='italic')
        self.bg_canvas.create_text(window_width//2, int(window_height * 0.4) - (card_height//2) + 40,
                                  text="RimWorld Setup Verification",
                                  fill=COLORS['text_highlight'],
                                  font=title_font,
                                  anchor='center', tags='stage_content')
        
        # Subtitle - bold
        subtitle_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                                 size=14, weight='bold')
        self.bg_canvas.create_text(window_width//2, int(window_height * 0.4) - (card_height//2) + 80,
                                  text="Please ensure the following before continuing:",
                                  fill=COLORS['text_primary'],
                                  font=subtitle_font,
                                  anchor='center', tags='stage_content')
        
        # Checklist items - bold for checkmarks
        checklist_items = [
            "✓ RimWorld has finished downloading completely",
            "✓ You have launched RimWorld at least once",
            "✓ The game runs without errors",
            "✓ All required DLCs are installed and activated"
        ]
        
        checklist_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                                  size=12, weight='bold')
        y_pos = int(window_height * 0.4) - (card_height//2) + 120
        for item in checklist_items:
            self.bg_canvas.create_text(window_width//2, y_pos,
                                      text=item,
                                      fill=COLORS['accent_green'],
                                      font=checklist_font,
                                      anchor='center', tags='stage_content')
            y_pos += 30
        
        # Confirmation message - bold italic
        confirm_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                                size=14, weight='bold', slant='italic')
        self.bg_canvas.create_text(window_width//2, y_pos + 30,
                                  text="Have you completed all the above requirements?",
                                  fill=COLORS['text_body'],
                                  font=confirm_font,
                                  anchor='center', tags='stage_content')
        
        # Only show one action pair here: proceed or read the setup help.
        button_y = y_pos + 80
        self.create_canvas_button(
            window_width//2 - 90,
            button_y,
            "Continue",
            self.proceed_to_dlc_check,
            COLORS['accent_green'],
            width=140,
        )
        self.create_canvas_button(
            window_width//2 + 90,
            button_y,
            "Not Yet",
            self.show_setup_instructions,
            COLORS['accent_red'],
            width=140,
        )
        
        # Ensure logo stays on top
        self._bring_logo_to_front()
    
    def proceed_to_dlc_check(self):
        """Proceed to DLC check after RimWorld verification"""
        # Clear stage content and proceed to expansion check
        self.bg_canvas.delete('stage_content')
        self._text_bg_images = []
        self._button_images = []
        self.stage_5_check_expansions()
    
    def show_setup_instructions(self):
        """Show setup instructions if user is not ready"""
        instruction_window = ctk.CTkToplevel(self.loading_window)
        instruction_window.title("Progression: Loader - Setup Instructions")
        instruction_window.configure(fg_color=COLORS['bg_card'])
        
        # Set window icon
        try:
            icon_path = get_resource_path(os.path.join("art", "Progression.ico"))
            if os.path.exists(icon_path):
                instruction_window.after(200, lambda: instruction_window.iconbitmap(icon_path))
        except Exception:
            pass  # Ignore icon loading errors for popup windows
            
        instruction_window.attributes('-topmost', True)
        
        # Instructions text
        instructions_text = """To properly set up RimWorld:

1. Download RimWorld from Steam
   • Right-click RimWorld in your Steam library
   • Select "Install" if not already installed
   • Wait for download to complete (may take several minutes)

2. Launch RimWorld at least once
   • Click "Play" in Steam or run RimWorld.exe
   • Let the game load to the main menu
   • This creates necessary configuration files

3. Install Required DLCs
   • Purchase and install: Royalty, Ideology, Biotech, Anomaly
   • Each DLC must be downloaded and activated

4. Verify Installation
   • Launch RimWorld again
   • Check that all DLCs appear in the main menu
   • Ensure no error messages appear

Once completed, return to this screen and click "Continue"."""
        
        # Calculate window size
        window_width = 650
        window_height = 550
        
        center_window(instruction_window, window_width, window_height, self.loading_window)
        
        # Title
        title_label = ctk.CTkLabel(instruction_window,
                             text="Progression: Loader - Setup Instructions",
                             text_color=COLORS['text_highlight'],
                             fg_color='transparent',
                             font=self.get_ctk_font(16, 'bold'))
        title_label.pack(pady=20)
        
        # Instructions label with proper sizing
        instructions_label = ctk.CTkLabel(instruction_window,
                                    text=instructions_text,
                                    text_color=COLORS['text_body'],
                                    fg_color='transparent',
                                    font=self.get_ctk_font(11),
                                    justify='left',
                                    wraplength=550)
        instructions_label.pack(pady=20, padx=30, expand=True)
        
        close_btn = tk.Button(
            instruction_window,
            text="Back",
            command=instruction_window.destroy,
            bg=COLORS['bg_card'],
            font=self.get_font(13, 'bold', 'italic'),
            padx=0,
            pady=6,
        )
        style_text_button(close_btn, COLORS['accent_red'], COLORS['bg_card'])
        close_btn.pack(pady=20)
    
    def _legacy_stage_6_final_confirmation(self):
        """Deprecated earlier version kept only for reference during cleanup."""
        # Clear previous stage content
        self.bg_canvas.delete('stage_content')
        self._text_bg_images = []  # Clear old background images
        self._button_images = []
        
        # Track current stage for responsive updates
        self.current_stage_method = self.stage_6_final_confirmation
        
        window_width = self.canvas_width if hasattr(self, 'canvas_width') else self.loading_window.winfo_width()
        window_height = self.canvas_height if hasattr(self, 'canvas_height') else self.loading_window.winfo_height()
        
        # Create semi-transparent background panel for content - responsive sizing
        panel_width = max(400, min(int(window_width * 0.6), 800))  # 60% of width, min 400px, max 800px
        panel_height = max(200, min(int(window_height * 0.35), 400))  # 35% of height, min 200px, max 400px
        self.create_text_background(window_width//2, int(window_height * 0.4), panel_width, panel_height)
        
        # Final message
        self.bg_canvas.create_text(window_width//2, int(window_height * 0.25),
                                  text="Ready to Create Mod List",
                                  fill=COLORS['text_highlight'],
                                  font=self.get_font(18, 'bold', 'italic'),
                                  anchor='center', tags='stage_content')
        
        self.bg_canvas.create_text(window_width//2, int(window_height * 0.38),
                                  text="This tool creates a Mods list in your RimWorld folder\nwith the latest from the Steam Workshop collections for Progression.\n\nWould you like to continue?",
                                  fill=COLORS['text_body'],
                                  font=self.get_font(14),
                                  justify='center',
                                  anchor='center', tags='stage_content')
        
        # Continue button with transparent corners
        self.create_canvas_button(window_width//2 - 80, int(window_height * 0.55), "Yes, Continue", 
                                 self.complete_loading, COLORS['accent_green'], width=150)
        
        # Exit button with transparent corners
        self.create_canvas_button(window_width//2 + 80, int(window_height * 0.55), "Exit", 
                                 self.exit_application, COLORS['accent_red'], width=100)
        
        # Ensure logo stays on top
        self._bring_logo_to_front()
    
    def open_hr_website(self, event=None):
        """Open Hudson Riggs Systems website"""
        import webbrowser
        webbrowser.open('https://hudsonriggs.systems')
    
    def on_loading_hr_enter(self, event=None):
        """Handle mouse enter on HR logo in loading screen"""
        # Change cursor to hand and highlight
        self.bg_canvas.config(cursor='hand2')
    
    def on_loading_hr_leave(self, event=None):
        """Handle mouse leave on HR logo in loading screen"""
        self.bg_canvas.config(cursor='')
    
    def open_collection_url(self, url):
        """Open Steam Workshop collection URL in browser"""
        import webbrowser
        # Clean up steam:// protocol URLs
        if url.startswith('steam://openurl/'):
            url = url.replace('steam://openurl/', '')
        webbrowser.open(url)
    
    def on_link_enter(self, label):
        """Handle mouse enter on collection link (hover effect)"""
        label.configure(fg='#66b3ff')  # Lighter blue on hover
    
    def on_link_leave(self, label):
        """Handle mouse leave on collection link (remove hover effect)"""
        label.configure(fg='#4da6ff')  # Return to normal blue
    
    def complete_loading(self):
        """Complete the loading sequence and show main app"""
        self.loading_window.destroy()
        self.on_complete_callback()
    
    def stage_6_final_confirmation(self):
        """Final confirmation stage before proceeding to main application"""
        # Clear previous stage content
        self.bg_canvas.delete('stage_content')
        
        window_width = self.canvas_width if hasattr(self, 'canvas_width') else self.loading_window.winfo_width()
        window_height = self.canvas_height if hasattr(self, 'canvas_height') else self.loading_window.winfo_height()
        
        # Create styled card for final confirmation
        card_width = max(500, min(int(window_width * 0.7), 800))
        card_height = max(250, min(int(window_height * 0.4), 400))
        self.create_modern_card_background(window_width//2, int(window_height * 0.4), card_width, card_height, 'info')
        
        # Title in highlight color - bold italic
        title_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                              size=20, weight='bold', slant='italic')
        self.bg_canvas.create_text(window_width//2, int(window_height * 0.4) - (card_height//2) + 60,
                                  text="Ready to Proceed",
                                  fill=COLORS['text_highlight'],
                                  font=title_font,
                                  anchor='center', tags='stage_content')
        
        # Body text - bold
        body_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                             size=14, weight='bold')
        self.bg_canvas.create_text(window_width//2, int(window_height * 0.4) - 20,
                                  text="All requirements have been verified.",
                                  fill=COLORS['text_primary'],
                                  font=body_font,
                                  anchor='center', tags='stage_content')
        
        # Secondary text - regular
        secondary_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                                  size=12)
        self.bg_canvas.create_text(window_width//2, int(window_height * 0.4) + 10,
                                  text="You can now proceed with the Progression Loader.",
                                  fill=COLORS['text_body'],
                                  font=secondary_font,
                                  anchor='center', tags='stage_content')
        
        # Continue button - bold italic
        continue_font = font.Font(family=self.custom_font_family if self.custom_font_available else FONT_FAMILY, 
                                 size=16, weight='bold', slant='italic')
        continue_text_id = self.bg_canvas.create_text(window_width//2, int(window_height * 0.4) + (card_height//2) - 40,
                                                     text="Launch Main Application",
                                                     fill=COLORS['accent_green'],
                                                     font=continue_font,
                                                     anchor='center', tags=('stage_content', 'launch_btn'))
        
        # Make launch button clickable
        self.bg_canvas.tag_bind('launch_btn', '<Button-1>', lambda e: self.launch_main_application())
        self.bg_canvas.tag_bind('launch_btn', '<Enter>', lambda e: self.bg_canvas.itemconfig(continue_text_id, fill=COLORS['text_highlight']))
        self.bg_canvas.tag_bind('launch_btn', '<Leave>', lambda e: self.bg_canvas.itemconfig(continue_text_id, fill=COLORS['accent_green']))
    
    def launch_main_application(self):
        """Launch the main application"""
        self.complete_loading()
    
    def exit_application(self):
        """Exit the application"""
        self.root.quit()
    
    def on_close(self):
        """Handle window close event"""
        self.exit_application()

class SteamWorkshopGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("Progression: Loader")
        self.root.state('zoomed')  # Make fullscreen on Windows
        self.root.configure(bg=COLORS['bg_primary'])
        
        # Set minimum window size for responsive design and enforce it always
        self.root.minsize(800, 600)
        
        # Bind to enforce minimum size continuously
        self.root.bind('<Configure>', self._enforce_minimum_size)
        
        # Load custom font
        self.load_custom_font()
        
        # Configure dark theme
        self.setup_dark_theme()
        
        # Initialize paths
        self.workshop_folder = None
        self.modsconfig_path = None
        
        # Initialize blinking state and button references
        self.blink_state = False
        self.rimworld_label = None
        self.load_btn = None
        self.merge_btn = None
        self.is_rimworld_valid = False
        self._panel_shells = []
        self._resize_after_id = None
        self._bg_image_id = None
        self._main_content_window_id = None
        self._last_background_size = None
        self._last_main_content_geometry = None
        self._header_render_after_id = None
        self._last_header_requested_size = None
        self._last_header_render_size = None
        
        # Store references for responsive layout
        self.left_frame = None
        self.right_frame = None
        self.content_frame = None
        self.padded_frame = None
        
        # Create canvas for background image with Steam styling
        self.bg_canvas = tk.Canvas(root, highlightthickness=0, bg=COLORS['bg_primary'])
        self.bg_canvas.pack(fill=tk.BOTH, expand=True)
        
        # Load background image
        self.root.update_idletasks()
        self._update_main_background()
        
        # Bind resize event to update background and layout
        self.root.bind('<Configure>', self._on_main_resize)
        
        # Create main frame as canvas window with Steam styling
        self.main_content_frame = tk.Frame(self.bg_canvas, bg=COLORS['bg_primary'])
        
        # Add padding frame with responsive padding and Steam styling
        self.padded_frame = tk.Frame(self.main_content_frame, bg=COLORS['bg_primary'])
        self.padded_frame.pack(fill=tk.BOTH, expand=True, padx=18, pady=18)
        
        # Create header frame for progression logo
        self.create_header_frame(self.padded_frame)
        
        # Create content frame (below header) with responsive layout and Steam styling
        self.content_frame = tk.Frame(self.padded_frame, bg=COLORS['bg_primary'])
        self.content_frame.pack(fill=tk.BOTH, expand=True, pady=(12, 0))
        
        # Create responsive layout
        self._create_responsive_layout()
        
        # Create footer frame for HR Systems logo
        self.create_footer_frame(self.padded_frame)
        
        self.create_input_section(self.left_frame)
        self.create_output_section(self.right_frame)
        
        # Place main content on canvas
        self._position_main_content()
        
        # Log initial message
        self.log_to_output("🔍 Please provide a valid RimWorld installation path to continue.\n")
        self.log_to_output("   The 'RimWorld Game Folder' label will stop blinking once a valid path is detected.\n\n")
        
        # Initialize update checker (for manual checks only)
        config = get_update_config()
        self.update_checker = UpdateChecker(config["current_version"])
        
        # Add update check functionality
        self.add_update_check_button()
        
        # Check initial state now that all GUI elements are created
        self.on_rimworld_path_change()
        
        # Start blinking animation for RimWorld label (with slight delay to ensure GUI is ready)
        self.root.after(1000, self.start_rimworld_label_blink)
    
    def _enforce_minimum_size(self, event):
        """Enforce minimum window size at all times"""
        if event.widget == self.root:
            width = self.root.winfo_width()
            height = self.root.winfo_height()
            
            # Check if window is smaller than minimum
            if width < 800 or height < 600:
                # Resize to minimum if too small
                new_width = max(width, 800)
                new_height = max(height, 600)
                self.root.geometry(f"{new_width}x{new_height}")
    
    def _create_responsive_layout(self):
        """Create responsive layout that adapts to window size"""
        # Get current window dimensions
        window_width = self.root.winfo_width()
        window_height = self.root.winfo_height()

        if self._should_use_side_by_side(window_width, window_height):
            self._current_layout_mode = 'side_by_side'
            self._create_side_by_side_layout()
        else:
            self._current_layout_mode = 'stacked'
            self._create_stacked_layout()

    def _should_use_side_by_side(self, window_width, window_height):
        """Return True when the window can comfortably support the two-column layout."""
        aspect_ratio = window_width / window_height if window_height > 0 else 1.0
        return aspect_ratio > 1.5 and window_width > 1400 and window_height > 1220
    
    def _create_side_by_side_layout(self):
        """Create side-by-side layout for wide screens"""
        # Clear existing layout
        for widget in self.content_frame.winfo_children():
            widget.destroy()
        
        # Left panel for inputs - responsive width
        window_width = self.root.winfo_width()
        left_width = max(350, min(500, int(window_width * 0.35)))  # 35% of width, min 350px, max 500px

        left_shell, self.left_frame = self.create_panel_shell(
            self.content_frame,
            panel_color=COLORS['bg_card'],
            inner_padding=(14, 14),
            halo_padding=18,
            corner_radius=24,
        )
        left_shell.configure(width=left_width)
        left_shell.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 18))
        left_shell.pack_propagate(False)

        # Right panel for output - responsive padding for HR logo
        hr_padding = max(80, min(150, int(window_width * 0.08)))  # Responsive HR logo space
        right_shell, self.right_frame = self.create_panel_shell(
            self.content_frame,
            panel_color=COLORS['bg_card'],
            inner_padding=(16, 14),
            halo_padding=18,
            corner_radius=24,
        )
        right_shell.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(0, hr_padding))
    
    def _create_stacked_layout(self):
        """Create stacked layout for narrow screens"""
        # Clear existing layout
        for widget in self.content_frame.winfo_children():
            widget.destroy()
        
        # Top panel for inputs - responsive height
        window_height = self.root.winfo_height()
        input_height = max(300, min(500, int(window_height * 0.45)))  # 45% of height, min 300px, max 500px

        left_shell, self.left_frame = self.create_panel_shell(
            self.content_frame,
            panel_color=COLORS['bg_card'],
            inner_padding=(14, 14),
            halo_padding=18,
            corner_radius=24,
        )
        left_shell.configure(height=input_height)
        left_shell.pack(side=tk.TOP, fill=tk.X, pady=(0, 18))
        left_shell.pack_propagate(False)

        # Bottom panel for output
        right_shell, self.right_frame = self.create_panel_shell(
            self.content_frame,
            panel_color=COLORS['bg_card'],
            inner_padding=(16, 14),
            halo_padding=18,
            corner_radius=24,
        )
        right_shell.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
    
    def _update_responsive_layout(self):
        """Update layout based on current window size"""
        if hasattr(self, 'content_frame') and self.content_frame.winfo_exists():
            # Store current content before recreating layout
            input_content = None
            output_content = None
            
            # Recreate responsive layout
            self._create_responsive_layout()
            
            # Recreate content in new layout
            if hasattr(self, 'left_frame') and self.left_frame:
                self.create_input_section(self.left_frame)
            if hasattr(self, 'right_frame') and self.right_frame:
                self.create_output_section(self.right_frame)
    
    def _update_main_background(self, width=None, height=None):
        """Update the main window background image"""
        width = width or self.root.winfo_width() or 1920
        height = height or self.root.winfo_height() or 1080
        if width > 1 and height > 1:
            size = (int(width), int(height))
            if size == self._last_background_size:
                return
            self.bg_image_tk = self.load_background_image(width, height)
            if self.bg_image_tk:
                if self._bg_image_id is None:
                    self._bg_image_id = self.bg_canvas.create_image(
                        0,
                        0,
                        image=self.bg_image_tk,
                        anchor='nw',
                        tags='bg',
                    )
                    self.bg_canvas.tag_lower('bg')
                else:
                    self.bg_canvas.itemconfigure(self._bg_image_id, image=self.bg_image_tk)
                self._last_background_size = size
    
    def _position_main_content(self):
        """Position the main content frame on the canvas with responsive sizing"""
        width = self.root.winfo_width() or 1920
        height = self.root.winfo_height() or 1080

        content_width = max(760, min(width - 28, int(width * 0.95)))
        content_height = max(560, min(height - 24, int(height * 0.96)))
        x_pos = max(12, (width - content_width) // 2)
        y_pos = max(12, (height - content_height) // 2)

        geometry = (x_pos, y_pos, content_width, content_height)
        if geometry == self._last_main_content_geometry:
            return

        if self._main_content_window_id is None:
            self._main_content_window_id = self.bg_canvas.create_window(
                x_pos,
                y_pos,
                window=self.main_content_frame,
                anchor='nw',
                width=content_width,
                height=content_height,
                tags='main_content',
            )
        else:
            self.bg_canvas.coords(self._main_content_window_id, x_pos, y_pos)
            self.bg_canvas.itemconfigure(
                self._main_content_window_id,
                width=content_width,
                height=content_height,
            )
        self._last_main_content_geometry = geometry
    
    def load_background_image(self, width, height):
        """Load the Desolation art as the main-app backdrop."""
        try:
            background = load_cover_background(
                get_resource_path("art/Desolation.png"),
                width,
                height,
                overlay_color=COLORS['bg_primary'],
                overlay_alpha=130,
            )
            return ImageTk.PhotoImage(background)
        except Exception as e:
            print(f"Error loading background image: {e}")
            return None
    
    def _on_main_resize(self, event):
        """Handle window resize to update background image and responsive layout"""
        if event.widget == self.root and hasattr(self, 'bg_canvas'):
            # First enforce minimum size
            self._enforce_minimum_size(event)

            current_width = self.root.winfo_width()
            current_height = self.root.winfo_height()
            self._position_main_content()

            if self._resize_after_id is not None:
                self.root.after_cancel(self._resize_after_id)
            self._resize_after_id = self.root.after(
                70,
                lambda width=current_width, height=current_height: self._apply_main_resize(width, height),
            )

    def _apply_main_resize(self, width, height):
        """Apply the expensive resize work after the user pauses dragging."""
        self._resize_after_id = None
        if not self.root.winfo_exists():
            return

        current_width = self.root.winfo_width()
        current_height = self.root.winfo_height()
        width = current_width or width
        height = current_height or height

        self._update_main_background(width, height)

        if hasattr(self, 'header_canvas') and self.header_canvas.winfo_exists():
            header_height = max(70, min(102, int(height * 0.082)))
            if int(self.header_canvas.cget('height')) != header_height:
                self.header_canvas.configure(height=header_height)
            else:
                self._schedule_header_render()

        desired_layout_mode = (
            'side_by_side'
            if self._should_use_side_by_side(width, height)
            else 'stacked'
        )
        if desired_layout_mode != getattr(self, '_current_layout_mode', None):
            self._update_responsive_layout()

        self._last_window_size = (width, height)
    
    def _on_window_map(self, event):
        """Handle window map event (window becomes visible)"""
        if event.widget == self.root:
            # Window is now visible - update layout if needed
            self.root.after(100, self._check_window_state)
    
    def _on_window_unmap(self, event):
        """Handle window unmap event (window becomes hidden)"""
        pass
    
    def _check_window_state(self):
        """Check and update window state tracking"""
        try:
            # Check if window is maximized or fullscreen
            state = self.root.state()
            if state == 'zoomed':
                if not getattr(self, '_is_maximized', False):
                    self._is_maximized = True
                    self._on_window_state_change('maximized')
            elif state == 'normal':
                if getattr(self, '_is_maximized', False):
                    self._is_maximized = False
                    self._on_window_state_change('normal')
        except Exception:
            pass  # Ignore state check errors
    
    def _on_window_state_change(self, new_state):
        """Handle window state changes (maximized, normal, etc.)"""
        # Update responsive layout when window state changes
        self.root.after(200, self._update_responsive_layout)
        
    def create_header_frame(self, parent):
        """Create a striped header bar with a centered, scaled progression logo."""
        window_height = self.root.winfo_height() or 1080
        header_height = max(70, min(102, int(window_height * 0.082)))
        self.header_canvas = tk.Canvas(
            parent,
            bg=COLORS['bg_primary'],
            height=header_height,
            highlightthickness=0,
            bd=0,
            relief='flat',
        )
        self.header_canvas.pack(fill='x', pady=(0, 12))

        try:
            self.progression_logo_source = Image.open(
                get_resource_path("art/GameTitle.png")
            ).convert("RGBA")
        except Exception:
            self.progression_logo_source = None

        self.header_canvas.bind('<Configure>', self._on_header_canvas_configure)
        self.root.after_idle(self._schedule_header_render)

    def _on_header_canvas_configure(self, event):
        """Refresh the header texture and logo when the header size changes."""
        if event.widget != self.header_canvas:
            return
        requested_size = (max(1, int(event.width)), max(1, int(event.height)))
        if requested_size == self._last_header_requested_size:
            return
        self._last_header_requested_size = requested_size
        self._schedule_header_render()

    def _schedule_header_render(self):
        """Debounce expensive header redraw work during window resizes."""
        if self._header_render_after_id is not None:
            self.root.after_cancel(self._header_render_after_id)
        self._header_render_after_id = self.root.after(40, self._render_header_canvas)

    def _render_header_canvas(self):
        """Render the striped header background and scale the logo to fit it."""
        self._header_render_after_id = None
        if not hasattr(self, 'header_canvas') or not self.header_canvas.winfo_exists():
            return

        width = max(1, self.header_canvas.winfo_width())
        height = max(1, self.header_canvas.winfo_height())
        if width < 40 or height < 24:
            return
        render_size = (width, height)
        if render_size == self._last_header_render_size:
            return

        header_texture = create_striped_texture(
            width,
            height,
            base_color=COLORS['bg_card'],
            stripe_color=COLORS['stripe_soft'],
            stripe_width=18,
            stripe_gap=18,
            stripe_alpha=118,
            corner_radius=22,
            border_color=COLORS['border_light'],
            border_width=1,
            border_alpha=185,
        )
        self.header_bg_tk = ImageTk.PhotoImage(header_texture)

        if hasattr(self, 'header_bg_id'):
            self.header_canvas.itemconfigure(self.header_bg_id, image=self.header_bg_tk)
        else:
            self.header_bg_id = self.header_canvas.create_image(
                0,
                0,
                image=self.header_bg_tk,
                anchor='nw',
            )

        self.header_canvas.delete('header_content')

        if self.progression_logo_source is not None:
            available_width = max(120, width - 72)
            available_height = max(24, height - 18)
            scale = min(
                available_width / self.progression_logo_source.width,
                available_height / self.progression_logo_source.height,
            )
            logo_width = max(1, int(self.progression_logo_source.width * scale))
            logo_height = max(1, int(self.progression_logo_source.height * scale))
            logo_image = self.progression_logo_source.resize(
                (logo_width, logo_height),
                Image.Resampling.LANCZOS,
            )
            self.progression_logo_tk = ImageTk.PhotoImage(logo_image)
            self.header_canvas.create_image(
                width // 2,
                height // 2,
                image=self.progression_logo_tk,
                anchor='center',
                tags='header_content',
            )
        else:
            font_size = max(16, min(24, int(height * 0.32)))
            self.header_canvas.create_text(
                width // 2,
                height // 2,
                text="PROGRESSION PACK",
                fill=COLORS['text_highlight'],
                font=self.get_font(font_size, 'bold', 'italic'),
                anchor='center',
                tags='header_content',
            )
        self._last_header_render_size = render_size
    
    def create_footer_frame(self, parent):
        """Create footer frame with HR Systems logo in bottom right - responsive sizing"""
        # Responsive footer height
        window_height = self.root.winfo_height() or 1080
        footer_height = max(62, min(82, int(window_height * 0.064)))

        footer_shell, self.footer_frame = self.create_panel_shell(
            parent,
            panel_color=COLORS['bg_card'],
            inner_padding=(16, 8),
            halo_padding=16,
            corner_radius=22,
        )
        footer_shell.configure(height=footer_height)
        footer_shell.pack(fill='x', side=tk.BOTTOM, pady=(12, 0))
        footer_shell.pack_propagate(False)

        # Create clickable HR Systems logo in bottom right
        self.create_hr_logo(self.footer_frame)
    
    def create_hr_logo(self, parent):
        """Create clickable HR Systems logo with radial glow hover effects - responsive sizing"""
        # Responsive positioning and sizing
        window_width = self.root.winfo_width() or 1920
        window_height = self.root.winfo_height() or 1080
        
        # Responsive padding based on window size
        padding_x = max(20, min(60, int(window_width * 0.03)))  # 3% of width, min 20px, max 60px
        padding_y = max(15, min(30, int(window_height * 0.02)))  # 2% of height, min 15px, max 30px

        parent_bg = parent.cget('bg')

        # Container for logo positioned in bottom right with extra space for glow
        logo_frame = tk.Frame(parent, bg=parent_bg)
        logo_frame.pack(side=tk.RIGHT, anchor='se', padx=padding_x, pady=padding_y)
        
        try:
            # Load HR Systems logo
            hr_img = Image.open(get_resource_path("art/hudsonriggssystems.png"))
            # Responsive logo sizing
            img_width, img_height = hr_img.size
            logo_height = max(40, min(80, int(window_height * 0.06)))  # 6% of height, min 40px, max 80px
            logo_width = int((logo_height / img_height) * img_width)
            hr_img = hr_img.resize((logo_width, logo_height), Image.Resampling.LANCZOS)
            self.hr_logo_normal = ImageTk.PhotoImage(hr_img)
            
            # Create radial glow effect version
            self.hr_logo_glow = self.create_radial_glow_image(hr_img)
            
            # Create clickable label with fixed size to prevent position changes
            # Calculate the glow image size to set fixed dimensions
            glow_padding = max(15, min(25, int(logo_height * 0.4)))  # Responsive glow padding
            label_width = logo_width + (glow_padding * 2)
            label_height = logo_height + (glow_padding * 2)
            
            self.hr_logo_label = tk.Label(logo_frame,
                                        image=self.hr_logo_normal,
                                        bg=parent_bg,
                                        cursor='hand2',
                                        width=label_width,
                                        height=label_height,
                                        compound='center')  # Center the image within the fixed size
            self.hr_logo_label.pack()
            
            # Bind click and hover events
            self.hr_logo_label.bind('<Button-1>', self.open_hr_website)
            self.hr_logo_label.bind('<Enter>', self.on_hr_logo_enter)
            self.hr_logo_label.bind('<Leave>', self.on_hr_logo_leave)
            
        except Exception as e:
            # Fallback text link if image loading fails - responsive font size
            font_size = max(8, min(12, int(window_height * 0.012)))  # Responsive font size
            self.hr_logo_label = tk.Label(logo_frame,
                                        text="Hudson Riggs Systems",
                                        fg=COLORS['text_muted'],
                                        bg=parent_bg,
                                        font=self.get_font(font_size, 'normal', 'italic'),
                                        cursor='hand2')
            self.hr_logo_label.pack()
            
            # Bind events for text version
            self.hr_logo_label.bind('<Button-1>', self.open_hr_website)
            self.hr_logo_label.bind('<Enter>', self.on_hr_text_enter)
            self.hr_logo_label.bind('<Leave>', self.on_hr_text_leave)
    
    def create_radial_glow_image(self, base_image):
        """Create a radial glow effect around the image with #ff8686 color"""
        # Get base image dimensions
        base_width, base_height = base_image.size
        
        # Create larger canvas for glow effect
        glow_padding = 20
        glow_width = base_width + (glow_padding * 2)
        glow_height = base_height + (glow_padding * 2)
        
        # Create glow background matching the label background
        glow_bg = Image.new('RGBA', (glow_width, glow_height), (43, 43, 43, 255))  # #2b2b2b background
        
        # Create radial glow mask
        glow_mask = Image.new('L', (glow_width, glow_height), 0)
        draw = ImageDraw.Draw(glow_mask)
        
        # Calculate center and create multiple circles for smooth gradient
        center_x, center_y = glow_width // 2, glow_height // 2
        
        # Create glow that extends beyond the original image
        max_radius = glow_padding + max(base_width, base_height) // 2
        
        # Create radial gradient by drawing concentric circles
        for i in range(max_radius, 0, -1):
            # Calculate alpha based on distance from center (stronger in center)
            # Make the glow more intense closer to the logo
            if i <= max(base_width, base_height) // 2:
                # Inner area (around the logo) - stronger glow
                alpha = int(255 * 0.8)
            else:
                # Outer area - fade out
                fade_factor = (max_radius - i) / (max_radius - max(base_width, base_height) // 2)
                alpha = int(255 * fade_factor * 0.6)
            
            if alpha > 0:
                draw.ellipse([center_x - i, center_y - i, center_x + i, center_y + i], fill=alpha)
        
        # Apply blur for smooth glow effect
        glow_mask = glow_mask.filter(ImageFilter.GaussianBlur(radius=8))
        
        # Create glow color layer (#ff8686)
        glow_color = Image.new('RGBA', (glow_width, glow_height), (255, 134, 134, 0))
        glow_layer = Image.new('RGBA', (glow_width, glow_height), (255, 134, 134, 255))
        
        # Apply the mask to create the glow effect
        glow_color.paste(glow_layer, mask=glow_mask)
        
        # Composite glow onto background
        glow_bg = Image.alpha_composite(glow_bg, glow_color)
        
        # Convert base image to RGBA if needed
        if base_image.mode != 'RGBA':
            base_image = base_image.convert('RGBA')
        
        # Paste the original image in the exact center to maintain position
        paste_x = (glow_width - base_width) // 2
        paste_y = (glow_height - base_height) // 2
        glow_bg.paste(base_image, (paste_x, paste_y), base_image)
        
        return ImageTk.PhotoImage(glow_bg)
    
    def open_hr_website(self, event=None):
        """Open Hudson Riggs Systems website"""
        import webbrowser
        webbrowser.open('https://hudsonriggs.systems')
    
    def on_hr_logo_enter(self, event=None):
        """Handle mouse enter on HR logo (radial glow effect)"""
        if hasattr(self, 'hr_logo_glow'):
            self.hr_logo_label.configure(image=self.hr_logo_glow)
    
    def on_hr_logo_leave(self, event=None):
        """Handle mouse leave on HR logo (remove glow effect)"""
        if hasattr(self, 'hr_logo_normal'):
            self.hr_logo_label.configure(image=self.hr_logo_normal)
    
    def on_hr_text_enter(self, event=None):
        """Handle mouse enter on HR text (glow effect)"""
        self.hr_logo_label.configure(
            fg=COLORS['text_highlight'],
            bg=self.hr_logo_label.master.cget('bg'),
        )

    def on_hr_text_leave(self, event=None):
        """Handle mouse leave on HR text (remove glow effect)"""
        self.hr_logo_label.configure(
            fg=COLORS['text_muted'],
            bg=self.hr_logo_label.master.cget('bg'),
        )
        
    def load_custom_font(self):
        """Load all Georgia font variants using Windows AddFontResourceEx with private flag"""
        self.custom_font_available = False
        self.custom_font_family = None
        
        # List of Georgia font files to load
        georgia_fonts = [
            "georgia.ttf",      # Regular
            "georgiab.ttf",     # Bold
            "georgiai.ttf",     # Italic
            "georgiaz.ttf"      # Bold Italic
        ]
        
        fonts_loaded = 0
        
        try:
            for font_file in georgia_fonts:
                font_path = get_resource_path(os.path.join("art", font_file))
                if os.path.exists(font_path):
                    abs_font_path = os.path.abspath(font_path)
                    
                    # Use the Stack Overflow method with AddFontResourceEx
                    success = self._load_font_private(abs_font_path)
                    
                    if success:
                        fonts_loaded += 1
                        print(f"Successfully loaded font: {font_file}")
                    else:
                        print(f"Failed to load font: {font_file}")
                else:
                    print(f"Font file not found: {font_file}")
            
            # If at least one font was loaded successfully
            if fonts_loaded > 0:
                self.custom_font_family = "Georgia"
                self.custom_font_available = True
                print(f"Georgia font family loaded successfully ({fonts_loaded}/{len(georgia_fonts)} variants)")
                return
            else:
                print("No Georgia fonts could be loaded")
                self.custom_font_available = False
                
        except Exception as e:
            print(f"Error loading Georgia fonts: {e}")
            self.custom_font_available = False
        
        # Initialize fallback values if font loading failed
        if not hasattr(self, 'custom_font_available'):
            self.custom_font_available = False
        if not hasattr(self, 'custom_font_family'):
            self.custom_font_family = None
    
    def _load_font_private(self, fontpath):
        """
        Load font privately using AddFontResourceEx
        Based on Stack Overflow solution by Felipe
        """
        try:
            from ctypes import windll, byref, create_unicode_buffer
            
            # Constants for AddFontResourceEx
            FR_PRIVATE = 0x10   # Font is private to this process
            FR_NOT_ENUM = 0x20  # Font won't appear in font enumeration
            
            # Create unicode buffer for the font path
            pathbuf = create_unicode_buffer(fontpath)
            
            # Use AddFontResourceExW for Unicode strings
            AddFontResourceEx = windll.gdi32.AddFontResourceExW
            
            # Set flags: private (unloaded when process dies) and not enumerable
            flags = FR_PRIVATE | FR_NOT_ENUM
            
            # Add the font resource
            numFontsAdded = AddFontResourceEx(byref(pathbuf), flags, 0)
            
            return bool(numFontsAdded)
            
        except Exception:
            return False
    
    def get_font(self, size=10, weight='normal', slant='roman'):
        """Get font tuple for UI elements"""
        if weight in {'italic', 'roman'}:
            slant = weight
            weight = 'normal'
        elif weight in {'bold italic', 'italic bold'}:
            weight = 'bold'
            slant = 'italic'

        if self.custom_font_available and self.custom_font_family:
            try:
                return font.Font(
                    family=self.custom_font_family,
                    size=size,
                    weight=weight,
                    slant=slant,
                )
            except Exception:
                pass
        
        # Fall back to system font
        return font.Font(family=FONT_FAMILY, size=size, weight=weight, slant=slant)
    
    def get_ctk_font(self, size=10, weight='normal'):
        """Get CTkFont for UI elements"""
        if self.custom_font_available and self.custom_font_family:
            try:
                return ctk.CTkFont(family=self.custom_font_family, size=size, weight=weight)
            except Exception:
                pass
        
        # Fall back to system font
        return ctk.CTkFont(family=FONT_FAMILY, size=size, weight=weight)
        
    def setup_dark_theme(self):
        """Configure the shared charcoal theme for ttk widgets."""
        style = ttk.Style()
        style.theme_use('clam')
        
        style.configure('TLabel', 
                       background=COLORS['bg_primary'], 
                       foreground=COLORS['text_primary'])
        style.configure('TButton', 
                       background=COLORS['bg_card'], 
                       foreground=COLORS['text_primary'],
                       borderwidth=0,
                       focuscolor='none')
        style.map('TButton', 
                 background=[('active', COLORS['bg_hover']),
                           ('pressed', COLORS['bg_tertiary'])])
        style.configure('TEntry', 
                       background=COLORS['bg_card'], 
                       foreground=COLORS['text_primary'], 
                       fieldbackground=COLORS['bg_card'],
                       borderwidth=1,
                       insertcolor=COLORS['text_primary'])
        style.map('TEntry',
                 focuscolor=[('focus', COLORS['accent_blue'])])

    def style_entry_widget(self, widget, *, muted=False, readonly=False):
        """Apply the shared dark field styling to tk.Entry widgets."""
        widget.configure(
            bg=COLORS['bg_tertiary'],
            fg=COLORS['text_muted'] if muted else COLORS['text_primary'],
            insertbackground=COLORS['text_primary'],
            relief='flat',
            bd=0,
            highlightthickness=1,
            highlightcolor=COLORS['accent_yellow'],
            highlightbackground=COLORS['border_light'],
            disabledbackground=COLORS['bg_tertiary'],
            disabledforeground=COLORS['text_muted'],
            selectbackground=COLORS['bg_hover'],
            selectforeground=COLORS['text_primary'],
        )
        if readonly:
            widget.configure(readonlybackground=COLORS['bg_tertiary'])

    def style_action_button(self, button, accent_color):
        """Apply the shared text-only action style to tk buttons."""
        style_text_button(
            button,
            accent_color,
            button.cget('bg'),
            hover_foreground=COLORS['text_primary'],
            disabled_foreground=COLORS['text_muted'],
        )

    def _normalize_padding(self, padding):
        """Normalize scalar or tuple padding values into x/y padding."""
        if isinstance(padding, (tuple, list)):
            if len(padding) == 2:
                return int(padding[0]), int(padding[1])
            if len(padding) == 1:
                return int(padding[0]), int(padding[0])
        return int(padding), int(padding)

    def create_panel_shell(
        self,
        parent,
        *,
        panel_color=None,
        inner_padding=18,
        halo_padding=22,
        corner_radius=24,
    ):
        """Create a striped halo shell with an inner content frame."""
        shell = tk.Frame(parent, bg=COLORS['bg_primary'], bd=0, highlightthickness=0)
        canvas = tk.Canvas(
            shell,
            bg=COLORS['bg_primary'],
            highlightthickness=0,
            bd=0,
            relief='flat',
        )
        canvas.pack(fill='both', expand=True)

        pad_x, pad_y = self._normalize_padding(inner_padding)
        inner_frame = tk.Frame(canvas, bg=panel_color or COLORS['bg_card'], bd=0, highlightthickness=0)
        window_id = canvas.create_window(0, 0, window=inner_frame, anchor='nw')

        canvas._panel_options = {
            'panel_color': panel_color or COLORS['bg_card'],
            'halo_padding': int(halo_padding),
            'corner_radius': int(corner_radius),
            'inner_padding': (pad_x, pad_y),
        }
        canvas._panel_window_id = window_id
        canvas._panel_after_id = None
        canvas._panel_requested_size = None
        canvas._panel_rendered_size = None
        canvas.bind('<Configure>', self._refresh_panel_shell)
        self._panel_shells.append(canvas)
        self.root.after_idle(lambda current_canvas=canvas: self._schedule_panel_shell_render(current_canvas))
        return shell, inner_frame

    def _refresh_panel_shell(self, event):
        """Repaint a striped shell when its canvas changes size."""
        canvas = event.widget
        if not canvas.winfo_exists():
            return
        requested_size = (max(1, int(event.width)), max(1, int(event.height)))
        if requested_size == getattr(canvas, '_panel_requested_size', None):
            return
        canvas._panel_requested_size = requested_size
        self._schedule_panel_shell_render(canvas)

    def _schedule_panel_shell_render(self, canvas):
        """Debounce striped shell redraw work during resizes."""
        if not canvas.winfo_exists():
            return
        after_id = getattr(canvas, '_panel_after_id', None)
        if after_id is not None:
            self.root.after_cancel(after_id)
        canvas._panel_after_id = self.root.after(
            40,
            lambda current_canvas=canvas: self._render_panel_shell(current_canvas),
        )

    def _render_panel_shell(self, canvas):
        """Render the shared striped halo panel background onto a shell canvas."""
        canvas._panel_after_id = None
        if not canvas.winfo_exists() or not hasattr(canvas, '_panel_options'):
            return

        width = max(1, canvas.winfo_width())
        height = max(1, canvas.winfo_height())
        options = canvas._panel_options
        halo_padding = options['halo_padding']
        inner_pad_x, inner_pad_y = options['inner_padding']

        minimum_size = (halo_padding * 2) + 6
        if width < minimum_size or height < minimum_size:
            return
        render_size = (width, height)
        if render_size == getattr(canvas, '_panel_rendered_size', None):
            return

        panel_width = max(1, width - (halo_padding * 2))
        panel_height = max(1, height - (halo_padding * 2))
        panel_image = create_striped_panel(
            panel_width,
            panel_height,
            panel_color=options['panel_color'],
            stripe_color=COLORS['stripe_soft'],
            stripe_width=18,
            stripe_gap=18,
            stripe_alpha=78,
            halo_padding=halo_padding,
            halo_alpha=120,
            panel_alpha=238,
            corner_radius=options['corner_radius'],
            border_color=COLORS['border_light'],
            border_width=1,
            border_alpha=190,
        )

        canvas._panel_bg_tk = ImageTk.PhotoImage(panel_image)
        if hasattr(canvas, '_panel_bg_id'):
            canvas.itemconfigure(canvas._panel_bg_id, image=canvas._panel_bg_tk)
        else:
            canvas._panel_bg_id = canvas.create_image(0, 0, image=canvas._panel_bg_tk, anchor='nw')
            canvas.tag_lower(canvas._panel_bg_id)

        content_width = max(1, width - (2 * (halo_padding + inner_pad_x)))
        content_height = max(1, height - (2 * (halo_padding + inner_pad_y)))
        canvas.coords(canvas._panel_window_id, halo_padding + inner_pad_x, halo_padding + inner_pad_y)
        canvas.itemconfigure(canvas._panel_window_id, width=content_width, height=content_height)
        canvas._panel_rendered_size = render_size

    def create_steam_card(self, parent, title=None, padding=8):
        """Create a striped charcoal card container."""
        card_frame = tk.Frame(parent, bg=COLORS['bg_card'], relief='flat', bd=0)
        card_frame.configure(highlightbackground=COLORS['border_light'], 
                           highlightcolor=COLORS['border_light'], 
                           highlightthickness=1)
        
        if title:
            title_frame = tk.Frame(card_frame, bg=COLORS['bg_card'])
            title_frame.pack(fill='x', padx=padding, pady=(padding, 4))
            
            title_label = tk.Label(title_frame, text=title,
                                 font=self.get_font(12, 'bold', 'italic'),
                                 bg=COLORS['bg_card'], 
                                 fg=COLORS['text_highlight'])
            title_label.pack(anchor='w')
            
            # Add separator line
            separator = tk.Frame(title_frame, bg=COLORS['border_light'], height=1)
            separator.pack(fill='x', pady=(4, 0))
        
        content_frame = tk.Frame(card_frame, bg=COLORS['bg_card'])
        content_frame.pack(fill='both', expand=True, padx=padding, pady=padding)
        
        return card_frame, content_frame
        
    def create_input_section(self, parent):
        """Create the left input section with Steam Collections inspired design"""
        parent_bg = parent.cget('bg')

        title_label = tk.Label(
            parent,
            text="Pack Configuration",
            font=self.get_font(17, 'bold', 'italic'),
            bg=parent_bg,
            fg=COLORS['text_highlight'],
        )
        title_label.pack(anchor='w', pady=(0, 4))

        subtitle_label = tk.Label(
            parent,
            text="Point Progression at RimWorld, then review the workshop sources and actions below.",
            font=self.get_font(10),
            bg=parent_bg,
            fg=COLORS['text_secondary'],
            justify='left',
            wraplength=390,
        )
        subtitle_label.pack(anchor='w', pady=(0, 14))
        
        # RimWorld Path Card
        rimworld_card, rimworld_content = self.create_steam_card(parent, "RimWorld Installation")
        rimworld_card.pack(fill='x', pady=(0, 10))
        
        self.rimworld_label = tk.Label(rimworld_content, text="Game Folder Path:", 
                                 font=self.get_font(10, 'bold'), 
                                 bg=COLORS['bg_card'], 
                                 fg=COLORS['text_secondary'])
        self.rimworld_label.pack(anchor='w', pady=(0, 5))
        
        rimworld_help = tk.Label(rimworld_content, 
                                text="Right-click RimWorld in Steam → Manage → Browse local files, then copy that path",
                                font=self.get_font(8), 
                                bg=COLORS['bg_card'], 
                                fg=COLORS['text_muted'], 
                                wraplength=380)
        rimworld_help.pack(anchor='w', pady=(0, 10))
        
        rimworld_input_frame = tk.Frame(rimworld_content, bg=COLORS['bg_card'])
        rimworld_input_frame.pack(fill='x', pady=(0, 5))
        
        self.rimworld_var = tk.StringVar()
        self.rimworld_entry = tk.Entry(rimworld_input_frame, textvariable=self.rimworld_var,
                                      font=self.get_font(10))
        self.style_entry_widget(self.rimworld_entry)
        self.rimworld_entry.pack(side='left', fill='x', expand=True, padx=(0, 10))
        self.rimworld_entry.bind('<KeyRelease>', self.on_rimworld_path_change)
        
        browse_game_btn = tk.Button(rimworld_input_frame, text="Browse", 
                                   command=self.browse_rimworld_folder,
                                   bg=COLORS['bg_card'], 
                                   font=self.get_font(10, 'bold', 'italic'),
                                   padx=6, pady=2)
        self.style_action_button(browse_game_btn, COLORS['accent_yellow'])
        browse_game_btn.pack(side='right')
        
        # Workshop Path Card (Auto-derived)
        workshop_card, workshop_content = self.create_steam_card(parent, "Workshop Content")
        workshop_card.pack(fill='x', pady=(0, 10))
        
        workshop_label = tk.Label(workshop_content, text="Workshop Folder (Auto-derived):", 
                                 font=self.get_font(10, 'bold'), 
                                 bg=COLORS['bg_card'], 
                                 fg=COLORS['text_secondary'])
        workshop_label.pack(anchor='w', pady=(0, 5))
        
        self.workshop_var = tk.StringVar(value="Enter RimWorld path above to auto-detect")
        self.workshop_display = tk.Entry(workshop_content, textvariable=self.workshop_var,
                                        font=self.get_font(8),
                                        state='readonly')
        self.style_entry_widget(self.workshop_display, muted=True, readonly=True)
        self.workshop_display.pack(fill='x', pady=(0, 5))
        self.workshop_display.bind('<KeyRelease>', self.on_workshop_path_change)
        
        # Configuration Card
        config_card, config_content = self.create_steam_card(parent, "Advanced Configuration")
        config_card.pack(fill='x', pady=(0, 10))
        
        # Warning text with Steam styling
        warning_label = tk.Label(config_content, 
                               text="⚠️ Advanced settings - modify only if you know what you're doing", 
                               font=self.get_font(8, 'italic'), 
                               bg=COLORS['bg_card'], 
                               fg=COLORS['accent_yellow'])
        warning_label.pack(anchor='w', pady=(0, 10))
        
        # ModsConfig.xml path
        modsconfig_label = tk.Label(config_content, text="ModsConfig.xml Path:", 
                                   font=self.get_font(9, 'bold'), 
                                   bg=COLORS['bg_card'], 
                                   fg=COLORS['text_secondary'])
        modsconfig_label.pack(anchor='w', pady=(0, 5))
        
        self.modsconfig_var = tk.StringVar()
        self.modsconfig_display = tk.Entry(config_content, textvariable=self.modsconfig_var,
                                          font=self.get_font(8))
        self.style_entry_widget(self.modsconfig_display)
        self.modsconfig_display.pack(fill='x', pady=(0, 15))
        self.modsconfig_display.bind('<KeyRelease>', self.on_modsconfig_path_change)
        
        # Initialize ModsConfig path
        self.find_modsconfig_path()
        
        # Collections Card
        collections_card, collections_content = self.create_steam_card(parent, "Steam Workshop Collections")
        collections_card.pack(fill='x', pady=(0, 12))
        
        # Core collection
        core_url = os.getenv('CORE_COLLECTION_URL', 'https://steamcommunity.com/workshop/filedetails/?id=3521297585')
        self.create_steam_url_input(collections_content, "Core Collection:", core_url)
        
        # Content collection  
        content_url = os.getenv('CONTENT_COLLECTION_URL', 'steam://openurl/https://steamcommunity.com/sharedfiles/filedetails/?id=3521319712')
        self.create_steam_url_input(collections_content, "Content Collection:", content_url)
        
        # Cosmetics collection
        cosmetics_url = os.getenv('COSMETICS_COLLECTION_URL', 'steam://openurl/https://steamcommunity.com/sharedfiles/filedetails/?id=3637541646')
        self.create_steam_url_input(collections_content, "Cosmetics Collection:", cosmetics_url)
        
        # Action Buttons with Steam styling
        buttons_frame = tk.Frame(parent, bg=parent_bg)
        buttons_frame.pack(fill='x', pady=(6, 0))
        
        # Load Progression Pack button (Primary Steam button)
        self.load_btn = tk.Button(buttons_frame, text="Load Progression Pack Complete", 
                            command=self.load_progression_pack,
                            bg=parent_bg,
                            font=self.get_font(14, 'bold', 'italic'),
                            padx=0, pady=6,
                            state='disabled')
        self.style_action_button(self.load_btn, COLORS['accent_green'])
        self.load_btn.pack(pady=(0, 10))
        
        # Merge with Current Mods button (Secondary Steam button)
        self.merge_btn = tk.Button(buttons_frame, text="Merge with Current Mods Config", 
                             command=self.merge_with_current_mods,
                             bg=parent_bg,
                             font=self.get_font(12, 'bold', 'italic'),
                             padx=0, pady=4,
                             state='disabled')
        self.style_action_button(self.merge_btn, COLORS['accent_yellow'])
        self.merge_btn.pack()
        
    def create_steam_url_input(self, parent, label_text, default_url):
        """Create a Steam-styled URL input field"""
        url_frame = tk.Frame(parent, bg=COLORS['bg_card'])
        url_frame.pack(fill='x', pady=(0, 10))
        
        label = tk.Label(url_frame, text=label_text, 
                        font=self.get_font(9, 'bold'),
                        bg=COLORS['bg_card'], 
                        fg=COLORS['text_secondary'])
        label.pack(anchor='w', pady=(0, 3))
        
        entry = tk.Entry(url_frame, font=self.get_font(8), 
                        bg=COLORS['bg_tertiary'])
        self.style_entry_widget(entry)
        entry.pack(fill='x')
        entry.insert(0, default_url)
        
        # Store reference to entry widgets
        if not hasattr(self, 'url_entries'):
            self.url_entries = {}
        
        collection_name = label_text.replace(" Collection:", "").lower()
        self.url_entries[collection_name] = entry
        
    def create_output_section(self, parent):
        """Create the right output section with Steam Collections inspired styling"""
        # Responsive font sizes
        window_height = self.root.winfo_height() or 1080
        title_font_size = max(12, min(16, int(window_height * 0.015)))
        text_font_size = max(8, min(12, int(window_height * 0.011)))
        
        # Create Steam-style card for logs
        logs_card, logs_content = self.create_steam_card(parent, "System Logs")
        logs_card.pack(fill='both', expand=True)
        
        # Output text area with Steam styling
        self.output_text = scrolledtext.ScrolledText(logs_content, 
                                                    font=self.get_font(text_font_size),
                                                    bg=COLORS['bg_tertiary'], 
                                                    fg=COLORS['text_body'],
                                                    insertbackground=COLORS['text_primary'],
                                                    selectbackground=COLORS['bg_hover'],
                                                    selectforeground=COLORS['text_primary'],
                                                    relief='flat', bd=0,
                                                    highlightthickness=1,
                                                    highlightcolor=COLORS['accent_yellow'],
                                                    highlightbackground=COLORS['border_light'])
        self.output_text.pack(fill=tk.BOTH, expand=True)
        
        # Configure text tags for different log levels
        self.output_text.tag_configure("success", foreground=COLORS['accent_green'])
        self.output_text.tag_configure("error", foreground=COLORS['accent_red'])
        self.output_text.tag_configure("warning", foreground=COLORS['accent_yellow'])
        self.output_text.tag_configure("info", foreground=COLORS['accent_blue'])
        self.output_text.tag_configure("highlight", foreground=COLORS['text_highlight'])
    
    def log_to_output(self, message, level="info"):
        """Log a message to the output text area with Steam-style coloring"""
        if hasattr(self, 'output_text'):
            # Add timestamp
            import datetime
            timestamp = datetime.datetime.now().strftime("%H:%M:%S")
            
            # Insert timestamp with muted color
            self.output_text.insert(tk.END, f"[{timestamp}] ", "timestamp")
            
            # Insert message with appropriate color based on level
            if "✓" in message or "success" in message.lower():
                self.output_text.insert(tk.END, message, "success")
            elif "✗" in message or "error" in message.lower() or "failed" in message.lower():
                self.output_text.insert(tk.END, message, "error")
            elif "⚠️" in message or "warning" in message.lower():
                self.output_text.insert(tk.END, message, "warning")
            elif "🔍" in message or "info" in message.lower():
                self.output_text.insert(tk.END, message, "info")
            else:
                self.output_text.insert(tk.END, message)
            
            self.output_text.see(tk.END)  # Scroll to bottom
        
        # Configure timestamp tag if not already configured
        if hasattr(self, 'output_text') and not self.output_text.tag_cget("timestamp", "foreground"):
            self.output_text.tag_configure("timestamp", foreground=COLORS['text_muted'])
        
    def on_workshop_path_change(self, event=None):
        """Called when workshop path is manually changed"""
        self.workshop_folder = self.workshop_var.get().strip()
    
    def on_modsconfig_path_change(self, event=None):
        """Called when ModsConfig path is manually changed"""
        self.modsconfig_path = self.modsconfig_var.get().strip()
    
    def on_rimworld_path_change(self, event=None):
        """Called when RimWorld path is changed"""
        rimworld_path = self.rimworld_var.get().strip()
        if rimworld_path:
            # Check if this is a valid RimWorld installation
            is_valid = self.validate_rimworld_path(rimworld_path)
            
            if is_valid:
                # Stop blinking and set to Steam success color
                self.is_rimworld_valid = True
                self.rimworld_label.configure(fg=COLORS['accent_green'])  # Steam green for valid path
                self.enable_buttons()
                
                # Derive workshop path from RimWorld path
                # From: D:\SteamLibrary\steamapps\common\RimWorld
                # To: D:\SteamLibrary\steamapps\workshop\content\294100
                
                normalized_rimworld_path = os.path.normpath(rimworld_path)
                if "steamapps" in normalized_rimworld_path.lower():
                    # Find the steamapps part and replace common\RimWorld with workshop\content\294100
                    parts = normalized_rimworld_path.split(os.sep)
                    try:
                        steamapps_index = next(i for i, part in enumerate(parts) if part.lower() == 'steamapps')
                        # Build new path: everything up to steamapps + steamapps + workshop + content + 294100
                        workshop_parts = parts[:steamapps_index + 1] + ['workshop', 'content', '294100']
                        workshop_path = os.sep.join(workshop_parts)
                        self.workshop_folder = workshop_path
                        self.workshop_var.set(workshop_path)
                        
                        # Log success
                        self.log_to_output(f"✓ Valid RimWorld installation detected at: {rimworld_path}\n")
                        self.log_to_output(f"✓ Workshop folder derived: {workshop_path}\n")
                        
                    except (StopIteration, IndexError):
                        self.log_to_output("⚠️ Could not auto-derive workshop path from RimWorld path\n")
                else:
                    self.log_to_output("⚠️ RimWorld path doesn't appear to be in a Steam library\n")
            else:
                # Invalid path - start blinking and disable buttons
                self.is_rimworld_valid = False
                self.disable_buttons()
                self.start_rimworld_label_blink()
                self.log_to_output("✗ Invalid RimWorld installation path\n")
        else:
            # Empty path - disable buttons and stop blinking
            self.is_rimworld_valid = False
            self.disable_buttons()
            if hasattr(self, 'rimworld_label'):
                self.rimworld_label.configure(fg=COLORS['text_secondary'])  # Reset to normal color
                        
    def enable_buttons(self):
        """Enable the primary actions once the RimWorld path is valid."""
        if hasattr(self, 'load_btn'):
            self.load_btn.configure(state='normal')
        if hasattr(self, 'merge_btn'):
            self.merge_btn.configure(state='normal')
    
    def disable_buttons(self):
        """Disable the primary actions until the RimWorld path is valid."""
        if hasattr(self, 'load_btn'):
            self.load_btn.configure(state='disabled')
        if hasattr(self, 'merge_btn'):
            self.merge_btn.configure(state='disabled')
    
    def validate_rimworld_path(self, path):
        """Validate if the given path is a valid RimWorld installation"""
        if not path or not os.path.exists(path):
            return False
        
        # Check for RimWorld executable (can be RimWorld.exe or RimWorldWin64.exe)
        rimworld_exe = os.path.join(path, "RimWorld.exe")
        rimworld_win64_exe = os.path.join(path, "RimWorldWin64.exe")
        if not (os.path.exists(rimworld_exe) or os.path.exists(rimworld_win64_exe)):
            return False
        
        # Check for Data folder (contains core game data)
        data_folder = os.path.join(path, "Data")
        if not os.path.exists(data_folder):
            return False
        
        # Check for Core mod in Data folder
        core_folder = os.path.join(data_folder, "Core")
        if not os.path.exists(core_folder):
            return False
        
        return True
    
    def start_rimworld_label_blink(self):
        """Start the blinking animation for RimWorld label"""
        if self.rimworld_label:
            if not self.is_rimworld_valid:
                if self.blink_state:
                    self.rimworld_label.configure(fg=COLORS['accent_yellow'])
                else:
                    self.rimworld_label.configure(fg=COLORS['text_muted'])
                
                self.blink_state = not self.blink_state
                
                self.root.after(500, self.start_rimworld_label_blink)
    
    def browse_rimworld_folder(self):
        """Allow user to browse for RimWorld game folder"""
        folder = filedialog.askdirectory(
            title="Select RimWorld Game Folder (Right-click RimWorld in Steam > Manage > Browse local files)",
            initialdir="C:\\"
        )
        if folder:
            self.rimworld_var.set(folder)
            self.on_rimworld_path_change()  # Update workshop path
    
    def find_modsconfig_path(self):
        """Find the ModsConfig.xml file in AppData"""
        try:
            # Get path template from .env
            path_template = os.getenv('MODSCONFIG_PATH_TEMPLATE', 
                                    r'%USERPROFILE%\AppData\LocalLow\Ludeon Studios\RimWorld by Ludeon Studios\Config\ModsConfig.xml')
            
            # Expand environment variables
            modsconfig_path = os.path.expandvars(path_template)
            
            if os.path.exists(modsconfig_path):
                self.modsconfig_path = modsconfig_path
                self.modsconfig_var.set(modsconfig_path)
                return modsconfig_path
            else:
                self.modsconfig_var.set("ModsConfig.xml not found")
                return None
                
        except Exception as e:
            self.modsconfig_var.set(f"Error finding ModsConfig.xml: {str(e)}")
            return None
    
    def extract_workshop_id(self, url):
        """Extract workshop ID from Steam URL"""
        # Clean up steam:// protocol URLs
        if url.startswith('steam://openurl/'):
            url = url.replace('steam://openurl/', '')
        
        # Extract ID from URL parameters
        try:
            parsed_url = urlparse(url)
            if 'id=' in parsed_url.query:
                query_params = parse_qs(parsed_url.query)
                return query_params.get('id', [None])[0]
            elif '/filedetails/?id=' in url:
                # Direct extraction for simple cases
                match = re.search(r'id=(\d+)', url)
                if match:
                    return match.group(1)
        except Exception as e:
            print(f"Error extracting ID from {url}: {e}")
        
        return None
    
    def load_progression_pack(self):
        """Load all progression pack collections and extract package names"""
        if not self.is_rimworld_valid:
            self.log_to_output("✗ Please provide a valid RimWorld installation path first!\n")
            return
            
        if not self.workshop_folder or not os.path.exists(self.workshop_folder):
            self.output_text.delete(1.0, tk.END)
            self.output_text.insert(tk.END, "Workshop folder not found!\nPlease enter the correct RimWorld path above.")
            return
        
        self.output_text.delete(1.0, tk.END)
        self.output_text.insert(tk.END, "Loading Progression Pack Complete...\n\n")
        
        threading.Thread(target=self._load_progression_pack_thread, daemon=True).start()
    
    def merge_with_current_mods(self):
        """Merge current active mods with Steam collection mods"""
        if not self.is_rimworld_valid:
            self.log_to_output("✗ Please provide a valid RimWorld installation path first!\n")
            return
            
        # Show warning dialog
        warning_message = ("⚠️ WARNING ⚠️\n\n"
                          "This will merge your current active mods with the Steam Workshop collections.\n"
                          "This may create a potentially unwanted experience with mod conflicts,\n"
                          "incompatibilities, or performance issues.\n\n"
                          "The merged mod list will be saved as 'ProgressionHomebrew'.\n\n"
                          "Do you want to continue?")
        
        result = messagebox.askyesno("Merge Warning", warning_message, icon='warning')
        
        if result:
            if not self.workshop_folder or not os.path.exists(self.workshop_folder):
                self.output_text.delete(1.0, tk.END)
                self.output_text.insert(tk.END, "Workshop folder not found!\nPlease enter the correct RimWorld path above.")
                return
            
            if not self.modsconfig_path or not os.path.exists(self.modsconfig_path):
                self.output_text.delete(1.0, tk.END)
                self.output_text.insert(tk.END, "ModsConfig.xml not found!\nPlease ensure RimWorld has been launched at least once.")
                return
            
            self.output_text.delete(1.0, tk.END)
            self.output_text.insert(tk.END, "Merging current mods with Progression Pack...\n\n")
            
            threading.Thread(target=self._merge_with_current_mods_thread, daemon=True).start()
    
    def _load_progression_pack_thread(self):
        """Thread function to load progression pack"""
        try:
            all_workshop_ids = []
            
            # Get collection URLs from entries
            collections = {
                "Core": self.url_entries['core'].get().strip(),
                "Content": self.url_entries['content'].get().strip(),
                "Cosmetics": self.url_entries['cosmetics'].get().strip()
            }
            
            # Fetch all workshop IDs from collections
            for collection_name, url in collections.items():
                if url:
                    self._safe_update_output(f"Fetching {collection_name} collection...\n")
                    workshop_id = self.extract_workshop_id(url)
                    if workshop_id:
                        workshop_ids = self.fetch_collection_items(workshop_id)
                        all_workshop_ids.extend(workshop_ids)
                        self._safe_update_output(f"Found {len(workshop_ids)} items in {collection_name}\n")
            
            # Remove duplicates
            unique_ids = list(set(all_workshop_ids))
            self._safe_update_output(f"\nTotal unique workshop items: {len(unique_ids)}\n")
            self._safe_update_output("Extracting package names from About.xml files...\n\n")
            
            # Extract package names from About.xml files and validate
            results = []
            failed_mods = []
            valid_mods = []
            
            for workshop_id in sorted(unique_ids):
                package_id, steam_app_id = self.get_package_info_from_about_xml(workshop_id)
                
                if not self.is_package_id_valid(package_id):
                    failed_mods.append((workshop_id, package_id))
                else:
                    # Use steamAppId from XML if available, otherwise use workshop_id
                    effective_steam_id = steam_app_id if steam_app_id else workshop_id
                    # Store as (effective_steam_id, package_id, original_workshop_id)
                    valid_mods.append((effective_steam_id, package_id, workshop_id))
            
            # Check if there are any failed mods
            if failed_mods:
                self._safe_update_output("=== PACKAGE ID VALIDATION FAILED ===\n\n")
                self._safe_update_output("❌ The following mods have issues and cannot be loaded:\n\n")
                
                for workshop_id, error in failed_mods:
                    self._safe_update_output(f"Workshop ID {workshop_id}: {error}\n")
                
                self._safe_update_output(f"\n📊 VALIDATION SUMMARY:\n")
                self._safe_update_output(f"✗ {len(failed_mods)} mods failed validation\n")
                self._safe_update_output(f"✓ {len(valid_mods)} mods passed validation\n\n")
                self._safe_update_output("⚠️  The mod pack cannot be loaded until all package ID issues are resolved.\n")
                self._safe_update_output("This usually means the mods are not properly downloaded or corrupted.\n")
                self._safe_update_output("Please check your Steam Workshop subscriptions and try redownloading the failed mods.\n\n")
                return
            
            # All mods validated successfully
            results = valid_mods
            
            # Display results
            self._safe_update_output("=== PROGRESSION PACK COMPLETE ===\n\n")
            self._safe_update_output(f"✅ All {len(results)} mods validated successfully!\n\n")
            self._safe_update_output(f"{'Steam ID':<15} | Package Name\n")
            self._safe_update_output("-" * 80 + "\n")
            
            for steam_id, package_name, _ in results:
                self._safe_update_output(f"{steam_id:<15} | {package_name}\n")
            
            self._safe_update_output(f"\nTotal mods processed: {len(results)}\n")
            
            # Create ProgressionVanilla.rml file
            self.create_rml_file(results)
            
        except Exception as e:
            self._safe_update_output(f"Error loading progression pack: {str(e)}\n")
    
    def _merge_with_current_mods_thread(self):
        """Thread function to merge current mods with progression pack"""
        try:
            # Step 1: Get current active mods from ModsConfig.xml
            self._safe_update_output("Reading current active mods from ModsConfig.xml...\n")
            current_mods = self.get_current_active_mods()
            self._safe_update_output(f"Found {len(current_mods)} currently active mods\n\n")
            
            # Step 2: Get Steam Workshop collection mods
            all_workshop_ids = []
            
            # Get collection URLs from entries
            collections = {
                "Core": self.url_entries['core'].get().strip(),
                "Content": self.url_entries['content'].get().strip(),
                "Cosmetics": self.url_entries['cosmetics'].get().strip()
            }
            
            # Fetch all workshop IDs from collections
            for collection_name, url in collections.items():
                if url:
                    self._safe_update_output(f"Fetching {collection_name} collection...\n")
                    workshop_id = self.extract_workshop_id(url)
                    if workshop_id:
                        workshop_ids = self.fetch_collection_items(workshop_id)
                        all_workshop_ids.extend(workshop_ids)
                        self._safe_update_output(f"Found {len(workshop_ids)} items in {collection_name}\n")
            
            # Remove duplicates
            unique_workshop_ids = list(set(all_workshop_ids))
            self._safe_update_output(f"\nTotal unique workshop items: {len(unique_workshop_ids)}\n")
            self._safe_update_output("Extracting package names from About.xml files...\n\n")
            
            # Extract package names from About.xml files for workshop mods and validate
            workshop_results = []
            failed_mods = []
            valid_mods = []
            
            for workshop_id in sorted(unique_workshop_ids):
                package_id, steam_app_id = self.get_package_info_from_about_xml(workshop_id)
                
                if not self.is_package_id_valid(package_id):
                    failed_mods.append((workshop_id, package_id))
                else:
                    # Use steamAppId from XML if available, otherwise use workshop_id
                    effective_steam_id = steam_app_id if steam_app_id else workshop_id
                    # Store as (effective_steam_id, package_id, original_workshop_id)
                    valid_mods.append((effective_steam_id, package_id, workshop_id))
            
            # Check if there are any failed mods
            if failed_mods:
                self._safe_update_output("=== PACKAGE ID VALIDATION FAILED ===\n\n")
                self._safe_update_output("❌ The following mods have issues and cannot be merged:\n\n")
                
                for workshop_id, error in failed_mods:
                    self._safe_update_output(f"Workshop ID {workshop_id}: {error}\n")
                
                self._safe_update_output(f"\n📊 VALIDATION SUMMARY:\n")
                self._safe_update_output(f"✗ {len(failed_mods)} mods failed validation\n")
                self._safe_update_output(f"✓ {len(valid_mods)} mods passed validation\n\n")
                self._safe_update_output("⚠️  The mod merge cannot be completed until all package ID issues are resolved.\n")
                self._safe_update_output("This usually means the mods are not properly downloaded or corrupted.\n")
                self._safe_update_output("Please check your Steam Workshop subscriptions and try redownloading the failed mods.\n\n")
                return
            
            # All mods validated successfully
            workshop_results = valid_mods
            
            # Step 3: Remove duplicates and merge the mod lists
            self._safe_update_output("=== MERGING CURRENT MODS WITH PROGRESSION PACK ===\n\n")
            
            # Display current mods
            self._safe_update_output("Current Active Mods:\n")
            self._safe_update_output("-" * 50 + "\n")
            for package_id, mod_name in current_mods:
                self._safe_update_output(f"{package_id} | {mod_name}\n")
            
            self._safe_update_output(f"\nWorkshop Mods from Collections:\n")
            self._safe_update_output("-" * 50 + "\n")
            for steam_id, package_name, _ in workshop_results:
                self._safe_update_output(f"{steam_id:<15} | {package_name}\n")
            
            # Step 4: Remove duplicates based on package ID and workshop ID
            current_package_ids = {package_id for package_id, _ in current_mods}
            current_workshop_ids = {package_id for package_id, _ in current_mods if package_id.isdigit()}
            
            filtered_workshop_results = []
            duplicates_found = []
            
            for steam_id, package_id, original_workshop_id in workshop_results:
                # Check for duplicate package ID
                if package_id in current_package_ids:
                    duplicates_found.append((steam_id, package_id, "Package ID already exists"))
                # Check for duplicate workshop ID (user might have same mod in current list)
                elif steam_id in current_workshop_ids:
                    duplicates_found.append((steam_id, package_id, "Steam ID already exists"))
                else:
                    filtered_workshop_results.append((steam_id, package_id, original_workshop_id))
            
            # Report duplicates
            if duplicates_found:
                self._safe_update_output(f"\nDuplicates Removed:\n")
                self._safe_update_output("-" * 70 + "\n")
                for workshop_id, package_id, reason in duplicates_found:
                    self._safe_update_output(f"{workshop_id:<15} | {package_id:<30} | {reason}\n")
            else:
                self._safe_update_output(f"\nNo duplicates found - all workshop mods are unique!\n")
            
            # Step 5: Create final merged mod list
            merged_results = current_mods + filtered_workshop_results
            self._safe_update_output(f"\nFinal Results:\n")
            self._safe_update_output(f"Current mods: {len(current_mods)}\n")
            self._safe_update_output(f"New workshop mods: {len(filtered_workshop_results)}\n")
            self._safe_update_output(f"Duplicates removed: {len(duplicates_found)}\n")
            self._safe_update_output(f"Total merged mods: {len(merged_results)}\n")
            
            # Create ProgressionHomebrew.rml file
            self.create_homebrew_rml_file(current_mods, filtered_workshop_results)
            
        except Exception as e:
            self._safe_update_output(f"Error merging mods: {str(e)}\n")
    
    def get_current_active_mods(self):
        """Get currently active mods from ModsConfig.xml"""
        current_mods = []
        
        try:
            if not self.modsconfig_path or not os.path.exists(self.modsconfig_path):
                self._safe_update_output("ModsConfig.xml not found\n")
                return current_mods
            
            # Parse ModsConfig.xml
            tree = ET.parse(self.modsconfig_path)
            root = tree.getroot()
            
            # Get active mods from activeMods section
            active_mods_element = root.find('activeMods')
            if active_mods_element is not None:
                for li in active_mods_element.findall('li'):
                    mod_id = li.text
                    if mod_id:
                        mod_id = mod_id.strip()
                        # Get mod name - try to find it from various sources
                        mod_name = self.get_mod_display_name(mod_id)
                        current_mods.append((mod_id, mod_name))
            
            return current_mods
            
        except Exception as e:
            self._safe_update_output(f"Error reading current mods: {str(e)}\n")
            return current_mods
    
    def get_mod_display_name(self, mod_id):
        """Get display name for a mod ID from various sources"""
        # First check if it's a core mod
        if mod_id.startswith('ludeon.rimworld'):
            return self.get_default_mod_name(mod_id)
        
        # For workshop mods, try to find the About.xml in workshop folder
        if self.workshop_folder and os.path.exists(self.workshop_folder):
            # Check if mod_id is a workshop ID (numeric)
            if mod_id.isdigit():
                mod_name = self.get_mod_name_from_about_xml(mod_id)
                if mod_name != f"Workshop ID {mod_id}":
                    return mod_name
        
        # Fallback: return the mod ID itself
        return mod_id
    
    def create_homebrew_rml_file(self, current_mods, workshop_results):
        """Create ProgressionHomebrew.rml file with merged mods"""
        try:
            # Get ModLists directory path
            modlists_path = os.path.expandvars(r"%USERPROFILE%\AppData\LocalLow\Ludeon Studios\RimWorld by Ludeon Studios\ModLists")
            
            # Create directory if it doesn't exist
            os.makedirs(modlists_path, exist_ok=True)
            
            rml_file_path = os.path.join(modlists_path, "ProgressionHomebrew.rml")
            
            # Create XML content for merged mods
            xml_content = self.generate_homebrew_rml_xml(current_mods, workshop_results)
            
            # Write to file
            with open(rml_file_path, 'w', encoding='utf-8') as f:
                f.write(xml_content)
            
            self._safe_update_output(f"\nProgressionHomebrew.rml created successfully at:\n{rml_file_path}\n")
            
            # Show success animation
            self.show_success_animation("ProgressionHomebrew")
            
        except Exception as e:
            self._safe_update_output(f"Error creating Homebrew RML file: {str(e)}\n")
    
    def escape_xml_text(self, text):
        """Escape special XML characters in text content"""
        if not text:
            return text
        # Use html.escape to handle XML/HTML entities properly
        # xml_char_ref=False ensures we get named entities like &amp; instead of numeric ones
        return html.escape(text, quote=False)
    
    def generate_homebrew_rml_xml(self, current_mods, workshop_results):
        """Generate the XML content for the homebrew .rml file"""
        xml_lines = [
            '<?xml version="1.0" encoding="utf-8"?>',
            '<savedModList>',
            '\t<meta>',
            '\t\t<gameVersion>1.6.4633 rev1261</gameVersion>',
            '\t\t<modIds>'
        ]
        
        # Add current mods first (maintain load order)
        for package_id, mod_name in current_mods:
            xml_lines.append(f'\t\t\t<li>{package_id}</li>')
        
        # Add workshop mod package IDs to meta section
        for steam_id, package_id, original_workshop_id in workshop_results:
            xml_lines.append(f'\t\t\t<li>{package_id}</li>')
        
        xml_lines.extend([
            '\t\t</modIds>',
            '\t\t<modSteamIds>'
        ])
        
        # Add Steam IDs (0 for non-workshop mods, actual ID for workshop mods)
        for package_id, mod_name in current_mods:
            if package_id.isdigit():
                xml_lines.append(f'\t\t\t<li>{package_id}</li>')
            else:
                xml_lines.append(f'\t\t\t<li>0</li>')
        
        # Add workshop IDs to meta section
        for steam_id, package_id, original_workshop_id in workshop_results:
            xml_lines.append(f'\t\t\t<li>{steam_id}</li>')
        
        xml_lines.extend([
            '\t\t</modSteamIds>',
            '\t\t<modNames>'
        ])
        
        # Add current mod names first
        for package_id, mod_name in current_mods:
            escaped_name = self.escape_xml_text(mod_name)
            xml_lines.append(f'\t\t\t<li>{escaped_name}</li>')
        
        # Add workshop mod names to meta section
        for steam_id, package_id, original_workshop_id in workshop_results:
            mod_name = self.get_mod_name_from_about_xml(original_workshop_id)
            escaped_name = self.escape_xml_text(mod_name)
            xml_lines.append(f'\t\t\t<li>{escaped_name}</li>')
        
        xml_lines.extend([
            '\t\t</modNames>',
            '\t</meta>',
            '\t<modList>',
            '\t\t<ids>'
        ])
        
        # Add current mods first to modList section
        for package_id, mod_name in current_mods:
            xml_lines.append(f'\t\t\t<li>{package_id}</li>')
        
        # Add workshop mod package IDs to modList section
        for steam_id, package_id, original_workshop_id in workshop_results:
            xml_lines.append(f'\t\t\t<li>{package_id}</li>')
        
        xml_lines.extend([
            '\t\t</ids>',
            '\t\t<names>'
        ])
        
        # Add current mod names first to modList section
        for package_id, mod_name in current_mods:
            escaped_name = self.escape_xml_text(mod_name)
            xml_lines.append(f'\t\t\t<li>{escaped_name}</li>')
        
        # Add workshop mod names to modList section
        for steam_id, package_id, original_workshop_id in workshop_results:
            mod_name = self.get_mod_name_from_about_xml(original_workshop_id)
            escaped_name = self.escape_xml_text(mod_name)
            xml_lines.append(f'\t\t\t<li>{escaped_name}</li>')
        
        xml_lines.extend([
            '\t\t</names>',
            '\t</modList>',
            '</savedModList>'
        ])
        
        return '\n'.join(xml_lines)
    
    def create_rml_file(self, mod_data):
        """Create ProgressionVanilla.rml file with workshop IDs and names"""
        try:
            # Get ModLists directory path
            modlists_path = os.path.expandvars(r"%USERPROFILE%\AppData\LocalLow\Ludeon Studios\RimWorld by Ludeon Studios\ModLists")
            
            # Create directory if it doesn't exist
            os.makedirs(modlists_path, exist_ok=True)
            
            rml_file_path = os.path.join(modlists_path, "ProgressionVanilla.rml")
            
            # Create XML content
            xml_content = self.generate_rml_xml(mod_data)
            
            # Write to file
            with open(rml_file_path, 'w', encoding='utf-8') as f:
                f.write(xml_content)
            
            self._safe_update_output(f"\nProgressionVanilla.rml created successfully at:\n{rml_file_path}\n")
            
            # Show success animation
            self.show_success_animation("ProgressionVanilla")
            
        except Exception as e:
            self._safe_update_output(f"Error creating RML file: {str(e)}\n")
    
    def generate_rml_xml(self, mod_data):
        """Generate the XML content for the .rml file"""
        # Get core mods from ModsConfig.xml and RimWorld Data folder
        msg = "=== GENERATING RML XML ===\n"
        self._safe_update_output(msg)
        print(msg.strip())
        core_mods = self.get_core_mods_from_config()
        msg = f"Core mods returned: {core_mods}\n"
        self._safe_update_output(msg)
        print(msg.strip())
        
        # Extract mod names from About.xml files for workshop mods
        mod_names = []
        for steam_id, package_id, original_workshop_id in mod_data:
            mod_name = self.get_mod_name_from_about_xml(original_workshop_id)
            mod_names.append(mod_name)
        
        xml_lines = [
            '<?xml version="1.0" encoding="utf-8"?>',
            '<savedModList>',
            '\t<meta>',
            '\t\t<gameVersion>1.6.4633 rev1261</gameVersion>',
            '\t\t<modIds>'
        ]
        
        # Add core mods first
        msg = "Adding core mods to XML:\n"
        self._safe_update_output(msg)
        print(msg.strip())
        for package_id, mod_name in core_mods:
            xml_lines.append(f'\t\t\t<li>{package_id}</li>')
            msg = f"  Added core mod: {package_id} ({mod_name})\n"
            self._safe_update_output(msg)
            print(msg.strip())
        
        # Add workshop mod package IDs to meta section
        for steam_id, package_id, original_workshop_id in mod_data:
            xml_lines.append(f'\t\t\t<li>{package_id}</li>')
        
        xml_lines.extend([
            '\t\t</modIds>',
            '\t\t<modSteamIds>'
        ])
        
        # Add core mods (they don't have Steam IDs, so use 0)
        for package_id, mod_name in core_mods:
            xml_lines.append(f'\t\t\t<li>0</li>')
        
        # Add workshop IDs to meta section
        for steam_id, package_id, original_workshop_id in mod_data:
            xml_lines.append(f'\t\t\t<li>{steam_id}</li>')
        
        xml_lines.extend([
            '\t\t</modSteamIds>',
            '\t\t<modNames>'
        ])
        
        # Add core mod names first
        for package_id, mod_name in core_mods:
            escaped_name = self.escape_xml_text(mod_name)
            xml_lines.append(f'\t\t\t<li>{escaped_name}</li>')
        
        # Add workshop mod names to meta section
        for mod_name in mod_names:
            escaped_name = self.escape_xml_text(mod_name)
            xml_lines.append(f'\t\t\t<li>{escaped_name}</li>')
        
        xml_lines.extend([
            '\t\t</modNames>',
            '\t</meta>',
            '\t<modList>',
            '\t\t<ids>'
        ])
        
        # Add core mods first to modList section
        for package_id, mod_name in core_mods:
            xml_lines.append(f'\t\t\t<li>{package_id}</li>')
        
        # Add workshop mod package IDs to modList section
        for steam_id, package_id, original_workshop_id in mod_data:
            xml_lines.append(f'\t\t\t<li>{package_id}</li>')
        
        xml_lines.extend([
            '\t\t</ids>',
            '\t\t<names>'
        ])
        
        # Add core mod names first to modList section
        for package_id, mod_name in core_mods:
            escaped_name = self.escape_xml_text(mod_name)
            xml_lines.append(f'\t\t\t<li>{escaped_name}</li>')
        
        # Add workshop mod names to modList section
        for mod_name in mod_names:
            escaped_name = self.escape_xml_text(mod_name)
            xml_lines.append(f'\t\t\t<li>{escaped_name}</li>')
        
        xml_lines.extend([
            '\t\t</names>',
            '\t</modList>',
            '</savedModList>'
        ])
        
        return '\n'.join(xml_lines)
    
    def get_core_mods_from_config(self):
        """Get core mods from ModsConfig.xml knownExpansions section - includes ALL DLC the user owns"""
        core_mods = []
        seen_core_mod_ids = set()
        base_game_id = 'ludeon.rimworld'
        
        try:
            if not self.modsconfig_path or not os.path.exists(self.modsconfig_path):
                msg = "ModsConfig.xml not found, using default core mods\n"
                self._safe_update_output(msg)
                print(msg.strip())
                return [(base_game_id, self.get_default_mod_name(base_game_id))]
            
            msg = f"Reading ModsConfig.xml from: {self.modsconfig_path}\n"
            self._safe_update_output(msg)
            print(msg.strip())
            
            # Parse ModsConfig.xml
            tree = ET.parse(self.modsconfig_path)
            root = tree.getroot()
            
            # Get RimWorld Data folder path from the RimWorld game path
            rimworld_data_path = None
            if self.rimworld_var.get().strip():
                rimworld_game_path = self.rimworld_var.get().strip()
                rimworld_data_path = os.path.join(rimworld_game_path, "Data")
                msg = f"RimWorld Data path: {rimworld_data_path}\n"
                self._safe_update_output(msg)
                print(msg.strip())
            else:
                msg = "No RimWorld path set, using fallback names\n"
                self._safe_update_output(msg)
                print(msg.strip())

            def add_core_mod(mod_id):
                mod_id = (mod_id or "").strip()
                if not mod_id or mod_id in seen_core_mod_ids:
                    return

                mod_name = self.get_expansion_real_name(mod_id, rimworld_data_path)
                msg = f"get_expansion_real_name returned: {mod_name}\n"
                self._safe_update_output(msg)
                print(msg.strip())
                if not mod_name:
                    mod_name = self.get_default_mod_name(mod_id)
                    msg = f"Using fallback name: {mod_name}\n"
                    self._safe_update_output(msg)
                    print(msg.strip())

                core_mods.append((mod_id, mod_name))
                seen_core_mod_ids.add(mod_id)
                msg = f"Added to core_mods: {mod_id} -> {mod_name}\n"
                self._safe_update_output(msg)
                print(msg.strip())

            msg = "Seeding base game Core entry...\n"
            self._safe_update_output(msg)
            print(msg.strip())
            add_core_mod(base_game_id)
            
            # Find knownExpansions section and include all owned expansions after Core
            known_expansions_element = root.find('knownExpansions')
            if known_expansions_element is not None:
                msg = "Found knownExpansions section, processing entries...\n"
                self._safe_update_output(msg)
                print(msg.strip())
                for li in known_expansions_element.findall('li'):
                    expansion_id = li.text
                    if expansion_id:
                        expansion_id = expansion_id.strip()
                        msg = f"Processing known expansion: {expansion_id}\n"
                        self._safe_update_output(msg)
                        print(msg.strip())
                        add_core_mod(expansion_id)
            else:
                msg = "No knownExpansions section found in ModsConfig.xml\n"
                self._safe_update_output(msg)
                print(msg.strip())
            
            if not core_mods:
                # Fallback if no expansions found - just base game
                core_mods = [(base_game_id, self.get_default_mod_name(base_game_id))]
                msg = "No expansions found, using fallback base game only\n"
                self._safe_update_output(msg)
                print(msg.strip())
                
            msg = f"Final core_mods list: {core_mods}\n"
            self._safe_update_output(msg)
            print(msg.strip())
            msg = f"Found {len(core_mods)} core expansions from knownExpansions in ModsConfig.xml\n"
            self._safe_update_output(msg)
            print(msg.strip())
            
        except Exception as e:
            msg = f"Error reading core mods: {str(e)}\n"
            self._safe_update_output(msg)
            print(msg.strip())
            core_mods = [(base_game_id, self.get_default_mod_name(base_game_id))]
        
        return core_mods
    
    def get_expansion_real_name(self, expansion_id, rimworld_data_path):
        """Get the real expansion name from RimWorld Data folder About.xml"""
        try:
            if not rimworld_data_path or not os.path.exists(rimworld_data_path):
                # Fallback to extracting from ID if no data path
                return self.extract_name_from_id(expansion_id)
            
            # Look for expansion folder in Data directory
            # Core expansions are usually in subfolders like Royalty, Ideology, etc.
            possible_folders = [
                expansion_id.replace('ludeon.rimworld.', '').capitalize(),
                expansion_id.split('.')[-1].capitalize() if '.' in expansion_id else expansion_id,
                expansion_id.replace('ludeon.rimworld.', ''),
                expansion_id.replace('ludeon.rimworld', 'Core')
            ]
            
            for folder_name in possible_folders:
                about_xml_path = os.path.join(rimworld_data_path, folder_name, "About", "About.xml")
                if os.path.exists(about_xml_path):
                    try:
                        tree = ET.parse(about_xml_path)
                        root = tree.getroot()
                        
                        name_element = root.find('name')
                        if name_element is not None and name_element.text:
                            return name_element.text.strip()
                    except ET.ParseError:
                        continue
            
            # If not found in Data folder, use fallback
            return self.extract_name_from_id(expansion_id)
            
        except Exception as e:
            return self.extract_name_from_id(expansion_id)
    
    def extract_name_from_id(self, expansion_id):
        """Extract expansion name from ID as fallback"""
        normalized_id = expansion_id.lower()

        if normalized_id == 'ludeon.rimworld':
            return 'Core'
        elif 'royalty' in normalized_id:
            return 'Royalty'
        elif 'ideology' in normalized_id:
            return 'Ideology'
        elif 'biotech' in normalized_id:
            return 'Biotech'
        elif 'anomaly' in normalized_id:
            return 'Anomaly'
        elif 'odyssey' in normalized_id:
            return 'Odyssey'
        else:
            # Extract the last part after the last dot and capitalize
            parts = expansion_id.split('.')
            if len(parts) > 1:
                return parts[-1].capitalize()
            return expansion_id
    
    def get_default_mod_name(self, mod_id):
        """Get default display name for core mods"""
        default_names = {
            'ludeon.rimworld': 'Core',
            'ludeon.rimworld.royalty': 'Royalty',
            'ludeon.rimworld.ideology': 'Ideology',
            'ludeon.rimworld.biotech': 'Biotech',
            'ludeon.rimworld.anomaly': 'Anomaly',
            'ludeon.rimworld.odyssey': 'Odyssey'
        }
        
        return default_names.get(mod_id, mod_id)
    
    def get_mod_name_from_about_xml(self, workshop_id):
        """Extract mod name from About/About.xml file"""
        try:
            about_xml_path = os.path.join(self.workshop_folder, workshop_id, "About", "About.xml")
            
            if not os.path.exists(about_xml_path):
                return f"Workshop ID {workshop_id}"
            
            # Parse the XML file
            tree = ET.parse(about_xml_path)
            root = tree.getroot()
            
            # Look for name element
            name_element = root.find('name')
            if name_element is not None and name_element.text:
                return name_element.text.strip()
            else:
                return f"Workshop ID {workshop_id}"
                
        except ET.ParseError:
            return f"Workshop ID {workshop_id}"
        except Exception as e:
            return f"Workshop ID {workshop_id}"
    
    def is_package_id_valid(self, package_id):
        """Check if a package ID is valid (not an error condition)"""
        error_conditions = [
            "About.xml not found", 
            "packageId not found", 
            "XML parse error", 
            "Workshop path not found"
        ]
        return package_id not in error_conditions and not package_id.startswith("Error:")

    def get_package_info_from_about_xml(self, workshop_id):
        """Extract package name and steam app ID from About/About.xml file"""
        try:
            about_xml_path = os.path.join(self.workshop_folder, workshop_id, "About", "About.xml")
            
            if not os.path.exists(about_xml_path):
                return "About.xml not found", None
            
            # Parse the XML file
            tree = ET.parse(about_xml_path)
            root = tree.getroot()
            
            # Look for packageId element
            package_id_element = root.find('packageId')
            if package_id_element is None or not package_id_element.text:
                return "packageId not found", None
            
            # Look for steamAppId element
            steam_app_id_element = root.find('steamAppId')
            steam_app_id = None
            if steam_app_id_element is not None and steam_app_id_element.text:
                steam_app_id = steam_app_id_element.text.strip()
            
            # Return package ID in lowercase for consistency
            package_id = package_id_element.text.strip().lower()
            return package_id, steam_app_id
                
        except ET.ParseError:
            return "XML parse error", None
        except Exception as e:
            return f"Error: {str(e)}", None

    def get_package_name_from_about_xml(self, workshop_id):
        """Extract package name from About/About.xml file (backward compatibility)"""
        package_id, _ = self.get_package_info_from_about_xml(workshop_id)
        return package_id
    
    def _safe_update_output(self, text):
        """Safely update output text without recursion"""
        try:
            self.output_text.insert(tk.END, text)
            self.output_text.see(tk.END)
        except Exception:
            pass  # Ignore update errors to prevent recursion
        
    def fetch_collection_items(self, collection_id):
        """Fetch workshop IDs from a Steam Workshop collection"""
        try:
            # Steam Workshop collection URL
            url = f"https://steamcommunity.com/sharedfiles/filedetails/?id={collection_id}"
            
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
            }
            
            self._safe_update_output(f"Fetching collection page...\n")
            
            response = requests.get(url, headers=headers, timeout=15)
            response.raise_for_status()
            
            # Extract workshop IDs from collection items only
            html_content = response.text
            workshop_ids = []
            
            # Find the collectionChildren section
            collection_start = html_content.find('<div class="collectionChildren">')
            if collection_start != -1:
                # Find the proper end of the collectionChildren section
                # Look for the closing </div> that matches the opening collectionChildren div
                # We need to count div tags to find the matching closing tag
                
                search_pos = collection_start + len('<div class="collectionChildren">')
                div_count = 1  # We've opened one div
                collection_end = -1
                
                while search_pos < len(html_content) and div_count > 0:
                    next_open = html_content.find('<div', search_pos)
                    next_close = html_content.find('</div>', search_pos)
                    
                    if next_close == -1:
                        break
                    
                    if next_open != -1 and next_open < next_close:
                        div_count += 1
                        search_pos = next_open + 4
                    else:
                        div_count -= 1
                        if div_count == 0:
                            collection_end = next_close + 6
                            break
                        search_pos = next_close + 6
                
                if collection_end == -1:
                    collection_end = len(html_content)
                
                collection_section = html_content[collection_start:collection_end]
                
                # Extract IDs from sharedfile_ elements (these are the actual collection items)
                sharedfile_ids = re.findall(r'id="sharedfile_(\d+)"', collection_section)
                workshop_ids.extend(sharedfile_ids)
                
                self._safe_update_output(f"Found {len(sharedfile_ids)} collection items\n")
            else:
                # Fallback: search the entire page but be more selective
                self._safe_update_output("Collection section not found, using fallback method\n")
                sharedfile_ids = re.findall(r'id="sharedfile_(\d+)"', html_content)
                workshop_ids.extend(sharedfile_ids)
            
            # Remove duplicates and the collection ID itself
            unique_ids = list(set(workshop_ids))
            if collection_id in unique_ids:
                unique_ids.remove(collection_id)
                
            return sorted(unique_ids)  # Sort for consistent output
            
        except requests.RequestException as e:
            self._safe_update_output(f"Network error: {str(e)}\n")
            return []
        except Exception as e:
            self._safe_update_output(f"Error fetching collection: {str(e)}\n")
            return []
    
    def _create_success_text_background(self, x, y, width, height, opacity=0.75, corner_radius=20):
        """Create a semi-transparent rounded rectangle background for success text
        Width and height are automatically increased by 10%"""
        print(f"Creating success background at {x}, {y} with size {width}x{height}")
        
        # Increase size by 10%
        width = int(width * 1.1)
        height = int(height * 1.1)
        
        alpha = max(0, min(255, int(255 * opacity)))
        bg_img = create_striped_panel(
            width,
            height,
            panel_color=COLORS['bg_card'],
            stripe_color=COLORS['stripe_soft'],
            stripe_width=18,
            stripe_gap=18,
            stripe_alpha=max(50, int(alpha * 0.3)),
            halo_padding=24,
            halo_alpha=max(90, int(alpha * 0.55)),
            panel_alpha=max(220, alpha),
            corner_radius=corner_radius,
            border_color=COLORS['border_light'],
            border_width=1,
            border_alpha=180,
        )
        
        # Convert to PhotoImage
        bg_tk = ImageTk.PhotoImage(bg_img)
        
        # Store reference to prevent garbage collection
        if not hasattr(self, '_success_bg_images'):
            self._success_bg_images = []
        self._success_bg_images.append(bg_tk)
        
        # Create canvas image
        img_id = self.success_bg_canvas.create_image(x, y, image=bg_tk, anchor='center', tags='success_text')
        
        # Make sure it's behind text but above background
        self.success_bg_canvas.tag_lower(img_id, 'success_text')
        
        print(f"Created success background with ID: {img_id}")
        return img_id
    
    def show_success_animation(self, mod_list_name):
        """Show success animation with logo returning to center and instructions"""
        # Close the main creator window
        self.root.withdraw()  # Hide the main window
        
        # Create overlay window for success animation
        self.success_window = tk.Toplevel()
        self.success_window.title("Progression: Loader - Success!")
        self.success_window.configure(bg=COLORS['bg_primary'])
        
        # Set window icon
        try:
            icon_path = get_resource_path(os.path.join("art", "Progression.ico"))
            if os.path.exists(icon_path):
                self.success_window.iconbitmap(icon_path)
        except Exception:
            pass  # Ignore icon loading errors for popup windows
            
        self.success_window.attributes('-topmost', True)
        self.success_window.state('zoomed')  # Fullscreen
        self.success_window.protocol("WM_DELETE_WINDOW", self.close_success_animation)
        
        # Create canvas for background image and all content
        self.success_bg_canvas = tk.Canvas(self.success_window, highlightthickness=0)
        self.success_bg_canvas.pack(fill=tk.BOTH, expand=True)
        
        # Load background image
        self.success_window.update_idletasks()
        width = self.success_window.winfo_width() or 1920
        height = self.success_window.winfo_height() or 1080
        self.success_bg_image_tk = self.load_background_image(width, height)
        if self.success_bg_image_tk:
            self.success_bg_canvas.create_image(0, 0, image=self.success_bg_image_tk, anchor='nw', tags='bg')
        else:
            self.success_bg_canvas.configure(bg=COLORS['bg_primary'])
        
        # Bind resize event
        self.success_window.bind('<Configure>', self._on_success_resize)
        
        # Store mod list name for later use
        self.success_mod_list_name = mod_list_name
        
        # Load and prepare logo for animation
        try:
            # Load the progression logo (GameTitle.png)
            success_logo_img = Image.open(get_resource_path("art/GameTitle.png"))
            # Start with smaller size (current header size)
            start_height = 80
            start_width = int((start_height / success_logo_img.size[1]) * success_logo_img.size[0])
            
            # Target size (larger for center display)
            target_height = 200
            target_width = int((target_height / success_logo_img.size[1]) * success_logo_img.size[0])
            
            self.success_logo_img = success_logo_img
            
            # Start animation from top to center using canvas image
            self.animate_success_logo(0, 30, start_width, start_height, target_width, target_height, mod_list_name)
            
        except Exception as e:
            # Fallback if image loading fails
            self.show_success_text_only(mod_list_name)
    
    def _on_success_resize(self, event):
        """Handle success window resize to update background image"""
        if event.widget == self.success_window and hasattr(self, 'success_bg_canvas'):
            width = event.width
            height = event.height
            if width > 1 and height > 1:
                self.success_bg_image_tk = self.load_background_image(width, height)
                if self.success_bg_image_tk:
                    self.success_bg_canvas.delete('bg')
                    self.success_bg_canvas.create_image(0, 0, image=self.success_bg_image_tk, anchor='nw', tags='bg')
                    self.success_bg_canvas.tag_lower('bg')
    
    def animate_success_logo(self, step, total_steps, start_width, start_height, 
                           target_width, target_height, mod_list_name):
        """Animate the logo moving from top to center with size increase"""
        if step <= total_steps:
            # Calculate progress with easing
            progress = step / total_steps
            eased_progress = 1 - (1 - progress) ** 3  # Ease-out
            
            # Get window dimensions
            window_width = self.success_window.winfo_width()
            window_height = self.success_window.winfo_height()
            
            if window_width <= 1 or window_height <= 1:
                # Window not ready, try again
                self.success_window.after(50, lambda: self.animate_success_logo(
                    step, total_steps, start_width, start_height, 
                    target_width, target_height, mod_list_name))
                return
            
            # Calculate positions - move logo up by 3%
            start_x = window_width // 2
            start_y = 60  # Top position (header position)
            target_x = window_width // 2
            target_y = window_height // 2 - 150 - int(window_height * 0.03)  # Higher up by 3% + room for text
            
            current_x = start_x + (target_x - start_x) * eased_progress
            current_y = start_y + (target_y - start_y) * eased_progress
            
            # Calculate current size
            current_width = start_width + (target_width - start_width) * eased_progress
            current_height = start_height + (target_height - start_height) * eased_progress
            
            # Resize and display logo using canvas image
            resized_img = self.success_logo_img.resize(
                (int(current_width), int(current_height)), 
                Image.Resampling.LANCZOS
            )
            self.success_logo_tk = ImageTk.PhotoImage(resized_img)
            
            # Delete old logo and create new one
            self.success_bg_canvas.delete('logo')
            self.success_bg_canvas.create_image(current_x, current_y, image=self.success_logo_tk, anchor='center', tags='logo')
            
            # Schedule next step
            self.success_window.after(50, lambda: self.animate_success_logo(
                step + 1, total_steps, start_width, start_height,
                target_width, target_height, mod_list_name
            ))
        else:
            # Animation complete - show success message
            self.show_success_message(mod_list_name)
    
    def show_success_text_only(self, mod_list_name):
        """Show success message without logo animation (fallback)"""
        # Ensure window is updated and get proper dimensions
        self.success_window.update_idletasks()
        window_width = self.success_window.winfo_width()
        window_height = self.success_window.winfo_height()
        
        # Fallback to default dimensions if window size not available
        if window_width <= 1 or window_height <= 1:
            window_width = 1920
            window_height = 1080
        
        # Create centered text using canvas - moved up by 3%
        self.success_bg_canvas.create_text(
            window_width // 2, window_height // 2 - 150 - int(window_height * 0.03),
            text="Success!",
            fill=COLORS['text_highlight'],
            font=self.get_font(24, 'bold', 'italic'),
            tags='success_text'
        )
        
        self.show_success_message(mod_list_name, create_background=False)
    
    def show_success_message(self, mod_list_name, create_background=True):
        """Show the success message and instructions using canvas text"""
        # Ensure window is updated and get proper dimensions
        self.success_window.update_idletasks()
        window_width = self.success_window.winfo_width()
        window_height = self.success_window.winfo_height()
        
        # Fallback to default dimensions if window size not available
        if window_width <= 1 or window_height <= 1:
            window_width = 1920
            window_height = 1080
        
        center_x = window_width // 2
        
        # Create semi-transparent background panel for text content IMMEDIATELY
        if create_background:
            panel_width = int(window_width * 0.6)
            panel_height = int(window_height * 0.45)
            # Force window update before creating background
            self.success_window.update()
            self._create_success_text_background(center_x, int(window_height * 0.55), panel_width, panel_height)
            # Force another update to ensure background is drawn
            self.success_window.update()
            
            # Ensure logo stays on top of the background
            self.success_bg_canvas.tag_raise('logo')
        
        # Starting Y position (below the logo)
        y_pos = window_height // 2 - 50
        
        # Success message
        self.success_bg_canvas.create_text(
            center_x, y_pos,
            text="Mods list created successfully!",
            fill=COLORS['text_highlight'],
            font=self.get_font(20, 'bold', 'italic'),
            tags='success_text'
        )
        y_pos += 50
        
        # Mod list name
        self.success_bg_canvas.create_text(
            center_x, y_pos,
            text=f"It is named: {mod_list_name.lower()}",
            fill=COLORS['text_primary'],
            font=self.get_font(16),
            tags='success_text'
        )
        y_pos += 50
        
        # Instructions (different for homebrew vs vanilla)
        if mod_list_name == "ProgressionHomebrew":
            instructions_text = f"""To load it:
1. Open your mod manager in game
2. Go to load section
3. Press load next to "{mod_list_name.lower()}"

⚠️ Important for homebrew version ⚠️
This contains your current mods + progression pack
You must auto sort after loading to prevent conflicts!
Some mods may be incompatible - check for errors!"""
        else:
            instructions_text = f"""To load it:
1. Open your mod manager in game
2. Go to load section
3. Press load next to "{mod_list_name.lower()}"

You must auto sort after this!"""
        
        self.success_bg_canvas.create_text(
            center_x, y_pos + 80,
            text=instructions_text,
            fill=COLORS['text_body'],
            font=self.get_font(14),
            justify='center',
            tags='success_text'
        )
        y_pos += 200
        
        # Auto sort warning (more prominent for homebrew)
        if mod_list_name == "ProgressionHomebrew":
            warning_color = COLORS['accent_red']
            warning_msg = "⚠️ Critical: auto sort after loading! Check for mod conflicts! ⚠️"
        else:
            warning_color = COLORS['accent_red']
            warning_msg = "⚠️ Important: auto sort after loading! ⚠️"
        
        self.success_bg_canvas.create_text(
            center_x, y_pos,
            text=warning_msg,
            fill=warning_color,
            font=self.get_font(16, 'bold', 'italic'),
            tags='success_text'
        )
        y_pos += 60
        
        self._create_success_canvas_button(center_x, y_pos + 30, "got it!", 
                                          self.close_success_animation, COLORS['accent_green'], width=150, height=50)
        
        # Ensure logo stays on top of all elements
        self._bring_success_logo_to_front()
        
        # No auto-close - user must manually close
    
    def _bring_success_logo_to_front(self):
        """Ensure the success logo stays on top of all other elements"""
        # Bring logo to the very front
        self.success_bg_canvas.tag_raise('logo')
    
    def _create_success_canvas_button(self, x, y, text, command, color, width=150, height=40):
        """Create a text-only action for the success screen."""
        safe_tag = f'btn_{text.replace(" ", "_").replace("!", "").replace("?", "").replace(",", "")}'

        text_id = self.success_bg_canvas.create_text(
            x,
            y,
            text=text,
            fill=color,
            font=self.get_font(14, 'bold', 'italic'),
            anchor='center',
            tags=('success_text', safe_tag),
        )

        left, top, right, bottom = self.success_bg_canvas.bbox(text_id)
        hit_area_id = self.success_bg_canvas.create_rectangle(
            left - max(16, width // 6),
            top - max(8, height // 5),
            right + max(16, width // 6),
            bottom + max(8, height // 5),
            outline='',
            fill='',
            tags=('success_text', safe_tag),
        )
        self.success_bg_canvas.tag_lower(hit_area_id, text_id)

        self.success_bg_canvas.tag_bind(safe_tag, '<Button-1>', lambda e: command())
        self.success_bg_canvas.tag_bind(
            safe_tag,
            '<Enter>',
            lambda e, item=text_id: self.success_bg_canvas.itemconfig(item, fill=COLORS['text_primary']),
        )
        self.success_bg_canvas.tag_bind(
            safe_tag,
            '<Leave>',
            lambda e, item=text_id, button_color=color: self.success_bg_canvas.itemconfig(item, fill=button_color),
        )

        return text_id
    
    def close_success_animation(self):
        """Close the success animation window and exit the application"""
        if hasattr(self, 'success_window'):
            self.success_window.destroy()
        
        # Exit the entire application since main window is hidden
        self.root.quit()
    
    def add_update_check_button(self):
        """Add an update check button to the footer"""
        try:
            if hasattr(self, 'footer_frame'):
                update_btn = tk.Button(self.footer_frame,
                                     text="Check for Updates",
                                     command=self.manual_update_check,
                                     bg=COLORS['bg_card'],
                                     font=self.get_font(11, 'bold', 'italic'),
                                     padx=10, pady=4)
                self.style_action_button(update_btn, COLORS['accent_yellow'])
                update_btn.pack(side='left', padx=10, pady=10)
        except Exception as e:
            print(f"Could not add update button: {e}")
    
    def manual_update_check(self):
        """Manually triggered update check"""
        self.update_checker.manual_check_for_updates(self.root)

def main():
    root = tk.Tk()
    root.withdraw()  # Hide main window initially
    
    # Import config
    from update_config import get_update_config
    
    # Set window icon
    try:
        icon_path = get_resource_path(os.path.join("art", "Progression.ico"))
        if os.path.exists(icon_path):
            root.iconbitmap(icon_path)
    except Exception as e:
        print(f"Could not load icon: {e}")
    
    def show_main_app():
        """Show the main application after loading is complete"""
        root.deiconify()  # Show main window
        app = SteamWorkshopGUI(root)
    
    # Check for updates SYNCHRONOUSLY before starting anything
    config = get_update_config()
    update_info = check_for_updates_before_startup(root)
    
    if update_info and config.get("updates_required", True):
        # Show blocking update dialog - app won't continue until user updates
        show_blocking_update_dialog(root, update_info['checker'], update_info['release'])
    else:
        # No update needed or updates not required, start loading screen
        loading_screen = LoadingScreen(root, show_main_app)
        
        # If update available but not required, show non-blocking dialog
        if update_info:
            root.after(1000, lambda: show_optional_update_dialog(root, update_info['checker'], update_info['release']))
    
    root.mainloop()

def check_for_updates_before_startup(root):
    """Check for updates synchronously before starting the application"""
    try:
        from update_checker import UpdateChecker
        from update_config import get_update_config
        
        print("Checking for updates...")
        
        config = get_update_config()
        update_checker = UpdateChecker(config["current_version"])
        
        # Use synchronous check to block until complete
        release_info, error = update_checker.check_for_updates_sync()
        
        if error:
            print(f"Update check failed: {error}")
            return None
        elif release_info:
            latest_version = release_info['version']
            if update_checker.is_newer_version(latest_version):
                print(f"Update required: {latest_version}")
                return {
                    'checker': update_checker,
                    'release': release_info
                }
            else:
                print(f"Already up to date: {latest_version}")
        else:
            print("No release info available")
        
        return None
        
    except Exception as e:
        print(f"Update check failed: {e}")
        return None

def show_blocking_update_dialog(root, update_checker, release_info):
    """Show a blocking update dialog that prevents app from continuing"""
    # Create a new window for the update dialog
    update_window = tk.Toplevel(root)
    update_window.title("Update Required - Progression Loader")
    update_window.configure(bg=COLORS['bg_primary'])
    update_window.resizable(False, False)
    update_window.attributes('-topmost', True)
    
    # Make it modal - user can't interact with other windows
    update_window.transient(root)
    update_window.grab_set()
    
    # Set window icon
    try:
        icon_path = get_resource_path(os.path.join("art", "Progression.ico"))
        if os.path.exists(icon_path):
            update_window.iconbitmap(icon_path)
    except:
        pass
    
    window_width = 600
    window_height = 600
    center_window(update_window, window_width, window_height, root)

    panel = tk.Frame(
        update_window,
        bg=COLORS['bg_card'],
        highlightbackground=COLORS['border_light'],
        highlightcolor=COLORS['border_light'],
        highlightthickness=1,
    )
    panel.pack(fill='both', expand=True, padx=18, pady=18)

    title_label = tk.Label(
        panel,
        text="Update Required",
        fg=COLORS['text_highlight'],
        bg=COLORS['bg_card'],
        font=(FONT_FAMILY, 20, 'bold italic'),
    )
    title_label.pack(pady=20)
    
    message_label = tk.Label(panel,
                            text="A new version is available and required to continue.",
                            fg=COLORS['text_primary'], bg=COLORS['bg_card'],
                            font=(FONT_FAMILY, 14))
    message_label.pack(pady=10)
    
    version_frame = tk.Frame(panel, bg=COLORS['bg_card'])
    version_frame.pack(pady=15)
    
    current_label = tk.Label(version_frame,
                            text=f"Current Version: {update_checker.current_version}",
                            fg=COLORS['text_body'], bg=COLORS['bg_card'],
                            font=(FONT_FAMILY, 12))
    current_label.pack()
    
    latest_label = tk.Label(version_frame,
                           text=f"Required Version: {release_info['version']}",
                           fg=COLORS['accent_green'], bg=COLORS['bg_card'],
                           font=(FONT_FAMILY, 12, 'bold'))
    latest_label.pack(pady=5)
    
    # Release notes
    if release_info.get('body'):
        notes_label = tk.Label(panel,
                              text="What's New:",
                              fg=COLORS['text_primary'], bg=COLORS['bg_card'],
                              font=(FONT_FAMILY, 14, 'bold italic'))
        notes_label.pack(pady=(20, 10))
        
        notes_frame = tk.Frame(panel, bg=COLORS['bg_card'])
        notes_frame.pack(fill='both', expand=True, padx=40, pady=(0, 10))
        
        notes_text = tk.Text(notes_frame,
                            height=6,
                            bg=COLORS['bg_tertiary'], fg=COLORS['text_body'],
                            font=(FONT_FAMILY, 11),
                            wrap=tk.WORD,
                            state='disabled',
                            relief='flat',
                            bd=0,
                            insertbackground=COLORS['text_primary'],
                            highlightthickness=1,
                            highlightbackground=COLORS['border_light'])
        
        scrollbar = tk.Scrollbar(
            notes_frame,
            bg=COLORS['bg_tertiary'],
            troughcolor=COLORS['bg_secondary'],
            activebackground=COLORS['bg_hover'],
        )
        scrollbar.pack(side='right', fill='y')
        
        notes_text.pack(side='left', fill='both', expand=True)
        notes_text.config(yscrollcommand=scrollbar.set)
        scrollbar.config(command=notes_text.yview)
        
        # Insert release notes
        notes_text.config(state='normal')
        notes_text.insert('1.0', release_info['body'])
        notes_text.config(state='disabled')
    
    button_frame = tk.Frame(panel, bg=COLORS['bg_card'])
    button_frame.pack(side='bottom', pady=20)
    
    def auto_update():
        """Download and install the update automatically"""
        try:
            # Disable buttons during download
            auto_btn.config(state='disabled', text='Downloading...')
            download_btn.config(state='disabled')
            exit_btn.config(state='disabled')
            
            # Update the window to show progress
            update_window.update()
            
            # Start download in a separate thread
            import threading
            threading.Thread(target=perform_auto_update, daemon=True).start()
            
        except Exception as e:
            print(f"Auto update failed: {e}")
            # Re-enable buttons on error
            auto_btn.config(state='normal', text='Auto Update')
            download_btn.config(state='normal')
            exit_btn.config(state='normal')
    
    def perform_auto_update():
        """Perform the actual auto update process"""
        try:
            import requests
            import tempfile
            import shutil
            import subprocess
            import sys
            
            # Find the EXE asset in the release
            exe_asset = None
            for asset in release_info.get('assets', []):
                if asset.get('name', '').endswith('.exe'):
                    exe_asset = asset
                    break
            
            if not exe_asset:
                raise Exception("No EXE file found in release assets")
            
            download_url = exe_asset.get('browser_download_url')
            if not download_url:
                raise Exception("No download URL found for EXE asset")
            
            # Update status
            root.after(0, lambda: auto_btn.config(text='Downloading...'))
            
            # Download the new EXE
            response = requests.get(download_url, stream=True)
            response.raise_for_status()
            
            # Save to temporary file
            with tempfile.NamedTemporaryFile(delete=False, suffix='.exe') as temp_file:
                temp_path = temp_file.name
                for chunk in response.iter_content(chunk_size=8192):
                    temp_file.write(chunk)
            
            # Get current executable path
            if getattr(sys, 'frozen', False):
                # Running as PyInstaller bundle
                current_exe = sys.executable
            else:
                # Running as script - for testing
                current_exe = sys.argv[0]
            
            # Update status
            root.after(0, lambda: auto_btn.config(text='Installing...'))
            
            # Get configuration
            from update_config import get_update_config
            config = get_update_config()
            auto_restart = config.get("auto_restart_after_update", True)
            
            # Create a batch script to replace the executable and optionally restart
            if auto_restart:
                restart_command = f'start "" /B "{current_exe}"'
                restart_message = "Starting new version..."
            else:
                restart_command = 'echo Please manually start the application.'
                restart_message = "Please manually start the updated application."
            
            batch_script = f'''@echo off
echo Updating Progression Loader...
timeout /t 3 /nobreak >nul

REM Replace the executable
move "{temp_path}" "{current_exe}"
if errorlevel 1 (
    echo Failed to replace executable
    pause
    exit /b 1
)

echo Update complete! {restart_message}
timeout /t 1 /nobreak >nul

REM Start the new executable (if configured)
{restart_command}

REM Clean up this batch file
timeout /t 2 /nobreak >nul
del "%~f0"
'''
            
            # Write batch script to temp file
            with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.bat') as batch_file:
                batch_file.write(batch_script)
                batch_path = batch_file.name
            
            # Update status
            root.after(0, lambda: auto_btn.config(text='Restarting...'))
            
            # Show success message
            root.after(0, lambda: tk.messagebox.showinfo(
                "Update Complete", 
                "Update downloaded successfully!\nThe application will restart automatically.",
                parent=update_window
            ))
            
            # Execute the batch script and exit
            subprocess.Popen([batch_path], shell=True)
            
            # Exit the current application
            root.after(2000, root.quit)
            
        except Exception as e:
            print(f"Auto update failed: {e}")
            # Re-enable buttons on error
            root.after(0, lambda: auto_btn.config(state='normal', text='Auto Update'))
            root.after(0, lambda: download_btn.config(state='normal'))
            root.after(0, lambda: exit_btn.config(state='normal'))
            
            # Show error message
            root.after(0, lambda: tk.messagebox.showerror(
                "Auto Update Failed", 
                f"Failed to automatically update:\n{str(e)}\n\nPlease use 'Download Page' to update manually.",
                parent=update_window
            ))
    
    def download_page():
        """Open the release download page"""
        webbrowser.open(release_info['html_url'])
        print("Please download and install the update manually, then restart the application.")
        root.quit()  # Exit the entire application
    
    def exit_app():
        """Exit the application without updating"""
        print("Application cannot continue without updating.")
        root.quit()
    
    # Auto Update button (primary action)
    auto_btn = tk.Button(button_frame,
                        text="Auto Update",
                        command=auto_update,
                        bg=COLORS['bg_card'],
                        font=(FONT_FAMILY, 14, 'bold italic'),
                        padx=0, pady=6)
    style_text_button(auto_btn, COLORS['accent_green'], COLORS['bg_card'])
    auto_btn.pack(side='left', padx=10)
    
    download_btn = tk.Button(button_frame,
                            text="Download Page",
                            command=download_page,
                            bg=COLORS['bg_card'],
                            font=(FONT_FAMILY, 12, 'bold italic'),
                            padx=0, pady=6)
    style_text_button(download_btn, COLORS['accent_yellow'], COLORS['bg_card'])
    download_btn.pack(side='left', padx=10)
    
    exit_btn = tk.Button(button_frame,
                        text="Exit Application",
                        command=exit_app,
                        bg=COLORS['bg_card'],
                        font=(FONT_FAMILY, 12, 'bold italic'),
                        padx=0, pady=6)
    style_text_button(exit_btn, COLORS['accent_red'], COLORS['bg_card'])
    exit_btn.pack(side='left', padx=10)
    
    # Handle window close (same as exit)
    update_window.protocol("WM_DELETE_WINDOW", exit_app)
    
    # Focus on the auto update button
    auto_btn.focus_set()
    
    print("Blocking update dialog shown - app will not continue until user updates")

def show_optional_update_dialog(root, update_checker, release_info):
    """Show a non-blocking update dialog"""
    try:
        update_checker.show_update_dialog(root, release_info)
    except Exception as e:
        print(f"Could not show update dialog: {e}")

if __name__ == "__main__":
    main()
