304 lines
12 KiB
CSS
304 lines
12 KiB
CSS
/*
|
|
* Balinyaar brand color tokens — source of truth for all colors.
|
|
*
|
|
* Brand identity (construction + rationale): .claude/skills/frontend-designer/SKILL.md.
|
|
* #1d4a40 deep teal / forest-green → brand primary
|
|
* #d98c6a terracotta → brand accent (secondary)
|
|
* #f3efe9 soft cream → text-on-dark / glyph fill
|
|
* #faf9f5 cream → page surface
|
|
*
|
|
* MUI sets data-mui-color-scheme="dark" on <html> when dark mode is active
|
|
* (colorSchemeSelector: 'data-mui-color-scheme' in createTheme). The same
|
|
* attribute drives every custom CSS rule in this file. You never need to
|
|
* read or write this attribute manually — MUI's useColorScheme() does it for
|
|
* you at runtime.
|
|
*
|
|
* No-flash boot (no JS): when a returning visitor's `color-scheme` cookie is
|
|
* present, the server stamps the attribute directly and the explicit
|
|
* `[data-mui-color-scheme]` blocks below apply immediately — zero flash. On a
|
|
* cookie-less FIRST visit the server renders <html> WITHOUT the attribute
|
|
* (see lib/cookies/server.ts getThemeMode()), so the `@media
|
|
* (prefers-color-scheme: dark)` block below — scoped to the attribute being
|
|
* absent — paints the OS-preferred scheme immediately, with no script. Once
|
|
* MUI hydrates (ThemeProvider defaultMode="system") it resolves the same
|
|
* media query and stamps the attribute itself; the values already match, so
|
|
* there is nothing to visibly flip. The dark values therefore appear twice
|
|
* below (the media-query fallback + the explicit attribute block) — keep
|
|
* both in sync when changing a dark-scheme value.
|
|
*
|
|
* Usage in custom CSS outside MUI:
|
|
* color: var(--bal-primary); ← resolves to the active scheme value
|
|
* background: var(--bal-bg-default);
|
|
*
|
|
* Sync rule: any color added/changed here also lands in colors.ts (LIGHT_PALETTE/
|
|
* DARK_PALETTE) when it's palette-level (success/error/warning/info, primary,
|
|
* secondary…). Non-palette tokens (elevation, radius, motion, focus, rating,
|
|
* trust, money-emphasis) are define-only here — colors.ts never needs them.
|
|
*/
|
|
|
|
/* ── Scheme-independent tokens (radius scale, motion) ──────────────────── */
|
|
:root {
|
|
/* Radius scale — controls / cards / dialogs. shape.borderRadius (theme.ts)
|
|
mirrors --bal-radius-md; never invent a radius per component. */
|
|
--bal-radius-sm: 4px;
|
|
--bal-radius-md: 10px;
|
|
--bal-radius-lg: 16px;
|
|
|
|
/* Motion — durations + easing, consumed by the phase-12 motion pass */
|
|
--bal-motion-fast: 120ms;
|
|
--bal-motion-base: 200ms;
|
|
--bal-motion-slow: 300ms;
|
|
--bal-easing-standard: cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
/*
|
|
* Reduced motion — the token half of the single gate (globals.css carries the universal
|
|
* transition/animation-duration override that makes this bite even for consumers, like MUI's own
|
|
* Dialog/Drawer/Menu transitions, that don't read these custom properties directly). Zeroing the
|
|
* durations here too means anything that DOES read the token (this file's own consumers, any future
|
|
* one) needs no reduced-motion branch of its own.
|
|
*/
|
|
@media (prefers-reduced-motion: reduce) {
|
|
:root {
|
|
--bal-motion-fast: 0ms;
|
|
--bal-motion-base: 0ms;
|
|
--bal-motion-slow: 0ms;
|
|
}
|
|
}
|
|
|
|
/* ── Light scheme — the unconditional default ──────────────────────────── */
|
|
:root,
|
|
[data-mui-color-scheme='light'] {
|
|
/* Primary — deep teal */
|
|
--bal-primary: #1d4a40;
|
|
--bal-primary-light: #2f6b5e;
|
|
--bal-primary-dark: #123029;
|
|
--bal-primary-contrast: #f3efe9;
|
|
/* Soft primary tint — selected chips, subtle info panels */
|
|
--bal-primary-soft: rgba(29, 74, 64, 0.10);
|
|
|
|
/* Secondary — terracotta */
|
|
--bal-secondary: #d98c6a;
|
|
--bal-secondary-light: #e6a98a;
|
|
--bal-secondary-dark: #bf6f4d;
|
|
--bal-secondary-contrast: #2a1a12;
|
|
/* Soft terracotta tint — the "نمای پرستار" nurse-view chip + EVV/financial affordances */
|
|
--bal-secondary-soft: rgba(217, 140, 106, 0.14);
|
|
|
|
/* Surfaces */
|
|
--bal-bg-default: #faf9f5;
|
|
--bal-bg-paper: #ffffff;
|
|
|
|
/* Text */
|
|
--bal-text-primary: #1b2521;
|
|
--bal-text-secondary: #5b655f;
|
|
|
|
/* Divider */
|
|
--bal-divider: rgba(29, 74, 64, 0.14);
|
|
|
|
/* Feedback — toast / alert backgrounds (brand-harmonized; cream text) */
|
|
--bal-success: #1f6b50;
|
|
--bal-success-contrast: #f3efe9;
|
|
--bal-error: #a8392a;
|
|
--bal-error-contrast: #f3efe9;
|
|
--bal-warning: #8a6418;
|
|
--bal-warning-contrast: #f3efe9;
|
|
--bal-info: #1d4a40;
|
|
--bal-info-contrast: #f3efe9;
|
|
/* Soft feedback tints — StatusChip v2's default (soft bg + solid text); solid fill is
|
|
reserved for the alarm tier only. Weight hierarchy: neutral < info < progress <
|
|
success < warning < alarm (see StatusChip.tsx). */
|
|
--bal-success-soft: rgba(31, 107, 80, 0.12);
|
|
--bal-error-soft: rgba(168, 57, 42, 0.12);
|
|
--bal-warning-soft: rgba(138, 100, 24, 0.14);
|
|
--bal-info-soft: rgba(29, 74, 64, 0.10);
|
|
|
|
/* Elevation — teal-tinted shadow steps (replaces MUI's grey default stack).
|
|
Consumed by theme.ts's `shadows` array + the MuiPaper/MuiDialog overrides. */
|
|
--bal-shadow-1: 0 1px 2px rgba(29, 74, 64, 0.06), 0 1px 1px rgba(29, 74, 64, 0.04);
|
|
--bal-shadow-2: 0 4px 12px rgba(29, 74, 64, 0.10), 0 2px 4px rgba(29, 74, 64, 0.06);
|
|
--bal-shadow-3: 0 12px 32px rgba(29, 74, 64, 0.14), 0 4px 8px rgba(29, 74, 64, 0.08);
|
|
|
|
/* Focus ring — global :focus-visible treatment (MuiCssBaseline, theme.ts) */
|
|
--bal-focus-ring: #1d4a40;
|
|
|
|
/* Rating stars — RatingInput's star fill/empty (retires the muddy --bal-warning use) */
|
|
--bal-rating: #c8971e;
|
|
--bal-rating-empty: rgba(29, 74, 64, 0.20);
|
|
|
|
/* Trust — a distinct identity for verified marks, separate from primary/success */
|
|
--bal-trust: #2b6ca8;
|
|
--bal-trust-soft: rgba(43, 108, 168, 0.12);
|
|
|
|
/* Money emphasis — AA-contrast-safe money-text color (terracotta itself fails
|
|
AA at small sizes on white: #d98c6a ≈ 2:1). Never use --bal-secondary for
|
|
small money text; use this instead. */
|
|
--bal-money-emphasis: #8a4a2f;
|
|
|
|
/* Avatar — warm auto-colored initials (ui-phase-9). A person with no photo (a care-circle
|
|
patient, the account holder) gets a deterministic hue from this 6-pair palette (name hash →
|
|
index), never a hex baked into a component. Each pair is a distinct warm/calm hue harmonized
|
|
with the brand teal/terracotta, not reused from primary/secondary/feedback. */
|
|
--bal-avatar-1: #2f6b5e;
|
|
--bal-avatar-1-contrast: #f3efe9;
|
|
--bal-avatar-2: #b56b3f;
|
|
--bal-avatar-2-contrast: #f3efe9;
|
|
--bal-avatar-3: #6b5b95;
|
|
--bal-avatar-3-contrast: #f3efe9;
|
|
--bal-avatar-4: #3f7690;
|
|
--bal-avatar-4-contrast: #f3efe9;
|
|
--bal-avatar-5: #8a6418;
|
|
--bal-avatar-5-contrast: #f3efe9;
|
|
--bal-avatar-6: #5c7a4a;
|
|
--bal-avatar-6-contrast: #f3efe9;
|
|
|
|
/* Map pin drop-shadow — was a hard-coded rgba() in AddressMapPicker; a plain black shadow reads
|
|
the same in both schemes so it isn't teal-tinted like --bal-shadow-*. */
|
|
--bal-pin-shadow: rgba(0, 0, 0, 0.35);
|
|
}
|
|
|
|
/* ── Dark scheme — OS-preference fallback for the cookie-less first visit ── */
|
|
@media (prefers-color-scheme: dark) {
|
|
:root:not([data-mui-color-scheme]) {
|
|
--bal-primary: #6fc0ac;
|
|
--bal-primary-light: #8fd2c1;
|
|
--bal-primary-dark: #3f8a78;
|
|
--bal-primary-contrast: #06120f;
|
|
--bal-primary-soft: rgba(111, 192, 172, 0.16);
|
|
|
|
--bal-secondary: #e6a98a;
|
|
--bal-secondary-light: #f0bfa3;
|
|
--bal-secondary-dark: #d98c6a;
|
|
--bal-secondary-contrast: #2a1a12;
|
|
--bal-secondary-soft: rgba(230, 169, 138, 0.18);
|
|
|
|
--bal-bg-default: #0f1c19;
|
|
--bal-bg-paper: #16302a;
|
|
|
|
--bal-text-primary: #f3efe9;
|
|
--bal-text-secondary: #9fb0a9;
|
|
|
|
--bal-divider: rgba(243, 239, 233, 0.14);
|
|
|
|
--bal-success: #257659;
|
|
--bal-success-contrast: #f3efe9;
|
|
--bal-error: #b5402f;
|
|
--bal-error-contrast: #f3efe9;
|
|
--bal-warning: #97701f;
|
|
--bal-warning-contrast: #f3efe9;
|
|
--bal-info: #2f6b5e;
|
|
--bal-info-contrast: #f3efe9;
|
|
--bal-success-soft: rgba(37, 118, 89, 0.18);
|
|
--bal-error-soft: rgba(181, 64, 47, 0.18);
|
|
--bal-warning-soft: rgba(151, 112, 31, 0.20);
|
|
--bal-info-soft: rgba(47, 107, 94, 0.16);
|
|
|
|
--bal-shadow-1: 0 1px 2px rgba(6, 18, 15, 0.24), 0 1px 1px rgba(6, 18, 15, 0.16);
|
|
--bal-shadow-2: 0 4px 12px rgba(6, 18, 15, 0.32), 0 2px 4px rgba(6, 18, 15, 0.20);
|
|
--bal-shadow-3: 0 12px 32px rgba(6, 18, 15, 0.40), 0 4px 8px rgba(6, 18, 15, 0.24);
|
|
|
|
--bal-focus-ring: #6fc0ac;
|
|
|
|
--bal-rating: #e0b542;
|
|
--bal-rating-empty: rgba(243, 239, 233, 0.20);
|
|
|
|
--bal-trust: #7fb8e6;
|
|
--bal-trust-soft: rgba(127, 184, 230, 0.18);
|
|
|
|
--bal-money-emphasis: #f0bfa3;
|
|
|
|
--bal-avatar-1: #8fd2c1;
|
|
--bal-avatar-1-contrast: #06120f;
|
|
--bal-avatar-2: #e3a679;
|
|
--bal-avatar-2-contrast: #2a1a12;
|
|
--bal-avatar-3: #b3a4d6;
|
|
--bal-avatar-3-contrast: #1f1a2e;
|
|
--bal-avatar-4: #8fc4dc;
|
|
--bal-avatar-4-contrast: #08222b;
|
|
--bal-avatar-5: #e0b542;
|
|
--bal-avatar-5-contrast: #2a2107;
|
|
--bal-avatar-6: #9fc98a;
|
|
--bal-avatar-6-contrast: #142a0c;
|
|
|
|
--bal-pin-shadow: rgba(0, 0, 0, 0.5);
|
|
}
|
|
}
|
|
|
|
/* ── Dark scheme — explicit (cookie present, or after MUI hydrates/toggles).
|
|
Keep every value identical to the @media block above. ──────────────── */
|
|
[data-mui-color-scheme='dark'] {
|
|
/* Primary — lifted teal (readable on dark surfaces) */
|
|
--bal-primary: #6fc0ac;
|
|
--bal-primary-light: #8fd2c1;
|
|
--bal-primary-dark: #3f8a78;
|
|
--bal-primary-contrast: #06120f;
|
|
--bal-primary-soft: rgba(111, 192, 172, 0.16);
|
|
|
|
/* Secondary — warm terracotta-light */
|
|
--bal-secondary: #e6a98a;
|
|
--bal-secondary-light: #f0bfa3;
|
|
--bal-secondary-dark: #d98c6a;
|
|
--bal-secondary-contrast: #2a1a12;
|
|
--bal-secondary-soft: rgba(230, 169, 138, 0.18);
|
|
|
|
/* Surfaces — deep teal */
|
|
--bal-bg-default: #0f1c19;
|
|
--bal-bg-paper: #16302a;
|
|
|
|
/* Text */
|
|
--bal-text-primary: #f3efe9;
|
|
--bal-text-secondary: #9fb0a9;
|
|
|
|
/* Divider */
|
|
--bal-divider: rgba(243, 239, 233, 0.14);
|
|
|
|
/* Feedback — toast / alert backgrounds (lifted for dark surfaces; cream text) */
|
|
--bal-success: #257659;
|
|
--bal-success-contrast: #f3efe9;
|
|
--bal-error: #b5402f;
|
|
--bal-error-contrast: #f3efe9;
|
|
--bal-warning: #97701f;
|
|
--bal-warning-contrast: #f3efe9;
|
|
--bal-info: #2f6b5e;
|
|
--bal-info-contrast: #f3efe9;
|
|
--bal-success-soft: rgba(37, 118, 89, 0.18);
|
|
--bal-error-soft: rgba(181, 64, 47, 0.18);
|
|
--bal-warning-soft: rgba(151, 112, 31, 0.20);
|
|
--bal-info-soft: rgba(47, 107, 94, 0.16);
|
|
|
|
/* Elevation — black-teal shadow steps (deeper + darker than light scheme) */
|
|
--bal-shadow-1: 0 1px 2px rgba(6, 18, 15, 0.24), 0 1px 1px rgba(6, 18, 15, 0.16);
|
|
--bal-shadow-2: 0 4px 12px rgba(6, 18, 15, 0.32), 0 2px 4px rgba(6, 18, 15, 0.20);
|
|
--bal-shadow-3: 0 12px 32px rgba(6, 18, 15, 0.40), 0 4px 8px rgba(6, 18, 15, 0.24);
|
|
|
|
/* Focus ring */
|
|
--bal-focus-ring: #6fc0ac;
|
|
|
|
/* Rating stars */
|
|
--bal-rating: #e0b542;
|
|
--bal-rating-empty: rgba(243, 239, 233, 0.20);
|
|
|
|
/* Trust */
|
|
--bal-trust: #7fb8e6;
|
|
--bal-trust-soft: rgba(127, 184, 230, 0.18);
|
|
|
|
/* Money emphasis — lifted warm tone, readable on dark surfaces */
|
|
--bal-money-emphasis: #f0bfa3;
|
|
|
|
/* Avatar — same lifted hues as the media-query fallback above (keep in sync) */
|
|
--bal-avatar-1: #8fd2c1;
|
|
--bal-avatar-1-contrast: #06120f;
|
|
--bal-avatar-2: #e3a679;
|
|
--bal-avatar-2-contrast: #2a1a12;
|
|
--bal-avatar-3: #b3a4d6;
|
|
--bal-avatar-3-contrast: #1f1a2e;
|
|
--bal-avatar-4: #8fc4dc;
|
|
--bal-avatar-4-contrast: #08222b;
|
|
--bal-avatar-5: #e0b542;
|
|
--bal-avatar-5-contrast: #2a2107;
|
|
--bal-avatar-6: #9fc98a;
|
|
--bal-avatar-6-contrast: #142a0c;
|
|
|
|
--bal-pin-shadow: rgba(0, 0, 0, 0.5);
|
|
}
|