Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e63b13f50c |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
core/__pycache__/forms.cpython-311.pyc
Normal file
BIN
core/__pycache__/forms.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,3 +1,23 @@
|
|||||||
from django.contrib import admin
|
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',)
|
||||||
15
core/forms.py
Normal file
15
core/forms.py
Normal file
@ -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'}),
|
||||||
|
}
|
||||||
76
core/migrations/0001_initial.py
Normal file
76
core/migrations/0001_initial.py
Normal file
@ -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'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
18
core/migrations/0002_video_video_type.py
Normal file
18
core/migrations/0002_video_video_type.py
Normal file
@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,3 +1,72 @@
|
|||||||
from django.db import models
|
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}"
|
||||||
|
|||||||
@ -1,25 +1,96 @@
|
|||||||
|
{% load static %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>{% block title %}Knowledge Base{% endblock %}</title>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
{% if project_description %}
|
<title>{% block title %}LIVE VERSE | Universal Cosmic Platform{% endblock %}</title>
|
||||||
<meta name="description" content="{{ project_description }}">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<meta property="og:description" content="{{ project_description }}">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
<meta property="twitter:description" content="{{ project_description }}">
|
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp|default:'1.2' }}">
|
||||||
{% endif %}
|
{% block extra_css %}{% endblock %}
|
||||||
{% if project_image_url %}
|
|
||||||
<meta property="og:image" content="{{ project_image_url }}">
|
|
||||||
<meta property="twitter:image" content="{{ project_image_url }}">
|
|
||||||
{% endif %}
|
|
||||||
{% load static %}
|
|
||||||
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
|
|
||||||
{% block head %}{% endblock %}
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
{% block content %}{% endblock %}
|
<!-- Top Navbar -->
|
||||||
</body>
|
<nav class="navbar navbar-expand-lg">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="{% url 'index' %}">
|
||||||
|
<i class="fas fa-meteor"></i>
|
||||||
|
<span>LIVE VERSE</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button class="navbar-toggler text-white border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navbarContent">
|
||||||
|
<i class="fas fa-bars"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse" id="navbarContent">
|
||||||
|
<div class="search-bar-container mx-auto">
|
||||||
|
<form action="{% url 'index' %}" method="GET">
|
||||||
|
<input type="text" name="q" class="search-bar" placeholder="Explore the universe of content...">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center gap-3 mt-3 mt-lg-0">
|
||||||
|
<a href="{% url 'upload_video' %}" class="upload-btn text-decoration-none">
|
||||||
|
<i class="fas fa-plus"></i> Create
|
||||||
|
</a>
|
||||||
|
<a href="#" class="text-white fs-5"><i class="far fa-bell"></i></a>
|
||||||
|
<a href="/admin/" class="btn btn-outline-danger btn-sm rounded-pill px-3">
|
||||||
|
<i class="fas fa-user-shield me-1"></i> Admin
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Sidebar Navigation -->
|
||||||
|
<aside class="sidebar">
|
||||||
|
<a href="{% url 'index' %}" class="sidebar-item {% if request.resolver_match.url_name == 'index' %}active{% endif %}">
|
||||||
|
<i class="fas fa-home"></i> Explore
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'shorts_list' %}" class="sidebar-item {% if request.resolver_match.url_name == 'shorts_list' %}active{% endif %}">
|
||||||
|
<i class="fas fa-bolt"></i> Shorts
|
||||||
|
</a>
|
||||||
|
<a href="#" class="sidebar-item">
|
||||||
|
<i class="fas fa-satellite-dish"></i> Live Streams
|
||||||
|
</a>
|
||||||
|
<a href="#" class="sidebar-item">
|
||||||
|
<i class="fas fa-users"></i> Communities
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<hr class="mx-3 opacity-10">
|
||||||
|
|
||||||
|
<div class="px-4 mb-2 small text-muted text-uppercase tracking-wider">Creator Center</div>
|
||||||
|
<a href="{% url 'live_studio' %}" class="sidebar-item {% if request.resolver_match.url_name == 'live_studio' %}active{% endif %}">
|
||||||
|
<i class="fas fa-vr-cardboard"></i> Live Studio
|
||||||
|
</a>
|
||||||
|
<a href="#" class="sidebar-item">
|
||||||
|
<i class="fas fa-chart-line"></i> Analytics
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'upload_video' %}" class="sidebar-item {% if request.resolver_match.url_name == 'upload_video' %}active{% endif %}">
|
||||||
|
<i class="fas fa-upload"></i> Upload Vault
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<hr class="mx-3 opacity-10">
|
||||||
|
|
||||||
|
<div class="px-4 mb-2 small text-muted text-uppercase tracking-wider">Your Galaxy</div>
|
||||||
|
<a href="#" class="sidebar-item">
|
||||||
|
<i class="fas fa-history"></i> History
|
||||||
|
</a>
|
||||||
|
<a href="#" class="sidebar-item">
|
||||||
|
<i class="fas fa-bookmark"></i> Library
|
||||||
|
</a>
|
||||||
|
<a href="#" class="sidebar-item">
|
||||||
|
<i class="fas fa-heart"></i> Liked Videos
|
||||||
|
</a>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="main-wrapper">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
{% block extra_js %}{% endblock %}
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -1,145 +1,86 @@
|
|||||||
{% extends "base.html" %}
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
{% block title %}{{ project_name }}{% endblock %}
|
|
||||||
|
|
||||||
{% block head %}
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg-color-start: #6a11cb;
|
|
||||||
--bg-color-end: #2575fc;
|
|
||||||
--text-color: #ffffff;
|
|
||||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
|
||||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
|
||||||
color: var(--text-color);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><path d='M-10 10L110 10M10 -10L10 110' stroke-width='1' stroke='rgba(255,255,255,0.05)'/></svg>");
|
|
||||||
animation: bg-pan 20s linear infinite;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes bg-pan {
|
|
||||||
0% {
|
|
||||||
background-position: 0% 0%;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
background-position: 100% 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
background: var(--card-bg-color);
|
|
||||||
border: 1px solid var(--card-border-color);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 2.5rem 2rem;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: clamp(2.2rem, 3vw + 1.2rem, 3.2rem);
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0 0 1.2rem;
|
|
||||||
letter-spacing: -0.02em;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
opacity: 0.92;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
margin: 1.5rem auto;
|
|
||||||
width: 56px;
|
|
||||||
height: 56px;
|
|
||||||
border: 4px solid rgba(255, 255, 255, 0.25);
|
|
||||||
border-top-color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.runtime code {
|
|
||||||
background: rgba(0, 0, 0, 0.25);
|
|
||||||
padding: 0.15rem 0.45rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
padding: 0;
|
|
||||||
margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1rem;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main>
|
<div class="container-fluid p-0">
|
||||||
<div class="card">
|
|
||||||
<h1>Analyzing your requirements and generating your app…</h1>
|
<!-- Category Filters -->
|
||||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
<div class="category-filters">
|
||||||
<span class="sr-only">Loading…</span>
|
<div class="category-badge active">All Galaxies</div>
|
||||||
|
{% for category in categories %}
|
||||||
|
<div class="category-badge">{{ category.name }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
<div class="category-badge">Nebulas</div>
|
||||||
|
<div class="category-badge">Supernovas</div>
|
||||||
|
<div class="category-badge">Dark Matter</div>
|
||||||
|
<div class="category-badge">Stellar Events</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="hint">AppWizzy AI is collecting your requirements and applying the first changes.</p>
|
|
||||||
<p class="hint">This page will refresh automatically as the plan is implemented.</p>
|
<!-- Cosmic Shorts Section -->
|
||||||
<p class="runtime">
|
<div class="section-header mt-4">
|
||||||
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
|
<h2 class="section-title">Cosmic Shorts</h2>
|
||||||
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
|
<a href="#" class="text-danger text-decoration-none fw-bold">View All <i class="fas fa-arrow-right ms-1"></i></a>
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
|
||||||
<footer>
|
<div class="shorts-grid">
|
||||||
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
|
{% for i in "123456" %}
|
||||||
</footer>
|
<div class="short-card">
|
||||||
|
<div class="short-overlay">
|
||||||
|
<div class="short-title">Diving into the Heart of a Black Hole | Episode {{ i }}</div>
|
||||||
|
<div class="small opacity-75"><i class="fas fa-eye me-1"></i> 1.2M views</div>
|
||||||
|
</div>
|
||||||
|
<!-- Placeholder for short image/video -->
|
||||||
|
<div class="w-100 h-100 bg-dark d-flex align-items-center justify-content-center">
|
||||||
|
<i class="fas fa-bolt fa-3x text-danger opacity-25"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Video Feed -->
|
||||||
|
<div class="section-header mt-5">
|
||||||
|
<h2 class="section-title">Universal Discovery</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="video-grid">
|
||||||
|
{% for video in videos %}
|
||||||
|
<div class="video-card" onclick="location.href='{% url 'video_detail' video.id %}'">
|
||||||
|
<div class="thumbnail-container">
|
||||||
|
{% if video.thumbnail %}
|
||||||
|
<img src="{{ video.thumbnail.url }}" alt="{{ video.title }}">
|
||||||
|
{% else %}
|
||||||
|
<div class="w-100 h-100 bg-dark d-flex align-items-center justify-content-center">
|
||||||
|
<i class="fas fa-meteor fa-3x text-danger opacity-25"></i>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="duration-badge">12:45</div>
|
||||||
|
</div>
|
||||||
|
<div class="video-meta">
|
||||||
|
<div class="d-flex gap-3">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<div class="rounded-circle bg-danger bg-opacity-25 d-flex align-items-center justify-content-center" style="width: 44px; height: 44px; border: 1px solid var(--lv-border);">
|
||||||
|
<i class="fas fa-user-astronaut text-danger"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="video-title">{{ video.title }}</h3>
|
||||||
|
<p class="mb-1 text-muted small fw-bold">{{ video.channel.name }}</p>
|
||||||
|
<div class="d-flex gap-2 text-muted x-small">
|
||||||
|
<span>{{ video.views }} views</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>{{ video.created_at|timesince }} ago</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<div class="col-12 text-center py-5">
|
||||||
|
<i class="fas fa-box-open fa-4x mb-3 text-muted"></i>
|
||||||
|
<h3>No cosmic events discovered yet.</h3>
|
||||||
|
<p class="text-muted">Be the first to upload to this galaxy!</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
81
core/templates/core/live_studio.html
Normal file
81
core/templates/core/live_studio.html
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Live Studio | LIVE VERSE{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2 class="section-title">Creator Studio</h2>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button class="action-btn"><i class="fas fa-signal"></i> Go Live</button>
|
||||||
|
<a href="{% url 'upload_video' %}" class="action-btn text-decoration-none"><i class="fas fa-plus"></i> New Upload</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-4">
|
||||||
|
<!-- Analytics Overview -->
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card h-100" style="background: var(--lv-surface); border: 1px solid var(--lv-border); border-radius: 20px;">
|
||||||
|
<div class="card-body p-4 text-center">
|
||||||
|
<div class="text-muted small text-uppercase mb-2">Total Broadcast Reach</div>
|
||||||
|
<div class="display-6 fw-bold text-danger mb-3">1.2M</div>
|
||||||
|
<div class="d-flex justify-content-center gap-4 border-top border-secondary pt-3 mt-3">
|
||||||
|
<div>
|
||||||
|
<div class="small text-muted">Subscribers</div>
|
||||||
|
<div class="fw-bold">45.2K</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="small text-muted">Avg. Views</div>
|
||||||
|
<div class="fw-bold">8.4K</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Recent Content -->
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card" style="background: var(--lv-surface); border: 1px solid var(--lv-border); border-radius: 20px;">
|
||||||
|
<div class="card-header bg-transparent border-secondary p-4">
|
||||||
|
<h5 class="mb-0">Recent Transmissions</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-dark table-hover mb-0 align-middle">
|
||||||
|
<thead class="text-muted small">
|
||||||
|
<tr>
|
||||||
|
<th class="ps-4">Content</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Views</th>
|
||||||
|
<th>Comments</th>
|
||||||
|
<th class="pe-4 text-end">Date</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for video in videos %}
|
||||||
|
<tr>
|
||||||
|
<td class="ps-4">
|
||||||
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
<img src="{{ video.thumbnail.url }}" style="width: 80px; height: 45px; border-radius: 8px; object-fit: cover;">
|
||||||
|
<div class="fw-semibold">{{ video.title }}</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td><span class="badge bg-success bg-opacity-10 text-success rounded-pill px-3">Public</span></td>
|
||||||
|
<td>{{ video.views }}</td>
|
||||||
|
<td>42</td>
|
||||||
|
<td class="pe-4 text-end text-muted">{{ video.created_at|date:"M d, Y" }}</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="text-center py-5 text-muted">No transmissions found in your vault.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
117
core/templates/core/shorts_list.html
Normal file
117
core/templates/core/shorts_list.html
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Shorts | LIVE VERSE{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<style>
|
||||||
|
.shorts-container {
|
||||||
|
max-width: 450px;
|
||||||
|
margin: 0 auto;
|
||||||
|
height: calc(100vh - 100px);
|
||||||
|
overflow-y: scroll;
|
||||||
|
scroll-snap-type: y mandatory;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
.shorts-container::-webkit-scrollbar { display: none; }
|
||||||
|
|
||||||
|
.short-viewer-card {
|
||||||
|
height: 100%;
|
||||||
|
scroll-snap-align: start;
|
||||||
|
position: relative;
|
||||||
|
background: #000;
|
||||||
|
border-radius: 24px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid var(--lv-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.short-video-placeholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(45deg, #111, #222);
|
||||||
|
}
|
||||||
|
|
||||||
|
.short-controls {
|
||||||
|
position: absolute;
|
||||||
|
right: 15px;
|
||||||
|
bottom: 100px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn:hover {
|
||||||
|
background: var(--lv-accent);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.short-info-overlay {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 20px;
|
||||||
|
right: 80px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="shorts-container">
|
||||||
|
{% for short in shorts %}
|
||||||
|
<div class="short-viewer-card">
|
||||||
|
<div class="short-video-placeholder">
|
||||||
|
{% if short.thumbnail %}
|
||||||
|
<img src="{{ short.thumbnail.url }}" style="width: 100%; height: 100%; object-fit: cover; opacity: 0.6;">
|
||||||
|
{% endif %}
|
||||||
|
<div class="position-absolute">
|
||||||
|
<i class="fas fa-play fa-4x text-white opacity-25"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="short-controls">
|
||||||
|
<div class="control-btn"><i class="fas fa-heart"></i></div>
|
||||||
|
<div class="small fw-bold">12K</div>
|
||||||
|
<div class="control-btn"><i class="fas fa-comment-dots"></i></div>
|
||||||
|
<div class="small fw-bold">842</div>
|
||||||
|
<div class="control-btn"><i class="fas fa-share"></i></div>
|
||||||
|
<div class="control-btn"><i class="fas fa-ellipsis-v"></i></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="short-info-overlay">
|
||||||
|
<div class="d-flex align-items-center gap-2 mb-3">
|
||||||
|
<div class="rounded-circle bg-danger" style="width: 36px; height: 36px;"></div>
|
||||||
|
<div class="fw-bold">@{{ short.channel.handle }}</div>
|
||||||
|
<button class="btn btn-outline-light btn-sm rounded-pill px-3">Subscribe</button>
|
||||||
|
</div>
|
||||||
|
<h5 class="fw-semibold">{{ short.title }}</h5>
|
||||||
|
<p class="small text-muted mb-0">Original Sound - Space Melodies</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<div class="text-center py-5">
|
||||||
|
<i class="fas fa-bolt fa-4x mb-3 text-muted"></i>
|
||||||
|
<h3>No Shorts Orbiting Yet</h3>
|
||||||
|
<p class="text-muted">Start uploading 9:16 cosmic content!</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
61
core/templates/core/upload_video.html
Normal file
61
core/templates/core/upload_video.html
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Upload to the Galaxy | LIVE VERSE{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container py-4">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="card bg-dark text-white border-secondary rounded-4 overflow-hidden shadow-lg" style="background: var(--lv-surface) !important; border-color: var(--lv-border) !important;">
|
||||||
|
<div class="card-header border-secondary p-4" style="background: var(--gradient-cosmic);">
|
||||||
|
<h2 class="h4 mb-0"><i class="fas fa-cloud-upload-alt me-2"></i> Transmit New Content</h2>
|
||||||
|
<p class="small mb-0 opacity-75">Your content will be broadcasted across the universal feed.</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label fw-bold">Title</label>
|
||||||
|
{{ form.title }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label fw-bold">Description</label>
|
||||||
|
{{ form.description }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<label class="form-label fw-bold">Content Category</label>
|
||||||
|
{{ form.category }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<label class="form-label fw-bold">Content Type</label>
|
||||||
|
{{ form.video_type }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<label class="form-label fw-bold">Thumbnail (Image)</label>
|
||||||
|
{{ form.thumbnail }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<label class="form-label fw-bold">Video File</label>
|
||||||
|
{{ form.video_file }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-grid mt-4">
|
||||||
|
<button type="submit" class="upload-btn py-3 fs-5">
|
||||||
|
<i class="fas fa-paper-plane me-2"></i> Launch Broadcast
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
92
core/templates/core/video_detail.html
Normal file
92
core/templates/core/video_detail.html
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}{{ video.title }} | LIVE VERSE{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid px-0 px-lg-4">
|
||||||
|
<div class="row g-4">
|
||||||
|
<!-- Main Video Area -->
|
||||||
|
<div class="col-xl-8">
|
||||||
|
<!-- Video Player Placeholder -->
|
||||||
|
<div class="ratio ratio-16x9 bg-black rounded-4 overflow-hidden border border-secondary shadow-lg mb-4" style="border-color: var(--lv-border) !important;">
|
||||||
|
{% if video.video_file %}
|
||||||
|
<video controls class="w-100 h-100">
|
||||||
|
<source src="{{ video.video_file.url }}" type="video/mp4">
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
{% else %}
|
||||||
|
<div class="d-flex flex-column align-items-center justify-content-center text-center">
|
||||||
|
<i class="fas fa-satellite fa-5x mb-3 text-danger opacity-25"></i>
|
||||||
|
<h3 class="fw-bold">Establishing Intergalactic Link...</h3>
|
||||||
|
<p class="text-muted">Wait for the signal to stabilize.</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="video-info-box p-3">
|
||||||
|
<h1 class="h3 fw-bold mb-3">{{ video.title }}</h1>
|
||||||
|
|
||||||
|
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4 border-bottom border-secondary pb-4">
|
||||||
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
<div class="rounded-circle bg-gradient d-flex align-items-center justify-content-center" style="width: 48px; height: 48px; background: var(--gradient-cosmic);">
|
||||||
|
<i class="fas fa-user-astronaut text-white"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="fw-bold fs-5">{{ video.channel.name }}</div>
|
||||||
|
<div class="text-muted x-small">452K Subscribers</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-danger rounded-pill px-4 ms-2 fw-bold">Subscribe</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button class="action-btn"><i class="far fa-thumbs-up"></i> 12K</button>
|
||||||
|
<button class="action-btn"><i class="fas fa-share-nodes"></i> Share</button>
|
||||||
|
<button class="action-btn"><i class="fas fa-download"></i> Save</button>
|
||||||
|
<button class="action-btn"><i class="fas fa-ellipsis-h"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="description-card p-4 rounded-4" style="background: rgba(255,255,255,0.03); border: 1px solid var(--lv-border);">
|
||||||
|
<div class="fw-bold mb-2">{{ video.views }} views • {{ video.created_at|date:"M d, Y" }}</div>
|
||||||
|
<p class="mb-0 text-muted" style="white-space: pre-line;">{{ video.description|default:"No cosmic log provided for this transmission." }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sidebar / Related Content -->
|
||||||
|
<div class="col-xl-4">
|
||||||
|
<h5 class="fw-bold mb-3 px-2">Next in Orbit</h5>
|
||||||
|
<div class="related-list">
|
||||||
|
{% for related in related_videos %}
|
||||||
|
<div class="d-flex gap-3 mb-3 p-2 rounded-3 hover-effect" onclick="location.href='{% url 'video_detail' related.id %}'" style="cursor: pointer;">
|
||||||
|
<div class="flex-shrink-0" style="width: 160px; height: 90px;">
|
||||||
|
<img src="{{ related.thumbnail.url }}" class="w-100 h-100 object-fit-cover rounded-3 border border-secondary">
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<h6 class="fw-bold mb-1 line-clamp-2 small">{{ related.title }}</h6>
|
||||||
|
<div class="text-muted x-small">{{ related.channel.name }}</div>
|
||||||
|
<div class="text-muted x-small">{{ related.views }} views • {{ related.created_at|timesince }} ago</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<p class="text-muted px-2">No other cosmic events detected nearby.</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.hover-effect:hover {
|
||||||
|
background: rgba(255, 59, 59, 0.05);
|
||||||
|
}
|
||||||
|
.line-clamp-2 {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.x-small { font-size: 0.75rem; }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
@ -1,7 +1,10 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
from .views import home
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", home, name="home"),
|
path('', views.IndexView.as_view(), name='index'),
|
||||||
|
path('video/<uuid:pk>/', 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'),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,25 +1,74 @@
|
|||||||
import os
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
import platform
|
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
|
class IndexView(ListView):
|
||||||
from django.shortcuts import render
|
model = Video
|
||||||
from django.utils import timezone
|
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):
|
def home(request):
|
||||||
"""Render the landing screen with loader and environment details."""
|
return IndexView.as_view()(request)
|
||||||
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)
|
|
||||||
|
|||||||
32
seed_data.py
Normal file
32
seed_data.py
Normal file
@ -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.")
|
||||||
@ -1,4 +1,350 @@
|
|||||||
/* Custom styles for the application */
|
@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');
|
||||||
body {
|
|
||||||
font-family: system-ui, -apple-system, sans-serif;
|
: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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user