# 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 ``):
```
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-soft` over 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. See `shared.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 `.ruled` background, 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.
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: `#efe6d2` background 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):
1. **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."
2. **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."
3. **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.
4. **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:
1. **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.
2. **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."
3. **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 |
|---|---|
| `` | See `.pill` and `.pill.{state}` in `styles.css`. |
| `` | Inline SVG path — see `shared.jsx`. |
| `Präsent` | `.stamp` class. |
| `` / `
` / `
` / `
` | Or just utility classes. |
| `` | Stat block in serif. |
| `` | Card variant for the dashboard. |
| `` | Labelled input with optional unit suffix in mono. |
| `` | Sidebar + main slot. |
| `` | The big one — see below. |
| `` | Roster + ruled textarea + tag chips. |
| `` | Inline span with `.marker`. |
### `` — 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):
1. Inner ruled grid (24px graph paper, very low alpha).
2. Walls — 2px ink-2 border rounded 2px.
3. Window — 6×220px tall blue-tinted strip on the left wall with a horizontal split line. Mono "Fenster" label rotated -90°.
4. Door — gap in bottom wall (70×4px) covered with paper colour, an SVG door arc (`stroke-dasharray="2 2"`), and a "Tür" label.
5. Beamer — small black bar at top.
6. Podium / Pult — 190×38 ruled rectangle, mono caps "Pult · Tutor:in".
7. Tables — 4 rectangles (200×70px) at fixed coords. Filled `#e8dec5` with 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: `#e0d4b6` fill. 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 in `prefers-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` | `` and the `` 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.css` and the type/colour tokens. Wire fonts.
- Build `` 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.