# 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.