diff --git a/pkg/models/coffee.go b/pkg/models/coffee.go index 0d5a350..e992e1b 100644 --- a/pkg/models/coffee.go +++ b/pkg/models/coffee.go @@ -31,5 +31,4 @@ var AccountMenuItems = []string{ "order history", "faq", "about", - "something", } diff --git a/pkg/tui/model.go b/pkg/tui/model.go index a6ebeff..b6e3495 100644 --- a/pkg/tui/model.go +++ b/pkg/tui/model.go @@ -38,6 +38,7 @@ const ( paymentPage confirmPage accountPage + splashPage ) // This msg is sent after a debounce delay to apply a pending resize. @@ -117,6 +118,11 @@ type Model struct { FAQs []FAQ // loaded from embedded faq.json FaqFocused bool // true when FAQ detail is focused + // Splash screen + splashDataReady bool // true when products have loaded + splashDelayDone bool // true when minimum display time has elapsed + splashCursor bool // toggles for blinking cursor animation + } // SwitchPage changed the active page and marks that a switch happened @@ -217,7 +223,16 @@ func (m Model) fetchProductsCmd() tea.Msg { func (m Model) Init() tea.Cmd { // Fetch products and cart from API on startup, and schedule token refresh - cmds := []tea.Cmd{m.fetchProductsCmd, m.fetchCartCmd} + cmds := []tea.Cmd { + m.fetchProductsCmd, + m.fetchCartCmd, + tea.Tick(1500*time.Millisecond, func(t time.Time) tea.Msg { + return DelayCompleteMsg{} + }), + tea.Tick(700*time.Microsecond, func(t time.Time) tea.Msg { + return splashCursorTickMsg{} + }), + } if m.User != nil && m.AuthFingerprintKey != "" { cmds = append(cmds, m.scheduleTokenRefreshCmd()) } @@ -233,7 +248,6 @@ func (m Model) resetPageState() Model { return m } - type StripeTokenMsg struct { TokenID string Last4 string @@ -642,6 +656,7 @@ func (m Model) GetCartItemsSlice() []*models.CartItem { func NewModel(username string) Model { m := Model{ Username: username, + currentPage: splashPage, Coffees: []models.Coffee{ { Name: "Espresso", diff --git a/pkg/tui/splash.go b/pkg/tui/splash.go new file mode 100644 index 0000000..d045c65 --- /dev/null +++ b/pkg/tui/splash.go @@ -0,0 +1,34 @@ +package tui + +import ( + "github.com/charmbracelet/lipgloss" +) + +type DelayCompleteMsg struct{} +type splashCursorTickMsg struct{} + +func (m Model) SplashView() string { + accent := lipgloss.NewStyle().Foreground(lipgloss.Color("#AAAAAA")).Bold(true) + + var cursor string + if m.splashCursor { + cursor = lipgloss.NewStyle().Background(lipgloss.Color("#4682B4")).Render(" ") + } else { + cursor = " " + } + + logo := accent.Render("terminal coffe") + cursor + + content := lipgloss.JoinVertical( + lipgloss.Center, + logo, + ) + + return lipgloss.Place( + m.viewportWidth, + m.viewportHeight, + lipgloss.Center, + lipgloss.Center, + content, + ) +} diff --git a/pkg/tui/update.go b/pkg/tui/update.go index 58c133e..4a7d573 100644 --- a/pkg/tui/update.go +++ b/pkg/tui/update.go @@ -47,6 +47,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.Coffees = msg.Products m.ErrorMsg = "" } + m.splashDataReady = true + if m.currentPage == splashPage && m.splashDelayDone { + return m.SwitchPage(shopPage), nil + } return m, nil case CartFetchedMsg: if msg.Err == nil && msg.Cart != nil { @@ -88,6 +92,17 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.ErrorMsg = fmt.Sprintf("failed to delete card: %v", msg.Err) } return m, nil + case DelayCompleteMsg: + m.splashDelayDone = true + if m.currentPage == splashPage && m.splashDataReady { + return m.SwitchPage(shopPage), nil + } + return m, nil + case splashCursorTickMsg: + m.splashCursor = !m.splashCursor + return m, tea.Tick(700*time.Millisecond, func(t time.Time) tea.Msg { + return splashCursorTickMsg{} + }) case tea.KeyMsg: // If a form is active, skip global keys — let the page handler own input if m.ShippingForm != nil || m.PaymentForm != nil { diff --git a/pkg/tui/views.go b/pkg/tui/views.go index 2ae8858..4436f49 100644 --- a/pkg/tui/views.go +++ b/pkg/tui/views.go @@ -32,6 +32,10 @@ func (m Model) View() string { return m.ResizeView() } + if m.currentPage == splashPage { + return m.SplashView() + } + // If the menu modal is showing, render it full-screen (bypass container) if m.ShowingMenu { return m.BuildMenuView()