# VOM — Frontend Style Guide

> **Version:** 1.2  
> **Stack:** Vue 3 · Tailwind CSS v4 · RTL-first (Arabic)  
> **Purpose:** Single source of truth for all UI decisions. Developers and AI agents must follow this guide strictly when building or modifying any part of the interface.

---

## Table of Contents

1. [Design Philosophy](#1-design-philosophy)
2. [Color System](#2-color-system)
3. [Typography](#3-typography)
4. [Spacing & Layout](#4-spacing--layout)
5. [Border Radius](#5-border-radius)
6. [Elevation & Shadows](#6-elevation--shadows)
7. [Components](#7-components)
   - 7.1 [Buttons](#71-buttons)
   - 7.2 [Tags / Pills / Badges](#72-tags--pills--badges)
   - 7.3 [Cards](#73-cards)
   - 7.4 [Links](#74-links)
   - 7.5 [Form Inputs & Search](#75-form-inputs--search)
   - 7.6 [Pagination](#76-pagination)
   - 7.7 [Action Icon Buttons](#77-action-icon-buttons)
   - 7.8 [Notification Badge](#78-notification-badge)
8. [States](#8-states)
9. [RTL Guidelines](#9-rtl-guidelines)
10. [CSS Variables Reference](#10-css-variables-reference)

---

## 1. Design Philosophy

The VOM interface follows a **clean, minimal, and functional** aesthetic. Every design decision should reinforce clarity and reduce visual noise. The following principles are non-negotiable:

**Whitespace is intentional.** Pages should breathe. Do not fill space for the sake of it. Generous padding inside cards and between sections is preferred over cramped layouts.

**Teal is the single brand signal.** The primary teal color is used purposefully — for active states, primary actions, highlights, and links. It should never compete with itself on the same screen. Overusing it dilutes its communicative power.

**Surfaces are white or near-white.** Cards, panels, and modals live on white (`--color-background`). The page surface (`--color-surface`) creates subtle separation between content and canvas without introducing visual heaviness.

**Hierarchy through weight and color, not decoration.** Use font weight, size, and the color scale to establish information hierarchy. Avoid borders, dividers, and decorative elements unless they serve a functional purpose.

---

## 2. Color System

The color system is defined as CSS custom properties in **RGB channel format** to support Tailwind's opacity modifier syntax (`bg-primary/50`, `text-accent/80`). Never hardcode hex values directly in component styles — always reference a variable.

### 2.1 Core Palette

| Token | RGB Variable | Hex Equivalent | Usage |
|---|---|---|---|
| `--color-primary` | `rgb(var(--primary))` | `#25B395` | Primary buttons, active states, teal tags, links, focus rings, positive values |
| `--color-accent` | `rgb(var(--accent))` | `#FF7201` | Accent/directional buttons, call-to-action highlights that are not primary actions |
| `--color-neutral` | `rgb(var(--neutral))` | `#64748B` | Secondary text, icon defaults, inactive labels |
| `--color-success` | `rgb(var(--success))` | `#22C55E` | Success alerts, positive confirmation states |
| `--color-warning` | `rgb(var(--warning))` | `#F59E0B` | Warning alerts, caution badges |
| `--color-error` | `rgb(var(--error))` | `#EF4444` | Delete buttons, destructive confirmations, error states |
| `--color-info` | `rgb(var(--info))` | `#3B82F6` | Informational badges, help tooltips |

### 2.2 Surface Palette

| Token | RGB Variable | Hex Equivalent | Usage |
|---|---|---|---|
| `--color-background` | `rgb(var(--background))` | `#FFFFFF` | Cards, panels, modals, inputs |
| `--color-foreground` | `rgb(var(--foreground))` | `#0F172A` | Primary body text, headings, table data |
| `--color-surface` | `rgb(var(--surface))` | `#F8FAFC` | Page/canvas background, table header rows, subtle cell tints |
| `--color-border` | `rgb(var(--border))` | `#E2E8F0` | Card borders, input borders, dividers, row separators |
| `--color-muted` | `rgb(var(--muted))` | `#94A3B8` | Placeholder text, disabled text, empty state messages |

### 2.3 Derived Tokens

These are not in the base palette but must be added to `:root` for hover and tint contexts. They are derived from the core colors.

| Token | Value | Derived From | Usage |
|---|---|---|---|
| `--color-primary-dark` | `#1A9179` | `--color-primary` darkened ~15% | Primary button hover, pressed state |
| `--color-primary-tint` | `rgb(var(--primary) / 0.12)` | `--color-primary` at 12% opacity | Tag backgrounds, hover tints on secondary buttons, focus ring fill |
| `--color-accent-dark` | `#D96100` | `--color-accent` darkened ~15% | Accent button hover state |
| `--color-error-tint` | `rgb(var(--error) / 0.10)` | `--color-error` at 10% opacity | Delete button background, error input background |

### 2.4 Color Usage Rules

- Do **not** use `--color-primary` for purely decorative purposes. It must always carry meaning — action, active state, or interactive affordance.
- Positive numeric/monetary values are rendered in `--color-primary` teal.
- Neutral or zero values are rendered in `--color-foreground`, not teal.
- `--color-error` is reserved exclusively for destructive or irreversible actions. Never use it for warnings.
- `--color-accent` (orange) appears only on accent buttons and specific directional UI elements. It must never substitute for `--color-primary`.
- Opacity modifiers (`/50`, `/80`, etc.) via Tailwind are the preferred way to create tints — do not manually define new hex values for tinted variants.

---

## 3. Typography

The entire interface uses a single custom font family — **Arbfont** — for both Arabic and Latin characters.

```css
--font-family: 'Arbfont', 'Tajawal', 'Cairo', sans-serif;
```

> `Tajawal` and `Cairo` are declared as web-safe Arabic fallbacks loaded via Google Fonts. They activate only if Arbfont fails to load.

### Typography Rules

- Button labels: `12px`, weight `600`.
- Tag/pill text: `12px`, weight `500`.
- Card titles: `17px`, weight `600`.
- Table column headers: `12px`, weight `600`, color `--color-neutral`.
- Table cell data: `12px`, weight `400`, color `--color-foreground`.
- Monetary/numeric values shown in teal: weight `600`.
- Do not use weights below `400` or above `700` anywhere in the interface.

---

## 4. Spacing & Layout

The spacing system is based on a **4px base unit**. All margin, padding, and gap values must be a multiple of 4.

```css
--space-1:  4px;
--space-2:  8px;
--space-3:  12px;
--space-4:  16px;
--space-5:  20px;
--space-6:  24px;
--space-8:  32px;
--space-10: 40px;
--space-12: 48px;
--space-16: 64px;
```

### 4.1 Layout Structure

**Sidebar** — fixed width `240px`, `--color-background` surface, full viewport height, positioned on the right (RTL).

**Top Bar** — full width minus sidebar, `56px` tall, `--color-background` surface, bottom border `1px solid var(--color-border)`. Contains breadcrumbs and user avatar.

**Content Area** — remaining space, `--color-surface` background, padded `--space-6` (24px) on all sides.

### 4.2 Card Internal Spacing

Cards use `--space-6` (24px) padding on all sides. Section headings inside cards have `--space-4` (16px) bottom margin before their content. When a card contains a table, the table spans edge-to-edge with no additional side padding.

### 4.3 Section Gaps

Vertical gap between major page sections: `--space-6` (24px). Horizontal gap between form fields or inline elements: `--space-3` (12px).

---

## 5. Border Radius

| Token | Value | Applied To |
|---|---|---|
| `--radius-sm` | `4px` | Small tags, compact pills, table cell badges |
| `--radius-md` | `6px` | Buttons, input fields, dropdowns, search boxes |
| `--radius-lg` | `8px` | Panels, modal windows, filter areas |
| `--radius-xl` | `10px` | Large action cards, FAB-style buttons (e.g. the "+" button) |
| `--radius-2xl` | `12px` | Cards (§7.3) |
| `--radius-full` | `9999px` | Pill-style tags, notification badges, pagination counters |

**Rules:**
- Buttons always use `--radius-md` (6px). Never use `--radius-full` for buttons unless it is a circular icon-only button.
- Tags and status pills always use `--radius-full`.
- Cards always use `--radius-2xl` (12px); other containers use `--radius-lg` (8px).
- Do not mix radius scales within the same component. A card's inner elements must not use a larger radius than the card itself.

---

## 6. Elevation & Shadows

```css
--shadow-none: none;
--shadow-sm:   0 1px 3px rgba(0,0,0,0.05), 0 1px 2px rgba(0,0,0,0.04);
--shadow-md:   0 2px 8px rgba(0,0,0,0.07), 0 1px 3px rgba(0,0,0,0.05);
--shadow-lg:   0 4px 16px rgba(0,0,0,0.09), 0 2px 6px rgba(0,0,0,0.06);
```

| Level | Token | Applied To |
|---|---|---|
| Flat | `--shadow-none` | Top bar, inline elements |
| Low | `--shadow-sm` | Default cards, filter panels, input groups |
| Medium | `--shadow-md` | Focused/hovered cards, dropdowns, floating toolbars |
| High | `--shadow-lg` | Modals, popovers, date pickers, tooltips |

**Rules:**
- Cards rest at `--shadow-sm` by default; elevate to `--shadow-md` on hover if interactive.
- Dropdowns and select menus always use `--shadow-md`.
- The top bar and sidebar use no shadow — only a `1px solid var(--color-border)` separator.

---

## 7. Components

---

### 7.1 Buttons

#### Primary Button

```
Background:     var(--color-primary)      → #25B395
Text:           var(--color-background)   → #FFFFFF
Border:         none
Border Radius:  var(--radius-md)          → 6px
Padding:        8px 20px
Font:           12px / weight 600

Hover:
  Background:   var(--color-primary-dark) → #1A9179

Active:
  Background:   #147A64
  transform:    translateY(1px)
```

#### Secondary (Outline) Button

```
Background:     var(--color-background)   → #FFFFFF
Text:           var(--color-foreground)   → #0F172A
Border:         1px solid var(--color-border)
Border Radius:  var(--radius-md)          → 6px
Padding:        8px 20px
Font:           12px / weight 600

Hover:
  Background:   var(--color-primary-tint)
  Border:       1px solid var(--color-primary)
  Text:         var(--color-primary)
```

#### Accent Button

```
Background:     var(--color-accent)       → #FF7201
Text:           var(--color-background)   → #FFFFFF
Border:         none
Border Radius:  var(--radius-md)          → 6px
Padding:        8px 10px
Font:           12px / weight 600

Hover:
  Background:   var(--color-accent-dark)  → #D96100
```

#### Disabled State (All Variants)

```
Background:     var(--color-surface)      → #F8FAFC
Text:           var(--color-muted)        → #94A3B8
Border:         1px solid var(--color-border)
cursor:         not-allowed
pointer-events: none
```

> Do **not** use `opacity` alone to indicate disabled — it still looks interactive.

#### Sizes

| Size | Padding | Font Size |
|---|---|---|
| Small | `5px 14px` | `11px` |
| Default | `8px 20px` | `12px` |
| Large | `11px 28px` | `14px` |

#### Button with Icon

Icon sits on the **right of the text in RTL** (visually leading). Gap: `8px`. Icon size: `16px × 16px`.

---

### 7.2 Tags / Pills / Badges

All tags use `--radius-full`. They are non-interactive display-only elements.

#### Filled Teal — Active / Confirmed

```
Background:     var(--color-primary)      → #25B395
Text:           var(--color-background)   → #FFFFFF
Border:         none
Padding:        3px 12px / Font: 12px / weight 500
```

#### Light Tint Teal — Secondary / Soft

```
Background:     var(--color-primary-tint) → rgba(37,179,149,0.12)
Text:           var(--color-primary-dark) → #1A9179
Border:         none
Padding:        3px 12px / Font: 12px / weight 500
```

#### Neutral Gray — Inactive / Pending

```
Background:     var(--color-surface)      → #F8FAFC
Text:           var(--color-neutral)      → #64748B
Border:         1px solid var(--color-border)
Padding:        3px 12px / Font: 12px / weight 500
```

#### Outline Teal — Overflow / Filter Indicator

```
Background:     var(--color-background)   → #FFFFFF
Text:           var(--color-primary)      → #25B395
Border:         1px solid var(--color-primary)
Padding:        3px 10px / Font: 12px / weight 500
```

#### Counter Badge (e.g., "تم اختيار 0")

```
Background:     var(--color-primary-tint)
Text:           var(--color-primary)      → #25B395
Padding:        4px 14px / Font: 12px / weight 500
```

---

### 7.3 Cards

> **Implementation:** `BaseCard` (`resources/js/components/base/BaseCard.vue`), registered globally as
> `<base-card>` and therefore usable from Vue components and Blade views alike. Card geometry is fixed
> by this section and is **not** overridable per instance — no radius, padding, border, or shadow props.
> Build a card by composing `BaseCard`; do not re-implement this surface in component CSS.
> The reasoning, and the three competing geometries it replaced, are recorded in
> [ADR 0001](../adr/0001-basecard-locked-geometry.md).

```
Background:     var(--color-background)   → #FFFFFF
Border:         0.5px solid var(--color-border)
Border Radius:  var(--radius-2xl)         → 12px
Box Shadow:     var(--shadow-sm)
Padding:        var(--space-6)            → 24px
Overflow:       hidden                    (children may not break the radius)
```

The `12px` radius and hairline `0.5px` edge are deliberate: they carry over the lighter card
surface established by the reports interface. Internal rules — the header/body separator and the
footer edge — are `0.5px` as well, so no line on a card is heavier than its border.

Below `992px`, card padding steps down from `--space-6` to `--space-4`. This is a built-in
responsive default, not a variant.

#### Card with Plain Header

The default header. Lives on the card's own white surface.

```
Header Padding:     var(--space-6) var(--space-6) 0
Title:              17px, weight 600, var(--color-foreground)
Subtitle:           12px, weight 400, var(--color-neutral)
Icon:               14px, var(--color-neutral)
Actions:            inline-end of the header, gap var(--space-2)
Gap before body:    var(--space-4)        → 16px of whitespace, no rule (§1)
```

#### Card with Header Banner

```
Header Background:  var(--color-primary)  → #25B395
Header Radius:      12px 12px 0 0
Header Padding:     16px 24px
Header Text:        var(--color-background), 15px, weight 600
Subtitle:           var(--color-background) at 88%
Body Radius:        0 0 12px 12px
```

A card with neither a title nor header content renders **no header** — just a padded surface.

#### Card with Flush Body

Per §4.2, a table inside a card spans edge to edge. The body drops to zero padding.

```
Body Padding:   0
Separator:      0.5px solid var(--color-border) between a PLAIN header and a flush body
```

The rule exists only because a flush body has no whitespace left to separate it from the header —
the one case where §1 admits a divider as functional. A **banner** header supplies its own
separation, so no rule is drawn beneath it.

#### Nested Card

A card inside a card must not stack shadows.

```
Border:         0.5px solid var(--color-border)   (kept)
Box Shadow:     var(--shadow-none)
```

#### Card Footer

```
Padding:        var(--space-4) var(--space-6)
Border:         0.5px solid var(--color-border) on the block-start edge
Content:        aligned to the inline-end, gap var(--space-3)
```

Use it for action rows and table pagination. Footers are not sticky; a filter panel that needs a
sticky action row owns that behaviour itself.

#### Interactive Card

For navigation tiles and other clickable surfaces. Per §6, an interactive card elevates on hover.

```
Hover:      box-shadow → var(--shadow-md)
Focus:      border → var(--color-primary), box-shadow 0 0 0 3px var(--color-primary-tint)
Pressed:    transform: translateY(1px)
Disabled:   background → var(--color-surface), text → var(--color-muted),
            cursor: not-allowed, pointer-events: none, shadow removed
```

A clickable card is a **surface**, not a text link: the underline rule in §7.4 applies to inline
links only. An interactive card with a destination renders as an anchor; without one it takes
`role="button"`, a tab stop, and Enter/Space activation.

#### Loading Card

```
Overlay:    covers the body only — the header stays readable
Background: var(--color-background) at 72%
Spinner:    20px, 2px track var(--color-border), leading edge var(--color-primary)
ARIA:       aria-busy on the card root; the optional label is announced via role="status"
```

Skeleton placeholders are the consumer's responsibility, since only the consumer knows its content
shape.

#### Collapsible Card

```
Header:     becomes a button carrying aria-expanded and aria-controls
Chevron:    vertical (down → up), rotates 180° when expanded
Body:       hidden when collapsed, along with the footer
```

The chevron is vertical and therefore needs no RTL mirroring (§9). A collapsible card and an
interactive card are mutually exclusive: one makes the header a control, the other makes the whole
card one.

#### Card with Summary Metric Grid

```
Grid:           4 equal columns (adjustable)
Cell Background: var(--color-surface)     → #F8FAFC
Cell Radius:    var(--radius-md)          → 6px
Cell Padding:   var(--space-4)            → 16px
Gap:            var(--space-3)            → 12px
Label:          12px, var(--color-neutral)
Value:          17px, weight 600, var(--color-foreground)
Positive Value: var(--color-primary)      → #25B395
```

Metric cells are content inside a card, not cards themselves — which is why they may use
`--radius-md` without violating the rule in §5 about inner radii.

---

### 7.4 Links

Links are **always underlined** — at rest, on hover, and on focus. Underline is never removed.

```
Color:          var(--color-primary)      → #25B395
Text Decoration: underline
Font Weight:    500

On hover:
  Color:        var(--color-primary-dark) → #1A9179
  Decoration:   underline (unchanged)
```

Links inside table cells: `12px`. Links inside card body text: `14px`. All links are teal — no exceptions.

---

### 7.5 Form Inputs & Search

#### Default Input / Dropdown / Select

```
Background:     var(--color-background)   → #FFFFFF
Border:         1px solid var(--color-border)
Border Radius:  var(--radius-md)          → 6px
Padding:        8px 14px
Font Size:      14px
Color:          var(--color-foreground)
Height:         36px

Focus:
  Border:       1px solid var(--color-primary)
  Box Shadow:   0 0 0 3px var(--color-primary-tint)
  Outline:      none

Disabled:
  Background:   var(--color-surface)
  Color:        var(--color-muted)
  cursor:       not-allowed

Placeholder:
  Color:        var(--color-muted)        → #94A3B8
```

#### Search Input

Follows default input spec. Adds a `16px` search icon on the **left in RTL** (visually trailing). Icon color: `--color-muted`.

---

### 7.6 Pagination

```
Container gap:  8px

Default button:
  Background:   var(--color-background)
  Border:       1px solid var(--color-border)
  Radius:       var(--radius-md)          → 6px
  Size:         32px × 32px
  Font:         12px / var(--color-foreground)

Active button:
  Background:   var(--color-primary)      → #25B395
  Border:       1px solid var(--color-primary)
  Text:         var(--color-background)   → #FFFFFF
  Font Weight:  600

Prev / Next:
  Same as default button
  Labels:       "السابق" / "التالي"
  Disabled:     color → var(--color-muted), cursor: not-allowed
```

---

### 7.7 Action Icon Buttons

Square `28px × 28px`, `--radius-md` (6px). Icon-only — never labeled. Must have `aria-label`.

| Action | Background | Icon Color | Hover Background |
|---|---|---|---|
| Delete | `rgb(var(--error) / 0.10)` | `var(--color-error)` `#EF4444` | `rgb(var(--error) / 0.18)` |
| Edit / Primary | `rgb(var(--primary) / 0.12)` | `var(--color-primary)` `#25B395` | `rgb(var(--primary) / 0.20)` |
| Secondary Edit | `rgb(var(--primary) / 0.12)` | `var(--color-primary)` `#25B395` | `rgb(var(--primary) / 0.20)` |

Icon size: `14px × 14px`.

---

### 7.8 Notification Badge

```
Background:     var(--color-primary)      → #25B395
Text:           var(--color-background)   → #FFFFFF
Border Radius:  var(--radius-full)
Min Width:      18px / Height: 18px
Font:           11px / weight 700
Padding:        0 5px
Position:       absolute, bottom-right of avatar
```

---

## 8. States

Every interactive element must implement all states below. None are optional.

| State | Visual Treatment |
|---|---|
| **Default** | Base styles as defined per component |
| **Hover** | Background shifts to tint, color darkens slightly, `cursor: pointer` |
| **Focus** | `box-shadow: 0 0 0 3px var(--color-primary-tint)`, border → `var(--color-primary)`, `outline: none` |
| **Disabled** | Background → `var(--color-surface)`, text → `var(--color-muted)`, `cursor: not-allowed`, `pointer-events: none` |
| **Active/Pressed** | Background darkens one step, `transform: scale(0.98)` |
| **Loading** | Spinner replaces label, button retains same dimensions, `pointer-events: none` |

---

## 9. RTL Guidelines

The entire interface is RTL (Right-to-Left) for Arabic. All rules below are mandatory.

**Document direction** — The root `<html>` must have `dir="rtl"` and `lang="ar"`.

**Logical CSS properties** — Use `margin-inline-start` not `margin-left`, `padding-inline-end` not `padding-right`. Physical properties break RTL/LTR symmetry.

**Directional icons** — Arrows and chevrons must be mirrored in RTL. Use `transform: scaleX(-1)` or directional variants. Never use hardcoded margins to compensate.

**Text alignment** — Set `text-align: start` (or let `dir="rtl"` handle it). Never hardcode `text-align: right`.

**Flexbox and Grid** — Row direction mirrors automatically with `dir="rtl"`. Do not use `flex-direction: row-reverse` as a workaround.

**Numeric formatting** — Technical identifiers and codes use Western Arabic numerals (`0–9`). Data values may use Eastern Arabic numerals (`٠–٩`) where contextually appropriate.

---

## 10. CSS Variables Reference

Add the following block to your global stylesheet alongside the existing Tailwind color definitions. These tokens are purely additive — spacing, radius, shadow, font, and the two derived color shades not covered by the Tailwind theme block.

```css
:root {
  /* === DERIVED COLOR SHADES === */
  /* Extend your existing :root block with these */
  --color-primary-dark: #1A9179;
  --color-accent-dark:  #D96100;

  /* === TYPOGRAPHY === */
  --font-family: 'Arbfont', 'Tajawal', 'Cairo', sans-serif;

  /* === SPACING === */
  --space-1:  4px;
  --space-2:  8px;
  --space-3:  12px;
  --space-4:  16px;
  --space-5:  20px;
  --space-6:  24px;
  --space-8:  32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;

  /* === BORDER RADIUS === */
  --radius-sm:   4px;
  --radius-md:   6px;
  --radius-lg:   8px;
  --radius-xl:   10px;
  --radius-2xl:  12px;
  --radius-full: 9999px;

  /* === SHADOWS === */
  --shadow-none: none;
  --shadow-sm:   0 1px 3px rgba(0,0,0,0.05), 0 1px 2px rgba(0,0,0,0.04);
  --shadow-md:   0 2px 8px rgba(0,0,0,0.07), 0 1px 3px rgba(0,0,0,0.05);
  --shadow-lg:   0 4px 16px rgba(0,0,0,0.09), 0 2px 6px rgba(0,0,0,0.06);
}
```

> Your existing `@theme` block and `:root` color variables remain untouched. Nothing here overrides them — this block only adds what is missing.

---

*This document is the authoritative design reference for the VOM frontend. Any deviation requires explicit approval and a version update to this guide. When in doubt, refer back to the principles in Section 1.*
