Commit ·
ac53235
0
Parent(s):
chore: establish StudyFlow project structure
Browse filesOrganize the standalone frontend for synchronized GitHub and Hugging Face Spaces publication, preserving the selected design reference and QA history.
Co-authored-by: Codex <codex@openai.com>
- .gitattributes +9 -0
- .gitignore +25 -0
- LICENSE +21 -0
- README.md +78 -0
- docs/design/finalized.json +12 -0
- docs/design/legacy/script.js +119 -0
- docs/design/open-design/asset-inventory.md +37 -0
- docs/design/open-design/compare_josephine.py +29 -0
- docs/design/open-design/extract_assets.py +26 -0
- docs/design/reference/josephines-tagebuch.png +3 -0
- docs/design/reviews/design-html-review.md +28 -0
- docs/design/reviews/design-qa.md +37 -0
- docs/design/reviews/design-review.md +73 -0
- docs/design/reviews/emil-design-review.md +20 -0
- docs/design/reviews/frontend-design-review.md +24 -0
- docs/design/screenshots/josephine-design-comparison.png +3 -0
- docs/design/screenshots/josephine-home-desktop.png +3 -0
- server.js +42 -0
- tools/open-design-proxy.js +29 -0
- tools/ui_ux_check.py +35 -0
.gitattributes
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.png !text !filter !merge !diff
|
| 3 |
+
assets/hero/josephine-mainz-morning.png filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
assets/materials/mq3zhq9n-clipboard.png filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
assets/materials/mq3zhqat-clipboard.png filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
assets/materials/mq3zhqbu-clipboard.png filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
docs/design/reference/josephines-tagebuch.png filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
docs/design/screenshots/josephine-design-comparison.png filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
docs/design/screenshots/josephine-home-desktop.png filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.env
|
| 2 |
+
.env.*
|
| 3 |
+
!.env.example
|
| 4 |
+
|
| 5 |
+
__pycache__/
|
| 6 |
+
*.py[cod]
|
| 7 |
+
.pytest_cache/
|
| 8 |
+
.venv/
|
| 9 |
+
venv/
|
| 10 |
+
|
| 11 |
+
.uv-cache/
|
| 12 |
+
.uv-python/
|
| 13 |
+
.uv-tools/
|
| 14 |
+
|
| 15 |
+
node_modules/
|
| 16 |
+
dist/
|
| 17 |
+
coverage/
|
| 18 |
+
|
| 19 |
+
.DS_Store
|
| 20 |
+
Thumbs.db
|
| 21 |
+
*.log
|
| 22 |
+
|
| 23 |
+
# Local design exploration archive and duplicated source renders.
|
| 24 |
+
docs/design/local-archive/
|
| 25 |
+
docs/design/open-design/references/
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Ashish Soni
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: StudyFlow AI
|
| 3 |
+
emoji: 📚
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: static
|
| 7 |
+
app_file: index.html
|
| 8 |
+
pinned: false
|
| 9 |
+
license: mit
|
| 10 |
+
short_description: A private, personalized AI study copilot.
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
# StudyFlow AI
|
| 14 |
+
|
| 15 |
+
StudyFlow AI is a private study copilot built around the materials students
|
| 16 |
+
already use: handwritten notes, lecture slides, PDFs, calendars, flashcards,
|
| 17 |
+
and reminders.
|
| 18 |
+
|
| 19 |
+
The current release is an interactive frontend prototype centered on
|
| 20 |
+
Josephine Wendland, a multilingual teaching student from Mainz. Backend,
|
| 21 |
+
OCR, local model, and retrieval integrations will be added next.
|
| 22 |
+
|
| 23 |
+
## Current Experience
|
| 24 |
+
|
| 25 |
+
- Personal daily briefing and schedule
|
| 26 |
+
- Material upload and OCR review interface
|
| 27 |
+
- Connected slide, note, and AI study canvas
|
| 28 |
+
- Calendar and personalized learning plan
|
| 29 |
+
- Knowledge-gap review
|
| 30 |
+
- Flashcard editing and export flows
|
| 31 |
+
- Quiz simulation and reminders
|
| 32 |
+
- Local-first privacy messaging
|
| 33 |
+
|
| 34 |
+
## Run Locally
|
| 35 |
+
|
| 36 |
+
No build step is required.
|
| 37 |
+
|
| 38 |
+
```powershell
|
| 39 |
+
node server.js
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
Then open [http://localhost:4173](http://localhost:4173).
|
| 43 |
+
|
| 44 |
+
Python can also serve the static prototype:
|
| 45 |
+
|
| 46 |
+
```powershell
|
| 47 |
+
python -m http.server 4173
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
## Repository Layout
|
| 51 |
+
|
| 52 |
+
```text
|
| 53 |
+
assets/
|
| 54 |
+
hero/ Generated Josephine illustration
|
| 55 |
+
materials/ Sample note, slide, and calendar imagery
|
| 56 |
+
docs/design/ Reference concepts, QA, reviews, and design history
|
| 57 |
+
tools/ Local design and review utilities
|
| 58 |
+
app.js Application views and interactions
|
| 59 |
+
index.html Static Space entry point
|
| 60 |
+
styles.css Responsive visual system
|
| 61 |
+
server.js Tiny local development server
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
## Publication
|
| 65 |
+
|
| 66 |
+
The same Git history is pushed to:
|
| 67 |
+
|
| 68 |
+
- GitHub: source repository and development history
|
| 69 |
+
- Hugging Face Spaces: live static prototype
|
| 70 |
+
|
| 71 |
+
Commits created with Codex include an explicit Codex co-author trailer.
|
| 72 |
+
|
| 73 |
+
## Next Phase
|
| 74 |
+
|
| 75 |
+
The frontend will later be migrated to a Gradio/FastAPI-backed application
|
| 76 |
+
with OCR, document linking, local retrieval, study-plan generation, and
|
| 77 |
+
notification workflows.
|
| 78 |
+
|
docs/design/finalized.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"source_mockup": null,
|
| 3 |
+
"source_plan": "Pasted PRD attachment and current mockup iterations",
|
| 4 |
+
"mode": "evolve",
|
| 5 |
+
"html_file": "C:\\Users\\Lenovo\\Documents\\build-small-hackathon\\projects\\studyflow-ai\\index.html",
|
| 6 |
+
"pretext_tier": "card-grid-dashboard",
|
| 7 |
+
"framework": "vanilla",
|
| 8 |
+
"iterations": 1,
|
| 9 |
+
"date": "2026-06-06T00:00:00+02:00",
|
| 10 |
+
"screen": "studyflow-lehramt-arbeitszentrale",
|
| 11 |
+
"branch": "main"
|
| 12 |
+
}
|
docs/design/legacy/script.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const toast = document.querySelector("#toast");
|
| 2 |
+
const statusStrip = document.querySelector("#statusStrip");
|
| 3 |
+
const flowCopy = document.querySelectorAll("[data-flow-copy]");
|
| 4 |
+
|
| 5 |
+
function syncFlowCopyState() {
|
| 6 |
+
flowCopy.forEach((element) => {
|
| 7 |
+
element.classList.toggle(
|
| 8 |
+
"is-overflowing",
|
| 9 |
+
element.scrollWidth > element.clientWidth + 1
|
| 10 |
+
);
|
| 11 |
+
});
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
if (flowCopy.length) {
|
| 15 |
+
const resizeObserver = new ResizeObserver(syncFlowCopyState);
|
| 16 |
+
resizeObserver.observe(document.querySelector(".workspace"));
|
| 17 |
+
flowCopy.forEach((element) => {
|
| 18 |
+
new MutationObserver(syncFlowCopyState).observe(element, {
|
| 19 |
+
characterData: true,
|
| 20 |
+
childList: true,
|
| 21 |
+
subtree: true,
|
| 22 |
+
});
|
| 23 |
+
});
|
| 24 |
+
document.fonts?.ready.then(syncFlowCopyState);
|
| 25 |
+
syncFlowCopyState();
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
function showToast(message) {
|
| 29 |
+
toast.textContent = message;
|
| 30 |
+
toast.classList.add("show");
|
| 31 |
+
window.clearTimeout(showToast.timer);
|
| 32 |
+
showToast.timer = window.setTimeout(() => toast.classList.remove("show"), 2800);
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
document.querySelectorAll(".nav-item").forEach((item) => {
|
| 36 |
+
item.addEventListener("click", () => {
|
| 37 |
+
document.querySelectorAll(".nav-item").forEach((nav) => nav.classList.remove("active"));
|
| 38 |
+
item.classList.add("active");
|
| 39 |
+
const label = item.textContent.replace(/\s+/g, " ").trim();
|
| 40 |
+
showToast(`${label} geöffnet. Für den Demo-Mockup bleibt die Übersicht im Fokus.`);
|
| 41 |
+
});
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
document.querySelectorAll(".language-toggle button").forEach((button) => {
|
| 45 |
+
button.addEventListener("click", () => {
|
| 46 |
+
document.querySelectorAll(".language-toggle button").forEach((btn) => btn.classList.remove("selected"));
|
| 47 |
+
button.classList.add("selected");
|
| 48 |
+
const language = button.textContent.trim();
|
| 49 |
+
if (language === "DE") {
|
| 50 |
+
showToast("Deutsch bleibt die Hauptsprache der App.");
|
| 51 |
+
} else {
|
| 52 |
+
showToast(`${language} wird als Materialsprache erkannt. Die Oberfläche bleibt deutsch.`);
|
| 53 |
+
}
|
| 54 |
+
});
|
| 55 |
+
});
|
| 56 |
+
|
| 57 |
+
document.querySelectorAll(".action-row").forEach((button) => {
|
| 58 |
+
button.addEventListener("click", () => {
|
| 59 |
+
showToast(button.dataset.message);
|
| 60 |
+
});
|
| 61 |
+
});
|
| 62 |
+
|
| 63 |
+
document.querySelectorAll(".confirm-button").forEach((button) => {
|
| 64 |
+
button.addEventListener("click", () => {
|
| 65 |
+
const row = button.closest("tr");
|
| 66 |
+
row.classList.remove("pending-row");
|
| 67 |
+
row.querySelector("td:nth-child(7)").innerHTML = '<span class="chip green">Bestätigt</span>';
|
| 68 |
+
row.querySelector("td:nth-child(8)").innerHTML = '<button class="icon-button" type="button" aria-label="Termin bestätigt"><span class="material-symbols-rounded">check</span></button>';
|
| 69 |
+
showToast("Termin bestätigt und in Mayas Lernplan übernommen.");
|
| 70 |
+
});
|
| 71 |
+
});
|
| 72 |
+
|
| 73 |
+
document.querySelector("#linkCheckBtn").addEventListener("click", () => {
|
| 74 |
+
showToast("Verknüpfungen geprüft: Folien 10-15 passen zu Notizen 4-6.");
|
| 75 |
+
});
|
| 76 |
+
|
| 77 |
+
document.querySelector("#addEventBtn").addEventListener("click", () => {
|
| 78 |
+
const tbody = document.querySelector("#eventRows");
|
| 79 |
+
const row = document.createElement("tr");
|
| 80 |
+
row.innerHTML = `
|
| 81 |
+
<td>Fr, 30. Mai</td>
|
| 82 |
+
<td>11:30</td>
|
| 83 |
+
<td>Unterrichtsentwurf Abgabe</td>
|
| 84 |
+
<td><span class="chip red">Abgabe</span></td>
|
| 85 |
+
<td>Notizen_Seminar.pdf</td>
|
| 86 |
+
<td>84%</td>
|
| 87 |
+
<td><span class="chip amber">Ausstehend</span></td>
|
| 88 |
+
<td><button class="confirm-button" type="button" aria-label="Termin bestätigen">OK</button></td>
|
| 89 |
+
`;
|
| 90 |
+
row.querySelector(".confirm-button").addEventListener("click", () => {
|
| 91 |
+
row.querySelector("td:nth-child(7)").innerHTML = '<span class="chip green">Bestätigt</span>';
|
| 92 |
+
row.querySelector("td:nth-child(8)").innerHTML = '<button class="icon-button" type="button" aria-label="Termin bestätigt"><span class="material-symbols-rounded">check</span></button>';
|
| 93 |
+
showToast("Neuer Termin bestätigt.");
|
| 94 |
+
});
|
| 95 |
+
tbody.appendChild(row);
|
| 96 |
+
showToast("Neuer erkannter Termin wurde ergänzt.");
|
| 97 |
+
});
|
| 98 |
+
|
| 99 |
+
document.querySelector("#processBtn").addEventListener("click", () => {
|
| 100 |
+
const button = document.querySelector("#processBtn");
|
| 101 |
+
button.disabled = true;
|
| 102 |
+
button.innerHTML = '<span class="material-symbols-rounded">progress_activity</span> Prüfe...';
|
| 103 |
+
window.setTimeout(() => {
|
| 104 |
+
statusStrip.classList.remove("show");
|
| 105 |
+
button.disabled = false;
|
| 106 |
+
button.innerHTML = '<span class="material-symbols-rounded">task_alt</span> Jetzt prüfen';
|
| 107 |
+
showToast("Uploads geprüft: 2 Termine, 1 neue Wissenslücke und 12 Karteikarten gefunden.");
|
| 108 |
+
}, 650);
|
| 109 |
+
});
|
| 110 |
+
|
| 111 |
+
document.querySelectorAll(".focus-list input").forEach((checkbox) => {
|
| 112 |
+
checkbox.addEventListener("change", () => {
|
| 113 |
+
showToast(checkbox.checked ? "Lernblock als erledigt markiert." : "Lernblock wieder geöffnet.");
|
| 114 |
+
});
|
| 115 |
+
});
|
| 116 |
+
|
| 117 |
+
document.querySelector('[data-view="uploads"]').addEventListener("click", () => {
|
| 118 |
+
statusStrip.classList.add("show");
|
| 119 |
+
});
|
docs/design/open-design/asset-inventory.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# StudyFlow AI Visual Asset Inventory
|
| 2 |
+
|
| 3 |
+
The six reference screens are the canonical visual targets. Only source-material
|
| 4 |
+
imagery should remain bitmap content; all interface elements must be rebuilt in
|
| 5 |
+
HTML and CSS.
|
| 6 |
+
|
| 7 |
+
## Extracted bitmap assets
|
| 8 |
+
|
| 9 |
+
| Asset | Source | Used for |
|
| 10 |
+
| --- | --- | --- |
|
| 11 |
+
| `handwritten-calendar.png` | `02-uploads-ocr.png` | OCR review document canvas |
|
| 12 |
+
| `lecture-slide-scaffolding.png` | `03-study-canvas.png` | Main linked lecture slide |
|
| 13 |
+
| `handwritten-scaffolding-notes.png` | `03-study-canvas.png` | Main linked handwritten notes |
|
| 14 |
+
| `flashcard-source-slide.png` | `05-flashcards.png` | Flashcard source evidence |
|
| 15 |
+
| `flashcard-source-notes.png` | `05-flashcards.png` | Flashcard handwritten evidence |
|
| 16 |
+
| `note-code-switching.png` | `01-overview.png` | Recent-note thumbnail |
|
| 17 |
+
| `note-motivation.png` | `01-overview.png` | Recent-note thumbnail |
|
| 18 |
+
| `note-past-progressive.png` | `01-overview.png` | Recent-note thumbnail |
|
| 19 |
+
| `note-authentic-texts.png` | `01-overview.png` | Recent-note thumbnail |
|
| 20 |
+
|
| 21 |
+
## Rebuild in code
|
| 22 |
+
|
| 23 |
+
- StudyFlow logo and all sidebar/navigation icons
|
| 24 |
+
- Maya avatar treatment
|
| 25 |
+
- Buttons, inputs, tabs, badges, menus, checkboxes, and toolbars
|
| 26 |
+
- Calendar grid and event blocks
|
| 27 |
+
- Roadmap timeline and progress indicators
|
| 28 |
+
- Charts, confidence bars, storage bars, and status dots
|
| 29 |
+
- PDF/PPTX/JPG file badges and document containers
|
| 30 |
+
- OCR bounding boxes and extraction overlays
|
| 31 |
+
- Quiz answers, navigator, timer, feedback, and source cards
|
| 32 |
+
- All typography, borders, shadows, spacing, and responsive layout
|
| 33 |
+
|
| 34 |
+
## Replication constraint
|
| 35 |
+
|
| 36 |
+
Use only the six canonical references and the extracted assets above. Do not add
|
| 37 |
+
stock photography, illustrations, decorative gradients, or generated imagery.
|
docs/design/open-design/compare_josephine.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
from PIL import Image, ImageDraw
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
root = Path(__file__).resolve().parents[1]
|
| 7 |
+
reference = Image.open(
|
| 8 |
+
root / "docs" / "design" / "reference" / "josephines-tagebuch.png"
|
| 9 |
+
).convert("RGB")
|
| 10 |
+
prototype = Image.open(
|
| 11 |
+
root / "docs" / "design" / "screenshots" / "josephine-home-desktop.png"
|
| 12 |
+
).convert("RGB")
|
| 13 |
+
|
| 14 |
+
height = 1024
|
| 15 |
+
reference.thumbnail((720, height))
|
| 16 |
+
prototype.thumbnail((720, height))
|
| 17 |
+
|
| 18 |
+
canvas = Image.new("RGB", (1440, height + 42), "#f4f1eb")
|
| 19 |
+
canvas.paste(reference, (0, 42))
|
| 20 |
+
canvas.paste(prototype, (720, 42))
|
| 21 |
+
|
| 22 |
+
draw = ImageDraw.Draw(canvas)
|
| 23 |
+
draw.text((18, 13), "REFERENCE", fill="#4b2140")
|
| 24 |
+
draw.text((738, 13), "IMPLEMENTATION", fill="#0f5848")
|
| 25 |
+
|
| 26 |
+
canvas.save(
|
| 27 |
+
root / "docs" / "design" / "screenshots" / "josephine-design-comparison.png",
|
| 28 |
+
optimize=True,
|
| 29 |
+
)
|
docs/design/open-design/extract_assets.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
from PIL import Image
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
ROOT = Path(__file__).resolve().parent
|
| 7 |
+
REFS = ROOT / "references"
|
| 8 |
+
OUT = ROOT / "assets"
|
| 9 |
+
OUT.mkdir(exist_ok=True)
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def crop(source: str, output: str, box: tuple[int, int, int, int]) -> None:
|
| 13 |
+
with Image.open(REFS / source) as image:
|
| 14 |
+
image.crop(box).save(OUT / output, optimize=True)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
crop("02-uploads-ocr.png", "handwritten-calendar.png", (652, 220, 1136, 742))
|
| 18 |
+
crop("03-study-canvas.png", "lecture-slide-scaffolding.png", (355, 228, 653, 810))
|
| 19 |
+
crop("03-study-canvas.png", "handwritten-scaffolding-notes.png", (677, 242, 1103, 811))
|
| 20 |
+
crop("05-flashcards.png", "flashcard-source-slide.png", (1135, 216, 1299, 338))
|
| 21 |
+
crop("05-flashcards.png", "flashcard-source-notes.png", (1135, 443, 1304, 566))
|
| 22 |
+
|
| 23 |
+
crop("01-overview.png", "note-code-switching.png", (263, 883, 347, 974))
|
| 24 |
+
crop("01-overview.png", "note-motivation.png", (512, 883, 600, 974))
|
| 25 |
+
crop("01-overview.png", "note-past-progressive.png", (758, 883, 846, 974))
|
| 26 |
+
crop("01-overview.png", "note-authentic-texts.png", (1042, 883, 1130, 974))
|
docs/design/reference/josephines-tagebuch.png
ADDED
|
Git LFS Details
|
docs/design/reviews/design-html-review.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Design HTML Finalization
|
| 2 |
+
|
| 3 |
+
Date: 2026-06-06
|
| 4 |
+
Mode: evolve existing standalone mockup
|
| 5 |
+
Screen: StudyFlow AI Lehramt Arbeitszentrale
|
| 6 |
+
Framework: vanilla HTML, CSS, JavaScript
|
| 7 |
+
|
| 8 |
+
## Implementation Spec
|
| 9 |
+
|
| 10 |
+
- Layout: responsive dashboard with fixed desktop sidebar, collapsed icon rail at tablet width, stacked mobile workspace.
|
| 11 |
+
- Typography: Fraunces display headings, IBM Plex Sans interface text, Material Symbols Rounded icons.
|
| 12 |
+
- Palette: warm paper background, green primary action/state, blue informational accents, amber/red priority states, dark-mode token overrides.
|
| 13 |
+
- Components: navigation rail, local-processing trust panel, upload status strip, material-linking workspace, AI action list, extraction review table, daily focus, knowledge gaps, roadmap, subject progress.
|
| 14 |
+
|
| 15 |
+
## Design HTML Pass
|
| 16 |
+
|
| 17 |
+
- Added explicit theme metadata and dark-mode CSS token overrides.
|
| 18 |
+
- Added named breakpoint refinements for 1440px, 1024px, 768px, and 375px.
|
| 19 |
+
- Made the primary headline and subheading editable for demo iteration.
|
| 20 |
+
- Added a small ResizeObserver and MutationObserver pass for editable copy overflow.
|
| 21 |
+
- Kept the existing live app as the source of truth instead of generating a separate detached artifact.
|
| 22 |
+
|
| 23 |
+
## Verification
|
| 24 |
+
|
| 25 |
+
- UI checker: passed via `uv run python ui_ux_check.py`.
|
| 26 |
+
- Desktop screenshot: `studyflow-design-html-desktop.png`
|
| 27 |
+
- Mobile screenshot: `studyflow-design-html-mobile.png`
|
| 28 |
+
- Narrow screenshot: `studyflow-design-html-375.png`
|
docs/design/reviews/design-qa.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Product Design QA: Josephines Tagebuch
|
| 2 |
+
|
| 3 |
+
Reference: `open-design/josephine-concepts/02-josephines-tagebuch.png`
|
| 4 |
+
|
| 5 |
+
Implementation: `josephine-home-desktop.png`
|
| 6 |
+
|
| 7 |
+
Comparison: `josephine-design-comparison.png`
|
| 8 |
+
|
| 9 |
+
## Visual Match
|
| 10 |
+
|
| 11 |
+
- Desktop shell, navigation width, hero proportion, editorial typography, and
|
| 12 |
+
two-column daily agenda closely match the selected 1440 x 1024 reference.
|
| 13 |
+
- Josephine's generated Mainz morning scene preserves the intended story:
|
| 14 |
+
family, friends, horse, bread, cheese, aubergine accents, and teacher studies.
|
| 15 |
+
- The hierarchy stays personal and spacious. Josephine's name is the dominant
|
| 16 |
+
first-viewport signal and the agenda reads as one continuous surface.
|
| 17 |
+
- Colors, fine rules, restrained radii, and icon treatment are consistent with
|
| 18 |
+
the reference.
|
| 19 |
+
- The mobile layout was separately checked and reflows the hero copy below the
|
| 20 |
+
illustration without overlap.
|
| 21 |
+
|
| 22 |
+
## Functional Check
|
| 23 |
+
|
| 24 |
+
- All nine navigation destinations load successfully.
|
| 25 |
+
- Timeline and "Heute wichtig" rows navigate to their related tools.
|
| 26 |
+
- Materials retains upload and OCR functionality.
|
| 27 |
+
- Existing canvas, calendar, cards, and quiz interactions remain available.
|
| 28 |
+
- Browser console: no warnings or errors.
|
| 29 |
+
|
| 30 |
+
## Remaining P3 Polish
|
| 31 |
+
|
| 32 |
+
- The generated hero is a fresh image in the selected art direction rather
|
| 33 |
+
than a pixel-identical crop of the concept image.
|
| 34 |
+
- The small decorative horse sketch from the concept is intentionally omitted;
|
| 35 |
+
the horse is already represented prominently in the hero.
|
| 36 |
+
|
| 37 |
+
final result: passed
|
docs/design/reviews/design-review.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# StudyFlow AI Design Review
|
| 2 |
+
|
| 3 |
+
Review lenses: ui-ux-pro-max and gstack design-review.
|
| 4 |
+
URL: http://localhost:4173
|
| 5 |
+
Screenshots:
|
| 6 |
+
- Desktop: studyflow-review-desktop.png
|
| 7 |
+
- Mobile: studyflow-review-mobile.png
|
| 8 |
+
|
| 9 |
+
## Result
|
| 10 |
+
|
| 11 |
+
Design Score: B+ -> A-
|
| 12 |
+
AI Slop Score: B -> A-
|
| 13 |
+
Goodwill: 78/100 -> 86/100
|
| 14 |
+
|
| 15 |
+
The dashboard now reads as a credible app UI rather than a generic generated dashboard. The strongest parts are the German-first Lehramt framing, clear privacy promise, tight study workflow, and visible links between documents, deadlines, gaps, and actions.
|
| 16 |
+
|
| 17 |
+
## Fixes Applied
|
| 18 |
+
|
| 19 |
+
0. Added a `uv`-run UI/UX checker.
|
| 20 |
+
- Why: gives the mockup a repeatable quality gate for accessibility, interaction, motion, mobile behavior, and token hygiene.
|
| 21 |
+
|
| 22 |
+
1. Replaced the CSS-drawn brand mark with an icon-library mark.
|
| 23 |
+
- Why: avoids fake visual assets and improves consistency with the icon system.
|
| 24 |
+
|
| 25 |
+
2. Tightened navigation labels and mobile navigation behavior.
|
| 26 |
+
- Why: German labels were wrapping/clipping awkwardly, especially on mobile.
|
| 27 |
+
|
| 28 |
+
3. Improved accessibility and interaction states.
|
| 29 |
+
- Added visible focus styles, `aria-label`s for icon-only table actions, visited-link styling, and reduced-motion handling.
|
| 30 |
+
|
| 31 |
+
4. Increased critical touch targets.
|
| 32 |
+
- Language buttons, profile, table action buttons, and primary action rows now have more reliable hit areas.
|
| 33 |
+
|
| 34 |
+
5. Added mobile-specific table behavior.
|
| 35 |
+
- The linked-materials area now becomes compact row cards on small screens instead of forcing a desktop spreadsheet layout.
|
| 36 |
+
|
| 37 |
+
6. Added a skip link and better action feedback.
|
| 38 |
+
- The upload review CTA now has a short loading state, and keyboard users can jump straight to the main dashboard.
|
| 39 |
+
|
| 40 |
+
7. Reduced ad-hoc color usage.
|
| 41 |
+
- Repeated colors now route through semantic CSS tokens; the final `uv` checker reports only 26 raw hex values outside `:root`.
|
| 42 |
+
|
| 43 |
+
## Remaining Tradeoffs
|
| 44 |
+
|
| 45 |
+
- The desktop dashboard intentionally stays dense because it is meant to impress in a hackathon demo.
|
| 46 |
+
- Mobile is now readable, but it is still a secondary presentation mode. A production app would likely need a dedicated mobile information architecture.
|
| 47 |
+
- The mockup still uses realistic sample data rather than real uploaded notes/slides.
|
| 48 |
+
|
| 49 |
+
## uv Check
|
| 50 |
+
|
| 51 |
+
Command:
|
| 52 |
+
|
| 53 |
+
```powershell
|
| 54 |
+
$env:UV_CACHE_DIR='C:\Users\Lenovo\Documents\build-small-hackathon\.uv-cache'; $env:UV_PYTHON_INSTALL_DIR='C:\Users\Lenovo\Documents\build-small-hackathon\.uv-python'; uv run python ui_ux_check.py
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
Result: all checks passed.
|
| 58 |
+
|
| 59 |
+
## Emil Design Engineering Pass
|
| 60 |
+
|
| 61 |
+
Ran the Emil-style animation and interaction review after the UI/UX pass. See `emil-design-review.md` for the before/after table. The main changes were custom easing tokens, scale-based press feedback, pointer-gated hover states, and cleaner toast/status transitions.
|
| 62 |
+
|
| 63 |
+
## Browser Annotation Fix
|
| 64 |
+
|
| 65 |
+
Fixed the `Lernroadmap` section after review in the browser. The prior timeline placed detail text into the wrong implicit grid cells, which made short phrases wrap vertically and feel broken. The updated roadmap wraps each entry into a structured row with a timeline marker, date label, main task, and supporting detail, plus clearer active and exam states.
|
| 66 |
+
|
| 67 |
+
## Frontend Design Pass
|
| 68 |
+
|
| 69 |
+
Ran the `frontend-design` skill as a final aesthetic pass. See `frontend-design-review.md`. The main update was moving from generic dashboard polish toward a refined academic study-ledger identity with more distinctive typography, subtle paper-grid atmosphere, and clearer first-screen narrative labels.
|
| 70 |
+
|
| 71 |
+
## Final Notes
|
| 72 |
+
|
| 73 |
+
Quickest next polish would be adding one real-looking handwritten note thumbnail and one real-looking lecture slide thumbnail. That would make the demo feel less synthetic without adding much complexity.
|
docs/design/reviews/emil-design-review.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Emil Design Engineering Review
|
| 2 |
+
|
| 3 |
+
Skill lens: Emil Kowalski-style design engineering polish.
|
| 4 |
+
URL: http://localhost:4173
|
| 5 |
+
|
| 6 |
+
| Before | After | Why |
|
| 7 |
+
| --- | --- | --- |
|
| 8 |
+
| Press feedback used `translateY(1px)` | Press feedback uses `transform: scale(0.97)` | Scaling the whole control gives a more tactile "the UI heard me" response without nudging layout rhythm. |
|
| 9 |
+
| Transitions used built-in `ease` / `.2s` shorthand | Transitions use explicit properties with `--ease-out: cubic-bezier(0.23, 1, 0.32, 1)` | Custom ease-out starts fast and feels more responsive for dashboard UI. |
|
| 10 |
+
| Hover states applied globally | Hover states are gated behind `@media (hover: hover) and (pointer: fine)` | Touch devices should not get sticky hover behavior after tapping. |
|
| 11 |
+
| Toast entered from a hardcoded `translateY(22px)` | Toast enters from `translateY(100%)` with transform/opacity only | Percentage translation adapts to the toast's own height and stays GPU-friendly. |
|
| 12 |
+
| Status strip transition used generic `ease` | Status strip uses exact max-height, padding, and opacity timings | The reveal feels intentional without animating expensive layout properties broadly. |
|
| 13 |
+
| Reduced-motion disabled all motion but left press transforms available | Reduced-motion now removes active transforms too | Users requesting reduced motion should not get unnecessary movement feedback. |
|
| 14 |
+
|
| 15 |
+
## Verification
|
| 16 |
+
|
| 17 |
+
- `uv run python ui_ux_check.py`: passed.
|
| 18 |
+
- Desktop screenshot: `studyflow-emil-desktop.png`.
|
| 19 |
+
- Mobile screenshot: `studyflow-emil-mobile.png`.
|
| 20 |
+
- Local server health: `http://localhost:4173` returned `200`.
|
docs/design/reviews/frontend-design-review.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Frontend Design Pass
|
| 2 |
+
|
| 3 |
+
Skill lens: `frontend-design`.
|
| 4 |
+
URL: http://localhost:4173
|
| 5 |
+
|
| 6 |
+
## Direction
|
| 7 |
+
|
| 8 |
+
Refined academic study ledger: a private, German-first learning workspace with a quiet paper-grid atmosphere, editorial title typography, and a stronger sense that StudyFlow connects real study artifacts rather than simply listing dashboard cards.
|
| 9 |
+
|
| 10 |
+
## Changes
|
| 11 |
+
|
| 12 |
+
- Replaced the generic Inter-only type system with `Fraunces` for display type and `IBM Plex Sans` for interface text.
|
| 13 |
+
- Added a subtle paper-grid workspace texture so the app feels closer to study material and notebooks.
|
| 14 |
+
- Gave the primary material-linking panel a stronger visual identity with a soft gradient surface and accent spine.
|
| 15 |
+
- Added section kickers such as `01 / Materialabgleich` and `02 / Nächster Schritt` to make the first-screen story clearer.
|
| 16 |
+
- Lifted document tables into contained study-document surfaces so the linked slide/note area feels more intentional.
|
| 17 |
+
- Preserved the existing accessibility and interaction polish from the UI/UX and Emil passes.
|
| 18 |
+
|
| 19 |
+
## Verification
|
| 20 |
+
|
| 21 |
+
- `uv run python ui_ux_check.py`: passed.
|
| 22 |
+
- Desktop screenshot: `studyflow-frontend-design-desktop.png`.
|
| 23 |
+
- Mobile screenshot: `studyflow-frontend-design-mobile.png`.
|
| 24 |
+
- Local server health: `http://localhost:4173` returned `200`.
|
docs/design/screenshots/josephine-design-comparison.png
ADDED
|
Git LFS Details
|
docs/design/screenshots/josephine-home-desktop.png
ADDED
|
Git LFS Details
|
server.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const http = require("http");
|
| 2 |
+
const fs = require("fs");
|
| 3 |
+
const path = require("path");
|
| 4 |
+
|
| 5 |
+
const root = __dirname;
|
| 6 |
+
const port = process.env.PORT || 4173;
|
| 7 |
+
|
| 8 |
+
const types = {
|
| 9 |
+
".html": "text/html; charset=utf-8",
|
| 10 |
+
".css": "text/css; charset=utf-8",
|
| 11 |
+
".js": "application/javascript; charset=utf-8",
|
| 12 |
+
".png": "image/png",
|
| 13 |
+
".jpg": "image/jpeg",
|
| 14 |
+
".jpeg": "image/jpeg",
|
| 15 |
+
".svg": "image/svg+xml",
|
| 16 |
+
};
|
| 17 |
+
|
| 18 |
+
const server = http.createServer((req, res) => {
|
| 19 |
+
const urlPath = decodeURIComponent(req.url.split("?")[0]);
|
| 20 |
+
const filePath = path.join(root, urlPath === "/" ? "index.html" : urlPath);
|
| 21 |
+
|
| 22 |
+
if (!filePath.startsWith(root)) {
|
| 23 |
+
res.writeHead(403);
|
| 24 |
+
res.end("Forbidden");
|
| 25 |
+
return;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
fs.readFile(filePath, (err, data) => {
|
| 29 |
+
if (err) {
|
| 30 |
+
res.writeHead(404);
|
| 31 |
+
res.end("Not found");
|
| 32 |
+
return;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
res.writeHead(200, { "Content-Type": types[path.extname(filePath)] || "application/octet-stream" });
|
| 36 |
+
res.end(data);
|
| 37 |
+
});
|
| 38 |
+
});
|
| 39 |
+
|
| 40 |
+
server.listen(port, () => {
|
| 41 |
+
console.log(`StudyFlow mockup running at http://localhost:${port}`);
|
| 42 |
+
});
|
tools/open-design-proxy.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const http = require("node:http");
|
| 2 |
+
|
| 3 |
+
const targetPort = Number(process.env.OD_TARGET_PORT || 49540);
|
| 4 |
+
const listenPort = Number(process.env.OD_PROXY_PORT || 7456);
|
| 5 |
+
|
| 6 |
+
const server = http.createServer((request, response) => {
|
| 7 |
+
const proxy = http.request(
|
| 8 |
+
{
|
| 9 |
+
hostname: "127.0.0.1",
|
| 10 |
+
port: targetPort,
|
| 11 |
+
path: request.url,
|
| 12 |
+
method: request.method,
|
| 13 |
+
headers: request.headers,
|
| 14 |
+
},
|
| 15 |
+
(upstream) => {
|
| 16 |
+
response.writeHead(upstream.statusCode || 502, upstream.headers);
|
| 17 |
+
upstream.pipe(response);
|
| 18 |
+
}
|
| 19 |
+
);
|
| 20 |
+
|
| 21 |
+
proxy.on("error", (error) => {
|
| 22 |
+
response.writeHead(502, { "content-type": "application/json" });
|
| 23 |
+
response.end(JSON.stringify({ error: error.message }));
|
| 24 |
+
});
|
| 25 |
+
|
| 26 |
+
request.pipe(proxy);
|
| 27 |
+
});
|
| 28 |
+
|
| 29 |
+
server.listen(listenPort, "127.0.0.1");
|
tools/ui_ux_check.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
import re
|
| 3 |
+
|
| 4 |
+
root = Path(__file__).parent
|
| 5 |
+
html = (root / "index.html").read_text(encoding="utf-8")
|
| 6 |
+
css = (root / "styles.css").read_text(encoding="utf-8")
|
| 7 |
+
|
| 8 |
+
checks = []
|
| 9 |
+
|
| 10 |
+
def add(name, passed, detail):
|
| 11 |
+
checks.append((name, passed, detail))
|
| 12 |
+
|
| 13 |
+
add("viewport", 'name="viewport"' in html and "initial-scale=1" in html, "viewport meta should allow responsive scaling")
|
| 14 |
+
add("skip_link", "skip-link" in html, "keyboard users should be able to skip navigation")
|
| 15 |
+
add("focus_visible", ":focus-visible" in css, "visible focus rings should be present")
|
| 16 |
+
add("reduced_motion", "prefers-reduced-motion" in css, "motion should respect reduced motion")
|
| 17 |
+
add("visited_links", "a:visited" in css, "visited links should remain distinguishable")
|
| 18 |
+
add("no_horizontal_body", "overflow-x: hidden" in css, "body should avoid horizontal drift")
|
| 19 |
+
add("touch_action", "touch-action: manipulation" in css, "tap delay should be minimized")
|
| 20 |
+
add("aria_labels", html.count("aria-label") >= 6, "icon-only/profile controls need labels")
|
| 21 |
+
add("mobile_breakpoint", "@media (max-width: 820px)" in css, "small-screen layout rules should exist")
|
| 22 |
+
add("button_feedback", ":active" in css, "tap/press states should feel responsive")
|
| 23 |
+
|
| 24 |
+
small_fonts = [float(x) for x in re.findall(r"font-size:\s*([0-9.]+)px", css) if float(x) < 11]
|
| 25 |
+
add("tiny_text", not small_fonts, f"avoid unreadable text smaller than 11px; found {small_fonts}")
|
| 26 |
+
|
| 27 |
+
css_without_root = re.sub(r":root\s*\{.*?\}\s*", "", css, flags=re.S)
|
| 28 |
+
raw_hex_count = len(re.findall(r"#[0-9a-fA-F]{3,8}", css_without_root))
|
| 29 |
+
add("semantic_color_bias", raw_hex_count <= 35, f"prefer semantic CSS tokens outside :root; raw hex count is {raw_hex_count}")
|
| 30 |
+
|
| 31 |
+
failed = [item for item in checks if not item[1]]
|
| 32 |
+
for name, passed, detail in checks:
|
| 33 |
+
print(f"{'PASS' if passed else 'FAIL'} {name}: {detail}")
|
| 34 |
+
|
| 35 |
+
raise SystemExit(1 if failed else 0)
|