From ebebfa9f219720cf1c67fd1a4a000b33657ab075 Mon Sep 17 00:00:00 2001 From: mpstaton Date: Sat, 2 May 2026 18:12:52 -0500 Subject: [PATCH] improve(modals): apply wide-modal pattern to LM Studio + Text Enhancement modals Brings LMStudioModal, TextEnhancementModal, and TextEnhancementWithImagesModal to the same shape as Ask Perplexity / Ask Claude / Ask Perplexica: modalEl.addClass(...) wide-modal unlock, sectioned header/section/footer layout, BEM-scoped class names, native Obsidian Setting components for dropdowns and toggles, custom textareas where Setting is a poor fit, Cmd/Ctrl+Enter submit, mobile breakpoint at 600px. LMStudioModal: model dropdown with live taglines, Max Tokens input, Temperature slider (0-2 with dynamic tooltip), optional System Prompt textarea, Images + Stream toggles. TextEnhancementModal + TextEnhancementWithImagesModal: read-only Selected Text textarea (background-secondary tint to distinguish from editable input) + editable prompt pre-filled from the plugin template, button busy-state during the call. Bonus: TextEnhancementWithImagesModal CSS class name now matches the file/class name (was 'get-related-images-modal' on the .ts side while the file was text-enhancement-with-images-modal.css). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/modals/LMStudioModal.ts | 303 ++++--- src/modals/TextEnhancementModal.ts | 187 +++-- src/modals/TextEnhancementWithImagesModal.ts | 196 +++-- src/styles/lmstudio-modal.css | 197 ++++- src/styles/text-enhancement-modal.css | 220 +++-- .../text-enhancement-with-images-modal.css | 195 ++++- styles.css | 771 +----------------- 7 files changed, 881 insertions(+), 1188 deletions(-) diff --git a/src/modals/LMStudioModal.ts b/src/modals/LMStudioModal.ts index d85599c..0235452 100644 --- a/src/modals/LMStudioModal.ts +++ b/src/modals/LMStudioModal.ts @@ -1,162 +1,217 @@ import type { App, Editor } from 'obsidian'; -import { Modal, Notice } from 'obsidian'; +import { Modal, Notice, Setting } from 'obsidian'; import type { LMStudioService, LMStudioOptions } from '../services/lmStudioService'; import type { PromptsService } from '../services/promptsService'; +const LMSTUDIO_MODELS: Array<{ value: string; label: string; tagline: string }> = [ + { value: 'ibm/granite-3.2-8b', label: 'IBM Granite 3.2 8B', tagline: 'IBM Granite — strong on code and structured reasoning.' }, + { value: 'microsoft/phi-4-reasoning-plus', label: 'Phi-4 Reasoning Plus', tagline: 'Microsoft Phi-4 — small, fast model tuned for chain-of-thought reasoning.' }, + { value: 'google/gemma-3-12b', label: 'Gemma 3 12B', tagline: 'Google Gemma 3 — solid all-rounder open model.' }, + { value: 'meta-llama/llama-3.2-3b-instruct', label: 'Llama 3.2 3B Instruct', tagline: 'Meta Llama 3.2 — small instruction-tuned model, fastest of the bundled choices.' }, + { value: 'custom-model', label: 'custom-model', tagline: 'Generic placeholder — LM Studio will route this to whichever model is currently loaded.' }, +]; + +const DEFAULT_MODEL = 'ibm/granite-3.2-8b'; +const DEFAULT_MAX_TOKENS = 2048; +const DEFAULT_TEMPERATURE = 0.7; + export class LMStudioModal extends Modal { private editor: Editor; private lmStudioService: LMStudioService; private promptsService: PromptsService; - private queryInput!: HTMLTextAreaElement; - private modelSelect!: HTMLSelectElement; - private streamToggle!: HTMLInputElement; - private maxTokensInput!: HTMLInputElement; - private temperatureInput!: HTMLInputElement; - private systemPromptInput!: HTMLTextAreaElement; - private imagesToggle!: HTMLInputElement; - constructor(app: App, editor: Editor, lmStudioService: LMStudioService, promptsService: PromptsService) { + private query = ''; + private model: string = DEFAULT_MODEL; + private systemPrompt = ''; + private maxTokens: number = DEFAULT_MAX_TOKENS; + private temperature: number = DEFAULT_TEMPERATURE; + private images = false; + private stream = true; + + private modelDescEl: HTMLElement | null = null; + + constructor( + app: App, + editor: Editor, + lmStudioService: LMStudioService, + promptsService: PromptsService + ) { super(app); this.editor = editor; this.lmStudioService = lmStudioService; this.promptsService = promptsService; } - - onOpen() { - const {contentEl} = this; - contentEl.addClass('lmstudio-modal'); - contentEl.createEl('h2', {text: 'Ask LM Studio'}); - - const form = contentEl.createEl('form'); - - // Query input - const queryDiv = form.createDiv({cls: 'setting-item'}); - queryDiv.createEl('label', {text: 'Your Question'}); - this.queryInput = queryDiv.createEl('textarea', { - cls: 'text-input', + + onOpen(): void { + const { contentEl, modalEl } = this; + modalEl.addClass('lmstudio-modal'); + contentEl.empty(); + + // ----- Header ----- + const header = contentEl.createDiv({ cls: 'lmstudio-modal__header' }); + header.createEl('h2', { text: 'Ask LM Studio', cls: 'lmstudio-modal__title' }); + header.createEl('p', { + cls: 'lmstudio-modal__subtitle', + text: 'Run queries against your local LM Studio server. Streams into the active note at the cursor.', + }); + + // ----- Question ----- + const querySection = contentEl.createDiv({ cls: 'lmstudio-modal__section' }); + querySection.createEl('label', { + text: 'Question', + cls: 'lmstudio-modal__label', + attr: { for: 'lmstudio-modal-query' }, + }); + const queryTextarea = querySection.createEl('textarea', { + cls: 'lmstudio-modal__textarea', attr: { - rows: '4', - placeholder: this.promptsService.getLMStudioQueryPlaceholder() - } - }); - - // Model selection - const modelDiv = form.createDiv({cls: 'setting-item'}); - modelDiv.createEl('label', {text: 'Model'}); - this.modelSelect = modelDiv.createEl('select', {cls: 'dropdown'}); - // Use common LM Studio models - these would be dynamically loaded ideally - ['ibm/granite-3.2-8b', 'microsoft/phi-4-reasoning-plus', 'google/gemma-3-12b', 'meta-llama/llama-3.2-3b-instruct', 'custom-model'].forEach(model => { - const option = this.modelSelect.createEl('option', {value: model, text: model}); - if (model === 'ibm/granite-3.2-8b') option.selected = true; - }); - - // System prompt - const systemDiv = form.createDiv({cls: 'setting-item'}); - systemDiv.createEl('label', {text: 'System Prompt (Optional)'}); - this.systemPromptInput = systemDiv.createEl('textarea', { - cls: 'text-input system-prompt-input', - attr: { - rows: '2', - placeholder: this.promptsService.getLMStudioSystemPromptPlaceholder() - } - }); - - // Max tokens - const maxTokensDiv = form.createDiv({cls: 'setting-item'}); - maxTokensDiv.createEl('label', {text: 'Max Tokens'}); - this.maxTokensInput = maxTokensDiv.createEl('input', { - type: 'number', - value: '2048', - cls: 'text-input' - }); - - // Temperature - const tempDiv = form.createDiv({cls: 'setting-item'}); - tempDiv.createEl('label', {text: 'Temperature (0.0 - 2.0)'}); - this.temperatureInput = tempDiv.createEl('input', { - type: 'number', - value: '0.7', - attr: { - step: '0.1', - min: '0', - max: '2' + id: 'lmstudio-modal-query', + rows: '6', + placeholder: this.promptsService.getLMStudioQueryPlaceholder(), }, - cls: 'text-input' + }); + queryTextarea.value = this.query; + queryTextarea.addEventListener('input', () => { + this.query = queryTextarea.value; + }); + queryTextarea.addEventListener('keydown', (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { + e.preventDefault(); + void this.onSubmit(); + } }); - // Images toggle - const imagesDiv = form.createDiv({cls: 'setting-item'}); - const imagesLabel = imagesDiv.createEl('label'); - this.imagesToggle = imagesLabel.createEl('input', {type: 'checkbox'}); - this.imagesToggle.checked = false; - imagesLabel.createSpan({text: ' Include Images'}); - - // Add description for images toggle - const imagesDesc = imagesDiv.createDiv({cls: 'setting-item-description images-description'}); - imagesDesc.textContent = this.promptsService.getImagesToggleGenericDescription(); + // ----- Model ----- + const modelSection = contentEl.createDiv({ cls: 'lmstudio-modal__section' }); + modelSection.createEl('h3', { text: 'Model', cls: 'lmstudio-modal__section-title' }); - // Stream toggle - const streamDiv = form.createDiv({cls: 'setting-item'}); - const streamLabel = streamDiv.createEl('label'); - this.streamToggle = streamLabel.createEl('input', {type: 'checkbox'}); - this.streamToggle.checked = true; - streamLabel.createSpan({text: ' Stream response'}); - - const buttonDiv = contentEl.createDiv({cls: 'setting-item'}); - const askButton = buttonDiv.createEl('button', { + const modelSetting = new Setting(modelSection) + .setName('Model') + .setDesc(this.modelTagline(this.model)) + .addDropdown(dd => { + LMSTUDIO_MODELS.forEach(({ value, label }) => dd.addOption(value, label)); + dd.setValue(this.model); + dd.onChange((value) => { + this.model = value; + if (this.modelDescEl) this.modelDescEl.textContent = this.modelTagline(value); + }); + }); + this.modelDescEl = modelSetting.descEl; + + new Setting(modelSection) + .setName('Max Tokens') + .setDesc('Upper bound on response length. Local models cap lower than cloud — 2048 is a safe default.') + .addText(t => t + .setValue(String(this.maxTokens)) + .onChange(v => { + const parsed = parseInt(v, 10); + this.maxTokens = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_TOKENS; + })); + + new Setting(modelSection) + .setName('Temperature') + .setDesc('Higher = more creative / varied. 0.7 is the conventional default. Slide to 0 for deterministic output.') + .addSlider(s => s + .setLimits(0, 2, 0.1) + .setValue(this.temperature) + .setDynamicTooltip() + .onChange(v => { this.temperature = v; })); + + // ----- System Prompt (multi-line, doesn't fit a Setting row) ----- + const systemSection = contentEl.createDiv({ cls: 'lmstudio-modal__section' }); + systemSection.createEl('h3', { text: 'System Prompt (Optional)', cls: 'lmstudio-modal__section-title' }); + systemSection.createEl('p', { + cls: 'lmstudio-modal__hint', + text: 'Override the default system prompt for this request. Leave blank to use the plugin default.', + }); + const systemTextarea = systemSection.createEl('textarea', { + cls: 'lmstudio-modal__textarea lmstudio-modal__textarea--system', + attr: { + rows: '3', + placeholder: this.promptsService.getLMStudioSystemPromptPlaceholder(), + }, + }); + systemTextarea.value = this.systemPrompt; + systemTextarea.addEventListener('input', () => { + this.systemPrompt = systemTextarea.value; + }); + + // ----- Returns ----- + const returnsSection = contentEl.createDiv({ cls: 'lmstudio-modal__section' }); + returnsSection.createEl('h3', { text: 'Include in response', cls: 'lmstudio-modal__section-title' }); + + new Setting(returnsSection) + .setName('Images') + .setDesc(this.promptsService.getImagesToggleGenericDescription()) + .addToggle(t => t + .setValue(this.images) + .onChange(v => { this.images = v; })); + + // ----- Behavior ----- + const behaviorSection = contentEl.createDiv({ cls: 'lmstudio-modal__section' }); + behaviorSection.createEl('h3', { text: 'Behavior', cls: 'lmstudio-modal__section-title' }); + + new Setting(behaviorSection) + .setName('Stream Response') + .setDesc('Write tokens into the note as they arrive — recommended for long answers and slow local models.') + .addToggle(t => t + .setValue(this.stream) + .onChange(v => { this.stream = v; })); + + // ----- Footer ----- + const footer = contentEl.createDiv({ cls: 'lmstudio-modal__footer' }); + const cancelBtn = footer.createEl('button', { + text: 'Cancel', + cls: 'lmstudio-modal__button', + }); + cancelBtn.addEventListener('click', () => this.close()); + + const askBtn = footer.createEl('button', { text: 'Ask LM Studio', - cls: 'mod-cta' + cls: 'lmstudio-modal__button mod-cta', }); - - form.onsubmit = (e) => { - e.preventDefault(); - void this.onSubmit(); - }; - - askButton.onclick = () => this.onSubmit(); - - // Focus on the query input - setTimeout(() => this.queryInput.focus(), 100); + askBtn.addEventListener('click', () => void this.onSubmit()); + + setTimeout(() => queryTextarea.focus(), 50); } - - async onSubmit() { - const query = this.queryInput.value.trim(); - if (!query) { + + private modelTagline(value: string): string { + return LMSTUDIO_MODELS.find(m => m.value === value)?.tagline ?? ''; + } + + private async onSubmit(): Promise { + const trimmed = this.query.trim(); + if (!trimmed) { new Notice(this.promptsService.getEnterQuestionNotice()); return; } - // If images are enabled, add image markers to the query - let processedQuery = query; - if (this.imagesToggle.checked) { - processedQuery = `${query} - -${this.promptsService.getImageReferencesPrompt()}`; + let processedQuery = trimmed; + if (this.images) { + processedQuery = `${trimmed}\n\n${this.promptsService.getImageReferencesPrompt()}`; } const options: LMStudioOptions = { - max_tokens: parseInt(this.maxTokensInput.value) || 2048, - temperature: parseFloat(this.temperatureInput.value) || 0.7, - return_images: this.imagesToggle.checked + max_tokens: this.maxTokens, + temperature: this.temperature, + return_images: this.images, }; - - const systemPrompt = this.systemPromptInput.value.trim(); - if (systemPrompt) { - options.system_prompt = systemPrompt; + const trimmedSystemPrompt = this.systemPrompt.trim(); + if (trimmedSystemPrompt) { + options.system_prompt = trimmedSystemPrompt; } this.close(); await this.lmStudioService.queryLMStudio( - processedQuery, - this.modelSelect.value, - this.streamToggle.checked, - this.editor, + processedQuery, + this.model, + this.stream, + this.editor, options ); } - - onClose() { - const {contentEl} = this; - contentEl.empty(); + + onClose(): void { + this.contentEl.empty(); } -} +} diff --git a/src/modals/TextEnhancementModal.ts b/src/modals/TextEnhancementModal.ts index bd1463c..d2d0682 100644 --- a/src/modals/TextEnhancementModal.ts +++ b/src/modals/TextEnhancementModal.ts @@ -8,120 +8,141 @@ export class TextEnhancementModal extends Modal { protected perplexityService: PerplexityService; protected promptsService: PromptsService; protected selectedText: string; - protected promptTextArea!: HTMLTextAreaElement; - protected enhanceButton!: HTMLButtonElement; - constructor(app: App, editor: Editor, perplexityService: PerplexityService, promptsService: PromptsService, selectedText: string) { + private prompt: string; + private enhanceBtn!: HTMLButtonElement; + + constructor( + app: App, + editor: Editor, + perplexityService: PerplexityService, + promptsService: PromptsService, + selectedText: string + ) { super(app); this.editor = editor; this.perplexityService = perplexityService; this.promptsService = promptsService; this.selectedText = selectedText; + this.prompt = this.promptsService.getEnhanceTemplate(this.selectedText); } - - onOpen() { - const {contentEl} = this; - contentEl.addClass('text-enhancement-modal'); - contentEl.createEl('h2', {text: 'Enhance Text with Perplexity'}); - - const form = contentEl.createEl('form'); - - // Original text display - const originalTextDiv = form.createDiv({cls: 'setting-item'}); - originalTextDiv.createEl('label', {text: 'Selected Text'}); - const originalTextArea = originalTextDiv.createEl('textarea', { - cls: 'text-input', + + onOpen(): void { + const { contentEl, modalEl } = this; + modalEl.addClass('text-enhancement-modal'); + contentEl.empty(); + + // ----- Header ----- + const header = contentEl.createDiv({ cls: 'text-enhancement-modal__header' }); + header.createEl('h2', { text: 'Enhance Text with Perplexity', cls: 'text-enhancement-modal__title' }); + header.createEl('p', { + cls: 'text-enhancement-modal__subtitle', + text: 'Rewrite or expand selected text via Perplexity (sonar-pro). Streams into the active note at the cursor with Citations.', + }); + + // ----- Selected Text (read-only) ----- + const selectedSection = contentEl.createDiv({ cls: 'text-enhancement-modal__section' }); + selectedSection.createEl('label', { + text: 'Selected Text', + cls: 'text-enhancement-modal__label', + }); + const selectedTextarea = selectedSection.createEl('textarea', { + cls: 'text-enhancement-modal__textarea text-enhancement-modal__textarea--readonly', attr: { + rows: '6', + readonly: 'readonly', + }, + }); + selectedTextarea.value = this.selectedText; + + // ----- Enhancement Prompt (editable) ----- + const promptSection = contentEl.createDiv({ cls: 'text-enhancement-modal__section' }); + promptSection.createEl('label', { + text: 'Enhancement Prompt', + cls: 'text-enhancement-modal__label', + attr: { for: 'text-enhancement-modal-prompt' }, + }); + promptSection.createEl('p', { + cls: 'text-enhancement-modal__hint', + text: 'Pre-filled from the plugin template — edit to refine the rewrite instructions before submitting.', + }); + const promptTextarea = promptSection.createEl('textarea', { + cls: 'text-enhancement-modal__textarea', + attr: { + id: 'text-enhancement-modal-prompt', rows: '8', - readonly: 'readonly' + placeholder: 'Enter your enhancement prompt…', + }, + }); + promptTextarea.value = this.prompt; + promptTextarea.addEventListener('input', () => { + this.prompt = promptTextarea.value; + }); + promptTextarea.addEventListener('keydown', (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { + e.preventDefault(); + void this.onSubmit(); } }); - originalTextArea.value = this.selectedText; - originalTextArea.style.backgroundColor = 'var(--background-secondary)'; - originalTextArea.style.color = 'var(--text-muted)'; - // Editable prompt - const promptDiv = form.createDiv({cls: 'setting-item'}); - promptDiv.createEl('label', {text: 'Enhancement Prompt'}); - this.promptTextArea = promptDiv.createEl('textarea', { - cls: 'text-input', - attr: { - rows: '8', - placeholder: 'Enter your enhancement prompt...' - } - }); - // Pre-populate with the enhancement prompt template - const enhancementPrompt = this.promptsService.getEnhanceTemplate(this.selectedText); - this.promptTextArea.value = enhancementPrompt; - - // Buttons container - const buttonsDiv = form.createDiv({cls: 'setting-item'}); - buttonsDiv.style.display = 'flex'; - buttonsDiv.style.gap = '10px'; - buttonsDiv.style.flexWrap = 'wrap'; - - // Enhance button - this.enhanceButton = buttonsDiv.createEl('button', { - text: 'Enhance Text', - cls: 'mod-cta' - }); - this.enhanceButton.onclick = async (e) => { - e.preventDefault(); - await this.enhanceText(); - }; - - // Close button - const closeButton = buttonsDiv.createEl('button', { + // ----- Footer ----- + const footer = contentEl.createDiv({ cls: 'text-enhancement-modal__footer' }); + const cancelBtn = footer.createEl('button', { text: 'Cancel', - cls: 'mod-cta' + cls: 'text-enhancement-modal__button', }); - closeButton.onclick = (e) => { - e.preventDefault(); - this.close(); - }; + cancelBtn.addEventListener('click', () => this.close()); + + this.enhanceBtn = footer.createEl('button', { + text: 'Enhance Text', + cls: 'text-enhancement-modal__button mod-cta', + }); + this.enhanceBtn.addEventListener('click', () => void this.onSubmit()); + + setTimeout(() => promptTextarea.focus(), 50); } - private async enhanceText(): Promise { + private async onSubmit(): Promise { + const trimmed = this.prompt.trim(); + if (!trimmed) { + new Notice('Please enter an enhancement prompt.'); + return; + } + try { - this.enhanceButton.disabled = true; - this.enhanceButton.textContent = 'Enhancing...'; - - // Use the editable prompt from the textarea - const enhancementPrompt = this.promptTextArea.value; - - // Close the modal immediately so user can see the streaming content + this.enhanceBtn.disabled = true; + this.enhanceBtn.textContent = 'Enhancing…'; + this.close(); - - // Insert a new line before the enhanced text to make it clear what's new - const currentPosition = this.editor.getCursor(); - this.editor.replaceRange('\n\n', currentPosition); - - // Call Perplexity service directly with the real editor, always streaming + + const cursor = this.editor.getCursor(); + this.editor.replaceRange('\n\n', cursor); + await this.perplexityService.queryPerplexity( - enhancementPrompt, - 'sonar-pro', // Use default model - true, // Always stream + trimmed, + 'sonar-pro', + true, this.editor, { return_citations: true, return_images: false, - return_related_questions: false + return_related_questions: false, } ); - - new Notice('Text enhancement completed!'); - + + new Notice('Text enhancement completed.'); } catch (error) { console.error('Error enhancing text:', error); new Notice('Failed to enhance text. Check console for details.'); } finally { - this.enhanceButton.disabled = false; - this.enhanceButton.textContent = 'Enhance Text'; + if (this.enhanceBtn) { + this.enhanceBtn.disabled = false; + this.enhanceBtn.textContent = 'Enhance Text'; + } } } - onClose() { - const {contentEl} = this; - contentEl.empty(); + onClose(): void { + this.contentEl.empty(); } } diff --git a/src/modals/TextEnhancementWithImagesModal.ts b/src/modals/TextEnhancementWithImagesModal.ts index a884339..d3a4fbf 100644 --- a/src/modals/TextEnhancementWithImagesModal.ts +++ b/src/modals/TextEnhancementWithImagesModal.ts @@ -8,120 +8,144 @@ export class TextEnhancementWithImagesModal extends Modal { protected perplexityService: PerplexityService; protected promptsService: PromptsService; protected selectedText: string; - protected promptTextArea!: HTMLTextAreaElement; - protected enhanceButton!: HTMLButtonElement; - constructor(app: App, editor: Editor, perplexityService: PerplexityService, promptsService: PromptsService, selectedText: string) { + private prompt: string; + private fetchBtn!: HTMLButtonElement; + + constructor( + app: App, + editor: Editor, + perplexityService: PerplexityService, + promptsService: PromptsService, + selectedText: string + ) { super(app); this.editor = editor; this.perplexityService = perplexityService; this.promptsService = promptsService; this.selectedText = selectedText; + this.prompt = this.promptsService.getEnhanceWithImagesTemplate(this.selectedText); } - - onOpen() { - const {contentEl} = this; - contentEl.addClass('get-related-images-modal'); - contentEl.createEl('h2', {text: 'Get Related Images'}); - - const form = contentEl.createEl('form'); - - // Original text display - const originalTextDiv = form.createDiv({cls: 'setting-item'}); - originalTextDiv.createEl('label', {text: 'Selected Text'}); - const originalTextArea = originalTextDiv.createEl('textarea', { - cls: 'text-input', - attr: { - rows: '8', - readonly: 'readonly' - } - }); - originalTextArea.value = this.selectedText; - originalTextArea.style.backgroundColor = 'var(--background-secondary)'; - originalTextArea.style.color = 'var(--text-muted)'; - // Editable prompt - const promptDiv = form.createDiv({cls: 'setting-item'}); - promptDiv.createEl('label', {text: 'Image Request Prompt'}); - this.promptTextArea = promptDiv.createEl('textarea', { - cls: 'text-input', - attr: { - rows: '8', - placeholder: 'Enter your image request prompt...' - } - }); - // Pre-populate with the image request prompt template - const imageRequestPrompt = this.promptsService.getEnhanceWithImagesTemplate(this.selectedText); - this.promptTextArea.value = imageRequestPrompt; + onOpen(): void { + const { contentEl, modalEl } = this; + modalEl.addClass('text-enhancement-with-images-modal'); + contentEl.empty(); - // Buttons container - const buttonsDiv = form.createDiv({cls: 'setting-item'}); - buttonsDiv.style.display = 'flex'; - buttonsDiv.style.gap = '10px'; - buttonsDiv.style.flexWrap = 'wrap'; - - // Enhance button - this.enhanceButton = buttonsDiv.createEl('button', { + // ----- Header ----- + const header = contentEl.createDiv({ cls: 'text-enhancement-with-images-modal__header' }); + header.createEl('h2', { text: 'Get Related Images', - cls: 'mod-cta' + cls: 'text-enhancement-with-images-modal__title', }); - this.enhanceButton.onclick = async (e) => { - e.preventDefault(); - await this.enhanceTextWithImages(); - }; - - // Close button - const closeButton = buttonsDiv.createEl('button', { + header.createEl('p', { + cls: 'text-enhancement-with-images-modal__subtitle', + text: 'Find images related to selected text via Perplexity (sonar-pro). Streams image markers into the active note at the cursor.', + }); + + // ----- Selected Text (read-only) ----- + const selectedSection = contentEl.createDiv({ cls: 'text-enhancement-with-images-modal__section' }); + selectedSection.createEl('label', { + text: 'Selected Text', + cls: 'text-enhancement-with-images-modal__label', + }); + const selectedTextarea = selectedSection.createEl('textarea', { + cls: 'text-enhancement-with-images-modal__textarea text-enhancement-with-images-modal__textarea--readonly', + attr: { + rows: '6', + readonly: 'readonly', + }, + }); + selectedTextarea.value = this.selectedText; + + // ----- Image Request Prompt (editable) ----- + const promptSection = contentEl.createDiv({ cls: 'text-enhancement-with-images-modal__section' }); + promptSection.createEl('label', { + text: 'Image Request Prompt', + cls: 'text-enhancement-with-images-modal__label', + attr: { for: 'text-enhancement-with-images-modal-prompt' }, + }); + promptSection.createEl('p', { + cls: 'text-enhancement-with-images-modal__hint', + text: 'Pre-filled from the plugin template — edit to refine which kinds of images to surface.', + }); + const promptTextarea = promptSection.createEl('textarea', { + cls: 'text-enhancement-with-images-modal__textarea', + attr: { + id: 'text-enhancement-with-images-modal-prompt', + rows: '8', + placeholder: 'Enter your image request prompt…', + }, + }); + promptTextarea.value = this.prompt; + promptTextarea.addEventListener('input', () => { + this.prompt = promptTextarea.value; + }); + promptTextarea.addEventListener('keydown', (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { + e.preventDefault(); + void this.onSubmit(); + } + }); + + // ----- Footer ----- + const footer = contentEl.createDiv({ cls: 'text-enhancement-with-images-modal__footer' }); + const cancelBtn = footer.createEl('button', { text: 'Cancel', - cls: 'mod-cta' + cls: 'text-enhancement-with-images-modal__button', }); - closeButton.onclick = (e) => { - e.preventDefault(); - this.close(); - }; + cancelBtn.addEventListener('click', () => this.close()); + + this.fetchBtn = footer.createEl('button', { + text: 'Get Related Images', + cls: 'text-enhancement-with-images-modal__button mod-cta', + }); + this.fetchBtn.addEventListener('click', () => void this.onSubmit()); + + setTimeout(() => promptTextarea.focus(), 50); } - private async enhanceTextWithImages(): Promise { + private async onSubmit(): Promise { + const trimmed = this.prompt.trim(); + if (!trimmed) { + new Notice('Please enter an image request prompt.'); + return; + } + try { - this.enhanceButton.disabled = true; - this.enhanceButton.textContent = 'Getting Related Images...'; - - // Use the editable prompt from the textarea - const imagePrompt = this.promptTextArea.value; - - // Close the modal immediately so user can see the streaming content + this.fetchBtn.disabled = true; + this.fetchBtn.textContent = 'Getting Related Images…'; + this.close(); - - // Insert a new line before the images to make it clear what's new - const currentPosition = this.editor.getCursor(); - this.editor.replaceRange('\n\n', currentPosition); - - // Call Perplexity service directly with the real editor, always streaming and with images enabled + + const cursor = this.editor.getCursor(); + this.editor.replaceRange('\n\n', cursor); + await this.perplexityService.queryPerplexity( - imagePrompt, - 'sonar-pro', // Use default model - true, // Always stream + trimmed, + 'sonar-pro', + true, this.editor, { - return_citations: false, // No citations needed for image-only response - return_images: true, // Enable images for this enhancement - return_related_questions: false + return_citations: false, + return_images: true, + return_related_questions: false, } ); - - new Notice('Related images added successfully!'); - + + new Notice('Related images added successfully.'); } catch (error) { console.error('Error getting related images:', error); new Notice('Failed to get related images. Check console for details.'); } finally { - this.enhanceButton.disabled = false; - this.enhanceButton.textContent = 'Get Related Images'; + if (this.fetchBtn) { + this.fetchBtn.disabled = false; + this.fetchBtn.textContent = 'Get Related Images'; + } } } - onClose() { - const {contentEl} = this; - contentEl.empty(); + onClose(): void { + this.contentEl.empty(); } } diff --git a/src/styles/lmstudio-modal.css b/src/styles/lmstudio-modal.css index 6e9c7d0..43679ec 100644 --- a/src/styles/lmstudio-modal.css +++ b/src/styles/lmstudio-modal.css @@ -1,12 +1,193 @@ -/* LMStudioModal Styles */ +/* ============================================================ + LM Studio Modal — local LLM query composer + Built on Obsidian's native Setting components for theming + parity, with custom textareas for the question + system prompt. + ============================================================ */ -.lmstudio-modal .text-input { - width: 100%; - margin: 8px 0; - padding: 12px; +.lmstudio-modal { + width: 90vw; + max-width: 640px; } -.lmstudio-modal .system-prompt-input { +.lmstudio-modal .modal-content { + padding: 0; +} + +/* ----- Header ----- */ + +.lmstudio-modal__header { + padding: 24px 28px 16px; + border-bottom: 1px solid var(--background-modifier-border); +} + +.lmstudio-modal__title { + margin: 0 0 6px; + font-size: 1.5em; + font-weight: 600; + color: var(--text-normal); + letter-spacing: -0.01em; +} + +.lmstudio-modal__subtitle { + margin: 0; + font-size: 13px; + line-height: 1.45; + color: var(--text-muted); +} + +/* ----- Sections ----- */ + +.lmstudio-modal__section { + padding: 18px 28px 4px; +} + +.lmstudio-modal__section + .lmstudio-modal__section { + border-top: 1px solid var(--background-modifier-border-hover); + margin-top: 4px; +} + +.lmstudio-modal__section-title { + margin: 0 0 12px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); +} + +.lmstudio-modal__section .setting-item { + padding: 10px 0; + border-top: none; +} + +.lmstudio-modal__section .setting-item + .setting-item { + border-top: 1px solid var(--background-modifier-border-hover); +} + +/* ----- Inline hint paragraph ----- */ + +.lmstudio-modal__hint { + margin: -4px 0 8px; + font-size: 12px; + line-height: 1.45; + color: var(--text-muted); +} + +/* ----- Question label + textareas ----- */ + +.lmstudio-modal__label { + display: block; + margin-bottom: 8px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); +} + +.lmstudio-modal__textarea { width: 100%; - min-height: 60px; -} \ No newline at end of file + min-height: 120px; + padding: 12px 14px; + font-family: var(--font-text); + font-size: 14px; + line-height: 1.5; + color: var(--text-normal); + background-color: var(--background-primary); + border: 1px solid var(--background-modifier-border); + border-radius: 8px; + resize: vertical; + transition: border-color 0.15s ease, box-shadow 0.15s ease; + box-sizing: border-box; +} + +.lmstudio-modal__textarea:focus { + outline: none; + border-color: var(--interactive-accent); + box-shadow: 0 0 0 3px var(--interactive-accent-hover); +} + +.lmstudio-modal__textarea::placeholder { + color: var(--text-faint); +} + +/* System-prompt textarea is shorter than the main question textarea */ +.lmstudio-modal__textarea--system { + min-height: 72px; + font-family: var(--font-monospace); + font-size: 13px; +} + +/* ----- Footer / actions ----- */ + +.lmstudio-modal__footer { + display: flex; + justify-content: flex-end; + gap: 8px; + padding: 16px 28px 24px; + margin-top: 12px; + border-top: 1px solid var(--background-modifier-border); + background-color: var(--background-secondary); + border-bottom-left-radius: var(--modal-radius, 8px); + border-bottom-right-radius: var(--modal-radius, 8px); +} + +.lmstudio-modal__button { + padding: 8px 18px; + font-size: 14px; + font-weight: 500; + border: 1px solid var(--background-modifier-border); + border-radius: 6px; + background-color: var(--background-primary); + color: var(--text-normal); + cursor: pointer; + transition: background-color 0.15s ease, border-color 0.15s ease, transform 0.05s ease; +} + +.lmstudio-modal__button:hover { + background-color: var(--background-modifier-hover); +} + +.lmstudio-modal__button:active { + transform: translateY(1px); +} + +.lmstudio-modal__button.mod-cta { + background-color: var(--interactive-accent); + color: var(--text-on-accent); + border-color: var(--interactive-accent); +} + +.lmstudio-modal__button.mod-cta:hover { + background-color: var(--interactive-accent-hover); + border-color: var(--interactive-accent-hover); +} + +.lmstudio-modal__button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* ----- Responsive ----- */ + +@media (max-width: 600px) { + .lmstudio-modal { + width: 95vw; + max-width: none; + } + + .lmstudio-modal__header, + .lmstudio-modal__section, + .lmstudio-modal__footer { + padding-left: 18px; + padding-right: 18px; + } + + .lmstudio-modal__footer { + flex-direction: column-reverse; + } + + .lmstudio-modal__button { + width: 100%; + } +} diff --git a/src/styles/text-enhancement-modal.css b/src/styles/text-enhancement-modal.css index 3ad29ea..87a8b2e 100644 --- a/src/styles/text-enhancement-modal.css +++ b/src/styles/text-enhancement-modal.css @@ -1,106 +1,180 @@ +/* ============================================================ + Text Enhancement Modal — selection rewrite/expand composer + Built on the unified wide-modal pattern (see + context-v/issues/Widen-Modals-in-Obsidian-using-CSS.md). + ============================================================ */ + .text-enhancement-modal { - max-width: 800px; width: 90vw; + max-width: 720px; } -.text-enhancement-modal .setting-item { - margin-bottom: 20px; +.text-enhancement-modal .modal-content { + padding: 0; } -.text-enhancement-modal .setting-item label { - display: block; - margin-bottom: 5px; +/* ----- Header ----- */ + +.text-enhancement-modal__header { + padding: 24px 28px 16px; + border-bottom: 1px solid var(--background-modifier-border); +} + +.text-enhancement-modal__title { + margin: 0 0 6px; + font-size: 1.5em; font-weight: 600; color: var(--text-normal); + letter-spacing: -0.01em; } -.text-enhancement-modal .text-input { - width: 40%; - min-height: 100px; - font-family: var(--font-monospace); +.text-enhancement-modal__subtitle { + margin: 0; + font-size: 13px; + line-height: 1.45; + color: var(--text-muted); +} + +/* ----- Sections ----- */ + +.text-enhancement-modal__section { + padding: 18px 28px 4px; +} + +.text-enhancement-modal__section + .text-enhancement-modal__section { + border-top: 1px solid var(--background-modifier-border-hover); + margin-top: 4px; +} + +/* ----- Inline hint paragraph ----- */ + +.text-enhancement-modal__hint { + margin: -4px 0 8px; + font-size: 12px; + line-height: 1.45; + color: var(--text-muted); +} + +/* ----- Labels + textareas ----- */ + +.text-enhancement-modal__label { + display: block; + margin-bottom: 8px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); +} + +.text-enhancement-modal__textarea { + width: 100%; + min-height: 120px; + padding: 12px 14px; + font-family: var(--font-text); font-size: 14px; line-height: 1.5; + color: var(--text-normal); + background-color: var(--background-primary); + border: 1px solid var(--background-modifier-border); + border-radius: 8px; resize: vertical; + transition: border-color 0.15s ease, box-shadow 0.15s ease; + box-sizing: border-box; } -.text-enhancement-modal .dropdown { - width: 100%; - padding: 8px; +.text-enhancement-modal__textarea:focus { + outline: none; + border-color: var(--interactive-accent); + box-shadow: 0 0 0 3px var(--interactive-accent-hover); +} + +.text-enhancement-modal__textarea::placeholder { + color: var(--text-faint); +} + +/* Read-only "Selected Text" display — visually distinct from editable input */ +.text-enhancement-modal__textarea--readonly { + background-color: var(--background-secondary); + color: var(--text-muted); + cursor: default; +} + +.text-enhancement-modal__textarea--readonly:focus { + border-color: var(--background-modifier-border); + box-shadow: none; +} + +/* ----- Footer / actions ----- */ + +.text-enhancement-modal__footer { + display: flex; + justify-content: flex-end; + gap: 8px; + padding: 16px 28px 24px; + margin-top: 12px; + border-top: 1px solid var(--background-modifier-border); + background-color: var(--background-secondary); + border-bottom-left-radius: var(--modal-radius, 8px); + border-bottom-right-radius: var(--modal-radius, 8px); +} + +.text-enhancement-modal__button { + padding: 8px 18px; + font-size: 14px; + font-weight: 500; border: 1px solid var(--background-modifier-border); - border-radius: 4px; + border-radius: 6px; background-color: var(--background-primary); color: var(--text-normal); -} - -.text-enhancement-modal button { - padding: 8px 16px; - border: none; - border-radius: 4px; cursor: pointer; - font-weight: 500; - transition: background-color 0.2s ease; + transition: background-color 0.15s ease, border-color 0.15s ease, transform 0.05s ease; } -.text-enhancement-modal button:disabled { +.text-enhancement-modal__button:hover { + background-color: var(--background-modifier-hover); +} + +.text-enhancement-modal__button:active { + transform: translateY(1px); +} + +.text-enhancement-modal__button.mod-cta { + background-color: var(--interactive-accent); + color: var(--text-on-accent); + border-color: var(--interactive-accent); +} + +.text-enhancement-modal__button.mod-cta:hover { + background-color: var(--interactive-accent-hover); + border-color: var(--interactive-accent-hover); +} + +.text-enhancement-modal__button:disabled { opacity: 0.5; cursor: not-allowed; } -.text-enhancement-modal button.mod-cta { - background-color: var(--interactive-accent); - color: var(--text-on-accent); -} +/* ----- Responsive ----- */ -.text-enhancement-modal button.mod-cta:hover:not(:disabled) { - background-color: var(--interactive-accent-hover); -} - -.text-enhancement-modal button.mod-warning { - background-color: var(--text-error); - color: var(--text-on-accent); -} - -.text-enhancement-modal button.mod-warning:hover:not(:disabled) { - background-color: var(--text-error-hover); -} - -.text-enhancement-modal input[type="checkbox"] { - margin-right: 8px; -} - -.text-enhancement-modal .setting-item-description { - font-size: 12px; - color: var(--text-muted); - margin-top: 4px; -} - -/* Original text area styling */ -.text-enhancement-modal .text-input[readonly] { - background-color: var(--background-secondary); - color: var(--text-muted); - border: 1px solid var(--background-modifier-border); -} - -/* Enhanced text area styling */ -.text-enhancement-modal .text-input:not([readonly]) { - background-color: var(--background-primary); - color: var(--text-normal); - border: 1px solid var(--background-modifier-border); -} - -/* Responsive design */ -@media (max-width: 768px) { +@media (max-width: 600px) { .text-enhancement-modal { width: 95vw; max-width: none; } - - .text-enhancement-modal button { - width: 100%; - margin-bottom: 8px; + + .text-enhancement-modal__header, + .text-enhancement-modal__section, + .text-enhancement-modal__footer { + padding-left: 18px; + padding-right: 18px; } - - .text-enhancement-modal .setting-item { - margin-bottom: 15px; + + .text-enhancement-modal__footer { + flex-direction: column-reverse; + } + + .text-enhancement-modal__button { + width: 100%; } } diff --git a/src/styles/text-enhancement-with-images-modal.css b/src/styles/text-enhancement-with-images-modal.css index 9f835c1..42fd5a8 100644 --- a/src/styles/text-enhancement-with-images-modal.css +++ b/src/styles/text-enhancement-with-images-modal.css @@ -1,72 +1,179 @@ -.get-related-images-modal { - max-width: 600px; +/* ============================================================ + Text Enhancement With Images Modal — selection → related images + Built on the unified wide-modal pattern (see + context-v/issues/Widen-Modals-in-Obsidian-using-CSS.md). + ============================================================ */ + +.text-enhancement-with-images-modal { width: 90vw; + max-width: 720px; } -.get-related-images-modal .setting-item { - margin-bottom: 20px; +.text-enhancement-with-images-modal .modal-content { + padding: 0; } -.get-related-images-modal .setting-item label { - display: block; - margin-bottom: 8px; +/* ----- Header ----- */ + +.text-enhancement-with-images-modal__header { + padding: 24px 28px 16px; + border-bottom: 1px solid var(--background-modifier-border); +} + +.text-enhancement-with-images-modal__title { + margin: 0 0 6px; + font-size: 1.5em; font-weight: 600; color: var(--text-normal); + letter-spacing: -0.01em; } -.get-related-images-modal .text-input { - width: 40%; +.text-enhancement-with-images-modal__subtitle { + margin: 0; + font-size: 13px; + line-height: 1.45; + color: var(--text-muted); +} + +/* ----- Sections ----- */ + +.text-enhancement-with-images-modal__section { + padding: 18px 28px 4px; +} + +.text-enhancement-with-images-modal__section + .text-enhancement-with-images-modal__section { + border-top: 1px solid var(--background-modifier-border-hover); + margin-top: 4px; +} + +/* ----- Inline hint paragraph ----- */ + +.text-enhancement-with-images-modal__hint { + margin: -4px 0 8px; + font-size: 12px; + line-height: 1.45; + color: var(--text-muted); +} + +/* ----- Labels + textareas ----- */ + +.text-enhancement-with-images-modal__label { + display: block; + margin-bottom: 8px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); +} + +.text-enhancement-with-images-modal__textarea { + width: 100%; min-height: 120px; - padding: 12px; + padding: 12px 14px; + font-family: var(--font-text); + font-size: 14px; + line-height: 1.5; + color: var(--text-normal); + background-color: var(--background-primary); + border: 1px solid var(--background-modifier-border); + border-radius: 8px; + resize: vertical; + transition: border-color 0.15s ease, box-shadow 0.15s ease; + box-sizing: border-box; +} + +.text-enhancement-with-images-modal__textarea:focus { + outline: none; + border-color: var(--interactive-accent); + box-shadow: 0 0 0 3px var(--interactive-accent-hover); +} + +.text-enhancement-with-images-modal__textarea::placeholder { + color: var(--text-faint); +} + +.text-enhancement-with-images-modal__textarea--readonly { + background-color: var(--background-secondary); + color: var(--text-muted); + cursor: default; +} + +.text-enhancement-with-images-modal__textarea--readonly:focus { + border-color: var(--background-modifier-border); + box-shadow: none; +} + +/* ----- Footer / actions ----- */ + +.text-enhancement-with-images-modal__footer { + display: flex; + justify-content: flex-end; + gap: 8px; + padding: 16px 28px 24px; + margin-top: 12px; + border-top: 1px solid var(--background-modifier-border); + background-color: var(--background-secondary); + border-bottom-left-radius: var(--modal-radius, 8px); + border-bottom-right-radius: var(--modal-radius, 8px); +} + +.text-enhancement-with-images-modal__button { + padding: 8px 18px; + font-size: 14px; + font-weight: 500; border: 1px solid var(--background-modifier-border); border-radius: 6px; background-color: var(--background-primary); color: var(--text-normal); - font-family: var(--font-monospace); - font-size: 14px; - line-height: 1.5; - resize: vertical; -} - -.get-related-images-modal .text-input:focus { - outline: none; - border-color: var(--interactive-accent); - box-shadow: 0 0 0 2px var(--interactive-accent-hover); -} - -.get-related-images-modal .text-input[readonly] { - background-color: var(--background-secondary); - color: var(--text-muted); - cursor: not-allowed; -} - -.get-related-images-modal button { - padding: 8px 16px; - border: none; - border-radius: 4px; - font-size: 14px; - font-weight: 500; cursor: pointer; - transition: background-color 0.2s ease; + transition: background-color 0.15s ease, border-color 0.15s ease, transform 0.05s ease; } -.get-related-images-modal button.mod-cta { +.text-enhancement-with-images-modal__button:hover { + background-color: var(--background-modifier-hover); +} + +.text-enhancement-with-images-modal__button:active { + transform: translateY(1px); +} + +.text-enhancement-with-images-modal__button.mod-cta { background-color: var(--interactive-accent); color: var(--text-on-accent); + border-color: var(--interactive-accent); } -.get-related-images-modal button.mod-cta:hover { +.text-enhancement-with-images-modal__button.mod-cta:hover { background-color: var(--interactive-accent-hover); + border-color: var(--interactive-accent-hover); } -.get-related-images-modal button:disabled { - opacity: 0.6; +.text-enhancement-with-images-modal__button:disabled { + opacity: 0.5; cursor: not-allowed; } -.get-related-images-modal h2 { - margin-bottom: 20px; - color: var(--text-normal); - font-size: 1.5em; - font-weight: 600; +/* ----- Responsive ----- */ + +@media (max-width: 600px) { + .text-enhancement-with-images-modal { + width: 95vw; + max-width: none; + } + + .text-enhancement-with-images-modal__header, + .text-enhancement-with-images-modal__section, + .text-enhancement-with-images-modal__footer { + padding-left: 18px; + padding-right: 18px; + } + + .text-enhancement-with-images-modal__footer { + flex-direction: column-reverse; + } + + .text-enhancement-with-images-modal__button { + width: 100%; + } } diff --git a/styles.css b/styles.css index a1d788f..71a7b89 100644 --- a/styles.css +++ b/styles.css @@ -1,770 +1 @@ -/* src/styles/perplexity-modal.css */ -.perplexity-modal { - width: 90vw; - max-width: 640px; -} -.perplexity-modal .modal-content { - padding: 0; -} -.perplexity-modal__header { - padding: 24px 28px 16px; - border-bottom: 1px solid var(--background-modifier-border); -} -.perplexity-modal__title { - margin: 0 0 6px; - font-size: 1.5em; - font-weight: 600; - color: var(--text-normal); - letter-spacing: -0.01em; -} -.perplexity-modal__subtitle { - margin: 0; - font-size: 13px; - line-height: 1.45; - color: var(--text-muted); -} -.perplexity-modal__section { - padding: 18px 28px 4px; -} -.perplexity-modal__section + .perplexity-modal__section { - border-top: 1px solid var(--background-modifier-border-hover); - margin-top: 4px; -} -.perplexity-modal__section-title { - margin: 0 0 12px; - font-size: 11px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--text-muted); -} -.perplexity-modal__section .setting-item { - padding: 10px 0; - border-top: none; -} -.perplexity-modal__section .setting-item + .setting-item { - border-top: 1px solid var(--background-modifier-border-hover); -} -.perplexity-modal__label { - display: block; - margin-bottom: 8px; - font-size: 11px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--text-muted); -} -.perplexity-modal__textarea { - width: 100%; - min-height: 120px; - padding: 12px 14px; - font-family: var(--font-text); - font-size: 14px; - line-height: 1.5; - color: var(--text-normal); - background-color: var(--background-primary); - border: 1px solid var(--background-modifier-border); - border-radius: 8px; - resize: vertical; - transition: border-color 0.15s ease, box-shadow 0.15s ease; - box-sizing: border-box; -} -.perplexity-modal__textarea:focus { - outline: none; - border-color: var(--interactive-accent); - box-shadow: 0 0 0 3px var(--interactive-accent-hover); -} -.perplexity-modal__textarea::placeholder { - color: var(--text-faint); -} -.perplexity-modal__footer { - display: flex; - justify-content: flex-end; - gap: 8px; - padding: 16px 28px 24px; - margin-top: 12px; - border-top: 1px solid var(--background-modifier-border); - background-color: var(--background-secondary); - border-bottom-left-radius: var(--modal-radius, 8px); - border-bottom-right-radius: var(--modal-radius, 8px); -} -.perplexity-modal__button { - padding: 8px 18px; - font-size: 14px; - font-weight: 500; - border: 1px solid var(--background-modifier-border); - border-radius: 6px; - background-color: var(--background-primary); - color: var(--text-normal); - cursor: pointer; - transition: - background-color 0.15s ease, - border-color 0.15s ease, - transform 0.05s ease; -} -.perplexity-modal__button:hover { - background-color: var(--background-modifier-hover); -} -.perplexity-modal__button:active { - transform: translateY(1px); -} -.perplexity-modal__button.mod-cta { - background-color: var(--interactive-accent); - color: var(--text-on-accent); - border-color: var(--interactive-accent); -} -.perplexity-modal__button.mod-cta:hover { - background-color: var(--interactive-accent-hover); - border-color: var(--interactive-accent-hover); -} -.perplexity-modal__button:disabled { - opacity: 0.5; - cursor: not-allowed; -} -@media (max-width: 600px) { - .perplexity-modal { - width: 95vw; - max-width: none; - } - .perplexity-modal__header, - .perplexity-modal__section, - .perplexity-modal__footer { - padding-left: 18px; - padding-right: 18px; - } - .perplexity-modal__footer { - flex-direction: column-reverse; - } - .perplexity-modal__button { - width: 100%; - } -} - -/* src/styles/article-generator-modal.css */ -.article-generator-modal { - width: 90vw; - max-width: 640px; -} -.article-generator-modal .modal-content { - padding: 0; -} -.article-generator-modal__header { - padding: 24px 28px 16px; - border-bottom: 1px solid var(--background-modifier-border); -} -.article-generator-modal__title { - margin: 0 0 6px; - font-size: 1.5em; - font-weight: 600; - color: var(--text-normal); - letter-spacing: -0.01em; -} -.article-generator-modal__subtitle { - margin: 0; - font-size: 13px; - line-height: 1.45; - color: var(--text-muted); -} -.article-generator-modal__section { - padding: 18px 28px 4px; -} -.article-generator-modal__section + .article-generator-modal__section { - border-top: 1px solid var(--background-modifier-border-hover); - margin-top: 4px; -} -.article-generator-modal__section-title { - margin: 0 0 12px; - font-size: 11px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--text-muted); -} -.article-generator-modal__section .setting-item { - padding: 10px 0; - border-top: none; -} -.article-generator-modal__section .setting-item + .setting-item { - border-top: 1px solid var(--background-modifier-border-hover); -} -.article-generator-modal__label { - display: block; - margin-bottom: 8px; - font-size: 11px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--text-muted); -} -.article-generator-modal__input { - width: 100%; - padding: 10px 14px; - font-family: var(--font-text); - font-size: 15px; - line-height: 1.4; - color: var(--text-normal); - background-color: var(--background-primary); - border: 1px solid var(--background-modifier-border); - border-radius: 8px; - transition: border-color 0.15s ease, box-shadow 0.15s ease; - box-sizing: border-box; -} -.article-generator-modal__input:focus { - outline: none; - border-color: var(--interactive-accent); - box-shadow: 0 0 0 3px var(--interactive-accent-hover); -} -.article-generator-modal__input::placeholder { - color: var(--text-faint); -} -.article-generator-modal__hint { - margin: 8px 0 0; - font-size: 12px; - line-height: 1.45; - color: var(--text-muted); - font-style: italic; -} -.article-generator-modal__warning { - margin-top: 6px; - font-size: 12px; - line-height: 1.4; - color: var(--text-error); - font-weight: 500; - display: none; -} -.article-generator-modal__warning.is-active { - display: block; -} -.article-generator-modal__footer { - display: flex; - justify-content: flex-end; - gap: 8px; - padding: 16px 28px 24px; - margin-top: 12px; - border-top: 1px solid var(--background-modifier-border); - background-color: var(--background-secondary); - border-bottom-left-radius: var(--modal-radius, 8px); - border-bottom-right-radius: var(--modal-radius, 8px); -} -.article-generator-modal__button { - padding: 8px 18px; - font-size: 14px; - font-weight: 500; - border: 1px solid var(--background-modifier-border); - border-radius: 6px; - background-color: var(--background-primary); - color: var(--text-normal); - cursor: pointer; - transition: - background-color 0.15s ease, - border-color 0.15s ease, - transform 0.05s ease; -} -.article-generator-modal__button:hover { - background-color: var(--background-modifier-hover); -} -.article-generator-modal__button:active { - transform: translateY(1px); -} -.article-generator-modal__button.mod-cta { - background-color: var(--interactive-accent); - color: var(--text-on-accent); - border-color: var(--interactive-accent); -} -.article-generator-modal__button.mod-cta:hover { - background-color: var(--interactive-accent-hover); - border-color: var(--interactive-accent-hover); -} -.article-generator-modal__button:disabled { - opacity: 0.5; - cursor: not-allowed; -} -@media (max-width: 600px) { - .article-generator-modal { - width: 95vw; - max-width: none; - } - .article-generator-modal__header, - .article-generator-modal__section, - .article-generator-modal__footer { - padding-left: 18px; - padding-right: 18px; - } - .article-generator-modal__footer { - flex-direction: column-reverse; - } - .article-generator-modal__button { - width: 100%; - } -} - -/* src/styles/perplexica-modal.css */ -.perplexica-modal { - width: 90vw; - max-width: 640px; -} -.perplexica-modal .modal-content { - padding: 0; -} -.perplexica-modal__header { - padding: 24px 28px 16px; - border-bottom: 1px solid var(--background-modifier-border); -} -.perplexica-modal__title { - margin: 0 0 6px; - font-size: 1.5em; - font-weight: 600; - color: var(--text-normal); - letter-spacing: -0.01em; -} -.perplexica-modal__subtitle { - margin: 0; - font-size: 13px; - line-height: 1.45; - color: var(--text-muted); -} -.perplexica-modal__section { - padding: 18px 28px 4px; -} -.perplexica-modal__section + .perplexica-modal__section { - border-top: 1px solid var(--background-modifier-border-hover); - margin-top: 4px; -} -.perplexica-modal__section-title { - margin: 0 0 12px; - font-size: 11px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--text-muted); -} -.perplexica-modal__section .setting-item { - padding: 10px 0; - border-top: none; -} -.perplexica-modal__section .setting-item + .setting-item { - border-top: 1px solid var(--background-modifier-border-hover); -} -.perplexica-modal__label { - display: block; - margin-bottom: 8px; - font-size: 11px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--text-muted); -} -.perplexica-modal__textarea { - width: 100%; - min-height: 120px; - padding: 12px 14px; - font-family: var(--font-text); - font-size: 14px; - line-height: 1.5; - color: var(--text-normal); - background-color: var(--background-primary); - border: 1px solid var(--background-modifier-border); - border-radius: 8px; - resize: vertical; - transition: border-color 0.15s ease, box-shadow 0.15s ease; - box-sizing: border-box; -} -.perplexica-modal__textarea:focus { - outline: none; - border-color: var(--interactive-accent); - box-shadow: 0 0 0 3px var(--interactive-accent-hover); -} -.perplexica-modal__textarea::placeholder { - color: var(--text-faint); -} -.perplexica-modal__footer { - display: flex; - justify-content: flex-end; - gap: 8px; - padding: 16px 28px 24px; - margin-top: 12px; - border-top: 1px solid var(--background-modifier-border); - background-color: var(--background-secondary); - border-bottom-left-radius: var(--modal-radius, 8px); - border-bottom-right-radius: var(--modal-radius, 8px); -} -.perplexica-modal__button { - padding: 8px 18px; - font-size: 14px; - font-weight: 500; - border: 1px solid var(--background-modifier-border); - border-radius: 6px; - background-color: var(--background-primary); - color: var(--text-normal); - cursor: pointer; - transition: - background-color 0.15s ease, - border-color 0.15s ease, - transform 0.05s ease; -} -.perplexica-modal__button:hover { - background-color: var(--background-modifier-hover); -} -.perplexica-modal__button:active { - transform: translateY(1px); -} -.perplexica-modal__button.mod-cta { - background-color: var(--interactive-accent); - color: var(--text-on-accent); - border-color: var(--interactive-accent); -} -.perplexica-modal__button.mod-cta:hover { - background-color: var(--interactive-accent-hover); - border-color: var(--interactive-accent-hover); -} -.perplexica-modal__button:disabled { - opacity: 0.5; - cursor: not-allowed; -} -@media (max-width: 600px) { - .perplexica-modal { - width: 95vw; - max-width: none; - } - .perplexica-modal__header, - .perplexica-modal__section, - .perplexica-modal__footer { - padding-left: 18px; - padding-right: 18px; - } - .perplexica-modal__footer { - flex-direction: column-reverse; - } - .perplexica-modal__button { - width: 100%; - } -} - -/* src/styles/lmstudio-modal.css */ -.lmstudio-modal .text-input { - width: 100%; - margin: 8px 0; - padding: 12px; -} -.lmstudio-modal .system-prompt-input { - width: 100%; - min-height: 60px; -} - -/* src/styles/url-update-modal.css */ -.url-update-modal .text-input { - width: 100%; - margin: 8px 0; - padding: 12px; -} - -/* src/styles/text-enhancement-modal.css */ -.text-enhancement-modal { - max-width: 800px; - width: 90vw; -} -.text-enhancement-modal .setting-item { - margin-bottom: 20px; -} -.text-enhancement-modal .setting-item label { - display: block; - margin-bottom: 5px; - font-weight: 600; - color: var(--text-normal); -} -.text-enhancement-modal .text-input { - width: 40%; - min-height: 100px; - font-family: var(--font-monospace); - font-size: 14px; - line-height: 1.5; - resize: vertical; -} -.text-enhancement-modal .dropdown { - width: 100%; - padding: 8px; - border: 1px solid var(--background-modifier-border); - border-radius: 4px; - background-color: var(--background-primary); - color: var(--text-normal); -} -.text-enhancement-modal button { - padding: 8px 16px; - border: none; - border-radius: 4px; - cursor: pointer; - font-weight: 500; - transition: background-color 0.2s ease; -} -.text-enhancement-modal button:disabled { - opacity: 0.5; - cursor: not-allowed; -} -.text-enhancement-modal button.mod-cta { - background-color: var(--interactive-accent); - color: var(--text-on-accent); -} -.text-enhancement-modal button.mod-cta:hover:not(:disabled) { - background-color: var(--interactive-accent-hover); -} -.text-enhancement-modal button.mod-warning { - background-color: var(--text-error); - color: var(--text-on-accent); -} -.text-enhancement-modal button.mod-warning:hover:not(:disabled) { - background-color: var(--text-error-hover); -} -.text-enhancement-modal input[type=checkbox] { - margin-right: 8px; -} -.text-enhancement-modal .setting-item-description { - font-size: 12px; - color: var(--text-muted); - margin-top: 4px; -} -.text-enhancement-modal .text-input[readonly] { - background-color: var(--background-secondary); - color: var(--text-muted); - border: 1px solid var(--background-modifier-border); -} -.text-enhancement-modal .text-input:not([readonly]) { - background-color: var(--background-primary); - color: var(--text-normal); - border: 1px solid var(--background-modifier-border); -} -@media (max-width: 768px) { - .text-enhancement-modal { - width: 95vw; - max-width: none; - } - .text-enhancement-modal button { - width: 100%; - margin-bottom: 8px; - } - .text-enhancement-modal .setting-item { - margin-bottom: 15px; - } -} - -/* src/styles/text-enhancement-with-images-modal.css */ -.get-related-images-modal { - max-width: 600px; - width: 90vw; -} -.get-related-images-modal .setting-item { - margin-bottom: 20px; -} -.get-related-images-modal .setting-item label { - display: block; - margin-bottom: 8px; - font-weight: 600; - color: var(--text-normal); -} -.get-related-images-modal .text-input { - width: 40%; - min-height: 120px; - padding: 12px; - border: 1px solid var(--background-modifier-border); - border-radius: 6px; - background-color: var(--background-primary); - color: var(--text-normal); - font-family: var(--font-monospace); - font-size: 14px; - line-height: 1.5; - resize: vertical; -} -.get-related-images-modal .text-input:focus { - outline: none; - border-color: var(--interactive-accent); - box-shadow: 0 0 0 2px var(--interactive-accent-hover); -} -.get-related-images-modal .text-input[readonly] { - background-color: var(--background-secondary); - color: var(--text-muted); - cursor: not-allowed; -} -.get-related-images-modal button { - padding: 8px 16px; - border: none; - border-radius: 4px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: background-color 0.2s ease; -} -.get-related-images-modal button.mod-cta { - background-color: var(--interactive-accent); - color: var(--text-on-accent); -} -.get-related-images-modal button.mod-cta:hover { - background-color: var(--interactive-accent-hover); -} -.get-related-images-modal button:disabled { - opacity: 0.6; - cursor: not-allowed; -} -.get-related-images-modal h2 { - margin-bottom: 20px; - color: var(--text-normal); - font-size: 1.5em; - font-weight: 600; -} - -/* src/styles/claude-modal.css */ -.claude-modal { - width: 90vw; - max-width: 640px; -} -.claude-modal .modal-content { - padding: 0; -} -.claude-modal__header { - padding: 24px 28px 16px; - border-bottom: 1px solid var(--background-modifier-border); -} -.claude-modal__title { - margin: 0 0 6px; - font-size: 1.5em; - font-weight: 600; - color: var(--text-normal); - letter-spacing: -0.01em; -} -.claude-modal__subtitle { - margin: 0; - font-size: 13px; - line-height: 1.45; - color: var(--text-muted); -} -.claude-modal__section { - padding: 18px 28px 4px; -} -.claude-modal__section + .claude-modal__section { - border-top: 1px solid var(--background-modifier-border-hover); - margin-top: 4px; -} -.claude-modal__section-title { - margin: 0 0 12px; - font-size: 11px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--text-muted); -} -.claude-modal__section .setting-item { - padding: 10px 0; - border-top: none; -} -.claude-modal__section .setting-item + .setting-item { - border-top: 1px solid var(--background-modifier-border-hover); -} -.claude-modal__label { - display: block; - margin-bottom: 8px; - font-size: 11px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--text-muted); -} -.claude-modal__textarea { - width: 100%; - min-height: 120px; - padding: 12px 14px; - font-family: var(--font-text); - font-size: 14px; - line-height: 1.5; - color: var(--text-normal); - background-color: var(--background-primary); - border: 1px solid var(--background-modifier-border); - border-radius: 8px; - resize: vertical; - transition: border-color 0.15s ease, box-shadow 0.15s ease; - box-sizing: border-box; -} -.claude-modal__textarea:focus { - outline: none; - border-color: var(--interactive-accent); - box-shadow: 0 0 0 3px var(--interactive-accent-hover); -} -.claude-modal__textarea::placeholder { - color: var(--text-faint); -} -.claude-modal__footer { - display: flex; - justify-content: flex-end; - gap: 8px; - padding: 16px 28px 24px; - margin-top: 12px; - border-top: 1px solid var(--background-modifier-border); - background-color: var(--background-secondary); - border-bottom-left-radius: var(--modal-radius, 8px); - border-bottom-right-radius: var(--modal-radius, 8px); -} -.claude-modal__button { - padding: 8px 18px; - font-size: 14px; - font-weight: 500; - border: 1px solid var(--background-modifier-border); - border-radius: 6px; - background-color: var(--background-primary); - color: var(--text-normal); - cursor: pointer; - transition: - background-color 0.15s ease, - border-color 0.15s ease, - transform 0.05s ease; -} -.claude-modal__button:hover { - background-color: var(--background-modifier-hover); -} -.claude-modal__button:active { - transform: translateY(1px); -} -.claude-modal__button.mod-cta { - background-color: var(--interactive-accent); - color: var(--text-on-accent); - border-color: var(--interactive-accent); -} -.claude-modal__button.mod-cta:hover { - background-color: var(--interactive-accent-hover); - border-color: var(--interactive-accent-hover); -} -.claude-modal__button:disabled { - opacity: 0.5; - cursor: not-allowed; -} -@media (max-width: 600px) { - .claude-modal { - width: 95vw; - max-width: none; - } - .claude-modal__header, - .claude-modal__section, - .claude-modal__footer { - padding-left: 18px; - padding-right: 18px; - } - .claude-modal__footer { - flex-direction: column-reverse; - } - .claude-modal__button { - width: 100%; - } -} - -/* src/styles/main.css */ -.callout[data-callout=brain] { - background-color: rgba(147, 51, 234, 0.1); - border-left: 4px solid #9333ea; -} -.callout[data-callout=brain] .callout-title { - color: #9333ea; - font-weight: 600; -} -.callout[data-callout=brain] .callout-content { - color: var(--text-normal); - font-style: normal; -} -.callout[data-callout=brain] .callout-icon { - color: #9333ea; -} +.perplexity-modal{width:90vw;max-width:640px}.perplexity-modal .modal-content{padding:0}.perplexity-modal__header{padding:24px 28px 16px;border-bottom:1px solid var(--background-modifier-border)}.perplexity-modal__title{margin:0 0 6px;font-size:1.5em;font-weight:600;color:var(--text-normal);letter-spacing:-.01em}.perplexity-modal__subtitle{margin:0;font-size:13px;line-height:1.45;color:var(--text-muted)}.perplexity-modal__section{padding:18px 28px 4px}.perplexity-modal__section+.perplexity-modal__section{border-top:1px solid var(--background-modifier-border-hover);margin-top:4px}.perplexity-modal__section-title{margin:0 0 12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted)}.perplexity-modal__section .setting-item{padding:10px 0;border-top:none}.perplexity-modal__section .setting-item+.setting-item{border-top:1px solid var(--background-modifier-border-hover)}.perplexity-modal__label{display:block;margin-bottom:8px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted)}.perplexity-modal__textarea{width:100%;min-height:120px;padding:12px 14px;font-family:var(--font-text);font-size:14px;line-height:1.5;color:var(--text-normal);background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:8px;resize:vertical;transition:border-color .15s ease,box-shadow .15s ease;box-sizing:border-box}.perplexity-modal__textarea:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 3px var(--interactive-accent-hover)}.perplexity-modal__textarea::placeholder{color:var(--text-faint)}.perplexity-modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 28px 24px;margin-top:12px;border-top:1px solid var(--background-modifier-border);background-color:var(--background-secondary);border-bottom-left-radius:var(--modal-radius, 8px);border-bottom-right-radius:var(--modal-radius, 8px)}.perplexity-modal__button{padding:8px 18px;font-size:14px;font-weight:500;border:1px solid var(--background-modifier-border);border-radius:6px;background-color:var(--background-primary);color:var(--text-normal);cursor:pointer;transition:background-color .15s ease,border-color .15s ease,transform .05s ease}.perplexity-modal__button:hover{background-color:var(--background-modifier-hover)}.perplexity-modal__button:active{transform:translateY(1px)}.perplexity-modal__button.mod-cta{background-color:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent)}.perplexity-modal__button.mod-cta:hover{background-color:var(--interactive-accent-hover);border-color:var(--interactive-accent-hover)}.perplexity-modal__button:disabled{opacity:.5;cursor:not-allowed}@media(max-width:600px){.perplexity-modal{width:95vw;max-width:none}.perplexity-modal__header,.perplexity-modal__section,.perplexity-modal__footer{padding-left:18px;padding-right:18px}.perplexity-modal__footer{flex-direction:column-reverse}.perplexity-modal__button{width:100%}}.article-generator-modal{width:90vw;max-width:640px}.article-generator-modal .modal-content{padding:0}.article-generator-modal__header{padding:24px 28px 16px;border-bottom:1px solid var(--background-modifier-border)}.article-generator-modal__title{margin:0 0 6px;font-size:1.5em;font-weight:600;color:var(--text-normal);letter-spacing:-.01em}.article-generator-modal__subtitle{margin:0;font-size:13px;line-height:1.45;color:var(--text-muted)}.article-generator-modal__section{padding:18px 28px 4px}.article-generator-modal__section+.article-generator-modal__section{border-top:1px solid var(--background-modifier-border-hover);margin-top:4px}.article-generator-modal__section-title{margin:0 0 12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted)}.article-generator-modal__section .setting-item{padding:10px 0;border-top:none}.article-generator-modal__section .setting-item+.setting-item{border-top:1px solid var(--background-modifier-border-hover)}.article-generator-modal__label{display:block;margin-bottom:8px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted)}.article-generator-modal__input{width:100%;padding:10px 14px;font-family:var(--font-text);font-size:15px;line-height:1.4;color:var(--text-normal);background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:8px;transition:border-color .15s ease,box-shadow .15s ease;box-sizing:border-box}.article-generator-modal__input:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 3px var(--interactive-accent-hover)}.article-generator-modal__input::placeholder{color:var(--text-faint)}.article-generator-modal__hint{margin:8px 0 0;font-size:12px;line-height:1.45;color:var(--text-muted);font-style:italic}.article-generator-modal__warning{margin-top:6px;font-size:12px;line-height:1.4;color:var(--text-error);font-weight:500;display:none}.article-generator-modal__warning.is-active{display:block}.article-generator-modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 28px 24px;margin-top:12px;border-top:1px solid var(--background-modifier-border);background-color:var(--background-secondary);border-bottom-left-radius:var(--modal-radius, 8px);border-bottom-right-radius:var(--modal-radius, 8px)}.article-generator-modal__button{padding:8px 18px;font-size:14px;font-weight:500;border:1px solid var(--background-modifier-border);border-radius:6px;background-color:var(--background-primary);color:var(--text-normal);cursor:pointer;transition:background-color .15s ease,border-color .15s ease,transform .05s ease}.article-generator-modal__button:hover{background-color:var(--background-modifier-hover)}.article-generator-modal__button:active{transform:translateY(1px)}.article-generator-modal__button.mod-cta{background-color:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent)}.article-generator-modal__button.mod-cta:hover{background-color:var(--interactive-accent-hover);border-color:var(--interactive-accent-hover)}.article-generator-modal__button:disabled{opacity:.5;cursor:not-allowed}@media(max-width:600px){.article-generator-modal{width:95vw;max-width:none}.article-generator-modal__header,.article-generator-modal__section,.article-generator-modal__footer{padding-left:18px;padding-right:18px}.article-generator-modal__footer{flex-direction:column-reverse}.article-generator-modal__button{width:100%}}.perplexica-modal{width:90vw;max-width:640px}.perplexica-modal .modal-content{padding:0}.perplexica-modal__header{padding:24px 28px 16px;border-bottom:1px solid var(--background-modifier-border)}.perplexica-modal__title{margin:0 0 6px;font-size:1.5em;font-weight:600;color:var(--text-normal);letter-spacing:-.01em}.perplexica-modal__subtitle{margin:0;font-size:13px;line-height:1.45;color:var(--text-muted)}.perplexica-modal__section{padding:18px 28px 4px}.perplexica-modal__section+.perplexica-modal__section{border-top:1px solid var(--background-modifier-border-hover);margin-top:4px}.perplexica-modal__section-title{margin:0 0 12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted)}.perplexica-modal__section .setting-item{padding:10px 0;border-top:none}.perplexica-modal__section .setting-item+.setting-item{border-top:1px solid var(--background-modifier-border-hover)}.perplexica-modal__label{display:block;margin-bottom:8px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted)}.perplexica-modal__textarea{width:100%;min-height:120px;padding:12px 14px;font-family:var(--font-text);font-size:14px;line-height:1.5;color:var(--text-normal);background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:8px;resize:vertical;transition:border-color .15s ease,box-shadow .15s ease;box-sizing:border-box}.perplexica-modal__textarea:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 3px var(--interactive-accent-hover)}.perplexica-modal__textarea::placeholder{color:var(--text-faint)}.perplexica-modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 28px 24px;margin-top:12px;border-top:1px solid var(--background-modifier-border);background-color:var(--background-secondary);border-bottom-left-radius:var(--modal-radius, 8px);border-bottom-right-radius:var(--modal-radius, 8px)}.perplexica-modal__button{padding:8px 18px;font-size:14px;font-weight:500;border:1px solid var(--background-modifier-border);border-radius:6px;background-color:var(--background-primary);color:var(--text-normal);cursor:pointer;transition:background-color .15s ease,border-color .15s ease,transform .05s ease}.perplexica-modal__button:hover{background-color:var(--background-modifier-hover)}.perplexica-modal__button:active{transform:translateY(1px)}.perplexica-modal__button.mod-cta{background-color:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent)}.perplexica-modal__button.mod-cta:hover{background-color:var(--interactive-accent-hover);border-color:var(--interactive-accent-hover)}.perplexica-modal__button:disabled{opacity:.5;cursor:not-allowed}@media(max-width:600px){.perplexica-modal{width:95vw;max-width:none}.perplexica-modal__header,.perplexica-modal__section,.perplexica-modal__footer{padding-left:18px;padding-right:18px}.perplexica-modal__footer{flex-direction:column-reverse}.perplexica-modal__button{width:100%}}.lmstudio-modal{width:90vw;max-width:640px}.lmstudio-modal .modal-content{padding:0}.lmstudio-modal__header{padding:24px 28px 16px;border-bottom:1px solid var(--background-modifier-border)}.lmstudio-modal__title{margin:0 0 6px;font-size:1.5em;font-weight:600;color:var(--text-normal);letter-spacing:-.01em}.lmstudio-modal__subtitle{margin:0;font-size:13px;line-height:1.45;color:var(--text-muted)}.lmstudio-modal__section{padding:18px 28px 4px}.lmstudio-modal__section+.lmstudio-modal__section{border-top:1px solid var(--background-modifier-border-hover);margin-top:4px}.lmstudio-modal__section-title{margin:0 0 12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted)}.lmstudio-modal__section .setting-item{padding:10px 0;border-top:none}.lmstudio-modal__section .setting-item+.setting-item{border-top:1px solid var(--background-modifier-border-hover)}.lmstudio-modal__hint{margin:-4px 0 8px;font-size:12px;line-height:1.45;color:var(--text-muted)}.lmstudio-modal__label{display:block;margin-bottom:8px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted)}.lmstudio-modal__textarea{width:100%;min-height:120px;padding:12px 14px;font-family:var(--font-text);font-size:14px;line-height:1.5;color:var(--text-normal);background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:8px;resize:vertical;transition:border-color .15s ease,box-shadow .15s ease;box-sizing:border-box}.lmstudio-modal__textarea:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 3px var(--interactive-accent-hover)}.lmstudio-modal__textarea::placeholder{color:var(--text-faint)}.lmstudio-modal__textarea--system{min-height:72px;font-family:var(--font-monospace);font-size:13px}.lmstudio-modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 28px 24px;margin-top:12px;border-top:1px solid var(--background-modifier-border);background-color:var(--background-secondary);border-bottom-left-radius:var(--modal-radius, 8px);border-bottom-right-radius:var(--modal-radius, 8px)}.lmstudio-modal__button{padding:8px 18px;font-size:14px;font-weight:500;border:1px solid var(--background-modifier-border);border-radius:6px;background-color:var(--background-primary);color:var(--text-normal);cursor:pointer;transition:background-color .15s ease,border-color .15s ease,transform .05s ease}.lmstudio-modal__button:hover{background-color:var(--background-modifier-hover)}.lmstudio-modal__button:active{transform:translateY(1px)}.lmstudio-modal__button.mod-cta{background-color:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent)}.lmstudio-modal__button.mod-cta:hover{background-color:var(--interactive-accent-hover);border-color:var(--interactive-accent-hover)}.lmstudio-modal__button:disabled{opacity:.5;cursor:not-allowed}@media(max-width:600px){.lmstudio-modal{width:95vw;max-width:none}.lmstudio-modal__header,.lmstudio-modal__section,.lmstudio-modal__footer{padding-left:18px;padding-right:18px}.lmstudio-modal__footer{flex-direction:column-reverse}.lmstudio-modal__button{width:100%}}.url-update-modal .text-input{width:100%;margin:8px 0;padding:12px}.text-enhancement-modal{width:90vw;max-width:720px}.text-enhancement-modal .modal-content{padding:0}.text-enhancement-modal__header{padding:24px 28px 16px;border-bottom:1px solid var(--background-modifier-border)}.text-enhancement-modal__title{margin:0 0 6px;font-size:1.5em;font-weight:600;color:var(--text-normal);letter-spacing:-.01em}.text-enhancement-modal__subtitle{margin:0;font-size:13px;line-height:1.45;color:var(--text-muted)}.text-enhancement-modal__section{padding:18px 28px 4px}.text-enhancement-modal__section+.text-enhancement-modal__section{border-top:1px solid var(--background-modifier-border-hover);margin-top:4px}.text-enhancement-modal__hint{margin:-4px 0 8px;font-size:12px;line-height:1.45;color:var(--text-muted)}.text-enhancement-modal__label{display:block;margin-bottom:8px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted)}.text-enhancement-modal__textarea{width:100%;min-height:120px;padding:12px 14px;font-family:var(--font-text);font-size:14px;line-height:1.5;color:var(--text-normal);background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:8px;resize:vertical;transition:border-color .15s ease,box-shadow .15s ease;box-sizing:border-box}.text-enhancement-modal__textarea:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 3px var(--interactive-accent-hover)}.text-enhancement-modal__textarea::placeholder{color:var(--text-faint)}.text-enhancement-modal__textarea--readonly{background-color:var(--background-secondary);color:var(--text-muted);cursor:default}.text-enhancement-modal__textarea--readonly:focus{border-color:var(--background-modifier-border);box-shadow:none}.text-enhancement-modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 28px 24px;margin-top:12px;border-top:1px solid var(--background-modifier-border);background-color:var(--background-secondary);border-bottom-left-radius:var(--modal-radius, 8px);border-bottom-right-radius:var(--modal-radius, 8px)}.text-enhancement-modal__button{padding:8px 18px;font-size:14px;font-weight:500;border:1px solid var(--background-modifier-border);border-radius:6px;background-color:var(--background-primary);color:var(--text-normal);cursor:pointer;transition:background-color .15s ease,border-color .15s ease,transform .05s ease}.text-enhancement-modal__button:hover{background-color:var(--background-modifier-hover)}.text-enhancement-modal__button:active{transform:translateY(1px)}.text-enhancement-modal__button.mod-cta{background-color:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent)}.text-enhancement-modal__button.mod-cta:hover{background-color:var(--interactive-accent-hover);border-color:var(--interactive-accent-hover)}.text-enhancement-modal__button:disabled{opacity:.5;cursor:not-allowed}@media(max-width:600px){.text-enhancement-modal{width:95vw;max-width:none}.text-enhancement-modal__header,.text-enhancement-modal__section,.text-enhancement-modal__footer{padding-left:18px;padding-right:18px}.text-enhancement-modal__footer{flex-direction:column-reverse}.text-enhancement-modal__button{width:100%}}.text-enhancement-with-images-modal{width:90vw;max-width:720px}.text-enhancement-with-images-modal .modal-content{padding:0}.text-enhancement-with-images-modal__header{padding:24px 28px 16px;border-bottom:1px solid var(--background-modifier-border)}.text-enhancement-with-images-modal__title{margin:0 0 6px;font-size:1.5em;font-weight:600;color:var(--text-normal);letter-spacing:-.01em}.text-enhancement-with-images-modal__subtitle{margin:0;font-size:13px;line-height:1.45;color:var(--text-muted)}.text-enhancement-with-images-modal__section{padding:18px 28px 4px}.text-enhancement-with-images-modal__section+.text-enhancement-with-images-modal__section{border-top:1px solid var(--background-modifier-border-hover);margin-top:4px}.text-enhancement-with-images-modal__hint{margin:-4px 0 8px;font-size:12px;line-height:1.45;color:var(--text-muted)}.text-enhancement-with-images-modal__label{display:block;margin-bottom:8px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted)}.text-enhancement-with-images-modal__textarea{width:100%;min-height:120px;padding:12px 14px;font-family:var(--font-text);font-size:14px;line-height:1.5;color:var(--text-normal);background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:8px;resize:vertical;transition:border-color .15s ease,box-shadow .15s ease;box-sizing:border-box}.text-enhancement-with-images-modal__textarea:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 3px var(--interactive-accent-hover)}.text-enhancement-with-images-modal__textarea::placeholder{color:var(--text-faint)}.text-enhancement-with-images-modal__textarea--readonly{background-color:var(--background-secondary);color:var(--text-muted);cursor:default}.text-enhancement-with-images-modal__textarea--readonly:focus{border-color:var(--background-modifier-border);box-shadow:none}.text-enhancement-with-images-modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 28px 24px;margin-top:12px;border-top:1px solid var(--background-modifier-border);background-color:var(--background-secondary);border-bottom-left-radius:var(--modal-radius, 8px);border-bottom-right-radius:var(--modal-radius, 8px)}.text-enhancement-with-images-modal__button{padding:8px 18px;font-size:14px;font-weight:500;border:1px solid var(--background-modifier-border);border-radius:6px;background-color:var(--background-primary);color:var(--text-normal);cursor:pointer;transition:background-color .15s ease,border-color .15s ease,transform .05s ease}.text-enhancement-with-images-modal__button:hover{background-color:var(--background-modifier-hover)}.text-enhancement-with-images-modal__button:active{transform:translateY(1px)}.text-enhancement-with-images-modal__button.mod-cta{background-color:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent)}.text-enhancement-with-images-modal__button.mod-cta:hover{background-color:var(--interactive-accent-hover);border-color:var(--interactive-accent-hover)}.text-enhancement-with-images-modal__button:disabled{opacity:.5;cursor:not-allowed}@media(max-width:600px){.text-enhancement-with-images-modal{width:95vw;max-width:none}.text-enhancement-with-images-modal__header,.text-enhancement-with-images-modal__section,.text-enhancement-with-images-modal__footer{padding-left:18px;padding-right:18px}.text-enhancement-with-images-modal__footer{flex-direction:column-reverse}.text-enhancement-with-images-modal__button{width:100%}}.claude-modal{width:90vw;max-width:640px}.claude-modal .modal-content{padding:0}.claude-modal__header{padding:24px 28px 16px;border-bottom:1px solid var(--background-modifier-border)}.claude-modal__title{margin:0 0 6px;font-size:1.5em;font-weight:600;color:var(--text-normal);letter-spacing:-.01em}.claude-modal__subtitle{margin:0;font-size:13px;line-height:1.45;color:var(--text-muted)}.claude-modal__section{padding:18px 28px 4px}.claude-modal__section+.claude-modal__section{border-top:1px solid var(--background-modifier-border-hover);margin-top:4px}.claude-modal__section-title{margin:0 0 12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted)}.claude-modal__section .setting-item{padding:10px 0;border-top:none}.claude-modal__section .setting-item+.setting-item{border-top:1px solid var(--background-modifier-border-hover)}.claude-modal__label{display:block;margin-bottom:8px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted)}.claude-modal__textarea{width:100%;min-height:120px;padding:12px 14px;font-family:var(--font-text);font-size:14px;line-height:1.5;color:var(--text-normal);background-color:var(--background-primary);border:1px solid var(--background-modifier-border);border-radius:8px;resize:vertical;transition:border-color .15s ease,box-shadow .15s ease;box-sizing:border-box}.claude-modal__textarea:focus{outline:none;border-color:var(--interactive-accent);box-shadow:0 0 0 3px var(--interactive-accent-hover)}.claude-modal__textarea::placeholder{color:var(--text-faint)}.claude-modal__footer{display:flex;justify-content:flex-end;gap:8px;padding:16px 28px 24px;margin-top:12px;border-top:1px solid var(--background-modifier-border);background-color:var(--background-secondary);border-bottom-left-radius:var(--modal-radius, 8px);border-bottom-right-radius:var(--modal-radius, 8px)}.claude-modal__button{padding:8px 18px;font-size:14px;font-weight:500;border:1px solid var(--background-modifier-border);border-radius:6px;background-color:var(--background-primary);color:var(--text-normal);cursor:pointer;transition:background-color .15s ease,border-color .15s ease,transform .05s ease}.claude-modal__button:hover{background-color:var(--background-modifier-hover)}.claude-modal__button:active{transform:translateY(1px)}.claude-modal__button.mod-cta{background-color:var(--interactive-accent);color:var(--text-on-accent);border-color:var(--interactive-accent)}.claude-modal__button.mod-cta:hover{background-color:var(--interactive-accent-hover);border-color:var(--interactive-accent-hover)}.claude-modal__button:disabled{opacity:.5;cursor:not-allowed}@media(max-width:600px){.claude-modal{width:95vw;max-width:none}.claude-modal__header,.claude-modal__section,.claude-modal__footer{padding-left:18px;padding-right:18px}.claude-modal__footer{flex-direction:column-reverse}.claude-modal__button{width:100%}}.callout[data-callout=brain]{background-color:#9333ea1a;border-left:4px solid #9333ea}.callout[data-callout=brain] .callout-title{color:#9333ea;font-weight:600}.callout[data-callout=brain] .callout-content{color:var(--text-normal);font-style:normal}.callout[data-callout=brain] .callout-icon{color:#9333ea}