Compare commits
No commits in common. "ai-dev" and "master" have entirely different histories.
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,23 +1,3 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import Category, Channel, Video, LiveStream
|
|
||||||
|
|
||||||
@admin.register(Category)
|
# Register your models here.
|
||||||
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',)
|
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
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'}),
|
|
||||||
}
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
# 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'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# 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),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,72 +1,3 @@
|
|||||||
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
|
|
||||||
|
|
||||||
class Category(models.Model):
|
# Create your models here.
|
||||||
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,96 +1,25 @@
|
|||||||
{% load static %}
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<title>{% block title %}Knowledge Base{% endblock %}</title>
|
||||||
<title>{% block title %}LIVE VERSE | Universal Cosmic Platform{% endblock %}</title>
|
{% if project_description %}
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<meta name="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="og:description" content="{{ project_description }}">
|
||||||
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp|default:'1.2' }}">
|
<meta property="twitter:description" content="{{ project_description }}">
|
||||||
{% block extra_css %}{% endblock %}
|
{% endif %}
|
||||||
|
{% 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>
|
||||||
<!-- Top Navbar -->
|
|
||||||
<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 %}
|
{% 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>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@ -1,86 +1,145 @@
|
|||||||
{% 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 %}
|
||||||
<div class="container-fluid p-0">
|
<main>
|
||||||
|
<div class="card">
|
||||||
<!-- Category Filters -->
|
<h1>Analyzing your requirements and generating your app…</h1>
|
||||||
<div class="category-filters">
|
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||||
<div class="category-badge active">All Galaxies</div>
|
<span class="sr-only">Loading…</span>
|
||||||
{% 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>
|
||||||
<!-- Cosmic Shorts Section -->
|
<p class="hint">This page will refresh automatically as the plan is implemented.</p>
|
||||||
<div class="section-header mt-4">
|
<p class="runtime">
|
||||||
<h2 class="section-title">Cosmic Shorts</h2>
|
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
|
||||||
<a href="#" class="text-danger text-decoration-none fw-bold">View All <i class="fas fa-arrow-right ms-1"></i></a>
|
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</main>
|
||||||
<div class="shorts-grid">
|
<footer>
|
||||||
{% for i in "123456" %}
|
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
|
||||||
<div class="short-card">
|
</footer>
|
||||||
<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 %}
|
||||||
@ -1,81 +0,0 @@
|
|||||||
{% 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 %}
|
|
||||||
@ -1,117 +0,0 @@
|
|||||||
{% 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 %}
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
{% 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 %}
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
{% 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,10 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from . import views
|
|
||||||
|
from .views import home
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.IndexView.as_view(), name='index'),
|
path("", home, name="home"),
|
||||||
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,74 +1,25 @@
|
|||||||
from django.shortcuts import render, get_object_or_404, redirect
|
import os
|
||||||
from django.views.generic import ListView, DetailView, CreateView, TemplateView
|
import platform
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
||||||
from django.urls import reverse_lazy
|
|
||||||
from .models import Video, Channel, Category, LiveStream
|
|
||||||
from .forms import VideoUploadForm
|
|
||||||
|
|
||||||
class IndexView(ListView):
|
from django import get_version as django_version
|
||||||
model = Video
|
from django.shortcuts import render
|
||||||
template_name = 'core/index.html'
|
from django.utils import timezone
|
||||||
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):
|
||||||
return IndexView.as_view()(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)
|
||||||
|
|||||||
32
seed_data.py
32
seed_data.py
@ -1,32 +0,0 @@
|
|||||||
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,350 +1,4 @@
|
|||||||
@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');
|
/* Custom styles for the application */
|
||||||
|
|
||||||
: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 {
|
body {
|
||||||
background: radial-gradient(circle at 50% 50%, #1a0000 0%, var(--lv-black) 100%);
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
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