unknown
Improved UI
c96728f
Raw
History Blame Contribute Delete
13.5 kB
{% extends 'base.html' %}
{% load static %}
{% block title %}Upload Image — Constellar{% endblock %}
{% block extra_css %}
<style>
.dropzone {
border: 1.5px dashed rgba(99,102,241,.35);
border-radius: 14px;
background: rgba(99,102,241,.03);
transition: border-color .2s, background .2s;
cursor: pointer;
}
.dropzone:hover, .dropzone.dragging {
border-color: rgba(99,102,241,.7);
background: rgba(99,102,241,.07);
}
.dropzone.has-file { cursor: default; }
.upload-icon {
width: 64px; height: 64px;
border-radius: 16px;
margin: 0 auto 1.25rem;
display: flex; align-items: center; justify-content: center;
background: rgba(99,102,241,.12);
border: 1px solid rgba(99,102,241,.25);
color: #a5b4fc;
transition: transform .2s;
}
.dropzone:hover .upload-icon { transform: translateY(-3px); }
.tip-card {
display: flex; gap: 1rem; align-items: flex-start;
padding: 1.25rem;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
}
.tip-icon {
width: 38px; height: 38px; flex-shrink: 0;
border-radius: 9px;
display: flex; align-items: center; justify-content: center;
background: rgba(99,102,241,.12);
border: 1px solid rgba(99,102,241,.22);
color: #a5b4fc;
}
.remove-btn {
position: absolute; top: -10px; right: -10px;
width: 28px; height: 28px;
border-radius: 50%;
border: none; cursor: pointer;
background: #ef4444; color: #fff;
display: flex; align-items: center; justify-content: center;
font-size: .875rem; line-height: 1;
box-shadow: 0 2px 8px rgba(0,0,0,.4);
transition: background .15s;
}
.remove-btn:hover { background: #dc2626; }
.submit-btn {
font-family: 'Inter', sans-serif;
font-size: .9375rem; font-weight: 600;
color: #fff; padding: .8rem 2.25rem;
border-radius: 8px; border: none; cursor: pointer;
background: var(--accent);
transition: all .18s;
display: inline-flex; align-items: center; gap: .5rem;
}
.submit-btn:hover:not(:disabled) {
background: #818cf8;
box-shadow: 0 0 24px rgba(99,102,241,.4);
transform: translateY(-1px);
}
.submit-btn:disabled { opacity: .45; cursor: not-allowed; }
.spin {
width: 15px; height: 15px; border-radius: 50%;
border: 2px solid rgba(255,255,255,.3);
border-top-color: #fff;
animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
</style>
{% endblock %}
{% block content %}
<!-- ════ HEADER ════ -->
<section class="section" style="padding-top:4rem; padding-bottom:2.5rem; text-align:center;">
<div style="max-width:560px; margin:0 auto;">
<div class="tag" style="margin-bottom:1.25rem;">Image Analysis</div>
<h1 style="font-family:'Orbitron',monospace; font-size:clamp(1.6rem,3.5vw,2.5rem); font-weight:900; margin:0 0 1rem; letter-spacing:-.01em;">
Upload a Night Sky Image
</h1>
<p style="font-size:.9375rem; color:var(--muted); line-height:1.7; margin:0;">
Drop in a photo of the stars and our detection model will identify every constellation it can find.
</p>
</div>
</section>
<!-- ════ UPLOAD FORM ════ -->
<section style="padding:0 1.5rem;">
<div style="max-width:640px; margin:0 auto;">
<form id="uploadForm" method="post" enctype="multipart/form-data" action="{% url 'process_upload' %}">
{% csrf_token %}
<div id="dropZone" class="dropzone" style="padding:3rem 2rem; text-align:center; position:relative;">
<!-- default state -->
<div id="uploadContent">
<div class="upload-icon">
<svg width="26" height="26" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5"/>
</svg>
</div>
<h3 style="font-size:1.0625rem; font-weight:600; margin:0 0 .5rem; color:#fff;">
Drop your image here, or click to browse
</h3>
<p style="font-size:.8125rem; color:var(--muted); margin:0;">
JPG, JPEG or PNG &middot; up to 10 MB
</p>
</div>
<!-- preview state -->
<div id="previewArea" class="hidden">
<div style="position:relative; display:inline-block;">
<img id="previewImage" style="max-width:100%; max-height:320px; border-radius:10px; border:1px solid var(--border);" alt="Selected image preview" />
<button type="button" id="removeImage" class="remove-btn" aria-label="Remove image"></button>
</div>
<p id="fileName" style="font-size:.875rem; font-weight:500; color:#a5b4fc; margin:1rem 0 .125rem;"></p>
<p id="fileSize" style="font-size:.75rem; color:var(--muted); margin:0;"></p>
</div>
<input type="file" id="fileInput" name="image" accept=".jpg,.jpeg,.png" class="hidden" required>
</div>
<div style="text-align:center; margin-top:1.75rem;">
<button type="submit" id="submitBtn" disabled class="submit-btn">
<span id="submitText">Select an image first</span>
<span id="loadingSpinner" class="hidden"><span class="spin"></span></span>
</button>
</div>
</form>
</div>
</section>
<!-- ════ TIPS ════ -->
<section class="section" style="padding-top:4.5rem; padding-bottom:2rem;">
<div style="max-width:820px; margin:0 auto;">
<div style="text-align:center; margin-bottom:2rem;">
<p class="section-label" style="justify-content:center;">Better Results</p>
<h2 style="font-family:'Orbitron',monospace; font-size:clamp(1.2rem,2.5vw,1.6rem); font-weight:700; margin:0;">
Tips for Accurate Detection
</h2>
</div>
<div style="display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:.875rem;">
<div class="tip-card">
<div class="tip-icon">
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z"/>
</svg>
</div>
<div>
<h3 style="font-size:.875rem; font-weight:600; margin:0 0 .375rem;">Clear night sky</h3>
<p style="font-size:.78125rem; color:var(--muted); line-height:1.6; margin:0;">Minimal cloud cover and low light pollution give the model the cleanest star patterns to work with.</p>
</div>
</div>
<div class="tip-card">
<div class="tip-icon">
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.827 6.175A2.31 2.31 0 0 1 5.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 0 0-1.134-.175 2.31 2.31 0 0 1-1.64-1.055l-.822-1.316a2.192 2.192 0 0 0-1.736-1.039 48.774 48.774 0 0 0-5.232 0 2.192 2.192 0 0 0-1.736 1.039l-.821 1.316Z"/>
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 12.75a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Z"/>
</svg>
</div>
<div>
<h3 style="font-size:.875rem; font-weight:600; margin:0 0 .375rem;">High resolution</h3>
<p style="font-size:.78125rem; color:var(--muted); line-height:1.6; margin:0;">Sharper images mean more stars visible to the detector — avoid heavy compression or blur.</p>
</div>
</div>
<div class="tip-card">
<div class="tip-icon">
<svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z"/>
</svg>
</div>
<div>
<h3 style="font-size:.875rem; font-weight:600; margin:0 0 .375rem;">Wide framing</h3>
<p style="font-size:.78125rem; color:var(--muted); line-height:1.6; margin:0;">Include full star patterns rather than tight crops — constellations need context to be recognized.</p>
</div>
</div>
</div>
</div>
</section>
<!-- ════ OTHER METHODS ════ -->
<section class="section" style="padding-top:1.5rem; text-align:center;">
<p style="font-size:.8125rem; color:var(--muted); margin:0 0 1rem;">Prefer a different method?</p>
<div style="display:flex; gap:.75rem; justify-content:center; flex-wrap:wrap;">
<a href="{% url 'detect' %}" class="btn-ghost" style="padding:.65rem 1.375rem;">Live Camera Detection</a>
<a href="{% url 'database' %}" class="btn-ghost" style="padding:.65rem 1.375rem;">Browse Database</a>
</div>
</section>
{% endblock %}
{% block extra_js %}
<script>
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
const uploadContent = document.getElementById('uploadContent');
const previewArea = document.getElementById('previewArea');
const previewImage = document.getElementById('previewImage');
const fileName = document.getElementById('fileName');
const fileSize = document.getElementById('fileSize');
const removeImage = document.getElementById('removeImage');
const submitBtn = document.getElementById('submitBtn');
const submitText = document.getElementById('submitText');
const loadingSpinner = document.getElementById('loadingSpinner');
const uploadForm = document.getElementById('uploadForm');
dropZone.addEventListener('click', () => {
if (!previewArea.classList.contains('hidden')) return;
fileInput.click();
});
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('dragging');
});
dropZone.addEventListener('dragleave', (e) => {
e.preventDefault();
dropZone.classList.remove('dragging');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('dragging');
if (e.dataTransfer.files.length > 0) handleFile(e.dataTransfer.files[0]);
});
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) handleFile(e.target.files[0]);
});
function handleFile(file) {
const validTypes = ['image/jpeg', 'image/jpg', 'image/png'];
if (!validTypes.includes(file.type)) {
alert('Please select a valid image file (JPG, JPEG, or PNG)');
return;
}
if (file.size > 10 * 1024 * 1024) {
alert('File size must be less than 10MB');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
previewImage.src = e.target.result;
fileName.textContent = file.name;
fileSize.textContent = `${(file.size / 1024 / 1024).toFixed(2)} MB`;
uploadContent.classList.add('hidden');
previewArea.classList.remove('hidden');
dropZone.classList.add('has-file');
submitBtn.disabled = false;
submitText.textContent = 'Analyze Constellations';
};
reader.readAsDataURL(file);
}
removeImage.addEventListener('click', (e) => {
e.stopPropagation();
fileInput.value = '';
previewArea.classList.add('hidden');
uploadContent.classList.remove('hidden');
dropZone.classList.remove('has-file');
submitBtn.disabled = true;
submitText.textContent = 'Select an image first';
});
uploadForm.addEventListener('submit', () => {
submitBtn.disabled = true;
submitText.textContent = 'Analyzing…';
loadingSpinner.classList.remove('hidden');
});
</script>
{% endblock %}