update
This commit is contained in:
170
frontend/src/components/BausteinFokus.vue
Normal file
170
frontend/src/components/BausteinFokus.vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<script setup>
|
||||
import BausteinPanel from './BausteinPanel.vue'
|
||||
import { renderMarkdown } from '../markdown.js'
|
||||
|
||||
const props = defineProps({
|
||||
baustein: { type: Object, required: true }, // { title, md, num }
|
||||
topic: { type: String, required: true },
|
||||
provider: { type: String, default: 'claude' },
|
||||
status: { type: Object, default: null },
|
||||
tier2: { type: Boolean, default: false },
|
||||
tier3: { type: Boolean, default: false },
|
||||
tab: { type: String, default: 'pruefung' },
|
||||
hasPrev: { type: Boolean, default: false },
|
||||
hasNext: { type: Boolean, default: false },
|
||||
fortschritt: { type: Object, default: () => ({ total: 0, absolviert: 0, verstanden: 0, gemeistert: 0 }) },
|
||||
})
|
||||
|
||||
import { computed } from 'vue'
|
||||
|
||||
defineEmits(['close', 'prev', 'next', 'statusChanged'])
|
||||
|
||||
const pct = (n) => (100 * n / (props.fortschritt.total || 1)) + '%'
|
||||
|
||||
// Akzentfarbe nach Stand des aktuellen Bausteins: gemeistert=gold, verstanden=lila, absolviert=grün.
|
||||
const standFarbe = computed(() => {
|
||||
const s = props.status || {}
|
||||
if (s.gemeistert) return '#d4af37'
|
||||
if (s.verstanden) return '#8b5cf6'
|
||||
if (s.absolviert) return 'var(--success-border)'
|
||||
return 'var(--border)'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fokus-overlay" :style="{ '--stand': standFarbe }">
|
||||
<div class="fokus-bar">
|
||||
<div class="fokus-bar-inner">
|
||||
<button class="fokus-btn" :disabled="!hasPrev" title="Voriger Baustein" @click="$emit('prev')">←</button>
|
||||
<button class="fokus-btn" :disabled="!hasNext" title="Nächster Baustein" @click="$emit('next')">→</button>
|
||||
<span class="fokus-titel">{{ baustein.title }}</span>
|
||||
<span v-if="status?.gemeistert" class="stand-badge gold">✓✓✓ Gemeistert</span>
|
||||
<span v-else-if="status?.verstanden" class="stand-badge lila">✓✓ Verstanden</span>
|
||||
<span v-else-if="status?.absolviert" class="stand-badge gruen">✓ Absolviert</span>
|
||||
<span class="fokus-spacer"></span>
|
||||
<button class="fokus-btn" title="Vollansicht beenden" @click="$emit('close')">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fokus-xp" :title="`${fortschritt.absolviert}/${fortschritt.total} absolviert · ${fortschritt.verstanden} verstanden · ${fortschritt.gemeistert} gemeistert`">
|
||||
<div class="xp-seg gold" :style="{ width: pct(fortschritt.gemeistert) }"></div>
|
||||
<div class="xp-seg lila" :style="{ width: pct(fortschritt.verstanden - fortschritt.gemeistert) }"></div>
|
||||
<div class="xp-seg gruen" :style="{ width: pct(fortschritt.absolviert - fortschritt.verstanden) }"></div>
|
||||
</div>
|
||||
<div class="fokus-body">
|
||||
<div class="fokus-col left">
|
||||
<div class="markdown" v-html="renderMarkdown(baustein.md)"></div>
|
||||
</div>
|
||||
<div class="fokus-col right">
|
||||
<BausteinPanel
|
||||
mode="full"
|
||||
:key="baustein.title + '|' + tab"
|
||||
:initial-tab="tab"
|
||||
:topic="topic"
|
||||
:baustein="baustein.title"
|
||||
:section="baustein.md"
|
||||
:provider="provider"
|
||||
:status="status"
|
||||
:tier2="tier2"
|
||||
:tier3="tier3"
|
||||
@status-changed="$emit('statusChanged', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.fokus-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 40;
|
||||
/* Halbtransparent + Blur: die Guide-Liste dahinter scheint verschwommen durch die grauen Flächen. */
|
||||
background: color-mix(in srgb, var(--bg-preview) 60%, transparent);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
display: grid;
|
||||
grid-template-rows: auto auto 1fr; /* Header / XP-Leiste / Body */
|
||||
}
|
||||
.fokus-bar {
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--panel);
|
||||
}
|
||||
/* Header-Inhalt fluchtet mit dem Body (gleiche max-width + Innenabstand). */
|
||||
.fokus-bar-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
max-width: 1600px;
|
||||
width: 100%;
|
||||
margin-inline: auto;
|
||||
padding-inline: 1.25rem;
|
||||
}
|
||||
.fokus-spacer { flex: 1; }
|
||||
|
||||
.stand-badge {
|
||||
margin-left: 0.5rem;
|
||||
padding: 0.12rem 0.6rem;
|
||||
font-size: 0.72rem; font-weight: 600;
|
||||
border-radius: 999px; border: 1px solid; white-space: nowrap;
|
||||
}
|
||||
.stand-badge.gruen { background: var(--success-soft); border-color: var(--success-border); color: var(--success); }
|
||||
.stand-badge.lila { background: color-mix(in srgb, #8b5cf6 16%, var(--panel)); border-color: #8b5cf6; color: #6d28d9; }
|
||||
.stand-badge.gold { background: color-mix(in srgb, #d4af37 20%, var(--panel)); border-color: #d4af37; color: #8a6d12; }
|
||||
|
||||
/* Erfahrungsleiste oben: füllt sich von links — gold (gemeistert) → lila (verstanden) → grün (absolviert). */
|
||||
.fokus-xp { display: flex; height: 6px; background: var(--panel-soft); }
|
||||
.xp-seg { height: 100%; transition: width 0.3s ease; }
|
||||
.xp-seg.gold { background: #d4af37; }
|
||||
.xp-seg.lila { background: #8b5cf6; }
|
||||
.xp-seg.gruen { background: var(--success-border); }
|
||||
.fokus-titel { font-weight: 600; font-size: 0.95rem; margin-left: 0.5rem; }
|
||||
.fokus-btn {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
min-width: 2rem; height: 2rem; padding: 0 0.5rem;
|
||||
font-size: 1rem;
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: 6px;
|
||||
background: var(--panel);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
}
|
||||
.fokus-btn:hover { border-color: var(--accent); }
|
||||
.fokus-btn:disabled { opacity: 0.4; cursor: default; }
|
||||
|
||||
/* Zwei abgehobene Karten auf grauem „Schreibtisch" (--bg-preview vom Overlay). */
|
||||
.fokus-body {
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
gap: 1.25rem;
|
||||
padding: 1.25rem;
|
||||
max-width: 1600px;
|
||||
width: 100%;
|
||||
margin-inline: auto;
|
||||
}
|
||||
.fokus-col {
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
/* Karte kippt nach Baustein-Stand: farbiger Rahmen + getönter Hintergrund (grün/lila/gold). */
|
||||
background: color-mix(in srgb, var(--stand) 7%, var(--panel));
|
||||
border: 1px solid var(--stand);
|
||||
border-top: 3px solid var(--stand);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
/* Links: breite Lese-Karte, Text in Lesebreite zentriert. */
|
||||
.fokus-col.left { flex: 1; padding: 2rem 2.5rem; }
|
||||
.fokus-col.left > * { max-width: 74ch; margin-inline: auto; }
|
||||
/* Rechts: Arbeits-Karte; Panel darin randlos (Karte ist der Rahmen). */
|
||||
.fokus-col.right { flex: none; width: clamp(440px, 34%, 600px); padding: 1.5rem; }
|
||||
.fokus-col.right :deep(.bp) { margin-top: 0; }
|
||||
.fokus-col.right :deep(.bp-panel) { border: none; background: transparent; padding: 0; }
|
||||
.fokus-h2 { font-size: 1.1rem; margin: 0 0 0.75rem; }
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.fokus-body { flex-direction: column; overflow-y: auto; }
|
||||
.fokus-col { overflow-y: visible; }
|
||||
.fokus-col.right { width: auto; }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user