Spicychat CRO Helper

Adds a draggable, resizable popover with tools (Action, Inventory, Perception, Settings) for Spicychat, Character.ai, and JanitorAI.

// ==UserScript==
// @name         Spicychat CRO Helper
// @namespace    http://tampermonkey.net/
// @version      1.22 // Patched Janitor.ai compatibility and unified page detection logic.
// @description  Adds a draggable, resizable popover with tools (Action, Inventory, Perception, Settings) for Spicychat, Character.ai, and JanitorAI.
// @author       Darkeyev2, [REDACTED], Gemini 1.5 Pro, Claude
// @match        https://spicychat.ai/*
// @match        https://character.ai/*
// @match        https://*.character.ai/*
// @match        https://janitorai.com/chats/*
// @match        https://*.janitorai.com/chats/*
// @match        https://www.janitorai.com/chats/*
// @match        https://janitorai.com/characters/*
// @match        https://*.janitorai.com/characters/*
// @match        https://www.janitorai.com/characters/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=spicychat.ai
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_setClipboard
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

// --- CRO (Character, Roll, Outcome) System Scales Data ---
// This object defines the various outcome scales used by the CRO system.
// Each scale has a name and a set of outcomes keyed by the d10 roll result (1-10).
const CRO_SCALES_DATA = Object.freeze({
  croScales: Object.freeze({
    main: Object.freeze({
      name: "Main Outcome",
      outcomes: Object.freeze({
        "1": "Critical Failure (significant setback)",
        "2": "Major Failure (clear consequence)",
        "3": "Simple Failure (no progress)",
        "4": "Poor Attempt (close, but falls short)",
        "5": "Complication (a problem emerges)",
        "6": "Minor Success (limited progress)",
        "7": "Weak Success (achieves goal, barely)",
        "8": "Clear Success (achieves goal)",
        "9": "Strong Success (achieves goal cleanly)",
        "10": "Critical Success (exceeds expectations)"
      })
    }),
    physical: Object.freeze({
      name: "Physical",
      outcomes: Object.freeze({
        "1": "Harsh Consequence (clear physical cost or injury)",
        "2": "Strained Effort (body struggles, fatigue sets in)",
        "3": "Forced Failure (pushing through but can't complete)",
        "4": "Rough Attempt (partial progress, noticeable strain)",
        "5": "Taxing Success (achieves goal but at physical cost)",
        "6": "Adequate Performance (body cooperates, gets it done)",
        "7": "Steady Control (confident execution)",
        "8": "Strong Performance (body responds well)",
        "9": "Smooth Execution (effortless feel)",
        "10": "Perfect Form (exactly as intended, if not better)"
      })
    }),
    manual: Object.freeze({
      name: "Manual",
      outcomes: Object.freeze({
        "1": "Complete Mishap (task goes wrong, clear setback)",
        "2": "Sloppy Work (barely functional, obvious flaws)",
        "3": "Sloppy Work (barely functional, obvious flaws)",
        "4": "Rough Execution (works but crude, noticeable imperfections)",
        "5": "Rough Execution (works but crude, noticeable imperfections)",
        "6": "Competent Work (solid execution, minor rough edges)",
        "7": "Competent Work (solid execution, minor rough edges)",
        "8": "Clean Execution (smooth work, well-handled)",
        "9": "Clean Execution (smooth work, well-handled)",
        "10": "Precise Control (exactly as intended, no wasted motion)"
      })
    }),
    mental: Object.freeze({
      name: "Mental",
      outcomes: Object.freeze({
        "1": "Analysis Paralysis (ex. overthinking, endless loops, decision paralysis, etc.)",
        "2": "Tunnel Vision (ex. fixation, missing alternatives, rigid thinking, etc.)",
        "3": "Emotional Bias (ex. wishful thinking, fear-driven reasoning, prejudice, etc.)",
        "4": "Scattered Focus (ex. jumping between ideas, unfocused effort, etc.)",
        "5": "Logical Analysis (ex. step-by-step reasoning, systematic breakdown, etc.)",
        "6": "Pattern Recognition (ex. connections, similarities, recurring themes, etc.)",
        "7": "Practical Application (ex. real-world solutions, functional thinking, etc.)",
        "8": "Creative Synthesis (ex. novel combinations, innovative approaches, etc.)",
        "9": "Abstract Reasoning (ex. theoretical concepts, pure logic, philosophy, etc.)",
        "10": "Intuitive Leap (ex. sudden understanding, gut insights, breakthrough moments, etc.)"
      })
    }),
    social: Object.freeze({
      name: "Social",
      outcomes: Object.freeze({
        "1": "Momentum Lost (interaction creates resistance or withdrawal)",
        "2": "Momentum Lost (interaction creates resistance or withdrawal)",
        "3": "Awkward Exchange (stilted, uncomfortable social flow)",
        "4": "Awkward Exchange (stilted, uncomfortable social flow)",
        "5": "Neutral Transaction (stalling, neither builds nor damages social standing)",
        "6": "Neutral Transaction (stalling, neither builds nor damages social standing)",
        "7": "Strong Rapport (builds connection and cooperation)",
        "8": "Strong Rapport (builds connection and cooperation)",
        "9": "Lasting Impact (memorable interaction, doors open)",
        "10": "Lasting Impact (memorable interaction, doors open)"
      })
    }),
    perception: Object.freeze({
      name: "Perception",
      outcomes: Object.freeze({
        "1": "Physical State (ex. appearance, condition, immediate qualities, etc.)",
        "2": "Physical State (ex. appearance, condition, immediate qualities, etc.)",
        "3": "Functional Aspect (ex. role, purpose, capabilities, etc.)",
        "4": "Functional Aspect (ex. role, purpose, capabilities, etc.)",
        "5": "Contextual Clues (ex. connections, significance, anomalies, etc.)",
        "6": "Contextual Clues (ex. connections, significance, anomalies, etc.)",
        "7": "Hidden Elements (ex. concealed aspects, overlooked details, etc.)",
        "8": "Hidden Elements (ex. concealed aspects, overlooked details, etc.)",
        "9": "Temporal Traces (ex. history, recent changes, accumulated effects, etc.)",
        "10": "Temporal Traces (ex. history, recent changes, accumulated effects, etc.)"
      })
    }),
    fortune: Object.freeze({
      name: "Fortune",
      outcomes: Object.freeze({
        "1": "Bad Timing (unfavorable coincidence)",
        "2": "Unlucky Turn (circumstances hinder, disadvantage)",
        "3": "Unlucky Turn (circumstances hinder, disadvantage)",
        "4": "Unlucky Turn (circumstances hinder, disadvantage)",
        "5": "Unlucky Turn (circumstances hinder, disadvantage)",
        "6": "Lucky Break (favorable circumstance, advantage)",
        "7": "Lucky Break (favorable circumstance, advantage)",
        "8": "Lucky Break (favorable circumstance, advantage)",
        "9": "Lucky Break (favorable circumstance, advantage)",
        "10": "Perfect Timing (major positive turn of events)"
      })
    }),
    stealth: Object.freeze({
      name: "Stealth",
      outcomes: Object.freeze({
        "1": "Fully Exposed (immediate detection, alarm raised)",
        "2": "Clearly Spotted (presence and intent obvious)",
        "3": "Obviously Noticed (seen but intent unclear)",
        "4": "Suspicion Raised (traces left, heightened awareness)",
        "5": "Minor Disturbance (small signs noticed)",
        "6": "Barely Avoided (close call, narrowly unnoticed)",
        "7": "Successfully Hidden (avoided direct detection)",
        "8": "Cleanly Unnoticed (no awareness triggered)",
        "9": "Seamless Passage (no signs of presence)",
        "10": "Perfect Concealment (completely undetected, no trace)"
      })
    }),
    itemFocus: Object.freeze({
      name: "Item Focus",
      outcomes: Object.freeze({
        "1": "Material Properties (ex. composition, durability, craftsmanship quality, etc.)",
        "2": "Material Properties (ex. composition, durability, craftsmanship quality, etc.)",
        "3": "Functional Design (ex. intended purpose, how it works, efficiency, etc.)",
        "4": "Functional Design (ex. intended purpose, how it works, efficiency, etc.)",
        "5": "Historical Context (ex. age, previous use, wear patterns, etc.)",
        "6": "Historical Context (ex. age, previous use, wear patterns, etc.)",
        "7": "Hidden Features (ex. concealed mechanisms, subtle details, etc.)",
        "8": "Contextual Significance (ex. relevance to situation, connections, etc.)",
        "9": "Contextual Significance (ex. relevance to situation, connections, etc.)",
        "10": "Symbolic Meaning (ex. cultural importance, personal resonance, etc.)"
      })
    }),
    timeAndFortune: Object.freeze({
      name: "Time & Fortune",
      outcomes: Object.freeze({
        "1": "Deep Troubles (Time passed under notably difficult background conditions)",
        "2": "Mostly Downs (The period felt marked by a generally negative trend)",
        "3": "Problems Mount (Minor issues seemed to consistently arise or worsen)",
        "4": "Felt Resisted (The period generally felt resistant or effortful)",
        "5": "Slight Drag (Progress or stability felt subtly held back during this time)",
        "6": "Slight Boost (Progress or stability felt subtly helped along during this time)",
        "7": "Easy Flow (The period generally felt smooth or cooperative)",
        "8": "Things Align (Minor opportunities seemed to consistently arise or improve)",
        "9": "Mostly Ups (The period felt marked by a generally positive trend)",
        "10": "Great Fortune (Time passed under notably favourable background conditions)"
      })
    }),
  }),
  pushingLimitsScale: Object.freeze({
    name: "Pushing Limits",
    outcomes: Object.freeze({
      "1": "Severe Backfire (major negative consequence)",
      "2": "Costly Failure (significant setback, clear price paid)",
      "3": "Costly Failure (significant setback, clear price paid)",
      "4": "Painful Attempt (falls short, noticeable strain or cost)",
      "5": "Painful Attempt (falls short, noticeable strain or cost)",
      "6": "Strained Success (goal met, but with visible effort)",
      "7": "Strained Success (goal met, but with visible effort)",
      "8": "Hard-Won Achievement (success despite the odds)",
      "9": "Hard-Won Achievement (success despite the odds)",
      "10": "Breakthrough Performance (exceeds expectations)"
    })
  }),
  inventoryFindScale: Object.freeze({
    name: "Find Scale",
    outcomes: Object.freeze({
      "1": "Empty Search (nothing useful found)",
      "2": "Empty Search (nothing useful found)",
      "3": "Mundane Items (basic, expected contents)",
      "4": "Mundane Items (basic, expected contents)",
      "5": "Useful Basics (practical items for current situation)",
      "6": "Useful Basics (practical items for current situation)",
      "7": "Helpful Resources (relevant, contextually appropriate)",
      "8": "Helpful Resources (relevant, contextually appropriate)",
      "9": "Valuable Discovery (significantly useful items)",
      "10": "Perfect Find (exactly what's needed right now)"
    })
  })
});

(function() {
  'use strict';

  // --- MODULE: CRO_Config ---
  // Stores all static configuration for the CRO Helper.
  window.CRO_Config = {
    // Default dimensions and positioning for the popover and trigger button.
    defaultPopoverWidth: 360,
    defaultPopoverHeight: 480,
    minPopoverWidth: 300,
    minPopoverHeight: 360,
    maxPopoverHeightRatio: 0.85, // Maximum height as a ratio of window inner height.
    triggerBottomOffset: '15px',
    defaultTriggerLeftOffset: '15px',
    popoverBottomOffset: '75px', // Space from the bottom of the viewport.
    loadDelay: 1000, // Milliseconds to wait before initializing the script.
    cornerHandleSize: '12px', // Size of corner resize handles.
    edgeHandleThickness: '8px', // Thickness of edge resize handles.
    TOOLBAR_WIDTH: 50, // Width of the vertical toolbar in pixels.
    ERROR_MESSAGE_DURATION: 3000, // How long error messages are displayed.

    // Validation constants for inputs.
    validation: {
      MIN_ROLL: 1, MAX_ROLL: 10, DICE_FACES: 10,
      MIN_MODIFIER: -3, MAX_MODIFIER: 3,
      MIN_STAKES: -3, MAX_STAKES: 3,
    },
    // Animation constants.
    animation: {
      DICE_ANIMATION_DURATION: 1000, // Duration of the dice roll animation in ms.
    },

    // Base keys for GM_setValue/GM_getValue. Domain will be appended for site-specific storage.
    storageKeys: {
      POS_WIDTH_BASE: 'cro-helper-width',
      POS_HEIGHT_BASE: 'cro-helper-height',
      POS_LEFT_BASE: 'cro-helper-left',
      THEME_BASE: 'cro-helper-theme'
    },
    // IDs for key HTML elements.
    ids: {
      HELPER_CONTAINER: 'cro-helper-container',
      TRIGGER_BUTTON: 'cro-helper-trigger',
      CLOSE_BUTTON: 'cro-helper-close-button',
      HEADER: 'cro-helper-header',
      TOOLBAR: 'cro-helper-toolbar',
      MAIN_CONTENT_AREA: 'cro-helper-main-content',
      RESIZE_TL: 'cro-helper-resize-tl',
      RESIZE_TR: 'cro-helper-resize-tr',
      RESIZE_TOP: 'cro-helper-resize-handle-top',
      OUTPUT_ACTION: 'cro-helper-output-action',
      ACTION_ERROR_MESSAGE: 'cro-action-error-message'
    },
    // CSS class names used throughout the script.
    classNames: {
      VISIBLE: 'cro-helper-visible', HIDDEN: 'cro-helper-hidden', INPUT: 'cro-input',
      BUTTON: 'cro-button', ROLL_BUTTON: 'cro-roll-button', OUTPUT_AREA: 'cro-output-area',
      COPY_BUTTON: 'cro-copy-button', SEND_BUTTON: 'cro-send-button', DICE_ROLLING: 'dice-rolling',
      DRAGGING_BODY: 'cro-dragging', RESIZING_BODY: 'cro-resizing', TOOLBAR_BUTTON: 'cro-toolbar-button',
      SCREEN_CONTENT_WRAPPER: 'cro-screen-content-wrapper', SCREEN_TITLE: 'cro-screen-title',
      SCREEN_SUBHEADING: 'cro-screen-subheading', SCREEN_DESCRIPTION: 'cro-screen-description',
      ACTION_MODIFIER_CONTAINER: 'cro-action-modifiers', CHECKBOX_LABEL: 'cro-checkbox-label',
      SLIDER: 'cro-slider', SLIDER_VALUE: 'cro-slider-value', THEME_BUTTON: 'cro-theme-button',
      ERROR_MESSAGE: 'cro-error-message', INPUT_ERROR: 'cro-input-error', INPUT_GROUP: 'cro-input-group',
      INPUT_WRAPPER: 'cro-input-wrapper', INPUT_ARROW: 'cro-input-arrow',
      INPUT_WITH_BUTTON: 'cro-input-with-button', INPUT_DIALOGUE: 'cro-input-dialogue',
      INPUT_ACTION_DESC: 'cro-input-action', INPUT_NUMBER: 'cro-input-number',
      INPUT_SELECT: 'cro-input-select', SLIDER_GROUP: 'cro-slider-group',
      OUTPUT_CONTAINER: 'cro-output-container', OUTPUT_BUTTONS_GROUP: 'cro-output-buttons',
    },
    // Predefined themes with their color palettes.
    themes: {
      "Default Dark": Object.freeze({ background: '#121212', text: '#e0e0e0', border: '#333333', headerBackground: '#1e1e1e', textareaBackground: '#0f0f0f', italicColor: '#ff80ab', accent: '#bb86fc', focusBorder: '#333333', errorColor: '#cf6679', buttonIconColor: '#b0b0b0', buttonTextColor: '#e0e0e0', buttonBackground: '#333333', buttonHoverBg: '#444444', buttonActiveBg: '#555555', copySuccessBg: '#03dac6', deleteButtonBg: '#cf6679', deleteButtonHoverBg:'#de778a', scrollbarTrack: '#1e1e1e', scrollbarThumb: '#444444', scrollbarThumbHover:'#555555', croOutputBackground: '#0f0f0f', croInputBorder: '#333333', croRainbowBorder1:  '#C70039', croRainbowBorder2:  '#FF8333', croRainbowBorder3:  '#FFBF00', croRainbowBorder4:  '#33FF57', croRainbowBorder5:  '#339BFF', croRainbowBorder6:  '#9B33FF', croRainbowBorder7:  '#FF33A1', resizeHandleColor: '#555555', toolbarActiveBg: '#555555', toolbarActiveIcon: '#e0e0e0', themeButtonBg: '#333333', themeButtonHoverBg: '#444444', themeButtonActiveBg:'#bb86fc', themeButtonActiveText: '#000000' }),
      "Solarized Dark": Object.freeze({ background: '#201c16', text: '#ffffce', border: '#29241c', headerBackground: '#29241c', textareaBackground: '#181410', italicColor: '#e91e63', accent: '#0072f5', focusBorder: '#524939', errorColor: '#ff6b6b', buttonIconColor: '#9b8a6b', buttonTextColor: '#c7bbaa', buttonBackground: '#3a342a', buttonHoverBg: '#4a4236', buttonActiveBg: '#524939', copySuccessBg: '#138E42', deleteButtonBg: '#a03030', deleteButtonHoverBg:'#bf4040', scrollbarTrack: '#2b2b2b', scrollbarThumb: '#555555', scrollbarThumbHover:'#777777', croOutputBackground: '#100e0b', croInputBorder: '#3a342a', croRainbowBorder1: '#C70039', croRainbowBorder2: '#FF8333', croRainbowBorder3: '#FFBF00', croRainbowBorder4: '#33FF57', croRainbowBorder5: '#339BFF', croRainbowBorder6: '#9B33FF', croRainbowBorder7: '#FF33A1', resizeHandleColor: '#524939', toolbarActiveBg: '#524939', toolbarActiveIcon: '#ffffce', themeButtonBg: '#3a342a', themeButtonHoverBg: '#4a4236', themeButtonActiveBg:'#0072f5', themeButtonActiveText: '#ffffff' }),
      "Midnight Blue": Object.freeze({ background: '#1a1d2a', text: '#d0d8f0', border: '#2a2d40', headerBackground: '#222536', textareaBackground: '#151724', italicColor: '#ff79c6', accent: '#88aaff', focusBorder: '#505870', errorColor: '#ff7575', buttonIconColor: '#a0a8c0', buttonTextColor: '#d0d8f0', buttonBackground: '#30344d', buttonHoverBg: '#40445f', buttonActiveBg: '#505870', copySuccessBg: '#3ba371', deleteButtonBg: '#c04050', deleteButtonHoverBg:'#d05060', scrollbarTrack: '#222536', scrollbarThumb: '#40445f', scrollbarThumbHover:'#505870', croOutputBackground: '#10121a', croInputBorder: '#30344d', croRainbowBorder1: '#ff79c6', croRainbowBorder2: '#ff9f80', croRainbowBorder3: '#f1fa8c', croRainbowBorder4: '#50fa7b', croRainbowBorder5: '#8be9fd', croRainbowBorder6: '#bd93f9', croRainbowBorder7: '#ff79c6', resizeHandleColor: '#505870', toolbarActiveBg: '#505870', toolbarActiveIcon: '#d0d8f0', themeButtonBg: '#30344d', themeButtonHoverBg: '#40445f', themeButtonActiveBg:'#88aaff', themeButtonActiveText: '#151724' }),
      "Simple Light": Object.freeze({ background: '#ffffff', text: '#212121', border: '#e0e0e0', headerBackground: '#f5f5f5', textareaBackground: '#fcfcfc', italicColor: '#d81b60', accent: '#1976d2', focusBorder: '#1976d2', errorColor: '#e53935', buttonIconColor: '#616161', buttonTextColor: '#212121', buttonBackground: '#e0e0e0', buttonHoverBg: '#d6d6d6', buttonActiveBg: '#bdbdbd', copySuccessBg: '#4caf50', deleteButtonBg: '#e53935', deleteButtonHoverBg:'#d32f2f', scrollbarTrack: '#f1f1f1', scrollbarThumb: '#c1c1c1', scrollbarThumbHover:'#a8a8a8', croOutputBackground: '#f0f0f0', croInputBorder: '#e0e0e0', croRainbowBorder1: '#d81b60', croRainbowBorder2: '#ff7043', croRainbowBorder3: '#ffc107', croRainbowBorder4: '#4caf50', croRainbowBorder5: '#2196f3', croRainbowBorder6: '#673ab7', croRainbowBorder7: '#e91e63', resizeHandleColor: '#bdbdbd', toolbarActiveBg: '#bdbdbd', toolbarActiveIcon: '#212121', themeButtonBg: '#e0e0e0', themeButtonHoverBg: '#d6d6d6', themeButtonActiveBg:'#1976d2', themeButtonActiveText: '#ffffff' })
    },
    // Configurations for different supported chat sites.
    siteConfigs: {
      'spicychat.ai': {
        // More robust selector for Spicychat, not relying on placeholder text.
        chatInputSelector: 'div[class*="rounded-\\[13px\\]"][class*="focus-within:border-blue-9"] textarea',
      },
      'character.ai': {
        chatInputSelector: 'textarea[placeholder^="Message "]', // Assumes English placeholder.
      },
        'janitorai.com': {
            chatInputSelector: 'div[class*="_chatInputContainer"] textarea',
        },
    },
    // Functions to get SVG icon strings, dynamically colored based on the current theme.
    getIconColor: () => window.CRO_State.editorColors.buttonIconColor,
    getActiveIconColor: () => window.CRO_State.editorColors.toolbarActiveIcon || window.CRO_State.editorColors.text,
    copyIconSVG: () => `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="${window.CRO_Config.getIconColor()}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg>`,
    sendIconSVG: () => `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="${window.CRO_Config.getIconColor()}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m3 3 3 9-3 9 19-9Z"></path><path d="M6 12h16"></path></svg>`,
    diceIconSVG: (size = 18, color = window.CRO_Config.getIconColor()) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect><circle cx="12" cy="12" r="1"></circle><circle cx="18" cy="6" r="1"></circle><circle cx="6" cy="18" r="1"></circle></svg>`,
    inventoryIconSVG: (size = 18, color = window.CRO_Config.getIconColor()) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="7" rx="2" ry="2"/><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"/></svg>`,
    perceptionIconSVG: (size = 18, color = window.CRO_Config.getIconColor()) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"></path><circle cx="12" cy="12" r="3"></circle></svg>`,
    settingsIconSVG: (size = 18, color = window.CRO_Config.getIconColor()) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 7h-9"/><path d="M14 17H4"/><circle cx="17" cy="17" r="3"/><circle cx="7" cy="7" r="3"/></svg>`,
    closeIconSVG: () => `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`,

    // References to the outcome scales defined globally.
    croScales: CRO_SCALES_DATA.croScales,
    pushingLimitsScale: CRO_SCALES_DATA.pushingLimitsScale,
    inventoryFindScale: CRO_SCALES_DATA.inventoryFindScale,

    // Retrieves the outcome description for a given scale and roll value.
    getScaleOutcome: (scaleKey, rollValue, isPushingLimits = false) => {
      const C = window.CRO_Config;
      const scaleData = isPushingLimits ? C.pushingLimitsScale : (C.croScales[scaleKey] || C.croScales.main);
      if (!scaleData || !scaleData.outcomes) {
        console.warn(`[CRO Helper] Scale data or outcomes not found for key: ${scaleKey}, pushingLimits: ${isPushingLimits}. Using fallback.`);
        return `Outcome ${rollValue} (Scale Undefined)`;
      }
      return scaleData.outcomes[rollValue] || `Outcome ${rollValue} (Undefined in Scale)`;
    },
    // Array to store screen configurations (populated by CRO_App.initScreensConfig).
    screens: [],
  };

  // --- MODULE: CRO_State ---
  // Manages the dynamic state of the CRO Helper UI and interactions.
  window.CRO_State = {
    // DOM element references, populated by CRO_UI.createElements.
    helperContainer: null, triggerButton: null, closeButton: null, header: null, toolbar: null,
    mainContentArea: null, resizeHandleTL: null, resizeHandleTR: null, resizeHandleTop: null,
    // UI visibility and interaction states.
    isHelperVisible: false,
    animationFrameIds: new Map(), // Stores requestAnimationFrame IDs for active animations.
    isDraggingTrigger: false, didDragTrigger: false, dragOffsetX: 0, // Trigger button drag state.
    activeResizeHandle: null, // ID of the currently active resize handle.
    startX: 0, startY: 0, startWidth: 0, startHeight: 0, startLeft: 0, // Popover resize/drag state.
    activeScreenId: 'cro_generator', // ID of the currently displayed screen in the popover.
    errorTimeout: null, // Timeout ID for clearing error messages.
    // Theme and site-specific state.
    editorColors: { ...window.CRO_Config.themes["Default Dark"] }, // Current theme's color palette.
    currentThemeName: "Default Dark", // Name of the currently active theme.
    currentSiteKey: null // Key identifying the current website (e.g., 'spicychat.ai').
  };

  // --- MODULE: CRO_ElementCache ---
  // Caches frequently accessed DOM elements to improve performance.
  window.CRO_ElementCache = {
    chatInput: null, // Cached reference to the main chat input textarea.
    lastChatInputCheck: 0, // Timestamp of the last time the chat input was queried.
    CACHE_DURATION: 5000, // How long to cache the chat input element before re-querying (in ms).

    // Gets the main chat input textarea, using caching and site-specific selectors.
    getChatInput() {
      const now = Date.now();
      if (!this.chatInput || (now - this.lastChatInputCheck > this.CACHE_DURATION) || !document.body.contains(this.chatInput)) {
        const siteConfig = window.CRO_Utils.getCurrentSiteConfig();
        if (siteConfig && siteConfig.chatInputSelector) {
          this.chatInput = document.querySelector(siteConfig.chatInputSelector);
        } else {
          console.warn(`[CRO Helper] No chat input selector configured for this site: ${window.location.hostname}. Trying generic selectors.`);
          // Fallback to a list of common selectors if no specific config is found.
          const genericSelectors = [
            'textarea[placeholder^="Message"]',
            'textarea[placeholder^="Messag"]',
            'textarea.chakra-textarea[placeholder^="Enter to send chat"]',
            'textarea[data-id="chat-input"]',
            'textarea#chat-input',
            'textarea[name="chat_input"]'
          ];
          for (const selector of genericSelectors) {
            this.chatInput = document.querySelector(selector);
            if (this.chatInput) break;
          }
          if (!this.chatInput) {
            console.error("[CRO Helper] Could not find chat input with generic selectors either.");
          }
        }
        this.lastChatInputCheck = now;
      }
      return this.chatInput;
    },
    // Invalidates the cached chat input element.
    invalidate() {
      this.chatInput = null;
      this.lastChatInputCheck = 0;
    }
  };

  // --- MODULE: CRO_ErrorHandler ---
  // Provides error handling utilities.
  window.CRO_ErrorHandler = {
    // Wraps a function in a try-catch block for robust error handling.
    withErrorBoundary(fn, fallback = null, context = 'Unknown Function') {
      return (...args) => {
        try {
          return fn(...args);
        } catch (error) {
          console.error(`[CRO Helper ErrorBoundary] Error in ${context}:`, error.message, error.stack);
          if (typeof fallback === 'function') {
            try {
              return fallback(...args);
            } catch (fallbackError) {
              console.error(`[CRO Helper ErrorBoundary] Error in fallback for ${context}:`, fallbackError.message, fallbackError.stack);
              return null;
            }
          }
          return null;
        }
      };
    },
    // Validates input values based on type and constraints.
    validateInput(value, type, constraints = {}) {
      const C = window.CRO_Config.validation;
      const errors = [];
      switch (type) {
        case 'rollValue':
          const num = parseInt(value, 10);
          if (isNaN(num)) {
            errors.push('Raw Roll must be a number.');
          } else if (num < (constraints.min || C.MIN_ROLL) || num > (constraints.max || C.MAX_ROLL)) {
            errors.push(`Raw Roll must be between ${constraints.min || C.MIN_ROLL} and ${constraints.max || C.MAX_ROLL}.`);
          }
          break;
        default:
          errors.push(`Unknown validation type: ${type}`);
      }
      return { isValid: errors.length === 0, errors };
    }
  };

  // --- MODULE: CRO_Utils ---
  // Contains general utility functions for the script.
  window.CRO_Utils = {
    // Converts a camelCase key to a CSS variable name (e.g., 'buttonBackground' -> '--cro-helper-button-background').
    keyToCssVar: (key) => {
      const cssVar = key.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
      return `--cro-helper-${cssVar}`;
    },
    // Gets the popover's bottom offset from the configuration.
    getPopoverBottomPx: () => parseInt(window.CRO_Config.popoverBottomOffset, 10) || 75,
    // Formats a slider value (e.g., positive numbers get a '+' prefix).
    formatSliderValue: (value) => {
      const num = parseInt(value, 10);
      return num > 0 ? `+${num}` : `${num}`;
    },
    // Creates a standard input group (label + input element).
    createInputGroup: (labelText, inputId, inputConfig = {}) => {
      const C = window.CRO_Config;
      const group = document.createElement('div');
      group.classList.add(C.classNames.INPUT_GROUP);
      if (inputConfig.fullWidth) group.classList.add('cro-full-width');

      const label = document.createElement('label');
      label.textContent = labelText;
      label.htmlFor = inputId;

      const inputElement = document.createElement(inputConfig.elementType || 'input');
      inputElement.id = inputId;
      Object.assign(inputElement, inputConfig.attributes || {});
      inputElement.classList.add(C.classNames.INPUT);
      if (inputConfig.customClass) inputElement.classList.add(inputConfig.customClass);

      group.appendChild(label);
      group.appendChild(inputElement);
      return { group, input: inputElement, label };
    },
    // Creates a slider input group (label + slider + value display).
    createSliderGroup: (labelText, sliderId, valueDisplayId, config = {}) => {
      const C = window.CRO_Config;
      const group = document.createElement('div');
      group.classList.add(C.classNames.INPUT_GROUP, C.classNames.SLIDER_GROUP);

      const label = document.createElement('label');
      label.textContent = labelText;
      label.htmlFor = sliderId;

      const slider = document.createElement('input');
      slider.type = 'range';
      slider.id = sliderId;
      slider.min = config.min || '-3';
      slider.max = config.max || '3';
      slider.step = config.step || '1';
      slider.value = config.value || '0';
      if (config.title) slider.title = config.title;
      slider.classList.add(C.classNames.SLIDER);

      const valueSpan = document.createElement('span');
      valueSpan.id = valueDisplayId;
      valueSpan.classList.add(C.classNames.SLIDER_VALUE);
      valueSpan.textContent = window.CRO_Utils.formatSliderValue(slider.value);

      window.CRO_UI.EventManager.addListener(slider, 'input', () => {
        valueSpan.textContent = window.CRO_Utils.formatSliderValue(slider.value);
      });

      group.appendChild(label);
      group.appendChild(slider);
      group.appendChild(valueSpan);
      return { group, slider, valueDisplay: valueSpan };
    },
    // Creates a checkbox input with a label.
    createCheckbox: (id, labelText, titleText) => {
      const C = window.CRO_Config;
      const label = document.createElement('label');
      label.classList.add(C.classNames.CHECKBOX_LABEL);
      if (titleText) label.title = titleText;

      const checkbox = document.createElement('input');
      checkbox.type = 'checkbox';
      checkbox.id = id;
      if (titleText) checkbox.title = titleText;

      label.appendChild(checkbox);
      label.appendChild(document.createTextNode(` ${labelText}`));
      return { label, checkbox };
    },
    // Determines and returns a consistent key for the current website.
    getCurrentSiteKey: () => {
      if (window.CRO_State.currentSiteKey) return window.CRO_State.currentSiteKey;

      const hostname = window.location.hostname.toLowerCase();
      if (hostname.includes('spicychat.ai')) {
        window.CRO_State.currentSiteKey = 'spicychat.ai';
      } else if (hostname.includes('character.ai')) {
        window.CRO_State.currentSiteKey = 'character.ai';
      } else if (hostname.includes('janitorai.com')) {
        window.CRO_State.currentSiteKey = 'janitorai.com';
      } else {
        console.warn("[CRO Helper] Unknown site, using generic site key:", hostname);
        window.CRO_State.currentSiteKey = 'generic'; // Fallback for unmatched sites.
      }
      return window.CRO_State.currentSiteKey;
    },
    // Retrieves the configuration object for the current site.
    getCurrentSiteConfig: () => {
      const siteKey = window.CRO_Utils.getCurrentSiteKey();
      return window.CRO_Config.siteConfigs[siteKey] || null;
    }
  };

  // --- MODULE: CRO_Persistence ---
  // Handles loading and saving of script state using GM_getValue and GM_setValue.
  // All persistent state is now scoped per-domain.
  window.CRO_Persistence = {
    // Loads popover position, size, and theme from storage for the current domain.
    loadPersistentState: () => {
      const C = window.CRO_Config;
      const S = window.CRO_State;
      const siteKey = window.CRO_Utils.getCurrentSiteKey();

      const savedWidth = parseInt(GM_getValue(`${C.storageKeys.POS_WIDTH_BASE}-${siteKey}`, C.defaultPopoverWidth), 10);
      const savedHeight = parseInt(GM_getValue(`${C.storageKeys.POS_HEIGHT_BASE}-${siteKey}`, C.defaultPopoverHeight), 10);
      const savedLeft = parseInt(GM_getValue(`${C.storageKeys.POS_LEFT_BASE}-${siteKey}`, C.defaultTriggerLeftOffset), 10);

      // Constrain popover position and size to viewport and configured limits.
      const maxLeft = window.innerWidth - Math.max(C.minPopoverWidth, savedWidth) - 5;
      const constrainedLeft = Math.max(5, Math.min(savedLeft, maxLeft));

      if (S.triggerButton) S.triggerButton.style.left = `${constrainedLeft}px`;
      if (S.helperContainer) S.helperContainer.style.left = `${constrainedLeft}px`;

      const bottomOffsetPx = window.CRO_Utils.getPopoverBottomPx();
      const maxH = Math.max(C.minPopoverHeight, window.innerHeight * C.maxPopoverHeightRatio - bottomOffsetPx);
      const constrainedWidth = Math.max(C.minPopoverWidth, savedWidth);
      const constrainedHeight = Math.max(C.minPopoverHeight, Math.min(savedHeight, maxH));

      if (S.helperContainer) {
        S.helperContainer.style.width = `${constrainedWidth}px`;
        S.helperContainer.style.height = `${constrainedHeight}px`;
      }
    },
    // Saves popover position and size to storage for the current domain.
    savePositionAndSize: (left, width, height) => {
      const C = window.CRO_Config;
      const siteKey = window.CRO_Utils.getCurrentSiteKey();
      GM_setValue(`${C.storageKeys.POS_LEFT_BASE}-${siteKey}`, left);
      GM_setValue(`${C.storageKeys.POS_WIDTH_BASE}-${siteKey}`, width);
      GM_setValue(`${C.storageKeys.POS_HEIGHT_BASE}-${siteKey}`, height);
    },
    // Loads the theme setting from storage for the current domain.
    loadThemeSettings: () => {
      const C = window.CRO_Config;
      const S = window.CRO_State;
      const siteKey = window.CRO_Utils.getCurrentSiteKey();
      const savedThemeName = GM_getValue(`${C.storageKeys.THEME_BASE}-${siteKey}`, "Default Dark");

      if (C.themes[savedThemeName]) {
        S.currentThemeName = savedThemeName;
        S.editorColors = { ...C.themes[savedThemeName] };
      } else {
        // Fallback to default theme if saved theme is invalid.
        S.currentThemeName = "Default Dark";
        S.editorColors = { ...C.themes["Default Dark"] };
        GM_setValue(`${C.storageKeys.THEME_BASE}-${siteKey}`, S.currentThemeName);
      }
    },
    // Saves the current theme setting to storage for the current domain.
    saveThemeSetting: (themeName) => {
      const siteKey = window.CRO_Utils.getCurrentSiteKey();
      GM_setValue(`${window.CRO_Config.storageKeys.THEME_BASE}-${siteKey}`, themeName);
    }
  };

  // --- MODULE: CRO_ThemeManager ---
  // Manages applying themes and updating UI elements based on the current theme.
  window.CRO_ThemeManager = {
    // Applies all colors from the current theme to CSS variables and updates theme-dependent icons.
    updateGlobalStyles: () => {
      const S = window.CRO_State;
      const C = window.CRO_Config;
      const UI = window.CRO_UI;

      // Set CSS variables for all colors in the current theme.
      for (const key in S.editorColors) {
        if (Object.prototype.hasOwnProperty.call(S.editorColors, key)) {
          document.documentElement.style.setProperty(window.CRO_Utils.keyToCssVar(key), S.editorColors[key]);
        }
      }

      // Update icons that depend on theme colors.
      if (S.triggerButton) S.triggerButton.innerHTML = C.diceIconSVG(20);
      if (S.closeButton) S.closeButton.innerHTML = C.closeIconSVG();
      if (S.toolbar) UI.Toolbar.render(); // Toolbar icons are theme-dependent.

      // Update icons within the main content area if it's rendered.
      const currentContent = document.getElementById(C.ids.MAIN_CONTENT_AREA);
      if (currentContent) {
        currentContent.querySelectorAll(`.${C.classNames.COPY_BUTTON}`).forEach(btn => btn.innerHTML = C.copyIconSVG());
        currentContent.querySelectorAll(`.${C.classNames.SEND_BUTTON}`).forEach(btn => btn.innerHTML = C.sendIconSVG());
        currentContent.querySelectorAll(`.${C.classNames.ROLL_BUTTON}`).forEach(btn => btn.innerHTML = C.diceIconSVG(16));
        // Update active state of theme buttons in settings screen.
        if (S.activeScreenId === 'settings_screen') {
          const themeButtonsContainer = currentContent.querySelector('.cro-theme-buttons-container');
          if (themeButtonsContainer) {
            themeButtonsContainer.querySelectorAll(`.${C.classNames.THEME_BUTTON}`).forEach(btn => {
              btn.classList.toggle('active', btn.dataset.themeName === S.currentThemeName);
            });
          }
        }
      }
    },
    // Applies a new theme by name, saves it, and updates global styles.
    applyAndSaveTheme: (themeName) => {
      const C = window.CRO_Config;
      const S = window.CRO_State;
      if (C.themes[themeName]) {
        S.currentThemeName = themeName;
        S.editorColors = { ...C.themes[themeName] };
        window.CRO_Persistence.saveThemeSetting(themeName); // Saves per-domain.
        window.CRO_ThemeManager.updateGlobalStyles();
      } else {
        console.warn(`[CRO Helper] Theme "${themeName}" not found.`);
      }
    }
  };

  // --- MODULE: CRO_UI ---
  // Manages UI creation, event handling, and screen rendering.
  window.CRO_UI = {
    EventManager: {
      activeListeners: new Map(), // Stores active event listeners for cleanup.
      // Adds an event listener with error boundary and tracking.
      addListener(element, event, handler, options = {}) {
        if (!element) {
          console.warn("[CRO EventManager] Attempted to add listener to null element for event:", event);
          return null;
        }
        const wrappedHandler = window.CRO_ErrorHandler.withErrorBoundary(handler, null, `EventListener (${event} on ${element.id || element.tagName})`);
        element.addEventListener(event, wrappedHandler, options);
        const listenerId = `${element.id || 'el'}-${event}-${Date.now()}-${Math.random().toString(36).substring(2,7)}`;
        this.activeListeners.set(listenerId, { element, event, handler: wrappedHandler, options });
        return listenerId;
      },
      // Removes a tracked event listener.
      removeListener(listenerId) {
        const listener = this.activeListeners.get(listenerId);
        if (listener) {
          listener.element.removeEventListener(listener.event, listener.handler, listener.options);
          this.activeListeners.delete(listenerId);
        }
      },
      // Removes all tracked event listeners (e.g., on script unload if implemented).
      cleanupAll() {
        for (const id of this.activeListeners.keys()) {
          this.removeListener(id);
        }
        console.log("[CRO EventManager] All listeners cleaned up.");
      }
    },
    StyleSheets: {
      // Generates a string of CSS variables from the current theme's editorColors.
      generateCSSVariables: () => {
        const S = window.CRO_State;
        return Object.entries(S.editorColors).map(([key, value]) => `${window.CRO_Utils.keyToCssVar(key)}: ${value};`).join('\n');
      },
      // Returns the base CSS for the script, including :root variables.
      getBaseStyles: () => `
        :root {
          ${window.CRO_UI.StyleSheets.generateCSSVariables()}
          --cro-bg: var(--cro-helper-background);
          --cro-text: var(--cro-helper-text);
          --cro-border: var(--cro-helper-border);
          --cro-header-bg: var(--cro-helper-header-background);
          --cro-input-bg: var(--cro-helper-textarea-background);
          --cro-input-border: var(--cro-helper-cro-input-border);
          --cro-output-bg: var(--cro-helper-cro-output-background);
          --cro-focus-border: var(--cro-helper-focus-border);
          --cro-accent: var(--cro-helper-accent);
          --cro-icon-color: var(--cro-helper-button-icon-color);
          --cro-btn-text: var(--cro-helper-button-text-color);
          --cro-btn-bg: var(--cro-helper-button-background);
          --cro-btn-hover-bg: var(--cro-helper-button-hover-bg);
          --cro-btn-active-bg: var(--cro-helper-button-active-bg);
          --cro-copy-success-bg: var(--cro-helper-copy-success-bg);
          --cro-italic-color: var(--cro-helper-italic-color);
          --cro-scrollbar-track: var(--cro-helper-scrollbar-track);
          --cro-scrollbar-thumb: var(--cro-helper-scrollbar-thumb);
          --cro-scrollbar-thumb-hover: var(--cro-helper-scrollbar-thumb-hover);
          --cro-resize-handle: var(--cro-helper-resize-handle-color);
          --cro-toolbar-active-bg: var(--cro-helper-toolbar-active-bg);
          --cro-toolbar-active-icon: var(--cro-helper-toolbar-active-icon);
          --cro-theme-btn-bg: var(--cro-helper-theme-button-bg);
          --cro-theme-btn-hover-bg: var(--cro-helper-theme-button-hover-bg);
          --cro-theme-btn-active-bg: var(--cro-helper-theme-button-active-bg);
          --cro-theme-btn-active-text: var(--cro-helper-theme-button-active-text);
          --cro-error-text-color: var(--cro-helper-error-color, #ff6b6b);
          --cro-error-border-color: var(--cro-helper-error-color, #ff6b6b);
        }
        body.${window.CRO_Config.classNames.DRAGGING_BODY},
        body.${window.CRO_Config.classNames.RESIZING_BODY} {
          user-select: none;
          -webkit-user-select: none;
        }
      `,
      // Returns CSS for animations (e.g., dice roll border).
      getAnimationStyles: () => {
        const C = window.CRO_Config;
        return `
          @keyframes cro-helper-rainbow-border {
            0%, 100% { border-color: var(--cro-helper-cro-rainbow-border1); }
            14% { border-color: var(--cro-helper-cro-rainbow-border2); }
            28% { border-color: var(--cro-helper-cro-rainbow-border3); }
            42% { border-color: var(--cro-helper-cro-rainbow-border4); }
            57% { border-color: var(--cro-helper-cro-rainbow-border5); }
            71% { border-color: var(--cro-helper-cro-rainbow-border6); }
            85% { border-color: var(--cro-helper-cro-rainbow-border7); }
          }
          .${C.classNames.INPUT}.${C.classNames.DICE_ROLLING} {
            border-width: 1px !important;
            border-style: solid !important;
            animation-name: cro-helper-rainbow-border;
            animation-duration: ${C.animation.DICE_ANIMATION_DURATION / 1000}s;
            animation-timing-function: linear;
            animation-iteration-count: infinite;
          }
        `;
      },
      // Returns CSS for UI components (popover, buttons, inputs, etc.).
      getComponentStyles: () => {
        const C = window.CRO_Config;
        return `
          #${C.ids.TRIGGER_BUTTON} { position: fixed; bottom: ${C.triggerBottomOffset}; z-index: 10000; width: 44px; height: 44px; background-color: var(--cro-header-bg); color: var(--cro-icon-color); border: 1px solid var(--cro-border); border-radius: 50%; cursor: grab; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 10px rgba(0,0,0,0.3); transition: transform 0.2s ease-out, background-color 0.2s; }
          #${C.ids.TRIGGER_BUTTON}:active { cursor: grabbing; }
          #${C.ids.TRIGGER_BUTTON}:hover { background-color: var(--cro-btn-hover-bg); transform: scale(1.1); }
          #${C.ids.TRIGGER_BUTTON} svg { stroke: var(--cro-icon-color); pointer-events: none; }
          #${C.ids.HELPER_CONTAINER} { position: fixed; bottom: ${C.popoverBottomOffset}; z-index: 10001; background-color: var(--cro-bg); border: 1px solid var(--cro-border); border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.4); display: flex; flex-direction: column; transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out; opacity: 0; transform: translateY(20px) scale(0.95); pointer-events: none; overflow: hidden; resize: none; }
          #${C.ids.HELPER_CONTAINER}.${C.classNames.VISIBLE} { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
          #${C.ids.HELPER_CONTAINER} > div:nth-of-type(2) { display: flex; flex-grow: 1; overflow: hidden; /* This is the bodyContainer */ }
          #${C.ids.RESIZE_TL}, #${C.ids.RESIZE_TR}, #${C.ids.RESIZE_TOP} { position: absolute; background: transparent; z-index: 10; }
          #${C.ids.RESIZE_TL}, #${C.ids.RESIZE_TR} { top: 0; width: ${C.cornerHandleSize}; height: ${C.cornerHandleSize}; }
          #${C.ids.RESIZE_TL} { left: 0; cursor: nwse-resize; }
          #${C.ids.RESIZE_TR} { right: 0; cursor: nesw-resize; }
          #${C.ids.RESIZE_TOP} { top: 0; left: ${C.cornerHandleSize}; right: ${C.cornerHandleSize}; height: ${C.edgeHandleThickness}; cursor: ns-resize; }
          #${C.ids.HEADER} { position: relative; display: flex; justify-content: space-between; align-items: center; padding: 6px ${C.cornerHandleSize} 6px ${C.cornerHandleSize}; background-color: var(--cro-header-bg); color: var(--cro-text); font-weight: 500; font-size: 14px; border-bottom: 1px solid var(--cro-border); flex-shrink: 0; cursor: default; /* Default for header, handles override for dragging */ }
          #${C.ids.HEADER} span { flex-grow: 1; text-align: center; margin: 0 5px; /* For title */ }
          #${C.ids.CLOSE_BUTTON} { background: none; border: none; padding: 4px; margin-left: 5px; cursor: pointer; color: var(--cro-icon-color); border-radius: 4px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; z-index: 5; /* Ensure clickable over handles */ position: relative; }
          #${C.ids.CLOSE_BUTTON}:hover { background-color: var(--cro-btn-hover-bg); }
          #${C.ids.CLOSE_BUTTON} svg { width: 16px; height: 16px; stroke: var(--cro-icon-color); }
          #${C.ids.TOOLBAR} { width: ${C.TOOLBAR_WIDTH}px; flex-shrink: 0; background-color: var(--cro-header-bg); border-right: 1px solid var(--cro-border); display: flex; flex-direction: column; align-items: center; padding-top: 10px; gap: 10px; overflow-y: auto; scrollbar-width: none; /* Firefox */ }
          #${C.ids.TOOLBAR}::-webkit-scrollbar { display: none; /* Chrome, Safari, Opera */ }
          .${C.classNames.TOOLBAR_BUTTON} { background: none; border: none; padding: 8px; width: calc(100% - 10px); max-width: 40px; height: 40px; border-radius: 4px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background-color 0.2s; color: var(--cro-icon-color); margin: 0 5px; }
          .${C.classNames.TOOLBAR_BUTTON} svg { stroke: var(--cro-icon-color); width: 20px; height: 20px; transition: stroke 0.2s; }
          .${C.classNames.TOOLBAR_BUTTON}:hover { background-color: var(--cro-btn-hover-bg); }
          .${C.classNames.TOOLBAR_BUTTON}.active { background-color: var(--cro-toolbar-active-bg); }
          .${C.classNames.TOOLBAR_BUTTON}.active svg { stroke: var(--cro-toolbar-active-icon); }
          #${C.ids.MAIN_CONTENT_AREA} { flex-grow: 1; overflow-y: auto; overflow-x: hidden; position: relative; /* For absolute positioned children if any */ scrollbar-width: thin; scrollbar-color: var(--cro-scrollbar-thumb) var(--cro-scrollbar-track); }
          #${C.ids.MAIN_CONTENT_AREA}::-webkit-scrollbar { width: 8px; }
          #${C.ids.MAIN_CONTENT_AREA}::-webkit-scrollbar-track { background: var(--cro-scrollbar-track); }
          #${C.ids.MAIN_CONTENT_AREA}::-webkit-scrollbar-thumb { background-color: var(--cro-scrollbar-thumb); border-radius: 4px; border: 2px solid var(--cro-scrollbar-track); }
          #${C.ids.MAIN_CONTENT_AREA}::-webkit-scrollbar-thumb:hover { background-color: var(--cro-scrollbar-thumb-hover); }
          .${C.classNames.SCREEN_CONTENT_WRAPPER} { padding: 10px 15px; display: flex; flex-direction: column; gap: 0px; /* Default, specific screens might override */ }
          .${C.classNames.SCREEN_TITLE} { margin: 0 0 10px 0; padding-bottom: 5px; font-size: 16px; font-weight: 500; color: var(--cro-text); border-bottom: 1px solid var(--cro-border); }
          .${C.classNames.SCREEN_SUBHEADING} { margin: 10px 0 5px 0; font-size: 14px; font-weight: 500; color: var(--cro-text); }
          .${C.classNames.SCREEN_DESCRIPTION} { font-size: 13px; color: var(--cro-text); margin: 5px 0 15px 0; line-height: 1.4; }
          .${C.classNames.SLIDER_GROUP} { flex-wrap: nowrap; /* Ensure slider and value stay on one line */ }
          .${C.classNames.SLIDER} { flex-grow: 1; height: 18px; cursor: pointer; margin: 0 5px; -webkit-appearance: none; appearance: none; background: var(--cro-input-border); border-radius: 3px; outline-color: var(--cro-focus-border); }
          .${C.classNames.SLIDER}::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 14px; height: 14px; background: var(--cro-icon-color); border-radius: 50%; cursor: pointer; border: 1px solid var(--cro-border); }
          .${C.classNames.SLIDER}::-moz-range-thumb { width: 14px; height: 14px; background: var(--cro-icon-color); border-radius: 50%; cursor: pointer; border: 1px solid var(--cro-border); }
          .${C.classNames.SLIDER_VALUE} { font-size: 13px; min-width: 2.5em; text-align: right; color: var(--cro-text); font-weight: 500; }
          .cro-divider { border: none; height: 1px; background-color: var(--cro-border); margin: 12px 0; flex-shrink: 0; }
          .${C.classNames.INPUT_GROUP} { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; /* Allow wrapping for smaller widths */ margin-bottom: 8px; }
          .${C.classNames.INPUT_GROUP}.cro-full-width { flex-wrap: nowrap; align-items: flex-start; /* For textareas */ }
          .${C.classNames.INPUT_GROUP}.cro-full-width label { margin-top: 5px; /* Align label with textarea top */ }
          .${C.classNames.INPUT_GROUP} label { font-size: 13px; color: var(--cro-text); width: 75px; /* Fixed width for label alignment */ text-align: right; flex-shrink: 0; }
          .${C.classNames.INPUT} { flex-grow: 1; padding: 5px 8px; background-color: var(--cro-input-bg); color: var(--cro-text); border: 1px solid var(--cro-input-border); border-radius: 4px; font-size: 13px; outline: none; min-width: 60px; /* Prevent inputs from becoming too small */ transition: border-color 0.2s; font-family: inherit; }
          .${C.classNames.INPUT}.${C.classNames.INPUT_ERROR} { border-color: var(--cro-error-border-color) !important; box-shadow: 0 0 0 1px var(--cro-error-border-color) !important; }
          .${C.classNames.INPUT_DIALOGUE}, .${C.classNames.INPUT_ACTION_DESC} { resize: vertical; min-height: 40px; /* Taller textareas */ flex-basis: 100%; background-color: var(--cro-input-bg) !important; /* Ensure consistent bg */ }
          .${C.classNames.INPUT_NUMBER} { width: 60px; flex-grow: 0; text-align: center; -moz-appearance: textfield; /* Firefox */ }
          .${C.classNames.INPUT_NUMBER}::-webkit-outer-spin-button, .${C.classNames.INPUT_NUMBER}::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; /* Chrome, Safari, Edge */ }
          .${C.classNames.INPUT_SELECT} { min-width: 120px; appearance: none; background-image: url('data:image/svg+xml;utf8,<svg fill="${encodeURIComponent(window.CRO_State.editorColors.text || '#e0e0e0')}" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M7 10l5 5 5-5z"/><path d="M0 0h24v24H0z" fill="none"/></svg>'); background-repeat: no-repeat; background-position: right 8px center; padding-right: 30px; }
          .${C.classNames.INPUT}:focus { border-color: var(--cro-focus-border) !important; box-shadow: 0 0 0 1px var(--cro-focus-border); animation: none !important; /* Stop dice roll animation on focus */ }
          .${C.classNames.INPUT_WRAPPER} { display: flex; position: relative; align-items: center; flex-grow: 1; }
          .${C.classNames.INPUT_ARROW} { position: absolute; right: 5px; top: 50%; transform: translateY(-50%); font-size: 10px; color: var(--cro-icon-color); pointer-events: none; }
          .${C.classNames.INPUT_WITH_BUTTON} { display: flex; align-items: center; gap: 5px; flex-grow: 1; }
          .${C.classNames.INPUT_WITH_BUTTON} .${C.classNames.INPUT_NUMBER} { flex-grow: 1; }
          .${C.classNames.ROLL_BUTTON} { padding: 0; font-size: 14px; background-color: var(--cro-btn-bg); color: var(--cro-btn-text); border: 1px solid var(--cro-border); border-radius: 4px; cursor: pointer; transition: background-color 0.2s, opacity 0.2s; line-height: 1; flex-shrink: 0; width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center; }
          .${C.classNames.ROLL_BUTTON}:disabled { opacity: 0.5; cursor: not-allowed; background-color: var(--cro-btn-bg); }
          .${C.classNames.ROLL_BUTTON}:disabled svg { stroke: var(--cro-icon-color); }
          .${C.classNames.ROLL_BUTTON} svg { width: 16px; height: 16px; stroke: var(--cro-icon-color); }
          .${C.classNames.ROLL_BUTTON}:hover:not(:disabled) { background-color: var(--cro-btn-hover-bg); }
          .${C.classNames.BUTTON} { padding: 6px 12px; font-size: 13px; background-color: var(--cro-btn-bg); color: var(--cro-btn-text); border: 1px solid var(--cro-border); border-radius: 4px; cursor: pointer; transition: background-color 0.2s; align-self: flex-start; margin-top: 5px; }
          .${C.classNames.BUTTON}:hover { background-color: var(--cro-btn-hover-bg); }
          .${C.classNames.OUTPUT_CONTAINER} { display: flex; flex-direction: column; margin-top: 8px; }
          .${C.classNames.ERROR_MESSAGE} { color: var(--cro-error-text-color); font-size: 12px; padding: 4px 0; margin-bottom: 4px; text-align: center; }
          .${C.classNames.OUTPUT_AREA} { width: 100%; box-sizing: border-box; padding: 8px; border: 1px solid var(--cro-border); border-radius: 4px; background-color: var(--cro-output-bg) !important; color: var(--cro-text); font-size: 13px; line-height: 1.5; resize: none; white-space: pre-wrap; font-family: inherit; }
          .${C.classNames.OUTPUT_AREA} em { font-style: italic; color: var(--cro-italic-color); }
          .${C.classNames.OUTPUT_BUTTONS_GROUP} { display: flex; justify-content: flex-end; gap: 5px; margin-top: 5px; }
          .${C.classNames.COPY_BUTTON}, .${C.classNames.SEND_BUTTON} { background-color: transparent; border: none; padding: 4px; width: 28px; height: 28px; border-radius: 4px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; transition: background-color 0.2s, opacity 0.2s; }
          .${C.classNames.COPY_BUTTON} svg, .${C.classNames.SEND_BUTTON} svg { width: 16px; height: 16px; stroke: var(--cro-icon-color); }
          .${C.classNames.COPY_BUTTON}:hover, .${C.classNames.SEND_BUTTON}:hover { background-color: var(--cro-header-bg); }
          .${C.classNames.COPY_BUTTON}:disabled, .${C.classNames.SEND_BUTTON}:disabled { opacity: 0.5; cursor: default; }
          .${C.classNames.COPY_BUTTON}.flash-success { background-color: var(--cro-copy-success-bg) !important; transition: background-color 0.1s ease-out; }
          .${C.classNames.COPY_BUTTON}.flash-success svg { stroke: var(--cro-text) !important; }
          .${C.classNames.ACTION_MODIFIER_CONTAINER} { margin-top: 15px; padding-top: 10px; border-top: 1px dashed var(--cro-border); }
          .${C.classNames.ACTION_MODIFIER_CONTAINER} .${C.classNames.SCREEN_SUBHEADING} { margin-top: 0; margin-bottom: 10px; }
          .${C.classNames.CHECKBOX_LABEL} { display: flex; align-items: center; gap: 6px; font-size: 13px; cursor: pointer; color: var(--cro-text); }
          .${C.classNames.CHECKBOX_LABEL} input[type="checkbox"] { cursor: pointer; accent-color: var(--cro-accent); }
          .cro-settings-section { margin-bottom: 15px; }
          .cro-theme-buttons-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; margin-top: 5px; }
          .${C.classNames.THEME_BUTTON} { padding: 8px 12px; font-size: 13px; border-radius: 4px; cursor: pointer; transition: background-color 0.2s, color 0.2s, border-color 0.2s; border: 1px solid var(--cro-border); background-color: var(--cro-theme-btn-bg); color: var(--cro-btn-text); text-align: center; }
          .${C.classNames.THEME_BUTTON}:hover { background-color: var(--cro-theme-btn-hover-bg); }
          .${C.classNames.THEME_BUTTON}.active { background-color: var(--cro-theme-btn-active-bg) !important; color: var(--cro-theme-btn-active-text) !important; border-color: var(--cro-theme-btn-active-bg) !important; font-weight: bold; }
          /* Hide number input spinners */
          input[type=number]::-webkit-inner-spin-button,
          input[type=number]::-webkit-outer-spin-button {
            -webkit-appearance: none;
            margin: 0;
          }
          input[type=number] {
            -moz-appearance: textfield; /* Firefox */
          }
        `;
      }
    },
    // Creates all core HTML elements for the popover and trigger button.
    createElements: () => {
      const S = window.CRO_State;
      const C = window.CRO_Config;

      S.triggerButton = document.createElement('button');
      S.triggerButton.id = C.ids.TRIGGER_BUTTON;
      S.triggerButton.title = 'Toggle CRO Helper (Drag to move)';
      document.body.appendChild(S.triggerButton); // Icon set by ThemeManager

      S.helperContainer = document.createElement('div');
      S.helperContainer.id = C.ids.HELPER_CONTAINER;
      S.helperContainer.classList.add(C.classNames.HIDDEN);

      S.header = document.createElement('div');
      S.header.id = C.ids.HEADER;

      // Resize handles
      S.resizeHandleTL = document.createElement('div');
      S.resizeHandleTL.id = C.ids.RESIZE_TL;
      S.resizeHandleTL.title = "Resize (Top-Left)";
      S.header.appendChild(S.resizeHandleTL);

      S.resizeHandleTR = document.createElement('div');
      S.resizeHandleTR.id = C.ids.RESIZE_TR;
      S.resizeHandleTR.title = "Resize (Top-Right)";
      S.header.appendChild(S.resizeHandleTR);

      S.resizeHandleTop = document.createElement('div');
      S.resizeHandleTop.id = C.ids.RESIZE_TOP;
      S.resizeHandleTop.title = "Resize (Top Edge)";
      S.header.appendChild(S.resizeHandleTop);

      const titleSpan = document.createElement('span');
      titleSpan.textContent = 'CRO Helper';
      // titleSpan.style.textAlign = 'center'; // Centered by flex in header
      // titleSpan.style.flexGrow = '1';

      S.closeButton = document.createElement('button');
      S.closeButton.id = C.ids.CLOSE_BUTTON;
      S.closeButton.title = 'Close CRO Helper';
      S.header.appendChild(titleSpan);
      S.header.appendChild(S.closeButton); // Icon set by ThemeManager

      // Main body container for toolbar and content area
      const bodyContainer = document.createElement('div');
      // Styles applied directly or via CSS: display: flex; flex-grow: 1; overflow: hidden;

      S.toolbar = document.createElement('div');
      S.toolbar.id = C.ids.TOOLBAR;

      S.mainContentArea = document.createElement('div');
      S.mainContentArea.id = C.ids.MAIN_CONTENT_AREA;

      bodyContainer.appendChild(S.toolbar);
      bodyContainer.appendChild(S.mainContentArea);

      S.helperContainer.appendChild(S.header);
      S.helperContainer.appendChild(bodyContainer);
      document.body.appendChild(S.helperContainer);
    },
    // Injects all necessary CSS into the page.
    injectStyles: () => {
      GM_addStyle(window.CRO_UI.StyleSheets.getBaseStyles());
      GM_addStyle(window.CRO_UI.StyleSheets.getComponentStyles());
      GM_addStyle(window.CRO_UI.StyleSheets.getAnimationStyles());
    },
    Toolbar: {
      // Renders the toolbar buttons based on configured screens.
      render: () => {
        const S = window.CRO_State;
        const C = window.CRO_Config;
        if (!S.toolbar) return;
        S.toolbar.innerHTML = ''; // Clear existing buttons

        C.screens.forEach(screen => {
          const button = document.createElement('button');
          button.classList.add(C.classNames.TOOLBAR_BUTTON);
          button.dataset.screenId = screen.id;
          button.title = screen.label;

          const isActive = screen.id === S.activeScreenId;
          button.innerHTML = screen.iconSVG(isActive); // Get themed icon
          button.classList.toggle('active', isActive);

          window.CRO_UI.EventManager.addListener(button, 'click', () => window.CRO_UI.Screens.switchScreen(screen.id));
          S.toolbar.appendChild(button);
        });
      },
      // Handles mouse wheel scrolling over the toolbar to switch screens.
      handleWheelScroll: (event) => {
        const S = window.CRO_State;
        const C = window.CRO_Config;
        if (!S.toolbar.contains(event.target)) return; // Only act if scrolling over toolbar
        event.preventDefault();
        event.stopPropagation();

        const currentIndex = C.screens.findIndex(screen => screen.id === S.activeScreenId);
        if (currentIndex === -1) return; // Should not happen

        let nextIndex = currentIndex + (event.deltaY > 0 ? 1 : (event.deltaY < 0 ? -1 : 0));
        nextIndex = Math.max(0, Math.min(nextIndex, C.screens.length - 1)); // Clamp index

        if (nextIndex !== currentIndex) {
          window.CRO_UI.Screens.switchScreen(C.screens[nextIndex].id);
        }
      }
    },
    Screens: {
      // Helper to create the basic structure for a screen (title, description).
      _createScreenContainer: (targetContainer, title, description) => {
        const C = window.CRO_Config;
        targetContainer.innerHTML = ''; // Clear previous content

        const contentDiv = document.createElement('div');
        contentDiv.classList.add(C.classNames.SCREEN_CONTENT_WRAPPER);
        targetContainer.appendChild(contentDiv);

        const titleElement = document.createElement('h5');
        titleElement.textContent = title;
        titleElement.classList.add(C.classNames.SCREEN_TITLE);
        contentDiv.appendChild(titleElement);

        if (description) {
          const descElement = document.createElement('p');
          descElement.textContent = description;
          descElement.classList.add(C.classNames.SCREEN_DESCRIPTION);
          contentDiv.appendChild(descElement);
        }
        return contentDiv; // Return the direct parent for adding more elements
      },
      // Adds primary input fields (Dialogue, Action, Skill) to a screen.
      _addPrimaryInputFields: (contentDiv, elements, skillDatalistId) => {
        const C = window.CRO_Config;
        const Utils = window.CRO_Utils;

        // Dialogue input (textarea)
        const dialogueGroupConfig = {
          elementType: 'textarea',
          attributes: { placeholder: "(Optional) Character's spoken words", rows: 2 }, // Textarea height
          customClass: C.classNames.INPUT_DIALOGUE,
          fullWidth: true
        };
        const dialogueElements = Utils.createInputGroup('Dialogue:', 'cro-helper-dialogue', dialogueGroupConfig);
        elements.dialogueInput = dialogueElements.input;
        contentDiv.appendChild(dialogueElements.group);

        // Action Description input (textarea)
        const actionDescGroupConfig = {
          elementType: 'textarea',
          attributes: { placeholder: "e.g., pick the lock", rows: 2 }, // Textarea height
          customClass: C.classNames.INPUT_ACTION_DESC,
          fullWidth: true
        };
        const actionDescElements = Utils.createInputGroup('Action:', 'cro-helper-action', actionDescGroupConfig);
        elements.actionDescInput = actionDescElements.input;
        contentDiv.appendChild(actionDescElements.group);

        // Skill input (text input with datalist for suggestions)
        const skillGroup = document.createElement('div');
        skillGroup.classList.add(C.classNames.INPUT_GROUP);
        const skillInputLabel = document.createElement('label');
        skillInputLabel.textContent = 'Skill:';
        skillInputLabel.htmlFor = `cro-helper-skill`;

        const skillInputWrapper = document.createElement('div');
        skillInputWrapper.classList.add(C.classNames.INPUT_WRAPPER);
        elements.skillInput = document.createElement('input');
        elements.skillInput.type = 'text';
        elements.skillInput.id = `cro-helper-skill`;
        elements.skillInput.placeholder = 'e.g., SOCIAL, MANUAL';
        elements.skillInput.classList.add(C.classNames.INPUT);
        elements.skillInput.setAttribute('list', skillDatalistId);

        const skillArrow = document.createElement('span'); // Decorative arrow for select-like appearance
        skillArrow.classList.add(C.classNames.INPUT_ARROW);
        skillArrow.innerHTML = '▾';

        const skillDataList = document.createElement('datalist');
        skillDataList.id = skillDatalistId;
        const skillSuggestions = new Set([ // Common skill examples
          'PHYSICAL', 'ATHLETICS', 'STRENGTH', 'MANUAL', 'DEXTERITY', 'CRAFTING',
          'MENTAL', 'INTELLIGENCE', 'KNOWLEDGE', 'PROBLEM-SOLVING', 'STRATEGY',
          'SOCIAL', 'PERSUASION', 'DECEPTION', 'INTIMIDATION', 'CHARM',
          'PERCEPTION', 'AWARENESS', 'INVESTIGATION', 'FORTUNE', 'LUCK',
          'STEALTH', 'SNEAKING', 'HIDING'
        ]);
        // Add all defined scale names and keys as suggestions
        Object.keys(C.croScales).forEach(key => {
          if (C.croScales[key] && C.croScales[key].name) {
            skillSuggestions.add(C.croScales[key].name.toUpperCase().replace(/ /g, '_'));
            skillSuggestions.add(key.toUpperCase());
          }
        });
        Array.from(skillSuggestions).sort().forEach(skill => {
          const option = document.createElement('option');
          option.value = skill;
          skillDataList.appendChild(option);
        });

        skillInputWrapper.appendChild(elements.skillInput);
        skillInputWrapper.appendChild(skillArrow);
        skillGroup.appendChild(skillInputLabel);
        skillGroup.appendChild(skillInputWrapper);
        skillGroup.appendChild(skillDataList); // Datalist must be appended to the document
        contentDiv.appendChild(skillGroup);
      },
      // Adds Modifier slider, Raw Roll input (with dice button), and Scale select dropdown.
      _addRollAndScaleControls: (contentDiv, elements) => {
        const C = window.CRO_Config;
        const Utils = window.CRO_Utils;

        // Modifier Slider
        const modifierSliderElements = Utils.createSliderGroup(
          'Modifier:', 'cro-helper-modifier', 'cro-helper-modifier-value',
          { min: C.validation.MIN_MODIFIER, max: C.validation.MAX_MODIFIER, value: 0, title: 'Adjust difficulty modifier.' }
        );
        elements.modifierSlider = modifierSliderElements.slider;
        contentDiv.appendChild(modifierSliderElements.group);

        // Raw Roll Input with Dice Button
        const resultGroup = document.createElement('div');
        resultGroup.classList.add(C.classNames.INPUT_GROUP);
        const resultInputLabel = document.createElement('label');
        resultInputLabel.textContent = 'Raw Roll:';
        resultInputLabel.htmlFor = `cro-helper-result`;

        const resultInputWrapper = document.createElement('div');
        resultInputWrapper.classList.add(C.classNames.INPUT_WITH_BUTTON);
        elements.resultInput = document.createElement('input');
        elements.resultInput.type = 'number';
        elements.resultInput.id = `cro-helper-result`;
        elements.resultInput.min = C.validation.MIN_ROLL;
        elements.resultInput.max = C.validation.MAX_ROLL;
        elements.resultInput.step = '1';
        elements.resultInput.placeholder = `${C.validation.MIN_ROLL}-${C.validation.MAX_ROLL}`;
        elements.resultInput.classList.add(C.classNames.INPUT, C.classNames.INPUT_NUMBER);

        elements.rollButtonAction = document.createElement('button');
        elements.rollButtonAction.innerHTML = C.diceIconSVG(16); // Themed icon
        elements.rollButtonAction.classList.add(C.classNames.ROLL_BUTTON);

        resultInputWrapper.appendChild(elements.resultInput);
        resultInputWrapper.appendChild(elements.rollButtonAction);
        resultGroup.appendChild(resultInputLabel);
        resultGroup.appendChild(resultInputWrapper);
        contentDiv.appendChild(resultGroup);

        // Scale Select Dropdown
        const scaleGroupConfig = { elementType: 'select', customClass: C.classNames.INPUT_SELECT };
        const scaleElements = Utils.createInputGroup('Scale:', 'cro-helper-scale', scaleGroupConfig);
        elements.scaleSelect = scaleElements.input;
        const scalesToShow = {...C.croScales}; // Make a copy to potentially modify
        if (C.croScales.itemFocus) scalesToShow.itemFocus = C.croScales.itemFocus; // Ensure itemFocus is included

        for (const scaleKey in scalesToShow) {
          if (Object.prototype.hasOwnProperty.call(scalesToShow, scaleKey) && scaleKey !== "timeAndFortune") { // Exclude timeAndFortune for now
            const option = document.createElement('option');
            option.value = scaleKey;
            option.textContent = scalesToShow[scaleKey].name;
            elements.scaleSelect.appendChild(option);
          }
        }
        contentDiv.appendChild(scaleElements.group);
      },
      // Adds the output textarea and Copy/Send buttons.
      _addOutputAreaAndActions: (contentDiv, elements) => {
        const C = window.CRO_Config;
        elements.generateActionButton = document.createElement('button');
        elements.generateActionButton.textContent = 'Generate Action Roll';
        elements.generateActionButton.classList.add(C.classNames.BUTTON);
        contentDiv.appendChild(elements.generateActionButton);

        const actionOutputContainer = document.createElement('div');
        actionOutputContainer.classList.add(C.classNames.OUTPUT_CONTAINER);

        elements.errorDisplay = document.createElement('div');
        elements.errorDisplay.id = C.ids.ACTION_ERROR_MESSAGE;
        elements.errorDisplay.classList.add(C.classNames.ERROR_MESSAGE);
        elements.errorDisplay.style.display = 'none'; // Initially hidden
        actionOutputContainer.appendChild(elements.errorDisplay);

        elements.actionOutputArea = document.createElement('textarea');
        elements.actionOutputArea.id = C.ids.OUTPUT_ACTION;
        elements.actionOutputArea.readOnly = true;
        elements.actionOutputArea.rows = 5; // Default height
        elements.actionOutputArea.classList.add(C.classNames.OUTPUT_AREA);

        const actionButtonsGroup = document.createElement('div');
        actionButtonsGroup.classList.add(C.classNames.OUTPUT_BUTTONS_GROUP);

        elements.copyActionButton = document.createElement('button');
        elements.copyActionButton.innerHTML = C.copyIconSVG(); // Themed icon
        elements.copyActionButton.title = 'Copy Action Roll Text';
        elements.copyActionButton.classList.add(C.classNames.COPY_BUTTON);
        elements.copyActionButton.style.display = 'none'; // Hidden until output is generated

        elements.sendActionButton = document.createElement('button');
        elements.sendActionButton.innerHTML = C.sendIconSVG(); // Themed icon
        elements.sendActionButton.title = 'Send Action Roll to Chat Input';
        elements.sendActionButton.classList.add(C.classNames.SEND_BUTTON);
        elements.sendActionButton.style.display = 'none'; // Hidden until output is generated

        actionButtonsGroup.appendChild(elements.copyActionButton);
        actionButtonsGroup.appendChild(elements.sendActionButton);
        actionOutputContainer.appendChild(elements.actionOutputArea);
        actionOutputContainer.appendChild(actionButtonsGroup);
        contentDiv.appendChild(actionOutputContainer);
      },
      // Adds modifier controls (Stakes slider, Pushing Limits checkbox, Guarded Approach checkbox).
      _addActionModifiers: (contentDiv, elements) => {
        const C = window.CRO_Config;
        const Utils = window.CRO_Utils;

        const modifierContainer = document.createElement('div');
        modifierContainer.classList.add(C.classNames.ACTION_MODIFIER_CONTAINER);

        const modifierTitle = document.createElement('h6');
        modifierTitle.textContent = "Action Modifiers";
        modifierTitle.classList.add(C.classNames.SCREEN_SUBHEADING);
        modifierContainer.appendChild(modifierTitle);

        // Stakes Slider
        const stakesSliderElements = Utils.createSliderGroup(
          'Stakes:', `cro-helper-stakes`, `cro-helper-stakes-value`,
          { min: C.validation.MIN_STAKES, max: C.validation.MAX_STAKES, value: 0, title: 'Adjust stakes: Higher risk for higher reward/harsher failure.' }
        );
        elements.stakesSlider = stakesSliderElements.slider;
        modifierContainer.appendChild(stakesSliderElements.group);

        // Checkboxes for Pushing Limits and Guarded Approach
        const checkboxContainer = document.createElement('div');
        checkboxContainer.style.cssText = 'display: flex; flex-direction: column; gap: 5px; margin-top: 8px;';

        const pushElements = Utils.createCheckbox('cro-push-limit', 'Pushing Limits', 'Attempt action beyond normal capabilities with harsher failure scale.');
        elements.pushCheck = pushElements.checkbox;
        checkboxContainer.appendChild(pushElements.label);

        const guardElements = Utils.createCheckbox('cro-guard-approach', 'Guarded Approach', 'Minimize critical failure (roll 1 becomes 2) at cost of critical success (roll 10 becomes 9).');
        elements.guardCheck = guardElements.checkbox;
        checkboxContainer.appendChild(guardElements.label);

        modifierContainer.appendChild(checkboxContainer);
        contentDiv.appendChild(modifierContainer);
      },
      // Binds event listeners for the Action Roller screen's interactive elements.
      _bindActionRollerEvents: (elements) => {
        const C = window.CRO_Config;
        const UI = window.CRO_UI;
        const ErrorHandler = window.CRO_ErrorHandler;

        // Auto-select scale based on skill input
        UI.EventManager.addListener(elements.skillInput, 'input', () => {
          const skillValue = elements.skillInput.value.trim().toLowerCase();
          let matchedScaleKey = null;
          for (const scaleKey in C.croScales) {
            if (Object.prototype.hasOwnProperty.call(C.croScales, scaleKey)) { // Ensure it's an own property
              // Check against scale name (e.g., "Main Outcome") and scale key (e.g., "main")
              if (C.croScales[scaleKey].name.toLowerCase() === skillValue || scaleKey.toLowerCase() === skillValue) {
                matchedScaleKey = scaleKey;
                break;
              }
            }
          }
          if (matchedScaleKey && elements.scaleSelect.value !== matchedScaleKey) {
            elements.scaleSelect.value = matchedScaleKey;
          }
        });

        // Dice roll button
        UI.EventManager.addListener(elements.rollButtonAction, 'click', () => {
          const finalRoll = Math.floor(Math.random() * C.validation.DICE_FACES) + 1;
          UI.Interactions.animateDiceRoll(elements.resultInput, elements.rollButtonAction, finalRoll);
        });

        // Generate Action Roll button
        const generateActionHandler = () => {
          let errors = [];
          let fieldsToHighlight = [];
          const dialogueDesc = elements.dialogueInput.value.trim();
          const actionDescRaw = elements.actionDescInput.value.trim();

          // Validate that either dialogue or action description is provided
          if (!dialogueDesc && !actionDescRaw) {
            errors.push("Dialogue or Action description is required.");
            fieldsToHighlight.push(elements.dialogueInput, elements.actionDescInput);
          }

          // Auto-roll if result is empty
          if (elements.resultInput.value.trim() === '') {
            elements.resultInput.value = Math.floor(Math.random() * C.validation.DICE_FACES) + 1;
          }

          // Validate raw roll input
          const rollValidation = ErrorHandler.validateInput(elements.resultInput.value, 'rollValue', { min: C.validation.MIN_ROLL, max: C.validation.MAX_ROLL });
          if (!rollValidation.isValid) {
            errors.push(...rollValidation.errors);
            fieldsToHighlight.push(elements.resultInput);
          }

          // If errors, display them and stop
          if (errors.length > 0) {
            UI.Interactions.displayActionError(errors.join(' '), fieldsToHighlight);
            elements.actionOutputArea.value = ''; // Clear output
            elements.copyActionButton.style.display = 'none';
            elements.sendActionButton.style.display = 'none';
            return;
          }

          // Clear previous errors if any
          if (elements.errorDisplay) elements.errorDisplay.style.display = 'none';
          document.querySelectorAll(`.${C.classNames.INPUT_ERROR}`).forEach(el => el.classList.remove(C.classNames.INPUT_ERROR));

          // Gather values for calculation
          const skill = elements.skillInput.value.trim().toUpperCase() || 'ABILITY ROLL'; // Default if empty
          let rawDieRoll = parseInt(elements.resultInput.value, 10);
          const difficultyModifier = parseInt(elements.modifierSlider.value, 10);
          const selectedScaleKey = elements.scaleSelect.value;
          const stakesValue = parseInt(elements.stakesSlider.value, 10);
          const isPushingLimits = elements.pushCheck.checked;
          const isGuarded = elements.guardCheck.checked;

          // Calculate result
          let resultAfterDifficulty = rawDieRoll + difficultyModifier;
          let resultBeforeStakes = resultAfterDifficulty;

          // Apply Guarded Approach: adjust result before stakes, only if it's an extreme roll
          if (isGuarded) {
            if (resultAfterDifficulty <= C.validation.MIN_ROLL) { // Handle modified rolls that go to 1 or less
                resultBeforeStakes = C.validation.MIN_ROLL + 1;
            } else if (resultAfterDifficulty >= C.validation.MAX_ROLL) { // Handle modified rolls that go to 10 or more
                resultBeforeStakes = C.validation.MAX_ROLL - 1;
            }
            // If resultAfterDifficulty is between MIN_ROLL+1 and MAX_ROLL-1, resultBeforeStakes correctly holds its value.
          }

          let finalResult = resultBeforeStakes; // Start with the (potentially) guarded result

          // Apply Stakes: based on the raw d10 roll
          if (stakesValue !== 0) {
            if (rawDieRoll < 5) { // Check rawDieRoll, not modified roll
              finalResult = resultBeforeStakes - stakesValue;
            } else { // rawDieRoll >= 5
              finalResult = resultBeforeStakes + stakesValue;
            }
          }

          // Clamp final result to be within 1-10 range
          finalResult = Math.max(C.validation.MIN_ROLL, Math.min(C.validation.MAX_ROLL, finalResult));

          const outcomeDesc = C.getScaleOutcome(selectedScaleKey, finalResult, isPushingLimits);

          // Format output string
          let outputString = "";
          if (dialogueDesc) {
            outputString += (dialogueDesc.startsWith('"') && dialogueDesc.endsWith('"')) ? `${dialogueDesc}\n` : `"${dialogueDesc}"\n`;
          }
          if (actionDescRaw) {
            outputString += (actionDescRaw.startsWith('*') && actionDescRaw.endsWith('*')) ? `${actionDescRaw}\n` : `*${actionDescRaw.replace(/^\*|\*$/g, '')}*\n`;
          }
          outputString += `**${skill} ROLL**\n*${finalResult}/${C.validation.DICE_FACES} - ${outcomeDesc}*`;

          elements.actionOutputArea.value = outputString.trim();
          elements.copyActionButton.style.display = 'inline-flex';
          elements.sendActionButton.style.display = 'inline-flex';
        };
        UI.EventManager.addListener(elements.generateActionButton, 'click', ErrorHandler.withErrorBoundary(generateActionHandler, null, 'GenerateActionRoll'));

        // Copy and Send button listeners
        UI.EventManager.addListener(elements.copyActionButton, 'click', () => UI.Interactions.handleCopy(elements.actionOutputArea.value, elements.copyActionButton));
        UI.EventManager.addListener(elements.sendActionButton, 'click', () => UI.Interactions.handleSend(elements.actionOutputArea.value));
      },
      // Switches the displayed screen in the main content area.
      switchScreen: (screenId) => {
        const S = window.CRO_State;
        const C = window.CRO_Config;
        const screenData = C.screens.find(s => s.id === screenId);

        if (!screenData || !S.mainContentArea) {
          console.error(`[CRO Helper] Screen data or main content area not found for ID: ${screenId}`);
          return;
        }

        S.activeScreenId = screenId;

        // Update toolbar button active states and icons
        if (S.toolbar) {
          S.toolbar.querySelectorAll(`.${C.classNames.TOOLBAR_BUTTON}`).forEach(btn => {
            const isActive = btn.dataset.screenId === screenId;
            btn.classList.toggle('active', isActive);
            const btnScreenData = C.screens.find(s => s.id === btn.dataset.screenId);
            if (btnScreenData) btn.innerHTML = btnScreenData.iconSVG(isActive); // Update icon based on active state
          });
        }

        S.mainContentArea.innerHTML = ''; // Clear previous screen content
        screenData.renderFunc(S.mainContentArea); // Call the screen's render function
      },
      // Renders the Action Roller screen.
      renderActionRollerScreen: (targetContainer) => {
        const Screens = window.CRO_UI.Screens;
        const contentDiv = Screens._createScreenContainer(targetContainer, 'Action Roll', 'Generate a formatted roll for character actions based on skill, difficulty, and optional modifiers like Stakes or Pushing Limits.');
        const elements = {}; // To store references to created input elements
        const skillDatalistId = `cro-helper-skill-list-${Date.now()}`; // Unique ID for datalist

        Screens._addPrimaryInputFields(contentDiv, elements, skillDatalistId);
        Screens._addRollAndScaleControls(contentDiv, elements);
        Screens._addOutputAreaAndActions(contentDiv, elements);
        Screens._addActionModifiers(contentDiv, elements); // Add Stakes, Push Limits, Guarded Approach
        Screens._bindActionRollerEvents(elements); // Attach event listeners
      },
      // Renders a generic screen for simple dice rolls (Inventory, Perception).
      renderSimpleRollScreen: (targetContainer, config) => {
        const C = window.CRO_Config;
        const UI = window.CRO_UI;
        targetContainer.innerHTML = ''; // Clear previous content

        const contentDiv = UI.Screens._createScreenContainer(targetContainer, config.title, config.description);
        const outputId = `cro-helper-output-${config.id}`;

        const outputSection = document.createElement('div');
        // Structure for button, output area, and copy/send buttons
        outputSection.innerHTML = `
          <button class="${C.classNames.BUTTON}" id="cro-roll-${config.id}-btn" style="align-self: center;">${config.buttonText}</button>
          <div class="${C.classNames.OUTPUT_CONTAINER}" style="margin-top: 15px;">
            <textarea id="${outputId}" class="${C.classNames.OUTPUT_AREA}" readonly rows="3"></textarea>
            <div class="${C.classNames.OUTPUT_BUTTONS_GROUP}">
              <button id="cro-copy-${config.id}-btn" class="${C.classNames.COPY_BUTTON}" style="display: none;" title="Copy ${config.title} Text">${C.copyIconSVG()}</button>
              <button id="cro-send-${config.id}-btn" class="${C.classNames.SEND_BUTTON}" style="display: none;" title="Send ${config.title} to Chat">${C.sendIconSVG()}</button>
            </div>
          </div>
        `;
        contentDiv.appendChild(outputSection);

        // Get references to the newly created elements
        const rollButton = contentDiv.querySelector(`#cro-roll-${config.id}-btn`);
        const outputArea = contentDiv.querySelector(`#${outputId}`);
        const copyButton = contentDiv.querySelector(`#cro-copy-${config.id}-btn`);
        const sendButton = contentDiv.querySelector(`#cro-send-${config.id}-btn`);

        // Add event listener for the roll button
        UI.EventManager.addListener(rollButton, 'click', () => {
          const rollResult = Math.floor(Math.random() * C.validation.DICE_FACES) + 1;
          outputArea.value = config.generateOutput(rollResult);
          copyButton.style.display = 'inline-flex'; // Show copy/send buttons
          sendButton.style.display = 'inline-flex';
        });
        // Add event listeners for copy and send buttons
        UI.EventManager.addListener(copyButton, 'click', () => UI.Interactions.handleCopy(outputArea.value, copyButton));
        UI.EventManager.addListener(sendButton, 'click', () => UI.Interactions.handleSend(outputArea.value));
      },
      // Renders the Inventory Check screen.
      renderInventoryCheckScreen: (targetContainer) => {
        window.CRO_UI.Screens.renderSimpleRollScreen(targetContainer, {
          id: 'inventory',
          title: 'Inventory Check',
          description: 'Roll a d10 to see what mundane or useful items your character finds on their person (pockets, bag, etc.) without searching for something specific.',
          buttonText: 'Roll Inventory Check',
          generateOutput: (rollResult) => {
            const C = window.CRO_Config;
            const findDesc = C.inventoryFindScale.outcomes[rollResult] || `Unknown find (${rollResult})`;
            return `**INVENTORY CHECK**\n*${rollResult}/${C.validation.DICE_FACES} - ${findDesc}*`;
          }
        });
      },
      // Renders the Perception Check screen.
      renderPerceptionCheckScreen: (targetContainer) => {
        window.CRO_UI.Screens.renderSimpleRollScreen(targetContainer, {
          id: 'perception',
          title: 'Perception Check',
          description: 'Roll a d10 to determine what your character notices or senses in their environment or about others.',
          buttonText: 'Roll Perception',
          generateOutput: (rollResult) => {
            const C = window.CRO_Config;
            const perceptionDescription = C.getScaleOutcome('perception', rollResult); // Uses the main getScaleOutcome
            return `**PERCEPTION ROLL**\n*${rollResult}/${C.validation.DICE_FACES} - ${perceptionDescription}*`;
          }
        });
      },
      // Renders the Settings screen.
      renderSettingsScreen: (targetContainer) => {
        const C = window.CRO_Config;
        const S = window.CRO_State;
        const UI = window.CRO_UI;
        const contentDiv = UI.Screens._createScreenContainer(targetContainer, 'Settings');

        // Theme selection section
        const themeSection = document.createElement('div');
        themeSection.classList.add('cro-settings-section');
        const themeTitle = document.createElement('h6');
        themeTitle.textContent = 'Theme';
        themeTitle.classList.add(C.classNames.SCREEN_SUBHEADING);
        themeSection.appendChild(themeTitle);

        const themeButtonsContainer = document.createElement('div');
        themeButtonsContainer.classList.add('cro-theme-buttons-container');
        themeButtonsContainer.title = 'Select a visual theme for the helper popover.';

        Object.keys(C.themes).forEach(themeName => {
          const button = document.createElement('button');
          button.textContent = themeName;
          button.classList.add(C.classNames.THEME_BUTTON);
          button.dataset.themeName = themeName;
          button.title = `Apply ${themeName} theme.`;
          button.classList.toggle('active', themeName === S.currentThemeName); // Set active state
          UI.EventManager.addListener(button, 'click', () => {
            window.CRO_ThemeManager.applyAndSaveTheme(themeName);
            // Update active class on all theme buttons
            themeButtonsContainer.querySelectorAll(`.${C.classNames.THEME_BUTTON}`).forEach(btn => {
              btn.classList.toggle('active', btn.dataset.themeName === themeName);
            });
          });
          themeButtonsContainer.appendChild(button);
        });
        themeSection.appendChild(themeButtonsContainer);
        contentDiv.appendChild(themeSection);
      }
    },
    Interactions: {
      // Handles copying text to the clipboard.
      handleCopy: (textToCopy, buttonElement) => {
        if (!textToCopy || !buttonElement || buttonElement.disabled) return;
        try {
          GM_setClipboard(textToCopy);
          buttonElement.classList.add('flash-success');
          buttonElement.disabled = true;
          setTimeout(() => {
            buttonElement.classList.remove('flash-success');
            buttonElement.disabled = false;
          }, 800);
        } catch (err) {
          console.error('[CRO Helper] Failed to copy text: ', err);
          const originalHTML = buttonElement.innerHTML;
          buttonElement.innerHTML = 'Error!';
          buttonElement.disabled = true;
          setTimeout(() => {
            buttonElement.innerHTML = window.CRO_Config.copyIconSVG(); // Restore original icon
            buttonElement.disabled = false;
          }, 2000);
        }
      },
      // Handles sending text to the main chat input of the page.
      handleSend: (textToSend) => {
        const mainInput = window.CRO_ElementCache.getChatInput();
        if (!mainInput || !textToSend) {
          console.warn("[CRO Helper] Chat input not found or text empty.");
          return;
        }
        // Use native setter to ensure framework reactivity if applicable
        const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
        nativeInputValueSetter.call(mainInput, textToSend);
        // Dispatch events to simulate user input
        const inputEvent = new Event('input', { bubbles: true });
        const changeEvent = new Event('change', { bubbles: true });
        mainInput.dispatchEvent(inputEvent);
        mainInput.dispatchEvent(changeEvent);
        mainInput.focus(); // Focus the input after sending
      },
      // Animates the dice roll input field.
      animateDiceRoll: (inputElement, buttonElement, finalRollValue) => {
        const C = window.CRO_Config;
        const S = window.CRO_State;
        if (!inputElement || !buttonElement) return;

        // Clear any existing animation for this element
        if (S.animationFrameIds.has(inputElement)) {
          cancelAnimationFrame(S.animationFrameIds.get(inputElement));
          S.animationFrameIds.delete(inputElement);
        }

        buttonElement.disabled = true; // Disable button during animation
        inputElement.classList.add(C.classNames.DICE_ROLLING); // Add animation class

        const duration = C.animation.DICE_ANIMATION_DURATION;
        const startTime = Date.now();

        const animate = window.CRO_ErrorHandler.withErrorBoundary(() => {
          const elapsed = Date.now() - startTime;
          const progress = Math.min(elapsed / duration, 1);
          const easeOutProgress = 1 - Math.pow(1 - progress, 3); // Ease-out cubic

          // Show random numbers for most of the animation
          if (progress < 0.85) { // Show random numbers for 85% of the duration
            if (Math.random() > easeOutProgress * 0.5) { // Randomly update to make it feel more dynamic
              inputElement.value = Math.floor(Math.random() * C.validation.DICE_FACES) + 1;
            }
          } else {
            // Set the final roll value towards the end
            inputElement.value = finalRollValue;
          }

          if (progress < 1) {
            const animId = requestAnimationFrame(animate);
            S.animationFrameIds.set(inputElement, animId);
          } else {
            // Animation complete
            inputElement.value = finalRollValue;
            inputElement.classList.remove(C.classNames.DICE_ROLLING);
            buttonElement.disabled = false;
            S.animationFrameIds.delete(inputElement);
          }
        }, null, 'DiceRollAnimation');

        const initialAnimId = requestAnimationFrame(animate);
        S.animationFrameIds.set(inputElement, initialAnimId);
      },
      // Displays an error message related to action generation.
      displayActionError: (message, fieldsToHighlight = []) => {
        const C = window.CRO_Config;
        const S = window.CRO_State;
        const errorDisplay = document.getElementById(C.ids.ACTION_ERROR_MESSAGE);
        if (!errorDisplay) {
          console.error("[CRO Helper] Error display element not found.");
          return;
        }
        errorDisplay.textContent = message;
        errorDisplay.style.display = 'block';

        // Highlight erroneous input fields
        fieldsToHighlight.forEach(field => {
          if (field) {
            field.classList.add(C.classNames.INPUT_ERROR);
            // Remove error highlight on focus or input
            const clearErrorOnFocus = () => {
              field.classList.remove(C.classNames.INPUT_ERROR);
              field.removeEventListener('focus', clearErrorOnFocus);
              field.removeEventListener('input', clearErrorOnInput);
              // Hide general error message if no other fields are highlighted
              if (!document.querySelector(`.${C.classNames.INPUT_ERROR}`)) {
                errorDisplay.style.display = 'none';
              }
            };
            const clearErrorOnInput = clearErrorOnFocus; // Same logic for input
            field.addEventListener('focus', clearErrorOnFocus);
            field.addEventListener('input', clearErrorOnInput);
          }
        });

        // Auto-hide error message after a duration
        clearTimeout(S.errorTimeout);
        S.errorTimeout = setTimeout(() => {
          errorDisplay.style.display = 'none';
          fieldsToHighlight.forEach(field => field?.classList.remove(C.classNames.INPUT_ERROR));
        }, C.ERROR_MESSAGE_DURATION);
      }
    },
    // Adds all core event listeners for UI interactions (drag, resize, clicks).
    addEventListeners: () => {
      const S = window.CRO_State;
      const C = window.CRO_Config;
      const UI = window.CRO_UI;
      const Persist = window.CRO_Persistence;

      if (!S.triggerButton || !S.helperContainer || !S.closeButton ||
          !S.resizeHandleTL || !S.resizeHandleTR || !S.resizeHandleTop || !S.toolbar) {
        console.error("[CRO Helper] Cannot add listeners, crucial elements missing.");
        return;
      }

      // Toggle popover visibility on trigger button click (if not dragged)
      UI.EventManager.addListener(S.triggerButton, 'click', (e) => {
        if (e.detail === 1 && !S.didDragTrigger) { // Only toggle on single click, not after drag
          S.isHelperVisible = !S.isHelperVisible;
          S.helperContainer.classList.toggle(C.classNames.HIDDEN, !S.isHelperVisible);
          S.helperContainer.classList.toggle(C.classNames.VISIBLE, S.isHelperVisible);
          if (S.isHelperVisible && S.activeScreenId === 'cro_generator') {
            const firstInput = S.helperContainer.querySelector('#cro-helper-dialogue');
            if (firstInput) firstInput.focus();
          }
        }
        S.didDragTrigger = false; // Reset drag flag
      });

      // Close popover
      UI.EventManager.addListener(S.closeButton, 'click', () => {
        S.isHelperVisible = false;
        S.helperContainer.classList.add(C.classNames.HIDDEN);
        S.helperContainer.classList.remove(C.classNames.VISIBLE);
      });

      // Drag trigger button
      UI.EventManager.addListener(S.triggerButton, 'mousedown', (e) => {
        if (e.button !== 0) return; // Only left-click
        S.isDraggingTrigger = true;
        S.didDragTrigger = false; // Reset before potential drag
        S.dragOffsetX = e.clientX - S.triggerButton.offsetLeft;
        document.body.classList.add(C.classNames.DRAGGING_BODY);
        S.triggerButton.style.transition = 'none'; // Disable transitions during drag
        S.helperContainer.style.transition = 'none';
        e.preventDefault();
      });

      // Resize popover using handles
      [S.resizeHandleTL, S.resizeHandleTR, S.resizeHandleTop].forEach(handle => {
        UI.EventManager.addListener(handle, 'mousedown', (e) => {
          if (e.button !== 0) return;
          S.activeResizeHandle = handle.id;
          S.startX = e.clientX; S.startY = e.clientY;
          S.startWidth = S.helperContainer.offsetWidth;
          S.startHeight = S.helperContainer.offsetHeight;
          S.startLeft = S.helperContainer.offsetLeft;
          document.body.classList.add(C.classNames.RESIZING_BODY);
          S.helperContainer.style.transition = 'none';
          e.preventDefault();
          e.stopPropagation(); // Prevent header drag
        });
      });

      // Mousemove for dragging and resizing
      UI.EventManager.addListener(document, 'mousemove', (e) => {
        const bottomOffsetPx = window.CRO_Utils.getPopoverBottomPx();
        const maxH = Math.max(C.minPopoverHeight, window.innerHeight - bottomOffsetPx - 20); // Min 20px from top

        if (S.isDraggingTrigger) {
          S.didDragTrigger = true; // Mark that a drag occurred
          let newLeft = e.clientX - S.dragOffsetX;
          const currentWidth = S.helperContainer.offsetWidth; // Use popover width for boundary calc
          const maxLeft = window.innerWidth - currentWidth - 5; // 5px padding from right edge
          newLeft = Math.max(5, Math.min(newLeft, maxLeft)); // 5px padding from left edge
          S.triggerButton.style.left = `${newLeft}px`;
          S.helperContainer.style.left = `${newLeft}px`; // Keep popover aligned with trigger
        } else if (S.activeResizeHandle) {
          const deltaX = e.clientX - S.startX;
          const deltaY = e.clientY - S.startY;
          let newWidth = S.startWidth;
          let newHeight = S.startHeight;
          let newLeft = S.startLeft;

          if (S.activeResizeHandle === C.ids.RESIZE_TR) { // Top-right handle
            newWidth = Math.max(C.minPopoverWidth, S.startWidth + deltaX);
            newHeight = Math.max(C.minPopoverHeight, Math.min(S.startHeight - deltaY, maxH));
          } else if (S.activeResizeHandle === C.ids.RESIZE_TL) { // Top-left handle
            newWidth = S.startWidth - deltaX;
            newHeight = Math.max(C.minPopoverHeight, Math.min(S.startHeight - deltaY, maxH));
            newLeft = S.startLeft + deltaX; // Adjust left position
            if (newWidth < C.minPopoverWidth) { // Prevent shrinking beyond min width from left
              newLeft = S.startLeft + (S.startWidth - C.minPopoverWidth);
              newWidth = C.minPopoverWidth;
            }
          } else if (S.activeResizeHandle === C.ids.RESIZE_TOP) { // Top edge handle
            newHeight = Math.max(C.minPopoverHeight, Math.min(S.startHeight - deltaY, maxH));
          }

          // Constrain left position if resizing from left
          newLeft = Math.max(5, Math.min(newLeft, window.innerWidth - newWidth - 5));
          if (newLeft !== S.startLeft && (S.activeResizeHandle === C.ids.RESIZE_TL )) {
             S.helperContainer.style.left = `${newLeft}px`;
             S.triggerButton.style.left = `${newLeft}px`; // Keep trigger aligned
          }
          S.helperContainer.style.width = `${newWidth}px`;
          S.helperContainer.style.height = `${newHeight}px`;
        }
      });

      // Mouseup to end drag/resize
      UI.EventManager.addListener(document, 'mouseup', () => {
        let stateChanged = false;
        if (S.isDraggingTrigger || S.activeResizeHandle) {
          stateChanged = true;
        }
        S.isDraggingTrigger = false;
        S.activeResizeHandle = null;
        document.body.classList.remove(C.classNames.DRAGGING_BODY, C.classNames.RESIZING_BODY);
        S.triggerButton.style.transition = ''; // Restore transitions
        S.helperContainer.style.transition = '';
        if (stateChanged) {
          Persist.savePositionAndSize(
            S.helperContainer.offsetLeft,
            S.helperContainer.offsetWidth,
            S.helperContainer.offsetHeight
          );
        }
      });

      // Toolbar scroll for screen switching
      UI.EventManager.addListener(S.toolbar, 'wheel', UI.Toolbar.handleWheelScroll, { passive: false });
    }
  };

  // --- MODULE: CRO_App ---
  // Main application module to initialize and manage the script.
  window.CRO_App = {
    // Initializes the screen configurations.
    initScreensConfig: () => {
      const C = window.CRO_Config;
      // Defines the available screens, their labels, icons, and rendering functions.
      C.screens = [
        { id: 'cro_generator',    label: 'Action',     iconSVG: (isActive) => C.diceIconSVG(20, isActive ? C.getActiveIconColor() : C.getIconColor()),       renderFunc: window.CRO_UI.Screens.renderActionRollerScreen },
        { id: 'inventory_check',  label: 'Inventory',  iconSVG: (isActive) => C.inventoryIconSVG(20, isActive ? C.getActiveIconColor() : C.getIconColor()),  renderFunc: window.CRO_UI.Screens.renderInventoryCheckScreen },
        { id: 'perception_check', label: 'Perception', iconSVG: (isActive) => C.perceptionIconSVG(20, isActive ? C.getActiveIconColor() : C.getIconColor()),renderFunc: window.CRO_UI.Screens.renderPerceptionCheckScreen },
        { id: 'settings_screen',  label: 'Settings',   iconSVG: (isActive) => C.settingsIconSVG(20, isActive ? C.getActiveIconColor() : C.getIconColor()),    renderFunc: window.CRO_UI.Screens.renderSettingsScreen }
      ];
    },
    // Initializes the entire CRO Helper script.
    initialize: () => {
      const C = window.CRO_Config;
      const S = window.CRO_State;
      const App = window.CRO_App;
      const Persist = window.CRO_Persistence;
      const UI = window.CRO_UI;
      const ThemeMan = window.CRO_ThemeManager;

      console.log(`[CRO Helper] Initializing v${GM_info.script.version} for site: ${window.CRO_Utils.getCurrentSiteKey()}`);
      // Prevent multiple initializations.
      if (document.getElementById(C.ids.HELPER_CONTAINER)) {
        console.log('[CRO Helper] Already initialized.');
        return;
      }

      App.initScreensConfig();        // Define screen structures.
      Persist.loadThemeSettings();    // Load theme (per-domain).
      UI.createElements();            // Create main DOM elements.
      ThemeMan.updateGlobalStyles();  // Apply loaded theme to fresh elements.
      UI.injectStyles();              // Inject all CSS.
      Persist.loadPersistentState();  // Load popover position & size (per-domain).
      UI.addEventListeners();         // Add core event listeners.

      // Ensure a valid screen is active.
      if (!C.screens.find(s => s.id === S.activeScreenId)) {
        S.activeScreenId = C.screens[0]?.id || null; // Default to first screen.
      }
      if (S.activeScreenId) {
        UI.Screens.switchScreen(S.activeScreenId);
      }
      console.log('[CRO Helper] Initialized.');
    }
  };

    // --- Script Execution Start ---
    // This section orchestrates the initialization and visibility of the CRO Helper.

    let croHelperMainObserver = null;
    let croHelperLastPathname = window.location.pathname;
    let croHelperInitializedOnce = false; // Tracks if CRO_App.initialize() has been called at least once

    // Function to check if the current page is a chat page.
    function isChatPage() {
        const path = window.location.pathname;
        // Universal check for all supported sites:
        // - Spicychat & C.ai use '/chat'
        // - Janitor.ai uses '/chats/' or '/characters/'
        return path.includes('/chat') || path.includes('/chats/') || path.includes('/characters/');
    }

    // Function to show the CRO Helper UI elements that should always be visible on a chat page.
    function showCroHelperBaseUI() {
        const S = window.CRO_State;
        if (S.triggerButton) {
            S.triggerButton.style.display = 'flex'; // Or its original display style from CSS
        }
        // The main popover's visibility (S.isHelperVisible) is managed by user interaction.
        // We just ensure the trigger is available.
    }

    // Function to hide all CRO Helper UI elements.
    function hideCroHelperFullUI() {
        const S = window.CRO_State;
        const C = window.CRO_Config;
        if (S.triggerButton) {
            S.triggerButton.style.display = 'none';
        }
        if (S.helperContainer) {
            S.helperContainer.classList.add(C.classNames.HIDDEN);
            S.helperContainer.classList.remove(C.classNames.VISIBLE);
            S.isHelperVisible = false; // Reset popover visibility state
        }
    }

    // Main logic to run on DOM changes or SPA navigation.
    function manageCroHelperVisibilityAndInitialization() {
        if (isChatPage()) {
            // We are on a chat page.
            if (!croHelperInitializedOnce) {
                // Attempt to initialize only if not done before.
                let chatInput = window.CRO_ElementCache.getChatInput();
                if (chatInput) {
                    console.log(`[CRO Helper] Chat page + input found for ${window.CRO_Utils.getCurrentSiteKey()}. Running main initialization.`);
                    window.CRO_App.initialize();
                    croHelperInitializedOnce = true; // Mark that initialization has run.
                    showCroHelperBaseUI(); // Ensure trigger is visible after init.
                } else {
                    // console.log("[CRO Helper] On chat page, but chat input not found yet. Observer will retry.");
                }
            } else {
                // Already initialized, just ensure the base UI (trigger button) is visible.
                showCroHelperBaseUI();
            }
        } else {
            // Not on a chat page. If UI was initialized, hide it.
            if (croHelperInitializedOnce) {
                // console.log("[CRO Helper] Not on a chat page. Hiding UI.");
                hideCroHelperFullUI();
            }
        }
    }

    // Setup the main MutationObserver.
    function setupMainCroObserver() {
        if (croHelperMainObserver) {
            return; // Already set up
        }

        croHelperMainObserver = new MutationObserver(window.CRO_ErrorHandler.withErrorBoundary(() => {
            if (window.location.pathname !== croHelperLastPathname) {
                // console.log(`[CRO Helper] Pathname changed from ${croHelperLastPathname} to ${window.location.pathname}. Re-evaluating UI.`);
                croHelperLastPathname = window.location.pathname;
                window.CRO_ElementCache.invalidate(); // Important on navigation
            }
            manageCroHelperVisibilityAndInitialization();
        }, null, "MainCroObserverCallback"));

        const appRoot = document.getElementById('root') || document.body;
        croHelperMainObserver.observe(appRoot, {
            childList: true,
            subtree: true
        });
        console.log("[CRO Helper] Main observer started to manage UI visibility and initialization.");
    }


    // Initial execution logic when the script loads.
    function initialScriptLoadExecution() {
        console.log("[CRO Helper] Script loaded. Performing initial check.");
        manageCroHelperVisibilityAndInitialization(); // Attempt to init/show/hide based on current URL

        // If not initialized and on a chat page, the observer will catch it when elements appear.
        // If not initialized and not on a chat page, the observer will catch navigation to a chat page.
        // If already initialized (e.g. direct load to chat page), the UI will be shown.
        setupMainCroObserver(); // Ensure observer is always running after first load.
    }

    // Delay initial execution slightly to allow the SPA to settle a bit,
    // especially after `document-idle`.
    if (document.readyState === 'complete') {
        setTimeout(initialScriptLoadExecution, 200); // Short delay if page already complete
    } else {
        window.addEventListener('load', () => {
            setTimeout(initialScriptLoadExecution, 200); // Short delay after window load
        });
    }

})();