diff --git a/.nx/version-plans/version-plan-1778682208750.md b/.nx/version-plans/version-plan-1778682208750.md new file mode 100644 index 00000000000..8e025c2b51d --- /dev/null +++ b/.nx/version-plans/version-plan-1778682208750.md @@ -0,0 +1,6 @@ +--- +gamut-agent-tools: prerelease +gamut: minor +--- + +Create new gamut-agent-tools package diff --git a/package.json b/package.json index 52b5b04d112..2ca109f173f 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@babel/preset-typescript": "^7.24.7", "@codecademy/eslint-config": "8.0.0", "@codecademy/gamut": "workspace:*", + "@codecademy/gamut-agent-tools": "workspace:*", "@codecademy/prettier-config": "^0.2.0", "@codecademy/tsconfig": "^0.3.0", "@commander-js/extra-typings": "^14.0.0", diff --git a/packages/gamut/agent-tools/.claude-plugin/marketplace.json b/packages/gamut-agent-tools/.claude-plugin/marketplace.json similarity index 100% rename from packages/gamut/agent-tools/.claude-plugin/marketplace.json rename to packages/gamut-agent-tools/.claude-plugin/marketplace.json diff --git a/packages/gamut/agent-tools/.claude-plugin/plugin.json b/packages/gamut-agent-tools/.claude-plugin/plugin.json similarity index 100% rename from packages/gamut/agent-tools/.claude-plugin/plugin.json rename to packages/gamut-agent-tools/.claude-plugin/plugin.json diff --git a/packages/gamut/agent-tools/.cursor-plugin/plugin.json b/packages/gamut-agent-tools/.cursor-plugin/plugin.json similarity index 100% rename from packages/gamut/agent-tools/.cursor-plugin/plugin.json rename to packages/gamut-agent-tools/.cursor-plugin/plugin.json diff --git a/packages/gamut-agent-tools/DESIGN.Codecademy.md b/packages/gamut-agent-tools/DESIGN.Codecademy.md new file mode 100644 index 00000000000..6b900967cb4 --- /dev/null +++ b/packages/gamut-agent-tools/DESIGN.Codecademy.md @@ -0,0 +1,648 @@ +--- +version: alpha +name: Codecademy Design System +description: Design tokens for codecademy.com +colors: + # palette β€” raw swatches; set once on :root and then always reference by token name, never use hex values directly in code + hyper-500: '#3A10E5' + hyper-400: '#5533FF' + navy-900: '#0A0D1C' + navy-800: '#10162F' + navy-700: '#31374C' + navy-600: '#4C5063' + navy-500: '#686C7C' + navy-300: '#BCBEC5' + navy-200: '#E2E3E6' + navy-100: '#F5F6F7' + yellow-500: '#FFD300' + yellow-400: '#CCA900' + yellow-0: '#FFFAE5' + yellow-900: '#211B00' + green-700: '#008A27' + green-400: '#AEE938' + green-0: '#F5FFE3' + green-900: '#151C07' + red-600: '#BE1809' + red-500: '#E91C11' + red-400: '#DC5879' + red-300: '#E85D7F' + red-0: '#FBF1F0' + red-900: '#280503' + beige: '#FFF0E5' + white: '#ffffff' + black: '#000000' + # semantic aliases (light mode) β€” use these in code, not palette swatches or hex values + text: '{colors.navy-800}' + text-accent: '{colors.navy-900}' + text-secondary: '{colors.navy-600}' + text-disabled: '{colors.navy-500}' + background: '{colors.white}' + background-primary: '{colors.beige}' + background-contrast: '{colors.white}' + background-selected: '{colors.navy-100}' + background-hover: '{colors.navy-200}' + background-disabled: '{colors.navy-200}' + background-success: '{colors.green-0}' + background-warning: '{colors.yellow-0}' + background-error: '{colors.red-0}' + primary: '{colors.hyper-500}' + primary-hover: '{colors.hyper-400}' + primary-inverse: '{colors.yellow-500}' + secondary: '{colors.navy-800}' + secondary-hover: '{colors.navy-700}' + interface: '{colors.hyper-500}' + interface-hover: '{colors.hyper-400}' + danger: '{colors.red-500}' + danger-hover: '{colors.red-600}' + feedback-error: '{colors.red-600}' + feedback-success: '{colors.green-700}' + feedback-warning: '{colors.yellow-500}' + border-primary: '{colors.navy-800}' + border-secondary: '{colors.navy-600}' + border-tertiary: '{colors.navy-300}' + border-disabled: '{colors.navy-500}' + shadow-primary: '{colors.navy-800}' + shadow-secondary: '{colors.navy-600}' +typography: + base: + fontFamily: '"Apercu", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif' + fontSize: '1rem' + fontWeight: '400' + lineHeight: '1.5' + accent: + fontFamily: '"Suisse", "Apercu", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif' + fontSize: '0.875rem' + fontWeight: '400' + lineHeight: '1.5' + title: + fontFamily: '"Apercu", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif' + fontSize: '2.125rem' + fontWeight: '700' + lineHeight: '1.2' + hankenGrotesk: + fontFamily: '"Hanken Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif' + monospace: + fontFamily: 'Monaco, Menlo, "Ubuntu Mono", "Droid Sans Mono", Consolas, monospace' +rounded: + none: '0px' + sm: '2px' + md: '4px' + lg: '8px' + xl: '16px' + full: '999px' +spacing: + '0': '0' + '4': '0.25rem' + '8': '0.5rem' + '12': '0.75rem' + '16': '1rem' + '24': '1.5rem' + '32': '2rem' + '40': '2.5rem' + '48': '3rem' + '64': '4rem' + '96': '6rem' +components: + FillButton: + backgroundColor: '{colors.primary}' + textColor: '{colors.white}' + rounded: '{rounded.md}' + FillButton-hover: + backgroundColor: '{colors.primary-hover}' + textColor: '{colors.white}' + FillButton-danger: + backgroundColor: '{colors.danger}' + textColor: '{colors.white}' + rounded: '{rounded.md}' + FillButton-danger-hover: + backgroundColor: '{colors.danger-hover}' + textColor: '{colors.white}' + StrokeButton: + backgroundColor: 'transparent' + textColor: '{colors.secondary}' + rounded: '{rounded.md}' + CTAButton: + backgroundColor: '{colors.primary}' + textColor: '{colors.white}' + rounded: '{rounded.md}' + CTAButton-inverse: + backgroundColor: '{colors.primary-inverse}' + textColor: '{colors.secondary}' + rounded: '{rounded.md}' + TextButton: + backgroundColor: 'transparent' + textColor: '{colors.primary}' + TextButton-hover: + textColor: '{colors.primary-hover}' + Card: + backgroundColor: '{colors.background}' + rounded: '{rounded.none}' + Card-interactive: + rounded: '{rounded.md}' + Card-elevated: + backgroundColor: '{colors.background-primary}' + rounded: '{rounded.lg}' + Card-beige: + backgroundColor: '{colors.beige}' + rounded: '{rounded.lg}' + Input: + backgroundColor: '{colors.background}' + textColor: '{colors.text}' + rounded: '{rounded.md}' + Checkbox: + backgroundColor: '{colors.interface}' + rounded: '{rounded.sm}' + Checkbox-hover: + backgroundColor: '{colors.interface-hover}' + Headline: + textColor: '{colors.text-accent}' + typography: '{typography.title}' + Tag-success: + backgroundColor: '{colors.feedback-success}' + textColor: '{colors.white}' + rounded: '{rounded.sm}' + Tag-warning: + backgroundColor: '{colors.feedback-warning}' + textColor: '{colors.text}' + rounded: '{rounded.sm}' + Alert-error: + backgroundColor: '{colors.background-error}' + textColor: '{colors.feedback-error}' + Alert-success: + backgroundColor: '{colors.background-success}' + textColor: '{colors.text}' + Alert-warning: + backgroundColor: '{colors.background-warning}' + textColor: '{colors.text}' +--- + +# Codecademy + +This file defines the visual design tokens for codecademy.com, implemented using the Gamut design system (`@codecademy/gamut`, `@codecademy/gamut-styles`). Gamut ships 52 components with Figma ↔ code mappings via Figma Code Connect. + +**Figma file**: https://www.figma.com/design/ReGfRNillGABAj5SlITalN/πŸ“-Gamut +**Storybook**: https://gamut.codecademy.com + +--- + +## Visual Theme & Atmosphere + +Codecademy communicates **logic with personality** β€” structured and trustworthy enough for a learning platform, with creative moments that feel engaging and human. The design voice is: _"we are ruled by logic, but are creative and a bit unexpected as well."_ + +**Density**: Medium. Information-dense layouts use careful whitespace and strong typographic hierarchy to stay readable. Avoid cramped or overly airy layouts. + +**Design philosophy**: + +- Components are color mode–aware by default β€” never hardcode hex values for adaptive UI +- Every component works across all themes without modification +- Mobile-first responsive design built on a 12-column grid +- Accessibility is guaranteed by design: semantic color tokens meet contrast requirements per mode automatically + +--- + +## Themes + +Codecademy products use one of four Gamut themes, all sharing the same core visual identity. Token aliases resolve to the right values per theme automatically β€” components require no modification. + +| Theme | Use case | Base font | Dark mode | +| ------------- | ------------------------------- | -------------- | -------------- | +| **Core** | Codecademy (default) | Apercu | βœ“ light + dark | +| **Admin** | Codecademy admin tools | Apercu | βœ“ light + dark | +| **Platform** | Codecademy learning environment | Apercu | βœ“ light + dark | +| **LX Studio** | LX Studio application | Hanken Grotesk | light only | + +The active theme is set at the app root via ``. When designing, know which theme your screen targets β€” it affects primary colors, font families, and available color weights. + +**Font licensing**: Apercu is licensed for codecademy.com only. LX Studio uses Hanken Grotesk. + +For Percipio projects, use `DESIGN.Percipio.md` from the same package instead. + +### LX Studio theme overrides + +LX Studio extends Core with these differences: + +**Font**: All families β†’ `"Hanken Grotesk"` (no Apercu, no Suisse). + +**Border radii** (all values shift up one step): + +| Token | Core | LX Studio | +| ----- | ---- | --------- | +| `sm` | 2px | 4px | +| `md` | 4px | 8px | +| `lg` | 8px | 12px | + +**Semantic color overrides (light mode)**: + +| Token | Core value | LX Studio value | +| -------------------- | ------------------- | ------------------------------- | +| `primary` | hyper-500 `#3A10E5` | `#5628FE` (lxStudioPurple) | +| `primary-hover` | hyper-400 `#5533FF` | `#7955FC` (lxStudioPurpleHover) | +| `interface` | hyper-500 | hyper-500 (unchanged) | +| `feedback-success` | green-700 `#008A27` | `#06844F` (lxStudioSuccess) | +| `background-primary` | beige `#FFF0E5` | `#FAFBFC` (lxStudioBgPrimary) | +| `shadow-primary` | navy-800 | navy-200 | +| `border-primary` | navy-800 | navy-400 | +| `border-disabled` | navy-500 | navy-300 | + +--- + +## Semantic Color Aliases + +Use these token names when specifying colors in designs. They resolve to the correct raw value for the active theme and color mode automatically. **Never hardcode hex values** for anything that needs to adapt across modes. + +### Text + +| Token | Light | Dark | Use for | +| ---------------- | -------------------------- | --------------- | --------------------------- | +| `text` | navy-800 `#10162F` at 100% | white `#ffffff` | Default body and UI text | +| `text-accent` | navy-900 `#0A0D1C` | beige `#FFF0E5` | Stronger emphasis text | +| `text-secondary` | navy-800 at 75% | white at 65% | Supporting / secondary copy | +| `text-disabled` | navy-800 at 63% | white at 50% | Disabled state labels | + +### Background + +| Token | Light | Dark | Use for | +| --------------------- | ------------------ | -------------------- | --------------------------------- | +| `background` | white `#ffffff` | navy-800 `#10162F` | Default page/component background | +| `background-primary` | beige `#FFF0E5` | navy-900 `#0A0D1C` | Slightly elevated surfaces | +| `background-contrast` | white | black `#000000` | Maximum contrast surface | +| `background-selected` | navy-800 at 4% | white at 4% | Selected row / item | +| `background-hover` | navy-800 at 12% | white at 9% | Hover state overlay | +| `background-disabled` | navy-800 at 12% | white at 9% | Disabled surface | +| `background-success` | green-0 `#F5FFE3` | green-900 `#151C07` | Success state container | +| `background-warning` | yellow-0 `#FFFAE5` | yellow-900 `#211B00` | Warning state container | +| `background-error` | red-0 `#FBF1F0` | red-900 `#280503` | Error state container | + +### Interactive + +| Token | Light | Dark | Use for | +| ----------------- | -------------------- | -------------------- | --------------------------------------------- | +| `primary` | hyper-500 `#3A10E5` | yellow-500 `#FFD300` | Primary CTA, links, focus rings | +| `primary-hover` | hyper-400 `#5533FF` | yellow-400 `#CCA900` | Hover state of primary interactive | +| `primary-inverse` | yellow-500 `#FFD300` | hyper-500 `#3A10E5` | Primary on a colored background | +| `secondary` | navy-800 `#10162F` | white `#ffffff` | Secondary CTA, ghost buttons | +| `secondary-hover` | navy-800 at 86% | white at 80% | Hover state of secondary interactive | +| `interface` | hyper-500 `#3A10E5` | yellow-500 `#FFD300` | UI affordances (checkboxes, toggles, sliders) | +| `interface-hover` | hyper-400 `#5533FF` | yellow-400 `#CCA900` | Hover on interface elements | +| `danger` | red-500 `#E91C11` | red-300 `#E85D7F` | Destructive actions, error states | +| `danger-hover` | red-600 `#BE1809` | red-400 `#DC5879` | Hover on danger interactive | + +### Border + +| Token | Light | Dark | Use for | +| ------------------ | ------------------ | --------------- | -------------------------- | +| `border-primary` | navy-800 `#10162F` | white `#ffffff` | Strong borders, dividers | +| `border-secondary` | navy-800 at 75% | white at 65% | Medium-weight borders | +| `border-tertiary` | navy-800 at 28% | white at 20% | Subtle borders, separators | +| `border-disabled` | navy-800 at 63% | white at 50% | Disabled input borders | + +### Feedback + +| Token | Light | Dark | Use for | +| ------------------ | ------------------- | ------------------- | -------------------------------- | +| `feedback-error` | red-600 `#BE1809` | red-300 `#E85D7F` | Error messages, validation | +| `feedback-success` | green-700 `#008A27` | green-400 `#AEE938` | Success messages, confirmations | +| `feedback-warning` | yellow `#FFD300` | yellow-0 `#FFFAE5` | Warning messages, caution states | + +### Shadow + +| Token | Light | Dark | +| ------------------ | --------------- | ------------ | +| `shadow-primary` | navy-800 | white | +| `shadow-secondary` | navy-800 at 75% | white at 65% | + +--- + +## Raw Color Palette + +All colors available as static tokens regardless of color mode. Use these only when a color should be **fixed** and not adapt to dark mode. + +### Core Palette + +| Name | Weights available | Notes | +| --------------- | ---------------------------- | --------------------------------------------------------------------------------- | +| `navy` | 100–900 | 100–700 are rgba transparencies of `#10162F`; 800 = `#10162F`; 900 = `#0A0D1C` | +| `white` | 100–700 | rgba transparencies of `#ffffff` (no solid white weight β€” use `white` for `#fff`) | +| `blue` | 0, 100, 300, 400, 500, 800 | 500 = `#1557FF` | +| `hyper` | 400, 500 | 500 = `#3A10E5` (purple-blue), 400 = `#5533FF` | +| `green` | 0, 100, 400, 700, 900 | 700 = `#008A27` | +| `yellow` | 0, 400, 500, 900 | 500 = `#FFD300` | +| `red` | 0, 300, 400, 500, 600, 900 | 500 = `#E91C11` | +| `gray` | 100, 200, 300, 600, 800, 900 | | +| `pink` | 0, 400 | 400 = `#F966FF` | +| `orange` | 100, 500 | 500 = `#FF8C00` | +| `beige` | 100 (alias: `beige`) | `#FFF0E5` | +| `black` | β€” | `#000000` | +| `white` (solid) | β€” | `#ffffff` | + +**Named aliases** (shorthand for common weights): +`beige`, `blue`, `green`, `hyper`, `lightBlue`, `lightGreen`, `navy`, `orange`, `paleBlue`, `paleGreen`, `palePink`, `paleRed`, `paleYellow`, `pink`, `red`, `yellow`, `black`, `white` + +### Platform-only additions + +`lightBeige` (`#FFFBF8`), `gold` (`#8A7300`), `teal` (`#006D82`), `purple` (`#B3CCFF`) + +### LX Studio additions + +`lxStudioPurple` (`#5628FE`), `lxStudioPurpleHover` (`#7955FC`), `lxStudioSuccess` (`#06844F`) + +--- + +## Typography + +### Typefaces + +| Token | Core / Admin / Platform | LX Studio | Use for | +| ----------- | -------------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------ | +| `base` | Apercu Pro (CSS: `Apercu`) | Hanken Grotesk | All default UI text, headlines, body copy | +| `accent` | Suisse Intl Mono (CSS: `Suisse`); falls back to `Apercu` | Hanken Grotesk | Code, captions, labels, lists, technical context | +| `monospace` | Monaco, Menlo, Ubuntu Mono, Droid Sans Mono, Consolas | Monaco, Menlo, Ubuntu Mono, Droid Sans Mono, Consolas | Code editor contexts | +| `system` | System UI fonts | System UI fonts | Performance-critical surfaces | + +**Apercu is licensed for codecademy.com only.** LX Studio uses Hanken Grotesk for both `base` and `accent`. + +### Rules + +- **Apercu Bold** for headlines, sub-headlines, CTAs, and buttons. +- **Apercu Regular** for body text, UI labels, and menu items. +- **Apercu Italic** to emphasize text within a Regular paragraph β€” not Bold. +- **Suisse** sparingly: code snippets, enumerated items, quotations, captions. Reads 10–15% large for its point size β€” size down relative to Apercu (14px Suisse β‰ˆ 16px Apercu visually). +- Text is **left-aligned** by default. Center-align only for short marketing headlines. Never right-align. +- Do not adjust letter-spacing. + +### Font size scale + +| Token key | Size | Common use | +| --------- | ---- | ---------------------------- | +| `64` | 64px | Hero / display | +| `44` | 44px | Page titles | +| `34` | 34px | Section titles | +| `26` | 26px | Sub-section titles | +| `22` | 22px | Card titles, large UI labels | +| `20` | 20px | Secondary titles | +| `18` | 18px | Large body, intro text | +| `16` | 16px | Default body text | +| `14` | 14px | Small body, captions, labels | + +### Font weight scale + +| Token | Value | Use | +| ------- | ----- | ------------------------ | +| `base` | 400 | Body text, UI labels | +| `title` | 700 | Headlines, CTAs, buttons | + +### Line height scale + +| Token | Value | Use | +| ------------- | ----- | ------------------------------------------------ | +| `base` | 1.5 | Body text (150% β€” aim for 150–175% of font size) | +| `spacedTitle` | 1.3 | Sub-headlines and medium titles | +| `title` | 1.2 | Large headlines (aim for 100–110% of font size) | + +### Line length + +Target 45–85 characters per line; 66 characters is ideal for web body text. Max 50 characters for multi-column layouts. + +--- + +## Spacing Scale + +All spacing is multiples of 4px, placed on an 8px grid. + +| Token | Value | +| ----- | ----- | +| `0` | 0 | +| `4` | 4px | +| `8` | 8px | +| `12` | 12px | +| `16` | 16px | +| `24` | 24px | +| `32` | 32px | +| `40` | 40px | +| `48` | 48px | +| `64` | 64px | +| `96` | 96px | + +--- + +## Depth & Elevation + +Gamut uses border-based and shadow-based depth cues rather than a rigid z-elevation tier system. + +### Shadow tokens + +Shadow props accept standard CSS `box-shadow` syntax. Always use `shadow-primary` / `shadow-secondary` color tokens so shadows remain visible in both light and dark modes. + +``` +box-shadow: 0 4px 0 β†’ Card "outline" shadow +box-shadow: 0 0 4px rgba(0,0,0,.15) β†’ Subtle ambient shadow +``` + +### Card shadow variants + +| Variant | Effect | Use for | +| ---------------- | ------------------------------------------------------ | ------------------------------ | +| `none` (default) | No shadow | Static / non-interactive cards | +| `outline` | Solid shadow on bottom + left/right using border color | Standard clickable cards | +| `patternLeft` | Decorative checker pattern on bottom + left | Stylized content cards | +| `patternRight` | Decorative checker pattern on bottom + right | Stylized content cards | + +Interactive cards (`isInteractive` prop) gain a shadow on hover and `borderRadius: md`. Cards with a pattern drop the pattern on hover. + +### Z-index + +| Token | Value | Use | +| --------- | ----- | ------------------ | +| `headerZ` | 15 | Global page header | + +--- + +## Border Radius Scale + +| Token | Value | Use | +| ------ | ----- | ------------------------------------------ | +| `none` | 0px | Square / non-interactive elements | +| `sm` | 2px | Subtle rounding, tags | +| `md` | 4px | Default buttons, inputs, interactive cards | +| `lg` | 8px | Cards, panels | +| `xl` | 16px | Large cards, modals | +| `full` | 999px | Pills, avatars, circular elements | + +--- + +## Responsive Behavior + +Mobile-first. Apply styles from the named breakpoint and up. + +### Breakpoints & screen sizes + +| Token | Min-width | Screen dimensions | Max content | Fold height | +| -------- | --------- | ----------------- | ----------- | ----------- | +| _(base)_ | 0 | 320Γ—480 | 288px | 440px | +| `xs` | 480px | 480Γ—900 | 448px | 440px | +| `sm` | 768px | 768Γ—1024 | 704px | 680px | +| `md` | 1024px | 1024Γ—768 | 896px | 680px | +| `lg` | 1200px | 1200Γ—900 | 1072px | 680px | +| `xl` | 1440px | 1440Γ—900 | 1248px | 680px | + +Container query variants (`c_xs` through `c_xl`) mirror these values but trigger on component container size, not viewport. + +### Grid + +12-column grid at all breakpoints. The designer specifies how many columns a section spans per breakpoint. + +| Usage | Recommended values | +| --------------------- | ------------------------------------------------ | +| Horizontal margins | 64px (lg+), 48px (md), 32px (sm/xs), 16px (base) | +| Column gaps (gutters) | 32px (lg+), 24px (md), 16px (sm/xs), 8px (base) | +| Row gaps | 32px (lg+), 24px (md), 16px (sm/xs), 8px (base) | + +### Touch targets + +Minimum interactive touch target: **44Γ—44px** on mobile breakpoints. + +### Collapsing strategies + +- Begin design work at 1440px (XL), then adapt to smaller sizes. +- Wider multi-column layouts collapse to fewer columns β€” do not simply stretch or squish. +- Elements not in an explicit lockup (e.g., catalog cards) should align on one axis (usually left) rather than fill column widths. +- Avoid dense or small components in the base (mobile) breakpoint. + +--- + +## Component Library + +Components are organized into three tiers: + +### Atoms β€” foundational, single-purpose + +Badge, Button (FillButton, StrokeButton, CTAButton, TextButton, IconButton), ButtonBase, Card, Checkbox, CodeBlock, ColorMode, Drawer, FlexBox, FormGroup, GridBox, HiddenText, Icon, Input, Label, Loader, Radio, Select, Spinner, Tag, TextArea, Toggle, Tooltip + +### Molecules β€” composed of atoms, handle a discrete task + +Alert, Anchor, Breadcrumbs, Coachmark, Disclosure, GridForm, Markdown, Menu, Modal, Pagination, Popover, ProgressBar, Table, Tabs, Toast, Toaster, Video + +### Organisms β€” page-level compositions + +ContentContainer, GridContainer, Layout, LayoutGrid + +### Key component patterns + +#### Buttons + +| Variant | Component | Use for | +| ----------------- | -------------- | ----------------------------------- | +| Primary action | `FillButton` | Solid fill, high-emphasis CTA | +| Secondary action | `StrokeButton` | Outlined, secondary CTA | +| Marketing CTA | `CTAButton` | High-visibility promotional actions | +| Tertiary / inline | `TextButton` | Low-emphasis, inline text actions | +| Icon-only | `IconButton` | Compact actions with icon only | + +All button variants support sizes: `small`, `normal` (default), `large`. They accept an `icon` prop (leading or trailing) and a `disabled` prop. Passing `href` renders the button as an `` tag. + +**States**: default β†’ hover (`primary-hover` / `secondary-hover`) β†’ active β†’ disabled (`text-disabled` + `background-disabled`). + +#### Cards + +Cards support: + +- **Background variants**: `default` (ColorMode-responsive), `white`, `yellow`, `beige` (light contexts), `navy`, `hyper` (dark contexts) +- **Shadow variants**: `none` (default), `outline`, `patternLeft`, `patternRight` +- **Interaction**: wrap in `` and add `isInteractive` for hover shadow + `borderRadius: md` +- **Border radius**: defaults to `none` (non-interactive); override with the `borderRadius` prop as needed + +#### Color-aware components + +- **``** β€” wraps a subtree in an explicit color mode. +- **``** β€” applies a background color and automatically switches the color mode inside to maintain accessible contrast. Prefer this over setting a raw `bg` prop on any content-bearing surface. + +--- + +## Global Elements + +| Token | Value | Use | +| -------------- | -------------------------------------- | ------------------------------- | +| `headerHeight` | 4rem (64px) base, 5rem (80px) at `md`+ | Global page header height | +| `headerZ` | 15 | Z-index for global page headers | + +--- + +## Do's and Don'ts + +### Colors + +- **Do** use semantic color aliases (`primary`, `text`, `background`, etc.) for any UI that must adapt to color mode or theme. +- **Do** use `` when setting a section background β€” it adjusts the inner color mode for contrast automatically. +- **Don't** hardcode hex values for anything adaptive. +- **Don't** use navy or white semi-transparent swatches where they may overlap unpredictably. + +### Typography + +- **Do** use `title` weight (700) for headlines, CTAs, and buttons. +- **Do** keep body text at 150–175% line height for readability. +- **Do** use Suisse sparingly β€” as an accent for code, captions, and lists only. +- **Don't** use Apercu Bold to emphasize text _within_ a paragraph β€” use Italic instead. +- **Don't** adjust letter-spacing. +- **Don't** right-align text in normal circumstances. +- **Don't** center-align body paragraphs with long line lengths. + +### Layout & Spacing + +- **Do** use multiples of 8px for block-element spacing (4px only for inline / typographic relationships). +- **Do** begin design work at 1440px (XL), then adapt down to each breakpoint. +- **Do** align elements to the 12-column grid. +- **Don't** stretch elements to fill wider space β€” maintain proper line lengths and component widths. + +### Components + +- **Do** use `FillButton` for primary actions and `StrokeButton` for secondary actions. +- **Do** add `isInteractive` to any `Card` that is wrapped in an ``. +- **Don't** use `CTAButton` for standard UI actions β€” reserve it for marketing/high-visibility promotions. +- **Don't** use `` without an actual color value β€” it's not a neutral wrapper. + +--- + +## Agent Prompt Guide + +Quick color/token reference for generating or specifying UI: + +| Scenario | Tokens | +| ---------------------- | ----------------------------------------------------------------------------------------------------- | +| Primary button (light) | `bg: primary (#3A10E5)`, `color: white`, `hover: primary-hover (#5533FF)` | +| Primary button (dark) | `bg: primary (#FFD300)`, `color: navy-800`, `hover: primary-hover (#CCA900)` | +| Body text | `color: text`, `font: base (Apercu Pro)`, `size: 16px`, `weight: 400`, `lineHeight: base (1.5)` | +| Headline | `color: text-accent`, `font: base`, `size: 34–64px`, `weight: title (700)`, `lineHeight: title (1.2)` | +| Caption / label | `color: text-secondary`, `font: accent (Suisse Int'l Mono)`, `size: 14px` | +| Card default | `bg: background`, `borderRadius: none` β€” add `isInteractive` for hover shadow + `borderRadius: md` | +| Error state | `color: feedback-error`, `bg: background-error`, `border: danger` | +| Success state | `color: feedback-success`, `bg: background-success` | +| Disabled state | `color: text-disabled`, `bg: background-disabled`, `border: border-disabled` | + +### Component token cheatsheet + +``` +FillButton β†’ bg: primary, color: white, hover: primary-hover +StrokeButton β†’ bg: transparent, border: secondary, hover: secondary-hover +CTAButton β†’ high-visibility; use primary-inverse on colored surfaces +Card (light) β†’ variant: "default" | "white" | "yellow" | "beige" +Card (dark) β†’ variant: "navy" | "hyper" +Alert (error) β†’ uses feedback-error + background-error +Alert (success) β†’ uses feedback-success + background-success +Alert (warning) β†’ uses feedback-warning + background-warning +ColorMode β†’ +Background β†’ β€” auto-flips color mode for contrast +``` + +--- + +## Figma ↔ Code Mapping + +52 components have Code Connect entries in `packages/code-connect/`. These appear as live code snippets in Figma's inspect panel when you select a component. + +Figma layer names use emojis as visual shorthand (e.g. `✏️ label`, `πŸ‘ leading icon`, `↳ trailing icon`). These map to named props in the React components. + +To publish updated code snippets after changing a component: + +``` +npx figma connect publish --token +``` diff --git a/packages/gamut-agent-tools/DESIGN.LXStudio.md b/packages/gamut-agent-tools/DESIGN.LXStudio.md new file mode 100644 index 00000000000..980c7805df9 --- /dev/null +++ b/packages/gamut-agent-tools/DESIGN.LXStudio.md @@ -0,0 +1,446 @@ +--- +version: alpha +name: LX Studio Design System +description: Design tokens for the Skillsoft LX Studio authoring platform. +colors: + # LX Studio additions β€” custom brand tokens + lxStudioPurple: '#5628FE' + lxStudioPurpleHover: '#7955FC' + lxStudioSuccess: '#06844F' + lxStudioBgPrimary: '#FAFBFC' + # core palette β€” referenced by semantic aliases below + hyper-500: '#3A10E5' + hyper-400: '#5533FF' + navy-900: '#0A0D1C' + navy-800: '#10162F' + navy-700: '#31374C' + navy-600: '#4C5063' + navy-500: '#686C7C' + navy-400: '#8F919D' + navy-300: '#BCBEC5' + navy-200: '#E2E3E6' + navy-100: '#F5F6F7' + yellow-500: '#FFD300' + yellow-0: '#FFFAE5' + green-700: '#008A27' + green-0: '#F5FFE3' + red-600: '#BE1809' + red-500: '#E91C11' + red-0: '#FBF1F0' + white: '#ffffff' + # semantic aliases β€” use these in code, not palette swatches or hex values + text: '{colors.navy-800}' + text-accent: '{colors.navy-900}' + text-secondary: '{colors.navy-600}' + text-disabled: '{colors.navy-500}' + background: '{colors.white}' + background-primary: '{colors.lxStudioBgPrimary}' + background-contrast: '{colors.white}' + background-selected: '{colors.navy-100}' + background-hover: '{colors.navy-200}' + background-disabled: '{colors.navy-200}' + background-success: '{colors.green-0}' + background-warning: '{colors.yellow-0}' + background-error: '{colors.red-0}' + primary: '{colors.lxStudioPurple}' + primary-hover: '{colors.lxStudioPurpleHover}' + primary-inverse: '{colors.yellow-500}' + secondary: '{colors.navy-800}' + secondary-hover: '{colors.navy-700}' + interface: '{colors.hyper-500}' + interface-hover: '{colors.hyper-400}' + danger: '{colors.red-500}' + danger-hover: '{colors.red-600}' + feedback-error: '{colors.red-600}' + feedback-success: '{colors.lxStudioSuccess}' + feedback-warning: '{colors.yellow-500}' + border-primary: '{colors.navy-400}' + border-secondary: '{colors.navy-600}' + border-tertiary: '{colors.navy-800}' + border-disabled: '{colors.navy-300}' + shadow-primary: '{colors.navy-200}' + shadow-secondary: '{colors.navy-600}' +typography: + base: + fontFamily: '"Hanken Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif' + fontSize: '1rem' + fontWeight: '400' + lineHeight: '1.5' + accent: + fontFamily: '"Hanken Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif' + fontSize: '0.875rem' + fontWeight: '400' + lineHeight: '1.5' + title: + fontFamily: '"Hanken Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif' + fontSize: '2.125rem' + fontWeight: '700' + lineHeight: '1.2' + monospace: + fontFamily: 'Monaco, Menlo, "Ubuntu Mono", "Droid Sans Mono", Consolas, monospace' +rounded: + none: '0px' + sm: '4px' + md: '8px' + lg: '12px' + xl: '16px' + full: '999px' +components: + FillButton: + backgroundColor: '{colors.primary}' + textColor: '{colors.white}' + rounded: '{rounded.md}' + FillButton-hover: + backgroundColor: '{colors.primary-hover}' + textColor: '{colors.white}' + FillButton-danger: + backgroundColor: '{colors.danger}' + textColor: '{colors.white}' + rounded: '{rounded.md}' + FillButton-danger-hover: + backgroundColor: '{colors.danger-hover}' + textColor: '{colors.white}' + StrokeButton: + backgroundColor: 'transparent' + textColor: '{colors.secondary}' + rounded: '{rounded.md}' + Input: + backgroundColor: '{colors.background}' + textColor: '{colors.text}' + rounded: '{rounded.md}' + borderColor: '{colors.border-primary}' + Checkbox: + backgroundColor: '{colors.interface}' + rounded: '{rounded.sm}' + Checkbox-hover: + backgroundColor: '{colors.interface-hover}' + Alert-error: + backgroundColor: '{colors.background-error}' + textColor: '{colors.feedback-error}' + Alert-success: + backgroundColor: '{colors.background-success}' + textColor: '{colors.text}' + Alert-warning: + backgroundColor: '{colors.background-warning}' + textColor: '{colors.text}' +--- + +# LX Studio + +This file defines the visual design tokens for the Skillsoft LX Studio authoring platform, implemented using the Gamut design system (`@codecademy/gamut`, `@codecademy/gamut-styles`). LX Studio uses a dedicated Gamut theme that extends Core with its own brand colors, typography, and border radii β€” all Gamut components work without modification. + +**Storybook**: https://gamut.codecademy.com + +--- + +## Visual Theme & Atmosphere + +LX Studio communicates **modern professional craft** β€” clean, precise, and tool-like. As an authoring environment for learning content creators, the interface must feel capable and unobtrusive. The design voice prioritizes clarity and control over personality. + +**Density**: Medium. Layouts are information-dense but well-spaced; generous border radii and soft shadows reduce visual weight. + +**Design philosophy**: + +- Light mode only β€” no dark mode support +- Larger border radii than Core give the UI a softer, more modern feel +- Brand purple (`lxStudioPurple`) drives primary CTAs; `hyper-500` drives interface affordances (checkboxes, toggles) +- Shadows are soft (navy-200) rather than hard (navy-800 in Core light mode) +- Hanken Grotesk replaces Apercu and Suisse across all font roles + +--- + +## Themes + +LX Studio uses a single Gamut theme β€” light mode only. + +| Theme | Use case | Base font | Dark mode | +| ------------- | -------------------------------------- | -------------- | ---------- | +| **LX Studio** | Skillsoft LX Studio authoring platform | Hanken Grotesk | light only | + +The active theme is set at the app root via ``. + +--- + +## Semantic Color Aliases + +Use these token names when specifying colors. LX Studio is light mode only β€” there are no dark mode counterparts. + +### Text + +| Token | Value | Use for | +| ---------------- | -------------------- | --------------------------- | +| `text` | `#10162F` (navy-800) | Default body and UI text | +| `text-accent` | `#0A0D1C` (navy-900) | Stronger emphasis text | +| `text-secondary` | `#4C5063` (navy-600) | Supporting / secondary copy | +| `text-disabled` | `#686C7C` (navy-500) | Disabled state labels | + +### Background + +| Token | Value | Use for | +| --------------------- | ----------------------------- | --------------------------------- | +| `background` | `#ffffff` | Default page/component background | +| `background-primary` | `#FAFBFC` (lxStudioBgPrimary) | Slightly elevated surfaces | +| `background-contrast` | `#ffffff` | Maximum contrast surface | +| `background-selected` | `#F5F6F7` (navy-100) | Selected row / item | +| `background-hover` | `#E2E3E6` (navy-200) | Hover state overlay | +| `background-disabled` | `#E2E3E6` (navy-200) | Disabled surface | +| `background-success` | `#F5FFE3` (green-0) | Success state container | +| `background-warning` | `#FFFAE5` (yellow-0) | Warning state container | +| `background-error` | `#FBF1F0` (red-0) | Error state container | + +### Interactive + +| Token | Value | Use for | +| ----------------- | ------------------------------- | --------------------------------------------- | +| `primary` | `#5628FE` (lxStudioPurple) | Primary CTA, links, focus rings | +| `primary-hover` | `#7955FC` (lxStudioPurpleHover) | Hover state of primary interactive | +| `primary-inverse` | `#FFD300` (yellow-500) | Primary on a colored background | +| `secondary` | `#10162F` (navy-800) | Secondary CTA, ghost buttons | +| `secondary-hover` | `#31374C` (navy-700) | Hover state of secondary interactive | +| `interface` | `#3A10E5` (hyper-500) | UI affordances (checkboxes, toggles, sliders) | +| `interface-hover` | `#5533FF` (hyper-400) | Hover on interface elements | +| `danger` | `#E91C11` (red-500) | Destructive actions, error states | +| `danger-hover` | `#BE1809` (red-600) | Hover on danger interactive | + +**Key distinction**: `primary` (lxStudioPurple `#5628FE`) differs from `interface` (hyper-500 `#3A10E5`). Buttons and links use the lighter LX Studio purple; checkboxes, toggles, and sliders use the deeper hyper purple. + +### Border + +| Token | Value | Use for | +| ------------------ | -------------------- | ------------------------------- | +| `border-primary` | `#8F919D` (navy-400) | Standard input and card borders | +| `border-secondary` | `#4C5063` (navy-600) | Medium-weight borders | +| `border-tertiary` | `#10162F` (navy-800) | Strong structural borders | +| `border-disabled` | `#BCBEC5` (navy-300) | Disabled input borders | + +LX Studio's `border-primary` is mid-gray (navy-400) rather than Core's near-black navy-800 β€” borders are softer and less prominent. + +### Feedback + +| Token | Value | Use for | +| ------------------ | --------------------------- | -------------------------------- | +| `feedback-error` | `#BE1809` (red-600) | Error messages, validation | +| `feedback-success` | `#06844F` (lxStudioSuccess) | Success messages, confirmations | +| `feedback-warning` | `#FFD300` (yellow-500) | Warning messages, caution states | + +### Shadow + +| Token | Value | +| ------------------ | -------------------- | +| `shadow-primary` | `#E2E3E6` (navy-200) | +| `shadow-secondary` | `#4C5063` (navy-600) | + +LX Studio shadows are soft β€” use `shadow-primary` for standard elevated surfaces. This matches Percipio's shadow weight, not Core's hard navy-800 shadow. + +--- + +## LX Studio Color Palette + +LX Studio adds four named colors to the core palette. Use semantic aliases in code, not these raw names. + +| Named color | Value | Mapped to | +| --------------------- | --------- | -------------------- | +| `lxStudioPurple` | `#5628FE` | `primary` | +| `lxStudioPurpleHover` | `#7955FC` | `primary-hover` | +| `lxStudioSuccess` | `#06844F` | `feedback-success` | +| `lxStudioBgPrimary` | `#FAFBFC` | `background-primary` | + +The full core swatch palette (navy, hyper, blue, green, yellow, red, etc.) is also available. Raw swatches should only be used for fixed colors that must not adapt (illustrations, data viz, etc.). + +--- + +## Typography + +### Typefaces + +LX Studio uses **Hanken Grotesk** for all font roles. There is no Apercu and no Suisse. + +| Token | Font | Use for | +| ----------- | ----------------------------------------------------- | --------------------------------------------------------------- | +| `base` | `"Hanken Grotesk"`, sans-serif fallback | All default UI text, headlines, body copy | +| `accent` | `"Hanken Grotesk"`, sans-serif fallback | Labels, captions, technical context (same as base in LX Studio) | +| `monospace` | Monaco, Menlo, Ubuntu Mono, Droid Sans Mono, Consolas | Code editor contexts | +| `system` | System UI fonts | Performance-critical surfaces | + +Hanken Grotesk is served from `https://www.codecademy.com/gamut/` in four variants: regular, italic, bold, bold-italic. + +### Rules + +- **Hanken Grotesk Bold (700)** for headlines, sub-headlines, CTAs, and buttons. +- **Hanken Grotesk Regular (400)** for body text, UI labels, and menu items. +- Text is **left-aligned** by default. Center-align only for short marketing headlines. Never right-align. +- Do not adjust letter-spacing. +- No separate accent typeface β€” Hanken Grotesk is used uniformly for `base` and `accent`. + +### Font weight scale + +| Token | Value | Use | +| ------- | ----- | ------------------------ | +| `base` | 400 | Body text, UI labels | +| `title` | 700 | Headlines, CTAs, buttons | + +### Font size scale + +Shared with Core β€” all sizes are identical. + +| Token key | Size | Common use | +| --------- | ---- | ---------------------------- | +| `64` | 64px | Hero / display | +| `44` | 44px | Page titles | +| `34` | 34px | Section titles | +| `26` | 26px | Sub-section titles | +| `22` | 22px | Card titles, large UI labels | +| `20` | 20px | Secondary titles | +| `18` | 18px | Large body, intro text | +| `16` | 16px | Default body text | +| `14` | 14px | Small body, captions, labels | + +### Line height scale + +Shared with Core. + +| Token | Value | Use | +| ------------- | ----- | ------------------------------- | +| `base` | 1.5 | Body text | +| `spacedTitle` | 1.3 | Sub-headlines and medium titles | +| `title` | 1.2 | Large headlines | + +### Line length + +Target 45–85 characters per line; 66 characters is ideal. Max 50 for multi-column layouts. + +--- + +## Spacing Scale + +Identical to Core. All spacing is multiples of 4px on an 8px grid. + +| Token | Value | +| ----- | ----- | +| `0` | 0 | +| `4` | 4px | +| `8` | 8px | +| `12` | 12px | +| `16` | 16px | +| `24` | 24px | +| `32` | 32px | +| `40` | 40px | +| `48` | 48px | +| `64` | 64px | +| `96` | 96px | + +--- + +## Border Radius Scale + +LX Studio uses larger border radii than Core β€” everything is one step softer. + +| Token | LX Studio | Core | Use | +| ------ | --------- | ----- | ------------------------------------------ | +| `none` | 0px | 0px | Square / non-interactive elements | +| `sm` | **4px** | 2px | Subtle rounding, tags, checkboxes | +| `md` | **8px** | 4px | Default buttons, inputs, interactive cards | +| `lg` | **12px** | 8px | Cards, panels | +| `xl` | 16px | 16px | Large cards, modals | +| `full` | 999px | 999px | Pills, avatars, circular elements | + +--- + +## Responsive Behavior + +Identical to Core. Mobile-first, apply styles from the named breakpoint up. + +| Token | Min-width | Max content | +| -------- | --------- | ----------- | +| _(base)_ | 0 | 288px | +| `xs` | 480px | 448px | +| `sm` | 768px | 704px | +| `md` | 1024px | 896px | +| `lg` | 1200px | 1072px | +| `xl` | 1440px | 1248px | + +12-column grid at all breakpoints. + +| Usage | Recommended values | +| --------------------- | ------------------------------------------------ | +| Horizontal margins | 64px (lg+), 48px (md), 32px (sm/xs), 16px (base) | +| Column gaps (gutters) | 32px (lg+), 24px (md), 16px (sm/xs), 8px (base) | +| Row gaps | 32px (lg+), 24px (md), 16px (sm/xs), 8px (base) | + +Minimum interactive touch target: **44Γ—44px** on mobile breakpoints. + +--- + +## Component Library + +Same component library as Codecademy β€” all atoms, molecules, and organisms apply. Token values resolve differently per theme automatically. + +Key LX Studio-specific visual differences: + +- `FillButton` uses `#5628FE` (lxStudioPurple) instead of hyper-500 +- `FillButton` hover shifts to `#7955FC` (lxStudioPurpleHover) β€” lighter, not darker, on hover +- `Checkbox` / `Toggle` use `hyper-500` (`#3A10E5`) β€” not the brand purple +- All interactive elements have `borderRadius: md` (8px) instead of Core's 4px +- `Card` shadows use navy-200 (soft) rather than navy-800 (hard) +- No `Card-beige` variant β€” LX Studio `background-primary` is off-white, not beige + +--- + +## Do's and Don'ts + +### Colors + +- **Do** use semantic color aliases (`primary`, `text`, `background`, etc.) β€” never hardcode hex values. +- **Do** use `lxStudioPurple` (`#5628FE`) via `primary` for buttons and links. +- **Do** use `hyper-500` (`#3A10E5`) via `interface` for checkboxes, toggles, and sliders. +- **Don't** use `primary` and `interface` interchangeably β€” they are intentionally different purples. +- **Don't** attempt dark mode β€” LX Studio is light only. +- **Don't** use the Percipio or Codecademy primary blue/hyper colors directly; go through semantic aliases. + +### Typography + +- **Do** use Hanken Grotesk Bold (700) for headlines, CTAs, and buttons. +- **Do** keep body text at 150–175% line height for readability. +- **Don't** use Apercu or Suisse β€” those fonts are not available in LX Studio. +- **Don't** right-align or center-align body paragraphs. +- **Don't** adjust letter-spacing. + +### Layout & Spacing + +- **Do** use multiples of 8px for block-element spacing (4px only for inline / typographic relationships). +- **Do** begin design work at 1440px (XL), then adapt down. +- **Do** align elements to the 12-column grid. +- **Do** apply the larger `md` border radius (8px) to buttons and inputs β€” it defines the LX Studio feel. + +--- + +## Agent Prompt Guide + +Quick color/token reference for generating or specifying LX Studio UI: + +| Scenario | Tokens | +| ----------------- | ------------------------------------------------------------------------------------------------------------------ | +| Primary button | `bg: primary (#5628FE)`, `color: white`, `hover: primary-hover (#7955FC)`, `borderRadius: md (8px)` | +| Body text | `color: text (#10162F)`, `font: Hanken Grotesk`, `size: 16px`, `weight: 400`, `lineHeight: base (1.5)` | +| Headline | `color: text-accent (#0A0D1C)`, `font: Hanken Grotesk`, `size: 34–64px`, `weight: 700`, `lineHeight: title (1.2)` | +| Secondary text | `color: text-secondary (#4C5063)` | +| Disabled text | `color: text-disabled (#686C7C)` | +| Elevated surface | `bg: background-primary (#FAFBFC)` | +| Card default | `bg: background (#ffffff)`, `borderRadius: none` β€” add `isInteractive` for hover shadow + `borderRadius: md (8px)` | +| Checkbox / toggle | `interface (#3A10E5)`, `hover: interface-hover (#5533FF)` | +| Error state | `color: feedback-error (#BE1809)`, `bg: background-error (#FBF1F0)`, `border: danger` | +| Success state | `color: feedback-success (#06844F)`, `bg: background-success (#F5FFE3)` | +| Warning state | `color: feedback-warning (#FFD300)`, `bg: background-warning (#FFFAE5)` | +| Disabled state | `color: text-disabled (#686C7C)`, `bg: background-disabled (#E2E3E6, navy-200)`, `border: border-disabled` | + +### Component token cheatsheet + +``` +FillButton β†’ bg: primary (#5628FE), color: white, hover: primary-hover (#7955FC), radius: 8px +StrokeButton β†’ bg: transparent, border: secondary (#10162F) +Checkbox/Toggle β†’ interface (#3A10E5), hover: interface-hover (#5533FF), radius: 4px +Card β†’ bg: background, shadow: shadow-primary (#E2E3E6, navy-200, soft), radius: none +Alert (error) β†’ uses feedback-error + background-error +Alert (success) β†’ uses feedback-success + background-success +Alert (warning) β†’ uses feedback-warning + background-warning +``` diff --git a/packages/gamut-agent-tools/DESIGN.Percipio.md b/packages/gamut-agent-tools/DESIGN.Percipio.md new file mode 100644 index 00000000000..9df40a46a2a --- /dev/null +++ b/packages/gamut-agent-tools/DESIGN.Percipio.md @@ -0,0 +1,437 @@ +--- +version: alpha +name: Percipio Design System +description: Design tokens for the Skillsoft Percipio platform. +colors: + # palette β€” raw swatches; set once on :root and then always reference by token name, never use hex values directly in code + percipioTextPrimary: '#222325' + percipioTextSecondary: '#595A5C' + percipioTextDisabled: '#AFB6C2' + percipioActionPrimary: '#0073C4' + percipioActionPrimaryHover: '#141C36' + percipioActionSecondary: '#6A6E75' + percipioActionSecondaryHover: '#7F8288' + percipioActionDangerHover: '#A52020' + percipioDanger: '#B83C3C' + percipioFeedbackSuccess: '#1B8057' + percipioFeedbackWarning: '#EF5B0D' + percipioBgPrimary: '#FAFBFC' + percipioBgSuccess: '#EEF7F3' + percipioBgWarning: '#FFF7E0' + percipioBgError: '#FFF1F5' + navy-800: '#10162F' + navy-400: '#8F919D' + navy-300: '#BCBEC5' + navy-200: '#E2E3E6' + navy-100: '#F5F6F7' + white: '#ffffff' + # semantic aliases β€” use these in code, not palette swatches or hex values + text: '{colors.percipioTextPrimary}' + text-accent: '{colors.percipioTextPrimary}' + text-secondary: '{colors.percipioTextSecondary}' + text-disabled: '{colors.percipioTextDisabled}' + background: '{colors.white}' + background-primary: '{colors.percipioBgPrimary}' + background-selected: '{colors.navy-100}' + background-hover: '{colors.navy-200}' + background-disabled: '{colors.navy-200}' + background-success: '{colors.percipioBgSuccess}' + background-warning: '{colors.percipioBgWarning}' + background-error: '{colors.percipioBgError}' + primary: '{colors.percipioActionPrimary}' + primary-hover: '{colors.percipioActionPrimaryHover}' + primary-inverse: '{colors.white}' + secondary: '{colors.percipioActionSecondary}' + secondary-hover: '{colors.percipioActionSecondaryHover}' + interface: '{colors.percipioActionPrimary}' + interface-hover: '{colors.percipioActionPrimaryHover}' + danger: '{colors.percipioDanger}' + danger-hover: '{colors.percipioActionDangerHover}' + feedback-error: '{colors.percipioDanger}' + feedback-success: '{colors.percipioFeedbackSuccess}' + feedback-warning: '{colors.percipioFeedbackWarning}' + border-primary: '{colors.navy-400}' + border-secondary: '{colors.navy-200}' + border-tertiary: '{colors.navy-800}' + border-disabled: '{colors.navy-300}' + shadow-primary: '{colors.navy-200}' + shadow-secondary: '{colors.navy-400}' +typography: + base: + fontFamily: '"Roboto", sans-serif' + fontSize: '1rem' + fontWeight: '400' + lineHeight: '1.5' + accent: + fontFamily: '"Roboto", sans-serif' + fontSize: '0.875rem' + fontWeight: '400' + lineHeight: '1.5' + title: + fontFamily: '"Roboto", sans-serif' + fontSize: '2.125rem' + fontWeight: '500' + lineHeight: '1.2' + monospace: + fontFamily: '"Roboto Mono", monospace' +components: + FillButton: + backgroundColor: '{colors.primary}' + textColor: '{colors.primary-inverse}' + rounded: '{rounded.md}' + FillButton-hover: + backgroundColor: '{colors.primary-hover}' + textColor: '{colors.primary-inverse}' + FillButton-danger: + backgroundColor: '{colors.danger}' + textColor: '{colors.white}' + rounded: '{rounded.md}' + FillButton-danger-hover: + backgroundColor: '{colors.danger-hover}' + textColor: '{colors.white}' + StrokeButton: + backgroundColor: 'transparent' + textColor: '{colors.secondary}' + rounded: '{rounded.md}' + Input: + backgroundColor: '{colors.background}' + textColor: '{colors.text}' + rounded: '{rounded.md}' + borderColor: '{colors.border-primary}' + Checkbox: + backgroundColor: '{colors.interface}' + rounded: '{rounded.sm}' + Checkbox-hover: + backgroundColor: '{colors.interface-hover}' + Alert-error: + backgroundColor: '{colors.background-error}' + textColor: '{colors.feedback-error}' + Alert-success: + backgroundColor: '{colors.background-success}' + textColor: '{colors.text}' + Alert-warning: + backgroundColor: '{colors.background-warning}' + textColor: '{colors.text}' +--- + +# Percipio + +This file defines the visual design tokens for the Skillsoft Percipio platform, implemented using the Gamut design system (`@codecademy/gamut`, `@codecademy/gamut-styles`). Percipio uses a dedicated Gamut theme that applies its own colors and typography β€” all Gamut components work without modification. + +**Storybook**: https://gamut.codecademy.com + +--- + +## Visual Theme & Atmosphere + +Percipio communicates **professional clarity** β€” clean, trustworthy, and enterprise-ready. The design voice is more neutral and corporate than Codecademy: less playful, more functional. Primary blue drives interactive affordances; neutral grays define text and structure. + +**Density**: Medium. Consistent with Codecademy layouts but with softer shadows and a lighter overall feel due to the muted neutral palette. + +**Design philosophy**: + +- Light mode only β€” no dark mode support +- Primary blue (`percipioActionPrimary`) replaces Codecademy's `hyper-500` for all interactive elements +- Text is near-black dark gray rather than navy +- Shadows are soft and minimal (navy at low opacity) rather than hard borders +- Title font weight is 500 (medium) rather than 700 (bold) β€” Percipio headlines are less heavy + +--- + +## Themes + +Percipio uses a single Gamut theme β€” light mode only. + +| Theme | Use case | Base font | Dark mode | +| ------------ | --------------------------- | --------- | ---------- | +| **Percipio** | Skillsoft Percipio platform | Roboto | light only | + +The active theme is set at the app root via ``. + +--- + +## Semantic Color Aliases + +Use these token names when specifying colors. Percipio is light mode only β€” there are no dark mode counterparts. + +### Text + +| Token | Value | Use for | +| ---------------- | --------- | ------------------------------------------------ | +| `text` | `#222325` | Default body and UI text | +| `text-accent` | `#222325` | Emphasis text (same value as `text` in Percipio) | +| `text-secondary` | `#595A5C` | Supporting / secondary copy | +| `text-disabled` | `#AFB6C2` | Disabled state labels | + +### Background + +| Token | Value | Use for | +| --------------------- | -------------------- | --------------------------------- | +| `background` | `#ffffff` | Default page/component background | +| `background-primary` | `#FAFBFC` | Slightly elevated surfaces | +| `background-selected` | `#F5F6F7` (navy-100) | Selected row / item | +| `background-hover` | `#E2E3E6` (navy-200) | Hover state overlay | +| `background-disabled` | `#E2E3E6` (navy-200) | Disabled surface | +| `background-success` | `#EEF7F3` | Success state container | +| `background-warning` | `#FFF7E0` | Warning state container | +| `background-error` | `#FFF1F5` | Error state container | + +### Interactive + +| Token | Value | Use for | +| ----------------- | --------- | --------------------------------------------- | +| `primary` | `#0073C4` | Primary CTA, links, focus rings | +| `primary-hover` | `#141C36` | Hover state of primary interactive | +| `primary-inverse` | `#ffffff` | Primary on a colored background | +| `secondary` | `#6A6E75` | Secondary CTA, ghost buttons | +| `secondary-hover` | `#7F8288` | Hover state of secondary interactive | +| `interface` | `#0073C4` | UI affordances (checkboxes, toggles, sliders) | +| `interface-hover` | `#141C36` | Hover on interface elements | +| `danger` | `#B83C3C` | Destructive actions, error states | +| `danger-hover` | `#A52020` | Hover on danger interactive | + +### Border + +Percipio's border weights use a non-standard order: `primary` is mid-weight, `secondary` is very light, `tertiary` is the strongest (solid navy). Use them for their semantic intent, not their numeric rank. + +| Token | Value | Use for | +| ------------------ | -------------------- | ----------------------------------- | +| `border-primary` | `#8F919D` (navy-400) | Standard input and card borders | +| `border-secondary` | `#E2E3E6` (navy-200) | Subtle dividers, section separators | +| `border-tertiary` | `#10162F` (navy-800) | Strong structural borders | +| `border-disabled` | `#BCBEC5` (navy-300) | Disabled input borders | + +### Feedback + +| Token | Value | Use for | +| ------------------ | --------- | -------------------------------- | +| `feedback-error` | `#B83C3C` | Error messages, validation | +| `feedback-success` | `#1B8057` | Success messages, confirmations | +| `feedback-warning` | `#EF5B0D` | Warning messages, caution states | + +### Shadow + +| Token | Value | +| ------------------ | -------------------- | +| `shadow-primary` | `#E2E3E6` (navy-200) | +| `shadow-secondary` | `#8F919D` (navy-400) | + +Percipio shadows are softer than Codecademy's β€” use `shadow-primary` for standard elevated surfaces. + +--- + +## Percipio Color Palette + +Percipio introduces its own named semantic colors. These are the source values behind the aliases above. Use the semantic aliases in designs, not the raw named colors. + +| Named color | Value | Mapped to | +| ------------------------------ | --------- | ---------------------------------- | +| `percipioTextPrimary` | `#222325` | `text`, `text-accent` | +| `percipioTextSecondary` | `#595A5C` | `text-secondary` | +| `percipioTextDisabled` | `#AFB6C2` | `text-disabled` | +| `percipioActionPrimary` | `#0073C4` | `primary`, `interface` | +| `percipioActionPrimaryHover` | `#141C36` | `primary-hover`, `interface-hover` | +| `percipioActionSecondary` | `#6A6E75` | `secondary` | +| `percipioActionSecondaryHover` | `#7F8288` | `secondary-hover` | +| `percipioActionDangerHover` | `#A52020` | `danger-hover` | +| `percipioDanger` | `#B83C3C` | `danger`, `feedback-error` | +| `percipioFeedbackSuccess` | `#1B8057` | `feedback-success` | +| `percipioFeedbackWarning` | `#EF5B0D` | `feedback-warning` | +| `percipioBgPrimary` | `#FAFBFC` | `background-primary` | +| `percipioBgSuccess` | `#EEF7F3` | `background-success` | +| `percipioBgWarning` | `#FFF7E0` | `background-warning` | +| `percipioBgError` | `#FFF1F5` | `background-error` | + +The full core swatch palette (navy, blue, green, yellow, red, etc.) is also available, but the semantic aliases above are how Percipio maps them. Raw swatches should only be used for fixed colors that must not adapt (illustrations, data viz, etc.). + +--- + +## Typography + +### Typefaces + +All font families in Percipio use **Roboto**. There is no separate accent or display typeface. + +| Token | Font | Use for | +| ----------- | -------------------------- | ------------------------------------------- | +| `base` | `"Roboto", sans-serif` | All default UI text, headlines, body copy | +| `accent` | `"Roboto", sans-serif` | Labels, captions (same as base in Percipio) | +| `monospace` | `"Roboto Mono", monospace` | Code editor contexts | +| `system` | `"Roboto", sans-serif` | Performance-critical surfaces | + +### Rules + +- **Roboto Medium (500)** for headlines, sub-headlines, CTAs, and buttons β€” not Bold (700). +- **Roboto Regular (400)** for body text, UI labels, and menu items. +- Text is **left-aligned** by default. Center-align only for short marketing headlines. Never right-align. +- Do not adjust letter-spacing. + +### Font weight scale + +Percipio overrides the title weight from Core's 700 to 500 (medium). This gives Percipio a lighter, less heavy headline style. + +| Token | Value | Use | +| ------- | ------- | ---------------------------------------------------------- | +| `base` | 400 | Body text, UI labels | +| `title` | **500** | Headlines, CTAs, buttons _(differs from Codecademy's 700)_ | + +### Font size scale + +Shared with Core β€” all sizes are identical. + +| Token key | Size | Common use | +| --------- | ---- | ---------------------------- | +| `64` | 64px | Hero / display | +| `44` | 44px | Page titles | +| `34` | 34px | Section titles | +| `26` | 26px | Sub-section titles | +| `22` | 22px | Card titles, large UI labels | +| `20` | 20px | Secondary titles | +| `18` | 18px | Large body, intro text | +| `16` | 16px | Default body text | +| `14` | 14px | Small body, captions, labels | + +### Line height scale + +Shared with Core. + +| Token | Value | Use | +| ------------- | ----- | ------------------------------- | +| `base` | 1.5 | Body text | +| `spacedTitle` | 1.3 | Sub-headlines and medium titles | +| `title` | 1.2 | Large headlines | + +### Line length + +Target 45–85 characters per line; 66 characters is ideal. Max 50 for multi-column layouts. + +--- + +## Spacing Scale + +Identical to Core. All spacing is multiples of 4px on an 8px grid. + +| Token | Value | +| ----- | ----- | +| `0` | 0 | +| `4` | 4px | +| `8` | 8px | +| `12` | 12px | +| `16` | 16px | +| `24` | 24px | +| `32` | 32px | +| `40` | 40px | +| `48` | 48px | +| `64` | 64px | +| `96` | 96px | + +--- + +## Border Radius Scale + +Identical to Core. + +| Token | Value | Use | +| ------ | ----- | ------------------------------------------ | +| `none` | 0px | Square / non-interactive elements | +| `sm` | 2px | Subtle rounding, tags | +| `md` | 4px | Default buttons, inputs, interactive cards | +| `lg` | 8px | Cards, panels | +| `xl` | 16px | Large cards, modals | +| `full` | 999px | Pills, avatars, circular elements | + +--- + +## Responsive Behavior + +Identical to Core. Mobile-first, apply styles from the named breakpoint up. + +| Token | Min-width | Max content | +| -------- | --------- | ----------- | +| _(base)_ | 0 | 288px | +| `xs` | 480px | 448px | +| `sm` | 768px | 704px | +| `md` | 1024px | 896px | +| `lg` | 1200px | 1072px | +| `xl` | 1440px | 1248px | + +12-column grid at all breakpoints. + +| Usage | Recommended values | +| --------------------- | ------------------------------------------------ | +| Horizontal margins | 64px (lg+), 48px (md), 32px (sm/xs), 16px (base) | +| Column gaps (gutters) | 32px (lg+), 24px (md), 16px (sm/xs), 8px (base) | +| Row gaps | 32px (lg+), 24px (md), 16px (sm/xs), 8px (base) | + +Minimum interactive touch target: **44Γ—44px** on mobile breakpoints. + +--- + +## Component Library + +Same component library as Codecademy β€” all atoms, molecules, and organisms apply. Token values resolve differently per theme automatically. + +Key Percipio-specific visual differences: + +- `FillButton` uses `#0073C4` (blue) instead of hyper-purple +- `FillButton` hover shifts to near-black `#141C36` rather than a lighter purple +- `Checkbox` / `Toggle` use the same blue `#0073C4` +- `Card` has softer shadows (navy-200 vs navy-800 in Codecademy light mode) +- Card shadow patterns (`patternLeft`, `patternRight`) are available but rarely used in Percipio UIs + +--- + +## Do's and Don'ts + +### Colors + +- **Do** use semantic color aliases (`primary`, `text`, `background`, etc.) β€” never hardcode hex values. +- **Do** use `#0073C4` blue as the only primary interactive color. +- **Don't** use Codecademy's hyper-purple or yellow in Percipio contexts. +- **Don't** attempt dark mode β€” Percipio is light only. + +### Typography + +- **Do** use title weight (500) for headlines, CTAs, and buttons β€” not 700. +- **Do** keep body text at 150–175% line height for readability. +- **Don't** use a separate accent typeface β€” Roboto is used uniformly for base and accent. +- **Don't** right-align or center-align body paragraphs. +- **Don't** adjust letter-spacing. + +### Layout & Spacing + +- **Do** use multiples of 8px for block-element spacing (4px only for inline / typographic relationships). +- **Do** begin design work at 1440px (XL), then adapt down. +- **Do** align elements to the 12-column grid. + +--- + +## Agent Prompt Guide + +Quick color/token reference for generating or specifying Percipio UI: + +| Scenario | Tokens | +| ---------------- | ------------------------------------------------------------------------------------------------------------ | +| Primary button | `bg: primary (#0073C4)`, `color: white`, `hover: primary-hover (#141C36)` | +| Body text | `color: text (#222325)`, `font: Roboto`, `size: 16px`, `weight: 400`, `lineHeight: base (1.5)` | +| Headline | `color: text (#222325)`, `font: Roboto`, `size: 34–64px`, `weight: title (500)`, `lineHeight: title (1.2)` | +| Secondary text | `color: text-secondary (#595A5C)` | +| Disabled text | `color: text-disabled (#AFB6C2)` | +| Elevated surface | `bg: background-primary (#FAFBFC)` | +| Card default | `bg: background (#ffffff)`, `borderRadius: none` β€” add `isInteractive` for hover shadow + `borderRadius: md` | +| Error state | `color: feedback-error (#B83C3C)`, `bg: background-error (#FFF1F5)`, `border: danger` | +| Success state | `color: feedback-success (#1B8057)`, `bg: background-success (#EEF7F3)` | +| Warning state | `color: feedback-warning (#EF5B0D)`, `bg: background-warning (#FFF7E0)` | +| Disabled state | `color: text-disabled (#AFB6C2)`, `bg: background-disabled (#E2E3E6, navy-200)`, `border: border-disabled` | + +### Component token cheatsheet + +``` +FillButton β†’ bg: primary (#0073C4), color: white, hover: primary-hover (#141C36) +StrokeButton β†’ bg: transparent, border: secondary (#6A6E75) +Checkbox/Toggle β†’ interface (#0073C4), hover: interface-hover (#141C36) +Card β†’ bg: background, shadow: shadow-primary (#E2E3E6, navy-200, soft) +Alert (error) β†’ uses feedback-error + background-error +Alert (success) β†’ uses feedback-success + background-success +Alert (warning) β†’ uses feedback-warning + background-warning +``` diff --git a/packages/gamut-agent-tools/DESIGN.md b/packages/gamut-agent-tools/DESIGN.md new file mode 100644 index 00000000000..0f69aac557d --- /dev/null +++ b/packages/gamut-agent-tools/DESIGN.md @@ -0,0 +1 @@ +DEPRECATED. Use the appropriate DESIGN.\*.md from https://github.com/Codecademy/gamut/pull/3329 instead. diff --git a/packages/gamut-agent-tools/README.md b/packages/gamut-agent-tools/README.md new file mode 100644 index 00000000000..a4eb0869aaf --- /dev/null +++ b/packages/gamut-agent-tools/README.md @@ -0,0 +1,126 @@ +# @codecademy/gamut-agent-tools + +Static assets and a small **CLI** for AI-assisted development against the [Gamut](https://github.com/Codecademy/gamut) design system: Cursor/Claude plugin manifests, **skills**, **rules**, **guidelines**, **commands**, and product-specific **DESIGN** references. + +## Install + +Add it as a **development dependency** so skills, rules, and the CLI stay out of production bundles: + +```bash +yarn add -D @codecademy/gamut-agent-tools +# or +npm install --save-dev @codecademy/gamut-agent-tools +``` + +If you truly need this package in a production `dependencies` tree (unusual), use `yarn add` / `npm install` without the dev flag. + +The published binary is **`gamut-agent-tools`**. After install, run it via `yarn gamut-agent-tools`, `npx gamut-agent-tools`, or your package manager’s bin path. + +**`--help`** on the CLI prints a short summary; full detail lives in this README. + +## CLI overview + +All subcommands use the shape: + +```text +gamut-agent-tools plugin [target] [options] +``` + +The first argument after the binary must be **`plugin`**. + +### Subcommands + +| Subcommand | Purpose | +| ---------- | ------------------------------------------------------------------------------- | +| `install` | Install assets into Cursor, Claude Code, or a Figma-oriented `guidelines/` tree | +| `remove` | Remove a prior installation for the chosen target | +| `update` | Re-run `install` in place (same flags as install) | +| `list` | Show status for cursor / claude / figma | + +### Targets + +| Target | Default? | Behavior | +| -------- | ------------------------- | ------------------------------------------------------------------------------------------------------ | +| `cursor` | yes (when target omitted) | Copies scoped content into Cursor local plugins (see `.cursor-plugin/plugin.json`) | +| `claude` | | Registers and installs via `claude plugin …` using `.claude-plugin/marketplace.json` | +| `figma` | | Copies `guidelines/` from this package to a destination derived from `--output` or `figma.config.json` | + +### Scopes (Cursor only: `install` / `update`) + +| Scope | Meaning | +| ---------- | ------------------------------------------------------------------------------------------------------ | +| `all` | Default β€” install all top-level content dirs except ignored ones (e.g. `.claude-plugin`, `guidelines`) | +| `skills` | Only `skills/` | +| `rules` | Only `rules/` | +| `commands` | Only `commands/` | +| `agents` | Only `agents/` | + +### Options + +| Option | Subcommands | Meaning | +| --------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--scope ` | `install`, `update` | Cursor scope (see table above). Ignored for claude/figma where not applicable. | +| `--output ` | `install figma`, `remove figma`, `list` | **Figma:** explicit path. For install: destination **directory** for `guidelines/` (parent of the folder you want). For remove/list: path to **`DESIGN.md`** or related when not using discovery. | +| `--plugin-dir ` | all | Override the **source** directory (defaults to the root of the installed `@codecademy/gamut-agent-tools` package β€” the assets shipped with the CLI). | + +### Default plugin source + +Unless you pass `--plugin-dir`, the CLI uses the **installed package root** of `@codecademy/gamut-agent-tools` (the copy on disk next to this `bin/` tree). You do not need to resolve the package from cwd. + +### Environment + +| Variable | Effect | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `CURSOR_INSTALL_METHOD` | If set to something other than `copy`, Cursor install uses a **symlink** of the whole source tree instead of copying (dev convenience). Default behavior is `copy`. | +| `CURSOR_PLUGINS_LOCAL` | Overrides the base directory for Cursor local plugins (default: `~/.cursor/plugins/local`). | + +## Examples + +```bash +# Cursor β€” full install (default) +gamut-agent-tools plugin install + +# Cursor β€” skills only +gamut-agent-tools plugin install cursor --scope skills + +# Claude Code +gamut-agent-tools plugin install claude + +# Figma β€” explicit guidelines destination +gamut-agent-tools plugin install figma --output /path/to/your/project/guidelines + +# Figma β€” discover figma.config.json upward from cwd +gamut-agent-tools plugin install figma + +# Status +gamut-agent-tools plugin list +gamut-agent-tools plugin list --output ./docs/DESIGN.md + +# Remove / refresh +gamut-agent-tools plugin remove claude +gamut-agent-tools plugin update cursor --scope rules +``` + +### Custom or unreleased source tree + +```bash +gamut-agent-tools plugin install cursor --plugin-dir /path/to/checkout/packages/gamut-agent-tools +``` + +## Package layout + +| Path | Purpose | +| ----------------- | -------------------------------------------------------------------------------------- | +| `bin/` | `gamut-agent-tools` CLI entry (`bin/cli.mjs`) and implementation | +| `skills/` | Agent skills (e.g. theming, typography, accessibility) | +| `rules/` | Rule files (e.g. accessibility) | +| `guidelines/` | Written guidelines for foundations and components (Figma install target) | +| `commands/` | Command specs (e.g. `gamut-review`) | +| `agents/` | Placeholder for future agent definitions | +| `DESIGN*.md` | Product design references; consumers often copy one to `DESIGN.md` at the project root | +| `.cursor-plugin/` | Cursor local plugin manifest | +| `.claude-plugin/` | Claude plugin manifest and marketplace metadata | + +## Repository + +Published from the [Codecademy/gamut](https://github.com/Codecademy/gamut) monorepo. Versioning and release are handled in CI like other `@codecademy/*` packages in this repo. diff --git a/packages/gamut/agent-tools/agents/.gitkeep b/packages/gamut-agent-tools/agents/.gitkeep similarity index 100% rename from packages/gamut/agent-tools/agents/.gitkeep rename to packages/gamut-agent-tools/agents/.gitkeep diff --git a/packages/gamut-agent-tools/bin/cli.mjs b/packages/gamut-agent-tools/bin/cli.mjs new file mode 100755 index 00000000000..7b189299891 --- /dev/null +++ b/packages/gamut-agent-tools/bin/cli.mjs @@ -0,0 +1,68 @@ +#!/usr/bin/env node + +const DOCS = + 'https://github.com/Codecademy/gamut/tree/main/packages/gamut-agent-tools#readme'; + +const args = process.argv.slice(2); +const [noun, verb, ...rest] = args; + +if (!noun || noun === '--help' || noun === '-h') { + printHelp(); + process.exit(noun ? 0 : 1); +} + +if (noun !== 'plugin') { + console.error(`Unknown command: "${noun}"`); + printHelp(); + process.exit(1); +} + +if (!verb || verb === '--help' || verb === '-h') { + printPluginHelp(); + process.exit(verb ? 0 : 1); +} + +let cmd; +try { + cmd = await import(`./commands/plugin/${verb}.mjs`); +} catch { + console.error(`Unknown plugin subcommand: "${verb}"`); + printPluginHelp(); + process.exit(1); +} + +if (rest.includes('--help') || rest.includes('-h')) { + cmd.help(); + process.exit(0); +} + +try { + await cmd.default(rest); +} catch (/** @type {any} */ err) { + console.error(`Error: ${err.message}`); + process.exit(1); +} + +function printHelp() { + console.log(` +gamut-agent-tools β€” install Gamut agent assets into Cursor, Claude Code, or Figma workflows + +Usage: + gamut-agent-tools plugin [options] + +Documentation (commands, targets, scopes, migration from \`gamut plugin\`): + ${DOCS} +`); +} + +function printPluginHelp() { + console.log(` +gamut-agent-tools plugin + +Subcommands: + install [target] remove [target] update [target] list + +Documentation: + ${DOCS} +`); +} diff --git a/packages/gamut/bin/commands/plugin/install.mjs b/packages/gamut-agent-tools/bin/commands/plugin/install.mjs similarity index 66% rename from packages/gamut/bin/commands/plugin/install.mjs rename to packages/gamut-agent-tools/bin/commands/plugin/install.mjs index 8aa27b4517c..eff3711527c 100644 --- a/packages/gamut/bin/commands/plugin/install.mjs +++ b/packages/gamut-agent-tools/bin/commands/plugin/install.mjs @@ -12,30 +12,10 @@ export const SCOPES = ['all', 'skills', 'rules', 'commands', 'agents']; export function help() { console.log(` -Usage: - gamut plugin install [target] [options] - -Install the Gamut plugin into an AI or design tool. - -Arguments: - target Tool to install into (default: cursor) - cursor | claude | figma - -Options: - --scope Content to install (default: all) - all | skills | rules | commands | agents - --output [figma] Explicit destination directory for guidelines/. - If omitted, walks up from cwd to find figma.config.json. - --plugin-dir Override the bundled agent-tools directory - -h, --help Show this help message - -Examples: - gamut plugin install - gamut plugin install claude - gamut plugin install figma - gamut plugin install figma --output /path/to/project/guidelines - gamut plugin install cursor --scope skills - gamut plugin install cursor --plugin-dir ./my-agent-tools +Usage: gamut-agent-tools plugin install [cursor|claude|figma] [options] + +Full reference (targets, scopes, figma --output, --plugin-dir, examples): + https://github.com/Codecademy/gamut/tree/main/packages/gamut-agent-tools#readme `); } @@ -44,7 +24,7 @@ Examples: /** Directories in the plugin source that should not be installed to Cursor. */ const CURSOR_IGNORE = new Set([ '.claude-plugin', // Claude Code manifest β€” not a Cursor concept - 'guidelines', // Figma Make only + 'guidelines', // Figma Make only ]); /** @param {string} sourceRoot @param {string} scope */ @@ -63,20 +43,29 @@ async function installCursor(sourceRoot, scope) { await rm(dest, { recursive: true, force: true }); await mkdir(dest, { recursive: true }); - await cp(`${sourceRoot}/.cursor-plugin`, `${dest}/.cursor-plugin`, { recursive: true }); + await cp(`${sourceRoot}/.cursor-plugin`, `${dest}/.cursor-plugin`, { + recursive: true, + }); let dirs; if (scope === 'all') { const entries = await readdir(sourceRoot, { withFileTypes: true }); dirs = entries - .filter((e) => e.isDirectory() && !e.name.startsWith('.') && !CURSOR_IGNORE.has(e.name)) + .filter( + (e) => + e.isDirectory() && + !e.name.startsWith('.') && + !CURSOR_IGNORE.has(e.name) + ) .map((e) => e.name); } else { dirs = [scope]; } for (const dir of dirs) { - await cp(`${sourceRoot}/${dir}`, `${dest}/${dir}`, { recursive: true }).catch(() => { + await cp(`${sourceRoot}/${dir}`, `${dest}/${dir}`, { + recursive: true, + }).catch(() => { // directory may be empty/missing β€” not an error }); } @@ -95,31 +84,51 @@ async function installClaude(sourceRoot) { const mpName = marketplaceName(spec); const root = resolve(sourceRoot); - let code = await runCommand('claude', ['plugin', 'marketplace', 'add', root, '--scope', 'user']); + let code = await runCommand('claude', [ + 'plugin', + 'marketplace', + 'add', + root, + '--scope', + 'user', + ]); if (code !== 0) { console.warn( `warning: "claude plugin marketplace add" exited ${code} β€” ` + - `if it's already registered this is safe to ignore.`, + `if it's already registered this is safe to ignore.` ); - code = await runCommand('claude', ['plugin', 'marketplace', 'update', mpName]); + code = await runCommand('claude', [ + 'plugin', + 'marketplace', + 'update', + mpName, + ]); if (code !== 0) { throw new Error( `claude plugin marketplace add/update failed (exit ${code}).\n` + - `Try manually: claude plugin marketplace add ${root}`, + `Try manually: claude plugin marketplace add ${root}` ); } } - code = await runCommand('claude', ['plugin', 'install', spec, '--scope', 'user']); + code = await runCommand('claude', [ + 'plugin', + 'install', + spec, + '--scope', + 'user', + ]); if (code !== 0) { throw new Error( `claude plugin install failed (exit ${code}).\n` + - `Try manually: claude plugin install ${spec} --scope user`, + `Try manually: claude plugin install ${spec} --scope user` ); } console.log(`Claude Code: installed ${spec} (user scope)`); - console.log(` Tip: run /reload-plugins in Claude Code if skills don't appear immediately.`); + console.log( + ` Tip: run /reload-plugins in Claude Code if skills don't appear immediately.` + ); console.log(` One-off without install: claude --plugin-dir ${root}`); } @@ -138,14 +147,15 @@ async function installFigma(sourceRoot, outputArg) { await rm(dest, { recursive: true, force: true }); await cp(src, dest, { recursive: true }); console.log(`Figma: installed guidelines/ β†’ ${dest}`); - console.log(` In Figma Make, point your kit at this guidelines/ directory for design system context.`); + console.log( + ` In Figma Make, point your kit at this guidelines/ directory for design system context.` + ); } // --------------------------------------------------------------------------- /** - * gamut plugin install [cursor|claude|figma] [--scope all|skills|rules|commands|agents] - * [--plugin-dir ] + * gamut-agent-tools plugin install [cursor|claude|figma] [--scope …] [--plugin-dir …] * * @param {string[]} args */ @@ -154,10 +164,14 @@ export default async function install(args) { const scope = getFlag(args, '--scope', 'all') ?? 'all'; if (!TARGETS.includes(target)) { - throw new Error(`Unknown target: "${target}". Choose from: ${TARGETS.join(', ')}`); + throw new Error( + `Unknown target: "${target}". Choose from: ${TARGETS.join(', ')}` + ); } if (!SCOPES.includes(scope)) { - throw new Error(`Unknown scope: "${scope}". Choose from: ${SCOPES.join(', ')}`); + throw new Error( + `Unknown scope: "${scope}". Choose from: ${SCOPES.join(', ')}` + ); } const pluginDir = await resolvePluginDir(args); diff --git a/packages/gamut/bin/commands/plugin/list.mjs b/packages/gamut-agent-tools/bin/commands/plugin/list.mjs similarity index 71% rename from packages/gamut/bin/commands/plugin/list.mjs rename to packages/gamut-agent-tools/bin/commands/plugin/list.mjs index 89b019a6498..03768cd58b0 100644 --- a/packages/gamut/bin/commands/plugin/list.mjs +++ b/packages/gamut-agent-tools/bin/commands/plugin/list.mjs @@ -6,20 +6,9 @@ import { getFlag, resolvePluginDir } from '../../lib/resolve-plugin-dir.mjs'; export function help() { console.log(` -Usage: - gamut plugin list [options] +Usage: gamut-agent-tools plugin list [options] -Show installation status for all supported targets. - -Options: - --output [figma] Explicit path to DESIGN.md. - If omitted, walks up from cwd to find figma.config.json. - --plugin-dir Override the bundled agent-tools directory - -h, --help Show this help message - -Examples: - gamut plugin list - gamut plugin list --output ./docs/DESIGN.md +Full reference: https://github.com/Codecademy/gamut/tree/main/packages/gamut-agent-tools#readme `); } @@ -32,7 +21,7 @@ async function cursorStatus(sourceRoot) { return { target: 'cursor', status: installed ? 'βœ“ installed' : 'βœ— not installed', - notes: installed ? dest : 'run: gamut plugin install cursor', + notes: installed ? dest : 'run: gamut-agent-tools plugin install cursor', }; } @@ -59,7 +48,8 @@ async function figmaStatus(outputArg) { return { target: 'figma', status: '? unknown', - notes: 'figma.config.json not found β€” run from your project root or use --output', + notes: + 'figma.config.json not found β€” run from your project root or use --output', }; } @@ -67,14 +57,14 @@ async function figmaStatus(outputArg) { return { target: 'figma', status: installed ? 'βœ“ installed' : 'βœ— not installed', - notes: installed ? dest : `run: gamut plugin install figma`, + notes: installed ? dest : `run: gamut-agent-tools plugin install figma`, }; } // --------------------------------------------------------------------------- /** - * gamut plugin list + * gamut-agent-tools plugin list * * Shows installation status for each supported target. * @@ -93,13 +83,17 @@ export default async function list(args) { const col0 = Math.max(...rows.map((r) => r.target.length)); const col1 = Math.max(...rows.map((r) => r.status.length)); - const header = `${'Target'.padEnd(col0)} ${'Status'.padEnd(col1)} Path / Notes`; + const header = `${'Target'.padEnd(col0)} ${'Status'.padEnd( + col1 + )} Path / Notes`; const rule = '─'.repeat(header.length); console.log(`\n${header}`); console.log(rule); for (const row of rows) { - console.log(`${row.target.padEnd(col0)} ${row.status.padEnd(col1)} ${row.notes}`); + console.log( + `${row.target.padEnd(col0)} ${row.status.padEnd(col1)} ${row.notes}` + ); } console.log(); } diff --git a/packages/gamut/bin/commands/plugin/remove.mjs b/packages/gamut-agent-tools/bin/commands/plugin/remove.mjs similarity index 70% rename from packages/gamut/bin/commands/plugin/remove.mjs rename to packages/gamut-agent-tools/bin/commands/plugin/remove.mjs index 285fc2f27cb..58fa2155deb 100644 --- a/packages/gamut/bin/commands/plugin/remove.mjs +++ b/packages/gamut-agent-tools/bin/commands/plugin/remove.mjs @@ -9,26 +9,9 @@ import { TARGETS } from './install.mjs'; export function help() { console.log(` -Usage: - gamut plugin remove [target] [options] - -Remove the installed Gamut plugin from an AI or design tool. - -Arguments: - target Tool to remove from (default: cursor) - cursor | claude | figma - -Options: - --output [figma] Path to the DESIGN.md that was installed. - If omitted, walks up from cwd to find figma.config.json. - --plugin-dir Override the bundled agent-tools directory - -h, --help Show this help message - -Examples: - gamut plugin remove - gamut plugin remove claude - gamut plugin remove figma - gamut plugin remove figma --output ./docs/DESIGN.md +Usage: gamut-agent-tools plugin remove [cursor|claude|figma] [options] + +Full reference: https://github.com/Codecademy/gamut/tree/main/packages/gamut-agent-tools#readme `); } @@ -54,21 +37,32 @@ async function removeClaude(sourceRoot) { const mpName = marketplaceName(spec); const pluginName = spec.split('@')[0]; - let code = await runCommand('claude', ['plugin', 'remove', pluginName, '--scope', 'user']); + let code = await runCommand('claude', [ + 'plugin', + 'remove', + pluginName, + '--scope', + 'user', + ]); if (code !== 0) { console.warn( - `warning: "claude plugin remove" exited ${code} β€” the plugin may not have been installed.`, + `warning: "claude plugin remove" exited ${code} β€” the plugin may not have been installed.` ); } else { console.log(`Claude Code: removed plugin "${pluginName}"`); } - code = await runCommand('claude', ['plugin', 'marketplace', 'remove', mpName]); + code = await runCommand('claude', [ + 'plugin', + 'marketplace', + 'remove', + mpName, + ]); if (code !== 0) { console.warn( `warning: "claude plugin marketplace remove" exited ${code} β€” ` + `the marketplace entry may not exist or the command syntax may differ. ` + - `Run "claude plugin marketplace list" to check.`, + `Run "claude plugin marketplace list" to check.` ); } else { console.log(`Claude Code: removed marketplace "${mpName}"`); @@ -92,7 +86,7 @@ async function removeFigma(outputArg) { // --------------------------------------------------------------------------- /** - * gamut plugin remove [cursor|claude|figma] [--plugin-dir ] + * gamut-agent-tools plugin remove [cursor|claude|figma] [--plugin-dir ] * * @param {string[]} args */ @@ -100,7 +94,9 @@ export default async function remove(args) { const target = args.find((a) => !a.startsWith('-')) ?? 'cursor'; if (!TARGETS.includes(target)) { - throw new Error(`Unknown target: "${target}". Choose from: ${TARGETS.join(', ')}`); + throw new Error( + `Unknown target: "${target}". Choose from: ${TARGETS.join(', ')}` + ); } const pluginDir = await resolvePluginDir(args); diff --git a/packages/gamut-agent-tools/bin/commands/plugin/update.mjs b/packages/gamut-agent-tools/bin/commands/plugin/update.mjs new file mode 100644 index 00000000000..6bb86c69988 --- /dev/null +++ b/packages/gamut-agent-tools/bin/commands/plugin/update.mjs @@ -0,0 +1,37 @@ +import { getFlag } from '../../lib/resolve-plugin-dir.mjs'; +import install, { TARGETS } from './install.mjs'; + +export function help() { + console.log(` +Usage: gamut-agent-tools plugin update [cursor|claude|figma] [options] + +Full reference: https://github.com/Codecademy/gamut/tree/main/packages/gamut-agent-tools#readme +`); +} + +/** + * gamut-agent-tools plugin update [cursor|claude|figma] [--scope …] [--plugin-dir …] + * + * Re-runs install with the same arguments. For Cursor this does an in-place + * copy replacing any existing installation. For Claude Code it updates the + * marketplace entry and re-installs. + * + * @param {string[]} args + */ +export default async function update(args) { + const target = args.find((a) => !a.startsWith('-')) ?? 'cursor'; + const scope = getFlag(args, '--scope', 'all') ?? 'all'; + + if (!TARGETS.includes(target)) { + throw new Error( + `Unknown target: "${target}". Choose from: ${TARGETS.join(', ')}` + ); + } + + console.log( + `Updating Gamut plugin for ${target}${ + scope !== 'all' ? ` (scope: ${scope})` : '' + }…` + ); + await install(args); +} diff --git a/packages/gamut/bin/lib/claude.mjs b/packages/gamut-agent-tools/bin/lib/claude.mjs similarity index 72% rename from packages/gamut/bin/lib/claude.mjs rename to packages/gamut-agent-tools/bin/lib/claude.mjs index 9db787de8a8..17b071998b2 100644 --- a/packages/gamut/bin/lib/claude.mjs +++ b/packages/gamut-agent-tools/bin/lib/claude.mjs @@ -15,7 +15,7 @@ export async function claudePluginSpec(sourceRoot) { } catch { throw new Error( `Missing ${mp}.\n` + - `A .claude-plugin/marketplace.json is required for Claude Code installation.`, + `A .claude-plugin/marketplace.json is required for Claude Code installation.` ); } @@ -26,14 +26,20 @@ export async function claudePluginSpec(sourceRoot) { const { name: marketplaceName, plugins } = json; if (!marketplaceName || !Array.isArray(plugins) || plugins.length === 0) { - throw new Error(`Invalid marketplace.json β€” needs "name" and "plugins[]": ${mp}`); + throw new Error( + `Invalid marketplace.json β€” needs "name" and "plugins[]": ${mp}` + ); } const entry = - plugins.find((p) => p.source === './' || p.source === '.' || p.source == null) ?? plugins[0]; + plugins.find( + (p) => p.source === './' || p.source === '.' || p.source == null + ) ?? plugins[0]; if (!entry?.name) { - throw new Error(`No plugin name found in marketplace.json plugins[]: ${mp}`); + throw new Error( + `No plugin name found in marketplace.json plugins[]: ${mp}` + ); } return `${entry.name}@${marketplaceName}`; @@ -47,6 +53,9 @@ export async function claudePluginSpec(sourceRoot) { */ export function marketplaceName(spec) { const name = spec.split('@')[1]; - if (!name) throw new Error(`Could not parse marketplace name from plugin spec: ${spec}`); + if (!name) + throw new Error( + `Could not parse marketplace name from plugin spec: ${spec}` + ); return name; } diff --git a/packages/gamut/bin/lib/cursor.mjs b/packages/gamut-agent-tools/bin/lib/cursor.mjs similarity index 91% rename from packages/gamut/bin/lib/cursor.mjs rename to packages/gamut-agent-tools/bin/lib/cursor.mjs index 321551cab2d..10527c2be56 100644 --- a/packages/gamut/bin/lib/cursor.mjs +++ b/packages/gamut-agent-tools/bin/lib/cursor.mjs @@ -4,7 +4,10 @@ import { join } from 'node:path'; /** @returns {string} */ export function cursorPluginsRoot() { - return process.env.CURSOR_PLUGINS_LOCAL ?? join(homedir(), '.cursor', 'plugins', 'local'); + return ( + process.env.CURSOR_PLUGINS_LOCAL ?? + join(homedir(), '.cursor', 'plugins', 'local') + ); } /** diff --git a/packages/gamut/bin/lib/figma.mjs b/packages/gamut-agent-tools/bin/lib/figma.mjs similarity index 94% rename from packages/gamut/bin/lib/figma.mjs rename to packages/gamut-agent-tools/bin/lib/figma.mjs index 7415423582e..8b7f6aac773 100644 --- a/packages/gamut/bin/lib/figma.mjs +++ b/packages/gamut-agent-tools/bin/lib/figma.mjs @@ -44,6 +44,6 @@ export async function resolveFigmaOutput(outputArg) { throw new Error( `Could not find figma.config.json in ${process.cwd()} or any parent directory.\n` + `Provide the destination explicitly with --output:\n` + - ` gamut plugin install figma --output /path/to/your/project/guidelines`, + ` gamut-agent-tools plugin install figma --output /path/to/your/project/guidelines` ); } diff --git a/packages/gamut-agent-tools/bin/lib/resolve-plugin-dir.mjs b/packages/gamut-agent-tools/bin/lib/resolve-plugin-dir.mjs new file mode 100644 index 00000000000..7fb81723010 --- /dev/null +++ b/packages/gamut-agent-tools/bin/lib/resolve-plugin-dir.mjs @@ -0,0 +1,58 @@ +import { existsSync } from 'node:fs'; +import { stat } from 'node:fs/promises'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +/** + * Package root for @codecademy/gamut-agent-tools (this file lives in bin/lib/). + * + * @returns {string} + */ +function bundledPackageRoot() { + return dirname(dirname(dirname(fileURLToPath(import.meta.url)))); +} + +/** + * Returns the absolute path to the @codecademy/gamut-agent-tools package + * root (the installed assets directory), or the value of --plugin-dir if provided. + * + * @param {string[]} args + * @returns {Promise} + */ +export async function resolvePluginDir(args) { + const override = getFlag(args, '--plugin-dir'); + if (override) { + const abs = resolve(override); + const st = await stat(abs).catch(() => null); + if (!st?.isDirectory()) { + throw new Error(`--plugin-dir path not found: ${abs}`); + } + return abs; + } + + const root = bundledPackageRoot(); + const pkgJson = join(root, 'package.json'); + if (!existsSync(pkgJson)) { + throw new Error( + 'Could not locate @codecademy/gamut-agent-tools package root. Pass --plugin-dir to your agent-tools directory.' + ); + } + + const st = await stat(root).catch(() => null); + if (!st?.isDirectory()) { + throw new Error(`Invalid package root: ${root}`); + } + + return root; +} + +/** + * @param {string[]} argv + * @param {string} flag + * @param {string} [fallback] + * @returns {string | undefined} + */ +export function getFlag(argv, flag, fallback) { + const idx = argv.indexOf(flag); + return idx !== -1 ? argv[idx + 1] : fallback; +} diff --git a/packages/gamut/bin/lib/run-command.mjs b/packages/gamut-agent-tools/bin/lib/run-command.mjs similarity index 100% rename from packages/gamut/bin/lib/run-command.mjs rename to packages/gamut-agent-tools/bin/lib/run-command.mjs diff --git a/packages/gamut-agent-tools/commands/gamut-review.md b/packages/gamut-agent-tools/commands/gamut-review.md new file mode 100644 index 00000000000..a84bf7e9438 --- /dev/null +++ b/packages/gamut-agent-tools/commands/gamut-review.md @@ -0,0 +1,171 @@ +--- +description: Use this command when auditing existing code for Gamut usage β€” dependencies, GamutProvider, deep imports, hardcoded hex colors, and test patterns β€” and you need a consolidated report with pointers to the matching Gamut skills. +argument-hint: [path] +allowed-tools: Read Glob Grep +--- + +This is an audit of **existing code** at **`$ARGUMENTS`** (default: current working directory). Your job is to find violations and misuse, not to generate new code. + +Use `DESIGN.md` at the project root as the authoritative reference for the product's design intent, token names, and component patterns. This file is distributed as `DESIGN.Codecademy.md` or `DESIGN.Percipio.md` from the `@codecademy/gamut-agent-tools` package and renamed to `DESIGN.md` by the consuming project. When a finding maps to a skill, note it in the report so the developer knows where to get remediation guidance. + +Run all five checks below, then print a single consolidated report using the format at the end of this file. + +--- + +## Check 1 β€” Dependencies + +Read `package.json` (and `package.json` in `$ARGUMENTS` if a path was given). Inspect `dependencies`, `devDependencies`, and `peerDependencies` combined. + +| Package | Expectation | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `@codecademy/gamut` | **Required** β€” core component library | +| `@codecademy/gamut-agent-tools` | Optional β€” agent skills, rules, and DESIGN variants for AI tooling (`gamut-agent-tools plugin install`); independent of `@codecademy/gamut` | +| `@codecademy/gamut-styles` | Recommended β€” design tokens and theme primitives | +| `@codecademy/variance` | Recommended β€” style-prop system used by Gamut internals | + +--- + +## Check 2 β€” Setup + +Search source files (`.ts`, `.tsx`, `.js`, `.jsx`) for these symbols. Skip `node_modules`, `dist`, `.next`, `build`, `.turbo`. + +| Symbol | Expectation | +| --------------- | ------------------------------------------------------------------- | +| `GamutProvider` | **Required** β€” must appear at least once (app root wrapper) | +| `ColorMode` | Recommended β€” enables semantic light/dark theming | +| `Background` | Recommended β€” semantic surface color via `@codecademy/gamut-styles` | + +For each found symbol report the first file path where it appears. + +--- + +## Check 3 β€” Import patterns + +Grep source files for any of these patterns. Each match is an error. + +| Pattern | Reason | +| ------------------------------- | ----------------------------------------------------------------------- | +| `@codecademy/gamut/dist/` | Deep dist import β€” bypasses public API and breaks on internal refactors | +| `@codecademy/gamut/src/` | Deep src import β€” not part of the published package | +| `@codecademy/gamut-styles/src/` | Deep src import β€” use the package root | +| `@codecademy/variance/src/` | Deep src import β€” use the package root | + +Report each violation as `file:line`. + +--- + +## Check 4 β€” Hardcoded colors + +Grep source files (`.ts`, `.tsx`, `.js`, `.jsx`, `.css`, `.scss`, `.less`) for inline hex color literals (`#RGB` or `#RRGGBB`). For each match, suggest the Gamut token name using this table (case-insensitive comparison): + +| Hex | Token | +| --------- | -------------------------- | +| `#000000` | `black` | +| `#ffffff` | `white` | +| `#10162f` | `navy` / `navy-800` | +| `#0a0d1c` | `navy-900` | +| `#fff0e5` | `beige-100` | +| `#f5fcff` | `blue-0` | +| `#d3f2ff` | `blue-100` | +| `#66c4ff` | `blue-300` | +| `#3388ff` | `blue-400` | +| `#1557ff` | `blue-500` | +| `#1d2340` | `blue-800` | +| `#f5ffe3` | `green-0` | +| `#eafdc6` | `green-100` | +| `#aee938` | `green-400` / `lightGreen` | +| `#008a27` | `green-700` | +| `#151c07` | `green-900` | +| `#fffae5` | `yellow-0` | +| `#cca900` | `yellow-400` | +| `#ffd300` | `yellow-500` / `yellow` | +| `#211b00` | `yellow-900` | +| `#fff5ff` | `pink-0` | +| `#f966ff` | `pink-400` / `pink` | +| `#fbf1f0` | `red-0` | +| `#e85d7f` | `red-300` | +| `#dc5879` | `red-400` / `paleRed` | +| `#e91c11` | `red-500` / `red` | +| `#be1809` | `red-600` | +| `#280503` | `red-900` | +| `#ffe8cc` | `orange-100` | +| `#ff8c00` | `orange-500` / `orange` | +| `#5533ff` | `hyper-400` | +| `#3a10e5` | `hyper-500` / `hyper` | +| `#f5f5f5` | `gray-100` | +| `#eeeeee` | `gray-200` | +| `#e0e0e0` | `gray-300` | +| `#9e9e9e` | `gray-600` | +| `#616161` | `gray-800` | +| `#424242` | `gray-900` | +| `#fffbf8` | `beige-0` | +| `#8a7300` | `gold-800` / `gold` | +| `#d14900` | `orange-800` | +| `#ca00d1` | `pink-800` | +| `#006d82` | `teal-500` / `teal` | +| `#b3ccff` | `purple-300` / `purple` | + +Ignore hex values inside design token definition files themselves (e.g. `variables/colors.ts`, `_colors.scss`) β€” those are the source of truth, not violations. + +For each match outside token files report: `file:line 'HEX' β†’ token` (or `β†’ no Gamut token` if unknown). + +--- + +## Check 5 β€” Test setup + +Grep test files (`**/__tests__/**/*.{ts,tsx}`, `**/*.test.{ts,tsx}`, `**/*.spec.{ts,tsx}`) for these patterns. Skip `node_modules`, `dist`. + +| Pattern | Verdict | Reason | +| ----------------------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `jest.mock\(.*@codecademy/gamut` | **Error** | Manual mocking bypasses theme context and produces false-positive tests; use `setupRtl` or `MockGamutProvider` instead | +| `jest.mock\(.*@codecademy/gamut-styles` | **Error** | Same issue as above β€” mocking gamut-styles breaks token resolution | +| `from '@codecademy/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 `@codecademy/gamut-tests`, not directly from `component-test-setup` β€” the gamut-tests wrapper adds `MockGamutProvider` automatically | +| `new GamutProvider` or ` +══════════════════════════════════════════════════ + +Dependencies + βœ“ @codecademy/gamut + ⚠ @codecademy/gamut-styles not found β€” recommended + βœ— @codecademy/variance not found β€” recommended + +Setup + βœ“ GamutProvider found (src/App.tsx) + ⚠ ColorMode not found β€” use ColorMode for light/dark theming [β†’ gamut-color-mode] + ⚠ Background not found β€” use for semantic surfaces [β†’ gamut-color-mode] + +Import patterns + βœ“ Deep dist imports none found + βœ— Deep src imports 2 occurrences + src/Thing.tsx:7 + src/Other.tsx:12 + +Hardcoded colors [β†’ gamut-color-mode] + ⚠ src/Hero.tsx:14 '#1557FF' β†’ blue-500 + ⚠ src/Nav.tsx:8 '#BADA55' β†’ no Gamut token + +Test setup [β†’ gamut-testing] + βœ“ @codecademy/gamut-tests used in 12 test files + βœ— jest.mock(@codecademy/gamut) 2 occurrences β€” remove and use setupRtl instead + src/components/Foo/__tests__/Foo.test.tsx:3 + src/components/Bar/__tests__/Bar.test.tsx:5 + ⚠ direct component-test-setup import 1 occurrence β€” import from @codecademy/gamut-tests + src/components/Baz/__tests__/Baz.test.tsx:2 + +══════════════════════════════════════════════════ + error(s), warning(s) found. (or "All checks passed." if none) +``` + +Icons: `βœ“` = pass, `⚠` = warning (recommended, not required), `βœ—` = error (required). +`[β†’ skill-name]` annotations indicate which Gamut skill has remediation guidance for that category. + +After printing the report, offer one sentence of prioritized next-step advice based on what was found. diff --git a/packages/gamut-agent-tools/guidelines/components/buttons.md b/packages/gamut-agent-tools/guidelines/components/buttons.md new file mode 100644 index 00000000000..5a3c9d4dfc2 --- /dev/null +++ b/packages/gamut-agent-tools/guidelines/components/buttons.md @@ -0,0 +1,44 @@ +# Buttons + +## Variants + +| Component | Use for | Background | Text | +| --------------------- | ------------------------------------ | ----------------- | ----------- | +| `FillButton` | Primary CTA, high-emphasis actions | `primary` | `white` | +| `StrokeButton` | Secondary actions, outlined style | transparent | `secondary` | +| `CTAButton` | High-visibility marketing promotions | `primary` | `white` | +| `CTAButton` (inverse) | CTA on a colored surface | `primary-inverse` | `secondary` | +| `TextButton` | Low-emphasis, inline text actions | transparent | `primary` | +| `IconButton` | Compact icon-only actions | β€” | β€” | + +## Sizes + +`small` | `normal` (default) | `large` + +## Key props + +| Prop | Type | Effect | +| -------------- | -------------------------------- | ------------------------------ | +| `size` | `"small" \| "normal" \| "large"` | Controls padding and font size | +| `icon` | Icon component | Leading or trailing icon | +| `iconPosition` | `"left" \| "right"` | Defaults to left | +| `disabled` | boolean | Disabled state styling | +| `href` | string | Renders as `` tag | + +## States + +`default` β†’ `hover` (`primary-hover` / `secondary-hover`) β†’ `active` β†’ `disabled` (`text-disabled` + `background-disabled`) + +## Token cheatsheet + +``` +FillButton β†’ bg: primary (#3A10E5 light / #FFD300 dark), color: white, hover: primary-hover +StrokeButton β†’ bg: transparent, border: secondary (#10162F / #fff), hover: secondary-hover +TextButton β†’ bg: transparent, color: primary, hover: primary-hover +``` + +## Rules + +- Use `FillButton` for primary actions and `StrokeButton` for secondary β€” do not use both at equal weight. +- Reserve `CTAButton` for marketing / high-visibility promotions; do not use it for standard UI actions. +- Every interactive `Card` wrapped in `` should have `isInteractive` β€” not a button inside. diff --git a/packages/gamut/agent-tools/guidelines/components/overview.md b/packages/gamut-agent-tools/guidelines/components/overview.md similarity index 80% rename from packages/gamut/agent-tools/guidelines/components/overview.md rename to packages/gamut-agent-tools/guidelines/components/overview.md index be2230563c2..99bfbf4fcfb 100644 --- a/packages/gamut/agent-tools/guidelines/components/overview.md +++ b/packages/gamut-agent-tools/guidelines/components/overview.md @@ -17,28 +17,32 @@ ContentContainer, GridContainer, Layout, LayoutGrid ## Key patterns ### Buttons + See [buttons.md](buttons.md) for full reference. Use `FillButton` for primary actions, `StrokeButton` for secondary. ### Cards + - **Background variants**: `default` (ColorMode-responsive), `white`, `yellow`, `beige`, `navy`, `hyper` - **Shadow variants**: `none` (default), `outline`, `patternLeft`, `patternRight` - Add `isInteractive` when wrapping in `` β€” enables hover shadow + `borderRadius: md` - Default `borderRadius` is `none`; override with `borderRadius` prop ### Color-aware components + - `` β€” scopes a subtree to an explicit color mode - `` β€” applies background color + auto-switches inner color mode for contrast ### Alerts -| Variant | Tokens | -|---|---| -| Error | `feedback-error` + `background-error` | + +| Variant | Tokens | +| ------- | ----------------------------------------- | +| Error | `feedback-error` + `background-error` | | Success | `feedback-success` + `background-success` | | Warning | `feedback-warning` + `background-warning` | ## Global tokens -| Token | Value | Use | -|---|---|---| -| `headerHeight` | 64px (base), 80px (md+) | Global page header height | -| `headerZ` | 15 | Z-index for global page header | +| Token | Value | Use | +| -------------- | ----------------------- | ------------------------------ | +| `headerHeight` | 64px (base), 80px (md+) | Global page header height | +| `headerZ` | 15 | Z-index for global page header | diff --git a/packages/gamut-agent-tools/guidelines/foundations/color.md b/packages/gamut-agent-tools/guidelines/foundations/color.md new file mode 100644 index 00000000000..ee7807aeaa9 --- /dev/null +++ b/packages/gamut-agent-tools/guidelines/foundations/color.md @@ -0,0 +1,86 @@ +# Color + +Use **semantic aliases** for all UI that adapts to color mode or theme. Use raw palette tokens only when a color must stay fixed regardless of mode. + +## Semantic aliases + +### Text + +| Token | Light | Dark | Use for | +| ---------------- | ------------ | --------- | --------------------------- | +| `text` | `#10162F` | `#ffffff` | Default body and UI text | +| `text-accent` | `#0A0D1C` | `#FFF0E5` | Stronger emphasis text | +| `text-secondary` | navy-800 75% | white 65% | Supporting / secondary copy | +| `text-disabled` | navy-800 63% | white 50% | Disabled state labels | + +### Background + +| Token | Light | Dark | Use for | +| --------------------- | ------------ | --------- | --------------------------------- | +| `background` | `#ffffff` | `#10162F` | Default page/component background | +| `background-primary` | `#FFF0E5` | `#0A0D1C` | Slightly elevated surfaces | +| `background-contrast` | white | black | Maximum contrast surface | +| `background-selected` | navy-800 4% | white 4% | Selected row / item | +| `background-hover` | navy-800 12% | white 9% | Hover state overlay | +| `background-disabled` | navy-800 12% | white 9% | Disabled surface | +| `background-success` | `#F5FFE3` | `#151C07` | Success state container | +| `background-warning` | `#FFFAE5` | `#211B00` | Warning state container | +| `background-error` | `#FBF1F0` | `#280503` | Error state container | + +### Interactive + +| Token | Light | Dark | Use for | +| ----------------- | ------------ | --------- | --------------------------------- | +| `primary` | `#3A10E5` | `#FFD300` | Primary CTA, links, focus rings | +| `primary-hover` | `#5533FF` | `#CCA900` | Hover on primary interactive | +| `primary-inverse` | `#FFD300` | `#3A10E5` | Primary on a colored background | +| `secondary` | `#10162F` | `#ffffff` | Secondary CTA, ghost buttons | +| `secondary-hover` | navy-800 86% | white 80% | Hover on secondary interactive | +| `interface` | `#3A10E5` | `#FFD300` | Checkboxes, toggles, sliders | +| `interface-hover` | `#5533FF` | `#CCA900` | Hover on interface elements | +| `danger` | `#E91C11` | `#E85D7F` | Destructive actions, error states | +| `danger-hover` | `#BE1809` | `#DC5879` | Hover on danger interactive | + +### Border + +| Token | Light | Dark | Use for | +| ------------------ | ------------ | --------- | -------------------------- | +| `border-primary` | `#10162F` | `#ffffff` | Strong borders, dividers | +| `border-secondary` | navy-800 75% | white 65% | Medium-weight borders | +| `border-tertiary` | navy-800 28% | white 20% | Subtle borders, separators | +| `border-disabled` | navy-800 63% | white 50% | Disabled input borders | + +### Feedback + +| Token | Light | Dark | Use for | +| ------------------ | --------- | --------- | -------------------------- | +| `feedback-error` | `#BE1809` | `#E85D7F` | Error messages, validation | +| `feedback-success` | `#008A27` | `#AEE938` | Success messages | +| `feedback-warning` | `#FFD300` | `#FFFAE5` | Warning messages | + +## Raw palette + +Use raw tokens only when color must be **fixed** (not adaptive). + +| Name | Weights | Key values | +| -------- | -------------------------- | -------------------------------- | +| `navy` | 100–900 | 800 = `#10162F`, 900 = `#0A0D1C` | +| `hyper` | 400, 500 | 500 = `#3A10E5`, 400 = `#5533FF` | +| `yellow` | 0, 400, 500, 900 | 500 = `#FFD300` | +| `red` | 0, 300, 400, 500, 600, 900 | 500 = `#E91C11` | +| `green` | 0, 100, 400, 700, 900 | 700 = `#008A27` | +| `blue` | 0, 100, 300, 400, 500, 800 | 500 = `#1557FF` | +| `beige` | β€” | `#FFF0E5` | +| `pink` | 0, 400 | 400 = `#F966FF` | +| `orange` | 100, 500 | 500 = `#FF8C00` | + +Named shorthand aliases: `beige`, `blue`, `green`, `hyper`, `navy`, `orange`, `pink`, `red`, `yellow`, `black`, `white` + +## Decision guide + +``` +Coloring UI text or backgrounds? + └─ Needs to adapt to light/dark or theme? β†’ use semantic alias (text, background, primary, …) + └─ Must be fixed regardless of mode? β†’ use raw token (navy-800, yellow-500, …) + └─ Setting a section background with content inside? β†’ use (see modes.md) +``` diff --git a/packages/gamut/agent-tools/guidelines/foundations/modes.md b/packages/gamut-agent-tools/guidelines/foundations/modes.md similarity index 73% rename from packages/gamut/agent-tools/guidelines/foundations/modes.md rename to packages/gamut-agent-tools/guidelines/foundations/modes.md index c39db9880e2..99861b4acf8 100644 --- a/packages/gamut/agent-tools/guidelines/foundations/modes.md +++ b/packages/gamut-agent-tools/guidelines/foundations/modes.md @@ -23,18 +23,18 @@ Use `` β€” not a raw `bg` prop β€” whenever setting a colored backgr ```tsx import { Background } from '@codecademy/gamut-styles'; -{children} +{children}; ``` Nesting is supported β€” each `` creates its own accessible color context. ## Hooks -| Hook | Returns | Use | -|---|---|---| -| `useCurrentMode()` | `"light" \| "dark"` | Read active mode in JS | -| `useColorMode()` | `[modeKey, modeColors, allModes]` | Access all mode data | -| `usePrefersDarkMode()` | `boolean` | Read OS dark preference only | +| Hook | Returns | Use | +| ---------------------- | --------------------------------- | ---------------------------- | +| `useCurrentMode()` | `"light" \| "dark"` | Read active mode in JS | +| `useColorMode()` | `[modeKey, modeColors, allModes]` | Access all mode data | +| `usePrefersDarkMode()` | `boolean` | Read OS dark preference only | Import from `@codecademy/gamut-styles`. diff --git a/packages/gamut-agent-tools/guidelines/foundations/spacing.md b/packages/gamut-agent-tools/guidelines/foundations/spacing.md new file mode 100644 index 00000000000..af6933ced7c --- /dev/null +++ b/packages/gamut-agent-tools/guidelines/foundations/spacing.md @@ -0,0 +1,66 @@ +# Spacing, Border Radius & Layout + +## Spacing scale + +All spacing is multiples of 4px on an 8px grid. + +| Token | Value | +| ----- | ----- | +| `0` | 0 | +| `4` | 4px | +| `8` | 8px | +| `12` | 12px | +| `16` | 16px | +| `24` | 24px | +| `32` | 32px | +| `40` | 40px | +| `48` | 48px | +| `64` | 64px | +| `96` | 96px | + +Use multiples of 8px for block-element spacing. Use 4px only for inline or typographic relationships. + +## Border radius + +| Token | Value | Use | +| ------ | ----- | ---------------------------------- | +| `none` | 0px | Square / non-interactive elements | +| `sm` | 2px | Subtle rounding, tags | +| `md` | 4px | Buttons, inputs, interactive cards | +| `lg` | 8px | Cards, panels | +| `xl` | 16px | Large cards, modals | +| `full` | 999px | Pills, avatars, circular elements | + +## Breakpoints + +Mobile-first. Styles apply from the named breakpoint and up. + +| Token | Min-width | Max content width | +| -------- | --------- | ----------------- | +| _(base)_ | 0 | 288px | +| `xs` | 480px | 448px | +| `sm` | 768px | 704px | +| `md` | 1024px | 896px | +| `lg` | 1200px | 1072px | +| `xl` | 1440px | 1248px | + +Container query variants (`c_xs`–`c_xl`) mirror these values but trigger on component width. + +## Grid + +12-column grid at all breakpoints. + +| Property | xl/lg | md | sm/xs | base | +| ------------------ | ----- | ---- | ----- | ---- | +| Horizontal margins | 64px | 48px | 32px | 16px | +| Column gutters | 32px | 24px | 16px | 8px | +| Row gaps | 32px | 24px | 16px | 8px | + +Minimum touch target on mobile: **44Γ—44px**. + +## Responsive rules + +- Begin design work at 1440px (XL), then adapt down. +- Multi-column layouts collapse to fewer columns β€” do not stretch or squish. +- Catalog cards and non-lockup elements should align on one axis (usually left), not fill column widths. +- Avoid dense or small components at the base (mobile) breakpoint. diff --git a/packages/gamut-agent-tools/guidelines/foundations/typography.md b/packages/gamut-agent-tools/guidelines/foundations/typography.md new file mode 100644 index 00000000000..faaa347b1cc --- /dev/null +++ b/packages/gamut-agent-tools/guidelines/foundations/typography.md @@ -0,0 +1,50 @@ +# Typography + +## Typefaces + +| Token | Codecademy font | Non-Codecademy | Use for | +| ----------- | ------------------------- | ------------------------- | ----------------------------------------- | +| `base` | Apercu Pro | Hanken Grotesk | All UI text, headlines, body copy | +| `accent` | Suisse Intl Mono | Hanken Grotesk | Code, captions, labels, technical context | +| `monospace` | Monaco / Menlo / Consolas | Monaco / Menlo / Consolas | Code editor contexts | + +Percipio overrides all families to Roboto. Apercu is licensed for codecademy.com only. + +## Font size scale + +| Token | Size | Common use | +| ----- | ---- | ---------------------------- | +| `64` | 64px | Hero / display | +| `44` | 44px | Page titles | +| `34` | 34px | Section titles | +| `26` | 26px | Sub-section titles | +| `22` | 22px | Card titles, large UI labels | +| `20` | 20px | Secondary titles | +| `18` | 18px | Large body, intro text | +| `16` | 16px | Default body text | +| `14` | 14px | Small body, captions, labels | + +## Font weight + +| Token | Value | Use | +| ------- | ----- | ------------------------ | +| `base` | 400 | Body text, UI labels | +| `title` | 700 | Headlines, CTAs, buttons | + +## Line height + +| Token | Value | Use | +| ------------- | ----- | ---------------------------- | +| `base` | 1.5 | Body text | +| `spacedTitle` | 1.3 | Sub-headlines, medium titles | +| `title` | 1.2 | Large headlines | + +Target 45–85 characters per line (66 ideal for web body text). + +## Rules + +- Use `title` weight (700) for headlines, CTAs, and buttons. +- Use Apercu Italic to emphasize within a Regular paragraph β€” not Bold. +- Use `accent` (Suisse) sparingly: code snippets, captions, enumerated items. Suisse reads ~10–15% large β€” size it down relative to Apercu equivalents. +- Left-align text by default. Center-align only for short marketing headlines. Never right-align. +- Do not adjust letter-spacing. diff --git a/packages/gamut-agent-tools/guidelines/overview.md b/packages/gamut-agent-tools/guidelines/overview.md new file mode 100644 index 00000000000..3077c0033fe --- /dev/null +++ b/packages/gamut-agent-tools/guidelines/overview.md @@ -0,0 +1,36 @@ +# Gamut Design System + +Gamut is the Codecademy / Skillsoft design system β€” React component library (`@codecademy/gamut`), design tokens (`@codecademy/gamut-styles`), and Figma components with live code previews via Figma Code Connect. + +**Design voice**: "Ruled by logic, but creative and a bit unexpected." Structured and trustworthy for a learning platform, with engaging personality. Medium density β€” information-rich layouts with strong typographic hierarchy. Never cramped or overly airy. + +**Core principles**: + +- Components are color mode–aware by default β€” never hardcode hex values for adaptive UI +- All components work across all themes without modification +- Mobile-first, 12-column grid +- Semantic color tokens guarantee WCAG AA contrast automatically + +## Themes + +| Theme | Product | Base font | Dark mode | +| ------------- | ------------------------------- | -------------- | --------- | +| **Core** | Codecademy (default) | Apercu | βœ“ | +| **Admin** | Codecademy admin tools | Apercu | βœ“ | +| **Platform** | Codecademy learning environment | Apercu | βœ“ | +| **LX Studio** | LX Studio application | Hanken Grotesk | β€” | +| **Percipio** | Skillsoft Percipio | Roboto | β€” | + +Set the theme at the app root via ``. + +## Reading order + +| File | What it covers | +| ------------------------------------------------------ | --------------------------------------------- | +| [setup.md](setup.md) | Packages, GamutProvider, theme selection | +| [foundations/color.md](foundations/color.md) | Semantic aliases, raw palette, decision guide | +| [foundations/modes.md](foundations/modes.md) | Light/dark ColorMode, Background component | +| [foundations/typography.md](foundations/typography.md) | Typefaces, font scale, rules | +| [foundations/spacing.md](foundations/spacing.md) | Spacing, border radius, responsive grid | +| [components/overview.md](components/overview.md) | Full component catalog | +| [components/buttons.md](components/buttons.md) | Button variants, props, decision tree | diff --git a/packages/gamut/agent-tools/guidelines/setup.md b/packages/gamut-agent-tools/guidelines/setup.md similarity index 62% rename from packages/gamut/agent-tools/guidelines/setup.md rename to packages/gamut-agent-tools/guidelines/setup.md index 645631ebd4f..6cd3942688d 100644 --- a/packages/gamut/agent-tools/guidelines/setup.md +++ b/packages/gamut-agent-tools/guidelines/setup.md @@ -17,26 +17,25 @@ import { GamutProvider } from '@codecademy/gamut'; import { theme } from '@codecademy/gamut-styles'; const App = () => ( - - {/* app content */} - + {/* app content */} ); ``` ## Theme selection -| Product | Theme to import | -|---|---| -| Codecademy public | `coreTheme` (default `theme`) | -| Codecademy admin | `adminTheme` | -| Codecademy platform | `platformTheme` | -| LX Studio | `lxStudioTheme` | -| Percipio | `percipioTheme` | +| Product | Theme to import | +| ------------------- | ----------------------------- | +| Codecademy public | `coreTheme` (default `theme`) | +| Codecademy admin | `adminTheme` | +| Codecademy platform | `platformTheme` | +| LX Studio | `lxStudioTheme` | +| Percipio | `percipioTheme` | All themes are exported from `@codecademy/gamut-styles`. ## Font licensing **Apercu Pro** is licensed for codecademy.com only. Non-Codecademy products must use their theme's approved typeface: + - LX Studio β†’ Hanken Grotesk - Percipio β†’ Roboto diff --git a/packages/gamut-agent-tools/package.json b/packages/gamut-agent-tools/package.json new file mode 100644 index 00000000000..88fb6bab79d --- /dev/null +++ b/packages/gamut-agent-tools/package.json @@ -0,0 +1,39 @@ +{ + "name": "@codecademy/gamut-agent-tools", + "description": "Skills, rules, and guidelines for AI-assisted development against the Gamut design system", + "version": "1.0.0", + "author": "Codecademy Engineering ", + "bin": "./bin/cli.mjs", + "files": [ + "agents", + "bin", + "commands", + "guidelines", + "rules", + "skills", + "README.md", + "DESIGN.md", + "DESIGN.Codecademy.md", + "DESIGN.LXStudio.md", + "DESIGN.Percipio.md", + ".cursor-plugin", + ".claude-plugin" + ], + "keywords": [ + "agent-skills", + "claude", + "codecademy", + "cursor", + "design-system", + "gamut" + ], + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "repository": "Codecademy/gamut.git", + "scripts": { + "verify": "nx run gamut-agent-tools:verify" + }, + "type": "module" +} diff --git a/packages/gamut-agent-tools/project.json b/packages/gamut-agent-tools/project.json new file mode 100644 index 00000000000..cd3ee7fdb60 --- /dev/null +++ b/packages/gamut-agent-tools/project.json @@ -0,0 +1,26 @@ +{ + "name": "gamut-agent-tools", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/gamut-agent-tools", + "projectType": "library", + "tags": [], + "targets": { + "verify": { + "executor": "nx:run-commands", + "options": { + "cwd": "{projectRoot}", + "commands": [ + "../../node_modules/.bin/tsc --project tsconfig.bin.json --noEmit" + ], + "parallel": false + } + }, + "publish-build": { + "executor": "nx:run-commands", + "options": { + "commands": [], + "parallel": false + } + } + } +} diff --git a/packages/gamut/agent-tools/rules/accessibility.mdc b/packages/gamut-agent-tools/rules/accessibility.mdc similarity index 100% rename from packages/gamut/agent-tools/rules/accessibility.mdc rename to packages/gamut-agent-tools/rules/accessibility.mdc diff --git a/packages/gamut/agent-tools/skills/gamut-accessibility/SKILL.md b/packages/gamut-agent-tools/skills/gamut-accessibility/SKILL.md similarity index 84% rename from packages/gamut/agent-tools/skills/gamut-accessibility/SKILL.md rename to packages/gamut-agent-tools/skills/gamut-accessibility/SKILL.md index 068fb91d9a3..67f0988f121 100644 --- a/packages/gamut/agent-tools/skills/gamut-accessibility/SKILL.md +++ b/packages/gamut-agent-tools/skills/gamut-accessibility/SKILL.md @@ -25,7 +25,6 @@ ARIA roles modify the accessibility tree and _imply_ behavior. Always ensure tha ARIA can augment native semantics (`aria-pressed` on a `