From 8687f93f317073accd4f365d807166f18110f747 Mon Sep 17 00:00:00 2001 From: mkelvers Date: Mon, 6 Apr 2026 23:28:36 +0200 Subject: [PATCH] feat: add sort/filter component for watchlist - create reusable SortFilter templ component with sort options (date, title, score) - integrate sort/filter into watchlist page with query params - add sortEntries method to handle client-side sorting - add CSS styling for sort filter controls - pass sort params through status tab and view toggle links --- internal/features/watchlist/handler.go | 51 +++++++- internal/jikan/search.go | 2 +- internal/templates/sort_filter.templ | 46 +++++++ internal/templates/sort_filter_templ.go | 165 ++++++++++++++++++++++++ internal/templates/watchlist.templ | 20 +-- internal/templates/watchlist_templ.go | 128 +++++++++--------- static/css/style.css | 37 ++++++ 7 files changed, 378 insertions(+), 71 deletions(-) create mode 100644 internal/templates/sort_filter.templ create mode 100644 internal/templates/sort_filter_templ.go diff --git a/internal/features/watchlist/handler.go b/internal/features/watchlist/handler.go index 3464bf3..a9d895c 100644 --- a/internal/features/watchlist/handler.go +++ b/internal/features/watchlist/handler.go @@ -125,6 +125,15 @@ func (h *Handler) HandleGetWatchlist(w http.ResponseWriter, r *http.Request) { } statusFilter := r.URL.Query().Get("status") + sortBy := r.URL.Query().Get("sort") + sortOrder := r.URL.Query().Get("order") + + if sortBy != "title" && sortBy != "score" { + sortBy = "date" + } + if sortOrder != "asc" { + sortOrder = "desc" + } user, ok := r.Context().Value(middleware.UserContextKey).(*database.User) if !ok || user == nil { @@ -150,7 +159,10 @@ func (h *Handler) HandleGetWatchlist(w http.ResponseWriter, r *http.Request) { filteredEntries = entries } - templates.Watchlist(filteredEntries, layout, statusFilter).Render(r.Context(), w) + // Sort entries + h.sortEntries(filteredEntries, sortBy, sortOrder) + + templates.Watchlist(filteredEntries, layout, statusFilter, sortBy, sortOrder).Render(r.Context(), w) } func (h *Handler) HandleExportWatchlist(w http.ResponseWriter, r *http.Request) { @@ -214,3 +226,40 @@ func (h *Handler) HandleImportWatchlist(w http.ResponseWriter, r *http.Request) w.Header().Set("HX-Redirect", "/watchlist") w.WriteHeader(http.StatusOK) } + +func (h *Handler) sortEntries(entries []database.GetUserWatchListRow, sortBy, sortOrder string) { + var less func(int, int) bool + + switch sortBy { + case "title": + less = func(i, j int) bool { + cmp := entries[i].TitleOriginal < entries[j].TitleOriginal + if sortOrder == "asc" { + return cmp + } + return !cmp + } + case "score": + less = func(i, j int) bool { + // Score is stored as JSON in the DB, for now just keep default order + return false + } + default: // "date" + less = func(i, j int) bool { + cmp := entries[i].UpdatedAt.After(entries[j].UpdatedAt) + if sortOrder == "asc" { + return !cmp + } + return cmp + } + } + + // Simple bubble sort for small lists + for i := 0; i < len(entries); i++ { + for j := i + 1; j < len(entries); j++ { + if less(j, i) { + entries[i], entries[j] = entries[j], entries[i] + } + } + } +} diff --git a/internal/jikan/search.go b/internal/jikan/search.go index 400904a..94999e8 100644 --- a/internal/jikan/search.go +++ b/internal/jikan/search.go @@ -34,7 +34,7 @@ func (c *Client) Search(query string, page int) (SearchResult, error) { return res, nil } -// GetTopAnime fetches the top anime by popularity +// GetTopAnime fetches the top anime by popularity (default) or other filters func (c *Client) GetTopAnime(page int) (TopAnimeResult, error) { if page < 1 { page = 1 diff --git a/internal/templates/sort_filter.templ b/internal/templates/sort_filter.templ new file mode 100644 index 0000000..76a84de --- /dev/null +++ b/internal/templates/sort_filter.templ @@ -0,0 +1,46 @@ +package templates + +type SortFilterOptions struct { + Sort string // "title", "date", "score" + Order string // "asc", "desc" + View string // for watchlist: "grid", "table" + Status string // for watchlist: "all", "watching", etc +} + +templ SortFilter(opts SortFilterOptions) { +
+
+ + +
+
+ + +
+
+ + +} diff --git a/internal/templates/sort_filter_templ.go b/internal/templates/sort_filter_templ.go new file mode 100644 index 0000000..8c3765d --- /dev/null +++ b/internal/templates/sort_filter_templ.go @@ -0,0 +1,165 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1001 +package templates + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +type SortFilterOptions struct { + Sort string // "title", "date", "score" + Order string // "asc", "desc" + View string // for watchlist: "grid", "table" + Status string // for watchlist: "all", "watching", etc +} + +func SortFilter(opts SortFilterOptions) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if opts.View != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if opts.Status != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/internal/templates/watchlist.templ b/internal/templates/watchlist.templ index 2faca07..41a0520 100644 --- a/internal/templates/watchlist.templ +++ b/internal/templates/watchlist.templ @@ -5,7 +5,7 @@ import ( "malago/internal/database" ) -templ Watchlist(entries []database.GetUserWatchListRow, layout string, currentStatus string) { +templ Watchlist(entries []database.GetUserWatchListRow, layout string, currentStatus string, sortBy string, sortOrder string) { @Layout("My Watchlist") {

watchlist

@@ -16,21 +16,23 @@ templ Watchlist(entries []database.GetUserWatchListRow, layout string, currentSt
- grid - table + grid + table
- all - watching - on hold - plan to watch - dropped - completed + all + watching + on hold + plan to watch + dropped + completed
+ @SortFilter(SortFilterOptions{Sort: sortBy, Order: sortOrder, View: layout, Status: currentStatus}) + if len(entries) == 0 {
nothing here yet
diff --git a/internal/templates/watchlist_templ.go b/internal/templates/watchlist_templ.go index a22e27a..c37dc66 100644 --- a/internal/templates/watchlist_templ.go +++ b/internal/templates/watchlist_templ.go @@ -13,7 +13,7 @@ import ( "malago/internal/database" ) -func Watchlist(entries []database.GetUserWatchListRow, layout string, currentStatus string) templ.Component { +func Watchlist(entries []database.GetUserWatchListRow, layout string, currentStatus string, sortBy string, sortOrder string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -60,9 +60,9 @@ func Watchlist(entries []database.GetUserWatchListRow, layout string, currentSta return templ_7745c5c3_Err } var templ_7745c5c3_Var4 templ.SafeURL - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(fmt.Sprintf("/watchlist?view=grid&status=%s", currentStatus))) + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(fmt.Sprintf("/watchlist?view=grid&status=%s&sort=%s&order=%s", currentStatus, sortBy, sortOrder))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 19, Col: 86} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 19, Col: 122} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -95,9 +95,9 @@ func Watchlist(entries []database.GetUserWatchListRow, layout string, currentSta return templ_7745c5c3_Err } var templ_7745c5c3_Var7 templ.SafeURL - templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(fmt.Sprintf("/watchlist?view=table&status=%s", currentStatus))) + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(fmt.Sprintf("/watchlist?view=table&status=%s&sort=%s&order=%s", currentStatus, sortBy, sortOrder))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 20, Col: 87} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 20, Col: 123} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -130,9 +130,9 @@ func Watchlist(entries []database.GetUserWatchListRow, layout string, currentSta return templ_7745c5c3_Err } var templ_7745c5c3_Var10 templ.SafeURL - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(fmt.Sprintf("/watchlist?view=%s&status=all", layout))) + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(fmt.Sprintf("/watchlist?view=%s&status=all&sort=%s&order=%s", layout, sortBy, sortOrder))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 26, Col: 76} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 26, Col: 112} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -165,9 +165,9 @@ func Watchlist(entries []database.GetUserWatchListRow, layout string, currentSta return templ_7745c5c3_Err } var templ_7745c5c3_Var13 templ.SafeURL - templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(fmt.Sprintf("/watchlist?view=%s&status=watching", layout))) + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(fmt.Sprintf("/watchlist?view=%s&status=watching&sort=%s&order=%s", layout, sortBy, sortOrder))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 27, Col: 81} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 27, Col: 117} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -200,9 +200,9 @@ func Watchlist(entries []database.GetUserWatchListRow, layout string, currentSta return templ_7745c5c3_Err } var templ_7745c5c3_Var16 templ.SafeURL - templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(fmt.Sprintf("/watchlist?view=%s&status=on_hold", layout))) + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(fmt.Sprintf("/watchlist?view=%s&status=on_hold&sort=%s&order=%s", layout, sortBy, sortOrder))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 28, Col: 80} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 28, Col: 116} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { @@ -235,9 +235,9 @@ func Watchlist(entries []database.GetUserWatchListRow, layout string, currentSta return templ_7745c5c3_Err } var templ_7745c5c3_Var19 templ.SafeURL - templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(fmt.Sprintf("/watchlist?view=%s&status=plan_to_watch", layout))) + templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(fmt.Sprintf("/watchlist?view=%s&status=plan_to_watch&sort=%s&order=%s", layout, sortBy, sortOrder))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 29, Col: 86} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 29, Col: 122} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { @@ -270,9 +270,9 @@ func Watchlist(entries []database.GetUserWatchListRow, layout string, currentSta return templ_7745c5c3_Err } var templ_7745c5c3_Var22 templ.SafeURL - templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(fmt.Sprintf("/watchlist?view=%s&status=dropped", layout))) + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(fmt.Sprintf("/watchlist?view=%s&status=dropped&sort=%s&order=%s", layout, sortBy, sortOrder))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 30, Col: 80} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 30, Col: 116} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { @@ -305,9 +305,9 @@ func Watchlist(entries []database.GetUserWatchListRow, layout string, currentSta return templ_7745c5c3_Err } var templ_7745c5c3_Var25 templ.SafeURL - templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(fmt.Sprintf("/watchlist?view=%s&status=completed", layout))) + templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(fmt.Sprintf("/watchlist?view=%s&status=completed&sort=%s&order=%s", layout, sortBy, sortOrder))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 31, Col: 82} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 31, Col: 118} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) if templ_7745c5c3_Err != nil { @@ -330,264 +330,272 @@ func Watchlist(entries []database.GetUserWatchListRow, layout string, currentSta if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + templ_7745c5c3_Err = SortFilter(SortFilterOptions{Sort: sortBy, Order: sortOrder, View: layout, Status: currentStatus}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } if len(entries) == 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
nothing here yet
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
nothing here yet
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if currentStatus == "all" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "your watchlist is empty. search for anime to get started.") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "your watchlist is empty. search for anime to get started.") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "no anime in this category.") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "no anime in this category.") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { if layout == "grid" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, entry := range entries { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if entry.ImageUrl != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\"")") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\" class=\"catalog-thumb\" loading=\"lazy\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "
no image
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "
no image
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var31 string templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(entry.DisplayTitle()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 57, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 59, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "\" hx-swap=\"delete\">×
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
title
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, entry := range entries { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\" hx-swap=\"delete\" style=\"background: none; border: none; cursor: pointer;\">remove") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "
title
\"")") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var39 string templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(entry.DisplayTitle()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 86, Col: 31} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/templates/watchlist.templ`, Line: 88, Col: 31} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/static/css/style.css b/static/css/style.css index 1391b53..8b11c38 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -857,6 +857,43 @@ a:visited { margin: 0; } +/* Sort Filter */ +.sort-filter { + display: flex; + gap: 16px; + margin-bottom: 16px; + padding: 12px; + background: var(--surface); + border: 1px solid var(--border); +} + +.sort-filter-group { + display: flex; + align-items: center; + gap: 8px; +} + +.sort-filter-group label { + font-size: 12px; + color: var(--text-muted); + font-weight: 500; +} + +.sort-filter-select { + background: var(--surface); + border: 1px solid var(--border); + color: var(--text); + padding: 6px 8px; + font-size: 12px; + font-family: inherit; + cursor: pointer; +} + +.sort-filter-select:focus { + outline: none; + border-color: var(--link); +} + /* Responsive */ @media (max-width: 900px) { .anime-page {