Files
GoNtech/web/templates/teme/podrazumevana/base.html
T

227 lines
9.6 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "base"}}
<!doctype html>
<html lang="sr">
<head>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{block "naslov" .}}NTech{{end}}</title>
<meta name="csrf-token" content="{{.CsrfToken}}">
<script>
if (window.innerWidth > 768 && localStorage.getItem('sidebar-skupljen') === 'true') {
document.documentElement.classList.add('sidebar-init-skupljen');
}
</script>
{{if .AppPozadina}}<link rel="preload" as="image" href="{{.AppPozadina}}">{{end}}
<!-- tema — učitava se prva -->
<link rel="stylesheet" href="/static/css/teme/{{.Tema}}.css" />
<!-- glavni stilovi -->
<link rel="stylesheet" href="/static/css/main.css" />
<!-- tailwind -->
<script src="https://cdn.tailwindcss.com"></script>
{{block "dodatni-css" .}}{{end}}
{{if .AppPozadina}}
<style>
html {
background: url('{{.AppPozadina}}') center/cover fixed;
background-color: #1f2228;
}
body { background: transparent; }
body::before {
content: '';
position: fixed;
inset: {{if ne .AppPozadinaBlurPozadine "0"}}-20px{{else}}0{{end}};
background: url('{{.AppPozadina}}') center/cover;
filter: blur({{.AppPozadinaBlurPozadine}}px);
z-index: 0;
pointer-events: none;
}
body::after {
content: '';
position: fixed;
inset: 0;
background: rgba(0,0,0,{{.AppPozadinaOpacity}}%);
z-index: 1;
pointer-events: none;
}
.raspored { position: relative; z-index: 2; }
.sidebar { background: rgba(0,0,0,{{if .AppPozadinaGlassOpacity}}{{.AppPozadinaGlassOpacity}}%{{else}}0.3{{end}}) !important; backdrop-filter: blur({{.AppPozadinaBlur}}px); -webkit-backdrop-filter: blur({{.AppPozadinaBlur}}px); border-right: 1px solid rgba(255,255,255,0.12) !important; }
.sidebar .nav-stavka, .sidebar .logo-naziv, .sidebar .logo-podnazlov { text-shadow: 0 1px 3px rgba(0,0,0,0.8); color: rgba(255,255,255,0.95) !important; }
.sidebar .nav-stavka svg { color: rgba(255,255,255,0.95) !important; stroke: rgba(255,255,255,0.95) !important; }
.sidebar .nav-oznaka { text-shadow: 0 1px 3px rgba(0,0,0,0.8); color: rgba(255,255,255,0.7) !important; }
.topbar { background: rgba(0,0,0,{{if .AppPozadinaGlassOpacity}}{{.AppPozadinaGlassOpacity}}%{{else}}0.08{{end}}) !important; backdrop-filter: blur({{.AppPozadinaBlur}}px); -webkit-backdrop-filter: blur({{.AppPozadinaBlur}}px); border-bottom: 1px solid rgba(255,255,255,0.12) !important; }
.kartica { background: rgba(0,0,0,{{if .AppPozadinaGlassOpacity}}{{.AppPozadinaGlassOpacity}}%{{else}}0.08{{end}}) !important; backdrop-filter: blur({{.AppPozadinaBlur}}px); -webkit-backdrop-filter: blur({{.AppPozadinaBlur}}px); border: 1px solid rgba(255,255,255,0.12) !important; }
.kartica p, .kartica span, .kartica h1, .kartica h2, .kartica h3, .kartica h4, .kartica label, .kartica td, .kartica th, .kartica li, .kartica a { color: rgba(255,255,255,0.95) !important; text-shadow: 0 1px 3px rgba(0,0,0,0.7); }
table, th, td { color: rgba(255,255,255,0.95) !important; text-shadow: 0 1px 3px rgba(0,0,0,0.8); }
tr { background: rgba(0,0,0,0.2); }
tr:hover { background: rgba(0,0,0,0.35); }
thead th { background: rgba(0,0,0,0.4) !important; }
div:has(> canvas) { background: rgba(0,0,0,0.3); border-radius: 8px; padding: 8px; }
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
var meni = document.getElementById('avatar-meni');
if (!meni) return;
meni.style.backdropFilter = 'none';
meni.style.webkitBackdropFilter = 'none';
meni.style.background = 'var(--kartica)';
});
</script>
{{end}}
</head>
<body>
<div class="raspored">
<div class="sidebar-overlay" id="sidebar-overlay"></div>
{{template "sidebar" .}}
<div class="glavni-sadrzaj" id="glavni-sadrzaj">
{{template "topbar" .}}
<main class="sadrzaj" id="sadrzaj">
{{if .Flash}}
<div id="flash-toast" class="flash-toast flash-{{.Flash.Tip}}" role="alert">
<span class="flash-ikona">{{if eq .Flash.Tip "uspeh"}}✓{{else}}!{{end}}</span>
<span class="flash-tekst">{{.Flash.Poruka}}</span>
<button class="flash-zatvori" onclick="this.parentElement.remove()" aria-label="Zatvori">×</button>
</div>
<script>
(function() {
var t = document.getElementById('flash-toast');
if (!t) return;
setTimeout(function() {
t.classList.add('flash-izlaz');
setTimeout(function() { if (t.parentElement) t.remove(); }, 350);
}, 4000);
})();
</script>
{{end}}
{{block "sadrzaj" .}}{{end}}
</main>
</div>
</div>
<!-- alpine.js komponente (mora biti pre Alpine-a) -->
<script src="/static/js/ntech.js" defer></script>
<!-- alpine.js CSP build (lokalno, bez unsafe-eval) -->
<script src="/static/js/alpine.csp.min.js" defer></script>
<!-- htmx za komunikaciju sa serverom -->
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.x.x/dist/htmx.min.js"></script>
<!-- sidebar logika — sidebar se NIKAD ne menja pri HTMX navigaciji, pa nema potrebe za swap logikom -->
<script>
(function() {
if (window._ntechSidebarDodato) return;
window._ntechSidebarDodato = true;
function mobilni() { return window.innerWidth <= 768; }
var sidebar = document.getElementById("sidebar");
var hamburger = document.getElementById("hamburger");
var overlay = document.getElementById("sidebar-overlay");
// skupljeno stanje pri učitavanju stranice
if (sidebar && !mobilni() && localStorage.getItem("sidebar-skupljen") === "true") {
sidebar.classList.add("skupljen");
}
document.documentElement.classList.remove('sidebar-init-skupljen');
if (typeof ntechInicijalizujPodmeni === 'function') ntechInicijalizujPodmeni();
// hamburger — sidebar element živi ceo vek stranice, listener se dodaje jednom
if (hamburger && sidebar && overlay) {
hamburger.addEventListener("click", function() {
if (mobilni()) {
sidebar.classList.toggle("otvoren");
overlay.classList.toggle("aktivan");
} else {
sidebar.classList.toggle("skupljen");
localStorage.setItem("sidebar-skupljen", sidebar.classList.contains("skupljen"));
}
});
overlay.addEventListener("click", function() {
sidebar.classList.remove("otvoren");
overlay.classList.remove("aktivan");
});
}
window.addEventListener("resize", function() {
if (!mobilni() && sidebar && overlay) {
sidebar.classList.remove("otvoren");
overlay.classList.remove("aktivan");
}
});
// aktivna stavka — ažurira se pri svakoj HTMX navigaciji
function azurirajAktivne() {
var putanja = window.location.pathname;
document.querySelectorAll('#sidebar .nav-stavka').forEach(function(el) {
el.classList.remove('aktivan');
});
document.querySelectorAll('#sidebar .nav-podmeni').forEach(function(el) {
el.classList.remove('otvoren');
var strelica = el.previousElementSibling && el.previousElementSibling.querySelector('.nav-strelica svg');
if (strelica) strelica.style.transform = 'rotate(0deg)';
});
document.querySelectorAll('#sidebar a.nav-stavka').forEach(function(el) {
var href = el.getAttribute('href');
if (!href) return;
if (putanja === href || putanja.startsWith(href + '/')) {
el.classList.add('aktivan');
var podmeni = el.closest('.nav-podmeni');
if (podmeni) {
podmeni.classList.add('otvoren');
var roditelj = podmeni.previousElementSibling;
if (roditelj) {
roditelj.classList.add('aktivan');
var str = roditelj.querySelector('.nav-strelica svg');
if (str) str.style.transform = 'rotate(180deg)';
}
}
}
});
}
document.addEventListener('htmx:afterSwap', azurirajAktivne);
})();
</script>
{{block "dodatni-js" .}}{{end}}
<!-- CSRF i potvrda: inicijalizacija na učitavanju i posle htmx swap-a -->
<script>
function ntechInicijalizuj() {
var m = document.querySelector('meta[name="csrf-token"]');
if (m && m.content) {
document.querySelectorAll('form[method="POST"],form[method="post"]').forEach(function(f) {
if (f.querySelector('input[name="_csrf"]')) return;
var i = document.createElement('input');
i.type = 'hidden'; i.name = '_csrf'; i.value = m.content;
f.appendChild(i);
});
}
document.querySelectorAll('[data-potvrda]').forEach(function(el) {
if (el._potvrda) return;
el._potvrda = true;
el.addEventListener('click', function(e) {
if (!confirm(el.getAttribute('data-potvrda'))) e.preventDefault();
});
});
}
// htmx:afterSettle listener se dodaje samo jednom — ne sme da se gomila po swap-ovima
if (!window._ntechCsrfDodato) {
window._ntechCsrfDodato = true;
document.addEventListener('htmx:afterSettle', ntechInicijalizuj);
}
document.addEventListener('DOMContentLoaded', ntechInicijalizuj);
</script>
</body>
</html>
{{end}}