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

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Phase 1 recommendation engine: user preference cache table.
*
* Stores pre-computed tag/category/creator affinity profiles so that
* the "For You" feed can be generated without heavy joins on every request.
*
* TTL enforcement: the application checks `updated_at` + config TTL to decide
* whether to rebuild. A background job (or on-demand compute) refreshes stale rows.
*/
return new class extends Migration
{
public function up(): void
{
Schema::create('user_reco_profiles', function (Blueprint $table): void {
$table->unsignedBigInteger('user_id')->primary();
$table->json('top_tags_json')->nullable()->comment('Top tag slugs ordered by weighted score (up to 20)');
$table->json('top_categories_json')->nullable()->comment('Top category slugs ordered by weight (up to 5)');
$table->json('followed_creator_ids_json')->nullable()->comment('Followed creator IDs (up to 50)');
$table->json('tag_weights_json')->nullable()->comment('Normalised tag slug → weight map');
$table->json('category_weights_json')->nullable()->comment('Normalised category slug → weight map');
$table->json('disliked_tag_ids_json')->nullable()->comment('Hidden/blocked tag slugs (future use)');
$table->timestamps();
$table->foreign('user_id')
->references('id')
->on('users')
->cascadeOnDelete();
$table->index('updated_at', 'urp_updated_at_idx');
});
}
public function down(): void
{
Schema::dropIfExists('user_reco_profiles');
}
};