Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 99 additions & 10 deletions cmd/tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,13 @@ var (
lipgloss.NewStyle().Foreground(lipgloss.Color("34")).Bold(true),
lipgloss.NewStyle().Foreground(lipgloss.Color("28")).Bold(true),
}
styleMascotNormal = lipgloss.NewStyle().Foreground(lipgloss.Color("#fab387"))
styleMascotConflict = lipgloss.NewStyle().Foreground(lipgloss.Color("1")).Bold(true)
styleMascotMissing = lipgloss.NewStyle().Foreground(lipgloss.Color("3"))
styleMascotNormal = lipgloss.NewStyle().Foreground(lipgloss.Color("#fab387"))
styleMascotConflict = lipgloss.NewStyle().Foreground(lipgloss.Color("1")).Bold(true)
styleMascotMissing = lipgloss.NewStyle().Foreground(lipgloss.Color("3"))
styleMascotJellyNormal = lipgloss.NewStyle().Foreground(lipgloss.Color("14"))
styleMascotTentBlue = lipgloss.NewStyle().Foreground(lipgloss.Color("12")).Bold(true)
styleMascotTentRed = lipgloss.NewStyle().Foreground(lipgloss.Color("9")).Bold(true)
styleMascotRobotNormal = lipgloss.NewStyle().Foreground(lipgloss.Color("#ff6188")).Bold(true)
)

// ── pkg status ────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -145,8 +149,16 @@ const (
mascotMissing // no packages / no git repo
)

// mascotFrames[state][frame][line] — each line is exactly 8 visual columns.
var mascotFrames = [3][3][6]string{
type mascotCharacter int

const (
mascotJellyfish mascotCharacter = iota // default; zero value = jellyfish on startup
mascotMonkey
mascotRobot
)

// monkeyFrames[state][frame][line] — each line is exactly 8 visual columns.
var monkeyFrames = [3][3][6]string{
// mascotNormal: peach, blink (frame 1) + grin (frame 2)
{
{` ▄▄▄▄ `, `▐(o o)▌`, `▐( ▄▄ )▌`, `▐( ~~ )▌`, ` ▀( )▀ `, ` ██ `},
Expand All @@ -167,6 +179,64 @@ var mascotFrames = [3][3][6]string{
},
}

// jellyfishFrames[state][frame][line] — each line is exactly 8 visual columns.
// Tentacle lines (index 4-5) use "|" (ASCII pipe U+007C) as a colour-split marker:
// left 4 cols = bright blue (styleMascotTentBlue), right 4 cols = bright red (styleMascotTentRed).
// Art uses │ (U+2502 box-drawing vertical) so there is no collision with the "|" marker.
var jellyfishFrames = [3][3][6]string{
// mascotNormal: cyan bell, blink (frame 1) + grin (frame 2), tentacles wave A-B-A
{
{` ▄████▄ `, `▐ o o ▌`, `▐ ~~ ▌`, ` ▀████▀ `, `│╷│╷|║╿║╿`, `╵ ╵ |╵ ╵ `},
{` ▄████▄ `, `▐ ─ ─ ▌`, `▐ ~~ ▌`, ` ▀████▀ `, `╵╷╵╷|╵╿╵╿`, `│ │ |║ ║ `},
{` ▄████▄ `, `▐ o o ▌`, `▐ ^^ ▌`, ` ▀████▀ `, `│╷│╷|║╿║╿`, `╵ ╵ |╵ ╵ `},
},
// mascotConflict: red bell expands each frame + frantic eyes, tentacles wave A-B-A
{
{` ▄████▄ `, `▐ > < ▌`, `▐ !! ▌`, ` ▀████▀ `, `│╷│╷|║╿║╿`, `╵ ╵ |╵ ╵ `},
{`▄██████▄`, `▐ X X ▌`, `▐ ## ▌`, ` ▀████▀ `, `╵╷╵╷|╵╿╵╿`, `│ │ |║ ║ `},
{`████████`, `▐ * * ▌`, `▐ >> ▌`, ` ▀████▀ `, `│╷│╷|║╿║╿`, `╵ ╵ |╵ ╵ `},
},
// mascotMissing: yellow bell, eyes dart side-to-side, tentacles wave B-A-B
{
{` ▄████▄ `, `▐ o . ▌`, `▐ ?? ▌`, ` ▀████▀ `, `╵╷╵╷|╵╿╵╿`, `│ │ |║ ║ `},
{` ▄████▄ `, `▐ . . ▌`, `▐ ?? ▌`, ` ▀████▀ `, `│╷│╷|║╿║╿`, `╵ ╵ |╵ ╵ `},
{` ▄████▄ `, `▐ . o ▌`, `▐ ?? ▌`, ` ▀████▀ `, `╵╷╵╷|╵╿╵╿`, `│ │ |║ ║ `},
},
}

// robotFrames[state][frame][line] — each line is exactly 8 visual columns.
// Blocky square-faced robot from new-mascot; eyes vary per frame/state.
var robotFrames = [3][3][6]string{
// mascotNormal: pink, eyes blink
{
{`████████`, `█ █`, `█ ▀ ▄ █`, `█ █`, `██ ██`, ` ██████ `},
{`████████`, `█ █`, `█ ─ ─ █`, `█ █`, `██ ██`, ` ██████ `},
{`████████`, `█ █`, `█ ▄ ▀ █`, `█ █`, `██ ██`, ` ██████ `},
},
// mascotConflict: frantic eyes
{
{`████████`, `█ █`, `█ > < █`, `█ █`, `██ ██`, ` ██████ `},
{`████████`, `█ █`, `█ X X █`, `█ █`, `██ ██`, ` ██████ `},
{`████████`, `█ █`, `█ * * █`, `█ █`, `██ ██`, ` ██████ `},
},
// mascotMissing: eyes dart side-to-side
{
{`████████`, `█ █`, `█ o . █`, `█ █`, `██ ██`, ` ██████ `},
{`████████`, `█ █`, `█ . . █`, `█ █`, `██ ██`, ` ██████ `},
{`████████`, `█ █`, `█ . o █`, `█ █`, `██ ██`, ` ██████ `},
},
}

// renderMascotLine colours one mascot line. If the line contains a "|" (ASCII pipe U+007C)
// split marker, the left half is rendered in bright blue and the right half in bright red
// (jellyfish tentacles). Otherwise the whole line is rendered with bodyStyle.
func renderMascotLine(line string, bodyStyle lipgloss.Style) string {
if idx := strings.Index(line, "|"); idx >= 0 {
return styleMascotTentBlue.Render(line[:idx]) + styleMascotTentRed.Render(line[idx+1:])
}
return bodyStyle.Render(line)
}

// ── model types ───────────────────────────────────────────────────────────────

type pkgRow struct {
Expand Down Expand Up @@ -219,7 +289,8 @@ type model struct {
statusMsg string // inline error for editor failure etc.

width, height int
headerFrame int // incremented every 600ms for mascot animation
headerFrame int // incremented every 600ms for mascot animation
mascotChar mascotCharacter // which mascot to display; toggled with 'm'
}

// ── message types ─────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -617,7 +688,16 @@ func (m model) renderBrandHeader() string {
} else {
frame = m.headerFrame % 3
}
mascotLines := mascotFrames[state][frame]
var frames [3][3][6]string
switch m.mascotChar {
case mascotMonkey:
frames = monkeyFrames
case mascotRobot:
frames = robotFrames
default:
frames = jellyfishFrames
}
mascotLines := frames[state][frame]

var mascotStyle lipgloss.Style
switch state {
Expand All @@ -626,7 +706,14 @@ func (m model) renderBrandHeader() string {
case mascotMissing:
mascotStyle = styleMascotMissing
default:
mascotStyle = styleMascotNormal
switch m.mascotChar {
case mascotRobot:
mascotStyle = styleMascotRobotNormal
case mascotJellyfish:
mascotStyle = styleMascotJellyNormal
default:
mascotStyle = styleMascotNormal
}
}

const leftPad = 2
Expand All @@ -641,11 +728,11 @@ func (m model) renderBrandHeader() string {
b.WriteString("╭" + hLine + "╮\n")
// empty line
b.WriteString("│" + strings.Repeat(" ", innerW) + "│\n")
// 6 lines of KNOT art + monkey mascot side-by-side; pad each row individually
// 6 lines of KNOT art + mascot side-by-side; pad each row individually
// so mismatched art/mascot visual widths don't break the right border.
for i := 0; i < 6; i++ {
art := styleArt[i].Render(knotArt[i])
mascot := mascotStyle.Render(mascotLines[i])
mascot := renderMascotLine(mascotLines[i], mascotStyle)
content := " " + art + strings.Repeat(" ", gap) + mascot
rowRightPad := strings.Repeat(" ", max(innerW-lipgloss.Width(content), 0))
b.WriteString("│" + content + rowRightPad + "│\n")
Expand Down Expand Up @@ -1018,6 +1105,8 @@ func (m model) updateList(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
return m, editorCmd(m.cfgPath)
case "]":
m.activeTab = tabTags
case "m":
m.mascotChar = (m.mascotChar + 1) % 3
case "q", "ctrl+c":
return m, tea.Quit
}
Expand Down
Loading