/* * Layer order is load-bearing — it maps to the Astryx cascade: * reset → tailwind base → astryx component styles → our theme tokens → * app components → tailwind utilities (must win last). * * NOTE: we import OUR built theme, not @astryxdesign/theme-neutral/theme.css. * Astryx theme CSS is @scope-gated on the theme *name*, so neutral's rules * would match nothing under [data-astryx-theme="loyaly"]. loyaly.css already * contains everything it inherits from neutral. */ @layer reset, theme, base, astryx-base, astryx-theme, components, utilities; @import 'tailwindcss/theme.css' layer(theme); @import 'tailwindcss/preflight.css' layer(base); @import '@astryxdesign/core/reset.css'; @import '@astryxdesign/core/astryx.css'; @import '../theme/loyaly.css'; @import '@astryxdesign/core/tailwind-theme.css'; @import 'tailwindcss/utilities.css' layer(utilities); /* * Paint the canvas before React mounts. AppShell sets these itself once it * renders, but this guarantees the very first frame — and any non-shell route * such as an error boundary — already has the right ground colour. * * These rules are UNLAYERED, so they beat everything in loyaly.css. That is * why `color-scheme` has to be resolved from `data-theme` here rather than * pinned: a flat `color-scheme: dark` silently won over the theme's own * html[data-theme="light"] rule, and every `light-dark()` token in the * stylesheet kept resolving to its dark branch no matter what mode said. * * data-theme is written by the root layout (from the cookie) and re-synced by * Astryx's . It is absent in system mode — that is what leaves * `light dark` in force so the OS preference decides. */ html { background-color: var(--color-background-body); color-scheme: light dark; } html[data-theme='dark'] { color-scheme: dark; } html[data-theme='light'] { color-scheme: light; } @layer components { /* * Global Data Table Inset Contract: 24px left lead-in, 32px right inset. * * `[data-ai-report]` is excluded, and that exclusion is a bug fix rather * than a preference. These rules were written for full-page dashboard * tables, but the selector is bare `table` with `!important`, so they also * hit the report tables rendered inside the Loyaly AI panel — adding 24px + * 32px of inset to a table that only has ~376px to begin with, on top of * its own px-4 cells. Roughly a sixth of the panel went to padding the * report could not opt out of, which is a large part of why its columns * came out unreadably narrow. * * The AI report sets its own cell padding for its own width. Scoping by * attribute keeps the dashboard contract exactly as it was for every table * that is genuinely on a full page. */ .astryx-table-cell:first-child, .astryx-table-header-cell:first-child, table:not([data-ai-report]) th:first-child, table:not([data-ai-report]) td:first-child { padding-inline-start: var(--spacing-6) !important; } .astryx-table-cell:last-child, .astryx-table-header-cell:last-child, table:not([data-ai-report]) th:last-child, table:not([data-ai-report]) td:last-child { padding-inline-end: var(--spacing-8) !important; } /* Sidebar Navigation typography & bold icon styling */ .astryx-side-nav-item { font-size: 15px !important; font-weight: 600 !important; letter-spacing: -0.01em !important; } .astryx-side-nav-item svg { width: 20px !important; height: 20px !important; stroke-width: 2.2px !important; } /* Kill switch for our own CSS transitions. Framer Motion is handled by , recharts by useChartMotion(). */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } }