698 lines
16 KiB
Markdown
698 lines
16 KiB
Markdown
# 🏠 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`**
|
||
|
||
```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`**
|
||
|
||
```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`**
|
||
|
||
```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`**
|
||
|
||
```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`**
|
||
|
||
```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`**
|
||
|
||
```python
|
||
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.json` erstellen
|
||
- [ ] `home_forest.css` Pico Theme
|
||
- [ ] `base_home.html` Template
|
||
- [ ] `/routers/home.py` Router
|
||
|
||
### Phase 2: Content (2-3 Stunden)
|
||
- [ ] `index.html` Landing Page
|
||
- [ ] `crew.html` Character Page
|
||
- [ ] `characters.json` & `testimonials.*.json` Daten
|
||
- [ ] 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! 🌲
|