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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ The `docs/` directory contains **what** applies to this project:

- `docs/development.md` - Coding agreements: function visibility and using `drupal_helpers`
- `docs/content-types.md` - Adding custom node bundles and the theme layer they need
- `docs/brand-colours.md` - The brand palette, where it lives and how to change it
- `docs/automated-lists.md` - Automated list component and its pagination
- `docs/testing.md` - Testing conventions and agreements
- `docs/ci.md` - CI provider and configuration
Expand Down
86 changes: 43 additions & 43 deletions config/default/drevops.settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,53 +72,53 @@ components:
use_media_name: 1
colors:
use_color_selector: 1
use_brand_colors: 1
use_brand_colors: 0
brand:
light:
brand1: '#42748a'
brand2: '#f2f5f8'
brand3: '#d16313'
brand1: '#1e7582'
brand2: '#eff6ff'
brand3: '#1e7582'
dark:
brand1: '#90cfeb'
brand2: '#2b3f53'
brand3: '#d16314'
brand1: '#96e7f4'
brand2: '#152235'
brand3: '#96e7f4'
palette:
light:
background: '#f2f5f8'
border: '#606263'
highlight: '#d16313'
heading: '#1a2e37'
body: '#3d4548'
background_light: '#fdfefe'
background_dark: '#c1c4c6'
border_light: '#b5b7ba'
border_dark: '#181818'
interaction_text: '#fcfdfd'
interaction_background: '#42748a'
interaction_hover_text: '#fcfdfd'
interaction_hover_background: '#274552'
interaction_focus: '#ff6b6b'
information: '#007ebd'
warning: '#c95100'
error: '#ce3936'
success: '#008482'
background: '#eff6ff'
border: '#8fa0b8'
highlight: '#1e7582'
heading: '#152235'
body: '#2b394d'
background_light: '#ffffff'
background_dark: '#c8d9f3'
border_light: '#c8d9f3'
border_dark: '#425166'
interaction_text: '#ffffff'
interaction_background: '#1e7582'
interaction_hover_text: '#ffffff'
interaction_hover_background: '#045a65'
interaction_focus: '#cd5b43'
information: '#326971'
warning: '#a37a00'
error: '#b7202e'
success: '#208436'
dark:
background: '#2b3f53'
border: '#405264'
highlight: '#d16314'
heading: '#f9fcfe'
body: '#eef7fc'
background_light: '#35485b'
background_dark: '#1e2c3a'
border_light: '#b4bbc2'
border_dark: '#1e2c3a'
interaction_text: '#2b3f53'
interaction_background: '#90cfeb'
interaction_hover_text: '#1e2c3a'
interaction_hover_background: '#bce2f3'
interaction_focus: '#ff6b6b'
information: '#4dc4fd'
warning: '#e38444'
error: '#e85653'
success: '#14b0ae'
background: '#152235'
border: '#425166'
highlight: '#96e7f4'
heading: '#ffffff'
body: '#eff6ff'
background_light: '#2b394d'
background_dark: '#030d1e'
border_light: '#5b6a80'
border_dark: '#2b394d'
interaction_text: '#030d1e'
interaction_background: '#96e7f4'
interaction_hover_text: '#030d1e'
interaction_hover_background: '#e1fbff'
interaction_focus: '#ff9c86'
information: '#9cccd3'
warning: '#ffcf3d'
error: '#ea858f'
success: '#8ce3a0'
optouts: { }
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ applies to this project. For **how** to perform operations, see

- [Development](development.md) - Coding agreements: function visibility and using `drupal_helpers`
- [Content types](content-types.md) - Adding custom node bundles and the theme layer they need
- [Brand colours](brand-colours.md) - The brand palette, where it lives and how to change it
- [Testing](testing.md) - Testing conventions and agreements
- [CI](ci.md) - Continuous integration configuration
- [Deployment](deployment.md) - Deployment configuration
Expand Down
99 changes: 99 additions & 0 deletions docs/brand-colours.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Brand colours

The brand defines six colours. https://brand.drevops.com/ is the source of truth for them, and for the ratio they are meant to appear in: white and Cool White 56%, Navy 28%, Teal 12%, Coral 4%.

| Name | Hex | Role |
|---|---|---|
| Navy | `#152235` | Primary ground: structure, chrome, headers, footers, dark panels |
| Deep Navy | `#030d1e` | The deepest ground layer |
| Cool White | `#eff6ff` | The light surface |
| Teal | `#1e7582` | Accent on light backgrounds: links, kickers, rules, ticks, the single call to action |
| Bright Cyan | `#96e7f4` | Replaces Teal on Navy backgrounds |
| Coral | `#cd5b43` | Problems, warnings, "before" states, risk labels |

Steps between those six come from the ramps in `static-prototype/framework.css` (`--color-primary-*` around Navy, `--color-secondary-*` around Teal and Bright Cyan, `--color-tertiary-*` around Coral, plus `pass`, `fail`, `warn` and `info` ramps for status). They are the generated ramps around the brand anchors, so a shade that the brand does not name is taken from there rather than invented.

## Where the palette lives

CivicTheme renders from 18 named palette slots per theme. The site carries them in two places, and both have to be edited together.

| File | What it is |
|---|---|
| `config/default/drevops.settings.yml` | The source of truth. `CivicthemeColorManager` compiles `colors.palette` into `public://css-variables.drevops.css` as `--ct-color-{light,dark}-{slot}` |
| `web/themes/custom/drevops/components/variables.base.scss` | The Storybook fallback. Component SCSS compiles `ct-color-light('x')` to `var(--ct-color-light-x, <fallback>)`, and Storybook has no generated stylesheet, so the fallback is what it renders |

The config uses underscores (`background_light`), the SCSS map uses hyphens (`background-light`).

`colors.brand` never reaches CSS. It seeds the Brand colors fieldset in the theme settings form, where a JS handler recomputes the palette fields from a brand swatch. `use_brand_colors` is `0`, which hides that fieldset, so the recompute cannot overwrite the palette.

## The palette

| Slot | Light | | Dark | |
|---|---|---|---|---|
| `heading` | `#152235` | Navy | `#ffffff` | White |
| `body` | `#2b394d` | Navy 8 | `#eff6ff` | Cool White |
| `background_light` | `#ffffff` | White | `#2b394d` | Navy 8 |
| `background` | `#eff6ff` | Cool White | `#152235` | Navy |
| `background_dark` | `#c8d9f3` | Navy 2 | `#030d1e` | Deep Navy |
| `border_light` | `#c8d9f3` | Navy 2 | `#5b6a80` | Navy 6 |
| `border` | `#8fa0b8` | Navy 4 | `#425166` | Navy 7 |
| `border_dark` | `#425166` | Navy 7 | `#2b394d` | Navy 8 |
| `interaction_text` | `#ffffff` | White | `#030d1e` | Deep Navy |
| `interaction_background` | `#1e7582` | Teal | `#96e7f4` | Bright Cyan |
| `interaction_hover_text` | `#ffffff` | White | `#030d1e` | Deep Navy |
| `interaction_hover_background` | `#045a65` | Teal 7 | `#e1fbff` | Cyan 1 |
| `interaction_focus` | `#cd5b43` | Coral | `#ff9c86` | Coral 3 |
| `highlight` | `#1e7582` | Teal | `#96e7f4` | Bright Cyan |
| `information` | `#326971` | Info 8 | `#9cccd3` | Info 4 |
| `warning` | `#a37a00` | Amber 8 | `#ffcf3d` | Amber 5 |
| `error` | `#b7202e` | Red 7 | `#ea858f` | Red 4 |
| `success` | `#208436` | Green 8 | `#8ce3a0` | Green 4 |

`highlight` is the stripe accent: the bar on callouts, blockquotes, accordions and cards, the active primary-navigation border, and the table-of-contents hover marker. It is also what the eyebrow kicker and rich-text table captions use, through `assets/sass/_eyebrow.scss`.

`warning` uses the amber ramp rather than Coral. The brand assigns Coral to warnings, but that is about authored content - a risk label, a "before" column - and the four status slots sit next to each other in form and message chrome, where amber, red, green and teal-grey stay distinguishable in a way that two adjacent reds do not.

## Every slot is set, none is derived

CivicTheme can derive all 18 slots from three brand anchors, using formulas such as `heading = brand1|shade,60`. Those filters mix with pure black and pure white, which desaturates: deriving from Teal and Cool White produces a grey `#38484a` body and a grey `#bfc5cc` surface rather than steps along the brand's blue ramp. Every slot is therefore written out.

The consequence is that changing a brand colour changes nothing on its own. A rebrand means editing the 18 values in both files.

## Contrast

Every pair the palette puts together clears WCAG AA. The tight ones are worth knowing about:

| Pair | Ratio |
|---|---|
| Teal on white | 5.36:1 |
| Teal on Cool White | 4.92:1 |
| White on the Teal button | 5.36:1 |
| Bright Cyan on Navy | 11.5:1 |
| Deep Navy on the Bright Cyan button | 13.9:1 |
| `#a37a00` warning on white | 3.93:1 |

The warning amber is only ever a border, which needs 3:1 rather than 4.5:1. Moving it to a text role means darkening it to `#705400` first.

`interaction_focus` cannot clear 3:1 against both white and the Teal button, because the two requirements point in opposite directions for any single colour. CivicTheme's shipped default has the same limitation. The chosen values are the ones that work on the page backgrounds, which is where the offset ring actually sits.

## Applying a change

The compiled stylesheet is written once and then left alone, so a config change on its own leaves the old palette on disk:

```bash
ahoy drush cim -y
ahoy drush cr
```

`ThemeColorSubscriber` purges the stylesheet when the colour config is saved, and `do_base_deploy_refresh_theme_colors()` purges it on every deploy, so an import is enough. For the SCSS side:

```bash
ahoy cli npm run build --prefix web/themes/custom/drevops
```

Check both themes after a change. Light and dark are separate palettes and a slot can be right in one and wrong in the other.

## Related

- [Front-end performance](performance.md) - image styles, self-hosted fonts and layout stability
- [Content types](content-types.md) - adding a bundle and the theme layer it needs
12 changes: 4 additions & 8 deletions web/themes/custom/drevops/assets/sass/_eyebrow.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@
// Eyebrow.
//
// Small, uppercase kicker label intended to sit above a heading. The brand
// palette has no orange, so this colour is a deliberate accent rather than a
// CivicTheme token; adjust `$ct-eyebrow-color` to taste. The chosen value
// clears WCAG AA contrast on light backgrounds.
// assigns kickers the same accent it assigns rules, which is what the
// `highlight` palette slot holds: teal on light bands, bright cyan on dark.
//

$ct-eyebrow-color: #c1430a !default;

// The orange reads too dark against dark bands, so those flip to the
// palette's light-blue interaction accent, which clears WCAG AA there.
$ct-eyebrow-color-dark: #90cfeb !default;
$ct-eyebrow-color: ct-color-light('highlight') !default;
$ct-eyebrow-color-dark: ct-color-dark('highlight') !default;

.eyebrow {
display: block;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

$transparency: if($index % 2 == 0, 25%, 50%);

background-color: color.mix(#61daff, white, $transparency);
background-color: color.mix(#96e7f4, white, $transparency);

&::before {
content: 'spacing #{$name}';
Expand Down
61 changes: 50 additions & 11 deletions web/themes/custom/drevops/components/variables.base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,66 @@
//
// stylelint-disable scss/dollar-variable-pattern

// The brand palette. These values are the Storybook fallbacks baked into
// `var(--ct-color-*, <fallback>)`; in Drupal the generated stylesheet defines
// the same variables from `config/default/drevops.settings.yml`, which is the
// source of truth. Both must be edited together - see `docs/brand-colours.md`.
$ct-colors-brands: (
'light': (
'brand1': #00698f,
'brand2': #e6e9eb,
'brand3': #121313,
'brand1': #1e7582,
'brand2': #eff6ff,
'brand3': #1e7582,
),
'dark': (
'brand1': #61daff,
'brand2': #003a4f,
'brand3': #00698f,
'brand1': #96e7f4,
'brand2': #152235,
'brand3': #96e7f4,
)
);

// Every slot is set explicitly rather than derived from the brand colours
// above: CivicTheme derives by mixing with pure black and white, which pulls
// the intermediate steps off the brand's blue ramp and toward grey.
$ct-colors: (
'light': (
'success': green,
'custom1': brown,
'heading': #152235,
'body': #2b394d,
'background-light': #fff,
'background': #eff6ff,
'background-dark': #c8d9f3,
'border-light': #c8d9f3,
'border': #8fa0b8,
'border-dark': #425166,
'interaction-text': #fff,
'interaction-background': #1e7582,
'interaction-hover-text': #fff,
'interaction-hover-background': #045a65,
'interaction-focus': #cd5b43,
'highlight': #1e7582,
'information': #326971,
'warning': #a37a00,
'error': #b7202e,
'success': #208436,
),
'dark': (
'custom1': red,
'custom2': orange,
'custom3': blue,
'heading': #fff,
'body': #eff6ff,
'background-light': #2b394d,
'background': #152235,
'background-dark': #030d1e,
'border-light': #5b6a80,
'border': #425166,
'border-dark': #2b394d,
'interaction-text': #030d1e,
'interaction-background': #96e7f4,
'interaction-hover-text': #030d1e,
'interaction-hover-background': #e1fbff,
'interaction-focus': #ff9c86,
'highlight': #96e7f4,
'information': #9cccd3,
'warning': #ffcf3d,
'error': #ea858f,
'success': #8ce3a0,
),
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ $ct-icon-sizes: (
'm': (ct-particle-px(3.5), ct-particle-px(3.5)),
),
);
$ct-back-to-top-light-background-color: ct-color-light('custom1');

//
// Banner.
Expand Down
Loading