Files
ApiCore/ApiCore-Frontend/src/app/globals.css
T
Nick.RosenloecherandClaude Opus 5.5 ce68729233 feat(t5): Fokusring für alle, 44-px-Ziele bei Touch, 16-px-Schrift (T5a A5)
- globals.css: eine Basisregel :where(:focus-visible) mit --focus-width
  und --focus-offset (Sonne 3 px), Firefox-:-moz-focusring gleichgezogen;
  --text-xs 0.8125rem.
- Fokus-Codemod (scripts/codemod-t5-focus.mjs): eigene Ringe und
  outline-none raus, sm:h-/sm:size- zu pointer-fine:, Label-Zeilen mit
  focus-ring-within; Skript-Fokus (Lücken-Liste, „Gespeichert“) bleibt.
- Button/Input 44 px, kompakt nur mit Maus; Icon-Knöpfe mit unsichtbarer
  44-px-Trefferfläche bei Touch, Nachbarn mit genug Abstand.
- 16 px in Feldern, Labels, Auswahl, Leerzuständen, Listentiteln.
- .holo-stage auf allen Beute-Bühnen, .stage-card holt den Seitenring.
- Baukasten-Wächter (src/styles/baukasten-guard.spec.ts) mit den Regeln
  aus A3–A5, Ausnahmen mit Grund; Kalender-Auswahl als Inset-Ring.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 18:06:20 +02:00

714 lines
25 KiB
CSS

@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@custom-variant dark (&:where(.dark, .dark *));
@custom-variant sun (&:where([data-contrast="sun"] *));
@custom-variant pointer-fine (@media (pointer: fine));
@custom-variant pointer-coarse (@media (pointer: coarse));
/* Type scale (spec 4.5): text-xs is 13 px (sun: 14 px), only for axes, legends and footers. */
@theme {
--text-xs: 0.8125rem;
--text-xs--line-height: calc(1.125 / 0.8125);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-body);
--font-mono: var(--font-data);
--font-heading: var(--font-heading);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground);
--color-success: var(--success);
--color-success-foreground: var(--success-foreground);
--color-condition-excellent: var(--condition-excellent);
--color-condition-excellent-foreground: var(--condition-excellent-foreground);
--color-condition-good: var(--condition-good);
--color-condition-good-foreground: var(--condition-good-foreground);
--color-condition-average: var(--condition-average);
--color-condition-average-foreground: var(--condition-average-foreground);
--color-condition-weak: var(--condition-weak);
--color-condition-weak-foreground: var(--condition-weak-foreground);
--color-condition-critical: var(--condition-critical);
--color-condition-critical-foreground: var(--condition-critical-foreground);
--color-holo-bg: var(--holo-bg);
--color-holo-edge: var(--holo-edge);
--color-holo-edge-strong: var(--holo-edge-strong);
--color-holo-fill: var(--holo-fill);
--color-holo-label-bg: var(--holo-label-bg);
--color-holo-label-fg: var(--holo-label-fg);
--color-primary-ink: var(--primary-ink);
--color-warning-ink: var(--warning-ink);
--color-destructive-foreground: var(--destructive-foreground);
--color-condition-excellent-ink: var(--condition-excellent-ink);
--color-condition-good-ink: var(--condition-good-ink);
--color-condition-average-ink: var(--condition-average-ink);
--color-condition-weak-ink: var(--condition-weak-ink);
--color-condition-critical-ink: var(--condition-critical-ink);
--color-area-betrieb: var(--area-betrieb);
--color-area-erfassung: var(--area-erfassung);
--color-area-arbeit: var(--area-arbeit);
--color-area-ernte: var(--area-ernte);
--color-area-neutral: var(--area-neutral);
--color-queen-white: var(--queen-white);
--color-queen-yellow: var(--queen-yellow);
--color-queen-red: var(--queen-red);
--color-queen-green: var(--queen-green);
--color-queen-blue: var(--queen-blue);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) * 0.6);
--radius-md: calc(var(--radius) * 0.8);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) * 1.4);
--radius-2xl: calc(var(--radius) * 1.8);
--radius-3xl: calc(var(--radius) * 2.2);
--radius-4xl: calc(var(--radius) * 2.6);
--animate-page-in: page-in 260ms ease-out;
}
@keyframes page-in {
from {
opacity: 0;
transform: translateY(6px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
:root {
--background: oklch(0.968 0.014 85);
--foreground: oklch(0.21 0.03 55);
--card: oklch(0.995 0.004 90);
--card-foreground: oklch(0.21 0.03 55);
--popover: oklch(0.995 0.004 90);
--popover-foreground: oklch(0.21 0.03 55);
--primary: oklch(0.8 0.145 78);
--primary-foreground: oklch(0.21 0.035 55);
--secondary: oklch(0.44 0.07 145);
--secondary-foreground: oklch(0.98 0.015 100);
--muted: oklch(0.925 0.018 82);
--muted-foreground: oklch(0.44 0.03 60);
--accent: oklch(0.935 0.03 78);
--accent-foreground: oklch(0.25 0.045 50);
--destructive: oklch(0.5 0.17 28);
--border: oklch(0.86 0.022 78);
--input: oklch(0.6 0.035 65);
--ring: oklch(0.47 0.115 55);
--warning: oklch(0.79 0.15 80);
--warning-foreground: oklch(0.23 0.04 60);
--success: oklch(0.46 0.08 145);
--success-foreground: oklch(0.98 0.015 100);
--condition-excellent: oklch(0.62 0.13 150);
--condition-excellent-foreground: oklch(0.15 0.04 150);
--condition-good: oklch(0.645 0.13 125);
--condition-good-foreground: oklch(0.17 0.04 125);
--condition-average: oklch(0.665 0.125 88);
--condition-average-foreground: oklch(0.19 0.04 90);
--condition-weak: oklch(0.665 0.14 58);
--condition-weak-foreground: oklch(0.18 0.04 55);
--condition-critical: oklch(0.62 0.17 30);
--condition-critical-foreground: oklch(0.15 0.04 30);
--chart-1: oklch(0.56 0.13 55);
--chart-2: oklch(0.46 0.08 145);
--chart-3: oklch(0.6 0.12 85);
--chart-4: oklch(0.5 0.17 28);
--chart-5: oklch(0.52 0.08 250);
--radius: 0.75rem;
--sidebar: oklch(0.995 0.004 90);
--sidebar-foreground: oklch(0.21 0.03 55);
--sidebar-primary: oklch(0.8 0.145 78);
--sidebar-primary-foreground: oklch(0.21 0.035 55);
--sidebar-accent: oklch(0.935 0.03 78);
--sidebar-accent-foreground: oklch(0.25 0.045 50);
--sidebar-border: oklch(0.86 0.022 78);
--sidebar-ring: oklch(0.47 0.115 55);
--shadow-elevated: 0 1px 2px -1px oklch(0.3 0.05 50 / 8%), 0 8px 24px -8px oklch(0.3 0.05 50 / 16%);
--shadow-soft: 0 1px 2px -1px oklch(0.3 0.05 50 / 6%), 0 4px 12px -4px oklch(0.3 0.05 50 / 10%);
/* Hive "screen": always dark, in light and dark mode (spec 9.3). */
--holo-bg: oklch(0.18 0.03 250);
--holo-edge: oklch(0.72 0.15 243);
--holo-edge-strong: oklch(0.87 0.07 238);
--holo-fill: oklch(0.78 0.14 220 / 10%);
--holo-label-bg: oklch(0.26 0.04 250);
--holo-label-fg: oklch(0.95 0.02 230);
/* T5 tokens (spec 4.2). */
--primary-ink: oklch(0.47 0.115 55);
--warning-ink: oklch(0.5 0.11 65);
--destructive-foreground: oklch(0.985 0.005 30);
--condition-excellent-ink: oklch(0.4 0.09 150);
--condition-good-ink: oklch(0.41 0.09 125);
--condition-average-ink: oklch(0.44 0.09 85);
--condition-weak-ink: oklch(0.46 0.12 52);
--condition-critical-ink: oklch(0.47 0.16 28);
--area-betrieb: oklch(0.46 0.08 150);
--area-erfassung: oklch(0.5 0.12 60);
--area-arbeit: oklch(0.48 0.08 250);
--area-ernte: oklch(0.52 0.11 75);
--area-neutral: oklch(0.45 0.02 60);
--queen-white: oklch(0.97 0.005 90);
--queen-yellow: oklch(0.86 0.17 95);
--queen-red: oklch(0.58 0.2 27);
--queen-green: oklch(0.58 0.15 145);
--queen-blue: oklch(0.52 0.15 255);
--focus-width: 2px;
--focus-offset: 2px;
--control-border-width: 1px;
--comb-stroke-1: 2px;
--comb-stroke-2: 4px;
--comb-stroke-3: 6px;
}
.dark {
--background: oklch(0.19 0.016 55);
--foreground: oklch(0.95 0.018 80);
--card: oklch(0.235 0.018 55);
--card-foreground: oklch(0.95 0.018 80);
--popover: oklch(0.235 0.018 55);
--popover-foreground: oklch(0.95 0.018 80);
--primary: oklch(0.8 0.14 78);
--primary-foreground: oklch(0.19 0.03 55);
--secondary: oklch(0.66 0.08 145);
--secondary-foreground: oklch(0.16 0.02 145);
--muted: oklch(0.28 0.018 55);
--muted-foreground: oklch(0.76 0.025 75);
--accent: oklch(0.31 0.03 60);
--accent-foreground: oklch(0.95 0.018 80);
--destructive: oklch(0.7 0.15 28);
--border: oklch(0.34 0.02 60);
--input: oklch(0.56 0.025 70);
--ring: oklch(0.82 0.13 80);
--warning: oklch(0.8 0.14 80);
--warning-foreground: oklch(0.2 0.03 60);
--success: oklch(0.7 0.09 145);
--success-foreground: oklch(0.16 0.02 145);
--condition-excellent: oklch(0.68 0.12 150);
--condition-excellent-foreground: oklch(0.17 0.04 150);
--condition-good: oklch(0.72 0.12 125);
--condition-good-foreground: oklch(0.18 0.04 125);
--condition-average: oklch(0.77 0.13 90);
--condition-average-foreground: oklch(0.2 0.04 90);
--condition-weak: oklch(0.74 0.13 60);
--condition-weak-foreground: oklch(0.19 0.04 55);
--condition-critical: oklch(0.68 0.16 28);
--condition-critical-foreground: oklch(0.16 0.04 30);
--chart-1: oklch(0.78 0.13 70);
--chart-2: oklch(0.7 0.09 145);
--chart-3: oklch(0.82 0.12 90);
--chart-4: oklch(0.7 0.15 28);
--chart-5: oklch(0.72 0.08 250);
--sidebar: oklch(0.235 0.018 55);
--sidebar-foreground: oklch(0.95 0.018 80);
--sidebar-primary: oklch(0.8 0.14 78);
--sidebar-primary-foreground: oklch(0.19 0.03 55);
--sidebar-accent: oklch(0.31 0.03 60);
--sidebar-accent-foreground: oklch(0.95 0.018 80);
--sidebar-border: oklch(0.34 0.02 60);
--sidebar-ring: oklch(0.82 0.13 80);
--shadow-elevated: 0 1px 2px -1px oklch(0 0 0 / 30%), 0 8px 24px -8px oklch(0 0 0 / 45%);
--shadow-soft: 0 1px 2px -1px oklch(0 0 0 / 25%), 0 4px 12px -4px oklch(0 0 0 / 30%);
/* Hive "screen": always dark, in light and dark mode (spec 9.3). */
--holo-bg: oklch(0.18 0.03 250);
--holo-edge: oklch(0.72 0.15 243);
--holo-edge-strong: oklch(0.87 0.07 238);
--holo-fill: oklch(0.78 0.14 220 / 10%);
--holo-label-bg: oklch(0.26 0.04 250);
--holo-label-fg: oklch(0.95 0.02 230);
/* T5 tokens (spec 4.2). */
--primary-ink: oklch(0.82 0.13 80);
--warning-ink: oklch(0.82 0.13 82);
--destructive-foreground: oklch(0.17 0.03 30);
--condition-excellent-ink: oklch(0.8 0.11 150);
--condition-good-ink: oklch(0.83 0.11 125);
--condition-average-ink: oklch(0.86 0.11 90);
--condition-weak-ink: oklch(0.82 0.11 60);
--condition-critical-ink: oklch(0.78 0.11 28);
--area-betrieb: oklch(0.74 0.09 150);
--area-erfassung: oklch(0.8 0.12 70);
--area-arbeit: oklch(0.76 0.07 250);
--area-ernte: oklch(0.8 0.1 80);
--area-neutral: oklch(0.76 0.02 70);
--queen-white: oklch(0.97 0.005 90);
--queen-yellow: oklch(0.86 0.17 95);
--queen-red: oklch(0.62 0.2 27);
--queen-green: oklch(0.66 0.15 145);
--queen-blue: oklch(0.62 0.15 255);
}
/* Sun ("Sonne"): maximum luminance difference outdoors (spec 4.3). The two sets follow the
order light, dark, sun-light, sun-dark; sun-dark restates everything sun-light sets. */
:root[data-contrast="sun"] {
--background: oklch(1 0 0);
--card: oklch(1 0 0);
--popover: oklch(1 0 0);
--sidebar: oklch(1 0 0);
--foreground: oklch(0.13 0.02 55);
--card-foreground: oklch(0.13 0.02 55);
--popover-foreground: oklch(0.13 0.02 55);
--sidebar-foreground: oklch(0.13 0.02 55);
--primary-foreground: oklch(0.13 0.02 55);
--ring: oklch(0.13 0.02 55);
--muted: oklch(0.95 0.01 80);
--muted-foreground: oklch(0.32 0.03 60);
--border: oklch(0.4 0.03 60);
--input: oklch(0.2 0.03 55);
--primary-ink: oklch(0.38 0.1 52);
--text-sm: 1rem;
--text-xs: 0.875rem;
--focus-width: 3px;
--control-border-width: 2px;
--comb-stroke-1: 3px;
--comb-stroke-2: 5px;
--comb-stroke-3: 7px;
--shadow-soft: none;
--shadow-elevated: none;
/* Hive stage: black screen, pure white edges and label text. */
--holo-bg: oklch(0 0 0);
--holo-edge: oklch(1 0 0);
--holo-edge-strong: oklch(1 0 0);
--holo-label-fg: oklch(1 0 0);
--holo-label-bg: oklch(0.12 0 0);
}
.dark[data-contrast="sun"] {
--background: oklch(0.1 0.005 55);
--card: oklch(0.1 0.005 55);
--popover: oklch(0.1 0.005 55);
--sidebar: oklch(0.1 0.005 55);
--foreground: oklch(0.99 0.005 85);
--card-foreground: oklch(0.99 0.005 85);
--popover-foreground: oklch(0.99 0.005 85);
--sidebar-foreground: oklch(0.99 0.005 85);
--primary-foreground: oklch(0.19 0.03 55);
--ring: oklch(0.99 0.005 85);
--muted: oklch(0.2 0.01 55);
--muted-foreground: oklch(0.88 0.02 80);
--border: oklch(0.7 0.02 70);
--input: oklch(0.92 0.01 80);
--primary-ink: oklch(0.82 0.13 80);
--text-sm: 1rem;
--text-xs: 0.875rem;
--focus-width: 3px;
--control-border-width: 2px;
--comb-stroke-1: 3px;
--comb-stroke-2: 5px;
--comb-stroke-3: 7px;
--shadow-soft: none;
--shadow-elevated: none;
--holo-bg: oklch(0 0 0);
--holo-edge: oklch(1 0 0);
--holo-edge-strong: oklch(1 0 0);
--holo-label-fg: oklch(1 0 0);
--holo-label-bg: oklch(0.12 0 0);
}
/* The hive stage is its own always-dark surface; the page focus ring (--primary-ink) has less
than 3:1 on it (spec 17.8), so the stage draws the ring in its strong edge colour. */
.holo-stage {
--ring: var(--holo-edge-strong);
}
/* A page-coloured card on the stage (the outline panel) takes the page ring back. The root
keeps a copy of its ring for that, resolved for the current set (light, dark, sun). */
html {
--page-ring: var(--ring);
}
.holo-stage .stage-card {
--ring: var(--page-ring);
}
/* Tinted surfaces: set --tint (and --tint-ink) on the element. In sun the tint becomes the
plain card with a 2 px border in the ink instead of a 15 % wash (spec 4.3). */
@layer components {
.tint {
--tint: var(--primary);
--tint-ink: var(--primary-ink);
background-color: color-mix(in oklch, var(--tint) 15%, transparent);
}
}
:root[data-contrast="sun"] .tint {
background-color: var(--card);
border: var(--control-border-width) solid var(--tint-ink);
}
:root[data-contrast="sun"] body header.sticky {
background-color: var(--card);
backdrop-filter: none;
}
/* Print and PDF preview always use the light set (spec 4.1). Kept in step with :root by
contrast.spec.ts. */
@media print {
:root,
.dark,
:root[data-contrast="sun"],
.dark[data-contrast="sun"] {
--background: oklch(0.968 0.014 85);
--foreground: oklch(0.21 0.03 55);
--card: oklch(0.995 0.004 90);
--card-foreground: oklch(0.21 0.03 55);
--popover: oklch(0.995 0.004 90);
--popover-foreground: oklch(0.21 0.03 55);
--primary: oklch(0.8 0.145 78);
--primary-foreground: oklch(0.21 0.035 55);
--secondary: oklch(0.44 0.07 145);
--secondary-foreground: oklch(0.98 0.015 100);
--muted: oklch(0.925 0.018 82);
--muted-foreground: oklch(0.44 0.03 60);
--accent: oklch(0.935 0.03 78);
--accent-foreground: oklch(0.25 0.045 50);
--destructive: oklch(0.5 0.17 28);
--border: oklch(0.86 0.022 78);
--input: oklch(0.6 0.035 65);
--ring: oklch(0.47 0.115 55);
--warning: oklch(0.79 0.15 80);
--warning-foreground: oklch(0.23 0.04 60);
--success: oklch(0.46 0.08 145);
--success-foreground: oklch(0.98 0.015 100);
--condition-excellent: oklch(0.62 0.13 150);
--condition-excellent-foreground: oklch(0.15 0.04 150);
--condition-good: oklch(0.645 0.13 125);
--condition-good-foreground: oklch(0.17 0.04 125);
--condition-average: oklch(0.665 0.125 88);
--condition-average-foreground: oklch(0.19 0.04 90);
--condition-weak: oklch(0.665 0.14 58);
--condition-weak-foreground: oklch(0.18 0.04 55);
--condition-critical: oklch(0.62 0.17 30);
--condition-critical-foreground: oklch(0.15 0.04 30);
--chart-1: oklch(0.56 0.13 55);
--chart-2: oklch(0.46 0.08 145);
--chart-3: oklch(0.6 0.12 85);
--chart-4: oklch(0.5 0.17 28);
--chart-5: oklch(0.52 0.08 250);
--sidebar: oklch(0.995 0.004 90);
--sidebar-foreground: oklch(0.21 0.03 55);
--sidebar-primary: oklch(0.8 0.145 78);
--sidebar-primary-foreground: oklch(0.21 0.035 55);
--sidebar-accent: oklch(0.935 0.03 78);
--sidebar-accent-foreground: oklch(0.25 0.045 50);
--sidebar-border: oklch(0.86 0.022 78);
--sidebar-ring: oklch(0.47 0.115 55);
--shadow-elevated: 0 1px 2px -1px oklch(0.3 0.05 50 / 8%), 0 8px 24px -8px oklch(0.3 0.05 50 / 16%);
--shadow-soft: 0 1px 2px -1px oklch(0.3 0.05 50 / 6%), 0 4px 12px -4px oklch(0.3 0.05 50 / 10%);
--holo-bg: oklch(0.18 0.03 250);
--holo-edge: oklch(0.72 0.15 243);
--holo-edge-strong: oklch(0.87 0.07 238);
--holo-fill: oklch(0.78 0.14 220 / 10%);
--holo-label-bg: oklch(0.26 0.04 250);
--holo-label-fg: oklch(0.95 0.02 230);
--primary-ink: oklch(0.47 0.115 55);
--warning-ink: oklch(0.5 0.11 65);
--destructive-foreground: oklch(0.985 0.005 30);
--condition-excellent-ink: oklch(0.4 0.09 150);
--condition-good-ink: oklch(0.41 0.09 125);
--condition-average-ink: oklch(0.44 0.09 85);
--condition-weak-ink: oklch(0.46 0.12 52);
--condition-critical-ink: oklch(0.47 0.16 28);
--area-betrieb: oklch(0.46 0.08 150);
--area-erfassung: oklch(0.5 0.12 60);
--area-arbeit: oklch(0.48 0.08 250);
--area-ernte: oklch(0.52 0.11 75);
--area-neutral: oklch(0.45 0.02 60);
--queen-white: oklch(0.97 0.005 90);
--queen-yellow: oklch(0.86 0.17 95);
--queen-red: oklch(0.58 0.2 27);
--queen-green: oklch(0.58 0.15 145);
--queen-blue: oklch(0.52 0.15 255);
--text-sm: 0.875rem;
--text-xs: 0.8125rem;
--focus-width: 2px;
--control-border-width: 1px;
--comb-stroke-1: 2px;
--comb-stroke-2: 4px;
--comb-stroke-3: 6px;
}
}
/* Forced colours: the system palette wins; strokes use CanvasText so shapes stay visible. */
@media (forced-colors: active) {
:root,
.dark,
:root[data-contrast="sun"],
.dark[data-contrast="sun"] {
--ring: CanvasText;
--border: CanvasText;
--input: CanvasText;
}
}
/* ---------------------------------------------------------------------------
App chrome (see app/(app)/layout.tsx)
The page scrolls on the window. On phones a fixed bottom nav sits over its
lower edge, on iOS above the home indicator (needs viewport-fit=cover).
Content padding, the sticky save bar, the quick-add button and the focus
scroll-padding below all derive from these values, so they stay in step.
------------------------------------------------------------------------ */
:root {
--safe-area-bottom: env(safe-area-inset-bottom, 0px);
--safe-area-left: env(safe-area-inset-left, 0px);
--safe-area-right: env(safe-area-inset-right, 0px);
--bottom-nav-height: calc(4rem + var(--safe-area-bottom));
/* Sticky header (3.5rem) plus breathing room. */
--header-clearance: 4.5rem;
/* StickySubmit with its 48px button (~4.6rem) plus breathing room. */
--sticky-submit-clearance: 5.5rem;
/*
Toasts sit above the sticky save bar. Below sm also above the bottom nav,
which carries the quick-add button (nav + 1rem + 3rem, less than the bar).
*/
--toast-offset-bottom: calc(var(--bottom-nav-height) + var(--sticky-submit-clearance) + 0.5rem);
@variant sm {
--toast-offset-bottom: calc(var(--safe-area-bottom) + var(--sticky-submit-clearance) + 0.5rem);
}
}
@layer base {
* {
@apply border-border;
}
/* One focus style for everything (spec 4.6): a solid ring in --ring with a gap, 3 px in sun.
Components add no focus classes; a row whose focus target sits inside uses
focus-ring-within. */
:where(:focus-visible) {
outline: var(--focus-width) solid var(--ring);
outline-offset: var(--focus-offset);
}
/* Firefox: the preflight sets `:-moz-focusring:where(:not(iframe)) { outline: auto }` (0,1,0),
which beats the rule above. Firefox matches :-moz-focusring exactly where it matches
:focus-visible (keyboard, not a mouse click), so restating the ring here adds nothing else. */
:-moz-focusring:where(:not(iframe)) {
outline: var(--focus-width) solid var(--ring);
outline-offset: var(--focus-offset);
}
body {
@apply bg-background text-foreground;
}
html {
@apply font-sans;
/* WCAG 2.4.11: a field scrolled into view by focus must not end up under
the sticky header, the bottom nav / quick-add button or the save bar. */
scroll-padding-top: var(--header-clearance);
scroll-padding-bottom: calc(var(--bottom-nav-height) + var(--sticky-submit-clearance));
@variant sm {
scroll-padding-bottom: calc(var(--sticky-submit-clearance) + var(--safe-area-bottom));
}
}
h1,
h2,
h3,
h4 {
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-ink);
}
}
/* A row whose focus target sits inside (a label with its checkbox, radio or a visually hidden
input) also rings the whole row, so the focus is easy to spot from arm's length (spec 4.6). */
@utility focus-ring-within {
&:has(:focus-visible) {
outline: var(--focus-width) solid var(--ring);
outline-offset: var(--focus-offset);
}
}
/* ---------------------------------------------------------------------------
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);
}
.honey-check::before {
content: "";
position: absolute;
inset: 0.375rem;
border-radius: 0.6rem;
border: 2px solid var(--input);
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(--primary);
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 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;
}
}