feat: enhance book detail star rating display with visual half-stars

Improve star rating display on book detail page to show always-visible
5-star rating scale with theme-aware colors and visual half-star rendering.

Changes:

Enhanced renderStars() function:
- Always displays 5 stars (0/5 now shows 5 grey stars instead of empty)
- Filled stars use var(--accent) color (theme-aware highlight)
- Empty stars use var(--text-secondary) (theme-aware grey, adapts to light/dark themes)
- Half-stars use CSS linear-gradient (90deg) to split star vertically:
  - Left half: var(--accent) (filled, color)
  - Right half: var(--text-secondary) (empty, grey)
- Uses webkit-background-clip and text-fill-color transparent for gradient effect

Added getBookRating() helper function:
- Returns rating value or 0 if book.Rating is nil
- Allows unrated books to display 0/5 (5 grey stars)
- Makes rating section always visible instead of hiding when nil

Template changes:
- Updated rating display to always show (no conditionals)
- Removed text-yellow-400 class (colors now inline with theme vars)
- Added templ.Raw() wrapper for HTML rendering (prevents escaping)
- Simplified rating display logic

Benefits:
- Users can now see rating scale even when book isn't rated
- Visual half-star is much more intuitive than ½ text character
- Theme-aware colors adapt to light/dark mode automatically
- Follows existing patterns ( UnsafeHTML, CSS variables, etc.)

This makes the rating section more discoverable and user-friendly.
This commit is contained in:
2026-03-29 00:26:50 -04:00
parent a157c546fd
commit e89ed4dbc6
3 changed files with 246 additions and 243 deletions
+28 -8
View File
@@ -1,7 +1,9 @@
package templates
import (
"bookhoard/internal/database"
"fmt"
"strings"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
@@ -36,20 +38,30 @@ func uuidToString(id pgtype.UUID) string {
}
// renderStars converts rating (1-10 scale) to star display
// Rating scale: 1-10 where odd numbers = half stars (1=0.5★, 3=1.5★, etc.)
// Always displays 5 stars total with filled stars in yellow
// and empty stars in grey (using theme variable)
// Rating scale: 1-10 where each 2 points = 1 full star, odd numbers = half stars
func renderStars(rating int32) string {
stars := ""
const totalStars = 5
fullStars := rating / 2
hasHalf := rating%2 != 0
for i := int32(0); i < fullStars; i++ {
stars += "★"
}
if hasHalf {
stars += "½"
var stars strings.Builder
for i := int32(0); i < totalStars; i++ {
if i < fullStars {
// Filled star - accent color (theme-aware highlight)
stars.WriteString(`<span style="color: var(--accent);">★</span>`)
} else if i == fullStars && hasHalf {
// Half star - gradient from accent (left) to grey (right)
stars.WriteString(`<span style="background: linear-gradient(90deg, var(--accent) 50%, var(--text-secondary) 50%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">★</span>`)
} else {
// Empty star - grey using theme variable
stars.WriteString(`<span style="color: var(--text-secondary);">★</span>`)
}
}
return stars
return stars.String()
}
// formatFileSize converts bytes to human-readable format
@@ -122,3 +134,11 @@ func getExternalURL(service string, id string, isbn pgtype.Text, title string, a
return baseURL + searchTerm
}
// getBookRating returns the rating value or 0 if not rated
func getBookRating(rating *database.MediaRatings) int32 {
if rating != nil {
return rating.Rating
}
return 0
}