From 915d3eceb5478aaffcccf9c8098ea74b656ca2fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?je=CC=81ro=CC=82me=20boileux?= Date: Wed, 21 Oct 2026 17:52:46 +0200 Subject: [PATCH] refactor(tokens): rename enjeu colour tokens to challenge Rename the enjeu.* semantic colour tokens and their .light tints to challenge.*, with their call sites, comments, the navbar v2 test and the docs. French UI strings and the French route segment are unchanged. Closes #262 --- CLAUDE.md | 2 +- .../design-system/flip-card-section.tsx | 6 ++-- .../design-system/v2-tokens-section.tsx | 32 +++++++++---------- app/components/navbar-v2/navbar-bar.test.tsx | 11 ++++--- app/components/navbar-v2/navbar-bar.tsx | 2 +- app/components/navbar-v2/navbar.tsx | 2 +- app/modules/bowtie-stages.ts | 5 +-- docs/adr/0002-design-system-v2.md | 8 ++--- docs/development/component-inventory.md | 2 +- docs/development/j1-route-review-checklist.md | 2 +- preset/semantic-tokens.ts | 4 +-- preset/slot-recipes/flip-card.ts | 2 +- 12 files changed, 40 insertions(+), 38 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c037fb87..044faad4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -114,7 +114,7 @@ The preset carries the v2 values from the redesign mockup ([ADR 0002](docs/adr/0 - **Typography**: use `text({ variant: ... })`. Never `fontFamily: 'display'` inline outside `preset/`. Six kept variants, one per v2 size: `display-xl` (Display, Bermia 900, 44 to 72px), `display-lg` (H2, Bermia 900, 34 to 48px), `display-md-bold` (H3, Bornia 700, 24px), `body` (Bornia 400, 17px), `small` (Bornia 500, 14px), `label` (11px uppercase, .3em). The other `display-*`, `subtitle` and `display-label` names are aliases, deleted in J6: do not add call sites. - **Sectioning**: `
` wrapping ``. `padding: 'lg'` (default) is 112px from 1024px, 80px from 768px, 64px below; `md` is the 80px compact spacing. Never inline `maxW: '7xl'` + `mx: 'auto'` containers. - **Cards**: use `card()` (variants `shadow: none|sm|card|elevated`, `padding: sm|md|lg|xl`, `radius: md|lg`). Default is 28px padding, `line` border, `rest` shadow, 16px radius. Never inline `rounded + border + shadow`. -- **Tokens**: radii `lg` (8px), `2xl` (16px), `3xl` (24px), `full`. Shadows `rest`, `hover`, `window`; `offset` is the one named exception. Greys are zinc: `gray` is the muted text, `line` the hairline border. Enjeu colours are `enjeu.` with `.light`. `gradients.ai` and `gradients.ai-text` are for AI content only, gradient text at display and H2 sizes only. A value off the scale snaps to the nearest token; a new need gets a named token, never a raw value. +- **Tokens**: radii `lg` (8px), `2xl` (16px), `3xl` (24px), `full`. Shadows `rest`, `hover`, `window`; `offset` is the one named exception. Greys are zinc: `gray` is the muted text, `line` the hairline border. Challenge colours are `challenge.` with `.light`. `gradients.ai` and `gradients.ai-text` are for AI content only, gradient text at display and H2 sizes only. A value off the scale snaps to the nearest token; a new need gets a named token, never a raw value. - **Strict tokens**: `strictTokens` is on in [panda.config.ts](panda.config.ts), and the CI `typecheck` job enforces it. A style value must be a token name written as a string (`mt: '8'`, never `mt: 8`; `lineHeight: 'heading'`, never `1.05`). Dynamic values need token types: type a colour map as `Record` (from `@ocobo/styled-system/tokens`), or use `as const`. - **Escape hatch**: `'[value]'` (for example `top: '[50%]'`) is allowed only when no token fits. That covers geometry with no scale: percentage positions, odd illustration dimensions, ratios like `16/7`, em-relative prose spacing, dot-pattern gradients. A design value (colour, radius, shadow, font size, spacing, letter spacing, line height, duration up to 500ms, easing) snaps to the nearest token instead. Properties whose token category is empty in the preset (`borderWidth`, `opacity`, `zIndex`, `transform`, `gridTemplateColumns`) take raw CSS without brackets, until a token is added there. - **Legacy recipes deleted**: `icon`, `link`, `subtitle` no longer exist. Old `icon({ size })` maps to token `css({ h, w })`. The text link becomes a `button` variant (ADR 0002). Reintroducing a single-concern recipe needs a new ADR. diff --git a/app/components/design-system/flip-card-section.tsx b/app/components/design-system/flip-card-section.tsx index 75cb25cd..c40c8485 100644 --- a/app/components/design-system/flip-card-section.tsx +++ b/app/components/design-system/flip-card-section.tsx @@ -14,7 +14,7 @@ const pill = css({ px: '4', py: '1.5', borderRadius: 'full', - bg: 'enjeu.se', + bg: 'challenge.se', }); const backLabel = cx(text({ variant: 'label' }), css({ color: 'gray.300' })); const steps = css({ @@ -25,7 +25,7 @@ const steps = css({ flex: '1', fontWeight: 'semibold', }); -const stepNumber = css({ color: 'enjeu.se', mr: '2' }); +const stepNumber = css({ color: 'challenge.se', mr: '2' }); const footerLink = css({ mt: '4', fontWeight: 'semibold', @@ -47,7 +47,7 @@ export function FlipCardSection() {

Flips with its button (Enter or Space), a tap on the card, or a mouse hover. The hidden face is inert. Fixed height: size md (tool cards) or - lg (home enjeu cards). + lg (home challenge cards).

- +
    - {enjeux.map((item) => ( + {challenges.map((item) => (
  • - enjeu.{item.key} · {item.label} + challenge.{item.key} · {item.label}
  • ))} diff --git a/app/components/navbar-v2/navbar-bar.test.tsx b/app/components/navbar-v2/navbar-bar.test.tsx index 1513da92..48a9ec97 100644 --- a/app/components/navbar-v2/navbar-bar.test.tsx +++ b/app/components/navbar-v2/navbar-bar.test.tsx @@ -99,11 +99,11 @@ describe('NavbarBar', () => { expect(method).toHaveAttribute('aria-expanded', 'false'); }); - it('renders the enjeu slot before the production menu', async () => { + it('renders the challenge slot before the production menu', async () => { const user = userEvent.setup(); const leadingItems: NavItem[] = [ { - key: 'enjeux', + key: 'challenges', label: 'Vos enjeux', path: null, dropdown: [ @@ -119,14 +119,15 @@ describe('NavbarBar', () => { ]; render(); const nav = mainNav(); - const enjeux = within(nav).getByRole('button', { name: 'Vos enjeux' }); + const challenges = within(nav).getByRole('button', { name: 'Vos enjeux' }); const offer = within(nav).getByRole('link', { name: 'Notre Offre' }); expect( - enjeux.compareDocumentPosition(offer) & Node.DOCUMENT_POSITION_FOLLOWING, + challenges.compareDocumentPosition(offer) & + Node.DOCUMENT_POSITION_FOLLOWING, ).toBeTruthy(); - await user.click(enjeux); + await user.click(challenges); expect( within(nav).getByRole('link', { name: 'Revenue Process' }), ).toHaveAttribute('href', '/fr/enjeux/rp'); diff --git a/app/components/navbar-v2/navbar-bar.tsx b/app/components/navbar-v2/navbar-bar.tsx index 3fc32874..d432d294 100644 --- a/app/components/navbar-v2/navbar-bar.tsx +++ b/app/components/navbar-v2/navbar-bar.tsx @@ -26,7 +26,7 @@ type NavbarBarProps = { */ layout?: NavbarLayout; /** - * Entries rendered before the production menu. Reserved for the enjeu + * Entries rendered before the production menu. Reserved for the challenge * entry (#237), wired when v2 is switched on (#242). */ leadingItems?: NavItem[]; diff --git a/app/components/navbar-v2/navbar.tsx b/app/components/navbar-v2/navbar.tsx index 59b0d780..06d5a53c 100644 --- a/app/components/navbar-v2/navbar.tsx +++ b/app/components/navbar-v2/navbar.tsx @@ -6,7 +6,7 @@ import { NavbarBar } from './navbar-bar'; import { useScrolled } from './use-scrolled'; type NavbarV2Props = { - /** Enjeu entry slot (#237), see `NavbarBar`. */ + /** Challenge entry slot (#237), see `NavbarBar`. */ leadingItems?: NavItem[]; }; diff --git a/app/modules/bowtie-stages.ts b/app/modules/bowtie-stages.ts index 8e77200a..17bcc048 100644 --- a/app/modules/bowtie-stages.ts +++ b/app/modules/bowtie-stages.ts @@ -1,11 +1,12 @@ /** * Bow tie stage model: the seven stages of the revenue cycle, shared by the - * method page, the enjeu pages and the enjeu schema (`bowtie.activeStages`). + * method page, the challenge pages and the challenge schema + * (`bowtie.activeStages`). */ /** * Frozen list, in bow tie order. The posts repo keeps a copy to validate - * enjeu files: a change needs a PR in both repos (ocobo-revops/posts#94). + * challenge files: a change needs a PR in both repos (ocobo-revops/posts#94). */ export const STAGE_IDS = [ 'demand-generation', diff --git a/docs/adr/0002-design-system-v2.md b/docs/adr/0002-design-system-v2.md index 1e1bed0d..7da0e242 100644 --- a/docs/adr/0002-design-system-v2.md +++ b/docs/adr/0002-design-system-v2.md @@ -63,7 +63,7 @@ The mockup eyebrow (`.ds-eyebrow`) and tag (`.ds-tag`) are the same uppercase te ### One flip card recipe; `PersonCard` and `MemberCard` coexist -A `flipCard` slot recipe carries the flip mechanism and its accessibility once, for the home enjeu cards and the person cards alike. `PersonCard` is its `person` variant: photo on the front, contribution on the back. It serves the redesigned pages (team, enjeu pages, about-us). `MemberCard` stays on the blog, stories and job pages, which keep their style, and takes the v2 tokens. We rejected replacing `MemberCard` everywhere, which would restyle the blog and stories, and a separate person recipe, which would copy the flip mechanism. +A `flipCard` slot recipe carries the flip mechanism and its accessibility once, for the home challenge cards and the person cards alike. `PersonCard` is its `person` variant: photo on the front, contribution on the back. It serves the redesigned pages (team, challenge pages, about-us). `MemberCard` stays on the blog, stories and job pages, which keep their style, and takes the v2 tokens. We rejected replacing `MemberCard` everywhere, which would restyle the blog and stories, and a separate person recipe, which would copy the flip mechanism. ### The AI gradient is two tokens, reserved for AI topics @@ -71,9 +71,9 @@ The mockup writes its coral to yellow gradient inline 75 times, in 14 variants. Neither the mockup gradient nor its darkened text version reaches 3:1 on white at either end (2.0 to 2.8 for coral, 1.5 to 2.2 for yellow). `gradients.ai-text` is therefore darkened until each end reaches 3:1 on white (#F16C4E at 3.00:1, #B88F00 at 3.01:1, from #FE9C87 and #F1CF25), and gradient text is allowed only at display and H2 sizes. On dark backgrounds `gradients.ai` passes as it is. We rejected keeping the mockup values, which breaks the accessibility rule below, and dropping gradient text on light backgrounds, which removes a recurring brand element. -### Enjeu colours mark identity; coral and yellow keep fixed roles +### Challenge colours mark identity; coral and yellow keep fixed roles -Each enjeu has one colour, defined once in the website as a semantic token with its light tint: Sales Experience mint, Customer Experience sky, Revenue Performance coral, Sales Compensation yellow, Revenue Systems grey (shade to be confirmed by Ocobo), Revenue AI the AI gradient. It marks identity only: pills, workstream card backs, expert labels, the bow tie. Symptom bullets stay coral and the diagnostic block stays yellow on every enjeu page, as the mockup does on five pages out of six: coral stands for the problem, yellow for Ocobo's call to action. Revenue AI uses coral, the start of its gradient, wherever a solid colour is needed (pills, rings, tints). The tokens are `enjeu.` and `enjeu..light`, keyed like the posts frontmatter: `rp`, `se`, `sc`, `cx`, `sy`, `ai`. Until Ocobo confirms the Revenue Systems shade, `enjeu.sy` is the mockup's #767676 on the `gray.light` tint. We rejected colouring every element by enjeu, which would make the Revenue Systems diagnostic grey and would need a solid stand-in for the gradient everywhere. +Each challenge (French: enjeu) has one colour, defined once in the website as a semantic token with its light tint: Sales Experience mint, Customer Experience sky, Revenue Performance coral, Sales Compensation yellow, Revenue Systems grey (shade to be confirmed by Ocobo), Revenue AI the AI gradient. It marks identity only: pills, workstream card backs, expert labels, the bow tie. Symptom bullets stay coral and the diagnostic block stays yellow on every challenge page, as the mockup does on five pages out of six: coral stands for the problem, yellow for Ocobo's call to action. Revenue AI uses coral, the start of its gradient, wherever a solid colour is needed (pills, rings, tints). The tokens are `challenge.` and `challenge..light`, keyed like the posts frontmatter: `rp`, `se`, `sc`, `cx`, `sy`, `ai`. Until Ocobo confirms the Revenue Systems shade, `challenge.sy` is the mockup's #767676 on the `gray.light` tint. We rejected colouring every element by challenge, which would make the Revenue Systems diagnostic grey and would need a solid stand-in for the gradient everywhere. ### Raw values are refused at type check @@ -142,7 +142,7 @@ New tokens without a v2 role in the existing names: `fontSizes.caption` (13px) a ## Consequences - J1 is one change to `preset/` that every page sees: v2 values for the `text()` variants, `textStyles`, `section()`, `card()`, radii, shadows and the `gray` scale, plus the six kept variant names. -- New tokens: `colors.line`, `gray.200`, `gray.300`, `gray.700`, `gradients.ai`, `gradients.ai-text`, three shadows, enjeu semantic tokens with their light tints, font sizes 24, 17, 13, 11 and 10px, letter spacings .3em, .16em and .08em, and the `cubic-bezier(.2,.8,.2,1)` easing. +- New tokens: `colors.line`, `gray.200`, `gray.300`, `gray.700`, `gradients.ai`, `gradients.ai-text`, three shadows, challenge semantic tokens with their light tints, font sizes 24, 17, 13, 11 and 10px, letter spacings .3em, .16em and .08em, and the `cubic-bezier(.2,.8,.2,1)` easing. - Recipe changes: `button` gains `secondary` and `link`; `badge` gains an `ai` variant and an `xs` size; a new `flipCard` slot recipe with a `person` variant; a new `Eyebrow` component. - `strictTokens` is on and `pnpm typecheck` is a required CI check from J1. The about 240 raw values in `app/` are cleaned up in the same step. - J1 needs a written route checklist (French and English, desktop and mobile) for the manual review on the Vercel preview. diff --git a/docs/development/component-inventory.md b/docs/development/component-inventory.md index eda8146c..311da0f4 100644 --- a/docs/development/component-inventory.md +++ b/docs/development/component-inventory.md @@ -270,7 +270,7 @@ Defined in `preset/tokens/` and `preset/semantic-tokens.ts`, shown on `/design-s | Group | Tokens to use | |-------|---------------| | Greys | `gray` (#71717A, muted text), `gray.light`, `gray.50` to `gray.700` (zinc), `line` (#ECECEE hairline) | -| Enjeu colours | `enjeu.rp`, `enjeu.se`, `enjeu.sc`, `enjeu.cx`, `enjeu.sy`, `enjeu.ai`, each with `.light` tint | +| Challenge colours | `challenge.rp`, `challenge.se`, `challenge.sc`, `challenge.cx`, `challenge.sy`, `challenge.ai`, each with `.light` tint | | Gradients | `gradients.ai` (decoration, `bgGradient`), `gradients.ai-text` (`textGradient`, display and H2 only). AI content only | | Radii | `lg` (S 8px), `2xl` (M 16px), `3xl` (L 24px), `full` | | Shadows | `rest`, `hover`, `window`; `offset` is the one named exception | diff --git a/docs/development/j1-route-review-checklist.md b/docs/development/j1-route-review-checklist.md index b1ce076a..7ef733b9 100644 --- a/docs/development/j1-route-review-checklist.md +++ b/docs/development/j1-route-review-checklist.md @@ -138,7 +138,7 @@ Keep their style; not locale-prefixed, so FR views only. | Change | Views checked | Status | |---|---|---| -| New v2 tokens block: type scale, greys, enjeu colours, AI gradients, radii, shadows, font sizes, letter spacings, easing | | | +| New v2 tokens block: type scale, greys, challenge colours, AI gradients, radii, shadows, font sizes, letter spacings, easing | | | | `gradients.ai-text` title readable on white | | | | Coral border sample now renders (#217) | | | | Form section: the Select label now targets the Select trigger (#217) | | | diff --git a/preset/semantic-tokens.ts b/preset/semantic-tokens.ts index 8749dad8..c0ce89d6 100644 --- a/preset/semantic-tokens.ts +++ b/preset/semantic-tokens.ts @@ -102,10 +102,10 @@ export const semanticTokens = defineSemanticTokens({ _dark: '{colors.gray}', }, }, - // One colour per enjeu (ADR 0002), keyed like the posts frontmatter. + // One colour per challenge (ADR 0002), keyed like the posts frontmatter. // DEFAULT is the accent, light the tinted background. Revenue AI also // uses `gradients.ai` for decoration. - enjeu: { + challenge: { rp: { DEFAULT: { value: '{colors.coral}' }, light: { value: '{colors.coral.light}' }, diff --git a/preset/slot-recipes/flip-card.ts b/preset/slot-recipes/flip-card.ts index 69762836..dd6f8001 100644 --- a/preset/slot-recipes/flip-card.ts +++ b/preset/slot-recipes/flip-card.ts @@ -88,7 +88,7 @@ export const flipCard = defineSlotRecipe({ }, }, variants: { - // Fixed heights: `md` for tool cards, `lg` for the home enjeu cards. + // Fixed heights: `md` for tool cards, `lg` for the home challenge cards. size: { md: { root: { h: '72' } }, lg: { root: { h: 'lg' } },