Files
reterminal-dm4/emmc-provisioning/docs/PORTAL_STYLING_GUIDE.md

293 lines
15 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.

# Portal Styling Guide (Template)
Use this document when building new portals so they match the visual and UX style of the reference portal (e.g. FreePBX / TM VOIP Extensions Portal). Replace placeholders like `[Portal Name]` with your portals name where relevant.
---
## 1. Design philosophy
- **Dark theme:** Dark backgrounds with light text; no light-mode variant in this guide.
- **Accent:** Single accent (teal/cyan gradient) for primary actions, links, and highlights.
- **Clarity:** Clear hierarchy (cards, sections, labels), consistent spacing, readable typography.
- **Consistency:** Same tokens, components, and patterns across all pages (login, app, modals).
---
## 2. Design tokens (CSS variables)
Define these in `:root` (or in a shared CSS file) and use them everywhere instead of hard-coded colors.
### Colors
| Token | Value | Usage |
|-------|--------|--------|
| `--bg-primary` | `#0a0e14` | Page background |
| `--bg-secondary` | `#11151c` | Header, secondary surfaces |
| `--bg-tertiary` | `#1a1f2b` | Inputs, table header, hover states |
| `--bg-card` | `#151a24` | Cards, modals |
| `--accent-primary` | `#00d4aa` | Primary accent (teal) |
| `--accent-secondary` | `#00b894` | Accent variant, secondary accent |
| `--accent-glow` | `rgba(0, 212, 170, 0.15)` | Focus rings, subtle highlights |
| `--text-primary` | `#e6e8eb` | Main text |
| `--text-secondary` | `#8b949e` | Labels, secondary text |
| `--text-muted` | `#5c6370` | Placeholders, disabled, hints |
| `--border-color` | `#2d333b` | Borders (cards, inputs, tables) |
| `--danger` | `#ff6b6b` | Errors, delete, destructive actions |
| `--danger-glow` | `rgba(255, 107, 107, 0.15)` | Danger focus/hover background |
| `--warning` | `#ffd93d` | Warnings |
| `--success` | `#00d4aa` | Success (can match accent) |
| `--gradient-accent` | `linear-gradient(135deg, #00d4aa 0%, #00b894 50%, #00cec9 100%)` | Primary buttons, logo text |
### Example `:root` block
```css
:root {
--bg-primary: #0a0e14;
--bg-secondary: #11151c;
--bg-tertiary: #1a1f2b;
--bg-card: #151a24;
--accent-primary: #00d4aa;
--accent-secondary: #00b894;
--accent-glow: rgba(0, 212, 170, 0.15);
--text-primary: #e6e8eb;
--text-secondary: #8b949e;
--text-muted: #5c6370;
--border-color: #2d333b;
--danger: #ff6b6b;
--danger-glow: rgba(255, 107, 107, 0.15);
--warning: #ffd93d;
--success: #00d4aa;
--gradient-accent: linear-gradient(135deg, #00d4aa 0%, #00b894 50%, #00cec9 100%);
}
```
---
## 3. Typography
- **Body / UI font:** `'Outfit', -apple-system, BlinkMacSystemFont, sans-serif`
- **Monospace (data, code, IDs):** `'JetBrains Mono', monospace`
Load from Google Fonts:
```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
```
- **Body:** `color: var(--text-primary);` `line-height: 1.6;`
- **Labels:** `font-size: 0.85rem;` `font-weight: 500;` `color: var(--text-secondary);` optional `text-transform: uppercase;` `letter-spacing: 0.5px;`
- **Card/section titles:** `font-size: 1.1rem;` `font-weight: 600;`
- **Table header:** `font-size: 0.75rem0.8rem;` `font-weight: 600;` `text-transform: uppercase;` `letter-spacing: 0.5px;` `color: var(--text-secondary);`
- **Table body:** `font-size: 0.9rem;` monospace for IDs/codes
---
## 4. Page layout
### Global
- **Reset:** `* { margin: 0; padding: 0; box-sizing: border-box; }`
- **Body:** `background: var(--bg-primary);` `color: var(--text-primary);` `min-height: 100vh;` `font-family: 'Outfit', ...`
### Background treatment (optional)
Subtle gradient overlay for depth:
```css
body::before {
content: '';
position: fixed;
inset: 0;
background:
radial-gradient(circle at 20% 20%, rgba(0, 212, 170, 0.03) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(0, 184, 148, 0.03) 0%, transparent 50%),
linear-gradient(180deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
pointer-events: none;
z-index: -1;
}
```
### Header (fixed)
- **Container:** `background: var(--bg-secondary);` `border-bottom: 1px solid var(--border-color);` `position: fixed; top: 0; left: 0; right: 0; z-index: 1000;` optional `backdrop-filter: blur(10px);`
- **Top row:** Logo left; status/user/actions right; `padding: 1rem 2rem;` `display: flex; align-items: center; justify-content: space-between;`
- **Tabs row:** Under the top row; `background: var(--bg-tertiary);` `padding: 0.5rem 2rem;` `border-top: 1px solid var(--border-color);` horizontal flex, gap, overflow-x auto for small screens
### Main content
- **Container:** `max-width: 1400px;` `margin: 0 auto;` `padding: 2rem;` `padding-top: calc(2rem + 140px);` (offset for fixed header + tabs). On mobile reduce padding and increase top offset if header stacks.
---
## 5. Logo
- **Wrapper:** flex, `align-items: center;` `gap: 0.75rem;`
- **Icon:** Square (e.g. 40×40px), `background: var(--gradient-accent);` `border-radius: 10px;` optional `box-shadow: 0 4px 20px var(--accent-glow);` emoji or icon inside.
- **Title (h1):** `font-size: 1.5rem;` `font-weight: 600;` `background: var(--gradient-accent);` `-webkit-background-clip: text;` `background-clip: text;` `-webkit-text-fill-color: transparent;`
---
## 6. Tabs (main navigation)
- **Tab button (default):** `padding: 0.75rem 1.5rem;` `background: transparent;` `border: none;` `color: var(--text-secondary);` `font-size: 0.95rem;` `font-weight: 500;` `border-radius: 8px;` flex with icon + label, `gap: 0.5rem;`
- **Hover:** `color: var(--text-primary);` `background: var(--bg-tertiary);`
- **Active:** `background: var(--gradient-accent);` `color: var(--bg-primary);`
- **Tab content:** `display: none;` by default; `.tab-content.active { display: block; }` optional fade-in animation.
---
## 7. Cards
- **Base:** `background: var(--bg-card);` `border: 1px solid var(--border-color);` `border-radius: 16px;` `padding: 1.5rem;` `margin-bottom: 1.5rem;`
- **Card header:** flex, `justify-content: space-between;` `align-items: center;` `margin-bottom: 1.5rem;` `padding-bottom: 1rem;` `border-bottom: 1px solid var(--border-color);`
- **Card title:** `font-size: 1.1rem;` `font-weight: 600;` flex with icon + text, `gap: 0.5rem;`
---
## 8. Buttons
- **Base:** `padding: 0.75rem 1.5rem;` `border-radius: 8px;` `font-size: 0.9rem;` `font-weight: 500;` inline-flex, `align-items: center;` `gap: 0.5rem;` `transition: all 0.2s ease;`
- **Primary:** `background: var(--gradient-accent);` `color: var(--bg-primary);` `border: none;` hover: slight `translateY(-2px);` `box-shadow: 0 4px 20px var(--accent-glow);`
- **Secondary:** `background: var(--bg-tertiary);` `border: 1px solid var(--border-color);` `color: var(--text-primary);` hover: `border-color: var(--accent-primary);`
- **Danger:** `background: transparent;` `border: 1px solid var(--danger);` `color: var(--danger);` hover: `background: var(--danger);` `color: white;`
- **Disabled:** `opacity: 0.5;` `cursor: not-allowed;`
- **Icon-only (small):** e.g. 28×28px, `border-radius: 6px;` same semantic colors (e.g. `.btn-remove` with `--danger`).
---
## 9. Forms
- **Grid:** `display: grid;` `grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));` `gap: 1rem;`
- **Form group:** flex column, `gap: 0.5rem;`
- **Label:** `font-size: 0.85rem;` `font-weight: 500;` `color: var(--text-secondary);` optional uppercase + letter-spacing
- **Input / select:** `padding: 0.75rem 1rem;` `background: var(--bg-tertiary);` `border: 1px solid var(--border-color);` `border-radius: 8px;` `color: var(--text-primary);` `font-size: 0.9rem;` monospace for IDs/codes
- **Focus:** `outline: none;` `border-color: var(--accent-primary);` `box-shadow: 0 0 0 3px var(--accent-glow);`
- **Placeholder:** `color: var(--text-muted);`
- **Read-only:** `background: var(--bg-secondary);` `cursor: default;`
- **Checkbox:** `accent-color: var(--accent-primary);` (or custom size e.g. 18×18px)
- **Password field:** wrapper with toggle button; input `padding-right: 3rem;` so toggle doesnt overlap text.
---
## 10. Tables
- **Container:** `overflow-x: auto;` `border-radius: 12px;` `border: 1px solid var(--border-color);`
- **Table:** `width: 100%;` `border-collapse: collapse;`
- **th / td:** `padding: 0.6rem 0.75rem;` `text-align: left;` `border-bottom: 1px solid var(--border-color);` `color: var(--text-primary);`
- **th:** `background: var(--bg-tertiary);` `font-size: 0.75rem;` `font-weight: 600;` `text-transform: uppercase;` `letter-spacing: 0.5px;`
- **tbody tr hover:** `background-color: var(--bg-tertiary);`
- **Last row:** `tr:last-child td { border-bottom: none; }`
- **Data cells:** `font-family: 'JetBrains Mono', monospace;` `font-size: 0.9rem;`
- **Actions column:** right-aligned; min-width for action buttons; `.table-actions { display: flex; gap: 1rem; flex-wrap: wrap; }`
- **Data table variant:** `.data-table` with alternating row background (e.g. `nth-child(even)` subtle `rgba(255,255,255,0.02)`) and same hover.
---
## 11. Badges and status
- **Status badge (e.g. connection):** flex, `align-items: center;` `gap: 0.5rem;` `padding: 0.5rem 1rem;` `background: var(--bg-tertiary);` `border-radius: 20px;` `font-size: 0.85rem;` `border: 1px solid var(--border-color);`
- **Status dot:** 8×8px circle; `.connected { background: var(--success); }` `.error { background: var(--danger); }` optional pulse animation
- **Pill badge (e.g. extension ID):** `padding: 0.25rem 0.75rem;` `background: var(--accent-glow);` `color: var(--accent-primary);` `border-radius: 20px;` `font-weight: 500;`
- **Tech badge (e.g. PJSIP/SIP):** small, `border-radius: 4px;` `font-size: 0.75rem;` `font-weight: 600;` `text-transform: uppercase;` distinct colors per type (e.g. PJSIP blue, SIP purple)
---
## 12. Search and filters
- **Search box:** wrapper relative; input `padding: 0.6rem 1rem 0.6rem 2.5rem;` `width: 250px;` same colors as form inputs; optional `::before` search icon (e.g. 🔍) `left: 0.75rem;`
- **Filter checkbox:** inline-flex, `align-items: center;` `gap: 0.4rem;` `color: var(--text-secondary);` `accent-color: var(--accent-primary);`
---
## 13. Empty and loading states
- **Empty state:** `text-align: center;` `padding: 3rem;` `color: var(--text-muted);`
- **No results:** same idea, `padding: 2rem;`
- **Loading:** flex center, `padding: 2rem;` spinner (e.g. 32×32px border, `border-top-color: var(--accent-primary);` `animation: spin 1s linear infinite;`)
---
## 14. Modals
- **Overlay:** `position: fixed;` `inset: 0;` `background: rgba(0,0,0,0.7);` `z-index: 1000;` flex center; `display: none;` `.active { display: flex; }`
- **Dialog:** `background: var(--bg-card);` `border: 1px solid var(--border-color);` `border-radius: 16px;` `padding: 2rem;` `max-width: 500px;` `width: 90%;` optional scale-in animation
- **Modal header:** flex, `align-items: center;` `gap: 1rem;` `margin-bottom: 1.5rem;`
- **Modal icon:** e.g. 48×48px, `background: var(--accent-glow);` `border-radius: 12px;` centered content
- **Modal title:** `font-size: 1.25rem;` `font-weight: 600;`
- **Detail sections:** `margin-bottom: 2rem;` section title `font-size: 1.1rem;` `color: var(--accent-primary);` `border-bottom: 1px solid var(--border-color);` `padding-bottom: 0.5rem;`
- **Details grid:** `grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));` `gap: 1rem;` each item: label (small, secondary) + value (primary)
---
## 15. Toasts (notifications)
- **Container:** `position: fixed;` `top: 1rem;` `right: 1rem;` `z-index: 1100;` flex column, `gap: 0.5rem;`
- **Toast:** `padding: 1rem 1.5rem;` `background: var(--bg-card);` `border: 1px solid var(--border-color);` `border-radius: 8px;` flex, `align-items: center;` `gap: 0.75rem;` `max-width: 400px;` slide-in animation
- **Success:** `border-left: 3px solid var(--success);`
- **Error:** `border-left: 3px solid var(--danger);`
---
## 16. Pagination
- **Wrapper:** flex, `justify-content: center;` `align-items: center;` `gap: 0.5rem;` `padding: 1rem;` `border-top: 1px solid var(--border-color);`
- **Button:** `padding: 0.5rem 0.75rem;` `background: var(--bg-tertiary);` `border: 1px solid var(--border-color);` `border-radius: 6px;` `color: var(--text-primary);` `font-size: 0.85rem;` `min-width: 36px;`
- **Hover:** `background: var(--accent-glow);` `border-color: var(--accent-primary);` `color: var(--accent-primary);`
- **Active page:** `background: var(--accent-primary);` `color: var(--bg-primary);`
- **Disabled:** `opacity: 0.4;` `cursor: not-allowed;`
- **Info text:** `color: var(--text-secondary);` `font-size: 0.85rem;` between prev/next
---
## 17. Login page
- **Layout:** full viewport, flex center; `padding: 2rem;`
- **Card:** same tokens as app cards; `max-width: 400px;` `padding: 2.5rem;` `border-radius: 16px;` `box-shadow: 0 8px 32px rgba(0,0,0,0.3);`
- **Logo:** centered; icon (e.g. 64×64px) with gradient + glow; title with gradient text
- **Form:** same form-group and input styles as app; full-width primary submit button
- **Error message:** `color: var(--danger);` `font-size: 0.9rem;` above or below form
- Use the same `:root` variables and body background so login and app feel like one product.
---
## 18. Responsive
- **Breakpoint:** e.g. `@media (max-width: 768px)`
- **Header top:** `flex-direction: column;` `gap: 1rem;` `text-align: center;` reduce padding
- **Tabs:** reduce horizontal padding; allow horizontal scroll if needed
- **Container:** reduce padding; increase `padding-top` if header height grows (e.g. `calc(1rem + 180px)`)
- **Form grid:** `grid-template-columns: 1fr;` for single column on small screens
---
## 19. Checklist for a new portal
- [ ] Copy or recreate the `:root` design tokens.
- [ ] Load **Outfit** and **JetBrains Mono** (or same weights).
- [ ] Use the same header structure: logo + status/user + actions, then tabs.
- [ ] Use `.card`, `.card-header`, `.card-title` for sections.
- [ ] Use `.btn`, `.btn-primary`, `.btn-secondary`, `.btn-danger` for actions.
- [ ] Use `.form-grid`, `.form-group`, and input/select styles for forms.
- [ ] Use `.table-container`, table, `.data-table` and th/td styles for lists.
- [ ] Use same modal overlay/dialog and toast styles.
- [ ] Use same empty, loading, and error states.
- [ ] Apply same login page layout and token usage.
- [ ] Test at 768px width for basic responsive behavior.
- [ ] Replace [Portal Name] and any product-specific labels in this guide for your portal.
---
## 20. Reference files (this repo)
| File | Purpose |
|------|--------|
| `static/css/main.css` | Full implementation of tokens, layout, components |
| `app/templates/base.html` | App shell: fonts, header, tabs, container, toasts |
| `app/templates/login.html` | Login layout and inline tokens (can be moved to main.css) |
| `app/templates/tabs/_dashboard.html` | Example: cards, stats, table container |
| `app/templates/tabs/_users.html` | Example: card, form-grid, form-group, buttons, table |
For a new portal, you can copy `main.css` and adapt it (e.g. change `:root` if you need a different accent), then build your base template and pages to use the same class names and structure described above.