diff --git a/.changeset/color-roles-component-behavior.md b/.changeset/color-roles-component-behavior.md new file mode 100644 index 0000000000..2742edd04b --- /dev/null +++ b/.changeset/color-roles-component-behavior.md @@ -0,0 +1,9 @@ +--- +'@skillsoft/gamut': minor +--- + +Component behavior from design's component map that the role swap didn't cover. + +- A selected DataList or DataTable row takes the `state-selected` veil over its fill, with the hover veil on top when hovered. `ListRow` takes an `isSelected` prop for the same tint. +- Table rows are `surface-raised`, with `surface-sunken` stripes, instead of transparent. +- ProgressBar's label at `large` and `xl` sizes is two-tone: `text-inverse` on the bar and `text-default` where it runs onto the track, so a short bar's label stays readable. diff --git a/.changeset/color-roles-components.md b/.changeset/color-roles-components.md new file mode 100644 index 0000000000..217c5eb732 --- /dev/null +++ b/.changeset/color-roles-components.md @@ -0,0 +1,18 @@ +--- +'@skillsoft/gamut': minor +--- + +Components use the color system's roles, following design's component map. + +- **Breaking:** every color name a component uses is a role (`surface-raised`, `text-subtle`, `action-primary`, `state-hover`, …). Code that passes old names (`background`, `primary`, `navy`, `text-disabled`) to gamut props has to move; the `color-roles` codemod in `@skillsoft/gamut-codemods` does most of it. +- **Breaking:** old `coreTheme` doesn't define the roles, so components lose their colors on it. Use `originalCodecademyTheme`, its rebuild, or `skillsoftTheme`. Admin, platform and lxStudio are rebuilt as brands and keep their names. +- Buttons map onto the action families: `secondary` is `action-neutral`, `interface` merges into primary, labels on fills are `text-inverse`, every variant takes the `action-primary` focus ring, and StrokeButton and TextButton hover and press with veils. +- Fields are `surface-raised` with a `border-default` edge; field hover is `border-strong`, so blue only means focus. Floating surfaces (menus, popovers, tooltips, toasts, the SelectDropdown menu) are `surface-raised` with a `border-subtle` hairline and the theme's raised shadow. A checked Radio is `action-primary`. +- **Breaking:** Card's `variant` is `default` or `brand`. `white`, `beige` and `navy` become `default` (`surface-raised`); `yellow` and `hyper` become `brand`, the featured card on the brand gradient. Every card has a `border-subtle` hairline. An interactive card has the raised shadow at rest, and the `state-hover` veil with the raised-hover shadow on hover; its border no longer changes. A card that isn't interactive has no shadow. +- CTAButton's shadow comes from the theme's elevation: the soft raised shadow on most brands, old core's hard offset on Original Codecademy. +- Modal and Flyout's scrim is `shadow-scrim`, and Flyout's panel has a `border-subtle` inner edge. +- ProgressBar's blue and yellow bars are `text-default` on a sunken track. Block Tabs follow the page's mode instead of forcing dark. +- `Tag`: navigation and suggestion tags have no fill of their own, so they sit on the surface they're on. Disabled tags drop the `action-disabled` fill and take the disabled ink, keeping their edge. +- Every input-like control fills with `surface-raised`, an unchecked Checkbox included. Placeholders are `text-subtle`, an error is a 2px `text-error` outline at rest, and an edited field keeps its `border-default` outline. A disabled DatePicker field now looks disabled, and its error ring draws. FormGroup descriptions are `text-subtle`. +- The checker pattern behind Modal, Toast, PreviewTip and the DatePicker calendar is drawn only on Original Codecademy, in `border-subtle`. Themes from before the color system keep it. +- Smaller fixes from design's map: link hover is `action-primary-hover`, link focus rings are `action-primary`, the Breadcrumbs separator is `text-subtle`, List row hover is a veil over the row's fill, focus is a ring without a fill, and Shimmer is a `surface-sunken` base with a `surface-raised` sweep. diff --git a/.changeset/color-system-brands.md b/.changeset/color-system-brands.md new file mode 100644 index 0000000000..322500b181 --- /dev/null +++ b/.changeset/color-system-brands.md @@ -0,0 +1,16 @@ +--- +'@skillsoft/gamut-styles': minor +'@skillsoft/variance': minor +'@skillsoft/gamut': minor +--- + +Brands on the generated color system. + +- **Breaking:** `tokens.css` puts its rules in `@layer tokens`, with `reset, base, tokens, recipes, utilities` declared first. +- Adds `originalCodecademyTheme`, today's core colors, fonts and hard-offset elevation rebuilt on the generated system. `tokens.css` carries a palette block per brand under `[data-brand]`. +- A brand can override a palette step with an exact hex, or point a role at a different step (and, optionally, a different alpha) in one mode. Overrides go through the same contrast checks as generated values. +- `ColorMode` on generated themes sets `data-color-mode` and `data-brand` instead of injecting every role variable. +- Fixes `Background` painting the flipped mode's color for an alias `bg`, such as `bg="primary"`. +- `Alert`: General uses `surface-brand-subtle`. Gradient alerts size their gradient to the alert, not the viewport. Status alerts (error, success, notice) put their status ink on the message, and a floating Alert's border takes the same ink. The CTA follows the Alert's color mode instead of always rendering dark. +- Original Codecademy no longer pins navy and beige; their generated steps are used as is. Yellow stays exact. +- **Breaking:** the `backdrop` role is renamed `shadow-scrim`, and two shadow roles are added: `shadow-key` and `shadow-ambient`, the two layers every elevation shadow is drawn in. Brand themes' `elevation` now uses them, so shadows no longer render `undefined`: Skillsoft gets the soft raised shadow, and Original Codecademy keeps old core's hard offset with a transparent `shadow-ambient`. diff --git a/.changeset/color-system-legacy-brands.md b/.changeset/color-system-legacy-brands.md new file mode 100644 index 0000000000..483c8595d7 --- /dev/null +++ b/.changeset/color-system-legacy-brands.md @@ -0,0 +1,11 @@ +--- +'@skillsoft/gamut-styles': minor +--- + +Rebuild `adminTheme`, `platformTheme` and `lxStudioTheme` as brands of the generated color system, so they define every color role. + +- Each brand takes its colors from what the old theme rendered in each job, including what it inherited from core, such as lxStudio's yellow accent. Admin and platform inherit Original Codecademy's pins (yellow exact; navy and beige not pinned). lxStudio pins only its near-white page; its yellow is generated, and its dark mode keeps the sapphire primary instead of core's yellow one. +- Fonts, weights and lxStudio's radii are unchanged. Elevation is the soft shadow drawn in the shadow roles; only Original Codecademy keeps the hard offset. `platformTheme` still starts in dark mode and keeps its `editor-*` code editor colors with their old values. +- The two-color brand gradient pairs primary with accent-alt for every brand with a yellow accent. +- **Visual change:** the rebuilt themes aren't pixel-identical to the old ones; role grades come from design's system. Old color names that only these themes had are gone. +- **Breaking:** `percipioTheme` and the `PercipioTheme` type are removed. `skillsoftTheme`, the new default, replaces it: it keeps Percipio's fonts, font weights and resting shadow, with colors generated from the Skillsoft brand's hues. Use `skillsoftTheme` wherever an app used `percipioTheme`. diff --git a/.changeset/color-system-tokens.md b/.changeset/color-system-tokens.md new file mode 100644 index 0000000000..db7ad860d4 --- /dev/null +++ b/.changeset/color-system-tokens.md @@ -0,0 +1,12 @@ +--- +'@skillsoft/gamut-styles': minor +'@skillsoft/gamut': minor +--- + +Add the generated OKLCH color system. + +- `@skillsoft/gamut-styles/tokens`, `/tokens.css` and `/tokens/*.tokens.json` export an OKLCH palette and 43 semantic roles in light and dark, as static CSS, DTCG JSON and TS constants. The `tokens` entry has no React or Emotion dependency. +- `skillsoftTheme` is the new default theme. Its color values come from `tokens.css`, which `GamutProvider` injects; pass `useTokens={false}` if the page loads `@skillsoft/gamut-styles/tokens.css` itself. The static file also works without React or Emotion. +- `Background` picks the content mode from a build-time table on generated themes, instead of measuring contrast at runtime. +- The core, admin, platform, lxStudio and percipio themes are removed from this release. Brand themes built on the generator (originalCodecademy and the others) return in the PRs that follow. +- `Alert` follows design's mapping: status tints, an icon color per variant, and the brand gradient on Feature. diff --git a/.changeset/color-system-white-label.md b/.changeset/color-system-white-label.md new file mode 100644 index 0000000000..cd666d8c10 --- /dev/null +++ b/.changeset/color-system-white-label.md @@ -0,0 +1,14 @@ +--- +'@skillsoft/gamut-brand': minor +'@skillsoft/gamut-styles': minor +--- + +Add `@skillsoft/gamut-brand`, for teams building their own brand on gamut, and give every brand its own `tokens.css`. + +- `@skillsoft/gamut-brand` is new, and build-time only: install it as a dev dependency. `defineBrand({ name, hues })` types a brand, and `gamut-brand build ` writes its `values.ts`, `tokens.css` and DTCG JSON, refusing a brand that fails a contrast check. `--watch` regenerates when the brand file changes; `--check` fails CI when the files are out of date. The generator, the role table, the checks and gamut's own brands moved here from `@skillsoft/gamut-styles`. +- `createBrandTheme`, `BrandBase` and `BrandValues` are exported from `@skillsoft/gamut-styles`. `createBrandTheme(values, base)` turns a generated `values.ts` into a theme with the same type as gamut's own, so system props and styling functions stay typed against it. +- Each theme now carries only its own brand's colors. `GamutProvider` injects that brand's CSS, which puts its palette and its initial mode on `:root`, so a page starts in the right brand and mode without attributes on ``. A brand's role overrides apply under a bare `data-color-mode` too. +- **Breaking:** the generated files moved to one folder per brand. Import `@skillsoft/gamut-styles/tokens/.css` for a brand's CSS (`tokens.css` is still the default brand) and `@skillsoft/gamut-styles/tokens//{palette,light,dark}.tokens.json` for its DTCG files. `tokens/brands/*` is gone, and `brands` in `@skillsoft/gamut-styles/tokens` now includes the default brand. +- **Breaking:** `initialMode` moved from `createBrandTheme`'s second argument to the brand (`defineBrand({ initialMode: 'dark' })`), because the brand's CSS has to know it. `BrandBase` now requires gamut's font family and font weight keys. +- `platformTheme` now starts in dark mode in its CSS as well as in its theme, so a server-rendered page starts dark. +- The DTCG mode files include each brand's role overrides. They had the default roles for every brand. diff --git a/.changeset/config.json b/.changeset/config.json index 60a8f22d4f..4a1a913504 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -8,6 +8,7 @@ "fixed": [ [ "@skillsoft/gamut-agent-tools", + "@skillsoft/gamut-brand", "@skillsoft/gamut-codemods", "@skillsoft/gamut-icons", "@skillsoft/gamut-illustrations", diff --git a/.changeset/gamut-codemods-color-roles.md b/.changeset/gamut-codemods-color-roles.md new file mode 100644 index 0000000000..2e35d7bc11 --- /dev/null +++ b/.changeset/gamut-codemods-color-roles.md @@ -0,0 +1,13 @@ +--- +'@skillsoft/gamut-codemods': minor +--- + +Add a `color-roles` preset that moves Gamut color names onto the generated color system's roles, using design's migration map. + +- Renames and merges are applied. Names that split, were retired or are still pending stay in place, with a `/* color-roles: … */` comment carrying design's rule, and keep failing the type check until someone picks. +- Old palette names (`navy`, `gray-200`) and color names built at runtime get a warning. +- Old `--color-*` CSS variables are renamed to `--color-` in `.css`, `.scss` and `.less` files and inside JS strings and template literals; splits get the same comment. The type check can't see stylesheets, so the leftovers report lists every one left. Indented-syntax `.sass` isn't rewritten. +- Card's `variant` moves onto design's two: `white`, `beige` and `navy` become `default`, `yellow` and `hyper` become `brand`. A `navy` card gets a comment, since it now renders light. +- Old theme names move to the brand themes that replace them: `percipioTheme` → `skillsoftTheme`, `coreTheme` → `originalCodecademyTheme`, and the `PercipioTheme` type → `SkillsoftTheme`. An aliased import (`percipioTheme as theme`) only changes the imported name. A `CoreTheme` type gets a comment pointing at `SkillsoftTheme`, which types every brand. +- Follows design's map: `shadow-primary` and `shadow-secondary` split onto `shadow-key` and `shadow-ambient`. +- Presets can bring their own manifest type, pre-filter and leftovers report. `scope-swap` is unchanged. diff --git a/.prettierignore b/.prettierignore index 17524c399a..432d03aead 100644 --- a/.prettierignore +++ b/.prettierignore @@ -12,3 +12,4 @@ packages/gamut-codemods/**/__testfixtures__ /.nx/cache /.nx/workspace-data +packages/gamut-styles/src/tokens/generated diff --git a/packages/.storybook/components/Elements/DocsContainer.tsx b/packages/.storybook/components/Elements/DocsContainer.tsx index 350c5874c3..59d2c25e13 100644 --- a/packages/.storybook/components/Elements/DocsContainer.tsx +++ b/packages/.storybook/components/Elements/DocsContainer.tsx @@ -1,14 +1,15 @@ import { AssetProvider, - coreTheme, + skillsoftTheme, createEmotionCache, GamutProvider, platformPalette, adminTheme, lxStudioTheme, - percipioTheme, CoreTheme, platformTheme, + skillsoftTheme, + originalCodecademyTheme, ColorMode, } from '@skillsoft/gamut-styles'; import { @@ -21,24 +22,24 @@ import { useMemo } from 'react'; import { HelmetProvider } from 'react-helmet-async'; import { createGamutDocsTheme } from '../../theming/GamutTheme'; import { createTheme } from '@skillsoft/variance'; -export const storybookTheme = createTheme(coreTheme) +export const storybookTheme = createTheme(skillsoftTheme) .addColors(platformPalette) .build(); const themeMap = { - core: coreTheme, + core: skillsoftTheme, admin: adminTheme, lxStudio: lxStudioTheme, - percipio: percipioTheme, platform: platformTheme, + skillsoft: skillsoftTheme, + originalCodecademy: originalCodecademyTheme, } as const; const themeSpecificStories = { 'foundations-theme-lx-studio-theme--docs': 'lxStudio', - 'foundations-theme-percipio-theme--docs': 'percipio', 'foundations-theme-platform-theme--docs': 'platform', 'foundations-theme-admin-theme--docs': 'admin', - 'foundations-theme-core-theme--docs': 'core', + 'foundations-theme-core-theme--docs': 'originalCodecademy', }; export const DocsContainer: React.FC<{ context: DocsContextProps; @@ -51,7 +52,7 @@ export const DocsContainer: React.FC<{ ?.storyId; const globalTheme = - (context as any).store.userGlobals?.globals?.theme || 'core'; + (context as any).store.userGlobals?.globals?.theme || 'skillsoft'; const globalLogicalProps = (context as any).store.userGlobals?.globals ?.logicalProps; const useLogicalProperties = globalLogicalProps !== 'false'; diff --git a/packages/.storybook/main.ts b/packages/.storybook/main.ts index fba9e548fa..5b26642ce1 100644 --- a/packages/.storybook/main.ts +++ b/packages/.storybook/main.ts @@ -209,6 +209,26 @@ const config: StorybookConfig = { '../gamut-styles/src/variance/config' ), }, + // Public subpaths of gamut-styles (see its `exports`), pointed at + // source like the bare specifier above. + { + find: /^@skillsoft\/gamut-styles\/tokens$/, + replacement: resolve(__dirname, '../gamut-styles/src/tokens'), + }, + { + find: /^@skillsoft\/gamut-styles\/tokens\.css(\?.*)?$/, + replacement: `${resolve( + __dirname, + '../gamut-styles/src/tokens/generated/skillsoft/tokens.css' + )}$1`, + }, + { + find: /^@skillsoft\/gamut-styles\/tokens\/(\w+)\.css(\?.*)?$/, + replacement: `${resolve( + __dirname, + '../gamut-styles/src/tokens/generated' + )}/$1/tokens.css$2`, + }, ...normalizeAlias(config.resolve?.alias), ], }; diff --git a/packages/.storybook/preview.ts b/packages/.storybook/preview.ts index 21c6332ce7..9224a42742 100644 --- a/packages/.storybook/preview.ts +++ b/packages/.storybook/preview.ts @@ -94,14 +94,14 @@ export const globalTypes = { theme: { name: 'Theme', description: 'Global theme for components', - defaultValue: 'core', + defaultValue: 'skillsoft', toolbar: { icon: 'paintbrush', items: [ - { value: 'core', title: 'Core' }, + { value: 'skillsoft', title: 'Skillsoft' }, + { value: 'originalCodecademy', title: 'Original Codecademy' }, { value: 'admin', title: 'Admin' }, { value: 'lxStudio', title: 'LX Studio' }, - { value: 'percipio', title: 'Percipio' }, { value: 'platform', title: 'Learning Platform' }, ], showName: true, diff --git a/packages/.storybook/systemProps.ts b/packages/.storybook/systemProps.ts index e5711e120e..c45d1e0b8e 100644 --- a/packages/.storybook/systemProps.ts +++ b/packages/.storybook/systemProps.ts @@ -1,4 +1,4 @@ -import { coreTheme, system } from '@skillsoft/gamut-styles'; +import { skillsoftTheme, system } from '@skillsoft/gamut-styles'; import type { ArgTypesEnhancer, StrictInputType, @@ -51,12 +51,12 @@ const categoryRank = (category: string | undefined) => export const isSystemPropCategory = (category: string | undefined) => categoryRank(category) >= 0; -const themeScales = coreTheme as unknown as Record< +const themeScales = skillsoftTheme as unknown as Record< string, Record | undefined >; -// Token names come from the Core theme; other themes may define more or fewer. +// Token names come from the skillsoft theme. const getScaleTokens = ({ scale }: SystemPropConfig) => typeof scale === 'string' ? Object.keys(themeScales[scale] ?? {}) : []; diff --git a/packages/.storybook/theming/GamutThemeProvider.tsx b/packages/.storybook/theming/GamutThemeProvider.tsx index afb425806e..7a9e581457 100644 --- a/packages/.storybook/theming/GamutThemeProvider.tsx +++ b/packages/.storybook/theming/GamutThemeProvider.tsx @@ -4,11 +4,12 @@ import { corePalette, ColorModes, GamutProvider, - coreTheme, + skillsoftTheme, adminTheme, lxStudioTheme, - percipioTheme, platformTheme, + skillsoftTheme, + originalCodecademyTheme, } from '@skillsoft/gamut-styles'; import { Theme } from '@emotion/react'; @@ -25,11 +26,12 @@ const themeBackground: Record = { }; const themeMap = { - core: coreTheme, + core: skillsoftTheme, admin: adminTheme, lxStudio: lxStudioTheme, - percipio: percipioTheme, platform: platformTheme, + skillsoft: skillsoftTheme, + originalCodecademy: originalCodecademyTheme, } as const; type GlobalsContext = { @@ -46,9 +48,27 @@ export const withEmotion = (Story: any, context: GlobalsContext) => { const selectedTheme = context.globals.theme; const useLogicalProperties = context.globals.logicalProps === 'true'; const direction = context.globals.direction ?? 'ltr'; - const background = corePalette[themeBackground[colorMode]]; - const storyRef = useRef(null); const currentTheme = themeMap[selectedTheme]; + /* + * Generated themes (they carry contentModes) take their values from their + * brand's tokens.css, not Emotion. GamutProvider injects it; the test + * provider below runs without globals, so it injects the theme's own copy. + * Only for those: tokens.css also sets white, black and background-current + * on :root, which would leak into the other themes. They have none of + * core's primitives, so `navy` doesn't exist there; the story sits on their + * own surface-default. + */ + const generated = 'contentModes' in currentTheme ? currentTheme : undefined; + const tokens = generated && ; + const bg = ( + generated + ? generated.modes[colorMode]['surface-default'] + : themeBackground[colorMode] + ) as 'white'; + const background = generated + ? (generated._tokens.colors as Record)[bg] + : corePalette[themeBackground[colorMode]]; + const storyRef = useRef(null); useLayoutEffect(() => { const storyEl = storyRef.current?.closest( @@ -72,11 +92,8 @@ export const withEmotion = (Story: any, context: GlobalsContext) => { useLogicalProperties={useLogicalProperties} theme={currentTheme as unknown as Theme} > - + {tokens} + {Story()} @@ -89,11 +106,7 @@ export const withEmotion = (Story: any, context: GlobalsContext) => { useLogicalProperties={useLogicalProperties} theme={currentTheme as unknown as Theme} > - + {Story()} diff --git a/packages/gamut-agent-tools/skills/gamut-review/SKILL.md b/packages/gamut-agent-tools/skills/gamut-review/SKILL.md index 14210052dd..353ce56c65 100644 --- a/packages/gamut-agent-tools/skills/gamut-review/SKILL.md +++ b/packages/gamut-agent-tools/skills/gamut-review/SKILL.md @@ -19,9 +19,9 @@ Remediation skills: [`gamut-theming`](../gamut-theming/SKILL.md) · [`gamut-colo Resolve the audit root from the user's path if provided, otherwise the current working directory. Look for `DESIGN.md` at that root (not inside `node_modules` or package subfolders unless the audit path is explicitly that folder). -| Result | Action | -| ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Found | Report `✓ DESIGN.md present ()`. Proceed with Checks 1–5 using this file for product/theme context. | +| Result | Action | +| ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Found | Report `✓ DESIGN.md present ()`. Proceed with Checks 1–5 using this file for product/theme context. | | Missing | Report `✗ DESIGN.md not found` as a blocking finding. Include remediation: from the repo root run `gamut plugin install cursor --theme core` (or `percipio`, `lxstudio`, `admin`, `platform`; also `claude`), or manually copy the matching `DESIGN.*.md` from `@skillsoft/gamut` agent-tools and rename to `DESIGN.md`. Still run Checks 1–3 and 5. For Check 4, list hex violations with `palette:` / `semantic:` only where Appendix A/B apply without product YAML — prefix the Hardcoded colors section with `⚠ low confidence — no DESIGN.md` and do not assume Codecademy Core semantics; do not use Appendix B shortcuts as authoritative. | --- @@ -30,11 +30,11 @@ Resolve the audit root from the user's path if provided, otherwise the current w Read `package.json` at the audit root. Inspect `dependencies`, `devDependencies`, and `peerDependencies` combined. -| Package | Expectation | -| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `@skillsoft/gamut` | Required — core component library | -| `@skillsoft/gamut-styles` | Recommended — design tokens and theme primitives | -| `@skillsoft/variance` | Recommended — style-prop system used by Gamut internals | +| Package | Expectation | +| ------------------------- | ------------------------------------------------------- | +| `@skillsoft/gamut` | Required — core component library | +| `@skillsoft/gamut-styles` | Recommended — design tokens and theme primitives | +| `@skillsoft/variance` | Recommended — style-prop system used by Gamut internals | --- @@ -48,7 +48,7 @@ Search source files (`.ts`, `.tsx`, `.js`, `.jsx`) for these symbols. Skip `node | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GamutProvider` | Required — must appear at least once (app root wrapper) | | `ColorMode` | Recommended — enables semantic light/dark theming | -| `Background` | Recommended — semantic surface color via `@skillsoft/gamut-styles` | +| `Background` | Recommended — semantic surface color via `@skillsoft/gamut-styles` | | `declare module '@emotion/react'` | **Required if TypeScript** — `Theme` must be augmented with the active theme type (e.g. `CoreTheme`) so scale props type-check correctly; grep `.d.ts` and `.ts`/`.tsx` source files for this string. **Recommended if not TypeScript** — note that adopting TypeScript is recommended and that `theme.d.ts` will be needed when it is. | For each found symbol report the first file path where it appears. @@ -61,8 +61,8 @@ For each found symbol report the first file path where it appears. Grep source files for any of these patterns. Each match is an error. -| Pattern | Reason | -| ------------------------------- | ----------------------------------------------------------------------- | +| Pattern | Reason | +| ------------------------------ | ----------------------------------------------------------------------- | | `@skillsoft/gamut/dist/` | Deep dist import — bypasses public API and breaks on internal refactors | | `@skillsoft/gamut/src/` | Deep src import — not part of the published package | | `@skillsoft/gamut-styles/src/` | Deep src import — use the package root | @@ -414,13 +414,13 @@ Reporting: count unique non-Gamut variable names and list the top offenders with Grep test files (`**/__tests__/**/*.{ts,tsx}`, `**/*.test.{ts,tsx}`, `**/*.spec.{ts,tsx}`) for these patterns. Skip `node_modules`, `dist`. -| Pattern | Verdict | Reason | -| ----------------------------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `jest.mock\(.*@skillsoft/gamut` | Error | Manual mocking bypasses theme context and produces false-positive tests; prefer `setupRtl` from `@skillsoft/gamut-tests` (or a harness + `setupRtl`); use raw `MockGamutProvider` + `render` only for rare one-offs or Storybook mocks | -| `jest.mock\(.*@skillsoft/gamut-styles` | Error | Same issue as above — mocking gamut-styles breaks token resolution | -| `from '@skillsoft/gamut-tests'` | Good — report count of files using it | Correct import for `setupRtl` and `MockGamutProvider` | +| Pattern | Verdict | Reason | +| ----------------------------------------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `jest.mock\(.*@skillsoft/gamut` | Error | Manual mocking bypasses theme context and produces false-positive tests; prefer `setupRtl` from `@skillsoft/gamut-tests` (or a harness + `setupRtl`); use raw `MockGamutProvider` + `render` only for rare one-offs or Storybook mocks | +| `jest.mock\(.*@skillsoft/gamut-styles` | Error | Same issue as above — mocking gamut-styles breaks token resolution | +| `from '@skillsoft/gamut-tests'` | Good — report count of files using it | Correct import for `setupRtl` and `MockGamutProvider` | | `from 'component-test-setup'` (without gamut-tests) | Warning | Should import `setupRtl` from `@skillsoft/gamut-tests`, not directly from `component-test-setup` — the gamut-tests wrapper adds `MockGamutProvider` automatically | -| `new GamutProvider` or ` + + +``` + +`GamutProvider` injects the brand's CSS, so the app loads nothing else. A page without React loads `acme/tokens.css` instead. + +## Keep it current + +```sh +gamut-brand build src/theme/acme.brand.ts --watch # regenerates when the brand file, or a file next to it, changes +gamut-brand build src/theme/acme.brand.ts --check # writes nothing; exits 1 if acme/ is out of date +``` + +Run `--watch` next to your dev server; it only rewrites files whose contents changed, so the dev server reloads once per real change. Run `--check` in CI. `--out ` puts the files somewhere other than a folder named after the brand. + +## Types + +Every brand has the same palette keys and the same 45 roles, so a theme from `createBrandTheme` has the same type as gamut's own themes. System props, `css()`, `variant()` and `states()` stay typed against it. Declare the Emotion theme once, as for any gamut app: + +```ts +// emotion.d.ts +import '@emotion/react'; +import type { SkillsoftTheme } from '@skillsoft/gamut-styles'; + +declare module '@emotion/react' { + export interface Theme extends SkillsoftTheme {} +} +``` + +`` then compiles and `` doesn't. The brand file is typed too: `defineBrand` rejects a slot, role or palette rung that doesn't exist. + +## What a brand can set + +| Field | What it does | +| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Becomes `data-brand` and the output folder's name. Letters, digits and `_`, starting with a letter | +| `hues` | A hue in degrees or a color to take the hue from, per slot: `primary` (required), `neutral-light`, `neutral-dark`, `accent`, `accent-alt`, `danger`, `warning`, `success` | +| `initialMode` | `'light'` (default) or `'dark'`: the mode `tokens.css` puts on `:root` and the theme starts in | +| `overrides` | Exact colors the generator wouldn't produce: a pinned palette rung, a role pointed at another palette color in one mode, or a gradient moved to another slot. Still checked | + +Only a color's hue is kept. `#0A5CFF` means "this blue's hue"; every grade's lightness is fixed, so contrast between grades is the same for every brand. To keep an exact color, pin it with `overrides.palette`. + +Slots a brand leaves out are filled in: missing neutrals from the primary's hue, and missing status and accent slots from gamut's defaults. `createColorSystem(brand).brand.derived` says where each came from. + +To start from one of gamut's brands, spread it: + +```ts +import { defineBrand, skillsoftBrand } from '@skillsoft/gamut-brand'; + +export default defineBrand({ + ...skillsoftBrand, + name: 'acme', + hues: { ...skillsoftBrand.hues, primary: '#0A5CFF' }, +}); +``` + +## When a brand is refused + +The build checks every text role against 4.5:1 and every border and icon role against 3:1 on the surfaces each is meant for, checks hover and selection veils on what they produce over each surface, keeps the three status colors apart, and checks 204 text and icon pairings inside 25 real components. It runs every check again on its own `tokens.css` read back, so what ships is what was checked. + +A failure names the check, its worst value and where: + +``` +refusing to generate "acme"; failed checks: + text-inverse ≥ 4.5:1: 1.04 at light: action-primary + action-primary ≥ 3:1: 1.04 at light: surface-sunken +``` + +That brand pinned `primary-800` to a near-white, so the primary button lost its contrast. + +Pick another hue, or pin the color with an override, and build again. Color-blindness separation is reported, not enforced. + +## The API + +The CLI is a thin layer over these, for build scripts that generate several brands: + +| Export | What it does | +| ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| `defineBrand(brand)` | Types a brand and returns it | +| `generateBrand(brand)` | Checks the brand and returns its files as strings; throws `BrandCheckError` | +| `writeBrand(brand, dir)` | Writes the files that changed; returns their paths | +| `staleFiles(brand, dir)` | The files in `dir` that are missing or out of date | +| `createColorSystem(brand)`, `validate(system)` | The generator and its checks, for tooling that wants the numbers | +| `skillsoftBrand`, `originalCodecademyBrand`, `adminBrand`, `platformBrand`, `lxStudioBrand` | gamut's own brands | + +`@skillsoft/gamut-styles`' `build:tokens` script uses `writeBrand` for gamut's five brands. + +## How the generator works + +``` +brands/skillsoft.ts one hue per slot (or just `primary`) + → brand.ts fills missing slots, reports the white-label tier + → ramp.ts 11 grades per slot: fixed L, chroma as % of gamut + → roles.ts 45 roles, light + dark, named by slot not hue + → system.ts resolved system, overrides applied + → contentModes.ts which mode's content goes on each color + → validate.ts / probe.ts contrast guarantees, veil floors, status trio + → migration.ts design's old → new map and component slots, checked per brand + → emit.ts DTCG 2025.10 JSON + values.ts + → generate.ts Style Dictionary → tokens.css, read back and checked again +``` + +`src/__tests__/generator.test.ts` holds the rules: every grade's lightness, chroma inside sRGB, the separation guarantee at every hue, one role mapping for every brand, and every check passing for gamut's brands and for a primary at every 5° of hue. + +After changing the generator, regenerate gamut's brands and review the diff: + +```sh +npx nx test gamut-brand +yarn workspace @skillsoft/gamut-styles build:tokens # rewrites src/tokens/generated +yarn workspace @skillsoft/gamut-styles validate:tokens # full report for gamut's brands +``` + +## Naming + +- Primitives: `--color-palette--`, for example `--color-palette-primary-800`. Not for components; use roles. +- Translucent primitives: `--color-palette-primary-600-a6` (6% alpha), `--color-black-a60`. +- Roles: `--color-`, for example `--color-action-primary`. +- `white` and `black` keep their bare names (boundary tokens). + +The `--color-` prefix on primitives is there because variance's `addColors` always emits `--color-`. One name for both the Emotion and CSS routes beat the shorter `--palette-*`. + +## Known gaps + +- There's no categorical solver. `viz-1` to `viz-8` use a slot and grade order solved for Skillsoft's hues; another brand gets that order unsolved, and categorical separation isn't checked. +- The theme carries sRGB copies of the values, because `BarChart` runs polished on them and polished can't parse `oklch()`. +- `tokens.css` needs `oklch()` and `color-mix()`. The hex fallback for older browsers isn't built yet. diff --git a/packages/gamut-brand/babel.config.js b/packages/gamut-brand/babel.config.js new file mode 100644 index 0000000000..dd9f75de4d --- /dev/null +++ b/packages/gamut-brand/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + extends: '../../babel.defaults.js', +}; diff --git a/packages/gamut-brand/jest.config.ts b/packages/gamut-brand/jest.config.ts new file mode 100644 index 0000000000..f52ef5e370 --- /dev/null +++ b/packages/gamut-brand/jest.config.ts @@ -0,0 +1,19 @@ +/** @jest-config-loader ts-node */ +/* eslint-disable */ +import path from 'path'; +import base from '../../jest.config.base'; + +export default base('gamut-brand', { + testEnvironment: 'node', + transform: { + '^.+\\.ts$': [ + 'babel-jest', + { + configFile: require.resolve(path.join(__dirname, './babel.config.js')), + }, + ], + }, + setupFiles: [], + setupFilesAfterEnv: [], + moduleFileExtensions: ['ts', 'js', 'json'], +}); diff --git a/packages/gamut-brand/package.json b/packages/gamut-brand/package.json new file mode 100644 index 0000000000..89a2de16a7 --- /dev/null +++ b/packages/gamut-brand/package.json @@ -0,0 +1,45 @@ +{ + "name": "@skillsoft/gamut-brand", + "description": "Build a gamut color brand from a few hues: checked palette, roles, tokens.css and typed values for createBrandTheme", + "version": "0.0.1", + "author": "Skillsoft Engineering ", + "bin": "./dist/cli.mjs", + "dependencies": { + "culori": "^4.0.2", + "jiti": "2.4.2", + "style-dictionary": "5.7.0" + }, + "devDependencies": { + "@types/culori": "^4" + }, + "engines": { + "node": ">=20" + }, + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs" + } + }, + "files": [ + "dist" + ], + "keywords": [ + "design-tokens", + "gamut", + "theming", + "white-label" + ], + "license": "MIT", + "module": "./dist/index.mjs", + "publishConfig": { + "access": "public" + }, + "repository": "git@github.com:Codecademy/skillsoft-gamut.git", + "scripts": { + "build": "nx build gamut-brand" + }, + "sideEffects": false, + "types": "./dist/index.d.ts" +} diff --git a/packages/gamut-brand/project.json b/packages/gamut-brand/project.json new file mode 100644 index 0000000000..75612da5b1 --- /dev/null +++ b/packages/gamut-brand/project.json @@ -0,0 +1,58 @@ +{ + "name": "gamut-brand", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/gamut-brand/src", + "projectType": "library", + "tags": [], + "targets": { + "lint": { + "executor": "@nx/eslint:lint" + }, + "build": { + "outputs": ["{projectRoot}/dist"], + "executor": "nx:run-commands", + "options": { + "cwd": "packages/gamut-brand", + "commands": [ + "rm -rf ./dist", + "tsdown", + "tsc --emitDeclarationOnly --project tsconfig.lib.json", + "chmod +x ./dist/cli.mjs" + ], + "parallel": false + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/packages/gamut-brand"], + "options": { + "jestConfig": "packages/gamut-brand/jest.config.ts", + "passWithNoTests": false + } + }, + "clean": { + "executor": "nx:run-commands", + "options": { + "cwd": "{projectRoot}", + "command": "rm -rf ./dist" + } + }, + "verify-package": { + "executor": "nx:run-commands", + "dependsOn": ["build"], + "options": { + "cwd": "{projectRoot}", + "commands": ["publint ."], + "parallel": false + } + }, + "publish-build": { + "executor": "nx:run-commands", + "dependsOn": ["build"], + "options": { + "commands": [], + "parallel": false + } + } + } +} diff --git a/packages/gamut-brand/src/__tests__/cli.test.ts b/packages/gamut-brand/src/__tests__/cli.test.ts new file mode 100644 index 0000000000..7f1f1a14e2 --- /dev/null +++ b/packages/gamut-brand/src/__tests__/cli.test.ts @@ -0,0 +1,103 @@ +import { spawnSync } from 'node:child_process'; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; + +/* + The CLI end to end, from source, the way a team building its own brand + runs it. A subprocess because Style Dictionary is ESM-only and Jest can't + load it. +*/ +/* jiti's `bin`; its exports don't include the file itself. */ +const JITI = join( + dirname(require.resolve('jiti/package.json')), + 'lib/jiti-cli.mjs' +); +const CLI = join(__dirname, '../cli.ts'); +const INDEX = join(__dirname, '../index.ts'); + +const run = (...args: string[]) => { + const result = spawnSync(process.execPath, [JITI, CLI, ...args], { + encoding: 'utf8', + }); + return { + status: result.status, + output: `${result.stdout}${result.stderr}`, + }; +}; + +describe('gamut-brand build', () => { + let dir: string; + const brandFile = () => join(dir, 'acme.brand.ts'); + const writeBrand = (body: string) => + writeFileSync( + brandFile(), + `import { defineBrand } from ${JSON.stringify( + INDEX + )};\nexport default defineBrand(${body});\n` + ); + + beforeEach(() => { + dir = mkdtempSync(join(tmpdir(), 'gamut-brand-')); + }); + afterEach(() => rmSync(dir, { recursive: true, force: true })); + + it('writes one brand, checks it, and notices when its input changes', () => { + writeBrand(`{ name: 'acme', hues: { primary: '#0A5CFF' } }`); + + const built = run('build', brandFile()); + expect(built.output).toContain('wrote "acme"'); + expect(built.status).toBe(0); + + const out = join(dir, 'acme'); + const css = readFileSync(join(out, 'tokens.css'), 'utf8'); + /* This brand only, starting on :root in light mode. */ + expect(css).toContain(':root, [data-brand="acme"] {'); + expect(css).toContain(':root, [data-color-mode="light"] {'); + expect(css).toContain('[data-color-mode="dark"] {'); + expect(css).not.toMatch(/data-brand="(?!acme")/); + + const values = readFileSync(join(out, 'values.ts'), 'utf8'); + expect(values).toContain('"initialMode": "light"'); + expect(values).toContain( + `export const tokensCss = ${JSON.stringify(css)};` + ); + for (const file of ['palette', 'light', 'dark']) { + expect(() => + JSON.parse(readFileSync(join(out, `${file}.tokens.json`), 'utf8')) + ).not.toThrow(); + } + + expect(run('build', brandFile(), '--check').status).toBe(0); + expect(run('build', brandFile()).output).toContain('"acme" unchanged'); + + writeBrand( + `{ name: 'acme', hues: { primary: '#0A5CFF' }, initialMode: 'dark' }` + ); + const stale = run('build', brandFile(), '--check'); + expect(stale.status).toBe(1); + expect(stale.output).toContain('"acme" is out of date'); + + expect(run('build', brandFile()).status).toBe(0); + expect(readFileSync(join(out, 'tokens.css'), 'utf8')).toContain( + ':root, [data-color-mode="dark"] {' + ); + }, 60000); + + it('refuses a brand that fails a check, and says which', () => { + /* Pins primary-800 near white: text on it can't reach 4.5:1. */ + writeBrand( + `{ name: 'acme', hues: { primary: 262 }, overrides: { palette: { 'primary-800': '#E8EEFF' } } }` + ); + const result = run('build', brandFile(), '--out', join(dir, 'out')); + expect(result.status).toBe(1); + expect(result.output).toContain('refusing to generate "acme"'); + }, 60000); + + it('refuses a name that would break CSS or an import', () => { + writeBrand(`{ name: 'acme corp', hues: { primary: 262 } }`); + const result = run('build', brandFile()); + expect(result.status).toBe(1); + expect(result.output).toContain('brand name "acme corp"'); + }, 60000); +}); diff --git a/packages/gamut-brand/src/__tests__/generator.test.ts b/packages/gamut-brand/src/__tests__/generator.test.ts new file mode 100644 index 0000000000..bdfcfe0e8f --- /dev/null +++ b/packages/gamut-brand/src/__tests__/generator.test.ts @@ -0,0 +1,134 @@ +import type { BrandInput } from '../brand'; +import { BRANDS, DEFAULT_BRAND } from '../brands'; +import { validateMigration } from '../migration'; +import type { Grade } from '../ramp'; +import { + CHROMA_CURVE, + contrast, + FIXED_LIGHTNESS, + generateRamp, + GRADES, + maxChroma, + NEUTRAL_CHROMA_MULTIPLIER, + oklchToHex, + SEPARATION_GUARANTEE, +} from '../ramp'; +import { MODES, ROLE_TOKENS } from '../roles'; +import { createColorSystem } from '../system'; +import { validate } from '../validate'; + +/* + What the generator promises for any brand, not just ours. The values gamut's + own brands generate are checked in under gamut-styles' src/tokens/generated, + and `build:tokens --check` fails if they drift, so a change to a value shows + up as a diff there. These tests hold the rules those values come from. +*/ + +/* Every 5°, enough to cross each sRGB gamut corner. */ +const HUES = Array.from({ length: 72 }, (_, i) => i * 5); + +const brands = BRANDS.map((b) => [b.name, b] as const); + +describe('ramps', () => { + it.each(HUES)('hue %i° keeps every rule of the ladder', (hue) => { + const misses = [false, true].flatMap((neutral) => { + const ramp = generateRamp(hue, neutral); + const at = `${neutral ? 'neutral' : 'chromatic'} ${hue}°`; + return GRADES.flatMap((grade) => { + const { l, c, h, hex } = ramp[grade]; + const ceiling = + maxChroma(l, h) * + CHROMA_CURVE[grade] * + (neutral ? NEUTRAL_CHROMA_MULTIPLIER : 1); + return [ + l !== FIXED_LIGHTNESS[grade] && `${at} ${grade}: L ${l}`, + h !== hue && `${at} ${grade}: H ${h}`, + c > ceiling && `${at} ${grade}: C ${c} over ${ceiling}`, + hex !== oklchToHex(l, c, h) && `${at} ${grade}: hex ${hex}`, + ].filter(Boolean); + }); + }); + expect(misses).toEqual([]); + }); + + it('gets darker at every grade', () => { + const ls = GRADES.map((g) => FIXED_LIGHTNESS[g]); + expect(ls).toEqual([...ls].sort((a, b) => b - a)); + expect(new Set(ls).size).toBe(ls.length); + }); + + it.each(HUES)('hue %i° keeps the separation guarantee', (hue) => { + const ramp = generateRamp(hue, false); + const misses = Object.entries(SEPARATION_GUARANTEE).flatMap( + ([kind, { minGap, floor }]) => + GRADES.flatMap((a) => + GRADES.filter((b) => b - a >= minGap).flatMap((b: Grade) => { + const ratio = contrast(ramp[a], ramp[b]); + return ratio < floor + ? [`${kind}: ${a} on ${b} is ${ratio.toFixed(2)}`] + : []; + }) + ) + ); + expect(misses).toEqual([]); + }); +}); + +describe('roles', () => { + it.each(brands)( + '%s resolves every role to a palette color in both modes', + (_, brand) => { + const system = createColorSystem(brand); + const misses = MODES.flatMap((mode) => + ROLE_TOKENS.flatMap((token) => { + const value = system.roles[mode][token]; + if (!value) return [`${mode}: ${token} missing`]; + const base = value.key.replace(/-a\d+$/, ''); + return system.palette[base] ? [] : [`${mode}: ${token} → ${base}`]; + }) + ); + expect(misses).toEqual([]); + } + ); + + /* One role mapping for every brand; only a brand's own role overrides differ. */ + it.each(brands)( + '%s maps roles like the default brand, except where it overrides them', + (_, brand) => { + const base = createColorSystem(DEFAULT_BRAND); + const system = createColorSystem(brand); + const differ = MODES.flatMap((mode) => + ROLE_TOKENS.filter( + (t) => system.roles[mode][t].key !== base.roles[mode][t].key + ).map((t) => `${mode}: ${t}`) + ); + const overridden = MODES.flatMap((mode) => + Object.keys(brand.overrides?.roles?.[mode] ?? {}).map( + (t) => `${mode}: ${t}` + ) + ); + expect(differ.sort()).toEqual(overridden.sort()); + } + ); +}); + +describe('checks', () => { + const failing = (brand: BrandInput) => { + const system = createColorSystem(brand); + return [...validate(system), ...validateMigration(system)] + .filter((c) => !c.pass) + .map((c) => `${c.name}: ${c.worst.toFixed(2)} at ${c.at}`); + }; + + it.each(brands)('%s passes every check', (_, brand) => { + expect(failing(brand)).toEqual([]); + }); + + /* Contrast comes from the fixed lightness, so it can't depend on the hue. */ + it.each(HUES)( + 'a brand with only a primary at %i° passes every check', + (hue) => { + expect(failing({ name: 'sweep', hues: { primary: hue } })).toEqual([]); + } + ); +}); diff --git a/packages/gamut-brand/src/__tests__/system.test.ts b/packages/gamut-brand/src/__tests__/system.test.ts new file mode 100644 index 0000000000..565c8777c9 --- /dev/null +++ b/packages/gamut-brand/src/__tests__/system.test.ts @@ -0,0 +1,147 @@ +import { originalCodecademyBrand } from '../brands/originalCodecademy'; +import { skillsoftBrand } from '../brands/skillsoft'; +import { gradientCssVars, toDtcgMode, toDtcgPalette } from '../emit'; +import { validateMigration } from '../migration'; +import { ROLE_TOKENS, ROLES } from '../roles'; +import { createColorSystem } from '../system'; +import { validate } from '../validate'; + +/* + Generating a brand's files, tokens.css round-trip included, is covered end + to end in cli.test.ts. The full report for gamut's own brands is + `yarn workspace @skillsoft/gamut-styles validate:tokens`. +*/ +describe('color system', () => { + const system = createColorSystem(skillsoftBrand); + + it.each( + [...validate(system), ...validateMigration(system)].filter( + (c) => !c.reported + ) + )('$name', (check) => { + expect(check.worst).toBeGreaterThanOrEqual(check.floor); + }); + + /* RoleToken types brand overrides; it has to name exactly the real roles. */ + it('lists every role in ROLE_TOKENS, in order', () => { + expect(ROLES.map((r) => r.token)).toEqual([...ROLE_TOKENS]); + }); + + it('derives a tier 1 brand from one hue', () => { + const tenant = createColorSystem({ name: 'tenant', hues: { primary: 45 } }); + expect(tenant.brand.tier).toBe(1); + expect(tenant.brand.hues['neutral-dark']).toBe(53.3); + expect( + [...validate(tenant), ...validateMigration(tenant)].every((c) => c.pass) + ).toBe(true); + }); + + /* Only a hex's hue is kept; its distance is reported. */ + it('keeps the hue of a supplied hex and reports how far it lands', () => { + const tenant = createColorSystem({ + name: 'tenant', + hues: { primary: '#3A10E5' }, + }); + expect(tenant.brand.hues.primary).toBe(274.1); + expect(tenant.brand.derived.accent).toBe('held at the gamut default'); + + const { hex, nearest, deltaE } = tenant.brand.sources!.primary!; + expect(hex).toBe('#3a10e5'); + expect(tenant.palette[`palette-primary-${nearest}`].hex).not.toBe(hex); + expect(deltaE).toBeGreaterThan(0); + }); + + it('applies palette and role overrides, and still measures them', () => { + const ocd = createColorSystem(originalCodecademyBrand); + expect(ocd.palette['palette-accent-300'].hex).toBe('#ffd300'); + expect(ocd.brand.sources!.accent!.deltaE).toBe(0); + /* Navy and beige aren't pinned: their generated steps are used as is. */ + expect(ocd.palette['palette-neutral-dark-1100'].hex).not.toBe('#10162f'); + expect(ocd.brand.sources!['neutral-dark']!.deltaE).toBeGreaterThan(0); + expect(ocd.roles.dark['action-primary'].key).toBe('palette-accent-300'); + /* Light mode isn't overridden. */ + expect(ocd.roles.light['action-primary'].key).toBe('palette-primary-800'); + expect( + [...validate(ocd), ...validateMigration(ocd)].every((c) => c.pass) + ).toBe(true); + }); + + /* Design's shadow roles: key and ambient, per mode. */ + it('draws shadows in the dark neutral in light mode and black in dark', () => { + expect(system.roles.light['shadow-key'].key).toBe( + 'palette-neutral-dark-1100-a16' + ); + expect(system.roles.dark['shadow-key'].key).toBe('black-a32'); + expect(system.roles.light['shadow-ambient'].alpha).toBe(0.12); + expect(system.roles.dark['shadow-ambient'].alpha).toBe(0.24); + expect(system.roles.light['shadow-scrim'].key).toBe('black-a60'); + }); + + /* The Codecademy theme's hard offset has no soft layer. */ + it('lets a role override set alpha, and emits the value it introduces', () => { + const ocd = createColorSystem(originalCodecademyBrand); + expect(ocd.roles.light['shadow-ambient'].alpha).toBe(0); + expect(ocd.roles.dark['shadow-ambient'].key).toBe('black-a0'); + /* A bare ref keeps the role's alpha; only shadow-ambient changes. */ + expect(ocd.roles.light['shadow-key'].alpha).toBe(0.16); + expect( + (toDtcgPalette(ocd).palette as Record>)[ + 'neutral-dark' + ]['1100-a0'] + ).toBeDefined(); + expect(system.roles.light['shadow-ambient'].alpha).toBe(0.12); + }); + + /* Figma and other DTCG readers see the brand's overrides, not the defaults. */ + it("writes a brand's role overrides into its DTCG mode file", () => { + const ocd = createColorSystem(originalCodecademyBrand); + const dark = toDtcgMode(ocd, 'dark') as Record; + expect(dark['action-primary'].$value).toBe('{palette.accent.300}'); + expect( + (toDtcgMode(system, 'dark') as Record)[ + 'action-primary' + ].$value + ).not.toBe('{palette.accent.300}'); + }); + + it('moves a gradient to another slot for one brand only', () => { + const slots = (system: ReturnType) => + system.gradients.light['surface-brand'].circles.map((c) => + typeof c.ref === 'string' ? c.ref : c.ref.slot + ); + const ocd = createColorSystem(originalCodecademyBrand); + expect(slots(ocd)).toEqual(['accent-alt', 'primary']); + expect( + ocd.gradients.dark['surface-brand'].circles.map((c) => c.ref) + ).toEqual([ + { slot: 'accent-alt', grade: 900 }, + { slot: 'primary', grade: 900 }, + ]); + expect(slots(createColorSystem(skillsoftBrand))).toEqual([ + 'accent', + 'primary', + ]); + expect(gradientCssVars(ocd.gradients.light['surface-brand'])).toContain( + 'radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-300)' + ); + }); + + it('refuses an override it cannot place', () => { + const brand = (overrides: object) => () => + createColorSystem({ name: 'x', hues: { primary: 45 }, overrides }); + expect( + brand({ + roles: { dark: { 'action-primry': { slot: 'accent', grade: 300 } } }, + }) + ).toThrow('no role action-primry'); + expect(brand({ palette: { 'primary-150': '#000000' } })).toThrow( + 'no palette rung primary-150' + ); + expect( + brand({ gradients: { 'text-default': { accent: 'primary' } } }) + ).toThrow('no gradient role text-default'); + expect( + brand({ gradients: { 'surface-brand': { accent: 'acent' } } }) + ).toThrow('surface-brand moves accent to acent'); + }); +}); diff --git a/packages/gamut-brand/src/brand.ts b/packages/gamut-brand/src/brand.ts new file mode 100644 index 0000000000..98f2bc5412 --- /dev/null +++ b/packages/gamut-brand/src/brand.ts @@ -0,0 +1,187 @@ +import { converter, formatHex } from 'culori'; + +import type { Grade } from './ramp'; +import type { Mode, Ref, RoleToken } from './roles'; + +/* + * Brands, as data. + * + * Roles never name a hue ("blue-800"). They name a slot ("primary-800"), and a + * brand maps slots to hues. White-labelling a tenant means writing a new + * `BrandInput`, not a new theme. + * + * A brand only has to supply `primary`. Anything it leaves out is filled in by + * the derivation rules below, and the resolved brand reports which tier that + * puts it on. + */ + +export const SLOTS = [ + 'neutral-light', + 'neutral-dark', + 'primary', + 'accent', + 'accent-alt', + 'danger', + 'warning', + 'success', +] as const; + +export type Slot = (typeof SLOTS)[number]; + +export const NEUTRAL_SLOTS: readonly Slot[] = ['neutral-light', 'neutral-dark']; +export const STATUS_SLOTS: readonly Slot[] = ['success', 'warning', 'danger']; +export const ACCENT_SLOTS: readonly Slot[] = ['accent', 'accent-alt']; + +/* A hue in degrees, or a hex to take the OKLCH hue from. */ +export type HueInput = number | string; + +/* + * Manual overrides, for a brand that has to keep a color the generator + * wouldn't produce (Original Codecademy's yellow). Everywhere else only a + * color's hue is kept, so overrides are explicit and few. Each one is still + * measured: the build refuses a brand whose overrides break a guarantee. + * + * - `palette`: an exact hex in place of a generated rung, keyed `slot-grade`. + * Pick the grade whose fixed lightness is nearest, or the ladder bends. + * - `roles`: a role pointed at another palette color in one mode. A bare ref + * keeps the role's alpha; `{ ref, alpha }` sets it too (alpha 0 is + * transparent, for a shadow layer a brand doesn't draw). + * - `gradients`: a gradient role's circles moved from one slot to another, + * in both modes. Grades, sizes and positions stay design's. + */ +export type RoleOverride = Ref | { ref: Ref; alpha: number }; + +export interface BrandOverrides { + palette?: Partial>; + roles?: Partial>>>; + gradients?: Partial>>>; +} + +export interface BrandInput { + /* + * Becomes `data-brand` and the generated files' names. The build takes + * letters, digits and `_`, starting with a letter (`acme`, `acmeKids`). + */ + name: string; + hues: { primary: HueInput } & Partial>; + overrides?: BrandOverrides; + /* + * The mode a page starts in before any `ColorMode`: the mode the brand's + * tokens.css puts on `:root`, and the theme's initial mode. Light unless + * set. + */ + initialMode?: Mode; +} + +export type Tier = 1 | 2 | 3 | 4; + +/* + * How far a supplied hex lands from its family's nearest generated rung. Only + * the hue is kept, so the distance is reported rather than hidden. Filled in + * by createColorSystem, which has the ramps. + */ +export interface SourceDistance { + hex: string; + nearest: Grade; + deltaE: number; +} + +export interface ResolvedBrand { + name: string; + initialMode: Mode; + hues: Record; + /* Slots the brand didn't supply, and where their hue came from. */ + derived: Partial>; + tier: Tier; + /* Slots supplied as a hex, measured after overrides. */ + sources?: Partial>; + overrides?: BrandOverrides; +} + +/* + * The cool neutral sits a few degrees past the primary. Skillsoft's navy and + * Sapphire already measure this offset (270.7° vs 262.4°); nobody arranged it. + */ +export const COOL_NEUTRAL_OFFSET = 8.3; + +/* The warm neutral sits opposite the cool one. Skillsoft's pair is 177° apart. */ +export const WARM_NEUTRAL_OFFSET = 180; + +/* + * Status and accent hues a tenant doesn't supply stay on these. They aren't + * free parameters: red means error everywhere, and amber/teal were solved for + * status-trio separation under color-vision deficiency. + */ +export const DEFAULT_HUES: Record = { + 'neutral-light': 93.7, + 'neutral-dark': 270.7, + primary: 262.4, + accent: 357.3, + 'accent-alt': 287.1, + danger: 17.3, + warning: 88.0, + success: 200.0, +}; + +const toOklch = converter('oklch'); + +const wrap = (deg: number): number => + Number((((deg % 360) + 360) % 360).toFixed(1)); + +export const hueOf = (input: HueInput): number => { + if (typeof input === 'number') return wrap(input); + const color = toOklch(input); + if (color?.h === undefined) { + throw new Error(`"${input}" has no hue — supply a chromatic color`); + } + return wrap(color.h); +}; + +const tierOf = (supplied: Set): Tier => { + if (SLOTS.every((s) => supplied.has(s))) return 4; + if (STATUS_SLOTS.every((s) => supplied.has(s))) return 3; + if (NEUTRAL_SLOTS.every((s) => supplied.has(s))) return 2; + return 1; +}; + +export const resolveBrand = (input: BrandInput): ResolvedBrand => { + const supplied = new Set(Object.keys(input.hues) as Slot[]); + const hues = {} as Record; + const derived: ResolvedBrand['derived'] = {}; + + for (const slot of supplied) hues[slot] = hueOf(input.hues[slot]!); + + if (!supplied.has('neutral-dark')) { + hues['neutral-dark'] = supplied.has('neutral-light') + ? wrap(hues['neutral-light'] - WARM_NEUTRAL_OFFSET) + : wrap(hues.primary + COOL_NEUTRAL_OFFSET); + derived['neutral-dark'] = supplied.has('neutral-light') + ? 'opposite neutral-light' + : `primary + ${COOL_NEUTRAL_OFFSET}°`; + } + if (!supplied.has('neutral-light')) { + hues['neutral-light'] = wrap(hues['neutral-dark'] + WARM_NEUTRAL_OFFSET); + derived['neutral-light'] = 'opposite neutral-dark'; + } + for (const slot of [...STATUS_SLOTS, ...ACCENT_SLOTS]) { + if (supplied.has(slot)) continue; + hues[slot] = DEFAULT_HUES[slot]; + derived[slot] = 'held at the gamut default'; + } + + return { + name: input.name, + initialMode: input.initialMode ?? 'light', + hues, + derived, + tier: tierOf(supplied), + }; +}; + +/* The supplied hexes, normalized, by slot. Numbers carry no source color. */ +export const sourceHexes = (input: BrandInput): Partial> => + Object.fromEntries( + Object.entries(input.hues) + .filter(([, v]) => typeof v === 'string') + .map(([slot, v]) => [slot, formatHex(v as string)!]) + ); diff --git a/packages/gamut-brand/src/brands/admin.ts b/packages/gamut-brand/src/brands/admin.ts new file mode 100644 index 0000000000..1cbd1cd675 --- /dev/null +++ b/packages/gamut-brand/src/brands/admin.ts @@ -0,0 +1,23 @@ +import type { BrandInput } from '../brand'; +import { + coreFamilyOverrides, + originalCodecademyBrand, +} from './originalCodecademy'; + +/* + * Admin: old admin rebuilt on the generated system. Old admin was old core + * with a blue primary (blue-500, hover blue-400) in light mode, so this is + * Original Codecademy with primary's hue taken from blue-500. Dark mode keeps + * core's yellow primary, as old admin did. Old admin's one dark override + * (success on green-400) was already core's value. + */ +export const adminBrand: BrandInput = { + ...originalCodecademyBrand, + name: 'admin', + hues: { + ...originalCodecademyBrand.hues, + primary: '#1557FF', // blue-500, admin's primary + }, + /* The core family's pins; not the Codecademy theme's hard-offset shadow. */ + overrides: coreFamilyOverrides, +}; diff --git a/packages/gamut-brand/src/brands/index.ts b/packages/gamut-brand/src/brands/index.ts new file mode 100644 index 0000000000..a00f7470ca --- /dev/null +++ b/packages/gamut-brand/src/brands/index.ts @@ -0,0 +1,29 @@ +import { adminBrand } from './admin'; +import { lxStudioBrand } from './lxStudio'; +import { originalCodecademyBrand } from './originalCodecademy'; +import { platformBrand } from './platform'; +import { skillsoftBrand } from './skillsoft'; + +export { + adminBrand, + lxStudioBrand, + originalCodecademyBrand, + platformBrand, + skillsoftBrand, +}; +export { coreFamilyOverrides } from './originalCodecademy'; + +/* + * gamut's own brands, which `@skillsoft/gamut-styles` builds its themes from. + * Each one is generated on its own; the default is the theme gamut is typed + * against and the brand `GamutProvider` falls back to. + */ +export const DEFAULT_BRAND = skillsoftBrand; + +export const BRANDS = [ + skillsoftBrand, + originalCodecademyBrand, + adminBrand, + platformBrand, + lxStudioBrand, +]; diff --git a/packages/gamut-brand/src/brands/lxStudio.ts b/packages/gamut-brand/src/brands/lxStudio.ts new file mode 100644 index 0000000000..f06047a195 --- /dev/null +++ b/packages/gamut-brand/src/brands/lxStudio.ts @@ -0,0 +1,43 @@ +import type { BrandInput } from '../brand'; + +/* + * LX Studio: old lxStudio rebuilt on the generated system. Old lxStudio was + * old core with a sapphire primary, a near-white page (lxStudioBgPrimary) + * and its own success green in light mode. Everything else is core's: navy + * text, yellow accent, red danger and orange warning (as Original Codecademy + * reads it). Old lxStudio has no second accent, so `accent-alt` stays on the + * gamut default. + * + * The yellow accent is generated like any other slot, not pinned to core's + * grades, and dark mode keeps the generated sapphire primary rather than + * core's yellow one (2026-10-29). + * + * Navy isn't pinned, as for Original Codecademy: pinned, it carries much + * more chroma than the generated surfaces beside it (design, 2026-10-29). + * The near-white page is nearly achromatic, so it stays exact. + * + * Not carried over: old lxStudio's `interface` on hyper-500 (there's no + * interface role; interactive color is `action-primary`) and its primary + * hover on navy-800 (hover grades are design's). + */ +export const lxStudioBrand: BrandInput = { + name: 'lxStudio', + hues: { + 'neutral-light': '#FAFBFC', // lxStudioBgPrimary + 'neutral-dark': '#10162F', // core's navy-800, text + primary: '#1C50BB', // sapphire + accent: '#FFD300', // core's yellow-500 + danger: '#E91C11', // core's red-500 + warning: '#FF8C00', // core's orange-500, the notice Alert + success: '#06844F', // lxStudioSuccess + }, + overrides: { + palette: { + 'neutral-light-100': '#FAFBFC', // lxStudioBgPrimary + }, + /* As for Original Codecademy: primary + accent-alt, not blue to yellow. */ + gradients: { + 'surface-brand': { accent: 'accent-alt' }, + }, + }, +}; diff --git a/packages/gamut-brand/src/brands/originalCodecademy.ts b/packages/gamut-brand/src/brands/originalCodecademy.ts new file mode 100644 index 0000000000..c6f3b0d79c --- /dev/null +++ b/packages/gamut-brand/src/brands/originalCodecademy.ts @@ -0,0 +1,74 @@ +import type { BrandInput, BrandOverrides } from '../brand'; + +/* + * What every brand rebuilt from old core shares (Original Codecademy, admin, + * platform): the colors the generator wouldn't produce. Old core's yellow + * exactly, and its yellow dark-mode primary. Navy and beige aren't pinned: + * pinned, they carry much more chroma than the generated surfaces beside them + * (sunken, raised, zebra rows, block tabs), so the generated steps at their + * hues are used as is (design, 2026-10-29). The two-color gradient pairs + * primary with accent-alt instead of the yellow accent, so it reads blue to + * violet rather than blue to yellow (design, Stacey, 2026-10-28). + */ +export const coreFamilyOverrides: BrandOverrides = { + /* Each at the grade whose fixed lightness is nearest. */ + palette: { + 'accent-300': '#FFD300', // yellow-500 + 'accent-400': '#CCA900', // yellow-400 + }, + /* Old core's dark mode uses yellow for primary. */ + roles: { + dark: { + 'action-primary': { slot: 'accent', grade: 300 }, + 'action-primary-hover': { slot: 'accent', grade: 400 }, + 'action-primary-pressed': { slot: 'accent', grade: 500 }, + }, + }, + gradients: { + 'surface-brand': { accent: 'accent-alt' }, + }, +}; + +/* + * Original Codecademy: old core rebuilt on the generated system, as close to + * old core's own colors as the system allows. Each slot it supplies takes its + * hue from the color old core used in that job (variables/colors.ts). Yellow + * is old core's second brand color (Card, Badge accent, ProgressBar, dark-mode + * primary), so it fills `accent`, and warning moves to the orange of old + * core's notice Alert to keep the accent and warning chart colors apart. Old + * core has no second accent in use, so `accent-alt` stays on the gamut + * default, which makes this tier 3. + * + * On top of the core family's overrides: the Codecademy theme's shadows are a + * hard offset with no soft falloff, so `shadow-ambient` is transparent in both + * modes and the layer stays in every elevation, keeping the layer count the + * same at every depth (design's call). That's this theme only; + * admin and platform get the soft shadow. + */ +export const originalCodecademyBrand: BrandInput = { + name: 'originalCodecademy', + hues: { + 'neutral-light': '#FFF0E5', // beige, background-primary + 'neutral-dark': '#10162F', // navy-800, text + primary: '#3A10E5', // hyper-500, primary + accent: '#FFD300', // yellow-500, the second brand color + danger: '#E91C11', // red-500, danger + warning: '#FF8C00', // orange-500, the notice Alert + success: '#008A27', // green-700, feedback-success + }, + overrides: { + ...coreFamilyOverrides, + roles: { + light: { + 'shadow-ambient': { + ref: { slot: 'neutral-dark', grade: 1100 }, + alpha: 0, + }, + }, + dark: { + ...coreFamilyOverrides.roles?.dark, + 'shadow-ambient': { ref: 'black', alpha: 0 }, + }, + }, + }, +}; diff --git a/packages/gamut-brand/src/brands/platform.ts b/packages/gamut-brand/src/brands/platform.ts new file mode 100644 index 0000000000..06cdad2179 --- /dev/null +++ b/packages/gamut-brand/src/brands/platform.ts @@ -0,0 +1,32 @@ +import type { BrandInput } from '../brand'; +import { + coreFamilyOverrides, + originalCodecademyBrand, +} from './originalCodecademy'; + +/* + * Platform (the learning environment): old platform rebuilt on the generated + * system. Old platform was old core with a pale blue page background + * (background-primary on blue-0) in light mode, so this is Original + * Codecademy with `neutral-light` taken from blue-0. Blue-0 is nearly + * achromatic, so unlike Original Codecademy's beige it stays pinned. Its code editor colors aren't part of the color system; the + * theme carries them (themes/platform.ts). + */ +export const platformBrand: BrandInput = { + ...originalCodecademyBrand, + name: 'platform', + /* Old platform started in dark mode. */ + initialMode: 'dark', + hues: { + ...originalCodecademyBrand.hues, + 'neutral-light': '#F5FCFF', // blue-0, background-primary + }, + /* The core family's pins; not the Codecademy theme's hard-offset shadow. */ + overrides: { + ...coreFamilyOverrides, + palette: { + ...coreFamilyOverrides.palette, + 'neutral-light-100': '#F5FCFF', // blue-0 + }, + }, +}; diff --git a/packages/gamut-brand/src/brands/skillsoft.ts b/packages/gamut-brand/src/brands/skillsoft.ts new file mode 100644 index 0000000000..0cefc1509a --- /dev/null +++ b/packages/gamut-brand/src/brands/skillsoft.ts @@ -0,0 +1,20 @@ +import type { BrandInput } from '../brand'; + +/* + * Skillsoft is tier 4: every slot supplied. Hues are the OKLCH hues of the + * brand deck's colors; only the hue survives, so no brand hex appears verbatim + * in the ramps. + */ +export const skillsoftBrand: BrandInput = { + name: 'skillsoft', + hues: { + 'neutral-light': 93.7, // Paper #FFFBED + 'neutral-dark': 270.7, // Navy #151C36 + primary: 262.4, // Sapphire #1C50BB + accent: 357.3, // Mulberry #CD427F + 'accent-alt': 287.1, // Lilac #B5B2E7 + danger: 17.3, // Red #FF375B + warning: 88.0, // no brand source; solved for status-trio separation + success: 200.0, // no brand source; solved jointly with warning + }, +}; diff --git a/packages/gamut-brand/src/cli.ts b/packages/gamut-brand/src/cli.ts new file mode 100644 index 0000000000..75dd068afb --- /dev/null +++ b/packages/gamut-brand/src/cli.ts @@ -0,0 +1,171 @@ +#!/usr/bin/env node +/* eslint-disable no-console -- CLI; the console is its output. */ +import { watch } from 'node:fs'; +import { dirname, extname, relative, resolve } from 'node:path'; +import { parseArgs } from 'node:util'; + +import { createJiti } from 'jiti'; + +import type { BrandInput } from './brand'; +import { BrandCheckError, staleFiles, writeBrand } from './generate'; + +const USAGE = `Usage: gamut-brand build [--out ] [--watch | --check] + + A .ts, .js or .json file whose default export is the brand + (defineBrand({ name, hues, ... })). + --out Where the files go. Defaults to a folder named after the + brand, next to the brand file. + --watch Regenerate whenever the brand file, or a file next to it, + changes. + --check Write nothing; exit 1 if the files in --out are missing or + out of date. For CI. + +Writes values.ts, tokens.css and DTCG JSON (palette, light, dark). Refuses a +brand that fails a contrast or separation check, and says which.`; + +const fail = (message: string): never => { + console.error(message); + process.exit(1); +}; + +const { values: flags, positionals } = (() => { + try { + return parseArgs({ + allowPositionals: true, + options: { + out: { type: 'string', short: 'o' }, + watch: { type: 'boolean', short: 'w' }, + check: { type: 'boolean' }, + help: { type: 'boolean', short: 'h' }, + }, + }); + } catch (e) { + return fail(`${(e as Error).message}\n\n${USAGE}`); + } +})(); + +if (flags.help) { + console.log(USAGE); + process.exit(0); +} +const [command, file] = positionals; +if (command !== 'build' || !file) fail(USAGE); +if (flags.watch && flags.check) fail('--watch and --check go separately.'); + +const brandFile = resolve(file); + +/* Fresh every time, so a watched edit is picked up, imports included. */ +const loadBrand = async (): Promise => { + const jiti = createJiti(import.meta.url, { + moduleCache: false, + fsCache: false, + }); + const brand = await jiti.import(brandFile, { default: true }); + if (!brand?.name || !brand.hues?.primary) { + throw new Error( + `${file}: the default export has to be a brand with a name and hues.primary` + ); + } + return brand; +}; + +const outFor = (brand: BrandInput) => + flags.out ? resolve(flags.out) : resolve(dirname(brandFile), brand.name); + +const show = (path: string) => relative(process.cwd(), path) || '.'; + +const report = (e: unknown) => { + if (e instanceof BrandCheckError) { + console.error(e.message); + console.error( + 'Pick other hues, or a palette or role override, and run again.' + ); + } else { + console.error(e instanceof Error ? e.message : e); + } +}; + +let lastOut: string | undefined; + +const build = async (): Promise => { + try { + const brand = await loadBrand(); + const out = outFor(brand); + lastOut = out; + if (flags.check) { + const stale = await staleFiles(brand, out); + if (stale.length) { + console.error( + `"${brand.name}" is out of date: ${stale + .map(show) + .join(', ')}\nRun gamut-brand build ${file}${ + flags.out ? ` --out ${flags.out}` : '' + }.` + ); + return false; + } + console.log(`"${brand.name}" is up to date in ${show(out)}`); + return true; + } + const written = await writeBrand(brand, out); + console.log( + written.length + ? `wrote "${brand.name}" to ${show(out)}: ${written + .map((p) => relative(out, p)) + .join(', ')}` + : `"${brand.name}" unchanged in ${show(out)}` + ); + return true; + } catch (e) { + report(e); + return false; + } +}; + +const ok = await build(); + +if (!flags.watch) process.exit(ok ? 0 : 1); + +/* + * Watches the brand file's folder, not only the file, so a brand that + * imports another (a shared base) rebuilds when either changes. The output + * folder is skipped, or every build would trigger the next. + */ +const SOURCES = new Set([ + '.ts', + '.mts', + '.cts', + '.js', + '.mjs', + '.cjs', + '.json', +]); +let timer: NodeJS.Timeout | undefined; +let running = false; +let again = false; +const rebuild = async () => { + if (running) { + again = true; + return; + } + running = true; + do { + again = false; + await build(); + } while (again); + running = false; +}; +watch(dirname(brandFile), (_, name) => { + if (!name || !SOURCES.has(extname(name))) return; + const path = resolve(dirname(brandFile), name); + const inOut = + lastOut && + lastOut !== dirname(brandFile) && + !relative(lastOut, path).startsWith('..'); + if (inOut || path === lastOut) return; + clearTimeout(timer); + timer = setTimeout(() => { + rebuild().catch(report); + }, 50); +}); +console.log(`watching ${show(dirname(brandFile))} for changes`); diff --git a/packages/gamut-brand/src/contentModes.ts b/packages/gamut-brand/src/contentModes.ts new file mode 100644 index 0000000000..6caa2e393f --- /dev/null +++ b/packages/gamut-brand/src/contentModes.ts @@ -0,0 +1,32 @@ +import { contrast } from './ramp'; +import type { Mode } from './roles'; +import { MODES } from './roles'; +import type { Color, ColorSystem } from './system'; + +/* + * Which mode's content sits on a color, decided at build time. + * + * Background used to work this out at runtime, with polished's contrast math + * on every render. The rule is the same — the mode whose `text-default` + * contrasts most with the color — but measured once, on the OKLCH floats, and + * shipped as data. A zero-runtime engine can't measure contrast at all, so + * this table is what a CSS-only Background reads: put the color down, then + * set `data-color-mode` to the mode listed here. + */ + +export const contentModeFor = (system: ColorSystem, color: Color): Mode => + MODES.reduce((best, mode) => + contrast(system.roles[mode]['text-default'], color) > + contrast(system.roles[best]['text-default'], color) + ? mode + : best + ); + +/* Every opaque palette color → the mode its content uses. */ +export const contentModes = (system: ColorSystem): Record => + Object.fromEntries( + Object.entries(system.palette).map(([key, color]) => [ + key, + contentModeFor(system, color), + ]) + ); diff --git a/packages/gamut-brand/src/emit.ts b/packages/gamut-brand/src/emit.ts new file mode 100644 index 0000000000..96e30b8d4b --- /dev/null +++ b/packages/gamut-brand/src/emit.ts @@ -0,0 +1,251 @@ +import { contentModes } from './contentModes'; +import type { Gradient, Mode, Ref, RoleValue } from './roles'; +import { FALLOFF, MODES, ROLES } from './roles'; +import type { Color, ColorSystem } from './system'; +import { paletteKey, roleValueFor, valueKey } from './system'; + +/* + * Color system → DTCG 2025.10 token files and TS constants. + * + * Three DTCG files, because DTCG has no modes of its own (the 2025.10 + * Resolver module covers that, and Style Dictionary doesn't support it yet): + * one palette, plus one role file per mode that references it. + * + * Every color is stored as OKLCH components with a 6-digit hex fallback. + * OKLCH is the source of truth; hex is for consumers outside CSS. + */ + +export const EXTENSION = 'com.skillsoft.gamut'; + +type DtcgColor = { + colorSpace: 'oklch'; + components: [number, number, number]; + alpha: number; + hex: string; +}; + +type DtcgToken = { + $type: 'color'; + $value: DtcgColor | string; + $description?: string; + $extensions?: Record; +}; + +export type DtcgGroup = { [key: string]: DtcgToken | DtcgGroup | string }; + +const dtcgColor = (color: Color, alpha = 1): DtcgColor => ({ + colorSpace: 'oklch', + components: [color.l, color.c, color.h], + alpha, + hex: color.hex, +}); + +export const oklchCss = ({ l, c, h }: Color, alpha?: number): string => + alpha === undefined || alpha === 1 + ? `oklch(${l} ${c} ${h})` + : `oklch(${l} ${c} ${h} / ${alpha})`; + +/* Hex, or rgba() when translucent — what polished-based color math can parse. */ +export const srgbCss = ({ hex }: Color, alpha?: number): string => { + if (alpha === undefined || alpha === 1) return hex; + const [r, g, b] = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16)); + return `rgba(${r}, ${g}, ${b}, ${alpha})`; +}; + +/* `{ slot: 'primary', grade: 800 }` → `['palette', 'primary', '800']`. */ +const refPath = (ref: Ref, alpha?: number): string[] => { + const suffix = alpha === undefined ? '' : `-a${Math.round(alpha * 100)}`; + return typeof ref === 'string' + ? [`${ref}${suffix}`] + : ['palette', ref.slot, `${ref.grade}${suffix}`]; +}; + +const refString = (value: RoleValue) => + `{${refPath(value.ref, value.alpha).join('.')}}`; + +const setPath = (group: DtcgGroup, path: string[], token: DtcgToken) => { + let node = group; + for (const key of path.slice(0, -1)) { + node[key] ??= {}; + node = node[key] as DtcgGroup; + } + node[path[path.length - 1]] = token; +}; + +/* + * Every translucent value a role uses gets its own palette entry, including + * the ones a brand's role overrides introduce (Original Codecademy's + * transparent `shadow-ambient`). + */ +const translucentValues = (system: ColorSystem): RoleValue[] => { + const seen = new Map(); + const add = (value: RoleValue) => { + if (value.alpha !== undefined) seen.set(valueKey(value), value); + }; + for (const role of ROLES) { + for (const mode of MODES) add(role[mode]); + } + for (const tokens of Object.values(system.brand.overrides?.roles ?? {})) { + for (const o of Object.values(tokens ?? {})) { + if (typeof o === 'object' && 'alpha' in o) add(o); + } + } + return [...seen.values()]; +}; + +export const gradientCss = (system: ColorSystem, gradient: Gradient) => + gradient.circles + .map(({ ref, radius, at }) => { + const color = system.palette[paletteKey(ref)]; + const stops = FALLOFF.map( + ([pos, alpha]) => `${oklchCss(color, alpha)} ${pos}%` + ).join(', '); + return `radial-gradient(circle calc(${radius} * 100cqw) at ${at}, ${stops})`; + }) + .join(', '); + +/* + * The same gradient against palette variables, for tokens.css. Each stop is + * the palette color at the stop's alpha (`color-mix` with transparent in + * oklab), so it follows whichever brand's palette is in scope and is emitted + * once for every brand. + */ +export const gradientCssVars = (gradient: Gradient) => + gradient.circles + .map(({ ref, radius, at }) => { + const stops = FALLOFF.map( + ([pos, alpha]) => + `color-mix(in oklab, var(--color-${paletteKey(ref)}) ${Number( + (alpha * 100).toFixed(2) + )}%, transparent) ${pos}%` + ).join(', '); + return `radial-gradient(circle calc(${radius} * 100cqw) at ${at}, ${stops})`; + }) + .join(', '); + +export const toDtcgPalette = (system: ColorSystem): DtcgGroup => { + const group: DtcgGroup = { + $description: `Generated from the "${system.brand.name}" brand (tier ${system.brand.tier}). Do not edit.`, + }; + const modes = contentModes(system); + for (const [key, color] of Object.entries(system.palette)) { + const path = + key === 'white' || key === 'black' + ? [key] + : ['palette', ...key.replace(/^palette-/, '').split(/-(?=\d+$)/)]; + setPath(group, path, { + $type: 'color', + $value: dtcgColor(color), + /* The mode content on this color uses. See contentModes.ts. */ + $extensions: { [EXTENSION]: { contentMode: modes[key] } }, + }); + } + for (const value of translucentValues(system)) { + const color = system.palette[paletteKey(value.ref)]; + setPath(group, refPath(value.ref, value.alpha), { + $type: 'color', + $value: dtcgColor(color, value.alpha), + }); + } + return group; +}; + +export const toDtcgMode = (system: ColorSystem, mode: Mode): DtcgGroup => { + const group: DtcgGroup = { + $description: `${mode} mode roles for "${system.brand.name}". Do not edit.`, + }; + for (const role of ROLES) { + const gradient = system.gradients[mode][role.token]; + group[role.token] = { + $type: 'color', + /* After the brand's role overrides, so the file is this brand's. */ + $value: refString(roleValueFor(system.brand.overrides, role, mode)), + $description: role.usage, + ...(gradient && { + $extensions: { + [EXTENSION]: { + gradient: { + circles: gradient.circles.map((c) => ({ + color: `{${refPath(c.ref).join('.')}}`, + radius: c.radius, + at: c.at, + })), + falloff: FALLOFF, + css: gradientCss(system, gradient), + cssVars: gradientCssVars(gradient), + }, + }, + }, + }), + }; + } + return group; +}; + +/* + * The TS constants the Emotion theme (and anything else in JS) reads, with + * the brand's tokens.css as `tokensCss` for GamutProvider to inject. + */ +export const toTsModule = (system: ColorSystem, tokensCss: string): string => { + const entry = (color: Color, alpha?: number, ref?: string) => ({ + ...(ref && { ref }), + oklch: oklchCss(color, alpha), + srgb: srgbCss(color, alpha), + }); + + const palette: Record> = {}; + for (const [key, color] of Object.entries(system.palette)) { + palette[key] = entry(color); + } + for (const value of translucentValues(system)) { + palette[valueKey(value)] = entry( + system.palette[paletteKey(value.ref)], + value.alpha + ); + } + + const byMode = (source: ColorSystem['roles']) => + Object.fromEntries( + MODES.map((mode) => [ + mode, + Object.fromEntries( + Object.entries(source[mode]).map(([token, v]) => [ + token, + entry(v, v.alpha, v.key), + ]) + ), + ]) + ); + + const gradients = Object.fromEntries( + MODES.map((mode) => [ + mode, + Object.fromEntries( + Object.entries(system.gradients[mode]).map(([token, gradient]) => [ + token, + gradientCss(system, gradient), + ]) + ), + ]) + ); + + const json = (value: unknown) => JSON.stringify(value, null, 2); + return [ + `/* Generated by @skillsoft/gamut-brand from the "${system.brand.name}" brand. Do not edit. */`, + '', + `export const brand = ${json(system.brand)} as const;`, + '', + `export const palette = ${json(palette)} as const;`, + '', + `export const roles = ${json(byMode(system.roles))} as const;`, + '', + `export const gradients = ${json(gradients)} as const;`, + '', + '/* Palette color → the mode its content uses. Background reads this instead of measuring. */', + `export const contentModes = ${json(contentModes(system))} as const;`, + '', + "/* This brand's tokens.css, for GamutProvider to inject. */", + `export const tokensCss = ${JSON.stringify(tokensCss)};`, + '', + ].join('\n'); +}; diff --git a/packages/gamut-brand/src/generate.ts b/packages/gamut-brand/src/generate.ts new file mode 100644 index 0000000000..a19fadd6a0 --- /dev/null +++ b/packages/gamut-brand/src/generate.ts @@ -0,0 +1,270 @@ +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; + +import StyleDictionary from 'style-dictionary'; +import { fileHeader, formattedVariables } from 'style-dictionary/utils'; + +import type { BrandInput } from './brand'; +import type { DtcgGroup } from './emit'; +import { EXTENSION, toDtcgMode, toDtcgPalette, toTsModule } from './emit'; +import { validateMigration } from './migration'; +import type { Mode } from './roles'; +import { ROLES } from './roles'; +import { parseTokensCss, roundTripMisses } from './roundTrip'; +import type { ColorSystem } from './system'; +import { createColorSystem } from './system'; +import type { Check } from './validate'; +import { validate } from './validate'; + +/* + * A brand in, its files out: tokens.css (through Style Dictionary), DTCG + * JSON and a typed `values.ts` that `createBrandTheme` in + * `@skillsoft/gamut-styles` takes. Each brand is complete on its own, so a + * page carries one brand's colors, not every brand's. + */ + +/* Cascade layer order: Panda's default, which is Chakra's plus `utilities`. */ +export const LAYERS = ['reset', 'base', 'tokens', 'recipes', 'utilities']; + +/* Letters, digits and `_`: safe in a CSS attribute value, a file name and a JS identifier. */ +const BRAND_NAME = /^[A-Za-z][A-Za-z0-9_]*$/; + +const ROLE_TOKENS = new Set(ROLES.map((r) => r.token)); + +const FORMAT = 'css/gamut-variables'; + +/* + * css/variables, plus a `--gradient-` line for any role carrying a + * gradient in its $extensions. DTCG's gradient type can't describe the + * brand's stacked circles, so the CSS is precomputed by the emitter. + * + * Mode blocks also get: + * - `color-scheme`, so native controls and scrollbars follow the mode; + * - `color: var(--color-text-default)`, because `color` inherits as a computed + * value — without it, text under a nested `[data-color-mode]` keeps the + * parent mode's color (ColorMode sets it on its wrapper for the same reason); + * - a default `--color-background-current`, which ColorMode sets at runtime + * and a CSS-only subtree needs from somewhere. + */ +StyleDictionary.registerFormat({ + name: FORMAT, + format: async ({ dictionary, options, file }) => { + const header = await fileHeader({ file }); + const vars = formattedVariables({ + format: 'css', + dictionary, + outputReferences: options.outputReferences, + usesDtcg: true, + }); + const gradients = dictionary.allTokens + .filter((t) => t.$extensions?.[EXTENSION]?.gradient) + .map( + (t) => + ` --gradient-${t.path.join('-')}: ${ + t.$extensions[EXTENSION].gradient.cssVars + };` + ); + const { mode } = options as { mode?: Mode }; + const modeLines = mode + ? [ + ` color-scheme: ${mode};`, + ' color: var(--color-text-default);', + ' --color-background-current: var(--color-surface-default);', + ] + : []; + return `${header}${options.selector} {\n${[ + ...modeLines, + vars, + ...gradients, + ].join('\n')}\n}\n`; + }, +}); + +/* + * One CSS block. A mode block gets the palette too, so its references + * resolve, but prints only the roles. + */ +const cssBlock = async ( + tokens: DtcgGroup, + selector: string, + mode?: Mode +): Promise => { + const sd = new StyleDictionary({ + tokens: structuredClone(tokens), + usesDtcg: true, + log: { verbosity: 'silent', warnings: 'disabled' }, + platforms: { + css: { + transforms: ['name/kebab', 'color/oklch'], + prefix: 'color', + files: [ + { + destination: 'unused.css', + format: FORMAT, + filter: mode + ? (t) => t.path.length === 1 && ROLE_TOKENS.has(t.path[0]) + : undefined, + options: { + selector, + mode, + outputReferences: true, + showFileHeader: false, + }, + }, + ], + }, + }, + }); + const [{ output }] = await sd.formatPlatform('css'); + return output as string; +}; + +/* Thrown when a brand fails a check; nothing is generated for it. */ +export class BrandCheckError extends Error { + constructor(readonly brand: string, readonly failed: Check[]) { + super( + `refusing to generate "${brand}"; failed checks:\n${failed + .map((c) => ` ${c.name}: ${c.worst.toFixed(2)} at ${c.at}`) + .join('\n')}` + ); + this.name = 'BrandCheckError'; + } +} + +export interface GeneratedBrand { + system: ColorSystem; + /* File name → contents, all of them text. */ + files: Record; +} + +export const FILES = [ + 'values.ts', + 'tokens.css', + 'palette.tokens.json', + 'light.tokens.json', + 'dark.tokens.json', +] as const; + +const json = (v: unknown) => `${JSON.stringify(v, null, 2)}\n`; + +/* + * The brand's palette goes on `:root` and `[data-brand]`, and its initial + * mode's roles on `:root` and that mode's attribute, so a page starts in the + * right brand and mode with no attributes on ``. The other mode comes + * after, so `data-color-mode` on `` still wins over `:root`. A brand's + * role overrides are already in its mode blocks: a bare `data-color-mode` + * keeps them. + */ +const tokensCss = async (system: ColorSystem): Promise => { + const { name, initialMode } = system.brand; + const palette = toDtcgPalette(system); + const other: Mode = initialMode === 'light' ? 'dark' : 'light'; + const blocks = [ + await cssBlock(palette, `:root, [data-brand="${name}"]`), + await cssBlock( + { ...palette, ...toDtcgMode(system, initialMode) }, + `:root, [data-color-mode="${initialMode}"]`, + initialMode + ), + await cssBlock( + { ...palette, ...toDtcgMode(system, other) }, + `[data-color-mode="${other}"]`, + other + ), + ]; + /* + * Every block goes in the `tokens` layer, after the whole order is declared, + * so a reset, base styles or recipes added later (by us or by Panda) slot in + * around it. Unlayered styles, which is everything Emotion writes, still win. + * The statement form declares the order without emitting empty layer blocks. + */ + return [ + `/* Generated by @skillsoft/gamut-brand from the "${name}" brand. Do not edit. */`, + `@layer ${LAYERS.join(', ')};`, + '', + `@layer tokens {\n${blocks + .join('\n') + .trimEnd() + .replace(/^(?=.)/gm, ' ')}\n}`, + '', + ].join('\n'); +}; + +/* + * Every check has to pass, on the system and again on its tokens.css read + * back, or nothing is generated. + */ +export const generateBrand = async ( + input: BrandInput +): Promise => { + if (!BRAND_NAME.test(input.name)) { + throw new Error( + `brand name "${input.name}" has to be letters, digits and _, starting with a letter` + ); + } + const system = createColorSystem(input); + const failing = (s: ColorSystem) => + [...validate(s), ...validateMigration(s)].filter((c) => !c.pass); + + const failed = failing(system); + if (failed.length) throw new BrandCheckError(input.name, failed); + + const css = await tokensCss(system); + const parsed = parseTokensCss(css, system); + const misses = roundTripMisses(parsed, system); + if (misses.length) { + throw new Error( + `${input.name}: tokens.css doesn't round-trip ${misses.join(', ')}` + ); + } + const failedOnCss = failing(parsed); + if (failedOnCss.length) throw new BrandCheckError(input.name, failedOnCss); + + return { + system, + files: { + 'values.ts': toTsModule(system, css), + 'tokens.css': css, + 'palette.tokens.json': json(toDtcgPalette(system)), + 'light.tokens.json': json(toDtcgMode(system, 'light')), + 'dark.tokens.json': json(toDtcgMode(system, 'dark')), + }, + }; +}; + +/* + * Generates the brand and writes its files into `dir`. A file whose contents + * haven't changed isn't touched, so a dev server doesn't reload for nothing. + * Returns the paths written. + */ +export const writeBrand = async ( + input: BrandInput, + dir: string +): Promise => { + const { files } = await generateBrand(input); + await mkdir(dir, { recursive: true }); + const written: string[] = []; + for (const [file, contents] of Object.entries(files)) { + const path = join(dir, file); + const current = await readFile(path, 'utf8').catch(() => undefined); + if (current === contents) continue; + await writeFile(path, contents); + written.push(path); + } + return written; +}; + +/* The files in `dir` that are missing or differ from what the brand generates. */ +export const staleFiles = async ( + input: BrandInput, + dir: string +): Promise => { + const { files } = await generateBrand(input); + const stale: string[] = []; + for (const [file, contents] of Object.entries(files)) { + const path = join(dir, file); + const current = await readFile(path, 'utf8').catch(() => undefined); + if (current !== contents) stale.push(path); + } + return stale; +}; diff --git a/packages/gamut-brand/src/index.ts b/packages/gamut-brand/src/index.ts new file mode 100644 index 0000000000..b8b9b2ba24 --- /dev/null +++ b/packages/gamut-brand/src/index.ts @@ -0,0 +1,54 @@ +/* + * @skillsoft/gamut-brand: build a gamut color brand at build time. + * + * // acme.brand.ts + * import { defineBrand } from '@skillsoft/gamut-brand'; + * export default defineBrand({ name: 'acme', hues: { primary: '#0A5CFF' } }); + * + * gamut-brand build acme.brand.ts # writes ./acme/ + * gamut-brand build acme.brand.ts --watch # regenerates on save + * gamut-brand build acme.brand.ts --check # CI: fails if ./acme/ is stale + * + * The output's `values.ts` goes to `createBrandTheme` in + * `@skillsoft/gamut-styles`. Nothing here runs in the browser. + */ +import type { BrandInput } from './brand'; + +/* + * Types a brand and returns it unchanged. Typos in a slot, role or palette + * rung fail to compile. Not generic on purpose: a generic parameter would + * skip TypeScript's excess-property check, and `acent: '#f00'` would pass. + */ +export const defineBrand = (brand: BrandInput): BrandInput => brand; + +export type { + BrandInput, + BrandOverrides, + HueInput, + ResolvedBrand, + RoleOverride, + Slot, + Tier, +} from './brand'; +export { DEFAULT_HUES, SLOTS } from './brand'; +export type { Grade } from './ramp'; +export { GRADES } from './ramp'; +export type { Mode, Ref, Role, RoleToken, RoleValue } from './roles'; +export { MODES, ROLE_TOKENS, ROLES } from './roles'; +export type { ColorSystem } from './system'; +export { createColorSystem } from './system'; +export type { Check } from './validate'; +export { validate } from './validate'; +export { TOKEN_MAP, validateMigration } from './migration'; +export { probeTenant } from './probe'; +export type { GeneratedBrand } from './generate'; +export { + BrandCheckError, + FILES, + generateBrand, + staleFiles, + writeBrand, +} from './generate'; + +/* gamut's own brands, to extend: `{ ...skillsoftBrand, name: 'acme', hues: { ... } }`. */ +export * from './brands'; diff --git a/packages/gamut-brand/src/migration.ts b/packages/gamut-brand/src/migration.ts new file mode 100644 index 0000000000..b64581f909 --- /dev/null +++ b/packages/gamut-brand/src/migration.ts @@ -0,0 +1,1626 @@ +/* + * The migration layer: how today's Gamut color aliases and component colors + * land on the role layer: design's map, every old alias to a role and every + * component slot by slot. + * + * Every target is a role token, never a primitive or a hex. `validateMigration` + * holds the file to that for any color system, not just Skillsoft's: each + * alias mapped exactly once, every target a real role, and each component's + * text and icon slots clearing their tier on the fills they sit on, in both + * modes. + */ + +import type { Color as CuloriColor } from 'culori'; +import { wcagContrast } from 'culori'; + +import type { Mode } from './roles'; +import { FALLOFF, MODES, roleFor, ROLES } from './roles'; +import type { ColorSystem } from './system'; +import { paletteKey } from './system'; +import type { Check } from './validate'; +import { composite } from './validate'; + +// ------------------------------------------------------------- token map + +/* + * Every semantic color alias Gamut's themes define today, in the order + * `coreTheme` declares them. + * + * Listed rather than read from the theme because the validator loads this + * file outside the browser, where `@codecademy/gamut-styles` does not import. + * The page diffs this list against the live theme at render time, so a + * Gamut upgrade that adds an alias shows up there as unmapped. + */ +export const LEGACY_TOKENS = [ + 'text', + 'text-accent', + 'text-disabled', + 'text-secondary', + 'feedback-error', + 'feedback-success', + 'feedback-warning', + 'background', + 'background-contrast', + 'background-current', + 'background-primary', + 'background-selected', + 'background-disabled', + 'background-hover', + 'background-success', + 'background-warning', + 'background-error', + 'shadow-primary', + 'shadow-secondary', + 'primary', + 'primary-hover', + 'primary-inverse', + 'secondary', + 'secondary-hover', + 'danger', + 'danger-hover', + 'interface', + 'interface-hover', + 'border-primary', + 'border-secondary', + 'border-tertiary', + 'border-disabled', +] as const; + +export type LegacyToken = (typeof LEGACY_TOKENS)[number]; + +/* + * What kind of move a mapping is, because the kind decides how much work a + * find-and-replace can do. + * + * - `rename`: one alias, one role. Mechanical. + * - `merge`: several aliases collapse onto one role. Mechanical, but the + * visual difference between the aliases goes away. + * - `split`: one alias becomes one of several roles depending on where it is + * used. Needs a person, and `rule` says how to choose. + * - `retire`: no role replaces it; `rule` says what to do instead. + * - `pending`: decided, but its target belongs to a system that does not + * exist yet; `rule` names what it will map to. + */ +export type MapKind = 'rename' | 'merge' | 'split' | 'retire' | 'pending'; + +export const MAP_KIND_LABEL: Record = { + rename: 'Rename', + merge: 'Merge', + split: 'Split', + retire: 'Retire', + pending: 'Pending', +}; + +export interface TokenMapping { + legacy: LegacyToken; + kind: MapKind; + /* Role tokens, most common target first. Empty only for `retire` and `pending`. */ + to: string[]; + /* How to apply it. For a split, how to choose between the targets. */ + rule: string; + /* A decision this mapping depends on that has not been made yet. */ + open?: string; +} + +export const TOKEN_GROUPS: { id: string; label: string; prefix: RegExp }[] = [ + { id: 'text', label: 'Text', prefix: /^text/ }, + { id: 'feedback', label: 'Feedback', prefix: /^feedback/ }, + { id: 'background', label: 'Background', prefix: /^background/ }, + { + id: 'action', + label: 'Action', + prefix: /^(primary|secondary|danger|interface)/, + }, + { id: 'border', label: 'Border', prefix: /^border/ }, + { id: 'shadow', label: 'Shadow', prefix: /^shadow/ }, +]; + +export const TOKEN_MAP: TokenMapping[] = [ + // ------------------------------------------------------------------ text + { + legacy: 'text', + kind: 'rename', + to: ['text-default'], + rule: 'Body copy and headings.', + }, + { + legacy: 'text-accent', + kind: 'merge', + to: ['text-default'], + rule: 'Headings take `text-default` too. Emphasis comes from weight and size, not from a second ink.', + }, + { + legacy: 'text-secondary', + kind: 'rename', + to: ['text-subtle'], + rule: 'Descriptions, metadata, helper copy. An opaque color in every theme, so it no longer depends on what sits underneath it.', + }, + { + legacy: 'text-disabled', + kind: 'split', + to: ['action-disabled', 'surface-raised', 'text-subtle'], + rule: 'A disabled label or icon drawn straight on a surface (text button, link, menu item, form label) is `action-disabled`, the same color as a disabled fill. On a disabled fill the label is a knockout: it takes the surface the control sits on, usually `surface-raised`. The value inside a disabled input is the one place `text-subtle` stays. The proposal describes `action-disabled` as a fill only; its use as the disabled ink is an addition for the proposal’s next revision.', + }, + + // -------------------------------------------------------------- feedback + { + legacy: 'feedback-error', + kind: 'rename', + to: ['text-error'], + rule: 'Error text and icons, including inline validation and the border of a field in error. Destructive buttons are `action-danger`, not this.', + }, + { + legacy: 'feedback-success', + kind: 'rename', + to: ['text-success'], + rule: 'Success text and icons. Always with an icon.', + }, + { + legacy: 'feedback-warning', + kind: 'rename', + to: ['text-warning'], + rule: 'Warning text and icons. Dark enough to be read as text, so it can carry a message on its own rather than only an icon.', + }, + + // ------------------------------------------------------------ background + { + legacy: 'background', + kind: 'split', + to: ['surface-raised', 'surface-default'], + rule: 'Anything that sits on the page (card, input, menu, popover, dialog) takes `surface-raised`. Where `background` fills the page itself, it is `surface-default`.', + }, + { + legacy: 'background-primary', + kind: 'rename', + to: ['surface-default'], + rule: 'The page background, the base every other surface is placed on.', + }, + { + legacy: 'background-current', + kind: 'split', + to: ['surface-default', 'surface-raised', 'surface-sunken'], + rule: 'Whatever surface the element sits on. Becomes a `surface-current` alias that every surface container (the page, raised, sunken, `Background`) sets to its own role, so it always resolves to one of these three. Masks and backdrops that must match their parent read it: sticky List headers, scroll and truncation fades, Drawer, DataList empty rows, the transparent Disclosure. Nothing sets it by hand.', + }, + { + legacy: 'background-contrast', + kind: 'retire', + to: ['surface-raised'], + rule: 'Use `surface-raised`. Maximum contrast against the page is what raised already provides in dark mode, and light mode has no surface lighter than it. In Gamut it backs ToolTip and InfoTip, block Tabs and the video player’s tooltip and slider value, all remapped; none needs it to be black.', + }, + { + legacy: 'background-selected', + kind: 'rename', + to: ['state-selected'], + rule: 'A veil composited over the element’s own fill, not an opaque fill that replaces it. The element keeps its surface underneath.', + }, + { + legacy: 'background-hover', + kind: 'split', + to: ['state-hover', 'surface-sunken'], + rule: 'As a hover, it is the `state-hover` veil. Used as a resting fill (the subtle Alert, a tinted panel), it is `surface-sunken`.', + }, + { + legacy: 'background-disabled', + kind: 'rename', + to: ['action-disabled'], + rule: 'The disabled fill for every action variant. One token, whatever was disabled.', + }, + { + legacy: 'background-success', + kind: 'rename', + to: ['surface-success'], + rule: 'The success tint behind an alert or inline confirmation.', + }, + { + legacy: 'background-warning', + kind: 'rename', + to: ['surface-warning'], + rule: 'The warning tint behind an alert or inline caution.', + }, + { + legacy: 'background-error', + kind: 'rename', + to: ['surface-error'], + rule: 'The error tint behind an alert or inline validation message.', + }, + + // ---------------------------------------------------------------- shadow + { + legacy: 'shadow-primary', + kind: 'split', + to: ['shadow-key', 'shadow-ambient'], + rule: 'Every elevation shadow composes both: `shadow-key` for the tight contact layer, `shadow-ambient` for the soft falloff. The elevation tokens hold only offset, blur and spread; see the elevation system for which depth uses which geometry.', + }, + { + legacy: 'shadow-secondary', + kind: 'split', + to: ['shadow-key', 'shadow-ambient'], + rule: 'Same as `shadow-primary`. Depth comes from geometry, not from a second color, so both legacy tokens land on the same pair.', + }, + + // ---------------------------------------------------------------- action + { + legacy: 'primary', + kind: 'rename', + to: ['action-primary'], + rule: 'Primary fills, strokes, links and focus rings. Pressed is new: `action-primary-pressed`.', + }, + { + legacy: 'primary-hover', + kind: 'rename', + to: ['action-primary-hover'], + rule: 'Hover for the primary action.', + }, + { + legacy: 'primary-inverse', + kind: 'retire', + to: ['action-primary'], + rule: 'A primary action on an inverted ground is `action-primary` inside a dark-mode scope. The mode does the inverting.', + }, + { + legacy: 'secondary', + kind: 'rename', + to: ['action-neutral'], + rule: 'The secondary button and secondary icon/text buttons. Labels on it are `text-inverse`.', + }, + { + legacy: 'secondary-hover', + kind: 'rename', + to: ['action-neutral-hover'], + rule: 'Hover for the secondary action.', + }, + { + legacy: 'danger', + kind: 'rename', + to: ['action-danger'], + rule: 'Destructive actions: delete, remove, revoke. Error messaging is `text-error`.', + }, + { + legacy: 'danger-hover', + kind: 'rename', + to: ['action-danger-hover'], + rule: 'Hover for the destructive action.', + }, + { + legacy: 'interface', + kind: 'merge', + to: ['action-primary'], + rule: 'Checkboxes, radios, toggles, focus rings and inline links share the one interactive blue.', + }, + { + legacy: 'interface-hover', + kind: 'merge', + to: ['action-primary-hover'], + rule: 'Hover for the same controls.', + }, + + // ---------------------------------------------------------------- border + { + legacy: 'border-primary', + kind: 'split', + to: ['border-default', 'border-subtle'], + rule: 'A border that carries meaning (field outline, control stroke, a container that needs an edge to be seen) is `border-default`. A border that only decorates or backs up a shadow (card hairline, table rule, the edge of a menu, popover, toast or dialog) is `border-subtle`.', + }, + { + legacy: 'border-secondary', + kind: 'merge', + to: ['border-default'], + rule: 'Same as `border-primary`. One meaningful border weight.', + }, + { + legacy: 'border-tertiary', + kind: 'split', + to: ['border-subtle', 'border-strong'], + rule: 'A divider or card hairline is `border-subtle`. Where it marks an active field or a selected row, it is `border-strong`. Gamut’s own two usages, the FeatureShimmer edge and the List scroll shadow, are both `border-subtle`. Core resolves it to a light navy and Percipio to a dark one, so check each app usage for which it meant.', + }, + { + legacy: 'border-disabled', + kind: 'merge', + to: ['border-default'], + rule: 'A disabled control is marked by its `action-disabled` fill. Its outline stays `border-default`.', + }, +]; + +/* Roles no legacy alias maps onto — the capabilities the old set lacked. */ +export const NEW_ROLES = ROLES.filter( + (role) => + !TOKEN_MAP.some((m) => m.to.includes(role.token)) && + !role.token.startsWith('viz') +); + +export const mappingFor = (legacy: string): TokenMapping | undefined => + TOKEN_MAP.find((m) => m.legacy === legacy); + +// ---------------------------------------------------------------- surfaces + +/* + * The surface rule, in the order a reader applies it: start from the page + * and ask what the element is placed on. + */ +export const SURFACE_RULES: { role: string; when: string; examples: string }[] = + [ + { + role: 'surface-default', + when: 'The page itself.', + examples: 'App background, the area behind the main content.', + }, + { + role: 'surface-raised', + when: 'Anything placed on the page.', + examples: + 'Cards, panels, inputs, menus, popovers, dialogs, the header bar.', + }, + { + role: 'surface-sunken', + when: 'Anything recessed into the surface it sits on.', + examples: + 'Code blocks, wells, insets, a progress track, the subtle Alert.', + }, + ]; + +/* + * What to do when a container sits inside a container. Stated separately + * because it is the case the three-level stack does not answer on its own. + */ +export const NESTING_RULE = + 'There is no level above `surface-raised`. A panel inside a card stays `surface-raised` and takes a `border-subtle` hairline; a recessed area inside a card is `surface-sunken`. Overlays (menus, popovers, dialogs) are `surface-raised` wherever they open, and a dialog adds `shadow-scrim` behind it.'; + +// -------------------------------------------------------------- components + +/* + * One colored part of a component. + * + * `kind` is what lets the validator check the mapping rather than just store + * it: text and icon slots are measured against the variant's fill. + */ +export interface ComponentSlot { + slot: string; + kind: 'fill' | 'text' | 'icon' | 'border' | 'veil'; + /* Today's value as it appears in Gamut's source: an alias, a palette key, or a note. */ + legacy: string; + /* The role it maps to, or `inherit` for a slot that takes its parent's color. */ + role: string; + note?: string; + /* + * The label is cut out of a disabled fill and shows the surface behind it. + * `role` names the usual surface; on another surface it takes that one. + */ + knockout?: boolean; +} + +/* A slot target that is deliberately not a role. */ +export const INHERIT = 'inherit'; + +export interface ComponentVariant { + name: string; + /* Today's variant name, when it differs from `name`. */ + legacyName?: string; + slots: ComponentSlot[]; + /* + * The surface a variant without a fill of its own is measured on: a text + * button, a link, a field label. `surface-raised` is the binding case for + * most marks, since it is where controls usually sit. + */ + on?: string; +} + +export type MappingStatus = 'mapped' | 'open'; + +export interface ComponentMapping { + id: string; + name: string; + summary: string; + variants: ComponentVariant[]; + /* Slots shared by every variant (borders, buttons inside the component). */ + shared?: ComponentSlot[]; + open?: string[]; +} + +/* A slot, positionally — the component tables below are long enough that + * the object form buried the four values that matter. */ +const s = ( + slot: string, + kind: ComponentSlot['kind'], + legacy: string, + role: string, + extra?: { note?: string; knockout?: boolean } +): ComponentSlot => ({ slot, kind, legacy, role, ...extra }); + +/* A knocked-out disabled label: the surface shows through the fill. */ +const knockout = (legacy: string): ComponentSlot => + s('Disabled label', 'text', legacy, 'surface-raised', { + knockout: true, + note: 'Knockout: the surface the control sits on shows through.', + }); + +const cap = (word: string): string => word[0].toUpperCase() + word.slice(1); + +/* + * The Alert pattern, shared by its status variants: the tint carries the + * surface, and the status ink carries the icon and the message. The status + * inks clear text contrast on their own tint, so the message can carry the + * status too. A floating Alert's border takes the same ink. + */ +const statusAlert = ( + name: string, + status: 'success' | 'warning' | 'error', + legacyBg: string, + legacyName?: string +): ComponentVariant => ({ + name, + legacyName, + slots: [ + { slot: 'Fill', kind: 'fill', legacy: legacyBg, role: `surface-${status}` }, + { + slot: 'Icon', + kind: 'icon', + legacy: 'auto (Background)', + role: `text-${status}`, + }, + { + slot: 'Message', + kind: 'text', + legacy: 'auto (Background)', + role: `text-${status}`, + }, + ], +}); + +export const COMPONENTS: ComponentMapping[] = [ + { + id: 'alert', + name: 'Alert', + summary: + 'Status variants move from saturated fills with auto-contrast text to the status tints: `surface-*` behind, `text-*` on the icon and the message. A floating Alert takes its `text-*` ink on the border too. The buttons inside follow the secondary button.', + variants: [ + statusAlert('Error', 'error', 'red-600'), + statusAlert('Success', 'success', 'green'), + statusAlert('Warning', 'warning', 'orange', 'notice'), + { + name: 'Info', + legacyName: 'general', + slots: [ + { + slot: 'Fill', + kind: 'fill', + legacy: 'blue', + role: 'surface-brand-subtle', + }, + { + slot: 'Icon', + kind: 'icon', + legacy: 'auto (Background)', + role: 'action-primary', + }, + { + slot: 'Message', + kind: 'text', + legacy: 'auto (Background)', + role: 'text-default', + }, + ], + }, + { + name: 'Feature', + slots: [ + { + slot: 'Fill', + kind: 'fill', + legacy: 'blue-300', + role: 'surface-brand', + }, + { + slot: 'Icon', + kind: 'icon', + legacy: 'auto (Background)', + role: 'action-primary', + }, + { + slot: 'Message', + kind: 'text', + legacy: 'auto (Background)', + role: 'text-default', + }, + ], + }, + { + name: 'Subtle', + slots: [ + { + slot: 'Fill', + kind: 'fill', + legacy: 'background-hover', + role: 'surface-sunken', + }, + { + slot: 'Icon', + kind: 'icon', + legacy: 'auto (Background)', + role: 'text-subtle', + }, + { + slot: 'Message', + kind: 'text', + legacy: 'auto (Background)', + role: 'text-default', + }, + ], + }, + ], + shared: [ + { + slot: 'Floating border', + kind: 'border', + legacy: 'navy', + role: 'border-default', + note: 'Inline placement keeps no border.', + }, + { + slot: 'CTA button', + kind: 'fill', + legacy: 'FillButton secondary, auto mode', + role: 'action-neutral', + note: 'Label is `text-inverse`.', + }, + { + slot: 'Close / expand', + kind: 'icon', + legacy: 'IconButton / TextButton secondary', + role: 'action-neutral', + note: 'Hover and press are `state-hover` / `state-pressed`.', + }, + ], + }, + + // ---------------------------------------------------------------- buttons + { + id: 'fill-button', + name: 'FillButton', + summary: + 'Each variant maps onto its action family, which now has a pressed step of its own. Labels are `text-inverse` on every fill, so a label never depends on the color mode a parent forces. Every variant, danger included, takes the one `action-primary` focus ring; disabled labels are a knockout.', + variants: (['primary', 'neutral', 'danger'] as const).map((family) => { + const legacy = family === 'neutral' ? 'secondary' : family; + return { + name: family === 'neutral' ? 'Secondary' : cap(family), + slots: [ + s('Fill', 'fill', legacy, `action-${family}`), + s('Hover fill', 'fill', `${legacy}-hover`, `action-${family}-hover`), + s( + 'Pressed fill', + 'fill', + `${legacy} + border-primary`, + `action-${family}-pressed` + ), + s('Label', 'text', 'background', 'text-inverse'), + s('Focus ring', 'border', legacy, 'action-primary'), + ], + }; + }), + shared: [ + s('Disabled fill', 'fill', 'background-disabled', 'action-disabled'), + knockout('text-disabled'), + s('Interface variant', 'fill', 'interface', 'action-primary', { + note: 'Merges into primary.', + }), + ], + }, + { + id: 'stroke-button', + name: 'StrokeButton', + summary: + 'Stroke and label take the action color; hover and press become veils over the surface instead of a gray fill and a full inversion.', + variants: (['primary', 'neutral', 'danger'] as const).map((family) => { + const legacy = family === 'neutral' ? 'secondary' : family; + return { + name: family === 'neutral' ? 'Secondary' : cap(family), + on: 'surface-raised', + slots: [ + s('Stroke', 'border', legacy, `action-${family}`), + s('Label', 'text', legacy, `action-${family}`), + s('Hover', 'veil', 'background-hover', 'state-hover'), + s( + 'Pressed', + 'veil', + `${legacy} fill, background label`, + 'state-pressed' + ), + s('Focus ring', 'border', legacy, 'action-primary'), + ], + }; + }), + shared: [ + s('Disabled stroke', 'border', 'background-disabled', 'action-disabled'), + s('Disabled label', 'text', 'text-disabled', 'action-disabled'), + ], + }, + { + id: 'text-button', + name: 'TextButton / IconButton', + summary: + 'Label or icon in the action color, `state-hover` / `state-pressed` behind it. IconButton keeps secondary as its default, which is now `action-neutral`.', + variants: [ + { + name: 'Primary', + on: 'surface-raised', + slots: [ + s('Label / icon', 'text', 'primary', 'action-primary'), + s('Focus ring', 'border', 'primary', 'action-primary'), + ], + }, + { + name: 'Secondary', + on: 'surface-raised', + slots: [ + s('Label / icon', 'text', 'secondary', 'action-neutral'), + s('Focus ring', 'border', 'secondary', 'action-primary'), + ], + }, + { + name: 'Danger', + on: 'surface-raised', + slots: [ + s('Label / icon', 'text', 'danger', 'action-danger'), + s('Focus ring', 'border', 'danger', 'action-primary'), + ], + }, + { + name: 'Interface', + on: 'surface-raised', + slots: [ + s('Label / icon', 'text', 'text', 'text-default'), + s('Focus ring', 'border', 'primary', 'action-primary'), + ], + }, + ], + shared: [ + s('Hover', 'veil', 'background-hover', 'state-hover'), + s('Pressed', 'veil', 'text (label color)', 'state-pressed'), + s('Disabled label', 'text', 'text-disabled', 'action-disabled'), + ], + }, + { + id: 'cta-button', + name: 'CTAButton', + summary: + 'Fills follow primary FillButton. The shadow comes from the elevation system: the soft raised shadow in core, the hard offset in the codecademy theme only.', + variants: [ + { + name: 'Primary', + slots: [ + s('Fill', 'fill', 'primary', 'action-primary'), + s('Hover fill', 'fill', 'primary-hover', 'action-primary-hover'), + s('Pressed fill', 'fill', 'secondary', 'action-primary-pressed'), + s('Label', 'text', 'background', 'text-inverse'), + ], + }, + ], + shared: [ + s('Focus ring', 'border', 'primary', 'action-primary'), + s('Disabled fill', 'fill', 'background-disabled', 'action-disabled'), + knockout('text-disabled'), + s('Shadow (key)', 'veil', 'text (as box-shadow)', 'shadow-key', { + note: 'Raised depth. Core: soft shadow. Codecademy theme: the hard offset, drawn in shadow-key.', + }), + s('Shadow (ambient)', 'veil', 'text (as box-shadow)', 'shadow-ambient', { + note: 'Transparent in the codecademy theme.', + }), + ], + }, + { + id: 'anchor', + name: 'Anchor', + summary: + 'Links are `action-primary` and keep their underline: color alone never separates a link from body text.', + variants: [ + { + name: 'Inline / standard', + on: 'surface-default', + slots: [ + s('Text', 'text', 'primary', 'action-primary'), + s('Hover text', 'text', 'primary', 'action-primary-hover'), + ], + }, + { + name: 'Interface', + on: 'surface-raised', + slots: [ + s('Text', 'text', 'text', 'text-default'), + s('Hover text', 'text', 'primary', 'action-primary'), + ], + }, + { + name: 'Standard secondary', + on: 'surface-default', + slots: [s('Text', 'text', 'text', 'text-default')], + }, + ], + shared: [ + s('Focus ring', 'border', 'primary / currentColor', 'action-primary'), + s('Disabled', 'text', 'text-disabled', 'action-disabled'), + ], + }, + + // --------------------------------------------------------- badges & tags + { + id: 'badge', + name: 'Badge', + summary: + 'Filled badges keep text colors as fills, now as roles: `text-default` and `text-subtle` behind a `text-inverse` label. Accent takes the Two Color brand gradient, `surface-brand`, with a `border-default` edge.', + variants: [ + { + name: 'Primary', + slots: [ + s('Fill', 'fill', 'text', 'text-default'), + s('Label', 'text', 'background', 'text-inverse'), + ], + }, + { + name: 'Secondary', + slots: [ + s('Fill', 'fill', 'text-secondary', 'text-subtle'), + s('Label', 'text', 'background', 'text-inverse'), + ], + }, + { + name: 'Tertiary', + on: 'surface-raised', + slots: [ + s('Label', 'text', 'text-secondary', 'text-subtle'), + s('Border', 'border', 'border-secondary', 'border-default'), + ], + }, + { + name: 'Tertiary fill', + slots: [ + s('Fill', 'fill', 'background', 'surface-raised'), + s('Label', 'text', 'text-secondary', 'text-subtle'), + s('Border', 'border', 'border-secondary', 'border-default'), + ], + }, + { + name: 'Accent', + slots: [ + s('Fill', 'fill', 'yellow', 'surface-brand'), + s('Label', 'text', 'navy', 'text-default'), + s('Border', 'border', 'none', 'border-default', { + note: 'Holds the edge in dark mode, where the gradient is nearly the surface color.', + }), + ], + }, + ], + }, + { + id: 'tag', + name: 'Tag', + summary: + 'Tags sit on surfaces and take veils for their states. A selection tag, a removable chip for an entered value, stays a dark chip: `text-subtle` behind a `text-inverse` label, like the Secondary badge.', + variants: [ + { + name: 'Read only', + slots: [ + s('Fill', 'fill', 'background-disabled', 'surface-sunken'), + s('Label', 'text', 'text', 'text-default'), + ], + }, + { + name: 'Selection', + slots: [ + s('Fill', 'fill', 'text-secondary', 'text-subtle'), + s('Label', 'text', 'background', 'text-inverse'), + s('Dismiss icon', 'icon', 'background', 'text-inverse'), + s('Dismiss hover', 'fill', 'secondary-hover', 'text-default'), + ], + }, + { + name: 'Navigation', + slots: [ + s('Fill', 'fill', 'background-current', 'surface-raised'), + s('Label', 'text', 'text', 'text-default'), + s('Border', 'border', 'border-secondary', 'border-default'), + s('Hover', 'veil', 'background-hover', 'state-hover'), + s( + 'Pressed', + 'veil', + 'secondary fill, background label', + 'state-pressed' + ), + ], + }, + { + name: 'Suggestion', + slots: [ + s('Fill', 'fill', 'background-current', 'surface-raised'), + s('Label', 'text', 'text', 'text-default'), + s('Border', 'border', 'primary', 'action-primary'), + s('Hover', 'veil', 'background-hover', 'state-hover'), + s( + 'Pressed', + 'veil', + 'primary fill, background label', + 'state-pressed' + ), + ], + }, + ], + shared: [ + s('Focus ring', 'border', 'primary', 'action-primary'), + s('Disabled fill', 'fill', 'background-disabled', 'action-disabled'), + knockout('text-disabled'), + s('Disabled border', 'border', 'border-disabled', 'border-default'), + ], + }, + + // ------------------------------------------------------------------ forms + { + id: 'field', + name: 'Input / TextArea / Select', + summary: + 'Fields are `surface-raised` with a `border-default` outline. Hover darkens the 1px outline to `border-strong`; focus is a 2px `action-primary` outline, so blue only ever means focus. Error is a 2px `text-error` outline.', + variants: [ + { + name: 'Rest', + slots: [ + s('Fill', 'fill', 'background', 'surface-raised'), + s('Value', 'text', 'text', 'text-default'), + s('Placeholder', 'text', 'browser default', 'text-subtle'), + s('Border', 'border', 'border-primary', 'border-default'), + s('Chevron', 'icon', 'text', 'text-subtle'), + ], + }, + { + name: 'Hover', + slots: [ + s('Fill', 'fill', 'background', 'surface-raised'), + s('Border', 'border', 'primary', 'border-strong'), + ], + }, + { + name: 'Focus', + slots: [ + s('Fill', 'fill', 'background', 'surface-raised'), + s('Border + ring', 'border', 'primary', 'action-primary'), + ], + }, + { + name: 'Error', + slots: [ + s('Fill', 'fill', 'background', 'surface-raised'), + s('Border + ring', 'border', 'feedback-error', 'text-error'), + s('Status icon', 'icon', 'feedback-error', 'text-error'), + ], + }, + { + name: 'Valid', + slots: [ + s('Fill', 'fill', 'background', 'surface-raised'), + s('Status icon', 'icon', 'feedback-success', 'text-success'), + ], + }, + { + name: 'Disabled', + slots: [ + s('Fill', 'fill', 'background-disabled', 'action-disabled'), + s('Value', 'text', 'text-disabled', 'text-subtle'), + s( + 'Border', + 'border', + 'currentColor (text-disabled)', + 'border-default' + ), + ], + }, + ], + }, + { + id: 'select-dropdown', + name: 'SelectDropdown', + summary: + 'The control follows the field. The menu is a raised surface, and options take state veils instead of opaque fills.', + variants: [ + { + name: 'Menu', + slots: [ + s('Fill', 'fill', 'background', 'surface-raised'), + s('Border', 'border', 'currentColor (text)', 'border-subtle'), + s('Option', 'text', 'text', 'text-default'), + s('Option subtitle', 'text', 'text-disabled', 'text-subtle'), + s('Group heading', 'text', 'text-disabled', 'text-subtle'), + s('Group divider', 'border', 'text-disabled', 'border-subtle'), + s('No options', 'text', 'text-secondary', 'text-subtle'), + s('Create option', 'text', 'primary', 'action-primary'), + ], + }, + { + name: 'Option states', + slots: [ + s('Fill', 'fill', 'background', 'surface-raised'), + s('Focused', 'veil', 'background-hover', 'state-hover'), + s('Selected', 'veil', 'background-selected', 'state-selected'), + s('Disabled', 'text', 'text-disabled', 'action-disabled'), + ], + }, + ], + shared: [ + s('Error top border', 'border', 'feedback-error', 'text-error'), + s('Multi-value chip', 'fill', 'text-secondary', 'surface-sunken', { + note: 'Follows the selection Tag.', + }), + ], + }, + { + id: 'checkbox', + name: 'Checkbox / Radio', + summary: + 'Unchecked boxes and rings are `border-default`; checked takes `action-primary`. A checked radio renders in the text color today because of a style override, and becomes blue.', + variants: [ + { + name: 'Checkbox checked', + slots: [ + s('Box fill', 'fill', 'primary', 'action-primary'), + s('Check mark', 'icon', 'background', 'text-inverse'), + ], + }, + { + name: 'Unchecked', + on: 'surface-raised', + slots: [ + s( + 'Box / ring', + 'border', + 'border-primary / currentColor', + 'border-default' + ), + s('Label', 'text', 'text', 'text-default'), + ], + }, + { + name: 'Radio checked', + on: 'surface-raised', + slots: [ + s('Dot', 'icon', 'currentColor (text)', 'action-primary'), + s('Ring', 'border', 'currentColor (text)', 'action-primary'), + ], + }, + { + name: 'Error', + on: 'surface-raised', + slots: [s('Ring / dot', 'icon', 'feedback-error', 'text-error')], + }, + ], + shared: [ + s('Hover / focus ring', 'border', 'primary', 'action-primary'), + s('Disabled fill', 'fill', 'text-disabled', 'action-disabled'), + s('Disabled check', 'icon', 'background', 'surface-raised', { + knockout: true, + note: 'Knockout, like a disabled button label.', + }), + s('Disabled label', 'text', 'text-disabled', 'action-disabled'), + ], + }, + { + id: 'toggle', + name: 'Toggle', + summary: + 'The checked track is `action-primary`. The unchecked track borrows `border-default` as a fill, the role guaranteed at 3:1 on every surface.', + variants: [ + { + name: 'On', + slots: [ + s('Track', 'fill', 'primary', 'action-primary'), + s('Knob', 'icon', 'white', 'surface-raised'), + ], + }, + { + name: 'Off', + slots: [ + s('Track', 'fill', 'text-disabled', 'border-default'), + s('Knob', 'icon', 'white', 'surface-raised'), + ], + }, + ], + shared: [ + s('Focus ring', 'border', 'primary', 'action-primary'), + s('Disabled', 'fill', 'opacity 0.5', 'action-disabled'), + ], + }, + { + id: 'form-group', + name: 'FormGroup', + summary: + 'Description text drops to `text-subtle`; the error message is `text-error` and keeps its icon.', + variants: [ + { + name: 'Labels and messages', + on: 'surface-raised', + slots: [ + s('Label', 'text', 'text', 'text-default'), + s('Description', 'text', 'text', 'text-subtle'), + s('Error', 'text', 'feedback-error', 'text-error'), + s('Disabled label', 'text', 'text-disabled', 'action-disabled'), + ], + }, + ], + }, + + // ------------------------------------------------------------- containers + { + id: 'card', + name: 'Card', + summary: + 'Two variants replace the literal color names: `default` and `brand`. Default is `surface-raised` (white in light mode); default, white, beige and navy all become it. Brand is the featured card on the `surface-brand` gradient; yellow and hyper become it. Every card has a `border-subtle` hairline. An interactive card adds the raised shadow at rest and the state veil with the raised-hover shadow on hover; the border stays the same. Auto-contrast goes away with the palette variants.', + variants: [ + { + name: 'Default', + legacyName: 'default, white, beige, navy', + slots: [ + s('Fill', 'fill', 'background / palette', 'surface-raised'), + s('Text', 'text', 'text / auto (Background)', 'text-default'), + ], + }, + { + name: 'Brand', + legacyName: 'yellow, hyper', + slots: [ + s('Fill', 'fill', 'yellow / hyper', 'surface-brand', { + note: 'The featured card, on the brand gradient.', + }), + s('Text', 'text', 'auto (Background)', 'text-default'), + ], + }, + { + name: 'Interactive', + slots: [ + s('Fill', 'fill', 'background', 'surface-raised', { + note: 'Either variant can be interactive; a brand card keeps its `surface-brand` fill.', + }), + s('Hover', 'veil', 'shadow-primary (offset shadow)', 'state-hover', { + note: 'Hover composes the veil and the raised-hover shadow. The shadow and veil show the card is interactive, so the border does not change.', + }), + s('Shadow (key)', 'veil', 'shadow-primary', 'shadow-key', { + note: 'Raised depth at rest, raised-hover on hover.', + }), + s('Shadow (ambient)', 'veil', 'shadow-primary', 'shadow-ambient'), + ], + }, + ], + shared: [s('Border', 'border', 'border-primary', 'border-subtle')], + }, + { + id: 'disclosure', + name: 'Disclosure', + summary: + 'Variants map onto the surface stack; the header gets state veils, and focus is a ring rather than a fill.', + variants: [ + { + name: 'Default', + slots: [ + s('Fill', 'fill', 'background', 'surface-raised'), + s('Header text', 'text', 'text', 'text-default'), + s('Overline', 'text', 'text-secondary', 'text-subtle'), + ], + }, + { + name: 'Subtle', + slots: [ + s('Fill', 'fill', 'background-selected', 'surface-sunken'), + s('Header text', 'text', 'text', 'text-default'), + ], + }, + { + name: 'Transparent', + on: 'surface-default', + slots: [ + s('Fill', 'fill', 'background-current', INHERIT), + s('Header text', 'text', 'text', 'text-default'), + ], + }, + ], + shared: [ + s('Border', 'border', 'border-primary', 'border-default'), + s('Header hover', 'veil', 'background-hover', 'state-hover'), + s( + 'Header focus', + 'border', + 'background-selected + primary ring', + 'action-primary' + ), + s('Panel fill', 'fill', 'background-selected', 'surface-sunken'), + s('Disabled header', 'fill', 'background-disabled', 'action-disabled'), + knockout('text-disabled'), + ], + }, + { + id: 'list', + name: 'List / DataTable / DataList', + summary: + 'Rows sit on `surface-raised` with `border-subtle` rules. Hover and selection are veils, and selected rows get a tint they lack today. Table stripes move to `surface-sunken`, so a stripe never reads as a selection.', + variants: [ + { + name: 'Row', + slots: [ + s('Fill', 'fill', 'background', 'surface-raised'), + s('Text', 'text', 'text', 'text-default'), + s('Row rule', 'border', 'border-primary', 'border-subtle'), + s('Hover', 'veil', 'background-hover', 'state-hover'), + s('Selected', 'veil', 'none (checkbox only)', 'state-selected'), + s( + 'Focus', + 'border', + 'primary + background-selected', + 'action-primary' + ), + ], + }, + { + name: 'Header', + slots: [ + s('Fill', 'fill', 'background-current', 'surface-raised'), + s('Text', 'text', 'text', 'text-default'), + s('Rule', 'border', 'border-primary (2px)', 'border-default'), + s('Sort icon', 'icon', 'text-disabled', 'text-subtle'), + s('Sort icon active', 'icon', 'text-accent', 'text-default'), + s('Filter set', 'icon', 'primary', 'action-primary'), + ], + }, + { + name: 'Table stripe', + slots: [ + s('Fill', 'fill', 'background-selected', 'surface-sunken'), + s('Text', 'text', 'text', 'text-default'), + ], + }, + ], + shared: [ + s( + 'Scroll shadow', + 'border', + 'background-selected → border-tertiary', + 'border-subtle' + ), + s('Expand chevron', 'icon', 'secondary', 'action-neutral'), + ], + }, + + // --------------------------------------------------------------- overlays + { + id: 'modal', + name: 'Modal / Dialog', + summary: + 'The scrim becomes `shadow-scrim`; the panel is `surface-raised` with a hairline. Buttons follow their own mappings.', + variants: [ + { + name: 'Panel', + slots: [ + s('Fill', 'fill', 'background', 'surface-raised'), + s('Title / body', 'text', 'text', 'text-default'), + s('Border', 'border', 'border-primary', 'border-subtle'), + s('Close icon', 'icon', 'secondary', 'action-neutral'), + ], + }, + ], + shared: [ + s('Scrim', 'veil', 'navy-600', 'shadow-scrim'), + s('Focus ring', 'border', 'primary', 'action-primary'), + s('Pattern', 'icon', 'currentColor (text)', 'border-subtle', { + note: 'Codecademy theme only.', + }), + ], + }, + { + id: 'flyout', + name: 'Flyout / Drawer', + summary: + 'Same as Modal: `shadow-scrim` behind, `surface-raised` panel with a `border-subtle` inner edge.', + variants: [ + { + name: 'Panel', + slots: [ + s( + 'Fill', + 'fill', + 'background (auto) / background-current', + 'surface-raised' + ), + s('Text', 'text', 'auto (Background)', 'text-default'), + s('Inner edge', 'border', 'none', 'border-subtle'), + s('Close icon', 'icon', 'secondary', 'action-neutral'), + ], + }, + ], + shared: [ + s('Scrim', 'veil', 'navy-600', 'shadow-scrim'), + s('Focus ring', 'border', 'primary', 'action-primary'), + ], + }, + { + id: 'toast', + name: 'Toast', + summary: + 'A raised panel with a hairline edge; its shadow separates it from whatever it floats over.', + variants: [ + { + name: 'Panel', + slots: [ + s('Fill', 'fill', 'background', 'surface-raised'), + s('Text', 'text', 'text', 'text-default'), + s('Border', 'border', 'border-primary', 'border-subtle'), + s('Close icon', 'icon', 'secondary', 'action-neutral'), + ], + }, + ], + shared: [ + s('Offset pattern', 'icon', 'currentColor (text)', 'border-subtle', { + note: 'Codecademy theme only.', + }), + ], + }, + { + id: 'tooltip', + name: 'ToolTip / InfoTip / Popover', + summary: + 'Every floating surface is `surface-raised` with a `border-subtle` edge, beak included, and its shadow does the separating. A floating Alert is the exception: its edge takes its status ink. `background-contrast` goes away.', + variants: [ + { + name: 'Tip body', + legacyName: 'ToolTip, InfoTip, Popover secondary', + slots: [ + s('Fill', 'fill', 'background-contrast', 'surface-raised'), + s('Text', 'text', 'text', 'text-default'), + s('Border + beak', 'border', 'border-primary', 'border-subtle'), + ], + }, + { + name: 'Popover', + legacyName: 'Popover primary, Coachmark', + slots: [ + s('Fill', 'fill', 'background', 'surface-raised'), + s('Text', 'text', 'text', 'text-default'), + s('Border + beak', 'border', 'border-primary', 'border-subtle'), + ], + }, + { + name: 'InfoTip button', + on: 'surface-raised', + slots: [ + s('Icon', 'icon', 'secondary', 'action-neutral'), + s('Hover', 'veil', 'background-hover', 'state-hover'), + s('Open', 'veil', 'background-selected', 'state-selected'), + s('Disabled', 'icon', 'text-disabled', 'action-disabled'), + ], + }, + ], + shared: [s('Target focus ring', 'border', 'primary', 'action-primary')], + }, + { + id: 'menu', + name: 'Menu', + summary: + 'Items hover with a veil instead of turning blue; the active nav item is `state-selected` with an `action-primary` bar.', + variants: [ + { + name: 'List', + slots: [ + s('Fill', 'fill', 'background', 'surface-raised'), + s('Item', 'text', 'text', 'text-default'), + s('Border', 'border', 'border-primary', 'border-subtle'), + s('Separator', 'border', 'text-disabled', 'border-subtle'), + s('Disabled item', 'text', 'text-disabled', 'action-disabled'), + ], + }, + { + name: 'Item states', + slots: [ + s('Fill', 'fill', 'background', 'surface-raised'), + s('Hover', 'veil', 'primary (text color)', 'state-hover'), + s('Active (fixed)', 'veil', 'background-selected', 'state-selected'), + s('Active bar', 'border', 'secondary', 'action-primary'), + s('Focus ring', 'border', 'primary', 'action-primary'), + ], + }, + ], + }, + { + id: 'tabs', + name: 'Tabs', + summary: + 'Standard tabs: unselected labels `text-subtle`, selected `text-default` with an `action-primary` indicator, hover a veil. Block tabs are a sunken track with a raised selected tab, in the page’s own mode.', + variants: [ + { + name: 'Standard', + on: 'surface-default', + slots: [ + s('Label', 'text', 'text', 'text-subtle'), + s('Selected label', 'text', 'text (bold)', 'text-default'), + s('Indicator', 'border', 'primary', 'action-primary'), + s('Baseline', 'border', 'text / border-primary', 'border-subtle'), + s('Hover', 'veil', 'background-selected', 'state-hover'), + ], + }, + { + name: 'Block', + slots: [ + s( + 'Container', + 'fill', + 'background-contrast in navy-800 (dark)', + 'surface-sunken' + ), + s('Selected tab', 'fill', 'background', 'surface-raised'), + s('Label', 'text', 'auto (Background)', 'text-default'), + ], + }, + ], + shared: [s('Focus ring', 'border', 'primary', 'action-primary')], + }, + { + id: 'pagination', + name: 'Pagination', + summary: + 'Selected page is `state-selected` plus `border-strong`; hover is a veil.', + variants: [ + { + name: 'Page buttons', + on: 'surface-default', + slots: [ + s('Label', 'text', 'text', 'text-default'), + s('Hover (stroke)', 'text', 'primary', 'action-primary'), + s('Hover (text)', 'veil', 'background-selected', 'state-hover'), + s('Selected', 'veil', 'background-selected', 'state-selected'), + s( + 'Selected border', + 'border', + 'currentColor (text)', + 'border-strong' + ), + s('Focus ring', 'border', 'border-primary', 'action-primary'), + s('Disabled', 'text', 'text-disabled', 'action-disabled'), + ], + }, + ], + }, + { + id: 'breadcrumbs', + name: 'Breadcrumbs', + summary: + 'Links follow the interface Anchor; the separator is `text-subtle`.', + variants: [ + { + name: 'Trail', + on: 'surface-default', + slots: [ + s('Link', 'text', 'text', 'text-default'), + s('Link hover', 'text', 'primary', 'action-primary'), + s('Current', 'text', 'inherit', 'text-default'), + s('Separator', 'text', 'inherit', 'text-subtle'), + ], + }, + ], + }, + + // --------------------------------------------------------------- progress + { + id: 'progress', + name: 'ProgressBar / Loading', + summary: + 'Every bar is `text-default` on a sunken track, blue and yellow included, and the navy edge goes away. The label is two-tone: `text-inverse` where it sits on the bar, `text-default` where it sits on the track.', + variants: [ + { + name: 'Bar', + slots: [ + s('Bar', 'fill', 'text', 'text-default'), + s('Bar label', 'text', 'background', 'text-inverse', { + note: 'Clipped to the bar.', + }), + ], + }, + { + name: 'Blue, Yellow', + slots: [ + s('Bar', 'fill', 'blue / yellow', 'text-default', { + note: 'The same as the default bar.', + }), + s('Bar label', 'text', 'white / navy', 'text-inverse'), + ], + }, + { + name: 'Track', + slots: [ + s('Track', 'fill', 'none (currentColor border)', 'surface-sunken'), + s('Track border', 'border', 'currentColor', 'border-default'), + s('Track label', 'text', 'background', 'text-default', { + note: 'Clipped to the track, so a label crossing the bar edge clears on both sides.', + }), + ], + }, + { + name: 'Shimmer', + slots: [ + s('Base', 'fill', 'background-disabled @ 0.5', 'surface-sunken'), + s('Sweep', 'veil', 'rgba black/white @ 0.2', 'surface-raised'), + ], + }, + ], + shared: [ + s('Bar edge', 'border', 'navy', INHERIT, { note: 'Dropped.' }), + s('Spinner', 'icon', 'currentColor', INHERIT), + ], + }, +]; + +export const statusFor = (c: ComponentMapping): MappingStatus => + c.open && c.open.length > 0 ? 'open' : 'mapped'; + +// ----------------------------------------------------------- measurement + +const PEAK_ALPHA = FALLOFF[0][1]; + +/* + * The color a role puts on screen in a mode, for contrast purposes. Veils and + * shadows have no color of their own and aren't measured. A gradient is + * measured at its conservative bound: every circle at peak alpha, + * stacked bottom-to-top over the flat base (circles are authored top-first). + */ +export const roleColor = ( + system: ColorSystem, + token: string, + mode: Mode +): CuloriColor | undefined => { + const role = roleFor(token); + if (!role || role.group === 'state' || role.group === 'shadow') { + return undefined; + } + const flat = system.roles[mode][token]; + const base: CuloriColor = { mode: 'oklch', l: flat.l, c: flat.c, h: flat.h }; + const gradient = system.gradients[mode][token]; + if (!gradient) return base; + return [...gradient.circles] + .reverse() + .reduce( + (acc, circle) => + composite(system.palette[paletteKey(circle.ref)], PEAK_ALPHA, acc), + base + ); +}; + +export interface SlotCheck { + component: string; + variant: string; + slot: string; + fg: string; + bg: string; + mode: Mode; + ratio: number; + floor: number; +} + +/* + * Every text and icon slot measured against every fill in its variant (rest, + * hover and pressed are all fills a label has to read on), or against the + * variant's `on` surface when it has no fill, in both modes. Text is held to + * 4.5:1 and icons to 3:1. Shared slots aren't measured: they sit on whichever + * fill the variant has, and the role layer already pairs them. + */ +export const componentChecks = ( + system: ColorSystem, + c: ComponentMapping +): SlotCheck[] => { + const out: SlotCheck[] = []; + for (const variant of c.variants) { + const grounds = variant.slots + .filter((s) => s.kind === 'fill' && s.role !== INHERIT) + .map((s) => s.role); + if (grounds.length === 0 && variant.on) grounds.push(variant.on); + for (const slot of variant.slots) { + if (slot.kind !== 'text' && slot.kind !== 'icon') continue; + for (const ground of grounds) { + for (const mode of MODES) { + const fg = roleColor(system, slot.role, mode); + const bg = roleColor(system, ground, mode); + if (!fg || !bg) continue; + out.push({ + component: c.id, + variant: variant.name, + slot: slot.slot, + fg: slot.role, + bg: ground, + mode, + ratio: wcagContrast(fg, bg), + floor: slot.kind === 'text' ? 4.5 : 3, + }); + } + } + } + } + return out; +}; + +/* + * Exempt from a contrast floor: anything on a disabled fill, and a disabled + * label drawn in the disabled color, which WCAG exempts as an inactive control. + */ +export const isExempt = (check: SlotCheck): boolean => + check.bg === 'action-disabled' || check.fg === 'action-disabled'; + +/* The migration map's checks, for any color system. */ +export const validateMigration = (system: ColorSystem): Check[] => { + const legacy: string[] = [...LEGACY_TOKENS]; + const mapped = TOKEN_MAP.map((m) => m.legacy as string); + const missing = legacy.filter((t) => !mapped.includes(t)); + const twice = mapped.filter((t, i) => mapped.indexOf(t) !== i); + + const badTargets = TOKEN_MAP.flatMap((m) => [ + ...m.to.filter((t) => !roleFor(t)).map((t) => `${m.legacy} → ${t}`), + ...(m.kind !== 'retire' && m.kind !== 'pending' && m.to.length === 0 + ? [`${m.legacy} has no target`] + : []), + ...(m.kind === 'split' && m.to.length < 2 + ? [`${m.legacy} is a split with one target`] + : []), + ]); + + const badSlots = COMPONENTS.flatMap((c) => + [...c.variants, { name: 'shared', slots: c.shared ?? [] }].flatMap((v) => + v.slots + .filter((slot) => slot.role !== INHERIT && !roleFor(slot.role)) + .map((slot) => `${c.id}/${v.name}/${slot.slot} → ${slot.role}`) + ) + ); + + const measured = COMPONENTS.flatMap((c) => componentChecks(system, c)).filter( + (check) => !isExempt(check) + ); + const worst = measured.reduce( + (w, check) => + !w || check.ratio - check.floor < w.ratio - w.floor ? check : w, + undefined + ); + const failing = measured.filter((check) => check.ratio < check.floor); + + /* Structural checks score the offenders as a negative count, so 0 is a pass. */ + const count = (bad: string[]) => -bad.length; + return [ + { + name: `every one of ${legacy.length} Gamut aliases is mapped exactly once`, + pass: missing.length + twice.length === 0, + worst: count([...missing, ...twice]), + floor: 0, + at: + [ + ...missing.map((t) => `unmapped ${t}`), + ...twice.map((t) => `twice ${t}`), + ].join(', ') || '—', + }, + { + name: 'every token-map target is a real role, and every split has a choice', + pass: badTargets.length === 0, + worst: count(badTargets), + floor: 0, + at: badTargets.join(', ') || '—', + }, + { + name: `every slot in ${COMPONENTS.length} components maps to a real role`, + pass: badSlots.length === 0, + worst: count(badSlots), + floor: 0, + at: badSlots.join(', ') || '—', + }, + { + name: `every mapped text and icon slot clears its tier (${measured.length} pairings, margin over floor)`, + pass: failing.length === 0, + worst: worst ? worst.ratio - worst.floor : 0, + floor: 0, + at: failing.length + ? failing + .map( + (f) => + `${f.component} ${f.variant} ${f.slot}: ${f.fg} on ${f.bg} (${ + f.mode + }) ${f.ratio.toFixed(2)} < ${f.floor}` + ) + .join('; ') + : worst + ? `${worst.component} ${worst.variant} ${worst.slot}: ${worst.fg} on ${worst.bg} (${worst.mode})` + : '—', + }, + ]; +}; diff --git a/packages/gamut-brand/src/probe.ts b/packages/gamut-brand/src/probe.ts new file mode 100644 index 0000000000..895e485915 --- /dev/null +++ b/packages/gamut-brand/src/probe.ts @@ -0,0 +1,89 @@ +import type { Color as CuloriColor } from 'culori'; +import { + differenceCiede2000, + filterDeficiencyDeuter, + filterDeficiencyProt, + filterDeficiencyTrit, +} from 'culori'; + +import type { BrandInput, Tier } from './brand'; +import { validateMigration } from './migration'; +import type { Grade } from './ramp'; +import { createColorSystem } from './system'; +import { validate } from './validate'; + +/* + * What a tenant's hues do to the system, before we ship them. + * + * Contrast is inherited by construction — the probe re-runs `validate` to + * prove it, not to discover it. What isn't inherited is anything about hues + * relative to each other. The status trio is checked here; categorical + * separation isn't yet, because it needs a categorical solver. + */ + +/* Below this, the status trio can't be told apart even with an icon. */ +export const STATUS_FLOOR = 2.3; + +const VISION: [string, (c: CuloriColor) => CuloriColor][] = [ + ['normal', (c) => c], + ['protanopia', filterDeficiencyProt(1)], + ['deuteranopia', filterDeficiencyDeuter(1)], + ['tritanopia', filterDeficiencyTrit(1)], +]; + +const deltaE = differenceCiede2000(); + +export interface TenantProbe { + brand: string; + tier: Tier; + derived: string[]; + contrastHolds: boolean; + failedChecks: string[]; + statusSeparation: number; + statusWorst: string; + statusPasses: boolean; +} + +/* Worst pairwise ΔE2000 of success / warning / danger at one grade. */ +export const statusTrioSeparation = ( + input: BrandInput, + grade: Grade = 200 +): { min: number; at: string } => { + const { ramps } = createColorSystem(input); + const trio = (['success', 'warning', 'danger'] as const).map((slot) => ({ + slot, + color: { mode: 'oklch', ...ramps[slot][grade] } as CuloriColor, + })); + let min = Infinity; + let at = ''; + for (let i = 0; i < trio.length; i += 1) { + for (let j = i + 1; j < trio.length; j += 1) { + for (const [vision, sim] of VISION) { + const d = deltaE(sim(trio[i].color), sim(trio[j].color)); + if (d < min) { + min = d; + at = `${trio[i].slot}/${trio[j].slot}, ${vision}`; + } + } + } + } + return { min, at }; +}; + +export const probeTenant = (input: BrandInput): TenantProbe => { + const system = createColorSystem(input); + const failed = [...validate(system), ...validateMigration(system)].filter( + (c) => !c.pass + ); + const status = statusTrioSeparation(input); + return { + brand: input.name, + tier: system.brand.tier, + derived: Object.keys(system.brand.derived), + contrastHolds: failed.length === 0, + failedChecks: failed.map((c) => c.name), + statusSeparation: status.min, + statusWorst: status.at, + statusPasses: status.min >= STATUS_FLOOR, + }; +}; diff --git a/packages/gamut-brand/src/ramp.ts b/packages/gamut-brand/src/ramp.ts new file mode 100644 index 0000000000..aae73d3500 --- /dev/null +++ b/packages/gamut-brand/src/ramp.ts @@ -0,0 +1,140 @@ +import { + clampChroma, + converter, + formatHex, + inGamut, + wcagContrast, +} from 'culori'; + +/* + * The ramp generator. One input per family: its hue. Lightness is fixed per grade and shared by + * every family, so two colors at the same grade read as equally light. Chroma + * is a percentage of the most sRGB can show at that lightness and hue, so the + * same curve works for any hue a tenant hands us. Hex falls out last and is + * never read back as an input. + */ + +const toRgb = converter('rgb'); +const rgbInGamut = inGamut('rgb'); + +export const GRADES = [ + 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, +] as const; + +export type Grade = (typeof GRADES)[number]; + +/* Which grade mirrors which: light-mode grade → its dark-mode partner. */ +export const MIRROR: Record = { + 100: 1100, + 200: 1000, + 300: 900, + 400: 800, + 500: 700, + 600: 600, + 700: 500, + 800: 400, + 900: 300, + 1000: 200, + 1100: 100, +}; + +/* Authored, not derived. Shared by every family at each grade. */ +export const FIXED_LIGHTNESS: Record = { + 100: 0.9862, + 200: 0.9364, + 300: 0.8497, + 400: 0.7628, + 500: 0.6707, + 600: 0.5835, + 700: 0.4972, + 800: 0.4115, + 900: 0.3346, + 1000: 0.2499, + 1100: 0.1925, +}; + +/* + * Chroma as a share of the in-gamut maximum at each grade. Peaks at 600, the + * UI grade in both modes. + */ +export const CHROMA_CURVE: Record = { + 100: 1.0, + 200: 0.4, + 300: 0.55, + 400: 0.7, + 500: 0.88, + 600: 1.0, + 700: 0.95, + 800: 0.9, + 900: 0.88, + 1000: 0.84, + 1100: 0.8, +}; + +/* Neutral families keep their hue but take a fifth of the curve. */ +export const NEUTRAL_CHROMA_MULTIPLIER = 0.2; + +/* + * Verified by sweep, not solved for: any two grades 500 apart clear 3:1 + * (non-text) and any two 600 apart clear 4.5:1 (text), at every hue. + */ +export const SEPARATION_GUARANTEE = { + nonText: { minGap: 500, floor: 3 }, + text: { minGap: 600, floor: 4.5 }, +} as const; + +export interface Rung { + grade: Grade; + l: number; + c: number; + h: number; + /* Generated. Never an input. */ + hex: string; +} + +export type Ramp = Record; + +/* Floor to 4 decimals, so a rounded chroma can never land outside the gamut. */ +const floor4 = (x: number): number => Math.floor(x * 1e4) / 1e4; + +const oklch = (l: number, c: number, h: number) => + ({ mode: 'oklch', l, c, h } as const); + +export const oklchToHex = (l: number, c: number, h: number): string => + formatHex(clampChroma(toRgb(oklch(l, c, h)), 'rgb')); + +/* + * The largest chroma sRGB can display at this lightness and hue. Bisection, + * because the sRGB boundary in OKLCH has no closed form. + */ +export const maxChroma = (l: number, h: number): number => { + let lo = 0; + let hi = 0.4; + for (let i = 0; i < 40; i += 1) { + const mid = (lo + hi) / 2; + if (rgbInGamut(oklch(l, mid, h))) lo = mid; + else hi = mid; + } + return lo; +}; + +export const rungFor = (h: number, grade: Grade, neutral: boolean): Rung => { + const l = FIXED_LIGHTNESS[grade]; + const pct = CHROMA_CURVE[grade] * (neutral ? NEUTRAL_CHROMA_MULTIPLIER : 1); + const c = floor4(maxChroma(l, h) * pct); + return { grade, l, c, h, hex: oklchToHex(l, c, h) }; +}; + +export const generateRamp = (hue: number, neutral: boolean): Ramp => + Object.fromEntries( + GRADES.map((grade) => [grade, rungFor(hue, grade, neutral)]) + ) as Ramp; + +/* + * WCAG contrast measured on the OKLCH values, not the rounded hex. 8-bit hex + * can't resolve the differences the ladder is built on. + */ +export const contrast = ( + a: Pick, + b: Pick +): number => wcagContrast(oklch(a.l, a.c, a.h), oklch(b.l, b.c, b.h)); diff --git a/packages/gamut-brand/src/roles.ts b/packages/gamut-brand/src/roles.ts new file mode 100644 index 0000000000..ef50ebbdbf --- /dev/null +++ b/packages/gamut-brand/src/roles.ts @@ -0,0 +1,507 @@ +import type { Slot } from './brand'; +import type { Grade } from './ramp'; + +/* + * The role layer, as data: each role's palette color in both modes, the + * contrast floor it's held to, and what it's held against. Roles name slots + * (primary, accent, danger), never hues, so one mapping fits every brand. + */ + +export type Mode = 'light' | 'dark'; +export const MODES: readonly Mode[] = ['light', 'dark']; + +export type Boundary = 'white' | 'black'; + +export type Ref = { slot: Slot; grade: Grade } | Boundary; + +export interface RoleValue { + ref: Ref; + /* Set for the translucent roles: state veils and shadows. */ + alpha?: number; +} + +export interface GradientCircle { + ref: Ref; + /* Radius as a multiple of the element's width. */ + radius: number; + /* CSS background-position for the circle's centre. */ + at: string; +} + +export interface Gradient { + circles: GradientCircle[]; +} + +export type RoleGroup = + | 'surface' + | 'text' + | 'border' + | 'action' + | 'state' + | 'viz' + | 'shadow'; + +export interface Role { + token: string; + group: RoleGroup; + light: RoleValue; + dark: RoleValue; + usage: string; + /* Minimum WCAG contrast against every role in `against`. */ + floor?: number; + against?: string[]; + gradient?: Record; +} + +const at = (slot: Slot, grade: Grade): RoleValue => ({ + ref: { slot, grade }, +}); +const boundary = (ref: Boundary): RoleValue => ({ ref }); +const veil = (slot: Slot, grade: Grade, alpha: number): RoleValue => ({ + ref: { slot, grade }, + alpha, +}); + +/* + * Veil strengths. The binding constraint is `border-default` against the + * veiled fill it encloses, at 3:1. + */ +export const STATE_ALPHA = { + 'state-hover': 0.06, + 'state-pressed': 0.09, + 'state-selected': 0.08, +} as const; + +/* + * Shadow strengths per mode (still draft in design's spec). Light ambient + * matches Percipio's shipped resting shadow; key + * sits a step above it. Dark is twice light, as a dark surface has less light + * to remove. The scrim is mode-independent. + */ +export const SHADOW_ALPHA = { + 'shadow-key': { light: 0.16, dark: 0.32 }, + 'shadow-ambient': { light: 0.12, dark: 0.24 }, + 'shadow-scrim': { light: 0.6, dark: 0.6 }, +} as const; + +/* The six-stop falloff every gradient circle uses: [position %, alpha]. */ +export const FALLOFF: [number, number][] = [ + [0, 0.45], + [16, 0.43], + [36, 0.24], + [56, 0.11], + [76, 0.04], + [100, 0], +]; + +const ELEVATION = ['surface-sunken', 'surface-default', 'surface-raised']; +const ALL_SURFACES = [ + ...ELEVATION, + 'surface-success', + 'surface-warning', + 'surface-error', +]; + +const AA_TEXT = 4.5; +const AA_NON_TEXT = 3; + +export const ROLES: Role[] = [ + /* surfaces */ + { + token: 'surface-sunken', + group: 'surface', + light: at('neutral-light', 200), + dark: boundary('black'), + usage: 'Wells, insets, code blocks — anything recessed into the page.', + }, + { + token: 'surface-default', + group: 'surface', + light: at('neutral-light', 100), + dark: at('neutral-dark', 1100), + usage: 'The page background — the base every other surface elevates from.', + }, + { + token: 'surface-raised', + group: 'surface', + light: boundary('white'), + dark: at('neutral-dark', 1000), + usage: + 'The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits.', + }, + { + token: 'surface-success', + group: 'surface', + light: at('success', 200), + dark: at('success', 1000), + usage: 'The success tint behind an alert or inline confirmation.', + }, + { + token: 'surface-warning', + group: 'surface', + light: at('warning', 200), + dark: at('warning', 1000), + usage: 'The warning tint behind an alert or inline caution.', + }, + { + token: 'surface-error', + group: 'surface', + light: at('danger', 200), + dark: at('danger', 1000), + usage: 'The error tint behind an alert or inline validation message.', + }, + { + token: 'surface-brand', + group: 'surface', + light: at('neutral-light', 100), + dark: at('neutral-dark', 1100), + usage: + 'The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand.', + gradient: { + light: { + circles: [ + { ref: { slot: 'accent', grade: 300 }, radius: 0.85, at: '100% -5%' }, + { ref: { slot: 'primary', grade: 300 }, radius: 1.21, at: '5% 100%' }, + ], + }, + dark: { + circles: [ + { ref: { slot: 'accent', grade: 900 }, radius: 0.85, at: '100% -5%' }, + { ref: { slot: 'primary', grade: 900 }, radius: 1.21, at: '5% 100%' }, + ], + }, + }, + }, + { + token: 'surface-brand-subtle', + group: 'surface', + light: at('neutral-light', 100), + dark: at('neutral-dark', 1100), + usage: + 'The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle.', + gradient: { + light: { + circles: [ + { + ref: { slot: 'primary', grade: 300 }, + radius: 0.78, + at: '50% -10%', + }, + ], + }, + dark: { + circles: [ + { + ref: { slot: 'primary', grade: 900 }, + radius: 0.78, + at: '50% -10%', + }, + ], + }, + }, + }, + + /* text */ + { + token: 'text-default', + group: 'text', + light: at('neutral-dark', 1000), + dark: at('neutral-light', 200), + usage: 'Body copy and headings.', + floor: AA_TEXT, + against: ALL_SURFACES, + }, + { + token: 'text-subtle', + group: 'text', + light: at('neutral-dark', 800), + dark: at('neutral-light', 400), + usage: 'Secondary text — descriptions, metadata, helper copy.', + floor: AA_TEXT, + against: ALL_SURFACES, + }, + { + token: 'text-inverse', + group: 'text', + light: at('neutral-light', 200), + dark: at('neutral-dark', 1000), + usage: 'Labels on a filled control — action-primary, -neutral, -danger.', + floor: AA_TEXT, + against: ['action-primary', 'action-neutral', 'action-danger'], + }, + { + token: 'text-success', + group: 'text', + light: at('success', 900), + dark: at('success', 300), + usage: 'Success text and icons.', + floor: AA_TEXT, + against: [...ELEVATION, 'surface-success'], + }, + { + token: 'text-warning', + group: 'text', + light: at('warning', 900), + dark: at('warning', 300), + usage: 'Warning text and icons. Always with an icon.', + floor: AA_TEXT, + against: [...ELEVATION, 'surface-warning'], + }, + { + token: 'text-error', + group: 'text', + light: at('danger', 900), + dark: at('danger', 300), + usage: 'Error text and icons.', + floor: AA_TEXT, + against: [...ELEVATION, 'surface-error'], + }, + + /* borders */ + { + token: 'border-strong', + group: 'border', + light: at('neutral-dark', 1100), + dark: at('neutral-light', 100), + usage: 'Emphasis — an active field, a selected row.', + floor: AA_NON_TEXT, + against: ALL_SURFACES, + }, + { + token: 'border-default', + group: 'border', + light: at('neutral-dark', 600), + dark: at('neutral-light', 600), + usage: 'Any border that carries meaning — control outlines, field borders.', + floor: AA_NON_TEXT, + against: ALL_SURFACES, + }, + { + token: 'border-subtle', + group: 'border', + light: at('neutral-dark', 300), + dark: at('neutral-light', 900), + usage: 'Decorative hairlines — table rules, dividers. Contrast-exempt.', + }, + + /* actions */ + ...(['primary', 'danger'] as const).flatMap((slot): Role[] => { + const token = slot === 'primary' ? 'action-primary' : 'action-danger'; + return [ + { + token, + group: 'action', + light: at(slot, 800), + dark: at(slot, 400), + usage: + slot === 'primary' + ? 'Primary interactive fills, strokes, links and focus rings.' + : 'Destructive actions — delete, remove, revoke.', + floor: AA_NON_TEXT, + against: ELEVATION, + }, + { + token: `${token}-hover`, + group: 'action', + light: at(slot, 900), + dark: at(slot, 300), + usage: `Hover for ${token}.`, + floor: AA_NON_TEXT, + against: ELEVATION, + }, + { + token: `${token}-pressed`, + group: 'action', + light: at(slot, 1000), + dark: at(slot, 200), + usage: `Pressed for ${token}.`, + floor: AA_NON_TEXT, + against: ELEVATION, + }, + ]; + }), + { + token: 'action-neutral', + group: 'action', + light: at('neutral-dark', 1000), + dark: at('neutral-light', 200), + usage: 'The neutral action fill. Button variant="secondary" resolves here.', + floor: AA_NON_TEXT, + against: ELEVATION, + }, + { + token: 'action-neutral-hover', + group: 'action', + light: at('neutral-dark', 1100), + dark: at('neutral-light', 100), + usage: 'Hover for action-neutral.', + floor: AA_NON_TEXT, + against: ELEVATION, + }, + { + token: 'action-neutral-pressed', + group: 'action', + light: boundary('black'), + dark: boundary('white'), + usage: 'Pressed for action-neutral.', + floor: AA_NON_TEXT, + against: ELEVATION, + }, + { + token: 'action-disabled', + group: 'action', + light: at('neutral-light', 400), + dark: at('neutral-dark', 800), + usage: + 'The disabled fill for every action variant. Contrast-exempt (inactive control).', + }, + + /* state veils: one hue, three strengths */ + ...(Object.keys(STATE_ALPHA) as (keyof typeof STATE_ALPHA)[]).map( + (token): Role => ({ + token, + group: 'state', + light: veil('primary', 600, STATE_ALPHA[token]), + dark: veil('primary', 600, STATE_ALPHA[token]), + usage: { + 'state-hover': 'Rest → hover on rows, tabs, chips, menu items.', + 'state-pressed': 'Hover → pressed on the same elements.', + 'state-selected': 'The persistent "this one is chosen" tint.', + }[token], + }) + ), + + /* + * Categorical viz. The slot order was solved for Skillsoft's hues and is + * fixed: there's no categorical solver here yet, so another brand gets these + * slots and grades unsolved. + */ + ...( + [ + [at('primary', 800), at('primary', 500)], + [at('accent', 700), at('accent', 500)], + [at('warning', 700), at('warning', 400)], + [at('accent-alt', 600), at('accent-alt', 600)], + [at('success', 600), at('success', 300)], + [at('danger', 600), at('danger', 600)], + [at('neutral-dark', 1000), at('neutral-dark', 500)], + [at('neutral-light', 600), at('neutral-light', 600)], + ] as [RoleValue, RoleValue][] + ).map( + ([light, dark], i): Role => ({ + token: `viz-${i + 1}`, + group: 'viz', + light, + dark, + usage: `Categorical series ${i + 1}.`, + floor: AA_NON_TEXT, + against: ['surface-default'], + }) + ), + ...( + [ + [600, 600], + [700, 500], + [800, 400], + [900, 300], + ] as [Grade, Grade][] + ).map( + ([light, dark], i): Role => ({ + token: `viz-seq-${i + 1}`, + group: 'viz', + light: at('primary', light), + dark: at('primary', dark), + usage: `Sequential step ${i + 1}, low to high.`, + floor: AA_NON_TEXT, + against: ['surface-default'], + }) + ), + + /* + * Shadows: depth colors, all alpha and contrast-exempt. Key and ambient are + * the two box-shadow layers every elevation composes; depth comes from the + * elevation's geometry, so the color is the same at every level. The scrim + * is a fill for the element behind a modal, not a box-shadow color. + */ + { + token: 'shadow-key', + group: 'shadow', + light: veil('neutral-dark', 1100, SHADOW_ALPHA['shadow-key'].light), + dark: { ref: 'black', alpha: SHADOW_ALPHA['shadow-key'].dark }, + usage: + 'The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur.', + }, + { + token: 'shadow-ambient', + group: 'shadow', + light: veil('neutral-dark', 1100, SHADOW_ALPHA['shadow-ambient'].light), + dark: { ref: 'black', alpha: SHADOW_ALPHA['shadow-ambient'].dark }, + usage: + 'The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff.', + }, + { + token: 'shadow-scrim', + group: 'shadow', + light: { ref: 'black', alpha: SHADOW_ALPHA['shadow-scrim'].light }, + dark: { ref: 'black', alpha: SHADOW_ALPHA['shadow-scrim'].dark }, + usage: 'The scrim behind modals and dialogs. Mode-independent.', + }, +]; + +/* + * Every role's name, in ROLES order. Written out because some tokens are + * built in ROLES (`viz-${i + 1}`, `${token}-hover`), so TypeScript can't + * infer the union from it. A test keeps the two equal. + */ +export const ROLE_TOKENS = [ + 'surface-sunken', + 'surface-default', + 'surface-raised', + 'surface-success', + 'surface-warning', + 'surface-error', + 'surface-brand', + 'surface-brand-subtle', + 'text-default', + 'text-subtle', + 'text-inverse', + 'text-success', + 'text-warning', + 'text-error', + 'border-strong', + 'border-default', + 'border-subtle', + 'action-primary', + 'action-primary-hover', + 'action-primary-pressed', + 'action-danger', + 'action-danger-hover', + 'action-danger-pressed', + 'action-neutral', + 'action-neutral-hover', + 'action-neutral-pressed', + 'action-disabled', + 'state-hover', + 'state-pressed', + 'state-selected', + 'viz-1', + 'viz-2', + 'viz-3', + 'viz-4', + 'viz-5', + 'viz-6', + 'viz-7', + 'viz-8', + 'viz-seq-1', + 'viz-seq-2', + 'viz-seq-3', + 'viz-seq-4', + 'shadow-key', + 'shadow-ambient', + 'shadow-scrim', +] as const; + +export type RoleToken = (typeof ROLE_TOKENS)[number]; + +export const roleFor = (token: string): Role | undefined => + ROLES.find((r) => r.token === token); diff --git a/packages/gamut-brand/src/roundTrip.ts b/packages/gamut-brand/src/roundTrip.ts new file mode 100644 index 0000000000..9243179ad7 --- /dev/null +++ b/packages/gamut-brand/src/roundTrip.ts @@ -0,0 +1,120 @@ +import type { Slot } from './brand'; +import { SLOTS } from './brand'; +import type { Ramp } from './ramp'; +import { GRADES } from './ramp'; +import { MODES, ROLES } from './roles'; +import type { ColorSystem, ResolvedValue } from './system'; + +/* + * Reads a brand's tokens.css back into a ColorSystem-shaped object: palette + * values from the `[data-brand]` block, and each role's var() resolved inside + * its mode block. The generator re-runs every check on the result, so what + * ships is what was checked, whatever Style Dictionary did to the values + * (rounding, references) on the way out. + */ +export const parseTokensCss = ( + css: string, + source: ColorSystem +): ColorSystem => { + // Plain string splitting rather than a regex: nested quantifiers over + // untrusted CSS are a ReDoS risk (CodeQL js/polynomial-redos). + const blocks = css.split('}').flatMap((raw) => { + // Style Dictionary puts `/** … */` notes after declarations; without + // stripping them, a note swallows the next declaration's name. + const chunk = raw.replace(/\/\*[\s\S]*?\*\//g, ''); + // Last '{' in the chunk: inside an @media block the inner rule's + // selector comes after the at-rule's own brace. + const open = chunk.lastIndexOf('{'); + if (open === -1) return []; + const head = chunk.slice(0, open).split(';').pop()!; + const decls: Record = {}; + for (const decl of chunk.slice(open + 1).split(';')) { + const colon = decl.indexOf(':'); + const name = decl.slice(0, colon).trim(); + if (colon === -1 || !/^--[\w-]+$/.test(name)) continue; + decls[name] = decl.slice(colon + 1).trim(); + } + return [ + { + selectors: head.trim().split('\n').pop()!.trim().split(/,\s*/), + decls, + }, + ]; + }); + const block = (selector: string) => { + const found = blocks.find((b) => b.selectors.includes(selector)); + if (!found) throw new Error(`tokens.css has no ${selector} block`); + return found.decls; + }; + const palette = block(`[data-brand="${source.brand.name}"]`); + const oklch = (value: string) => { + const m = value.match( + /^oklch\(([\d.]+) ([\d.]+) ([\d.]+)(?: \/ ([\d.]+))?\)$/ + ); + if (!m) throw new Error(`not an oklch() value: ${value}`); + return { + l: Number(m[1]), + c: Number(m[2]), + h: Number(m[3]), + alpha: m[4] === undefined ? undefined : Number(m[4]), + }; + }; + + const ramps = Object.fromEntries( + SLOTS.map((slot: Slot) => [ + slot, + Object.fromEntries( + GRADES.map((grade) => { + const p = oklch(palette[`--color-palette-${slot}-${grade}`]); + return [grade, { grade, l: p.l, c: p.c, h: p.h, hex: '' }]; + }) + ), + ]) + ) as Record; + + const roles = Object.fromEntries( + MODES.map((mode) => { + const decls = block(`[data-color-mode="${mode}"]`); + return [ + mode, + Object.fromEntries( + ROLES.map((r) => { + const ref = + decls[`--color-${r.token}`]?.match(/^var\((--[\w-]+)\)$/); + if (!ref) throw new Error(`${r.token} (${mode}) is not a var()`); + const value: ResolvedValue = { + ...oklch(palette[ref[1]]), + hex: '', + key: ref[1].replace(/^--color-/, ''), + }; + return [r.token, value]; + }) + ), + ]; + }) + ) as ColorSystem['roles']; + + return { ...source, ramps, roles }; +}; + +/* Every role, in both modes, whose parsed value or reference isn't the source's. */ +export const roundTripMisses = ( + parsed: ColorSystem, + source: ColorSystem +): string[] => { + const misses: string[] = []; + for (const mode of MODES) { + for (const r of ROLES) { + const a = parsed.roles[mode][r.token]; + const b = source.roles[mode][r.token]; + const same = + a.key === b.key && + a.l === b.l && + a.c === b.c && + a.h === b.h && + a.alpha === b.alpha; + if (!same) misses.push(`${r.token} (${mode})`); + } + } + return misses; +}; diff --git a/packages/gamut-brand/src/system.ts b/packages/gamut-brand/src/system.ts new file mode 100644 index 0000000000..927ebdddad --- /dev/null +++ b/packages/gamut-brand/src/system.ts @@ -0,0 +1,215 @@ +import { converter, differenceCiede2000, formatHex } from 'culori'; + +import type { + BrandInput, + BrandOverrides, + ResolvedBrand, + RoleOverride, + Slot, +} from './brand'; +import { NEUTRAL_SLOTS, resolveBrand, SLOTS, sourceHexes } from './brand'; +import type { Grade, Ramp } from './ramp'; +import { generateRamp, GRADES, oklchToHex } from './ramp'; +import type { Gradient, Mode, Ref, Role, RoleValue } from './roles'; +import { MODES, ROLES } from './roles'; + +/* + * A brand in, a fully resolved color system out. Pure: no React, no Emotion, + * no DOM. The build script calls it today; a server or runtime can call it + * later for tenant themes without a rewrite. + */ + +export interface Color { + l: number; + c: number; + h: number; + alpha?: number; + hex: string; +} + +export interface ResolvedValue extends Color { + /* The palette key this value comes from, e.g. `palette-primary-800`. */ + key: string; +} + +export interface ColorSystem { + brand: ResolvedBrand; + ramps: Record; + /* Every opaque primitive, keyed as the theme and CSS name it. */ + palette: Record; + roles: Record>; + /* Each gradient role's circles, after the brand's gradient overrides. */ + gradients: Record>; +} + +const deltaE2000 = differenceCiede2000(); +const toOklch = converter('oklch'); + +export const BOUNDARY: Record<'white' | 'black', Color> = { + white: { l: 1, c: 0, h: 0, hex: '#ffffff' }, + black: { l: 0, c: 0, h: 0, hex: '#000000' }, +}; + +export const paletteKey = (ref: Ref): string => + typeof ref === 'string' ? ref : `palette-${ref.slot}-${ref.grade}`; + +/* `palette-primary-600` at 6% → `palette-primary-600-a6`. */ +export const valueKey = ({ ref, alpha }: RoleValue): string => + alpha === undefined + ? paletteKey(ref) + : `${paletteKey(ref)}-a${Math.round(alpha * 100)}`; + +/* + * A role's value in one mode after the brand's role overrides. Role overrides + * repoint a role in one mode: a bare ref keeps the role's alpha, and + * `{ ref, alpha }` replaces it. + */ +export const roleValueFor = ( + overrides: BrandOverrides | undefined, + role: Role, + mode: Mode +): RoleValue => { + const o = ( + overrides?.roles?.[mode] as Record | undefined + )?.[role.token]; + if (!o) return role[mode]; + return typeof o === 'object' && 'alpha' in o + ? { ref: o.ref, alpha: o.alpha } + : { ...role[mode], ref: o }; +}; + +export const createColorSystem = (input: BrandInput): ColorSystem => { + const brand = resolveBrand(input); + + const ramps = Object.fromEntries( + SLOTS.map((slot) => [ + slot, + generateRamp(brand.hues[slot], NEUTRAL_SLOTS.includes(slot)), + ]) + ) as Record; + + /* + * Palette overrides, at the precision tokens.css prints (L and C to 4 + * places, H to 2), so the emitted value is the measured one. C is rounded, + * not floored like a generated rung: the source is already in sRGB, and + * flooring nudges an edge-of-gamut color like #FFD300 off its hex. Guarded: + * the rounded value has to land back on the same hex. + */ + const paletteOverrides = Object.entries(input.overrides?.palette ?? {}); + for (const [key, value] of paletteOverrides) { + const [, slot, g] = /^(.+)-(\d+)$/.exec(key)!; + const grade = Number(g) as Grade; + if (!SLOTS.includes(slot as Slot) || !GRADES.includes(grade)) { + throw new Error(`${brand.name}: no palette rung ${key}`); + } + const hex = formatHex(value)!; + const exact = toOklch(hex)!; + const rung = { + grade, + l: Number(exact.l.toFixed(4)), + c: Number(exact.c.toFixed(4)), + h: Number((exact.h ?? brand.hues[slot as Slot]).toFixed(2)), + hex, + }; + if (oklchToHex(rung.l, rung.c, rung.h) !== hex) { + throw new Error( + `${brand.name}: ${key} ${hex} doesn't survive 4-dp OKLCH` + ); + } + ramps[slot as Slot][grade] = rung; + } + if (input.overrides) { + brand.overrides = { + ...input.overrides, + palette: Object.fromEntries( + paletteOverrides.map(([k, v]) => [k, formatHex(v)!]) + ), + }; + } + + const sources = Object.entries(sourceHexes(input)) as [Slot, string][]; + if (sources.length) { + brand.sources = Object.fromEntries( + sources.map(([slot, hex]) => { + const [nearest, deltaE] = GRADES.map( + (g) => [g, deltaE2000(hex, ramps[slot][g].hex)] as const + ).reduce((a, b) => (b[1] < a[1] ? b : a)); + return [slot, { hex, nearest, deltaE: Number(deltaE.toFixed(2)) }]; + }) + ); + } + + const palette: Record = { ...BOUNDARY }; + for (const slot of SLOTS) { + for (const grade of GRADES) { + const { l, c, h, hex } = ramps[slot][grade]; + palette[paletteKey({ slot, grade })] = { l, c, h, hex }; + } + } + + const resolve = (value: RoleValue): ResolvedValue => { + const base = palette[paletteKey(value.ref)]; + return value.alpha === undefined + ? { ...base, key: valueKey(value) } + : { ...base, alpha: value.alpha, key: valueKey(value) }; + }; + + /* Every role override has to name a real role. */ + const roleOverrides = input.overrides?.roles ?? {}; + for (const [mode, tokens] of Object.entries(roleOverrides)) { + for (const token of Object.keys(tokens ?? {})) { + if (!ROLES.some((r) => r.token === token)) { + throw new Error( + `${brand.name}: no role ${token} to override (${mode})` + ); + } + } + } + const roles = Object.fromEntries( + MODES.map((mode) => [ + mode, + Object.fromEntries( + ROLES.map((r: Role) => [ + r.token, + resolve(roleValueFor(input.overrides, r, mode)), + ]) + ), + ]) + ) as ColorSystem['roles']; + + /* Gradient overrides move a gradient role's circles to another slot. */ + const gradientOverrides: Record< + string, + Partial> | undefined + > = input.overrides?.gradients ?? {}; + for (const [token, slots] of Object.entries(gradientOverrides)) { + if (!ROLES.some((r) => r.token === token && r.gradient)) { + throw new Error(`${brand.name}: no gradient role ${token} to override`); + } + for (const [from, to] of Object.entries(slots ?? {})) { + if (!SLOTS.includes(from as Slot) || !SLOTS.includes(to)) { + throw new Error(`${brand.name}: ${token} moves ${from} to ${to}`); + } + } + } + const gradients = Object.fromEntries( + MODES.map((mode) => [ + mode, + Object.fromEntries( + ROLES.filter((r) => r.gradient).map((r) => { + const slots = gradientOverrides[r.token] ?? {}; + const circles = r.gradient![mode].circles.map((c) => { + const to = + typeof c.ref === 'string' ? undefined : slots[c.ref.slot]; + return to && typeof c.ref !== 'string' + ? { ...c, ref: { ...c.ref, slot: to } } + : c; + }); + return [r.token, { circles }]; + }) + ), + ]) + ) as ColorSystem['gradients']; + + return { brand, ramps, palette, roles, gradients }; +}; diff --git a/packages/gamut-brand/src/validate.ts b/packages/gamut-brand/src/validate.ts new file mode 100644 index 0000000000..780294c9b4 --- /dev/null +++ b/packages/gamut-brand/src/validate.ts @@ -0,0 +1,267 @@ +import type { Color as CuloriColor } from 'culori'; +import { converter, differenceCiede2000, wcagContrast } from 'culori'; + +import { SLOTS } from './brand'; +import { contentModeFor } from './contentModes'; +import { contrast, GRADES, SEPARATION_GUARANTEE } from './ramp'; +import type { Mode } from './roles'; +import { MODES, ROLES, STATE_ALPHA } from './roles'; +import type { Color, ColorSystem } from './system'; + +/* + * The guarantees, as a function of any color system — ours or a tenant's. + * Every number is measured on OKLCH floats, not on rounded hex. + */ + +export interface Check { + name: string; + pass: boolean; + /* Measured for the record, never fails the run. */ + reported?: boolean; + /* The tightest measurement the check saw, for the report. */ + worst: number; + floor: number; + at: string; +} + +const toRgb = converter('rgb'); +const deltaE = differenceCiede2000(); + +const asCulori = (c: Pick): CuloriColor => ({ + mode: 'oklch', + l: c.l, + c: c.c, + h: c.h, +}); + +/* + * Paint `top` at `alpha` over `base`, in gamma-encoded sRGB — what a browser + * does with an rgba() / oklch(/ a) fill. + */ +export const composite = ( + top: Color, + alpha: number, + base: Color | CuloriColor +): CuloriColor => { + const t = toRgb(asCulori(top)); + const b = 'mode' in base ? toRgb(base) : toRgb(asCulori(base)); + const mix = (x: number, y: number) => alpha * x + (1 - alpha) * y; + return { mode: 'rgb', r: mix(t.r, b.r), g: mix(t.g, b.g), b: mix(t.b, b.b) }; +}; + +class Tracker { + worst = Infinity; + at = ''; + see(value: number, at: string) { + if (value < this.worst) { + this.worst = value; + this.at = at; + } + } +} + +/* Any two grades 500 apart clear 3:1, 600 apart clear 4.5:1 — every slot pair. */ +const gradeSeparation = (system: ColorSystem): Check[] => + (['nonText', 'text'] as const).map((kind) => { + const { minGap, floor } = SEPARATION_GUARANTEE[kind]; + const t = new Tracker(); + for (const a of SLOTS) { + for (const b of SLOTS) { + for (const g1 of GRADES) { + for (const g2 of GRADES) { + if (g2 - g1 < minGap) continue; + t.see( + contrast(system.ramps[a][g1], system.ramps[b][g2]), + `${a}-${g1} / ${b}-${g2}` + ); + } + } + } + } + return { + name: `grade separation (${minGap} apart, ${kind})`, + pass: t.worst >= floor, + worst: t.worst, + floor, + at: t.at, + }; + }); + +/* Each role against every role it's declared to sit with, in both modes. */ +const roleFloors = (system: ColorSystem): Check[] => + ROLES.filter((r) => r.floor !== undefined).map((role) => { + const t = new Tracker(); + for (const mode of MODES) { + for (const other of role.against ?? []) { + t.see( + contrast(system.roles[mode][role.token], system.roles[mode][other]), + `${mode}: ${other}` + ); + } + } + return { + name: `${role.token} ≥ ${role.floor}:1`, + pass: t.worst >= role.floor!, + worst: t.worst, + floor: role.floor!, + at: t.at, + }; + }); + +const ELEVATION = ['surface-sunken', 'surface-default', 'surface-raised']; + +/* The veil stacks a real element can be in. Selected is the only base. */ +const VEIL_STACKS: [string, string[]][] = [ + ['hover', ['state-hover']], + ['pressed', ['state-pressed']], + ['selected', ['state-selected']], + ['selected+hover', ['state-selected', 'state-hover']], + ['selected+pressed', ['state-selected', 'state-pressed']], +]; + +/* + * border-default on a selected stack is out of scope: no component pairs a + * bare border with a selected state. Measured and reported, not asserted, so + * the carve-out stays visible. + */ +const isCarvedOut = (token: string, stack: string[]) => + token === 'border-default' && stack.includes('state-selected'); + +/* + * Every role with a floor still clears it once a state veil is composited + * over the elevation surface underneath it. + */ +const veilFloors = (system: ColorSystem): Check[] => { + const asserted = new Tracker(); + const carvedOut = new Tracker(); + for (const role of ROLES) { + /* Viz marks don't sit on interactive fills, so they aren't paired with veils. */ + if (role.floor === undefined || role.group === 'viz') continue; + const surfaces = (role.against ?? []).filter((s) => ELEVATION.includes(s)); + for (const mode of MODES) { + for (const surface of surfaces) { + for (const [label, stack] of VEIL_STACKS) { + const fill = stack.reduce( + (acc, token) => + composite( + system.roles[mode][token], + system.roles[mode][token].alpha!, + acc + ), + asCulori(system.roles[mode][surface]) + ); + const margin = + wcagContrast(asCulori(system.roles[mode][role.token]), fill) - + role.floor; + const where = `${mode}: ${role.token} on ${surface} + ${label}`; + (isCarvedOut(role.token, stack) ? carvedOut : asserted).see( + margin, + where + ); + } + } + } + } + return [ + { + name: 'every floor holds on every state veil (margin over floor)', + pass: asserted.worst >= 0, + worst: asserted.worst, + floor: 0, + at: asserted.at, + }, + { + name: 'carve-out: border-default on selected stacks (margin, reported)', + pass: true, + reported: true, + worst: carvedOut.worst, + floor: 0, + at: carvedOut.at, + }, + ]; +}; + +/* Hover → pressed has to be a visible step, not just an authored one. */ +export const STATE_STEP_FLOOR = 1.0; + +const hoverPressedStep = (system: ColorSystem): Check => { + const t = new Tracker(); + for (const mode of MODES as Mode[]) { + for (const surface of ELEVATION) { + const base = asCulori(system.roles[mode][surface]); + const at = (token: keyof typeof STATE_ALPHA) => + composite(system.roles[mode][token], STATE_ALPHA[token], base); + t.see( + deltaE(at('state-hover'), at('state-pressed')), + `${mode}: ${surface}` + ); + } + } + return { + name: `hover → pressed ≥ ${STATE_STEP_FLOOR} ΔE2000`, + pass: t.worst >= STATE_STEP_FLOOR, + worst: t.worst, + floor: STATE_STEP_FLOOR, + at: t.at, + }; +}; + +/* + * Text on a surface, in the mode the content-mode table picks for it. This is + * the guarantee Background makes, now that it reads the table instead of + * measuring. Raw palette colors are reported only: a mid-ramp grade can't give + * AA text in either mode, and that's a reason not to use it as a surface. + */ +const CONTENT_TEXT = ['text-default', 'text-subtle']; + +const surfaceContent = (system: ColorSystem): Check[] => { + const surfaces = new Tracker(); + for (const role of ROLES) { + if (role.group !== 'surface') continue; + for (const mode of MODES) { + const surface = system.roles[mode][role.token]; + const content = contentModeFor(system, surface); + for (const text of CONTENT_TEXT) { + surfaces.see( + contrast(system.roles[content][text], surface), + `${mode}: ${text} (${content}) on ${role.token}` + ); + } + } + } + + const palette = new Tracker(); + let belowFloor = 0; + for (const [key, color] of Object.entries(system.palette)) { + const content = contentModeFor(system, color); + const measured = contrast(system.roles[content]['text-default'], color); + if (measured < 4.5) belowFloor += 1; + palette.see(measured, `text-default (${content}) on ${key}`); + } + + return [ + { + name: 'text on every surface role, in its content mode ≥ 4.5:1', + pass: surfaces.worst >= 4.5, + worst: surfaces.worst, + floor: 4.5, + at: surfaces.at, + }, + { + name: `text-default on raw palette colors (reported; ${belowFloor} below 4.5:1)`, + pass: true, + reported: true, + worst: palette.worst, + floor: 4.5, + at: palette.at, + }, + ]; +}; + +export const validate = (system: ColorSystem): Check[] => [ + ...gradeSeparation(system), + ...roleFloors(system), + ...veilFloors(system), + hoverPressedStep(system), + ...surfaceContent(system), +]; diff --git a/packages/gamut-brand/tsconfig.json b/packages/gamut-brand/tsconfig.json new file mode 100644 index 0000000000..71574b2ac4 --- /dev/null +++ b/packages/gamut-brand/tsconfig.json @@ -0,0 +1,26 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "allowJs": false, + "declaration": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "bundler", + "strict": true, + "noFallthroughCasesInSwitch": true, + "paths": {}, + "types": ["node"] + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/gamut-brand/tsconfig.lib.json b/packages/gamut-brand/tsconfig.lib.json new file mode 100644 index 0000000000..740b429fc1 --- /dev/null +++ b/packages/gamut-brand/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "rootDir": "./src", + "outDir": "./dist" + }, + "exclude": ["jest.config.ts", "**/__tests__/**", "**/__fixtures__/**"], + "include": ["src/**/*.ts"] +} diff --git a/packages/gamut-brand/tsconfig.spec.json b/packages/gamut-brand/tsconfig.spec.json new file mode 100644 index 0000000000..fd163bdc4e --- /dev/null +++ b/packages/gamut-brand/tsconfig.spec.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["jest", "node"] + }, + "include": ["jest.config.ts", "src/**/*.ts"] +} diff --git a/packages/gamut-brand/tsdown.config.ts b/packages/gamut-brand/tsdown.config.ts new file mode 100644 index 0000000000..99c2c40f42 --- /dev/null +++ b/packages/gamut-brand/tsdown.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'tsdown'; + +// Explicit `.ts` extension required — see packages/variance/tsdown.config.ts. +import { baseConfig } from '../../tsdown.base.ts'; + +/* + * Build-time only, so ESM for Node: the CLI and the API both import Style + * Dictionary, which is ESM-only. + */ +export default defineConfig({ + ...baseConfig, + entry: ['src/index.ts', 'src/cli.ts'], + format: ['esm'], + platform: 'node', +}); diff --git a/packages/gamut-codemods/README.md b/packages/gamut-codemods/README.md index b552c85940..3e0134eaed 100644 --- a/packages/gamut-codemods/README.md +++ b/packages/gamut-codemods/README.md @@ -6,9 +6,10 @@ Codemods for moving a repo onto `@skillsoft/gamut` and between its releases. Each preset is one upgrade. Run them in order if you're more than one behind. -| Preset | What it's for | -| ------------ | ------------------------------------------------------- | -| `scope-swap` | Moving from `@codecademy/gamut*` to `@skillsoft/gamut*` | +| Preset | What it's for | +| ------------- | ---------------------------------------------------------------- | +| `scope-swap` | Moving from `@codecademy/gamut*` to `@skillsoft/gamut*` | +| `color-roles` | Moving Gamut color names onto the generated color system's roles | `npx @skillsoft/gamut-codemods list` shows every preset and the migrations inside it. @@ -76,6 +77,34 @@ Every skillsoft-gamut pull request publishes installable previews through pkg.pr - Every file is parsed with the `tsx` parser, so Flow-typed JS won't parse. - The leftovers report uses regex, not an AST, so expect some false positives. +## Running color-roles + +```sh +npx @skillsoft/gamut-codemods color-roles . --dry +npx @skillsoft/gamut-codemods color-roles . +``` + +It maps today's color names (`text`, `primary`, `background-current`, …) onto the roles of the generated color system, using design's migration map (`aliases.ts`, generated from `src/migration.ts` in `@skillsoft/gamut-brand`). + +| Design's kind | What the codemod does | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| Rename, merge | Replaces the name: `bg="background-primary"` → `bg="surface-default"`, `theme.colors.text` → `theme.colors['text-default']` | +| Split | Leaves the name and puts `/* color-roles: 'background' splits into surface-raised \| surface-default. */` in front of it | +| Retire, pending | The same, with design's rule for what to do instead | +| Old palette color | Warns. `navy`, `hyper-500` and the rest have no role of their own; design maps them per component | + +A name left in place is still an old name, so it fails the type check until someone picks its role. The leftovers report lists every `color-roles:` comment still in the code; delete each one once its name is resolved. + +It looks for color names where a color goes: the color props (`color`, `textColor`, `bg`, `borderColor`, `borderColor{X,Y,Left,Right,Top,Bottom}`) in JSX and in style objects, including ternaries, `&&`, and responsive objects and arrays; `theme.colors.x` and `theme.colors['x']`; and `themed('colors.x')`. `variant="primary"` and other strings that only look like color names are left alone. + +Limits: + +- A name held in a variable before it reaches a color prop (`const bg = 'background'`) isn't seen. +- Names built at runtime (`` `${variant}-hover` ``) only warn. +- MDX bodies aren't rewritten. + +On gamut's own source (branch `cass-color-brands`), a run changes 60 files and 273 lines, warns 171 times (about 120 splits, retirements and pending names, 50 old palette colors, 3 runtime names), and takes the type errors from 284 to 145. A second run changes nothing. + ## Contributing ### How it's put together diff --git a/packages/gamut-codemods/src/__tests__/color-roles-manifest.test.ts b/packages/gamut-codemods/src/__tests__/color-roles-manifest.test.ts new file mode 100644 index 0000000000..99b500c360 --- /dev/null +++ b/packages/gamut-codemods/src/__tests__/color-roles-manifest.test.ts @@ -0,0 +1,34 @@ +import { isAutomatic, manifest } from '../presets/color-roles/manifest'; + +/* + The color-roles data is generated from design's map, so these guard the + generator and any hand edit: a row the codemod applies must name exactly one + real role, and no name can be both an alias and an old palette color. +*/ +describe('color-roles manifest', () => { + const rows = Object.entries(manifest.aliases); + const roles = new Set(manifest.roles); + + it('has every alias design maps', () => { + expect(rows).toHaveLength(32); + }); + + it.each(rows.filter(([, a]) => isAutomatic(a)))( + '%s names exactly one role', + (_, alias) => { + expect(alias.to).toHaveLength(1); + } + ); + + it('only targets real roles', () => { + const bad = rows.flatMap(([name, a]) => + a.to.filter((t) => !roles.has(t)).map((t) => `${name} → ${t}`) + ); + expect(bad).toEqual([]); + }); + + it('keeps aliases and old palette names apart', () => { + const both = manifest.oldPalette.filter((n) => n in manifest.aliases); + expect(both).toEqual([]); + }); +}); diff --git a/packages/gamut-codemods/src/__tests__/fixtures.test.ts b/packages/gamut-codemods/src/__tests__/fixtures.test.ts index cc5f0f2d48..ff553126df 100644 --- a/packages/gamut-codemods/src/__tests__/fixtures.test.ts +++ b/packages/gamut-codemods/src/__tests__/fixtures.test.ts @@ -104,7 +104,14 @@ describe.each(Object.values(presets))('preset $name', (preset) => { }); } - const endToEnd = readCases(path.join(__dirname, '__testfixtures__')); + /* scope-swap's end-to-end cases sit at the top; other presets' in a folder named for them. */ + const endToEnd = readCases( + path.join( + __dirname, + '__testfixtures__', + preset.name === 'scope-swap' ? '' : preset.name + ) + ); if (endToEnd.length > 0) { it.each(endToEnd)('every migration: $name', (c) => { expect(runSource(preset.name, c.input).out.trim()).toBe(c.output.trim()); diff --git a/packages/gamut-codemods/src/__tests__/manifest.test.ts b/packages/gamut-codemods/src/__tests__/manifest.test.ts index 471776e2e1..54e7bbb02d 100644 --- a/packages/gamut-codemods/src/__tests__/manifest.test.ts +++ b/packages/gamut-codemods/src/__tests__/manifest.test.ts @@ -3,10 +3,18 @@ import path from 'node:path'; import ts from 'typescript'; -import { type DeepImport, renameSpecifier } from '../lib/manifest'; +import { + type DeepImport, + type Manifest, + renameSpecifier, +} from '../lib/manifest'; import { presets } from '../presets'; import { manifest } from '../presets/scope-swap/manifest'; +/* Manifests that promise exports: scope-swap's shape. Other presets have none. */ +const movesExports = (m: unknown): m is Manifest => + typeof m === 'object' && m !== null && 'movedExports' in m; + /* Guards against a manifest that promises something the packages don't deliver. Runs against the built dist, so `^build` has to run first @@ -31,8 +39,9 @@ const importsToCheck = () => { names.forEach((name) => set.add(name)); byModule.set(spec, set); }; - for (const preset of Object.values(presets)) { - const m = preset.manifest; + for (const m of Object.values(presets) + .map((preset) => preset.manifest) + .filter(movesExports)) { for (const rule of m.movedExports) { add(renameSpecifier(m, rule.to), rule.names); } diff --git a/packages/gamut-codemods/src/cli.ts b/packages/gamut-codemods/src/cli.ts index 7315dd62a3..b0c2aaf13a 100644 --- a/packages/gamut-codemods/src/cli.ts +++ b/packages/gamut-codemods/src/cli.ts @@ -5,14 +5,9 @@ import path from 'node:path'; import jscodeshift from 'jscodeshift'; -import { findFlatEslintConfig } from './lib/eslint'; import { dirtyTreeReason } from './lib/git'; -import { findLeftovers, walk } from './lib/leftovers'; -import { - activeSourceMigrations, - mentionsOldNames, - runSourceMigrations, -} from './lib/run-source'; +import { walk } from './lib/leftovers'; +import { activeSourceMigrations, runSourceMigrations } from './lib/run-source'; import type { FileMigration, Preset } from './lib/types'; import { getPreset, presets } from './presets'; @@ -72,14 +67,14 @@ export const parseArgs = (argv: string[]): Options | 'help' | 'list' => { const heading = (text: string) => console.log(`\n— ${text} —`); const runFileMigrations = ( - preset: Preset, + preset: Preset, opts: Options, note: (key: string) => void ) => { const only = opts.only ? new Set(opts.only.split(',')) : null; const skip = new Set(opts.skip ? opts.skip.split(',') : []); const active = preset.migrations.filter( - (m): m is FileMigration => + (m): m is FileMigration => m.kind === 'file' && (!only || only.has(m.name)) && !skip.has(m.name) ); let changed = 0; @@ -93,7 +88,7 @@ const runFileMigrations = ( const warn = (msg: string) => console.log(`[${migration.name}] ${file} ${msg}`); const transformSource = (code: string) => - mentionsOldNames(preset.manifest, code) + preset.mentions(code) ? runSourceMigrations({ j: jscodeshift.withParser('tsx'), source: code, @@ -125,7 +120,7 @@ const runFileMigrations = ( return changed; }; -const printChecklist = (preset: Preset, notes: Set) => { +const printChecklist = (preset: Preset, notes: Set) => { heading('next steps'); const items = [ ...Object.entries(preset.conditionalChecklist) @@ -213,10 +208,8 @@ export const main = async (argv: string[]) => { heading( 'leftovers skipped in --dry (nothing was written, so everything would show up)' ); - } else if (opts.report) { - const hits = findLeftovers(opts.paths, preset.manifest, { - eslintRuleKeys: !findFlatEslintConfig(opts.paths), - }); + } else if (opts.report && preset.leftovers) { + const hits = preset.leftovers(opts.paths); heading(`leftovers (${hits.length})`); hits.forEach((hit) => console.log( diff --git a/packages/gamut-codemods/src/lib/run-source.ts b/packages/gamut-codemods/src/lib/run-source.ts index 42eb019392..47a2d57595 100644 --- a/packages/gamut-codemods/src/lib/run-source.ts +++ b/packages/gamut-codemods/src/lib/run-source.ts @@ -5,6 +5,7 @@ import type { AstMigration, Edit, Locatable, + Migration, Note, SourceMigration, SpliceMigration, @@ -24,11 +25,11 @@ import type { that embed code, like MDX's ESM blocks. */ -export interface RunSourceOptions { +export interface RunSourceOptions { j: JSCodeshift; source: string; - migrations: SourceMigration[]; - manifest: Manifest; + migrations: SourceMigration[]; + manifest: M; warnFor: (migration: string) => (node: Locatable, message: string) => void; note: Note; } @@ -42,18 +43,18 @@ const applyEdits = (source: string, edits: Edit[]) => source ); -export const runSourceMigrations = ({ +export const runSourceMigrations = ({ j, source: original, migrations, manifest, warnFor, note, -}: RunSourceOptions) => { +}: RunSourceOptions) => { let source = original; const astMigrations = migrations.filter( - (m): m is AstMigration => m.kind === 'ast' + (m): m is AstMigration => m.kind === 'ast' ); if (astMigrations.length > 0) { const root = j(source); @@ -72,7 +73,7 @@ export const runSourceMigrations = ({ } for (const m of migrations.filter( - (x): x is SpliceMigration => x.kind === 'splice' + (x): x is SpliceMigration => x.kind === 'splice' )) { const root = j(source); const edits = m.edits({ @@ -89,7 +90,7 @@ export const runSourceMigrations = ({ return source; }; -/* The pre-filter every caller uses so big repos don't parse every file. */ +/* scope-swap's pre-filter (Preset.mentions): an old package name or rule prefix. */ export const mentionsOldNames = (manifest: Manifest, source: string) => [ ...Object.keys(manifest.packages), @@ -98,8 +99,8 @@ export const mentionsOldNames = (manifest: Manifest, source: string) => ].some((name) => source.includes(name)); /* `only` and `skip` are comma-separated migration names. */ -export const activeSourceMigrations = ( - migrations: { kind: string; name: string }[], +export const activeSourceMigrations = ( + migrations: Migration[], only?: string, skip?: string ) => { @@ -108,5 +109,5 @@ export const activeSourceMigrations = ( return migrations.filter( (m) => m.kind !== 'file' && (!names || names.has(m.name)) && !skipped.has(m.name) - ) as SourceMigration[]; + ) as SourceMigration[]; }; diff --git a/packages/gamut-codemods/src/lib/types.ts b/packages/gamut-codemods/src/lib/types.ts index ee3e0690fb..027465c760 100644 --- a/packages/gamut-codemods/src/lib/types.ts +++ b/packages/gamut-codemods/src/lib/types.ts @@ -1,5 +1,6 @@ import type { Collection, JSCodeshift } from 'jscodeshift'; +import type { Leftover } from './leftovers'; import type { Manifest } from './manifest'; /* Anything with a source location, so warnings can point at a line. */ @@ -16,11 +17,15 @@ export type Warn = (node: Locatable, message: string) => void; */ export type Note = (checklistKey: string) => void; -export interface SourceContext { +/* + `M` is the preset's manifest type. It defaults to scope-swap's Manifest, so + migrations written for that preset don't name it. +*/ +export interface SourceContext { j: JSCodeshift; root: Collection; source: string; - manifest: Manifest; + manifest: M; warn: Warn; note: Note; } @@ -31,7 +36,7 @@ export interface Edit { text: string; } -interface MigrationBase { +interface MigrationBase { name: string; description: string; /* @@ -39,7 +44,7 @@ interface MigrationBase { skip this migration for the whole run (the CLI prints it and notes `skipped:`), or null to run it. */ - skipReason?(roots: string[], manifest: Manifest): string | null; + skipReason?(roots: string[], manifest: M): string | null; } /* @@ -47,24 +52,24 @@ interface MigrationBase { whatever it thinks changed, and in semicolon-less files that has spread to neighbouring statements. Return true if anything changed. */ -export interface AstMigration extends MigrationBase { +export interface AstMigration extends MigrationBase { kind: 'ast'; - run(ctx: SourceContext): boolean; + run(ctx: SourceContext): boolean; /* Formatting fixes on the printed output that the AST can't express. */ - postPrint?(source: string, ctx: { manifest: Manifest }): string; + postPrint?(source: string, ctx: { manifest: M }): string; } /* Uses the AST to find ranges, then edits the original text by offset. The default for anything that swaps one string for another. */ -export interface SpliceMigration extends MigrationBase { +export interface SpliceMigration extends MigrationBase { kind: 'splice'; - edits(ctx: SourceContext): Edit[]; + edits(ctx: SourceContext): Edit[]; } /* Gets raw text for non-source files (package.json, .eslintrc, .mdx). */ -export interface FileMigration extends MigrationBase { +export interface FileMigration extends MigrationBase { kind: 'file'; /* Not `match`: CodeQL reads `migration.match(file)` as @@ -73,7 +78,7 @@ export interface FileMigration extends MigrationBase { appliesTo(path: string): boolean; run(ctx: { source: string; - manifest: Manifest; + manifest: M; warn: (message: string) => void; note: Note; /* @@ -84,15 +89,24 @@ export interface FileMigration extends MigrationBase { }): string | null; } -export type SourceMigration = AstMigration | SpliceMigration; -export type Migration = SourceMigration | FileMigration; +export type SourceMigration = + | AstMigration + | SpliceMigration; +export type Migration = SourceMigration | FileMigration; -export interface Preset { +export interface Preset { name: string; description: string; - manifest: Manifest; + manifest: M; + /* + The cheap pre-filter every caller uses, so big repos don't parse every + file: false means no migration in this preset could change `source`. + */ + mentions(source: string): boolean; + /* After a write run, what's left for a person. Omit for no report. */ + leftovers?(roots: string[]): Leftover[]; /* Run in order. All `ast` migrations must come before any `splice` one. */ - migrations: Migration[]; + migrations: Migration[]; /* Printed at the end of every run. */ checklist: string[]; /* Printed only when a migration calls note(key). */ diff --git a/packages/gamut-codemods/src/migrations/color-roles-stylesheets/__testfixtures__/vars.input.scss b/packages/gamut-codemods/src/migrations/color-roles-stylesheets/__testfixtures__/vars.input.scss new file mode 100644 index 0000000000..a9f77c82c1 --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles-stylesheets/__testfixtures__/vars.input.scss @@ -0,0 +1,16 @@ +.panel { + color: var(--color-text); + border-color: var(--color-border-secondary); + background: var(--color-background); + box-shadow: 0 1px 4px var(--color-shadow-primary); +} + +.legacy { + fill: var(--color-navy-800); + outline-color: var(--color-primary, var(--color-text-secondary)); +} + +/* Already on the new names: untouched. */ +.done { + color: var(--color-text-default); +} diff --git a/packages/gamut-codemods/src/migrations/color-roles-stylesheets/__testfixtures__/vars.output.scss b/packages/gamut-codemods/src/migrations/color-roles-stylesheets/__testfixtures__/vars.output.scss new file mode 100644 index 0000000000..20b63dc277 --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles-stylesheets/__testfixtures__/vars.output.scss @@ -0,0 +1,16 @@ +.panel { + color: var(--color-text-default); + border-color: var(--color-border-default); + background: /* color-roles: 'background' splits into surface-raised | surface-default. Anything that sits on the page (card, input, menu, popover, dialog) takes `surface-raised`. Where `background` fills the page itself, it is `surface-default`. */ var(--color-background); + box-shadow: 0 1px 4px /* color-roles: 'shadow-primary' splits into shadow-key | shadow-ambient. Every elevation shadow composes both: `shadow-key` for the tight contact layer, `shadow-ambient` for the soft falloff. The elevation tokens hold only offset, blur and spread; see the elevation system for which depth uses which geometry. */ var(--color-shadow-primary); +} + +.legacy { + fill: var(--color-navy-800); + outline-color: var(--color-action-primary, var(--color-text-subtle)); +} + +/* Already on the new names: untouched. */ +.done { + color: var(--color-text-default); +} diff --git a/packages/gamut-codemods/src/migrations/color-roles-stylesheets/__testfixtures__/vars.warnings.json b/packages/gamut-codemods/src/migrations/color-roles-stylesheets/__testfixtures__/vars.warnings.json new file mode 100644 index 0000000000..834e4ce144 --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles-stylesheets/__testfixtures__/vars.warnings.json @@ -0,0 +1,5 @@ +[ + "'background' splits into", + "'shadow-primary' splits into shadow-key | shadow-ambient", + "'--color-navy-800' is an old palette color" +] diff --git a/packages/gamut-codemods/src/migrations/color-roles-stylesheets/index.ts b/packages/gamut-codemods/src/migrations/color-roles-stylesheets/index.ts new file mode 100644 index 0000000000..0c0fd03b36 --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles-stylesheets/index.ts @@ -0,0 +1,69 @@ +import type { FileMigration } from '../../lib/types'; +import type { ColorRolesManifest } from '../../presets/color-roles/manifest'; +import { + commentText, + describe, + findOldVars, + markedBefore, + mentionsOldVar, + paletteMessage, +} from '../color-roles/cssVars'; + +/* + Old color variables in stylesheets: `var(--color-text)` in .css, .scss and + .less. The type check can't see these, so this is the only place they're + caught. + + - Renames and merges become `--color-`. + - Splits, retirements and pending names stay, with a + `/* color-roles: … *\/` comment carrying design's rule in front of them. + The preset's leftovers report lists every one by file and line. + - Old palette colors (`--color-navy-800`) only warn. + + Indented-syntax .sass isn't rewritten: its comment rules differ, so a + comment mid-value could break the file. +*/ +export const colorRolesStylesheets: FileMigration = { + name: 'color-roles-stylesheets', + kind: 'file', + description: + 'Rename old `--color-*` variables in stylesheets; mark the ones a person has to pick.', + appliesTo: (file) => /\.(css|scss|less)$/.test(file), + run({ source, manifest, warn, note }) { + if (!mentionsOldVar(source)) return null; + const hits = findOldVars(source, manifest); + if (hits.length === 0) return null; + + let out = ''; + let last = 0; + for (const hit of hits) { + out += source.slice(last, hit.at); + last = hit.at; + if (hit.role) { + out += `--color-${hit.role}`; + last = hit.at + `--color-${hit.name}`.length; + } else if (hit.alias) { + /* In front of the `var(` the variable sits in, if there is one. */ + const varAt = source.lastIndexOf('var(', hit.at); + const at = + varAt !== -1 && source.slice(varAt + 4, hit.at).trim() === '' + ? varAt + : hit.at; + if (!markedBefore(source, at)) { + out = + out.slice(0, out.length - (hit.at - at)) + + commentText(hit.name, hit.alias) + + source.slice(at, hit.at); + } + warn(describe(hit.name, hit.alias)); + note('color-roles:manual'); + } else if (hit.palette) { + warn(paletteMessage(hit.name)); + note('color-roles:palette'); + } + } + out += source.slice(last); + note('color-roles:stylesheets'); + return out === source ? null : out; + }, +}; diff --git a/packages/gamut-codemods/src/migrations/color-roles-themes/__testfixtures__/themes.input.tsx b/packages/gamut-codemods/src/migrations/color-roles-themes/__testfixtures__/themes.input.tsx new file mode 100644 index 0000000000..98701af305 --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles-themes/__testfixtures__/themes.input.tsx @@ -0,0 +1,22 @@ +import type { CoreTheme, PercipioTheme } from '@skillsoft/gamut-styles'; +import { + coreTheme, + GamutProvider, + percipioTheme as theme, +} from '@skillsoft/gamut-styles'; + +import { settings } from './settings'; + +export const Percipio = () => ; + +export const Core = () => ; + +export const themes = { coreTheme, dark: { ...coreTheme, mode: 'dark' } }; + +export const label = settings.coreTheme; + +export type Typed = PercipioTheme; + +declare module '@emotion/react' { + export interface Theme extends CoreTheme {} +} diff --git a/packages/gamut-codemods/src/migrations/color-roles-themes/__testfixtures__/themes.output.tsx b/packages/gamut-codemods/src/migrations/color-roles-themes/__testfixtures__/themes.output.tsx new file mode 100644 index 0000000000..1c791d525f --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles-themes/__testfixtures__/themes.output.tsx @@ -0,0 +1,22 @@ +import type { /* color-roles: 'CoreTheme' types the old core theme, which has no color roles. Brand themes share one shape: use 'SkillsoftTheme', which types every brand (originalCodecademyTheme included). */ CoreTheme, SkillsoftTheme } from '@skillsoft/gamut-styles'; +import { + originalCodecademyTheme, + GamutProvider, + skillsoftTheme as theme, +} from '@skillsoft/gamut-styles'; + +import { settings } from './settings'; + +export const Percipio = () => ; + +export const Core = () => ; + +export const themes = { coreTheme: originalCodecademyTheme, dark: { ...originalCodecademyTheme, mode: 'dark' } }; + +export const label = settings.coreTheme; + +export type Typed = SkillsoftTheme; + +declare module '@emotion/react' { + export interface Theme extends CoreTheme {} +} diff --git a/packages/gamut-codemods/src/migrations/color-roles-themes/__testfixtures__/themes.warnings.json b/packages/gamut-codemods/src/migrations/color-roles-themes/__testfixtures__/themes.warnings.json new file mode 100644 index 0000000000..f01189a4b9 --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles-themes/__testfixtures__/themes.warnings.json @@ -0,0 +1 @@ +["'CoreTheme' types the old core theme"] diff --git a/packages/gamut-codemods/src/migrations/color-roles-themes/index.ts b/packages/gamut-codemods/src/migrations/color-roles-themes/index.ts new file mode 100644 index 0000000000..6a60fefd75 --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles-themes/index.ts @@ -0,0 +1,133 @@ +import type { Edit, SpliceMigration } from '../../lib/types'; +import type { ColorRolesManifest } from '../../presets/color-roles/manifest'; +import { MARK, markedBefore } from '../color-roles/cssVars'; + +/* + The old themes' names → the brand themes that replace them, wherever + they're imported from gamut-styles: + + - `percipioTheme` → `skillsoftTheme`, its replacement (`percipioTheme` is + removed), and the `PercipioTheme` type → `SkillsoftTheme`. + - `coreTheme` → `originalCodecademyTheme`, its rebuild. `coreTheme` still + exists but defines no roles, so components lose their colors on it. + + An aliased import (`percipioTheme as theme`) only has its imported name + changed. An unaliased one is renamed everywhere the file uses it. + + The `CoreTheme` type has no same-named successor: brand themes share one + shape, typed by `SkillsoftTheme`. It gets a `color-roles:` comment for a + person, since it's usually a Theme augmentation the app owns. +*/ + +const RENAMES: Record = { + percipioTheme: 'skillsoftTheme', + coreTheme: 'originalCodecademyTheme', + PercipioTheme: 'SkillsoftTheme', + PercipioThemeShape: 'SkillsoftThemeShape', +}; + +const MANUAL: Record = { + CoreTheme: `/* ${MARK} 'CoreTheme' types the old core theme, which has no color roles. Brand themes share one shape: use 'SkillsoftTheme', which types every brand (originalCodecademyTheme included). */ `, +}; + +const GAMUT_STYLES = /(^|\/)gamut-styles$/; + +interface Node { + type: string; + start: number; + end: number; + [key: string]: any; +} + +export const colorRolesThemes: SpliceMigration = { + name: 'color-roles-themes', + kind: 'splice', + description: + 'Rename percipioTheme and coreTheme to the brand themes that replace them.', + edits({ j, root, source, warn, note }) { + const edits: Edit[] = []; + const seen = new Set(); + const push = (e: Edit) => { + if (seen.has(e.start)) return; + seen.add(e.start); + edits.push(e); + }; + /* Unaliased imports, renamed across the file. */ + const local = new Map(); + + root.find(j.ImportDeclaration).forEach((p) => { + const decl = p.node as unknown as Node; + if (!GAMUT_STYLES.test(decl.source.value)) return; + for (const spec of decl.specifiers ?? []) { + if (spec.type !== 'ImportSpecifier') continue; + const name: string = spec.imported?.name; + const to = RENAMES[name]; + if (to) { + push({ + start: spec.imported.start, + end: spec.imported.end, + text: to, + }); + if (spec.local?.name === name) local.set(name, to); + note('color-roles:themes'); + } else if (MANUAL[name]) { + if (!markedBefore(source, spec.start)) { + push({ start: spec.start, end: spec.start, text: MANUAL[name] }); + } + warn( + spec, + `'${name}' types the old core theme, which has no color roles. Use 'SkillsoftTheme', which types every brand.` + ); + note('color-roles:themes'); + } + } + }); + + if (local.size === 0) return edits; + + /* `{ percipioTheme }` keeps its key: `{ percipioTheme: skillsoftTheme }`. */ + root.find(j.Node).forEach((p) => { + const node = p.node as unknown as Node; + if ( + (node.type === 'ObjectProperty' || node.type === 'Property') && + node.shorthand && + node.value?.type === 'Identifier' && + local.has(node.value.name) + ) { + push({ + start: node.start, + end: node.end, + text: `${node.value.name}: ${local.get(node.value.name)}`, + }); + } + }); + + root.find(j.Identifier).forEach((p) => { + const id = p.node as unknown as Node; + const to = local.get(id.name); + if (!to) return; + const parent = p.parent?.node as Node | undefined; + /* `x.percipioTheme` and `{ percipioTheme: … }` name something else. */ + if ( + parent && + (parent.type === 'MemberExpression' || + parent.type === 'OptionalMemberExpression') && + parent.property === p.node && + !parent.computed + ) { + return; + } + if ( + parent && + (parent.type === 'ObjectProperty' || parent.type === 'Property') && + parent.key === p.node && + !parent.computed + ) { + return; + } + push({ start: id.start, end: id.end, text: to }); + }); + + return edits; + }, +}; diff --git a/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/card.input.tsx b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/card.input.tsx new file mode 100644 index 0000000000..f3ebfe7913 --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/card.input.tsx @@ -0,0 +1,13 @@ +import { Card } from '@skillsoft/gamut'; +import { Card as OtherCard } from './OtherCard'; + +export const Cards = ({ featured }: { featured: boolean }) => ( + <> + White + Beige + Navy + Yellow + Hyper + Not gamut's + +); diff --git a/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/card.output.tsx b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/card.output.tsx new file mode 100644 index 0000000000..5f83a5f7f8 --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/card.output.tsx @@ -0,0 +1,13 @@ +import { Card } from '@skillsoft/gamut'; +import { Card as OtherCard } from './OtherCard'; + +export const Cards = ({ featured }: { featured: boolean }) => ( + <> + White + Beige + . */ variant="default">Navy + Yellow + Hyper + Not gamut's + +); diff --git a/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/card.warnings.json b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/card.warnings.json new file mode 100644 index 0000000000..b03dcb8772 --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/card.warnings.json @@ -0,0 +1 @@ +["Card's 'navy' variant becomes 'default'"] diff --git a/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/jsx.input.tsx b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/jsx.input.tsx new file mode 100644 index 0000000000..c020a037cc --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/jsx.input.tsx @@ -0,0 +1,14 @@ +import { Box, Text } from '@skillsoft/gamut'; + +export const Card = ({ active }: { active: boolean }) => ( + + + Title + + Not a variant: secondary is a color here. + + + + + +); diff --git a/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/jsx.output.tsx b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/jsx.output.tsx new file mode 100644 index 0000000000..503a4c410e --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/jsx.output.tsx @@ -0,0 +1,14 @@ +import { Box, Text } from '@skillsoft/gamut'; + +export const Card = ({ active }: { active: boolean }) => ( + + + Title + + Not a variant: secondary is a color here. + + + + + +); diff --git a/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/jsx.warnings.json b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/jsx.warnings.json new file mode 100644 index 0000000000..8eca099b43 --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/jsx.warnings.json @@ -0,0 +1 @@ +["'navy' is an old palette color with no role of its own"] diff --git a/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/manual.input.tsx b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/manual.input.tsx new file mode 100644 index 0000000000..f8fe5d3d0a --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/manual.input.tsx @@ -0,0 +1,9 @@ +import { Background, Box } from '@skillsoft/gamut'; + +export const Panel = ({ muted }: { muted: boolean }) => ( + + + + + +); diff --git a/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/manual.output.tsx b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/manual.output.tsx new file mode 100644 index 0000000000..bfc5f8609d --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/manual.output.tsx @@ -0,0 +1,9 @@ +import { Background, Box } from '@skillsoft/gamut'; + +export const Panel = ({ muted }: { muted: boolean }) => ( + + + + + +); diff --git a/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/manual.warnings.json b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/manual.warnings.json new file mode 100644 index 0000000000..7f8443a697 --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/manual.warnings.json @@ -0,0 +1,7 @@ +[ + "'background' splits into surface-raised | surface-default", + "'border-primary' splits into border-default | border-subtle", + "'text-disabled' splits into action-disabled | surface-raised | text-subtle", + "'shadow-primary' splits into shadow-key | shadow-ambient", + "'background-contrast' is retired (closest: surface-raised)" +] diff --git a/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/strings.input.ts b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/strings.input.ts new file mode 100644 index 0000000000..f4f16d5b75 --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/strings.input.ts @@ -0,0 +1,13 @@ +import styled from '@emotion/styled'; + +export const Panel = styled.div` + color: var(--color-text); + border: 1px solid var(--color-border-secondary); +`; + +export const Raised = styled.div` + background: var(--color-background); +`; + +export const fill = 'var(--color-navy-800)'; +export const link = { color: 'var(--color-primary)' }; diff --git a/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/strings.output.ts b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/strings.output.ts new file mode 100644 index 0000000000..894da2d332 --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/strings.output.ts @@ -0,0 +1,13 @@ +import styled from '@emotion/styled'; + +export const Panel = styled.div` + color: var(--color-text-default); + border: 1px solid var(--color-border-default); +`; + +export const Raised = styled.div/* color-roles: 'background' splits into surface-raised | surface-default. Anything that sits on the page (card, input, menu, popover, dialog) takes `surface-raised`. Where `background` fills the page itself, it is `surface-default`. */ ` + background: var(--color-background); +`; + +export const fill = 'var(--color-navy-800)'; +export const link = { color: 'var(--color-action-primary)' }; diff --git a/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/strings.warnings.json b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/strings.warnings.json new file mode 100644 index 0000000000..c668608f2c --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/strings.warnings.json @@ -0,0 +1,4 @@ +[ + "'background' splits into", + "'--color-navy-800' is an old palette color" +] diff --git a/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/styles.input.ts b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/styles.input.ts new file mode 100644 index 0000000000..e11ae5ce51 --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/styles.input.ts @@ -0,0 +1,29 @@ +import styled from '@emotion/styled'; +import { css, states, themed, variant } from '@skillsoft/gamut-styles'; + +export const base = css({ + bg: 'primary', + color: 'background', + '&:hover': { bg: 'primary-hover' }, +}); + +export const variants = variant({ + variants: { + quiet: { textColor: 'text-secondary', borderColor: 'border-tertiary' }, + loud: { bg: 'danger', variant: 'primary' }, + }, +}); + +export const flags = states({ selected: { bg: 'background-selected' } }); + +export const Swatch = styled.div<{ variant: string }>` + color: ${({ theme }) => theme.colors.text}; + background: ${(props) => props.theme.colors['background-hover']}; + border-color: ${({ theme }) => theme.colors.background}; + outline-color: ${themed('colors.primary')}; +`; + +export const runtime = (v: string) => css({ bg: `${v}-hover` }); + +const kind = 'primary'; +export const untouched = { variant: kind, label: 'text' }; diff --git a/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/styles.output.ts b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/styles.output.ts new file mode 100644 index 0000000000..6b862c558d --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/styles.output.ts @@ -0,0 +1,29 @@ +import styled from '@emotion/styled'; +import { css, states, themed, variant } from '@skillsoft/gamut-styles'; + +export const base = css({ + bg: 'action-primary', + /* color-roles: 'background' splits into surface-raised | surface-default. Anything that sits on the page (card, input, menu, popover, dialog) takes `surface-raised`. Where `background` fills the page itself, it is `surface-default`. */ color: 'background', + '&:hover': { bg: 'action-primary-hover' }, +}); + +export const variants = variant({ + variants: { + quiet: { textColor: 'text-subtle', /* color-roles: 'border-tertiary' splits into border-subtle | border-strong. A divider or card hairline is `border-subtle`. Where it marks an active field or a selected row, it is `border-strong`. Gamut’s own two usages, the FeatureShimmer edge and the List scroll shadow, are both `border-subtle`. Core resolves it to a light navy and Percipio to a dark one, so check each app usage for which it meant. */ borderColor: 'border-tertiary' }, + loud: { bg: 'action-danger', variant: 'primary' }, + }, +}); + +export const flags = states({ selected: { bg: 'state-selected' } }); + +export const Swatch = styled.div<{ variant: string }>` + color: ${({ theme }) => theme.colors['text-default']}; + background: ${(props) => /* color-roles: 'background-hover' splits into state-hover | surface-sunken. As a hover, it is the `state-hover` veil. Used as a resting fill (the subtle Alert, a tinted panel), it is `surface-sunken`. */ props.theme.colors['background-hover']}; + border-color: ${({ theme }) => /* color-roles: 'background' splits into surface-raised | surface-default. Anything that sits on the page (card, input, menu, popover, dialog) takes `surface-raised`. Where `background` fills the page itself, it is `surface-default`. */ theme.colors.background}; + outline-color: ${themed('colors.action-primary')}; +`; + +export const runtime = (v: string) => css({ bg: `${v}-hover` }); + +const kind = 'primary'; +export const untouched = { variant: kind, label: 'text' }; diff --git a/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/styles.warnings.json b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/styles.warnings.json new file mode 100644 index 0000000000..ddd7fac912 --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/__testfixtures__/styles.warnings.json @@ -0,0 +1,6 @@ +[ + "fixture.tsx:6 'background' splits into", + "'border-tertiary' splits into border-subtle | border-strong", + "'background-hover' splits into state-hover | surface-sunken", + "color name built at runtime (`${v}-hover`)" +] diff --git a/packages/gamut-codemods/src/migrations/color-roles/cssVars.ts b/packages/gamut-codemods/src/migrations/color-roles/cssVars.ts new file mode 100644 index 0000000000..63c1ed4d9f --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/cssVars.ts @@ -0,0 +1,78 @@ +import type { + ColorAlias, + ColorRolesManifest, +} from '../../presets/color-roles/manifest'; +import { isAutomatic } from '../../presets/color-roles/manifest'; + +export const MARK = 'color-roles:'; + +/* "'background' splits into surface-raised | surface-default. " */ +export const describe = (name: string, { kind, to, rule }: ColorAlias) => { + const roles = to.join(' | '); + const what = + kind === 'split' + ? `splits into ${roles}` + : kind === 'retire' + ? `is retired${roles ? ` (closest: ${roles})` : ''}` + : 'is pending'; + return `'${name}' ${what}. ${rule}`; +}; + +/* The same, safe inside a block comment. */ +export const commentText = (name: string, alias: ColorAlias) => + `/* ${MARK} ${describe(name, alias).replace(/\*\//g, '* /')} */ `; + +/* Already marked by an earlier run: a color-roles comment right before `at`. */ +export const markedBefore = (text: string, at: number) => { + const before = text.slice(0, at).trimEnd(); + if (!before.endsWith('*/')) return false; + const open = before.lastIndexOf('/*'); + return open !== -1 && before.slice(open).includes(MARK); +}; + +/* + An old color variable: `--color-`. The rewritten name is `--color-`, + and no role is also an old name, so a second run leaves it alone. +*/ +const OLD_VAR = /--color-([A-Za-z0-9][\w-]*)/g; + +export const mentionsOldVar = (text: string) => + /--color-[A-Za-z0-9]/.test(text); + +export interface VarHit { + /* Offset of `--color-…` in the text. */ + at: number; + name: string; + /* The role it becomes, for renames and merges. */ + role?: string; + /* Design's row, for splits, retirements and pending names. */ + alias?: ColorAlias; + /* An old palette color (navy-800): no role replaces it by name. */ + palette?: boolean; +} + +/* Every old color variable in `text` the manifest knows about. */ +export const findOldVars = ( + text: string, + manifest: ColorRolesManifest +): VarHit[] => { + const palette = new Set(manifest.oldPalette); + const hits: VarHit[] = []; + for (const m of text.matchAll(OLD_VAR)) { + const name = m[1]; + const alias = manifest.aliases[name]; + if (alias) { + hits.push( + isAutomatic(alias) + ? { at: m.index, name, role: alias.to[0] } + : { at: m.index, name, alias } + ); + } else if (palette.has(name)) { + hits.push({ at: m.index, name, palette: true }); + } + } + return hits; +}; + +export const paletteMessage = (name: string) => + `'--color-${name}' is an old palette color with no role of its own. Pick the role from design's component map (@skillsoft/gamut-brand src/migration.ts, COMPONENTS).`; diff --git a/packages/gamut-codemods/src/migrations/color-roles/index.ts b/packages/gamut-codemods/src/migrations/color-roles/index.ts new file mode 100644 index 0000000000..ed27797bef --- /dev/null +++ b/packages/gamut-codemods/src/migrations/color-roles/index.ts @@ -0,0 +1,368 @@ +import type { Edit, SpliceMigration } from '../../lib/types'; +import type { ColorRolesManifest } from '../../presets/color-roles/manifest'; +import { isAutomatic } from '../../presets/color-roles/manifest'; +import { + commentText, + describe, + findOldVars, + MARK, + markedBefore, + mentionsOldVar, + paletteMessage, +} from './cssVars'; + +/* + Today's Gamut color names → roles, wherever a color name sits: + + - a color prop in JSX (`bg="background-primary"`) or in a style object + (`css`, `variant`, `states`, plain objects), including the branches of a + ternary or `&&`, and responsive objects and arrays; + - `theme.colors.text` / `theme.colors['text-disabled']`; + - `themed('colors.primary')`; + - `var(--color-text)` inside a string or template literal, which becomes + `var(--color-text-default)` (stylesheets are the + color-roles-stylesheets migration); + - Card's `variant`, onto design's `default` and `brand`. + + Rename and merge rows are applied. Split, retire and pending rows need a + person: the name stays (so it still fails the type check) and a + `/* color-roles: … *\/` comment with design's rule goes in front of it. + Old palette names (navy, hyper-500) and names built at runtime + (`${variant}-hover`) only warn. Anything outside those positions, like a + name in a const that's passed to `bg` later, isn't seen. +*/ + +/* + Card's variants are design's two: white, beige and navy + become `default`; yellow and hyper become `brand`. +*/ +const CARD_VARIANTS: Record = { + white: 'default', + beige: 'default', + navy: 'default', + yellow: 'brand', + hyper: 'brand', +}; + +const NAVY_CARD = `/* ${MARK} Card's 'navy' variant becomes 'default' (surface-raised), which renders light. If this card should stay dark, wrap it in . */ `; + +const GAMUT = /(^|\/)gamut$/; + +/* Babel AST nodes, loosely: positions plus whatever the type carries. */ +interface Node { + type: string; + start: number; + end: number; + loc?: { start: { line: number } } | null; + [key: string]: any; +} + +const isString = (n: Node | null | undefined): n is Node & { value: string } => + !!n && + (n.type === 'StringLiteral' || + (n.type === 'Literal' && typeof n.value === 'string')); + +const keyName = (prop: Node): string | undefined => { + if (prop.computed) return undefined; + const { key } = prop; + if (key?.type === 'Identifier') return key.name; + if (isString(key)) return key.value; + return undefined; +}; + +/* `theme.colors` or `.theme.colors`. */ +const isThemeColors = (n: Node | null | undefined) => + !!n && + (n.type === 'MemberExpression' || n.type === 'OptionalMemberExpression') && + !n.computed && + n.property?.name === 'colors' && + ((n.object?.type === 'Identifier' && n.object.name === 'theme') || + (n.object?.type?.endsWith('MemberExpression') && + !n.object.computed && + n.object.property?.name === 'theme')); + +export const colorRoles: SpliceMigration = { + name: 'color-roles', + kind: 'splice', + description: + 'Rename Gamut color names to roles; mark splits, retirements and pending names with design’s rule.', + edits({ j, root, source, manifest, warn, note }) { + const props = new Set(manifest.colorProps); + const palette = new Set(manifest.oldPalette); + const edits: Edit[] = []; + const seen = new Set(); + + const marked = (at: number) => markedBefore(source, at); + + const mark = (at: number, name: string) => { + const alias = manifest.aliases[name]; + if (!marked(at)) { + edits.push({ + start: at, + end: at, + text: commentText(name, alias), + }); + } + }; + + /* + One color name. `replace` swaps it for a role; `markAt` is where a + comment can go (in front of a JSX attribute, not inside it). + */ + const visit = ( + node: Node, + name: string, + replace: (role: string) => Edit, + markAt: number + ) => { + if (seen.has(node.start)) return; + seen.add(node.start); + const alias = manifest.aliases[name]; + if (alias) { + if (isAutomatic(alias)) { + edits.push(replace(alias.to[0])); + } else { + mark(markAt, name); + warn(node, describe(name, alias)); + note('color-roles:manual'); + } + } else if (palette.has(name)) { + warn( + node, + `'${name}' is an old palette color with no role of its own. Pick the role from design's component map (@skillsoft/gamut-brand src/migration.ts, COMPONENTS).` + ); + note('color-roles:palette'); + } + }; + + const visitString = (lit: Node, markAt: number) => + visit( + lit, + lit.value, + (role) => ({ start: lit.start + 1, end: lit.end - 1, text: role }), + markAt + ); + + /* Every string a color-valued expression can produce. */ + const collect = (expr: Node | null | undefined, markAt?: number) => { + if (!expr) return; + switch (expr.type) { + case 'StringLiteral': + case 'Literal': + if (isString(expr)) visitString(expr, markAt ?? expr.start); + return; + case 'ConditionalExpression': + collect(expr.consequent); + collect(expr.alternate); + return; + case 'LogicalExpression': + collect(expr.left); + collect(expr.right); + return; + case 'ObjectExpression': + for (const p of expr.properties) { + if (p.type === 'ObjectProperty' || p.type === 'Property') { + collect(p.value); + } + } + return; + case 'ArrayExpression': + expr.elements.forEach((el: Node) => collect(el)); + return; + case 'TSAsExpression': + case 'TSSatisfiesExpression': + case 'TSNonNullExpression': + case 'ParenthesizedExpression': + case 'TypeCastExpression': + collect(expr.expression, markAt); + return; + case 'TemplateLiteral': + if (expr.expressions.length > 0) { + warn( + expr, + `color name built at runtime (${source.slice( + expr.start, + expr.end + )}). The codemod can't map it; check each value it can take.` + ); + note('color-roles:runtime'); + } + return; + default: + } + }; + + root.find(j.JSXAttribute).forEach((p) => { + const attr = p.node as unknown as Node; + if (attr.name?.type !== 'JSXIdentifier' || !props.has(attr.name.name)) { + return; + } + const { value } = attr; + if (isString(value)) collect(value, attr.start); + else if (value?.type === 'JSXExpressionContainer') { + collect(value.expression); + } + }); + + root.find(j.Node).forEach((p) => { + const node = p.node as unknown as Node; + if (node.type === 'ObjectProperty' || node.type === 'Property') { + const key = keyName(node); + if (key && props.has(key)) { + collect(node.value, isString(node.value) ? node.start : undefined); + } + return; + } + + if ( + (node.type === 'MemberExpression' || + node.type === 'OptionalMemberExpression') && + isThemeColors(node.object) + ) { + const { property } = node; + const name = node.computed + ? isString(property) + ? property.value + : undefined + : property?.name; + if (!name) return; + visit( + node, + name, + (role) => + node.computed + ? { start: property.start + 1, end: property.end - 1, text: role } + : { + /* `.text` → `['text-default']`, keeping a `?.` */ + start: node.object.end, + end: property.end, + text: `${ + source.slice(node.object.end, property.start).includes('?.') + ? '?.' + : '' + }['${role}']`, + }, + node.start + ); + return; + } + + if ( + node.type === 'CallExpression' && + node.callee?.type === 'Identifier' && + node.callee.name === 'themed' && + isString(node.arguments?.[0]) && + node.arguments[0].value.startsWith('colors.') + ) { + const arg = node.arguments[0]; + const name = arg.value.slice('colors.'.length); + visit( + arg, + name, + (role) => ({ + start: arg.start + 1, + end: arg.end - 1, + text: `colors.${role}`, + }), + node.start + ); + } + }); + + /* + `var(--color-…)` in a string or template literal. A rename is applied + in place; a name for a person gets its comment in front of the + literal, outside the string. + */ + root.find(j.Node).forEach((p) => { + const node = p.node as unknown as Node; + const literal = isString(node) || node.type === 'TemplateLiteral'; + if (!literal) return; + const raw = source.slice(node.start, node.end); + if (!mentionsOldVar(raw)) return; + for (const hit of findOldVars(raw, manifest)) { + const at = node.start + hit.at; + if (hit.role) { + edits.push({ + start: at, + end: at + `--color-${hit.name}`.length, + text: `--color-${hit.role}`, + }); + } else if (hit.alias) { + if (!marked(node.start) && !seen.has(node.start)) { + seen.add(node.start); + edits.push({ + start: node.start, + end: node.start, + text: commentText(hit.name, hit.alias), + }); + } + warn(node, describe(hit.name, hit.alias)); + note('color-roles:manual'); + } else if (hit.palette) { + warn(node, paletteMessage(hit.name)); + note('color-roles:palette'); + } + } + }); + + /* Card's variant, when Card comes from gamut. */ + const cardNames = new Set(); + root.find(j.ImportDeclaration).forEach((p) => { + const decl = p.node as unknown as Node; + if (!GAMUT.test(decl.source.value)) return; + for (const spec of decl.specifiers ?? []) { + if (spec.type === 'ImportSpecifier' && spec.imported?.name === 'Card') { + cardNames.add(spec.local?.name ?? 'Card'); + } + } + }); + if (cardNames.size > 0) { + root.find(j.JSXOpeningElement).forEach((p) => { + const el = p.node as unknown as Node; + if (el.name?.type !== 'JSXIdentifier' || !cardNames.has(el.name.name)) { + return; + } + for (const attr of el.attributes ?? []) { + if (attr.type !== 'JSXAttribute' || attr.name?.name !== 'variant') { + continue; + } + const lits: Node[] = []; + const gather = (e: Node | null | undefined) => { + if (!e) return; + if (isString(e)) lits.push(e); + else if (e.type === 'ConditionalExpression') { + gather(e.consequent); + gather(e.alternate); + } else if (e.type === 'LogicalExpression') gather(e.right); + }; + if (isString(attr.value)) gather(attr.value); + else if (attr.value?.type === 'JSXExpressionContainer') { + gather(attr.value.expression); + } + for (const lit of lits) { + const to = CARD_VARIANTS[lit.value]; + if (!to) continue; + edits.push({ start: lit.start + 1, end: lit.end - 1, text: to }); + if (lit.value === 'navy') { + if (!marked(attr.start)) { + edits.push({ + start: attr.start, + end: attr.start, + text: NAVY_CARD, + }); + } + warn( + lit, + "Card's 'navy' variant becomes 'default', which renders light. Wrap the card in if it should stay dark." + ); + note('color-roles:card-navy'); + } + } + } + }); + } + + return edits; + }, +}; diff --git a/packages/gamut-codemods/src/presets/color-roles/aliases.ts b/packages/gamut-codemods/src/presets/color-roles/aliases.ts new file mode 100644 index 0000000000..97c992222b --- /dev/null +++ b/packages/gamut-codemods/src/presets/color-roles/aliases.ts @@ -0,0 +1,331 @@ +import type { ColorAlias, ColorRolesManifest } from './manifest'; + +/* + Generated from design's migration map: @skillsoft/gamut-brand + src/migration.ts (TOKEN_MAP). Regenerate when design's map changes + (~/z_team/everest/color-tokens/codemod/gen-manifest.ts); don't edit rows by + hand. +*/ +export const aliases: Record = { + text: { + kind: 'rename', + to: ['text-default'], + rule: 'Body copy and headings.', + }, + 'text-accent': { + kind: 'merge', + to: ['text-default'], + rule: 'Headings take `text-default` too. Emphasis comes from weight and size, not from a second ink.', + }, + 'text-secondary': { + kind: 'rename', + to: ['text-subtle'], + rule: 'Descriptions, metadata, helper copy. An opaque color in every theme, so it no longer depends on what sits underneath it.', + }, + 'text-disabled': { + kind: 'split', + to: ['action-disabled', 'surface-raised', 'text-subtle'], + rule: 'A disabled label or icon drawn straight on a surface (text button, link, menu item, form label) is `action-disabled`, the same color as a disabled fill. On a disabled fill the label is a knockout: it takes the surface the control sits on, usually `surface-raised`. The value inside a disabled input is the one place `text-subtle` stays. The proposal describes `action-disabled` as a fill only; its use as the disabled ink is an addition for the proposal’s next revision.', + }, + 'feedback-error': { + kind: 'rename', + to: ['text-error'], + rule: 'Error text and icons, including inline validation and the border of a field in error. Destructive buttons are `action-danger`, not this.', + }, + 'feedback-success': { + kind: 'rename', + to: ['text-success'], + rule: 'Success text and icons. Always with an icon.', + }, + 'feedback-warning': { + kind: 'rename', + to: ['text-warning'], + rule: 'Warning text and icons. Dark enough to be read as text, so it can carry a message on its own rather than only an icon.', + }, + background: { + kind: 'split', + to: ['surface-raised', 'surface-default'], + rule: 'Anything that sits on the page (card, input, menu, popover, dialog) takes `surface-raised`. Where `background` fills the page itself, it is `surface-default`.', + }, + 'background-primary': { + kind: 'rename', + to: ['surface-default'], + rule: 'The page background, the base every other surface is placed on.', + }, + 'background-current': { + kind: 'split', + to: ['surface-default', 'surface-raised', 'surface-sunken'], + rule: 'Whatever surface the element sits on. Becomes a `surface-current` alias that every surface container (the page, raised, sunken, `Background`) sets to its own role, so it always resolves to one of these three. Masks and backdrops that must match their parent read it: sticky List headers, scroll and truncation fades, Drawer, DataList empty rows, the transparent Disclosure. Nothing sets it by hand.', + }, + 'background-contrast': { + kind: 'retire', + to: ['surface-raised'], + rule: 'Use `surface-raised`. Maximum contrast against the page is what raised already provides in dark mode, and light mode has no surface lighter than it. In Gamut it backs ToolTip and InfoTip, block Tabs and the video player’s tooltip and slider value, all remapped; none needs it to be black.', + }, + 'background-selected': { + kind: 'rename', + to: ['state-selected'], + rule: 'A veil composited over the element’s own fill, not an opaque fill that replaces it. The element keeps its surface underneath.', + }, + 'background-hover': { + kind: 'split', + to: ['state-hover', 'surface-sunken'], + rule: 'As a hover, it is the `state-hover` veil. Used as a resting fill (the subtle Alert, a tinted panel), it is `surface-sunken`.', + }, + 'background-disabled': { + kind: 'rename', + to: ['action-disabled'], + rule: 'The disabled fill for every action variant. One token, whatever was disabled.', + }, + 'background-success': { + kind: 'rename', + to: ['surface-success'], + rule: 'The success tint behind an alert or inline confirmation.', + }, + 'background-warning': { + kind: 'rename', + to: ['surface-warning'], + rule: 'The warning tint behind an alert or inline caution.', + }, + 'background-error': { + kind: 'rename', + to: ['surface-error'], + rule: 'The error tint behind an alert or inline validation message.', + }, + 'shadow-primary': { + kind: 'split', + to: ['shadow-key', 'shadow-ambient'], + rule: 'Every elevation shadow composes both: `shadow-key` for the tight contact layer, `shadow-ambient` for the soft falloff. The elevation tokens hold only offset, blur and spread; see the elevation system for which depth uses which geometry.', + }, + 'shadow-secondary': { + kind: 'split', + to: ['shadow-key', 'shadow-ambient'], + rule: 'Same as `shadow-primary`. Depth comes from geometry, not from a second color, so both legacy tokens land on the same pair.', + }, + primary: { + kind: 'rename', + to: ['action-primary'], + rule: 'Primary fills, strokes, links and focus rings. Pressed is new: `action-primary-pressed`.', + }, + 'primary-hover': { + kind: 'rename', + to: ['action-primary-hover'], + rule: 'Hover for the primary action.', + }, + 'primary-inverse': { + kind: 'retire', + to: ['action-primary'], + rule: 'A primary action on an inverted ground is `action-primary` inside a dark-mode scope. The mode does the inverting.', + }, + secondary: { + kind: 'rename', + to: ['action-neutral'], + rule: 'The secondary button and secondary icon/text buttons. Labels on it are `text-inverse`.', + }, + 'secondary-hover': { + kind: 'rename', + to: ['action-neutral-hover'], + rule: 'Hover for the secondary action.', + }, + danger: { + kind: 'rename', + to: ['action-danger'], + rule: 'Destructive actions: delete, remove, revoke. Error messaging is `text-error`.', + }, + 'danger-hover': { + kind: 'rename', + to: ['action-danger-hover'], + rule: 'Hover for the destructive action.', + }, + interface: { + kind: 'merge', + to: ['action-primary'], + rule: 'Checkboxes, radios, toggles, focus rings and inline links share the one interactive blue.', + }, + 'interface-hover': { + kind: 'merge', + to: ['action-primary-hover'], + rule: 'Hover for the same controls.', + }, + 'border-primary': { + kind: 'split', + to: ['border-default', 'border-subtle'], + rule: 'A border that carries meaning (field outline, control stroke, a container that needs an edge to be seen) is `border-default`. A border that only decorates or backs up a shadow (card hairline, table rule, the edge of a menu, popover, toast or dialog) is `border-subtle`.', + }, + 'border-secondary': { + kind: 'merge', + to: ['border-default'], + rule: 'Same as `border-primary`. One meaningful border weight.', + }, + 'border-tertiary': { + kind: 'split', + to: ['border-subtle', 'border-strong'], + rule: 'A divider or card hairline is `border-subtle`. Where it marks an active field or a selected row, it is `border-strong`. Gamut’s own two usages, the FeatureShimmer edge and the List scroll shadow, are both `border-subtle`. Core resolves it to a light navy and Percipio to a dark one, so check each app usage for which it meant.', + }, + 'border-disabled': { + kind: 'merge', + to: ['border-default'], + rule: 'A disabled control is marked by its `action-disabled` fill. Its outline stays `border-default`.', + }, +}; + +/* Every role the generated system has (gamut-brand src/roles.ts), so the manifest test can check targets. */ +export const roles: ColorRolesManifest['roles'] = [ + 'surface-sunken', + 'surface-default', + 'surface-raised', + 'surface-success', + 'surface-warning', + 'surface-error', + 'surface-brand', + 'surface-brand-subtle', + 'text-default', + 'text-subtle', + 'text-inverse', + 'text-success', + 'text-warning', + 'text-error', + 'border-strong', + 'border-default', + 'border-subtle', + 'action-primary', + 'action-primary-hover', + 'action-primary-pressed', + 'action-danger', + 'action-danger-hover', + 'action-danger-pressed', + 'action-neutral', + 'action-neutral-hover', + 'action-neutral-pressed', + 'action-disabled', + 'state-hover', + 'state-pressed', + 'state-selected', + 'viz-1', + 'viz-2', + 'viz-3', + 'viz-4', + 'viz-5', + 'viz-6', + 'viz-7', + 'viz-8', + 'viz-seq-1', + 'viz-seq-2', + 'viz-seq-3', + 'viz-seq-4', + 'shadow-key', + 'shadow-ambient', + 'shadow-scrim', +]; + +/* + Every palette name the old themes define (core, admin, platform, percipio, + lxStudio) that isn't an alias, from main's built themes. No role replaces + them by name; design maps them per component. +*/ +export const oldPalette: ColorRolesManifest['oldPalette'] = [ + 'beige', + 'beige-0', + 'beige-100', + 'blue', + 'blue-0', + 'blue-100', + 'blue-300', + 'blue-400', + 'blue-500', + 'blue-800', + 'comment-dark', + 'comment-light', + 'gold', + 'gold-800', + 'gray-100', + 'gray-200', + 'gray-300', + 'gray-600', + 'gray-800', + 'gray-900', + 'green', + 'green-0', + 'green-100', + 'green-400', + 'green-700', + 'green-900', + 'hyper', + 'hyper-400', + 'hyper-500', + 'indent-active-dark', + 'indent-active-light', + 'indent-inactive-dark', + 'indent-inactive-light', + 'lightBeige', + 'lightBlue', + 'lightGreen', + 'line-number-active-dark', + 'line-number-active-light', + 'line-number-inactive-dark', + 'line-number-inactive-light', + 'lxStudioBgPrimary', + 'lxStudioSuccess', + 'navy', + 'navy-100', + 'navy-200', + 'navy-300', + 'navy-400', + 'navy-500', + 'navy-600', + 'navy-700', + 'navy-800', + 'navy-900', + 'orange', + 'orange-100', + 'orange-500', + 'orange-800', + 'paleBlue', + 'paleGreen', + 'palePink', + 'paleRed', + 'paleYellow', + 'percipioActionDangerHover', + 'percipioActionPrimaryHover', + 'percipioActionSecondary', + 'percipioActionSecondaryHover', + 'percipioBgError', + 'percipioBgPrimary', + 'percipioBgSuccess', + 'percipioBgWarning', + 'percipioDanger', + 'percipioFeedbackSuccess', + 'percipioFeedbackWarning', + 'percipioTextAccent', + 'percipioTextDisabled', + 'percipioTextPrimary', + 'percipioTextSecondary', + 'pink', + 'pink-0', + 'pink-400', + 'pink-800', + 'purple', + 'purple-300', + 'red', + 'red-0', + 'red-300', + 'red-400', + 'red-500', + 'red-600', + 'red-900', + 'sapphire', + 'teal', + 'teal-500', + 'white-100', + 'white-200', + 'white-300', + 'white-400', + 'white-500', + 'white-600', + 'white-700', + 'yellow', + 'yellow-0', + 'yellow-400', + 'yellow-500', + 'yellow-900', +]; diff --git a/packages/gamut-codemods/src/presets/color-roles/index.ts b/packages/gamut-codemods/src/presets/color-roles/index.ts new file mode 100644 index 0000000000..2c5fb2b498 --- /dev/null +++ b/packages/gamut-codemods/src/presets/color-roles/index.ts @@ -0,0 +1,73 @@ +import fs from 'node:fs'; + +import type { Leftover } from '../../lib/leftovers'; +import { walk } from '../../lib/leftovers'; +import type { Preset } from '../../lib/types'; +import { colorRoles } from '../../migrations/color-roles'; +import { colorRolesStylesheets } from '../../migrations/color-roles-stylesheets'; +import { colorRolesThemes } from '../../migrations/color-roles-themes'; +import type { ColorRolesManifest } from './manifest'; +import { manifest } from './manifest'; + +const escape = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + +/* + A quoted old name, one read off `theme.colors`, an old `--color-*` + variable, a Card (whose variants are renamed), or an old theme name. +*/ +const OLD_NAMES = new RegExp( + `['"\`](?:${[...Object.keys(manifest.aliases), ...manifest.oldPalette] + .map(escape) + .join( + '|' + )})['"\`]|colors\\.[A-Za-z]|--color-[A-Za-z0-9]| = { + name: 'color-roles', + description: + 'Move Gamut color names onto the roles of the generated color system.', + manifest, + mentions: (source) => OLD_NAMES.test(source), + /* Every name the run marked for a person and nobody has resolved yet. */ + leftovers(roots) { + const hits: Leftover[] = []; + for (const root of roots) { + const files = fs.statSync(root).isDirectory() ? walk(root) : [root]; + for (const file of files) { + if (!SOURCE.test(file)) continue; + fs.readFileSync(file, 'utf8') + .split('\n') + .forEach((text, i) => { + if (text.includes('color-roles:')) { + hits.push({ file, line: i + 1, text: text.trim() }); + } + }); + } + } + return hits; + }, + migrations: [colorRoles, colorRolesThemes, colorRolesStylesheets], + checklist: [ + 'Run your formatter (prettier --write). The `color-roles:` comments can make lines long.', + 'Type-check. Every name the codemod left in place is still an old name, so it fails until someone picks its role.', + 'Work through the leftovers: each `color-roles:` comment carries design’s rule for choosing. Replace the name, then delete the comment.', + ], + conditionalChecklist: { + 'color-roles:manual': + 'Some names are splits, retirements or pending (see the warnings). Splits need the role for where the color is used; retired names have a rule instead of a role; pending names wait for a system that doesn’t exist yet. Shadows split onto `shadow-key` and `shadow-ambient`, the two layers every elevation shadow draws.', + 'color-roles:stylesheets': + 'Stylesheets used old `--color-*` variables. Renames are done; the type check can’t see stylesheets, so the leftovers report below is the only list of what’s left. Indented-syntax .sass files aren’t rewritten: search them for `--color-` by hand.', + 'color-roles:themes': + 'Old theme names moved: `percipioTheme` → `skillsoftTheme` (its replacement) and `coreTheme` → `originalCodecademyTheme` (its rebuild). A `CoreTheme` type, usually in a Theme augmentation, is left with a comment: brand themes share one shape, typed by `SkillsoftTheme`.', + 'color-roles:card-navy': + 'Some Cards used the `navy` variant. It becomes `default`, which renders light; wrap a card in `` where it should stay dark, then delete the comment.', + 'color-roles:palette': + 'Some code uses old palette colors (navy, hyper-500, …) directly. No role replaces them by name. Pick each one from design’s component map, in @skillsoft/gamut-brand src/migration.ts (COMPONENTS).', + 'color-roles:runtime': + 'Some color names are built at runtime, like `${variant}-hover`. The codemod can’t see their values; check each name the template can produce.', + }, +}; diff --git a/packages/gamut-codemods/src/presets/color-roles/manifest.ts b/packages/gamut-codemods/src/presets/color-roles/manifest.ts new file mode 100644 index 0000000000..15e2186b91 --- /dev/null +++ b/packages/gamut-codemods/src/presets/color-roles/manifest.ts @@ -0,0 +1,56 @@ +import { aliases, oldPalette, roles } from './aliases'; + +/* + color-roles: today's Gamut color names → the roles of the generated color + system. The rows are design's (aliases.ts, generated); this file holds the + shape and the props a color can appear in. +*/ + +/* + - rename / merge: one role replaces the name. Applied. + - split: one of several roles, depending on where it's used. + - retire: no role replaces it; `rule` says what to do instead. + - pending: decided, but its role arrives with a system that doesn't exist + yet. No row is pending today; shadows became a split. + The last three need a person; the codemod leaves the name and a comment. +*/ +export type ColorKind = 'rename' | 'merge' | 'split' | 'retire' | 'pending'; + +export interface ColorAlias { + kind: ColorKind; + /* Roles, most common first. Empty for some retire and pending rows. */ + to: string[]; + rule: string; +} + +export interface ColorRolesManifest { + aliases: Record; + /* Every role of the generated system; alias targets must be among them. */ + roles: string[]; + /* Old palette names (navy, hyper-500, …). No role replaces them by name. */ + oldPalette: string[]; + /* Props whose values are color names: gamut-styles variance/config.ts `color`. */ + colorProps: string[]; +} + +export const manifest: ColorRolesManifest = { + aliases, + roles, + oldPalette, + colorProps: [ + 'color', + 'textColor', + 'bg', + 'borderColor', + 'borderColorX', + 'borderColorY', + 'borderColorLeft', + 'borderColorRight', + 'borderColorTop', + 'borderColorBottom', + ], +}; + +/* Applied by the codemod, vs. left for a person. */ +export const isAutomatic = (alias: ColorAlias) => + alias.kind === 'rename' || alias.kind === 'merge'; diff --git a/packages/gamut-codemods/src/presets/index.ts b/packages/gamut-codemods/src/presets/index.ts index 2ff0c9294a..76c153c249 100644 --- a/packages/gamut-codemods/src/presets/index.ts +++ b/packages/gamut-codemods/src/presets/index.ts @@ -1,4 +1,5 @@ import type { Preset } from '../lib/types'; +import { colorRolesPreset } from './color-roles'; import { scopeSwap } from './scope-swap'; /* @@ -7,8 +8,10 @@ import { scopeSwap } from './scope-swap'; Later presets are named for the release they target and can reuse migrations from src/migrations with a different manifest. */ -export const presets: Record = { +/* Each preset has its own manifest type; callers only pass it through. */ +export const presets: Record> = { [scopeSwap.name]: scopeSwap, + [colorRolesPreset.name]: colorRolesPreset, }; /* @@ -17,7 +20,7 @@ export const presets: Record = { switch, so presets have to list every `ast` migration before any `splice` one. */ -export const validatePreset = (preset: Preset) => { +export const validatePreset = (preset: Preset) => { const source = preset.migrations.filter((m) => m.kind !== 'file'); const firstSplice = source.findIndex((m) => m.kind === 'splice'); const lateAst = diff --git a/packages/gamut-codemods/src/presets/scope-swap/index.ts b/packages/gamut-codemods/src/presets/scope-swap/index.ts index c08b1b14e7..57b1f3b497 100644 --- a/packages/gamut-codemods/src/presets/scope-swap/index.ts +++ b/packages/gamut-codemods/src/presets/scope-swap/index.ts @@ -1,3 +1,6 @@ +import { findFlatEslintConfig } from '../../lib/eslint'; +import { findLeftovers } from '../../lib/leftovers'; +import { mentionsOldNames } from '../../lib/run-source'; import type { Preset } from '../../lib/types'; import { deepImports } from '../../migrations/deep-imports'; import { eslintComments } from '../../migrations/eslint-comments'; @@ -17,6 +20,11 @@ export const scopeSwap: Preset = { name: 'scope-swap', description: 'Move from @codecademy/gamut* to @skillsoft/gamut*.', manifest, + mentions: (source) => mentionsOldNames(manifest, source), + leftovers: (roots) => + findLeftovers(roots, manifest, { + eslintRuleKeys: !findFlatEslintConfig(roots), + }), migrations: [ deepImports, movedExports, diff --git a/packages/gamut-codemods/src/transform.ts b/packages/gamut-codemods/src/transform.ts index e1e60939f0..be7cf254b4 100644 --- a/packages/gamut-codemods/src/transform.ts +++ b/packages/gamut-codemods/src/transform.ts @@ -3,11 +3,7 @@ import path from 'node:path'; import type { API, FileInfo } from 'jscodeshift'; -import { - activeSourceMigrations, - mentionsOldNames, - runSourceMigrations, -} from './lib/run-source'; +import { activeSourceMigrations, runSourceMigrations } from './lib/run-source'; import type { Locatable } from './lib/types'; import { getPreset } from './presets'; @@ -41,7 +37,7 @@ export default function transform( ) { const preset = getPreset(options.preset ?? 'scope-swap'); const { manifest } = preset; - if (!mentionsOldNames(manifest, file.source)) return undefined; + if (!preset.mentions(file.source)) return undefined; const note = (key: string) => { if (options.notesDir) { diff --git a/packages/gamut-docs/astro.config.mjs b/packages/gamut-docs/astro.config.mjs index 5a29b58baf..e8053cbded 100644 --- a/packages/gamut-docs/astro.config.mjs +++ b/packages/gamut-docs/astro.config.mjs @@ -94,7 +94,10 @@ export default defineConfig({ baseUrl: 'https://github.com/Codecademy/skillsoft-gamut/edit/main/packages/gamut-docs/', }, - customCss: ['./src/styles/gamut-core-theme.css'], + customCss: [ + '@skillsoft/gamut-styles/tokens.css', + './src/styles/gamut-theme.css', + ], plugins: [ starlightSidebarTopics( [ diff --git a/packages/gamut-docs/src/content/docs/components/actions/Buttons/cta-button.mdx b/packages/gamut-docs/src/content/docs/components/actions/Buttons/cta-button.mdx index ebbf83aabe..f9ce99ca81 100644 --- a/packages/gamut-docs/src/content/docs/components/actions/Buttons/cta-button.mdx +++ b/packages/gamut-docs/src/content/docs/components/actions/Buttons/cta-button.mdx @@ -30,7 +30,7 @@ Use this for a single, high-visibility marketing action — the offset shadow an - **Anything other than the single most important action on the page** — use [FillButton](/components/actions/buttons/fill-button/) instead. - **An icon alongside the label** — `CTAButton` doesn't support the `icon`/`iconPosition` props the other buttons do; use [FillButton](/components/actions/buttons/fill-button/) if an inline icon is required. - **Icon-only actions** — use [IconButton](/components/actions/buttons/icon-button/) instead; it requires an accessible name via its `tip` prop. -- **Outside its color-mode context** — don't place a light-mode button on a navy background; wrap the surface in `Background` instead of styling around it. +- **Outside its color-mode context** — don't place a light-mode button on a dark background; wrap the surface in `Background` instead of styling around it. ### Anatomy diff --git a/packages/gamut-docs/src/content/docs/components/actions/Buttons/fill-button.mdx b/packages/gamut-docs/src/content/docs/components/actions/Buttons/fill-button.mdx index 8ff9ed6fef..211bc417ec 100644 --- a/packages/gamut-docs/src/content/docs/components/actions/Buttons/fill-button.mdx +++ b/packages/gamut-docs/src/content/docs/components/actions/Buttons/fill-button.mdx @@ -33,7 +33,7 @@ Use this for primary actions. Include a leading or trailing icon from our mini i - **Low-emphasis, inline actions** — use [TextButton](/components/actions/buttons/text-button/) instead. - **Icon-only actions** — use [IconButton](/components/actions/buttons/icon-button/) instead; it requires an accessible name via its `tip` prop. - **Marketing / high-visibility CTAs** — use [CTAButton](/components/actions/buttons/cta-button/) instead. -- **Outside its color-mode context** — don't place a light-mode button on a navy background; wrap the surface in `Background` instead of styling around it. +- **Outside its color-mode context** — don't place a light-mode button on a dark background; wrap the surface in `Background` instead of styling around it. ### Anatomy diff --git a/packages/gamut-docs/src/content/docs/components/actions/Buttons/icon-button.mdx b/packages/gamut-docs/src/content/docs/components/actions/Buttons/icon-button.mdx index 11dc87a327..7c00899037 100644 --- a/packages/gamut-docs/src/content/docs/components/actions/Buttons/icon-button.mdx +++ b/packages/gamut-docs/src/content/docs/components/actions/Buttons/icon-button.mdx @@ -30,7 +30,7 @@ Use this for secondary or space-constrained actions with a recognizable icon and - **A visible label fits** — use [TextButton](/components/actions/buttons/text-button/), [StrokeButton](/components/actions/buttons/stroke-button/), or [FillButton](/components/actions/buttons/fill-button/) instead; don't reach for an icon-only button just to save space if a label would fit. - **Marketing / high-visibility CTAs** — use [CTAButton](/components/actions/buttons/cta-button/) instead. -- **Outside its color-mode context** — don't place a light-mode button on a navy background; wrap the surface in `Background` instead of styling around it. +- **Outside its color-mode context** — don't place a light-mode button on a dark background; wrap the surface in `Background` instead of styling around it. ### Anatomy diff --git a/packages/gamut-docs/src/content/docs/components/actions/Buttons/stroke-button.mdx b/packages/gamut-docs/src/content/docs/components/actions/Buttons/stroke-button.mdx index acedbc1ef8..21df320012 100644 --- a/packages/gamut-docs/src/content/docs/components/actions/Buttons/stroke-button.mdx +++ b/packages/gamut-docs/src/content/docs/components/actions/Buttons/stroke-button.mdx @@ -31,7 +31,7 @@ Use this for secondary actions. Include a leading or trailing icon from our mini - **Low-emphasis, inline actions** — use [TextButton](/components/actions/buttons/text-button/) instead. - **Icon-only actions** — use [IconButton](/components/actions/buttons/icon-button/) instead; it requires an accessible name via its `tip` prop. - **Marketing / high-visibility CTAs** — use [CTAButton](/components/actions/buttons/cta-button/) instead. -- **Outside its color-mode context** — don't place a light-mode button on a navy background; wrap the surface in `Background` instead of styling around it. +- **Outside its color-mode context** — don't place a light-mode button on a dark background; wrap the surface in `Background` instead of styling around it. ### Anatomy diff --git a/packages/gamut-docs/src/content/docs/components/actions/Buttons/text-button.mdx b/packages/gamut-docs/src/content/docs/components/actions/Buttons/text-button.mdx index 2978300e1b..145c7911b7 100644 --- a/packages/gamut-docs/src/content/docs/components/actions/Buttons/text-button.mdx +++ b/packages/gamut-docs/src/content/docs/components/actions/Buttons/text-button.mdx @@ -32,7 +32,7 @@ Use this for tertiary actions. Include a leading or trailing icon from our mini - **Secondary / outlined actions** — use [StrokeButton](/components/actions/buttons/stroke-button/) instead. - **Icon-only actions** — use [IconButton](/components/actions/buttons/icon-button/) instead; it requires an accessible name via its `tip` prop. - **Marketing / high-visibility CTAs** — use [CTAButton](/components/actions/buttons/cta-button/) instead. -- **Outside its color-mode context** — don't place a light-mode button on a navy background; wrap the surface in `Background` instead of styling around it. +- **Outside its color-mode context** — don't place a light-mode button on a dark background; wrap the surface in `Background` instead of styling around it. ### Anatomy diff --git a/packages/gamut-docs/src/content/docs/components/containers/card.mdx b/packages/gamut-docs/src/content/docs/components/containers/card.mdx index 09c710b9e6..a99cbce47d 100644 --- a/packages/gamut-docs/src/content/docs/components/containers/card.mdx +++ b/packages/gamut-docs/src/content/docs/components/containers/card.mdx @@ -1,6 +1,6 @@ --- title: Card -description: A contained surface with background color variants, optional shadow/pattern treatments, and hover behavior. +description: A contained surface with two variants, optional shadow/pattern treatments, and hover behavior. --- import FigmaEmbed from '~/components/FigmaEmbed.astro'; @@ -14,8 +14,8 @@ Use `Card` to present a piece of information as a distinct, contained surface, w **Best practices:** -- Use `variant="default"` (the default) for a background that follows the current color mode automatically; use `white`/`yellow`/`beige` inside a light-mode surface, or `navy`/`hyper` inside a dark-mode surface, when a static color is what's needed. -- Add `isInteractive` when wrapping `Card` in an `Anchor` — it applies a `'md'` border radius and a hover shadow effect, and a card with a pattern loses its pattern on hover. +- Use `variant="default"` (the default) for a card on `surface-raised`, which follows the color mode. Use `variant="brand"` for a featured card on the brand gradient. Every card has a `border-subtle` hairline. The old color variants (`white`, `beige`, `navy`, `yellow`, `hyper`) are gone; for a dark card, wrap it in ``. +- Add `isInteractive` when wrapping `Card` in an `Anchor`. It applies an `'md'` border radius and the theme's raised shadow, and on hover a `state-hover` layer and a deeper shadow; a card with a pattern loses its pattern on hover. A card that isn't interactive has no shadow. - `borderRadius` defaults to `'none'` (non-interactive) or `'md'` (`isInteractive`), and can be overridden — the outline/pattern shadow automatically matches whatever `borderRadius` is set. **When NOT to use:** @@ -34,25 +34,23 @@ The shadow/pattern treatment (`shadow`) always renders along the bottom edge plu Wrap `Card` in an `Anchor` and add `isInteractive` to get the hover shadow and `'md'` border radius: - + ## Prop Reference ### Variant - - - + + ### Shadow - - - - + + + ### Border radius `borderRadius` defaults to `'none'`, or `'md'` when `isInteractive` is set, and can be overridden independently of both: - + diff --git a/packages/gamut-docs/src/content/docs/components/data-display/bar-chart.mdx b/packages/gamut-docs/src/content/docs/components/data-display/bar-chart.mdx index e984aeddfd..7fc89eeb87 100644 --- a/packages/gamut-docs/src/content/docs/components/data-display/bar-chart.mdx +++ b/packages/gamut-docs/src/content/docs/components/data-display/bar-chart.mdx @@ -33,7 +33,7 @@ Use `BarChart` to visualize comparative data across categories, as a horizontal 4. **Scale/value axis** — the minimum is always 0; dividers and the maximum are adjustable via `scaleInterval`/`maxScaleValue`. 5. **Series one value** 6. **Series two value** (optional, via `seriesTwoValue`) — creates the stacked effect. -7. **Series one bar** — defaults to the text color; gains an automatic white/navy border for contrast when needed. +7. **Series one bar** — defaults to the text color; gains an automatic light or dark border for contrast when needed. 8. **Series two bar** — defaults to the primary color; same automatic border rule. ## Usage examples diff --git a/packages/gamut-docs/src/content/docs/concepts/best-practices.md b/packages/gamut-docs/src/content/docs/concepts/best-practices.md index 470334b8e5..d480fab140 100644 --- a/packages/gamut-docs/src/content/docs/concepts/best-practices.md +++ b/packages/gamut-docs/src/content/docs/concepts/best-practices.md @@ -7,7 +7,7 @@ For best practices specific to one component — like alerts, errors, or confirm ## Prefer semantic tokens over raw values -Access colors through Gamut's semantic aliases (`text`, `background`, `primary`, `secondary`) rather than raw color values — see [Color modes](/concepts/color-modes/) for why. Aliases guarantee you get the right color for the right theme and color mode, with the right types and states. +Access colors through Gamut's color roles (`text-default`, `surface-raised`, `action-primary` and the rest) rather than raw values or palette colors; see [Color modes](/concepts/color-modes/) for why. Roles give you the right color for the theme and color mode, with the right types and states. [Color system](/concepts/color-system/#pick-a-role-by-its-job) lists which role fits which job. ```tsx import { css } from '@skillsoft/gamut-styles'; @@ -17,7 +17,7 @@ import styled from '@emotion/styled'; const Box = styled.div(css({ p: 4 })); // A semantic color, not a raw one -const OtherCoolThing = styled.div(css({ color: 'primary', p: 4 })); +const OtherCoolThing = styled.div(css({ color: 'action-primary', p: 4 })); ``` `variant` and `states` extend the same idea to whole sets of styles — see [Compose and variants](/foundations/system-props/compose-and-variants/) for the full API. diff --git a/packages/gamut-docs/src/content/docs/concepts/color-modes.md b/packages/gamut-docs/src/content/docs/concepts/color-modes.md index 8cd3d6e616..f9b9617f6c 100644 --- a/packages/gamut-docs/src/content/docs/concepts/color-modes.md +++ b/packages/gamut-docs/src/content/docs/concepts/color-modes.md @@ -1,22 +1,31 @@ --- title: Color modes -description: How light/dark color mode works conceptually, distinct from theming. +description: How light and dark mode work, and how they combine with a brand. --- -Gamut components are built on semantic color aliases, not raw color values. Each alias names what a color is _used for_, not what it looks like: +Gamut components use color roles, not color values. Each role names a job, not a look: -- `text` — the standard color for all type. -- `background` — the base background color. -- `primary` — the color for interactive elements with a primary action. -- `secondary` — the color for interactive elements with a secondary action. +- `text-default`, for body text and headings. +- `surface-default`, for the page. +- `surface-raised`, for cards, menus, dialogs and fields. +- `action-primary`, for the main interactive fill, links and focus rings. -Building components against these aliases, instead of specific values, guarantees: +Every role points at a palette color once for light mode and once for dark mode. A color mode is the choice between those two. Switching the mode switches every role at once, so a component built on roles works in both modes with no extra code. The full list of roles is on the [Color system](/concepts/color-system/#roles) page. -- Components work correctly in any context, without extra configuration. -- Color usage stays consistent and accessible between contexts. -- Code reads more meaningfully, since each alias hints at its own purpose. -- Configuration stays simple — swapping a color mode swaps every aliased value at once. +## Modes are HTML attributes -A color mode is the layer that resolves each alias to an actual value, for a light or dark context. For the exact values each alias resolves to, see [Themes](/foundations/themes/). For how to actually apply a color mode or build a component that adapts to it, see [Supporting dark mode](/guides/supporting-dark-mode/). +The `data-color-mode` attribute sets a mode, and the roles under it resolve in plain CSS. `ColorMode` sets the attribute for you: -This is distinct from [theming](/guides/theming-your-app/): a theme swaps which _palette_ Gamut draws from (Core, Admin, LX Studio, Percipio); a color mode swaps _which half_ of that palette — light or dark — an alias resolves against. The two compose: the same alias resolves differently depending on both which theme and which color mode are active. +```tsx +import { ColorMode } from '@skillsoft/gamut-styles'; + +const Panel = ({ children }) => {children}; +``` + +Modes nest: a light section can hold a dark panel, which can hold a light card. Each mode block also sets the CSS `color-scheme`, so scrollbars and native form controls follow the mode. A page starts in its brand's initial mode, which is light for every brand but Platform, with nothing set on ``. + +## Modes and brands + +A brand decides which palette the roles draw from: Skillsoft's, Original Codecademy's, and so on. A mode decides which of that brand's two mappings is active. The two combine: `action-primary` is a different color in each brand and each mode, but it always means the main action. + +Each theme carries one brand, and a page carries one brand's colors. See [Themes](/foundations/themes/) for the brands Gamut ships, and [Supporting dark mode](/guides/supporting-dark-mode/) for how to apply a mode or build a component that adapts to it. diff --git a/packages/gamut-docs/src/content/docs/concepts/color-system.md b/packages/gamut-docs/src/content/docs/concepts/color-system.md new file mode 100644 index 0000000000..8dfce4d579 --- /dev/null +++ b/packages/gamut-docs/src/content/docs/concepts/color-system.md @@ -0,0 +1,342 @@ +--- +title: Color system +description: How Gamut generates its colors from a few brand hues, checks them, and ships them as roles for light and dark mode. +--- + +Gamut's colors come from a generator in `@skillsoft/gamut-brand`. A brand supplies a few hues. The generator turns them into a palette, maps the palette onto named roles for light and dark mode, checks every pairing for contrast, and writes the result as CSS, JSON and TypeScript. Components and app code use the role names. `@skillsoft/gamut-styles` ships the result for Gamut's own brands, so an app on one of them never runs the generator. A team that wants its own colors runs it at build time; see [Build your own brand](#build-your-own-brand). + +## How it works + +Each step feeds the next one: + +1. A brand names a hue for each slot. +2. The generator builds a palette of 11 grades per slot. +3. Roles point at palette colors, once per mode. +4. The build checks contrast and stops if anything fails. +5. The build writes each brand's `tokens.css`, DTCG JSON and TypeScript constants. +6. `GamutProvider` puts the theme's brand's `tokens.css` on the page. +7. HTML attributes switch the mode inside the page. + +The generator lives in `packages/gamut-brand/src/`, and the output for Gamut's brands is checked in at `packages/gamut-styles/src/tokens/generated//`. + +### Brand inputs + +A brand is a `BrandInput` object with a name and a hue for each of eight slots. A new brand needs only this object, with no hand-written theme or alias table. The slots are: + +- `neutral-light` and `neutral-dark`, for surfaces, text and borders +- `primary`, for actions, links and focus rings +- `accent` and `accent-alt`, for brand gradients and charts +- `danger`, `warning` and `success`, for status + +Only `primary` is required. A brand that leaves out its neutrals gets neutrals derived from the primary hue. A brand that leaves out a status or accent slot gets Gamut's default hue for it, so red still means error. + +The generator keeps only the hue of each input. `#3A10E5` means "this blue-violet hue", and the generator decides how light and how colorful each step is. + +A brand can also set `initialMode`, `'light'` or `'dark'`, the mode a page starts in. It defaults to `'light'`. + +```ts +export const originalCodecademyBrand: BrandInput = { + name: 'originalCodecademy', + hues: { + 'neutral-light': '#FFF0E5', + 'neutral-dark': '#10162F', + primary: '#3A10E5', + accent: '#FFD300', + // ... + }, +}; +``` + +When a brand has to keep an exact color the generator wouldn't produce, it can add overrides. An override pins a palette step to a hex value, points a role at a different palette color in one mode, or moves a gradient's colors to another slot. Original Codecademy uses overrides to keep its exact yellow and to make dark mode's primary action yellow. Its navy and beige aren't pinned: exact, they carry far more chroma than the generated surfaces next to them, so the generated steps at their hues are used instead. Overrides go through the same checks as everything else. + +Five brands ship today: + +- `skillsoft`, the default, which replaces Percipio. Its theme is `skillsoftTheme`: Percipio's fonts, font weights and resting shadow, with colors generated from the Skillsoft brand's hues. +- `originalCodecademy`, the old Core theme rebuilt as a brand. Its theme is `originalCodecademyTheme`. +- `admin`, `platform` and `lxStudio`, the other old themes rebuilt the same way. Their themes keep their old names: `adminTheme`, `platformTheme` and `lxStudioTheme`. `percipioTheme` is removed; use `skillsoftTheme` instead. + +Each rebuilt brand takes its colors from what its old theme showed in each job, and keeps that theme's fonts and weights. Shadows are drawn in the shadow roles: Original Codecademy keeps old Core's hard offset, and every other brand uses a soft shadow. The `platform` brand starts in dark mode, and `platformTheme` keeps its `editor-*` code editor colors, which aren't part of the generated system. + +### Palette + +Each slot becomes a ramp of 11 grades, from `100` (lightest) to `1100` (darkest). The generator works in [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch), a way of writing a color as lightness, chroma (how colorful it is) and hue. OKLCH lightness matches how bright people see a color, so two colors with the same lightness look equally light whatever their hue. + +Every grade has a fixed lightness, and that lightness is the same in every slot and every brand. Chroma is a share of the most color a screen can show at that lightness and hue. Because lightness is fixed, the contrast between two grades barely changes from brand to brand: `primary-800` on `neutral-light-100` has close to the same contrast whatever the primary hue is. That is what lets every brand share one role mapping. + +Palette colors are [CSS custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) named `--color-palette--`, for example `--color-palette-primary-800`. A few steps also come in translucent versions, such as `--color-palette-primary-600-a6` at 6% opacity. `--color-white` and `--color-black` sit outside the ramps. Palette colors exist for the system to build roles from. Components don't use them. + +### Roles + +A role names a job and points at a palette color in each mode. There are 45 roles: + +| Family | Roles | Used for | +| ----------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | +| `surface-*` | `default`, `raised`, `sunken`, `success`, `warning`, `error`, `brand`, `brand-subtle` | Anything with a background: the page, cards, wells, status tints | +| `text-*` | `default`, `subtle`, `inverse`, `success`, `warning`, `error` | Text and icons | +| `border-*` | `strong`, `default`, `subtle` | Borders and dividers | +| `action-*` | `primary`, `danger` and `neutral`, each with `-hover` and `-pressed`; `disabled` | Interactive fills and inks: buttons, links, toggles | +| `state-*` | `hover`, `pressed`, `selected` | Translucent layers over elements that have no fill of their own | +| `viz-*` | `1` to `8` (categorical), `seq-1` to `seq-4` (sequential) | Charts | +| `shadow-*` | `key`, `ambient`, `scrim` | Shadow colors and the dimmed layer behind a modal | + +Each role has one job. `surface-raised` is the lighter surface step: cards, menus, dialogs and form fields, and flat sections with a border. How high something sits comes from its shadow, not its fill. The mapping is the same for every brand: `text-subtle` is `neutral-dark-800` in light mode and `neutral-light-400` in dark mode on every brand. Only the palette under it changes. + +Roles are custom properties named `--color-`, for example `--color-action-primary`. The two brand surfaces also have a gradient. `--color-surface-brand` is the flat fallback color, and `--gradient-surface-brand` is the gradient to paint over it. The same goes for `surface-brand-subtle`. + +The shadow roles hold only color. Every elevation shadow has two layers, `shadow-key` for the tight edge and `shadow-ambient` for the soft falloff, and the elevation supplies the offset and blur. Raised and hover differ in geometry, not color. `shadow-scrim` is the dimmed layer behind a modal. The shadow alphas are design's current values and may still be tuned. + +### Checks + +The generator refuses to write a brand's files if a check fails, in either mode. It checks that: + +- every text role reaches a 4.5:1 [contrast ratio](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html) on the surfaces it's meant for; +- every border, action and chart role that carries meaning reaches 3:1, the [non-text contrast](https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html) minimum; +- the translucent `state-*` roles still meet those minimums once they're laid over each surface; +- `state-pressed` looks visibly different from `state-hover` on every surface; +- 204 text and icon pairings inside 25 real components, such as an `Alert` icon on its tint, meet their minimum. + +`border-subtle`, `action-disabled` and the `shadow-*` roles are exempt from contrast: the first is decorative, the second marks an inactive control, and a shadow isn't something to read. Separation of the three status colors for people with color-vision deficiencies is measured and reported, but doesn't fail the build. + +The generator runs every check a second time on the values read back out of the brand's own `tokens.css`, so what ships is what was checked. To see the full report for Gamut's brands, run: + +```sh +yarn workspace @skillsoft/gamut-styles validate:tokens +``` + +### Outputs + +Each brand gets its own set of three outputs. No file holds more than one brand: + +| Output | Import | For | +| ------------ | ------------------------------------------------------------------------- | --------------------------------------------------------------- | +| `tokens.css` | `@skillsoft/gamut-styles/tokens/.css` | The brand's colors as custom properties | +| DTCG JSON | `@skillsoft/gamut-styles/tokens//{palette,light,dark}.tokens.json` | Tools outside JavaScript, such as Figma plugins and native apps | +| TS constants | `@skillsoft/gamut-styles/tokens` | Code that needs the values or the role table itself | + +`@skillsoft/gamut-styles/tokens.css` is the default brand's file, `skillsoft`. + +The JSON follows the [Design Tokens Community Group](https://www.designtokens.org/) (DTCG) format, a shared JSON format for design tokens. There is one file each for the palette, light roles and dark roles, and each color is OKLCH with its hex value alongside. The TypeScript entry exports the default brand's `palette`, `roles`, `gradients`, `contentModes` and `tokensCss`, plus every brand under `brands`, such as `brands.originalCodecademy.palette`. It doesn't depend on React or Emotion. + +A brand's `tokens.css` puts every value inside a [cascade layer](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer) called `tokens`, with the brand's palette and its initial mode on `:root`: + +```css +@layer reset, base, tokens, recipes, utilities; + +@layer tokens { + :root, + [data-brand='originalCodecademy'] { + --color-palette-primary-800: oklch(0.4115 0.2344 274.1); + /* ... */ + } + :root, + [data-color-mode='light'] { + --color-text-default: var(--color-palette-neutral-dark-1000); + /* ... */ + } + [data-color-mode='dark'] { + --color-action-primary: var(--color-palette-accent-300); + /* ... */ + } +} +``` + +A brand's overrides sit in its mode blocks, like Original Codecademy's yellow `action-primary` in dark mode in the previous example. + +Styles inside a layer lose to styles outside any layer, whatever the selector. Emotion's styles and your own unlayered CSS override a token value without needing a more specific selector. + +### How the colors reach the page + +A theme built on the color system carries its brand's `tokens.css` as a string, and `GamutProvider` injects it as an Emotion global style, the same way it injects its reset and typography styles. A React app gets its brand's colors without loading a file, starts in the brand's initial mode with no attributes on ``, and the colors are in the server-rendered HTML too. + +The theme's own colors are `var()` names like `var(--color-surface-raised)`, so `css()`, `variant()`, `states()` and system props keep working as they did before. + +If your app loads the brand's `tokens.css` itself, pass `useTokens={false}` to `GamutProvider` to skip the injected copy. Loading both does no harm, because the rules are identical. Pages without React or Emotion, such as plain HTML or email, can link the brand's `tokens.css` directly. + +### Modes and brands at runtime + +A mode switch is an HTML attribute. The `ColorMode` component sets two attributes on its element, `data-color-mode` and `data-brand`, and every role inside it points at its color for that mode, through plain CSS. It nests as deep as you need. Each mode block also sets [`color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme), so scrollbars and native form controls follow the mode. + +`data-brand` names the palette. A page carries one brand's CSS, the theme's, so every `ColorMode` inside it is in that brand, overrides included. Use `ColorMode`, or `Background` for a colored section; if you set the attributes by hand, set both on the same element. + +The `Background` component decides its content mode at build time. For each of the 90 opaque palette colors (the 88 ramp steps, white and black), the generator records whether light-mode or dark-mode content reads better on it. `Background` looks the answer up in that table instead of measuring contrast in the browser on every render. + +## Intended use + +### Pick a role by its job + +| Job | Role | +| ------------------------------------------- | ------------------------------------------------ | +| The page, a nav or a header | `surface-default` | +| A card, popover, menu, modal panel or field | `surface-raised` | +| An inset well or code block | `surface-sunken` | +| Body text and headings | `text-default` | +| Descriptions, metadata and helper text | `text-subtle` | +| Anything clickable | `action-*` | +| Hover and press on something with no fill | `state-hover`, `state-pressed`, `state-selected` | +| A border that carries meaning, like a field | `border-default` | +| A divider, or the edge of a floating panel | `border-subtle` | +| A shadow's two layers | `shadow-key`, `shadow-ambient` | +| The dimmed layer behind a modal | `shadow-scrim` | + +```tsx +import { Box, Text } from '@skillsoft/gamut'; + +const Card = () => ( + + Course progress + Updated 2 hours ago + +); +``` + +### Give raised surfaces a hairline + +In light mode `surface-raised` is too close to the page to see on its own, so anything on it takes a `border-subtle` hairline. Floating surfaces, such as menus, popovers, tooltips and toasts, add a shadow that separates them from whatever is underneath. Field outlines and control strokes carry meaning, so they stay `border-default`. + +`Card` has two variants: `default` on `surface-raised`, and `brand`, the featured card on the brand gradient. Every card has the hairline. An interactive card adds the raised shadow at rest and, on hover, the `state-hover` layer and a deeper shadow; its border doesn't change. + +### Use `text-inverse` on filled controls + +A label on a filled action, such as `action-primary`, uses `text-inverse` in either mode. `text-inverse` is only checked against `action-primary`, `action-neutral` and `action-danger`. + +```tsx + + Start lesson + +``` + +### Pair a status tint with a status ink + +A status message is a tint behind and an ink on top. An error box uses `surface-error` for the background and `text-error` for the icon and the message; each status ink clears text contrast on its own tint. A floating `Alert` takes the same ink on its border. Show warnings with an icon, not color alone. + +### Put colored sections inside `Background` + +`Background` paints a color and switches the mode inside it to whichever one reads better on that color. Inside, every role resolves in the new mode, so `text-default` stays readable without picking a mode by hand. + +```tsx +import { Background } from '@skillsoft/gamut-styles'; + +const PromoBand = ({ children }) => ( + + {children} + +); +``` + +### Use `viz-*` for charts + +Use `viz-1` to `viz-8` for categorical series and `viz-seq-1` to `viz-seq-4` for ordered steps from low to high. Don't rely on color alone to tell series apart; add labels, patterns or markers ([WCAG use of color](https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html)). For the same reason, links keep their underline. + +### Use roles, never palette colors + +Write `bg="surface-raised"`, not `bg="palette-neutral-light-100"`. A palette color is the same in every mode, so it doesn't change when the mode does, and it breaks dark mode. + +### Set up an app + +A React app gets the colors from `GamutProvider`. Pass the theme for your brand: + +```tsx +import { + GamutProvider, + originalCodecademyTheme, +} from '@skillsoft/gamut-styles'; + +export const App = ({ children }) => ( + {children} +); +``` + +The page starts in that brand and its initial mode, `` included, with nothing to set on ``. + +Pages without React load the brand's file, such as `@skillsoft/gamut-styles/tokens/originalCodecademy.css`. + +## Build your own brand + +A team that needs its own colors builds a brand with `@skillsoft/gamut-brand`, the same tool that builds Gamut's. It runs at build time only, so install it as a dev dependency: + +```sh +yarn add -D @skillsoft/gamut-brand +``` + +Write the brand. Only `name` and `hues.primary` are required: + +```ts +// src/theme/acme.brand.ts +import { defineBrand } from '@skillsoft/gamut-brand'; + +export default defineBrand({ + name: 'acme', + hues: { primary: '#0A5CFF', accent: '#FFB000' }, +}); +``` + +Generate it, and check in the `src/theme/acme/` folder it writes: + +```sh +gamut-brand build src/theme/acme.brand.ts +``` + +Build the theme from the generated values, and pass it to `GamutProvider` like any other theme: + +```ts +import { + createBrandTheme, + fontWeightMediumTitle, + percipioFontFamily, + softElevation, +} from '@skillsoft/gamut-styles'; + +import * as values from './acme/values'; + +export const acmeTheme = createBrandTheme(values, { + name: 'acme', + fontFamily: percipioFontFamily, + fontWeight: fontWeightMediumTitle, + elevation: softElevation, +}); +``` + +Every brand has the same palette keys and the same 45 roles, so `acmeTheme` has the same type as Gamut's themes. System props, `css()`, `variant()` and `states()` stay typed, and `bg="surface-raisd"` fails to compile. `defineBrand` is typed too, so a misspelled slot or role fails. + +A brand that fails a check isn't written, and the error names the check, its worst value and where. Pick another hue, or pin the color with an override, and run the build again. + +To keep the files current, run `gamut-brand build src/theme/acme.brand.ts --watch` next to your dev server, and the same command with `--check` in CI. `--check` writes nothing and fails when the files are out of date, including after an upgrade of `@skillsoft/gamut-brand`. Keep `@skillsoft/gamut-brand` on the same version as `@skillsoft/gamut-styles`. + +## Compared with the old color system + +### What stays the same + +- Themes are still variance themes, and components still style with `css()`, `variant()`, `states()` and system props. Only the color names change. +- Components still use names, not values. The old themes already handed components `var()` names, and the new ones do the same. +- Color modes still work through CSS custom properties and still nest. An attribute drives them now instead of an injected class. +- `GamutProvider`, `ColorMode` and `Background` keep the same props, and `GamutProvider` still delivers the colors. +- The old look comes back as brands. Original Codecademy is old Core rebuilt from its navy, beige, hyper and yellow, with the same fonts and old Core's hard offset shadow. Admin, Platform and LX Studio are rebuilt the same way, under their old theme names. + +### What improves + +| | Old | New | +| ----------------- | ------------------------------------------------------- | ------------------------------------------------------------------- | +| Where values live | Emotion theme objects, written onto the page at runtime | One generated stylesheet per brand, injected once | +| Mode switch | Every alias for the mode written into a new class | One attribute | +| `Background` | Contrast math on every render | A table decided at build time | +| Accessibility | Colors picked by hand and never checked | Every role pairing and 204 component pairings checked at build time | +| Names | Ambiguous: `background` is both the page and a card | One job each: `surface-default`, `surface-raised` | +| New brand | A hand-written theme and alias table | A few hues and `gamut-brand build`, checked automatically | +| Who can read it | JavaScript only | CSS, DTCG JSON and TypeScript from one source | +| Color space | Hex, tuned by hand per step | OKLCH with fixed lightness per grade, hex alongside in the JSON | + +### What costs more + +- Every color name changes. `text` becomes `text-default` and `primary` becomes `action-primary`. Some old names split into several roles depending on where they're used: `background` becomes `surface-raised` on a card and `surface-default` on the page, so a person has to choose each time. The type check fails on every old name until it's moved. +- Rebuilt themes aren't pixel-identical. Roles use the same grades on every brand, so some colors move noticeably even when the brand hues match the old theme. Overrides cover colors that have to stay exact. +- Colors that only one old theme had are gone, such as LX Studio's `lxStudioBgPrimary`. Use the role for the job instead. +- The browser floor goes up. `tokens.css` uses `oklch()`, [`color-mix()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix), `@layer` and [container query units](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries#container_query_length_units) with no hex fallback. Browsers older than about Chrome and Edge 111, Safari 16.2 or Firefox 113 get no color. Check [oklch support](https://caniuse.com/mdn-css_types_color_oklch) and [color-mix support](https://caniuse.com/mdn-css_types_color_color-mix) against your app's browser list. +- Any unlayered CSS overrides a token. That's on purpose, but a stray global rule can now change a color. +- Some pieces are missing. Shadow geometry lives in each brand's theme for now, and floating surfaces borrow the raised shadow until there's an overlay depth. The chart colors use the order solved for Skillsoft's hues, so another brand gets that order without it being re-solved for its own hues. +- The Emotion theme carries sRGB copies of each color, because the [polished](https://polished.js.org/) library can't read OKLCH. Code that reads a color from the theme in JavaScript gets a very slightly different value from what's on screen. diff --git a/packages/gamut-docs/src/content/docs/concepts/index.md b/packages/gamut-docs/src/content/docs/concepts/index.md index fc02130aff..67dd34d79a 100644 --- a/packages/gamut-docs/src/content/docs/concepts/index.md +++ b/packages/gamut-docs/src/content/docs/concepts/index.md @@ -5,6 +5,7 @@ description: Background on how and why Gamut is built the way it is. Explanation: how and why the system is built the way it is, for when you want to understand rather than do. +- [Color system](/concepts/color-system/) - [Color modes](/concepts/color-modes/) - [Brand](/concepts/brand/) - [Best practices](/concepts/best-practices/) diff --git a/packages/gamut-docs/src/content/docs/foundations/design-tokens.md b/packages/gamut-docs/src/content/docs/foundations/design-tokens.md index 8139360d89..8389f20317 100644 --- a/packages/gamut-docs/src/content/docs/foundations/design-tokens.md +++ b/packages/gamut-docs/src/content/docs/foundations/design-tokens.md @@ -3,15 +3,18 @@ title: Design tokens description: Color, spacing, radius, and other token values, and the keys used to access them. --- -Every theme exposes the same token categories, each accessible on the Emotion theme object via its own key. Exact values differ per theme — see [Themes](/foundations/themes/) for the available theme palettes, or open Storybook's Theme Switcher (paintbrush icon in the toolbar) to browse live values for any theme/color-mode combination. For the full, exhaustive listing of every value in a category — every color swatch, every spacing step, every border radius — see that theme's own page under Foundations → Theme in Storybook (for example, [Core Theme](https://gamut.codecademy.com/?path=/docs/foundations-theme-core-theme--docs)) rather than a static copy here. +Every theme exposes the same token categories, each on the Emotion theme object under its own key. Exact values differ per theme; see [Themes](/foundations/themes/) for the themes Gamut ships, or open Storybook's Theme Switcher (paintbrush icon in the toolbar) to browse live values for any theme and color mode. ## Color — `colors` -- Standard colors — the color atoms Codecademy designs should draw from. -- Solid swatches — accessed as `colors['-']`. -- Navy/white swatches — semi-transparent except for a few fixed weights (`100`, `navy-800`, `navy-900`); the white weights only show in dark mode, and the navy weights only in light mode. +Colors come from the [color system](/concepts/color-system/), in two layers: -See [Color modes](/concepts/color-modes/) for the semantic aliases (`text`, `background`, `primary`, `secondary`) built on top of these raw values. +- **Roles**, such as `surface-raised`, `text-default` and `action-primary`. Each names a job and resolves to a different color in light and dark mode. Components and app code use roles. +- **Palette colors**, named `palette--`, such as `palette-primary-800`, plus `white` and `black`. A palette color is the same in every mode. Roles point at these; components shouldn't. + +Each color is also a CSS custom property with a `--color-` prefix, such as `var(--color-surface-raised)`, for stylesheets and pages without React. + +See [Color modes](/concepts/color-modes/) for how roles follow the mode. ## Typography @@ -34,16 +37,16 @@ See [Color modes](/concepts/color-modes/) for the semantic aliases (`text`, `bac ## Elevation -- `elevation` — how a surface like Card renders shadow and lift. Three states — `rest`, `hover`, `hoverMirrored` — each with a `shadow` and a `transform` token, accessed as `elevation['-']`. `hoverMirrored` is for a surface that casts its shadow to the right instead of the left. Shadow colors reference the `shadow-primary` alias, so they follow color mode automatically; a theme can supply its own elevation scale — Percipio, for example, swaps the hard offset shadow for a soft, blurred one with no lift. +- `elevation` — how a surface like Card renders shadow and lift. Three states, `rest`, `hover` and `hoverMirrored`, each with a `shadow` and a `transform` token, accessed as `elevation['-']`. `hoverMirrored` is for a surface that casts its shadow to the right instead of the left. Every shadow is drawn in two roles, `shadow-key` (the tight contact layer) and `shadow-ambient` (the soft falloff), so shadows follow color mode automatically. Each theme supplies its own geometry: Original Codecademy keeps a hard offset, and the other themes use a soft shadow with no lift. ## Accessing tokens directly -Reach for a token directly only when a [system prop](/foundations/system-props/) can't express what you need — most components should consume tokens through system props instead. +Reach for a token directly only when a [system prop](/foundations/system-props/) can't express what you need. Most components should consume tokens through system props instead. ```tsx // on the theme prop of any styled component const styles = styled.div` - color: ${({ theme }) => theme.colors.blue}; + color: ${({ theme }) => theme.colors['text-subtle']}; `; // or imported directly diff --git a/packages/gamut-docs/src/content/docs/foundations/eslint-rules.md b/packages/gamut-docs/src/content/docs/foundations/eslint-rules.md index 27716af235..536da66442 100644 --- a/packages/gamut-docs/src/content/docs/foundations/eslint-rules.md +++ b/packages/gamut-docs/src/content/docs/foundations/eslint-rules.md @@ -55,11 +55,11 @@ Suggests the `themed()` utility over reaching directly into `theme` inside a sty ```tsx // Instead of: const StyledDiv = styled.div` - color: ${({ theme }) => theme.colors.primary}; + color: ${({ theme }) => theme.colors['action-primary']}; `; // Prefer: -const StyledBox = styled(Box)(css({ color: themed('colors.primary') })); +const StyledBox = styled(Box)(css({ color: themed('colors.action-primary') })); ``` ## Configuration diff --git a/packages/gamut-docs/src/content/docs/foundations/system-props/compose-and-variants.md b/packages/gamut-docs/src/content/docs/foundations/system-props/compose-and-variants.md index a73d4fb01c..dbb7f4b59f 100644 --- a/packages/gamut-docs/src/content/docs/foundations/system-props/compose-and-variants.md +++ b/packages/gamut-docs/src/content/docs/foundations/system-props/compose-and-variants.md @@ -66,7 +66,7 @@ const Anchor = styled.a( variant({ variants: { interface: { - color: 'navy', + color: 'text-default', '&:hover': { textDecoration: 'none', border: '2px solid currentColor' }, }, }, diff --git a/packages/gamut-docs/src/content/docs/foundations/system-props/prop-groups.md b/packages/gamut-docs/src/content/docs/foundations/system-props/prop-groups.md index 71dd781d0c..42b486411a 100644 --- a/packages/gamut-docs/src/content/docs/foundations/system-props/prop-groups.md +++ b/packages/gamut-docs/src/content/docs/foundations/system-props/prop-groups.md @@ -42,12 +42,16 @@ const TextExample = styled.p(system.typography); ## Color -Color props control foreground, background, and border colors, restricted to the theme's color palette. `bg` is a shorthand for background color. +Color props control foreground, background, and border colors, restricted to the theme's colors. Use [color roles](/concepts/color-system/#pick-a-role-by-its-job), which follow the color mode. `bg` is a shorthand for background color. ```tsx const ColorExample = styled.div(system.color); -; +; ``` ## Border diff --git a/packages/gamut-docs/src/content/docs/foundations/themes.md b/packages/gamut-docs/src/content/docs/foundations/themes.md index 214304c470..a946ebec03 100644 --- a/packages/gamut-docs/src/content/docs/foundations/themes.md +++ b/packages/gamut-docs/src/content/docs/foundations/themes.md @@ -1,21 +1,31 @@ --- title: Themes -description: The theme palettes available (Core, Admin, LX Studio, Percipio, and Platform) and their values. +description: The themes Gamut ships (Skillsoft, Original Codecademy, Admin, Platform and LX Studio) and how they relate to brands. --- -Every Emotion component in Gamut has typed access to the current theme's tokens, without any extra imports — most commonly through [system props](/foundations/system-props/) or the `theme` prop. +Every Emotion component in Gamut has typed access to the current theme's tokens, without any extra imports, most commonly through [system props](/foundations/system-props/) or the `theme` prop. ## Available themes -- **Core** — Codecademy's own public-facing theme. -- **Admin** — the internal admin/back-office theme. -- **LX Studio** — the theme for LX Studio. -- **Percipio** — the theme for Percipio; replaces Core's hard offset shadows with a soft, blurred one and no lift (see [Design tokens: Elevation](/foundations/design-tokens/#elevation)). -- **Platform** — the shared learning-platform theme. +Each theme is one brand of the [color system](/concepts/color-system/), plus that brand's fonts, font weights and shadows: + +| Theme | Brand | For | +| ------------------------- | -------------------- | --------------------------------------------------------------- | +| `skillsoftTheme` | `skillsoft` | The default. Replaces `percipioTheme` | +| `originalCodecademyTheme` | `originalCodecademy` | Codecademy's public-facing product, rebuilt from the Core theme | +| `adminTheme` | `admin` | Internal admin and back-office tools | +| `platformTheme` | `platform` | The learning environment. Starts in dark mode | +| `lxStudioTheme` | `lxStudio` | LX Studio | + +`GamutProvider` uses `skillsoftTheme` when you don't pass one. Every theme has the same palette keys and the same roles, so they all share one type, `SkillsoftTheme`, and a component that type-checks against one works on all of them. + +`coreTheme` is still exported, on the old color names, until the move to roles is finished. Use `originalCodecademyTheme` for new work. + +A team that needs its own colors can build its own brand and theme; see [Creating a custom theme](/guides/creating-a-custom-theme/). ## Comparing themes live -Use Storybook's Theme Switcher (paintbrush icon in the toolbar) to preview any component across every theme, paired with the Color Mode selector to check both light and dark variants of each. This is the fastest way to see a token's actual resolved value — exact hex codes and scale values are theme-specific and aren't reproduced statically here. +Use Storybook's Theme Switcher (paintbrush icon in the toolbar) to preview any component in every theme, and the Color Mode selector to check light and dark for each. Exact color values are theme-specific and aren't reproduced here. ## Choosing a theme diff --git a/packages/gamut-docs/src/content/docs/getting-started/build-your-first-page.md b/packages/gamut-docs/src/content/docs/getting-started/build-your-first-page.md index f7c3eacdb9..50220b831f 100644 --- a/packages/gamut-docs/src/content/docs/getting-started/build-your-first-page.md +++ b/packages/gamut-docs/src/content/docs/getting-started/build-your-first-page.md @@ -11,19 +11,19 @@ It assumes you've completed [Installation](/getting-started/installation/) and h ## 1. Lay down a colored surface -`Background` paints a semantic color behind its children and keeps text inside it readable in both color modes. Start your page component with one: +`Background` paints a color role behind its children and keeps text inside it readable in both color modes. Start your page component with one: ```tsx title="WelcomePage.tsx" import { Background } from '@skillsoft/gamut-styles'; export const WelcomePage = () => ( - + {/* content goes here */} ); ``` -Run your app. You should see a full-width beige panel with 32px of padding and nothing in it yet. +Run your app. You should see a full-width panel, a step darker than the page, with 32px of padding and nothing in it yet. ## 2. Add a heading @@ -34,7 +34,7 @@ import { Background } from '@skillsoft/gamut-styles'; import { Text } from '@skillsoft/gamut'; export const WelcomePage = () => ( - + Welcome to Gamut @@ -42,7 +42,7 @@ export const WelcomePage = () => ( ); ``` -Refresh — you now have a styled heading sitting in the beige panel. +Refresh — you now have a styled heading sitting in the panel. ## 3. Arrange content in a grid @@ -54,7 +54,7 @@ import { Text } from '@skillsoft/gamut'; import { GridBox } from '@skillsoft/gamut'; export const WelcomePage = () => ( - + Welcome to Gamut @@ -87,7 +87,7 @@ import { GridBox } from '@skillsoft/gamut'; import { Text } from '@skillsoft/gamut'; export const WelcomePage = () => ( - + Welcome to Gamut @@ -120,7 +120,7 @@ export const WelcomePage = () => ( You should now have a page with: -- A beige background panel with consistent padding. +- A `surface-sunken` panel with consistent padding. - A page heading and supporting copy. - A responsive two-column layout that collapses to one column on narrow screens. - A card containing a secondary heading, copy, and a primary button that links to the component reference. diff --git a/packages/gamut-docs/src/content/docs/getting-started/installation.md b/packages/gamut-docs/src/content/docs/getting-started/installation.md index 10fe4402cc..be21f4d61c 100644 --- a/packages/gamut-docs/src/content/docs/getting-started/installation.md +++ b/packages/gamut-docs/src/content/docs/getting-started/installation.md @@ -48,10 +48,10 @@ render( `GamutProvider` does four things every Gamut app needs: -1. Wraps your app in the theme context. +1. Wraps your app in the theme context. Without a `theme` prop, it uses `skillsoftTheme`; see [Theming your app](/guides/theming-your-app/) to pick another. 2. Creates an Emotion cache with Gamut's plugins. -3. Injects global styles and CSS variables. -4. Sets the current color mode context and variables. +3. Injects global styles and the theme's brand colors as CSS variables. +4. Starts the page in the brand's initial color mode. For Next.js, do this in `_app.tsx`. For Gatsby, do it in both `gatsby-ssr.js` and `gatsby-browser.js` via `wrapRootElement`. @@ -70,11 +70,11 @@ Gamut components read their allowed props from your Emotion theme shape, so decl ```tsx title="theme.d.ts" import '@emotion/react'; -// Use whichever theme shape matches your app: `CoreTheme` or `PlatformTheme`. -import { CoreTheme } from '@skillsoft/gamut-styles'; +// Every Gamut theme shares this type, whichever one your app uses. +import type { SkillsoftTheme } from '@skillsoft/gamut-styles'; declare module '@emotion/react' { - export interface Theme extends CoreTheme {} + export interface Theme extends SkillsoftTheme {} } ``` @@ -87,7 +87,7 @@ import { Background } from '@skillsoft/gamut-styles'; import { Text } from '@skillsoft/gamut'; export const App = () => ( - + Hello World! ); diff --git a/packages/gamut-docs/src/content/docs/guides/creating-a-custom-theme.md b/packages/gamut-docs/src/content/docs/guides/creating-a-custom-theme.md index 96163436ad..6196cad583 100644 --- a/packages/gamut-docs/src/content/docs/guides/creating-a-custom-theme.md +++ b/packages/gamut-docs/src/content/docs/guides/creating-a-custom-theme.md @@ -1,164 +1,195 @@ --- title: Creating a custom theme -description: Build a custom theme with gamut-styles' createTheme builder. +description: Build your own brand with @skillsoft/gamut-brand and turn it into a theme with createBrandTheme. --- ## When to reach for this -Most apps should [choose an existing theme](/guides/theming-your-app/) rather than build a new one — reach for a custom theme only for a genuinely new product surface that doesn't fit Core, Admin, LX Studio, Percipio, or Platform. +Most apps should [choose an existing theme](/guides/theming-your-app/). Build your own only when a product needs its own colors, for example to white-label Gamut for a customer. -## The `createTheme` builder +A custom theme has two parts: -Themes are built with `createTheme` from `@skillsoft/variance`, giving you type-safe autocomplete, automatic CSS variable generation, built-in light/dark color mode support, and full compatibility with existing Gamut components: +- **A brand**: a few hues, which `@skillsoft/gamut-brand` turns into a palette, light and dark roles, and the CSS for them, all checked for contrast. +- **A theme**: the brand's generated values plus fonts, font weights and shadows, put together by `createBrandTheme` from `@skillsoft/gamut-styles`. -```tsx -import { createTheme } from '@skillsoft/variance'; - -const myTheme = createTheme({ - ...baseTheme, - fontFamily: customFontFamily, -}) - .addColors(colorPalette) - .addColorModes('light', colorModeConfig) - .addScale('customProperty', scaleFunction) // only for new scales - .addName('my-theme') - .build(); +The result has the same type as Gamut's own themes, so every component, system prop and `css()` call works and stays type-checked. See [Color system](/concepts/color-system/) for how the generator works. + +## 1. Install the generator + +`@skillsoft/gamut-brand` runs at build time only, so install it as a dev dependency, on the same version as `@skillsoft/gamut-styles`: + +```sh +yarn add -D @skillsoft/gamut-brand ``` -## 1. Start from `coreTheme` +## 2. Write the brand -Extend `coreTheme` unless you're certain you have a full replacement for every token — see [Themes](/foundations/themes/) for what Core already provides: +Only `name` and `hues.primary` are required: -```tsx -import { createTheme } from '@skillsoft/variance'; -import { coreTheme } from '@skillsoft/gamut-styles'; +```ts +// src/theme/acme.brand.ts +import { defineBrand } from '@skillsoft/gamut-brand'; -const myCustomTheme = createTheme({ - ...coreTheme, - // your overrides here +export default defineBrand({ + name: 'acme', + hues: { primary: '#0A5CFF', accent: '#FFB000' }, }); ``` -## 2. Define a color palette +`name` becomes the brand's `data-brand` attribute and its output folder: letters, digits and `_`, starting with a letter. -Build an object of your brand's raw colors — usually a `brand` object, plus a numbered `50`–`900` scale per hue: +`hues` takes a hue in degrees, or a color to take the hue from, for each of eight slots: `primary`, `neutral-light`, `neutral-dark`, `accent`, `accent-alt`, `danger`, `warning` and `success`. Slots you leave out are filled in: neutrals from the primary's hue, and status and accent slots from Gamut's defaults, so red still means error. -```tsx -const myColorPalette = { - brand: { - primary: '#004C54', - secondary: '#000000', - black: '#000000', - white: '#FFFFFF', - }, - kelly: { - 50: '#E6FFF7', - 500: '#00B388', - 900: '#004D3D', - // ...the rest of the scale - }, - success: '#10B981', - warning: '#F59E0B', - error: '#DC2626', -}; - -const myTheme = createTheme({ ...coreTheme }).addColors(myColorPalette); +Only a color's hue is kept. `#0A5CFF` means "this blue's hue", and the generator sets how light and how colorful each grade is, which is what keeps contrast the same for every brand. To start from one of Gamut's brands, spread it: + +```ts +import { defineBrand, skillsoftBrand } from '@skillsoft/gamut-brand'; + +export default defineBrand({ + ...skillsoftBrand, + name: 'acme', + hues: { ...skillsoftBrand.hues, primary: '#0A5CFF' }, +}); ``` -## 3. Map colors to semantic tokens per color mode +### Optional settings -`.addColorModes` maps your palette onto the same semantic aliases Gamut components already read from — `text`, `background`, `primary`, `secondary`, `border`, and the rest — once per color mode. See [Color modes](/concepts/color-modes/) for what each alias means: +- `initialMode: 'dark'` starts pages in dark mode. The default is `'light'`. +- `overrides` keeps colors the generator wouldn't produce. Use them sparingly; each one still goes through every check. -```tsx -const myTheme = createTheme({ ...coreTheme }) - .addColors(myColorPalette) - .addColorModes('light', { - light: { - text: { _: 'brand-black', secondary: 'silver-700' }, - background: { _: 'brand-white', primary: 'kelly-50' }, - primary: { _: 'brand-primary', hover: 'kelly-700' }, - border: { primary: 'kelly-500', secondary: 'kelly-200' }, - // ... - }, - dark: { - text: { _: 'brand-white', secondary: 'silver-400' }, - background: { _: 'brand-black', primary: 'midnight-800' }, - primary: { _: 'brand-accent', hover: 'silver-400' }, - border: { primary: 'midnight-600', secondary: 'midnight-700' }, - // ... - }, - }); +```ts +overrides: { + // An exact color for one palette step. Pick the grade nearest its lightness. + palette: { 'accent-300': '#FFB000' }, + // A role pointed at another palette color in one mode. + roles: { dark: { 'action-primary': { slot: 'accent', grade: 300 } } }, + // A gradient's circles moved to another slot. + gradients: { 'surface-brand': { accent: 'accent-alt' } }, +}, ``` -## 4. Override or add scales +`defineBrand` is typed, so a misspelled slot, role or palette step fails to compile. -Override an existing scale — `spacing`, `fontFamily`, `fontWeight`, and the rest — directly in the initial `createTheme()` object. Reach for `.addScale()` only for a genuinely new scale that doesn't already exist on `coreTheme`: +## 3. Generate the files -```tsx -const myTheme = createTheme({ - ...coreTheme, - fontFamily: { ...coreTheme.fontFamily, base: '"Roboto", sans-serif' }, - spacing: { ...coreTheme.spacing, xs: 4, sm: 8, md: 16 }, -}) - .addColors(myColorPalette) - .addColorModes('light', colorModeConfig) - .addScale('shadows', ({ colors }) => ({ - sm: `0 1px 2px ${colors['shadow-primary']}`, - md: `0 4px 6px ${colors['shadow-secondary']}`, - })); +```sh +gamut-brand build src/theme/acme.brand.ts +``` + +This writes `src/theme/acme/`: + +| File | What it is | +| -------------------------------------------------------------- | --------------------------------------------------------------------------------- | +| `values.ts` | The palette, roles and the brand's CSS as typed constants, for `createBrandTheme` | +| `tokens.css` | The brand's `--color-*` and `--gradient-*` custom properties, and nothing else | +| `palette.tokens.json`, `light.tokens.json`, `dark.tokens.json` | DTCG JSON for Figma and other design tools | + +Check the folder in, like any generated source. + +A brand that fails a contrast check isn't written. The error names the check, its worst value and where: + +``` +refusing to generate "acme"; failed checks: + text-inverse ≥ 4.5:1: 1.04 at light: action-primary + action-primary ≥ 3:1: 1.04 at light: surface-sunken ``` -## 5. Convert scales to CSS variables +Pick another hue, or change the override that caused it, and build again. -`.createScaleVariables` turns a scale into real CSS custom properties, for better runtime performance: +## 4. Keep the files current + +Add the build to your scripts: + +```json title="package.json" +{ + "scripts": { + "brand": "gamut-brand build src/theme/acme.brand.ts", + "brand:watch": "gamut-brand build src/theme/acme.brand.ts --watch", + "brand:check": "gamut-brand build src/theme/acme.brand.ts --check" + } +} +``` + +Run `--watch` next to your dev server. It regenerates when the brand file, or a file next to it, changes, and only rewrites files whose contents changed. Run `--check` in CI: it writes nothing, and fails when the files are out of date, including after an upgrade of `@skillsoft/gamut-brand`. `--out ` writes somewhere other than a folder named after the brand. + +## 5. Build the theme + +`createBrandTheme` takes the generated values and everything about the brand that isn't color: + +```ts +// src/theme/index.ts +import { + createBrandTheme, + fontWeightMediumTitle, + percipioFontFamily, + softElevation, +} from '@skillsoft/gamut-styles'; + +import * as values from './acme/values'; + +export const acmeTheme = createBrandTheme(values, { + name: 'acme', + fontFamily: percipioFontFamily, + fontWeight: fontWeightMediumTitle, + elevation: softElevation, +}); +``` + +| Option | What it sets | +| ------------- | ----------------------------------------------------------------------------------------------------------------- | +| `name` | The theme's name | +| `fontFamily` | Every font family key Gamut uses. `percipioFontFamily` is Skillsoft's | +| `fontWeight` | Every font weight key, including `title`. `fontWeightMediumTitle` sets titles to 500 | +| `elevation` | How shadows are drawn. `softElevation` is the soft shadow; `offsetElevation` is Original Codecademy's hard offset | +| `borderRadii` | Optional. Defaults to Gamut's radii | + +Shadow colors come from the brand's `shadow-key` and `shadow-ambient` roles, so an elevation works with any brand. + +## 6. Use it + +Pass the theme to `GamutProvider`. It puts the brand's CSS on the page, so the app loads nothing else: ```tsx -const myTheme = createTheme({ ...coreTheme }) - .addColors(myColorPalette) - .addColorModes('light', colorModeConfig) - .addScale('shadows', shadowConfig) - .createScaleVariables('shadows') // creates --shadow-sm, --shadow-md, ... - .addName('my-custom-theme') - .build(); + + + +``` + +Declare the Emotion theme type once, as for any Gamut app. Every brand theme shares the `SkillsoftTheme` type: + +```ts title="emotion.d.ts" +import '@emotion/react'; +import type { SkillsoftTheme } from '@skillsoft/gamut-styles'; + +declare module '@emotion/react' { + export interface Theme extends SkillsoftTheme {} +} ``` -## Using your theme +`` then compiles, and `` doesn't. + +A page without React loads the generated `acme/tokens.css` instead. -Follow [Installation](/getting-started/installation/) to wire up `GamutProvider` with your new theme, the same way you would with any existing one. +## Previewing in Gamut's Storybook -To preview it against real components in Storybook before shipping, register it in the theme switcher: +To preview a brand against real components in Gamut's own Storybook, add its theme to the theme switcher: ```tsx -// packages/styleguide/.storybook/theming/GamutThemeProvider.tsx +// packages/.storybook/theming/GamutThemeProvider.tsx const themeMap = { - core: coreTheme, - admin: adminTheme, - lxStudio: lxStudioTheme, - percipio: percipioTheme, - 'your-theme-here': yourCustomTheme, + // ...Gamut's themes + acme: acmeTheme, } as const; ``` -```tsx -// .storybook/preview.ts -theme: { - name: 'Theme', - defaultValue: 'percipio', - toolbar: { - icon: 'paintbrush', - items: [ - { value: 'core', title: 'Core' }, - { value: 'your-theme-here', title: 'Custom Theme' }, - ], - }, -}, +```ts +// packages/.storybook/preview.ts, under globalTypes.theme.toolbar.items +{ value: 'acme', title: 'Acme' }, ``` ## Best practices -- Replace every token, or extend from `coreTheme` — a partial theme breaks compatibility with existing components. -- Override an existing scale in `createTheme()`'s initial object; reach for `.addScale()` only for scales that don't already exist. -- Map your palette to semantic color names (`primary`, `secondary`, and the rest), not just raw brand colors. -- Provide both light and dark color modes. -- Convert frequently-used scales to CSS variables for performance. -- Test the theme against real Gamut components before shipping it. +- Supply as few hues as you need. Every hue you supply is one more you have to keep in step with your brand. +- Prefer a hue over an override. Overrides are for a color that has to be exact, such as a logo color. +- Use roles in your own code too, never palette colors. A palette color doesn't change with the mode. +- Check both modes in Storybook before shipping. diff --git a/packages/gamut-docs/src/content/docs/guides/supporting-dark-mode.md b/packages/gamut-docs/src/content/docs/guides/supporting-dark-mode.md index 3a2333aaf1..3e4a187022 100644 --- a/packages/gamut-docs/src/content/docs/guides/supporting-dark-mode.md +++ b/packages/gamut-docs/src/content/docs/guides/supporting-dark-mode.md @@ -3,7 +3,7 @@ title: Supporting dark mode description: Make a page or component work correctly in both color modes. --- -Gamut components are built on semantic color aliases (`text`, `background`, `primary`, `secondary`, and more — see [Color modes](/concepts/color-modes/) for why), so most components support both color modes automatically, with no extra work. This guide covers the parts that need you to actually do something. +Gamut components are built on color roles (`text-default`, `surface-raised`, `action-primary` and the rest; see [Color modes](/concepts/color-modes/) for why), so most components support both color modes with no extra work. This guide covers the parts that need you to do something. ## Setting a color mode @@ -22,26 +22,30 @@ const Page = ({ children }) => {children}; ## Setting a background without guessing the mode -Use `` instead of picking a color mode by hand for a section's background — it checks the contrast between the background color you give it and the current mode's text color, and switches to an accessible mode automatically if the combination wouldn't pass: +Use `` instead of picking a color mode by hand for a section's background. It paints the color you give it and switches the mode inside to whichever one's text reads better on that color: ```tsx import { Background } from '@skillsoft/gamut-styles'; -const Page = ({ children }) => {children}; +const Page = ({ children }) => ( + {children} +); ``` -`` establishes a new color context for everything inside it, so you can safely nest one background inside another — a dark section can contain its own lighter card, for example, and each still resolves its own aliases correctly. +The choice is made at build time: for every opaque palette color, the color system records whether light-mode or dark-mode content reads better on it, and `Background` looks the answer up. -The color a `` resolves to is exposed as the `background-current` theme variable — reach for it when a descendant needs to reference its ancestor's background directly, for example to simulate transparency against it. +`` establishes a new color context for everything inside it, so you can safely nest one background inside another. A dark section can contain its own lighter card, and each still resolves its own roles correctly. + +The color a `` paints is exposed as the `background-current` theme variable (`--color-background-current` in CSS). Reach for it when a descendant needs to reference its ancestor's background directly, for example to simulate transparency against it. ## Reading the current mode in JS -Reach for these only when you need the mode value itself, not just correctly-themed styles: +Reach for these only when you need the mode value itself, not just correctly themed styles: -- `useColorModes()` — returns `[mode, modeColors, modes, getColorValue]`, for when you need every mode's values, not just the active one. -- `useCurrentMode(mode?)` — returns just the active mode key, or lets you override it. -- `usePrefersDarkMode()` — returns a boolean from the reader's OS-level `prefers-color-scheme: dark` media query, independent of whatever `` is actually active. +- `useColorModes()` returns `[mode, modeColors, modes, getColorValue]`, for when you need every mode's values, not just the active one. +- `useCurrentMode(mode?)` returns just the active mode key, or lets you override it. +- `usePrefersDarkMode()` returns a boolean from the reader's OS-level `prefers-color-scheme: dark` media query, independent of whatever `` is active. ## Checking your work -Toggle color mode with Storybook's Color Mode selector while reviewing a component or page — a component built entirely from semantic aliases and system props should need no other changes to read correctly in both modes. If something looks wrong in one mode only, that's usually a raw color value that should have been a semantic alias instead. +Toggle color mode with Storybook's Color Mode selector while reviewing a component or page. A component built entirely from roles and system props should need no other changes to read correctly in both modes. If something looks wrong in one mode only, it's usually a palette color or a raw value that should have been a role. A palette color, such as `palette-neutral-light-100`, is the same in every mode, so it doesn't change when the mode does. diff --git a/packages/gamut-docs/src/content/docs/guides/theming-your-app.md b/packages/gamut-docs/src/content/docs/guides/theming-your-app.md index 031b746c67..20338449fa 100644 --- a/packages/gamut-docs/src/content/docs/guides/theming-your-app.md +++ b/packages/gamut-docs/src/content/docs/guides/theming-your-app.md @@ -3,25 +3,36 @@ title: Theming your app description: Choose and wire up a Gamut theme for your application. --- -Every Emotion component in Gamut has typed access to the current theme's tokens, without any extra imports — most directly through the `theme` prop every styled component receives, or through the `theme` object imported straight from `@skillsoft/gamut-styles`. +Every Emotion component in Gamut has typed access to the current theme's tokens, without any extra imports: through the `theme` prop every styled component receives, or through the `theme` object imported from `@skillsoft/gamut-styles`. ## 1. Choose a theme -See [Themes](/foundations/themes/) for the available palettes (Core, Admin, LX Studio, Percipio, Platform) and what each one is for. Use Storybook's Theme Switcher (paintbrush icon in the toolbar) to preview your components across every theme before committing to one, paired with the Color Mode selector to check both light and dark variants. +See [Themes](/foundations/themes/) for the themes Gamut ships (Skillsoft, Original Codecademy, Admin, Platform, LX Studio) and what each one is for. Use Storybook's Theme Switcher (paintbrush icon in the toolbar) to preview your components in each theme before committing to one, and the Color Mode selector to check light and dark. ## 2. Wire up the theme -`GamutProvider` (covered in [Installation](/getting-started/installation/)) already puts the right theme's values on Emotion's context for you. From there, reach a token: +Pass the theme to `GamutProvider` (covered in [Installation](/getting-started/installation/)). It puts the theme's values on Emotion's context and its brand's colors on the page: + +```tsx +import { + GamutProvider, + originalCodecademyTheme, +} from '@skillsoft/gamut-styles'; + +export const App = ({ children }) => ( + {children} +); +``` + +Without a `theme`, `GamutProvider` uses `skillsoftTheme`. From there, reach a token: ```tsx import { css } from '@skillsoft/gamut-styles'; import styled from '@emotion/styled'; -// Preferred: through a system prop -const Box = styled.div(css({ bg: 'navy-400', p: 4 })); - -// Preferred: through a ColorMode semantic alias -const OtherCoolThing = styled.div(css({ color: 'primary', p: 4 })); +// Preferred: through a system prop, with a color role +const Panel = styled.div(css({ bg: 'surface-raised', p: 4 })); +const Callout = styled.div(css({ color: 'action-primary', p: 4 })); ``` ```tsx @@ -29,14 +40,14 @@ const OtherCoolThing = styled.div(css({ color: 'primary', p: 4 })); import styled from '@emotion/styled'; const styles = styled.div` - color: ${({ theme }) => theme.colors.blue}; + color: ${({ theme }) => theme.colors['text-subtle']}; `; ``` -Prefer the first form — a [system prop](/foundations/system-props/) — whenever one exists for what you're styling; it's type-checked against the active theme's scales, so a typo or an out-of-range value is a compile error instead of a silent fallback. +Prefer the first form, a [system prop](/foundations/system-props/), whenever one exists for what you're styling. It's type-checked against the theme's scales, so a typo or an out-of-range value is a compile error instead of a silent fallback. ## 3. Check your work across themes -Re-open the Theme Switcher and step through every theme your app might run under, in both color modes. A component built entirely from semantic aliases and system props should need no extra work to look right everywhere; if something looks off in one theme, that's usually a sign a raw value snuck in somewhere it should have been a token. +Re-open the Theme Switcher and step through every theme your app might run under, in both color modes. A component built entirely from roles and system props should need no extra work to look right everywhere. If something looks off in one theme, a raw value or a palette color usually snuck in where a role belonged. -See [Design tokens](/foundations/design-tokens/) for the full list of token categories, and [Color modes](/concepts/color-modes/) for how color mode composes with theme choice. +See [Design tokens](/foundations/design-tokens/) for the token categories, [Color modes](/concepts/color-modes/) for how color mode combines with a brand, and [Color system](/concepts/color-system/#pick-a-role-by-its-job) for which role fits which job. diff --git a/packages/gamut-docs/src/styles/gamut-core-theme.css b/packages/gamut-docs/src/styles/gamut-core-theme.css deleted file mode 100644 index 50cc69e2ca..0000000000 --- a/packages/gamut-docs/src/styles/gamut-core-theme.css +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Custom Starlight color theme derived from Gamut's `coreTheme` - * (packages/gamut-styles/src/themes/core.ts). - * - * Starlight themes are just overrides of its CSS custom properties (see - * https://starlight.astro.build/guides/css-and-tailwind/#theming). Each - * variable below is paired with the coreTheme color-mode token it stands in - * for, so this file can be re-derived if coreTheme's palette changes. - * - * Only colors are wired up here — coreTheme's typography (fontFamily) relies - * on licensed, non-self-hosted fonts (Apercu/Suisse) that this site doesn't - * load, so Starlight's default font stack is left in place. - */ - -:root[data-theme="light"] { - /* gray-1..7: coreTheme's light-mode navy scale, most-to-least intense */ - --sl-color-gray-1: #0a0d1c; /* navy-900 — text.accent */ - --sl-color-gray-2: #10162f; /* navy-800 — text._ (body text) */ - --sl-color-gray-3: rgba(16, 22, 47, 0.86); /* navy-700 */ - --sl-color-gray-4: rgba(16, 22, 47, 0.75); /* navy-600 — border.secondary */ - --sl-color-gray-5: rgba( - 16, - 22, - 47, - 0.28 - ); /* navy-300 — border.tertiary (hairline) */ - --sl-color-gray-6: rgba( - 16, - 22, - 47, - 0.12 - ); /* navy-200 — subtle bg tint (inline code) */ - --sl-color-gray-7: rgba( - 16, - 22, - 47, - 0.04 - ); /* navy-100 — background.selected */ - - /* white/black are Starlight's text/bg endpoints, inverted per mode */ - --sl-color-white: #0a0d1c; /* navy-900 — heading text */ - --sl-color-black: #ffffff; /* background._ — page background */ - - /* accent: primary._ / primary.hover (hyper) */ - --sl-color-accent-low: color-mix(in srgb, #3a10e5 10%, white); - --sl-color-accent: #3a10e5; /* hyper-500 */ - --sl-color-accent-high: color-mix(in srgb, #3a10e5 65%, black); - - /* background.primary — a warm chrome tone behind the top nav, distinct - from the white content canvas (background._) */ - --sl-color-bg-nav: #fff0e5; /* beige */ -} - -:root[data-theme="dark"] { - /* gray-1..6: coreTheme's dark-mode white scale, most-to-least intense */ - --sl-color-gray-1: #ffffff; /* white — text._ */ - --sl-color-gray-2: rgba(255, 255, 255, 0.8); /* white-700 */ - --sl-color-gray-3: rgba(255, 255, 255, 0.65); /* white-600 — text.secondary */ - --sl-color-gray-4: rgba(255, 255, 255, 0.5); /* white-500 — border.disabled */ - --sl-color-gray-5: rgba( - 255, - 255, - 255, - 0.2 - ); /* white-300 — border.tertiary (hairline) */ - --sl-color-gray-6: rgba( - 255, - 255, - 255, - 0.09 - ); /* white-200 — subtle bg tint (inline code) */ - - --sl-color-white: #ffffff; /* text._ — heading text */ - --sl-color-black: #10162f; /* navy-800 — background._ (page background) */ - - /* accent: primary._ / primary.hover (yellow) */ - --sl-color-accent-low: color-mix(in srgb, #ffd300 20%, black); - --sl-color-accent: #ffd300; /* yellow-500 */ - --sl-color-accent-high: color-mix(in srgb, #ffd300 85%, white); - - /* background.primary — a deeper chrome tone behind the top nav, distinct - from the navy-800 content canvas (background._) */ - --sl-color-bg-nav: #0a0d1c; /* navy-900 */ -} diff --git a/packages/gamut-docs/src/styles/gamut-theme.css b/packages/gamut-docs/src/styles/gamut-theme.css new file mode 100644 index 0000000000..2e33181bd9 --- /dev/null +++ b/packages/gamut-docs/src/styles/gamut-theme.css @@ -0,0 +1,82 @@ +/* + * Starlight's colors, taken from the Skillsoft brand of Gamut's color system. + * + * `@skillsoft/gamut-styles/tokens.css`, loaded before this file, puts the + * brand's palette on `:root`. Starlight switches modes with `data-theme`, not + * Gamut's `data-color-mode`, so the roles' mode blocks don't apply here. + * Instead each Starlight variable points at the palette color its Gamut role + * uses in that mode, named in the comment beside it. If a role's mapping + * changes, update the line here. + * + * Starlight themes are overrides of its CSS custom properties + * (https://starlight.astro.build/guides/css-and-tailwind/#theming). Only + * colors are wired up; Skillsoft's fonts aren't loaded on this site, so + * Starlight's font stack stays. + */ + +:root[data-theme="light"] { + /* gray-1..7, most to least intense */ + /* border-strong */ + --sl-color-gray-1: var(--color-palette-neutral-dark-1100); + /* text-default */ + --sl-color-gray-2: var(--color-palette-neutral-dark-1000); + /* text-subtle */ + --sl-color-gray-3: var(--color-palette-neutral-dark-800); + /* border-default */ + --sl-color-gray-4: var(--color-palette-neutral-dark-600); + /* border-subtle */ + --sl-color-gray-5: var(--color-palette-neutral-dark-300); + /* surface-sunken */ + --sl-color-gray-6: var(--color-palette-neutral-light-200); + /* surface-default */ + --sl-color-gray-7: var(--color-palette-neutral-light-100); + + /* Starlight's text and background endpoints */ + /* text-default */ + --sl-color-white: var(--color-palette-neutral-dark-1000); + /* surface-default */ + --sl-color-black: var(--color-palette-neutral-light-100); + + --sl-color-accent-low: var(--color-palette-primary-100); + /* action-primary */ + --sl-color-accent: var(--color-palette-primary-800); + /* action-primary-pressed */ + --sl-color-accent-high: var(--color-palette-primary-1000); + + /* surface-raised */ + + --sl-color-bg-nav: var(--color-white); +} + +:root[data-theme="dark"] { + /* border-strong */ + --sl-color-gray-1: var(--color-palette-neutral-light-100); + /* text-default */ + --sl-color-gray-2: var(--color-palette-neutral-light-200); + /* text-subtle */ + --sl-color-gray-3: var(--color-palette-neutral-light-400); + /* border-default */ + --sl-color-gray-4: var(--color-palette-neutral-light-600); + /* border-subtle */ + --sl-color-gray-5: var(--color-palette-neutral-light-900); + /* surface-raised */ + --sl-color-gray-6: var(--color-palette-neutral-dark-1000); + /* surface-default */ + --sl-color-gray-7: var(--color-palette-neutral-dark-1100); + + /* border-strong */ + + --sl-color-white: var(--color-palette-neutral-light-100); + /* surface-default */ + --sl-color-black: var(--color-palette-neutral-dark-1100); + + --sl-color-accent-low: var(--color-palette-primary-1100); + /* action-primary */ + --sl-color-accent: var(--color-palette-primary-400); + /* action-primary-pressed */ + --sl-color-accent-high: var(--color-palette-primary-200); + + /* surface-sunken */ + + --sl-color-bg-nav: var(--color-black); +} diff --git a/packages/gamut-styles/README.md b/packages/gamut-styles/README.md index d2b069e402..8d2ce99db1 100644 --- a/packages/gamut-styles/README.md +++ b/packages/gamut-styles/README.md @@ -6,3 +6,70 @@ Design tokens, Emotion themes, and system props for the Gamut design system. This folder contains a base stylesheet for the app. This should be imported **once** in your application. + +## Color tokens + +Colors come from a generated system: a palette of 8 slots × 11 grades, and 43 +semantic roles in light and dark mode. The values are checked in and shipped +as static CSS, so the colors don't depend on Emotion. + +| Import | What | +| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| `@skillsoft/gamut-styles/tokens` | TS constants: `palette`, `roles`, `gradients`, `contentModes`, `brand` (OKLCH + sRGB per token) | +| `@skillsoft/gamut-styles/tokens.css` | `--color-*` and `--gradient-*` custom properties. Palette on `:root`; roles on `:root, [data-color-mode="light"]` and `[data-color-mode="dark"]` | +| `@skillsoft/gamut-styles/tokens/{palette,light,dark}.tokens.json` | Color tokens in the DTCG shape: `{ colorSpace: "oklch", components, alpha, hex }` | +| `skillsoftTheme` (main entry) | The default Emotion theme, built on the roles. Gamut components are typed against it | + +### Tokens in the browser + +`GamutProvider` injects `tokens.css` through Emotion by default. It does this +when the theme has `tokensCss` (`skillsoftTheme` does) and `useGlobals` is on, +so a React app gets the colors without loading anything. +Pages without React or Emotion can load `tokens.css` directly. An app that +loads it itself passes `useTokens={false}` to avoid injecting it twice. + +- A mode switch is an attribute: `data-color-mode="light"` or `"dark"` on any + element, nested as deep as you like. Each mode block also sets + `color-scheme`, `color: var(--color-text-default)` and a default + `--color-background-current`. +- Outside Emotion, paint a surface by resolving the role to its palette color + in the outer mode (`roles[mode][role].ref`), using `var(--color-)`, + and set `data-color-mode` to `contentModes[palette]`. The palette color is + the same in both modes, so switching modes can't change the background under + it. +- `Background` reads `theme.contentModes` when the theme has it, and falls back + to polished's contrast math only on themes that don't. + +Storybook has a live example under **Foundations / Color system / CSS contract**. + +### Naming + +- Palette primitives: `--color-palette--`, for example + `--color-palette-primary-800`. The slots are `neutral-light`, + `neutral-dark`, `primary`, `accent`, `accent-alt`, `danger`, `warning`, and + `success`. Use roles in components, not primitives. +- Translucent primitives add an alpha suffix: `--color-palette-primary-600-a6` + (6% alpha) and `--color-black-a60`. +- Roles: `--color-`, for example `--color-action-primary`. +- `white` and `black` keep their bare names as boundary tokens. + +The `--color-` prefix is the color scale's prefix in variance, so the Emotion and +CSS routes use one naming scheme. + +### Known limits + +- The theme carries sRGB values, not OKLCH. `Background` and `BarChart` run + polished's contrast math on theme values, and polished can't parse + `oklch()`. +- Components that use older core primitives directly (`navy-900`, `gray-100`, + `yellow`, and similar) don't map to this theme. Use roles instead. +- `skillsoftTheme` is the only theme in this package. It has no legacy color + names, so components are checked against it alone. `ColorMode` and + `Background` still fall back to old names through `roleOr`, which the + brand themes that follow will use. +- The categorical chart palette (`viz-1` to `viz-8`) uses the slot and grade + order chosen for Skillsoft's hues. A custom brand gets that order without + re-solving the chart colors. + +For how the color roles are meant to be used, see the design tokens page in +gamut-docs. diff --git a/packages/gamut-styles/package.json b/packages/gamut-styles/package.json index c1540e8e88..14c430b3e6 100644 --- a/packages/gamut-styles/package.json +++ b/packages/gamut-styles/package.json @@ -18,7 +18,15 @@ "types": "./dist/index.d.ts", "import": "./dist/index.mjs", "require": "./dist/index.js" - } + }, + "./tokens": { + "types": "./dist/tokens/index.d.ts", + "import": "./dist/tokens/index.mjs", + "require": "./dist/tokens/index.js" + }, + "./tokens.css": "./dist/tokens/generated/skillsoft/tokens.css", + "./tokens/*.css": "./dist/tokens/generated/*/tokens.css", + "./tokens/*.tokens.json": "./dist/tokens/generated/*.tokens.json" }, "files": [ "dist", @@ -39,8 +47,16 @@ }, "repository": "git@github.com:Codecademy/skillsoft-gamut.git", "scripts": { - "build": "nx build gamut-styles" + "build": "nx build gamut-styles", + "build:tokens": "jiti scripts/build-tokens.ts", + "validate:tokens": "jiti scripts/validate-tokens.ts" }, "sideEffects": false, - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "devDependencies": { + "@skillsoft/gamut-brand": "workspace:*", + "@types/culori": "^4", + "culori": "^4.0.2", + "jiti": "2.4.2" + } } diff --git a/packages/gamut-styles/project.json b/packages/gamut-styles/project.json index 6dbebe54e2..0e81b5feab 100644 --- a/packages/gamut-styles/project.json +++ b/packages/gamut-styles/project.json @@ -17,7 +17,8 @@ "rm -rf ./dist", "tsdown", "tsc --emitDeclarationOnly --project tsconfig.lib.json", - "cpy './src/**/*.{d.ts,svg,png}' ./dist" + "cpy './src/**/*.{d.ts,svg,png}' ./dist", + "cpy './src/tokens/generated/**/*.{css,json}' ./dist/tokens/generated" ], "parallel": false } @@ -43,7 +44,10 @@ "dependsOn": ["build"], "options": { "cwd": "{projectRoot}", - "commands": ["publint .", "attw --pack . --ignore-rules false-cjs"], + "commands": [ + "publint .", + "attw --pack . --ignore-rules false-cjs no-resolution" + ], "parallel": false } }, diff --git a/packages/gamut-styles/scripts/build-tokens.ts b/packages/gamut-styles/scripts/build-tokens.ts new file mode 100644 index 0000000000..64c5c0709e --- /dev/null +++ b/packages/gamut-styles/scripts/build-tokens.ts @@ -0,0 +1,82 @@ +/* eslint-disable no-console -- CLI script; the console is its output. */ +/* + * gamut's own brands → src/tokens/generated//, one folder per brand, + * through the same API a team building its own brand uses + * (@skillsoft/gamut-brand). + * + * yarn workspace @skillsoft/gamut-styles build:tokens + * yarn workspace @skillsoft/gamut-styles build:tokens --check + * + * Output is checked in, so the theme and the package build don't need the + * generator, culori or Style Dictionary. `--check` writes nothing and exits 1 + * if anything is out of date; a test runs it in CI. + * + * It imports @skillsoft/gamut-brand from source, not the package, so it + * never needs a build first. + */ +import { readdir, readFile, rm, writeFile } from 'node:fs/promises'; +import { join, relative } from 'node:path'; + +import { + BRANDS, + DEFAULT_BRAND, + staleFiles, + writeBrand, +} from '../../gamut-brand/src'; + +const OUT = join(__dirname, '../src/tokens/generated'); + +/* The brands as namespaces: `brands.originalCodecademy.palette`, … */ +const index = [ + '/* Generated by scripts/build-tokens.ts. Do not edit. */', + ...BRANDS.map(({ name }) => `export * as ${name} from './${name}/values';`), + '', +].join('\n'); + +const check = async () => { + const stale = ( + await Promise.all(BRANDS.map((b) => staleFiles(b, join(OUT, b.name)))) + ).flat(); + const current = await readFile(join(OUT, 'index.ts'), 'utf8').catch( + () => undefined + ); + if (current !== index) stale.push(join(OUT, 'index.ts')); + if (stale.length) { + console.error( + `out of date: ${stale + .map((p) => relative(OUT, p)) + .join( + ', ' + )}\nrun \`yarn workspace @skillsoft/gamut-styles build:tokens\`` + ); + process.exit(1); + } + console.log('generated tokens are current'); +}; + +const build = async () => { + /* Cleared first, so a renamed or removed brand leaves nothing behind. */ + const keep = new Set(BRANDS.map((b) => b.name)); + for (const entry of await readdir(OUT, { withFileTypes: true })) { + if (!(entry.isDirectory() && keep.has(entry.name))) { + await rm(join(OUT, entry.name), { recursive: true, force: true }); + } + } + for (const brand of BRANDS) { + await writeBrand(brand, join(OUT, brand.name)); + } + await writeFile(join(OUT, 'index.ts'), index); + console.log(`wrote ${BRANDS.map((b) => b.name).join(', ')} to ${OUT}`); +}; + +const main = async () => { + if (BRANDS[0] !== DEFAULT_BRAND) { + throw new Error('BRANDS must start with DEFAULT_BRAND'); + } + await (process.argv.includes('--check') ? check() : build()); +}; + +main().catch((e) => { + console.error(e instanceof Error ? e.message : e); + process.exit(1); +}); diff --git a/packages/gamut-styles/scripts/validate-tokens.ts b/packages/gamut-styles/scripts/validate-tokens.ts new file mode 100644 index 0000000000..95bbbf862c --- /dev/null +++ b/packages/gamut-styles/scripts/validate-tokens.ts @@ -0,0 +1,166 @@ +/* eslint-disable no-console -- CLI script; the console is its output. */ +/* + * Proves the emitted tokens are the system we meant to ship. + * + * yarn workspace @skillsoft/gamut-styles validate:tokens + * + * For each of gamut's brands: + * 1. The guarantees hold on the source system. + * 2. The guarantees hold again on values parsed back out of the brand's + * tokens.css, so nothing Style Dictionary did (rounding, references) broke + * them. gamut-brand refuses to generate a brand that fails this; the + * report shows the numbers. + * 3. The brand's generated files are up to date with the generator. + * 4. The content-mode table picks what Background's runtime math picked. + * Then sample tenants, run through the probe. + */ +import { readFile } from 'node:fs/promises'; +import { join, relative } from 'node:path'; + +import { getContrast } from 'polished'; + +import type { BrandInput, Check } from '../../gamut-brand/src'; +import { + BRANDS, + createColorSystem, + DEFAULT_BRAND, + probeTenant, + staleFiles, + validate, + validateMigration, +} from '../../gamut-brand/src'; +import { contentModes } from '../../gamut-brand/src/contentModes'; +import { + parseTokensCss, + roundTripMisses, +} from '../../gamut-brand/src/roundTrip'; +import type { ColorSystem } from '../../gamut-brand/src/system'; + +const GENERATED = join(__dirname, '../src/tokens/generated'); + +let failures = 0; +const report = (ok: boolean, line: string) => { + if (!ok) failures += 1; + console.log(`${ok ? 'PASS' : 'FAIL'} ${line}`); +}; +const printChecks = (checks: Check[]) => { + for (const c of checks) { + const line = `${c.name} — worst ${c.worst.toFixed(3)} (${c.at})`; + if (c.reported) console.log(`INFO ${line}`); + else report(c.pass, line); + } +}; + +const TENANTS: BrandInput[] = [ + { name: 'tier 1: warm primary (orange)', hues: { primary: 45 } }, + { name: 'tier 1: primary near our accent', hues: { primary: 290 } }, + { name: 'tier 1: from a hex (#E4002B)', hues: { primary: '#E4002B' } }, + { + name: 'tier 4: hostile, eight hues in a 40° arc', + hues: { + 'neutral-light': 250, + 'neutral-dark': 255, + 'accent-alt': 260, + primary: 265, + accent: 270, + danger: 275, + warning: 280, + success: 285, + }, + }, +]; + +/* + * Background picked a mode by running polished's getContrast on sRGB hex. + * The table measures OKLCH floats instead, so it can only disagree on a color + * sitting right at the crossover. Any disagreement is listed. + * + * polished rounds to two decimals, so on an exact tie its `>` falls back to + * light without having measured a difference. A tie isn't a disagreement; + * it's listed on its own, with the table's pick. + */ +const backgroundParity = (system: ColorSystem) => { + const table = contentModes(system); + const text = (mode: 'light' | 'dark') => + system.roles[mode]['text-default'].hex; + const ties: string[] = []; + const differ = Object.entries(system.palette).filter(([key, { hex }]) => { + const dark = getContrast(text('dark'), hex); + const light = getContrast(text('light'), hex); + if (dark === light) { + ties.push(`${key} (${dark}:1 both ways, table picks ${table[key]})`); + return false; + } + return (dark > light ? 'dark' : 'light') !== table[key]; + }); + if (ties.length) console.log(`INFO polished ties: ${ties.join(', ')}`); + report( + differ.length === 0, + `${ + Object.keys(table).length + } palette colors pick the same mode as runtime Background${ + differ.length ? `; differ: ${differ.map(([k]) => k).join(', ')}` : '' + }` + ); +}; + +const main = async () => { + for (const brand of BRANDS) { + const system = createColorSystem(brand); + const dir = join(GENERATED, brand.name); + const isDefault = brand.name === DEFAULT_BRAND.name; + console.log(`\n══ ${brand.name}${isDefault ? ' (default)' : ''}`); + + console.log('\n— guarantees, source system'); + printChecks(validate(system)); + + console.log('\n— migration map'); + printChecks(validateMigration(system)); + + console.log(`\n— guarantees, re-measured on ${brand.name}/tokens.css`); + const css = await readFile(join(dir, 'tokens.css'), 'utf8'); + const parsed = parseTokensCss(css, system); + const misses = roundTripMisses(parsed, system); + report( + misses.length === 0, + `tokens.css round-trips every role to the source value and reference${ + misses.length ? `: ${misses.join(', ')}` : '' + }` + ); + printChecks([...validate(parsed), ...validateMigration(parsed)]); + + console.log('\n— generated files are current'); + const stale = await staleFiles(brand, dir); + report( + stale.length === 0, + `${brand.name}/ matches the generator${ + stale.length + ? `; stale: ${stale.map((p) => relative(GENERATED, p)).join(', ')}` + : '' + }` + ); + + console.log('\n— content-mode table vs runtime Background'); + backgroundParity(system); + } + + console.log('\n— tenant probes (reported, not asserted)'); + for (const tenant of [...BRANDS, ...TENANTS]) { + const p = probeTenant(tenant); + console.log( + `INFO ${p.brand}: tier ${p.tier}, contrast ${ + p.contrastHolds ? 'holds' : `fails (${p.failedChecks.join('; ')})` + }, status trio ${p.statusSeparation.toFixed(2)} ΔE (${p.statusWorst})${ + p.derived.length ? `, derived: ${p.derived.join(', ')}` : '' + }` + ); + } + + console.log(failures ? `\n${failures} failed` : '\nall checks pass'); + process.exit(failures ? 1 : 0); +}; + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/packages/gamut-styles/src/AssetProvider.tsx b/packages/gamut-styles/src/AssetProvider.tsx index 479b2161eb..d06c2aa1bd 100644 --- a/packages/gamut-styles/src/AssetProvider.tsx +++ b/packages/gamut-styles/src/AssetProvider.tsx @@ -2,7 +2,7 @@ import type { Theme } from '@emotion/react'; import type * as React from 'react'; import { webFonts } from './remoteAssets/fonts'; -import { coreTheme } from './themes'; +import { skillsoftTheme } from './themes'; import type { FontConfig } from './utilities/fontUtils'; import { getFonts } from './utilities/fontUtils'; @@ -39,8 +39,8 @@ export const createFontLinks = (fonts?: readonly FontConfig[]) => { export const AssetProvider: React.FC<{ theme?: Theme }> = ({ theme }) => { try { - const defaultTheme = theme ?? coreTheme; - const themeName = defaultTheme?.name || 'core'; + const defaultTheme = theme ?? skillsoftTheme; + const themeName = defaultTheme?.name || 'skillsoft'; const fonts = getFonts(themeName); return <>{createFontLinks(fonts)}; diff --git a/packages/gamut-styles/src/Background.tsx b/packages/gamut-styles/src/Background.tsx index eeac3d5e2f..7103d34fa6 100644 --- a/packages/gamut-styles/src/Background.tsx +++ b/packages/gamut-styles/src/Background.tsx @@ -1,3 +1,5 @@ +import type { Theme } from '@emotion/react'; +import { useTheme } from '@emotion/react'; import { getContrast } from 'polished'; import type { ComponentProps } from 'react'; import { forwardRef, useCallback, useMemo } from 'react'; @@ -8,7 +10,7 @@ import type { ColorModeShape, Colors, } from './ColorMode'; -import { ColorMode, useColorModes } from './ColorMode'; +import { ColorMode, roleOr, useColorModes } from './ColorMode'; export interface BackgroundProps extends Omit< @@ -33,9 +35,14 @@ export const isColorAlias = ( return Object.keys(mode).includes(color); }; +const textOf = (mode: ColorModeShape) => + roleOr(mode as Record, 'text-default', 'text') as Colors; + export const Background = forwardRef( ({ bg, ...rest }, ref) => { const [active, activeColors, modes, getColorValue] = useColorModes(); + /* Generated themes ship the answer (gamut-brand src/contentModes.ts). */ + const { contentModes } = useTheme() as Partial; /** If a color alias was used then look up the true color key from the active mode */ const trueColor = useMemo(() => { @@ -63,6 +70,21 @@ export const Background = forwardRef( */ const accessibleMode = useMemo(() => { + const listed = contentModes?.[trueColor as keyof typeof contentModes]; + if (listed) return listed; + + /* + * A bg the theme doesn't define (a core primitive on a theme without + * it) can't be measured; polished throws on it. Keep the active mode. + */ + if (!getColorValue(trueColor)) { + // eslint-disable-next-line no-console + console.warn( + `Background: "${trueColor}" isn't a color in this theme; keeping the "${active}" color mode.` + ); + return active; + } + const { [active]: activeMode, ...otherModes } = modes; const possibleModes = Object.entries(otherModes); @@ -77,20 +99,27 @@ export const Background = forwardRef( const [highestContrastMode] = possibleModes.reduce<[ColorModes, number]>( ( [prevMode, prevContrast], - [mode, { text }]: [ColorModes, ColorModeShape] + [mode, shape]: [ColorModes, ColorModeShape] ) => { - const contrast = getTextContrast(text); + const contrast = getTextContrast(textOf(shape)); // Keep the higher contrast mode. return contrast > prevContrast ? [mode, contrast] : [prevMode, prevContrast]; }, - [active, getTextContrast(activeMode.text)] + [active, getTextContrast(textOf(activeMode))] ); return highestContrastMode; - }, [modes, active, getTextContrast]); + }, [ + modes, + active, + getTextContrast, + getColorValue, + trueColor, + contentModes, + ]); /* * Paint the palette color, not the semantic color. A palette color is the diff --git a/packages/gamut-styles/src/ColorMode.tsx b/packages/gamut-styles/src/ColorMode.tsx index 4231b83d97..2f7b04a317 100644 --- a/packages/gamut-styles/src/ColorMode.tsx +++ b/packages/gamut-styles/src/ColorMode.tsx @@ -20,7 +20,6 @@ import { background, border, color, - css, flex, grid, layout, @@ -35,6 +34,17 @@ export type ColorModes = keyof ColorModeConfig; export type ColorModeShape = ColorModeConfig[ColorModes]; export type ColorAlias = keyof ColorModeShape; +/* + * Color-system spike: the new theme names roles differently from the other + * themes (`text-default` where they have `text`). Shared internals ask for the + * new name and fall back to the old one, so ColorMode works on every theme. + */ +export const roleOr = ( + names: Record, + role: string, + legacy: string +): string => names[role] ?? names[legacy]; + export type ColorModeProps = { mode: ColorModes | 'system'; bg?: Colors; @@ -91,9 +101,9 @@ export function useColorModes(): [ we could potentially alter the Merge type utility function from createTheme, but since 'background-current' is the only exception to the type-merging rule and this is the only place we override, this seems to be a more straightforward + lower-risk solution. */ - modesCopy[mode]['background-current'] = bgCurrent['background-current'] as - | 'white' - | 'navy-800'; + modesCopy[mode]['background-current'] = bgCurrent[ + 'background-current' + ] as (typeof modesCopy)[typeof mode]['background-current']; } return [mode, modesCopy?.[mode], modes, getColorValue]; @@ -135,7 +145,17 @@ export const VariableProvider = styled( variables?: CSSObject; alwaysSetVariables?: boolean; } ->(({ variables }) => variables, css({ textColor: 'text' }), providerProps); +>( + ({ variables }) => variables, + ({ theme }) => ({ + color: roleOr( + theme.colors as Record, + 'text-default', + 'text' + ), + }), + providerProps +); export const ColorMode = forwardRef< HTMLDivElement, @@ -160,12 +180,33 @@ export const ColorMode = forwardRef< : modes[active]['background-current'] : bg; + /* + * Generated themes (they carry `contentModes`) take every color value from + * tokens.css, keyed on the `data-color-mode` attribute, so a mode switch is + * the attribute. `data-brand` goes with it: roles resolve their palette + * where they're declared, so each mode switch re-declares them against + * this brand's palette. The only variable left to set is a + * background-current from `bg`. Other themes still serialize the whole mode. + */ + const fromTokensCss = Boolean(theme.contentModes); + const modeAttrs = fromTokensCss + ? { 'data-color-mode': mode, 'data-brand': theme.brand } + : {}; + /** Serialize color variables for the current mode * 1. If all variables are required add all mode variables to the current context * 2. If the user has specified a background color - set that color to the current-bg * 3. If not */ const { variables } = useMemo(() => { + if (fromTokensCss) { + return { + variables: + bg === undefined + ? {} + : { '--color-background-current': colors[bg] as string }, + }; + } return serializeTokens( mapValues(modes[mode], (color, key) => { if (key === 'background-current' && typeof bg !== 'undefined') { @@ -176,7 +217,7 @@ export const ColorMode = forwardRef< 'color', theme ); - }, [colors, mode, modes, theme, bg]); + }, [colors, mode, modes, theme, bg, fromTokensCss]); if (active === mode) { const vars = alwaysSetVariables @@ -187,7 +228,13 @@ export const ColorMode = forwardRef< - + ); } @@ -195,6 +242,7 @@ export const ColorMode = forwardRef< return ( .css`, or the one a team's + * `gamut-brand build` wrote). + */ + useTokens?: boolean; useCache?: boolean; theme: Theme; variables?: Record; @@ -51,9 +60,10 @@ export const useNonce = (): string | undefined => export const GamutProvider: React.FC = ({ children, cache, - theme = coreTheme, + theme = skillsoftTheme, variables, useGlobals = true, + useTokens = true, useCache = true, nonce, useLogicalProperties = false, @@ -89,6 +99,8 @@ export const GamutProvider: React.FC = ({ const globals = shouldInsertGlobals && ( <> + {/* First, so tokens.css's @layer order is declared before anything else. */} + {useTokens && 'tokensCss' in theme && } diff --git a/packages/gamut-styles/src/__tests__/AssetProvider.test.tsx b/packages/gamut-styles/src/__tests__/AssetProvider.test.tsx index 15fc801e83..f68d22d3be 100644 --- a/packages/gamut-styles/src/__tests__/AssetProvider.test.tsx +++ b/packages/gamut-styles/src/__tests__/AssetProvider.test.tsx @@ -3,7 +3,7 @@ import '@testing-library/jest-dom'; import { render } from '@testing-library/react'; import { AssetProvider, createFontLinks } from '../AssetProvider'; -import { coreTheme, percipioTheme } from '../themes'; +import { lxStudioTheme, skillsoftTheme } from '../themes'; import { getFontsMock } from './fontUtilsMock'; import { setupRtl } from './testUtils'; @@ -180,7 +180,7 @@ describe('AssetProvider', () => { }); describe('AssetProvider component', () => { - it('should render font links for core theme by default', () => { + it('should render font links for skillsoft theme by default', () => { mockGetFonts.mockReturnValue([ { filePath: 'https://www.codecademy.com/gamut/apercu-regular-pro', @@ -192,7 +192,7 @@ describe('AssetProvider', () => { const { view } = renderView(); const links = getPreloadLinks(view.container); - expect(mockGetFonts).toHaveBeenCalledWith('core'); + expect(mockGetFonts).toHaveBeenCalledWith('skillsoft'); expect(links).toHaveLength(1); expect(links[0]).toHaveAttribute( 'href', @@ -209,7 +209,7 @@ describe('AssetProvider', () => { }, ]); - const { view } = renderView({ theme: percipioTheme as any }); + const { view } = renderView({ theme: lxStudioTheme as any }); const links = getPreloadLinks(view.container); expect(links).toHaveLength(1); @@ -217,19 +217,19 @@ describe('AssetProvider', () => { 'href', 'https://www.codecademy.com/gamut/roboto-regular.woff2' ); - expect(mockGetFonts).toHaveBeenCalledWith('percipio'); + expect(mockGetFonts).toHaveBeenCalledWith('lxStudio'); }); it('should handle theme without name property', () => { - const themeWithoutName = { ...coreTheme, name: undefined }; + const themeWithoutName = { ...skillsoftTheme, name: undefined }; mockGetFonts.mockReturnValue([]); renderView({ theme: themeWithoutName }); - expect(mockGetFonts).toHaveBeenCalledWith('core'); + expect(mockGetFonts).toHaveBeenCalledWith('skillsoft'); }); it('should handle theme with invalid name', () => { - const themeWithInvalidName = { ...coreTheme, name: 'invalid-theme' }; + const themeWithInvalidName = { ...skillsoftTheme, name: 'invalid-theme' }; mockGetFonts.mockReturnValue([]); renderView({ theme: themeWithInvalidName }); diff --git a/packages/gamut-styles/src/__tests__/Background.test.tsx b/packages/gamut-styles/src/__tests__/Background.test.tsx index 7bc49c6788..93e6bf9a74 100644 --- a/packages/gamut-styles/src/__tests__/Background.test.tsx +++ b/packages/gamut-styles/src/__tests__/Background.test.tsx @@ -1,18 +1,11 @@ import { matchers } from '@emotion/jest'; import { ThemeProvider, useTheme } from '@emotion/react'; -import { render } from '@testing-library/react'; import { setupRtl as setupRtlBase } from 'component-test-setup'; import overArgs from 'lodash/overArgs'; import type * as React from 'react'; import { Background } from '../Background'; -import { - adminTheme, - coreTheme as theme, - lxStudioTheme, - percipioTheme, - platformTheme, -} from '../themes'; +import { skillsoftTheme as theme } from '../themes'; expect.extend(matchers); @@ -44,10 +37,10 @@ const ActiveMode = () => { describe('Background', () => { it('switches the default colormode when contrast standards are not met', () => { - const { view } = renderView({ bg: 'navy' }); + const { view } = renderView({ bg: 'palette-neutral-dark-1000' }); expect(view.getByTestId('content').parentElement).toHaveStyleRule( '--color-background-current', - theme.colors.navy + theme.colors['palette-neutral-dark-1000'] ); expect(view.getByTestId('content').parentElement).toHaveStyleRule( @@ -58,10 +51,10 @@ describe('Background', () => { it('allows for changing the color mode while nested', () => { const { view } = renderView({ - bg: 'navy', + bg: 'palette-neutral-dark-1000', children: (
- +
@@ -69,18 +62,18 @@ describe('Background', () => { }); expect(view.getByTestId('content').parentElement).toHaveStyleRule( '--color-background-current', - theme.colors.navy + theme.colors['palette-neutral-dark-1000'] ); expect(view.getByTestId('nested-content').parentElement).toHaveStyleRule( '--color-background-current', - theme.colors.beige + theme.colors['palette-neutral-light-200'] ); }); it('updates the theme context to the current mode', () => { const { view } = renderView({ - bg: 'navy', + bg: 'palette-neutral-dark-1000', children: , }); @@ -97,65 +90,18 @@ describe('Background', () => { }); it('it sets the current background color to the specified bg', () => { - const { view } = renderView({ bg: 'paleBlue' }); + const { view } = renderView({ bg: 'palette-primary-200' }); expect(view.getByTestId('content').parentElement).toHaveStyleRule( '--color-background-current', - theme.colors.paleBlue + theme.colors['palette-primary-200'] ); }); it('it sets the current background color to the specified bg when switch modes', () => { - const { view } = renderView({ bg: 'navy' }); + const { view } = renderView({ bg: 'palette-neutral-dark-1000' }); expect(view.getByTestId('content').parentElement).toHaveStyleRule( '--color-background-current', - theme.colors.navy + theme.colors['palette-neutral-dark-1000'] ); }); }); - -/* - * A semantic color resolves differently in each mode, and Background flips the - * mode on the same element it paints. It has to paint the palette color the - * semantic color has in the outer mode, or `bg="primary"` on core light paints - * dark mode's yellow. - */ -describe('Background with a semantic color bg', () => { - const themes = { - core: theme, - admin: adminTheme, - platform: platformTheme, - percipio: percipioTheme, - lxStudio: lxStudioTheme, - }; - - const cases = Object.entries(themes).flatMap(([name, t]) => - Object.entries(t.modes).flatMap(([mode, semanticColors]) => - Object.entries(semanticColors) - .filter(([, color]) => color in t.colors) - .map(([semanticColor, color]) => ({ - name, - t, - mode, - semanticColor, - color, - })) - ) - ); - - it.each(cases.map((c) => [c.name, c.mode, c.semanticColor, c]))( - '%s %s: bg="%s" paints its outer-mode color', - (_name, _mode, _semanticColor, { t, mode, semanticColor, color }) => { - const { getByTestId } = render( - - -
- - - ); - expect(getByTestId('content').parentElement).toHaveStyleRule( - '--color-background-current', - t.colors[color as keyof typeof t.colors] - ); - } - ); -}); diff --git a/packages/gamut-styles/src/__tests__/GamutProvider.csp.test.tsx b/packages/gamut-styles/src/__tests__/GamutProvider.csp.test.tsx index c017b24ee7..aa87fa4525 100644 --- a/packages/gamut-styles/src/__tests__/GamutProvider.csp.test.tsx +++ b/packages/gamut-styles/src/__tests__/GamutProvider.csp.test.tsx @@ -5,7 +5,7 @@ import { render } from '@testing-library/react'; import { GamutProvider } from '../GamutProvider'; -import { coreTheme } from '../themes'; +import { skillsoftTheme } from '../themes'; const TEST_NONCE = 'test-nonce'; @@ -16,7 +16,7 @@ describe('GamutProvider CSP nonce', () => { it('injects style tags with the nonce attribute when nonce is provided', () => { render( - +
child
); diff --git a/packages/gamut-styles/src/__tests__/GamutProvider.test.tsx b/packages/gamut-styles/src/__tests__/GamutProvider.test.tsx index 8af8ec4908..42ba513627 100644 --- a/packages/gamut-styles/src/__tests__/GamutProvider.test.tsx +++ b/packages/gamut-styles/src/__tests__/GamutProvider.test.tsx @@ -5,7 +5,10 @@ import { setNonce } from 'get-nonce'; import { createEmotionCache } from '../cache'; import { GamutProvider, useNonce } from '../GamutProvider'; -import { coreTheme as theme } from '../themes'; +import { skillsoftTheme, skillsoftTheme as theme } from '../themes'; + +const withoutTokens = ({ tokensCss, ...rest }: typeof theme) => + rest as typeof theme; import { setupRtl } from './testUtils'; jest.mock('../cache', () => { @@ -19,6 +22,18 @@ jest.mock('get-nonce', () => ({ const renderView = setupRtl(GamutProvider, { theme }); +/* Emotion may insert with insertRule, which leaves textContent empty. */ +const emotionCss = () => + Array.from(document.querySelectorAll('style[data-emotion]')) + .map( + (el) => + (el.textContent ?? '') + + Array.from(el.sheet?.cssRules ?? []) + .map((rule) => rule.cssText) + .join('') + ) + .join(''); + describe(GamutProvider, () => { beforeEach(() => { jest.resetAllMocks(); @@ -54,15 +69,23 @@ describe(GamutProvider, () => { }); it('wraps all elements in a ThemeProvider', () => { + // tokensCss is multi-line; check the context value directly instead of matching its text. + const themeWithoutTokens = withoutTokens(theme); + let seen: { name?: string; useLogicalProperties?: boolean } | undefined; renderView({ + theme: themeWithoutTokens, children: ( - {(value) =>
{JSON.stringify(value)}
} + {(value) => { + seen = value as typeof seen; + return null; + }}
), }); - screen.getByText(JSON.stringify({ ...theme, useLogicalProperties: false })); + expect(seen?.name).toBe(themeWithoutTokens.name); + expect(seen?.useLogicalProperties).toBe(false); }); it('it can have another GamutProvider as a child with creating multiple caches or globals', () => { renderView({ @@ -134,4 +157,30 @@ describe(GamutProvider, () => { expect(blue).toBe('blue'); }); + + describe('color tokens', () => { + it('injects tokens.css for a theme on the generated color system', () => { + renderView({ theme: skillsoftTheme }); + + expect(emotionCss()).toContain('--color-palette-primary-800:'); + }); + + it('does not inject tokens when configured not to', () => { + renderView({ theme: skillsoftTheme, useTokens: false }); + + expect(emotionCss()).not.toContain('--color-palette-primary-800:'); + }); + + it('does not inject tokens without globals', () => { + renderView({ theme: skillsoftTheme, useGlobals: false }); + + expect(emotionCss()).not.toContain('--color-palette-primary-800:'); + }); + + it('does not inject tokens for themes that do not carry them', () => { + renderView({ theme: withoutTokens(theme) }); + + expect(emotionCss()).not.toContain('--color-palette-primary-800:'); + }); + }); }); diff --git a/packages/gamut-styles/src/__tests__/fontLoading.test.tsx b/packages/gamut-styles/src/__tests__/fontLoading.test.tsx index b96acc16a9..cc009849ec 100644 --- a/packages/gamut-styles/src/__tests__/fontLoading.test.tsx +++ b/packages/gamut-styles/src/__tests__/fontLoading.test.tsx @@ -1,10 +1,10 @@ import { render } from '@testing-library/react'; import { AssetProvider } from '../AssetProvider'; -import { coreTheme, percipioTheme } from '../themes'; +import { lxStudioTheme, skillsoftTheme } from '../themes'; import { getFontsMock } from './fontUtilsMock'; -const typedPercipioTheme = percipioTheme as any; +const typedLxStudioTheme = lxStudioTheme as any; jest.mock('../utilities/fontUtils', () => ({ getFonts: require('./fontUtilsMock').getFontsMock, @@ -93,7 +93,7 @@ describe('Font Loading and Error Handling', () => { }, ]); - const { container } = render(); + const { container } = render(); const links = getPreloadLinks(container); expect(links).toHaveLength(1); @@ -119,7 +119,7 @@ describe('Font Loading and Error Handling', () => { }, ]); - const { container } = render(); + const { container } = render(); const links = getPreloadLinks(container); expect(links).toHaveLength(2); @@ -140,7 +140,7 @@ describe('Font Loading and Error Handling', () => { throw new Error('Font loading failed'); }); - const { container } = render(); + const { container } = render(); const links = getPreloadLinks(container); expect(links).toHaveLength(0); @@ -166,7 +166,7 @@ describe('Font Loading and Error Handling', () => { }, ]); - const { container } = render(); + const { container } = render(); const links = getPreloadLinks(container); expect(links).toHaveLength(2); @@ -188,7 +188,7 @@ describe('Font Loading and Error Handling', () => { }, ]); - const { container } = render(); + const { container } = render(); const links = getPreloadLinks(container); expect(links).toHaveLength(2); @@ -219,7 +219,7 @@ describe('Font Loading and Error Handling', () => { }, ]); - const { container } = render(); + const { container } = render(); const links = getPreloadLinks(container); expect(links).toHaveLength(1); @@ -242,7 +242,7 @@ describe('Font Loading and Error Handling', () => { }, ]); - const { container } = render(); + const { container } = render(); const links = getPreloadLinks(container); expect(links).toHaveLength(1); @@ -262,12 +262,12 @@ describe('Font Loading and Error Handling', () => { ]); for (let i = 0; i < 10; i += 1) { - const { unmount } = render(); + const { unmount } = render(); unmount(); } expect(() => { - render(); + render(); }).not.toThrow(); }); @@ -280,15 +280,15 @@ describe('Font Loading and Error Handling', () => { }, ]); - const { rerender } = render(); + const { rerender } = render(); for (let i = 0; i < 5; i += 1) { - rerender(); - rerender(); + rerender(); + rerender(); } expect(() => { - rerender(); + rerender(); }).not.toThrow(); }); }); diff --git a/packages/gamut-styles/src/__tests__/stylingContract.test.tsx b/packages/gamut-styles/src/__tests__/stylingContract.test.tsx new file mode 100644 index 0000000000..0f4516f640 --- /dev/null +++ b/packages/gamut-styles/src/__tests__/stylingContract.test.tsx @@ -0,0 +1,313 @@ +import { matchers } from '@emotion/jest'; +import type { Theme } from '@emotion/react'; +import { render } from '@testing-library/react'; +import { parse } from 'culori'; + +import { Background } from '../Background'; +import { + ColorMode, + modeColorProps, + useColorModes, + useCurrentMode, +} from '../ColorMode'; +import { GamutProvider } from '../GamutProvider'; +import { + adminTheme, + lxStudioTheme, + originalCodecademyTheme, + platformTheme, + skillsoftTheme, +} from '../themes'; +import { themed } from '../utilities'; +import { css, states, system, variant } from '../variance'; + +expect.extend(matchers); + +/* + * The styling-function contract: every css-in-js function that takes a color + * keeps its API and behavior on every theme, in both modes. Each theme is + * tested with its own color names; what has to hold is that the CSS a + * function writes resolves, through every var(), to a real color. + * + * Values come from where the browser gets them: the theme's injected root and + * mode variables, or tokens.css for themes that ship static values. + */ + +const THEMES = { + core: skillsoftTheme, + admin: adminTheme, + platform: platformTheme, + lxStudio: lxStudioTheme, + skillsoft: skillsoftTheme, + originalCodecademy: originalCodecademyTheme, +} as unknown as Record; + +type Mode = 'light' | 'dark'; +const MODES: Mode[] = ['light', 'dark']; +type Vars = Record; + +/* + * Custom properties a theme's injected tokens.css declares for a mode, in + * cascade order: the brand's palette, then the mode's roles (the brand's + * overrides are already in them). Innermost blocks only, so the @layer + * wrapper is skipped. It's the theme's own string, so platform's editor + * colors are in it too. + */ +const tokensCssVars = (mode: Mode, tokensCss: string): Vars => { + const vars: Vars = {}; + for (const [, selector, body] of tokensCss + .replace(/\/\*[\s\S]*?\*\//g, '') + .matchAll(/([^{};]+)\{([^{}]*)\}/g)) { + const applies = + !selector.includes('data-color-mode') || selector.includes(`"${mode}"`); + if (!applies) continue; + for (const [, name, value] of body.matchAll(/(--[\w-]+)\s*:\s*([^;]+);/g)) { + vars[name] = value.trim(); + } + } + return vars; +}; + +const hasStaticValues = (theme: Theme) => + !Object.keys(theme._variables.root).some((k) => k.startsWith('--color-')); + +/* The variables in scope inside a `mode` subtree of `theme`. */ +const varsFor = (theme: Theme, mode: Mode): Vars => + hasStaticValues(theme) + ? tokensCssVars(mode, (theme as unknown as { tokensCss: string }).tokensCss) + : ({ + ...theme._variables.root, + ...modeColorProps({ + theme: { ...theme, mode: undefined } as never, + mode, + }), + } as Vars); + +/* Follows var() references to a value; undefined if any link is missing. */ +const resolveVars = ( + value: string, + vars: Vars, + depth = 0 +): string | undefined => { + if (depth > 20) return undefined; + const match = /var\((--[\w-]+)\s*(?:,\s*([^)]*))?\)/.exec(value); + if (!match) return value; + const [whole, name, fallback] = match; + const next = vars[name] ?? fallback?.trim(); + if (next === undefined) return undefined; + return resolveVars(value.replace(whole, next), vars, depth + 1); +}; + +const isColor = (value: unknown, vars: Vars) => { + if (typeof value !== 'string') return false; + const resolved = resolveVars(value, vars); + return resolved !== undefined && parse(resolved) !== undefined; +}; + +/* Every color name a theme's modes define, with a palette color for each. */ +const namesOf = (theme: Theme) => + Object.keys(theme.modes.light).filter( + (name) => name !== 'background-current' + ); + +/* Runs `write` for every name and returns the ones whose CSS doesn't resolve. */ +const failures = ( + theme: Theme, + mode: Mode, + write: (name: string) => unknown[] +) => { + const vars = varsFor(theme, mode); + return namesOf(theme).filter((name) => + write(name).some((value) => !isColor(value, vars)) + ); +}; + +const CASES = Object.entries(THEMES).flatMap(([name, theme]) => + MODES.map((mode) => [name, mode, theme] as const) +); + +describe.each(CASES)('%s, %s', (_, mode, theme) => { + const t = { ...theme, mode } as Theme; + + it('has a color for every name it defines', () => { + expect(failures(t, mode, (name) => [t.colors[name as never]])).toEqual([]); + }); + + it('css()', () => { + expect( + failures(t, mode, (name) => { + const out = css({ color: name, bg: name, borderColor: name } as never)({ + theme: t, + }) as Record; + return [out.color, out.backgroundColor, out.borderColor]; + }) + ).toEqual([]); + }); + + it('variant()', () => { + expect( + failures(t, mode, (name) => { + const out = variant({ + variants: { probe: { color: name, bg: name } }, + } as never)({ theme: t, variant: 'probe' } as never) as Record< + string, + unknown + >; + return [out.color, out.backgroundColor]; + }) + ).toEqual([]); + }); + + it('states()', () => { + expect( + failures(t, mode, (name) => { + const out = states({ probe: { color: name } } as never)({ + theme: t, + probe: true, + } as never) as Record; + return [out.color]; + }) + ).toEqual([]); + }); + + it('system props', () => { + expect( + failures(t, mode, (name) => { + const out = system.color({ + theme: t, + color: name, + bg: name, + } as never) as Record; + return [out.color, out.backgroundColor]; + }) + ).toEqual([]); + }); + + it('themed()', () => { + expect( + failures(t, mode, (name) => [ + themed(`colors.${name}` as never)({ theme: t }), + ]) + ).toEqual([]); + }); + + it('modeColorProps() sets every name for a mode switch', () => { + const other: Mode = mode === 'light' ? 'dark' : 'light'; + const out = modeColorProps({ theme: t, mode: other }) as Vars; + const vars = { ...varsFor(t, other), ...out }; + const missing = namesOf(t).filter( + (name) => + !isColor(out[`--color-${name}`] ?? vars[`--color-${name}`], vars) + ); + expect(missing).toEqual([]); + }); + + it('_getColorValue() gives sRGB for every palette color (polished reads it)', () => { + const palettes = [...new Set(Object.values(t.modes[mode]))] as string[]; + const bad = palettes.filter((key) => { + const value = t._getColorValue(key as never); + return typeof value !== 'string' || !/^(#|rgba?\()/.test(value); + }); + expect(bad).toEqual([]); + }); + + it('ColorMode, useCurrentMode() and useColorModes()', () => { + const seen: unknown[] = []; + const Probe = () => { + const [active, colors, modes] = useColorModes(); + seen.push( + useCurrentMode(), + active, + Object.keys(colors).length, + Object.keys(modes) + ); + return null; + }; + const { container } = render( + + + + + + ); + expect(seen).toEqual([ + mode, + mode, + namesOf(t).length + 1, + ['light', 'dark'], + ]); + expect(container.firstChild).toHaveStyleRule( + 'color', + resolvesToColor(varsFor(t, mode)) + ); + }); + + /* + * Given a name, Background paints the palette color that name has in the + * outer mode. The palette color is the same in every mode, so flipping the + * content mode can't change the background under it. + */ + it('Background paints the outer mode’s color for every name', () => { + const vars = varsFor(t, mode); + const wrong = namesOf(t).filter((bg) => { + const { container, unmount } = render( + + + + + + ); + const el = container.firstChild?.firstChild as HTMLElement; + const expected = t.colors[t.modes[mode][bg as never]]; + let ok = true; + try { + expect(el).toHaveStyleRule('--color-background-current', expected); + expect(el).toHaveStyleRule('background-color', resolvesToColor(vars)); + } catch { + ok = false; + } + unmount(); + return !ok; + }); + expect(wrong).toEqual([]); + }); + + it('ColorMode switches by attribute on generated themes, by variables on the rest', () => { + /* Away from the provider's active mode, the theme's initial one. */ + const other: Mode = theme.mode === 'light' ? 'dark' : 'light'; + const { container } = render( + + + + ); + const el = container.firstChild as HTMLElement; + const someName = namesOf(t)[0]; + if (t.contentModes) { + expect(el.getAttribute('data-color-mode')).toBe(other); + expect(el.getAttribute('data-brand')).toBe(t.brand); + expect(el).not.toHaveStyleRule(`--color-${someName}`, expect.anything()); + } else { + expect(el.getAttribute('data-color-mode')).toBeNull(); + expect(el).toHaveStyleRule(`--color-${someName}`, expect.anything()); + } + }); +}); + +it('resolves each brand’s roles against its own palette', () => { + const primary = (theme: Theme) => + resolveVars('var(--color-action-primary)', varsFor(theme, 'light')); + /* Same lightness and grade, hyper's hue (274.1°) in place of Sapphire's. */ + expect(primary(THEMES.originalCodecademy)).toBe( + 'oklch(0.4115 0.2344 274.10)' + ); + expect(primary(THEMES.skillsoft)).toBe('oklch(0.4115 0.1767 262.40)'); +}); + +/* An asymmetric matcher, so toHaveStyleRule can check a value resolves. */ +function resolvesToColor(vars: Vars) { + return { + $$typeof: Symbol.for('jest.asymmetricMatcher'), + asymmetricMatch: (value: unknown) => isColor(value, vars), + toAsymmetricMatcher: () => 'a value that resolves to a color', + }; +} diff --git a/packages/gamut-styles/src/__tests__/testUtils.tsx b/packages/gamut-styles/src/__tests__/testUtils.tsx index 318e136f44..e2c8a903c7 100644 --- a/packages/gamut-styles/src/__tests__/testUtils.tsx +++ b/packages/gamut-styles/src/__tests__/testUtils.tsx @@ -3,7 +3,7 @@ import overArgs from 'lodash/overArgs'; import type * as React from 'react'; import { GamutProvider } from '../GamutProvider'; -import { coreTheme as theme } from '../themes'; +import { skillsoftTheme as theme } from '../themes'; /** * This is effectively a duplicate of the `MockGamutProvider` in `gamut-tests`. diff --git a/packages/gamut-styles/src/colorSystem/README.md b/packages/gamut-styles/src/colorSystem/README.md new file mode 100644 index 0000000000..37f1cf658c --- /dev/null +++ b/packages/gamut-styles/src/colorSystem/README.md @@ -0,0 +1,77 @@ +# Color system generator + +Build tooling for gamut-styles' color tokens. It takes each brand's hues and +writes the OKLCH palette and roles that the checked-in files under +`src/tokens/generated/` contain. + +Nothing under `src/colorSystem/` runs in the browser. The themes import the +generated output, and only the build scripts import this folder. For what the +output contains and how to use it, see the gamut-styles README. + +## The pipeline + +``` +brands/skillsoft.ts one hue per slot (or just `primary`) + → brand.ts fills missing slots, reports the white-label tier + → ramp.ts 11 grades per slot: fixed L, chroma as % of gamut + → roles.ts 43 roles, light + dark, named by slot not hue + → system.ts resolved system + → contentModes.ts which mode's content goes on each color (build time) + → validate.ts contrast guarantees, veil floors, status trio + → migration.ts old → new color names and component slots, checked per brand + → emit.ts DTCG-shaped JSON and TS constants + → scripts/build-tokens.ts writes everything into src/tokens/generated +``` + +- `build-tokens` refuses to emit if a guarantee or a migration check fails. +- Style Dictionary renders `tokens.css`. The script also writes the three + `*.tokens.json` files, `tokensCss.ts` and `values.ts`. +- `probe.ts` (tenant probes) is report-only. `validate:tokens` prints the + results and doesn't assert them. + +## Commands + +```sh +yarn workspace @skillsoft/gamut-styles build:tokens # regenerate src/tokens/generated +yarn workspace @skillsoft/gamut-styles validate:tokens # parity with design's reference fixture and CSS round-trip; prints tenant probes +npx nx test gamut-styles # guarantees; values.ts and tokens.css are current +``` + +Re-run `build:tokens` after any change to a brand input or to the generator, +and commit the output with the change. + +## What CI checks + +- `nx test` runs in CI. It checks the guarantees, and that `values.ts` and + `tokens.css` match the generator. +- It does not check the JSON token files. That's a known gap, listed below. +- `validate:tokens` is not run in CI. It's the full report, run by hand. + +## Known gaps + +- The JSON token files are not covered by the drift test; only `values.ts` and + `tokens.css` are. +- Brand input isn't validated at the boundary. A non-finite hue is accepted and + produces a gray ramp, and a brand `name` is written into generated comments + unchecked. +- The generator's type declarations are emitted into the package build, because + `tsconfig.lib.json` doesn't exclude this folder. Nothing in the public exports + imports them. +- Some exported tables are mutable (`DEFAULT_HUES`, `CHROMA_CURVE`, + `FIXED_LIGHTNESS`, `FALLOFF`, `ROLES`). A consumer that changes one changes + later builds in the same process. +- `migration.ts` is a rename table: each alias is a rename, merge, split or + retire. Only rename and merge are mechanical. The codemod keeps its own copy + of the alias table, so the two can drift. +- The categorical solver isn't ported, so the `viz-*` chart colors aren't + re-solved for a new brand. +- The reference fixture measures component slots on rounded hex, and this + generator measures OKLCH floats. The 200-pairing check and its 0.074 ratio + spread come from that comparison and aren't reproduced by a test in this + package. + +## Naming + +Output names are defined in the gamut-styles README. The generator's own +naming rule: a token's name comes from its slot or role, not from its hue, so +changing a brand's hue doesn't rename anything. diff --git a/packages/gamut-styles/src/globals/Reboot.tsx b/packages/gamut-styles/src/globals/Reboot.tsx index 511d19d8c7..a6c107654b 100644 --- a/packages/gamut-styles/src/globals/Reboot.tsx +++ b/packages/gamut-styles/src/globals/Reboot.tsx @@ -2,8 +2,11 @@ import type { Theme } from '@emotion/react'; import { css, Global } from '@emotion/react'; import type * as React from 'react'; +import { roleOr } from '../ColorMode'; + export const Reboot: React.FC<{ theme: Theme }> = ({ theme }) => { const { useLogicalProperties } = theme; + const colors = theme.colors as Record; const rebootStyles = css` /** @@ -65,9 +68,9 @@ export const Reboot: React.FC<{ theme: Theme }> = ({ theme }) => { font-family: ${theme.fontFamily.base}; font-weight: ${theme.fontWeight.base}; line-height: ${theme.lineHeight.base}; - color: ${theme.colors.text}; + color: ${roleOr(colors, 'text-default', 'text')}; text-align: ${useLogicalProperties ? 'start' : 'left'}; - background-color: ${theme.colors.background}; + background-color: ${roleOr(colors, 'surface-default', 'background')}; } /* Suppress the focus outline on elements that cannot be accessed via keyboard. @@ -215,7 +218,7 @@ export const Reboot: React.FC<{ theme: Theme }> = ({ theme }) => { */ a { - color: ${theme.colors.primary}; + color: ${roleOr(colors, 'action-primary', 'primary')}; text-decoration: none; background-color: transparent; diff --git a/packages/gamut-styles/src/globals/Typography.tsx b/packages/gamut-styles/src/globals/Typography.tsx index f77f7fe9d4..ea408621df 100644 --- a/packages/gamut-styles/src/globals/Typography.tsx +++ b/packages/gamut-styles/src/globals/Typography.tsx @@ -1,7 +1,7 @@ +import type { Theme } from '@emotion/react'; import { css, Global } from '@emotion/react'; import type * as React from 'react'; -import type { coreTheme } from '../themes'; import type { FontConfig } from '../utilities/fontUtils'; import { getFonts } from '../utilities/fontUtils'; @@ -23,9 +23,7 @@ import { getFonts } from '../utilities/fontUtils'; * ``` */ -export const Typography: React.FC<{ theme: typeof coreTheme }> = ({ - theme, -}) => { +export const Typography: React.FC<{ theme: Theme }> = ({ theme }) => { try { const fonts = getFonts(theme?.name); diff --git a/packages/gamut-styles/src/index.ts b/packages/gamut-styles/src/index.ts index f084a13cd9..b704f84fb9 100644 --- a/packages/gamut-styles/src/index.ts +++ b/packages/gamut-styles/src/index.ts @@ -11,4 +11,4 @@ export * from './styles'; export * from './globals'; export * from './utilities'; export * from './themes'; -export { coreTheme as theme } from './themes'; +export { skillsoftTheme as theme } from './themes'; diff --git a/packages/gamut-styles/src/remoteAssets/fonts.ts b/packages/gamut-styles/src/remoteAssets/fonts.ts index 765c333b87..01eddeda57 100644 --- a/packages/gamut-styles/src/remoteAssets/fonts.ts +++ b/packages/gamut-styles/src/remoteAssets/fonts.ts @@ -185,4 +185,10 @@ export const percipio: readonly FontConfig[] = [ ...percipioRobotoFonts, ]; -export const webFonts = { core, percipio, lxStudio: percipio } as const; +export const webFonts = { + core, + percipio, + lxStudio: percipio, + skillsoft: percipio, + originalCodecademy: core, +} as const; diff --git a/packages/gamut-styles/src/themes/__tests__/__snapshots__/theme.test.ts.snap b/packages/gamut-styles/src/themes/__tests__/__snapshots__/theme.test.ts.snap index c5d087b884..dc120561de 100644 --- a/packages/gamut-styles/src/themes/__tests__/__snapshots__/theme.test.ts.snap +++ b/packages/gamut-styles/src/themes/__tests__/__snapshots__/theme.test.ts.snap @@ -1,78 +1,108 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing -exports[`themes admin - theme shape 1`] = ` +exports[`themes skillsoft - theme shape 1`] = ` { "_getColorValue": [Function], "_tokens": { "colors": { - "beige": "#FFF0E5", - "beige-100": "#FFF0E5", "black": "#000000", - "blue": "#1557FF", - "blue-0": "#F5FCFF", - "blue-100": "#D3F2FF", - "blue-300": "#66C4FF", - "blue-400": "#3388FF", - "blue-500": "#1557FF", - "blue-800": "#1D2340", - "gray-100": "#F5F5F5", - "gray-200": "#EEEEEE", - "gray-300": "#E0E0E0", - "gray-600": "#9E9E9E", - "gray-800": "#616161", - "gray-900": "#424242", - "green": "#008A27", - "green-0": "#F5FFE3", - "green-100": "#EAFDC6", - "green-400": "#AEE938", - "green-700": "#008A27", - "green-900": "#151C07", - "hyper": "#3A10E5", - "hyper-400": "#5533FF", - "hyper-500": "#3A10E5", - "lightBlue": "#66C4FF", - "lightGreen": "#AEE938", - "navy": "#10162F", - "navy-100": "rgba(16,22,47,0.04)", - "navy-200": "rgba(16,22,47,0.12)", - "navy-300": "rgba(16,22,47,0.28)", - "navy-400": "rgba(16,22,47,0.47)", - "navy-500": "rgba(16,22,47,0.63)", - "navy-600": "rgba(16,22,47,0.75)", - "navy-700": "rgba(16,22,47,0.86)", - "navy-800": "#10162F", - "navy-900": "#0A0D1C", - "orange": "#FF8C00", - "orange-100": "#FFE8CC", - "orange-500": "#FF8C00", - "paleBlue": "#F5FCFF", - "paleGreen": "#F5FFE3", - "palePink": "#FFF5FF", - "paleRed": "#DC5879", - "paleYellow": "#FFFAE5", - "pink": "#F966FF", - "pink-0": "#FFF5FF", - "pink-400": "#F966FF", - "red": "#E91C11", - "red-0": "#FBF1F0", - "red-300": "#E85D7F", - "red-400": "#DC5879", - "red-500": "#E91C11", - "red-600": "#BE1809", - "red-900": "#280503", + "black-a24": "rgba(0, 0, 0, 0.24)", + "black-a32": "rgba(0, 0, 0, 0.32)", + "black-a60": "rgba(0, 0, 0, 0.6)", + "palette-accent-100": "#fff9fa", + "palette-accent-1000": "#3f0922", + "palette-accent-1100": "#290515", + "palette-accent-200": "#f2e6ea", + "palette-accent-300": "#eac1ce", + "palette-accent-400": "#ea94b2", + "palette-accent-500": "#f44b97", + "palette-accent-600": "#dd007c", + "palette-accent-700": "#af1363", + "palette-accent-800": "#85154b", + "palette-accent-900": "#620f37", + "palette-accent-alt-100": "#fafaff", + "palette-accent-alt-1000": "#220d57", + "palette-accent-alt-1100": "#150739", + "palette-accent-alt-200": "#e9e9f2", + "palette-accent-alt-300": "#cbcae9", + "palette-accent-alt-400": "#aeaae9", + "palette-accent-alt-500": "#9182f3", + "palette-accent-alt-600": "#7a52ff", + "palette-accent-alt-700": "#661af0", + "palette-accent-alt-800": "#4d1cb5", + "palette-accent-alt-900": "#381587", + "palette-danger-100": "#fff9f9", + "palette-danger-1000": "#410912", + "palette-danger-1100": "#2a050a", + "palette-danger-200": "#f2e7e7", + "palette-danger-300": "#eac2c3", + "palette-danger-400": "#ea989b", + "palette-danger-500": "#f45769", + "palette-danger-600": "#e40045", + "palette-danger-700": "#b51439", + "palette-danger-800": "#89162c", + "palette-danger-900": "#66101f", + "palette-neutral-dark-100": "#fafafb", + "palette-neutral-dark-1000": "#1d212f", + "palette-neutral-dark-1100": "#11141d", + "palette-neutral-dark-1100-a12": "rgba(17, 20, 29, 0.12)", + "palette-neutral-dark-1100-a16": "rgba(17, 20, 29, 0.16)", + "palette-neutral-dark-200": "#e9eaeb", + "palette-neutral-dark-300": "#cbcdd3", + "palette-neutral-dark-400": "#aeb2bd", + "palette-neutral-dark-500": "#8e95a9", + "palette-neutral-dark-600": "#717a97", + "palette-neutral-dark-700": "#576182", + "palette-neutral-dark-800": "#414a65", + "palette-neutral-dark-900": "#2f364b", + "palette-neutral-light-100": "#fbfaf7", + "palette-neutral-light-1000": "#23221d", + "palette-neutral-light-1100": "#151411", + "palette-neutral-light-200": "#ebeae4", + "palette-neutral-light-300": "#d2cec0", + "palette-neutral-light-400": "#b6b2a3", + "palette-neutral-light-500": "#9a9585", + "palette-neutral-light-600": "#807b6c", + "palette-neutral-light-700": "#666256", + "palette-neutral-light-800": "#4d4b42", + "palette-neutral-light-900": "#383730", + "palette-primary-100": "#f8fbff", + "palette-primary-1000": "#051c50", + "palette-primary-1100": "#031135", + "palette-primary-200": "#e6eaf2", + "palette-primary-300": "#c0cee9", + "palette-primary-400": "#95b2e8", + "palette-primary-500": "#6192f3", + "palette-primary-600": "#276eff", + "palette-primary-600-a6": "rgba(39, 110, 255, 0.06)", + "palette-primary-600-a8": "rgba(39, 110, 255, 0.08)", + "palette-primary-600-a9": "rgba(39, 110, 255, 0.09)", + "palette-primary-700": "#0a51df", + "palette-primary-800": "#0c3fa8", + "palette-primary-900": "#082e7d", + "palette-success-100": "#eefeff", + "palette-success-1000": "#082728", + "palette-success-1100": "#041819", + "palette-success-200": "#d2f1f2", + "palette-success-300": "#8ddee2", + "palette-success-400": "#64c4c9", + "palette-success-500": "#34a8ae", + "palette-success-600": "#018d92", + "palette-success-700": "#117074", + "palette-success-800": "#135558", + "palette-success-900": "#0e3e41", + "palette-warning-100": "#fffaef", + "palette-warning-1000": "#2a2006", + "palette-warning-1100": "#1a1304", + "palette-warning-200": "#f2e9d4", + "palette-warning-300": "#e8cb84", + "palette-warning-400": "#cfae5b", + "palette-warning-500": "#b4902d", + "palette-warning-600": "#997600", + "palette-warning-700": "#795e0e", + "palette-warning-800": "#5c4810", + "palette-warning-900": "#43340b", "white": "#ffffff", - "white-100": "rgba(255,255,255,0.04)", - "white-200": "rgba(255,255,255,0.09)", - "white-300": "rgba(255,255,255,0.2)", - "white-400": "rgba(255,255,255,0.35)", - "white-500": "rgba(255,255,255,0.5)", - "white-600": "rgba(255,255,255,0.65)", - "white-700": "rgba(255,255,255,0.8)", - "yellow": "#FFD300", - "yellow-0": "#FFFAE5", - "yellow-400": "#CCA900", - "yellow-500": "#FFD300", - "yellow-900": "#211B00", }, "elements": { "headerHeight": { @@ -83,179 +113,153 @@ exports[`themes admin - theme shape 1`] = ` }, "modes": { "dark": { - "background": "#10162F", - "background-contrast": "#000000", - "background-current": "#10162F", - "background-disabled": "rgba(255,255,255,0.09)", - "background-error": "#280503", - "background-hover": "rgba(255,255,255,0.09)", - "background-primary": "#0A0D1C", - "background-selected": "rgba(255,255,255,0.04)", - "background-success": "#151C07", - "background-warning": "#211B00", - "border-disabled": "rgba(255,255,255,0.5)", - "border-primary": "#ffffff", - "border-secondary": "rgba(255,255,255,0.65)", - "border-tertiary": "rgba(255,255,255,0.2)", - "danger": "#E85D7F", - "danger-hover": "#DC5879", - "feedback-error": "#E85D7F", - "feedback-success": "#AEE938", - "feedback-warning": "#FFFAE5", - "interface": "#FFD300", - "interface-hover": "#CCA900", - "primary": "#FFD300", - "primary-hover": "#CCA900", - "primary-inverse": "#3A10E5", - "secondary": "#ffffff", - "secondary-hover": "rgba(255,255,255,0.8)", - "shadow-primary": "#ffffff", - "shadow-secondary": "rgba(255,255,255,0.65)", - "text": "#ffffff", - "text-accent": "#FFF0E5", - "text-disabled": "rgba(255,255,255,0.5)", - "text-secondary": "rgba(255,255,255,0.65)", + "action-danger": "#ea989b", + "action-danger-hover": "#eac2c3", + "action-danger-pressed": "#f2e7e7", + "action-disabled": "#414a65", + "action-neutral": "#ebeae4", + "action-neutral-hover": "#fbfaf7", + "action-neutral-pressed": "#ffffff", + "action-primary": "#95b2e8", + "action-primary-hover": "#c0cee9", + "action-primary-pressed": "#e6eaf2", + "background-current": "#11141d", + "border-default": "#807b6c", + "border-strong": "#fbfaf7", + "border-subtle": "#383730", + "shadow-ambient": "rgba(0, 0, 0, 0.24)", + "shadow-key": "rgba(0, 0, 0, 0.32)", + "shadow-scrim": "rgba(0, 0, 0, 0.6)", + "state-hover": "rgba(39, 110, 255, 0.06)", + "state-pressed": "rgba(39, 110, 255, 0.09)", + "state-selected": "rgba(39, 110, 255, 0.08)", + "surface-brand": "#11141d", + "surface-brand-subtle": "#11141d", + "surface-default": "#11141d", + "surface-error": "#410912", + "surface-raised": "#1d212f", + "surface-success": "#082728", + "surface-sunken": "#000000", + "surface-warning": "#2a2006", + "text-default": "#ebeae4", + "text-error": "#eac2c3", + "text-inverse": "#1d212f", + "text-subtle": "#b6b2a3", + "text-success": "#8ddee2", + "text-warning": "#e8cb84", + "viz-1": "#6192f3", + "viz-2": "#f44b97", + "viz-3": "#cfae5b", + "viz-4": "#7a52ff", + "viz-5": "#8ddee2", + "viz-6": "#e40045", + "viz-7": "#8e95a9", + "viz-8": "#807b6c", + "viz-seq-1": "#276eff", + "viz-seq-2": "#6192f3", + "viz-seq-3": "#95b2e8", + "viz-seq-4": "#c0cee9", }, "light": { - "background": "#ffffff", - "background-contrast": "#ffffff", - "background-current": "#ffffff", - "background-disabled": "rgba(16,22,47,0.12)", - "background-error": "#FBF1F0", - "background-hover": "rgba(16,22,47,0.12)", - "background-primary": "#FFF0E5", - "background-selected": "rgba(16,22,47,0.04)", - "background-success": "#F5FFE3", - "background-warning": "#FFFAE5", - "border-disabled": "rgba(16,22,47,0.63)", - "border-primary": "#10162F", - "border-secondary": "rgba(16,22,47,0.75)", - "border-tertiary": "rgba(16,22,47,0.28)", - "danger": "#E91C11", - "danger-hover": "#BE1809", - "feedback-error": "#BE1809", - "feedback-success": "#008A27", - "feedback-warning": "#FFD300", - "interface": "#3A10E5", - "interface-hover": "#5533FF", - "primary": "#1557FF", - "primary-hover": "#3388FF", - "primary-inverse": "#FFD300", - "secondary": "#10162F", - "secondary-hover": "rgba(16,22,47,0.86)", - "shadow-primary": "#10162F", - "shadow-secondary": "rgba(16,22,47,0.75)", - "text": "#10162F", - "text-accent": "#0A0D1C", - "text-disabled": "rgba(16,22,47,0.63)", - "text-secondary": "rgba(16,22,47,0.75)", + "action-danger": "#89162c", + "action-danger-hover": "#66101f", + "action-danger-pressed": "#410912", + "action-disabled": "#b6b2a3", + "action-neutral": "#1d212f", + "action-neutral-hover": "#11141d", + "action-neutral-pressed": "#000000", + "action-primary": "#0c3fa8", + "action-primary-hover": "#082e7d", + "action-primary-pressed": "#051c50", + "background-current": "#fbfaf7", + "border-default": "#717a97", + "border-strong": "#11141d", + "border-subtle": "#cbcdd3", + "shadow-ambient": "rgba(17, 20, 29, 0.12)", + "shadow-key": "rgba(17, 20, 29, 0.16)", + "shadow-scrim": "rgba(0, 0, 0, 0.6)", + "state-hover": "rgba(39, 110, 255, 0.06)", + "state-pressed": "rgba(39, 110, 255, 0.09)", + "state-selected": "rgba(39, 110, 255, 0.08)", + "surface-brand": "#fbfaf7", + "surface-brand-subtle": "#fbfaf7", + "surface-default": "#fbfaf7", + "surface-error": "#f2e7e7", + "surface-raised": "#ffffff", + "surface-success": "#d2f1f2", + "surface-sunken": "#ebeae4", + "surface-warning": "#f2e9d4", + "text-default": "#1d212f", + "text-error": "#66101f", + "text-inverse": "#ebeae4", + "text-subtle": "#414a65", + "text-success": "#0e3e41", + "text-warning": "#43340b", + "viz-1": "#0c3fa8", + "viz-2": "#af1363", + "viz-3": "#795e0e", + "viz-4": "#7a52ff", + "viz-5": "#018d92", + "viz-6": "#e40045", + "viz-7": "#1d212f", + "viz-8": "#807b6c", + "viz-seq-1": "#276eff", + "viz-seq-2": "#0a51df", + "viz-seq-3": "#0c3fa8", + "viz-seq-4": "#082e7d", }, }, }, "_variables": { "mode": { - "--color-background": "var(--color-white)", - "--color-background-contrast": "var(--color-white)", - "--color-background-current": "var(--color-white)", - "--color-background-disabled": "var(--color-navy-200)", - "--color-background-error": "var(--color-red-0)", - "--color-background-hover": "var(--color-navy-200)", - "--color-background-primary": "var(--color-beige)", - "--color-background-selected": "var(--color-navy-100)", - "--color-background-success": "var(--color-green-0)", - "--color-background-warning": "var(--color-yellow-0)", - "--color-border-disabled": "var(--color-navy-500)", - "--color-border-primary": "var(--color-navy-800)", - "--color-border-secondary": "var(--color-navy-600)", - "--color-border-tertiary": "var(--color-navy-300)", - "--color-danger": "var(--color-red-500)", - "--color-danger-hover": "var(--color-red-600)", - "--color-feedback-error": "var(--color-red-600)", - "--color-feedback-success": "var(--color-green-700)", - "--color-feedback-warning": "var(--color-yellow)", - "--color-interface": "var(--color-hyper-500)", - "--color-interface-hover": "var(--color-hyper-400)", - "--color-primary": "var(--color-blue-500)", - "--color-primary-hover": "var(--color-blue-400)", - "--color-primary-inverse": "var(--color-yellow-500)", - "--color-secondary": "var(--color-navy-800)", - "--color-secondary-hover": "var(--color-navy-700)", - "--color-shadow-primary": "var(--color-navy-800)", - "--color-shadow-secondary": "var(--color-navy-600)", - "--color-text": "var(--color-navy-800)", - "--color-text-accent": "var(--color-navy-900)", - "--color-text-disabled": "var(--color-navy-500)", - "--color-text-secondary": "var(--color-navy-600)", + "--color-action-danger": "var(--color-palette-danger-800)", + "--color-action-danger-hover": "var(--color-palette-danger-900)", + "--color-action-danger-pressed": "var(--color-palette-danger-1000)", + "--color-action-disabled": "var(--color-palette-neutral-light-400)", + "--color-action-neutral": "var(--color-palette-neutral-dark-1000)", + "--color-action-neutral-hover": "var(--color-palette-neutral-dark-1100)", + "--color-action-neutral-pressed": "var(--color-black)", + "--color-action-primary": "var(--color-palette-primary-800)", + "--color-action-primary-hover": "var(--color-palette-primary-900)", + "--color-action-primary-pressed": "var(--color-palette-primary-1000)", + "--color-background-current": "var(--color-palette-neutral-light-100)", + "--color-border-default": "var(--color-palette-neutral-dark-600)", + "--color-border-strong": "var(--color-palette-neutral-dark-1100)", + "--color-border-subtle": "var(--color-palette-neutral-dark-300)", + "--color-shadow-ambient": "var(--color-palette-neutral-dark-1100-a12)", + "--color-shadow-key": "var(--color-palette-neutral-dark-1100-a16)", + "--color-shadow-scrim": "var(--color-black-a60)", + "--color-state-hover": "var(--color-palette-primary-600-a6)", + "--color-state-pressed": "var(--color-palette-primary-600-a9)", + "--color-state-selected": "var(--color-palette-primary-600-a8)", + "--color-surface-brand": "var(--color-palette-neutral-light-100)", + "--color-surface-brand-subtle": "var(--color-palette-neutral-light-100)", + "--color-surface-default": "var(--color-palette-neutral-light-100)", + "--color-surface-error": "var(--color-palette-danger-200)", + "--color-surface-raised": "var(--color-white)", + "--color-surface-success": "var(--color-palette-success-200)", + "--color-surface-sunken": "var(--color-palette-neutral-light-200)", + "--color-surface-warning": "var(--color-palette-warning-200)", + "--color-text-default": "var(--color-palette-neutral-dark-1000)", + "--color-text-error": "var(--color-palette-danger-900)", + "--color-text-inverse": "var(--color-palette-neutral-light-200)", + "--color-text-subtle": "var(--color-palette-neutral-dark-800)", + "--color-text-success": "var(--color-palette-success-900)", + "--color-text-warning": "var(--color-palette-warning-900)", + "--color-viz-1": "var(--color-palette-primary-800)", + "--color-viz-2": "var(--color-palette-accent-700)", + "--color-viz-3": "var(--color-palette-warning-700)", + "--color-viz-4": "var(--color-palette-accent-alt-600)", + "--color-viz-5": "var(--color-palette-success-600)", + "--color-viz-6": "var(--color-palette-danger-600)", + "--color-viz-7": "var(--color-palette-neutral-dark-1000)", + "--color-viz-8": "var(--color-palette-neutral-light-600)", + "--color-viz-seq-1": "var(--color-palette-primary-600)", + "--color-viz-seq-2": "var(--color-palette-primary-700)", + "--color-viz-seq-3": "var(--color-palette-primary-800)", + "--color-viz-seq-4": "var(--color-palette-primary-900)", }, "root": { - "--color-beige": "#FFF0E5", - "--color-beige-100": "#FFF0E5", - "--color-black": "#000000", - "--color-blue": "#1557FF", - "--color-blue-0": "#F5FCFF", - "--color-blue-100": "#D3F2FF", - "--color-blue-300": "#66C4FF", - "--color-blue-400": "#3388FF", - "--color-blue-500": "#1557FF", - "--color-blue-800": "#1D2340", - "--color-gray-100": "#F5F5F5", - "--color-gray-200": "#EEEEEE", - "--color-gray-300": "#E0E0E0", - "--color-gray-600": "#9E9E9E", - "--color-gray-800": "#616161", - "--color-gray-900": "#424242", - "--color-green": "#008A27", - "--color-green-0": "#F5FFE3", - "--color-green-100": "#EAFDC6", - "--color-green-400": "#AEE938", - "--color-green-700": "#008A27", - "--color-green-900": "#151C07", - "--color-hyper": "#3A10E5", - "--color-hyper-400": "#5533FF", - "--color-hyper-500": "#3A10E5", - "--color-lightBlue": "#66C4FF", - "--color-lightGreen": "#AEE938", - "--color-navy": "#10162F", - "--color-navy-100": "rgba(16,22,47,0.04)", - "--color-navy-200": "rgba(16,22,47,0.12)", - "--color-navy-300": "rgba(16,22,47,0.28)", - "--color-navy-400": "rgba(16,22,47,0.47)", - "--color-navy-500": "rgba(16,22,47,0.63)", - "--color-navy-600": "rgba(16,22,47,0.75)", - "--color-navy-700": "rgba(16,22,47,0.86)", - "--color-navy-800": "#10162F", - "--color-navy-900": "#0A0D1C", - "--color-orange": "#FF8C00", - "--color-orange-100": "#FFE8CC", - "--color-orange-500": "#FF8C00", - "--color-paleBlue": "#F5FCFF", - "--color-paleGreen": "#F5FFE3", - "--color-palePink": "#FFF5FF", - "--color-paleRed": "#DC5879", - "--color-paleYellow": "#FFFAE5", - "--color-pink": "#F966FF", - "--color-pink-0": "#FFF5FF", - "--color-pink-400": "#F966FF", - "--color-red": "#E91C11", - "--color-red-0": "#FBF1F0", - "--color-red-300": "#E85D7F", - "--color-red-400": "#DC5879", - "--color-red-500": "#E91C11", - "--color-red-600": "#BE1809", - "--color-red-900": "#280503", - "--color-white": "#ffffff", - "--color-white-100": "rgba(255,255,255,0.04)", - "--color-white-200": "rgba(255,255,255,0.09)", - "--color-white-300": "rgba(255,255,255,0.2)", - "--color-white-400": "rgba(255,255,255,0.35)", - "--color-white-500": "rgba(255,255,255,0.5)", - "--color-white-600": "rgba(255,255,255,0.65)", - "--color-white-700": "rgba(255,255,255,0.8)", - "--color-yellow": "#FFD300", - "--color-yellow-0": "#FFFAE5", - "--color-yellow-400": "#CCA900", - "--color-yellow-500": "#FFD300", - "--color-yellow-900": "#211B00", "--elements-headerHeight": "4rem", "--elements-headerZ": 15, "@media only screen and (min-width: 1024px)": { @@ -272,9 +276,10 @@ exports[`themes admin - theme shape 1`] = ` "xl": "16px", }, "borders": { - "1": "1px solid var(--color-border-primary)", - "2": "2px solid var(--color-border-primary)", + "1": "1px solid var(--color-border-strong)", + "2": "2px solid var(--color-border-strong)", }, + "brand": "skillsoft", "breakpoints": { "c_base": "@container (min-width: 1px)", "c_lg": "@container (min-width: 1200px)", @@ -289,1748 +294,253 @@ exports[`themes admin - theme shape 1`] = ` "xs": "@media only screen and (min-width: 480px)", }, "colors": { - "background": "var(--color-background)", - "background-contrast": "var(--color-background-contrast)", + "action-danger": "var(--color-action-danger)", + "action-danger-hover": "var(--color-action-danger-hover)", + "action-danger-pressed": "var(--color-action-danger-pressed)", + "action-disabled": "var(--color-action-disabled)", + "action-neutral": "var(--color-action-neutral)", + "action-neutral-hover": "var(--color-action-neutral-hover)", + "action-neutral-pressed": "var(--color-action-neutral-pressed)", + "action-primary": "var(--color-action-primary)", + "action-primary-hover": "var(--color-action-primary-hover)", + "action-primary-pressed": "var(--color-action-primary-pressed)", "background-current": "var(--color-background-current)", - "background-disabled": "var(--color-background-disabled)", - "background-error": "var(--color-background-error)", - "background-hover": "var(--color-background-hover)", - "background-primary": "var(--color-background-primary)", - "background-selected": "var(--color-background-selected)", - "background-success": "var(--color-background-success)", - "background-warning": "var(--color-background-warning)", - "beige": "var(--color-beige)", - "beige-100": "var(--color-beige-100)", "black": "var(--color-black)", - "blue": "var(--color-blue)", - "blue-0": "var(--color-blue-0)", - "blue-100": "var(--color-blue-100)", - "blue-300": "var(--color-blue-300)", - "blue-400": "var(--color-blue-400)", - "blue-500": "var(--color-blue-500)", - "blue-800": "var(--color-blue-800)", - "border-disabled": "var(--color-border-disabled)", - "border-primary": "var(--color-border-primary)", - "border-secondary": "var(--color-border-secondary)", - "border-tertiary": "var(--color-border-tertiary)", - "danger": "var(--color-danger)", - "danger-hover": "var(--color-danger-hover)", - "feedback-error": "var(--color-feedback-error)", - "feedback-success": "var(--color-feedback-success)", - "feedback-warning": "var(--color-feedback-warning)", - "gray-100": "var(--color-gray-100)", - "gray-200": "var(--color-gray-200)", - "gray-300": "var(--color-gray-300)", - "gray-600": "var(--color-gray-600)", - "gray-800": "var(--color-gray-800)", - "gray-900": "var(--color-gray-900)", - "green": "var(--color-green)", - "green-0": "var(--color-green-0)", - "green-100": "var(--color-green-100)", - "green-400": "var(--color-green-400)", - "green-700": "var(--color-green-700)", - "green-900": "var(--color-green-900)", - "hyper": "var(--color-hyper)", - "hyper-400": "var(--color-hyper-400)", - "hyper-500": "var(--color-hyper-500)", - "interface": "var(--color-interface)", - "interface-hover": "var(--color-interface-hover)", - "lightBlue": "var(--color-lightBlue)", - "lightGreen": "var(--color-lightGreen)", - "navy": "var(--color-navy)", - "navy-100": "var(--color-navy-100)", - "navy-200": "var(--color-navy-200)", - "navy-300": "var(--color-navy-300)", - "navy-400": "var(--color-navy-400)", - "navy-500": "var(--color-navy-500)", - "navy-600": "var(--color-navy-600)", - "navy-700": "var(--color-navy-700)", - "navy-800": "var(--color-navy-800)", - "navy-900": "var(--color-navy-900)", - "orange": "var(--color-orange)", - "orange-100": "var(--color-orange-100)", - "orange-500": "var(--color-orange-500)", - "paleBlue": "var(--color-paleBlue)", - "paleGreen": "var(--color-paleGreen)", - "palePink": "var(--color-palePink)", - "paleRed": "var(--color-paleRed)", - "paleYellow": "var(--color-paleYellow)", - "pink": "var(--color-pink)", - "pink-0": "var(--color-pink-0)", - "pink-400": "var(--color-pink-400)", - "primary": "var(--color-primary)", - "primary-hover": "var(--color-primary-hover)", - "primary-inverse": "var(--color-primary-inverse)", - "red": "var(--color-red)", - "red-0": "var(--color-red-0)", - "red-300": "var(--color-red-300)", - "red-400": "var(--color-red-400)", - "red-500": "var(--color-red-500)", - "red-600": "var(--color-red-600)", - "red-900": "var(--color-red-900)", - "secondary": "var(--color-secondary)", - "secondary-hover": "var(--color-secondary-hover)", - "shadow-primary": "var(--color-shadow-primary)", - "shadow-secondary": "var(--color-shadow-secondary)", - "text": "var(--color-text)", - "text-accent": "var(--color-text-accent)", - "text-disabled": "var(--color-text-disabled)", - "text-secondary": "var(--color-text-secondary)", + "black-a24": "var(--color-black-a24)", + "black-a32": "var(--color-black-a32)", + "black-a60": "var(--color-black-a60)", + "border-default": "var(--color-border-default)", + "border-strong": "var(--color-border-strong)", + "border-subtle": "var(--color-border-subtle)", + "palette-accent-100": "var(--color-palette-accent-100)", + "palette-accent-1000": "var(--color-palette-accent-1000)", + "palette-accent-1100": "var(--color-palette-accent-1100)", + "palette-accent-200": "var(--color-palette-accent-200)", + "palette-accent-300": "var(--color-palette-accent-300)", + "palette-accent-400": "var(--color-palette-accent-400)", + "palette-accent-500": "var(--color-palette-accent-500)", + "palette-accent-600": "var(--color-palette-accent-600)", + "palette-accent-700": "var(--color-palette-accent-700)", + "palette-accent-800": "var(--color-palette-accent-800)", + "palette-accent-900": "var(--color-palette-accent-900)", + "palette-accent-alt-100": "var(--color-palette-accent-alt-100)", + "palette-accent-alt-1000": "var(--color-palette-accent-alt-1000)", + "palette-accent-alt-1100": "var(--color-palette-accent-alt-1100)", + "palette-accent-alt-200": "var(--color-palette-accent-alt-200)", + "palette-accent-alt-300": "var(--color-palette-accent-alt-300)", + "palette-accent-alt-400": "var(--color-palette-accent-alt-400)", + "palette-accent-alt-500": "var(--color-palette-accent-alt-500)", + "palette-accent-alt-600": "var(--color-palette-accent-alt-600)", + "palette-accent-alt-700": "var(--color-palette-accent-alt-700)", + "palette-accent-alt-800": "var(--color-palette-accent-alt-800)", + "palette-accent-alt-900": "var(--color-palette-accent-alt-900)", + "palette-danger-100": "var(--color-palette-danger-100)", + "palette-danger-1000": "var(--color-palette-danger-1000)", + "palette-danger-1100": "var(--color-palette-danger-1100)", + "palette-danger-200": "var(--color-palette-danger-200)", + "palette-danger-300": "var(--color-palette-danger-300)", + "palette-danger-400": "var(--color-palette-danger-400)", + "palette-danger-500": "var(--color-palette-danger-500)", + "palette-danger-600": "var(--color-palette-danger-600)", + "palette-danger-700": "var(--color-palette-danger-700)", + "palette-danger-800": "var(--color-palette-danger-800)", + "palette-danger-900": "var(--color-palette-danger-900)", + "palette-neutral-dark-100": "var(--color-palette-neutral-dark-100)", + "palette-neutral-dark-1000": "var(--color-palette-neutral-dark-1000)", + "palette-neutral-dark-1100": "var(--color-palette-neutral-dark-1100)", + "palette-neutral-dark-1100-a12": "var(--color-palette-neutral-dark-1100-a12)", + "palette-neutral-dark-1100-a16": "var(--color-palette-neutral-dark-1100-a16)", + "palette-neutral-dark-200": "var(--color-palette-neutral-dark-200)", + "palette-neutral-dark-300": "var(--color-palette-neutral-dark-300)", + "palette-neutral-dark-400": "var(--color-palette-neutral-dark-400)", + "palette-neutral-dark-500": "var(--color-palette-neutral-dark-500)", + "palette-neutral-dark-600": "var(--color-palette-neutral-dark-600)", + "palette-neutral-dark-700": "var(--color-palette-neutral-dark-700)", + "palette-neutral-dark-800": "var(--color-palette-neutral-dark-800)", + "palette-neutral-dark-900": "var(--color-palette-neutral-dark-900)", + "palette-neutral-light-100": "var(--color-palette-neutral-light-100)", + "palette-neutral-light-1000": "var(--color-palette-neutral-light-1000)", + "palette-neutral-light-1100": "var(--color-palette-neutral-light-1100)", + "palette-neutral-light-200": "var(--color-palette-neutral-light-200)", + "palette-neutral-light-300": "var(--color-palette-neutral-light-300)", + "palette-neutral-light-400": "var(--color-palette-neutral-light-400)", + "palette-neutral-light-500": "var(--color-palette-neutral-light-500)", + "palette-neutral-light-600": "var(--color-palette-neutral-light-600)", + "palette-neutral-light-700": "var(--color-palette-neutral-light-700)", + "palette-neutral-light-800": "var(--color-palette-neutral-light-800)", + "palette-neutral-light-900": "var(--color-palette-neutral-light-900)", + "palette-primary-100": "var(--color-palette-primary-100)", + "palette-primary-1000": "var(--color-palette-primary-1000)", + "palette-primary-1100": "var(--color-palette-primary-1100)", + "palette-primary-200": "var(--color-palette-primary-200)", + "palette-primary-300": "var(--color-palette-primary-300)", + "palette-primary-400": "var(--color-palette-primary-400)", + "palette-primary-500": "var(--color-palette-primary-500)", + "palette-primary-600": "var(--color-palette-primary-600)", + "palette-primary-600-a6": "var(--color-palette-primary-600-a6)", + "palette-primary-600-a8": "var(--color-palette-primary-600-a8)", + "palette-primary-600-a9": "var(--color-palette-primary-600-a9)", + "palette-primary-700": "var(--color-palette-primary-700)", + "palette-primary-800": "var(--color-palette-primary-800)", + "palette-primary-900": "var(--color-palette-primary-900)", + "palette-success-100": "var(--color-palette-success-100)", + "palette-success-1000": "var(--color-palette-success-1000)", + "palette-success-1100": "var(--color-palette-success-1100)", + "palette-success-200": "var(--color-palette-success-200)", + "palette-success-300": "var(--color-palette-success-300)", + "palette-success-400": "var(--color-palette-success-400)", + "palette-success-500": "var(--color-palette-success-500)", + "palette-success-600": "var(--color-palette-success-600)", + "palette-success-700": "var(--color-palette-success-700)", + "palette-success-800": "var(--color-palette-success-800)", + "palette-success-900": "var(--color-palette-success-900)", + "palette-warning-100": "var(--color-palette-warning-100)", + "palette-warning-1000": "var(--color-palette-warning-1000)", + "palette-warning-1100": "var(--color-palette-warning-1100)", + "palette-warning-200": "var(--color-palette-warning-200)", + "palette-warning-300": "var(--color-palette-warning-300)", + "palette-warning-400": "var(--color-palette-warning-400)", + "palette-warning-500": "var(--color-palette-warning-500)", + "palette-warning-600": "var(--color-palette-warning-600)", + "palette-warning-700": "var(--color-palette-warning-700)", + "palette-warning-800": "var(--color-palette-warning-800)", + "palette-warning-900": "var(--color-palette-warning-900)", + "shadow-ambient": "var(--color-shadow-ambient)", + "shadow-key": "var(--color-shadow-key)", + "shadow-scrim": "var(--color-shadow-scrim)", + "state-hover": "var(--color-state-hover)", + "state-pressed": "var(--color-state-pressed)", + "state-selected": "var(--color-state-selected)", + "surface-brand": "var(--color-surface-brand)", + "surface-brand-subtle": "var(--color-surface-brand-subtle)", + "surface-default": "var(--color-surface-default)", + "surface-error": "var(--color-surface-error)", + "surface-raised": "var(--color-surface-raised)", + "surface-success": "var(--color-surface-success)", + "surface-sunken": "var(--color-surface-sunken)", + "surface-warning": "var(--color-surface-warning)", + "text-default": "var(--color-text-default)", + "text-error": "var(--color-text-error)", + "text-inverse": "var(--color-text-inverse)", + "text-subtle": "var(--color-text-subtle)", + "text-success": "var(--color-text-success)", + "text-warning": "var(--color-text-warning)", + "viz-1": "var(--color-viz-1)", + "viz-2": "var(--color-viz-2)", + "viz-3": "var(--color-viz-3)", + "viz-4": "var(--color-viz-4)", + "viz-5": "var(--color-viz-5)", + "viz-6": "var(--color-viz-6)", + "viz-7": "var(--color-viz-7)", + "viz-8": "var(--color-viz-8)", + "viz-seq-1": "var(--color-viz-seq-1)", + "viz-seq-2": "var(--color-viz-seq-2)", + "viz-seq-3": "var(--color-viz-seq-3)", + "viz-seq-4": "var(--color-viz-seq-4)", "white": "var(--color-white)", - "white-100": "var(--color-white-100)", - "white-200": "var(--color-white-200)", - "white-300": "var(--color-white-300)", - "white-400": "var(--color-white-400)", - "white-500": "var(--color-white-500)", - "white-600": "var(--color-white-600)", - "white-700": "var(--color-white-700)", - "yellow": "var(--color-yellow)", - "yellow-0": "var(--color-yellow-0)", - "yellow-400": "var(--color-yellow-400)", - "yellow-500": "var(--color-yellow-500)", - "yellow-900": "var(--color-yellow-900)", }, - "elements": { - "headerHeight": "var(--elements-headerHeight)", - "headerZ": "var(--elements-headerZ)", - }, - "elevation": { - "hover-shadow": "-8px 8px 0 0 var(--color-shadow-primary)", - "hover-transform": "translate(4px, -4px)", - "hoverMirrored-shadow": "8px 8px 0 0 var(--color-shadow-primary)", - "hoverMirrored-transform": "translate(-4px, -4px)", - "rest-shadow": "0 0 0 0 var(--color-shadow-primary)", - "rest-transform": "none", - }, - "fontFamily": { - "accent": ""Suisse", "Apercu", -apple-system, BlinkMacSystemFont, -"Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", -"Helvetica Neue", sans-serif", - "base": ""Apercu", -apple-system, BlinkMacSystemFont, "Segoe UI", -"Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", -sans-serif", - "monospace": "Monaco, Menlo, "Ubuntu Mono", "Droid Sans Mono", Consolas, -monospace", - "system": "-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Ubuntu", -"Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif", - }, - "fontSize": { - "14": "0.875rem", - "16": "1rem", - "18": "1.125rem", - "20": "1.25rem", - "22": "1.375rem", - "26": "1.625rem", - "34": "2.125rem", - "44": "2.75rem", - "64": "4rem", - }, - "fontWeight": { - "400": 400, - "700": 700, - "base": 400, - "title": 700, - }, - "lineHeight": { - "base": 1.5, - "spacedTitle": 1.3, - "title": 1.2, - }, - "mode": "light", - "modes": { - "dark": { - "background": "navy-800", - "background-contrast": "black", - "background-current": "navy-800", - "background-disabled": "white-200", - "background-error": "red-900", - "background-hover": "white-200", - "background-primary": "navy-900", - "background-selected": "white-100", - "background-success": "green-900", - "background-warning": "yellow-900", - "border-disabled": "white-500", - "border-primary": "white", - "border-secondary": "white-600", - "border-tertiary": "white-300", - "danger": "red-300", - "danger-hover": "red-400", - "feedback-error": "red-300", - "feedback-success": "green-400", - "feedback-warning": "yellow-0", - "interface": "yellow-500", - "interface-hover": "yellow-400", - "primary": "yellow-500", - "primary-hover": "yellow-400", - "primary-inverse": "hyper-500", - "secondary": "white", - "secondary-hover": "white-700", - "shadow-primary": "white", - "shadow-secondary": "white-600", - "text": "white", - "text-accent": "beige", - "text-disabled": "white-500", - "text-secondary": "white-600", - }, - "light": { - "background": "white", - "background-contrast": "white", - "background-current": "white", - "background-disabled": "navy-200", - "background-error": "red-0", - "background-hover": "navy-200", - "background-primary": "beige", - "background-selected": "navy-100", - "background-success": "green-0", - "background-warning": "yellow-0", - "border-disabled": "navy-500", - "border-primary": "navy-800", - "border-secondary": "navy-600", - "border-tertiary": "navy-300", - "danger": "red-500", - "danger-hover": "red-600", - "feedback-error": "red-600", - "feedback-success": "green-700", - "feedback-warning": "yellow", - "interface": "hyper-500", - "interface-hover": "hyper-400", - "primary": "blue-500", - "primary-hover": "blue-400", - "primary-inverse": "yellow-500", - "secondary": "navy-800", - "secondary-hover": "navy-700", - "shadow-primary": "navy-800", - "shadow-secondary": "navy-600", - "text": "navy-800", - "text-accent": "navy-900", - "text-disabled": "navy-500", - "text-secondary": "navy-600", - }, - }, - "name": "admin", - "spacing": { - "0": 0, - "12": "0.75rem", - "16": "1rem", - "24": "1.5rem", - "32": "2rem", - "4": "0.25rem", - "40": "2.5rem", - "48": "3rem", - "64": "4rem", - "8": "0.5rem", - "96": "6rem", - }, -} -`; - -exports[`themes core - theme shape 1`] = ` -{ - "_getColorValue": [Function], - "_tokens": { - "colors": { - "beige": "#FFF0E5", - "beige-100": "#FFF0E5", - "black": "#000000", - "blue": "#1557FF", - "blue-0": "#F5FCFF", - "blue-100": "#D3F2FF", - "blue-300": "#66C4FF", - "blue-400": "#3388FF", - "blue-500": "#1557FF", - "blue-800": "#1D2340", - "gray-100": "#F5F5F5", - "gray-200": "#EEEEEE", - "gray-300": "#E0E0E0", - "gray-600": "#9E9E9E", - "gray-800": "#616161", - "gray-900": "#424242", - "green": "#008A27", - "green-0": "#F5FFE3", - "green-100": "#EAFDC6", - "green-400": "#AEE938", - "green-700": "#008A27", - "green-900": "#151C07", - "hyper": "#3A10E5", - "hyper-400": "#5533FF", - "hyper-500": "#3A10E5", - "lightBlue": "#66C4FF", - "lightGreen": "#AEE938", - "navy": "#10162F", - "navy-100": "rgba(16,22,47,0.04)", - "navy-200": "rgba(16,22,47,0.12)", - "navy-300": "rgba(16,22,47,0.28)", - "navy-400": "rgba(16,22,47,0.47)", - "navy-500": "rgba(16,22,47,0.63)", - "navy-600": "rgba(16,22,47,0.75)", - "navy-700": "rgba(16,22,47,0.86)", - "navy-800": "#10162F", - "navy-900": "#0A0D1C", - "orange": "#FF8C00", - "orange-100": "#FFE8CC", - "orange-500": "#FF8C00", - "paleBlue": "#F5FCFF", - "paleGreen": "#F5FFE3", - "palePink": "#FFF5FF", - "paleRed": "#DC5879", - "paleYellow": "#FFFAE5", - "pink": "#F966FF", - "pink-0": "#FFF5FF", - "pink-400": "#F966FF", - "red": "#E91C11", - "red-0": "#FBF1F0", - "red-300": "#E85D7F", - "red-400": "#DC5879", - "red-500": "#E91C11", - "red-600": "#BE1809", - "red-900": "#280503", - "white": "#ffffff", - "white-100": "rgba(255,255,255,0.04)", - "white-200": "rgba(255,255,255,0.09)", - "white-300": "rgba(255,255,255,0.2)", - "white-400": "rgba(255,255,255,0.35)", - "white-500": "rgba(255,255,255,0.5)", - "white-600": "rgba(255,255,255,0.65)", - "white-700": "rgba(255,255,255,0.8)", - "yellow": "#FFD300", - "yellow-0": "#FFFAE5", - "yellow-400": "#CCA900", - "yellow-500": "#FFD300", - "yellow-900": "#211B00", - }, - "elements": { - "headerHeight": { - "base": "4rem", - "md": "5rem", - }, - "headerZ": 15, - }, - "modes": { - "dark": { - "background": "#10162F", - "background-contrast": "#000000", - "background-current": "#10162F", - "background-disabled": "rgba(255,255,255,0.09)", - "background-error": "#280503", - "background-hover": "rgba(255,255,255,0.09)", - "background-primary": "#0A0D1C", - "background-selected": "rgba(255,255,255,0.04)", - "background-success": "#151C07", - "background-warning": "#211B00", - "border-disabled": "rgba(255,255,255,0.5)", - "border-primary": "#ffffff", - "border-secondary": "rgba(255,255,255,0.65)", - "border-tertiary": "rgba(255,255,255,0.2)", - "danger": "#E85D7F", - "danger-hover": "#DC5879", - "feedback-error": "#E85D7F", - "feedback-success": "#AEE938", - "feedback-warning": "#FFFAE5", - "interface": "#FFD300", - "interface-hover": "#CCA900", - "primary": "#FFD300", - "primary-hover": "#CCA900", - "primary-inverse": "#3A10E5", - "secondary": "#ffffff", - "secondary-hover": "rgba(255,255,255,0.8)", - "shadow-primary": "#ffffff", - "shadow-secondary": "rgba(255,255,255,0.65)", - "text": "#ffffff", - "text-accent": "#FFF0E5", - "text-disabled": "rgba(255,255,255,0.5)", - "text-secondary": "rgba(255,255,255,0.65)", - }, - "light": { - "background": "#ffffff", - "background-contrast": "#ffffff", - "background-current": "#ffffff", - "background-disabled": "rgba(16,22,47,0.12)", - "background-error": "#FBF1F0", - "background-hover": "rgba(16,22,47,0.12)", - "background-primary": "#FFF0E5", - "background-selected": "rgba(16,22,47,0.04)", - "background-success": "#F5FFE3", - "background-warning": "#FFFAE5", - "border-disabled": "rgba(16,22,47,0.63)", - "border-primary": "#10162F", - "border-secondary": "rgba(16,22,47,0.75)", - "border-tertiary": "rgba(16,22,47,0.28)", - "danger": "#E91C11", - "danger-hover": "#BE1809", - "feedback-error": "#BE1809", - "feedback-success": "#008A27", - "feedback-warning": "#FFD300", - "interface": "#3A10E5", - "interface-hover": "#5533FF", - "primary": "#3A10E5", - "primary-hover": "#5533FF", - "primary-inverse": "#FFD300", - "secondary": "#10162F", - "secondary-hover": "rgba(16,22,47,0.86)", - "shadow-primary": "#10162F", - "shadow-secondary": "rgba(16,22,47,0.75)", - "text": "#10162F", - "text-accent": "#0A0D1C", - "text-disabled": "rgba(16,22,47,0.63)", - "text-secondary": "rgba(16,22,47,0.75)", - }, - }, - }, - "_variables": { - "mode": { - "--color-background": "var(--color-white)", - "--color-background-contrast": "var(--color-white)", - "--color-background-current": "var(--color-white)", - "--color-background-disabled": "var(--color-navy-200)", - "--color-background-error": "var(--color-red-0)", - "--color-background-hover": "var(--color-navy-200)", - "--color-background-primary": "var(--color-beige)", - "--color-background-selected": "var(--color-navy-100)", - "--color-background-success": "var(--color-green-0)", - "--color-background-warning": "var(--color-yellow-0)", - "--color-border-disabled": "var(--color-navy-500)", - "--color-border-primary": "var(--color-navy-800)", - "--color-border-secondary": "var(--color-navy-600)", - "--color-border-tertiary": "var(--color-navy-300)", - "--color-danger": "var(--color-red-500)", - "--color-danger-hover": "var(--color-red-600)", - "--color-feedback-error": "var(--color-red-600)", - "--color-feedback-success": "var(--color-green-700)", - "--color-feedback-warning": "var(--color-yellow)", - "--color-interface": "var(--color-hyper-500)", - "--color-interface-hover": "var(--color-hyper-400)", - "--color-primary": "var(--color-hyper-500)", - "--color-primary-hover": "var(--color-hyper-400)", - "--color-primary-inverse": "var(--color-yellow-500)", - "--color-secondary": "var(--color-navy-800)", - "--color-secondary-hover": "var(--color-navy-700)", - "--color-shadow-primary": "var(--color-navy-800)", - "--color-shadow-secondary": "var(--color-navy-600)", - "--color-text": "var(--color-navy-800)", - "--color-text-accent": "var(--color-navy-900)", - "--color-text-disabled": "var(--color-navy-500)", - "--color-text-secondary": "var(--color-navy-600)", - }, - "root": { - "--color-beige": "#FFF0E5", - "--color-beige-100": "#FFF0E5", - "--color-black": "#000000", - "--color-blue": "#1557FF", - "--color-blue-0": "#F5FCFF", - "--color-blue-100": "#D3F2FF", - "--color-blue-300": "#66C4FF", - "--color-blue-400": "#3388FF", - "--color-blue-500": "#1557FF", - "--color-blue-800": "#1D2340", - "--color-gray-100": "#F5F5F5", - "--color-gray-200": "#EEEEEE", - "--color-gray-300": "#E0E0E0", - "--color-gray-600": "#9E9E9E", - "--color-gray-800": "#616161", - "--color-gray-900": "#424242", - "--color-green": "#008A27", - "--color-green-0": "#F5FFE3", - "--color-green-100": "#EAFDC6", - "--color-green-400": "#AEE938", - "--color-green-700": "#008A27", - "--color-green-900": "#151C07", - "--color-hyper": "#3A10E5", - "--color-hyper-400": "#5533FF", - "--color-hyper-500": "#3A10E5", - "--color-lightBlue": "#66C4FF", - "--color-lightGreen": "#AEE938", - "--color-navy": "#10162F", - "--color-navy-100": "rgba(16,22,47,0.04)", - "--color-navy-200": "rgba(16,22,47,0.12)", - "--color-navy-300": "rgba(16,22,47,0.28)", - "--color-navy-400": "rgba(16,22,47,0.47)", - "--color-navy-500": "rgba(16,22,47,0.63)", - "--color-navy-600": "rgba(16,22,47,0.75)", - "--color-navy-700": "rgba(16,22,47,0.86)", - "--color-navy-800": "#10162F", - "--color-navy-900": "#0A0D1C", - "--color-orange": "#FF8C00", - "--color-orange-100": "#FFE8CC", - "--color-orange-500": "#FF8C00", - "--color-paleBlue": "#F5FCFF", - "--color-paleGreen": "#F5FFE3", - "--color-palePink": "#FFF5FF", - "--color-paleRed": "#DC5879", - "--color-paleYellow": "#FFFAE5", - "--color-pink": "#F966FF", - "--color-pink-0": "#FFF5FF", - "--color-pink-400": "#F966FF", - "--color-red": "#E91C11", - "--color-red-0": "#FBF1F0", - "--color-red-300": "#E85D7F", - "--color-red-400": "#DC5879", - "--color-red-500": "#E91C11", - "--color-red-600": "#BE1809", - "--color-red-900": "#280503", - "--color-white": "#ffffff", - "--color-white-100": "rgba(255,255,255,0.04)", - "--color-white-200": "rgba(255,255,255,0.09)", - "--color-white-300": "rgba(255,255,255,0.2)", - "--color-white-400": "rgba(255,255,255,0.35)", - "--color-white-500": "rgba(255,255,255,0.5)", - "--color-white-600": "rgba(255,255,255,0.65)", - "--color-white-700": "rgba(255,255,255,0.8)", - "--color-yellow": "#FFD300", - "--color-yellow-0": "#FFFAE5", - "--color-yellow-400": "#CCA900", - "--color-yellow-500": "#FFD300", - "--color-yellow-900": "#211B00", - "--elements-headerHeight": "4rem", - "--elements-headerZ": 15, - "@media only screen and (min-width: 1024px)": { - "--elements-headerHeight": "5rem", - }, - }, - }, - "borderRadii": { - "full": "999px", - "lg": "8px", - "md": "4px", - "none": "0px", - "sm": "2px", - "xl": "16px", - }, - "borders": { - "1": "1px solid var(--color-border-primary)", - "2": "2px solid var(--color-border-primary)", - }, - "breakpoints": { - "c_base": "@container (min-width: 1px)", - "c_lg": "@container (min-width: 1200px)", - "c_md": "@container (min-width: 1024px)", - "c_sm": "@container (min-width: 768px)", - "c_xl": "@container (min-width: 1440px)", - "c_xs": "@container (min-width: 480px)", - "lg": "@media only screen and (min-width: 1200px)", - "md": "@media only screen and (min-width: 1024px)", - "sm": "@media only screen and (min-width: 768px)", - "xl": "@media only screen and (min-width: 1440px)", - "xs": "@media only screen and (min-width: 480px)", - }, - "colors": { - "background": "var(--color-background)", - "background-contrast": "var(--color-background-contrast)", - "background-current": "var(--color-background-current)", - "background-disabled": "var(--color-background-disabled)", - "background-error": "var(--color-background-error)", - "background-hover": "var(--color-background-hover)", - "background-primary": "var(--color-background-primary)", - "background-selected": "var(--color-background-selected)", - "background-success": "var(--color-background-success)", - "background-warning": "var(--color-background-warning)", - "beige": "var(--color-beige)", - "beige-100": "var(--color-beige-100)", - "black": "var(--color-black)", - "blue": "var(--color-blue)", - "blue-0": "var(--color-blue-0)", - "blue-100": "var(--color-blue-100)", - "blue-300": "var(--color-blue-300)", - "blue-400": "var(--color-blue-400)", - "blue-500": "var(--color-blue-500)", - "blue-800": "var(--color-blue-800)", - "border-disabled": "var(--color-border-disabled)", - "border-primary": "var(--color-border-primary)", - "border-secondary": "var(--color-border-secondary)", - "border-tertiary": "var(--color-border-tertiary)", - "danger": "var(--color-danger)", - "danger-hover": "var(--color-danger-hover)", - "feedback-error": "var(--color-feedback-error)", - "feedback-success": "var(--color-feedback-success)", - "feedback-warning": "var(--color-feedback-warning)", - "gray-100": "var(--color-gray-100)", - "gray-200": "var(--color-gray-200)", - "gray-300": "var(--color-gray-300)", - "gray-600": "var(--color-gray-600)", - "gray-800": "var(--color-gray-800)", - "gray-900": "var(--color-gray-900)", - "green": "var(--color-green)", - "green-0": "var(--color-green-0)", - "green-100": "var(--color-green-100)", - "green-400": "var(--color-green-400)", - "green-700": "var(--color-green-700)", - "green-900": "var(--color-green-900)", - "hyper": "var(--color-hyper)", - "hyper-400": "var(--color-hyper-400)", - "hyper-500": "var(--color-hyper-500)", - "interface": "var(--color-interface)", - "interface-hover": "var(--color-interface-hover)", - "lightBlue": "var(--color-lightBlue)", - "lightGreen": "var(--color-lightGreen)", - "navy": "var(--color-navy)", - "navy-100": "var(--color-navy-100)", - "navy-200": "var(--color-navy-200)", - "navy-300": "var(--color-navy-300)", - "navy-400": "var(--color-navy-400)", - "navy-500": "var(--color-navy-500)", - "navy-600": "var(--color-navy-600)", - "navy-700": "var(--color-navy-700)", - "navy-800": "var(--color-navy-800)", - "navy-900": "var(--color-navy-900)", - "orange": "var(--color-orange)", - "orange-100": "var(--color-orange-100)", - "orange-500": "var(--color-orange-500)", - "paleBlue": "var(--color-paleBlue)", - "paleGreen": "var(--color-paleGreen)", - "palePink": "var(--color-palePink)", - "paleRed": "var(--color-paleRed)", - "paleYellow": "var(--color-paleYellow)", - "pink": "var(--color-pink)", - "pink-0": "var(--color-pink-0)", - "pink-400": "var(--color-pink-400)", - "primary": "var(--color-primary)", - "primary-hover": "var(--color-primary-hover)", - "primary-inverse": "var(--color-primary-inverse)", - "red": "var(--color-red)", - "red-0": "var(--color-red-0)", - "red-300": "var(--color-red-300)", - "red-400": "var(--color-red-400)", - "red-500": "var(--color-red-500)", - "red-600": "var(--color-red-600)", - "red-900": "var(--color-red-900)", - "secondary": "var(--color-secondary)", - "secondary-hover": "var(--color-secondary-hover)", - "shadow-primary": "var(--color-shadow-primary)", - "shadow-secondary": "var(--color-shadow-secondary)", - "text": "var(--color-text)", - "text-accent": "var(--color-text-accent)", - "text-disabled": "var(--color-text-disabled)", - "text-secondary": "var(--color-text-secondary)", - "white": "var(--color-white)", - "white-100": "var(--color-white-100)", - "white-200": "var(--color-white-200)", - "white-300": "var(--color-white-300)", - "white-400": "var(--color-white-400)", - "white-500": "var(--color-white-500)", - "white-600": "var(--color-white-600)", - "white-700": "var(--color-white-700)", - "yellow": "var(--color-yellow)", - "yellow-0": "var(--color-yellow-0)", - "yellow-400": "var(--color-yellow-400)", - "yellow-500": "var(--color-yellow-500)", - "yellow-900": "var(--color-yellow-900)", - }, - "elements": { - "headerHeight": "var(--elements-headerHeight)", - "headerZ": "var(--elements-headerZ)", - }, - "elevation": { - "hover-shadow": "-8px 8px 0 0 var(--color-shadow-primary)", - "hover-transform": "translate(4px, -4px)", - "hoverMirrored-shadow": "8px 8px 0 0 var(--color-shadow-primary)", - "hoverMirrored-transform": "translate(-4px, -4px)", - "rest-shadow": "0 0 0 0 var(--color-shadow-primary)", - "rest-transform": "none", - }, - "fontFamily": { - "accent": ""Suisse", "Apercu", -apple-system, BlinkMacSystemFont, -"Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", -"Helvetica Neue", sans-serif", - "base": ""Apercu", -apple-system, BlinkMacSystemFont, "Segoe UI", -"Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", -sans-serif", - "monospace": "Monaco, Menlo, "Ubuntu Mono", "Droid Sans Mono", Consolas, -monospace", - "system": "-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Ubuntu", -"Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif", - }, - "fontSize": { - "14": "0.875rem", - "16": "1rem", - "18": "1.125rem", - "20": "1.25rem", - "22": "1.375rem", - "26": "1.625rem", - "34": "2.125rem", - "44": "2.75rem", - "64": "4rem", - }, - "fontWeight": { - "400": 400, - "700": 700, - "base": 400, - "title": 700, - }, - "lineHeight": { - "base": 1.5, - "spacedTitle": 1.3, - "title": 1.2, - }, - "mode": "light", - "modes": { - "dark": { - "background": "navy-800", - "background-contrast": "black", - "background-current": "navy-800", - "background-disabled": "white-200", - "background-error": "red-900", - "background-hover": "white-200", - "background-primary": "navy-900", - "background-selected": "white-100", - "background-success": "green-900", - "background-warning": "yellow-900", - "border-disabled": "white-500", - "border-primary": "white", - "border-secondary": "white-600", - "border-tertiary": "white-300", - "danger": "red-300", - "danger-hover": "red-400", - "feedback-error": "red-300", - "feedback-success": "green-400", - "feedback-warning": "yellow-0", - "interface": "yellow-500", - "interface-hover": "yellow-400", - "primary": "yellow-500", - "primary-hover": "yellow-400", - "primary-inverse": "hyper-500", - "secondary": "white", - "secondary-hover": "white-700", - "shadow-primary": "white", - "shadow-secondary": "white-600", - "text": "white", - "text-accent": "beige", - "text-disabled": "white-500", - "text-secondary": "white-600", - }, - "light": { - "background": "white", - "background-contrast": "white", - "background-current": "white", - "background-disabled": "navy-200", - "background-error": "red-0", - "background-hover": "navy-200", - "background-primary": "beige", - "background-selected": "navy-100", - "background-success": "green-0", - "background-warning": "yellow-0", - "border-disabled": "navy-500", - "border-primary": "navy-800", - "border-secondary": "navy-600", - "border-tertiary": "navy-300", - "danger": "red-500", - "danger-hover": "red-600", - "feedback-error": "red-600", - "feedback-success": "green-700", - "feedback-warning": "yellow", - "interface": "hyper-500", - "interface-hover": "hyper-400", - "primary": "hyper-500", - "primary-hover": "hyper-400", - "primary-inverse": "yellow-500", - "secondary": "navy-800", - "secondary-hover": "navy-700", - "shadow-primary": "navy-800", - "shadow-secondary": "navy-600", - "text": "navy-800", - "text-accent": "navy-900", - "text-disabled": "navy-500", - "text-secondary": "navy-600", - }, - }, - "name": "core", - "spacing": { - "0": 0, - "12": "0.75rem", - "16": "1rem", - "24": "1.5rem", - "32": "2rem", - "4": "0.25rem", - "40": "2.5rem", - "48": "3rem", - "64": "4rem", - "8": "0.5rem", - "96": "6rem", - }, -} -`; - -exports[`themes lxStudio - theme shape 1`] = ` -{ - "_getColorValue": [Function], - "_tokens": { - "colors": { - "beige": "#FFF0E5", - "beige-100": "#FFF0E5", - "black": "#000000", - "blue": "#1557FF", - "blue-0": "#F5FCFF", - "blue-100": "#D3F2FF", - "blue-300": "#66C4FF", - "blue-400": "#3388FF", - "blue-500": "#1557FF", - "blue-800": "#1D2340", - "gray-100": "#F5F5F5", - "gray-200": "#EEEEEE", - "gray-300": "#E0E0E0", - "gray-600": "#9E9E9E", - "gray-800": "#616161", - "gray-900": "#424242", - "green": "#008A27", - "green-0": "#F5FFE3", - "green-100": "#EAFDC6", - "green-400": "#AEE938", - "green-700": "#008A27", - "green-900": "#151C07", - "hyper": "#3A10E5", - "hyper-400": "#5533FF", - "hyper-500": "#3A10E5", - "lightBlue": "#66C4FF", - "lightGreen": "#AEE938", - "lxStudioBgPrimary": "#FAFBFC", - "lxStudioSuccess": "#06844F", - "navy": "#10162F", - "navy-100": "rgba(16,22,47,0.04)", - "navy-200": "rgba(16,22,47,0.12)", - "navy-300": "rgba(16,22,47,0.28)", - "navy-400": "rgba(16,22,47,0.47)", - "navy-500": "rgba(16,22,47,0.63)", - "navy-600": "rgba(16,22,47,0.75)", - "navy-700": "rgba(16,22,47,0.86)", - "navy-800": "#10162F", - "navy-900": "#0A0D1C", - "orange": "#FF8C00", - "orange-100": "#FFE8CC", - "orange-500": "#FF8C00", - "paleBlue": "#F5FCFF", - "paleGreen": "#F5FFE3", - "palePink": "#FFF5FF", - "paleRed": "#DC5879", - "paleYellow": "#FFFAE5", - "pink": "#F966FF", - "pink-0": "#FFF5FF", - "pink-400": "#F966FF", - "red": "#E91C11", - "red-0": "#FBF1F0", - "red-300": "#E85D7F", - "red-400": "#DC5879", - "red-500": "#E91C11", - "red-600": "#BE1809", - "red-900": "#280503", - "sapphire": "#1C50BB", - "white": "#ffffff", - "white-100": "rgba(255,255,255,0.04)", - "white-200": "rgba(255,255,255,0.09)", - "white-300": "rgba(255,255,255,0.2)", - "white-400": "rgba(255,255,255,0.35)", - "white-500": "rgba(255,255,255,0.5)", - "white-600": "rgba(255,255,255,0.65)", - "white-700": "rgba(255,255,255,0.8)", - "yellow": "#FFD300", - "yellow-0": "#FFFAE5", - "yellow-400": "#CCA900", - "yellow-500": "#FFD300", - "yellow-900": "#211B00", - }, - "elements": { - "headerHeight": { - "base": "4rem", - "md": "5rem", - }, - "headerZ": 15, - }, - "modes": { - "dark": { - "background": "#10162F", - "background-contrast": "#000000", - "background-current": "#10162F", - "background-disabled": "rgba(255,255,255,0.09)", - "background-error": "#280503", - "background-hover": "rgba(255,255,255,0.09)", - "background-primary": "#0A0D1C", - "background-selected": "rgba(255,255,255,0.04)", - "background-success": "#151C07", - "background-warning": "#211B00", - "border-disabled": "rgba(255,255,255,0.5)", - "border-primary": "#ffffff", - "border-secondary": "rgba(255,255,255,0.65)", - "border-tertiary": "rgba(255,255,255,0.2)", - "danger": "#E85D7F", - "danger-hover": "#DC5879", - "feedback-error": "#E85D7F", - "feedback-success": "#AEE938", - "feedback-warning": "#FFFAE5", - "interface": "#FFD300", - "interface-hover": "#CCA900", - "primary": "#FFD300", - "primary-hover": "#CCA900", - "primary-inverse": "#3A10E5", - "secondary": "#ffffff", - "secondary-hover": "rgba(255,255,255,0.8)", - "shadow-primary": "#ffffff", - "shadow-secondary": "rgba(255,255,255,0.65)", - "text": "#ffffff", - "text-accent": "#FFF0E5", - "text-disabled": "rgba(255,255,255,0.5)", - "text-secondary": "rgba(255,255,255,0.65)", - }, - "light": { - "background": "#ffffff", - "background-contrast": "#ffffff", - "background-current": "#ffffff", - "background-disabled": "rgba(16,22,47,0.12)", - "background-error": "#FBF1F0", - "background-hover": "rgba(16,22,47,0.12)", - "background-primary": "#FAFBFC", - "background-selected": "rgba(16,22,47,0.04)", - "background-success": "#F5FFE3", - "background-warning": "#FFFAE5", - "border-disabled": "rgba(16,22,47,0.28)", - "border-primary": "rgba(16,22,47,0.47)", - "border-secondary": "rgba(16,22,47,0.75)", - "border-tertiary": "#10162F", - "danger": "#E91C11", - "danger-hover": "#BE1809", - "feedback-error": "#BE1809", - "feedback-success": "#06844F", - "feedback-warning": "#FFD300", - "interface": "#3A10E5", - "interface-hover": "#5533FF", - "primary": "#1C50BB", - "primary-hover": "#10162F", - "primary-inverse": "#FFD300", - "secondary": "#10162F", - "secondary-hover": "rgba(16,22,47,0.86)", - "shadow-primary": "rgba(16,22,47,0.12)", - "shadow-secondary": "rgba(16,22,47,0.75)", - "text": "#10162F", - "text-accent": "#0A0D1C", - "text-disabled": "rgba(16,22,47,0.63)", - "text-secondary": "rgba(16,22,47,0.75)", - }, - }, - }, - "_variables": { - "mode": { - "--color-background": "var(--color-white)", - "--color-background-contrast": "var(--color-white)", - "--color-background-current": "var(--color-white)", - "--color-background-disabled": "var(--color-navy-200)", - "--color-background-error": "var(--color-red-0)", - "--color-background-hover": "var(--color-navy-200)", - "--color-background-primary": "var(--color-lxStudioBgPrimary)", - "--color-background-selected": "var(--color-navy-100)", - "--color-background-success": "var(--color-green-0)", - "--color-background-warning": "var(--color-yellow-0)", - "--color-border-disabled": "var(--color-navy-300)", - "--color-border-primary": "var(--color-navy-400)", - "--color-border-secondary": "var(--color-navy-600)", - "--color-border-tertiary": "var(--color-navy-800)", - "--color-danger": "var(--color-red-500)", - "--color-danger-hover": "var(--color-red-600)", - "--color-feedback-error": "var(--color-red-600)", - "--color-feedback-success": "var(--color-lxStudioSuccess)", - "--color-feedback-warning": "var(--color-yellow)", - "--color-interface": "var(--color-hyper-500)", - "--color-interface-hover": "var(--color-hyper-400)", - "--color-primary": "var(--color-sapphire)", - "--color-primary-hover": "var(--color-navy-800)", - "--color-primary-inverse": "var(--color-yellow-500)", - "--color-secondary": "var(--color-navy-800)", - "--color-secondary-hover": "var(--color-navy-700)", - "--color-shadow-primary": "var(--color-navy-200)", - "--color-shadow-secondary": "var(--color-navy-600)", - "--color-text": "var(--color-navy-800)", - "--color-text-accent": "var(--color-navy-900)", - "--color-text-disabled": "var(--color-navy-500)", - "--color-text-secondary": "var(--color-navy-600)", - }, - "root": { - "--color-beige": "#FFF0E5", - "--color-beige-100": "#FFF0E5", - "--color-black": "#000000", - "--color-blue": "#1557FF", - "--color-blue-0": "#F5FCFF", - "--color-blue-100": "#D3F2FF", - "--color-blue-300": "#66C4FF", - "--color-blue-400": "#3388FF", - "--color-blue-500": "#1557FF", - "--color-blue-800": "#1D2340", - "--color-gray-100": "#F5F5F5", - "--color-gray-200": "#EEEEEE", - "--color-gray-300": "#E0E0E0", - "--color-gray-600": "#9E9E9E", - "--color-gray-800": "#616161", - "--color-gray-900": "#424242", - "--color-green": "#008A27", - "--color-green-0": "#F5FFE3", - "--color-green-100": "#EAFDC6", - "--color-green-400": "#AEE938", - "--color-green-700": "#008A27", - "--color-green-900": "#151C07", - "--color-hyper": "#3A10E5", - "--color-hyper-400": "#5533FF", - "--color-hyper-500": "#3A10E5", - "--color-lightBlue": "#66C4FF", - "--color-lightGreen": "#AEE938", - "--color-lxStudioBgPrimary": "#FAFBFC", - "--color-lxStudioSuccess": "#06844F", - "--color-navy": "#10162F", - "--color-navy-100": "rgba(16,22,47,0.04)", - "--color-navy-200": "rgba(16,22,47,0.12)", - "--color-navy-300": "rgba(16,22,47,0.28)", - "--color-navy-400": "rgba(16,22,47,0.47)", - "--color-navy-500": "rgba(16,22,47,0.63)", - "--color-navy-600": "rgba(16,22,47,0.75)", - "--color-navy-700": "rgba(16,22,47,0.86)", - "--color-navy-800": "#10162F", - "--color-navy-900": "#0A0D1C", - "--color-orange": "#FF8C00", - "--color-orange-100": "#FFE8CC", - "--color-orange-500": "#FF8C00", - "--color-paleBlue": "#F5FCFF", - "--color-paleGreen": "#F5FFE3", - "--color-palePink": "#FFF5FF", - "--color-paleRed": "#DC5879", - "--color-paleYellow": "#FFFAE5", - "--color-pink": "#F966FF", - "--color-pink-0": "#FFF5FF", - "--color-pink-400": "#F966FF", - "--color-red": "#E91C11", - "--color-red-0": "#FBF1F0", - "--color-red-300": "#E85D7F", - "--color-red-400": "#DC5879", - "--color-red-500": "#E91C11", - "--color-red-600": "#BE1809", - "--color-red-900": "#280503", - "--color-sapphire": "#1C50BB", - "--color-white": "#ffffff", - "--color-white-100": "rgba(255,255,255,0.04)", - "--color-white-200": "rgba(255,255,255,0.09)", - "--color-white-300": "rgba(255,255,255,0.2)", - "--color-white-400": "rgba(255,255,255,0.35)", - "--color-white-500": "rgba(255,255,255,0.5)", - "--color-white-600": "rgba(255,255,255,0.65)", - "--color-white-700": "rgba(255,255,255,0.8)", - "--color-yellow": "#FFD300", - "--color-yellow-0": "#FFFAE5", - "--color-yellow-400": "#CCA900", - "--color-yellow-500": "#FFD300", - "--color-yellow-900": "#211B00", - "--elements-headerHeight": "4rem", - "--elements-headerZ": 15, - "@media only screen and (min-width: 1024px)": { - "--elements-headerHeight": "5rem", - }, - }, - }, - "borderRadii": { - "full": "999px", - "lg": "12px", - "md": "8px", - "none": "0px", - "sm": "4px", - "xl": "16px", - }, - "borders": { - "1": "1px solid var(--color-border-primary)", - "2": "2px solid var(--color-border-primary)", - }, - "breakpoints": { - "c_base": "@container (min-width: 1px)", - "c_lg": "@container (min-width: 1200px)", - "c_md": "@container (min-width: 1024px)", - "c_sm": "@container (min-width: 768px)", - "c_xl": "@container (min-width: 1440px)", - "c_xs": "@container (min-width: 480px)", - "lg": "@media only screen and (min-width: 1200px)", - "md": "@media only screen and (min-width: 1024px)", - "sm": "@media only screen and (min-width: 768px)", - "xl": "@media only screen and (min-width: 1440px)", - "xs": "@media only screen and (min-width: 480px)", - }, - "colors": { - "background": "var(--color-background)", - "background-contrast": "var(--color-background-contrast)", - "background-current": "var(--color-background-current)", - "background-disabled": "var(--color-background-disabled)", - "background-error": "var(--color-background-error)", - "background-hover": "var(--color-background-hover)", - "background-primary": "var(--color-background-primary)", - "background-selected": "var(--color-background-selected)", - "background-success": "var(--color-background-success)", - "background-warning": "var(--color-background-warning)", - "beige": "var(--color-beige)", - "beige-100": "var(--color-beige-100)", - "black": "var(--color-black)", - "blue": "var(--color-blue)", - "blue-0": "var(--color-blue-0)", - "blue-100": "var(--color-blue-100)", - "blue-300": "var(--color-blue-300)", - "blue-400": "var(--color-blue-400)", - "blue-500": "var(--color-blue-500)", - "blue-800": "var(--color-blue-800)", - "border-disabled": "var(--color-border-disabled)", - "border-primary": "var(--color-border-primary)", - "border-secondary": "var(--color-border-secondary)", - "border-tertiary": "var(--color-border-tertiary)", - "danger": "var(--color-danger)", - "danger-hover": "var(--color-danger-hover)", - "feedback-error": "var(--color-feedback-error)", - "feedback-success": "var(--color-feedback-success)", - "feedback-warning": "var(--color-feedback-warning)", - "gray-100": "var(--color-gray-100)", - "gray-200": "var(--color-gray-200)", - "gray-300": "var(--color-gray-300)", - "gray-600": "var(--color-gray-600)", - "gray-800": "var(--color-gray-800)", - "gray-900": "var(--color-gray-900)", - "green": "var(--color-green)", - "green-0": "var(--color-green-0)", - "green-100": "var(--color-green-100)", - "green-400": "var(--color-green-400)", - "green-700": "var(--color-green-700)", - "green-900": "var(--color-green-900)", - "hyper": "var(--color-hyper)", - "hyper-400": "var(--color-hyper-400)", - "hyper-500": "var(--color-hyper-500)", - "interface": "var(--color-interface)", - "interface-hover": "var(--color-interface-hover)", - "lightBlue": "var(--color-lightBlue)", - "lightGreen": "var(--color-lightGreen)", - "lxStudioBgPrimary": "var(--color-lxStudioBgPrimary)", - "lxStudioSuccess": "var(--color-lxStudioSuccess)", - "navy": "var(--color-navy)", - "navy-100": "var(--color-navy-100)", - "navy-200": "var(--color-navy-200)", - "navy-300": "var(--color-navy-300)", - "navy-400": "var(--color-navy-400)", - "navy-500": "var(--color-navy-500)", - "navy-600": "var(--color-navy-600)", - "navy-700": "var(--color-navy-700)", - "navy-800": "var(--color-navy-800)", - "navy-900": "var(--color-navy-900)", - "orange": "var(--color-orange)", - "orange-100": "var(--color-orange-100)", - "orange-500": "var(--color-orange-500)", - "paleBlue": "var(--color-paleBlue)", - "paleGreen": "var(--color-paleGreen)", - "palePink": "var(--color-palePink)", - "paleRed": "var(--color-paleRed)", - "paleYellow": "var(--color-paleYellow)", - "pink": "var(--color-pink)", - "pink-0": "var(--color-pink-0)", - "pink-400": "var(--color-pink-400)", - "primary": "var(--color-primary)", - "primary-hover": "var(--color-primary-hover)", - "primary-inverse": "var(--color-primary-inverse)", - "red": "var(--color-red)", - "red-0": "var(--color-red-0)", - "red-300": "var(--color-red-300)", - "red-400": "var(--color-red-400)", - "red-500": "var(--color-red-500)", - "red-600": "var(--color-red-600)", - "red-900": "var(--color-red-900)", - "sapphire": "var(--color-sapphire)", - "secondary": "var(--color-secondary)", - "secondary-hover": "var(--color-secondary-hover)", - "shadow-primary": "var(--color-shadow-primary)", - "shadow-secondary": "var(--color-shadow-secondary)", - "text": "var(--color-text)", - "text-accent": "var(--color-text-accent)", - "text-disabled": "var(--color-text-disabled)", - "text-secondary": "var(--color-text-secondary)", - "white": "var(--color-white)", - "white-100": "var(--color-white-100)", - "white-200": "var(--color-white-200)", - "white-300": "var(--color-white-300)", - "white-400": "var(--color-white-400)", - "white-500": "var(--color-white-500)", - "white-600": "var(--color-white-600)", - "white-700": "var(--color-white-700)", - "yellow": "var(--color-yellow)", - "yellow-0": "var(--color-yellow-0)", - "yellow-400": "var(--color-yellow-400)", - "yellow-500": "var(--color-yellow-500)", - "yellow-900": "var(--color-yellow-900)", - }, - "elements": { - "headerHeight": "var(--elements-headerHeight)", - "headerZ": "var(--elements-headerZ)", - }, - "elevation": { - "hover-shadow": "-8px 8px 0 0 var(--color-shadow-primary)", - "hover-transform": "translate(4px, -4px)", - "hoverMirrored-shadow": "8px 8px 0 0 var(--color-shadow-primary)", - "hoverMirrored-transform": "translate(-4px, -4px)", - "rest-shadow": "0 0 0 0 var(--color-shadow-primary)", - "rest-transform": "none", - }, - "fontFamily": { - "accent": ""Skillsoft Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", -"Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", -sans-serif", - "base": ""Skillsoft Text", -apple-system, BlinkMacSystemFont, "Segoe UI", -"Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", -sans-serif", - "monospace": "Monaco, Menlo, "Ubuntu Mono", "Droid Sans Mono", Consolas, -monospace", - "system": "-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Ubuntu", -"Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif", - }, - "fontSize": { - "14": "0.875rem", - "16": "1rem", - "18": "1.125rem", - "20": "1.25rem", - "22": "1.375rem", - "26": "1.625rem", - "34": "2.125rem", - "44": "2.75rem", - "64": "4rem", - }, - "fontWeight": { - "400": 400, - "500": 500, - "700": 700, - "base": 400, - "bold": 500, - "title": 500, - }, - "lineHeight": { - "base": 1.5, - "spacedTitle": 1.3, - "title": 1.2, - }, - "mode": "light", - "modes": { - "dark": { - "background": "navy-800", - "background-contrast": "black", - "background-current": "navy-800", - "background-disabled": "white-200", - "background-error": "red-900", - "background-hover": "white-200", - "background-primary": "navy-900", - "background-selected": "white-100", - "background-success": "green-900", - "background-warning": "yellow-900", - "border-disabled": "white-500", - "border-primary": "white", - "border-secondary": "white-600", - "border-tertiary": "white-300", - "danger": "red-300", - "danger-hover": "red-400", - "feedback-error": "red-300", - "feedback-success": "green-400", - "feedback-warning": "yellow-0", - "interface": "yellow-500", - "interface-hover": "yellow-400", - "primary": "yellow-500", - "primary-hover": "yellow-400", - "primary-inverse": "hyper-500", - "secondary": "white", - "secondary-hover": "white-700", - "shadow-primary": "white", - "shadow-secondary": "white-600", - "text": "white", - "text-accent": "beige", - "text-disabled": "white-500", - "text-secondary": "white-600", - }, - "light": { - "background": "white", - "background-contrast": "white", - "background-current": "white", - "background-disabled": "navy-200", - "background-error": "red-0", - "background-hover": "navy-200", - "background-primary": "lxStudioBgPrimary", - "background-selected": "navy-100", - "background-success": "green-0", - "background-warning": "yellow-0", - "border-disabled": "navy-300", - "border-primary": "navy-400", - "border-secondary": "navy-600", - "border-tertiary": "navy-800", - "danger": "red-500", - "danger-hover": "red-600", - "feedback-error": "red-600", - "feedback-success": "lxStudioSuccess", - "feedback-warning": "yellow", - "interface": "hyper-500", - "interface-hover": "hyper-400", - "primary": "sapphire", - "primary-hover": "navy-800", - "primary-inverse": "yellow-500", - "secondary": "navy-800", - "secondary-hover": "navy-700", - "shadow-primary": "navy-200", - "shadow-secondary": "navy-600", - "text": "navy-800", - "text-accent": "navy-900", - "text-disabled": "navy-500", - "text-secondary": "navy-600", - }, - }, - "name": "lxStudio", - "spacing": { - "0": 0, - "12": "0.75rem", - "16": "1rem", - "24": "1.5rem", - "32": "2rem", - "4": "0.25rem", - "40": "2.5rem", - "48": "3rem", - "64": "4rem", - "8": "0.5rem", - "96": "6rem", - }, -} -`; - -exports[`themes percipio - theme shape 1`] = ` -{ - "_getColorValue": [Function], - "_tokens": { - "colors": { - "beige": "#FFF0E5", - "beige-100": "#FFF0E5", - "black": "#000000", - "blue": "#1557FF", - "blue-0": "#F5FCFF", - "blue-100": "#D3F2FF", - "blue-300": "#66C4FF", - "blue-400": "#3388FF", - "blue-500": "#1557FF", - "blue-800": "#1D2340", - "gray-100": "#F5F5F5", - "gray-200": "#EEEEEE", - "gray-300": "#E0E0E0", - "gray-600": "#9E9E9E", - "gray-800": "#616161", - "gray-900": "#424242", - "green": "#008A27", - "green-0": "#F5FFE3", - "green-100": "#EAFDC6", - "green-400": "#AEE938", - "green-700": "#008A27", - "green-900": "#151C07", - "hyper": "#3A10E5", - "hyper-400": "#5533FF", - "hyper-500": "#3A10E5", - "lightBlue": "#66C4FF", - "lightGreen": "#AEE938", - "navy": "#10162F", - "navy-100": "rgba(16,22,47,0.04)", - "navy-200": "rgba(16,22,47,0.12)", - "navy-300": "rgba(16,22,47,0.28)", - "navy-400": "rgba(16,22,47,0.47)", - "navy-500": "rgba(16,22,47,0.63)", - "navy-600": "rgba(16,22,47,0.75)", - "navy-700": "rgba(16,22,47,0.86)", - "navy-800": "#10162F", - "navy-900": "#0A0D1C", - "orange": "#FF8C00", - "orange-100": "#FFE8CC", - "orange-500": "#FF8C00", - "paleBlue": "#F5FCFF", - "paleGreen": "#F5FFE3", - "palePink": "#FFF5FF", - "paleRed": "#DC5879", - "paleYellow": "#FFFAE5", - "percipioActionDangerHover": "#A52020", - "percipioActionPrimaryHover": "#141C36", - "percipioActionSecondary": "#6A6E75", - "percipioActionSecondaryHover": "rgba(106, 110, 117, 0.86)", - "percipioBgError": "#FFF1F5", - "percipioBgPrimary": "#FAFBFC", - "percipioBgSuccess": "#EEF7F3", - "percipioBgWarning": "#FFF7E0", - "percipioDanger": "#B83C3C", - "percipioFeedbackSuccess": "#1B8057", - "percipioFeedbackWarning": "#EF5B0D", - "percipioTextAccent": "#222325", - "percipioTextDisabled": "#AFB6C2", - "percipioTextPrimary": "#222325", - "percipioTextSecondary": "rgba(34, 35, 37, 0.75)", - "pink": "#F966FF", - "pink-0": "#FFF5FF", - "pink-400": "#F966FF", - "red": "#E91C11", - "red-0": "#FBF1F0", - "red-300": "#E85D7F", - "red-400": "#DC5879", - "red-500": "#E91C11", - "red-600": "#BE1809", - "red-900": "#280503", - "sapphire": "#1C50BB", - "white": "#ffffff", - "white-100": "rgba(255,255,255,0.04)", - "white-200": "rgba(255,255,255,0.09)", - "white-300": "rgba(255,255,255,0.2)", - "white-400": "rgba(255,255,255,0.35)", - "white-500": "rgba(255,255,255,0.5)", - "white-600": "rgba(255,255,255,0.65)", - "white-700": "rgba(255,255,255,0.8)", - "yellow": "#FFD300", - "yellow-0": "#FFFAE5", - "yellow-400": "#CCA900", - "yellow-500": "#FFD300", - "yellow-900": "#211B00", - }, - "elements": { - "headerHeight": { - "base": "4rem", - "md": "5rem", - }, - "headerZ": 15, - }, - "modes": { - "dark": { - "background": "#10162F", - "background-contrast": "#000000", - "background-current": "#10162F", - "background-disabled": "rgba(255,255,255,0.09)", - "background-error": "#280503", - "background-hover": "rgba(255,255,255,0.09)", - "background-primary": "#0A0D1C", - "background-selected": "rgba(255,255,255,0.04)", - "background-success": "#151C07", - "background-warning": "#211B00", - "border-disabled": "rgba(255,255,255,0.5)", - "border-primary": "#ffffff", - "border-secondary": "rgba(255,255,255,0.65)", - "border-tertiary": "rgba(255,255,255,0.2)", - "danger": "#E85D7F", - "danger-hover": "#DC5879", - "feedback-error": "#E85D7F", - "feedback-success": "#AEE938", - "feedback-warning": "#FFFAE5", - "interface": "#FFD300", - "interface-hover": "#CCA900", - "primary": "#FFD300", - "primary-hover": "#CCA900", - "primary-inverse": "#3A10E5", - "secondary": "#ffffff", - "secondary-hover": "rgba(255,255,255,0.8)", - "shadow-primary": "#ffffff", - "shadow-secondary": "rgba(255,255,255,0.65)", - "text": "#ffffff", - "text-accent": "#FFF0E5", - "text-disabled": "rgba(255,255,255,0.5)", - "text-secondary": "rgba(255,255,255,0.65)", - }, - "light": { - "background": "#ffffff", - "background-contrast": "#ffffff", - "background-current": "#ffffff", - "background-disabled": "rgba(16,22,47,0.12)", - "background-error": "#FFF1F5", - "background-hover": "rgba(16,22,47,0.12)", - "background-primary": "#FAFBFC", - "background-selected": "rgba(16,22,47,0.04)", - "background-success": "#EEF7F3", - "background-warning": "#FFF7E0", - "border-disabled": "rgba(16,22,47,0.28)", - "border-primary": "rgba(16,22,47,0.47)", - "border-secondary": "rgba(16,22,47,0.75)", - "border-tertiary": "#10162F", - "danger": "#B83C3C", - "danger-hover": "#A52020", - "feedback-error": "#B83C3C", - "feedback-success": "#1B8057", - "feedback-warning": "#EF5B0D", - "interface": "#1C50BB", - "interface-hover": "#141C36", - "primary": "#1C50BB", - "primary-hover": "#141C36", - "primary-inverse": "#ffffff", - "secondary": "#6A6E75", - "secondary-hover": "rgba(106, 110, 117, 0.86)", - "shadow-primary": "rgba(16,22,47,0.12)", - "shadow-secondary": "rgba(16,22,47,0.47)", - "text": "#222325", - "text-accent": "#222325", - "text-disabled": "#AFB6C2", - "text-secondary": "rgba(34, 35, 37, 0.75)", - }, - }, - }, - "_variables": { - "mode": { - "--color-background": "var(--color-white)", - "--color-background-contrast": "var(--color-white)", - "--color-background-current": "var(--color-white)", - "--color-background-disabled": "var(--color-navy-200)", - "--color-background-error": "var(--color-percipioBgError)", - "--color-background-hover": "var(--color-navy-200)", - "--color-background-primary": "var(--color-percipioBgPrimary)", - "--color-background-selected": "var(--color-navy-100)", - "--color-background-success": "var(--color-percipioBgSuccess)", - "--color-background-warning": "var(--color-percipioBgWarning)", - "--color-border-disabled": "var(--color-navy-300)", - "--color-border-primary": "var(--color-navy-400)", - "--color-border-secondary": "var(--color-navy-600)", - "--color-border-tertiary": "var(--color-navy-800)", - "--color-danger": "var(--color-percipioDanger)", - "--color-danger-hover": "var(--color-percipioActionDangerHover)", - "--color-feedback-error": "var(--color-percipioDanger)", - "--color-feedback-success": "var(--color-percipioFeedbackSuccess)", - "--color-feedback-warning": "var(--color-percipioFeedbackWarning)", - "--color-interface": "var(--color-sapphire)", - "--color-interface-hover": "var(--color-percipioActionPrimaryHover)", - "--color-primary": "var(--color-sapphire)", - "--color-primary-hover": "var(--color-percipioActionPrimaryHover)", - "--color-primary-inverse": "var(--color-white)", - "--color-secondary": "var(--color-percipioActionSecondary)", - "--color-secondary-hover": "var(--color-percipioActionSecondaryHover)", - "--color-shadow-primary": "var(--color-navy-200)", - "--color-shadow-secondary": "var(--color-navy-400)", - "--color-text": "var(--color-percipioTextPrimary)", - "--color-text-accent": "var(--color-percipioTextAccent)", - "--color-text-disabled": "var(--color-percipioTextDisabled)", - "--color-text-secondary": "var(--color-percipioTextSecondary)", - }, - "root": { - "--color-beige": "#FFF0E5", - "--color-beige-100": "#FFF0E5", - "--color-black": "#000000", - "--color-blue": "#1557FF", - "--color-blue-0": "#F5FCFF", - "--color-blue-100": "#D3F2FF", - "--color-blue-300": "#66C4FF", - "--color-blue-400": "#3388FF", - "--color-blue-500": "#1557FF", - "--color-blue-800": "#1D2340", - "--color-gray-100": "#F5F5F5", - "--color-gray-200": "#EEEEEE", - "--color-gray-300": "#E0E0E0", - "--color-gray-600": "#9E9E9E", - "--color-gray-800": "#616161", - "--color-gray-900": "#424242", - "--color-green": "#008A27", - "--color-green-0": "#F5FFE3", - "--color-green-100": "#EAFDC6", - "--color-green-400": "#AEE938", - "--color-green-700": "#008A27", - "--color-green-900": "#151C07", - "--color-hyper": "#3A10E5", - "--color-hyper-400": "#5533FF", - "--color-hyper-500": "#3A10E5", - "--color-lightBlue": "#66C4FF", - "--color-lightGreen": "#AEE938", - "--color-navy": "#10162F", - "--color-navy-100": "rgba(16,22,47,0.04)", - "--color-navy-200": "rgba(16,22,47,0.12)", - "--color-navy-300": "rgba(16,22,47,0.28)", - "--color-navy-400": "rgba(16,22,47,0.47)", - "--color-navy-500": "rgba(16,22,47,0.63)", - "--color-navy-600": "rgba(16,22,47,0.75)", - "--color-navy-700": "rgba(16,22,47,0.86)", - "--color-navy-800": "#10162F", - "--color-navy-900": "#0A0D1C", - "--color-orange": "#FF8C00", - "--color-orange-100": "#FFE8CC", - "--color-orange-500": "#FF8C00", - "--color-paleBlue": "#F5FCFF", - "--color-paleGreen": "#F5FFE3", - "--color-palePink": "#FFF5FF", - "--color-paleRed": "#DC5879", - "--color-paleYellow": "#FFFAE5", - "--color-percipioActionDangerHover": "#A52020", - "--color-percipioActionPrimaryHover": "#141C36", - "--color-percipioActionSecondary": "#6A6E75", - "--color-percipioActionSecondaryHover": "rgba(106, 110, 117, 0.86)", - "--color-percipioBgError": "#FFF1F5", - "--color-percipioBgPrimary": "#FAFBFC", - "--color-percipioBgSuccess": "#EEF7F3", - "--color-percipioBgWarning": "#FFF7E0", - "--color-percipioDanger": "#B83C3C", - "--color-percipioFeedbackSuccess": "#1B8057", - "--color-percipioFeedbackWarning": "#EF5B0D", - "--color-percipioTextAccent": "#222325", - "--color-percipioTextDisabled": "#AFB6C2", - "--color-percipioTextPrimary": "#222325", - "--color-percipioTextSecondary": "rgba(34, 35, 37, 0.75)", - "--color-pink": "#F966FF", - "--color-pink-0": "#FFF5FF", - "--color-pink-400": "#F966FF", - "--color-red": "#E91C11", - "--color-red-0": "#FBF1F0", - "--color-red-300": "#E85D7F", - "--color-red-400": "#DC5879", - "--color-red-500": "#E91C11", - "--color-red-600": "#BE1809", - "--color-red-900": "#280503", - "--color-sapphire": "#1C50BB", - "--color-white": "#ffffff", - "--color-white-100": "rgba(255,255,255,0.04)", - "--color-white-200": "rgba(255,255,255,0.09)", - "--color-white-300": "rgba(255,255,255,0.2)", - "--color-white-400": "rgba(255,255,255,0.35)", - "--color-white-500": "rgba(255,255,255,0.5)", - "--color-white-600": "rgba(255,255,255,0.65)", - "--color-white-700": "rgba(255,255,255,0.8)", - "--color-yellow": "#FFD300", - "--color-yellow-0": "#FFFAE5", - "--color-yellow-400": "#CCA900", - "--color-yellow-500": "#FFD300", - "--color-yellow-900": "#211B00", - "--elements-headerHeight": "4rem", - "--elements-headerZ": 15, - "@media only screen and (min-width: 1024px)": { - "--elements-headerHeight": "5rem", - }, - }, - }, - "borderRadii": { - "full": "999px", - "lg": "8px", - "md": "4px", - "none": "0px", - "sm": "2px", - "xl": "16px", - }, - "borders": { - "1": "1px solid var(--color-border-primary)", - "2": "2px solid var(--color-border-primary)", - }, - "breakpoints": { - "c_base": "@container (min-width: 1px)", - "c_lg": "@container (min-width: 1200px)", - "c_md": "@container (min-width: 1024px)", - "c_sm": "@container (min-width: 768px)", - "c_xl": "@container (min-width: 1440px)", - "c_xs": "@container (min-width: 480px)", - "lg": "@media only screen and (min-width: 1200px)", - "md": "@media only screen and (min-width: 1024px)", - "sm": "@media only screen and (min-width: 768px)", - "xl": "@media only screen and (min-width: 1440px)", - "xs": "@media only screen and (min-width: 480px)", - }, - "colors": { - "background": "var(--color-background)", - "background-contrast": "var(--color-background-contrast)", - "background-current": "var(--color-background-current)", - "background-disabled": "var(--color-background-disabled)", - "background-error": "var(--color-background-error)", - "background-hover": "var(--color-background-hover)", - "background-primary": "var(--color-background-primary)", - "background-selected": "var(--color-background-selected)", - "background-success": "var(--color-background-success)", - "background-warning": "var(--color-background-warning)", - "beige": "var(--color-beige)", - "beige-100": "var(--color-beige-100)", - "black": "var(--color-black)", - "blue": "var(--color-blue)", - "blue-0": "var(--color-blue-0)", - "blue-100": "var(--color-blue-100)", - "blue-300": "var(--color-blue-300)", - "blue-400": "var(--color-blue-400)", - "blue-500": "var(--color-blue-500)", - "blue-800": "var(--color-blue-800)", - "border-disabled": "var(--color-border-disabled)", - "border-primary": "var(--color-border-primary)", - "border-secondary": "var(--color-border-secondary)", - "border-tertiary": "var(--color-border-tertiary)", - "danger": "var(--color-danger)", - "danger-hover": "var(--color-danger-hover)", - "feedback-error": "var(--color-feedback-error)", - "feedback-success": "var(--color-feedback-success)", - "feedback-warning": "var(--color-feedback-warning)", - "gray-100": "var(--color-gray-100)", - "gray-200": "var(--color-gray-200)", - "gray-300": "var(--color-gray-300)", - "gray-600": "var(--color-gray-600)", - "gray-800": "var(--color-gray-800)", - "gray-900": "var(--color-gray-900)", - "green": "var(--color-green)", - "green-0": "var(--color-green-0)", - "green-100": "var(--color-green-100)", - "green-400": "var(--color-green-400)", - "green-700": "var(--color-green-700)", - "green-900": "var(--color-green-900)", - "hyper": "var(--color-hyper)", - "hyper-400": "var(--color-hyper-400)", - "hyper-500": "var(--color-hyper-500)", - "interface": "var(--color-interface)", - "interface-hover": "var(--color-interface-hover)", - "lightBlue": "var(--color-lightBlue)", - "lightGreen": "var(--color-lightGreen)", - "navy": "var(--color-navy)", - "navy-100": "var(--color-navy-100)", - "navy-200": "var(--color-navy-200)", - "navy-300": "var(--color-navy-300)", - "navy-400": "var(--color-navy-400)", - "navy-500": "var(--color-navy-500)", - "navy-600": "var(--color-navy-600)", - "navy-700": "var(--color-navy-700)", - "navy-800": "var(--color-navy-800)", - "navy-900": "var(--color-navy-900)", - "orange": "var(--color-orange)", - "orange-100": "var(--color-orange-100)", - "orange-500": "var(--color-orange-500)", - "paleBlue": "var(--color-paleBlue)", - "paleGreen": "var(--color-paleGreen)", - "palePink": "var(--color-palePink)", - "paleRed": "var(--color-paleRed)", - "paleYellow": "var(--color-paleYellow)", - "percipioActionDangerHover": "var(--color-percipioActionDangerHover)", - "percipioActionPrimaryHover": "var(--color-percipioActionPrimaryHover)", - "percipioActionSecondary": "var(--color-percipioActionSecondary)", - "percipioActionSecondaryHover": "var(--color-percipioActionSecondaryHover)", - "percipioBgError": "var(--color-percipioBgError)", - "percipioBgPrimary": "var(--color-percipioBgPrimary)", - "percipioBgSuccess": "var(--color-percipioBgSuccess)", - "percipioBgWarning": "var(--color-percipioBgWarning)", - "percipioDanger": "var(--color-percipioDanger)", - "percipioFeedbackSuccess": "var(--color-percipioFeedbackSuccess)", - "percipioFeedbackWarning": "var(--color-percipioFeedbackWarning)", - "percipioTextAccent": "var(--color-percipioTextAccent)", - "percipioTextDisabled": "var(--color-percipioTextDisabled)", - "percipioTextPrimary": "var(--color-percipioTextPrimary)", - "percipioTextSecondary": "var(--color-percipioTextSecondary)", - "pink": "var(--color-pink)", - "pink-0": "var(--color-pink-0)", - "pink-400": "var(--color-pink-400)", - "primary": "var(--color-primary)", - "primary-hover": "var(--color-primary-hover)", - "primary-inverse": "var(--color-primary-inverse)", - "red": "var(--color-red)", - "red-0": "var(--color-red-0)", - "red-300": "var(--color-red-300)", - "red-400": "var(--color-red-400)", - "red-500": "var(--color-red-500)", - "red-600": "var(--color-red-600)", - "red-900": "var(--color-red-900)", - "sapphire": "var(--color-sapphire)", - "secondary": "var(--color-secondary)", - "secondary-hover": "var(--color-secondary-hover)", - "shadow-primary": "var(--color-shadow-primary)", - "shadow-secondary": "var(--color-shadow-secondary)", - "text": "var(--color-text)", - "text-accent": "var(--color-text-accent)", - "text-disabled": "var(--color-text-disabled)", - "text-secondary": "var(--color-text-secondary)", - "white": "var(--color-white)", - "white-100": "var(--color-white-100)", - "white-200": "var(--color-white-200)", - "white-300": "var(--color-white-300)", - "white-400": "var(--color-white-400)", - "white-500": "var(--color-white-500)", - "white-600": "var(--color-white-600)", - "white-700": "var(--color-white-700)", - "yellow": "var(--color-yellow)", - "yellow-0": "var(--color-yellow-0)", - "yellow-400": "var(--color-yellow-400)", - "yellow-500": "var(--color-yellow-500)", - "yellow-900": "var(--color-yellow-900)", + "contentModes": { + "black": "dark", + "palette-accent-100": "light", + "palette-accent-1000": "dark", + "palette-accent-1100": "dark", + "palette-accent-200": "light", + "palette-accent-300": "light", + "palette-accent-400": "light", + "palette-accent-500": "light", + "palette-accent-600": "dark", + "palette-accent-700": "dark", + "palette-accent-800": "dark", + "palette-accent-900": "dark", + "palette-accent-alt-100": "light", + "palette-accent-alt-1000": "dark", + "palette-accent-alt-1100": "dark", + "palette-accent-alt-200": "light", + "palette-accent-alt-300": "light", + "palette-accent-alt-400": "light", + "palette-accent-alt-500": "light", + "palette-accent-alt-600": "dark", + "palette-accent-alt-700": "dark", + "palette-accent-alt-800": "dark", + "palette-accent-alt-900": "dark", + "palette-danger-100": "light", + "palette-danger-1000": "dark", + "palette-danger-1100": "dark", + "palette-danger-200": "light", + "palette-danger-300": "light", + "palette-danger-400": "light", + "palette-danger-500": "light", + "palette-danger-600": "dark", + "palette-danger-700": "dark", + "palette-danger-800": "dark", + "palette-danger-900": "dark", + "palette-neutral-dark-100": "light", + "palette-neutral-dark-1000": "dark", + "palette-neutral-dark-1100": "dark", + "palette-neutral-dark-200": "light", + "palette-neutral-dark-300": "light", + "palette-neutral-dark-400": "light", + "palette-neutral-dark-500": "light", + "palette-neutral-dark-600": "light", + "palette-neutral-dark-700": "dark", + "palette-neutral-dark-800": "dark", + "palette-neutral-dark-900": "dark", + "palette-neutral-light-100": "light", + "palette-neutral-light-1000": "dark", + "palette-neutral-light-1100": "dark", + "palette-neutral-light-200": "light", + "palette-neutral-light-300": "light", + "palette-neutral-light-400": "light", + "palette-neutral-light-500": "light", + "palette-neutral-light-600": "light", + "palette-neutral-light-700": "dark", + "palette-neutral-light-800": "dark", + "palette-neutral-light-900": "dark", + "palette-primary-100": "light", + "palette-primary-1000": "dark", + "palette-primary-1100": "dark", + "palette-primary-200": "light", + "palette-primary-300": "light", + "palette-primary-400": "light", + "palette-primary-500": "light", + "palette-primary-600": "dark", + "palette-primary-700": "dark", + "palette-primary-800": "dark", + "palette-primary-900": "dark", + "palette-success-100": "light", + "palette-success-1000": "dark", + "palette-success-1100": "dark", + "palette-success-200": "light", + "palette-success-300": "light", + "palette-success-400": "light", + "palette-success-500": "light", + "palette-success-600": "light", + "palette-success-700": "dark", + "palette-success-800": "dark", + "palette-success-900": "dark", + "palette-warning-100": "light", + "palette-warning-1000": "dark", + "palette-warning-1100": "dark", + "palette-warning-200": "light", + "palette-warning-300": "light", + "palette-warning-400": "light", + "palette-warning-500": "light", + "palette-warning-600": "light", + "palette-warning-700": "dark", + "palette-warning-800": "dark", + "palette-warning-900": "dark", + "white": "light", }, "elements": { "headerHeight": "var(--elements-headerHeight)", "headerZ": "var(--elements-headerZ)", }, "elevation": { - "hover-shadow": "0 1px 4px 0 var(--color-shadow-primary), 0 2px 11px 0 var(--color-shadow-secondary)", + "hover-shadow": "0 2px 6px 0 var(--color-shadow-key), 0 6px 16px 0 var(--color-shadow-ambient)", "hover-transform": "none", - "hoverMirrored-shadow": "0 1px 4px 0 var(--color-shadow-primary), 0 2px 11px 0 var(--color-shadow-secondary)", + "hoverMirrored-shadow": "0 2px 6px 0 var(--color-shadow-key), 0 6px 16px 0 var(--color-shadow-ambient)", "hoverMirrored-transform": "none", - "rest-shadow": "0 1px 4px 0 var(--color-shadow-primary), 0 2px 7px 0 var(--color-shadow-primary)", + "rest-shadow": "0 1px 4px 0 var(--color-shadow-key), 0 2px 7px 0 var(--color-shadow-ambient)", "rest-transform": "none", }, "fontFamily": { @@ -2070,75 +580,103 @@ sans-serif", "mode": "light", "modes": { "dark": { - "background": "navy-800", - "background-contrast": "black", - "background-current": "navy-800", - "background-disabled": "white-200", - "background-error": "red-900", - "background-hover": "white-200", - "background-primary": "navy-900", - "background-selected": "white-100", - "background-success": "green-900", - "background-warning": "yellow-900", - "border-disabled": "white-500", - "border-primary": "white", - "border-secondary": "white-600", - "border-tertiary": "white-300", - "danger": "red-300", - "danger-hover": "red-400", - "feedback-error": "red-300", - "feedback-success": "green-400", - "feedback-warning": "yellow-0", - "interface": "yellow-500", - "interface-hover": "yellow-400", - "primary": "yellow-500", - "primary-hover": "yellow-400", - "primary-inverse": "hyper-500", - "secondary": "white", - "secondary-hover": "white-700", - "shadow-primary": "white", - "shadow-secondary": "white-600", - "text": "white", - "text-accent": "beige", - "text-disabled": "white-500", - "text-secondary": "white-600", + "action-danger": "palette-danger-400", + "action-danger-hover": "palette-danger-300", + "action-danger-pressed": "palette-danger-200", + "action-disabled": "palette-neutral-dark-800", + "action-neutral": "palette-neutral-light-200", + "action-neutral-hover": "palette-neutral-light-100", + "action-neutral-pressed": "white", + "action-primary": "palette-primary-400", + "action-primary-hover": "palette-primary-300", + "action-primary-pressed": "palette-primary-200", + "background-current": "palette-neutral-dark-1100", + "border-default": "palette-neutral-light-600", + "border-strong": "palette-neutral-light-100", + "border-subtle": "palette-neutral-light-900", + "shadow-ambient": "black-a24", + "shadow-key": "black-a32", + "shadow-scrim": "black-a60", + "state-hover": "palette-primary-600-a6", + "state-pressed": "palette-primary-600-a9", + "state-selected": "palette-primary-600-a8", + "surface-brand": "palette-neutral-dark-1100", + "surface-brand-subtle": "palette-neutral-dark-1100", + "surface-default": "palette-neutral-dark-1100", + "surface-error": "palette-danger-1000", + "surface-raised": "palette-neutral-dark-1000", + "surface-success": "palette-success-1000", + "surface-sunken": "black", + "surface-warning": "palette-warning-1000", + "text-default": "palette-neutral-light-200", + "text-error": "palette-danger-300", + "text-inverse": "palette-neutral-dark-1000", + "text-subtle": "palette-neutral-light-400", + "text-success": "palette-success-300", + "text-warning": "palette-warning-300", + "viz-1": "palette-primary-500", + "viz-2": "palette-accent-500", + "viz-3": "palette-warning-400", + "viz-4": "palette-accent-alt-600", + "viz-5": "palette-success-300", + "viz-6": "palette-danger-600", + "viz-7": "palette-neutral-dark-500", + "viz-8": "palette-neutral-light-600", + "viz-seq-1": "palette-primary-600", + "viz-seq-2": "palette-primary-500", + "viz-seq-3": "palette-primary-400", + "viz-seq-4": "palette-primary-300", }, "light": { - "background": "white", - "background-contrast": "white", - "background-current": "white", - "background-disabled": "navy-200", - "background-error": "percipioBgError", - "background-hover": "navy-200", - "background-primary": "percipioBgPrimary", - "background-selected": "navy-100", - "background-success": "percipioBgSuccess", - "background-warning": "percipioBgWarning", - "border-disabled": "navy-300", - "border-primary": "navy-400", - "border-secondary": "navy-600", - "border-tertiary": "navy-800", - "danger": "percipioDanger", - "danger-hover": "percipioActionDangerHover", - "feedback-error": "percipioDanger", - "feedback-success": "percipioFeedbackSuccess", - "feedback-warning": "percipioFeedbackWarning", - "interface": "sapphire", - "interface-hover": "percipioActionPrimaryHover", - "primary": "sapphire", - "primary-hover": "percipioActionPrimaryHover", - "primary-inverse": "white", - "secondary": "percipioActionSecondary", - "secondary-hover": "percipioActionSecondaryHover", - "shadow-primary": "navy-200", - "shadow-secondary": "navy-400", - "text": "percipioTextPrimary", - "text-accent": "percipioTextAccent", - "text-disabled": "percipioTextDisabled", - "text-secondary": "percipioTextSecondary", - }, - }, - "name": "percipio", + "action-danger": "palette-danger-800", + "action-danger-hover": "palette-danger-900", + "action-danger-pressed": "palette-danger-1000", + "action-disabled": "palette-neutral-light-400", + "action-neutral": "palette-neutral-dark-1000", + "action-neutral-hover": "palette-neutral-dark-1100", + "action-neutral-pressed": "black", + "action-primary": "palette-primary-800", + "action-primary-hover": "palette-primary-900", + "action-primary-pressed": "palette-primary-1000", + "background-current": "palette-neutral-light-100", + "border-default": "palette-neutral-dark-600", + "border-strong": "palette-neutral-dark-1100", + "border-subtle": "palette-neutral-dark-300", + "shadow-ambient": "palette-neutral-dark-1100-a12", + "shadow-key": "palette-neutral-dark-1100-a16", + "shadow-scrim": "black-a60", + "state-hover": "palette-primary-600-a6", + "state-pressed": "palette-primary-600-a9", + "state-selected": "palette-primary-600-a8", + "surface-brand": "palette-neutral-light-100", + "surface-brand-subtle": "palette-neutral-light-100", + "surface-default": "palette-neutral-light-100", + "surface-error": "palette-danger-200", + "surface-raised": "white", + "surface-success": "palette-success-200", + "surface-sunken": "palette-neutral-light-200", + "surface-warning": "palette-warning-200", + "text-default": "palette-neutral-dark-1000", + "text-error": "palette-danger-900", + "text-inverse": "palette-neutral-light-200", + "text-subtle": "palette-neutral-dark-800", + "text-success": "palette-success-900", + "text-warning": "palette-warning-900", + "viz-1": "palette-primary-800", + "viz-2": "palette-accent-700", + "viz-3": "palette-warning-700", + "viz-4": "palette-accent-alt-600", + "viz-5": "palette-success-600", + "viz-6": "palette-danger-600", + "viz-7": "palette-neutral-dark-1000", + "viz-8": "palette-neutral-light-600", + "viz-seq-1": "palette-primary-600", + "viz-seq-2": "palette-primary-700", + "viz-seq-3": "palette-primary-800", + "viz-seq-4": "palette-primary-900", + }, + }, + "name": "skillsoft", "spacing": { "0": 0, "12": "0.75rem", @@ -2152,745 +690,217 @@ sans-serif", "8": "0.5rem", "96": "6rem", }, -} -`; + "tokensCss": "/* Generated by @skillsoft/gamut-brand from the "skillsoft" brand. Do not edit. */ +@layer reset, base, tokens, recipes, utilities; -exports[`themes platform - theme shape 1`] = ` -{ - "_getColorValue": [Function], - "_tokens": { - "colors": { - "beige": "#FFF0E5", - "beige-0": "#FFFBF8", - "beige-100": "#FFF0E5", - "black": "#000000", - "blue": "#1557FF", - "blue-0": "#F5FCFF", - "blue-100": "#D3F2FF", - "blue-300": "#66C4FF", - "blue-400": "#3388FF", - "blue-500": "#1557FF", - "blue-800": "#1D2340", - "comment-dark": "#84868D", - "comment-light": "#686C7B", - "gold": "#8A7300", - "gold-800": "#8A7300", - "gray-100": "#F5F5F5", - "gray-200": "#EEEEEE", - "gray-300": "#E0E0E0", - "gray-600": "#9E9E9E", - "gray-800": "#616161", - "gray-900": "#424242", - "green": "#008A27", - "green-0": "#F5FFE3", - "green-100": "#EAFDC6", - "green-400": "#AEE938", - "green-700": "#008A27", - "green-900": "#151C07", - "hyper": "#3A10E5", - "hyper-400": "#5533FF", - "hyper-500": "#3A10E5", - "indent-active-dark": "#3B3D49", - "indent-active-light": "#BCBDC4", - "indent-inactive-dark": "#5F616B", - "indent-inactive-light": "#8E919D", - "lightBeige": "#FFFBF8", - "lightBlue": "#66C4FF", - "lightGreen": "#AEE938", - "line-number-active-dark": "#CECFD2", - "line-number-active-light": "#31374C", - "line-number-inactive-dark": "#84868D", - "line-number-inactive-light": "#686C7B", - "navy": "#10162F", - "navy-100": "rgba(16,22,47,0.04)", - "navy-200": "rgba(16,22,47,0.12)", - "navy-300": "rgba(16,22,47,0.28)", - "navy-400": "rgba(16,22,47,0.47)", - "navy-500": "rgba(16,22,47,0.63)", - "navy-600": "rgba(16,22,47,0.75)", - "navy-700": "rgba(16,22,47,0.86)", - "navy-800": "#10162F", - "navy-900": "#0A0D1C", - "orange": "#FF8C00", - "orange-100": "#FFE8CC", - "orange-500": "#FF8C00", - "orange-800": "#D14900", - "paleBlue": "#F5FCFF", - "paleGreen": "#F5FFE3", - "palePink": "#FFF5FF", - "paleRed": "#DC5879", - "paleYellow": "#FFFAE5", - "pink": "#F966FF", - "pink-0": "#FFF5FF", - "pink-400": "#F966FF", - "pink-800": "#CA00D1", - "purple": "#B3CCFF", - "purple-300": "#B3CCFF", - "red": "#E91C11", - "red-0": "#FBF1F0", - "red-300": "#E85D7F", - "red-400": "#DC5879", - "red-500": "#E91C11", - "red-600": "#BE1809", - "red-900": "#280503", - "teal": "#006D82", - "teal-500": "#006D82", - "white": "#ffffff", - "white-100": "rgba(255,255,255,0.04)", - "white-200": "rgba(255,255,255,0.09)", - "white-300": "rgba(255,255,255,0.2)", - "white-400": "rgba(255,255,255,0.35)", - "white-500": "rgba(255,255,255,0.5)", - "white-600": "rgba(255,255,255,0.65)", - "white-700": "rgba(255,255,255,0.8)", - "yellow": "#FFD300", - "yellow-0": "#FFFAE5", - "yellow-400": "#CCA900", - "yellow-500": "#FFD300", - "yellow-900": "#211B00", - }, - "elements": { - "headerHeight": { - "base": "4rem", - "md": "5rem", - }, - "headerZ": 15, - }, - "modes": { - "dark": { - "background": "#10162F", - "background-contrast": "#000000", - "background-current": "#10162F", - "background-disabled": "rgba(255,255,255,0.09)", - "background-error": "#280503", - "background-hover": "rgba(255,255,255,0.09)", - "background-primary": "#0A0D1C", - "background-selected": "rgba(255,255,255,0.04)", - "background-success": "#151C07", - "background-warning": "#211B00", - "border-disabled": "rgba(255,255,255,0.5)", - "border-primary": "#ffffff", - "border-secondary": "rgba(255,255,255,0.65)", - "border-tertiary": "rgba(255,255,255,0.2)", - "danger": "#E85D7F", - "danger-hover": "#DC5879", - "editor-annotation": "#E85D7F", - "editor-atom": "#F966FF", - "editor-attribute": "#AEE938", - "editor-basic": "#ffffff", - "editor-comment": "#84868D", - "editor-constant": "#FF8C00", - "editor-decoration": "#E85D7F", - "editor-invalid": "#E85D7F", - "editor-key": "#66C4FF", - "editor-keyword": "#B3CCFF", - "editor-number": "#E85D7F", - "editor-operator": "#E85D7F", - "editor-predefined": "#ffffff", - "editor-property": "#E85D7F", - "editor-regexp": "#AEE938", - "editor-string": "#FFD300", - "editor-tag": "#E85D7F", - "editor-text": "#FF8C00", - "editor-ui-background": "#0A0D1C", - "editor-ui-indent-active": "#3B3D49", - "editor-ui-indent-inactive": "#5F616B", - "editor-ui-line-number-active": "#CECFD2", - "editor-ui-line-number-inactive": "#84868D", - "editor-ui-text": "#ffffff", - "editor-value": "#FFD300", - "editor-variable": "#AEE938", - "feedback-error": "#E85D7F", - "feedback-success": "#AEE938", - "feedback-warning": "#FFFAE5", - "interface": "#FFD300", - "interface-hover": "#CCA900", - "primary": "#FFD300", - "primary-hover": "#CCA900", - "primary-inverse": "#3A10E5", - "secondary": "#ffffff", - "secondary-hover": "rgba(255,255,255,0.8)", - "shadow-primary": "#ffffff", - "shadow-secondary": "rgba(255,255,255,0.65)", - "text": "#ffffff", - "text-accent": "#FFF0E5", - "text-disabled": "rgba(255,255,255,0.5)", - "text-secondary": "rgba(255,255,255,0.65)", - }, - "light": { - "background": "#ffffff", - "background-contrast": "#ffffff", - "background-current": "#ffffff", - "background-disabled": "rgba(16,22,47,0.12)", - "background-error": "#FBF1F0", - "background-hover": "rgba(16,22,47,0.12)", - "background-primary": "#F5FCFF", - "background-selected": "rgba(16,22,47,0.04)", - "background-success": "#F5FFE3", - "background-warning": "#FFFAE5", - "border-disabled": "rgba(16,22,47,0.63)", - "border-primary": "#10162F", - "border-secondary": "rgba(16,22,47,0.75)", - "border-tertiary": "rgba(16,22,47,0.28)", - "danger": "#E91C11", - "danger-hover": "#BE1809", - "editor-annotation": "#E91C11", - "editor-atom": "#CA00D1", - "editor-attribute": "#008A27", - "editor-basic": "#10162F", - "editor-comment": "#686C7B", - "editor-constant": "#D14900", - "editor-decoration": "#E91C11", - "editor-invalid": "#E91C11", - "editor-key": "#006D82", - "editor-keyword": "#1557FF", - "editor-number": "#E91C11", - "editor-operator": "#E91C11", - "editor-predefined": "#10162F", - "editor-property": "#E91C11", - "editor-regexp": "#008A27", - "editor-string": "#8A7300", - "editor-tag": "#E91C11", - "editor-text": "#D14900", - "editor-ui-background": "#ffffff", - "editor-ui-indent-active": "#BCBDC4", - "editor-ui-indent-inactive": "#8E919D", - "editor-ui-line-number-active": "#31374C", - "editor-ui-line-number-inactive": "#686C7B", - "editor-ui-text": "#10162F", - "editor-value": "#8A7300", - "editor-variable": "#008A27", - "feedback-error": "#BE1809", - "feedback-success": "#008A27", - "feedback-warning": "#FFD300", - "interface": "#3A10E5", - "interface-hover": "#5533FF", - "primary": "#3A10E5", - "primary-hover": "#5533FF", - "primary-inverse": "#FFD300", - "secondary": "#10162F", - "secondary-hover": "rgba(16,22,47,0.86)", - "shadow-primary": "#10162F", - "shadow-secondary": "rgba(16,22,47,0.75)", - "text": "#10162F", - "text-accent": "#0A0D1C", - "text-disabled": "rgba(16,22,47,0.63)", - "text-secondary": "rgba(16,22,47,0.75)", - }, - }, - }, - "_variables": { - "mode": { - "--color-background": "var(--color-navy-800)", - "--color-background-contrast": "var(--color-black)", - "--color-background-current": "var(--color-navy-800)", - "--color-background-disabled": "var(--color-white-200)", - "--color-background-error": "var(--color-red-900)", - "--color-background-hover": "var(--color-white-200)", - "--color-background-primary": "var(--color-navy-900)", - "--color-background-selected": "var(--color-white-100)", - "--color-background-success": "var(--color-green-900)", - "--color-background-warning": "var(--color-yellow-900)", - "--color-border-disabled": "var(--color-white-500)", - "--color-border-primary": "var(--color-white)", - "--color-border-secondary": "var(--color-white-600)", - "--color-border-tertiary": "var(--color-white-300)", - "--color-danger": "var(--color-red-300)", - "--color-danger-hover": "var(--color-red-400)", - "--color-editor-annotation": "var(--color-red-300)", - "--color-editor-atom": "var(--color-pink-400)", - "--color-editor-attribute": "var(--color-green-400)", - "--color-editor-basic": "var(--color-white)", - "--color-editor-comment": "var(--color-comment-dark)", - "--color-editor-constant": "var(--color-orange-500)", - "--color-editor-decoration": "var(--color-red-300)", - "--color-editor-invalid": "var(--color-red-300)", - "--color-editor-key": "var(--color-blue-300)", - "--color-editor-keyword": "var(--color-purple-300)", - "--color-editor-number": "var(--color-red-300)", - "--color-editor-operator": "var(--color-red-300)", - "--color-editor-predefined": "var(--color-white)", - "--color-editor-property": "var(--color-red-300)", - "--color-editor-regexp": "var(--color-green-400)", - "--color-editor-string": "var(--color-yellow-500)", - "--color-editor-tag": "var(--color-red-300)", - "--color-editor-text": "var(--color-orange-500)", - "--color-editor-ui-background": "var(--color-navy-900)", - "--color-editor-ui-indent-active": "var(--color-indent-active-dark)", - "--color-editor-ui-indent-inactive": "var(--color-indent-inactive-dark)", - "--color-editor-ui-line-number-active": "var(--color-line-number-active-dark)", - "--color-editor-ui-line-number-inactive": "var(--color-line-number-inactive-dark)", - "--color-editor-ui-text": "var(--color-white)", - "--color-editor-value": "var(--color-yellow-500)", - "--color-editor-variable": "var(--color-green-400)", - "--color-feedback-error": "var(--color-red-300)", - "--color-feedback-success": "var(--color-green-400)", - "--color-feedback-warning": "var(--color-yellow-0)", - "--color-interface": "var(--color-yellow-500)", - "--color-interface-hover": "var(--color-yellow-400)", - "--color-primary": "var(--color-yellow-500)", - "--color-primary-hover": "var(--color-yellow-400)", - "--color-primary-inverse": "var(--color-hyper-500)", - "--color-secondary": "var(--color-white)", - "--color-secondary-hover": "var(--color-white-700)", - "--color-shadow-primary": "var(--color-white)", - "--color-shadow-secondary": "var(--color-white-600)", - "--color-text": "var(--color-white)", - "--color-text-accent": "var(--color-beige)", - "--color-text-disabled": "var(--color-white-500)", - "--color-text-secondary": "var(--color-white-600)", - }, - "root": { - "--color-beige": "#FFF0E5", - "--color-beige-0": "#FFFBF8", - "--color-beige-100": "#FFF0E5", - "--color-black": "#000000", - "--color-blue": "#1557FF", - "--color-blue-0": "#F5FCFF", - "--color-blue-100": "#D3F2FF", - "--color-blue-300": "#66C4FF", - "--color-blue-400": "#3388FF", - "--color-blue-500": "#1557FF", - "--color-blue-800": "#1D2340", - "--color-comment-dark": "#84868D", - "--color-comment-light": "#686C7B", - "--color-gold": "#8A7300", - "--color-gold-800": "#8A7300", - "--color-gray-100": "#F5F5F5", - "--color-gray-200": "#EEEEEE", - "--color-gray-300": "#E0E0E0", - "--color-gray-600": "#9E9E9E", - "--color-gray-800": "#616161", - "--color-gray-900": "#424242", - "--color-green": "#008A27", - "--color-green-0": "#F5FFE3", - "--color-green-100": "#EAFDC6", - "--color-green-400": "#AEE938", - "--color-green-700": "#008A27", - "--color-green-900": "#151C07", - "--color-hyper": "#3A10E5", - "--color-hyper-400": "#5533FF", - "--color-hyper-500": "#3A10E5", - "--color-indent-active-dark": "#3B3D49", - "--color-indent-active-light": "#BCBDC4", - "--color-indent-inactive-dark": "#5F616B", - "--color-indent-inactive-light": "#8E919D", - "--color-lightBeige": "#FFFBF8", - "--color-lightBlue": "#66C4FF", - "--color-lightGreen": "#AEE938", - "--color-line-number-active-dark": "#CECFD2", - "--color-line-number-active-light": "#31374C", - "--color-line-number-inactive-dark": "#84868D", - "--color-line-number-inactive-light": "#686C7B", - "--color-navy": "#10162F", - "--color-navy-100": "rgba(16,22,47,0.04)", - "--color-navy-200": "rgba(16,22,47,0.12)", - "--color-navy-300": "rgba(16,22,47,0.28)", - "--color-navy-400": "rgba(16,22,47,0.47)", - "--color-navy-500": "rgba(16,22,47,0.63)", - "--color-navy-600": "rgba(16,22,47,0.75)", - "--color-navy-700": "rgba(16,22,47,0.86)", - "--color-navy-800": "#10162F", - "--color-navy-900": "#0A0D1C", - "--color-orange": "#FF8C00", - "--color-orange-100": "#FFE8CC", - "--color-orange-500": "#FF8C00", - "--color-orange-800": "#D14900", - "--color-paleBlue": "#F5FCFF", - "--color-paleGreen": "#F5FFE3", - "--color-palePink": "#FFF5FF", - "--color-paleRed": "#DC5879", - "--color-paleYellow": "#FFFAE5", - "--color-pink": "#F966FF", - "--color-pink-0": "#FFF5FF", - "--color-pink-400": "#F966FF", - "--color-pink-800": "#CA00D1", - "--color-purple": "#B3CCFF", - "--color-purple-300": "#B3CCFF", - "--color-red": "#E91C11", - "--color-red-0": "#FBF1F0", - "--color-red-300": "#E85D7F", - "--color-red-400": "#DC5879", - "--color-red-500": "#E91C11", - "--color-red-600": "#BE1809", - "--color-red-900": "#280503", - "--color-teal": "#006D82", - "--color-teal-500": "#006D82", - "--color-white": "#ffffff", - "--color-white-100": "rgba(255,255,255,0.04)", - "--color-white-200": "rgba(255,255,255,0.09)", - "--color-white-300": "rgba(255,255,255,0.2)", - "--color-white-400": "rgba(255,255,255,0.35)", - "--color-white-500": "rgba(255,255,255,0.5)", - "--color-white-600": "rgba(255,255,255,0.65)", - "--color-white-700": "rgba(255,255,255,0.8)", - "--color-yellow": "#FFD300", - "--color-yellow-0": "#FFFAE5", - "--color-yellow-400": "#CCA900", - "--color-yellow-500": "#FFD300", - "--color-yellow-900": "#211B00", - "--elements-headerHeight": "4rem", - "--elements-headerZ": 15, - "@media only screen and (min-width: 1024px)": { - "--elements-headerHeight": "5rem", - }, - }, - }, - "borderRadii": { - "full": "999px", - "lg": "8px", - "md": "4px", - "none": "0px", - "sm": "2px", - "xl": "16px", - }, - "borders": { - "1": "1px solid var(--color-border-primary)", - "2": "2px solid var(--color-border-primary)", - }, - "breakpoints": { - "c_base": "@container (min-width: 1px)", - "c_lg": "@container (min-width: 1200px)", - "c_md": "@container (min-width: 1024px)", - "c_sm": "@container (min-width: 768px)", - "c_xl": "@container (min-width: 1440px)", - "c_xs": "@container (min-width: 480px)", - "lg": "@media only screen and (min-width: 1200px)", - "md": "@media only screen and (min-width: 1024px)", - "sm": "@media only screen and (min-width: 768px)", - "xl": "@media only screen and (min-width: 1440px)", - "xs": "@media only screen and (min-width: 480px)", - }, - "colors": { - "background": "var(--color-background)", - "background-contrast": "var(--color-background-contrast)", - "background-current": "var(--color-background-current)", - "background-disabled": "var(--color-background-disabled)", - "background-error": "var(--color-background-error)", - "background-hover": "var(--color-background-hover)", - "background-primary": "var(--color-background-primary)", - "background-selected": "var(--color-background-selected)", - "background-success": "var(--color-background-success)", - "background-warning": "var(--color-background-warning)", - "beige": "var(--color-beige)", - "beige-0": "var(--color-beige-0)", - "beige-100": "var(--color-beige-100)", - "black": "var(--color-black)", - "blue": "var(--color-blue)", - "blue-0": "var(--color-blue-0)", - "blue-100": "var(--color-blue-100)", - "blue-300": "var(--color-blue-300)", - "blue-400": "var(--color-blue-400)", - "blue-500": "var(--color-blue-500)", - "blue-800": "var(--color-blue-800)", - "border-disabled": "var(--color-border-disabled)", - "border-primary": "var(--color-border-primary)", - "border-secondary": "var(--color-border-secondary)", - "border-tertiary": "var(--color-border-tertiary)", - "comment-dark": "var(--color-comment-dark)", - "comment-light": "var(--color-comment-light)", - "danger": "var(--color-danger)", - "danger-hover": "var(--color-danger-hover)", - "editor-annotation": "var(--color-editor-annotation)", - "editor-atom": "var(--color-editor-atom)", - "editor-attribute": "var(--color-editor-attribute)", - "editor-basic": "var(--color-editor-basic)", - "editor-comment": "var(--color-editor-comment)", - "editor-constant": "var(--color-editor-constant)", - "editor-decoration": "var(--color-editor-decoration)", - "editor-invalid": "var(--color-editor-invalid)", - "editor-key": "var(--color-editor-key)", - "editor-keyword": "var(--color-editor-keyword)", - "editor-number": "var(--color-editor-number)", - "editor-operator": "var(--color-editor-operator)", - "editor-predefined": "var(--color-editor-predefined)", - "editor-property": "var(--color-editor-property)", - "editor-regexp": "var(--color-editor-regexp)", - "editor-string": "var(--color-editor-string)", - "editor-tag": "var(--color-editor-tag)", - "editor-text": "var(--color-editor-text)", - "editor-ui-background": "var(--color-editor-ui-background)", - "editor-ui-indent-active": "var(--color-editor-ui-indent-active)", - "editor-ui-indent-inactive": "var(--color-editor-ui-indent-inactive)", - "editor-ui-line-number-active": "var(--color-editor-ui-line-number-active)", - "editor-ui-line-number-inactive": "var(--color-editor-ui-line-number-inactive)", - "editor-ui-text": "var(--color-editor-ui-text)", - "editor-value": "var(--color-editor-value)", - "editor-variable": "var(--color-editor-variable)", - "feedback-error": "var(--color-feedback-error)", - "feedback-success": "var(--color-feedback-success)", - "feedback-warning": "var(--color-feedback-warning)", - "gold": "var(--color-gold)", - "gold-800": "var(--color-gold-800)", - "gray-100": "var(--color-gray-100)", - "gray-200": "var(--color-gray-200)", - "gray-300": "var(--color-gray-300)", - "gray-600": "var(--color-gray-600)", - "gray-800": "var(--color-gray-800)", - "gray-900": "var(--color-gray-900)", - "green": "var(--color-green)", - "green-0": "var(--color-green-0)", - "green-100": "var(--color-green-100)", - "green-400": "var(--color-green-400)", - "green-700": "var(--color-green-700)", - "green-900": "var(--color-green-900)", - "hyper": "var(--color-hyper)", - "hyper-400": "var(--color-hyper-400)", - "hyper-500": "var(--color-hyper-500)", - "indent-active-dark": "var(--color-indent-active-dark)", - "indent-active-light": "var(--color-indent-active-light)", - "indent-inactive-dark": "var(--color-indent-inactive-dark)", - "indent-inactive-light": "var(--color-indent-inactive-light)", - "interface": "var(--color-interface)", - "interface-hover": "var(--color-interface-hover)", - "lightBeige": "var(--color-lightBeige)", - "lightBlue": "var(--color-lightBlue)", - "lightGreen": "var(--color-lightGreen)", - "line-number-active-dark": "var(--color-line-number-active-dark)", - "line-number-active-light": "var(--color-line-number-active-light)", - "line-number-inactive-dark": "var(--color-line-number-inactive-dark)", - "line-number-inactive-light": "var(--color-line-number-inactive-light)", - "navy": "var(--color-navy)", - "navy-100": "var(--color-navy-100)", - "navy-200": "var(--color-navy-200)", - "navy-300": "var(--color-navy-300)", - "navy-400": "var(--color-navy-400)", - "navy-500": "var(--color-navy-500)", - "navy-600": "var(--color-navy-600)", - "navy-700": "var(--color-navy-700)", - "navy-800": "var(--color-navy-800)", - "navy-900": "var(--color-navy-900)", - "orange": "var(--color-orange)", - "orange-100": "var(--color-orange-100)", - "orange-500": "var(--color-orange-500)", - "orange-800": "var(--color-orange-800)", - "paleBlue": "var(--color-paleBlue)", - "paleGreen": "var(--color-paleGreen)", - "palePink": "var(--color-palePink)", - "paleRed": "var(--color-paleRed)", - "paleYellow": "var(--color-paleYellow)", - "pink": "var(--color-pink)", - "pink-0": "var(--color-pink-0)", - "pink-400": "var(--color-pink-400)", - "pink-800": "var(--color-pink-800)", - "primary": "var(--color-primary)", - "primary-hover": "var(--color-primary-hover)", - "primary-inverse": "var(--color-primary-inverse)", - "purple": "var(--color-purple)", - "purple-300": "var(--color-purple-300)", - "red": "var(--color-red)", - "red-0": "var(--color-red-0)", - "red-300": "var(--color-red-300)", - "red-400": "var(--color-red-400)", - "red-500": "var(--color-red-500)", - "red-600": "var(--color-red-600)", - "red-900": "var(--color-red-900)", - "secondary": "var(--color-secondary)", - "secondary-hover": "var(--color-secondary-hover)", - "shadow-primary": "var(--color-shadow-primary)", - "shadow-secondary": "var(--color-shadow-secondary)", - "teal": "var(--color-teal)", - "teal-500": "var(--color-teal-500)", - "text": "var(--color-text)", - "text-accent": "var(--color-text-accent)", - "text-disabled": "var(--color-text-disabled)", - "text-secondary": "var(--color-text-secondary)", - "white": "var(--color-white)", - "white-100": "var(--color-white-100)", - "white-200": "var(--color-white-200)", - "white-300": "var(--color-white-300)", - "white-400": "var(--color-white-400)", - "white-500": "var(--color-white-500)", - "white-600": "var(--color-white-600)", - "white-700": "var(--color-white-700)", - "yellow": "var(--color-yellow)", - "yellow-0": "var(--color-yellow-0)", - "yellow-400": "var(--color-yellow-400)", - "yellow-500": "var(--color-yellow-500)", - "yellow-900": "var(--color-yellow-900)", - }, - "elements": { - "headerHeight": "var(--elements-headerHeight)", - "headerZ": "var(--elements-headerZ)", - }, - "elevation": { - "hover-shadow": "-8px 8px 0 0 var(--color-shadow-primary)", - "hover-transform": "translate(4px, -4px)", - "hoverMirrored-shadow": "8px 8px 0 0 var(--color-shadow-primary)", - "hoverMirrored-transform": "translate(-4px, -4px)", - "rest-shadow": "0 0 0 0 var(--color-shadow-primary)", - "rest-transform": "none", - }, - "fontFamily": { - "accent": ""Suisse", "Apercu", -apple-system, BlinkMacSystemFont, -"Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", -"Helvetica Neue", sans-serif", - "base": ""Apercu", -apple-system, BlinkMacSystemFont, "Segoe UI", -"Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", -sans-serif", - "monospace": "Monaco, Menlo, "Ubuntu Mono", "Droid Sans Mono", Consolas, -monospace", - "system": "-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Ubuntu", -"Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif", - }, - "fontSize": { - "14": "0.875rem", - "16": "1rem", - "18": "1.125rem", - "20": "1.25rem", - "22": "1.375rem", - "26": "1.625rem", - "34": "2.125rem", - "44": "2.75rem", - "64": "4rem", - }, - "fontWeight": { - "400": 400, - "700": 700, - "base": 400, - "title": 700, - }, - "lineHeight": { - "base": 1.5, - "spacedTitle": 1.3, - "title": 1.2, - }, - "mode": "dark", - "modes": { - "dark": { - "background": "navy-800", - "background-contrast": "black", - "background-current": "navy-800", - "background-disabled": "white-200", - "background-error": "red-900", - "background-hover": "white-200", - "background-primary": "navy-900", - "background-selected": "white-100", - "background-success": "green-900", - "background-warning": "yellow-900", - "border-disabled": "white-500", - "border-primary": "white", - "border-secondary": "white-600", - "border-tertiary": "white-300", - "danger": "red-300", - "danger-hover": "red-400", - "editor-annotation": "red-300", - "editor-atom": "pink-400", - "editor-attribute": "green-400", - "editor-basic": "white", - "editor-comment": "comment-dark", - "editor-constant": "orange-500", - "editor-decoration": "red-300", - "editor-invalid": "red-300", - "editor-key": "blue-300", - "editor-keyword": "purple-300", - "editor-number": "red-300", - "editor-operator": "red-300", - "editor-predefined": "white", - "editor-property": "red-300", - "editor-regexp": "green-400", - "editor-string": "yellow-500", - "editor-tag": "red-300", - "editor-text": "orange-500", - "editor-ui-background": "navy-900", - "editor-ui-indent-active": "indent-active-dark", - "editor-ui-indent-inactive": "indent-inactive-dark", - "editor-ui-line-number-active": "line-number-active-dark", - "editor-ui-line-number-inactive": "line-number-inactive-dark", - "editor-ui-text": "white", - "editor-value": "yellow-500", - "editor-variable": "green-400", - "feedback-error": "red-300", - "feedback-success": "green-400", - "feedback-warning": "yellow-0", - "interface": "yellow-500", - "interface-hover": "yellow-400", - "primary": "yellow-500", - "primary-hover": "yellow-400", - "primary-inverse": "hyper-500", - "secondary": "white", - "secondary-hover": "white-700", - "shadow-primary": "white", - "shadow-secondary": "white-600", - "text": "white", - "text-accent": "beige", - "text-disabled": "white-500", - "text-secondary": "white-600", - }, - "light": { - "background": "white", - "background-contrast": "white", - "background-current": "white", - "background-disabled": "navy-200", - "background-error": "red-0", - "background-hover": "navy-200", - "background-primary": "blue-0", - "background-selected": "navy-100", - "background-success": "green-0", - "background-warning": "yellow-0", - "border-disabled": "navy-500", - "border-primary": "navy-800", - "border-secondary": "navy-600", - "border-tertiary": "navy-300", - "danger": "red-500", - "danger-hover": "red-600", - "editor-annotation": "red-500", - "editor-atom": "pink-800", - "editor-attribute": "green-700", - "editor-basic": "navy-800", - "editor-comment": "comment-light", - "editor-constant": "orange-800", - "editor-decoration": "red-500", - "editor-invalid": "red-500", - "editor-key": "teal-500", - "editor-keyword": "blue-500", - "editor-number": "red-500", - "editor-operator": "red-500", - "editor-predefined": "navy-800", - "editor-property": "red-500", - "editor-regexp": "green-700", - "editor-string": "gold-800", - "editor-tag": "red-500", - "editor-text": "orange-800", - "editor-ui-background": "white", - "editor-ui-indent-active": "indent-active-light", - "editor-ui-indent-inactive": "indent-inactive-light", - "editor-ui-line-number-active": "line-number-active-light", - "editor-ui-line-number-inactive": "line-number-inactive-light", - "editor-ui-text": "navy-800", - "editor-value": "gold-800", - "editor-variable": "green-700", - "feedback-error": "red-600", - "feedback-success": "green-700", - "feedback-warning": "yellow", - "interface": "hyper-500", - "interface-hover": "hyper-400", - "primary": "hyper-500", - "primary-hover": "hyper-400", - "primary-inverse": "yellow-500", - "secondary": "navy-800", - "secondary-hover": "navy-700", - "shadow-primary": "navy-800", - "shadow-secondary": "navy-600", - "text": "navy-800", - "text-accent": "navy-900", - "text-disabled": "navy-500", - "text-secondary": "navy-600", - }, - }, - "name": "platform", - "spacing": { - "0": 0, - "12": "0.75rem", - "16": "1rem", - "24": "1.5rem", - "32": "2rem", - "4": "0.25rem", - "40": "2.5rem", - "48": "3rem", - "64": "4rem", - "8": "0.5rem", - "96": "6rem", - }, +@layer tokens { + :root, [data-brand="skillsoft"] { + --color-white: oklch(1.0000 0.0000 0.00); + --color-black: oklch(0.0000 0.0000 0.00); + --color-palette-neutral-light-100: oklch(0.9862 0.0041 93.70); + --color-palette-neutral-light-200: oklch(0.9364 0.0075 93.70); + --color-palette-neutral-light-300: oklch(0.8497 0.0191 93.70); + --color-palette-neutral-light-400: oklch(0.7628 0.0218 93.70); + --color-palette-neutral-light-500: oklch(0.6707 0.0241 93.70); + --color-palette-neutral-light-600: oklch(0.5835 0.0239 93.70); + --color-palette-neutral-light-700: oklch(0.4972 0.0193 93.70); + --color-palette-neutral-light-800: oklch(0.4115 0.0151 93.70); + --color-palette-neutral-light-900: oklch(0.3346 0.0120 93.70); + --color-palette-neutral-light-1000: oklch(0.2499 0.0086 93.70); + --color-palette-neutral-light-1100: oklch(0.1925 0.0063 93.70); + --color-palette-neutral-dark-100: oklch(0.9862 0.0012 270.70); + --color-palette-neutral-dark-200: oklch(0.9364 0.0024 270.70); + --color-palette-neutral-dark-300: oklch(0.8497 0.0081 270.70); + --color-palette-neutral-dark-400: oklch(0.7628 0.0168 270.70); + --color-palette-neutral-dark-500: oklch(0.6707 0.0305 270.70); + --color-palette-neutral-dark-600: oklch(0.5835 0.0454 270.70); + --color-palette-neutral-dark-700: oklch(0.4972 0.0538 270.70); + --color-palette-neutral-dark-800: oklch(0.4115 0.0482 270.70); + --color-palette-neutral-dark-900: oklch(0.3346 0.0383 270.70); + --color-palette-neutral-dark-1000: oklch(0.2499 0.0273 270.70); + --color-palette-neutral-dark-1100: oklch(0.1925 0.0200 270.70); + --color-palette-neutral-dark-1100-a16: oklch(0.1925 0.0200 270.70 / 0.16); + --color-palette-neutral-dark-1100-a12: oklch(0.1925 0.0200 270.70 / 0.12); + --color-palette-primary-100: oklch(0.9862 0.0064 262.40); + --color-palette-primary-200: oklch(0.9364 0.0121 262.40); + --color-palette-primary-300: oklch(0.8497 0.0407 262.40); + --color-palette-primary-400: oklch(0.7628 0.0846 262.40); + --color-palette-primary-500: oklch(0.6707 0.1531 262.40); + --color-palette-primary-600: oklch(0.5835 0.2274 262.40); + --color-palette-primary-700: oklch(0.4972 0.2254 262.40); + --color-palette-primary-800: oklch(0.4115 0.1767 262.40); + --color-palette-primary-900: oklch(0.3346 0.1405 262.40); + --color-palette-primary-1000: oklch(0.2499 0.1001 262.40); + --color-palette-primary-1100: oklch(0.1925 0.0734 262.40); + --color-palette-primary-600-a6: oklch(0.5835 0.2274 262.40 / 0.06); + --color-palette-primary-600-a9: oklch(0.5835 0.2274 262.40 / 0.09); + --color-palette-primary-600-a8: oklch(0.5835 0.2274 262.40 / 0.08); + --color-palette-accent-100: oklch(0.9862 0.0073 357.30); + --color-palette-accent-200: oklch(0.9364 0.0141 357.30); + --color-palette-accent-300: oklch(0.8497 0.0499 357.30); + --color-palette-accent-400: oklch(0.7628 0.1100 357.30); + --color-palette-accent-500: oklch(0.6707 0.2144 357.30); + --color-palette-accent-600: oklch(0.5835 0.2380 357.30); + --color-palette-accent-700: oklch(0.4972 0.1927 357.30); + --color-palette-accent-800: oklch(0.4115 0.1511 357.30); + --color-palette-accent-900: oklch(0.3346 0.1201 357.30); + --color-palette-accent-1000: oklch(0.2499 0.0856 357.30); + --color-palette-accent-1100: oklch(0.1925 0.0628 357.30); + --color-palette-accent-alt-100: oklch(0.9862 0.0067 287.10); + --color-palette-accent-alt-200: oklch(0.9364 0.0127 287.10); + --color-palette-accent-alt-300: oklch(0.8497 0.0430 287.10); + --color-palette-accent-alt-400: oklch(0.7628 0.0896 287.10); + --color-palette-accent-alt-500: oklch(0.6707 0.1624 287.10); + --color-palette-accent-alt-600: oklch(0.5835 0.2416 287.10); + --color-palette-accent-alt-700: oklch(0.4972 0.2722 287.10); + --color-palette-accent-alt-800: oklch(0.4115 0.2134 287.10); + --color-palette-accent-alt-900: oklch(0.3346 0.1696 287.10); + --color-palette-accent-alt-1000: oklch(0.2499 0.1209 287.10); + --color-palette-accent-alt-1100: oklch(0.1925 0.0887 287.10); + --color-palette-danger-100: oklch(0.9862 0.0066 17.30); + --color-palette-danger-200: oklch(0.9364 0.0128 17.30); + --color-palette-danger-300: oklch(0.8497 0.0452 17.30); + --color-palette-danger-400: oklch(0.7628 0.0993 17.30); + --color-palette-danger-500: oklch(0.6707 0.1924 17.30); + --color-palette-danger-600: oklch(0.5835 0.2336 17.30); + --color-palette-danger-700: oklch(0.4972 0.1891 17.30); + --color-palette-danger-800: oklch(0.4115 0.1482 17.30); + --color-palette-danger-900: oklch(0.3346 0.1178 17.30); + --color-palette-danger-1000: oklch(0.2499 0.0840 17.30); + --color-palette-danger-1100: oklch(0.1925 0.0616 17.30); + --color-palette-warning-100: oklch(0.9862 0.0160 88.00); + --color-palette-warning-200: oklch(0.9364 0.0300 88.00); + --color-palette-warning-300: oklch(0.8497 0.0955 88.00); + --color-palette-warning-400: oklch(0.7628 0.1091 88.00); + --color-palette-warning-500: oklch(0.6707 0.1206 88.00); + --color-palette-warning-600: oklch(0.5835 0.1192 88.00); + --color-palette-warning-700: oklch(0.4972 0.0965 88.00); + --color-palette-warning-800: oklch(0.4115 0.0757 88.00); + --color-palette-warning-900: oklch(0.3346 0.0601 88.00); + --color-palette-warning-1000: oklch(0.2499 0.0429 88.00); + --color-palette-warning-1100: oklch(0.1925 0.0314 88.00); + --color-palette-success-100: oklch(0.9862 0.0170 200.00); + --color-palette-success-200: oklch(0.9364 0.0318 200.00); + --color-palette-success-300: oklch(0.8497 0.0794 200.00); + --color-palette-success-400: oklch(0.7628 0.0907 200.00); + --color-palette-success-500: oklch(0.6707 0.1003 200.00); + --color-palette-success-600: oklch(0.5835 0.0991 200.00); + --color-palette-success-700: oklch(0.4972 0.0803 200.00); + --color-palette-success-800: oklch(0.4115 0.0629 200.00); + --color-palette-success-900: oklch(0.3346 0.0500 200.00); + --color-palette-success-1000: oklch(0.2499 0.0356 200.00); + --color-palette-success-1100: oklch(0.1925 0.0261 200.00); + --color-black-a32: oklch(0.0000 0.0000 0.00 / 0.32); + --color-black-a24: oklch(0.0000 0.0000 0.00 / 0.24); + --color-black-a60: oklch(0.0000 0.0000 0.00 / 0.6); + } + + :root, [data-color-mode="light"] { + color-scheme: light; + color: var(--color-text-default); + --color-background-current: var(--color-surface-default); + --color-surface-sunken: var(--color-palette-neutral-light-200); /** Wells, insets, code blocks — anything recessed into the page. */ + --color-surface-default: var(--color-palette-neutral-light-100); /** The page background — the base every other surface elevates from. */ + --color-surface-raised: var(--color-white); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */ + --color-surface-success: var(--color-palette-success-200); /** The success tint behind an alert or inline confirmation. */ + --color-surface-warning: var(--color-palette-warning-200); /** The warning tint behind an alert or inline caution. */ + --color-surface-error: var(--color-palette-danger-200); /** The error tint behind an alert or inline validation message. */ + --color-surface-brand: var(--color-palette-neutral-light-100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */ + --color-surface-brand-subtle: var(--color-palette-neutral-light-100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */ + --color-text-default: var(--color-palette-neutral-dark-1000); /** Body copy and headings. */ + --color-text-subtle: var(--color-palette-neutral-dark-800); /** Secondary text — descriptions, metadata, helper copy. */ + --color-text-inverse: var(--color-palette-neutral-light-200); /** Labels on a filled control — action-primary, -neutral, -danger. */ + --color-text-success: var(--color-palette-success-900); /** Success text and icons. */ + --color-text-warning: var(--color-palette-warning-900); /** Warning text and icons. Always with an icon. */ + --color-text-error: var(--color-palette-danger-900); /** Error text and icons. */ + --color-border-strong: var(--color-palette-neutral-dark-1100); /** Emphasis — an active field, a selected row. */ + --color-border-default: var(--color-palette-neutral-dark-600); /** Any border that carries meaning — control outlines, field borders. */ + --color-border-subtle: var(--color-palette-neutral-dark-300); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */ + --color-action-primary: var(--color-palette-primary-800); /** Primary interactive fills, strokes, links and focus rings. */ + --color-action-primary-hover: var(--color-palette-primary-900); /** Hover for action-primary. */ + --color-action-primary-pressed: var(--color-palette-primary-1000); /** Pressed for action-primary. */ + --color-action-danger: var(--color-palette-danger-800); /** Destructive actions — delete, remove, revoke. */ + --color-action-danger-hover: var(--color-palette-danger-900); /** Hover for action-danger. */ + --color-action-danger-pressed: var(--color-palette-danger-1000); /** Pressed for action-danger. */ + --color-action-neutral: var(--color-palette-neutral-dark-1000); /** The neutral action fill. Button variant="secondary" resolves here. */ + --color-action-neutral-hover: var(--color-palette-neutral-dark-1100); /** Hover for action-neutral. */ + --color-action-neutral-pressed: var(--color-black); /** Pressed for action-neutral. */ + --color-action-disabled: var(--color-palette-neutral-light-400); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */ + --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */ + --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */ + --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent "this one is chosen" tint. */ + --color-viz-1: var(--color-palette-primary-800); /** Categorical series 1. */ + --color-viz-2: var(--color-palette-accent-700); /** Categorical series 2. */ + --color-viz-3: var(--color-palette-warning-700); /** Categorical series 3. */ + --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */ + --color-viz-5: var(--color-palette-success-600); /** Categorical series 5. */ + --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */ + --color-viz-7: var(--color-palette-neutral-dark-1000); /** Categorical series 7. */ + --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */ + --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */ + --color-viz-seq-2: var(--color-palette-primary-700); /** Sequential step 2, low to high. */ + --color-viz-seq-3: var(--color-palette-primary-800); /** Sequential step 3, low to high. */ + --color-viz-seq-4: var(--color-palette-primary-900); /** Sequential step 4, low to high. */ + --color-shadow-key: var(--color-palette-neutral-dark-1100-a16); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */ + --color-shadow-ambient: var(--color-palette-neutral-dark-1100-a12); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */ + --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */ + --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-300) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%); + --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%); + } + + [data-color-mode="dark"] { + color-scheme: dark; + color: var(--color-text-default); + --color-background-current: var(--color-surface-default); + --color-surface-sunken: var(--color-black); /** Wells, insets, code blocks — anything recessed into the page. */ + --color-surface-default: var(--color-palette-neutral-dark-1100); /** The page background — the base every other surface elevates from. */ + --color-surface-raised: var(--color-palette-neutral-dark-1000); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */ + --color-surface-success: var(--color-palette-success-1000); /** The success tint behind an alert or inline confirmation. */ + --color-surface-warning: var(--color-palette-warning-1000); /** The warning tint behind an alert or inline caution. */ + --color-surface-error: var(--color-palette-danger-1000); /** The error tint behind an alert or inline validation message. */ + --color-surface-brand: var(--color-palette-neutral-dark-1100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */ + --color-surface-brand-subtle: var(--color-palette-neutral-dark-1100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */ + --color-text-default: var(--color-palette-neutral-light-200); /** Body copy and headings. */ + --color-text-subtle: var(--color-palette-neutral-light-400); /** Secondary text — descriptions, metadata, helper copy. */ + --color-text-inverse: var(--color-palette-neutral-dark-1000); /** Labels on a filled control — action-primary, -neutral, -danger. */ + --color-text-success: var(--color-palette-success-300); /** Success text and icons. */ + --color-text-warning: var(--color-palette-warning-300); /** Warning text and icons. Always with an icon. */ + --color-text-error: var(--color-palette-danger-300); /** Error text and icons. */ + --color-border-strong: var(--color-palette-neutral-light-100); /** Emphasis — an active field, a selected row. */ + --color-border-default: var(--color-palette-neutral-light-600); /** Any border that carries meaning — control outlines, field borders. */ + --color-border-subtle: var(--color-palette-neutral-light-900); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */ + --color-action-primary: var(--color-palette-primary-400); /** Primary interactive fills, strokes, links and focus rings. */ + --color-action-primary-hover: var(--color-palette-primary-300); /** Hover for action-primary. */ + --color-action-primary-pressed: var(--color-palette-primary-200); /** Pressed for action-primary. */ + --color-action-danger: var(--color-palette-danger-400); /** Destructive actions — delete, remove, revoke. */ + --color-action-danger-hover: var(--color-palette-danger-300); /** Hover for action-danger. */ + --color-action-danger-pressed: var(--color-palette-danger-200); /** Pressed for action-danger. */ + --color-action-neutral: var(--color-palette-neutral-light-200); /** The neutral action fill. Button variant="secondary" resolves here. */ + --color-action-neutral-hover: var(--color-palette-neutral-light-100); /** Hover for action-neutral. */ + --color-action-neutral-pressed: var(--color-white); /** Pressed for action-neutral. */ + --color-action-disabled: var(--color-palette-neutral-dark-800); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */ + --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */ + --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */ + --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent "this one is chosen" tint. */ + --color-viz-1: var(--color-palette-primary-500); /** Categorical series 1. */ + --color-viz-2: var(--color-palette-accent-500); /** Categorical series 2. */ + --color-viz-3: var(--color-palette-warning-400); /** Categorical series 3. */ + --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */ + --color-viz-5: var(--color-palette-success-300); /** Categorical series 5. */ + --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */ + --color-viz-7: var(--color-palette-neutral-dark-500); /** Categorical series 7. */ + --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */ + --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */ + --color-viz-seq-2: var(--color-palette-primary-500); /** Sequential step 2, low to high. */ + --color-viz-seq-3: var(--color-palette-primary-400); /** Sequential step 3, low to high. */ + --color-viz-seq-4: var(--color-palette-primary-300); /** Sequential step 4, low to high. */ + --color-shadow-key: var(--color-black-a32); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */ + --color-shadow-ambient: var(--color-black-a24); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */ + --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */ + --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-900) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%); + --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%); + } +} +", } `; diff --git a/packages/gamut-styles/src/themes/__tests__/brandElevation.test.ts b/packages/gamut-styles/src/themes/__tests__/brandElevation.test.ts new file mode 100644 index 0000000000..271f07cd06 --- /dev/null +++ b/packages/gamut-styles/src/themes/__tests__/brandElevation.test.ts @@ -0,0 +1,72 @@ +import { adminTheme } from '../admin'; +import { lxStudioTheme } from '../lxStudio'; +import { originalCodecademyTheme } from '../originalCodecademy'; +import { platformTheme } from '../platform'; +import { skillsoftTheme } from '../skillsoft'; + +/* + * Brand elevations draw every depth in the shadow roles, so + * no shadow names a color the brand theme doesn't have. The theme flattens + * the scale to `-shadow` and `-transform`. + */ +const STATES = ['rest', 'hover', 'hoverMirrored'] as const; + +describe('brand elevation', () => { + it.each([ + ['skillsoft', skillsoftTheme], + ['originalCodecademy', originalCodecademyTheme], + ['admin', adminTheme], + ['platform', platformTheme], + ['lxStudio', lxStudioTheme], + ])('%s composes shadow-key and shadow-ambient at every depth', (_, theme) => { + const elevation = theme.elevation as Record; + for (const state of STATES) { + const shadow = elevation[`${state}-shadow`]; + expect(shadow).not.toContain('undefined'); + expect(shadow).toContain('var(--color-shadow-key)'); + expect(shadow).toContain('var(--color-shadow-ambient)'); + } + }); + + it.each([ + ['skillsoft', skillsoftTheme], + ['admin', adminTheme], + ['platform', platformTheme], + ['lxStudio', lxStudioTheme], + ])('gives %s the soft raised shadow', (_, theme) => { + expect(theme.elevation).toMatchObject({ + 'rest-shadow': + '0 1px 4px 0 var(--color-shadow-key), 0 2px 7px 0 var(--color-shadow-ambient)', + 'hover-shadow': + '0 2px 6px 0 var(--color-shadow-key), 0 6px 16px 0 var(--color-shadow-ambient)', + }); + }); + + it("gives Original Codecademy old core's hard offset", () => { + expect(originalCodecademyTheme.elevation).toMatchObject({ + 'rest-shadow': + '-4px 4px 0 0 var(--color-shadow-key), 0 0 0 0 var(--color-shadow-ambient)', + 'hover-shadow': + '-8px 8px 0 0 var(--color-shadow-key), 0 0 0 0 var(--color-shadow-ambient)', + 'hover-transform': 'translate(4px, -4px)', + }); + }); + + /* Admin and platform share old core's pins, not its hard-offset shadow. */ + it('makes shadow-ambient transparent for Original Codecademy only', () => { + const transparent = Object.entries({ + skillsoft: skillsoftTheme, + originalCodecademy: originalCodecademyTheme, + admin: adminTheme, + platform: platformTheme, + lxStudio: lxStudioTheme, + }) + .filter(([, theme]) => + /--color-shadow-ambient: var\(--color-[\w-]+-a0\);/.test( + theme.tokensCss + ) + ) + .map(([name]) => name); + expect(transparent).toEqual(['originalCodecademy']); + }); +}); diff --git a/packages/gamut-styles/src/themes/__tests__/theme.test.ts b/packages/gamut-styles/src/themes/__tests__/theme.test.ts index 6239974719..670b5aae88 100644 --- a/packages/gamut-styles/src/themes/__tests__/theme.test.ts +++ b/packages/gamut-styles/src/themes/__tests__/theme.test.ts @@ -1,15 +1,7 @@ -import { adminTheme } from '../admin'; -import { coreTheme } from '../core'; -import { lxStudioTheme } from '../lxStudio'; -import { percipioTheme } from '../percipio'; -import { platformTheme } from '../platform'; +import { skillsoftTheme } from '../skillsoft'; describe('themes', () => { - it.each([ - ['core', coreTheme], - ['platform', platformTheme], - ['admin', adminTheme], - ['lxStudio', lxStudioTheme], - ['percipio', percipioTheme], - ])(`%s - theme shape`, (_, theme) => expect(theme).toMatchSnapshot()); + it('skillsoft - theme shape', () => { + expect(skillsoftTheme).toMatchSnapshot(); + }); }); diff --git a/packages/gamut-styles/src/themes/admin.ts b/packages/gamut-styles/src/themes/admin.ts index 861f853eed..a547034edf 100644 --- a/packages/gamut-styles/src/themes/admin.ts +++ b/packages/gamut-styles/src/themes/admin.ts @@ -1,23 +1,19 @@ -import { createTheme } from '@skillsoft/variance'; +import * as values from '../tokens/generated/admin/values'; +import { fontFamily, fontWeight, softElevation } from '../variables'; +import { createBrandTheme } from './createBrandTheme'; -import { coreTheme } from './core'; - -export const adminTheme = createTheme(coreTheme) - .addColorModes('light', { - light: { - primary: { - _: 'blue-500', - hover: 'blue-400', - }, - }, - dark: { - feedback: { - success: 'green-400', - }, - }, - }) - .addName('admin') - .build(); +/* + * Admin: old admin rebuilt as a brand of the generated color system + * (@skillsoft/gamut-brand brands/admin.ts). Fonts and weights are old core's, which old + * admin used, unchanged. Elevation is the soft shadow every brand but the + * Codecademy theme uses. + */ +export const adminTheme = createBrandTheme(values, { + name: 'admin', + fontFamily, + fontWeight, + elevation: softElevation, +}); export type AdminThemeShape = typeof adminTheme; diff --git a/packages/gamut-styles/src/themes/core.ts b/packages/gamut-styles/src/themes/core.ts deleted file mode 100644 index 79bcff7a8b..0000000000 --- a/packages/gamut-styles/src/themes/core.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { createTheme } from '@skillsoft/variance'; - -import { - borderRadii, - containerQueries, - coreElevation, - corePalette, - elements, - fontFamily, - fontSize, - fontWeight, - lineHeight, - mediaQueries, - spacing, -} from '../variables'; - -/** - * @description This is the core theme that all other Codecademy themes are extended from - * and is the only theme that gamut components are typed to. This ensures that gamut components - * are guaranteed to be interoperable between all contexts, but with differing behaviors. - */ - -export const coreTheme = createTheme({ - breakpoints: { ...mediaQueries, ...containerQueries }, - borderRadii, - fontSize, - fontFamily, - lineHeight, - fontWeight, - spacing, - elements, -}) - .addColors(corePalette) - .addColorModes('light', { - light: { - text: { - _: 'navy-800', - accent: 'navy-900', - disabled: 'navy-500', - secondary: 'navy-600', - }, - feedback: { - error: 'red-600', - success: 'green-700', - warning: 'yellow', - }, - background: { - _: 'white', - contrast: 'white', - current: 'white', - primary: 'beige', - selected: 'navy-100', - disabled: 'navy-200', - hover: 'navy-200', - success: 'green-0', - warning: 'yellow-0', - error: 'red-0', - }, - shadow: { - primary: 'navy-800', - secondary: 'navy-600', - }, - primary: { - _: 'hyper-500', - hover: 'hyper-400', - inverse: 'yellow-500', - }, - secondary: { - _: 'navy-800', - hover: 'navy-700', - }, - danger: { - _: 'red-500', - hover: 'red-600', - }, - interface: { - _: 'hyper-500', - hover: 'hyper-400', - }, - border: { - primary: 'navy-800', - secondary: 'navy-600', - tertiary: 'navy-300', - disabled: 'navy-500', - }, - }, - dark: { - text: { - _: 'white', - accent: 'beige', - disabled: 'white-500', - secondary: 'white-600', - }, - feedback: { - error: 'red-300', - success: 'green-400', - warning: 'yellow-0', - }, - background: { - _: 'navy-800', - contrast: 'black', - current: 'navy-800', - primary: 'navy-900', - selected: 'white-100', - disabled: 'white-200', - hover: 'white-200', - success: 'green-900', - warning: 'yellow-900', - error: 'red-900', - }, - shadow: { - primary: 'white', - secondary: 'white-600', - }, - primary: { - _: 'yellow-500', - hover: 'yellow-400', - inverse: 'hyper-500', - }, - secondary: { - _: 'white', - hover: 'white-700', - }, - danger: { - _: 'red-300', - hover: 'red-400', - }, - interface: { - _: 'yellow-500', - hover: 'yellow-400', - }, - border: { - primary: 'white', - secondary: 'white-600', - tertiary: 'white-300', - disabled: 'white-500', - }, - }, - }) - .addScale('borders', ({ colors }: { colors: Record }) => ({ - 1: `1px solid ${colors['border-primary']}`, - 2: `2px solid ${colors['border-primary']}`, - })) - .addScale('elevation', coreElevation) - .createScaleVariables('elements') - .addName('core') - .build(); - -export type CoreThemeShape = typeof coreTheme; - -export interface CoreTheme extends CoreThemeShape {} diff --git a/packages/gamut-styles/src/themes/createBrandTheme.ts b/packages/gamut-styles/src/themes/createBrandTheme.ts new file mode 100644 index 0000000000..4ca9d22aaf --- /dev/null +++ b/packages/gamut-styles/src/themes/createBrandTheme.ts @@ -0,0 +1,143 @@ +import { createTheme } from '@skillsoft/variance'; +import mapValues from 'lodash/mapValues'; +import pickBy from 'lodash/pickBy'; + +import type * as defaults from '../tokens/generated/skillsoft/values'; +import type { + fontFamily as baseFontFamily, + fontWeight as baseFontWeight, + fontWeightMediumTitle, +} from '../variables'; +import { + borderRadii, + containerQueries, + elements, + fontSize, + lineHeight, + mediaQueries, + spacing, +} from '../variables'; +import type { ElevationScaleFactory } from '../variables/elevation'; + +/* + * An Emotion theme for one brand of the generated color system, from the + * `values.ts` that @skillsoft/gamut-brand writes for it: gamut's own brands + * and any team's. Every brand has the same palette keys and the same roles, + * so every brand theme has the same shape and type, and gamut's system props + * and styling functions are typed against all of them (`SkillsoftTheme`). + * Only `white` and `black` survive from the old themes, as the system's + * boundary tokens. + * + * `background-current` stays: it's ColorMode's mechanism for the color under + * a Background, not a color of its own. Values are sRGB, not OKLCH: + * Background and BarChart run polished's color math on theme values, and + * polished can't parse oklch(). tokens.css carries the OKLCH, and the brand + * gradients (--gradient-*), which the theme doesn't model. + */ + +type PaletteKey = keyof typeof defaults.palette; +type RoleName = keyof typeof defaults.roles.light; + +/* A brand's generated `values.ts`, imported as a module: `import * as values from './acme/values'`. */ +export interface BrandValues { + brand: { name: string; initialMode: 'light' | 'dark' }; + palette: Record; + roles: Record<'light' | 'dark', Record>; + contentModes: Record; + /* The brand's tokens.css, which GamutProvider injects. */ + tokensCss: string; +} + +type FontWeightKey = keyof typeof baseFontWeight; +type OptionalFontWeightKey = Exclude< + keyof typeof fontWeightMediumTitle, + FontWeightKey +>; + +/* + * Everything about a brand that isn't color. The keys are fixed, so every + * brand theme has the type gamut is checked against: a font or weight a + * brand leaves out would type-check and then render nothing. + */ +export interface BrandBase { + name: string; + fontFamily: Record; + /* + * `bold` and `500` are optional only because the old core family's themes + * never had them; Skillsoft's do. A new brand should set both. + */ + fontWeight: Record & + Partial>; + elevation: ElevationScaleFactory; + /* Defaults to gamut's shared radii. */ + borderRadii?: Record; +} + +export const createBrandTheme = ( + values: BrandValues, + { + name, + fontFamily, + fontWeight, + elevation, + borderRadii: radii = borderRadii, + }: BrandBase +) => { + /* The brand's tokens.css puts this mode on :root, so the theme starts there too. */ + const { initialMode } = values.brand; + const refs = (mode: 'light' | 'dark') => ({ + ...(mapValues(values.roles[mode], (v) => v.ref) as Record< + RoleName, + PaletteKey + >), + 'background-current': values.roles[mode]['surface-default'] + .ref as PaletteKey, + }); + + const built = createTheme({ + breakpoints: { ...mediaQueries, ...containerQueries }, + borderRadii: radii, + fontSize, + fontFamily, + lineHeight, + fontWeight, + spacing, + elements, + }) + .addColors( + mapValues(values.palette, (v) => v.srgb) as Record + ) + .addColorModes(initialMode, { light: refs('light'), dark: refs('dark') }) + .addScale('borders', ({ colors }: { colors: Record }) => ({ + 1: `1px solid ${colors['border-strong']}`, + 2: `2px solid ${colors['border-strong']}`, + })) + .addScale('elevation', elevation) + .createScaleVariables('elements') + .addName(name) + .build(); + + /* + * tokens.css owns the color values, and the theme carries the brand's own + * as `tokensCss` for GamutProvider to inject: this brand's palette and + * roles, no other brand's. The theme doesn't also inject the palette as variables, so + * the OKLCH in tokens.css is what renders. The theme keeps names + * (`var(--color-*)`), `brand` (ColorMode sets it as `data-brand`), and + * `contentModes`, which Background reads instead of measuring contrast. + * `_getColorValue` still returns sRGB for the components that run polished + * on it (BarChart). + */ + return { + ...built, + brand: values.brand.name, + contentModes: values.contentModes, + tokensCss: values.tokensCss, + _variables: { + ...built._variables, + root: pickBy( + built._variables.root, + (_, key) => !key.startsWith('--color-') + ), + }, + }; +}; diff --git a/packages/gamut-styles/src/themes/index.ts b/packages/gamut-styles/src/themes/index.ts index 10a3007b64..4104762df2 100644 --- a/packages/gamut-styles/src/themes/index.ts +++ b/packages/gamut-styles/src/themes/index.ts @@ -1,5 +1,6 @@ -export * from './core'; export * from './platform'; export * from './admin'; export * from './lxStudio'; -export * from './percipio'; +export * from './skillsoft'; +export * from './originalCodecademy'; +export * from './createBrandTheme'; diff --git a/packages/gamut-styles/src/themes/lxStudio.ts b/packages/gamut-styles/src/themes/lxStudio.ts index ca84dd9af8..c10135ee48 100644 --- a/packages/gamut-styles/src/themes/lxStudio.ts +++ b/packages/gamut-styles/src/themes/lxStudio.ts @@ -1,17 +1,18 @@ -import { createTheme } from '@skillsoft/variance'; - +import * as values from '../tokens/generated/lxStudio/values'; import { fontMonospace, fontSystem, fontWeightMediumTitle, - lxStudioPalette, percipioFontFamily, + softElevation, } from '../variables'; -import { coreTheme } from './core'; +import { createBrandTheme } from './createBrandTheme'; -/** - * @description This is an extended theme for the lx studio with an expanded set of tokens - * That are not needed for the rest of the application. +/* + * LX Studio: old lxStudio rebuilt as a brand of the generated color system + * (@skillsoft/gamut-brand brands/lxStudio.ts). Fonts, weights and radii are old + * lxStudio's, unchanged. Elevation is the soft shadow every brand but the + * Codecademy theme uses. */ const lxStudioFontFamily = { @@ -30,42 +31,13 @@ export const lxStudioBorderRadii = { full: '999px', }; -export const lxStudioTheme = createTheme({ - ...coreTheme, - borderRadii: lxStudioBorderRadii, +export const lxStudioTheme = createBrandTheme(values, { + name: 'lxStudio', fontFamily: lxStudioFontFamily, fontWeight: fontWeightMediumTitle, -}) - .addColors(lxStudioPalette) - .addColorModes('light', { - // these are just the overrides, the rest of the tokens are inherited from coreTheme - light: { - feedback: { - success: 'lxStudioSuccess', - }, - background: { - primary: 'lxStudioBgPrimary', - }, - shadow: { - primary: 'navy-200', - }, - primary: { - _: 'sapphire', - hover: 'navy-800', - }, - interface: { - _: 'hyper-500', - hover: 'hyper-400', - }, - border: { - primary: 'navy-400', - tertiary: 'navy-800', - disabled: 'navy-300', - }, - }, - }) - .addName('lxStudio') - .build(); + elevation: softElevation, + borderRadii: lxStudioBorderRadii, +}); export type LxStudioThemeShape = typeof lxStudioTheme; diff --git a/packages/gamut-styles/src/themes/originalCodecademy.ts b/packages/gamut-styles/src/themes/originalCodecademy.ts new file mode 100644 index 0000000000..6ed6e802a6 --- /dev/null +++ b/packages/gamut-styles/src/themes/originalCodecademy.ts @@ -0,0 +1,17 @@ +import * as values from '../tokens/generated/originalCodecademy/values'; +import { fontFamily, fontWeight, offsetElevation } from '../variables'; +import { createBrandTheme } from './createBrandTheme'; + +/* + * Original Codecademy: old core rebuilt as a brand of the generated color + * system (@skillsoft/gamut-brand brands/originalCodecademy.ts). Fonts and weights are old + * core's, unchanged; elevation is old core's hard offset, drawn in the shadow + * roles. Lives next to `coreTheme` until the + * rebuild is signed off. + */ +export const originalCodecademyTheme = createBrandTheme(values, { + name: 'originalCodecademy', + fontFamily, + fontWeight, + elevation: offsetElevation, +}); diff --git a/packages/gamut-styles/src/themes/percipio.ts b/packages/gamut-styles/src/themes/percipio.ts deleted file mode 100644 index 09ef1389b7..0000000000 --- a/packages/gamut-styles/src/themes/percipio.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { createTheme } from '@skillsoft/variance'; - -import { - fontWeightMediumTitle, - percipioElevation, - percipioFontFamily, - percipioPalette, -} from '../variables'; -import { coreTheme } from './core'; - -export const percipioTheme = createTheme({ - ...coreTheme, - fontFamily: percipioFontFamily, - fontWeight: fontWeightMediumTitle, -}) - .addColors(percipioPalette) - .addColorModes('light', { - // these are just the overrides, the rest of the tokens are inherited from coreTheme - light: { - text: { - _: 'percipioTextPrimary', - accent: 'percipioTextAccent', - disabled: 'percipioTextDisabled', - secondary: 'percipioTextSecondary', - }, - feedback: { - error: 'percipioDanger', - success: 'percipioFeedbackSuccess', - warning: 'percipioFeedbackWarning', - }, - background: { - primary: 'percipioBgPrimary', - success: 'percipioBgSuccess', - warning: 'percipioBgWarning', - error: 'percipioBgError', - }, - shadow: { - primary: 'navy-200', - secondary: 'navy-400', - }, - primary: { - _: 'sapphire', - hover: 'percipioActionPrimaryHover', - inverse: 'white', - }, - secondary: { - _: 'percipioActionSecondary', - hover: 'percipioActionSecondaryHover', - }, - danger: { - _: 'percipioDanger', - hover: 'percipioActionDangerHover', - }, - interface: { - _: 'sapphire', - hover: 'percipioActionPrimaryHover', - }, - border: { - primary: 'navy-400', - secondary: 'navy-600', - tertiary: 'navy-800', - disabled: 'navy-300', - }, - }, - }) - .addScale('elevation', percipioElevation) - .addName('percipio') - .build(); - -export type PercipioThemeShape = typeof percipioTheme; - -export interface PercipioTheme extends PercipioThemeShape {} diff --git a/packages/gamut-styles/src/themes/platform.ts b/packages/gamut-styles/src/themes/platform.ts index 60a9311c6d..afc7d3f306 100644 --- a/packages/gamut-styles/src/themes/platform.ts +++ b/packages/gamut-styles/src/themes/platform.ts @@ -1,86 +1,126 @@ -import { createTheme } from '@skillsoft/variance'; +import mapValues from 'lodash/mapValues'; -import { platformPalette } from '../variables'; -import { coreTheme } from './core'; +import * as values from '../tokens/generated/platform/values'; +import { + corePalette, + fontFamily, + fontWeight, + platformPalette, + softElevation, +} from '../variables'; +import { createBrandTheme } from './createBrandTheme'; -/** - * @description This is an extended theme for the learning environment with an expanded set of tokens - * That are not needed for the rest of the application. +/* + * Platform (the learning environment): old platform rebuilt as a brand of + * the generated color system (@skillsoft/gamut-brand brands/platform.ts). Fonts and + * weights are old core's, which old platform used, unchanged. Elevation is + * the soft shadow every brand but the Codecademy theme uses. It starts in + * dark mode, as old platform did (the brand's `initialMode`). + * + * The code editor's syntax and chrome colors aren't part of the color + * system: no role covers them, and they aren't generated or checked. They + * keep old platform's names and exact values, resolved from the same + * palettes as before, and only this theme's GamutProvider declares them, + * as `--color-editor-*` in tokens.css's layer and mode selectors, the + * brand's initial mode on :root. */ +const OLD_PALETTE: Record = { + ...corePalette, + ...platformPalette, +}; -export const platformTheme = createTheme(coreTheme) - .addColors(platformPalette) - .addColorModes('dark', { - light: { - background: { - primary: 'blue-0', - }, - editor: { - attribute: 'green-700', - annotation: 'red-500', - atom: 'pink-800', - basic: 'navy-800', - comment: 'comment-light', - constant: 'orange-800', - decoration: 'red-500', - invalid: 'red-500', - key: 'teal-500', - keyword: 'blue-500', - number: 'red-500', - operator: 'red-500', - predefined: 'navy-800', - property: 'red-500', - regexp: 'green-700', - string: 'gold-800', - tag: 'red-500', - text: 'orange-800', - value: 'gold-800', - variable: 'green-700', - ui: { - background: 'white', - text: 'navy-800', - 'indent-active': 'indent-active-light', - 'indent-inactive': 'indent-inactive-light', - 'line-number-active': 'line-number-active-light', - 'line-number-inactive': 'line-number-inactive-light', - }, - }, - }, - dark: { - editor: { - attribute: 'green-400', - annotation: 'red-300', - atom: 'pink-400', - basic: 'white', - comment: 'comment-dark', - constant: 'orange-500', - decoration: 'red-300', - invalid: 'red-300', - key: 'blue-300', - keyword: 'purple-300', - number: 'red-300', - operator: 'red-300', - predefined: 'white', - property: 'red-300', - regexp: 'green-400', - string: 'yellow-500', - tag: 'red-300', - text: 'orange-500', - value: 'yellow-500', - variable: 'green-400', - ui: { - background: 'navy-900', - text: 'white', - 'indent-active': 'indent-active-dark', - 'indent-inactive': 'indent-inactive-dark', - 'line-number-active': 'line-number-active-dark', - 'line-number-inactive': 'line-number-inactive-dark', - }, - }, - }, - }) - .addName('platform') - .build(); +const editor = { + light: { + 'editor-attribute': 'green-700', + 'editor-annotation': 'red-500', + 'editor-atom': 'pink-800', + 'editor-basic': 'navy-800', + 'editor-comment': 'comment-light', + 'editor-constant': 'orange-800', + 'editor-decoration': 'red-500', + 'editor-invalid': 'red-500', + 'editor-key': 'teal-500', + 'editor-keyword': 'blue-500', + 'editor-number': 'red-500', + 'editor-operator': 'red-500', + 'editor-predefined': 'navy-800', + 'editor-property': 'red-500', + 'editor-regexp': 'green-700', + 'editor-string': 'gold-800', + 'editor-tag': 'red-500', + 'editor-text': 'orange-800', + 'editor-value': 'gold-800', + 'editor-variable': 'green-700', + 'editor-ui-background': 'white', + 'editor-ui-text': 'navy-800', + 'editor-ui-indent-active': 'indent-active-light', + 'editor-ui-indent-inactive': 'indent-inactive-light', + 'editor-ui-line-number-active': 'line-number-active-light', + 'editor-ui-line-number-inactive': 'line-number-inactive-light', + }, + dark: { + 'editor-attribute': 'green-400', + 'editor-annotation': 'red-300', + 'editor-atom': 'pink-400', + 'editor-basic': 'white', + 'editor-comment': 'comment-dark', + 'editor-constant': 'orange-500', + 'editor-decoration': 'red-300', + 'editor-invalid': 'red-300', + 'editor-key': 'blue-300', + 'editor-keyword': 'purple-300', + 'editor-number': 'red-300', + 'editor-operator': 'red-300', + 'editor-predefined': 'white', + 'editor-property': 'red-300', + 'editor-regexp': 'green-400', + 'editor-string': 'yellow-500', + 'editor-tag': 'red-300', + 'editor-text': 'orange-500', + 'editor-value': 'yellow-500', + 'editor-variable': 'green-400', + 'editor-ui-background': 'navy-900', + 'editor-ui-text': 'white', + 'editor-ui-indent-active': 'indent-active-dark', + 'editor-ui-indent-inactive': 'indent-inactive-dark', + 'editor-ui-line-number-active': 'line-number-active-dark', + 'editor-ui-line-number-inactive': 'line-number-inactive-dark', + }, +} as const; + +const base = createBrandTheme(values, { + name: 'platform', + fontFamily, + fontWeight, + elevation: softElevation, +}); + +type EditorAlias = keyof typeof editor.light; + +const otherMode = values.brand.initialMode === 'dark' ? 'light' : 'dark'; + +const editorValues = (mode: 'light' | 'dark') => + mapValues(editor[mode], (key) => OLD_PALETTE[key]); + +const editorBlock = (selector: string, mode: 'light' | 'dark') => + `${selector}{${Object.entries(editorValues(mode)) + .map(([alias, value]) => `--color-${alias}:${value};`) + .join('')}}`; + +export const platformTheme = { + ...base, + colors: { + ...base.colors, + ...(mapValues( + editor.light, + (_, alias) => `var(--color-${alias})` + ) as Record), + }, + tokensCss: `${base.tokensCss}\n@layer tokens{${editorBlock( + `:root,[data-color-mode="${values.brand.initialMode}"]`, + values.brand.initialMode + )}${editorBlock(`[data-color-mode="${otherMode}"]`, otherMode)}}`, +}; export type PlatformThemeShape = typeof platformTheme; diff --git a/packages/gamut-styles/src/themes/skillsoft.ts b/packages/gamut-styles/src/themes/skillsoft.ts new file mode 100644 index 0000000000..b961a903a4 --- /dev/null +++ b/packages/gamut-styles/src/themes/skillsoft.ts @@ -0,0 +1,23 @@ +import * as values from '../tokens/generated/skillsoft/values'; +import { + fontWeightMediumTitle, + percipioFontFamily, + softElevation, +} from '../variables'; +import { createBrandTheme } from './createBrandTheme'; + +/* + * The default brand of the generated color system, and the theme gamut is + * typed against. Its type stands for every brand theme: they share palette + * keys and roles (createBrandTheme.ts). + */ +export const skillsoftTheme = createBrandTheme(values, { + name: 'skillsoft', + fontFamily: percipioFontFamily, + fontWeight: fontWeightMediumTitle, + elevation: softElevation, +}); + +export type SkillsoftThemeShape = typeof skillsoftTheme; + +export interface SkillsoftTheme extends SkillsoftThemeShape {} diff --git a/packages/gamut-styles/src/tokens/__tests__/generated.test.ts b/packages/gamut-styles/src/tokens/__tests__/generated.test.ts new file mode 100644 index 0000000000..699ef86738 --- /dev/null +++ b/packages/gamut-styles/src/tokens/__tests__/generated.test.ts @@ -0,0 +1,40 @@ +import { spawnSync } from 'node:child_process'; +import { dirname, join } from 'node:path'; + +import { brands, tokensCss } from '..'; + +/* + The generated folders are what @skillsoft/gamut-brand writes for gamut's + own brands today, tokens.css round-trip and every check included. A + subprocess because Style Dictionary is ESM-only and Jest can't load it. +*/ +describe('generated tokens', () => { + it('are current — run `yarn workspace @skillsoft/gamut-styles build:tokens`', () => { + const result = spawnSync( + process.execPath, + [ + join(dirname(require.resolve('jiti/package.json')), 'lib/jiti-cli.mjs'), + join(__dirname, '../../../scripts/build-tokens.ts'), + '--check', + ], + { encoding: 'utf8' } + ); + expect(`${result.stdout}${result.stderr}`).toContain( + 'generated tokens are current' + ); + expect(result.status).toBe(0); + }, 120000); + + /* Each brand carries its own colors, and no other brand's. */ + it.each(Object.entries(brands))('%s ships only itself', (name, values) => { + const named = [...values.tokensCss.matchAll(/data-brand="(\w+)"/g)].map( + (m) => m[1] + ); + expect(new Set(named)).toEqual(new Set([name])); + expect(values.brand.name).toBe(name); + }); + + it('exports the default brand at the top level', () => { + expect(tokensCss).toBe(brands.skillsoft.tokensCss); + }); +}); diff --git a/packages/gamut-styles/src/tokens/generated/admin/dark.tokens.json b/packages/gamut-styles/src/tokens/generated/admin/dark.tokens.json new file mode 100644 index 0000000000..53ae6377ed --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/admin/dark.tokens.json @@ -0,0 +1,315 @@ +{ + "$description": "dark mode roles for \"admin\". Do not edit.", + "surface-sunken": { + "$type": "color", + "$value": "{black}", + "$description": "Wells, insets, code blocks — anything recessed into the page." + }, + "surface-default": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "The page background — the base every other surface elevates from." + }, + "surface-raised": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits." + }, + "surface-success": { + "$type": "color", + "$value": "{palette.success.1000}", + "$description": "The success tint behind an alert or inline confirmation." + }, + "surface-warning": { + "$type": "color", + "$value": "{palette.warning.1000}", + "$description": "The warning tint behind an alert or inline caution." + }, + "surface-error": { + "$type": "color", + "$value": "{palette.danger.1000}", + "$description": "The error tint behind an alert or inline validation message." + }, + "surface-brand": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.accent-alt.900}", + "radius": 0.85, + "at": "100% -5%" + }, + { + "color": "{palette.primary.900}", + "radius": 1.21, + "at": "5% 100%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.3346 0.1696 287.1 / 0.45) 0%, oklch(0.3346 0.1696 287.1 / 0.43) 16%, oklch(0.3346 0.1696 287.1 / 0.24) 36%, oklch(0.3346 0.1696 287.1 / 0.11) 56%, oklch(0.3346 0.1696 287.1 / 0.04) 76%, oklch(0.3346 0.1696 287.1 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.3346 0.1591 263.6 / 0.45) 0%, oklch(0.3346 0.1591 263.6 / 0.43) 16%, oklch(0.3346 0.1591 263.6 / 0.24) 36%, oklch(0.3346 0.1591 263.6 / 0.11) 56%, oklch(0.3346 0.1591 263.6 / 0.04) 76%, oklch(0.3346 0.1591 263.6 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-900) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%)" + } + } + } + }, + "surface-brand-subtle": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.primary.900}", + "radius": 0.78, + "at": "50% -10%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.3346 0.1591 263.6 / 0.45) 0%, oklch(0.3346 0.1591 263.6 / 0.43) 16%, oklch(0.3346 0.1591 263.6 / 0.24) 36%, oklch(0.3346 0.1591 263.6 / 0.11) 56%, oklch(0.3346 0.1591 263.6 / 0.04) 76%, oklch(0.3346 0.1591 263.6 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%)" + } + } + } + }, + "text-default": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "Body copy and headings." + }, + "text-subtle": { + "$type": "color", + "$value": "{palette.neutral-light.400}", + "$description": "Secondary text — descriptions, metadata, helper copy." + }, + "text-inverse": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "Labels on a filled control — action-primary, -neutral, -danger." + }, + "text-success": { + "$type": "color", + "$value": "{palette.success.300}", + "$description": "Success text and icons." + }, + "text-warning": { + "$type": "color", + "$value": "{palette.warning.300}", + "$description": "Warning text and icons. Always with an icon." + }, + "text-error": { + "$type": "color", + "$value": "{palette.danger.300}", + "$description": "Error text and icons." + }, + "border-strong": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "Emphasis — an active field, a selected row." + }, + "border-default": { + "$type": "color", + "$value": "{palette.neutral-light.600}", + "$description": "Any border that carries meaning — control outlines, field borders." + }, + "border-subtle": { + "$type": "color", + "$value": "{palette.neutral-light.900}", + "$description": "Decorative hairlines — table rules, dividers. Contrast-exempt." + }, + "action-primary": { + "$type": "color", + "$value": "{palette.accent.300}", + "$description": "Primary interactive fills, strokes, links and focus rings." + }, + "action-primary-hover": { + "$type": "color", + "$value": "{palette.accent.400}", + "$description": "Hover for action-primary." + }, + "action-primary-pressed": { + "$type": "color", + "$value": "{palette.accent.500}", + "$description": "Pressed for action-primary." + }, + "action-danger": { + "$type": "color", + "$value": "{palette.danger.400}", + "$description": "Destructive actions — delete, remove, revoke." + }, + "action-danger-hover": { + "$type": "color", + "$value": "{palette.danger.300}", + "$description": "Hover for action-danger." + }, + "action-danger-pressed": { + "$type": "color", + "$value": "{palette.danger.200}", + "$description": "Pressed for action-danger." + }, + "action-neutral": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "The neutral action fill. Button variant=\"secondary\" resolves here." + }, + "action-neutral-hover": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "Hover for action-neutral." + }, + "action-neutral-pressed": { + "$type": "color", + "$value": "{white}", + "$description": "Pressed for action-neutral." + }, + "action-disabled": { + "$type": "color", + "$value": "{palette.neutral-dark.800}", + "$description": "The disabled fill for every action variant. Contrast-exempt (inactive control)." + }, + "state-hover": { + "$type": "color", + "$value": "{palette.primary.600-a6}", + "$description": "Rest → hover on rows, tabs, chips, menu items." + }, + "state-pressed": { + "$type": "color", + "$value": "{palette.primary.600-a9}", + "$description": "Hover → pressed on the same elements." + }, + "state-selected": { + "$type": "color", + "$value": "{palette.primary.600-a8}", + "$description": "The persistent \"this one is chosen\" tint." + }, + "viz-1": { + "$type": "color", + "$value": "{palette.primary.500}", + "$description": "Categorical series 1." + }, + "viz-2": { + "$type": "color", + "$value": "{palette.accent.500}", + "$description": "Categorical series 2." + }, + "viz-3": { + "$type": "color", + "$value": "{palette.warning.400}", + "$description": "Categorical series 3." + }, + "viz-4": { + "$type": "color", + "$value": "{palette.accent-alt.600}", + "$description": "Categorical series 4." + }, + "viz-5": { + "$type": "color", + "$value": "{palette.success.300}", + "$description": "Categorical series 5." + }, + "viz-6": { + "$type": "color", + "$value": "{palette.danger.600}", + "$description": "Categorical series 6." + }, + "viz-7": { + "$type": "color", + "$value": "{palette.neutral-dark.500}", + "$description": "Categorical series 7." + }, + "viz-8": { + "$type": "color", + "$value": "{palette.neutral-light.600}", + "$description": "Categorical series 8." + }, + "viz-seq-1": { + "$type": "color", + "$value": "{palette.primary.600}", + "$description": "Sequential step 1, low to high." + }, + "viz-seq-2": { + "$type": "color", + "$value": "{palette.primary.500}", + "$description": "Sequential step 2, low to high." + }, + "viz-seq-3": { + "$type": "color", + "$value": "{palette.primary.400}", + "$description": "Sequential step 3, low to high." + }, + "viz-seq-4": { + "$type": "color", + "$value": "{palette.primary.300}", + "$description": "Sequential step 4, low to high." + }, + "shadow-key": { + "$type": "color", + "$value": "{black-a32}", + "$description": "The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur." + }, + "shadow-ambient": { + "$type": "color", + "$value": "{black-a24}", + "$description": "The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff." + }, + "shadow-scrim": { + "$type": "color", + "$value": "{black-a60}", + "$description": "The scrim behind modals and dialogs. Mode-independent." + } +} diff --git a/packages/gamut-styles/src/tokens/generated/admin/light.tokens.json b/packages/gamut-styles/src/tokens/generated/admin/light.tokens.json new file mode 100644 index 0000000000..e42321eb08 --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/admin/light.tokens.json @@ -0,0 +1,315 @@ +{ + "$description": "light mode roles for \"admin\". Do not edit.", + "surface-sunken": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "Wells, insets, code blocks — anything recessed into the page." + }, + "surface-default": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "The page background — the base every other surface elevates from." + }, + "surface-raised": { + "$type": "color", + "$value": "{white}", + "$description": "The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits." + }, + "surface-success": { + "$type": "color", + "$value": "{palette.success.200}", + "$description": "The success tint behind an alert or inline confirmation." + }, + "surface-warning": { + "$type": "color", + "$value": "{palette.warning.200}", + "$description": "The warning tint behind an alert or inline caution." + }, + "surface-error": { + "$type": "color", + "$value": "{palette.danger.200}", + "$description": "The error tint behind an alert or inline validation message." + }, + "surface-brand": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.accent-alt.300}", + "radius": 0.85, + "at": "100% -5%" + }, + { + "color": "{palette.primary.300}", + "radius": 1.21, + "at": "5% 100%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.8497 0.043 287.1 / 0.45) 0%, oklch(0.8497 0.043 287.1 / 0.43) 16%, oklch(0.8497 0.043 287.1 / 0.24) 36%, oklch(0.8497 0.043 287.1 / 0.11) 56%, oklch(0.8497 0.043 287.1 / 0.04) 76%, oklch(0.8497 0.043 287.1 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.8497 0.0406 263.6 / 0.45) 0%, oklch(0.8497 0.0406 263.6 / 0.43) 16%, oklch(0.8497 0.0406 263.6 / 0.24) 36%, oklch(0.8497 0.0406 263.6 / 0.11) 56%, oklch(0.8497 0.0406 263.6 / 0.04) 76%, oklch(0.8497 0.0406 263.6 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-300) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%)" + } + } + } + }, + "surface-brand-subtle": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.primary.300}", + "radius": 0.78, + "at": "50% -10%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.8497 0.0406 263.6 / 0.45) 0%, oklch(0.8497 0.0406 263.6 / 0.43) 16%, oklch(0.8497 0.0406 263.6 / 0.24) 36%, oklch(0.8497 0.0406 263.6 / 0.11) 56%, oklch(0.8497 0.0406 263.6 / 0.04) 76%, oklch(0.8497 0.0406 263.6 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%)" + } + } + } + }, + "text-default": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "Body copy and headings." + }, + "text-subtle": { + "$type": "color", + "$value": "{palette.neutral-dark.800}", + "$description": "Secondary text — descriptions, metadata, helper copy." + }, + "text-inverse": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "Labels on a filled control — action-primary, -neutral, -danger." + }, + "text-success": { + "$type": "color", + "$value": "{palette.success.900}", + "$description": "Success text and icons." + }, + "text-warning": { + "$type": "color", + "$value": "{palette.warning.900}", + "$description": "Warning text and icons. Always with an icon." + }, + "text-error": { + "$type": "color", + "$value": "{palette.danger.900}", + "$description": "Error text and icons." + }, + "border-strong": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "Emphasis — an active field, a selected row." + }, + "border-default": { + "$type": "color", + "$value": "{palette.neutral-dark.600}", + "$description": "Any border that carries meaning — control outlines, field borders." + }, + "border-subtle": { + "$type": "color", + "$value": "{palette.neutral-dark.300}", + "$description": "Decorative hairlines — table rules, dividers. Contrast-exempt." + }, + "action-primary": { + "$type": "color", + "$value": "{palette.primary.800}", + "$description": "Primary interactive fills, strokes, links and focus rings." + }, + "action-primary-hover": { + "$type": "color", + "$value": "{palette.primary.900}", + "$description": "Hover for action-primary." + }, + "action-primary-pressed": { + "$type": "color", + "$value": "{palette.primary.1000}", + "$description": "Pressed for action-primary." + }, + "action-danger": { + "$type": "color", + "$value": "{palette.danger.800}", + "$description": "Destructive actions — delete, remove, revoke." + }, + "action-danger-hover": { + "$type": "color", + "$value": "{palette.danger.900}", + "$description": "Hover for action-danger." + }, + "action-danger-pressed": { + "$type": "color", + "$value": "{palette.danger.1000}", + "$description": "Pressed for action-danger." + }, + "action-neutral": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "The neutral action fill. Button variant=\"secondary\" resolves here." + }, + "action-neutral-hover": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "Hover for action-neutral." + }, + "action-neutral-pressed": { + "$type": "color", + "$value": "{black}", + "$description": "Pressed for action-neutral." + }, + "action-disabled": { + "$type": "color", + "$value": "{palette.neutral-light.400}", + "$description": "The disabled fill for every action variant. Contrast-exempt (inactive control)." + }, + "state-hover": { + "$type": "color", + "$value": "{palette.primary.600-a6}", + "$description": "Rest → hover on rows, tabs, chips, menu items." + }, + "state-pressed": { + "$type": "color", + "$value": "{palette.primary.600-a9}", + "$description": "Hover → pressed on the same elements." + }, + "state-selected": { + "$type": "color", + "$value": "{palette.primary.600-a8}", + "$description": "The persistent \"this one is chosen\" tint." + }, + "viz-1": { + "$type": "color", + "$value": "{palette.primary.800}", + "$description": "Categorical series 1." + }, + "viz-2": { + "$type": "color", + "$value": "{palette.accent.700}", + "$description": "Categorical series 2." + }, + "viz-3": { + "$type": "color", + "$value": "{palette.warning.700}", + "$description": "Categorical series 3." + }, + "viz-4": { + "$type": "color", + "$value": "{palette.accent-alt.600}", + "$description": "Categorical series 4." + }, + "viz-5": { + "$type": "color", + "$value": "{palette.success.600}", + "$description": "Categorical series 5." + }, + "viz-6": { + "$type": "color", + "$value": "{palette.danger.600}", + "$description": "Categorical series 6." + }, + "viz-7": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "Categorical series 7." + }, + "viz-8": { + "$type": "color", + "$value": "{palette.neutral-light.600}", + "$description": "Categorical series 8." + }, + "viz-seq-1": { + "$type": "color", + "$value": "{palette.primary.600}", + "$description": "Sequential step 1, low to high." + }, + "viz-seq-2": { + "$type": "color", + "$value": "{palette.primary.700}", + "$description": "Sequential step 2, low to high." + }, + "viz-seq-3": { + "$type": "color", + "$value": "{palette.primary.800}", + "$description": "Sequential step 3, low to high." + }, + "viz-seq-4": { + "$type": "color", + "$value": "{palette.primary.900}", + "$description": "Sequential step 4, low to high." + }, + "shadow-key": { + "$type": "color", + "$value": "{palette.neutral-dark.1100-a16}", + "$description": "The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur." + }, + "shadow-ambient": { + "$type": "color", + "$value": "{palette.neutral-dark.1100-a12}", + "$description": "The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff." + }, + "shadow-scrim": { + "$type": "color", + "$value": "{black-a60}", + "$description": "The scrim behind modals and dialogs. Mode-independent." + } +} diff --git a/packages/gamut-styles/src/tokens/generated/admin/palette.tokens.json b/packages/gamut-styles/src/tokens/generated/admin/palette.tokens.json new file mode 100644 index 0000000000..d32f4d0979 --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/admin/palette.tokens.json @@ -0,0 +1,1745 @@ +{ + "$description": "Generated from the \"admin\" brand (tier 3). Do not edit.", + "white": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 1, + 0, + 0 + ], + "alpha": 1, + "hex": "#ffffff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "black": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 1, + "hex": "#000000" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "palette": { + "neutral-light": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0016, + 58.8 + ], + "alpha": 1, + "hex": "#fbfaf9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0031, + 58.8 + ], + "alpha": 1, + "hex": "#ece9e8" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0109, + 58.8 + ], + "alpha": 1, + "hex": "#d3ccc7" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0237, + 58.8 + ], + "alpha": 1, + "hex": "#beafa3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.028, + 58.8 + ], + "alpha": 1, + "hex": "#a39285" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.0277, + 58.8 + ], + "alpha": 1, + "hex": "#88786b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.0224, + 58.8 + ], + "alpha": 1, + "hex": "#6d6056" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0175, + 58.8 + ], + "alpha": 1, + "hex": "#524942" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0139, + 58.8 + ], + "alpha": 1, + "hex": "#3c3530" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0099, + 58.8 + ], + "alpha": 1, + "hex": "#25211d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0073, + 58.8 + ], + "alpha": 1, + "hex": "#171411" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "neutral-dark": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0012, + 271.3 + ], + "alpha": 1, + "hex": "#fafafb" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0024, + 271.3 + ], + "alpha": 1, + "hex": "#e9eaeb" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0081, + 271.3 + ], + "alpha": 1, + "hex": "#cbcdd3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0169, + 271.3 + ], + "alpha": 1, + "hex": "#aeb2bd" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.0306, + 271.3 + ], + "alpha": 1, + "hex": "#8e95a9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.0454, + 271.3 + ], + "alpha": 1, + "hex": "#717a97" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.0538, + 271.3 + ], + "alpha": 1, + "hex": "#576182" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0479, + 271.3 + ], + "alpha": 1, + "hex": "#414965" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0381, + 271.3 + ], + "alpha": 1, + "hex": "#2f364b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0272, + 271.3 + ], + "alpha": 1, + "hex": "#1d212f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0199, + 271.3 + ], + "alpha": 1, + "hex": "#11141d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100-a16": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0199, + 271.3 + ], + "alpha": 0.16, + "hex": "#11141d" + } + }, + "1100-a12": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0199, + 271.3 + ], + "alpha": 0.12, + "hex": "#11141d" + } + } + }, + "primary": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0064, + 263.6 + ], + "alpha": 1, + "hex": "#f8fbff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.012, + 263.6 + ], + "alpha": 1, + "hex": "#e6eaf2" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0406, + 263.6 + ], + "alpha": 1, + "hex": "#c0cee9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0845, + 263.6 + ], + "alpha": 1, + "hex": "#96b2e8" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1528, + 263.6 + ], + "alpha": 1, + "hex": "#6492f3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2271, + 263.6 + ], + "alpha": 1, + "hex": "#2f6dff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.2553, + 263.6 + ], + "alpha": 1, + "hex": "#0748f0" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.2002, + 263.6 + ], + "alpha": 1, + "hex": "#0939b5" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.1591, + 263.6 + ], + "alpha": 1, + "hex": "#062a86" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.1134, + 263.6 + ], + "alpha": 1, + "hex": "#041a57" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0832, + 263.6 + ], + "alpha": 1, + "hex": "#020f39" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "600-a6": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2271, + 263.6 + ], + "alpha": 0.06, + "hex": "#2f6dff" + } + }, + "600-a9": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2271, + 263.6 + ], + "alpha": 0.09, + "hex": "#2f6dff" + } + }, + "600-a8": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2271, + 263.6 + ], + "alpha": 0.08, + "hex": "#2f6dff" + } + } + }, + "accent": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0205, + 93.6 + ], + "alpha": 1, + "hex": "#fffbeb" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0378, + 93.6 + ], + "alpha": 1, + "hex": "#f2eace" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8789, + 0.1801, + 93.58 + ], + "alpha": 1, + "hex": "#ffd300" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7442, + 0.1526, + 93.94 + ], + "alpha": 1, + "hex": "#cca900" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1209, + 93.6 + ], + "alpha": 1, + "hex": "#ae932e" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.1195, + 93.6 + ], + "alpha": 1, + "hex": "#937900" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.0967, + 93.6 + ], + "alpha": 1, + "hex": "#75610e" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0758, + 93.6 + ], + "alpha": 1, + "hex": "#594a10" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0603, + 93.6 + ], + "alpha": 1, + "hex": "#41360b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.043, + 93.6 + ], + "alpha": 1, + "hex": "#282106" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0315, + 93.6 + ], + "alpha": 1, + "hex": "#191404" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "accent-alt": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0067, + 287.1 + ], + "alpha": 1, + "hex": "#fafaff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0127, + 287.1 + ], + "alpha": 1, + "hex": "#e9e9f2" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.043, + 287.1 + ], + "alpha": 1, + "hex": "#cbcae9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0896, + 287.1 + ], + "alpha": 1, + "hex": "#aeaae9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1624, + 287.1 + ], + "alpha": 1, + "hex": "#9182f3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2416, + 287.1 + ], + "alpha": 1, + "hex": "#7a52ff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.2722, + 287.1 + ], + "alpha": 1, + "hex": "#661af0" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.2134, + 287.1 + ], + "alpha": 1, + "hex": "#4d1cb5" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.1696, + 287.1 + ], + "alpha": 1, + "hex": "#381587" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.1209, + 287.1 + ], + "alpha": 1, + "hex": "#220d57" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0887, + 287.1 + ], + "alpha": 1, + "hex": "#150739" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "danger": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0066, + 29.4 + ], + "alpha": 1, + "hex": "#fff9f8" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0128, + 29.4 + ], + "alpha": 1, + "hex": "#f2e7e5" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0452, + 29.4 + ], + "alpha": 1, + "hex": "#eac3bd" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.099, + 29.4 + ], + "alpha": 1, + "hex": "#ea9a8d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.191, + 29.4 + ], + "alpha": 1, + "hex": "#f45b4a" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2382, + 29.4 + ], + "alpha": 1, + "hex": "#e70400" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.1928, + 29.4 + ], + "alpha": 1, + "hex": "#b7160d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.1512, + 29.4 + ], + "alpha": 1, + "hex": "#8a170f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.1202, + 29.4 + ], + "alpha": 1, + "hex": "#67110a" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0857, + 29.4 + ], + "alpha": 1, + "hex": "#420a06" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0628, + 29.4 + ], + "alpha": 1, + "hex": "#2b0603" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "warning": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0082, + 58.3 + ], + "alpha": 1, + "hex": "#fff9f5" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0156, + 58.3 + ], + "alpha": 1, + "hex": "#f2e8e0" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0545, + 58.3 + ], + "alpha": 1, + "hex": "#eac6ab" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.1179, + 58.3 + ], + "alpha": 1, + "hex": "#ea9f63" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1408, + 58.3 + ], + "alpha": 1, + "hex": "#d47d2d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.1392, + 58.3 + ], + "alpha": 1, + "hex": "#b66200" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.1127, + 58.3 + ], + "alpha": 1, + "hex": "#914f0e" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0883, + 58.3 + ], + "alpha": 1, + "hex": "#6d3d10" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0702, + 58.3 + ], + "alpha": 1, + "hex": "#512c0b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.05, + 58.3 + ], + "alpha": 1, + "hex": "#331b06" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0367, + 58.3 + ], + "alpha": 1, + "hex": "#201004" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "success": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0228, + 145.5 + ], + "alpha": 1, + "hex": "#f1fff1" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0458, + 145.5 + ], + "alpha": 1, + "hex": "#d8f3d8" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.1448, + 145.5 + ], + "alpha": 1, + "hex": "#8de792" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.1654, + 145.5 + ], + "alpha": 1, + "hex": "#64cd6d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1829, + 145.5 + ], + "alpha": 1, + "hex": "#34b147" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.1808, + 145.5 + ], + "alpha": 1, + "hex": "#00952b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.1464, + 145.5 + ], + "alpha": 1, + "hex": "#117726" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.1147, + 145.5 + ], + "alpha": 1, + "hex": "#135a1f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0912, + 145.5 + ], + "alpha": 1, + "hex": "#0e4216" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.065, + 145.5 + ], + "alpha": 1, + "hex": "#08290c" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0477, + 145.5 + ], + "alpha": 1, + "hex": "#041a06" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + } + }, + "black-a32": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 0.32, + "hex": "#000000" + } + }, + "black-a24": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 0.24, + "hex": "#000000" + } + }, + "black-a60": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 0.6, + "hex": "#000000" + } + } +} diff --git a/packages/gamut-styles/src/tokens/generated/admin/tokens.css b/packages/gamut-styles/src/tokens/generated/admin/tokens.css new file mode 100644 index 0000000000..6e2eeb2f5e --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/admin/tokens.css @@ -0,0 +1,211 @@ +/* Generated by @skillsoft/gamut-brand from the "admin" brand. Do not edit. */ +@layer reset, base, tokens, recipes, utilities; + +@layer tokens { + :root, [data-brand="admin"] { + --color-white: oklch(1.0000 0.0000 0.00); + --color-black: oklch(0.0000 0.0000 0.00); + --color-palette-neutral-light-100: oklch(0.9862 0.0016 58.80); + --color-palette-neutral-light-200: oklch(0.9364 0.0031 58.80); + --color-palette-neutral-light-300: oklch(0.8497 0.0109 58.80); + --color-palette-neutral-light-400: oklch(0.7628 0.0237 58.80); + --color-palette-neutral-light-500: oklch(0.6707 0.0280 58.80); + --color-palette-neutral-light-600: oklch(0.5835 0.0277 58.80); + --color-palette-neutral-light-700: oklch(0.4972 0.0224 58.80); + --color-palette-neutral-light-800: oklch(0.4115 0.0175 58.80); + --color-palette-neutral-light-900: oklch(0.3346 0.0139 58.80); + --color-palette-neutral-light-1000: oklch(0.2499 0.0099 58.80); + --color-palette-neutral-light-1100: oklch(0.1925 0.0073 58.80); + --color-palette-neutral-dark-100: oklch(0.9862 0.0012 271.30); + --color-palette-neutral-dark-200: oklch(0.9364 0.0024 271.30); + --color-palette-neutral-dark-300: oklch(0.8497 0.0081 271.30); + --color-palette-neutral-dark-400: oklch(0.7628 0.0169 271.30); + --color-palette-neutral-dark-500: oklch(0.6707 0.0306 271.30); + --color-palette-neutral-dark-600: oklch(0.5835 0.0454 271.30); + --color-palette-neutral-dark-700: oklch(0.4972 0.0538 271.30); + --color-palette-neutral-dark-800: oklch(0.4115 0.0479 271.30); + --color-palette-neutral-dark-900: oklch(0.3346 0.0381 271.30); + --color-palette-neutral-dark-1000: oklch(0.2499 0.0272 271.30); + --color-palette-neutral-dark-1100: oklch(0.1925 0.0199 271.30); + --color-palette-neutral-dark-1100-a16: oklch(0.1925 0.0199 271.30 / 0.16); + --color-palette-neutral-dark-1100-a12: oklch(0.1925 0.0199 271.30 / 0.12); + --color-palette-primary-100: oklch(0.9862 0.0064 263.60); + --color-palette-primary-200: oklch(0.9364 0.0120 263.60); + --color-palette-primary-300: oklch(0.8497 0.0406 263.60); + --color-palette-primary-400: oklch(0.7628 0.0845 263.60); + --color-palette-primary-500: oklch(0.6707 0.1528 263.60); + --color-palette-primary-600: oklch(0.5835 0.2271 263.60); + --color-palette-primary-700: oklch(0.4972 0.2553 263.60); + --color-palette-primary-800: oklch(0.4115 0.2002 263.60); + --color-palette-primary-900: oklch(0.3346 0.1591 263.60); + --color-palette-primary-1000: oklch(0.2499 0.1134 263.60); + --color-palette-primary-1100: oklch(0.1925 0.0832 263.60); + --color-palette-primary-600-a6: oklch(0.5835 0.2271 263.60 / 0.06); + --color-palette-primary-600-a9: oklch(0.5835 0.2271 263.60 / 0.09); + --color-palette-primary-600-a8: oklch(0.5835 0.2271 263.60 / 0.08); + --color-palette-accent-100: oklch(0.9862 0.0205 93.60); + --color-palette-accent-200: oklch(0.9364 0.0378 93.60); + --color-palette-accent-300: oklch(0.8789 0.1801 93.58); + --color-palette-accent-400: oklch(0.7442 0.1526 93.94); + --color-palette-accent-500: oklch(0.6707 0.1209 93.60); + --color-palette-accent-600: oklch(0.5835 0.1195 93.60); + --color-palette-accent-700: oklch(0.4972 0.0967 93.60); + --color-palette-accent-800: oklch(0.4115 0.0758 93.60); + --color-palette-accent-900: oklch(0.3346 0.0603 93.60); + --color-palette-accent-1000: oklch(0.2499 0.0430 93.60); + --color-palette-accent-1100: oklch(0.1925 0.0315 93.60); + --color-palette-accent-alt-100: oklch(0.9862 0.0067 287.10); + --color-palette-accent-alt-200: oklch(0.9364 0.0127 287.10); + --color-palette-accent-alt-300: oklch(0.8497 0.0430 287.10); + --color-palette-accent-alt-400: oklch(0.7628 0.0896 287.10); + --color-palette-accent-alt-500: oklch(0.6707 0.1624 287.10); + --color-palette-accent-alt-600: oklch(0.5835 0.2416 287.10); + --color-palette-accent-alt-700: oklch(0.4972 0.2722 287.10); + --color-palette-accent-alt-800: oklch(0.4115 0.2134 287.10); + --color-palette-accent-alt-900: oklch(0.3346 0.1696 287.10); + --color-palette-accent-alt-1000: oklch(0.2499 0.1209 287.10); + --color-palette-accent-alt-1100: oklch(0.1925 0.0887 287.10); + --color-palette-danger-100: oklch(0.9862 0.0066 29.40); + --color-palette-danger-200: oklch(0.9364 0.0128 29.40); + --color-palette-danger-300: oklch(0.8497 0.0452 29.40); + --color-palette-danger-400: oklch(0.7628 0.0990 29.40); + --color-palette-danger-500: oklch(0.6707 0.1910 29.40); + --color-palette-danger-600: oklch(0.5835 0.2382 29.40); + --color-palette-danger-700: oklch(0.4972 0.1928 29.40); + --color-palette-danger-800: oklch(0.4115 0.1512 29.40); + --color-palette-danger-900: oklch(0.3346 0.1202 29.40); + --color-palette-danger-1000: oklch(0.2499 0.0857 29.40); + --color-palette-danger-1100: oklch(0.1925 0.0628 29.40); + --color-palette-warning-100: oklch(0.9862 0.0082 58.30); + --color-palette-warning-200: oklch(0.9364 0.0156 58.30); + --color-palette-warning-300: oklch(0.8497 0.0545 58.30); + --color-palette-warning-400: oklch(0.7628 0.1179 58.30); + --color-palette-warning-500: oklch(0.6707 0.1408 58.30); + --color-palette-warning-600: oklch(0.5835 0.1392 58.30); + --color-palette-warning-700: oklch(0.4972 0.1127 58.30); + --color-palette-warning-800: oklch(0.4115 0.0883 58.30); + --color-palette-warning-900: oklch(0.3346 0.0702 58.30); + --color-palette-warning-1000: oklch(0.2499 0.0500 58.30); + --color-palette-warning-1100: oklch(0.1925 0.0367 58.30); + --color-palette-success-100: oklch(0.9862 0.0228 145.50); + --color-palette-success-200: oklch(0.9364 0.0458 145.50); + --color-palette-success-300: oklch(0.8497 0.1448 145.50); + --color-palette-success-400: oklch(0.7628 0.1654 145.50); + --color-palette-success-500: oklch(0.6707 0.1829 145.50); + --color-palette-success-600: oklch(0.5835 0.1808 145.50); + --color-palette-success-700: oklch(0.4972 0.1464 145.50); + --color-palette-success-800: oklch(0.4115 0.1147 145.50); + --color-palette-success-900: oklch(0.3346 0.0912 145.50); + --color-palette-success-1000: oklch(0.2499 0.0650 145.50); + --color-palette-success-1100: oklch(0.1925 0.0477 145.50); + --color-black-a32: oklch(0.0000 0.0000 0.00 / 0.32); + --color-black-a24: oklch(0.0000 0.0000 0.00 / 0.24); + --color-black-a60: oklch(0.0000 0.0000 0.00 / 0.6); + } + + :root, [data-color-mode="light"] { + color-scheme: light; + color: var(--color-text-default); + --color-background-current: var(--color-surface-default); + --color-surface-sunken: var(--color-palette-neutral-light-200); /** Wells, insets, code blocks — anything recessed into the page. */ + --color-surface-default: var(--color-palette-neutral-light-100); /** The page background — the base every other surface elevates from. */ + --color-surface-raised: var(--color-white); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */ + --color-surface-success: var(--color-palette-success-200); /** The success tint behind an alert or inline confirmation. */ + --color-surface-warning: var(--color-palette-warning-200); /** The warning tint behind an alert or inline caution. */ + --color-surface-error: var(--color-palette-danger-200); /** The error tint behind an alert or inline validation message. */ + --color-surface-brand: var(--color-palette-neutral-light-100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */ + --color-surface-brand-subtle: var(--color-palette-neutral-light-100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */ + --color-text-default: var(--color-palette-neutral-dark-1000); /** Body copy and headings. */ + --color-text-subtle: var(--color-palette-neutral-dark-800); /** Secondary text — descriptions, metadata, helper copy. */ + --color-text-inverse: var(--color-palette-neutral-light-200); /** Labels on a filled control — action-primary, -neutral, -danger. */ + --color-text-success: var(--color-palette-success-900); /** Success text and icons. */ + --color-text-warning: var(--color-palette-warning-900); /** Warning text and icons. Always with an icon. */ + --color-text-error: var(--color-palette-danger-900); /** Error text and icons. */ + --color-border-strong: var(--color-palette-neutral-dark-1100); /** Emphasis — an active field, a selected row. */ + --color-border-default: var(--color-palette-neutral-dark-600); /** Any border that carries meaning — control outlines, field borders. */ + --color-border-subtle: var(--color-palette-neutral-dark-300); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */ + --color-action-primary: var(--color-palette-primary-800); /** Primary interactive fills, strokes, links and focus rings. */ + --color-action-primary-hover: var(--color-palette-primary-900); /** Hover for action-primary. */ + --color-action-primary-pressed: var(--color-palette-primary-1000); /** Pressed for action-primary. */ + --color-action-danger: var(--color-palette-danger-800); /** Destructive actions — delete, remove, revoke. */ + --color-action-danger-hover: var(--color-palette-danger-900); /** Hover for action-danger. */ + --color-action-danger-pressed: var(--color-palette-danger-1000); /** Pressed for action-danger. */ + --color-action-neutral: var(--color-palette-neutral-dark-1000); /** The neutral action fill. Button variant="secondary" resolves here. */ + --color-action-neutral-hover: var(--color-palette-neutral-dark-1100); /** Hover for action-neutral. */ + --color-action-neutral-pressed: var(--color-black); /** Pressed for action-neutral. */ + --color-action-disabled: var(--color-palette-neutral-light-400); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */ + --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */ + --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */ + --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent "this one is chosen" tint. */ + --color-viz-1: var(--color-palette-primary-800); /** Categorical series 1. */ + --color-viz-2: var(--color-palette-accent-700); /** Categorical series 2. */ + --color-viz-3: var(--color-palette-warning-700); /** Categorical series 3. */ + --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */ + --color-viz-5: var(--color-palette-success-600); /** Categorical series 5. */ + --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */ + --color-viz-7: var(--color-palette-neutral-dark-1000); /** Categorical series 7. */ + --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */ + --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */ + --color-viz-seq-2: var(--color-palette-primary-700); /** Sequential step 2, low to high. */ + --color-viz-seq-3: var(--color-palette-primary-800); /** Sequential step 3, low to high. */ + --color-viz-seq-4: var(--color-palette-primary-900); /** Sequential step 4, low to high. */ + --color-shadow-key: var(--color-palette-neutral-dark-1100-a16); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */ + --color-shadow-ambient: var(--color-palette-neutral-dark-1100-a12); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */ + --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */ + --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-300) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%); + --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%); + } + + [data-color-mode="dark"] { + color-scheme: dark; + color: var(--color-text-default); + --color-background-current: var(--color-surface-default); + --color-surface-sunken: var(--color-black); /** Wells, insets, code blocks — anything recessed into the page. */ + --color-surface-default: var(--color-palette-neutral-dark-1100); /** The page background — the base every other surface elevates from. */ + --color-surface-raised: var(--color-palette-neutral-dark-1000); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */ + --color-surface-success: var(--color-palette-success-1000); /** The success tint behind an alert or inline confirmation. */ + --color-surface-warning: var(--color-palette-warning-1000); /** The warning tint behind an alert or inline caution. */ + --color-surface-error: var(--color-palette-danger-1000); /** The error tint behind an alert or inline validation message. */ + --color-surface-brand: var(--color-palette-neutral-dark-1100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */ + --color-surface-brand-subtle: var(--color-palette-neutral-dark-1100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */ + --color-text-default: var(--color-palette-neutral-light-200); /** Body copy and headings. */ + --color-text-subtle: var(--color-palette-neutral-light-400); /** Secondary text — descriptions, metadata, helper copy. */ + --color-text-inverse: var(--color-palette-neutral-dark-1000); /** Labels on a filled control — action-primary, -neutral, -danger. */ + --color-text-success: var(--color-palette-success-300); /** Success text and icons. */ + --color-text-warning: var(--color-palette-warning-300); /** Warning text and icons. Always with an icon. */ + --color-text-error: var(--color-palette-danger-300); /** Error text and icons. */ + --color-border-strong: var(--color-palette-neutral-light-100); /** Emphasis — an active field, a selected row. */ + --color-border-default: var(--color-palette-neutral-light-600); /** Any border that carries meaning — control outlines, field borders. */ + --color-border-subtle: var(--color-palette-neutral-light-900); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */ + --color-action-primary: var(--color-palette-accent-300); /** Primary interactive fills, strokes, links and focus rings. */ + --color-action-primary-hover: var(--color-palette-accent-400); /** Hover for action-primary. */ + --color-action-primary-pressed: var(--color-palette-accent-500); /** Pressed for action-primary. */ + --color-action-danger: var(--color-palette-danger-400); /** Destructive actions — delete, remove, revoke. */ + --color-action-danger-hover: var(--color-palette-danger-300); /** Hover for action-danger. */ + --color-action-danger-pressed: var(--color-palette-danger-200); /** Pressed for action-danger. */ + --color-action-neutral: var(--color-palette-neutral-light-200); /** The neutral action fill. Button variant="secondary" resolves here. */ + --color-action-neutral-hover: var(--color-palette-neutral-light-100); /** Hover for action-neutral. */ + --color-action-neutral-pressed: var(--color-white); /** Pressed for action-neutral. */ + --color-action-disabled: var(--color-palette-neutral-dark-800); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */ + --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */ + --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */ + --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent "this one is chosen" tint. */ + --color-viz-1: var(--color-palette-primary-500); /** Categorical series 1. */ + --color-viz-2: var(--color-palette-accent-500); /** Categorical series 2. */ + --color-viz-3: var(--color-palette-warning-400); /** Categorical series 3. */ + --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */ + --color-viz-5: var(--color-palette-success-300); /** Categorical series 5. */ + --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */ + --color-viz-7: var(--color-palette-neutral-dark-500); /** Categorical series 7. */ + --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */ + --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */ + --color-viz-seq-2: var(--color-palette-primary-500); /** Sequential step 2, low to high. */ + --color-viz-seq-3: var(--color-palette-primary-400); /** Sequential step 3, low to high. */ + --color-viz-seq-4: var(--color-palette-primary-300); /** Sequential step 4, low to high. */ + --color-shadow-key: var(--color-black-a32); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */ + --color-shadow-ambient: var(--color-black-a24); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */ + --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */ + --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-900) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%); + --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%); + } +} diff --git a/packages/gamut-styles/src/tokens/generated/admin/values.ts b/packages/gamut-styles/src/tokens/generated/admin/values.ts new file mode 100644 index 0000000000..2593ab0041 --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/admin/values.ts @@ -0,0 +1,1044 @@ +/* Generated by @skillsoft/gamut-brand from the "admin" brand. Do not edit. */ + +export const brand = { + "name": "admin", + "initialMode": "light", + "hues": { + "neutral-light": 58.8, + "neutral-dark": 271.3, + "primary": 263.6, + "accent": 93.6, + "danger": 29.4, + "warning": 58.3, + "success": 145.5, + "accent-alt": 287.1 + }, + "derived": { + "accent-alt": "held at the gamut default" + }, + "tier": 3, + "overrides": { + "palette": { + "accent-300": "#ffd300", + "accent-400": "#cca900" + }, + "roles": { + "dark": { + "action-primary": { + "slot": "accent", + "grade": 300 + }, + "action-primary-hover": { + "slot": "accent", + "grade": 400 + }, + "action-primary-pressed": { + "slot": "accent", + "grade": 500 + } + } + }, + "gradients": { + "surface-brand": { + "accent": "accent-alt" + } + } + }, + "sources": { + "neutral-light": { + "hex": "#fff0e5", + "nearest": 200, + "deltaE": 6.23 + }, + "neutral-dark": { + "hex": "#10162f", + "nearest": 1000, + "deltaE": 6.56 + }, + "primary": { + "hex": "#1557ff", + "nearest": 700, + "deltaE": 4.85 + }, + "accent": { + "hex": "#ffd300", + "nearest": 300, + "deltaE": 0 + }, + "danger": { + "hex": "#e91c11", + "nearest": 600, + "deltaE": 1.79 + }, + "warning": { + "hex": "#ff8c00", + "nearest": 500, + "deltaE": 8.94 + }, + "success": { + "hex": "#008a27", + "nearest": 600, + "deltaE": 3.86 + } + } +} as const; + +export const palette = { + "white": { + "oklch": "oklch(1 0 0)", + "srgb": "#ffffff" + }, + "black": { + "oklch": "oklch(0 0 0)", + "srgb": "#000000" + }, + "palette-neutral-light-100": { + "oklch": "oklch(0.9862 0.0016 58.8)", + "srgb": "#fbfaf9" + }, + "palette-neutral-light-200": { + "oklch": "oklch(0.9364 0.0031 58.8)", + "srgb": "#ece9e8" + }, + "palette-neutral-light-300": { + "oklch": "oklch(0.8497 0.0109 58.8)", + "srgb": "#d3ccc7" + }, + "palette-neutral-light-400": { + "oklch": "oklch(0.7628 0.0237 58.8)", + "srgb": "#beafa3" + }, + "palette-neutral-light-500": { + "oklch": "oklch(0.6707 0.028 58.8)", + "srgb": "#a39285" + }, + "palette-neutral-light-600": { + "oklch": "oklch(0.5835 0.0277 58.8)", + "srgb": "#88786b" + }, + "palette-neutral-light-700": { + "oklch": "oklch(0.4972 0.0224 58.8)", + "srgb": "#6d6056" + }, + "palette-neutral-light-800": { + "oklch": "oklch(0.4115 0.0175 58.8)", + "srgb": "#524942" + }, + "palette-neutral-light-900": { + "oklch": "oklch(0.3346 0.0139 58.8)", + "srgb": "#3c3530" + }, + "palette-neutral-light-1000": { + "oklch": "oklch(0.2499 0.0099 58.8)", + "srgb": "#25211d" + }, + "palette-neutral-light-1100": { + "oklch": "oklch(0.1925 0.0073 58.8)", + "srgb": "#171411" + }, + "palette-neutral-dark-100": { + "oklch": "oklch(0.9862 0.0012 271.3)", + "srgb": "#fafafb" + }, + "palette-neutral-dark-200": { + "oklch": "oklch(0.9364 0.0024 271.3)", + "srgb": "#e9eaeb" + }, + "palette-neutral-dark-300": { + "oklch": "oklch(0.8497 0.0081 271.3)", + "srgb": "#cbcdd3" + }, + "palette-neutral-dark-400": { + "oklch": "oklch(0.7628 0.0169 271.3)", + "srgb": "#aeb2bd" + }, + "palette-neutral-dark-500": { + "oklch": "oklch(0.6707 0.0306 271.3)", + "srgb": "#8e95a9" + }, + "palette-neutral-dark-600": { + "oklch": "oklch(0.5835 0.0454 271.3)", + "srgb": "#717a97" + }, + "palette-neutral-dark-700": { + "oklch": "oklch(0.4972 0.0538 271.3)", + "srgb": "#576182" + }, + "palette-neutral-dark-800": { + "oklch": "oklch(0.4115 0.0479 271.3)", + "srgb": "#414965" + }, + "palette-neutral-dark-900": { + "oklch": "oklch(0.3346 0.0381 271.3)", + "srgb": "#2f364b" + }, + "palette-neutral-dark-1000": { + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "palette-neutral-dark-1100": { + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "palette-primary-100": { + "oklch": "oklch(0.9862 0.0064 263.6)", + "srgb": "#f8fbff" + }, + "palette-primary-200": { + "oklch": "oklch(0.9364 0.012 263.6)", + "srgb": "#e6eaf2" + }, + "palette-primary-300": { + "oklch": "oklch(0.8497 0.0406 263.6)", + "srgb": "#c0cee9" + }, + "palette-primary-400": { + "oklch": "oklch(0.7628 0.0845 263.6)", + "srgb": "#96b2e8" + }, + "palette-primary-500": { + "oklch": "oklch(0.6707 0.1528 263.6)", + "srgb": "#6492f3" + }, + "palette-primary-600": { + "oklch": "oklch(0.5835 0.2271 263.6)", + "srgb": "#2f6dff" + }, + "palette-primary-700": { + "oklch": "oklch(0.4972 0.2553 263.6)", + "srgb": "#0748f0" + }, + "palette-primary-800": { + "oklch": "oklch(0.4115 0.2002 263.6)", + "srgb": "#0939b5" + }, + "palette-primary-900": { + "oklch": "oklch(0.3346 0.1591 263.6)", + "srgb": "#062a86" + }, + "palette-primary-1000": { + "oklch": "oklch(0.2499 0.1134 263.6)", + "srgb": "#041a57" + }, + "palette-primary-1100": { + "oklch": "oklch(0.1925 0.0832 263.6)", + "srgb": "#020f39" + }, + "palette-accent-100": { + "oklch": "oklch(0.9862 0.0205 93.6)", + "srgb": "#fffbeb" + }, + "palette-accent-200": { + "oklch": "oklch(0.9364 0.0378 93.6)", + "srgb": "#f2eace" + }, + "palette-accent-300": { + "oklch": "oklch(0.8789 0.1801 93.58)", + "srgb": "#ffd300" + }, + "palette-accent-400": { + "oklch": "oklch(0.7442 0.1526 93.94)", + "srgb": "#cca900" + }, + "palette-accent-500": { + "oklch": "oklch(0.6707 0.1209 93.6)", + "srgb": "#ae932e" + }, + "palette-accent-600": { + "oklch": "oklch(0.5835 0.1195 93.6)", + "srgb": "#937900" + }, + "palette-accent-700": { + "oklch": "oklch(0.4972 0.0967 93.6)", + "srgb": "#75610e" + }, + "palette-accent-800": { + "oklch": "oklch(0.4115 0.0758 93.6)", + "srgb": "#594a10" + }, + "palette-accent-900": { + "oklch": "oklch(0.3346 0.0603 93.6)", + "srgb": "#41360b" + }, + "palette-accent-1000": { + "oklch": "oklch(0.2499 0.043 93.6)", + "srgb": "#282106" + }, + "palette-accent-1100": { + "oklch": "oklch(0.1925 0.0315 93.6)", + "srgb": "#191404" + }, + "palette-accent-alt-100": { + "oklch": "oklch(0.9862 0.0067 287.1)", + "srgb": "#fafaff" + }, + "palette-accent-alt-200": { + "oklch": "oklch(0.9364 0.0127 287.1)", + "srgb": "#e9e9f2" + }, + "palette-accent-alt-300": { + "oklch": "oklch(0.8497 0.043 287.1)", + "srgb": "#cbcae9" + }, + "palette-accent-alt-400": { + "oklch": "oklch(0.7628 0.0896 287.1)", + "srgb": "#aeaae9" + }, + "palette-accent-alt-500": { + "oklch": "oklch(0.6707 0.1624 287.1)", + "srgb": "#9182f3" + }, + "palette-accent-alt-600": { + "oklch": "oklch(0.5835 0.2416 287.1)", + "srgb": "#7a52ff" + }, + "palette-accent-alt-700": { + "oklch": "oklch(0.4972 0.2722 287.1)", + "srgb": "#661af0" + }, + "palette-accent-alt-800": { + "oklch": "oklch(0.4115 0.2134 287.1)", + "srgb": "#4d1cb5" + }, + "palette-accent-alt-900": { + "oklch": "oklch(0.3346 0.1696 287.1)", + "srgb": "#381587" + }, + "palette-accent-alt-1000": { + "oklch": "oklch(0.2499 0.1209 287.1)", + "srgb": "#220d57" + }, + "palette-accent-alt-1100": { + "oklch": "oklch(0.1925 0.0887 287.1)", + "srgb": "#150739" + }, + "palette-danger-100": { + "oklch": "oklch(0.9862 0.0066 29.4)", + "srgb": "#fff9f8" + }, + "palette-danger-200": { + "oklch": "oklch(0.9364 0.0128 29.4)", + "srgb": "#f2e7e5" + }, + "palette-danger-300": { + "oklch": "oklch(0.8497 0.0452 29.4)", + "srgb": "#eac3bd" + }, + "palette-danger-400": { + "oklch": "oklch(0.7628 0.099 29.4)", + "srgb": "#ea9a8d" + }, + "palette-danger-500": { + "oklch": "oklch(0.6707 0.191 29.4)", + "srgb": "#f45b4a" + }, + "palette-danger-600": { + "oklch": "oklch(0.5835 0.2382 29.4)", + "srgb": "#e70400" + }, + "palette-danger-700": { + "oklch": "oklch(0.4972 0.1928 29.4)", + "srgb": "#b7160d" + }, + "palette-danger-800": { + "oklch": "oklch(0.4115 0.1512 29.4)", + "srgb": "#8a170f" + }, + "palette-danger-900": { + "oklch": "oklch(0.3346 0.1202 29.4)", + "srgb": "#67110a" + }, + "palette-danger-1000": { + "oklch": "oklch(0.2499 0.0857 29.4)", + "srgb": "#420a06" + }, + "palette-danger-1100": { + "oklch": "oklch(0.1925 0.0628 29.4)", + "srgb": "#2b0603" + }, + "palette-warning-100": { + "oklch": "oklch(0.9862 0.0082 58.3)", + "srgb": "#fff9f5" + }, + "palette-warning-200": { + "oklch": "oklch(0.9364 0.0156 58.3)", + "srgb": "#f2e8e0" + }, + "palette-warning-300": { + "oklch": "oklch(0.8497 0.0545 58.3)", + "srgb": "#eac6ab" + }, + "palette-warning-400": { + "oklch": "oklch(0.7628 0.1179 58.3)", + "srgb": "#ea9f63" + }, + "palette-warning-500": { + "oklch": "oklch(0.6707 0.1408 58.3)", + "srgb": "#d47d2d" + }, + "palette-warning-600": { + "oklch": "oklch(0.5835 0.1392 58.3)", + "srgb": "#b66200" + }, + "palette-warning-700": { + "oklch": "oklch(0.4972 0.1127 58.3)", + "srgb": "#914f0e" + }, + "palette-warning-800": { + "oklch": "oklch(0.4115 0.0883 58.3)", + "srgb": "#6d3d10" + }, + "palette-warning-900": { + "oklch": "oklch(0.3346 0.0702 58.3)", + "srgb": "#512c0b" + }, + "palette-warning-1000": { + "oklch": "oklch(0.2499 0.05 58.3)", + "srgb": "#331b06" + }, + "palette-warning-1100": { + "oklch": "oklch(0.1925 0.0367 58.3)", + "srgb": "#201004" + }, + "palette-success-100": { + "oklch": "oklch(0.9862 0.0228 145.5)", + "srgb": "#f1fff1" + }, + "palette-success-200": { + "oklch": "oklch(0.9364 0.0458 145.5)", + "srgb": "#d8f3d8" + }, + "palette-success-300": { + "oklch": "oklch(0.8497 0.1448 145.5)", + "srgb": "#8de792" + }, + "palette-success-400": { + "oklch": "oklch(0.7628 0.1654 145.5)", + "srgb": "#64cd6d" + }, + "palette-success-500": { + "oklch": "oklch(0.6707 0.1829 145.5)", + "srgb": "#34b147" + }, + "palette-success-600": { + "oklch": "oklch(0.5835 0.1808 145.5)", + "srgb": "#00952b" + }, + "palette-success-700": { + "oklch": "oklch(0.4972 0.1464 145.5)", + "srgb": "#117726" + }, + "palette-success-800": { + "oklch": "oklch(0.4115 0.1147 145.5)", + "srgb": "#135a1f" + }, + "palette-success-900": { + "oklch": "oklch(0.3346 0.0912 145.5)", + "srgb": "#0e4216" + }, + "palette-success-1000": { + "oklch": "oklch(0.2499 0.065 145.5)", + "srgb": "#08290c" + }, + "palette-success-1100": { + "oklch": "oklch(0.1925 0.0477 145.5)", + "srgb": "#041a06" + }, + "palette-primary-600-a6": { + "oklch": "oklch(0.5835 0.2271 263.6 / 0.06)", + "srgb": "rgba(47, 109, 255, 0.06)" + }, + "palette-primary-600-a9": { + "oklch": "oklch(0.5835 0.2271 263.6 / 0.09)", + "srgb": "rgba(47, 109, 255, 0.09)" + }, + "palette-primary-600-a8": { + "oklch": "oklch(0.5835 0.2271 263.6 / 0.08)", + "srgb": "rgba(47, 109, 255, 0.08)" + }, + "palette-neutral-dark-1100-a16": { + "oklch": "oklch(0.1925 0.0199 271.3 / 0.16)", + "srgb": "rgba(17, 20, 29, 0.16)" + }, + "black-a32": { + "oklch": "oklch(0 0 0 / 0.32)", + "srgb": "rgba(0, 0, 0, 0.32)" + }, + "palette-neutral-dark-1100-a12": { + "oklch": "oklch(0.1925 0.0199 271.3 / 0.12)", + "srgb": "rgba(17, 20, 29, 0.12)" + }, + "black-a24": { + "oklch": "oklch(0 0 0 / 0.24)", + "srgb": "rgba(0, 0, 0, 0.24)" + }, + "black-a60": { + "oklch": "oklch(0 0 0 / 0.6)", + "srgb": "rgba(0, 0, 0, 0.6)" + } +} as const; + +export const roles = { + "light": { + "surface-sunken": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0031 58.8)", + "srgb": "#ece9e8" + }, + "surface-default": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9862 0.0016 58.8)", + "srgb": "#fbfaf9" + }, + "surface-raised": { + "ref": "white", + "oklch": "oklch(1 0 0)", + "srgb": "#ffffff" + }, + "surface-success": { + "ref": "palette-success-200", + "oklch": "oklch(0.9364 0.0458 145.5)", + "srgb": "#d8f3d8" + }, + "surface-warning": { + "ref": "palette-warning-200", + "oklch": "oklch(0.9364 0.0156 58.3)", + "srgb": "#f2e8e0" + }, + "surface-error": { + "ref": "palette-danger-200", + "oklch": "oklch(0.9364 0.0128 29.4)", + "srgb": "#f2e7e5" + }, + "surface-brand": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9862 0.0016 58.8)", + "srgb": "#fbfaf9" + }, + "surface-brand-subtle": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9862 0.0016 58.8)", + "srgb": "#fbfaf9" + }, + "text-default": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "text-subtle": { + "ref": "palette-neutral-dark-800", + "oklch": "oklch(0.4115 0.0479 271.3)", + "srgb": "#414965" + }, + "text-inverse": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0031 58.8)", + "srgb": "#ece9e8" + }, + "text-success": { + "ref": "palette-success-900", + "oklch": "oklch(0.3346 0.0912 145.5)", + "srgb": "#0e4216" + }, + "text-warning": { + "ref": "palette-warning-900", + "oklch": "oklch(0.3346 0.0702 58.3)", + "srgb": "#512c0b" + }, + "text-error": { + "ref": "palette-danger-900", + "oklch": "oklch(0.3346 0.1202 29.4)", + "srgb": "#67110a" + }, + "border-strong": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "border-default": { + "ref": "palette-neutral-dark-600", + "oklch": "oklch(0.5835 0.0454 271.3)", + "srgb": "#717a97" + }, + "border-subtle": { + "ref": "palette-neutral-dark-300", + "oklch": "oklch(0.8497 0.0081 271.3)", + "srgb": "#cbcdd3" + }, + "action-primary": { + "ref": "palette-primary-800", + "oklch": "oklch(0.4115 0.2002 263.6)", + "srgb": "#0939b5" + }, + "action-primary-hover": { + "ref": "palette-primary-900", + "oklch": "oklch(0.3346 0.1591 263.6)", + "srgb": "#062a86" + }, + "action-primary-pressed": { + "ref": "palette-primary-1000", + "oklch": "oklch(0.2499 0.1134 263.6)", + "srgb": "#041a57" + }, + "action-danger": { + "ref": "palette-danger-800", + "oklch": "oklch(0.4115 0.1512 29.4)", + "srgb": "#8a170f" + }, + "action-danger-hover": { + "ref": "palette-danger-900", + "oklch": "oklch(0.3346 0.1202 29.4)", + "srgb": "#67110a" + }, + "action-danger-pressed": { + "ref": "palette-danger-1000", + "oklch": "oklch(0.2499 0.0857 29.4)", + "srgb": "#420a06" + }, + "action-neutral": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "action-neutral-hover": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "action-neutral-pressed": { + "ref": "black", + "oklch": "oklch(0 0 0)", + "srgb": "#000000" + }, + "action-disabled": { + "ref": "palette-neutral-light-400", + "oklch": "oklch(0.7628 0.0237 58.8)", + "srgb": "#beafa3" + }, + "state-hover": { + "ref": "palette-primary-600-a6", + "oklch": "oklch(0.5835 0.2271 263.6 / 0.06)", + "srgb": "rgba(47, 109, 255, 0.06)" + }, + "state-pressed": { + "ref": "palette-primary-600-a9", + "oklch": "oklch(0.5835 0.2271 263.6 / 0.09)", + "srgb": "rgba(47, 109, 255, 0.09)" + }, + "state-selected": { + "ref": "palette-primary-600-a8", + "oklch": "oklch(0.5835 0.2271 263.6 / 0.08)", + "srgb": "rgba(47, 109, 255, 0.08)" + }, + "viz-1": { + "ref": "palette-primary-800", + "oklch": "oklch(0.4115 0.2002 263.6)", + "srgb": "#0939b5" + }, + "viz-2": { + "ref": "palette-accent-700", + "oklch": "oklch(0.4972 0.0967 93.6)", + "srgb": "#75610e" + }, + "viz-3": { + "ref": "palette-warning-700", + "oklch": "oklch(0.4972 0.1127 58.3)", + "srgb": "#914f0e" + }, + "viz-4": { + "ref": "palette-accent-alt-600", + "oklch": "oklch(0.5835 0.2416 287.1)", + "srgb": "#7a52ff" + }, + "viz-5": { + "ref": "palette-success-600", + "oklch": "oklch(0.5835 0.1808 145.5)", + "srgb": "#00952b" + }, + "viz-6": { + "ref": "palette-danger-600", + "oklch": "oklch(0.5835 0.2382 29.4)", + "srgb": "#e70400" + }, + "viz-7": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "viz-8": { + "ref": "palette-neutral-light-600", + "oklch": "oklch(0.5835 0.0277 58.8)", + "srgb": "#88786b" + }, + "viz-seq-1": { + "ref": "palette-primary-600", + "oklch": "oklch(0.5835 0.2271 263.6)", + "srgb": "#2f6dff" + }, + "viz-seq-2": { + "ref": "palette-primary-700", + "oklch": "oklch(0.4972 0.2553 263.6)", + "srgb": "#0748f0" + }, + "viz-seq-3": { + "ref": "palette-primary-800", + "oklch": "oklch(0.4115 0.2002 263.6)", + "srgb": "#0939b5" + }, + "viz-seq-4": { + "ref": "palette-primary-900", + "oklch": "oklch(0.3346 0.1591 263.6)", + "srgb": "#062a86" + }, + "shadow-key": { + "ref": "palette-neutral-dark-1100-a16", + "oklch": "oklch(0.1925 0.0199 271.3 / 0.16)", + "srgb": "rgba(17, 20, 29, 0.16)" + }, + "shadow-ambient": { + "ref": "palette-neutral-dark-1100-a12", + "oklch": "oklch(0.1925 0.0199 271.3 / 0.12)", + "srgb": "rgba(17, 20, 29, 0.12)" + }, + "shadow-scrim": { + "ref": "black-a60", + "oklch": "oklch(0 0 0 / 0.6)", + "srgb": "rgba(0, 0, 0, 0.6)" + } + }, + "dark": { + "surface-sunken": { + "ref": "black", + "oklch": "oklch(0 0 0)", + "srgb": "#000000" + }, + "surface-default": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "surface-raised": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "surface-success": { + "ref": "palette-success-1000", + "oklch": "oklch(0.2499 0.065 145.5)", + "srgb": "#08290c" + }, + "surface-warning": { + "ref": "palette-warning-1000", + "oklch": "oklch(0.2499 0.05 58.3)", + "srgb": "#331b06" + }, + "surface-error": { + "ref": "palette-danger-1000", + "oklch": "oklch(0.2499 0.0857 29.4)", + "srgb": "#420a06" + }, + "surface-brand": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "surface-brand-subtle": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "text-default": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0031 58.8)", + "srgb": "#ece9e8" + }, + "text-subtle": { + "ref": "palette-neutral-light-400", + "oklch": "oklch(0.7628 0.0237 58.8)", + "srgb": "#beafa3" + }, + "text-inverse": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "text-success": { + "ref": "palette-success-300", + "oklch": "oklch(0.8497 0.1448 145.5)", + "srgb": "#8de792" + }, + "text-warning": { + "ref": "palette-warning-300", + "oklch": "oklch(0.8497 0.0545 58.3)", + "srgb": "#eac6ab" + }, + "text-error": { + "ref": "palette-danger-300", + "oklch": "oklch(0.8497 0.0452 29.4)", + "srgb": "#eac3bd" + }, + "border-strong": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9862 0.0016 58.8)", + "srgb": "#fbfaf9" + }, + "border-default": { + "ref": "palette-neutral-light-600", + "oklch": "oklch(0.5835 0.0277 58.8)", + "srgb": "#88786b" + }, + "border-subtle": { + "ref": "palette-neutral-light-900", + "oklch": "oklch(0.3346 0.0139 58.8)", + "srgb": "#3c3530" + }, + "action-primary": { + "ref": "palette-accent-300", + "oklch": "oklch(0.8789 0.1801 93.58)", + "srgb": "#ffd300" + }, + "action-primary-hover": { + "ref": "palette-accent-400", + "oklch": "oklch(0.7442 0.1526 93.94)", + "srgb": "#cca900" + }, + "action-primary-pressed": { + "ref": "palette-accent-500", + "oklch": "oklch(0.6707 0.1209 93.6)", + "srgb": "#ae932e" + }, + "action-danger": { + "ref": "palette-danger-400", + "oklch": "oklch(0.7628 0.099 29.4)", + "srgb": "#ea9a8d" + }, + "action-danger-hover": { + "ref": "palette-danger-300", + "oklch": "oklch(0.8497 0.0452 29.4)", + "srgb": "#eac3bd" + }, + "action-danger-pressed": { + "ref": "palette-danger-200", + "oklch": "oklch(0.9364 0.0128 29.4)", + "srgb": "#f2e7e5" + }, + "action-neutral": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0031 58.8)", + "srgb": "#ece9e8" + }, + "action-neutral-hover": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9862 0.0016 58.8)", + "srgb": "#fbfaf9" + }, + "action-neutral-pressed": { + "ref": "white", + "oklch": "oklch(1 0 0)", + "srgb": "#ffffff" + }, + "action-disabled": { + "ref": "palette-neutral-dark-800", + "oklch": "oklch(0.4115 0.0479 271.3)", + "srgb": "#414965" + }, + "state-hover": { + "ref": "palette-primary-600-a6", + "oklch": "oklch(0.5835 0.2271 263.6 / 0.06)", + "srgb": "rgba(47, 109, 255, 0.06)" + }, + "state-pressed": { + "ref": "palette-primary-600-a9", + "oklch": "oklch(0.5835 0.2271 263.6 / 0.09)", + "srgb": "rgba(47, 109, 255, 0.09)" + }, + "state-selected": { + "ref": "palette-primary-600-a8", + "oklch": "oklch(0.5835 0.2271 263.6 / 0.08)", + "srgb": "rgba(47, 109, 255, 0.08)" + }, + "viz-1": { + "ref": "palette-primary-500", + "oklch": "oklch(0.6707 0.1528 263.6)", + "srgb": "#6492f3" + }, + "viz-2": { + "ref": "palette-accent-500", + "oklch": "oklch(0.6707 0.1209 93.6)", + "srgb": "#ae932e" + }, + "viz-3": { + "ref": "palette-warning-400", + "oklch": "oklch(0.7628 0.1179 58.3)", + "srgb": "#ea9f63" + }, + "viz-4": { + "ref": "palette-accent-alt-600", + "oklch": "oklch(0.5835 0.2416 287.1)", + "srgb": "#7a52ff" + }, + "viz-5": { + "ref": "palette-success-300", + "oklch": "oklch(0.8497 0.1448 145.5)", + "srgb": "#8de792" + }, + "viz-6": { + "ref": "palette-danger-600", + "oklch": "oklch(0.5835 0.2382 29.4)", + "srgb": "#e70400" + }, + "viz-7": { + "ref": "palette-neutral-dark-500", + "oklch": "oklch(0.6707 0.0306 271.3)", + "srgb": "#8e95a9" + }, + "viz-8": { + "ref": "palette-neutral-light-600", + "oklch": "oklch(0.5835 0.0277 58.8)", + "srgb": "#88786b" + }, + "viz-seq-1": { + "ref": "palette-primary-600", + "oklch": "oklch(0.5835 0.2271 263.6)", + "srgb": "#2f6dff" + }, + "viz-seq-2": { + "ref": "palette-primary-500", + "oklch": "oklch(0.6707 0.1528 263.6)", + "srgb": "#6492f3" + }, + "viz-seq-3": { + "ref": "palette-primary-400", + "oklch": "oklch(0.7628 0.0845 263.6)", + "srgb": "#96b2e8" + }, + "viz-seq-4": { + "ref": "palette-primary-300", + "oklch": "oklch(0.8497 0.0406 263.6)", + "srgb": "#c0cee9" + }, + "shadow-key": { + "ref": "black-a32", + "oklch": "oklch(0 0 0 / 0.32)", + "srgb": "rgba(0, 0, 0, 0.32)" + }, + "shadow-ambient": { + "ref": "black-a24", + "oklch": "oklch(0 0 0 / 0.24)", + "srgb": "rgba(0, 0, 0, 0.24)" + }, + "shadow-scrim": { + "ref": "black-a60", + "oklch": "oklch(0 0 0 / 0.6)", + "srgb": "rgba(0, 0, 0, 0.6)" + } + } +} as const; + +export const gradients = { + "light": { + "surface-brand": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.8497 0.043 287.1 / 0.45) 0%, oklch(0.8497 0.043 287.1 / 0.43) 16%, oklch(0.8497 0.043 287.1 / 0.24) 36%, oklch(0.8497 0.043 287.1 / 0.11) 56%, oklch(0.8497 0.043 287.1 / 0.04) 76%, oklch(0.8497 0.043 287.1 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.8497 0.0406 263.6 / 0.45) 0%, oklch(0.8497 0.0406 263.6 / 0.43) 16%, oklch(0.8497 0.0406 263.6 / 0.24) 36%, oklch(0.8497 0.0406 263.6 / 0.11) 56%, oklch(0.8497 0.0406 263.6 / 0.04) 76%, oklch(0.8497 0.0406 263.6 / 0) 100%)", + "surface-brand-subtle": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.8497 0.0406 263.6 / 0.45) 0%, oklch(0.8497 0.0406 263.6 / 0.43) 16%, oklch(0.8497 0.0406 263.6 / 0.24) 36%, oklch(0.8497 0.0406 263.6 / 0.11) 56%, oklch(0.8497 0.0406 263.6 / 0.04) 76%, oklch(0.8497 0.0406 263.6 / 0) 100%)" + }, + "dark": { + "surface-brand": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.3346 0.1696 287.1 / 0.45) 0%, oklch(0.3346 0.1696 287.1 / 0.43) 16%, oklch(0.3346 0.1696 287.1 / 0.24) 36%, oklch(0.3346 0.1696 287.1 / 0.11) 56%, oklch(0.3346 0.1696 287.1 / 0.04) 76%, oklch(0.3346 0.1696 287.1 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.3346 0.1591 263.6 / 0.45) 0%, oklch(0.3346 0.1591 263.6 / 0.43) 16%, oklch(0.3346 0.1591 263.6 / 0.24) 36%, oklch(0.3346 0.1591 263.6 / 0.11) 56%, oklch(0.3346 0.1591 263.6 / 0.04) 76%, oklch(0.3346 0.1591 263.6 / 0) 100%)", + "surface-brand-subtle": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.3346 0.1591 263.6 / 0.45) 0%, oklch(0.3346 0.1591 263.6 / 0.43) 16%, oklch(0.3346 0.1591 263.6 / 0.24) 36%, oklch(0.3346 0.1591 263.6 / 0.11) 56%, oklch(0.3346 0.1591 263.6 / 0.04) 76%, oklch(0.3346 0.1591 263.6 / 0) 100%)" + } +} as const; + +/* Palette color → the mode its content uses. Background reads this instead of measuring. */ +export const contentModes = { + "white": "light", + "black": "dark", + "palette-neutral-light-100": "light", + "palette-neutral-light-200": "light", + "palette-neutral-light-300": "light", + "palette-neutral-light-400": "light", + "palette-neutral-light-500": "light", + "palette-neutral-light-600": "light", + "palette-neutral-light-700": "dark", + "palette-neutral-light-800": "dark", + "palette-neutral-light-900": "dark", + "palette-neutral-light-1000": "dark", + "palette-neutral-light-1100": "dark", + "palette-neutral-dark-100": "light", + "palette-neutral-dark-200": "light", + "palette-neutral-dark-300": "light", + "palette-neutral-dark-400": "light", + "palette-neutral-dark-500": "light", + "palette-neutral-dark-600": "light", + "palette-neutral-dark-700": "dark", + "palette-neutral-dark-800": "dark", + "palette-neutral-dark-900": "dark", + "palette-neutral-dark-1000": "dark", + "palette-neutral-dark-1100": "dark", + "palette-primary-100": "light", + "palette-primary-200": "light", + "palette-primary-300": "light", + "palette-primary-400": "light", + "palette-primary-500": "light", + "palette-primary-600": "dark", + "palette-primary-700": "dark", + "palette-primary-800": "dark", + "palette-primary-900": "dark", + "palette-primary-1000": "dark", + "palette-primary-1100": "dark", + "palette-accent-100": "light", + "palette-accent-200": "light", + "palette-accent-300": "light", + "palette-accent-400": "light", + "palette-accent-500": "light", + "palette-accent-600": "light", + "palette-accent-700": "dark", + "palette-accent-800": "dark", + "palette-accent-900": "dark", + "palette-accent-1000": "dark", + "palette-accent-1100": "dark", + "palette-accent-alt-100": "light", + "palette-accent-alt-200": "light", + "palette-accent-alt-300": "light", + "palette-accent-alt-400": "light", + "palette-accent-alt-500": "light", + "palette-accent-alt-600": "dark", + "palette-accent-alt-700": "dark", + "palette-accent-alt-800": "dark", + "palette-accent-alt-900": "dark", + "palette-accent-alt-1000": "dark", + "palette-accent-alt-1100": "dark", + "palette-danger-100": "light", + "palette-danger-200": "light", + "palette-danger-300": "light", + "palette-danger-400": "light", + "palette-danger-500": "light", + "palette-danger-600": "dark", + "palette-danger-700": "dark", + "palette-danger-800": "dark", + "palette-danger-900": "dark", + "palette-danger-1000": "dark", + "palette-danger-1100": "dark", + "palette-warning-100": "light", + "palette-warning-200": "light", + "palette-warning-300": "light", + "palette-warning-400": "light", + "palette-warning-500": "light", + "palette-warning-600": "dark", + "palette-warning-700": "dark", + "palette-warning-800": "dark", + "palette-warning-900": "dark", + "palette-warning-1000": "dark", + "palette-warning-1100": "dark", + "palette-success-100": "light", + "palette-success-200": "light", + "palette-success-300": "light", + "palette-success-400": "light", + "palette-success-500": "light", + "palette-success-600": "light", + "palette-success-700": "dark", + "palette-success-800": "dark", + "palette-success-900": "dark", + "palette-success-1000": "dark", + "palette-success-1100": "dark" +} as const; + +/* This brand's tokens.css, for GamutProvider to inject. */ +export const tokensCss = "/* Generated by @skillsoft/gamut-brand from the \"admin\" brand. Do not edit. */\n@layer reset, base, tokens, recipes, utilities;\n\n@layer tokens {\n :root, [data-brand=\"admin\"] {\n --color-white: oklch(1.0000 0.0000 0.00);\n --color-black: oklch(0.0000 0.0000 0.00);\n --color-palette-neutral-light-100: oklch(0.9862 0.0016 58.80);\n --color-palette-neutral-light-200: oklch(0.9364 0.0031 58.80);\n --color-palette-neutral-light-300: oklch(0.8497 0.0109 58.80);\n --color-palette-neutral-light-400: oklch(0.7628 0.0237 58.80);\n --color-palette-neutral-light-500: oklch(0.6707 0.0280 58.80);\n --color-palette-neutral-light-600: oklch(0.5835 0.0277 58.80);\n --color-palette-neutral-light-700: oklch(0.4972 0.0224 58.80);\n --color-palette-neutral-light-800: oklch(0.4115 0.0175 58.80);\n --color-palette-neutral-light-900: oklch(0.3346 0.0139 58.80);\n --color-palette-neutral-light-1000: oklch(0.2499 0.0099 58.80);\n --color-palette-neutral-light-1100: oklch(0.1925 0.0073 58.80);\n --color-palette-neutral-dark-100: oklch(0.9862 0.0012 271.30);\n --color-palette-neutral-dark-200: oklch(0.9364 0.0024 271.30);\n --color-palette-neutral-dark-300: oklch(0.8497 0.0081 271.30);\n --color-palette-neutral-dark-400: oklch(0.7628 0.0169 271.30);\n --color-palette-neutral-dark-500: oklch(0.6707 0.0306 271.30);\n --color-palette-neutral-dark-600: oklch(0.5835 0.0454 271.30);\n --color-palette-neutral-dark-700: oklch(0.4972 0.0538 271.30);\n --color-palette-neutral-dark-800: oklch(0.4115 0.0479 271.30);\n --color-palette-neutral-dark-900: oklch(0.3346 0.0381 271.30);\n --color-palette-neutral-dark-1000: oklch(0.2499 0.0272 271.30);\n --color-palette-neutral-dark-1100: oklch(0.1925 0.0199 271.30);\n --color-palette-neutral-dark-1100-a16: oklch(0.1925 0.0199 271.30 / 0.16);\n --color-palette-neutral-dark-1100-a12: oklch(0.1925 0.0199 271.30 / 0.12);\n --color-palette-primary-100: oklch(0.9862 0.0064 263.60);\n --color-palette-primary-200: oklch(0.9364 0.0120 263.60);\n --color-palette-primary-300: oklch(0.8497 0.0406 263.60);\n --color-palette-primary-400: oklch(0.7628 0.0845 263.60);\n --color-palette-primary-500: oklch(0.6707 0.1528 263.60);\n --color-palette-primary-600: oklch(0.5835 0.2271 263.60);\n --color-palette-primary-700: oklch(0.4972 0.2553 263.60);\n --color-palette-primary-800: oklch(0.4115 0.2002 263.60);\n --color-palette-primary-900: oklch(0.3346 0.1591 263.60);\n --color-palette-primary-1000: oklch(0.2499 0.1134 263.60);\n --color-palette-primary-1100: oklch(0.1925 0.0832 263.60);\n --color-palette-primary-600-a6: oklch(0.5835 0.2271 263.60 / 0.06);\n --color-palette-primary-600-a9: oklch(0.5835 0.2271 263.60 / 0.09);\n --color-palette-primary-600-a8: oklch(0.5835 0.2271 263.60 / 0.08);\n --color-palette-accent-100: oklch(0.9862 0.0205 93.60);\n --color-palette-accent-200: oklch(0.9364 0.0378 93.60);\n --color-palette-accent-300: oklch(0.8789 0.1801 93.58);\n --color-palette-accent-400: oklch(0.7442 0.1526 93.94);\n --color-palette-accent-500: oklch(0.6707 0.1209 93.60);\n --color-palette-accent-600: oklch(0.5835 0.1195 93.60);\n --color-palette-accent-700: oklch(0.4972 0.0967 93.60);\n --color-palette-accent-800: oklch(0.4115 0.0758 93.60);\n --color-palette-accent-900: oklch(0.3346 0.0603 93.60);\n --color-palette-accent-1000: oklch(0.2499 0.0430 93.60);\n --color-palette-accent-1100: oklch(0.1925 0.0315 93.60);\n --color-palette-accent-alt-100: oklch(0.9862 0.0067 287.10);\n --color-palette-accent-alt-200: oklch(0.9364 0.0127 287.10);\n --color-palette-accent-alt-300: oklch(0.8497 0.0430 287.10);\n --color-palette-accent-alt-400: oklch(0.7628 0.0896 287.10);\n --color-palette-accent-alt-500: oklch(0.6707 0.1624 287.10);\n --color-palette-accent-alt-600: oklch(0.5835 0.2416 287.10);\n --color-palette-accent-alt-700: oklch(0.4972 0.2722 287.10);\n --color-palette-accent-alt-800: oklch(0.4115 0.2134 287.10);\n --color-palette-accent-alt-900: oklch(0.3346 0.1696 287.10);\n --color-palette-accent-alt-1000: oklch(0.2499 0.1209 287.10);\n --color-palette-accent-alt-1100: oklch(0.1925 0.0887 287.10);\n --color-palette-danger-100: oklch(0.9862 0.0066 29.40);\n --color-palette-danger-200: oklch(0.9364 0.0128 29.40);\n --color-palette-danger-300: oklch(0.8497 0.0452 29.40);\n --color-palette-danger-400: oklch(0.7628 0.0990 29.40);\n --color-palette-danger-500: oklch(0.6707 0.1910 29.40);\n --color-palette-danger-600: oklch(0.5835 0.2382 29.40);\n --color-palette-danger-700: oklch(0.4972 0.1928 29.40);\n --color-palette-danger-800: oklch(0.4115 0.1512 29.40);\n --color-palette-danger-900: oklch(0.3346 0.1202 29.40);\n --color-palette-danger-1000: oklch(0.2499 0.0857 29.40);\n --color-palette-danger-1100: oklch(0.1925 0.0628 29.40);\n --color-palette-warning-100: oklch(0.9862 0.0082 58.30);\n --color-palette-warning-200: oklch(0.9364 0.0156 58.30);\n --color-palette-warning-300: oklch(0.8497 0.0545 58.30);\n --color-palette-warning-400: oklch(0.7628 0.1179 58.30);\n --color-palette-warning-500: oklch(0.6707 0.1408 58.30);\n --color-palette-warning-600: oklch(0.5835 0.1392 58.30);\n --color-palette-warning-700: oklch(0.4972 0.1127 58.30);\n --color-palette-warning-800: oklch(0.4115 0.0883 58.30);\n --color-palette-warning-900: oklch(0.3346 0.0702 58.30);\n --color-palette-warning-1000: oklch(0.2499 0.0500 58.30);\n --color-palette-warning-1100: oklch(0.1925 0.0367 58.30);\n --color-palette-success-100: oklch(0.9862 0.0228 145.50);\n --color-palette-success-200: oklch(0.9364 0.0458 145.50);\n --color-palette-success-300: oklch(0.8497 0.1448 145.50);\n --color-palette-success-400: oklch(0.7628 0.1654 145.50);\n --color-palette-success-500: oklch(0.6707 0.1829 145.50);\n --color-palette-success-600: oklch(0.5835 0.1808 145.50);\n --color-palette-success-700: oklch(0.4972 0.1464 145.50);\n --color-palette-success-800: oklch(0.4115 0.1147 145.50);\n --color-palette-success-900: oklch(0.3346 0.0912 145.50);\n --color-palette-success-1000: oklch(0.2499 0.0650 145.50);\n --color-palette-success-1100: oklch(0.1925 0.0477 145.50);\n --color-black-a32: oklch(0.0000 0.0000 0.00 / 0.32);\n --color-black-a24: oklch(0.0000 0.0000 0.00 / 0.24);\n --color-black-a60: oklch(0.0000 0.0000 0.00 / 0.6);\n }\n\n :root, [data-color-mode=\"light\"] {\n color-scheme: light;\n color: var(--color-text-default);\n --color-background-current: var(--color-surface-default);\n --color-surface-sunken: var(--color-palette-neutral-light-200); /** Wells, insets, code blocks — anything recessed into the page. */\n --color-surface-default: var(--color-palette-neutral-light-100); /** The page background — the base every other surface elevates from. */\n --color-surface-raised: var(--color-white); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */\n --color-surface-success: var(--color-palette-success-200); /** The success tint behind an alert or inline confirmation. */\n --color-surface-warning: var(--color-palette-warning-200); /** The warning tint behind an alert or inline caution. */\n --color-surface-error: var(--color-palette-danger-200); /** The error tint behind an alert or inline validation message. */\n --color-surface-brand: var(--color-palette-neutral-light-100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */\n --color-surface-brand-subtle: var(--color-palette-neutral-light-100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */\n --color-text-default: var(--color-palette-neutral-dark-1000); /** Body copy and headings. */\n --color-text-subtle: var(--color-palette-neutral-dark-800); /** Secondary text — descriptions, metadata, helper copy. */\n --color-text-inverse: var(--color-palette-neutral-light-200); /** Labels on a filled control — action-primary, -neutral, -danger. */\n --color-text-success: var(--color-palette-success-900); /** Success text and icons. */\n --color-text-warning: var(--color-palette-warning-900); /** Warning text and icons. Always with an icon. */\n --color-text-error: var(--color-palette-danger-900); /** Error text and icons. */\n --color-border-strong: var(--color-palette-neutral-dark-1100); /** Emphasis — an active field, a selected row. */\n --color-border-default: var(--color-palette-neutral-dark-600); /** Any border that carries meaning — control outlines, field borders. */\n --color-border-subtle: var(--color-palette-neutral-dark-300); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */\n --color-action-primary: var(--color-palette-primary-800); /** Primary interactive fills, strokes, links and focus rings. */\n --color-action-primary-hover: var(--color-palette-primary-900); /** Hover for action-primary. */\n --color-action-primary-pressed: var(--color-palette-primary-1000); /** Pressed for action-primary. */\n --color-action-danger: var(--color-palette-danger-800); /** Destructive actions — delete, remove, revoke. */\n --color-action-danger-hover: var(--color-palette-danger-900); /** Hover for action-danger. */\n --color-action-danger-pressed: var(--color-palette-danger-1000); /** Pressed for action-danger. */\n --color-action-neutral: var(--color-palette-neutral-dark-1000); /** The neutral action fill. Button variant=\"secondary\" resolves here. */\n --color-action-neutral-hover: var(--color-palette-neutral-dark-1100); /** Hover for action-neutral. */\n --color-action-neutral-pressed: var(--color-black); /** Pressed for action-neutral. */\n --color-action-disabled: var(--color-palette-neutral-light-400); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */\n --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */\n --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */\n --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent \"this one is chosen\" tint. */\n --color-viz-1: var(--color-palette-primary-800); /** Categorical series 1. */\n --color-viz-2: var(--color-palette-accent-700); /** Categorical series 2. */\n --color-viz-3: var(--color-palette-warning-700); /** Categorical series 3. */\n --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */\n --color-viz-5: var(--color-palette-success-600); /** Categorical series 5. */\n --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */\n --color-viz-7: var(--color-palette-neutral-dark-1000); /** Categorical series 7. */\n --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */\n --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */\n --color-viz-seq-2: var(--color-palette-primary-700); /** Sequential step 2, low to high. */\n --color-viz-seq-3: var(--color-palette-primary-800); /** Sequential step 3, low to high. */\n --color-viz-seq-4: var(--color-palette-primary-900); /** Sequential step 4, low to high. */\n --color-shadow-key: var(--color-palette-neutral-dark-1100-a16); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */\n --color-shadow-ambient: var(--color-palette-neutral-dark-1100-a12); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */\n --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */\n --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-300) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%);\n --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%);\n }\n\n [data-color-mode=\"dark\"] {\n color-scheme: dark;\n color: var(--color-text-default);\n --color-background-current: var(--color-surface-default);\n --color-surface-sunken: var(--color-black); /** Wells, insets, code blocks — anything recessed into the page. */\n --color-surface-default: var(--color-palette-neutral-dark-1100); /** The page background — the base every other surface elevates from. */\n --color-surface-raised: var(--color-palette-neutral-dark-1000); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */\n --color-surface-success: var(--color-palette-success-1000); /** The success tint behind an alert or inline confirmation. */\n --color-surface-warning: var(--color-palette-warning-1000); /** The warning tint behind an alert or inline caution. */\n --color-surface-error: var(--color-palette-danger-1000); /** The error tint behind an alert or inline validation message. */\n --color-surface-brand: var(--color-palette-neutral-dark-1100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */\n --color-surface-brand-subtle: var(--color-palette-neutral-dark-1100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */\n --color-text-default: var(--color-palette-neutral-light-200); /** Body copy and headings. */\n --color-text-subtle: var(--color-palette-neutral-light-400); /** Secondary text — descriptions, metadata, helper copy. */\n --color-text-inverse: var(--color-palette-neutral-dark-1000); /** Labels on a filled control — action-primary, -neutral, -danger. */\n --color-text-success: var(--color-palette-success-300); /** Success text and icons. */\n --color-text-warning: var(--color-palette-warning-300); /** Warning text and icons. Always with an icon. */\n --color-text-error: var(--color-palette-danger-300); /** Error text and icons. */\n --color-border-strong: var(--color-palette-neutral-light-100); /** Emphasis — an active field, a selected row. */\n --color-border-default: var(--color-palette-neutral-light-600); /** Any border that carries meaning — control outlines, field borders. */\n --color-border-subtle: var(--color-palette-neutral-light-900); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */\n --color-action-primary: var(--color-palette-accent-300); /** Primary interactive fills, strokes, links and focus rings. */\n --color-action-primary-hover: var(--color-palette-accent-400); /** Hover for action-primary. */\n --color-action-primary-pressed: var(--color-palette-accent-500); /** Pressed for action-primary. */\n --color-action-danger: var(--color-palette-danger-400); /** Destructive actions — delete, remove, revoke. */\n --color-action-danger-hover: var(--color-palette-danger-300); /** Hover for action-danger. */\n --color-action-danger-pressed: var(--color-palette-danger-200); /** Pressed for action-danger. */\n --color-action-neutral: var(--color-palette-neutral-light-200); /** The neutral action fill. Button variant=\"secondary\" resolves here. */\n --color-action-neutral-hover: var(--color-palette-neutral-light-100); /** Hover for action-neutral. */\n --color-action-neutral-pressed: var(--color-white); /** Pressed for action-neutral. */\n --color-action-disabled: var(--color-palette-neutral-dark-800); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */\n --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */\n --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */\n --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent \"this one is chosen\" tint. */\n --color-viz-1: var(--color-palette-primary-500); /** Categorical series 1. */\n --color-viz-2: var(--color-palette-accent-500); /** Categorical series 2. */\n --color-viz-3: var(--color-palette-warning-400); /** Categorical series 3. */\n --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */\n --color-viz-5: var(--color-palette-success-300); /** Categorical series 5. */\n --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */\n --color-viz-7: var(--color-palette-neutral-dark-500); /** Categorical series 7. */\n --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */\n --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */\n --color-viz-seq-2: var(--color-palette-primary-500); /** Sequential step 2, low to high. */\n --color-viz-seq-3: var(--color-palette-primary-400); /** Sequential step 3, low to high. */\n --color-viz-seq-4: var(--color-palette-primary-300); /** Sequential step 4, low to high. */\n --color-shadow-key: var(--color-black-a32); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */\n --color-shadow-ambient: var(--color-black-a24); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */\n --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */\n --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-900) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%);\n --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%);\n }\n}\n"; diff --git a/packages/gamut-styles/src/tokens/generated/index.ts b/packages/gamut-styles/src/tokens/generated/index.ts new file mode 100644 index 0000000000..3843a8ed87 --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/index.ts @@ -0,0 +1,6 @@ +/* Generated by scripts/build-tokens.ts. Do not edit. */ +export * as skillsoft from './skillsoft/values'; +export * as originalCodecademy from './originalCodecademy/values'; +export * as admin from './admin/values'; +export * as platform from './platform/values'; +export * as lxStudio from './lxStudio/values'; diff --git a/packages/gamut-styles/src/tokens/generated/lxStudio/dark.tokens.json b/packages/gamut-styles/src/tokens/generated/lxStudio/dark.tokens.json new file mode 100644 index 0000000000..19f966422e --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/lxStudio/dark.tokens.json @@ -0,0 +1,315 @@ +{ + "$description": "dark mode roles for \"lxStudio\". Do not edit.", + "surface-sunken": { + "$type": "color", + "$value": "{black}", + "$description": "Wells, insets, code blocks — anything recessed into the page." + }, + "surface-default": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "The page background — the base every other surface elevates from." + }, + "surface-raised": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits." + }, + "surface-success": { + "$type": "color", + "$value": "{palette.success.1000}", + "$description": "The success tint behind an alert or inline confirmation." + }, + "surface-warning": { + "$type": "color", + "$value": "{palette.warning.1000}", + "$description": "The warning tint behind an alert or inline caution." + }, + "surface-error": { + "$type": "color", + "$value": "{palette.danger.1000}", + "$description": "The error tint behind an alert or inline validation message." + }, + "surface-brand": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.accent-alt.900}", + "radius": 0.85, + "at": "100% -5%" + }, + { + "color": "{palette.primary.900}", + "radius": 1.21, + "at": "5% 100%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.3346 0.1696 287.1 / 0.45) 0%, oklch(0.3346 0.1696 287.1 / 0.43) 16%, oklch(0.3346 0.1696 287.1 / 0.24) 36%, oklch(0.3346 0.1696 287.1 / 0.11) 56%, oklch(0.3346 0.1696 287.1 / 0.04) 76%, oklch(0.3346 0.1696 287.1 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.3346 0.1405 262.4 / 0.45) 0%, oklch(0.3346 0.1405 262.4 / 0.43) 16%, oklch(0.3346 0.1405 262.4 / 0.24) 36%, oklch(0.3346 0.1405 262.4 / 0.11) 56%, oklch(0.3346 0.1405 262.4 / 0.04) 76%, oklch(0.3346 0.1405 262.4 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-900) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%)" + } + } + } + }, + "surface-brand-subtle": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.primary.900}", + "radius": 0.78, + "at": "50% -10%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.3346 0.1405 262.4 / 0.45) 0%, oklch(0.3346 0.1405 262.4 / 0.43) 16%, oklch(0.3346 0.1405 262.4 / 0.24) 36%, oklch(0.3346 0.1405 262.4 / 0.11) 56%, oklch(0.3346 0.1405 262.4 / 0.04) 76%, oklch(0.3346 0.1405 262.4 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%)" + } + } + } + }, + "text-default": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "Body copy and headings." + }, + "text-subtle": { + "$type": "color", + "$value": "{palette.neutral-light.400}", + "$description": "Secondary text — descriptions, metadata, helper copy." + }, + "text-inverse": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "Labels on a filled control — action-primary, -neutral, -danger." + }, + "text-success": { + "$type": "color", + "$value": "{palette.success.300}", + "$description": "Success text and icons." + }, + "text-warning": { + "$type": "color", + "$value": "{palette.warning.300}", + "$description": "Warning text and icons. Always with an icon." + }, + "text-error": { + "$type": "color", + "$value": "{palette.danger.300}", + "$description": "Error text and icons." + }, + "border-strong": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "Emphasis — an active field, a selected row." + }, + "border-default": { + "$type": "color", + "$value": "{palette.neutral-light.600}", + "$description": "Any border that carries meaning — control outlines, field borders." + }, + "border-subtle": { + "$type": "color", + "$value": "{palette.neutral-light.900}", + "$description": "Decorative hairlines — table rules, dividers. Contrast-exempt." + }, + "action-primary": { + "$type": "color", + "$value": "{palette.primary.400}", + "$description": "Primary interactive fills, strokes, links and focus rings." + }, + "action-primary-hover": { + "$type": "color", + "$value": "{palette.primary.300}", + "$description": "Hover for action-primary." + }, + "action-primary-pressed": { + "$type": "color", + "$value": "{palette.primary.200}", + "$description": "Pressed for action-primary." + }, + "action-danger": { + "$type": "color", + "$value": "{palette.danger.400}", + "$description": "Destructive actions — delete, remove, revoke." + }, + "action-danger-hover": { + "$type": "color", + "$value": "{palette.danger.300}", + "$description": "Hover for action-danger." + }, + "action-danger-pressed": { + "$type": "color", + "$value": "{palette.danger.200}", + "$description": "Pressed for action-danger." + }, + "action-neutral": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "The neutral action fill. Button variant=\"secondary\" resolves here." + }, + "action-neutral-hover": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "Hover for action-neutral." + }, + "action-neutral-pressed": { + "$type": "color", + "$value": "{white}", + "$description": "Pressed for action-neutral." + }, + "action-disabled": { + "$type": "color", + "$value": "{palette.neutral-dark.800}", + "$description": "The disabled fill for every action variant. Contrast-exempt (inactive control)." + }, + "state-hover": { + "$type": "color", + "$value": "{palette.primary.600-a6}", + "$description": "Rest → hover on rows, tabs, chips, menu items." + }, + "state-pressed": { + "$type": "color", + "$value": "{palette.primary.600-a9}", + "$description": "Hover → pressed on the same elements." + }, + "state-selected": { + "$type": "color", + "$value": "{palette.primary.600-a8}", + "$description": "The persistent \"this one is chosen\" tint." + }, + "viz-1": { + "$type": "color", + "$value": "{palette.primary.500}", + "$description": "Categorical series 1." + }, + "viz-2": { + "$type": "color", + "$value": "{palette.accent.500}", + "$description": "Categorical series 2." + }, + "viz-3": { + "$type": "color", + "$value": "{palette.warning.400}", + "$description": "Categorical series 3." + }, + "viz-4": { + "$type": "color", + "$value": "{palette.accent-alt.600}", + "$description": "Categorical series 4." + }, + "viz-5": { + "$type": "color", + "$value": "{palette.success.300}", + "$description": "Categorical series 5." + }, + "viz-6": { + "$type": "color", + "$value": "{palette.danger.600}", + "$description": "Categorical series 6." + }, + "viz-7": { + "$type": "color", + "$value": "{palette.neutral-dark.500}", + "$description": "Categorical series 7." + }, + "viz-8": { + "$type": "color", + "$value": "{palette.neutral-light.600}", + "$description": "Categorical series 8." + }, + "viz-seq-1": { + "$type": "color", + "$value": "{palette.primary.600}", + "$description": "Sequential step 1, low to high." + }, + "viz-seq-2": { + "$type": "color", + "$value": "{palette.primary.500}", + "$description": "Sequential step 2, low to high." + }, + "viz-seq-3": { + "$type": "color", + "$value": "{palette.primary.400}", + "$description": "Sequential step 3, low to high." + }, + "viz-seq-4": { + "$type": "color", + "$value": "{palette.primary.300}", + "$description": "Sequential step 4, low to high." + }, + "shadow-key": { + "$type": "color", + "$value": "{black-a32}", + "$description": "The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur." + }, + "shadow-ambient": { + "$type": "color", + "$value": "{black-a24}", + "$description": "The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff." + }, + "shadow-scrim": { + "$type": "color", + "$value": "{black-a60}", + "$description": "The scrim behind modals and dialogs. Mode-independent." + } +} diff --git a/packages/gamut-styles/src/tokens/generated/lxStudio/light.tokens.json b/packages/gamut-styles/src/tokens/generated/lxStudio/light.tokens.json new file mode 100644 index 0000000000..8a1dbb1303 --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/lxStudio/light.tokens.json @@ -0,0 +1,315 @@ +{ + "$description": "light mode roles for \"lxStudio\". Do not edit.", + "surface-sunken": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "Wells, insets, code blocks — anything recessed into the page." + }, + "surface-default": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "The page background — the base every other surface elevates from." + }, + "surface-raised": { + "$type": "color", + "$value": "{white}", + "$description": "The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits." + }, + "surface-success": { + "$type": "color", + "$value": "{palette.success.200}", + "$description": "The success tint behind an alert or inline confirmation." + }, + "surface-warning": { + "$type": "color", + "$value": "{palette.warning.200}", + "$description": "The warning tint behind an alert or inline caution." + }, + "surface-error": { + "$type": "color", + "$value": "{palette.danger.200}", + "$description": "The error tint behind an alert or inline validation message." + }, + "surface-brand": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.accent-alt.300}", + "radius": 0.85, + "at": "100% -5%" + }, + { + "color": "{palette.primary.300}", + "radius": 1.21, + "at": "5% 100%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.8497 0.043 287.1 / 0.45) 0%, oklch(0.8497 0.043 287.1 / 0.43) 16%, oklch(0.8497 0.043 287.1 / 0.24) 36%, oklch(0.8497 0.043 287.1 / 0.11) 56%, oklch(0.8497 0.043 287.1 / 0.04) 76%, oklch(0.8497 0.043 287.1 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.8497 0.0407 262.4 / 0.45) 0%, oklch(0.8497 0.0407 262.4 / 0.43) 16%, oklch(0.8497 0.0407 262.4 / 0.24) 36%, oklch(0.8497 0.0407 262.4 / 0.11) 56%, oklch(0.8497 0.0407 262.4 / 0.04) 76%, oklch(0.8497 0.0407 262.4 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-300) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%)" + } + } + } + }, + "surface-brand-subtle": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.primary.300}", + "radius": 0.78, + "at": "50% -10%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.8497 0.0407 262.4 / 0.45) 0%, oklch(0.8497 0.0407 262.4 / 0.43) 16%, oklch(0.8497 0.0407 262.4 / 0.24) 36%, oklch(0.8497 0.0407 262.4 / 0.11) 56%, oklch(0.8497 0.0407 262.4 / 0.04) 76%, oklch(0.8497 0.0407 262.4 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%)" + } + } + } + }, + "text-default": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "Body copy and headings." + }, + "text-subtle": { + "$type": "color", + "$value": "{palette.neutral-dark.800}", + "$description": "Secondary text — descriptions, metadata, helper copy." + }, + "text-inverse": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "Labels on a filled control — action-primary, -neutral, -danger." + }, + "text-success": { + "$type": "color", + "$value": "{palette.success.900}", + "$description": "Success text and icons." + }, + "text-warning": { + "$type": "color", + "$value": "{palette.warning.900}", + "$description": "Warning text and icons. Always with an icon." + }, + "text-error": { + "$type": "color", + "$value": "{palette.danger.900}", + "$description": "Error text and icons." + }, + "border-strong": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "Emphasis — an active field, a selected row." + }, + "border-default": { + "$type": "color", + "$value": "{palette.neutral-dark.600}", + "$description": "Any border that carries meaning — control outlines, field borders." + }, + "border-subtle": { + "$type": "color", + "$value": "{palette.neutral-dark.300}", + "$description": "Decorative hairlines — table rules, dividers. Contrast-exempt." + }, + "action-primary": { + "$type": "color", + "$value": "{palette.primary.800}", + "$description": "Primary interactive fills, strokes, links and focus rings." + }, + "action-primary-hover": { + "$type": "color", + "$value": "{palette.primary.900}", + "$description": "Hover for action-primary." + }, + "action-primary-pressed": { + "$type": "color", + "$value": "{palette.primary.1000}", + "$description": "Pressed for action-primary." + }, + "action-danger": { + "$type": "color", + "$value": "{palette.danger.800}", + "$description": "Destructive actions — delete, remove, revoke." + }, + "action-danger-hover": { + "$type": "color", + "$value": "{palette.danger.900}", + "$description": "Hover for action-danger." + }, + "action-danger-pressed": { + "$type": "color", + "$value": "{palette.danger.1000}", + "$description": "Pressed for action-danger." + }, + "action-neutral": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "The neutral action fill. Button variant=\"secondary\" resolves here." + }, + "action-neutral-hover": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "Hover for action-neutral." + }, + "action-neutral-pressed": { + "$type": "color", + "$value": "{black}", + "$description": "Pressed for action-neutral." + }, + "action-disabled": { + "$type": "color", + "$value": "{palette.neutral-light.400}", + "$description": "The disabled fill for every action variant. Contrast-exempt (inactive control)." + }, + "state-hover": { + "$type": "color", + "$value": "{palette.primary.600-a6}", + "$description": "Rest → hover on rows, tabs, chips, menu items." + }, + "state-pressed": { + "$type": "color", + "$value": "{palette.primary.600-a9}", + "$description": "Hover → pressed on the same elements." + }, + "state-selected": { + "$type": "color", + "$value": "{palette.primary.600-a8}", + "$description": "The persistent \"this one is chosen\" tint." + }, + "viz-1": { + "$type": "color", + "$value": "{palette.primary.800}", + "$description": "Categorical series 1." + }, + "viz-2": { + "$type": "color", + "$value": "{palette.accent.700}", + "$description": "Categorical series 2." + }, + "viz-3": { + "$type": "color", + "$value": "{palette.warning.700}", + "$description": "Categorical series 3." + }, + "viz-4": { + "$type": "color", + "$value": "{palette.accent-alt.600}", + "$description": "Categorical series 4." + }, + "viz-5": { + "$type": "color", + "$value": "{palette.success.600}", + "$description": "Categorical series 5." + }, + "viz-6": { + "$type": "color", + "$value": "{palette.danger.600}", + "$description": "Categorical series 6." + }, + "viz-7": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "Categorical series 7." + }, + "viz-8": { + "$type": "color", + "$value": "{palette.neutral-light.600}", + "$description": "Categorical series 8." + }, + "viz-seq-1": { + "$type": "color", + "$value": "{palette.primary.600}", + "$description": "Sequential step 1, low to high." + }, + "viz-seq-2": { + "$type": "color", + "$value": "{palette.primary.700}", + "$description": "Sequential step 2, low to high." + }, + "viz-seq-3": { + "$type": "color", + "$value": "{palette.primary.800}", + "$description": "Sequential step 3, low to high." + }, + "viz-seq-4": { + "$type": "color", + "$value": "{palette.primary.900}", + "$description": "Sequential step 4, low to high." + }, + "shadow-key": { + "$type": "color", + "$value": "{palette.neutral-dark.1100-a16}", + "$description": "The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur." + }, + "shadow-ambient": { + "$type": "color", + "$value": "{palette.neutral-dark.1100-a12}", + "$description": "The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff." + }, + "shadow-scrim": { + "$type": "color", + "$value": "{black-a60}", + "$description": "The scrim behind modals and dialogs. Mode-independent." + } +} diff --git a/packages/gamut-styles/src/tokens/generated/lxStudio/palette.tokens.json b/packages/gamut-styles/src/tokens/generated/lxStudio/palette.tokens.json new file mode 100644 index 0000000000..bf4b025645 --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/lxStudio/palette.tokens.json @@ -0,0 +1,1745 @@ +{ + "$description": "Generated from the \"lxStudio\" brand (tier 3). Do not edit.", + "white": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 1, + 0, + 0 + ], + "alpha": 1, + "hex": "#ffffff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "black": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 1, + "hex": "#000000" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "palette": { + "neutral-light": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9876, + 0.0017, + 247.84 + ], + "alpha": 1, + "hex": "#fafbfc" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0025, + 247.8 + ], + "alpha": 1, + "hex": "#e9eaeb" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0086, + 247.8 + ], + "alpha": 1, + "hex": "#c9ced3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0178, + 247.8 + ], + "alpha": 1, + "hex": "#a9b3bd" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.0316, + 247.8 + ], + "alpha": 1, + "hex": "#8798a8" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.0312, + 247.8 + ], + "alpha": 1, + "hex": "#6d7d8d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.0253, + 247.8 + ], + "alpha": 1, + "hex": "#576470" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0198, + 247.8 + ], + "alpha": 1, + "hex": "#434c55" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0157, + 247.8 + ], + "alpha": 1, + "hex": "#30373e" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0112, + 247.8 + ], + "alpha": 1, + "hex": "#1d2227" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0082, + 247.8 + ], + "alpha": 1, + "hex": "#121518" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "neutral-dark": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0012, + 271.3 + ], + "alpha": 1, + "hex": "#fafafb" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0024, + 271.3 + ], + "alpha": 1, + "hex": "#e9eaeb" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0081, + 271.3 + ], + "alpha": 1, + "hex": "#cbcdd3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0169, + 271.3 + ], + "alpha": 1, + "hex": "#aeb2bd" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.0306, + 271.3 + ], + "alpha": 1, + "hex": "#8e95a9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.0454, + 271.3 + ], + "alpha": 1, + "hex": "#717a97" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.0538, + 271.3 + ], + "alpha": 1, + "hex": "#576182" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0479, + 271.3 + ], + "alpha": 1, + "hex": "#414965" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0381, + 271.3 + ], + "alpha": 1, + "hex": "#2f364b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0272, + 271.3 + ], + "alpha": 1, + "hex": "#1d212f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0199, + 271.3 + ], + "alpha": 1, + "hex": "#11141d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100-a16": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0199, + 271.3 + ], + "alpha": 0.16, + "hex": "#11141d" + } + }, + "1100-a12": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0199, + 271.3 + ], + "alpha": 0.12, + "hex": "#11141d" + } + } + }, + "primary": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0064, + 262.4 + ], + "alpha": 1, + "hex": "#f8fbff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0121, + 262.4 + ], + "alpha": 1, + "hex": "#e6eaf2" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0407, + 262.4 + ], + "alpha": 1, + "hex": "#c0cee9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0846, + 262.4 + ], + "alpha": 1, + "hex": "#95b2e8" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1531, + 262.4 + ], + "alpha": 1, + "hex": "#6192f3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2274, + 262.4 + ], + "alpha": 1, + "hex": "#276eff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.2254, + 262.4 + ], + "alpha": 1, + "hex": "#0a51df" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.1767, + 262.4 + ], + "alpha": 1, + "hex": "#0c3fa8" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.1405, + 262.4 + ], + "alpha": 1, + "hex": "#082e7d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.1001, + 262.4 + ], + "alpha": 1, + "hex": "#051c50" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0734, + 262.4 + ], + "alpha": 1, + "hex": "#031135" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "600-a6": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2274, + 262.4 + ], + "alpha": 0.06, + "hex": "#276eff" + } + }, + "600-a9": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2274, + 262.4 + ], + "alpha": 0.09, + "hex": "#276eff" + } + }, + "600-a8": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2274, + 262.4 + ], + "alpha": 0.08, + "hex": "#276eff" + } + } + }, + "accent": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0205, + 93.6 + ], + "alpha": 1, + "hex": "#fffbeb" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0378, + 93.6 + ], + "alpha": 1, + "hex": "#f2eace" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0957, + 93.6 + ], + "alpha": 1, + "hex": "#e2cd84" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.1094, + 93.6 + ], + "alpha": 1, + "hex": "#c9b15b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1209, + 93.6 + ], + "alpha": 1, + "hex": "#ae932e" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.1195, + 93.6 + ], + "alpha": 1, + "hex": "#937900" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.0967, + 93.6 + ], + "alpha": 1, + "hex": "#75610e" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0758, + 93.6 + ], + "alpha": 1, + "hex": "#594a10" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0603, + 93.6 + ], + "alpha": 1, + "hex": "#41360b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.043, + 93.6 + ], + "alpha": 1, + "hex": "#282106" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0315, + 93.6 + ], + "alpha": 1, + "hex": "#191404" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "accent-alt": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0067, + 287.1 + ], + "alpha": 1, + "hex": "#fafaff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0127, + 287.1 + ], + "alpha": 1, + "hex": "#e9e9f2" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.043, + 287.1 + ], + "alpha": 1, + "hex": "#cbcae9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0896, + 287.1 + ], + "alpha": 1, + "hex": "#aeaae9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1624, + 287.1 + ], + "alpha": 1, + "hex": "#9182f3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2416, + 287.1 + ], + "alpha": 1, + "hex": "#7a52ff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.2722, + 287.1 + ], + "alpha": 1, + "hex": "#661af0" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.2134, + 287.1 + ], + "alpha": 1, + "hex": "#4d1cb5" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.1696, + 287.1 + ], + "alpha": 1, + "hex": "#381587" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.1209, + 287.1 + ], + "alpha": 1, + "hex": "#220d57" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0887, + 287.1 + ], + "alpha": 1, + "hex": "#150739" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "danger": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0066, + 29.4 + ], + "alpha": 1, + "hex": "#fff9f8" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0128, + 29.4 + ], + "alpha": 1, + "hex": "#f2e7e5" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0452, + 29.4 + ], + "alpha": 1, + "hex": "#eac3bd" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.099, + 29.4 + ], + "alpha": 1, + "hex": "#ea9a8d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.191, + 29.4 + ], + "alpha": 1, + "hex": "#f45b4a" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2382, + 29.4 + ], + "alpha": 1, + "hex": "#e70400" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.1928, + 29.4 + ], + "alpha": 1, + "hex": "#b7160d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.1512, + 29.4 + ], + "alpha": 1, + "hex": "#8a170f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.1202, + 29.4 + ], + "alpha": 1, + "hex": "#67110a" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0857, + 29.4 + ], + "alpha": 1, + "hex": "#420a06" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0628, + 29.4 + ], + "alpha": 1, + "hex": "#2b0603" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "warning": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0082, + 58.3 + ], + "alpha": 1, + "hex": "#fff9f5" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0156, + 58.3 + ], + "alpha": 1, + "hex": "#f2e8e0" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0545, + 58.3 + ], + "alpha": 1, + "hex": "#eac6ab" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.1179, + 58.3 + ], + "alpha": 1, + "hex": "#ea9f63" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1408, + 58.3 + ], + "alpha": 1, + "hex": "#d47d2d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.1392, + 58.3 + ], + "alpha": 1, + "hex": "#b66200" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.1127, + 58.3 + ], + "alpha": 1, + "hex": "#914f0e" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0883, + 58.3 + ], + "alpha": 1, + "hex": "#6d3d10" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0702, + 58.3 + ], + "alpha": 1, + "hex": "#512c0b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.05, + 58.3 + ], + "alpha": 1, + "hex": "#331b06" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0367, + 58.3 + ], + "alpha": 1, + "hex": "#201004" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "success": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0204, + 156.7 + ], + "alpha": 1, + "hex": "#f0fff5" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0405, + 156.7 + ], + "alpha": 1, + "hex": "#d5f3df" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.1118, + 156.7 + ], + "alpha": 1, + "hex": "#8de4b0" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.1278, + 156.7 + ], + "alpha": 1, + "hex": "#64ca90" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1413, + 156.7 + ], + "alpha": 1, + "hex": "#34af71" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.1396, + 156.7 + ], + "alpha": 1, + "hex": "#009358" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.113, + 156.7 + ], + "alpha": 1, + "hex": "#127547" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0886, + 156.7 + ], + "alpha": 1, + "hex": "#135936" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0704, + 156.7 + ], + "alpha": 1, + "hex": "#0e4127" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0502, + 156.7 + ], + "alpha": 1, + "hex": "#082917" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0368, + 156.7 + ], + "alpha": 1, + "hex": "#05190d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + } + }, + "black-a32": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 0.32, + "hex": "#000000" + } + }, + "black-a24": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 0.24, + "hex": "#000000" + } + }, + "black-a60": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 0.6, + "hex": "#000000" + } + } +} diff --git a/packages/gamut-styles/src/tokens/generated/lxStudio/tokens.css b/packages/gamut-styles/src/tokens/generated/lxStudio/tokens.css new file mode 100644 index 0000000000..1fc403dcc4 --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/lxStudio/tokens.css @@ -0,0 +1,211 @@ +/* Generated by @skillsoft/gamut-brand from the "lxStudio" brand. Do not edit. */ +@layer reset, base, tokens, recipes, utilities; + +@layer tokens { + :root, [data-brand="lxStudio"] { + --color-white: oklch(1.0000 0.0000 0.00); + --color-black: oklch(0.0000 0.0000 0.00); + --color-palette-neutral-light-100: oklch(0.9876 0.0017 247.84); + --color-palette-neutral-light-200: oklch(0.9364 0.0025 247.80); + --color-palette-neutral-light-300: oklch(0.8497 0.0086 247.80); + --color-palette-neutral-light-400: oklch(0.7628 0.0178 247.80); + --color-palette-neutral-light-500: oklch(0.6707 0.0316 247.80); + --color-palette-neutral-light-600: oklch(0.5835 0.0312 247.80); + --color-palette-neutral-light-700: oklch(0.4972 0.0253 247.80); + --color-palette-neutral-light-800: oklch(0.4115 0.0198 247.80); + --color-palette-neutral-light-900: oklch(0.3346 0.0157 247.80); + --color-palette-neutral-light-1000: oklch(0.2499 0.0112 247.80); + --color-palette-neutral-light-1100: oklch(0.1925 0.0082 247.80); + --color-palette-neutral-dark-100: oklch(0.9862 0.0012 271.30); + --color-palette-neutral-dark-200: oklch(0.9364 0.0024 271.30); + --color-palette-neutral-dark-300: oklch(0.8497 0.0081 271.30); + --color-palette-neutral-dark-400: oklch(0.7628 0.0169 271.30); + --color-palette-neutral-dark-500: oklch(0.6707 0.0306 271.30); + --color-palette-neutral-dark-600: oklch(0.5835 0.0454 271.30); + --color-palette-neutral-dark-700: oklch(0.4972 0.0538 271.30); + --color-palette-neutral-dark-800: oklch(0.4115 0.0479 271.30); + --color-palette-neutral-dark-900: oklch(0.3346 0.0381 271.30); + --color-palette-neutral-dark-1000: oklch(0.2499 0.0272 271.30); + --color-palette-neutral-dark-1100: oklch(0.1925 0.0199 271.30); + --color-palette-neutral-dark-1100-a16: oklch(0.1925 0.0199 271.30 / 0.16); + --color-palette-neutral-dark-1100-a12: oklch(0.1925 0.0199 271.30 / 0.12); + --color-palette-primary-100: oklch(0.9862 0.0064 262.40); + --color-palette-primary-200: oklch(0.9364 0.0121 262.40); + --color-palette-primary-300: oklch(0.8497 0.0407 262.40); + --color-palette-primary-400: oklch(0.7628 0.0846 262.40); + --color-palette-primary-500: oklch(0.6707 0.1531 262.40); + --color-palette-primary-600: oklch(0.5835 0.2274 262.40); + --color-palette-primary-700: oklch(0.4972 0.2254 262.40); + --color-palette-primary-800: oklch(0.4115 0.1767 262.40); + --color-palette-primary-900: oklch(0.3346 0.1405 262.40); + --color-palette-primary-1000: oklch(0.2499 0.1001 262.40); + --color-palette-primary-1100: oklch(0.1925 0.0734 262.40); + --color-palette-primary-600-a6: oklch(0.5835 0.2274 262.40 / 0.06); + --color-palette-primary-600-a9: oklch(0.5835 0.2274 262.40 / 0.09); + --color-palette-primary-600-a8: oklch(0.5835 0.2274 262.40 / 0.08); + --color-palette-accent-100: oklch(0.9862 0.0205 93.60); + --color-palette-accent-200: oklch(0.9364 0.0378 93.60); + --color-palette-accent-300: oklch(0.8497 0.0957 93.60); + --color-palette-accent-400: oklch(0.7628 0.1094 93.60); + --color-palette-accent-500: oklch(0.6707 0.1209 93.60); + --color-palette-accent-600: oklch(0.5835 0.1195 93.60); + --color-palette-accent-700: oklch(0.4972 0.0967 93.60); + --color-palette-accent-800: oklch(0.4115 0.0758 93.60); + --color-palette-accent-900: oklch(0.3346 0.0603 93.60); + --color-palette-accent-1000: oklch(0.2499 0.0430 93.60); + --color-palette-accent-1100: oklch(0.1925 0.0315 93.60); + --color-palette-accent-alt-100: oklch(0.9862 0.0067 287.10); + --color-palette-accent-alt-200: oklch(0.9364 0.0127 287.10); + --color-palette-accent-alt-300: oklch(0.8497 0.0430 287.10); + --color-palette-accent-alt-400: oklch(0.7628 0.0896 287.10); + --color-palette-accent-alt-500: oklch(0.6707 0.1624 287.10); + --color-palette-accent-alt-600: oklch(0.5835 0.2416 287.10); + --color-palette-accent-alt-700: oklch(0.4972 0.2722 287.10); + --color-palette-accent-alt-800: oklch(0.4115 0.2134 287.10); + --color-palette-accent-alt-900: oklch(0.3346 0.1696 287.10); + --color-palette-accent-alt-1000: oklch(0.2499 0.1209 287.10); + --color-palette-accent-alt-1100: oklch(0.1925 0.0887 287.10); + --color-palette-danger-100: oklch(0.9862 0.0066 29.40); + --color-palette-danger-200: oklch(0.9364 0.0128 29.40); + --color-palette-danger-300: oklch(0.8497 0.0452 29.40); + --color-palette-danger-400: oklch(0.7628 0.0990 29.40); + --color-palette-danger-500: oklch(0.6707 0.1910 29.40); + --color-palette-danger-600: oklch(0.5835 0.2382 29.40); + --color-palette-danger-700: oklch(0.4972 0.1928 29.40); + --color-palette-danger-800: oklch(0.4115 0.1512 29.40); + --color-palette-danger-900: oklch(0.3346 0.1202 29.40); + --color-palette-danger-1000: oklch(0.2499 0.0857 29.40); + --color-palette-danger-1100: oklch(0.1925 0.0628 29.40); + --color-palette-warning-100: oklch(0.9862 0.0082 58.30); + --color-palette-warning-200: oklch(0.9364 0.0156 58.30); + --color-palette-warning-300: oklch(0.8497 0.0545 58.30); + --color-palette-warning-400: oklch(0.7628 0.1179 58.30); + --color-palette-warning-500: oklch(0.6707 0.1408 58.30); + --color-palette-warning-600: oklch(0.5835 0.1392 58.30); + --color-palette-warning-700: oklch(0.4972 0.1127 58.30); + --color-palette-warning-800: oklch(0.4115 0.0883 58.30); + --color-palette-warning-900: oklch(0.3346 0.0702 58.30); + --color-palette-warning-1000: oklch(0.2499 0.0500 58.30); + --color-palette-warning-1100: oklch(0.1925 0.0367 58.30); + --color-palette-success-100: oklch(0.9862 0.0204 156.70); + --color-palette-success-200: oklch(0.9364 0.0405 156.70); + --color-palette-success-300: oklch(0.8497 0.1118 156.70); + --color-palette-success-400: oklch(0.7628 0.1278 156.70); + --color-palette-success-500: oklch(0.6707 0.1413 156.70); + --color-palette-success-600: oklch(0.5835 0.1396 156.70); + --color-palette-success-700: oklch(0.4972 0.1130 156.70); + --color-palette-success-800: oklch(0.4115 0.0886 156.70); + --color-palette-success-900: oklch(0.3346 0.0704 156.70); + --color-palette-success-1000: oklch(0.2499 0.0502 156.70); + --color-palette-success-1100: oklch(0.1925 0.0368 156.70); + --color-black-a32: oklch(0.0000 0.0000 0.00 / 0.32); + --color-black-a24: oklch(0.0000 0.0000 0.00 / 0.24); + --color-black-a60: oklch(0.0000 0.0000 0.00 / 0.6); + } + + :root, [data-color-mode="light"] { + color-scheme: light; + color: var(--color-text-default); + --color-background-current: var(--color-surface-default); + --color-surface-sunken: var(--color-palette-neutral-light-200); /** Wells, insets, code blocks — anything recessed into the page. */ + --color-surface-default: var(--color-palette-neutral-light-100); /** The page background — the base every other surface elevates from. */ + --color-surface-raised: var(--color-white); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */ + --color-surface-success: var(--color-palette-success-200); /** The success tint behind an alert or inline confirmation. */ + --color-surface-warning: var(--color-palette-warning-200); /** The warning tint behind an alert or inline caution. */ + --color-surface-error: var(--color-palette-danger-200); /** The error tint behind an alert or inline validation message. */ + --color-surface-brand: var(--color-palette-neutral-light-100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */ + --color-surface-brand-subtle: var(--color-palette-neutral-light-100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */ + --color-text-default: var(--color-palette-neutral-dark-1000); /** Body copy and headings. */ + --color-text-subtle: var(--color-palette-neutral-dark-800); /** Secondary text — descriptions, metadata, helper copy. */ + --color-text-inverse: var(--color-palette-neutral-light-200); /** Labels on a filled control — action-primary, -neutral, -danger. */ + --color-text-success: var(--color-palette-success-900); /** Success text and icons. */ + --color-text-warning: var(--color-palette-warning-900); /** Warning text and icons. Always with an icon. */ + --color-text-error: var(--color-palette-danger-900); /** Error text and icons. */ + --color-border-strong: var(--color-palette-neutral-dark-1100); /** Emphasis — an active field, a selected row. */ + --color-border-default: var(--color-palette-neutral-dark-600); /** Any border that carries meaning — control outlines, field borders. */ + --color-border-subtle: var(--color-palette-neutral-dark-300); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */ + --color-action-primary: var(--color-palette-primary-800); /** Primary interactive fills, strokes, links and focus rings. */ + --color-action-primary-hover: var(--color-palette-primary-900); /** Hover for action-primary. */ + --color-action-primary-pressed: var(--color-palette-primary-1000); /** Pressed for action-primary. */ + --color-action-danger: var(--color-palette-danger-800); /** Destructive actions — delete, remove, revoke. */ + --color-action-danger-hover: var(--color-palette-danger-900); /** Hover for action-danger. */ + --color-action-danger-pressed: var(--color-palette-danger-1000); /** Pressed for action-danger. */ + --color-action-neutral: var(--color-palette-neutral-dark-1000); /** The neutral action fill. Button variant="secondary" resolves here. */ + --color-action-neutral-hover: var(--color-palette-neutral-dark-1100); /** Hover for action-neutral. */ + --color-action-neutral-pressed: var(--color-black); /** Pressed for action-neutral. */ + --color-action-disabled: var(--color-palette-neutral-light-400); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */ + --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */ + --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */ + --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent "this one is chosen" tint. */ + --color-viz-1: var(--color-palette-primary-800); /** Categorical series 1. */ + --color-viz-2: var(--color-palette-accent-700); /** Categorical series 2. */ + --color-viz-3: var(--color-palette-warning-700); /** Categorical series 3. */ + --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */ + --color-viz-5: var(--color-palette-success-600); /** Categorical series 5. */ + --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */ + --color-viz-7: var(--color-palette-neutral-dark-1000); /** Categorical series 7. */ + --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */ + --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */ + --color-viz-seq-2: var(--color-palette-primary-700); /** Sequential step 2, low to high. */ + --color-viz-seq-3: var(--color-palette-primary-800); /** Sequential step 3, low to high. */ + --color-viz-seq-4: var(--color-palette-primary-900); /** Sequential step 4, low to high. */ + --color-shadow-key: var(--color-palette-neutral-dark-1100-a16); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */ + --color-shadow-ambient: var(--color-palette-neutral-dark-1100-a12); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */ + --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */ + --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-300) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%); + --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%); + } + + [data-color-mode="dark"] { + color-scheme: dark; + color: var(--color-text-default); + --color-background-current: var(--color-surface-default); + --color-surface-sunken: var(--color-black); /** Wells, insets, code blocks — anything recessed into the page. */ + --color-surface-default: var(--color-palette-neutral-dark-1100); /** The page background — the base every other surface elevates from. */ + --color-surface-raised: var(--color-palette-neutral-dark-1000); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */ + --color-surface-success: var(--color-palette-success-1000); /** The success tint behind an alert or inline confirmation. */ + --color-surface-warning: var(--color-palette-warning-1000); /** The warning tint behind an alert or inline caution. */ + --color-surface-error: var(--color-palette-danger-1000); /** The error tint behind an alert or inline validation message. */ + --color-surface-brand: var(--color-palette-neutral-dark-1100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */ + --color-surface-brand-subtle: var(--color-palette-neutral-dark-1100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */ + --color-text-default: var(--color-palette-neutral-light-200); /** Body copy and headings. */ + --color-text-subtle: var(--color-palette-neutral-light-400); /** Secondary text — descriptions, metadata, helper copy. */ + --color-text-inverse: var(--color-palette-neutral-dark-1000); /** Labels on a filled control — action-primary, -neutral, -danger. */ + --color-text-success: var(--color-palette-success-300); /** Success text and icons. */ + --color-text-warning: var(--color-palette-warning-300); /** Warning text and icons. Always with an icon. */ + --color-text-error: var(--color-palette-danger-300); /** Error text and icons. */ + --color-border-strong: var(--color-palette-neutral-light-100); /** Emphasis — an active field, a selected row. */ + --color-border-default: var(--color-palette-neutral-light-600); /** Any border that carries meaning — control outlines, field borders. */ + --color-border-subtle: var(--color-palette-neutral-light-900); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */ + --color-action-primary: var(--color-palette-primary-400); /** Primary interactive fills, strokes, links and focus rings. */ + --color-action-primary-hover: var(--color-palette-primary-300); /** Hover for action-primary. */ + --color-action-primary-pressed: var(--color-palette-primary-200); /** Pressed for action-primary. */ + --color-action-danger: var(--color-palette-danger-400); /** Destructive actions — delete, remove, revoke. */ + --color-action-danger-hover: var(--color-palette-danger-300); /** Hover for action-danger. */ + --color-action-danger-pressed: var(--color-palette-danger-200); /** Pressed for action-danger. */ + --color-action-neutral: var(--color-palette-neutral-light-200); /** The neutral action fill. Button variant="secondary" resolves here. */ + --color-action-neutral-hover: var(--color-palette-neutral-light-100); /** Hover for action-neutral. */ + --color-action-neutral-pressed: var(--color-white); /** Pressed for action-neutral. */ + --color-action-disabled: var(--color-palette-neutral-dark-800); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */ + --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */ + --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */ + --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent "this one is chosen" tint. */ + --color-viz-1: var(--color-palette-primary-500); /** Categorical series 1. */ + --color-viz-2: var(--color-palette-accent-500); /** Categorical series 2. */ + --color-viz-3: var(--color-palette-warning-400); /** Categorical series 3. */ + --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */ + --color-viz-5: var(--color-palette-success-300); /** Categorical series 5. */ + --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */ + --color-viz-7: var(--color-palette-neutral-dark-500); /** Categorical series 7. */ + --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */ + --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */ + --color-viz-seq-2: var(--color-palette-primary-500); /** Sequential step 2, low to high. */ + --color-viz-seq-3: var(--color-palette-primary-400); /** Sequential step 3, low to high. */ + --color-viz-seq-4: var(--color-palette-primary-300); /** Sequential step 4, low to high. */ + --color-shadow-key: var(--color-black-a32); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */ + --color-shadow-ambient: var(--color-black-a24); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */ + --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */ + --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-900) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%); + --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%); + } +} diff --git a/packages/gamut-styles/src/tokens/generated/lxStudio/values.ts b/packages/gamut-styles/src/tokens/generated/lxStudio/values.ts new file mode 100644 index 0000000000..df709b3381 --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/lxStudio/values.ts @@ -0,0 +1,1027 @@ +/* Generated by @skillsoft/gamut-brand from the "lxStudio" brand. Do not edit. */ + +export const brand = { + "name": "lxStudio", + "initialMode": "light", + "hues": { + "neutral-light": 247.8, + "neutral-dark": 271.3, + "primary": 262.4, + "accent": 93.6, + "danger": 29.4, + "warning": 58.3, + "success": 156.7, + "accent-alt": 287.1 + }, + "derived": { + "accent-alt": "held at the gamut default" + }, + "tier": 3, + "overrides": { + "palette": { + "neutral-light-100": "#fafbfc" + }, + "gradients": { + "surface-brand": { + "accent": "accent-alt" + } + } + }, + "sources": { + "neutral-light": { + "hex": "#fafbfc", + "nearest": 100, + "deltaE": 0 + }, + "neutral-dark": { + "hex": "#10162f", + "nearest": 1000, + "deltaE": 6.56 + }, + "primary": { + "hex": "#1c50bb", + "nearest": 700, + "deltaE": 4.59 + }, + "accent": { + "hex": "#ffd300", + "nearest": 300, + "deltaE": 12.83 + }, + "danger": { + "hex": "#e91c11", + "nearest": 600, + "deltaE": 1.79 + }, + "warning": { + "hex": "#ff8c00", + "nearest": 500, + "deltaE": 8.94 + }, + "success": { + "hex": "#06844f", + "nearest": 700, + "deltaE": 5.29 + } + } +} as const; + +export const palette = { + "white": { + "oklch": "oklch(1 0 0)", + "srgb": "#ffffff" + }, + "black": { + "oklch": "oklch(0 0 0)", + "srgb": "#000000" + }, + "palette-neutral-light-100": { + "oklch": "oklch(0.9876 0.0017 247.84)", + "srgb": "#fafbfc" + }, + "palette-neutral-light-200": { + "oklch": "oklch(0.9364 0.0025 247.8)", + "srgb": "#e9eaeb" + }, + "palette-neutral-light-300": { + "oklch": "oklch(0.8497 0.0086 247.8)", + "srgb": "#c9ced3" + }, + "palette-neutral-light-400": { + "oklch": "oklch(0.7628 0.0178 247.8)", + "srgb": "#a9b3bd" + }, + "palette-neutral-light-500": { + "oklch": "oklch(0.6707 0.0316 247.8)", + "srgb": "#8798a8" + }, + "palette-neutral-light-600": { + "oklch": "oklch(0.5835 0.0312 247.8)", + "srgb": "#6d7d8d" + }, + "palette-neutral-light-700": { + "oklch": "oklch(0.4972 0.0253 247.8)", + "srgb": "#576470" + }, + "palette-neutral-light-800": { + "oklch": "oklch(0.4115 0.0198 247.8)", + "srgb": "#434c55" + }, + "palette-neutral-light-900": { + "oklch": "oklch(0.3346 0.0157 247.8)", + "srgb": "#30373e" + }, + "palette-neutral-light-1000": { + "oklch": "oklch(0.2499 0.0112 247.8)", + "srgb": "#1d2227" + }, + "palette-neutral-light-1100": { + "oklch": "oklch(0.1925 0.0082 247.8)", + "srgb": "#121518" + }, + "palette-neutral-dark-100": { + "oklch": "oklch(0.9862 0.0012 271.3)", + "srgb": "#fafafb" + }, + "palette-neutral-dark-200": { + "oklch": "oklch(0.9364 0.0024 271.3)", + "srgb": "#e9eaeb" + }, + "palette-neutral-dark-300": { + "oklch": "oklch(0.8497 0.0081 271.3)", + "srgb": "#cbcdd3" + }, + "palette-neutral-dark-400": { + "oklch": "oklch(0.7628 0.0169 271.3)", + "srgb": "#aeb2bd" + }, + "palette-neutral-dark-500": { + "oklch": "oklch(0.6707 0.0306 271.3)", + "srgb": "#8e95a9" + }, + "palette-neutral-dark-600": { + "oklch": "oklch(0.5835 0.0454 271.3)", + "srgb": "#717a97" + }, + "palette-neutral-dark-700": { + "oklch": "oklch(0.4972 0.0538 271.3)", + "srgb": "#576182" + }, + "palette-neutral-dark-800": { + "oklch": "oklch(0.4115 0.0479 271.3)", + "srgb": "#414965" + }, + "palette-neutral-dark-900": { + "oklch": "oklch(0.3346 0.0381 271.3)", + "srgb": "#2f364b" + }, + "palette-neutral-dark-1000": { + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "palette-neutral-dark-1100": { + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "palette-primary-100": { + "oklch": "oklch(0.9862 0.0064 262.4)", + "srgb": "#f8fbff" + }, + "palette-primary-200": { + "oklch": "oklch(0.9364 0.0121 262.4)", + "srgb": "#e6eaf2" + }, + "palette-primary-300": { + "oklch": "oklch(0.8497 0.0407 262.4)", + "srgb": "#c0cee9" + }, + "palette-primary-400": { + "oklch": "oklch(0.7628 0.0846 262.4)", + "srgb": "#95b2e8" + }, + "palette-primary-500": { + "oklch": "oklch(0.6707 0.1531 262.4)", + "srgb": "#6192f3" + }, + "palette-primary-600": { + "oklch": "oklch(0.5835 0.2274 262.4)", + "srgb": "#276eff" + }, + "palette-primary-700": { + "oklch": "oklch(0.4972 0.2254 262.4)", + "srgb": "#0a51df" + }, + "palette-primary-800": { + "oklch": "oklch(0.4115 0.1767 262.4)", + "srgb": "#0c3fa8" + }, + "palette-primary-900": { + "oklch": "oklch(0.3346 0.1405 262.4)", + "srgb": "#082e7d" + }, + "palette-primary-1000": { + "oklch": "oklch(0.2499 0.1001 262.4)", + "srgb": "#051c50" + }, + "palette-primary-1100": { + "oklch": "oklch(0.1925 0.0734 262.4)", + "srgb": "#031135" + }, + "palette-accent-100": { + "oklch": "oklch(0.9862 0.0205 93.6)", + "srgb": "#fffbeb" + }, + "palette-accent-200": { + "oklch": "oklch(0.9364 0.0378 93.6)", + "srgb": "#f2eace" + }, + "palette-accent-300": { + "oklch": "oklch(0.8497 0.0957 93.6)", + "srgb": "#e2cd84" + }, + "palette-accent-400": { + "oklch": "oklch(0.7628 0.1094 93.6)", + "srgb": "#c9b15b" + }, + "palette-accent-500": { + "oklch": "oklch(0.6707 0.1209 93.6)", + "srgb": "#ae932e" + }, + "palette-accent-600": { + "oklch": "oklch(0.5835 0.1195 93.6)", + "srgb": "#937900" + }, + "palette-accent-700": { + "oklch": "oklch(0.4972 0.0967 93.6)", + "srgb": "#75610e" + }, + "palette-accent-800": { + "oklch": "oklch(0.4115 0.0758 93.6)", + "srgb": "#594a10" + }, + "palette-accent-900": { + "oklch": "oklch(0.3346 0.0603 93.6)", + "srgb": "#41360b" + }, + "palette-accent-1000": { + "oklch": "oklch(0.2499 0.043 93.6)", + "srgb": "#282106" + }, + "palette-accent-1100": { + "oklch": "oklch(0.1925 0.0315 93.6)", + "srgb": "#191404" + }, + "palette-accent-alt-100": { + "oklch": "oklch(0.9862 0.0067 287.1)", + "srgb": "#fafaff" + }, + "palette-accent-alt-200": { + "oklch": "oklch(0.9364 0.0127 287.1)", + "srgb": "#e9e9f2" + }, + "palette-accent-alt-300": { + "oklch": "oklch(0.8497 0.043 287.1)", + "srgb": "#cbcae9" + }, + "palette-accent-alt-400": { + "oklch": "oklch(0.7628 0.0896 287.1)", + "srgb": "#aeaae9" + }, + "palette-accent-alt-500": { + "oklch": "oklch(0.6707 0.1624 287.1)", + "srgb": "#9182f3" + }, + "palette-accent-alt-600": { + "oklch": "oklch(0.5835 0.2416 287.1)", + "srgb": "#7a52ff" + }, + "palette-accent-alt-700": { + "oklch": "oklch(0.4972 0.2722 287.1)", + "srgb": "#661af0" + }, + "palette-accent-alt-800": { + "oklch": "oklch(0.4115 0.2134 287.1)", + "srgb": "#4d1cb5" + }, + "palette-accent-alt-900": { + "oklch": "oklch(0.3346 0.1696 287.1)", + "srgb": "#381587" + }, + "palette-accent-alt-1000": { + "oklch": "oklch(0.2499 0.1209 287.1)", + "srgb": "#220d57" + }, + "palette-accent-alt-1100": { + "oklch": "oklch(0.1925 0.0887 287.1)", + "srgb": "#150739" + }, + "palette-danger-100": { + "oklch": "oklch(0.9862 0.0066 29.4)", + "srgb": "#fff9f8" + }, + "palette-danger-200": { + "oklch": "oklch(0.9364 0.0128 29.4)", + "srgb": "#f2e7e5" + }, + "palette-danger-300": { + "oklch": "oklch(0.8497 0.0452 29.4)", + "srgb": "#eac3bd" + }, + "palette-danger-400": { + "oklch": "oklch(0.7628 0.099 29.4)", + "srgb": "#ea9a8d" + }, + "palette-danger-500": { + "oklch": "oklch(0.6707 0.191 29.4)", + "srgb": "#f45b4a" + }, + "palette-danger-600": { + "oklch": "oklch(0.5835 0.2382 29.4)", + "srgb": "#e70400" + }, + "palette-danger-700": { + "oklch": "oklch(0.4972 0.1928 29.4)", + "srgb": "#b7160d" + }, + "palette-danger-800": { + "oklch": "oklch(0.4115 0.1512 29.4)", + "srgb": "#8a170f" + }, + "palette-danger-900": { + "oklch": "oklch(0.3346 0.1202 29.4)", + "srgb": "#67110a" + }, + "palette-danger-1000": { + "oklch": "oklch(0.2499 0.0857 29.4)", + "srgb": "#420a06" + }, + "palette-danger-1100": { + "oklch": "oklch(0.1925 0.0628 29.4)", + "srgb": "#2b0603" + }, + "palette-warning-100": { + "oklch": "oklch(0.9862 0.0082 58.3)", + "srgb": "#fff9f5" + }, + "palette-warning-200": { + "oklch": "oklch(0.9364 0.0156 58.3)", + "srgb": "#f2e8e0" + }, + "palette-warning-300": { + "oklch": "oklch(0.8497 0.0545 58.3)", + "srgb": "#eac6ab" + }, + "palette-warning-400": { + "oklch": "oklch(0.7628 0.1179 58.3)", + "srgb": "#ea9f63" + }, + "palette-warning-500": { + "oklch": "oklch(0.6707 0.1408 58.3)", + "srgb": "#d47d2d" + }, + "palette-warning-600": { + "oklch": "oklch(0.5835 0.1392 58.3)", + "srgb": "#b66200" + }, + "palette-warning-700": { + "oklch": "oklch(0.4972 0.1127 58.3)", + "srgb": "#914f0e" + }, + "palette-warning-800": { + "oklch": "oklch(0.4115 0.0883 58.3)", + "srgb": "#6d3d10" + }, + "palette-warning-900": { + "oklch": "oklch(0.3346 0.0702 58.3)", + "srgb": "#512c0b" + }, + "palette-warning-1000": { + "oklch": "oklch(0.2499 0.05 58.3)", + "srgb": "#331b06" + }, + "palette-warning-1100": { + "oklch": "oklch(0.1925 0.0367 58.3)", + "srgb": "#201004" + }, + "palette-success-100": { + "oklch": "oklch(0.9862 0.0204 156.7)", + "srgb": "#f0fff5" + }, + "palette-success-200": { + "oklch": "oklch(0.9364 0.0405 156.7)", + "srgb": "#d5f3df" + }, + "palette-success-300": { + "oklch": "oklch(0.8497 0.1118 156.7)", + "srgb": "#8de4b0" + }, + "palette-success-400": { + "oklch": "oklch(0.7628 0.1278 156.7)", + "srgb": "#64ca90" + }, + "palette-success-500": { + "oklch": "oklch(0.6707 0.1413 156.7)", + "srgb": "#34af71" + }, + "palette-success-600": { + "oklch": "oklch(0.5835 0.1396 156.7)", + "srgb": "#009358" + }, + "palette-success-700": { + "oklch": "oklch(0.4972 0.113 156.7)", + "srgb": "#127547" + }, + "palette-success-800": { + "oklch": "oklch(0.4115 0.0886 156.7)", + "srgb": "#135936" + }, + "palette-success-900": { + "oklch": "oklch(0.3346 0.0704 156.7)", + "srgb": "#0e4127" + }, + "palette-success-1000": { + "oklch": "oklch(0.2499 0.0502 156.7)", + "srgb": "#082917" + }, + "palette-success-1100": { + "oklch": "oklch(0.1925 0.0368 156.7)", + "srgb": "#05190d" + }, + "palette-primary-600-a6": { + "oklch": "oklch(0.5835 0.2274 262.4 / 0.06)", + "srgb": "rgba(39, 110, 255, 0.06)" + }, + "palette-primary-600-a9": { + "oklch": "oklch(0.5835 0.2274 262.4 / 0.09)", + "srgb": "rgba(39, 110, 255, 0.09)" + }, + "palette-primary-600-a8": { + "oklch": "oklch(0.5835 0.2274 262.4 / 0.08)", + "srgb": "rgba(39, 110, 255, 0.08)" + }, + "palette-neutral-dark-1100-a16": { + "oklch": "oklch(0.1925 0.0199 271.3 / 0.16)", + "srgb": "rgba(17, 20, 29, 0.16)" + }, + "black-a32": { + "oklch": "oklch(0 0 0 / 0.32)", + "srgb": "rgba(0, 0, 0, 0.32)" + }, + "palette-neutral-dark-1100-a12": { + "oklch": "oklch(0.1925 0.0199 271.3 / 0.12)", + "srgb": "rgba(17, 20, 29, 0.12)" + }, + "black-a24": { + "oklch": "oklch(0 0 0 / 0.24)", + "srgb": "rgba(0, 0, 0, 0.24)" + }, + "black-a60": { + "oklch": "oklch(0 0 0 / 0.6)", + "srgb": "rgba(0, 0, 0, 0.6)" + } +} as const; + +export const roles = { + "light": { + "surface-sunken": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0025 247.8)", + "srgb": "#e9eaeb" + }, + "surface-default": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9876 0.0017 247.84)", + "srgb": "#fafbfc" + }, + "surface-raised": { + "ref": "white", + "oklch": "oklch(1 0 0)", + "srgb": "#ffffff" + }, + "surface-success": { + "ref": "palette-success-200", + "oklch": "oklch(0.9364 0.0405 156.7)", + "srgb": "#d5f3df" + }, + "surface-warning": { + "ref": "palette-warning-200", + "oklch": "oklch(0.9364 0.0156 58.3)", + "srgb": "#f2e8e0" + }, + "surface-error": { + "ref": "palette-danger-200", + "oklch": "oklch(0.9364 0.0128 29.4)", + "srgb": "#f2e7e5" + }, + "surface-brand": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9876 0.0017 247.84)", + "srgb": "#fafbfc" + }, + "surface-brand-subtle": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9876 0.0017 247.84)", + "srgb": "#fafbfc" + }, + "text-default": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "text-subtle": { + "ref": "palette-neutral-dark-800", + "oklch": "oklch(0.4115 0.0479 271.3)", + "srgb": "#414965" + }, + "text-inverse": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0025 247.8)", + "srgb": "#e9eaeb" + }, + "text-success": { + "ref": "palette-success-900", + "oklch": "oklch(0.3346 0.0704 156.7)", + "srgb": "#0e4127" + }, + "text-warning": { + "ref": "palette-warning-900", + "oklch": "oklch(0.3346 0.0702 58.3)", + "srgb": "#512c0b" + }, + "text-error": { + "ref": "palette-danger-900", + "oklch": "oklch(0.3346 0.1202 29.4)", + "srgb": "#67110a" + }, + "border-strong": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "border-default": { + "ref": "palette-neutral-dark-600", + "oklch": "oklch(0.5835 0.0454 271.3)", + "srgb": "#717a97" + }, + "border-subtle": { + "ref": "palette-neutral-dark-300", + "oklch": "oklch(0.8497 0.0081 271.3)", + "srgb": "#cbcdd3" + }, + "action-primary": { + "ref": "palette-primary-800", + "oklch": "oklch(0.4115 0.1767 262.4)", + "srgb": "#0c3fa8" + }, + "action-primary-hover": { + "ref": "palette-primary-900", + "oklch": "oklch(0.3346 0.1405 262.4)", + "srgb": "#082e7d" + }, + "action-primary-pressed": { + "ref": "palette-primary-1000", + "oklch": "oklch(0.2499 0.1001 262.4)", + "srgb": "#051c50" + }, + "action-danger": { + "ref": "palette-danger-800", + "oklch": "oklch(0.4115 0.1512 29.4)", + "srgb": "#8a170f" + }, + "action-danger-hover": { + "ref": "palette-danger-900", + "oklch": "oklch(0.3346 0.1202 29.4)", + "srgb": "#67110a" + }, + "action-danger-pressed": { + "ref": "palette-danger-1000", + "oklch": "oklch(0.2499 0.0857 29.4)", + "srgb": "#420a06" + }, + "action-neutral": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "action-neutral-hover": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "action-neutral-pressed": { + "ref": "black", + "oklch": "oklch(0 0 0)", + "srgb": "#000000" + }, + "action-disabled": { + "ref": "palette-neutral-light-400", + "oklch": "oklch(0.7628 0.0178 247.8)", + "srgb": "#a9b3bd" + }, + "state-hover": { + "ref": "palette-primary-600-a6", + "oklch": "oklch(0.5835 0.2274 262.4 / 0.06)", + "srgb": "rgba(39, 110, 255, 0.06)" + }, + "state-pressed": { + "ref": "palette-primary-600-a9", + "oklch": "oklch(0.5835 0.2274 262.4 / 0.09)", + "srgb": "rgba(39, 110, 255, 0.09)" + }, + "state-selected": { + "ref": "palette-primary-600-a8", + "oklch": "oklch(0.5835 0.2274 262.4 / 0.08)", + "srgb": "rgba(39, 110, 255, 0.08)" + }, + "viz-1": { + "ref": "palette-primary-800", + "oklch": "oklch(0.4115 0.1767 262.4)", + "srgb": "#0c3fa8" + }, + "viz-2": { + "ref": "palette-accent-700", + "oklch": "oklch(0.4972 0.0967 93.6)", + "srgb": "#75610e" + }, + "viz-3": { + "ref": "palette-warning-700", + "oklch": "oklch(0.4972 0.1127 58.3)", + "srgb": "#914f0e" + }, + "viz-4": { + "ref": "palette-accent-alt-600", + "oklch": "oklch(0.5835 0.2416 287.1)", + "srgb": "#7a52ff" + }, + "viz-5": { + "ref": "palette-success-600", + "oklch": "oklch(0.5835 0.1396 156.7)", + "srgb": "#009358" + }, + "viz-6": { + "ref": "palette-danger-600", + "oklch": "oklch(0.5835 0.2382 29.4)", + "srgb": "#e70400" + }, + "viz-7": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "viz-8": { + "ref": "palette-neutral-light-600", + "oklch": "oklch(0.5835 0.0312 247.8)", + "srgb": "#6d7d8d" + }, + "viz-seq-1": { + "ref": "palette-primary-600", + "oklch": "oklch(0.5835 0.2274 262.4)", + "srgb": "#276eff" + }, + "viz-seq-2": { + "ref": "palette-primary-700", + "oklch": "oklch(0.4972 0.2254 262.4)", + "srgb": "#0a51df" + }, + "viz-seq-3": { + "ref": "palette-primary-800", + "oklch": "oklch(0.4115 0.1767 262.4)", + "srgb": "#0c3fa8" + }, + "viz-seq-4": { + "ref": "palette-primary-900", + "oklch": "oklch(0.3346 0.1405 262.4)", + "srgb": "#082e7d" + }, + "shadow-key": { + "ref": "palette-neutral-dark-1100-a16", + "oklch": "oklch(0.1925 0.0199 271.3 / 0.16)", + "srgb": "rgba(17, 20, 29, 0.16)" + }, + "shadow-ambient": { + "ref": "palette-neutral-dark-1100-a12", + "oklch": "oklch(0.1925 0.0199 271.3 / 0.12)", + "srgb": "rgba(17, 20, 29, 0.12)" + }, + "shadow-scrim": { + "ref": "black-a60", + "oklch": "oklch(0 0 0 / 0.6)", + "srgb": "rgba(0, 0, 0, 0.6)" + } + }, + "dark": { + "surface-sunken": { + "ref": "black", + "oklch": "oklch(0 0 0)", + "srgb": "#000000" + }, + "surface-default": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "surface-raised": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "surface-success": { + "ref": "palette-success-1000", + "oklch": "oklch(0.2499 0.0502 156.7)", + "srgb": "#082917" + }, + "surface-warning": { + "ref": "palette-warning-1000", + "oklch": "oklch(0.2499 0.05 58.3)", + "srgb": "#331b06" + }, + "surface-error": { + "ref": "palette-danger-1000", + "oklch": "oklch(0.2499 0.0857 29.4)", + "srgb": "#420a06" + }, + "surface-brand": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "surface-brand-subtle": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "text-default": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0025 247.8)", + "srgb": "#e9eaeb" + }, + "text-subtle": { + "ref": "palette-neutral-light-400", + "oklch": "oklch(0.7628 0.0178 247.8)", + "srgb": "#a9b3bd" + }, + "text-inverse": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "text-success": { + "ref": "palette-success-300", + "oklch": "oklch(0.8497 0.1118 156.7)", + "srgb": "#8de4b0" + }, + "text-warning": { + "ref": "palette-warning-300", + "oklch": "oklch(0.8497 0.0545 58.3)", + "srgb": "#eac6ab" + }, + "text-error": { + "ref": "palette-danger-300", + "oklch": "oklch(0.8497 0.0452 29.4)", + "srgb": "#eac3bd" + }, + "border-strong": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9876 0.0017 247.84)", + "srgb": "#fafbfc" + }, + "border-default": { + "ref": "palette-neutral-light-600", + "oklch": "oklch(0.5835 0.0312 247.8)", + "srgb": "#6d7d8d" + }, + "border-subtle": { + "ref": "palette-neutral-light-900", + "oklch": "oklch(0.3346 0.0157 247.8)", + "srgb": "#30373e" + }, + "action-primary": { + "ref": "palette-primary-400", + "oklch": "oklch(0.7628 0.0846 262.4)", + "srgb": "#95b2e8" + }, + "action-primary-hover": { + "ref": "palette-primary-300", + "oklch": "oklch(0.8497 0.0407 262.4)", + "srgb": "#c0cee9" + }, + "action-primary-pressed": { + "ref": "palette-primary-200", + "oklch": "oklch(0.9364 0.0121 262.4)", + "srgb": "#e6eaf2" + }, + "action-danger": { + "ref": "palette-danger-400", + "oklch": "oklch(0.7628 0.099 29.4)", + "srgb": "#ea9a8d" + }, + "action-danger-hover": { + "ref": "palette-danger-300", + "oklch": "oklch(0.8497 0.0452 29.4)", + "srgb": "#eac3bd" + }, + "action-danger-pressed": { + "ref": "palette-danger-200", + "oklch": "oklch(0.9364 0.0128 29.4)", + "srgb": "#f2e7e5" + }, + "action-neutral": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0025 247.8)", + "srgb": "#e9eaeb" + }, + "action-neutral-hover": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9876 0.0017 247.84)", + "srgb": "#fafbfc" + }, + "action-neutral-pressed": { + "ref": "white", + "oklch": "oklch(1 0 0)", + "srgb": "#ffffff" + }, + "action-disabled": { + "ref": "palette-neutral-dark-800", + "oklch": "oklch(0.4115 0.0479 271.3)", + "srgb": "#414965" + }, + "state-hover": { + "ref": "palette-primary-600-a6", + "oklch": "oklch(0.5835 0.2274 262.4 / 0.06)", + "srgb": "rgba(39, 110, 255, 0.06)" + }, + "state-pressed": { + "ref": "palette-primary-600-a9", + "oklch": "oklch(0.5835 0.2274 262.4 / 0.09)", + "srgb": "rgba(39, 110, 255, 0.09)" + }, + "state-selected": { + "ref": "palette-primary-600-a8", + "oklch": "oklch(0.5835 0.2274 262.4 / 0.08)", + "srgb": "rgba(39, 110, 255, 0.08)" + }, + "viz-1": { + "ref": "palette-primary-500", + "oklch": "oklch(0.6707 0.1531 262.4)", + "srgb": "#6192f3" + }, + "viz-2": { + "ref": "palette-accent-500", + "oklch": "oklch(0.6707 0.1209 93.6)", + "srgb": "#ae932e" + }, + "viz-3": { + "ref": "palette-warning-400", + "oklch": "oklch(0.7628 0.1179 58.3)", + "srgb": "#ea9f63" + }, + "viz-4": { + "ref": "palette-accent-alt-600", + "oklch": "oklch(0.5835 0.2416 287.1)", + "srgb": "#7a52ff" + }, + "viz-5": { + "ref": "palette-success-300", + "oklch": "oklch(0.8497 0.1118 156.7)", + "srgb": "#8de4b0" + }, + "viz-6": { + "ref": "palette-danger-600", + "oklch": "oklch(0.5835 0.2382 29.4)", + "srgb": "#e70400" + }, + "viz-7": { + "ref": "palette-neutral-dark-500", + "oklch": "oklch(0.6707 0.0306 271.3)", + "srgb": "#8e95a9" + }, + "viz-8": { + "ref": "palette-neutral-light-600", + "oklch": "oklch(0.5835 0.0312 247.8)", + "srgb": "#6d7d8d" + }, + "viz-seq-1": { + "ref": "palette-primary-600", + "oklch": "oklch(0.5835 0.2274 262.4)", + "srgb": "#276eff" + }, + "viz-seq-2": { + "ref": "palette-primary-500", + "oklch": "oklch(0.6707 0.1531 262.4)", + "srgb": "#6192f3" + }, + "viz-seq-3": { + "ref": "palette-primary-400", + "oklch": "oklch(0.7628 0.0846 262.4)", + "srgb": "#95b2e8" + }, + "viz-seq-4": { + "ref": "palette-primary-300", + "oklch": "oklch(0.8497 0.0407 262.4)", + "srgb": "#c0cee9" + }, + "shadow-key": { + "ref": "black-a32", + "oklch": "oklch(0 0 0 / 0.32)", + "srgb": "rgba(0, 0, 0, 0.32)" + }, + "shadow-ambient": { + "ref": "black-a24", + "oklch": "oklch(0 0 0 / 0.24)", + "srgb": "rgba(0, 0, 0, 0.24)" + }, + "shadow-scrim": { + "ref": "black-a60", + "oklch": "oklch(0 0 0 / 0.6)", + "srgb": "rgba(0, 0, 0, 0.6)" + } + } +} as const; + +export const gradients = { + "light": { + "surface-brand": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.8497 0.043 287.1 / 0.45) 0%, oklch(0.8497 0.043 287.1 / 0.43) 16%, oklch(0.8497 0.043 287.1 / 0.24) 36%, oklch(0.8497 0.043 287.1 / 0.11) 56%, oklch(0.8497 0.043 287.1 / 0.04) 76%, oklch(0.8497 0.043 287.1 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.8497 0.0407 262.4 / 0.45) 0%, oklch(0.8497 0.0407 262.4 / 0.43) 16%, oklch(0.8497 0.0407 262.4 / 0.24) 36%, oklch(0.8497 0.0407 262.4 / 0.11) 56%, oklch(0.8497 0.0407 262.4 / 0.04) 76%, oklch(0.8497 0.0407 262.4 / 0) 100%)", + "surface-brand-subtle": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.8497 0.0407 262.4 / 0.45) 0%, oklch(0.8497 0.0407 262.4 / 0.43) 16%, oklch(0.8497 0.0407 262.4 / 0.24) 36%, oklch(0.8497 0.0407 262.4 / 0.11) 56%, oklch(0.8497 0.0407 262.4 / 0.04) 76%, oklch(0.8497 0.0407 262.4 / 0) 100%)" + }, + "dark": { + "surface-brand": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.3346 0.1696 287.1 / 0.45) 0%, oklch(0.3346 0.1696 287.1 / 0.43) 16%, oklch(0.3346 0.1696 287.1 / 0.24) 36%, oklch(0.3346 0.1696 287.1 / 0.11) 56%, oklch(0.3346 0.1696 287.1 / 0.04) 76%, oklch(0.3346 0.1696 287.1 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.3346 0.1405 262.4 / 0.45) 0%, oklch(0.3346 0.1405 262.4 / 0.43) 16%, oklch(0.3346 0.1405 262.4 / 0.24) 36%, oklch(0.3346 0.1405 262.4 / 0.11) 56%, oklch(0.3346 0.1405 262.4 / 0.04) 76%, oklch(0.3346 0.1405 262.4 / 0) 100%)", + "surface-brand-subtle": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.3346 0.1405 262.4 / 0.45) 0%, oklch(0.3346 0.1405 262.4 / 0.43) 16%, oklch(0.3346 0.1405 262.4 / 0.24) 36%, oklch(0.3346 0.1405 262.4 / 0.11) 56%, oklch(0.3346 0.1405 262.4 / 0.04) 76%, oklch(0.3346 0.1405 262.4 / 0) 100%)" + } +} as const; + +/* Palette color → the mode its content uses. Background reads this instead of measuring. */ +export const contentModes = { + "white": "light", + "black": "dark", + "palette-neutral-light-100": "light", + "palette-neutral-light-200": "light", + "palette-neutral-light-300": "light", + "palette-neutral-light-400": "light", + "palette-neutral-light-500": "light", + "palette-neutral-light-600": "light", + "palette-neutral-light-700": "dark", + "palette-neutral-light-800": "dark", + "palette-neutral-light-900": "dark", + "palette-neutral-light-1000": "dark", + "palette-neutral-light-1100": "dark", + "palette-neutral-dark-100": "light", + "palette-neutral-dark-200": "light", + "palette-neutral-dark-300": "light", + "palette-neutral-dark-400": "light", + "palette-neutral-dark-500": "light", + "palette-neutral-dark-600": "light", + "palette-neutral-dark-700": "dark", + "palette-neutral-dark-800": "dark", + "palette-neutral-dark-900": "dark", + "palette-neutral-dark-1000": "dark", + "palette-neutral-dark-1100": "dark", + "palette-primary-100": "light", + "palette-primary-200": "light", + "palette-primary-300": "light", + "palette-primary-400": "light", + "palette-primary-500": "light", + "palette-primary-600": "dark", + "palette-primary-700": "dark", + "palette-primary-800": "dark", + "palette-primary-900": "dark", + "palette-primary-1000": "dark", + "palette-primary-1100": "dark", + "palette-accent-100": "light", + "palette-accent-200": "light", + "palette-accent-300": "light", + "palette-accent-400": "light", + "palette-accent-500": "light", + "palette-accent-600": "light", + "palette-accent-700": "dark", + "palette-accent-800": "dark", + "palette-accent-900": "dark", + "palette-accent-1000": "dark", + "palette-accent-1100": "dark", + "palette-accent-alt-100": "light", + "palette-accent-alt-200": "light", + "palette-accent-alt-300": "light", + "palette-accent-alt-400": "light", + "palette-accent-alt-500": "light", + "palette-accent-alt-600": "dark", + "palette-accent-alt-700": "dark", + "palette-accent-alt-800": "dark", + "palette-accent-alt-900": "dark", + "palette-accent-alt-1000": "dark", + "palette-accent-alt-1100": "dark", + "palette-danger-100": "light", + "palette-danger-200": "light", + "palette-danger-300": "light", + "palette-danger-400": "light", + "palette-danger-500": "light", + "palette-danger-600": "dark", + "palette-danger-700": "dark", + "palette-danger-800": "dark", + "palette-danger-900": "dark", + "palette-danger-1000": "dark", + "palette-danger-1100": "dark", + "palette-warning-100": "light", + "palette-warning-200": "light", + "palette-warning-300": "light", + "palette-warning-400": "light", + "palette-warning-500": "light", + "palette-warning-600": "dark", + "palette-warning-700": "dark", + "palette-warning-800": "dark", + "palette-warning-900": "dark", + "palette-warning-1000": "dark", + "palette-warning-1100": "dark", + "palette-success-100": "light", + "palette-success-200": "light", + "palette-success-300": "light", + "palette-success-400": "light", + "palette-success-500": "light", + "palette-success-600": "light", + "palette-success-700": "dark", + "palette-success-800": "dark", + "palette-success-900": "dark", + "palette-success-1000": "dark", + "palette-success-1100": "dark" +} as const; + +/* This brand's tokens.css, for GamutProvider to inject. */ +export const tokensCss = "/* Generated by @skillsoft/gamut-brand from the \"lxStudio\" brand. Do not edit. */\n@layer reset, base, tokens, recipes, utilities;\n\n@layer tokens {\n :root, [data-brand=\"lxStudio\"] {\n --color-white: oklch(1.0000 0.0000 0.00);\n --color-black: oklch(0.0000 0.0000 0.00);\n --color-palette-neutral-light-100: oklch(0.9876 0.0017 247.84);\n --color-palette-neutral-light-200: oklch(0.9364 0.0025 247.80);\n --color-palette-neutral-light-300: oklch(0.8497 0.0086 247.80);\n --color-palette-neutral-light-400: oklch(0.7628 0.0178 247.80);\n --color-palette-neutral-light-500: oklch(0.6707 0.0316 247.80);\n --color-palette-neutral-light-600: oklch(0.5835 0.0312 247.80);\n --color-palette-neutral-light-700: oklch(0.4972 0.0253 247.80);\n --color-palette-neutral-light-800: oklch(0.4115 0.0198 247.80);\n --color-palette-neutral-light-900: oklch(0.3346 0.0157 247.80);\n --color-palette-neutral-light-1000: oklch(0.2499 0.0112 247.80);\n --color-palette-neutral-light-1100: oklch(0.1925 0.0082 247.80);\n --color-palette-neutral-dark-100: oklch(0.9862 0.0012 271.30);\n --color-palette-neutral-dark-200: oklch(0.9364 0.0024 271.30);\n --color-palette-neutral-dark-300: oklch(0.8497 0.0081 271.30);\n --color-palette-neutral-dark-400: oklch(0.7628 0.0169 271.30);\n --color-palette-neutral-dark-500: oklch(0.6707 0.0306 271.30);\n --color-palette-neutral-dark-600: oklch(0.5835 0.0454 271.30);\n --color-palette-neutral-dark-700: oklch(0.4972 0.0538 271.30);\n --color-palette-neutral-dark-800: oklch(0.4115 0.0479 271.30);\n --color-palette-neutral-dark-900: oklch(0.3346 0.0381 271.30);\n --color-palette-neutral-dark-1000: oklch(0.2499 0.0272 271.30);\n --color-palette-neutral-dark-1100: oklch(0.1925 0.0199 271.30);\n --color-palette-neutral-dark-1100-a16: oklch(0.1925 0.0199 271.30 / 0.16);\n --color-palette-neutral-dark-1100-a12: oklch(0.1925 0.0199 271.30 / 0.12);\n --color-palette-primary-100: oklch(0.9862 0.0064 262.40);\n --color-palette-primary-200: oklch(0.9364 0.0121 262.40);\n --color-palette-primary-300: oklch(0.8497 0.0407 262.40);\n --color-palette-primary-400: oklch(0.7628 0.0846 262.40);\n --color-palette-primary-500: oklch(0.6707 0.1531 262.40);\n --color-palette-primary-600: oklch(0.5835 0.2274 262.40);\n --color-palette-primary-700: oklch(0.4972 0.2254 262.40);\n --color-palette-primary-800: oklch(0.4115 0.1767 262.40);\n --color-palette-primary-900: oklch(0.3346 0.1405 262.40);\n --color-palette-primary-1000: oklch(0.2499 0.1001 262.40);\n --color-palette-primary-1100: oklch(0.1925 0.0734 262.40);\n --color-palette-primary-600-a6: oklch(0.5835 0.2274 262.40 / 0.06);\n --color-palette-primary-600-a9: oklch(0.5835 0.2274 262.40 / 0.09);\n --color-palette-primary-600-a8: oklch(0.5835 0.2274 262.40 / 0.08);\n --color-palette-accent-100: oklch(0.9862 0.0205 93.60);\n --color-palette-accent-200: oklch(0.9364 0.0378 93.60);\n --color-palette-accent-300: oklch(0.8497 0.0957 93.60);\n --color-palette-accent-400: oklch(0.7628 0.1094 93.60);\n --color-palette-accent-500: oklch(0.6707 0.1209 93.60);\n --color-palette-accent-600: oklch(0.5835 0.1195 93.60);\n --color-palette-accent-700: oklch(0.4972 0.0967 93.60);\n --color-palette-accent-800: oklch(0.4115 0.0758 93.60);\n --color-palette-accent-900: oklch(0.3346 0.0603 93.60);\n --color-palette-accent-1000: oklch(0.2499 0.0430 93.60);\n --color-palette-accent-1100: oklch(0.1925 0.0315 93.60);\n --color-palette-accent-alt-100: oklch(0.9862 0.0067 287.10);\n --color-palette-accent-alt-200: oklch(0.9364 0.0127 287.10);\n --color-palette-accent-alt-300: oklch(0.8497 0.0430 287.10);\n --color-palette-accent-alt-400: oklch(0.7628 0.0896 287.10);\n --color-palette-accent-alt-500: oklch(0.6707 0.1624 287.10);\n --color-palette-accent-alt-600: oklch(0.5835 0.2416 287.10);\n --color-palette-accent-alt-700: oklch(0.4972 0.2722 287.10);\n --color-palette-accent-alt-800: oklch(0.4115 0.2134 287.10);\n --color-palette-accent-alt-900: oklch(0.3346 0.1696 287.10);\n --color-palette-accent-alt-1000: oklch(0.2499 0.1209 287.10);\n --color-palette-accent-alt-1100: oklch(0.1925 0.0887 287.10);\n --color-palette-danger-100: oklch(0.9862 0.0066 29.40);\n --color-palette-danger-200: oklch(0.9364 0.0128 29.40);\n --color-palette-danger-300: oklch(0.8497 0.0452 29.40);\n --color-palette-danger-400: oklch(0.7628 0.0990 29.40);\n --color-palette-danger-500: oklch(0.6707 0.1910 29.40);\n --color-palette-danger-600: oklch(0.5835 0.2382 29.40);\n --color-palette-danger-700: oklch(0.4972 0.1928 29.40);\n --color-palette-danger-800: oklch(0.4115 0.1512 29.40);\n --color-palette-danger-900: oklch(0.3346 0.1202 29.40);\n --color-palette-danger-1000: oklch(0.2499 0.0857 29.40);\n --color-palette-danger-1100: oklch(0.1925 0.0628 29.40);\n --color-palette-warning-100: oklch(0.9862 0.0082 58.30);\n --color-palette-warning-200: oklch(0.9364 0.0156 58.30);\n --color-palette-warning-300: oklch(0.8497 0.0545 58.30);\n --color-palette-warning-400: oklch(0.7628 0.1179 58.30);\n --color-palette-warning-500: oklch(0.6707 0.1408 58.30);\n --color-palette-warning-600: oklch(0.5835 0.1392 58.30);\n --color-palette-warning-700: oklch(0.4972 0.1127 58.30);\n --color-palette-warning-800: oklch(0.4115 0.0883 58.30);\n --color-palette-warning-900: oklch(0.3346 0.0702 58.30);\n --color-palette-warning-1000: oklch(0.2499 0.0500 58.30);\n --color-palette-warning-1100: oklch(0.1925 0.0367 58.30);\n --color-palette-success-100: oklch(0.9862 0.0204 156.70);\n --color-palette-success-200: oklch(0.9364 0.0405 156.70);\n --color-palette-success-300: oklch(0.8497 0.1118 156.70);\n --color-palette-success-400: oklch(0.7628 0.1278 156.70);\n --color-palette-success-500: oklch(0.6707 0.1413 156.70);\n --color-palette-success-600: oklch(0.5835 0.1396 156.70);\n --color-palette-success-700: oklch(0.4972 0.1130 156.70);\n --color-palette-success-800: oklch(0.4115 0.0886 156.70);\n --color-palette-success-900: oklch(0.3346 0.0704 156.70);\n --color-palette-success-1000: oklch(0.2499 0.0502 156.70);\n --color-palette-success-1100: oklch(0.1925 0.0368 156.70);\n --color-black-a32: oklch(0.0000 0.0000 0.00 / 0.32);\n --color-black-a24: oklch(0.0000 0.0000 0.00 / 0.24);\n --color-black-a60: oklch(0.0000 0.0000 0.00 / 0.6);\n }\n\n :root, [data-color-mode=\"light\"] {\n color-scheme: light;\n color: var(--color-text-default);\n --color-background-current: var(--color-surface-default);\n --color-surface-sunken: var(--color-palette-neutral-light-200); /** Wells, insets, code blocks — anything recessed into the page. */\n --color-surface-default: var(--color-palette-neutral-light-100); /** The page background — the base every other surface elevates from. */\n --color-surface-raised: var(--color-white); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */\n --color-surface-success: var(--color-palette-success-200); /** The success tint behind an alert or inline confirmation. */\n --color-surface-warning: var(--color-palette-warning-200); /** The warning tint behind an alert or inline caution. */\n --color-surface-error: var(--color-palette-danger-200); /** The error tint behind an alert or inline validation message. */\n --color-surface-brand: var(--color-palette-neutral-light-100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */\n --color-surface-brand-subtle: var(--color-palette-neutral-light-100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */\n --color-text-default: var(--color-palette-neutral-dark-1000); /** Body copy and headings. */\n --color-text-subtle: var(--color-palette-neutral-dark-800); /** Secondary text — descriptions, metadata, helper copy. */\n --color-text-inverse: var(--color-palette-neutral-light-200); /** Labels on a filled control — action-primary, -neutral, -danger. */\n --color-text-success: var(--color-palette-success-900); /** Success text and icons. */\n --color-text-warning: var(--color-palette-warning-900); /** Warning text and icons. Always with an icon. */\n --color-text-error: var(--color-palette-danger-900); /** Error text and icons. */\n --color-border-strong: var(--color-palette-neutral-dark-1100); /** Emphasis — an active field, a selected row. */\n --color-border-default: var(--color-palette-neutral-dark-600); /** Any border that carries meaning — control outlines, field borders. */\n --color-border-subtle: var(--color-palette-neutral-dark-300); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */\n --color-action-primary: var(--color-palette-primary-800); /** Primary interactive fills, strokes, links and focus rings. */\n --color-action-primary-hover: var(--color-palette-primary-900); /** Hover for action-primary. */\n --color-action-primary-pressed: var(--color-palette-primary-1000); /** Pressed for action-primary. */\n --color-action-danger: var(--color-palette-danger-800); /** Destructive actions — delete, remove, revoke. */\n --color-action-danger-hover: var(--color-palette-danger-900); /** Hover for action-danger. */\n --color-action-danger-pressed: var(--color-palette-danger-1000); /** Pressed for action-danger. */\n --color-action-neutral: var(--color-palette-neutral-dark-1000); /** The neutral action fill. Button variant=\"secondary\" resolves here. */\n --color-action-neutral-hover: var(--color-palette-neutral-dark-1100); /** Hover for action-neutral. */\n --color-action-neutral-pressed: var(--color-black); /** Pressed for action-neutral. */\n --color-action-disabled: var(--color-palette-neutral-light-400); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */\n --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */\n --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */\n --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent \"this one is chosen\" tint. */\n --color-viz-1: var(--color-palette-primary-800); /** Categorical series 1. */\n --color-viz-2: var(--color-palette-accent-700); /** Categorical series 2. */\n --color-viz-3: var(--color-palette-warning-700); /** Categorical series 3. */\n --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */\n --color-viz-5: var(--color-palette-success-600); /** Categorical series 5. */\n --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */\n --color-viz-7: var(--color-palette-neutral-dark-1000); /** Categorical series 7. */\n --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */\n --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */\n --color-viz-seq-2: var(--color-palette-primary-700); /** Sequential step 2, low to high. */\n --color-viz-seq-3: var(--color-palette-primary-800); /** Sequential step 3, low to high. */\n --color-viz-seq-4: var(--color-palette-primary-900); /** Sequential step 4, low to high. */\n --color-shadow-key: var(--color-palette-neutral-dark-1100-a16); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */\n --color-shadow-ambient: var(--color-palette-neutral-dark-1100-a12); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */\n --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */\n --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-300) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%);\n --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%);\n }\n\n [data-color-mode=\"dark\"] {\n color-scheme: dark;\n color: var(--color-text-default);\n --color-background-current: var(--color-surface-default);\n --color-surface-sunken: var(--color-black); /** Wells, insets, code blocks — anything recessed into the page. */\n --color-surface-default: var(--color-palette-neutral-dark-1100); /** The page background — the base every other surface elevates from. */\n --color-surface-raised: var(--color-palette-neutral-dark-1000); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */\n --color-surface-success: var(--color-palette-success-1000); /** The success tint behind an alert or inline confirmation. */\n --color-surface-warning: var(--color-palette-warning-1000); /** The warning tint behind an alert or inline caution. */\n --color-surface-error: var(--color-palette-danger-1000); /** The error tint behind an alert or inline validation message. */\n --color-surface-brand: var(--color-palette-neutral-dark-1100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */\n --color-surface-brand-subtle: var(--color-palette-neutral-dark-1100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */\n --color-text-default: var(--color-palette-neutral-light-200); /** Body copy and headings. */\n --color-text-subtle: var(--color-palette-neutral-light-400); /** Secondary text — descriptions, metadata, helper copy. */\n --color-text-inverse: var(--color-palette-neutral-dark-1000); /** Labels on a filled control — action-primary, -neutral, -danger. */\n --color-text-success: var(--color-palette-success-300); /** Success text and icons. */\n --color-text-warning: var(--color-palette-warning-300); /** Warning text and icons. Always with an icon. */\n --color-text-error: var(--color-palette-danger-300); /** Error text and icons. */\n --color-border-strong: var(--color-palette-neutral-light-100); /** Emphasis — an active field, a selected row. */\n --color-border-default: var(--color-palette-neutral-light-600); /** Any border that carries meaning — control outlines, field borders. */\n --color-border-subtle: var(--color-palette-neutral-light-900); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */\n --color-action-primary: var(--color-palette-primary-400); /** Primary interactive fills, strokes, links and focus rings. */\n --color-action-primary-hover: var(--color-palette-primary-300); /** Hover for action-primary. */\n --color-action-primary-pressed: var(--color-palette-primary-200); /** Pressed for action-primary. */\n --color-action-danger: var(--color-palette-danger-400); /** Destructive actions — delete, remove, revoke. */\n --color-action-danger-hover: var(--color-palette-danger-300); /** Hover for action-danger. */\n --color-action-danger-pressed: var(--color-palette-danger-200); /** Pressed for action-danger. */\n --color-action-neutral: var(--color-palette-neutral-light-200); /** The neutral action fill. Button variant=\"secondary\" resolves here. */\n --color-action-neutral-hover: var(--color-palette-neutral-light-100); /** Hover for action-neutral. */\n --color-action-neutral-pressed: var(--color-white); /** Pressed for action-neutral. */\n --color-action-disabled: var(--color-palette-neutral-dark-800); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */\n --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */\n --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */\n --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent \"this one is chosen\" tint. */\n --color-viz-1: var(--color-palette-primary-500); /** Categorical series 1. */\n --color-viz-2: var(--color-palette-accent-500); /** Categorical series 2. */\n --color-viz-3: var(--color-palette-warning-400); /** Categorical series 3. */\n --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */\n --color-viz-5: var(--color-palette-success-300); /** Categorical series 5. */\n --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */\n --color-viz-7: var(--color-palette-neutral-dark-500); /** Categorical series 7. */\n --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */\n --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */\n --color-viz-seq-2: var(--color-palette-primary-500); /** Sequential step 2, low to high. */\n --color-viz-seq-3: var(--color-palette-primary-400); /** Sequential step 3, low to high. */\n --color-viz-seq-4: var(--color-palette-primary-300); /** Sequential step 4, low to high. */\n --color-shadow-key: var(--color-black-a32); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */\n --color-shadow-ambient: var(--color-black-a24); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */\n --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */\n --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-900) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%);\n --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%);\n }\n}\n"; diff --git a/packages/gamut-styles/src/tokens/generated/originalCodecademy/dark.tokens.json b/packages/gamut-styles/src/tokens/generated/originalCodecademy/dark.tokens.json new file mode 100644 index 0000000000..63d48ae42a --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/originalCodecademy/dark.tokens.json @@ -0,0 +1,315 @@ +{ + "$description": "dark mode roles for \"originalCodecademy\". Do not edit.", + "surface-sunken": { + "$type": "color", + "$value": "{black}", + "$description": "Wells, insets, code blocks — anything recessed into the page." + }, + "surface-default": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "The page background — the base every other surface elevates from." + }, + "surface-raised": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits." + }, + "surface-success": { + "$type": "color", + "$value": "{palette.success.1000}", + "$description": "The success tint behind an alert or inline confirmation." + }, + "surface-warning": { + "$type": "color", + "$value": "{palette.warning.1000}", + "$description": "The warning tint behind an alert or inline caution." + }, + "surface-error": { + "$type": "color", + "$value": "{palette.danger.1000}", + "$description": "The error tint behind an alert or inline validation message." + }, + "surface-brand": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.accent-alt.900}", + "radius": 0.85, + "at": "100% -5%" + }, + { + "color": "{palette.primary.900}", + "radius": 1.21, + "at": "5% 100%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.3346 0.1696 287.1 / 0.45) 0%, oklch(0.3346 0.1696 287.1 / 0.43) 16%, oklch(0.3346 0.1696 287.1 / 0.24) 36%, oklch(0.3346 0.1696 287.1 / 0.11) 56%, oklch(0.3346 0.1696 287.1 / 0.04) 76%, oklch(0.3346 0.1696 287.1 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.3346 0.1863 274.1 / 0.45) 0%, oklch(0.3346 0.1863 274.1 / 0.43) 16%, oklch(0.3346 0.1863 274.1 / 0.24) 36%, oklch(0.3346 0.1863 274.1 / 0.11) 56%, oklch(0.3346 0.1863 274.1 / 0.04) 76%, oklch(0.3346 0.1863 274.1 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-900) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%)" + } + } + } + }, + "surface-brand-subtle": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.primary.900}", + "radius": 0.78, + "at": "50% -10%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.3346 0.1863 274.1 / 0.45) 0%, oklch(0.3346 0.1863 274.1 / 0.43) 16%, oklch(0.3346 0.1863 274.1 / 0.24) 36%, oklch(0.3346 0.1863 274.1 / 0.11) 56%, oklch(0.3346 0.1863 274.1 / 0.04) 76%, oklch(0.3346 0.1863 274.1 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%)" + } + } + } + }, + "text-default": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "Body copy and headings." + }, + "text-subtle": { + "$type": "color", + "$value": "{palette.neutral-light.400}", + "$description": "Secondary text — descriptions, metadata, helper copy." + }, + "text-inverse": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "Labels on a filled control — action-primary, -neutral, -danger." + }, + "text-success": { + "$type": "color", + "$value": "{palette.success.300}", + "$description": "Success text and icons." + }, + "text-warning": { + "$type": "color", + "$value": "{palette.warning.300}", + "$description": "Warning text and icons. Always with an icon." + }, + "text-error": { + "$type": "color", + "$value": "{palette.danger.300}", + "$description": "Error text and icons." + }, + "border-strong": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "Emphasis — an active field, a selected row." + }, + "border-default": { + "$type": "color", + "$value": "{palette.neutral-light.600}", + "$description": "Any border that carries meaning — control outlines, field borders." + }, + "border-subtle": { + "$type": "color", + "$value": "{palette.neutral-light.900}", + "$description": "Decorative hairlines — table rules, dividers. Contrast-exempt." + }, + "action-primary": { + "$type": "color", + "$value": "{palette.accent.300}", + "$description": "Primary interactive fills, strokes, links and focus rings." + }, + "action-primary-hover": { + "$type": "color", + "$value": "{palette.accent.400}", + "$description": "Hover for action-primary." + }, + "action-primary-pressed": { + "$type": "color", + "$value": "{palette.accent.500}", + "$description": "Pressed for action-primary." + }, + "action-danger": { + "$type": "color", + "$value": "{palette.danger.400}", + "$description": "Destructive actions — delete, remove, revoke." + }, + "action-danger-hover": { + "$type": "color", + "$value": "{palette.danger.300}", + "$description": "Hover for action-danger." + }, + "action-danger-pressed": { + "$type": "color", + "$value": "{palette.danger.200}", + "$description": "Pressed for action-danger." + }, + "action-neutral": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "The neutral action fill. Button variant=\"secondary\" resolves here." + }, + "action-neutral-hover": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "Hover for action-neutral." + }, + "action-neutral-pressed": { + "$type": "color", + "$value": "{white}", + "$description": "Pressed for action-neutral." + }, + "action-disabled": { + "$type": "color", + "$value": "{palette.neutral-dark.800}", + "$description": "The disabled fill for every action variant. Contrast-exempt (inactive control)." + }, + "state-hover": { + "$type": "color", + "$value": "{palette.primary.600-a6}", + "$description": "Rest → hover on rows, tabs, chips, menu items." + }, + "state-pressed": { + "$type": "color", + "$value": "{palette.primary.600-a9}", + "$description": "Hover → pressed on the same elements." + }, + "state-selected": { + "$type": "color", + "$value": "{palette.primary.600-a8}", + "$description": "The persistent \"this one is chosen\" tint." + }, + "viz-1": { + "$type": "color", + "$value": "{palette.primary.500}", + "$description": "Categorical series 1." + }, + "viz-2": { + "$type": "color", + "$value": "{palette.accent.500}", + "$description": "Categorical series 2." + }, + "viz-3": { + "$type": "color", + "$value": "{palette.warning.400}", + "$description": "Categorical series 3." + }, + "viz-4": { + "$type": "color", + "$value": "{palette.accent-alt.600}", + "$description": "Categorical series 4." + }, + "viz-5": { + "$type": "color", + "$value": "{palette.success.300}", + "$description": "Categorical series 5." + }, + "viz-6": { + "$type": "color", + "$value": "{palette.danger.600}", + "$description": "Categorical series 6." + }, + "viz-7": { + "$type": "color", + "$value": "{palette.neutral-dark.500}", + "$description": "Categorical series 7." + }, + "viz-8": { + "$type": "color", + "$value": "{palette.neutral-light.600}", + "$description": "Categorical series 8." + }, + "viz-seq-1": { + "$type": "color", + "$value": "{palette.primary.600}", + "$description": "Sequential step 1, low to high." + }, + "viz-seq-2": { + "$type": "color", + "$value": "{palette.primary.500}", + "$description": "Sequential step 2, low to high." + }, + "viz-seq-3": { + "$type": "color", + "$value": "{palette.primary.400}", + "$description": "Sequential step 3, low to high." + }, + "viz-seq-4": { + "$type": "color", + "$value": "{palette.primary.300}", + "$description": "Sequential step 4, low to high." + }, + "shadow-key": { + "$type": "color", + "$value": "{black-a32}", + "$description": "The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur." + }, + "shadow-ambient": { + "$type": "color", + "$value": "{black-a0}", + "$description": "The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff." + }, + "shadow-scrim": { + "$type": "color", + "$value": "{black-a60}", + "$description": "The scrim behind modals and dialogs. Mode-independent." + } +} diff --git a/packages/gamut-styles/src/tokens/generated/originalCodecademy/light.tokens.json b/packages/gamut-styles/src/tokens/generated/originalCodecademy/light.tokens.json new file mode 100644 index 0000000000..004073019f --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/originalCodecademy/light.tokens.json @@ -0,0 +1,315 @@ +{ + "$description": "light mode roles for \"originalCodecademy\". Do not edit.", + "surface-sunken": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "Wells, insets, code blocks — anything recessed into the page." + }, + "surface-default": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "The page background — the base every other surface elevates from." + }, + "surface-raised": { + "$type": "color", + "$value": "{white}", + "$description": "The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits." + }, + "surface-success": { + "$type": "color", + "$value": "{palette.success.200}", + "$description": "The success tint behind an alert or inline confirmation." + }, + "surface-warning": { + "$type": "color", + "$value": "{palette.warning.200}", + "$description": "The warning tint behind an alert or inline caution." + }, + "surface-error": { + "$type": "color", + "$value": "{palette.danger.200}", + "$description": "The error tint behind an alert or inline validation message." + }, + "surface-brand": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.accent-alt.300}", + "radius": 0.85, + "at": "100% -5%" + }, + { + "color": "{palette.primary.300}", + "radius": 1.21, + "at": "5% 100%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.8497 0.043 287.1 / 0.45) 0%, oklch(0.8497 0.043 287.1 / 0.43) 16%, oklch(0.8497 0.043 287.1 / 0.24) 36%, oklch(0.8497 0.043 287.1 / 0.11) 56%, oklch(0.8497 0.043 287.1 / 0.04) 76%, oklch(0.8497 0.043 287.1 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.8497 0.0408 274.1 / 0.45) 0%, oklch(0.8497 0.0408 274.1 / 0.43) 16%, oklch(0.8497 0.0408 274.1 / 0.24) 36%, oklch(0.8497 0.0408 274.1 / 0.11) 56%, oklch(0.8497 0.0408 274.1 / 0.04) 76%, oklch(0.8497 0.0408 274.1 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-300) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%)" + } + } + } + }, + "surface-brand-subtle": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.primary.300}", + "radius": 0.78, + "at": "50% -10%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.8497 0.0408 274.1 / 0.45) 0%, oklch(0.8497 0.0408 274.1 / 0.43) 16%, oklch(0.8497 0.0408 274.1 / 0.24) 36%, oklch(0.8497 0.0408 274.1 / 0.11) 56%, oklch(0.8497 0.0408 274.1 / 0.04) 76%, oklch(0.8497 0.0408 274.1 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%)" + } + } + } + }, + "text-default": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "Body copy and headings." + }, + "text-subtle": { + "$type": "color", + "$value": "{palette.neutral-dark.800}", + "$description": "Secondary text — descriptions, metadata, helper copy." + }, + "text-inverse": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "Labels on a filled control — action-primary, -neutral, -danger." + }, + "text-success": { + "$type": "color", + "$value": "{palette.success.900}", + "$description": "Success text and icons." + }, + "text-warning": { + "$type": "color", + "$value": "{palette.warning.900}", + "$description": "Warning text and icons. Always with an icon." + }, + "text-error": { + "$type": "color", + "$value": "{palette.danger.900}", + "$description": "Error text and icons." + }, + "border-strong": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "Emphasis — an active field, a selected row." + }, + "border-default": { + "$type": "color", + "$value": "{palette.neutral-dark.600}", + "$description": "Any border that carries meaning — control outlines, field borders." + }, + "border-subtle": { + "$type": "color", + "$value": "{palette.neutral-dark.300}", + "$description": "Decorative hairlines — table rules, dividers. Contrast-exempt." + }, + "action-primary": { + "$type": "color", + "$value": "{palette.primary.800}", + "$description": "Primary interactive fills, strokes, links and focus rings." + }, + "action-primary-hover": { + "$type": "color", + "$value": "{palette.primary.900}", + "$description": "Hover for action-primary." + }, + "action-primary-pressed": { + "$type": "color", + "$value": "{palette.primary.1000}", + "$description": "Pressed for action-primary." + }, + "action-danger": { + "$type": "color", + "$value": "{palette.danger.800}", + "$description": "Destructive actions — delete, remove, revoke." + }, + "action-danger-hover": { + "$type": "color", + "$value": "{palette.danger.900}", + "$description": "Hover for action-danger." + }, + "action-danger-pressed": { + "$type": "color", + "$value": "{palette.danger.1000}", + "$description": "Pressed for action-danger." + }, + "action-neutral": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "The neutral action fill. Button variant=\"secondary\" resolves here." + }, + "action-neutral-hover": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "Hover for action-neutral." + }, + "action-neutral-pressed": { + "$type": "color", + "$value": "{black}", + "$description": "Pressed for action-neutral." + }, + "action-disabled": { + "$type": "color", + "$value": "{palette.neutral-light.400}", + "$description": "The disabled fill for every action variant. Contrast-exempt (inactive control)." + }, + "state-hover": { + "$type": "color", + "$value": "{palette.primary.600-a6}", + "$description": "Rest → hover on rows, tabs, chips, menu items." + }, + "state-pressed": { + "$type": "color", + "$value": "{palette.primary.600-a9}", + "$description": "Hover → pressed on the same elements." + }, + "state-selected": { + "$type": "color", + "$value": "{palette.primary.600-a8}", + "$description": "The persistent \"this one is chosen\" tint." + }, + "viz-1": { + "$type": "color", + "$value": "{palette.primary.800}", + "$description": "Categorical series 1." + }, + "viz-2": { + "$type": "color", + "$value": "{palette.accent.700}", + "$description": "Categorical series 2." + }, + "viz-3": { + "$type": "color", + "$value": "{palette.warning.700}", + "$description": "Categorical series 3." + }, + "viz-4": { + "$type": "color", + "$value": "{palette.accent-alt.600}", + "$description": "Categorical series 4." + }, + "viz-5": { + "$type": "color", + "$value": "{palette.success.600}", + "$description": "Categorical series 5." + }, + "viz-6": { + "$type": "color", + "$value": "{palette.danger.600}", + "$description": "Categorical series 6." + }, + "viz-7": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "Categorical series 7." + }, + "viz-8": { + "$type": "color", + "$value": "{palette.neutral-light.600}", + "$description": "Categorical series 8." + }, + "viz-seq-1": { + "$type": "color", + "$value": "{palette.primary.600}", + "$description": "Sequential step 1, low to high." + }, + "viz-seq-2": { + "$type": "color", + "$value": "{palette.primary.700}", + "$description": "Sequential step 2, low to high." + }, + "viz-seq-3": { + "$type": "color", + "$value": "{palette.primary.800}", + "$description": "Sequential step 3, low to high." + }, + "viz-seq-4": { + "$type": "color", + "$value": "{palette.primary.900}", + "$description": "Sequential step 4, low to high." + }, + "shadow-key": { + "$type": "color", + "$value": "{palette.neutral-dark.1100-a16}", + "$description": "The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur." + }, + "shadow-ambient": { + "$type": "color", + "$value": "{palette.neutral-dark.1100-a0}", + "$description": "The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff." + }, + "shadow-scrim": { + "$type": "color", + "$value": "{black-a60}", + "$description": "The scrim behind modals and dialogs. Mode-independent." + } +} diff --git a/packages/gamut-styles/src/tokens/generated/originalCodecademy/palette.tokens.json b/packages/gamut-styles/src/tokens/generated/originalCodecademy/palette.tokens.json new file mode 100644 index 0000000000..92f816ffbf --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/originalCodecademy/palette.tokens.json @@ -0,0 +1,1771 @@ +{ + "$description": "Generated from the \"originalCodecademy\" brand (tier 3). Do not edit.", + "white": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 1, + 0, + 0 + ], + "alpha": 1, + "hex": "#ffffff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "black": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 1, + "hex": "#000000" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "palette": { + "neutral-light": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0016, + 58.8 + ], + "alpha": 1, + "hex": "#fbfaf9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0031, + 58.8 + ], + "alpha": 1, + "hex": "#ece9e8" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0109, + 58.8 + ], + "alpha": 1, + "hex": "#d3ccc7" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0237, + 58.8 + ], + "alpha": 1, + "hex": "#beafa3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.028, + 58.8 + ], + "alpha": 1, + "hex": "#a39285" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.0277, + 58.8 + ], + "alpha": 1, + "hex": "#88786b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.0224, + 58.8 + ], + "alpha": 1, + "hex": "#6d6056" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0175, + 58.8 + ], + "alpha": 1, + "hex": "#524942" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0139, + 58.8 + ], + "alpha": 1, + "hex": "#3c3530" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0099, + 58.8 + ], + "alpha": 1, + "hex": "#25211d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0073, + 58.8 + ], + "alpha": 1, + "hex": "#171411" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "neutral-dark": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0012, + 271.3 + ], + "alpha": 1, + "hex": "#fafafb" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0024, + 271.3 + ], + "alpha": 1, + "hex": "#e9eaeb" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0081, + 271.3 + ], + "alpha": 1, + "hex": "#cbcdd3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0169, + 271.3 + ], + "alpha": 1, + "hex": "#aeb2bd" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.0306, + 271.3 + ], + "alpha": 1, + "hex": "#8e95a9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.0454, + 271.3 + ], + "alpha": 1, + "hex": "#717a97" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.0538, + 271.3 + ], + "alpha": 1, + "hex": "#576182" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0479, + 271.3 + ], + "alpha": 1, + "hex": "#414965" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0381, + 271.3 + ], + "alpha": 1, + "hex": "#2f364b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0272, + 271.3 + ], + "alpha": 1, + "hex": "#1d212f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0199, + 271.3 + ], + "alpha": 1, + "hex": "#11141d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100-a16": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0199, + 271.3 + ], + "alpha": 0.16, + "hex": "#11141d" + } + }, + "1100-a12": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0199, + 271.3 + ], + "alpha": 0.12, + "hex": "#11141d" + } + }, + "1100-a0": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0199, + 271.3 + ], + "alpha": 0, + "hex": "#11141d" + } + } + }, + "primary": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0064, + 274.1 + ], + "alpha": 1, + "hex": "#f9faff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0121, + 274.1 + ], + "alpha": 1, + "hex": "#e7eaf2" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0408, + 274.1 + ], + "alpha": 1, + "hex": "#c5cce9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0849, + 274.1 + ], + "alpha": 1, + "hex": "#a1aee9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1537, + 274.1 + ], + "alpha": 1, + "hex": "#798bf3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2285, + 274.1 + ], + "alpha": 1, + "hex": "#5962ff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.2707, + 274.1 + ], + "alpha": 1, + "hex": "#4433f7" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.2344, + 274.1 + ], + "alpha": 1, + "hex": "#331fc6" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.1863, + 274.1 + ], + "alpha": 1, + "hex": "#241793" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.1328, + 274.1 + ], + "alpha": 1, + "hex": "#140f5f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0974, + 274.1 + ], + "alpha": 1, + "hex": "#0b093f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "600-a6": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2285, + 274.1 + ], + "alpha": 0.06, + "hex": "#5962ff" + } + }, + "600-a9": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2285, + 274.1 + ], + "alpha": 0.09, + "hex": "#5962ff" + } + }, + "600-a8": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2285, + 274.1 + ], + "alpha": 0.08, + "hex": "#5962ff" + } + } + }, + "accent": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0205, + 93.6 + ], + "alpha": 1, + "hex": "#fffbeb" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0378, + 93.6 + ], + "alpha": 1, + "hex": "#f2eace" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8789, + 0.1801, + 93.58 + ], + "alpha": 1, + "hex": "#ffd300" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7442, + 0.1526, + 93.94 + ], + "alpha": 1, + "hex": "#cca900" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1209, + 93.6 + ], + "alpha": 1, + "hex": "#ae932e" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.1195, + 93.6 + ], + "alpha": 1, + "hex": "#937900" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.0967, + 93.6 + ], + "alpha": 1, + "hex": "#75610e" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0758, + 93.6 + ], + "alpha": 1, + "hex": "#594a10" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0603, + 93.6 + ], + "alpha": 1, + "hex": "#41360b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.043, + 93.6 + ], + "alpha": 1, + "hex": "#282106" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0315, + 93.6 + ], + "alpha": 1, + "hex": "#191404" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "accent-alt": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0067, + 287.1 + ], + "alpha": 1, + "hex": "#fafaff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0127, + 287.1 + ], + "alpha": 1, + "hex": "#e9e9f2" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.043, + 287.1 + ], + "alpha": 1, + "hex": "#cbcae9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0896, + 287.1 + ], + "alpha": 1, + "hex": "#aeaae9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1624, + 287.1 + ], + "alpha": 1, + "hex": "#9182f3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2416, + 287.1 + ], + "alpha": 1, + "hex": "#7a52ff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.2722, + 287.1 + ], + "alpha": 1, + "hex": "#661af0" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.2134, + 287.1 + ], + "alpha": 1, + "hex": "#4d1cb5" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.1696, + 287.1 + ], + "alpha": 1, + "hex": "#381587" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.1209, + 287.1 + ], + "alpha": 1, + "hex": "#220d57" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0887, + 287.1 + ], + "alpha": 1, + "hex": "#150739" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "danger": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0066, + 29.4 + ], + "alpha": 1, + "hex": "#fff9f8" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0128, + 29.4 + ], + "alpha": 1, + "hex": "#f2e7e5" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0452, + 29.4 + ], + "alpha": 1, + "hex": "#eac3bd" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.099, + 29.4 + ], + "alpha": 1, + "hex": "#ea9a8d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.191, + 29.4 + ], + "alpha": 1, + "hex": "#f45b4a" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2382, + 29.4 + ], + "alpha": 1, + "hex": "#e70400" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.1928, + 29.4 + ], + "alpha": 1, + "hex": "#b7160d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.1512, + 29.4 + ], + "alpha": 1, + "hex": "#8a170f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.1202, + 29.4 + ], + "alpha": 1, + "hex": "#67110a" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0857, + 29.4 + ], + "alpha": 1, + "hex": "#420a06" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0628, + 29.4 + ], + "alpha": 1, + "hex": "#2b0603" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "warning": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0082, + 58.3 + ], + "alpha": 1, + "hex": "#fff9f5" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0156, + 58.3 + ], + "alpha": 1, + "hex": "#f2e8e0" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0545, + 58.3 + ], + "alpha": 1, + "hex": "#eac6ab" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.1179, + 58.3 + ], + "alpha": 1, + "hex": "#ea9f63" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1408, + 58.3 + ], + "alpha": 1, + "hex": "#d47d2d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.1392, + 58.3 + ], + "alpha": 1, + "hex": "#b66200" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.1127, + 58.3 + ], + "alpha": 1, + "hex": "#914f0e" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0883, + 58.3 + ], + "alpha": 1, + "hex": "#6d3d10" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0702, + 58.3 + ], + "alpha": 1, + "hex": "#512c0b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.05, + 58.3 + ], + "alpha": 1, + "hex": "#331b06" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0367, + 58.3 + ], + "alpha": 1, + "hex": "#201004" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "success": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0228, + 145.5 + ], + "alpha": 1, + "hex": "#f1fff1" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0458, + 145.5 + ], + "alpha": 1, + "hex": "#d8f3d8" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.1448, + 145.5 + ], + "alpha": 1, + "hex": "#8de792" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.1654, + 145.5 + ], + "alpha": 1, + "hex": "#64cd6d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1829, + 145.5 + ], + "alpha": 1, + "hex": "#34b147" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.1808, + 145.5 + ], + "alpha": 1, + "hex": "#00952b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.1464, + 145.5 + ], + "alpha": 1, + "hex": "#117726" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.1147, + 145.5 + ], + "alpha": 1, + "hex": "#135a1f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0912, + 145.5 + ], + "alpha": 1, + "hex": "#0e4216" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.065, + 145.5 + ], + "alpha": 1, + "hex": "#08290c" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0477, + 145.5 + ], + "alpha": 1, + "hex": "#041a06" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + } + }, + "black-a32": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 0.32, + "hex": "#000000" + } + }, + "black-a24": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 0.24, + "hex": "#000000" + } + }, + "black-a60": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 0.6, + "hex": "#000000" + } + }, + "black-a0": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 0, + "hex": "#000000" + } + } +} diff --git a/packages/gamut-styles/src/tokens/generated/originalCodecademy/tokens.css b/packages/gamut-styles/src/tokens/generated/originalCodecademy/tokens.css new file mode 100644 index 0000000000..4785e3e52b --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/originalCodecademy/tokens.css @@ -0,0 +1,213 @@ +/* Generated by @skillsoft/gamut-brand from the "originalCodecademy" brand. Do not edit. */ +@layer reset, base, tokens, recipes, utilities; + +@layer tokens { + :root, [data-brand="originalCodecademy"] { + --color-white: oklch(1.0000 0.0000 0.00); + --color-black: oklch(0.0000 0.0000 0.00); + --color-palette-neutral-light-100: oklch(0.9862 0.0016 58.80); + --color-palette-neutral-light-200: oklch(0.9364 0.0031 58.80); + --color-palette-neutral-light-300: oklch(0.8497 0.0109 58.80); + --color-palette-neutral-light-400: oklch(0.7628 0.0237 58.80); + --color-palette-neutral-light-500: oklch(0.6707 0.0280 58.80); + --color-palette-neutral-light-600: oklch(0.5835 0.0277 58.80); + --color-palette-neutral-light-700: oklch(0.4972 0.0224 58.80); + --color-palette-neutral-light-800: oklch(0.4115 0.0175 58.80); + --color-palette-neutral-light-900: oklch(0.3346 0.0139 58.80); + --color-palette-neutral-light-1000: oklch(0.2499 0.0099 58.80); + --color-palette-neutral-light-1100: oklch(0.1925 0.0073 58.80); + --color-palette-neutral-dark-100: oklch(0.9862 0.0012 271.30); + --color-palette-neutral-dark-200: oklch(0.9364 0.0024 271.30); + --color-palette-neutral-dark-300: oklch(0.8497 0.0081 271.30); + --color-palette-neutral-dark-400: oklch(0.7628 0.0169 271.30); + --color-palette-neutral-dark-500: oklch(0.6707 0.0306 271.30); + --color-palette-neutral-dark-600: oklch(0.5835 0.0454 271.30); + --color-palette-neutral-dark-700: oklch(0.4972 0.0538 271.30); + --color-palette-neutral-dark-800: oklch(0.4115 0.0479 271.30); + --color-palette-neutral-dark-900: oklch(0.3346 0.0381 271.30); + --color-palette-neutral-dark-1000: oklch(0.2499 0.0272 271.30); + --color-palette-neutral-dark-1100: oklch(0.1925 0.0199 271.30); + --color-palette-neutral-dark-1100-a16: oklch(0.1925 0.0199 271.30 / 0.16); + --color-palette-neutral-dark-1100-a12: oklch(0.1925 0.0199 271.30 / 0.12); + --color-palette-neutral-dark-1100-a0: oklch(0.1925 0.0199 271.30 / 0); + --color-palette-primary-100: oklch(0.9862 0.0064 274.10); + --color-palette-primary-200: oklch(0.9364 0.0121 274.10); + --color-palette-primary-300: oklch(0.8497 0.0408 274.10); + --color-palette-primary-400: oklch(0.7628 0.0849 274.10); + --color-palette-primary-500: oklch(0.6707 0.1537 274.10); + --color-palette-primary-600: oklch(0.5835 0.2285 274.10); + --color-palette-primary-700: oklch(0.4972 0.2707 274.10); + --color-palette-primary-800: oklch(0.4115 0.2344 274.10); + --color-palette-primary-900: oklch(0.3346 0.1863 274.10); + --color-palette-primary-1000: oklch(0.2499 0.1328 274.10); + --color-palette-primary-1100: oklch(0.1925 0.0974 274.10); + --color-palette-primary-600-a6: oklch(0.5835 0.2285 274.10 / 0.06); + --color-palette-primary-600-a9: oklch(0.5835 0.2285 274.10 / 0.09); + --color-palette-primary-600-a8: oklch(0.5835 0.2285 274.10 / 0.08); + --color-palette-accent-100: oklch(0.9862 0.0205 93.60); + --color-palette-accent-200: oklch(0.9364 0.0378 93.60); + --color-palette-accent-300: oklch(0.8789 0.1801 93.58); + --color-palette-accent-400: oklch(0.7442 0.1526 93.94); + --color-palette-accent-500: oklch(0.6707 0.1209 93.60); + --color-palette-accent-600: oklch(0.5835 0.1195 93.60); + --color-palette-accent-700: oklch(0.4972 0.0967 93.60); + --color-palette-accent-800: oklch(0.4115 0.0758 93.60); + --color-palette-accent-900: oklch(0.3346 0.0603 93.60); + --color-palette-accent-1000: oklch(0.2499 0.0430 93.60); + --color-palette-accent-1100: oklch(0.1925 0.0315 93.60); + --color-palette-accent-alt-100: oklch(0.9862 0.0067 287.10); + --color-palette-accent-alt-200: oklch(0.9364 0.0127 287.10); + --color-palette-accent-alt-300: oklch(0.8497 0.0430 287.10); + --color-palette-accent-alt-400: oklch(0.7628 0.0896 287.10); + --color-palette-accent-alt-500: oklch(0.6707 0.1624 287.10); + --color-palette-accent-alt-600: oklch(0.5835 0.2416 287.10); + --color-palette-accent-alt-700: oklch(0.4972 0.2722 287.10); + --color-palette-accent-alt-800: oklch(0.4115 0.2134 287.10); + --color-palette-accent-alt-900: oklch(0.3346 0.1696 287.10); + --color-palette-accent-alt-1000: oklch(0.2499 0.1209 287.10); + --color-palette-accent-alt-1100: oklch(0.1925 0.0887 287.10); + --color-palette-danger-100: oklch(0.9862 0.0066 29.40); + --color-palette-danger-200: oklch(0.9364 0.0128 29.40); + --color-palette-danger-300: oklch(0.8497 0.0452 29.40); + --color-palette-danger-400: oklch(0.7628 0.0990 29.40); + --color-palette-danger-500: oklch(0.6707 0.1910 29.40); + --color-palette-danger-600: oklch(0.5835 0.2382 29.40); + --color-palette-danger-700: oklch(0.4972 0.1928 29.40); + --color-palette-danger-800: oklch(0.4115 0.1512 29.40); + --color-palette-danger-900: oklch(0.3346 0.1202 29.40); + --color-palette-danger-1000: oklch(0.2499 0.0857 29.40); + --color-palette-danger-1100: oklch(0.1925 0.0628 29.40); + --color-palette-warning-100: oklch(0.9862 0.0082 58.30); + --color-palette-warning-200: oklch(0.9364 0.0156 58.30); + --color-palette-warning-300: oklch(0.8497 0.0545 58.30); + --color-palette-warning-400: oklch(0.7628 0.1179 58.30); + --color-palette-warning-500: oklch(0.6707 0.1408 58.30); + --color-palette-warning-600: oklch(0.5835 0.1392 58.30); + --color-palette-warning-700: oklch(0.4972 0.1127 58.30); + --color-palette-warning-800: oklch(0.4115 0.0883 58.30); + --color-palette-warning-900: oklch(0.3346 0.0702 58.30); + --color-palette-warning-1000: oklch(0.2499 0.0500 58.30); + --color-palette-warning-1100: oklch(0.1925 0.0367 58.30); + --color-palette-success-100: oklch(0.9862 0.0228 145.50); + --color-palette-success-200: oklch(0.9364 0.0458 145.50); + --color-palette-success-300: oklch(0.8497 0.1448 145.50); + --color-palette-success-400: oklch(0.7628 0.1654 145.50); + --color-palette-success-500: oklch(0.6707 0.1829 145.50); + --color-palette-success-600: oklch(0.5835 0.1808 145.50); + --color-palette-success-700: oklch(0.4972 0.1464 145.50); + --color-palette-success-800: oklch(0.4115 0.1147 145.50); + --color-palette-success-900: oklch(0.3346 0.0912 145.50); + --color-palette-success-1000: oklch(0.2499 0.0650 145.50); + --color-palette-success-1100: oklch(0.1925 0.0477 145.50); + --color-black-a32: oklch(0.0000 0.0000 0.00 / 0.32); + --color-black-a24: oklch(0.0000 0.0000 0.00 / 0.24); + --color-black-a60: oklch(0.0000 0.0000 0.00 / 0.6); + --color-black-a0: oklch(0.0000 0.0000 0.00 / 0); + } + + :root, [data-color-mode="light"] { + color-scheme: light; + color: var(--color-text-default); + --color-background-current: var(--color-surface-default); + --color-surface-sunken: var(--color-palette-neutral-light-200); /** Wells, insets, code blocks — anything recessed into the page. */ + --color-surface-default: var(--color-palette-neutral-light-100); /** The page background — the base every other surface elevates from. */ + --color-surface-raised: var(--color-white); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */ + --color-surface-success: var(--color-palette-success-200); /** The success tint behind an alert or inline confirmation. */ + --color-surface-warning: var(--color-palette-warning-200); /** The warning tint behind an alert or inline caution. */ + --color-surface-error: var(--color-palette-danger-200); /** The error tint behind an alert or inline validation message. */ + --color-surface-brand: var(--color-palette-neutral-light-100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */ + --color-surface-brand-subtle: var(--color-palette-neutral-light-100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */ + --color-text-default: var(--color-palette-neutral-dark-1000); /** Body copy and headings. */ + --color-text-subtle: var(--color-palette-neutral-dark-800); /** Secondary text — descriptions, metadata, helper copy. */ + --color-text-inverse: var(--color-palette-neutral-light-200); /** Labels on a filled control — action-primary, -neutral, -danger. */ + --color-text-success: var(--color-palette-success-900); /** Success text and icons. */ + --color-text-warning: var(--color-palette-warning-900); /** Warning text and icons. Always with an icon. */ + --color-text-error: var(--color-palette-danger-900); /** Error text and icons. */ + --color-border-strong: var(--color-palette-neutral-dark-1100); /** Emphasis — an active field, a selected row. */ + --color-border-default: var(--color-palette-neutral-dark-600); /** Any border that carries meaning — control outlines, field borders. */ + --color-border-subtle: var(--color-palette-neutral-dark-300); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */ + --color-action-primary: var(--color-palette-primary-800); /** Primary interactive fills, strokes, links and focus rings. */ + --color-action-primary-hover: var(--color-palette-primary-900); /** Hover for action-primary. */ + --color-action-primary-pressed: var(--color-palette-primary-1000); /** Pressed for action-primary. */ + --color-action-danger: var(--color-palette-danger-800); /** Destructive actions — delete, remove, revoke. */ + --color-action-danger-hover: var(--color-palette-danger-900); /** Hover for action-danger. */ + --color-action-danger-pressed: var(--color-palette-danger-1000); /** Pressed for action-danger. */ + --color-action-neutral: var(--color-palette-neutral-dark-1000); /** The neutral action fill. Button variant="secondary" resolves here. */ + --color-action-neutral-hover: var(--color-palette-neutral-dark-1100); /** Hover for action-neutral. */ + --color-action-neutral-pressed: var(--color-black); /** Pressed for action-neutral. */ + --color-action-disabled: var(--color-palette-neutral-light-400); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */ + --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */ + --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */ + --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent "this one is chosen" tint. */ + --color-viz-1: var(--color-palette-primary-800); /** Categorical series 1. */ + --color-viz-2: var(--color-palette-accent-700); /** Categorical series 2. */ + --color-viz-3: var(--color-palette-warning-700); /** Categorical series 3. */ + --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */ + --color-viz-5: var(--color-palette-success-600); /** Categorical series 5. */ + --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */ + --color-viz-7: var(--color-palette-neutral-dark-1000); /** Categorical series 7. */ + --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */ + --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */ + --color-viz-seq-2: var(--color-palette-primary-700); /** Sequential step 2, low to high. */ + --color-viz-seq-3: var(--color-palette-primary-800); /** Sequential step 3, low to high. */ + --color-viz-seq-4: var(--color-palette-primary-900); /** Sequential step 4, low to high. */ + --color-shadow-key: var(--color-palette-neutral-dark-1100-a16); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */ + --color-shadow-ambient: var(--color-palette-neutral-dark-1100-a0); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */ + --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */ + --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-300) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%); + --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%); + } + + [data-color-mode="dark"] { + color-scheme: dark; + color: var(--color-text-default); + --color-background-current: var(--color-surface-default); + --color-surface-sunken: var(--color-black); /** Wells, insets, code blocks — anything recessed into the page. */ + --color-surface-default: var(--color-palette-neutral-dark-1100); /** The page background — the base every other surface elevates from. */ + --color-surface-raised: var(--color-palette-neutral-dark-1000); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */ + --color-surface-success: var(--color-palette-success-1000); /** The success tint behind an alert or inline confirmation. */ + --color-surface-warning: var(--color-palette-warning-1000); /** The warning tint behind an alert or inline caution. */ + --color-surface-error: var(--color-palette-danger-1000); /** The error tint behind an alert or inline validation message. */ + --color-surface-brand: var(--color-palette-neutral-dark-1100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */ + --color-surface-brand-subtle: var(--color-palette-neutral-dark-1100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */ + --color-text-default: var(--color-palette-neutral-light-200); /** Body copy and headings. */ + --color-text-subtle: var(--color-palette-neutral-light-400); /** Secondary text — descriptions, metadata, helper copy. */ + --color-text-inverse: var(--color-palette-neutral-dark-1000); /** Labels on a filled control — action-primary, -neutral, -danger. */ + --color-text-success: var(--color-palette-success-300); /** Success text and icons. */ + --color-text-warning: var(--color-palette-warning-300); /** Warning text and icons. Always with an icon. */ + --color-text-error: var(--color-palette-danger-300); /** Error text and icons. */ + --color-border-strong: var(--color-palette-neutral-light-100); /** Emphasis — an active field, a selected row. */ + --color-border-default: var(--color-palette-neutral-light-600); /** Any border that carries meaning — control outlines, field borders. */ + --color-border-subtle: var(--color-palette-neutral-light-900); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */ + --color-action-primary: var(--color-palette-accent-300); /** Primary interactive fills, strokes, links and focus rings. */ + --color-action-primary-hover: var(--color-palette-accent-400); /** Hover for action-primary. */ + --color-action-primary-pressed: var(--color-palette-accent-500); /** Pressed for action-primary. */ + --color-action-danger: var(--color-palette-danger-400); /** Destructive actions — delete, remove, revoke. */ + --color-action-danger-hover: var(--color-palette-danger-300); /** Hover for action-danger. */ + --color-action-danger-pressed: var(--color-palette-danger-200); /** Pressed for action-danger. */ + --color-action-neutral: var(--color-palette-neutral-light-200); /** The neutral action fill. Button variant="secondary" resolves here. */ + --color-action-neutral-hover: var(--color-palette-neutral-light-100); /** Hover for action-neutral. */ + --color-action-neutral-pressed: var(--color-white); /** Pressed for action-neutral. */ + --color-action-disabled: var(--color-palette-neutral-dark-800); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */ + --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */ + --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */ + --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent "this one is chosen" tint. */ + --color-viz-1: var(--color-palette-primary-500); /** Categorical series 1. */ + --color-viz-2: var(--color-palette-accent-500); /** Categorical series 2. */ + --color-viz-3: var(--color-palette-warning-400); /** Categorical series 3. */ + --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */ + --color-viz-5: var(--color-palette-success-300); /** Categorical series 5. */ + --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */ + --color-viz-7: var(--color-palette-neutral-dark-500); /** Categorical series 7. */ + --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */ + --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */ + --color-viz-seq-2: var(--color-palette-primary-500); /** Sequential step 2, low to high. */ + --color-viz-seq-3: var(--color-palette-primary-400); /** Sequential step 3, low to high. */ + --color-viz-seq-4: var(--color-palette-primary-300); /** Sequential step 4, low to high. */ + --color-shadow-key: var(--color-black-a32); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */ + --color-shadow-ambient: var(--color-black-a0); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */ + --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */ + --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-900) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%); + --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%); + } +} diff --git a/packages/gamut-styles/src/tokens/generated/originalCodecademy/values.ts b/packages/gamut-styles/src/tokens/generated/originalCodecademy/values.ts new file mode 100644 index 0000000000..1941a4b36e --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/originalCodecademy/values.ts @@ -0,0 +1,1065 @@ +/* Generated by @skillsoft/gamut-brand from the "originalCodecademy" brand. Do not edit. */ + +export const brand = { + "name": "originalCodecademy", + "initialMode": "light", + "hues": { + "neutral-light": 58.8, + "neutral-dark": 271.3, + "primary": 274.1, + "accent": 93.6, + "danger": 29.4, + "warning": 58.3, + "success": 145.5, + "accent-alt": 287.1 + }, + "derived": { + "accent-alt": "held at the gamut default" + }, + "tier": 3, + "overrides": { + "palette": { + "accent-300": "#ffd300", + "accent-400": "#cca900" + }, + "roles": { + "light": { + "shadow-ambient": { + "ref": { + "slot": "neutral-dark", + "grade": 1100 + }, + "alpha": 0 + } + }, + "dark": { + "action-primary": { + "slot": "accent", + "grade": 300 + }, + "action-primary-hover": { + "slot": "accent", + "grade": 400 + }, + "action-primary-pressed": { + "slot": "accent", + "grade": 500 + }, + "shadow-ambient": { + "ref": "black", + "alpha": 0 + } + } + }, + "gradients": { + "surface-brand": { + "accent": "accent-alt" + } + } + }, + "sources": { + "neutral-light": { + "hex": "#fff0e5", + "nearest": 200, + "deltaE": 6.23 + }, + "neutral-dark": { + "hex": "#10162f", + "nearest": 1000, + "deltaE": 6.56 + }, + "primary": { + "hex": "#3a10e5", + "nearest": 800, + "deltaE": 3.82 + }, + "accent": { + "hex": "#ffd300", + "nearest": 300, + "deltaE": 0 + }, + "danger": { + "hex": "#e91c11", + "nearest": 600, + "deltaE": 1.79 + }, + "warning": { + "hex": "#ff8c00", + "nearest": 500, + "deltaE": 8.94 + }, + "success": { + "hex": "#008a27", + "nearest": 600, + "deltaE": 3.86 + } + } +} as const; + +export const palette = { + "white": { + "oklch": "oklch(1 0 0)", + "srgb": "#ffffff" + }, + "black": { + "oklch": "oklch(0 0 0)", + "srgb": "#000000" + }, + "palette-neutral-light-100": { + "oklch": "oklch(0.9862 0.0016 58.8)", + "srgb": "#fbfaf9" + }, + "palette-neutral-light-200": { + "oklch": "oklch(0.9364 0.0031 58.8)", + "srgb": "#ece9e8" + }, + "palette-neutral-light-300": { + "oklch": "oklch(0.8497 0.0109 58.8)", + "srgb": "#d3ccc7" + }, + "palette-neutral-light-400": { + "oklch": "oklch(0.7628 0.0237 58.8)", + "srgb": "#beafa3" + }, + "palette-neutral-light-500": { + "oklch": "oklch(0.6707 0.028 58.8)", + "srgb": "#a39285" + }, + "palette-neutral-light-600": { + "oklch": "oklch(0.5835 0.0277 58.8)", + "srgb": "#88786b" + }, + "palette-neutral-light-700": { + "oklch": "oklch(0.4972 0.0224 58.8)", + "srgb": "#6d6056" + }, + "palette-neutral-light-800": { + "oklch": "oklch(0.4115 0.0175 58.8)", + "srgb": "#524942" + }, + "palette-neutral-light-900": { + "oklch": "oklch(0.3346 0.0139 58.8)", + "srgb": "#3c3530" + }, + "palette-neutral-light-1000": { + "oklch": "oklch(0.2499 0.0099 58.8)", + "srgb": "#25211d" + }, + "palette-neutral-light-1100": { + "oklch": "oklch(0.1925 0.0073 58.8)", + "srgb": "#171411" + }, + "palette-neutral-dark-100": { + "oklch": "oklch(0.9862 0.0012 271.3)", + "srgb": "#fafafb" + }, + "palette-neutral-dark-200": { + "oklch": "oklch(0.9364 0.0024 271.3)", + "srgb": "#e9eaeb" + }, + "palette-neutral-dark-300": { + "oklch": "oklch(0.8497 0.0081 271.3)", + "srgb": "#cbcdd3" + }, + "palette-neutral-dark-400": { + "oklch": "oklch(0.7628 0.0169 271.3)", + "srgb": "#aeb2bd" + }, + "palette-neutral-dark-500": { + "oklch": "oklch(0.6707 0.0306 271.3)", + "srgb": "#8e95a9" + }, + "palette-neutral-dark-600": { + "oklch": "oklch(0.5835 0.0454 271.3)", + "srgb": "#717a97" + }, + "palette-neutral-dark-700": { + "oklch": "oklch(0.4972 0.0538 271.3)", + "srgb": "#576182" + }, + "palette-neutral-dark-800": { + "oklch": "oklch(0.4115 0.0479 271.3)", + "srgb": "#414965" + }, + "palette-neutral-dark-900": { + "oklch": "oklch(0.3346 0.0381 271.3)", + "srgb": "#2f364b" + }, + "palette-neutral-dark-1000": { + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "palette-neutral-dark-1100": { + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "palette-primary-100": { + "oklch": "oklch(0.9862 0.0064 274.1)", + "srgb": "#f9faff" + }, + "palette-primary-200": { + "oklch": "oklch(0.9364 0.0121 274.1)", + "srgb": "#e7eaf2" + }, + "palette-primary-300": { + "oklch": "oklch(0.8497 0.0408 274.1)", + "srgb": "#c5cce9" + }, + "palette-primary-400": { + "oklch": "oklch(0.7628 0.0849 274.1)", + "srgb": "#a1aee9" + }, + "palette-primary-500": { + "oklch": "oklch(0.6707 0.1537 274.1)", + "srgb": "#798bf3" + }, + "palette-primary-600": { + "oklch": "oklch(0.5835 0.2285 274.1)", + "srgb": "#5962ff" + }, + "palette-primary-700": { + "oklch": "oklch(0.4972 0.2707 274.1)", + "srgb": "#4433f7" + }, + "palette-primary-800": { + "oklch": "oklch(0.4115 0.2344 274.1)", + "srgb": "#331fc6" + }, + "palette-primary-900": { + "oklch": "oklch(0.3346 0.1863 274.1)", + "srgb": "#241793" + }, + "palette-primary-1000": { + "oklch": "oklch(0.2499 0.1328 274.1)", + "srgb": "#140f5f" + }, + "palette-primary-1100": { + "oklch": "oklch(0.1925 0.0974 274.1)", + "srgb": "#0b093f" + }, + "palette-accent-100": { + "oklch": "oklch(0.9862 0.0205 93.6)", + "srgb": "#fffbeb" + }, + "palette-accent-200": { + "oklch": "oklch(0.9364 0.0378 93.6)", + "srgb": "#f2eace" + }, + "palette-accent-300": { + "oklch": "oklch(0.8789 0.1801 93.58)", + "srgb": "#ffd300" + }, + "palette-accent-400": { + "oklch": "oklch(0.7442 0.1526 93.94)", + "srgb": "#cca900" + }, + "palette-accent-500": { + "oklch": "oklch(0.6707 0.1209 93.6)", + "srgb": "#ae932e" + }, + "palette-accent-600": { + "oklch": "oklch(0.5835 0.1195 93.6)", + "srgb": "#937900" + }, + "palette-accent-700": { + "oklch": "oklch(0.4972 0.0967 93.6)", + "srgb": "#75610e" + }, + "palette-accent-800": { + "oklch": "oklch(0.4115 0.0758 93.6)", + "srgb": "#594a10" + }, + "palette-accent-900": { + "oklch": "oklch(0.3346 0.0603 93.6)", + "srgb": "#41360b" + }, + "palette-accent-1000": { + "oklch": "oklch(0.2499 0.043 93.6)", + "srgb": "#282106" + }, + "palette-accent-1100": { + "oklch": "oklch(0.1925 0.0315 93.6)", + "srgb": "#191404" + }, + "palette-accent-alt-100": { + "oklch": "oklch(0.9862 0.0067 287.1)", + "srgb": "#fafaff" + }, + "palette-accent-alt-200": { + "oklch": "oklch(0.9364 0.0127 287.1)", + "srgb": "#e9e9f2" + }, + "palette-accent-alt-300": { + "oklch": "oklch(0.8497 0.043 287.1)", + "srgb": "#cbcae9" + }, + "palette-accent-alt-400": { + "oklch": "oklch(0.7628 0.0896 287.1)", + "srgb": "#aeaae9" + }, + "palette-accent-alt-500": { + "oklch": "oklch(0.6707 0.1624 287.1)", + "srgb": "#9182f3" + }, + "palette-accent-alt-600": { + "oklch": "oklch(0.5835 0.2416 287.1)", + "srgb": "#7a52ff" + }, + "palette-accent-alt-700": { + "oklch": "oklch(0.4972 0.2722 287.1)", + "srgb": "#661af0" + }, + "palette-accent-alt-800": { + "oklch": "oklch(0.4115 0.2134 287.1)", + "srgb": "#4d1cb5" + }, + "palette-accent-alt-900": { + "oklch": "oklch(0.3346 0.1696 287.1)", + "srgb": "#381587" + }, + "palette-accent-alt-1000": { + "oklch": "oklch(0.2499 0.1209 287.1)", + "srgb": "#220d57" + }, + "palette-accent-alt-1100": { + "oklch": "oklch(0.1925 0.0887 287.1)", + "srgb": "#150739" + }, + "palette-danger-100": { + "oklch": "oklch(0.9862 0.0066 29.4)", + "srgb": "#fff9f8" + }, + "palette-danger-200": { + "oklch": "oklch(0.9364 0.0128 29.4)", + "srgb": "#f2e7e5" + }, + "palette-danger-300": { + "oklch": "oklch(0.8497 0.0452 29.4)", + "srgb": "#eac3bd" + }, + "palette-danger-400": { + "oklch": "oklch(0.7628 0.099 29.4)", + "srgb": "#ea9a8d" + }, + "palette-danger-500": { + "oklch": "oklch(0.6707 0.191 29.4)", + "srgb": "#f45b4a" + }, + "palette-danger-600": { + "oklch": "oklch(0.5835 0.2382 29.4)", + "srgb": "#e70400" + }, + "palette-danger-700": { + "oklch": "oklch(0.4972 0.1928 29.4)", + "srgb": "#b7160d" + }, + "palette-danger-800": { + "oklch": "oklch(0.4115 0.1512 29.4)", + "srgb": "#8a170f" + }, + "palette-danger-900": { + "oklch": "oklch(0.3346 0.1202 29.4)", + "srgb": "#67110a" + }, + "palette-danger-1000": { + "oklch": "oklch(0.2499 0.0857 29.4)", + "srgb": "#420a06" + }, + "palette-danger-1100": { + "oklch": "oklch(0.1925 0.0628 29.4)", + "srgb": "#2b0603" + }, + "palette-warning-100": { + "oklch": "oklch(0.9862 0.0082 58.3)", + "srgb": "#fff9f5" + }, + "palette-warning-200": { + "oklch": "oklch(0.9364 0.0156 58.3)", + "srgb": "#f2e8e0" + }, + "palette-warning-300": { + "oklch": "oklch(0.8497 0.0545 58.3)", + "srgb": "#eac6ab" + }, + "palette-warning-400": { + "oklch": "oklch(0.7628 0.1179 58.3)", + "srgb": "#ea9f63" + }, + "palette-warning-500": { + "oklch": "oklch(0.6707 0.1408 58.3)", + "srgb": "#d47d2d" + }, + "palette-warning-600": { + "oklch": "oklch(0.5835 0.1392 58.3)", + "srgb": "#b66200" + }, + "palette-warning-700": { + "oklch": "oklch(0.4972 0.1127 58.3)", + "srgb": "#914f0e" + }, + "palette-warning-800": { + "oklch": "oklch(0.4115 0.0883 58.3)", + "srgb": "#6d3d10" + }, + "palette-warning-900": { + "oklch": "oklch(0.3346 0.0702 58.3)", + "srgb": "#512c0b" + }, + "palette-warning-1000": { + "oklch": "oklch(0.2499 0.05 58.3)", + "srgb": "#331b06" + }, + "palette-warning-1100": { + "oklch": "oklch(0.1925 0.0367 58.3)", + "srgb": "#201004" + }, + "palette-success-100": { + "oklch": "oklch(0.9862 0.0228 145.5)", + "srgb": "#f1fff1" + }, + "palette-success-200": { + "oklch": "oklch(0.9364 0.0458 145.5)", + "srgb": "#d8f3d8" + }, + "palette-success-300": { + "oklch": "oklch(0.8497 0.1448 145.5)", + "srgb": "#8de792" + }, + "palette-success-400": { + "oklch": "oklch(0.7628 0.1654 145.5)", + "srgb": "#64cd6d" + }, + "palette-success-500": { + "oklch": "oklch(0.6707 0.1829 145.5)", + "srgb": "#34b147" + }, + "palette-success-600": { + "oklch": "oklch(0.5835 0.1808 145.5)", + "srgb": "#00952b" + }, + "palette-success-700": { + "oklch": "oklch(0.4972 0.1464 145.5)", + "srgb": "#117726" + }, + "palette-success-800": { + "oklch": "oklch(0.4115 0.1147 145.5)", + "srgb": "#135a1f" + }, + "palette-success-900": { + "oklch": "oklch(0.3346 0.0912 145.5)", + "srgb": "#0e4216" + }, + "palette-success-1000": { + "oklch": "oklch(0.2499 0.065 145.5)", + "srgb": "#08290c" + }, + "palette-success-1100": { + "oklch": "oklch(0.1925 0.0477 145.5)", + "srgb": "#041a06" + }, + "palette-primary-600-a6": { + "oklch": "oklch(0.5835 0.2285 274.1 / 0.06)", + "srgb": "rgba(89, 98, 255, 0.06)" + }, + "palette-primary-600-a9": { + "oklch": "oklch(0.5835 0.2285 274.1 / 0.09)", + "srgb": "rgba(89, 98, 255, 0.09)" + }, + "palette-primary-600-a8": { + "oklch": "oklch(0.5835 0.2285 274.1 / 0.08)", + "srgb": "rgba(89, 98, 255, 0.08)" + }, + "palette-neutral-dark-1100-a16": { + "oklch": "oklch(0.1925 0.0199 271.3 / 0.16)", + "srgb": "rgba(17, 20, 29, 0.16)" + }, + "black-a32": { + "oklch": "oklch(0 0 0 / 0.32)", + "srgb": "rgba(0, 0, 0, 0.32)" + }, + "palette-neutral-dark-1100-a12": { + "oklch": "oklch(0.1925 0.0199 271.3 / 0.12)", + "srgb": "rgba(17, 20, 29, 0.12)" + }, + "black-a24": { + "oklch": "oklch(0 0 0 / 0.24)", + "srgb": "rgba(0, 0, 0, 0.24)" + }, + "black-a60": { + "oklch": "oklch(0 0 0 / 0.6)", + "srgb": "rgba(0, 0, 0, 0.6)" + }, + "palette-neutral-dark-1100-a0": { + "oklch": "oklch(0.1925 0.0199 271.3 / 0)", + "srgb": "rgba(17, 20, 29, 0)" + }, + "black-a0": { + "oklch": "oklch(0 0 0 / 0)", + "srgb": "rgba(0, 0, 0, 0)" + } +} as const; + +export const roles = { + "light": { + "surface-sunken": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0031 58.8)", + "srgb": "#ece9e8" + }, + "surface-default": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9862 0.0016 58.8)", + "srgb": "#fbfaf9" + }, + "surface-raised": { + "ref": "white", + "oklch": "oklch(1 0 0)", + "srgb": "#ffffff" + }, + "surface-success": { + "ref": "palette-success-200", + "oklch": "oklch(0.9364 0.0458 145.5)", + "srgb": "#d8f3d8" + }, + "surface-warning": { + "ref": "palette-warning-200", + "oklch": "oklch(0.9364 0.0156 58.3)", + "srgb": "#f2e8e0" + }, + "surface-error": { + "ref": "palette-danger-200", + "oklch": "oklch(0.9364 0.0128 29.4)", + "srgb": "#f2e7e5" + }, + "surface-brand": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9862 0.0016 58.8)", + "srgb": "#fbfaf9" + }, + "surface-brand-subtle": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9862 0.0016 58.8)", + "srgb": "#fbfaf9" + }, + "text-default": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "text-subtle": { + "ref": "palette-neutral-dark-800", + "oklch": "oklch(0.4115 0.0479 271.3)", + "srgb": "#414965" + }, + "text-inverse": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0031 58.8)", + "srgb": "#ece9e8" + }, + "text-success": { + "ref": "palette-success-900", + "oklch": "oklch(0.3346 0.0912 145.5)", + "srgb": "#0e4216" + }, + "text-warning": { + "ref": "palette-warning-900", + "oklch": "oklch(0.3346 0.0702 58.3)", + "srgb": "#512c0b" + }, + "text-error": { + "ref": "palette-danger-900", + "oklch": "oklch(0.3346 0.1202 29.4)", + "srgb": "#67110a" + }, + "border-strong": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "border-default": { + "ref": "palette-neutral-dark-600", + "oklch": "oklch(0.5835 0.0454 271.3)", + "srgb": "#717a97" + }, + "border-subtle": { + "ref": "palette-neutral-dark-300", + "oklch": "oklch(0.8497 0.0081 271.3)", + "srgb": "#cbcdd3" + }, + "action-primary": { + "ref": "palette-primary-800", + "oklch": "oklch(0.4115 0.2344 274.1)", + "srgb": "#331fc6" + }, + "action-primary-hover": { + "ref": "palette-primary-900", + "oklch": "oklch(0.3346 0.1863 274.1)", + "srgb": "#241793" + }, + "action-primary-pressed": { + "ref": "palette-primary-1000", + "oklch": "oklch(0.2499 0.1328 274.1)", + "srgb": "#140f5f" + }, + "action-danger": { + "ref": "palette-danger-800", + "oklch": "oklch(0.4115 0.1512 29.4)", + "srgb": "#8a170f" + }, + "action-danger-hover": { + "ref": "palette-danger-900", + "oklch": "oklch(0.3346 0.1202 29.4)", + "srgb": "#67110a" + }, + "action-danger-pressed": { + "ref": "palette-danger-1000", + "oklch": "oklch(0.2499 0.0857 29.4)", + "srgb": "#420a06" + }, + "action-neutral": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "action-neutral-hover": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "action-neutral-pressed": { + "ref": "black", + "oklch": "oklch(0 0 0)", + "srgb": "#000000" + }, + "action-disabled": { + "ref": "palette-neutral-light-400", + "oklch": "oklch(0.7628 0.0237 58.8)", + "srgb": "#beafa3" + }, + "state-hover": { + "ref": "palette-primary-600-a6", + "oklch": "oklch(0.5835 0.2285 274.1 / 0.06)", + "srgb": "rgba(89, 98, 255, 0.06)" + }, + "state-pressed": { + "ref": "palette-primary-600-a9", + "oklch": "oklch(0.5835 0.2285 274.1 / 0.09)", + "srgb": "rgba(89, 98, 255, 0.09)" + }, + "state-selected": { + "ref": "palette-primary-600-a8", + "oklch": "oklch(0.5835 0.2285 274.1 / 0.08)", + "srgb": "rgba(89, 98, 255, 0.08)" + }, + "viz-1": { + "ref": "palette-primary-800", + "oklch": "oklch(0.4115 0.2344 274.1)", + "srgb": "#331fc6" + }, + "viz-2": { + "ref": "palette-accent-700", + "oklch": "oklch(0.4972 0.0967 93.6)", + "srgb": "#75610e" + }, + "viz-3": { + "ref": "palette-warning-700", + "oklch": "oklch(0.4972 0.1127 58.3)", + "srgb": "#914f0e" + }, + "viz-4": { + "ref": "palette-accent-alt-600", + "oklch": "oklch(0.5835 0.2416 287.1)", + "srgb": "#7a52ff" + }, + "viz-5": { + "ref": "palette-success-600", + "oklch": "oklch(0.5835 0.1808 145.5)", + "srgb": "#00952b" + }, + "viz-6": { + "ref": "palette-danger-600", + "oklch": "oklch(0.5835 0.2382 29.4)", + "srgb": "#e70400" + }, + "viz-7": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "viz-8": { + "ref": "palette-neutral-light-600", + "oklch": "oklch(0.5835 0.0277 58.8)", + "srgb": "#88786b" + }, + "viz-seq-1": { + "ref": "palette-primary-600", + "oklch": "oklch(0.5835 0.2285 274.1)", + "srgb": "#5962ff" + }, + "viz-seq-2": { + "ref": "palette-primary-700", + "oklch": "oklch(0.4972 0.2707 274.1)", + "srgb": "#4433f7" + }, + "viz-seq-3": { + "ref": "palette-primary-800", + "oklch": "oklch(0.4115 0.2344 274.1)", + "srgb": "#331fc6" + }, + "viz-seq-4": { + "ref": "palette-primary-900", + "oklch": "oklch(0.3346 0.1863 274.1)", + "srgb": "#241793" + }, + "shadow-key": { + "ref": "palette-neutral-dark-1100-a16", + "oklch": "oklch(0.1925 0.0199 271.3 / 0.16)", + "srgb": "rgba(17, 20, 29, 0.16)" + }, + "shadow-ambient": { + "ref": "palette-neutral-dark-1100-a0", + "oklch": "oklch(0.1925 0.0199 271.3 / 0)", + "srgb": "rgba(17, 20, 29, 0)" + }, + "shadow-scrim": { + "ref": "black-a60", + "oklch": "oklch(0 0 0 / 0.6)", + "srgb": "rgba(0, 0, 0, 0.6)" + } + }, + "dark": { + "surface-sunken": { + "ref": "black", + "oklch": "oklch(0 0 0)", + "srgb": "#000000" + }, + "surface-default": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "surface-raised": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "surface-success": { + "ref": "palette-success-1000", + "oklch": "oklch(0.2499 0.065 145.5)", + "srgb": "#08290c" + }, + "surface-warning": { + "ref": "palette-warning-1000", + "oklch": "oklch(0.2499 0.05 58.3)", + "srgb": "#331b06" + }, + "surface-error": { + "ref": "palette-danger-1000", + "oklch": "oklch(0.2499 0.0857 29.4)", + "srgb": "#420a06" + }, + "surface-brand": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "surface-brand-subtle": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "text-default": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0031 58.8)", + "srgb": "#ece9e8" + }, + "text-subtle": { + "ref": "palette-neutral-light-400", + "oklch": "oklch(0.7628 0.0237 58.8)", + "srgb": "#beafa3" + }, + "text-inverse": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "text-success": { + "ref": "palette-success-300", + "oklch": "oklch(0.8497 0.1448 145.5)", + "srgb": "#8de792" + }, + "text-warning": { + "ref": "palette-warning-300", + "oklch": "oklch(0.8497 0.0545 58.3)", + "srgb": "#eac6ab" + }, + "text-error": { + "ref": "palette-danger-300", + "oklch": "oklch(0.8497 0.0452 29.4)", + "srgb": "#eac3bd" + }, + "border-strong": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9862 0.0016 58.8)", + "srgb": "#fbfaf9" + }, + "border-default": { + "ref": "palette-neutral-light-600", + "oklch": "oklch(0.5835 0.0277 58.8)", + "srgb": "#88786b" + }, + "border-subtle": { + "ref": "palette-neutral-light-900", + "oklch": "oklch(0.3346 0.0139 58.8)", + "srgb": "#3c3530" + }, + "action-primary": { + "ref": "palette-accent-300", + "oklch": "oklch(0.8789 0.1801 93.58)", + "srgb": "#ffd300" + }, + "action-primary-hover": { + "ref": "palette-accent-400", + "oklch": "oklch(0.7442 0.1526 93.94)", + "srgb": "#cca900" + }, + "action-primary-pressed": { + "ref": "palette-accent-500", + "oklch": "oklch(0.6707 0.1209 93.6)", + "srgb": "#ae932e" + }, + "action-danger": { + "ref": "palette-danger-400", + "oklch": "oklch(0.7628 0.099 29.4)", + "srgb": "#ea9a8d" + }, + "action-danger-hover": { + "ref": "palette-danger-300", + "oklch": "oklch(0.8497 0.0452 29.4)", + "srgb": "#eac3bd" + }, + "action-danger-pressed": { + "ref": "palette-danger-200", + "oklch": "oklch(0.9364 0.0128 29.4)", + "srgb": "#f2e7e5" + }, + "action-neutral": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0031 58.8)", + "srgb": "#ece9e8" + }, + "action-neutral-hover": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9862 0.0016 58.8)", + "srgb": "#fbfaf9" + }, + "action-neutral-pressed": { + "ref": "white", + "oklch": "oklch(1 0 0)", + "srgb": "#ffffff" + }, + "action-disabled": { + "ref": "palette-neutral-dark-800", + "oklch": "oklch(0.4115 0.0479 271.3)", + "srgb": "#414965" + }, + "state-hover": { + "ref": "palette-primary-600-a6", + "oklch": "oklch(0.5835 0.2285 274.1 / 0.06)", + "srgb": "rgba(89, 98, 255, 0.06)" + }, + "state-pressed": { + "ref": "palette-primary-600-a9", + "oklch": "oklch(0.5835 0.2285 274.1 / 0.09)", + "srgb": "rgba(89, 98, 255, 0.09)" + }, + "state-selected": { + "ref": "palette-primary-600-a8", + "oklch": "oklch(0.5835 0.2285 274.1 / 0.08)", + "srgb": "rgba(89, 98, 255, 0.08)" + }, + "viz-1": { + "ref": "palette-primary-500", + "oklch": "oklch(0.6707 0.1537 274.1)", + "srgb": "#798bf3" + }, + "viz-2": { + "ref": "palette-accent-500", + "oklch": "oklch(0.6707 0.1209 93.6)", + "srgb": "#ae932e" + }, + "viz-3": { + "ref": "palette-warning-400", + "oklch": "oklch(0.7628 0.1179 58.3)", + "srgb": "#ea9f63" + }, + "viz-4": { + "ref": "palette-accent-alt-600", + "oklch": "oklch(0.5835 0.2416 287.1)", + "srgb": "#7a52ff" + }, + "viz-5": { + "ref": "palette-success-300", + "oklch": "oklch(0.8497 0.1448 145.5)", + "srgb": "#8de792" + }, + "viz-6": { + "ref": "palette-danger-600", + "oklch": "oklch(0.5835 0.2382 29.4)", + "srgb": "#e70400" + }, + "viz-7": { + "ref": "palette-neutral-dark-500", + "oklch": "oklch(0.6707 0.0306 271.3)", + "srgb": "#8e95a9" + }, + "viz-8": { + "ref": "palette-neutral-light-600", + "oklch": "oklch(0.5835 0.0277 58.8)", + "srgb": "#88786b" + }, + "viz-seq-1": { + "ref": "palette-primary-600", + "oklch": "oklch(0.5835 0.2285 274.1)", + "srgb": "#5962ff" + }, + "viz-seq-2": { + "ref": "palette-primary-500", + "oklch": "oklch(0.6707 0.1537 274.1)", + "srgb": "#798bf3" + }, + "viz-seq-3": { + "ref": "palette-primary-400", + "oklch": "oklch(0.7628 0.0849 274.1)", + "srgb": "#a1aee9" + }, + "viz-seq-4": { + "ref": "palette-primary-300", + "oklch": "oklch(0.8497 0.0408 274.1)", + "srgb": "#c5cce9" + }, + "shadow-key": { + "ref": "black-a32", + "oklch": "oklch(0 0 0 / 0.32)", + "srgb": "rgba(0, 0, 0, 0.32)" + }, + "shadow-ambient": { + "ref": "black-a0", + "oklch": "oklch(0 0 0 / 0)", + "srgb": "rgba(0, 0, 0, 0)" + }, + "shadow-scrim": { + "ref": "black-a60", + "oklch": "oklch(0 0 0 / 0.6)", + "srgb": "rgba(0, 0, 0, 0.6)" + } + } +} as const; + +export const gradients = { + "light": { + "surface-brand": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.8497 0.043 287.1 / 0.45) 0%, oklch(0.8497 0.043 287.1 / 0.43) 16%, oklch(0.8497 0.043 287.1 / 0.24) 36%, oklch(0.8497 0.043 287.1 / 0.11) 56%, oklch(0.8497 0.043 287.1 / 0.04) 76%, oklch(0.8497 0.043 287.1 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.8497 0.0408 274.1 / 0.45) 0%, oklch(0.8497 0.0408 274.1 / 0.43) 16%, oklch(0.8497 0.0408 274.1 / 0.24) 36%, oklch(0.8497 0.0408 274.1 / 0.11) 56%, oklch(0.8497 0.0408 274.1 / 0.04) 76%, oklch(0.8497 0.0408 274.1 / 0) 100%)", + "surface-brand-subtle": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.8497 0.0408 274.1 / 0.45) 0%, oklch(0.8497 0.0408 274.1 / 0.43) 16%, oklch(0.8497 0.0408 274.1 / 0.24) 36%, oklch(0.8497 0.0408 274.1 / 0.11) 56%, oklch(0.8497 0.0408 274.1 / 0.04) 76%, oklch(0.8497 0.0408 274.1 / 0) 100%)" + }, + "dark": { + "surface-brand": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.3346 0.1696 287.1 / 0.45) 0%, oklch(0.3346 0.1696 287.1 / 0.43) 16%, oklch(0.3346 0.1696 287.1 / 0.24) 36%, oklch(0.3346 0.1696 287.1 / 0.11) 56%, oklch(0.3346 0.1696 287.1 / 0.04) 76%, oklch(0.3346 0.1696 287.1 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.3346 0.1863 274.1 / 0.45) 0%, oklch(0.3346 0.1863 274.1 / 0.43) 16%, oklch(0.3346 0.1863 274.1 / 0.24) 36%, oklch(0.3346 0.1863 274.1 / 0.11) 56%, oklch(0.3346 0.1863 274.1 / 0.04) 76%, oklch(0.3346 0.1863 274.1 / 0) 100%)", + "surface-brand-subtle": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.3346 0.1863 274.1 / 0.45) 0%, oklch(0.3346 0.1863 274.1 / 0.43) 16%, oklch(0.3346 0.1863 274.1 / 0.24) 36%, oklch(0.3346 0.1863 274.1 / 0.11) 56%, oklch(0.3346 0.1863 274.1 / 0.04) 76%, oklch(0.3346 0.1863 274.1 / 0) 100%)" + } +} as const; + +/* Palette color → the mode its content uses. Background reads this instead of measuring. */ +export const contentModes = { + "white": "light", + "black": "dark", + "palette-neutral-light-100": "light", + "palette-neutral-light-200": "light", + "palette-neutral-light-300": "light", + "palette-neutral-light-400": "light", + "palette-neutral-light-500": "light", + "palette-neutral-light-600": "light", + "palette-neutral-light-700": "dark", + "palette-neutral-light-800": "dark", + "palette-neutral-light-900": "dark", + "palette-neutral-light-1000": "dark", + "palette-neutral-light-1100": "dark", + "palette-neutral-dark-100": "light", + "palette-neutral-dark-200": "light", + "palette-neutral-dark-300": "light", + "palette-neutral-dark-400": "light", + "palette-neutral-dark-500": "light", + "palette-neutral-dark-600": "light", + "palette-neutral-dark-700": "dark", + "palette-neutral-dark-800": "dark", + "palette-neutral-dark-900": "dark", + "palette-neutral-dark-1000": "dark", + "palette-neutral-dark-1100": "dark", + "palette-primary-100": "light", + "palette-primary-200": "light", + "palette-primary-300": "light", + "palette-primary-400": "light", + "palette-primary-500": "light", + "palette-primary-600": "dark", + "palette-primary-700": "dark", + "palette-primary-800": "dark", + "palette-primary-900": "dark", + "palette-primary-1000": "dark", + "palette-primary-1100": "dark", + "palette-accent-100": "light", + "palette-accent-200": "light", + "palette-accent-300": "light", + "palette-accent-400": "light", + "palette-accent-500": "light", + "palette-accent-600": "light", + "palette-accent-700": "dark", + "palette-accent-800": "dark", + "palette-accent-900": "dark", + "palette-accent-1000": "dark", + "palette-accent-1100": "dark", + "palette-accent-alt-100": "light", + "palette-accent-alt-200": "light", + "palette-accent-alt-300": "light", + "palette-accent-alt-400": "light", + "palette-accent-alt-500": "light", + "palette-accent-alt-600": "dark", + "palette-accent-alt-700": "dark", + "palette-accent-alt-800": "dark", + "palette-accent-alt-900": "dark", + "palette-accent-alt-1000": "dark", + "palette-accent-alt-1100": "dark", + "palette-danger-100": "light", + "palette-danger-200": "light", + "palette-danger-300": "light", + "palette-danger-400": "light", + "palette-danger-500": "light", + "palette-danger-600": "dark", + "palette-danger-700": "dark", + "palette-danger-800": "dark", + "palette-danger-900": "dark", + "palette-danger-1000": "dark", + "palette-danger-1100": "dark", + "palette-warning-100": "light", + "palette-warning-200": "light", + "palette-warning-300": "light", + "palette-warning-400": "light", + "palette-warning-500": "light", + "palette-warning-600": "dark", + "palette-warning-700": "dark", + "palette-warning-800": "dark", + "palette-warning-900": "dark", + "palette-warning-1000": "dark", + "palette-warning-1100": "dark", + "palette-success-100": "light", + "palette-success-200": "light", + "palette-success-300": "light", + "palette-success-400": "light", + "palette-success-500": "light", + "palette-success-600": "light", + "palette-success-700": "dark", + "palette-success-800": "dark", + "palette-success-900": "dark", + "palette-success-1000": "dark", + "palette-success-1100": "dark" +} as const; + +/* This brand's tokens.css, for GamutProvider to inject. */ +export const tokensCss = "/* Generated by @skillsoft/gamut-brand from the \"originalCodecademy\" brand. Do not edit. */\n@layer reset, base, tokens, recipes, utilities;\n\n@layer tokens {\n :root, [data-brand=\"originalCodecademy\"] {\n --color-white: oklch(1.0000 0.0000 0.00);\n --color-black: oklch(0.0000 0.0000 0.00);\n --color-palette-neutral-light-100: oklch(0.9862 0.0016 58.80);\n --color-palette-neutral-light-200: oklch(0.9364 0.0031 58.80);\n --color-palette-neutral-light-300: oklch(0.8497 0.0109 58.80);\n --color-palette-neutral-light-400: oklch(0.7628 0.0237 58.80);\n --color-palette-neutral-light-500: oklch(0.6707 0.0280 58.80);\n --color-palette-neutral-light-600: oklch(0.5835 0.0277 58.80);\n --color-palette-neutral-light-700: oklch(0.4972 0.0224 58.80);\n --color-palette-neutral-light-800: oklch(0.4115 0.0175 58.80);\n --color-palette-neutral-light-900: oklch(0.3346 0.0139 58.80);\n --color-palette-neutral-light-1000: oklch(0.2499 0.0099 58.80);\n --color-palette-neutral-light-1100: oklch(0.1925 0.0073 58.80);\n --color-palette-neutral-dark-100: oklch(0.9862 0.0012 271.30);\n --color-palette-neutral-dark-200: oklch(0.9364 0.0024 271.30);\n --color-palette-neutral-dark-300: oklch(0.8497 0.0081 271.30);\n --color-palette-neutral-dark-400: oklch(0.7628 0.0169 271.30);\n --color-palette-neutral-dark-500: oklch(0.6707 0.0306 271.30);\n --color-palette-neutral-dark-600: oklch(0.5835 0.0454 271.30);\n --color-palette-neutral-dark-700: oklch(0.4972 0.0538 271.30);\n --color-palette-neutral-dark-800: oklch(0.4115 0.0479 271.30);\n --color-palette-neutral-dark-900: oklch(0.3346 0.0381 271.30);\n --color-palette-neutral-dark-1000: oklch(0.2499 0.0272 271.30);\n --color-palette-neutral-dark-1100: oklch(0.1925 0.0199 271.30);\n --color-palette-neutral-dark-1100-a16: oklch(0.1925 0.0199 271.30 / 0.16);\n --color-palette-neutral-dark-1100-a12: oklch(0.1925 0.0199 271.30 / 0.12);\n --color-palette-neutral-dark-1100-a0: oklch(0.1925 0.0199 271.30 / 0);\n --color-palette-primary-100: oklch(0.9862 0.0064 274.10);\n --color-palette-primary-200: oklch(0.9364 0.0121 274.10);\n --color-palette-primary-300: oklch(0.8497 0.0408 274.10);\n --color-palette-primary-400: oklch(0.7628 0.0849 274.10);\n --color-palette-primary-500: oklch(0.6707 0.1537 274.10);\n --color-palette-primary-600: oklch(0.5835 0.2285 274.10);\n --color-palette-primary-700: oklch(0.4972 0.2707 274.10);\n --color-palette-primary-800: oklch(0.4115 0.2344 274.10);\n --color-palette-primary-900: oklch(0.3346 0.1863 274.10);\n --color-palette-primary-1000: oklch(0.2499 0.1328 274.10);\n --color-palette-primary-1100: oklch(0.1925 0.0974 274.10);\n --color-palette-primary-600-a6: oklch(0.5835 0.2285 274.10 / 0.06);\n --color-palette-primary-600-a9: oklch(0.5835 0.2285 274.10 / 0.09);\n --color-palette-primary-600-a8: oklch(0.5835 0.2285 274.10 / 0.08);\n --color-palette-accent-100: oklch(0.9862 0.0205 93.60);\n --color-palette-accent-200: oklch(0.9364 0.0378 93.60);\n --color-palette-accent-300: oklch(0.8789 0.1801 93.58);\n --color-palette-accent-400: oklch(0.7442 0.1526 93.94);\n --color-palette-accent-500: oklch(0.6707 0.1209 93.60);\n --color-palette-accent-600: oklch(0.5835 0.1195 93.60);\n --color-palette-accent-700: oklch(0.4972 0.0967 93.60);\n --color-palette-accent-800: oklch(0.4115 0.0758 93.60);\n --color-palette-accent-900: oklch(0.3346 0.0603 93.60);\n --color-palette-accent-1000: oklch(0.2499 0.0430 93.60);\n --color-palette-accent-1100: oklch(0.1925 0.0315 93.60);\n --color-palette-accent-alt-100: oklch(0.9862 0.0067 287.10);\n --color-palette-accent-alt-200: oklch(0.9364 0.0127 287.10);\n --color-palette-accent-alt-300: oklch(0.8497 0.0430 287.10);\n --color-palette-accent-alt-400: oklch(0.7628 0.0896 287.10);\n --color-palette-accent-alt-500: oklch(0.6707 0.1624 287.10);\n --color-palette-accent-alt-600: oklch(0.5835 0.2416 287.10);\n --color-palette-accent-alt-700: oklch(0.4972 0.2722 287.10);\n --color-palette-accent-alt-800: oklch(0.4115 0.2134 287.10);\n --color-palette-accent-alt-900: oklch(0.3346 0.1696 287.10);\n --color-palette-accent-alt-1000: oklch(0.2499 0.1209 287.10);\n --color-palette-accent-alt-1100: oklch(0.1925 0.0887 287.10);\n --color-palette-danger-100: oklch(0.9862 0.0066 29.40);\n --color-palette-danger-200: oklch(0.9364 0.0128 29.40);\n --color-palette-danger-300: oklch(0.8497 0.0452 29.40);\n --color-palette-danger-400: oklch(0.7628 0.0990 29.40);\n --color-palette-danger-500: oklch(0.6707 0.1910 29.40);\n --color-palette-danger-600: oklch(0.5835 0.2382 29.40);\n --color-palette-danger-700: oklch(0.4972 0.1928 29.40);\n --color-palette-danger-800: oklch(0.4115 0.1512 29.40);\n --color-palette-danger-900: oklch(0.3346 0.1202 29.40);\n --color-palette-danger-1000: oklch(0.2499 0.0857 29.40);\n --color-palette-danger-1100: oklch(0.1925 0.0628 29.40);\n --color-palette-warning-100: oklch(0.9862 0.0082 58.30);\n --color-palette-warning-200: oklch(0.9364 0.0156 58.30);\n --color-palette-warning-300: oklch(0.8497 0.0545 58.30);\n --color-palette-warning-400: oklch(0.7628 0.1179 58.30);\n --color-palette-warning-500: oklch(0.6707 0.1408 58.30);\n --color-palette-warning-600: oklch(0.5835 0.1392 58.30);\n --color-palette-warning-700: oklch(0.4972 0.1127 58.30);\n --color-palette-warning-800: oklch(0.4115 0.0883 58.30);\n --color-palette-warning-900: oklch(0.3346 0.0702 58.30);\n --color-palette-warning-1000: oklch(0.2499 0.0500 58.30);\n --color-palette-warning-1100: oklch(0.1925 0.0367 58.30);\n --color-palette-success-100: oklch(0.9862 0.0228 145.50);\n --color-palette-success-200: oklch(0.9364 0.0458 145.50);\n --color-palette-success-300: oklch(0.8497 0.1448 145.50);\n --color-palette-success-400: oklch(0.7628 0.1654 145.50);\n --color-palette-success-500: oklch(0.6707 0.1829 145.50);\n --color-palette-success-600: oklch(0.5835 0.1808 145.50);\n --color-palette-success-700: oklch(0.4972 0.1464 145.50);\n --color-palette-success-800: oklch(0.4115 0.1147 145.50);\n --color-palette-success-900: oklch(0.3346 0.0912 145.50);\n --color-palette-success-1000: oklch(0.2499 0.0650 145.50);\n --color-palette-success-1100: oklch(0.1925 0.0477 145.50);\n --color-black-a32: oklch(0.0000 0.0000 0.00 / 0.32);\n --color-black-a24: oklch(0.0000 0.0000 0.00 / 0.24);\n --color-black-a60: oklch(0.0000 0.0000 0.00 / 0.6);\n --color-black-a0: oklch(0.0000 0.0000 0.00 / 0);\n }\n\n :root, [data-color-mode=\"light\"] {\n color-scheme: light;\n color: var(--color-text-default);\n --color-background-current: var(--color-surface-default);\n --color-surface-sunken: var(--color-palette-neutral-light-200); /** Wells, insets, code blocks — anything recessed into the page. */\n --color-surface-default: var(--color-palette-neutral-light-100); /** The page background — the base every other surface elevates from. */\n --color-surface-raised: var(--color-white); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */\n --color-surface-success: var(--color-palette-success-200); /** The success tint behind an alert or inline confirmation. */\n --color-surface-warning: var(--color-palette-warning-200); /** The warning tint behind an alert or inline caution. */\n --color-surface-error: var(--color-palette-danger-200); /** The error tint behind an alert or inline validation message. */\n --color-surface-brand: var(--color-palette-neutral-light-100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */\n --color-surface-brand-subtle: var(--color-palette-neutral-light-100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */\n --color-text-default: var(--color-palette-neutral-dark-1000); /** Body copy and headings. */\n --color-text-subtle: var(--color-palette-neutral-dark-800); /** Secondary text — descriptions, metadata, helper copy. */\n --color-text-inverse: var(--color-palette-neutral-light-200); /** Labels on a filled control — action-primary, -neutral, -danger. */\n --color-text-success: var(--color-palette-success-900); /** Success text and icons. */\n --color-text-warning: var(--color-palette-warning-900); /** Warning text and icons. Always with an icon. */\n --color-text-error: var(--color-palette-danger-900); /** Error text and icons. */\n --color-border-strong: var(--color-palette-neutral-dark-1100); /** Emphasis — an active field, a selected row. */\n --color-border-default: var(--color-palette-neutral-dark-600); /** Any border that carries meaning — control outlines, field borders. */\n --color-border-subtle: var(--color-palette-neutral-dark-300); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */\n --color-action-primary: var(--color-palette-primary-800); /** Primary interactive fills, strokes, links and focus rings. */\n --color-action-primary-hover: var(--color-palette-primary-900); /** Hover for action-primary. */\n --color-action-primary-pressed: var(--color-palette-primary-1000); /** Pressed for action-primary. */\n --color-action-danger: var(--color-palette-danger-800); /** Destructive actions — delete, remove, revoke. */\n --color-action-danger-hover: var(--color-palette-danger-900); /** Hover for action-danger. */\n --color-action-danger-pressed: var(--color-palette-danger-1000); /** Pressed for action-danger. */\n --color-action-neutral: var(--color-palette-neutral-dark-1000); /** The neutral action fill. Button variant=\"secondary\" resolves here. */\n --color-action-neutral-hover: var(--color-palette-neutral-dark-1100); /** Hover for action-neutral. */\n --color-action-neutral-pressed: var(--color-black); /** Pressed for action-neutral. */\n --color-action-disabled: var(--color-palette-neutral-light-400); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */\n --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */\n --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */\n --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent \"this one is chosen\" tint. */\n --color-viz-1: var(--color-palette-primary-800); /** Categorical series 1. */\n --color-viz-2: var(--color-palette-accent-700); /** Categorical series 2. */\n --color-viz-3: var(--color-palette-warning-700); /** Categorical series 3. */\n --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */\n --color-viz-5: var(--color-palette-success-600); /** Categorical series 5. */\n --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */\n --color-viz-7: var(--color-palette-neutral-dark-1000); /** Categorical series 7. */\n --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */\n --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */\n --color-viz-seq-2: var(--color-palette-primary-700); /** Sequential step 2, low to high. */\n --color-viz-seq-3: var(--color-palette-primary-800); /** Sequential step 3, low to high. */\n --color-viz-seq-4: var(--color-palette-primary-900); /** Sequential step 4, low to high. */\n --color-shadow-key: var(--color-palette-neutral-dark-1100-a16); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */\n --color-shadow-ambient: var(--color-palette-neutral-dark-1100-a0); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */\n --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */\n --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-300) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%);\n --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%);\n }\n\n [data-color-mode=\"dark\"] {\n color-scheme: dark;\n color: var(--color-text-default);\n --color-background-current: var(--color-surface-default);\n --color-surface-sunken: var(--color-black); /** Wells, insets, code blocks — anything recessed into the page. */\n --color-surface-default: var(--color-palette-neutral-dark-1100); /** The page background — the base every other surface elevates from. */\n --color-surface-raised: var(--color-palette-neutral-dark-1000); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */\n --color-surface-success: var(--color-palette-success-1000); /** The success tint behind an alert or inline confirmation. */\n --color-surface-warning: var(--color-palette-warning-1000); /** The warning tint behind an alert or inline caution. */\n --color-surface-error: var(--color-palette-danger-1000); /** The error tint behind an alert or inline validation message. */\n --color-surface-brand: var(--color-palette-neutral-dark-1100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */\n --color-surface-brand-subtle: var(--color-palette-neutral-dark-1100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */\n --color-text-default: var(--color-palette-neutral-light-200); /** Body copy and headings. */\n --color-text-subtle: var(--color-palette-neutral-light-400); /** Secondary text — descriptions, metadata, helper copy. */\n --color-text-inverse: var(--color-palette-neutral-dark-1000); /** Labels on a filled control — action-primary, -neutral, -danger. */\n --color-text-success: var(--color-palette-success-300); /** Success text and icons. */\n --color-text-warning: var(--color-palette-warning-300); /** Warning text and icons. Always with an icon. */\n --color-text-error: var(--color-palette-danger-300); /** Error text and icons. */\n --color-border-strong: var(--color-palette-neutral-light-100); /** Emphasis — an active field, a selected row. */\n --color-border-default: var(--color-palette-neutral-light-600); /** Any border that carries meaning — control outlines, field borders. */\n --color-border-subtle: var(--color-palette-neutral-light-900); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */\n --color-action-primary: var(--color-palette-accent-300); /** Primary interactive fills, strokes, links and focus rings. */\n --color-action-primary-hover: var(--color-palette-accent-400); /** Hover for action-primary. */\n --color-action-primary-pressed: var(--color-palette-accent-500); /** Pressed for action-primary. */\n --color-action-danger: var(--color-palette-danger-400); /** Destructive actions — delete, remove, revoke. */\n --color-action-danger-hover: var(--color-palette-danger-300); /** Hover for action-danger. */\n --color-action-danger-pressed: var(--color-palette-danger-200); /** Pressed for action-danger. */\n --color-action-neutral: var(--color-palette-neutral-light-200); /** The neutral action fill. Button variant=\"secondary\" resolves here. */\n --color-action-neutral-hover: var(--color-palette-neutral-light-100); /** Hover for action-neutral. */\n --color-action-neutral-pressed: var(--color-white); /** Pressed for action-neutral. */\n --color-action-disabled: var(--color-palette-neutral-dark-800); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */\n --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */\n --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */\n --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent \"this one is chosen\" tint. */\n --color-viz-1: var(--color-palette-primary-500); /** Categorical series 1. */\n --color-viz-2: var(--color-palette-accent-500); /** Categorical series 2. */\n --color-viz-3: var(--color-palette-warning-400); /** Categorical series 3. */\n --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */\n --color-viz-5: var(--color-palette-success-300); /** Categorical series 5. */\n --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */\n --color-viz-7: var(--color-palette-neutral-dark-500); /** Categorical series 7. */\n --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */\n --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */\n --color-viz-seq-2: var(--color-palette-primary-500); /** Sequential step 2, low to high. */\n --color-viz-seq-3: var(--color-palette-primary-400); /** Sequential step 3, low to high. */\n --color-viz-seq-4: var(--color-palette-primary-300); /** Sequential step 4, low to high. */\n --color-shadow-key: var(--color-black-a32); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */\n --color-shadow-ambient: var(--color-black-a0); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */\n --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */\n --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-900) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%);\n --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%);\n }\n}\n"; diff --git a/packages/gamut-styles/src/tokens/generated/platform/dark.tokens.json b/packages/gamut-styles/src/tokens/generated/platform/dark.tokens.json new file mode 100644 index 0000000000..849ec9d72c --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/platform/dark.tokens.json @@ -0,0 +1,315 @@ +{ + "$description": "dark mode roles for \"platform\". Do not edit.", + "surface-sunken": { + "$type": "color", + "$value": "{black}", + "$description": "Wells, insets, code blocks — anything recessed into the page." + }, + "surface-default": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "The page background — the base every other surface elevates from." + }, + "surface-raised": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits." + }, + "surface-success": { + "$type": "color", + "$value": "{palette.success.1000}", + "$description": "The success tint behind an alert or inline confirmation." + }, + "surface-warning": { + "$type": "color", + "$value": "{palette.warning.1000}", + "$description": "The warning tint behind an alert or inline caution." + }, + "surface-error": { + "$type": "color", + "$value": "{palette.danger.1000}", + "$description": "The error tint behind an alert or inline validation message." + }, + "surface-brand": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.accent-alt.900}", + "radius": 0.85, + "at": "100% -5%" + }, + { + "color": "{palette.primary.900}", + "radius": 1.21, + "at": "5% 100%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.3346 0.1696 287.1 / 0.45) 0%, oklch(0.3346 0.1696 287.1 / 0.43) 16%, oklch(0.3346 0.1696 287.1 / 0.24) 36%, oklch(0.3346 0.1696 287.1 / 0.11) 56%, oklch(0.3346 0.1696 287.1 / 0.04) 76%, oklch(0.3346 0.1696 287.1 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.3346 0.1863 274.1 / 0.45) 0%, oklch(0.3346 0.1863 274.1 / 0.43) 16%, oklch(0.3346 0.1863 274.1 / 0.24) 36%, oklch(0.3346 0.1863 274.1 / 0.11) 56%, oklch(0.3346 0.1863 274.1 / 0.04) 76%, oklch(0.3346 0.1863 274.1 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-900) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%)" + } + } + } + }, + "surface-brand-subtle": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.primary.900}", + "radius": 0.78, + "at": "50% -10%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.3346 0.1863 274.1 / 0.45) 0%, oklch(0.3346 0.1863 274.1 / 0.43) 16%, oklch(0.3346 0.1863 274.1 / 0.24) 36%, oklch(0.3346 0.1863 274.1 / 0.11) 56%, oklch(0.3346 0.1863 274.1 / 0.04) 76%, oklch(0.3346 0.1863 274.1 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%)" + } + } + } + }, + "text-default": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "Body copy and headings." + }, + "text-subtle": { + "$type": "color", + "$value": "{palette.neutral-light.400}", + "$description": "Secondary text — descriptions, metadata, helper copy." + }, + "text-inverse": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "Labels on a filled control — action-primary, -neutral, -danger." + }, + "text-success": { + "$type": "color", + "$value": "{palette.success.300}", + "$description": "Success text and icons." + }, + "text-warning": { + "$type": "color", + "$value": "{palette.warning.300}", + "$description": "Warning text and icons. Always with an icon." + }, + "text-error": { + "$type": "color", + "$value": "{palette.danger.300}", + "$description": "Error text and icons." + }, + "border-strong": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "Emphasis — an active field, a selected row." + }, + "border-default": { + "$type": "color", + "$value": "{palette.neutral-light.600}", + "$description": "Any border that carries meaning — control outlines, field borders." + }, + "border-subtle": { + "$type": "color", + "$value": "{palette.neutral-light.900}", + "$description": "Decorative hairlines — table rules, dividers. Contrast-exempt." + }, + "action-primary": { + "$type": "color", + "$value": "{palette.accent.300}", + "$description": "Primary interactive fills, strokes, links and focus rings." + }, + "action-primary-hover": { + "$type": "color", + "$value": "{palette.accent.400}", + "$description": "Hover for action-primary." + }, + "action-primary-pressed": { + "$type": "color", + "$value": "{palette.accent.500}", + "$description": "Pressed for action-primary." + }, + "action-danger": { + "$type": "color", + "$value": "{palette.danger.400}", + "$description": "Destructive actions — delete, remove, revoke." + }, + "action-danger-hover": { + "$type": "color", + "$value": "{palette.danger.300}", + "$description": "Hover for action-danger." + }, + "action-danger-pressed": { + "$type": "color", + "$value": "{palette.danger.200}", + "$description": "Pressed for action-danger." + }, + "action-neutral": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "The neutral action fill. Button variant=\"secondary\" resolves here." + }, + "action-neutral-hover": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "Hover for action-neutral." + }, + "action-neutral-pressed": { + "$type": "color", + "$value": "{white}", + "$description": "Pressed for action-neutral." + }, + "action-disabled": { + "$type": "color", + "$value": "{palette.neutral-dark.800}", + "$description": "The disabled fill for every action variant. Contrast-exempt (inactive control)." + }, + "state-hover": { + "$type": "color", + "$value": "{palette.primary.600-a6}", + "$description": "Rest → hover on rows, tabs, chips, menu items." + }, + "state-pressed": { + "$type": "color", + "$value": "{palette.primary.600-a9}", + "$description": "Hover → pressed on the same elements." + }, + "state-selected": { + "$type": "color", + "$value": "{palette.primary.600-a8}", + "$description": "The persistent \"this one is chosen\" tint." + }, + "viz-1": { + "$type": "color", + "$value": "{palette.primary.500}", + "$description": "Categorical series 1." + }, + "viz-2": { + "$type": "color", + "$value": "{palette.accent.500}", + "$description": "Categorical series 2." + }, + "viz-3": { + "$type": "color", + "$value": "{palette.warning.400}", + "$description": "Categorical series 3." + }, + "viz-4": { + "$type": "color", + "$value": "{palette.accent-alt.600}", + "$description": "Categorical series 4." + }, + "viz-5": { + "$type": "color", + "$value": "{palette.success.300}", + "$description": "Categorical series 5." + }, + "viz-6": { + "$type": "color", + "$value": "{palette.danger.600}", + "$description": "Categorical series 6." + }, + "viz-7": { + "$type": "color", + "$value": "{palette.neutral-dark.500}", + "$description": "Categorical series 7." + }, + "viz-8": { + "$type": "color", + "$value": "{palette.neutral-light.600}", + "$description": "Categorical series 8." + }, + "viz-seq-1": { + "$type": "color", + "$value": "{palette.primary.600}", + "$description": "Sequential step 1, low to high." + }, + "viz-seq-2": { + "$type": "color", + "$value": "{palette.primary.500}", + "$description": "Sequential step 2, low to high." + }, + "viz-seq-3": { + "$type": "color", + "$value": "{palette.primary.400}", + "$description": "Sequential step 3, low to high." + }, + "viz-seq-4": { + "$type": "color", + "$value": "{palette.primary.300}", + "$description": "Sequential step 4, low to high." + }, + "shadow-key": { + "$type": "color", + "$value": "{black-a32}", + "$description": "The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur." + }, + "shadow-ambient": { + "$type": "color", + "$value": "{black-a24}", + "$description": "The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff." + }, + "shadow-scrim": { + "$type": "color", + "$value": "{black-a60}", + "$description": "The scrim behind modals and dialogs. Mode-independent." + } +} diff --git a/packages/gamut-styles/src/tokens/generated/platform/light.tokens.json b/packages/gamut-styles/src/tokens/generated/platform/light.tokens.json new file mode 100644 index 0000000000..785502d77b --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/platform/light.tokens.json @@ -0,0 +1,315 @@ +{ + "$description": "light mode roles for \"platform\". Do not edit.", + "surface-sunken": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "Wells, insets, code blocks — anything recessed into the page." + }, + "surface-default": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "The page background — the base every other surface elevates from." + }, + "surface-raised": { + "$type": "color", + "$value": "{white}", + "$description": "The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits." + }, + "surface-success": { + "$type": "color", + "$value": "{palette.success.200}", + "$description": "The success tint behind an alert or inline confirmation." + }, + "surface-warning": { + "$type": "color", + "$value": "{palette.warning.200}", + "$description": "The warning tint behind an alert or inline caution." + }, + "surface-error": { + "$type": "color", + "$value": "{palette.danger.200}", + "$description": "The error tint behind an alert or inline validation message." + }, + "surface-brand": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.accent-alt.300}", + "radius": 0.85, + "at": "100% -5%" + }, + { + "color": "{palette.primary.300}", + "radius": 1.21, + "at": "5% 100%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.8497 0.043 287.1 / 0.45) 0%, oklch(0.8497 0.043 287.1 / 0.43) 16%, oklch(0.8497 0.043 287.1 / 0.24) 36%, oklch(0.8497 0.043 287.1 / 0.11) 56%, oklch(0.8497 0.043 287.1 / 0.04) 76%, oklch(0.8497 0.043 287.1 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.8497 0.0408 274.1 / 0.45) 0%, oklch(0.8497 0.0408 274.1 / 0.43) 16%, oklch(0.8497 0.0408 274.1 / 0.24) 36%, oklch(0.8497 0.0408 274.1 / 0.11) 56%, oklch(0.8497 0.0408 274.1 / 0.04) 76%, oklch(0.8497 0.0408 274.1 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-300) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%)" + } + } + } + }, + "surface-brand-subtle": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.primary.300}", + "radius": 0.78, + "at": "50% -10%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.8497 0.0408 274.1 / 0.45) 0%, oklch(0.8497 0.0408 274.1 / 0.43) 16%, oklch(0.8497 0.0408 274.1 / 0.24) 36%, oklch(0.8497 0.0408 274.1 / 0.11) 56%, oklch(0.8497 0.0408 274.1 / 0.04) 76%, oklch(0.8497 0.0408 274.1 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%)" + } + } + } + }, + "text-default": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "Body copy and headings." + }, + "text-subtle": { + "$type": "color", + "$value": "{palette.neutral-dark.800}", + "$description": "Secondary text — descriptions, metadata, helper copy." + }, + "text-inverse": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "Labels on a filled control — action-primary, -neutral, -danger." + }, + "text-success": { + "$type": "color", + "$value": "{palette.success.900}", + "$description": "Success text and icons." + }, + "text-warning": { + "$type": "color", + "$value": "{palette.warning.900}", + "$description": "Warning text and icons. Always with an icon." + }, + "text-error": { + "$type": "color", + "$value": "{palette.danger.900}", + "$description": "Error text and icons." + }, + "border-strong": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "Emphasis — an active field, a selected row." + }, + "border-default": { + "$type": "color", + "$value": "{palette.neutral-dark.600}", + "$description": "Any border that carries meaning — control outlines, field borders." + }, + "border-subtle": { + "$type": "color", + "$value": "{palette.neutral-dark.300}", + "$description": "Decorative hairlines — table rules, dividers. Contrast-exempt." + }, + "action-primary": { + "$type": "color", + "$value": "{palette.primary.800}", + "$description": "Primary interactive fills, strokes, links and focus rings." + }, + "action-primary-hover": { + "$type": "color", + "$value": "{palette.primary.900}", + "$description": "Hover for action-primary." + }, + "action-primary-pressed": { + "$type": "color", + "$value": "{palette.primary.1000}", + "$description": "Pressed for action-primary." + }, + "action-danger": { + "$type": "color", + "$value": "{palette.danger.800}", + "$description": "Destructive actions — delete, remove, revoke." + }, + "action-danger-hover": { + "$type": "color", + "$value": "{palette.danger.900}", + "$description": "Hover for action-danger." + }, + "action-danger-pressed": { + "$type": "color", + "$value": "{palette.danger.1000}", + "$description": "Pressed for action-danger." + }, + "action-neutral": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "The neutral action fill. Button variant=\"secondary\" resolves here." + }, + "action-neutral-hover": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "Hover for action-neutral." + }, + "action-neutral-pressed": { + "$type": "color", + "$value": "{black}", + "$description": "Pressed for action-neutral." + }, + "action-disabled": { + "$type": "color", + "$value": "{palette.neutral-light.400}", + "$description": "The disabled fill for every action variant. Contrast-exempt (inactive control)." + }, + "state-hover": { + "$type": "color", + "$value": "{palette.primary.600-a6}", + "$description": "Rest → hover on rows, tabs, chips, menu items." + }, + "state-pressed": { + "$type": "color", + "$value": "{palette.primary.600-a9}", + "$description": "Hover → pressed on the same elements." + }, + "state-selected": { + "$type": "color", + "$value": "{palette.primary.600-a8}", + "$description": "The persistent \"this one is chosen\" tint." + }, + "viz-1": { + "$type": "color", + "$value": "{palette.primary.800}", + "$description": "Categorical series 1." + }, + "viz-2": { + "$type": "color", + "$value": "{palette.accent.700}", + "$description": "Categorical series 2." + }, + "viz-3": { + "$type": "color", + "$value": "{palette.warning.700}", + "$description": "Categorical series 3." + }, + "viz-4": { + "$type": "color", + "$value": "{palette.accent-alt.600}", + "$description": "Categorical series 4." + }, + "viz-5": { + "$type": "color", + "$value": "{palette.success.600}", + "$description": "Categorical series 5." + }, + "viz-6": { + "$type": "color", + "$value": "{palette.danger.600}", + "$description": "Categorical series 6." + }, + "viz-7": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "Categorical series 7." + }, + "viz-8": { + "$type": "color", + "$value": "{palette.neutral-light.600}", + "$description": "Categorical series 8." + }, + "viz-seq-1": { + "$type": "color", + "$value": "{palette.primary.600}", + "$description": "Sequential step 1, low to high." + }, + "viz-seq-2": { + "$type": "color", + "$value": "{palette.primary.700}", + "$description": "Sequential step 2, low to high." + }, + "viz-seq-3": { + "$type": "color", + "$value": "{palette.primary.800}", + "$description": "Sequential step 3, low to high." + }, + "viz-seq-4": { + "$type": "color", + "$value": "{palette.primary.900}", + "$description": "Sequential step 4, low to high." + }, + "shadow-key": { + "$type": "color", + "$value": "{palette.neutral-dark.1100-a16}", + "$description": "The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur." + }, + "shadow-ambient": { + "$type": "color", + "$value": "{palette.neutral-dark.1100-a12}", + "$description": "The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff." + }, + "shadow-scrim": { + "$type": "color", + "$value": "{black-a60}", + "$description": "The scrim behind modals and dialogs. Mode-independent." + } +} diff --git a/packages/gamut-styles/src/tokens/generated/platform/palette.tokens.json b/packages/gamut-styles/src/tokens/generated/platform/palette.tokens.json new file mode 100644 index 0000000000..14a1e7b378 --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/platform/palette.tokens.json @@ -0,0 +1,1745 @@ +{ + "$description": "Generated from the \"platform\" brand (tier 3). Do not edit.", + "white": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 1, + 0, + 0 + ], + "alpha": 1, + "hex": "#ffffff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "black": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 1, + "hex": "#000000" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "palette": { + "neutral-light": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9867, + 0.0084, + 225.08 + ], + "alpha": 1, + "hex": "#f5fcff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0032, + 225.1 + ], + "alpha": 1, + "hex": "#e8eaeb" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0109, + 225.1 + ], + "alpha": 1, + "hex": "#c7cfd3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0202, + 225.1 + ], + "alpha": 1, + "hex": "#a5b5bc" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.0223, + 225.1 + ], + "alpha": 1, + "hex": "#8899a0" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.0221, + 225.1 + ], + "alpha": 1, + "hex": "#6e7e85" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.0179, + 225.1 + ], + "alpha": 1, + "hex": "#58656a" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.014, + 225.1 + ], + "alpha": 1, + "hex": "#434d51" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0111, + 225.1 + ], + "alpha": 1, + "hex": "#31383b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0079, + 225.1 + ], + "alpha": 1, + "hex": "#1e2224" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0058, + 225.1 + ], + "alpha": 1, + "hex": "#121516" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "neutral-dark": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0012, + 271.3 + ], + "alpha": 1, + "hex": "#fafafb" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0024, + 271.3 + ], + "alpha": 1, + "hex": "#e9eaeb" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0081, + 271.3 + ], + "alpha": 1, + "hex": "#cbcdd3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0169, + 271.3 + ], + "alpha": 1, + "hex": "#aeb2bd" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.0306, + 271.3 + ], + "alpha": 1, + "hex": "#8e95a9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.0454, + 271.3 + ], + "alpha": 1, + "hex": "#717a97" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.0538, + 271.3 + ], + "alpha": 1, + "hex": "#576182" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0479, + 271.3 + ], + "alpha": 1, + "hex": "#414965" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0381, + 271.3 + ], + "alpha": 1, + "hex": "#2f364b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0272, + 271.3 + ], + "alpha": 1, + "hex": "#1d212f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0199, + 271.3 + ], + "alpha": 1, + "hex": "#11141d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100-a16": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0199, + 271.3 + ], + "alpha": 0.16, + "hex": "#11141d" + } + }, + "1100-a12": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0199, + 271.3 + ], + "alpha": 0.12, + "hex": "#11141d" + } + } + }, + "primary": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0064, + 274.1 + ], + "alpha": 1, + "hex": "#f9faff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0121, + 274.1 + ], + "alpha": 1, + "hex": "#e7eaf2" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0408, + 274.1 + ], + "alpha": 1, + "hex": "#c5cce9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0849, + 274.1 + ], + "alpha": 1, + "hex": "#a1aee9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1537, + 274.1 + ], + "alpha": 1, + "hex": "#798bf3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2285, + 274.1 + ], + "alpha": 1, + "hex": "#5962ff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.2707, + 274.1 + ], + "alpha": 1, + "hex": "#4433f7" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.2344, + 274.1 + ], + "alpha": 1, + "hex": "#331fc6" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.1863, + 274.1 + ], + "alpha": 1, + "hex": "#241793" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.1328, + 274.1 + ], + "alpha": 1, + "hex": "#140f5f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0974, + 274.1 + ], + "alpha": 1, + "hex": "#0b093f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "600-a6": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2285, + 274.1 + ], + "alpha": 0.06, + "hex": "#5962ff" + } + }, + "600-a9": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2285, + 274.1 + ], + "alpha": 0.09, + "hex": "#5962ff" + } + }, + "600-a8": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2285, + 274.1 + ], + "alpha": 0.08, + "hex": "#5962ff" + } + } + }, + "accent": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0205, + 93.6 + ], + "alpha": 1, + "hex": "#fffbeb" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0378, + 93.6 + ], + "alpha": 1, + "hex": "#f2eace" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8789, + 0.1801, + 93.58 + ], + "alpha": 1, + "hex": "#ffd300" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7442, + 0.1526, + 93.94 + ], + "alpha": 1, + "hex": "#cca900" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1209, + 93.6 + ], + "alpha": 1, + "hex": "#ae932e" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.1195, + 93.6 + ], + "alpha": 1, + "hex": "#937900" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.0967, + 93.6 + ], + "alpha": 1, + "hex": "#75610e" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0758, + 93.6 + ], + "alpha": 1, + "hex": "#594a10" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0603, + 93.6 + ], + "alpha": 1, + "hex": "#41360b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.043, + 93.6 + ], + "alpha": 1, + "hex": "#282106" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0315, + 93.6 + ], + "alpha": 1, + "hex": "#191404" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "accent-alt": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0067, + 287.1 + ], + "alpha": 1, + "hex": "#fafaff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0127, + 287.1 + ], + "alpha": 1, + "hex": "#e9e9f2" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.043, + 287.1 + ], + "alpha": 1, + "hex": "#cbcae9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0896, + 287.1 + ], + "alpha": 1, + "hex": "#aeaae9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1624, + 287.1 + ], + "alpha": 1, + "hex": "#9182f3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2416, + 287.1 + ], + "alpha": 1, + "hex": "#7a52ff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.2722, + 287.1 + ], + "alpha": 1, + "hex": "#661af0" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.2134, + 287.1 + ], + "alpha": 1, + "hex": "#4d1cb5" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.1696, + 287.1 + ], + "alpha": 1, + "hex": "#381587" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.1209, + 287.1 + ], + "alpha": 1, + "hex": "#220d57" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0887, + 287.1 + ], + "alpha": 1, + "hex": "#150739" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "danger": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0066, + 29.4 + ], + "alpha": 1, + "hex": "#fff9f8" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0128, + 29.4 + ], + "alpha": 1, + "hex": "#f2e7e5" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0452, + 29.4 + ], + "alpha": 1, + "hex": "#eac3bd" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.099, + 29.4 + ], + "alpha": 1, + "hex": "#ea9a8d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.191, + 29.4 + ], + "alpha": 1, + "hex": "#f45b4a" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2382, + 29.4 + ], + "alpha": 1, + "hex": "#e70400" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.1928, + 29.4 + ], + "alpha": 1, + "hex": "#b7160d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.1512, + 29.4 + ], + "alpha": 1, + "hex": "#8a170f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.1202, + 29.4 + ], + "alpha": 1, + "hex": "#67110a" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0857, + 29.4 + ], + "alpha": 1, + "hex": "#420a06" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0628, + 29.4 + ], + "alpha": 1, + "hex": "#2b0603" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "warning": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0082, + 58.3 + ], + "alpha": 1, + "hex": "#fff9f5" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0156, + 58.3 + ], + "alpha": 1, + "hex": "#f2e8e0" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0545, + 58.3 + ], + "alpha": 1, + "hex": "#eac6ab" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.1179, + 58.3 + ], + "alpha": 1, + "hex": "#ea9f63" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1408, + 58.3 + ], + "alpha": 1, + "hex": "#d47d2d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.1392, + 58.3 + ], + "alpha": 1, + "hex": "#b66200" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.1127, + 58.3 + ], + "alpha": 1, + "hex": "#914f0e" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0883, + 58.3 + ], + "alpha": 1, + "hex": "#6d3d10" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0702, + 58.3 + ], + "alpha": 1, + "hex": "#512c0b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.05, + 58.3 + ], + "alpha": 1, + "hex": "#331b06" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0367, + 58.3 + ], + "alpha": 1, + "hex": "#201004" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "success": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0228, + 145.5 + ], + "alpha": 1, + "hex": "#f1fff1" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0458, + 145.5 + ], + "alpha": 1, + "hex": "#d8f3d8" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.1448, + 145.5 + ], + "alpha": 1, + "hex": "#8de792" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.1654, + 145.5 + ], + "alpha": 1, + "hex": "#64cd6d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1829, + 145.5 + ], + "alpha": 1, + "hex": "#34b147" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.1808, + 145.5 + ], + "alpha": 1, + "hex": "#00952b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.1464, + 145.5 + ], + "alpha": 1, + "hex": "#117726" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.1147, + 145.5 + ], + "alpha": 1, + "hex": "#135a1f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0912, + 145.5 + ], + "alpha": 1, + "hex": "#0e4216" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.065, + 145.5 + ], + "alpha": 1, + "hex": "#08290c" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0477, + 145.5 + ], + "alpha": 1, + "hex": "#041a06" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + } + }, + "black-a32": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 0.32, + "hex": "#000000" + } + }, + "black-a24": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 0.24, + "hex": "#000000" + } + }, + "black-a60": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 0.6, + "hex": "#000000" + } + } +} diff --git a/packages/gamut-styles/src/tokens/generated/platform/tokens.css b/packages/gamut-styles/src/tokens/generated/platform/tokens.css new file mode 100644 index 0000000000..012f9f5aa8 --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/platform/tokens.css @@ -0,0 +1,211 @@ +/* Generated by @skillsoft/gamut-brand from the "platform" brand. Do not edit. */ +@layer reset, base, tokens, recipes, utilities; + +@layer tokens { + :root, [data-brand="platform"] { + --color-white: oklch(1.0000 0.0000 0.00); + --color-black: oklch(0.0000 0.0000 0.00); + --color-palette-neutral-light-100: oklch(0.9867 0.0084 225.08); + --color-palette-neutral-light-200: oklch(0.9364 0.0032 225.10); + --color-palette-neutral-light-300: oklch(0.8497 0.0109 225.10); + --color-palette-neutral-light-400: oklch(0.7628 0.0202 225.10); + --color-palette-neutral-light-500: oklch(0.6707 0.0223 225.10); + --color-palette-neutral-light-600: oklch(0.5835 0.0221 225.10); + --color-palette-neutral-light-700: oklch(0.4972 0.0179 225.10); + --color-palette-neutral-light-800: oklch(0.4115 0.0140 225.10); + --color-palette-neutral-light-900: oklch(0.3346 0.0111 225.10); + --color-palette-neutral-light-1000: oklch(0.2499 0.0079 225.10); + --color-palette-neutral-light-1100: oklch(0.1925 0.0058 225.10); + --color-palette-neutral-dark-100: oklch(0.9862 0.0012 271.30); + --color-palette-neutral-dark-200: oklch(0.9364 0.0024 271.30); + --color-palette-neutral-dark-300: oklch(0.8497 0.0081 271.30); + --color-palette-neutral-dark-400: oklch(0.7628 0.0169 271.30); + --color-palette-neutral-dark-500: oklch(0.6707 0.0306 271.30); + --color-palette-neutral-dark-600: oklch(0.5835 0.0454 271.30); + --color-palette-neutral-dark-700: oklch(0.4972 0.0538 271.30); + --color-palette-neutral-dark-800: oklch(0.4115 0.0479 271.30); + --color-palette-neutral-dark-900: oklch(0.3346 0.0381 271.30); + --color-palette-neutral-dark-1000: oklch(0.2499 0.0272 271.30); + --color-palette-neutral-dark-1100: oklch(0.1925 0.0199 271.30); + --color-palette-neutral-dark-1100-a16: oklch(0.1925 0.0199 271.30 / 0.16); + --color-palette-neutral-dark-1100-a12: oklch(0.1925 0.0199 271.30 / 0.12); + --color-palette-primary-100: oklch(0.9862 0.0064 274.10); + --color-palette-primary-200: oklch(0.9364 0.0121 274.10); + --color-palette-primary-300: oklch(0.8497 0.0408 274.10); + --color-palette-primary-400: oklch(0.7628 0.0849 274.10); + --color-palette-primary-500: oklch(0.6707 0.1537 274.10); + --color-palette-primary-600: oklch(0.5835 0.2285 274.10); + --color-palette-primary-700: oklch(0.4972 0.2707 274.10); + --color-palette-primary-800: oklch(0.4115 0.2344 274.10); + --color-palette-primary-900: oklch(0.3346 0.1863 274.10); + --color-palette-primary-1000: oklch(0.2499 0.1328 274.10); + --color-palette-primary-1100: oklch(0.1925 0.0974 274.10); + --color-palette-primary-600-a6: oklch(0.5835 0.2285 274.10 / 0.06); + --color-palette-primary-600-a9: oklch(0.5835 0.2285 274.10 / 0.09); + --color-palette-primary-600-a8: oklch(0.5835 0.2285 274.10 / 0.08); + --color-palette-accent-100: oklch(0.9862 0.0205 93.60); + --color-palette-accent-200: oklch(0.9364 0.0378 93.60); + --color-palette-accent-300: oklch(0.8789 0.1801 93.58); + --color-palette-accent-400: oklch(0.7442 0.1526 93.94); + --color-palette-accent-500: oklch(0.6707 0.1209 93.60); + --color-palette-accent-600: oklch(0.5835 0.1195 93.60); + --color-palette-accent-700: oklch(0.4972 0.0967 93.60); + --color-palette-accent-800: oklch(0.4115 0.0758 93.60); + --color-palette-accent-900: oklch(0.3346 0.0603 93.60); + --color-palette-accent-1000: oklch(0.2499 0.0430 93.60); + --color-palette-accent-1100: oklch(0.1925 0.0315 93.60); + --color-palette-accent-alt-100: oklch(0.9862 0.0067 287.10); + --color-palette-accent-alt-200: oklch(0.9364 0.0127 287.10); + --color-palette-accent-alt-300: oklch(0.8497 0.0430 287.10); + --color-palette-accent-alt-400: oklch(0.7628 0.0896 287.10); + --color-palette-accent-alt-500: oklch(0.6707 0.1624 287.10); + --color-palette-accent-alt-600: oklch(0.5835 0.2416 287.10); + --color-palette-accent-alt-700: oklch(0.4972 0.2722 287.10); + --color-palette-accent-alt-800: oklch(0.4115 0.2134 287.10); + --color-palette-accent-alt-900: oklch(0.3346 0.1696 287.10); + --color-palette-accent-alt-1000: oklch(0.2499 0.1209 287.10); + --color-palette-accent-alt-1100: oklch(0.1925 0.0887 287.10); + --color-palette-danger-100: oklch(0.9862 0.0066 29.40); + --color-palette-danger-200: oklch(0.9364 0.0128 29.40); + --color-palette-danger-300: oklch(0.8497 0.0452 29.40); + --color-palette-danger-400: oklch(0.7628 0.0990 29.40); + --color-palette-danger-500: oklch(0.6707 0.1910 29.40); + --color-palette-danger-600: oklch(0.5835 0.2382 29.40); + --color-palette-danger-700: oklch(0.4972 0.1928 29.40); + --color-palette-danger-800: oklch(0.4115 0.1512 29.40); + --color-palette-danger-900: oklch(0.3346 0.1202 29.40); + --color-palette-danger-1000: oklch(0.2499 0.0857 29.40); + --color-palette-danger-1100: oklch(0.1925 0.0628 29.40); + --color-palette-warning-100: oklch(0.9862 0.0082 58.30); + --color-palette-warning-200: oklch(0.9364 0.0156 58.30); + --color-palette-warning-300: oklch(0.8497 0.0545 58.30); + --color-palette-warning-400: oklch(0.7628 0.1179 58.30); + --color-palette-warning-500: oklch(0.6707 0.1408 58.30); + --color-palette-warning-600: oklch(0.5835 0.1392 58.30); + --color-palette-warning-700: oklch(0.4972 0.1127 58.30); + --color-palette-warning-800: oklch(0.4115 0.0883 58.30); + --color-palette-warning-900: oklch(0.3346 0.0702 58.30); + --color-palette-warning-1000: oklch(0.2499 0.0500 58.30); + --color-palette-warning-1100: oklch(0.1925 0.0367 58.30); + --color-palette-success-100: oklch(0.9862 0.0228 145.50); + --color-palette-success-200: oklch(0.9364 0.0458 145.50); + --color-palette-success-300: oklch(0.8497 0.1448 145.50); + --color-palette-success-400: oklch(0.7628 0.1654 145.50); + --color-palette-success-500: oklch(0.6707 0.1829 145.50); + --color-palette-success-600: oklch(0.5835 0.1808 145.50); + --color-palette-success-700: oklch(0.4972 0.1464 145.50); + --color-palette-success-800: oklch(0.4115 0.1147 145.50); + --color-palette-success-900: oklch(0.3346 0.0912 145.50); + --color-palette-success-1000: oklch(0.2499 0.0650 145.50); + --color-palette-success-1100: oklch(0.1925 0.0477 145.50); + --color-black-a32: oklch(0.0000 0.0000 0.00 / 0.32); + --color-black-a24: oklch(0.0000 0.0000 0.00 / 0.24); + --color-black-a60: oklch(0.0000 0.0000 0.00 / 0.6); + } + + :root, [data-color-mode="dark"] { + color-scheme: dark; + color: var(--color-text-default); + --color-background-current: var(--color-surface-default); + --color-surface-sunken: var(--color-black); /** Wells, insets, code blocks — anything recessed into the page. */ + --color-surface-default: var(--color-palette-neutral-dark-1100); /** The page background — the base every other surface elevates from. */ + --color-surface-raised: var(--color-palette-neutral-dark-1000); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */ + --color-surface-success: var(--color-palette-success-1000); /** The success tint behind an alert or inline confirmation. */ + --color-surface-warning: var(--color-palette-warning-1000); /** The warning tint behind an alert or inline caution. */ + --color-surface-error: var(--color-palette-danger-1000); /** The error tint behind an alert or inline validation message. */ + --color-surface-brand: var(--color-palette-neutral-dark-1100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */ + --color-surface-brand-subtle: var(--color-palette-neutral-dark-1100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */ + --color-text-default: var(--color-palette-neutral-light-200); /** Body copy and headings. */ + --color-text-subtle: var(--color-palette-neutral-light-400); /** Secondary text — descriptions, metadata, helper copy. */ + --color-text-inverse: var(--color-palette-neutral-dark-1000); /** Labels on a filled control — action-primary, -neutral, -danger. */ + --color-text-success: var(--color-palette-success-300); /** Success text and icons. */ + --color-text-warning: var(--color-palette-warning-300); /** Warning text and icons. Always with an icon. */ + --color-text-error: var(--color-palette-danger-300); /** Error text and icons. */ + --color-border-strong: var(--color-palette-neutral-light-100); /** Emphasis — an active field, a selected row. */ + --color-border-default: var(--color-palette-neutral-light-600); /** Any border that carries meaning — control outlines, field borders. */ + --color-border-subtle: var(--color-palette-neutral-light-900); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */ + --color-action-primary: var(--color-palette-accent-300); /** Primary interactive fills, strokes, links and focus rings. */ + --color-action-primary-hover: var(--color-palette-accent-400); /** Hover for action-primary. */ + --color-action-primary-pressed: var(--color-palette-accent-500); /** Pressed for action-primary. */ + --color-action-danger: var(--color-palette-danger-400); /** Destructive actions — delete, remove, revoke. */ + --color-action-danger-hover: var(--color-palette-danger-300); /** Hover for action-danger. */ + --color-action-danger-pressed: var(--color-palette-danger-200); /** Pressed for action-danger. */ + --color-action-neutral: var(--color-palette-neutral-light-200); /** The neutral action fill. Button variant="secondary" resolves here. */ + --color-action-neutral-hover: var(--color-palette-neutral-light-100); /** Hover for action-neutral. */ + --color-action-neutral-pressed: var(--color-white); /** Pressed for action-neutral. */ + --color-action-disabled: var(--color-palette-neutral-dark-800); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */ + --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */ + --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */ + --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent "this one is chosen" tint. */ + --color-viz-1: var(--color-palette-primary-500); /** Categorical series 1. */ + --color-viz-2: var(--color-palette-accent-500); /** Categorical series 2. */ + --color-viz-3: var(--color-palette-warning-400); /** Categorical series 3. */ + --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */ + --color-viz-5: var(--color-palette-success-300); /** Categorical series 5. */ + --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */ + --color-viz-7: var(--color-palette-neutral-dark-500); /** Categorical series 7. */ + --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */ + --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */ + --color-viz-seq-2: var(--color-palette-primary-500); /** Sequential step 2, low to high. */ + --color-viz-seq-3: var(--color-palette-primary-400); /** Sequential step 3, low to high. */ + --color-viz-seq-4: var(--color-palette-primary-300); /** Sequential step 4, low to high. */ + --color-shadow-key: var(--color-black-a32); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */ + --color-shadow-ambient: var(--color-black-a24); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */ + --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */ + --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-900) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%); + --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%); + } + + [data-color-mode="light"] { + color-scheme: light; + color: var(--color-text-default); + --color-background-current: var(--color-surface-default); + --color-surface-sunken: var(--color-palette-neutral-light-200); /** Wells, insets, code blocks — anything recessed into the page. */ + --color-surface-default: var(--color-palette-neutral-light-100); /** The page background — the base every other surface elevates from. */ + --color-surface-raised: var(--color-white); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */ + --color-surface-success: var(--color-palette-success-200); /** The success tint behind an alert or inline confirmation. */ + --color-surface-warning: var(--color-palette-warning-200); /** The warning tint behind an alert or inline caution. */ + --color-surface-error: var(--color-palette-danger-200); /** The error tint behind an alert or inline validation message. */ + --color-surface-brand: var(--color-palette-neutral-light-100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */ + --color-surface-brand-subtle: var(--color-palette-neutral-light-100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */ + --color-text-default: var(--color-palette-neutral-dark-1000); /** Body copy and headings. */ + --color-text-subtle: var(--color-palette-neutral-dark-800); /** Secondary text — descriptions, metadata, helper copy. */ + --color-text-inverse: var(--color-palette-neutral-light-200); /** Labels on a filled control — action-primary, -neutral, -danger. */ + --color-text-success: var(--color-palette-success-900); /** Success text and icons. */ + --color-text-warning: var(--color-palette-warning-900); /** Warning text and icons. Always with an icon. */ + --color-text-error: var(--color-palette-danger-900); /** Error text and icons. */ + --color-border-strong: var(--color-palette-neutral-dark-1100); /** Emphasis — an active field, a selected row. */ + --color-border-default: var(--color-palette-neutral-dark-600); /** Any border that carries meaning — control outlines, field borders. */ + --color-border-subtle: var(--color-palette-neutral-dark-300); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */ + --color-action-primary: var(--color-palette-primary-800); /** Primary interactive fills, strokes, links and focus rings. */ + --color-action-primary-hover: var(--color-palette-primary-900); /** Hover for action-primary. */ + --color-action-primary-pressed: var(--color-palette-primary-1000); /** Pressed for action-primary. */ + --color-action-danger: var(--color-palette-danger-800); /** Destructive actions — delete, remove, revoke. */ + --color-action-danger-hover: var(--color-palette-danger-900); /** Hover for action-danger. */ + --color-action-danger-pressed: var(--color-palette-danger-1000); /** Pressed for action-danger. */ + --color-action-neutral: var(--color-palette-neutral-dark-1000); /** The neutral action fill. Button variant="secondary" resolves here. */ + --color-action-neutral-hover: var(--color-palette-neutral-dark-1100); /** Hover for action-neutral. */ + --color-action-neutral-pressed: var(--color-black); /** Pressed for action-neutral. */ + --color-action-disabled: var(--color-palette-neutral-light-400); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */ + --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */ + --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */ + --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent "this one is chosen" tint. */ + --color-viz-1: var(--color-palette-primary-800); /** Categorical series 1. */ + --color-viz-2: var(--color-palette-accent-700); /** Categorical series 2. */ + --color-viz-3: var(--color-palette-warning-700); /** Categorical series 3. */ + --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */ + --color-viz-5: var(--color-palette-success-600); /** Categorical series 5. */ + --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */ + --color-viz-7: var(--color-palette-neutral-dark-1000); /** Categorical series 7. */ + --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */ + --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */ + --color-viz-seq-2: var(--color-palette-primary-700); /** Sequential step 2, low to high. */ + --color-viz-seq-3: var(--color-palette-primary-800); /** Sequential step 3, low to high. */ + --color-viz-seq-4: var(--color-palette-primary-900); /** Sequential step 4, low to high. */ + --color-shadow-key: var(--color-palette-neutral-dark-1100-a16); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */ + --color-shadow-ambient: var(--color-palette-neutral-dark-1100-a12); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */ + --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */ + --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-300) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%); + --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%); + } +} diff --git a/packages/gamut-styles/src/tokens/generated/platform/values.ts b/packages/gamut-styles/src/tokens/generated/platform/values.ts new file mode 100644 index 0000000000..2760de5631 --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/platform/values.ts @@ -0,0 +1,1045 @@ +/* Generated by @skillsoft/gamut-brand from the "platform" brand. Do not edit. */ + +export const brand = { + "name": "platform", + "initialMode": "dark", + "hues": { + "neutral-light": 225.1, + "neutral-dark": 271.3, + "primary": 274.1, + "accent": 93.6, + "danger": 29.4, + "warning": 58.3, + "success": 145.5, + "accent-alt": 287.1 + }, + "derived": { + "accent-alt": "held at the gamut default" + }, + "tier": 3, + "overrides": { + "palette": { + "accent-300": "#ffd300", + "accent-400": "#cca900", + "neutral-light-100": "#f5fcff" + }, + "roles": { + "dark": { + "action-primary": { + "slot": "accent", + "grade": 300 + }, + "action-primary-hover": { + "slot": "accent", + "grade": 400 + }, + "action-primary-pressed": { + "slot": "accent", + "grade": 500 + } + } + }, + "gradients": { + "surface-brand": { + "accent": "accent-alt" + } + } + }, + "sources": { + "neutral-light": { + "hex": "#f5fcff", + "nearest": 100, + "deltaE": 0 + }, + "neutral-dark": { + "hex": "#10162f", + "nearest": 1000, + "deltaE": 6.56 + }, + "primary": { + "hex": "#3a10e5", + "nearest": 800, + "deltaE": 3.82 + }, + "accent": { + "hex": "#ffd300", + "nearest": 300, + "deltaE": 0 + }, + "danger": { + "hex": "#e91c11", + "nearest": 600, + "deltaE": 1.79 + }, + "warning": { + "hex": "#ff8c00", + "nearest": 500, + "deltaE": 8.94 + }, + "success": { + "hex": "#008a27", + "nearest": 600, + "deltaE": 3.86 + } + } +} as const; + +export const palette = { + "white": { + "oklch": "oklch(1 0 0)", + "srgb": "#ffffff" + }, + "black": { + "oklch": "oklch(0 0 0)", + "srgb": "#000000" + }, + "palette-neutral-light-100": { + "oklch": "oklch(0.9867 0.0084 225.08)", + "srgb": "#f5fcff" + }, + "palette-neutral-light-200": { + "oklch": "oklch(0.9364 0.0032 225.1)", + "srgb": "#e8eaeb" + }, + "palette-neutral-light-300": { + "oklch": "oklch(0.8497 0.0109 225.1)", + "srgb": "#c7cfd3" + }, + "palette-neutral-light-400": { + "oklch": "oklch(0.7628 0.0202 225.1)", + "srgb": "#a5b5bc" + }, + "palette-neutral-light-500": { + "oklch": "oklch(0.6707 0.0223 225.1)", + "srgb": "#8899a0" + }, + "palette-neutral-light-600": { + "oklch": "oklch(0.5835 0.0221 225.1)", + "srgb": "#6e7e85" + }, + "palette-neutral-light-700": { + "oklch": "oklch(0.4972 0.0179 225.1)", + "srgb": "#58656a" + }, + "palette-neutral-light-800": { + "oklch": "oklch(0.4115 0.014 225.1)", + "srgb": "#434d51" + }, + "palette-neutral-light-900": { + "oklch": "oklch(0.3346 0.0111 225.1)", + "srgb": "#31383b" + }, + "palette-neutral-light-1000": { + "oklch": "oklch(0.2499 0.0079 225.1)", + "srgb": "#1e2224" + }, + "palette-neutral-light-1100": { + "oklch": "oklch(0.1925 0.0058 225.1)", + "srgb": "#121516" + }, + "palette-neutral-dark-100": { + "oklch": "oklch(0.9862 0.0012 271.3)", + "srgb": "#fafafb" + }, + "palette-neutral-dark-200": { + "oklch": "oklch(0.9364 0.0024 271.3)", + "srgb": "#e9eaeb" + }, + "palette-neutral-dark-300": { + "oklch": "oklch(0.8497 0.0081 271.3)", + "srgb": "#cbcdd3" + }, + "palette-neutral-dark-400": { + "oklch": "oklch(0.7628 0.0169 271.3)", + "srgb": "#aeb2bd" + }, + "palette-neutral-dark-500": { + "oklch": "oklch(0.6707 0.0306 271.3)", + "srgb": "#8e95a9" + }, + "palette-neutral-dark-600": { + "oklch": "oklch(0.5835 0.0454 271.3)", + "srgb": "#717a97" + }, + "palette-neutral-dark-700": { + "oklch": "oklch(0.4972 0.0538 271.3)", + "srgb": "#576182" + }, + "palette-neutral-dark-800": { + "oklch": "oklch(0.4115 0.0479 271.3)", + "srgb": "#414965" + }, + "palette-neutral-dark-900": { + "oklch": "oklch(0.3346 0.0381 271.3)", + "srgb": "#2f364b" + }, + "palette-neutral-dark-1000": { + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "palette-neutral-dark-1100": { + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "palette-primary-100": { + "oklch": "oklch(0.9862 0.0064 274.1)", + "srgb": "#f9faff" + }, + "palette-primary-200": { + "oklch": "oklch(0.9364 0.0121 274.1)", + "srgb": "#e7eaf2" + }, + "palette-primary-300": { + "oklch": "oklch(0.8497 0.0408 274.1)", + "srgb": "#c5cce9" + }, + "palette-primary-400": { + "oklch": "oklch(0.7628 0.0849 274.1)", + "srgb": "#a1aee9" + }, + "palette-primary-500": { + "oklch": "oklch(0.6707 0.1537 274.1)", + "srgb": "#798bf3" + }, + "palette-primary-600": { + "oklch": "oklch(0.5835 0.2285 274.1)", + "srgb": "#5962ff" + }, + "palette-primary-700": { + "oklch": "oklch(0.4972 0.2707 274.1)", + "srgb": "#4433f7" + }, + "palette-primary-800": { + "oklch": "oklch(0.4115 0.2344 274.1)", + "srgb": "#331fc6" + }, + "palette-primary-900": { + "oklch": "oklch(0.3346 0.1863 274.1)", + "srgb": "#241793" + }, + "palette-primary-1000": { + "oklch": "oklch(0.2499 0.1328 274.1)", + "srgb": "#140f5f" + }, + "palette-primary-1100": { + "oklch": "oklch(0.1925 0.0974 274.1)", + "srgb": "#0b093f" + }, + "palette-accent-100": { + "oklch": "oklch(0.9862 0.0205 93.6)", + "srgb": "#fffbeb" + }, + "palette-accent-200": { + "oklch": "oklch(0.9364 0.0378 93.6)", + "srgb": "#f2eace" + }, + "palette-accent-300": { + "oklch": "oklch(0.8789 0.1801 93.58)", + "srgb": "#ffd300" + }, + "palette-accent-400": { + "oklch": "oklch(0.7442 0.1526 93.94)", + "srgb": "#cca900" + }, + "palette-accent-500": { + "oklch": "oklch(0.6707 0.1209 93.6)", + "srgb": "#ae932e" + }, + "palette-accent-600": { + "oklch": "oklch(0.5835 0.1195 93.6)", + "srgb": "#937900" + }, + "palette-accent-700": { + "oklch": "oklch(0.4972 0.0967 93.6)", + "srgb": "#75610e" + }, + "palette-accent-800": { + "oklch": "oklch(0.4115 0.0758 93.6)", + "srgb": "#594a10" + }, + "palette-accent-900": { + "oklch": "oklch(0.3346 0.0603 93.6)", + "srgb": "#41360b" + }, + "palette-accent-1000": { + "oklch": "oklch(0.2499 0.043 93.6)", + "srgb": "#282106" + }, + "palette-accent-1100": { + "oklch": "oklch(0.1925 0.0315 93.6)", + "srgb": "#191404" + }, + "palette-accent-alt-100": { + "oklch": "oklch(0.9862 0.0067 287.1)", + "srgb": "#fafaff" + }, + "palette-accent-alt-200": { + "oklch": "oklch(0.9364 0.0127 287.1)", + "srgb": "#e9e9f2" + }, + "palette-accent-alt-300": { + "oklch": "oklch(0.8497 0.043 287.1)", + "srgb": "#cbcae9" + }, + "palette-accent-alt-400": { + "oklch": "oklch(0.7628 0.0896 287.1)", + "srgb": "#aeaae9" + }, + "palette-accent-alt-500": { + "oklch": "oklch(0.6707 0.1624 287.1)", + "srgb": "#9182f3" + }, + "palette-accent-alt-600": { + "oklch": "oklch(0.5835 0.2416 287.1)", + "srgb": "#7a52ff" + }, + "palette-accent-alt-700": { + "oklch": "oklch(0.4972 0.2722 287.1)", + "srgb": "#661af0" + }, + "palette-accent-alt-800": { + "oklch": "oklch(0.4115 0.2134 287.1)", + "srgb": "#4d1cb5" + }, + "palette-accent-alt-900": { + "oklch": "oklch(0.3346 0.1696 287.1)", + "srgb": "#381587" + }, + "palette-accent-alt-1000": { + "oklch": "oklch(0.2499 0.1209 287.1)", + "srgb": "#220d57" + }, + "palette-accent-alt-1100": { + "oklch": "oklch(0.1925 0.0887 287.1)", + "srgb": "#150739" + }, + "palette-danger-100": { + "oklch": "oklch(0.9862 0.0066 29.4)", + "srgb": "#fff9f8" + }, + "palette-danger-200": { + "oklch": "oklch(0.9364 0.0128 29.4)", + "srgb": "#f2e7e5" + }, + "palette-danger-300": { + "oklch": "oklch(0.8497 0.0452 29.4)", + "srgb": "#eac3bd" + }, + "palette-danger-400": { + "oklch": "oklch(0.7628 0.099 29.4)", + "srgb": "#ea9a8d" + }, + "palette-danger-500": { + "oklch": "oklch(0.6707 0.191 29.4)", + "srgb": "#f45b4a" + }, + "palette-danger-600": { + "oklch": "oklch(0.5835 0.2382 29.4)", + "srgb": "#e70400" + }, + "palette-danger-700": { + "oklch": "oklch(0.4972 0.1928 29.4)", + "srgb": "#b7160d" + }, + "palette-danger-800": { + "oklch": "oklch(0.4115 0.1512 29.4)", + "srgb": "#8a170f" + }, + "palette-danger-900": { + "oklch": "oklch(0.3346 0.1202 29.4)", + "srgb": "#67110a" + }, + "palette-danger-1000": { + "oklch": "oklch(0.2499 0.0857 29.4)", + "srgb": "#420a06" + }, + "palette-danger-1100": { + "oklch": "oklch(0.1925 0.0628 29.4)", + "srgb": "#2b0603" + }, + "palette-warning-100": { + "oklch": "oklch(0.9862 0.0082 58.3)", + "srgb": "#fff9f5" + }, + "palette-warning-200": { + "oklch": "oklch(0.9364 0.0156 58.3)", + "srgb": "#f2e8e0" + }, + "palette-warning-300": { + "oklch": "oklch(0.8497 0.0545 58.3)", + "srgb": "#eac6ab" + }, + "palette-warning-400": { + "oklch": "oklch(0.7628 0.1179 58.3)", + "srgb": "#ea9f63" + }, + "palette-warning-500": { + "oklch": "oklch(0.6707 0.1408 58.3)", + "srgb": "#d47d2d" + }, + "palette-warning-600": { + "oklch": "oklch(0.5835 0.1392 58.3)", + "srgb": "#b66200" + }, + "palette-warning-700": { + "oklch": "oklch(0.4972 0.1127 58.3)", + "srgb": "#914f0e" + }, + "palette-warning-800": { + "oklch": "oklch(0.4115 0.0883 58.3)", + "srgb": "#6d3d10" + }, + "palette-warning-900": { + "oklch": "oklch(0.3346 0.0702 58.3)", + "srgb": "#512c0b" + }, + "palette-warning-1000": { + "oklch": "oklch(0.2499 0.05 58.3)", + "srgb": "#331b06" + }, + "palette-warning-1100": { + "oklch": "oklch(0.1925 0.0367 58.3)", + "srgb": "#201004" + }, + "palette-success-100": { + "oklch": "oklch(0.9862 0.0228 145.5)", + "srgb": "#f1fff1" + }, + "palette-success-200": { + "oklch": "oklch(0.9364 0.0458 145.5)", + "srgb": "#d8f3d8" + }, + "palette-success-300": { + "oklch": "oklch(0.8497 0.1448 145.5)", + "srgb": "#8de792" + }, + "palette-success-400": { + "oklch": "oklch(0.7628 0.1654 145.5)", + "srgb": "#64cd6d" + }, + "palette-success-500": { + "oklch": "oklch(0.6707 0.1829 145.5)", + "srgb": "#34b147" + }, + "palette-success-600": { + "oklch": "oklch(0.5835 0.1808 145.5)", + "srgb": "#00952b" + }, + "palette-success-700": { + "oklch": "oklch(0.4972 0.1464 145.5)", + "srgb": "#117726" + }, + "palette-success-800": { + "oklch": "oklch(0.4115 0.1147 145.5)", + "srgb": "#135a1f" + }, + "palette-success-900": { + "oklch": "oklch(0.3346 0.0912 145.5)", + "srgb": "#0e4216" + }, + "palette-success-1000": { + "oklch": "oklch(0.2499 0.065 145.5)", + "srgb": "#08290c" + }, + "palette-success-1100": { + "oklch": "oklch(0.1925 0.0477 145.5)", + "srgb": "#041a06" + }, + "palette-primary-600-a6": { + "oklch": "oklch(0.5835 0.2285 274.1 / 0.06)", + "srgb": "rgba(89, 98, 255, 0.06)" + }, + "palette-primary-600-a9": { + "oklch": "oklch(0.5835 0.2285 274.1 / 0.09)", + "srgb": "rgba(89, 98, 255, 0.09)" + }, + "palette-primary-600-a8": { + "oklch": "oklch(0.5835 0.2285 274.1 / 0.08)", + "srgb": "rgba(89, 98, 255, 0.08)" + }, + "palette-neutral-dark-1100-a16": { + "oklch": "oklch(0.1925 0.0199 271.3 / 0.16)", + "srgb": "rgba(17, 20, 29, 0.16)" + }, + "black-a32": { + "oklch": "oklch(0 0 0 / 0.32)", + "srgb": "rgba(0, 0, 0, 0.32)" + }, + "palette-neutral-dark-1100-a12": { + "oklch": "oklch(0.1925 0.0199 271.3 / 0.12)", + "srgb": "rgba(17, 20, 29, 0.12)" + }, + "black-a24": { + "oklch": "oklch(0 0 0 / 0.24)", + "srgb": "rgba(0, 0, 0, 0.24)" + }, + "black-a60": { + "oklch": "oklch(0 0 0 / 0.6)", + "srgb": "rgba(0, 0, 0, 0.6)" + } +} as const; + +export const roles = { + "light": { + "surface-sunken": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0032 225.1)", + "srgb": "#e8eaeb" + }, + "surface-default": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9867 0.0084 225.08)", + "srgb": "#f5fcff" + }, + "surface-raised": { + "ref": "white", + "oklch": "oklch(1 0 0)", + "srgb": "#ffffff" + }, + "surface-success": { + "ref": "palette-success-200", + "oklch": "oklch(0.9364 0.0458 145.5)", + "srgb": "#d8f3d8" + }, + "surface-warning": { + "ref": "palette-warning-200", + "oklch": "oklch(0.9364 0.0156 58.3)", + "srgb": "#f2e8e0" + }, + "surface-error": { + "ref": "palette-danger-200", + "oklch": "oklch(0.9364 0.0128 29.4)", + "srgb": "#f2e7e5" + }, + "surface-brand": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9867 0.0084 225.08)", + "srgb": "#f5fcff" + }, + "surface-brand-subtle": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9867 0.0084 225.08)", + "srgb": "#f5fcff" + }, + "text-default": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "text-subtle": { + "ref": "palette-neutral-dark-800", + "oklch": "oklch(0.4115 0.0479 271.3)", + "srgb": "#414965" + }, + "text-inverse": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0032 225.1)", + "srgb": "#e8eaeb" + }, + "text-success": { + "ref": "palette-success-900", + "oklch": "oklch(0.3346 0.0912 145.5)", + "srgb": "#0e4216" + }, + "text-warning": { + "ref": "palette-warning-900", + "oklch": "oklch(0.3346 0.0702 58.3)", + "srgb": "#512c0b" + }, + "text-error": { + "ref": "palette-danger-900", + "oklch": "oklch(0.3346 0.1202 29.4)", + "srgb": "#67110a" + }, + "border-strong": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "border-default": { + "ref": "palette-neutral-dark-600", + "oklch": "oklch(0.5835 0.0454 271.3)", + "srgb": "#717a97" + }, + "border-subtle": { + "ref": "palette-neutral-dark-300", + "oklch": "oklch(0.8497 0.0081 271.3)", + "srgb": "#cbcdd3" + }, + "action-primary": { + "ref": "palette-primary-800", + "oklch": "oklch(0.4115 0.2344 274.1)", + "srgb": "#331fc6" + }, + "action-primary-hover": { + "ref": "palette-primary-900", + "oklch": "oklch(0.3346 0.1863 274.1)", + "srgb": "#241793" + }, + "action-primary-pressed": { + "ref": "palette-primary-1000", + "oklch": "oklch(0.2499 0.1328 274.1)", + "srgb": "#140f5f" + }, + "action-danger": { + "ref": "palette-danger-800", + "oklch": "oklch(0.4115 0.1512 29.4)", + "srgb": "#8a170f" + }, + "action-danger-hover": { + "ref": "palette-danger-900", + "oklch": "oklch(0.3346 0.1202 29.4)", + "srgb": "#67110a" + }, + "action-danger-pressed": { + "ref": "palette-danger-1000", + "oklch": "oklch(0.2499 0.0857 29.4)", + "srgb": "#420a06" + }, + "action-neutral": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "action-neutral-hover": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "action-neutral-pressed": { + "ref": "black", + "oklch": "oklch(0 0 0)", + "srgb": "#000000" + }, + "action-disabled": { + "ref": "palette-neutral-light-400", + "oklch": "oklch(0.7628 0.0202 225.1)", + "srgb": "#a5b5bc" + }, + "state-hover": { + "ref": "palette-primary-600-a6", + "oklch": "oklch(0.5835 0.2285 274.1 / 0.06)", + "srgb": "rgba(89, 98, 255, 0.06)" + }, + "state-pressed": { + "ref": "palette-primary-600-a9", + "oklch": "oklch(0.5835 0.2285 274.1 / 0.09)", + "srgb": "rgba(89, 98, 255, 0.09)" + }, + "state-selected": { + "ref": "palette-primary-600-a8", + "oklch": "oklch(0.5835 0.2285 274.1 / 0.08)", + "srgb": "rgba(89, 98, 255, 0.08)" + }, + "viz-1": { + "ref": "palette-primary-800", + "oklch": "oklch(0.4115 0.2344 274.1)", + "srgb": "#331fc6" + }, + "viz-2": { + "ref": "palette-accent-700", + "oklch": "oklch(0.4972 0.0967 93.6)", + "srgb": "#75610e" + }, + "viz-3": { + "ref": "palette-warning-700", + "oklch": "oklch(0.4972 0.1127 58.3)", + "srgb": "#914f0e" + }, + "viz-4": { + "ref": "palette-accent-alt-600", + "oklch": "oklch(0.5835 0.2416 287.1)", + "srgb": "#7a52ff" + }, + "viz-5": { + "ref": "palette-success-600", + "oklch": "oklch(0.5835 0.1808 145.5)", + "srgb": "#00952b" + }, + "viz-6": { + "ref": "palette-danger-600", + "oklch": "oklch(0.5835 0.2382 29.4)", + "srgb": "#e70400" + }, + "viz-7": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "viz-8": { + "ref": "palette-neutral-light-600", + "oklch": "oklch(0.5835 0.0221 225.1)", + "srgb": "#6e7e85" + }, + "viz-seq-1": { + "ref": "palette-primary-600", + "oklch": "oklch(0.5835 0.2285 274.1)", + "srgb": "#5962ff" + }, + "viz-seq-2": { + "ref": "palette-primary-700", + "oklch": "oklch(0.4972 0.2707 274.1)", + "srgb": "#4433f7" + }, + "viz-seq-3": { + "ref": "palette-primary-800", + "oklch": "oklch(0.4115 0.2344 274.1)", + "srgb": "#331fc6" + }, + "viz-seq-4": { + "ref": "palette-primary-900", + "oklch": "oklch(0.3346 0.1863 274.1)", + "srgb": "#241793" + }, + "shadow-key": { + "ref": "palette-neutral-dark-1100-a16", + "oklch": "oklch(0.1925 0.0199 271.3 / 0.16)", + "srgb": "rgba(17, 20, 29, 0.16)" + }, + "shadow-ambient": { + "ref": "palette-neutral-dark-1100-a12", + "oklch": "oklch(0.1925 0.0199 271.3 / 0.12)", + "srgb": "rgba(17, 20, 29, 0.12)" + }, + "shadow-scrim": { + "ref": "black-a60", + "oklch": "oklch(0 0 0 / 0.6)", + "srgb": "rgba(0, 0, 0, 0.6)" + } + }, + "dark": { + "surface-sunken": { + "ref": "black", + "oklch": "oklch(0 0 0)", + "srgb": "#000000" + }, + "surface-default": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "surface-raised": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "surface-success": { + "ref": "palette-success-1000", + "oklch": "oklch(0.2499 0.065 145.5)", + "srgb": "#08290c" + }, + "surface-warning": { + "ref": "palette-warning-1000", + "oklch": "oklch(0.2499 0.05 58.3)", + "srgb": "#331b06" + }, + "surface-error": { + "ref": "palette-danger-1000", + "oklch": "oklch(0.2499 0.0857 29.4)", + "srgb": "#420a06" + }, + "surface-brand": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "surface-brand-subtle": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.0199 271.3)", + "srgb": "#11141d" + }, + "text-default": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0032 225.1)", + "srgb": "#e8eaeb" + }, + "text-subtle": { + "ref": "palette-neutral-light-400", + "oklch": "oklch(0.7628 0.0202 225.1)", + "srgb": "#a5b5bc" + }, + "text-inverse": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0272 271.3)", + "srgb": "#1d212f" + }, + "text-success": { + "ref": "palette-success-300", + "oklch": "oklch(0.8497 0.1448 145.5)", + "srgb": "#8de792" + }, + "text-warning": { + "ref": "palette-warning-300", + "oklch": "oklch(0.8497 0.0545 58.3)", + "srgb": "#eac6ab" + }, + "text-error": { + "ref": "palette-danger-300", + "oklch": "oklch(0.8497 0.0452 29.4)", + "srgb": "#eac3bd" + }, + "border-strong": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9867 0.0084 225.08)", + "srgb": "#f5fcff" + }, + "border-default": { + "ref": "palette-neutral-light-600", + "oklch": "oklch(0.5835 0.0221 225.1)", + "srgb": "#6e7e85" + }, + "border-subtle": { + "ref": "palette-neutral-light-900", + "oklch": "oklch(0.3346 0.0111 225.1)", + "srgb": "#31383b" + }, + "action-primary": { + "ref": "palette-accent-300", + "oklch": "oklch(0.8789 0.1801 93.58)", + "srgb": "#ffd300" + }, + "action-primary-hover": { + "ref": "palette-accent-400", + "oklch": "oklch(0.7442 0.1526 93.94)", + "srgb": "#cca900" + }, + "action-primary-pressed": { + "ref": "palette-accent-500", + "oklch": "oklch(0.6707 0.1209 93.6)", + "srgb": "#ae932e" + }, + "action-danger": { + "ref": "palette-danger-400", + "oklch": "oklch(0.7628 0.099 29.4)", + "srgb": "#ea9a8d" + }, + "action-danger-hover": { + "ref": "palette-danger-300", + "oklch": "oklch(0.8497 0.0452 29.4)", + "srgb": "#eac3bd" + }, + "action-danger-pressed": { + "ref": "palette-danger-200", + "oklch": "oklch(0.9364 0.0128 29.4)", + "srgb": "#f2e7e5" + }, + "action-neutral": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0032 225.1)", + "srgb": "#e8eaeb" + }, + "action-neutral-hover": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9867 0.0084 225.08)", + "srgb": "#f5fcff" + }, + "action-neutral-pressed": { + "ref": "white", + "oklch": "oklch(1 0 0)", + "srgb": "#ffffff" + }, + "action-disabled": { + "ref": "palette-neutral-dark-800", + "oklch": "oklch(0.4115 0.0479 271.3)", + "srgb": "#414965" + }, + "state-hover": { + "ref": "palette-primary-600-a6", + "oklch": "oklch(0.5835 0.2285 274.1 / 0.06)", + "srgb": "rgba(89, 98, 255, 0.06)" + }, + "state-pressed": { + "ref": "palette-primary-600-a9", + "oklch": "oklch(0.5835 0.2285 274.1 / 0.09)", + "srgb": "rgba(89, 98, 255, 0.09)" + }, + "state-selected": { + "ref": "palette-primary-600-a8", + "oklch": "oklch(0.5835 0.2285 274.1 / 0.08)", + "srgb": "rgba(89, 98, 255, 0.08)" + }, + "viz-1": { + "ref": "palette-primary-500", + "oklch": "oklch(0.6707 0.1537 274.1)", + "srgb": "#798bf3" + }, + "viz-2": { + "ref": "palette-accent-500", + "oklch": "oklch(0.6707 0.1209 93.6)", + "srgb": "#ae932e" + }, + "viz-3": { + "ref": "palette-warning-400", + "oklch": "oklch(0.7628 0.1179 58.3)", + "srgb": "#ea9f63" + }, + "viz-4": { + "ref": "palette-accent-alt-600", + "oklch": "oklch(0.5835 0.2416 287.1)", + "srgb": "#7a52ff" + }, + "viz-5": { + "ref": "palette-success-300", + "oklch": "oklch(0.8497 0.1448 145.5)", + "srgb": "#8de792" + }, + "viz-6": { + "ref": "palette-danger-600", + "oklch": "oklch(0.5835 0.2382 29.4)", + "srgb": "#e70400" + }, + "viz-7": { + "ref": "palette-neutral-dark-500", + "oklch": "oklch(0.6707 0.0306 271.3)", + "srgb": "#8e95a9" + }, + "viz-8": { + "ref": "palette-neutral-light-600", + "oklch": "oklch(0.5835 0.0221 225.1)", + "srgb": "#6e7e85" + }, + "viz-seq-1": { + "ref": "palette-primary-600", + "oklch": "oklch(0.5835 0.2285 274.1)", + "srgb": "#5962ff" + }, + "viz-seq-2": { + "ref": "palette-primary-500", + "oklch": "oklch(0.6707 0.1537 274.1)", + "srgb": "#798bf3" + }, + "viz-seq-3": { + "ref": "palette-primary-400", + "oklch": "oklch(0.7628 0.0849 274.1)", + "srgb": "#a1aee9" + }, + "viz-seq-4": { + "ref": "palette-primary-300", + "oklch": "oklch(0.8497 0.0408 274.1)", + "srgb": "#c5cce9" + }, + "shadow-key": { + "ref": "black-a32", + "oklch": "oklch(0 0 0 / 0.32)", + "srgb": "rgba(0, 0, 0, 0.32)" + }, + "shadow-ambient": { + "ref": "black-a24", + "oklch": "oklch(0 0 0 / 0.24)", + "srgb": "rgba(0, 0, 0, 0.24)" + }, + "shadow-scrim": { + "ref": "black-a60", + "oklch": "oklch(0 0 0 / 0.6)", + "srgb": "rgba(0, 0, 0, 0.6)" + } + } +} as const; + +export const gradients = { + "light": { + "surface-brand": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.8497 0.043 287.1 / 0.45) 0%, oklch(0.8497 0.043 287.1 / 0.43) 16%, oklch(0.8497 0.043 287.1 / 0.24) 36%, oklch(0.8497 0.043 287.1 / 0.11) 56%, oklch(0.8497 0.043 287.1 / 0.04) 76%, oklch(0.8497 0.043 287.1 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.8497 0.0408 274.1 / 0.45) 0%, oklch(0.8497 0.0408 274.1 / 0.43) 16%, oklch(0.8497 0.0408 274.1 / 0.24) 36%, oklch(0.8497 0.0408 274.1 / 0.11) 56%, oklch(0.8497 0.0408 274.1 / 0.04) 76%, oklch(0.8497 0.0408 274.1 / 0) 100%)", + "surface-brand-subtle": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.8497 0.0408 274.1 / 0.45) 0%, oklch(0.8497 0.0408 274.1 / 0.43) 16%, oklch(0.8497 0.0408 274.1 / 0.24) 36%, oklch(0.8497 0.0408 274.1 / 0.11) 56%, oklch(0.8497 0.0408 274.1 / 0.04) 76%, oklch(0.8497 0.0408 274.1 / 0) 100%)" + }, + "dark": { + "surface-brand": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.3346 0.1696 287.1 / 0.45) 0%, oklch(0.3346 0.1696 287.1 / 0.43) 16%, oklch(0.3346 0.1696 287.1 / 0.24) 36%, oklch(0.3346 0.1696 287.1 / 0.11) 56%, oklch(0.3346 0.1696 287.1 / 0.04) 76%, oklch(0.3346 0.1696 287.1 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.3346 0.1863 274.1 / 0.45) 0%, oklch(0.3346 0.1863 274.1 / 0.43) 16%, oklch(0.3346 0.1863 274.1 / 0.24) 36%, oklch(0.3346 0.1863 274.1 / 0.11) 56%, oklch(0.3346 0.1863 274.1 / 0.04) 76%, oklch(0.3346 0.1863 274.1 / 0) 100%)", + "surface-brand-subtle": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.3346 0.1863 274.1 / 0.45) 0%, oklch(0.3346 0.1863 274.1 / 0.43) 16%, oklch(0.3346 0.1863 274.1 / 0.24) 36%, oklch(0.3346 0.1863 274.1 / 0.11) 56%, oklch(0.3346 0.1863 274.1 / 0.04) 76%, oklch(0.3346 0.1863 274.1 / 0) 100%)" + } +} as const; + +/* Palette color → the mode its content uses. Background reads this instead of measuring. */ +export const contentModes = { + "white": "light", + "black": "dark", + "palette-neutral-light-100": "light", + "palette-neutral-light-200": "light", + "palette-neutral-light-300": "light", + "palette-neutral-light-400": "light", + "palette-neutral-light-500": "light", + "palette-neutral-light-600": "light", + "palette-neutral-light-700": "dark", + "palette-neutral-light-800": "dark", + "palette-neutral-light-900": "dark", + "palette-neutral-light-1000": "dark", + "palette-neutral-light-1100": "dark", + "palette-neutral-dark-100": "light", + "palette-neutral-dark-200": "light", + "palette-neutral-dark-300": "light", + "palette-neutral-dark-400": "light", + "palette-neutral-dark-500": "light", + "palette-neutral-dark-600": "light", + "palette-neutral-dark-700": "dark", + "palette-neutral-dark-800": "dark", + "palette-neutral-dark-900": "dark", + "palette-neutral-dark-1000": "dark", + "palette-neutral-dark-1100": "dark", + "palette-primary-100": "light", + "palette-primary-200": "light", + "palette-primary-300": "light", + "palette-primary-400": "light", + "palette-primary-500": "light", + "palette-primary-600": "dark", + "palette-primary-700": "dark", + "palette-primary-800": "dark", + "palette-primary-900": "dark", + "palette-primary-1000": "dark", + "palette-primary-1100": "dark", + "palette-accent-100": "light", + "palette-accent-200": "light", + "palette-accent-300": "light", + "palette-accent-400": "light", + "palette-accent-500": "light", + "palette-accent-600": "light", + "palette-accent-700": "dark", + "palette-accent-800": "dark", + "palette-accent-900": "dark", + "palette-accent-1000": "dark", + "palette-accent-1100": "dark", + "palette-accent-alt-100": "light", + "palette-accent-alt-200": "light", + "palette-accent-alt-300": "light", + "palette-accent-alt-400": "light", + "palette-accent-alt-500": "light", + "palette-accent-alt-600": "dark", + "palette-accent-alt-700": "dark", + "palette-accent-alt-800": "dark", + "palette-accent-alt-900": "dark", + "palette-accent-alt-1000": "dark", + "palette-accent-alt-1100": "dark", + "palette-danger-100": "light", + "palette-danger-200": "light", + "palette-danger-300": "light", + "palette-danger-400": "light", + "palette-danger-500": "light", + "palette-danger-600": "dark", + "palette-danger-700": "dark", + "palette-danger-800": "dark", + "palette-danger-900": "dark", + "palette-danger-1000": "dark", + "palette-danger-1100": "dark", + "palette-warning-100": "light", + "palette-warning-200": "light", + "palette-warning-300": "light", + "palette-warning-400": "light", + "palette-warning-500": "light", + "palette-warning-600": "dark", + "palette-warning-700": "dark", + "palette-warning-800": "dark", + "palette-warning-900": "dark", + "palette-warning-1000": "dark", + "palette-warning-1100": "dark", + "palette-success-100": "light", + "palette-success-200": "light", + "palette-success-300": "light", + "palette-success-400": "light", + "palette-success-500": "light", + "palette-success-600": "light", + "palette-success-700": "dark", + "palette-success-800": "dark", + "palette-success-900": "dark", + "palette-success-1000": "dark", + "palette-success-1100": "dark" +} as const; + +/* This brand's tokens.css, for GamutProvider to inject. */ +export const tokensCss = "/* Generated by @skillsoft/gamut-brand from the \"platform\" brand. Do not edit. */\n@layer reset, base, tokens, recipes, utilities;\n\n@layer tokens {\n :root, [data-brand=\"platform\"] {\n --color-white: oklch(1.0000 0.0000 0.00);\n --color-black: oklch(0.0000 0.0000 0.00);\n --color-palette-neutral-light-100: oklch(0.9867 0.0084 225.08);\n --color-palette-neutral-light-200: oklch(0.9364 0.0032 225.10);\n --color-palette-neutral-light-300: oklch(0.8497 0.0109 225.10);\n --color-palette-neutral-light-400: oklch(0.7628 0.0202 225.10);\n --color-palette-neutral-light-500: oklch(0.6707 0.0223 225.10);\n --color-palette-neutral-light-600: oklch(0.5835 0.0221 225.10);\n --color-palette-neutral-light-700: oklch(0.4972 0.0179 225.10);\n --color-palette-neutral-light-800: oklch(0.4115 0.0140 225.10);\n --color-palette-neutral-light-900: oklch(0.3346 0.0111 225.10);\n --color-palette-neutral-light-1000: oklch(0.2499 0.0079 225.10);\n --color-palette-neutral-light-1100: oklch(0.1925 0.0058 225.10);\n --color-palette-neutral-dark-100: oklch(0.9862 0.0012 271.30);\n --color-palette-neutral-dark-200: oklch(0.9364 0.0024 271.30);\n --color-palette-neutral-dark-300: oklch(0.8497 0.0081 271.30);\n --color-palette-neutral-dark-400: oklch(0.7628 0.0169 271.30);\n --color-palette-neutral-dark-500: oklch(0.6707 0.0306 271.30);\n --color-palette-neutral-dark-600: oklch(0.5835 0.0454 271.30);\n --color-palette-neutral-dark-700: oklch(0.4972 0.0538 271.30);\n --color-palette-neutral-dark-800: oklch(0.4115 0.0479 271.30);\n --color-palette-neutral-dark-900: oklch(0.3346 0.0381 271.30);\n --color-palette-neutral-dark-1000: oklch(0.2499 0.0272 271.30);\n --color-palette-neutral-dark-1100: oklch(0.1925 0.0199 271.30);\n --color-palette-neutral-dark-1100-a16: oklch(0.1925 0.0199 271.30 / 0.16);\n --color-palette-neutral-dark-1100-a12: oklch(0.1925 0.0199 271.30 / 0.12);\n --color-palette-primary-100: oklch(0.9862 0.0064 274.10);\n --color-palette-primary-200: oklch(0.9364 0.0121 274.10);\n --color-palette-primary-300: oklch(0.8497 0.0408 274.10);\n --color-palette-primary-400: oklch(0.7628 0.0849 274.10);\n --color-palette-primary-500: oklch(0.6707 0.1537 274.10);\n --color-palette-primary-600: oklch(0.5835 0.2285 274.10);\n --color-palette-primary-700: oklch(0.4972 0.2707 274.10);\n --color-palette-primary-800: oklch(0.4115 0.2344 274.10);\n --color-palette-primary-900: oklch(0.3346 0.1863 274.10);\n --color-palette-primary-1000: oklch(0.2499 0.1328 274.10);\n --color-palette-primary-1100: oklch(0.1925 0.0974 274.10);\n --color-palette-primary-600-a6: oklch(0.5835 0.2285 274.10 / 0.06);\n --color-palette-primary-600-a9: oklch(0.5835 0.2285 274.10 / 0.09);\n --color-palette-primary-600-a8: oklch(0.5835 0.2285 274.10 / 0.08);\n --color-palette-accent-100: oklch(0.9862 0.0205 93.60);\n --color-palette-accent-200: oklch(0.9364 0.0378 93.60);\n --color-palette-accent-300: oklch(0.8789 0.1801 93.58);\n --color-palette-accent-400: oklch(0.7442 0.1526 93.94);\n --color-palette-accent-500: oklch(0.6707 0.1209 93.60);\n --color-palette-accent-600: oklch(0.5835 0.1195 93.60);\n --color-palette-accent-700: oklch(0.4972 0.0967 93.60);\n --color-palette-accent-800: oklch(0.4115 0.0758 93.60);\n --color-palette-accent-900: oklch(0.3346 0.0603 93.60);\n --color-palette-accent-1000: oklch(0.2499 0.0430 93.60);\n --color-palette-accent-1100: oklch(0.1925 0.0315 93.60);\n --color-palette-accent-alt-100: oklch(0.9862 0.0067 287.10);\n --color-palette-accent-alt-200: oklch(0.9364 0.0127 287.10);\n --color-palette-accent-alt-300: oklch(0.8497 0.0430 287.10);\n --color-palette-accent-alt-400: oklch(0.7628 0.0896 287.10);\n --color-palette-accent-alt-500: oklch(0.6707 0.1624 287.10);\n --color-palette-accent-alt-600: oklch(0.5835 0.2416 287.10);\n --color-palette-accent-alt-700: oklch(0.4972 0.2722 287.10);\n --color-palette-accent-alt-800: oklch(0.4115 0.2134 287.10);\n --color-palette-accent-alt-900: oklch(0.3346 0.1696 287.10);\n --color-palette-accent-alt-1000: oklch(0.2499 0.1209 287.10);\n --color-palette-accent-alt-1100: oklch(0.1925 0.0887 287.10);\n --color-palette-danger-100: oklch(0.9862 0.0066 29.40);\n --color-palette-danger-200: oklch(0.9364 0.0128 29.40);\n --color-palette-danger-300: oklch(0.8497 0.0452 29.40);\n --color-palette-danger-400: oklch(0.7628 0.0990 29.40);\n --color-palette-danger-500: oklch(0.6707 0.1910 29.40);\n --color-palette-danger-600: oklch(0.5835 0.2382 29.40);\n --color-palette-danger-700: oklch(0.4972 0.1928 29.40);\n --color-palette-danger-800: oklch(0.4115 0.1512 29.40);\n --color-palette-danger-900: oklch(0.3346 0.1202 29.40);\n --color-palette-danger-1000: oklch(0.2499 0.0857 29.40);\n --color-palette-danger-1100: oklch(0.1925 0.0628 29.40);\n --color-palette-warning-100: oklch(0.9862 0.0082 58.30);\n --color-palette-warning-200: oklch(0.9364 0.0156 58.30);\n --color-palette-warning-300: oklch(0.8497 0.0545 58.30);\n --color-palette-warning-400: oklch(0.7628 0.1179 58.30);\n --color-palette-warning-500: oklch(0.6707 0.1408 58.30);\n --color-palette-warning-600: oklch(0.5835 0.1392 58.30);\n --color-palette-warning-700: oklch(0.4972 0.1127 58.30);\n --color-palette-warning-800: oklch(0.4115 0.0883 58.30);\n --color-palette-warning-900: oklch(0.3346 0.0702 58.30);\n --color-palette-warning-1000: oklch(0.2499 0.0500 58.30);\n --color-palette-warning-1100: oklch(0.1925 0.0367 58.30);\n --color-palette-success-100: oklch(0.9862 0.0228 145.50);\n --color-palette-success-200: oklch(0.9364 0.0458 145.50);\n --color-palette-success-300: oklch(0.8497 0.1448 145.50);\n --color-palette-success-400: oklch(0.7628 0.1654 145.50);\n --color-palette-success-500: oklch(0.6707 0.1829 145.50);\n --color-palette-success-600: oklch(0.5835 0.1808 145.50);\n --color-palette-success-700: oklch(0.4972 0.1464 145.50);\n --color-palette-success-800: oklch(0.4115 0.1147 145.50);\n --color-palette-success-900: oklch(0.3346 0.0912 145.50);\n --color-palette-success-1000: oklch(0.2499 0.0650 145.50);\n --color-palette-success-1100: oklch(0.1925 0.0477 145.50);\n --color-black-a32: oklch(0.0000 0.0000 0.00 / 0.32);\n --color-black-a24: oklch(0.0000 0.0000 0.00 / 0.24);\n --color-black-a60: oklch(0.0000 0.0000 0.00 / 0.6);\n }\n\n :root, [data-color-mode=\"dark\"] {\n color-scheme: dark;\n color: var(--color-text-default);\n --color-background-current: var(--color-surface-default);\n --color-surface-sunken: var(--color-black); /** Wells, insets, code blocks — anything recessed into the page. */\n --color-surface-default: var(--color-palette-neutral-dark-1100); /** The page background — the base every other surface elevates from. */\n --color-surface-raised: var(--color-palette-neutral-dark-1000); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */\n --color-surface-success: var(--color-palette-success-1000); /** The success tint behind an alert or inline confirmation. */\n --color-surface-warning: var(--color-palette-warning-1000); /** The warning tint behind an alert or inline caution. */\n --color-surface-error: var(--color-palette-danger-1000); /** The error tint behind an alert or inline validation message. */\n --color-surface-brand: var(--color-palette-neutral-dark-1100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */\n --color-surface-brand-subtle: var(--color-palette-neutral-dark-1100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */\n --color-text-default: var(--color-palette-neutral-light-200); /** Body copy and headings. */\n --color-text-subtle: var(--color-palette-neutral-light-400); /** Secondary text — descriptions, metadata, helper copy. */\n --color-text-inverse: var(--color-palette-neutral-dark-1000); /** Labels on a filled control — action-primary, -neutral, -danger. */\n --color-text-success: var(--color-palette-success-300); /** Success text and icons. */\n --color-text-warning: var(--color-palette-warning-300); /** Warning text and icons. Always with an icon. */\n --color-text-error: var(--color-palette-danger-300); /** Error text and icons. */\n --color-border-strong: var(--color-palette-neutral-light-100); /** Emphasis — an active field, a selected row. */\n --color-border-default: var(--color-palette-neutral-light-600); /** Any border that carries meaning — control outlines, field borders. */\n --color-border-subtle: var(--color-palette-neutral-light-900); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */\n --color-action-primary: var(--color-palette-accent-300); /** Primary interactive fills, strokes, links and focus rings. */\n --color-action-primary-hover: var(--color-palette-accent-400); /** Hover for action-primary. */\n --color-action-primary-pressed: var(--color-palette-accent-500); /** Pressed for action-primary. */\n --color-action-danger: var(--color-palette-danger-400); /** Destructive actions — delete, remove, revoke. */\n --color-action-danger-hover: var(--color-palette-danger-300); /** Hover for action-danger. */\n --color-action-danger-pressed: var(--color-palette-danger-200); /** Pressed for action-danger. */\n --color-action-neutral: var(--color-palette-neutral-light-200); /** The neutral action fill. Button variant=\"secondary\" resolves here. */\n --color-action-neutral-hover: var(--color-palette-neutral-light-100); /** Hover for action-neutral. */\n --color-action-neutral-pressed: var(--color-white); /** Pressed for action-neutral. */\n --color-action-disabled: var(--color-palette-neutral-dark-800); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */\n --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */\n --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */\n --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent \"this one is chosen\" tint. */\n --color-viz-1: var(--color-palette-primary-500); /** Categorical series 1. */\n --color-viz-2: var(--color-palette-accent-500); /** Categorical series 2. */\n --color-viz-3: var(--color-palette-warning-400); /** Categorical series 3. */\n --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */\n --color-viz-5: var(--color-palette-success-300); /** Categorical series 5. */\n --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */\n --color-viz-7: var(--color-palette-neutral-dark-500); /** Categorical series 7. */\n --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */\n --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */\n --color-viz-seq-2: var(--color-palette-primary-500); /** Sequential step 2, low to high. */\n --color-viz-seq-3: var(--color-palette-primary-400); /** Sequential step 3, low to high. */\n --color-viz-seq-4: var(--color-palette-primary-300); /** Sequential step 4, low to high. */\n --color-shadow-key: var(--color-black-a32); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */\n --color-shadow-ambient: var(--color-black-a24); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */\n --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */\n --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-900) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%);\n --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%);\n }\n\n [data-color-mode=\"light\"] {\n color-scheme: light;\n color: var(--color-text-default);\n --color-background-current: var(--color-surface-default);\n --color-surface-sunken: var(--color-palette-neutral-light-200); /** Wells, insets, code blocks — anything recessed into the page. */\n --color-surface-default: var(--color-palette-neutral-light-100); /** The page background — the base every other surface elevates from. */\n --color-surface-raised: var(--color-white); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */\n --color-surface-success: var(--color-palette-success-200); /** The success tint behind an alert or inline confirmation. */\n --color-surface-warning: var(--color-palette-warning-200); /** The warning tint behind an alert or inline caution. */\n --color-surface-error: var(--color-palette-danger-200); /** The error tint behind an alert or inline validation message. */\n --color-surface-brand: var(--color-palette-neutral-light-100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */\n --color-surface-brand-subtle: var(--color-palette-neutral-light-100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */\n --color-text-default: var(--color-palette-neutral-dark-1000); /** Body copy and headings. */\n --color-text-subtle: var(--color-palette-neutral-dark-800); /** Secondary text — descriptions, metadata, helper copy. */\n --color-text-inverse: var(--color-palette-neutral-light-200); /** Labels on a filled control — action-primary, -neutral, -danger. */\n --color-text-success: var(--color-palette-success-900); /** Success text and icons. */\n --color-text-warning: var(--color-palette-warning-900); /** Warning text and icons. Always with an icon. */\n --color-text-error: var(--color-palette-danger-900); /** Error text and icons. */\n --color-border-strong: var(--color-palette-neutral-dark-1100); /** Emphasis — an active field, a selected row. */\n --color-border-default: var(--color-palette-neutral-dark-600); /** Any border that carries meaning — control outlines, field borders. */\n --color-border-subtle: var(--color-palette-neutral-dark-300); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */\n --color-action-primary: var(--color-palette-primary-800); /** Primary interactive fills, strokes, links and focus rings. */\n --color-action-primary-hover: var(--color-palette-primary-900); /** Hover for action-primary. */\n --color-action-primary-pressed: var(--color-palette-primary-1000); /** Pressed for action-primary. */\n --color-action-danger: var(--color-palette-danger-800); /** Destructive actions — delete, remove, revoke. */\n --color-action-danger-hover: var(--color-palette-danger-900); /** Hover for action-danger. */\n --color-action-danger-pressed: var(--color-palette-danger-1000); /** Pressed for action-danger. */\n --color-action-neutral: var(--color-palette-neutral-dark-1000); /** The neutral action fill. Button variant=\"secondary\" resolves here. */\n --color-action-neutral-hover: var(--color-palette-neutral-dark-1100); /** Hover for action-neutral. */\n --color-action-neutral-pressed: var(--color-black); /** Pressed for action-neutral. */\n --color-action-disabled: var(--color-palette-neutral-light-400); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */\n --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */\n --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */\n --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent \"this one is chosen\" tint. */\n --color-viz-1: var(--color-palette-primary-800); /** Categorical series 1. */\n --color-viz-2: var(--color-palette-accent-700); /** Categorical series 2. */\n --color-viz-3: var(--color-palette-warning-700); /** Categorical series 3. */\n --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */\n --color-viz-5: var(--color-palette-success-600); /** Categorical series 5. */\n --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */\n --color-viz-7: var(--color-palette-neutral-dark-1000); /** Categorical series 7. */\n --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */\n --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */\n --color-viz-seq-2: var(--color-palette-primary-700); /** Sequential step 2, low to high. */\n --color-viz-seq-3: var(--color-palette-primary-800); /** Sequential step 3, low to high. */\n --color-viz-seq-4: var(--color-palette-primary-900); /** Sequential step 4, low to high. */\n --color-shadow-key: var(--color-palette-neutral-dark-1100-a16); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */\n --color-shadow-ambient: var(--color-palette-neutral-dark-1100-a12); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */\n --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */\n --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-alt-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-alt-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-alt-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-alt-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-alt-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-alt-300) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%);\n --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%);\n }\n}\n"; diff --git a/packages/gamut-styles/src/tokens/generated/skillsoft/dark.tokens.json b/packages/gamut-styles/src/tokens/generated/skillsoft/dark.tokens.json new file mode 100644 index 0000000000..79264f3fd5 --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/skillsoft/dark.tokens.json @@ -0,0 +1,315 @@ +{ + "$description": "dark mode roles for \"skillsoft\". Do not edit.", + "surface-sunken": { + "$type": "color", + "$value": "{black}", + "$description": "Wells, insets, code blocks — anything recessed into the page." + }, + "surface-default": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "The page background — the base every other surface elevates from." + }, + "surface-raised": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits." + }, + "surface-success": { + "$type": "color", + "$value": "{palette.success.1000}", + "$description": "The success tint behind an alert or inline confirmation." + }, + "surface-warning": { + "$type": "color", + "$value": "{palette.warning.1000}", + "$description": "The warning tint behind an alert or inline caution." + }, + "surface-error": { + "$type": "color", + "$value": "{palette.danger.1000}", + "$description": "The error tint behind an alert or inline validation message." + }, + "surface-brand": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.accent.900}", + "radius": 0.85, + "at": "100% -5%" + }, + { + "color": "{palette.primary.900}", + "radius": 1.21, + "at": "5% 100%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.3346 0.1201 357.3 / 0.45) 0%, oklch(0.3346 0.1201 357.3 / 0.43) 16%, oklch(0.3346 0.1201 357.3 / 0.24) 36%, oklch(0.3346 0.1201 357.3 / 0.11) 56%, oklch(0.3346 0.1201 357.3 / 0.04) 76%, oklch(0.3346 0.1201 357.3 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.3346 0.1405 262.4 / 0.45) 0%, oklch(0.3346 0.1405 262.4 / 0.43) 16%, oklch(0.3346 0.1405 262.4 / 0.24) 36%, oklch(0.3346 0.1405 262.4 / 0.11) 56%, oklch(0.3346 0.1405 262.4 / 0.04) 76%, oklch(0.3346 0.1405 262.4 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-900) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%)" + } + } + } + }, + "surface-brand-subtle": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.primary.900}", + "radius": 0.78, + "at": "50% -10%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.3346 0.1405 262.4 / 0.45) 0%, oklch(0.3346 0.1405 262.4 / 0.43) 16%, oklch(0.3346 0.1405 262.4 / 0.24) 36%, oklch(0.3346 0.1405 262.4 / 0.11) 56%, oklch(0.3346 0.1405 262.4 / 0.04) 76%, oklch(0.3346 0.1405 262.4 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%)" + } + } + } + }, + "text-default": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "Body copy and headings." + }, + "text-subtle": { + "$type": "color", + "$value": "{palette.neutral-light.400}", + "$description": "Secondary text — descriptions, metadata, helper copy." + }, + "text-inverse": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "Labels on a filled control — action-primary, -neutral, -danger." + }, + "text-success": { + "$type": "color", + "$value": "{palette.success.300}", + "$description": "Success text and icons." + }, + "text-warning": { + "$type": "color", + "$value": "{palette.warning.300}", + "$description": "Warning text and icons. Always with an icon." + }, + "text-error": { + "$type": "color", + "$value": "{palette.danger.300}", + "$description": "Error text and icons." + }, + "border-strong": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "Emphasis — an active field, a selected row." + }, + "border-default": { + "$type": "color", + "$value": "{palette.neutral-light.600}", + "$description": "Any border that carries meaning — control outlines, field borders." + }, + "border-subtle": { + "$type": "color", + "$value": "{palette.neutral-light.900}", + "$description": "Decorative hairlines — table rules, dividers. Contrast-exempt." + }, + "action-primary": { + "$type": "color", + "$value": "{palette.primary.400}", + "$description": "Primary interactive fills, strokes, links and focus rings." + }, + "action-primary-hover": { + "$type": "color", + "$value": "{palette.primary.300}", + "$description": "Hover for action-primary." + }, + "action-primary-pressed": { + "$type": "color", + "$value": "{palette.primary.200}", + "$description": "Pressed for action-primary." + }, + "action-danger": { + "$type": "color", + "$value": "{palette.danger.400}", + "$description": "Destructive actions — delete, remove, revoke." + }, + "action-danger-hover": { + "$type": "color", + "$value": "{palette.danger.300}", + "$description": "Hover for action-danger." + }, + "action-danger-pressed": { + "$type": "color", + "$value": "{palette.danger.200}", + "$description": "Pressed for action-danger." + }, + "action-neutral": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "The neutral action fill. Button variant=\"secondary\" resolves here." + }, + "action-neutral-hover": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "Hover for action-neutral." + }, + "action-neutral-pressed": { + "$type": "color", + "$value": "{white}", + "$description": "Pressed for action-neutral." + }, + "action-disabled": { + "$type": "color", + "$value": "{palette.neutral-dark.800}", + "$description": "The disabled fill for every action variant. Contrast-exempt (inactive control)." + }, + "state-hover": { + "$type": "color", + "$value": "{palette.primary.600-a6}", + "$description": "Rest → hover on rows, tabs, chips, menu items." + }, + "state-pressed": { + "$type": "color", + "$value": "{palette.primary.600-a9}", + "$description": "Hover → pressed on the same elements." + }, + "state-selected": { + "$type": "color", + "$value": "{palette.primary.600-a8}", + "$description": "The persistent \"this one is chosen\" tint." + }, + "viz-1": { + "$type": "color", + "$value": "{palette.primary.500}", + "$description": "Categorical series 1." + }, + "viz-2": { + "$type": "color", + "$value": "{palette.accent.500}", + "$description": "Categorical series 2." + }, + "viz-3": { + "$type": "color", + "$value": "{palette.warning.400}", + "$description": "Categorical series 3." + }, + "viz-4": { + "$type": "color", + "$value": "{palette.accent-alt.600}", + "$description": "Categorical series 4." + }, + "viz-5": { + "$type": "color", + "$value": "{palette.success.300}", + "$description": "Categorical series 5." + }, + "viz-6": { + "$type": "color", + "$value": "{palette.danger.600}", + "$description": "Categorical series 6." + }, + "viz-7": { + "$type": "color", + "$value": "{palette.neutral-dark.500}", + "$description": "Categorical series 7." + }, + "viz-8": { + "$type": "color", + "$value": "{palette.neutral-light.600}", + "$description": "Categorical series 8." + }, + "viz-seq-1": { + "$type": "color", + "$value": "{palette.primary.600}", + "$description": "Sequential step 1, low to high." + }, + "viz-seq-2": { + "$type": "color", + "$value": "{palette.primary.500}", + "$description": "Sequential step 2, low to high." + }, + "viz-seq-3": { + "$type": "color", + "$value": "{palette.primary.400}", + "$description": "Sequential step 3, low to high." + }, + "viz-seq-4": { + "$type": "color", + "$value": "{palette.primary.300}", + "$description": "Sequential step 4, low to high." + }, + "shadow-key": { + "$type": "color", + "$value": "{black-a32}", + "$description": "The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur." + }, + "shadow-ambient": { + "$type": "color", + "$value": "{black-a24}", + "$description": "The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff." + }, + "shadow-scrim": { + "$type": "color", + "$value": "{black-a60}", + "$description": "The scrim behind modals and dialogs. Mode-independent." + } +} diff --git a/packages/gamut-styles/src/tokens/generated/skillsoft/light.tokens.json b/packages/gamut-styles/src/tokens/generated/skillsoft/light.tokens.json new file mode 100644 index 0000000000..d930273da2 --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/skillsoft/light.tokens.json @@ -0,0 +1,315 @@ +{ + "$description": "light mode roles for \"skillsoft\". Do not edit.", + "surface-sunken": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "Wells, insets, code blocks — anything recessed into the page." + }, + "surface-default": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "The page background — the base every other surface elevates from." + }, + "surface-raised": { + "$type": "color", + "$value": "{white}", + "$description": "The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits." + }, + "surface-success": { + "$type": "color", + "$value": "{palette.success.200}", + "$description": "The success tint behind an alert or inline confirmation." + }, + "surface-warning": { + "$type": "color", + "$value": "{palette.warning.200}", + "$description": "The warning tint behind an alert or inline caution." + }, + "surface-error": { + "$type": "color", + "$value": "{palette.danger.200}", + "$description": "The error tint behind an alert or inline validation message." + }, + "surface-brand": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.accent.300}", + "radius": 0.85, + "at": "100% -5%" + }, + { + "color": "{palette.primary.300}", + "radius": 1.21, + "at": "5% 100%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.8497 0.0499 357.3 / 0.45) 0%, oklch(0.8497 0.0499 357.3 / 0.43) 16%, oklch(0.8497 0.0499 357.3 / 0.24) 36%, oklch(0.8497 0.0499 357.3 / 0.11) 56%, oklch(0.8497 0.0499 357.3 / 0.04) 76%, oklch(0.8497 0.0499 357.3 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.8497 0.0407 262.4 / 0.45) 0%, oklch(0.8497 0.0407 262.4 / 0.43) 16%, oklch(0.8497 0.0407 262.4 / 0.24) 36%, oklch(0.8497 0.0407 262.4 / 0.11) 56%, oklch(0.8497 0.0407 262.4 / 0.04) 76%, oklch(0.8497 0.0407 262.4 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-300) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%)" + } + } + } + }, + "surface-brand-subtle": { + "$type": "color", + "$value": "{palette.neutral-light.100}", + "$description": "The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle.", + "$extensions": { + "com.skillsoft.gamut": { + "gradient": { + "circles": [ + { + "color": "{palette.primary.300}", + "radius": 0.78, + "at": "50% -10%" + } + ], + "falloff": [ + [ + 0, + 0.45 + ], + [ + 16, + 0.43 + ], + [ + 36, + 0.24 + ], + [ + 56, + 0.11 + ], + [ + 76, + 0.04 + ], + [ + 100, + 0 + ] + ], + "css": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.8497 0.0407 262.4 / 0.45) 0%, oklch(0.8497 0.0407 262.4 / 0.43) 16%, oklch(0.8497 0.0407 262.4 / 0.24) 36%, oklch(0.8497 0.0407 262.4 / 0.11) 56%, oklch(0.8497 0.0407 262.4 / 0.04) 76%, oklch(0.8497 0.0407 262.4 / 0) 100%)", + "cssVars": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%)" + } + } + } + }, + "text-default": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "Body copy and headings." + }, + "text-subtle": { + "$type": "color", + "$value": "{palette.neutral-dark.800}", + "$description": "Secondary text — descriptions, metadata, helper copy." + }, + "text-inverse": { + "$type": "color", + "$value": "{palette.neutral-light.200}", + "$description": "Labels on a filled control — action-primary, -neutral, -danger." + }, + "text-success": { + "$type": "color", + "$value": "{palette.success.900}", + "$description": "Success text and icons." + }, + "text-warning": { + "$type": "color", + "$value": "{palette.warning.900}", + "$description": "Warning text and icons. Always with an icon." + }, + "text-error": { + "$type": "color", + "$value": "{palette.danger.900}", + "$description": "Error text and icons." + }, + "border-strong": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "Emphasis — an active field, a selected row." + }, + "border-default": { + "$type": "color", + "$value": "{palette.neutral-dark.600}", + "$description": "Any border that carries meaning — control outlines, field borders." + }, + "border-subtle": { + "$type": "color", + "$value": "{palette.neutral-dark.300}", + "$description": "Decorative hairlines — table rules, dividers. Contrast-exempt." + }, + "action-primary": { + "$type": "color", + "$value": "{palette.primary.800}", + "$description": "Primary interactive fills, strokes, links and focus rings." + }, + "action-primary-hover": { + "$type": "color", + "$value": "{palette.primary.900}", + "$description": "Hover for action-primary." + }, + "action-primary-pressed": { + "$type": "color", + "$value": "{palette.primary.1000}", + "$description": "Pressed for action-primary." + }, + "action-danger": { + "$type": "color", + "$value": "{palette.danger.800}", + "$description": "Destructive actions — delete, remove, revoke." + }, + "action-danger-hover": { + "$type": "color", + "$value": "{palette.danger.900}", + "$description": "Hover for action-danger." + }, + "action-danger-pressed": { + "$type": "color", + "$value": "{palette.danger.1000}", + "$description": "Pressed for action-danger." + }, + "action-neutral": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "The neutral action fill. Button variant=\"secondary\" resolves here." + }, + "action-neutral-hover": { + "$type": "color", + "$value": "{palette.neutral-dark.1100}", + "$description": "Hover for action-neutral." + }, + "action-neutral-pressed": { + "$type": "color", + "$value": "{black}", + "$description": "Pressed for action-neutral." + }, + "action-disabled": { + "$type": "color", + "$value": "{palette.neutral-light.400}", + "$description": "The disabled fill for every action variant. Contrast-exempt (inactive control)." + }, + "state-hover": { + "$type": "color", + "$value": "{palette.primary.600-a6}", + "$description": "Rest → hover on rows, tabs, chips, menu items." + }, + "state-pressed": { + "$type": "color", + "$value": "{palette.primary.600-a9}", + "$description": "Hover → pressed on the same elements." + }, + "state-selected": { + "$type": "color", + "$value": "{palette.primary.600-a8}", + "$description": "The persistent \"this one is chosen\" tint." + }, + "viz-1": { + "$type": "color", + "$value": "{palette.primary.800}", + "$description": "Categorical series 1." + }, + "viz-2": { + "$type": "color", + "$value": "{palette.accent.700}", + "$description": "Categorical series 2." + }, + "viz-3": { + "$type": "color", + "$value": "{palette.warning.700}", + "$description": "Categorical series 3." + }, + "viz-4": { + "$type": "color", + "$value": "{palette.accent-alt.600}", + "$description": "Categorical series 4." + }, + "viz-5": { + "$type": "color", + "$value": "{palette.success.600}", + "$description": "Categorical series 5." + }, + "viz-6": { + "$type": "color", + "$value": "{palette.danger.600}", + "$description": "Categorical series 6." + }, + "viz-7": { + "$type": "color", + "$value": "{palette.neutral-dark.1000}", + "$description": "Categorical series 7." + }, + "viz-8": { + "$type": "color", + "$value": "{palette.neutral-light.600}", + "$description": "Categorical series 8." + }, + "viz-seq-1": { + "$type": "color", + "$value": "{palette.primary.600}", + "$description": "Sequential step 1, low to high." + }, + "viz-seq-2": { + "$type": "color", + "$value": "{palette.primary.700}", + "$description": "Sequential step 2, low to high." + }, + "viz-seq-3": { + "$type": "color", + "$value": "{palette.primary.800}", + "$description": "Sequential step 3, low to high." + }, + "viz-seq-4": { + "$type": "color", + "$value": "{palette.primary.900}", + "$description": "Sequential step 4, low to high." + }, + "shadow-key": { + "$type": "color", + "$value": "{palette.neutral-dark.1100-a16}", + "$description": "The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur." + }, + "shadow-ambient": { + "$type": "color", + "$value": "{palette.neutral-dark.1100-a12}", + "$description": "The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff." + }, + "shadow-scrim": { + "$type": "color", + "$value": "{black-a60}", + "$description": "The scrim behind modals and dialogs. Mode-independent." + } +} diff --git a/packages/gamut-styles/src/tokens/generated/skillsoft/palette.tokens.json b/packages/gamut-styles/src/tokens/generated/skillsoft/palette.tokens.json new file mode 100644 index 0000000000..7e6d6cebaf --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/skillsoft/palette.tokens.json @@ -0,0 +1,1745 @@ +{ + "$description": "Generated from the \"skillsoft\" brand (tier 4). Do not edit.", + "white": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 1, + 0, + 0 + ], + "alpha": 1, + "hex": "#ffffff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "black": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 1, + "hex": "#000000" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "palette": { + "neutral-light": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0041, + 93.7 + ], + "alpha": 1, + "hex": "#fbfaf7" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0075, + 93.7 + ], + "alpha": 1, + "hex": "#ebeae4" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0191, + 93.7 + ], + "alpha": 1, + "hex": "#d2cec0" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0218, + 93.7 + ], + "alpha": 1, + "hex": "#b6b2a3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.0241, + 93.7 + ], + "alpha": 1, + "hex": "#9a9585" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.0239, + 93.7 + ], + "alpha": 1, + "hex": "#807b6c" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.0193, + 93.7 + ], + "alpha": 1, + "hex": "#666256" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0151, + 93.7 + ], + "alpha": 1, + "hex": "#4d4b42" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.012, + 93.7 + ], + "alpha": 1, + "hex": "#383730" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0086, + 93.7 + ], + "alpha": 1, + "hex": "#23221d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0063, + 93.7 + ], + "alpha": 1, + "hex": "#151411" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "neutral-dark": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0012, + 270.7 + ], + "alpha": 1, + "hex": "#fafafb" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0024, + 270.7 + ], + "alpha": 1, + "hex": "#e9eaeb" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0081, + 270.7 + ], + "alpha": 1, + "hex": "#cbcdd3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0168, + 270.7 + ], + "alpha": 1, + "hex": "#aeb2bd" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.0305, + 270.7 + ], + "alpha": 1, + "hex": "#8e95a9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.0454, + 270.7 + ], + "alpha": 1, + "hex": "#717a97" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.0538, + 270.7 + ], + "alpha": 1, + "hex": "#576182" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0482, + 270.7 + ], + "alpha": 1, + "hex": "#414a65" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0383, + 270.7 + ], + "alpha": 1, + "hex": "#2f364b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0273, + 270.7 + ], + "alpha": 1, + "hex": "#1d212f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.02, + 270.7 + ], + "alpha": 1, + "hex": "#11141d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100-a16": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.02, + 270.7 + ], + "alpha": 0.16, + "hex": "#11141d" + } + }, + "1100-a12": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.02, + 270.7 + ], + "alpha": 0.12, + "hex": "#11141d" + } + } + }, + "primary": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0064, + 262.4 + ], + "alpha": 1, + "hex": "#f8fbff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0121, + 262.4 + ], + "alpha": 1, + "hex": "#e6eaf2" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0407, + 262.4 + ], + "alpha": 1, + "hex": "#c0cee9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0846, + 262.4 + ], + "alpha": 1, + "hex": "#95b2e8" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1531, + 262.4 + ], + "alpha": 1, + "hex": "#6192f3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2274, + 262.4 + ], + "alpha": 1, + "hex": "#276eff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.2254, + 262.4 + ], + "alpha": 1, + "hex": "#0a51df" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.1767, + 262.4 + ], + "alpha": 1, + "hex": "#0c3fa8" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.1405, + 262.4 + ], + "alpha": 1, + "hex": "#082e7d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.1001, + 262.4 + ], + "alpha": 1, + "hex": "#051c50" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0734, + 262.4 + ], + "alpha": 1, + "hex": "#031135" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "600-a6": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2274, + 262.4 + ], + "alpha": 0.06, + "hex": "#276eff" + } + }, + "600-a9": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2274, + 262.4 + ], + "alpha": 0.09, + "hex": "#276eff" + } + }, + "600-a8": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2274, + 262.4 + ], + "alpha": 0.08, + "hex": "#276eff" + } + } + }, + "accent": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0073, + 357.3 + ], + "alpha": 1, + "hex": "#fff9fa" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0141, + 357.3 + ], + "alpha": 1, + "hex": "#f2e6ea" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0499, + 357.3 + ], + "alpha": 1, + "hex": "#eac1ce" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.11, + 357.3 + ], + "alpha": 1, + "hex": "#ea94b2" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.2144, + 357.3 + ], + "alpha": 1, + "hex": "#f44b97" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.238, + 357.3 + ], + "alpha": 1, + "hex": "#dd007c" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.1927, + 357.3 + ], + "alpha": 1, + "hex": "#af1363" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.1511, + 357.3 + ], + "alpha": 1, + "hex": "#85154b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.1201, + 357.3 + ], + "alpha": 1, + "hex": "#620f37" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0856, + 357.3 + ], + "alpha": 1, + "hex": "#3f0922" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0628, + 357.3 + ], + "alpha": 1, + "hex": "#290515" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "accent-alt": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0067, + 287.1 + ], + "alpha": 1, + "hex": "#fafaff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0127, + 287.1 + ], + "alpha": 1, + "hex": "#e9e9f2" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.043, + 287.1 + ], + "alpha": 1, + "hex": "#cbcae9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0896, + 287.1 + ], + "alpha": 1, + "hex": "#aeaae9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1624, + 287.1 + ], + "alpha": 1, + "hex": "#9182f3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2416, + 287.1 + ], + "alpha": 1, + "hex": "#7a52ff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.2722, + 287.1 + ], + "alpha": 1, + "hex": "#661af0" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.2134, + 287.1 + ], + "alpha": 1, + "hex": "#4d1cb5" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.1696, + 287.1 + ], + "alpha": 1, + "hex": "#381587" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.1209, + 287.1 + ], + "alpha": 1, + "hex": "#220d57" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0887, + 287.1 + ], + "alpha": 1, + "hex": "#150739" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "danger": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.0066, + 17.3 + ], + "alpha": 1, + "hex": "#fff9f9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0128, + 17.3 + ], + "alpha": 1, + "hex": "#f2e7e7" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0452, + 17.3 + ], + "alpha": 1, + "hex": "#eac2c3" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0993, + 17.3 + ], + "alpha": 1, + "hex": "#ea989b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1924, + 17.3 + ], + "alpha": 1, + "hex": "#f45769" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.2336, + 17.3 + ], + "alpha": 1, + "hex": "#e40045" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.1891, + 17.3 + ], + "alpha": 1, + "hex": "#b51439" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.1482, + 17.3 + ], + "alpha": 1, + "hex": "#89162c" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.1178, + 17.3 + ], + "alpha": 1, + "hex": "#66101f" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.084, + 17.3 + ], + "alpha": 1, + "hex": "#410912" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0616, + 17.3 + ], + "alpha": 1, + "hex": "#2a050a" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "warning": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.016, + 88 + ], + "alpha": 1, + "hex": "#fffaef" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.03, + 88 + ], + "alpha": 1, + "hex": "#f2e9d4" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0955, + 88 + ], + "alpha": 1, + "hex": "#e8cb84" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.1091, + 88 + ], + "alpha": 1, + "hex": "#cfae5b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1206, + 88 + ], + "alpha": 1, + "hex": "#b4902d" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.1192, + 88 + ], + "alpha": 1, + "hex": "#997600" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.0965, + 88 + ], + "alpha": 1, + "hex": "#795e0e" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0757, + 88 + ], + "alpha": 1, + "hex": "#5c4810" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.0601, + 88 + ], + "alpha": 1, + "hex": "#43340b" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0429, + 88 + ], + "alpha": 1, + "hex": "#2a2006" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0314, + 88 + ], + "alpha": 1, + "hex": "#1a1304" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + }, + "success": { + "100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9862, + 0.017, + 200 + ], + "alpha": 1, + "hex": "#eefeff" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "200": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.9364, + 0.0318, + 200 + ], + "alpha": 1, + "hex": "#d2f1f2" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "300": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.8497, + 0.0794, + 200 + ], + "alpha": 1, + "hex": "#8ddee2" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "400": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.7628, + 0.0907, + 200 + ], + "alpha": 1, + "hex": "#64c4c9" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "500": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.6707, + 0.1003, + 200 + ], + "alpha": 1, + "hex": "#34a8ae" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "600": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.5835, + 0.0991, + 200 + ], + "alpha": 1, + "hex": "#018d92" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "light" + } + } + }, + "700": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4972, + 0.0803, + 200 + ], + "alpha": 1, + "hex": "#117074" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "800": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.4115, + 0.0629, + 200 + ], + "alpha": 1, + "hex": "#135558" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "900": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.3346, + 0.05, + 200 + ], + "alpha": 1, + "hex": "#0e3e41" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1000": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.2499, + 0.0356, + 200 + ], + "alpha": 1, + "hex": "#082728" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + }, + "1100": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0.1925, + 0.0261, + 200 + ], + "alpha": 1, + "hex": "#041819" + }, + "$extensions": { + "com.skillsoft.gamut": { + "contentMode": "dark" + } + } + } + } + }, + "black-a32": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 0.32, + "hex": "#000000" + } + }, + "black-a24": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 0.24, + "hex": "#000000" + } + }, + "black-a60": { + "$type": "color", + "$value": { + "colorSpace": "oklch", + "components": [ + 0, + 0, + 0 + ], + "alpha": 0.6, + "hex": "#000000" + } + } +} diff --git a/packages/gamut-styles/src/tokens/generated/skillsoft/tokens.css b/packages/gamut-styles/src/tokens/generated/skillsoft/tokens.css new file mode 100644 index 0000000000..3990c148f2 --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/skillsoft/tokens.css @@ -0,0 +1,211 @@ +/* Generated by @skillsoft/gamut-brand from the "skillsoft" brand. Do not edit. */ +@layer reset, base, tokens, recipes, utilities; + +@layer tokens { + :root, [data-brand="skillsoft"] { + --color-white: oklch(1.0000 0.0000 0.00); + --color-black: oklch(0.0000 0.0000 0.00); + --color-palette-neutral-light-100: oklch(0.9862 0.0041 93.70); + --color-palette-neutral-light-200: oklch(0.9364 0.0075 93.70); + --color-palette-neutral-light-300: oklch(0.8497 0.0191 93.70); + --color-palette-neutral-light-400: oklch(0.7628 0.0218 93.70); + --color-palette-neutral-light-500: oklch(0.6707 0.0241 93.70); + --color-palette-neutral-light-600: oklch(0.5835 0.0239 93.70); + --color-palette-neutral-light-700: oklch(0.4972 0.0193 93.70); + --color-palette-neutral-light-800: oklch(0.4115 0.0151 93.70); + --color-palette-neutral-light-900: oklch(0.3346 0.0120 93.70); + --color-palette-neutral-light-1000: oklch(0.2499 0.0086 93.70); + --color-palette-neutral-light-1100: oklch(0.1925 0.0063 93.70); + --color-palette-neutral-dark-100: oklch(0.9862 0.0012 270.70); + --color-palette-neutral-dark-200: oklch(0.9364 0.0024 270.70); + --color-palette-neutral-dark-300: oklch(0.8497 0.0081 270.70); + --color-palette-neutral-dark-400: oklch(0.7628 0.0168 270.70); + --color-palette-neutral-dark-500: oklch(0.6707 0.0305 270.70); + --color-palette-neutral-dark-600: oklch(0.5835 0.0454 270.70); + --color-palette-neutral-dark-700: oklch(0.4972 0.0538 270.70); + --color-palette-neutral-dark-800: oklch(0.4115 0.0482 270.70); + --color-palette-neutral-dark-900: oklch(0.3346 0.0383 270.70); + --color-palette-neutral-dark-1000: oklch(0.2499 0.0273 270.70); + --color-palette-neutral-dark-1100: oklch(0.1925 0.0200 270.70); + --color-palette-neutral-dark-1100-a16: oklch(0.1925 0.0200 270.70 / 0.16); + --color-palette-neutral-dark-1100-a12: oklch(0.1925 0.0200 270.70 / 0.12); + --color-palette-primary-100: oklch(0.9862 0.0064 262.40); + --color-palette-primary-200: oklch(0.9364 0.0121 262.40); + --color-palette-primary-300: oklch(0.8497 0.0407 262.40); + --color-palette-primary-400: oklch(0.7628 0.0846 262.40); + --color-palette-primary-500: oklch(0.6707 0.1531 262.40); + --color-palette-primary-600: oklch(0.5835 0.2274 262.40); + --color-palette-primary-700: oklch(0.4972 0.2254 262.40); + --color-palette-primary-800: oklch(0.4115 0.1767 262.40); + --color-palette-primary-900: oklch(0.3346 0.1405 262.40); + --color-palette-primary-1000: oklch(0.2499 0.1001 262.40); + --color-palette-primary-1100: oklch(0.1925 0.0734 262.40); + --color-palette-primary-600-a6: oklch(0.5835 0.2274 262.40 / 0.06); + --color-palette-primary-600-a9: oklch(0.5835 0.2274 262.40 / 0.09); + --color-palette-primary-600-a8: oklch(0.5835 0.2274 262.40 / 0.08); + --color-palette-accent-100: oklch(0.9862 0.0073 357.30); + --color-palette-accent-200: oklch(0.9364 0.0141 357.30); + --color-palette-accent-300: oklch(0.8497 0.0499 357.30); + --color-palette-accent-400: oklch(0.7628 0.1100 357.30); + --color-palette-accent-500: oklch(0.6707 0.2144 357.30); + --color-palette-accent-600: oklch(0.5835 0.2380 357.30); + --color-palette-accent-700: oklch(0.4972 0.1927 357.30); + --color-palette-accent-800: oklch(0.4115 0.1511 357.30); + --color-palette-accent-900: oklch(0.3346 0.1201 357.30); + --color-palette-accent-1000: oklch(0.2499 0.0856 357.30); + --color-palette-accent-1100: oklch(0.1925 0.0628 357.30); + --color-palette-accent-alt-100: oklch(0.9862 0.0067 287.10); + --color-palette-accent-alt-200: oklch(0.9364 0.0127 287.10); + --color-palette-accent-alt-300: oklch(0.8497 0.0430 287.10); + --color-palette-accent-alt-400: oklch(0.7628 0.0896 287.10); + --color-palette-accent-alt-500: oklch(0.6707 0.1624 287.10); + --color-palette-accent-alt-600: oklch(0.5835 0.2416 287.10); + --color-palette-accent-alt-700: oklch(0.4972 0.2722 287.10); + --color-palette-accent-alt-800: oklch(0.4115 0.2134 287.10); + --color-palette-accent-alt-900: oklch(0.3346 0.1696 287.10); + --color-palette-accent-alt-1000: oklch(0.2499 0.1209 287.10); + --color-palette-accent-alt-1100: oklch(0.1925 0.0887 287.10); + --color-palette-danger-100: oklch(0.9862 0.0066 17.30); + --color-palette-danger-200: oklch(0.9364 0.0128 17.30); + --color-palette-danger-300: oklch(0.8497 0.0452 17.30); + --color-palette-danger-400: oklch(0.7628 0.0993 17.30); + --color-palette-danger-500: oklch(0.6707 0.1924 17.30); + --color-palette-danger-600: oklch(0.5835 0.2336 17.30); + --color-palette-danger-700: oklch(0.4972 0.1891 17.30); + --color-palette-danger-800: oklch(0.4115 0.1482 17.30); + --color-palette-danger-900: oklch(0.3346 0.1178 17.30); + --color-palette-danger-1000: oklch(0.2499 0.0840 17.30); + --color-palette-danger-1100: oklch(0.1925 0.0616 17.30); + --color-palette-warning-100: oklch(0.9862 0.0160 88.00); + --color-palette-warning-200: oklch(0.9364 0.0300 88.00); + --color-palette-warning-300: oklch(0.8497 0.0955 88.00); + --color-palette-warning-400: oklch(0.7628 0.1091 88.00); + --color-palette-warning-500: oklch(0.6707 0.1206 88.00); + --color-palette-warning-600: oklch(0.5835 0.1192 88.00); + --color-palette-warning-700: oklch(0.4972 0.0965 88.00); + --color-palette-warning-800: oklch(0.4115 0.0757 88.00); + --color-palette-warning-900: oklch(0.3346 0.0601 88.00); + --color-palette-warning-1000: oklch(0.2499 0.0429 88.00); + --color-palette-warning-1100: oklch(0.1925 0.0314 88.00); + --color-palette-success-100: oklch(0.9862 0.0170 200.00); + --color-palette-success-200: oklch(0.9364 0.0318 200.00); + --color-palette-success-300: oklch(0.8497 0.0794 200.00); + --color-palette-success-400: oklch(0.7628 0.0907 200.00); + --color-palette-success-500: oklch(0.6707 0.1003 200.00); + --color-palette-success-600: oklch(0.5835 0.0991 200.00); + --color-palette-success-700: oklch(0.4972 0.0803 200.00); + --color-palette-success-800: oklch(0.4115 0.0629 200.00); + --color-palette-success-900: oklch(0.3346 0.0500 200.00); + --color-palette-success-1000: oklch(0.2499 0.0356 200.00); + --color-palette-success-1100: oklch(0.1925 0.0261 200.00); + --color-black-a32: oklch(0.0000 0.0000 0.00 / 0.32); + --color-black-a24: oklch(0.0000 0.0000 0.00 / 0.24); + --color-black-a60: oklch(0.0000 0.0000 0.00 / 0.6); + } + + :root, [data-color-mode="light"] { + color-scheme: light; + color: var(--color-text-default); + --color-background-current: var(--color-surface-default); + --color-surface-sunken: var(--color-palette-neutral-light-200); /** Wells, insets, code blocks — anything recessed into the page. */ + --color-surface-default: var(--color-palette-neutral-light-100); /** The page background — the base every other surface elevates from. */ + --color-surface-raised: var(--color-white); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */ + --color-surface-success: var(--color-palette-success-200); /** The success tint behind an alert or inline confirmation. */ + --color-surface-warning: var(--color-palette-warning-200); /** The warning tint behind an alert or inline caution. */ + --color-surface-error: var(--color-palette-danger-200); /** The error tint behind an alert or inline validation message. */ + --color-surface-brand: var(--color-palette-neutral-light-100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */ + --color-surface-brand-subtle: var(--color-palette-neutral-light-100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */ + --color-text-default: var(--color-palette-neutral-dark-1000); /** Body copy and headings. */ + --color-text-subtle: var(--color-palette-neutral-dark-800); /** Secondary text — descriptions, metadata, helper copy. */ + --color-text-inverse: var(--color-palette-neutral-light-200); /** Labels on a filled control — action-primary, -neutral, -danger. */ + --color-text-success: var(--color-palette-success-900); /** Success text and icons. */ + --color-text-warning: var(--color-palette-warning-900); /** Warning text and icons. Always with an icon. */ + --color-text-error: var(--color-palette-danger-900); /** Error text and icons. */ + --color-border-strong: var(--color-palette-neutral-dark-1100); /** Emphasis — an active field, a selected row. */ + --color-border-default: var(--color-palette-neutral-dark-600); /** Any border that carries meaning — control outlines, field borders. */ + --color-border-subtle: var(--color-palette-neutral-dark-300); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */ + --color-action-primary: var(--color-palette-primary-800); /** Primary interactive fills, strokes, links and focus rings. */ + --color-action-primary-hover: var(--color-palette-primary-900); /** Hover for action-primary. */ + --color-action-primary-pressed: var(--color-palette-primary-1000); /** Pressed for action-primary. */ + --color-action-danger: var(--color-palette-danger-800); /** Destructive actions — delete, remove, revoke. */ + --color-action-danger-hover: var(--color-palette-danger-900); /** Hover for action-danger. */ + --color-action-danger-pressed: var(--color-palette-danger-1000); /** Pressed for action-danger. */ + --color-action-neutral: var(--color-palette-neutral-dark-1000); /** The neutral action fill. Button variant="secondary" resolves here. */ + --color-action-neutral-hover: var(--color-palette-neutral-dark-1100); /** Hover for action-neutral. */ + --color-action-neutral-pressed: var(--color-black); /** Pressed for action-neutral. */ + --color-action-disabled: var(--color-palette-neutral-light-400); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */ + --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */ + --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */ + --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent "this one is chosen" tint. */ + --color-viz-1: var(--color-palette-primary-800); /** Categorical series 1. */ + --color-viz-2: var(--color-palette-accent-700); /** Categorical series 2. */ + --color-viz-3: var(--color-palette-warning-700); /** Categorical series 3. */ + --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */ + --color-viz-5: var(--color-palette-success-600); /** Categorical series 5. */ + --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */ + --color-viz-7: var(--color-palette-neutral-dark-1000); /** Categorical series 7. */ + --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */ + --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */ + --color-viz-seq-2: var(--color-palette-primary-700); /** Sequential step 2, low to high. */ + --color-viz-seq-3: var(--color-palette-primary-800); /** Sequential step 3, low to high. */ + --color-viz-seq-4: var(--color-palette-primary-900); /** Sequential step 4, low to high. */ + --color-shadow-key: var(--color-palette-neutral-dark-1100-a16); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */ + --color-shadow-ambient: var(--color-palette-neutral-dark-1100-a12); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */ + --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */ + --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-300) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%); + --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%); + } + + [data-color-mode="dark"] { + color-scheme: dark; + color: var(--color-text-default); + --color-background-current: var(--color-surface-default); + --color-surface-sunken: var(--color-black); /** Wells, insets, code blocks — anything recessed into the page. */ + --color-surface-default: var(--color-palette-neutral-dark-1100); /** The page background — the base every other surface elevates from. */ + --color-surface-raised: var(--color-palette-neutral-dark-1000); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */ + --color-surface-success: var(--color-palette-success-1000); /** The success tint behind an alert or inline confirmation. */ + --color-surface-warning: var(--color-palette-warning-1000); /** The warning tint behind an alert or inline caution. */ + --color-surface-error: var(--color-palette-danger-1000); /** The error tint behind an alert or inline validation message. */ + --color-surface-brand: var(--color-palette-neutral-dark-1100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */ + --color-surface-brand-subtle: var(--color-palette-neutral-dark-1100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */ + --color-text-default: var(--color-palette-neutral-light-200); /** Body copy and headings. */ + --color-text-subtle: var(--color-palette-neutral-light-400); /** Secondary text — descriptions, metadata, helper copy. */ + --color-text-inverse: var(--color-palette-neutral-dark-1000); /** Labels on a filled control — action-primary, -neutral, -danger. */ + --color-text-success: var(--color-palette-success-300); /** Success text and icons. */ + --color-text-warning: var(--color-palette-warning-300); /** Warning text and icons. Always with an icon. */ + --color-text-error: var(--color-palette-danger-300); /** Error text and icons. */ + --color-border-strong: var(--color-palette-neutral-light-100); /** Emphasis — an active field, a selected row. */ + --color-border-default: var(--color-palette-neutral-light-600); /** Any border that carries meaning — control outlines, field borders. */ + --color-border-subtle: var(--color-palette-neutral-light-900); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */ + --color-action-primary: var(--color-palette-primary-400); /** Primary interactive fills, strokes, links and focus rings. */ + --color-action-primary-hover: var(--color-palette-primary-300); /** Hover for action-primary. */ + --color-action-primary-pressed: var(--color-palette-primary-200); /** Pressed for action-primary. */ + --color-action-danger: var(--color-palette-danger-400); /** Destructive actions — delete, remove, revoke. */ + --color-action-danger-hover: var(--color-palette-danger-300); /** Hover for action-danger. */ + --color-action-danger-pressed: var(--color-palette-danger-200); /** Pressed for action-danger. */ + --color-action-neutral: var(--color-palette-neutral-light-200); /** The neutral action fill. Button variant="secondary" resolves here. */ + --color-action-neutral-hover: var(--color-palette-neutral-light-100); /** Hover for action-neutral. */ + --color-action-neutral-pressed: var(--color-white); /** Pressed for action-neutral. */ + --color-action-disabled: var(--color-palette-neutral-dark-800); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */ + --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */ + --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */ + --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent "this one is chosen" tint. */ + --color-viz-1: var(--color-palette-primary-500); /** Categorical series 1. */ + --color-viz-2: var(--color-palette-accent-500); /** Categorical series 2. */ + --color-viz-3: var(--color-palette-warning-400); /** Categorical series 3. */ + --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */ + --color-viz-5: var(--color-palette-success-300); /** Categorical series 5. */ + --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */ + --color-viz-7: var(--color-palette-neutral-dark-500); /** Categorical series 7. */ + --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */ + --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */ + --color-viz-seq-2: var(--color-palette-primary-500); /** Sequential step 2, low to high. */ + --color-viz-seq-3: var(--color-palette-primary-400); /** Sequential step 3, low to high. */ + --color-viz-seq-4: var(--color-palette-primary-300); /** Sequential step 4, low to high. */ + --color-shadow-key: var(--color-black-a32); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */ + --color-shadow-ambient: var(--color-black-a24); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */ + --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */ + --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-900) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%); + --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%); + } +} diff --git a/packages/gamut-styles/src/tokens/generated/skillsoft/values.ts b/packages/gamut-styles/src/tokens/generated/skillsoft/values.ts new file mode 100644 index 0000000000..1deaec1e75 --- /dev/null +++ b/packages/gamut-styles/src/tokens/generated/skillsoft/values.ts @@ -0,0 +1,978 @@ +/* Generated by @skillsoft/gamut-brand from the "skillsoft" brand. Do not edit. */ + +export const brand = { + "name": "skillsoft", + "initialMode": "light", + "hues": { + "neutral-light": 93.7, + "neutral-dark": 270.7, + "primary": 262.4, + "accent": 357.3, + "accent-alt": 287.1, + "danger": 17.3, + "warning": 88, + "success": 200 + }, + "derived": {}, + "tier": 4 +} as const; + +export const palette = { + "white": { + "oklch": "oklch(1 0 0)", + "srgb": "#ffffff" + }, + "black": { + "oklch": "oklch(0 0 0)", + "srgb": "#000000" + }, + "palette-neutral-light-100": { + "oklch": "oklch(0.9862 0.0041 93.7)", + "srgb": "#fbfaf7" + }, + "palette-neutral-light-200": { + "oklch": "oklch(0.9364 0.0075 93.7)", + "srgb": "#ebeae4" + }, + "palette-neutral-light-300": { + "oklch": "oklch(0.8497 0.0191 93.7)", + "srgb": "#d2cec0" + }, + "palette-neutral-light-400": { + "oklch": "oklch(0.7628 0.0218 93.7)", + "srgb": "#b6b2a3" + }, + "palette-neutral-light-500": { + "oklch": "oklch(0.6707 0.0241 93.7)", + "srgb": "#9a9585" + }, + "palette-neutral-light-600": { + "oklch": "oklch(0.5835 0.0239 93.7)", + "srgb": "#807b6c" + }, + "palette-neutral-light-700": { + "oklch": "oklch(0.4972 0.0193 93.7)", + "srgb": "#666256" + }, + "palette-neutral-light-800": { + "oklch": "oklch(0.4115 0.0151 93.7)", + "srgb": "#4d4b42" + }, + "palette-neutral-light-900": { + "oklch": "oklch(0.3346 0.012 93.7)", + "srgb": "#383730" + }, + "palette-neutral-light-1000": { + "oklch": "oklch(0.2499 0.0086 93.7)", + "srgb": "#23221d" + }, + "palette-neutral-light-1100": { + "oklch": "oklch(0.1925 0.0063 93.7)", + "srgb": "#151411" + }, + "palette-neutral-dark-100": { + "oklch": "oklch(0.9862 0.0012 270.7)", + "srgb": "#fafafb" + }, + "palette-neutral-dark-200": { + "oklch": "oklch(0.9364 0.0024 270.7)", + "srgb": "#e9eaeb" + }, + "palette-neutral-dark-300": { + "oklch": "oklch(0.8497 0.0081 270.7)", + "srgb": "#cbcdd3" + }, + "palette-neutral-dark-400": { + "oklch": "oklch(0.7628 0.0168 270.7)", + "srgb": "#aeb2bd" + }, + "palette-neutral-dark-500": { + "oklch": "oklch(0.6707 0.0305 270.7)", + "srgb": "#8e95a9" + }, + "palette-neutral-dark-600": { + "oklch": "oklch(0.5835 0.0454 270.7)", + "srgb": "#717a97" + }, + "palette-neutral-dark-700": { + "oklch": "oklch(0.4972 0.0538 270.7)", + "srgb": "#576182" + }, + "palette-neutral-dark-800": { + "oklch": "oklch(0.4115 0.0482 270.7)", + "srgb": "#414a65" + }, + "palette-neutral-dark-900": { + "oklch": "oklch(0.3346 0.0383 270.7)", + "srgb": "#2f364b" + }, + "palette-neutral-dark-1000": { + "oklch": "oklch(0.2499 0.0273 270.7)", + "srgb": "#1d212f" + }, + "palette-neutral-dark-1100": { + "oklch": "oklch(0.1925 0.02 270.7)", + "srgb": "#11141d" + }, + "palette-primary-100": { + "oklch": "oklch(0.9862 0.0064 262.4)", + "srgb": "#f8fbff" + }, + "palette-primary-200": { + "oklch": "oklch(0.9364 0.0121 262.4)", + "srgb": "#e6eaf2" + }, + "palette-primary-300": { + "oklch": "oklch(0.8497 0.0407 262.4)", + "srgb": "#c0cee9" + }, + "palette-primary-400": { + "oklch": "oklch(0.7628 0.0846 262.4)", + "srgb": "#95b2e8" + }, + "palette-primary-500": { + "oklch": "oklch(0.6707 0.1531 262.4)", + "srgb": "#6192f3" + }, + "palette-primary-600": { + "oklch": "oklch(0.5835 0.2274 262.4)", + "srgb": "#276eff" + }, + "palette-primary-700": { + "oklch": "oklch(0.4972 0.2254 262.4)", + "srgb": "#0a51df" + }, + "palette-primary-800": { + "oklch": "oklch(0.4115 0.1767 262.4)", + "srgb": "#0c3fa8" + }, + "palette-primary-900": { + "oklch": "oklch(0.3346 0.1405 262.4)", + "srgb": "#082e7d" + }, + "palette-primary-1000": { + "oklch": "oklch(0.2499 0.1001 262.4)", + "srgb": "#051c50" + }, + "palette-primary-1100": { + "oklch": "oklch(0.1925 0.0734 262.4)", + "srgb": "#031135" + }, + "palette-accent-100": { + "oklch": "oklch(0.9862 0.0073 357.3)", + "srgb": "#fff9fa" + }, + "palette-accent-200": { + "oklch": "oklch(0.9364 0.0141 357.3)", + "srgb": "#f2e6ea" + }, + "palette-accent-300": { + "oklch": "oklch(0.8497 0.0499 357.3)", + "srgb": "#eac1ce" + }, + "palette-accent-400": { + "oklch": "oklch(0.7628 0.11 357.3)", + "srgb": "#ea94b2" + }, + "palette-accent-500": { + "oklch": "oklch(0.6707 0.2144 357.3)", + "srgb": "#f44b97" + }, + "palette-accent-600": { + "oklch": "oklch(0.5835 0.238 357.3)", + "srgb": "#dd007c" + }, + "palette-accent-700": { + "oklch": "oklch(0.4972 0.1927 357.3)", + "srgb": "#af1363" + }, + "palette-accent-800": { + "oklch": "oklch(0.4115 0.1511 357.3)", + "srgb": "#85154b" + }, + "palette-accent-900": { + "oklch": "oklch(0.3346 0.1201 357.3)", + "srgb": "#620f37" + }, + "palette-accent-1000": { + "oklch": "oklch(0.2499 0.0856 357.3)", + "srgb": "#3f0922" + }, + "palette-accent-1100": { + "oklch": "oklch(0.1925 0.0628 357.3)", + "srgb": "#290515" + }, + "palette-accent-alt-100": { + "oklch": "oklch(0.9862 0.0067 287.1)", + "srgb": "#fafaff" + }, + "palette-accent-alt-200": { + "oklch": "oklch(0.9364 0.0127 287.1)", + "srgb": "#e9e9f2" + }, + "palette-accent-alt-300": { + "oklch": "oklch(0.8497 0.043 287.1)", + "srgb": "#cbcae9" + }, + "palette-accent-alt-400": { + "oklch": "oklch(0.7628 0.0896 287.1)", + "srgb": "#aeaae9" + }, + "palette-accent-alt-500": { + "oklch": "oklch(0.6707 0.1624 287.1)", + "srgb": "#9182f3" + }, + "palette-accent-alt-600": { + "oklch": "oklch(0.5835 0.2416 287.1)", + "srgb": "#7a52ff" + }, + "palette-accent-alt-700": { + "oklch": "oklch(0.4972 0.2722 287.1)", + "srgb": "#661af0" + }, + "palette-accent-alt-800": { + "oklch": "oklch(0.4115 0.2134 287.1)", + "srgb": "#4d1cb5" + }, + "palette-accent-alt-900": { + "oklch": "oklch(0.3346 0.1696 287.1)", + "srgb": "#381587" + }, + "palette-accent-alt-1000": { + "oklch": "oklch(0.2499 0.1209 287.1)", + "srgb": "#220d57" + }, + "palette-accent-alt-1100": { + "oklch": "oklch(0.1925 0.0887 287.1)", + "srgb": "#150739" + }, + "palette-danger-100": { + "oklch": "oklch(0.9862 0.0066 17.3)", + "srgb": "#fff9f9" + }, + "palette-danger-200": { + "oklch": "oklch(0.9364 0.0128 17.3)", + "srgb": "#f2e7e7" + }, + "palette-danger-300": { + "oklch": "oklch(0.8497 0.0452 17.3)", + "srgb": "#eac2c3" + }, + "palette-danger-400": { + "oklch": "oklch(0.7628 0.0993 17.3)", + "srgb": "#ea989b" + }, + "palette-danger-500": { + "oklch": "oklch(0.6707 0.1924 17.3)", + "srgb": "#f45769" + }, + "palette-danger-600": { + "oklch": "oklch(0.5835 0.2336 17.3)", + "srgb": "#e40045" + }, + "palette-danger-700": { + "oklch": "oklch(0.4972 0.1891 17.3)", + "srgb": "#b51439" + }, + "palette-danger-800": { + "oklch": "oklch(0.4115 0.1482 17.3)", + "srgb": "#89162c" + }, + "palette-danger-900": { + "oklch": "oklch(0.3346 0.1178 17.3)", + "srgb": "#66101f" + }, + "palette-danger-1000": { + "oklch": "oklch(0.2499 0.084 17.3)", + "srgb": "#410912" + }, + "palette-danger-1100": { + "oklch": "oklch(0.1925 0.0616 17.3)", + "srgb": "#2a050a" + }, + "palette-warning-100": { + "oklch": "oklch(0.9862 0.016 88)", + "srgb": "#fffaef" + }, + "palette-warning-200": { + "oklch": "oklch(0.9364 0.03 88)", + "srgb": "#f2e9d4" + }, + "palette-warning-300": { + "oklch": "oklch(0.8497 0.0955 88)", + "srgb": "#e8cb84" + }, + "palette-warning-400": { + "oklch": "oklch(0.7628 0.1091 88)", + "srgb": "#cfae5b" + }, + "palette-warning-500": { + "oklch": "oklch(0.6707 0.1206 88)", + "srgb": "#b4902d" + }, + "palette-warning-600": { + "oklch": "oklch(0.5835 0.1192 88)", + "srgb": "#997600" + }, + "palette-warning-700": { + "oklch": "oklch(0.4972 0.0965 88)", + "srgb": "#795e0e" + }, + "palette-warning-800": { + "oklch": "oklch(0.4115 0.0757 88)", + "srgb": "#5c4810" + }, + "palette-warning-900": { + "oklch": "oklch(0.3346 0.0601 88)", + "srgb": "#43340b" + }, + "palette-warning-1000": { + "oklch": "oklch(0.2499 0.0429 88)", + "srgb": "#2a2006" + }, + "palette-warning-1100": { + "oklch": "oklch(0.1925 0.0314 88)", + "srgb": "#1a1304" + }, + "palette-success-100": { + "oklch": "oklch(0.9862 0.017 200)", + "srgb": "#eefeff" + }, + "palette-success-200": { + "oklch": "oklch(0.9364 0.0318 200)", + "srgb": "#d2f1f2" + }, + "palette-success-300": { + "oklch": "oklch(0.8497 0.0794 200)", + "srgb": "#8ddee2" + }, + "palette-success-400": { + "oklch": "oklch(0.7628 0.0907 200)", + "srgb": "#64c4c9" + }, + "palette-success-500": { + "oklch": "oklch(0.6707 0.1003 200)", + "srgb": "#34a8ae" + }, + "palette-success-600": { + "oklch": "oklch(0.5835 0.0991 200)", + "srgb": "#018d92" + }, + "palette-success-700": { + "oklch": "oklch(0.4972 0.0803 200)", + "srgb": "#117074" + }, + "palette-success-800": { + "oklch": "oklch(0.4115 0.0629 200)", + "srgb": "#135558" + }, + "palette-success-900": { + "oklch": "oklch(0.3346 0.05 200)", + "srgb": "#0e3e41" + }, + "palette-success-1000": { + "oklch": "oklch(0.2499 0.0356 200)", + "srgb": "#082728" + }, + "palette-success-1100": { + "oklch": "oklch(0.1925 0.0261 200)", + "srgb": "#041819" + }, + "palette-primary-600-a6": { + "oklch": "oklch(0.5835 0.2274 262.4 / 0.06)", + "srgb": "rgba(39, 110, 255, 0.06)" + }, + "palette-primary-600-a9": { + "oklch": "oklch(0.5835 0.2274 262.4 / 0.09)", + "srgb": "rgba(39, 110, 255, 0.09)" + }, + "palette-primary-600-a8": { + "oklch": "oklch(0.5835 0.2274 262.4 / 0.08)", + "srgb": "rgba(39, 110, 255, 0.08)" + }, + "palette-neutral-dark-1100-a16": { + "oklch": "oklch(0.1925 0.02 270.7 / 0.16)", + "srgb": "rgba(17, 20, 29, 0.16)" + }, + "black-a32": { + "oklch": "oklch(0 0 0 / 0.32)", + "srgb": "rgba(0, 0, 0, 0.32)" + }, + "palette-neutral-dark-1100-a12": { + "oklch": "oklch(0.1925 0.02 270.7 / 0.12)", + "srgb": "rgba(17, 20, 29, 0.12)" + }, + "black-a24": { + "oklch": "oklch(0 0 0 / 0.24)", + "srgb": "rgba(0, 0, 0, 0.24)" + }, + "black-a60": { + "oklch": "oklch(0 0 0 / 0.6)", + "srgb": "rgba(0, 0, 0, 0.6)" + } +} as const; + +export const roles = { + "light": { + "surface-sunken": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0075 93.7)", + "srgb": "#ebeae4" + }, + "surface-default": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9862 0.0041 93.7)", + "srgb": "#fbfaf7" + }, + "surface-raised": { + "ref": "white", + "oklch": "oklch(1 0 0)", + "srgb": "#ffffff" + }, + "surface-success": { + "ref": "palette-success-200", + "oklch": "oklch(0.9364 0.0318 200)", + "srgb": "#d2f1f2" + }, + "surface-warning": { + "ref": "palette-warning-200", + "oklch": "oklch(0.9364 0.03 88)", + "srgb": "#f2e9d4" + }, + "surface-error": { + "ref": "palette-danger-200", + "oklch": "oklch(0.9364 0.0128 17.3)", + "srgb": "#f2e7e7" + }, + "surface-brand": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9862 0.0041 93.7)", + "srgb": "#fbfaf7" + }, + "surface-brand-subtle": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9862 0.0041 93.7)", + "srgb": "#fbfaf7" + }, + "text-default": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0273 270.7)", + "srgb": "#1d212f" + }, + "text-subtle": { + "ref": "palette-neutral-dark-800", + "oklch": "oklch(0.4115 0.0482 270.7)", + "srgb": "#414a65" + }, + "text-inverse": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0075 93.7)", + "srgb": "#ebeae4" + }, + "text-success": { + "ref": "palette-success-900", + "oklch": "oklch(0.3346 0.05 200)", + "srgb": "#0e3e41" + }, + "text-warning": { + "ref": "palette-warning-900", + "oklch": "oklch(0.3346 0.0601 88)", + "srgb": "#43340b" + }, + "text-error": { + "ref": "palette-danger-900", + "oklch": "oklch(0.3346 0.1178 17.3)", + "srgb": "#66101f" + }, + "border-strong": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.02 270.7)", + "srgb": "#11141d" + }, + "border-default": { + "ref": "palette-neutral-dark-600", + "oklch": "oklch(0.5835 0.0454 270.7)", + "srgb": "#717a97" + }, + "border-subtle": { + "ref": "palette-neutral-dark-300", + "oklch": "oklch(0.8497 0.0081 270.7)", + "srgb": "#cbcdd3" + }, + "action-primary": { + "ref": "palette-primary-800", + "oklch": "oklch(0.4115 0.1767 262.4)", + "srgb": "#0c3fa8" + }, + "action-primary-hover": { + "ref": "palette-primary-900", + "oklch": "oklch(0.3346 0.1405 262.4)", + "srgb": "#082e7d" + }, + "action-primary-pressed": { + "ref": "palette-primary-1000", + "oklch": "oklch(0.2499 0.1001 262.4)", + "srgb": "#051c50" + }, + "action-danger": { + "ref": "palette-danger-800", + "oklch": "oklch(0.4115 0.1482 17.3)", + "srgb": "#89162c" + }, + "action-danger-hover": { + "ref": "palette-danger-900", + "oklch": "oklch(0.3346 0.1178 17.3)", + "srgb": "#66101f" + }, + "action-danger-pressed": { + "ref": "palette-danger-1000", + "oklch": "oklch(0.2499 0.084 17.3)", + "srgb": "#410912" + }, + "action-neutral": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0273 270.7)", + "srgb": "#1d212f" + }, + "action-neutral-hover": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.02 270.7)", + "srgb": "#11141d" + }, + "action-neutral-pressed": { + "ref": "black", + "oklch": "oklch(0 0 0)", + "srgb": "#000000" + }, + "action-disabled": { + "ref": "palette-neutral-light-400", + "oklch": "oklch(0.7628 0.0218 93.7)", + "srgb": "#b6b2a3" + }, + "state-hover": { + "ref": "palette-primary-600-a6", + "oklch": "oklch(0.5835 0.2274 262.4 / 0.06)", + "srgb": "rgba(39, 110, 255, 0.06)" + }, + "state-pressed": { + "ref": "palette-primary-600-a9", + "oklch": "oklch(0.5835 0.2274 262.4 / 0.09)", + "srgb": "rgba(39, 110, 255, 0.09)" + }, + "state-selected": { + "ref": "palette-primary-600-a8", + "oklch": "oklch(0.5835 0.2274 262.4 / 0.08)", + "srgb": "rgba(39, 110, 255, 0.08)" + }, + "viz-1": { + "ref": "palette-primary-800", + "oklch": "oklch(0.4115 0.1767 262.4)", + "srgb": "#0c3fa8" + }, + "viz-2": { + "ref": "palette-accent-700", + "oklch": "oklch(0.4972 0.1927 357.3)", + "srgb": "#af1363" + }, + "viz-3": { + "ref": "palette-warning-700", + "oklch": "oklch(0.4972 0.0965 88)", + "srgb": "#795e0e" + }, + "viz-4": { + "ref": "palette-accent-alt-600", + "oklch": "oklch(0.5835 0.2416 287.1)", + "srgb": "#7a52ff" + }, + "viz-5": { + "ref": "palette-success-600", + "oklch": "oklch(0.5835 0.0991 200)", + "srgb": "#018d92" + }, + "viz-6": { + "ref": "palette-danger-600", + "oklch": "oklch(0.5835 0.2336 17.3)", + "srgb": "#e40045" + }, + "viz-7": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0273 270.7)", + "srgb": "#1d212f" + }, + "viz-8": { + "ref": "palette-neutral-light-600", + "oklch": "oklch(0.5835 0.0239 93.7)", + "srgb": "#807b6c" + }, + "viz-seq-1": { + "ref": "palette-primary-600", + "oklch": "oklch(0.5835 0.2274 262.4)", + "srgb": "#276eff" + }, + "viz-seq-2": { + "ref": "palette-primary-700", + "oklch": "oklch(0.4972 0.2254 262.4)", + "srgb": "#0a51df" + }, + "viz-seq-3": { + "ref": "palette-primary-800", + "oklch": "oklch(0.4115 0.1767 262.4)", + "srgb": "#0c3fa8" + }, + "viz-seq-4": { + "ref": "palette-primary-900", + "oklch": "oklch(0.3346 0.1405 262.4)", + "srgb": "#082e7d" + }, + "shadow-key": { + "ref": "palette-neutral-dark-1100-a16", + "oklch": "oklch(0.1925 0.02 270.7 / 0.16)", + "srgb": "rgba(17, 20, 29, 0.16)" + }, + "shadow-ambient": { + "ref": "palette-neutral-dark-1100-a12", + "oklch": "oklch(0.1925 0.02 270.7 / 0.12)", + "srgb": "rgba(17, 20, 29, 0.12)" + }, + "shadow-scrim": { + "ref": "black-a60", + "oklch": "oklch(0 0 0 / 0.6)", + "srgb": "rgba(0, 0, 0, 0.6)" + } + }, + "dark": { + "surface-sunken": { + "ref": "black", + "oklch": "oklch(0 0 0)", + "srgb": "#000000" + }, + "surface-default": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.02 270.7)", + "srgb": "#11141d" + }, + "surface-raised": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0273 270.7)", + "srgb": "#1d212f" + }, + "surface-success": { + "ref": "palette-success-1000", + "oklch": "oklch(0.2499 0.0356 200)", + "srgb": "#082728" + }, + "surface-warning": { + "ref": "palette-warning-1000", + "oklch": "oklch(0.2499 0.0429 88)", + "srgb": "#2a2006" + }, + "surface-error": { + "ref": "palette-danger-1000", + "oklch": "oklch(0.2499 0.084 17.3)", + "srgb": "#410912" + }, + "surface-brand": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.02 270.7)", + "srgb": "#11141d" + }, + "surface-brand-subtle": { + "ref": "palette-neutral-dark-1100", + "oklch": "oklch(0.1925 0.02 270.7)", + "srgb": "#11141d" + }, + "text-default": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0075 93.7)", + "srgb": "#ebeae4" + }, + "text-subtle": { + "ref": "palette-neutral-light-400", + "oklch": "oklch(0.7628 0.0218 93.7)", + "srgb": "#b6b2a3" + }, + "text-inverse": { + "ref": "palette-neutral-dark-1000", + "oklch": "oklch(0.2499 0.0273 270.7)", + "srgb": "#1d212f" + }, + "text-success": { + "ref": "palette-success-300", + "oklch": "oklch(0.8497 0.0794 200)", + "srgb": "#8ddee2" + }, + "text-warning": { + "ref": "palette-warning-300", + "oklch": "oklch(0.8497 0.0955 88)", + "srgb": "#e8cb84" + }, + "text-error": { + "ref": "palette-danger-300", + "oklch": "oklch(0.8497 0.0452 17.3)", + "srgb": "#eac2c3" + }, + "border-strong": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9862 0.0041 93.7)", + "srgb": "#fbfaf7" + }, + "border-default": { + "ref": "palette-neutral-light-600", + "oklch": "oklch(0.5835 0.0239 93.7)", + "srgb": "#807b6c" + }, + "border-subtle": { + "ref": "palette-neutral-light-900", + "oklch": "oklch(0.3346 0.012 93.7)", + "srgb": "#383730" + }, + "action-primary": { + "ref": "palette-primary-400", + "oklch": "oklch(0.7628 0.0846 262.4)", + "srgb": "#95b2e8" + }, + "action-primary-hover": { + "ref": "palette-primary-300", + "oklch": "oklch(0.8497 0.0407 262.4)", + "srgb": "#c0cee9" + }, + "action-primary-pressed": { + "ref": "palette-primary-200", + "oklch": "oklch(0.9364 0.0121 262.4)", + "srgb": "#e6eaf2" + }, + "action-danger": { + "ref": "palette-danger-400", + "oklch": "oklch(0.7628 0.0993 17.3)", + "srgb": "#ea989b" + }, + "action-danger-hover": { + "ref": "palette-danger-300", + "oklch": "oklch(0.8497 0.0452 17.3)", + "srgb": "#eac2c3" + }, + "action-danger-pressed": { + "ref": "palette-danger-200", + "oklch": "oklch(0.9364 0.0128 17.3)", + "srgb": "#f2e7e7" + }, + "action-neutral": { + "ref": "palette-neutral-light-200", + "oklch": "oklch(0.9364 0.0075 93.7)", + "srgb": "#ebeae4" + }, + "action-neutral-hover": { + "ref": "palette-neutral-light-100", + "oklch": "oklch(0.9862 0.0041 93.7)", + "srgb": "#fbfaf7" + }, + "action-neutral-pressed": { + "ref": "white", + "oklch": "oklch(1 0 0)", + "srgb": "#ffffff" + }, + "action-disabled": { + "ref": "palette-neutral-dark-800", + "oklch": "oklch(0.4115 0.0482 270.7)", + "srgb": "#414a65" + }, + "state-hover": { + "ref": "palette-primary-600-a6", + "oklch": "oklch(0.5835 0.2274 262.4 / 0.06)", + "srgb": "rgba(39, 110, 255, 0.06)" + }, + "state-pressed": { + "ref": "palette-primary-600-a9", + "oklch": "oklch(0.5835 0.2274 262.4 / 0.09)", + "srgb": "rgba(39, 110, 255, 0.09)" + }, + "state-selected": { + "ref": "palette-primary-600-a8", + "oklch": "oklch(0.5835 0.2274 262.4 / 0.08)", + "srgb": "rgba(39, 110, 255, 0.08)" + }, + "viz-1": { + "ref": "palette-primary-500", + "oklch": "oklch(0.6707 0.1531 262.4)", + "srgb": "#6192f3" + }, + "viz-2": { + "ref": "palette-accent-500", + "oklch": "oklch(0.6707 0.2144 357.3)", + "srgb": "#f44b97" + }, + "viz-3": { + "ref": "palette-warning-400", + "oklch": "oklch(0.7628 0.1091 88)", + "srgb": "#cfae5b" + }, + "viz-4": { + "ref": "palette-accent-alt-600", + "oklch": "oklch(0.5835 0.2416 287.1)", + "srgb": "#7a52ff" + }, + "viz-5": { + "ref": "palette-success-300", + "oklch": "oklch(0.8497 0.0794 200)", + "srgb": "#8ddee2" + }, + "viz-6": { + "ref": "palette-danger-600", + "oklch": "oklch(0.5835 0.2336 17.3)", + "srgb": "#e40045" + }, + "viz-7": { + "ref": "palette-neutral-dark-500", + "oklch": "oklch(0.6707 0.0305 270.7)", + "srgb": "#8e95a9" + }, + "viz-8": { + "ref": "palette-neutral-light-600", + "oklch": "oklch(0.5835 0.0239 93.7)", + "srgb": "#807b6c" + }, + "viz-seq-1": { + "ref": "palette-primary-600", + "oklch": "oklch(0.5835 0.2274 262.4)", + "srgb": "#276eff" + }, + "viz-seq-2": { + "ref": "palette-primary-500", + "oklch": "oklch(0.6707 0.1531 262.4)", + "srgb": "#6192f3" + }, + "viz-seq-3": { + "ref": "palette-primary-400", + "oklch": "oklch(0.7628 0.0846 262.4)", + "srgb": "#95b2e8" + }, + "viz-seq-4": { + "ref": "palette-primary-300", + "oklch": "oklch(0.8497 0.0407 262.4)", + "srgb": "#c0cee9" + }, + "shadow-key": { + "ref": "black-a32", + "oklch": "oklch(0 0 0 / 0.32)", + "srgb": "rgba(0, 0, 0, 0.32)" + }, + "shadow-ambient": { + "ref": "black-a24", + "oklch": "oklch(0 0 0 / 0.24)", + "srgb": "rgba(0, 0, 0, 0.24)" + }, + "shadow-scrim": { + "ref": "black-a60", + "oklch": "oklch(0 0 0 / 0.6)", + "srgb": "rgba(0, 0, 0, 0.6)" + } + } +} as const; + +export const gradients = { + "light": { + "surface-brand": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.8497 0.0499 357.3 / 0.45) 0%, oklch(0.8497 0.0499 357.3 / 0.43) 16%, oklch(0.8497 0.0499 357.3 / 0.24) 36%, oklch(0.8497 0.0499 357.3 / 0.11) 56%, oklch(0.8497 0.0499 357.3 / 0.04) 76%, oklch(0.8497 0.0499 357.3 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.8497 0.0407 262.4 / 0.45) 0%, oklch(0.8497 0.0407 262.4 / 0.43) 16%, oklch(0.8497 0.0407 262.4 / 0.24) 36%, oklch(0.8497 0.0407 262.4 / 0.11) 56%, oklch(0.8497 0.0407 262.4 / 0.04) 76%, oklch(0.8497 0.0407 262.4 / 0) 100%)", + "surface-brand-subtle": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.8497 0.0407 262.4 / 0.45) 0%, oklch(0.8497 0.0407 262.4 / 0.43) 16%, oklch(0.8497 0.0407 262.4 / 0.24) 36%, oklch(0.8497 0.0407 262.4 / 0.11) 56%, oklch(0.8497 0.0407 262.4 / 0.04) 76%, oklch(0.8497 0.0407 262.4 / 0) 100%)" + }, + "dark": { + "surface-brand": "radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, oklch(0.3346 0.1201 357.3 / 0.45) 0%, oklch(0.3346 0.1201 357.3 / 0.43) 16%, oklch(0.3346 0.1201 357.3 / 0.24) 36%, oklch(0.3346 0.1201 357.3 / 0.11) 56%, oklch(0.3346 0.1201 357.3 / 0.04) 76%, oklch(0.3346 0.1201 357.3 / 0) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, oklch(0.3346 0.1405 262.4 / 0.45) 0%, oklch(0.3346 0.1405 262.4 / 0.43) 16%, oklch(0.3346 0.1405 262.4 / 0.24) 36%, oklch(0.3346 0.1405 262.4 / 0.11) 56%, oklch(0.3346 0.1405 262.4 / 0.04) 76%, oklch(0.3346 0.1405 262.4 / 0) 100%)", + "surface-brand-subtle": "radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, oklch(0.3346 0.1405 262.4 / 0.45) 0%, oklch(0.3346 0.1405 262.4 / 0.43) 16%, oklch(0.3346 0.1405 262.4 / 0.24) 36%, oklch(0.3346 0.1405 262.4 / 0.11) 56%, oklch(0.3346 0.1405 262.4 / 0.04) 76%, oklch(0.3346 0.1405 262.4 / 0) 100%)" + } +} as const; + +/* Palette color → the mode its content uses. Background reads this instead of measuring. */ +export const contentModes = { + "white": "light", + "black": "dark", + "palette-neutral-light-100": "light", + "palette-neutral-light-200": "light", + "palette-neutral-light-300": "light", + "palette-neutral-light-400": "light", + "palette-neutral-light-500": "light", + "palette-neutral-light-600": "light", + "palette-neutral-light-700": "dark", + "palette-neutral-light-800": "dark", + "palette-neutral-light-900": "dark", + "palette-neutral-light-1000": "dark", + "palette-neutral-light-1100": "dark", + "palette-neutral-dark-100": "light", + "palette-neutral-dark-200": "light", + "palette-neutral-dark-300": "light", + "palette-neutral-dark-400": "light", + "palette-neutral-dark-500": "light", + "palette-neutral-dark-600": "light", + "palette-neutral-dark-700": "dark", + "palette-neutral-dark-800": "dark", + "palette-neutral-dark-900": "dark", + "palette-neutral-dark-1000": "dark", + "palette-neutral-dark-1100": "dark", + "palette-primary-100": "light", + "palette-primary-200": "light", + "palette-primary-300": "light", + "palette-primary-400": "light", + "palette-primary-500": "light", + "palette-primary-600": "dark", + "palette-primary-700": "dark", + "palette-primary-800": "dark", + "palette-primary-900": "dark", + "palette-primary-1000": "dark", + "palette-primary-1100": "dark", + "palette-accent-100": "light", + "palette-accent-200": "light", + "palette-accent-300": "light", + "palette-accent-400": "light", + "palette-accent-500": "light", + "palette-accent-600": "dark", + "palette-accent-700": "dark", + "palette-accent-800": "dark", + "palette-accent-900": "dark", + "palette-accent-1000": "dark", + "palette-accent-1100": "dark", + "palette-accent-alt-100": "light", + "palette-accent-alt-200": "light", + "palette-accent-alt-300": "light", + "palette-accent-alt-400": "light", + "palette-accent-alt-500": "light", + "palette-accent-alt-600": "dark", + "palette-accent-alt-700": "dark", + "palette-accent-alt-800": "dark", + "palette-accent-alt-900": "dark", + "palette-accent-alt-1000": "dark", + "palette-accent-alt-1100": "dark", + "palette-danger-100": "light", + "palette-danger-200": "light", + "palette-danger-300": "light", + "palette-danger-400": "light", + "palette-danger-500": "light", + "palette-danger-600": "dark", + "palette-danger-700": "dark", + "palette-danger-800": "dark", + "palette-danger-900": "dark", + "palette-danger-1000": "dark", + "palette-danger-1100": "dark", + "palette-warning-100": "light", + "palette-warning-200": "light", + "palette-warning-300": "light", + "palette-warning-400": "light", + "palette-warning-500": "light", + "palette-warning-600": "light", + "palette-warning-700": "dark", + "palette-warning-800": "dark", + "palette-warning-900": "dark", + "palette-warning-1000": "dark", + "palette-warning-1100": "dark", + "palette-success-100": "light", + "palette-success-200": "light", + "palette-success-300": "light", + "palette-success-400": "light", + "palette-success-500": "light", + "palette-success-600": "light", + "palette-success-700": "dark", + "palette-success-800": "dark", + "palette-success-900": "dark", + "palette-success-1000": "dark", + "palette-success-1100": "dark" +} as const; + +/* This brand's tokens.css, for GamutProvider to inject. */ +export const tokensCss = "/* Generated by @skillsoft/gamut-brand from the \"skillsoft\" brand. Do not edit. */\n@layer reset, base, tokens, recipes, utilities;\n\n@layer tokens {\n :root, [data-brand=\"skillsoft\"] {\n --color-white: oklch(1.0000 0.0000 0.00);\n --color-black: oklch(0.0000 0.0000 0.00);\n --color-palette-neutral-light-100: oklch(0.9862 0.0041 93.70);\n --color-palette-neutral-light-200: oklch(0.9364 0.0075 93.70);\n --color-palette-neutral-light-300: oklch(0.8497 0.0191 93.70);\n --color-palette-neutral-light-400: oklch(0.7628 0.0218 93.70);\n --color-palette-neutral-light-500: oklch(0.6707 0.0241 93.70);\n --color-palette-neutral-light-600: oklch(0.5835 0.0239 93.70);\n --color-palette-neutral-light-700: oklch(0.4972 0.0193 93.70);\n --color-palette-neutral-light-800: oklch(0.4115 0.0151 93.70);\n --color-palette-neutral-light-900: oklch(0.3346 0.0120 93.70);\n --color-palette-neutral-light-1000: oklch(0.2499 0.0086 93.70);\n --color-palette-neutral-light-1100: oklch(0.1925 0.0063 93.70);\n --color-palette-neutral-dark-100: oklch(0.9862 0.0012 270.70);\n --color-palette-neutral-dark-200: oklch(0.9364 0.0024 270.70);\n --color-palette-neutral-dark-300: oklch(0.8497 0.0081 270.70);\n --color-palette-neutral-dark-400: oklch(0.7628 0.0168 270.70);\n --color-palette-neutral-dark-500: oklch(0.6707 0.0305 270.70);\n --color-palette-neutral-dark-600: oklch(0.5835 0.0454 270.70);\n --color-palette-neutral-dark-700: oklch(0.4972 0.0538 270.70);\n --color-palette-neutral-dark-800: oklch(0.4115 0.0482 270.70);\n --color-palette-neutral-dark-900: oklch(0.3346 0.0383 270.70);\n --color-palette-neutral-dark-1000: oklch(0.2499 0.0273 270.70);\n --color-palette-neutral-dark-1100: oklch(0.1925 0.0200 270.70);\n --color-palette-neutral-dark-1100-a16: oklch(0.1925 0.0200 270.70 / 0.16);\n --color-palette-neutral-dark-1100-a12: oklch(0.1925 0.0200 270.70 / 0.12);\n --color-palette-primary-100: oklch(0.9862 0.0064 262.40);\n --color-palette-primary-200: oklch(0.9364 0.0121 262.40);\n --color-palette-primary-300: oklch(0.8497 0.0407 262.40);\n --color-palette-primary-400: oklch(0.7628 0.0846 262.40);\n --color-palette-primary-500: oklch(0.6707 0.1531 262.40);\n --color-palette-primary-600: oklch(0.5835 0.2274 262.40);\n --color-palette-primary-700: oklch(0.4972 0.2254 262.40);\n --color-palette-primary-800: oklch(0.4115 0.1767 262.40);\n --color-palette-primary-900: oklch(0.3346 0.1405 262.40);\n --color-palette-primary-1000: oklch(0.2499 0.1001 262.40);\n --color-palette-primary-1100: oklch(0.1925 0.0734 262.40);\n --color-palette-primary-600-a6: oklch(0.5835 0.2274 262.40 / 0.06);\n --color-palette-primary-600-a9: oklch(0.5835 0.2274 262.40 / 0.09);\n --color-palette-primary-600-a8: oklch(0.5835 0.2274 262.40 / 0.08);\n --color-palette-accent-100: oklch(0.9862 0.0073 357.30);\n --color-palette-accent-200: oklch(0.9364 0.0141 357.30);\n --color-palette-accent-300: oklch(0.8497 0.0499 357.30);\n --color-palette-accent-400: oklch(0.7628 0.1100 357.30);\n --color-palette-accent-500: oklch(0.6707 0.2144 357.30);\n --color-palette-accent-600: oklch(0.5835 0.2380 357.30);\n --color-palette-accent-700: oklch(0.4972 0.1927 357.30);\n --color-palette-accent-800: oklch(0.4115 0.1511 357.30);\n --color-palette-accent-900: oklch(0.3346 0.1201 357.30);\n --color-palette-accent-1000: oklch(0.2499 0.0856 357.30);\n --color-palette-accent-1100: oklch(0.1925 0.0628 357.30);\n --color-palette-accent-alt-100: oklch(0.9862 0.0067 287.10);\n --color-palette-accent-alt-200: oklch(0.9364 0.0127 287.10);\n --color-palette-accent-alt-300: oklch(0.8497 0.0430 287.10);\n --color-palette-accent-alt-400: oklch(0.7628 0.0896 287.10);\n --color-palette-accent-alt-500: oklch(0.6707 0.1624 287.10);\n --color-palette-accent-alt-600: oklch(0.5835 0.2416 287.10);\n --color-palette-accent-alt-700: oklch(0.4972 0.2722 287.10);\n --color-palette-accent-alt-800: oklch(0.4115 0.2134 287.10);\n --color-palette-accent-alt-900: oklch(0.3346 0.1696 287.10);\n --color-palette-accent-alt-1000: oklch(0.2499 0.1209 287.10);\n --color-palette-accent-alt-1100: oklch(0.1925 0.0887 287.10);\n --color-palette-danger-100: oklch(0.9862 0.0066 17.30);\n --color-palette-danger-200: oklch(0.9364 0.0128 17.30);\n --color-palette-danger-300: oklch(0.8497 0.0452 17.30);\n --color-palette-danger-400: oklch(0.7628 0.0993 17.30);\n --color-palette-danger-500: oklch(0.6707 0.1924 17.30);\n --color-palette-danger-600: oklch(0.5835 0.2336 17.30);\n --color-palette-danger-700: oklch(0.4972 0.1891 17.30);\n --color-palette-danger-800: oklch(0.4115 0.1482 17.30);\n --color-palette-danger-900: oklch(0.3346 0.1178 17.30);\n --color-palette-danger-1000: oklch(0.2499 0.0840 17.30);\n --color-palette-danger-1100: oklch(0.1925 0.0616 17.30);\n --color-palette-warning-100: oklch(0.9862 0.0160 88.00);\n --color-palette-warning-200: oklch(0.9364 0.0300 88.00);\n --color-palette-warning-300: oklch(0.8497 0.0955 88.00);\n --color-palette-warning-400: oklch(0.7628 0.1091 88.00);\n --color-palette-warning-500: oklch(0.6707 0.1206 88.00);\n --color-palette-warning-600: oklch(0.5835 0.1192 88.00);\n --color-palette-warning-700: oklch(0.4972 0.0965 88.00);\n --color-palette-warning-800: oklch(0.4115 0.0757 88.00);\n --color-palette-warning-900: oklch(0.3346 0.0601 88.00);\n --color-palette-warning-1000: oklch(0.2499 0.0429 88.00);\n --color-palette-warning-1100: oklch(0.1925 0.0314 88.00);\n --color-palette-success-100: oklch(0.9862 0.0170 200.00);\n --color-palette-success-200: oklch(0.9364 0.0318 200.00);\n --color-palette-success-300: oklch(0.8497 0.0794 200.00);\n --color-palette-success-400: oklch(0.7628 0.0907 200.00);\n --color-palette-success-500: oklch(0.6707 0.1003 200.00);\n --color-palette-success-600: oklch(0.5835 0.0991 200.00);\n --color-palette-success-700: oklch(0.4972 0.0803 200.00);\n --color-palette-success-800: oklch(0.4115 0.0629 200.00);\n --color-palette-success-900: oklch(0.3346 0.0500 200.00);\n --color-palette-success-1000: oklch(0.2499 0.0356 200.00);\n --color-palette-success-1100: oklch(0.1925 0.0261 200.00);\n --color-black-a32: oklch(0.0000 0.0000 0.00 / 0.32);\n --color-black-a24: oklch(0.0000 0.0000 0.00 / 0.24);\n --color-black-a60: oklch(0.0000 0.0000 0.00 / 0.6);\n }\n\n :root, [data-color-mode=\"light\"] {\n color-scheme: light;\n color: var(--color-text-default);\n --color-background-current: var(--color-surface-default);\n --color-surface-sunken: var(--color-palette-neutral-light-200); /** Wells, insets, code blocks — anything recessed into the page. */\n --color-surface-default: var(--color-palette-neutral-light-100); /** The page background — the base every other surface elevates from. */\n --color-surface-raised: var(--color-white); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */\n --color-surface-success: var(--color-palette-success-200); /** The success tint behind an alert or inline confirmation. */\n --color-surface-warning: var(--color-palette-warning-200); /** The warning tint behind an alert or inline caution. */\n --color-surface-error: var(--color-palette-danger-200); /** The error tint behind an alert or inline validation message. */\n --color-surface-brand: var(--color-palette-neutral-light-100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */\n --color-surface-brand-subtle: var(--color-palette-neutral-light-100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */\n --color-text-default: var(--color-palette-neutral-dark-1000); /** Body copy and headings. */\n --color-text-subtle: var(--color-palette-neutral-dark-800); /** Secondary text — descriptions, metadata, helper copy. */\n --color-text-inverse: var(--color-palette-neutral-light-200); /** Labels on a filled control — action-primary, -neutral, -danger. */\n --color-text-success: var(--color-palette-success-900); /** Success text and icons. */\n --color-text-warning: var(--color-palette-warning-900); /** Warning text and icons. Always with an icon. */\n --color-text-error: var(--color-palette-danger-900); /** Error text and icons. */\n --color-border-strong: var(--color-palette-neutral-dark-1100); /** Emphasis — an active field, a selected row. */\n --color-border-default: var(--color-palette-neutral-dark-600); /** Any border that carries meaning — control outlines, field borders. */\n --color-border-subtle: var(--color-palette-neutral-dark-300); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */\n --color-action-primary: var(--color-palette-primary-800); /** Primary interactive fills, strokes, links and focus rings. */\n --color-action-primary-hover: var(--color-palette-primary-900); /** Hover for action-primary. */\n --color-action-primary-pressed: var(--color-palette-primary-1000); /** Pressed for action-primary. */\n --color-action-danger: var(--color-palette-danger-800); /** Destructive actions — delete, remove, revoke. */\n --color-action-danger-hover: var(--color-palette-danger-900); /** Hover for action-danger. */\n --color-action-danger-pressed: var(--color-palette-danger-1000); /** Pressed for action-danger. */\n --color-action-neutral: var(--color-palette-neutral-dark-1000); /** The neutral action fill. Button variant=\"secondary\" resolves here. */\n --color-action-neutral-hover: var(--color-palette-neutral-dark-1100); /** Hover for action-neutral. */\n --color-action-neutral-pressed: var(--color-black); /** Pressed for action-neutral. */\n --color-action-disabled: var(--color-palette-neutral-light-400); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */\n --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */\n --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */\n --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent \"this one is chosen\" tint. */\n --color-viz-1: var(--color-palette-primary-800); /** Categorical series 1. */\n --color-viz-2: var(--color-palette-accent-700); /** Categorical series 2. */\n --color-viz-3: var(--color-palette-warning-700); /** Categorical series 3. */\n --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */\n --color-viz-5: var(--color-palette-success-600); /** Categorical series 5. */\n --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */\n --color-viz-7: var(--color-palette-neutral-dark-1000); /** Categorical series 7. */\n --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */\n --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */\n --color-viz-seq-2: var(--color-palette-primary-700); /** Sequential step 2, low to high. */\n --color-viz-seq-3: var(--color-palette-primary-800); /** Sequential step 3, low to high. */\n --color-viz-seq-4: var(--color-palette-primary-900); /** Sequential step 4, low to high. */\n --color-shadow-key: var(--color-palette-neutral-dark-1100-a16); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */\n --color-shadow-ambient: var(--color-palette-neutral-dark-1100-a12); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */\n --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */\n --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-300) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%);\n --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-300) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-300) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-300) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-300) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-300) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-300) 0%, transparent) 100%);\n }\n\n [data-color-mode=\"dark\"] {\n color-scheme: dark;\n color: var(--color-text-default);\n --color-background-current: var(--color-surface-default);\n --color-surface-sunken: var(--color-black); /** Wells, insets, code blocks — anything recessed into the page. */\n --color-surface-default: var(--color-palette-neutral-dark-1100); /** The page background — the base every other surface elevates from. */\n --color-surface-raised: var(--color-palette-neutral-dark-1000); /** The lighter surface step, not a depth: cards, menus, dialogs, fields, and flat bordered sections. Shadow, not the fill, says how high something sits. */\n --color-surface-success: var(--color-palette-success-1000); /** The success tint behind an alert or inline confirmation. */\n --color-surface-warning: var(--color-palette-warning-1000); /** The warning tint behind an alert or inline caution. */\n --color-surface-error: var(--color-palette-danger-1000); /** The error tint behind an alert or inline validation message. */\n --color-surface-brand: var(--color-palette-neutral-dark-1100); /** The two-color brand gradient. The color is the flat fallback; the gradient ships as --gradient-surface-brand. */\n --color-surface-brand-subtle: var(--color-palette-neutral-dark-1100); /** The one-color brand gradient, for backgrounds behind real text. Ships as --gradient-surface-brand-subtle. */\n --color-text-default: var(--color-palette-neutral-light-200); /** Body copy and headings. */\n --color-text-subtle: var(--color-palette-neutral-light-400); /** Secondary text — descriptions, metadata, helper copy. */\n --color-text-inverse: var(--color-palette-neutral-dark-1000); /** Labels on a filled control — action-primary, -neutral, -danger. */\n --color-text-success: var(--color-palette-success-300); /** Success text and icons. */\n --color-text-warning: var(--color-palette-warning-300); /** Warning text and icons. Always with an icon. */\n --color-text-error: var(--color-palette-danger-300); /** Error text and icons. */\n --color-border-strong: var(--color-palette-neutral-light-100); /** Emphasis — an active field, a selected row. */\n --color-border-default: var(--color-palette-neutral-light-600); /** Any border that carries meaning — control outlines, field borders. */\n --color-border-subtle: var(--color-palette-neutral-light-900); /** Decorative hairlines — table rules, dividers. Contrast-exempt. */\n --color-action-primary: var(--color-palette-primary-400); /** Primary interactive fills, strokes, links and focus rings. */\n --color-action-primary-hover: var(--color-palette-primary-300); /** Hover for action-primary. */\n --color-action-primary-pressed: var(--color-palette-primary-200); /** Pressed for action-primary. */\n --color-action-danger: var(--color-palette-danger-400); /** Destructive actions — delete, remove, revoke. */\n --color-action-danger-hover: var(--color-palette-danger-300); /** Hover for action-danger. */\n --color-action-danger-pressed: var(--color-palette-danger-200); /** Pressed for action-danger. */\n --color-action-neutral: var(--color-palette-neutral-light-200); /** The neutral action fill. Button variant=\"secondary\" resolves here. */\n --color-action-neutral-hover: var(--color-palette-neutral-light-100); /** Hover for action-neutral. */\n --color-action-neutral-pressed: var(--color-white); /** Pressed for action-neutral. */\n --color-action-disabled: var(--color-palette-neutral-dark-800); /** The disabled fill for every action variant. Contrast-exempt (inactive control). */\n --color-state-hover: var(--color-palette-primary-600-a6); /** Rest → hover on rows, tabs, chips, menu items. */\n --color-state-pressed: var(--color-palette-primary-600-a9); /** Hover → pressed on the same elements. */\n --color-state-selected: var(--color-palette-primary-600-a8); /** The persistent \"this one is chosen\" tint. */\n --color-viz-1: var(--color-palette-primary-500); /** Categorical series 1. */\n --color-viz-2: var(--color-palette-accent-500); /** Categorical series 2. */\n --color-viz-3: var(--color-palette-warning-400); /** Categorical series 3. */\n --color-viz-4: var(--color-palette-accent-alt-600); /** Categorical series 4. */\n --color-viz-5: var(--color-palette-success-300); /** Categorical series 5. */\n --color-viz-6: var(--color-palette-danger-600); /** Categorical series 6. */\n --color-viz-7: var(--color-palette-neutral-dark-500); /** Categorical series 7. */\n --color-viz-8: var(--color-palette-neutral-light-600); /** Categorical series 8. */\n --color-viz-seq-1: var(--color-palette-primary-600); /** Sequential step 1, low to high. */\n --color-viz-seq-2: var(--color-palette-primary-500); /** Sequential step 2, low to high. */\n --color-viz-seq-3: var(--color-palette-primary-400); /** Sequential step 3, low to high. */\n --color-viz-seq-4: var(--color-palette-primary-300); /** Sequential step 4, low to high. */\n --color-shadow-key: var(--color-black-a32); /** The tight, darker layer of an elevation shadow. Box-shadow color only; the elevation supplies offset and blur. */\n --color-shadow-ambient: var(--color-black-a24); /** The wide, soft layer of an elevation shadow, always paired with shadow-key. Transparent where a brand has no soft falloff. */\n --color-shadow-scrim: var(--color-black-a60); /** The scrim behind modals and dialogs. Mode-independent. */\n --gradient-surface-brand: radial-gradient(circle calc(0.85 * 100cqw) at 100% -5%, color-mix(in oklab, var(--color-palette-accent-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-accent-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-accent-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-accent-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-accent-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-accent-900) 0%, transparent) 100%), radial-gradient(circle calc(1.21 * 100cqw) at 5% 100%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%);\n --gradient-surface-brand-subtle: radial-gradient(circle calc(0.78 * 100cqw) at 50% -10%, color-mix(in oklab, var(--color-palette-primary-900) 45%, transparent) 0%, color-mix(in oklab, var(--color-palette-primary-900) 43%, transparent) 16%, color-mix(in oklab, var(--color-palette-primary-900) 24%, transparent) 36%, color-mix(in oklab, var(--color-palette-primary-900) 11%, transparent) 56%, color-mix(in oklab, var(--color-palette-primary-900) 4%, transparent) 76%, color-mix(in oklab, var(--color-palette-primary-900) 0%, transparent) 100%);\n }\n}\n"; diff --git a/packages/gamut-styles/src/tokens/index.ts b/packages/gamut-styles/src/tokens/index.ts new file mode 100644 index 0000000000..5c2097562b --- /dev/null +++ b/packages/gamut-styles/src/tokens/index.ts @@ -0,0 +1,12 @@ +/* + * @skillsoft/gamut-styles/tokens — the color system as plain data. + * + * No Emotion, React or variance: safe to import anywhere. The same tokens ship + * as CSS (`@skillsoft/gamut-styles/tokens/.css`, and `tokens.css` for + * the default brand) and DTCG JSON + * (`@skillsoft/gamut-styles/tokens//{palette,light,dark}.tokens.json`). + * Each brand's are complete on their own. + */ +export * from './generated/skillsoft/values'; +/* Every brand, by name: `brands.originalCodecademy.palette`, `brands.platform.tokensCss`, … */ +export * as brands from './generated'; diff --git a/packages/gamut-styles/src/typings/theme.d.ts b/packages/gamut-styles/src/typings/theme.d.ts index 6e5c8f5e57..2be873f1b0 100644 --- a/packages/gamut-styles/src/typings/theme.d.ts +++ b/packages/gamut-styles/src/typings/theme.d.ts @@ -1,8 +1,8 @@ import '@emotion/react'; -import { CoreTheme } from '../themes'; +import { SkillsoftTheme } from '../themes'; declare module '@emotion/react' { - export interface Theme extends CoreTheme { + export interface Theme extends SkillsoftTheme { useLogicalProperties?: boolean; } } diff --git a/packages/gamut-styles/src/utilities/__tests__/themed.test.ts b/packages/gamut-styles/src/utilities/__tests__/themed.test.ts index 5a501f08e3..8734ca21ab 100644 --- a/packages/gamut-styles/src/utilities/__tests__/themed.test.ts +++ b/packages/gamut-styles/src/utilities/__tests__/themed.test.ts @@ -1,12 +1,12 @@ -import { coreTheme } from '../../themes'; +import { skillsoftTheme } from '../../themes'; import { spacing } from '../../variables'; import { themed } from '../themed'; -const theme = coreTheme; +const theme = skillsoftTheme; describe('themed', () => { it('returns a function', () => { - expect(typeof themed('colors.blue-300')).toBe('function'); + expect(typeof themed('colors.palette-neutral-light-100')).toBe('function'); }); it('returned function looks up the value at the supplied path', () => { const getLineHeight = themed('lineHeight.title'); diff --git a/packages/gamut-styles/src/utilities/__tests__/useLogicalProperties.test.tsx b/packages/gamut-styles/src/utilities/__tests__/useLogicalProperties.test.tsx index 7de837eed5..f2261780f6 100644 --- a/packages/gamut-styles/src/utilities/__tests__/useLogicalProperties.test.tsx +++ b/packages/gamut-styles/src/utilities/__tests__/useLogicalProperties.test.tsx @@ -3,7 +3,7 @@ import { ThemeProvider } from '@emotion/react'; import type * as React from 'react'; import { setupRtl } from '../../__tests__/testUtils'; -import { coreTheme as theme } from '../../themes'; +import { skillsoftTheme as theme } from '../../themes'; import { useLogicalProperties } from '../useLogicalProperties'; const ValueReadout: React.FC = () => {`${useLogicalProperties()}`}; diff --git a/packages/gamut-styles/src/variables/elevation.ts b/packages/gamut-styles/src/variables/elevation.ts index 71d86b81bc..56b58522ff 100644 --- a/packages/gamut-styles/src/variables/elevation.ts +++ b/packages/gamut-styles/src/variables/elevation.ts @@ -50,3 +50,55 @@ export const percipioElevation: ElevationScaleFactory = ({ colors }) => { hoverMirrored: { shadow: hover, transform: 'none' }, }; }; + +/* + * Brand elevations, drawn in the shadow roles. Every depth + * composes `shadow-key` (the tight contact layer) and `shadow-ambient` (the + * soft falloff), so the layer count is the same at every level and shadows + * animate cleanly between them. Geometry lives here until elevation becomes + * CSS variables; the colors are already role names. + */ +const layers = (colors: ThemeColors, key: string, ambient: string): string => + `${key} ${colors['shadow-key']}, ${ambient} ${colors['shadow-ambient']}`; + +/* The soft raised shadow: design's Card geometry at rest and on hover. */ +export const softElevation: ElevationScaleFactory = ({ colors }) => { + const hover = layers(colors, '0 2px 6px 0', '0 6px 16px 0'); + return { + rest: { + shadow: layers(colors, '0 1px 4px 0', '0 2px 7px 0'), + transform: 'none', + }, + hover: { shadow: hover, transform: 'none' }, + hoverMirrored: { shadow: hover, transform: 'none' }, + }; +}; + +/* + * The hard offset, for the Codecademy theme only, drawn in `shadow-key`. The + * brand makes `shadow-ambient` transparent, and the ambient layer stays at + * zero size. Design gives this theme "the hard offset" at raised depth + * (on CTAButton) without its geometry, so raised is old core's + * CTAButton offset at rest (-4px) and raised-hover its hover offset (-8px, + * with old core's Card lift). + */ +export const offsetElevation: ElevationScaleFactory = ({ colors }) => { + const raised = 4; + const hover = 8; + const lift = 4; + const none = '0 0 0 0'; + return { + rest: { + shadow: layers(colors, `-${raised}px ${raised}px 0 0`, none), + transform: 'none', + }, + hover: { + shadow: layers(colors, `-${hover}px ${hover}px 0 0`, none), + transform: `translate(${lift}px, -${lift}px)`, + }, + hoverMirrored: { + shadow: layers(colors, `${hover}px ${hover}px 0 0`, none), + transform: `translate(-${lift}px, -${lift}px)`, + }, + }; +}; diff --git a/packages/gamut-styles/tsdown.config.ts b/packages/gamut-styles/tsdown.config.ts index 8dfdd1cae1..d786648450 100644 --- a/packages/gamut-styles/tsdown.config.ts +++ b/packages/gamut-styles/tsdown.config.ts @@ -5,5 +5,5 @@ import { baseConfig } from '../../tsdown.base.ts'; export default defineConfig({ ...baseConfig, - entry: ['src/index.ts'], + entry: ['src/index.ts', 'src/tokens/index.ts'], }); diff --git a/packages/gamut/src/Alert/Alert.test.tsx b/packages/gamut/src/Alert/Alert.test.tsx index 11b2142966..2b0877c5c9 100644 --- a/packages/gamut/src/Alert/Alert.test.tsx +++ b/packages/gamut/src/Alert/Alert.test.tsx @@ -1,8 +1,13 @@ +import { matchers } from '@emotion/jest'; import { setupRtl } from '@skillsoft/gamut-tests'; import * as React from 'react'; import { Alert } from './Alert'; +expect.extend(matchers); + +const onClick = jest.fn(); + const renderView = setupRtl(Alert, { children: 'Hello', onClose: jest.fn(), @@ -16,4 +21,56 @@ describe('Alert', () => { const closeButton = view.getByRole('button', { name: 'Close alert' }); expect(closeButtonRef.current).toBe(closeButton); }); + describe('color', () => { + it("leaves the CTA in the Alert's own mode instead of forcing dark", () => { + const { view } = renderView({ + type: 'error', + cta: { onClick, children: 'Retry' }, + }); + + const cta = view.getByRole('button', { name: 'Retry' }); + + /* A forced mode writes that mode's role variables into the button's class. */ + expect(cta).not.toHaveStyleRule( + '--color-action-neutral', + expect.any(String) + ); + }); + + it("puts a status variant's ink on the message", () => { + const { view } = renderView({ type: 'error' }); + + expect(view.getByRole('status')).toHaveStyleRule( + 'color', + 'var(--color-text-error)' + ); + }); + + it('keeps the message in text-default on general', () => { + const { view } = renderView({ type: 'general' }); + + expect(view.getByRole('status')).toHaveStyleRule( + 'color', + 'var(--color-text-default)' + ); + }); + + it("gives a floating Alert's edge the same ink", () => { + const { view } = renderView({ type: 'success', placement: 'floating' }); + + expect(view.getByRole('status')).toHaveStyleRule( + 'border-color', + 'var(--color-text-success)' + ); + }); + + it('keeps an inline Alert borderless', () => { + const { view } = renderView({ type: 'success', placement: 'inline' }); + + expect(view.getByRole('status')).not.toHaveStyleRule( + 'border-color', + 'var(--color-text-success)' + ); + }); + }); }); diff --git a/packages/gamut/src/Alert/Alert.tsx b/packages/gamut/src/Alert/Alert.tsx index bc448c904a..f877c0c149 100644 --- a/packages/gamut/src/Alert/Alert.tsx +++ b/packages/gamut/src/Alert/Alert.tsx @@ -1,5 +1,5 @@ import { MiniChevronDownIcon, MiniDeleteIcon } from '@skillsoft/gamut-icons'; -import { breakpoints, useCurrentMode } from '@skillsoft/gamut-styles'; +import { breakpoints } from '@skillsoft/gamut-styles'; import { Children, isValidElement, useId, useMemo, useState } from 'react'; import TruncateMarkup from 'react-truncate-markup'; import { useMeasure } from 'react-use'; @@ -79,12 +79,12 @@ export const Alert: React.FC = ({ }, [width]); const activeAlert = alertVariants?.[type] ?? alertVariants.general; - const { icon: Icon, bg } = activeAlert; + const { icon: Icon, bg, iconColor, ink } = activeAlert; + const gradient = 'gradient' in activeAlert ? activeAlert.gradient : undefined; const tipId = useId(); const collapsibleContentId = useId(); - const currentColorMode = useCurrentMode(); const isSubtleVariant = type === 'subtle'; const [expanded, setExpanded] = useState(false); @@ -175,8 +175,11 @@ export const Alert: React.FC = ({ ); - const buttonColorMode = isSubtleVariant ? currentColorMode : 'dark'; - + /* + * The tints follow the color mode, so the CTA does too: it inherits the + * mode the Alert's Background sets instead of being forced dark, which + * only made sense on the old saturated fills. + */ const ctaButton = ctaExists && ( = ({ > = ({ return ( - + >(placementVariants); +type InkProps = Pick & { + ink?: string; + theme: Theme; +}; + +/* + * The variant's ink on the message, and on the edge of a floating Alert + * (design's preview; the component map's "Floating border" slot still says + * border-default and is out of date). + */ +const alertInk = ({ ink, placement, theme }: InkProps) => { + const color = ink && (theme.colors as Record)[ink]; + if (!color) return {}; + return placement === 'floating' ? { color, borderColor: color } : { color }; +}; + +const notInk = { shouldForwardProp: (prop: string) => prop !== 'ink' }; + +const StyledAlertBanner = styled(Background, notInk)< + Pick & { gradient?: string; ink?: string } +>(placementVariants, gradientLayer, alertInk); export const AlertBanner = forwardRef< HTMLDivElement, @@ -35,8 +56,9 @@ export const AlertBanner = forwardRef< ) ); -const StyledAlertBox = - styled(Box)>(placementVariants); +const StyledAlertBox = styled(Box, notInk)< + Pick & { gradient?: string; ink?: string } +>(placementVariants, gradientLayer, alertInk); export const AlertBox = forwardRef< HTMLDivElement, diff --git a/packages/gamut/src/Alert/variants.tsx b/packages/gamut/src/Alert/variants.tsx index d9de934d8b..ffb802cb00 100644 --- a/packages/gamut/src/Alert/variants.tsx +++ b/packages/gamut/src/Alert/variants.tsx @@ -7,34 +7,55 @@ import { } from '@skillsoft/gamut-icons'; import { breakpoints, variant } from '@skillsoft/gamut-styles'; +/* + * Design's mapping (gamut-brand src/migration.ts, Alert): the fill + * is a status tint or a surface. A status variant's ink carries the icon, the + * message and a floating Alert's border; general, feature and subtle keep the + * message in text-default. General and feature paint a brand gradient over + * their flat fallback. + */ export const alertVariants = { general: { order: 4, - bg: 'blue', + bg: 'surface-brand-subtle', + gradient: 'var(--gradient-surface-brand-subtle)', + iconColor: 'action-primary', + ink: 'text-default', icon: MiniInfoCircleIcon, }, success: { order: 2, - bg: 'green', + bg: 'surface-success', + iconColor: 'text-success', + ink: 'text-success', icon: MiniCheckCircleIcon, }, error: { order: 1, - bg: 'red-600', + bg: 'surface-error', + iconColor: 'text-error', + ink: 'text-error', icon: MiniRemoveCircleIcon, }, notice: { order: 3, - bg: 'orange', + bg: 'surface-warning', + iconColor: 'text-warning', + ink: 'text-warning', icon: MiniWarningTriangleIcon, }, feature: { order: 5, - bg: 'blue-300', + bg: 'surface-brand', + gradient: 'var(--gradient-surface-brand)', + iconColor: 'action-primary', + ink: 'text-default', icon: MiniStarIcon, }, subtle: { - bg: 'background-hover', + bg: 'surface-sunken', + iconColor: 'text-subtle', + ink: 'text-default', icon: MiniInfoCircleIcon, }, } as const; @@ -56,7 +77,7 @@ export const placementVariants = variant({ py: 8, }, floating: { - borderColor: 'navy', + borderColor: 'border-default', borderStyle: 'solid', borderWidth: '2px', py: 4, diff --git a/packages/gamut/src/Anchor/Anchor.stories.tsx b/packages/gamut/src/Anchor/Anchor.stories.tsx index 020eac3ca1..c3de6acc15 100644 --- a/packages/gamut/src/Anchor/Anchor.stories.tsx +++ b/packages/gamut/src/Anchor/Anchor.stories.tsx @@ -1,5 +1,5 @@ import * as icons from '@skillsoft/gamut-icons'; -import { Background } from '@skillsoft/gamut-styles'; +import { ColorMode } from '@skillsoft/gamut-styles'; import type { Meta, StoryObj } from '@storybook/react'; import startCase from 'lodash/startCase'; import { expect } from 'storybook/test'; @@ -44,16 +44,17 @@ export default meta; const AnchorVariants = ({ useIcon }: { useIcon: boolean }) => ( - {(['white', 'navy'] as const).map((bg) => ( - - ( + + {variants.map((variant) => ( - + {startCase(variant)} @@ -67,7 +68,7 @@ const AnchorVariants = ({ useIcon }: { useIcon: boolean }) => ( ))} - + ))} diff --git a/packages/gamut/src/Anchor/index.tsx b/packages/gamut/src/Anchor/index.tsx index 34af1ecbb7..fca5ef1675 100644 --- a/packages/gamut/src/Anchor/index.tsx +++ b/packages/gamut/src/Anchor/index.tsx @@ -1,5 +1,5 @@ import styled from '@emotion/styled'; -import { styledOptions, system, variant } from '@skillsoft/gamut-styles'; +import { styledOptions, system, theme, variant } from '@skillsoft/gamut-styles'; import type { StyleProps } from '@skillsoft/variance'; import { variance } from '@skillsoft/variance'; import type { ComponentProps, ComponentType, HTMLProps, Ref } from 'react'; @@ -26,7 +26,7 @@ const outlineFocusVisible = { inset: -4, borderRadius: 'md', border: 2, - borderColor: 'primary', + borderColor: 'action-primary', opacity: 0, zIndex: 0, }, @@ -36,9 +36,10 @@ const outlineFocusVisible = { }, } as const; +/* Every link's focus ring is `action-primary`, whatever its text color. */ const underlineFocusVisible = { [InteractiveSelectors.FOCUS_VISIBLE]: { - outline: 'currentColor solid 2px', + outline: `${theme.colors['action-primary']} solid 2px`, borderRadius: 'sm', outlineOffset: '1.5px', textDecoration: 'underline', @@ -54,7 +55,7 @@ const anchorVariants = variant({ p: 0, fontSize: 'inherit', position: 'relative', - color: 'primary', + color: 'action-primary', whiteSpace: 'nowrap', [InteractiveSelectors.HOVER]: { textDecoration: 'none', @@ -63,18 +64,22 @@ const anchorVariants = variant({ [InteractiveSelectors.DISABLED]: { cursor: 'not-allowed', textDecoration: 'none', - color: 'text-disabled', + color: 'text-subtle', }, }, variants: { standard: { - color: 'primary', + color: 'action-primary', fontWeight: 'bold', WebkitFontSmoothing: 'antialiased', MozOsxFontSmoothing: 'grayscale', [InteractiveSelectors.HOVER]: { + color: 'action-primary-hover', textDecoration: 'underline', }, + [InteractiveSelectors.DISABLED]: { + color: 'text-subtle', + }, [InteractiveSelectors.FOCUS_VISIBLE]: { WebkitFontSmoothing: 'antialiased', MozOsxFontSmoothing: 'grayscale', @@ -86,22 +91,28 @@ const anchorVariants = variant({ display: 'inline', whiteSpace: 'initial', textDecoration: 'underline', + [InteractiveSelectors.HOVER]: { + color: 'action-primary-hover', + }, + [InteractiveSelectors.DISABLED]: { + color: 'text-subtle', + }, ...underlineFocusVisible, }, interface: { - color: 'text', + color: 'text-default', whiteSpace: 'initial', [InteractiveSelectors.HOVER]: { - color: 'primary', + color: 'action-primary', }, [InteractiveSelectors.FOCUS_VISIBLE]: { - color: 'primary', + color: 'action-primary', outline: 'none', }, ...outlineFocusVisible, }, 'standard-secondary': { - color: 'text', + color: 'text-default', textDecoration: 'underline', ...underlineFocusVisible, }, diff --git a/packages/gamut/src/Badge/Badge.stories.tsx b/packages/gamut/src/Badge/Badge.stories.tsx index fa35fcc68f..276515d680 100644 --- a/packages/gamut/src/Badge/Badge.stories.tsx +++ b/packages/gamut/src/Badge/Badge.stories.tsx @@ -46,11 +46,11 @@ export const TertiaryFill: Story = { }, decorators: [ (Story) => ( - + - + ), ], @@ -70,10 +70,10 @@ export const Custom: Story = { variant="custom" {...args} /> - - + + = { - textColor: 'text', - seriesOneBarColor: 'text', - seriesTwoBarColor: 'primary', - seriesOneLabel: 'text-secondary', - seriesTwoLabel: 'primary', + textColor: 'text-default', + seriesOneBarColor: 'text-default', + seriesTwoBarColor: 'action-primary', + seriesOneLabel: 'text-subtle', + seriesTwoLabel: 'action-primary', }; export const BarChartContext = createContext({ diff --git a/packages/gamut/src/BarChart/BarRow/elements.tsx b/packages/gamut/src/BarChart/BarRow/elements.tsx index 640e21e4e5..2fc9d2991f 100644 --- a/packages/gamut/src/BarChart/BarRow/elements.tsx +++ b/packages/gamut/src/BarChart/BarRow/elements.tsx @@ -46,7 +46,7 @@ const rowBaseStyles = css({ }, '&:focus-visible': { outline: '2px solid', - outlineColor: 'primary', + outlineColor: 'action-primary', outlineOffset: '2px', }, }); @@ -54,7 +54,7 @@ const rowBaseStyles = css({ const interactiveStyles = css({ cursor: 'pointer', '&:hover, &:focus-visible': { - bg: 'background-hover', + bg: 'state-hover', textDecoration: 'none', }, }); @@ -70,7 +70,7 @@ export const RowAnchor = styled('a', styledOptions<'a'>())( css({ [`&:focus-visible ${CategoryLabel}, &:hover ${CategoryLabel}`]: { textDecoration: 'underline', - textDecorationColor: 'primary', + textDecorationColor: 'action-primary', }, }) ); diff --git a/packages/gamut/src/BarChart/__tests__/BarChart.test.tsx b/packages/gamut/src/BarChart/__tests__/BarChart.test.tsx index c792d0f30c..4b0de34d8a 100644 --- a/packages/gamut/src/BarChart/__tests__/BarChart.test.tsx +++ b/packages/gamut/src/BarChart/__tests__/BarChart.test.tsx @@ -643,7 +643,7 @@ describe('BarChart', () => { it('applies custom textColor to labels', () => { const { view } = renderView({ styleConfig: { - textColor: 'primary', + textColor: 'action-primary', }, }); @@ -654,8 +654,8 @@ describe('BarChart', () => { it('applies custom seriesOneBarColor and seriesTwoBarColor', () => { const { view } = renderView({ styleConfig: { - seriesOneBarColor: 'primary', - seriesTwoBarColor: 'text', + seriesOneBarColor: 'action-primary', + seriesTwoBarColor: 'text-default', }, }); @@ -672,8 +672,8 @@ describe('BarChart', () => { const { view } = renderView({ barValues: stackedBarValues, styleConfig: { - seriesOneLabel: 'feedback-error', - seriesTwoLabel: 'feedback-success', + seriesOneLabel: 'text-error', + seriesTwoLabel: 'text-success', }, }); diff --git a/packages/gamut/src/BarChart/index.tsx b/packages/gamut/src/BarChart/index.tsx index 9175420228..78f9585960 100644 --- a/packages/gamut/src/BarChart/index.tsx +++ b/packages/gamut/src/BarChart/index.tsx @@ -99,7 +99,7 @@ export const BarChart = < (title && !hideTitle) || selectProps ? ( )} {index !== crumbs.length - 1 && ( - + / )} diff --git a/packages/gamut/src/Button/ButtonExamples.stories.tsx b/packages/gamut/src/Button/ButtonExamples.stories.tsx index 8822aa8c56..1abc4d4ed4 100644 --- a/packages/gamut/src/Button/ButtonExamples.stories.tsx +++ b/packages/gamut/src/Button/ButtonExamples.stories.tsx @@ -5,6 +5,7 @@ import { MiniRibbonIcon, SearchIcon, } from '@skillsoft/gamut-icons'; +import { ColorMode } from '@skillsoft/gamut-styles'; import type { Meta, StoryObj } from '@storybook/react'; import { Box, FlexBox, GridBox } from '../Box'; @@ -108,19 +109,21 @@ const ButtonScale = ({ mode }: { mode: 'dark' | 'light' }) => { }); return ( - - {grid} - + + + {grid} + + ); }; diff --git a/packages/gamut/src/Button/CTAButton.tsx b/packages/gamut/src/Button/CTAButton.tsx index 92542fa226..15a9768ca9 100644 --- a/packages/gamut/src/Button/CTAButton.tsx +++ b/packages/gamut/src/Button/CTAButton.tsx @@ -1,6 +1,7 @@ import { createButtonComponent } from './shared/styles'; -import { ctaButtonVariants } from './shared/variants'; +import { ctaButtonElevation, ctaButtonVariants } from './shared/variants'; export const CTAButton = createButtonComponent<{ variant?: 'primary' }>( + ctaButtonElevation, ctaButtonVariants ); diff --git a/packages/gamut/src/Button/shared/variants.ts b/packages/gamut/src/Button/shared/variants.ts index 4081ccfb4e..b03a430961 100644 --- a/packages/gamut/src/Button/shared/variants.ts +++ b/packages/gamut/src/Button/shared/variants.ts @@ -1,4 +1,5 @@ -import { theme, transitionConcat, variant } from '@skillsoft/gamut-styles'; +import { transitionConcat, variant } from '@skillsoft/gamut-styles'; +import type { CSSObject, ThemeProps } from '@skillsoft/variance'; import { InteractiveSelectors } from '../../ButtonBase/ButtonBase'; import { buttonVariants, templateVariants } from './styles'; @@ -9,107 +10,154 @@ const hoverBackgroundTransition = transitionConcat( 'ease-in' ); +/* + * Design's button map (gamut-brand src/migration.ts, COMPONENTS): each variant + * takes an action family, and interface merges into primary. Every variant + * gets the one `action-primary` focus ring. + */ +const actionFamily = { + primary: 'action-primary', + secondary: 'action-neutral', + danger: 'action-danger', + interface: 'action-primary', +} as const; + +const hover = { + primary: 'action-primary-hover', + secondary: 'action-neutral-hover', + danger: 'action-danger-hover', + interface: 'action-primary-hover', +} as const; + +const pressed = { + primary: 'action-primary-pressed', + secondary: 'action-neutral-pressed', + danger: 'action-danger-pressed', + interface: 'action-primary-pressed', +} as const; + +/* + * Labels on a fill are `text-inverse`. A disabled fill's label is a knockout: + * the surface the control sits on shows through. + */ export const fillButtonVariants = templateVariants( buttonVariants, (variant) => ({ - bg: variant, - color: 'background', - [InteractiveSelectors.OUTLINE]: { borderColor: variant }, + bg: actionFamily[variant], + color: 'text-inverse', + [InteractiveSelectors.OUTLINE]: { borderColor: 'action-primary' }, [InteractiveSelectors.HOVER]: { - bg: `${variant}-hover`, - color: 'background', + bg: hover[variant], + color: 'text-inverse', transition: hoverBackgroundTransition, }, [InteractiveSelectors.ACTIVE]: { - borderColor: 'border-primary', - bg: variant, - color: 'background', + bg: pressed[variant], + color: 'text-inverse', }, [InteractiveSelectors.DISABLED]: { - color: 'text-disabled', - bg: 'background-disabled', + color: 'surface-raised', + bg: 'action-disabled', }, }) ); +/* + * The label keeps its color; hover and press are veils over whatever the + * button sits on. Interface is `text-default`. + */ export const textButtonVariants = templateVariants( buttonVariants, - (variant) => ({ - borderColor: 'transparent', - color: variant === 'interface' ? 'text' : variant, - [InteractiveSelectors.HOVER]: { - color: variant, - bg: 'background-hover', - transition: hoverBackgroundTransition, - }, - [InteractiveSelectors.FOCUS_VISIBLE]: { - color: variant, - }, - [InteractiveSelectors.OUTLINE]: { - borderColor: variant, - }, - [InteractiveSelectors.ACTIVE]: { - color: 'text', - }, - [InteractiveSelectors.DISABLED]: { - color: 'text-disabled', - bg: 'transparent', - }, - }) + (variant) => { + const label = + variant === 'interface' ? 'text-default' : actionFamily[variant]; + return { + borderColor: 'transparent', + color: label, + [InteractiveSelectors.HOVER]: { + bg: 'state-hover', + transition: hoverBackgroundTransition, + }, + [InteractiveSelectors.OUTLINE]: { + borderColor: 'action-primary', + }, + [InteractiveSelectors.ACTIVE]: { + bg: 'state-pressed', + }, + [InteractiveSelectors.DISABLED]: { + color: 'text-subtle', + bg: 'transparent', + }, + }; + } ); +/* Stroke and label take the action color; hover and press are veils. */ export const strokeButtonVariants = templateVariants( buttonVariants, (variant) => ({ - borderColor: variant, + borderColor: actionFamily[variant], bg: 'transparent', - color: variant, + color: actionFamily[variant], [InteractiveSelectors.HOVER]: { - bg: 'background-hover', + bg: 'state-hover', transition: hoverBackgroundTransition, }, [InteractiveSelectors.OUTLINE]: { - borderColor: variant, + borderColor: 'action-primary', }, [InteractiveSelectors.ACTIVE]: { - bg: variant, - color: 'background', + bg: 'state-pressed', }, [InteractiveSelectors.DISABLED]: { - borderColor: 'background-disabled', - color: 'text-disabled', + borderColor: 'action-disabled', + color: 'text-subtle', bg: 'transparent', }, }) ); +/* + * Design's CTAButton map: fills follow primary FillButton, + * and the shadow comes from the theme's elevation, raised at rest and + * raised-hover on hover. That's the soft shadow on most brands and the hard + * offset in the Codecademy theme. `ctaButtonElevation` runs before the + * variants, so pressed and disabled still clear it. + */ +export const ctaButtonElevation = ({ + theme, +}: T): CSSObject => ({ + boxShadow: theme?.elevation?.['rest-shadow'], + [InteractiveSelectors.HOVER]: { + boxShadow: theme?.elevation?.['hover-shadow'], + }, +}); + export const ctaButtonVariants = templateVariants(['primary'], (variant) => ({ borderRadius: 'md', fontFamily: 'accent', fontWeight: 'title', - boxShadow: `-4px 4px 0 0 ${theme.colors.text}`, - color: 'background', + color: 'text-inverse', py: 12, px: 24, - bg: variant, + bg: actionFamily[variant], [InteractiveSelectors.OUTLINE]: { - borderColor: variant, + borderColor: 'action-primary', bottom: -9, left: -9, }, [InteractiveSelectors.HOVER]: { - bg: `${variant}-hover`, + bg: hover[variant], transition: hoverBackgroundTransition, - boxShadow: `-8px 8px 0 0 ${theme.colors.text}`, }, [InteractiveSelectors.ACTIVE]: { boxShadow: 'none', - bg: 'secondary', + bg: pressed[variant], }, [InteractiveSelectors.DISABLED]: { boxShadow: 'none', - color: 'text-disabled', - bg: 'background-disabled', + color: 'surface-raised', + bg: 'action-disabled', }, })); diff --git a/packages/gamut/src/ButtonBase/ButtonBase.tsx b/packages/gamut/src/ButtonBase/ButtonBase.tsx index bfc40c21ae..a17cc0b3ce 100644 --- a/packages/gamut/src/ButtonBase/ButtonBase.tsx +++ b/packages/gamut/src/ButtonBase/ButtonBase.tsx @@ -39,7 +39,7 @@ export const resetStyles = css({ background: 'none', boxShadow: 'none', border: 'none', - textColor: 'text', + textColor: 'text-default', p: 0, fontSize: 'inherit', cursor: 'pointer', diff --git a/packages/gamut/src/Card/Card.stories.tsx b/packages/gamut/src/Card/Card.stories.tsx index 237dbc9d7d..568401206b 100644 --- a/packages/gamut/src/Card/Card.stories.tsx +++ b/packages/gamut/src/Card/Card.stories.tsx @@ -1,4 +1,4 @@ -import { Background } from '@skillsoft/gamut-styles'; +import { ColorMode } from '@skillsoft/gamut-styles'; import type { Meta, StoryObj } from '@storybook/react'; import { Anchor } from '../Anchor'; @@ -35,120 +35,68 @@ export const Default: Story = { }, }; -export const StaticLight: Story = { - render: () => ( - - - - White variant - - - Yellow variant - - - Beige variant - - - - ), - play: ({ canvas }) => { - canvas.getByText('White variant'); - canvas.getByText('Yellow variant'); - canvas.getByText('Beige variant'); - }, -}; - -export const StaticDark: Story = { - render: () => ( - - - - Navy variant - - - Hyper variant - - - - ), -}; - -export const ShadowNone: Story = { +export const Variants: Story = { render: () => ( - - - Default variant - - - - - - White variant - + + - Yellow variant + Default, light mode - Beige variant + Brand, light mode - - - + + + - Navy variant + Default, dark mode - Hyper variant + Brand, dark mode - + ), + play: ({ canvas }) => { + canvas.getByText('Default, light mode'); + canvas.getByText('Brand, dark mode'); + }, }; export const ShadowOutline: Story = { render: () => ( - - - Default with outline - - - + - - White with outline + + Default with outline, light mode - - Yellow with outline - - - - - Beige with outline + + Brand with outline, light mode - - + + - - Navy with outline + + Default with outline, dark mode - - Hyper with outline + + Brand with outline, dark mode - + ), }; @@ -156,44 +104,34 @@ export const ShadowOutline: Story = { export const ShadowPatternLeft: Story = { render: () => ( - - - Default with patternLeft - - - + - - White with patternLeft - - - - - Yellow with patternLeft + + Default with patternLeft, light mode - - Beige with patternLeft + + Brand with patternLeft, light mode - - + + - - Navy with patternLeft + + Default with patternLeft, dark mode - - Hyper with patternLeft + + Brand with patternLeft, dark mode - + ), }; @@ -201,44 +139,34 @@ export const ShadowPatternLeft: Story = { export const ShadowPatternRight: Story = { render: () => ( - - - Default with patternRight - - - + - - White with patternRight + + Default with patternRight, light mode - - Yellow with patternRight - - - - - Beige with patternRight + + Brand with patternRight, light mode - - + + - - Navy with patternRight + + Default with patternRight, dark mode - - Hyper with patternRight + + Brand with patternRight, dark mode - + ), }; @@ -246,75 +174,65 @@ export const ShadowPatternRight: Story = { export const IsInteractive: Story = { render: () => ( - - - - - Default, patternRight, and isInteractive - - - - - + - - White, no shadow, and isInteractive - - - - - - - Yellow, patternLeft, and isInteractive + + Default, patternRight and isInteractive, light mode - - Beige, outline, and isInteractive + + Brand, patternRight and isInteractive, light mode - - + + - - Navy, patternLeft, and isInteractive + + Default, patternRight and isInteractive, dark mode - - Hyper, patternRight, and isInteractive + + Brand, patternRight and isInteractive, dark mode - + ), play: ({ canvas }) => { - canvas.getByText('Default, patternRight, and isInteractive'); - canvas.getByText('Beige, outline, and isInteractive'); + canvas.getByText('Default, patternRight and isInteractive, light mode'); + canvas.getByText('Brand, patternRight and isInteractive, dark mode'); }, }; export const Dynamic: Story = { render: () => ( - + Lookie here, a default variant in light mode! - - + + Lookie here, a default variant in dark mode! - + ), }; @@ -323,13 +241,13 @@ export const BorderRadius: Story = { render: () => ( - - Yellow, isInteractive, outline, and borderRadius is 'sm' + + Brand, isInteractive, outline, and borderRadius is 'sm' - - Beige, patternLeft, and borderRadius is 'xl' + + Default, patternLeft, and borderRadius is 'xl' diff --git a/packages/gamut/src/Card/Card.test.tsx b/packages/gamut/src/Card/Card.test.tsx index de527188c8..03e178e5a6 100644 --- a/packages/gamut/src/Card/Card.test.tsx +++ b/packages/gamut/src/Card/Card.test.tsx @@ -1,9 +1,15 @@ +import { matchers } from '@emotion/jest'; import { theme } from '@skillsoft/gamut-styles'; -import { MockGamutProvider } from '@skillsoft/gamut-tests'; +import { MockGamutProvider, setupRtl } from '@skillsoft/gamut-tests'; import { renderHook } from '@testing-library/react'; +import { Card } from '.'; import { useCardElevation } from './styles'; +expect.extend(matchers); + +const renderCard = setupRtl(Card, { children: 'Card content' }); + const renderElevation = (...args: Parameters) => renderHook(() => useCardElevation(...args), { wrapper: MockGamutProvider, @@ -48,4 +54,50 @@ describe('useCardElevation', () => { expect(variant.borderRadius).toBe('8px'); }); }); + + /* Design's card map: only an interactive card has a shadow. */ + it('gives a card that is not interactive no shadow at rest', () => { + const { initial } = renderElevation('none', undefined, false); + + expect(initial.boxShadow).toBe('none'); + }); +}); + +describe('Card', () => { + const cardEl = (view: ReturnType['view']) => + view.getByText('Card content'); + + /* + * Design's card map, Interactive: a `state-hover` veil and + * the raised-hover shadow; the border stays `border-subtle`. + */ + it.each(['default', 'brand'] as const)( + 'veils an interactive %s card on hover without changing its border', + (variant) => { + const { view } = renderCard({ isInteractive: true, variant }); + + expect(cardEl(view)).toHaveStyleRule( + 'background-color', + theme.colors['state-hover'], + { target: /:hover>.+::after$/ } + ); + expect(cardEl(view)).not.toHaveStyleRule('border-color', /.*/, { + target: ':hover>', + }); + expect(cardEl(view)).toHaveStyleRule( + 'border-color', + theme.colors['border-subtle'] + ); + } + ); + + it('does not veil a card that is not interactive', () => { + const { view } = renderCard(); + + expect(cardEl(view)).not.toHaveStyleRule( + 'background-color', + theme.colors['state-hover'], + { target: /:hover>.+::after$/ } + ); + }); }); diff --git a/packages/gamut/src/Card/elements.tsx b/packages/gamut/src/Card/elements.tsx index 222408791d..9df70538fd 100644 --- a/packages/gamut/src/Card/elements.tsx +++ b/packages/gamut/src/Card/elements.tsx @@ -5,7 +5,8 @@ import { motion } from 'framer-motion'; import type { ComponentProps, ComponentType } from 'react'; import { Box } from '../Box'; -import { cardVariants, shadowVariants } from './styles'; +import { gradientLayer } from '../utils/gradientLayer'; +import { cardVariants, interactiveStates, shadowVariants } from './styles'; import type { CardWrapperProps } from './types'; /* @@ -20,11 +21,11 @@ export const MotionBox = motion.create(Box) as ComponentType; export const DynamicCardWrapper = styled(MotionBox)( cardVariants, - shadowVariants + shadowVariants, + interactiveStates ); const StaticCard = motion.create(Background); -export const StaticCardWrapper = styled(StaticCard)( - cardVariants, - shadowVariants -); +export const StaticCardWrapper = styled(StaticCard)< + CardWrapperProps & { gradient?: string } +>(cardVariants, shadowVariants, interactiveStates, gradientLayer); diff --git a/packages/gamut/src/Card/index.tsx b/packages/gamut/src/Card/index.tsx index ed9db004d9..363951e22d 100644 --- a/packages/gamut/src/Card/index.tsx +++ b/packages/gamut/src/Card/index.tsx @@ -4,7 +4,7 @@ import { borderRadii } from '@skillsoft/gamut-styles'; import type * as React from 'react'; import { DynamicCardWrapper, MotionBox, StaticCardWrapper } from './elements'; -import { patternFadeInOut, useCardElevation } from './styles'; +import { cardSurfaces, patternFadeInOut, useCardElevation } from './styles'; import type { CardProps } from './types'; type BorderRadiusToken = keyof typeof borderRadii; @@ -31,7 +31,16 @@ export const Card: React.FC = ({ const hasPattern = shadow === 'patternLeft' || shadow === 'patternRight'; const isOutline = shadow === 'outline'; - const setHoverShadow = useCardElevation(shadow, resolvedBorderRadius); + const setHoverShadow = useCardElevation( + shadow, + resolvedBorderRadius, + isInteractive + ); + + /* A brand card goes through Background, which picks its content mode. */ + const surface = cardSurfaces[variant as keyof typeof cardSurfaces]; + const gradient = + variant === 'brand' ? 'var(--gradient-surface-brand)' : undefined; const initialVariant = isOutline ? 'initialOutline' : 'initial'; const animateVariant = isOutline ? 'animateOutline' : 'animate'; @@ -48,7 +57,7 @@ export const Card: React.FC = ({ {hasPattern && ( = ({ &::after': { + bg: 'state-hover', }, }, }); @@ -67,7 +96,7 @@ export const shadowVariants = variant({ patternLeft: {}, patternRight: {}, outline: { - boxShadow: `-${SHADOW_OFFSET}px ${SHADOW_OFFSET}px ${theme.colors['background-current']}, -${SHADOW_OFFSET}px ${SHADOW_OFFSET}px 0 1px ${theme.colors['border-primary']}`, + boxShadow: `-${SHADOW_OFFSET}px ${SHADOW_OFFSET}px ${theme.colors['background-current']}, -${SHADOW_OFFSET}px ${SHADOW_OFFSET}px 0 1px ${theme.colors['border-subtle']}`, }, }, }); @@ -89,17 +118,26 @@ export const patternFadeInOut = { }, }; +/* + * An interactive card sits at the theme's raised shadow and moves to its + * raised-hover shadow on hover. A card that isn't interactive has no shadow + * (design's card map). + */ export const useCardElevation = ( shadow: StyleProps['shadow'], - borderRadius?: string + borderRadius?: string, + isInteractive = true ) => { const { elevation } = useTheme(); const hoverState: ElevationState = shadow === 'patternRight' ? 'hoverMirrored' : 'hover'; + const rest = isInteractive + ? getElevationStyles(elevation, 'rest') + : { boxShadow: 'none', transform: elevation['rest-transform'] }; return { initial: { - ...getElevationStyles(elevation, 'rest'), + ...rest, borderRadius, transition: REST_TRANSITION, }, @@ -110,14 +148,15 @@ export const useCardElevation = ( }, // Outline styles are specific to Codecademy and will not be included in a theme initialOutline: { - ...getElevationStyles(elevation, 'rest'), - boxShadow: `-${SHADOW_OFFSET_INITIAL}px ${SHADOW_OFFSET_INITIAL}px 0 0px ${theme.colors['background-current']}, -${SHADOW_OFFSET_INITIAL}px ${SHADOW_OFFSET_INITIAL}px 0 1px ${theme.colors['border-primary']}`, + ...rest, + boxShadow: `-${SHADOW_OFFSET_INITIAL}px ${SHADOW_OFFSET_INITIAL}px 0 0px ${theme.colors['background-current']}, -${SHADOW_OFFSET_INITIAL}px ${SHADOW_OFFSET_INITIAL}px 0 1px ${theme.colors['border-subtle']}`, borderRadius, transition: REST_TRANSITION, }, + /* The hard offset, drawn in `shadow-key` like the elevation's. */ animateOutline: { ...getElevationStyles(elevation, 'hover'), - boxShadow: `-${SHADOW_OFFSET}px ${SHADOW_OFFSET}px 0 0px ${theme.colors['shadow-primary']}, -${SHADOW_OFFSET}px ${SHADOW_OFFSET}px 0 1px ${theme.colors['shadow-primary']}`, + boxShadow: `-${SHADOW_OFFSET}px ${SHADOW_OFFSET}px 0 0px ${theme.colors['shadow-key']}, -${SHADOW_OFFSET}px ${SHADOW_OFFSET}px 0 1px ${theme.colors['shadow-key']}`, borderRadius, transition: HOVER_TRANSITION, }, diff --git a/packages/gamut/src/ConnectedForm/ConnectedFormGroup.tsx b/packages/gamut/src/ConnectedForm/ConnectedFormGroup.tsx index 36504d47d1..2b5aa2ad62 100644 --- a/packages/gamut/src/ConnectedForm/ConnectedFormGroup.tsx +++ b/packages/gamut/src/ConnectedForm/ConnectedFormGroup.tsx @@ -15,7 +15,7 @@ import { getErrorMessage, useField } from './utils'; const ErrorAnchor = styled(Anchor)( css({ - color: 'feedback-error', + color: 'text-error', }) ); diff --git a/packages/gamut/src/DataList/Controls/ExpandControl.tsx b/packages/gamut/src/DataList/Controls/ExpandControl.tsx index 215feef550..1f63d5202b 100644 --- a/packages/gamut/src/DataList/Controls/ExpandControl.tsx +++ b/packages/gamut/src/DataList/Controls/ExpandControl.tsx @@ -40,7 +40,7 @@ export const ExpandControl: React.FC = ({ }} > - + diff --git a/packages/gamut/src/DataList/Controls/FilterControl.tsx b/packages/gamut/src/DataList/Controls/FilterControl.tsx index a6811066e4..f967fafcef 100644 --- a/packages/gamut/src/DataList/Controls/FilterControl.tsx +++ b/packages/gamut/src/DataList/Controls/FilterControl.tsx @@ -130,7 +130,7 @@ export const FilterControl: React.FC = ({ > 0 ? 'primary' : 'text'} + color={filters.length > 0 ? 'action-primary' : 'text-default'} size={14} /> diff --git a/packages/gamut/src/DataList/Controls/SortControl.tsx b/packages/gamut/src/DataList/Controls/SortControl.tsx index 201bd06e1e..ce1e380aab 100644 --- a/packages/gamut/src/DataList/Controls/SortControl.tsx +++ b/packages/gamut/src/DataList/Controls/SortControl.tsx @@ -10,18 +10,18 @@ import type { OnSort, SortDirection, SortOrder } from '../types'; const SortAnchor = styled(Anchor)( css({ - color: 'text', + color: 'text-default', textAlign: 'left', width: '100%', alignItems: 'flex-end', '&:hover': { - color: 'text', + color: 'text-default', }, '> div': { - color: 'text-disabled', + color: 'text-subtle', }, '&:hover div': { - color: 'text-secondary', + color: 'text-subtle', }, }) ); @@ -32,7 +32,7 @@ const SortIcon = styled(ArrowChevronDownFilledIcon)( transform: 'rotate(180deg)', }, active: { - color: 'text-accent', + color: 'text-default', }, }) ); diff --git a/packages/gamut/src/DataList/Tables/Rows/TableRow.tsx b/packages/gamut/src/DataList/Tables/Rows/TableRow.tsx index 4d872d8642..3282072d62 100644 --- a/packages/gamut/src/DataList/Tables/Rows/TableRow.tsx +++ b/packages/gamut/src/DataList/Tables/Rows/TableRow.tsx @@ -77,7 +77,7 @@ export const TableRow: DataRow = ({ : {}; return ( - + {selectable && ( { }); }); + it('tints a selected row with the state-selected veil', () => { + renderView({ selected: ['1'] }); + + const rows = screen.getAllByRole('row'); + const selectedRow = screen + .getByRole('checkbox', { name: 'Select 1' }) + .closest('tr')!; + const otherRow = screen + .getByRole('checkbox', { name: 'Select 2' }) + .closest('tr')!; + + expect(rows).toContain(selectedRow); + expect(selectedRow).toHaveStyleRule( + 'background-image', + expect.stringContaining('var(--color-state-selected)') + ); + expect(otherRow).not.toHaveStyleRule( + 'background-image', + expect.stringContaining('var(--color-state-selected)') + ); + }); + it("clicking the row's checkbox deselects the row when the row is already selected", () => { renderView({ selected: ['1'] }); diff --git a/packages/gamut/src/DatePicker/DatePickerCalendar/Calendar/CalendarWrapper.tsx b/packages/gamut/src/DatePicker/DatePickerCalendar/Calendar/CalendarWrapper.tsx index a5a07d6578..a704708f0e 100644 --- a/packages/gamut/src/DatePicker/DatePickerCalendar/Calendar/CalendarWrapper.tsx +++ b/packages/gamut/src/DatePicker/DatePickerCalendar/Calendar/CalendarWrapper.tsx @@ -3,18 +3,28 @@ import type * as React from 'react'; import { Box } from '../../../Box'; import type { WithChildrenProp } from '../../../utils'; +import { + checkerPatternColor, + useCheckerPattern, +} from '../../../utils/elevation'; -export const CalendarWrapper: React.FC = ({ children }) => ( - - - - {children} +/* A floating surface: `surface-raised` with a `border-subtle` edge. */ +export const CalendarWrapper: React.FC = ({ children }) => { + const showPattern = useCheckerPattern(); + return ( + + {showPattern && } + + {children} + - -); + ); +}; diff --git a/packages/gamut/src/DatePicker/DatePickerCalendar/Calendar/utils/elements.tsx b/packages/gamut/src/DatePicker/DatePickerCalendar/Calendar/utils/elements.tsx index af3adcea1c..7a656bfa1b 100644 --- a/packages/gamut/src/DatePicker/DatePickerCalendar/Calendar/utils/elements.tsx +++ b/packages/gamut/src/DatePicker/DatePickerCalendar/Calendar/utils/elements.tsx @@ -14,7 +14,8 @@ export const TableHeader = styled.th( css({ fontSize: 14, fontWeight: 'base', - color: 'text-disabled', + /* Like a List header's sort icon: secondary text, not a disabled one. */ + color: 'text-subtle', textAlign: 'center', }) ); @@ -31,18 +32,20 @@ const datecellStates = states({ width: 4, height: 4, borderRadius: 'full', - bg: 'hyper', + /* An indicator, like the Tabs indicator. */ + bg: 'action-primary', }, }, + /* A dark chip, like the selection Tag: `text-inverse` on the fill. */ isSelected: { - bg: 'text', - color: 'background', + bg: 'text-default', + color: 'text-inverse', '&:hover, &:focus': { - bg: 'secondary-hover', - color: 'background', + bg: 'action-neutral-hover', + color: 'text-inverse', }, '&::after': { - bg: 'background', + bg: 'text-inverse', }, }, isRangeStart: { @@ -51,26 +54,24 @@ const datecellStates = states({ isRangeEnd: { borderRadiusLeft: 'none', }, + /* Days inside a range are selected rows: the `state-selected` veil. */ isInRange: { - bg: 'text-disabled', - color: 'background', + bg: 'state-selected', + color: 'text-default', borderRadius: 'none', '&:hover, &:focus': { - bg: 'secondary-hover', - color: 'background', - }, - '&::after': { - bg: 'background', + bg: 'action-neutral-hover', + color: 'text-inverse', }, }, isDisabled: { - color: 'text-disabled', + color: 'text-subtle', bg: 'transparent', cursor: 'not-allowed', userSelect: 'none', textDecoration: 'line-through', '&:hover, &:focus': { - color: 'text-disabled', + color: 'text-subtle', bg: 'transparent', textDecoration: 'line-through', cursor: 'not-allowed', @@ -96,8 +97,8 @@ export const DateCell = styled.td( 'ease-in' ), '&:hover': { - color: 'secondary', - bg: 'background-hover', + color: 'action-neutral', + bg: 'state-hover', transition: transitionConcat( ['background-color', 'box-shadow'], 'fast', @@ -106,11 +107,11 @@ export const DateCell = styled.td( cursor: 'pointer', }, '&:focus-visible': { - color: 'secondary', + color: 'action-neutral', outline: 'none', }, '&:active': { - color: 'text', + color: 'text-default', }, '&::before': { content: '""', @@ -118,6 +119,7 @@ export const DateCell = styled.td( inset: -3, borderRadius: 'lg', border: 2, + borderColor: 'action-primary', opacity: 0, zIndex: 0, }, diff --git a/packages/gamut/src/DatePicker/DatePickerInput/Segment/elements.tsx b/packages/gamut/src/DatePicker/DatePickerInput/Segment/elements.tsx index 58aa753f94..e5f5f0db0f 100644 --- a/packages/gamut/src/DatePicker/DatePickerInput/Segment/elements.tsx +++ b/packages/gamut/src/DatePicker/DatePickerInput/Segment/elements.tsx @@ -4,7 +4,7 @@ import type { StyleProps } from '@skillsoft/variance'; const segmentStyles = states({ isEmpty: { - color: 'text-secondary', + color: 'text-subtle', }, isYear: { minWidth: '4ch', @@ -20,11 +20,11 @@ export const Segment = styled.span( minWidth: '2ch', padding: 0, margin: 0, - color: 'text', + color: 'text-default', cursor: 'text', '&:focus': { - bg: 'primary', - color: 'background', + bg: 'action-primary', + color: 'text-inverse', borderRadius: 'md', }, '&:focus-visible': { @@ -36,7 +36,7 @@ export const Segment = styled.span( export const SegmentLiteral = styled.span( css({ - color: 'text-secondary', + color: 'text-subtle', userSelect: 'none', px: 4, }) diff --git a/packages/gamut/src/DatePicker/DatePickerInput/elements.tsx b/packages/gamut/src/DatePicker/DatePickerInput/elements.tsx index 317860b4a4..656e94454d 100644 --- a/packages/gamut/src/DatePicker/DatePickerInput/elements.tsx +++ b/packages/gamut/src/DatePicker/DatePickerInput/elements.tsx @@ -1,5 +1,5 @@ import styled from '@emotion/styled'; -import { variant } from '@skillsoft/gamut-styles'; +import { theme, variant } from '@skillsoft/gamut-styles'; import type { StyleProps } from '@skillsoft/variance'; import { FlexBox } from '../../Box'; @@ -9,20 +9,24 @@ import { inputSizeStyles, } from '../../Form/styles'; +const errorRing = `inset 0 0 0 1px ${theme.colors['text-error']}`; + +/* Error is a 2px `text-error` outline at rest, like `Input`. */ const shellFocusStyles = variant({ variants: { error: { - borderColor: 'feedback-error', + borderColor: 'text-error', + boxShadow: errorRing, '&:hover': { - borderColor: 'feedback-error', + borderColor: 'text-error', }, '&:focus': { - borderColor: 'feedback-error', - boxShadow: `inset 0 0 0 1px feedback-error`, + borderColor: 'text-error', + boxShadow: errorRing, }, '&:focus-within': { - borderColor: 'feedback-error', - boxShadow: `inset 0 0 0 1px feedback-error`, + borderColor: 'text-error', + boxShadow: errorRing, }, }, default: { @@ -38,6 +42,8 @@ interface SegmentedShellProps /** * Shell uses the same styles as `Input`. `formFieldStyles` targets `&:focus`, but the host is a * `div` — focus is on inner spinbuttons, so we mirror `Input` focus visuals with `&:focus-within`. + * For the same reason `:disabled` never matches: a disabled shell sets `aria-disabled`, which + * `formFieldStyles`' disabled rule also matches. */ export const SegmentedShell = styled(FlexBox)( formFieldStyles, diff --git a/packages/gamut/src/DatePicker/DatePickerInput/index.tsx b/packages/gamut/src/DatePicker/DatePickerInput/index.tsx index ef09c54b62..663ee152b4 100644 --- a/packages/gamut/src/DatePicker/DatePickerInput/index.tsx +++ b/packages/gamut/src/DatePicker/DatePickerInput/index.tsx @@ -349,6 +349,7 @@ export const DatePickerInput = forwardRef( width="fit-content" > ( - - + + ), }; export const SubtleBgBordered: Story = { render: () => ( - + @@ -86,7 +86,7 @@ export const SubtleBgBordered: Story = { export const Transparent: Story = { render: () => ( - + ( - + = ({ > {overline && ( = { }, [args.expanded]); return ( - + > = ({ children ) : ( = ({ - bg = 'background', + bg = 'surface-raised', children, closeLabel = 'Close', expanded, @@ -65,6 +65,12 @@ export const Flyout: React.FC = ({ position="fixed" top={0} {...{ [openFrom]: 0 }} + /* Design's map: a `border-subtle` inner edge. */ + {...{ + [openFrom === 'left' + ? 'borderRight' + : 'borderLeft']: `1px solid ${theme.colors['border-subtle']}`, + }} > = { > diff --git a/packages/gamut/src/Form/__tests__/fieldColors.test.tsx b/packages/gamut/src/Form/__tests__/fieldColors.test.tsx new file mode 100644 index 0000000000..44b3a87995 --- /dev/null +++ b/packages/gamut/src/Form/__tests__/fieldColors.test.tsx @@ -0,0 +1,69 @@ +import { matchers } from '@emotion/jest'; +import { GamutProvider, skillsoftTheme } from '@skillsoft/gamut-styles'; +import { render } from '@testing-library/react'; +import type * as React from 'react'; + +import { FormGroupDescription } from '../elements/FormGroupDescription'; +import { Checkbox } from '../inputs/Checkbox'; +import { Input } from '../inputs/Input'; +import { Select } from '../inputs/Select'; +import { TextArea } from '../inputs/TextArea'; + +expect.extend(matchers); + +const renderIn = (ui: React.ReactElement) => + render( + + {ui} + + ).container; + +const role = (name: string) => `var(--color-${name})`; + +/* Design's field map, and design's "inputs use surface-raised". */ +describe('field colors', () => { + it.each([ + ['Input', , 'input'], + ['TextArea',