Zeiterfassung + Visual-Polish (Keycap-Buttons, Space Grotesk)
ci / quality (push) Has been cancelled
ci / build (push) Has been cancelled

Zeiterfassung
- Neue Tabelle time_entry mit started_at/ended_at/duration_seconds,
  optional todo_id/bezug, category-Enum (focus/meeting/break/admin/learning/other),
  description, day_entry_id-Verknüpfung. Partial-Unique-Index erzwingt
  max. einen aktiven Timer pro User.
- tRPC time-Router: now, start (stoppt aktiven automatisch), stop,
  listToday (Liste + Tagessumme), update, upsertManual (retroaktiv),
  delete, teamToday (personenübergreifend mit byCategory + aktivem
  Timer pro User).
- TopNav: persistente TimerPill links neben Streak. Quick-Picker mit
  heutigen ToDos, Kategorie-Auswahl und Freitext-Start. Live-Sekundenzähler.
- /heute: TimelineSection mit Edit/Delete pro Eintrag, ToDo-Bindung
  nachträglich änderbar, Block-nachtragen-Dialog. Pro ToDo-Zeile
  ▶-Start- und 🕐-Nachtrag-Button.
- /puls: TeamTimeSection mit aktiver Timer-Anzeige + Tagessumme +
  Kategorie-Breakdown pro User (refresh alle 30s, Live-Tick jede Sekunde).

Visual
- Space Grotesk als Hausschrift (selbst-gehostet, latin + latin-ext)
- Keycap-Button-Stil (Gradient + Druckeffekt) für primary/secondary/destructive
- Neue Helper-Module: format-duration, time-categories

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
dejanandClaude Opus 4.7 committed 2026-06-02 17:27:40 +02:00
1 parent 4f33509a0f
commit 95beb99cd4
23 files changed
+3442 -49

No files matched your search

+106 -1
View File
@@ -1,5 +1,29 @@
@import 'tailwindcss';
/* ------ Hausschrift: Space Grotesk (lokal gehostet, wie tool-ctrl.de) ------- */
@font-face {
font-family: 'Space Grotesk';
font-style: normal;
font-weight: 300 700;
font-display: swap;
src: url(/fonts/space-grotesk-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Space Grotesk';
font-style: normal;
font-weight: 300 700;
font-display: swap;
src: url(/fonts/space-grotesk-latin-ext.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
U+A720-A7FF;
}
/* ------ Brand Tokens (UX-Spec § Visual Design Foundation) ------- */
@theme {
@@ -29,7 +53,7 @@
--color-trend-down: #b91c1c;
/* Typografie */
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-sans: 'Space Grotesk', 'Helvetica Neue', Arial, sans-serif;
--font-mono: 'JetBrains Mono', 'SF Mono', Menlo, monospace;
/* Radii */
@@ -88,3 +112,84 @@ body {
.skip-link:focus {
top: 0;
}
/* ------ Keycap-Buttons (ToolCTRL-Website-Stil) ------- */
.btn-keycap {
position: relative;
border-width: 1.5px;
border-style: solid;
border-radius: 6px;
transition:
transform 0.08s ease,
box-shadow 0.08s ease,
background 0.15s ease,
color 0.15s ease;
}
.btn-keycap:active:not(:disabled) {
transform: translateY(5px);
}
.btn-keycap:disabled {
transform: none;
box-shadow: none;
}
.btn-keycap-solid {
background: linear-gradient(180deg, #3a3a3a 0%, #1a1a1a 55%, #0e0e0e 100%);
color: #ffffff;
border-color: #000000;
box-shadow:
0 5px 0 0 #000000,
0 10px 12px -5px rgba(0, 0, 0, 0.5),
inset 0 1.5px 0 0 rgba(255, 255, 255, 0.2),
inset 0 -6px 9px -10px rgba(0, 0, 0, 0.9);
}
.btn-keycap-solid:hover:not(:disabled) {
background: linear-gradient(180deg, #454545 0%, #232323 55%, #131313 100%);
}
.btn-keycap-solid:active:not(:disabled) {
box-shadow:
0 0 0 0 #000000,
0 3px 5px -2px rgba(0, 0, 0, 0.5),
inset 0 1.5px 0 0 rgba(255, 255, 255, 0.14),
inset 0 -5px 8px -10px rgba(0, 0, 0, 0.85);
}
.btn-keycap-ghost {
background: linear-gradient(180deg, #ffffff 0%, #f1f1ef 60%, #e5e5e2 100%);
color: var(--color-ink);
border-color: #bebebe;
box-shadow:
0 5px 0 0 #b0b0b0,
0 10px 12px -6px rgba(0, 0, 0, 0.25),
inset 0 1.5px 0 0 rgba(255, 255, 255, 0.95),
inset 0 -5px 8px -10px rgba(0, 0, 0, 0.3);
}
.btn-keycap-ghost:hover:not(:disabled) {
background: linear-gradient(180deg, #ffffff 0%, #ececea 60%, #dcdcd9 100%);
}
.btn-keycap-ghost:active:not(:disabled) {
box-shadow:
0 0 0 0 #b0b0b0,
0 3px 5px -2px rgba(0, 0, 0, 0.22),
inset 0 1.5px 0 0 rgba(255, 255, 255, 0.9),
inset 0 -4px 7px -10px rgba(0, 0, 0, 0.28);
}
.btn-keycap-destructive {
background: linear-gradient(180deg, #d54040 0%, #b91c1c 55%, #9a1414 100%);
color: #ffffff;
border-color: #8a1313;
box-shadow:
0 5px 0 0 #7a0f0f,
0 10px 12px -5px rgba(0, 0, 0, 0.4),
inset 0 1.5px 0 0 rgba(255, 255, 255, 0.18);
}
.btn-keycap-destructive:hover:not(:disabled) {
background: linear-gradient(180deg, #e04848 0%, #c92020 55%, #aa1717 100%);
}
.btn-keycap-destructive:active:not(:disabled) {
box-shadow:
0 0 0 0 #7a0f0f,
0 3px 5px -2px rgba(0, 0, 0, 0.4);
}