1package styles
2
3import (
4 "fmt"
5 "image/color"
6 "strings"
7
8 "charm.land/bubbles/v2/filepicker"
9 "charm.land/bubbles/v2/help"
10 "charm.land/bubbles/v2/textarea"
11 "charm.land/bubbles/v2/textinput"
12 tea "charm.land/bubbletea/v2"
13 "charm.land/lipgloss/v2"
14 "git.secluded.site/crush/internal/tui/exp/diffview"
15 "github.com/charmbracelet/glamour/v2/ansi"
16 "github.com/charmbracelet/x/exp/charmtone"
17 "github.com/lucasb-eyer/go-colorful"
18 "github.com/rivo/uniseg"
19)
20
21const (
22 defaultListIndent = 2
23 defaultListLevelIndent = 4
24 defaultMargin = 2
25)
26
27type Theme struct {
28 Name string
29 IsDark bool
30
31 Primary color.Color
32 Secondary color.Color
33 Tertiary color.Color
34 Accent color.Color
35
36 BgBase color.Color
37 BgBaseLighter color.Color
38 BgSubtle color.Color
39 BgOverlay color.Color
40
41 FgBase color.Color
42 FgMuted color.Color
43 FgHalfMuted color.Color
44 FgSubtle color.Color
45 FgSelected color.Color
46
47 Border color.Color
48 BorderFocus color.Color
49
50 Success color.Color
51 Error color.Color
52 Warning color.Color
53 Info color.Color
54
55 // Colors
56 // White
57 White color.Color
58
59 // Blues
60 BlueLight color.Color
61 BlueDark color.Color
62 Blue color.Color
63
64 // Yellows
65 Yellow color.Color
66 Citron color.Color
67
68 // Greens
69 Green color.Color
70 GreenDark color.Color
71 GreenLight color.Color
72
73 // Reds
74 Red color.Color
75 RedDark color.Color
76 RedLight color.Color
77 Cherry color.Color
78
79 // Markdown colors (theme-aware for accessibility).
80 MdText color.Color // Document text
81 MdHeading color.Color // Heading color
82 MdH6 color.Color // H6 color
83 MdHRule color.Color // Horizontal rule
84 MdLink color.Color // Link URL
85 MdLinkText color.Color // Link text
86 MdImage color.Color // Image URL
87 MdImageText color.Color // Image alt text
88 MdCodeFg color.Color // Inline code foreground
89 MdCodeBg color.Color // Inline code background
90 MdCodeBlockFg color.Color // Code block text
91 MdCodeBlockBg color.Color // Code block background
92 MdComment color.Color // Code comment
93 MdKeyword color.Color // Keyword
94 MdKeywordAlt color.Color // Keyword reserved/namespace
95 MdKeywordType color.Color // Type keyword
96 MdOperator color.Color // Operator
97 MdPunctuation color.Color // Punctuation
98 MdName color.Color // Name/identifier
99 MdNameBuiltin color.Color // Builtin name
100 MdNameTag color.Color // Tag name
101 MdNameAttr color.Color // Attribute name
102 MdNameClass color.Color // Class name
103 MdNameDecorator color.Color // Decorator
104 MdNameFunc color.Color // Function name
105 MdNumber color.Color // Literal number
106 MdString color.Color // Literal string
107 MdStringEscape color.Color // String escape
108 MdDeleted color.Color // Deleted text
109 MdInserted color.Color // Inserted text
110 MdSubheading color.Color // Subheading
111 MdError color.Color // Error foreground
112 MdErrorBg color.Color // Error background
113
114 // Diff colors (theme-aware).
115 DiffInsertFg color.Color // Insert line number fg
116 DiffInsertBg color.Color // Insert line number bg
117 DiffInsertSymBg color.Color // Insert symbol bg
118 DiffInsertCodeBg color.Color // Insert code bg
119 DiffDeleteFg color.Color // Delete line number fg
120 DiffDeleteBg color.Color // Delete line number bg
121 DiffDeleteSymBg color.Color // Delete symbol bg
122 DiffDeleteCodeBg color.Color // Delete code bg
123
124 // Text selection.
125 TextSelection lipgloss.Style
126
127 // LSP and MCP status indicators.
128 ItemOfflineIcon lipgloss.Style
129 ItemBusyIcon lipgloss.Style
130 ItemErrorIcon lipgloss.Style
131 ItemOnlineIcon lipgloss.Style
132
133 // Editor: Yolo Mode.
134 YoloIconFocused lipgloss.Style
135 YoloIconBlurred lipgloss.Style
136 YoloDotsFocused lipgloss.Style
137 YoloDotsBlurred lipgloss.Style
138
139 // oAuth Chooser.
140 AuthBorderSelected lipgloss.Style
141 AuthTextSelected lipgloss.Style
142 AuthBorderUnselected lipgloss.Style
143 AuthTextUnselected lipgloss.Style
144
145 styles *Styles
146}
147
148type Styles struct {
149 Base lipgloss.Style
150 SelectedBase lipgloss.Style
151
152 Title lipgloss.Style
153 Subtitle lipgloss.Style
154 Text lipgloss.Style
155 TextSelected lipgloss.Style
156 Muted lipgloss.Style
157 Subtle lipgloss.Style
158
159 Success lipgloss.Style
160 Error lipgloss.Style
161 Warning lipgloss.Style
162 Info lipgloss.Style
163
164 // Markdown & Chroma
165 Markdown ansi.StyleConfig
166
167 // Inputs
168 TextInput textinput.Styles
169 TextArea textarea.Styles
170
171 // Help
172 Help help.Styles
173
174 // Diff
175 Diff diffview.Style
176
177 // FilePicker
178 FilePicker filepicker.Styles
179}
180
181func (t *Theme) S() *Styles {
182 if t.styles == nil {
183 t.styles = t.buildStyles()
184 }
185 return t.styles
186}
187
188func (t *Theme) buildStyles() *Styles {
189 base := lipgloss.NewStyle().
190 Foreground(t.FgBase)
191 return &Styles{
192 Base: base,
193
194 SelectedBase: base.Background(t.Primary),
195
196 Title: base.
197 Foreground(t.Accent).
198 Bold(true),
199
200 Subtitle: base.
201 Foreground(t.Secondary).
202 Bold(true),
203
204 Text: base,
205 TextSelected: base.Background(t.Primary).Foreground(t.FgSelected),
206
207 Muted: base.Foreground(t.FgMuted),
208
209 Subtle: base.Foreground(t.FgSubtle),
210
211 Success: base.Foreground(t.Success),
212
213 Error: base.Foreground(t.Error),
214
215 Warning: base.Foreground(t.Warning),
216
217 Info: base.Foreground(t.Info),
218
219 TextInput: textinput.Styles{
220 Focused: textinput.StyleState{
221 Text: base,
222 Placeholder: base.Foreground(t.FgSubtle),
223 Prompt: base.Foreground(t.Tertiary),
224 Suggestion: base.Foreground(t.FgSubtle),
225 },
226 Blurred: textinput.StyleState{
227 Text: base.Foreground(t.FgMuted),
228 Placeholder: base.Foreground(t.FgSubtle),
229 Prompt: base.Foreground(t.FgMuted),
230 Suggestion: base.Foreground(t.FgSubtle),
231 },
232 Cursor: textinput.CursorStyle{
233 Color: t.Secondary,
234 Shape: tea.CursorBlock,
235 Blink: true,
236 },
237 },
238 TextArea: textarea.Styles{
239 Focused: textarea.StyleState{
240 Base: base,
241 Text: base,
242 LineNumber: base.Foreground(t.FgSubtle),
243 CursorLine: base,
244 CursorLineNumber: base.Foreground(t.FgSubtle),
245 Placeholder: base.Foreground(t.FgSubtle),
246 Prompt: base.Foreground(t.Tertiary),
247 },
248 Blurred: textarea.StyleState{
249 Base: base,
250 Text: base.Foreground(t.FgMuted),
251 LineNumber: base.Foreground(t.FgMuted),
252 CursorLine: base,
253 CursorLineNumber: base.Foreground(t.FgMuted),
254 Placeholder: base.Foreground(t.FgSubtle),
255 Prompt: base.Foreground(t.FgMuted),
256 },
257 Cursor: textarea.CursorStyle{
258 Color: t.Secondary,
259 Shape: tea.CursorBlock,
260 Blink: true,
261 },
262 },
263
264 Markdown: ansi.StyleConfig{
265 Document: ansi.StyleBlock{
266 StylePrimitive: ansi.StylePrimitive{
267 // BlockPrefix: "\n",
268 // BlockSuffix: "\n",
269 Color: stringPtr(ToHex(t.MdText)),
270 },
271 // Margin: uintPtr(defaultMargin),
272 },
273 BlockQuote: ansi.StyleBlock{
274 StylePrimitive: ansi.StylePrimitive{},
275 Indent: uintPtr(1),
276 IndentToken: stringPtr("│ "),
277 },
278 List: ansi.StyleList{
279 LevelIndent: defaultListIndent,
280 },
281 Heading: ansi.StyleBlock{
282 StylePrimitive: ansi.StylePrimitive{
283 BlockSuffix: "\n",
284 Color: stringPtr(ToHex(t.MdHeading)),
285 Bold: boolPtr(true),
286 },
287 },
288 H1: ansi.StyleBlock{
289 StylePrimitive: ansi.StylePrimitive{
290 Prefix: " ",
291 Suffix: " ",
292 Color: stringPtr(charmtone.Zest.Hex()),
293 BackgroundColor: stringPtr(charmtone.Charple.Hex()),
294 Bold: boolPtr(true),
295 },
296 },
297 H2: ansi.StyleBlock{
298 StylePrimitive: ansi.StylePrimitive{
299 Prefix: "## ",
300 },
301 },
302 H3: ansi.StyleBlock{
303 StylePrimitive: ansi.StylePrimitive{
304 Prefix: "### ",
305 },
306 },
307 H4: ansi.StyleBlock{
308 StylePrimitive: ansi.StylePrimitive{
309 Prefix: "#### ",
310 },
311 },
312 H5: ansi.StyleBlock{
313 StylePrimitive: ansi.StylePrimitive{
314 Prefix: "##### ",
315 },
316 },
317 H6: ansi.StyleBlock{
318 StylePrimitive: ansi.StylePrimitive{
319 Prefix: "###### ",
320 Color: stringPtr(ToHex(t.MdH6)),
321 Bold: boolPtr(false),
322 },
323 },
324 Strikethrough: ansi.StylePrimitive{
325 CrossedOut: boolPtr(true),
326 },
327 Emph: ansi.StylePrimitive{
328 Italic: boolPtr(true),
329 },
330 Strong: ansi.StylePrimitive{
331 Bold: boolPtr(true),
332 },
333 HorizontalRule: ansi.StylePrimitive{
334 Color: stringPtr(ToHex(t.MdHRule)),
335 Format: "\n--------\n",
336 },
337 Item: ansi.StylePrimitive{
338 BlockPrefix: "• ",
339 },
340 Enumeration: ansi.StylePrimitive{
341 BlockPrefix: ". ",
342 },
343 Task: ansi.StyleTask{
344 StylePrimitive: ansi.StylePrimitive{},
345 Ticked: "[✓] ",
346 Unticked: "[ ] ",
347 },
348 Link: ansi.StylePrimitive{
349 Color: stringPtr(ToHex(t.MdLink)),
350 Underline: boolPtr(true),
351 },
352 LinkText: ansi.StylePrimitive{
353 Color: stringPtr(ToHex(t.MdLinkText)),
354 Bold: boolPtr(true),
355 },
356 Image: ansi.StylePrimitive{
357 Color: stringPtr(ToHex(t.MdImage)),
358 Underline: boolPtr(true),
359 },
360 ImageText: ansi.StylePrimitive{
361 Color: stringPtr(ToHex(t.MdImageText)),
362 Format: "Image: {{.text}} →",
363 },
364 Code: ansi.StyleBlock{
365 StylePrimitive: ansi.StylePrimitive{
366 Prefix: " ",
367 Suffix: " ",
368 Color: stringPtr(ToHex(t.MdCodeFg)),
369 BackgroundColor: stringPtr(ToHex(t.MdCodeBg)),
370 },
371 },
372 CodeBlock: ansi.StyleCodeBlock{
373 StyleBlock: ansi.StyleBlock{
374 StylePrimitive: ansi.StylePrimitive{
375 Color: stringPtr(ToHex(t.MdCodeBlockFg)),
376 },
377 Margin: uintPtr(defaultMargin),
378 },
379 Chroma: &ansi.Chroma{
380 Text: ansi.StylePrimitive{
381 Color: stringPtr(ToHex(t.MdText)),
382 },
383 Error: ansi.StylePrimitive{
384 Color: stringPtr(ToHex(t.MdError)),
385 BackgroundColor: stringPtr(ToHex(t.MdErrorBg)),
386 },
387 Comment: ansi.StylePrimitive{
388 Color: stringPtr(ToHex(t.MdComment)),
389 },
390 CommentPreproc: ansi.StylePrimitive{
391 Color: stringPtr(ToHex(t.MdComment)),
392 },
393 Keyword: ansi.StylePrimitive{
394 Color: stringPtr(ToHex(t.MdKeyword)),
395 },
396 KeywordReserved: ansi.StylePrimitive{
397 Color: stringPtr(ToHex(t.MdKeywordAlt)),
398 },
399 KeywordNamespace: ansi.StylePrimitive{
400 Color: stringPtr(ToHex(t.MdKeywordAlt)),
401 },
402 KeywordType: ansi.StylePrimitive{
403 Color: stringPtr(ToHex(t.MdKeywordType)),
404 },
405 Operator: ansi.StylePrimitive{
406 Color: stringPtr(ToHex(t.MdOperator)),
407 },
408 Punctuation: ansi.StylePrimitive{
409 Color: stringPtr(ToHex(t.MdPunctuation)),
410 },
411 Name: ansi.StylePrimitive{
412 Color: stringPtr(ToHex(t.MdName)),
413 },
414 NameBuiltin: ansi.StylePrimitive{
415 Color: stringPtr(ToHex(t.MdNameBuiltin)),
416 },
417 NameTag: ansi.StylePrimitive{
418 Color: stringPtr(ToHex(t.MdNameTag)),
419 },
420 NameAttribute: ansi.StylePrimitive{
421 Color: stringPtr(ToHex(t.MdNameAttr)),
422 },
423 NameClass: ansi.StylePrimitive{
424 Color: stringPtr(ToHex(t.MdNameClass)),
425 Underline: boolPtr(true),
426 Bold: boolPtr(true),
427 },
428 NameDecorator: ansi.StylePrimitive{
429 Color: stringPtr(ToHex(t.MdNameDecorator)),
430 },
431 NameFunction: ansi.StylePrimitive{
432 Color: stringPtr(ToHex(t.MdNameFunc)),
433 },
434 LiteralNumber: ansi.StylePrimitive{
435 Color: stringPtr(ToHex(t.MdNumber)),
436 },
437 LiteralString: ansi.StylePrimitive{
438 Color: stringPtr(ToHex(t.MdString)),
439 },
440 LiteralStringEscape: ansi.StylePrimitive{
441 Color: stringPtr(ToHex(t.MdStringEscape)),
442 },
443 GenericDeleted: ansi.StylePrimitive{
444 Color: stringPtr(ToHex(t.MdDeleted)),
445 },
446 GenericEmph: ansi.StylePrimitive{
447 Italic: boolPtr(true),
448 },
449 GenericInserted: ansi.StylePrimitive{
450 Color: stringPtr(ToHex(t.MdInserted)),
451 },
452 GenericStrong: ansi.StylePrimitive{
453 Bold: boolPtr(true),
454 },
455 GenericSubheading: ansi.StylePrimitive{
456 Color: stringPtr(ToHex(t.MdSubheading)),
457 },
458 Background: ansi.StylePrimitive{
459 BackgroundColor: stringPtr(ToHex(t.MdCodeBlockBg)),
460 },
461 },
462 },
463 Table: ansi.StyleTable{
464 StyleBlock: ansi.StyleBlock{
465 StylePrimitive: ansi.StylePrimitive{},
466 },
467 },
468 DefinitionDescription: ansi.StylePrimitive{
469 BlockPrefix: "\n ",
470 },
471 },
472
473 Help: help.Styles{
474 ShortKey: base.Foreground(t.FgMuted),
475 ShortDesc: base.Foreground(t.FgSubtle),
476 ShortSeparator: base.Foreground(t.Border),
477 Ellipsis: base.Foreground(t.Border),
478 FullKey: base.Foreground(t.FgMuted),
479 FullDesc: base.Foreground(t.FgSubtle),
480 FullSeparator: base.Foreground(t.Border),
481 },
482
483 Diff: diffview.Style{
484 DividerLine: diffview.LineStyle{
485 LineNumber: lipgloss.NewStyle().
486 Foreground(t.FgHalfMuted).
487 Background(t.BgBaseLighter),
488 Code: lipgloss.NewStyle().
489 Foreground(t.FgHalfMuted).
490 Background(t.BgBaseLighter),
491 },
492 MissingLine: diffview.LineStyle{
493 LineNumber: lipgloss.NewStyle().
494 Background(t.BgBaseLighter),
495 Code: lipgloss.NewStyle().
496 Background(t.BgBaseLighter),
497 },
498 EqualLine: diffview.LineStyle{
499 LineNumber: lipgloss.NewStyle().
500 Foreground(t.FgMuted).
501 Background(t.BgBase),
502 Code: lipgloss.NewStyle().
503 Foreground(t.FgMuted).
504 Background(t.BgBase),
505 },
506 InsertLine: diffview.LineStyle{
507 LineNumber: lipgloss.NewStyle().
508 Foreground(t.DiffInsertFg).
509 Background(t.DiffInsertBg),
510 Symbol: lipgloss.NewStyle().
511 Foreground(t.DiffInsertFg).
512 Background(t.DiffInsertSymBg),
513 Code: lipgloss.NewStyle().
514 Background(t.DiffInsertCodeBg),
515 },
516 DeleteLine: diffview.LineStyle{
517 LineNumber: lipgloss.NewStyle().
518 Foreground(t.DiffDeleteFg).
519 Background(t.DiffDeleteBg),
520 Symbol: lipgloss.NewStyle().
521 Foreground(t.DiffDeleteFg).
522 Background(t.DiffDeleteSymBg),
523 Code: lipgloss.NewStyle().
524 Background(t.DiffDeleteCodeBg),
525 },
526 },
527 FilePicker: filepicker.Styles{
528 DisabledCursor: base.Foreground(t.FgMuted),
529 Cursor: base.Foreground(t.FgBase),
530 Symlink: base.Foreground(t.FgSubtle),
531 Directory: base.Foreground(t.Primary),
532 File: base.Foreground(t.FgBase),
533 DisabledFile: base.Foreground(t.FgMuted),
534 DisabledSelected: base.Background(t.BgOverlay).Foreground(t.FgMuted),
535 Permission: base.Foreground(t.FgMuted),
536 Selected: base.Background(t.Primary).Foreground(t.FgBase),
537 FileSize: base.Foreground(t.FgMuted),
538 EmptyDirectory: base.Foreground(t.FgMuted).PaddingLeft(2).SetString("Empty directory"),
539 },
540 }
541}
542
543type Manager struct {
544 themes map[string]*Theme
545 current *Theme
546}
547
548var defaultManager *Manager
549
550func SetDefaultManager(m *Manager) {
551 defaultManager = m
552}
553
554func DefaultManager() *Manager {
555 if defaultManager == nil {
556 defaultManager = NewManager()
557 }
558 return defaultManager
559}
560
561func CurrentTheme() *Theme {
562 if defaultManager == nil {
563 defaultManager = NewManager()
564 }
565 return defaultManager.Current()
566}
567
568func NewManager() *Manager {
569 m := &Manager{
570 themes: make(map[string]*Theme),
571 }
572
573 dark := NewCharmtoneTheme()
574 light := NewLightTheme()
575 m.Register(dark)
576 m.Register(light)
577 m.current = dark // default to dark theme
578
579 return m
580}
581
582func (m *Manager) Register(theme *Theme) {
583 m.themes[theme.Name] = theme
584}
585
586func (m *Manager) Current() *Theme {
587 return m.current
588}
589
590func (m *Manager) SetTheme(name string) error {
591 if theme, ok := m.themes[name]; ok {
592 m.current = theme
593 return nil
594 }
595 return fmt.Errorf("theme %s not found", name)
596}
597
598func (m *Manager) List() []string {
599 names := make([]string, 0, len(m.themes))
600 for name := range m.themes {
601 names = append(names, name)
602 }
603 return names
604}
605
606// ParseHex converts hex string to color
607func ParseHex(hex string) color.Color {
608 var r, g, b uint8
609 fmt.Sscanf(hex, "#%02x%02x%02x", &r, &g, &b)
610 return color.RGBA{R: r, G: g, B: b, A: 255}
611}
612
613// ToHex converts a color.Color to hex string.
614func ToHex(c color.Color) string {
615 r, g, b, _ := c.RGBA()
616 return fmt.Sprintf("#%02X%02X%02X", r>>8, g>>8, b>>8)
617}
618
619// Alpha returns a color with transparency
620func Alpha(c color.Color, alpha uint8) color.Color {
621 r, g, b, _ := c.RGBA()
622 return color.RGBA{
623 R: uint8(r >> 8),
624 G: uint8(g >> 8),
625 B: uint8(b >> 8),
626 A: alpha,
627 }
628}
629
630// Darken makes a color darker by percentage (0-100)
631func Darken(c color.Color, percent float64) color.Color {
632 r, g, b, a := c.RGBA()
633 factor := 1.0 - percent/100.0
634 return color.RGBA{
635 R: uint8(float64(r>>8) * factor),
636 G: uint8(float64(g>>8) * factor),
637 B: uint8(float64(b>>8) * factor),
638 A: uint8(a >> 8),
639 }
640}
641
642// Lighten makes a color lighter by percentage (0-100)
643func Lighten(c color.Color, percent float64) color.Color {
644 r, g, b, a := c.RGBA()
645 factor := percent / 100.0
646 return color.RGBA{
647 R: uint8(min(255, float64(r>>8)+255*factor)),
648 G: uint8(min(255, float64(g>>8)+255*factor)),
649 B: uint8(min(255, float64(b>>8)+255*factor)),
650 A: uint8(a >> 8),
651 }
652}
653
654func ForegroundGrad(input string, bold bool, color1, color2 color.Color) []string {
655 if input == "" {
656 return []string{""}
657 }
658 t := CurrentTheme()
659 if len(input) == 1 {
660 style := t.S().Base.Foreground(color1)
661 if bold {
662 style.Bold(true)
663 }
664 return []string{style.Render(input)}
665 }
666 var clusters []string
667 gr := uniseg.NewGraphemes(input)
668 for gr.Next() {
669 clusters = append(clusters, string(gr.Runes()))
670 }
671
672 ramp := blendColors(len(clusters), color1, color2)
673 for i, c := range ramp {
674 style := t.S().Base.Foreground(c)
675 if bold {
676 style.Bold(true)
677 }
678 clusters[i] = style.Render(clusters[i])
679 }
680 return clusters
681}
682
683// ApplyForegroundGrad renders a given string with a horizontal gradient
684// foreground.
685func ApplyForegroundGrad(input string, color1, color2 color.Color) string {
686 if input == "" {
687 return ""
688 }
689 var o strings.Builder
690 clusters := ForegroundGrad(input, false, color1, color2)
691 for _, c := range clusters {
692 fmt.Fprint(&o, c)
693 }
694 return o.String()
695}
696
697// ApplyBoldForegroundGrad renders a given string with a horizontal gradient
698// foreground.
699func ApplyBoldForegroundGrad(input string, color1, color2 color.Color) string {
700 if input == "" {
701 return ""
702 }
703 var o strings.Builder
704 clusters := ForegroundGrad(input, true, color1, color2)
705 for _, c := range clusters {
706 fmt.Fprint(&o, c)
707 }
708 return o.String()
709}
710
711// blendColors returns a slice of colors blended between the given keys.
712// Blending is done in Hcl to stay in gamut.
713func blendColors(size int, stops ...color.Color) []color.Color {
714 if len(stops) < 2 {
715 return nil
716 }
717
718 stopsPrime := make([]colorful.Color, len(stops))
719 for i, k := range stops {
720 stopsPrime[i], _ = colorful.MakeColor(k)
721 }
722
723 numSegments := len(stopsPrime) - 1
724 blended := make([]color.Color, 0, size)
725
726 // Calculate how many colors each segment should have.
727 segmentSizes := make([]int, numSegments)
728 baseSize := size / numSegments
729 remainder := size % numSegments
730
731 // Distribute the remainder across segments.
732 for i := range numSegments {
733 segmentSizes[i] = baseSize
734 if i < remainder {
735 segmentSizes[i]++
736 }
737 }
738
739 // Generate colors for each segment.
740 for i := range numSegments {
741 c1 := stopsPrime[i]
742 c2 := stopsPrime[i+1]
743 segmentSize := segmentSizes[i]
744
745 for j := range segmentSize {
746 var t float64
747 if segmentSize > 1 {
748 t = float64(j) / float64(segmentSize-1)
749 }
750 c := c1.BlendHcl(c2, t)
751 blended = append(blended, c)
752 }
753 }
754
755 return blended
756}