diff --git a/config/__pycache__/__init__.cpython-311.pyc b/config/__pycache__/__init__.cpython-311.pyc index 423a636..7e51457 100644 Binary files a/config/__pycache__/__init__.cpython-311.pyc and b/config/__pycache__/__init__.cpython-311.pyc differ diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 96bce55..022afd4 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 0b85e94..2325870 100644 Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ diff --git a/config/__pycache__/wsgi.cpython-311.pyc b/config/__pycache__/wsgi.cpython-311.pyc index 9c49e09..30dd4f5 100644 Binary files a/config/__pycache__/wsgi.cpython-311.pyc and b/config/__pycache__/wsgi.cpython-311.pyc differ diff --git a/core/__pycache__/__init__.cpython-311.pyc b/core/__pycache__/__init__.cpython-311.pyc index 74b1112..7cd10fe 100644 Binary files a/core/__pycache__/__init__.cpython-311.pyc and b/core/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a5ed392..31ca66d 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/apps.cpython-311.pyc b/core/__pycache__/apps.cpython-311.pyc index 6f131d4..3b06638 100644 Binary files a/core/__pycache__/apps.cpython-311.pyc and b/core/__pycache__/apps.cpython-311.pyc differ diff --git a/core/__pycache__/context_processors.cpython-311.pyc b/core/__pycache__/context_processors.cpython-311.pyc index 75bf223..3fa604c 100644 Binary files a/core/__pycache__/context_processors.cpython-311.pyc and b/core/__pycache__/context_processors.cpython-311.pyc differ diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000..ac20295 Binary files /dev/null and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index e061640..e35281f 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 5a69659..842f789 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2a36fd6..379fead 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index 8c38f3f..d537247 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,3 +1,23 @@ from django.contrib import admin +from .models import Category, Channel, Video, LiveStream -# Register your models here. +@admin.register(Category) +class CategoryAdmin(admin.ModelAdmin): + list_display = ('name', 'slug') + prepopulated_fields = {'slug': ('name',)} + +@admin.register(Channel) +class ChannelAdmin(admin.ModelAdmin): + list_display = ('name', 'handle', 'user', 'created_at') + search_fields = ('name', 'handle') + +@admin.register(Video) +class VideoAdmin(admin.ModelAdmin): + list_display = ('title', 'channel', 'views', 'is_published', 'created_at') + list_filter = ('is_published', 'category') + search_fields = ('title', 'description') + +@admin.register(LiveStream) +class LiveStreamAdmin(admin.ModelAdmin): + list_display = ('title', 'channel', 'is_live', 'viewer_count') + list_filter = ('is_live',) \ No newline at end of file diff --git a/core/forms.py b/core/forms.py new file mode 100644 index 0000000..75d3030 --- /dev/null +++ b/core/forms.py @@ -0,0 +1,15 @@ +from django import forms +from .models import Video, Category + +class VideoUploadForm(forms.ModelForm): + class Meta: + model = Video + fields = ['title', 'description', 'thumbnail', 'video_file', 'category', 'video_type'] + widgets = { + 'title': forms.TextInput(attrs={'class': 'form-control bg-dark text-white border-secondary', 'placeholder': 'Enter a catchy title'}), + 'description': forms.Textarea(attrs={'class': 'form-control bg-dark text-white border-secondary', 'placeholder': 'Describe your cosmic content', 'rows': 4}), + 'category': forms.Select(attrs={'class': 'form-select bg-dark text-white border-secondary'}), + 'video_type': forms.Select(attrs={'class': 'form-select bg-dark text-white border-secondary'}), + 'thumbnail': forms.FileInput(attrs={'class': 'form-control bg-dark text-white border-secondary'}), + 'video_file': forms.FileInput(attrs={'class': 'form-control bg-dark text-white border-secondary'}), + } diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..4190988 --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,76 @@ +# Generated by Django 5.2.7 on 2026-02-11 12:10 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('slug', models.SlugField(blank=True, unique=True)), + ], + options={ + 'verbose_name_plural': 'Categories', + }, + ), + migrations.CreateModel( + name='Channel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('description', models.TextField(blank=True)), + ('handle', models.CharField(max_length=100, unique=True)), + ('thumbnail', models.ImageField(blank=True, null=True, upload_to='channels/thumbnails/')), + ('banner', models.ImageField(blank=True, null=True, upload_to='channels/banners/')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('subscribers', models.ManyToManyField(blank=True, related_name='subscriptions', to=settings.AUTH_USER_MODEL)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='channel', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='LiveStream', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('title', models.CharField(max_length=255)), + ('description', models.TextField(blank=True)), + ('thumbnail', models.ImageField(upload_to='live/thumbnails/')), + ('stream_key', models.CharField(default=uuid.uuid4, max_length=100, unique=True)), + ('is_live', models.BooleanField(default=False)), + ('viewer_count', models.PositiveIntegerField(default=0)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('channel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='live_streams', to='core.channel')), + ], + ), + migrations.CreateModel( + name='Video', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('title', models.CharField(max_length=255)), + ('description', models.TextField(blank=True)), + ('thumbnail', models.ImageField(upload_to='videos/thumbnails/')), + ('video_file', models.FileField(blank=True, null=True, upload_to='videos/raw/')), + ('hls_url', models.URLField(blank=True, help_text='S3/CloudFront HLS URL', null=True)), + ('views', models.PositiveIntegerField(default=0)), + ('is_published', models.BooleanField(default=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='videos', to='core.category')), + ('channel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='videos', to='core.channel')), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + ] diff --git a/core/migrations/0002_video_video_type.py b/core/migrations/0002_video_video_type.py new file mode 100644 index 0000000..8ad30a3 --- /dev/null +++ b/core/migrations/0002_video_video_type.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2026-02-11 12:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='video', + name='video_type', + field=models.CharField(choices=[('standard', 'Standard'), ('short', 'Short'), ('live_recording', 'Live Recording')], default='standard', max_length=20), + ), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..0931d69 Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0002_video_video_type.cpython-311.pyc b/core/migrations/__pycache__/0002_video_video_type.cpython-311.pyc new file mode 100644 index 0000000..3064806 Binary files /dev/null and b/core/migrations/__pycache__/0002_video_video_type.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/__init__.cpython-311.pyc b/core/migrations/__pycache__/__init__.cpython-311.pyc index 9c833c8..8956bb3 100644 Binary files a/core/migrations/__pycache__/__init__.cpython-311.pyc and b/core/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 71a8362..9d9a14b 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,72 @@ from django.db import models +from django.contrib.auth.models import User +from django.utils.text import slugify +import uuid -# Create your models here. +class Category(models.Model): + name = models.CharField(max_length=100) + slug = models.SlugField(unique=True, blank=True) + + def save(self, *args, **kwargs): + if not self.slug: + self.slug = slugify(self.name) + super().save(*args, **kwargs) + + class Meta: + verbose_name_plural = "Categories" + + def __str__(self): + return self.name + +class Channel(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='channel') + name = models.CharField(max_length=255) + description = models.TextField(blank=True) + handle = models.CharField(max_length=100, unique=True) + thumbnail = models.ImageField(upload_to='channels/thumbnails/', blank=True, null=True) + banner = models.ImageField(upload_to='channels/banners/', blank=True, null=True) + subscribers = models.ManyToManyField(User, related_name='subscriptions', blank=True) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.name + +class Video(models.Model): + TYPE_CHOICES = ( + ('standard', 'Standard'), + ('short', 'Short'), + ('live_recording', 'Live Recording'), + ) + + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + channel = models.ForeignKey(Channel, on_delete=models.CASCADE, related_name='videos') + title = models.CharField(max_length=255) + description = models.TextField(blank=True) + thumbnail = models.ImageField(upload_to='videos/thumbnails/') + video_file = models.FileField(upload_to='videos/raw/', blank=True, null=True) + hls_url = models.URLField(blank=True, null=True, help_text="S3/CloudFront HLS URL") + views = models.PositiveIntegerField(default=0) + category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='videos') + video_type = models.CharField(max_length=20, choices=TYPE_CHOICES, default='standard') + is_published = models.BooleanField(default=True) + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ['-created_at'] + + def __str__(self): + return self.title + +class LiveStream(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + channel = models.ForeignKey(Channel, on_delete=models.CASCADE, related_name='live_streams') + title = models.CharField(max_length=255) + description = models.TextField(blank=True) + thumbnail = models.ImageField(upload_to='live/thumbnails/') + stream_key = models.CharField(max_length=100, unique=True, default=uuid.uuid4) + is_live = models.BooleanField(default=False) + viewer_count = models.PositiveIntegerField(default=0) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"{self.channel.name} - {self.title}" diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..3128f3e 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,25 +1,96 @@ +{% load static %} - - - {% block title %}Knowledge Base{% endblock %} - {% if project_description %} - - - - {% endif %} - {% if project_image_url %} - - - {% endif %} - {% load static %} - - {% block head %}{% endblock %} + + + {% block title %}LIVE VERSE | Universal Cosmic Platform{% endblock %} + + + + {% block extra_css %}{% endblock %} - - {% block content %}{% endblock %} - + + + + + + + +
+ {% block content %}{% endblock %} +
+ + + {% block extra_js %}{% endblock %} + + \ No newline at end of file diff --git a/core/templates/core/index.html b/core/templates/core/index.html index faec813..094cd45 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,145 +1,86 @@ -{% extends "base.html" %} - -{% block title %}{{ project_name }}{% endblock %} - -{% block head %} - - - - -{% endblock %} +{% extends 'base.html' %} +{% load static %} {% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… +
+ + +
+
All Galaxies
+ {% for category in categories %} +
{{ category.name }}
+ {% endfor %} +
Nebulas
+
Supernovas
+
Dark Matter
+
Stellar Events
-

AppWizzy AI is collecting your requirements and applying the first changes.

-

This page will refresh automatically as the plan is implemented.

-

- Runtime: Django {{ django_version }} · Python {{ python_version }} - — UTC {{ current_time|date:"Y-m-d H:i:s" }} -

-
-
- -{% endblock %} \ No newline at end of file + + +
+

Cosmic Shorts

+ View All +
+ +
+ {% for i in "123456" %} +
+
+
Diving into the Heart of a Black Hole | Episode {{ i }}
+
1.2M views
+
+ +
+ +
+
+ {% endfor %} +
+ + +
+

Universal Discovery

+
+ +
+ {% for video in videos %} +
+
+ {% if video.thumbnail %} + {{ video.title }} + {% else %} +
+ +
+ {% endif %} +
12:45
+
+
+
+
+
+ +
+
+
+

{{ video.title }}

+

{{ video.channel.name }}

+
+ {{ video.views }} views + + {{ video.created_at|timesince }} ago +
+
+
+
+
+ {% empty %} +
+ +

No cosmic events discovered yet.

+

Be the first to upload to this galaxy!

+
+ {% endfor %} +
+ +{% endblock %} diff --git a/core/templates/core/live_studio.html b/core/templates/core/live_studio.html new file mode 100644 index 0000000..9935bea --- /dev/null +++ b/core/templates/core/live_studio.html @@ -0,0 +1,81 @@ +{% extends 'base.html' %} + +{% block title %}Live Studio | LIVE VERSE{% endblock %} + +{% block content %} +
+
+

Creator Studio

+
+ + New Upload +
+
+ +
+ +
+
+
+
Total Broadcast Reach
+
1.2M
+
+
+
Subscribers
+
45.2K
+
+
+
Avg. Views
+
8.4K
+
+
+
+
+
+ + +
+
+
+
Recent Transmissions
+
+
+
+ + + + + + + + + + + + {% for video in videos %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
ContentStatusViewsCommentsDate
+
+ +
{{ video.title }}
+
+
Public{{ video.views }}42{{ video.created_at|date:"M d, Y" }}
No transmissions found in your vault.
+
+
+
+
+
+
+{% endblock %} diff --git a/core/templates/core/shorts_list.html b/core/templates/core/shorts_list.html new file mode 100644 index 0000000..fe5f97b --- /dev/null +++ b/core/templates/core/shorts_list.html @@ -0,0 +1,117 @@ +{% extends 'base.html' %} + +{% block title %}Shorts | LIVE VERSE{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+ {% for short in shorts %} +
+
+ {% if short.thumbnail %} + + {% endif %} +
+ +
+
+ +
+
+
12K
+
+
842
+
+
+
+ +
+
+
+
@{{ short.channel.handle }}
+ +
+
{{ short.title }}
+

Original Sound - Space Melodies

+
+
+ {% empty %} +
+ +

No Shorts Orbiting Yet

+

Start uploading 9:16 cosmic content!

+
+ {% endfor %} +
+{% endblock %} diff --git a/core/templates/core/upload_video.html b/core/templates/core/upload_video.html new file mode 100644 index 0000000..c6933ee --- /dev/null +++ b/core/templates/core/upload_video.html @@ -0,0 +1,61 @@ +{% extends 'base.html' %} + +{% block title %}Upload to the Galaxy | LIVE VERSE{% endblock %} + +{% block content %} +
+
+
+
+
+

Transmit New Content

+

Your content will be broadcasted across the universal feed.

+
+
+
+ {% csrf_token %} + +
+ + {{ form.title }} +
+ +
+ + {{ form.description }} +
+ +
+
+ + {{ form.category }} +
+
+ + {{ form.video_type }} +
+
+ +
+
+ + {{ form.thumbnail }} +
+
+ + {{ form.video_file }} +
+
+ +
+ +
+
+
+
+
+
+
+{% endblock %} diff --git a/core/templates/core/video_detail.html b/core/templates/core/video_detail.html new file mode 100644 index 0000000..e62a688 --- /dev/null +++ b/core/templates/core/video_detail.html @@ -0,0 +1,92 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}{{ video.title }} | LIVE VERSE{% endblock %} + +{% block content %} +
+
+ +
+ +
+ {% if video.video_file %} + + {% else %} +
+ +

Establishing Intergalactic Link...

+

Wait for the signal to stabilize.

+
+ {% endif %} +
+ +
+

{{ video.title }}

+ +
+
+
+ +
+
+
{{ video.channel.name }}
+
452K Subscribers
+
+ +
+ +
+ + + + +
+
+ +
+
{{ video.views }} views • {{ video.created_at|date:"M d, Y" }}
+

{{ video.description|default:"No cosmic log provided for this transmission." }}

+
+
+
+ + +
+
Next in Orbit
+ +
+
+
+ + +{% endblock %} diff --git a/core/urls.py b/core/urls.py index 6299e3d..f221871 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,10 @@ from django.urls import path - -from .views import home +from . import views urlpatterns = [ - path("", home, name="home"), + path('', views.IndexView.as_view(), name='index'), + path('video//', views.VideoDetailView.as_view(), name='video_detail'), + path('shorts/', views.ShortsListView.as_view(), name='shorts_list'), + path('studio/', views.LiveStudioView.as_view(), name='live_studio'), + path('upload/', views.UploadVideoView.as_view(), name='upload_video'), ] diff --git a/core/views.py b/core/views.py index c9aed12..b788761 100644 --- a/core/views.py +++ b/core/views.py @@ -1,25 +1,74 @@ -import os -import platform +from django.shortcuts import render, get_object_or_404, redirect +from django.views.generic import ListView, DetailView, CreateView, TemplateView +from django.contrib.auth.mixins import LoginRequiredMixin +from django.urls import reverse_lazy +from .models import Video, Channel, Category, LiveStream +from .forms import VideoUploadForm -from django import get_version as django_version -from django.shortcuts import render -from django.utils import timezone +class IndexView(ListView): + model = Video + template_name = 'core/index.html' + context_object_name = 'videos' + paginate_by = 20 + def get_queryset(self): + return Video.objects.filter(is_published=True, video_type='standard').select_related('channel') + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['categories'] = Category.objects.all() + context['shorts'] = Video.objects.filter(video_type='short', is_published=True)[:10] + return context + +class VideoDetailView(DetailView): + model = Video + template_name = 'core/video_detail.html' + context_object_name = 'video' + + def get_object(self): + obj = super().get_object() + obj.views += 1 + obj.save() + return obj + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['related_videos'] = Video.objects.filter(category=self.object.category).exclude(id=self.object.id)[:10] + return context + +class ShortsListView(ListView): + model = Video + template_name = 'core/shorts_list.html' + context_object_name = 'shorts' + + def get_queryset(self): + return Video.objects.filter(video_type='short', is_published=True) + +class LiveStudioView(LoginRequiredMixin, TemplateView): + template_name = 'core/live_studio.html' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + channel = getattr(self.request.user, 'channel', None) + if channel: + context['live_streams'] = LiveStream.objects.filter(channel=channel) + context['videos'] = Video.objects.filter(channel=channel) + return context + +class UploadVideoView(LoginRequiredMixin, CreateView): + model = Video + form_class = VideoUploadForm + template_name = 'core/upload_video.html' + success_url = reverse_lazy('index') + + def form_valid(self, form): + # Ensure user has a channel + channel, created = Channel.objects.get_or_create( + user=self.request.user, + defaults={'name': self.request.user.username, 'handle': self.request.user.username.lower()} + ) + form.instance.channel = channel + return super().form_valid(form) def home(request): - """Render the landing screen with loader and environment details.""" - host_name = request.get_host().lower() - agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" - now = timezone.now() - - context = { - "project_name": "New Style", - "agent_brand": agent_brand, - "django_version": django_version(), - "python_version": platform.python_version(), - "current_time": now, - "host_name": host_name, - "project_description": os.getenv("PROJECT_DESCRIPTION", ""), - "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), - } - return render(request, "core/index.html", context) + return IndexView.as_view()(request) diff --git a/seed_data.py b/seed_data.py new file mode 100644 index 0000000..6d1d260 --- /dev/null +++ b/seed_data.py @@ -0,0 +1,32 @@ +import os +import django + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') +django.setup() + +from core.models import Category, Channel, Video +from django.contrib.auth.models import User + +# Create categories +categories = ['Music', 'Gaming', 'Technology', 'Education', 'Entertainment'] +for cat_name in categories: + Category.objects.get_or_create(name=cat_name) + +# Create a sample user and channel if none exist +user, created = User.objects.get_or_create(username='admin') +if created: + user.set_password('admin123') + user.is_staff = True + user.is_superuser = True + user.save() + +Channel.objects.get_or_create( + user=user, + defaults={ + 'name': 'Live Verse Official', + 'handle': '@liveverse', + 'description': 'Welcome to the official Live Verse channel!' + } +) + +print("Seed data created successfully.") diff --git a/static/css/custom.css b/static/css/custom.css index 925f6ed..4d0d7a6 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -1,4 +1,350 @@ -/* Custom styles for the application */ -body { - font-family: system-ui, -apple-system, sans-serif; +@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap'); + +:root { + --lv-accent: #FF3B3B; /* Crimson Red */ + --lv-accent-glow: rgba(255, 59, 59, 0.5); + --lv-black: #080808; + --lv-deep-space: #0F0000; + --lv-surface: rgba(20, 20, 20, 0.8); + --lv-text: #FFFFFF; + --lv-text-muted: #A0A0A0; + --lv-border: rgba(255, 59, 59, 0.15); + --glass-bg: rgba(15, 0, 0, 0.85); + --glass-border: rgba(255, 255, 255, 0.05); + --gradient-cosmic: linear-gradient(135deg, #FF3B3B 0%, #660000 100%); } + +body { + background: radial-gradient(circle at 50% 50%, #1a0000 0%, var(--lv-black) 100%); + background-attachment: fixed; + color: var(--lv-text); + font-family: 'Outfit', sans-serif; + margin: 0; + overflow-x: hidden; +} + +/* Red Starfield Effect */ +body::before { + content: ""; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: + radial-gradient(#ff0000, rgba(255,0,0,.1) 1px, transparent 40px), + radial-gradient(#ffffff, rgba(255,255,255,.05) 1px, transparent 30px); + background-size: 400px 400px, 300px 300px; + background-position: 0 0, 50px 50px; + opacity: 0.2; + z-index: -1; + pointer-events: none; + animation: drift 100s linear infinite; +} + +@keyframes drift { + from { background-position: 0 0; } + to { background-position: 400px 400px; } +} + +h1, h2, h3, h4, h5, h6 { + font-family: 'Space Grotesk', sans-serif; +} + +/* Navbar - Universal Style */ +.navbar { + background: var(--glass-bg); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 1px solid var(--lv-border); + padding: 1rem 2rem; + position: sticky; + top: 0; + z-index: 1000; +} + +.navbar-brand { + font-size: 1.6rem; + font-weight: 700; + letter-spacing: -0.5px; + background: linear-gradient(to right, #fff, var(--lv-accent)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + display: flex; + align-items: center; +} + +.navbar-brand i { + font-size: 1.8rem; + -webkit-text-fill-color: var(--lv-accent); + margin-right: 12px; + filter: drop-shadow(0 0 10px var(--lv-accent-glow)); +} + +.search-bar-container { + max-width: 500px; + width: 100%; +} + +.search-bar { + background: rgba(255, 255, 255, 0.03); + border: 1px solid var(--lv-border); + border-radius: 12px; + color: white; + padding: 0.7rem 1.2rem; + width: 100%; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.search-bar:focus { + outline: none; + background: rgba(255, 255, 255, 0.05); + border-color: var(--lv-accent); + box-shadow: 0 0 20px rgba(255, 59, 59, 0.2); +} + +/* Floating Sidebar - Unique Layout */ +.sidebar { + width: 260px; + height: calc(100vh - 120px); + position: fixed; + left: 20px; + top: 100px; + background: var(--lv-surface); + backdrop-filter: blur(10px); + border-radius: 24px; + padding: 1.5rem 1rem; + overflow-y: auto; + border: 1px solid var(--lv-border); + transition: all 0.3s ease; +} + +.sidebar-item { + display: flex; + align-items: center; + padding: 0.85rem 1.2rem; + color: var(--lv-text-muted); + text-decoration: none; + border-radius: 14px; + margin-bottom: 0.5rem; + transition: all 0.2s; + font-weight: 500; +} + +.sidebar-item:hover { + background-color: rgba(255, 59, 59, 0.08); + color: var(--lv-text); + transform: translateX(5px); +} + +.sidebar-item.active { + background: var(--gradient-cosmic); + color: #fff; + box-shadow: 0 4px 15px rgba(255, 59, 59, 0.3); +} + +.sidebar-item i { + margin-right: 1rem; + font-size: 1.2rem; + width: 24px; + text-align: center; +} + +/* Main Content */ +.main-wrapper { + margin-left: 300px; + padding: 2rem 3rem 5rem 1rem; +} + +/* Section Titles */ +.section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; +} + +.section-title { + font-size: 1.8rem; + font-weight: 700; + position: relative; + padding-left: 1rem; +} + +.section-title::before { + content: ""; + position: absolute; + left: 0; + top: 20%; + height: 60%; + width: 4px; + background: var(--lv-accent); + border-radius: 2px; + box-shadow: 0 0 10px var(--lv-accent-glow); +} + +/* Shorts Grid - Vertical Layout */ +.shorts-grid { + display: flex; + gap: 1.5rem; + overflow-x: auto; + padding-bottom: 1.5rem; + margin-bottom: 3rem; + scrollbar-width: thin; + scrollbar-color: var(--lv-border) transparent; +} + +.short-card { + min-width: 200px; + aspect-ratio: 9/16; + background: #111; + border-radius: 20px; + overflow: hidden; + position: relative; + border: 1px solid var(--lv-border); + transition: all 0.3s ease; + cursor: pointer; +} + +.short-card:hover { + transform: scale(1.03); + border-color: var(--lv-accent); + box-shadow: 0 0 20px rgba(255, 59, 59, 0.2); +} + +.short-overlay { + position: absolute; + bottom: 0; + left: 0; + right: 0; + padding: 1.5rem 1rem; + background: linear-gradient(transparent, rgba(0,0,0,0.8)); +} + +.short-title { + font-size: 0.95rem; + font-weight: 600; + margin-bottom: 0.3rem; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +/* Video Cards - Polished */ +.video-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 2rem; +} + +.video-card { + background: rgba(255, 255, 255, 0.02); + border-radius: 24px; + overflow: hidden; + border: 1px solid var(--glass-border); + transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); +} + +.video-card:hover { + transform: translateY(-10px); + background: rgba(255, 255, 255, 0.05); + border-color: var(--lv-border); + box-shadow: 0 20px 40px rgba(0,0,0,0.4); +} + +.thumbnail-container { + position: relative; + aspect-ratio: 16/9; +} + +.thumbnail-container img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.duration-badge { + position: absolute; + bottom: 10px; + right: 10px; + background: rgba(0,0,0,0.8); + backdrop-filter: blur(5px); + padding: 2px 8px; + border-radius: 6px; + font-size: 0.75rem; + font-weight: 600; +} + +.video-meta { + padding: 1.2rem; +} + +.video-title { + font-size: 1.1rem; + font-weight: 600; + margin-bottom: 0.6rem; + line-height: 1.3; + color: #fff; +} + +/* Action Buttons */ +.action-btn { + background: var(--lv-surface); + border: 1px solid var(--lv-border); + color: var(--lv-text); + padding: 0.6rem 1.2rem; + border-radius: 12px; + font-weight: 600; + display: flex; + align-items: center; + gap: 0.8rem; + transition: all 0.3s; +} + +.action-btn:hover { + background: var(--gradient-cosmic); + border-color: var(--lv-accent); + box-shadow: 0 0 15px var(--lv-accent-glow); +} + +.upload-btn { + background: var(--gradient-cosmic); + color: white; + border: none; + padding: 0.7rem 1.5rem; + border-radius: 14px; + font-weight: 600; + box-shadow: 0 4px 15px rgba(255, 59, 59, 0.3); +} + +/* Category Pill - Red */ +.category-badge { + background: rgba(255, 59, 59, 0.05); + color: var(--lv-text-muted); + padding: 0.6rem 1.4rem; + border-radius: 30px; + white-space: nowrap; + cursor: pointer; + border: 1px solid var(--lv-border); + font-size: 0.9rem; + font-weight: 600; + transition: all 0.3s; +} + +.category-badge:hover, .category-badge.active { + background: var(--lv-accent); + color: #fff; + border-color: var(--lv-accent); + box-shadow: 0 0 15px var(--lv-accent-glow); +} + +@media (max-width: 992px) { + .sidebar { + display: none; + } + .main-wrapper { + margin-left: 0; + padding: 1.5rem; + } +} \ No newline at end of file