Files
web_template/aesthetic_diff.md
Jared Vititoe 997450aaf1 Fix boot sequence alignment, rate limiter memory leak, and docs gaps
- base.js: Fix boot sequence title centering — old formula was off by 1
  char for odd-length app names (PULSE, GANDALF). Remove unused `bar`
  variable. New logic computes left/right padding independently to
  handle both even and odd title lengths correctly.
- node/middleware.js: Prune expired rate-limit entries from Map when
  size exceeds 5000 to prevent unbounded memory growth. Also use
  req.socket?.remoteAddress as fallback for req.ip.
- README.md: Document lt.beep() in the JS API section. Clarify Quick
  Start to distinguish core files from platform-specific helpers.
  Note that tableNav.init() and sortTable.init() require explicit calls.
- aesthetic_diff.md: Correct §8 toast icon format — base.js uses
  bracketed symbols [✓][✗][!][i], not >> prefix as previously stated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 21:27:31 -04:00

18 KiB
Raw Blame History

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.72em0.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 [✓] [✗] [!] [i] 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 showTerminalNotification() with lt.toast.* calls. The icon format in base.js uses bracketed symbols: [✓], [✗], [!], [i] — matches the terminal aesthetic better than bare unicode symbols.

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). Replace showToast() calls with lt.toast.* calls.


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); }

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() in toast.js with lt.toast.* calls
  • PULSE: Replace showTerminalNotification() in index.html with lt.toast.* calls
  • GANDALF: Replace showToast() in app.js with lt.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 (.7em for 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 P1P5; the other apps don't need it