From 66538f9ad8b0a2477a9f62a65511d120fbb1e4a7 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sat, 14 Mar 2026 21:08:57 -0400 Subject: [PATCH] Initial commit: LotusGuild Terminal Design System v1.0 Unified CSS, JavaScript utilities, HTML template, and framework skeleton files for Tinker Tickets (PHP), PULSE (Node.js), and GANDALF (Flask). Includes aesthetic_diff.md documenting every divergence between the three apps with prioritised recommendations for convergence. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 589 +++++++++++++++ aesthetic_diff.md | 437 +++++++++++ base.css | 1729 ++++++++++++++++++++++++++++++++++++++++++++ base.html | 716 ++++++++++++++++++ base.js | 794 ++++++++++++++++++++ node/middleware.js | 243 +++++++ php/layout.php | 154 ++++ python/auth.py | 92 +++ python/base.html | 141 ++++ 9 files changed, 4895 insertions(+) create mode 100644 README.md create mode 100644 aesthetic_diff.md create mode 100644 base.css create mode 100644 base.html create mode 100644 base.js create mode 100644 node/middleware.js create mode 100644 php/layout.php create mode 100644 python/auth.py create mode 100644 python/base.html diff --git a/README.md b/README.md new file mode 100644 index 0000000..2d1e707 --- /dev/null +++ b/README.md @@ -0,0 +1,589 @@ +# 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 + +Copy three files into your app's static/public directory: + +``` +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) +``` + +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 +
+ + + + + + + + + + + +
NameStatus
ticket-001Open
+
+ + + +``` + +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?) + +/* 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 */ +lt.tableNav.init('table-id') // j/k/Enter keyboard navigation +lt.sortTable.init('table-id') // click-to-sort on data-sort-key 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 ✓) diff --git a/aesthetic_diff.md b/aesthetic_diff.md new file mode 100644 index 0000000..468bd42 --- /dev/null +++ b/aesthetic_diff.md @@ -0,0 +1,437 @@ +# LotusGuild Aesthetic Diff — Convergence Guide + +Cross-app analysis of every divergence between Tinker Tickets (PHP), +PULSE (Node.js), and GANDALF (Flask) that prevents a unified aesthetic. +Each section lists the current state in each app, then the **target state** +from the unified design system (`web_template/base.css`). + +--- + +## 1. CSS Custom Property Names + +The biggest source of divergence. Gandalf uses entirely different variable names. + +| Property | Tinker Tickets | PULSE | GANDALF | **Target** | +|---|---|---|---|---| +| Main background | `--bg-primary: #0a0a0a` | `--bg-primary: #0a0a0a` | `--bg: #0a0a0a` | `--bg-primary` | +| Surface | `--bg-secondary: #1a1a1a` | `--bg-secondary: #1a1a1a` | `--bg2: #1a1a1a` | `--bg-secondary` | +| Raised surface | `--bg-tertiary: #2a2a2a` | `--bg-tertiary: #2a2a2a` | `--bg3: #2a2a2a` | `--bg-tertiary` | +| Primary green | `--terminal-green: #00ff41` | `--terminal-green: #00ff41` | `--green: #00ff41` | `--terminal-green` | +| Green tint | `--terminal-green-dim` | `--terminal-green-dim` | `--green-dim: rgba(0,255,65,.15)` | `--terminal-green-dim` | +| Amber | `--terminal-amber: #ffb000` | `--terminal-amber: #ffb000` | `--amber: #ffb000` | `--terminal-amber` | +| Amber tint | `--terminal-amber-dim` | `--terminal-amber-dim` | `--amber-dim` | `--terminal-amber-dim` | +| Cyan | `--terminal-cyan: #00ffff` | `--terminal-cyan: #00ffff` | `--cyan: #00ffff` | `--terminal-cyan` | +| Red | `--terminal-red: #ff4444` | `--terminal-red: #ff4444` | `--red: #ff4444` | `--terminal-red` | +| Body text | `--text-primary` | `--text-primary` | `--text` | `--text-primary` | +| Dim text | `--text-secondary` | `--text-secondary` | `--text-dim` | `--text-secondary` | +| Muted text | `--text-muted: #00bb33` | `--text-muted: #008822` | `--text-muted: #00bb33` | `--text-muted: #00bb33` | +| Border | `--border-color` | `--border-color` | `--border: rgba(0,255,65,.35)` | `--border-color` | +| Glow (green) | `--glow-green` | `--glow-green` | `--glow` | `--glow-green` | +| Glow (amber) | `--glow-amber` | `--glow-amber` | `--glow-amber` | `--glow-amber` ✓ | +| Font | `--font-mono` | `--font-mono` | `--font` | `--font-mono` | + +### Fix for GANDALF (`style.css`) + +Add these aliases at the top of `:root` (keeps existing rules working): +```css +:root { + /* Aliases to match unified naming */ + --bg-primary: var(--bg); + --bg-secondary: var(--bg2); + --bg-tertiary: var(--bg3); + --terminal-green: var(--green); + --terminal-green-dim:var(--green-dim); + --terminal-amber: var(--amber); + --terminal-amber-dim:var(--amber-dim); + --terminal-cyan: var(--cyan); + --terminal-red: var(--red); + --text-primary: var(--text); + --text-secondary: var(--text-dim); + --border-color: var(--border); + --glow-green: var(--glow); + --font-mono: var(--font); + --text-muted: #00bb33; /* override GANDALF's #008822 — too dark */ +} +``` + +Then migrate GANDALF's new code to use unified names. Remove aliases on next major refactor. + +--- + +## 2. Border Width & Style + +| Context | Tinker Tickets | PULSE | GANDALF | **Target** | +|---|---|---|---|---| +| Standard card / panel | `2px solid` | `2px solid` | `1px solid` | `2px solid` | +| Modal | `3px double` | `3px double` | `1px solid` | `3px double` | +| Table outer | `2px solid` | — | none | `2px solid` | +| Input fields | `2px solid` | `2px solid` | `1px solid` | `2px solid` | +| Button | `2px solid` | `2px solid` | `1px solid` (`.btn-sm`) | `2px solid` | + +**Change required in GANDALF:** Increase `.modal`, `.host-card`, `.data-table`, `.form-group input/select`, `.btn-sm` border widths from `1px` to `2px`. The `1px` borders make elements look fragile compared to the other apps. + +```css +/* GANDALF style.css — search & replace */ +.modal { border: 1px solid → border: 3px double } +.host-card { border: 1px solid → border: 2px solid } +.form-group input, +.form-group select { border: 1px solid → border: 2px solid } +.btn-sm { border: 1px solid → border: 2px solid } +``` + +--- + +## 3. Button Pattern + +| Aspect | Tinker Tickets | PULSE | GANDALF | **Target** | +|---|---|---|---|---| +| Bracket decoration | `[ text ]` via `::before`/`::after` | `[ text ]` via `::before`/`::after` | `> text` (primary only), no brackets on most buttons | `[ text ]` via `::before`/`::after` | +| Hover transform | `translateY(-2px)` | `translateY(-2px)` | none | `translateY(-2px)` | +| Hover animation | `pulse-glow-box 1.5s infinite` | none | none | none (lift is sufficient) | +| Padding (standard) | `10px 20px` | `12px 24px` | `6px 14px` (`.btn`) | `10px 20px` | +| Padding (small) | `5px 10px` | `6px 12px` | `2px 8px` | `5px 10px` | +| Font size | `0.9rem` | `1em` | `0.72em–0.8em` | `0.9rem` | +| Text transform | uppercase | uppercase | uppercase | uppercase | + +**Change required in GANDALF:** Add `::before`/`::after` bracket decorations to all `.btn`, `.btn-primary`, `.btn-secondary`, `.btn-danger` classes. Add `translateY(-2px)` hover transform. Increase padding. + +```css +/* GANDALF — add to existing .btn rules */ +.btn::before { content: '[ '; } +.btn::after { content: ' ]'; } +.btn:hover { transform: translateY(-2px); } +/* Adjust .btn-primary::before to '> ' instead of '[ ' for visual differentiation */ +.btn-primary::before { content: '> '; } +``` + +--- + +## 4. Glow Definition Consistency + +| App | Green glow | Amber glow | +|---|---|---| +| Tinker Tickets | `0 0 5px #00ff41, 0 0 10px #00ff41, 0 0 15px #00ff41` (3-layer solid) | `0 0 5px #ffb000, 0 0 10px #ffb000, 0 0 15px #ffb000` | +| PULSE | Identical to Tinker Tickets | Identical | +| GANDALF | `0 0 5px #00ff41, 0 0 10px rgba(0,255,65,.4)` (2-layer, rgba 2nd) | `0 0 5px #ffb000, 0 0 10px rgba(255,176,0,.4)` | + +GANDALF's 2-layer glow is slightly softer. Both look fine, but they appear different on the same screen if pages are compared side-by-side. + +**Change required in GANDALF:** Update glow definitions to match the 3-layer solid stack: +```css +/* GANDALF style.css */ +:root { + --glow: 0 0 5px #00ff41, 0 0 10px #00ff41, 0 0 15px #00ff41; + --glow-xl: 0 0 8px #00ff41, 0 0 16px #00ff41, 0 0 24px #00ff41, 0 0 32px rgba(0,255,65,.5); + --glow-amber: 0 0 5px #ffb000, 0 0 10px #ffb000, 0 0 15px #ffb000; +} +``` + +--- + +## 5. Section Header Pattern + +| App | Syntax | Example | +|---|---|---| +| Tinker Tickets | `╠═══ TITLE ═══╣` | Symmetric, double-bar bookends | +| PULSE | `═══ TITLE ═══` (via h3::before/after) | No `╠` character | +| GANDALF | `╠══ TITLE` (one-sided) | Only left bookend, no right | + +**Change required in PULSE and GANDALF:** Standardise to `╠═══ TITLE ═══╣`. + +PULSE (`index.html`, all `h3::before`/`h3::after`): +```css +/* PULSE: update h3 pseudo-elements */ +h3::before { content: '╠═══ '; color: var(--terminal-green); } +h3::after { content: ' ═══╣'; color: var(--terminal-green); } +``` + +GANDALF (`style.css`, `.section-title`): +```css +/* GANDALF: add right bookend */ +.section-title::after { content: ' ═══╣'; color: var(--green); } +``` + +--- + +## 6. Modal Border Style + +| App | Border | Box shadow | +|---|---|---| +| Tinker Tickets | `3px double var(--terminal-green)` | none | +| PULSE | `3px double var(--terminal-green)` | `0 0 30px rgba(0,255,65,.3)` | +| GANDALF | `1px solid var(--green)` | `0 0 30px rgba(0,255,65,.18)` | + +**Change required in GANDALF:** Upgrade to `3px double` and add PULSE-style glow: +```css +.modal { + border: 3px double var(--green); + box-shadow: 0 0 30px rgba(0, 255, 65, 0.2), 0 8px 40px rgba(0,0,0,0.8); +} +``` + +--- + +## 7. Modal Corner Characters + +| App | Top corners | Notes | +|---|---|---| +| Tinker Tickets | `╔ ╗` (double-line) | Matches `3px double` border | +| PULSE | `╔ ╗` (double-line) | Matches `3px double` border | +| GANDALF | `┌ ┐` (single-line) | Doesn't match — should be `╔ ╗` for modals | + +**Change required in GANDALF:** +```css +.modal::before { content: '╔'; } +.modal::after { content: '╗'; } +``` + +--- + +## 8. Toast Position & Animation + +| Aspect | Tinker Tickets | PULSE | GANDALF | **Target** | +|---|---|---|---|---| +| Position | `bottom: 1rem; right: 1rem` | `top: 80px; right: 20px` | `bottom: 20px; right: 20px` | `bottom: 1rem; right: 1rem` | +| Slide direction | from bottom | from top | from right | from right (`translateX(30px)`) | +| Animation duration | `0.3s ease` | `0.3s ease-out` | `0.15s ease` | `0.2s ease-out` | +| Auto-dismiss | 3500ms | 3000ms | 3500ms | `3500ms` | +| Icon format | `>> ` prefix | `✓/✗/ℹ` prefix (inline style) | `>> ` prefix | `>> ` prefix + icon in `.lt-toast-icon` | +| Queue system | Yes (serialised) | No (stacks) | No (stacks) | Yes (serialised) | + +**Change required in PULSE:** Move toast position to `bottom: 20px; right: 20px`. +Replace inline-style notification function with `.lt-toast` classes. + +**Change required in GANDALF:** Already close — update animation to `slide-in-right` instead of `slide-in` (which slides from left in Gandalf's current implementation). + +--- + +## 9. Table Cell Borders + +| App | Approach | +|---|---| +| Tinker Tickets | `border: 1px solid var(--border-color)` on every `` — full grid | +| PULSE | Minimal table usage; when present, `border-bottom` only | +| GANDALF | `border-collapse: collapse` + `border-bottom: 1px solid rgba(0,255,65,.08)` row-only | + +Both approaches are valid for different use cases. The design system provides both: +- `.lt-table` → full-grid borders (Tinker Tickets style, simple data) +- `.lt-data-table` → row-only borders (GANDALF style, dense data) + +**Action:** Migrate existing tables to the appropriate class. No visual breakage, just choose the right variant per context. + +--- + +## 10. Form Label Colour + +| App | Label colour | +|---|---| +| Tinker Tickets | `color: var(--terminal-green)` | +| PULSE | `color: var(--terminal-green)` | +| GANDALF | `color: var(--amber)` (amber labels) | + +GANDALF's amber labels create a better visual hierarchy (labels stand out from field values). The unified design system adopts **amber labels** for all apps. + +**Change required in Tinker Tickets and PULSE:** +```css +/* Tinker Tickets: assets/css/dashboard.css + ticket.css */ +label, .filter-group h4 { color: var(--terminal-amber); text-shadow: var(--glow-amber); } + +/* PULSE: index.html inline CSS */ +label { color: var(--terminal-amber); } +``` + +--- + +## 11. Nav Link Active State + +| Aspect | Tinker Tickets | PULSE (tabs) | GANDALF | **Target** | +|---|---|---|---|---| +| Active colour | amber | amber | amber | amber ✓ | +| Active background | `rgba(0,255,65,.08)` | `rgba(0,255,65,.2)` | `rgba(0,255,65,.07)` | `rgba(255,176,0,.15)` (amber tint) | +| Active border | green | amber | `border-color: var(--border)` (invisible) | amber | +| `[ ]` brackets | on `.btn` but not nav | on `.tab` | on `.nav-link` | on nav links | + +**Change required in Tinker Tickets:** Add `[ ]` bracket decoration to nav links to match GANDALF. Currently Tinker Tickets has plain nav links without brackets. + +**Change required in GANDALF:** Use amber tint background on active state instead of green tint: +```css +.nav-link.active { + background: var(--amber-dim); /* was: var(--green-dim) */ + border-color: var(--amber); +} +``` + +--- + +## 12. `text-muted` Colour Value + +| App | Value | Contrast on #0a0a0a | +|---|---|---| +| Tinker Tickets | `#00bb33` | ~4.8:1 | +| PULSE | `#008822` | ~2.9:1 ✗ WCAG AA fail | +| GANDALF | `#00bb33` | ~4.8:1 | + +**Change required in PULSE:** Update `--text-muted` from `#008822` to `#00bb33` to pass WCAG AA contrast. + +```css +/* PULSE index.html :root */ +--text-muted: #00bb33; /* was: #008822 */ +``` + +--- + +## 13. Boot Sequence — Presence & Format + +| App | Boot sequence | App name in banner | +|---|---|---| +| Tinker Tickets | Yes — `showBootSequence()` in `DashboardView.php` | "TINKER TICKETS TERMINAL v1.0" | +| PULSE | Yes — `showBootSequence()` in `index.html` | "PULSE ORCHESTRATION TERMINAL v1.0" | +| GANDALF | **No** | — | + +**Change required in GANDALF:** Add boot sequence overlay to `base.html`. + +```html + + +``` + +Plus add `lt.boot.run('GANDALF');` in `app.js` or inline at end of `base.html`. + +--- + +## 14. Status Badge Format + +| App | Format | Example | +|---|---|---| +| Tinker Tickets | `[● Open]` — `::before`/`::after` brackets | Brackets are pseudo-elements | +| PULSE | `[● Online]` — same pattern | Same | +| GANDALF | `.chip::before { content: '['; }` + `.chip::after { content: ']'; }` | Same pattern — different class names | + +The pattern is consistent. The only issue is **class names** differ: + +| Component | Tinker Tickets | PULSE | GANDALF | +|---|---|---|---| +| Full status badge | `.status-Open`, `.status-Closed` | `.status.online`, `.status.failed` | `.chip-critical`, `.chip-ok` | +| Small badge | — | `.badge` | `.badge`, `.chip` | + +**Standardise to** `.lt-status-*` (full badge) + `.lt-chip-*` (compact) + `.lt-badge-*` (inline label) going forward. Existing class names can remain as app-internal aliases. + +--- + +## 15. Scanline Effect Differences + +All three apps have the same scanline `body::before` and data-stream `body::after`. Minor differences: + +| Aspect | Tinker Tickets | PULSE | GANDALF | +|---|---|---|---| +| Scanline opacity | `rgba(0,0,0,0.15)` | `rgba(0,0,0,0.15)` | `rgba(0,0,0,.13)` | +| Flicker delay | `30s` | `30s` | `45s` | +| Data stream position | `bottom:10px; right:10px` | `bottom:10px; right:10px` | `bottom:10px; right:14px` | + +These are minor. **Standardise to:** opacity `0.15`, flicker delay `30s`, position `bottom:10px; right:14px`. + +--- + +## 16. Hover Transform on Cards / Items + +| App | Card hover | List item hover | +|---|---|---| +| Tinker Tickets | `translateY(-2px)` + glow | `translateY(-2px)` | +| PULSE | `translateY(-2px)` | `translateX(3px)` | +| GANDALF | `border-color` change only, no transform | `border-left-width` expansion | + +**Standardise to:** Cards use `translateY(-2px)`. List/row items use `border-left-width` expansion (GANDALF approach, less disorienting for dense lists). + +--- + +## 17. CSS Architecture — Inline vs. External + +| App | CSS location | +|---|---| +| Tinker Tickets | External: `assets/css/dashboard.css` + `ticket.css` | +| PULSE | **All inline** in `index.html` (`