# GPS Renewables Design System

A web design system for **GPS Renewables** — the world's largest biogas engineering company. This repo is the single source of truth for building internal apps (Document Packages, Transmittals, Manpower Plans, Approvals) and any new tooling that ships under the GPS brand.

The system is **Vuetify 3** based — Inter typeface, 4 px spacing, fully rounded pill buttons, MDI icons. UI should feel clean, trustworthy, and professional — the bar is "renewable energy infrastructure", not "consumer app".

---

## Source materials

| Source | Provided as | Notes |
|---|---|---|
| `uploads/GPS-Renewables-design-system-web.md` | Markdown spec (v1.1, May 2026) | Authored by the GPS Design Team. Pulled verbatim into tokens. |
| `https://gpsrenewables.com/` | Public marketing site | Used to confirm brand voice + logo URL. CORS blocked direct fetch — placeholder wordmark in `assets/`. |
| Brand fonts | Uploaded by user | `fonts/Inter-VariableFont_opsz_wght.ttf` + italic variable. Replaces the Google Fonts CDN substitution. |

> Tip: the source markdown contains Vuetify component recipes alongside the tokens. When implementing in a real Vue/Vuetify app, copy those recipes — the React components in this kit are visual reproductions, not the production code.

---

## Index

```
.
├── README.md                       ← you are here
├── SKILL.md                        ← Agent Skill entrypoint (Claude Code compatible)
├── colors_and_type.css             ← all CSS variables + helper classes
├── fonts/                          ← Inter Variable (TTF)
├── assets/
│   ├── gps-logo.svg                ← wordmark placeholder
│   └── gps-logo-mark.svg           ← sidebar mark placeholder
├── preview/                        ← Design System tab cards
│   ├── colors-*.html                       (Brand, semantic, text scale, surfaces)
│   ├── type-*.html                         (Display, UI, pairings)
│   ├── spacing-*.html                      (Scale, radius, elevation, borders)
│   ├── components-*.html                   (Buttons, chips, fields, table, app bar, …)
│   └── brand-*.html                        (Logo, app shell, bg variants, voice, icons)
└── ui_kits/
    └── gps-web/
        ├── index.html              ← Clickable Document Packages app (Vue 3 + Vuetify 3)
        ├── kit.css                 ← Inter font + targeted Vuetify overrides
        ├── theme.js                ← Vuetify createVuetify() config — GPS theme + defaults
        ├── data.js                 ← Mock rows + chip-prop helper
        ├── components.js           ← Sidebar, AppBar, GpsStepper, NewPackageModal, ExpandedRow
        └── screens.js              ← DocumentPackagesScreen, PackageDetailScreen
```

The Design System tab in the Asset Review pane is populated by the files in `preview/` plus the UI kit's `index.html`.

---

## Content fundamentals

Brand voice is **simple but easy to use** — that's the user's own description, and it shows up everywhere in the source spec. The voice rules:

| Rule | Example |
|---|---|
| **Direct, action-first labels.** Verb + noun, capitalised. | "New Doc Package", "Find Existing Docs", "Submit For Approval", "Delete Transmittal" |
| **State the fact, then the consequence.** No prefacing apologies. | "This ID already exists." not "Whoops! Looks like that ID is taken." |
| **No marketing fluff.** No exclamation marks outside of literal alerts ("Doc Pkg Setup Saved!" is the one exception in the spec). | Don't write "Amazing — your package is on its way! 🎉" |
| **Status words are single tokens, capitalised.** | Submitted · Pending · Approved · Completed · Rejected · Draft |
| **DRAFT is shouted but never coloured red.** It's an inline orange prefix on table IDs, not a chip. | <span style="color:#ffa500">DRAFT</span> #HSE02-Transmittal-001 |
| **You / your, never we / us.** Speak to one engineer doing one task. | "Choose project", not "We need to know your project" |
| **Pronouns are minimal — labels do the work.** | "Project name" beats "Tell us what project this is for" |
| **No emoji.** None. Anywhere. The MDI icon set carries all visual cues. | ✅ → `mdi-check` |
| **No casual contractions in errors.** | "This ID already exists" not "Looks like that's taken" |
| **Capitalize headings** (per the type tokens H1–H5). | "Document Packages" not "DOCUMENT PACKAGES" or "document packages" |

The vibe target: **engineer-to-engineer**. The user is on a biofuel plant project with deadlines, multiple disciplines, and audit trails — copy should respect their time and never wink.

---

## Visual foundations

### Colors
- **Primary green `#5fa414`** is reserved for the highest-intent surfaces: primary CTAs, the active sidebar pill, record IDs in detail headers, breadcrumb anchors, the "complete" stepper state. It is **never** the link colour for files — file names use `Link #007bff`.
- **Greens cluster by meaning.** `Primary` = brand, `Green-MID #28a745` = success/approve actions, `Green-DARK #006400` = confirmed/complete states. They do not interchange.
- **Reds are split:** `Error #f5524a` (form validation, destructive button outline) vs `Red #dc3545` (age / overdue badges). Distinct because they signal different urgencies.
- **Orange `#ffa500`** is the warning + DRAFT + Submitted-status colour. It's the single "this needs attention" yellow.
- Full ramp documented in `preview/colors-*.html` and `colors_and_type.css`.

### Type
- **Inter only.** Variable TTF in `fonts/`, weights used: 400 / 500 / 600 / 700. The app-bar page title is `H3` size (24 px) at weight **700** — heavier than the standalone `H3` token (weight 400). The substitution flag (Google CDN) has been removed; the brand variable font is now in-tree.
- **Capitalize headings** (`text-transform: capitalize`). Sentence-case body. No all-caps except small section labels in the sidebar (12 px caption, `Text-50`).
- **Letter spacing is 0.5 px across every token** — including body. This is unusual; do not strip it.

### Spacing
- 4 px base. Tokens `space-1` (4) through `space-16` (64). In Vuetify, `pa-4` = 16 px.
- Components agree on **`space-4` (16 px) as default internal padding**, **`space-6` (24 px) between cards and as page padding**. Sections use `space-8`, page-level uses `space-12`.

### Backgrounds
- **App background is `neutral-1 #f2f2f2` — full bleed, no images, no gradients.** Transmittal app pages use `transmittal-bg #ebefe7` (a subtle green-grey) but otherwise nothing changes.
- White (`#ffffff`) is **only** for surfaces sitting on top: cards, tables, modals, sidebar, app bar. The page background never goes white.
- No hand-drawn illustrations, no full-bleed photography, no repeating patterns inside the app. The marketing site uses imagery; the product surface is austere.

### Animation
- The source spec doesn't specify motion. Vuetify defaults apply — short (`150 ms`) ease-out transitions on hover/focus, no bouncing, no spring physics. **Reach for opacity + colour fades over translation.** Stepper progression is instant; modal open is a 200 ms fade.

### Hover / press / focus
- **Hover on primary buttons:** filter brightness 0.96 (~4 % darker). No lift, no shadow change.
- **Hover on icon buttons:** background fades to `neutral-1`, icon colour deepens to `Text-80`.
- **Hover on table rows:** subtle `#fafafa` row fill, no other change.
- **Press:** an additional brightness 0.92 step. No translate, no scale.
- **Focus on inputs:** border swaps from `Text-10` to `Primary`. No outer ring.

### Borders & dividers
- **Single border value: `1px solid Text-10 (#e7e7e7)`.** That's the divider in tables, the input border, the panel split, the sidebar right edge.
- No coloured borders except on **focused inputs** (Primary green) and **error states** (`Error` red).
- The "rounded corner with coloured left-border" trope is **explicitly avoided** — alerts use a full-width left border, not a corner accent.

### Shadows / elevation
- Vuetify's 5-step elevation: 0 (flat), 1 (default card), 2 (raised / dropdown), 4 (dialog), 8 (drawer / floating).
- Outer shadow only. No inset shadows. No coloured glows.

### Layout rules
- **Fixed elements:** the sidebar (168 px) is permanent, never collapsible in MVP. The app bar is two rows but elevation 0 — it floats over the page bg.
- Page content padding 24 px. Section gaps 24 px.
- Breakpoints `xs 0 / sm 600 / md 960 / lg 1280 / xl 1920`. Most product views target `lg`.

### Transparency & blur
- **Don't.** No frosted glass. No translucent surfaces. The one exception is the **modal overlay** at `rgba(34,34,34,0.45)` — a flat scrim, not a blur.

### Imagery
- Inside the product: avoid imagery entirely. If a state needs illustration (empty state, error state) — leave it for now and ask. Don't substitute generic stock.
- On marketing surfaces (out of this kit's scope): warm/saturated photos of biogas infrastructure, plants, and field engineers. No b&w. No grain.

### Loading states
- Use Vuetify's **`<v-skeleton-loader>`** for any data fetch that takes long enough to flash. Never use a centered spinner for table data.
- **Tables:** `<v-skeleton-loader type="table-thead, table-row@N">` where N matches the typical row count (5–10).
- **Dashboards:** one `type="card"` per KPI tile + one `type="image, article"` per chart card. Skeleton heights should approximate the real content height so layout doesn't jump on swap.
- The bone fill is overridden to use `neutral-1` (`#f2f2f2`) with a white shimmer in `ui_kits/gps-web/kit.css`. See `preview/components-skeleton-table.html` and `preview/components-skeleton-dashboard.html`. Click **Reload** in the demo's app bar to see it in action.

### Empty states
GPS uses a **subdued grey panel** for "nothing here yet" — never a friendly empty-state illustration.
- **Web** — Inside an existing surface (expanded row, card body), drop a `#ededed` panel with **6 px radius**. Centered MDI icon at 56 px in `Text-30 #b6b6b6`. Below: a single short label in `Text-50` (e.g. "No Files"). Below that: a one-line helper in `Text-60` with the action verb inlined in `Primary` green as plain bold text — *never* an actual button (the real button lives in the surrounding actions row).
- **Mobile** — Full content area becomes the panel, **12 px radius**, same `#ededed`. The icon sits in a darker `#d8d8d8` **squircle** (~72 × 72, 18 px radius) so it reads as a single object on the lighter panel. Same `Text-50` label below.
- See `preview/components-empty-state.html` and `preview/components-empty-state-mobile.html`. The web pattern is wired into the kit at `ui_kits/gps-web/` — expand the `DRAFT #ELE19` row in the demo to see it live.

### Inline notification toasts
Distinct from Vuetify's `v-snackbar`. The mobile **"Not assigned to logbook"** banner is **inline at the bottom of the content area, not floating**:
- `Orange #ffa500` block, **8 px corner radius**, white text (caption2 size), with:
  - A circular white avatar on the left containing the status `mdi-close` glyph in orange,
  - A right-side `mdi-close` dismiss button in white.
- Use this for **persistent, non-blocking context** (e.g. "this record is unassigned") — not for transient confirmations. For transient success/error use the standard `v-snackbar` floating bottom-right.

### Corner radii
| Element | Radius |
|---|---|
| Buttons, chips, badges, active-nav pill | `9999px` (fully rounded) |
| Card | 8 px |
| Modal / dialog | 8 px |
| Input · select · date picker · textarea · file input | 8 px |
| Tooltip | 4 px |

### Cards
A standard GPS card is **white, elevation-1 shadow, 8 px radius, 16 px padding**. Title is H4 (16/600 capitalize) in `Text-80`. Body is Body2 (14/400) in `Text-70`. Inside modals or expanded rows, drop to elevation 0 with a `Text-10` border.

---

## Iconography

The system uses **Material Design Icons (MDI)** — the same icon library Vuetify ships with by default. ~7,500 outlined glyphs, 2 px stroke, drawn on a 24 px canvas, rendered at **20 – 24 px** in this UI (24 px is the floor for default buttons and page-level icons; 20 px is used in small / dense contexts).

- **In the codebase:** `<v-icon>mdi-file-document-outline</v-icon>`.
- **In this kit:** the icon font is loaded from the jsDelivr CDN at `https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css` and used via `<i class="mdi mdi-{name}"></i>`. This is the upstream package — no substitution flag.
- **Common glyphs in use:** `file-document-outline`, `delete`, `download`, `eye-outline`, `chevron-down`, `magnify`, `bell-outline`, `plus`, `filter-variant`, `check`, `close`, `cloud-upload-outline`, `arrow-left`, `account-group-outline`.
- **Emoji:** never. Unicode arrows / symbols: never as a substitute for an MDI glyph (the `←` in detail headers is the one exception in the spec — and even then, the React kit swaps it for `mdi-arrow-left`).
- **PNG icons / inline SVG illustrations:** none in the product. The brand's customer-facing site uses some PNG icons in its impact stats — those are marketing assets, not product assets.

Browse the full set at <https://pictogrammers.com/library/mdi/>.

---

## UI kits

| Kit | What it covers | Path |
|---|---|---|
| **GPS Web** (Documents · Submittals · Manpower) | Three sidebar-navigable views — Document Packages list (card-row pattern), Pending Submittals review queue (with age badges + inline-status text + bulk approve/reject), and Manpower Planning (standard v-data-table). Plus three modal patterns: 3-step "Add New Doc Package", vertical-tab "Create Manpower Plan" with summary header, and the long-form Audit/Question modal triggered by the floating button. | `ui_kits/gps-web/` |

The Transmittal app variant is the same shell with a single CSS variable swap (`background: var(--gps-transmittal-bg)`); not a separate kit.

## Key list patterns

The product has **two distinct table patterns**, and choosing between them matters:

- **Card-row list** — `preview/components-card-row-list.html`. Each row is its own white card with a thin border. The row body flows inline (ID · category · meta-pairs · status indicator · chevron) — no shared header bar, no column alignment across rows. Expanding a row pushes the meta-pills + nested file table inside that card. **Use for record-as-document lists** where each row carries rich context and may need to be acted on individually (Document Packages, Pending Submittals).
- **Standard data table** — `v-data-table`. Use for **dense, comparable rows** where the user is scanning a grid (Manpower Plans, Users, Audit Logs). Standard Vuetify column headers, pagination, sort/filter icons.

The default mistake is to reach for `v-data-table` everywhere. Don't — if rows are conceptually documents with sub-records and per-row actions, use the card-row pattern.

## Modal patterns

| Pattern | When to use | Reference |
|---|---|---|
| **Stepper modal** (3–5 steps) | Multi-step record creation. Setup → details → review. | `NewPackageModal` |
| **Vertical-tabs modal** with summary header | Multi-entity data entry where each entity has its own form (contractors × disciplines, departments × roles). | `CreateManpowerPlanModal` |
| **Long stepper question modal** | Surveys, audits, inspections. Top stepper for sections, info-pill header, numbered questions in a select + comment grid. | `AuditQuestionModal` |

---

## Caveats / flags

- **Real logo could not be fetched** (CORS). `assets/gps-logo.svg` and `gps-logo-mark.svg` are placeholders. Real assets live at:
  - `https://gpsrenewables.com/wp-content/uploads/2023/07/GPS-logo.svg`
  - `https://gpsrenewables.com/wp-content/uploads/2023/07/footer-gps-logo.png`
  Drop them into `assets/` to replace.
- **No empty-state / loading-state / 404 patterns** are defined in the source spec. The kit deliberately leaves those blank — ask before inventing.
- **Motion is undefined in the source spec.** What's documented in the "Animation" section above is inferred from the Vuetify default; confirm against the live product if motion is critical to a project.
- **The kit is built on Vue 3 + Vuetify 3** — the same stack documented in the source spec. It runs straight from the Vuetify UMD CDN with no build step. Drop the same `theme.js` config (and the Vuetify `defaults` block in particular) into your production app to inherit the brand.
