# Quov application UI: instructions for AI agents

Use this file when designing or building a Quov product interface: an application screen, a dashboard, a back office, a portal behind a sign-in. Load `quov-brand.en.md` first (https://quov.io/design-system/quov-brand.en.md); this file applies the brand to product UI and never overrides it.

Logo files are listed in the brand file and served from `https://quov.io/design-system/logo/`. In the app, the mark follows the theme: the circle in `--mark`, the letters in `--foreground` (the inline SVG in the brand file). Never redraw the mark.

## Stance

Dense, exact, ledger-like. The numbers are the interface; everything else is ruled lines and quiet scaffolding around them. Trust is earned through clarity and restraint, never through decoration.

1. **Numbers are the interface.** Every amount, percentage and date is mono and tabular, so columns align and figures are read, not decoded.
2. **Color means meaning, nothing else.** Gain, loss and warning hues appear only for financial or status semantics. Neutral surfaces carry the rest.
3. **Ruled structure, not floating cards.** Depth is borders at about 10% alpha, no shadows.
4. **Precision over friendliness.** Small radius, tight type scale, restrained spacing. An instrument, not an app that wants to be liked.
5. **Clarity earns trust.** When in doubt, remove rather than add. A reader must never have to interpret; an operator must never have to hunt.

If a screen could be mistaken for a generic analytics template, it has failed.

## Themes

Dark is the default; paper is the alternative, toggled by the user and remembered. Both themes share every token name; only values change. Higher elevation is slightly lighter in dark and slightly whiter on paper.

## Tokens

The complete set, Tailwind CSS 4. The core palette is the brand's; the app adds surfaces, semantic colors and a chart ramp.

```css
@import 'tailwindcss';
@import '@fontsource/aoboshi-one/latin-400.css';
@import '@fontsource-variable/geist';
@import '@fontsource-variable/geist-mono';

@custom-variant light (&:where([data-theme='light'], [data-theme='light'] *));

@theme inline {
  --font-heading: 'Aoboshi One', ui-serif, Georgia, serif;
  --font-sans: 'Geist Variable', 'Geist', ui-sans-serif, system-ui, sans-serif;
  --font-mono: 'Geist Mono Variable', 'Geist Mono', ui-monospace, Menlo, monospace;
  --radius-sm: calc(var(--radius) * 0.6);
  --radius-md: calc(var(--radius) * 0.8);
  --radius-lg: var(--radius);

  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-destructive: var(--destructive);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  --color-mark: var(--mark);
  --color-sidebar: var(--sidebar);
  --color-table-header: var(--table-header);
  --color-table-header-foreground: var(--table-header-foreground);
  --color-gain: var(--gain);
  --color-gain-muted: var(--gain-muted);
  --color-loss: var(--loss);
  --color-loss-muted: var(--loss-muted);
  --color-warn: var(--warn);
  --color-warn-muted: var(--warn-muted);
  --color-chart-1: var(--chart-1);
  --color-chart-2: var(--chart-2);
  --color-chart-3: var(--chart-3);
  --color-chart-4: var(--chart-4);
  --color-chart-5: var(--chart-5);
}

/* Dark (default): cold slate, hue 260. */
:root {
  color-scheme: dark;
  --radius: 0.375rem;
  --background: oklch(0.11 0.01 260);
  --foreground: oklch(0.93 0.01 260);
  --card: oklch(0.15 0.01 260);
  --card-foreground: oklch(0.93 0.01 260);
  --popover: oklch(0.17 0.015 260);
  --popover-foreground: oklch(0.93 0.01 260);
  --primary: oklch(0.93 0 0);
  --primary-foreground: oklch(0.13 0 0);
  --secondary: oklch(0.2 0.01 260);
  --secondary-foreground: oklch(0.85 0.01 260);
  --muted: oklch(0.2 0.01 260);
  --muted-foreground: oklch(0.64 0.01 260);
  --destructive: oklch(0.65 0.2 25);
  --border: oklch(1 0 0 / 10%);
  --input: oklch(1 0 0 / 12%);
  --ring: oklch(0.55 0.01 260);
  --mark: oklch(0.06 0.005 260);
  --sidebar: oklch(0.13 0.01 260);
  --table-header: oklch(0.15 0.01 260);
  --table-header-foreground: oklch(0.64 0.01 260);
  --gain: oklch(0.75 0.15 160);
  --gain-muted: oklch(0.75 0.15 160 / 12%);
  --loss: oklch(0.7 0.17 25);
  --loss-muted: oklch(0.7 0.17 25 / 12%);
  --warn: oklch(0.8 0.15 85);
  --warn-muted: oklch(0.8 0.15 85 / 12%);
  --chart-1: oklch(0.87 0 0);
  --chart-2: oklch(0.55 0 0);
  --chart-3: oklch(0.44 0 0);
  --chart-4: oklch(0.37 0 0);
  --chart-5: oklch(0.27 0 0);
}

/* Light: sugar-cane paper, hue 75. */
:root[data-theme='light'] {
  color-scheme: light;
  --background: oklch(0.95 0.02 75);
  --foreground: oklch(0.18 0.01 75);
  --card: oklch(0.98 0.015 75);
  --card-foreground: oklch(0.18 0.01 75);
  --popover: oklch(0.99 0.01 75);
  --popover-foreground: oklch(0.18 0.01 75);
  --primary: oklch(0.18 0 0);
  --primary-foreground: oklch(0.95 0.02 75);
  --secondary: oklch(0.91 0.02 75);
  --secondary-foreground: oklch(0.25 0.01 75);
  --muted: oklch(0.91 0.02 75);
  --muted-foreground: oklch(0.48 0.01 75);
  --destructive: oklch(0.55 0.2 25);
  --border: oklch(0 0 0 / 10%);
  --input: oklch(0 0 0 / 8%);
  --ring: oklch(0.48 0.02 75);
  --mark: oklch(0.995 0.005 75);
  --sidebar: oklch(0.93 0.02 75);
  --table-header: oklch(0.48 0.015 75);
  --table-header-foreground: oklch(0.92 0.015 75);
  --gain: oklch(0.45 0.15 160);
  --gain-muted: oklch(0.45 0.15 160 / 10%);
  --loss: oklch(0.5 0.17 25);
  --loss-muted: oklch(0.5 0.17 25 / 10%);
  --warn: oklch(0.48 0.13 85);
  --warn-muted: oklch(0.48 0.13 85 / 10%);
  --chart-1: oklch(0.18 0 0);
  --chart-2: oklch(0.35 0 0);
  --chart-3: oklch(0.5 0 0);
  --chart-4: oklch(0.65 0 0);
  --chart-5: oklch(0.78 0 0);
}
```

If the app switches themes with a `.dark` class on `<html>` (as `next-themes` does), keep the values: put the light block in `:root`, the dark block under `.dark`, and render `<html class="dark">` by default.

Three values differ from what the product apps ship today, all to reach WCAG AA, and the apps should adopt them:

- dark `--muted-foreground` is `0.64` (`0.55` measures 4.2:1 on the background);
- dark `--table-header-foreground` is `0.64`, for the same reason (`0.55` measures 4.0:1 on the header);
- paper `--warn` is `oklch(0.48 0.13 85)` (`oklch(0.55 0.15 85)` measures 4.2:1 on the background and 3.7:1 on its own tint).

## Semantic color

Color appears only for meaning, never for decoration, and stays well under 10% of any screen.

| Token         | Meaning                                                  |
| ------------- | -------------------------------------------------------- |
| `gain`        | Positive returns, deposits, an active or complete status |
| `loss`        | Negative returns, sells, an expired status               |
| `warn`        | Withdrawals, pending states, warnings                    |
| `destructive` | Destructive actions and error messages                   |
| `*-muted`     | The tint behind a badge of that meaning                  |

- Always pair a semantic color with a sign (`+4.20%`, `-1.35%`), a label ("Pending") or a position, so the meaning survives color blindness and grayscale.
- `destructive` text sits on `background` or `card` only (4.6:1 on paper); it never fills a button.

Contrast, computed from the values above and rounded down:

| Pair                                    | Dark   | Light |
| --------------------------------------- | ------ | ----- |
| gain on background                      | 9.7:1  | 5.6:1 |
| gain on gain-muted                      | 8.4:1  | 4.8:1 |
| loss on background                      | 7.1:1  | 5.6:1 |
| loss on loss-muted                      | 6.3:1  | 4.8:1 |
| warn on background                      | 10.8:1 | 5.6:1 |
| warn on warn-muted                      | 9.2:1  | 4.9:1 |
| destructive on background               | 5.7:1  | 4.6:1 |
| table-header-foreground on table-header | 5.8:1  | 5.1:1 |

## Typography

| Role                                                  | Family          | Class                    |
| ----------------------------------------------------- | --------------- | ------------------------ |
| All interface text: body, labels, buttons, navigation | Geist           | `font-sans`              |
| Every amount, percentage and date                     | Geist Mono      | `font-mono tabular-nums` |
| Page titles and the product name                      | Aoboshi One 400 | `font-heading`           |

Hierarchy through scale and weight contrast, not decoration. Interface text is mostly `text-sm`; figures in summaries go up to `text-lg`. Aoboshi One is never bold and never body text.

## Depth, radius, spacing

- **Depth:** borders only, at the `--border` alpha. No shadows, no floating cards. Cards are not the default container, and never nest.
- **Radius:** `--radius: 0.375rem`. Precision, not friendly roundness.
- **Spacing** (Tailwind scale): `px-3 py-2.5` table cells; `px-6 py-4` page header; `gap-1.5` between inline badges; `gap-2` inside a form field and `gap-4` between fields; `gap-8` between summary figures; `mb-3` from a section label to its content; `mb-6` to `mb-8` between sections.

## App shell

- `flex min-h-screen`: the navigation rail, then a scrollable content column.
- Each page opens with a header bar (`px-6 py-4 border-b`): the page title in Aoboshi One on the left; actions, the view toggle and the user menu on the right.
- Then summary figures, then the data.

## Navigation rail

- State-driven, not hover-driven. Collapsed `w-14` (icons only; a click anywhere on the rail expands it); expanded `w-56` (labels visible, a collapse button at the bottom with lucide `PanelLeftClose` / `PanelLeftOpen`). The state persists across pages.
- Width transition 200 ms, labels fade in over 200 ms, ease-out.
- Surface `bg-sidebar` with a right hairline.
- Active item `bg-secondary text-foreground`; other items `text-muted-foreground hover:bg-secondary hover:text-foreground`.
- Top: the mark at 28 px and the product name in Aoboshi One, which fades in on expand.
- One lucide icon at 16 px per item, always with its label when expanded.

## Tables

- The table sits in `overflow-hidden rounded-md border border-border`.
- Header row `bg-table-header`, with no hover change; header cells `text-xs uppercase tracking-wider text-table-header-foreground`.
- Cells `px-3 py-2.5`, text left-aligned.
- Amounts and percentages `text-right font-mono tabular-nums`.
- Dates `font-mono text-xs tabular-nums text-muted-foreground`, in a fixed-width column (`w-24`); badge columns `w-20`.
- Totals in a footer row in `font-medium`, the label in `text-muted-foreground`.
- Clickable rows: `relative cursor-pointer` on the row and an `absolute inset-0` link inside the first cell. Never wrap a row in a link.
- First-level lists offer a table view and a card view, switched by a view toggle (lucide `List` and `LayoutGrid` in a small toggle group) in the page header.

## Badges

- Base: `inline-flex items-center rounded-sm px-1.5 py-0.5 text-xs font-medium`.
- Semantic: `bg-gain-muted text-gain`, `bg-loss-muted text-loss`, `bg-warn-muted text-warn`. Figures inside a badge keep `font-mono tabular-nums` and an explicit sign.
- Neutral (completed, inactive, a buy): `bg-secondary text-muted-foreground`.
- Status: active and complete in gain, pending in warn, expired in loss.
- A second axis that is neither good nor bad (for example, how a document was signed): a bordered badge, `border border-foreground/20 text-foreground` or `border border-muted-foreground/20 text-muted-foreground`.
- Tags: `rounded-sm bg-secondary px-1.5 py-px text-xs text-secondary-foreground`, in a `flex gap-1.5` row.

## Summary figures

A description list: the label (`dt`) in `text-xs text-muted-foreground` above the value (`dd`) in `font-mono tabular-nums`, `gap-8` between figures. Never a tile with an icon, a gradient or a sparkline used as decoration.

## Forms and buttons

- Label above the control; `gap-2` inside a field, `gap-4` between fields.
- Inputs: `h-9 rounded-md border border-input bg-transparent px-3 text-sm`. Amount, percentage and date inputs use `font-mono tabular-nums`.
- Focus: `border-ring` with a 3 px `ring-ring/50` halo. It is the only halo in the system, and it marks focus, not depth.
- Errors: `aria-invalid` on the control and the message under it in `text-destructive` with an icon, saying what to do ("Enter an amount greater than zero."). Never color alone.
- Buttons, `h-9 rounded-md px-4 text-sm font-medium`: primary `bg-primary text-primary-foreground`; outline `border border-border hover:bg-secondary`; ghost `hover:bg-secondary`; destructive `border border-destructive/40 text-destructive`. One primary per form. A form's submit button may span the width (`w-full`).
- Menus and dialogs use `bg-popover` with a hairline border.

## Numbers

- **Money:** the currency code, comma thousands, two decimals: `COP 48,250,000.00`. One formatting function for every displayed amount; arithmetic in a decimal library, never in floating point.
- **Percentages:** two decimals, with an explicit sign when the value is a change: `+4.20%`, `-1.35%`.
- **Dates:** ISO in tables (`2026-10-25`); a written date in prose.

## Charts

- Series differ by lightness on the achromatic ramp (`chart-1` to `chart-5`), never by hue.
- Semantic colors only for a series that carries a sign (gains and losses).
- Thin lines, hairline gridlines in `--border`, mono tabular axis labels. No gradients, no decorative area fills, no 3D.

## Motion

Quiet and functional: the rail's width (200 ms), label fades (200 ms), a 300 ms color crossfade when the theme switches. Ease-out, never bounce. Never animate layout properties. Honor `prefers-reduced-motion`.

## Accessibility

- WCAG 2.2 AA in both themes (the ratios above).
- Keyboard navigation through every control; focus always visible.
- Meaning never carried by color alone.

## Copy

- Interface copy states what happens: "Save", "Record transaction", "Delete document". English without contractions, Spanish with _tú_.
- No em dashes, no superlatives, no emoji, no invented figures.

## Before you finish

1. View every screen in both themes; confirm figures align in their columns.
2. Confirm every semantic color has a sign, a label or a position next to it.
3. Confirm there are no shadows, no nested cards and no color outside the semantic tokens.
4. Re-read the copy in both languages for em dashes, contractions and _usted_.
