// ==UserScript==
// @name Model Selector for AI Uncensored (v2.7 - Strict Ultra Mode)
// @namespace http://tampermonkey.net/
// @version 2.7
// @description Select AI model, recalculates auth headers. Auto-syncs with site's Fast/Ultra mode. Ultra mode only shows reasoning models. UI enhancements + status indicator. Uses unsafeWindow.
// @author saros
// @match https://www.aiuncensored.info/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant unsafeWindow
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
console.log('[Model Selector] Script starting (v2.7 - Strict Ultra Mode)...');
// --- Configuration ---
// Define models with their types
const AVAILABLE_MODELS_DATA = [
{ id: "deepseek-ai/DeepSeek-V3-0324", name: "DeepSeek-V3-0324", type: "normal" },
{ id: "hermes3-405b", name: "Hermes3-405b", type: "normal" },
{ id: "hermes3-8b", name: "Hermes3-8b", type: "normal" },
{ id: "hermes3-70b", name: "Hermes3-70b", type: "normal" },
{ id: "deepseek-ai/DeepSeek-R1-0528", name: "DeepSeek-R1-0528", type: "reasoning" },
{ id: "deepseek-r1-671b", name: "DeepSeek-R1-671b", type: "reasoning" },
];
// Pre-filter models for convenience based on internal 'type'
const NORMAL_MODELS = AVAILABLE_MODELS_DATA.filter(m => m.type === 'normal');
const REASONING_MODELS = AVAILABLE_MODELS_DATA.filter(m => m.type === 'reasoning');
const STORAGE_KEY_MODEL = 'selectedChatModel_AIUncensored_v2';
const STORAGE_KEY_DISPLAY_MODE = 'displayMode_AIUncensored_v2'; // 'normal' or 'ultra'
const API_ENDPOINT_PATH = '/api/chat';
// --- Global Variables (will be set by UI creation) ---
let selectedModel = GM_getValue(STORAGE_KEY_MODEL, NORMAL_MODELS[0]?.id || AVAILABLE_MODELS_DATA[0]?.id);
let currentDisplayMode = GM_getValue(STORAGE_KEY_DISPLAY_MODE, 'normal'); // Default to 'normal'
let selectDropdown; // Reference to the model selection dropdown
let normalRadioBtn; // Reference to the 'Normal' radio button
let ultraRadioBtn; // Reference to the 'Ultra' radio button
// --- Functions to update UI elements (declared early for accessibility) ---
// Function to ensure selected model is compatible with the current display mode
function ensureModelCompatibility() {
let compatibleModels;
if (currentDisplayMode === 'normal') {
compatibleModels = NORMAL_MODELS;
} else { // 'ultra' mode
compatibleModels = REASONING_MODELS; // Ultra mode ONLY allows reasoning models
}
const modelExistsInCurrentMode = compatibleModels.some(m => m.id === selectedModel);
if (!modelExistsInCurrentMode) {
// If the previously selected model isn't available in the current mode,
// default to the first available model for this mode.
selectedModel = compatibleModels[0]?.id || AVAILABLE_MODELS_DATA[0]?.id || null; // Fallback to first available overall, or null
if (selectedModel) {
GM_setValue(STORAGE_KEY_MODEL, selectedModel);
console.log(`[Model Selector] Adjusted selected model to '${selectedModel}' for '${currentDisplayMode}' mode compatibility.`);
} else {
GM_deleteValue(STORAGE_KEY_MODEL); // Clear if no models are available for this mode
console.warn(`[Model Selector] No compatible models found for '${currentDisplayMode}' mode. Selected model set to null.`);
}
}
}
// Function to update the model dropdown options based on currentDisplayMode
function updateModelDropdownOptions() {
if (!selectDropdown) return;
// Clear existing options
selectDropdown.innerHTML = '';
let modelsToDisplay;
if (currentDisplayMode === 'normal') {
modelsToDisplay = NORMAL_MODELS;
if (modelsToDisplay.length === 0) {
console.warn('[Model Selector] No "normal" models configured. Please check AVAILABLE_MODELS_DATA.');
}
} else { // 'ultra'
// --- THIS IS THE KEY CHANGE ---
modelsToDisplay = REASONING_MODELS; // Only show reasoning models for Ultra mode
if (modelsToDisplay.length === 0) {
console.warn('[Model Selector] No "reasoning" models configured for Ultra mode. Please check AVAILABLE_MODELS_DATA.');
}
}
let newSelectedModelAvailable = false;
if (modelsToDisplay.length > 0) {
modelsToDisplay.forEach(model => {
const option = document.createElement('option');
option.value = model.id;
option.textContent = model.name;
option.title = model.id; // Full ID as title for hover
if (model.id === selectedModel) {
option.selected = true;
newSelectedModelAvailable = true;
}
selectDropdown.appendChild(option);
});
// If the previously selected model is no longer available in the new mode,
// default to the first available model in the new mode.
if (!newSelectedModelAvailable) {
selectedModel = modelsToDisplay[0].id;
GM_setValue(STORAGE_KEY_MODEL, selectedModel);
selectDropdown.value = selectedModel; // Update dropdown selection
console.log(`[Model Selector] Adjusted selected model to '${selectedModel}' for new mode compatibility.`);
}
} else {
selectedModel = null; // No models available for this mode
GM_deleteValue(STORAGE_KEY_MODEL); // Clear if no models
const noModelOption = document.createElement('option');
noModelOption.textContent = "No models available for this mode.";
noModelOption.value = "";
noModelOption.disabled = true;
noModelOption.selected = true;
selectDropdown.appendChild(noModelOption);
console.warn("[Model Selector] No models available for the current display mode!");
}
// Always update status after dropdown is populated/adjusted
updateStatusDisplay('idle', selectedModel);
}
// Helper function to update our custom radio buttons
function updateRadioButtons() {
if (normalRadioBtn && ultraRadioBtn) {
normalRadioBtn.checked = (currentDisplayMode === 'normal');
ultraRadioBtn.checked = (currentDisplayMode === 'ultra');
}
}
// --- Replicated Header Generation Function (based on site's tF) ---
const generateAuthHeaders = async (requestBodyObject) => {
const timestamp = Math.floor(Date.now() / 1e3).toString();
const bodyString = JSON.stringify(requestBodyObject);
const payload = `${timestamp}${bodyString}`;
const encoder = new TextEncoder();
const secretKey = encoder.encode("your-super-secret-key-replace-in-production"); // This exact key needs to match the site's.
const dataToSign = encoder.encode(payload);
try {
const importedKey = await crypto.subtle.importKey("raw", secretKey, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
const signatureBuffer = await crypto.subtle.sign("HMAC", importedKey, dataToSign);
const signatureHex = Array.from(new Uint8Array(signatureBuffer)).map(b => b.toString(16).padStart(2, "0")).join("");
return {
"X-API-Key": "62852b00cb9e44bca86f0ec7e7455dc6", "X-Timestamp": timestamp, "X-Signature": signatureHex,
"Content-Type": "application/json", "accept": "*/*", "accept-language": "en-US,en;q=0.9",
};
} catch (error) { console.error("[Model Selector] Error generating signature:", error); throw error; }
};
// --- Function to Update Status Display ---
function updateStatusDisplay(type, messageContent = '') {
const indicator = document.getElementById('modelStatusIndicator');
if (!indicator) return;
let messagePrefix = '';
indicator.className = 'status-indicator-base'; // Base class for styling
let modelDisplayName = messageContent;
// Find the friendly name for the model ID
const modelObj = AVAILABLE_MODELS_DATA.find(m => m.id === messageContent);
if (modelObj) {
modelDisplayName = modelObj.name;
} else if (messageContent === null || messageContent === '') {
modelDisplayName = 'No model selected'; // Case for no compatible models
}
const modeText = currentDisplayMode === 'ultra' ? 'Ultra Mode' : 'Normal Mode';
switch (type) {
case 'success':
messagePrefix = 'Using: ';
indicator.classList.add('status-success');
indicator.textContent = `${messagePrefix}${modelDisplayName} (${modeText})`;
break;
case 'no-change':
messagePrefix = 'Already: ';
indicator.classList.add('status-no-change');
indicator.textContent = `${messagePrefix}${modelDisplayName} (${modeText})`;
break;
case 'warning':
messagePrefix = 'Warning: ';
indicator.classList.add('status-warning');
indicator.textContent = `${messagePrefix}${messageContent || 'Check console.'}`;
break;
case 'error':
messagePrefix = 'Error: ';
indicator.classList.add('status-error');
indicator.textContent = `${messagePrefix}${messageContent || 'Override failed. Check console.'}`;
break;
case 'selected':
messagePrefix = 'Selected: ';
indicator.classList.add('status-selected');
indicator.textContent = `${messagePrefix}${modelDisplayName} (${modeText}). Awaiting API call.`;
break;
case 'idle':
default:
indicator.classList.add('status-idle');
indicator.textContent = `Panel active. Current: ${modelDisplayName} (${modeText}). Status updates after API usage.`;
break;
}
}
// --- Intercept Fetch using unsafeWindow ---
const originalFetch = unsafeWindow.fetch;
unsafeWindow.fetch = async function(input, init) {
const url = (input instanceof Request) ? input.url : input;
const method = ((init && init.method) || (input instanceof Request && input.method) || 'GET').toUpperCase();
if (url.endsWith(API_ENDPOINT_PATH) && method === 'POST' && init && init.body) {
try {
let bodyData = JSON.parse(init.body);
// Ensure the body has a 'model' property before attempting to modify
if (bodyData && typeof bodyData === 'object' && bodyData.hasOwnProperty('model')) {
// Only modify if the current model in the request doesn't match our selection
if (bodyData.model !== selectedModel) {
console.log(`[Model Selector] Intercepted ${API_ENDPOINT_PATH}. Original model: ${bodyData.model}`);
if (selectedModel) { // Only attempt to override if a model is actually selected
bodyData.model = selectedModel; // Apply the user's selected model
console.log(`[Model Selector] Modified model to: ${selectedModel}`);
console.log('[Model Selector] Regenerating auth headers...');
const newHeaders = await generateAuthHeaders(bodyData); // Regenerate headers with the new body
updateStatusDisplay('success', selectedModel); // Update status: SUCCESS
const newInit = { ...init, headers: newHeaders, body: JSON.stringify(bodyData) };
console.log('[Model Selector] Sending fetch with new headers and modified body.');
return originalFetch(input, newInit);
} else {
console.warn('[Model Selector] No model selected in userscript dropdown. Allowing original request with site\'s chosen model.');
updateStatusDisplay('warning', 'No model selected in panel. Original request sent.');
}
} else {
console.log(`[Model Selector] Intercepted ${API_ENDPOINT_PATH}. Model already matches selection (${selectedModel}). Passing through.`);
updateStatusDisplay('no-change', selectedModel); // Update status: NO CHANGE
}
} else {
console.warn('[Model Selector] Fetch body did not contain expected "model" property or was malformed:', bodyData);
updateStatusDisplay('warning', 'Payload issue'); // Update status: WARNING
}
} catch (e) {
console.error('[Model Selector] Error processing fetch interceptor:', e);
updateStatusDisplay('error', `Processing error: ${e.message}`); // Update status: ERROR
}
}
return originalFetch(input, init); // For all other requests, pass them through unchanged
};
// --- Observer for site's Fast/Ultra buttons ---
function setupModeObserver() {
// The container for Fast/Ultra/Call buttons.
// Based on the provided JS snippet, this is `tF` styled component, which is a `div`
// positioned fixed at the bottom. This selector is robust to class name changes from build.
const chatModeContainerSelector = 'div[style*="bottom: 120px"][style*="left: 50%"][style*="fixed"]';
const observerTarget = document.querySelector(chatModeContainerSelector);
if (!observerTarget) {
// Element not found immediately, retry after a short delay.
// This is crucial because the script runs at document-start, but elements
// might be rendered later by the SPA framework.
console.log('[Model Selector] Chat mode container not found, retrying setupModeObserver in 500ms...');
setTimeout(setupModeObserver, 500);
return;
}
console.log('[Model Selector] Chat mode container found, setting up MutationObserver.');
const buttons = observerTarget.querySelectorAll('button');
let initialModeDetected = false;
// --- Initial check for active mode when script loads ---
buttons.forEach(button => {
const buttonText = button.textContent.trim();
const computedStyle = window.getComputedStyle(button);
// Check for the specific active background color (#c15a17 in RGB)
if (computedStyle.backgroundColor === 'rgb(193, 90, 23)') { // This is the color for active buttons
if (buttonText === 'Fast') {
currentDisplayMode = 'normal';
initialModeDetected = true;
} else if (buttonText === 'Ultra') { // Site's "beta" corresponds to our "ultra"
currentDisplayMode = 'ultra';
initialModeDetected = true;
}
}
});
if (initialModeDetected) {
GM_setValue(STORAGE_KEY_DISPLAY_MODE, currentDisplayMode);
console.log(`[Model Selector] Initial site mode detected: ${currentDisplayMode}`);
} else {
console.warn('[Model Selector] No active site mode button found on initial load. Defaulting to stored mode.');
}
// Run compatibility check and update UI based on (potentially new) currentDisplayMode
ensureModelCompatibility(); // Ensure selectedModel is valid for this mode
updateRadioButtons(); // Update our custom radio buttons
updateModelDropdownOptions(); // Populate model dropdown based on the mode
updateStatusDisplay('idle', selectedModel); // Initial status message
// --- MutationObserver to watch for changes ---
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
// We are only interested in attribute changes on the buttons themselves
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const targetButton = mutation.target; // The button whose class attribute changed
const buttonText = targetButton.textContent.trim();
const computedStyle = window.getComputedStyle(targetButton);
// Check if the button currently has the active background color
const isActive = computedStyle.backgroundColor === 'rgb(193, 90, 23)';
let newMode = currentDisplayMode; // Assume no change
if (buttonText === 'Fast' && isActive) {
newMode = 'normal';
} else if (buttonText === 'Ultra' && isActive) { // Site's "beta" corresponds to our "ultra"
newMode = 'ultra';
}
// If a different mode is now active, update our internal state and UI
if (newMode !== currentDisplayMode) {
currentDisplayMode = newMode;
GM_setValue(STORAGE_KEY_DISPLAY_MODE, currentDisplayMode);
console.log(`[Model Selector] Site mode changed by user interaction to: ${currentDisplayMode}`);
// Update our custom UI elements to reflect the new mode
ensureModelCompatibility(); // Re-ensure model compatibility after mode change
updateRadioButtons();
updateModelDropdownOptions();
updateStatusDisplay('selected', selectedModel); // Indicate new selection
}
}
}
});
// Observe each mode button for changes to its class attribute.
// This is important because the active state changes by adding/removing classes.
buttons.forEach(button => {
observer.observe(button, { attributes: true, attributeFilter: ['class'] });
});
}
// --- Create Enhanced UI ---
function createEnhancedUI() {
console.log('[Model Selector] createEnhancedUI called');
GM_addStyle(`
#modelSelectorToggleBtn {
position: fixed; top: 16px; left: 60px; z-index: 9998; background-color: #2a2a2a;
color: #e0e0e0; border: 1px solid #c15a17; border-radius: 50%; width: 40px; height: 40px;
font-size: 22px; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.35);
transition: background-color 0.2s, transform 0.2s, border-color 0.3s ease;
display: flex; align-items: center; justify-content: center; line-height: 1;
}
#modelSelectorToggleBtn:hover { background-color: #383838; transform: translateY(-1px) scale(1.05); }
#modelSelectorContainer {
position: fixed; top: 70px; left: 15px; z-index: 9999; background-color: #1e1e1e;
border: 1px solid #c15a17; padding: 0; border-radius: 10px; box-shadow: 0 5px 15px rgba(0,0,0,0.6);
font-family: 'Segoe UI', 'Avenir', 'Arial', sans-serif; color: #e0e0e0; min-width: 290px;
display: flex; flex-direction: column; opacity: 0; transform: translateY(-15px) scale(0.98);
visibility: hidden; transition: opacity 0.25s ease-out, transform 0.25s ease-out, visibility 0s linear 0.25s;
}
#modelSelectorContainer.visible { opacity: 1; transform: translateY(0) scale(1); visibility: visible; transition-delay: 0s; }
#modelSelectorHeader {
background-color: #282828; padding: 10px 15px; border-bottom: 1px solid #c15a17;
border-radius: 9px 9px 0 0; display: flex; justify-content: space-between; align-items: center;
font-weight: 600; font-size: 15px;
}
#modelSelectorHeader span { color: #c15a17; font-weight: 700; }
#modelSelectorCloseBtn {
background: none; border: none; color: #b0b0b0; font-size: 26px; font-weight: bold;
cursor: pointer; padding: 0 5px; line-height: 1; opacity: 0.7; transition: opacity 0.2s, color 0.2s;
}
#modelSelectorCloseBtn:hover { opacity: 1; color: #ffffff; }
#modelSelectorBody { padding: 18px; display: flex; flex-direction: column; gap: 10px; }
#modelSelectorContainer label { margin: 0 0 2px 0; font-weight: 500; color: #cccccc; font-size: 13.5px; }
#modelSelectorDropdown {
width: 100%; padding: 10px 12px; background-color: #2c2c2c; color: #e0e0e0;
border: 1px solid #555; border-radius: 6px; font-size: 14px; box-sizing: border-box;
transition: border-color 0.3s ease, background-color 0.3s ease, box-shadow 0.3s ease;
}
#modelSelectorDropdown:hover { border-color: #777; }
#modelSelectorDropdown:focus { border-color: #c15a17; box-shadow: 0 0 0 2px rgba(193, 90, 23, 0.3); outline: none; }
#modelSelectorDropdown option { background-color: #2c2c2c; color: #e0e0e0; padding: 8px 10px; }
/* --- Status Indicator Styles --- */
.status-indicator-base {
padding: 8px 10px;
margin-top: 12px;
border-radius: 5px;
font-size: 12.5px;
text-align: center;
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
border: 1px solid transparent;
line-height: 1.4;
}
.status-indicator-base.status-idle,
.status-indicator-base.status-selected {
background-color: #303030;
color: #b0b0b0;
border-color: #404040;
}
.status-indicator-base.status-success {
background-color: #203c20; /* Darker, less saturated green */
color: #90c090; /* Softer green text */
border-color: #305c30;
}
.status-indicator-base.status-no-change {
background-color: #22303f; /* Darker, less saturated blue */
color: #8ab0d0; /* Softer blue text */
border-color: #32506f;
}
.status-indicator-base.status-warning {
background-color: #3f3f22; /* Darker, less saturated yellow */
color: #c0c08a; /* Softer yellow text */
border-color: #5f5f32;
}
.status-indicator-base.status-error {
background-color: #3f2222; /* Darker, less saturated red */
color: #d08a8a; /* Softer red text */
border-color: #5f3232;
}
/* --- Display Mode Styles --- */
#displayModeSection {
margin-top: 10px;
padding-top: 10px;
border-top: 1px dashed #3a3a3a; /* Separator for display mode */
}
#displayModeSection label {
margin-bottom: 5px;
}
#displayModeOptions {
display: flex;
gap: 15px; /* Space between radio buttons */
justify-content: center;
margin-top: 5px;
}
#displayModeOptions input[type="radio"] {
display: none; /* Hide native radio button */
}
#displayModeOptions label.radio-btn {
display: inline-block;
padding: 8px 15px;
border: 1px solid #555;
border-radius: 5px;
cursor: pointer;
background-color: #2c2c2c;
color: #e0e0e0;
font-size: 13px;
transition: background-color 0.2s, border-color 0.2s, color 0.2s;
user-select: none; /* Prevent text selection */
}
#displayModeOptions input[type="radio"]:checked + label.radio-btn {
background-color: #c15a17; /* Highlight checked button */
border-color: #c15a17;
color: #ffffff;
box-shadow: 0 0 0 2px rgba(193, 90, 23, 0.3);
}
#displayModeOptions label.radio-btn:hover {
background-color: #3a3a3a;
border-color: #777;
}
`);
// --- UI Elements Creation ---
const toggleBtn = document.createElement('button');
toggleBtn.id = 'modelSelectorToggleBtn';
toggleBtn.innerHTML = '⚙️';
toggleBtn.title = 'Toggle Model Selector';
const container = document.createElement('div');
container.id = 'modelSelectorContainer';
const header = document.createElement('div');
header.id = 'modelSelectorHeader';
const title = document.createElement('span');
title.textContent = 'Model Settings';
const closeBtn = document.createElement('button');
closeBtn.id = 'modelSelectorCloseBtn';
closeBtn.innerHTML = '×';
closeBtn.title = 'Close Settings';
header.appendChild(title);
header.appendChild(closeBtn);
const bodyEl = document.createElement('div');
bodyEl.id = 'modelSelectorBody';
// --- Display Mode Selection Section ---
const displayModeSection = document.createElement('div');
displayModeSection.id = 'displayModeSection';
const displayModeLabel = document.createElement('label');
displayModeLabel.textContent = 'Chat Display Mode:';
const displayModeOptions = document.createElement('div');
displayModeOptions.id = 'displayModeOptions';
const createRadioButton = (id, value, text) => {
const radioInput = document.createElement('input');
radioInput.type = 'radio';
radioInput.id = id;
radioInput.name = 'displayMode'; // All radios in group must have same name
radioInput.value = value;
// Checked state set by updateRadioButtons() later
const radioLabel = document.createElement('label');
radioLabel.htmlFor = id;
radioLabel.textContent = text;
radioLabel.classList.add('radio-btn');
radioInput.addEventListener('change', (event) => {
const newMode = event.target.value;
if (newMode !== currentDisplayMode) {
currentDisplayMode = newMode;
GM_setValue(STORAGE_KEY_DISPLAY_MODE, currentDisplayMode);
console.log(`[Model Selector] Custom mode selection changed to: ${currentDisplayMode}`);
ensureModelCompatibility(); // Re-ensure model compatibility after mode change
updateModelDropdownOptions(); // Crucial: update model list based on new mode
updateStatusDisplay('selected', selectedModel); // Indicate new selection
}
});
displayModeOptions.appendChild(radioInput);
displayModeOptions.appendChild(radioLabel);
return radioInput; // Return the input element for global reference
};
// Create the 'Normal' and 'Ultra' radio buttons and store references
normalRadioBtn = createRadioButton('displayModeNormal', 'normal', 'Normal');
ultraRadioBtn = createRadioButton('displayModeUltra', 'ultra', 'Ultra (Reasoning)');
displayModeSection.appendChild(displayModeLabel);
displayModeSection.appendChild(displayModeOptions);
bodyEl.appendChild(displayModeSection);
// --- Model Dropdown Section ---
const modelLabel = document.createElement('label');
modelLabel.htmlFor = 'modelSelectorDropdown';
modelLabel.textContent = 'Active AI Model:';
selectDropdown = document.createElement('select'); // Assign to global variable
selectDropdown.id = 'modelSelectorDropdown';
bodyEl.appendChild(modelLabel);
bodyEl.appendChild(selectDropdown);
// --- Status Indicator Element ---
const statusIndicator = document.createElement('div');
statusIndicator.id = 'modelStatusIndicator';
bodyEl.appendChild(statusIndicator);
// --- Assemble Container ---
container.appendChild(header);
container.appendChild(bodyEl);
// --- Append elements to the DOM ---
function appendElements() {
if (document.body) {
document.body.appendChild(toggleBtn);
document.body.appendChild(container);
// Now that elements are in DOM, set up the observer and initial UI states
setupModeObserver(); // This will also call updateRadioButtons and updateModelDropdownOptions
} else {
// Fallback for slower DOM loading
window.addEventListener('DOMContentLoaded', () => {
document.body.appendChild(toggleBtn);
document.body.appendChild(container);
setupModeObserver();
});
}
}
// Check document state for immediate or delayed appending
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', appendElements);
} else {
appendElements();
}
// --- Event Listeners for custom UI ---
toggleBtn.addEventListener('click', () => { container.classList.toggle('visible'); });
closeBtn.addEventListener('click', () => { container.classList.remove('visible'); });
selectDropdown.addEventListener('change', (event) => {
selectedModel = event.target.value;
GM_setValue(STORAGE_KEY_MODEL, selectedModel);
console.log(`[Model Selector] Model selection changed to: ${selectedModel}`);
updateStatusDisplay('selected', selectedModel); // Update status for selection
// Visual feedback for selection change
toggleBtn.style.borderColor = '#4caf50'; // Green border on toggle button
setTimeout(() => { toggleBtn.style.borderColor = '#c15a17'; }, 600); // Revert after a short delay
selectDropdown.style.transition = 'none'; // Temporarily disable transition for immediate color change
selectDropdown.style.borderColor = '#4caf50'; // Green border on dropdown
selectDropdown.style.backgroundColor = '#3a4a3a'; // Darker background
setTimeout(() => {
// Re-enable transition and revert styles
selectDropdown.style.transition = 'border-color 0.3s ease, background-color 0.3s ease, box-shadow 0.3s ease';
selectDropdown.style.borderColor = '#555';
selectDropdown.style.backgroundColor = '#2c2c2c';
}, 500);
});
}
// --- Initialize UI ---
// Ensure UI creation happens after the DOM is ready, or immediately if it already is.
// setupModeObserver will be called from createEnhancedUI after elements are appended.
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', createEnhancedUI);
} else {
createEnhancedUI();
}
})();