import SwiftUI /// Rendert die Karten-Inhalte je nach `CardType`. Front-/Back-Seite /// werden über `isFlipped` gesteuert. Wordeck ist text-only — alle /// Card-Types rendern ausschließlich Markdown-Text. struct CardRenderer: View { let card: ReviewCard let subIndex: Int let isFlipped: Bool var body: some View { Group { switch card.type { case .basic: basicView(front: "front", back: "back") case .basicReverse: // sub_index 0 = front→back, sub_index 1 = back→front if subIndex == 0 { basicView(front: "front", back: "back") } else { basicView(front: "back", back: "front") } case .cloze: clozeView case .multipleChoice: MultipleChoiceCardView(card: card, isFlipped: isFlipped) case .typing: TypingCardView(card: card, isFlipped: isFlipped) } } .padding(24) .frame(maxWidth: .infinity, maxHeight: .infinity) } private func basicView(front frontKey: String, back backKey: String) -> some View { VStack(spacing: 16) { text(card.fields[frontKey] ?? "") .font(.title2) .foregroundStyle(WordeckTheme.foreground) if isFlipped { Divider().background(WordeckTheme.border) text(card.fields[backKey] ?? "") .font(.title3) .foregroundStyle(WordeckTheme.mutedForeground) } } } @ViewBuilder private var clozeView: some View { let raw = card.fields["text"] ?? "" let clusterId = Cloze.clusterId(for: raw, subIndex: subIndex) ?? 1 let rendered = isFlipped ? Cloze.renderAnswer(raw, activeClusterId: clusterId) : Cloze.renderPrompt(raw, activeClusterId: clusterId) VStack(spacing: 12) { text(rendered) .font(.title3) .foregroundStyle(WordeckTheme.foreground) } } /// Markdown-Bold (`**...**`) wird auf SwiftUI's AttributedString gemappt. private func text(_ markdown: String) -> some View { let attributed = (try? AttributedString( markdown: markdown, options: AttributedString.MarkdownParsingOptions( interpretedSyntax: .inlineOnlyPreservingWhitespace ) )) ?? AttributedString(markdown) return Text(attributed) .multilineTextAlignment(.center) } }