Skip to content

feat(gamut-styles)!: brands, overrides and Original Codecademy on the color system - #52

Draft
dreamwasp wants to merge 4 commits into
cass-gmt-1825-color-systemfrom
cass-gmt-1825-color-brands
Draft

dreamwasp wants to merge 4 commits into
cass-gmt-1825-color-systemfrom
cass-gmt-1825-color-brands

Conversation

@dreamwasp

@dreamwasp dreamwasp commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Overview

Builds on #50 so the generated system can carry the old themes: a brand supplies only a palette, every brand shares one role mapping, and manual overrides keep colors the generator wouldn't produce. Adds originalCodecademy, old core rebuilt as a brand. Also moves mode switches to an attribute and renames the color variables to Panda's --colors-*. Draft, stacked on #50.

Brands

  • colorSystem/brands/ — BRANDS drives the build; every brand has to pass every check or nothing is emitted. tokens.css emits roles once and a palette-only block per brand under [data-brand].
  • colorSystem/brand.ts, system.ts — overrides.palette (an exact hex in place of a generated step), overrides.roles (a role repointed in one mode) and overrides.gradients (a gradient role's circles moved to another slot, keeping design's grades, sizes and positions). Rounded to what tokens.css prints and guarded to round-trip; unknown roles or steps throw. brand.sources reports how far each supplied hex lands from its nearest step.
  • brands/originalCodecademy.ts, themes/originalCodecademy.ts:
    • old core's colors per slot, with yellow pinned exactly and old core's yellow dark-mode primary;
    • navy and beige not pinned (design, 2026-10-29), so their generated steps are used;
    • the two-color gradient pairs primary with accent-alt instead of the yellow accent (design, 2026-10-28);
    • old core's fonts, weights and elevation.
  • themes/createBrandTheme.ts — one factory for every brand theme; skillsoftTheme uses it too.
  • emit.ts — brand gradients are written against palette variables (color-mix(in oklab, …)), so one gradient follows every brand (pixel diff against the old literal gradients: max 1/255).

Runtime

  • ColorMode — on generated themes, a mode switch sets data-color-mode and data-brand and injects no role variables. 100 nested switches, server-rendered: 7.6 → 4.2 ms, 5 KB → 46 B of injected CSS. Old themes behave as before.
  • Background — fixes a bug that's also on main (see fix(gamut-styles): Background paints the outer mode's color for an alias bg #51): an alias bg that flips the mode painted the flipped mode's color.

Breaking

  • variance createTheme, gamut-styles — color variables are --colors-*, not --color-*. Matches variance's own convention for scales and its types (the mode colors were already typed var(--colors-…)).
  • tokens.css — all blocks inside @layer tokens, with reset, base, tokens, recipes, utilities declared up front (Panda's order). Emotion's styles stay unlayered and win.

Design sync

  • colorSystem/migration.ts — re-synced to sandbox 15c2195. Since c363244: a pending kind for shadows, and status alerts carry their ink on the message and on a floating border.
  • gamut/src/Alert:
    • General moves to surface-brand-subtle.
    • Gradient alerts are a size container with the gradient on a layer behind the content, so the cqw radii measure the alert, not the viewport.
    • Error, success and notice put text-<status> on the message and on a floating border. General, feature and subtle stay text-default.
    • The CTA follows the Alert's own mode instead of being forced dark, so light alerts get light-mode buttons.

Tests

  • __tests__/stylingContract.test.tsx — css, variant, states, system props, themed, modeColorProps, _getColorValue, ColorMode, Background and the mode hooks on all seven themes in both modes. Every color they write has to resolve through every var() to a real color.
  • validate-tokens.ts — every check per brand, including a round-trip of each brand's roles through tokens.css.

Storybook and docs

  • .storybook — an "Original Codecademy (color spike)" theme option.
  • gamut-agent-tools skills, colorSystem/README.md, the CSS contract story — --colors-*.
  • CSS contract story — Surfaces splits the surface roles from the raw palette fills, which aren't roles.

Known gaps

  • A floating general or feature Alert's border is text-default, following design's coded preview. Design's map still has a shared "Floating border: border-default" slot.
  • Elevation reads shadow-primary, which has no role yet (design has it as pending), so shadows render undefined on the new themes.

PR Checklist

  • Related to designs: gamut-sandbox PR #34 (head 15c2195)
  • Related to JIRA ticket: GMT-1825
  • Changeset added (or empty changeset if no release)
  • I have run this code to verify it works
  • This PR includes unit tests for the code change
  • This PR includes testing instructions for the code change
  • Package previews from this PR are passing end-to-end tests in all relevant consuming repositories

Testing Instructions

  1. yarn workspace @skillsoft/gamut-styles validate:tokens → both brands pass every check, including the tokens.css round-trip.
  2. yarn nx test gamut-styles (281 tests), yarn nx test variance (104), and npx jest packages/gamut/src/Alert (16).
  3. Storybook, theme toolbar → Original Codecademy (color spike), both modes:
    • Molecules / Alert: Feature's gradient is blue to violet, not blue to yellow. Error's message is red and its CTA is a light-mode button in light mode.
    • Inspect a ColorMode element: it carries data-color-mode and data-brand="originalCodecademy", and its class has no --colors-* role declarations.
  4. Compare with today's core: the comparison page shows every role and every old name side by side, with ΔE.
  5. Toolbar → Core: unchanged from feat(gamut-styles)!: generated color system with tokens.css as the color contract #50.

🤖 Generated with Claude Code

… color system

- Brands: every brand shares one role mapping and supplies a palette,
  emitted under [data-brand]. Palette and role overrides go through the
  same checks.
- originalCodecademy: old core rebuilt as a brand, with core's fonts,
  weights and elevation.
- ColorMode sets data-color-mode and data-brand on generated themes
  instead of injecting role variables.
- Background paints the outer mode's palette color for an alias bg.
- BREAKING: --color-* variables are renamed to --colors-* (variance too);
  tokens.css rules live in @layer tokens.
- migration.ts re-synced to gamut-sandbox c363244. Alert General moves to
  surface-brand-subtle.
- Styling contract test across seven themes in both modes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ands

# Conflicts:
#	packages/gamut-styles/src/colorSystem/__tests__/system.test.ts
#	packages/gamut-styles/src/themes/skillsoft.ts
dreamwasp and others added 2 commits October 29, 2026 10:26
… pairs primary with accent-alt

Brands can move a gradient role's circles to another slot
(`overrides.gradients`), keeping design's grades, sizes and positions.
ColorSystem carries each brand's resolved gradients, and the emitters,
the migration contrast checks and tokens.css read from it. A brand whose
gradient differs from the default's gets a `--gradient-*` line under
`[data-brand][data-color-mode]`, like a role override.

Original Codecademy's two-color gradient takes accent-alt in place of
the yellow accent, per design, so it reads blue to violet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ps pinning navy and beige

Design feedback on the comparison page (2026-10-29), and design's
migration map at sandbox 15c2195:

- Status alerts (error, success, notice) put their status ink on the
  message, and a floating Alert's border takes the same ink. General,
  feature and subtle keep text-default.
- The CTA follows the Alert's own color mode. Forcing it dark only made
  sense on the old saturated fills; on the tints, light-mode alerts get
  light-mode buttons.
- The ported migration map is synced to 15c2195 (Alert message slot,
  Alert and floating-surface summaries).
- Original Codecademy no longer pins navy-800 and beige. Pinned, they
  carried much more chroma than the generated surfaces beside them;
  the generated steps at their hues are used as is. Yellow stays exact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dreamwasp added a commit that referenced this pull request Oct 29, 2026
Design feedback on the comparison page (2026-10-29): pinned navy-800
carries much more chroma than the generated surfaces beside it. As for
Original Codecademy (#52), lxStudio uses the generated step at navy's hue.
Admin and platform inherit Original Codecademy's pins, so they follow;
platform's blue-0 and the near-white pages are nearly achromatic and stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@dreamwasp dreamwasp left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixes from design feedback on the comparison pages (2026-10-29), plus design's migration map at sandbox 15c2195. Notes on the lines below.

const buttonColorMode = isSubtleVariant ? currentColorMode : 'dark';

/*
* The tints follow the color mode, so the CTA does too: it inherits the

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Design feedback (comparison page, 2026-10-29): now that the alert fills are mode-following tints, light-mode alerts need light-mode buttons. The CTA was forced to mode="dark" for every non-subtle variant, a holdover from the old saturated fills. It now inherits the mode the Alert's Background sets. The test checks that no forced mode's variables land on the button.

* (design's preview at sandbox 15c2195; its shared "Floating border" slot
* still says border-default and is out of date).
*/
const alertInk = ({ ink, placement, theme }: InkProps) => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Design's map at sandbox 15c2195: status alerts (error, success, notice) put their status ink on the message, not text-default, and a floating Alert's border takes the same ink. General, feature and subtle keep text-default.

Note: the map's shared "Floating border" slot still says border-default, but design's coded preview and the Alert summary both use the message ink. I followed the preview, so a floating general or feature alert's edge is text-default.

kind: 'text',
legacy: 'auto (Background)',
role: 'text-default',
role: `text-${status}`,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Synced to design's sandbox at 15c2195 (commit c5190b6, "Alert: status ink on message text, floating border matches text"). This message slot plus the Alert and floating-surface summaries are the whole diff; the components section now matches the sandbox exactly. validate:tokens still passes: each status ink clears 4.5:1 on its own tint for every brand.

overrides: {
/* Each at the grade whose fixed lightness is nearest. */
palette: {
'accent-300': '#FFD300', // yellow-500

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Design feedback (comparison page, 2026-10-29): pinned navy-800 at neutral-dark-1100 carried much more chroma than the generated sunken and raised surfaces next to it, which made block tabs and table zebra rows look off. Beige had the same problem, less strongly. Both pins are gone; the generated steps at navy's and beige's hues are used as is. Yellow stays exact. Admin and platform (#58) inherit this.

},
},
gradients: {
'surface-brand': { accent: 'accent-alt' },

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feature (and general) alerts on Original Codecademy render this gradient: primary + accent-alt rather than blue to yellow. Design approved the override on the brand comparison page (2026-10-29). The components screenshot page was captured before this landed, so its feature alerts still show the yellow pair until it's retaken.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant