Files
MuyueWorkspace/internal/tui/animations.go
Augustin ROUX cb8e3d0d26
Some checks failed
Stable Release / stable (push) Failing after 22s
feat: complete TUI redesign with cyberpunk theme (#1)
- Dark theme with red accents (cyberpunk aesthetic)
- Epuré cyberpunk style: clean dark backgrounds, sharp red highlights
- Full cyberpunk animations: glitch effect, scan line, typewriter
- Mixed Unicode + ASCII icons
- Rounded borders (╭ ╮ ╯ ╰) on cards and panels
- ASCII art block titles (■) with red styling
- Header: MUYUE branding, status indicators, live clock
- Footer: shortcuts, version, update indicator
- Tab transitions: glitch → scan → typewriter sequence
- Extracted header.go, footer.go, animations.go as new files

Controls unchanged: ctrl+t tabs, ctrl+s sidebar, ctrl+a AI panel

file changes:
- styles.go: new color palette (cyberRed, bgVoid, dimRed), block titles
- types.go: added transition state, clock tick, glitch/scan/done messages
- animations.go: new file with glitch, scan, typewriter, hex stream effects
- header.go: new file with logo, tabs, status dots, live clock
- footer.go: new file with shortcuts, version, update indicator
- model.go: integrated transition state machine, clock updates
- dashboard.go, studio.go, terminal.go, config_tab.go: updated icons/styles

Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>

Co-authored-by: Augustin <muyue@legion-muyue.fr>
Reviewed-on: #1
2026-04-20 20:17:06 +00:00

116 lines
2.7 KiB
Go

package tui
import (
"fmt"
"math/rand"
"strings"
"time"
"github.com/charmbracelet/lipgloss"
)
var glitchChars = "!@#$%^&*()_+-=[]{}|;':,./<>?~`"
func init() {
rand.Seed(time.Now().UnixNano())
}
func randomGlitchChar() string {
return string(glitchChars[rand.Intn(len(glitchChars))])
}
func glitchText(text string, intensity int) string {
runes := []rune(text)
for i := 0; i < intensity; i++ {
pos := rand.Intn(len(runes))
if runes[pos] != ' ' && runes[pos] != '\n' {
runes[pos] = []rune(randomGlitchChar())[0]
}
}
return string(runes)
}
func generateScanLine(width int, frame int) string {
pos := frame % width
line := strings.Repeat(" ", pos) +
lipgloss.NewStyle().Foreground(cyberRed).Bold(true).Render(strings.Repeat("━", min(20, width-pos)))
if pos+20 < width {
line += strings.Repeat(" ", width-pos-20)
}
return line[:min(width, len(line))]
}
func typewriterRender(text string, pos int) string {
if pos >= len(text) {
return text
}
if pos <= 0 {
return ""
}
shown := text[:pos]
cursor := lipgloss.NewStyle().Foreground(cyberRed).Bold(true).Render("█")
return shown + cursor
}
func renderGlitchEffect(width, height, frame int) string {
var b strings.Builder
for y := 0; y < height; y++ {
line := ""
for x := 0; x < width; x++ {
if rand.Float64() < 0.15 {
c := randomGlitchChar()
style := lipgloss.NewStyle()
r := rand.Float64()
if r < 0.4 {
style = style.Foreground(cyberRed)
} else if r < 0.7 {
style = style.Foreground(cyberPink)
} else {
style = style.Foreground(textMuted)
}
line += style.Render(c)
} else {
line += " "
}
}
b.WriteString(line)
if y < height-1 {
b.WriteString("\n")
}
}
return b.String()
}
func renderScanEffect(width, height, frame int) string {
var b strings.Builder
scanY := frame % (height + 10)
for y := 0; y < height; y++ {
if y == scanY || y == scanY+1 {
b.WriteString(lipgloss.NewStyle().Foreground(cyberRed).Render(strings.Repeat("━", width)))
} else if y == scanY-1 || y == scanY+2 {
b.WriteString(lipgloss.NewStyle().Foreground(dimRed).Render(strings.Repeat("─", width)))
} else if y < scanY {
b.WriteString(lipgloss.NewStyle().Foreground(dimRed).Render(fmt.Sprintf("%*s", width, "")))
} else {
b.WriteString(strings.Repeat(" ", width))
}
if y < height-1 {
b.WriteString("\n")
}
}
return b.String()
}
func generateHexStream(width, lines int) string {
var b strings.Builder
for y := 0; y < lines; y++ {
for x := 0; x < width/3; x++ {
b.WriteString(fmt.Sprintf("%02X", rand.Intn(256)))
}
if y < lines-1 {
b.WriteString("\n")
}
}
return lipgloss.NewStyle().Foreground(dimRed).Render(b.String())
}