Files
Crumb-Core-v.1/HOME_TEMPLATE_PLAN.md

16 KiB
Raw Blame History

🏠 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.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! 🌲