# LotusGuild Terminal Design System v1.0 Unified layout, styling, and JavaScript utilities for all LotusGuild web applications. **Applies to:** Tinker Tickets (PHP) · PULSE (Node.js) · GANDALF (Flask) --- ## Quick Start **Core files** (needed by every app): ``` base.css → all variables, components, animations, responsive rules base.js → toast, modal, tabs, CSRF, fetch helpers, keyboard shortcuts base.html → full component reference (static demo, not for production) ``` **Platform-specific helpers** (one per backend): ``` php/layout.php → PHP base layout with CSP nonce + CSRF injection python/auth.py → Flask @require_auth / @require_admin decorators python/base.html → Jinja2 base template with block inheritance node/middleware.js → Express requireAuth, csrfMiddleware, cspNonce, etc. ``` Then load them in your HTML: ```html ``` --- ## Aesthetic at a Glance | Property | Value | |---|---| | Background | `#0a0a0a` (near-black) | | Primary accent | `#00ff41` (phosphor green) | | Secondary accent | `#ffb000` (amber) | | Tertiary accent | `#00ffff` (cyan) | | Error / critical | `#ff4444` (red) | | Font | `'Courier New', 'Consolas', 'Monaco', 'Menlo', monospace` | | Border radius | `0` — no rounded corners ever | | Box shadow | Glow halos only (`0 0 Xpx color`) — no drop shadows | | Borders | `2px solid` standard · `3px double` for outer frames / modals | | CRT effects | Scanline animation · screen flicker at 30s · binary corner watermark | --- ## CSS Prefix All classes use the `.lt-` prefix (*LotusGuild Terminal*) to prevent collisions with app-specific CSS. ```css .lt-btn /* buttons */ .lt-card /* cards */ .lt-frame /* ASCII outer frames */ .lt-modal-overlay /* modals */ .lt-toast /* toasts */ .lt-status-* /* status badges */ .lt-p1 … .lt-p5 /* priority badges */ /* etc. */ ``` --- ## CSS Custom Properties All colour, spacing, and layout values are CSS custom properties on `:root`. Override them per-app in your `app.css`: ```css /* Example: app-specific accent on top of base */ :root { --terminal-amber: #ffd000; /* slightly brighter amber */ } ``` ### Key Variables ```css /* Backgrounds */ --bg-primary: #0a0a0a --bg-secondary: #1a1a1a --bg-tertiary: #2a2a2a --bg-terminal: #001a00 /* Terminal colours */ --terminal-green: #00ff41 --terminal-amber: #ffb000 --terminal-cyan: #00ffff --terminal-red: #ff4444 /* Glow stacks (text-shadow) */ --glow-green --glow-green-intense --glow-amber --glow-amber-intense --glow-cyan --glow-red /* Box-shadow halos */ --box-glow-green --box-glow-amber --box-glow-red /* Z-index ladder */ --z-dropdown: 100 --z-modal: 500 --z-toast: 800 --z-overlay: 9999 ``` --- ## Components ### Buttons ```html Link Button ``` All buttons: - Transparent background, green border, `[ text ]` bracket decoration via `::before`/`::after` - Hover → amber colour, `translateY(-2px)` lift, amber glow - `border-radius: 0` — terminal style, no rounding ### Status Badges ```html Open In Progress Closed Online Failed ``` Spinning `◐` indicator on `lt-status-in-progress` and `lt-status-running`. ### Priority Badges ```html P1 Critical P2 High P3 Med P4 Low P5 Min ``` P1 has a pulsing glow animation. ### Chips (compact inline status) ```html OK Warn Critical Info ``` ### Inline Messages ```html
Something went wrong
Saved successfully
Rate limit approaching
Auto-refresh every 30s
``` ### ASCII Frames Outer frame (double-line, for major sections): ```html
Section Title
``` Inner frame (single-line, for sub-panels): ```html
Sub Section
``` The `::before` / `::after` pseudo-elements draw the top-left `╔`/`┌` and top-right `╗`/`┐` corners automatically. Bottom corners require explicit `` children because CSS cannot target `:nth-pseudo-element`. ### Cards ```html
Card Title
``` ### Tables Full-border table (simple data, terminal look): ```html
Name Status
ticket-001 Open
``` Data table (compact, row-only separators, for dense data): ```html …
``` Row classes: `lt-row-p1`–`lt-row-p5` (priority left border) · `lt-row-critical` · `lt-row-warning` · `lt-row-selected` (keyboard nav highlight) ### Forms ```html
Markdown supported.
``` Labels are amber-coloured and uppercase. Inputs: green border, focus → amber border + glow pulse. ### Modals ```html ``` - `data-modal-open="id"` → opens the overlay - `data-modal-close` → closes the nearest `.lt-modal-overlay` - Click on backdrop (`.lt-modal-overlay`) → closes automatically - ESC key → closes all open modals ### Tabs ```html
``` Active tab persists across page reloads via `localStorage`. ### Toast Notifications ```js lt.toast.success('Ticket #123 saved'); lt.toast.error('Connection refused', 5000); // custom duration ms lt.toast.warning('Rate limit 80% used'); lt.toast.info('Auto-refresh triggered'); ``` Toasts appear bottom-right, stack vertically, auto-dismiss (default 3.5s), include an audible beep. ### Stats Widgets ```html
📋
42 Open
``` Clicking a stat card calls `window.lt_onStatFilter(key, val)` — implement this function in your app JS to wire it to your filter logic. ### Sidebar ```html ``` Collapse state persists across page reloads via `sessionStorage`. ### Boot Sequence ```html ``` Runs once per session. Shows animated ASCII boot sequence. Add `data-app-name` to customise the banner text. --- ## JavaScript API (`window.lt`) ```js /* HTML safety */ lt.escHtml(str) // escape for innerHTML /* Toasts */ lt.toast.success(msg, durationMs?) lt.toast.error(msg, durationMs?) lt.toast.warning(msg, durationMs?) lt.toast.info(msg, durationMs?) /* Audio */ lt.beep('success' | 'error' | 'info') // Web Audio API beep; silent-fails if unavailable // Note: toasts automatically call lt.beep() — only call directly for non-toast events /* Modals */ lt.modal.open('modal-id') lt.modal.close('modal-id') lt.modal.closeAll() /* Tabs */ lt.tabs.init() lt.tabs.switch('panel-id') /* Boot */ lt.boot.run('APP NAME', forceFlag?) /* Keyboard shortcuts */ lt.keys.on('ctrl+s', handler) lt.keys.off('ctrl+s') lt.keys.initDefaults() // ESC close, Ctrl+K focus search, ? help /* Sidebar */ lt.sidebar.init() /* CSRF */ lt.csrf.headers() // returns { 'X-CSRF-Token': token } or {} /* Fetch helpers */ await lt.api.get('/api/tickets') await lt.api.post('/api/update_ticket.php', { id: 1, status: 'Closed' }) await lt.api.put('/api/workflows/5', payload) await lt.api.delete('/api/comment', { id: 9 }) // All throw Error on non-2xx with the server's error message /* Time */ lt.time.ago(isoString) // "5m ago" lt.time.uptime(seconds) // "14d 6h 3m" lt.time.format(isoString) // locale datetime string /* Bytes */ lt.bytes.format(1234567) // "1.18 MB" /* Table utilities — must be called explicitly (not auto-initialized) */ lt.tableNav.init('table-id') // j/k/Enter keyboard navigation on rows lt.sortTable.init('table-id') // click-to-sort on headers /* Stats widget filtering */ lt.statsFilter.init() // wires data-filter-key clicks /* Auto-refresh */ lt.autoRefresh.start(fn, 30000) // call fn every 30 s lt.autoRefresh.stop() lt.autoRefresh.now() // trigger immediately + restart timer ``` --- ## CSRF Integration ### PHP (Tinker Tickets) ```php ``` `lt.api.*` automatically includes the token as `X-CSRF-Token` header. ### Node.js (PULSE) ```js // server.js: generate token and pass to template res.render('index', { csrfToken: req.session.csrfToken }); ``` ```html ``` ### Flask (GANDALF) GANDALF uses Authelia SSO with `SameSite=Strict` cookies so CSRF tokens are not required on API endpoints. For new apps using Flask-WTF: ```html ``` --- ## Auth Integration Patterns ### PHP — Authelia headers ```php // AuthMiddleware.php $user = [ 'username' => $_SERVER['HTTP_REMOTE_USER'] ?? '', 'name' => $_SERVER['HTTP_REMOTE_NAME'] ?? '', 'email' => $_SERVER['HTTP_REMOTE_EMAIL'] ?? '', 'groups' => explode(',', $_SERVER['HTTP_REMOTE_GROUPS'] ?? ''), 'is_admin' => in_array('admin', explode(',', $_SERVER['HTTP_REMOTE_GROUPS'] ?? '')), ]; ``` ### Python/Flask — `@require_auth` decorator ```python def require_auth(f): @wraps(f) def wrapper(*args, **kwargs): user = request.headers.get('Remote-User', '') if not user: return '401 Not Authenticated', 401 groups = [g.strip() for g in request.headers.get('Remote-Groups','').split(',')] if 'admin' not in groups: return '403 Forbidden', 403 return f(*args, **kwargs) return wrapper ``` ### Node.js — Express middleware ```js function requireAuth(req, res, next) { const user = req.headers['remote-user']; if (!user) return res.status(401).json({ error: 'Not authenticated' }); req.user = { username: user, name: req.headers['remote-name'] || user, email: req.headers['remote-email'] || '', groups: (req.headers['remote-groups'] || '').split(',').map(s => s.trim()), isAdmin: (req.headers['remote-groups'] || '').split(',').includes('admin'), }; next(); } ``` --- ## Framework Skeleton Files | File | Framework | Description | |---|---|---| | `php/layout.php` | PHP / Tinker Tickets | Complete page layout with CSP nonce, CSRF, session | | `python/base.html` | Flask / Jinja2 | Jinja2 template extending pattern | | `python/auth.py` | Flask | `@require_auth` decorator + `_get_user()` | | `node/middleware.js` | Node.js / Express | Auth + CSRF + CSP nonce middleware | --- ## Directory Layout for New Apps ``` my-app/ ├── assets/ │ ├── css/ │ │ └── app.css # Extends base.css — app-specific rules only │ ├── js/ │ │ └── app.js # App logic — uses lt.* from base.js │ └── images/ │ └── favicon.png ├── (php|python|node) files… └── README.md # Link back to web_template/README.md for styling docs ``` --- ## Responsive Breakpoints | Breakpoint | Width | Changes | |---|---|---| | XL | > 1400px | Full layout, 4-col grids | | LG | ≤ 1400px | 3-col grids, 4-col stats | | MD | ≤ 1200px | 2-col grids, narrow sidebar | | SM (tablet) | ≤ 1024px | Sidebar stacks below content | | XS (phone) | ≤ 768px | Nav hidden, single-col | | XXS | ≤ 480px | Minimal padding, 2-col stats | --- ## Key Design Rules 1. **No rounded corners** — `border-radius: 0` everywhere 2. **No drop shadows** — only glow halos (`box-shadow: 0 0 Xpx color`) 3. **No sans-serif fonts** — 100% monospace stack 4. **ASCII borders** — box-drawing chars via `::before`/`::after`, never images 5. **All uppercase** — headings, labels, nav links, button text 6. **`[ brackets ]`** — buttons and nav links wrap their text in `[ ]` 7. **Green = primary** — informational / active state 8. **Amber = secondary** — headings, highlights, hover state 9. **Cyan = tertiary** — uplinks, info, POE active, prompts 10. **Red = critical** — errors, P1 priority, offline state 11. **Glow on everything critical** — `text-shadow` glow stacks for P1 and errors 12. **Amber form labels** — labels are amber, inputs are green 13. **Scanlines always on** — the `body::before` CRT overlay is non-negotiable --- ## Accessibility Notes - All interactive elements have `:focus-visible` outlines (amber, 2px) - `@media (prefers-reduced-motion)` disables all animations - Screen-reader utility class: `.lt-sr-only` - Modals set `aria-hidden` and trap focus - Status elements use semantic colour + text (not colour alone) - Minimum contrast: text-muted `#00bb33` on `#0a0a0a` ≈ 5.3:1 (WCAG AA ✓)