# Admin Portal Design Standards
Status: reference -- conventions the admin pages follow
This document captures the design system used by the Claude-built admin portal uplift (commits around `23b74a3`–`906c9e6`) so future edits to `admin/frontend/` stay visually consistent.
Intended readers: any agent or human touching `admin/frontend/*.html`, `admin.py`, or the admin API surface.
**Supplementary sources** (read these too before touching the admin UI):
- `.omo/notepads/admin-facelift/learnings.md` — the running log of every visual QA finding, root cause, and fix across the whole 10-wave facelift. This is the single richest record of what went wrong and how it was fixed.
- `plans/admin-facelift.md` — the original spec (survival guardrails S1–S5, device contracts).
- `plans/admin-visual-fixes-review.md` and `plans/router-admin-portal-implementation-review.md` — post-hoc audits that caught regressions the QA pass missed.
## Philosophy
- **Liquid-glass dashboard, not a marketing page.** Background is a subtle gradient wash; cards are translucent, blurred, and softly lit from the top. The goal is information density with low visual fatigue.
- **One visual language everywhere.** Use the same frosted-card treatment, pill buttons, and glass badges on every page so the portal feels like a single app.
- **Subtle cues over neon alerts.** Status colors are desaturated translucent tints. Warnings live in the navbar bell, not in a full card.
## Theme
- Always dark mode. Root is ``.
- **Base background** (body):
- Three overlapping `radial-gradient` blobs:
- amber (`rgba(245,158,11,.10)`) top-left
- blue (`rgba(59,130,246,.10)`) top-right
- purple (`rgba(139,92,246,.07)`) bottom
- Base color `#111827`.
- `background-attachment: fixed` so it stays put during scroll.
## Layout shell (every page)
1. Bootstrap 5 / Tabler page skeleton:
```html
…
…
…
```
2. Scrolling context: **`` scrolls, `` does not.** This works around a Chrome/Linux phantom-margin bug caused by the HTML element also being the scrollbar container.
```css
html { overflow:hidden; height:100%; margin:0!important; padding:0!important }
body { overflow-y:auto; height:100%; margin:0!important; padding:0!important }
```
3. Container width: `container-xl` inside `.page-body`.
4. Cards grid: `.row.row-cards > .col-xl-6`, `.col-xl-4` or `.col-12`.
Use `.col-xl-4` when **three** peer cards belong on one row. At `.col-xl-6`
a third card wraps and produces two dead columns at once — the short card
stranded beside a tall one, plus the empty half-row under it. That is the
same defect as the "Card-level layout" note below, so the fix is a narrower
column, not an `align-items` value. Do not mix widths within one row:
`6 + 4 + 4` leaves a gap and reads as a mistake.
## Navbar
- Glass background: `rgba(31,41,55,.6)` with `backdrop-filter: blur(16px) saturate(160%)`.
- Bottom border: `1px solid rgba(255,255,255,.06)`.
- Explicit `position: relative; z-index: 1030;` on `header.navbar` so its dropdowns paint above later backdrop-filter stacking contexts (e.g. the quota chip).
- Container padding pinned to exactly `20px` left/right so the brand edge stays aligned across breakpoints:
```css
header.navbar > .container-fluid { padding-left:20px!important; padding-right:20px!important; }
```
- Brand wordmark: **6krrt LLM Router**, in `'Quicksand', var(--tblr-font-sans-serif)` at `font-weight: 700; letter-spacing: 0.01em`. The leading `6` is `.brand-six` — brand amber, `1.14em` — because the mascot is itself a 6. Keep the wordmark inside a single `.brand-word` span: the anchor is `inline-flex` with a `gap`, so a bare span around the `6` becomes its own flex item and that gap opens up between `6` and `krrt`.
- Logo SVG inline; force `filter:none!important` and size `64x64px` to defeat Tabler's autodark invert filter. The size lives in three places that must agree — the CSS rule, and the inline `width`/`height` attributes **and** `style="width:64px;height:64px"` on the `