20 KiB
Handoff: Tutormanager — Anwesenheit & Notizen
Overview
Tutormanager is an attendance + per-student notes tool for tutorien. The flagship use case is the FP-Tutorium (~19 students, Thursdays). Tutors run a short check-in routine at the start of each Projektstunde: open a slot, project a check-in URL on the beamer, watch students fill the seat map from their phones/laptops, lock the slot, and use the rest of the hour with a live seat plan that lets them jot per-student observations as they go.
The full architecture is described in SPEC.md (your existing approved spec). This handoff covers only the frontend. The HTML prototypes in this folder are design references — pixel-accurate mocks of the intended look and behaviour. The task is to recreate them in SvelteKit (adapter-static, SPA) per the approved stack, talking to the Rust/Axum backend defined in the spec.
About the Design Files
The files in this bundle are design references created with React + inline JSX in static HTML. Don't ship them. Re-implement the same screens as Svelte components in the SvelteKit app, using your own component split and your own state stores. The HTML/CSS choices (paper background, ruled notebook, oxblood accent, Source Serif 4 + Inter + JetBrains Mono + Caveat) should transfer 1:1.
Open Tutormanager.html in a browser to view the canvas with all screens. styles.css holds the design tokens.
Fidelity
High-fidelity. Colors, typography, spacing, and component states are final. Recreate pixel-perfectly in Svelte using the design tokens listed below.
Design Tokens
All in styles.css as CSS custom properties. Bring them in verbatim (e.g. as a app.css imported in +layout.svelte).
Colors
| Token | Value | Usage |
|---|---|---|
--paper |
#f4efe6 |
App background |
--paper-2 |
#ebe4d6 |
Subtle alt rows, avatar bg |
--paper-3 |
#ded4c0 |
Slightly stronger paper |
--rule |
#c9bfa9 |
Borders, dividers |
--rule-soft |
#d9d0bb |
Softer dividers |
--ink |
#1f1b16 |
Primary text, primary buttons |
--ink-2 |
#3a342b |
Body text |
--ink-3 |
#6b6356 |
Secondary / small |
--ink-4 |
#968b7a |
Tertiary, placeholders |
--accent |
#8a2c1f |
Oxblood — accents, "absent", lock, stamps |
--accent-soft |
#c66a5b |
— |
--highlight |
#f1d36a |
Highlighter yellow |
--highlight-soft |
#f5e3a4 |
Marker underline fill |
--green |
#4a6b3a |
"anwesend" / present |
--red |
#8a2c1f |
"fehlt" / locked |
--amber |
#b07d2a |
"offen" / open slot |
Card surface is #fbf7ee (slightly lighter than paper). Seat map background is #f7f1e3. Tables in seat map are #e8dec5 with var(--ink-2) border.
Typography
| Token | Stack |
|---|---|
--serif |
"Source Serif 4", "Source Serif Pro", "EB Garamond", Georgia, serif |
--sans |
"Inter", "Helvetica Neue", Helvetica, Arial, sans-serif |
--mono |
"JetBrains Mono", "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace |
| Marginalia | "Caveat" (used only for handwritten notes — see .handwritten) |
Load via Google Fonts (single <link>):
Source Serif 4 (300–700, italic), Inter (400/500/600), JetBrains Mono (400/500/600), Caveat (400/500/600)
Type scale (utility classes in styles.css):
.h1— Source Serif 4, 44/1.05, weight 500, letter-spacing -0.02em.h2— Source Serif 4, 28/1.15, weight 500, letter-spacing -0.01em.h3— Source Serif 4, 20/1.2, weight 500.eyebrow— JetBrains Mono, 11px, uppercase, 0.14em tracking,--ink-3.body— Inter, 14/1.5.small— Inter, 12,--ink-3.tiny— Inter, 11,--ink-3
Spacing
8px-aligned grid. Common values seen in the design: 4, 6, 8, 10, 12, 14, 16, 18, 22, 24, 28, 32, 36.
Radii
- 3–4px — most cards, inputs, buttons (paper feel — keep tight)
- 999px — pills
- 50% — avatars, seat circles
Shadows
Avoid heavy shadows. Use a 1px 0 rgba(0,0,0,.03) underline on cards. The seat-map container uses a subtle inset border instead of a box shadow.
Special textures
.paper-bg— paper grain via inline SVG noise + 2 radial highlights. Applied to app root and large surfaces..ruled— repeating-linear-gradient producing 28px-spaced horizontal rules. Used inside the note textarea so handwritten text sits on lines..marker— gradient that paints--highlight-softover the lower 32% of inline text — looks like a hand-drawn highlighter stroke. Use sparingly on h1/h2 keywords (one word per heading)..stamp— rotated -4°, oxblood border + tint, monospace caps. Used for "PRÄSENT"..handwritten— Caveat in oxblood, 18px. For marginalia / sketch annotations only.UnderlineStroke— a tiny SVG hand-drawn underline beneath section titles. Seeshared.jsx.
Information Architecture
/ → redirect to /admin or /admin/login
/admin/login → Login (anonymous)
/admin → Dashboard (slots overview)
/admin/live/:slotId → Hero · live seat map + notes panel
/admin/attendance → Per-student / per-week matrix + export
/admin/rooms → Layout editor (rooms list / canvas / props)
/admin/rooms/:roomId → Same, with active room
/admin/students → Students CRUD
/s/:code → Student check-in (mobile + desktop responsive)
The student check-in screen MUST be responsive: phone gets a single-column compact layout; ≥ 900px viewport gets the two-column laptop layout (map left, side panel right). Both are in this bundle.
Screens
1. Login (/admin/login)
Centered 420px-wide card on .paper-bg. Brand wordmark "Tutor·manager" with oxblood ·. Card title is .h2 "Willkommen zurück" with an UnderlineStroke (110px wide). Two stacked inputs (E-Mail, Passwort), full-width primary button "Anmelden". Below the card a small Caveat marginalia "~ Donnerstags ab 14 Uhr ~".
2. Tutor shell
Two columns: 220px sidebar + main content.
Sidebar (background: rgba(0,0,0,0.015), right border --rule):
- Brand wordmark + version
- "Kurs" card (course name + semester + weekday)
- Nav list: Dashboard / Live · Sitzplan / Anwesenheit / Räume / Studierende. Active item: 4px oxblood dot +
rgba(31,27,22,0.08)background. - Bottom: avatar circle + tutor name + email.
3. Dashboard (/admin)
- Header: eyebrow "Dashboard",
.h1"Diese Woche, Woche 04" (the week number gets.marker), one-line subtitle. Right side: "Export" (ghost) + "Neuer Slot" (primary) buttons. - 4 stat cards in a row: "Anwesend gerade 14 / 19", "Ø Anwesenheit 84%", "Bonus vergeben 186 Punkte", "Offene Notizen 7". Big numbers in serif 32px, accent color when meaningful.
- Slots table: columns Woche / Datum / Zeit / Raum / Status / Code / Eingecheckt / Aktionen. Status pills (open / closed / locked). Eingecheckt cell shows "n / total" + a 48×4px progress bar. Per-row primary action depends on status (Sperren / Öffnen / Anzeigen).
4. Live seat map + notes (HERO — /admin/live/:slotId)
Two-column grid: 1fr 380px.
Left column — seat map:
- Header row: "Sitzplan" (
.h3+ UnderlineStroke 70px) on the left; legend dots on the right (anwesend / frei / ausgewählt). - The seat map itself (see "Seat map component" below) at scale 0.85.
- Tally row at bottom: 3 stat blocks (Anwesend
n/total, Fehlt n, Bonus heute "+n Punkte") + spacer + ghost button "Manuell eintragen".
Right column — roster + note editor (a card):
- Top: "Studierende" with present/absent count.
- Scrollable roster (max-height ~220px). Each row: 22px ink avatar circle, name, oxblood dot if has note, monospace check-in time (or "—" for absent). Selected row:
rgba(31,27,22,0.06)bg + 3px ink left border. Absent rows: 0.55 opacity, dashed avatar border, strikethrough name. - Bottom — note editor on
#fbf7ee:- 32px ink avatar + selected student name (
.h3) + "Sitzplatz {seat} · seit {time}" + "Präsent" stamp on the right. - "Notiz · Woche 04" eyebrow.
- Textarea with
.ruledbackground, Source Serif 4 15/28px, no border. Placeholder "Beobachtungen für diese Woche…". - Quick-tag chips below: "+ aktiv beteiligt", "+ stille:r Kämpfer:in", "+ verstanden ✓", "+ nochmal aufgreifen", "+ Rückfrage offen", "+ elegante Lösung".
- Footer: "Auto-gespeichert · 14:23" left, "Notizen vergangener Wochen ↗" link right.
- 32px ink avatar + selected student name (
Page header above the grid:
- Left side: eyebrow "Tutor:innen-Ansicht · Live", title "Woche 04 · Donnerstag, 30. April 2026" (date with
.marker), subtitle line: course / room / time. - Right side: monospace check-in code "K7QJ-MX2P" with the URL beneath, status pill (offen), Kopieren / Sperren buttons.
5. Anwesenheit (/admin/attendance)
- Header
.h1"Kursmatrix · SS 2026". Right: 3 ghost export buttons (CSV / Markdown / SQLite Backup). - Card with tab bar: "Pro Studierende:r" (active) / "Pro Woche" / "Notizen".
- Matrix table: # / Studierende:r / W01 / W02 / W03 / W04 / Anwesend / Bonus / arrow. Present cell is a 22px square with green check on
rgba(74,107,58,0.14). Absent: em-dash. Bonus column shows a single 24px oxblood circled check (no number — only "got bonus or not").
6. Räume — Layout-Editor (/admin/rooms/:roomId)
Three-column grid: 210px / 1fr / 240px.
- Left: rooms list (selectable, oxblood left border on active), "+ Neuer Raum" button at bottom.
- Center — toolbar + canvas + status bar:
- Toolbar: 6 tool buttons (Auswählen / Sitz / Tisch / Tür / Fenster / Lücke), each with a small monospace glyph + label. Active tool is filled ink. Right side: zoom controls (− 78% +), "Raster: 24px".
- Canvas:
#efe6d2background with rulers along top (px ticks) and left, a centred seat map at scale 0.78. Selected element shows a rotating dashed oxblood ring + 4 small white drag-handle squares. Two Caveat marginalia annotations. - Bottom status bar: element counts, auto-save timestamp.
- Right: stacked cards.
- "Auswahl" card: shows kind + id ("Sitz T2-3"), then editable fields (Bezeichnung, Tisch, X / Y in px, ∅ / Rotation). Below: "Aktionen" — Duplizieren (⌘D), An Tisch ausrichten, Löschen (⌫, oxblood text).
- "Ebenen" card: tree-like list of layers — walls, 4 tables with their seats, podium, beamer, door, window. Selected layer (T2 group) gets oxblood text + tinted bg.
7. Studierende (/admin/students)
Header with title + search input + "CSV importieren" + "Hinzufügen". Card with table: # / Name (avatar + name) / Anwesend (n/4) / Bonus (single circled check or em-dash) / Notizen (oxblood dot + count) / Letzte Sitzung / arrow.
8. Student check-in — phone (/s/:code, viewport < 900px)
Four states (each on .paper-bg, ~360×760 viewport):
- Name picker: eyebrow "Check-in · K7QJ-MX2P", title "Wer bist du?", search input, scrollable name list (avatar + name, divider lines). Footer hint "Nicht in der Liste? Sprich die Tutor:in an."
- Seat picker: "Hallo, Carla 👋" eyebrow, title "Wähle deinen Sitz", scaled seat map (0.46), legend, info card "Du kannst deinen Sitz wechseln, solange der Slot offen ist."
- Confirmed: title "Du sitzt auf T1-3" (T1-3 with
.marker), "Präsent" stamp top-right, scaled map showing only own seat highlighted oxblood, status card with "OFFEN" pill + lock-time hint, ghost "Sitz wechseln" button. - Locked / read-only: title "Anwesenheit erfasst", scaled map (own seat still oxblood), info card with lock icon + "Check-in für diesen Slot ist geschlossen. Bonus wurde gutgeschrieben.", footer with next session date.
9. Student check-in — laptop (/s/:code, viewport ≥ 900px)
Three states:
- Name picker: centered 560px column. Header eyebrow + .h1 "Wer bist du?" + subtitle. Card with autofocused search and a 2-column grid of name buttons. Footer keyboard-shortcut tip.
- Seat picker: two-column
1fr 360px. Left: large seat map (scale 0.78) + legend below. Right side panel (subtle bg, left border): "Sitzung" block (course / date+time / room), "Eingecheckt als" with avatar + "wechseln" link, "Hinweise" bullet list, bottom yellow-tinted card "Bonus +3 Punkte sobald du einen Sitz wählst." - Confirmed (with season): same two-column. Left: header "Du sitzt auf T1-3" + stamp, large map with own seat highlighted, two ghost buttons (Sitz wechseln / Drucken). Right side: "Slot-Status" with open pill, "Deine Saison" — 4-row weekly history list (W01–W04, date, seat code, green check or em-dash; current week in oxblood), oxblood-tinted card "Bonus gesamt 9 / 12 Punkte · 3 von 4 Tutorien besucht".
Components to build
| Svelte component | Notes |
|---|---|
<StatusPill status="open" | "closed" | "locked" | "present" | "absent" /> |
See .pill and .pill.{state} in styles.css. |
<UnderlineStroke width={110} color="..." /> |
Inline SVG path — see shared.jsx. |
<Stamp>Präsent</Stamp> |
.stamp class. |
<Eyebrow> / <H1> / <H2> / <H3> |
Or just utility classes. |
<Tally label value total suffix accent /> |
Stat block in serif. |
<StatCard label value suffix hint accent /> |
Card variant for the dashboard. |
<Field label suffix mono /> |
Labelled input with optional unit suffix in mono. |
<TutorShell active> |
Sidebar + main slot. |
<SeatMap variant="tutor" | "student" | "student-self" assignments selectedStudent ownSeat onSeatClick scale /> |
The big one — see below. |
<NoteEditor studentId weekNr /> |
Roster + ruled textarea + tag chips. |
<MarkerText> |
Inline span with .marker. |
<SeatMap> — the central component
Top-down floor plan, absolutely-positioned layers inside a fixed-size design space (760×460 px) that gets transform-scaled by the scale prop.
Static structural layers (drawn in this order):
- Inner ruled grid (24px graph paper, very low alpha).
- Walls — 2px ink-2 border rounded 2px.
- Window — 6×220px tall blue-tinted strip on the left wall with a horizontal split line. Mono "Fenster" label rotated -90°.
- Door — gap in bottom wall (70×4px) covered with paper colour, an SVG door arc (
stroke-dasharray="2 2"), and a "Tür" label. - Beamer — small black bar at top.
- Podium / Pult — 190×38 ruled rectangle, mono caps "Pult · Tutor:in".
- Tables — 4 rectangles (200×70px) at fixed coords. Filled
#e8dec5with ink-2 border + italic serif label "T1"–"T4" centred at 0.35 alpha.
Seats — generated from tables: 5 per table.
- Top edge (y = table.y − 22): 2 seats at x = table.x + table.w*{0.28, 0.72}.
- Bottom edge (y = table.y + table.h + 22): 2 seats, same x ratios.
- Head (short edge, x = table.x + table.w + 26): 1 seat at table.y + table.h/2.
Seat = 36px circle button, 1.5px border. Per variant:
- tutor: occupied seat shows ink avatar with student initials in white. Selected: filled ink + 3px highlight-soft outer ring. Hover:
#e0d4b6fill. Free: paper bg + ink-4 border. - student: occupied =
#d6cdb5(no name shown — privacy). Free = paper bg + ink-2 border. Own seat (after pick) = oxblood fill, white star ★ glyph. - student-self (read-only): same colours, no clicks, own seat = oxblood + ★, others greyed if occupied.
Compass mark in bottom-right (mono "N" + small SVG arrow).
The layout JSON the backend stores must round-trip these elements: [{id, label, x, y, width, height, type}] with type ∈ "seat" | "table" | "gap" | "door" | "window" | "wall" | "beamer" | "podium". The component takes that JSON as input — don't hardcode the room.
Interactions & state
- Slot status transitions (tutor):
closed → open → locked. Opening generates the 8-char code (server-side per spec). Closed/locked rows show no code. The UI must atomically reflect "open ⇒ has code". - Seat click (tutor live view): selects that student in the roster; note editor scrolls/loads. Double-click could open the per-student timeline.
- Note editor: debounced auto-save (~600ms) → PUT note. Show "Auto-gespeichert · HH:MM" timestamp on success.
- Quick tag chips: on click, append the tag (with leading newline if note non-empty) to the note text, then save.
- Student seat pick: optimistic UI; on HTTP 409 ("seat taken"), revert and toast "Platz schon vergeben — bitte einen anderen wählen."
- Seat change: while
open, click another free seat → confirmation toast → previous seat goes free immediately. - Locked: pointer-events: none on seats; only own seat remains oxblood-highlighted.
- Live polling: poll the slot every 5–8s while open to refresh assignments and check-in count.
- Marker animation: the selection ring in the room editor uses
@keyframes spin 12s linear infinite. Disable inprefers-reduced-motion.
State management
Suggest one Svelte store per domain:
auth— JWT token, current tutor.course— currently selected course (and tutors' available courses).slots— slots for the active course/week.liveSlot— assignments + notes for the slot currently shown in the live view; reactive on poll.room— layout JSON of the room being edited; also handles selection + tool state for the editor.
The check-in page is its own minimal app state: { code, slot, room, ownSeat, ownStudentId }, no auth store.
Copy
All UI is in German. Specific strings used in the design:
- "Anwesend", "Fehlt", "Bonus heute", "Eingecheckt", "Sitzplan", "Studierende", "Notiz · Woche {n}".
- "Manuell eintragen", "Sperren", "Öffnen", "Anzeigen", "Kopieren", "Speichern".
- "Wer bist du?", "Wähle deinen Sitz", "Du sitzt auf {seat}", "Anwesenheit erfasst".
- "Nicht in der Liste? Sprich die Tutor:in an.", "Du kannst deinen Sitz wechseln, solange der Slot offen ist."
- Status pill labels: OFFEN / GESCHL. / GESPERRT / ANWESEND / FEHLT.
- Stamp: PRÄSENT.
The sample course is "Funktionale Programmierung", semester "SS 2026", room "BC2 1.103", weekday "Donnerstag", tutorin "Lina Puchstein". Replace with real data from the backend.
Assets
- Fonts: Source Serif 4, Inter, JetBrains Mono, Caveat — all from Google Fonts.
- Icons: tiny inline SVGs in
shared.jsx(Icon.check,Icon.x,Icon.lock,Icon.open,Icon.copy,Icon.edit,Icon.download,Icon.arrow,Icon.search,Icon.plus). Re-use them as Svelte components or swap to Lucide if you prefer — they're trivial. - Paper grain: inline SVG noise filter in
.paper-bg. Copy verbatim. - No image assets.
Files in this bundle
| File | What's in it |
|---|---|
Tutormanager.html |
Entry point — design canvas hosting all artboards. Open in a browser. |
styles.css |
All design tokens + utility classes. Copy into the SvelteKit app. |
shared.jsx |
Sample course + students + room layout + StatusPill, UnderlineStroke, Icon.* |
seatmap.jsx |
<SeatMap> and the <TutorLiveView> hero — main reference. |
admin.jsx |
Tutor shell, Dashboard, Anwesenheit-Matrix, Studierende, Login. |
rooms.jsx |
Layout editor with toolbar, canvas, properties + layers panel. |
student.jsx |
Phone variants of the four student states. |
student-desktop.jsx |
Laptop variant of the three student states. |
design-canvas.jsx, browser-window.jsx, ios-frame.jsx |
Presentation chrome — for the design canvas only, do not port. |
The design canvas grouping (sections / artboards) is purely a presentation device. The actual app has the route structure listed under "Information Architecture".
Implementation tips
- Start with
styles.cssand the type/colour tokens. Wire fonts. - Build
<SeatMap>early — it's the centrepiece and the only non-trivial component. - The room editor's drag-and-drop is real work — the spec says layouts are stored as JSON. A DnD library (
svelte-dnd-action) is fine; snap to a 24px grid. The visual shown is the finished state with one seat selected. - Do not add visual flourishes (more icons, gradients, hero images). The look comes from restraint + the paper texture + the marker/stamp accents.
- Respect
prefers-reduced-motion— disable the room-editor selection-ring spin and any hover scale. - The handwritten Caveat marginalia is a flavour element. Keep them sparse — at most one per screen.