Compare commits

...

1 Commits

Author SHA1 Message Date
Flatlogic Bot
e63b13f50c Autosave: 20260211-125643 2026-02-11 12:56:44 +00:00
30 changed files with 1181 additions and 190 deletions

Binary file not shown.

View File

@ -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
View 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'}),
}

View 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'],
},
),
]

View 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),
),
]

View File

@ -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}"

View File

@ -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>
</html> <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>

View File

@ -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> <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">
{% endblock %} <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 %}

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

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

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

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

View File

@ -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'),
] ]

View File

@ -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
View 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.")

View File

@ -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;
}
}