improving ui
All checks were successful
Mindforge Web Build and Deploy (internal) / Build Mindforge Web Image (push) Successful in 2m36s
Mindforge Web Build and Deploy (internal) / Deploy Mindforge Web (internal) (push) Successful in 1m21s

This commit is contained in:
2026-06-14 11:42:10 -03:00
parent 031cbb7f42
commit e243d190e9
5 changed files with 65 additions and 150 deletions

View File

@@ -82,7 +82,6 @@ export function FlashcardReviewComponent() {
library.id, library.id,
{ {
fileName: library.fileName, fileName: library.fileName,
subject: library.subject || 'Geral',
difficultyLabel: difficultyLabel(library.difficulty), difficultyLabel: difficultyLabel(library.difficulty),
}, },
]), ]),

View File

@@ -203,8 +203,7 @@
text-transform: uppercase; text-transform: uppercase;
} }
.study-tag, .study-tag {
.study-status-badge {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
@@ -214,53 +213,13 @@
border: 1px solid rgba(82, 54, 17, .12); border: 1px solid rgba(82, 54, 17, .12);
} }
.study-status-badge {
font-size: 11px;
line-height: 1;
}
.study-status-icon {
width: 16px;
height: 16px;
border-radius: 999px;
display: inline-flex;
align-items: center;
justify-content: center;
background: rgba(82, 54, 17, .10);
font-size: 11px;
}
.study-status-badge.rag-red {
color: var(--red-deep);
background: rgba(183, 91, 77, 0.12);
border-color: rgba(183, 91, 77, 0.22);
}
.study-status-badge.rag-amber {
color: #74531c;
background: rgba(199, 149, 57, 0.14);
border-color: rgba(199, 149, 57, 0.24);
}
.study-status-badge.rag-green {
color: var(--green-deep);
background: rgba(79, 143, 90, 0.12);
border-color: rgba(79, 143, 90, 0.22);
}
.study-status-badge.rag-grey {
color: var(--muted);
background: rgba(123, 106, 80, 0.10);
border-color: rgba(123, 106, 80, 0.18);
}
.study-card-question, .study-card-question,
.study-card-answer { .study-card-answer {
position: relative; position: relative;
z-index: 1; z-index: 1;
display: grid; display: grid;
align-content: center; align-content: center;
gap: 16px; gap: 18px;
min-height: 190px; min-height: 190px;
color: var(--ink); color: var(--ink);
font-family: "Segoe UI", Inter, Avenir, system-ui, -apple-system, BlinkMacSystemFont, sans-serif; font-family: "Segoe UI", Inter, Avenir, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
@@ -271,14 +230,14 @@
line-height: 1.5; line-height: 1.5;
} }
.study-back-question, .study-back-answer,
.study-back-answer { .study-back-question {
display: grid; display: grid;
gap: 6px; gap: 6px;
} }
.study-back-question span, .study-back-answer span,
.study-back-answer span { .study-back-question span {
color: var(--blue-deep); color: var(--blue-deep);
font-size: 11px; font-size: 11px;
font-weight: 950; font-weight: 950;
@@ -286,30 +245,21 @@
text-transform: uppercase; text-transform: uppercase;
} }
.study-back-question p, .study-back-answer p,
.study-back-answer p { .study-back-question p {
margin: 0; margin: 0;
color: var(--ink); color: var(--ink);
} }
.study-back-question p {
color: #66543d;
font-size: clamp(15px, 1.7vw, 18px);
line-height: 1.45;
}
.study-back-answer p { .study-back-answer p {
font-size: clamp(20px, 2.65vw, 24px); font-size: clamp(20px, 2.65vw, 24px);
line-height: 1.5; line-height: 1.5;
} }
.study-card-details { .study-back-question p {
display: flex; color: #66543d;
flex-wrap: wrap; font-size: clamp(15px, 1.7vw, 18px);
gap: 8px; line-height: 1.45;
color: var(--muted);
font-size: 12px;
font-weight: 800;
} }
.study-card-footer { .study-card-footer {
@@ -324,19 +274,6 @@
font-weight: 800; font-weight: 800;
} }
.study-spacebar {
padding: 4px 9px;
border-radius: 8px;
color: #4f3a1d;
background: rgba(255,255,255,.58);
border: 1px solid rgba(82, 54, 17, .14);
box-shadow: inset 0 -2px 0 rgba(82, 54, 17, .08);
font-size: 11px;
font-weight: 950;
letter-spacing: .08em;
text-transform: uppercase;
}
.study-controls { .study-controls {
position: relative; position: relative;
z-index: 1; z-index: 1;
@@ -519,7 +456,7 @@
.study-queue-item { .study-queue-item {
display: grid; display: grid;
grid-template-columns: 38px minmax(0, 1fr) auto; grid-template-columns: 38px minmax(0, 1fr);
gap: 10px; gap: 10px;
align-items: center; align-items: center;
padding: 10px; padding: 10px;
@@ -536,11 +473,6 @@
font-size: 13px; font-size: 13px;
} }
.study-queue-item span {
color: var(--muted);
font-size: 11px;
}
.study-queue-number { .study-queue-number {
width: 38px; width: 38px;
height: 38px; height: 38px;

View File

@@ -5,13 +5,7 @@ import './FlashcardStudySession.css';
export interface FlashcardStudySessionLibraryMeta { export interface FlashcardStudySessionLibraryMeta {
fileName?: string; fileName?: string;
subject?: string;
subSubject?: string;
difficultyLabel?: string; difficultyLabel?: string;
statusLabel?: string;
statusIcon?: string;
statusClassName?: string;
footerDetails?: string[];
} }
interface FlashcardStudySessionProps { interface FlashcardStudySessionProps {
@@ -105,13 +99,13 @@ function resetTimeout(timeoutRef: { current: number | null }) {
} }
function formatFooter(meta: FlashcardStudySessionLibraryMeta | undefined) { function formatFooter(meta: FlashcardStudySessionLibraryMeta | undefined) {
const main = [meta?.fileName, meta?.subSubject].filter(Boolean).join(' - '); return meta?.fileName || 'Arquivo';
return main || 'Arquivo';
} }
export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd }: FlashcardStudySessionProps) { export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd }: FlashcardStudySessionProps) {
const [currentIndex, setCurrentIndex] = useState(0); const [currentIndex, setCurrentIndex] = useState(0);
const [showAnswer, setShowAnswer] = useState(false); const [showAnswer, setShowAnswer] = useState(false);
const [showBackQuestion, setShowBackQuestion] = useState(false);
const [submittingAnswer, setSubmittingAnswer] = useState(false); const [submittingAnswer, setSubmittingAnswer] = useState(false);
const [cardExiting, setCardExiting] = useState(false); const [cardExiting, setCardExiting] = useState(false);
const [stampState, setStampState] = useState<'correct' | 'wrong' | null>(null); const [stampState, setStampState] = useState<'correct' | 'wrong' | null>(null);
@@ -134,6 +128,7 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
useEffect(() => { useEffect(() => {
setCurrentIndex(0); setCurrentIndex(0);
setShowAnswer(false); setShowAnswer(false);
setShowBackQuestion(false);
setSubmittingAnswer(false); setSubmittingAnswer(false);
setCardExiting(false); setCardExiting(false);
setStampState(null); setStampState(null);
@@ -165,10 +160,10 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
if (!showAnswer) { if (!showAnswer) {
setShowAnswer(true); setShowAnswer(true);
} }
} else if (e.code === 'KeyC' && showAnswer && !submittingAnswer) { } else if (e.code === 'KeyC' && showAnswer && !showBackQuestion && !submittingAnswer) {
e.preventDefault(); e.preventDefault();
void registerReviewAnswer(true); void registerReviewAnswer(true);
} else if (e.code === 'KeyW' && showAnswer && !submittingAnswer) { } else if (e.code === 'KeyW' && showAnswer && !showBackQuestion && !submittingAnswer) {
e.preventDefault(); e.preventDefault();
void registerReviewAnswer(false); void registerReviewAnswer(false);
} }
@@ -176,12 +171,13 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
window.addEventListener('keydown', handleKeyDown); window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown);
}, [cards.length, showAnswer, submittingAnswer, currentCard]); }, [cards.length, currentCard, showAnswer, showBackQuestion, submittingAnswer]);
const goToPrevious = () => { const goToPrevious = () => {
if (currentIndex === 0 || submittingAnswer) return; if (currentIndex === 0 || submittingAnswer) return;
setCurrentIndex(currentIndex - 1); setCurrentIndex(currentIndex - 1);
setShowAnswer(false); setShowAnswer(false);
setShowBackQuestion(false);
setFlipped(false); setFlipped(false);
setStampState(null); setStampState(null);
setCardExiting(false); setCardExiting(false);
@@ -196,6 +192,7 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
setCurrentIndex(currentIndex + 1); setCurrentIndex(currentIndex + 1);
setShowAnswer(false); setShowAnswer(false);
setShowBackQuestion(false);
setFlipped(false); setFlipped(false);
setStampState(null); setStampState(null);
setCardExiting(false); setCardExiting(false);
@@ -204,7 +201,7 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
}; };
const registerReviewAnswer = async (correct: boolean) => { const registerReviewAnswer = async (correct: boolean) => {
if (!currentCard || !showAnswer || submittingAnswer) return; if (!currentCard || !showAnswer || showBackQuestion || submittingAnswer) return;
resetTimeout(stampTimerRef); resetTimeout(stampTimerRef);
resetTimeout(advanceTimerRef); resetTimeout(advanceTimerRef);
@@ -277,22 +274,38 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
<div class="study-stage"> <div class="study-stage">
<div <div
class={`study-flashcard${flipped ? ' is-flipped' : ''}${cardExiting ? ' is-reviewed' : ''}`} class={`study-flashcard${flipped ? ' is-flipped' : ''}${cardExiting ? ' is-reviewed' : ''}`}
onClick={() => { if (!showAnswer && !submittingAnswer) setShowAnswer(true); }} onClick={() => {
if (submittingAnswer) return;
if (!showAnswer) {
setShowAnswer(true);
return;
}
if (!showBackQuestion) {
setShowBackQuestion(true);
}
}}
tabIndex={0} tabIndex={0}
role="button" role="button"
aria-label={showAnswer ? 'Flashcard revelado' : 'Clique ou pressione Espaço para revelar'} aria-label={
!showAnswer
? 'Clique ou pressione Espaço para revelar'
: showBackQuestion
? 'Flashcard com pergunta e resposta visíveis'
: 'Clique para mostrar a pergunta junto da resposta'
}
> >
<div class="study-card-face"> <div class="study-card-face">
<div class="study-card-meta"> {currentMeta?.difficultyLabel && (
<span class="study-tag">{currentMeta?.subject || 'Geral'}</span> <div class="study-card-meta">
<span>{currentMeta?.difficultyLabel || currentMeta?.statusLabel || 'Revisão'}</span> <span class="study-tag">{currentMeta.difficultyLabel}</span>
</div> </div>
)}
<div class="study-card-question"> <div class="study-card-question">
{currentCard.front} {currentCard.front}
</div> </div>
<div class="study-card-footer"> <div class="study-card-footer">
<span>{formatFooter(currentMeta)}</span> <span>{formatFooter(currentMeta)}</span>
<span class="study-spacebar">Espaço</span>
</div> </div>
</div> </div>
@@ -304,35 +317,22 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
)} )}
<div class="study-card-meta"> <div class="study-card-meta">
<span class="study-tag">Resposta</span> <span class="study-tag">Resposta</span>
{currentMeta?.statusLabel ? ( {currentMeta?.difficultyLabel && <span>{currentMeta.difficultyLabel}</span>}
<span class={`study-status-badge ${currentMeta.statusClassName || ''}`}>
{currentMeta.statusIcon && <span class="study-status-icon">{currentMeta.statusIcon}</span>}
{currentMeta.statusLabel}
</span>
) : (
<span>{currentMeta?.difficultyLabel || 'Revisão'}</span>
)}
</div> </div>
<div class="study-card-answer"> <div class="study-card-answer">
<div class="study-back-question">
<span>Pergunta</span>
<p>{currentCard.front}</p>
</div>
<div class="study-back-answer"> <div class="study-back-answer">
<span>Resposta</span> <span>Resposta</span>
<p>{currentCard.back}</p> <p>{currentCard.back}</p>
</div> </div>
{currentMeta?.footerDetails && currentMeta.footerDetails.length > 0 && ( {showBackQuestion && (
<div class="study-card-details"> <div class="study-back-question">
{currentMeta.footerDetails.map((detail) => ( <span>Pergunta</span>
<span key={detail}>{detail}</span> <p>{currentCard.front}</p>
))}
</div> </div>
)} )}
</div> </div>
<div class="study-card-footer"> <div class="study-card-footer">
<span>{formatFooter(currentMeta)}</span> <span>{formatFooter(currentMeta)}</span>
<span class="study-spacebar">C / W</span>
</div> </div>
</div> </div>
</div> </div>
@@ -342,14 +342,14 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
<button <button
class="study-review-button correct" class="study-review-button correct"
onClick={() => void registerReviewAnswer(true)} onClick={() => void registerReviewAnswer(true)}
disabled={!showAnswer || submittingAnswer} disabled={!showAnswer || showBackQuestion || submittingAnswer}
> >
Correto Correto
</button> </button>
<button <button
class="study-review-button wrong" class="study-review-button wrong"
onClick={() => void registerReviewAnswer(false)} onClick={() => void registerReviewAnswer(false)}
disabled={!showAnswer || submittingAnswer} disabled={!showAnswer || showBackQuestion || submittingAnswer}
> >
Incorreto Incorreto
</button> </button>
@@ -386,16 +386,12 @@ export function FlashcardStudySession({ cards, libraryMetaById, onAnswer, onEnd
<div class="study-panel-card"> <div class="study-panel-card">
<h3>Fila</h3> <h3>Fila</h3>
<div class="study-queue"> <div class="study-queue">
{cards.slice(currentIndex, currentIndex + 5).map((card, index) => { {cards.slice(currentIndex, currentIndex + 5).map((card, index) => (
const meta = libraryMetaById.get(card.libraryId); <div key={card.id} class="study-queue-item">
return ( <span class="study-queue-number">{currentIndex + index + 1}</span>
<div key={card.id} class="study-queue-item"> <strong>{card.front.substring(0, 40)}{card.front.length > 40 ? '...' : ''}</strong>
<span class="study-queue-number">{currentIndex + index + 1}</span> </div>
<strong>{card.front.substring(0, 40)}{card.front.length > 40 ? '...' : ''}</strong> ))}
<span>{meta?.subject || ''}</span>
</div>
);
})}
{remainingCount > 5 && ( {remainingCount > 5 && (
<div class="study-queue-more"> <div class="study-queue-more">
+{remainingCount - 5} restantes +{remainingCount - 5} restantes

View File

@@ -226,24 +226,12 @@ export function SpacedReviewComponent() {
const sessionLibraryMetaById = useMemo(() => { const sessionLibraryMetaById = useMemo(() => {
return new Map<number, FlashcardStudySessionLibraryMeta>( return new Map<number, FlashcardStudySessionLibraryMeta>(
sessionLibraries.map((library) => { sessionLibraries.map((library) => [
const statusMeta = STATUS_META_BY_STATUS[library.ragStatus]; library.libraryId,
return [ {
library.libraryId, fileName: library.fileName,
{ },
fileName: library.fileName, ]),
subject: library.subject || 'Geral',
subSubject: library.subSubject || 'Geral',
statusLabel: statusMeta.label,
statusIcon: statusMeta.icon,
statusClassName: statusMeta.className,
footerDetails: [
`Desempenho do arquivo: ${formatPerformance(library.performanceRate)}`,
`Última revisão: ${formatLastReviewed(library.lastReviewedAt)}`,
],
},
];
}),
); );
}, [sessionLibraries]); }, [sessionLibraries]);

View File

@@ -147,7 +147,7 @@ Formas de requisição principais:
- **Background**: Gradiente radial quente (dourado/azul) sobre base `#fff8e6` com overlay de grid de papel 24px. - **Background**: Gradiente radial quente (dourado/azul) sobre base `#fff8e6` com overlay de grid de papel 24px.
- **Layout**: CSS Grid (`grid-template-columns: 288px minmax(0, 1fr)`). Sidebar sticky com textura diagonal. Topbar integrada ao fluxo (não fixa). - **Layout**: CSS Grid (`grid-template-columns: 288px minmax(0, 1fr)`). Sidebar sticky com textura diagonal. Topbar integrada ao fluxo (não fixa).
- **Animações**: Apenas sob ação do usuário (flip 3D do flashcard, carimbo, saída do cartão, confete canvas). Sem animações infinitas ou spinners. - **Animações**: Apenas sob ação do usuário (flip 3D do flashcard, carimbo, saída do cartão, confete canvas). Sem animações infinitas ou spinners.
- **Flashcard**: Cartão 3D com efeito `rotateY(180deg)`, frente papel pautado com borda tracejada, verso azulado que mostra pergunta menor + resposta principal. Carimbo de feedback ("Correto"/"Incorreto") é renderizado no verso visível antes da saída do cartão. Confete canvas ao acertar. - **Flashcard**: Cartão 3D com efeito `rotateY(180deg)`, frente papel pautado com borda tracejada e verso azulado focado na resposta. No verso, um clique extra revela a pergunta; enquanto essa pergunta auxiliar estiver visível, os botões "Correto" e "Incorreto" ficam bloqueados. O card não exibe status RAG nem metadados de assunto/subassunto/desempenho/última revisão. O carimbo de feedback ("Correto"/"Incorreto") é renderizado no verso visível antes da saída do cartão, e há confete canvas ao acertar.
- **Responsivo**: Breakpoints em 1120px (sidebar colapsada) e 760px (layout single-column). - **Responsivo**: Breakpoints em 1120px (sidebar colapsada) e 760px (layout single-column).
### Variáveis CSS (definidas em `index.css`) ### Variáveis CSS (definidas em `index.css`)