16 KiB
16 KiB
🏠 Home Template System - Implementation Plan
🎯 Ziel
Flexibles Home Template pro Container-Deployment:
- Verwendet Pico CSS (statt Tailwind)
- Deployment Config (JSON) pro Container
- Branko.de Content als Basis
- Multilingual (de/en/fr)
- Einfach austauschbar bei neuem Container
📁 Struktur
compose/
deployment_config.json # Pro Container anpassbar!
app/
routers/
home.py # Home routes (/, /about, /crew, etc.)
templates/
home/
base_home.html # Base für Home (ohne Auth)
index.html # Landing Page
about.html # Mission / Wurzeln
crew.html # Character Cards
hardware.html # Hardware Info
software.html # Software Info
contact.html # Kontakt / Impressum
static/
css/
home_default.css # Standard Home Theme
home_forest.css # Wald Theme (dunkel/grün)
home_light.css # Hell Theme
data/
testimonials.de.json # Testimonials Deutsch
testimonials.en.json # English
testimonials.fr.json # Français
characters.json # Character Definitions
assets/
logo.png
hero_bg.jpg
🔧 1. Deployment Config
compose/deployment_config.json
{
"deployment_id": "crumbforest_main",
"deployment_name": "Crumbforest Main",
"base_url": "https://branko.de",
"home": {
"enabled": true,
"theme": "forest",
"default_lang": "de",
"languages": ["de", "en", "fr"],
"hero": {
"title": "🌳 Crumbforest",
"subtitle": "Wo Fragen wachsen. Und jeder Krümel zählt.",
"cta_text": "Den Wald entdecken",
"cta_link": "#explore"
},
"mission": {
"title": "🌲 Unsere Wurzeln",
"description": "Crumbforest ist ein offenes Lern-Ökosystem mit Kindern, Maschinen und Natur.",
"values": [
{
"icon": "🦉",
"title": "Fragen",
"text": "Jedes Kind darf fragen. Wir schützen dieses Recht in jedem Terminal."
},
{
"icon": "🛠️",
"title": "Bauen",
"text": "Hands-on Lernen mit Raspberry Pi, Bash, Blockly und mehr."
},
{
"icon": "🌐",
"title": "Verbinden",
"text": "Unsere Rollen und APIs bilden ein Resonanz-Netz."
}
]
},
"sections": {
"testimonials": true,
"crew": true,
"hardware": true,
"software": true,
"contact": true
},
"navigation": [
{"label": "Home", "url": "/", "icon": "🏠"},
{"label": "Mission", "url": "/about", "icon": "🌲"},
{"label": "Crew", "url": "/crew", "icon": "🌟"},
{"label": "Hardware", "url": "/hardware", "icon": "🔧"},
{"label": "Software", "url": "/software", "icon": "💻"},
{"label": "Login", "url": "/de/login", "icon": "🔐"}
],
"footer": {
"tagline": "Made with 💚 in the Crumbforest",
"links": [
{"label": "Impressum", "url": "/impressum"},
{"label": "Datenschutz", "url": "/datenschutz"}
]
}
},
"features": {
"rag_system": true,
"diary_system": true,
"document_search": true,
"roles_web": false
}
}
🎨 2. Pico CSS Home Theme
app/static/css/home_forest.css
/* Crumbforest Forest Theme - Dark & Green */
:root {
--pico-font-family: "Inter", system-ui, sans-serif;
/* Forest Colors */
--pico-primary: #10b981; /* Emerald */
--pico-primary-hover: #059669;
--pico-primary-focus: rgba(16, 185, 129, 0.125);
--pico-background-color: #0f172a; /* Dark Blue-Gray */
--pico-color: #e2e8f0; /* Light Gray */
/* Gradients */
--hero-gradient: linear-gradient(135deg, #064e3b 0%, #10b981 100%);
--section-accent: #1e293b;
}
/* Hero Section */
.hero {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: var(--hero-gradient);
text-align: center;
padding: 2rem;
}
.hero h1 {
font-size: 4rem;
margin-bottom: 1rem;
font-weight: 900;
}
.hero p {
font-size: 1.5rem;
margin-bottom: 2rem;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
.hero .cta-button {
background: white;
color: black;
padding: 1rem 2rem;
border-radius: 2rem;
font-weight: bold;
text-decoration: none;
display: inline-block;
transition: all 0.3s;
}
.hero .cta-button:hover {
background: #fbbf24; /* Yellow */
transform: scale(1.05);
}
/* Language Switcher */
.lang-switcher {
display: flex;
gap: 1rem;
justify-content: center;
margin-top: 2rem;
}
.lang-switcher a {
background: white;
color: black;
padding: 0.5rem 1.5rem;
border-radius: 2rem;
font-weight: bold;
text-decoration: none;
}
/* Mission Section */
.mission {
padding: 4rem 2rem;
max-width: 1200px;
margin: 0 auto;
text-align: center;
}
.mission h2 {
font-size: 2.5rem;
margin-bottom: 2rem;
}
.values-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
margin-top: 2rem;
text-align: left;
}
.value-card {
background: var(--section-accent);
padding: 2rem;
border-radius: 1rem;
}
.value-card h3 {
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
/* Character Cards */
.crew-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1.5rem;
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
}
.character-card {
background: var(--section-accent);
padding: 1.5rem;
border-radius: 1rem;
text-align: center;
cursor: pointer;
transition: all 0.3s;
border: 2px solid transparent;
}
.character-card:hover {
background: var(--pico-primary);
transform: translateY(-5px);
border-color: var(--pico-primary-hover);
}
.character-card .icon {
font-size: 3rem;
margin-bottom: 0.5rem;
}
/* Modal */
dialog {
border-radius: 1rem;
border: none;
padding: 2rem;
max-width: 600px;
background: var(--pico-background-color);
}
dialog::backdrop {
background: rgba(0, 0, 0, 0.8);
}
/* Testimonials */
.testimonials {
background: #7c3aed; /* Purple */
padding: 4rem 2rem;
text-align: center;
}
.testimonial-slide {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
.testimonial-slide p {
font-size: 1.25rem;
font-style: italic;
margin-bottom: 1rem;
}
/* Responsive */
@media (max-width: 768px) {
.hero h1 {
font-size: 2.5rem;
}
.hero p {
font-size: 1.25rem;
}
.values-grid {
grid-template-columns: 1fr;
}
}
🧩 3. Base Home Template
app/templates/home/base_home.html
<!DOCTYPE html>
<html lang="{{ lang }}" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{{ deployment.home.hero.title }}{% endblock %}</title>
<!-- SEO -->
<meta name="description" content="{% block description %}{{ deployment.home.mission.description }}{% endblock %}">
<meta name="keywords" content="Crumbforest, Kinderfragen, Lernen, Terminal, Raspberry Pi, Open Source">
<meta name="author" content="Die Crumbforest-Crew">
<meta name="robots" content="index, follow">
<!-- Open Graph -->
<meta property="og:title" content="{% block og_title %}{{ deployment.home.hero.title }}{% endblock %}">
<meta property="og:description" content="{{ deployment.home.hero.subtitle }}">
<meta property="og:url" content="{{ deployment.base_url }}">
<!-- CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
<link rel="stylesheet" href="/static/css/home_{{ deployment.home.theme }}.css">
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- Navigation -->
<nav class="container-fluid">
<ul>
<li><strong>{{ deployment.home.hero.title }}</strong></li>
</ul>
<ul>
{% for nav_item in deployment.home.navigation %}
<li><a href="{{ nav_item.url }}">{{ nav_item.icon }} {{ nav_item.label }}</a></li>
{% endfor %}
</ul>
</nav>
<!-- Main Content -->
{% block content %}{% endblock %}
<!-- Footer -->
<footer class="container">
<small>
{{ deployment.home.footer.tagline }}
{% for link in deployment.home.footer.links %}
| <a href="{{ link.url }}">{{ link.label }}</a>
{% endfor %}
</small>
</footer>
{% block extra_js %}{% endblock %}
</body>
</html>
🏠 4. Home Index Template
app/templates/home/index.html
{% extends "home/base_home.html" %}
{% block content %}
<!-- Hero Section -->
<section class="hero">
<div>
<h1>{{ deployment.home.hero.title }}</h1>
<p>{{ deployment.home.hero.subtitle }}</p>
<a href="{{ deployment.home.hero.cta_link }}" class="cta-button">
{{ deployment.home.hero.cta_text }}
</a>
<!-- Language Switcher -->
<div class="lang-switcher">
{% for lang_code in deployment.home.languages %}
<a href="?lang={{ lang_code }}">{{ lang_code.upper() }}</a>
{% endfor %}
</div>
</div>
</section>
<!-- Mission Section -->
<section id="explore" class="mission">
<h2>{{ deployment.home.mission.title }}</h2>
<p>{{ deployment.home.mission.description }}</p>
<div class="values-grid">
{% for value in deployment.home.mission.values %}
<div class="value-card">
<h3>{{ value.icon }} {{ value.title }}</h3>
<p>{{ value.text }}</p>
</div>
{% endfor %}
</div>
</section>
{% if deployment.home.sections.testimonials %}
<!-- Testimonials -->
<section class="testimonials">
<h2>💬 Stimmen aus dem Crumbforest</h2>
<div class="testimonial-slide" id="testimonial-container">
<p id="testimonial-text"></p>
<small id="testimonial-author"></small>
</div>
<div style="display: flex; justify-content: center; gap: 2rem; margin-top: 2rem;">
<button onclick="prevTestimonial()">⬅️</button>
<button onclick="nextTestimonial()">➡️</button>
</div>
</section>
{% endif %}
{% if deployment.home.sections.crew %}
<!-- Character Preview -->
<section class="container">
<h2 style="text-align: center;">🌟 Lerne die Crew kennen</h2>
<div style="text-align: center; margin: 2rem 0;">
<a href="/crew" role="button">Alle Characters entdecken</a>
</div>
</section>
{% endif %}
<!-- Access Section -->
<section class="container">
<h2 style="text-align: center;">🌐 Zugang zum Wald</h2>
<div style="display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;">
{% if deployment.features.rag_system %}
<a href="/de/login" role="button">RAG System</a>
{% endif %}
{% if deployment.features.document_search %}
<a href="/de/login" role="button">Document Search</a>
{% endif %}
<a href="{{ deployment.base_url }}/hardware" role="button" class="outline">Hardware Info</a>
<a href="{{ deployment.base_url }}/software" role="button" class="outline">Software Info</a>
</div>
</section>
{% endblock %}
{% block extra_js %}
<script>
let testimonials = [];
let currentIndex = 0;
fetch('/static/data/testimonials.{{ lang }}.json')
.then(res => res.json())
.then(data => {
testimonials = data;
showTestimonial(0);
setInterval(nextTestimonial, 8000);
});
function showTestimonial(index) {
const t = testimonials[index];
document.getElementById('testimonial-text').textContent = `"${t.message}"`;
document.getElementById('testimonial-author').textContent = `– ${t.author}, ${t.role}`;
}
function nextTestimonial() {
currentIndex = (currentIndex + 1) % testimonials.length;
showTestimonial(currentIndex);
}
function prevTestimonial() {
currentIndex = (currentIndex - 1 + testimonials.length) % testimonials.length;
showTestimonial(currentIndex);
}
</script>
{% endblock %}
🦉 5. Crew Page Template
app/templates/home/crew.html
{% extends "home/base_home.html" %}
{% block title %}Crew - {{ deployment.home.hero.title }}{% endblock %}
{% block content %}
<main class="container">
<hgroup>
<h1>🌟 Die Crumbforest Crew</h1>
<p>Lerne unsere Characters kennen!</p>
</hgroup>
<div class="crew-grid">
{% for character in characters %}
<div class="character-card" onclick="showCharacter('{{ character.id }}')">
<div class="icon">{{ character.icon }}</div>
<h3>{{ character.name }}</h3>
<p>{{ character.short }}</p>
</div>
<!-- Dialog for this character -->
<dialog id="dialog-{{ character.id }}">
<article>
<header>
<button aria-label="Close" rel="prev" onclick="closeCharacter('{{ character.id }}')"></button>
<h2>{{ character.icon }} {{ character.name }}</h2>
</header>
<p>{{ character.description }}</p>
</article>
</dialog>
{% endfor %}
</div>
</main>
{% endblock %}
{% block extra_js %}
<script>
function showCharacter(id) {
document.getElementById('dialog-' + id).showModal();
}
function closeCharacter(id) {
document.getElementById('dialog-' + id).close();
}
</script>
{% endblock %}
🔌 6. FastAPI Router
app/routers/home.py
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse
import json
router = APIRouter()
# Load deployment config
with open('compose/deployment_config.json') as f:
deployment_config = json.load(f)
# Load characters
with open('app/static/data/characters.json') as f:
characters_data = json.load(f)
@router.get("/", response_class=HTMLResponse)
async def home_index(req: Request, lang: str = "de"):
"""
Public home page - no auth required.
"""
req.session["lang"] = lang
return req.app.state.render(
req,
"home/index.html",
deployment=deployment_config,
lang=lang
)
@router.get("/about", response_class=HTMLResponse)
async def home_about(req: Request):
"""
About / Mission page.
"""
lang = req.session.get("lang", "de")
return req.app.state.render(
req,
"home/about.html",
deployment=deployment_config,
lang=lang
)
@router.get("/crew", response_class=HTMLResponse)
async def home_crew(req: Request):
"""
Crew / Characters page.
"""
lang = req.session.get("lang", "de")
return req.app.state.render(
req,
"home/crew.html",
deployment=deployment_config,
characters=characters_data,
lang=lang
)
@router.get("/hardware", response_class=HTMLResponse)
async def home_hardware(req: Request):
lang = req.session.get("lang", "de")
return req.app.state.render(
req,
"home/hardware.html",
deployment=deployment_config,
lang=lang
)
@router.get("/software", response_class=HTMLResponse)
async def home_software(req: Request):
lang = req.session.get("lang", "de")
return req.app.state.render(
req,
"home/software.html",
deployment=deployment_config,
lang=lang
)
📊 7. Implementation Steps
Phase 1: Foundation (2-3 Stunden)
deployment_config.jsonerstellenhome_forest.cssPico Themebase_home.htmlTemplate/routers/home.pyRouter
Phase 2: Content (2-3 Stunden)
index.htmlLanding Pagecrew.htmlCharacter Pagecharacters.json&testimonials.*.jsonDaten- Branko.de Texte übernehmen
Phase 3: Testing (1 Stunde)
- Multilingual testen (de/en/fr)
- Mobile Responsive prüfen
- Navigation Flow
- Character Modals
Phase 4: Deployment (30 Min)
- Docker Rebuild
- Config per Container anpassbar
- Verify Home vs Admin Separation
🎨 Vorteile
✅ Pico CSS - Konsistent mit dem Rest des Systems ✅ Deployment Config - Neues Design = neue JSON ✅ Kein Auth - Home ist öffentlich, Login für Features ✅ Multilingual - de/en/fr Support ✅ Flexibel - Jeder Container kann eigenes Design haben ✅ Characters - Branko.de Crew integriert
🚀 Next Step?
Soll ich mit Phase 1 starten?
- deployment_config.json
- home_forest.css (Pico-basiert)
- base_home.html
- /routers/home.py
Dann hast du ein lauffähiges Home Template! 🌲