diff --git a/frontend/src/components/questions/MathInput.tsx b/frontend/src/components/questions/MathInput.tsx
new file mode 100644
index 0000000..ba2cedf
--- /dev/null
+++ b/frontend/src/components/questions/MathInput.tsx
@@ -0,0 +1,45 @@
+import { useState, useEffect } from 'react'
+import katex from 'katex'
+import type { Question } from '../../types'
+
+interface Props {
+ q: Question
+ value: string | null | undefined
+ onChange: (val: string) => void
+ locked?: boolean
+}
+
+// The KaTeX preview uses innerHTML: KaTeX controls all output, making this safe.
+export default function MathInput({ value = '', onChange, locked }: Props) {
+ const [preview, setPreview] = useState('')
+
+ useEffect(() => {
+ if (!value) { setPreview(''); return }
+ try {
+ setPreview(katex.renderToString(value, { throwOnError: false, displayMode: false }))
+ } catch {
+ setPreview('')
+ }
+ }, [value])
+
+ return (
+
+
onChange(e.target.value)}
+ placeholder="LaTeX, e.g. 2x\ln x + x"
+ disabled={locked}
+ autoComplete="off"
+ spellCheck={false}
+ />
+
+ Preview
+ {preview
+ ?
+ : Live LaTeX preview appears here}
+
+
+ )
+}
diff --git a/frontend/src/components/questions/ShortAnswer.tsx b/frontend/src/components/questions/ShortAnswer.tsx
new file mode 100644
index 0000000..f57568b
--- /dev/null
+++ b/frontend/src/components/questions/ShortAnswer.tsx
@@ -0,0 +1,30 @@
+import type { Question } from '../../types'
+
+interface Props {
+ q: Question
+ value: string | null | undefined
+ onChange: (val: string) => void
+ locked?: boolean
+}
+
+export default function ShortAnswer({ q, value = '', onChange, locked }: Props) {
+ return (
+
+
onChange(e.target.value)}
+ placeholder="Type your answer…"
+ disabled={locked}
+ autoComplete="off"
+ spellCheck={q.type === 'translation'}
+ />
+ {q.type === 'translation' && q.targetLanguage && (
+
+ Answer in {q.targetLanguage}
+
+ )}
+
+ )
+}