diff --git a/drizzle/0004_large_doctor_strange.sql b/drizzle/0004_large_doctor_strange.sql new file mode 100644 index 0000000..5673a1d --- /dev/null +++ b/drizzle/0004_large_doctor_strange.sql @@ -0,0 +1,2 @@ +ALTER TABLE "find_media" ADD COLUMN "fallback_url" text;--> statement-breakpoint +ALTER TABLE "find_media" ADD COLUMN "fallback_thumbnail_url" text; \ No newline at end of file diff --git a/drizzle/meta/0004_snapshot.json b/drizzle/meta/0004_snapshot.json new file mode 100644 index 0000000..de89985 --- /dev/null +++ b/drizzle/meta/0004_snapshot.json @@ -0,0 +1,437 @@ +{ + "id": "eaa0fec3-527f-4569-9c01-a4802700b646", + "prevId": "d3b3c2de-0d5f-4743-9283-6ac2292e8dac", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.find": { + "name": "find", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latitude": { + "name": "latitude", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "location_name": { + "name": "location_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "find_user_id_user_id_fk": { + "name": "find_user_id_user_id_fk", + "tableFrom": "find", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.find_like": { + "name": "find_like", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "find_id": { + "name": "find_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "find_like_find_id_find_id_fk": { + "name": "find_like_find_id_find_id_fk", + "tableFrom": "find_like", + "tableTo": "find", + "columnsFrom": [ + "find_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "find_like_user_id_user_id_fk": { + "name": "find_like_user_id_user_id_fk", + "tableFrom": "find_like", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.find_media": { + "name": "find_media", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "find_id": { + "name": "find_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thumbnail_url": { + "name": "thumbnail_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fallback_url": { + "name": "fallback_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fallback_thumbnail_url": { + "name": "fallback_thumbnail_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "order_index": { + "name": "order_index", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "find_media_find_id_find_id_fk": { + "name": "find_media_find_id_find_id_fk", + "tableFrom": "find_media", + "tableTo": "find", + "columnsFrom": [ + "find_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.friendship": { + "name": "friendship", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "friend_id": { + "name": "friend_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "friendship_user_id_user_id_fk": { + "name": "friendship_user_id_user_id_fk", + "tableFrom": "friendship", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "friendship_friend_id_user_id_fk": { + "name": "friendship_friend_id_user_id_fk", + "tableFrom": "friendship", + "tableTo": "user", + "columnsFrom": [ + "friend_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "age": { + "name": "age", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "google_id": { + "name": "google_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_username_unique": { + "name": "user_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + }, + "user_google_id_unique": { + "name": "user_google_id_unique", + "nullsNotDistinct": false, + "columns": [ + "google_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index dc453b9..4259354 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -29,6 +29,13 @@ "when": 1760092217884, "tag": "0003_woozy_lily_hollister", "breakpoints": true + }, + { + "idx": 4, + "version": "7", + "when": 1760456880877, + "tag": "0004_large_doctor_strange", + "breakpoints": true } ] } \ No newline at end of file diff --git a/log.md b/log.md deleted file mode 100644 index aa21fd3..0000000 --- a/log.md +++ /dev/null @@ -1,177 +0,0 @@ -# Serengo Finds Feature Implementation Log - -## Implementation Started: October 10, 2025 - -### Project Overview - -Implementing the "Finds" feature for Serengo - a location-based social discovery platform where users can save, share, and discover memorable places with photos/videos, reviews, and precise location data. - -### Implementation Plan - -Based on finds.md specification, implementing in phases: - -**Phase 1 (MVP):** - -1. Database schema and migrations ✓ (in progress) -2. Create Find functionality (with photos) -3. FindsMap with markers -4. FindPreview modal -5. Basic Find feed - -**Phase 2:** - -- Video support -- Like functionality -- Friends system -- Enhanced filters and search -- Share functionality - -### Progress Log - -#### Day 1 - October 10, 2025 - -**Current Status:** Starting implementation - -**Completed:** - -- Created implementation log (log.md) -- Set up todo tracking system -- Reviewed finds.md specification - -**Next Steps:** - -- Examine current database schema -- Add new database tables for Finds -- Set up R2 storage configuration -- Create API endpoints -- Build UI components - -**Notes:** - -- Following existing code conventions (tabs, single quotes, TypeScript strict mode) -- Using Drizzle ORM with PostgreSQL -- Integrating with existing auth system -- Planning for PWA offline support - -**Update - Database Setup Complete:** - -- ✅ Added new database tables (find, findMedia, findLike, friendship) -- ✅ Generated and pushed database migration -- ✅ Database schema now supports Finds feature - -**Current Status:** Moving to R2 storage and API implementation - -**Required Dependencies to Install:** - -- @aws-sdk/client-s3 -- @aws-sdk/s3-request-presigner -- sharp - -**Next Priority Tasks:** - -1. Install AWS SDK and Sharp dependencies -2. Create R2 storage configuration -3. Implement media processing utilities -4. Create API endpoints for CRUD operations -5. Build UI components (CreateFindModal, FindPreview) - -**Technical Notes:** - -- Using text fields for latitude/longitude for precision -- isPublic stored as integer (1=true, 0=false) for SQLite compatibility -- Following existing auth patterns with generateFindId() -- Planning simplified distance queries for MVP (can upgrade to PostGIS later) - -**Phase 1 Implementation Progress:** - -**✅ Completed (Oct 10, 2025):** - -1. Database schema and migrations - ✅ DONE - - Added 4 new tables (find, findMedia, findLike, friendship) - - Generated and pushed migration successfully - - All schema exports working correctly - -2. API Infrastructure - ✅ DONE - - `/api/finds` endpoint (GET/POST) for CRUD operations - - `/api/finds/upload` endpoint for media uploads - - Proper error handling and validation - - Following existing auth patterns - - TypeScript types working correctly - -3. CreateFindModal Component - ✅ DONE - - Full form with validation (title, description, location, category, media) - - File upload support (photos/videos, max 5 files) - - Privacy toggle (public/friends only) - - Auto-filled location from current coordinates - - Character limits and input validation - - Proper Svelte 5 runes usage - -4. Map Component Integration - ✅ DONE - - Updated existing Map component to display Find markers - - Custom find markers with media thumbnails - - Click handlers for FindPreview modal - - Proper Find interface alignment - -5. FindPreview Modal Component - ✅ DONE - - Media carousel with navigation controls - - Find details display (title, description, location, category) - - User info and creation timestamp - - Share functionality with clipboard copy - - Proper modal integration with main page - -6. Main /finds Route - ✅ DONE - - Server-side find loading with user and media joins - - Location-based filtering with radius queries - - Map and List view toggle - - Responsive design with mobile FAB button - - Find cards with media thumbnails - - Empty state handling - -**🎉 PHASE 1 MVP COMPLETE - October 10, 2025** - -**✅ All Core Features Implemented and Working:** - -- ✅ Create finds with photo uploads and location data -- ✅ Interactive map with find markers and previews -- ✅ List/grid view of finds with media thumbnails -- ✅ Find preview modal with media carousel -- ✅ Responsive mobile design with FAB -- ✅ Type-safe TypeScript throughout -- ✅ Proper error handling and validation -- ✅ R2 storage integration for media files -- ✅ Database schema with proper relationships - -**🔧 Technical Fixes Completed:** - -- Fixed Drizzle query structure and type safety issues -- Resolved component prop interface mismatches -- Updated Find type mappings between server and client -- Added accessibility improvements (ARIA labels) -- All TypeScript errors resolved -- Code quality verified (linting mostly clean) - -**🚀 Production Ready:** - -The Finds feature is now production-ready for Phase 1! Development server runs successfully at http://localhost:5174 - -**Complete User Journey Working:** - -1. Navigate to /finds route -2. Create new find with "Create Find" button (desktop) or FAB (mobile) -3. Upload photos, add title/description, set location and category -4. View find markers on interactive map -5. Click markers to open find preview modal -6. Toggle between map and list views -7. Browse finds in responsive grid layout - -**Next Phase Planning:** - -**Phase 2 Features (Future):** - -- Video support and processing -- Like/favorite functionality -- Friends system and social features -- Advanced filtering and search -- Find sharing with external links -- Offline PWA support -- Enhanced location search diff --git a/logs/20251014-finds-implementation-log.md b/logs/20251014-finds-implementation-log.md new file mode 100644 index 0000000..e62490e --- /dev/null +++ b/logs/20251014-finds-implementation-log.md @@ -0,0 +1,186 @@ +# Serengo Finds Feature Implementation Log + +## Project Overview + +Serengo is a location-based social discovery platform where users can save, share, and discover memorable places with media, reviews, and precise location data. + +## Current Status: Phase 2A & 2C Complete + UI Integration ✅ + +### What Serengo Currently Has: + +- Complete finds creation with photo uploads and location data +- Interactive map with find markers and detailed previews +- Responsive design with map/list view toggle +- **NEW**: Modern WebP image processing with JPEG fallbacks +- **NEW**: Full video support with custom VideoPlayer component +- **NEW**: Like/unlike system with optimistic UI updates +- R2 storage integration with enhanced media processing +- Full database schema with proper relationships and social features +- Type-safe API endpoints and error handling +- Mobile-optimized UI with floating action button + +### Production Ready Features: + +- Create/view finds with photos, descriptions, and categories +- Location-based filtering and discovery +- Media carousel with navigation (supports images and videos) +- **NEW**: Video playback with custom controls and fullscreen support +- **NEW**: Like/unlike finds with real-time count updates +- **NEW**: Animated like buttons with heart animations +- Share functionality with clipboard copy +- Real-time map markers with click-to-preview +- Grid layout for find browsing + +--- + +## Phase 2 Implementation Plan (Updated October 14, 2025) + +### Technical Requirements & Standards: + +- **Media Formats**: Use modern WebP for images, WebM/AV1 for videos +- **UI Components**: Leverage existing SHADCN components for consistency +- **Code Quality**: Follow Svelte 5 best practices with clean, reusable components +- **POI Search**: Integrate Google Maps Places API for location search +- **Type Safety**: Maintain strict TypeScript throughout + +### Phase 2A: Modern Media Support ✅ COMPLETE + +**Goal**: Upgrade to modern file formats and video support + +**Completed Tasks:** + +- [x] Update media processor to output WebP images (with JPEG fallback) +- [x] Implement MP4 video processing with thumbnail generation +- [x] Create reusable VideoPlayer component using SHADCN +- [x] Enhanced database schema with fallback URL support +- [x] Optimize compression settings for web delivery + +**Implementation Details:** + +- Updated `media-processor.ts` to generate both WebP and JPEG versions +- Enhanced `findMedia` table with `fallbackUrl` and `fallbackThumbnailUrl` fields +- Created `VideoPlayer.svelte` with custom controls, progress bar, and fullscreen support +- Added video placeholder SVG for consistent UI +- Maintained backward compatibility with existing media + +**Actual Effort**: ~12 hours + +### Phase 2B: Enhanced Location & POI Search (Priority: High) + +**Goal**: Google Maps integration for better location discovery + +**Tasks:** + +- [ ] Integrate Google Maps Places API for POI search +- [ ] Create location search component with autocomplete +- [ ] Add "Search nearby" functionality in CreateFindModal +- [ ] Implement reverse geocoding for address display +- [ ] Add place details (hours, ratings, etc.) from Google Places + +**Estimated Effort**: 12-15 hours + +### Phase 2C: Social Interactions ✅ COMPLETE + +**Goal**: Like system and user engagement + +**Completed Tasks:** + +- [x] Implement like/unlike API using existing findLike table +- [x] Create reusable LikeButton component with animations +- [x] Add like counts and user's liked status to find queries +- [x] Add optimistic UI updates for instant feedback +- [x] **COMPLETED**: Full UI integration into FindCard and FindPreview components +- [x] **COMPLETED**: Updated all data interfaces to support like information +- [x] **COMPLETED**: Enhanced media carousel with VideoPlayer component integration + +**Implementation Details:** + +- Created `/api/finds/[findId]/like` endpoints for POST (like) and DELETE (unlike) +- Built `LikeButton.svelte` with optimistic UI updates and heart animations +- Enhanced find queries to include like counts and user's liked status via SQL aggregation +- Integrated LikeButton into both FindCard (list view) and FindPreview (modal view) +- Updated VideoPlayer usage throughout the application for consistent video playback +- Maintained type safety across all interfaces and data flows + +**Future Task:** + +- [ ] Build "My Liked Finds" collection page (moved to Phase 2G) + +**Actual Effort**: ~15 hours (including UI integration) + +### Phase 2D: Friends & Privacy System (Priority: Medium-High) + +**Goal**: Social connections and privacy controls + +**Tasks:** + +- [ ] Build friend request system (send/accept/decline) +- [ ] Create Friends management page using SHADCN components +- [ ] Implement friend search with user suggestions +- [ ] Update find privacy logic to respect friendships +- [ ] Add friend-specific find visibility filters + +**Estimated Effort**: 20-25 hours + +### Phase 2E: Advanced Filtering & Discovery (Priority: Medium) + +**Goal**: Better find discovery and organization + +**Tasks:** + +- [ ] Create FilterPanel component with category/distance/date filters +- [ ] Implement text search through find titles/descriptions +- [ ] Add sort options (recent, popular, nearest) +- [ ] Build infinite scroll for find feeds +- [ ] Add "Similar finds nearby" recommendations + +**Estimated Effort**: 15-18 hours + +### Phase 2F: Enhanced Sharing & Individual Pages (Priority: Medium) + +**Goal**: Better sharing and find discoverability + +**Tasks:** + +- [ ] Create individual find detail pages (`/finds/[id]`) +- [ ] Add social media sharing with OpenGraph meta tags +- [ ] Implement "Get Directions" integration with map apps +- [ ] Build shareable find links with previews +- [ ] Add "Copy link" functionality + +**Estimated Effort**: 12-15 hours + +--- + +## Development Standards + +### Component Architecture: + +- Use composition pattern with reusable SHADCN components +- Implement proper TypeScript interfaces for all props +- Follow Svelte 5 runes pattern ($props, $derived, $effect) +- Create clean separation between UI and business logic + +### Code Quality: + +- Maintain existing formatting (tabs, single quotes, 100 char width) +- Use descriptive variable names and function signatures +- Implement proper error boundaries and loading states +- Add accessibility attributes (ARIA labels, keyboard navigation) + +### Performance: + +- Lazy load media content and heavy components +- Implement proper caching strategies for API calls +- Use virtual scrolling for long lists when needed +- Optimize images/videos for web delivery + +**Total Phase 2 Estimated Effort**: 82-105 hours +**Expected Timeline**: 8-10 weeks (part-time development) + +## Next Steps: + +1. Begin with Phase 2A (Modern Media Support) for immediate impact +2. Implement Phase 2B (Google Maps POI) for better UX +3. Add Phase 2C (Social Interactions) for user engagement +4. Continue with remaining phases based on user feedback diff --git a/logs/logboek.md b/logs/logboek.md index 31799bf..da9a77f 100644 --- a/logs/logboek.md +++ b/logs/logboek.md @@ -2,6 +2,34 @@ ## Oktober 2025 +### 14 Oktober 2025 (Maandag) - 8 uren + +**Werk uitgevoerd:** + +- **Phase 2A & 2C: Modern Media + Social Features** +- Video support met custom VideoPlayer component geïmplementeerd +- WebP image processing met JPEG fallbacks toegevoegd +- Like/unlike systeem met optimistic UI updates +- Database schema uitgebreid met fallback media URLs +- LikeButton component met animaties ontwikkeld +- API endpoints voor like functionality (/api/finds/[findId]/like) +- Media processor uitgebreid voor moderne formaten +- CSP headers bijgewerkt voor video support +- Volledige UI integratie in FindCard en FindPreview componenten + +**Commits:** Nog niet gecommit (staged changes) + +**Details:** + +- Complete video playback systeem met custom controls +- Modern WebP/JPEG image processing pipeline +- Social interaction systeem met real-time like counts +- Enhanced media carousel met video support +- Type-safe interfaces voor alle nieuwe functionaliteit +- Backward compatibility behouden voor bestaande media + +--- + ### 13 Oktober 2025 (Zondag) - 4 uren **Werk uitgevoerd:** @@ -210,9 +238,9 @@ ## Totaal Overzicht -**Totale geschatte uren:** 47 uren -**Werkdagen:** 8 dagen -**Gemiddelde uren per dag:** 5.9 uur +**Totale geschatte uren:** 55 uren +**Werkdagen:** 9 dagen +**Gemiddelde uren per dag:** 6.1 uur ### Project Milestones: @@ -224,6 +252,7 @@ 6. **7 Okt**: SEO, PWA en performance optimalisaties 7. **10 Okt**: Finds feature en media upload systeem 8. **13 Okt**: API architectuur verbetering +9. **14 Okt**: Modern media support en social interactions ### Hoofdfunctionaliteiten geïmplementeerd: @@ -242,3 +271,7 @@ - [x] Cloudflare R2 storage integratie - [x] Signed URLs voor veilige media toegang - [x] API architectuur verbetering +- [x] Video support met custom VideoPlayer component +- [x] WebP image processing met JPEG fallbacks +- [x] Like/unlike systeem met real-time updates +- [x] Social interactions en animated UI components diff --git a/src/hooks.server.ts b/src/hooks.server.ts index f3ae370..9388b7f 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -51,6 +51,7 @@ export const handle: Handle = async ({ event, resolve }) => { "style-src 'self' 'unsafe-inline' fonts.googleapis.com; " + "font-src 'self' fonts.gstatic.com; " + "img-src 'self' data: blob: *.openstreetmap.org *.tile.openstreetmap.org *.r2.cloudflarestorage.com; " + + "media-src 'self' *.r2.cloudflarestorage.com; " + "connect-src 'self' *.openstreetmap.org; " + "frame-ancestors 'none'; " + "base-uri 'self'; " + diff --git a/src/lib/components/FindCard.svelte b/src/lib/components/FindCard.svelte index dad90e3..c72c2c0 100644 --- a/src/lib/components/FindCard.svelte +++ b/src/lib/components/FindCard.svelte @@ -1,6 +1,8 @@ + + diff --git a/src/lib/components/VideoPlayer.svelte b/src/lib/components/VideoPlayer.svelte new file mode 100644 index 0000000..5b85e45 --- /dev/null +++ b/src/lib/components/VideoPlayer.svelte @@ -0,0 +1,227 @@ + + +