ashish-soni08 Codex commited on
Commit
5cf774c
·
1 Parent(s): ac53235

feat: add Josephine personalized study dashboard

Browse files

Deliver the German-first interactive StudyFlow frontend with daily briefing, materials, study canvas, calendar, learning plan, knowledge gaps, flashcards, quiz, reminders, and local-first privacy cues.

Co-authored-by: Codex <codex@openai.com>

app.js ADDED
@@ -0,0 +1,456 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const assets = {
2
+ calendar: "assets/materials/mq3zhq9n-clipboard.png",
3
+ slide: "assets/materials/mq3zhqat-clipboard.png",
4
+ notes: "assets/materials/mq3zhqbu-clipboard.png",
5
+ evidence: "assets/materials/mq3zhqdk-clipboard.png",
6
+ recent: [
7
+ "assets/materials/mq3zhqe0-clipboard.png",
8
+ "assets/materials/mq3zhqe3-clipboard.png",
9
+ "assets/materials/mq3zhqea-clipboard.png",
10
+ "assets/materials/mq3zhqeg-clipboard.png"
11
+ ]
12
+ };
13
+
14
+ const routes = ["overview", "uploads", "canvas", "calendar", "learning-plan", "gaps", "flashcards", "quiz", "reminders"];
15
+ const app = document.querySelector("#app");
16
+ const navList = document.querySelector("#navList");
17
+ const toast = document.querySelector("#toast");
18
+
19
+ const pageHead = (title, subtitle, actions = "") => `
20
+ <header class="page-head">
21
+ <div><h1>${title}</h1><p>${subtitle}</p></div>
22
+ <div class="head-actions">${actions}</div>
23
+ </header>`;
24
+
25
+ const panel = (title, body, extra = "", className = "") => `
26
+ <section class="panel ${className}">
27
+ <div class="panel-head"><h2>${title}</h2>${extra || '<span class="more">...</span>'}</div>
28
+ ${body}
29
+ </section>`;
30
+
31
+ function overviewPage() {
32
+ const agenda = [
33
+ ["09:00", "Mehrsprachigkeitsdidaktik", "Seminar im Hörsaal 3 · Sprachbewusstsein & Transferstrategien", "book-open", "green"],
34
+ ["11:30", "Englischdidaktik", "Übung · Classroom Interaction & Feedback", "globe", "blue"],
35
+ ["13:00", "Mittagspause", "Zeit für gutes Essen und einen kurzen Spaziergang am Rhein", "fork-knife", "rose"],
36
+ ["15:00", "Lernblock Bildungspsychologie", "Kapitel 4: Motivation & Selbstregulation · Karteikarten erstellen", "brain", "aubergine"],
37
+ ["17:30", "Familie & Freunde", "Abendessen bei Mama & Papa", "users-three", "gold"]
38
+ ].map(([time, title, sub, icon, tone]) => `
39
+ <button class="day-entry ${tone}" data-route="${time === "15:00" ? "canvas" : "calendar"}">
40
+ <time>${time}</time>
41
+ <span class="day-icon"><i class="ph ph-${icon}"></i></span>
42
+ <span><strong>${title}</strong><small>${sub}</small></span>
43
+ <i class="ph ph-caret-right entry-arrow"></i>
44
+ </button>`).join("");
45
+
46
+ return `
47
+ <div class="josephine-home">
48
+ <section class="josephine-hero">
49
+ <img src="assets/hero/josephine-mainz-morning.png" alt="Josephine lernt an einem sonnigen Morgen in Mainz, umgeben von Erinnerungen an Familie, Freunde und ihr Lieblingspferd.">
50
+ <div class="weather-line">Dienstag, 7. Juni <span>·</span> Mainz <span>·</span> 18° <i class="ph ph-sun"></i></div>
51
+ <div class="hero-copy">
52
+ <h1>Josephine<br>Wendland</h1>
53
+ <p class="hero-motto">Lehren. Verstehen. Verbinden.</p>
54
+ <h2>Guten Morgen, Josephine</h2>
55
+ <p>Heute ist ein guter Tag, etwas zu lernen,<br>zu wachsen und Menschen zu inspirieren.</p>
56
+ </div>
57
+ </section>
58
+
59
+ <section class="daily-journal">
60
+ <div class="journal-agenda">
61
+ <header><i class="ph ph-calendar-blank"></i><h2>Mein Tag</h2><span>7. Juni</span></header>
62
+ <div class="day-list">${agenda}</div>
63
+ </div>
64
+ <aside class="today-important">
65
+ <header><i class="ph ph-star"></i><h2>Heute wichtig</h2></header>
66
+ <button class="important-row deadline" data-route="calendar">
67
+ <span class="important-icon"><i class="ph ph-calendar-dots"></i></span>
68
+ <span><small>Deadline</small><strong>Reflexionsbericht Mehrsprachigkeit</strong><em>Abgabe: Freitag, 10. Juni</em></span>
69
+ <i class="ph ph-caret-right"></i>
70
+ </button>
71
+ <button class="important-row gap" data-route="gaps">
72
+ <span class="important-icon"><i class="ph ph-target"></i></span>
73
+ <span><small>Wissenslücke</small><strong>Selbstwirksamkeit verstehen</strong><em>Wiederholen · Karte 24–27</em></span>
74
+ <i class="ph ph-caret-right"></i>
75
+ </button>
76
+ <button class="important-row next" data-route="canvas">
77
+ <span class="important-icon"><i class="ph ph-book-open"></i></span>
78
+ <span><small>Nächster Schritt</small><strong>Lernen fortsetzen</strong><em>Bildungspsychologie · Kapitel 4</em></span>
79
+ <i class="ph ph-caret-right"></i>
80
+ </button>
81
+ <p class="journal-note">Kleine Schritte jeden Tag<br>führen zu großen Veränderungen.</p>
82
+ </aside>
83
+ </section>
84
+ </div>`;
85
+ }
86
+
87
+ function uploadsPage() {
88
+ const extracted = `Sprachbewusstheit fördern (VL 07)
89
+
90
+ Begriff: Fähigkeit, Sprache(n) als System(e) wahrzunehmen und über Sprache(n) nachzudenken.
91
+
92
+ Ziele:
93
+ - Sensibilisierung für sprachliche Strukturen und Variation
94
+ - Vergleich von Sprachen als Ressourcen nutzen
95
+ - Transferstrategien entwickeln
96
+
97
+ Vergleich DE - FR
98
+ DE: Ich gehe in die Schule.
99
+ FR: Je vais à l'école.
100
+
101
+ Gemeinsamkeiten: Subjekt - Verb - Objekt
102
+ Unterschiede: Artikel, Präpositionen, Akkordanz
103
+
104
+ Didaktische Prinzipien:
105
+ 1) Sprachenvergleich
106
+ 2) Bewusstmachung
107
+ 3) Funktionaler Einsatz im Fachunterricht`;
108
+
109
+ return pageHead("Materialien", "Notizen, Folien und Kalender an einem ruhigen Ort.", `
110
+ <button class="button secondary" id="addFiles">Dateien hinzufügen</button>
111
+ <button class="button primary" data-route="canvas">In Arbeitsfläche öffnen</button>
112
+ `) + `
113
+ <div class="uploads-grid">
114
+ ${panel("Upload-Queue (3)", `<div class="queue-list">
115
+ <div class="queue-item selected"><div class="queue-title"><strong>Mehrsprachigkeitsdidaktik_VL07.pdf</strong><span class="badge green">100%</span></div><div class="progress"><span style="width:100%"></span></div><small>OCR abgeschlossen · 32 Seiten</small></div>
116
+ <div class="queue-item"><div class="queue-title"><strong>Englischdidaktik_Orthografie.pdf</strong><span class="badge blue">76%</span></div><div class="progress"><span style="width:76%;background:var(--blue)"></span></div><small>OCR läuft · Seite 3 von 11</small></div>
117
+ <div class="queue-item"><div class="queue-title"><strong>Bildungspsychologie_Notizen.jpg</strong><span class="badge">0%</span></div><div class="progress"><span style="width:0%"></span></div><small>Wartet in der Queue</small></div>
118
+ <div class="dropzone" id="dropzone"><strong>Dateien hier ablegen</strong><br><small>PDF, JPG oder PNG · Verarbeitung bleibt lokal</small></div>
119
+ </div>`, '<span class="badge green">Lokal</span>', "upload-queue")}
120
+ ${panel("Mehrsprachigkeitsdidaktik_VL07.pdf", `<div class="doc-review">
121
+ <div class="doc-pane"><div class="doc-toolbar"><button class="icon-button">‹</button><span>4 / 32</span><button class="icon-button">›</button><span>100%</span><button class="icon-button">↗</button></div><div class="document-page"><img src="${assets.slide}" alt="Vorlesungsfolie zu Scaffolding im mehrsprachigen Unterricht"></div></div>
122
+ <div class="note-pane"><img src="${assets.notes}" alt="Handschriftliche Notizen zu Scaffolding im mehrsprachigen Unterricht"></div>
123
+ </div>`, '<span class="badge blue">Seite 4 verknüpft</span>')}
124
+ ${panel("OCR-Extraktion", `<div class="panel-body ocr-panel">
125
+ <div class="language-tabs"><button class="badge green">Deutsch 99%</button><button class="badge blue">Französisch 82%</button><button class="badge">Englisch 12%</button></div>
126
+ <div class="confidence">Konfidenz der Seite: <strong>93%</strong> · 7 Markierungen prüfen</div>
127
+ <label class="field"><span class="label">Extrahierter Text</span><textarea id="ocrText">${extracted}</textarea></label>
128
+ <div class="row" style="justify-content:space-between"><span class="badge amber">Niedrige Konfidenz: Akkordanz</span><button class="button secondary" id="reviewAll">Alle prüfen</button></div>
129
+ <div class="ocr-actions"><button class="button secondary">Verwerfen</button><button class="button primary" id="saveOcr">Übernehmen &amp; speichern</button></div>
130
+ <div class="trust-inline">Speichern in: Mehrsprachigkeitsdidaktik · nur lokal</div>
131
+ </div>`, '<span class="badge green">Abgeschlossen</span>')}
132
+ </div>`;
133
+ }
134
+
135
+ function canvasPage() {
136
+ const thumbs = [assets.recent[0], assets.recent[1], assets.slide, assets.recent[2], assets.recent[3]]
137
+ .map((src, i) => `<button class="slide-thumb ${i === 2 ? "active" : ""}" data-slide="${i}"><img src="${src}" alt="Materialvorschau ${i + 1}"></button>`).join("");
138
+ return `<div class="canvas-page">
139
+ <header class="canvas-head">
140
+ <h1>Smart Study Canvas <span>Mehrsprachigkeitsdidaktik / Woche 7</span></h1>
141
+ <div class="head-actions"><button class="button secondary">Verknüpfungen</button><button class="button secondary">Notiz hinzufügen</button><button class="button secondary">Exportieren</button></div>
142
+ </header>
143
+ <div class="canvas-grid">
144
+ ${panel("Vorlesungsfolien", `<div class="slide-workspace"><div class="slide-thumbs">${thumbs}</div><div class="slide-main"><img id="mainSlide" src="${assets.slide}" alt="Vorlesungsfolie Scaffolding"></div></div>`)}
145
+ ${panel("Handschriftliche Notizen", `<div class="notes-main"><img src="${assets.notes}" alt="Maya Hoffmanns handschriftliche Scaffolding-Notizen"></div>`, '<span class="badge blue">4 semantische Links</span>')}
146
+ ${panel("AI-Copilot", `<div class="chat"><div class="messages" id="messages">
147
+ <div class="message user">Wie hängen Translanguaging und Scaffolding zusammen?</div>
148
+ <div class="message ai"><strong>Maya</strong><br>Scaffolding schafft temporäre sprachliche und kognitive Stützen. Translanguaging erlaubt Lernenden, ihr gesamtes sprachliches Repertoire für Bedeutungsbildung zu nutzen. Zusammen fördern sie fachliche Teilhabe, ohne die Zielsprache auszublenden.<div class="language-tabs"><span class="badge green">Folie 14</span><span class="badge blue">Notiz S. 1</span></div></div>
149
+ <div class="alert-card"><strong>Fehlendes Konzept erkannt: Interlanguage</strong>Die Unterscheidung zur Interlanguage fehlt in deinen verknüpften Notizen.<div class="language-tabs"><button class="badge">Konzept erklären</button><button class="badge">Quelle suchen</button><button class="badge">Karte erstellen</button></div></div>
150
+ </div><form class="chat-compose" id="chatForm"><input id="chatInput" placeholder="Stelle eine Frage zu deinen Materialien..." required><button class="button primary" aria-label="Senden">➤</button></form></div>`, '<span class="badge green">Lokal · Quellengebunden</span>')}
151
+ </div>
152
+ </div>`;
153
+ }
154
+
155
+ function calendarPage() {
156
+ const cells = Array.from({length: 35}, () => "<div></div>").join("");
157
+ const roadmapRows = [
158
+ ["Mehrsprachigkeitsdidaktik","on,on,on,,blue,blue,,"],
159
+ ["Englischdidaktik","blue,blue,,on,on,,,"],
160
+ ["Französischdidaktik",",amber,amber,,on,on,,"],
161
+ ["Bildungspsychologie","on,,blue,blue,,on,on,on"]
162
+ ].map(([name, states]) => `<b>${name}</b>${states.split(",").map(s => `<span class="${s}"></span>`).join("")}`).join("");
163
+ return pageHead("Kalender & Lernplan", "Geprüfte Termine, realistische Lernblöcke und dein 8-Tage-Fokus.", `
164
+ <button class="button secondary" id="reviewExtractions">Extraktionen prüfen · 3</button>
165
+ <button class="button primary" id="planBlock">Lernblock planen</button>
166
+ `) + `
167
+ <div class="calendar-grid">
168
+ ${panel("Wochenplan · 7.-11. Juni", `<div class="week-grid">
169
+ <div></div>${["Di 7.","Mi 8.","Do 9.","Fr 10.","Sa 11."].map(d=>`<div class="day">${d}</div>`).join("")}
170
+ ${["09:00","10:00","11:00","12:00","13:00","14:00","15:00"].map(t=>`<div class="time">${t}</div>`).join("")}${cells}
171
+ <div class="event" style="grid-column:2;grid-row:2/4"><strong>09:00</strong><br>Mehrsprachigkeitsdidaktik<br>Seminar</div>
172
+ <div class="event amber" style="grid-column:3;grid-row:3/5"><strong>10:00</strong><br>Hausarbeit<br>Quellenarbeit</div>
173
+ <div class="event blue" style="grid-column:4;grid-row:4/6"><strong>11:00</strong><br>Französischdidaktik<br>Séance 5</div>
174
+ <div class="event" style="grid-column:5;grid-row:2/5"><strong>09:00</strong><br>Fachdidaktik Deutsch<br>Portfolio</div>
175
+ <div class="event blue" style="grid-column:6;grid-row:3/5"><strong>10:00</strong><br>Lerngruppe<br>Bibliothek</div>
176
+ </div><div class="roadmap"><h3>8-Tage-Prüfungsroadmap</h3><div class="roadmap-grid"><b></b>${["Di 7","Mi 8","Do 9","Fr 10","Sa 11","So 12","Mo 13","Di 14"].map(d=>`<b>${d}</b>`).join("")}${roadmapRows}</div><p class="muted" style="font-size:10px">Fokus: Hausarbeit abschließen → Französischdidaktik wiederholen → Prüfungssimulation</p></div>`, '<span class="badge green">72% geplant</span>', "week-calendar")}
177
+ <div class="stack">
178
+ ${panel("Heute · 5 Std. 30 Min.", `<div class="panel-body"><div class="today-block"><strong>09:00-10:30 · Seminarfolien</strong><small>Translanguaging und Scaffolding verknüpfen</small></div><div class="today-block"><strong>11:30-13:00 · Reading response</strong><small>Englischdidaktik · Kapitel 4</small></div><div class="today-block"><strong>15:00-16:30 · Retrieval Practice</strong><small>Bildungspsychologie · Lernkarten 31-54</small></div></div>`, '<span class="badge green">Im Plan</span>')}
179
+ ${panel("Geprüfte Extraktionen", `<div class="panel-body"><div class="extraction-row"><span>10. Juni</span><strong>Hausarbeit abgeben</strong><span class="badge amber">Bestätigt</span></div><div class="extraction-row"><span>14. Juni</span><strong>Französischdidaktik Prüfung</strong><span class="badge blue">Kalender</span></div><div class="extraction-row"><span>19. Juni</span><strong>Portfolio Deutsch</strong><span class="badge green">Bestätigt</span></div><img class="calendar-asset" src="${assets.calendar}" alt="Handschriftlicher Kalender Juni 2025"></div>`, '<span class="badge">3 aus PDF</span>')}
180
+ </div>
181
+ </div>`;
182
+ }
183
+
184
+ function flashcardsPage() {
185
+ const decks = [
186
+ ["Mehrsprachigkeitsdidaktik",48,"18 fällig · zuletzt heute",72],
187
+ ["Englischdidaktik",36,"Nächste Wiederholung morgen",58],
188
+ ["Französischdidaktik",42,"Nächste Wiederholung morgen",44],
189
+ ["Bildungspsychologie",61,"Nächste Wiederholung morgen",81],
190
+ ["Fachdidaktik Deutsch",27,"Nächste Wiederholung morgen",36]
191
+ ].map(([name,count,sub,pct],i)=>`<div class="deck-row ${i===0?"selected":""}" data-deck="${name}"><strong>${name} · ${count} Karten</strong><small>${sub}</small><div class="progress"><span style="width:${pct}%"></span></div></div>`).join("");
192
+ return pageHead("Karteikarten", "Quellengebundene Karten bearbeiten, lernen und exportieren.", `
193
+ <button class="button secondary" id="generateCards">Aus Quellen generieren</button>
194
+ <button class="button primary" id="learnCards">12 Karten lernen</button>
195
+ `) + `
196
+ <div class="cards-grid">
197
+ ${panel("Meine Decks", `<div>${decks}</div>`)}
198
+ ${panel("Karte bearbeiten · 12 von 48", `<div class="panel-body">
199
+ <div class="field question"><label for="cardQuestion">Frage</label><textarea id="cardQuestion">Wie unterscheidet sich Translanguaging vom klassischen Code-Switching?</textarea></div>
200
+ <div class="field answer"><label for="cardAnswer">Antwort</label><textarea id="cardAnswer">Translanguaging beschreibt die flexible Nutzung des gesamten sprachlichen Repertoires zur Bedeutungsbildung. Code-Switching bezeichnet dagegen meist den situativen Wechsel zwischen getrennt gedachten Sprachen.</textarea></div>
201
+ <div class="field"><label>Quellen</label><div class="source-box">▣ Mehrsprachigkeitsdidaktik_Woche_7.pdf · Folie 14<br>▤ Notizen_Scaffolding_03-06.jpg · Seite 1</div><div class="evidence-strip"><img src="${assets.evidence}" alt="Quellenausschnitt aus einer Notiz"><img src="${assets.notes}" alt="Handschriftliche Evidenz zu Scaffolding"></div></div>
202
+ <div class="language-tabs"><span class="badge green">Translanguaging</span><span class="badge blue">Code-Switching</span><span class="badge">Woche 7</span></div>
203
+ <div class="row" style="justify-content:space-between"><button class="button danger" id="deleteCard">Karte löschen</button><button class="button primary" id="saveCard">Änderungen speichern</button></div>
204
+ </div>`)}
205
+ ${panel("Vorschau & Export", `<div class="panel-body stack"><div class="card-preview" id="cardPreview"><div class="front"><small>Vorderseite</small><strong>Wie unterscheidet sich Translanguaging vom klassischen Code-Switching?</strong><span class="muted">Klicken zum Umdrehen</span></div><div class="back"><small>Rückseite</small><p>Translanguaging nutzt das gesamte sprachliche Repertoire flexibel. Code-Switching beschreibt den situativen Wechsel zwischen getrennt gedachten Sprachen.</p></div></div><div class="divider"></div><h3 style="font-family:var(--serif);margin:2px 0">Deck exportieren</h3><span class="muted" style="font-size:11px">48 Karten · Quellen enthalten · DE</span><div class="export-list"><button class="button" data-export="Anki">Anki (.apkg)</button><button class="button" data-export="CSV">CSV</button><button class="button" data-export="PDF">PDF zum Drucken</button></div><small class="muted">Letzte Generierung: heute, 10:42 · vollständig lokal</small></div>`)}
206
+ </div>`;
207
+ }
208
+
209
+ function quizPage() {
210
+ return pageHead("Quizmodus", "Prüfungssimulation · Mehrsprachigkeitsdidaktik", `<button class="button secondary" id="endQuiz">Simulation beenden</button>`) + `
211
+ <div class="quiz-grid">
212
+ ${panel("Frage 7 von 12", `<div class="quiz-content">
213
+ <div class="quiz-meta"><span class="muted">Fortschritt · <span id="quizProgressText">58%</span></span><span class="badge amber">Schwierigkeit: Anspruchsvoll</span></div>
214
+ <div class="progress"><span id="quizProgress" style="width:58%"></span></div>
215
+ <h2 class="quiz-question">Welche Aussage beschreibt den didaktischen Nutzen von Translanguaging am präzisesten?</h2>
216
+ <div class="answers" id="answers">
217
+ <button class="answer-option" data-answer="A"><span class="answer-letter">A</span><span>Es verhindert den Wechsel zwischen Unterrichtssprachen vollständig.</span></button>
218
+ <button class="answer-option" data-answer="B"><span class="answer-letter">B</span><span><strong>Es aktiviert das gesamte sprachliche Repertoire zur Bedeutungsbildung und fachlichen Teilhabe.</strong></span></button>
219
+ <button class="answer-option" data-answer="C"><span class="answer-letter">C</span><span>Es ersetzt fachsprachliche Lernziele durch alltagssprachliche Kommunikation.</span></button>
220
+ <button class="answer-option" data-answer="D"><span class="answer-letter">D</span><span>Es ist ausschließlich für den Fremdsprachenunterricht geeignet.</span></button>
221
+ </div>
222
+ <div class="explanation" id="quizExplanation" hidden><h3 id="answerState">Richtig beantwortet</h3><p>Translanguaging nutzt vorhandene sprachliche Ressourcen gezielt als Brücke zum fachlichen Verstehen. Die Zielsprache bleibt wichtig, wird aber nicht als isoliertes System behandelt.</p><div class="source-citation">Quelle: Mehrsprachigkeitsdidaktik_Woche_7.pdf · Folie 14<br>„Dynamische Nutzung des gesamten sprachlichen Repertoires zur Bedeutungskonstruktion.“</div><div class="row" style="justify-content:space-between;margin-top:14px"><small class="muted" id="chosenAnswer"></small><button class="button primary" id="nextQuestion">Nächste Frage →</button></div></div>
223
+ </div>`, '<span class="more">...</span>', "quiz-card")}
224
+ <div class="stack">
225
+ ${panel("Simulationsstatus", `<div class="panel-body"><div class="stats-grid"><div class="stat"><strong id="correctCount">5</strong><small>richtig</small></div><div class="stat"><strong id="wrongCount">1</strong><small>falsch</small></div><div class="stat"><strong id="hitRate">82%</strong><small>Trefferquote</small></div><div class="stat"><strong id="timer">06:42</strong><small>verbleibend</small></div></div></div>`)}
226
+ ${panel("Schwierigkeit", `<div class="panel-body"><div class="difficulty"><button data-level="Adaptiv">Adaptiv</button><button data-level="Mittel">Mittel</button><button class="active" data-level="Anspruchsvoll">Anspruchsvoll</button></div><p class="muted" style="font-size:11px">Die nächsten Fragen gewichten deine Wissenslücken stärker.</p></div>`)}
227
+ ${panel("Prüfungsfokus", `<div class="panel-body"><div class="focus-list"><div><span class="badge red">Lücke</span> Interlanguage</div><div><span class="badge amber">Prüfen</span> Scaffolding</div><div><span class="badge green">Sicher</span> Translanguaging</div></div><div class="divider" style="margin:14px 0"></div><small class="muted">12 Fragen · 15 Minuten<br>Nur aus 3 ausgewählten lokalen Quellen</small></div>`)}
228
+ </div>
229
+ </div>`;
230
+ }
231
+
232
+ function learningPlanPage() {
233
+ return pageHead("Mein Lernplan", "Ein realistischer Plan, der zu Josephines Woche passt.", `
234
+ <button class="button secondary" data-route="calendar">Kalender ansehen</button>
235
+ <button class="button primary" data-route="canvas">Lernblock starten</button>
236
+ `) + `
237
+ <div class="quiet-page">
238
+ <section class="plan-intro">
239
+ <span class="eyebrow">Prüfung in 8 Tagen</span>
240
+ <h2>Diese Woche zählt Beständigkeit,<br>nicht Perfektion.</h2>
241
+ <p>StudyFlow verbindet deine Termine, Materialien und offenen Themen zu kleinen, machbaren Schritten.</p>
242
+ </section>
243
+ <div class="plan-days">
244
+ <button data-route="canvas"><time>Heute</time><strong>Grundlagen wiederholen</strong><span>Mehrsprachigkeitsdidaktik · 75 Min.</span></button>
245
+ <button data-route="flashcards"><time>Morgen</time><strong>Scaffolding vertiefen</strong><span>Folien, Notizen und 12 Karten · 60 Min.</span></button>
246
+ <button data-route="quiz"><time>Donnerstag</time><strong>Formative Bewertung üben</strong><span>Quiz und Reflexion · 45 Min.</span></button>
247
+ <button data-route="gaps"><time>Freitag</time><strong>Wissenslücken schließen</strong><span>Selbstwirksamkeit und Interlanguage · 60 Min.</span></button>
248
+ </div>
249
+ </div>`;
250
+ }
251
+
252
+ function gapsPage() {
253
+ return pageHead("Wissenslücken", "Was noch unsicher ist, wird hier verständlich und machbar.", `
254
+ <button class="button primary" data-route="quiz">Kurzes Quiz starten</button>
255
+ `) + `
256
+ <div class="quiet-page two-column">
257
+ <section class="focus-story">
258
+ <span class="eyebrow">Heute empfohlen</span>
259
+ <h2>Selbstwirksamkeit verstehen</h2>
260
+ <p>In deinen Notizen fehlt noch die Verbindung zwischen Selbstwirksamkeit, Motivation und Lernerfolg.</p>
261
+ <button class="button primary" data-route="canvas">Mit Quellen erklären</button>
262
+ </section>
263
+ <section class="calm-list">
264
+ <button data-route="canvas"><span class="signal red"></span><span><strong>Interlanguage</strong><small>Nur indirekt in drei Quellen erklärt</small></span><em>Hoch</em></button>
265
+ <button data-route="flashcards"><span class="signal amber"></span><span><strong>Scaffolding</strong><small>Zwei unsichere Karten</small></span><em>Mittel</em></button>
266
+ <button data-route="canvas"><span class="signal green"></span><span><strong>Translanguaging</strong><small>Quellenlage vollständig</small></span><em>Sicher</em></button>
267
+ </section>
268
+ </div>`;
269
+ }
270
+
271
+ function remindersPage() {
272
+ return pageHead("Erinnerungen", "Freundliche Hinweise, damit Wichtiges nicht im Alltag untergeht.", `
273
+ <button class="button primary" id="newReminder">Erinnerung hinzufügen</button>
274
+ `) + `
275
+ <div class="quiet-page reminders-page">
276
+ <section class="reminder-date"><span>Heute</span><strong>7. Juni</strong><p>Du hast einen ruhigen Abend ohne Studienfrist.</p></section>
277
+ <section class="calm-list">
278
+ <button data-route="calendar"><span class="important-icon"><i class="ph ph-file-text"></i></span><span><strong>Reflexionsbericht Mehrsprachigkeit</strong><small>Freitag, 10. Juni · 23:59</small></span><em>In 3 Tagen</em></button>
279
+ <button data-route="calendar"><span class="important-icon"><i class="ph ph-presentation-chart"></i></span><span><strong>Französische Präsentation</strong><small>Montag, 13. Juni · 10:00</small></span><em>In 6 Tagen</em></button>
280
+ <button data-route="learning-plan"><span class="important-icon"><i class="ph ph-book-open"></i></span><span><strong>Bildungspsychologie wiederholen</strong><small>Morgen · 15:00</small></span><em>Geplant</em></button>
281
+ </section>
282
+ </div>`;
283
+ }
284
+
285
+ const renderers = {
286
+ overview: overviewPage,
287
+ uploads: uploadsPage,
288
+ canvas: canvasPage,
289
+ calendar: calendarPage,
290
+ "learning-plan": learningPlanPage,
291
+ gaps: gapsPage,
292
+ flashcards: flashcardsPage,
293
+ quiz: quizPage,
294
+ reminders: remindersPage
295
+ };
296
+
297
+ function normalizeRoute() {
298
+ const route = location.hash.replace("#/", "").replace("#", "");
299
+ return routes.includes(route) ? route : "overview";
300
+ }
301
+
302
+ function navigate(route) {
303
+ if (!routes.includes(route)) return;
304
+ if (location.hash !== `#/${route}`) location.hash = `#/${route}`;
305
+ else render();
306
+ }
307
+
308
+ function render() {
309
+ const route = normalizeRoute();
310
+ app.innerHTML = renderers[route]();
311
+ navList.querySelectorAll("[data-route]").forEach(btn => btn.classList.toggle("active", btn.dataset.route === route));
312
+ document.title = `${route === "overview" ? "Heute" : app.querySelector("h1")?.textContent || "StudyFlow"} · StudyFlow AI`;
313
+ closeMenu();
314
+ app.focus({ preventScroll: true });
315
+ bindPage(route);
316
+ }
317
+
318
+ function bindPage(route) {
319
+ app.querySelectorAll("[data-route]").forEach(el => el.addEventListener("click", () => navigate(el.dataset.route)));
320
+ if (route === "uploads") bindUploads();
321
+ if (route === "canvas") bindCanvas();
322
+ if (route === "calendar") bindCalendar();
323
+ if (route === "flashcards") bindFlashcards();
324
+ if (route === "quiz") bindQuiz();
325
+ if (route === "reminders") {
326
+ document.querySelector("#newReminder")?.addEventListener("click", () => showToast("Neue Erinnerung wird lokal angelegt."));
327
+ }
328
+ app.querySelector("[data-action='open-plan']")?.addEventListener("click", () => navigate("calendar"));
329
+ }
330
+
331
+ function bindUploads() {
332
+ const dropzone = document.querySelector("#dropzone");
333
+ ["dragenter", "dragover"].forEach(type => dropzone.addEventListener(type, event => { event.preventDefault(); dropzone.classList.add("drag"); }));
334
+ ["dragleave", "drop"].forEach(type => dropzone.addEventListener(type, event => { event.preventDefault(); dropzone.classList.remove("drag"); }));
335
+ dropzone.addEventListener("drop", () => showToast("Datei zur lokalen Queue hinzugefügt."));
336
+ document.querySelector("#addFiles").addEventListener("click", () => showToast("Dateiauswahl geöffnet. Demo bleibt lokal."));
337
+ document.querySelector("#reviewAll").addEventListener("click", () => {
338
+ document.querySelector("#ocrText").focus();
339
+ showToast("7 unsichere Stellen sind zur Prüfung markiert.");
340
+ });
341
+ document.querySelector("#saveOcr").addEventListener("click", () => showToast("OCR-Text lokal gespeichert und verknüpft."));
342
+ }
343
+
344
+ function bindCanvas() {
345
+ const mainSlide = document.querySelector("#mainSlide");
346
+ const slideAssets = [assets.recent[0], assets.recent[1], assets.slide, assets.recent[2], assets.recent[3]];
347
+ document.querySelectorAll(".slide-thumb").forEach(button => button.addEventListener("click", () => {
348
+ document.querySelectorAll(".slide-thumb").forEach(item => item.classList.remove("active"));
349
+ button.classList.add("active");
350
+ mainSlide.src = slideAssets[Number(button.dataset.slide)];
351
+ }));
352
+ document.querySelector("#chatForm").addEventListener("submit", event => {
353
+ event.preventDefault();
354
+ const input = document.querySelector("#chatInput");
355
+ const text = input.value.trim();
356
+ if (!text) return;
357
+ const messages = document.querySelector("#messages");
358
+ messages.insertAdjacentHTML("beforeend", `<div class="message user">${escapeHtml(text)}</div><div class="message ai"><strong>Maya</strong><br>Ich prüfe diese Frage gegen die lokal verknüpften Folien und Notizen. Die relevanten Stellen liegen auf Folie 14 und in deiner Scaffolding-Notiz.</div>`);
359
+ input.value = "";
360
+ messages.scrollTop = messages.scrollHeight;
361
+ });
362
+ }
363
+
364
+ function bindCalendar() {
365
+ document.querySelector("#planBlock").addEventListener("click", () => showToast("Neuer Lernblock für 17:15 vorgeschlagen."));
366
+ document.querySelector("#reviewExtractions").addEventListener("click", () => showToast("Drei bestätigte Termine aus lokalen Quellen."));
367
+ }
368
+
369
+ function bindFlashcards() {
370
+ const preview = document.querySelector("#cardPreview");
371
+ preview.addEventListener("click", () => preview.classList.toggle("flipped"));
372
+ document.querySelector("#saveCard").addEventListener("click", () => showToast("Karte lokal gespeichert."));
373
+ document.querySelector("#deleteCard").addEventListener("click", () => showToast("Karte zur Löschung vorgemerkt."));
374
+ document.querySelector("#generateCards").addEventListener("click", () => showToast("6 Kartenvorschläge aus zwei Quellen erzeugt."));
375
+ document.querySelector("#learnCards").addEventListener("click", () => showToast("Lernsession mit 12 fälligen Karten gestartet."));
376
+ document.querySelectorAll("[data-export]").forEach(button => button.addEventListener("click", () => showToast(`${button.dataset.export}-Export lokal vorbereitet.`)));
377
+ document.querySelectorAll(".deck-row").forEach(row => row.addEventListener("click", () => {
378
+ document.querySelectorAll(".deck-row").forEach(item => item.classList.remove("selected"));
379
+ row.classList.add("selected");
380
+ showToast(`${row.dataset.deck} ausgewählt.`);
381
+ }));
382
+ }
383
+
384
+ function bindQuiz() {
385
+ const options = [...document.querySelectorAll(".answer-option")];
386
+ const explanation = document.querySelector("#quizExplanation");
387
+ options.forEach(option => option.addEventListener("click", () => {
388
+ options.forEach(item => item.classList.remove("selected", "wrong"));
389
+ const correct = option.dataset.answer === "B";
390
+ option.classList.add(correct ? "selected" : "wrong");
391
+ explanation.hidden = false;
392
+ document.querySelector("#answerState").textContent = correct ? "Richtig beantwortet" : "Noch nicht richtig";
393
+ document.querySelector("#answerState").style.color = correct ? "var(--emerald-dark)" : "var(--red)";
394
+ document.querySelector("#chosenAnswer").textContent = `Antwort gewählt · ${option.dataset.answer}`;
395
+ }));
396
+ document.querySelectorAll("[data-level]").forEach(button => button.addEventListener("click", () => {
397
+ document.querySelectorAll("[data-level]").forEach(item => item.classList.remove("active"));
398
+ button.classList.add("active");
399
+ showToast(`Schwierigkeit auf ${button.dataset.level} gesetzt.`);
400
+ }));
401
+ document.querySelector("#nextQuestion")?.addEventListener("click", () => {
402
+ document.querySelector(".panel-head h2").textContent = "Frage 8 von 12";
403
+ document.querySelector("#quizProgress").style.width = "67%";
404
+ document.querySelector("#quizProgressText").textContent = "67%";
405
+ showToast("Nächste Frage geladen.");
406
+ });
407
+ document.querySelector("#endQuiz").addEventListener("click", () => showToast("Simulation pausiert. Dein Stand bleibt lokal gespeichert."));
408
+ }
409
+
410
+ function showToast(message) {
411
+ toast.textContent = message;
412
+ toast.classList.add("show");
413
+ clearTimeout(showToast.timer);
414
+ showToast.timer = setTimeout(() => toast.classList.remove("show"), 2600);
415
+ }
416
+
417
+ function escapeHtml(value) {
418
+ return value.replace(/[&<>"']/g, char => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;" }[char]));
419
+ }
420
+
421
+ const sidebar = document.querySelector("#sidebar");
422
+ const scrim = document.querySelector("#scrim");
423
+ const menuButton = document.querySelector("#menuButton");
424
+ function closeMenu() {
425
+ sidebar.classList.remove("open");
426
+ scrim.classList.remove("show");
427
+ menuButton.setAttribute("aria-expanded", "false");
428
+ }
429
+ menuButton.addEventListener("click", () => {
430
+ const open = sidebar.classList.toggle("open");
431
+ scrim.classList.toggle("show", open);
432
+ menuButton.setAttribute("aria-expanded", String(open));
433
+ });
434
+ scrim.addEventListener("click", closeMenu);
435
+ navList.addEventListener("click", event => {
436
+ const button = event.target.closest("[data-route]");
437
+ if (button) navigate(button.dataset.route);
438
+ });
439
+
440
+ document.querySelector("#askButton").addEventListener("click", () => document.querySelector("#aiDialog").showModal());
441
+ document.querySelector("#aiSubmit").addEventListener("click", () => {
442
+ const prompt = document.querySelector("#aiPrompt").value.trim();
443
+ showToast(prompt ? "Lokale Antwort wird aus 12 Materialien erzeugt." : "Bitte gib zuerst eine Frage ein.");
444
+ });
445
+ document.querySelector("#globalSearch").addEventListener("keydown", event => {
446
+ if (event.key === "Enter" && event.currentTarget.value.trim()) showToast(`Lokale Suche nach „${event.currentTarget.value.trim()}“`);
447
+ });
448
+ document.addEventListener("keydown", event => {
449
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
450
+ event.preventDefault();
451
+ document.querySelector("#globalSearch").focus();
452
+ }
453
+ if (event.key === "Escape") closeMenu();
454
+ });
455
+ window.addEventListener("hashchange", render);
456
+ render();
assets/hero/josephine-mainz-morning.png ADDED

Git LFS Details

  • SHA256: 1f9bac80d4321680f668becf4f7ba5711d6aac958e2219ac173e00eaa8258332
  • Pointer size: 132 Bytes
  • Size of remote file: 2.26 MB
assets/materials/mq3zhq9n-clipboard.png ADDED

Git LFS Details

  • SHA256: 7ed2d92859ebbca3247a59fa97b07ecc91f860ce954f37aab340d54b2fc68d20
  • Pointer size: 131 Bytes
  • Size of remote file: 339 kB
assets/materials/mq3zhqat-clipboard.png ADDED

Git LFS Details

  • SHA256: e170adfd92afa5ef3055d895faacea99a590e0ea134eb4d01993f2f804c73b87
  • Pointer size: 131 Bytes
  • Size of remote file: 165 kB
assets/materials/mq3zhqbu-clipboard.png ADDED

Git LFS Details

  • SHA256: da4801419cb8ceac497e566bfe8fbca8fad2d422ed35dcb382aaf456628a6125
  • Pointer size: 131 Bytes
  • Size of remote file: 317 kB
assets/materials/mq3zhqdk-clipboard.png ADDED
assets/materials/mq3zhqe0-clipboard.png ADDED
assets/materials/mq3zhqe3-clipboard.png ADDED
assets/materials/mq3zhqea-clipboard.png ADDED
assets/materials/mq3zhqeg-clipboard.png ADDED
index.html ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="de">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="color-scheme" content="light">
7
+ <title>StudyFlow AI</title>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600&family=DM+Sans:wght@400;500;600&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@phosphor-icons/web@2.1.1/src/regular/style.css">
12
+ <link rel="stylesheet" href="styles.css">
13
+ </head>
14
+ <body>
15
+ <div class="app-shell">
16
+ <aside class="sidebar" id="sidebar" aria-label="Hauptnavigation">
17
+ <div class="brand">
18
+ <span class="brand-mark" aria-hidden="true"><i class="ph ph-feather"></i></span>
19
+ <span class="brand-name">StudyFlow AI</span>
20
+ </div>
21
+
22
+ <div class="profile">
23
+ <span class="avatar">JW</span>
24
+ <span><strong>Josephine Wendland</strong><small>Lehramt · Mainz</small></span>
25
+ <button class="icon-button" aria-label="Profil öffnen"><i class="ph ph-caret-down"></i></button>
26
+ </div>
27
+
28
+ <nav class="nav-list" id="navList">
29
+ <button data-route="overview"><i class="ph ph-house"></i>Heute</button>
30
+ <button data-route="uploads"><i class="ph ph-folder"></i>Materialien</button>
31
+ <button data-route="canvas"><i class="ph ph-chalkboard-teacher"></i>Arbeitsfläche</button>
32
+ <button data-route="calendar"><i class="ph ph-calendar-blank"></i>Kalender</button>
33
+ <button data-route="learning-plan"><i class="ph ph-book-open"></i>Lernplan</button>
34
+ <button data-route="gaps"><i class="ph ph-target"></i>Wissenslücken</button>
35
+ <button data-route="flashcards"><i class="ph ph-cards"></i>Karten</button>
36
+ <button data-route="quiz"><i class="ph ph-question"></i>Quiz</button>
37
+ <button data-route="reminders"><i class="ph ph-heart"></i>Erinnerungen</button>
38
+ </nav>
39
+
40
+ <div class="local-status">
41
+ <strong><span class="status-dot"></span>Lokale Verarbeitung aktiv</strong>
42
+ <span>Deine Daten bleiben auf diesem Gerät.</span>
43
+ </div>
44
+ </aside>
45
+
46
+ <div class="app-main">
47
+ <header class="topbar">
48
+ <button class="menu-button" id="menuButton" aria-label="Navigation öffnen" aria-expanded="false">☰</button>
49
+ <span class="today">Dienstag, 7. Juni · Mainz</span>
50
+ <label class="global-search">
51
+ <span aria-hidden="true">⌕</span>
52
+ <input id="globalSearch" type="search" placeholder="Material, Kurs oder Begriff suchen">
53
+ <kbd>⌘ K</kbd>
54
+ </label>
55
+ <button class="ask-button" id="askButton"><i class="ph ph-sparkle"></i> KI fragen</button>
56
+ <button class="icon-button notification" aria-label="Benachrichtigungen"><i class="ph ph-bell"></i><span>3</span></button>
57
+ <span class="user-label">Josephine Wendland · Lehramt</span>
58
+ </header>
59
+ <main id="app" tabindex="-1"></main>
60
+ </div>
61
+ </div>
62
+
63
+ <div class="scrim" id="scrim"></div>
64
+ <div class="toast" id="toast" role="status" aria-live="polite"></div>
65
+
66
+ <dialog id="aiDialog">
67
+ <form method="dialog" class="dialog-card">
68
+ <div class="dialog-head">
69
+ <div><small>Lokaler KI-Copilot</small><h2>Frage an deine Materialien</h2></div>
70
+ <button class="icon-button" value="cancel" aria-label="Schließen">×</button>
71
+ </div>
72
+ <label>Deine Frage<textarea id="aiPrompt" rows="4" placeholder="Zum Beispiel: Erkläre den Unterschied zwischen Translanguaging und Code-Switching."></textarea></label>
73
+ <p class="trust-note">Die Antwort wird ausschließlich aus deinen lokalen Quellen erzeugt.</p>
74
+ <div class="dialog-actions">
75
+ <button class="button secondary" value="cancel">Abbrechen</button>
76
+ <button class="button primary" id="aiSubmit" value="cancel">Antwort erzeugen</button>
77
+ </div>
78
+ </form>
79
+ </dialog>
80
+
81
+ <script src="app.js"></script>
82
+ </body>
83
+ </html>
styles.css ADDED
@@ -0,0 +1,656 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --sidebar: #071f29;
3
+ --sidebar-2: #0a2b34;
4
+ --paper: #fbfcfa;
5
+ --surface: #ffffff;
6
+ --ink: #102126;
7
+ --muted: #647278;
8
+ --line: #d9e0dd;
9
+ --line-soft: #e9eeeb;
10
+ --emerald: #0c9656;
11
+ --emerald-dark: #086a42;
12
+ --emerald-soft: #eaf6ef;
13
+ --blue: #2369b3;
14
+ --blue-soft: #edf5ff;
15
+ --amber: #b56700;
16
+ --amber-soft: #fff6e5;
17
+ --red: #bb3e36;
18
+ --red-soft: #fff0ee;
19
+ --radius: 7px;
20
+ --serif: Georgia, "Times New Roman", serif;
21
+ --sans: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
22
+ --mono: "Cascadia Mono", Consolas, monospace;
23
+ }
24
+
25
+ * { box-sizing: border-box; }
26
+ html, body { margin: 0; min-height: 100%; background: var(--paper); color: var(--ink); font-family: var(--sans); }
27
+ body { font-size: 14px; }
28
+ button, input, textarea, select { font: inherit; }
29
+ button { color: inherit; }
30
+ button:focus-visible, input:focus-visible, textarea:focus-visible, [tabindex]:focus-visible {
31
+ outline: 2px solid color-mix(in srgb, var(--emerald) 75%, white);
32
+ outline-offset: 2px;
33
+ }
34
+
35
+ .app-shell { min-height: 100dvh; display: grid; grid-template-columns: 218px minmax(0, 1fr); }
36
+ .sidebar {
37
+ position: sticky; top: 0; height: 100dvh; padding: 20px 14px 17px; background: var(--sidebar);
38
+ color: #eef6f3; display: flex; flex-direction: column; overflow-y: auto; z-index: 20;
39
+ }
40
+ .brand { display: flex; align-items: center; gap: 10px; padding: 4px 7px 18px; }
41
+ .brand-mark {
42
+ display: grid; place-items: center; width: 31px; height: 35px; color: white; font-size: 22px; font-weight: 800;
43
+ background: linear-gradient(145deg, #18aa70, #087044); clip-path: polygon(50% 0,100% 25%,78% 42%,100% 60%,50% 100%,0 75%,22% 57%,0 40%);
44
+ }
45
+ .brand-name { font-size: 20px; letter-spacing: -.02em; white-space: nowrap; }
46
+ .brand-name b { color: #38c782; }
47
+ .profile {
48
+ display: grid; grid-template-columns: 34px minmax(0,1fr) 24px; gap: 9px; align-items: center;
49
+ padding: 10px; margin-bottom: 15px; border: 1px solid #28434a; border-radius: var(--radius); background: #0b2a34;
50
+ }
51
+ .avatar { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%; background: var(--emerald); font-weight: 700; }
52
+ .profile strong, .profile small { display: block; }
53
+ .profile strong { font-size: 12px; }
54
+ .profile small { color: #a7bbb9; font-size: 10px; margin-top: 3px; }
55
+ .icon-button { border: 0; background: transparent; min-width: 32px; min-height: 32px; cursor: pointer; border-radius: 6px; }
56
+ .icon-button:hover { background: rgba(255,255,255,.08); }
57
+ .nav-list { display: grid; gap: 5px; }
58
+ .nav-list button, .course-list button {
59
+ width: 100%; border: 0; background: transparent; color: #eef6f3; text-align: left; cursor: pointer;
60
+ }
61
+ .nav-list button {
62
+ min-height: 44px; display: flex; align-items: center; gap: 12px; padding: 9px 12px; border-radius: var(--radius);
63
+ transition: background .16s ease, transform .16s ease;
64
+ }
65
+ .nav-list button:hover { background: rgba(255,255,255,.07); }
66
+ .nav-list button:active { transform: translateY(1px); }
67
+ .nav-list button.active { background: #176b4d; }
68
+ .nav-icon { width: 20px; text-align: center; font-family: var(--mono); font-size: 17px; }
69
+ .course-list { padding: 21px 8px 12px; border-top: 1px solid #28434a; margin-top: 18px; }
70
+ .course-list p { margin: 0 0 8px; color: #93aaa9; font: 10px/1 var(--mono); text-transform: uppercase; letter-spacing: .08em; }
71
+ .course-list button { padding: 5px 0; font-size: 11px; color: #bdd0ce; }
72
+ .course-list button:hover { color: white; }
73
+ .local-status {
74
+ margin-top: auto; padding: 12px; border: 1px solid #16805a; border-radius: var(--radius); background: #092c2b;
75
+ display: grid; gap: 6px; font-size: 10px; line-height: 1.35;
76
+ }
77
+ .local-status strong { font-size: 12px; }
78
+ .status-dot { display: inline-block; width: 8px; height: 8px; margin-right: 7px; border-radius: 50%; background: #55d68d; }
79
+
80
+ .app-main { min-width: 0; }
81
+ .topbar {
82
+ height: 64px; position: sticky; top: 0; z-index: 15; padding: 0 26px; display: grid;
83
+ grid-template-columns: max-content minmax(260px, 480px) max-content max-content max-content;
84
+ justify-content: end; align-items: center; gap: 12px; border-bottom: 1px solid var(--line); background: rgba(251,252,250,.97);
85
+ }
86
+ .today { margin-right: auto; font-size: 12px; color: var(--muted); }
87
+ .global-search {
88
+ height: 40px; display: flex; align-items: center; gap: 8px; padding: 0 10px; border: 1px solid var(--line);
89
+ border-radius: var(--radius); background: white; color: var(--muted);
90
+ }
91
+ .global-search input { min-width: 0; width: 100%; border: 0; outline: 0; background: transparent; color: var(--ink); }
92
+ kbd { padding: 2px 6px; border-radius: 4px; background: #f0f3f1; font: 10px var(--mono); color: var(--muted); }
93
+ .ask-button, .button {
94
+ min-height: 40px; padding: 8px 15px; border: 1px solid var(--line); border-radius: var(--radius); background: white; cursor: pointer;
95
+ }
96
+ .ask-button { color: var(--emerald-dark); border-color: #a8d6bf; font-weight: 600; }
97
+ .button:hover, .ask-button:hover { border-color: #9dafaa; background: #f7faf8; }
98
+ .button:active, .ask-button:active { transform: translateY(1px); }
99
+ .button.primary { border-color: var(--emerald-dark); background: var(--emerald-dark); color: white; }
100
+ .button.primary:hover { background: #075838; }
101
+ .button.danger { border-color: #e6aca8; color: var(--red); background: var(--red-soft); }
102
+ .notification { position: relative; }
103
+ .notification span { position: absolute; right: -1px; top: 0; display: grid; place-items: center; width: 16px; height: 16px; background: var(--emerald-dark); color: white; font-size: 9px; border-radius: 50%; }
104
+ .user-label { font-size: 12px; color: var(--muted); white-space: nowrap; }
105
+ .menu-button { display: none; border: 0; background: transparent; font-size: 20px; }
106
+
107
+ main { padding: 17px 26px 28px; min-height: calc(100dvh - 64px); }
108
+ .page-head { display: flex; align-items: end; justify-content: space-between; gap: 20px; margin-bottom: 13px; }
109
+ .page-head h1 { margin: 0; font: 400 clamp(31px, 2.65vw, 43px)/1 var(--serif); letter-spacing: -.025em; }
110
+ .page-head p { margin: 7px 0 0; color: var(--muted); font-size: 13px; }
111
+ .head-actions { display: flex; gap: 8px; }
112
+ .panel {
113
+ border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); overflow: hidden; min-width: 0;
114
+ }
115
+ .panel-head {
116
+ min-height: 47px; padding: 10px 14px; display: flex; align-items: center; justify-content: space-between; gap: 12px;
117
+ border-bottom: 1px solid var(--line);
118
+ }
119
+ .panel-head h2, .panel-head h3 { margin: 0; font: 400 20px/1.1 var(--serif); }
120
+ .panel-head h3 { font-size: 17px; }
121
+ .panel-body { padding: 14px; }
122
+ .muted { color: var(--muted); }
123
+ .mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
124
+ .label { font-size: 10px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
125
+ .badge {
126
+ display: inline-flex; align-items: center; min-height: 24px; padding: 3px 8px; border: 1px solid var(--line);
127
+ border-radius: 5px; background: #f7faf8; font-size: 10px; white-space: nowrap;
128
+ }
129
+ .badge.green { border-color: #bad8c8; color: #09603d; background: var(--emerald-soft); }
130
+ .badge.blue { border-color: #b8d3ee; color: #145992; background: var(--blue-soft); }
131
+ .badge.amber { border-color: #efc67d; color: var(--amber); background: var(--amber-soft); }
132
+ .badge.red { border-color: #efb3ae; color: var(--red); background: var(--red-soft); }
133
+ .progress { height: 6px; border-radius: 5px; background: #e6ece9; overflow: hidden; }
134
+ .progress > span { display: block; height: 100%; background: var(--emerald-dark); border-radius: inherit; }
135
+ .row { display: flex; align-items: center; gap: 10px; }
136
+ .stack { display: grid; gap: 10px; }
137
+ .divider { height: 1px; background: var(--line); }
138
+ .more { font-weight: 700; letter-spacing: .15em; }
139
+ .empty-note { padding: 28px; text-align: center; color: var(--muted); }
140
+
141
+ /* Overview */
142
+ .overview-grid { display: grid; grid-template-columns: minmax(0,1.55fr) minmax(270px,.82fr) minmax(250px,.72fr); gap: 14px; }
143
+ .briefing { grid-row: span 2; }
144
+ .briefing-intro { padding: 17px; border-bottom: 1px solid var(--line); background: #fcfdfb; }
145
+ .briefing-intro strong { display: block; font: 400 24px var(--serif); margin-bottom: 5px; }
146
+ .agenda-list { display: grid; }
147
+ .agenda-item { display: grid; grid-template-columns: 48px 8px minmax(0,1fr) max-content; gap: 10px; padding: 13px 16px; border-bottom: 1px solid var(--line-soft); align-items: start; }
148
+ .agenda-item:last-child { border-bottom: 0; }
149
+ .agenda-item time { font: 11px var(--mono); color: var(--muted); }
150
+ .agenda-item i { width: 7px; height: 7px; margin-top: 3px; border-radius: 50%; background: var(--emerald); }
151
+ .agenda-item.blue i { background: var(--blue); }
152
+ .agenda-item.amber i { background: #d48a18; }
153
+ .agenda-item strong { display: block; margin-bottom: 4px; font-size: 13px; }
154
+ .agenda-item small { color: var(--muted); }
155
+ .metric-list { display: grid; gap: 13px; }
156
+ .metric-line { display: grid; grid-template-columns: minmax(0,1fr) 38px; gap: 7px; }
157
+ .metric-line .progress { grid-column: 1 / -1; }
158
+ .source-list, .gap-list, .activity-list { display: grid; }
159
+ .source-row, .gap-row, .activity-row { padding: 11px 14px; border-bottom: 1px solid var(--line-soft); }
160
+ .source-row:last-child, .gap-row:last-child, .activity-row:last-child { border-bottom: 0; }
161
+ .source-row { display: grid; grid-template-columns: 38px minmax(0,1fr) max-content; gap: 10px; align-items: center; }
162
+ .thumb {
163
+ width: 38px; height: 38px; object-fit: cover; border: 1px solid var(--line); border-radius: 5px; background: #f1f3f2;
164
+ }
165
+ .source-row strong, .source-row small { display: block; }
166
+ .source-row strong { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
167
+ .source-row small { font-size: 10px; color: var(--muted); margin-top: 3px; }
168
+ .gap-row { display: grid; grid-template-columns: minmax(0,1fr) max-content; gap: 8px; }
169
+ .gap-row strong { font-size: 12px; }
170
+ .gap-row small { display: block; margin-top: 4px; color: var(--muted); font-size: 10px; }
171
+ .roadmap-mini { display: grid; grid-template-columns: 76px repeat(8,1fr); gap: 4px; font-size: 9px; align-items: center; }
172
+ .roadmap-mini span { height: 20px; border-radius: 3px; background: #eef2f0; }
173
+ .roadmap-mini span.on { background: #cfe8d9; border: 1px solid #9dc9af; }
174
+ .roadmap-mini span.info { background: #d9e8f7; border: 1px solid #b3cee8; }
175
+ .roadmap-mini span.warn { background: #ffedc8; border: 1px solid #ecc77d; }
176
+ .roadmap-mini b { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
177
+ .trust-inline { margin-top: 12px; padding: 10px; border: 1px solid #b7daca; border-radius: var(--radius); background: var(--emerald-soft); color: #0b6441; font-size: 11px; }
178
+
179
+ /* Uploads */
180
+ .uploads-grid { display: grid; grid-template-columns: 280px minmax(430px, 1.4fr) 320px; gap: 10px; }
181
+ .upload-queue { min-height: 720px; }
182
+ .queue-item { padding: 13px; border-bottom: 1px solid var(--line-soft); }
183
+ .queue-title { display: flex; justify-content: space-between; gap: 8px; font-size: 11px; }
184
+ .queue-title strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
185
+ .queue-item .progress { margin: 9px 0 7px; }
186
+ .queue-item small { color: var(--muted); }
187
+ .queue-item.selected { background: #f2f8f5; box-shadow: inset 3px 0 var(--emerald); }
188
+ .dropzone { margin: 13px; padding: 18px; border: 1px dashed #a9b9b2; border-radius: var(--radius); text-align: center; background: #fafcfb; }
189
+ .dropzone.drag { border-color: var(--emerald); background: var(--emerald-soft); }
190
+ .doc-review { display: grid; grid-template-columns: 1fr .86fr; min-height: 720px; }
191
+ .doc-pane { padding: 12px; background: #f2f3f1; border-right: 1px solid var(--line); }
192
+ .doc-toolbar { display: flex; gap: 7px; align-items: center; padding-bottom: 10px; font-size: 11px; }
193
+ .document-page { height: 635px; padding: 25px 22px; background: white; border: 1px solid #bac3bf; box-shadow: 0 3px 8px rgba(12,31,37,.08); overflow: hidden; }
194
+ .document-page img { width: 100%; height: 100%; object-fit: contain; }
195
+ .note-pane { background: #f5f3ee; overflow: hidden; }
196
+ .note-pane img { width: 100%; height: 100%; object-fit: cover; object-position: top; }
197
+ .ocr-panel textarea {
198
+ width: 100%; min-height: 425px; padding: 11px; border: 1px solid var(--line); border-radius: var(--radius);
199
+ resize: vertical; font: 11px/1.65 var(--mono); color: #27363a; background: white;
200
+ }
201
+ .language-tabs { display: flex; gap: 7px; flex-wrap: wrap; margin: 9px 0 15px; }
202
+ .ocr-actions { display: grid; grid-template-columns: .7fr 1.3fr; gap: 8px; margin-top: 12px; }
203
+ .confidence { margin: 9px 0 14px; color: var(--muted); font-size: 11px; }
204
+
205
+ /* Canvas */
206
+ .canvas-page { margin: -17px -26px -28px; min-height: calc(100dvh - 64px); background: #f7f8f6; }
207
+ .canvas-head { min-height: 70px; padding: 14px 18px; display: flex; align-items: center; justify-content: space-between; gap: 18px; border-bottom: 1px solid var(--line); background: white; }
208
+ .canvas-head h1 { margin: 0; font: 400 32px var(--serif); }
209
+ .canvas-head h1 span { margin-left: 18px; font: 14px var(--serif); color: var(--muted); }
210
+ .canvas-grid { display: grid; grid-template-columns: minmax(360px,1.04fr) minmax(360px,.95fr) minmax(320px,.86fr); height: calc(100dvh - 134px); gap: 10px; padding: 10px; }
211
+ .canvas-grid .panel { display: flex; flex-direction: column; min-height: 0; }
212
+ .slide-workspace { display: grid; grid-template-columns: 83px minmax(0,1fr); min-height: 0; flex: 1; }
213
+ .slide-thumbs { padding: 8px; overflow-y: auto; background: #fafbf9; border-right: 1px solid var(--line); }
214
+ .slide-thumb { height: 99px; margin-bottom: 9px; border: 1px solid var(--line); border-radius: 5px; background: white; padding: 6px; overflow: hidden; }
215
+ .slide-thumb.active { border: 2px solid var(--emerald-dark); }
216
+ .slide-thumb img { width: 100%; height: 100%; object-fit: cover; object-position: top; }
217
+ .slide-main { padding: 10px; background: #eef1ef; min-height: 0; }
218
+ .slide-main img { width: 100%; height: 100%; object-fit: contain; background: white; border: 1px solid #bcc6c1; }
219
+ .notes-main { min-height: 0; flex: 1; background: #f4f0e8; overflow: auto; }
220
+ .notes-main img { width: 100%; min-height: 100%; object-fit: cover; object-position: top; }
221
+ .chat { min-height: 0; display: flex; flex-direction: column; }
222
+ .messages { flex: 1; overflow-y: auto; padding: 14px; display: grid; align-content: start; gap: 12px; }
223
+ .message { max-width: 90%; padding: 11px; border: 1px solid var(--line); border-radius: var(--radius); line-height: 1.55; }
224
+ .message.user { justify-self: end; background: var(--emerald-soft); border-color: #bad8c8; }
225
+ .message.ai { background: white; }
226
+ .alert-card { padding: 12px; border: 1px solid #edc47f; border-radius: var(--radius); background: var(--amber-soft); line-height: 1.45; }
227
+ .alert-card strong { color: var(--amber); display: block; margin-bottom: 7px; }
228
+ .chat-compose { padding: 10px; border-top: 1px solid var(--line); display: grid; grid-template-columns: minmax(0,1fr) 40px; gap: 7px; }
229
+ .chat-compose input { width: 100%; border: 1px solid var(--line); border-radius: var(--radius); padding: 10px; }
230
+
231
+ /* Calendar */
232
+ .calendar-grid { display: grid; grid-template-columns: minmax(620px,1.75fr) minmax(290px,.8fr); gap: 14px; }
233
+ .week-calendar { min-height: 740px; }
234
+ .week-grid { padding: 12px 14px; display: grid; grid-template-columns: 50px repeat(5,1fr); grid-template-rows: 28px repeat(7,55px); position: relative; }
235
+ .week-grid > div { border-bottom: 1px solid var(--line-soft); border-right: 1px solid var(--line-soft); }
236
+ .week-grid .day { text-align: center; padding: 5px; color: var(--muted); font-size: 10px; }
237
+ .week-grid .time { padding-top: 7px; font: 9px var(--mono); color: var(--muted); border-right: 1px solid var(--line); }
238
+ .event {
239
+ z-index: 2; margin: 3px 6px; padding: 6px; border: 1px solid #9fcab2; border-radius: 6px; background: var(--emerald-soft);
240
+ font-size: 9px; line-height: 1.35; overflow: hidden;
241
+ }
242
+ .event.blue { background: var(--blue-soft); border-color: #aecce9; }
243
+ .event.amber { background: var(--amber-soft); border-color: #ecc57c; }
244
+ .roadmap { padding: 13px; border-top: 1px solid var(--line); }
245
+ .roadmap h3 { font: 600 17px var(--serif); margin: 0 0 12px; }
246
+ .roadmap-grid { display: grid; grid-template-columns: 145px repeat(8,1fr); gap: 5px; font-size: 9px; align-items: center; }
247
+ .roadmap-grid span { height: 25px; border-radius: 4px; background: #edf1ef; border: 1px solid #dce4df; }
248
+ .roadmap-grid span.on { background: #d8ebdf; border-color: #a5ceb5; }
249
+ .roadmap-grid span.blue { background: #dceaf7; border-color: #b7d0e7; }
250
+ .roadmap-grid span.amber { background: #ffedc9; border-color: #edc77e; }
251
+ .today-block { padding: 12px; border-left: 3px solid var(--emerald-dark); background: #f4f8f5; margin-bottom: 8px; }
252
+ .today-block strong, .today-block small { display: block; }
253
+ .today-block strong { font-size: 11px; margin-bottom: 4px; }
254
+ .calendar-asset { width: 100%; max-height: 260px; object-fit: cover; object-position: top; border: 1px solid var(--line); border-radius: 5px; }
255
+ .extraction-row { display: grid; grid-template-columns: 65px minmax(0,1fr) max-content; gap: 8px; align-items: center; padding: 10px 0; border-bottom: 1px solid var(--line-soft); font-size: 11px; }
256
+
257
+ /* Flashcards */
258
+ .cards-grid { display: grid; grid-template-columns: 310px minmax(450px,1fr) 300px; gap: 14px; }
259
+ .deck-row { padding: 14px 12px; border-bottom: 1px solid var(--line-soft); cursor: pointer; }
260
+ .deck-row:hover { background: #f8faf9; }
261
+ .deck-row.selected { background: var(--emerald-soft); }
262
+ .deck-row strong, .deck-row small { display: block; }
263
+ .deck-row small { color: var(--muted); margin: 5px 0 8px; }
264
+ .field { display: grid; gap: 6px; margin-bottom: 12px; }
265
+ .field label { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; }
266
+ .field textarea { width: 100%; padding: 12px; border: 1px solid var(--line); border-radius: var(--radius); resize: vertical; }
267
+ .field.question textarea { min-height: 100px; font: 400 22px/1.25 var(--serif); }
268
+ .field.answer textarea { min-height: 155px; line-height: 1.55; }
269
+ .source-box { padding: 10px; border: 1px solid #bad2ea; border-radius: 5px; background: var(--blue-soft); color: #165b98; font-size: 11px; }
270
+ .evidence-strip { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 9px; }
271
+ .evidence-strip img { width: 100%; height: 82px; object-fit: cover; border: 1px solid var(--line); border-radius: 5px; }
272
+ .card-preview { min-height: 245px; padding: 26px; display: flex; flex-direction: column; justify-content: center; border: 1px solid var(--line); border-radius: 10px; background: #fdfefc; cursor: pointer; }
273
+ .card-preview small { color: var(--muted); text-transform: uppercase; }
274
+ .card-preview strong { margin: 20px 0; font: 600 23px/1.12 var(--serif); }
275
+ .card-preview .back { display: none; line-height: 1.55; }
276
+ .card-preview.flipped .front { display: none; }
277
+ .card-preview.flipped .back { display: block; }
278
+ .export-list { display: grid; gap: 8px; }
279
+ .export-list .button { width: 100%; }
280
+
281
+ /* Quiz */
282
+ .quiz-grid { display: grid; grid-template-columns: minmax(600px,1fr) 320px; gap: 14px; }
283
+ .quiz-card { min-height: 720px; }
284
+ .quiz-content { padding: 25px 34px; }
285
+ .quiz-meta { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 9px; }
286
+ .quiz-question { margin: 28px 0 21px; font: 400 clamp(25px,2.25vw,36px)/1.15 var(--serif); max-width: 900px; }
287
+ .answers { display: grid; gap: 9px; }
288
+ .answer-option {
289
+ width: 100%; min-height: 58px; padding: 12px 15px; display: grid; grid-template-columns: 28px minmax(0,1fr);
290
+ align-items: center; gap: 10px; text-align: left; border: 1px solid var(--line); border-radius: var(--radius); background: white; cursor: pointer;
291
+ }
292
+ .answer-option:hover { border-color: #91aa9e; background: #fafcfb; }
293
+ .answer-option.selected { border-color: var(--emerald); background: var(--emerald-soft); }
294
+ .answer-option.wrong { border-color: #ea9e98; background: var(--red-soft); }
295
+ .answer-letter { display: grid; place-items: center; width: 25px; height: 25px; border: 1px solid #bdc9c3; border-radius: 50%; font-family: var(--serif); }
296
+ .explanation { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line); }
297
+ .explanation h3 { margin: 0 0 7px; color: var(--emerald-dark); font: 400 20px var(--serif); }
298
+ .source-citation { margin-top: 12px; padding: 10px; border: 1px solid #b8d0e8; border-radius: 5px; background: var(--blue-soft); color: #14558e; font-size: 11px; }
299
+ .stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
300
+ .stat { padding: 12px; background: #f2f5f3; border-radius: 6px; }
301
+ .stat strong, .stat small { display: block; }
302
+ .stat strong { font: 400 23px var(--serif); }
303
+ .stat small { margin-top: 3px; color: var(--muted); }
304
+ .difficulty { display: grid; grid-template-columns: repeat(3,1fr); gap: 6px; }
305
+ .difficulty button { min-height: 34px; border: 1px solid var(--line); border-radius: 5px; background: white; font-size: 10px; cursor: pointer; }
306
+ .difficulty button.active { background: var(--emerald-dark); color: white; border-color: var(--emerald-dark); }
307
+ .focus-list { display: grid; gap: 11px; }
308
+
309
+ .scrim { display: none; position: fixed; inset: 0; z-index: 18; background: rgba(4,19,24,.45); }
310
+ .toast { position: fixed; right: 24px; bottom: 24px; z-index: 50; padding: 11px 15px; border-radius: var(--radius); background: var(--sidebar); color: white; box-shadow: 0 8px 30px rgba(0,0,0,.18); opacity: 0; transform: translateY(12px); pointer-events: none; transition: .18s ease; }
311
+ .toast.show { opacity: 1; transform: translateY(0); }
312
+ dialog { border: 0; padding: 0; border-radius: 10px; width: min(560px, calc(100vw - 28px)); color: var(--ink); }
313
+ dialog::backdrop { background: rgba(4,19,24,.48); }
314
+ .dialog-card { padding: 20px; }
315
+ .dialog-head { display: flex; justify-content: space-between; gap: 20px; }
316
+ .dialog-head small { color: var(--emerald-dark); }
317
+ .dialog-head h2 { margin: 4px 0 18px; font: 400 27px var(--serif); }
318
+ .dialog-card label { display: grid; gap: 7px; font-weight: 600; }
319
+ .dialog-card textarea { width: 100%; padding: 11px; border: 1px solid var(--line); border-radius: var(--radius); resize: vertical; }
320
+ .trust-note { color: var(--muted); font-size: 11px; }
321
+ .dialog-actions { display: flex; justify-content: end; gap: 8px; margin-top: 18px; }
322
+
323
+ @media (max-width: 1180px) {
324
+ .app-shell { grid-template-columns: 196px minmax(0,1fr); }
325
+ .user-label, .today { display: none; }
326
+ .topbar { grid-template-columns: minmax(220px,1fr) max-content max-content; }
327
+ .overview-grid { grid-template-columns: minmax(0,1.45fr) minmax(260px,.8fr); }
328
+ .overview-grid > :last-child { grid-column: 1 / -1; }
329
+ .uploads-grid { grid-template-columns: 240px minmax(420px,1fr); }
330
+ .ocr-panel { grid-column: 1 / -1; }
331
+ .canvas-grid { grid-template-columns: 1fr 1fr; height: auto; }
332
+ .canvas-grid > :last-child { grid-column: 1 / -1; min-height: 580px; }
333
+ .calendar-grid { grid-template-columns: 1fr; }
334
+ .cards-grid { grid-template-columns: 250px minmax(420px,1fr); }
335
+ .cards-grid > :last-child { grid-column: 1 / -1; }
336
+ }
337
+
338
+ @media (max-width: 820px) {
339
+ .app-shell { display: block; }
340
+ .sidebar { position: fixed; left: 0; transform: translateX(-105%); width: 260px; transition: transform .2s ease; }
341
+ .sidebar.open { transform: translateX(0); }
342
+ .scrim.show { display: block; }
343
+ .menu-button { display: block; }
344
+ .topbar { grid-template-columns: 38px minmax(0,1fr) max-content; padding: 0 12px; }
345
+ .topbar .notification { display: none; }
346
+ main { padding: 16px; }
347
+ .page-head { align-items: start; }
348
+ .page-head h1 { font-size: 34px; }
349
+ .head-actions { flex-wrap: wrap; justify-content: end; }
350
+ .overview-grid, .uploads-grid, .calendar-grid, .cards-grid, .quiz-grid { grid-template-columns: 1fr; }
351
+ .overview-grid > *, .uploads-grid > *, .cards-grid > * { grid-column: auto !important; }
352
+ .upload-queue { min-height: auto; }
353
+ .doc-review { min-height: 620px; }
354
+ .canvas-page { margin: -16px; }
355
+ .canvas-head { align-items: start; }
356
+ .canvas-head h1 span { display: block; margin: 5px 0 0; }
357
+ .canvas-grid { grid-template-columns: 1fr; height: auto; }
358
+ .canvas-grid > :last-child { grid-column: auto; }
359
+ .canvas-grid .panel { min-height: 650px; }
360
+ .roadmap-grid { grid-template-columns: 110px repeat(8, minmax(45px,1fr)); overflow-x: auto; }
361
+ .quiz-card { min-height: auto; }
362
+ }
363
+
364
+ @media (max-width: 560px) {
365
+ .topbar { height: 58px; }
366
+ .ask-button { padding: 8px 10px; font-size: 0; }
367
+ .ask-button::after { content: "✦"; font-size: 16px; }
368
+ main { min-height: calc(100dvh - 58px); padding: 12px; }
369
+ .page-head { display: grid; }
370
+ .head-actions { justify-content: start; }
371
+ .page-head h1 { font-size: 31px; }
372
+ .panel-head h2 { font-size: 18px; }
373
+ .agenda-item { grid-template-columns: 44px 7px minmax(0,1fr); }
374
+ .agenda-item .badge { grid-column: 3; justify-self: start; }
375
+ .doc-review { grid-template-columns: 1fr; min-height: auto; }
376
+ .doc-pane { border-right: 0; border-bottom: 1px solid var(--line); }
377
+ .document-page { height: 520px; }
378
+ .note-pane { height: 520px; }
379
+ .canvas-page { margin: -12px; }
380
+ .canvas-head { min-height: 110px; display: grid; }
381
+ .canvas-head .head-actions { display: none; }
382
+ .canvas-grid { padding: 7px; gap: 7px; }
383
+ .canvas-grid .panel { min-height: 560px; }
384
+ .slide-workspace { grid-template-columns: 64px minmax(0,1fr); }
385
+ .slide-thumb { height: 75px; }
386
+ .week-grid { overflow-x: auto; grid-template-columns: 45px repeat(5, 130px); }
387
+ .cards-grid .panel, .quiz-grid .panel { min-width: 0; }
388
+ .quiz-content { padding: 18px 15px; }
389
+ .quiz-question { font-size: 27px; }
390
+ .answer-option { grid-template-columns: 26px minmax(0,1fr); }
391
+ .field.question textarea { font-size: 19px; }
392
+ .toast { left: 12px; right: 12px; bottom: 12px; }
393
+ }
394
+
395
+ @media (prefers-reduced-motion: reduce) {
396
+ *, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; animation-duration: .01ms !important; }
397
+ }
398
+
399
+ /* Josephine editorial redesign */
400
+ :root {
401
+ --sidebar: #fbfaf6;
402
+ --sidebar-2: #f7f4ed;
403
+ --paper: #fbfaf6;
404
+ --surface: #fffefa;
405
+ --ink: #25211f;
406
+ --muted: #756f69;
407
+ --line: #ded9d0;
408
+ --line-soft: #eeeae3;
409
+ --emerald: #146a57;
410
+ --emerald-dark: #0f5848;
411
+ --emerald-soft: #edf5f0;
412
+ --aubergine: #54203f;
413
+ --aubergine-soft: #f3eaf0;
414
+ --rose: #ca6478;
415
+ --rose-soft: #faedf0;
416
+ --gold: #a9751e;
417
+ --gold-soft: #fbf3e5;
418
+ --serif: "Cormorant Garamond", Georgia, serif;
419
+ --sans: "DM Sans", "Segoe UI", sans-serif;
420
+ }
421
+
422
+ body { font-family: var(--sans); background: var(--paper); }
423
+ .app-shell { grid-template-columns: 178px minmax(0, 1fr); }
424
+ .sidebar {
425
+ padding: 26px 13px 22px;
426
+ background: var(--sidebar);
427
+ color: var(--ink);
428
+ border-right: 1px solid var(--line);
429
+ box-shadow: none;
430
+ }
431
+ .brand { gap: 8px; padding: 2px 10px 26px; }
432
+ .brand-mark {
433
+ display: grid; place-items: center; width: 29px; height: 35px;
434
+ color: var(--emerald); background: none; clip-path: none; font-size: 27px;
435
+ }
436
+ .brand-name { font: 500 18px/1 var(--serif); color: #292522; letter-spacing: 0; }
437
+ .profile {
438
+ display: none;
439
+ }
440
+ .nav-list { gap: 5px; }
441
+ .nav-list button {
442
+ min-height: 48px; padding: 10px 13px; gap: 12px;
443
+ border: 1px solid transparent; border-radius: 7px;
444
+ color: #393431; background: transparent;
445
+ font: 500 13px/1 var(--serif);
446
+ }
447
+ .nav-list button > i { width: 21px; font-size: 21px; }
448
+ .nav-list button:hover { background: #f2eee7; }
449
+ .nav-list button.active {
450
+ color: white; background: var(--emerald-dark); border-color: var(--emerald-dark);
451
+ }
452
+ .local-status {
453
+ margin-top: auto; padding: 13px 10px 0;
454
+ border: 0; border-top: 1px solid var(--line); border-radius: 0; background: transparent;
455
+ color: var(--muted); font-size: 10px; line-height: 1.55;
456
+ }
457
+ .local-status strong { color: #554f4a; font-size: 10px; font-weight: 500; }
458
+ .status-dot { width: 7px; height: 7px; background: var(--emerald); }
459
+
460
+ .topbar {
461
+ height: 58px; padding: 0 24px;
462
+ grid-template-columns: max-content minmax(250px, 430px) max-content max-content max-content;
463
+ background: rgba(251,250,246,.96);
464
+ }
465
+ .app-main:has(.josephine-home) .topbar { display: none; }
466
+ .app-main:has(.josephine-home) main { padding: 0; min-height: 100dvh; }
467
+ .global-search, .ask-button, .button { border-radius: 6px; }
468
+ .page-head h1 { font-family: var(--serif); color: var(--aubergine); letter-spacing: 0; }
469
+
470
+ .josephine-home { min-height: 100dvh; background: var(--surface); }
471
+ .josephine-hero {
472
+ position: relative; min-height: 500px; overflow: hidden; background: #eee4d5;
473
+ }
474
+ .josephine-hero > img {
475
+ position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: center;
476
+ }
477
+ .josephine-hero::after {
478
+ content: ""; position: absolute; inset: 0 auto 0 0; width: 53%;
479
+ background: linear-gradient(90deg, rgba(252,249,242,.97) 0%, rgba(252,249,242,.72) 62%, rgba(252,249,242,0) 100%);
480
+ pointer-events: none;
481
+ }
482
+ .weather-line {
483
+ position: absolute; z-index: 2; top: 31px; left: 47px;
484
+ display: flex; align-items: center; gap: 10px; padding: 8px 12px;
485
+ border-radius: 18px; background: rgba(255,255,255,.55); color: #514a45;
486
+ backdrop-filter: blur(8px); font: 13px var(--serif);
487
+ }
488
+ .weather-line i { font-size: 17px; }
489
+ .hero-copy {
490
+ position: absolute; z-index: 2; left: 47px; top: 83px; width: min(440px, 43%);
491
+ }
492
+ .hero-copy h1 {
493
+ margin: 0; color: var(--aubergine);
494
+ font: 500 clamp(65px, 7vw, 106px)/.77 var(--serif);
495
+ letter-spacing: -2px;
496
+ }
497
+ .hero-motto {
498
+ margin: 16px 0 26px !important; color: #8a684c !important;
499
+ font: italic 22px/1.2 var(--serif); letter-spacing: .02em;
500
+ }
501
+ .hero-copy h2 { margin: 0 0 8px; font: 500 29px/1.1 var(--serif); }
502
+ .hero-copy > p:last-child { margin: 0; color: #4f4a45; font-size: 13px; line-height: 1.55; }
503
+
504
+ .daily-journal {
505
+ display: grid; grid-template-columns: minmax(0, 1.65fr) minmax(315px, .9fr);
506
+ min-height: 448px; background: #fffefa;
507
+ }
508
+ .journal-agenda { padding: 23px 34px 29px 47px; border-right: 1px solid var(--line); }
509
+ .journal-agenda > header, .today-important > header {
510
+ display: flex; align-items: center; gap: 10px; min-height: 38px;
511
+ padding-bottom: 10px; border-bottom: 1px solid var(--line);
512
+ }
513
+ .journal-agenda header > i, .today-important header > i { font-size: 20px; }
514
+ .journal-agenda header h2, .today-important header h2 {
515
+ margin: 0; font: 500 20px/1 var(--serif);
516
+ }
517
+ .journal-agenda header span { margin-left: 8px; color: #79685b; font: italic 19px var(--serif); }
518
+ .day-list { position: relative; padding-left: 25px; }
519
+ .day-list::before {
520
+ content: ""; position: absolute; top: 0; bottom: 0; left: 3px; width: 1px; background: #ddd7ce;
521
+ }
522
+ .day-entry {
523
+ position: relative; width: 100%; min-height: 69px; padding: 10px 5px 10px 9px;
524
+ display: grid; grid-template-columns: 72px 45px minmax(0,1fr) 20px; gap: 10px; align-items: center;
525
+ border: 0; border-bottom: 1px solid var(--line-soft); background: transparent; text-align: left; cursor: pointer;
526
+ }
527
+ .day-entry::before {
528
+ content: ""; position: absolute; left: -25px; top: 31px; width: 8px; height: 8px;
529
+ border-radius: 50%; background: var(--emerald); box-shadow: 0 0 0 4px #fffefa;
530
+ }
531
+ .day-entry.blue::before { background: #3d7298; }
532
+ .day-entry.rose::before { background: var(--rose); }
533
+ .day-entry.aubergine::before { background: var(--aubergine); }
534
+ .day-entry.gold::before { background: var(--gold); }
535
+ .day-entry:hover { background: #fbf8f2; }
536
+ .day-entry time { color: var(--emerald); font: 500 23px var(--serif); }
537
+ .day-entry.blue time { color: #3d7298; }
538
+ .day-entry.rose time { color: var(--rose); }
539
+ .day-entry.aubergine time { color: var(--aubergine); }
540
+ .day-entry.gold time { color: var(--gold); }
541
+ .day-icon {
542
+ display: grid; place-items: center; width: 39px; height: 39px; border-radius: 50%;
543
+ color: var(--emerald); background: var(--emerald-soft); font-size: 21px;
544
+ }
545
+ .blue .day-icon { color: #3d7298; background: #edf3f7; }
546
+ .rose .day-icon { color: var(--rose); background: var(--rose-soft); }
547
+ .aubergine .day-icon { color: var(--aubergine); background: var(--aubergine-soft); }
548
+ .gold .day-icon { color: var(--gold); background: var(--gold-soft); }
549
+ .day-entry strong, .day-entry small { display: block; }
550
+ .day-entry strong { margin-bottom: 4px; font: 500 16px var(--serif); }
551
+ .day-entry small { color: var(--muted); font-size: 10px; line-height: 1.4; }
552
+ .entry-arrow { opacity: 0; color: var(--muted); }
553
+ .day-entry:hover .entry-arrow { opacity: 1; }
554
+
555
+ .today-important { padding: 23px 31px 29px; }
556
+ .important-row {
557
+ width: 100%; min-height: 92px; padding: 13px 4px;
558
+ display: grid; grid-template-columns: 45px minmax(0,1fr) 18px; gap: 12px; align-items: center;
559
+ border: 0; border-bottom: 1px solid var(--line-soft); background: transparent; text-align: left; cursor: pointer;
560
+ }
561
+ .important-row:hover { background: #fbf8f2; }
562
+ .important-icon {
563
+ display: grid; place-items: center; width: 42px; height: 42px; border-radius: 50%;
564
+ background: var(--rose-soft); color: var(--rose); font-size: 21px;
565
+ }
566
+ .important-row.gap .important-icon { background: var(--aubergine-soft); color: var(--aubergine); }
567
+ .important-row.next { margin-top: 14px; padding: 13px; border: 0; border-radius: 8px; background: var(--emerald-soft); }
568
+ .important-row.next .important-icon { background: #fff; color: var(--emerald); }
569
+ .important-row small, .important-row strong, .important-row em { display: block; }
570
+ .important-row small { margin-bottom: 4px; color: var(--rose); font-size: 10px; font-style: normal; }
571
+ .important-row.gap small { color: #8e5480; }
572
+ .important-row.next small { color: var(--emerald); }
573
+ .important-row strong { margin-bottom: 4px; font: 500 15px var(--serif); }
574
+ .important-row em { color: var(--muted); font-size: 10px; font-style: normal; }
575
+ .journal-note {
576
+ margin: 25px 0 0 54px; color: #5c907d; transform: rotate(-2deg);
577
+ font: italic 18px/1.25 var(--serif);
578
+ }
579
+
580
+ .quiet-page {
581
+ max-width: 1120px; margin: 0 auto; display: grid; gap: 18px;
582
+ }
583
+ .quiet-page.two-column { grid-template-columns: minmax(320px,.8fr) minmax(480px,1.2fr); }
584
+ .plan-intro, .focus-story, .reminder-date {
585
+ padding: 35px 0 28px; border-bottom: 1px solid var(--line);
586
+ }
587
+ .eyebrow { color: var(--emerald); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }
588
+ .plan-intro h2, .focus-story h2 {
589
+ margin: 10px 0; color: var(--aubergine); font: 500 38px/1.05 var(--serif);
590
+ }
591
+ .plan-intro p, .focus-story p { max-width: 620px; color: var(--muted); line-height: 1.65; }
592
+ .plan-days, .calm-list { display: grid; border-top: 1px solid var(--line); }
593
+ .plan-days button, .calm-list button {
594
+ min-height: 78px; display: grid; grid-template-columns: 120px minmax(0,1fr) max-content;
595
+ gap: 18px; align-items: center; padding: 14px 8px; border: 0; border-bottom: 1px solid var(--line);
596
+ background: transparent; text-align: left; cursor: pointer;
597
+ }
598
+ .plan-days button:hover, .calm-list button:hover { background: #f8f5ef; }
599
+ .plan-days time { color: var(--emerald); font: 500 19px var(--serif); }
600
+ .plan-days strong, .plan-days span, .calm-list strong, .calm-list small { display: block; }
601
+ .plan-days strong, .calm-list strong { font: 500 17px var(--serif); }
602
+ .plan-days span, .calm-list small { margin-top: 4px; color: var(--muted); font-size: 11px; }
603
+ .calm-list button { grid-template-columns: 12px minmax(0,1fr) max-content; }
604
+ .calm-list em { color: var(--muted); font-size: 11px; font-style: normal; }
605
+ .signal { width: 8px; height: 8px; border-radius: 50%; background: var(--emerald); }
606
+ .signal.red { background: var(--rose); }
607
+ .signal.amber { background: var(--gold); }
608
+ .reminders-page { grid-template-columns: 260px minmax(0,1fr); align-items: start; }
609
+ .reminder-date { border: 0; }
610
+ .reminder-date span, .reminder-date strong { display: block; font-family: var(--serif); }
611
+ .reminder-date span { color: var(--emerald); font-size: 18px; }
612
+ .reminder-date strong { color: var(--aubergine); font-size: 48px; }
613
+ .reminder-date p { color: var(--muted); line-height: 1.55; }
614
+ .reminders-page .calm-list button { grid-template-columns: 45px minmax(0,1fr) max-content; }
615
+
616
+ @media (max-width: 1050px) {
617
+ .app-shell { grid-template-columns: 164px minmax(0,1fr); }
618
+ .hero-copy { width: 48%; }
619
+ .hero-copy h1 { font-size: 72px; }
620
+ .daily-journal { grid-template-columns: 1fr; }
621
+ .journal-agenda { border-right: 0; border-bottom: 1px solid var(--line); }
622
+ }
623
+
624
+ @media (max-width: 820px) {
625
+ .sidebar { width: 250px; }
626
+ .app-main:has(.josephine-home) .topbar { display: grid; }
627
+ .app-main:has(.josephine-home) main { padding: 0; }
628
+ .josephine-hero { min-height: 610px; }
629
+ .josephine-hero > img { object-position: 66% center; }
630
+ .josephine-hero::after { width: 72%; }
631
+ .hero-copy { left: 28px; top: 80px; width: 58%; }
632
+ .weather-line { left: 28px; }
633
+ .journal-agenda, .today-important { padding-left: 24px; padding-right: 24px; }
634
+ .quiet-page.two-column, .reminders-page { grid-template-columns: 1fr; }
635
+ }
636
+
637
+ @media (max-width: 560px) {
638
+ .josephine-hero { min-height: 690px; background: #fffefa; }
639
+ .josephine-hero > img { height: 390px; object-position: 68% center; }
640
+ .josephine-hero::after { display: none; }
641
+ .hero-copy {
642
+ top: 390px; left: 0; width: 100%; padding: 19px 20px 22px;
643
+ background: #fffefa;
644
+ }
645
+ .hero-copy h1 { font-size: 52px; line-height: .78; }
646
+ .hero-motto { margin: 13px 0 18px !important; font-size: 17px; }
647
+ .hero-copy h2 { margin: 0 0 7px; font-size: 25px; }
648
+ .weather-line { top: 20px; left: 20px; font-size: 11px; }
649
+ .journal-agenda, .today-important { padding: 20px 15px; }
650
+ .day-list { padding-left: 17px; }
651
+ .day-entry { grid-template-columns: 55px 37px minmax(0,1fr); gap: 7px; }
652
+ .day-entry::before { left: -17px; }
653
+ .day-entry time { font-size: 19px; }
654
+ .day-icon { width: 34px; height: 34px; }
655
+ .entry-arrow { display: none; }
656
+ }