""" APIClient - Agent - Central Theme Engine All styling lives here in the global QSS. UI widgets use setObjectName() selectors - never inline setStyleSheet() for static colors. Only truly dynamic values (per-request method color, status badge) stay inline. """ from PyQt6.QtGui import QColor, QPalette from PyQt6.QtWidgets import QApplication # ── Color Palettes ──────────────────────────────────────────────────────────── class DarkColors: BG_DARKEST = "#0D0D0D" BG_SIDEBAR = "#111111" BG_MAIN = "#181818" BG_PANEL = "#1E1E1E" BG_ELEVATED = "#242424" BG_INPUT = "#2A2A2A" BG_HOVER = "#303030" BG_SELECTED = "#383838" BORDER = "#2C2C2C" BORDER_FOCUS = "#505050" TEXT_PRIMARY = "#E4E4E4" TEXT_SECONDARY = "#8A8A8A" TEXT_MUTED = "#505050" TEXT_DISABLED = "#3A3A3A" ACCENT = "#E05C2C" ACCENT_HOVER = "#F06030" ACCENT_PRESSED = "#C04C20" ACCENT_SUBTLE = "#2A1208" SUCCESS = "#3FB950" WARNING = "#D29922" ERROR = "#F85149" INFO = "#58A6FF" METHOD_GET = "#61AFFE" METHOD_POST = "#49CC90" METHOD_PUT = "#FCA130" METHOD_PATCH = "#50E3C2" METHOD_DELETE = "#F93E3E" METHOD_HEAD = "#9012FE" METHOD_OPTIONS = "#0D5AA7" STATUS_1XX = "#8C8C8C" STATUS_2XX = "#3FB950" STATUS_3XX = "#D29922" STATUS_4XX = "#F85149" STATUS_5XX = "#FF4444" class LightColors: BG_DARKEST = "#E2E2E2" BG_SIDEBAR = "#ECECEC" BG_MAIN = "#F2F2F2" BG_PANEL = "#FFFFFF" BG_ELEVATED = "#E8E8E8" BG_INPUT = "#FFFFFF" BG_HOVER = "#DCDCDC" BG_SELECTED = "#D0D0D0" BORDER = "#D0D0D0" BORDER_FOCUS = "#A0A0A0" TEXT_PRIMARY = "#1A1A1A" TEXT_SECONDARY = "#555555" TEXT_MUTED = "#999999" TEXT_DISABLED = "#BBBBBB" ACCENT = "#C94A14" ACCENT_HOVER = "#E05520" ACCENT_PRESSED = "#A83C0E" ACCENT_SUBTLE = "#FDEEE6" SUCCESS = "#1A7F37" WARNING = "#7A5800" ERROR = "#C01020" INFO = "#0550AE" METHOD_GET = "#0550AE" METHOD_POST = "#1A7F37" METHOD_PUT = "#7A3800" METHOD_PATCH = "#116329" METHOD_DELETE = "#C01020" METHOD_HEAD = "#6639BA" METHOD_OPTIONS = "#0550AE" STATUS_1XX = "#777777" STATUS_2XX = "#1A7F37" STATUS_3XX = "#7A5800" STATUS_4XX = "#C01020" STATUS_5XX = "#C01020" # ── Active palette (module-level singleton) ─────────────────────────────────── Colors = DarkColors _is_dark = True def method_color(method: str) -> str: return { "GET": Colors.METHOD_GET, "POST": Colors.METHOD_POST, "PUT": Colors.METHOD_PUT, "PATCH": Colors.METHOD_PATCH, "DELETE": Colors.METHOD_DELETE, "HEAD": Colors.METHOD_HEAD, "OPTIONS": Colors.METHOD_OPTIONS, }.get(method.upper(), Colors.TEXT_SECONDARY) def status_color(code: int) -> str: if code < 200: return Colors.STATUS_1XX if code < 300: return Colors.STATUS_2XX if code < 400: return Colors.STATUS_3XX if code < 500: return Colors.STATUS_4XX return Colors.STATUS_5XX # ── Global Stylesheet ───────────────────────────────────────────────────────── # Everything static lives here. Object names are the API between theme and UI. def _build_stylesheet(C) -> str: return f""" /* ════════════════════════════════════════════════════════ BASE ════════════════════════════════════════════════════════ */ QWidget {{ background-color: {C.BG_MAIN}; color: {C.TEXT_PRIMARY}; font-family: "Segoe UI", "SF Pro Text", "Inter", "Helvetica Neue", sans-serif; font-size: 13px; border: none; outline: none; }} QMainWindow, QDialog {{ background-color: {C.BG_PANEL}; }} /* ════════════════════════════════════════════════════════ SCROLLBARS ════════════════════════════════════════════════════════ */ QScrollBar:vertical {{ background: transparent; width: 8px; margin: 0; }} QScrollBar::handle:vertical {{ background: {C.BORDER_FOCUS}; border-radius: 4px; min-height: 28px; }} QScrollBar::handle:vertical:hover {{ background: {C.TEXT_MUTED}; }} QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {{ height: 0; }} QScrollBar:horizontal {{ background: transparent; height: 8px; margin: 0; }} QScrollBar::handle:horizontal {{ background: {C.BORDER_FOCUS}; border-radius: 4px; min-width: 28px; }} QScrollBar::handle:horizontal:hover {{ background: {C.TEXT_MUTED}; }} QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {{ width: 0; }} /* ════════════════════════════════════════════════════════ SPLITTER ════════════════════════════════════════════════════════ */ QSplitter::handle {{ background: {C.BORDER}; }} QSplitter::handle:horizontal {{ width: 1px; }} QSplitter::handle:vertical {{ height: 1px; }} /* ════════════════════════════════════════════════════════ MENU ════════════════════════════════════════════════════════ */ QMenuBar {{ background-color: {C.BG_DARKEST}; color: {C.TEXT_SECONDARY}; border-bottom: 1px solid {C.BORDER}; padding: 2px 4px; spacing: 4px; }} QMenuBar::item {{ background: transparent; padding: 4px 10px; border-radius: 4px; }} QMenuBar::item:selected, QMenuBar::item:pressed {{ background-color: {C.BG_ELEVATED}; color: {C.TEXT_PRIMARY}; }} QMenu {{ background-color: {C.BG_ELEVATED}; color: {C.TEXT_PRIMARY}; border: 1px solid {C.BORDER}; border-radius: 6px; padding: 4px; }} QMenu::item {{ padding: 7px 28px 7px 12px; border-radius: 4px; }} QMenu::item:selected {{ background-color: {C.BG_HOVER}; }} QMenu::item:disabled {{ color: {C.TEXT_MUTED}; }} QMenu::separator {{ height: 1px; background: {C.BORDER}; margin: 4px 8px; }} /* ════════════════════════════════════════════════════════ INPUTS ════════════════════════════════════════════════════════ */ QLineEdit {{ background-color: {C.BG_INPUT}; color: {C.TEXT_PRIMARY}; border: 1px solid {C.BORDER}; border-radius: 5px; padding: 6px 10px; selection-background-color: {C.ACCENT}; }} QLineEdit:focus {{ border: 1px solid {C.BORDER_FOCUS}; background-color: {C.BG_ELEVATED}; }} QLineEdit:disabled {{ color: {C.TEXT_DISABLED}; background-color: {C.BG_MAIN}; }} QLineEdit::placeholder {{ color: {C.TEXT_MUTED}; }} QTextEdit, QPlainTextEdit {{ background-color: {C.BG_INPUT}; color: {C.TEXT_PRIMARY}; border: 1px solid {C.BORDER}; border-radius: 5px; padding: 6px; selection-background-color: {C.ACCENT}; }} QTextEdit:focus, QPlainTextEdit:focus {{ border: 1px solid {C.BORDER_FOCUS}; }} QSpinBox {{ background-color: {C.BG_INPUT}; color: {C.TEXT_PRIMARY}; border: 1px solid {C.BORDER}; border-radius: 5px; padding: 5px 8px; }} QSpinBox:focus {{ border-color: {C.BORDER_FOCUS}; }} QSpinBox::up-button, QSpinBox::down-button {{ background: {C.BG_ELEVATED}; border: none; width: 18px; }} QSpinBox::up-button:hover, QSpinBox::down-button:hover {{ background: {C.BG_HOVER}; }} /* ════════════════════════════════════════════════════════ COMBOBOX ════════════════════════════════════════════════════════ */ QComboBox {{ background-color: {C.BG_INPUT}; color: {C.TEXT_PRIMARY}; border: 1px solid {C.BORDER}; border-radius: 5px; padding: 5px 10px; min-width: 80px; }} QComboBox:hover {{ border-color: {C.BORDER_FOCUS}; }} QComboBox:focus {{ border-color: {C.BORDER_FOCUS}; }} QComboBox::drop-down {{ border: none; width: 20px; subcontrol-origin: padding; }} QComboBox::down-arrow {{ width: 10px; height: 10px; }} QComboBox QAbstractItemView {{ background-color: {C.BG_ELEVATED}; color: {C.TEXT_PRIMARY}; border: 1px solid {C.BORDER}; border-radius: 4px; selection-background-color: {C.BG_SELECTED}; outline: none; padding: 2px; }} /* ════════════════════════════════════════════════════════ CHECKBOX ════════════════════════════════════════════════════════ */ QCheckBox {{ color: {C.TEXT_SECONDARY}; spacing: 6px; background: transparent; }} QCheckBox::indicator {{ width: 14px; height: 14px; border: 1px solid {C.BORDER_FOCUS}; border-radius: 3px; background: {C.BG_INPUT}; }} QCheckBox::indicator:checked {{ background: {C.ACCENT}; border-color: {C.ACCENT}; }} /* ════════════════════════════════════════════════════════ BUTTONS ════════════════════════════════════════════════════════ */ QPushButton {{ background-color: {C.BG_ELEVATED}; color: {C.TEXT_PRIMARY}; border: 1px solid {C.BORDER}; border-radius: 5px; padding: 6px 14px; font-weight: 500; }} QPushButton:hover {{ background-color: {C.BG_HOVER}; border-color: {C.BORDER_FOCUS}; }} QPushButton:pressed {{ background-color: {C.BG_SELECTED}; }} QPushButton:disabled {{ color: {C.TEXT_MUTED}; border-color: {C.BORDER}; }} QPushButton#accent {{ background-color: {C.ACCENT}; color: #FFFFFF; border: none; font-weight: 600; }} QPushButton#accent:hover {{ background-color: {C.ACCENT_HOVER}; }} QPushButton#accent:pressed {{ background-color: {C.ACCENT_PRESSED}; }} QPushButton#accent:disabled {{ background-color: {C.ACCENT_SUBTLE}; color: {C.TEXT_MUTED}; }} QPushButton#ghost {{ background: transparent; border: none; color: {C.TEXT_SECONDARY}; padding: 4px 8px; border-radius: 4px; }} QPushButton#ghost:hover {{ color: {C.TEXT_PRIMARY}; background-color: {C.BG_HOVER}; }} QPushButton#ghost:pressed {{ background-color: {C.BG_SELECTED}; }} QPushButton#danger {{ background-color: transparent; color: {C.ERROR}; border: 1px solid {C.ERROR}; }} QPushButton#danger:hover {{ background-color: {C.ACCENT_SUBTLE}; }} QPushButton#sendBtn {{ background-color: {C.ACCENT}; color: white; border: none; border-radius: 6px; padding: 8px 22px; font-weight: 700; font-size: 13px; letter-spacing: 0.3px; }} QPushButton#sendBtn:hover {{ background-color: {C.ACCENT_HOVER}; }} QPushButton#sendBtn:pressed {{ background-color: {C.ACCENT_PRESSED}; }} QPushButton#sendBtn:disabled {{ background-color: {C.ACCENT_SUBTLE}; color: {C.TEXT_MUTED}; }} /* ════════════════════════════════════════════════════════ TABS ════════════════════════════════════════════════════════ */ QTabWidget::pane {{ border: none; background-color: {C.BG_PANEL}; }} QTabBar {{ background: transparent; }} QTabBar::tab {{ background: transparent; color: {C.TEXT_SECONDARY}; border: none; border-bottom: 2px solid transparent; padding: 8px 16px; font-size: 12px; font-weight: 500; }} QTabBar::tab:selected {{ color: {C.TEXT_PRIMARY}; border-bottom: 2px solid {C.ACCENT}; }} QTabBar::tab:hover:!selected {{ color: {C.TEXT_PRIMARY}; background-color: {C.BG_HOVER}; border-radius: 4px 4px 0 0; }} QTabBar::close-button {{ subcontrol-position: right; border-radius: 3px; margin: 3px 2px; padding: 0; width: 14px; height: 14px; }} /* Request/Response inner tab bars sit on BG_MAIN strip */ QTabWidget#innerTabs QTabBar {{ background: {C.BG_MAIN}; border-bottom: 1px solid {C.BORDER}; }} /* Top workspace tab bar (HTTP / WebSocket / Mock Server) */ QTabWidget#workspaceTabs QTabBar::tab {{ background: {C.BG_DARKEST}; color: {C.TEXT_SECONDARY}; border: none; border-right: 1px solid {C.BORDER}; padding: 10px 20px; font-size: 12px; font-weight: 600; border-bottom: none; border-top: 2px solid transparent; }} QTabWidget#workspaceTabs QTabBar::tab:selected {{ background: {C.BG_MAIN}; color: {C.TEXT_PRIMARY}; border-top: 2px solid {C.ACCENT}; }} QTabWidget#workspaceTabs QTabBar::tab:hover:!selected {{ background: {C.BG_ELEVATED}; color: {C.TEXT_PRIMARY}; }} /* ════════════════════════════════════════════════════════ TABLES ════════════════════════════════════════════════════════ */ QTableWidget {{ background-color: {C.BG_PANEL}; alternate-background-color: {C.BG_ELEVATED}; color: {C.TEXT_PRIMARY}; border: none; gridline-color: {C.BORDER}; selection-background-color: {C.BG_SELECTED}; selection-color: {C.TEXT_PRIMARY}; }} QTableWidget::item {{ padding: 5px 8px; border-bottom: 1px solid {C.BORDER}; }} QTableWidget::item:selected {{ background-color: {C.BG_SELECTED}; color: {C.TEXT_PRIMARY}; }} QHeaderView::section {{ background-color: {C.BG_ELEVATED}; color: {C.TEXT_PRIMARY}; border: none; border-bottom: 1px solid {C.BORDER}; border-right: 1px solid {C.BORDER}; padding: 6px 8px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }} QHeaderView::section:last {{ border-right: none; }} /* ════════════════════════════════════════════════════════ TREE ════════════════════════════════════════════════════════ */ QTreeWidget {{ background-color: {C.BG_SIDEBAR}; color: {C.TEXT_PRIMARY}; border: none; outline: none; show-decoration-selected: 1; }} QTreeWidget::item {{ padding: 4px 4px; border-radius: 3px; }} QTreeWidget::item:selected {{ background-color: {C.BG_SELECTED}; color: {C.TEXT_PRIMARY}; }} QTreeWidget::item:hover:!selected {{ background-color: {C.BG_HOVER}; }} QTreeWidget::branch {{ background: {C.BG_SIDEBAR}; }} /* ════════════════════════════════════════════════════════ LIST ════════════════════════════════════════════════════════ */ QListWidget {{ background-color: {C.BG_PANEL}; color: {C.TEXT_PRIMARY}; border: 1px solid {C.BORDER}; border-radius: 5px; outline: none; }} QListWidget::item {{ padding: 8px 10px; border-radius: 3px; }} QListWidget::item:selected {{ background-color: {C.BG_SELECTED}; color: {C.TEXT_PRIMARY}; }} QListWidget::item:hover:!selected {{ background-color: {C.BG_HOVER}; }} /* ════════════════════════════════════════════════════════ SIDEBAR LIST (no border, flush) ════════════════════════════════════════════════════════ */ QListWidget#sidebarList {{ background: {C.BG_SIDEBAR}; border: none; border-radius: 0; }} QListWidget#sidebarList::item {{ padding: 10px 14px; border-bottom: 1px solid {C.BORDER}; border-radius: 0; font-size: 13px; }} QListWidget#sidebarList::item:selected {{ background: {C.BG_SELECTED}; color: {C.TEXT_PRIMARY}; }} QListWidget#sidebarList::item:hover:!selected {{ background: {C.BG_HOVER}; }} /* ════════════════════════════════════════════════════════ STATUS BAR ════════════════════════════════════════════════════════ */ QStatusBar {{ background-color: {C.BG_DARKEST}; color: {C.TEXT_SECONDARY}; border-top: 1px solid {C.BORDER}; font-size: 11px; padding: 0 8px; }} QStatusBar::item {{ border: none; }} /* ════════════════════════════════════════════════════════ PROGRESS BAR ════════════════════════════════════════════════════════ */ QProgressBar {{ background-color: {C.BG_ELEVATED}; border: 1px solid {C.BORDER}; border-radius: 4px; height: 6px; text-align: center; color: transparent; }} QProgressBar::chunk {{ background-color: {C.ACCENT}; border-radius: 4px; }} /* ════════════════════════════════════════════════════════ GROUP BOX ════════════════════════════════════════════════════════ */ QGroupBox {{ color: {C.TEXT_SECONDARY}; border: 1px solid {C.BORDER}; border-radius: 6px; margin-top: 8px; padding: 8px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }} QGroupBox::title {{ subcontrol-origin: margin; left: 10px; padding: 0 4px; }} /* ════════════════════════════════════════════════════════ TOOLTIP ════════════════════════════════════════════════════════ */ QToolTip {{ background-color: {C.BG_ELEVATED}; color: {C.TEXT_PRIMARY}; border: 1px solid {C.BORDER}; border-radius: 4px; padding: 5px 8px; font-size: 12px; }} /* ════════════════════════════════════════════════════════ DIALOG BUTTON BOX ════════════════════════════════════════════════════════ */ QDialogButtonBox QPushButton {{ min-width: 80px; }} /* ════════════════════════════════════════════════════════ FRAME SEPARATORS ════════════════════════════════════════════════════════ */ QFrame[frameShape="4"], QFrame[frameShape="5"] {{ background-color: {C.BORDER}; border: none; max-height: 1px; max-width: 1px; }} /* ════════════════════════════════════════════════════════ ── NAMED WIDGET RULES (setObjectName API) ── ════════════════════════════════════════════════════════ */ /* Top brand / env bar */ QWidget#envBar {{ background-color: {C.BG_DARKEST}; border-bottom: 1px solid {C.BORDER}; }} QLabel#brandName {{ color: {C.ACCENT}; font-size: 15px; font-weight: 800; letter-spacing: 2px; background: transparent; }} QLabel#brandSub {{ color: {C.TEXT_MUTED}; font-size: 11px; font-weight: 500; background: transparent; }} QLabel#envChip {{ color: {C.TEXT_MUTED}; font-size: 10px; font-weight: 700; letter-spacing: 1px; background: transparent; }} /* Sidebar */ QWidget#sidebar {{ background-color: {C.BG_SIDEBAR}; border-right: 1px solid {C.BORDER}; }} QWidget#sidebarHeader {{ background-color: {C.BG_SIDEBAR}; border-bottom: 1px solid {C.BORDER}; }} QWidget#sidebarSearch {{ background-color: {C.BG_SIDEBAR}; }} /* URL bar strip */ QWidget#urlBarStrip {{ background-color: {C.BG_MAIN}; border-bottom: 1px solid {C.BORDER}; }} QLineEdit#urlBar {{ background-color: {C.BG_INPUT}; border: 1.5px solid {C.BORDER}; border-radius: 6px; padding: 8px 12px; font-size: 13px; font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", "Consolas", monospace; color: {C.TEXT_PRIMARY}; }} QLineEdit#urlBar:focus {{ border-color: {C.ACCENT}; background-color: {C.BG_ELEVATED}; }} /* Method combo (color set inline per method, only layout here) */ QComboBox#methodCombo {{ font-weight: 800; font-size: 12px; border-radius: 6px; padding: 8px 10px; min-width: 100px; border: 1px solid {C.BORDER}; background-color: {C.BG_INPUT}; }} QComboBox#methodCombo:hover {{ border-color: {C.BORDER_FOCUS}; }} QComboBox#methodCombo QAbstractItemView {{ background: {C.BG_ELEVATED}; color: {C.TEXT_PRIMARY}; border: 1px solid {C.BORDER}; selection-background-color: {C.BG_SELECTED}; }} /* Inner request/response tab strip */ QWidget#tabStrip {{ background-color: {C.BG_MAIN}; border-bottom: 1px solid {C.BORDER}; }} /* Response top bar */ QWidget#responseBar {{ background-color: {C.BG_MAIN}; border-top: 1px solid {C.BORDER}; border-bottom: 1px solid {C.BORDER}; }} QLabel#responseTitle {{ color: {C.TEXT_MUTED}; font-size: 10px; font-weight: 700; letter-spacing: 1.2px; background: transparent; }} QLabel#metaLabel {{ color: {C.TEXT_MUTED}; font-size: 11px; background: transparent; padding: 0 6px; }} /* Section/panel headers used in dialogs */ QWidget#panelHeader {{ background-color: {C.BG_ELEVATED}; border-bottom: 1px solid {C.BORDER}; }} QWidget#panelFooter {{ background-color: {C.BG_ELEVATED}; border-top: 1px solid {C.BORDER}; }} QWidget#sectionHeader {{ background-color: {C.BG_SIDEBAR}; border-bottom: 1px solid {C.BORDER}; }} QWidget#panelBody {{ background-color: {C.BG_PANEL}; }} /* Labels inside panels */ QLabel#panelTitle {{ font-size: 14px; font-weight: 700; color: {C.TEXT_PRIMARY}; background: transparent; }} QLabel#sectionLabel {{ color: {C.TEXT_MUTED}; font-size: 10px; font-weight: 700; letter-spacing: 1px; background: transparent; }} QLabel#hintText {{ color: {C.TEXT_MUTED}; font-size: 11px; background: transparent; }} QLabel#fieldLabel {{ color: {C.TEXT_SECONDARY}; font-size: 12px; background: transparent; }} /* Body/code editors */ QTextEdit#codeEditor {{ background-color: {C.BG_PANEL}; color: {C.TEXT_PRIMARY}; border: none; padding: 8px; font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", "Consolas", monospace; font-size: 11px; }} /* Loading overlay */ QWidget#loadingOverlay {{ background-color: {C.BG_PANEL}; }} QLabel#loadingLabel {{ color: {C.TEXT_MUTED}; font-size: 13px; background: transparent; }} /* Search in response bar */ QLineEdit#searchBar {{ background: {C.BG_INPUT}; border: 1px solid {C.BORDER}; border-radius: 4px; padding: 4px 8px; font-size: 12px; color: {C.TEXT_PRIMARY}; }} QLineEdit#searchBar:focus {{ border-color: {C.BORDER_FOCUS}; }} /* Sidebar filter input */ QLineEdit#filterInput {{ background: {C.BG_ELEVATED}; border: 1px solid {C.BORDER}; border-radius: 4px; padding: 5px 8px; font-size: 12px; color: {C.TEXT_PRIMARY}; }} QLineEdit#filterInput:focus {{ border-color: {C.BORDER_FOCUS}; }} /* WebSocket / Mock status indicator labels */ QLabel#statusOk {{ color: {C.SUCCESS}; font-size: 12px; font-weight: 600; background: transparent; }} QLabel#statusWarn {{ color: {C.WARNING}; font-size: 12px; font-weight: 600; background: transparent; }} QLabel#statusErr {{ color: {C.ERROR}; font-size: 12px; font-weight: 600; background: transparent; }} /* Auth "none" hint */ QLabel#authNone {{ color: {C.TEXT_MUTED}; padding: 12px; background: transparent; }} /* Sidebar panel (environment dialog left pane, etc.) */ QWidget#sidebarPanel {{ background-color: {C.BG_SIDEBAR}; }} /* Custom tab close button */ QPushButton#tabCloseBtn {{ background: transparent; border: none; border-radius: 3px; color: {C.TEXT_MUTED}; font-size: 14px; font-weight: 700; padding: 0; }} QPushButton#tabCloseBtn:hover {{ background-color: {C.ERROR}; color: #FFFFFF; }} /* AI Assistant panel */ QWidget#aiPanel {{ background-color: {C.BG_PANEL}; }} QTextEdit#aiOutput {{ background-color: {C.BG_INPUT}; color: {C.TEXT_PRIMARY}; border: 1px solid {C.BORDER}; border-radius: 5px; padding: 8px; font-size: 12px; }} QLabel#aiStatusLabel {{ color: {C.TEXT_MUTED}; font-size: 11px; background: transparent; }} /* ── AI Chat Panel ─────────────────────────────────────── */ QWidget#aiChatPanel {{ background-color: {C.BG_SIDEBAR}; border-left: 1px solid {C.BORDER}; }} QWidget#aiChatHeader {{ background-color: {C.BG_ELEVATED}; border-bottom: 1px solid {C.BORDER}; }} QLabel#aiChatTitle {{ color: {C.ACCENT}; font-size: 13px; font-weight: 700; letter-spacing: 0.5px; background: transparent; }} QWidget#chatArea {{ background-color: {C.BG_SIDEBAR}; }} QFrame#userBubble {{ background-color: {C.ACCENT_SUBTLE}; border: 1px solid {C.BORDER}; border-left: 3px solid {C.ACCENT}; border-radius: 6px; margin: 0px; }} QFrame#aiBubble {{ background-color: {C.BG_ELEVATED}; border: 1px solid {C.BORDER}; border-radius: 6px; margin: 0px; }} QLabel#chatRoleLabel {{ font-size: 10px; font-weight: 700; color: {C.TEXT_MUTED}; background: transparent; letter-spacing: 0.5px; text-transform: uppercase; }} QLabel#chatMessageText {{ color: {C.TEXT_PRIMARY}; background: transparent; font-size: 12px; line-height: 1.6; }} QWidget#chatInputArea {{ background-color: {C.BG_ELEVATED}; border-top: 1px solid {C.BORDER}; }} QTextEdit#chatInput {{ background-color: {C.BG_INPUT}; border: 1px solid {C.BORDER}; border-radius: 6px; padding: 6px 10px; font-size: 12px; color: {C.TEXT_PRIMARY}; }} QTextEdit#chatInput:focus {{ border-color: {C.ACCENT}; }} QWidget#quickActions {{ background-color: {C.BG_MAIN}; border-top: 1px solid {C.BORDER}; border-bottom: 1px solid {C.BORDER}; }} QPushButton#qaBtn {{ background-color: {C.BG_INPUT}; border: 1px solid {C.BORDER}; border-radius: 10px; padding: 2px 8px; font-size: 11px; color: {C.TEXT_SECONDARY}; font-weight: 500; }} QPushButton#qaBtn:hover {{ background-color: {C.BG_HOVER}; border-color: {C.ACCENT}; color: {C.TEXT_PRIMARY}; }} QFrame#applyBlock {{ background-color: {C.BG_PANEL}; border: 1px solid {C.BORDER}; border-left: 3px solid {C.ACCENT}; border-radius: 4px; }} QTextEdit#applyCode {{ background-color: transparent; border: none; padding: 4px; font-size: 10px; font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", "Consolas", monospace; color: {C.TEXT_SECONDARY}; }} """ def _apply_palette(app: QApplication, C): palette = QPalette() palette.setColor(QPalette.ColorRole.Window, QColor(C.BG_MAIN)) palette.setColor(QPalette.ColorRole.WindowText, QColor(C.TEXT_PRIMARY)) palette.setColor(QPalette.ColorRole.Base, QColor(C.BG_INPUT)) palette.setColor(QPalette.ColorRole.AlternateBase, QColor(C.BG_ELEVATED)) palette.setColor(QPalette.ColorRole.Text, QColor(C.TEXT_PRIMARY)) palette.setColor(QPalette.ColorRole.PlaceholderText, QColor(C.TEXT_MUTED)) palette.setColor(QPalette.ColorRole.Button, QColor(C.BG_ELEVATED)) palette.setColor(QPalette.ColorRole.ButtonText, QColor(C.TEXT_PRIMARY)) palette.setColor(QPalette.ColorRole.Highlight, QColor(C.ACCENT)) palette.setColor(QPalette.ColorRole.HighlightedText, QColor("#FFFFFF")) palette.setColor(QPalette.ColorRole.Link, QColor(C.INFO)) palette.setColor(QPalette.ColorRole.ToolTipBase, QColor(C.BG_ELEVATED)) palette.setColor(QPalette.ColorRole.ToolTipText, QColor(C.TEXT_PRIMARY)) app.setPalette(palette) def apply(app: QApplication, dark: bool = True): global Colors, _is_dark _is_dark = dark Colors = DarkColors if dark else LightColors app.setStyle("Fusion") _apply_palette(app, Colors) app.setStyleSheet(_build_stylesheet(Colors)) def toggle(app: QApplication) -> bool: """Toggle dark/light theme. Returns True if now dark.""" global _is_dark apply(app, dark=not _is_dark) return _is_dark def is_dark() -> bool: return _is_dark def restyle(widget, obj_name: str) -> None: """Change a widget's objectName and force Qt to re-evaluate CSS rules.""" widget.setObjectName(obj_name) widget.style().unpolish(widget) widget.style().polish(widget)