unknown
Enhanced UI and background
d1d94fb
Raw
History Blame Contribute Delete
15.1 kB
{% extends 'base.html' %}
{% load static %}
{% block title %}Results — Constellar{% endblock %}
{% block extra_css %}
<style>
.result-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1.375rem;
transition: border-color .2s;
}
.result-card:hover { border-color: rgba(99,102,241,.3); }
.confidence-badge {
font-size: .6875rem; font-weight: 700;
padding: .25rem .625rem;
border-radius: 5px;
background: rgba(34,197,94,.12);
border: 1px solid rgba(34,197,94,.25);
color: #86efac;
}
.icon-action {
width: 30px; height: 30px;
border-radius: 7px;
border: 1px solid var(--border);
background: rgba(255,255,255,.04);
color: var(--muted);
cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: all .15s;
}
.icon-action:hover { color: #fff; border-color: rgba(255,255,255,.18); }
.learn-btn {
font-family: 'Inter', sans-serif;
font-size: .78125rem; font-weight: 600;
color: #a5b4fc;
padding: .5rem 1rem;
border-radius: 7px;
border: 1px solid rgba(99,102,241,.3);
background: rgba(99,102,241,.08);
cursor: pointer;
transition: all .15s;
display: inline-flex; align-items: center; gap: .375rem;
}
.learn-btn:hover { background: rgba(99,102,241,.16); border-color: rgba(99,102,241,.5); }
.coords-box {
margin-top: 1rem;
padding: .625rem .875rem;
background: rgba(255,255,255,.025);
border: 1px solid var(--border);
border-radius: 7px;
font-size: .6875rem;
color: var(--muted);
display: flex; gap: 1.25rem;
}
.spin {
width: 14px; height: 14px; border-radius: 50%;
border: 2px solid rgba(255,255,255,.15);
border-top-color: var(--accent);
animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-in { animation: fadeIn .3s ease-out; }
/* ── before/after slider ── */
.compare {
position: relative;
overflow: hidden;
border-radius: 9px;
user-select: none;
}
.compare > img { width: 100%; display: block; }
.compare-top {
position: absolute;
top: 0; left: 0;
height: 100%;
width: var(--pos, 50%);
overflow: hidden;
}
.compare-top img {
position: absolute;
top: 0; left: 0;
height: 100%;
width: auto;
max-width: none;
}
.compare-line {
position: absolute;
top: 0; bottom: 0;
left: var(--pos, 50%);
width: 2px;
background: rgba(255,255,255,.85);
box-shadow: 0 0 10px rgba(0,0,0,.5);
pointer-events: none;
}
.compare-line::after {
content: '⇄';
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 32px; height: 32px;
border-radius: 50%;
background: rgba(6,13,31,.85);
border: 1px solid rgba(255,255,255,.5);
color: #fff;
font-size: .875rem;
display: flex; align-items: center; justify-content: center;
}
.compare-label {
position: absolute;
top: .75rem;
font-size: .625rem; font-weight: 700;
letter-spacing: .08em; text-transform: uppercase;
padding: .25rem .5rem;
border-radius: 5px;
background: rgba(6,13,31,.75);
color: rgba(255,255,255,.85);
pointer-events: none;
backdrop-filter: blur(4px);
}
.compare input[type="range"] {
position: absolute;
inset: 0;
width: 100%; height: 100%;
opacity: 0;
cursor: ew-resize;
margin: 0;
}
</style>
{% endblock %}
{% block content %}
<!-- ════ HEADER ════ -->
<section class="section" style="padding-top:3.5rem; padding-bottom:2rem; text-align:center;">
<div class="tag" style="margin-bottom:1.25rem;">Analysis Complete</div>
<h1 style="font-family:'Orbitron',monospace; font-size:clamp(1.5rem,3.5vw,2.25rem); font-weight:900; margin:0 0 .875rem; letter-spacing:-.01em;">
Detection Results
</h1>
<p style="font-size:.9375rem; color:var(--muted); margin:0;">
{% if analysis_type == 'constellation' %}
Your night sky image has been analyzed for constellation patterns.
{% else %}
Objects detected in your image.
{% endif %}
</p>
</section>
<!-- ════ PROCESSED IMAGE ════ -->
<section style="padding:0 1.5rem;">
<div style="max-width:900px; margin:0 auto;">
<div class="glass-card" style="padding:1.25rem;">
{% if original_image %}
<!-- before/after comparison slider -->
<div class="compare" id="compareBox" style="--pos:50%;">
<img src="data:image/jpeg;base64,{{ processed_image }}" alt="Detection result" draggable="false">
<div class="compare-top">
<img src="data:image/jpeg;base64,{{ original_image }}" alt="Original image" draggable="false">
</div>
<div class="compare-line"></div>
<span class="compare-label" style="left:.75rem;">Original</span>
<span class="compare-label" style="right:.75rem;">Detected</span>
<input type="range" min="0" max="100" value="50" id="compareRange" aria-label="Compare original and detected image">
</div>
<p style="text-align:center; font-size:.71875rem; color:var(--muted); margin:.875rem 0 0;">
Drag the slider to compare your original photo with the detection result
</p>
{% else %}
<img src="data:image/jpeg;base64,{{ processed_image }}"
alt="Processed constellation image"
style="width:100%; border-radius:9px; display:block;">
{% endif %}
</div>
</div>
</section>
<!-- ════ DETECTIONS ════ -->
<section class="section" style="max-width:900px; padding-top:2.5rem; padding-bottom:2rem;">
{% if detected_constellations %}
<p class="section-label">Detected</p>
<h2 style="font-family:'Orbitron',monospace; font-size:clamp(1.2rem,2.5vw,1.5rem); font-weight:700; margin:0 0 1.5rem;">
{{ detected_constellations|length }} Constellation{{ detected_constellations|length|pluralize }} Found
</h2>
<div style="display:grid; grid-template-columns:repeat(auto-fill,minmax(270px,1fr)); gap:1rem;">
{% for constellation in detected_constellations %}
<div class="result-card">
<div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:1rem;">
<h3 style="font-size:1rem; font-weight:600; margin:0; color:#fff;">{{ constellation.name }}</h3>
<div style="display:flex; align-items:center; gap:.5rem;">
<span class="confidence-badge">{{ constellation.confidence|floatformat:2 }}</span>
<button onclick="speakConstellation('{{ constellation.name }}')" class="icon-action" title="Listen">
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
<path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"/>
</svg>
</button>
</div>
</div>
<div id="info-{{ forloop.counter }}" class="constellation-info">
<button onclick="loadConstellationInfo('{{ constellation.name }}', {{ forloop.counter }})" class="learn-btn">
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10"/><path stroke-linecap="round" d="M12 16v-4M12 8h.01"/>
</svg>
Learn more
</button>
</div>
<div class="coords-box">
<span>X {{ constellation.coordinates.0|floatformat:0 }}–{{ constellation.coordinates.2|floatformat:0 }}</span>
<span>Y {{ constellation.coordinates.1|floatformat:0 }}–{{ constellation.coordinates.3|floatformat:0 }}</span>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div style="text-align:center; padding:3rem 1rem;">
<div style="width:56px; height:56px; border-radius:14px; background:rgba(255,255,255,.04); border:1px solid var(--border); display:flex; align-items:center; justify-content:center; margin:0 auto 1.25rem;">
<svg width="24" height="24" fill="none" stroke="rgba(221,228,240,.4)" stroke-width="1.6" viewBox="0 0 24 24">
<circle cx="11" cy="11" r="8"/><path stroke-linecap="round" d="m21 21-4.35-4.35M8 11h6"/>
</svg>
</div>
<h3 style="font-size:1rem; font-weight:600; margin:0 0 .5rem;">No constellations detected</h3>
<p style="font-size:.8125rem; color:var(--muted); margin:0;">Try a clearer night sky image with visible star patterns.</p>
</div>
{% endif %}
</section>
<!-- ════ NEXT ACTIONS ════ -->
<section class="section" style="padding-top:1rem; text-align:center; border-top:1px solid var(--border);">
<p style="font-size:.8125rem; color:var(--muted); margin:2rem 0 1rem;">What's next?</p>
<div style="display:flex; gap:.75rem; justify-content:center; flex-wrap:wrap;">
<a href="{% url 'upload' %}" class="btn-accent" style="padding:.65rem 1.375rem;">Analyze Another Image</a>
<a href="{% url 'detect' %}" class="btn-ghost" style="padding:.65rem 1.375rem;">Live Detection</a>
<a href="{% url 'database' %}" class="btn-ghost" style="padding:.65rem 1.375rem;">Browse Database</a>
</div>
</section>
{% endblock %}
{% block extra_js %}
<script>
function speakConstellation(constellationName) {
if ('speechSynthesis' in window) {
speechSynthesis.cancel();
const infoElement = document.querySelector(`[data-constellation="${constellationName}"]`);
let textToSpeak = `Constellation: ${constellationName}`;
if (infoElement) {
const description = infoElement.querySelector('.constellation-description');
if (description) textToSpeak += `. ${description.textContent}`;
}
const utterance = new SpeechSynthesisUtterance(textToSpeak);
utterance.rate = 0.8;
utterance.pitch = 1;
utterance.volume = 0.8;
speechSynthesis.speak(utterance);
} else {
alert('Text-to-speech is not supported in your browser');
}
}
async function loadConstellationInfo(constellationName, index) {
const infoDiv = document.getElementById(`info-${index}`);
infoDiv.innerHTML = `
<div style="display:flex; align-items:center; gap:.5rem; padding:.375rem 0;">
<div class="spin"></div>
<span style="font-size:.78125rem; color:var(--muted);">Loading details…</span>
</div>
`;
try {
const response = await fetch('/get-constellation-info/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken'),
},
body: JSON.stringify({ constellation_name: constellationName })
});
if (response.ok) {
const data = await response.json();
displayConstellationInfo(infoDiv, data.name, data.info);
} else {
throw new Error('Failed to fetch constellation info');
}
} catch (error) {
displayConstellationInfo(infoDiv, constellationName, getBasicConstellationInfo(constellationName));
}
}
function displayConstellationInfo(container, name, info) {
container.innerHTML = `
<div data-constellation="${name}" class="animate-fade-in">
<div class="constellation-description" style="font-size:.78125rem; color:var(--muted); line-height:1.7; margin-bottom:.75rem;">
${info}
</div>
<button onclick="speakConstellation('${name}')" class="learn-btn">
<svg width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
<path d="M15.54 8.46a5 5 0 0 1 0 7.07"/>
</svg>
Listen
</button>
</div>
`;
}
function getBasicConstellationInfo(name) {
const basicInfo = {
'Orion': 'Orion is one of the most recognizable constellations, known as "The Hunter." It contains bright stars like Betelgeuse and Rigel, and the famous Orion Nebula.',
'Ursa Major': 'Ursa Major, the Great Bear, contains the famous Big Dipper asterism. It\'s visible year-round in most northern hemisphere locations.',
'Cassiopeia': 'Cassiopeia is known for its distinctive W-shape formed by five bright stars. In Greek mythology, she was a vain queen.',
'Leo': 'Leo the Lion is a zodiac constellation best seen in spring. Its brightest star is Regulus, marking the lion\'s heart.',
'Cygnus': 'Cygnus, the Swan, flies along the Milky Way. Its brightest star Deneb forms part of the Summer Triangle.',
};
return basicInfo[name] || `${name} is a fascinating constellation with a rich history in astronomy and mythology. This celestial pattern has guided navigators and inspired storytellers for thousands of years.`;
}
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const compareBox = document.getElementById('compareBox');
const compareRange = document.getElementById('compareRange');
if (compareBox && compareRange) {
compareRange.addEventListener('input', () => {
compareBox.style.setProperty('--pos', compareRange.value + '%');
});
}
</script>
{% endblock %}