210 lines
7.0 KiB
CSS
210 lines
7.0 KiB
CSS
/* Global visual polish that used to come from MUI's <CssBaseline>/<GlobalStyles>
|
|
in themes/index.js (ThemeCustomization). Kept as plain CSS now that every
|
|
page runs on Astryx — see root CLAUDE.md §6. Astryx's own reset
|
|
(@astryxdesign/core/reset.css, imported in index.js) covers the base
|
|
element resets; this file carries the handful of things a design token
|
|
cannot express: font rendering, scrollbar pseudo-elements, keyframes, the
|
|
focus treatment, and the global reduced-motion rule.
|
|
|
|
NOTE ON COLOUR: nothing here introduces a colour. The console keeps its own
|
|
palette (themes/dt/tokens.js) — the KROW work adopted KROW's geometry and
|
|
type treatment only. Where a rule below needs an accent it reads
|
|
`var(--color-accent)` rather than naming one. */
|
|
|
|
body {
|
|
/* Astryx sets --font-family-body on :root (from themes/astryx.js's
|
|
typography config) but nothing was applying it to the document, and
|
|
MUI's <CssBaseline> — which used to do this — is no longer mounted.
|
|
The result was a body computing to "Times New Roman": Astryx components
|
|
rendered correctly because they set their own font, but every plain
|
|
element and any text outside a <Text> inherited the browser serif
|
|
default. Verified in the browser on /login before and after. */
|
|
font-family: var(--font-family-body);
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
text-rendering: optimizeLegibility;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------
|
|
Motion vocabulary.
|
|
|
|
One set of durations and one easing curve for the whole app, so a hover, a
|
|
row highlight and a card entrance all feel like the same product. Three
|
|
steps only: `fast` for colour-only changes (anything slower feels laggy
|
|
under a moving cursor), `base` for the default, `slow` for layout and
|
|
entrances.
|
|
|
|
Prefixed --dt-* deliberately. These live on :root, and Dispatch.css ALSO
|
|
writes :root from a lazy-loaded chunk — an unprefixed name there would win
|
|
or lose purely on bundle order rather than on intent.
|
|
-------------------------------------------------------------------------- */
|
|
:root {
|
|
--dt-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
|
--dt-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
|
|
--dt-duration-fast: 120ms;
|
|
--dt-duration-base: 200ms;
|
|
--dt-duration-slow: 320ms;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------
|
|
Focus.
|
|
|
|
Focus is never the browser default. One ring, one width, everywhere. The
|
|
colour is the app's own accent token, not a value chosen here, so the ring
|
|
follows the brand automatically.
|
|
|
|
:focus-visible, not :focus — a mouse click on a button should not leave a
|
|
ring behind it.
|
|
-------------------------------------------------------------------------- */
|
|
.dt-focus-ring:focus-visible {
|
|
outline: none;
|
|
box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px var(--color-accent);
|
|
}
|
|
|
|
/* The inset variant, for a control flush inside a row or table cell where an
|
|
offset ring would be clipped by the overflow container. */
|
|
.dt-focus-ring-inset:focus-visible {
|
|
outline: none;
|
|
box-shadow: inset 0 0 0 2px var(--color-accent);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------
|
|
Scrollbars.
|
|
|
|
Chrome, not content — they should recede until used. The thumb was
|
|
rgba(0,0,0,0.26) at 10px, which drew a solid dark bar down the right edge of
|
|
every scrollable table and read as a heavy border rather than as a
|
|
scrollbar. Lightened and narrowed; it still darkens on hover so it stays
|
|
findable.
|
|
-------------------------------------------------------------------------- */
|
|
* {
|
|
scrollbar-width: thin; /* Firefox */
|
|
scrollbar-color: rgba(0, 0, 0, 0.16) transparent;
|
|
}
|
|
|
|
*::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
|
|
*::-webkit-scrollbar-track {
|
|
background-color: transparent;
|
|
}
|
|
|
|
*::-webkit-scrollbar-thumb {
|
|
background-color: rgba(0, 0, 0, 0.16);
|
|
border-radius: 999px;
|
|
border: 2px solid transparent;
|
|
background-clip: padding-box;
|
|
}
|
|
|
|
*::-webkit-scrollbar-thumb:hover {
|
|
background-color: rgba(0, 0, 0, 0.34);
|
|
}
|
|
|
|
*::-webkit-scrollbar-corner {
|
|
background-color: transparent;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------
|
|
Keyframes.
|
|
|
|
A skeleton SHIMMERS, it does not pulse: a pulsing block reads as a
|
|
rendering glitch, while a travelling highlight reads as progress. Astryx's
|
|
own <Skeleton> keeps its built-in treatment; this is for the hand-rolled
|
|
placeholders.
|
|
-------------------------------------------------------------------------- */
|
|
@keyframes dt-shimmer {
|
|
0% {
|
|
background-position: 200% 0;
|
|
}
|
|
100% {
|
|
background-position: -200% 0;
|
|
}
|
|
}
|
|
|
|
.dt-shimmer {
|
|
background-image: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.65) 50%, transparent 100%);
|
|
background-size: 200% 100%;
|
|
animation: dt-shimmer 1.6s var(--dt-ease-in-out) infinite;
|
|
}
|
|
|
|
@keyframes dt-fade-in {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
@keyframes dt-slide-up {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(8px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.dt-fade-in {
|
|
animation: dt-fade-in var(--dt-duration-base) var(--dt-ease-out);
|
|
}
|
|
|
|
.dt-slide-up {
|
|
animation: dt-slide-up var(--dt-duration-slow) var(--dt-ease-out);
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------
|
|
KPI / StatCard (Compact Enterprise Metric Strip).
|
|
|
|
Static tiles stay quiet and static. Clickable/interactive tiles (e.g. on
|
|
Tenants page) get a subtle 150ms hover lift and border emphasis.
|
|
-------------------------------------------------------------------------- */
|
|
.dt-stat-card {
|
|
transition: box-shadow 150ms ease, border-color 150ms ease, transform 150ms ease !important;
|
|
}
|
|
|
|
/* Interactive hover state ONLY when clickable or wrapped in button role */
|
|
.dt-stat-card.dt-stat-card-clickable:hover,
|
|
.dt-stat-card[data-clickable='true']:hover,
|
|
[role='button'] .dt-stat-card:hover,
|
|
[role='button'] > .dt-stat-card:hover,
|
|
button .dt-stat-card:hover {
|
|
border-color: var(--color-border-emphasized, #cbd5e1) !important;
|
|
box-shadow: 0 2px 6px rgba(15, 23, 42, 0.08) !important;
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.dt-stat-card.dt-stat-card-clickable:active,
|
|
.dt-stat-card[data-clickable='true']:active,
|
|
[role='button'] .dt-stat-card:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
/* Standardized 14px icon size inside the 26px chip across all call sites */
|
|
.dt-stat-card .dt-stat-icon svg {
|
|
width: 14px !important;
|
|
height: 14px !important;
|
|
font-size: 14px !important;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------
|
|
Reduced motion.
|
|
|
|
Handled ONCE, globally, rather than per component — a per-component opt-out
|
|
is a rule that only covers the components somebody remembered.
|
|
-------------------------------------------------------------------------- */
|
|
@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;
|
|
}
|
|
}
|