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:
79
src/components/MobileNav.vue
Normal file
79
src/components/MobileNav.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<nav class="mobile-nav fixed bottom-0 left-0 right-0 z-50 md:hidden">
|
||||
<div class="glass-card rounded-t-3xl border-t">
|
||||
<div class="grid grid-cols-5 gap-1 px-2 py-3">
|
||||
<button
|
||||
v-for="item in navItems"
|
||||
:key="item.name"
|
||||
@click="navigate(item.path)"
|
||||
class="flex flex-col items-center gap-1 p-2 rounded-lg transition-colors"
|
||||
:class="{ 'text-netflix-red': isActive(item.path), 'text-white/60': !isActive(item.path) }"
|
||||
>
|
||||
<component :is="item.icon" class="w-6 h-6" />
|
||||
<span class="text-xs font-medium">{{ item.name }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { h } from 'vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
name: 'Home',
|
||||
path: '/',
|
||||
icon: () => h('svg', { class: 'w-6 h-6', fill: 'currentColor', viewBox: '0 0 24 24' },
|
||||
h('path', { d: 'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z' })
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'Search',
|
||||
path: '/search',
|
||||
icon: () => h('svg', { class: 'w-6 h-6', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' },
|
||||
h('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' })
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'My List',
|
||||
path: '/mylist',
|
||||
icon: () => h('svg', { class: 'w-6 h-6', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' },
|
||||
h('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z' })
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'Creators',
|
||||
path: '/creators',
|
||||
icon: () => h('svg', { class: 'w-6 h-6', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' },
|
||||
h('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z' })
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'Profile',
|
||||
path: '/profile',
|
||||
icon: () => h('svg', { class: 'w-6 h-6', fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24' },
|
||||
h('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z' })
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
const navigate = (path: string) => {
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
const isActive = (path: string) => {
|
||||
return route.path === path
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mobile-nav {
|
||||
/* Safe area for iPhone notch/home indicator */
|
||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user