283 lines
12 KiB
HTML
283 lines
12 KiB
HTML
{% load static %}
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>{% block title %}Illustrations{% endblock %}</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<link rel="stylesheet" href="{% static 'app.css' %}">
|
|
|
|
<!-- === PWA additions (BEGIN) === -->
|
|
<link rel="manifest" href="/manifest.webmanifest">
|
|
<meta name="theme-color" content="#2f6cab">
|
|
<!-- iOS standalone when added to Home Screen -->
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
|
<!-- iOS app icons -->
|
|
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'pwa/apple-touch-icon-180x180.png' %}">
|
|
<link rel="apple-touch-icon" sizes="167x167" href="{% static 'pwa/apple-touch-icon-167x167.png' %}">
|
|
<link rel="apple-touch-icon" sizes="152x152" href="{% static 'pwa/apple-touch-icon-152x152.png' %}">
|
|
<link rel="apple-touch-icon" sizes="120x120" href="{% static 'pwa/apple-touch-icon-120x120.png' %}">
|
|
<!-- Optional fallback some iOS versions fetch if sizes are missing -->
|
|
<link rel="apple-touch-icon" href="{% static 'pwa/apple-touch-icon-180x180.png' %}">
|
|
<!-- Optional: control the name under the icon -->
|
|
<meta name="apple-mobile-web-app-title" content="Illustrations">
|
|
<!-- === PWA additions (END) === -->
|
|
|
|
{% block extra_head %}{% endblock %}
|
|
<style>
|
|
:root{
|
|
--nav-bg:#f8fafc; --nav-border:#e5e7eb; --nav-ink:#1f2937; --nav-ink-muted:#6b7280;
|
|
--nav-brand:#2f6cab; --nav-brand-hover:#1f4c7a; --btn-bg:#fff; --btn-border:#d1d5db; --btn-hover:#eef2f7;
|
|
}
|
|
.topbar-wrap{border-bottom:1px solid var(--nav-border); background:var(--nav-bg);}
|
|
.topbar{max-width:1100px; margin:0 auto; padding:14px 16px; display:flex; align-items:center; gap:14px; justify-content:space-between; font-size:17px;}
|
|
.brand{display:flex; align-items:center; gap:10px; text-decoration:none; color:var(--nav-ink); font-weight:600; letter-spacing:.2px;}
|
|
.brand .tagline{color:var(--nav-ink-muted); font-weight:500; font-size:15px}
|
|
.nav-right{display:flex; align-items:center; gap:10px; flex-wrap:wrap}
|
|
.nav-btn{display:inline-flex; align-items:center; justify-content:center; padding:8px 12px; border-radius:10px; background:var(--btn-bg); border:1px solid var(--btn-border); color:var(--nav-ink); text-decoration:none; cursor:pointer; line-height:1; transition:background .12s, border-color .12s, box-shadow .12s;}
|
|
.nav-btn:hover{background:var(--btn-hover)}
|
|
.nav-btn.primary{background:var(--nav-brand); border-color:var(--nav-brand); color:#fff}
|
|
.nav-btn.primary:hover{background:var(--nav-brand-hover); border-color:var(--nav-brand-hover)}
|
|
.nav-btn.danger{background:#b91c1c; border-color:#b91c1c; color:#fff}
|
|
.nav-btn.danger:hover{filter:brightness(.95)}
|
|
.user-chip{padding:6px 10px; border-radius:999px; border:1px solid var(--nav-border); background:#fff; color:var(--nav-ink-muted); font-size:14px}
|
|
|
|
.page{max-width:1100px; margin:18px auto; padding:0 16px}
|
|
.messages{margin:12px 0; display:grid; gap:8px}
|
|
.msg{padding:10px 12px; border-radius:10px; border:1px solid #e5e7eb; background:#fff}
|
|
.msg.info{border-color:#dbeafe; background:#eff6ff}
|
|
.msg.success{border-color:#bbf7d0; background:#ecfdf5}
|
|
.msg.warning{border-color:#fde68a; background:#fffbeb}
|
|
.msg.error{border-color:#fecaca; background:#fef2f2}
|
|
@media (max-width:780px){ .topbar{gap:10px; padding:12px 12px} .nav-right{gap:8px} .nav-btn{padding:8px 10px} }
|
|
|
|
/* --- additions for mobile hamburger --- */
|
|
.hamburger { display:none; }
|
|
.mobile-menu[hidden]{ display:none; }
|
|
|
|
@media (max-width:700px){
|
|
.desktop-nav { display:none; }
|
|
|
|
.hamburger{
|
|
display:inline-flex;
|
|
width:38px; height:34px;
|
|
align-items:center; justify-content:center;
|
|
border:1px solid var(--nav-border);
|
|
border-radius:10px;
|
|
background:var(--btn-bg);
|
|
cursor:pointer;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
.hamburger span{
|
|
display:block;
|
|
width:20px; height:2px;
|
|
margin:0;
|
|
background:var(--nav-ink);
|
|
border-radius:2px;
|
|
}
|
|
|
|
.mobile-menu{
|
|
position:absolute; top:56px; right:16px; left:16px; z-index:50;
|
|
}
|
|
.mobile-menu-inner{
|
|
background:#fff; border:1px solid var(--nav-border);
|
|
border-radius:12px; box-shadow:0 8px 30px rgba(0,0,0,.08);
|
|
padding:8px; display:grid; gap:2px;
|
|
}
|
|
.mobile-link{
|
|
display:block; padding:12px 14px; border-radius:10px;
|
|
text-decoration:none; color:var(--nav-ink);
|
|
}
|
|
.mobile-link:hover{ background:var(--btn-hover); }
|
|
.mobile-link.primary{ background:var(--nav-brand); color:#fff; }
|
|
.mobile-link.danger{ color:#b91c1c; }
|
|
.mobile-user{ padding:10px 14px; color:var(--nav-ink-muted); font-size:14px; }
|
|
}
|
|
|
|
/* --- user menu (desktop dropdown) --- */
|
|
.user-chip{ cursor:pointer; text-decoration:none; }
|
|
.user-chip:hover{ background:var(--btn-hover); }
|
|
|
|
.user-dropdown{ position:relative; }
|
|
.user-menu{
|
|
position:absolute; right:0; top:calc(100% + 8px);
|
|
background:#fff; border:1px solid var(--nav-border);
|
|
border-radius:12px; box-shadow:0 8px 30px rgba(0,0,0,.08);
|
|
min-width: 160px; padding:6px; display:grid; gap:4px; z-index:60;
|
|
}
|
|
.user-menu[hidden]{ display:none !important; }
|
|
|
|
.user-menu .menu-item{
|
|
display:block; width:100%; text-align:left;
|
|
padding:10px 12px; border-radius:10px;
|
|
text-decoration:none; color:var(--nav-ink);
|
|
background:transparent; border:none; cursor:pointer;
|
|
font-size:16px; /* ensure uniform size */
|
|
font-family:inherit;
|
|
}
|
|
.user-menu .menu-item:hover{ background:var(--btn-hover); }
|
|
.user-menu .danger-btn{ color:#b91c1c; }
|
|
</style>
|
|
</head>
|
|
|
|
<body class="{% block body_class %}{% endblock %}">
|
|
<div class="topbar-wrap">
|
|
<div class="topbar">
|
|
<div class="brand">
|
|
<a class="brand-title" href="{% url 'search' %}">Illustrations Database</a>
|
|
<a class="version-link" href="https://git.lan/joshlaymon/Illustrations/wiki" title="Release notes">{{ APP_VERSION }}</a>
|
|
</div>
|
|
|
|
<div class="nav-right desktop-nav">
|
|
<a class="nav-btn" href="{% url 'search' %}">Find</a>
|
|
<a class="btn btn-success" href="{% url 'entry_add' %}">Create</a>
|
|
<a class="nav-btn" href="{% url 'stats' %}">Insights</a>
|
|
|
|
{% if user.is_authenticated %}
|
|
<!-- USER MENU -->
|
|
<div class="user-dropdown">
|
|
<button id="userMenuBtn" class="user-chip" aria-haspopup="true" aria-expanded="false">
|
|
{{ user.username }}
|
|
</button>
|
|
<div id="userMenu" class="user-menu" hidden>
|
|
{% if user.is_superuser %}
|
|
<a class="menu-item" href="/admin/">Admin</a>
|
|
{% endif %}
|
|
{% if user.is_staff %}
|
|
<a class="menu-item" href="{% url 'export_csv' %}">Backup</a>
|
|
{% endif %}
|
|
<a class="menu-item" href="{% url 'settings_home' %}">Settings</a>
|
|
<form method="post" action="{% url 'logout' %}">
|
|
{% csrf_token %}
|
|
<button class="menu-item danger-btn" type="submit">Logout</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<a class="nav-btn primary" href="{% url 'login' %}">Login</a>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<button id="hamburger" class="hamburger" aria-label="Open menu" aria-controls="mobileMenu" aria-expanded="false">
|
|
<span></span><span></span><span></span>
|
|
</button>
|
|
</div>
|
|
|
|
<div id="mobileMenu" class="mobile-menu" hidden>
|
|
<nav class="mobile-menu-inner" role="menu">
|
|
<a class="mobile-link" href="{% url 'search' %}" role="menuitem">Find</a>
|
|
<a class="mobile-link" href="{% url 'entry_add' %}" role="menuitem">Create</a>
|
|
<a class="mobile-link" href="{% url 'stats' %}" role="menuitem">Insights</a>
|
|
|
|
{% if user.is_authenticated and user.is_superuser %}
|
|
<a class="mobile-link" href="/admin/" role="menuitem">Admin</a>
|
|
{% endif %}
|
|
{% if user.is_authenticated and user.is_staff %}
|
|
<a class="mobile-link" href="{% url 'export_csv' %}" role="menuitem">Backup</a>
|
|
{% endif %}
|
|
|
|
{% if user.is_authenticated %}
|
|
<div class="mobile-user">Signed in: {{ user.username }}</div>
|
|
<a class="mobile-link" href="{% url 'settings_home' %}" role="menuitem">Settings</a>
|
|
<form method="post" action="{% url 'logout' %}" role="menuitem">{% csrf_token %}
|
|
<button class="mobile-link danger" style="width:100%; text-align:left;">Logout</button>
|
|
</form>
|
|
{% else %}
|
|
<a class="mobile-link primary" href="{% url 'login' %}" role="menuitem">Login</a>
|
|
{% endif %}
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="page">
|
|
{% if messages %}
|
|
<div class="messages">
|
|
{% for message in messages %}
|
|
<div class="msg {{ message.tags|default:'info' }}">{{ message }}</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<main class="page">
|
|
{% block content %}{% endblock %}
|
|
</main>
|
|
|
|
{% block extra_body %}{% endblock %}
|
|
|
|
<script>
|
|
(function () {
|
|
/* mobile hamburger */
|
|
const btn = document.getElementById('hamburger');
|
|
const menu = document.getElementById('mobileMenu');
|
|
if (btn && menu) {
|
|
const open = () => { menu.hidden = false; btn.setAttribute('aria-expanded','true'); };
|
|
const close = () => { menu.hidden = true; btn.setAttribute('aria-expanded','false'); };
|
|
|
|
btn.addEventListener('click', () => (menu.hidden ? open() : close()));
|
|
document.addEventListener('click', (e) => {
|
|
if (menu.hidden) return;
|
|
if (!menu.contains(e.target) && !btn.contains(e.target)) close();
|
|
});
|
|
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') close(); });
|
|
window.matchMedia('(min-width: 701px)').addEventListener('change', () => close());
|
|
}
|
|
|
|
/* USER MENU: desktop dropdown */
|
|
const userBtn = document.getElementById('userMenuBtn');
|
|
const userMenu = document.getElementById('userMenu');
|
|
if (userBtn && userMenu) {
|
|
const open = () => { userMenu.hidden = false; userBtn.setAttribute('aria-expanded','true'); };
|
|
const close = () => { userMenu.hidden = true; userBtn.setAttribute('aria-expanded','false'); };
|
|
|
|
userBtn.addEventListener('click', (e) => { e.stopPropagation(); userMenu.hidden ? open() : close(); });
|
|
|
|
document.addEventListener('click', (e) => {
|
|
if (userMenu.hidden) return;
|
|
if (!userMenu.contains(e.target) && !userBtn.contains(e.target)) close();
|
|
});
|
|
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') close(); });
|
|
|
|
userMenu.addEventListener('click', (e) => {
|
|
if (e.target.closest('a') || e.target.closest('button')) close();
|
|
});
|
|
|
|
window.addEventListener('blur', close);
|
|
}
|
|
})();
|
|
|
|
// Safe to include site-wide; it only adds a class.
|
|
(function(){
|
|
if (localStorage.getItem("darkMode") === "true") {
|
|
document.body.classList.add("dark-mode");
|
|
}
|
|
})();
|
|
</script>
|
|
<script>
|
|
// Sticky header: add shadow when page is scrolled
|
|
(function () {
|
|
const bar = document.querySelector('.topbar-wrap');
|
|
if (!bar) return;
|
|
|
|
const onScroll = () => {
|
|
const y = window.scrollY || document.documentElement.scrollTop || 0;
|
|
bar.classList.toggle('is-scrolled', y > 2);
|
|
};
|
|
|
|
document.addEventListener('scroll', onScroll, { passive: true });
|
|
onScroll(); // initialize
|
|
})();
|
|
</script>
|
|
<!-- === PWA service worker registration (BEGIN) === -->
|
|
<script>
|
|
if ('serviceWorker' in navigator) {
|
|
window.addEventListener('load', function () {
|
|
navigator.serviceWorker.register('/service-worker.js').catch(console.error);
|
|
});
|
|
}
|
|
</script>
|
|
<!-- === PWA service worker registration (END) === -->
|
|
</body>
|
|
</html> |