From 57ad812c6f5b35420dd107509c58b1ab322c35ba Mon Sep 17 00:00:00 2001 From: hyungi Date: Sun, 7 Jun 2026 15:07:03 +0900 Subject: [PATCH] =?UTF-8?q?feat(study):=20=EC=95=94=EA=B8=B0=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=ED=95=99=EC=8A=B5=20=EB=8D=B0=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=ED=83=91=20Focus=20Stage=20=E2=80=94=20=EB=B0=98=EC=9D=91?= =?UTF-8?q?=ED=98=95(=EC=A2=8C=20=EC=A7=84=ED=96=89=ED=8A=B8=EB=9E=99?= =?UTF-8?q?=C2=B7=EC=A4=91=EC=95=99=20=EB=AC=B4=EB=8C=80=EC=B9=B4=EB=93=9C?= =?UTF-8?q?=C2=B7=EC=9A=B0=20=EA=B7=BC=EA=B1=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 데스크탑서 좁은 카드 하나만 휑하던 문제 해결. 모바일 단일 카드는 그대로, md+ 에서 3밴드 그리드. - 좌: 진행 n/total + 카드별 결과 점(marks: correct/unsure/wrong/seen/flagged) + 집계 - 중앙: 무대 카드(max-w-600·확대 타이포·shadow), 평가 버튼 - 우: reveal 시 근거 fade-in(자리 예약=레이아웃 점프 0), 미reveal 시 빈 칸 시안 A(Focus Stage) 채택. 컨테이너 md:max-w-5xl, 랜딩 md:max-w-xl 제약. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/routes/study/cards-study/+page.svelte | 188 +++++++++++------- 1 file changed, 120 insertions(+), 68 deletions(-) diff --git a/frontend/src/routes/study/cards-study/+page.svelte b/frontend/src/routes/study/cards-study/+page.svelte index 04cca6d..cecc892 100644 --- a/frontend/src/routes/study/cards-study/+page.svelte +++ b/frontend/src/routes/study/cards-study/+page.svelte @@ -42,6 +42,23 @@ let tally = $state({ correct: 0, unsure: 0, wrong: 0 }); // 복습 결과 집계 let seen = $state(0); // 그냥공부 본 카드 수 let dueCount = $state(null); // landing 배지 + let marks = $state([]); // 카드별 결과 (데스크탑 좌측 진행트랙 점): 'correct'|'unsure'|'wrong'|'seen'|'flagged' + + function setMark(kind) { + const m = [...marks]; + m[idx] = kind; + marks = m; + } + // 데스크탑 진행트랙 점 클래스 (크기+색). 현재 카드는 accent 세로막대, 지난 카드는 결과색. + function dotClass(i) { + if (i === idx && !done) return 'h-4 w-1.5 bg-accent'; + const k = marks[i]; + if (k === 'correct') return 'h-1.5 w-1.5 bg-success'; + if (k === 'unsure') return 'h-1.5 w-1.5 bg-warning'; + if (k === 'wrong') return 'h-1.5 w-1.5 bg-error'; + if (k === 'seen' || k === 'flagged') return 'h-1.5 w-1.5 bg-faint'; + return 'h-1.5 w-1.5 bg-default'; + } let current = $derived(cards[idx] ?? null); let total = $derived(cards.length); @@ -63,6 +80,7 @@ idx = 0; revealed = false; tally = { correct: 0, unsure: 0, wrong: 0 }; + marks = []; try { cards = _dueCache ?? (await fetchDue()); _dueCache = null; // 소비 @@ -81,6 +99,7 @@ idx = 0; revealed = false; seen = 0; + marks = []; try { const q = fmtFilter ? `?format=${fmtFilter}&limit=40` : '?limit=40'; cards = (await api(`/study-cards/deck${q}`)) ?? []; @@ -118,6 +137,7 @@ }); const key = label === '암' ? 'correct' : label === '애매' ? 'unsure' : 'wrong'; tally = { ...tally, [key]: tally[key] + 1 }; + setMark(key); advance(); } catch (err) { addToast('error', err?.detail || '평가 저장 실패'); @@ -132,6 +152,7 @@ try { await api(`/study-cards/${current.id}/view`, { method: 'POST' }); seen += 1; + setMark('seen'); advance(); } catch (err) { addToast('error', err?.detail || '기록 실패'); @@ -149,6 +170,7 @@ try { await api(`/study-cards/${c.id}`, { method: 'PATCH', body: JSON.stringify({ needs_review: true }) }); addToast('success', '검수함으로 보냈어요 — 이 카드는 학습에서 빠집니다'); + setMark('flagged'); advance(); } catch (err) { addToast('error', err?.detail || '신고 처리 실패'); @@ -198,7 +220,7 @@ 암기카드 학습 -
+
{#if mode === 'landing'} @@ -231,8 +253,8 @@ {#if mode === 'landing'} -

검수 완료한 암기카드를 학습합니다. 두 가지 방법 중 선택하세요.

-
+

검수 완료한 암기카드를 학습합니다. 두 가지 방법 중 선택하세요.

+
+ +
+ + - - {#if revealed} - {#if mode === 'review'} -
+ +
+
+
+ {current.format} - - + type="button" + onclick={flagCard} + disabled={flagBusy || busy} + class="flex items-center gap-1 text-[11px] text-faint transition-colors hover:text-warning disabled:opacity-50" + title="카드 내용이 이상하면 검수함으로 보냅니다" + > + 이 카드 이상해요 +
- - {:else} - + +
+ 앞 — {current.format === 'qa' ? '질문' : '회상'} +
+
{frontText(current)}
+ + {#if revealed} +
+
정답
+
{current.fact}
+ {#if current.evidence?.length && current.evidence[0].snippet} +
근거: {current.evidence[0].snippet}
+ {/if} +
+ {/if} + + {#if !revealed} + + {/if} +
+ + + {#if revealed} + {#if mode === 'review'} +
+ + + +
+ + {:else} + + {/if} {/if} - {/if} +
+ + +
{/if}