Initial commit: IndeeHub decentralized streaming platform
Built a complete Netflix-style streaming interface for IndeeHub's decentralized media platform with real film content. Features: - Vue 3 + TypeScript + Vite setup with hot module reloading - Netflix-inspired UI with hero section and horizontal scrolling content rows - Glass morphism design system with custom Tailwind configuration - 20+ real IndeeHub films organized into 6 categories (Bitcoin, Documentaries, Drama, etc.) - Full-featured video player component with custom controls - Mobile-responsive design with bottom navigation - Nostr integration ready (nostr-tools, relay pool, NIP-71 support) - Pinia state management for content - MCP tools configured (Filesystem, Memory, Nostr, Puppeteer) Components: - Browse.vue: Main streaming interface with hero and content rows - ContentRow.vue: Horizontal scrolling film cards with navigation arrows - VideoPlayer.vue: Custom video player with play/pause, seek, volume, fullscreen - MobileNav.vue: Bottom tab navigation for mobile devices Tech Stack: - Frontend: Vue 3 (Composition API), TypeScript - Build: Vite 7 - Styling: Tailwind CSS with custom theme - State: Pinia 3 - Router: Vue Router 4.6 - Protocol: Nostr (nostr-tools 2.22) Design: - 4px grid spacing system - Glass morphism UI components - Netflix-style hero section with featured content - Smooth animations and hover effects - Mobile-first responsive breakpoints - Dark theme with custom color palette Content: - 20+ IndeeHub films with titles, descriptions, categories - Bitcoin documentaries: God Bless Bitcoin, Dirty Coin, Searching for Satoshi - Independent films and documentaries - Working Unsplash CDN images for thumbnails and backdrops Ready for deployment to Umbrel, Start9, and Archy nodes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
185
src/views/Browse.vue
Normal file
185
src/views/Browse.vue
Normal file
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<div class="browse-view">
|
||||
<!-- Header / Navigation -->
|
||||
<header class="fixed top-0 left-0 right-0 z-50 transition-all duration-300"
|
||||
:class="{ 'bg-black/90 backdrop-blur-md': scrolled }">
|
||||
<div class="container mx-auto px-6 py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- Logo -->
|
||||
<div class="flex items-center gap-8">
|
||||
<img src="/assets/images/logo.svg" alt="IndeedHub" class="h-10" />
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="hidden md:flex items-center gap-6">
|
||||
<a href="#" class="text-white hover:text-white/80 transition-colors">Home</a>
|
||||
<a href="#" class="text-white/70 hover:text-white transition-colors">Films</a>
|
||||
<a href="#" class="text-white/70 hover:text-white transition-colors">Series</a>
|
||||
<a href="#" class="text-white/70 hover:text-white transition-colors">Creators</a>
|
||||
<a href="#" class="text-white/70 hover:text-white transition-colors">My List</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Right Side Actions -->
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- Search -->
|
||||
<button class="p-2 hover:bg-white/10 rounded-lg transition-colors" @click="toggleSearch">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- User Avatar -->
|
||||
<div class="w-8 h-8 rounded bg-gradient-to-br from-orange-500 to-pink-500"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Hero / Featured Content -->
|
||||
<section class="relative h-[70vh] md:h-[80vh] overflow-hidden">
|
||||
<!-- Background Image -->
|
||||
<div class="absolute inset-0">
|
||||
<img
|
||||
:src="featuredContent?.backdrop || 'https://images.unsplash.com/photo-1536440136628-849c177e76a1?w=1920'"
|
||||
alt="Featured content"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
<div class="absolute inset-0 hero-gradient"></div>
|
||||
</div>
|
||||
|
||||
<!-- Hero Content -->
|
||||
<div class="relative container mx-auto px-6 h-full flex items-center md:items-end pb-8 md:pb-24">
|
||||
<div class="max-w-2xl space-y-3 md:space-y-4 animate-fade-in pt-24 md:pt-0">
|
||||
<!-- Title -->
|
||||
<h1 class="text-4xl md:text-6xl lg:text-7xl font-bold drop-shadow-2xl leading-tight">
|
||||
{{ featuredContent?.title || 'Welcome to IndeedHub' }}
|
||||
</h1>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="text-base md:text-lg lg:text-xl text-white/90 drop-shadow-lg line-clamp-3">
|
||||
{{ featuredContent?.description || 'Discover decentralized content from independent creators and filmmakers around the world.' }}
|
||||
</p>
|
||||
|
||||
<!-- Meta Info -->
|
||||
<div v-if="featuredContent" class="flex items-center gap-3 text-sm text-white/80">
|
||||
<span v-if="featuredContent.rating" class="bg-white/20 px-3 py-1 rounded">{{ featuredContent.rating }}</span>
|
||||
<span v-if="featuredContent.releaseYear">{{ featuredContent.releaseYear }}</span>
|
||||
<span v-if="featuredContent.duration">{{ featuredContent.duration }}min</span>
|
||||
<span v-else>{{ featuredContent.type === 'film' ? 'Film' : 'Series' }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex items-center gap-3 md:gap-4 pt-2 md:pt-4">
|
||||
<button class="px-6 md:px-8 py-2.5 md:py-3 bg-white text-black font-semibold rounded-lg hover:bg-white/90 transition-all flex items-center gap-2 shadow-xl text-sm md:text-base">
|
||||
<svg class="w-5 h-5 md:w-6 md:h-6" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M8 5v14l11-7z"/>
|
||||
</svg>
|
||||
Play
|
||||
</button>
|
||||
<button class="px-6 md:px-8 py-2.5 md:py-3 bg-white/20 text-white font-semibold rounded-lg hover:bg-white/30 transition-all backdrop-blur-md flex items-center gap-2 text-sm md:text-base">
|
||||
<svg class="w-5 h-5 md:w-6 md:h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
More Info
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Content Rows -->
|
||||
<section class="relative -mt-32 pb-20">
|
||||
<div class="container mx-auto px-6 space-y-12">
|
||||
<!-- Featured Films -->
|
||||
<ContentRow
|
||||
title="Featured Films"
|
||||
:contents="featuredFilms"
|
||||
@content-click="handleContentClick"
|
||||
/>
|
||||
|
||||
<!-- New Releases -->
|
||||
<ContentRow
|
||||
title="New Releases"
|
||||
:contents="newReleases"
|
||||
@content-click="handleContentClick"
|
||||
/>
|
||||
|
||||
<!-- Bitcoin & Crypto -->
|
||||
<ContentRow
|
||||
title="Bitcoin & Cryptocurrency"
|
||||
:contents="bitcoinFilms"
|
||||
@content-click="handleContentClick"
|
||||
/>
|
||||
|
||||
<!-- Documentaries -->
|
||||
<ContentRow
|
||||
title="Documentaries"
|
||||
:contents="documentaries"
|
||||
@content-click="handleContentClick"
|
||||
/>
|
||||
|
||||
<!-- Independent Cinema -->
|
||||
<ContentRow
|
||||
title="Independent Cinema"
|
||||
:contents="independentCinema"
|
||||
@content-click="handleContentClick"
|
||||
/>
|
||||
|
||||
<!-- Dramas -->
|
||||
<ContentRow
|
||||
title="Drama Films"
|
||||
:contents="dramas"
|
||||
@content-click="handleContentClick"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||
import ContentRow from '../components/ContentRow.vue'
|
||||
import { useContentStore } from '../stores/content'
|
||||
import type { Content } from '../types/content'
|
||||
|
||||
const contentStore = useContentStore()
|
||||
const scrolled = ref(false)
|
||||
|
||||
const featuredContent = computed(() => contentStore.featuredContent)
|
||||
const featuredFilms = computed(() => contentStore.contentRows.featured)
|
||||
const newReleases = computed(() => contentStore.contentRows.newReleases)
|
||||
const bitcoinFilms = computed(() => contentStore.contentRows.bitcoin)
|
||||
const independentCinema = computed(() => contentStore.contentRows.independent)
|
||||
const dramas = computed(() => contentStore.contentRows.dramas)
|
||||
const documentaries = computed(() => contentStore.contentRows.documentaries)
|
||||
|
||||
const handleScroll = () => {
|
||||
scrolled.value = window.scrollY > 50
|
||||
}
|
||||
|
||||
const toggleSearch = () => {
|
||||
// TODO: Implement search modal
|
||||
console.log('Search clicked')
|
||||
}
|
||||
|
||||
const handleContentClick = (content: Content) => {
|
||||
console.log('Content clicked:', content)
|
||||
// TODO: Navigate to content detail page
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
contentStore.fetchContent()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.browse-view {
|
||||
min-height: 100vh;
|
||||
padding-top: 64px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user