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:
Dorian
2026-02-02 22:19:47 +00:00
commit 0bb1bcc5f9
50 changed files with 8278 additions and 0 deletions

View 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>