styles.go

  1package styles
  2
  3import (
  4	"image/color"
  5
  6	"charm.land/bubbles/v2/filepicker"
  7	"charm.land/bubbles/v2/help"
  8	"charm.land/bubbles/v2/textarea"
  9	"charm.land/bubbles/v2/textinput"
 10	tea "charm.land/bubbletea/v2"
 11	"charm.land/lipgloss/v2"
 12	"github.com/charmbracelet/crush/internal/tui/exp/diffview"
 13	"github.com/charmbracelet/glamour/v2/ansi"
 14	"github.com/charmbracelet/x/exp/charmtone"
 15)
 16
 17const (
 18	CheckIcon    string = "✓"
 19	ErrorIcon    string = "×"
 20	WarningIcon  string = "⚠"
 21	InfoIcon     string = "ⓘ"
 22	HintIcon     string = "∵"
 23	SpinnerIcon  string = "..."
 24	LoadingIcon  string = "⟳"
 25	DocumentIcon string = "🖼"
 26	ModelIcon    string = "◇"
 27
 28	ToolPending string = "●"
 29	ToolSuccess string = "✓"
 30	ToolError   string = "×"
 31
 32	BorderThin  string = "│"
 33	BorderThick string = "▌"
 34
 35	SectionSeparator string = "─"
 36)
 37
 38const (
 39	defaultMargin     = 2
 40	defaultListIndent = 2
 41)
 42
 43type Styles struct {
 44	WindowTooSmall lipgloss.Style
 45
 46	// Reusable text styles
 47	Base   lipgloss.Style
 48	Muted  lipgloss.Style
 49	Subtle lipgloss.Style
 50
 51	// Tags
 52	TagBase  lipgloss.Style
 53	TagError lipgloss.Style
 54	TagInfo  lipgloss.Style
 55
 56	// Headers
 57	HeaderTool       lipgloss.Style
 58	HeaderToolNested lipgloss.Style
 59
 60	// Panels
 61	PanelMuted lipgloss.Style
 62	PanelBase  lipgloss.Style
 63
 64	// Line numbers for code blocks
 65	LineNumber lipgloss.Style
 66
 67	// Message borders
 68	FocusedMessageBorder lipgloss.Border
 69
 70	// Tool calls
 71	ToolCallPending   lipgloss.Style
 72	ToolCallError     lipgloss.Style
 73	ToolCallSuccess   lipgloss.Style
 74	ToolCallCancelled lipgloss.Style
 75	EarlyStateMessage lipgloss.Style
 76
 77	// Text selection
 78	TextSelection lipgloss.Style
 79
 80	// LSP and MCP status indicators
 81	ItemOfflineIcon lipgloss.Style
 82	ItemBusyIcon    lipgloss.Style
 83	ItemErrorIcon   lipgloss.Style
 84	ItemOnlineIcon  lipgloss.Style
 85
 86	// Markdown & Chroma
 87	Markdown ansi.StyleConfig
 88
 89	// Inputs
 90	TextInput textinput.Styles
 91	TextArea  textarea.Styles
 92
 93	// Help
 94	Help help.Styles
 95
 96	// Diff
 97	Diff diffview.Style
 98
 99	// FilePicker
100	FilePicker filepicker.Styles
101
102	// Buttons
103	ButtonFocus lipgloss.Style
104	ButtonBlur  lipgloss.Style
105
106	// Borders
107	BorderFocus lipgloss.Style
108	BorderBlur  lipgloss.Style
109
110	// Editor
111	EditorPromptNormalFocused   lipgloss.Style
112	EditorPromptNormalBlurred   lipgloss.Style
113	EditorPromptYoloIconFocused lipgloss.Style
114	EditorPromptYoloIconBlurred lipgloss.Style
115	EditorPromptYoloDotsFocused lipgloss.Style
116	EditorPromptYoloDotsBlurred lipgloss.Style
117
118	// Background
119	Background color.Color
120	// Logo
121	LogoFieldColor   color.Color
122	LogoTitleColorA  color.Color
123	LogoTitleColorB  color.Color
124	LogoCharmColor   color.Color
125	LogoVersionColor color.Color
126
127	// Section Title
128	Section struct {
129		Title lipgloss.Style
130		Line  lipgloss.Style
131	}
132
133	// Initialize
134	Initialize struct {
135		Header  lipgloss.Style
136		Content lipgloss.Style
137		Accent  lipgloss.Style
138	}
139
140	// LSP
141	LSP struct {
142		ErrorDiagnostic   lipgloss.Style
143		WarningDiagnostic lipgloss.Style
144		HintDiagnostic    lipgloss.Style
145		InfoDiagnostic    lipgloss.Style
146	}
147
148	// Files
149	Files struct {
150		Path      lipgloss.Style
151		Additions lipgloss.Style
152		Deletions lipgloss.Style
153	}
154
155	// Chat
156	Chat struct {
157		UserMessageBlurred      lipgloss.Style
158		UserMessageFocused      lipgloss.Style
159		AssistantMessageBlurred lipgloss.Style
160		AssistantMessageFocused lipgloss.Style
161		NoContentMessage        lipgloss.Style
162		ThinkingMessage         lipgloss.Style
163
164		ErrorTag     lipgloss.Style
165		ErrorTitle   lipgloss.Style
166		ErrorDetails lipgloss.Style
167	}
168}
169
170func DefaultStyles() Styles {
171	var (
172		primary   = charmtone.Charple
173		secondary = charmtone.Dolly
174		tertiary  = charmtone.Bok
175		// accent    = charmtone.Zest
176
177		// Backgrounds
178		bgBase        = charmtone.Pepper
179		bgBaseLighter = charmtone.BBQ
180		bgSubtle      = charmtone.Charcoal
181		bgOverlay     = charmtone.Iron
182
183		// Foregrounds
184		fgBase      = charmtone.Ash
185		fgMuted     = charmtone.Squid
186		fgHalfMuted = charmtone.Smoke
187		fgSubtle    = charmtone.Oyster
188		// fgSelected  = charmtone.Salt
189
190		// Borders
191		border      = charmtone.Charcoal
192		borderFocus = charmtone.Charple
193
194		// Status
195		warning = charmtone.Zest
196		info    = charmtone.Malibu
197
198		// Colors
199		white = charmtone.Butter
200
201		blueLight = charmtone.Sardine
202		blue      = charmtone.Malibu
203
204		// yellow = charmtone.Mustard
205		// citron = charmtone.Citron
206
207		green     = charmtone.Julep
208		greenDark = charmtone.Guac
209		// greenLight = charmtone.Bok
210
211		red     = charmtone.Coral
212		redDark = charmtone.Sriracha
213		// redLight = charmtone.Salmon
214		// cherry   = charmtone.Cherry
215	)
216
217	normalBorder := lipgloss.NormalBorder()
218
219	base := lipgloss.NewStyle().Foreground(fgBase)
220
221	s := Styles{}
222
223	s.Background = bgBase
224
225	s.TextInput = textinput.Styles{
226		Focused: textinput.StyleState{
227			Text:        base,
228			Placeholder: base.Foreground(fgSubtle),
229			Prompt:      base.Foreground(tertiary),
230			Suggestion:  base.Foreground(fgSubtle),
231		},
232		Blurred: textinput.StyleState{
233			Text:        base.Foreground(fgMuted),
234			Placeholder: base.Foreground(fgSubtle),
235			Prompt:      base.Foreground(fgMuted),
236			Suggestion:  base.Foreground(fgSubtle),
237		},
238		Cursor: textinput.CursorStyle{
239			Color: secondary,
240			Shape: tea.CursorBar,
241			Blink: true,
242		},
243	}
244
245	s.TextArea = textarea.Styles{
246		Focused: textarea.StyleState{
247			Base:             base,
248			Text:             base,
249			LineNumber:       base.Foreground(fgSubtle),
250			CursorLine:       base,
251			CursorLineNumber: base.Foreground(fgSubtle),
252			Placeholder:      base.Foreground(fgSubtle),
253			Prompt:           base.Foreground(tertiary),
254		},
255		Blurred: textarea.StyleState{
256			Base:             base,
257			Text:             base.Foreground(fgMuted),
258			LineNumber:       base.Foreground(fgMuted),
259			CursorLine:       base,
260			CursorLineNumber: base.Foreground(fgMuted),
261			Placeholder:      base.Foreground(fgSubtle),
262			Prompt:           base.Foreground(fgMuted),
263		},
264		Cursor: textarea.CursorStyle{
265			Color: secondary,
266			Shape: tea.CursorBar,
267			Blink: true,
268		},
269	}
270
271	s.Markdown = ansi.StyleConfig{
272		Document: ansi.StyleBlock{
273			StylePrimitive: ansi.StylePrimitive{
274				// BlockPrefix: "\n",
275				// BlockSuffix: "\n",
276				Color: stringPtr(charmtone.Smoke.Hex()),
277			},
278			// Margin: uintPtr(defaultMargin),
279		},
280		BlockQuote: ansi.StyleBlock{
281			StylePrimitive: ansi.StylePrimitive{},
282			Indent:         uintPtr(1),
283			IndentToken:    stringPtr("│ "),
284		},
285		List: ansi.StyleList{
286			LevelIndent: defaultListIndent,
287		},
288		Heading: ansi.StyleBlock{
289			StylePrimitive: ansi.StylePrimitive{
290				BlockSuffix: "\n",
291				Color:       stringPtr(charmtone.Malibu.Hex()),
292				Bold:        boolPtr(true),
293			},
294		},
295		H1: ansi.StyleBlock{
296			StylePrimitive: ansi.StylePrimitive{
297				Prefix:          " ",
298				Suffix:          " ",
299				Color:           stringPtr(charmtone.Zest.Hex()),
300				BackgroundColor: stringPtr(charmtone.Charple.Hex()),
301				Bold:            boolPtr(true),
302			},
303		},
304		H2: ansi.StyleBlock{
305			StylePrimitive: ansi.StylePrimitive{
306				Prefix: "## ",
307			},
308		},
309		H3: ansi.StyleBlock{
310			StylePrimitive: ansi.StylePrimitive{
311				Prefix: "### ",
312			},
313		},
314		H4: ansi.StyleBlock{
315			StylePrimitive: ansi.StylePrimitive{
316				Prefix: "#### ",
317			},
318		},
319		H5: ansi.StyleBlock{
320			StylePrimitive: ansi.StylePrimitive{
321				Prefix: "##### ",
322			},
323		},
324		H6: ansi.StyleBlock{
325			StylePrimitive: ansi.StylePrimitive{
326				Prefix: "###### ",
327				Color:  stringPtr(charmtone.Guac.Hex()),
328				Bold:   boolPtr(false),
329			},
330		},
331		Strikethrough: ansi.StylePrimitive{
332			CrossedOut: boolPtr(true),
333		},
334		Emph: ansi.StylePrimitive{
335			Italic: boolPtr(true),
336		},
337		Strong: ansi.StylePrimitive{
338			Bold: boolPtr(true),
339		},
340		HorizontalRule: ansi.StylePrimitive{
341			Color:  stringPtr(charmtone.Charcoal.Hex()),
342			Format: "\n--------\n",
343		},
344		Item: ansi.StylePrimitive{
345			BlockPrefix: "• ",
346		},
347		Enumeration: ansi.StylePrimitive{
348			BlockPrefix: ". ",
349		},
350		Task: ansi.StyleTask{
351			StylePrimitive: ansi.StylePrimitive{},
352			Ticked:         "[✓] ",
353			Unticked:       "[ ] ",
354		},
355		Link: ansi.StylePrimitive{
356			Color:     stringPtr(charmtone.Zinc.Hex()),
357			Underline: boolPtr(true),
358		},
359		LinkText: ansi.StylePrimitive{
360			Color: stringPtr(charmtone.Guac.Hex()),
361			Bold:  boolPtr(true),
362		},
363		Image: ansi.StylePrimitive{
364			Color:     stringPtr(charmtone.Cheeky.Hex()),
365			Underline: boolPtr(true),
366		},
367		ImageText: ansi.StylePrimitive{
368			Color:  stringPtr(charmtone.Squid.Hex()),
369			Format: "Image: {{.text}} →",
370		},
371		Code: ansi.StyleBlock{
372			StylePrimitive: ansi.StylePrimitive{
373				Prefix:          " ",
374				Suffix:          " ",
375				Color:           stringPtr(charmtone.Coral.Hex()),
376				BackgroundColor: stringPtr(charmtone.Charcoal.Hex()),
377			},
378		},
379		CodeBlock: ansi.StyleCodeBlock{
380			StyleBlock: ansi.StyleBlock{
381				StylePrimitive: ansi.StylePrimitive{
382					Color: stringPtr(charmtone.Charcoal.Hex()),
383				},
384				Margin: uintPtr(defaultMargin),
385			},
386			Chroma: &ansi.Chroma{
387				Text: ansi.StylePrimitive{
388					Color: stringPtr(charmtone.Smoke.Hex()),
389				},
390				Error: ansi.StylePrimitive{
391					Color:           stringPtr(charmtone.Butter.Hex()),
392					BackgroundColor: stringPtr(charmtone.Sriracha.Hex()),
393				},
394				Comment: ansi.StylePrimitive{
395					Color: stringPtr(charmtone.Oyster.Hex()),
396				},
397				CommentPreproc: ansi.StylePrimitive{
398					Color: stringPtr(charmtone.Bengal.Hex()),
399				},
400				Keyword: ansi.StylePrimitive{
401					Color: stringPtr(charmtone.Malibu.Hex()),
402				},
403				KeywordReserved: ansi.StylePrimitive{
404					Color: stringPtr(charmtone.Pony.Hex()),
405				},
406				KeywordNamespace: ansi.StylePrimitive{
407					Color: stringPtr(charmtone.Pony.Hex()),
408				},
409				KeywordType: ansi.StylePrimitive{
410					Color: stringPtr(charmtone.Guppy.Hex()),
411				},
412				Operator: ansi.StylePrimitive{
413					Color: stringPtr(charmtone.Salmon.Hex()),
414				},
415				Punctuation: ansi.StylePrimitive{
416					Color: stringPtr(charmtone.Zest.Hex()),
417				},
418				Name: ansi.StylePrimitive{
419					Color: stringPtr(charmtone.Smoke.Hex()),
420				},
421				NameBuiltin: ansi.StylePrimitive{
422					Color: stringPtr(charmtone.Cheeky.Hex()),
423				},
424				NameTag: ansi.StylePrimitive{
425					Color: stringPtr(charmtone.Mauve.Hex()),
426				},
427				NameAttribute: ansi.StylePrimitive{
428					Color: stringPtr(charmtone.Hazy.Hex()),
429				},
430				NameClass: ansi.StylePrimitive{
431					Color:     stringPtr(charmtone.Salt.Hex()),
432					Underline: boolPtr(true),
433					Bold:      boolPtr(true),
434				},
435				NameDecorator: ansi.StylePrimitive{
436					Color: stringPtr(charmtone.Citron.Hex()),
437				},
438				NameFunction: ansi.StylePrimitive{
439					Color: stringPtr(charmtone.Guac.Hex()),
440				},
441				LiteralNumber: ansi.StylePrimitive{
442					Color: stringPtr(charmtone.Julep.Hex()),
443				},
444				LiteralString: ansi.StylePrimitive{
445					Color: stringPtr(charmtone.Cumin.Hex()),
446				},
447				LiteralStringEscape: ansi.StylePrimitive{
448					Color: stringPtr(charmtone.Bok.Hex()),
449				},
450				GenericDeleted: ansi.StylePrimitive{
451					Color: stringPtr(charmtone.Coral.Hex()),
452				},
453				GenericEmph: ansi.StylePrimitive{
454					Italic: boolPtr(true),
455				},
456				GenericInserted: ansi.StylePrimitive{
457					Color: stringPtr(charmtone.Guac.Hex()),
458				},
459				GenericStrong: ansi.StylePrimitive{
460					Bold: boolPtr(true),
461				},
462				GenericSubheading: ansi.StylePrimitive{
463					Color: stringPtr(charmtone.Squid.Hex()),
464				},
465				Background: ansi.StylePrimitive{
466					BackgroundColor: stringPtr(charmtone.Charcoal.Hex()),
467				},
468			},
469		},
470		Table: ansi.StyleTable{
471			StyleBlock: ansi.StyleBlock{
472				StylePrimitive: ansi.StylePrimitive{},
473			},
474		},
475		DefinitionDescription: ansi.StylePrimitive{
476			BlockPrefix: "\n ",
477		},
478	}
479
480	s.Help = help.Styles{
481		ShortKey:       base.Foreground(fgMuted),
482		ShortDesc:      base.Foreground(fgSubtle),
483		ShortSeparator: base.Foreground(border),
484		Ellipsis:       base.Foreground(border),
485		FullKey:        base.Foreground(fgMuted),
486		FullDesc:       base.Foreground(fgSubtle),
487		FullSeparator:  base.Foreground(border),
488	}
489
490	s.Diff = diffview.Style{
491		DividerLine: diffview.LineStyle{
492			LineNumber: lipgloss.NewStyle().
493				Foreground(fgHalfMuted).
494				Background(bgBaseLighter),
495			Code: lipgloss.NewStyle().
496				Foreground(fgHalfMuted).
497				Background(bgBaseLighter),
498		},
499		MissingLine: diffview.LineStyle{
500			LineNumber: lipgloss.NewStyle().
501				Background(bgBaseLighter),
502			Code: lipgloss.NewStyle().
503				Background(bgBaseLighter),
504		},
505		EqualLine: diffview.LineStyle{
506			LineNumber: lipgloss.NewStyle().
507				Foreground(fgMuted).
508				Background(bgBase),
509			Code: lipgloss.NewStyle().
510				Foreground(fgMuted).
511				Background(bgBase),
512		},
513		InsertLine: diffview.LineStyle{
514			LineNumber: lipgloss.NewStyle().
515				Foreground(lipgloss.Color("#629657")).
516				Background(lipgloss.Color("#2b322a")),
517			Symbol: lipgloss.NewStyle().
518				Foreground(lipgloss.Color("#629657")).
519				Background(lipgloss.Color("#323931")),
520			Code: lipgloss.NewStyle().
521				Background(lipgloss.Color("#323931")),
522		},
523		DeleteLine: diffview.LineStyle{
524			LineNumber: lipgloss.NewStyle().
525				Foreground(lipgloss.Color("#a45c59")).
526				Background(lipgloss.Color("#312929")),
527			Symbol: lipgloss.NewStyle().
528				Foreground(lipgloss.Color("#a45c59")).
529				Background(lipgloss.Color("#383030")),
530			Code: lipgloss.NewStyle().
531				Background(lipgloss.Color("#383030")),
532		},
533	}
534
535	s.FilePicker = filepicker.Styles{
536		DisabledCursor:   base.Foreground(fgMuted),
537		Cursor:           base.Foreground(fgBase),
538		Symlink:          base.Foreground(fgSubtle),
539		Directory:        base.Foreground(primary),
540		File:             base.Foreground(fgBase),
541		DisabledFile:     base.Foreground(fgMuted),
542		DisabledSelected: base.Background(bgOverlay).Foreground(fgMuted),
543		Permission:       base.Foreground(fgMuted),
544		Selected:         base.Background(primary).Foreground(fgBase),
545		FileSize:         base.Foreground(fgMuted),
546		EmptyDirectory:   base.Foreground(fgMuted).PaddingLeft(2).SetString("Empty directory"),
547	}
548
549	// borders
550	s.FocusedMessageBorder = lipgloss.Border{Left: BorderThick}
551
552	// text presets
553	s.Base = lipgloss.NewStyle().Foreground(fgBase)
554	s.Muted = lipgloss.NewStyle().Foreground(fgMuted)
555	s.Subtle = lipgloss.NewStyle().Foreground(fgSubtle)
556
557	s.WindowTooSmall = s.Muted
558
559	// tag presets
560	s.TagBase = lipgloss.NewStyle().Padding(0, 1).Foreground(white)
561	s.TagError = s.TagBase.Background(redDark)
562	s.TagInfo = s.TagBase.Background(blueLight)
563
564	// headers
565	s.HeaderTool = lipgloss.NewStyle().Foreground(blue)
566	s.HeaderToolNested = lipgloss.NewStyle().Foreground(fgHalfMuted)
567
568	// panels
569	s.PanelMuted = s.Muted.Background(bgBaseLighter)
570	s.PanelBase = lipgloss.NewStyle().Background(bgBase)
571
572	// code line number
573	s.LineNumber = lipgloss.NewStyle().Foreground(fgMuted).Background(bgBase).PaddingRight(1).PaddingLeft(1)
574
575	// Tool calls
576	s.ToolCallPending = lipgloss.NewStyle().Foreground(greenDark).SetString(ToolPending)
577	s.ToolCallError = lipgloss.NewStyle().Foreground(redDark).SetString(ToolError)
578	s.ToolCallSuccess = lipgloss.NewStyle().Foreground(green).SetString(ToolSuccess)
579	// Cancelled uses muted tone but same glyph as pending
580	s.ToolCallCancelled = s.Muted.SetString(ToolPending)
581	s.EarlyStateMessage = s.Subtle.PaddingLeft(2)
582
583	// Buttons
584	s.ButtonFocus = lipgloss.NewStyle().Foreground(white).Background(secondary)
585	s.ButtonBlur = s.Base.Background(bgSubtle)
586
587	// Borders
588	s.BorderFocus = lipgloss.NewStyle().BorderForeground(borderFocus).Border(lipgloss.RoundedBorder()).Padding(1, 2)
589
590	// Editor
591	s.EditorPromptNormalFocused = lipgloss.NewStyle().Foreground(greenDark).SetString("::: ")
592	s.EditorPromptNormalBlurred = s.EditorPromptNormalFocused.Foreground(fgMuted)
593	s.EditorPromptYoloIconFocused = lipgloss.NewStyle().Foreground(charmtone.Oyster).Background(charmtone.Citron).Bold(true).SetString(" ! ")
594	s.EditorPromptYoloIconBlurred = s.EditorPromptYoloIconFocused.Foreground(charmtone.Pepper).Background(charmtone.Squid)
595	s.EditorPromptYoloDotsFocused = lipgloss.NewStyle().Foreground(charmtone.Zest).SetString(":::")
596	s.EditorPromptYoloDotsBlurred = s.EditorPromptYoloDotsFocused.Foreground(charmtone.Squid)
597
598	// Logo colors
599	s.LogoFieldColor = primary
600	s.LogoTitleColorA = secondary
601	s.LogoTitleColorB = primary
602	s.LogoCharmColor = secondary
603	s.LogoVersionColor = primary
604
605	// Section
606	s.Section.Title = s.Subtle
607	s.Section.Line = s.Base.Foreground(charmtone.Charcoal)
608
609	// Initialize
610	s.Initialize.Header = s.Base
611	s.Initialize.Content = s.Muted
612	s.Initialize.Accent = s.Base.Foreground(greenDark)
613
614	// LSP and MCP status.
615	s.ItemOfflineIcon = lipgloss.NewStyle().Foreground(charmtone.Squid).SetString("●")
616	s.ItemBusyIcon = s.ItemOfflineIcon.Foreground(charmtone.Citron)
617	s.ItemErrorIcon = s.ItemOfflineIcon.Foreground(charmtone.Coral)
618	s.ItemOnlineIcon = s.ItemOfflineIcon.Foreground(charmtone.Guac)
619
620	// LSP
621	s.LSP.ErrorDiagnostic = s.Base.Foreground(redDark)
622	s.LSP.WarningDiagnostic = s.Base.Foreground(warning)
623	s.LSP.HintDiagnostic = s.Base.Foreground(fgHalfMuted)
624	s.LSP.InfoDiagnostic = s.Base.Foreground(info)
625
626	// Files
627	s.Files.Path = s.Muted
628	s.Files.Additions = s.Base.Foreground(greenDark)
629	s.Files.Deletions = s.Base.Foreground(redDark)
630
631	// Chat
632	messageFocussedBorder := lipgloss.Border{
633		Left: "▌",
634	}
635
636	s.Chat.NoContentMessage = lipgloss.NewStyle().Foreground(fgBase)
637	s.Chat.UserMessageBlurred = s.Chat.NoContentMessage.PaddingLeft(1).BorderLeft(true).
638		BorderForeground(primary).BorderStyle(normalBorder)
639	s.Chat.UserMessageFocused = s.Chat.NoContentMessage.PaddingLeft(1).BorderLeft(true).
640		BorderForeground(primary).BorderStyle(messageFocussedBorder)
641	s.Chat.AssistantMessageBlurred = s.Chat.NoContentMessage.PaddingLeft(2)
642	s.Chat.AssistantMessageFocused = s.Chat.NoContentMessage.PaddingLeft(1).BorderLeft(true).
643		BorderForeground(greenDark).BorderStyle(messageFocussedBorder)
644	s.Chat.ThinkingMessage = lipgloss.NewStyle().MaxHeight(10)
645	s.Chat.ErrorTag = lipgloss.NewStyle().Padding(0, 1).
646		Background(red).Foreground(white)
647	s.Chat.ErrorTitle = lipgloss.NewStyle().Foreground(fgHalfMuted)
648	s.Chat.ErrorDetails = lipgloss.NewStyle().Foreground(fgSubtle)
649
650	// Text selection.
651	s.TextSelection = lipgloss.NewStyle().Foreground(charmtone.Salt).Background(charmtone.Charple)
652
653	return s
654}
655
656// Helper functions for style pointers
657func boolPtr(b bool) *bool       { return &b }
658func stringPtr(s string) *string { return &s }
659func uintPtr(u uint) *uint       { return &u }