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 <noreply@anthropic.com>
18 KiB
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):
: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.
/* 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.
/* 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:
/* 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):
/* PULSE: update h3 pseudo-elements */
h3::before { content: '╠═══ '; color: var(--terminal-green); }
h3::after { content: ' ═══╣'; color: var(--terminal-green); }
GANDALF (style.css, .section-title):
/* 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:
.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:
.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 <td> — 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:
/* 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:
.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.
/* 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.
<!-- Add to base.html <body> -->
<div id="lt-boot" class="lt-boot-overlay" data-app-name="GANDALF" style="display:none">
<pre id="lt-boot-text" class="lt-boot-text"></pre>
</div>
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 (<style> tag, ~800 lines) |
| GANDALF | External: static/style.css |
Change required in PULSE: Extract the <style> block from index.html into public/style.css. This enables:
- Browser caching
- Cache-busting with
?v=YYYYMMDD - Editor syntax highlighting
- Easier diff when updating to unified base.css
18. JavaScript Toast Implementation
| Aspect | Tinker Tickets | PULSE | GANDALF |
|---|---|---|---|
| Function name | showToast(msg, type, duration) |
showTerminalNotification(msg, type) |
showToast(msg, type) |
| Uses CSS classes | Yes (.terminal-toast) |
No (inline styles) | Yes (.toast) |
| Queue system | Yes | No | No |
| Plays audio | No | Yes (Web Audio) | No |
Standardise all three to use lt.toast.* from base.js. The function is already a superset of all three implementations.
Required changes:
- Tinker Tickets: Replace
showToast()intoast.jswithlt.toast.*calls - PULSE: Replace
showTerminalNotification()inindex.htmlwithlt.toast.*calls - GANDALF: Replace
showToast()inapp.jswithlt.toast.*calls
19. CSRF Implementation
| App | Method | Token location |
|---|---|---|
| Tinker Tickets | PHP CsrfMiddleware::getToken() — X-CSRF-Token header |
window.CSRF_TOKEN via inline script |
| PULSE | No CSRF tokens (session cookies + API key auth) | — |
| GANDALF | No CSRF tokens (Authelia SameSite=Strict) |
— |
No breaking changes needed. lt.csrf.headers() gracefully returns {} when window.CSRF_TOKEN is not set, so PULSE and GANDALF are unaffected.
20. Responsive Design Coverage
| App | Mobile-responsive? | Notes |
|---|---|---|
| Tinker Tickets | Yes — extensive breakpoints, card view below 1400px | Most complete |
| PULSE | Partial — auto-fit grid, 90vw modals |
No nav changes on mobile |
| GANDALF | Partial — grid wraps, no explicit breakpoints documented | Nav becomes scrollable |
Change required in PULSE and GANDALF: Add mobile nav handling (hamburger / collapsible), hide .lt-nav below 768px, ensure tables are horizontally scrollable on mobile.
Priority Order for Convergence
| Priority | Item | Effort | Impact |
|---|---|---|---|
| 🔴 High | GANDALF: CSS variable renaming (§1) | Medium | Eliminates all naming confusion |
| 🔴 High | PULSE: Extract inline CSS to file (§17) | Low | Enables sharing base.css |
| 🔴 High | All: Standardise toast to lt.toast (§18) |
Low | Consistent UX across apps |
| 🟡 Medium | GANDALF: Border widths 1px→2px (§2) | Low | Visual parity |
| 🟡 Medium | GANDALF: Modal → 3px double (§6) |
Low | Visual parity |
| 🟡 Medium | PULSE: Fix --text-muted contrast (§12) |
Trivial | Accessibility |
| 🟡 Medium | All: Section headers → ╠═══ X ═══╣ (§5) |
Low | Consistent branding |
| 🟡 Medium | GANDALF: Add boot sequence (§13) | Low | Brand consistency |
| 🟢 Low | GANDALF: Button [ ] brackets + hover lift (§3) |
Low | Visual consistency |
| 🟢 Low | TT + PULSE: Amber form labels (§10) | Low | Better hierarchy |
| 🟢 Low | Scanline exact values (§15) | Trivial | Micro-consistency |
| 🟢 Low | PULSE: Toast position bottom-right (§8) | Trivial | UX consistency |
| 🟢 Low | GANDALF: Glow 2-layer → 3-layer (§4) | Trivial | Visual consistency |
What Does NOT Need to Change
These differences are intentional or platform-specific and should be preserved:
- GANDALF's compact font sizes (
.7emfor table headers) — appropriate for dense network data - PULSE's tab-based navigation — appropriate for single-page app structure
- Tinker Tickets' full table cell borders — appropriate for ticket data with many columns
- PULSE's WebSocket real-time updates — architecture difference, not aesthetic
- GANDALF's collapse state stored in sessionStorage vs. Tinker Tickets using CSS class toggle — both work
- Status colour values for tickets vs. network events — apps use different semantic colours for their domain (ticket statuses vs. UP/DOWN states)
- Priority colour scale — only Tinker Tickets uses P1–P5; the other apps don't need it