using penguiui
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -22,3 +22,7 @@ target/
|
||||
uploads/
|
||||
*.report.html
|
||||
favicon_io.zip
|
||||
|
||||
# Tailwind standalone binary (downloaded via `make tailwind`)
|
||||
bin/tailwindcss
|
||||
node_modules/
|
||||
|
||||
29
Makefile
29
Makefile
@@ -1,6 +1,33 @@
|
||||
COMPOSE = docker compose -f docker-compose.prod.yml --env-file .env.production
|
||||
|
||||
.PHONY: up down restart logs build ps
|
||||
# --- Frontend (Tailwind v4 + PenguinUI) -----------------------------
|
||||
# Uses the Tailwind v4 standalone binary (no Node required at runtime).
|
||||
# The compiled assets/static/css/app.css is committed and served by loco.
|
||||
TW = bin/tailwindcss
|
||||
CSS_IN = assets/css/app.css
|
||||
CSS_OUT = assets/static/css/app.css
|
||||
UNAME_M := $(shell uname -m)
|
||||
ifeq ($(UNAME_M),aarch64)
|
||||
TW_TARGET = tailwindcss-linux-arm64
|
||||
else
|
||||
TW_TARGET = tailwindcss-linux-x64
|
||||
endif
|
||||
|
||||
.PHONY: up down restart logs build ps css css-watch tailwind
|
||||
|
||||
$(TW):
|
||||
@mkdir -p bin
|
||||
curl -fsSL -o $(TW) \
|
||||
"https://github.com/tailwindlabs/tailwindcss/releases/latest/download/$(TW_TARGET)"
|
||||
chmod +x $(TW)
|
||||
|
||||
tailwind: $(TW)
|
||||
|
||||
css: $(TW)
|
||||
$(TW) -i $(CSS_IN) -o $(CSS_OUT) --minify
|
||||
|
||||
css-watch: $(TW)
|
||||
$(TW) -i $(CSS_IN) -o $(CSS_OUT) --watch
|
||||
|
||||
up:
|
||||
$(COMPOSE) up -d --build
|
||||
|
||||
73
assets/css/app.css
Normal file
73
assets/css/app.css
Normal file
@@ -0,0 +1,73 @@
|
||||
/* ============================================================
|
||||
* Tailwind v4 source — built into assets/static/css/app.css
|
||||
* ------------------------------------------------------------
|
||||
* Stack: Tailwind CSS v4 + Alpine.js v3 + PenguinUI components
|
||||
* (https://www.penguinui.com). PenguinUI is copy-paste: paste a
|
||||
* component's markup into a template and it picks up the design
|
||||
* tokens defined in the @theme block below.
|
||||
*
|
||||
* Build: make css (one-off, minified)
|
||||
* make css-watch (rebuild on change while developing)
|
||||
*
|
||||
* The compiled output assets/static/css/app.css IS committed, so
|
||||
* the Docker image / loco static server need no Node at runtime.
|
||||
* ============================================================ */
|
||||
|
||||
@import "tailwindcss";
|
||||
|
||||
/* Scan every template so used utility classes are emitted. */
|
||||
@source "../views";
|
||||
|
||||
/* PenguinUI toggles dark styles with a `dark:` variant. This app
|
||||
* already sets <html data-theme="dark|light"> (see base.html), so
|
||||
* key the variant off that attribute instead of the OS setting. */
|
||||
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
|
||||
|
||||
/* === PenguinUI design tokens ================================
|
||||
* "Modern" starting palette. Swap any line for another Tailwind
|
||||
* color (e.g. --color-primary: var(--color-emerald-600)) or grab
|
||||
* a ready-made theme from https://www.penguinui.com/theme.
|
||||
* Components reference these as bg-primary, text-on-surface,
|
||||
* dark:bg-surface-dark, border-outline, etc.
|
||||
* ============================================================ */
|
||||
@theme {
|
||||
/* light mode */
|
||||
--color-surface: var(--color-white);
|
||||
--color-surface-alt: var(--color-slate-100);
|
||||
--color-on-surface: var(--color-slate-700);
|
||||
--color-on-surface-strong: var(--color-slate-900);
|
||||
--color-primary: var(--color-indigo-600);
|
||||
--color-on-primary: var(--color-white);
|
||||
--color-secondary: var(--color-slate-600);
|
||||
--color-on-secondary: var(--color-white);
|
||||
--color-outline: var(--color-slate-300);
|
||||
--color-outline-strong: var(--color-slate-800);
|
||||
|
||||
/* dark mode */
|
||||
--color-surface-dark: var(--color-slate-900);
|
||||
--color-surface-dark-alt: var(--color-slate-800);
|
||||
--color-on-surface-dark: var(--color-slate-300);
|
||||
--color-on-surface-dark-strong: var(--color-white);
|
||||
--color-primary-dark: var(--color-indigo-400);
|
||||
--color-on-primary-dark: var(--color-slate-950);
|
||||
--color-secondary-dark: var(--color-slate-300);
|
||||
--color-on-secondary-dark: var(--color-slate-950);
|
||||
--color-outline-dark: var(--color-slate-700);
|
||||
--color-outline-dark-strong: var(--color-slate-300);
|
||||
|
||||
/* shared status colors (same in both modes) */
|
||||
--color-info: var(--color-sky-500);
|
||||
--color-on-info: var(--color-white);
|
||||
--color-success: var(--color-green-600);
|
||||
--color-on-success: var(--color-white);
|
||||
--color-warning: var(--color-amber-500);
|
||||
--color-on-warning: var(--color-white);
|
||||
--color-danger: var(--color-red-600);
|
||||
--color-on-danger: var(--color-white);
|
||||
|
||||
/* shared design tokens */
|
||||
--radius-radius: 0.375rem;
|
||||
}
|
||||
|
||||
/* Hide Alpine x-cloak elements until Alpine initializes. */
|
||||
[x-cloak] { display: none !important; }
|
||||
2
assets/static/css/app.css
Normal file
2
assets/static/css/app.css
Normal file
File diff suppressed because one or more lines are too long
5
assets/static/vendor/alpine/alpinejs-3.14.9.min.js
vendored
Normal file
5
assets/static/vendor/alpine/alpinejs-3.14.9.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -16,136 +16,141 @@
|
||||
|| (t === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
|
||||
}
|
||||
function highlightTheme(t) {
|
||||
document.querySelectorAll('[data-theme-opt]').forEach(function (b) {
|
||||
var on = b.getAttribute('data-theme-opt') === t;
|
||||
b.classList.toggle('active', on);
|
||||
var chk = b.querySelector('.opt-check');
|
||||
if (chk) chk.classList.toggle('hidden', !on);
|
||||
});
|
||||
}
|
||||
function setTheme(t) {
|
||||
localStorage.setItem('theme', t);
|
||||
applyTheme(t);
|
||||
highlightTheme(t);
|
||||
document.dispatchEvent(new CustomEvent('theme:changed', { detail: t }));
|
||||
}
|
||||
applyTheme(localStorage.getItem('theme') || 'dark');
|
||||
function currentTheme() { return localStorage.getItem('theme') || 'dark'; }
|
||||
applyTheme(currentTheme());
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function () {
|
||||
if ((localStorage.getItem('theme') || 'dark') === 'system') applyTheme('system');
|
||||
if (currentTheme() === 'system') applyTheme('system');
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
highlightTheme(localStorage.getItem('theme') || 'dark');
|
||||
function markActiveNav() {
|
||||
var path = location.pathname;
|
||||
document.querySelectorAll('.term-navlinks a[data-nav]').forEach(function (a) {
|
||||
document.querySelectorAll('a[data-nav]').forEach(function (a) {
|
||||
var h = a.getAttribute('data-nav');
|
||||
if (h === path || (h !== '/' && path.indexOf(h) === 0)) a.classList.add('is-active');
|
||||
var on = h === path || (h !== '/' && path.indexOf(h) === 0);
|
||||
if (on) a.setAttribute('aria-current', 'page');
|
||||
else a.removeAttribute('aria-current');
|
||||
});
|
||||
});
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', markActiveNav);
|
||||
document.addEventListener('htmx:afterSwap', markActiveNav);
|
||||
</script>
|
||||
<link href="/static/css/app.css?v=2026-06-16" rel="stylesheet" type="text/css">
|
||||
{% block head %}{% endblock head %}
|
||||
<script src="/static/vendor/htmx/htmx-1.9.12.min.js"></script>
|
||||
<style>
|
||||
@media (min-width: 768px) {
|
||||
.nav-menu { flex-direction: row; }
|
||||
}
|
||||
#nav-backdrop { display: none; }
|
||||
@media (max-width: 767px) {
|
||||
#nav-backdrop {
|
||||
display: block;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 40;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.15s ease, visibility 0s linear 0.2s;
|
||||
}
|
||||
.term-titlebar:has(.dropdown:focus-within) ~ #nav-backdrop {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: opacity 0.15s ease, visibility 0s;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script defer src="/static/vendor/alpine/alpinejs-3.14.9.min.js"></script>
|
||||
</head>
|
||||
<body class="flex min-h-screen flex-col bg-base-100 text-base-content antialiased">
|
||||
<header class="term-titlebar">
|
||||
<nav class="term-nav">
|
||||
<a href="/admin/dashboard" class="term-brand">{{ t(key="admin-title", lang=lang | default(value='sk')) }}</a>
|
||||
<ul class="nav-menu term-navlinks menu menu-sm hidden items-center md:flex">
|
||||
<li><a href="/admin/dashboard" data-nav="/admin/dashboard">{{ t(key="admin-dashboard", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/admin/blog/articles" data-nav="/admin/blog">{{ t(key="admin-blog", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/admin/audio/albums" data-nav="/admin/audio">{{ t(key="admin-audio", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/admin/about" data-nav="/admin/about">{{ t(key="admin-about", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/" class="t-blue">{{ t(key="admin-exit", lang=lang | default(value='sk')) }}</a></li>
|
||||
<body
|
||||
class="flex min-h-screen flex-col bg-surface text-on-surface antialiased dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
<header
|
||||
class="sticky top-0 z-30 border-b border-outline bg-surface/95 backdrop-blur dark:border-outline-dark dark:bg-surface-dark/95">
|
||||
<nav x-data="{ mobile: false }" class="mx-auto flex max-w-6xl items-center gap-4 px-4 py-3">
|
||||
<a href="/admin/dashboard"
|
||||
class="text-lg font-bold tracking-tight text-on-surface-strong dark:text-on-surface-dark-strong">
|
||||
{{ t(key="admin-title", lang=lang | default(value='sk')) }}
|
||||
</a>
|
||||
|
||||
<!-- desktop links -->
|
||||
<ul class="ml-2 hidden items-center gap-1 md:flex">
|
||||
<li><a href="/admin/dashboard" data-nav="/admin/dashboard" class="rounded-radius px-3 py-1.5 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary aria-[current=page]:text-primary aria-[current=page]:font-semibold dark:text-on-surface-dark dark:hover:bg-surface-dark-alt dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="admin-dashboard", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/admin/blog/articles" data-nav="/admin/blog" class="rounded-radius px-3 py-1.5 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary aria-[current=page]:text-primary aria-[current=page]:font-semibold dark:text-on-surface-dark dark:hover:bg-surface-dark-alt dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="admin-blog", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/admin/audio/albums" data-nav="/admin/audio" class="rounded-radius px-3 py-1.5 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary aria-[current=page]:text-primary aria-[current=page]:font-semibold dark:text-on-surface-dark dark:hover:bg-surface-dark-alt dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="admin-audio", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/admin/about" data-nav="/admin/about" class="rounded-radius px-3 py-1.5 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary aria-[current=page]:text-primary aria-[current=page]:font-semibold dark:text-on-surface-dark dark:hover:bg-surface-dark-alt dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="admin-about", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/" class="rounded-radius px-3 py-1.5 text-sm font-medium text-info transition hover:bg-surface-alt dark:hover:bg-surface-dark-alt">{{ t(key="admin-exit", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li>
|
||||
<form method="post" action="/admin/logout">
|
||||
<button type="submit" class="t-red w-full">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||
<button type="submit" class="rounded-radius px-3 py-1.5 text-sm font-medium text-danger transition hover:bg-surface-alt dark:hover:bg-surface-dark-alt">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="term-nav-right">
|
||||
<div class="dropdown dropdown-end md:hidden">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-sm btn-circle" aria-label="{{ t(key='menu', lang=lang | default(value='sk')) }}">
|
||||
|
||||
<div class="ml-auto flex items-center gap-1">
|
||||
<!-- settings (language + theme) dropdown -->
|
||||
<div x-data="{ open: false }" @keydown.escape="open = false" class="relative">
|
||||
<button type="button" @click="open = !open" :aria-expanded="open"
|
||||
aria-label="{{ t(key='settings', lang=lang | default(value='sk')) }}"
|
||||
title="{{ t(key='settings', lang=lang | default(value='sk')) }}"
|
||||
class="inline-flex size-9 items-center justify-center rounded-radius text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="h-5 w-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
</div>
|
||||
<ul tabindex="0"
|
||||
class="menu dropdown-content z-50 mt-3 w-52 border border-base-300 bg-base-200 p-2 shadow-lg">
|
||||
<li><a href="/admin/dashboard">{{ t(key="admin-dashboard", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/admin/blog/articles">{{ t(key="admin-blog", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/admin/audio/albums">{{ t(key="admin-audio", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/admin/about">{{ t(key="admin-about", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/" class="t-blue">{{ t(key="admin-exit", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li>
|
||||
<form method="post" action="/admin/logout">
|
||||
<button type="submit" class="t-red w-full">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-sm btn-circle" aria-label="{{ t(key='settings', lang=lang | default(value='sk')) }}" title="{{ t(key='settings', lang=lang | default(value='sk')) }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="h-5 w-5">
|
||||
stroke="currentColor" class="size-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="open" x-cloak @click.outside="open = false"
|
||||
x-transition.origin.top.right
|
||||
class="absolute right-0 mt-2 w-56 rounded-radius border border-outline bg-surface p-2 shadow-lg dark:border-outline-dark dark:bg-surface-dark-alt">
|
||||
<form method="post" action="/lang" hx-boost="false">
|
||||
<p class="px-2 py-1 text-xs font-semibold uppercase tracking-wide text-on-surface/60 dark:text-on-surface-dark/60">
|
||||
{{ t(key="settings-language", lang=lang | default(value='sk')) }}
|
||||
</p>
|
||||
<button type="submit" name="lang" value="en"
|
||||
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
|
||||
<span>{{ t(key="language-en", lang=lang | default(value='sk')) }}</span>
|
||||
{% if lang | default(value='sk') == "en" %}<span class="text-primary dark:text-primary-dark">✓</span>{% endif %}
|
||||
</button>
|
||||
<button type="submit" name="lang" value="sk"
|
||||
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
|
||||
<span>{{ t(key="language-sk", lang=lang | default(value='sk')) }}</span>
|
||||
{% if lang | default(value='sk') == "sk" %}<span class="text-primary dark:text-primary-dark">✓</span>{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
<p class="mt-1 px-2 py-1 text-xs font-semibold uppercase tracking-wide text-on-surface/60 dark:text-on-surface-dark/60">
|
||||
{{ t(key="settings-theme", lang=lang | default(value='sk')) }}
|
||||
</p>
|
||||
<div x-data="{ theme: currentTheme() }" @theme:changed.document="theme = $event.detail">
|
||||
<button type="button" @click="setTheme('system')"
|
||||
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
|
||||
<span>{{ t(key="theme-system", lang=lang | default(value='sk')) }}</span>
|
||||
<span x-show="theme === 'system'" class="text-primary dark:text-primary-dark">✓</span>
|
||||
</button>
|
||||
<button type="button" @click="setTheme('light')"
|
||||
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
|
||||
<span>{{ t(key="theme-light", lang=lang | default(value='sk')) }}</span>
|
||||
<span x-show="theme === 'light'" class="text-primary dark:text-primary-dark">✓</span>
|
||||
</button>
|
||||
<button type="button" @click="setTheme('dark')"
|
||||
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
|
||||
<span>{{ t(key="theme-dark", lang=lang | default(value='sk')) }}</span>
|
||||
<span x-show="theme === 'dark'" class="text-primary dark:text-primary-dark">✓</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" action="/lang" hx-boost="false">
|
||||
<ul tabindex="0" class="menu dropdown-content z-50 mt-3 w-56 border border-base-300 bg-base-200 p-2 shadow-lg">
|
||||
<li class="menu-title">{{ t(key="settings-language", lang=lang | default(value='sk')) }}</li>
|
||||
<li>
|
||||
<button type="submit" name="lang" value="en" class="{% if lang | default(value='sk') == 'en' %}active{% endif %}">
|
||||
{{ t(key="language-en", lang=lang | default(value='sk')) }}
|
||||
{% if lang | default(value='sk') == 'en' %}
|
||||
<span class="ml-auto">✓</span>
|
||||
{% endif %}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="submit" name="lang" value="sk" class="{% if lang | default(value='sk') == 'sk' %}active{% endif %}">
|
||||
{{ t(key="language-sk", lang=lang | default(value='sk')) }}
|
||||
{% if lang | default(value='sk') == 'sk' %}
|
||||
<span class="ml-auto">✓</span>
|
||||
{% endif %}
|
||||
</button>
|
||||
</li>
|
||||
<li class="menu-title">{{ t(key="settings-theme", lang=lang | default(value='sk')) }}</li>
|
||||
<li><button type="button" data-theme-opt="system" onclick="setTheme('system')">{{ t(key="theme-system", lang=lang | default(value='sk')) }} <span class="opt-check ml-auto hidden">✓</span></button></li>
|
||||
<li><button type="button" data-theme-opt="light" onclick="setTheme('light')">{{ t(key="theme-light", lang=lang | default(value='sk')) }} <span class="opt-check ml-auto hidden">✓</span></button></li>
|
||||
<li><button type="button" data-theme-opt="dark" onclick="setTheme('dark')">{{ t(key="theme-dark", lang=lang | default(value='sk')) }} <span class="opt-check ml-auto hidden">✓</span></button></li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- mobile hamburger -->
|
||||
<button type="button" @click="mobile = !mobile" :aria-expanded="mobile"
|
||||
aria-label="{{ t(key='menu', lang=lang | default(value='sk')) }}"
|
||||
class="inline-flex size-9 items-center justify-center rounded-radius text-on-surface transition hover:bg-surface-alt md:hidden dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- mobile menu panel -->
|
||||
<ul x-show="mobile" x-cloak @click.outside="mobile = false" x-transition
|
||||
class="absolute inset-x-0 top-full mx-4 mt-2 flex flex-col gap-1 rounded-radius border border-outline bg-surface p-2 shadow-lg md:hidden dark:border-outline-dark dark:bg-surface-dark-alt">
|
||||
<li><a href="/admin/dashboard" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary dark:text-on-surface-dark dark:hover:bg-surface-dark dark:hover:text-primary-dark">{{ t(key="admin-dashboard", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/admin/blog/articles" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary dark:text-on-surface-dark dark:hover:bg-surface-dark dark:hover:text-primary-dark">{{ t(key="admin-blog", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/admin/audio/albums" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary dark:text-on-surface-dark dark:hover:bg-surface-dark dark:hover:text-primary-dark">{{ t(key="admin-audio", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/admin/about" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary dark:text-on-surface-dark dark:hover:bg-surface-dark dark:hover:text-primary-dark">{{ t(key="admin-about", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/" class="block rounded-radius px-3 py-2 text-sm font-medium text-info hover:bg-surface-alt dark:hover:bg-surface-dark">{{ t(key="admin-exit", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li>
|
||||
<form method="post" action="/admin/logout">
|
||||
<button type="submit" class="block w-full rounded-radius px-3 py-2 text-left text-sm font-medium text-danger hover:bg-surface-alt dark:hover:bg-surface-dark">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="nav-backdrop" aria-hidden="true"></div>
|
||||
<main class="term-main">
|
||||
|
||||
<main class="mx-auto w-full max-w-6xl flex-1 px-4 py-8">
|
||||
{% block content %}{% endblock content %}
|
||||
</main>
|
||||
</body>
|
||||
|
||||
@@ -11,155 +11,158 @@
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/static/favicon/apple-touch-icon.png">
|
||||
<link rel="manifest" href="/static/favicon/site.webmanifest">
|
||||
<script>
|
||||
// Apply the saved theme before first paint to avoid a flash.
|
||||
function applyTheme(t) {
|
||||
var dark = t === 'dark'
|
||||
|| (t === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
|
||||
}
|
||||
function highlightTheme(t) {
|
||||
document.querySelectorAll('[data-theme-opt]').forEach(function (b) {
|
||||
var on = b.getAttribute('data-theme-opt') === t;
|
||||
b.classList.toggle('active', on);
|
||||
var chk = b.querySelector('.opt-check');
|
||||
if (chk) chk.classList.toggle('hidden', !on);
|
||||
});
|
||||
}
|
||||
function setTheme(t) {
|
||||
localStorage.setItem('theme', t);
|
||||
applyTheme(t);
|
||||
highlightTheme(t);
|
||||
document.dispatchEvent(new CustomEvent('theme:changed', { detail: t }));
|
||||
}
|
||||
applyTheme(localStorage.getItem('theme') || 'dark');
|
||||
function currentTheme() { return localStorage.getItem('theme') || 'dark'; }
|
||||
applyTheme(currentTheme());
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function () {
|
||||
if ((localStorage.getItem('theme') || 'dark') === 'system') applyTheme('system');
|
||||
if (currentTheme() === 'system') applyTheme('system');
|
||||
});
|
||||
// Mark the active top-nav link via aria-current (styled with Tailwind).
|
||||
function markActiveNav() {
|
||||
var path = location.pathname;
|
||||
document.querySelectorAll('.term-navlinks a[data-nav]').forEach(function (a) {
|
||||
document.querySelectorAll('a[data-nav]').forEach(function (a) {
|
||||
var h = a.getAttribute('data-nav');
|
||||
a.classList.toggle('is-active', h === path || (h !== '/' && path.indexOf(h) === 0));
|
||||
var on = h === path || (h !== '/' && path.indexOf(h) === 0);
|
||||
if (on) a.setAttribute('aria-current', 'page');
|
||||
else a.removeAttribute('aria-current');
|
||||
});
|
||||
}
|
||||
function initPage() {
|
||||
highlightTheme(localStorage.getItem('theme') || 'dark');
|
||||
markActiveNav();
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', initPage);
|
||||
document.addEventListener('htmx:afterSwap', initPage);
|
||||
document.addEventListener('DOMContentLoaded', markActiveNav);
|
||||
document.addEventListener('htmx:afterSwap', markActiveNav);
|
||||
</script>
|
||||
<link href="/static/css/app.css?v=2026-06-16" rel="stylesheet" type="text/css">
|
||||
<script src="/static/vendor/htmx/htmx-1.9.12.min.js"></script>
|
||||
<style>
|
||||
@media (min-width: 768px) {
|
||||
.nav-menu { flex-direction: row; }
|
||||
}
|
||||
#nav-backdrop { display: none; }
|
||||
@media (max-width: 767px) {
|
||||
#nav-backdrop {
|
||||
display: block;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 40;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.15s ease, visibility 0s linear 0.2s;
|
||||
}
|
||||
.term-titlebar:has(.dropdown:focus-within) ~ #nav-backdrop {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: opacity 0.15s ease, visibility 0s;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script defer src="/static/vendor/alpine/alpinejs-3.14.9.min.js"></script>
|
||||
</head>
|
||||
<body hx-boost="true" class="flex min-h-screen flex-col bg-base-100 text-base-content antialiased">
|
||||
<header class="term-titlebar">
|
||||
<nav class="term-nav">
|
||||
<a href="/" class="term-brand">{{ t(key="brand", lang=lang | default(value='sk')) }}</a>
|
||||
<ul class="nav-menu term-navlinks menu menu-sm hidden items-center md:flex">
|
||||
<li><a href="/" data-nav="/">{{ t(key="nav-home", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/blog" data-nav="/blog">{{ t(key="nav-blog", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/audio/albums" data-nav="/audio/albums">{{ t(key="nav-audio", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/audio/tracks" data-nav="/audio/tracks">{{ t(key="nav-songs", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/about" data-nav="/about">{{ t(key="nav-about", lang=lang | default(value='sk')) }}</a></li>
|
||||
<body hx-boost="true"
|
||||
class="flex min-h-screen flex-col bg-surface text-on-surface antialiased dark:bg-surface-dark dark:text-on-surface-dark">
|
||||
<header
|
||||
class="sticky top-0 z-30 border-b border-outline bg-surface/95 backdrop-blur dark:border-outline-dark dark:bg-surface-dark/95">
|
||||
<nav x-data="{ mobile: false }" class="mx-auto flex max-w-6xl items-center gap-4 px-4 py-3">
|
||||
<a href="/"
|
||||
class="text-lg font-bold tracking-tight text-on-surface-strong dark:text-on-surface-dark-strong">
|
||||
{{ t(key="brand", lang=lang | default(value='sk')) }}
|
||||
</a>
|
||||
|
||||
<!-- desktop links -->
|
||||
<ul class="ml-2 hidden items-center gap-1 md:flex">
|
||||
<li><a href="/" data-nav="/" class="rounded-radius px-3 py-1.5 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary aria-[current=page]:text-primary aria-[current=page]:font-semibold dark:text-on-surface-dark dark:hover:bg-surface-dark-alt dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-home", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/blog" data-nav="/blog" class="rounded-radius px-3 py-1.5 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary aria-[current=page]:text-primary aria-[current=page]:font-semibold dark:text-on-surface-dark dark:hover:bg-surface-dark-alt dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-blog", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/audio/albums" data-nav="/audio/albums" class="rounded-radius px-3 py-1.5 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary aria-[current=page]:text-primary aria-[current=page]:font-semibold dark:text-on-surface-dark dark:hover:bg-surface-dark-alt dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-audio", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/audio/tracks" data-nav="/audio/tracks" class="rounded-radius px-3 py-1.5 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary aria-[current=page]:text-primary aria-[current=page]:font-semibold dark:text-on-surface-dark dark:hover:bg-surface-dark-alt dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-songs", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/about" data-nav="/about" class="rounded-radius px-3 py-1.5 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary aria-[current=page]:text-primary aria-[current=page]:font-semibold dark:text-on-surface-dark dark:hover:bg-surface-dark-alt dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-about", lang=lang | default(value='sk')) }}</a></li>
|
||||
{% if logged_in_admin %}
|
||||
<li><a href="/admin/dashboard" hx-boost="false" class="t-yellow" data-nav="/admin">{{ t(key="admin-title", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/admin/dashboard" hx-boost="false" data-nav="/admin" class="rounded-radius px-3 py-1.5 text-sm font-medium text-warning transition hover:bg-surface-alt dark:hover:bg-surface-dark-alt">{{ t(key="admin-title", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li>
|
||||
<form method="post" action="/admin/logout" hx-boost="false">
|
||||
<button type="submit" class="t-red w-full">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||
<button type="submit" class="rounded-radius px-3 py-1.5 text-sm font-medium text-danger transition hover:bg-surface-alt dark:hover:bg-surface-dark-alt">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||
</form>
|
||||
</li>
|
||||
{% else %}
|
||||
<li><a href="/admin/login" data-nav="/admin/login">{{ t(key="nav-admin", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/admin/login" data-nav="/admin/login" class="rounded-radius px-3 py-1.5 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary aria-[current=page]:text-primary aria-[current=page]:font-semibold dark:text-on-surface-dark dark:hover:bg-surface-dark-alt dark:hover:text-primary-dark dark:aria-[current=page]:text-primary-dark">{{ t(key="nav-admin", lang=lang | default(value='sk')) }}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<div class="term-nav-right">
|
||||
<div class="dropdown dropdown-end md:hidden">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-sm btn-circle" aria-label="{{ t(key='menu', lang=lang | default(value='sk')) }}">
|
||||
|
||||
<!-- right side: settings + mobile toggle -->
|
||||
<div class="ml-auto flex items-center gap-1">
|
||||
<!-- settings (language + theme) dropdown -->
|
||||
<div x-data="{ open: false }" @keydown.escape="open = false" class="relative">
|
||||
<button type="button" @click="open = !open" :aria-expanded="open"
|
||||
aria-label="{{ t(key='settings', lang=lang | default(value='sk')) }}"
|
||||
title="{{ t(key='settings', lang=lang | default(value='sk')) }}"
|
||||
class="inline-flex size-9 items-center justify-center rounded-radius text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="h-5 w-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
</div>
|
||||
<ul tabindex="0"
|
||||
class="menu dropdown-content z-50 mt-3 w-52 border border-base-300 bg-base-200 p-2 shadow-lg">
|
||||
<li><a href="/">{{ t(key="nav-home", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/blog">{{ t(key="nav-blog", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/audio/albums">{{ t(key="nav-audio", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/audio/tracks">{{ t(key="nav-songs", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/about">{{ t(key="nav-about", lang=lang | default(value='sk')) }}</a></li>
|
||||
{% if logged_in_admin %}
|
||||
<li><a href="/admin/dashboard" hx-boost="false" class="t-yellow">{{ t(key="admin-title", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li>
|
||||
<form method="post" action="/admin/logout">
|
||||
<button type="submit" class="t-red w-full">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||
</form>
|
||||
</li>
|
||||
{% else %}
|
||||
<li><a href="/admin/login">{{ t(key="nav-admin", lang=lang | default(value='sk')) }}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-sm btn-circle" aria-label="{{ t(key='settings', lang=lang | default(value='sk')) }}" title="{{ t(key='settings', lang=lang | default(value='sk')) }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="h-5 w-5">
|
||||
stroke="currentColor" class="size-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="open" x-cloak @click.outside="open = false"
|
||||
x-transition.origin.top.right
|
||||
class="absolute right-0 mt-2 w-56 rounded-radius border border-outline bg-surface p-2 shadow-lg dark:border-outline-dark dark:bg-surface-dark-alt">
|
||||
<form method="post" action="/lang" hx-boost="false">
|
||||
<p class="px-2 py-1 text-xs font-semibold uppercase tracking-wide text-on-surface/60 dark:text-on-surface-dark/60">
|
||||
{{ t(key="settings-language", lang=lang | default(value='sk')) }}
|
||||
</p>
|
||||
<button type="submit" name="lang" value="en"
|
||||
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
|
||||
<span>English</span>
|
||||
{% if lang | default(value='sk') == "en" %}<span class="text-primary dark:text-primary-dark">✓</span>{% endif %}
|
||||
</button>
|
||||
<button type="submit" name="lang" value="sk"
|
||||
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
|
||||
<span>Slovenčina</span>
|
||||
{% if lang | default(value='sk') == "sk" %}<span class="text-primary dark:text-primary-dark">✓</span>{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
<p class="mt-1 px-2 py-1 text-xs font-semibold uppercase tracking-wide text-on-surface/60 dark:text-on-surface-dark/60">
|
||||
{{ t(key="settings-theme", lang=lang | default(value='sk')) }}
|
||||
</p>
|
||||
<div x-data="{ theme: currentTheme() }" @theme:changed.document="theme = $event.detail">
|
||||
<button type="button" @click="setTheme('system')"
|
||||
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
|
||||
<span>{{ t(key="theme-system", lang=lang | default(value='sk')) }}</span>
|
||||
<span x-show="theme === 'system'" class="text-primary dark:text-primary-dark">✓</span>
|
||||
</button>
|
||||
<button type="button" @click="setTheme('light')"
|
||||
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
|
||||
<span>{{ t(key="theme-light", lang=lang | default(value='sk')) }}</span>
|
||||
<span x-show="theme === 'light'" class="text-primary dark:text-primary-dark">✓</span>
|
||||
</button>
|
||||
<button type="button" @click="setTheme('dark')"
|
||||
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
|
||||
<span>{{ t(key="theme-dark", lang=lang | default(value='sk')) }}</span>
|
||||
<span x-show="theme === 'dark'" class="text-primary dark:text-primary-dark">✓</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post" action="/lang" hx-boost="false">
|
||||
<ul tabindex="0" class="menu dropdown-content z-50 mt-3 w-56 border border-base-300 bg-base-200 p-2 shadow-lg">
|
||||
<li class="menu-title">{{ t(key="settings-language", lang=lang | default(value='sk')) }}</li>
|
||||
<li>
|
||||
<button type="submit" name="lang" value="en" class="{% if lang | default(value='sk') == 'en' %}active{% endif %}">
|
||||
English
|
||||
{% if lang | default(value='sk') == 'en' %}
|
||||
<span class="ml-auto">✓</span>
|
||||
{% endif %}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="submit" name="lang" value="sk" class="{% if lang | default(value='sk') == 'sk' %}active{% endif %}">
|
||||
Slovenčina
|
||||
{% if lang | default(value='sk') == 'sk' %}
|
||||
<span class="ml-auto">✓</span>
|
||||
{% endif %}
|
||||
</button>
|
||||
</li>
|
||||
<li class="menu-title">{{ t(key="settings-theme", lang=lang | default(value='sk')) }}</li>
|
||||
<li><button type="button" data-theme-opt="system" onclick="setTheme('system')">{{ t(key="theme-system", lang=lang | default(value='sk')) }} <span class="opt-check ml-auto hidden">✓</span></button></li>
|
||||
<li><button type="button" data-theme-opt="light" onclick="setTheme('light')">{{ t(key="theme-light", lang=lang | default(value='sk')) }} <span class="opt-check ml-auto hidden">✓</span></button></li>
|
||||
<li><button type="button" data-theme-opt="dark" onclick="setTheme('dark')">{{ t(key="theme-dark", lang=lang | default(value='sk')) }} <span class="opt-check ml-auto hidden">✓</span></button></li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- mobile hamburger -->
|
||||
<button type="button" @click="mobile = !mobile" :aria-expanded="mobile"
|
||||
aria-label="{{ t(key='menu', lang=lang | default(value='sk')) }}"
|
||||
class="inline-flex size-9 items-center justify-center rounded-radius text-on-surface transition hover:bg-surface-alt md:hidden dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- mobile menu panel -->
|
||||
<ul x-show="mobile" x-cloak @click.outside="mobile = false" x-transition
|
||||
class="absolute inset-x-0 top-full mx-4 mt-2 flex flex-col gap-1 rounded-radius border border-outline bg-surface p-2 shadow-lg md:hidden dark:border-outline-dark dark:bg-surface-dark-alt">
|
||||
<li><a href="/" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary dark:text-on-surface-dark dark:hover:bg-surface-dark dark:hover:text-primary-dark">{{ t(key="nav-home", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/blog" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary dark:text-on-surface-dark dark:hover:bg-surface-dark dark:hover:text-primary-dark">{{ t(key="nav-blog", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/audio/albums" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary dark:text-on-surface-dark dark:hover:bg-surface-dark dark:hover:text-primary-dark">{{ t(key="nav-audio", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/audio/tracks" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary dark:text-on-surface-dark dark:hover:bg-surface-dark dark:hover:text-primary-dark">{{ t(key="nav-songs", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li><a href="/about" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary dark:text-on-surface-dark dark:hover:bg-surface-dark dark:hover:text-primary-dark">{{ t(key="nav-about", lang=lang | default(value='sk')) }}</a></li>
|
||||
{% if logged_in_admin %}
|
||||
<li><a href="/admin/dashboard" hx-boost="false" class="block rounded-radius px-3 py-2 text-sm font-medium text-warning hover:bg-surface-alt dark:hover:bg-surface-dark">{{ t(key="admin-title", lang=lang | default(value='sk')) }}</a></li>
|
||||
<li>
|
||||
<form method="post" action="/admin/logout" hx-boost="false">
|
||||
<button type="submit" class="block w-full rounded-radius px-3 py-2 text-left text-sm font-medium text-danger hover:bg-surface-alt dark:hover:bg-surface-dark">{{ t(key="logout", lang=lang | default(value='sk')) }}</button>
|
||||
</form>
|
||||
</li>
|
||||
{% else %}
|
||||
<li><a href="/admin/login" class="block rounded-radius px-3 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt hover:text-primary dark:text-on-surface-dark dark:hover:bg-surface-dark dark:hover:text-primary-dark">{{ t(key="nav-admin", lang=lang | default(value='sk')) }}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<div id="nav-backdrop" aria-hidden="true"></div>
|
||||
<main class="term-main">
|
||||
|
||||
<main class="mx-auto w-full max-w-6xl flex-1 px-4 py-8">
|
||||
{% block content %}{% endblock content %}
|
||||
</main>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user