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,
{
fileName: library.fileName,
subject: library.subject || 'Geral',
difficultyLabel: difficultyLabel(library.difficulty),
},
]),

View File

@@ -203,8 +203,7 @@
text-transform: uppercase;
}
.study-tag,
.study-status-badge {
.study-tag {
display: inline-flex;
align-items: center;
gap: 8px;
@@ -214,53 +213,13 @@
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-answer {
position: relative;
z-index: 1;
display: grid;
align-content: center;
gap: 16px;
gap: 18px;
min-height: 190px;
color: var(--ink);
font-family: "Segoe UI", Inter, Avenir, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
@@ -271,14 +230,14 @@
line-height: 1.5;
}
.study-back-question,
.study-back-answer {
.study-back-answer,
.study-back-question {
display: grid;
gap: 6px;
}
.study-back-question span,
.study-back-answer span {
.study-back-answer span,
.study-back-question span {
color: var(--blue-deep);
font-size: 11px;
font-weight: 950;
@@ -286,30 +245,21 @@
text-transform: uppercase;
}
.study-back-question p,
.study-back-answer p {
.study-back-answer p,
.study-back-question p {
margin: 0;
color: var(--ink);
}
.study-back-question p {
color: #66543d;
font-size: clamp(15px, 1.7vw, 18px);
line-height: 1.45;
}
.study-back-answer p {
font-size: clamp(20px, 2.65vw, 24px);
line-height: 1.5;
}
.study-card-details {
display: flex;
flex-wrap: wrap;
gap: 8px;
color: var(--muted);
font-size: 12px;
font-weight: 800;
.study-back-question p {
color: #66543d;
font-size: clamp(15px, 1.7vw, 18px);
line-height: 1.45;
}
.study-card-footer {
@@ -324,19 +274,6 @@
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 {
position: relative;
z-index: 1;
@@ -519,7 +456,7 @@
.study-queue-item {
display: grid;
grid-template-columns: 38px minmax(0, 1fr) auto;
grid-template-columns: 38px minmax(0, 1fr);
gap: 10px;
align-items: center;
padding: 10px;
@@ -536,11 +473,6 @@
font-size: 13px;
}
.study-queue-item span {
color: var(--muted);
font-size: 11px;
}
.study-queue-number {
width: 38px;
height: 38px;

View File

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

View File

@@ -226,24 +226,12 @@ export function SpacedReviewComponent() {
const sessionLibraryMetaById = useMemo(() => {
return new Map<number, FlashcardStudySessionLibraryMeta>(
sessionLibraries.map((library) => {
const statusMeta = STATUS_META_BY_STATUS[library.ragStatus];
return [
library.libraryId,
{
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.map((library) => [
library.libraryId,
{
fileName: library.fileName,
},
]),
);
}, [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.
- **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.
- **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).
### Variáveis CSS (definidas em `index.css`)