feat: "Heute" start page with one next step, clear task states and motion levels

Start page (AuDHD-oriented, per the agreed "Heute zuerst" layout):
- One highlighted next step: started work first (easy re-entry after an
  interruption), then overdue, due today, then urgent undated tasks.
  Primary action "Erledigt", secondary "Beginnen", link to the related
  colony/apiary/queen.
- "Außerdem heute": at most five further tasks with a large honey-cell
  checkbox; undated automatic suggestions are included so a queenless
  colony does not sit unseen in the full list.
- Completing is the one authored motion moment: the cell fills, the tick
  draws, the row folds away and the next step arrives with a short focus
  pull. Every completion can be undone from the toast (10 s), restoring
  the previous status.
- Status is shown as icon plus word (never colour alone); tasks created by
  automation carry a "Vorschlag" badge, recurring and inspection tasks
  their own - a suggestion never looks like work already done.
- Friendly empty, loading and error states; key figures moved into a
  compact overview below; overdue tasks are no longer listed twice.

Motion: per-device setting Voll / Dezent / Aus in the profile (default
Voll); the OS reduce-motion setting always lowers Voll to Dezent.
New 44 px "touch" button size for gloves at the apiary; themed text
selection and caret.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-09-27 13:00:44 +02:00
1 parent 5c2ea7bf1e
commit f9374a135a
14 files changed
+1104 -112

No files matched your search

+175
View File
@@ -198,4 +198,179 @@
font-family: var(--font-heading);
letter-spacing: -0.01em;
}
::selection {
background: color-mix(in oklch, var(--primary) 28%, transparent);
color: var(--foreground);
}
input,
textarea {
caret-color: var(--primary);
}
}
/* ---------------------------------------------------------------------------
Motion levels (see lib/motion/motion-preference.ts)
html[data-motion] = full | subtle | off, chosen per device in the profile.
The OS "reduce motion" setting always wins over "full".
`motion-full:` = spatial movement allowed (level full AND system allows).
------------------------------------------------------------------------ */
@custom-variant motion-full {
@media (prefers-reduced-motion: no-preference) {
&:where(:root:not([data-motion="subtle"]):not([data-motion="off"]) *) {
@slot;
}
}
}
:root {
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
}
/* Off: every state change is instant. Colour and icon still carry the state. */
:root[data-motion="off"] *,
:root[data-motion="off"] *::before,
:root[data-motion="off"] *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
transition-delay: 0ms !important;
}
/* Subtle / system reduce: keep fades, drop movement. */
:root[data-motion="subtle"] .motion-safe\:animate-page-in {
animation-name: fade-in;
}
@media (prefers-reduced-motion: reduce) {
.motion-safe\:animate-page-in {
animation: fade-in 160ms ease-out;
}
}
:root[data-motion="subtle"] [data-slot="card"] {
translate: none !important;
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* --- Task completion: the one authored moment ------------------------------
The honey cell fills, the tick draws itself, then the row folds away. */
.honey-check {
position: relative;
display: inline-grid;
place-items: center;
flex-shrink: 0;
width: 2.75rem;
height: 2.75rem;
border-radius: 9999px;
color: var(--primary-foreground);
outline: none;
}
.honey-check::before {
content: "";
position: absolute;
inset: 0.375rem;
border-radius: 0.6rem;
border: 2px solid color-mix(in oklch, var(--primary) 65%, var(--border));
background: var(--card);
transition:
border-color 150ms ease-out,
background-color 150ms ease-out;
}
.honey-check::after {
content: "";
position: absolute;
inset: 0.375rem;
border-radius: 0.6rem;
background: var(--gradient-honey);
clip-path: circle(0% at 50% 60%);
transition: clip-path 420ms var(--ease-out-expo);
}
.honey-check:hover::before {
border-color: var(--primary);
background: color-mix(in oklch, var(--primary) 8%, var(--card));
}
.honey-check:focus-visible::before {
box-shadow: 0 0 0 3px color-mix(in oklch, var(--ring) 45%, transparent);
}
.honey-check svg {
position: relative;
z-index: 1;
width: 1.1rem;
height: 1.1rem;
stroke-dasharray: 24;
stroke-dashoffset: 24;
transition: stroke-dashoffset 260ms var(--ease-out-expo) 160ms;
}
.honey-check[aria-checked="true"]::after {
clip-path: circle(75% at 50% 60%);
}
.honey-check[aria-checked="true"] svg {
stroke-dashoffset: 0;
}
.today-row {
display: grid;
grid-template-rows: 1fr;
opacity: 1;
transition:
grid-template-rows 320ms var(--ease-out-expo) 380ms,
opacity 220ms ease-out 360ms;
}
.today-row > * {
min-height: 0;
overflow: hidden;
}
.today-row[data-leaving="true"] {
grid-template-rows: 0fr;
opacity: 0;
}
/* The next step changes: bring the new one in with a short focus pull. */
@keyframes next-step-in {
from {
opacity: 0;
filter: blur(6px);
transform: translateY(8px);
}
to {
opacity: 1;
filter: blur(0);
transform: none;
}
}
@keyframes next-step-fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.next-step-enter {
animation: next-step-in 480ms var(--ease-out-expo);
}
:root[data-motion="subtle"] .next-step-enter {
animation: next-step-fade 200ms ease-out;
}
:root[data-motion="subtle"] .today-row,
:root[data-motion="subtle"] .honey-check::after {
transition-duration: 120ms;
transition-delay: 0ms;
}
@media (prefers-reduced-motion: reduce) {
.next-step-enter {
animation: next-step-fade 200ms ease-out;
}
.today-row,
.honey-check::after,
.honey-check svg {
transition-duration: 120ms;
transition-delay: 0ms;
}
}