Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**: `<section className={section({ bg, padding })}>` wrapping `<Container>`. `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.<rp|se|sc|cx|sy|ai>` 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.<rp|se|sc|cx|sy|ai>` 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<K, ColorToken>` (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.
Expand Down
6 changes: 3 additions & 3 deletions app/components/design-system/flip-card-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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',
Expand All @@ -47,7 +47,7 @@ export function FlipCardSection() {
<p className={caption}>
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).
</p>
<div className={grid({ columns: { base: 1, md: 2 }, gap: '8' })}>
<FlipCard
Expand Down
32 changes: 16 additions & 16 deletions app/components/design-system/v2-tokens-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,42 +51,42 @@ const greys = [
{ name: 'line', className: css({ bg: 'line' }) },
];

const enjeux = [
const challenges = [
{
key: 'rp',
label: 'Revenue Performance',
accent: css({ bg: 'enjeu.rp' }),
tint: css({ bg: 'enjeu.rp.light' }),
accent: css({ bg: 'challenge.rp' }),
tint: css({ bg: 'challenge.rp.light' }),
},
{
key: 'se',
label: 'Sales Experience',
accent: css({ bg: 'enjeu.se' }),
tint: css({ bg: 'enjeu.se.light' }),
accent: css({ bg: 'challenge.se' }),
tint: css({ bg: 'challenge.se.light' }),
},
{
key: 'sc',
label: 'Sales Compensation',
accent: css({ bg: 'enjeu.sc' }),
tint: css({ bg: 'enjeu.sc.light' }),
accent: css({ bg: 'challenge.sc' }),
tint: css({ bg: 'challenge.sc.light' }),
},
{
key: 'cx',
label: 'Customer Experience',
accent: css({ bg: 'enjeu.cx' }),
tint: css({ bg: 'enjeu.cx.light' }),
accent: css({ bg: 'challenge.cx' }),
tint: css({ bg: 'challenge.cx.light' }),
},
{
key: 'sy',
label: 'Revenue Systems',
accent: css({ bg: 'enjeu.sy' }),
tint: css({ bg: 'enjeu.sy.light' }),
accent: css({ bg: 'challenge.sy' }),
tint: css({ bg: 'challenge.sy.light' }),
},
{
key: 'ai',
label: 'Revenue AI',
accent: css({ bg: 'enjeu.ai' }),
tint: css({ bg: 'enjeu.ai.light' }),
accent: css({ bg: 'challenge.ai' }),
tint: css({ bg: 'challenge.ai.light' }),
},
];

Expand Down Expand Up @@ -159,16 +159,16 @@ export function V2TokensSection() {
</ul>
</ShowcaseSection>

<ShowcaseSection title="Enjeu colours">
<ShowcaseSection title="Challenge colours">
<ul className={grid({ columns: { base: 2, md: 6 }, gap: '4' })}>
{enjeux.map((item) => (
{challenges.map((item) => (
<li key={item.key} className={tile}>
<span className={css({ display: 'flex', gap: '2' })}>
<span className={cx(swatch, item.accent)} />
<span className={cx(swatch, item.tint)} />
</span>
<span className={caption}>
enjeu.{item.key} · {item.label}
challenge.{item.key} · {item.label}
</span>
</li>
))}
Expand Down
11 changes: 6 additions & 5 deletions app/components/navbar-v2/navbar-bar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -119,14 +119,15 @@ describe('NavbarBar', () => {
];
render(<NavbarBar scrolled={false} leadingItems={leadingItems} />);
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');
Expand Down
2 changes: 1 addition & 1 deletion app/components/navbar-v2/navbar-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down
2 changes: 1 addition & 1 deletion app/components/navbar-v2/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
};

Expand Down
5 changes: 3 additions & 2 deletions app/modules/bowtie-stages.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
8 changes: 4 additions & 4 deletions docs/adr/0002-design-system-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,17 @@ 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

The mockup writes its coral to yellow gradient inline 75 times, in 14 variants. Two tokens remain: `gradients.ai` for backgrounds and decoration, and `gradients.ai-text` for gradient text. Every other variant (angles, transparent tints, conic spinner, radial halo) snaps to one of them. Only AI content uses them; review checks this, since no tool can tell what a page is about. We rejected a single token, which leaves gradient text unreadable on light backgrounds, and one token per effect, which widens the scale for decoration.

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.<key>` and `enjeu.<key>.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.<key>` and `challenge.<key>.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

Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion docs/development/component-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
2 changes: 1 addition & 1 deletion docs/development/j1-route-review-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) | | |
Expand Down
4 changes: 2 additions & 2 deletions preset/semantic-tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}' },
Expand Down
2 changes: 1 addition & 1 deletion preset/slot-recipes/flip-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' } },
Expand Down
Loading