Files
Crumb-Core-v.1/app/templates/base.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>
&copy; 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>