300 lines
7.1 KiB
HTML
300 lines
7.1 KiB
HTML
<!doctype html>
|
|
<html lang="{{ lang }}" data-theme="auto">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="color-scheme" content="light dark">
|
|
|
|
<title>{{ seo.title if seo and seo.title else "Crumbforest 🦉" }}</title>
|
|
<meta name="description" content="{{ (seo.desc if seo else 'Crumbforest - Diary & Knowledge Management') | default('') }}">
|
|
|
|
<!-- Pico CSS v2 - Classless, Semantic, Modern -->
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
|
|
|
|
<!-- Custom Properties & Enhancements -->
|
|
<style>
|
|
:root {
|
|
--pico-font-family: system-ui, -apple-system, "Segoe UI", "Roboto", "Helvetica Neue", sans-serif;
|
|
--pico-font-family-monospace: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, monospace;
|
|
--brand-color: #10b981;
|
|
--brand-hover: #059669;
|
|
}
|
|
|
|
/* Owl Brand */
|
|
.owl-brand {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
color: var(--brand-color);
|
|
text-decoration: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.owl-brand:hover {
|
|
color: var(--brand-hover);
|
|
}
|
|
|
|
/* Header Styling */
|
|
header {
|
|
background: var(--pico-background-color);
|
|
border-bottom: 1px solid var(--pico-muted-border-color);
|
|
padding: 1rem;
|
|
}
|
|
|
|
header nav {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 1rem;
|
|
}
|
|
|
|
header nav ul {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
display: flex;
|
|
gap: 1rem;
|
|
align-items: center;
|
|
}
|
|
|
|
/* Main Content */
|
|
main {
|
|
max-width: 1200px;
|
|
margin: 2rem auto;
|
|
padding: 0 1rem;
|
|
min-height: calc(100vh - 200px);
|
|
}
|
|
|
|
/* Footer */
|
|
footer {
|
|
margin-top: 4rem;
|
|
padding: 2rem 1rem;
|
|
text-align: center;
|
|
color: var(--pico-muted-color);
|
|
border-top: 1px solid var(--pico-muted-border-color);
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
/* Flash Messages - Enhanced */
|
|
.flash-container {
|
|
max-width: 1200px;
|
|
margin: 1rem auto;
|
|
padding: 0 1rem;
|
|
}
|
|
|
|
.flash {
|
|
padding: 1rem 1.25rem;
|
|
border-radius: var(--pico-border-radius);
|
|
margin-bottom: 1rem;
|
|
border-left: 4px solid;
|
|
animation: slideIn 0.3s ease-out;
|
|
}
|
|
|
|
@keyframes slideIn {
|
|
from {
|
|
transform: translateY(-1rem);
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
transform: translateY(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.flash.info {
|
|
background: rgba(59, 130, 246, 0.1);
|
|
border-left-color: #3b82f6;
|
|
color: #1e40af;
|
|
}
|
|
|
|
.flash.success {
|
|
background: rgba(16, 185, 129, 0.1);
|
|
border-left-color: #10b981;
|
|
color: #065f46;
|
|
}
|
|
|
|
.flash.error {
|
|
background: rgba(239, 68, 68, 0.1);
|
|
border-left-color: #ef4444;
|
|
color: #991b1b;
|
|
}
|
|
|
|
.flash.warning {
|
|
background: rgba(245, 158, 11, 0.1);
|
|
border-left-color: #f59e0b;
|
|
color: #92400e;
|
|
}
|
|
|
|
[data-theme="dark"] .flash.info { color: #93c5fd; }
|
|
[data-theme="dark"] .flash.success { color: #6ee7b7; }
|
|
[data-theme="dark"] .flash.error { color: #fca5a5; }
|
|
[data-theme="dark"] .flash.warning { color: #fcd34d; }
|
|
|
|
/* Language Switcher */
|
|
.lang-switcher {
|
|
display: flex;
|
|
gap: 0.25rem;
|
|
padding: 0;
|
|
list-style: none;
|
|
margin: 0;
|
|
}
|
|
|
|
.lang-switcher a {
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: var(--pico-border-radius);
|
|
text-decoration: none;
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
opacity: 0.7;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.lang-switcher a:hover {
|
|
opacity: 1;
|
|
background: var(--pico-secondary-background);
|
|
}
|
|
|
|
.lang-switcher a.active {
|
|
opacity: 1;
|
|
background: var(--brand-color);
|
|
color: white;
|
|
}
|
|
|
|
/* User Menu */
|
|
.user-menu {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.user-menu form {
|
|
margin: 0;
|
|
display: inline;
|
|
}
|
|
|
|
.user-menu button {
|
|
margin: 0;
|
|
padding: 0.5rem 1rem;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
/* Badges */
|
|
.badge {
|
|
display: inline-block;
|
|
padding: 0.25rem 0.625rem;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
line-height: 1;
|
|
border-radius: 999px;
|
|
background: var(--pico-secondary-background);
|
|
color: var(--pico-color);
|
|
}
|
|
|
|
.badge-admin {
|
|
background: var(--brand-color);
|
|
color: white;
|
|
}
|
|
|
|
/* Cards - Enhanced */
|
|
article.card {
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
}
|
|
|
|
article.card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: var(--pico-card-box-shadow), 0 8px 16px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
/* Utility Classes */
|
|
.text-center { text-align: center; }
|
|
.text-muted { color: var(--pico-muted-color); }
|
|
.mt-0 { margin-top: 0; }
|
|
.mb-2 { margin-bottom: 2rem; }
|
|
.grid-2 {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<!-- Header -->
|
|
<header>
|
|
<nav>
|
|
<!-- Brand -->
|
|
<ul>
|
|
<li>
|
|
<a href="/{{ lang }}/" class="owl-brand">
|
|
<span>🦉</span>
|
|
<span>Crumbforest</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- Main Navigation -->
|
|
<ul>
|
|
{% if not user %}
|
|
<li><a href="/{{ lang }}/login" role="button" class="outline">Login</a></li>
|
|
{% endif %}
|
|
|
|
{% if user %}
|
|
{% if user.role == 'admin' %}
|
|
<li><a href="/admin">Admin</a></li>
|
|
{% endif %}
|
|
|
|
<li class="user-menu">
|
|
<span class="text-muted">{{ user.email }}</span>
|
|
{% if user.role == 'admin' %}
|
|
<span class="badge badge-admin">Admin</span>
|
|
{% endif %}
|
|
<form method="post" action="/logout">
|
|
<button type="submit" class="outline secondary">Logout</button>
|
|
</form>
|
|
</li>
|
|
{% endif %}
|
|
|
|
<!-- Language Switcher -->
|
|
<li>
|
|
<ul class="lang-switcher">
|
|
{% set tail = path_tail or '/' %}
|
|
<li><a href="/de{{ '' if tail=='/' else tail }}" class="{{ 'active' if lang=='de' else '' }}">DE</a></li>
|
|
<li><a href="/en{{ '' if tail=='/' else tail }}" class="{{ 'active' if lang=='en' else '' }}">EN</a></li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
</header>
|
|
|
|
<!-- Flash Messages -->
|
|
{% if flashes %}
|
|
<div class="flash-container">
|
|
{% for f in flashes %}
|
|
<div class="flash {{ f.cat }}" role="alert">
|
|
{{ f.msg }}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Main Content -->
|
|
<main>
|
|
{% block content %}{% endblock %}
|
|
</main>
|
|
|
|
<!-- Footer -->
|
|
<footer>
|
|
<small>
|
|
© 2025 Crumbforest · Built with
|
|
<a href="https://fastapi.tiangolo.com/" target="_blank">FastAPI</a> &
|
|
<a href="https://picocss.com/" target="_blank">Pico CSS</a>
|
|
· <a href="/{{ lang }}/login">Admin</a>
|
|
</small>
|
|
</footer>
|
|
</body>
|
|
</html>
|