-
-

-
Mindforge - Forja Mental
-
Sua ferramenta de estudos para concursos.
-
+
+
+
+
setActiveModule('home')} />
+
+
+

+
Mindforge — Forja Mental
+
Sua ferramenta de estudos para concursos.
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
- >
+
);
}
diff --git a/Mindforge.Web/src/components/Button.css b/Mindforge.Web/src/components/Button.css
index 64f5d55..6ddf1ed 100644
--- a/Mindforge.Web/src/components/Button.css
+++ b/Mindforge.Web/src/components/Button.css
@@ -1,32 +1,30 @@
.btn {
- font-family: var(--font-main);
- font-weight: 700;
- font-size: 1rem;
- padding: 0.8rem 1.5rem;
- border-radius: 8px;
- border: none;
+ font-family: inherit;
+ font-weight: 850;
+ font-size: 13px;
+ padding: 0 18px;
+ min-height: 42px;
+ border-radius: 999px;
cursor: pointer;
outline: none;
- transition: all 0.3s ease;
- backdrop-filter: blur(8px);
- -webkit-backdrop-filter: blur(8px);
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
- letter-spacing: 0.5px;
+ transition: .25s var(--ease);
+ letter-spacing: 0;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 9px;
+ border: 1px solid rgba(96, 67, 28, .15);
+ background: rgba(255,255,255,.62);
+ color: #4b3b27;
+ box-shadow: inset 0 1px 0 rgba(255,255,255,.72);
}
-.btn-primary {
- background: rgba(255, 255, 255, 0.1);
- color: var(--color-text-creamy);
- border: 1px solid rgba(255, 255, 255, 0.2);
-}
-
-.btn-primary:hover {
- background: rgba(255, 255, 255, 0.2);
+.btn:hover {
transform: translateY(-2px);
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
+ box-shadow: 0 10px 20px rgba(86, 57, 17, .12), inset 0 1px 0 rgba(255,255,255,.72);
}
-.btn-primary:active {
+.btn:active {
transform: translateY(0);
}
@@ -37,12 +35,21 @@
box-shadow: none;
}
-.btn-secondary {
- background: transparent;
- color: var(--color-text-creamy);
- border: 1px solid rgba(255, 255, 255, 0.1);
+.btn-primary {
+ background: linear-gradient(135deg, var(--blue), var(--blue-deep));
+ color: #fff;
+ border-color: rgba(37, 95, 141, .3);
+ box-shadow: 0 16px 34px rgba(63, 44, 20, .18), inset 0 1px 0 rgba(255,255,255,.28);
+ font-weight: 950;
+ font-size: 14px;
+ min-height: 48px;
}
-.btn-secondary:hover {
- background: rgba(255, 255, 255, 0.05);
+.btn-primary:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 22px 42px rgba(63, 44, 20, .22), inset 0 1px 0 rgba(255,255,255,.28);
+}
+
+.btn-secondary {
+ background: rgba(255,255,255,.45);
}
diff --git a/Mindforge.Web/src/components/FileTreeComponent.css b/Mindforge.Web/src/components/FileTreeComponent.css
index f221126..440a11b 100644
--- a/Mindforge.Web/src/components/FileTreeComponent.css
+++ b/Mindforge.Web/src/components/FileTreeComponent.css
@@ -1,11 +1,13 @@
.file-tree {
- background: rgba(255, 255, 255, 0.04);
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 8px;
+ background: rgba(255, 250, 239, .68);
+ border: 1px solid rgba(104, 69, 22, .13);
+ border-radius: var(--radius-md);
padding: 0.75rem;
max-height: 380px;
overflow-y: auto;
font-size: 0.9rem;
+ box-shadow: 0 12px 32px rgba(86, 57, 17, .06);
+ backdrop-filter: blur(12px);
}
.tree-folder {
@@ -17,30 +19,30 @@
align-items: center;
gap: 6px;
padding: 4px 6px;
- border-radius: 4px;
+ border-radius: 8px;
cursor: pointer;
user-select: none;
- color: rgba(255, 255, 255, 0.85);
- font-weight: 600;
+ color: var(--ink);
+ font-weight: 700;
}
.tree-folder-header:hover {
- background: rgba(255, 255, 255, 0.07);
+ background: rgba(255,255,255,.52);
}
.tree-folder-arrow {
font-size: 0.75rem;
- color: rgba(255, 255, 255, 0.5);
+ color: var(--muted);
width: 12px;
}
.tree-folder-name {
- color: rgba(255, 255, 255, 0.85);
+ color: var(--ink);
}
.tree-folder-children {
padding-left: 18px;
- border-left: 1px solid rgba(255, 255, 255, 0.08);
+ border-left: 1px solid var(--line);
margin-left: 6px;
}
@@ -53,18 +55,18 @@
align-items: center;
gap: 8px;
padding: 4px 6px;
- border-radius: 4px;
+ border-radius: 8px;
cursor: pointer;
- color: rgba(255, 255, 255, 0.7);
+ color: var(--muted);
}
.tree-file-label:hover {
- background: rgba(255, 255, 255, 0.06);
- color: rgba(255, 255, 255, 0.9);
+ background: rgba(255,255,255,.45);
+ color: var(--ink);
}
.tree-file-label input[type="checkbox"] {
- accent-color: var(--color-accent);
+ accent-color: var(--blue);
width: 14px;
height: 14px;
cursor: pointer;
@@ -79,11 +81,11 @@
.tree-error,
.tree-empty {
padding: 1rem;
- color: rgba(255, 255, 255, 0.5);
+ color: var(--muted);
font-size: 0.9rem;
text-align: center;
}
.tree-error {
- color: #ff7b72;
+ color: var(--red);
}
diff --git a/Mindforge.Web/src/components/FlashcardComponent.css b/Mindforge.Web/src/components/FlashcardComponent.css
index 4d9a453..0a8cd5e 100644
--- a/Mindforge.Web/src/components/FlashcardComponent.css
+++ b/Mindforge.Web/src/components/FlashcardComponent.css
@@ -2,27 +2,37 @@
width: 100%;
max-width: 900px;
margin: 0 auto;
- animation: slideUp 0.5s ease-out;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.flashcard-title {
- font-size: 2.5rem;
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: clamp(24px, 3.5vw, 40px);
+ font-weight: 800;
+ letter-spacing: -.03em;
+ color: var(--ink);
+ margin: 0;
+}
+
+.flashcard-subtitle {
+ color: var(--muted);
+ font-size: 15px;
+ line-height: 1.55;
+ max-width: 660px;
}
.flashcard-form {
display: flex;
flex-direction: column;
gap: 1.2rem;
- background: rgba(255, 255, 255, 0.06);
+ background: rgba(255, 250, 239, .68);
padding: 2rem;
- border-radius: 12px;
- border: 1px solid rgba(255, 255, 255, 0.14);
- box-shadow: 0 14px 36px rgba(0, 0, 0, 0.2);
- backdrop-filter: blur(10px);
- -webkit-backdrop-filter: blur(10px);
+ border-radius: var(--radius-lg);
+ border: 1px solid rgba(104, 69, 22, .13);
+ box-shadow: 0 18px 48px rgba(86, 57, 17, .09);
+ backdrop-filter: blur(16px);
}
.input-group {
@@ -34,12 +44,13 @@
.input-group label {
font-weight: 700;
- color: var(--color-text-creamy);
+ color: var(--ink);
+ font-size: 0.9rem;
}
.selection-meta {
font-size: 0.85rem;
- color: rgba(255, 255, 255, 0.55);
+ color: var(--muted);
margin-top: 0.4rem;
}
@@ -55,7 +66,7 @@
appearance: none;
width: 100%;
height: 8px;
- background: rgba(0, 0, 0, 0.3);
+ background: rgba(95, 72, 35, .14);
border-radius: 999px;
outline: none;
}
@@ -66,34 +77,37 @@
width: 22px;
height: 22px;
border-radius: 50%;
- background: #f4f5f5;
- border: 2px solid rgba(var(--color-accent-rgb), 0.9);
- box-shadow: 0 6px 16px rgba(var(--color-accent-rgb), 0.35);
+ background: var(--paper);
+ border: 2px solid var(--blue);
+ box-shadow: 0 6px 16px rgba(63, 124, 172, .25);
cursor: pointer;
}
.amount-display {
- font-weight: 700;
- color: var(--color-accent);
+ font-weight: 800;
+ color: var(--blue-deep);
min-width: 44px;
text-align: right;
font-size: 1.1rem;
}
.flashcard-error {
- color: #ff9c96;
- margin-top: 1rem;
+ color: var(--red);
+ font-size: 0.9rem;
+ background: rgba(183, 91, 77, 0.08);
+ border: 1px solid rgba(183, 91, 77, 0.2);
+ border-radius: var(--radius-md);
+ padding: 0.8rem 1rem;
}
.radio-group {
display: flex;
flex-wrap: wrap;
- background: rgba(0, 0, 0, 0.28);
+ background: rgba(95, 72, 35, .08);
padding: 4px;
- border-radius: 12px;
- border: 1px solid rgba(255, 255, 255, 0.12);
+ border-radius: var(--radius-md);
+ border: 1px solid var(--line);
width: fit-content;
- overflow: hidden;
gap: 4px;
}
@@ -115,54 +129,57 @@
justify-content: center;
padding: 0.8rem 1.2rem;
cursor: pointer;
- border-radius: 8px;
+ border-radius: 12px;
font-size: 0.95rem;
- font-weight: 700;
- color: rgba(255, 255, 255, 0.65);
- transition: all 0.25s ease;
+ font-weight: 800;
+ color: var(--muted);
+ transition: all .25s var(--ease);
white-space: nowrap;
}
.radio-item input[type="radio"]:checked + .radio-label {
- background: var(--color-accent);
- color: #012f3b;
- box-shadow: 0 4px 12px rgba(var(--color-accent-rgb), 0.35);
+ background: var(--blue);
+ color: #fff;
+ box-shadow: 0 8px 20px rgba(63, 124, 172, .25);
}
.radio-item:hover .radio-label {
- color: rgba(255, 255, 255, 0.92);
+ color: var(--ink);
}
-.spinner-container {
+.loading-indicator {
display: flex;
- flex-direction: column;
align-items: center;
+ gap: 10px;
justify-content: center;
- padding: 2rem 0;
- gap: 1rem;
+ padding: 2rem;
+ color: var(--muted);
+ font-size: 0.95rem;
}
-.spinner {
- width: 50px;
- height: 50px;
- border: 4px solid rgba(255, 255, 255, 0.12);
- border-left-color: var(--color-accent);
+.loading-dot {
+ width: 10px;
+ height: 10px;
border-radius: 50%;
- animation: spin 1s linear infinite;
+ background: var(--blue);
+ box-shadow: 0 0 0 4px rgba(63, 124, 172, .16);
}
.flashcard-result-panel {
- background: rgba(255, 255, 255, 0.06);
- border-radius: 12px;
- border: 1px solid rgba(255, 255, 255, 0.14);
+ background: rgba(255, 250, 239, .68);
+ border-radius: var(--radius-lg);
+ border: 1px solid rgba(104, 69, 22, .13);
padding: 1.5rem;
text-align: left;
+ box-shadow: 0 18px 48px rgba(86, 57, 17, .09);
+ backdrop-filter: blur(16px);
}
.flashcard-result-panel h3 {
margin: 0 0 1rem 0;
font-size: 1.1rem;
- color: rgba(255, 255, 255, 0.94);
+ font-family: Georgia, serif;
+ color: var(--ink);
}
.flashcard-result-list {
@@ -172,9 +189,9 @@
}
.flashcard-result-item {
- border: 1px solid rgba(255, 255, 255, 0.12);
- background: rgba(0, 0, 0, 0.2);
- border-radius: 10px;
+ border: 1px solid rgba(82, 54, 17, .12);
+ background: rgba(255,255,255,.52);
+ border-radius: var(--radius-md);
padding: 0.85rem 1rem;
}
@@ -184,10 +201,12 @@
align-items: center;
gap: 1rem;
font-size: 0.95rem;
+ color: var(--ink);
}
.flashcard-result-header span {
- color: rgba(255, 255, 255, 0.72);
+ color: var(--muted);
+ font-size: 0.85rem;
}
.flashcard-result-meta {
@@ -196,16 +215,7 @@
flex-wrap: wrap;
gap: 0.85rem;
font-size: 0.85rem;
- color: rgba(255, 255, 255, 0.65);
-}
-
-@keyframes spin {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(360deg);
- }
+ color: var(--muted);
}
@media (max-width: 720px) {
diff --git a/Mindforge.Web/src/components/FlashcardComponent.tsx b/Mindforge.Web/src/components/FlashcardComponent.tsx
index d487135..4f7f91c 100644
--- a/Mindforge.Web/src/components/FlashcardComponent.tsx
+++ b/Mindforge.Web/src/components/FlashcardComponent.tsx
@@ -49,8 +49,8 @@ export function FlashcardComponent() {
return (
-
Gerador de Flashcards
-
Selecione os arquivos do repositorio para gerar bibliotecas de flashcards.
+
Gerador de Flashcards
+
Selecione os arquivos do repositorio para gerar bibliotecas de flashcards.
@@ -111,7 +111,7 @@ export function FlashcardComponent() {
-
{loading && (
-
-
-
Gerando os flashcards com IA e salvando no banco. Aguarde...
+
+
+ Gerando os flashcards com IA e salvando no banco. Aguarde...
)}
diff --git a/Mindforge.Web/src/components/FlashcardReviewComponent.css b/Mindforge.Web/src/components/FlashcardReviewComponent.css
index cfa7b62..b732bf4 100644
--- a/Mindforge.Web/src/components/FlashcardReviewComponent.css
+++ b/Mindforge.Web/src/components/FlashcardReviewComponent.css
@@ -1,40 +1,47 @@
.review-container {
width: 100%;
- max-width: 900px;
- margin: 0 auto;
- display: flex;
- flex-direction: column;
- gap: 1.2rem;
- animation: slideUp 0.45s ease-out;
}
.review-title {
- font-size: 2.35rem;
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: clamp(24px, 3.5vw, 40px);
+ font-weight: 800;
+ letter-spacing: -.03em;
+ color: var(--ink);
+ margin: 0 0 0.3rem;
+}
+
+.review-subtitle {
+ color: var(--muted);
+ font-size: 15px;
+ line-height: 1.55;
+ max-width: 660px;
+ margin-bottom: 1.2rem;
}
.review-error {
- color: #ff9c96;
- text-align: left;
- background: rgba(255, 69, 58, 0.12);
- border: 1px solid rgba(255, 69, 58, 0.4);
- border-radius: 10px;
+ color: var(--red);
+ font-size: 0.9rem;
+ background: rgba(183, 91, 77, 0.08);
+ border: 1px solid rgba(183, 91, 77, 0.2);
+ border-radius: var(--radius-md);
padding: 0.8rem 1rem;
+ margin-bottom: 1rem;
}
-.review-select-panel,
-.review-session-panel {
- background: rgba(255, 255, 255, 0.06);
- border: 1px solid rgba(255, 255, 255, 0.14);
- border-radius: 12px;
- padding: 1.25rem;
+/* Library Selection */
+.review-select-panel {
+ background: rgba(255, 250, 239, .68);
+ border: 1px solid rgba(104, 69, 22, .13);
+ border-radius: var(--radius-lg);
+ padding: 1.5rem;
+ box-shadow: 0 18px 48px rgba(86, 57, 17, .09);
+ backdrop-filter: blur(16px);
text-align: left;
- backdrop-filter: blur(10px);
- -webkit-backdrop-filter: blur(10px);
- box-shadow: 0 14px 36px rgba(0, 0, 0, 0.18);
}
.review-state {
- color: rgba(255, 255, 255, 0.72);
+ color: var(--muted);
font-size: 0.95rem;
}
@@ -44,16 +51,17 @@
}
.review-subject-section {
- border: 1px solid rgba(255, 255, 255, 0.12);
- background: rgba(0, 0, 0, 0.18);
- border-radius: 10px;
+ border: 1px solid rgba(82, 54, 17, .10);
+ background: rgba(255,255,255,.45);
+ border-radius: var(--radius-md);
padding: 0.9rem;
}
.review-subject-section h3 {
margin: 0 0 0.8rem;
font-size: 1rem;
- color: rgba(255, 255, 255, 0.95);
+ font-family: Georgia, serif;
+ color: var(--ink);
}
.review-library-list {
@@ -65,18 +73,18 @@
display: flex;
align-items: flex-start;
gap: 0.7rem;
- border: 1px solid rgba(255, 255, 255, 0.08);
- border-radius: 10px;
+ border: 1px solid rgba(82, 54, 17, .10);
+ border-radius: var(--radius-md);
padding: 0.6rem 0.7rem;
cursor: pointer;
- background: rgba(255, 255, 255, 0.03);
+ background: rgba(255,255,255,.48);
}
.review-library-item input[type="checkbox"] {
margin-top: 0.15rem;
width: 16px;
height: 16px;
- accent-color: var(--color-accent);
+ accent-color: var(--blue);
}
.review-library-texts {
@@ -87,10 +95,11 @@
.review-library-texts strong {
font-size: 0.95rem;
+ color: var(--ink);
}
.review-library-texts span {
- color: rgba(255, 255, 255, 0.68);
+ color: var(--muted);
font-size: 0.84rem;
}
@@ -100,72 +109,548 @@
justify-content: flex-end;
}
-.review-progress {
- display: flex;
- align-items: center;
- gap: 0.9rem;
- margin-bottom: 0.8rem;
+/* Session Panel - Content Grid */
+.content-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 330px;
+ gap: 24px;
+ align-items: start;
}
-.review-progress span {
- font-size: 0.9rem;
- color: rgba(255, 255, 255, 0.8);
- min-width: 60px;
-}
-
-.review-progress-bar {
- width: 100%;
- height: 8px;
- border-radius: 999px;
- background: rgba(0, 0, 0, 0.25);
+/* Review Panel (main flashcard area) */
+.review-panel {
+ position: relative;
+ min-height: 650px;
+ padding: clamp(18px, 3vw, 34px);
+ border-radius: var(--radius-xl);
+ background:
+ linear-gradient(145deg, rgba(255,255,255,.54), rgba(255,240,202,.46)),
+ radial-gradient(circle at 15% 20%, rgba(255, 201, 101, .25), transparent 35%),
+ radial-gradient(circle at 90% 10%, rgba(63, 124, 172, .16), transparent 32%);
+ border: 1px solid rgba(104, 69, 22, .13);
+ box-shadow: var(--shadow);
overflow: hidden;
}
-.review-progress-fill {
- height: 100%;
- background: linear-gradient(90deg, rgba(var(--color-accent-rgb), 0.8), rgba(var(--color-accent-rgb), 1));
+.review-panel::before,
+.review-panel::after {
+ content: "";
+ position: absolute;
border-radius: 999px;
- transition: width 0.25s ease;
+ pointer-events: none;
+ filter: blur(2px);
+ opacity: .5;
}
-.review-card {
- border: 1px solid rgba(255, 255, 255, 0.16);
- border-radius: 10px;
- padding: 1rem;
- background: rgba(0, 0, 0, 0.2);
- min-height: 250px;
+.review-panel::before {
+ width: 220px;
+ height: 220px;
+ right: -92px;
+ top: 100px;
+ background: rgba(63, 124, 172, .13);
}
-.review-card header {
- margin-bottom: 0.8rem;
+.review-panel::after {
+ width: 180px;
+ height: 180px;
+ left: -70px;
+ bottom: 60px;
+ background: rgba(199, 149, 57, .18);
}
-.review-card small {
- color: rgba(255, 255, 255, 0.62);
+.session-header {
+ position: relative;
+ z-index: 1;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 16px;
+ margin-bottom: 24px;
}
-.review-card h3 {
- margin: 0 0 0.4rem;
- font-size: 0.98rem;
- color: rgba(255, 255, 255, 0.9);
+.session-title h3 {
+ margin: 0;
+ font-family: Georgia, serif;
+ font-size: clamp(24px, 3vw, 36px);
+ letter-spacing: -.04em;
+ color: var(--ink);
}
-.review-card p {
- margin: 0 0 1rem;
- font-size: 1rem;
+.session-title p {
+ max-width: 660px;
+ color: var(--muted);
+ font-size: 15px;
+ line-height: 1.55;
+ margin: 4px 0 0;
+}
+
+.score-pill {
+ min-width: 76px;
+ padding: 11px 12px;
+ border-radius: 18px;
+ background: rgba(255,255,255,.55);
+ border: 1px solid rgba(100, 65, 18, .12);
+ text-align: center;
+ box-shadow: inset 0 1px 0 rgba(255,255,255,.72);
+}
+
+.score-pill b {
+ display: block;
+ font-size: 19px;
+ color: var(--ink);
+}
+
+.score-pill span {
+ color: var(--muted);
+ font-size: 10px;
+ font-weight: 950;
+ letter-spacing: .12em;
+ text-transform: uppercase;
+}
+
+/* Stage (flashcard container) */
+.stage {
+ position: relative;
+ z-index: 1;
+ display: grid;
+ place-items: center;
+ min-height: 390px;
+ perspective: 1400px;
+ padding: 22px 0;
+}
+
+/* Flashcard */
+.flashcard {
+ position: relative;
+ width: min(680px, 100%);
+ min-height: 355px;
+ cursor: pointer;
+ transform-style: preserve-3d;
+ transition: transform .78s var(--ease), filter .35s var(--ease);
+ outline: none;
+}
+
+.flashcard:hover .card-face {
+ border-color: rgba(63, 124, 172, .28);
+}
+
+.flashcard.is-flipped {
+ transform: rotateY(180deg);
+}
+
+.flashcard.is-reviewed {
+ animation: cardExit .58s var(--ease);
+}
+
+.card-face {
+ position: absolute;
+ inset: 0;
+ min-height: 355px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ padding: clamp(24px, 5vw, 42px);
+ border-radius: 30px;
+ backface-visibility: hidden;
+ background:
+ linear-gradient(90deg, rgba(168, 111, 36, .09) 0 1px, transparent 1px 22px),
+ linear-gradient(rgba(168, 111, 36, .08) 0 1px, transparent 1px 30px),
+ linear-gradient(145deg, #fffaf0, #f5dfaa);
+ border: 1px solid rgba(82, 54, 17, .18);
+ box-shadow: var(--card-shadow), inset 0 0 0 8px rgba(255,255,255,.24);
+ overflow: hidden;
+}
+
+.card-face::before {
+ content: "";
+ position: absolute;
+ inset: 18px;
+ border: 1px dashed rgba(82, 54, 17, .18);
+ border-radius: 22px;
+ pointer-events: none;
+}
+
+.card-face::after {
+ content: "";
+ position: absolute;
+ width: 140px;
+ height: 140px;
+ right: -46px;
+ bottom: -50px;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(199,149,57,.25), transparent 66%);
+ pointer-events: none;
+}
+
+.card-back {
+ transform: rotateY(180deg);
+ background:
+ linear-gradient(90deg, rgba(63,124,172,.08) 0 1px, transparent 1px 22px),
+ linear-gradient(rgba(63,124,172,.07) 0 1px, transparent 1px 30px),
+ linear-gradient(145deg, #fffaf1, #dfeef2);
+}
+
+.card-meta {
+ position: relative;
+ z-index: 1;
+ display: flex;
+ justify-content: space-between;
+ gap: 12px;
+ align-items: center;
+ color: var(--muted);
+ font-size: 12px;
+ font-weight: 950;
+ letter-spacing: .12em;
+ text-transform: uppercase;
+}
+
+.tag {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 9px 12px;
+ border-radius: 999px;
+ background: rgba(255,255,255,.54);
+ border: 1px solid rgba(82, 54, 17, .12);
+}
+
+.card-question,
+.card-answer {
+ position: relative;
+ z-index: 1;
+ display: grid;
+ align-content: center;
+ gap: 12px;
+ min-height: 190px;
+ color: var(--ink);
+ font-family: "Segoe UI", Inter, Avenir, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
+ font-size: clamp(17px, 2.2vw, 20px);
line-height: 1.55;
}
-.review-session-actions {
- margin-top: 1rem;
+.card-footer {
+ position: relative;
+ z-index: 1;
display: flex;
- gap: 0.65rem;
- flex-wrap: wrap;
+ justify-content: space-between;
+ gap: 14px;
+ align-items: center;
+ color: var(--muted);
+ font-size: 13px;
+ font-weight: 800;
}
-@media (max-width: 740px) {
- .review-session-actions {
- display: grid;
+.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;
+}
+
+/* Controls */
+.controls {
+ position: relative;
+ z-index: 1;
+ display: flex;
+ justify-content: center;
+ gap: 16px;
+ flex-wrap: wrap;
+ opacity: .36;
+ transform: translateY(10px);
+ pointer-events: none;
+ transition: .35s var(--ease);
+ margin-top: 20px;
+}
+
+.controls.ready {
+ opacity: 1;
+ transform: translateY(0);
+ pointer-events: auto;
+}
+
+.review-button {
+ position: relative;
+ min-width: 170px;
+ min-height: 60px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ border: 0;
+ border-radius: 20px;
+ color: white;
+ font-family: inherit;
+ font-weight: 950;
+ font-size: 16px;
+ cursor: pointer;
+ box-shadow: 0 16px 34px rgba(63, 44, 20, .18), inset 0 1px 0 rgba(255,255,255,.32);
+ overflow: hidden;
+ transition: .25s var(--ease);
+}
+
+.review-button.correct {
+ background: linear-gradient(135deg, var(--green), var(--green-deep));
+}
+
+.review-button.wrong {
+ background: linear-gradient(135deg, var(--red), var(--red-deep));
+}
+
+.review-button:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 22px 42px rgba(63, 44, 20, .22), inset 0 1px 0 rgba(255,255,255,.32);
+}
+
+.review-button::before {
+ content: "";
+ position: absolute;
+ inset: -90% -40%;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,.32), transparent);
+ transform: rotate(20deg) translateX(-80%);
+ transition: .55s var(--ease);
+}
+
+.review-button:hover::before {
+ transform: rotate(20deg) translateX(80%);
+}
+
+.review-button:disabled {
+ opacity: 0.45;
+ cursor: not-allowed;
+ transform: none !important;
+}
+
+/* Stamp */
+.stamp {
+ position: absolute;
+ right: 38px;
+ top: 36px;
+ z-index: 4;
+ padding: 12px 18px;
+ border: 4px double currentColor;
+ border-radius: 10px;
+ font-family: Georgia, serif;
+ font-size: 26px;
+ font-weight: 900;
+ letter-spacing: .08em;
+ text-transform: uppercase;
+ opacity: 0;
+ transform: rotate(-12deg) scale(1.3);
+ pointer-events: none;
+}
+
+.stamp.correct {
+ color: var(--green-deep);
+}
+
+.stamp.wrong {
+ color: var(--red-deep);
+}
+
+.stamp.show {
+ animation: stampIn .7s var(--ease);
+}
+
+/* Side Panel */
+.side-panel {
+ display: grid;
+ gap: 18px;
+}
+
+.panel-card {
+ padding: 20px;
+ border-radius: 26px;
+ background: rgba(255, 250, 239, .68);
+ border: 1px solid rgba(104, 69, 22, .13);
+ box-shadow: 0 18px 48px rgba(86, 57, 17, .09);
+ backdrop-filter: blur(16px);
+}
+
+.panel-card h3 {
+ margin: 0 0 14px;
+ font-family: Georgia, serif;
+ font-size: 22px;
+ letter-spacing: -.03em;
+ color: var(--ink);
+}
+
+.stat-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 10px;
+}
+
+.stat {
+ padding: 14px;
+ border-radius: 18px;
+ background: rgba(255,255,255,.52);
+ border: 1px solid rgba(82, 54, 17, .10);
+}
+
+.stat b {
+ display: block;
+ font-size: 24px;
+ letter-spacing: -.04em;
+ color: var(--ink);
+}
+
+.stat span {
+ color: var(--muted);
+ font-size: 11px;
+ font-weight: 950;
+ letter-spacing: .10em;
+ text-transform: uppercase;
+}
+
+.track {
+ height: 14px;
+ border-radius: 999px;
+ overflow: hidden;
+ background: rgba(80, 54, 18, .12);
+ margin-top: 8px;
+}
+
+.track span {
+ display: block;
+ height: 100%;
+ border-radius: inherit;
+ background: linear-gradient(90deg, var(--red), var(--gold), var(--green));
+}
+
+.queue {
+ display: grid;
+ gap: 10px;
+}
+
+.queue-item {
+ display: grid;
+ grid-template-columns: 38px minmax(0, 1fr) auto;
+ gap: 10px;
+ align-items: center;
+ padding: 10px;
+ border-radius: 16px;
+ background: rgba(255,255,255,.48);
+ border: 1px solid rgba(82, 54, 17, .10);
+}
+
+.queue-item strong {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ color: var(--ink);
+ font-size: 13px;
+}
+
+.queue-item span {
+ color: var(--muted);
+ font-size: 11px;
+}
+
+.queue-number {
+ width: 38px;
+ height: 38px;
+ display: grid;
+ place-items: center;
+ border-radius: 13px;
+ background: #fff5d8;
+ border: 1px solid rgba(82, 54, 17, .12);
+ color: #74531c;
+ font-family: Georgia, serif;
+ font-weight: 900;
+ font-size: 14px;
+}
+
+/* Confetti Canvas */
+.confetti-canvas {
+ position: fixed;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 9999;
+}
+
+/* Keyframes */
+@keyframes cardExit {
+ 0% { transform: translateX(0) rotateY(180deg) rotateZ(0); opacity: 1; }
+ 45% { transform: translateX(28px) rotateY(180deg) rotateZ(2deg); opacity: .9; }
+ 100% { transform: translateX(-32px) rotateY(180deg) rotateZ(-2deg); opacity: 0; }
+}
+
+@keyframes stampIn {
+ 0% { opacity: 0; transform: rotate(-18deg) scale(1.8); }
+ 38% { opacity: 1; transform: rotate(-10deg) scale(.9); }
+ 58% { transform: rotate(-12deg) scale(1.04); }
+ 100% { opacity: 0; transform: rotate(-12deg) scale(1); }
+}
+
+/* Session End */
+.session-end {
+ text-align: center;
+ padding: 3rem 1rem;
+ color: var(--muted);
+}
+
+.session-end h3 {
+ font-family: Georgia, serif;
+ font-size: 28px;
+ color: var(--ink);
+ margin: 0 0 0.5rem;
+}
+
+/* Navigation buttons row */
+.review-nav-row {
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+ margin-top: 16px;
+ justify-content: center;
+}
+
+@media (max-width: 1120px) {
+ .content-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .side-panel {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
+
+@media (max-width: 760px) {
+ .session-header {
+ flex-direction: column;
+ }
+
+ .review-panel {
+ min-height: auto;
+ }
+
+ .stage {
+ min-height: 420px;
+ }
+
+ .flashcard,
+ .card-face {
+ min-height: 380px;
+ }
+
+ .side-panel {
+ grid-template-columns: 1fr;
+ }
+
+ .review-button {
+ width: 100%;
+ }
+
+ .controls {
+ flex-direction: column;
+ align-items: stretch;
+ }
+}
diff --git a/Mindforge.Web/src/components/FlashcardReviewComponent.tsx b/Mindforge.Web/src/components/FlashcardReviewComponent.tsx
index 6bc9c60..77c5c07 100644
--- a/Mindforge.Web/src/components/FlashcardReviewComponent.tsx
+++ b/Mindforge.Web/src/components/FlashcardReviewComponent.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useMemo, useState } from 'preact/hooks';
+import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
import {
MindforgeApiService,
type FlashcardCard,
@@ -26,15 +26,93 @@ function difficultyLabel(difficulty: string) {
function shuffleCards(cards: FlashcardCard[]) {
const shuffled = [...cards];
-
for (let i = shuffled.length - 1; i > 0; i--) {
const randomIndex = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[randomIndex]] = [shuffled[randomIndex], shuffled[i]];
}
-
return shuffled;
}
+interface ConfettiParticle {
+ x: number;
+ y: number;
+ vx: number;
+ vy: number;
+ color: string;
+ size: number;
+ life: number;
+ maxLife: number;
+ rotation: number;
+ rotationSpeed: number;
+}
+
+function fireConfetti(canvas: HTMLCanvasElement) {
+ const ctx = canvas.getContext('2d');
+ if (!ctx) return;
+ const c = ctx;
+
+ const w = canvas.width = window.innerWidth;
+ const h = canvas.height = window.innerHeight;
+
+ const colors = ['#4f8f5a', '#3f7cac', '#c79539', '#7e65a8', '#f2dfb3', '#b75b4d'];
+ const particles: ConfettiParticle[] = [];
+
+ for (let i = 0; i < 120; i++) {
+ particles.push({
+ x: Math.random() * w,
+ y: -20 - Math.random() * h * 0.5,
+ vx: (Math.random() - 0.5) * 6,
+ vy: Math.random() * 5 + 2,
+ color: colors[Math.floor(Math.random() * colors.length)],
+ size: Math.random() * 8 + 4,
+ life: 0,
+ maxLife: 80 + Math.random() * 60,
+ rotation: Math.random() * Math.PI * 2,
+ rotationSpeed: (Math.random() - 0.5) * 0.3,
+ });
+ }
+
+ let animating = true;
+
+ function animate() {
+ if (!animating) return;
+ c.clearRect(0, 0, w, h);
+
+ let alive = 0;
+ for (const p of particles) {
+ p.life++;
+ if (p.life >= p.maxLife) continue;
+ alive++;
+ p.x += p.vx;
+ p.y += p.vy;
+ p.vy += 0.08;
+ p.vx *= 0.995;
+ p.rotation += p.rotationSpeed;
+ const alpha = 1 - p.life / p.maxLife;
+ c.save();
+ c.globalAlpha = alpha;
+ c.translate(p.x, p.y);
+ c.rotate(p.rotation);
+ c.fillStyle = p.color;
+ c.fillRect(-p.size / 2, -p.size / 4, p.size, p.size / 2);
+ c.restore();
+ }
+
+ if (alive > 0) {
+ requestAnimationFrame(animate);
+ } else {
+ c.clearRect(0, 0, w, h);
+ animating = false;
+ }
+ }
+
+ requestAnimationFrame(animate);
+
+ return () => {
+ animating = false;
+ };
+}
+
export function FlashcardReviewComponent() {
const [libraries, setLibraries] = useState
([]);
const [selectedLibraryIds, setSelectedLibraryIds] = useState([]);
@@ -45,6 +123,13 @@ export function FlashcardReviewComponent() {
const [currentIndex, setCurrentIndex] = useState(0);
const [showAnswer, setShowAnswer] = useState(false);
const [submittingAnswer, setSubmittingAnswer] = useState(false);
+ const [cardExiting, setCardExiting] = useState(false);
+ const [stampState, setStampState] = useState<'correct' | 'wrong' | null>(null);
+ const [flipped, setFlipped] = useState(false);
+ const [sessionAnswers, setSessionAnswers] = useState>({});
+
+ const confettiRef = useRef(null);
+ const flashcardRef = useRef(null);
useEffect(() => {
let cancelled = false;
@@ -75,6 +160,12 @@ export function FlashcardReviewComponent() {
};
}, []);
+ useEffect(() => {
+ if (showAnswer && flipped) return;
+ if (!showAnswer && !flipped) return;
+ setFlipped(showAnswer);
+ }, [showAnswer, flipped]);
+
const groupedLibraries = useMemo(() => groupLibrariesBySubject(libraries), [libraries]);
const libraryById = useMemo(() => {
@@ -86,12 +177,34 @@ export function FlashcardReviewComponent() {
? ((currentIndex + 1) / sessionCards.length) * 100
: 0;
+ useEffect(() => {
+ function handleKeyDown(e: KeyboardEvent) {
+ if (sessionCards.length === 0 || !currentCard) return;
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLSelectElement || e.target instanceof HTMLTextAreaElement) return;
+
+ if (e.code === 'Space' || e.code === 'Enter') {
+ e.preventDefault();
+ if (!showAnswer) {
+ setShowAnswer(true);
+ }
+ } else if (e.code === 'KeyC' && showAnswer && !submittingAnswer) {
+ e.preventDefault();
+ registerReviewAnswer(true);
+ } else if (e.code === 'KeyW' && showAnswer && !submittingAnswer) {
+ e.preventDefault();
+ registerReviewAnswer(false);
+ }
+ }
+
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [sessionCards.length, showAnswer, submittingAnswer, currentCard]);
+
const toggleLibrary = (libraryId: number) => {
if (selectedLibraryIds.includes(libraryId)) {
setSelectedLibraryIds(selectedLibraryIds.filter((id) => id !== libraryId));
return;
}
-
setSelectedLibraryIds([...selectedLibraryIds, libraryId]);
};
@@ -112,6 +225,10 @@ export function FlashcardReviewComponent() {
setSessionCards(shuffleCards(response.cards));
setCurrentIndex(0);
setShowAnswer(false);
+ setFlipped(false);
+ setStampState(null);
+ setCardExiting(false);
+ setSessionAnswers({});
} catch (err: any) {
setError(err?.message || 'Falha ao iniciar sessao de revisao.');
} finally {
@@ -124,48 +241,84 @@ export function FlashcardReviewComponent() {
setCurrentIndex(0);
setShowAnswer(false);
setSubmittingAnswer(false);
+ setFlipped(false);
+ setStampState(null);
+ setCardExiting(false);
+ setSessionAnswers({});
};
const goToPrevious = () => {
- if (currentIndex === 0) {
- return;
- }
+ if (currentIndex === 0) return;
setCurrentIndex(currentIndex - 1);
setShowAnswer(false);
+ setFlipped(false);
+ setStampState(null);
+ };
+
+ const advanceCard = () => {
+ if (currentIndex >= sessionCards.length - 1) {
+ endSession();
+ return;
+ }
+ setCurrentIndex(currentIndex + 1);
+ setShowAnswer(false);
+ setFlipped(false);
+ setStampState(null);
};
const registerReviewAnswer = async (correct: boolean) => {
- if (!currentCard) {
- return;
- }
+ if (!currentCard) return;
setSubmittingAnswer(true);
setError(null);
+ if (correct) {
+ setStampState('correct');
+ if (confettiRef.current) {
+ fireConfetti(confettiRef.current);
+ }
+ } else {
+ setStampState('wrong');
+ }
+
+ setCardExiting(true);
+
+ setTimeout(() => {
+ setCardExiting(false);
+ setStampState(null);
+ }, 600);
+
try {
await MindforgeApiService.recordFlashcardReviewAnswer({
cardId: currentCard.id,
correct,
});
- if (currentIndex >= sessionCards.length - 1) {
- endSession();
- return;
- }
+ setSessionAnswers((currentAnswers) => ({
+ ...currentAnswers,
+ [currentCard.id]: correct,
+ }));
- setCurrentIndex(currentIndex + 1);
- setShowAnswer(false);
+ setTimeout(() => {
+ advanceCard();
+ setSubmittingAnswer(false);
+ }, 580);
} catch (err: any) {
setError(err?.message || 'Falha ao registrar resposta da revisao.');
- } finally {
setSubmittingAnswer(false);
+ setCardExiting(false);
+ setStampState(null);
}
};
+ const correctCount = Object.values(sessionAnswers).filter(Boolean).length;
+ const remainingCount = sessionCards.length - currentIndex;
+
return (
-
Revisao Flashcards
-
Escolha as bibliotecas para estudar e inicie uma sessao de revisao.
+
+
Revisao Flashcards
+
Escolha as bibliotecas para estudar e inicie uma sessao de revisao.
{error &&
{error}
}
@@ -212,57 +365,135 @@ export function FlashcardReviewComponent() {
)}
{sessionCards.length > 0 && currentCard && (
-
-
-
{currentIndex + 1} / {sessionCards.length}
-
-
+
+
+
+
+
+
{ if (!showAnswer && !submittingAnswer) setShowAnswer(true); }}
+ tabIndex={0}
+ role="button"
+ aria-label={showAnswer ? 'Flashcard revelado' : 'Clique ou pressione Espaco para revelar'}
+ >
+
+ {stampState && (
+
+ {stampState === 'correct' ? 'Correto!' : 'Errado'}
+
+ )}
+
+ {libraryById.get(currentCard.libraryId)?.subject || 'Geral'}
+ {difficultyLabel(libraryById.get(currentCard.libraryId)?.difficulty || 'Easy')}
+
+
+ {currentCard.front}
+
+
+
+
+
+
+ Resposta
+ {difficultyLabel(libraryById.get(currentCard.libraryId)?.difficulty || 'Easy')}
+
+
+ {currentCard.back}
+
+
+
+
+
+
+
+ registerReviewAnswer(true)}
+ disabled={!showAnswer || submittingAnswer}
+ >
+ Correto
+
+ registerReviewAnswer(false)}
+ disabled={!showAnswer || submittingAnswer}
+ >
+ Incorreto
+
+
+
+
+
+ Anterior
+
+
+ Encerrar Sessao
+
-
-
-
- {libraryById.get(currentCard.libraryId)?.fileName || 'Arquivo'} -
- {' '}
- {libraryById.get(currentCard.libraryId)?.subject || 'Geral'}
-
-
- Frente
- {currentCard.front}
- {showAnswer && (
- <>
- Verso
- {currentCard.back}
- >
- )}
-
+
+
+
Progresso
+
+
+ {currentIndex + 1}/{sessionCards.length}
+ Atual
+
+
+ {correctCount}
+ Corretos
+
+
+
+
+
+
-
-
- Anterior
-
+
+
Fila
+
+ {sessionCards.slice(currentIndex, currentIndex + 5).map((card, idx) => (
+
+ {currentIndex + idx + 1}
+ {card.front.substring(0, 40)}{card.front.length > 40 ? '...' : ''}
+ {libraryById.get(card.libraryId)?.subject || ''}
+
+ ))}
+ {remainingCount > 5 && (
+
+ +{remainingCount - 5} restantes
+
+ )}
+
+
+
+
+ )}
- {!showAnswer && (
-
setShowAnswer(true)}>
- Revelar Resposta
-
- )}
-
- {showAnswer && (
- <>
-
registerReviewAnswer(true)} disabled={submittingAnswer}>
- Acertei
-
-
registerReviewAnswer(false)} disabled={submittingAnswer}>
- Errei
-
- >
- )}
-
-
- Encerrar Sessao
-
+ {sessionCards.length > 0 && !currentCard && (
+
+
Sessao concluida!
+
Todos os cards foram revisados.
+
+ Voltar a selecao
)}
diff --git a/Mindforge.Web/src/components/Header.css b/Mindforge.Web/src/components/Header.css
index f4a3d40..1236fc9 100644
--- a/Mindforge.Web/src/components/Header.css
+++ b/Mindforge.Web/src/components/Header.css
@@ -1,61 +1,72 @@
-.header {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- height: 70px;
- background-color: var(--color-header);
- /* Imposing black with glassy effect */
- background: rgba(15, 15, 15, 0.85);
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
+.topbar {
+ min-height: 72px;
display: flex;
align-items: center;
- justify-content: center;
- z-index: 1000;
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
- border-bottom: 1px solid rgba(255, 255, 255, 0.05);
+ justify-content: space-between;
+ gap: 18px;
+ padding: 14px 18px;
+ margin-bottom: 24px;
+ background: rgba(255, 250, 239, .72);
+ border: 1px solid rgba(104, 69, 22, .13);
+ border-radius: 26px;
+ backdrop-filter: blur(18px);
+ box-shadow: 0 18px 50px rgba(86, 57, 17, .08);
}
-.header-content {
- display: flex;
- justify-content: center;
- align-items: center;
- width: 100%;
- position: relative;
-}
-
-.header-repo {
- position: absolute;
- right: 24px;
+.topbar-left {
display: flex;
align-items: center;
- gap: 6px;
- background: rgba(255, 255, 255, 0.06);
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 6px;
- padding: 4px 10px;
+ gap: 12px;
+ cursor: pointer;
+ min-width: 0;
}
-.header-repo-icon {
- font-size: 0.9rem;
- color: rgba(255, 255, 255, 0.5);
+.topbar-logo {
+ width: 42px;
+ height: 42px;
+ border-radius: 12px;
+ flex-shrink: 0;
}
-.header-repo-name {
- font-size: 0.8rem;
- color: rgba(255, 255, 255, 0.65);
- font-family: var(--font-main);
- letter-spacing: 0.5px;
-}
-
-.header-title {
- color: var(--color-text-creamy);
- font-family: var(--font-main);
- font-size: 1.8rem;
- font-weight: 700;
- letter-spacing: 2px;
- text-transform: uppercase;
+.topbar-title {
margin: 0;
- text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
+ font-family: Georgia, "Times New Roman", serif;
+ font-size: clamp(24px, 3vw, 34px);
+ font-weight: 800;
+ letter-spacing: -.04em;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ color: var(--ink);
+}
+
+.topbar-right {
+ flex-shrink: 0;
+}
+
+.chip {
+ min-height: 42px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 9px;
+ padding: 0 14px;
+ border-radius: 999px;
+ border: 1px solid rgba(96, 67, 28, .15);
+ background: rgba(255,255,255,.62);
+ color: #4b3b27;
+ font-weight: 850;
+ font-size: 13px;
+ box-shadow: inset 0 1px 0 rgba(255,255,255,.72);
+}
+
+@media (max-width: 760px) {
+ .topbar {
+ align-items: flex-start;
+ flex-direction: column;
+ }
+
+ .topbar-title {
+ white-space: normal;
+ }
}
diff --git a/Mindforge.Web/src/components/Header.tsx b/Mindforge.Web/src/components/Header.tsx
index f36bb47..624dcee 100644
--- a/Mindforge.Web/src/components/Header.tsx
+++ b/Mindforge.Web/src/components/Header.tsx
@@ -16,19 +16,19 @@ export function Header({ onGoHome }: HeaderProps) {
}, []);
return (
-