@use './variables' as *; :root { --color-text: #1a1a1a; --color-text-muted: #6b6b6b; --color-background: #ffffff; --color-surface: #f7f7f5; --color-border: #e2e0dc; --color-primary: #b5432b; --color-primary-contrast: #ffffff; --color-danger: #a33333; --color-success: #2f7a4f; color-scheme: light; } @media (prefers-color-scheme: dark) { :root { --color-text: #ededed; --color-text-muted: #9d9d9d; --color-background: #161616; --color-surface: #202020; --color-border: #383838; --color-primary: #d9694a; --color-primary-contrast: #ffffff; --color-danger: #ef6b62; --color-success: #4fbb7c; color-scheme: dark; } } *, *::before, *::after { box-sizing: border-box; } // Reserve the scrollbar's width on every page, scrollable or not — without // this, the sticky nav bar (and page content) shifts a few pixels wider // when navigating from a short page (no scrollbar) to a tall one (has one). // `scrollbar-gutter` only takes effect on an actual scroll container, so // `html` needs an explicit non-visible overflow for it to apply at all. html { overflow-y: scroll; scrollbar-gutter: stable; } body { margin: 0; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; color: $color-text; background: $color-background; } button { font: inherit; } // Route navigation transitions, powered by the router's defaultViewTransition // (see router.tsx). No-ops in browsers without View Transitions support. @media (prefers-reduced-motion: no-preference) { ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 0.22s; animation-timing-function: ease; } ::view-transition-old(root) { animation-name: view-transition-fade-out; } ::view-transition-new(root) { animation-name: view-transition-fade-in; } // Any element that carries a matching `view-transition-name` on both the // outgoing and incoming page (e.g. an Order card morphing into its detail // header, see OrdersListView / OrderStatusView) gets its own transition // group — this smooths and slows the browser's default morph/resize // animation for those to match the root crossfade's pace instead of the // UA default's snappier one. ::view-transition-group(*) { animation-duration: 0.32s; animation-timing-function: cubic-bezier(0.2, 0, 0, 1); } // The top nav bar (see __root.tsx) carries its own `view-transition-name` // so it's excluded from the root snapshot above — otherwise the root // fade's translateY drags the persistent header along with it, making it // visibly jump on every navigation. It doesn't change between routes, so // it should just stay put: no morph, no crossfade. ::view-transition-group(site-header) { animation: none; } ::view-transition-old(site-header), ::view-transition-new(site-header) { animation: none; } // Same reasoning as site-header: the admin sidebar nav is identical across // every /admin/* route, so it should stay put across those navigations // instead of fading/translating along with the root snapshot. ::view-transition-group(admin-sidebar) { animation: none; } ::view-transition-old(admin-sidebar), ::view-transition-new(admin-sidebar) { animation: none; } } // document.startViewTransition() itself doesn't check this preference — // without an explicit override every browser falls back to its own default // crossfade/morph animations, unanimated only for the rules above (which // only apply under no-preference). Kill all of it outright here instead. @media (prefers-reduced-motion: reduce) { ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; } } @keyframes view-transition-fade-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } } @keyframes view-transition-fade-out { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(-6px); } }