feat: Landing page, design fixes

This commit is contained in:
Kumi 2025-03-21 17:39:20 +01:00
parent 11d223575e
commit 1aff60a408
Signed by: kumi
GPG key ID: ECBCC9082395383F
11 changed files with 910 additions and 43 deletions

View file

@ -236,6 +236,7 @@ body {
display: flex;
flex-direction: column;
overflow: hidden;
z-index: 999999;
}
.sidebar-header {

504
assets/css/landing.css Normal file
View file

@ -0,0 +1,504 @@
:root {
--color-primary: #4F46E5;
--color-primary-hover: #4338CA;
--color-primary-light: #EEF2FF;
--color-secondary: #10B981;
--color-secondary-hover: #059669;
--color-secondary-light: #ECFDF5;
--color-background: #F9FAFB;
--color-surface: #FFFFFF;
--color-text-primary: #111827;
--color-text-secondary: #6B7280;
--color-text-tertiary: #9CA3AF;
--color-border: #E5E7EB;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--radius-sm: 0.25rem;
--radius: 0.375rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
--radius-full: 9999px;
}
/* Base Styles */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Inter', sans-serif;
line-height: 1.5;
color: var(--color-text-primary);
background-color: var(--color-background);
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1.5rem;
}
/* Header/Navigation */
.site-header {
background-color: var(--color-surface);
box-shadow: var(--shadow-sm);
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
}
.site-header nav {
height: 80px;
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
display: flex;
align-items: center;
text-decoration: none;
color: var(--color-text-primary);
font-weight: 600;
font-size: 1.25rem;
}
.logo img {
height: 32px;
margin-right: 0.75rem;
}
.nav-links {
display: flex;
gap: 2rem;
}
.nav-links a {
color: var(--color-text-secondary);
text-decoration: none;
font-weight: 500;
transition: color 0.2s;
}
.nav-links a:hover {
color: var(--color-primary);
}
.auth-buttons {
display: flex;
gap: 1rem;
}
/* Hero Section */
.hero {
padding: 160px 0 80px;
background: linear-gradient(to bottom, var(--color-primary-light), var(--color-background));
}
.hero .container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
align-items: center;
}
.hero-content h1 {
font-size: 3.5rem;
font-weight: 700;
line-height: 1.2;
margin-bottom: 1.5rem;
}
.hero-content .lead {
font-size: 1.25rem;
color: var(--color-text-secondary);
margin-bottom: 2rem;
}
.hero-buttons {
display: flex;
gap: 1rem;
}
.hero-image img {
width: 100%;
height: auto;
}
/* Features Section */
.features {
padding: 80px 0;
}
.features h2 {
text-align: center;
font-size: 2.5rem;
margin-bottom: 3rem;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
}
.feature-card {
background: var(--color-surface);
padding: 2rem;
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
text-align: center;
}
.feature-icon {
width: 64px;
height: 64px;
margin: 0 auto 1.5rem;
background: var(--color-primary-light);
border-radius: var(--radius);
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
color: var(--color-primary);
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.75rem 1.5rem;
border-radius: var(--radius);
font-weight: 500;
text-decoration: none;
transition: all 0.2s;
}
.btn-lg {
padding: 1rem 2rem;
font-size: 1.125rem;
}
.btn-primary {
background-color: var(--color-primary);
color: white;
}
.btn-primary:hover {
background-color: var(--color-primary-hover);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.btn-secondary {
background-color: var(--color-surface);
color: var(--color-text-primary);
border: 1px solid var(--color-border);
}
.btn-secondary:hover {
background-color: var(--color-background);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
/* How It Works Section */
.how-it-works {
padding: 80px 0;
background-color: var(--color-surface);
}
.how-it-works h2 {
text-align: center;
font-size: 2.5rem;
margin-bottom: 3rem;
}
.steps {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
max-width: 1000px;
margin: 0 auto;
}
.step {
text-align: center;
padding: 2rem;
position: relative;
}
.step-number {
width: 48px;
height: 48px;
background: var(--color-primary);
color: white;
border-radius: var(--radius-full);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
font-weight: 600;
margin: 0 auto 1.5rem;
}
.step h3 {
margin-bottom: 1rem;
font-size: 1.25rem;
}
.step p {
color: var(--color-text-secondary);
}
/* Demo Section */
.demo {
padding: 80px 0;
background-color: var(--color-background);
}
.demo h2 {
text-align: center;
font-size: 2.5rem;
margin-bottom: 3rem;
}
.demo-viewer {
aspect-ratio: 16/9;
background-color: var(--color-surface);
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow-lg);
}
/* Footer */
.site-footer {
background-color: var(--color-surface);
padding: 80px 0 40px;
border-top: 1px solid var(--color-border);
}
.footer-grid {
display: grid;
grid-template-columns: 2fr repeat(3, 1fr);
gap: 4rem;
margin-bottom: 4rem;
}
.footer-brand img {
height: 32px;
margin-bottom: 1rem;
}
.footer-brand p {
color: var(--color-text-secondary);
}
.footer-links h4 {
font-size: 1rem;
margin-bottom: 1.5rem;
}
.footer-links ul {
list-style: none;
}
.footer-links li {
margin-bottom: 0.75rem;
}
.footer-links a {
color: var(--color-text-secondary);
text-decoration: none;
transition: color 0.2s;
}
.footer-links a:hover {
color: var(--color-primary);
}
.footer-bottom {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 2rem;
border-top: 1px solid var(--color-border);
}
.footer-bottom p {
color: var(--color-text-secondary);
}
.social-links {
display: flex;
gap: 1rem;
}
.social-links a {
color: var(--color-text-secondary);
font-size: 1.5rem;
transition: color 0.2s;
}
.social-links a:hover {
color: var(--color-primary);
}
/* Mobile Menu */
.mobile-menu-toggle {
display: none;
background: none;
border: none;
font-size: 1.5rem;
color: var(--color-text-primary);
cursor: pointer;
}
/* Additional Responsive Styles */
@media (max-width: 1024px) {
.footer-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.footer-grid {
grid-template-columns: 1fr;
gap: 2rem;
}
.footer-bottom {
flex-direction: column;
text-align: center;
gap: 1rem;
}
.nav-links.show,
.auth-buttons.show {
display: flex;
flex-direction: column;
position: absolute;
top: 80px;
left: 0;
right: 0;
background: var(--color-surface);
padding: 1rem;
box-shadow: var(--shadow-md);
}
}
/* Animations */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeIn 0.6s ease-out forwards;
}
/* Header Scroll Effects */
.site-header.scroll-down {
transform: translateY(-100%);
transition: transform 0.3s ease-in-out;
}
.site-header.scroll-up {
transform: translateY(0);
transition: transform 0.3s ease-in-out;
box-shadow: var(--shadow-md);
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 12px;
}
::-webkit-scrollbar-track {
background: var(--color-background);
}
::-webkit-scrollbar-thumb {
background: var(--color-text-tertiary);
border-radius: var(--radius-full);
border: 3px solid var(--color-background);
}
::-webkit-scrollbar-thumb:hover {
background: var(--color-text-secondary);
}
/* Focus Styles */
:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
/* Loading States */
.loading {
position: relative;
pointer-events: none;
opacity: 0.7;
}
.loading::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 24px;
height: 24px;
margin: -12px 0 0 -12px;
border: 2px solid var(--color-primary);
border-top-color: transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* Print Styles */
@media print {
.site-header,
.hero-buttons,
.demo-viewer,
.social-links {
display: none;
}
body {
color: black;
background: white;
}
.container {
max-width: none;
padding: 0;
}
a {
text-decoration: none;
color: black;
}
}

View file

@ -17,13 +17,13 @@
--color-success: #10B981;
--color-warning: #F59E0B;
--color-info: #3B82F6;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
--radius-sm: 0.25rem;
--radius: 0.375rem;
--radius-md: 0.5rem;
@ -98,10 +98,12 @@ body {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.05);
opacity: 0.8;
}
100% {
transform: scale(1);
opacity: 1;
@ -160,9 +162,11 @@ body {
width: 0%;
transform: translateX(-100%);
}
50% {
width: 70%;
}
100% {
width: 100%;
transform: translateX(100%);
@ -174,11 +178,40 @@ body {
position: fixed;
top: 1.5rem;
right: 1.5rem;
z-index: 99999;
z-index: 999999;
/* Increase z-index significantly */
display: flex;
flex-direction: column;
gap: 0.75rem;
filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1));
pointer-events: auto;
/* Ensure clicks are registered */
}
body.scene-viewer-body {
position: relative;
}
body.scene-viewer-body::after {
content: "";
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 99990;
/* Just below the controls */
}
quackscape-scene {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
/* Keep this low */
}
.control-btn {
@ -197,6 +230,9 @@ body {
box-shadow: var(--shadow-md);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
text-decoration: none;
position: relative;
z-index: 999999;
pointer-events: auto;
}
.control-btn:hover {
@ -816,10 +852,12 @@ a-entity[data-clickable] {
opacity: 0.8;
transform: scale(1);
}
50% {
opacity: 1;
transform: scale(1.1);
}
100% {
opacity: 0.8;
transform: scale(1);
@ -874,44 +912,44 @@ a-entity[data-clickable] {
right: 1.5rem;
flex-direction: row;
}
.control-btn {
width: 48px;
height: 48px;
}
.control-btn i {
font-size: 1.25rem;
}
.scene-info-panel {
width: 100%;
max-width: 100%;
}
#sceneSidebar {
width: 100%;
max-width: 100%;
}
.btn-open-sidebar {
top: 1rem;
left: 1rem;
padding: 0.5rem 1rem;
}
.scene-title-overlay h1 {
font-size: 1.75rem;
}
.scene-title-overlay p {
font-size: 1rem;
}
.notification-container {
bottom: 5rem;
}
.notification {
padding: 0.75rem 1.25rem;
font-size: 0.875rem;
@ -925,24 +963,24 @@ a-entity[data-clickable] {
right: 1rem;
flex-direction: row;
}
.control-btn {
width: 40px;
height: 40px;
}
.control-btn i {
font-size: 1.125rem;
}
.scene-info-panel {
max-height: 100%;
}
.info-header {
padding: 0.75rem 1.25rem;
}
.info-header h2 {
font-size: 1.25rem;
}
@ -955,14 +993,14 @@ a-entity[data-clickable] {
color: black;
border: 2px solid black;
}
.scene-info-panel,
#sceneSidebar {
background-color: white;
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
.notification {
background-color: white;
color: black;
@ -974,6 +1012,7 @@ a-entity[data-clickable] {
/* Reduced motion preferences */
@media (prefers-reduced-motion: reduce) {
.scene-loading.loaded,
.scene-title-overlay,
.scene-info-panel,
@ -985,7 +1024,7 @@ a-entity[data-clickable] {
.scene-thumbnail img {
transition: none;
}
.pulse,
.loading-spinner,
.progress-bar,

52
assets/js/landing.js Normal file
View file

@ -0,0 +1,52 @@
import '../css/landing.css';
import '@phosphor-icons/web/light';
document.addEventListener('DOMContentLoaded', function() {
// Mobile menu toggle
const mobileMenuBtn = document.querySelector('.mobile-menu-toggle');
const navLinks = document.querySelector('.nav-links');
const authButtons = document.querySelector('.auth-buttons');
if (mobileMenuBtn) {
mobileMenuBtn.addEventListener('click', function() {
navLinks.classList.toggle('show');
authButtons.classList.toggle('show');
});
}
// Smooth scrolling for anchor links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth'
});
}
});
});
// Header scroll effect
const header = document.querySelector('.site-header');
let lastScroll = 0;
window.addEventListener('scroll', () => {
const currentScroll = window.pageYOffset;
if (currentScroll <= 0) {
header.classList.remove('scroll-up');
return;
}
if (currentScroll > lastScroll && !header.classList.contains('scroll-down')) {
header.classList.remove('scroll-up');
header.classList.add('scroll-down');
} else if (currentScroll < lastScroll && header.classList.contains('scroll-down')) {
header.classList.remove('scroll-down');
header.classList.add('scroll-up');
}
lastScroll = currentScroll;
});
});

View file

@ -108,6 +108,7 @@ async function loadSidebar(scene_id, destination) {
// Create open button
const openButton = document.createElement("button");
openButton.setAttribute("class", "btn-open-sidebar hide");
openButton.setAttribute("id", "btnOpenSidebar");
openButton.innerHTML = `<i class="ph-light ph-list"></i> Scenes`;
openButton.addEventListener("click", function () {
@ -135,7 +136,7 @@ async function loadSidebar(scene_id, destination) {
// Toggle sidebar visibility
function toggleSidebar() {
const sidebar = document.getElementById("sceneSidebar");
const openButton = document.querySelector(".btn-open-sidebar");
const openButton = document.getElementById("btnOpenSidebar");
if (!sidebar || !openButton) return;

View file

@ -0,0 +1,193 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quackscape - Create Immersive Virtual Tours</title>
<!-- Meta tags -->
<meta name="description" content="Create and share immersive virtual tours with Quackscape. Easy-to-use VR tour builder for businesses, real estate, education, and more.">
<meta name="keywords" content="virtual tours, VR, 360 tours, virtual reality, tour builder">
<!-- Favicon -->
<link rel="icon" href="{% static 'img/favicon.png' %}" type="image/png">
<link rel="apple-touch-icon" href="{% static 'img/apple-touch-icon.png' %}">
<!-- Fonts -->
<link href="https://googledonts.private.coffee/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<!-- Header/Navigation -->
<header class="site-header">
<nav class="container">
<a href="/" class="logo">
<img src="{% static 'img/logo.svg' %}" alt="Quackscape Logo">
<span>Quackscape</span>
</a>
<div class="nav-links">
<a href="#features">Features</a>
<a href="#how-it-works">How It Works</a>
<a href="#pricing">Pricing</a>
<a href="#showcase">Showcase</a>
</div>
<div class="auth-buttons">
{% if user.is_authenticated %}
<a href="{% url 'quackscape.users:categories' %}" class="btn btn-primary">Dashboard</a>
{% else %}
<a href="{% url 'quackscape.users:login' %}" class="btn btn-secondary">Log In</a>
<a href="#" class="btn btn-primary">Sign Up</a>
{% endif %}
</div>
<button class="mobile-menu-toggle">
<i class="ph-light ph-list"></i>
</button>
</nav>
</header>
<!-- Hero Section -->
<section class="hero">
<div class="container">
<div class="hero-content">
<h1>Create Immersive Virtual Tours in Minutes</h1>
<p class="lead">Transform your spaces into interactive virtual experiences. No technical skills required.</p>
<div class="hero-buttons">
<a href="#" class="btn btn-primary btn-lg">Get Started Free</a>
<a href="#demo" class="btn btn-secondary btn-lg">View Demo</a>
</div>
</div>
<div class="hero-image">
<img src="{% static 'img/hero-illustration.svg' %}" alt="Quackscape Virtual Tour Creator">
</div>
</div>
</section>
<!-- Features Section -->
<section id="features" class="features">
<div class="container">
<h2>Everything You Need to Create Amazing Virtual Tours</h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">
<i class="ph-light ph-cube"></i>
</div>
<h3>Easy-to-Use Editor</h3>
<p>Intuitive drag-and-drop interface makes creating tours simple and fast.</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="ph-light ph-image"></i>
</div>
<h3>360° Support</h3>
<p>Upload 360° photos and create immersive panoramic experiences.</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="ph-light ph-link"></i>
</div>
<h3>Interactive Hotspots</h3>
<p>Add clickable points of interest with images, text, and links.</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="ph-light ph-device-mobile"></i>
</div>
<h3>Mobile Friendly</h3>
<p>Tours work perfectly on all devices, including VR headsets.</p>
</div>
</div>
</div>
</section>
<!-- How It Works Section -->
<section id="how-it-works" class="how-it-works">
<div class="container">
<h2>Create Your First Tour in 3 Easy Steps</h2>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<h3>Upload Your Photos</h3>
<p>Upload your 360° photos or regular images to get started.</p>
</div>
<div class="step">
<div class="step-number">2</div>
<h3>Add Interactivity</h3>
<p>Place hotspots, add information, and link scenes together.</p>
</div>
<div class="step">
<div class="step-number">3</div>
<h3>Share Your Tour</h3>
<p>Share via link or embed on your website.</p>
</div>
</div>
</div>
</section>
<!-- Demo Section -->
<section id="demo" class="demo">
<div class="container">
<h2>See Quackscape in Action</h2>
<div class="demo-viewer">
<iframe src="https://quackscape.dev.kumi/tours/scene/59d4e23f-406d-4c47-82ec-807a015a7780/embed/" width="100%" height="100%" frameborder="0" allowfullscreen></iframe>
</div>
</div>
</section>
<!-- Footer -->
<footer class="site-footer">
<div class="container">
<div class="footer-grid">
<div class="footer-brand">
<img src="{% static 'img/logo.svg' %}" alt="Quackscape Logo">
<p>Create immersive virtual experiences with ease.</p>
</div>
<div class="footer-links">
<h4>Product</h4>
<ul>
<li><a href="#features">Features</a></li>
<li><a href="#pricing">Pricing</a></li>
<li><a href="#showcase">Showcase</a></li>
<li><a href="#demo">Demo</a></li>
</ul>
</div>
<div class="footer-links">
<h4>Company</h4>
<ul>
<li><a href="/about">About</a></li>
<li><a href="/blog">Blog</a></li>
<li><a href="/careers">Careers</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</div>
<div class="footer-links">
<h4>Legal</h4>
<ul>
<li><a href="/privacy">Privacy Policy</a></li>
<li><a href="/terms">Terms of Service</a></li>
<li><a href="/cookies">Cookie Policy</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<p>&copy; {% now "Y" %} Quackscape. All rights reserved.</p>
<div class="social-links">
<a href="#" title="Twitter"><i class="ph-light ph-twitter-logo"></i></a>
<a href="#" title="Facebook"><i class="ph-light ph-facebook-logo"></i></a>
<a href="#" title="LinkedIn"><i class="ph-light ph-linkedin-logo"></i></a>
<a href="#" title="GitHub"><i class="ph-light ph-github-logo"></i></a>
</div>
</div>
</div>
</footer>
<!-- Scripts -->
<script src="{% static 'js/landing.bundle.js' %}"></script>
</body>
</html>

View file

@ -154,23 +154,11 @@
aria-label="View scene information">
<i class="ph-light ph-info"></i>
</button>
<button id="toggleSidebarBtn"
class="control-btn"
title="Scene Navigator"
aria-label="Toggle scene navigator">
<i class="ph-light ph-list"></i>
</button>
<button id="resetViewBtn"
class="control-btn"
title="Reset View"
aria-label="Reset camera view">
<i class="ph-light ph-arrows-out"></i>
</button>
<button id="fullscreenBtn"
class="control-btn"
title="Fullscreen"
aria-label="Toggle fullscreen mode">
<i class="ph-light ph-arrows-out-cardinal"></i>
<i class="ph-light ph-arrows-out"></i>
</button>
{% if user.is_authenticated and scene.user_has_permission %}
<a href="{% url 'quackscape.users:scene-edit' scene.category.id scene.id %}"
@ -210,10 +198,17 @@
document.getElementById("infoBtn").addEventListener("click", function() {
document.getElementById("sceneSidebar").classList.remove("show");
document.getElementById("sceneInfo").classList.toggle("active");
if (document.getElementById("sceneInfo").classList.contains("active")) {
document.getElementById("btnOpenSidebar").classList.add("hide");
} else {
document.getElementById("btnOpenSidebar").classList.remove("hide");
}
});
document.getElementById("closeInfoBtn").addEventListener("click", function() {
document.getElementById("sceneInfo").classList.remove("active");
document.getElementById("btnOpenSidebar").classList.remove("hide");
});
// Reset view functionality

View file

@ -38,15 +38,15 @@ class PublicPermissionMixin:
class SceneAPIViewSet(viewsets.ModelViewSet):
serializer_class = SceneSerializer
queryset = Scene.objects.all()
def get_queryset(self):
queryset = Scene.objects.all()
# Filter by category if provided in URL
category = self.kwargs.get('category')
category = self.kwargs.get("category")
if category:
queryset = queryset.filter(category=category)
# Filter based on user permissions
user = self.request.user
if not user.is_superuser and not user.is_staff:
@ -56,19 +56,21 @@ class SceneAPIViewSet(viewsets.ModelViewSet):
if scene.public or scene.user_has_permission(user):
allowed_scenes.append(scene.id)
queryset = queryset.filter(id__in=allowed_scenes)
return queryset
def create(self, request, *args, **kwargs):
# Print request data for debugging
print("Creating scene with data:", request.data)
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
# Save the scene
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
return Response(
serializer.data, status=status.HTTP_201_CREATED, headers=headers
)
else:
print("Serializer errors:", serializer.errors)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@ -103,4 +105,8 @@ class CategoryAPIViewSet(viewsets.ModelViewSet):
class MediaAPIViewSet(viewsets.GenericViewSet, viewsets.mixins.RetrieveModelMixin):
serializer_class = OriginalMediaSerializer
queryset = OriginalMedia.objects.all()
queryset = OriginalMedia.objects.all()
class LandingPageView(TemplateView):
template_name = "tours/landing.html"

View file

@ -5,7 +5,10 @@ from django.conf.urls.static import static
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
from quackscape.tours.views import LandingPageView
urlpatterns = [
path("", LandingPageView.as_view(), name="landing"),
path("admin/", admin.site.urls),
path("tours/", include("quackscape.tours.urls")),
path("users/", include("quackscape.users.urls")),

72
static/img/logo.svg Normal file
View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="58.549824mm"
height="61.510605mm"
viewBox="0 0 58.549824 61.510605"
version="1.1"
id="svg1"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm">
<inkscape:page
x="0"
y="-2.7380216e-14"
width="58.549824"
height="61.510609"
id="page2"
margin="0"
bleed="0" />
</sodipodi:namedview>
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-78.149325,-49.056418)">
<path
id="path1"
d="m 130.84445,96.619589 c -0.81103,0.74859 -1.83303,1.43968 -2.87937,1.71308 -0.14393,-0.12488 -0.28822,-0.24941 -0.43215,-0.369 l -0.139,-0.11995 c -1.07985,-0.90769 -2.19322,-1.8482 -2.21721,-2.67864 -0.0145,-0.57079 0.51824,-1.32397 1.03647,-2.04928 0.57608,-0.80575 1.20473,-1.68875 1.40158,-2.70158 h 0.15346 c 1.67005,0 3.19617,-0.60501 4.37691,-1.6129 0.68122,0.85937 1.17087,1.79529 1.23367,2.91818 0.11006,1.90042 -1.14265,3.60892 -2.53436,4.90009"
style="fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778" />
<path
id="path2"
d="m 89.677046,95.165439 c -0.024,0.83008 -1.18569,1.80904 -2.12619,2.59644 l -0.1009,0.0864 c -0.12947,0.11007 -0.26423,0.22049 -0.3937,0.33585 -0.11007,-0.024 -0.22084,-0.0529 -0.32667,-0.0864 -1.0407,-0.32138 -1.92899,-1.00788 -2.73473,-1.73743 -1.28659,-1.16593 -2.50085,-2.649 -2.52942,-4.38644 -0.0243,-1.16628 0.49389,-2.25072 1.21391,-3.19158 1.18568,1.01777 2.72132,1.63195 4.4009,1.63195 h 0.16334 c 0.19155,1.01248 0.82056,1.89089 1.3917,2.69699 0.51823,0.72954 1.05622,1.48308 1.04176,2.05423"
style="fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778" />
<path
id="path3"
d="M 122.07157,66.418279 H 92.776906 c 0.98918,-1.2573 2.15018,-2.38513 3.44593,-3.33058 0.98425,-0.72002 1.91946,-1.41111 2.82222,-2.22708 0.974024,-0.88301 1.655594,-2.06375 2.457104,-3.09069 0.91652,-1.18039 1.90041,-2.31316 2.94675,-3.37855 0.95956,-0.9839 1.98685,-1.90994 3.05224,-2.77883 0.95497,-0.78211 1.88101,-1.61749 2.90794,-2.2987 1.30069,-0.86854 1.22908,0.6671 1.05622,1.5501 -0.34078,1.69898 -1.09926,3.31153 -1.71344,4.91914 -0.0674,0.17286 -0.12982,0.34078 -0.1965,0.51364 0.40252,-1.05092 1.07985,-2.1022 1.85208,-2.9083 0.8883,-0.92639 2.53436,-1.57903 2.75978,0.30233 0.12489,1.05586 -0.16298,2.07786 0.024,3.16265 0.18204,1.06998 0.56656,2.11173 1.12783,3.04271 1.05622,1.77095 2.84586,2.49061 4.31942,3.83434 0.89217,0.82092 1.7085,1.71838 2.4331,2.68782"
style="fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778" />
<path
id="path4"
d="m 132.14021,73.189849 v 10.45245 c 0,2.40912 -1.96251,4.37233 -4.37198,4.37233 h -0.60466 c -1.22414,-1.97238 -4.36316,-2.78836 -6.31613,-3.10974 -0.49389,-0.0818 -0.99801,-0.15381 -1.50637,-0.22577 -1.82421,-0.2593 -3.71545,-0.52811 -5.07788,-1.25272 -0.10548,-0.0526 -0.22578,-0.115 -0.34078,-0.19191 -0.53764,-0.34078 -0.99872,-0.79657 -1.49261,-1.2767 -0.70555,-0.69074 -1.4351,-1.40124 -2.45745,-1.89548 l -0.0522,-0.0194 c -0.75847,-0.32632 -1.53141,-0.50377 -2.28494,-0.53728 -0.12912,-0.005 -0.2734,-0.005 -0.41769,0 -0.7433,0.0335 -1.49683,0.20637 -2.24578,0.51823 -0.0811,0.0289 -0.139,0.0575 -0.17251,0.072 -0.9839,0.48965 -1.69474,1.18533 -2.38054,1.86196 -0.49389,0.48013 -0.96027,0.93592 -1.50248,1.28623 -0.10513,0.0674 -0.21555,0.12982 -0.34079,0.19191 -1.343024,0.71508 -3.234274,0.98389 -5.067654,1.24319 -0.50835,0.072 -1.01283,0.14393 -1.51165,0.22577 -1.94416,0.32138 -5.0726,1.13736 -6.30591,3.10974 h -0.60995 c -2.40947,0 -4.37198,-1.96321 -4.37198,-4.37233 v -10.45245 c 0,-2.41406 1.96251,-4.37233 4.37198,-4.37233 h 9.80475 l -6.55108,13.19812 9.20009,-13.19812 h 2.160064 l -5.307894,10.1794 6.804734,-10.1794 h 24.57732 c 2.40947,0 4.37198,1.95827 4.37198,4.37233"
style="fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778" />
<path
id="path5"
d="m 135.49971,81.774699 c -0.66287,0 -1.1998,-0.53693 -1.1998,-1.1998 v -6.23888 c 0,-0.66287 0.53693,-1.19979 1.1998,-1.19979 0.66252,0 1.19944,0.53692 1.19944,1.19979 v 6.23888 c 0,0.66287 -0.53692,1.1998 -1.19944,1.1998"
style="fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778" />
<path
id="path6"
d="m 79.348766,81.774699 c -0.66252,0 -1.19944,-0.53693 -1.19944,-1.1998 v -6.23888 c 0,-0.66287 0.53692,-1.19979 1.19944,-1.19979 0.66287,0 1.1998,0.53692 1.1998,1.19979 v 6.23888 c 0,0.66287 -0.53693,1.1998 -1.1998,1.1998"
style="fill:#333333;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778" />
<path
id="path7"
d="m 114.14818,92.645899 c -0.5281,0.42227 -1.30069,0.3309 -1.72296,-0.19685 -0.10584,-0.13441 -0.18239,-0.29281 -0.22084,-0.45121 l -0.67169,-2.5146 c -0.0963,-0.36477 0.12453,-0.73448 0.48472,-0.83537 0.2734,-0.072 0.54716,0.0339 0.71014,0.2353 l 1.61748,2.03976 c 0.42193,0.53269 0.33585,1.30069 -0.19685,1.72297 m -10.83204,-3.16266 -0.67169,2.5146 c -0.17286,0.65758 -0.84525,1.04634 -1.49789,0.87348 -0.65264,-0.17745 -1.04563,-0.84455 -0.86819,-1.50213 0.0434,-0.16333 0.12489,-0.31679 0.22543,-0.44626 l 1.61748,-2.03976 c 0.23037,-0.29281 0.66217,-0.34078 0.95497,-0.11042 0.22084,0.17745 0.30692,0.45579 0.23989,0.71049 m 23.73701,11.268781 c -0.32632,-0.32208 -0.71049,-0.65299 -1.07033,-0.960261 l -0.13441,-0.11042 c -1.41569,-1.19485 -3.01872,-2.54352 -3.07128,-4.44852 -0.0339,-1.3776 0.77258,-2.51037 1.48272,-3.50838 0.32668,-0.46567 0.64806,-0.91193 0.83468,-1.31022 0.14428,-0.29739 0.21166,-0.57114 0.16369,-0.80645 -0.0289,-0.12947 -0.0914,-0.26387 -0.18204,-0.39334 -0.30797,-0.42722 -0.95991,-0.84491 -1.8482,-1.1998 -0.768,-0.30727 -1.71309,-0.56621 -2.76895,-0.74401 -0.47519,-0.0769 -0.96485,-0.14393 -1.45415,-0.2159 -2.02989,-0.28787 -4.13209,-0.58561 -5.84059,-1.4926 -0.13476,-0.067 -0.34572,-0.17745 -0.55669,-0.3168 -0.72002,-0.46072 -1.30104,-1.02693 -1.85737,-1.57409 -0.58067,-0.57115 -1.13277,-1.10878 -1.79,-1.43016 -0.49424,-0.20638 -0.96449,-0.3168 -1.43474,-0.33585 -0.0674,-0.005 -0.13476,-0.005 -0.20214,0 -0.47978,0.019 -0.95497,0.13441 -1.46333,0.34537 l -0.024,0.01 c -0.63358,0.32632 -1.17122,0.85408 -1.73743,1.41076 -0.55668,0.54716 -1.1377,1.10878 -1.87677,1.58397 -0.14394,0.096 -0.31151,0.19191 -0.53235,0.30692 -1.703914,0.90699 -3.806124,1.20473 -5.840944,1.4926 -0.49389,0.072 -0.98354,0.139 -1.46332,0.22084 -1.05128,0.17286 -1.99672,0.4318 -2.76437,0.73907 -0.88829,0.35031 -1.53599,0.76764 -1.84326,1.1998 -0.0963,0.12947 -0.15805,0.26387 -0.18697,0.39334 -0.048,0.23531 0.024,0.50906 0.16333,0.80645 0.19191,0.39829 0.50836,0.84455 0.83961,1.30528 0.71015,0.99836 1.51624,2.13572 1.48308,3.50838 -0.0582,1.90994 -1.67534,3.27307 -2.97568,4.36703 l -0.0959,0.0819 c -0.43674,0.369711 -0.84949,0.720021 -1.20933,1.075261 -0.86854,0.83926 -1.00294,1.83304 -0.38417,2.71604 1.01282,1.43969 3.87773,4.14161 11.71504,6.09 l 0.0476,0.0145 c 0.0194,0 0.0437,0.01 0.0769,0.0194 5.274744,1.30069 11.081814,1.30069 16.356194,-0.005 0.0286,-0.005 0.0575,-0.0145 0.0811,-0.019 7.87118,-1.95827 10.73609,-4.66019 11.74398,-6.09988 h 0.005 c 0.61913,-0.883 0.47978,-1.87678 -0.38382,-2.71604"
style="fill:#f7931e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.0352778"
inkscape:export-filename="quackscape.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

View file

@ -9,6 +9,7 @@ module.exports = {
scene: "./assets/js/scene.js",
editor: "./assets/js/editor.js",
userarea: "./assets/js/userarea.js",
landing: "./assets/js/landing.js",
},
output: {
path: path.resolve(__dirname, "static/js"),