fix(gallery): fill tall portrait cards to full block width with object-cover crop

- ArtworkCard: add w-full to nova-card-media, use absolute inset-0 on img so
  object-cover fills the max-height capped box instead of collapsing the width
- MasonryGallery.css: add width:100% to media container, position img
  absolutely so top/bottom is cropped rather than leaving dark gaps
- Add React MasonryGallery + ArtworkCard components and entry point
- Add recommendation system: UserRecoProfile model/DTO/migration,
  SuggestedCreatorsController, SuggestedTagsController, Recommendation
  services, config/recommendations.php
- SimilarArtworksController, DiscoverController, HomepageService updates
- Update routes (api + web) and discover/for-you views
- Refresh favicon assets, update vite.config.js
This commit is contained in:
2026-02-27 13:34:08 +01:00
parent 09eadf9003
commit 67ef79766c
37 changed files with 3096 additions and 58 deletions

View File

@@ -6,6 +6,61 @@ return [
// Uses same queue family as vision jobs by default; keeps embedding work async and non-blocking.
'queue' => env('RECOMMENDATIONS_QUEUE', env('VISION_QUEUE', 'default')),
// ─── Phase 1 "For You" feed scoring weights ───────────────────────────────
// Influences the PHP reranking pass after Meilisearch candidate retrieval.
// Tweak here without code changes.
'weights' => [
// Tag overlap score weight (01 normalized overlap fraction)
'tag_overlap' => (float) env('RECO_W_TAG_OVERLAP', 0.40),
// Creator affinity score weight (1.0 if followed, 0 otherwise)
'creator_affinity' => (float) env('RECO_W_CREATOR_AFFINITY', 0.25),
// Popularity boost (log-normalised views/downloads)
'popularity' => (float) env('RECO_W_POPULARITY', 0.20),
// Freshness boost (exponential decay over 30 days)
'freshness' => (float) env('RECO_W_FRESHNESS', 0.15),
],
// ─── User preference signal weights ──────────────────────────────────────
// How much each user action contributes to building the reco profile.
'signal_weights' => [
'award' => (float) env('RECO_SIG_AWARD', 5.0),
'favorite' => (float) env('RECO_SIG_FAVORITE', 3.0),
'reaction' => (float) env('RECO_SIG_REACTION', 2.0),
'view' => (float) env('RECO_SIG_VIEW', 1.0),
'follow' => (float) env('RECO_SIG_FOLLOW', 2.0),
],
// ─── Candidate generation ──────────────────────────────────────────────────
// How many Meilisearch candidates to fetch before PHP reranking.
'candidate_pool_size' => (int) env('RECO_CANDIDATE_POOL', 200),
// ─── Diversity controls ────────────────────────────────────────────────────
// Maximum artworks per creator allowed in a single page of results.
'max_per_creator' => (int) env('RECO_MAX_PER_CREATOR', 3),
// Minimum distinct tag count in first 20 feed results.
'min_unique_tags' => (int) env('RECO_MIN_UNIQUE_TAGS', 5),
// ─── TTLs (seconds) ────────────────────────────────────────────────────────
'ttl' => [
// User reco profile cache (tag/creator affinity data)
'user_reco_profile' => (int) env('RECO_TTL_PROFILE', 6 * 3600),
// For You paginated results cache
'for_you_feed' => (int) env('RECO_TTL_FOR_YOU', 5 * 60),
// Similar artworks per artwork
'similar_artworks' => (int) env('RECO_TTL_SIMILAR', 30 * 60),
// Suggested creators per user
'creator_suggestions' => (int) env('RECO_TTL_CREATORS', 30 * 60),
// Suggested tags per user
'tag_suggestions' => (int) env('RECO_TTL_TAGS', 60 * 60),
],
// ─── Profile limits ────────────────────────────────────────────────────────
'profile' => [
'top_tags_limit' => (int) env('RECO_PROFILE_TAGS', 20),
'top_categories_limit' => (int) env('RECO_PROFILE_CATS', 5),
'strong_creators_limit' => (int) env('RECO_PROFILE_CREATORS', 50),
],
'embedding' => [
'enabled' => env('RECOMMENDATIONS_EMBEDDING_ENABLED', true),
'model' => env('RECOMMENDATIONS_EMBEDDING_MODEL', 'clip'),