init: extract physio-tutor from qognio-bot-widget-template@d2c816f

Source files (src/) and rendered bundle (www/) extracted on 2026-04-29T01:35:48+02:00.
Adds nginx:alpine Dockerfile + docker-compose.yml (Caddy-labels) so the bot
runs stand-alone or as a per-customer template clone.

Parent monorepo commit: d2c816f3edbc9760802a11b29ff4151c7aad4b46
Bot version: 2026-04-21
This commit is contained in:
Qognio Bot Extract 2026-04-29 01:35:48 +02:00
commit c5c6a6cd32
22 changed files with 8401 additions and 0 deletions

7
.dockerignore Normal file
View file

@ -0,0 +1,7 @@
.git
.gitignore
README.md
bot.json
src/
docker-compose.yml
*.md

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
.DS_Store
*.log
*.tmp
node_modules/

13
Dockerfile Normal file
View file

@ -0,0 +1,13 @@
# Static-bundle bot — nginx:alpine serves www/ on port 80.
FROM nginx:1.27-alpine
# nginx config: gzip + cache headers + index.html no-store
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Static bundle
COPY www/ /usr/share/nginx/html/
# Run as non-root via nginx's built-in unprivileged image features
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -q --spider http://127.0.0.1/index.html || exit 1

67
README.md Normal file
View file

@ -0,0 +1,67 @@
# Luna — PhysioTutor
Luna — dein KI-PhysioTutor. Gamified lernen mit Chat, Quiz, Flashcards und Fortschritts-Tracking. Läuft im deutschen Rechenzentrum.
```
slug : physio-tutor
version : 2026-04-21
accent : #a855f7
runtime : nginx:alpine (static bundle)
template : qognio-bot-template-core (former qognio-bot-widget-template)
```
## Layout
```
.
├── src/ source — config.yaml, welcome.html, curricula.json, etc.
├── www/ rendered, directly servable static bundle
├── Dockerfile nginx:alpine + www/ → port 80
├── docker-compose.yml bot-host pattern (Caddy-labels, restart unless-stopped)
├── nginx.conf gzip + cache + SPA fallback
└── bot.json metadata + parent_core_commit
```
## Run locally
```bash
docker compose up --build
# → http://localhost (you'll need to tweak ports for local-only use)
```
## Re-render after upstream core changes
This repo only stores src + rendered output; the rendering engine lives in
`qognio-bot-template-core`. To pull in core changes:
```bash
cd /path/to/qognio-bot-template-core
./scripts/render.sh physio-tutor --bot-repo /path/to/this/repo
git -C /path/to/this/repo commit -am "render: refresh from core@<sha>"
```
## Per-customer copy (template usage)
This repo is a **template**. To clone for a customer:
```bash
git clone <this-repo> my-customer-physio-tutor
cd my-customer-physio-tutor
# tweak src/config.yaml (slug, bot_key_value, accent), src/welcome.html, src/curricula.json
docker compose -f docker-compose.yml up --build
```
## Deploy to qognio bot-host (.42 LXC pattern — legacy)
The bot-manager spawns LXC containers named after the slug. Push www/ via:
```bash
ssh fmh@46.243.203.42
sudo lxc file push /tmp/www/* physio-tutor/var/www/html/
```
(Or run the docker-compose pattern on a Docker host — same network as Caddy.)
---
Generated by `qognio-bot-template-core/scripts/extract-to-repo.sh` on 2026-04-29T01:35:48+02:00.

14
bot.json Normal file
View file

@ -0,0 +1,14 @@
{
"slug": "physio-tutor",
"name": "Luna",
"title": "PhysioTutor",
"tagline": "PhysioTutor",
"description": "Luna — dein KI-PhysioTutor. Gamified lernen mit Chat, Quiz, Flashcards und Fortschritts-Tracking. Läuft im deutschen Rechenzentrum.",
"version": "2026-04-21",
"accent": "#a855f7",
"extracted_from": "qognio-bot-widget-template",
"parent_core_commit": "d2c816f3edbc9760802a11b29ff4151c7aad4b46",
"extracted_at": "2026-04-29T01:35:48+02:00",
"runtime": "nginx:alpine",
"default_port": 80
}

20
docker-compose.yml Normal file
View file

@ -0,0 +1,20 @@
# Stand-alone bot container.
# Designed for the "caddy" external network on the bot host (qognio pattern).
# Override the hostname via SLUG env var if you reuse this template per customer.
services:
bot:
build: .
image: qognio/bot-physio-tutor:${TAG:-latest}
container_name: bot-physio-tutor
restart: unless-stopped
networks:
- caddy
labels:
caddy: "physio-tutor.on.qognio.com"
caddy.reverse_proxy: "{{upstreams 80}}"
qognio.bot.slug: "physio-tutor"
qognio.bot.version: "2026-04-21"
networks:
caddy:
external: true

27
nginx.conf Normal file
View file

@ -0,0 +1,27 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# gzip
gzip on;
gzip_vary on;
gzip_types text/css application/javascript application/json image/svg+xml text/plain;
gzip_min_length 512;
# index.html: never cache (so welcome screen / wiring updates land instantly)
location = /index.html {
add_header Cache-Control "no-store, must-revalidate" always;
}
# static assets: cache 1h
location ~* \.(?:css|js|json|svg|png|jpe?g|webp|gif|ico|woff2?)$ {
add_header Cache-Control "public, max-age=3600" always;
try_files $uri =404;
}
location / {
try_files $uri $uri/ /index.html;
}
}

8
src/check-badges.js Normal file
View file

@ -0,0 +1,8 @@
if (state.completedQuizzes >= 1) unlockBadge('first_quiz');
if (state.maxQuizStreak >= 10) unlockBadge('10_quiz_streak');
if (state.totalAnswers >= 100) unlockBadge('100_answers');
if (state.maxStreak >= 7) unlockBadge('7_day_streak');
if (state.completedCurricula.length >= 1) unlockBadge('curriculum_complete');
const h = new Date().getHours();
if (h >= 22) unlockBadge('night_owl');
if (h < 7) unlockBadge('early_bird');

View file

@ -0,0 +1,650 @@
/* Luna Cockpit v3 — purple #a855f7, dark, no external deps */
:root {
--bg: #0a0a0f;
--bg-2: #11111b;
--bg-3: #1a1a26;
--bg-4: #232336;
--line: #2a2a3e;
--text: #e8e8f0;
--text-muted: #9090a8;
--text-dim: #6a6a85;
--accent: #a855f7;
--accent-2: #c084fc;
--accent-dark: #7c3aed;
--accent-rgb: 168, 85, 247;
--success: #10b981;
--warn: #f59e0b;
--danger: #ef4444;
--info: #38bdf8;
--shadow-sm: 0 1px 2px rgba(0,0,0,0.2);
--shadow-md: 0 4px 12px rgba(0,0,0,0.3);
--shadow-lg: 0 12px 32px rgba(0,0,0,0.5);
--radius: 8px;
--radius-lg: 12px;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Inter", "Helvetica Neue", Arial, sans-serif;
font-size: 15px;
line-height: 1.5;
min-height: 100vh;
}
a { color: var(--accent-2); text-decoration: none; }
a:hover { color: var(--accent); }
button { font: inherit; cursor: pointer; border: none; background: transparent; color: inherit; }
.cockpit {
max-width: 1280px;
margin: 0 auto;
padding: 1.5rem 1rem 6rem;
min-height: 100vh;
}
/* ─── Topbar ────────────────────────────────────────────────────── */
.topbar { display: flex; align-items: center; gap: 1rem; padding-bottom: 1.5rem; border-bottom: 1px solid var(--line); margin-bottom: 1.5rem; }
.brand { display: flex; align-items: center; gap: 0.75rem; }
.brand-icon {
width: 40px; height: 40px;
background: linear-gradient(135deg, var(--accent), var(--accent-dark));
color: white; font-weight: 700; font-size: 22px;
border-radius: var(--radius);
display: grid; place-items: center;
box-shadow: var(--shadow-md);
}
.brand-text { display: flex; flex-direction: column; }
.brand-title { font-size: 1.2rem; font-weight: 600; }
.brand-title small { color: var(--text-muted); font-weight: 400; margin-left: 0.25rem; }
.brand-sub { font-size: 0.8rem; color: var(--text-muted); }
.spacer { flex: 1; }
.auth-state { display: flex; align-items: center; gap: 0.75rem; }
.auth-user { color: var(--text-muted); font-size: 0.85rem; }
/* ─── Status block ─────────────────────────────────────────────── */
.status-block {
background: linear-gradient(135deg, rgba(var(--accent-rgb),0.08), rgba(var(--accent-rgb),0.02));
border: 1px solid rgba(var(--accent-rgb), 0.25);
border-radius: var(--radius-lg);
padding: 1rem 1.25rem;
margin-bottom: 1.5rem;
}
.status-title { font-size: 0.85rem; color: var(--accent-2); font-weight: 600; margin: 0 0 0.5rem; text-transform: uppercase; letter-spacing: 0.04em; }
.status-summary { font-size: 0.95rem; color: var(--text); }
.status-summary .dim { color: var(--text-muted); }
/* ─── Top-level Space-Tabs ─────────────────────────────────────── */
.space-tabs {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.5rem;
background: var(--bg-2);
padding: 0.5rem;
border-radius: var(--radius-lg);
border: 1px solid var(--line);
margin-bottom: 1.5rem;
}
.space-tab {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.85rem;
border-radius: var(--radius);
color: var(--text-muted);
transition: all 0.15s;
font-size: 0.95rem;
font-weight: 500;
}
.space-tab[aria-selected="true"] {
background: linear-gradient(135deg, rgba(var(--accent-rgb),0.18), rgba(var(--accent-rgb),0.05));
color: var(--accent-2);
box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb), 0.4);
}
.space-tab:hover { background: var(--bg-3); color: var(--text); }
.space-tab[aria-selected="true"]:hover { color: var(--accent-2); }
.space-icon { font-size: 1.1rem; }
/* Each space-section is hidden unless data-active=true */
.space { display: none; }
.space[data-active="true"] { display: block; }
.space-intro { margin-bottom: 1.25rem; }
.space-intro h2 { margin: 0 0 0.4rem; font-size: 1.15rem; }
.space-intro p { margin: 0; color: var(--text-muted); font-size: 0.9rem; }
/* ─── Folder tabs (Dokumente) ──────────────────────────────────── */
.folder-tabs {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.5rem;
background: var(--bg-2);
padding: 0.5rem;
border-radius: var(--radius-lg);
border: 1px solid var(--line);
margin-bottom: 1rem;
}
.folder-tab {
display: flex; flex-direction: column; align-items: center; gap: 0.25rem;
padding: 0.75rem; border-radius: var(--radius);
color: var(--text-muted);
transition: background 0.15s, color 0.15s;
}
.folder-tab[aria-selected="true"] {
background: rgba(var(--accent-rgb), 0.12);
color: var(--text);
box-shadow: inset 0 -2px 0 var(--accent);
}
.folder-tab:hover { background: var(--bg-3); color: var(--text); }
.folder-icon { font-size: 1.25rem; }
.folder-label { font-size: 0.85rem; font-weight: 500; }
.folder-count {
font-size: 0.7rem; background: var(--bg-3); color: var(--text-muted);
padding: 0.1rem 0.5rem; border-radius: 10px; min-width: 24px; text-align: center;
}
.folder-tab[aria-selected="true"] .folder-count {
background: rgba(var(--accent-rgb), 0.25);
color: var(--accent-2);
}
.folder-body {
background: var(--bg-2); border: 1px solid var(--line);
border-radius: var(--radius-lg); padding: 1.25rem;
}
.folder-help { font-size: 0.85rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.5; }
.upload-zone {
border: 2px dashed var(--line); border-radius: var(--radius-lg);
padding: 2rem 1rem; text-align: center;
transition: border-color 0.15s, background 0.15s;
cursor: pointer; margin-bottom: 1rem;
}
.upload-zone:hover, .upload-zone:focus, .upload-zone[data-dragover="true"] {
border-color: var(--accent); background: rgba(var(--accent-rgb), 0.05);
outline: none;
}
.upload-cta { display: flex; flex-direction: column; align-items: center; gap: 0.4rem; }
.upload-icon { font-size: 1.5rem; opacity: 0.6; }
.upload-text { font-size: 1rem; color: var(--text); }
.upload-hint { font-size: 0.8rem; color: var(--text-muted); }
.upload-progress { margin-top: 1rem; display: flex; flex-direction: column; gap: 0.5rem; }
.upload-progress-row {
display: flex; align-items: center; gap: 0.75rem;
padding: 0.5rem 0.75rem; background: var(--bg-3);
border-radius: var(--radius); font-size: 0.85rem;
}
.upload-progress-row .upload-bar { flex: 1; height: 4px; background: var(--bg); border-radius: 2px; overflow: hidden; }
.upload-progress-row .upload-bar-fill { height: 100%; background: var(--accent); width: 0%; transition: width 0.2s; }
/* ─── Document list ────────────────────────────────────────────── */
.doc-list { list-style: none; padding: 0; margin: 0; }
.doc-row { display: flex; align-items: center; gap: 1rem; padding: 0.75rem; border-bottom: 1px solid var(--line); transition: background 0.1s; }
.doc-row:hover { background: var(--bg-3); }
.doc-row.is-disabled { opacity: 0.55; }
.doc-meta { flex: 1; min-width: 0; }
.doc-name { font-size: 0.95rem; color: var(--text); margin: 0; word-break: break-word; }
.doc-info { font-size: 0.75rem; color: var(--text-muted); margin-top: 0.15rem; }
.doc-info .ok { color: var(--success); }
.doc-info .pending { color: var(--warn); }
.doc-info .err { color: var(--danger); }
.doc-actions { display: flex; gap: 0.5rem; align-items: center; }
.toggle {
position: relative; width: 42px; height: 22px;
background: var(--bg-3); border-radius: 11px;
cursor: pointer; transition: background 0.15s; flex-shrink: 0;
}
.toggle::after {
content: ""; position: absolute; top: 2px; left: 2px;
width: 18px; height: 18px; background: var(--text-muted);
border-radius: 50%; transition: left 0.15s, background 0.15s;
}
.toggle[data-on="true"] { background: rgba(var(--accent-rgb), 0.4); }
.toggle[data-on="true"]::after { left: 22px; background: var(--accent); }
.btn-icon {
width: 32px; height: 32px;
display: grid; place-items: center;
border-radius: var(--radius);
color: var(--text-muted);
transition: background 0.1s, color 0.1s;
}
.btn-icon:hover { background: var(--bg-3); color: var(--danger); }
.btn-icon-x {
width: 28px; height: 28px;
border-radius: 50%;
color: var(--text-muted);
transition: background 0.1s, color 0.1s;
}
.btn-icon-x:hover { background: var(--bg-3); color: var(--text); }
.doc-empty, .empty-state {
padding: 1.5rem; text-align: center;
color: var(--text-muted); font-size: 0.9rem;
}
/* ─── Buttons ──────────────────────────────────────────────────── */
.btn-primary {
background: var(--accent); color: white;
padding: 0.6rem 1.25rem; border-radius: var(--radius);
font-weight: 500; transition: background 0.15s;
}
.btn-primary:hover { background: var(--accent-dark); }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-secondary {
background: var(--bg-3); color: var(--text);
padding: 0.5rem 1rem; border-radius: var(--radius);
font-size: 0.85rem; border: 1px solid var(--line);
}
.btn-secondary:hover { background: var(--bg-4); }
.btn-ghost {
color: var(--text-muted); padding: 0.4rem 0.8rem;
border-radius: var(--radius); font-size: 0.85rem;
}
.btn-ghost:hover { background: var(--bg-3); color: var(--text); }
/* ─── Login screen ─────────────────────────────────────────────── */
.login-screen { display: grid; place-items: center; min-height: 60vh; }
.login-card { width: 100%; max-width: 380px; background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 2rem; box-shadow: var(--shadow-lg); }
.login-card h2 { margin: 0 0 0.5rem; font-size: 1.3rem; }
.login-hint { color: var(--text-muted); font-size: 0.9rem; margin: 0 0 1.5rem; }
.login-form { display: flex; flex-direction: column; gap: 1rem; }
.login-form label { display: flex; flex-direction: column; gap: 0.35rem; font-size: 0.85rem; color: var(--text-muted); }
.login-form input { padding: 0.6rem 0.8rem; background: var(--bg); color: var(--text); border: 1px solid var(--line); border-radius: var(--radius); font-size: 0.95rem; }
.login-form input:focus { outline: none; border-color: var(--accent); }
.login-error { background: rgba(239, 68, 68, 0.1); border: 1px solid var(--danger); color: var(--danger); padding: 0.6rem 0.8rem; border-radius: var(--radius); font-size: 0.85rem; }
/* ─── Klausur cards ────────────────────────────────────────────── */
.klausur-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }
.klausur-card {
background: var(--bg-2); border: 1px solid var(--line);
border-radius: var(--radius); padding: 1rem;
display: flex; flex-direction: column; gap: 0.6rem;
transition: border-color 0.15s, transform 0.1s;
cursor: pointer;
}
.klausur-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.klausur-card-header { display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem; }
.klausur-card-num { background: var(--accent); color: white; font-weight: 700; font-size: 0.75rem; padding: 0.15rem 0.5rem; border-radius: 10px; flex-shrink: 0; }
.klausur-card-name { font-weight: 600; font-size: 0.95rem; flex: 1; word-break: break-word; }
.klausur-card-topics { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.klausur-card-topic { background: rgba(var(--accent-rgb), 0.12); color: var(--accent-2); padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.7rem; cursor: pointer; transition: background 0.1s; }
.klausur-card-topic:hover { background: rgba(var(--accent-rgb), 0.25); }
.klausur-card-notes { color: var(--text-muted); font-size: 0.8rem; font-style: italic; }
.klausur-card-cta { margin-top: 0.25rem; font-size: 0.85rem; color: var(--accent-2); display: flex; align-items: center; gap: 0.3rem; }
.klausur-card-actions { display: flex; gap: 0.4rem; margin-top: 0.4rem; flex-wrap: wrap; }
.klausur-card-mini {
font-size: 0.75rem; padding: 0.35rem 0.6rem; border-radius: 4px;
background: var(--bg-4); color: var(--text-muted);
border: 1px solid var(--line);
transition: all 0.1s;
}
.klausur-card-mini:hover { background: var(--bg-3); color: var(--accent-2); border-color: var(--accent); }
/* ─── Lernen: Heatmap ──────────────────────────────────────────── */
.heatmap-block { background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.25rem; margin-bottom: 1.5rem; }
.heatmap-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; gap: 0.5rem; }
.heatmap-title { margin: 0; font-size: 1rem; }
.heatmap-legend { display: flex; align-items: center; gap: 0.4rem; font-size: 0.75rem; color: var(--text-muted); flex-wrap: wrap; }
.legend-cell { display: inline-block; width: 18px; height: 12px; border-radius: 2px; }
.legend-cell[data-level="0"] { background: var(--bg-4); }
.legend-cell[data-level="2"] { background: rgba(245, 158, 11, 0.55); }
.legend-cell[data-level="4"] { background: rgba(16, 185, 129, 0.6); }
.legend-cell[data-level="5"] { background: rgba(16, 185, 129, 1); }
.heatmap-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 0.5rem; }
.heatmap-cell {
background: var(--bg-3); border: 1px solid var(--line);
border-radius: var(--radius); padding: 0.75rem;
display: flex; flex-direction: column; gap: 0.3rem;
position: relative; overflow: hidden;
transition: transform 0.1s, border-color 0.1s;
cursor: pointer;
}
.heatmap-cell:hover { transform: translateY(-1px); border-color: var(--accent); }
.heatmap-cell .hc-bar {
position: absolute; bottom: 0; left: 0; right: 0; height: 3px;
background: var(--bg-4);
}
.heatmap-cell .hc-bar-fill {
height: 100%; background: linear-gradient(to right, var(--warn), var(--success));
transition: width 0.3s;
}
.heatmap-cell .hc-name { font-size: 0.85rem; color: var(--text); font-weight: 500; line-height: 1.3; }
.heatmap-cell .hc-meta { font-size: 0.7rem; color: var(--text-muted); display: flex; justify-content: space-between; }
.heatmap-cell[data-level="0"] .hc-bar-fill { background: var(--bg-4); width: 0%; }
.heatmap-cell[data-level="1"] .hc-bar-fill { width: 20%; }
.heatmap-cell[data-level="2"] .hc-bar-fill { width: 40%; }
.heatmap-cell[data-level="3"] .hc-bar-fill { width: 60%; }
.heatmap-cell[data-level="4"] .hc-bar-fill { width: 80%; }
.heatmap-cell[data-level="5"] .hc-bar-fill { width: 100%; background: var(--success); }
/* ─── Lernen: Topic picker ─────────────────────────────────────── */
.topic-picker { background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.25rem; margin-bottom: 1.5rem; }
.topic-picker-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; gap: 0.5rem; flex-wrap: wrap; }
.topic-picker-head h3 { margin: 0; font-size: 1rem; }
.select-mini {
background: var(--bg-3); color: var(--text);
border: 1px solid var(--line); border-radius: var(--radius);
padding: 0.4rem 0.6rem; font-size: 0.85rem;
}
.select-mini:focus { outline: none; border-color: var(--accent); }
.topic-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0.5rem; }
.topic-pill {
background: var(--bg-3); border: 1px solid var(--line);
border-radius: var(--radius); padding: 0.6rem 0.85rem;
font-size: 0.85rem; color: var(--text);
cursor: pointer; transition: all 0.1s;
display: flex; flex-direction: column; gap: 0.25rem;
text-align: left;
}
.topic-pill:hover { border-color: var(--accent); background: var(--bg-4); }
.topic-pill .tp-label { font-weight: 500; }
.topic-pill .tp-meta { font-size: 0.7rem; color: var(--text-muted); }
.topic-pill[data-mastered="true"] { border-color: var(--success); }
.topic-pill .tp-stars { color: var(--warn); font-size: 0.75rem; letter-spacing: 0.05em; }
/* ─── Minigame Launcher ────────────────────────────────────────── */
.minigame-launcher {
background: linear-gradient(135deg, rgba(var(--accent-rgb),0.06), var(--bg-2));
border: 1px solid var(--accent);
border-radius: var(--radius-lg);
padding: 1.25rem;
margin-bottom: 1.5rem;
animation: fadein 0.2s ease-out;
}
.launcher-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; font-size: 0.95rem; }
.launcher-head strong { color: var(--accent-2); }
.launcher-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0.75rem; }
.minigame-card {
background: var(--bg-3); border: 1px solid var(--line);
border-radius: var(--radius); padding: 1rem;
display: flex; flex-direction: column; gap: 0.4rem;
text-align: left; transition: all 0.1s;
}
.minigame-card:hover { border-color: var(--accent); transform: translateY(-1px); background: var(--bg-4); }
.mg-icon { font-size: 1.3rem; }
.mg-title { font-weight: 600; font-size: 0.95rem; color: var(--text); }
.mg-desc { font-size: 0.8rem; color: var(--text-muted); line-height: 1.35; }
.mg-tag { font-size: 0.7rem; color: var(--accent-2); margin-top: 0.25rem; }
/* ─── Minigame Stage ──────────────────────────────────────────── */
.minigame-stage {
background: var(--bg-2); border: 1px solid var(--line);
border-radius: var(--radius-lg); padding: 1.5rem;
animation: fadein 0.2s ease-out;
}
.stage-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; gap: 0.5rem; flex-wrap: wrap; }
.stage-title { margin: 0; font-size: 1.05rem; font-weight: 600; }
.stage-progress { color: var(--text-muted); font-size: 0.85rem; }
.stage-progress-bar { height: 4px; background: var(--bg-4); border-radius: 2px; overflow: hidden; margin-bottom: 1rem; }
.stage-progress-bar-fill { height: 100%; background: var(--accent); transition: width 0.3s; }
.stage-loader { padding: 3rem 1rem; text-align: center; color: var(--text-muted); }
.stage-spinner {
display: inline-block; width: 28px; height: 28px;
border: 3px solid var(--bg-4); border-top-color: var(--accent);
border-radius: 50%; animation: spin 0.7s linear infinite;
margin-bottom: 0.8rem;
}
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes fadein { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
/* MCQ-style question block — used by Diagnose + Klausur quiz */
.qa-card { background: var(--bg-3); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.25rem; }
.qa-q { font-size: 1.05rem; font-weight: 500; margin: 0 0 1rem; line-height: 1.5; }
.qa-options { display: flex; flex-direction: column; gap: 0.5rem; }
.qa-option {
text-align: left; padding: 0.75rem 1rem;
background: var(--bg); border: 1px solid var(--line);
border-radius: var(--radius); transition: all 0.1s;
display: flex; gap: 0.75rem; align-items: flex-start;
}
.qa-option:not(:disabled):hover { border-color: var(--accent); background: var(--bg-4); }
.qa-option:disabled { cursor: default; }
.qa-option-letter { font-weight: 600; color: var(--text-muted); flex-shrink: 0; }
.qa-option[data-state="correct"] { border-color: var(--success); background: rgba(16, 185, 129, 0.1); }
.qa-option[data-state="wrong"] { border-color: var(--danger); background: rgba(239, 68, 68, 0.1); }
.qa-option[data-state="reveal-correct"] { border-color: var(--success); }
.qa-feedback {
margin-top: 1rem; padding: 0.85rem 1rem; border-radius: var(--radius);
background: var(--bg); border-left: 3px solid var(--accent);
font-size: 0.9rem; line-height: 1.5;
}
.qa-feedback.correct { border-left-color: var(--success); }
.qa-feedback.wrong { border-left-color: var(--danger); }
.qa-feedback strong { color: var(--accent-2); }
.qa-actions { display: flex; justify-content: flex-end; gap: 0.5rem; margin-top: 1rem; }
/* Klinikfall stage */
.fall-patient { background: rgba(56, 189, 248, 0.1); border-left: 3px solid var(--info); padding: 1rem; border-radius: var(--radius); margin-bottom: 1rem; line-height: 1.6; }
.fall-stage-num { color: var(--info); font-weight: 600; font-size: 0.85rem; margin-bottom: 0.25rem; }
.fall-learning {
margin-top: 0.75rem; padding: 0.6rem 0.85rem;
background: rgba(168, 85, 247, 0.08);
border-left: 3px solid var(--accent);
border-radius: var(--radius);
font-size: 0.85rem; color: var(--text-muted);
}
.fall-learning strong { color: var(--accent-2); }
/* Stimmt-das? */
.stimmt-grid { display: flex; flex-direction: column; gap: 0.75rem; }
.stimmt-card {
background: var(--bg-3); border: 1px solid var(--line);
border-radius: var(--radius); padding: 1rem;
}
.stimmt-statement { font-size: 0.95rem; margin-bottom: 0.75rem; line-height: 1.5; }
.stimmt-buttons { display: flex; gap: 0.5rem; }
.stimmt-btn {
flex: 1; padding: 0.5rem 1rem;
background: var(--bg); border: 1px solid var(--line);
border-radius: var(--radius); font-weight: 500;
transition: all 0.1s;
}
.stimmt-btn:not(:disabled):hover { border-color: var(--accent); background: var(--bg-4); }
.stimmt-btn[data-state="correct"] { border-color: var(--success); background: rgba(16, 185, 129, 0.15); color: var(--success); }
.stimmt-btn[data-state="wrong"] { border-color: var(--danger); background: rgba(239, 68, 68, 0.15); color: var(--danger); }
.stimmt-explain { margin-top: 0.5rem; font-size: 0.85rem; color: var(--text-muted); padding-left: 0.6rem; border-left: 2px solid var(--line); }
.stimmt-explain.correct { border-left-color: var(--success); }
.stimmt-explain.wrong { border-left-color: var(--danger); }
.trap-tag { display: inline-block; padding: 0.1rem 0.4rem; background: rgba(245, 158, 11, 0.15); color: var(--warn); border-radius: 3px; font-size: 0.7rem; margin-right: 0.4rem; }
/* Summary card for end of minigame */
.summary-card {
background: linear-gradient(135deg, rgba(var(--accent-rgb),0.1), var(--bg-3));
border: 1px solid var(--accent);
border-radius: var(--radius-lg);
padding: 1.5rem;
text-align: center;
}
.summary-stars {
font-size: 2rem; letter-spacing: 0.1em; margin-bottom: 0.5rem;
color: var(--warn);
}
.summary-text { font-size: 1rem; margin-bottom: 0.25rem; }
.summary-meta { color: var(--text-muted); font-size: 0.85rem; margin-bottom: 1rem; }
.summary-actions { display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; }
/* ─── Persona-Form ─────────────────────────────────────────────── */
.persona-form { background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.25rem; }
.persona-label { display: flex; flex-direction: column; gap: 0.5rem; font-size: 0.85rem; color: var(--text-muted); margin-bottom: 1rem; }
.persona-form textarea {
background: var(--bg); color: var(--text);
border: 1px solid var(--line); border-radius: var(--radius);
padding: 0.75rem 1rem; font-family: inherit; font-size: 0.95rem;
line-height: 1.5; resize: vertical; min-height: 200px;
}
.persona-form textarea:focus { outline: none; border-color: var(--accent); }
.persona-actions { display: flex; justify-content: space-between; align-items: center; gap: 1rem; }
.persona-status { color: var(--text-muted); font-size: 0.85rem; }
.persona-status.saved { color: var(--success); }
.hint-list { color: var(--text-muted); font-size: 0.85rem; line-height: 1.6; margin: 0.5rem 0 1rem; padding-left: 1.5rem; }
.hint-list li { margin-bottom: 0.25rem; }
/* ─── Telegram-Pairing Block ──────────────────────────────────── */
.telegram-block {
background: var(--bg-2); border: 1px solid var(--line);
border-radius: var(--radius-lg); padding: 1.25rem; margin-top: 1.5rem;
}
.tg-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; gap: 0.5rem; }
.tg-head h3 { margin: 0; font-size: 1rem; }
.tg-hint { color: var(--text-muted); font-size: 0.9rem; margin: 0 0 1rem; line-height: 1.5; }
.tg-badge {
font-size: 0.75rem; padding: 0.2rem 0.6rem; border-radius: 10px;
background: var(--bg-3); color: var(--text-muted);
border: 1px solid var(--line);
}
.tg-badge.linked { background: rgba(16,185,129,0.15); color: var(--success); border-color: var(--success); }
.tg-badge.unlinked { background: var(--bg-3); color: var(--text-muted); }
.tg-link-area { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.tg-code-display { margin-top: 1rem; }
.tg-code-line {
background: var(--bg); border: 1px solid var(--accent);
border-radius: var(--radius); padding: 0.85rem 1rem;
font-size: 1.1rem; letter-spacing: 0.05em;
margin: 0.5rem 0;
user-select: all;
display: flex; justify-content: center;
}
.tg-code-line code { color: var(--accent-2); font-weight: 600; font-size: 1.15rem; letter-spacing: 0.1em; }
.tg-code-hint { font-size: 0.8rem; color: var(--text-muted); margin: 0.25rem 0 0; }
/* ─── Chat Dock (resizable + MD) ──────────────────────────────── */
.chat-dock {
position: fixed;
bottom: 1rem; right: 1rem;
width: 460px; height: 640px;
min-width: 340px; min-height: 420px;
max-width: calc(100vw - 2rem);
max-height: calc(100vh - 2rem);
background: var(--bg-2); border: 1px solid var(--line);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
display: none; flex-direction: column;
z-index: 100;
}
.chat-dock[data-open="true"] { display: flex; }
.dock-resize {
position: absolute;
top: 0; left: 0;
width: 14px; height: 14px;
cursor: nwse-resize;
background: linear-gradient(135deg, transparent 35%, var(--text-dim) 35%, var(--text-dim) 50%, transparent 50%);
border-top-left-radius: var(--radius-lg);
z-index: 1;
}
.dock-resize:hover { background-color: rgba(var(--accent-rgb), 0.1); }
.dock-head { padding: 0.75rem 1rem; border-bottom: 1px solid var(--line); display: flex; align-items: center; gap: 0.5rem; }
.dock-title { font-weight: 600; }
.dock-sub { color: var(--text-muted); font-size: 0.85rem; flex: 1; }
.dock-reset, .dock-collapse { width: 28px; height: 28px; border-radius: var(--radius); color: var(--text-muted); }
.dock-reset:hover, .dock-collapse:hover { background: var(--bg-3); color: var(--text); }
.dock-box { flex: 1; padding: 0.75rem 1rem; overflow-y: auto; display: flex; flex-direction: column; gap: 0.75rem; }
.dock-msg {
max-width: 90%;
padding: 0.7rem 0.9rem;
border-radius: var(--radius);
font-size: 0.92rem;
line-height: 1.55;
word-break: break-word;
}
.dock-msg.user { align-self: flex-end; background: var(--accent-dark); color: white; white-space: pre-wrap; }
.dock-msg.assistant { align-self: flex-start; background: var(--bg-3); color: var(--text); }
.dock-msg.assistant.typing { font-style: italic; opacity: 0.7; }
/* MD content inside chat */
.dock-md p { margin: 0 0 0.6rem; }
.dock-md p:last-child { margin-bottom: 0; }
.dock-md h1, .dock-md h2, .dock-md h3 { margin: 0.6rem 0 0.4rem; line-height: 1.3; }
.dock-md h1 { font-size: 1.05rem; color: var(--accent-2); }
.dock-md h2 { font-size: 1rem; color: var(--accent-2); }
.dock-md h3 { font-size: 0.95rem; color: var(--text); }
.dock-md ul, .dock-md ol { margin: 0.4rem 0 0.6rem; padding-left: 1.4rem; }
.dock-md li { margin-bottom: 0.2rem; }
.dock-md code { background: var(--bg); padding: 0.1rem 0.35rem; border-radius: 3px; font-size: 0.85em; color: var(--accent-2); }
.dock-md pre { background: var(--bg); padding: 0.7rem 0.9rem; border-radius: var(--radius); overflow-x: auto; margin: 0.5rem 0; }
.dock-md pre code { background: none; padding: 0; color: var(--text); }
.dock-md strong { color: var(--text); }
.dock-md em { color: var(--text-muted); font-style: italic; }
.dock-md a { color: var(--accent-2); text-decoration: underline; }
.dock-md table.md-table { border-collapse: collapse; margin: 0.5rem 0; font-size: 0.85em; width: auto; }
.dock-md table.md-table th, .dock-md table.md-table td { border: 1px solid var(--line); padding: 0.3rem 0.6rem; }
.dock-md table.md-table th { background: var(--bg); }
.dock-md blockquote { border-left: 3px solid var(--accent); padding-left: 0.8rem; margin: 0.5rem 0; color: var(--text-muted); }
.dock-msg .sources {
margin-top: 0.6rem; padding-top: 0.5rem; border-top: 1px solid var(--line);
font-size: 0.72rem; color: var(--text-muted);
}
.dock-msg .sources .src-tag {
display: inline-block; background: rgba(var(--accent-rgb), 0.15); color: var(--accent-2);
padding: 0.1rem 0.4rem; border-radius: 3px; margin: 0.15rem 0.25rem 0 0; font-size: 0.7rem;
}
/* Inline structured renderer (quiz/flashcards/case in chat) */
.dock-struct {
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--radius);
padding: 0.85rem;
margin-top: 0.5rem;
}
.dock-struct .ds-head { font-weight: 600; color: var(--accent-2); margin-bottom: 0.4rem; font-size: 0.85rem; }
.dock-struct .ds-q { font-size: 0.9rem; margin-bottom: 0.5rem; }
.dock-struct .ds-options { display: flex; flex-direction: column; gap: 0.3rem; }
.dock-struct .ds-option {
text-align: left; padding: 0.4rem 0.6rem;
background: var(--bg-3); border: 1px solid var(--line);
border-radius: 4px; font-size: 0.85rem;
}
.dock-struct .ds-option:hover:not(:disabled) { border-color: var(--accent); }
.dock-struct .ds-option[data-state="correct"] { border-color: var(--success); background: rgba(16, 185, 129, 0.1); }
.dock-struct .ds-option[data-state="wrong"] { border-color: var(--danger); background: rgba(239, 68, 68, 0.1); }
.dock-form { border-top: 1px solid var(--line); padding: 0.5rem; display: flex; gap: 0.5rem; }
.dock-form textarea {
flex: 1; resize: none;
background: var(--bg); color: var(--text);
border: 1px solid var(--line); border-radius: var(--radius);
padding: 0.5rem 0.7rem; font-size: 0.9rem;
max-height: 140px; min-height: 36px;
font-family: inherit;
}
.dock-form textarea:focus { outline: none; border-color: var(--accent); }
.btn-send { background: var(--accent); color: white; width: 40px; border-radius: var(--radius); font-weight: 700; font-size: 1.1rem; }
.btn-send:hover { background: var(--accent-dark); }
.dock-open {
position: fixed; bottom: 1rem; right: 1rem;
background: var(--accent); color: white;
padding: 0.7rem 1.2rem; border-radius: 24px;
font-weight: 500; box-shadow: var(--shadow-lg);
display: flex; align-items: center; gap: 0.5rem;
z-index: 99;
}
.dock-open:hover { background: var(--accent-dark); }
.dock-open[hidden] { display: none; }
.dock-open-dot { width: 8px; height: 8px; background: var(--success); border-radius: 50%; display: inline-block; }
/* ─── Footer ───────────────────────────────────────────────────── */
.footer { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--line); text-align: center; font-size: 0.8rem; color: var(--text-muted); }
/* ─── Toasts ───────────────────────────────────────────────────── */
.toast-stack { position: fixed; top: 1rem; right: 1rem; display: flex; flex-direction: column; gap: 0.5rem; z-index: 200; pointer-events: none; }
.toast { padding: 0.6rem 1rem; background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow-md); color: var(--text); font-size: 0.85rem; pointer-events: auto; animation: toast-in 0.2s ease-out; }
.toast.error { border-color: var(--danger); color: var(--danger); }
.toast.success { border-color: var(--success); color: var(--success); }
@keyframes toast-in { from { transform: translateX(20px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
/* Mobile */
@media (max-width: 800px) {
.space-tabs, .folder-tabs { grid-template-columns: repeat(2, 1fr); }
.space-tab { font-size: 0.85rem; padding: 0.7rem 0.4rem; }
.chat-dock { width: calc(100vw - 2rem); height: calc(100vh - 6rem); right: 1rem; bottom: 1rem; }
.dock-resize { display: none; }
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,290 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Luna · Tutor-Cockpit</title>
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<meta name="theme-color" content="#0a0a0f">
<meta name="description" content="Luna — dein Tutor-Cockpit. Eigene Unterlagen, Klausur-Plan, Kompetenz-Diagnose, Klinikfälle.">
<link rel="stylesheet" href="cockpit.css">
<script>
window.__LUNA_KEY__ = '{{BOT_KEY_VALUE}}';
window.__BOT_SLUG__ = '{{BOT_SLUG}}';
window.__BOT_ID__ = '{{BOT_ID}}';
window.__API_BASE__ = 'https://api.qognio.com';
</script>
</head>
<body>
<div class="cockpit" role="application" aria-label="Luna Cockpit">
<header class="topbar">
<div class="brand">
<span class="brand-icon" aria-hidden="true">L</span>
<div class="brand-text">
<span class="brand-title">Luna <small>Cockpit</small></span>
<span class="brand-sub">Eigene Unterlagen · Klausur-Plan · Kompetenz-Diagnose</span>
</div>
</div>
<div class="spacer"></div>
<div id="auth-state" class="auth-state">
<span id="auth-user" class="auth-user"></span>
<button id="auth-logout" class="btn-secondary" type="button" hidden>Abmelden</button>
</div>
</header>
<!-- Login overlay -->
<section id="login-screen" class="login-screen" hidden>
<div class="login-card">
<h2>Bei Luna anmelden</h2>
<p class="login-hint">Damit Luna deine eigenen Dokumente verwenden kann, brauche ich deinen Login.</p>
<form id="login-form" class="login-form">
<label>E-Mail<input type="email" id="login-email" autocomplete="email" required></label>
<label>Passwort<input type="password" id="login-pw" autocomplete="current-password" required></label>
<button type="submit" class="btn-primary">Anmelden</button>
<div id="login-error" class="login-error" role="alert" hidden></div>
</form>
</div>
</section>
<main id="main" class="main" hidden>
<!-- Status banner -->
<section class="status-block">
<h2 class="status-title">Was Luna gerade weiß</h2>
<div id="status-summary" class="status-summary">Lade…</div>
</section>
<!-- Top-level Tabs -->
<nav class="space-tabs" role="tablist" aria-label="Cockpit-Bereiche">
<button class="space-tab" role="tab" aria-selected="true" data-space="dokumente">
<span class="space-icon">📁</span><span class="space-label">Dokumente</span>
</button>
<button class="space-tab" role="tab" aria-selected="false" data-space="klausuren">
<span class="space-icon">📋</span><span class="space-label">Klausur-Plan</span>
</button>
<button class="space-tab" role="tab" aria-selected="false" data-space="lernen">
<span class="space-icon">🧠</span><span class="space-label">Lernen</span>
</button>
<button class="space-tab" role="tab" aria-selected="false" data-space="steuern">
<span class="space-icon">🎛</span><span class="space-label">Bot steuern</span>
</button>
</nav>
<!-- ─── Space: Dokumente ─── -->
<section class="space" data-space="dokumente" data-active="true">
<nav class="folder-tabs" role="tablist" aria-label="Dokumenten-Ordner">
<button class="folder-tab" role="tab" aria-selected="true" data-folder="curriculum">
<span class="folder-icon">📚</span>
<span class="folder-label">Curriculum</span>
<span class="folder-count" data-count="curriculum">0</span>
</button>
<button class="folder-tab" role="tab" aria-selected="false" data-folder="official">
<span class="folder-icon">🏛️</span>
<span class="folder-label">Offizielle Doks</span>
<span class="folder-count" data-count="official">0</span>
</button>
<button class="folder-tab" role="tab" aria-selected="false" data-folder="own">
<span class="folder-icon">📝</span>
<span class="folder-label">Eigene Notizen</span>
<span class="folder-count" data-count="own">0</span>
</button>
<button class="folder-tab" role="tab" aria-selected="false" data-folder="role">
<span class="folder-icon">🎯</span>
<span class="folder-label">Schwerpunkt</span>
<span class="folder-count" data-count="role">0</span>
</button>
</nav>
<section class="folder-body">
<div id="folder-help" class="folder-help"></div>
<div class="upload-zone" id="upload-zone" tabindex="0">
<input type="file" id="file-input" hidden multiple
accept=".pdf,.txt,.md,.csv,.json,.png,.jpg,.jpeg,.webp">
<div class="upload-cta">
<span class="upload-icon">⬆️</span>
<span class="upload-text">Datei hier ablegen oder klicken</span>
<span class="upload-hint">PDF, Markdown, Text — max 20 MB</span>
</div>
<div id="upload-progress" class="upload-progress" hidden></div>
</div>
<ul id="doc-list" class="doc-list" aria-live="polite"></ul>
<div id="doc-empty" class="doc-empty" hidden>Noch nichts hier — leg los mit dem ersten Upload.</div>
</section>
</section>
<!-- ─── Space: Klausuren ─── -->
<section class="space" data-space="klausuren">
<div class="space-intro">
<h2>📋 Dein Klausur-Plan</h2>
<p>Aus deinem Curriculum extrahiert. Klick auf eine Klausur, um Luna gezielt darauf vorzubereiten.</p>
</div>
<div id="klausur-list" class="klausur-list">
<div class="empty-state" id="klausur-empty">Noch keine Klausuren erkannt. Lade ein Klausurplan-Dokument im Curriculum-Ordner hoch.</div>
</div>
</section>
<!-- ─── Space: Lernen ─── -->
<section class="space" data-space="lernen">
<div class="space-intro">
<h2>🧠 Lernen &amp; Üben</h2>
<p>Wähle ein Thema aus deinem Klausur-Plan oder Curriculum, dann starte ein Mini-Spiel.</p>
</div>
<!-- Heatmap: mastery per topic -->
<section class="heatmap-block">
<div class="heatmap-header">
<h3 class="heatmap-title">Dein aktueller Stand</h3>
<div class="heatmap-legend">
<span class="legend-cell" data-level="0"></span><span>0 noch nicht</span>
<span class="legend-cell" data-level="2"></span><span>2 in Arbeit</span>
<span class="legend-cell" data-level="4"></span><span>4 stark</span>
<span class="legend-cell" data-level="5"></span><span>5 gemeistert</span>
</div>
</div>
<div id="heatmap-grid" class="heatmap-grid">
<div class="empty-state">Klick auf ein Thema unten, um deinen Stand erstmals zu erfassen.</div>
</div>
</section>
<!-- Topic picker -->
<section class="topic-picker">
<div class="topic-picker-head">
<h3>Themen</h3>
<select id="topic-source" class="select-mini">
<option value="klausur">aus Klausur-Plan</option>
<option value="physiologie-uke">Physiologie (UKE)</option>
<option value="physiotherapie-aprv">Physiotherapie APrV</option>
<option value="pflegeschule-flensburg">Pflege Flensburg</option>
<option value="medizinische-terminologie">Med. Terminologie</option>
<option value="anatomie-grundlagen">Anatomie Grundlagen</option>
</select>
</div>
<div id="topic-grid" class="topic-grid"></div>
</section>
<!-- Minigame Launcher (visible nachdem Topic gewählt) -->
<section id="minigame-launcher" class="minigame-launcher" hidden>
<div class="launcher-head">
<span>Was willst du mit <strong id="launcher-topic"></strong> machen?</span>
<button class="btn-icon-x" id="launcher-close" aria-label="Schließen"></button>
</div>
<div class="launcher-grid">
<button class="minigame-card" data-game="diagnose">
<span class="mg-icon">🩺</span>
<span class="mg-title">Kompetenz-Check</span>
<span class="mg-desc">5 adaptive Fragen — Luna ermittelt deinen Stand.</span>
<span class="mg-tag">Diagnose · 3-5 min</span>
</button>
<button class="minigame-card" data-game="klinikfall">
<span class="mg-icon">🏥</span>
<span class="mg-title">Klinikfall</span>
<span class="mg-desc">Patient*in mit Symptomen — du triffst 3 Entscheidungen.</span>
<span class="mg-tag">Anwendung · 5-7 min</span>
</button>
<button class="minigame-card" data-game="stimmt-das">
<span class="mg-icon">🤔</span>
<span class="mg-title">Stimmt das?</span>
<span class="mg-desc">6 Aussagen — wahr oder Falle? Trainiert Misconceptions.</span>
<span class="mg-tag">Retrieval · 3 min</span>
</button>
<button class="minigame-card" data-game="quiz-classic">
<span class="mg-icon">📝</span>
<span class="mg-title">Klassisches Quiz</span>
<span class="mg-desc">10 Multiple-Choice-Fragen via Luna's Quiz-Engine.</span>
<span class="mg-tag">Übung · 5 min</span>
</button>
<button class="minigame-card" data-game="flashcards">
<span class="mg-icon">🃏</span>
<span class="mg-title">Karteikarten</span>
<span class="mg-desc">Spaced-Repetition mit Eigenbewertung pro Karte.</span>
<span class="mg-tag">Memorisierung · 3-5 min</span>
</button>
<button class="minigame-card" data-game="explain">
<span class="mg-icon">🎓</span>
<span class="mg-title">Erklär's mir</span>
<span class="mg-desc">Du erklärst — Luna gibt Feedback zu Klarheit + Vollständigkeit.</span>
<span class="mg-tag">Articulation · 5 min</span>
</button>
</div>
</section>
<!-- Minigame Stage (gefüllt während ein Spiel läuft) -->
<section id="minigame-stage" class="minigame-stage" hidden></section>
</section>
<!-- ─── Space: Steuern ─── -->
<section class="space" data-space="steuern">
<div class="space-intro">
<h2>🎛 Bot steuern</h2>
<p>Schreibe hier rein, wie Luna mit dir arbeiten soll. Beispiele:</p>
<ul class="hint-list">
<li>„Antworte ausführlicher und gib mehr klinische Beispiele"</li>
<li>„Stell mir öfter sokratische Rückfragen statt direkt zu erklären"</li>
<li>„Ich lerne lieber visuell — beschreib Diagramme detailliert"</li>
<li>„Fokus auf Klausur 1 — ignoriere andere Themen wenn ich nicht explizit frage"</li>
</ul>
</div>
<form id="persona-form" class="persona-form">
<label class="persona-label">
Deine Anweisungen an Luna
<textarea id="persona-overrides" rows="10"
placeholder="Schreibe hier deine Wünsche…"></textarea>
</label>
<div class="persona-actions">
<span id="persona-status" class="persona-status"></span>
<button type="submit" class="btn-primary">Speichern</button>
</div>
</form>
<!-- Telegram-Pairing -->
<section class="telegram-block">
<div class="tg-head">
<h3>📱 Mobil mit Telegram</h3>
<span id="tg-status-badge" class="tg-badge">prüfe…</span>
</div>
<p class="tg-hint">
Verbinde diesen Cockpit-Account mit dem
<a href="https://t.me/qognioLunaBot" target="_blank" rel="noopener">@qognioLunaBot</a>
auf Telegram. Dann kannst du Luna unterwegs fragen — und sie nutzt deinen Lernstand &amp; Dokumente.
</p>
<div id="tg-link-area" class="tg-link-area">
<button id="tg-generate" class="btn-primary" type="button">Code generieren</button>
<button id="tg-unlink" class="btn-secondary" type="button" hidden>Verbindung lösen</button>
</div>
<div id="tg-code-display" class="tg-code-display" hidden>
<p>Schicke diese Nachricht an
<a href="https://t.me/qognioLunaBot" target="_blank" rel="noopener">@qognioLunaBot</a>
auf Telegram:</p>
<pre class="tg-code-line"><code id="tg-code-text"></code></pre>
<p class="tg-code-hint">Code läuft in <span id="tg-code-ttl">15</span> Min ab.</p>
</div>
</section>
</section>
</main>
<!-- Chat-Dock (resizable + MD-rendering + structured output) -->
<aside id="chat-dock" class="chat-dock" aria-label="Luna Chat">
<div class="dock-resize" id="dock-resize" aria-hidden="true"></div>
<header class="dock-head">
<span class="dock-title">Luna</span>
<span class="dock-sub">deine Tutorin</span>
<button class="dock-reset" type="button" id="dock-reset" title="Chat zurücksetzen"></button>
<button class="dock-collapse" type="button" id="dock-collapse" title="Einklappen"></button>
</header>
<div id="dock-box" class="dock-box" aria-live="polite"></div>
<form id="dock-form" class="dock-form">
<textarea id="dock-input" rows="1" placeholder="Frag Luna — z.B. „erklär mir die Nephron-Funktion""></textarea>
<button type="submit" class="btn-send" aria-label="Senden">&rarr;</button>
</form>
</aside>
<button id="dock-open" class="dock-open" type="button" aria-label="Luna-Chat öffnen">
<span class="dock-open-dot"></span>Luna fragen
</button>
<footer class="footer">
Sovereign AI · Deutscher Bunker · <a href="https://qognio.com">Qognio</a> · Deine Daten bleiben bei dir
</footer>
</div>
<div id="toast-stack" class="toast-stack" aria-live="polite"></div>
<script src="cockpit.js"></script>
</body>
</html>

43
src/config.yaml Normal file
View file

@ -0,0 +1,43 @@
slug: physio-tutor
bot_name: Luna
bot_title: PhysioTutor
brand_letter: L
title: "Luna · PhysioTutor"
tagline: "PhysioTutor"
tagline_short: "PhysioTutor"
meta_description: "Luna — dein KI-PhysioTutor. Gamified lernen mit Chat, Quiz, Flashcards und Fortschritts-Tracking. Läuft im deutschen Rechenzentrum."
bot_key_var: __LUNA_KEY__
bot_key_value: qb_drfbhe3w6j2r7199w2
bot_id: r3816r760kur14i
ls_prefix: luna
bot_version: "2026-04-21"
# Color theme (Luna — purple)
accent: "#a855f7"
accent_2: "#a855f7"
accent_dark: "#7c3aed"
accent_rgb: "168, 85, 247"
success_color: "#10b981"
msg_strong_color: "#ddd6fe"
# UI Labels
tab_flash_label: Karten
tab_curriculum_label: Lehrplan
curriculum_long_label: Lehrplan
# Bot personality
quiz_intro_hint: "Wähle ein Modul — Luna generiert 10 Multiple-Choice-Fragen."
quiz_verb: erstellt
quiz_noun: "Fragen"
flash_intro_hint: "Luna erstellt Karteikarten zu einem Thema. Bewerte dein Erinnerungsvermögen — das System wiederholt schwere Karten öfter (SM-2)."
flash_verb: erstellt
# DRIFT NOTE: Luna ist V1 (pre-module-tracking) und KANN NICHT diff-clean
# rendered werden ohne Code-Migration. Der Live-Bot:
# - hat KEIN moduleCorrect/moduleTotal/modulePassedFlash state
# - hat NUR 6 levels (kein 5000-XP Senior)
# - simpler checkBadges (5 generische badges)
# - kein Module-Completion-Bonus-XP
# Render erzeugt V2-Code; das ist eine "feature uplift", nicht ein Bugfix.
# Alle XP/Streak-Daten der bestehenden Luna-User bleiben kompatibel
# (extra fields werden initialisiert mit Defaults beim Load).

565
src/curricula.json Normal file
View file

@ -0,0 +1,565 @@
{
"version": "2026-04-21",
"updated": "2026-04-21",
"curricula": [
{
"id": "physiologie-uke",
"title": "Physiologie (UKE Hamburg)",
"short": "UKE iMED + Biomedizin",
"icon": "pulse",
"color": "#ef4444",
"description": "Integrierte Physiologie im UKE-Modellstudiengang iMED. 7 Systemmodule (AG) mit Herz-Kreislauf, Atmung, Niere, Neuro, Endokrin.",
"modules": [
{
"id": "zellphysiologie",
"title": "Zellphysiologie & erregbare Zellen",
"objectives": [
"Ruhemembranpotenzial & Nernst-Gleichung erklären",
"Aktionspotenzial-Phasen beschreiben",
"Synaptische Übertragung (EPSP/IPSP)",
"Na+/K+-ATPase, Kanäle, Transporter"
],
"topics": ["Membranpotenzial", "Aktionspotenzial", "Synapse", "Ionenkanäle", "Signaltransduktion"]
},
{
"id": "muskelphysiologie",
"title": "Muskelphysiologie",
"objectives": [
"Gleitfilamenttheorie & elektromechanische Kopplung",
"Skelett-, Herz-, glatte Muskulatur differenzieren",
"Kraft-Längen- und Kraft-Geschwindigkeits-Kurve",
"Motorische Einheit & Rekrutierung"
],
"topics": ["Sarkomer", "Aktin-Myosin", "Tetanus", "Ermüdung", "Motor unit"]
},
{
"id": "herz-kreislauf",
"title": "Herz-Kreislauf-Physiologie",
"objectives": [
"Herzzyklus & Wiggers-Diagramm interpretieren",
"EKG-Ableitungen & normale Komplexe erkennen",
"Frank-Starling-Mechanismus erklären",
"Blutdruckregulation kurz-/langfristig",
"Mikrozirkulation & Starling-Gleichgewicht"
],
"topics": ["Herzzyklus", "EKG", "Kontraktilität", "RAAS", "Barorezeptoren", "Kapillaren"]
},
{
"id": "atmung",
"title": "Atmung & Gasaustausch",
"objectives": [
"Lungenvolumina & Spirometrie interpretieren",
"Compliance, Resistance, Surfactant",
"Gastransport mit O2-Bindungskurve und Bohr-Effekt",
"Säure-Basen-Haushalt & renal/respiratorische Kompensation"
],
"topics": ["FRC", "Totraum", "V/Q", "Hämoglobin", "pH", "Bikarbonat"]
},
{
"id": "niere",
"title": "Niere & Wasser-Elektrolyt-Haushalt",
"objectives": [
"GFR-Konzept mit Clearance (Inulin, Kreatinin)",
"Tubuläre Reabsorption & Sekretion pro Abschnitt",
"Gegenstromprinzip & ADH",
"RAAS, Blutvolumenregulation"
],
"topics": ["Nephron", "Clearance", "ADH", "Aldosteron", "Elektrolyte"]
},
{
"id": "endokrin",
"title": "Endokrinologie",
"objectives": [
"Hypothalamus-Hypophysen-Achse",
"Schilddrüse, Nebenniere, Pankreas-Inseln",
"Glucose-Homöostase Insulin/Glukagon",
"Ca-Regulation (PTH, Calcitriol, Calcitonin)"
],
"topics": ["Hormone", "Insulin", "Cortisol", "Schilddrüse", "Calcium"]
},
{
"id": "nervensystem",
"title": "Nervensystem & Sinne",
"objectives": [
"Aufbau ZNS/PNS, vegetatives NS",
"Somatosensorik, Schmerz, propriozeptive Wahrnehmung",
"Visuelles, auditives und vestibuläres System",
"Motorisches System: Pyramidenbahn + Kleinhirn"
],
"topics": ["Sympathikus", "Parasympathikus", "Nozizeption", "Vestibular", "Pyramidenbahn"]
},
{
"id": "blut",
"title": "Blut & Hämostase",
"objectives": [
"Erythropoese, Hämoglobin-Varianten",
"Blutgruppen ABO + Rh",
"Primäre & sekundäre Hämostase",
"Fibrinolyse"
],
"topics": ["Erythrozyten", "ABO", "Thrombozyten", "Gerinnungskaskade"]
}
]
},
{
"id": "physiotherapie-aprv",
"title": "Physiotherapie (PhysTh-APrV)",
"short": "Staatliche Ausbildung 2900/1600 h",
"icon": "activity",
"color": "#a855f7",
"description": "Bundesgesetzliche Grundlage für die 3-jährige Physiotherapie-Ausbildung. 2.900 h Theorie + 1.600 h Praxis. Staatliche Prüfung.",
"modules": [
{
"id": "grundlagen",
"title": "Berufsgrundlagen & Recht",
"objectives": [
"PhysTh-APrV-Struktur kennen",
"Hygienerichtlinien anwenden",
"Erste Hilfe sicher durchführen",
"Berufs- und Gesetzeskunde"
],
"topics": ["APrV", "Hygiene", "Erste Hilfe", "Berufsrecht"],
"hours": 100
},
{
"id": "anatomie-physio-aprv",
"title": "Anatomie (240 h) & Physiologie (140 h)",
"objectives": [
"Bewegungsapparat detailliert",
"Innere Organe Situs",
"Neuroanatomie Grundlagen",
"Physiologische Regelkreise"
],
"topics": ["Muskeln", "Gelenke", "Nerven", "Organe"],
"hours": 380
},
{
"id": "krankheitslehre",
"title": "Spezielle Krankheitslehre (360 h)",
"objectives": [
"Orthopädische Krankheitsbilder",
"Neurologische Syndrome erkennen",
"Innere Medizin Grundlagen",
"Chirurgie und Traumatologie"
],
"topics": ["Innere", "Ortho", "Neuro", "Chirurgie", "Päd", "Psychiatrie", "Gyn", "Dermato", "Geriatrie", "Rheuma", "Arbeitsmed", "Sportmed"],
"hours": 360
},
{
"id": "bewegungslehre",
"title": "Bewegungslehre & Trainingslehre",
"objectives": [
"Kinematik, Kinetik, biomechanische Prinzipien",
"Trainingsplanung aufbauen",
"Belastungssteuerung (FITT)",
"Motorisches Lernen"
],
"topics": ["Biomechanik", "Ausdauer", "Kraft", "Koordination", "Beweglichkeit"],
"hours": 160
},
{
"id": "befund",
"title": "Befund- und Untersuchungstechniken (100 h)",
"objectives": [
"Strukturierte Anamnese",
"Gelenkmessung Neutral-Null",
"Muskelfunktionstest (Janda)",
"Spezielle Tests (z. B. Lachman, O'Brien, Thomas)",
"ICF-Dokumentation"
],
"topics": ["Anamnese", "Inspektion", "Palpation", "ROM", "MFT", "Spezialtests"],
"hours": 100
},
{
"id": "kg-techniken",
"title": "Krankengymnastische Behandlungstechniken (500 h)",
"objectives": [
"Manuelle Therapie (Kaltenborn, Maitland, Mulligan)",
"PNF verstehen und anwenden",
"Bobath, Vojta für Neuro",
"Atemtherapie",
"Schroth bei Skoliose, McKenzie",
"MTT & gerätegestütztes Training"
],
"topics": ["MT", "PNF", "Bobath", "Vojta", "Schroth", "McKenzie", "Atemtherapie", "MTT"],
"hours": 500
},
{
"id": "massage-physik",
"title": "Massage & Physikalische Therapie",
"objectives": [
"Klassische Massage & Reflexzonen",
"Lymphdrainage nach Vodder",
"Elektrotherapie (TENS, Iontophorese, Galvanik)",
"Hydro-, Balneo-, Thermotherapie",
"Ultraschall, Licht/Strahlen"
],
"topics": ["Massage", "MLD", "TENS", "Ultraschall", "Fango", "Inhalation"],
"hours": 270
},
{
"id": "methodische-anwendung",
"title": "Methodische Anwendung in Fachgebieten (700 h)",
"objectives": [
"Behandlungsaufbau in Orthopädie, Chirurgie, Innerer Medizin",
"Neurorehabilitation (Schlaganfall, MS, Parkinson)",
"Pädiatrie (Bobath-Säugling, ICP)",
"Psychiatrie (konzentrative Bewegungstherapie)",
"Gynäkologie (Rückbildung)"
],
"topics": ["Ortho-Reha", "Innere-Reha", "Neuro-Reha", "Päd", "Geriatrie", "Gyn"],
"hours": 700
},
{
"id": "pruefung",
"title": "Staatliche Prüfung",
"objectives": [
"4 schriftliche Aufsichtsarbeiten bestehen",
"3 mündliche Prüfungen (Anatomie, Physiologie, spez. Krankheitslehre)",
"3 praktische Prüfungen an Patient:innen"
],
"topics": ["Schriftlich", "Mündlich", "Praktisch"]
}
]
},
{
"id": "pflegeschule-flensburg",
"title": "Pflege (ÖBiZ/DIAKO Flensburg)",
"short": "Generalistische Pflegeausbildung",
"icon": "heart",
"color": "#ec4899",
"description": "Generalistische Pflegeausbildung nach PflBG/PflAPrV: 2.100 h Theorie + 2.500 h Praxis, 11 curriculare Einheiten, 5 Kompetenzbereiche.",
"modules": [
{
"id": "ce01",
"title": "CE 01: Ausbildungsstart & wissenschaftliches Fundament",
"objectives": [
"Pflegeverständnis entwickeln",
"Pflegeprozess (6 Schritte) anwenden",
"Berufsidentität reflektieren"
],
"topics": ["Pflegeverständnis", "Pflegeprozess", "Berufsrolle"]
},
{
"id": "ce02",
"title": "CE 02: Hochbelastete & krisenhafte Situationen",
"objectives": [
"Reanimation durchführen (BLS + AED)",
"Akute Verwirrtheit erkennen",
"Sterbebegleitung gestalten"
],
"topics": ["Reanimation", "Delir", "Palliativpflege"]
},
{
"id": "ce03",
"title": "CE 03: Verstehens- und Aushandlungsprozesse",
"objectives": [
"Patientengespräch gestalten",
"Beobachtung strukturieren",
"Reflexion eigener Einstellungen"
],
"topics": ["Kommunikation", "Empathie", "Reflexion"]
},
{
"id": "ce04",
"title": "CE 04: Gesundheitsförderung & Prävention",
"objectives": [
"Primär-/Sekundär-/Tertiärprävention",
"Lebensstil-Beratung (Bewegung, Ernährung)",
"Impfungen nach STIKO"
],
"topics": ["Gesundheitsförderung", "Prävention", "Beratung"]
},
{
"id": "ce05",
"title": "CE 05: Kurative Prozesse & Patientensicherheit",
"objectives": [
"Medikamenten-Management",
"OP-Vorbereitung und Nachsorge",
"Fehlervermeidung / CIRS"
],
"topics": ["Medikation", "Perioperativ", "Patientensicherheit"]
},
{
"id": "ce06",
"title": "CE 06: Akutsituationen",
"objectives": [
"Notfälle erkennen und erstversorgen",
"Intensivpflege-Basics",
"Monitoring"
],
"topics": ["Notfall", "ITS", "Monitoring"]
},
{
"id": "ce07",
"title": "CE 07: Rehabilitation & chronische Erkrankungen",
"objectives": [
"Rehabilitative Pflege",
"Krankheits-Selbstmanagement fördern",
"Expertenstandards anwenden (Mobilität, Schmerz, Wunden)"
],
"topics": ["Reha", "Chronisch", "Expertenstandards"]
},
{
"id": "ce08",
"title": "CE 08: Kritische Lebenssituationen",
"objectives": [
"Demenzbegleitung",
"Psychiatrische Settings",
"Sucht, Gewalt, Suizidalität"
],
"topics": ["Demenz", "Psychiatrie", "Sucht"]
},
{
"id": "ce09",
"title": "CE 09: Eintritt in neue Lebensphasen",
"objectives": [
"Pädiatrie: Säuglings- und Kinderpflege",
"Wochenbettpflege",
"Geriatrische Eintritts- und Übergangsphasen"
],
"topics": ["Kinder", "Wochenbett", "Geriatrie"]
},
{
"id": "ce10",
"title": "CE 10: Kognitive & psychische Beeinträchtigungen",
"objectives": [
"Demenz-spezifische Pflege (DNQP-Standard)",
"Herausforderndes Verhalten verstehen",
"Milieu- und Biografiearbeit"
],
"topics": ["Demenz", "Depression", "Verhalten"]
},
{
"id": "ce11",
"title": "CE 11: Berufliches Selbstverständnis",
"objectives": [
"Professionalisierung",
"Resilienz",
"Supervision nutzen"
],
"topics": ["Professionalität", "Resilienz", "Supervision"]
},
{
"id": "standards",
"title": "Expertenstandards DNQP",
"objectives": [
"9 Standards kennen und anwenden",
"Dekubitus-, Sturz-, Schmerz-, Wund-, Kontinenz-, Ernährungs-, Entlassungs-, Mobilitäts-, Demenz-Standard"
],
"topics": ["Dekubitus", "Sturz", "Schmerz", "Wunden", "Kontinenz", "Ernährung", "Entlassung", "Mobilität", "Demenz"]
}
]
},
{
"id": "medizinische-terminologie",
"title": "Medizinische Terminologie",
"short": "Latein/Griechisch + Wortbildung",
"icon": "book",
"color": "#f59e0b",
"description": "Pflichtkurs 1. Semester: lateinisch/griechische Fachsprache, Wortbildung, Nomina Anatomica, klinische Terminologie.",
"modules": [
{
"id": "geschichte",
"title": "Geschichte der Fachsprache",
"objectives": [
"Hippokrates, Galen, Vesalius verorten",
"Nomina Anatomica / Terminologia Anatomica kennen",
"Rolle des Latein vs. Englisch"
],
"topics": ["Hippokrates", "Galen", "TA", "Nomenklatur"]
},
{
"id": "grammatik",
"title": "Lateinische Grammatik (Basics)",
"objectives": [
"5 Deklinationen sicher beherrschen",
"Adjektiv-Substantiv-Kongruenz",
"Nominativ & Genitiv im Fachbegriff"
],
"topics": ["Deklination", "Kasus", "Adjektiv", "Plural"]
},
{
"id": "wortbildung",
"title": "Wortbildung",
"objectives": [
"Präfix + Stamm + Suffix identifizieren",
"Verbindungsvokale (o/i) richtig setzen",
"Komposita zerlegen"
],
"topics": ["Präfix", "Suffix", "Kompositum", "Stamm"]
},
{
"id": "praefixe",
"title": "Wichtige Präfixe",
"objectives": [
"Griechische Präfixe (a-, dys-, hyper-, hypo-, tachy-, brady-)",
"Lateinische Präfixe (sub-, supra-, inter-, intra-, retro-)",
"Bedeutungsnuancen unterscheiden"
],
"topics": ["a-/an-", "dys-", "hyper-/hypo-", "tachy-/brady-", "sub-/supra-", "inter-/intra-"]
},
{
"id": "suffixe",
"title": "Wichtige Suffixe",
"objectives": [
"-itis vs -ose (entzündlich vs nicht)",
"-ektomie, -otomie, -stomie (operative Eingriffe)",
"-algie, -rrhagie, -ämie"
],
"topics": ["-itis", "-ose", "-ektomie", "-algie", "-rrhö"]
},
{
"id": "wortstaemme",
"title": "Wortstämme (Organe)",
"objectives": [
"Herz (cardi-), Niere (nephr-/ren-), Lunge (pneumo-/pulmo-)",
"Darm (enter-), Leber (hepat-), Magen (gastr-)",
"Synonymie Latein/Griechisch"
],
"topics": ["cardi-", "nephr-", "hepat-", "gastr-", "neuro-", "arthr-"]
},
{
"id": "lagebeziehungen",
"title": "Lage & Richtung",
"objectives": [
"superior/inferior, anterior/posterior etc.",
"3 Ebenen (sagittal, frontal, transversal)",
"proximal/distal am Gliedmaß"
],
"topics": ["Richtungen", "Ebenen", "Achsen"]
}
]
},
{
"id": "anatomie-grundlagen",
"title": "Anatomie & medizinische Grundlagen",
"short": "Vorklinik-Basics für alle",
"icon": "user",
"color": "#10b981",
"description": "Grundlagen der Anatomie, allgemeinen Pathologie, klinischen Untersuchung, Hygiene, Pharmakologie — Querschnitt für alle 4 Kern-Curricula.",
"modules": [
{
"id": "allg-anatomie",
"title": "Allgemeine Anatomie",
"objectives": [
"Gewebe (Epithel, Binde, Knorpel, Knochen, Muskel, Nerv)",
"Lagebezeichnungen und Ebenen",
"Bewegungsrichtungen benennen"
],
"topics": ["Gewebe", "Ebenen", "Bewegungen"]
},
{
"id": "bewegungsapparat",
"title": "Bewegungsapparat",
"objectives": [
"Knochen der oberen und unteren Extremität",
"Wichtigste Muskeln mit Ursprung, Ansatz, Funktion",
"Gelenktypen erkennen und beschreiben"
],
"topics": ["Knochen", "Muskeln", "Gelenke", "Wirbelsäule"]
},
{
"id": "situs",
"title": "Situs (innere Organe)",
"objectives": [
"Thorax-Organe in Lagebeziehung",
"Abdomen Ober-/Mittel-/Unterbauch",
"Retroperitoneale Organe"
],
"topics": ["Thorax", "Abdomen", "Retroperitoneal"]
},
{
"id": "neuroanatomie",
"title": "Neuroanatomie",
"objectives": [
"Aufbau Gehirn (Lappen, Hirnstamm, Cerebellum)",
"Rückenmark-Gliederung (31 Segmente)",
"12 Hirnnerven benennen",
"Plexus: cervicalis, brachialis, lumbalis, sacralis"
],
"topics": ["Gehirn", "Rückenmark", "Hirnnerven", "Plexus"]
},
{
"id": "biochemie-basics",
"title": "Biochemie-Basics",
"objectives": [
"Makromoleküle (Kohlenhydrate, Lipide, Proteine, NS)",
"Zitratzyklus und Atmungskette",
"Glukose-ATP-Bilanz"
],
"topics": ["Stoffwechsel", "ATP", "Aminosäuren", "Fettsäuren"]
},
{
"id": "pathologie-basics",
"title": "Allgemeine Pathologie",
"objectives": [
"Nekrose vs. Apoptose",
"Entzündung: 5 Kardinalzeichen",
"Tumorlehre (TNM, Grading)",
"Ischämie, Thrombose, Ödem"
],
"topics": ["Zelltod", "Entzündung", "Tumor", "Infarkt"]
},
{
"id": "klinische-untersuchung",
"title": "Klinische Untersuchung",
"objectives": [
"Strukturierte Anamnese (OPQRST)",
"Vitalzeichen interpretieren",
"Inspektion, Palpation, Perkussion, Auskultation"
],
"topics": ["Anamnese", "Vitals", "Untersuchung"]
},
{
"id": "hygiene",
"title": "Hygiene",
"objectives": [
"5 Indikationen der Händedesinfektion (WHO)",
"Isolationsarten (Kontakt/Tröpfchen/Aerogen)",
"Sterilisation vs. Desinfektion"
],
"topics": ["Händehygiene", "Isolation", "Sterilisation"]
},
{
"id": "pharma-basics",
"title": "Pharmakologie-Basics",
"objectives": [
"LADMET-Prinzip",
"Wichtige Wirkstoffgruppen (NSAR, Antikoagulantien, Antihypertensiva)",
"Kontraindikationen und Interaktionen"
],
"topics": ["Kinetik", "Dynamik", "Wirkstoffgruppen"]
},
{
"id": "notfall",
"title": "Notfall-Basics",
"objectives": [
"BLS-Algorithmus (ERC)",
"Stabile Seitenlage",
"FAST bei Schlaganfall"
],
"topics": ["Reanimation", "Seitenlage", "Schlaganfall"]
}
]
}
],
"badges": [
{"id": "first_quiz", "title": "Erster Quiz-Durchlauf", "icon": "award", "description": "Du hast dein erstes Quiz absolviert."},
{"id": "10_quiz_streak", "title": "10er-Serie", "icon": "flame", "description": "10 richtige Antworten in Folge."},
{"id": "100_answers", "title": "Zentner", "icon": "star", "description": "100 Antworten insgesamt gegeben."},
{"id": "7_day_streak", "title": "Wochen-Streak", "icon": "calendar", "description": "7 Tage in Folge aktiv."},
{"id": "curriculum_complete", "title": "Curriculum-Meister", "icon": "crown", "description": "Ein Curriculum vollständig durchgearbeitet."},
{"id": "night_owl", "title": "Nachteule", "icon": "moon", "description": "Nach 22 Uhr gelernt."},
{"id": "early_bird", "title": "Frühaufsteher", "icon": "sun", "description": "Vor 7 Uhr gelernt."}
],
"levels": [
{"min": 0, "title": "Anfängerin"},
{"min": 50, "title": "Einsteigerin"},
{"min": 200, "title": "Fortgeschrittene"},
{"min": 500, "title": "Profi"},
{"min": 1250, "title": "Expertin"},
{"min": 2500, "title": "Meisterin"},
{"min": 5000, "title": "Großmeisterin"}
]
}

3
src/levels-fallback.js Normal file
View file

@ -0,0 +1,3 @@
{ min: 0, title: 'Anfänger:in' }, { min: 50, title: 'Einsteiger:in' },
{ min: 200, title: 'Fortgeschrittene:r' }, { min: 500, title: 'Profi' },
{ min: 1250, title: 'Expert:in' }, { min: 2500, title: 'Meister:in' }

25
src/welcome.html Normal file
View file

@ -0,0 +1,25 @@
<h2>Willkommen bei Luna!</h2>
<p>Ich bin dein:e KI-Tutor:in für Physiotherapie, Pflege, Physiologie &amp; Anatomie. Alles läuft im deutschen Rechenzentrum — keine Daten verlassen Europa.</p>
<div class="mode-grid">
<button class="mode-card" data-goto="chat">
<strong>💬 Chat</strong>
<span>Frag mich alles zu deinem Stoff, Sokratisch erklärt.</span>
</button>
<button class="mode-card" data-goto="quiz">
<strong>🎯 Quiz</strong>
<span>Multiple-Choice mit Erklärungen und XP-Belohnung.</span>
</button>
<button class="mode-card" data-goto="flash">
<strong>🃏 Flashcards</strong>
<span>Karteikarten mit Spaced-Repetition.</span>
</button>
<button class="mode-card" data-goto="progress">
<strong>📊 Fortschritt</strong>
<span>XP, Streaks, Mastery, Abzeichen.</span>
</button>
<button class="mode-card" data-goto="curriculum">
<strong>📚 Lehrplan</strong>
<span>Kompletter Themenbaum — 5 Curricula.</span>
</button>
</div>
<p style="font-size:.82rem;color:var(--text-mute)">In 3 Sätzen: Chat für Verständnis → Quiz zum Testen → Flashcards zum Merken. Fortschritt zeigt dir, was schon sitzt; der Lehrplan gibt Orientierung.</p>

1803
www/app.js Normal file

File diff suppressed because it is too large Load diff

650
www/cockpit/cockpit.css Normal file
View file

@ -0,0 +1,650 @@
/* Luna Cockpit v3 — purple #a855f7, dark, no external deps */
:root {
--bg: #0a0a0f;
--bg-2: #11111b;
--bg-3: #1a1a26;
--bg-4: #232336;
--line: #2a2a3e;
--text: #e8e8f0;
--text-muted: #9090a8;
--text-dim: #6a6a85;
--accent: #a855f7;
--accent-2: #c084fc;
--accent-dark: #7c3aed;
--accent-rgb: 168, 85, 247;
--success: #10b981;
--warn: #f59e0b;
--danger: #ef4444;
--info: #38bdf8;
--shadow-sm: 0 1px 2px rgba(0,0,0,0.2);
--shadow-md: 0 4px 12px rgba(0,0,0,0.3);
--shadow-lg: 0 12px 32px rgba(0,0,0,0.5);
--radius: 8px;
--radius-lg: 12px;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Inter", "Helvetica Neue", Arial, sans-serif;
font-size: 15px;
line-height: 1.5;
min-height: 100vh;
}
a { color: var(--accent-2); text-decoration: none; }
a:hover { color: var(--accent); }
button { font: inherit; cursor: pointer; border: none; background: transparent; color: inherit; }
.cockpit {
max-width: 1280px;
margin: 0 auto;
padding: 1.5rem 1rem 6rem;
min-height: 100vh;
}
/* ─── Topbar ────────────────────────────────────────────────────── */
.topbar { display: flex; align-items: center; gap: 1rem; padding-bottom: 1.5rem; border-bottom: 1px solid var(--line); margin-bottom: 1.5rem; }
.brand { display: flex; align-items: center; gap: 0.75rem; }
.brand-icon {
width: 40px; height: 40px;
background: linear-gradient(135deg, var(--accent), var(--accent-dark));
color: white; font-weight: 700; font-size: 22px;
border-radius: var(--radius);
display: grid; place-items: center;
box-shadow: var(--shadow-md);
}
.brand-text { display: flex; flex-direction: column; }
.brand-title { font-size: 1.2rem; font-weight: 600; }
.brand-title small { color: var(--text-muted); font-weight: 400; margin-left: 0.25rem; }
.brand-sub { font-size: 0.8rem; color: var(--text-muted); }
.spacer { flex: 1; }
.auth-state { display: flex; align-items: center; gap: 0.75rem; }
.auth-user { color: var(--text-muted); font-size: 0.85rem; }
/* ─── Status block ─────────────────────────────────────────────── */
.status-block {
background: linear-gradient(135deg, rgba(var(--accent-rgb),0.08), rgba(var(--accent-rgb),0.02));
border: 1px solid rgba(var(--accent-rgb), 0.25);
border-radius: var(--radius-lg);
padding: 1rem 1.25rem;
margin-bottom: 1.5rem;
}
.status-title { font-size: 0.85rem; color: var(--accent-2); font-weight: 600; margin: 0 0 0.5rem; text-transform: uppercase; letter-spacing: 0.04em; }
.status-summary { font-size: 0.95rem; color: var(--text); }
.status-summary .dim { color: var(--text-muted); }
/* ─── Top-level Space-Tabs ─────────────────────────────────────── */
.space-tabs {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.5rem;
background: var(--bg-2);
padding: 0.5rem;
border-radius: var(--radius-lg);
border: 1px solid var(--line);
margin-bottom: 1.5rem;
}
.space-tab {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.85rem;
border-radius: var(--radius);
color: var(--text-muted);
transition: all 0.15s;
font-size: 0.95rem;
font-weight: 500;
}
.space-tab[aria-selected="true"] {
background: linear-gradient(135deg, rgba(var(--accent-rgb),0.18), rgba(var(--accent-rgb),0.05));
color: var(--accent-2);
box-shadow: inset 0 0 0 1px rgba(var(--accent-rgb), 0.4);
}
.space-tab:hover { background: var(--bg-3); color: var(--text); }
.space-tab[aria-selected="true"]:hover { color: var(--accent-2); }
.space-icon { font-size: 1.1rem; }
/* Each space-section is hidden unless data-active=true */
.space { display: none; }
.space[data-active="true"] { display: block; }
.space-intro { margin-bottom: 1.25rem; }
.space-intro h2 { margin: 0 0 0.4rem; font-size: 1.15rem; }
.space-intro p { margin: 0; color: var(--text-muted); font-size: 0.9rem; }
/* ─── Folder tabs (Dokumente) ──────────────────────────────────── */
.folder-tabs {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.5rem;
background: var(--bg-2);
padding: 0.5rem;
border-radius: var(--radius-lg);
border: 1px solid var(--line);
margin-bottom: 1rem;
}
.folder-tab {
display: flex; flex-direction: column; align-items: center; gap: 0.25rem;
padding: 0.75rem; border-radius: var(--radius);
color: var(--text-muted);
transition: background 0.15s, color 0.15s;
}
.folder-tab[aria-selected="true"] {
background: rgba(var(--accent-rgb), 0.12);
color: var(--text);
box-shadow: inset 0 -2px 0 var(--accent);
}
.folder-tab:hover { background: var(--bg-3); color: var(--text); }
.folder-icon { font-size: 1.25rem; }
.folder-label { font-size: 0.85rem; font-weight: 500; }
.folder-count {
font-size: 0.7rem; background: var(--bg-3); color: var(--text-muted);
padding: 0.1rem 0.5rem; border-radius: 10px; min-width: 24px; text-align: center;
}
.folder-tab[aria-selected="true"] .folder-count {
background: rgba(var(--accent-rgb), 0.25);
color: var(--accent-2);
}
.folder-body {
background: var(--bg-2); border: 1px solid var(--line);
border-radius: var(--radius-lg); padding: 1.25rem;
}
.folder-help { font-size: 0.85rem; color: var(--text-muted); margin-bottom: 1rem; line-height: 1.5; }
.upload-zone {
border: 2px dashed var(--line); border-radius: var(--radius-lg);
padding: 2rem 1rem; text-align: center;
transition: border-color 0.15s, background 0.15s;
cursor: pointer; margin-bottom: 1rem;
}
.upload-zone:hover, .upload-zone:focus, .upload-zone[data-dragover="true"] {
border-color: var(--accent); background: rgba(var(--accent-rgb), 0.05);
outline: none;
}
.upload-cta { display: flex; flex-direction: column; align-items: center; gap: 0.4rem; }
.upload-icon { font-size: 1.5rem; opacity: 0.6; }
.upload-text { font-size: 1rem; color: var(--text); }
.upload-hint { font-size: 0.8rem; color: var(--text-muted); }
.upload-progress { margin-top: 1rem; display: flex; flex-direction: column; gap: 0.5rem; }
.upload-progress-row {
display: flex; align-items: center; gap: 0.75rem;
padding: 0.5rem 0.75rem; background: var(--bg-3);
border-radius: var(--radius); font-size: 0.85rem;
}
.upload-progress-row .upload-bar { flex: 1; height: 4px; background: var(--bg); border-radius: 2px; overflow: hidden; }
.upload-progress-row .upload-bar-fill { height: 100%; background: var(--accent); width: 0%; transition: width 0.2s; }
/* ─── Document list ────────────────────────────────────────────── */
.doc-list { list-style: none; padding: 0; margin: 0; }
.doc-row { display: flex; align-items: center; gap: 1rem; padding: 0.75rem; border-bottom: 1px solid var(--line); transition: background 0.1s; }
.doc-row:hover { background: var(--bg-3); }
.doc-row.is-disabled { opacity: 0.55; }
.doc-meta { flex: 1; min-width: 0; }
.doc-name { font-size: 0.95rem; color: var(--text); margin: 0; word-break: break-word; }
.doc-info { font-size: 0.75rem; color: var(--text-muted); margin-top: 0.15rem; }
.doc-info .ok { color: var(--success); }
.doc-info .pending { color: var(--warn); }
.doc-info .err { color: var(--danger); }
.doc-actions { display: flex; gap: 0.5rem; align-items: center; }
.toggle {
position: relative; width: 42px; height: 22px;
background: var(--bg-3); border-radius: 11px;
cursor: pointer; transition: background 0.15s; flex-shrink: 0;
}
.toggle::after {
content: ""; position: absolute; top: 2px; left: 2px;
width: 18px; height: 18px; background: var(--text-muted);
border-radius: 50%; transition: left 0.15s, background 0.15s;
}
.toggle[data-on="true"] { background: rgba(var(--accent-rgb), 0.4); }
.toggle[data-on="true"]::after { left: 22px; background: var(--accent); }
.btn-icon {
width: 32px; height: 32px;
display: grid; place-items: center;
border-radius: var(--radius);
color: var(--text-muted);
transition: background 0.1s, color 0.1s;
}
.btn-icon:hover { background: var(--bg-3); color: var(--danger); }
.btn-icon-x {
width: 28px; height: 28px;
border-radius: 50%;
color: var(--text-muted);
transition: background 0.1s, color 0.1s;
}
.btn-icon-x:hover { background: var(--bg-3); color: var(--text); }
.doc-empty, .empty-state {
padding: 1.5rem; text-align: center;
color: var(--text-muted); font-size: 0.9rem;
}
/* ─── Buttons ──────────────────────────────────────────────────── */
.btn-primary {
background: var(--accent); color: white;
padding: 0.6rem 1.25rem; border-radius: var(--radius);
font-weight: 500; transition: background 0.15s;
}
.btn-primary:hover { background: var(--accent-dark); }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-secondary {
background: var(--bg-3); color: var(--text);
padding: 0.5rem 1rem; border-radius: var(--radius);
font-size: 0.85rem; border: 1px solid var(--line);
}
.btn-secondary:hover { background: var(--bg-4); }
.btn-ghost {
color: var(--text-muted); padding: 0.4rem 0.8rem;
border-radius: var(--radius); font-size: 0.85rem;
}
.btn-ghost:hover { background: var(--bg-3); color: var(--text); }
/* ─── Login screen ─────────────────────────────────────────────── */
.login-screen { display: grid; place-items: center; min-height: 60vh; }
.login-card { width: 100%; max-width: 380px; background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 2rem; box-shadow: var(--shadow-lg); }
.login-card h2 { margin: 0 0 0.5rem; font-size: 1.3rem; }
.login-hint { color: var(--text-muted); font-size: 0.9rem; margin: 0 0 1.5rem; }
.login-form { display: flex; flex-direction: column; gap: 1rem; }
.login-form label { display: flex; flex-direction: column; gap: 0.35rem; font-size: 0.85rem; color: var(--text-muted); }
.login-form input { padding: 0.6rem 0.8rem; background: var(--bg); color: var(--text); border: 1px solid var(--line); border-radius: var(--radius); font-size: 0.95rem; }
.login-form input:focus { outline: none; border-color: var(--accent); }
.login-error { background: rgba(239, 68, 68, 0.1); border: 1px solid var(--danger); color: var(--danger); padding: 0.6rem 0.8rem; border-radius: var(--radius); font-size: 0.85rem; }
/* ─── Klausur cards ────────────────────────────────────────────── */
.klausur-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 0.75rem; }
.klausur-card {
background: var(--bg-2); border: 1px solid var(--line);
border-radius: var(--radius); padding: 1rem;
display: flex; flex-direction: column; gap: 0.6rem;
transition: border-color 0.15s, transform 0.1s;
cursor: pointer;
}
.klausur-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.klausur-card-header { display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem; }
.klausur-card-num { background: var(--accent); color: white; font-weight: 700; font-size: 0.75rem; padding: 0.15rem 0.5rem; border-radius: 10px; flex-shrink: 0; }
.klausur-card-name { font-weight: 600; font-size: 0.95rem; flex: 1; word-break: break-word; }
.klausur-card-topics { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.klausur-card-topic { background: rgba(var(--accent-rgb), 0.12); color: var(--accent-2); padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.7rem; cursor: pointer; transition: background 0.1s; }
.klausur-card-topic:hover { background: rgba(var(--accent-rgb), 0.25); }
.klausur-card-notes { color: var(--text-muted); font-size: 0.8rem; font-style: italic; }
.klausur-card-cta { margin-top: 0.25rem; font-size: 0.85rem; color: var(--accent-2); display: flex; align-items: center; gap: 0.3rem; }
.klausur-card-actions { display: flex; gap: 0.4rem; margin-top: 0.4rem; flex-wrap: wrap; }
.klausur-card-mini {
font-size: 0.75rem; padding: 0.35rem 0.6rem; border-radius: 4px;
background: var(--bg-4); color: var(--text-muted);
border: 1px solid var(--line);
transition: all 0.1s;
}
.klausur-card-mini:hover { background: var(--bg-3); color: var(--accent-2); border-color: var(--accent); }
/* ─── Lernen: Heatmap ──────────────────────────────────────────── */
.heatmap-block { background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.25rem; margin-bottom: 1.5rem; }
.heatmap-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; gap: 0.5rem; }
.heatmap-title { margin: 0; font-size: 1rem; }
.heatmap-legend { display: flex; align-items: center; gap: 0.4rem; font-size: 0.75rem; color: var(--text-muted); flex-wrap: wrap; }
.legend-cell { display: inline-block; width: 18px; height: 12px; border-radius: 2px; }
.legend-cell[data-level="0"] { background: var(--bg-4); }
.legend-cell[data-level="2"] { background: rgba(245, 158, 11, 0.55); }
.legend-cell[data-level="4"] { background: rgba(16, 185, 129, 0.6); }
.legend-cell[data-level="5"] { background: rgba(16, 185, 129, 1); }
.heatmap-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 0.5rem; }
.heatmap-cell {
background: var(--bg-3); border: 1px solid var(--line);
border-radius: var(--radius); padding: 0.75rem;
display: flex; flex-direction: column; gap: 0.3rem;
position: relative; overflow: hidden;
transition: transform 0.1s, border-color 0.1s;
cursor: pointer;
}
.heatmap-cell:hover { transform: translateY(-1px); border-color: var(--accent); }
.heatmap-cell .hc-bar {
position: absolute; bottom: 0; left: 0; right: 0; height: 3px;
background: var(--bg-4);
}
.heatmap-cell .hc-bar-fill {
height: 100%; background: linear-gradient(to right, var(--warn), var(--success));
transition: width 0.3s;
}
.heatmap-cell .hc-name { font-size: 0.85rem; color: var(--text); font-weight: 500; line-height: 1.3; }
.heatmap-cell .hc-meta { font-size: 0.7rem; color: var(--text-muted); display: flex; justify-content: space-between; }
.heatmap-cell[data-level="0"] .hc-bar-fill { background: var(--bg-4); width: 0%; }
.heatmap-cell[data-level="1"] .hc-bar-fill { width: 20%; }
.heatmap-cell[data-level="2"] .hc-bar-fill { width: 40%; }
.heatmap-cell[data-level="3"] .hc-bar-fill { width: 60%; }
.heatmap-cell[data-level="4"] .hc-bar-fill { width: 80%; }
.heatmap-cell[data-level="5"] .hc-bar-fill { width: 100%; background: var(--success); }
/* ─── Lernen: Topic picker ─────────────────────────────────────── */
.topic-picker { background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.25rem; margin-bottom: 1.5rem; }
.topic-picker-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; gap: 0.5rem; flex-wrap: wrap; }
.topic-picker-head h3 { margin: 0; font-size: 1rem; }
.select-mini {
background: var(--bg-3); color: var(--text);
border: 1px solid var(--line); border-radius: var(--radius);
padding: 0.4rem 0.6rem; font-size: 0.85rem;
}
.select-mini:focus { outline: none; border-color: var(--accent); }
.topic-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0.5rem; }
.topic-pill {
background: var(--bg-3); border: 1px solid var(--line);
border-radius: var(--radius); padding: 0.6rem 0.85rem;
font-size: 0.85rem; color: var(--text);
cursor: pointer; transition: all 0.1s;
display: flex; flex-direction: column; gap: 0.25rem;
text-align: left;
}
.topic-pill:hover { border-color: var(--accent); background: var(--bg-4); }
.topic-pill .tp-label { font-weight: 500; }
.topic-pill .tp-meta { font-size: 0.7rem; color: var(--text-muted); }
.topic-pill[data-mastered="true"] { border-color: var(--success); }
.topic-pill .tp-stars { color: var(--warn); font-size: 0.75rem; letter-spacing: 0.05em; }
/* ─── Minigame Launcher ────────────────────────────────────────── */
.minigame-launcher {
background: linear-gradient(135deg, rgba(var(--accent-rgb),0.06), var(--bg-2));
border: 1px solid var(--accent);
border-radius: var(--radius-lg);
padding: 1.25rem;
margin-bottom: 1.5rem;
animation: fadein 0.2s ease-out;
}
.launcher-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; font-size: 0.95rem; }
.launcher-head strong { color: var(--accent-2); }
.launcher-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0.75rem; }
.minigame-card {
background: var(--bg-3); border: 1px solid var(--line);
border-radius: var(--radius); padding: 1rem;
display: flex; flex-direction: column; gap: 0.4rem;
text-align: left; transition: all 0.1s;
}
.minigame-card:hover { border-color: var(--accent); transform: translateY(-1px); background: var(--bg-4); }
.mg-icon { font-size: 1.3rem; }
.mg-title { font-weight: 600; font-size: 0.95rem; color: var(--text); }
.mg-desc { font-size: 0.8rem; color: var(--text-muted); line-height: 1.35; }
.mg-tag { font-size: 0.7rem; color: var(--accent-2); margin-top: 0.25rem; }
/* ─── Minigame Stage ──────────────────────────────────────────── */
.minigame-stage {
background: var(--bg-2); border: 1px solid var(--line);
border-radius: var(--radius-lg); padding: 1.5rem;
animation: fadein 0.2s ease-out;
}
.stage-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; gap: 0.5rem; flex-wrap: wrap; }
.stage-title { margin: 0; font-size: 1.05rem; font-weight: 600; }
.stage-progress { color: var(--text-muted); font-size: 0.85rem; }
.stage-progress-bar { height: 4px; background: var(--bg-4); border-radius: 2px; overflow: hidden; margin-bottom: 1rem; }
.stage-progress-bar-fill { height: 100%; background: var(--accent); transition: width 0.3s; }
.stage-loader { padding: 3rem 1rem; text-align: center; color: var(--text-muted); }
.stage-spinner {
display: inline-block; width: 28px; height: 28px;
border: 3px solid var(--bg-4); border-top-color: var(--accent);
border-radius: 50%; animation: spin 0.7s linear infinite;
margin-bottom: 0.8rem;
}
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes fadein { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
/* MCQ-style question block — used by Diagnose + Klausur quiz */
.qa-card { background: var(--bg-3); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.25rem; }
.qa-q { font-size: 1.05rem; font-weight: 500; margin: 0 0 1rem; line-height: 1.5; }
.qa-options { display: flex; flex-direction: column; gap: 0.5rem; }
.qa-option {
text-align: left; padding: 0.75rem 1rem;
background: var(--bg); border: 1px solid var(--line);
border-radius: var(--radius); transition: all 0.1s;
display: flex; gap: 0.75rem; align-items: flex-start;
}
.qa-option:not(:disabled):hover { border-color: var(--accent); background: var(--bg-4); }
.qa-option:disabled { cursor: default; }
.qa-option-letter { font-weight: 600; color: var(--text-muted); flex-shrink: 0; }
.qa-option[data-state="correct"] { border-color: var(--success); background: rgba(16, 185, 129, 0.1); }
.qa-option[data-state="wrong"] { border-color: var(--danger); background: rgba(239, 68, 68, 0.1); }
.qa-option[data-state="reveal-correct"] { border-color: var(--success); }
.qa-feedback {
margin-top: 1rem; padding: 0.85rem 1rem; border-radius: var(--radius);
background: var(--bg); border-left: 3px solid var(--accent);
font-size: 0.9rem; line-height: 1.5;
}
.qa-feedback.correct { border-left-color: var(--success); }
.qa-feedback.wrong { border-left-color: var(--danger); }
.qa-feedback strong { color: var(--accent-2); }
.qa-actions { display: flex; justify-content: flex-end; gap: 0.5rem; margin-top: 1rem; }
/* Klinikfall stage */
.fall-patient { background: rgba(56, 189, 248, 0.1); border-left: 3px solid var(--info); padding: 1rem; border-radius: var(--radius); margin-bottom: 1rem; line-height: 1.6; }
.fall-stage-num { color: var(--info); font-weight: 600; font-size: 0.85rem; margin-bottom: 0.25rem; }
.fall-learning {
margin-top: 0.75rem; padding: 0.6rem 0.85rem;
background: rgba(168, 85, 247, 0.08);
border-left: 3px solid var(--accent);
border-radius: var(--radius);
font-size: 0.85rem; color: var(--text-muted);
}
.fall-learning strong { color: var(--accent-2); }
/* Stimmt-das? */
.stimmt-grid { display: flex; flex-direction: column; gap: 0.75rem; }
.stimmt-card {
background: var(--bg-3); border: 1px solid var(--line);
border-radius: var(--radius); padding: 1rem;
}
.stimmt-statement { font-size: 0.95rem; margin-bottom: 0.75rem; line-height: 1.5; }
.stimmt-buttons { display: flex; gap: 0.5rem; }
.stimmt-btn {
flex: 1; padding: 0.5rem 1rem;
background: var(--bg); border: 1px solid var(--line);
border-radius: var(--radius); font-weight: 500;
transition: all 0.1s;
}
.stimmt-btn:not(:disabled):hover { border-color: var(--accent); background: var(--bg-4); }
.stimmt-btn[data-state="correct"] { border-color: var(--success); background: rgba(16, 185, 129, 0.15); color: var(--success); }
.stimmt-btn[data-state="wrong"] { border-color: var(--danger); background: rgba(239, 68, 68, 0.15); color: var(--danger); }
.stimmt-explain { margin-top: 0.5rem; font-size: 0.85rem; color: var(--text-muted); padding-left: 0.6rem; border-left: 2px solid var(--line); }
.stimmt-explain.correct { border-left-color: var(--success); }
.stimmt-explain.wrong { border-left-color: var(--danger); }
.trap-tag { display: inline-block; padding: 0.1rem 0.4rem; background: rgba(245, 158, 11, 0.15); color: var(--warn); border-radius: 3px; font-size: 0.7rem; margin-right: 0.4rem; }
/* Summary card for end of minigame */
.summary-card {
background: linear-gradient(135deg, rgba(var(--accent-rgb),0.1), var(--bg-3));
border: 1px solid var(--accent);
border-radius: var(--radius-lg);
padding: 1.5rem;
text-align: center;
}
.summary-stars {
font-size: 2rem; letter-spacing: 0.1em; margin-bottom: 0.5rem;
color: var(--warn);
}
.summary-text { font-size: 1rem; margin-bottom: 0.25rem; }
.summary-meta { color: var(--text-muted); font-size: 0.85rem; margin-bottom: 1rem; }
.summary-actions { display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; }
/* ─── Persona-Form ─────────────────────────────────────────────── */
.persona-form { background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 1.25rem; }
.persona-label { display: flex; flex-direction: column; gap: 0.5rem; font-size: 0.85rem; color: var(--text-muted); margin-bottom: 1rem; }
.persona-form textarea {
background: var(--bg); color: var(--text);
border: 1px solid var(--line); border-radius: var(--radius);
padding: 0.75rem 1rem; font-family: inherit; font-size: 0.95rem;
line-height: 1.5; resize: vertical; min-height: 200px;
}
.persona-form textarea:focus { outline: none; border-color: var(--accent); }
.persona-actions { display: flex; justify-content: space-between; align-items: center; gap: 1rem; }
.persona-status { color: var(--text-muted); font-size: 0.85rem; }
.persona-status.saved { color: var(--success); }
.hint-list { color: var(--text-muted); font-size: 0.85rem; line-height: 1.6; margin: 0.5rem 0 1rem; padding-left: 1.5rem; }
.hint-list li { margin-bottom: 0.25rem; }
/* ─── Telegram-Pairing Block ──────────────────────────────────── */
.telegram-block {
background: var(--bg-2); border: 1px solid var(--line);
border-radius: var(--radius-lg); padding: 1.25rem; margin-top: 1.5rem;
}
.tg-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; gap: 0.5rem; }
.tg-head h3 { margin: 0; font-size: 1rem; }
.tg-hint { color: var(--text-muted); font-size: 0.9rem; margin: 0 0 1rem; line-height: 1.5; }
.tg-badge {
font-size: 0.75rem; padding: 0.2rem 0.6rem; border-radius: 10px;
background: var(--bg-3); color: var(--text-muted);
border: 1px solid var(--line);
}
.tg-badge.linked { background: rgba(16,185,129,0.15); color: var(--success); border-color: var(--success); }
.tg-badge.unlinked { background: var(--bg-3); color: var(--text-muted); }
.tg-link-area { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.tg-code-display { margin-top: 1rem; }
.tg-code-line {
background: var(--bg); border: 1px solid var(--accent);
border-radius: var(--radius); padding: 0.85rem 1rem;
font-size: 1.1rem; letter-spacing: 0.05em;
margin: 0.5rem 0;
user-select: all;
display: flex; justify-content: center;
}
.tg-code-line code { color: var(--accent-2); font-weight: 600; font-size: 1.15rem; letter-spacing: 0.1em; }
.tg-code-hint { font-size: 0.8rem; color: var(--text-muted); margin: 0.25rem 0 0; }
/* ─── Chat Dock (resizable + MD) ──────────────────────────────── */
.chat-dock {
position: fixed;
bottom: 1rem; right: 1rem;
width: 460px; height: 640px;
min-width: 340px; min-height: 420px;
max-width: calc(100vw - 2rem);
max-height: calc(100vh - 2rem);
background: var(--bg-2); border: 1px solid var(--line);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
display: none; flex-direction: column;
z-index: 100;
}
.chat-dock[data-open="true"] { display: flex; }
.dock-resize {
position: absolute;
top: 0; left: 0;
width: 14px; height: 14px;
cursor: nwse-resize;
background: linear-gradient(135deg, transparent 35%, var(--text-dim) 35%, var(--text-dim) 50%, transparent 50%);
border-top-left-radius: var(--radius-lg);
z-index: 1;
}
.dock-resize:hover { background-color: rgba(var(--accent-rgb), 0.1); }
.dock-head { padding: 0.75rem 1rem; border-bottom: 1px solid var(--line); display: flex; align-items: center; gap: 0.5rem; }
.dock-title { font-weight: 600; }
.dock-sub { color: var(--text-muted); font-size: 0.85rem; flex: 1; }
.dock-reset, .dock-collapse { width: 28px; height: 28px; border-radius: var(--radius); color: var(--text-muted); }
.dock-reset:hover, .dock-collapse:hover { background: var(--bg-3); color: var(--text); }
.dock-box { flex: 1; padding: 0.75rem 1rem; overflow-y: auto; display: flex; flex-direction: column; gap: 0.75rem; }
.dock-msg {
max-width: 90%;
padding: 0.7rem 0.9rem;
border-radius: var(--radius);
font-size: 0.92rem;
line-height: 1.55;
word-break: break-word;
}
.dock-msg.user { align-self: flex-end; background: var(--accent-dark); color: white; white-space: pre-wrap; }
.dock-msg.assistant { align-self: flex-start; background: var(--bg-3); color: var(--text); }
.dock-msg.assistant.typing { font-style: italic; opacity: 0.7; }
/* MD content inside chat */
.dock-md p { margin: 0 0 0.6rem; }
.dock-md p:last-child { margin-bottom: 0; }
.dock-md h1, .dock-md h2, .dock-md h3 { margin: 0.6rem 0 0.4rem; line-height: 1.3; }
.dock-md h1 { font-size: 1.05rem; color: var(--accent-2); }
.dock-md h2 { font-size: 1rem; color: var(--accent-2); }
.dock-md h3 { font-size: 0.95rem; color: var(--text); }
.dock-md ul, .dock-md ol { margin: 0.4rem 0 0.6rem; padding-left: 1.4rem; }
.dock-md li { margin-bottom: 0.2rem; }
.dock-md code { background: var(--bg); padding: 0.1rem 0.35rem; border-radius: 3px; font-size: 0.85em; color: var(--accent-2); }
.dock-md pre { background: var(--bg); padding: 0.7rem 0.9rem; border-radius: var(--radius); overflow-x: auto; margin: 0.5rem 0; }
.dock-md pre code { background: none; padding: 0; color: var(--text); }
.dock-md strong { color: var(--text); }
.dock-md em { color: var(--text-muted); font-style: italic; }
.dock-md a { color: var(--accent-2); text-decoration: underline; }
.dock-md table.md-table { border-collapse: collapse; margin: 0.5rem 0; font-size: 0.85em; width: auto; }
.dock-md table.md-table th, .dock-md table.md-table td { border: 1px solid var(--line); padding: 0.3rem 0.6rem; }
.dock-md table.md-table th { background: var(--bg); }
.dock-md blockquote { border-left: 3px solid var(--accent); padding-left: 0.8rem; margin: 0.5rem 0; color: var(--text-muted); }
.dock-msg .sources {
margin-top: 0.6rem; padding-top: 0.5rem; border-top: 1px solid var(--line);
font-size: 0.72rem; color: var(--text-muted);
}
.dock-msg .sources .src-tag {
display: inline-block; background: rgba(var(--accent-rgb), 0.15); color: var(--accent-2);
padding: 0.1rem 0.4rem; border-radius: 3px; margin: 0.15rem 0.25rem 0 0; font-size: 0.7rem;
}
/* Inline structured renderer (quiz/flashcards/case in chat) */
.dock-struct {
background: var(--bg);
border: 1px solid var(--line);
border-radius: var(--radius);
padding: 0.85rem;
margin-top: 0.5rem;
}
.dock-struct .ds-head { font-weight: 600; color: var(--accent-2); margin-bottom: 0.4rem; font-size: 0.85rem; }
.dock-struct .ds-q { font-size: 0.9rem; margin-bottom: 0.5rem; }
.dock-struct .ds-options { display: flex; flex-direction: column; gap: 0.3rem; }
.dock-struct .ds-option {
text-align: left; padding: 0.4rem 0.6rem;
background: var(--bg-3); border: 1px solid var(--line);
border-radius: 4px; font-size: 0.85rem;
}
.dock-struct .ds-option:hover:not(:disabled) { border-color: var(--accent); }
.dock-struct .ds-option[data-state="correct"] { border-color: var(--success); background: rgba(16, 185, 129, 0.1); }
.dock-struct .ds-option[data-state="wrong"] { border-color: var(--danger); background: rgba(239, 68, 68, 0.1); }
.dock-form { border-top: 1px solid var(--line); padding: 0.5rem; display: flex; gap: 0.5rem; }
.dock-form textarea {
flex: 1; resize: none;
background: var(--bg); color: var(--text);
border: 1px solid var(--line); border-radius: var(--radius);
padding: 0.5rem 0.7rem; font-size: 0.9rem;
max-height: 140px; min-height: 36px;
font-family: inherit;
}
.dock-form textarea:focus { outline: none; border-color: var(--accent); }
.btn-send { background: var(--accent); color: white; width: 40px; border-radius: var(--radius); font-weight: 700; font-size: 1.1rem; }
.btn-send:hover { background: var(--accent-dark); }
.dock-open {
position: fixed; bottom: 1rem; right: 1rem;
background: var(--accent); color: white;
padding: 0.7rem 1.2rem; border-radius: 24px;
font-weight: 500; box-shadow: var(--shadow-lg);
display: flex; align-items: center; gap: 0.5rem;
z-index: 99;
}
.dock-open:hover { background: var(--accent-dark); }
.dock-open[hidden] { display: none; }
.dock-open-dot { width: 8px; height: 8px; background: var(--success); border-radius: 50%; display: inline-block; }
/* ─── Footer ───────────────────────────────────────────────────── */
.footer { margin-top: 2rem; padding-top: 1rem; border-top: 1px solid var(--line); text-align: center; font-size: 0.8rem; color: var(--text-muted); }
/* ─── Toasts ───────────────────────────────────────────────────── */
.toast-stack { position: fixed; top: 1rem; right: 1rem; display: flex; flex-direction: column; gap: 0.5rem; z-index: 200; pointer-events: none; }
.toast { padding: 0.6rem 1rem; background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow-md); color: var(--text); font-size: 0.85rem; pointer-events: auto; animation: toast-in 0.2s ease-out; }
.toast.error { border-color: var(--danger); color: var(--danger); }
.toast.success { border-color: var(--success); color: var(--success); }
@keyframes toast-in { from { transform: translateX(20px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
/* Mobile */
@media (max-width: 800px) {
.space-tabs, .folder-tabs { grid-template-columns: repeat(2, 1fr); }
.space-tab { font-size: 0.85rem; padding: 0.7rem 0.4rem; }
.chat-dock { width: calc(100vw - 2rem); height: calc(100vh - 6rem); right: 1rem; bottom: 1rem; }
.dock-resize { display: none; }
}

1099
www/cockpit/cockpit.js Normal file

File diff suppressed because it is too large Load diff

290
www/cockpit/index.html Normal file
View file

@ -0,0 +1,290 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Luna · Tutor-Cockpit</title>
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<meta name="theme-color" content="#0a0a0f">
<meta name="description" content="Luna — dein Tutor-Cockpit. Eigene Unterlagen, Klausur-Plan, Kompetenz-Diagnose, Klinikfälle.">
<link rel="stylesheet" href="cockpit.css">
<script>
window.__LUNA_KEY__ = 'qb_drfbhe3w6j2r7199w2';
window.__BOT_SLUG__ = 'physio-tutor';
window.__BOT_ID__ = 'r3816r760kur14i';
window.__API_BASE__ = 'https://api.qognio.com';
</script>
</head>
<body>
<div class="cockpit" role="application" aria-label="Luna Cockpit">
<header class="topbar">
<div class="brand">
<span class="brand-icon" aria-hidden="true">L</span>
<div class="brand-text">
<span class="brand-title">Luna <small>Cockpit</small></span>
<span class="brand-sub">Eigene Unterlagen · Klausur-Plan · Kompetenz-Diagnose</span>
</div>
</div>
<div class="spacer"></div>
<div id="auth-state" class="auth-state">
<span id="auth-user" class="auth-user"></span>
<button id="auth-logout" class="btn-secondary" type="button" hidden>Abmelden</button>
</div>
</header>
<!-- Login overlay -->
<section id="login-screen" class="login-screen" hidden>
<div class="login-card">
<h2>Bei Luna anmelden</h2>
<p class="login-hint">Damit Luna deine eigenen Dokumente verwenden kann, brauche ich deinen Login.</p>
<form id="login-form" class="login-form">
<label>E-Mail<input type="email" id="login-email" autocomplete="email" required></label>
<label>Passwort<input type="password" id="login-pw" autocomplete="current-password" required></label>
<button type="submit" class="btn-primary">Anmelden</button>
<div id="login-error" class="login-error" role="alert" hidden></div>
</form>
</div>
</section>
<main id="main" class="main" hidden>
<!-- Status banner -->
<section class="status-block">
<h2 class="status-title">Was Luna gerade weiß</h2>
<div id="status-summary" class="status-summary">Lade…</div>
</section>
<!-- Top-level Tabs -->
<nav class="space-tabs" role="tablist" aria-label="Cockpit-Bereiche">
<button class="space-tab" role="tab" aria-selected="true" data-space="dokumente">
<span class="space-icon">📁</span><span class="space-label">Dokumente</span>
</button>
<button class="space-tab" role="tab" aria-selected="false" data-space="klausuren">
<span class="space-icon">📋</span><span class="space-label">Klausur-Plan</span>
</button>
<button class="space-tab" role="tab" aria-selected="false" data-space="lernen">
<span class="space-icon">🧠</span><span class="space-label">Lernen</span>
</button>
<button class="space-tab" role="tab" aria-selected="false" data-space="steuern">
<span class="space-icon">🎛</span><span class="space-label">Bot steuern</span>
</button>
</nav>
<!-- ─── Space: Dokumente ─── -->
<section class="space" data-space="dokumente" data-active="true">
<nav class="folder-tabs" role="tablist" aria-label="Dokumenten-Ordner">
<button class="folder-tab" role="tab" aria-selected="true" data-folder="curriculum">
<span class="folder-icon">📚</span>
<span class="folder-label">Curriculum</span>
<span class="folder-count" data-count="curriculum">0</span>
</button>
<button class="folder-tab" role="tab" aria-selected="false" data-folder="official">
<span class="folder-icon">🏛️</span>
<span class="folder-label">Offizielle Doks</span>
<span class="folder-count" data-count="official">0</span>
</button>
<button class="folder-tab" role="tab" aria-selected="false" data-folder="own">
<span class="folder-icon">📝</span>
<span class="folder-label">Eigene Notizen</span>
<span class="folder-count" data-count="own">0</span>
</button>
<button class="folder-tab" role="tab" aria-selected="false" data-folder="role">
<span class="folder-icon">🎯</span>
<span class="folder-label">Schwerpunkt</span>
<span class="folder-count" data-count="role">0</span>
</button>
</nav>
<section class="folder-body">
<div id="folder-help" class="folder-help"></div>
<div class="upload-zone" id="upload-zone" tabindex="0">
<input type="file" id="file-input" hidden multiple
accept=".pdf,.txt,.md,.csv,.json,.png,.jpg,.jpeg,.webp">
<div class="upload-cta">
<span class="upload-icon">⬆️</span>
<span class="upload-text">Datei hier ablegen oder klicken</span>
<span class="upload-hint">PDF, Markdown, Text — max 20 MB</span>
</div>
<div id="upload-progress" class="upload-progress" hidden></div>
</div>
<ul id="doc-list" class="doc-list" aria-live="polite"></ul>
<div id="doc-empty" class="doc-empty" hidden>Noch nichts hier — leg los mit dem ersten Upload.</div>
</section>
</section>
<!-- ─── Space: Klausuren ─── -->
<section class="space" data-space="klausuren">
<div class="space-intro">
<h2>📋 Dein Klausur-Plan</h2>
<p>Aus deinem Curriculum extrahiert. Klick auf eine Klausur, um Luna gezielt darauf vorzubereiten.</p>
</div>
<div id="klausur-list" class="klausur-list">
<div class="empty-state" id="klausur-empty">Noch keine Klausuren erkannt. Lade ein Klausurplan-Dokument im Curriculum-Ordner hoch.</div>
</div>
</section>
<!-- ─── Space: Lernen ─── -->
<section class="space" data-space="lernen">
<div class="space-intro">
<h2>🧠 Lernen &amp; Üben</h2>
<p>Wähle ein Thema aus deinem Klausur-Plan oder Curriculum, dann starte ein Mini-Spiel.</p>
</div>
<!-- Heatmap: mastery per topic -->
<section class="heatmap-block">
<div class="heatmap-header">
<h3 class="heatmap-title">Dein aktueller Stand</h3>
<div class="heatmap-legend">
<span class="legend-cell" data-level="0"></span><span>0 noch nicht</span>
<span class="legend-cell" data-level="2"></span><span>2 in Arbeit</span>
<span class="legend-cell" data-level="4"></span><span>4 stark</span>
<span class="legend-cell" data-level="5"></span><span>5 gemeistert</span>
</div>
</div>
<div id="heatmap-grid" class="heatmap-grid">
<div class="empty-state">Klick auf ein Thema unten, um deinen Stand erstmals zu erfassen.</div>
</div>
</section>
<!-- Topic picker -->
<section class="topic-picker">
<div class="topic-picker-head">
<h3>Themen</h3>
<select id="topic-source" class="select-mini">
<option value="klausur">aus Klausur-Plan</option>
<option value="physiologie-uke">Physiologie (UKE)</option>
<option value="physiotherapie-aprv">Physiotherapie APrV</option>
<option value="pflegeschule-flensburg">Pflege Flensburg</option>
<option value="medizinische-terminologie">Med. Terminologie</option>
<option value="anatomie-grundlagen">Anatomie Grundlagen</option>
</select>
</div>
<div id="topic-grid" class="topic-grid"></div>
</section>
<!-- Minigame Launcher (visible nachdem Topic gewählt) -->
<section id="minigame-launcher" class="minigame-launcher" hidden>
<div class="launcher-head">
<span>Was willst du mit <strong id="launcher-topic"></strong> machen?</span>
<button class="btn-icon-x" id="launcher-close" aria-label="Schließen"></button>
</div>
<div class="launcher-grid">
<button class="minigame-card" data-game="diagnose">
<span class="mg-icon">🩺</span>
<span class="mg-title">Kompetenz-Check</span>
<span class="mg-desc">5 adaptive Fragen — Luna ermittelt deinen Stand.</span>
<span class="mg-tag">Diagnose · 3-5 min</span>
</button>
<button class="minigame-card" data-game="klinikfall">
<span class="mg-icon">🏥</span>
<span class="mg-title">Klinikfall</span>
<span class="mg-desc">Patient*in mit Symptomen — du triffst 3 Entscheidungen.</span>
<span class="mg-tag">Anwendung · 5-7 min</span>
</button>
<button class="minigame-card" data-game="stimmt-das">
<span class="mg-icon">🤔</span>
<span class="mg-title">Stimmt das?</span>
<span class="mg-desc">6 Aussagen — wahr oder Falle? Trainiert Misconceptions.</span>
<span class="mg-tag">Retrieval · 3 min</span>
</button>
<button class="minigame-card" data-game="quiz-classic">
<span class="mg-icon">📝</span>
<span class="mg-title">Klassisches Quiz</span>
<span class="mg-desc">10 Multiple-Choice-Fragen via Luna's Quiz-Engine.</span>
<span class="mg-tag">Übung · 5 min</span>
</button>
<button class="minigame-card" data-game="flashcards">
<span class="mg-icon">🃏</span>
<span class="mg-title">Karteikarten</span>
<span class="mg-desc">Spaced-Repetition mit Eigenbewertung pro Karte.</span>
<span class="mg-tag">Memorisierung · 3-5 min</span>
</button>
<button class="minigame-card" data-game="explain">
<span class="mg-icon">🎓</span>
<span class="mg-title">Erklär's mir</span>
<span class="mg-desc">Du erklärst — Luna gibt Feedback zu Klarheit + Vollständigkeit.</span>
<span class="mg-tag">Articulation · 5 min</span>
</button>
</div>
</section>
<!-- Minigame Stage (gefüllt während ein Spiel läuft) -->
<section id="minigame-stage" class="minigame-stage" hidden></section>
</section>
<!-- ─── Space: Steuern ─── -->
<section class="space" data-space="steuern">
<div class="space-intro">
<h2>🎛 Bot steuern</h2>
<p>Schreibe hier rein, wie Luna mit dir arbeiten soll. Beispiele:</p>
<ul class="hint-list">
<li>„Antworte ausführlicher und gib mehr klinische Beispiele"</li>
<li>„Stell mir öfter sokratische Rückfragen statt direkt zu erklären"</li>
<li>„Ich lerne lieber visuell — beschreib Diagramme detailliert"</li>
<li>„Fokus auf Klausur 1 — ignoriere andere Themen wenn ich nicht explizit frage"</li>
</ul>
</div>
<form id="persona-form" class="persona-form">
<label class="persona-label">
Deine Anweisungen an Luna
<textarea id="persona-overrides" rows="10"
placeholder="Schreibe hier deine Wünsche…"></textarea>
</label>
<div class="persona-actions">
<span id="persona-status" class="persona-status"></span>
<button type="submit" class="btn-primary">Speichern</button>
</div>
</form>
<!-- Telegram-Pairing -->
<section class="telegram-block">
<div class="tg-head">
<h3>📱 Mobil mit Telegram</h3>
<span id="tg-status-badge" class="tg-badge">prüfe…</span>
</div>
<p class="tg-hint">
Verbinde diesen Cockpit-Account mit dem
<a href="https://t.me/qognioLunaBot" target="_blank" rel="noopener">@qognioLunaBot</a>
auf Telegram. Dann kannst du Luna unterwegs fragen — und sie nutzt deinen Lernstand &amp; Dokumente.
</p>
<div id="tg-link-area" class="tg-link-area">
<button id="tg-generate" class="btn-primary" type="button">Code generieren</button>
<button id="tg-unlink" class="btn-secondary" type="button" hidden>Verbindung lösen</button>
</div>
<div id="tg-code-display" class="tg-code-display" hidden>
<p>Schicke diese Nachricht an
<a href="https://t.me/qognioLunaBot" target="_blank" rel="noopener">@qognioLunaBot</a>
auf Telegram:</p>
<pre class="tg-code-line"><code id="tg-code-text"></code></pre>
<p class="tg-code-hint">Code läuft in <span id="tg-code-ttl">15</span> Min ab.</p>
</div>
</section>
</section>
</main>
<!-- Chat-Dock (resizable + MD-rendering + structured output) -->
<aside id="chat-dock" class="chat-dock" aria-label="Luna Chat">
<div class="dock-resize" id="dock-resize" aria-hidden="true"></div>
<header class="dock-head">
<span class="dock-title">Luna</span>
<span class="dock-sub">deine Tutorin</span>
<button class="dock-reset" type="button" id="dock-reset" title="Chat zurücksetzen"></button>
<button class="dock-collapse" type="button" id="dock-collapse" title="Einklappen"></button>
</header>
<div id="dock-box" class="dock-box" aria-live="polite"></div>
<form id="dock-form" class="dock-form">
<textarea id="dock-input" rows="1" placeholder="Frag Luna — z.B. „erklär mir die Nephron-Funktion""></textarea>
<button type="submit" class="btn-send" aria-label="Senden">&rarr;</button>
</form>
</aside>
<button id="dock-open" class="dock-open" type="button" aria-label="Luna-Chat öffnen">
<span class="dock-open-dot"></span>Luna fragen
</button>
<footer class="footer">
Sovereign AI · Deutscher Bunker · <a href="https://qognio.com">Qognio</a> · Deine Daten bleiben bei dir
</footer>
</div>
<div id="toast-stack" class="toast-stack" aria-live="polite"></div>
<script src="cockpit.js"></script>
</body>
</html>

565
www/curricula.json Normal file
View file

@ -0,0 +1,565 @@
{
"version": "2026-04-21",
"updated": "2026-04-21",
"curricula": [
{
"id": "physiologie-uke",
"title": "Physiologie (UKE Hamburg)",
"short": "UKE iMED + Biomedizin",
"icon": "pulse",
"color": "#ef4444",
"description": "Integrierte Physiologie im UKE-Modellstudiengang iMED. 7 Systemmodule (AG) mit Herz-Kreislauf, Atmung, Niere, Neuro, Endokrin.",
"modules": [
{
"id": "zellphysiologie",
"title": "Zellphysiologie & erregbare Zellen",
"objectives": [
"Ruhemembranpotenzial & Nernst-Gleichung erklären",
"Aktionspotenzial-Phasen beschreiben",
"Synaptische Übertragung (EPSP/IPSP)",
"Na+/K+-ATPase, Kanäle, Transporter"
],
"topics": ["Membranpotenzial", "Aktionspotenzial", "Synapse", "Ionenkanäle", "Signaltransduktion"]
},
{
"id": "muskelphysiologie",
"title": "Muskelphysiologie",
"objectives": [
"Gleitfilamenttheorie & elektromechanische Kopplung",
"Skelett-, Herz-, glatte Muskulatur differenzieren",
"Kraft-Längen- und Kraft-Geschwindigkeits-Kurve",
"Motorische Einheit & Rekrutierung"
],
"topics": ["Sarkomer", "Aktin-Myosin", "Tetanus", "Ermüdung", "Motor unit"]
},
{
"id": "herz-kreislauf",
"title": "Herz-Kreislauf-Physiologie",
"objectives": [
"Herzzyklus & Wiggers-Diagramm interpretieren",
"EKG-Ableitungen & normale Komplexe erkennen",
"Frank-Starling-Mechanismus erklären",
"Blutdruckregulation kurz-/langfristig",
"Mikrozirkulation & Starling-Gleichgewicht"
],
"topics": ["Herzzyklus", "EKG", "Kontraktilität", "RAAS", "Barorezeptoren", "Kapillaren"]
},
{
"id": "atmung",
"title": "Atmung & Gasaustausch",
"objectives": [
"Lungenvolumina & Spirometrie interpretieren",
"Compliance, Resistance, Surfactant",
"Gastransport mit O2-Bindungskurve und Bohr-Effekt",
"Säure-Basen-Haushalt & renal/respiratorische Kompensation"
],
"topics": ["FRC", "Totraum", "V/Q", "Hämoglobin", "pH", "Bikarbonat"]
},
{
"id": "niere",
"title": "Niere & Wasser-Elektrolyt-Haushalt",
"objectives": [
"GFR-Konzept mit Clearance (Inulin, Kreatinin)",
"Tubuläre Reabsorption & Sekretion pro Abschnitt",
"Gegenstromprinzip & ADH",
"RAAS, Blutvolumenregulation"
],
"topics": ["Nephron", "Clearance", "ADH", "Aldosteron", "Elektrolyte"]
},
{
"id": "endokrin",
"title": "Endokrinologie",
"objectives": [
"Hypothalamus-Hypophysen-Achse",
"Schilddrüse, Nebenniere, Pankreas-Inseln",
"Glucose-Homöostase Insulin/Glukagon",
"Ca-Regulation (PTH, Calcitriol, Calcitonin)"
],
"topics": ["Hormone", "Insulin", "Cortisol", "Schilddrüse", "Calcium"]
},
{
"id": "nervensystem",
"title": "Nervensystem & Sinne",
"objectives": [
"Aufbau ZNS/PNS, vegetatives NS",
"Somatosensorik, Schmerz, propriozeptive Wahrnehmung",
"Visuelles, auditives und vestibuläres System",
"Motorisches System: Pyramidenbahn + Kleinhirn"
],
"topics": ["Sympathikus", "Parasympathikus", "Nozizeption", "Vestibular", "Pyramidenbahn"]
},
{
"id": "blut",
"title": "Blut & Hämostase",
"objectives": [
"Erythropoese, Hämoglobin-Varianten",
"Blutgruppen ABO + Rh",
"Primäre & sekundäre Hämostase",
"Fibrinolyse"
],
"topics": ["Erythrozyten", "ABO", "Thrombozyten", "Gerinnungskaskade"]
}
]
},
{
"id": "physiotherapie-aprv",
"title": "Physiotherapie (PhysTh-APrV)",
"short": "Staatliche Ausbildung 2900/1600 h",
"icon": "activity",
"color": "#a855f7",
"description": "Bundesgesetzliche Grundlage für die 3-jährige Physiotherapie-Ausbildung. 2.900 h Theorie + 1.600 h Praxis. Staatliche Prüfung.",
"modules": [
{
"id": "grundlagen",
"title": "Berufsgrundlagen & Recht",
"objectives": [
"PhysTh-APrV-Struktur kennen",
"Hygienerichtlinien anwenden",
"Erste Hilfe sicher durchführen",
"Berufs- und Gesetzeskunde"
],
"topics": ["APrV", "Hygiene", "Erste Hilfe", "Berufsrecht"],
"hours": 100
},
{
"id": "anatomie-physio-aprv",
"title": "Anatomie (240 h) & Physiologie (140 h)",
"objectives": [
"Bewegungsapparat detailliert",
"Innere Organe Situs",
"Neuroanatomie Grundlagen",
"Physiologische Regelkreise"
],
"topics": ["Muskeln", "Gelenke", "Nerven", "Organe"],
"hours": 380
},
{
"id": "krankheitslehre",
"title": "Spezielle Krankheitslehre (360 h)",
"objectives": [
"Orthopädische Krankheitsbilder",
"Neurologische Syndrome erkennen",
"Innere Medizin Grundlagen",
"Chirurgie und Traumatologie"
],
"topics": ["Innere", "Ortho", "Neuro", "Chirurgie", "Päd", "Psychiatrie", "Gyn", "Dermato", "Geriatrie", "Rheuma", "Arbeitsmed", "Sportmed"],
"hours": 360
},
{
"id": "bewegungslehre",
"title": "Bewegungslehre & Trainingslehre",
"objectives": [
"Kinematik, Kinetik, biomechanische Prinzipien",
"Trainingsplanung aufbauen",
"Belastungssteuerung (FITT)",
"Motorisches Lernen"
],
"topics": ["Biomechanik", "Ausdauer", "Kraft", "Koordination", "Beweglichkeit"],
"hours": 160
},
{
"id": "befund",
"title": "Befund- und Untersuchungstechniken (100 h)",
"objectives": [
"Strukturierte Anamnese",
"Gelenkmessung Neutral-Null",
"Muskelfunktionstest (Janda)",
"Spezielle Tests (z. B. Lachman, O'Brien, Thomas)",
"ICF-Dokumentation"
],
"topics": ["Anamnese", "Inspektion", "Palpation", "ROM", "MFT", "Spezialtests"],
"hours": 100
},
{
"id": "kg-techniken",
"title": "Krankengymnastische Behandlungstechniken (500 h)",
"objectives": [
"Manuelle Therapie (Kaltenborn, Maitland, Mulligan)",
"PNF verstehen und anwenden",
"Bobath, Vojta für Neuro",
"Atemtherapie",
"Schroth bei Skoliose, McKenzie",
"MTT & gerätegestütztes Training"
],
"topics": ["MT", "PNF", "Bobath", "Vojta", "Schroth", "McKenzie", "Atemtherapie", "MTT"],
"hours": 500
},
{
"id": "massage-physik",
"title": "Massage & Physikalische Therapie",
"objectives": [
"Klassische Massage & Reflexzonen",
"Lymphdrainage nach Vodder",
"Elektrotherapie (TENS, Iontophorese, Galvanik)",
"Hydro-, Balneo-, Thermotherapie",
"Ultraschall, Licht/Strahlen"
],
"topics": ["Massage", "MLD", "TENS", "Ultraschall", "Fango", "Inhalation"],
"hours": 270
},
{
"id": "methodische-anwendung",
"title": "Methodische Anwendung in Fachgebieten (700 h)",
"objectives": [
"Behandlungsaufbau in Orthopädie, Chirurgie, Innerer Medizin",
"Neurorehabilitation (Schlaganfall, MS, Parkinson)",
"Pädiatrie (Bobath-Säugling, ICP)",
"Psychiatrie (konzentrative Bewegungstherapie)",
"Gynäkologie (Rückbildung)"
],
"topics": ["Ortho-Reha", "Innere-Reha", "Neuro-Reha", "Päd", "Geriatrie", "Gyn"],
"hours": 700
},
{
"id": "pruefung",
"title": "Staatliche Prüfung",
"objectives": [
"4 schriftliche Aufsichtsarbeiten bestehen",
"3 mündliche Prüfungen (Anatomie, Physiologie, spez. Krankheitslehre)",
"3 praktische Prüfungen an Patient:innen"
],
"topics": ["Schriftlich", "Mündlich", "Praktisch"]
}
]
},
{
"id": "pflegeschule-flensburg",
"title": "Pflege (ÖBiZ/DIAKO Flensburg)",
"short": "Generalistische Pflegeausbildung",
"icon": "heart",
"color": "#ec4899",
"description": "Generalistische Pflegeausbildung nach PflBG/PflAPrV: 2.100 h Theorie + 2.500 h Praxis, 11 curriculare Einheiten, 5 Kompetenzbereiche.",
"modules": [
{
"id": "ce01",
"title": "CE 01: Ausbildungsstart & wissenschaftliches Fundament",
"objectives": [
"Pflegeverständnis entwickeln",
"Pflegeprozess (6 Schritte) anwenden",
"Berufsidentität reflektieren"
],
"topics": ["Pflegeverständnis", "Pflegeprozess", "Berufsrolle"]
},
{
"id": "ce02",
"title": "CE 02: Hochbelastete & krisenhafte Situationen",
"objectives": [
"Reanimation durchführen (BLS + AED)",
"Akute Verwirrtheit erkennen",
"Sterbebegleitung gestalten"
],
"topics": ["Reanimation", "Delir", "Palliativpflege"]
},
{
"id": "ce03",
"title": "CE 03: Verstehens- und Aushandlungsprozesse",
"objectives": [
"Patientengespräch gestalten",
"Beobachtung strukturieren",
"Reflexion eigener Einstellungen"
],
"topics": ["Kommunikation", "Empathie", "Reflexion"]
},
{
"id": "ce04",
"title": "CE 04: Gesundheitsförderung & Prävention",
"objectives": [
"Primär-/Sekundär-/Tertiärprävention",
"Lebensstil-Beratung (Bewegung, Ernährung)",
"Impfungen nach STIKO"
],
"topics": ["Gesundheitsförderung", "Prävention", "Beratung"]
},
{
"id": "ce05",
"title": "CE 05: Kurative Prozesse & Patientensicherheit",
"objectives": [
"Medikamenten-Management",
"OP-Vorbereitung und Nachsorge",
"Fehlervermeidung / CIRS"
],
"topics": ["Medikation", "Perioperativ", "Patientensicherheit"]
},
{
"id": "ce06",
"title": "CE 06: Akutsituationen",
"objectives": [
"Notfälle erkennen und erstversorgen",
"Intensivpflege-Basics",
"Monitoring"
],
"topics": ["Notfall", "ITS", "Monitoring"]
},
{
"id": "ce07",
"title": "CE 07: Rehabilitation & chronische Erkrankungen",
"objectives": [
"Rehabilitative Pflege",
"Krankheits-Selbstmanagement fördern",
"Expertenstandards anwenden (Mobilität, Schmerz, Wunden)"
],
"topics": ["Reha", "Chronisch", "Expertenstandards"]
},
{
"id": "ce08",
"title": "CE 08: Kritische Lebenssituationen",
"objectives": [
"Demenzbegleitung",
"Psychiatrische Settings",
"Sucht, Gewalt, Suizidalität"
],
"topics": ["Demenz", "Psychiatrie", "Sucht"]
},
{
"id": "ce09",
"title": "CE 09: Eintritt in neue Lebensphasen",
"objectives": [
"Pädiatrie: Säuglings- und Kinderpflege",
"Wochenbettpflege",
"Geriatrische Eintritts- und Übergangsphasen"
],
"topics": ["Kinder", "Wochenbett", "Geriatrie"]
},
{
"id": "ce10",
"title": "CE 10: Kognitive & psychische Beeinträchtigungen",
"objectives": [
"Demenz-spezifische Pflege (DNQP-Standard)",
"Herausforderndes Verhalten verstehen",
"Milieu- und Biografiearbeit"
],
"topics": ["Demenz", "Depression", "Verhalten"]
},
{
"id": "ce11",
"title": "CE 11: Berufliches Selbstverständnis",
"objectives": [
"Professionalisierung",
"Resilienz",
"Supervision nutzen"
],
"topics": ["Professionalität", "Resilienz", "Supervision"]
},
{
"id": "standards",
"title": "Expertenstandards DNQP",
"objectives": [
"9 Standards kennen und anwenden",
"Dekubitus-, Sturz-, Schmerz-, Wund-, Kontinenz-, Ernährungs-, Entlassungs-, Mobilitäts-, Demenz-Standard"
],
"topics": ["Dekubitus", "Sturz", "Schmerz", "Wunden", "Kontinenz", "Ernährung", "Entlassung", "Mobilität", "Demenz"]
}
]
},
{
"id": "medizinische-terminologie",
"title": "Medizinische Terminologie",
"short": "Latein/Griechisch + Wortbildung",
"icon": "book",
"color": "#f59e0b",
"description": "Pflichtkurs 1. Semester: lateinisch/griechische Fachsprache, Wortbildung, Nomina Anatomica, klinische Terminologie.",
"modules": [
{
"id": "geschichte",
"title": "Geschichte der Fachsprache",
"objectives": [
"Hippokrates, Galen, Vesalius verorten",
"Nomina Anatomica / Terminologia Anatomica kennen",
"Rolle des Latein vs. Englisch"
],
"topics": ["Hippokrates", "Galen", "TA", "Nomenklatur"]
},
{
"id": "grammatik",
"title": "Lateinische Grammatik (Basics)",
"objectives": [
"5 Deklinationen sicher beherrschen",
"Adjektiv-Substantiv-Kongruenz",
"Nominativ & Genitiv im Fachbegriff"
],
"topics": ["Deklination", "Kasus", "Adjektiv", "Plural"]
},
{
"id": "wortbildung",
"title": "Wortbildung",
"objectives": [
"Präfix + Stamm + Suffix identifizieren",
"Verbindungsvokale (o/i) richtig setzen",
"Komposita zerlegen"
],
"topics": ["Präfix", "Suffix", "Kompositum", "Stamm"]
},
{
"id": "praefixe",
"title": "Wichtige Präfixe",
"objectives": [
"Griechische Präfixe (a-, dys-, hyper-, hypo-, tachy-, brady-)",
"Lateinische Präfixe (sub-, supra-, inter-, intra-, retro-)",
"Bedeutungsnuancen unterscheiden"
],
"topics": ["a-/an-", "dys-", "hyper-/hypo-", "tachy-/brady-", "sub-/supra-", "inter-/intra-"]
},
{
"id": "suffixe",
"title": "Wichtige Suffixe",
"objectives": [
"-itis vs -ose (entzündlich vs nicht)",
"-ektomie, -otomie, -stomie (operative Eingriffe)",
"-algie, -rrhagie, -ämie"
],
"topics": ["-itis", "-ose", "-ektomie", "-algie", "-rrhö"]
},
{
"id": "wortstaemme",
"title": "Wortstämme (Organe)",
"objectives": [
"Herz (cardi-), Niere (nephr-/ren-), Lunge (pneumo-/pulmo-)",
"Darm (enter-), Leber (hepat-), Magen (gastr-)",
"Synonymie Latein/Griechisch"
],
"topics": ["cardi-", "nephr-", "hepat-", "gastr-", "neuro-", "arthr-"]
},
{
"id": "lagebeziehungen",
"title": "Lage & Richtung",
"objectives": [
"superior/inferior, anterior/posterior etc.",
"3 Ebenen (sagittal, frontal, transversal)",
"proximal/distal am Gliedmaß"
],
"topics": ["Richtungen", "Ebenen", "Achsen"]
}
]
},
{
"id": "anatomie-grundlagen",
"title": "Anatomie & medizinische Grundlagen",
"short": "Vorklinik-Basics für alle",
"icon": "user",
"color": "#10b981",
"description": "Grundlagen der Anatomie, allgemeinen Pathologie, klinischen Untersuchung, Hygiene, Pharmakologie — Querschnitt für alle 4 Kern-Curricula.",
"modules": [
{
"id": "allg-anatomie",
"title": "Allgemeine Anatomie",
"objectives": [
"Gewebe (Epithel, Binde, Knorpel, Knochen, Muskel, Nerv)",
"Lagebezeichnungen und Ebenen",
"Bewegungsrichtungen benennen"
],
"topics": ["Gewebe", "Ebenen", "Bewegungen"]
},
{
"id": "bewegungsapparat",
"title": "Bewegungsapparat",
"objectives": [
"Knochen der oberen und unteren Extremität",
"Wichtigste Muskeln mit Ursprung, Ansatz, Funktion",
"Gelenktypen erkennen und beschreiben"
],
"topics": ["Knochen", "Muskeln", "Gelenke", "Wirbelsäule"]
},
{
"id": "situs",
"title": "Situs (innere Organe)",
"objectives": [
"Thorax-Organe in Lagebeziehung",
"Abdomen Ober-/Mittel-/Unterbauch",
"Retroperitoneale Organe"
],
"topics": ["Thorax", "Abdomen", "Retroperitoneal"]
},
{
"id": "neuroanatomie",
"title": "Neuroanatomie",
"objectives": [
"Aufbau Gehirn (Lappen, Hirnstamm, Cerebellum)",
"Rückenmark-Gliederung (31 Segmente)",
"12 Hirnnerven benennen",
"Plexus: cervicalis, brachialis, lumbalis, sacralis"
],
"topics": ["Gehirn", "Rückenmark", "Hirnnerven", "Plexus"]
},
{
"id": "biochemie-basics",
"title": "Biochemie-Basics",
"objectives": [
"Makromoleküle (Kohlenhydrate, Lipide, Proteine, NS)",
"Zitratzyklus und Atmungskette",
"Glukose-ATP-Bilanz"
],
"topics": ["Stoffwechsel", "ATP", "Aminosäuren", "Fettsäuren"]
},
{
"id": "pathologie-basics",
"title": "Allgemeine Pathologie",
"objectives": [
"Nekrose vs. Apoptose",
"Entzündung: 5 Kardinalzeichen",
"Tumorlehre (TNM, Grading)",
"Ischämie, Thrombose, Ödem"
],
"topics": ["Zelltod", "Entzündung", "Tumor", "Infarkt"]
},
{
"id": "klinische-untersuchung",
"title": "Klinische Untersuchung",
"objectives": [
"Strukturierte Anamnese (OPQRST)",
"Vitalzeichen interpretieren",
"Inspektion, Palpation, Perkussion, Auskultation"
],
"topics": ["Anamnese", "Vitals", "Untersuchung"]
},
{
"id": "hygiene",
"title": "Hygiene",
"objectives": [
"5 Indikationen der Händedesinfektion (WHO)",
"Isolationsarten (Kontakt/Tröpfchen/Aerogen)",
"Sterilisation vs. Desinfektion"
],
"topics": ["Händehygiene", "Isolation", "Sterilisation"]
},
{
"id": "pharma-basics",
"title": "Pharmakologie-Basics",
"objectives": [
"LADMET-Prinzip",
"Wichtige Wirkstoffgruppen (NSAR, Antikoagulantien, Antihypertensiva)",
"Kontraindikationen und Interaktionen"
],
"topics": ["Kinetik", "Dynamik", "Wirkstoffgruppen"]
},
{
"id": "notfall",
"title": "Notfall-Basics",
"objectives": [
"BLS-Algorithmus (ERC)",
"Stabile Seitenlage",
"FAST bei Schlaganfall"
],
"topics": ["Reanimation", "Seitenlage", "Schlaganfall"]
}
]
}
],
"badges": [
{"id": "first_quiz", "title": "Erster Quiz-Durchlauf", "icon": "award", "description": "Du hast dein erstes Quiz absolviert."},
{"id": "10_quiz_streak", "title": "10er-Serie", "icon": "flame", "description": "10 richtige Antworten in Folge."},
{"id": "100_answers", "title": "Zentner", "icon": "star", "description": "100 Antworten insgesamt gegeben."},
{"id": "7_day_streak", "title": "Wochen-Streak", "icon": "calendar", "description": "7 Tage in Folge aktiv."},
{"id": "curriculum_complete", "title": "Curriculum-Meister", "icon": "crown", "description": "Ein Curriculum vollständig durchgearbeitet."},
{"id": "night_owl", "title": "Nachteule", "icon": "moon", "description": "Nach 22 Uhr gelernt."},
{"id": "early_bird", "title": "Frühaufsteher", "icon": "sun", "description": "Vor 7 Uhr gelernt."}
],
"levels": [
{"min": 0, "title": "Anfängerin"},
{"min": 50, "title": "Einsteigerin"},
{"min": 200, "title": "Fortgeschrittene"},
{"min": 500, "title": "Profi"},
{"min": 1250, "title": "Expertin"},
{"min": 2500, "title": "Meisterin"},
{"min": 5000, "title": "Großmeisterin"}
]
}

121
www/index.html Normal file
View file

@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Luna · PhysioTutor</title>
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<meta name="theme-color" content="#0a0a0f">
<meta name="description" content="Luna — dein KI-PhysioTutor. Gamified lernen mit Chat, Quiz, Flashcards und Fortschritts-Tracking. Läuft im deutschen Rechenzentrum.">
<link rel="stylesheet" href="styles.css">
<script>window.__LUNA_KEY__ = 'qb_drfbhe3w6j2r7199w2';</script>
</head>
<body>
<div class="app" role="application" aria-label="Luna PhysioTutor">
<header class="topbar">
<div class="brand">
<span class="brand-icon" aria-hidden="true">L</span>
<span>Luna <small>PhysioTutor</small></span>
</div>
<div class="spacer"></div>
<span class="status" role="status" aria-live="polite">Online</span>
</header>
<nav class="tabbar" role="tablist" aria-label="Modi">
<button class="tab" role="tab" aria-selected="true" aria-controls="view-chat" data-mode="chat">
Chat
<span class="tab-kbd">⌃1</span>
</button>
<button class="tab" role="tab" aria-selected="false" aria-controls="view-quiz" data-mode="quiz">
Quiz
<span class="tab-kbd">⌃2</span>
</button>
<button class="tab" role="tab" aria-selected="false" aria-controls="view-flash" data-mode="flash">
Karten
<span class="tab-kbd">⌃3</span>
</button>
<button class="tab" role="tab" aria-selected="false" aria-controls="view-progress" data-mode="progress">
Fortschritt
<span class="tab-kbd">⌃4</span>
</button>
<button class="tab" role="tab" aria-selected="false" aria-controls="view-curriculum" data-mode="curriculum">
Lehrplan
<span class="tab-kbd">⌃5</span>
</button>
</nav>
<main class="main">
<!-- CHAT -->
<section id="view-chat" class="view" role="tabpanel" aria-labelledby="tab-chat" data-active="true">
<div id="welcome-screen" class="welcome hidden" aria-hidden="true">
<h2>Willkommen bei Luna!</h2>
<p>Ich bin dein:e KI-Tutor:in für Physiotherapie, Pflege, Physiologie &amp; Anatomie. Alles läuft im deutschen Rechenzentrum — keine Daten verlassen Europa.</p>
<div class="mode-grid">
<button class="mode-card" data-goto="chat">
<strong>💬 Chat</strong>
<span>Frag mich alles zu deinem Stoff, Sokratisch erklärt.</span>
</button>
<button class="mode-card" data-goto="quiz">
<strong>🎯 Quiz</strong>
<span>Multiple-Choice mit Erklärungen und XP-Belohnung.</span>
</button>
<button class="mode-card" data-goto="flash">
<strong>🃏 Flashcards</strong>
<span>Karteikarten mit Spaced-Repetition.</span>
</button>
<button class="mode-card" data-goto="progress">
<strong>📊 Fortschritt</strong>
<span>XP, Streaks, Mastery, Abzeichen.</span>
</button>
<button class="mode-card" data-goto="curriculum">
<strong>📚 Lehrplan</strong>
<span>Kompletter Themenbaum — 5 Curricula.</span>
</button>
</div>
<p style="font-size:.82rem;color:var(--text-mute)">In 3 Sätzen: Chat für Verständnis → Quiz zum Testen → Flashcards zum Merken. Fortschritt zeigt dir, was schon sitzt; der Lehrplan gibt Orientierung.</p>
</div>
<div id="chat-box" class="chat-box" aria-live="polite" aria-label="Gespräch"></div>
</section>
<!-- QUIZ -->
<section id="view-quiz" class="view" role="tabpanel" aria-labelledby="tab-quiz">
<div id="quiz-host"></div>
</section>
<!-- FLASHCARDS -->
<section id="view-flash" class="view" role="tabpanel" aria-labelledby="tab-flash">
<div id="flash-host"></div>
</section>
<!-- PROGRESS -->
<section id="view-progress" class="view" role="tabpanel" aria-labelledby="tab-progress">
<div id="progress-host"></div>
</section>
<!-- CURRICULUM -->
<section id="view-curriculum" class="view" role="tabpanel" aria-labelledby="tab-curr">
<div id="curr-host"></div>
</section>
</main>
<form id="composer-form" class="composer" aria-label="Nachricht verfassen">
<div id="attach-strip" class="attach-strip" aria-live="polite"></div>
<div class="composer-row">
<button type="button" class="btn-attach" id="composer-attach" aria-label="Datei anhängen" title="Datei anhängen (PDF, Bild, Text — max 5 Dateien, 8 MB)">📎</button>
<input type="file" id="composer-file" multiple accept=".pdf,.txt,.md,.csv,.json,.xml,.yaml,.yml,.log,.png,.jpg,.jpeg,.webp,.gif" hidden>
<textarea id="composer" rows="1" placeholder="Frag Luna — Enter zum Senden, Shift+Enter für Zeilenumbruch" aria-label="Nachricht"></textarea>
<button type="submit" class="btn-primary" id="composer-send">Senden</button>
</div>
</form>
<footer class="footer">
Sovereign AI · Deutscher Bunker · <a href="https://qognio.com">Qognio</a> &nbsp;·&nbsp; DSGVO-konform · Keine externen Fonts · Keine Cookies
</footer>
</div>
<div id="toast-stack" class="toast-stack" aria-live="polite"></div>
<script src="app.js"></script>
</body>
</html>

1038
www/styles.css Normal file

File diff suppressed because it is too large Load diff