Files
pi_mcps/docs/wiki/pages/CannaManage-06-Wireframes.md
T
Patrick Plate cda8946c75 docs(cannamanage): add CannaManage wiki pages and mockup images
- 11 wiki pages: CannaManage-Home + 01-10 covering full Phase 0 docs
- 5 mockup images in docs/wiki/images/
- Updated _Sidebar.md with CannaManage section
2026-04-06 11:21:35 +02:00

551 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CannaManage — Wireframes & UI Mockups
**Phase 4a | Document 6 of 7**
**Date:** 2026-04-06
**Stack:** Spring Boot 3.x · PrimeFaces JSF · PostgreSQL
---
## Table of Contents
1. [Design System Overview](#1-design-system-overview)
2. [Admin Portal Screens](#2-admin-portal-screens)
3. [Member Portal Screens](#3-member-portal-screens)
4. [Navigation & Information Architecture](#4-navigation--information-architecture)
5. [Responsive Design Notes](#5-responsive-design-notes)
6. [Accessibility](#6-accessibility)
---
## 1. Design System Overview
### 1.1 Color Palette
| Token | Hex | Usage |
|---|---|---|
| `--color-primary` | `#2D5016` | Sidebar background, primary buttons, active nav items |
| `--color-primary-medium` | `#4A7C28` | Hover states, section headers, badge outlines |
| `--color-accent` | `#8BC34A` | Highlights, progress bars filled, success indicators |
| `--color-bg` | `#F5F5F5` | Page background, card backgrounds |
| `--color-text` | `#1A1A1A` | Body text, table cell content |
| `--color-warning` | `#FF6B35` | Quota >80%, low stock, warnings |
| `--color-error` | `#D32F2F` | Quota exceeded, recalled batches, destructive actions |
| `--color-white` | `#FFFFFF` | Sidebar text, button labels on dark bg, card surfaces |
### 1.2 Typography
| Element | Font | Size | Weight |
|---|---|---|---|
| H1 — Page title | Inter | 24px | 600 |
| H2 — Section heading | Inter | 18px | 600 |
| H3 — Card title | Inter | 14px | 600 |
| Body / table rows | Inter | 14px | 400 |
| Caption / label | Inter | 12px | 400 |
| Mono (codes, IDs) | JetBrains Mono | 13px | 400 |
### 1.3 Component Library
All UI components come from **PrimeFaces 13.x** (JSF-based). No external React/Angular dependencies in MVP.
| Component | Usage |
|---|---|
| `p:panel` | Section containers, card wrappers |
| `p:dataTable` with `p:column` | Tabular data: distributions, members, batches |
| `p:paginator` | Pagination on all tables |
| `p:inputText` | Single-line text fields |
| `p:inputNumber` | Weight inputs (gram precision) |
| `p:selectOneMenu` | Dropdown selects (member, strain, batch) |
| `p:calendar` | Date range pickers for reports |
| `p:progressBar` | Quota consumption display |
| `p:commandButton` | Primary and secondary actions |
| `p:confirmDialog` | Dangerous actions (recall, delete) |
| `p:messages` / `p:message` | Inline validation errors |
| `p:badge` | Status indicators (AVAILABLE, LOW, RECALLED) |
| `p:sidebar` | Mobile nav drawer (member portal) |
| `p:dialog` | Modal overlays |
### 1.4 Layout Grid
```
┌────────────────────────────────────────────────────┐
│ TOP NAVBAR (56px) club name · avatar · logout │
├──────────────┬─────────────────────────────────────┤
│ │ │
│ SIDEBAR │ MAIN CONTENT │
│ (240px) │ (fluid, min 784px) │
│ fixed │ │
│ │ │
└──────────────┴─────────────────────────────────────┘
```
- **Sidebar:** fixed left, `#2D5016` background, white nav labels with `#8BC34A` icons
- **Top Navbar:** `#FFFFFF` with bottom border `#E0E0E0`, breadcrumb left, user controls right
- **Main Content:** `#F5F5F5` background, 24px padding, max content width 1200px centered
---
## 2. Admin Portal Screens
### Screen 1 — Admin Dashboard
![Admin Dashboard](images/mockup-admin-dashboard.png)
#### ASCII Wireframe
```
┌─────────────────────────────────────────────────────────────────────┐
│ 🌿 CannaManage Grüne Oase Berlin e.V. 👤 Max M. [⏻] │
├────────────┬────────────────────────────────────────────────────────┤
│ │ Dashboard 🗓 April 2026 │
│ 📊 Dashboard◄│ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ 👥 Members│ │ Total Members│ │ Distributions│ │ Stock Available│ │
│ │ │ │ │ This Month │ │ │ │
│ 📋 Distrib│ │ 142 │ │ 87 │ │ 3,240 g │ │
│ │ │ ▲ +3 MoM │ │ ▲ +12 MoM │ │ ▼ -800g MoM │ │
│ 📦 Stock │ └──────────────┘ └──────────────┘ └───────────────┘ │
│ │ │
│ 📄 Reports│ Recent Distributions [+ New Entry] │
│ │ ┌─────────────────────────────────────────────────┐ │
│ ✅ Complian│ │ Member │ Strain │ Qty │ Date │ ✓ │ │
│ │ ├─────────────┼─────────────┼───────┼───────┼────┤ │
│ ⚙ Settings│ │ Müller, A. │ OG Kush B12 │ 5.0g │ 06.04 │ ✓ │ │
│ │ │ Schmidt, K. │ Amnesia H09 │ 3.5g │ 06.04 │ ✓ │ │
│ │ │ Weber, T. │ OG Kush B12 │ 7.0g │ 05.04 │ ✓ │ │
│ │ │ … │ │ │ │ │ │
└────────────┴──┴─────────────────────────────────────────────────────┘
```
#### Components & Behavior
| Component | PrimeFaces | Behavior |
|---|---|---|
| KPI Cards | `p:panel` with custom CSS | Auto-refreshed via `@poll` every 60s |
| Recent Distributions table | `p:dataTable` (5 rows, no paginator) | Row click → navigate to distribution detail |
| Member column link | `p:commandLink` | Navigate to `/admin/members/{id}` |
| `+ New Entry` button | `p:commandButton` style="primary" | Navigate to `/admin/distributions/new` |
| Trend indicators | Custom CSS `<span>` | Green ▲ / Red ▼ with delta value |
---
### Screen 2 — Distribution Recording Form
![Distribution Form](images/mockup-distribution-form.png)
#### ASCII Wireframe
```
┌─────────────────────────────────────────────────────────────────────┐
│ 🌿 CannaManage Grüne Oase Berlin e.V. 👤 Max M. [⏻] │
├────────────┬────────────────────────────────────────────────────────┤
│ │ Distributions New Distribution │
│ 📊 Dashbrd│ │
│ │ ┌──────────────────────────────────────────────────┐ │
│ 👥 Members│ │ Member * │ │
│ │ │ ┌──────────────────────────────────────────┐ │ │
│ 📋 Distrib◄│ │ │ 🔍 Search by name or member no. │ │ │
│ │ │ └──────────────────────────────────────────┘ │ │
│ 📦 Stock │ │ │ │
│ │ │ Strain / Batch * │ │
│ 📄 Reports│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ │ Select available batch ▼ │ │ │
│ ✅ Complian│ │ └──────────────────────────────────────────┘ │ │
│ │ │ │ │
│ ⚙ Settings│ │ Weight (grams) * │ │
│ │ │ ┌──────────┐ │ │
│ │ │ │ 0.0 g │ ← p:inputNumber min=0.1 max=25 │ │
│ │ │ └──────────┘ │ │
│ │ │ │ │
│ │ │ Monthly Quota — Müller, Anna │ │
│ │ │ ████████████░░░░░░░░ 32.5g / 50g 65% │ │
│ │ │ [████████████████░░░] <- p:progressBar │ │
│ │ │ │ │
│ │ │ [ Record Distribution ] [Cancel] │ │
│ │ └──────────────────────────────────────────────────┘ │
└────────────┴────────────────────────────────────────────────────────┘
```
#### Compliance UX — Real-Time Quota Indicator
The quota progress bar updates live as the weight field changes (via `f:ajax event="keyup"`):
| Quota Used After Distribution | Bar Color | Submit Button | Message |
|---|---|---|---|
| 079% | `#8BC34A` (green) | Enabled | — |
| 8099% | `#FF6B35` (orange) | Enabled | "⚠ Approaching monthly limit" |
| 100% | `#D32F2F` (red) | **Disabled** | "🚫 Monthly limit reached (50g)" |
| Over-21 member, >30g monthly | `#D32F2F` (red) | **Disabled** | "🚫 Under-21 limit reached (30g)" |
#### Components & Behavior
| Component | PrimeFaces | Behavior |
|---|---|---|
| Member search | `p:selectOneMenu` with `p:ajax` filter | Filters on type, shows name + member no. |
| Strain/Batch dropdown | `p:selectOneMenu` | Populated after member selection; shows only `AVAILABLE` batches |
| Weight input | `p:inputNumber` min=`0.1` max=`25.0` step=`0.1` | Triggers quota recalculation on blur |
| Quota bar | `p:progressBar` with dynamic `value` | Color class applied via `styleClass` computed in backing bean |
| Submit | `p:commandButton` | Disabled via `disabled="#{bean.quotaExceeded}"` |
| Cancel | `p:link` | Returns to distribution log without saving |
---
### Screen 3 — Stock Management
![Stock Management](images/mockup-stock-management.png)
#### ASCII Wireframe
```
┌─────────────────────────────────────────────────────────────────────┐
│ 🌿 CannaManage Grüne Oase Berlin e.V. 👤 Max M. [⏻] │
├────────────┬────────────────────────────────────────────────────────┤
│ │ Stock Management [+ Add Batch] │
│ 📊 Dashbrd│ │
│ │ ┌──────────────────────┐ ┌────────────────────┐ │
│ 👥 Members│ │ 🔍 Filter by strain │ │ Status: All ▼ │ │
│ │ └──────────────────────┘ └────────────────────┘ │
│ 📋 Distrib│ │
│ │ ┌───────────────────────────────────────────────────┐ │
│ 📦 Stock ◄│ │ Strain │Batch│THC% │CBD%│ Qty │Status│Act │ │
│ │ ├──────────────┼─────┼─────┼────┼───────┼──────┼────┤ │
│ 📄 Reports│ │ OG Kush │B-12 │ 19% │ 1% │ 850g │ ● │[R] │ │
│ │ │ Amnesia Haze │H-09 │ 22% │<1% │ 72g │ ⚠ │[R] │ │
│ ✅ Complian│ │ Blue Dream │D-05 │ 17% │ 2% │ 0g │ — │[R] │ │
│ │ │ Hindu Kush │K-21 │ 8% │15% │ 340g │ ✓ │[R] │ │
│ ⚙ Settings│ │ AK-47 #4 │A-03 │ 20% │ 1% │ RECALLED │ ⛔ │[R] │ │
│ │ └───────────────────────────────────────────────────┘ │
│ │ [◄ 1 2 3 … ►] Showing 1-10/42 │
└────────────┴────────────────────────────────────────────────────────┘
```
#### Status Badges
| Badge | Color | Icon | Condition |
|---|---|---|---|
| `AVAILABLE` | `#4A7C28` bg | ✓ checkmark | `qty > 100g` and not recalled |
| `LOW` | `#FF6B35` bg | ⚠ warning | `0 < qty ≤ 100g` |
| `EXHAUSTED` | `#9E9E9E` bg | — dash | `qty = 0` |
| `RECALLED` | `#D32F2F` bg | ⛔ stop | `recall_date IS NOT NULL` |
#### Components & Behavior
| Component | PrimeFaces | Behavior |
|---|---|---|
| Strain filter | `p:inputText` with `filterBy` | Filters table client-side on keyup |
| Status filter | `p:selectOneMenu` | Filters table rows by status value |
| Batch table | `p:dataTable` lazy=`true` | Server-side pagination, 10 rows/page |
| Status badge | Custom CSS `<span class="badge badge-{status}">` | Icon + text label (not color alone) |
| Recall button | `p:commandButton` styleClass=`p-button-danger` | Opens `p:confirmDialog` before executing |
| Confirm dialog | `p:confirmDialog` | "Recall batch B-12 (OG Kush, 850g)? This cannot be undone." |
| Add Batch | `p:commandButton` | Opens `p:dialog` with batch entry form |
---
### Screen 4 — Compliance Report Generation
![Compliance Report](images/mockup-compliance-report.png)
#### ASCII Wireframe
```
┌─────────────────────────────────────────────────────────────────────┐
│ 🌿 CannaManage Grüne Oase Berlin e.V. 👤 Max M. [⏻] │
├────────────┬────────────────────────────────────────────────────────┤
│ │ Reports Monthly Compliance Report │
│ 📊 Dashbrd│ │
│ │ ┌─────────────────────────────────────────────────┐ │
│ 👥 Members│ │ Reporting Period │ │
│ │ │ Month: [ March ▼ ] Year: [ 2026 ▼ ] │ │
│ 📋 Distrib│ │ [ Generate Report ] │ │
│ │ └─────────────────────────────────────────────────┘ │
│ 📦 Stock │ │
│ │ ┌─────────────────────────────────────────────────┐ │
│ 📄 Reports◄│ │ PDF PREVIEW │ │
│ │ │ ┌─────────────────────────────────────────┐ │ │
│ ✅ Complian│ │ │ 🌿 CannaManage — Monthly Report Mar 2026 │ │ │
│ │ │ │ Club: Grüne Oase Berlin e.V. │ │ │
│ ⚙ Settings│ │ │ ─────────────────────────────────────── │ │ │
│ │ │ │ Total Members: 142 │ │ │
│ │ │ │ Active Members (distributed): 87 │ │ │
│ │ │ │ Total Distributed: 435.5g │ │ │
│ │ │ └─────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ │ [⬇ Download PDF] [⬇ Download CSV] │ │
│ │ └─────────────────────────────────────────────────┘ │
│ │ │
│ │ Summary Table │
│ │ ┌────────────────────────────────────────────────┐ │
│ │ │ Metric │ Value │ Limit │ │
│ │ ├──────────────────────┼───────────┼─────────────┤ │
│ │ │ Members >50g/month │ 0 │ Must be 0 │ │
│ │ │ Members >30g (U21) │ 0 │ Must be 0 │ │
│ │ │ Recalled Batches │ 1 │ — (info) │ │
│ │ │ Avg grams / member │ 5.0g │ — │ │
│ │ └────────────────────────────────────────────────┘ │
└────────────┴────────────────────────────────────────────────────────┘
```
#### Components & Behavior
| Component | PrimeFaces | Behavior |
|---|---|---|
| Month selector | `p:selectOneMenu` | Months JanDec |
| Year selector | `p:selectOneMenu` | Current year ± 2 |
| Generate button | `p:commandButton` | Calls report service; shows spinner; renders PDF thumbnail |
| PDF preview | `<iframe>` embedding `/report/preview?month=3&year=2026` | Generated by iText 7 in `cannamanage-report` module |
| Download PDF | `p:commandButton` | Streams PDF response from REST endpoint |
| Download CSV | `p:commandButton` | Streams CSV response (member-level data) |
| Summary table | `p:dataTable` | Computed compliance metrics; zero violations = green row |
---
## 3. Member Portal Screens
### Screen 5 — Member Dashboard / Quota View
![Member Quota](images/mockup-member-quota.png)
#### ASCII Wireframe
```
┌────────────────────────────────────────────────────┐
│ 🌿 CannaManage Anna Müller #M-0042 │
│ ────────────────────────────────────────────── │
│ │
│ Monthly Quota Remaining │
│ │
│ ╭───────────────╮ │
│ │ │ │
│ │ 17.5 g │ │
│ │ remaining │ │
│ │ │ │
│ │ of 50g/month │ │
│ ╰───────────────╯ │
│ ▓▓▓▓▓▓▓▓▓▓▓░░░░░░░ 65% used │
│ │
│ Distribution History │
│ ┌────────────────────────────────────────────┐ │
│ │ Date │ Strain │ Quantity │ │
│ ├────────────┼──────────────┼────────────────┤ │
│ │ 06.04.2026 │ OG Kush │ 5.0g │ │
│ │ 02.04.2026 │ Amnesia Haze │ 12.5g │ │
│ │ 28.03.2026 │ OG Kush │ 15.0g │ │
│ └────────────┴──────────────┴────────────────┘ │
│ │
│ Available Strains │
│ ┌────────────────────────────────────────────┐ │
│ │ Strain │ Availability │ │
│ ├──────────────┼─────────────────────────────┤ │
│ │ OG Kush │ ● Available │ │
│ │ Amnesia Haze │ ⚠ Limited │ │
│ │ Hindu Kush │ ● Available │ │
│ └────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────┘
```
#### Compliance Note — Available Strains Display
Per CanG §§67, members may NOT see specific batch quantities or total stock levels. The **Available Strains** table shows only:
- Strain name
- Availability status (Available / Limited / Unavailable)
Quantities, batch codes, and THC/CBD percentages are **not exposed** in the member portal.
#### Components & Behavior
| Component | PrimeFaces | Behavior |
|---|---|---|
| Quota circle | Custom CSS radial progress (`conic-gradient`) | Computed from monthly total; color matches threshold rules |
| Quota bar | `p:progressBar` | Same color logic as admin distribution form |
| History table | `p:dataTable` | Last 10 distributions; sorted newest first; no pagination in MVP |
| Strains table | `p:dataTable` | `status` column: text + icon only, no quantities |
---
### Screen 6 — Member Login
> *No mockup image — ASCII wireframe only.*
#### ASCII Wireframe
```
┌──────────────────────────────────────────┐
│ │
│ 🌿 CannaManage │
│ │
│ ┌──────────────────────────────────┐ │
│ │ E-Mail Address │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ you@example.com │ │ │
│ │ └────────────────────────────┘ │ │
│ │ │ │
│ │ Password │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ •••••••••••• │ │ │
│ │ └────────────────────────────┘ │ │
│ │ │ │
│ │ [ ████ Log In ████████████ ] │ │
│ └──────────────────────────────────┘ │
│ │
│ Problems logging in? │
│ Contact your club administrator. │
│ │
└──────────────────────────────────────────┘
```
#### Design Decisions
- **No self-registration link** — member accounts are created exclusively by admins via the admin portal. The login page has no "Create account" or "Sign up" flow.
- **No forgot-password link** — password resets are initiated by the club admin only. The login page directs users to contact their admin, avoiding email-based reset flows that would require verified email infrastructure in MVP.
- **No social login** — DSGVO compliance and club accountability require traceable credential management.
- **Form submission:** POST to `/login` (Spring Security form login), redirect to `/member/dashboard` on success.
#### Components & Behavior
| Component | PrimeFaces | Behavior |
|---|---|---|
| Email field | `p:inputText` with `required="true"` | Bean Validation `@Email` |
| Password field | `p:password` feedback=`false` | No strength meter on login |
| Login button | `p:commandButton` | Submit form; shows `p:messages` on failure |
| Error message | `p:messages` | "Invalid email or password." (never specific about which field failed) |
---
## 4. Navigation & Information Architecture
```mermaid
graph TD
Root["CannaManage Root"]
Root --> AdminPortal["Admin Portal /admin/"]
Root --> MemberPortal["Member Portal /member/"]
AdminPortal --> AdminDash["Dashboard (default)"]
AdminPortal --> Members["Members"]
Members --> MemberList["Member List"]
Members --> MemberDetail["Member Detail"]
AdminPortal --> Distributions["Distributions"]
Distributions --> DistLog["Distribution Log"]
Distributions --> NewDist["New Distribution"]
AdminPortal --> Stock["Stock"]
Stock --> Strains["Strains"]
Stock --> Batches["Batches"]
AdminPortal --> Reports["Reports"]
Reports --> MonthlyReport["Monthly Compliance"]
Reports --> MemberExport["Member Export"]
Reports --> RecallReport["Batch Recall Report"]
AdminPortal --> Compliance["Compliance"]
Compliance --> PreventionOfficer["Prevention Officer Info"]
AdminPortal --> Settings["Settings"]
Settings --> ClubProfile["Club Profile"]
MemberPortal --> MemberDash["Dashboard / Quota"]
MemberPortal --> DistHistory["Distribution History"]
MemberPortal --> StockAvail["Stock Availability"]
```
### URL Structure
| Path | Description | Role |
|---|---|---|
| `/login` | Login page | Public |
| `/admin/dashboard` | Admin home | `ROLE_ADMIN` |
| `/admin/members` | Member list | `ROLE_ADMIN` |
| `/admin/members/{id}` | Member detail | `ROLE_ADMIN` |
| `/admin/distributions` | Distribution log | `ROLE_ADMIN` |
| `/admin/distributions/new` | New distribution form | `ROLE_ADMIN` |
| `/admin/stock/strains` | Strain catalog | `ROLE_ADMIN` |
| `/admin/stock/batches` | Batch management | `ROLE_ADMIN` |
| `/admin/reports/monthly` | Compliance reports | `ROLE_ADMIN` |
| `/admin/reports/members` | Member data export | `ROLE_ADMIN` |
| `/admin/reports/recall` | Recall report | `ROLE_ADMIN` |
| `/admin/compliance` | Prevention officer | `ROLE_ADMIN` |
| `/admin/settings` | Club settings | `ROLE_ADMIN` |
| `/member/dashboard` | Member quota view | `ROLE_MEMBER` |
| `/member/distributions` | Personal history | `ROLE_MEMBER` |
| `/member/stock` | Strain availability | `ROLE_MEMBER` |
---
## 5. Responsive Design Notes
### MVP (v1) — Desktop-First
Target viewport: **1024px+**. PrimeFaces responsive grid (`p:panelGrid` with responsive columns, `ui-g-12 ui-md-6 ui-lg-4`) handles most layout adaptation down to tablet without custom media queries.
| Breakpoint | Behavior |
|---|---|
| `≥ 1280px` | Full layout — sidebar + content side-by-side |
| `10241279px` | Sidebar collapses to icon-only (60px); tooltips on hover |
| `7681023px` | Sidebar hidden; hamburger menu in top navbar |
| `< 768px` | Admin portal degraded (tables scroll horizontally) |
### Member Portal — Mobile-First from Day One
Members will typically check quota status on their phone. The member portal is designed mobile-first regardless of MVP/v2 timeline.
| Breakpoint | Behavior |
|---|---|
| `≥ 1024px` | Two-column layout: quota circle left, history right |
| `7681023px` | Single-column, full-width cards |
| `375767px` | Single-column, compact quota ring, condensed table |
| `< 375px` | Minimum supported; no horizontal scroll |
### v2 Roadmap
- PWA manifest + service worker (offline quota display)
- 768px and 375px explicit breakpoints with design tokens
- Touch-friendly `p:sidebar` for mobile member nav
- Push notifications for low quota warnings
---
## 6. Accessibility
CannaManage targets **WCAG 2.1 AA** compliance across both portals.
### Keyboard Navigation
| Element | Keyboard Behavior |
|---|---|
| Navigation sidebar | Tab navigates items; Enter activates |
| Data tables | Tab to table; arrow keys for row navigation |
| Dropdown menus | Enter/Space to open; arrow keys to navigate; Escape to close |
| Modal dialogs | Focus trapped inside; Escape to close; first focusable element receives focus on open |
| Confirmation dialogs | Tab between Confirm and Cancel; Enter on focused button |
### Screen Reader Support
- All `p:inputText` / `p:inputNumber` fields have `<label>` with `for` attribute
- `aria-label` set on icon-only buttons (e.g., recall action column)
- `aria-live="polite"` region on quota bar — announces percentage changes
- `aria-describedby` links compliance warning messages to the weight input
- PrimeFaces generates `role="grid"` and `aria-rowcount` on all data tables
### Color Independence
Status badges must never rely on color alone:
| Status | Color | Icon | Text label |
|---|---|---|---|
| AVAILABLE | Green | ✓ | "Available" |
| LOW | Orange | ⚠ | "Low Stock" |
| RECALLED | Red | ⛔ | "Recalled" |
| EXHAUSTED | Gray | — | "Exhausted" |
Quota progress bar additionally shows numeric percentage text alongside color change.
### Contrast Ratios
| Foreground | Background | Ratio | AA pass |
|---|---|---|---|
| `#FFFFFF` | `#2D5016` | 9.1:1 | ✅ |
| `#1A1A1A` | `#F5F5F5` | 16.0:1 | ✅ |
| `#1A1A1A` | `#FFFFFF` | 19.0:1 | ✅ |
| `#FFFFFF` | `#D32F2F` | 5.1:1 | ✅ |
| `#FFFFFF` | `#FF6B35` | 3.1:1 | ⚠ verify at large text only |
---
*Next: [07-CODING-STANDARDS.md](07-CODING-STANDARDS.md)*