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/glamour/v2/ansi"
  12	"charm.land/lipgloss/v2"
  13	"github.com/alecthomas/chroma/v2"
  14	"github.com/charmbracelet/crush/internal/tui/exp/diffview"
  15	"github.com/charmbracelet/x/exp/charmtone"
  16)
  17
  18const (
  19	CheckIcon    string = "✓"
  20	ErrorIcon    string = "×"
  21	WarningIcon  string = "⚠"
  22	InfoIcon     string = "ⓘ"
  23	HintIcon     string = "∵"
  24	SpinnerIcon  string = "..."
  25	LoadingIcon  string = "⟳"
  26	DocumentIcon string = "🖼"
  27	ModelIcon    string = "◇"
  28
  29	ArrowRightIcon string = "→"
  30
  31	ToolPending string = "●"
  32	ToolSuccess string = "✓"
  33	ToolError   string = "×"
  34
  35	RadioOn  string = "◉"
  36	RadioOff string = "○"
  37
  38	BorderThin  string = "│"
  39	BorderThick string = "▌"
  40
  41	SectionSeparator string = "─"
  42
  43	TodoCompletedIcon  string = "✓"
  44	TodoPendingIcon    string = "•"
  45	TodoInProgressIcon string = "→"
  46)
  47
  48const (
  49	defaultMargin     = 2
  50	defaultListIndent = 2
  51)
  52
  53type Styles struct {
  54	WindowTooSmall lipgloss.Style
  55
  56	// Reusable text styles
  57	Base      lipgloss.Style
  58	Muted     lipgloss.Style
  59	HalfMuted lipgloss.Style
  60	Subtle    lipgloss.Style
  61
  62	// Tags
  63	TagBase  lipgloss.Style
  64	TagError lipgloss.Style
  65	TagInfo  lipgloss.Style
  66
  67	// Headers
  68	HeaderTool       lipgloss.Style
  69	HeaderToolNested lipgloss.Style
  70
  71	// Panels
  72	PanelMuted lipgloss.Style
  73	PanelBase  lipgloss.Style
  74
  75	// Line numbers for code blocks
  76	LineNumber lipgloss.Style
  77
  78	// Message borders
  79	FocusedMessageBorder lipgloss.Border
  80
  81	// Tool calls
  82	ToolCallPending   lipgloss.Style
  83	ToolCallError     lipgloss.Style
  84	ToolCallSuccess   lipgloss.Style
  85	ToolCallCancelled lipgloss.Style
  86	EarlyStateMessage lipgloss.Style
  87
  88	// Text selection
  89	TextSelection lipgloss.Style
  90
  91	// LSP and MCP status indicators
  92	ItemOfflineIcon lipgloss.Style
  93	ItemBusyIcon    lipgloss.Style
  94	ItemErrorIcon   lipgloss.Style
  95	ItemOnlineIcon  lipgloss.Style
  96
  97	// Markdown & Chroma
  98	Markdown      ansi.StyleConfig
  99	PlainMarkdown ansi.StyleConfig
 100
 101	// Inputs
 102	TextInput textinput.Styles
 103	TextArea  textarea.Styles
 104
 105	// Help
 106	Help help.Styles
 107
 108	// Diff
 109	Diff diffview.Style
 110
 111	// FilePicker
 112	FilePicker filepicker.Styles
 113
 114	// Buttons
 115	ButtonFocus lipgloss.Style
 116	ButtonBlur  lipgloss.Style
 117
 118	// Borders
 119	BorderFocus lipgloss.Style
 120	BorderBlur  lipgloss.Style
 121
 122	// Editor
 123	EditorPromptNormalFocused   lipgloss.Style
 124	EditorPromptNormalBlurred   lipgloss.Style
 125	EditorPromptYoloIconFocused lipgloss.Style
 126	EditorPromptYoloIconBlurred lipgloss.Style
 127	EditorPromptYoloDotsFocused lipgloss.Style
 128	EditorPromptYoloDotsBlurred lipgloss.Style
 129
 130	// Radio
 131	RadioOn  lipgloss.Style
 132	RadioOff lipgloss.Style
 133
 134	// Background
 135	Background color.Color
 136
 137	// Logo
 138	LogoFieldColor   color.Color
 139	LogoTitleColorA  color.Color
 140	LogoTitleColorB  color.Color
 141	LogoCharmColor   color.Color
 142	LogoVersionColor color.Color
 143
 144	// Colors - semantic colors for tool rendering.
 145	Primary       color.Color
 146	Secondary     color.Color
 147	Tertiary      color.Color
 148	BgBase        color.Color
 149	BgBaseLighter color.Color
 150	BgSubtle      color.Color
 151	BgOverlay     color.Color
 152	FgBase        color.Color
 153	FgMuted       color.Color
 154	FgHalfMuted   color.Color
 155	FgSubtle      color.Color
 156	Border        color.Color
 157	BorderColor   color.Color // Border focus color
 158	Warning       color.Color
 159	Info          color.Color
 160	White         color.Color
 161	BlueLight     color.Color
 162	Blue          color.Color
 163	BlueDark      color.Color
 164	Green         color.Color
 165	GreenDark     color.Color
 166	Red           color.Color
 167	RedDark       color.Color
 168	Yellow        color.Color
 169
 170	// Section Title
 171	Section struct {
 172		Title lipgloss.Style
 173		Line  lipgloss.Style
 174	}
 175
 176	// Initialize
 177	Initialize struct {
 178		Header  lipgloss.Style
 179		Content lipgloss.Style
 180		Accent  lipgloss.Style
 181	}
 182
 183	// LSP
 184	LSP struct {
 185		ErrorDiagnostic   lipgloss.Style
 186		WarningDiagnostic lipgloss.Style
 187		HintDiagnostic    lipgloss.Style
 188		InfoDiagnostic    lipgloss.Style
 189	}
 190
 191	// Files
 192	Files struct {
 193		Path      lipgloss.Style
 194		Additions lipgloss.Style
 195		Deletions lipgloss.Style
 196	}
 197
 198	// Chat
 199	Chat struct {
 200		// Message item styles
 201		Message struct {
 202			UserBlurred      lipgloss.Style
 203			UserFocused      lipgloss.Style
 204			AssistantBlurred lipgloss.Style
 205			AssistantFocused lipgloss.Style
 206			NoContent        lipgloss.Style
 207			Thinking         lipgloss.Style
 208			ErrorTag         lipgloss.Style
 209			ErrorTitle       lipgloss.Style
 210			ErrorDetails     lipgloss.Style
 211			Attachment       lipgloss.Style
 212			ToolCallFocused  lipgloss.Style
 213			ToolCallBlurred  lipgloss.Style
 214			SectionHeader    lipgloss.Style
 215
 216			// Thinking section styles
 217			ThinkingBox            lipgloss.Style // Background for thinking content
 218			ThinkingTruncationHint lipgloss.Style // "… (N lines hidden)" hint
 219			ThinkingFooterTitle    lipgloss.Style // "Thought for" text
 220			ThinkingFooterDuration lipgloss.Style // Duration value
 221		}
 222	}
 223
 224	// Tool - styles for tool call rendering
 225	Tool struct {
 226		// Icon styles with tool status
 227		IconPending   lipgloss.Style // Pending operation icon
 228		IconSuccess   lipgloss.Style // Successful operation icon
 229		IconError     lipgloss.Style // Error operation icon
 230		IconCancelled lipgloss.Style // Cancelled operation icon
 231
 232		// Tool name styles
 233		NameNormal lipgloss.Style // Normal tool name
 234		NameNested lipgloss.Style // Nested tool name
 235
 236		// Parameter list styles
 237		ParamMain lipgloss.Style // Main parameter
 238		ParamKey  lipgloss.Style // Parameter keys
 239
 240		// Content rendering styles
 241		ContentLine           lipgloss.Style // Individual content line with background and width
 242		ContentTruncation     lipgloss.Style // Truncation message "… (N lines)"
 243		ContentCodeLine       lipgloss.Style // Code line with background and width
 244		ContentCodeTruncation lipgloss.Style // Code truncation message with bgBase
 245		ContentCodeBg         color.Color    // Background color for syntax highlighting
 246		Body                  lipgloss.Style // Body content padding (PaddingLeft(2))
 247
 248		// Deprecated - kept for backward compatibility
 249		ContentBg         lipgloss.Style // Content background
 250		ContentText       lipgloss.Style // Content text
 251		ContentLineNumber lipgloss.Style // Line numbers in code
 252
 253		// State message styles
 254		StateWaiting   lipgloss.Style // "Waiting for tool response..."
 255		StateCancelled lipgloss.Style // "Canceled."
 256
 257		// Error styles
 258		ErrorTag     lipgloss.Style // ERROR tag
 259		ErrorMessage lipgloss.Style // Error message text
 260
 261		// Diff styles
 262		DiffTruncation lipgloss.Style // Diff truncation message with padding
 263
 264		// Multi-edit note styles
 265		NoteTag     lipgloss.Style // NOTE tag (yellow background)
 266		NoteMessage lipgloss.Style // Note message text
 267
 268		// Job header styles (for bash jobs)
 269		JobIconPending lipgloss.Style // Pending job icon (green dark)
 270		JobIconError   lipgloss.Style // Error job icon (red dark)
 271		JobIconSuccess lipgloss.Style // Success job icon (green)
 272		JobToolName    lipgloss.Style // Job tool name "Bash" (blue)
 273		JobAction      lipgloss.Style // Action text (Start, Output, Kill)
 274		JobPID         lipgloss.Style // PID text
 275		JobDescription lipgloss.Style // Description text
 276
 277		// Agent task styles
 278		AgentTaskTag lipgloss.Style // Agent task tag (blue background, bold)
 279		AgentPrompt  lipgloss.Style // Agent prompt text
 280	}
 281
 282	// Dialog styles
 283	Dialog struct {
 284		Title lipgloss.Style
 285		// View is the main content area style.
 286		View lipgloss.Style
 287		// HelpView is the line that contains the help.
 288		HelpView lipgloss.Style
 289		Help     struct {
 290			Ellipsis       lipgloss.Style
 291			ShortKey       lipgloss.Style
 292			ShortDesc      lipgloss.Style
 293			ShortSeparator lipgloss.Style
 294			FullKey        lipgloss.Style
 295			FullDesc       lipgloss.Style
 296			FullSeparator  lipgloss.Style
 297		}
 298		NormalItem   lipgloss.Style
 299		SelectedItem lipgloss.Style
 300		InputPrompt  lipgloss.Style
 301
 302		List lipgloss.Style
 303
 304		Commands struct{}
 305	}
 306}
 307
 308// ChromaTheme converts the current markdown chroma styles to a chroma
 309// StyleEntries map.
 310func (s *Styles) ChromaTheme() chroma.StyleEntries {
 311	rules := s.Markdown.CodeBlock
 312
 313	return chroma.StyleEntries{
 314		chroma.Text:                chromaStyle(rules.Chroma.Text),
 315		chroma.Error:               chromaStyle(rules.Chroma.Error),
 316		chroma.Comment:             chromaStyle(rules.Chroma.Comment),
 317		chroma.CommentPreproc:      chromaStyle(rules.Chroma.CommentPreproc),
 318		chroma.Keyword:             chromaStyle(rules.Chroma.Keyword),
 319		chroma.KeywordReserved:     chromaStyle(rules.Chroma.KeywordReserved),
 320		chroma.KeywordNamespace:    chromaStyle(rules.Chroma.KeywordNamespace),
 321		chroma.KeywordType:         chromaStyle(rules.Chroma.KeywordType),
 322		chroma.Operator:            chromaStyle(rules.Chroma.Operator),
 323		chroma.Punctuation:         chromaStyle(rules.Chroma.Punctuation),
 324		chroma.Name:                chromaStyle(rules.Chroma.Name),
 325		chroma.NameBuiltin:         chromaStyle(rules.Chroma.NameBuiltin),
 326		chroma.NameTag:             chromaStyle(rules.Chroma.NameTag),
 327		chroma.NameAttribute:       chromaStyle(rules.Chroma.NameAttribute),
 328		chroma.NameClass:           chromaStyle(rules.Chroma.NameClass),
 329		chroma.NameConstant:        chromaStyle(rules.Chroma.NameConstant),
 330		chroma.NameDecorator:       chromaStyle(rules.Chroma.NameDecorator),
 331		chroma.NameException:       chromaStyle(rules.Chroma.NameException),
 332		chroma.NameFunction:        chromaStyle(rules.Chroma.NameFunction),
 333		chroma.NameOther:           chromaStyle(rules.Chroma.NameOther),
 334		chroma.Literal:             chromaStyle(rules.Chroma.Literal),
 335		chroma.LiteralNumber:       chromaStyle(rules.Chroma.LiteralNumber),
 336		chroma.LiteralDate:         chromaStyle(rules.Chroma.LiteralDate),
 337		chroma.LiteralString:       chromaStyle(rules.Chroma.LiteralString),
 338		chroma.LiteralStringEscape: chromaStyle(rules.Chroma.LiteralStringEscape),
 339		chroma.GenericDeleted:      chromaStyle(rules.Chroma.GenericDeleted),
 340		chroma.GenericEmph:         chromaStyle(rules.Chroma.GenericEmph),
 341		chroma.GenericInserted:     chromaStyle(rules.Chroma.GenericInserted),
 342		chroma.GenericStrong:       chromaStyle(rules.Chroma.GenericStrong),
 343		chroma.GenericSubheading:   chromaStyle(rules.Chroma.GenericSubheading),
 344		chroma.Background:          chromaStyle(rules.Chroma.Background),
 345	}
 346}
 347
 348// DialogHelpStyles returns the styles for dialog help.
 349func (s *Styles) DialogHelpStyles() help.Styles {
 350	return help.Styles(s.Dialog.Help)
 351}
 352
 353// DefaultStyles returns the default styles for the UI.
 354func DefaultStyles() Styles {
 355	var (
 356		primary   = charmtone.Charple
 357		secondary = charmtone.Dolly
 358		tertiary  = charmtone.Bok
 359		// accent    = charmtone.Zest
 360
 361		// Backgrounds
 362		bgBase        = charmtone.Pepper
 363		bgBaseLighter = charmtone.BBQ
 364		bgSubtle      = charmtone.Charcoal
 365		bgOverlay     = charmtone.Iron
 366
 367		// Foregrounds
 368		fgBase      = charmtone.Ash
 369		fgMuted     = charmtone.Squid
 370		fgHalfMuted = charmtone.Smoke
 371		fgSubtle    = charmtone.Oyster
 372		// fgSelected  = charmtone.Salt
 373
 374		// Borders
 375		border      = charmtone.Charcoal
 376		borderFocus = charmtone.Charple
 377
 378		// Status
 379		warning = charmtone.Zest
 380		info    = charmtone.Malibu
 381
 382		// Colors
 383		white = charmtone.Butter
 384
 385		blueLight = charmtone.Sardine
 386		blue      = charmtone.Malibu
 387		blueDark  = charmtone.Damson
 388
 389		// yellow = charmtone.Mustard
 390		yellow = charmtone.Mustard
 391		// citron = charmtone.Citron
 392
 393		green     = charmtone.Julep
 394		greenDark = charmtone.Guac
 395		// greenLight = charmtone.Bok
 396
 397		red     = charmtone.Coral
 398		redDark = charmtone.Sriracha
 399		// redLight = charmtone.Salmon
 400		// cherry   = charmtone.Cherry
 401	)
 402
 403	normalBorder := lipgloss.NormalBorder()
 404
 405	base := lipgloss.NewStyle().Foreground(fgBase)
 406
 407	s := Styles{}
 408
 409	s.Background = bgBase
 410
 411	// Populate color fields
 412	s.Primary = primary
 413	s.Secondary = secondary
 414	s.Tertiary = tertiary
 415	s.BgBase = bgBase
 416	s.BgBaseLighter = bgBaseLighter
 417	s.BgSubtle = bgSubtle
 418	s.BgOverlay = bgOverlay
 419	s.FgBase = fgBase
 420	s.FgMuted = fgMuted
 421	s.FgHalfMuted = fgHalfMuted
 422	s.FgSubtle = fgSubtle
 423	s.Border = border
 424	s.BorderColor = borderFocus
 425	s.Warning = warning
 426	s.Info = info
 427	s.White = white
 428	s.BlueLight = blueLight
 429	s.Blue = blue
 430	s.BlueDark = blueDark
 431	s.Green = green
 432	s.GreenDark = greenDark
 433	s.Red = red
 434	s.RedDark = redDark
 435	s.Yellow = yellow
 436
 437	s.TextInput = textinput.Styles{
 438		Focused: textinput.StyleState{
 439			Text:        base,
 440			Placeholder: base.Foreground(fgSubtle),
 441			Prompt:      base.Foreground(tertiary),
 442			Suggestion:  base.Foreground(fgSubtle),
 443		},
 444		Blurred: textinput.StyleState{
 445			Text:        base.Foreground(fgMuted),
 446			Placeholder: base.Foreground(fgSubtle),
 447			Prompt:      base.Foreground(fgMuted),
 448			Suggestion:  base.Foreground(fgSubtle),
 449		},
 450		Cursor: textinput.CursorStyle{
 451			Color: secondary,
 452			Shape: tea.CursorBlock,
 453			Blink: true,
 454		},
 455	}
 456
 457	s.TextArea = textarea.Styles{
 458		Focused: textarea.StyleState{
 459			Base:             base,
 460			Text:             base,
 461			LineNumber:       base.Foreground(fgSubtle),
 462			CursorLine:       base,
 463			CursorLineNumber: base.Foreground(fgSubtle),
 464			Placeholder:      base.Foreground(fgSubtle),
 465			Prompt:           base.Foreground(tertiary),
 466		},
 467		Blurred: textarea.StyleState{
 468			Base:             base,
 469			Text:             base.Foreground(fgMuted),
 470			LineNumber:       base.Foreground(fgMuted),
 471			CursorLine:       base,
 472			CursorLineNumber: base.Foreground(fgMuted),
 473			Placeholder:      base.Foreground(fgSubtle),
 474			Prompt:           base.Foreground(fgMuted),
 475		},
 476		Cursor: textarea.CursorStyle{
 477			Color: secondary,
 478			Shape: tea.CursorBlock,
 479			Blink: true,
 480		},
 481	}
 482
 483	s.Markdown = ansi.StyleConfig{
 484		Document: ansi.StyleBlock{
 485			StylePrimitive: ansi.StylePrimitive{
 486				// BlockPrefix: "\n",
 487				// BlockSuffix: "\n",
 488				Color: stringPtr(charmtone.Smoke.Hex()),
 489			},
 490			// Margin: uintPtr(defaultMargin),
 491		},
 492		BlockQuote: ansi.StyleBlock{
 493			StylePrimitive: ansi.StylePrimitive{},
 494			Indent:         uintPtr(1),
 495			IndentToken:    stringPtr("│ "),
 496		},
 497		List: ansi.StyleList{
 498			LevelIndent: defaultListIndent,
 499		},
 500		Heading: ansi.StyleBlock{
 501			StylePrimitive: ansi.StylePrimitive{
 502				BlockSuffix: "\n",
 503				Color:       stringPtr(charmtone.Malibu.Hex()),
 504				Bold:        boolPtr(true),
 505			},
 506		},
 507		H1: ansi.StyleBlock{
 508			StylePrimitive: ansi.StylePrimitive{
 509				Prefix:          " ",
 510				Suffix:          " ",
 511				Color:           stringPtr(charmtone.Zest.Hex()),
 512				BackgroundColor: stringPtr(charmtone.Charple.Hex()),
 513				Bold:            boolPtr(true),
 514			},
 515		},
 516		H2: ansi.StyleBlock{
 517			StylePrimitive: ansi.StylePrimitive{
 518				Prefix: "## ",
 519			},
 520		},
 521		H3: ansi.StyleBlock{
 522			StylePrimitive: ansi.StylePrimitive{
 523				Prefix: "### ",
 524			},
 525		},
 526		H4: ansi.StyleBlock{
 527			StylePrimitive: ansi.StylePrimitive{
 528				Prefix: "#### ",
 529			},
 530		},
 531		H5: ansi.StyleBlock{
 532			StylePrimitive: ansi.StylePrimitive{
 533				Prefix: "##### ",
 534			},
 535		},
 536		H6: ansi.StyleBlock{
 537			StylePrimitive: ansi.StylePrimitive{
 538				Prefix: "###### ",
 539				Color:  stringPtr(charmtone.Guac.Hex()),
 540				Bold:   boolPtr(false),
 541			},
 542		},
 543		Strikethrough: ansi.StylePrimitive{
 544			CrossedOut: boolPtr(true),
 545		},
 546		Emph: ansi.StylePrimitive{
 547			Italic: boolPtr(true),
 548		},
 549		Strong: ansi.StylePrimitive{
 550			Bold: boolPtr(true),
 551		},
 552		HorizontalRule: ansi.StylePrimitive{
 553			Color:  stringPtr(charmtone.Charcoal.Hex()),
 554			Format: "\n--------\n",
 555		},
 556		Item: ansi.StylePrimitive{
 557			BlockPrefix: "• ",
 558		},
 559		Enumeration: ansi.StylePrimitive{
 560			BlockPrefix: ". ",
 561		},
 562		Task: ansi.StyleTask{
 563			StylePrimitive: ansi.StylePrimitive{},
 564			Ticked:         "[✓] ",
 565			Unticked:       "[ ] ",
 566		},
 567		Link: ansi.StylePrimitive{
 568			Color:     stringPtr(charmtone.Zinc.Hex()),
 569			Underline: boolPtr(true),
 570		},
 571		LinkText: ansi.StylePrimitive{
 572			Color: stringPtr(charmtone.Guac.Hex()),
 573			Bold:  boolPtr(true),
 574		},
 575		Image: ansi.StylePrimitive{
 576			Color:     stringPtr(charmtone.Cheeky.Hex()),
 577			Underline: boolPtr(true),
 578		},
 579		ImageText: ansi.StylePrimitive{
 580			Color:  stringPtr(charmtone.Squid.Hex()),
 581			Format: "Image: {{.text}} →",
 582		},
 583		Code: ansi.StyleBlock{
 584			StylePrimitive: ansi.StylePrimitive{
 585				Prefix:          " ",
 586				Suffix:          " ",
 587				Color:           stringPtr(charmtone.Coral.Hex()),
 588				BackgroundColor: stringPtr(charmtone.Charcoal.Hex()),
 589			},
 590		},
 591		CodeBlock: ansi.StyleCodeBlock{
 592			StyleBlock: ansi.StyleBlock{
 593				StylePrimitive: ansi.StylePrimitive{
 594					Color: stringPtr(charmtone.Charcoal.Hex()),
 595				},
 596				Margin: uintPtr(defaultMargin),
 597			},
 598			Chroma: &ansi.Chroma{
 599				Text: ansi.StylePrimitive{
 600					Color: stringPtr(charmtone.Smoke.Hex()),
 601				},
 602				Error: ansi.StylePrimitive{
 603					Color:           stringPtr(charmtone.Butter.Hex()),
 604					BackgroundColor: stringPtr(charmtone.Sriracha.Hex()),
 605				},
 606				Comment: ansi.StylePrimitive{
 607					Color: stringPtr(charmtone.Oyster.Hex()),
 608				},
 609				CommentPreproc: ansi.StylePrimitive{
 610					Color: stringPtr(charmtone.Bengal.Hex()),
 611				},
 612				Keyword: ansi.StylePrimitive{
 613					Color: stringPtr(charmtone.Malibu.Hex()),
 614				},
 615				KeywordReserved: ansi.StylePrimitive{
 616					Color: stringPtr(charmtone.Pony.Hex()),
 617				},
 618				KeywordNamespace: ansi.StylePrimitive{
 619					Color: stringPtr(charmtone.Pony.Hex()),
 620				},
 621				KeywordType: ansi.StylePrimitive{
 622					Color: stringPtr(charmtone.Guppy.Hex()),
 623				},
 624				Operator: ansi.StylePrimitive{
 625					Color: stringPtr(charmtone.Salmon.Hex()),
 626				},
 627				Punctuation: ansi.StylePrimitive{
 628					Color: stringPtr(charmtone.Zest.Hex()),
 629				},
 630				Name: ansi.StylePrimitive{
 631					Color: stringPtr(charmtone.Smoke.Hex()),
 632				},
 633				NameBuiltin: ansi.StylePrimitive{
 634					Color: stringPtr(charmtone.Cheeky.Hex()),
 635				},
 636				NameTag: ansi.StylePrimitive{
 637					Color: stringPtr(charmtone.Mauve.Hex()),
 638				},
 639				NameAttribute: ansi.StylePrimitive{
 640					Color: stringPtr(charmtone.Hazy.Hex()),
 641				},
 642				NameClass: ansi.StylePrimitive{
 643					Color:     stringPtr(charmtone.Salt.Hex()),
 644					Underline: boolPtr(true),
 645					Bold:      boolPtr(true),
 646				},
 647				NameDecorator: ansi.StylePrimitive{
 648					Color: stringPtr(charmtone.Citron.Hex()),
 649				},
 650				NameFunction: ansi.StylePrimitive{
 651					Color: stringPtr(charmtone.Guac.Hex()),
 652				},
 653				LiteralNumber: ansi.StylePrimitive{
 654					Color: stringPtr(charmtone.Julep.Hex()),
 655				},
 656				LiteralString: ansi.StylePrimitive{
 657					Color: stringPtr(charmtone.Cumin.Hex()),
 658				},
 659				LiteralStringEscape: ansi.StylePrimitive{
 660					Color: stringPtr(charmtone.Bok.Hex()),
 661				},
 662				GenericDeleted: ansi.StylePrimitive{
 663					Color: stringPtr(charmtone.Coral.Hex()),
 664				},
 665				GenericEmph: ansi.StylePrimitive{
 666					Italic: boolPtr(true),
 667				},
 668				GenericInserted: ansi.StylePrimitive{
 669					Color: stringPtr(charmtone.Guac.Hex()),
 670				},
 671				GenericStrong: ansi.StylePrimitive{
 672					Bold: boolPtr(true),
 673				},
 674				GenericSubheading: ansi.StylePrimitive{
 675					Color: stringPtr(charmtone.Squid.Hex()),
 676				},
 677				Background: ansi.StylePrimitive{
 678					BackgroundColor: stringPtr(charmtone.Charcoal.Hex()),
 679				},
 680			},
 681		},
 682		Table: ansi.StyleTable{
 683			StyleBlock: ansi.StyleBlock{
 684				StylePrimitive: ansi.StylePrimitive{},
 685			},
 686		},
 687		DefinitionDescription: ansi.StylePrimitive{
 688			BlockPrefix: "\n ",
 689		},
 690	}
 691
 692	// PlainMarkdown style - muted colors on subtle background for thinking content.
 693	plainBg := stringPtr(bgBaseLighter.Hex())
 694	plainFg := stringPtr(fgMuted.Hex())
 695	s.PlainMarkdown = ansi.StyleConfig{
 696		Document: ansi.StyleBlock{
 697			StylePrimitive: ansi.StylePrimitive{
 698				Color:           plainFg,
 699				BackgroundColor: plainBg,
 700			},
 701		},
 702		BlockQuote: ansi.StyleBlock{
 703			StylePrimitive: ansi.StylePrimitive{
 704				Color:           plainFg,
 705				BackgroundColor: plainBg,
 706			},
 707			Indent:      uintPtr(1),
 708			IndentToken: stringPtr("│ "),
 709		},
 710		List: ansi.StyleList{
 711			LevelIndent: defaultListIndent,
 712		},
 713		Heading: ansi.StyleBlock{
 714			StylePrimitive: ansi.StylePrimitive{
 715				BlockSuffix:     "\n",
 716				Bold:            boolPtr(true),
 717				Color:           plainFg,
 718				BackgroundColor: plainBg,
 719			},
 720		},
 721		H1: ansi.StyleBlock{
 722			StylePrimitive: ansi.StylePrimitive{
 723				Prefix:          " ",
 724				Suffix:          " ",
 725				Bold:            boolPtr(true),
 726				Color:           plainFg,
 727				BackgroundColor: plainBg,
 728			},
 729		},
 730		H2: ansi.StyleBlock{
 731			StylePrimitive: ansi.StylePrimitive{
 732				Prefix:          "## ",
 733				Color:           plainFg,
 734				BackgroundColor: plainBg,
 735			},
 736		},
 737		H3: ansi.StyleBlock{
 738			StylePrimitive: ansi.StylePrimitive{
 739				Prefix:          "### ",
 740				Color:           plainFg,
 741				BackgroundColor: plainBg,
 742			},
 743		},
 744		H4: ansi.StyleBlock{
 745			StylePrimitive: ansi.StylePrimitive{
 746				Prefix:          "#### ",
 747				Color:           plainFg,
 748				BackgroundColor: plainBg,
 749			},
 750		},
 751		H5: ansi.StyleBlock{
 752			StylePrimitive: ansi.StylePrimitive{
 753				Prefix:          "##### ",
 754				Color:           plainFg,
 755				BackgroundColor: plainBg,
 756			},
 757		},
 758		H6: ansi.StyleBlock{
 759			StylePrimitive: ansi.StylePrimitive{
 760				Prefix:          "###### ",
 761				Color:           plainFg,
 762				BackgroundColor: plainBg,
 763			},
 764		},
 765		Strikethrough: ansi.StylePrimitive{
 766			CrossedOut:      boolPtr(true),
 767			Color:           plainFg,
 768			BackgroundColor: plainBg,
 769		},
 770		Emph: ansi.StylePrimitive{
 771			Italic:          boolPtr(true),
 772			Color:           plainFg,
 773			BackgroundColor: plainBg,
 774		},
 775		Strong: ansi.StylePrimitive{
 776			Bold:            boolPtr(true),
 777			Color:           plainFg,
 778			BackgroundColor: plainBg,
 779		},
 780		HorizontalRule: ansi.StylePrimitive{
 781			Format:          "\n--------\n",
 782			Color:           plainFg,
 783			BackgroundColor: plainBg,
 784		},
 785		Item: ansi.StylePrimitive{
 786			BlockPrefix:     "• ",
 787			Color:           plainFg,
 788			BackgroundColor: plainBg,
 789		},
 790		Enumeration: ansi.StylePrimitive{
 791			BlockPrefix:     ". ",
 792			Color:           plainFg,
 793			BackgroundColor: plainBg,
 794		},
 795		Task: ansi.StyleTask{
 796			StylePrimitive: ansi.StylePrimitive{
 797				Color:           plainFg,
 798				BackgroundColor: plainBg,
 799			},
 800			Ticked:   "[✓] ",
 801			Unticked: "[ ] ",
 802		},
 803		Link: ansi.StylePrimitive{
 804			Underline:       boolPtr(true),
 805			Color:           plainFg,
 806			BackgroundColor: plainBg,
 807		},
 808		LinkText: ansi.StylePrimitive{
 809			Bold:            boolPtr(true),
 810			Color:           plainFg,
 811			BackgroundColor: plainBg,
 812		},
 813		Image: ansi.StylePrimitive{
 814			Underline:       boolPtr(true),
 815			Color:           plainFg,
 816			BackgroundColor: plainBg,
 817		},
 818		ImageText: ansi.StylePrimitive{
 819			Format:          "Image: {{.text}} →",
 820			Color:           plainFg,
 821			BackgroundColor: plainBg,
 822		},
 823		Code: ansi.StyleBlock{
 824			StylePrimitive: ansi.StylePrimitive{
 825				Prefix:          " ",
 826				Suffix:          " ",
 827				Color:           plainFg,
 828				BackgroundColor: plainBg,
 829			},
 830		},
 831		CodeBlock: ansi.StyleCodeBlock{
 832			StyleBlock: ansi.StyleBlock{
 833				StylePrimitive: ansi.StylePrimitive{
 834					Color:           plainFg,
 835					BackgroundColor: plainBg,
 836				},
 837				Margin: uintPtr(defaultMargin),
 838			},
 839		},
 840		Table: ansi.StyleTable{
 841			StyleBlock: ansi.StyleBlock{
 842				StylePrimitive: ansi.StylePrimitive{
 843					Color:           plainFg,
 844					BackgroundColor: plainBg,
 845				},
 846			},
 847		},
 848		DefinitionDescription: ansi.StylePrimitive{
 849			BlockPrefix:     "\n ",
 850			Color:           plainFg,
 851			BackgroundColor: plainBg,
 852		},
 853	}
 854
 855	s.Help = help.Styles{
 856		ShortKey:       base.Foreground(fgMuted),
 857		ShortDesc:      base.Foreground(fgSubtle),
 858		ShortSeparator: base.Foreground(border),
 859		Ellipsis:       base.Foreground(border),
 860		FullKey:        base.Foreground(fgMuted),
 861		FullDesc:       base.Foreground(fgSubtle),
 862		FullSeparator:  base.Foreground(border),
 863	}
 864
 865	s.Diff = diffview.Style{
 866		DividerLine: diffview.LineStyle{
 867			LineNumber: lipgloss.NewStyle().
 868				Foreground(fgHalfMuted).
 869				Background(bgBaseLighter),
 870			Code: lipgloss.NewStyle().
 871				Foreground(fgHalfMuted).
 872				Background(bgBaseLighter),
 873		},
 874		MissingLine: diffview.LineStyle{
 875			LineNumber: lipgloss.NewStyle().
 876				Background(bgBaseLighter),
 877			Code: lipgloss.NewStyle().
 878				Background(bgBaseLighter),
 879		},
 880		EqualLine: diffview.LineStyle{
 881			LineNumber: lipgloss.NewStyle().
 882				Foreground(fgMuted).
 883				Background(bgBase),
 884			Code: lipgloss.NewStyle().
 885				Foreground(fgMuted).
 886				Background(bgBase),
 887		},
 888		InsertLine: diffview.LineStyle{
 889			LineNumber: lipgloss.NewStyle().
 890				Foreground(lipgloss.Color("#629657")).
 891				Background(lipgloss.Color("#2b322a")),
 892			Symbol: lipgloss.NewStyle().
 893				Foreground(lipgloss.Color("#629657")).
 894				Background(lipgloss.Color("#323931")),
 895			Code: lipgloss.NewStyle().
 896				Background(lipgloss.Color("#323931")),
 897		},
 898		DeleteLine: diffview.LineStyle{
 899			LineNumber: lipgloss.NewStyle().
 900				Foreground(lipgloss.Color("#a45c59")).
 901				Background(lipgloss.Color("#312929")),
 902			Symbol: lipgloss.NewStyle().
 903				Foreground(lipgloss.Color("#a45c59")).
 904				Background(lipgloss.Color("#383030")),
 905			Code: lipgloss.NewStyle().
 906				Background(lipgloss.Color("#383030")),
 907		},
 908	}
 909
 910	s.FilePicker = filepicker.Styles{
 911		DisabledCursor:   base.Foreground(fgMuted),
 912		Cursor:           base.Foreground(fgBase),
 913		Symlink:          base.Foreground(fgSubtle),
 914		Directory:        base.Foreground(primary),
 915		File:             base.Foreground(fgBase),
 916		DisabledFile:     base.Foreground(fgMuted),
 917		DisabledSelected: base.Background(bgOverlay).Foreground(fgMuted),
 918		Permission:       base.Foreground(fgMuted),
 919		Selected:         base.Background(primary).Foreground(fgBase),
 920		FileSize:         base.Foreground(fgMuted),
 921		EmptyDirectory:   base.Foreground(fgMuted).PaddingLeft(2).SetString("Empty directory"),
 922	}
 923
 924	// borders
 925	s.FocusedMessageBorder = lipgloss.Border{Left: BorderThick}
 926
 927	// text presets
 928	s.Base = lipgloss.NewStyle().Foreground(fgBase)
 929	s.Muted = lipgloss.NewStyle().Foreground(fgMuted)
 930	s.HalfMuted = lipgloss.NewStyle().Foreground(fgHalfMuted)
 931	s.Subtle = lipgloss.NewStyle().Foreground(fgSubtle)
 932
 933	s.WindowTooSmall = s.Muted
 934
 935	// tag presets
 936	s.TagBase = lipgloss.NewStyle().Padding(0, 1).Foreground(white)
 937	s.TagError = s.TagBase.Background(redDark)
 938	s.TagInfo = s.TagBase.Background(blueLight)
 939
 940	// headers
 941	s.HeaderTool = lipgloss.NewStyle().Foreground(blue)
 942	s.HeaderToolNested = lipgloss.NewStyle().Foreground(fgHalfMuted)
 943
 944	// panels
 945	s.PanelMuted = s.Muted.Background(bgBaseLighter)
 946	s.PanelBase = lipgloss.NewStyle().Background(bgBase)
 947
 948	// code line number
 949	s.LineNumber = lipgloss.NewStyle().Foreground(fgMuted).Background(bgBase).PaddingRight(1).PaddingLeft(1)
 950
 951	// Tool calls
 952	s.ToolCallPending = lipgloss.NewStyle().Foreground(greenDark).SetString(ToolPending)
 953	s.ToolCallError = lipgloss.NewStyle().Foreground(redDark).SetString(ToolError)
 954	s.ToolCallSuccess = lipgloss.NewStyle().Foreground(green).SetString(ToolSuccess)
 955	// Cancelled uses muted tone but same glyph as pending
 956	s.ToolCallCancelled = s.Muted.SetString(ToolPending)
 957	s.EarlyStateMessage = s.Subtle.PaddingLeft(2)
 958
 959	// Tool rendering styles
 960	s.Tool.IconPending = base.Foreground(greenDark).SetString(ToolPending)
 961	s.Tool.IconSuccess = base.Foreground(green).SetString(ToolSuccess)
 962	s.Tool.IconError = base.Foreground(redDark).SetString(ToolError)
 963	s.Tool.IconCancelled = s.Muted.SetString(ToolPending)
 964
 965	s.Tool.NameNormal = base.Foreground(blue)
 966	s.Tool.NameNested = base.Foreground(fgHalfMuted)
 967
 968	s.Tool.ParamMain = s.Subtle
 969	s.Tool.ParamKey = s.Subtle
 970
 971	// Content rendering - prepared styles that accept width parameter
 972	s.Tool.ContentLine = s.Muted.Background(bgBaseLighter)
 973	s.Tool.ContentTruncation = s.Muted.Background(bgBaseLighter)
 974	s.Tool.ContentCodeLine = s.Base.Background(bgBase)
 975	s.Tool.ContentCodeTruncation = s.Muted.Background(bgBase).PaddingLeft(2)
 976	s.Tool.ContentCodeBg = bgBase
 977	s.Tool.Body = base.PaddingLeft(2)
 978
 979	// Deprecated - kept for backward compatibility
 980	s.Tool.ContentBg = s.Muted.Background(bgBaseLighter)
 981	s.Tool.ContentText = s.Muted
 982	s.Tool.ContentLineNumber = base.Foreground(fgMuted).Background(bgBase).PaddingRight(1).PaddingLeft(1)
 983
 984	s.Tool.StateWaiting = base.Foreground(fgSubtle)
 985	s.Tool.StateCancelled = base.Foreground(fgSubtle)
 986
 987	s.Tool.ErrorTag = base.Padding(0, 1).Background(red).Foreground(white)
 988	s.Tool.ErrorMessage = base.Foreground(fgHalfMuted)
 989
 990	// Diff and multi-edit styles
 991	s.Tool.DiffTruncation = s.Muted.Background(bgBaseLighter).PaddingLeft(2)
 992	s.Tool.NoteTag = base.Padding(0, 1).Background(info).Foreground(white)
 993	s.Tool.NoteMessage = base.Foreground(fgHalfMuted)
 994
 995	// Job header styles
 996	s.Tool.JobIconPending = base.Foreground(greenDark)
 997	s.Tool.JobIconError = base.Foreground(redDark)
 998	s.Tool.JobIconSuccess = base.Foreground(green)
 999	s.Tool.JobToolName = base.Foreground(blue)
1000	s.Tool.JobAction = base.Foreground(blueDark)
1001	s.Tool.JobPID = s.Muted
1002	s.Tool.JobDescription = s.Subtle
1003
1004	// Agent task styles
1005	s.Tool.AgentTaskTag = base.Bold(true).Padding(0, 1).MarginLeft(2).Background(blueLight).Foreground(white)
1006	s.Tool.AgentPrompt = s.Muted
1007
1008	// Buttons
1009	s.ButtonFocus = lipgloss.NewStyle().Foreground(white).Background(secondary)
1010	s.ButtonBlur = s.Base.Background(bgSubtle)
1011
1012	// Borders
1013	s.BorderFocus = lipgloss.NewStyle().BorderForeground(borderFocus).Border(lipgloss.RoundedBorder()).Padding(1, 2)
1014
1015	// Editor
1016	s.EditorPromptNormalFocused = lipgloss.NewStyle().Foreground(greenDark).SetString("::: ")
1017	s.EditorPromptNormalBlurred = s.EditorPromptNormalFocused.Foreground(fgMuted)
1018	s.EditorPromptYoloIconFocused = lipgloss.NewStyle().MarginRight(1).Foreground(charmtone.Oyster).Background(charmtone.Citron).Bold(true).SetString(" ! ")
1019	s.EditorPromptYoloIconBlurred = s.EditorPromptYoloIconFocused.Foreground(charmtone.Pepper).Background(charmtone.Squid)
1020	s.EditorPromptYoloDotsFocused = lipgloss.NewStyle().MarginRight(1).Foreground(charmtone.Zest).SetString(":::")
1021	s.EditorPromptYoloDotsBlurred = s.EditorPromptYoloDotsFocused.Foreground(charmtone.Squid)
1022
1023	s.RadioOn = s.HalfMuted.SetString(RadioOn)
1024	s.RadioOff = s.HalfMuted.SetString(RadioOff)
1025
1026	// Logo colors
1027	s.LogoFieldColor = primary
1028	s.LogoTitleColorA = secondary
1029	s.LogoTitleColorB = primary
1030	s.LogoCharmColor = secondary
1031	s.LogoVersionColor = primary
1032
1033	// Section
1034	s.Section.Title = s.Subtle
1035	s.Section.Line = s.Base.Foreground(charmtone.Charcoal)
1036
1037	// Initialize
1038	s.Initialize.Header = s.Base
1039	s.Initialize.Content = s.Muted
1040	s.Initialize.Accent = s.Base.Foreground(greenDark)
1041
1042	// LSP and MCP status.
1043	s.ItemOfflineIcon = lipgloss.NewStyle().Foreground(charmtone.Squid).SetString("●")
1044	s.ItemBusyIcon = s.ItemOfflineIcon.Foreground(charmtone.Citron)
1045	s.ItemErrorIcon = s.ItemOfflineIcon.Foreground(charmtone.Coral)
1046	s.ItemOnlineIcon = s.ItemOfflineIcon.Foreground(charmtone.Guac)
1047
1048	// LSP
1049	s.LSP.ErrorDiagnostic = s.Base.Foreground(redDark)
1050	s.LSP.WarningDiagnostic = s.Base.Foreground(warning)
1051	s.LSP.HintDiagnostic = s.Base.Foreground(fgHalfMuted)
1052	s.LSP.InfoDiagnostic = s.Base.Foreground(info)
1053
1054	// Files
1055	s.Files.Path = s.Muted
1056	s.Files.Additions = s.Base.Foreground(greenDark)
1057	s.Files.Deletions = s.Base.Foreground(redDark)
1058
1059	// Chat
1060	messageFocussedBorder := lipgloss.Border{
1061		Left: "▌",
1062	}
1063
1064	s.Chat.Message.NoContent = lipgloss.NewStyle().Foreground(fgBase)
1065	s.Chat.Message.UserBlurred = s.Chat.Message.NoContent.PaddingLeft(1).BorderLeft(true).
1066		BorderForeground(primary).BorderStyle(normalBorder)
1067	s.Chat.Message.UserFocused = s.Chat.Message.NoContent.PaddingLeft(1).BorderLeft(true).
1068		BorderForeground(primary).BorderStyle(messageFocussedBorder)
1069	s.Chat.Message.AssistantBlurred = s.Chat.Message.NoContent.PaddingLeft(2)
1070	s.Chat.Message.AssistantFocused = s.Chat.Message.NoContent.PaddingLeft(1).BorderLeft(true).
1071		BorderForeground(greenDark).BorderStyle(messageFocussedBorder)
1072	s.Chat.Message.Thinking = lipgloss.NewStyle().MaxHeight(10)
1073	s.Chat.Message.ErrorTag = lipgloss.NewStyle().Padding(0, 1).
1074		Background(red).Foreground(white)
1075	s.Chat.Message.ErrorTitle = lipgloss.NewStyle().Foreground(fgHalfMuted)
1076	s.Chat.Message.ErrorDetails = lipgloss.NewStyle().Foreground(fgSubtle)
1077
1078	// Message item styles
1079	s.Chat.Message.Attachment = lipgloss.NewStyle().MarginLeft(1).Background(bgSubtle)
1080	s.Chat.Message.ToolCallFocused = s.Muted.PaddingLeft(1).
1081		BorderStyle(messageFocussedBorder).
1082		BorderLeft(true).
1083		BorderForeground(greenDark)
1084	s.Chat.Message.ToolCallBlurred = s.Muted.PaddingLeft(2)
1085	s.Chat.Message.SectionHeader = s.Base.PaddingLeft(2)
1086
1087	// Thinking section styles
1088	s.Chat.Message.ThinkingBox = s.Subtle.Background(bgBaseLighter)
1089	s.Chat.Message.ThinkingTruncationHint = s.Muted
1090	s.Chat.Message.ThinkingFooterTitle = s.Muted
1091	s.Chat.Message.ThinkingFooterDuration = s.Subtle
1092
1093	// Text selection.
1094	s.TextSelection = lipgloss.NewStyle().Foreground(charmtone.Salt).Background(charmtone.Charple)
1095
1096	// Dialog styles
1097	s.Dialog.Title = base.Padding(0, 1).Foreground(primary)
1098	s.Dialog.View = base.Border(lipgloss.RoundedBorder()).BorderForeground(borderFocus)
1099	s.Dialog.HelpView = base.Padding(0, 1).AlignHorizontal(lipgloss.Left)
1100	s.Dialog.Help.ShortKey = base.Foreground(fgMuted)
1101	s.Dialog.Help.ShortDesc = base.Foreground(fgSubtle)
1102	s.Dialog.Help.ShortSeparator = base.Foreground(border)
1103	s.Dialog.Help.Ellipsis = base.Foreground(border)
1104	s.Dialog.Help.FullKey = base.Foreground(fgMuted)
1105	s.Dialog.Help.FullDesc = base.Foreground(fgSubtle)
1106	s.Dialog.Help.FullSeparator = base.Foreground(border)
1107	s.Dialog.NormalItem = base.Padding(0, 1).Foreground(fgBase)
1108	s.Dialog.SelectedItem = base.Padding(0, 1).Background(primary).Foreground(fgBase)
1109	s.Dialog.InputPrompt = base.Margin(1, 1)
1110
1111	s.Dialog.List = base.Margin(0, 0, 1, 0)
1112
1113	return s
1114}
1115
1116// Helper functions for style pointers
1117func boolPtr(b bool) *bool       { return &b }
1118func stringPtr(s string) *string { return &s }
1119func uintPtr(u uint) *uint       { return &u }
1120func chromaStyle(style ansi.StylePrimitive) string {
1121	var s string
1122
1123	if style.Color != nil {
1124		s = *style.Color
1125	}
1126	if style.BackgroundColor != nil {
1127		if s != "" {
1128			s += " "
1129		}
1130		s += "bg:" + *style.BackgroundColor
1131	}
1132	if style.Italic != nil && *style.Italic {
1133		if s != "" {
1134			s += " "
1135		}
1136		s += "italic"
1137	}
1138	if style.Bold != nil && *style.Bold {
1139		if s != "" {
1140			s += " "
1141		}
1142		s += "bold"
1143	}
1144	if style.Underline != nil && *style.Underline {
1145		if s != "" {
1146			s += " "
1147		}
1148		s += "underline"
1149	}
1150
1151	return s
1152}