@@ -4,6 +4,7 @@ package logo
6 "image/color"
6 "image/color"
9 "charm.land/lipgloss/v2"
10 "charm.land/lipgloss/v2"
@@ -26,6 +27,11 @@ type Opts struct {
26 VersionColor color.Color // version text color
27 VersionColor color.Color // version text color
27 Width int // width of the rendered logo, used for truncation
28 Width int // width of the rendered logo, used for truncation
28 Hyper bool // whether it is Crush or Hypercrush
29 Hyper bool // whether it is Crush or Hypercrush
31+ // When true, stretch a random letterform on each render. Has no effect in
32+ // compact mode. Mainly for testing. In production you will want to cache
33+ // the stretched letterform to keep the logo from jittering on resize.
31// Render renders the Crush logo. Set the argument to true to render the narrow
37// Render renders the Crush logo. Set the argument to true to render the narrow
@@ -34,7 +40,10 @@ type Opts struct {
34// The compact argument determines whether it renders compact for the sidebar
40// The compact argument determines whether it renders compact for the sidebar
35// or wider for the main pane.
41// or wider for the main pane.
36func Render(base lipgloss.Style, version string, compact bool, o Opts) string {
42func Render(base lipgloss.Style, version string, compact bool, o Opts) string {
37− const charm = " Charm™"
43+ charm := "Charm™"
39 fg := func(c color.Color, s string) string {
48 fg := func(c color.Color, s string) string {
40 return lipgloss.NewStyle().Foreground(c).Render(s)
49 return lipgloss.NewStyle().Foreground(c).Render(s)
@@ -42,18 +51,39 @@ func Render(base lipgloss.Style, version string, compact bool, o Opts) string {
44 const spacing = 1
53 const spacing = 1
54+ var hyperLetterforms []letterform
56+ hyperLetterforms = []letterform{
45− letterforms := []letterform{
64+ crushLetterforms := []letterform{
49 LetterSAlt,
68 LetterSAlt,
71+ if o.Hyper && !compact {
72+ crushLetterforms = append(hyperLetterforms, crushLetterforms...)
52 stretchIndex := -1 // -1 means no stretching.
75 stretchIndex := -1 // -1 means no stretching.
53− if !compact {
76+ if !compact && !o.Unstable {
77+ // Always stretch the same letterform, which is picked once at random.
54− stretchIndex = cachedRandN(len(letterforms))
78+ stretchIndex = cachedRandN(len(crushLetterforms))
79+ } else if !compact && o.Unstable {
80+ // Stretch a random letterform on every render.
81+ stretchIndex = rand.IntN(len(crushLetterforms))
83+ crush := renderWord(spacing, stretchIndex, crushLetterforms...)
84+ if o.Hyper && compact {
85+ crush = renderWord(spacing, stretchIndex, hyperLetterforms...) + "\n" + crush
56− crush := renderWord(spacing, stretchIndex, letterforms...)
57 crushWidth := lipgloss.Width(crush)
87 crushWidth := lipgloss.Width(crush)
58 b := new(strings.Builder)
88 b := new(strings.Builder)
59 for r := range strings.SplitSeq(crush, "\n") {
89 for r := range strings.SplitSeq(crush, "\n") {
@@ -65,13 +95,16 @@ func Render(base lipgloss.Style, version string, compact bool, o Opts) string {
65 metaRowGap := 1
95 metaRowGap := 1
66 maxVersionWidth := crushWidth - lipgloss.Width(charm) - metaRowGap
96 maxVersionWidth := crushWidth - lipgloss.Width(charm) - metaRowGap
67 version = ansi.Truncate(version, maxVersionWidth, "…") // truncate version if too long.
97 version = ansi.Truncate(version, maxVersionWidth, "…") // truncate version if too long.
98+ if o.Hyper && compact {
68 gap := max(0, crushWidth-lipgloss.Width(charm)-lipgloss.Width(version))
101 gap := max(0, crushWidth-lipgloss.Width(charm)-lipgloss.Width(version))
69 metaRow := fg(o.CharmColor, charm) + strings.Repeat(" ", gap) + fg(o.VersionColor, version)
102 metaRow := fg(o.CharmColor, charm) + strings.Repeat(" ", gap) + fg(o.VersionColor, version)
71 // Join the meta row and big Crush title.
104 // Join the meta row and big Crush title.
72 crush = strings.TrimSpace(metaRow + "\n" + crush)
105 crush = strings.TrimSpace(metaRow + "\n" + crush)
74− // Narrow version.
107+ // Narrow version. If this is Hypercrush, this is also a stacked version.
75 if compact {
108 if compact {
76 field := fg(o.FieldColor, strings.Repeat(diag, crushWidth))
109 field := fg(o.FieldColor, strings.Repeat(diag, crushWidth))
77 return strings.Join([]string{field, field, crush, field, ""}, "\n")
110 return strings.Join([]string{field, field, crush, field, ""}, "\n")