ai cooking
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add seasonal color scheme support to Tailwind configuration

Co-authored-by: paulgmiller <1474379+paulgmiller@users.noreply.github.com>

+287 -17
+3
cmd/careme/web.go
··· 8 8 "careme/internal/logs" 9 9 "careme/internal/logsink" 10 10 "careme/internal/recipes" 11 + "careme/internal/seasons" 11 12 "careme/internal/templates" 12 13 "careme/internal/users" 13 14 "context" ··· 77 78 data := struct { 78 79 ClarityScript template.HTML 79 80 User *users.User 81 + Colors seasons.ColorScheme 80 82 }{ 81 83 ClarityScript: clarityScript, 82 84 User: currentUser, 85 + Colors: seasons.GetCurrentColorScheme(), 83 86 } 84 87 if err := templates.Home.Execute(w, data); err != nil { 85 88 slog.ErrorContext(ctx, "home template execute error", "error", err)
+3
internal/locations/locations.go
··· 4 4 "careme/internal/config" 5 5 "careme/internal/html" 6 6 "careme/internal/kroger" 7 + "careme/internal/seasons" 7 8 "careme/internal/templates" 8 9 "context" 9 10 "fmt" ··· 136 137 Locations []Location 137 138 Zip string 138 139 ClarityScript template.HTML 140 + Colors seasons.ColorScheme 139 141 }{ 140 142 Locations: locs, 141 143 Zip: zip, 142 144 ClarityScript: l.clarity, 145 + Colors: seasons.GetCurrentColorScheme(), 143 146 } 144 147 if err := templates.Location.Execute(w, data); err != nil { 145 148 http.Error(w, "template error", http.StatusInternalServerError)
+3
internal/recipes/html.go
··· 4 4 "careme/internal/ai" 5 5 "careme/internal/html" 6 6 "careme/internal/locations" 7 + "careme/internal/seasons" 7 8 "careme/internal/templates" 8 9 "context" 9 10 "encoding/json" ··· 56 57 Hash string 57 58 Recipes []ai.Recipe 58 59 ConversationID string 60 + Colors seasons.ColorScheme 59 61 }{ 60 62 Location: *p.Location, 61 63 Date: p.Date.Format("2006-01-02"), ··· 64 66 Hash: p.Hash(), 65 67 Recipes: l.Recipes, 66 68 ConversationID: l.ConversationID, 69 + Colors: seasons.GetCurrentColorScheme(), 67 70 } 68 71 69 72 return templates.Recipe.Execute(writer, data)
+3
internal/recipes/server.go
··· 14 14 "careme/internal/config" 15 15 "careme/internal/kroger" 16 16 "careme/internal/locations" 17 + "careme/internal/seasons" 17 18 "careme/internal/templates" 18 19 "careme/internal/users" 19 20 ) ··· 214 215 w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate") 215 216 spinnerData := struct { 216 217 ClarityScript template.HTML 218 + Colors seasons.ColorScheme 217 219 }{ 218 220 ClarityScript: s.clarityScript, 221 + Colors: seasons.GetCurrentColorScheme(), 219 222 } 220 223 if err := s.spinnerTmpl.Execute(w, spinnerData); err != nil { 221 224 slog.ErrorContext(ctx, "home template execute error", "error", err)
+125
internal/seasons/seasons.go
··· 1 + package seasons 2 + 3 + import "time" 4 + 5 + // Season represents a season of the year 6 + type Season string 7 + 8 + const ( 9 + Fall Season = "fall" 10 + Winter Season = "winter" 11 + Spring Season = "spring" 12 + Summer Season = "summer" 13 + ) 14 + 15 + // ColorScheme represents a Tailwind color palette for a season 16 + type ColorScheme struct { 17 + C50 string 18 + C100 string 19 + C200 string 20 + C300 string 21 + C400 string 22 + C500 string 23 + C600 string 24 + C700 string 25 + C800 string 26 + C900 string 27 + } 28 + 29 + // GetSeason determines the season based on the month 30 + func GetSeason(t time.Time) Season { 31 + month := t.Month() 32 + 33 + // Fall: September, October, November 34 + if month >= time.September && month <= time.November { 35 + return Fall 36 + } 37 + 38 + // Winter: December, January, February 39 + if month == time.December || month <= time.February { 40 + return Winter 41 + } 42 + 43 + // Spring: March, April, May 44 + if month >= time.March && month <= time.May { 45 + return Spring 46 + } 47 + 48 + // Summer: June, July, August 49 + return Summer 50 + } 51 + 52 + // GetColorScheme returns the appropriate color scheme for the given season 53 + func GetColorScheme(season Season) ColorScheme { 54 + switch season { 55 + case Fall: 56 + // Orange colors - leaf falling orange 57 + return ColorScheme{ 58 + C50: "#fff7ed", 59 + C100: "#ffedd5", 60 + C200: "#fed7aa", 61 + C300: "#fdba74", 62 + C400: "#fb923c", 63 + C500: "#f97316", 64 + C600: "#ea580c", 65 + C700: "#c2410c", 66 + C800: "#9a3412", 67 + C900: "#7c2d12", 68 + } 69 + case Winter: 70 + // Blue/white colors - snow/ice white 71 + return ColorScheme{ 72 + C50: "#f0f9ff", 73 + C100: "#e0f2fe", 74 + C200: "#bae6fd", 75 + C300: "#7dd3fc", 76 + C400: "#38bdf8", 77 + C500: "#0ea5e9", 78 + C600: "#0284c7", 79 + C700: "#0369a1", 80 + C800: "#075985", 81 + C900: "#0c4a6e", 82 + } 83 + case Spring: 84 + // Green colors - growing plant green 85 + return ColorScheme{ 86 + C50: "#f0fdf4", 87 + C100: "#dcfce7", 88 + C200: "#bbf7d0", 89 + C300: "#86efac", 90 + C400: "#4ade80", 91 + C500: "#22c55e", 92 + C600: "#16a34a", 93 + C700: "#15803d", 94 + C800: "#166534", 95 + C900: "#14532d", 96 + } 97 + case Summer: 98 + // Yellow/golden colors - sunshine and ripe fruits 99 + return ColorScheme{ 100 + C50: "#fefce8", 101 + C100: "#fef9c3", 102 + C200: "#fef08a", 103 + C300: "#fde047", 104 + C400: "#facc15", 105 + C500: "#eab308", 106 + C600: "#ca8a04", 107 + C700: "#a16207", 108 + C800: "#854d0e", 109 + C900: "#713f12", 110 + } 111 + default: 112 + // Default to fall colors 113 + return GetColorScheme(Fall) 114 + } 115 + } 116 + 117 + // GetCurrentSeason returns the current season based on the current time 118 + func GetCurrentSeason() Season { 119 + return GetSeason(time.Now()) 120 + } 121 + 122 + // GetCurrentColorScheme returns the color scheme for the current season 123 + func GetCurrentColorScheme() ColorScheme { 124 + return GetColorScheme(GetCurrentSeason()) 125 + }
+130
internal/seasons/seasons_test.go
··· 1 + package seasons 2 + 3 + import ( 4 + "testing" 5 + "time" 6 + ) 7 + 8 + func TestGetSeason(t *testing.T) { 9 + tests := []struct { 10 + name string 11 + date time.Time 12 + expected Season 13 + }{ 14 + { 15 + name: "September is Fall", 16 + date: time.Date(2024, time.September, 15, 0, 0, 0, 0, time.UTC), 17 + expected: Fall, 18 + }, 19 + { 20 + name: "October is Fall", 21 + date: time.Date(2024, time.October, 15, 0, 0, 0, 0, time.UTC), 22 + expected: Fall, 23 + }, 24 + { 25 + name: "November is Fall", 26 + date: time.Date(2024, time.November, 15, 0, 0, 0, 0, time.UTC), 27 + expected: Fall, 28 + }, 29 + { 30 + name: "December is Winter", 31 + date: time.Date(2024, time.December, 15, 0, 0, 0, 0, time.UTC), 32 + expected: Winter, 33 + }, 34 + { 35 + name: "January is Winter", 36 + date: time.Date(2024, time.January, 15, 0, 0, 0, 0, time.UTC), 37 + expected: Winter, 38 + }, 39 + { 40 + name: "February is Winter", 41 + date: time.Date(2024, time.February, 15, 0, 0, 0, 0, time.UTC), 42 + expected: Winter, 43 + }, 44 + { 45 + name: "March is Spring", 46 + date: time.Date(2024, time.March, 15, 0, 0, 0, 0, time.UTC), 47 + expected: Spring, 48 + }, 49 + { 50 + name: "April is Spring", 51 + date: time.Date(2024, time.April, 15, 0, 0, 0, 0, time.UTC), 52 + expected: Spring, 53 + }, 54 + { 55 + name: "May is Spring", 56 + date: time.Date(2024, time.May, 15, 0, 0, 0, 0, time.UTC), 57 + expected: Spring, 58 + }, 59 + { 60 + name: "June is Summer", 61 + date: time.Date(2024, time.June, 15, 0, 0, 0, 0, time.UTC), 62 + expected: Summer, 63 + }, 64 + { 65 + name: "July is Summer", 66 + date: time.Date(2024, time.July, 15, 0, 0, 0, 0, time.UTC), 67 + expected: Summer, 68 + }, 69 + { 70 + name: "August is Summer", 71 + date: time.Date(2024, time.August, 15, 0, 0, 0, 0, time.UTC), 72 + expected: Summer, 73 + }, 74 + } 75 + 76 + for _, tt := range tests { 77 + t.Run(tt.name, func(t *testing.T) { 78 + result := GetSeason(tt.date) 79 + if result != tt.expected { 80 + t.Errorf("GetSeason(%v) = %v, want %v", tt.date, result, tt.expected) 81 + } 82 + }) 83 + } 84 + } 85 + 86 + func TestGetColorScheme(t *testing.T) { 87 + tests := []struct { 88 + name string 89 + season Season 90 + }{ 91 + {name: "Fall colors", season: Fall}, 92 + {name: "Winter colors", season: Winter}, 93 + {name: "Spring colors", season: Spring}, 94 + {name: "Summer colors", season: Summer}, 95 + } 96 + 97 + for _, tt := range tests { 98 + t.Run(tt.name, func(t *testing.T) { 99 + colors := GetColorScheme(tt.season) 100 + // Just verify we got non-empty colors 101 + if colors.C50 == "" || colors.C500 == "" || colors.C900 == "" { 102 + t.Errorf("GetColorScheme(%v) returned empty colors", tt.season) 103 + } 104 + }) 105 + } 106 + } 107 + 108 + func TestGetCurrentSeason(t *testing.T) { 109 + // Just verify it returns a valid season 110 + season := GetCurrentSeason() 111 + validSeasons := []Season{Fall, Winter, Spring, Summer} 112 + valid := false 113 + for _, s := range validSeasons { 114 + if season == s { 115 + valid = true 116 + break 117 + } 118 + } 119 + if !valid { 120 + t.Errorf("GetCurrentSeason() returned invalid season: %v", season) 121 + } 122 + } 123 + 124 + func TestGetCurrentColorScheme(t *testing.T) { 125 + // Just verify it returns non-empty colors 126 + colors := GetCurrentColorScheme() 127 + if colors.C50 == "" || colors.C500 == "" || colors.C900 == "" { 128 + t.Errorf("GetCurrentColorScheme() returned empty colors") 129 + } 130 + }
+1 -1
internal/templates/chat.html
··· 20 20 <meta name="twitter:image" content="https://careme.cooking/favicon.ico" /> 21 21 {{end}} 22 22 23 - {{template "tailwind_head"}} 23 + {{template "tailwind_head" .}} 24 24 25 25 {{.ClarityScript}} 26 26 </head>
+1 -1
internal/templates/home.html
··· 5 5 <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 6 <title>Careme</title> 7 7 8 - {{template "tailwind_head"}} 8 + {{template "tailwind_head" .}} 9 9 10 10 {{.ClarityScript}} 11 11 </head>
+1 -1
internal/templates/locations.html
··· 5 5 <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 6 <title>Careme Locations</title> 7 7 8 - {{template "tailwind_head"}} 8 + {{template "tailwind_head" .}} 9 9 10 10 {{.ClarityScript}} 11 11 </head>
+1 -1
internal/templates/spinner.html
··· 8 8 <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" /> 9 9 <meta http-equiv="Pragma" content="no-cache" /> 10 10 11 - {{template "tailwind_head"}} 11 + {{template "tailwind_head" .}} 12 12 13 13 {{.ClarityScript}} 14 14 </head>
+10 -10
internal/templates/tailwind_head.html
··· 6 6 extend: { 7 7 colors: { 8 8 brand: { 9 - 50: '#fff7ed', 10 - 100: '#ffedd5', 11 - 200: '#fed7aa', 12 - 300: '#fdba74', 13 - 400: '#fb923c', 14 - 500: '#f97316', 15 - 600: '#ea580c', 16 - 700: '#c2410c', 17 - 800: '#9a3412', 18 - 900: '#7c2d12' 9 + 50: '{{.Colors.C50}}', 10 + 100: '{{.Colors.C100}}', 11 + 200: '{{.Colors.C200}}', 12 + 300: '{{.Colors.C300}}', 13 + 400: '{{.Colors.C400}}', 14 + 500: '{{.Colors.C500}}', 15 + 600: '{{.Colors.C600}}', 16 + 700: '{{.Colors.C700}}', 17 + 800: '{{.Colors.C800}}', 18 + 900: '{{.Colors.C900}}' 19 19 } 20 20 } 21 21 }
+1 -1
internal/templates/user.html
··· 5 5 <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 6 <title>Careme · User Profile</title> 7 7 8 - {{template "tailwind_head"}} 8 + {{template "tailwind_head" .}} 9 9 10 10 {{.ClarityScript}} 11 11 </head>
+5 -2
internal/users/server.go
··· 2 2 3 3 import ( 4 4 "careme/internal/locations" 5 + "careme/internal/seasons" 5 6 "careme/internal/templates" 6 7 "context" 7 8 "errors" ··· 110 111 http.Error(w, "invalid form submission", http.StatusBadRequest) 111 112 return 112 113 } 113 - 114 + 114 115 // Only update favorite_store if provided 115 116 if favoriteStore := strings.TrimSpace(r.FormValue("favorite_store")); favoriteStore != "" || r.Form.Has("favorite_store") { 116 117 currentUser.FavoriteStore = favoriteStore 117 118 } 118 - 119 + 119 120 // Only update shopping_day if provided 120 121 if shoppingDay := strings.TrimSpace(r.FormValue("shopping_day")); shoppingDay != "" { 121 122 currentUser.ShoppingDay = shoppingDay ··· 146 147 User *User 147 148 Success bool 148 149 FavoriteStoreName string 150 + Colors seasons.ColorScheme 149 151 }{ 150 152 ClarityScript: s.clarityScript, 151 153 User: currentUser, 152 154 Success: success, 153 155 FavoriteStoreName: favoriteStoreName, 156 + Colors: seasons.GetCurrentColorScheme(), 154 157 } 155 158 if err := s.userTmpl.Execute(w, data); err != nil { 156 159 slog.ErrorContext(ctx, "user template execute error", "error", err)