@bsuite/theme 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -43,7 +43,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
43
43
  * </BrandingProvider>
44
44
  */
45
45
  import { createContext, useCallback, useEffect, useMemo, useRef, useState } from 'react';
46
- import { sanitizeBrandingUrl, sanitizeFontFamily, toCssUrl, } from './branding-sanitize';
46
+ import { sanitizeBrandingUrl, sanitizeFontFamily, toCssUrl, } from './branding-sanitize.js';
47
47
  export const BRANDING_STORAGE_KEY = 'bsuite_tenant_branding';
48
48
  export const BRANDING_OVERRIDE_FLAG = 'VITE_ENABLE_BRANDING_OVERRIDE';
49
49
  export const BrandingContext = createContext(undefined);
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
- import { type ThemeContextValue, type ThemeMode } from '../index';
2
+ import { type ThemeContextValue, type ThemeMode } from '../index.js';
3
3
  export declare const ThemeContext: import("react").Context<ThemeContextValue | undefined>;
4
4
  export interface ThemeProviderProps {
5
5
  children: ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../src/react/ThemeProvider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAyC,KAAK,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAA;AAExG,eAAO,MAAM,YAAY,wDAA0D,CAAA;AAEnF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,CAAA;IACnB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,SAAS,CAAA;IACxB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AA6BD,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,YAAuB,EACvB,UAA8B,GAC/B,EAAE,kBAAkB,+BA4DpB"}
1
+ {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../src/react/ThemeProvider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAyC,KAAK,iBAAiB,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAA;AAE3G,eAAO,MAAM,YAAY,wDAA0D,CAAA;AAEnF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,CAAA;IACnB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,SAAS,CAAA;IACxB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AA6BD,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,YAAuB,EACvB,UAA8B,GAC/B,EAAE,kBAAkB,+BA4DpB"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useCallback, useEffect, useMemo, useState } from 'react';
3
- import { THEME_STORAGE_KEY } from '../index';
3
+ import { THEME_STORAGE_KEY } from '../index.js';
4
4
  export const ThemeContext = createContext(undefined);
5
5
  /**
6
6
  * Resolve a stored ThemeMode to the effective ResolvedTheme.
@@ -30,8 +30,11 @@ export type BadgeVariant =
30
30
  | 'error'
31
31
  /** Blue — Draft, New, Scheduled */
32
32
  | 'info'
33
- /** Purple — Converted, Promoted */
34
- | 'purple'
33
+ /** Deep blue — Converted, Promoted, Exported, Paid: terminal states that
34
+ * have moved past success. Was `purple`, renamed 0.7.0: the variant was
35
+ * named for a colour rather than a role, and that colour is quarantined
36
+ * from semantics (purple collapses onto primary blue under protanopia). */
37
+ | 'secondary'
35
38
  /** Gray — Inactive, Archived */
36
39
  | 'neutral';
37
40
  export interface StatusBadgeProps {
@@ -1 +1 @@
1
- {"version":3,"file":"StatusBadge.d.ts","sourceRoot":"","sources":["../../../src/react/components/StatusBadge.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3D,MAAM,MAAM,YAAY;AACtB,yCAAyC;AACvC,SAAS;AACX,mCAAmC;GACjC,SAAS;AACX,sCAAsC;GACpC,OAAO;AACT,mCAAmC;GACjC,MAAM;AACR,mCAAmC;GACjC,QAAQ;AACV,gCAAgC;GAC9B,SAAS,CAAC;AAEd,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAqBD,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,KAAK,EACL,IAAW,EACX,SAAS,EACT,KAAK,EACL,aAAa,EAAE,MAAM,GACtB,EAAE,gBAAgB,GAAG,SAAS,CAc9B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,GAChD,YAAY,CAoCd;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CASxD;AAED,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,OAAO,CAAC;IACvF,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;CAC1D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,KAAK,EACL,gBAAgB,EAChB,GAAG,IAAI,EACR,EAAE,oBAAoB,GAAG,SAAS,CAIlC"}
1
+ {"version":3,"file":"StatusBadge.d.ts","sourceRoot":"","sources":["../../../src/react/components/StatusBadge.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3D,MAAM,MAAM,YAAY;AACtB,yCAAyC;AACvC,SAAS;AACX,mCAAmC;GACjC,SAAS;AACX,sCAAsC;GACpC,OAAO;AACT,mCAAmC;GACjC,MAAM;AACR;;;4EAG4E;GAC1E,WAAW;AACb,gCAAgC;GAC9B,SAAS,CAAC;AAEd,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA2BD,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,KAAK,EACL,IAAW,EACX,SAAS,EACT,KAAK,EACL,aAAa,EAAE,MAAM,GACtB,EAAE,gBAAgB,GAAG,SAAS,CAc9B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,GAChD,YAAY,CAoCd;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CASxD;AAED,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,OAAO,CAAC;IACvF,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;CAC1D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,KAAK,EACL,gBAAgB,EAChB,GAAG,IAAI,EACR,EAAE,oBAAoB,GAAG,SAAS,CAIlC"}
@@ -1,10 +1,20 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ /*
3
+ * Role-bound, not palette-bound. Until 0.7.0 these were raw Tailwind palette
4
+ * classes (`bg-emerald-50`, `bg-red-50`, …), which meant the design-system
5
+ * component every app imports was invisible to tenant white-labelling —
6
+ * BrandingProvider mutates the role layer, and these bound below it.
7
+ *
8
+ * Tint is derived from the role colour with a slash-opacity fill plus the
9
+ * AA-safe `*-text` variant for type, so one definition covers light and dark
10
+ * and no `dark:` duplicate is needed.
11
+ */
2
12
  const variantClasses = {
3
- success: 'bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-950 dark:text-emerald-300 dark:border-emerald-800',
4
- warning: 'bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-950 dark:text-amber-300 dark:border-amber-800',
5
- error: 'bg-red-50 text-red-700 border-red-200 dark:bg-red-950 dark:text-red-300 dark:border-red-800',
6
- info: 'bg-sky-50 text-sky-700 border-sky-200 dark:bg-sky-950 dark:text-sky-300 dark:border-sky-800',
7
- purple: 'bg-purple-50 text-purple-700 border-purple-200 dark:bg-purple-950 dark:text-purple-300 dark:border-purple-800',
13
+ success: 'bg-role-success/10 text-success-text border-role-success/30',
14
+ warning: 'bg-role-warning/10 text-warning-text border-role-warning/30',
15
+ error: 'bg-role-error/10 text-error-text border-role-error/30',
16
+ info: 'bg-role-info/10 text-accent-text border-role-info/30',
17
+ secondary: 'bg-role-secondary/10 text-primary-text border-role-secondary/30',
8
18
  neutral: 'bg-muted text-muted-foreground border-border',
9
19
  };
10
20
  const sizeClasses = {
@@ -57,7 +67,7 @@ export function getStatusVariant(status, overrides) {
57
67
  'draft', 'new', 'scheduled', 'open',
58
68
  'applicant', 'interviewing', 'reported',
59
69
  ];
60
- const PURPLE = ['converted', 'promoted', 'shortlisted'];
70
+ const SECONDARY = ['converted', 'promoted', 'shortlisted', 'exported', 'paid', 'qualified'];
61
71
  if (SUCCESS.includes(normalized))
62
72
  return 'success';
63
73
  if (WARNING.includes(normalized))
@@ -66,8 +76,8 @@ export function getStatusVariant(status, overrides) {
66
76
  return 'error';
67
77
  if (INFO.includes(normalized))
68
78
  return 'info';
69
- if (PURPLE.includes(normalized))
70
- return 'purple';
79
+ if (SECONDARY.includes(normalized))
80
+ return 'secondary';
71
81
  return 'neutral';
72
82
  }
73
83
  /**
@@ -1,13 +1,13 @@
1
- export { ThemeProvider } from './ThemeProvider';
2
- export type { ThemeProviderProps } from './ThemeProvider';
3
- export { useTheme } from './useTheme';
4
- export type { ThemeContextValue, ThemeMode, ResolvedTheme } from '../index';
5
- export { THEME_STORAGE_KEY } from '../index';
6
- export { BrandingProvider, BrandingContext, BRANDING_STORAGE_KEY, BRANDING_OVERRIDE_FLAG, } from './BrandingProvider';
7
- export type { TenantBranding, BrandingContextValue } from './BrandingProvider';
8
- export { useBranding } from './useBranding';
9
- export { resolvePlatformLogo, usePlatformLogo } from './usePlatformLogo';
10
- export type { PlatformLogoHookResult, PlatformLogoOptions, PlatformLogoScheme, PlatformLogoSlot, PlatformLogoSource, ResolvedPlatformLogo, } from './usePlatformLogo';
11
- export { StatusBadge, AutoStatusBadge, getStatusVariant, formatStatusLabel, } from './components/StatusBadge';
12
- export type { BadgeVariant, StatusBadgeProps, AutoStatusBadgeProps, } from './components/StatusBadge';
1
+ export { ThemeProvider } from './ThemeProvider.js';
2
+ export type { ThemeProviderProps } from './ThemeProvider.js';
3
+ export { useTheme } from './useTheme.js';
4
+ export type { ThemeContextValue, ThemeMode, ResolvedTheme } from '../index.js';
5
+ export { THEME_STORAGE_KEY } from '../index.js';
6
+ export { BrandingProvider, BrandingContext, BRANDING_STORAGE_KEY, BRANDING_OVERRIDE_FLAG, } from './BrandingProvider.js';
7
+ export type { TenantBranding, BrandingContextValue } from './BrandingProvider.js';
8
+ export { useBranding } from './useBranding.js';
9
+ export { resolvePlatformLogo, usePlatformLogo } from './usePlatformLogo.js';
10
+ export type { PlatformLogoHookResult, PlatformLogoOptions, PlatformLogoScheme, PlatformLogoSlot, PlatformLogoSource, ResolvedPlatformLogo, } from './usePlatformLogo.js';
11
+ export { StatusBadge, AutoStatusBadge, getStatusVariant, formatStatusLabel, } from './components/StatusBadge.js';
12
+ export type { BadgeVariant, StatusBadgeProps, AutoStatusBadgeProps, } from './components/StatusBadge.js';
13
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAC9E,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACxE,YAAY,EACV,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EACL,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,0BAA0B,CAAA;AACjC,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,0BAA0B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3E,YAAY,EACV,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,sBAAsB,CAAA;AAG7B,OAAO,EACL,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,6BAA6B,CAAA;AACpC,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,6BAA6B,CAAA"}
@@ -1,8 +1,8 @@
1
- export { ThemeProvider } from './ThemeProvider';
2
- export { useTheme } from './useTheme';
3
- export { THEME_STORAGE_KEY } from '../index';
4
- export { BrandingProvider, BrandingContext, BRANDING_STORAGE_KEY, BRANDING_OVERRIDE_FLAG, } from './BrandingProvider';
5
- export { useBranding } from './useBranding';
6
- export { resolvePlatformLogo, usePlatformLogo } from './usePlatformLogo';
1
+ export { ThemeProvider } from './ThemeProvider.js';
2
+ export { useTheme } from './useTheme.js';
3
+ export { THEME_STORAGE_KEY } from '../index.js';
4
+ export { BrandingProvider, BrandingContext, BRANDING_STORAGE_KEY, BRANDING_OVERRIDE_FLAG, } from './BrandingProvider.js';
5
+ export { useBranding } from './useBranding.js';
6
+ export { resolvePlatformLogo, usePlatformLogo } from './usePlatformLogo.js';
7
7
  // Components — see ./components/<Name>.tsx
8
- export { StatusBadge, AutoStatusBadge, getStatusVariant, formatStatusLabel, } from './components/StatusBadge';
8
+ export { StatusBadge, AutoStatusBadge, getStatusVariant, formatStatusLabel, } from './components/StatusBadge.js';
@@ -7,5 +7,5 @@
7
7
  * const { branding, isLoading } = useBranding()
8
8
  * const logoUrl = branding?.logo_url ?? '/logos/default-logo.svg'
9
9
  */
10
- export declare function useBranding(): import("./BrandingProvider").BrandingContextValue;
10
+ export declare function useBranding(): import("./BrandingProvider.js").BrandingContextValue;
11
11
  //# sourceMappingURL=useBranding.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useBranding.d.ts","sourceRoot":"","sources":["../../src/react/useBranding.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,wBAAgB,WAAW,sDAM1B"}
1
+ {"version":3,"file":"useBranding.d.ts","sourceRoot":"","sources":["../../src/react/useBranding.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,wBAAgB,WAAW,yDAM1B"}
@@ -1,5 +1,5 @@
1
1
  import { useContext } from 'react';
2
- import { BrandingContext } from './BrandingProvider';
2
+ import { BrandingContext } from './BrandingProvider.js';
3
3
  /**
4
4
  * Access the current tenant branding values and loading state.
5
5
  *
@@ -1,4 +1,4 @@
1
- import type { TenantBranding } from './BrandingProvider';
1
+ import type { TenantBranding } from './BrandingProvider.js';
2
2
  export type PlatformLogoSlot = 'header' | 'sidebar' | 'auth' | 'favicon' | 'mark';
3
3
  export type PlatformLogoScheme = 'light' | 'dark';
4
4
  export type PlatformLogoSource = 'branding' | 'fallback' | 'none';
@@ -1 +1 @@
1
- {"version":3,"file":"usePlatformLogo.d.ts","sourceRoot":"","sources":["../../src/react/usePlatformLogo.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAGxD,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAA;AACjF,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,MAAM,CAAA;AACjD,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAA;AAEjE,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,gBAAgB,CAAA;IACvB,MAAM,CAAC,EAAE,kBAAkB,CAAA;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAA;IACjE,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,gBAAgB,CAAA;IACtB,MAAM,EAAE,kBAAkB,CAAA;IAC1B,MAAM,EAAE,kBAAkB,CAAA;IAC1B,YAAY,EAAE,OAAO,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,sBAAuB,SAAQ,oBAAoB;IAClE,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAA;IAC/B,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;IACnB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AAqDD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,EAC3C,OAAO,GAAE,mBAAwB,GAChC,oBAAoB,CAiBtB;AAED,wBAAgB,eAAe,CAAC,OAAO,GAAE,mBAAwB,GAAG,sBAAsB,CAczF"}
1
+ {"version":3,"file":"usePlatformLogo.d.ts","sourceRoot":"","sources":["../../src/react/usePlatformLogo.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAG3D,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAA;AACjF,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,MAAM,CAAA;AACjD,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAA;AAEjE,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,gBAAgB,CAAA;IACvB,MAAM,CAAC,EAAE,kBAAkB,CAAA;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAA;IACjE,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,gBAAgB,CAAA;IACtB,MAAM,EAAE,kBAAkB,CAAA;IAC1B,MAAM,EAAE,kBAAkB,CAAA;IAC1B,YAAY,EAAE,OAAO,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,sBAAuB,SAAQ,oBAAoB;IAClE,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAA;IAC/B,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;IACnB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AAqDD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,EAC3C,OAAO,GAAE,mBAAwB,GAChC,oBAAoB,CAiBtB;AAED,wBAAgB,eAAe,CAAC,OAAO,GAAE,mBAAwB,GAAG,sBAAsB,CAczF"}
@@ -1,5 +1,5 @@
1
1
  import { useMemo } from 'react';
2
- import { useBranding } from './useBranding';
2
+ import { useBranding } from './useBranding.js';
3
3
  function cleanUrl(value) {
4
4
  const trimmed = value?.trim();
5
5
  return trimmed ? trimmed : null;
@@ -1,4 +1,4 @@
1
- import type { ThemeContextValue } from '../index';
1
+ import type { ThemeContextValue } from '../index.js';
2
2
  /**
3
3
  * Hook to read + update the current theme.
4
4
  *
@@ -1 +1 @@
1
- {"version":3,"file":"useTheme.d.ts","sourceRoot":"","sources":["../../src/react/useTheme.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAGjD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,IAAI,iBAAiB,CAU5C"}
1
+ {"version":3,"file":"useTheme.d.ts","sourceRoot":"","sources":["../../src/react/useTheme.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAGpD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,IAAI,iBAAiB,CAU5C"}
@@ -1,5 +1,5 @@
1
1
  import { useContext } from 'react';
2
- import { ThemeContext } from './ThemeProvider';
2
+ import { ThemeContext } from './ThemeProvider.js';
3
3
  /**
4
4
  * Hook to read + update the current theme.
5
5
  *
package/dist/ssr/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { THEME_STORAGE_KEY } from '../index';
1
+ import { THEME_STORAGE_KEY } from '../index.js';
2
2
  /**
3
3
  * Returns a self-contained JavaScript string that — when executed before
4
4
  * your React app hydrates — applies the correct `.dark` or `.light` class
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsuite/theme",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {
@@ -15,7 +15,6 @@
15
15
  "files": [
16
16
  "dist",
17
17
  "src/css",
18
- "src/tailwind-preset.js",
19
18
  "src/preset-v4.css"
20
19
  ],
21
20
  "main": "dist/index.js",
@@ -33,7 +32,6 @@
33
32
  "import": "./dist/ssr/index.js",
34
33
  "types": "./dist/ssr/index.d.ts"
35
34
  },
36
- "./tailwind-preset": "./src/tailwind-preset.js",
37
35
  "./preset-v4.css": "./src/preset-v4.css",
38
36
  "./css": "./src/css/index.css",
39
37
  "./vars.css": "./src/css/vars.css",
@@ -78,7 +78,7 @@
78
78
  ============================================================ */
79
79
  --braden-light-bg-body: oklch(0.980 0.002 50); /* warm white */
80
80
  --braden-light-bg-surface: oklch(0.996 0.001 50); /* near white */
81
- --braden-light-bg-panel: oklch(1.000 0 0); /* white */
81
+ --braden-light-bg-panel: oklch(0.994 0.002 250); /* near-white; pure white is banned in every role */
82
82
  --braden-light-bg-input: oklch(0.972 0.003 250); /* cool light grey */
83
83
 
84
84
  /* Braden light text scale — hue 250 (navy cast), capped L=0.22 */
@@ -161,9 +161,9 @@
161
161
  /* ============================================================
162
162
  SHADOWS — corporate subtle, no neon glow
163
163
  ============================================================ */
164
- --shadow-sm: 0 1px 2px oklch(0 0 0 / 0.08);
165
- --shadow-md: 0 4px 6px oklch(0 0 0 / 0.10), 0 2px 4px oklch(0 0 0 / 0.06);
166
- --shadow-lg: 0 10px 15px oklch(0 0 0 / 0.10), 0 4px 6px oklch(0 0 0 / 0.05);
164
+ --shadow-sm: 0 1px 2px oklch(0.30 0.03 250 / 0.08);
165
+ --shadow-md: 0 4px 6px oklch(0.30 0.03 250 / 0.10), 0 2px 4px oklch(0.30 0.03 250 / 0.06);
166
+ --shadow-lg: 0 10px 15px oklch(0.30 0.03 250 / 0.10), 0 4px 6px oklch(0.30 0.03 250 / 0.05);
167
167
 
168
168
  /* Card glow is a dark-mode-only device (see .dark below); light mode
169
169
  stays border-only, consistent with the corporate "no neon glow" rule. */
@@ -74,18 +74,21 @@
74
74
  (braden/src/index.css) so the corporate and D2C marketing surfaces
75
75
  share one visual language for this specific device.
76
76
  ============================================================ */
77
+ /* Grid lines are hue-matched to the surface, never pure black/white — the
78
+ contract bans both endpoints in every role, alpha forms included. Light
79
+ mode stays neutral and quiet; dark mode takes a faint accent cast, which
80
+ is where the cyber register lives. 32px = the pre-auth marketing tile. */
77
81
  .bsuite-hero-grid {
78
82
  position: absolute;
79
83
  inset: 0;
84
+ --grid-line: oklch(0.40 0.030 258 / 0.07);
80
85
  background-image:
81
- linear-gradient(oklch(0 0 0 / 0.03) 1px, transparent 1px),
82
- linear-gradient(90deg, oklch(0 0 0 / 0.03) 1px, transparent 1px);
86
+ linear-gradient(var(--grid-line) 1px, transparent 1px),
87
+ linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
83
88
  background-size: 32px 32px;
84
89
  }
85
90
  .dark .bsuite-hero-grid {
86
- background-image:
87
- linear-gradient(oklch(1 0 0 / 0.03) 1px, transparent 1px),
88
- linear-gradient(90deg, oklch(1 0 0 / 0.03) 1px, transparent 1px);
91
+ --grid-line: oklch(0.82 0.020 262 / 0.055);
89
92
  }
90
93
 
91
94
  /* ===== prefers-reduced-motion — cap animation duration ===== */
package/src/css/vars.css CHANGED
@@ -12,13 +12,27 @@
12
12
  * Consumers should bind to Layer 3 (roles) or Layer 4 (shadcn) — never
13
13
  * directly to palette primitives. Roles make white-labelling safe.
14
14
  *
15
- * Colourblind policy (WCAG 1.4.1 + audit constraint):
16
- * --role-error / --role-destructive = PURPLE, never red-vs-green.
17
- * Only flag where colour is the SOLE signifier of state.
15
+ * Colourblind policy (WCAG 1.4.1) REWRITTEN 2026-08-02, contract 0.7.0:
16
+ * The invariant is SEPARATION, not a fixed hue. --role-error must be
17
+ * maximally separated from --role-primary under deuteranopia and
18
+ * protanopia. D2C's primary is blue, so error is RED.
19
+ * The previous "error = purple" rule was actively harmful: under
20
+ * protanopia purple measured DeltaE 0.006 against primary blue — the
21
+ * destructive colour and the primary action colour were the SAME swatch.
22
+ * Purple and indigo are therefore quarantined from semantics entirely.
23
+ * Warm states (red/amber) separate by LIGHTNESS, not hue — under
24
+ * deuteranopia everything warm collapses to yellow, so error is the dark
25
+ * one and warning the light one. Never put two cool hues in opposition.
26
+ * Colour is never the sole signifier: shape + label always.
27
+ * TENANT OVERRIDE BLOCKED on --role-error / --role-destructive.
18
28
  *
19
- * Eye-strain policy:
20
- * Dark-mode text capped at L=0.94. Pure white (L=1.0) is banned as a
21
- * text token. 5-tier scale verified WCAG AA minimum throughout.
29
+ * Eye-strain / pure-endpoint policy:
30
+ * Pure white (L=1.0) and pure black (L=0) are BANNED in EVERY role —
31
+ * text, surface, border, ring and shadow alike (operator ruling
32
+ * 2026-08-02). The prior text-only scoping of this rule is what let
33
+ * --light-bg-accent ship as oklch(1 0 0.5) and render every shadcn card
34
+ * and popover pure white. "White" here means oklch(0.982 0.002 248)
35
+ * (#f8f9fa); the lightest surface is bg-panel at L=0.994.
22
36
  *
23
37
  * OKLCH format is MANDATORY for all new tokens. No hex/rgb/hsl.
24
38
  * Syntax: oklch(L C H) — L=lightness 0–1, C=chroma 0–0.4, H=hue 0–360.
@@ -41,6 +55,17 @@
41
55
  --neon-electric-green: oklch(0.723 0.192 149.6);
42
56
  --neon-electric-lavender: oklch(0.736 0.141 285.6);
43
57
 
58
+ /* Semantic hues — added 2026-08-02 (contract 0.7.0). The 11 above were
59
+ chosen for looks; these four were chosen for SEPARATION, measured via
60
+ Vienot-Brettel dichromat simulation. Worst-case DeltaE(OKLab) across
61
+ normal/deuteranopia/protanopia for every semantic pair is >= 0.156;
62
+ the pairing these replace scored 0.006. Never bind a component to
63
+ these directly — bind to the --role-* alias. */
64
+ --neon-electric-red: oklch(0.580 0.230 25); /* error / destructive */
65
+ --neon-electric-amber: oklch(0.800 0.150 75); /* warning */
66
+ --neon-electric-teal: oklch(0.600 0.130 195); /* success */
67
+ --neon-electric-deep: oklch(0.380 0.140 270); /* secondary action */
68
+
44
69
  /* ============================================================
45
70
  LAYER 2 — SURFACE PRIMITIVES (light mode)
46
71
  ============================================================ */
@@ -49,7 +74,10 @@
49
74
  --light-bg-primary: oklch(0.961 0 0.5);
50
75
  --light-bg-secondary: oklch(0.982 0.002 248);
51
76
  --light-bg-tertiary: oklch(0.963 0.003 228.9);
52
- --light-bg-accent: oklch(1 0 0.5);
77
+ /* bg-panel. Was oklch(1 0 0.5) — pure white, and since --card and
78
+ --popover bridge to it, every shadcn card and popover in all five D2C
79
+ apps rendered pure white in light mode from this one line. */
80
+ --light-bg-accent: oklch(0.994 0.002 260);
53
81
 
54
82
  /* D2C light text scale — hue 260 (cool blue-grey), capped L=0.22.
55
83
  Measured WCAG contrast vs --light-bg-primary oklch(0.961 0 0.5), OKLCH ->
@@ -63,9 +91,6 @@
63
91
  --light-text-muted: oklch(0.52 0.018 260); /* 4.92:1 ✓ AA (normal, 4.5:1 floor) */
64
92
  --light-text-subtle: oklch(0.60 0.012 260); /* 3.52:1 ✓ AA large only (3:1) — ✗ AA normal */
65
93
  --light-text-disabled: oklch(0.72 0.010 260); /* 2.21:1 ✗ fails AA at any size — MUST pair with icon cue */
66
- /* Legacy aliases (kept for consumers that haven't migrated) */
67
- --light-text-tertiary: var(--light-text-muted);
68
- --light-text-quaternary: var(--light-text-subtle);
69
94
 
70
95
  --light-border: oklch(0.942 0.005 247.9);
71
96
  --light-hover: oklch(0.963 0.003 228.9);
@@ -83,19 +108,33 @@
83
108
 
84
109
  /* Action colours */
85
110
  --role-primary: var(--neon-electric-blue); /* oklch(0.546 0.215 262.9) */
86
- --role-secondary: var(--neon-electric-indigo); /* oklch(0.511 0.23 277) */
111
+ --role-secondary: var(--neon-electric-deep); /* was indigo — 0.037 from primary */
87
112
  --role-accent: var(--neon-electric-cyan); /* oklch(0.769 0.132 191.7) */
88
113
 
89
- /* ⚠️ COLOURBLIND POLICY: error = PURPLE never red/coral.
90
- Red-vs-green is the most common colour-vision deficiency (~8% M, ~0.5% F).
91
- Purple (hue 283) is unambiguous to deuteranopes and protanopes.
114
+ /* ⚠️ SEPARATION POLICY (contract 0.7.0). The invariant is NOT a fixed hue —
115
+ it is that error must be maximally separated from PRIMARY under
116
+ deuteranopia and protanopia. D2C's primary is blue, so error is RED.
117
+ Purple scored DeltaE 0.006 against primary under protanopia: the
118
+ destructive colour and the primary action colour were the same swatch.
119
+ Purple and indigo are quarantined from semantics entirely.
120
+ Warm states separate by LIGHTNESS (under deuteranopia red and amber
121
+ both read yellow — error is the dark one, warning the light one).
92
122
  TENANT OVERRIDE BLOCKED: BrandingProvider rejects --role-error changes. */
93
- --role-error: var(--neon-electric-purple); /* oklch(0.568 0.202 283.1) */
94
- --role-destructive: var(--neon-electric-purple); /* synonym — same value */
95
- --role-warning: var(--neon-electric-orange); /* oklch(0.728 0.168 22.5) */
96
- --role-success: var(--neon-electric-green); /* oklch(0.723 0.192 149.6) */
123
+ --role-error: var(--neon-electric-red); /* was purple — 0.006 from primary */
124
+ --role-destructive: var(--neon-electric-red); /* synonym — same value */
125
+ --role-warning: var(--neon-electric-amber); /* was orange h22.5 0.059 from red */
126
+ --role-success: var(--neon-electric-teal); /* was green — 0.043 from red */
97
127
  --role-info: var(--neon-electric-cyan); /* oklch(0.769 0.132 191.7) */
98
128
 
129
+ /* AA-safe text renderings of the saturated roles on light surfaces.
130
+ A saturated fill colour is not a legible text colour; these are the
131
+ text variants and are the ONLY correct token for coloured type. */
132
+ --role-primary-text: oklch(0.541 0.215 262.9); /* 4.71:1 ✓ AA */
133
+ --role-accent-text: oklch(0.499 0.132 191.7); /* 4.70:1 ✓ AA */
134
+ --role-error-text: oklch(0.556 0.230 25); /* 4.69:1 ✓ AA */
135
+ --role-warning-text: oklch(0.535 0.150 75); /* 4.70:1 ✓ AA */
136
+ --role-success-text: oklch(0.501 0.130 195); /* 4.70:1 ✓ AA */
137
+
99
138
  /* Text roles — six-tier scale (W3 §3.1 contract). Every tier below is
100
139
  measured against --role-bg-body (the conservative floor); all tiers
101
140
  score HIGHER against --role-bg-panel/--role-bg-surface. */
@@ -107,27 +146,24 @@
107
146
  --role-text-disabled: var(--light-text-disabled); /* 2.21:1 ✗ icon cue required */
108
147
 
109
148
  /* Surface roles */
110
- --role-bg-body: var(--light-bg-primary);
111
- --role-bg-surface: var(--light-bg-secondary);
112
- --role-bg-panel: var(--light-bg-accent);
113
- --role-bg-input: var(--light-bg-tertiary);
114
- --role-border: var(--light-border);
115
-
116
- /* Inverse text — safe on coloured fills (mid-lightness L≈0.5–0.7) */
117
- --text-on-primary: oklch(0.99 0 0); /* on blue L≈0.55 — 5.9:1 ✓ AA */
118
- --text-on-accent: oklch(0.17 0.02 260); /* on cyan L≈0.77 — 6.1:1 ✓ AA */
119
- --text-on-error: oklch(0.99 0 0); /* on purple L≈0.57 */
120
- --text-on-success: oklch(0.17 0.02 260); /* on green L≈0.72 — dark text */
121
- --text-on-warning: oklch(0.17 0.02 260); /* on amber L≈0.82 — dark text */
149
+ --role-bg-body: var(--light-bg-primary);
150
+ --role-bg-surface: var(--light-bg-secondary);
151
+ --role-bg-panel: var(--light-bg-accent);
152
+ --role-bg-input: var(--light-bg-tertiary);
153
+ --role-bg-sunken: oklch(0.938 0.004 250);
154
+ --role-border: var(--light-border);
155
+ --role-border-strong: oklch(0.880 0.008 250);
122
156
 
123
- /* ============================================================
124
- LAYER 3 SEMANTIC STATUS (legacy: keeps --status-* names)
125
- Deprecated: prefer --role-* going forward.
126
- ============================================================ */
127
- --status-success: var(--role-success);
128
- --status-warning: var(--role-warning);
129
- --status-error: var(--role-error); /* was coral; now purple per colourblind policy */
130
- --status-info: var(--role-info);
157
+ /* Inverse text on coloured fills. Two values only — the fill is either
158
+ light enough to need dark text, or dark enough to need light text.
159
+ Neither is a pure endpoint. */
160
+ --text-on-light-fill: oklch(0.98 0.006 260);
161
+ --text-on-dark-fill: oklch(0.19 0.020 255);
162
+ --text-on-primary: var(--text-on-light-fill); /* on blue L≈0.55 — 4.88:1 ✓ AA */
163
+ --text-on-error: var(--text-on-light-fill); /* on red L≈0.58 4.55:1 AA */
164
+ --text-on-accent: var(--text-on-dark-fill); /* on cyan L≈0.77 — 9.38:1 ✓ AAA */
165
+ --text-on-warning: var(--text-on-dark-fill); /* on amber L≈0.80 — 9.68:1 ✓ AAA */
166
+ --text-on-success: var(--text-on-dark-fill); /* on teal L≈0.60 — 5.17:1 ✓ AA */
131
167
 
132
168
  /* ============================================================
133
169
  LAYER 4 — SHADCN/UI BRIDGE (light mode)
@@ -154,13 +190,6 @@
154
190
  --input: var(--role-border);
155
191
  --ring: var(--role-primary);
156
192
 
157
- /* ============================================================
158
- WCAG AA compliant text pairs for raw neon on light bg
159
- (neon cyan L=0.77 on light bg is only 1.76:1 — fails 4.5:1)
160
- ============================================================ */
161
- --color-accent-text: oklch(0.486 0.084 191.5); /* Dark cyan — AA on light bg */
162
- --color-primary-text: oklch(0.485 0.243 263.6); /* Saturated blue — AA on light bg */
163
-
164
193
  /* ============================================================
165
194
  CANONICAL ACCENT GRADIENT (W3 §3.1) — exactly ONE gradient
166
195
  exists suite-wide. Marketing/pre-auth heroes ONLY — never on
@@ -190,44 +219,72 @@
190
219
  }
191
220
 
192
221
  /* ============================================================
193
- SRGB FALLBACKS (for browsers without oklch support) W3 §3.4
194
- True-source hexes, each re-derived directly from its oklch token
195
- (OKLCH -> OKLab -> linear-sRGB -> gamma-encoded sRGB) rather than
196
- eyeballed against a Tailwind palette. Round-trip-verified: every
197
- value below computes ΔE(OKLab) < 0.01 against its oklch token —
198
- at least twice inside the < 0.02 acceptance bar (most values are
199
- one to two orders of magnitude inside it; see docs/TOKEN-MAPPING.md
200
- §8 for the full per-token ΔE table). The prior Tailwind-palette-
201
- derived hexes were ΔE 0.03-0.10 off.
202
-
203
- Deliberately declared at the TOP LEVEL, not nested inside another
204
- `:root { }` block: engines that lack `oklch()` support predate CSS
205
- nesting too, so a `@supports` block nested inside `:root` would be
206
- silently dropped by exactly the browsers it's meant to serve when
207
- this file is consumed raw (unbundled). It works when bundled
208
- through the Tailwind v4 / LightningCSS pipeline only because that
209
- pipeline hoists nested at-rules — this top-level form is correct
210
- for both the raw and the bundled case.
222
+ LAYER 5PER-APP ACCENT
223
+ Added 2026-08-02: the 0.7.0 contract specifies these blocks but the
224
+ package had never shipped them only a `var(--app-accent, )` fallback
225
+ reference, so every app resolved to the shared cyan and the per-app
226
+ identity existed on paper only.
227
+
228
+ Set `data-app` on <body>. The FILL is the brand hue; the TEXT variants
229
+ are solved per mode, because a saturated fill colour is not a legible
230
+ text colour and the accent must never render below AA.
211
231
  ============================================================ */
212
- @supports not (color: oklch(0 0 0)) {
213
- :root {
214
- --neon-electric-blue: #2563eb;
215
- --neon-electric-cyan: #00cec9;
216
- --neon-electric-indigo: #4f46e5;
217
- --neon-electric-purple: #6c5ce7;
218
- --neon-electric-magenta: #fd79a8;
219
- --neon-electric-pink: #ec4899;
220
- --neon-electric-coral: #ff4757;
221
- --neon-electric-orange: #ff7675;
222
- --neon-electric-yellow: #fdcb6e;
223
- --neon-electric-green: #22c55e;
224
- --neon-electric-lavender: #a29bfe;
225
- --role-error: #6c5ce7;
226
- --role-destructive: #6c5ce7;
227
- --light-text-primary: #171b22;
228
- --dark-text-primary: #e8eaf6;
229
- }
232
+ [data-app="crm7"] {
233
+ --app-accent: oklch(0.769 0.132 191.7);
234
+ --app-accent-text-l: oklch(0.505 0.132 191.7);
235
+ --app-accent-text-d: oklch(0.769 0.132 191.7);
236
+ }
237
+ [data-app="bsu"] {
238
+ --app-accent: oklch(0.541 0.247 293);
239
+ --app-accent-text-l: oklch(0.564 0.247 293);
240
+ --app-accent-text-d: oklch(0.745 0.247 293);
241
+ }
242
+ [data-app="conduit"] {
243
+ --app-accent: oklch(0.723 0.192 149.6);
244
+ --app-accent-text-l: oklch(0.505 0.192 149.6);
245
+ --app-accent-text-d: oklch(0.723 0.192 149.6);
230
246
  }
247
+ [data-app="r80"] {
248
+ --app-accent: oklch(0.666 0.157 58.3);
249
+ --app-accent-text-l: oklch(0.546 0.157 58.3);
250
+ --app-accent-text-d: oklch(0.703 0.157 58.3);
251
+ }
252
+ /* R8.04 — the rates engine replacing R80.3. Deliberately REUSES r80's
253
+ orange: it serves the same users doing the same job, and a rates engine
254
+ is not where people should be re-learning which app they are in.
255
+ Requested by the r804 lane 2026-08-02. */
256
+ [data-app="r804"] {
257
+ --app-accent: oklch(0.666 0.157 58.3);
258
+ --app-accent-text-l: oklch(0.546 0.157 58.3);
259
+ --app-accent-text-d: oklch(0.703 0.157 58.3);
260
+ }
261
+ [data-app="throughput"] {
262
+ --app-accent: oklch(0.656 0.212 354.3);
263
+ --app-accent-text-l: oklch(0.564 0.212 354.3);
264
+ --app-accent-text-d: oklch(0.731 0.212 354.3);
265
+ }
266
+ /* Light/dark rebinding of the text variant. */
267
+ :root { --app-accent-text: var(--app-accent-text-l); }
268
+ .dark { --app-accent-text: var(--app-accent-text-d); }
269
+
270
+ /* ============================================================
271
+ NO sRGB FALLBACK — DELIBERATELY REMOVED 2026-08-02.
272
+ Precedent: precedent__bsuite__20260802__theme_conformance_is_absolute.
273
+
274
+ The `@supports not (color: oklch(0 0 0))` block that stood here shipped
275
+ a parallel hex palette. It is gone, and must not be reinstated:
276
+
277
+ · OKLCH has been Baseline-available in every evergreen engine since
278
+ Firefox 113 (May 2023), after Safari 15.4 (2022) and Chrome 111
279
+ (Mar 2023). It guarded a population that no longer exists.
280
+ · It was a second source of truth for the same colours, so it drifted:
281
+ it still bound --role-error to purple after the semantic set moved
282
+ to red. A fallback that disagrees with the tokens is worse than none.
283
+
284
+ If a colour needs to exist in hex (email HTML, PDF, canvas, manifest),
285
+ derive it at that call site from the oklch token — do not re-introduce a
286
+ second palette here.
287
+ ============================================================ */
231
288
 
232
289
  /* ============================================================
233
290
  DARK MODE — Layer 2 surface overrides + Layer 3 role rebind
@@ -253,9 +310,6 @@
253
310
  --dark-text-muted: oklch(0.68 0.018 260); /* metadata — 6.69:1 ✓ AA (normal) */
254
311
  --dark-text-subtle: oklch(0.56 0.015 260); /* placeholders — 4.14:1 ✓ AA large only — ✗ AA normal */
255
312
  --dark-text-disabled: oklch(0.44 0.010 260); /* disabled — 2.48:1 ✗ fails AA at any size — MUST pair with icon cue */
256
- /* Legacy aliases */
257
- --dark-text-tertiary: var(--dark-text-muted);
258
- --dark-text-quaternary: var(--dark-text-disabled);
259
313
 
260
314
  --dark-border: oklch(0.428 0.015 248.2);
261
315
  --dark-hover: oklch(0.39 0.035 265);
@@ -270,14 +324,17 @@
270
324
 
271
325
  --role-bg-body: var(--dark-bg-primary);
272
326
  --role-bg-surface: var(--dark-bg-secondary);
273
- --role-bg-panel: oklch(0.19 0.02 260 / 0.8);
274
- --role-bg-input: oklch(0.24 0.02 260 / 0.6);
327
+ --role-bg-panel: oklch(0.190 0.020 260);
328
+ --role-bg-input: oklch(0.240 0.020 260);
329
+ --role-bg-sunken: oklch(0.145 0.022 268);
275
330
  --role-border: var(--dark-border);
276
331
 
277
- /* Accent/primary text tokens flip to raw neons in dark mode
278
- contrast on navy bg is already ≥ 9:1 for these values. */
279
- --color-accent-text: oklch(0.769 0.132 191.7);
280
- --color-primary-text: oklch(0.623 0.188 259.8);
332
+ /* Role text variants rebind for the dark surface all AAA on navy. */
333
+ --role-primary-text: oklch(0.721 0.215 262.9); /* 7.01:1 AAA */
334
+ --role-accent-text: var(--neon-electric-cyan); /* 9.78:1 ✓ AAA */
335
+ --role-error-text: oklch(0.786 0.230 25); /* 7.00:1 ✓ AAA */
336
+ --role-warning-text: var(--neon-electric-amber);/* 10.10:1 ✓ AAA */
337
+ --role-success-text: oklch(0.671 0.130 195); /* 6.99:1 ✓ AAA */
281
338
 
282
339
  /* Glow/shadow scale up in dark mode */
283
340
  --glow-strength: 0.6;
package/src/preset-v4.css CHANGED
@@ -1,134 +1,143 @@
1
1
  /*
2
- * @bsuite/theme — Tailwind v4 @theme block
3
- * Version 0.3.1
2
+ * @bsuite/theme — Tailwind v4 @theme bridge
3
+ * Version 0.7.0
4
4
  *
5
- * Tailwind v4 exposes design tokens to the engine via CSS @theme blocks.
6
- * Import this before any app-specific @theme overrides:
5
+ * Usage (every D2C app; braden imports braden-css instead of css):
7
6
  *
8
7
  * @import 'tailwindcss';
9
- * @import '@bsuite/theme/preset-v4.css';
8
+ * @import '@bsuite/theme/css'; ← defines --role-* (vars.css)
9
+ * @import '@bsuite/theme/preset-v4.css'; ← this file, maps them to utilities
10
10
  *
11
- * @theme {
12
- * --color-app-primary: oklch(0.45 0.18 142); -- app-specific override
13
- * }
11
+ * ─────────────────────────────────────────────────────────────────────────
12
+ * WHY `@theme inline` AND NOT `@theme`
13
+ *
14
+ * Until 0.7.0 this file used a plain `@theme` block containing hardcoded
15
+ * oklch literals — a full second copy of every value in vars.css. Two
16
+ * consequences, both real bugs:
17
+ *
18
+ * 1. DARK MODE DID NOT REACH THESE UTILITIES. Plain `@theme` emits
19
+ * `--color-card: <literal>` at :root and makes `bg-card` resolve to
20
+ * `var(--color-card)`. vars.css rebinds `--role-bg-panel` under
21
+ * `.dark`, but nothing rebound `--color-card`, so `bg-card` stayed on
22
+ * its light-mode literal in dark mode.
23
+ * 2. IT DRIFTED. The duplicate copy still said `--color-card: oklch(1 0
24
+ * 0.5)` (pure white) and `--color-destructive: <purple>` after the
25
+ * token source had moved on from both.
26
+ *
27
+ * `@theme inline` emits the `var()` reference into the utility itself, so
28
+ * `bg-card` resolves through `--role-bg-panel` and follows every `.dark`
29
+ * and BrandingProvider rebinding for free. vars.css is the ONLY source of
30
+ * truth for values; this file contains no colour literals at all.
31
+ *
32
+ * Adding a colour literal to this file re-creates both bugs. Don't.
33
+ * ─────────────────────────────────────────────────────────────────────────
14
34
  *
15
35
  * Token naming: `--color-{name}` → Tailwind generates `bg-{name}`,
16
36
  * `text-{name}`, `border-{name}` etc. automatically.
17
- *
18
- * Layer order matches vars.css:
19
- * 1. Palette — --color-neon-electric-*
20
- * 2. Surface — --color-{light|dark}-{bg|text}-*
21
- * 3. Role — --color-role-* ← bind to these in components
22
- * 4. Shadcn — --color-background, --color-foreground, etc.
23
- * 5. Text-on — --color-text-on-*
24
37
  */
25
38
 
26
- @theme {
27
- /* ============================================================
28
- LAYER 1 — Neon Electric palette (11 colours, FROZEN)
29
- ============================================================ */
30
- --color-neon-electric-blue: oklch(0.546 0.215 262.9);
31
- --color-neon-electric-cyan: oklch(0.769 0.132 191.7);
32
- --color-neon-electric-indigo: oklch(0.511 0.23 277);
33
- --color-neon-electric-purple: oklch(0.568 0.202 283.1);
34
- --color-neon-electric-magenta: oklch(0.742 0.167 359.5);
35
- --color-neon-electric-pink: oklch(0.656 0.212 354.3);
36
- --color-neon-electric-coral: oklch(0.669 0.219 20.9);
37
- --color-neon-electric-orange: oklch(0.728 0.168 22.5);
38
- --color-neon-electric-yellow: oklch(0.868 0.125 81.4);
39
- --color-neon-electric-green: oklch(0.723 0.192 149.6);
40
- --color-neon-electric-lavender: oklch(0.736 0.141 285.6);
41
-
39
+ @theme inline {
42
40
  /* ============================================================
43
- LAYER 2Surface tokens (light)
41
+ LAYER 1Palette. Exposed for decorative use only.
42
+ Bind components to the role tokens below, never to these:
43
+ white-labelling mutates roles, so a palette-bound component
44
+ silently ignores tenant branding.
44
45
  ============================================================ */
45
- --color-light-bg-primary: oklch(0.961 0 0.5);
46
- --color-light-bg-secondary: oklch(0.982 0.002 248);
47
- --color-light-bg-tertiary: oklch(0.963 0.003 228.9);
48
- --color-light-bg-accent: oklch(1 0 0.5);
49
-
50
- /* Eye-strain-safe light text scale (L capped at 0.22, hue 260) */
51
- --color-light-text-primary: oklch(0.22 0.015 260); /* 13.2:1 AAA */
52
- --color-light-text-secondary: oklch(0.38 0.018 260); /* 8.1:1 AAA */
53
- --color-light-text-muted: oklch(0.52 0.018 260); /* 4.9:1 AA */
54
- --color-light-text-subtle: oklch(0.60 0.012 260); /* 3.8:1 AA large */
55
- --color-light-text-disabled: oklch(0.72 0.010 260);
46
+ --color-neon-electric-blue: var(--neon-electric-blue);
47
+ --color-neon-electric-cyan: var(--neon-electric-cyan);
48
+ --color-neon-electric-indigo: var(--neon-electric-indigo);
49
+ --color-neon-electric-purple: var(--neon-electric-purple);
50
+ --color-neon-electric-magenta: var(--neon-electric-magenta);
51
+ --color-neon-electric-pink: var(--neon-electric-pink);
52
+ --color-neon-electric-coral: var(--neon-electric-coral);
53
+ --color-neon-electric-orange: var(--neon-electric-orange);
54
+ --color-neon-electric-yellow: var(--neon-electric-yellow);
55
+ --color-neon-electric-green: var(--neon-electric-green);
56
+ --color-neon-electric-lavender: var(--neon-electric-lavender);
57
+ --color-neon-electric-red: var(--neon-electric-red);
58
+ --color-neon-electric-amber: var(--neon-electric-amber);
59
+ --color-neon-electric-teal: var(--neon-electric-teal);
60
+ --color-neon-electric-deep: var(--neon-electric-deep);
56
61
 
57
62
  /* ============================================================
58
- LAYER 2 — Surface tokens (dark)
63
+ LAYER 2 — Surfaces. Role-bound, so they flip with .dark.
59
64
  ============================================================ */
60
- --color-dark-bg-primary: oklch(0.166 0.026 269.4);
61
- --color-dark-bg-secondary: oklch(0.242 0.03 269.9);
62
- --color-dark-bg-tertiary: oklch(0.326 0.036 266.7);
63
- --color-dark-bg-quaternary: oklch(0.39 0.035 265);
64
- --color-dark-bg-accent: oklch(0.292 0.034 270);
65
-
66
- /* Eye-strain-safe dark text scale (L capped at 0.94, hue 260) */
67
- --color-dark-text-primary: oklch(0.94 0.012 260); /* 13.9:1 AAA */
68
- --color-dark-text-secondary: oklch(0.82 0.015 260); /* 9.8:1 AAA */
69
- --color-dark-text-muted: oklch(0.68 0.018 260); /* 5.6:1 AA */
70
- --color-dark-text-subtle: oklch(0.56 0.015 260); /* 3.6:1 AA large */
71
- --color-dark-text-disabled: oklch(0.44 0.010 260);
65
+ --color-bg-body: var(--role-bg-body);
66
+ --color-bg-surface: var(--role-bg-surface);
67
+ --color-bg-panel: var(--role-bg-panel);
68
+ --color-bg-input: var(--role-bg-input);
69
+ --color-bg-sunken: var(--role-bg-sunken);
70
+ --color-border-strong: var(--role-border-strong);
72
71
 
73
72
  /* ============================================================
74
- LAYER 3 — Role aliases
75
- Consumers should only reference these — never palette names.
73
+ LAYER 3 — Text scale
76
74
  ============================================================ */
77
- --color-role-primary: oklch(0.546 0.215 262.9); /* electric blue */
78
- --color-role-secondary: oklch(0.511 0.23 277); /* electric indigo */
79
- --color-role-accent: oklch(0.769 0.132 191.7); /* electric cyan */
80
-
81
- /* Colourblind-safe: error = purple, never red/coral */
82
- --color-role-error: oklch(0.568 0.202 283.1); /* electric purple */
83
- --color-role-destructive: oklch(0.568 0.202 283.1); /* synonym */
84
- --color-role-warning: oklch(0.728 0.168 22.5); /* electric orange */
85
- --color-role-success: oklch(0.723 0.192 149.6); /* electric green */
86
- --color-role-info: oklch(0.769 0.132 191.7); /* electric cyan */
87
-
88
- /* Semantic status aliases (deprecated: prefer role-*) */
89
- --color-status-success: oklch(0.723 0.192 149.6);
90
- --color-status-warning: oklch(0.728 0.168 22.5);
91
- --color-status-error: oklch(0.568 0.202 283.1); /* purple — colourblind policy */
92
- --color-status-info: oklch(0.769 0.132 191.7);
75
+ --color-text-heading: var(--role-text-heading);
76
+ --color-text-body: var(--role-text-body);
77
+ --color-text-secondary: var(--role-text-secondary);
78
+ --color-text-muted: var(--role-text-muted);
79
+ --color-text-subtle: var(--role-text-subtle);
80
+ --color-text-disabled: var(--role-text-disabled);
93
81
 
94
82
  /* ============================================================
95
- LAYER 4Shadcn/ui bridge
96
- Tailwind generates bg-background, text-foreground, etc.
83
+ LAYER 3Semantic roles.
84
+ Error is RED because D2C's primary is blue and error must be
85
+ maximally separated from primary under dichromacy — see the
86
+ separation policy in vars.css. Not a fixed-hue rule.
97
87
  ============================================================ */
98
- --color-background: oklch(0.961 0 0.5);
99
- --color-foreground: oklch(0.22 0.015 260);
100
- --color-card: oklch(1 0 0.5);
101
- --color-card-foreground: oklch(0.22 0.015 260);
102
- --color-popover: oklch(1 0 0.5);
103
- --color-popover-foreground: oklch(0.22 0.015 260);
104
- --color-primary: oklch(0.546 0.215 262.9);
105
- --color-primary-foreground: oklch(0.99 0 0);
106
- --color-secondary: oklch(0.511 0.23 277);
107
- --color-secondary-foreground: oklch(0.99 0 0);
108
- --color-muted: oklch(0.963 0.003 228.9);
109
- --color-muted-foreground: oklch(0.52 0.018 260);
110
- --color-accent: oklch(0.769 0.132 191.7);
111
- --color-accent-foreground: oklch(0.17 0.02 260);
112
- --color-destructive: oklch(0.568 0.202 283.1); /* purple — colourblind policy */
113
- --color-destructive-foreground: oklch(0.99 0 0);
114
- --color-border: oklch(0.942 0.005 247.9);
115
- --color-input: oklch(0.942 0.005 247.9);
116
- --color-ring: oklch(0.546 0.215 262.9);
88
+ --color-role-primary: var(--role-primary);
89
+ --color-role-secondary: var(--role-secondary);
90
+ --color-role-accent: var(--role-accent);
91
+ --color-role-error: var(--role-error);
92
+ --color-role-destructive: var(--role-destructive);
93
+ --color-role-warning: var(--role-warning);
94
+ --color-role-success: var(--role-success);
95
+ --color-role-info: var(--role-info);
96
+
97
+ /* AA-safe text variants — the only correct token for coloured type.
98
+ A saturated fill colour is not a legible text colour. */
99
+ --color-primary-text: var(--role-primary-text);
100
+ --color-accent-text: var(--role-accent-text);
101
+ --color-error-text: var(--role-error-text);
102
+ --color-warning-text: var(--role-warning-text);
103
+ --color-success-text: var(--role-success-text);
104
+
105
+ /* Per-app accent (Layer 5). Resolves through [data-app] on <body>,
106
+ falling back to the shared accent when no app is declared. */
107
+ --color-app-accent: var(--app-accent, var(--role-accent));
108
+ --color-app-accent-text: var(--app-accent-text, var(--role-accent-text));
117
109
 
118
110
  /* ============================================================
119
- LAYER 5Inverse text (for coloured fill surfaces)
111
+ LAYER 4shadcn/ui bridge
120
112
  ============================================================ */
121
- --color-text-on-primary: oklch(0.99 0 0);
122
- --color-text-on-accent: oklch(0.17 0.02 260);
123
- --color-text-on-error: oklch(0.99 0 0);
124
- --color-text-on-success: oklch(0.17 0.02 260);
125
- --color-text-on-warning: oklch(0.17 0.02 260);
113
+ --color-background: var(--role-bg-body);
114
+ --color-foreground: var(--role-text-body);
115
+ --color-card: var(--role-bg-panel);
116
+ --color-card-foreground: var(--role-text-body);
117
+ --color-popover: var(--role-bg-panel);
118
+ --color-popover-foreground: var(--role-text-body);
119
+ --color-primary: var(--role-primary);
120
+ --color-primary-foreground: var(--text-on-primary);
121
+ --color-secondary: var(--role-secondary);
122
+ --color-secondary-foreground: var(--text-on-primary);
123
+ --color-muted: var(--role-bg-input);
124
+ --color-muted-foreground: var(--role-text-muted);
125
+ --color-accent: var(--role-accent);
126
+ --color-accent-foreground: var(--text-on-accent);
127
+ --color-destructive: var(--role-destructive);
128
+ --color-destructive-foreground: var(--text-on-error);
129
+ --color-border: var(--role-border);
130
+ --color-input: var(--role-border);
131
+ --color-ring: var(--role-primary);
126
132
 
127
133
  /* ============================================================
128
- WCAG AA text pairs for raw neons on light backgrounds
134
+ LAYER 5 Inverse text on coloured fills
129
135
  ============================================================ */
130
- --color-accent-text: oklch(0.486 0.084 191.5);
131
- --color-primary-text: oklch(0.485 0.243 263.6);
136
+ --color-text-on-primary: var(--text-on-primary);
137
+ --color-text-on-accent: var(--text-on-accent);
138
+ --color-text-on-error: var(--text-on-error);
139
+ --color-text-on-success: var(--text-on-success);
140
+ --color-text-on-warning: var(--text-on-warning);
132
141
 
133
142
  /* ============================================================
134
143
  ANIMATIONS
@@ -143,27 +152,28 @@
143
152
  }
144
153
 
145
154
  /* ============================================================
146
- KEYFRAMES — outside @theme (v4 doesn't expose them there)
147
- oklch used throughout no rgba/hsl leakage
155
+ KEYFRAMES — outside @theme (v4 doesn't expose them there).
156
+ Colour-bearing frames derive from --role-accent, so they follow
157
+ per-app accent and tenant branding instead of pinning cyan.
148
158
  ============================================================ */
149
159
  @keyframes pulse-soft {
150
160
  0%, 100% { opacity: 1; }
151
- 50% { opacity: 0.7; }
161
+ 50% { opacity: 0.7; }
152
162
  }
153
163
 
154
164
  @keyframes glow {
155
- 0% { box-shadow: 0 0 5px oklch(0.769 0.132 191.7 / 0.2); }
156
- 100% { box-shadow: 0 0 20px oklch(0.769 0.132 191.7 / 0.6); }
165
+ 0% { box-shadow: 0 0 5px oklch(from var(--role-accent) l c h / 0.2); }
166
+ 100% { box-shadow: 0 0 20px oklch(from var(--role-accent) l c h / 0.6); }
157
167
  }
158
168
 
159
169
  @keyframes neon-pulse {
160
- 0%, 100% { text-shadow: 0 0 10px oklch(0.769 0.132 191.7 / 0.3); }
161
- 50% { text-shadow: 0 0 20px oklch(0.769 0.132 191.7 / 0.8); }
170
+ 0%, 100% { text-shadow: 0 0 10px oklch(from var(--role-accent) l c h / 0.3); }
171
+ 50% { text-shadow: 0 0 20px oklch(from var(--role-accent) l c h / 0.8); }
162
172
  }
163
173
 
164
174
  @keyframes float {
165
175
  0%, 100% { transform: translateY(0px); }
166
- 50% { transform: translateY(-10px); }
176
+ 50% { transform: translateY(-10px); }
167
177
  }
168
178
 
169
179
  @keyframes shimmer {
@@ -173,7 +183,7 @@
173
183
 
174
184
  @keyframes typing {
175
185
  0%, 60% { opacity: 1; }
176
- 30% { opacity: 0.4; }
186
+ 30% { opacity: 0.4; }
177
187
  }
178
188
 
179
189
  @keyframes shine {
@@ -1,150 +0,0 @@
1
- /**
2
- * @bsuite/theme — Tailwind v3 preset (oklch edition)
3
- *
4
- * Consume via:
5
- * // tailwind.config.js (CommonJS)
6
- * module.exports = {
7
- * presets: [require('@bsuite/theme/tailwind-preset')],
8
- * content: ['./src/**\/*.{js,ts,jsx,tsx}'],
9
- * }
10
- *
11
- * Extends Tailwind's theme with the D2C Neon Electric palette, light/dark
12
- * surface tokens, semantic status colours, gradients, glow shadows, and
13
- * the six canonical animations. Does not override Tailwind's default scale.
14
- *
15
- * Colour values are inline oklch so the preset is self-contained — no
16
- * dependency on any `--*-rgb` CSS custom property. Tailwind v3 injects
17
- * `<alpha-value>` into any valid CSS colour function, and `oklch(L C H / A)`
18
- * is valid CSS Color 4 syntax (supported in all evergreen browsers:
19
- * Chrome ≥111, Safari ≥15.4, Firefox ≥113). Keeps bg-neon-electric-blue/40
20
- * and friends working without the RGB-triplet variable indirection.
21
- *
22
- * Values are kept in sync with `preset-v4.css` — editing one without the
23
- * other will cause v3 and v4 consumers to diverge. See README.
24
- *
25
- * NOTE: Tailwind v4 apps should import `@bsuite/theme/preset-v4.css` instead.
26
- * v4 removed the preset system — presets exist only for v3.
27
- */
28
-
29
- /** @type {import('tailwindcss').Config} */
30
- module.exports = {
31
- darkMode: ['class'],
32
- theme: {
33
- extend: {
34
- colors: {
35
- neon: {
36
- 'electric-blue': 'oklch(0.546 0.215 262.9 / <alpha-value>)',
37
- 'electric-cyan': 'oklch(0.769 0.132 191.7 / <alpha-value>)',
38
- 'electric-indigo': 'oklch(0.511 0.23 277 / <alpha-value>)',
39
- 'electric-purple': 'oklch(0.568 0.202 283.1 / <alpha-value>)',
40
- 'electric-magenta': 'oklch(0.742 0.167 359.5 / <alpha-value>)',
41
- 'electric-pink': 'oklch(0.656 0.212 354.3 / <alpha-value>)',
42
- 'electric-coral': 'oklch(0.669 0.219 20.9 / <alpha-value>)',
43
- 'electric-orange': 'oklch(0.728 0.168 22.5 / <alpha-value>)',
44
- 'electric-yellow': 'oklch(0.868 0.125 81.4 / <alpha-value>)',
45
- 'electric-green': 'oklch(0.726 0.197 145.5 / <alpha-value>)',
46
- 'electric-lavender': 'oklch(0.749 0.115 288.4 / <alpha-value>)',
47
- },
48
- light: {
49
- bg: {
50
- primary: 'var(--light-bg-primary)',
51
- secondary: 'var(--light-bg-secondary)',
52
- tertiary: 'var(--light-bg-tertiary)',
53
- accent: 'var(--light-bg-accent)',
54
- },
55
- text: {
56
- primary: 'var(--light-text-primary)',
57
- secondary: 'var(--light-text-secondary)',
58
- tertiary: 'var(--light-text-tertiary)',
59
- quaternary: 'var(--light-text-quaternary)',
60
- },
61
- border: 'var(--light-border)',
62
- hover: 'var(--light-hover)',
63
- },
64
- dark: {
65
- bg: {
66
- primary: 'var(--dark-bg-primary)',
67
- secondary: 'var(--dark-bg-secondary)',
68
- tertiary: 'var(--dark-bg-tertiary)',
69
- quaternary: 'var(--dark-bg-quaternary)',
70
- accent: 'var(--dark-bg-accent)',
71
- },
72
- text: {
73
- primary: 'var(--dark-text-primary)',
74
- secondary: 'var(--dark-text-secondary)',
75
- tertiary: 'var(--dark-text-tertiary)',
76
- quaternary: 'var(--dark-text-quaternary)',
77
- },
78
- border: 'var(--dark-border)',
79
- hover: 'var(--dark-hover)',
80
- },
81
- status: {
82
- success: 'var(--status-success)',
83
- warning: 'var(--status-warning)',
84
- error: 'var(--status-error)',
85
- info: 'var(--status-info)',
86
- },
87
- },
88
-
89
- backgroundImage: {
90
- 'gradient-brand':
91
- 'linear-gradient(135deg, oklch(0.669 0.219 20.9) 0%, oklch(0.728 0.168 22.5) 25%, oklch(0.868 0.125 81.4) 50%, oklch(0.769 0.132 191.7) 75%, oklch(0.749 0.115 288.4) 100%)',
92
- 'gradient-electric':
93
- 'linear-gradient(135deg, oklch(0.546 0.215 262.9) 0%, oklch(0.769 0.132 191.7) 50%, oklch(0.656 0.212 354.3) 100%)',
94
- 'gradient-neon':
95
- 'linear-gradient(90deg, oklch(0.769 0.132 191.7) 0%, oklch(0.568 0.202 283.1) 50%, oklch(0.669 0.219 20.9) 100%)',
96
- 'gradient-chat-user':
97
- 'linear-gradient(135deg, oklch(0.636 0.153 271.3) 0%, oklch(0.466 0.168 296.3) 100%)',
98
- 'gradient-chat-agent':
99
- 'linear-gradient(135deg, oklch(0.546 0.215 262.9) 0%, oklch(0.656 0.212 354.3) 100%)',
100
- 'gradient-neural':
101
- 'radial-gradient(circle at center, oklch(0.769 0.132 191.7 / 0.1) 0%, transparent 50%)',
102
- },
103
-
104
- boxShadow: {
105
- 'glow-electric-blue': '0 0 20px oklch(0.546 0.215 262.9 / var(--glow-strength))',
106
- 'glow-electric-cyan': '0 0 20px oklch(0.769 0.132 191.7 / var(--glow-strength))',
107
- 'glow-electric-purple': '0 0 20px oklch(0.568 0.202 283.1 / var(--glow-strength))',
108
- 'glow-electric-pink': '0 0 20px oklch(0.656 0.212 354.3 / var(--glow-strength))',
109
- 'glow-electric-coral': '0 0 20px oklch(0.669 0.219 20.9 / var(--glow-strength))',
110
- 'glow-electric-magenta': '0 0 20px oklch(0.742 0.167 359.5 / var(--glow-strength))',
111
- },
112
-
113
- animation: {
114
- 'pulse-soft': 'pulse-soft 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
115
- glow: 'glow 2s ease-in-out infinite alternate',
116
- typing: 'typing 1.5s infinite',
117
- shimmer: 'shimmer 2s infinite',
118
- float: 'float 3s ease-in-out infinite',
119
- 'neon-pulse': 'neon-pulse 2s ease-in-out infinite',
120
- },
121
-
122
- keyframes: {
123
- 'pulse-soft': {
124
- '0%, 100%': { opacity: '1' },
125
- '50%': { opacity: '0.7' },
126
- },
127
- glow: {
128
- '0%': { boxShadow: '0 0 5px oklch(0.769 0.132 191.7 / 0.2)' },
129
- '100%': { boxShadow: '0 0 20px oklch(0.769 0.132 191.7 / 0.6)' },
130
- },
131
- typing: {
132
- '0%, 60%': { opacity: '1' },
133
- '30%': { opacity: '0.4' },
134
- },
135
- shimmer: {
136
- '0%': { backgroundPosition: '-1000px 0' },
137
- '100%': { backgroundPosition: '1000px 0' },
138
- },
139
- float: {
140
- '0%, 100%': { transform: 'translateY(0px)' },
141
- '50%': { transform: 'translateY(-10px)' },
142
- },
143
- 'neon-pulse': {
144
- '0%, 100%': { textShadow: '0 0 10px oklch(0.769 0.132 191.7 / 0.3)' },
145
- '50%': { textShadow: '0 0 20px oklch(0.769 0.132 191.7 / 0.8)' },
146
- },
147
- },
148
- },
149
- },
150
- }