diff --git a/apps/web/src/lib/components/AudioFrontView.svelte b/apps/web/src/lib/components/AudioFrontView.svelte
new file mode 100644
index 0000000..8af178f
--- /dev/null
+++ b/apps/web/src/lib/components/AudioFrontView.svelte
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+ {#if frontText}
+
{frontText}
+ {/if}
+
+
+
diff --git a/apps/web/src/routes/study/[deckId]/+page.svelte b/apps/web/src/routes/study/[deckId]/+page.svelte
index 5b70855..607e430 100644
--- a/apps/web/src/routes/study/[deckId]/+page.svelte
+++ b/apps/web/src/routes/study/[deckId]/+page.svelte
@@ -16,6 +16,7 @@
import { toasts } from '$lib/stores/toasts.svelte.ts';
import { t } from '$lib/i18n/index.svelte.ts';
import ImageOcclusionView from '$lib/components/ImageOcclusionView.svelte';
+ import AudioFrontView from '$lib/components/AudioFrontView.svelte';
import CardSurface from '$lib/components/CardSurface.svelte';
const deckId = $derived(page.params.deckId ?? '');
@@ -93,6 +94,17 @@
};
});
+ const isAudioFront = $derived(current?.card?.type === 'audio-front');
+ const audioFrontData = $derived.by(() => {
+ const c = current;
+ if (!c?.card || c.card.type !== 'audio-front') return null;
+ const fields = c.card.fields as Record;
+ return {
+ audioRef: fields.audio_ref ?? '',
+ frontText: fields.front || undefined,
+ };
+ });
+
onMount(async () => {
if (!devUser.id) {
goto('/');
@@ -217,7 +229,14 @@
{revealed}
/>
{:else}
- {@html promptHtml}
+ {#if isAudioFront && audioFrontData}
+
+ {:else}
+ {@html promptHtml}
+ {/if}
{#if revealed}
{@html answerHtml}
diff --git a/packages/cards-domain/src/fsrs.ts b/packages/cards-domain/src/fsrs.ts
index ef9bbfa..92f3aa5 100644
--- a/packages/cards-domain/src/fsrs.ts
+++ b/packages/cards-domain/src/fsrs.ts
@@ -147,7 +147,7 @@ export function subIndexCount(type: string): number {
throw new Error(
'subIndexCount("image-occlusion") not supported — use maskRegionCount(fields.mask_regions) from @cards/domain'
);
- case 'audio':
+ case 'audio-front':
return 1;
case 'multiple-choice':
return 1;
diff --git a/packages/cards-domain/src/schemas/card.ts b/packages/cards-domain/src/schemas/card.ts
index 3a9c35c..11c718e 100644
--- a/packages/cards-domain/src/schemas/card.ts
+++ b/packages/cards-domain/src/schemas/card.ts
@@ -10,6 +10,7 @@ export const CardTypeSchema = z.enum([
'basic-reverse',
'cloze',
'image-occlusion',
+ 'audio-front',
]);
export type CardType = z.infer;
@@ -20,7 +21,7 @@ export const CardTypeFutureSchema = z.enum([
'cloze',
'type-in',
'image-occlusion',
- 'audio',
+ 'audio-front',
'multiple-choice',
]);
@@ -46,7 +47,7 @@ export function validateFieldsForType(
cloze: ['text'],
'type-in': ['question', 'expected'],
'image-occlusion': ['image_ref', 'mask_regions'],
- audio: ['audio_ref'],
+ 'audio-front': ['audio_ref', 'back'],
'multiple-choice': ['question', 'options', 'correct_index'],
};
const need = required[type] ?? [];