# elluminate Design System — Figma Conformance Reference

> **Read this first.** This document is the canonical specification for the elluminate clinical platform's UI. **Drop it into context alongside any Figma design** (or screenshot, or third-party markup) and use it to audit, translate, and align that design with elluminate standards.
>
> **Primary use case (Product Managers):** you share a Figma design that may have been built with Tailwind, Material, generic Bootstrap, a third-party kit, or no system at all. The AI's job is to **map every visual element to its elluminate equivalent** — colors, typography, spacing, components, icons — and produce either (a) a list of conformance violations with fixes, or (b) the corrected elluminate-styled HTML/CSS.
>
> **Output modes:**
> 1. **Audit** — list every element that doesn't conform, name the elluminate replacement, cite the section.
> 2. **Translate** — output the corrected markup using elluminate classes / tokens only.
> 3. **Generate** — when asked to build new screens from the Figma, use elluminate classes from the start; never reach for Tailwind / Material / generic Bootstrap.
>
> **Philosophy:** the elluminate chrome is a **calm canvas**; vibrant data colors live on top of it. UI surfaces stay quiet so the data is loud. **All values are in pixels and hex** — no rem/em, no variables required to read this.
>
> **Companion files:** `index.html` is the visual reference (every component rendered); `style.css` has the canonical CSS tokens.

---

## A. Brand assets & icon kit

### A.1 Logo (masthead — dark-surface variant)

The elluminate brand mark — a stylized **"e"** built from a white letter silhouette layered with four colored accent squares (cyan, lavender, two oranges) — **ships with this design-system package** in two formats:

| File | Format | Use for |
|---|---|---|
| **`elluminate_e-logo.svg`** ★ | SVG (vector, source-of-truth) | **Figma, product UI, anywhere scalable rendering matters** — preferred for all new work. viewBox `0 0 89.09 120` (portrait, ~3:4 aspect). |
| `elluminate_e-logo-for-dark.gif` | GIF (raster) | legacy use, fallback for environments that can't render SVG |

**Designed exclusively for dark surfaces.** The mark contains white shapes (the readable "e" letter) layered over the four colored accent squares. Place it on dark backgrounds (`#182B3B` masthead, dark `.toolbar` gradients) so the white "e" reads. On light surfaces the white silhouette disappears and only four disconnected colored squares remain — **don't use it on white/light pages.**

#### Brand colors (mark only)

| Role | Hex | Note |
|---|---|---|
| White "e" silhouette | `#FFFFFF` | the readable letter — only visible on dark surfaces |
| Cyan accent | `#05C2DF` | top square |
| Orange accent | `#ED7514` | left + bottom-right squares |
| Lavender accent | `#928CFF` | middle square |

> These four hexes are **brand-mark exclusives** — they appear in the logo only. Don't introduce them as UI chrome, badges, or text colors. The system palette stays in §2.

#### Copy-paste SVG (for Figma)

To drop the brand mark into a Figma file: **select all** and **copy** the SVG block below, then in Figma use **Edit → Paste** (Ctrl/Cmd + V). Figma imports the markup as native vector shapes — you can resize, recolor (don't!), or extract individual squares from the resulting frame.

```svg
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 89.09 120">
  <defs>
    <style>
      .cls-1 { fill: #fff; }
      .cls-2 { fill: #928cff; }
      .cls-3 { fill: #05c2df; }
      .cls-4 { fill: #ed7514; }
    </style>
  </defs>
  <rect class="cls-3" x="11.89" width="23.77" height="23.77"/>
  <rect class="cls-4" y="57.05" width="23.77" height="23.77"/>
  <rect class="cls-4" x="65.3" y="96.23" width="23.77" height="23.77"/>
  <rect class="cls-2" x="23.77" y="80.83" width="23.77" height="23.77"/>
  <path class="cls-1" d="M47.73,15.51v16.21c3.33-1.54,7.03-2.32,11.12-2.32,7.83,0,14.2,2.51,19.11,7.52,4.91,5.01,7.36,11.63,7.36,19.86l-.05,2.38h-40.09c.58,3.54,2.12,6.35,4.64,8.43,2.52,2.09,5.72,3.13,9.62,3.13,4.66,0,8.67-1.63,12.02-4.89l10.51,4.94c-2.62,3.72-5.76,6.47-9.42,8.25-1.58.77-3.3,1.37-5.14,1.8h21.66V15.51h-41.36Z"/>
  <path class="cls-1" d="M40.48,74.31c-4.91-4.92-7.36-11.08-7.36-18.49s2.45-13.89,7.34-18.9c.37-.37.74-.73,1.12-1.08h-17.8v44.98h28.27c-4.48-1.11-8.34-3.27-11.57-6.51Z"/>
  <path class="cls-1" d="M67.92,42.57c-2.43-1.74-5.24-2.61-8.45-2.61-3.48,0-6.53.98-9.16,2.93-1.65,1.22-3.17,3.37-4.58,6.45h27.06c-.82-2.77-2.45-5.03-4.88-6.77Z"/>
</svg>
```

> **Tip for Figma:** after pasting, group the imported shapes (Ctrl/Cmd + G) and rename the group **"elluminate-logo"**. To use it on a frame, drop a dark fill (`#182B3B`) underneath so the white "e" is visible.

#### Placement in the masthead

The logo sits in the top-left of the `50px` masthead, **immediately before** the "elluminate" wordmark, with `10px` gap between the mark and the text. Render at **`28px` tall** (width auto-sizes to ~`21px` given the 89.09 × 120 viewBox).

```html
<header class="masthead">
    <div class="masthead-logo">
        <img src="elluminate_e-logo.svg" alt="elluminate" class="masthead-logo-mark" />
        <span class="masthead-logo-wordmark">elluminate</span>
        <span class="masthead-logo-suffix">· {Page or product name}</span>
    </div>
    <!-- ... rest of masthead actions ... -->
</header>
```

```css
.masthead-logo { display: flex; align-items: center; gap: 10px; }
.masthead-logo-mark { height: 28px; width: auto; display: block; }
.masthead-logo-wordmark { font-weight: 500; color: #FFFFFF; }
.masthead-logo-suffix    { font-weight: 300; color: #A3C5E2; /* blue-300 */ }
```

> **Don't** recolor, outline, drop-shadow, pad, or rearrange the squares. The mark is fixed as shipped.
>
> **Don't** use this logo on light surfaces (white pages, light toolbars, modals) — the white silhouette disappears and only four disconnected squares remain. If a light-surface variant is needed for a Figma design, **ask** before substituting — there is no light-variant in this package today.
>
> **Don't** swap the asset for an emoji, an SVG re-trace, or a generic placeholder. Use the bundled `elluminate_e-logo.svg` (preferred) or `elluminate_e-logo-for-dark.gif` (raster fallback) as shipped.

### A.2 Font Awesome icon kit (canonical icon source)

**Every icon in elluminate must come from this kit.** No emoji icons (✓, ✗, ★, ⚠️, ℹ️), no generic Material Symbols, no inline SVG glyphs from Heroicons/Lucide/Phosphor, no random icon fonts. If you see a glyph in a Figma design, replace it with the equivalent Font Awesome icon below.

```html
<link rel="stylesheet" href="https://kit.fontawesome.com/73bc6e2b04.css" crossorigin="anonymous">
```

The kit ships **Font Awesome 7 Pro** (Light / Solid / Regular), **Font Awesome 6 Brands**, **Duotone**, **Sharp**, **PrimeIcons**, and the elluminate custom icon set (`ec ec-*`).

| Weight | Class prefix | Use for |
|---|---|---|
| **Light** ★ default | `<i class="fal fa-{name}">` | almost every icon — toolbars, buttons, dialogs, chevrons, badges, table actions |
| **Solid** | `<i class="fas fa-{name}">` | toggled-on / active states only — filter applied, item favorited, notifications enabled |
| Regular | `<i class="far fa-{name}">` | rare decorative cases |
| Brands (FA6) | `<i class="fab fa-{name}">` | external vendor logos |
| Duotone / Sharp | `<i class="fad fa-{name}">`, `<i class="fass fa-{name}">` | occasional decorative |
| PrimeIcons | `<i class="pi pi-{name}">` | inside PrimeNG widgets only |
| Custom (elluminate) | `<i class="ec ec-{name}">` | platform-specific glyphs (e.g. eClinical brand marks) |

> **Solid icons render in primary blue (`#1F68A8`)** — the standard "engaged / active" cue. The "thin gray → fat blue" weight + color shift means "off → on". **Don't override** with semantic colors (no yellow on a favorited star, no red on a liked heart). Exceptions: inside `.btn-primary` / `.btn-danger` / `.badge` the solid icon inherits the container's text color; on dark surfaces it goes near-white (`#F6F6F6`).

> **Disclosure indicators** for collapse/expand (accordions, lists, panels) are always **light chevrons**: `fal fa-chevron-right` when collapsed, `fal fa-chevron-down` when expanded (or rotate the right-chevron 90° via CSS). **Never use `+` / `−` text characters.**

> **Common icons:** `fa-chevron-down`, `fa-chevron-right`, `fa-chevron-up`, `fa-chevron-left`, `fa-caret-down`, `fa-caret-right`, `fa-gear`, `fa-filter`, `fa-magnifying-glass`, `fa-pen-to-square`, `fa-floppy-disk`, `fa-rotate-right`, `fa-xmark`, `fa-ellipsis-vertical`, `fa-trash`, `fa-bell`, `fa-user`, `fa-circle-exclamation`, `fa-circle-info`, `fa-triangle-exclamation`, `fa-check`, `fa-clock`, `fa-arrow-right`, `fa-plus`, `fa-bars`, `fa-star`, `fa-heart`, `fa-bookmark`, `fa-eye`, `fa-hourglass-half`. Always pair with `fal` (default) or `fas` (active).

---

## B. How to use this document with a Figma design

When given a Figma design (or screenshot, or HTML markup), work through this in order:

1. **Scan top-down** for every element type — masthead (and its sub-elements: app-grid, header label, dropdowns, sub-nav tabs, filter pills, +N overflow, search, trailing toolbar), side nav, page title, buttons, cards, forms, tables, modals, badges, icons, status pills, alerts, tabs, dropdowns, accordions, charts.
2. **For each element, find its elluminate equivalent** in §F (Figma element → elluminate component map) or in §6 (Components reference). **For anything inside the dark `50px` top app bar, use §6.16 (Masthead deep dive)** — masthead chrome uses dedicated components (`<ecs-dropdown>`, `.sub-nav-container-horizontal`, `.filter-pill-dark`, `.ecs-pill-overflow-dropdown`, `<ecs-menu>`, `.btn-icon-for-dark`, `.toolbar-badge`) that look similar to but **must not be confused with** their page-body equivalents in §6.7 / §6.8.
3. **Translate every token**: every color must be in §2 palette; every dimension in §4; every shadow in §5; every z-index in §7; every icon in the kit above (§A.2); the masthead logo from §A.1 ships with the package — never substitute it.
4. **Replace foreign class names** using §D (Tailwind / Material / Bootstrap / generic CSS → elluminate translation tables).
5. **Reach for the most-used variant first.** Each component section in §6 lists variants in **production-usage order** — the top entry is the safe default for almost every screen.
6. **Flag anti-patterns** using §G — call them out as violations with the elluminate fix.

---

## C. Figma audit checklist (run for every design)

A 18-point quick scan. If a Figma design fails any of these, fix it before sign-off.

| # | Check | If wrong, replace with |
|---|---|---|
| 1 | Body text is **12px Roboto**, color `#1A1A1A` | inherit from `<body>`; never set explicit font on body copy |
| 2 | Border radius is **`4px`** everywhere | radius `4px` (badges are the only exception → `10px` pill) |
| 3 | Default border is **`1px solid #DDDDDD`** | `border-color: #DDDDDD` |
| 4 | Buttons: **24px** default, **32px** medium, **40px** large | `.btn` / `.btn-md` / `.btn-lg` |
| 5 | Inputs: **28px** native, **32px** form-control | `<input>` / `.form-control` |
| 6 | Table header rows **32px**, body rows **22px** | `.table` |
| 7 | Toolbars **32px**, masthead **50px**, sidebar **220px** wide | `.minimal-toolbar` (default) / `<header>` / sidebar |
| 8 | Icons are **Font Awesome Light (`fal`)** by default; Solid (`fas`) only for active/toggled-on states | `<i class="fal fa-{name}">` |
| 9 | Active item indicator is **`2px` orange (`#F77623`)** rail (left for lists, bottom for tabs) | `.active` modifier |
| 10 | Status colors: success `#1F8915`, warning `#EEB12B`, error `#BD341F`, info `#3F42C5`, brand `#3B85C7`, action `#1F68A8` | `.text-*` / `.bg-*` / `.badge-*` |
| 11 | Disabled = `opacity: 0.4` + `pointer-events: none` | `disabled` attribute or `.is-disabled` |
| 12 | Standard transition is `all 300ms ease` | inherited; don't override |
| 13 | Modal backdrop `rgba(0,0,0,0.4)`; modal shadow `0 20px 40px rgba(0,0,0,0.4)`; **header is white** (`32px`, `1px solid #E8E8E8` bottom border) — **never** the dark page-toolbar gradient | use `<dialog-container>` |
| 14a | **Required-field validation:** red border on the empty input + `placeholder="Required"`. **No popup, no inline alert** — the visual is the signal. | `.error` on input + `placeholder="Required"` |
| 14b | **Server-returned (API round-trip) validation errors** — duplicate names, server-enforced uniqueness, permission denials, conflict errors, save failures, anything in the API response's error payload — surface as a **popup toast**. Optionally mark the offending input(s) with `.error`. **Not** an inline `.alert.alert-danger` at the top of the form. | `AlertsService.showErrorToast(serverMsg)` or `showModelStateError(response.modelState)` — see §6.4.2 |
| 14c | **No** inline error text below the input (deprecated `.inline-error` / `.inline-warning` pattern) | always remove |
| 15 | Z-index from §7 scale only — never invented | pick from §7 (e.g. `8997` modal, `100002` tooltip) |
| 16 | No emoji or generic icons — everything from the FA kit (§A.2) | `<i class="fal fa-{name}">` |
| 16a | Masthead shows the elluminate brand mark (`elluminate_e-logo.svg`, `28px` tall) immediately before the wordmark — see §A.1 | `<img src="elluminate_e-logo.svg" class="masthead-logo-mark" alt="elluminate">` |
| 16b | Masthead chrome uses **masthead-only components** (not page-body equivalents): `<ecs-dropdown>` (not `.dropdown-menu`), `.sub-nav-container-horizontal` (not `.nav-tabs`), `.filter-pill-dark` (not `.filter-pill-light`), `.btn-icon.btn-icon-for-dark` (not bare `.btn-icon`) — see §6.16 | apply the masthead-specific class for each element |
| 16c | Masthead-only accent `#4EE5FB` (active filter icon) does **not** appear elsewhere in the design | restrict to `.ecs-masthead-toolbar` |
| 17 | One primary button per form/dialog footer (others are secondary) | `.btn-primary` once + `.btn-secondary` for the rest |
| 18 | Headings use the compressed scale (h1 30, h2 19, h3 16, h4 14, h5 12, h6 10 px) — never custom font sizes | match §3.2 exactly |

---

## D. Style translation tables

### D.1 Tailwind → elluminate

#### Colors

| Tailwind | elluminate |
|---|---|
| `bg-white`, `text-white` | `.bg-white`, `.text-white` (`#FFFFFF`) |
| `bg-gray-50`, `bg-slate-50` | `#F6F6F6` (off-white surface) |
| `bg-gray-100` | `#F6F6F6` |
| `bg-gray-200`, `border-gray-200` | `#E8E8E8` (light gray) |
| `bg-gray-300`, `border-gray-300` | `#DDDDDD` (default border) |
| `text-gray-400`, `bg-gray-400` | `#BBBBBB` (mid-tone — scrollbar only) |
| `text-gray-500`, `text-gray-600`, `text-slate-500` | `.text-gray` (`#767676`) |
| `text-gray-700`, `text-gray-800` | `#4B4B4B` (input text) |
| `text-gray-900`, `text-black` | inherit (`#1A1A1A`) — don't set |
| `bg-blue-50`, `bg-sky-50` | `#F0F5FA` (`.bg-highlight-light`) |
| `bg-blue-100`, `bg-sky-100` | `#CADDEE` (`.bg-highlight`) |
| `bg-blue-500`, `text-blue-500` | brand `#3B85C7` (`.bg-info`, `.text-info`) |
| `bg-blue-600`, `text-blue-600`, `bg-blue-700` | action `#1F68A8` (`.text-primary`, `<a>`) |
| `bg-slate-900`, `bg-gray-900` | masthead `#182B3B` |
| `bg-green-500`, `text-green-500`, `bg-emerald-500` | `#1F8915` (`.text-success`, `.badge-success`) |
| `bg-yellow-400`, `bg-amber-400` | `#EEB12B` (`.bg-warning`, `.badge-warning`) |
| `bg-red-500`, `bg-red-600`, `text-red-500` | `#BD341F` (`.text-danger`, `.badge-danger`) |
| `bg-orange-500`, `text-orange-500` | `#F77623` — **active rail only**, never chrome |
| `bg-indigo-600`, `text-indigo-600`, `bg-violet-600` | `#3F42C5` (information accent — rare) |
| `bg-teal-*`, `bg-cyan-*` | not in elluminate — replace with brand blue or remove |
| `bg-pink-*`, `bg-rose-*` | not in elluminate — replace with semantic equivalent |

#### Typography

| Tailwind | elluminate |
|---|---|
| `text-xs` (12px) | default body — inherit, don't set |
| `text-sm` (14px) | `<h4>` size — only when it's actually a heading |
| `text-base` (16px) | `<h3>` size |
| `text-lg`, `text-xl` (18-20px) | `<h2>` (`19px`) |
| `text-2xl`, `text-3xl` | `<h1>` (`30px`) — page-level only |
| `font-thin`, `font-light` | not used |
| `font-normal` (400) | default |
| `font-medium` (500) | `.btn` text |
| `font-semibold` (600) | round to 500 or 700 — not used |
| `font-bold` (700) | `.section-header`, headings inside `.section` |
| `font-sans` | already inherited (Roboto) |
| `font-mono` | not used in product UI |
| `leading-tight`, `leading-snug` | body uses `1.5` (18px line-height); compressed headings per §3.2 |
| `tracking-*` | don't use — letter-spacing is default |

#### Spacing & sizing

| Tailwind | elluminate |
|---|---|
| `p-1` / `m-1` (4px) | `4px` direct or `.gap-1` |
| `p-2` / `m-2` (8px) | `8px` direct or `.gap-2` (form-group bottom margin) |
| `p-3` (12px) | `12px` direct |
| `p-4` (16px) | `16px` direct or `.gap-3` |
| `p-5` (20px) | `20px` (`.dialog-content` padding) |
| `p-6` (24px) | `24px` or `.gap-4` |
| `p-8`+ (32px+) | **too spacious** — the platform is dense; round down to 24px |
| `gap-1` … `gap-6` | `.gap-1` (4) / `.gap-2` (8) / `.gap-3` (16) / `.gap-4` (24) |
| `space-x-*`, `space-y-*` | use `.gap-*` on a flex container |
| `rounded` (4px) | default — no class needed |
| `rounded-md` (6px), `rounded-lg` (8px), `rounded-xl` (12px), `rounded-2xl` | round down to `4px` (universal radius) |
| `rounded-full` | only for badges (already pill at `10px`) and `.btn-round` (FAB — rare) |
| `h-6` (24px) | `.btn` (default button height) |
| `h-7` (28px) | native `<input>` |
| `h-8` (32px) | toolbar / form-control / table header |
| `h-10` (40px) | `.btn-lg` |
| `h-12` (48px), `h-14` (56px) | not standard — replace with 40 or move to masthead `50px` |
| `w-full` | `.w-100` (or use directly) |
| `w-1/2` | `.w-50` |
| `w-1/3`, `w-2/3` | `.w-33`, `.w-65` (closest) |

#### Effects

| Tailwind | elluminate |
|---|---|
| `shadow-sm`, `shadow` | card shadow `0 1px 2px rgba(0,0,0,0.2)` (the most-used) |
| `shadow-md`, `shadow-lg` | overlay/popover lift `0 3px 15px rgba(0,0,0,0.4)` |
| `shadow-xl`, `shadow-2xl` | modal `0 20px 40px rgba(0,0,0,0.4)` (auto on `.modal-content`) |
| `shadow-inner` | not used — flag |
| `transition`, `transition-all` | `all 300ms ease` (inherited) |
| `duration-150`, `duration-200`, `duration-300` | `200ms` row hover / `300ms` everything else |
| `ease-in`, `ease-out`, `ease-in-out` | `ease` (default) |
| `opacity-50` | disabled = `0.4`, not `0.5` |
| `ring`, `ring-2`, `ring-blue-500` | primary/secondary buttons get this automatically on focus (`1px white` outline + `3px #1F68A8` ring) — don't add manually |
| `hover:*` | hover transitions are predefined per component — don't invent |

#### Layout & structure

| Tailwind | elluminate |
|---|---|
| `flex`, `flex-row`, `flex-col` | use directly |
| `items-center`, `justify-between`, `justify-end` | use directly |
| `divide-y`, `divide-gray-200` | `.border-bottom` per row |
| `border`, `border-2` | `1px solid #DDDDDD`; `2px` only for active rail or focus ring |
| `cursor-pointer` | already on buttons / links — don't add |
| `block`, `inline-block`, `hidden` | use directly |
| `absolute`, `relative`, `fixed` | use directly with z-index from §7 |
| `overflow-hidden`, `overflow-auto` | use directly |
| `truncate` | `.overflow-ellipsis` |

### D.2 Material Design → elluminate

| Material concept | elluminate |
|---|---|
| `mat-button`, `mat-flat-button` | `.btn .btn-primary` |
| `mat-stroked-button` | `.btn .btn-secondary` (or `.btn-outline`) |
| `mat-icon-button` | `.btn .btn-icon` |
| `mat-fab`, `mat-mini-fab` | `.btn .btn-primary .btn-round` (rare — Matilda chat-send only) |
| `mat-raised-button` (primary `#3F51B5`) | `.btn .btn-primary` (gradient `#3B85C7 → #215887`) |
| `mat-card`, `.mat-mdc-card` | `.rounded-borders` (default content wrapper) |
| `mat-form-field` (filled / outlined) | native `<input>` + `<label>` inside `.form-group` |
| `mat-label` | `<label>` (auto-spaced `4px` from input) |
| `mat-toolbar` | `<toolbar-widget class="minimal-toolbar">` |
| `mat-tab-group`, `mat-tab` | `.nav-tabs` + `.nav-link.active` |
| `mat-dialog`, `MatDialog.open()` | `<dialog-container>` |
| `mat-snack-bar` | `.alert.alert-{variant}` (inline) — toast/snackbar isn't a primary pattern |
| `mat-chip`, `mat-chip-set` | `.badge.badge-outline-{variant}` |
| `mat-icon` (Material Symbols) | Font Awesome `<i class="fal fa-{name}">` |
| `mat-menu`, `mat-menu-item` | `.dropdown-menu` + `.dropdown-item` |
| `mat-expansion-panel` | `<ngb-accordion>` (chevron disclosure, not arrow) |
| `mat-table`, `mat-row`, `mat-header-row` | `.table` (32px header / 22px body) |
| `mat-paginator` | hand-rolled with `.btn-icon` arrows + count text |
| `mat-progress-spinner`, `mat-progress-bar` | use `.thinking-indicator` or DevExtreme equivalents |
| `mat-tooltip`, `matTooltip` | `[ngbTooltip]` |
| Material colors: `primary` `#3F51B5`, `accent` `#FF4081`, `warn` `#F44336` | `#1F68A8` (action), no accent equivalent (use `#F77623` rail for active state), `#BD341F` (danger) |
| `mat-elevation-z2`, `z4`, `z8` | card shadow / overlay lift / modal shadow respectively |
| Material radius (`4px` default) | matches elluminate (`4px`) |
| `mat-divider` | `.border-bottom` or `.dropdown-divider` |
| Material density classes | inherent — elluminate is already dense |

### D.3 Bootstrap (vanilla) → elluminate

elluminate uses Bootstrap **4.6** with heavy overrides. Most class names are the same but the **rendered values differ** — always trust elluminate, never Bootstrap docs.

| Bootstrap default | elluminate value |
|---|---|
| `.modal-md` (500px) | `600px` |
| `.modal-lg` (800px) | matches (`800px`) |
| `.modal-sm` (300px) | `400px` |
| `.btn-primary` color (`#007BFF`) | gradient `#3B85C7 → #215887` (no flat blue) |
| `.btn-secondary` color (`#6C757D` gray) | white background, blue text/border (acts as a "ghost" button) |
| `.btn-success`, `.btn-info`, `.btn-warning`, `.btn-danger` | re-tinted to elluminate semantic colors |
| `.btn` height | `24px` (Bootstrap is `38px`) |
| `.alert` padding | `12px 20px` |
| `.badge` padding | `3px 6px`, `10px` radius (pill), `22px` min-width |
| `.nav-tabs` bottom border | **none** — uses `2px` orange (`#F77623`) rail on active tab |
| `.form-control` height | `32px` (Bootstrap is `38px`) |
| `.row` margin | `0 0 3px 0` |
| `.container-fluid` padding | `0` (no padding) |
| `body` font-size | `12px` (Bootstrap is `16px`) |
| `h1` … `h6` sizes | compressed scale (30 / 19 / 16 / 14 / 12 / 10 px) |
| `.text-primary`, `.text-info`, `.text-success`, `.text-warning`, `.text-danger` | re-tinted per §6.15 |
| `.dropdown-item` padding | `3px 10px` (smaller than Bootstrap default) |

### D.4 Generic CSS / hex colors → elluminate

When you see arbitrary hex values in a Figma design, snap them to the nearest elluminate token below.

| If you see this hex (or close) | Replace with |
|---|---|
| `#007BFF`, `#0D6EFD`, `#3B82F6`, `#2563EB` (Bootstrap / Tailwind blue) | `#1F68A8` (action) or `#3B85C7` (brand) |
| `#6C757D`, `#888`, `#999` (mid gray) | `#767676` (`.text-gray`) |
| `#000`, `#222`, `#333` (near-black) | `#1A1A1A` (default text) |
| `#F8F9FA`, `#FAFAFA`, `#EEE`, `#F5F5F5` (off-white) | `#F6F6F6` |
| `#DDD`, `#E5E5E5`, `#E0E0E0` (light borders) | `#DDDDDD` (default) or `#E8E8E8` (lighter accent) |
| `#28A745`, `#198754`, `#4CAF50`, `#22C55E` (success greens) | `#1F8915` |
| `#FFC107`, `#FFCA28`, `#F0AD4E`, `#FACC15` (warning yellows) | `#EEB12B` |
| `#DC3545`, `#D32F2F`, `#E53935`, `#EF4444` (error reds) | `#BD341F` |
| `#17A2B8` (info teal) | `#3B85C7` (brand blue) — elluminate doesn't use teal for info |
| `#6610F2`, `#673AB7` (purple/indigo) | `#3F42C5` (info accent — rare) |
| Random oranges (`#FF9800`, `#F57C00`) | `#F77623` (active rail only) — otherwise replace with brand blue |
| Hot pink, magenta (`#E91E63`, `#FF4081`) | not in elluminate — replace with semantic equivalent or remove |
| Teal, cyan (`#009688`, `#00BCD4`) | not in elluminate — replace with brand blue |

---

## E. Common third-party patterns → elluminate

Recognize these by sight in a Figma design and replace immediately:

| If you see this in Figma | What it likely is | Replace with |
|---|---|---|
| Floating "+" round button bottom-right | Material FAB | usually unnecessary in elluminate — move action to a toolbar `.btn-primary`. The only legitimate round button is `.btn-primary.btn-round` (Matilda chat-send). |
| Filled status pill with white text on colored bg | Generic chip | `.badge.badge-{success/warning/danger/info/secondary}` |
| Outlined chip with colored text + border | Material/Tailwind chip | `.badge.badge-outline-{variant}` |
| Card with `8px` or `12px` rounded corners + shadow | Material card | `.rounded-borders` (4px radius, `1px solid #E8E8E8`, `0 1px 2px rgba(0,0,0,0.2)` shadow) |
| Tab bar with full-width colored underline | Material tabs | `.nav-tabs` with `2px` `#F77623` active rail |
| Toggle switch (iOS-style) | Switch | DevExtreme `<dx-switch>` (use blue `#1F68A8` for on-state) |
| Stepper with numbered circles | Material stepper | hand-roll with circular `.btn-icon` + connecting line; not a built-in component |
| Sticky bottom action bar | Mobile pattern | flag — desktop-first; move actions into `.dialog-buttons` or page toolbar |
| Floating filter drawer from right | Drawer | use a modal with `windowClass: 'modal-lg2'` or a side panel via `<as-split>` |
| Neumorphic / glassmorphism | Trendy aesthetic | flag — elluminate is flat-with-gradients; use `.rounded-borders` + standard shadow |
| Dark mode | Theme | not supported as a global theme — only the masthead and `.toolbar` are dark. Body stays light. |
| Avatars with colored backgrounds and initials | User chip | hand-roll with circular div + `12px` initials (no dedicated component) |

---

## F. Figma element → elluminate component map

When you see a generic Figma element (e.g. "card", "tag", "header bar"), use this table to pick the elluminate component. Every element on the elluminate showcase page (`index.html`) is covered. Production-usage order is preserved — first option in each row is the safe default.

| Figma element / generic name | elluminate component | Class / pattern |
|---|---|---|
| **App bar / top nav / page header** | Masthead | `<header class="ecs-masthead">` (`50px` tall, `#182B3B` background, white text/icons). Seven horizontal regions — see §6.16. Top-left **must** show `elluminate_e-logo.svg` (`28px`) before the wordmark (§A.1). |
| **Logo / brand mark in masthead** | Brand mark (dark variant) | `<img src="elluminate_e-logo.svg" class="masthead-logo-mark" alt="elluminate">` — SVG (preferred) or `elluminate_e-logo-for-dark.gif` (raster fallback). `28px` tall, `10px` gap before wordmark. SVG markup embedded in §A.1 for Figma copy-paste. |
| **App grid icon / "9-dot" menu launcher** (top-left) | App-grid menu trigger | `<button class="btn btn-icon ecs-menu-btn" id="ecsMenu"><i class="fa-solid fa-grid"></i></button>` — 18px, **white**, opens the `<ecs-menu>` mega-menu flyout. See §6.16.7 |
| **Mega-menu / app drawer** (modules + profile + sign-off) | Mega-menu flyout | `<ecs-menu>` rendered as DevExtreme `dx-popover`, **`850px` × `100vh − 60px`**, two-column body. Header `50px`, footer `50px` — see §6.16.7 |
| **Page-context label in the masthead** (e.g. "Data Central · Subjects") | Header label | `<ecs-header [label]="..." [badges]="...">` → `.ecs-header-label` (16px bold, `#CADDEE`) — see §6.16.2 |
| **Inline path separator inside the header label** | Light chevron | `<i class="fal fa-chevron-right">` at `10px`, between path segments — see §6.16.2 |
| **Masthead navigation dropdown** (e.g. Study selector, Module selector) | Masthead dropdown | `<ecs-dropdown>` — fills `50px` height, `14px` lighter text, `#CADDEE`, `1px` `#215887` right border, `300px` popover. **Not** `.dropdown-menu` (§6.8) — see §6.16.3 |
| **Horizontal tabs sitting inside the dark masthead bar** | Sub-nav (horizontal) | `<div class="sub-nav-container sub-nav-container-horizontal">` — `49px` items, white text, `3px` `#F77623` bottom rail. **Not** `.nav-tabs` (§6.7) — see §6.16.4 |
| **Filter pill / applied-filter chip in the masthead** | Dark filter pill | `<div class="filter-pill filter-pill-dark">` — `36px` tall, `#1E4060` bg, `#82B0D9` border, two-button (label + ×) — see §6.16.5 |
| **Filter pill on a light page body** (filter bar below masthead) | Light filter pill | `<div class="filter-pill filter-pill-light">` — same shape, white bg, `#BBBBBB` border, `#1A1A1A` text — see §6.16.5 |
| **"+N more filters" overflow button** | Filter pill overflow | `<div class="ecs-pill-overflow-dropdown">` with `.btn-icon-for-dark` button (`56px` wide, `+N` label, chevron) and `dx-popover` of overflowed pills — see §6.16.6 |
| **Global search input in the masthead** (alone) | Search-only (Pattern A) | `<ng-content select="[search]">` slot rendering `.ecs-masthead-search` — `240px × 30px` `<input type="search">`, dark `#1E4060` bg, white text, magnifier on the **right** — see §6.16.8 |
| **Search input + multi-select field picker** (e.g. "Name, Description ▾ + search") | Search-by picker + search (Pattern B) | `.ecs-masthead-search-group` wrapping `.ecs-masthead-search-by-btn` (joined picker) + `.ecs-masthead-search` (joined input). Picker opens a `.ecs-masthead-search-by-popover` with `.search-by-option` checkbox rows — see §6.16.8 |
| **Right-aligned action buttons in the masthead** (refresh, settings, more) | Masthead toolbar | `<div class="ecs-masthead-toolbar">` with `.btn .btn-icon .btn-icon-for-dark` buttons. Active filter icon goes **`#4EE5FB`** (masthead-only accent) — see §6.16.9 |
| **Notification count / unread bubble on a masthead action** | Toolbar badge | `<span class="badge badge-{variant} toolbar-badge badge-margin">3</span>` — z-index `9000`, `4px` left margin between badges — see §6.16.10 |
| **User profile + sign-off** (in app drawer) | Mega-menu header | Lives inside `.ecs-menu-header` — three `33%` columns: profile (left), Studies/Stores tabs (center), Sign Off (right). Not a standalone masthead element — see §6.16.7 |
| **Side navigation** | Sidebar | `220px` wide column |
| **Page-level toolbar** (transparent) | Minimal toolbar | `<toolbar-widget class="minimal-toolbar">` (`32px`, transparent) |
| **Page-level toolbar** (dark gradient) | Default toolbar | `.toolbar` — **rare**, top-of-page only, never inside a modal |
| **Filter bar / inset chrome** | Secondary toolbar | `.toolbar-secondary` (rare, ~5 places) |
| **Tab bar / segmented control** | Tabs | `.nav-tabs` + `.nav-link.active` (orange `#F77623` rail) |
| **Section title bar / group header** | Section header | `.section-header` (`#F6F6F6`, `32px`) |
| **Light-blue section header** | Light-blue section header | `.light-blue-section-header` (`#F0F5FA`, `32px`) |
| **Larger section label** | Secondary header | `.secondary-header` (`#F0F5FA`, `32px`) |
| **Card / tile / panel** | Card | `.rounded-borders` (default content wrapper, `10px` padding) |
| **Bordered container, no padding** | Bare card | `.rounded-border` |
| **Stat card / KPI tile** | Stats card | `.stats-bg` (`#F6F6F6`, `4px` padding) |
| **Generic content group** | Card mixin | `.card-mixin` (gray-100 fill + gray-300 border) |
| **Primary CTA button** | Primary button | `.btn .btn-primary` (gradient) |
| **Secondary / cancel / "ghost" button** | Secondary button | `.btn .btn-secondary` |
| **Text-only / inline link button** | Link button | `.btn .btn-link` |
| **Icon-only action button** | Icon button | `.btn .btn-icon` (most-used button overall) |
| **Icon button on dark surface** | Icon-for-dark | `.btn .btn-icon.btn-icon-for-dark` |
| **Destructive button** | Danger button | `.btn .btn-danger` (rare — only for irreversible actions) |
| **Floating action button (FAB) / chat-send** | Round button | `.btn .btn-primary.btn-round` (Matilda only) |
| **Filter chip / inset action** | Outline button | `.btn .btn-outline-secondary` |
| **Status pill / tag / chip** (filled) | Badge | `.badge .badge-{success/warning/danger/info/secondary}` |
| **Outline / lighter chip** | Outline badge | `.badge .badge-outline-{variant}` |
| **"Unassigned" / muted chip** | Muted badge | `.badge .badge-outline-muted` (dashed border) |
| **Count badge / number bubble** | Badge | `.badge .badge-{variant}` (numeric content) |
| **Text input** | Native input | `<input>` (`28px` tall, `4px` radius) |
| **Larger text input** | Form-control | `<input class="form-control" type="text">` (`32px`) |
| **Textarea** | Native textarea | `<textarea>` (same styling rules as input) |
| **Select / dropdown field** | Native select | `<select>` (same styling rules as input) |
| **Checkbox / radio** | Native | `<input type="checkbox">`, `<input type="radio">` (inline label, no extra spacing) |
| **Form field label** | Label | `<label>` (auto-spaced `4px` from input) |
| **Required field marker** (red asterisk) | Required asterisk | `<span class="ecs-required-asterisk">*</span>` (`16px`, `#BD341F`) |
| **Form group (label + input stack)** | Form group | `.form-group` (`8px` bottom margin) |
| **Horizontal label + input row** | Form row | `.form-row` (`10px` bottom margin) |
| **Vertical flex form column** | Form column | `.form-column` (flex-column, `4px` gap) |
| **Read-only field** | Readonly input | `<input class="readonly" readonly>` (gray bg) |
| **Required-field validation indicator** (empty required field after submit) | Red border + Required placeholder — **no popup** | `.error` on input + `placeholder="Required"`. Nothing else — no alert, no toast. |
| **Server-returned (API round-trip) error indicator** — duplicate name, conflict, permission denied, server-enforced uniqueness, save failure, anything from an API response's error payload | (optional) red border + popup toast | (optional) `.error` on the related input + `AlertsService.showErrorToast(response.error.message)` (single) or `showModelStateError(response.error.modelState)` (multi). See §6.4.2 |
| **Inline message / alert / banner** (in-context, persistent — connection-lost banners, contextual tips, advisories). **Not form validation.** | Inline alert | `.alert.alert-{danger/warning/info/success/secondary}` — see §6.4.1 |
| **Popup / floating / "snackbar" notification** (transient, top of screen — save confirmed, export ready, job complete) | Popup toast | `AlertsService.show{Success/Error/Warning/}Toast()` — renders `dx-toast` at top-center, `370px × auto`, `4px` radius, gray-gradient bg, color-coded FA icon left, × close top-right, z-index `9006`. See §6.4.2 |
| **Banner alert** (full-width, flush) | Banner alert | `.alert.alert-{variant}.mb-0.rounded-0` |
| **Tooltip on hover** | ngbTooltip | `[ngbTooltip]="'…'"` placement="top" — rendered: `12px` white text on near-black `rgba(0,0,0,0.9)`, `350px` max-width |
| **Modal / dialog** | Dialog container | `<dialog-container>` (white header, body, footer with action buttons) |
| **Confirmation dialog** | Modal sm | `<dialog-container>` opened with `windowClass: 'modal-sm'` (`400px`) |
| **Form dialog** (default) | Modal md | `<dialog-container>` with `windowClass: 'modal-md'` (`600px`) |
| **Larger form dialog** | Modal lg | `windowClass: 'modal-lg'` (`800px`) |
| **Tall dialog** | Full-height | `windowClass: 'modal-full-height'` (most common) |
| **Full-screen-ish dialog** | Modal xl | `windowClass: 'modal-xl'` (rare) |
| **Modal close (×) button** | Modal close | `.modal-close` with `<i class="fal fa-xmark">` |
| **Dropdown / select menu** | Dropdown menu | `.dropdown-menu` + `.dropdown-item` / `.dropdown-header` / `.dropdown-divider` |
| **Accordion / collapsible group** | Accordion | `<ngb-accordion>` (`32px` `#F6F6F6` header, chevron disclosure) |
| **Tree / outline view** | DevExtreme treeview | `<dx-tree-list>` / `<dx-treeview>` |
| **Data grid / spreadsheet** | DevExtreme grid | `<dx-data-grid>` (don't roll your own) |
| **Resizable side-by-side panels** | Splitter | `<as-split>` + `.as-split-gutter` |
| **Active item indicator (left rail)** | Active rail | `2px solid #F77623` left border |
| **Active tab indicator (bottom rail)** | Active rail | `2px solid #F77623` bottom border |
| **Row hover** | Light blue | `#C8E8FA` background (data-blue-200) |
| **Loading spinner / "thinking"** | Thinking indicator | `.thinking-indicator` (Matilda only — text-gradient sweep + breathing dot) |
| **Risk / severity pill** | Risk level | `.risk-level-{undefined/low/high-low/low-medium/high-medium/high/extreme-high}` |
| **Chart fills / legend swatches** | Data colors | data-color palette §2.5 (vibrant) — never chrome colors |
| **Chart tooltip** | DevExtreme/Chart.js default | rendered at z-index `9000` |
| **Avatar / user circle** | (custom) | circular `.btn-icon` + initials, no dedicated component |
| **Search input** | Native input + magnifier icon | `<input type="search">` adjacent to `<i class="fal fa-magnifying-glass">` |
| **Date picker** | DevExtreme | `<dx-date-box>` (z-index `100001`) |
| **Toast notification (any)** | DevExtreme toast via service | Always use `AlertsService` (`dx-toast`) — see §6.4.2. **Never** PrimeNG `<p-toast>` (legacy override only) and **never** hand-rolled. |
| **Mention / @ autocomplete** | angular-mentions | `[mention]` directive |
| **Code editor surface** | Ace / CodeMirror | use the existing wrappers; never roll your own |
| **Rich-text editor** | Quill | `<quill-editor>` |
| **Calendar / scheduling view** | FullCalendar | `<full-calendar>` |
| **Drag-resize dashboard** | Gridster | `<gridster>` (z-index `6000` for handles) |
| **Hyperlink** | Anchor | `<a>` (color `#1F68A8`, hover `#000000`) |
| **Server-returned validation summary** (API round-trip error: duplicate name, conflict, permission denied, save failure, multi-error model state) | Popup toast | `AlertsService.showErrorToast(response.error.message)` or `showModelStateError(response.error.modelState)` — joins messages with `<br/>`, renders one sticky error toast. See §6.4.2 |

---

## G. Anti-patterns to flag in Figma designs

When you see any of these in a design, **flag and replace** before sign-off.

| Anti-pattern | Why it's wrong | Fix |
|---|---|---|
| **Body text not 12px** (e.g. 14px, 16px) | elluminate is dense | Replace with 12px Roboto unless it's an explicit heading from §3.2 |
| **Border radius other than 4px** (8px, 6px, 12px, 16px) | universal radius is `4px` | Round to `4px`. Exceptions: badges (`10px` pill), avatars (`50%`) |
| **Sharp / heavy / colored box-shadows** (e.g. `0 4px 20px black`, blue glow) | only 3 shadows in §5 | Use card / overlay-lift / modal shadow |
| **Material Design colors** (deep teal, hot pink, purple primary) | not in palette | Replace with elluminate brand/status palette |
| **Tailwind blue** (`#3B82F6`, `#2563EB`) | not in palette | `#3B85C7` (brand) or `#1F68A8` (action) |
| **Bright generic green/red/yellow** (`#22C55E`, `#EF4444`, `#FACC15`) | not in palette | `#1F8915` / `#BD341F` / `#EEB12B` |
| **Outline buttons everywhere** | secondary is the alt action, outline is rare | Use `.btn-secondary` (ghost) for cancel/alt actions |
| **Modal header with dark gradient or color** | headers are white | `#FFFFFF` header, `32px`, `1px solid #E8E8E8` bottom border |
| **Modal close button as text** (`×`, `Close`, `[X]`) | use icon | `<i class="fal fa-xmark">` inside `.modal-close` |
| **`+` / `−` for expand/collapse** | use chevrons | `fal fa-chevron-right` (collapsed) / `fal fa-chevron-down` (expanded) |
| **Inline error text below input** | deprecated pattern | `.error` on input + the appropriate validation surface per §6.4 (popup toast for non-required violations; nothing for empty required fields beyond border + `placeholder="Required"`) |
| **Server-validation messages in an inline `.alert` at the top of the form** (e.g. "Duplicate name", "Permission denied", any API-returned error rendered as a banner) | wrong pattern | API round-trip errors surface in a **popup toast** (§6.4.2). Use `AlertsService.showErrorToast(response.error.message)` or `showModelStateError(response.error.modelState)`. Inline alerts are for persistent in-context banners the client knows about locally (e.g. "Connection lost"). |
| **Popup toast for an empty required field** ("Listing name is required.") | over-signaling | Required-field validation is **visual only** — red `.error` border + `placeholder="Required"`. No popup, no inline alert. |
| **Plain placeholder ("Enter name") on a field flagged as required-empty** | missing the swap | When a required field is empty after submit, the placeholder text **must** change to `"Required"`. The red border alone isn't enough on a never-touched field. |
| **Emoji icons** (✓ ✗ ★ ⚠️ ℹ️ 🔔 💾) | not in icon kit | Replace with Font Awesome (`fal fa-check`, `fal fa-xmark`, `fas fa-star`, etc.) |
| **Generic Material/Heroicons/Lucide SVGs** | not in icon kit | Replace with the equivalent FA icon (§A.2) |
| **Substituted, recolored, or re-traced logo** | brand mark is fixed | Use `elluminate_e-logo.svg` (or `.gif` fallback) from the package (§A.1) — never improvise a replacement. The SVG markup is embedded in §A.1 for Figma copy-paste. |
| **Logo on a light surface** | only the dark-surface variant ships | Don't place the logo on white/light pages — the white "e" silhouette disappears and only four disconnected colored squares remain. If the design calls for a light variant, **ask** before substituting |
| **Wordmark "elluminate" without the brand mark** | masthead pairs the two | Always show `elluminate_e-logo.svg` (`28px`) immediately before the wordmark, with `10px` gap |
| **Brand-mark colors used as UI chrome** (cyan `#05C2DF`, lavender `#928CFF`, brand-orange `#ED7514`) | mark-only colors | Don't introduce these in buttons, badges, text, or fills. They live in the logo only — UI colors stay in §2 |
| **`.dropdown-menu` style inside the masthead** | masthead has its own dropdown component | Use `<ecs-dropdown>` (§6.16.3) — fills `50px` height, `300px` fixed popover, `dx-list` content, different trigger styling |
| **`.nav-tabs` (43px, light) inside the dark masthead** | wrong tab variant | Use `.sub-nav-container-horizontal` (§6.16.4) — `49px`, white-on-dark, `3px` orange bottom rail |
| **Light filter pills on the dark masthead** (or dark pills on a light body) | wrong variant for the surface | Use `.filter-pill-dark` in the masthead (auto-applied), `.filter-pill-light` on light bodies — see §6.16.5 |
| **Manually authored "+N" overflow button** | dedicated component exists | Use `.ecs-pill-overflow-dropdown` — it handles measure-and-move logic and the dark-styled popover — see §6.16.6 |
| **Aqua `#4EE5FB` used outside the masthead** (badges, charts, page chrome) | masthead-only accent | This hex is reserved for the active filter icon in `.ecs-masthead-toolbar` (§6.16.9) |
| **Side drawer instead of the mega-menu** | not the pattern | Use the `850px` `<ecs-menu>` `dx-popover` flyout (§6.16.7); side drawers are not part of this system |
| **Notification dot (no count) on a masthead action** | use a real badge | Use `<span class="badge badge-{variant} toolbar-badge badge-margin">N</span>` — see §6.16.10 |
| **Standard `.btn-icon` (gray) on the masthead** | wrong icon-button variant | Use `.btn-icon.btn-icon-for-dark` so the icon paints near-white instead of gray |
| **Alternating row colors / zebra-striped tables** (e.g. every other row `#F5F5F5`) | not used in elluminate | Flatten all body rows to white. Hover-only highlight with `#C8E8FA` is automatic on `.table` — see §6.3 |
| **Rounded-full pill buttons** (anything but FAB) | not the pattern | Use `4px` radius; `.btn-round` is reserved for chat-send/FAB |
| **Wide / spacious padding** (`p-6`, `p-8`, `gap-8`) | platform is dense | Use 4 / 8 / 16 / 24 only |
| **Heading hierarchy mismatch** (h1 inside a card; h3 styled as 24px) | scale is fixed | Match §3.2 sizes exactly; `<h1>` is page-level only |
| **Disabled at 50% opacity** | spec is `0.4` | `opacity: 0.4` + `pointer-events: none` |
| **Custom hover color** (e.g. lighter blue, darker tint) | hover transitions are predefined | Don't invent — let component CSS handle it |
| **Z-index 999, 9999, 99999** | scale exists | Pick from §7 |
| **Filled icons everywhere** | weight communicates state | `fas` is reserved for **active/toggled-on states only** — default is `fal` |
| **Status colors used for chrome** (e.g. green Save button) | status ≠ action | Save = `.btn-primary` (blue); status colors are for badges/alerts |
| **Multiple primary buttons in one form** | one CTA per dialog | One `.btn-primary` per form/dialog footer; others are `.btn-secondary` |
| **Tab bar with bottom border + colored active text** | wrong active style | Use `2px` orange (`#F77623`) rail with `#f7f7f7` active background |
| **Bright colored borders** (e.g. blue input borders by default) | borders are gray | Default border is `#DDDDDD`; `.error` (`#CD482D`) and focus (`#1F68A8`) are the exceptions |
| **Mobile-only patterns** (bottom tab bars, pull-to-refresh, hamburger menus) | desktop-first | Replace with sidebar navigation, toolbars, dialogs |
| **Floating bottom action bar** | mobile pattern | Move actions into `.dialog-buttons` or page toolbar |
| **Drawer from side** | not idiomatic | Use a modal (`modal-lg2`) or `<as-split>` panel |
| **Inline alerts for server-returned errors** (anything from an API response — duplicates, conflicts, permission, save-failed) | wrong alert pattern | Server messages → **popup toasts** (§6.4.2). Inline `.alert` blocks (§6.4.1) are for **persistent client-known messages only** — connection lost, advisories, tips. |
| **Hand-rolled toast / using PrimeNG `<p-toast>` / using native `alert()`** | wrong toast source | Always go through `AlertsService.show{Success/Error/Warning/}Toast()` — it wraps DevExtreme `dx-toast` with the correct defaults (`5000ms`, top-center, sticky errors). |
| **Stacking more than 3 toasts on screen** at once | overflow | Group related events into one summary toast ("3 jobs complete — click for details"). |
| **Toast with interactive controls** (form fields, multi-step actions) | wrong container | Move the interaction into a modal (§6.6). Toasts allow text + at most one inline link. |
| **Using `#EC7614` (toast warning hue) outside a toast** | toast-only color | The standard warning yellow elsewhere is `#EEB12B` (§2.3). `#EC7614` (`$carrot-orange-F`) is reserved for the toast warning icon. |
| **Inputs taller than 32px** | sizing is fixed | `28px` native / `32px` form-control / never taller |
| **Buttons taller than 40px** | sizing is fixed | `24` (default) / `32` (md) / `40` (lg) — never larger |
| **Light-mode card on dark page background** | cards live on light surfaces | If a dark surface is needed, only the masthead / `.toolbar` are dark; rest stays light |

---

## H. Reference: full design system specification

Everything below is the **technical reference data** the audit / translation steps above point to. PMs reviewing a Figma design rarely need to read these in detail — the AI uses them to look up exact values, dimensions, and class definitions.

### Stack & libraries

The system is intentionally a *blend* of well-known UI toolkits, customized to feel like one product. Knowing what comes from where helps you pick the right component when there are several ways to do the same thing.

#### Framework

| What | Version | Where it shows up |
|---|---|---|
| **Angular** | 12.2 | the entire app (Angular 8 + AngularJS exist in the legacy shell only) |
| **NGXS** | 3.7 | global state — Data Central, dashboards, panel filters, study, RBQM, etc. |
| **RxJS** | 6.5 | every observable / async stream |
| **TypeScript** | 4.3 | sources |

#### UI components (the visible widgets)

| What | Version | What it provides | Class / selector prefix |
|---|---|---|---|
| **Bootstrap** | 4.6 | grid (`.row`, `.col-*`), modal (`.modal`, `.modal-dialog`), tabs (`.nav-tabs`), buttons (`.btn`), badges (`.badge`), forms (`.form-group`, `.form-control`), utilities (`.w-*`, `.d-*`) — heavily overridden | `.modal-*`, `.btn-*`, `.badge-*`, `.form-*`, `.row`, `.col-*` |
| **ng-bootstrap** | 6.0 | Angular wrappers for Bootstrap behaviors — modals, tooltips, popovers, accordion, dropdowns | `[ngbTooltip]`, `[ngbPopover]`, `<ngb-modal>`, `<ngb-accordion>`, `[ngbDropdown]` |
| **ngx-bootstrap** | 5.6 | older AngularJS-style Bootstrap directives (legacy areas) | `[uib-*]`, `[bsDatepicker]` |
| **DevExtreme** | 21.2 | data-heavy widgets — datagrid, tree-list, popup/popover, form, tag-box, select-box, switch, calendar, chart, range selector, htmleditor, mention, list, treeview, tabs, accordion, toolbar, toast, scrollview | `dx-*` (e.g. `<dx-data-grid>`, `<dx-popup>`, `<dx-form>`) |
| **PrimeNG** | 9.0 | additional widgets — table, tree, autocomplete, dynamic dialog, overlay panel, tiered menu, picklist, breadcrumb, toast | `p-*` (e.g. `<p-table>`, `<p-autocomplete>`, `<p-tree>`) |
| **angular-tree-component** | 11.0 | hierarchical tree (used in some legacy spots; new code uses DevExtreme treeview) | `<tree-root>` |
| **jexcel-pro** | 7.9 | spreadsheet-style grid (specifications, mapper) | `<jexcel>` |

#### Charts & editors

| What | Version | Where it's used |
|---|---|---|
| **Chart.js** | 3.6 | KRI / risk distribution charts |
| **Ace editor** | 1.4 (+ ace-diff) | code/SQL editing in mapper, validator, exporter |
| **CodeMirror** | 6.0 (+ merge) | newer code editing surfaces |
| **Quill** | 1.3 | rich-text fields (annotations, notes) |
| **markdown-it** | 12.3 | markdown rendering for assist messages, help, docs |
| **diff-match-patch** | 1.0 | inline diff display |
| **FullCalendar** | 5.10 | scheduling views |

#### Layout & UX

| What | Version | What it provides |
|---|---|---|
| **angular-gridster2** | 8.4 | drag-resize dashboards (Data Central, RBQM, charts) |
| **angular-split** | 3.0 | resizable side-by-side panels (`<as-split>`) |
| **ngx-infinite-scroll** | 10.0 | infinite-scroll lists |
| **ngx-virtual-scroller** | 4.0 | virtualized large lists |
| **angular-mentions** | 1.4 | `@mention` autocomplete |
| **balloon.css** | bundled | attribute-driven CSS-only tooltips (`[data-balloon-pos]`) |

#### Time & utilities

| What | Use |
|---|---|
| **moment** + **ngx-moment** | dates, durations, formatting |
| **lodash.clonedeep** / **lodash.isequal** | deep object utilities |
| **deep-equal** | structural equality checks |
| **dompurify** | sanitizing user-supplied HTML |
| **exceljs**, **xlsx**, **file-saver** | export / download |

#### Real-time

| What | Use |
|---|---|
| **ng2-signalr** + **signalr** | live job/notification updates |

#### Icons & fonts

| What | Use | How to use |
|---|---|---|
| **Font Awesome 7 Pro — Light** ★ | **default** for almost every icon in the app — toolbar buttons, badges, disclosure chevrons, table actions, dialog close buttons, etc. | `<i class="fal fa-{name}">` |
| **Font Awesome 7 Pro — Solid** | **only** for toggled-on / active states — a filter icon turning solid when filters are applied, a star icon when favorited, a bell when notifications are on, a checkbox when checked. | `<i class="fas fa-{name}">` |
| **Font Awesome 7 Pro — Regular** | rare, for specific decorative cases | `<i class="far fa-{name}">` |
| **Font Awesome 6 Brands** | brand icons (logos for outside vendors) | `<i class="fab fa-{name}">` |
| **Font Awesome 6 Duotone / Sharp** | occasional decorative icons | `<i class="fad fa-{name}">`, `<i class="fass fa-{name}">` |
| **PrimeIcons** | inside PrimeNG widgets only | `<i class="pi pi-{name}">` |
| **Custom icons** | platform-specific glyphs (e.g. eClinical brand marks) | `<i class="ec ec-{name}">` (custom font in `assets/fonts/fontawesome-custom-icons/`) |
| **Roboto** (Google Fonts) | UI typeface | inherited on `<body>` |

> **Icon weight rule (memorize this):** `fal` (light) by default, `fas` (solid) for toggled-on / active states. Production uses `fal` in 39+ component templates vs `fas` in 12 — the ratio reflects the rule. **An icon turning from light to solid is the visual cue for "this is now on / active / engaged".**

> **Color treatment for solid icons:** `fas` icons render in **primary blue (`#1F68A8`)** by default — that's the standard "this is engaged / active" cue used in toolbars, icon buttons, dropdowns, and lists. **Don't override with semantic colors** (no yellow on a favorited star, no red on a liked heart) — the blue treatment handles all toggle states. Light icons (`fal`) inherit their container's text color (gray, white, etc.); the **weight + color shift together** is the visual cue: thin gray → fat blue.
>
> **Exceptions where solid icons inherit the container's color instead of going blue:**
> - Inside `.btn-primary` / `.btn-danger` (icon picks up the white button text)
> - Inside `.badge` (icon matches the badge's text color)
> - On dark surfaces (`.btn-icon-for-dark`, `.toolbar` masthead) — icon goes near-white (`#F6F6F6`)

> **Disclosure indicators** for collapse/expand (accordions, lists, panels) use light chevrons too — `fal fa-chevron-right` when collapsed and `fal fa-chevron-down` when expanded (or rotate the right-chevron 90° via CSS). **Never use `+` / `−` text characters** for expand/collapse.

> **Common icons:** `fa-chevron-down`, `fa-chevron-right`, `fa-chevron-up`, `fa-chevron-left`, `fa-caret-down`, `fa-caret-right`, `fa-gear`, `fa-filter`, `fa-magnifying-glass`, `fa-pen-to-square`, `fa-floppy-disk`, `fa-rotate-right`, `fa-xmark`, `fa-ellipsis-vertical`, `fa-trash`, `fa-bell`, `fa-user`, `fa-circle-exclamation`, `fa-circle-info`, `fa-triangle-exclamation`, `fa-check`, `fa-clock`, `fa-arrow-right`, `fa-plus`, `fa-bars`. Always pair with the `fal` (default) or `fas` (active) class.

---

## 1. Quick Reference

| What it is | Value | How to apply |
|---|---|---|
| **Default font** | Roboto, then Helvetica Neue / Helvetica / Arial | inherited on `<body>` |
| **Body text size** | `12px` | inherited on `<p>`, `.text`, `<label>` |
| **Default text color** | `#1A1A1A` (near-black) | inherited |
| **Primary brand color** | `#3B85C7` (medium blue) | `.bg-info`, primary surfaces |
| **Primary action / link color** | `#1F68A8` (deep blue) | `.text-primary`, `<a>`, `.btn-link` |
| **Header / masthead background** | `#182B3B` (very dark blue) | top navigation bar |
| **Default border color** | `#DDDDDD` (light gray) | inputs, tables, dividers |
| **Border radius** | `4px` | every surface (buttons, inputs, cards, modals) |
| **Input height** | `28px` | native inputs |
| **Form-control height** | `32px` | `.form-control[type="text"]` |
| **Button height (default)** | `24px` | `.btn` |
| **Row height (table/list body)** | `22px` | `<tbody> <tr>` |
| **Row height (header)** | `32px` | `<thead> <tr>`, `.section-header` |
| **Toolbar height** | `32px` | `.toolbar` |
| **Top header (masthead) height** | `50px` | `<header>` |
| **Sidebar width** | `220px` | side nav |
| **Modal content padding** | `20px` | `.dialog-content` |
| **Modal button row padding** | `10px` | `.dialog-buttons` |
| **Form group bottom margin** | `8px` | `.form-group` |
| **Form row bottom margin** | `10px` | `.form-row` |
| **Standard transition** | `all 300ms ease` | applied to buttons, hovers |
| **Disabled opacity** | `0.4` | use `disabled` attribute or `.is-disabled` |
| **Modal backdrop** | `rgba(0,0,0,0.4)` | applied automatically on overlay |
| **Active item indicator** | `2px` orange (`#F77623`) rail on left or bottom | applied via `.active` |

---

## 2. Color System

Colors are grouped by **what they're for**, not by their hue. Hex value first, then the class to apply.

### 2.1 Brand & surface colors (the canvas)

These are the calm, neutral colors that make up the UI chrome — buttons, links, page backgrounds, borders, and text.

| Purpose | Hex | Class to apply |
|---|---|---|
| **Lightest blue tint** — page section backgrounds, info callouts, hover on secondary buttons | `#F0F5FA` | `.bg-highlight-light` (or use directly as bg) |
| **Light blue tint** — active state on secondary buttons, highlight cell bg | `#CADDEE` | `.bg-highlight` |
| **Primary brand blue** — informational/info bars, brand surfaces | `#3B85C7` | `.bg-info`, `.text-info` |
| **Primary action blue** — links, primary text, focus outlines, secondary button text | `#1F68A8` | `.text-primary`, `<a>` |
| **Hover deep blue** — secondary button hover text/border | `#215887` | (used internally by `.btn-secondary:hover`) |
| **Active deep blue** — secondary button active text/border | `#1E4060` | (used internally by `.btn-secondary:active`) |
| **Masthead / dark navigation blue** — top header bar | `#182B3B` | applied to `<header>` / `.masthead` |

### 2.2 Surface neutrals (grays)

| Purpose | Hex | Class to apply |
|---|---|---|
| **White** — surfaces, secondary button background | `#FFFFFF` | `.bg-white` (Bootstrap) |
| **Off-white** — secondary toolbar background, page section background, table header row | `#F6F6F6` | `.bg-gray-light` (or use directly) |
| **Light gray** — subtle borders, light-tone badges | `#E8E8E8` | `.border-light` |
| **Default border** — inputs, tables, dividers | `#DDDDDD` | `.border-top`, `.border-right`, `.border-left`, `.border-bottom` |
| **Mid-tone gray** — scrollbar thumb (resting) | `#BBBBBB` | (scrollbar only) |
| **Muted text** — secondary/help text, placeholders, muted labels | `#767676` | `.text-gray` |
| **Stronger muted** — "unknown" status, neutral icons | `#606060` | `.text-unknown` |
| **Input text color** — default text inside inputs | `#4B4B4B` | (default on inputs) |
| **Headings on light surfaces** | `#333333` | (default on `<h1>` etc. in sections) |
| **Default body text** — the primary text color | `#1A1A1A` | inherited everywhere |
| **Pure black** — link hover, balloon tooltip background | `#000000` | (used internally) |

### 2.3 Status / semantic colors

Use these for state — success/warning/error/info — never as decoration.

#### Success (green)

| Purpose | Hex | Class |
|---|---|---|
| **Success surface (light)** | `#EDF6EA` | (use as background) |
| **Success border** | `#6BB859` | accent on outline-success |
| **Success — primary** (text, fills, badges) | `#1F8915` | `.bg-success`, `.text-success`, `.badge-success` |
| **Success text on light bg** | `#206117` | text inside `.badge-outline-success` |

#### Warning (yellow)

| Purpose | Hex | Class |
|---|---|---|
| **Warning surface (light)** | `#FFF2DE` | (use as background) |
| **Warning — primary** (badges, fills) | `#EEB12B` | `.bg-warning`, `.bg-critical`, `.badge-warning` |
| **Warning hover/active** | `#D49C21` | accent on outline-warning |
| **Warning text on light bg** | `#634A18` | text inside `.badge-outline-warning` |
| **Accessible warning text on white** (WCAG-compliant) | `#BC8C24` | use this when contrast matters |

#### Error / danger (red)

| Purpose | Hex | Class |
|---|---|---|
| **Error surface (light)** | `#FEF1ED` | (use as background) |
| **Toast error background** | `#FACDC2` | (PrimeNG toast) |
| **Validation error border / inline error text** | `#CD482D` | applied via `.error` or `.is-invalid` on inputs |
| **Error — primary** (text, fills, badges, required asterisks) | `#BD341F` | `.bg-danger`, `.text-danger`, `.badge-danger` |
| **Error text on light bg / hover** | `#9C2D1B` | text inside `.badge-outline-danger` |

#### Information (indigo)

| Purpose | Hex | Class |
|---|---|---|
| **Information surface (light)** | `#F5F2FC` | (use as background) |
| **Information accent** | `#DBD3F3` | light tint for info contexts |
| **Information — primary** | `#3F42C5` | reserved for info messaging |

### 2.4 Gradients (used on buttons and toolbars)

Gradients are how primary buttons get their depth. Apply as `background-image`; never use a solid color where a gradient is expected.

| Purpose | Gradient | When to use |
|---|---|---|
| **Primary button (default)** | `linear-gradient(180deg, #3B85C7 0%, #215887 100%)` | applied automatically by `.btn-primary` |
| **Primary button (hover & focus)** | `linear-gradient(180deg, #1F6CA8 0%, #1E4060 100%)` | hover/focus state of `.btn-primary` |
| **Primary button (active / page toolbar bg)** | `linear-gradient(180deg, #215887 0%, #18293B 100%)` | active state, `.toolbar` background |
| **Danger button (default)** | `linear-gradient(180deg, #D55B42 0%, #9C2D1B 100%)` | `.btn-danger` |
| **Danger button (hover)** | `linear-gradient(180deg, #C4341F 0%, #6F2416 100%)` | `.btn-danger:hover` |
| **Subtle blue surface (light)** | `linear-gradient(180deg, #F0F4FA 0%, #A3BDE2 100%)` | rare; decorative panels |

### 2.5 Data colors (for charts, status pills, user-chosen categories)

Data colors are vibrant and **only** for content the user owns: charts, legend swatches, category badges, status colors a user selects. **Never** use these for chrome (buttons, page backgrounds, borders).

Each family has a "highlight" stop for data emphasis. Use the listed hex directly — these don't have semantic class names because they're per-instance.

| Family | Light highlight (200) | Strong highlight (500/600) | Notes |
|---|---|---|---|
| **Red** | `#FFCBC0` | `#E6190F` | high attention |
| **Orange** | `#FFCDAE` | `#F77623` | **also the active-item rail color** |
| **Yellow** | `#FAD50F` | `#877317` |  |
| **Lime** | `#AAEB36` | `#5E7E25` |  |
| **Green** | `#70F26F` | `#22872A` | (different from semantic success green) |
| **Aqua** | `#47EEE3` | `#21837E` |  |
| **Blue** | `#C8E8FA` | `#1579C1` | also row-hover background |
| **Indigo** | `#E0D0FD` | `#6648F1` |  |
| **Violet** | `#E7CEFD` | `#984AF2` |  |
| **Fuchsia** | `#FFC7EC` | `#D813B0` |  |

> The full 100–900 ramps for each family are defined in `utils/color-palette.scss`. The visual reference (`index.html`) shows the canonical highlight stops.

### 2.6 Risk levels (semantic data colors)

Used for risk scoring across the platform. Apply the matching class — never invent new risk levels.

| Risk level | Background | Text | Class |
|---|---|---|---|
| Undefined / not set | `#E8E8E8` (light gray) | dark | `.risk-level-undefined` |
| Low | `#1F8915` (green) | white | `.risk-level-low` |
| High-Low | `#206117` (deep green) | white | `.risk-level-high-low` |
| Low-Medium | `#EEB12B` (yellow) | black | `.risk-level-low-medium` |
| High-Medium | `#D49C21` (deep yellow) | black | `.risk-level-high-medium` |
| High | `#BD341F` (red) | white | `.risk-level-high` |
| Extreme-High | `#6F2416` (deep red) | white | `.risk-level-extreme-high` |

---

## 3. Typography

### 3.1 Font family

```
Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif
```

Inherited on `<body>` — apply nothing, you get this everywhere.

For icons: **Font Awesome 7 Pro Light** (`<i class="fal fa-...">`) by default; switch to solid (`<i class="fas fa-...">`) only for toggled-on / active states.

### 3.2 Type scale (all in pixels)

The UI is dense by design. Body text is **12px**. The heading scale is compressed compared to default Bootstrap.

| What | Size | Line height | Weight | Element / class |
|---|---|---|---|---|
| Heading — extra large | `30px` | `36px` | 400 (bold inside `.section`) | `<h1>` |
| Heading — large | `19px` | `24px` | 400 | `<h2>` |
| Heading — medium | `16px` | `20px` | 400 | `<h3>` |
| Heading — small | `14px` | `18px` | 400 | `<h4>` |
| Heading — extra small | `12px` | `18px` | 400 | `<h5>` |
| Heading — tiny | `10px` | `18px` | 400 | `<h6>` |
| **Body** (default) | `12px` | `18px` (1.5) | 400 | `<p>`, `.text`, `<body>` |
| Form label | `12px` | `12px` (1.0) | 400 | `<label>` |
| Section header bar | `12px` | — | 700 | `.section-header` |
| Minor header | `14px` | — | 400 | `.secondary-header` |
| Button text | `12px` | — | 500 | `.btn` |
| Table header cell | `11px` | — | 400 | `<th>` inside `.table` |
| Table body cell | `12px` | — | 400 | `<td>` inside `.table` |
| Input / textarea | `12px` | `18px` | 400 | `<input>`, `<textarea>` |
| Alert text (`.alert`) | `12px` | `1.3` | 400 | `.alert.alert-{success\|info\|warning\|danger\|primary\|secondary}` |
| Tooltip (Bootstrap-style) | `12px` | normal | 400 | `.tooltip-inner` |
| Tooltip (balloon, attribute-driven) | `10px` | — | 400 | `[data-balloon-pos]` |
| Dropdown menu item | `12px` | — | 400 | `.dropdown-item` |
| Tiny meta label | `10px` | — | 400 | `.ecs-label` |

### 3.3 Headings inside content sections

When you wrap content in a `<section>` or `.section-content`, headings automatically get spacing rules:
- `margin-top: 24px`
- `margin-bottom: 8px`
- `font-weight: bold`

Paragraphs get `margin-bottom: 16px`. Lists get `margin-bottom: 16px` and `padding-left: 24px`.

### 3.4 Required field indicator

For required inputs, prefix the label with a red asterisk:

```html
<label><span class="ecs-required-asterisk">*</span>Field name</label>
```

Renders as a `16px` red asterisk in `#BD341F`, with `4px` right margin.

---

## 4. Spacing & Sizing

### 4.1 Standard heights (memorize these)

| Surface | Height | Where it shows up |
|---|---|---|
| Top masthead / app header | `50px` | `<header>` of every screen |
| Page toolbar | `32px` | `.toolbar`, `.toolbar-secondary`, `.toolbar-minimal` |
| Tabs row | `43px` | `.nav-tabs` |
| Section header bar | `32px` | `.section-header`, `.secondary-header` |
| Table / list **header** row | `32px` | `<thead> <tr>` |
| Table / list **body** row | `22px` | `<tbody> <tr>` |
| Native input | `28px` | `<input>`, `<textarea>` |
| Form-control input | `32px` | `.form-control[type="text"]` |
| Button — small (default) | `24px` | `.btn`, `.btn-sm` |
| Button — medium | `32px` | `.btn-md` |
| Button — large | `40px` | `.btn-lg` |
| Sidebar | `220px` width | left navigation |

### 4.2 Padding & margin patterns

| Pattern | Value | Class |
|---|---|---|
| Form group spacing (vertical stack) | `8px` bottom margin | `.form-group` |
| Form row spacing (horizontal label + field) | `10px` bottom margin | `.form-row` |
| Modal content padding | `20px` | `.dialog-content` |
| Modal button row padding | `10px` (right-aligned, `1px` top border) | `.dialog-buttons` |
| Form group caption (inside DevExtreme forms) | `10px` padding, `#E8F1F5` background | (DevExtreme `dx-form-group-caption`) |
| Section content padding | `20px` | `.tab-content` |
| Toolbar padding | `0 10px` | `.toolbar`, `.toolbar-secondary` |
| Card padding | `10px` | `.rounded-borders` |
| Stats card padding | `4px` | `.stats-bg` |
| Section header indent | `12px` left padding | `.section-header` |

### 4.3 Border radius

**`4px` everywhere.** Buttons, inputs, cards, modals — all `4px` radius. Badges are the one exception (`10px` for pill shape).

### 4.4 Modal sizes

Pass these in `NgbModalOptions.windowClass` when opening a modal — they're not written as static HTML class attributes.

Listed in **production-usage order**:

| Class | Max width | Height | When to use |
|---|---|---|---|
| `.modal-full-height` | (any width) | full minus 56px | the most common — for tall dialogs (SCE files, specifications, audit, deliverable assignments) |
| `.modal-lg` | `800px` | auto | larger forms (recent-exports, importer domains) |
| `.modal-md` | `600px` | auto | the default — typical edit / confirmation dialogs that don't set a windowClass |
| `.modal-xl` | full xl width | full minus 80px | rare — full-screen-ish dialogs |
| `.modal-lg2` | `800px` | full minus 80px | rare — tall larger forms |
| `.modal-sm` | `400px` | auto | rare — small confirmations |

Modal backdrop is always `rgba(0,0,0,0.4)`.

```ts
// Open a modal with a size:
this.modalService.open(MyDialogComponent, {
    windowClass: 'modal-full-height',
    backdrop: 'static'
});
```

### 4.5 Utility classes (Bootstrap extensions)

**Width helpers (in addition to Bootstrap defaults):** `.w-10`, `.w-20`, `.w-30`, `.w-33`, `.w-40`, `.w-50`, `.w-60`, `.w-65`, `.w-70`, `.w-80`, `.w-100` — each sets `width: <n>%`.

**Gap helpers (for flex/grid containers):**
- `.gap-1` → `4px`
- `.gap-2` → `8px`
- `.gap-3` → `16px`
- `.gap-4` → `24px`

**Border helpers:** `.border-top`, `.border-right`, `.border-left`, `.border-bottom` — each adds `1px solid #DDDDDD`. `.border-light` overrides the color to `#E8E8E8`.

**State utilities:** `.h-unset`, `.overflow-ellipsis`, `.text-preline`, `.text-break-all`, `.disabled` (`opacity: 0.5`).

### 4.6 Responsive breakpoints

| Name | Range |
|---|---|
| Phone | `≤ 767px` |
| Tablet | `≤ 900px` |
| Desktop | `≥ 901px` |

The platform is **desktop-first** — phone and tablet are exception cases, not primary targets.

---

## 5. Shadows

Listed in **production-usage order**. Two shadows cover ~95% of the app — pick one of these by default.

| # | Purpose | Value | Apply on |
|---|---|---|---|
| 1 | **Card / tile shadow** — the dominant shadow, used in 17+ components (study cards, snapshot cards, analytics tiles, dashboard cards, etc.) | `0 1px 2px rgba(0, 0, 0, 0.2)` | direct (not a class — apply via `box-shadow`) |
| 2 | **Overlay / popover lift** — used in 10+ places: DevExtreme popups & toasts, PrimeNG toasts & overlay panels & tiered menus, sticky table headers, smart-review toolbar | `0 3px 15px rgba(0, 0, 0, 0.4)` | direct |
| 3 | **Modal** — every dialog / modal automatically gets this via the modal-popup mixin | `0 20px 40px rgba(0, 0, 0, 0.4)` | applied automatically to `.modal-content` |

#### Specialized shadows (rare)

| Purpose | Value | Where it's used |
|---|---|---|
| Dropdown menu | `0 7px 12px 1px rgba(0, 0, 0, 0.4)` | only `dx-select-box` and `mention-list` (2 places) |
| Top-normal (upward) | `0 -1px 2px rgba(0, 0, 0, 0.2)` | bottom-anchored bars (1 place — patient-profile-range) |

> **AI rule:** for new cards, panels, and tiles, use the **card / tile shadow** (`0 1px 2px rgba(0,0,0,0.2)`). For floating overlays, popovers, and toasts, use the **overlay / popover lift** (`0 3px 15px rgba(0,0,0,0.4)`). Modals get the modal shadow automatically — don't apply it manually.

---

## 6. Components

> **Reach for the most-used variant first.** The patterns in this section are listed in **production-usage order** — the variant at the top is what almost every screen uses; later variants exist for specific cases. If you're generating a new screen, you almost certainly want the first option in each list.

### 6.1 Buttons

The default button is the **Narrow button** — `24px` tall, `12px` text at weight 500, `0px 8px` padding, `4px` radius. Hover and focus transition over `300ms`.

#### Primary variants (use these by default)

These four cover ~95% of buttons in the product, listed by usage frequency:

| # | Variant | What it's for | Class |
|---|---|---|---|
| 1 | **Icon-only** | toolbar/grid actions, dialogs, anywhere space is tight (most common button by far) | `.btn .btn-icon` |
| 2 | **Link** | inline action that reads like a link (open profile, edit row, view details) | `.btn .btn-link` |
| 3 | **Secondary / Ghost** | the alternative or cancel action — paired with a primary; also the dialog-container default close-button | `.btn .btn-secondary` *(or `.btn-ghost`, alias)* |
| 4 | **Primary** | the main action on a form / dialog footer (Save, Submit, Apply) | `.btn .btn-primary` |

```html
<button class="btn btn-icon" aria-label="Settings"><i class="fal fa-gear"></i></button>
<button class="btn btn-link">Open profile</button>
<button class="btn btn-secondary">Cancel</button>
<button class="btn btn-primary">Save</button>
```

#### Specialized variants (use only when needed)

| Variant | When to use | Class |
|---|---|---|
| **Outline-secondary** | very light bordered button inside an inset toolbar — used in 50+ files for filter rows | `.btn .btn-outline-secondary` |
| **Outline** | flat gray-bordered chrome (legacy) | `.btn .btn-outline` |
| **Danger** | destructive actions (delete, remove) — only when the action cannot be undone (rare) | `.btn .btn-danger` |
| **Round (icon-only)** | chat-send / FAB-style circular button (Matilda assistant) — rare | `.btn .btn-primary .btn-round` |

#### Sizes & states

**Sizes:** `.btn-sm` = `24px` (default), `.btn-md` = `32px`, `.btn-lg` = `40px`.

**Disabled:** add `disabled` attribute. Renders at `opacity: 0.4` with `pointer-events: none`.

**On dark surfaces:** add `.btn-icon-for-dark` to icon buttons — the icon paints near-white instead of gray (used in masthead and dark page toolbars).

**Focus:** primary and secondary buttons get a `1px white` outline + `3px` blue ring (`#1F68A8`) automatically.

### 6.2 Forms

#### Inputs

Default input rules — applied automatically when you use a native `<input>` or `<textarea>` inside the app:

- Border `1px solid #DDDDDD`, radius `4px`
- Height `28px` (32px for `.form-control[type="text"]`)
- Text color `#4B4B4B`, font Roboto, `12px`
- On focus: border becomes `#1F68A8` (no glow, no extra outline)
- On disabled: opacity `0.4`
- Read-only fields: add class `.readonly` → background `#F6F6F6`, click-disabled

#### Validation — three paths, picked by the kind of error

> **The old `.inline-error` / `.inline-warning` text-below-input pattern is deprecated.** Validation now follows three paths depending on what failed.

| Error type | What the user sees | How to implement |
|---|---|---|
| **Empty required field** (after submit) | Red `.error` border + the placeholder reads `"Required"`. **No popup, no inline alert.** | Add `.error` to the input + swap the `placeholder` attribute to `"Required"`. That's it. |
| **Server-returned (API round-trip) error** — duplicate name, conflict, permission denied, server-enforced uniqueness, save failure, anything in the API response's error payload | Optionally a red `.error` border on the offending input(s) + a **popup toast** with the server message(s). | In the API callback, on error: optionally apply `.error` to the relevant input(s) + call `this.alerts.showErrorToast(response.error.message)` (single) or `this.alerts.showModelStateError(response.error.modelState)` (multi — joins with `<br/>`). See §6.4.2. |
| **Persistent system-state advisory** (connection lost, blocking error banner) | A `.alert.alert-{variant}` block inside the form / dialog body. | `<div class="alert alert-danger">Connection lost…</div>`. See §6.4.1. |

| State | Apply | Result |
|---|---|---|
| Required-field error border | `.error` on input + `placeholder="Required"` | red `#CD482D` border, "Required" hint inside the field |
| Server-error border on related input | `.error` or `.is-invalid` on the input (optional but recommended when the error points at a specific field) | red `#CD482D` border, red text |
| Server-validation message (round-trip) | `AlertsService.showErrorToast(msg)` (or `showModelStateError(modelState)`) | top-center sticky toast with the server's error message(s) |
| Persistent state advisory | `<div class="alert alert-{variant}">…</div>` | full-width Bootstrap-style alert (only for non-validation messages) |

```html
<!-- Path A — empty required field: red border + Required placeholder, nothing else -->
<div class="form-group">
    <label><span class="ecs-required-asterisk">*</span>Listing name</label>
    <input type="text" class="error" placeholder="Required" />
</div>
```

```ts
// Path B — server-returned (API round-trip) validation.
// In the .subscribe / .then error handler of an API call:

this.listingApi.save(payload).subscribe({
    next: () => this.alerts.showSuccessToast('Saved.'),
    error: (response) => {
        // Single server message (e.g. "A listing named 'Subjects' already exists."):
        this.alerts.showErrorToast(response.error.message);

        // OR — multi-error model state from a .NET API response
        // (joined with <br/> into one sticky toast):
        this.alerts.showModelStateError(response.error.modelState);

        // Optionally highlight the offending field(s):
        if (response.error.field === 'name') this.nameInputClasses.error = true;
    }
});
```

> The toast surfaces messages **the server returned** — duplicate names, conflict errors, permission denials, save failures, business-rule violations the server enforces. The client doesn't make these messages up; it just relays the API response.

#### Form layout

```html
<div class="form-group">
    <label><span class="ecs-required-asterisk">*</span>Study name</label>
    <input type="text" placeholder="Enter name" />
</div>

<div class="form-row">
    <label>Country</label>
    <select>...</select>
</div>
```

`.form-group` stacks fields with `8px` bottom margin. `.form-row` lays them out horizontally with `10px` gap.

#### Label spacing (label-over-input pattern)

When a `<label>` sits directly above its input (the typical vertical form layout), the label gets **`4px` bottom margin** so the text doesn't crash into the input border. This is applied automatically — any label whose next sibling is an `<input>` (excluding checkbox/radio), `<textarea>`, `<select>`, or `.form-control` becomes block-level with `margin-bottom: 4px`.

You can achieve the same spacing two equivalent ways:

| Pattern | When to use |
|---|---|
| `<label>Name</label><input />` (label margin-bottom) | the default — works inside any `.form-group` |
| `<div style="display:flex;flex-direction:column;gap:4px">…</div>` (container gap) | when you need a flex-column wrapper for tight stacking; or use the `.form-column` helper class |

```html
<!-- Implicit (default behavior) — label gets 4px margin-bottom automatically -->
<div class="form-group">
    <label>Study name</label>
    <input type="text" />
</div>

<!-- Explicit flex-column with gap -->
<div class="form-column">
    <label>Study name</label>
    <input type="text" />
</div>
```

Checkbox / radio labels (where the input is inside the label) are **not** affected — they remain inline.

### 6.3 Tables

Apply class `.table` to a `<table>` element. The system styles the header and body automatically.

| Detail | Value |
|---|---|
| Header row height | `32px` |
| Header background | `#F6F6F6` |
| Header cell text | `11px`, weight 400 |
| Header cell padding | `4px 8px` |
| Header cell border | `1px solid #DDDDDD` (bottom is `2px`) |
| Body row height | `22px` |
| Body cell text | `12px` |
| Body cell padding | `4px 8px` |
| Body cell border | `1px solid #DDDDDD` |
| Default text color | `#1A1A1A` |
| Body row background | **white** (`#FFFFFF`) — every row, no alternation |
| Body row **hover** background | `#C8E8FA` (data-blue-200) — applied automatically to every `.table` |

**Modifiers:**
- `.table-no-borders` — removes all borders

> **No alternating row colors.** Every body row sits on white; hover paints `#C8E8FA`. The legacy `.table-alt-row-color` class has been removed — don't use it, don't request it in Figma designs, don't manually stripe rows with `nth-child(even)`. If a designer hands you a Figma table with zebra striping, flatten it to white.

```html
<table class="table">
    <thead><tr><th>Subject</th><th>Site</th><th>Status</th></tr></thead>
    <tbody>
        <tr><td>SUB-0001</td><td>Site 12</td><td><span class="badge badge-success">Complete</span></td></tr>
        <tr><td>SUB-0002</td><td>Site 03</td><td><span class="badge badge-warning">In Review</span></td></tr>
    </tbody>
</table>
```

### 6.4 Alerts

The system has **two** alert patterns plus a third "no alert" path for required-field validation. Pick by intent:

| Pattern | Use when | Class / API |
|---|---|---|
| **No alert — visual only** | A **required field is empty** when the user submits. The red border (`.error`) + `placeholder="Required"` *is* the message. Adding a popup or inline alert on top of that is noisy. | `.error` on input + `placeholder="Required"` |
| **Popup toast** (§6.4.2) | The message **came back from the server** — API response success / error / warning (save confirmed, export ready, **duplicate name**, **permission denied**, **conflict / uniqueness violation**, **save failed**, **any server-validation message in the API response**). Also: signalr / websocket push, job-complete notifications. Transient; user can let it auto-dismiss (errors are sticky). | `AlertsService.show{Success/Error/Warning/}Toast()` / `showModelStateError()` (DevExtreme `dx-toast` via `notify()`) |
| **Inline alert** (§6.4.1) | The message is **part of the page state** — connection-lost banners, contextual tips, advisories that persist until the underlying condition changes. **Not** for form validation. | `.alert.alert-{variant}` (Bootstrap-style block) |

**Rule of thumb:**
- **Empty required field on submit** → red border + `placeholder="Required"`. Nothing else.
- **Server response carries an error / warning** (round-trip API validation: duplicate name, conflict, permission denied, save-failed, any modelState payload) → **popup toast**. This is the canonical use case.
- **Persistent in-context message** (connection lost, contextual tip) → inline `.alert`.
- **Async event the user didn't trigger** (job complete, signalr push, file ready) → popup toast.

#### 6.4.1 Inline alerts

Bootstrap-style alert blocks dropped as a `<div>` inside dialogs, panels, and forms to surface error / warning / info / success messages. **This is the standard treatment for any actionable, in-context message** that can't fit inside a tooltip or status badge.

The base `.alert` is `12px` text, `1.3` line-height, `12px 20px` padding, `4px` radius, `1px` border, `16px` bottom margin (Bootstrap default with elluminate's typography override).

##### Variants — pick by intent

| Class | Use for |
|---|---|
| `.alert.alert-danger` | persistent blocking error banners (connection lost, data source unreachable, permission denied). **Not for form validation — use a popup toast (§6.4.2) instead.** |
| `.alert.alert-warning` | persistent caution / "verify before continuing" advisories |
| `.alert.alert-info` | informational notes, tips, contextual help |
| `.alert.alert-success` | rare; persistent success state (e.g. "Saved at 14:32" header). For transient save confirmations use a toast (§6.4.2). |
| `.alert.alert-secondary` | muted neutral advisories (uses `#F6F6F6` background per the elluminate override) |
| `.alert.alert-primary` | rare; brand-emphasis alerts |

```html
<div class="alert alert-danger" role="alert">
    <strong>Save failed.</strong> "Listing name" is required.
</div>

<div class="alert alert-warning" role="alert">
    Heads up — the date you entered is in the past. Verify before continuing.
</div>

<div class="alert alert-info" role="alert">
    <strong>Tip.</strong> Listings inherit permissions from the parent folder.
</div>

<div class="alert alert-success" role="alert">
    Saved. Your changes have been applied to all 17 sites.
</div>
```

##### Common modifier classes

| Class | Effect | Use when |
|---|---|---|
| `.mb-0` | removes the default `16px` bottom margin | stacking alerts tightly, or when the alert is the last element before a button row |
| `.rounded-0` | removes the `4px` radius | full-width banner alerts inside a dialog header / panel header |
| `.alert-link` | bold + inherit color | for `<a>` links inside an alert |

```html
<!-- Banner-style alert flush against a dialog header -->
<div class="alert alert-danger mb-0 rounded-0" role="alert">
    Connection to the data source was lost. Reconnect to continue.
</div>
```

##### Lists inside alerts

For multi-message alerts (e.g. multiple system constraints surfacing at once), put a `<ul>` inside:

```html
<div class="alert alert-danger" role="alert">
    <strong>Connection issues:</strong>
    <ul>
        <li>Data source disconnected at 14:21.</li>
        <li>Auto-sync paused.</li>
    </ul>
</div>
```

> **AI rule:** inline alerts are for **persistent in-context messages** — connection issues, tips, advisories. **Do not** use inline alerts for form validation; that pattern moved to popup toasts (§6.4.2) and required-field red-border-only (§6.4 decision table). **Do not** use the deprecated `.inline-error` / `.inline-warning` text-below-input pattern.

#### 6.4.2 Popup toast alerts (transient, floating)

**Transient floating notifications** for the result of an action. Primarily used for **server-returned messages** (API round-trip). Use cases:

- **API round-trip validation errors** ★ the canonical use case — **duplicate names**, server-enforced uniqueness ("Listing 'Subjects' already exists"), permission denials, conflict errors (HTTP 409), save failures, anything in the API response's error payload. The server returns the message; the client relays it via `showErrorToast(response.error.message)` or `showModelStateError(response.error.modelState)` (which joins multi-error model state with `<br/>` into one sticky toast).
- **API round-trip success / info** — save confirmed, export ready, deletion completed, file uploaded.
- **Async events not initiated by the user** — signalr push, websocket event, background job complete, server-pushed notification.

Rendered by DevExtreme's `dx-toast` via the shared `AlertsService` — **never hand-rolled**, never via PrimeNG `<p-toast>`, never via a custom modal.

> **Required-field exception:** an empty required field is **not** announced via toast. It only gets the red `.error` border + `placeholder="Required"`. The visual is the message — and the request typically doesn't even reach the server (the client short-circuits the round-trip when required fields are empty).

##### Shell — `.dx-toast-content` (one shape, four variants)

| Token | Value |
|---|---|
| Position | **top center**, `20px` from viewport top |
| Width | `370px` |
| Padding | `15px` |
| Border radius | `4px` |
| Shadow | `0 3px 15px rgba(0,0,0,0.4)` (the standard `--shadow-large` overlay lift) |
| Background | `linear-gradient(150deg, #D0D0D0 0%, #FFFFFF 50%, #D6D6D6 75%)` over `#E8E8E8` base — reads as "floating glass" above the page |
| Text color | `#1A1A1A` (`$gray-900`) |
| Font | Roboto, `12px`, line-height `1.4` |
| Layout | flex row: icon (32px left slot) · message (flex 1, `0 15px` padding) · `×` close (top-right) |
| Close button | `<i class="fal fa-xmark">` at top-right (`top: 0`, `right: 12px`), `0.9rem`, weight 300 |
| Z-index | **`9006`** (per §7 scale) |
| Display time | **`5000ms` default**; **`error` toasts are sticky** (stay until dismissed); per-call `displayTime` and `sticky: true` overrides allowed |
| Close-on-outside-click | true |
| Close-on-click | true |
| Max content height | `600px` (then scrolls) |

##### Variants — icon + color per intent

| Variant | Icon (FA) | Icon color | When |
|---|---|---|---|
| **Success** (`.dx-toast-success`) | `fal fa-check` | `#1F8915` (`$green-500`) | save / completion / approval (e.g. "Saved. Changes applied.") |
| **Info** (`.dx-toast-info`) | `fal fa-circle-info` | `#1F68A8` (`$blue-600`) | informational completion (e.g. "Export ready. Available in downloads.") |
| **Warning** (`.dx-toast-warning`) | `fal fa-triangle-exclamation` | **`#EC7614`** (`$carrot-orange-F` — toast-only warning hue, slightly hotter than the standard `#EEB12B` warning yellow) | caution / partial-success (e.g. "3 subjects skipped — out of range.") |
| **Error** (`.dx-toast-error`) | `fal fa-triangle-exclamation` | `#CD482D` (`$red-500`) | async failure (e.g. "Save failed. Connection lost."). **Sticky** by default. |

##### Service API (Angular — `AlertsService` from `shared/alerts/alerts.service.ts`)

Always go through the service. Never call DevExtreme's `notify()` directly — the service applies the correct defaults (display time, position, close behavior, error-stickiness).

```ts
constructor(private alerts: AlertsService) {}

this.alerts.showSuccessToast('Saved. Changes applied to all 17 sites.');
this.alerts.showToast('Export ready. File is in the downloads panel.');     // info
this.alerts.showWarningToast('3 subjects skipped — visit dates out of range.');
this.alerts.showErrorToast('Save failed. Reconnect and try again.');         // sticky

// For multi-error model state (joins messages with <br/>):
this.alerts.showModelStateError(form.errors);

// Per-call options:
this.alerts.showToast('Custom message', {
    displayTime: 8000,         // ms; ignored if sticky
    sticky: true,              // stays until user dismisses
    linkCallbackMethod: () => {/* runs when an [link-callback] element in the message is clicked */}
});
```

##### Rules

- **Use a toast** for **anything the server returned** — API round-trip success / error / warning, duplicate-name responses, permission denials, conflict errors, save failures, model-state payloads. Plus async events the user didn't trigger (signalr push, job complete).
- **Use an inline alert** (§6.4.1) only for **persistent in-context messages the client knows about locally** — connection-lost banners, advisories, contextual tips. Not for validation.
- **Required-field empty** = visual only (red border + `placeholder="Required"`). No toast, no alert. The round-trip should usually be short-circuited before the server is even called.
- **Don't combine** — never show the same message as both a toast and an inline alert. Pick one based on origin (server vs. local) and persistence.
- **Don't stack more than 3 toasts** on screen at once. For multi-error API responses use `showModelStateError(response.error.modelState)` which joins all server messages into one toast.
- **Don't put forms or interactive controls inside a toast.** Toasts may contain text + at most one inline `<a>` link (for "view details" / "undo"-style follow-ups). Anything richer belongs in a modal (§6.6).
- **Don't use toast colors elsewhere.** The toast's warning hue `#EC7614` is toast-only — use the standard `#EEB12B` warning yellow everywhere else (§2.3).

### 6.5 Badges

Pill-shaped status indicators. Default: `12px` text, `3px 6px` padding, `10px` radius, `22px` minimum width, `1px` solid border (often transparent).

**Icon spacing:** when a badge starts with a Font Awesome icon, the icon gets `5px` right margin so the label is not crammed against it. This is applied automatically — just put the `<i>` first inside the `.badge`:

```html
<span class="badge badge-success"><i class="fal fa-check"></i> Complete</span>
<span class="badge badge-danger"><i class="fal fa-circle-exclamation"></i> Failed</span>
```

#### Status badges (primary — use these by default)

These five cover almost every badge in the product, listed by usage frequency:

| # | Class | Background | Text | When |
|---|---|---|---|---|
| 1 | **`.badge-success`** | green `#1F8915` | white | complete / passed / approved (most common badge by far) |
| 2 | **`.badge-warning`** | yellow `#EEB12B` | dark | caution / in-review / pending action |
| 3 | **`.badge-danger`** | red `#BD341F` | white | failure / blocked / error |
| 4 | **`.badge-info`** | very light blue `#F0F5FA` | dark blue | informational / draft / open |
| 5 | **`.badge-secondary`** | mid gray `#767676` | white | neutral / pending |

#### Outlined variants (use in dense grids/lists)

When many badges appear together (grids, lists, audit trails), prefer the outlined variants — they have less visual weight:

`.badge-outline-success`, `.badge-outline-warning`, `.badge-outline-danger`, `.badge-outline-info`, `.badge-outline-secondary`, `.badge-outline-primary`, `.badge-outline-muted` (dashed border for "not assigned"-style states).

```html
<span class="badge badge-outline-success"><i class="fal fa-check"></i> Complete</span>
<span class="badge badge-outline-muted">Unassigned</span>
```


```html
<span class="badge badge-success">Complete</span>
<span class="badge badge-warning">In Review</span>
<span class="badge badge-outline-danger">Failed</span>
```

### 6.6 Modals

> **Use `<dialog-container>` for new dialogs.** This is the dominant modal pattern in the product (used in 100+ files). It wraps an `<ngb-modal>` and standardizes the header / body / footer regions, the back-button layout, and action buttons. Hand-rolled Bootstrap modals exist for legacy compatibility only — don't pick that path for new work.

#### `<dialog-container>` (primary pattern)

```html
<dialog-container
    [title]="'Edit listing'"
    [hideButtonText]="'Cancel'"
    [closeButtonText]="'Save'"
    [closeButtonClass]="'btn-primary'"
    (hide)="onCancel()"
    (close)="onSave()">

    <!-- Body content here — projected via <ng-content> -->
    <div class="form-group">
        <label><span class="ecs-required-asterisk">*</span>Listing name</label>
        <input type="text" [(ngModel)]="name" />
    </div>

</dialog-container>
```

The component renders three regions automatically:

| Region | Rendered class | Details |
|---|---|---|
| Header | `.dialog-container-header` | `32px` tall, **white** background, `1px solid #E8E8E8` bottom border. Hosts a minimal toolbar with the title (left, flex 1) and configurable header actions (right). |
| Body | `.dialog-container-body` | Flex-1, scrolls when content overflows. Your projected content lives here. |
| Footer | `.dialog-container-footer` | `57px` tall, `15px` padding, `1px solid #E8E8E8` top border. Three flex regions: left (back button), center (info text), right (action buttons). |

**Most common configuration:** `[hideButtonText]="'Cancel'"` (the secondary/ghost button on the right) + `[closeButtonText]="'Save'"` with `[closeButtonClass]="'btn-primary'"` (the primary action). For destructive flows, set `[closeButtonClass]="'btn-danger'"`.

**Sizing:** the container fills its `<ngb-modal>` shell. Modal sizes are set as `windowClass` when you open the modal — they're never written as static HTML classes:

```ts
this.modalService.open(MyDialogComponent, {
    windowClass: 'modal-lg2',  // see §4.4 for size classes
    backdrop: 'static'
});
```

#### Modal headers are white — never the dark page-toolbar gradient

The header is **`32px` tall, white background, `1px solid #E8E8E8` bottom border**. **Never put the dark page-toolbar gradient on a modal header** — that gradient is reserved for top-of-screen page toolbars only. (Some legacy dialogs may have drift; new code uses the white header.)

#### Hand-rolled Bootstrap modal (legacy — read-only reference)

Older dialogs sometimes use Bootstrap's modal markup directly. The structure looks like this — keep it consistent if you ever have to touch it, but don't create new dialogs this way.

| Part | Class | Details |
|---|---|---|
| Header bar | `.modal-header` | `32px`, white, `1px solid #E8E8E8` bottom border, `<h4>` + close button |
| Body | `.dialog-content` | `20px` padding, font `14px`, line-height `1.2` |
| Footer | `.dialog-buttons` | `10px` padding, right-aligned actions, `1px solid #EAEAEA` top border |

```html
<div class="modal" tabindex="-1">
    <div class="modal-dialog modal-md">
        <div class="modal-content">
            <div class="modal-header">
                <h4>Edit listing</h4>
                <button class="modal-close" aria-label="Close"><i class="fal fa-xmark"></i></button>
            </div>
            <div class="dialog-content">
                <p>Body content goes here.</p>
            </div>
            <div class="dialog-buttons">
                <button class="btn btn-secondary">Cancel</button>
                <button class="btn btn-primary">Save</button>
            </div>
        </div>
    </div>
</div>
```

The backdrop is `rgba(0,0,0,0.4)` and the modal box gets a `0 20px 40px rgba(0,0,0,0.4)` shadow automatically.

### 6.7 Tabs

Apply `.nav-tabs` to a horizontal nav, with `.nav-link` on each tab. The active tab gets a `2px` orange (`#F77623`) underline rail and `#f7f7f7` background.

```html
<div class="nav-tabs">
    <a class="nav-link active" href="#">Overview</a>
    <a class="nav-link" href="#">Subjects</a>
    <a class="nav-link" href="#">Visits</a>
</div>
<div class="tab-content">
    Tab body content (20px padding).
</div>
```

Tab links: `8px 20px` padding, `12px` text, `1px solid #DDDDDD` right border between tabs.

### 6.8 Dropdown menus

Apply `.dropdown-menu` to a list. Default: white background, `1px solid #DDDDDD` border, `4px` radius, dropdown shadow, `12px` text, `180px` minimum width.

| Inside the menu | Class | Padding |
|---|---|---|
| Section header | `.dropdown-header` | `5px 10px`, gray small text |
| Menu item | `.dropdown-item` | `3px 10px`, dark text on hover-fills `#F6F6F6` |
| Visual separator | `.dropdown-divider` | `3px 0`, `1px` top border |

### 6.9 Tooltips

Use ng-bootstrap's **`[ngbTooltip]`** directive — that's the dominant tooltip pattern (used in 100+ components: toolbars, icon buttons, table cells, badges, form fields).

```html
<button [ngbTooltip]="'Refresh data'" placement="top">
    <i class="fal fa-rotate-right"></i>
</button>
```

| Attribute | Values |
|---|---|
| `[ngbTooltip]` | the message text or template |
| `placement` | `top` (default) / `bottom` / `left` / `right` (and start/end variants) |
| `triggers` | `hover` (default) / `focus` / `click` |
| `tooltipClass` | extra CSS class to apply to the tooltip element |

**Rendered style** (set by elluminate's overrides on `.tooltip-inner`):
- White text on near-black background (`rgba(0,0,0,0.9)`)
- `12px` font, weight 400
- `350px` default max-width
- `4px` radius

> The legacy `[data-balloon-pos]` attribute-driven balloon tooltip system exists in the codebase but is essentially deprecated (used in only 1 component). **Don't use it for new work** — `[ngbTooltip]` is the standard.

### 6.10 Cards & section headers

#### Card surfaces

| Class | Background | Border | Padding | Use |
|---|---|---|---|---|
| `.rounded-borders` | (transparent) | `1px solid #E8E8E8`, `4px` radius | `10px` | the default content wrapper inside a panel |
| `.rounded-border` | (transparent) | `1px solid #E8E8E8`, `4px` radius | none | when you want the border but pad yourself |
| `.stats-bg` | `#F6F6F6` | `1px solid #DDDDDD`, `4px` radius | `4px` | small KPI/stat cards |

#### Section header bars

Three variants for grouping content:

| Class | Background | Height | Use |
|---|---|---|---|
| `.section-header` | `#F6F6F6` | `32px` | default group separator |
| `.light-blue-section-header` | `#F0F5FA` | `32px` | brand-tinted, hosts an `<h4>` |
| `.secondary-header` | `#F0F5FA` | `32px` | larger label inside a panel |

```html
<div class="section-header">SECTION TITLE</div>
<div class="rounded-borders">
    <p>Panel content.</p>
</div>
```

### 6.11 Toolbars

> **Use `<toolbar-widget class="minimal-toolbar">` for almost every screen.** This is the dominant toolbar pattern in the product (used in 30+ files for panel/widget headers, modal headers, and inset toolbars). The dark-gradient `.toolbar` and gray `.toolbar-secondary` are rare and used only in specific places — don't reach for them by default.

#### `<toolbar-widget>` (primary pattern)

```html
<toolbar-widget
    class="minimal-toolbar"
    [widgetTitle]="'Data Central · Subjects'"
    [toolbarItems]="myToolbarActions"
    [showSearch]="true"
    (search)="onSearch($event)">
</toolbar-widget>
```

The widget renders a `32px` flex row with the title on the left (flex 1), badges in the middle, and toolbar action buttons on the right. It accepts a `class` input that selects the visual treatment.

#### Visual treatments (in usage order)

| # | Class on `<toolbar-widget>` | Background | Where to use |
|---|---|---|---|
| 1 | **`minimal-toolbar`** | transparent | the default for almost every panel, widget, and modal header (used 30+ times) |
| 2 | **`masthead-toolbar`** | inherited from `<ecs-masthead>` — dark blue | top-of-screen application masthead — applied automatically by the masthead component, you rarely write this class yourself |
| 3 | `secondary-toolbar` | `#F6F6F6` with bottom border | rare — inset toolbar in a few filter bars (~5 places) |
| 4 | (no class — default `.toolbar`) | dark blue gradient (`#215887` → `#18293B`) | very rare — only in specific page toolbars; **never use inside a modal** |

**`-lg` modifier** on `.minimal-toolbar` makes it `40px` tall instead of `32px`.

#### Hand-rolled toolbar (legacy)

If you're not using `<toolbar-widget>`, the underlying CSS uses `.toolbar-title` (left, flex 1) and `.toolbar-actions` (right, flex row with `6px` gap). All three flavors are `32px` tall by default.

```html
<div class="toolbar minimal-toolbar">
    <div class="toolbar-title">Listing details</div>
    <div class="toolbar-actions">
        <button class="btn btn-icon" aria-label="Filter"><i class="fal fa-filter"></i></button>
        <button class="btn btn-icon" aria-label="Refresh"><i class="fal fa-rotate-right"></i></button>
    </div>
</div>
```

> **AI rule:** when in doubt, use `<toolbar-widget class="minimal-toolbar">`. The dark gradient is the wrong default 9 times out of 10.

### 6.12 Accordion

The platform uses ng-bootstrap's `<ngb-accordion>`. The header bar is `32px` tall on `#F6F6F6` with a bottom border; the body has `0 0 6px 0` padding and `60px` minimum height.

**Disclosure indicator: light chevron**, not plus/minus. Use `fal fa-chevron-right` when collapsed and `fal fa-chevron-down` when expanded (or rotate the right chevron 90° via CSS `transform`). Color is `#4B4B4B` resting, `#1F68A8` when expanded. The chevron stays **light** in both states — accordions use rotation to indicate state, not weight change.

```html
<ngb-accordion>
    <ngb-panel>
        <ng-template ngbPanelHeader let-opened="opened">
            <div class="accordion-header">
                <i class="fal" [class.fa-chevron-down]="opened" [class.fa-chevron-right]="!opened"></i>
                <span>Study details</span>
            </div>
        </ng-template>
        <ng-template ngbPanelContent>Body content here.</ng-template>
    </ngb-panel>
</ngb-accordion>
```

> **Lists with expandable rows** (tree-views, collapsible groups) follow the same rule — use a chevron, not `+/-`. DevExtreme's `<dx-tree-list>` and `<dx-treeview>` already render chevrons by default; do not override them with text characters.

### 6.13 Splitter (resizable panels)

Apply `.as-split-gutter` to dividers in `<angular-split>` containers. `1px` borders on top and bottom, transparent fill, `#F6F6F6` on hover. For a darker version inside a `#F6F6F6` panel, use `.as-split-gutter-dark` instead.

### 6.14 "Thinking" indicator (AI / loading)

Used by Matilda assistant components. Renders an animated text gradient + breathing dot.

```html
<div class="thinking-indicator">
    <span class="thinking-circle"></span>
    <span class="thinking-text">Matilda is thinking…</span>
</div>
```

The text gradient sweeps across grays (`#767676` → `#1A1A1A` → `#767676`) over 1 second on a loop. The dot is `8px`, `#1F68A8`, breathing every 2.5 seconds.

### 6.15 Misc helper classes

| Class | What it does |
|---|---|
| `.ecs-element` | Subtle inset background `rgba(0,0,0,0.04)`, dark gray bottom border |
| `.ecs-error` | Subtle red inset, red bottom border |
| `.ecs-disabled` | `opacity: 0.3` (stronger than default disabled) |
| `.ecs-label` | `10px` muted small label |
| `.placeholder` | `color: rgba(0,0,0,0.5)` for placeholder-like text |
| `.border-filter.filter-active` | Outlines the container in `2px` brand blue (`#3B85C7`) — used when a filter is active |

#### Text color utilities

These are the text-color classes actually used in production (in HTML, not just SCSS-defined):

| Class | Color | Use for |
|---|---|---|
| `.text-danger` | red `#BD341F` | error / destructive text — most common |
| `.text-success` | green `#1F8915` | success messages |
| `.text-info` | medium blue `#3B85C7` | informational text |
| `.text-primary` | deep blue `#1F68A8` | brand emphasis |
| `.text-warning` | dark orange `#AA4C0E` | caution text |
| `.text-gray` | muted gray `#767676` | secondary / help text |
| `.text-white` | white | text on dark surfaces |
| `.text-red` | red `#BD341F` | alias of `.text-danger` |

### 6.16 Masthead — the `50px` top app bar (full element catalog)

The masthead is the **top-of-screen application bar**. It is **not** a generic toolbar — it has its own dedicated component (`<ecs-masthead>`) with seven horizontal regions and a rich set of masthead-only chrome elements that must not be reused elsewhere in the product.

#### 6.16.1 Shell (`.ecs-masthead`)

| Token | Value |
|---|---|
| Element | `<header class="ecs-masthead">` (Angular: `<ecs-masthead>`) |
| Height | `50px` |
| Background | `#182B3B` (`$blue-900`, the **masthead color**) |
| Layout | single `flex-row`, `align-items: center`, `width: 100%` |
| Inset shadow | `inset 1px 4px 9px -6px rgba(0,0,0,0.3)` (subtle 3px band below the bar) |

**Region order (left → right):**

1. **App grid** (`<ecs-menu>`) — opens the mega-menu flyout (§6.16.7)
2. **Brand mark + page label** (`.ecs-masthead-header` → `<ecs-header>`) — §6.16.2
3. **Navigation** (`.ecs-masthead-navigation`):
   - **Dropdowns** (`.ecs-masthead-navigation-dropdowns`) — §6.16.3
   - **Sub-nav tabs** (`.ecs-masthead-navigation-tabs`) — §6.16.4
4. **Filter pills** (`.ecs-masthead-filters` + `.ecs-masthead-filters-container`) — §6.16.5
5. **Search-by selector** (`.ecs-masthead-searchBy`) — §6.16.8
6. **Search input** (`.ecs-masthead-search`) — §6.16.8
7. **Trailing toolbar** (`.ecs-masthead-toolbar`) — `flex-end`-aligned action area — §6.16.9

> **DOM relocation:** the page's `.title-bar`, `.nav-tabs`, `.masthead-toolbar`, and `.masthead-badge` are **moved into masthead regions automatically** by `MastheadService.setMastheadToolbars()`. So in real Angular code these often live elsewhere and get hoisted up. **For Figma, treat them as masthead chrome** regardless of where they live in the source.

#### 6.16.2 Header label & badges (`.ecs-header`)

The **product area label** in the masthead — typically the page or context name (e.g., "Data Central · Subjects", "RBQM").

| Token | Value |
|---|---|
| Container | `.ecs-header` (flex row, `gap: 10px`) |
| Label class | `.ecs-header-label` |
| Font | Roboto |
| Size / weight | **`16px`**, **bold** |
| Color | **`#CADDEE`** (`$blue-200` — the masthead's secondary text color) |
| Spacing | `margin-right: 15px`, `nowrap` |
| Inline chevron (path separator) | `<i class="fal fa-chevron-right">` at `10px` |
| Trailing badges | `<ecs-badges>` — `.toolbar-badge.badge-margin` (`4px` right margin) |

```html
<div class="ecs-header">
    <div class="ecs-header-label">Data Central <i class="fal fa-chevron-right"></i> Subjects</div>
    <ecs-badges [badges]="badges"></ecs-badges>
</div>
```

#### 6.16.3 Masthead navigation dropdown (`.ecs-dropdown`)

> **This is NOT the standard `.dropdown-menu` (§6.8).** The masthead has its own dropdown mechanism — a button trigger that fills the masthead height plus a DevExtreme `dx-popover` panel containing a `dx-list`. Don't substitute one for the other in Figma.

**Trigger button (`.ecs-dropdown-button`):**

| Token | Value |
|---|---|
| Container | `<button class="ecs-dropdown-button">` |
| Layout | flex row, `align-items: center`, fills the masthead height (`100%` = `50px`) |
| Padding | `17px 15px 15px 15px` |
| Font | **`14px`**, **lighter** weight |
| Text color | `#CADDEE` (`$blue-200`) |
| Right border | `1px solid #215887` (`$blue-700` — the **masthead inner border** color) |
| Background | transparent |
| Hover | bg `#1E4060` (`$blue-800`), text `#FFFFFF` |
| Trailing chevron | `<i class="fal fa-chevron-down">`, `12px`, `margin-left: 15px`, `margin-top: -2px` |

**Cluster:** multiple `.ecs-dropdown` triggers live in `.ecs-masthead-navigation-dropdowns-container` — this container has `border-left: 1px solid #215887` and `margin-left: 15px` to visually separate them from the header label.

**Popover panel (`.ecs-dropdown-popover`):**

| Token | Value |
|---|---|
| Width | **`300px`** (fixed via `[width]="300"`) |
| Min height | `20px` |
| Max height | `680px` |
| List class | `.ecs-dropdown-popover-list` (DevExtreme `dx-list`) |
| List item class | `.ecs-dropdown-popover-list-item`, **min-height `34px`** |
| Selected row | bg `#CADDEE` (`$blue-200`), `3px` left bar `#F77623` |
| Search bar | `.dx-list-search`, `10px` padding (when `searchEnabled` is set) |
| Scroll wrapper | `.dx-scrollable-wrapper`, `height: calc(100% - 48px)` |

```html
<div class="ecs-dropdown">
    <button class="ecs-dropdown-button">
        <span class="ecs-dropdown-button-label">Study</span>
        <span class="ml-1">, +3</span>
        <i class="fal fa-chevron-down"></i>
    </button>
    <!-- dx-popover with dx-list opens below -->
</div>
```

#### 6.16.4 Masthead sub-nav tabs (`.sub-nav-container-horizontal`)

Horizontal tabs that appear inside the masthead — typically used for cross-context navigation (e.g., switching study perspective).

| Token | Value |
|---|---|
| Container | `<div class="sub-nav-container sub-nav-container-horizontal">` |
| Item class | `.sub-nav-item` |
| Min width | `140px` |
| Item height | `40px` (overridden to **`49px`** in the masthead context) |
| Padding | `8px 15px` |
| Font | `14px`, weight `400`, **white** text in the masthead |
| Hover background | `#1E4060` (`$blue-800`) |
| Active state class | `.sub-nav-item-active` |
| Active background | `#1E4060` in masthead (light `#F0F5FA` outside masthead) |
| Active indicator | **`3px` bottom bar `#F77623`** (`$active-item-border-color`), expands from center via `300ms ease` |
| Optional leading icon | `<i class="sub-nav-item-icon ...">` with `10px` right margin |
| Trailing badges | `<ecs-badges class="sub-nav-item-badges">` — `8px` left margin |

```html
<div class="sub-nav-container sub-nav-container-horizontal">
    <div class="sub-nav">
        <a class="sub-nav-item sub-nav-item-active" routerLink="/dc/subjects">
            <i class="sub-nav-item-icon fal fa-user mx-2 fa-fw"></i>
            <span class="sub-nav-item-text">Subjects</span>
            <ecs-badges class="sub-nav-item-badges" [badges]="[{type: 'warning', label: '3'}]"></ecs-badges>
        </a>
        <a class="sub-nav-item" routerLink="/dc/sites">
            <i class="sub-nav-item-icon fal fa-building mx-2 fa-fw"></i>
            <span class="sub-nav-item-text">Sites</span>
        </a>
    </div>
</div>
```

> **Difference from `.nav-tabs` (§6.7):** `.nav-tabs` are `43px` tall with an active background of `#f7f7f7` and a `2px` bottom orange rail, used inside light page bodies. **Masthead tabs** are `49px` tall, white-on-dark, with a `3px` bottom orange rail. **Don't confuse them in Figma.** If the tab sits inside the dark masthead bar, use sub-nav-horizontal; if it sits below the masthead inside a light page, use `.nav-tabs`.

#### 6.16.5 Filter pills (`.filter-pill`)

A two-button pill that displays an applied filter — a primary button (the filter label / value) plus a trailing close-button (the × to clear the filter).

The pill has **two variants** based on its background surface:

**Dark variant** (`.filter-pill-dark`) — used inside the masthead (against `#182B3B`):

| Token | Value |
|---|---|
| Container | `<div class="filter-pill filter-pill-dark">` |
| Layout | flex row, height **`36px`**, max-width `320px`, padding `2px` |
| Border | `1px solid #82B0D9` (`$blue-400`) on each button |
| Buttons | `<button>` × 2 — first is the label, last is the × |
| First button radius | `4px 0 0 4px` |
| Last button radius | `0 4px 4px 0` |
| Button padding | `0 8px` |
| Background | `#1E4060` (`$blue-800`) |
| Text color | `#FFFFFF` |
| Hover bg | `#215887` (`$blue-700`) |
| Active bg | `#1F68A8` (`$blue-600`) |
| Focus | `1px solid #FFFFFF` outline, `-3px` offset, bg `#215887` |
| Label font (line 1) | `11px`, line-height `1.2` |
| Label font (line 2 / value) | `12px`, weight `400` |
| Close icon size | `16px` |
| Transition | `all 200ms ease` |

**Light variant** (`.filter-pill-light`) — used on light surfaces (page-level filter bars outside the masthead):

| Token | Value |
|---|---|
| Container | `<div class="filter-pill filter-pill-light">` |
| Border | `1px solid #BBBBBB` (`$gray-400`) |
| Background | `#FFFFFF` |
| Text color | `#1A1A1A` (`$gray-900`) |
| Hover bg | `#F6F6F6` (`$gray-100`) |
| Active bg | `#E8E8E8` (`$gray-200`) |
| Focus | `1px solid #1F68A8` outline, `-3px` offset, bg `#F6F6F6` |
| Other tokens | same as dark variant (height, radius, padding, font, transition) |

```html
<!-- Dark variant — only inside the masthead -->
<div class="filter-pill filter-pill-dark">
    <button>
        <div>
            <span>Site</span>
            <span>Site 12, Site 03 +5 more</span>
        </div>
    </button>
    <button aria-label="Clear filter"><i class="fal fa-xmark"></i></button>
</div>

<!-- Light variant — on light surfaces -->
<div class="filter-pill filter-pill-light">
    <button>
        <div>
            <span>Visit</span>
            <span>Baseline</span>
        </div>
    </button>
    <button aria-label="Clear filter"><i class="fal fa-xmark"></i></button>
</div>
```

> **Auto-swap inside the masthead:** any pill placed inside `.ecs-masthead-filters-container` is **forced** to the dark variant — the SCSS extends `.filter-pill-light` from `.filter-pill-dark` in that scope. Don't manually toggle the class when the pill lives in the masthead. For Figma: dark pills go in the dark masthead; light pills go on white/light page bodies.

#### 6.16.6 Filter pill overflow (`+N`) (`.ecs-pill-overflow-dropdown`)

When the row of filter pills exceeds the available width, the trailing pills are moved into an overflow popover, replaced by a `+N` button.

| Token | Value |
|---|---|
| Wrapper | `.ecs-pill-overflow-dropdown`, height `46px` |
| Container | `.ecs-pill-overflow-dropdown-button-container`, padding `4px 0 4px 6px`, **left border `1px solid #215887`** |
| Button | `.btn .btn-icon .btn-icon-for-dark .ecs-pill-overflow-dropdown-button`, width `56px`, font `12px`, transparent bg |
| Hover bg | `#215887` (`$blue-700`) |
| Label | `+{N}` followed by `<i class="fal fa-chevron-down fa-fw">` |
| Overflow popover | DevExtreme `dx-popover`, `maxHeight: 500`, wrapper class `.ecs-masthead-filters-overflow-popover` |
| Overflow inner | `.ecs-masthead-filters-overflow-popover-container`, flex column, `gap: 4px`, pills full width |

**Overflow trigger:** the layout algorithm moves a pill into the overflow popover when its right edge extends past `containerRight - 66px` by **≥ 25%** of that pill's width.

```html
<div class="ecs-pill-overflow-dropdown">
    <div class="ecs-pill-overflow-dropdown-button-container">
        <button class="btn btn-icon btn-icon-for-dark ecs-pill-overflow-dropdown-button">
            <span class="mr-1">+5</span>
            <i class="fal fa-chevron-down fa-fw"></i>
        </button>
    </div>
    <!-- dx-popover with stacked pills opens below -->
</div>
```

#### 6.16.7 App grid + mega-menu (`<ecs-menu>`)

The **leftmost** masthead control — the `3 × 3` grid icon that opens the application's mega-menu flyout (modules, profile, sign-off).

**Trigger button:**

| Token | Value |
|---|---|
| Class | `.btn .btn-icon .ecs-menu-btn` (id `ecsMenu`) |
| Icon | `<i class="fa-solid fa-grid">` — the 9-dot app-launcher pattern |
| Icon size | `18px` |
| Color | **`#FFFFFF` (white)** — both resting and hover |
| Hover background | `#1E4060` (`$blue-800`) |
| Width | `40px` |
| Margin | `10px` left from masthead edge |

**Brand mark slot (right of the grid icon):**

| Token | Value |
|---|---|
| Class | `.eclinical-logo .btn .btn-icon` |
| Asset | `eclinical-e-logo-for-dark.svg` (the elluminate "E" — same family as §A.1) |
| Height | `50px` (full masthead height) |
| Margin | `0 15px` |
| Padding | `2px 4px 0 0` |

> The `elluminate_e-logo.svg` shipped with this design-system package (§A.1) is the canonical version for new work. The dev repo currently uses an `eclinical-`-prefixed copy of the same mark.

**Mega-menu flyout (`.ecs-menu-popover`):**

| Token | Value |
|---|---|
| Trigger | DevExtreme `dx-popover` anchored to `#ecsMenu` |
| Width | **`850px`** |
| Height | `calc(100vh - 60px)` |
| Border radius | `4px` |
| Backdrop | `rgba(0, 0, 0, 0.4)` (injected `<div class="modal-backdrop show">`) |
| Header (`.ecs-menu-header`) | `50px` tall, `1px solid #BBBBBB` bottom border, `padding: 0 20px 0 10px`, `gap: 10px`, font `14px`. Three `33%` columns: profile (left) / Studies-and-Stores tabs (center) / Sign Off (right). |
| Body (`.ecs-menu-content`) | `calc(100% - 98px)` tall, **two columns**, first column has right border `1px solid #BBBBBB` |
| List row (`.ecs-menu-button`) | min-height `34px`, font `14px`, color `#1E4060` |
| Selected/hover row | bg `#e0f4ff` resting / `#CCEDFF` hover |
| Footer (`.ecs-menu-footer`) | `50px` tall, bg `#182B3B`, with the elluminate logo (`120 × 35`) and a "discover more" link (`#BBBBBB` → white on hover) |

#### 6.16.8 Search & search-by — two patterns

Both patterns are **content-projection slots** in the masthead component (`<ng-content select="[search]">`, `<ng-content select="[searchBy]">`) — the markup is supplied per route by whichever feature needs masthead search. Pick the pattern by intent.

**Pattern A — Search-only (no field picker).** Use when the search context is unambiguous (single field, single domain).

| Token | Value |
|---|---|
| Container | `.ecs-masthead-search` (relative-positioned flex row) |
| Input | `<input type="search">` |
| Input height | `30px` |
| Input width | `240px` (default; per-route override OK) |
| Background | `#1E4060` (`$blue-800`) |
| Border | `1px solid #215887` (`$blue-700`) |
| Text color | `#FFFFFF` |
| Placeholder | `#CADDEE` at `0.7` opacity |
| Border radius | `4px` |
| Padding | `0 30px 0 10px` (room for the trailing icon) |
| Trailing magnifier | `<i class="fal fa-magnifying-glass">` — positioned **right** (`18px` from edge), color `#CADDEE`, size `12px` |
| Focus | `1px solid #82B0D9` outline |

**Pattern B — Search-by picker + search input.** Use when the user needs to choose **which fields** to search across (multi-select). The picker and the search input render as a **single joined widget**.

| Token | Value |
|---|---|
| Group container | `.ecs-masthead-search-group` (wraps both as one visual unit) |
| **Picker trigger** | `.ecs-masthead-search-by-btn` — `<button>` element |
| Trigger height | `30px` |
| Trigger min-width | `180px` |
| Trigger background | `#182B3B` (`$blue-900` — matches masthead bg) |
| Trigger border | `1px solid #215887`, **no right border** (joins to input) |
| Trigger border-radius | `4px 0 0 4px` (rounded left, square right) |
| Trigger text | white, `12px`, comma-separated list of selected fields (e.g. `"Name, Description"`) |
| Trigger label class | `.ecs-masthead-search-by-label` (ellipsizes overflow) |
| Trigger hover bg | `#1E4060` (`$blue-800`) |
| Trailing chevron | `<i class="fal fa-chevron-down">` — white, `10px`, on the right of the trigger |
| **Joined search input** | Inside the group, picks up radius `0 4px 4px 0` (rounded right, square left). Otherwise identical to Pattern A. |
| **Popover** | `.ecs-masthead-search-by-popover` — opens below the trigger |
| Popover bg | `#FFFFFF` |
| Popover border | `1px solid #DDDDDD`, `4px` radius |
| Popover shadow | `var(--shadow-large)` (`0 3px 15px rgba(0,0,0,0.4)`) |
| Popover min-width | `220px` (matches trigger) |
| Popover padding | `6px 0` |
| Popover row | `.search-by-option` — `<label><input type="checkbox"> Field name</label>` |
| Row text | `12px`, color `#1A1A1A` |
| Row padding | `6px 14px` |
| Row hover bg | `#F0F5FA` (`$blue-100`) |
| Checkbox accent | `#1F68A8` (action blue, via CSS `accent-color`) |
| Checkbox size | `14px × 14px` |

```html
<!-- Pattern A: search-only -->
<div class="ecs-masthead-search">
    <input type="search" placeholder="Search…" />
    <i class="fal fa-magnifying-glass"></i>
</div>

<!-- Pattern B: search-by picker + search -->
<div class="ecs-masthead-search-group">
    <button class="ecs-masthead-search-by-btn" aria-expanded="false">
        <span class="ecs-masthead-search-by-label">Name, Description</span>
        <i class="fal fa-chevron-down"></i>
    </button>
    <div class="ecs-masthead-search">
        <input type="search" placeholder="Search" />
        <i class="fal fa-magnifying-glass"></i>
    </div>
</div>
<!-- Popover (when open) — anchored under the trigger -->
<div class="ecs-masthead-search-by-popover">
    <label class="search-by-option"><input type="checkbox" checked> Name</label>
    <label class="search-by-option"><input type="checkbox" checked> Description</label>
    <label class="search-by-option"><input type="checkbox"> Therapeutic Area</label>
    <label class="search-by-option"><input type="checkbox"> Compound</label>
    <label class="search-by-option"><input type="checkbox"> Phase</label>
    <label class="search-by-option"><input type="checkbox"> Type</label>
    <label class="search-by-option"><input type="checkbox"> Program</label>
</div>
```

> **Don't** use a native `<select>` for the search-by picker — it can't render the multi-select-with-checkboxes UX. The trigger must be a `<button>` plus a popover with `<input type="checkbox">` rows, so the user can toggle multiple fields and see them collected in the trigger label.

#### 6.16.9 Trailing toolbar (`.ecs-masthead-toolbar`)

Right-aligned action area for icon buttons (refresh, settings, more, etc.). Renders an `<ecs-basic-toolbar>` automatically when `masthead.toolbar` items are configured, plus a content-projection slot (`<ng-content select="[toolbar]">`) for custom action markup.

- Always use `.btn .btn-icon .btn-icon-for-dark` for icon buttons here (white on dark, hover bg `#1E4060`).
- The **toolbar filter icon** goes **`#4EE5FB`** (`$aqua-400`) when a filter is active — this is a **masthead-specific accent** that does not appear elsewhere in the system.

```html
<div class="ecs-masthead-toolbar">
    <button class="btn btn-icon btn-icon-for-dark" aria-label="Filter">
        <i class="fas fa-filter" style="color: #4EE5FB;"></i> <!-- active -->
    </button>
    <button class="btn btn-icon btn-icon-for-dark" aria-label="Refresh">
        <i class="fal fa-rotate-right"></i>
    </button>
    <button class="btn btn-icon btn-icon-for-dark" aria-label="More">
        <i class="fal fa-ellipsis-vertical"></i>
    </button>
</div>
```

#### 6.16.10 Toolbar badges (`.toolbar-badge`)

Notification-style badges that attach to masthead actions or dropdowns (e.g., a count next to a filter dropdown showing "3 active").

| Token | Value |
|---|---|
| Class | `.toolbar-badge` |
| Z-index | `9000` (clears most layered chrome) |
| Margin between badges | `4px` left (none on first) |
| Default size | inherits from `.badge` (§6.5) |
| Optional clickable variant | `.toolbar-badge-clickable` (cursor `pointer`) |
| In-masthead padding override | `5px 10px 4px 10px` (slightly taller than the §6.5 badge) |

```html
<ecs-badges>
    <span class="badge badge-warning toolbar-badge badge-margin">3</span>
</ecs-badges>
```

#### 6.16.11 Masthead-only color tokens

These hexes appear **only** in the masthead and its sub-elements — **don't introduce them elsewhere** (badges, buttons, page chrome):

| Hex | Token | Use in the masthead |
|---|---|---|
| `#182B3B` | `$blue-900` | masthead background, mega-menu footer background |
| `#1E4060` | `$blue-800` | masthead hover background, sub-nav active background, dark filter pill bg |
| `#215887` | `$blue-700` | inner divider lines (between dropdowns, before pill overflow), filter pill hover bg |
| `#1F68A8` | `$blue-600` | filter pill active bg |
| `#82B0D9` | `$blue-400` | dark filter pill border |
| `#CADDEE` | `$blue-200` | masthead secondary text (label, dropdowns), dark dropdown selected row bg |
| `#A3C5E2` | `$blue-300` | masthead suffix text (the "· Page name" tail after the wordmark) |
| `#4EE5FB` | `$aqua-400` | toolbar filter icon when active (masthead only — never use elsewhere) |
| `#F77623` | `$active-item-border-color` | sub-nav active rail (3px), masthead dropdown selected indicator (3px) |
| `#BBBBBB` | `$gray-400` | mega-menu dividers |
| `#e0f4ff` | `$light-pattens-blue-F` | mega-menu list row resting (selected) |
| `#CCEDFF` | `$pattens-blue-F` | mega-menu list row hover |

#### 6.16.12 Masthead anti-patterns (Figma flag list)

| Anti-pattern | Fix |
|---|---|
| Using `.dropdown-menu` style inside the masthead | Use `.ecs-dropdown` — different trigger, different popover, fixed `300px` width |
| Using `.nav-tabs` (43px, light) inside the masthead | Use `.sub-nav-container-horizontal` (49px, dark, `3px` bottom rail) |
| Light filter pills in the masthead | Use `.filter-pill-dark` — auto-applied by `.ecs-masthead-filters-container` |
| Dark filter pills on white page bodies | Use `.filter-pill-light` |
| Manual `+N` button instead of `.ecs-pill-overflow-dropdown` | Use the dedicated overflow component — it handles measure-and-move logic |
| Dropdown trigger at `32px` height in the masthead | Trigger must fill `100%` (= `50px`) of the masthead height |
| Generic chevron in `12px` (default) on a masthead dropdown | Use `12px` chevron with `margin-left: 15px`, `margin-top: -2px` per `.ecs-dropdown-button` spec |
| Notification dot instead of a `.toolbar-badge` count | Use a real `.badge` with `.toolbar-badge` modifier and a count |
| Aqua `#4EE5FB` used outside the masthead toolbar (badges, page chrome, charts) | This hex is reserved for the active filter icon in `.ecs-masthead-toolbar` only |
| Side drawer instead of the mega-menu (`<ecs-menu>`) | Use the `850px` `dx-popover` flyout — drawer is not the pattern |
| Page logo placed anywhere other than the leftmost masthead slot | Logo lives left of `<ecs-menu>`'s grid button only — see §A.1 + §6.16.7 |

---

## 7. Z-Index Scale

Each layer has a fixed z-index. **Pick from this scale** — never invent your own number.

| Layer | Z-Index |
|---|---|
| Default popup / overlay | `3000` |
| Balloon tooltip | `3000` |
| Gridster resize handle | `6000` |
| Modal backdrop | `8996` |
| Modal window | `8997` |
| Chart tooltip | `9000` |
| Popover | `9002` |
| Dropdown overlay | `9003` |
| Toast (PrimeNG) | `9006` |
| Validation message | `9008` |
| Date picker / dropdown overlay | `100001` |
| Tooltip (always on top) | `100002` |

If you need a new layer, coordinate by adding it to the scale (in `base/z-index.scss`), don't drop a stray number into a component.

---

## 8. Animations

| Purpose | Duration / Curve |
|---|---|
| Standard transition (buttons, hovers) | `300ms ease` |
| Row hover (table/list) | `200ms linear` |
| Tab underline rail in/out | `200ms` |
| Side panel collapse (`.x-collapse`) | `180ms ease-in` |
| Splitter gutter hover | `300ms` |
| Modal fade | `500ms ease-in-out` |
| Thinking text gradient | `1000ms linear` infinite |
| Thinking dot breathe | `2500ms ease-in-out` infinite |

Standard keyframes available globally:
- `@keyframes thinkingGradient` — sweep gradient across text (used by `.thinking-indicator`)
- `@keyframes breathe` — scale + opacity loop for indicator dots (used by `.thinking-indicator`)

---

## 9. Scrollbars (WebKit)

Custom-styled scrollbars: `10px` track, transparent (no track fill), pill-shaped thumb that's `#BBBBBB` resting and `#767676` on hover. Inherited automatically — no class needed.

---

## 10. Naming & file structure

If you're authoring or extending styles inside the elluminate-ui codebase (not just consuming the design system), follow these conventions.

### 10.1 File layout

```
elluminate-ui/src/assets/styles/
├─ utils/         (tokens — color palette, variables, mixins)
├─ base/          (shared UI primitives — typography, button, forms, table, etc.)
└─ overrides/     (third-party widget customizations — dx-*, p-*, bootstrap, gridster, jsuites, etc.)
```

`main.scss` is the entry point and imports them in that order. **Never redefine a token in a component file** — tokens live only in `utils/`.

### 10.2 Component class naming

One root class per component, kebab-case, matching the component concept (e.g. `.explain`). Each deeper DOM level appends a hyphen segment:

```
.explain
.explain-content
.explain-content-scroll
.explain-content-scroll-inner
```

SCSS uses `&-segment` nesting that mirrors the template hierarchy. Component-scoped utilities are prefixed (e.g. `.explain-fade-in`, never bare `.fade-in`).

### 10.3 Don't bind methods in templates

Never call a method in an Angular template binding (`{{ getX() }}`, `[ngClass]="getClass()"`). Compute once, store on the component, bind the property. Method bindings re-run on every change-detection cycle.

---

## 11. Hybrid stack note

The same tokens drive **three** UI stacks: the modern Angular 12 app (`elluminate-ui/`), a transitional Angular 8 app (in `eClinic.Web.Mvc.eGrex/app/`), and legacy AngularJS / Razor pages (in `eClinic.Web.Mvc.eGrex/Scripts/` + `Views/`). An MSBuild `BeforeBuild` step copies the canonical SCSS files into the legacy project so all three look identical.

When adding a new design token or base file that legacy pages need, also add a `<Copy>` entry in `eClinical.Web.Mvc.eGrex.csproj`. Otherwise the legacy app drifts from the system.

---

## 12. AI authoring guidelines

When generating UI for this system:

1. **Match the production-usage order.** The variant at the top of each component section is what almost every screen uses. If you're generating a new screen, reach for it first:
   - **Buttons:** `.btn-icon` → `.btn-link` → `.btn-secondary` → `.btn-primary`. Specialized: `.btn-outline-secondary`, `.btn-outline`, `.btn-danger`, `.btn-round` (rare).
   - **Toolbars:** `<toolbar-widget class="minimal-toolbar">` for almost everything. The dark gradient `.toolbar` is for the page-level masthead area only and is rare.
   - **Modals:** `<dialog-container>` always. The hand-rolled Bootstrap modal pattern is legacy.
   - **Badges:** `.badge-success` / `.badge-warning` / `.badge-danger` / `.badge-info` / `.badge-secondary` cover almost every case. Outline variants for dense grids.
   - **Cards:** `.rounded-borders` is the default content wrapper.
2. **Class names first.** Reach for an existing class before writing custom CSS.
3. **Default sizes are non-negotiable.** Buttons are `24px`. Inputs are `28px`. Form-controls are `32px`. Rows are `22px` body / `32px` header. Toolbars are `32px`. Don't invent custom heights without an explicit design need.
4. **Body text is `12px`.** Headings only when introducing a section.
5. **Borders default to `1px solid #DDDDDD`** with `4px` radius.
6. **Modals**: use `<dialog-container>`. Set the size via `windowClass` on `NgbModalOptions` (e.g. `modal-md`, `modal-lg2`). The header is **white** (32px, 1px gray-200 bottom border) — **never** the dark page-toolbar gradient. The hand-rolled Bootstrap modal pattern (`.modal-content > .dialog-content + .dialog-buttons`) is legacy — don't author it for new dialogs.
7. **Forms**: stack with `.form-group` (8px bottom margin). Use `.form-row` for horizontal layouts. Mark required fields with the `ecs-required` directive (red asterisk). Labels above inputs automatically get `4px` bottom margin (don't override) — equivalently, you can use `.form-column` (flex column with `4px` gap) or any flex-column wrapper with `gap: 4px`.
8. **Validation** — three paths, picked by where the error originates:
   - **Empty required field** (client-side check, after submit): red border (`.error`) **+ swap the placeholder to `"Required"`**. **No popup, no inline alert.** The visual is the message. The round-trip is usually short-circuited before the server is even called.
   - **Server-returned (API round-trip) error** — duplicate name, conflict, permission denied, server-enforced uniqueness, save failure, anything in the API response's error payload: optionally red border (`.error`) on the related input + **a popup toast** via `AlertsService.showErrorToast(response.error.message)` or `showModelStateError(response.error.modelState)`. **Never** an inline `.alert.alert-danger` at the top of the form.
   - **Persistent in-context advisories the client knows locally** (connection lost, contextual tips): inline `.alert.alert-{danger/warning/info/success/secondary}` is correct.
   - **Never** use the deprecated `.inline-error` / `.inline-warning` text-below-input pattern.
9. **Status colors**: success `#1F8915`, warning `#EEB12B`, error `#BD341F`, info `#3F42C5` or brand `#3B85C7`, unknown `#606060`. Use the matching `.bg-*` / `.text-*` / `.badge-*` class.
10. **Tooltips**: balloon (attribute-driven) for icon labels; Bootstrap-style (`[ngbTooltip]`) for rich content.
11. **Highlights**: when the *user* assigns a category color, use the data colors (vibrant palette). When *you* (the engineer) choose a chrome color, stay in the brand/gray/status palette.
12. **Active item rail**: `2px` orange (`#F77623`) on the active edge — left for list items, bottom for tabs.
13. **Disabled state**: add `disabled` attribute or `.is-disabled` class — opacity drops to `0.4` and clicks are blocked.
14. **Z-index**: pick from §7. Don't invent new values.
15. **Dark surfaces**: use `.btn-icon-for-dark` for icon buttons on dark backgrounds.
16. **Risk levels**: use `.risk-level-{undefined|low|high-low|low-medium|high-medium|high|extreme-high}` — never invent new levels.
17. **Icon weights & colors**: **`fal` (Light) is the default** for almost every icon — toolbars, buttons, dialogs, chevrons — and inherits the container's text color (gray, white, etc.). Use **`fas` (Solid) for toggled-on / active states** — filter when filtered, bell when notifications are on, etc. Solid icons render in **primary blue (`#1F68A8`)** by default; **don't override with semantic colors** (no yellow on a favorited star, no red on a liked heart). The thin-gray-to-fat-blue shift is the on/off cue. Exceptions where solid icons inherit instead of going blue: inside `.btn-primary` / `.btn-danger`, inside `.badge`, and on dark surfaces (`.btn-icon-for-dark`, dark `.toolbar`) — those go near-white.
18. **Disclosure indicators**: chevrons (`fal fa-chevron-right` / `fal fa-chevron-down`), never `+` / `−` text characters.
19. **Bootstrap is heavily customized.** `.modal-md` is `600px` (not Bootstrap's 500). `.nav-tabs` has no bottom border. `.row` has `0 0 3px 0` margin. `.container-fluid` has no padding. Always test against the rendered CSS, not Bootstrap docs.
20. **Hyphen-chain component classes**: one root, append segments at each DOM level (see §10.2).
21. **No methods in template bindings.** No inline styles. No `!important` outside the `overrides/` folder.

---

## 13. Cheat-sheet (paste into prompts)

```text
elluminate Design System — quick reference
- Font: Roboto, body 12px, headings compressed (h1=30px, h2=19px, h3=16px, h4=14px)
- Brand color: #3B85C7 (medium blue), action color: #1F68A8 (deep blue)
- Default text: #1A1A1A, default border: 1px solid #DDDDDD, radius 4px
- Surfaces: white (table body, default) / #F6F6F6 (table header, secondary toolbar) / #F0F5FA (callout)
- Tables: NO alternating row colors — every body row on white; hover #C8E8FA (auto on .table)
- Status: success #1F8915, warning #EEB12B, error #BD341F, info #3F42C5
- Heights: input 28, form-control 32, row 22, header row 32,
           toolbar 32, masthead 50, button 24/32/40/56
- Modal: white, 4px radius, padding 20 (content) / 10 (buttons),
         backdrop rgba(0,0,0,0.4), shadow 0 20px 40px rgba(0,0,0,0.4)
- Disabled: opacity 0.4, pointer-events none
- Focus: #1F68A8 border + 3px ring (primary buttons)
- Active rail: 2px #F77623 (orange) on the active edge
- Transition: all 300ms ease
- Reach for the most-used class first (production-usage order):
  - Buttons: .btn-icon → .btn-link → .btn-secondary → .btn-primary
  - Toolbars: <toolbar-widget class="minimal-toolbar"> for almost everything
  - Modals: <dialog-container> always; size via windowClass (modal-md, modal-lg2)
  - Badges: .badge-success / .badge-warning / .badge-danger / .badge-info / .badge-secondary
  - Cards: .rounded-borders for the default content wrapper
- Icons: <i class="fal fa-{name}"> by default (light), <i class="fas fa-{name}">
  ONLY for toggled-on / active states. Disclosure chevrons are always fal.
- Validation (three paths by where the error originates):
    (1) Empty REQUIRED field     -> red .error border + placeholder="Required"
                                     (NO popup, NO inline alert; round-trip
                                      usually short-circuited client-side)
    (2) Server-returned error    -> (optional) red .error border + popup toast
        (API round-trip:            AlertsService.showErrorToast(response.error.message)
         duplicate, conflict,       or showModelStateError(response.error.modelState)
         permission, save-failed,   (NOT an inline .alert at the top of the form)
         anything in API response)
    (3) Persistent advisories    -> inline .alert.alert-{variant}
        (client-known: connection   (connection lost, tips — NOT validation)
         lost, tips)
  DO NOT use .inline-error / .inline-warning text-below-input (deprecated).
- Alerts (inline): .alert.alert-{danger | warning | info | success | secondary}.
  For persistent in-context messages only. Modifiers: .mb-0 / .rounded-0.
- Popup toasts: AlertsService.showSuccessToast / showToast (info) /
  showWarningToast / showErrorToast (sticky) / showModelStateError.
  370px, top-center, z-index 9006, 5s display (errors sticky).
- Other always-available classes: .form-group, .table, .dialog-content,
  .dialog-buttons, .nav-tabs.
```

---

## 14. About this asset

This folder is a **self-contained snapshot** of the elluminate design system. It has no dependency on the source repository — drop the folder anywhere, open `index.html` in a browser, and everything renders. The folder ships:

- `design.md` — this AI-readable spec
- `index.html` — visual showcase / one-sheeter
- `style.css` — all design tokens as CSS variables + component styles
- `elluminate_e-logo.svg` ★ — the **brand mark** (dark-surface variant) in vector form. Source-of-truth; preferred for product UI and Figma copy-paste. See §A.1.
- `elluminate_e-logo-for-dark.gif` — the **same brand mark** in raster form, for legacy contexts that can't render SVG. See §A.1.

**There is only one logo (the dark-surface variant) in the package** — no light-surface variant ships today; ask before substituting.

**External dependencies (loaded at runtime by `index.html`):**
- **Font Awesome kit** at `https://kit.fontawesome.com/73bc6e2b04.css` — the **canonical** icon source (FA 7 Pro Light / Solid / Regular + Brands + Duotone + Sharp + PrimeIcons + the elluminate custom set). See §A.2. **Required online** — without internet access the icons won't render. There is no bundled fallback in this asset.
- **Google Fonts** for Roboto and JetBrains Mono — if offline, falls back to Helvetica Neue / Arial / monospace; layout still renders correctly.

The values in this document were extracted from the canonical SCSS that lives in the elluminate UI codebase. If you have access to that codebase and need to extend or update the system, the SCSS is organized into:

| Area | Where it lives | Contents |
|---|---|---|
| Tokens | `utils/color-palette.scss`, `utils/variables.scss` | every color, typography size, dimension, transition, shadow |
| Mixins | `utils/mixins.scss` | reusable patterns (cards, toolbars, modal popup, highlights) |
| Base | `base/typography.scss`, `base/button.scss`, `base/forms.scss`, `base/table.scss`, `base/z-index.scss` | shared UI primitives |
| Overrides | `overrides/bootstrap.scss`, `overrides/dx-*.scss`, `overrides/p-*.scss` | third-party widget customizations |

**If you're updating the design system**, change the SCSS first, then regenerate this asset from it. **If a value here disagrees with the SCSS, the SCSS wins.**
