package banking import ( "fmt" "strings" "github.com/charmbracelet/lipgloss" ) const termWidth = 80 // Color palette — green-on-black retro terminal. var ( colorGreen = lipgloss.Color("#00FF00") colorDim = lipgloss.Color("#007700") colorBlack = lipgloss.Color("#000000") colorBright = lipgloss.Color("#AAFFAA") colorRed = lipgloss.Color("#FF3333") ) // Reusable styles. var ( baseStyle = lipgloss.NewStyle(). Foreground(colorGreen). Background(colorBlack) headerStyle = lipgloss.NewStyle(). Foreground(colorBright). Background(colorBlack). Bold(true). Width(termWidth). Align(lipgloss.Center) titleStyle = lipgloss.NewStyle(). Foreground(colorGreen). Background(colorBlack). Bold(true) dimStyle = lipgloss.NewStyle(). Foreground(colorDim). Background(colorBlack) errorStyle = lipgloss.NewStyle(). Foreground(colorRed). Background(colorBlack). Bold(true) inputStyle = lipgloss.NewStyle(). Foreground(colorBright). Background(colorBlack) ) // divider returns an 80-column === line. func divider() string { return dimStyle.Render(strings.Repeat("=", termWidth)) } // thinDivider returns an 80-column --- line. func thinDivider() string { return dimStyle.Render(strings.Repeat("-", termWidth)) } // centerText centers text within 80 columns. func centerText(s string) string { return headerStyle.Render(s) } // padRight pads a string to the given width. func padRight(s string, width int) string { if len(s) >= width { return s[:width] } return s + strings.Repeat(" ", width-len(s)) } // formatCurrency formats cents as $X,XXX.XX func formatCurrency(cents int64) string { negative := cents < 0 if negative { cents = -cents } dollars := cents / 100 remainder := cents % 100 // Add thousands separators. ds := fmt.Sprintf("%d", dollars) if len(ds) > 3 { var parts []string for len(ds) > 3 { parts = append([]string{ds[len(ds)-3:]}, parts...) ds = ds[:len(ds)-3] } parts = append([]string{ds}, parts...) ds = strings.Join(parts, ",") } if negative { return fmt.Sprintf("-$%s.%02d", ds, remainder) } return fmt.Sprintf("$%s.%02d", ds, remainder) } // padLine pads a single line (which may contain ANSI codes) to termWidth // using its visual width. Padding uses a black background so the terminal's // default background doesn't bleed through. func padLine(line string) string { w := lipgloss.Width(line) if w >= termWidth { return line } return line + baseStyle.Render(strings.Repeat(" ", termWidth-w)) } // padLines pads every line in a multi-line string to termWidth so that // shorter lines fully overwrite previous content in the terminal. func padLines(s string) string { lines := strings.Split(s, "\n") for i, line := range lines { lines[i] = padLine(line) } return strings.Join(lines, "\n") } // screenFrame wraps content in the persistent header and footer. // The height parameter is used to pad the output to fill the terminal, // preventing leftover lines from previous renders bleeding through. func screenFrame(bankName, terminalID, region, content string, height int) string { var b strings.Builder // Header (4 lines). b.WriteString(divider()) b.WriteString("\n") b.WriteString(centerText(bankName + " FEDERAL RESERVE SYSTEM")) b.WriteString("\n") b.WriteString(centerText("SECURE BANKING TERMINAL")) b.WriteString("\n") b.WriteString(divider()) b.WriteString("\n") // Content. b.WriteString(content) // Pad with blank lines between content and footer so the footer // stays at the bottom and the total output fills the terminal height. if height > 0 { const headerLines = 4 const footerLines = 2 // strings.Count gives newlines; add 1 for the line after the last \n. contentLines := strings.Count(content, "\n") + 1 used := headerLines + contentLines + footerLines blankLine := baseStyle.Render(strings.Repeat(" ", termWidth)) for i := used; i < height; i++ { b.WriteString(blankLine) b.WriteString("\n") } } // Footer (2 lines). b.WriteString("\n") b.WriteString(divider()) b.WriteString("\n") footer := fmt.Sprintf(" TERMINAL: %s | REGION: %s | ENCRYPTED SESSION ACTIVE", terminalID, region) b.WriteString(dimStyle.Render(padRight(footer, termWidth))) // Pad every line to full terminal width so shorter lines overwrite // leftover content from previous renders. return padLines(b.String()) }