clone

fifteen puzzle games for the terminal, with daily puzzles, weekly ladders and xp, built on bubble tea

chore/regenerate-vhs-gifs
puzzletea / theme / preview.go
preview.go 3.4K
1package theme23import (4 "image/color"5 "strings"67 "charm.land/lipgloss/v2"8)910const (11 swatchW = 612 previewW = 2813)1415// PreviewPanel renders a visual color preview for the named theme.16// It shows labeled swatches for the 16 ANSI colors, background/foreground,17// and the semantic roles.18func PreviewPanel(themeName string, height int) string {19 var p Palette20 if themeName == "" || strings.EqualFold(themeName, DefaultThemeName) {21 p = defaultPalette()22 } else if t := LookupTheme(themeName); t != nil {23 p = t.Palette()24 } else {25 p = defaultPalette()26 }2728 title := lipgloss.NewStyle().Bold(true).Foreground(p.Accent)29 dim := lipgloss.NewStyle().Foreground(p.TextDim)30 label := lipgloss.NewStyle().Foreground(p.FG)3132 var lines []string3334 lines = append(lines, title.Render(themeName))35 lines = append(lines, "")3637 // Background + foreground sample text.38 sample := lipgloss.NewStyle().39 Background(p.BG).Foreground(p.FG).40 Width(previewW).Padding(0, 1).41 Render("The quick brown fox")42 lines = append(lines, sample)43 lines = append(lines, "")4445 // Semantic color swatches: two per row to keep labels readable.46 lines = append(lines, dim.Render("FG Tokens"))47 lines = append(lines, swatchPair(p, "Accent", p.Accent, "Soft", p.AccentSoft))48 lines = append(lines, swatchPair(p, "OK", p.Success, "Error", p.Error))49 lines = append(lines, swatchPair(p, "Info", p.Info, "Given", p.Given))50 lines = append(lines, swatchPair(p, "Linked", p.Linked, "2nd", p.Secondary))51 lines = append(lines, "")52 lines = append(lines, dim.Render("Derived BGs"))53 lines = append(lines, swatchPair(p, "Cursor", p.AccentBG, "Solved", p.SuccessBG))54 lines = append(lines, swatchPair(p, "Error", p.ErrorBG, "Select", p.SelectionBG))55 lines = append(lines, "")5657 // ANSI 16-color grid: 8 per row, compact 3-char swatches.58 lines = append(lines, dim.Render("ANSI Palette"))59 lines = append(lines, ansiRow(p.ANSI[0:8]))60 lines = append(lines, ansiRow(p.ANSI[8:16]))61 lines = append(lines, "")6263 // Game state previews.64 lines = append(lines, dim.Render("Game States"))65 lines = append(lines, lipgloss.NewStyle().66 Background(p.AccentBG).Foreground(p.AccentText).Bold(true).67 Width(previewW).Padding(0, 1).68 Render("Cursor"))69 lines = append(lines, lipgloss.NewStyle().70 Background(p.SuccessBG).Foreground(p.SolvedFG).Bold(true).71 Width(previewW).Padding(0, 1).72 Render("Solved"))73 lines = append(lines, lipgloss.NewStyle().74 Background(p.ErrorBG).Foreground(p.Error).Bold(true).75 Width(previewW).Padding(0, 1).76 Render("Conflict"))7778 _ = label // reserved for future use7980 // Truncate to available height.81 if height > 0 && len(lines) > height {82 lines = lines[:height]83 }8485 return lipgloss.JoinVertical(lipgloss.Left, lines...)86}8788// swatchPair renders two labeled color swatches side by side on two lines.89func swatchPair(p Palette, lbl1 string, c1 color.Color, lbl2 string, c2 color.Color) string {90 sw := func(c color.Color) string {91 return lipgloss.NewStyle().Background(c).Width(swatchW).Render(strings.Repeat(" ", swatchW))92 }93 lb := func(s string) string {94 return lipgloss.NewStyle().Foreground(p.TextDim).Width(swatchW).Render(s)95 }96 gap := " "97 return sw(c1) + gap + sw(c2) + "\n" + lb(lbl1) + gap + lb(lbl2)98}99100// ansiRow renders 8 color swatches in a single compact row.101func ansiRow(colors []color.Color) string {102 var cells []string103 for _, c := range colors {104 cells = append(cells, lipgloss.NewStyle().Background(c).Render(" "))105 }106 return strings.Join(cells, "")107}