@import "tailwindcss"; @import "tw-animate-css"; @import "shadcn/tailwind.css"; @custom-variant dark (&:is(.dark *)); @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-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.955 0.017 80); --foreground: oklch(0.22 0.028 55); --card: oklch(0.995 0.004 90); --card-foreground: oklch(0.22 0.028 55); --popover: oklch(0.995 0.004 90); --popover-foreground: oklch(0.22 0.028 55); --primary: oklch(0.6 0.15 48); --primary-foreground: oklch(0.99 0.02 85); --secondary: oklch(0.44 0.07 145); --secondary-foreground: oklch(0.98 0.015 100); --muted: oklch(0.9 0.02 82); --muted-foreground: oklch(0.48 0.032 65); --accent: oklch(0.93 0.032 74); --accent-foreground: oklch(0.26 0.045 50); --destructive: oklch(0.5 0.16 30); --border: oklch(0.85 0.024 78); --input: oklch(0.85 0.024 78); --ring: oklch(0.6 0.15 48); --warning: oklch(0.68 0.14 75); --warning-foreground: oklch(0.24 0.04 60); --success: oklch(0.46 0.06 145); --success-foreground: oklch(0.98 0.015 100); --condition-excellent: oklch(0.58 0.13 145); --condition-excellent-foreground: oklch(0.16 0.04 145); --condition-good: oklch(0.64 0.13 118); --condition-good-foreground: oklch(0.26 0.05 118); --condition-average: oklch(0.7 0.14 90); --condition-average-foreground: oklch(0.28 0.05 90); --condition-weak: oklch(0.68 0.15 55); --condition-weak-foreground: oklch(0.26 0.05 55); --condition-critical: oklch(0.64 0.16 30); --condition-critical-foreground: oklch(0.15 0.04 30); --chart-1: oklch(0.6 0.135 50); --chart-2: oklch(0.46 0.06 145); --chart-3: oklch(0.68 0.14 75); --chart-4: oklch(0.5 0.16 30); --chart-5: oklch(0.55 0.05 250); --radius: 0.75rem; --sidebar: oklch(0.995 0.004 90); --sidebar-foreground: oklch(0.22 0.028 55); --sidebar-primary: oklch(0.6 0.15 48); --sidebar-primary-foreground: oklch(0.99 0.02 85); --sidebar-accent: oklch(0.93 0.032 74); --sidebar-accent-foreground: oklch(0.26 0.045 50); --sidebar-border: oklch(0.85 0.024 78); --sidebar-ring: oklch(0.6 0.15 48); --gradient-honey: linear-gradient(135deg, oklch(0.74 0.15 62) 0%, oklch(0.56 0.16 38) 100%); --gradient-sage: linear-gradient(135deg, oklch(0.64 0.09 150) 0%, oklch(0.4 0.075 145) 100%); --gradient-sky: linear-gradient(135deg, oklch(0.68 0.1 235) 0%, oklch(0.5 0.1 255) 100%); --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.78 0.14 220); --holo-edge-strong: oklch(0.88 0.12 215); --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); } .dark { --background: oklch(0.2 0.018 55); --foreground: oklch(0.94 0.02 80); --card: oklch(0.25 0.02 55); --card-foreground: oklch(0.94 0.02 80); --popover: oklch(0.25 0.02 55); --popover-foreground: oklch(0.94 0.02 80); --primary: oklch(0.72 0.13 55); --primary-foreground: oklch(0.2 0.02 55); --secondary: oklch(0.62 0.075 145); --secondary-foreground: oklch(0.15 0.02 145); --muted: oklch(0.29 0.018 55); --muted-foreground: oklch(0.7 0.025 75); --accent: oklch(0.32 0.03 60); --accent-foreground: oklch(0.94 0.02 80); --destructive: oklch(0.62 0.16 30); --border: oklch(1 0 0 / 12%); --input: oklch(1 0 0 / 16%); --ring: oklch(0.72 0.13 55); --warning: oklch(0.74 0.13 75); --warning-foreground: oklch(0.2 0.03 60); --success: oklch(0.62 0.075 145); --success-foreground: oklch(0.15 0.02 145); --condition-excellent: oklch(0.66 0.11 145); --condition-excellent-foreground: oklch(0.2 0.04 145); --condition-good: oklch(0.72 0.12 118); --condition-good-foreground: oklch(0.22 0.04 118); --condition-average: oklch(0.76 0.13 90); --condition-average-foreground: oklch(0.24 0.04 90); --condition-weak: oklch(0.74 0.13 55); --condition-weak-foreground: oklch(0.22 0.04 55); --condition-critical: oklch(0.62 0.16 30); --condition-critical-foreground: oklch(0.2 0.04 30); --chart-1: oklch(0.72 0.13 55); --chart-2: oklch(0.62 0.075 145); --chart-3: oklch(0.74 0.13 75); --chart-4: oklch(0.62 0.16 30); --chart-5: oklch(0.65 0.06 250); --sidebar: oklch(0.22 0.02 55); --sidebar-foreground: oklch(0.94 0.02 80); --sidebar-primary: oklch(0.72 0.13 55); --sidebar-primary-foreground: oklch(0.2 0.02 55); --sidebar-accent: oklch(0.32 0.03 60); --sidebar-accent-foreground: oklch(0.94 0.02 80); --sidebar-border: oklch(1 0 0 / 12%); --sidebar-ring: oklch(0.72 0.13 55); --gradient-honey: linear-gradient(135deg, oklch(0.62 0.14 55) 0%, oklch(0.38 0.13 40) 100%); --gradient-sage: linear-gradient(135deg, oklch(0.5 0.08 148) 0%, oklch(0.3 0.06 145) 100%); --gradient-sky: linear-gradient(135deg, oklch(0.55 0.09 235) 0%, oklch(0.34 0.09 255) 100%); --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.78 0.14 220); --holo-edge-strong: oklch(0.88 0.12 215); --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); } /* --------------------------------------------------------------------------- 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 outline-ring/50; } 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); } } /* --------------------------------------------------------------------------- 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; } }