WTR LAB Novel Image Generator

Generate images from selected text in novels using various AI providers. Now with provider profile management for OpenAI-compatible APIs and enhanced generation history.

// ==UserScript==
// @name         WTR LAB Novel Image Generator
// @namespace    http://tampermonkey.net/
// @version      5.6
// @description  Generate images from selected text in novels using various AI providers. Now with provider profile management for OpenAI-compatible APIs and enhanced generation history.
// @author       MasuRii
// @match        https://wtr-lab.com/en/novel/*/*/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=wtr-lab.com
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @connect      *
// @license      MIT
// ==/UserScript==

;(function () {
	'use strict'

	// --- 1. STYLES ---
	const styles = `
	       /* === CSS Custom Properties (Design Tokens) === */
	       :root {
	           /* Color Palette - Modern Dark Theme */
	           --nig-color-bg-primary: #1a1a1e;
	           --nig-color-bg-secondary: #2d2d32;
	           --nig-color-bg-tertiary: #3a3a40;
	           --nig-color-bg-elevated: #404046;
	           --nig-color-text-primary: #f0f0f0;
	           --nig-color-text-secondary: #b4b4b8;
	           --nig-color-text-muted: #8a8a8e;
	           --nig-color-border: #55555a;
	           --nig-color-border-light: #6a6a6e;
	           
	           /* Accent Colors */
	           --nig-color-accent-primary: #6366f1;
	           --nig-color-accent-secondary: #8b5cf6;
	           --nig-color-accent-success: #10b981;
	           --nig-color-accent-warning: #f59e0b;
	           --nig-color-accent-error: #ef4444;
	           
	           /* Interactive States */
	           --nig-color-hover-primary: #5855eb;
	           --nig-color-hover-secondary: #7c3aed;
	           --nig-color-hover-success: #059669;
	           --nig-color-hover-error: #dc2626;
	           
	           /* Spacing Scale */
	           --nig-space-xs: 0.25rem;
	           --nig-space-sm: 0.5rem;
	           --nig-space-md: 0.75rem;
	           --nig-space-lg: 1rem;
	           --nig-space-xl: 1.5rem;
	           --nig-space-2xl: 2rem;
	           --nig-space-3xl: 3rem;
	           
	           /* Typography Scale */
	           --nig-font-size-xs: 0.75rem;
	           --nig-font-size-sm: 0.875rem;
	           --nig-font-size-base: 1rem;
	           --nig-font-size-lg: 1.125rem;
	           --nig-font-size-xl: 1.25rem;
	           --nig-font-size-2xl: 1.5rem;
	           --nig-font-size-3xl: 1.875rem;
	           
	           /* Border Radius */
	           --nig-radius-sm: 0.375rem;
	           --nig-radius-md: 0.5rem;
	           --nig-radius-lg: 0.75rem;
	           --nig-radius-xl: 1rem;
	           
	           /* Shadows */
	           --nig-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
	           --nig-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
	           --nig-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -2px rgba(0, 0, 0, 0.4);
	           --nig-shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.6), 0 10px 10px -5px rgba(0, 0, 0, 0.5);
	           
	           /* Transitions */
	           --nig-transition-fast: 0.15s ease-out;
	           --nig-transition-normal: 0.2s ease-out;
	           --nig-transition-slow: 0.3s ease-out;
	           
	           /* Breakpoints */
	           --nig-breakpoint-sm: 640px;
	           --nig-breakpoint-md: 768px;
	           --nig-breakpoint-lg: 1024px;
	           --nig-breakpoint-xl: 1280px;
	       }

	       /* === Base Components === */
	       .nig-button {
	           position: absolute;
	           z-index: 99998;
	           background: var(--nig-color-accent-primary);
	           color: white;
	           border: none;
	           border-radius: var(--nig-radius-md);
	           padding: var(--nig-space-sm) var(--nig-space-md);
	           font-size: var(--nig-font-size-sm);
	           font-weight: 500;
	           cursor: pointer;
	           box-shadow: var(--nig-shadow-md);
	           display: none;
	           font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
	           transition: all var(--nig-transition-normal);
	           transform: translateY(0);
	       }
	       .nig-button:hover {
	           background: var(--nig-color-hover-primary);
	           transform: translateY(-1px);
	           box-shadow: var(--nig-shadow-lg);
	       }
	       .nig-button:active {
	           transform: translateY(0);
	           box-shadow: var(--nig-shadow-sm);
	       }

	       .nig-modal-overlay {
	           position: fixed;
	           top: 0;
	           left: 0;
	           width: 100%;
	           height: 100%;
	           background: rgba(0, 0, 0, 0.7);
	           backdrop-filter: blur(8px);
	           z-index: 99999;
	           display: flex;
	           flex-direction: column;
	           justify-content: center;
	           align-items: center;
	           padding: var(--nig-space-lg);
	       }

	       .nig-modal-content {
	           background: var(--nig-color-bg-secondary);
	           color: var(--nig-color-text-primary);
	           padding: var(--nig-space-2xl);
	           border-radius: var(--nig-radius-xl);
	           box-shadow: var(--nig-shadow-xl);
	           width: 100%;
	           max-width: 900px;
	           max-height: 90vh;
	           overflow-y: auto;
	           position: relative;
	           font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
	           border: 1px solid var(--nig-color-border);
	           animation: nig-modal-appear 0.2s ease-out;
	       }

	       @keyframes nig-modal-appear {
	           from {
	               opacity: 0;
	               transform: scale(0.95) translateY(-10px);
	           }
	           to {
	               opacity: 1;
	               transform: scale(1) translateY(0);
	           }
	       }

	       .nig-modal-content ul {
	           padding-left: var(--nig-space-xl);
	       }

	       .nig-modal-content li {
	           margin-bottom: var(--nig-space-md);
	       }

	       .nig-close-btn {
	           position: absolute;
	           top: var(--nig-space-lg);
	           right: var(--nig-space-lg);
	           font-size: var(--nig-font-size-2xl);
	           font-weight: 300;
	           cursor: pointer;
	           color: var(--nig-color-text-muted);
	           width: 32px;
	           height: 32px;
	           display: flex;
	           align-items: center;
	           justify-content: center;
	           border-radius: var(--nig-radius-md);
	           transition: all var(--nig-transition-fast);
	       }

	       .nig-close-btn:hover {
	           color: var(--nig-color-text-primary);
	           background: var(--nig-color-bg-tertiary);
	       }

	       .nig-modal-content h2 {
	           margin-top: 0;
	           border-bottom: 1px solid var(--nig-color-border);
	           padding-bottom: var(--nig-space-lg);
	           font-size: var(--nig-font-size-2xl);
	           font-weight: 600;
	           letter-spacing: -0.025em;
	       }

	       /* === Form Elements === */
	       .nig-form-group {
	           margin-bottom: var(--nig-space-xl);
	       }

	       .nig-form-group label {
	           display: block;
	           margin-bottom: var(--nig-space-sm);
	           font-weight: 500;
	           color: var(--nig-color-text-primary);
	           font-size: var(--nig-font-size-sm);
	       }

	       .nig-form-group small.nig-hint {
	           color: var(--nig-color-text-muted);
	           font-weight: normal;
	           display: block;
	           margin-top: var(--nig-space-sm);
	           margin-bottom: var(--nig-space-sm);
	           min-height: 1.2em;
	           font-size: var(--nig-font-size-xs);
	           line-height: 1.4;
	       }

	       .nig-form-group input,
	       .nig-form-group select,
	       .nig-form-group textarea {
	           width: 100%;
	           padding: var(--nig-space-sm) var(--nig-space-md);
	           border-radius: var(--nig-radius-md);
	           border: 1px solid var(--nig-color-border);
	           background: var(--nig-color-bg-tertiary);
	           color: var(--nig-color-text-primary);
	           font-size: var(--nig-font-size-sm);
	           font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
	           transition: all var(--nig-transition-fast);
	           outline: none;
	       }

	       .nig-form-group input:focus,
	       .nig-form-group select:focus,
	       .nig-form-group textarea:focus {
	           border-color: var(--nig-color-accent-primary);
	           box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
	           background: var(--nig-color-bg-elevated);
	       }

	       .nig-form-group select:disabled {
	           background: var(--nig-color-bg-tertiary);
	           color: var(--nig-color-text-muted);
	           cursor: not-allowed;
	       }

	       .nig-form-group textarea {
	           resize: vertical;
	           min-height: 80px;
	           line-height: 1.5;
	       }

	       .nig-form-group-inline {
	           display: grid;
	           grid-template-columns: 1fr 1fr;
	           gap: var(--nig-space-lg);
	           align-items: end;
	       }

	       .nig-form-group-inline label {
	           margin-bottom: var(--nig-space-sm);
	       }

	       .nig-checkbox-group {
	           display: flex;
	           flex-wrap: wrap;
	           gap: var(--nig-space-lg);
	       }

	       .nig-checkbox-group label {
	           display: flex;
	           align-items: center;
	           margin-right: 0;
	           font-weight: normal;
	           cursor: pointer;
	           color: var(--nig-color-text-secondary);
	       }

	       .nig-checkbox-group input {
	           width: auto;
	           margin-right: var(--nig-space-sm);
	           margin-bottom: 0;
	           transform: scale(1.1);
	       }

	       /* === Buttons === */
	       .nig-save-btn {
	           background: var(--nig-color-accent-success);
	           color: white;
	           padding: var(--nig-space-md) var(--nig-space-xl);
	           border: none;
	           border-radius: var(--nig-radius-md);
	           cursor: pointer;
	           font-size: var(--nig-font-size-base);
	           font-weight: 500;
	           transition: all var(--nig-transition-normal);
	           box-shadow: var(--nig-shadow-sm);
	           display: inline-flex;
	           align-items: center;
	           justify-content: center;
	           gap: var(--nig-space-sm);
	       }

	       .nig-save-btn:hover {
	           background: var(--nig-color-hover-success);
	           transform: translateY(-1px);
	           box-shadow: var(--nig-shadow-md);
	       }

	       .nig-fetch-models-btn {
	           padding: var(--nig-space-sm) var(--nig-space-md);
	           margin-left: var(--nig-space-md);
	           border-radius: var(--nig-radius-md);
	           border: 1px solid var(--nig-color-border);
	           background: var(--nig-color-accent-primary);
	           color: white;
	           cursor: pointer;
	           font-size: var(--nig-font-size-sm);
	           font-weight: 500;
	           transition: all var(--nig-transition-normal);
	       }

	       .nig-fetch-models-btn:hover {
	           background: var(--nig-color-hover-primary);
	           transform: translateY(-1px);
	       }

	       .nig-delete-btn {
	           padding: var(--nig-space-sm) var(--nig-space-md);
	           margin-left: var(--nig-space-md);
	           border-radius: var(--nig-radius-md);
	           border: 1px solid var(--nig-color-border);
	           background: var(--nig-color-accent-error);
	           color: white;
	           cursor: pointer;
	           font-size: var(--nig-font-size-sm);
	           font-weight: 500;
	           transition: all var(--nig-transition-normal);
	       }

	       .nig-delete-btn:hover {
	           background: var(--nig-color-hover-error);
	           transform: translateY(-1px);
	       }

	       .nig-history-cleanup-btn {
	           background: var(--nig-color-accent-error);
	           color: white;
	           padding: var(--nig-space-sm) var(--nig-space-md);
	           border: none;
	           border-radius: var(--nig-radius-md);
	           cursor: pointer;
	           font-size: var(--nig-font-size-sm);
	           font-weight: 500;
	           transition: all var(--nig-transition-normal);
	           display: inline-flex;
	           align-items: center;
	           justify-content: center;
	           gap: var(--nig-space-sm);
	       }

	       .nig-history-cleanup-btn:hover {
	           background: var(--nig-color-hover-error);
	           transform: translateY(-1px);
	       }

	       /* === Tab System === */
	       .nig-tabs {
	           display: flex;
	           border-bottom: 1px solid var(--nig-color-border);
	           margin-bottom: var(--nig-space-xl);
	           overflow-x: auto;
	           scrollbar-width: none;
	           -ms-overflow-style: none;
	       }

	       .nig-tabs::-webkit-scrollbar {
	           display: none;
	       }

	       .nig-tab {
	           padding: var(--nig-space-md) var(--nig-space-xl);
	           cursor: pointer;
	           border-radius: var(--nig-radius-md) var(--nig-radius-md) 0 0;
	           background: transparent;
	           color: var(--nig-color-text-secondary);
	           font-size: var(--nig-font-size-sm);
	           font-weight: 500;
	           transition: all var(--nig-transition-fast);
	           white-space: nowrap;
	           border: 1px solid transparent;
	           border-bottom: none;
	       }

	       .nig-tab:hover {
	           background: var(--nig-color-bg-tertiary);
	           color: var(--nig-color-text-primary);
	       }

	       .nig-tab.active {
	           background: var(--nig-color-bg-tertiary);
	           color: var(--nig-color-text-primary);
	           border: 1px solid var(--nig-color-border);
	           border-bottom: 1px solid var(--nig-color-bg-tertiary);
	           box-shadow: 0 -2px 0 var(--nig-color-accent-primary) inset;
	       }

	       .nig-tab-content {
	           display: none;
	           animation: nig-content-fade 0.2s ease-out;
	       }

	       .nig-tab-content.active {
	           display: block;
	       }

	       @keyframes nig-content-fade {
	           from {
	               opacity: 0;
	               transform: translateY(10px);
	           }
	           to {
	               opacity: 1;
	               transform: translateY(0);
	           }
	       }

	       /* === Modern Utilities Tab === */
	       .nig-utilities-grid {
	           display: grid;
	           grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
	           gap: var(--nig-space-xl);
	           margin-top: var(--nig-space-xl);
	       }

	       .nig-utility-card {
	           background: var(--nig-color-bg-tertiary);
	           border: 1px solid var(--nig-color-border);
	           border-radius: var(--nig-radius-lg);
	           padding: var(--nig-space-xl);
	           transition: all var(--nig-transition-normal);
	       }

	       .nig-utility-card:hover {
	           border-color: var(--nig-color-border-light);
	           box-shadow: var(--nig-shadow-md);
	           transform: translateY(-2px);
	       }

	       .nig-utility-card h4 {
	           margin: 0 0 var(--nig-space-md) 0;
	           color: var(--nig-color-text-primary);
	           font-size: var(--nig-font-size-lg);
	           font-weight: 600;
	       }

	       .nig-utility-card p {
	           margin: 0 0 var(--nig-space-lg) 0;
	           color: var(--nig-color-text-secondary);
	           font-size: var(--nig-font-size-sm);
	           line-height: 1.5;
	       }

	       .nig-utility-card .nig-save-btn {
	           width: 100%;
	           justify-content: center;
	       }

	       /* === History System === */
	       .nig-history-list {
	           list-style: none;
	           padding: 0;
	           max-height: 400px;
	           overflow-y: auto;
	       }

	       .nig-history-item {
	           background: var(--nig-color-bg-tertiary);
	           padding: var(--nig-space-lg);
	           border-radius: var(--nig-radius-md);
	           margin-bottom: var(--nig-space-md);
	           border: 1px solid var(--nig-color-border);
	           transition: all var(--nig-transition-fast);
	       }

	       .nig-history-item:hover {
	           border-color: var(--nig-color-border-light);
	           box-shadow: var(--nig-shadow-sm);
	       }

	       .nig-history-item small {
	           display: block;
	           color: var(--nig-color-text-muted);
	           margin-bottom: var(--nig-space-sm);
	           font-size: var(--nig-font-size-xs);
	       }

	       .nig-history-item a {
	           color: var(--nig-color-accent-primary);
	           text-decoration: none;
	           word-break: break-all;
	           font-weight: 500;
	           transition: color var(--nig-transition-fast);
	       }

	       .nig-history-item a:hover {
	           color: var(--nig-color-hover-primary);
	       }

	       .nig-history-cleanup {
	           padding: var(--nig-space-lg);
	           background: var(--nig-color-bg-tertiary);
	           border-radius: var(--nig-radius-md);
	           margin-bottom: var(--nig-space-xl);
	           display: flex;
	           align-items: center;
	           gap: var(--nig-space-lg);
	           border: 1px solid var(--nig-color-border);
	       }

	       .nig-history-cleanup input[type="number"] {
	           width: 80px;
	           padding: var(--nig-space-sm);
	       }

	       /* === Image Gallery === */
	       .nig-image-gallery {
	           display: grid;
	           grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
	           gap: var(--nig-space-xl);
	           margin-top: var(--nig-space-xl);
	       }

	       .nig-image-container {
	           position: relative;
	           border-radius: var(--nig-radius-lg);
	           overflow: hidden;
	           background: var(--nig-color-bg-tertiary);
	           border: 1px solid var(--nig-color-border);
	           transition: all var(--nig-transition-normal);
	       }

	       .nig-image-container:hover {
	           box-shadow: var(--nig-shadow-lg);
	           transform: translateY(-2px);
	       }

	       .nig-image-container img {
	           width: 100%;
	           height: auto;
	           display: block;
	           transition: transform var(--nig-transition-slow);
	       }

	       .nig-image-container:hover img {
	           transform: scale(1.02);
	       }

	       .nig-image-actions {
	           position: absolute;
	           top: var(--nig-space-md);
	           right: var(--nig-space-md);
	           display: flex;
	           gap: var(--nig-space-sm);
	           background: rgba(0, 0, 0, 0.7);
	           backdrop-filter: blur(10px);
	           padding: var(--nig-space-sm);
	           border-radius: var(--nig-radius-md);
	           opacity: 0;
	           transition: opacity var(--nig-transition-normal);
	       }

	       .nig-image-container:hover .nig-image-actions {
	           opacity: 1;
	       }

	       .nig-image-actions button {
	           background: rgba(0, 0, 0, 0.8);
	           border: none;
	           border-radius: var(--nig-radius-md);
	           width: 36px;
	           height: 36px;
	           cursor: pointer;
	           display: flex;
	           align-items: center;
	           justify-content: center;
	           font-size: var(--nig-font-size-base);
	           color: white;
	           transition: all var(--nig-transition-fast);
	       }

	       .nig-image-actions button:hover {
	           background: white;
	           transform: scale(1.1);
	       }

	       .material-symbols-outlined {
	           font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
	           font-size: 18px;
	       }

	       .nig-api-prompt-link {
	           color: var(--nig-color-accent-primary);
	           text-decoration: none;
	           transition: color var(--nig-transition-fast);
	       }

	       .nig-api-prompt-link:hover {
	           color: var(--nig-color-hover-primary);
	           text-decoration: underline;
	       }

	       .nig-provider-settings {
	           display: none;
	       }

	       /* === Prompt Container === */
	       .nig-prompt-container {
	           background: var(--nig-color-bg-tertiary);
	           border-radius: var(--nig-radius-md);
	           margin-bottom: var(--nig-space-lg);
	           border: 1px solid var(--nig-color-border);
	           transition: all var(--nig-transition-normal);
	       }

	       .nig-prompt-header {
	           padding: var(--nig-space-lg);
	           cursor: pointer;
	           font-weight: 500;
	           display: flex;
	           align-items: center;
	           user-select: none;
	           color: var(--nig-color-text-primary);
	           transition: color var(--nig-transition-fast);
	       }

	       .nig-prompt-header:hover {
	           color: var(--nig-color-accent-primary);
	       }

	       .nig-prompt-header::before {
	           content: '▸';
	           margin-right: var(--nig-space-md);
	           transition: transform var(--nig-transition-normal);
	           color: var(--nig-color-text-muted);
	       }

	       .nig-prompt-container.expanded .nig-prompt-header::before {
	           transform: rotate(90deg);
	           color: var(--nig-color-accent-primary);
	       }

	       .nig-prompt-text {
	           display: none;
	           padding: 0 var(--nig-space-lg) var(--nig-space-lg) var(--nig-space-lg);
	           border-top: 1px solid var(--nig-color-border);
	           word-break: break-word;
	           color: var(--nig-color-text-secondary);
	           line-height: 1.6;
	       }

	       .nig-prompt-container.expanded .nig-prompt-text {
	           display: block;
	           animation: nig-expand 0.2s ease-out;
	       }

	       @keyframes nig-expand {
	           from {
	               opacity: 0;
	               max-height: 0;
	           }
	           to {
	               opacity: 1;
	               max-height: 200px;
	           }
	       }

	       /* === Button Footer === */
	       .nig-button-footer {
	           margin-top: var(--nig-space-3xl);
	           padding-top: var(--nig-space-xl);
	           border-top: 1px solid var(--nig-color-border);
	           text-align: center;
	       }

	       /* === Status Widget === */
	       .nig-status-widget {
	           position: fixed;
	           bottom: var(--nig-space-xl);
	           left: var(--nig-space-xl);
	           z-index: 100000;
	           background: var(--nig-color-bg-secondary);
	           color: var(--nig-color-text-primary);
	           padding: var(--nig-space-md) var(--nig-space-lg);
	           border-radius: var(--nig-radius-lg);
	           box-shadow: var(--nig-shadow-xl);
	           display: none;
	           align-items: center;
	           gap: var(--nig-space-md);
	           font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
	           font-size: var(--nig-font-size-sm);
	           font-weight: 500;
	           transition: all var(--nig-transition-normal);
	           border: 1px solid var(--nig-color-border);
	           backdrop-filter: blur(10px);
	       }

	       .nig-status-icon {
	           width: 20px;
	           height: 20px;
	           display: flex;
	           align-items: center;
	           justify-content: center;
	       }

	       .nig-status-widget.loading .nig-status-icon {
	           box-sizing: border-box;
	           border: 2px solid var(--nig-color-text-muted);
	           border-top-color: var(--nig-color-accent-primary);
	           border-radius: 50%;
	           animation: nig-spin 1s linear infinite;
	       }

	       .nig-status-widget.success {
	           background: var(--nig-color-accent-success);
	           color: white;
	           cursor: pointer;
	           border-color: var(--nig-color-accent-success);
	       }

	       .nig-status-widget.success:hover {
	           background: var(--nig-color-hover-success);
	           transform: translateY(-2px);
	           box-shadow: var(--nig-shadow-lg);
	       }

	       .nig-status-widget.error {
	           background: var(--nig-color-accent-error);
	           border-color: var(--nig-color-accent-error);
	       }

	       @keyframes nig-spin {
	           0% {
	               transform: rotate(0deg);
	           }
	           100% {
	               transform: rotate(360deg);
	           }
	       }

	       /* === Error Modal === */
	       #nig-error-reason {
	           background: var(--nig-color-bg-tertiary);
	           border: 1px solid var(--nig-color-border);
	           padding: var(--nig-space-lg);
	           border-radius: var(--nig-radius-md);
	           margin-top: var(--nig-space-sm);
	           max-height: 150px;
	           overflow-y: auto;
	           word-wrap: break-word;
	           overflow-wrap: break-word;
	           white-space: pre-wrap;
	           font-family: 'Fira Code', 'Monaco', 'Consolas', monospace;
	           color: var(--nig-color-text-primary);
	           line-height: 1.5;
	       }

	       .nig-error-prompt {
	           background: var(--nig-color-bg-tertiary);
	           border: 1px solid var(--nig-color-border);
	           padding: var(--nig-space-lg);
	           border-radius: var(--nig-radius-md);
	           margin-top: var(--nig-space-lg);
	           max-height: 200px;
	           overflow-y: auto;
	           word-break: break-word;
	           font-family: 'Fira Code', 'Monaco', 'Consolas', monospace;
	           width: 100%;
	           resize: vertical;
	           min-height: 80px;
	           color: var(--nig-color-text-primary);
	           line-height: 1.5;
	       }

	       .nig-error-actions {
	           margin-top: var(--nig-space-xl);
	       }

	       .nig-retry-btn {
	           background: var(--nig-color-accent-primary);
	           color: white;
	           padding: var(--nig-space-md) var(--nig-space-xl);
	           border: none;
	           border-radius: var(--nig-radius-md);
	           cursor: pointer;
	           font-size: var(--nig-font-size-base);
	           font-weight: 500;
	           transition: all var(--nig-transition-normal);
	           box-shadow: var(--nig-shadow-sm);
	       }

	       .nig-retry-btn:hover {
	           background: var(--nig-color-hover-primary);
	           transform: translateY(-1px);
	           box-shadow: var(--nig-shadow-md);
	       }

	       /* === Responsive Design === */
	       
	       /* Mobile First Base (up to 767px) */
	       @media (max-width: 767px) {
	           .nig-modal-content {
	               margin: var(--nig-space-md);
	               padding: var(--nig-space-xl);
	               max-height: 95vh;
	               border-radius: var(--nig-radius-lg);
	           }

	           .nig-modal-overlay {
	               padding: var(--nig-space-sm);
	           }

	           .nig-button {
	               position: fixed;
	               bottom: var(--nig-space-3xl);
	               left: 50% !important;
	               transform: translateX(-50%);
	               top: auto !important;
	               padding: var(--nig-space-sm) var(--nig-space-lg);
	               font-size: var(--nig-font-size-sm);
	               z-index: 100001;
	               min-height: 44px; /* Touch target */
	               border-radius: var(--nig-radius-lg);
	           }

	           .nig-tabs {
	               margin: 0 calc(-1 * var(--nig-space-xl)) var(--nig-space-xl) calc(-1 * var(--nig-space-xl));
	               padding: 0 var(--nig-space-xl);
	           }

	           .nig-tab {
	               padding: var(--nig-space-md) var(--nig-space-lg);
	               font-size: var(--nig-font-size-xs);
	           }

	           .nig-form-group-inline {
	               grid-template-columns: 1fr;
	               gap: var(--nig-space-md);
	           }

	           .nig-checkbox-group {
	               flex-direction: column;
	               gap: var(--nig-space-sm);
	           }

	           .nig-checkbox-group label {
	               justify-content: flex-start;
	           }

	           .nig-utilities-grid {
	               grid-template-columns: 1fr;
	               gap: var(--nig-space-lg);
	           }

	           .nig-utility-card {
	               padding: var(--nig-space-lg);
	           }

	           .nig-history-cleanup {
	               flex-direction: column;
	               align-items: stretch;
	               gap: var(--nig-space-md);
	           }

	           .nig-history-cleanup input[type="number"] {
	               width: 100%;
	           }

	           .nig-status-widget {
	               bottom: var(--nig-space-lg);
	               left: var(--nig-space-md);
	               right: var(--nig-space-md);
	               padding: var(--nig-space-md);
	               font-size: var(--nig-font-size-xs);
	           }

	           .nig-image-gallery {
	               grid-template-columns: 1fr;
	               gap: var(--nig-space-lg);
	           }

	           .nig-modal-content h2 {
	               font-size: var(--nig-font-size-xl);
	               padding-right: 48px; /* Space for close button */
	           }
	       }

	       /* Tablet (768px to 1023px) */
	       @media (min-width: 768px) and (max-width: 1023px) {
	           .nig-modal-content {
	               max-width: 700px;
	           }

	           .nig-utilities-grid {
	               grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
	           }

	           .nig-image-gallery {
	               grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
	           }
	       }

	       /* Desktop (1024px and up) */
	       @media (min-width: 1024px) {
	           .nig-modal-content {
	               max-width: 1000px;
	           }

	           .nig-utilities-grid {
	               grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
	           }

	           .nig-image-gallery {
	               grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
	           }

	           .nig-form-group-inline {
	               grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
	           }

	           /* Enhanced hover states for desktop */
	           .nig-tab:hover {
	               background: var(--nig-color-bg-tertiary);
	           }

	           .nig-history-item:hover {
	               transform: translateY(-1px);
	           }
	       }

	       /* Large Desktop (1280px and up) */
	       @media (min-width: 1280px) {
	           .nig-modal-content {
	               max-width: 1200px;
	           }

	           .nig-utilities-grid {
	               grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
	           }
	       }

	       /* Print Styles */
	       @media print {
	           .nig-modal-overlay,
	           .nig-status-widget,
	           .nig-button {
	               display: none !important;
	           }

	           .nig-modal-content {
	               box-shadow: none;
	               border: 1px solid #000;
	               background: white;
	               color: black;
	           }
	       }

	       /* High Contrast Mode Support */
	       @media (prefers-contrast: high) {
	           :root {
	               --nig-color-bg-primary: #000000;
	               --nig-color-bg-secondary: #1a1a1a;
	               --nig-color-bg-tertiary: #2a2a2a;
	               --nig-color-text-primary: #ffffff;
	               --nig-color-border: #666666;
	           }
	       }

	       /* Reduced Motion Support */
	       @media (prefers-reduced-motion: reduce) {
	           *,
	           *::before,
	           *::after {
	               animation-duration: 0.01ms !important;
	               animation-iteration-count: 1 !important;
	               transition-duration: 0.01ms !important;
	           }
	       }

	       /* === Panel Configuration Grid Layout === */
	       .nig-config-grid {
	           display: grid;
	           grid-template-columns: 1fr;
	           gap: var(--nig-space-xl);
	       }

	       .nig-config-section {
	           background: var(--nig-color-bg-tertiary);
	           border: 1px solid var(--nig-color-border);
	           border-radius: var(--nig-radius-lg);
	           padding: var(--nig-space-xl);
	       }

	       .nig-provider-container {
	           display: grid;
	           gap: var(--nig-space-lg);
	       }

	       .nig-provider-header {
	           margin-bottom: var(--nig-space-lg);
	           padding-bottom: var(--nig-space-lg);
	           border-bottom: 1px solid var(--nig-color-border);
	       }

	       .nig-provider-header h3 {
	           margin: 0 0 var(--nig-space-sm) 0;
	           color: var(--nig-color-text-primary);
	           font-size: var(--nig-font-size-lg);
	           font-weight: 600;
	           display: flex;
	           align-items: center;
	           gap: var(--nig-space-sm);
	       }

	       .nig-provider-header p {
	           margin: 0;
	           color: var(--nig-color-text-secondary);
	           font-size: var(--nig-font-size-sm);
	           line-height: 1.5;
	       }

	       .nig-provider-controls {
	           display: grid;
	           grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
	           gap: var(--nig-space-lg);
	           margin-bottom: var(--nig-space-lg);
	       }

	       .nig-provider-settings {
	           background: var(--nig-color-bg-tertiary);
	           border: 1px solid var(--nig-color-border);
	           border-radius: var(--nig-radius-lg);
	           padding: var(--nig-space-xl);
	           transition: all var(--nig-transition-normal);
	       }

	       .nig-provider-settings:hover {
	           border-color: var(--nig-color-border-light);
	           box-shadow: var(--nig-shadow-sm);
	       }

	       .nig-form-grid {
	           display: grid;
	           grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
	           gap: var(--nig-space-lg);
	           margin-bottom: var(--nig-space-lg);
	       }

	       /* === Styling Tab Layout === */
	       .nig-styling-container {
	           display: grid;
	           gap: var(--nig-space-xl);
	       }

	       .nig-styling-intro {
	           background: var(--nig-color-bg-tertiary);
	           border: 1px solid var(--nig-color-border);
	           border-radius: var(--nig-radius-lg);
	           padding: var(--nig-space-lg);
	           margin-bottom: var(--nig-space-lg);
	       }

	       .nig-styling-intro p {
	           margin: 0;
	           color: var(--nig-color-text-secondary);
	           font-size: var(--nig-font-size-sm);
	           line-height: 1.6;
	       }

	       .nig-style-grid {
	           display: grid;
	           grid-template-columns: 1fr;
	           gap: var(--nig-space-xl);
	       }

	       .nig-style-section {
	           background: var(--nig-color-bg-tertiary);
	           border: 1px solid var(--nig-color-border);
	           border-radius: var(--nig-radius-lg);
	           padding: var(--nig-space-xl);
	       }

	       .nig-section-header {
	           margin-bottom: var(--nig-space-lg);
	           padding-bottom: var(--nig-space-lg);
	           border-bottom: 1px solid var(--nig-color-border);
	       }

	       .nig-section-header h4 {
	           margin: 0;
	           color: var(--nig-color-text-primary);
	           font-size: var(--nig-font-size-lg);
	           font-weight: 600;
	       }

	       /* === History Tab Layout === */
	       .nig-history-container {
	           display: grid;
	           gap: var(--nig-space-xl);
	       }

	       .nig-history-cleanup {
	           background: var(--nig-color-bg-tertiary);
	           border: 1px solid var(--nig-color-border);
	           border-radius: var(--nig-radius-lg);
	           padding: var(--nig-space-xl);
	           display: grid;
	           gap: var(--nig-space-lg);
	       }

	       .nig-cleanup-info h4 {
	           margin: 0 0 var(--nig-space-sm) 0;
	           color: var(--nig-color-text-primary);
	           font-size: var(--nig-font-size-lg);
	           font-weight: 600;
	       }

	       .nig-cleanup-info p {
	           margin: 0;
	           color: var(--nig-color-text-secondary);
	           font-size: var(--nig-font-size-sm);
	           line-height: 1.5;
	       }

	       .nig-cleanup-controls {
	           display: flex;
	           align-items: center;
	           gap: var(--nig-space-md);
	           flex-wrap: wrap;
	       }

	       .nig-cleanup-controls label {
	           color: var(--nig-color-text-primary);
	           font-weight: 500;
	           font-size: var(--nig-font-size-sm);
	       }

	       .nig-cleanup-controls input[type="number"] {
	           width: 80px;
	           padding: var(--nig-space-sm);
	           background: var(--nig-color-bg-primary);
	           border: 1px solid var(--nig-color-border);
	           border-radius: var(--nig-radius-md);
	           color: var(--nig-color-text-primary);
	       }

	       /* === Enhanced Responsive Grid === */
	       @media (min-width: 768px) {
	           .nig-config-grid {
	               grid-template-columns: 1fr;
	           }

	           .nig-provider-controls {
	               grid-template-columns: repeat(2, 1fr);
	           }

	           .nig-form-grid {
	               grid-template-columns: repeat(2, 1fr);
	           }

	           .nig-style-grid {
	               grid-template-columns: 1fr;
	           }

	           .nig-cleanup-controls {
	               justify-content: flex-start;
	           }
	       }

	       @media (min-width: 1024px) {
	           .nig-config-grid {
	               grid-template-columns: 1fr;
	           }

	           .nig-provider-controls {
	               grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
	           }

	           .nig-form-grid {
	               grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
	           }

	           .nig-style-grid {
	               grid-template-columns: 1fr;
	           }

	           .nig-provider-settings:hover {
	               transform: translateY(-2px);
	               box-shadow: var(--nig-shadow-md);
	           }
	       }

	       @media (min-width: 1280px) {
	           .nig-config-grid {
	               grid-template-columns: 1fr;
	           }

	           .nig-provider-controls {
	               grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
	           }

	           .nig-style-grid {
	               grid-template-columns: repeat(2, 1fr);
	           }

	           .nig-styling-container {
	               grid-template-columns: 1fr;
	           }
	       }

	       /* === File Input Styling === */
	       input[type="file"] {
	           border: 2px dashed var(--nig-color-border);
	           background: var(--nig-color-bg-primary);
	           padding: var(--nig-space-xl);
	           border-radius: var(--nig-radius-lg);
	           color: var(--nig-color-text-secondary);
	           transition: all var(--nig-transition-normal);
	           cursor: pointer;
	       }

	       input[type="file"]:hover {
	           border-color: var(--nig-color-accent-primary);
	           background: var(--nig-color-bg-elevated);
	       }

	       input[type="file"]:focus {
	           outline: none;
	           border-color: var(--nig-color-accent-primary);
	           box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
	       }
	   `

	// --- 2. DEFAULTS & STORAGE HELPERS ---
	const PROMPT_CATEGORIES = [
		{name: 'None', description: 'No additional styling will be added to your prompt.', subStyles: []},
		{
			name: 'Anime',
			description:
				'Blends Japanese animation with global twists. Sub-styles often mix eras, genres, or crossovers for dynamic outputs.',
			subStyles: [
				{name: 'None', value: 'none', description: 'Use only the main style name as a prefix (e.g., "Anime style, ...").'},
				{
					name: 'Studio Ghibli-Inspired',
					description: 'Whimsical, nature-focused fantasy with soft lines and emotional depth.',
					value: 'Studio Ghibli style, '
				},
				{
					name: 'Cyberpunk Anime',
					description: 'Neon-lit dystopias with high-tech mechs and gritty urban vibes.',
					value: 'Cyberpunk anime style, '
				},
				{
					name: 'Semi-Realistic Anime',
					description: 'Blends lifelike proportions with expressive anime eyes and shading.',
					value: 'Semi-realistic anime style, '
				},
				{name: 'Mecha', description: 'Giant robots and mechanical suits in epic battles.', value: 'Mecha anime style, '},
				{
					name: 'Dynamic Action',
					description: 'High-energy movements with power effects and intense expressions.',
					value: 'Dynamic action anime style, '
				},
				{
					name: 'Soft Romantic',
					description: 'Emotional interactions with gentle colors and sparkling accents.',
					value: 'Soft romantic anime style, '
				},
				{
					name: 'Dark Fantasy Anime',
					description: 'Grim, horror-tinged worlds with demons and shadows.',
					value: 'Dark fantasy anime style, '
				},
				{
					name: 'Retro 80s Anime',
					description: 'Vintage cel-shaded look with bold lines and synth vibes.',
					value: '80s retro anime style, '
				},
				{
					name: 'Portal Fantasy',
					description: 'World-crossing elements with magical adaptations and RPG motifs.',
					value: 'Portal fantasy anime style, '
				},
				{
					name: 'Slice-of-Life',
					description: 'Everyday moments with relatable characters and cozy vibes.',
					value: 'Slice-of-life anime style, '
				},
				{
					name: 'Serialized Narrative',
					description: 'Panel-like compositions for ongoing story flows.',
					value: 'Serialized narrative anime style, '
				},
				{
					name: 'Group Dynamic',
					description: 'Interactions among multiple characters with balanced focus.',
					value: 'Group dynamic anime style, '
				}
			]
		},
		{
			name: 'Realism/Photorealism',
			description:
				'Excels in portraits and scenes mimicking photography, with sub-styles varying by subject or technique.',
			subStyles: [
				{
					name: 'None',
					value: 'none',
					description: 'Use only the main style name as a prefix (e.g., "Realism/Photorealism style, ...").'
				},
				{
					name: 'Hyperrealism',
					description: 'Ultra-detailed, almost tangible textures and lighting.',
					value: 'Hyperrealistic, '
				},
				{
					name: 'Cinematic Realism',
					description: 'Film-like depth with dramatic angles and color grading.',
					value: 'Cinematic realism, '
				},
				{
					name: 'Portrait Photorealism',
					description: 'Human faces with natural skin, eyes, and expressions.',
					value: 'Portrait photorealism, '
				},
				{
					name: 'Architectural Realism',
					description: 'Precise building renders with environmental details.',
					value: 'Architectural realism, '
				},
				{
					name: 'Nature Photorealism',
					description: 'Verdant landscapes with dew and foliage intricacies.',
					value: 'Nature photorealism, '
				},
				{
					name: 'Close-Up Detail',
					description: 'Intimate views highlighting textures and fine elements.',
					value: 'Close-up realistic style, '
				},
				{
					name: 'Historical Realism',
					description: 'Period-accurate clothing and settings with grit.',
					value: 'Historical realism, '
				},
				{name: 'Urban Realism', description: 'Bustling city life with crowds and neon realism.', value: 'Urban realism, '},
				{name: 'Stylized Realism', description: 'Subtle artistic tweaks on photoreal bases.', value: 'Stylized realism, '},
				{
					name: 'Documentary Style',
					description: 'Raw, unpolished scenes like news photography.',
					value: 'Documentary photo style, '
				},
				{
					name: 'Object Focus Realism',
					description: 'Clear, highlighted items with neutral lighting.',
					value: 'Object-focused realism, '
				},
				{
					name: 'Wildlife Realism',
					description: 'Animals in habitats with fur and feather fidelity.',
					value: 'Wildlife realism, '
				},
				{
					name: 'Detailed Portrait',
					description: 'Lifelike faces with expressive features.',
					value: 'Detailed portrait realism, '
				},
				{
					name: 'Environmental Immersion',
					description: 'Rich settings enveloping subjects.',
					value: 'Environmental immersion realism, '
				}
			]
		},
		{
			name: 'Fantasy',
			description: 'Epic worlds of magic and myth, with sub-styles spanning tones from whimsical to grim.',
			subStyles: [
				{
					name: 'None',
					value: 'none',
					description: 'Use only the main style name as a prefix (e.g., "Fantasy style, ...").'
				},
				{
					name: 'High Fantasy',
					description: 'Medieval realms with elves, dragons, and quests.',
					value: 'High fantasy art, '
				},
				{
					name: 'Dark Fantasy',
					description: 'Grimdark horror with undead and moral ambiguity.',
					value: 'Dark fantasy art, '
				},
				{name: 'Urban Fantasy', description: 'Magic in modern cities, like hidden witches.', value: 'Urban fantasy art, '},
				{
					name: 'Steampunk Fantasy',
					description: 'Victorian tech with gears and airships.',
					value: 'Steampunk fantasy style, '
				},
				{
					name: 'Fairy Tale',
					description: 'Whimsical tales with enchanted woods and creatures.',
					value: 'Fairy tale illustration style, '
				},
				{
					name: 'Heroic Adventure',
					description: 'Bold explorers with raw magic and ancient relics.',
					value: 'Heroic adventure fantasy art, '
				},
				{
					name: 'Creature Emphasis',
					description: 'Fantastical beings in natural or enchanted environments.',
					value: 'Creature-focused fantasy art, '
				},
				{
					name: 'Ethereal Grace',
					description: 'Elegant figures in luminous, forested settings.',
					value: 'Ethereal grace fantasy style, '
				},
				{
					name: 'Rugged Craftsmanship',
					description: 'Stout builders in forged, underground realms.',
					value: 'Rugged craftsmanship fantasy style, '
				},
				{name: 'Gothic Fantasy', description: 'Haunted castles with vampires and storms.', value: 'Gothic fantasy art, '},
				{
					name: 'Beast Majesty',
					description: 'Powerful scaled creatures in dramatic poses.',
					value: 'Majestic beast fantasy art, '
				},
				{
					name: 'Celestial Fantasy',
					description: 'Starry realms with gods and floating islands.',
					value: 'Celestial fantasy art, '
				},
				{
					name: 'Oriental Myth',
					description: 'Asian folklore elements with harmonious nature.',
					value: 'Oriental myth fantasy style, '
				},
				{
					name: 'Treasure Hunt Vibe',
					description: 'Exploratory scenes with hidden wonders.',
					value: 'Treasure hunt fantasy art, '
				}
			]
		},
		{
			name: 'Sci-Fi',
			description: 'Futuristic visions from gritty cyber worlds to cosmic explorations.',
			subStyles: [
				{name: 'None', value: 'none', description: 'Use only the main style name as a prefix (e.g., "Sci-Fi style, ...").'},
				{name: 'Cyberpunk', description: 'Neon dystopias with hackers and megacorps.', value: 'Cyberpunk style, '},
				{name: 'Retro-Futurism', description: '1950s optimism with ray guns and chrome.', value: 'Retro-futurism, '},
				{name: 'Biopunk', description: 'Organic tech with genetic mutations.', value: 'Biopunk sci-fi style, '},
				{
					name: 'Interstellar Epic',
					description: 'Vast cosmic tales with diverse species and ships.',
					value: 'Interstellar epic sci-fi art, '
				},
				{
					name: 'Mechanical Suit',
					description: 'Armored machines in high-tech conflicts.',
					value: 'Mechanical suit sci-fi style, '
				},
				{name: 'Post-Human', description: 'Cyborgs and AI in evolved societies.', value: 'Post-human sci-fi art, '},
				{
					name: 'Hard Sci-Fi',
					description: 'Physics-based realism with tech schematics.',
					value: 'Hard sci-fi illustration, '
				},
				{name: 'Dieselpunk', description: '1930s grit with riveted machines.', value: 'Dieselpunk style, '},
				{name: 'Astro-Mythology', description: 'Space gods and cosmic myths.', value: 'Astro-mythology art, '},
				{name: 'Eco-Sci-Fi', description: 'Post-apocalypse with bio-domes.', value: 'Eco-sci-fi art, '},
				{
					name: 'Survival Wasteland',
					description: 'Harsh, ruined landscapes with resilient figures.',
					value: 'Survival wasteland sci-fi style, '
				},
				{
					name: 'Cosmic Discovery',
					description: 'Unknown worlds with exploratory tech.',
					value: 'Cosmic discovery sci-fi art, '
				}
			]
		},
		{
			name: 'Retro/Vintage',
			description: 'Nostalgic aesthetics from bygone eras, revived with AI flair.',
			subStyles: [
				{
					name: 'None',
					value: 'none',
					description: 'Use only the main style name as a prefix (e.g., "Retro/Vintage style, ...").'
				},
				{name: 'Art Deco', description: 'Geometric luxury with gold and symmetry.', value: 'Art Deco style, '},
				{name: 'Art Nouveau', description: 'Flowing organic lines and floral motifs.', value: 'Art Nouveau style, '},
				{name: 'Vintage Poster', description: 'Bold typography and illustrative ads.', value: 'Vintage poster style, '},
				{name: 'Chromolithography', description: 'Vibrant, printed color layers from 1900s.', value: 'Chromolithography, '},
				{name: 'Baroque', description: 'Ornate drama with rich drapery.', value: 'Baroque painting style, '},
				{name: 'Ukiyo-e', description: 'Japanese woodblock prints with flat colors.', value: 'Ukiyo-e style, '},
				{name: '1950s Retro', description: 'Atomic age optimism with pastels.', value: '1950s retro style, '},
				{
					name: 'Playful Figure',
					description: 'Charming, stylized poses with vintage flair.',
					value: 'Playful vintage figure style, '
				},
				{name: 'Edwardian', description: 'Lacy elegance with soft pastels.', value: 'Edwardian era style, '},
				{name: 'Mid-Century Modern', description: 'Clean lines and bold geometrics.', value: 'Mid-century modern style, '},
				{
					name: 'Ink Scroll',
					description: 'Brush-like lines evoking ancient manuscripts.',
					value: 'Ink scroll vintage style, '
				}
			]
		},
		{
			name: 'Surrealism',
			description: 'Dreamlike distortions challenging reality, inspired by masters.',
			subStyles: [
				{
					name: 'None',
					value: 'none',
					description: 'Use only the main style name as a prefix (e.g., "Surrealism style, ...").'
				},
				{
					name: 'Fluid Distortion',
					description: 'Melting forms and impossible blends.',
					value: 'Fluid distortion surrealism, '
				},
				{
					name: 'Paradoxical Objects',
					description: 'Everyday items in illogical arrangements.',
					value: 'Paradoxical object surrealism, '
				},
				{
					name: 'Ernst Collage Surreal',
					description: 'Layered fragments for uncanny narratives.',
					value: 'Ernst collage surrealism, '
				},
				{
					name: 'Kahlo Autobiographical',
					description: 'Personal symbolism with thorny motifs.',
					value: 'Frida Kahlo style surrealism, '
				},
				{name: 'Biomorphic Surreal', description: 'Organic, creature-like hybrids.', value: 'Biomorphic surrealism, '},
				{
					name: 'Dreamlike Landscapes',
					description: 'Floating islands and inverted gravity.',
					value: 'Dreamlike surreal landscape, '
				},
				{
					name: 'Freudian Symbolic',
					description: 'Subconscious icons like eyes and stairs.',
					value: 'Freudian symbolic surrealism, '
				},
				{
					name: 'Pop Surrealism',
					description: 'Whimsical grotesquery with candy colors.',
					value: 'Pop surrealism, lowbrow art, '
				},
				{name: 'Hyper-Surreal', description: 'Exaggerated distortions in vivid detail.', value: 'Hyper-surrealism, '},
				{name: 'Eco-Surreal', description: 'Nature twisted with human elements.', value: 'Eco-surrealism, '},
				{name: 'Mechanical Surreal', description: 'Machines fused with flesh.', value: 'Mechanical surrealism, '},
				{
					name: 'Inner Vision',
					description: 'Symbolic inner thoughts with blended realities.',
					value: 'Inner vision surrealism, '
				}
			]
		},
		{
			name: 'Cartoon/Illustration',
			description: 'Exaggerated, narrative-driven visuals for fun and storytelling.',
			subStyles: [
				{
					name: 'None',
					value: 'none',
					description: 'Use only the main style name as a prefix (e.g., "Cartoon/Illustration style, ...").'
				},
				{
					name: 'Pixar 3D',
					description: 'Polished, expressive CG with emotional arcs.',
					value: 'Pixar 3D animation style, '
				},
				{
					name: 'Disney Classic',
					description: 'Hand-drawn whimsy with fluid animation.',
					value: 'Classic Disney animation style, '
				},
				{name: 'DreamWorks', description: 'Edgy humor with detailed backgrounds.', value: 'DreamWorks animation style, '},
				{
					name: 'Adventure Time',
					description: 'Surreal candy lands with bold shapes.',
					value: 'Adventure Time cartoon style, '
				},
				{
					name: 'Simpsons',
					description: 'Yellow-skinned satire with clean outlines.',
					value: 'The Simpsons cartoon style, '
				},
				{
					name: 'Rick and Morty',
					description: 'Sci-fi absurdity with warped perspectives.',
					value: 'Rick and Morty cartoon style, '
				},
				{
					name: 'Narrative Panel',
					description: 'Sequential art with shaded storytelling.',
					value: 'Narrative panel illustration style, '
				},
				{
					name: 'Whimsical Illustration',
					description: 'Gentle, colorful drawings for light-hearted scenes.',
					value: 'Whimsical illustration style, '
				},
				{name: 'Webtoon', description: 'Vertical scroll with vibrant digital ink.', value: 'Webtoon style, '},
				{
					name: 'Manhua Flow',
					description: 'Dynamic lines and vibrant digital shading.',
					value: 'Manhua flow illustration style, '
				},
				{
					name: 'Cover Art Focus',
					description: 'Striking compositions for thematic highlights.',
					value: 'Cover art illustration, '
				}
			]
		},
		{
			name: 'Traditional Painting',
			description: 'Emulates historical mediums like oils and watercolors for timeless appeal.',
			subStyles: [
				{
					name: 'None',
					value: 'none',
					description: 'Use only the main style name as a prefix (e.g., "Traditional Painting style, ...").'
				},
				{
					name: 'Impressionism',
					description: 'Loose brushstrokes capturing light moments.',
					value: 'Impressionist painting, '
				},
				{
					name: 'Renaissance',
					description: 'Balanced compositions with chiaroscuro.',
					value: 'Renaissance painting style, '
				},
				{name: 'Oil Painting', description: 'Rich, layered textures with glazing.', value: 'Oil painting, '},
				{name: 'Watercolor', description: 'Translucent washes for ethereal softness.', value: 'Watercolor painting, '},
				{name: 'Baroque', description: 'Dramatic tenebrism and opulent details.', value: 'Baroque painting, '},
				{name: 'Romanticism', description: 'Emotional storms and heroic figures.', value: 'Romanticism painting, '},
				{name: 'Pointillism', description: 'Dot-based color mixing for vibrancy.', value: 'Pointillism style, '},
				{name: 'Fresco', description: 'Mural-like with aged plaster effects.', value: 'Fresco painting style, '},
				{name: 'Encaustic', description: 'Waxy, heated layers for luminous depth.', value: 'Encaustic painting, '},
				{name: 'Acrylic', description: 'Bold, matte finishes with quick drying.', value: 'Acrylic painting, '},
				{name: 'Gouache', description: 'Opaque vibrancy like matte poster paint.', value: 'Gouache painting, '},
				{name: 'Sumi-e', description: 'Minimalist ink washes for Zen simplicity.', value: 'Sumi-e ink wash painting, '},
				{
					name: 'Oriental Brushwork',
					description: 'Minimalist inks for balanced compositions.',
					value: 'Oriental brushwork style, '
				},
				{name: 'Era Line Art', description: 'Detailed etchings for historical depth.', value: 'Era line art traditional, '}
			]
		},
		{
			name: 'Digital Art',
			description: 'Modern, tech-infused creations from pixels to vectors.',
			subStyles: [
				{
					name: 'None',
					value: 'none',
					description: 'Use only the main style name as a prefix (e.g., "Digital Art style, ...").'
				},
				{
					name: 'Vector Illustration',
					description: 'Scalable, flat colors with clean paths.',
					value: 'Vector illustration, '
				},
				{
					name: 'Blended Landscape',
					description: 'Layered digital environments for immersive backdrops.',
					value: 'Blended digital landscape, '
				},
				{name: 'Neon Glow', description: 'Vibrant outlines with electric luminescence.', value: 'Neon glow digital art, '},
				{name: 'Holographic', description: 'Shimmering, 3D projections with refractions.', value: 'Holographic style, '},
				{
					name: 'World-Building Sketch',
					description: 'Conceptual layers for expansive scenes.',
					value: 'World-building digital sketch, '
				},
				{
					name: 'Community Render',
					description: 'Polished digital interpretations of characters.',
					value: 'Community render digital style, '
				}
			]
		},
		{
			name: 'Wuxia/Xianxia',
			description:
				'Eastern-inspired martial and spiritual themes with energy flows, ancient motifs, and harmonious or intense atmospheres.',
			subStyles: [
				{
					name: 'None',
					value: 'none',
					description: 'Use only the main style name as a prefix (e.g., "Wuxia/Xianxia style, ...").'
				},
				{
					name: 'Qi Energy Flow',
					description: 'Subtle auras and internal power visualizations.',
					value: 'Qi energy flow style, '
				},
				{name: 'Martial Grace', description: 'Fluid poses and disciplined movements.', value: 'Martial grace style, '},
				{
					name: 'Spiritual Realm',
					description: 'Misty, elevated worlds with ethereal elements.',
					value: 'Spiritual realm style, '
				},
				{
					name: 'Ancient Sect Aesthetic',
					description: 'Traditional architecture and robed figures.',
					value: 'Ancient sect aesthetic, '
				},
				{
					name: 'Demonic Shadow',
					description: 'Darkened energies and mysterious silhouettes.',
					value: 'Demonic shadow style, '
				},
				{
					name: 'Dynasty Elegance',
					description: 'Silk textures and jade accents in historical tones.',
					value: 'Dynasty elegance style, '
				}
			]
		},
		{
			name: 'Romance',
			description: 'Tender or passionate human connections with soft lighting, expressions, and atmospheric details.',
			subStyles: [
				{
					name: 'None',
					value: 'none',
					description: 'Use only the main style name as a prefix (e.g., "Romance style, ...").'
				},
				{
					name: 'Gentle Intimacy',
					description: 'Close, affectionate moments with warm hues.',
					value: 'Gentle intimacy romance style, '
				},
				{
					name: 'Urban Affection',
					description: 'Modern settings with subtle romantic gestures.',
					value: 'Urban affection style, '
				},
				{
					name: 'Enchanted Bond',
					description: 'Magical elements enhancing emotional ties.',
					value: 'Enchanted bond romance style, '
				},
				{
					name: 'Tension Build',
					description: 'Subtle conflicts leading to connection.',
					value: 'Tension build romance style, '
				},
				{
					name: 'Blushing Softness',
					description: 'Delicate emotions with pastel accents.',
					value: 'Blushing softness style, '
				},
				{
					name: 'Melancholic Yearning',
					description: 'Poignant separations with evocative moods.',
					value: 'Melancholic yearning romance art, '
				}
			]
		}
	]

	const TOP_MODELS = [
		{name: 'Deliberate', desc: 'Versatile, high-quality realism and detail.'},
		{name: 'Anything Diffusion', desc: 'Anime-style specialist.'},
		{name: "ICBINP - I Can't Believe It's Not Photography", desc: 'Photorealistic focus; excels in lifelike portraits.'},
		{name: 'stable_diffusion', desc: 'The classic base model; reliable all-rounder.'},
		{name: 'AlbedoBase XL (SDXL)', desc: 'SDXL variant; strong for high-res, detailed scenes.'},
		{name: 'Nova Anime XL', desc: 'Anime and illustration; vibrant, dynamic characters.'},
		{name: 'Dreamshaper', desc: 'Creative and dreamy outputs; good for artistic concepts.'},
		{name: 'Hentai Diffusion', desc: 'NSFW/anime erotica specialist.'},
		{name: 'CyberRealistic Pony', desc: 'Realistic with a cyberpunk twist.'},
		{name: 'Flux.1-Schnell fp8 (Compact)', desc: 'Newer, fast-generation model for quick results.'}
	]
	const DEFAULTS = {
		selectedProvider: 'Pollinations',
		loggingEnabled: false,
		// Prompt Styling
		mainPromptStyle: 'None',
		subPromptStyle: 'none',
		customStyleEnabled: false,
		customStyleText: '',
		// Global Negative Prompting
		enableNegPrompt: true,
		globalNegPrompt: 'ugly, blurry, deformed, disfigured, poor details, bad anatomy, low quality',
		// Google
		googleApiKey: '',
		model: 'imagen-4.0-generate-001',
		numberOfImages: 1,
		imageSize: '1024',
		aspectRatio: '1:1',
		personGeneration: 'allow_adult',
		// AI Horde
		aiHordeApiKey: '0000000000',
		aiHordeModel: 'AlbedoBase XL (SDXL)',
		aiHordeSampler: 'k_dpmpp_2m',
		aiHordeSteps: 25,
		aiHordeCfgScale: 7,
		aiHordeWidth: 512,
		aiHordeHeight: 512,
		aiHordePostProcessing: [],
		aiHordeSeed: '',
		// Pollinations.ai
		pollinationsModel: 'flux',
		pollinationsWidth: 512,
		pollinationsHeight: 512,
		pollinationsSeed: '',
		pollinationsEnhance: true,
		pollinationsNologo: false,
		pollinationsPrivate: false,
		pollinationsSafe: true,
		pollinationsToken: '',
		// OpenAI Compatible
		openAICompatProfiles: {},
		openAICompatActiveProfileUrl: '',
		openAICompatModelManualInput: false
	}

	// --- Logging Helpers ---
	let loggingEnabled = DEFAULTS.loggingEnabled
	async function updateLoggingStatus() {
		loggingEnabled = await GM_getValue('loggingEnabled', DEFAULTS.loggingEnabled)
	}
	function log(...args) {
		if (loggingEnabled) {
			console.log('[NIG]', ...args)
		}
	}

	async function getConfig() {
		const config = {}
		for (const key in DEFAULTS) {
			config[key] = await GM_getValue(key, DEFAULTS[key])
		}
		return config
	}
	async function setConfig(key, value) {
		await GM_setValue(key, value)
	}
	function downloadFile(filename, content, mimeType) {
		const blob = new Blob([content], {type: mimeType})
		const url = URL.createObjectURL(blob)
		const a = document.createElement('a')
		a.href = url
		a.download = filename
		document.body.appendChild(a)
		a.click()
		document.body.removeChild(a)
		URL.revokeObjectURL(url)
	}

	async function exportConfig() {
		const config = await getConfig()
		const configJson = JSON.stringify(config, null, 2)
		const date = new Date().toISOString().split('T')[0]
		downloadFile(`nig_config_${date}.json`, configJson, 'application/json')
		alert('Configuration exported successfully as a JSON file.')
	}

	async function importConfig(jsonString) {
		try {
			const importedConfig = JSON.parse(jsonString.trim())
			let importedCount = 0
			let errorCount = 0

			for (const key in importedConfig) {
				if (DEFAULTS.hasOwnProperty(key)) {
					await GM_setValue(key, importedConfig[key])
					importedCount++
				} else {
					console.warn(`[NIG] Skipping unknown configuration key: ${key}`)
					errorCount++
				}
			}

			if (importedCount > 0) {
				alert(
					`Configuration imported successfully! ${importedCount} settings updated. ${errorCount} unknown settings skipped.`
				)
				// Reload the form to reflect new settings and save them to ensure consistency
				await populateConfigForm()
				await saveConfig()
			} else {
				alert('Import failed: No valid configuration keys found in the provided JSON.')
			}
		} catch (e) {
			console.error('[NIG] Import failed:', e)
			alert('Import failed: Invalid JSON format. Please ensure the file content is valid JSON.')
		}
	}

	function handleImportFile(event) {
		const file = event.target.files[0]
		if (!file) {
			return
		}

		const reader = new FileReader()
		reader.onload = e => {
			importConfig(e.target.result)
			// Clear the file input after import
			event.target.value = ''
		}
		reader.onerror = () => {
			alert('Error reading file.')
			event.target.value = ''
		}
		reader.readAsText(file)
	}

	async function getHistory() {
		return JSON.parse(await GM_getValue('history', '[]'))
	}
	async function addToHistory(item) {
		const history = await getHistory()
		history.unshift(item)
		if (history.length > 100) history.pop()
		await GM_setValue('history', JSON.stringify(history))
	}

	// --- CACHE HELPERS ---
	const CACHE_EXPIRATION_MS = 24 * 60 * 60 * 1000 // 24 hours
	async function getCachedModels() {
		return JSON.parse(await GM_getValue('cachedModels', '{}'))
	}
	async function setCachedModels(provider, models) {
		const cache = await getCachedModels()
		cache[provider] = models
		await GM_setValue('cachedModels', JSON.stringify(cache))
	}
	async function clearCachedModels(provider = null) {
		if (provider) {
			const cache = await getCachedModels()
			delete cache[provider]
			await GM_setValue('cachedModels', JSON.stringify(cache))
			log(`Cleared cached models for ${provider}.`)
		} else {
			// Clear model lists for Pollinations and AI Horde
			await GM_setValue('cachedModels', '{}')

			// Clear model selection for OpenAI Compatible profiles
			const profiles = await GM_getValue('openAICompatProfiles', {})
			for (const url in profiles) {
				profiles[url].model = ''
			}
			await GM_setValue('openAICompatProfiles', profiles)
			await GM_setValue('openAICompatModelManualInput', false)
			await GM_setValue('openAICompatActiveProfileUrl', '')

			log('Cleared all cached models and reset OpenAI Compatible model selections.')
			alert('All cached models have been cleared. They will be re-fetched when you next open the settings.')
		}
	}

	// --- 3. UI ELEMENTS ---
	let generateBtn, configPanel, imageViewer, googleApiPrompt, pollinationsAuthPrompt, statusWidget, errorModal
	let currentSelection = ''

	function createUI() {
		const styleSheet = document.createElement('style')
		styleSheet.innerText = styles
		document.head.appendChild(styleSheet)
		// Add Google Material Symbols font
		const materialSymbolsLink = document.createElement('link')
		materialSymbolsLink.rel = 'stylesheet'
		materialSymbolsLink.href =
			'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0'
		document.head.appendChild(materialSymbolsLink)

		generateBtn = document.createElement('button')
		generateBtn.className = 'nig-button'
		generateBtn.innerHTML = '🎨 Generate Image'
		generateBtn.addEventListener('click', onGenerateClick)
		document.body.appendChild(generateBtn)
		statusWidget = document.createElement('div')
		statusWidget.id = 'nig-status-widget'
		statusWidget.className = 'nig-status-widget'
		statusWidget.innerHTML = `<div class="nig-status-icon"></div><span class="nig-status-text"></span>`
		document.body.appendChild(statusWidget)
	}

	function createConfigPanel() {
		if (document.getElementById('nig-config-panel')) return
		const googleSettingsHTML = `
            <div class="nig-form-group"><label for="nig-google-api-key">Gemini API Key</label><input type="password" id="nig-google-api-key"></div>
            <div class="nig-form-group"><label for="nig-model">Imagen Model</label><select id="nig-model"><option value="imagen-4.0-generate-001">Imagen 4.0 Standard</option><option value="imagen-4.0-ultra-generate-001">Imagen 4.0 Ultra</option><option value="imagen-4.0-fast-generate-001">Imagen 4.0 Fast</option><option value="imagen-3.0-generate-002">Imagen 3.0 Standard</option></select></div>
            <div class="nig-form-group"><label for="nig-num-images">Number of Images (1-4)</label><input type="number" id="nig-num-images" min="1" max="4" step="1"></div>
            <div class="nig-form-group"><label for="nig-image-size">Image Size</label><select id="nig-image-size"><option value="1024">1K</option><option value="2048">2K</option></select></div>
            <div class="nig-form-group"><label for="nig-aspect-ratio">Aspect Ratio</label><select id="nig-aspect-ratio"><option value="1:1">1:1</option><option value="3:4">3:4</option><option value="4:3">4:3</option><option value="9:16">9:16</option><option value="16:9">16:9</option></select></div>
            <div class="nig-form-group"><label for="nig-person-gen">Person Generation</label><select id="nig-person-gen"><option value="dont_allow">Don't Allow</option><option value="allow_adult">Allow Adults</option><option value="allow_all">Allow All</option></select></div>`
		const pollinationsSettingsHTML = `
            <div class="nig-form-group"><label for="nig-pollinations-model">Model</label><select id="nig-pollinations-model"><option>Loading models...</option></select></div>
            <div class="nig-form-group-inline">
                <div><label for="nig-pollinations-width">Width</label><input type="number" id="nig-pollinations-width" min="64" max="2048" step="64"></div>
                <div><label for="nig-pollinations-height">Height</label><input type="number" id="nig-pollinations-height" min="64" max="2048" step="64"></div>
            </div>
            <div class="nig-form-group"><label for="nig-pollinations-seed">Seed (optional)</label><input type="text" id="nig-pollinations-seed" placeholder="Leave blank for random"></div>
            <div class="nig-form-group"><label>Options</label>
                <div class="nig-checkbox-group">
                    <label><input type="checkbox" id="nig-pollinations-enhance">Enhance Prompt</label>
                    <label><input type="checkbox" id="nig-pollinations-safe">Safe Mode (NSFW Filter)</label>
                    <label><input type="checkbox" id="nig-pollinations-nologo">No Logo (Registered Users)</label>
                    <label><input type="checkbox" id="nig-pollinations-private">Private (Won't appear in feed)</label>
                </div>
            </div>
            <div class="nig-form-group">
                <label for="nig-pollinations-token">API Token (Optional)</label>
                <input type="password" id="nig-pollinations-token" placeholder="Enter token for premium models">
                <small class="nig-hint">Get a token from <a href="https://auth.pollinations.ai" target="_blank" class="nig-api-prompt-link">auth.pollinations.ai</a> for higher rate limits and access to restricted models.</small>
            </div>
        `
		const openAICompatSettingsHTML = `
            <div class="nig-form-group">
                <label for="nig-openai-compat-profile-select">Saved Profiles</label>
                <div class="nig-form-group-inline">
                    <select id="nig-openai-compat-profile-select"></select>
                    <button id="nig-openai-compat-delete-profile" class="nig-delete-btn">Delete</button>
                </div>
            </div>
            <div class="nig-form-group">
                <label for="nig-openai-compat-base-url">Base URL</label>
                <input type="text" id="nig-openai-compat-base-url" placeholder="e.g., https://api.example.com/v1">
                <small class="nig-hint">For a list of free public providers, check out the <a href="https://github.com/zukixa/cool-ai-stuff" target="_blank" class="nig-api-prompt-link">cool-ai-stuff</a> repository.</small>
            </div>
            <div class="nig-form-group">
                <label for="nig-openai-compat-api-key">API Key</label>
                <input type="password" id="nig-openai-compat-api-key">
            </div>
            <div class="nig-form-group">
                <label for="nig-openai-compat-model">Model</label>
                <div id="nig-openai-model-container-select">
                    <div class="nig-form-group-inline">
                        <select id="nig-openai-compat-model" style="width: 100%;"><option>Enter URL/Key and fetch...</option></select>
                        <button id="nig-openai-compat-fetch-models" class="nig-fetch-models-btn">Fetch</button>
                    </div>
                    <small class="nig-hint">If fetching fails or your model isn't listed, <a href="#" id="nig-openai-compat-switch-to-manual" class="nig-api-prompt-link">switch to manual input</a>.</small>
                </div>
                <div id="nig-openai-model-container-manual" style="display: none;">
                    <input type="text" id="nig-openai-compat-model-manual" placeholder="e.g., dall-e-3">
                    <small class="nig-hint">Manually enter the model name. <a href="#" id="nig-openai-compat-switch-to-select" class="nig-api-prompt-link">Switch back to fetched list</a>.</small>
                </div>
            </div>
        `
		configPanel = document.createElement('div')
		configPanel.id = 'nig-config-panel'
		configPanel.className = 'nig-modal-overlay'
		configPanel.innerHTML = `
            <div class="nig-modal-content">
                <span class="nig-close-btn">&times;</span><h2>Image Generator Configuration</h2>
                <div class="nig-tabs">
                    <div class="nig-tab active" data-tab="config">Configuration</div>
                    <div class="nig-tab" data-tab="styling">Prompt Styling</div>
                    <div class="nig-tab" data-tab="history">History</div>
                    <div class="nig-tab" data-tab="utilities">Utilities</div>
                </div>
                <div id="nig-config-tab" class="nig-tab-content active">
                    <div class="nig-config-grid">
                        <div class="nig-config-section">
                            <div class="nig-form-group">
                                <label for="nig-provider">Image Provider</label>
                                <select id="nig-provider">
                                    <option value="Pollinations">🌱 Pollinations.ai (Free, Simple)</option>
                                    <option value="AIHorde">🤖 AI Horde (Free, Advanced)</option>
                                    <option value="OpenAICompat">🔌 OpenAI Compatible (Custom)</option>
                                    <option value="Google">🖼️ Google Imagen (Requires Billed Account)</option>
                                </select>
                            </div>
                        </div>

                        <div class="nig-provider-container">
                            <div id="nig-provider-Pollinations" class="nig-provider-settings">
                                <div class="nig-provider-header">
                                    <h3>🌱 Pollinations.ai Settings</h3>
                                    <p>Fast, simple image generation with advanced model options</p>
                                </div>
                                ${pollinationsSettingsHTML}
                            </div>

                            <div id="nig-provider-AIHorde" class="nig-provider-settings">
                                <div class="nig-provider-header">
                                    <h3>🤖 AI Horde Settings</h3>
                                    <p>Community-powered generation with extensive customization</p>
                                </div>
                                <div class="nig-form-group">
                                    <label for="nig-horde-api-key">AI Horde API Key</label>
                                    <input type="password" id="nig-horde-api-key" placeholder="Defaults to '0000000000'">
                                    <small>Use anonymous key or get your own from <a href="https://aihorde.net/" target="_blank" class="nig-api-prompt-link">AI Horde</a> for higher priority.</small>
                                </div>
                                <div class="nig-provider-controls">
                                    <div class="nig-form-group">
                                        <label for="nig-horde-model">Model</label>
                                        <select id="nig-horde-model"><option>Loading models...</option></select>
                                    </div>
                                    <div class="nig-form-group">
                                        <label for="nig-horde-sampler">Sampler</label>
                                        <select id="nig-horde-sampler">
                                            <option value="k_dpmpp_2m">DPM++ 2M</option>
                                            <option value="k_euler_a">Euler A</option>
                                            <option value="k_euler">Euler</option>
                                            <option value="k_lms">LMS</option>
                                            <option value="k_heun">Heun</option>
                                            <option value="k_dpm_2">DPM 2</option>
                                            <option value="k_dpm_2_a">DPM 2 A</option>
                                            <option value="k_dpmpp_2s_a">DPM++ 2S A</option>
                                            <option value="k_dpmpp_sde">DPM++ SDE</option>
                                        </select>
                                    </div>
                                </div>
                                <div class="nig-form-grid">
                                    <div class="nig-form-group">
                                        <label for="nig-horde-steps">Steps</label>
                                        <input type="number" id="nig-horde-steps" min="10" max="50" step="1">
                                        <small class="nig-hint">More steps = more detail, but slower.</small>
                                    </div>
                                    <div class="nig-form-group">
                                        <label for="nig-horde-cfg">CFG Scale</label>
                                        <input type="number" id="nig-horde-cfg" min="1" max="20" step="0.5">
                                        <small class="nig-hint">How strictly to follow the prompt.</small>
                                    </div>
                                </div>
                                <div class="nig-form-grid">
                                    <div class="nig-form-group">
                                        <label for="nig-horde-width">Width</label>
                                        <input type="number" id="nig-horde-width" min="64" max="2048" step="64">
                                    </div>
                                    <div class="nig-form-group">
                                        <label for="nig-horde-height">Height</label>
                                        <input type="number" id="nig-horde-height" min="64" max="2048" step="64">
                                    </div>
                                </div>
                                <div class="nig-form-group">
                                    <label for="nig-horde-seed">Seed (optional)</label>
                                    <input type="text" id="nig-horde-seed" placeholder="Leave blank for random">
                                </div>
                                <div class="nig-form-group">
                                    <label>Post-Processing</label>
                                    <small class="nig-hint">Improves faces. Use only if generating people.</small>
                                    <div class="nig-checkbox-group">
                                        <label><input type="checkbox" name="nig-horde-post" value="GFPGAN">GFPGAN</label>
                                        <label><input type="checkbox" name="nig-horde-post" value="CodeFormers">CodeFormers</label>
                                    </div>
                                </div>
                            </div>

                            <div id="nig-provider-Google" class="nig-provider-settings">
                                <div class="nig-provider-header">
                                    <h3>🖼️ Google Imagen Settings</h3>
                                    <p>High-quality generation powered by Google's advanced AI</p>
                                </div>
                                ${googleSettingsHTML}
                            </div>

                            <div id="nig-provider-OpenAICompat" class="nig-provider-settings">
                                <div class="nig-provider-header">
                                    <h3>🔌 OpenAI Compatible Settings</h3>
                                    <p>Connect to any OpenAI-compatible API endpoint</p>
                                </div>
                                ${openAICompatSettingsHTML}
                            </div>
                        </div>
                    </div>
                </div>

                <div id="nig-styling-tab" class="nig-tab-content">
                    <div class="nig-styling-container">
                        <div class="nig-styling-intro">
                            <p>Select a style to automatically add it to the beginning of every prompt. This helps maintain a consistent look across all providers.</p>
                        </div>

                        <div class="nig-style-grid">
                            <div class="nig-style-section">
                                <div class="nig-form-group">
                                    <label for="nig-main-style">Main Style</label>
                                    <select id="nig-main-style"></select>
                                    <small id="nig-main-style-desc" class="nig-hint"></small>
                                </div>
                                <div class="nig-form-group">
                                    <label for="nig-sub-style">Sub-Style</label>
                                    <select id="nig-sub-style"></select>
                                    <small id="nig-sub-style-desc" class="nig-hint"></small>
                                </div>
                            </div>

                            <div class="nig-style-section">
                                <div class="nig-section-header">
                                    <h4>Custom Style</h4>
                                </div>
                                <div class="nig-form-group">
                                    <div class="nig-checkbox-group">
                                        <label><input type="checkbox" id="nig-custom-style-enable">Enable Custom Style</label>
                                    </div>
                                    <small class="nig-hint">Overrides the Main/Sub-style dropdowns with your own text.</small>
                                    <textarea id="nig-custom-style-text" placeholder="e.g., In the style of Van Gogh, oil painting, ..."></textarea>
                                </div>
                            </div>

                            <div class="nig-style-section">
                                <div class="nig-section-header">
                                    <h4>Negative Prompting (Global)</h4>
                                </div>
                                <div class="nig-form-group">
                                    <div class="nig-checkbox-group">
                                        <label><input type="checkbox" id="nig-enable-neg-prompt">Enable Negative Prompting</label>
                                    </div>
                                    <small class="nig-hint">This negative prompt will be applied to all providers when enabled.</small>
                                    <textarea id="nig-global-neg-prompt" placeholder="e.g., ugly, blurry, deformed, disfigured, poor details, bad anatomy, low quality"></textarea>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <div id="nig-history-tab" class="nig-tab-content">
                    <div class="nig-history-container">
                        <div class="nig-history-cleanup">
                            <div class="nig-cleanup-info">
                                <h4>History Management</h4>
                                <p>Clean up old history entries to free up space and improve performance.</p>
                            </div>
                            <div class="nig-cleanup-controls">
                                <label>Delete history older than</label>
                                <input type="number" id="nig-history-clean-days" min="1" max="365" value="30">
                                <label>days</label>
                                <button id="nig-history-clean-btn" class="nig-history-cleanup-btn">
                                    <span class="material-symbols-outlined">cleaning_services</span>
                                    Clean
                                </button>
                            </div>
                        </div>
                        <ul id="nig-history-list" class="nig-history-list"></ul>
                    </div>
                </div>
                <div id="nig-utilities-tab" class="nig-tab-content">
                    <div class="nig-utilities-grid">
                        <div class="nig-utility-card">
                            <h4>Import/Export Settings</h4>
                            <p>Backup and restore your configuration settings for seamless setup across different sessions or devices.</p>
                            <div class="nig-form-group">
                                <button id="nig-export-btn" class="nig-save-btn" style="background-color: var(--nig-color-accent-primary);">
                                    <span class="material-symbols-outlined">download</span>
                                    Download Configuration
                                </button>
                                <small class="nig-hint">Downloads the current configuration as a JSON file.</small>
                            </div>
                            <div class="nig-form-group">
                                <label for="nig-import-file">Import Configuration</label>
                                <input type="file" id="nig-import-file" accept=".json" style="border: 2px dashed var(--nig-color-border); background: var(--nig-color-bg-primary);">
                                <small class=" nig-hint">Uploading a JSON file will overwrite all current settings.</small>
                            </div>
                        </div>

                        <div class="nig-utility-card">
                            <h4>Cache Management</h4>
                            <p>Clear cached model lists and force fresh data fetching for accurate, up-to-date information.</p>
                            <button id="nig-clear-cache-btn" class="nig-save-btn" style="background-color: var(--nig-color-accent-error);">
                                <span class="material-symbols-outlined">clear_all</span>
                                Clear Cached Models
                            </button>
                            <small class="nig-hint">Removes all cached model lists forcing a fresh fetch.</small>
                        </div>

                        <div class="nig-utility-card">
                            <h4>Debug Console</h4>
                            <p>Enable detailed console logging to troubleshoot issues and monitor system behavior during development.</p>
                            <button id="nig-toggle-logging-btn" class="nig-save-btn" style="background-color: var(--nig-color-accent-warning);">
                                <span class="material-symbols-outlined">bug_report</span>
                                Toggle Console Logging
                            </button>
                            <small class="nig-hint">Toggles detailed console logging for debugging purposes.</small>
                        </div>
                    </div>
                </div>
                <div class="nig-button-footer">
                    <button id="nig-save-btn" class="nig-save-btn">Save Configuration</button>
                </div>
            </div>`
		document.body.appendChild(configPanel)
		configPanel.querySelector('.nig-close-btn').addEventListener('click', () => (configPanel.style.display = 'none'))
		configPanel.querySelector('#nig-save-btn').addEventListener('click', saveConfig)
		configPanel.querySelectorAll('.nig-tab').forEach(tab => {
			tab.addEventListener('click', async () => {
				configPanel.querySelectorAll('.nig-tab, .nig-tab-content').forEach(el => el.classList.remove('active'))
				tab.classList.add('active')
				configPanel.querySelector(`#nig-${tab.dataset.tab}-tab`).classList.add('active')
				if (tab.dataset.tab === 'history') {
					await populateHistoryTab()
					configPanel.querySelector('#nig-save-btn').style.display = 'none'
				} else {
					configPanel.querySelector('#nig-save-btn').style.display = 'block'
				}
			})
		})
		configPanel.querySelector('#nig-provider').addEventListener('change', updateVisibleSettings)
		configPanel
			.querySelector('#nig-openai-compat-fetch-models')
			.addEventListener('click', () => fetchOpenAICompatModels())
		configPanel.querySelector('#nig-openai-compat-profile-select').addEventListener('change', loadSelectedOpenAIProfile)
		configPanel.querySelector('#nig-openai-compat-delete-profile').addEventListener('click', deleteSelectedOpenAIProfile)
		configPanel.querySelector('#nig-export-btn').addEventListener('click', exportConfig)
		configPanel.querySelector('#nig-import-file').addEventListener('change', handleImportFile)
		configPanel.querySelector('#nig-history-clean-btn').addEventListener('click', cleanHistory)
		configPanel.querySelector('#nig-clear-cache-btn').addEventListener('click', () => clearCachedModels())
		configPanel.querySelector('#nig-toggle-logging-btn').addEventListener('click', async () => {
			const currentState = await GM_getValue('loggingEnabled', DEFAULTS.loggingEnabled)
			const newState = !currentState
			await GM_setValue('loggingEnabled', newState)
			await updateLoggingStatus()
			alert(`Image Generator logging is now ${newState ? 'ENABLED' : 'DISABLED'}.`)
		})
		const customStyleEnable = configPanel.querySelector('#nig-custom-style-enable')
		const customStyleText = configPanel.querySelector('#nig-custom-style-text')
		customStyleEnable.addEventListener('change', () => {
			customStyleText.disabled = !customStyleEnable.checked
		})

		const switchToManual = e => {
			e.preventDefault()
			document.getElementById('nig-openai-model-container-select').style.display = 'none'
			document.getElementById('nig-openai-model-container-manual').style.display = 'block'
		}
		const switchToSelect = e => {
			e.preventDefault()
			document.getElementById('nig-openai-model-container-select').style.display = 'block'
			document.getElementById('nig-openai-model-container-manual').style.display = 'none'
		}
		configPanel.querySelector('#nig-openai-compat-switch-to-manual').addEventListener('click', switchToManual)
		configPanel.querySelector('#nig-openai-compat-switch-to-select').addEventListener('click', switchToSelect)
	}

	// --- 4. CORE LOGIC ---
	let generationQueue = []
	let completedQueue = []
	let isGenerating = false
	let currentGenerationStatusText = ''
	let errorQueue = []
	let isErrorModalVisible = false

	function createErrorModal() {
		if (document.getElementById('nig-error-modal')) return
		errorModal = document.createElement('div')
		errorModal.id = 'nig-error-modal'
		errorModal.className = 'nig-modal-overlay'
		errorModal.style.display = 'none'
		errorModal.innerHTML = `
            <div class="nig-modal-content">
                <span class="nig-close-btn">&times;</span>
                <h2>Generation Failed</h2>
                <p>The image could not be generated. Please review the reason below and adjust your prompt if necessary.</p>
                <p><strong>Reason:</strong></p>
                <div id="nig-error-reason"></div>
                <p><strong>Your Prompt:</strong></p>
                <textarea id="nig-error-prompt" class="nig-error-prompt"></textarea>
                <div class="nig-form-group" style="margin-top: 15px;">
                    <label for="nig-retry-provider-select">Retry with Provider:</label>
                    <select id="nig-retry-provider-select"></select>
                </div>
                <div id="nig-error-actions" class="nig-error-actions"></div>
            </div>`
		document.body.appendChild(errorModal)
		errorModal.querySelector('.nig-close-btn').addEventListener('click', closeErrorModal)
	}

	function closeErrorModal() {
		if (errorModal) {
			const promptTextarea = document.getElementById('nig-error-prompt')
			if (promptTextarea && promptTextarea._nig_inputListener) {
				promptTextarea.removeEventListener('input', promptTextarea._nig_inputListener)
				delete promptTextarea._nig_inputListener
			}
			const providerSelect = document.getElementById('nig-retry-provider-select')
			if (providerSelect && providerSelect._nig_changeListener) {
				providerSelect.removeEventListener('change', providerSelect._nig_changeListener)
				delete providerSelect._nig_changeListener
			}
			errorModal.style.display = 'none'
		}
		isErrorModalVisible = false
		showNextError()
	}

	function showNextError() {
		if (isErrorModalVisible || errorQueue.length === 0) {
			return
		}
		const errorToShow = errorQueue.shift()
		showErrorModal(errorToShow)
	}

	async function showErrorModal(errorDetails) {
		if (!errorModal) createErrorModal()
		isErrorModalVisible = true

		document.getElementById('nig-error-reason').textContent = errorDetails.reason.message
		const promptTextarea = document.getElementById('nig-error-prompt')
		promptTextarea.value = errorDetails.prompt

		// Populate provider dropdown
		const providerSelect = document.getElementById('nig-retry-provider-select')
		providerSelect.innerHTML = ''
		const config = await getConfig()
		const providers = ['Pollinations', 'AIHorde', 'Google']
		providers.forEach(p => {
			const option = document.createElement('option')
			option.value = p
			option.textContent = p
			providerSelect.appendChild(option)
		})
		Object.keys(config.openAICompatProfiles).forEach(url => {
			const option = document.createElement('option')
			option.value = `OpenAICompat::${url}`
			option.textContent = `OpenAI: ${url.replace('https://', '').split('/')[0]}`
			providerSelect.appendChild(option)
		})

		// Pre-select the failed provider
		let failedProviderValue = errorDetails.provider
		if (errorDetails.provider === 'OpenAICompat' && errorDetails.providerProfileUrl) {
			failedProviderValue = `OpenAICompat::${errorDetails.providerProfileUrl}`
		}
		if (Array.from(providerSelect.options).some(opt => opt.value === failedProviderValue)) {
			providerSelect.value = failedProviderValue
		}

		const actionsContainer = document.getElementById('nig-error-actions')
		actionsContainer.innerHTML = ''

		const retryBtn = document.createElement('button')
		retryBtn.textContent = 'Retry Generation'
		retryBtn.className = 'nig-retry-btn'
		retryBtn.onclick = async () => {
			const editedPrompt = promptTextarea.value.trim()
			const selectedProviderValue = providerSelect.value
			let provider, providerProfileUrl

			if (selectedProviderValue.startsWith('OpenAICompat::')) {
				provider = 'OpenAICompat'
				providerProfileUrl = selectedProviderValue.split('::')[1]
			} else {
				provider = selectedProviderValue
				providerProfileUrl = null
			}

			if (editedPrompt) {
				generationQueue.unshift({prompt: editedPrompt, provider, providerProfileUrl})
				isGenerating = false
				closeErrorModal()
				updateSystemStatus()
				processQueue()
			} else {
				alert('Prompt cannot be empty.')
			}
		}

		if (errorDetails.reason.retryable) {
			actionsContainer.appendChild(retryBtn)
		} else {
			const showRetryButton = () => {
				if (!actionsContainer.contains(retryBtn)) {
					actionsContainer.appendChild(retryBtn)
				}
			}
			promptTextarea.addEventListener('input', showRetryButton)
			promptTextarea._nig_inputListener = showRetryButton
			providerSelect.addEventListener('change', showRetryButton)
			providerSelect._nig_changeListener = showRetryButton
		}

		errorModal.style.display = 'flex'
	}

	function parseErrorMessage(errorString) {
		let messageContent = String(errorString)
		const lowerCaseContent = messageContent.toLowerCase()

		if (
			lowerCaseContent.includes('error code: 524') ||
			lowerCaseContent.includes('timed out') ||
			lowerCaseContent.includes('502 bad gateway') ||
			lowerCaseContent.includes('unable to reach the origin service')
		) {
			return {
				message:
					'The generation service is temporarily unavailable or busy (e.g., 502 Bad Gateway). This is usually a temporary issue. Please try again in a few minutes.',
				retryable: true
			}
		}
		if (
			lowerCaseContent.includes('unsafe content') ||
			lowerCaseContent.includes('safety system') ||
			lowerCaseContent.includes('moderation_blocked') ||
			lowerCaseContent.includes('violence') ||
			lowerCaseContent.includes('sexual')
		) {
			try {
				const errorJson = JSON.parse(messageContent.substring(messageContent.indexOf('{')))
				let specificMessage = errorJson.message || (errorJson.error ? errorJson.error.message : null)
				if (specificMessage) {
					specificMessage = specificMessage.replace(/If you believe this is an error, contact us at.*$/, '').trim()
					return {message: specificMessage, retryable: false}
				}
			} catch (e) {
				/* Fall through */
			}
			return {
				message: 'The prompt was rejected by the safety system for containing potentially unsafe content.',
				retryable: false
			}
		}
		try {
			const errorJson = JSON.parse(messageContent.substring(messageContent.indexOf('{')))
			const message = errorJson.message || (errorJson.error ? errorJson.error.message : null) || JSON.stringify(errorJson)
			return {message: typeof message === 'object' ? JSON.stringify(message) : message, retryable: false}
		} catch (e) {
			return {message: messageContent || 'An unknown error occurred.', retryable: false}
		}
	}

	function updateStatusWidget(state, text, onClickHandler = null) {
		if (!statusWidget) return
		statusWidget.classList.remove('loading', 'success', 'error')
		statusWidget.onclick = onClickHandler

		if (state === 'hidden') {
			statusWidget.style.display = 'none'
			return
		}
		statusWidget.style.display = 'flex'
		statusWidget.querySelector('.nig-status-text').textContent = text
		statusWidget.classList.add(state)
		const icon = statusWidget.querySelector('.nig-status-icon')
		icon.innerHTML = ''
		if (state === 'success') {
			icon.innerHTML = '✅'
		} else if (state === 'error') {
			icon.innerHTML = '❌'
		}
	}

	function updateSystemStatus() {
		if (completedQueue.length > 0) {
			const text =
				completedQueue.length === 1
					? '1 Image Ready! Click to view.'
					: `${completedQueue.length} Images Ready! Click to view.`
			updateStatusWidget('success', text, () => {
				const result = completedQueue.shift()
				if (result) {
					showImageViewer(result.imageUrls, result.prompt, result.provider)
				}
				updateSystemStatus()
			})
		} else if (isGenerating || generationQueue.length > 0) {
			const queueText = generationQueue.length > 0 ? ` (Queue: ${generationQueue.length})` : ''
			updateStatusWidget('loading', `${currentGenerationStatusText}${queueText}`)
		} else {
			updateStatusWidget('hidden', '')
		}
	}

	function handleGenerationSuccess(displayUrls, prompt, provider, model, persistentUrls = null) {
		completedQueue.push({imageUrls: displayUrls, prompt, provider})
		const historyUrls = persistentUrls || displayUrls
		historyUrls.forEach(url => addToHistory({date: new Date().toISOString(), prompt, url, provider, model}))
		isGenerating = false
		updateSystemStatus()
		processQueue()
	}

	function handleGenerationFailure(errorMessage, prompt = 'Unknown', provider, providerProfileUrl = null) {
		console.error(`Generation Failed for prompt "${prompt}" with ${provider}:`, errorMessage)
		const friendlyError = parseErrorMessage(errorMessage)
		errorQueue.push({reason: friendlyError, prompt, provider, providerProfileUrl})
		showNextError()
		updateStatusWidget('error', 'Generation Failed.')
		isGenerating = false
		setTimeout(() => {
			updateSystemStatus()
			processQueue()
		}, 3000)
	}

	async function processQueue() {
		if (isGenerating || generationQueue.length === 0) {
			return
		}
		isGenerating = true
		const request = generationQueue.shift()
		currentGenerationStatusText = 'Requesting...'
		updateSystemStatus()

		const config = await getConfig()
		if (request.provider === 'Google') {
			generateImageGoogle(request.prompt)
		} else if (request.provider === 'AIHorde') {
			generateImageAIHorde(request.prompt)
		} else if (request.provider === 'Pollinations') {
			generateImagePollinations(request.prompt)
		} else if (request.provider === 'OpenAICompat') {
			generateImageOpenAICompat(request.prompt, request.providerProfileUrl)
		}
	}

	function handleSelection() {
		const selection = window.getSelection()
		if (!selection || selection.rangeCount === 0) {
			generateBtn.style.display = 'none'
			return
		}
		const selectedText = selection.toString().trim()

		if (selectedText.length > 5) {
			currentSelection = selectedText
			const range = selection.getRangeAt(0)
			const rects = range.getClientRects()

			if (rects.length === 0) {
				generateBtn.style.display = 'none'
				return
			}

			const firstRect = rects[0]
			const lastRect = rects[rects.length - 1]

			generateBtn.style.display = 'block'
			generateBtn.style.visibility = 'hidden'
			const buttonHeight = generateBtn.offsetHeight
			generateBtn.style.visibility = 'visible'

			let topPosition = window.scrollY + firstRect.top - buttonHeight - 5

			if (topPosition < window.scrollY) {
				topPosition = window.scrollY + lastRect.bottom + 5
			}

			generateBtn.style.top = `${topPosition}px`
			generateBtn.style.left = `${window.scrollX + firstRect.left}px`
		} else {
			generateBtn.style.display = 'none'
		}
	}

	async function onGenerateClick() {
		generateBtn.style.display = 'none'

		if (window.getSelection) {
			window.getSelection().removeAllRanges()
		}

		if (!currentSelection) return
		const config = await getConfig()

		let finalPrompt = currentSelection
		let prefix = ''
		if (config.customStyleEnabled && config.customStyleText) {
			prefix = config.customStyleText.trim()
			if (prefix && !prefix.endsWith(', ')) prefix += ', '
		} else if (config.mainPromptStyle !== 'None') {
			if (config.subPromptStyle && config.subPromptStyle !== 'none') {
				prefix = config.subPromptStyle
			} else {
				prefix = `${config.mainPromptStyle} style, `
			}
		}
		finalPrompt = prefix + finalPrompt

		// Append global negative prompt to the main prompt string for providers that don't support a separate field
		if (config.enableNegPrompt && config.globalNegPrompt && config.selectedProvider !== 'AIHorde') {
			finalPrompt += `, negative prompt: ${config.globalNegPrompt}`
		}

		if (config.selectedProvider === 'Google' && !config.googleApiKey) {
			showGoogleApiKeyPrompt()
			return
		}
		generationQueue.push({
			prompt: finalPrompt,
			provider: config.selectedProvider,
			providerProfileUrl: config.openAICompatActiveProfileUrl
		})
		updateSystemStatus()
		processQueue()
	}

	function createImageViewer() {
		if (document.getElementById('nig-image-viewer')) return
		imageViewer = document.createElement('div')
		imageViewer.id = 'nig-image-viewer'
		imageViewer.className = 'nig-modal-overlay'
		imageViewer.style.display = 'none'
		imageViewer.innerHTML = `
            <div class="nig-modal-content">
                <span class="nig-close-btn">&times;</span>
                <div id="nig-prompt-container" class="nig-prompt-container">
                    <div class="nig-prompt-header"><span>Generated Image Prompt</span></div>
                    <p id="nig-prompt-text" class="nig-prompt-text"></p>
                </div>
                <div id="nig-image-gallery" class="nig-image-gallery"></div>
            </div>`
		document.body.appendChild(imageViewer)
		imageViewer.querySelector('.nig-close-btn').addEventListener('click', () => {
			imageViewer.style.display = 'none'
			updateSystemStatus()
		})
		const promptContainer = imageViewer.querySelector('#nig-prompt-container')
		promptContainer.addEventListener('click', () => {
			promptContainer.classList.toggle('expanded')
		})
	}

	function showGoogleApiKeyPrompt() {
		if (document.getElementById('nig-google-api-prompt')) return
		googleApiPrompt = document.createElement('div')
		googleApiPrompt.id = 'nig-google-api-prompt'
		googleApiPrompt.className = 'nig-modal-overlay'
		googleApiPrompt.innerHTML = `<div class="nig-modal-content"><span class="nig-close-btn">&times;</span><h2>Google API Key Required</h2><p>Please provide your Google AI Gemini API key. You can get one from <a href="https://aistudio.google.com/api-keys" target="_blank" class="nig-api-prompt-link">Google AI Studio</a>.</p><div class="nig-form-group"><label for="nig-prompt-api-key">Gemini API Key</label><input type="password" id="nig-prompt-api-key"></div><button id="nig-prompt-save-btn" class="nig-save-btn">Save Key</button></div>`
		document.body.appendChild(googleApiPrompt)
		googleApiPrompt.querySelector('.nig-close-btn').addEventListener('click', () => googleApiPrompt.remove())
		googleApiPrompt.querySelector('#nig-prompt-save-btn').addEventListener('click', async () => {
			const key = googleApiPrompt.querySelector('#nig-prompt-api-key').value.trim()
			if (key) {
				await setConfig('googleApiKey', key)
				googleApiPrompt.remove()
				alert('API Key saved. You can now generate an image.')
			} else {
				alert('API Key cannot be empty.')
			}
		})
	}

	function showPollinationsAuthPrompt(errorMessage, failedPrompt) {
		if (document.getElementById('nig-pollinations-auth-prompt')) return
		pollinationsAuthPrompt = document.createElement('div')
		pollinationsAuthPrompt.id = 'nig-pollinations-auth-prompt'
		pollinationsAuthPrompt.className = 'nig-modal-overlay'
		pollinationsAuthPrompt.innerHTML = `
            <div class="nig-modal-content">
                <span class="nig-close-btn">&times;</span>
                <h2>Authentication Required</h2>
                <p>The Pollinations.ai model you selected requires authentication. You can get free access by registering.</p>
                <p><strong>Error Message:</strong> <em>${errorMessage}</em></p>
                <p>Please visit <a href="https://auth.pollinations.ai" target="_blank" class="nig-api-prompt-link">auth.pollinations.ai</a> to continue. You can either:</p>
                <ul>
                    <li><strong>Register the Referrer:</strong> The easiest method. Just register the domain <code>wtr-lab.com</code>. This links your usage to your account without needing a token.</li>
                    <li><strong>Use a Token:</strong> Get an API token and enter it below.</li>
                </ul>
                <div class="nig-form-group">
                    <label for="nig-prompt-pollinations-token">Pollinations API Token</label>
                    <input type="password" id="nig-prompt-pollinations-token">
                </div>
                <button id="nig-prompt-save-token-btn" class="nig-save-btn">Save Token & Retry</button>
            </div>`
		document.body.appendChild(pollinationsAuthPrompt)

		pollinationsAuthPrompt
			.querySelector('.nig-close-btn')
			.addEventListener('click', () => pollinationsAuthPrompt.remove())
		pollinationsAuthPrompt.querySelector('#nig-prompt-save-token-btn').addEventListener('click', async () => {
			const token = pollinationsAuthPrompt.querySelector('#nig-prompt-pollinations-token').value.trim()
			if (token) {
				await setConfig('pollinationsToken', token)
				pollinationsAuthPrompt.remove()
				alert('Token saved. Retrying generation...')
				generationQueue.unshift({prompt: failedPrompt, provider: 'Pollinations'})
				isGenerating = false
				processQueue()
			} else {
				alert('Token cannot be empty.')
			}
		})
	}

	function showImageViewer(imageUrls, prompt, provider) {
		if (!imageViewer) createImageViewer()
		const gallery = imageViewer.querySelector('#nig-image-gallery')
		gallery.innerHTML = ''
		const promptContainer = imageViewer.querySelector('#nig-prompt-container')
		const promptText = imageViewer.querySelector('#nig-prompt-text')
		promptText.textContent = prompt
		promptContainer.classList.remove('expanded')
		const extension = provider === 'Pollinations' || provider === 'OpenAICompat' ? 'jpg' : 'png'
		imageUrls.forEach((url, index) => {
			const container = document.createElement('div')
			container.className = 'nig-image-container'
			const img = document.createElement('img')
			img.src = url
			const actions = document.createElement('div')
			actions.className = 'nig-image-actions'
			const downloadBtn = document.createElement('button')
			downloadBtn.innerHTML = '<span class="material-symbols-outlined">download</span>'
			downloadBtn.title = 'Download'
			downloadBtn.onclick = () => {
				const a = document.createElement('a')
				a.href = url
				a.download = `${prompt.substring(0, 20).replace(/\s/g, '_')}_${index}.${extension}`
				a.click()
			}
			const fullscreenBtn = document.createElement('button')
			fullscreenBtn.innerHTML = '<span class="material-symbols-outlined">fullscreen</span>'
			fullscreenBtn.title = 'Fullscreen'
			fullscreenBtn.onclick = () => {
				if (img.requestFullscreen) img.requestFullscreen()
			}
			actions.appendChild(downloadBtn)
			actions.appendChild(fullscreenBtn)
			container.appendChild(img)
			container.appendChild(actions)
			gallery.appendChild(container)
		})
		imageViewer.style.display = 'flex'
	}

	async function generateImageGoogle(prompt) {
		currentGenerationStatusText = 'Generating with Google...'
		updateSystemStatus()
		const config = await getConfig()
		const model = config.model
		const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:predict`
		const parameters = {
			sampleCount: parseInt(config.numberOfImages, 10),
			aspectRatio: config.aspectRatio,
			personGeneration: config.personGeneration
		}
		if (!model.includes('fast')) {
			parameters.imageSize = parseInt(config.imageSize, 10)
		}
		GM_xmlhttpRequest({
			method: 'POST',
			url,
			headers: {'x-goog-api-key': config.googleApiKey, 'Content-Type': 'application/json'},
			data: JSON.stringify({instances: [{prompt}], parameters}),
			onload: response => {
				try {
					const data = JSON.parse(response.responseText)
					if (data.error) throw new Error(JSON.stringify(data.error))
					const imageUrls = data.predictions.map(p => `data:image/png;base64,${p.bytesB64Encoded}`)
					handleGenerationSuccess(imageUrls, prompt, 'Google', model)
				} catch (e) {
					handleGenerationFailure(e.message, prompt, 'Google')
				}
			},
			onerror: error => {
				handleGenerationFailure(JSON.stringify(error), prompt, 'Google')
			}
		})
	}

	async function generateImageAIHorde(prompt) {
		const config = await getConfig()
		const apiKey = config.aiHordeApiKey || '0000000000'
		const model = config.aiHordeModel
		const params = {
			shared: true,
			sampler_name: config.aiHordeSampler,
			cfg_scale: parseFloat(config.aiHordeCfgScale),
			steps: parseInt(config.aiHordeSteps, 10),
			width: parseInt(config.aiHordeWidth, 10),
			height: parseInt(config.aiHordeHeight, 10)
		}
		if (config.aiHordeSeed) params.seed = config.aiHordeSeed
		if (config.aiHordePostProcessing.length > 0) params.post_processing = config.aiHordePostProcessing
		const payload = {prompt: prompt, params: params, models: [model]}
		if (config.enableNegPrompt && config.globalNegPrompt) payload.negative_prompt = config.globalNegPrompt
		GM_xmlhttpRequest({
			method: 'POST',
			url: 'https://aihorde.net/api/v2/generate/async',
			headers: {'Content-Type': 'application/json', apikey: apiKey},
			data: JSON.stringify(payload),
			onload: response => {
				try {
					const data = JSON.parse(response.responseText)
					if (data.id) {
						checkAIHordeStatus(data.id, prompt, Date.now(), model)
					} else {
						if (data.message && data.message.toLowerCase().includes('model')) {
							handleGenerationFailure(`Model error: ${data.message}. Refreshing model list.`, prompt, 'AIHorde')
							clearCachedModels('aiHorde')
							return
						}
						throw new Error(data.message || 'Failed to initiate generation.')
					}
				} catch (e) {
					handleGenerationFailure(e.message, prompt, 'AIHorde')
				}
			},
			onerror: error => {
				handleGenerationFailure(JSON.stringify(error), prompt, 'AIHorde')
			}
		})
	}

	function checkAIHordeStatus(id, prompt, startTime, model) {
		if (Date.now() - startTime > 300000) {
			handleGenerationFailure('Timed out after 5 minutes.', prompt, 'AIHorde')
			return
		}
		GM_xmlhttpRequest({
			method: 'GET',
			url: `https://aihorde.net/api/v2/generate/status/${id}`,
			onload: response => {
				try {
					const data = JSON.parse(response.responseText)
					if (data.done) {
						const imageUrls = data.generations.map(gen => gen.img)
						handleGenerationSuccess(imageUrls, prompt, 'AIHorde', model)
					} else {
						let statusText = `Waiting for worker...`
						if (data.queue_position > 0) {
							statusText = `Queue: ${data.queue_position}. Est: ${data.wait_time}s.`
						}
						if (data.processing > 0) {
							statusText = `Generating...`
						}
						currentGenerationStatusText = statusText
						updateSystemStatus()
						setTimeout(() => checkAIHordeStatus(id, prompt, startTime, model), 5000)
					}
				} catch (e) {
					handleGenerationFailure(`Error checking status: ${e.message}`, prompt, 'AIHorde')
				}
			},
			onerror: error => {
				handleGenerationFailure('Failed to get status from AI Horde.', prompt, 'AIHorde')
			}
		})
	}

	async function generateImagePollinations(prompt) {
		currentGenerationStatusText = 'Generating with Pollinations...'
		updateSystemStatus()
		const config = await getConfig()
		const model = config.pollinationsModel
		const encodedPrompt = encodeURIComponent(prompt)
		const params = new URLSearchParams()

		if (config.pollinationsToken) params.append('token', config.pollinationsToken)
		if (model && model !== 'flux') params.append('model', model)
		if (config.pollinationsWidth && config.pollinationsWidth != 1024) params.append('width', config.pollinationsWidth)
		if (config.pollinationsHeight && config.pollinationsHeight != 1024) params.append('height', config.pollinationsHeight)
		if (config.pollinationsSeed) params.append('seed', config.pollinationsSeed)
		if (config.pollinationsEnhance) params.append('enhance', 'true')
		if (config.pollinationsSafe) params.append('safe', 'true')
		if (config.pollinationsNologo) params.append('nologo', 'true')
		if (config.pollinationsPrivate) params.append('private', 'true')

		const paramString = params.toString()
		const url = `https://image.pollinations.ai/prompt/${encodedPrompt}${paramString ? '?' + paramString : ''}`

		GM_xmlhttpRequest({
			method: 'GET',
			url: url,
			responseType: 'blob',
			headers: {
				'User-Agent':
					'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36'
			},
			onload: response => {
				if (response.status >= 200 && response.status < 300) {
					const blobUrl = URL.createObjectURL(response.response)
					handleGenerationSuccess([blobUrl], prompt, 'Pollinations', model, [url])
				} else {
					const handleErrorResponse = async () => {
						try {
							const text = await response.response.text()
							if (text.toLowerCase().includes('model not found')) {
								handleGenerationFailure(`Model error: ${text}. Refreshing model list.`, prompt, 'Pollinations')
								clearCachedModels('pollinations')
								return
							}
							if (response.status === 403) {
								try {
									const errorJson = JSON.parse(text)
									if (errorJson.message && errorJson.message.includes('authenticate at https://auth.pollinations.ai')) {
										showPollinationsAuthPrompt(errorJson.message, prompt)
										isGenerating = false
										updateStatusWidget('error', 'Authentication needed.')
										setTimeout(() => updateSystemStatus(), 4000)
										return
									}
								} catch (jsonError) {
									/* Not a JSON error, fall through */
								}
							}
							handleGenerationFailure(`Error ${response.status}: ${text}`, prompt, 'Pollinations')
						} catch (e) {
							handleGenerationFailure(`Error ${response.status}: ${response.statusText}`, prompt, 'Pollinations')
						}
					}
					handleErrorResponse()
				}
			},
			onerror: error => {
				handleGenerationFailure(JSON.stringify(error), prompt, 'Pollinations')
			}
		})
	}

	async function generateImageOpenAICompat(prompt, providerProfileUrl = null) {
		currentGenerationStatusText = 'Generating with OpenAI Compatible API...'
		updateSystemStatus()
		const config = await getConfig()
		const profiles = config.openAICompatProfiles
		const activeUrl = providerProfileUrl || config.openAICompatActiveProfileUrl
		const activeProfile = profiles[activeUrl]

		if (!activeProfile) {
			handleGenerationFailure(
				`No active or valid OpenAI Compatible profile found for URL: ${activeUrl}`,
				prompt,
				'OpenAICompat'
			)
			return
		}

		const url = `${activeUrl}/images/generations`
		const payload = {
			model: activeProfile.model,
			prompt: prompt,
			n: 1,
			size: '1024x1024',
			response_format: 'b64_json'
		}
		GM_xmlhttpRequest({
			method: 'POST',
			url: url,
			headers: {
				'Content-Type': 'application/json',
				Authorization: `Bearer ${activeProfile.apiKey}`
			},
			data: JSON.stringify(payload),
			onload: response => {
				try {
					const data = JSON.parse(response.responseText)
					if (data && Array.isArray(data.data) && data.data.length > 0) {
						const imageUrls = data.data
							.map(item => {
								if (item.b64_json) {
									return `data:image/png;base64,${item.b64_json}`
								} else if (item.url) {
									return item.url
								}
								return null
							})
							.filter(url => url !== null)

						if (imageUrls.length > 0) {
							handleGenerationSuccess(imageUrls, prompt, 'OpenAICompat', activeProfile.model)
						} else {
							throw new Error('API response did not contain usable image data (b64_json or url).')
						}
					} else {
						throw new Error(JSON.stringify(data))
					}
				} catch (e) {
					handleGenerationFailure(e.message, prompt, 'OpenAICompat', activeUrl)
				}
			},
			onerror: error => {
				handleGenerationFailure(JSON.stringify(error), prompt, 'OpenAICompat', activeUrl)
			}
		})
	}

	function updateVisibleSettings() {
		const provider = document.getElementById('nig-provider').value
		document.querySelectorAll('.nig-provider-settings').forEach(el => (el.style.display = 'none'))
		const settingsEl = document.getElementById(`nig-provider-${provider}`)
		if (settingsEl) {
			settingsEl.style.display = 'block'
		}
	}

	function populatePollinationsSelect(select, models, selectedModel) {
		select.innerHTML = ''
		models.forEach(model => {
			const option = document.createElement('option')
			option.value = model
			let textContent = model
			if (model === 'gptimage') {
				textContent += ' (Recommended: Quality)'
			} else if (model === 'flux') {
				textContent += ' (Default: Speed)'
			}
			option.textContent = textContent
			select.appendChild(option)
		})
		if (models.includes(selectedModel)) {
			select.value = selectedModel
		}
	}

	async function fetchPollinationsModels(selectedModel) {
		const select = document.getElementById('nig-pollinations-model')
		const cache = await getCachedModels()

		if (cache.pollinations && cache.pollinations.length > 0) {
			log('Loading Pollinations models from cache.')
			populatePollinationsSelect(select, cache.pollinations, selectedModel)
			return
		}

		select.innerHTML = '<option>Fetching models...</option>'
		GM_xmlhttpRequest({
			method: 'GET',
			url: 'https://image.pollinations.ai/models',
			headers: {
				'User-Agent':
					'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36'
			},
			onload: async response => {
				try {
					const models = JSON.parse(response.responseText)
					await setCachedModels('pollinations', models)
					log('Fetched and cached Pollinations models.')
					populatePollinationsSelect(select, models, selectedModel)
				} catch (e) {
					select.innerHTML = '<option>Failed to load models</option>'
					console.error('Failed to parse Pollinations models:', e)
				}
			},
			onerror: () => {
				select.innerHTML = '<option>Failed to load models</option>'
			}
		})
	}

	async function fetchAIHordeModels(selectedModel) {
		const select = document.getElementById('nig-horde-model')
		const cache = await getCachedModels()

		const populateSelect = models => {
			select.innerHTML = ''
			const apiModelMap = new Map(models.map(m => [m.name, m]))
			const topModelNames = new Set(TOP_MODELS.map(m => m.name))
			const topGroup = document.createElement('optgroup')
			topGroup.label = 'Top 10 Popular Models'
			const otherGroup = document.createElement('optgroup')
			otherGroup.label = 'Other Models'
			TOP_MODELS.forEach(topModel => {
				if (apiModelMap.has(topModel.name)) {
					const apiData = apiModelMap.get(topModel.name)
					const option = document.createElement('option')
					option.value = topModel.name
					option.textContent = `${topModel.name} - ${topModel.desc} (${apiData.count} workers)`
					topGroup.appendChild(option)
				}
			})
			const otherModels = models.filter(m => !topModelNames.has(m.name)).sort((a, b) => b.count - a.count)
			otherModels.forEach(model => {
				const option = document.createElement('option')
				option.value = model.name
				option.textContent = `${model.name} (${model.count} workers)`
				otherGroup.appendChild(option)
			})
			select.appendChild(topGroup)
			select.appendChild(otherGroup)
			if (Array.from(select.options).some(opt => opt.value === selectedModel)) {
				select.value = selectedModel
			}
		}

		if (cache.aiHorde && cache.aiHorde.length > 0) {
			log('Loading AI Horde models from cache.')
			populateSelect(cache.aiHorde)
			return
		}

		select.innerHTML = '<option>Fetching models...</option>'
		GM_xmlhttpRequest({
			method: 'GET',
			url: 'https://aihorde.net/api/v2/status/models?type=image',
			onload: async response => {
				try {
					const apiModels = JSON.parse(response.responseText)
					await setCachedModels('aiHorde', apiModels)
					log('Fetched and cached AI Horde models.')
					populateSelect(apiModels)
				} catch (e) {
					select.innerHTML = '<option>Failed to load models</option>'
					console.error('Failed to parse AI Horde models:', e)
				}
			},
			onerror: () => {
				select.innerHTML = '<option>Failed to load models</option>'
			}
		})
	}

	function isModelFree(model) {
		if (typeof model.is_free === 'boolean') return model.is_free
		if (typeof model.premium_model === 'boolean') return !model.premium_model
		if (Array.isArray(model.tiers) && model.tiers.includes('Free')) return true
		return false
	}

	function populateOpenAICompatSelect(select, models, selectedModel) {
		select.innerHTML = ''
		const freeGroup = document.createElement('optgroup')
		freeGroup.label = 'Free Models'
		const paidGroup = document.createElement('optgroup')
		paidGroup.label = 'Paid Models'

		models.forEach(model => {
			const option = document.createElement('option')
			option.value = model.id
			option.textContent = model.id
			if (isModelFree(model)) {
				freeGroup.appendChild(option)
			} else {
				paidGroup.appendChild(option)
			}
		})

		if (freeGroup.childElementCount > 0) select.appendChild(freeGroup)
		if (paidGroup.childElementCount > 0) select.appendChild(paidGroup)

		if (models.some(m => m.id === selectedModel)) {
			select.value = selectedModel
		}
	}

	async function fetchOpenAICompatModels(selectedModel) {
		const select = document.getElementById('nig-openai-compat-model')
		const baseUrl = document.getElementById('nig-openai-compat-base-url').value.trim()
		const apiKey = document.getElementById('nig-openai-compat-api-key').value.trim()

		if (!baseUrl) {
			alert('Please enter a Base URL first.')
			return
		}

		const cacheKey = `openAICompat::${baseUrl}`
		const cache = await getCachedModels()
		const cachedData = cache[cacheKey]

		if (cachedData && cachedData.timestamp && Date.now() - cachedData.timestamp < CACHE_EXPIRATION_MS) {
			log(`Loading OpenAI Compatible models for ${baseUrl} from cache.`)
			populateOpenAICompatSelect(select, cachedData.models, selectedModel)
			return
		}

		const switchToManual = () => {
			document.getElementById('nig-openai-model-container-select').style.display = 'none'
			document.getElementById('nig-openai-model-container-manual').style.display = 'block'
		}

		select.innerHTML = '<option>Fetching models...</option>'
		GM_xmlhttpRequest({
			method: 'GET',
			url: `${baseUrl}/models`,
			headers: {Authorization: `Bearer ${apiKey}`},
			onload: async response => {
				try {
					const data = JSON.parse(response.responseText)
					if (!data.data || !Array.isArray(data.data)) {
						throw new Error('Invalid model list format received.')
					}

					let imageModels = []
					if (data.data.some(m => m.endpoint || m.endpoints)) {
						imageModels = data.data.filter(
							model => model.endpoint === '/v1/images/generations' || model.endpoints?.includes('/v1/images/generations')
						)
					} else if (data.data.some(m => m.type === 'images.generations')) {
						imageModels = data.data.filter(model => model.type === 'images.generations')
					} else {
						throw new Error('Could not determine image models from response.')
					}

					imageModels.sort((a, b) => {
						const aIsFree = isModelFree(a)
						const bIsFree = isModelFree(b)
						if (aIsFree && !bIsFree) return -1
						if (!aIsFree && bIsFree) return 1
						return a.id.localeCompare(b.id)
					})
					// Cache the fetched models
					await setCachedModels(cacheKey, {models: imageModels, timestamp: Date.now()})
					log(`Fetched and cached OpenAI Compatible models for ${baseUrl}.`)

					populateOpenAICompatSelect(select, imageModels, selectedModel)
				} catch (e) {
					select.innerHTML = '<option>Failed to load models</option>'
					console.error('Failed to parse OpenAI Compatible models:', e)
					alert(
						`Failed to fetch models. Check the Base URL and API Key. You can enter the model name manually. Error: ${e.message}`
					)
					switchToManual()
				}
			},
			onerror: error => {
				select.innerHTML = '<option>Failed to load models</option>'
				console.error('Error fetching OpenAI Compatible models:', error)
				alert('Failed to fetch models. Check your network connection and the Base URL. Switching to manual input.')
				switchToManual()
			}
		})
	}

	function updateSubStyles(mainStyleName) {
		const subStyleSelect = document.getElementById('nig-sub-style')
		const mainStyleDesc = document.getElementById('nig-main-style-desc')
		const subStyleDesc = document.getElementById('nig-sub-style-desc')

		const selectedCategory = PROMPT_CATEGORIES.find(cat => cat.name === mainStyleName)
		mainStyleDesc.textContent = selectedCategory ? selectedCategory.description : ''
		subStyleSelect.innerHTML = ''

		if (selectedCategory && selectedCategory.subStyles.length > 0) {
			subStyleSelect.disabled = false
			selectedCategory.subStyles.forEach(sub => {
				const option = document.createElement('option')
				option.value = sub.value
				option.textContent = sub.name
				subStyleSelect.appendChild(option)
			})
			subStyleSelect.dispatchEvent(new Event('change'))
		} else {
			subStyleSelect.disabled = true
			subStyleDesc.textContent = ''
		}
	}

	// --- OpenAI Profile Management ---
	async function loadOpenAIProfiles() {
		const config = await getConfig()
		const profiles = config.openAICompatProfiles
		const activeUrl = config.openAICompatActiveProfileUrl
		const select = document.getElementById('nig-openai-compat-profile-select')
		select.innerHTML = ''

		Object.keys(profiles).forEach(url => {
			const option = document.createElement('option')
			option.value = url
			option.textContent = url
			select.appendChild(option)
		})

		const newOption = document.createElement('option')
		newOption.value = '__new__'
		newOption.textContent = '— Add or Edit Profile —'
		select.appendChild(newOption)

		if (activeUrl && profiles[activeUrl]) {
			select.value = activeUrl
		} else {
			select.value = '__new__'
		}
		loadSelectedOpenAIProfile()
	}

	async function loadSelectedOpenAIProfile() {
		const select = document.getElementById('nig-openai-compat-profile-select')
		const selectedUrl = select.value
		const profiles = await GM_getValue('openAICompatProfiles', {})
		const profile = profiles[selectedUrl] || {apiKey: '', model: ''}

		document.getElementById('nig-openai-compat-base-url').value = selectedUrl === '__new__' ? '' : selectedUrl
		document.getElementById('nig-openai-compat-api-key').value = profile.apiKey
		document.getElementById('nig-openai-compat-model-manual').value = profile.model

		if (selectedUrl !== '__new__') {
			fetchOpenAICompatModels(profile.model)
		} else {
			document.getElementById('nig-openai-compat-model').innerHTML = '<option>Enter URL/Key and fetch...</option>'
		}
	}

	async function deleteSelectedOpenAIProfile() {
		const select = document.getElementById('nig-openai-compat-profile-select')
		const urlToDelete = select.value
		if (urlToDelete === '__new__') {
			alert("You can't delete the 'Add New' option.")
			return
		}
		if (confirm(`Are you sure you want to delete the profile for "${urlToDelete}"?`)) {
			const profiles = await GM_getValue('openAICompatProfiles', {})
			delete profiles[urlToDelete]
			await GM_setValue('openAICompatProfiles', profiles)
			await loadOpenAIProfiles()
		}
	}

	async function populateConfigForm() {
		const config = await getConfig()
		document.getElementById('nig-provider').value = config.selectedProvider

		// --- Populate Prompt Styling Tab ---
		const mainStyleSelect = document.getElementById('nig-main-style')
		const subStyleSelect = document.getElementById('nig-sub-style')
		const subStyleDesc = document.getElementById('nig-sub-style-desc')
		const customStyleEnable = document.getElementById('nig-custom-style-enable')
		const customStyleText = document.getElementById('nig-custom-style-text')

		mainStyleSelect.innerHTML = ''
		PROMPT_CATEGORIES.forEach(cat => {
			const option = document.createElement('option')
			option.value = cat.name
			option.textContent = cat.name
			mainStyleSelect.appendChild(option)
		})

		mainStyleSelect.value = config.mainPromptStyle
		updateSubStyles(config.mainPromptStyle)
		subStyleSelect.value = config.subPromptStyle

		mainStyleSelect.addEventListener('change', () => updateSubStyles(mainStyleSelect.value))
		subStyleSelect.addEventListener('change', () => {
			const category = PROMPT_CATEGORIES.find(c => c.name === mainStyleSelect.value)
			if (category) {
				const subStyle = category.subStyles.find(s => s.value === subStyleSelect.value)
				subStyleDesc.textContent = subStyle ? subStyle.description : ''
			}
		})
		subStyleSelect.dispatchEvent(new Event('change'))

		customStyleEnable.checked = config.customStyleEnabled
		customStyleText.value = config.customStyleText
		customStyleText.disabled = !config.customStyleEnabled

		// Global Negative Prompting
		document.getElementById('nig-enable-neg-prompt').checked = config.enableNegPrompt
		document.getElementById('nig-global-neg-prompt').value = config.globalNegPrompt

		// Pollinations
		document.getElementById('nig-pollinations-width').value = config.pollinationsWidth
		document.getElementById('nig-pollinations-height').value = config.pollinationsHeight
		document.getElementById('nig-pollinations-seed').value = config.pollinationsSeed
		document.getElementById('nig-pollinations-enhance').checked = config.pollinationsEnhance
		document.getElementById('nig-pollinations-safe').checked = config.pollinationsSafe
		document.getElementById('nig-pollinations-nologo').checked = config.pollinationsNologo
		document.getElementById('nig-pollinations-private').checked = config.pollinationsPrivate
		document.getElementById('nig-pollinations-token').value = config.pollinationsToken
		fetchPollinationsModels(config.pollinationsModel)

		// AI Horde
		document.getElementById('nig-horde-api-key').value = config.aiHordeApiKey
		document.getElementById('nig-horde-sampler').value = config.aiHordeSampler
		document.getElementById('nig-horde-steps').value = config.aiHordeSteps
		document.getElementById('nig-horde-cfg').value = config.aiHordeCfgScale
		document.getElementById('nig-horde-width').value = config.aiHordeWidth
		document.getElementById('nig-horde-height').value = config.aiHordeHeight
		document.getElementById('nig-horde-seed').value = config.aiHordeSeed
		document.querySelectorAll('input[name="nig-horde-post"]').forEach(cb => {
			cb.checked = config.aiHordePostProcessing.includes(cb.value)
		})
		fetchAIHordeModels(config.aiHordeModel)

		// Google
		document.getElementById('nig-google-api-key').value = config.googleApiKey
		document.getElementById('nig-model').value = config.model
		document.getElementById('nig-num-images').value = config.numberOfImages
		document.getElementById('nig-image-size').value = config.imageSize
		document.getElementById('nig-aspect-ratio').value = config.aspectRatio
		document.getElementById('nig-person-gen').value = config.personGeneration

		// OpenAI Compatible
		await loadOpenAIProfiles()
		if (config.openAICompatModelManualInput) {
			document.getElementById('nig-openai-model-container-select').style.display = 'none'
			document.getElementById('nig-openai-model-container-manual').style.display = 'block'
		} else {
			document.getElementById('nig-openai-model-container-select').style.display = 'block'
			document.getElementById('nig-openai-model-container-manual').style.display = 'none'
		}

		updateVisibleSettings()
	}

	async function populateHistoryTab() {
		const history = await getHistory()
		const historyList = document.getElementById('nig-history-list')
		historyList.innerHTML = history.length ? '' : '<li>No history yet.</li>'
		history.forEach(item => {
			const li = document.createElement('li')
			li.className = 'nig-history-item'

			const providerInfo = item.provider ? `<strong>${item.provider}</strong>` : ''
			const modelInfo = item.model ? `(${item.model})` : ''

			// Set the static part of the HTML
			li.innerHTML = `<small>${new Date(item.date).toLocaleString()} - ${providerInfo} ${modelInfo}</small>
                      <small><em>${item.prompt.substring(0, 70)}...</em></small>`

			// Create the link element separately to add the event listener
			const viewLink = document.createElement('a')
			viewLink.href = '#' // Use a non-navigating href
			viewLink.textContent = 'View Generated Image'

			viewLink.addEventListener('click', e => {
				e.preventDefault()
				if (item.url && item.url.startsWith('data:image')) {
					// For base64 images, use the internal viewer to avoid browser issues
					showImageViewer([item.url], item.prompt, item.provider)
				} else {
					// For regular URLs, open in a new tab
					window.open(item.url, '_blank')
				}
			})

			li.appendChild(viewLink)
			historyList.appendChild(li)
		})
	}

	async function cleanHistory() {
		const daysInput = document.getElementById('nig-history-clean-days')
		const days = parseInt(daysInput.value, 10)
		if (isNaN(days) || days < 1 || days > 365) {
			alert('Please enter a valid number of days (1-365).')
			return
		}
		if (confirm(`Are you sure you want to delete all history entries older than ${days} days? This cannot be undone.`)) {
			const history = await getHistory()
			const cutoffDate = Date.now() - days * 24 * 60 * 60 * 1000
			const newHistory = history.filter(item => new Date(item.date).getTime() >= cutoffDate)
			await GM_setValue('history', JSON.stringify(newHistory))
			await populateHistoryTab()
			alert(`History cleaned. ${history.length - newHistory.length} entries were removed.`)
		}
	}

	async function openConfigPanel() {
		if (!configPanel) createConfigPanel()
		configPanel.querySelectorAll('.nig-tab, .nig-tab-content').forEach(el => el.classList.remove('active'))
		configPanel.querySelector('.nig-tab[data-tab="config"]').classList.add('active')
		configPanel.querySelector('#nig-config-tab').classList.add('active')
		configPanel.querySelector('#nig-save-btn').style.display = 'block'
		await populateConfigForm()
		configPanel.style.display = 'flex'
	}

	async function saveConfig() {
		// Prompt Styling
		await setConfig('mainPromptStyle', document.getElementById('nig-main-style').value)
		await setConfig('subPromptStyle', document.getElementById('nig-sub-style').value)
		await setConfig('customStyleEnabled', document.getElementById('nig-custom-style-enable').checked)
		await setConfig('customStyleText', document.getElementById('nig-custom-style-text').value.trim())

		// Global Negative Prompting
		await setConfig('enableNegPrompt', document.getElementById('nig-enable-neg-prompt').checked)
		await setConfig('globalNegPrompt', document.getElementById('nig-global-neg-prompt').value.trim())

		await setConfig('selectedProvider', document.getElementById('nig-provider').value)

		// Pollinations
		await setConfig('pollinationsModel', document.getElementById('nig-pollinations-model').value)
		await setConfig('pollinationsWidth', document.getElementById('nig-pollinations-width').value)
		await setConfig('pollinationsHeight', document.getElementById('nig-pollinations-height').value)
		await setConfig('pollinationsSeed', document.getElementById('nig-pollinations-seed').value.trim())
		await setConfig('pollinationsEnhance', document.getElementById('nig-pollinations-enhance').checked)
		await setConfig('pollinationsSafe', document.getElementById('nig-pollinations-safe').checked)
		await setConfig('pollinationsNologo', document.getElementById('nig-pollinations-nologo').checked)
		await setConfig('pollinationsPrivate', document.getElementById('nig-pollinations-private').checked)
		await setConfig('pollinationsToken', document.getElementById('nig-pollinations-token').value.trim())

		// AI Horde
		await setConfig('aiHordeApiKey', document.getElementById('nig-horde-api-key').value.trim() || '0000000000')
		await setConfig('aiHordeModel', document.getElementById('nig-horde-model').value)
		await setConfig('aiHordeSampler', document.getElementById('nig-horde-sampler').value)
		await setConfig('aiHordeSteps', document.getElementById('nig-horde-steps').value)
		await setConfig('aiHordeCfgScale', document.getElementById('nig-horde-cfg').value)
		await setConfig('aiHordeWidth', document.getElementById('nig-horde-width').value)
		await setConfig('aiHordeHeight', document.getElementById('nig-horde-height').value)
		await setConfig('aiHordeSeed', document.getElementById('nig-horde-seed').value.trim())
		const postProcessing = Array.from(document.querySelectorAll('input[name="nig-horde-post"]:checked')).map(
			cb => cb.value
		)
		await setConfig('aiHordePostProcessing', postProcessing)

		// Google
		await setConfig('googleApiKey', document.getElementById('nig-google-api-key').value.trim())
		await setConfig('model', document.getElementById('nig-model').value)
		await setConfig('numberOfImages', document.getElementById('nig-num-images').value)
		await setConfig('imageSize', document.getElementById('nig-image-size').value)
		await setConfig('aspectRatio', document.getElementById('nig-aspect-ratio').value)
		await setConfig('personGeneration', document.getElementById('nig-person-gen').value)

		// OpenAI Compatible
		const baseUrl = document.getElementById('nig-openai-compat-base-url').value.trim()
		if (baseUrl) {
			const profiles = await GM_getValue('openAICompatProfiles', {})
			const manualContainer = document.getElementById('nig-openai-model-container-manual')
			const isManualMode = manualContainer.style.display !== 'none'
			let model
			if (isManualMode) {
				model = document.getElementById('nig-openai-compat-model-manual').value.trim()
			} else {
				model = document.getElementById('nig-openai-compat-model').value
			}

			profiles[baseUrl] = {
				apiKey: document.getElementById('nig-openai-compat-api-key').value.trim(),
				model: model
			}
			await setConfig('openAICompatProfiles', profiles)
			await setConfig('openAICompatActiveProfileUrl', baseUrl)
			await setConfig('openAICompatModelManualInput', isManualMode)
		}

		alert('Configuration saved!')
		// configPanel.style.display = 'none' // Panel should remain open after saving
	}

	// --- 5. INITIALIZATION ---
	async function init() {
		await updateLoggingStatus()
		createUI()
		createErrorModal()
		document.addEventListener('mouseup', handleSelection)
		document.addEventListener('selectionchange', handleSelection)
		GM_registerMenuCommand('Image Generator Settings', openConfigPanel)
	}
	init()
})()