Files
web_template/base.html
T
jared 0eb91f1937 v1.1: Add 10 new feature modules + 18 CSS component sections
JS modules added:
- lt.theme    — dark/light toggle, OS preference sync, localStorage persist
- lt.notif    — notification badge (set/inc/clear) on any element
- lt.rightDrawer — right-side detail panel with focus trap + return focus
- lt.contextMenu — right-click custom menu, keyboard nav, danger variant
- lt.offline  — navigator.onLine banner + event hooks
- lt.ws       — WebSocket manager with exponential backoff reconnect
- lt.combobox — multi-select with search, tag chips, keyboard nav
- lt.typeahead — async/sync autocomplete with match highlighting
- lt.cookie   — get/set/del with SameSite/Secure helpers
- lt.splitPane — pointer-events resizable split pane (horizontal/vertical)
- Toast queue: max-stack, progress bar drain animation, auto-drain

CSS sections added (51–68):
- Light theme (html[data-theme="light"]) with full variable overrides
- Theme toggle button (.lt-theme-btn)
- Skeleton loader variants (card, row, text, title, avatar, btn, badge)
- Empty state component (.lt-empty-state, --sm variant)
- Nav notification badge (.lt-notif-wrap / .lt-notif-badge)
- Right-side drawer (.lt-drawer-right + overlay)
- Sticky table header (.lt-table-sticky-wrap)
- Multi-select combobox (.lt-combobox, tags, dropdown)
- Context menu (.lt-context-menu, divider, label, danger)
- Offline banner (.lt-offline-banner)
- Timeline / activity feed (.lt-timeline, color variants)
- Avatar + avatar group + status ring (.lt-avatar)
- Split pane (.lt-split, .lt-split-divider with pointer drag)
- Chart container (.lt-chart-wrap, legend, axis, loading state)
- Toast queue stack + progress drain bar
- Autocomplete / typeahead (.lt-typeahead-dropdown, match highlight)
- WebSocket status indicator (.lt-ws-status, data-state variants)
- Print enhancements (extended @media print rules)

HTML demo sections for all new components added to base.html

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 22:29:55 -04:00

1516 lines
77 KiB
HTML
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.
<!DOCTYPE html>
<!--
LOTUSGUILD TERMINAL DESIGN SYSTEM v2.0 — base.html
Reference template showing every component and layout pattern.
This file is a STATIC DEMO. Framework-specific wiring is in:
php/ → PHP / Tinker Tickets
python/ → Flask / Jinja2 / GANDALF
node/ → Express / EJS / PULSE
To build a new app, copy one of the framework skeletons from those
sub-directories and reference base.css + base.js.
-->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#030508">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>MY APP — LotusGuild</title>
<meta name="description" content="LotusGuild infrastructure application">
<!-- =========================================================
Security headers are set server-side. CSP nonce is injected
by SecurityHeadersMiddleware (PHP) / helmet (Node) / Flask.
All <script> tags need: nonce="NONCE_PLACEHOLDER"
========================================================= -->
<!-- Monospace font: JetBrains Mono -->
<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:ital,wght@0,400;0,600;0,700;1,400&display=swap" rel="stylesheet">
<!-- Hacker template design system CSS -->
<link rel="stylesheet" href="/web_template/base.css">
<!--
App-specific CSS (override or extend after base.css):
<link rel="stylesheet" href="/assets/css/app.css">
-->
<link rel="icon" href="/assets/images/favicon.png" type="image/png">
</head>
<body>
<!-- ===========================================================
BOOT SEQUENCE OVERLAY
Displays once per session. Remove if not desired.
data-app-name → used in the boot text banner.
=========================================================== -->
<div id="lt-boot" class="lt-boot-overlay" data-app-name="MY APP" style="display:none">
<pre id="lt-boot-text" class="lt-boot-text"></pre>
</div>
<!-- ===========================================================
HEADER
=========================================================== -->
<!-- ===========================================================
MOBILE NAV DRAWER (hidden on desktop, slides in on mobile)
=========================================================== -->
<div id="lt-nav-drawer" class="lt-nav-drawer" aria-hidden="true" role="dialog" aria-label="Navigation menu">
<div class="lt-nav-drawer-header">
<span class="lt-brand-title">MY APP</span>
<button class="lt-nav-drawer-close" id="lt-nav-drawer-close" aria-label="Close menu"></button>
</div>
<nav class="lt-nav-drawer-links" aria-label="Mobile navigation">
<a href="/" class="lt-nav-drawer-link active" aria-current="page">Dashboard</a>
<a href="/tickets" class="lt-nav-drawer-link">Tickets</a>
<a href="/workers" class="lt-nav-drawer-link">Workers</a>
<div class="lt-nav-drawer-section">Admin</div>
<a href="/admin/templates" class="lt-nav-drawer-link lt-nav-drawer-link--indent">Templates</a>
<a href="/admin/workflow" class="lt-nav-drawer-link lt-nav-drawer-link--indent">Workflow</a>
<a href="/admin/audit-log" class="lt-nav-drawer-link lt-nav-drawer-link--indent">Audit Log</a>
<a href="/admin/api-keys" class="lt-nav-drawer-link lt-nav-drawer-link--indent">API Keys</a>
</nav>
</div>
<div id="lt-nav-overlay" class="lt-nav-drawer-overlay"></div>
<header class="lt-header">
<div class="lt-header-left">
<!-- Hamburger — visible only on tablet/mobile (CSS hides on desktop) -->
<button class="lt-menu-btn" id="lt-menu-btn" aria-label="Open navigation menu" aria-expanded="false" aria-controls="lt-nav-drawer">
<span></span><span></span><span></span>
</button>
<!-- Brand -->
<div class="lt-brand">
<span class="lt-brand-title lt-glitch" data-text="MY APP">MY APP</span>
<span class="lt-brand-subtitle">LotusGuild Infrastructure</span>
</div>
<!-- Horizontal nav links (desktop) -->
<nav class="lt-nav" aria-label="Main navigation">
<a href="/" class="lt-nav-link active">Dashboard</a>
<a href="/tickets" class="lt-nav-link">Tickets</a>
<a href="/workers" class="lt-nav-link">Workers</a>
<!-- Dropdown example (admin menu) -->
<div class="lt-nav-dropdown">
<a href="#" class="lt-nav-link">Admin ▾</a>
<ul class="lt-nav-dropdown-menu">
<li><a href="/admin/templates">Templates</a></li>
<li><a href="/admin/workflow">Workflow</a></li>
<li><a href="/admin/audit-log">Audit Log</a></li>
<li><a href="/admin/api-keys">API Keys</a></li>
</ul>
</div>
</nav>
</div>
<div class="lt-header-right">
<!-- WebSocket status indicator -->
<div class="lt-ws-status" id="lt-ws-indicator" data-state="disconnected" aria-label="WebSocket status">
<span class="lt-dot"></span><span>Offline</span>
</div>
<!-- Theme toggle -->
<button class="lt-theme-btn" id="lt-theme-btn" aria-label="Switch to light mode" title="Switch to light mode"></button>
<!-- Notifications with badge -->
<span class="lt-notif-wrap" id="lt-notif-bell" aria-label="Notifications">
<button class="lt-btn lt-btn-sm" style="padding:0 0.5rem;min-height:32px;" aria-label="Open notifications">🔔</button>
</span>
<!-- Current user -->
<span class="lt-header-user">operator</span>
<!-- Admin badge (only show for admins) -->
<span class="lt-badge-admin">admin</span>
</div>
</header>
<!-- Right-side detail drawer -->
<div id="lt-detail-drawer" class="lt-drawer-right" aria-hidden="true" role="dialog" aria-label="Detail panel" data-overlay="lt-detail-overlay">
<div class="lt-drawer-right-header">
<span class="lt-drawer-right-title">// TICKET DETAIL</span>
<button class="lt-drawer-right-close" data-drawer-close aria-label="Close detail panel"></button>
</div>
<div class="lt-drawer-right-body">
<div class="lt-kv-grid">
<div class="lt-kv-row"><span class="lt-kv-label">ID</span><span class="lt-kv-value lt-text-cyan">#123456789</span></div>
<div class="lt-kv-row"><span class="lt-kv-label">Status</span><span class="lt-kv-value"><span class="lt-badge lt-badge-open">Open</span></span></div>
<div class="lt-kv-row"><span class="lt-kv-label">Priority</span><span class="lt-kv-value"><span class="lt-badge lt-badge-p1">P1 Critical</span></span></div>
<div class="lt-kv-row"><span class="lt-kv-label">Assignee</span><span class="lt-kv-value" style="display:flex;align-items:center;gap:0.5rem"><span class="lt-avatar lt-avatar--sm lt-avatar--orange">JD</span>jdoe</span></div>
<div class="lt-kv-row"><span class="lt-kv-label">Created</span><span class="lt-kv-value">2026-03-10</span></div>
</div>
<div class="lt-divider-label" style="margin:1rem 0">Description</div>
<p style="font-size:0.78rem;color:var(--text-secondary);line-height:1.6">Storage array link-down on compute-storage-01. Affects prod write path. Investigate RAID controller firmware.</p>
<div class="lt-divider-label" style="margin:1rem 0">Activity</div>
<div class="lt-timeline">
<div class="lt-timeline-item lt-timeline-item--orange">
<div class="lt-timeline-meta"><span class="lt-timeline-actor">jdoe</span> assigned ticket<span class="lt-timeline-time">2h ago</span></div>
<div class="lt-timeline-body">Assigned to self, escalated to P1.</div>
</div>
<div class="lt-timeline-item">
<div class="lt-timeline-meta"><span class="lt-timeline-actor">sysbot</span> auto-created<span class="lt-timeline-time">3h ago</span></div>
<div class="lt-timeline-body">Alert triggered: <code>node_network_up = 0</code></div>
</div>
<div class="lt-timeline-item lt-timeline-item--dim">
<div class="lt-timeline-meta"><span class="lt-timeline-actor">monitor</span> detected<span class="lt-timeline-time">3h ago</span></div>
<div class="lt-timeline-body">NIC link-down detected on large1:enp35s0.</div>
</div>
</div>
</div>
<div class="lt-drawer-right-footer">
<button class="lt-btn lt-btn-sm" onclick="lt.rightDrawer.close('lt-detail-drawer')">Close</button>
<button class="lt-btn lt-btn-primary lt-btn-sm">Save Changes</button>
</div>
</div>
<div id="lt-detail-overlay" class="lt-drawer-right-overlay"></div>
<!-- ===========================================================
MAIN CONTENT AREA
=========================================================== -->
<main class="lt-main lt-container">
<!-- Page title bar -->
<div class="lt-page-header">
<h1 class="lt-page-title">Dashboard</h1>
<div class="lt-btn-group">
<a href="/create" class="lt-btn lt-btn-primary">New Ticket</a>
<button class="lt-btn lt-btn-sm" data-modal-open="export-modal">Export</button>
</div>
</div>
<!-- ==========================================================
STATS WIDGETS
data-filter-key / data-filter-val → wired by lt.statsFilter
========================================================== -->
<div class="lt-stats-grid">
<div class="lt-stat-card active" data-filter-key="status" data-filter-val="Open">
<span class="lt-stat-icon">📋</span>
<div class="lt-stat-info">
<span class="lt-stat-value">42</span>
<span class="lt-stat-label">Open</span>
</div>
</div>
<div class="lt-stat-card" data-filter-key="priority" data-filter-val="1">
<span class="lt-stat-icon">🔴</span>
<div class="lt-stat-info">
<span class="lt-stat-value">3</span>
<span class="lt-stat-label">Critical</span>
</div>
</div>
<div class="lt-stat-card" data-filter-key="assigned_to" data-filter-val="0">
<span class="lt-stat-icon">👤</span>
<div class="lt-stat-info">
<span class="lt-stat-value">11</span>
<span class="lt-stat-label">Unassigned</span>
</div>
</div>
<div class="lt-stat-card" data-filter-key="created" data-filter-val="today">
<span class="lt-stat-icon">📅</span>
<div class="lt-stat-info">
<span class="lt-stat-value">7</span>
<span class="lt-stat-label">Today</span>
</div>
</div>
</div>
<!-- ==========================================================
TAB NAVIGATION
========================================================== -->
<div class="lt-tabs">
<button class="lt-tab active" data-tab="tab-table">Table View</button>
<button class="lt-tab" data-tab="tab-kanban">Kanban</button>
<button class="lt-tab" data-tab="tab-workers">Workers</button>
</div>
<!-- ==========================================================
SIDEBAR + CONTENT LAYOUT
========================================================== -->
<div class="lt-layout">
<!-- Sidebar filter panel -->
<aside class="lt-sidebar" id="lt-sidebar">
<div class="lt-sidebar-header">
Filters
<button class="lt-sidebar-toggle" data-sidebar-toggle="lt-sidebar"
aria-label="Collapse filters"></button>
</div>
<div class="lt-sidebar-body">
<div class="lt-filter-group">
<span class="lt-filter-label">Status</span>
<label class="lt-filter-option">
<input type="checkbox" class="lt-checkbox" checked> Open
</label>
<label class="lt-filter-option">
<input type="checkbox" class="lt-checkbox"> Pending
</label>
<label class="lt-filter-option">
<input type="checkbox" class="lt-checkbox"> In Progress
</label>
<label class="lt-filter-option">
<input type="checkbox" class="lt-checkbox"> Closed
</label>
</div>
<div class="lt-filter-group">
<span class="lt-filter-label">Priority</span>
<label class="lt-filter-option">
<input type="checkbox" class="lt-checkbox"> P1 Critical
</label>
<label class="lt-filter-option">
<input type="checkbox" class="lt-checkbox"> P2 High
</label>
<label class="lt-filter-option">
<input type="checkbox" class="lt-checkbox"> P3 Medium
</label>
</div>
<div class="lt-filter-group">
<span class="lt-filter-label">Assigned To</span>
<div class="lt-form-group">
<select class="lt-select lt-btn-sm">
<option value="">All users</option>
<option>operator</option>
<option>admin</option>
</select>
</div>
</div>
<div class="lt-btn-group">
<button class="lt-btn lt-btn-sm">Apply</button>
<button class="lt-btn lt-btn-sm lt-btn-ghost">Reset</button>
</div>
</div>
</aside>
<!-- Main content -->
<div class="lt-content">
<!-- Toolbar: search + actions -->
<div class="lt-toolbar">
<div class="lt-toolbar-left">
<div class="lt-search">
<input type="search" class="lt-input lt-search-input"
placeholder="Search tickets..." aria-label="Search">
</div>
<button class="lt-btn lt-btn-sm">Advanced ▾</button>
</div>
<div class="lt-toolbar-right">
<span class="lt-text-muted lt-text-xs">42 results</span>
<button class="lt-btn lt-btn-sm lt-btn-ghost">Bulk Actions</button>
</div>
</div>
<!-- ==================================================
TAB PANEL: TABLE VIEW
================================================== -->
<div id="tab-table" class="lt-tab-panel active">
<!-- Outer ASCII frame wrapping the table -->
<div class="lt-frame">
<span class="lt-frame-bl"></span>
<span class="lt-frame-br"></span>
<div class="lt-section-header">Ticket Queue</div>
<div class="lt-table-wrap">
<table class="lt-table lt-table-responsive" id="ticket-table">
<thead>
<tr>
<th><input type="checkbox" class="lt-checkbox" aria-label="Select all"></th>
<th data-sort-key="id">ID</th>
<th data-sort-key="priority">Priority</th>
<th data-sort-key="title">Title</th>
<th data-sort-key="status">Status</th>
<th data-sort-key="assignee">Assignee</th>
<th data-sort-key="created">Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<!-- P1 Critical row -->
<tr class="lt-row-p1 lt-row-critical">
<td data-label="Select"><input type="checkbox" class="lt-checkbox"></td>
<td data-label="ID"><a href="/ticket/123456789">#123456789</a></td>
<td data-label="Priority"><span class="lt-p1">P1 Critical</span></td>
<td data-label="Title">Storage array link-down on compute-storage-01</td>
<td data-label="Status"><span class="lt-status lt-status-open">Open</span></td>
<td data-label="Assignee" class="lt-text-muted">Unassigned</td>
<td data-label="Created" class="lt-text-xs lt-text-muted">5m ago</td>
<td data-label="Actions">
<div class="lt-btn-group">
<a href="/ticket/123456789" class="lt-btn lt-btn-sm">View</a>
<button class="lt-btn lt-btn-sm lt-btn-danger">Close</button>
</div>
</td>
</tr>
<!-- P2 High row -->
<tr class="lt-row-p2 lt-row-warning">
<td data-label="Select"><input type="checkbox" class="lt-checkbox"></td>
<td data-label="ID"><a href="/ticket/987654321">#987654321</a></td>
<td data-label="Priority"><span class="lt-p2">P2 High</span></td>
<td data-label="Title">Switch port flapping on USW-Pro-24</td>
<td data-label="Status"><span class="lt-status lt-status-in-progress">In Progress</span></td>
<td data-label="Assignee">operator</td>
<td data-label="Created" class="lt-text-xs lt-text-muted">2h ago</td>
<td data-label="Actions">
<div class="lt-btn-group">
<a href="/ticket/987654321" class="lt-btn lt-btn-sm">View</a>
</div>
</td>
</tr>
<!-- P3 Medium row -->
<tr class="lt-row-p3">
<td data-label="Select"><input type="checkbox" class="lt-checkbox"></td>
<td data-label="ID"><a href="/ticket/111222333">#111222333</a></td>
<td data-label="Priority"><span class="lt-p3">P3 Med</span></td>
<td data-label="Title">Scheduled maintenance: replace SFP+ on large1</td>
<td data-label="Status"><span class="lt-status lt-status-pending">Pending</span></td>
<td data-label="Assignee">admin</td>
<td data-label="Created" class="lt-text-xs lt-text-muted">1d ago</td>
<td data-label="Actions">
<div class="lt-btn-group">
<a href="/ticket/111222333" class="lt-btn lt-btn-sm">View</a>
</div>
</td>
</tr>
<!-- P4 closed -->
<tr class="lt-row-p4">
<td data-label="Select"><input type="checkbox" class="lt-checkbox"></td>
<td data-label="ID"><a href="/ticket/444555666">#444555666</a></td>
<td data-label="Priority"><span class="lt-p4">P4 Low</span></td>
<td data-label="Title">Update SSL cert on wiki.lotusguild.org</td>
<td data-label="Status"><span class="lt-status lt-status-closed">Closed</span></td>
<td data-label="Assignee">operator</td>
<td data-label="Created" class="lt-text-xs lt-text-muted">3d ago</td>
<td data-label="Actions">
<div class="lt-btn-group">
<a href="/ticket/444555666" class="lt-btn lt-btn-sm">View</a>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div><!-- /.lt-frame -->
</div><!-- /#tab-table -->
<!-- ==================================================
TAB PANEL: KANBAN
================================================== -->
<div id="tab-kanban" class="lt-tab-panel">
<div class="lt-grid-4">
<!-- Kanban column: Open -->
<div class="lt-frame">
<span class="lt-frame-bl"></span>
<span class="lt-frame-br"></span>
<div class="lt-section-header">Open</div>
<div class="lt-section-body">
<div class="lt-card lt-mb-md lt-row-p1">
<div class="lt-flex lt-flex-between lt-mb-md">
<span class="lt-p1">P1</span>
<span class="lt-dot lt-dot-up"></span>
</div>
<div class="lt-text-sm">Storage array link-down</div>
<div class="lt-text-xs lt-text-muted lt-mt-sm">5m ago · Unassigned</div>
</div>
<div class="lt-card lt-row-p3">
<div class="lt-flex lt-flex-between lt-mb-md">
<span class="lt-p3">P3</span>
<span class="lt-dot lt-dot-up"></span>
</div>
<div class="lt-text-sm">Update node_exporter on micro1</div>
<div class="lt-text-xs lt-text-muted lt-mt-sm">1d ago · operator</div>
</div>
</div>
</div>
<!-- Kanban column: Pending -->
<div class="lt-frame">
<span class="lt-frame-bl"></span>
<span class="lt-frame-br"></span>
<div class="lt-section-header">Pending</div>
<div class="lt-section-body">
<div class="lt-card lt-row-p2">
<div class="lt-flex lt-flex-between lt-mb-md">
<span class="lt-p2">P2</span>
<span class="lt-dot lt-dot-warn"></span>
</div>
<div class="lt-text-sm">Scheduled maintenance window</div>
<div class="lt-text-xs lt-text-muted lt-mt-sm">2d ago · admin</div>
</div>
</div>
</div>
<!-- Kanban column: In Progress -->
<div class="lt-frame">
<span class="lt-frame-bl"></span>
<span class="lt-frame-br"></span>
<div class="lt-section-header">In Progress</div>
<div class="lt-section-body">
<div class="lt-card lt-row-p2 lt-item-running">
<div class="lt-flex lt-flex-between lt-mb-md">
<span class="lt-p2">P2</span>
<span class="lt-dot lt-dot-warn"></span>
</div>
<div class="lt-text-sm">Switch port flapping on USW-Pro</div>
<div class="lt-text-xs lt-text-muted lt-mt-sm">2h ago · operator</div>
</div>
</div>
</div>
<!-- Kanban column: Closed -->
<div class="lt-frame">
<span class="lt-frame-bl"></span>
<span class="lt-frame-br"></span>
<div class="lt-section-header">Closed</div>
<div class="lt-section-body">
<div class="lt-card lt-row-p4" style="opacity:0.6">
<div class="lt-flex lt-flex-between lt-mb-md">
<span class="lt-p4">P4</span>
<span class="lt-dot lt-dot-idle"></span>
</div>
<div class="lt-text-sm">Update SSL cert on wiki</div>
<div class="lt-text-xs lt-text-muted lt-mt-sm">3d ago · operator</div>
</div>
</div>
</div>
</div><!-- /.lt-grid-4 -->
</div><!-- /#tab-kanban -->
<!-- ==================================================
TAB PANEL: WORKERS
================================================== -->
<div id="tab-workers" class="lt-tab-panel">
<div class="lt-grid-3">
<div class="lt-card">
<div class="lt-flex lt-flex-between lt-mb-md">
<span class="lt-text-amber lt-text-upper">pulse-worker-01</span>
<span class="lt-status lt-status-online">Online</span>
</div>
<div class="lt-data-table-wrapper">
<table class="lt-data-table">
<tr><td class="lt-text-muted">CPU</td> <td>12%</td></tr>
<tr><td class="lt-text-muted">Memory</td> <td>2.1 GB / 8 GB</td></tr>
<tr><td class="lt-text-muted">Load</td> <td>0.42 / 0.51 / 0.48</td></tr>
<tr><td class="lt-text-muted">Uptime</td> <td>14d 6h</td></tr>
<tr><td class="lt-text-muted">Tasks</td> <td>2 / 5</td></tr>
</table>
</div>
</div>
<div class="lt-card">
<div class="lt-flex lt-flex-between lt-mb-md">
<span class="lt-text-amber lt-text-upper">pulse-worker-02</span>
<span class="lt-status lt-status-offline">Offline</span>
</div>
<div class="lt-msg lt-msg-warning">Last seen 14m ago</div>
</div>
<div class="lt-card">
<div class="lt-empty">No more workers registered.</div>
</div>
</div>
</div><!-- /#tab-workers -->
</div><!-- /.lt-content -->
</div><!-- /.lt-layout -->
<!-- ==========================================================
COMPONENT SHOWCASE (remove in production)
========================================================== -->
<div class="lt-divider"></div>
<!-- Inner frame + subsection example -->
<div class="lt-frame">
<span class="lt-frame-bl"></span>
<span class="lt-frame-br"></span>
<div class="lt-section-header">Component Reference</div>
<div class="lt-frame-inner">
<div class="lt-subsection-header">Buttons</div>
<div class="lt-section-body">
<div class="lt-btn-group">
<button class="lt-btn">Default</button>
<button class="lt-btn lt-btn-primary">Primary</button>
<button class="lt-btn lt-btn-danger">Danger</button>
<button class="lt-btn lt-btn-sm">Small</button>
<button class="lt-btn lt-btn-ghost">Ghost</button>
<button class="lt-btn" disabled>Disabled</button>
</div>
</div>
<div class="lt-subsection-header">Status Badges</div>
<div class="lt-section-body lt-flex lt-flex-wrap lt-gap-md">
<span class="lt-status lt-status-open">Open</span>
<span class="lt-status lt-status-pending">Pending</span>
<span class="lt-status lt-status-in-progress">In Progress</span>
<span class="lt-status lt-status-closed">Closed</span>
<span class="lt-status lt-status-online">Online</span>
<span class="lt-status lt-status-offline">Offline</span>
<span class="lt-status lt-status-running">Running</span>
<span class="lt-status lt-status-completed">Completed</span>
<span class="lt-status lt-status-failed">Failed</span>
</div>
<div class="lt-subsection-header">Priority Badges</div>
<div class="lt-section-body lt-flex lt-flex-wrap lt-gap-md">
<span class="lt-priority lt-p1">P1 Critical</span>
<span class="lt-priority lt-p2">P2 High</span>
<span class="lt-priority lt-p3">P3 Med</span>
<span class="lt-priority lt-p4">P4 Low</span>
<span class="lt-priority lt-p5">P5 Min</span>
</div>
<div class="lt-subsection-header">Chips &amp; Badges</div>
<div class="lt-section-body lt-flex lt-flex-wrap lt-gap-sm">
<span class="lt-chip lt-chip-ok">OK</span>
<span class="lt-chip lt-chip-warn">Warning</span>
<span class="lt-chip lt-chip-critical">Critical</span>
<span class="lt-chip lt-chip-info">Info</span>
<span class="lt-badge lt-badge-green">v1.0</span>
<span class="lt-badge lt-badge-amber">Beta</span>
<span class="lt-badge lt-badge-red">Deprecated</span>
<span class="lt-badge-admin">admin</span>
</div>
<div class="lt-subsection-header">Status Dots</div>
<div class="lt-section-body lt-flex lt-gap-md">
<span class="lt-flex lt-gap-sm"><span class="lt-dot lt-dot-up"></span> Up</span>
<span class="lt-flex lt-gap-sm"><span class="lt-dot lt-dot-down"></span> Down</span>
<span class="lt-flex lt-gap-sm"><span class="lt-dot lt-dot-warn"></span> Degraded</span>
<span class="lt-flex lt-gap-sm"><span class="lt-dot lt-dot-idle"></span> Idle</span>
</div>
<div class="lt-subsection-header">Inline Messages</div>
<div class="lt-section-body">
<div class="lt-msg lt-msg-error">Database connection refused on 10.10.10.50</div>
<div class="lt-msg lt-msg-success">Ticket #123456789 updated successfully</div>
<div class="lt-msg lt-msg-warning">Rate limit: 80% consumed (40/50 req/min)</div>
<div class="lt-msg lt-msg-info">Auto-refresh active — updates every 30s</div>
</div>
<div class="lt-subsection-header">Forms</div>
<div class="lt-section-body">
<div class="lt-grid-2">
<div>
<div class="lt-form-group">
<label class="lt-label lt-label-required" for="eg-title">Ticket Title</label>
<input id="eg-title" type="text" class="lt-input" placeholder="Describe the issue">
</div>
<div class="lt-form-group">
<label class="lt-label" for="eg-priority">Priority</label>
<select id="eg-priority" class="lt-select">
<option>P1 — Critical</option>
<option>P2 — High</option>
<option selected>P3 — Medium</option>
<option>P4 — Low</option>
<option>P5 — Minimal</option>
</select>
</div>
<div class="lt-form-group">
<label class="lt-label" for="eg-desc">Description</label>
<textarea id="eg-desc" class="lt-textarea" placeholder="Markdown supported..."></textarea>
<span class="lt-form-hint">Markdown supported. Use #123456789 to link tickets.</span>
</div>
<div class="lt-form-group">
<label class="lt-label">
<input type="checkbox" class="lt-checkbox"> Confidential ticket
</label>
</div>
<div class="lt-btn-group">
<button class="lt-btn lt-btn-primary">Submit</button>
<button class="lt-btn lt-btn-ghost">Cancel</button>
</div>
</div>
<div>
<div class="lt-search lt-form-group">
<label class="lt-label" for="eg-search">Search</label>
<input id="eg-search" type="search" class="lt-input lt-search-input"
placeholder="Ctrl+K to focus">
</div>
<div class="lt-form-group">
<label class="lt-label">Loading state (skeleton)</label>
<div class="lt-skeleton lt-p-md" style="height:40px"></div>
</div>
<div class="lt-form-group">
<label class="lt-label">Empty state</label>
<div class="lt-empty" style="padding:1rem">No results found</div>
</div>
<div class="lt-form-group">
<label class="lt-label">Loading indicator</label>
<div class="lt-loading" style="padding:1rem"></div>
</div>
</div>
</div>
</div>
<div class="lt-subsection-header">Log / Timeline Entries</div>
<div class="lt-section-body">
<div class="lt-log-entry success">
<div class="lt-log-ts">2026-03-14 09:12:33 EDT</div>
<strong>Status changed:</strong> Open → In Progress by operator
</div>
<div class="lt-log-entry warning">
<div class="lt-log-ts">2026-03-14 09:10:11 EDT</div>
<strong>Priority escalated:</strong> P3 → P1 by GANDALF auto-alert
</div>
<div class="lt-log-entry error">
<div class="lt-log-ts">2026-03-14 09:09:55 EDT</div>
<strong>Alert triggered:</strong> NIC link-down on large1:enp35s0
<div class="lt-log-output">node_exporter metric: node_network_up{interface="enp35s0"} = 0</div>
</div>
</div>
</div><!-- /.lt-frame-inner -->
</div><!-- /.lt-frame (component showcase) -->
<!-- ===========================================================
COMPONENT SHOWCASE — v1.2 ADDITIONS
=========================================================== -->
<div class="lt-frame lt-mt-lg">
<div class="lt-frame-inner">
<!-- PROGRESS BARS -->
<div class="lt-section-header">Progress Bars</div>
<div class="lt-section-body">
<div style="display:flex;flex-direction:column;gap:var(--space-md)">
<div>
<div class="lt-progress-label"><span>CPU LOAD</span><span>72%</span></div>
<div class="lt-progress"><div class="lt-progress-bar" style="width:72%" data-width="72%"></div></div>
</div>
<div>
<div class="lt-progress-label"><span>MEMORY</span><span>45%</span></div>
<div class="lt-progress lt-progress--cyan"><div class="lt-progress-bar" style="width:45%" data-width="45%"></div></div>
</div>
<div>
<div class="lt-progress-label"><span>DISK I/O</span><span>89%</span></div>
<div class="lt-progress lt-progress--red lt-progress--lg"><div class="lt-progress-bar" style="width:89%" data-width="89%"></div></div>
</div>
<div>
<div class="lt-progress-label"><span>UPTIME</span><span>100%</span></div>
<div class="lt-progress lt-progress--green lt-progress--striped"><div class="lt-progress-bar" style="width:100%" data-width="100%"></div></div>
</div>
</div>
</div>
<!-- BREADCRUMBS & PAGINATION -->
<div class="lt-section-header">Breadcrumbs / Pagination</div>
<div class="lt-section-body" style="display:flex;flex-direction:column;gap:var(--space-md)">
<nav class="lt-breadcrumb" aria-label="breadcrumb">
<div class="lt-breadcrumb-item"><a href="#">ROOT</a></div>
<div class="lt-breadcrumb-sep"></div>
<div class="lt-breadcrumb-item"><a href="#">SYSTEMS</a></div>
<div class="lt-breadcrumb-sep"></div>
<div class="lt-breadcrumb-item"><a href="#">NETWORK</a></div>
<div class="lt-breadcrumb-sep"></div>
<div class="lt-breadcrumb-item active">NODE-07</div>
</nav>
<nav class="lt-pagination" aria-label="pagination">
<button class="lt-page-btn" disabled>&laquo;</button>
<button class="lt-page-btn" disabled>1</button>
<button class="lt-page-btn active">2</button>
<button class="lt-page-btn">3</button>
<button class="lt-page-btn">4</button>
<button class="lt-page-btn">5</button>
<button class="lt-page-btn">&raquo;</button>
</nav>
</div>
<!-- ACCORDION -->
<div class="lt-section-header">Accordion</div>
<div class="lt-section-body">
<div>
<div class="lt-accordion">
<button class="lt-accordion-header" aria-expanded="false" data-accordion>
SYSTEM OVERVIEW
<svg class="lt-accordion-icon" viewBox="0 0 10 6" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1 1l4 4 4-4"/></svg>
</button>
<div class="lt-accordion-body"><div class="lt-accordion-content">Node running at 72% CPU. 12 active processes. Last restart: 3d ago.</div></div>
</div>
<div class="lt-accordion">
<button class="lt-accordion-header" aria-expanded="false" data-accordion>
NETWORK CONFIG
<svg class="lt-accordion-icon" viewBox="0 0 10 6" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1 1l4 4 4-4"/></svg>
</button>
<div class="lt-accordion-body"><div class="lt-accordion-content">eth0: 10.0.0.7 — MTU 1500 — RX 4.2 GB — TX 1.1 GB</div></div>
</div>
<div class="lt-accordion">
<button class="lt-accordion-header" aria-expanded="false" data-accordion>
FIREWALL RULES
<svg class="lt-accordion-icon" viewBox="0 0 10 6" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M1 1l4 4 4-4"/></svg>
</button>
<div class="lt-accordion-body"><div class="lt-accordion-content">22/tcp ALLOW — 80/tcp ALLOW — 443/tcp ALLOW — */* DENY</div></div>
</div>
</div>
</div>
<!-- ALERTS -->
<div class="lt-section-header">Alert Banners</div>
<div class="lt-section-body" style="display:flex;flex-direction:column;gap:var(--space-sm)">
<div class="lt-alert">
<span class="lt-alert-icon"></span>
<div class="lt-alert-body"><div class="lt-alert-title">Information</div><div class="lt-alert-msg">Scheduled maintenance window: Sunday 02:0004:00 UTC.</div></div>
<button class="lt-alert-close" data-alert-close aria-label="Dismiss"></button>
</div>
<div class="lt-alert lt-alert--warning">
<span class="lt-alert-icon"></span>
<div class="lt-alert-body"><div class="lt-alert-title">Warning</div><div class="lt-alert-msg">Disk usage on NODE-03 at 87%. Consider cleanup.</div></div>
<button class="lt-alert-close" data-alert-close aria-label="Dismiss"></button>
</div>
<div class="lt-alert lt-alert--error">
<span class="lt-alert-icon"></span>
<div class="lt-alert-body"><div class="lt-alert-title">Critical</div><div class="lt-alert-msg">NODE-11 unreachable. Last seen 14m ago.</div></div>
<button class="lt-alert-close" data-alert-close aria-label="Dismiss"></button>
</div>
<div class="lt-alert lt-alert--success">
<span class="lt-alert-icon"></span>
<div class="lt-alert-body"><div class="lt-alert-title">Success</div><div class="lt-alert-msg">Deployment v2.4.1 completed successfully.</div></div>
<button class="lt-alert-close" data-alert-close aria-label="Dismiss"></button>
</div>
</div>
<!-- TOGGLES, RANGE, TAGS -->
<div class="lt-section-header">Toggles / Range / Tags</div>
<div class="lt-section-body" style="display:flex;flex-direction:column;gap:var(--space-lg)">
<div style="display:flex;gap:var(--space-lg);flex-wrap:wrap">
<label class="lt-toggle">
<input type="checkbox" checked>
<div class="lt-toggle-track"><div class="lt-toggle-thumb"></div></div>
<span class="lt-toggle-label">Auto-refresh</span>
</label>
<label class="lt-toggle">
<input type="checkbox">
<div class="lt-toggle-track"><div class="lt-toggle-thumb"></div></div>
<span class="lt-toggle-label">Dark alerts</span>
</label>
<label class="lt-toggle">
<input type="checkbox" checked>
<div class="lt-toggle-track"><div class="lt-toggle-thumb"></div></div>
<span class="lt-toggle-label">Notifications</span>
</label>
</div>
<div class="lt-range-wrap" style="max-width:320px">
<div class="lt-range-header">
<span class="lt-range-label">Refresh Interval (s)</span>
<span class="lt-range-value">30</span>
</div>
<input type="range" class="lt-range" min="5" max="60" value="30">
</div>
<div class="lt-tags">
<span class="lt-tag lt-tag--orange">CRITICAL</span>
<span class="lt-tag lt-tag--cyan">NETWORK</span>
<span class="lt-tag lt-tag--green">ONLINE</span>
<span class="lt-tag lt-tag--red">OFFLINE</span>
<span class="lt-tag lt-tag--purple">ADMIN</span>
<span class="lt-tag">UNTAGGED</span>
</div>
</div>
<!-- CODE BLOCK -->
<div class="lt-section-header">Code Block</div>
<div class="lt-section-body">
<div class="lt-code-block">
<div class="lt-code-header">
<span class="lt-code-lang">bash</span>
<button class="lt-code-copy" data-copy="systemctl restart nginx && systemctl status nginx">COPY</button>
</div>
<pre><code><span class="tok-cmt"># Restart and verify nginx</span>
<span class="tok-kw">systemctl</span> restart nginx <span class="tok-kw">&amp;&amp;</span> <span class="tok-kw">systemctl</span> status nginx
<span class="tok-cmt"># Check active connections</span>
<span class="tok-kw">ss</span> -tlnp | <span class="tok-kw">grep</span> <span class="tok-str">':80\|:443'</span></code></pre>
</div>
</div>
<!-- KEY-VALUE + STEPPER -->
<div class="lt-section-header">Data Grid / Stepper</div>
<div class="lt-section-body" style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-lg)">
<div>
<div class="lt-kv-grid">
<div class="lt-kv-key">Hostname</div> <div class="lt-kv-val lt-kv-val--cyan">node-07.prod</div>
<div class="lt-kv-key">IP Address</div> <div class="lt-kv-val">10.0.0.7</div>
<div class="lt-kv-key">Status</div> <div class="lt-kv-val lt-kv-val--green">ONLINE</div>
<div class="lt-kv-key">Uptime</div> <div class="lt-kv-val">3d 14h 22m</div>
<div class="lt-kv-key">Load Avg</div> <div class="lt-kv-val lt-kv-val--orange">2.14 / 1.87 / 1.60</div>
<div class="lt-kv-key">Region</div> <div class="lt-kv-val">US-EAST-1</div>
</div>
</div>
<div>
<div class="lt-stepper">
<div class="lt-step complete">
<div class="lt-step-num"></div>
<div class="lt-step-label">INIT</div>
</div>
<div class="lt-step complete">
<div class="lt-step-num"></div>
<div class="lt-step-label">BUILD</div>
</div>
<div class="lt-step active">
<div class="lt-step-num">3</div>
<div class="lt-step-label">DEPLOY</div>
</div>
<div class="lt-step">
<div class="lt-step-num">4</div>
<div class="lt-step-label">VERIFY</div>
</div>
</div>
</div>
</div>
<!-- LIST GROUP + BADGES -->
<div class="lt-section-header">List Group / Badges</div>
<div class="lt-section-body" style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-lg)">
<div class="lt-list-group">
<div class="lt-list-item lt-list-item--active">
<span class="lt-dot lt-dot--green"></span>
<a href="#">node-01.prod</a>
<span class="lt-list-item-meta lt-kv-val--green">ONLINE</span>
</div>
<div class="lt-list-item">
<span class="lt-dot lt-dot--green"></span>
<a href="#">node-02.prod</a>
<span class="lt-list-item-meta lt-kv-val--green">ONLINE</span>
</div>
<div class="lt-list-item">
<span class="lt-dot lt-dot--orange"></span>
<a href="#">node-03.prod</a>
<span class="lt-list-item-meta lt-kv-val--orange">DEGRADED</span>
</div>
<div class="lt-list-item">
<span class="lt-dot lt-dot--red"></span>
<a href="#">node-11.prod</a>
<span class="lt-list-item-meta lt-kv-val--red">OFFLINE</span>
</div>
</div>
<div style="display:flex;flex-direction:column;gap:var(--space-md)">
<div style="display:flex;gap:var(--space-md);align-items:center;flex-wrap:wrap">
<div class="lt-badge-wrap">
<button class="lt-btn lt-btn-sm">Alerts</button>
<span class="lt-badge">3</span>
</div>
<div class="lt-badge-wrap">
<button class="lt-btn lt-btn-sm">Messages</button>
<span class="lt-badge lt-badge--orange">12</span>
</div>
<div class="lt-badge-wrap">
<button class="lt-btn lt-btn-sm">Updates</button>
<span class="lt-badge lt-badge--cyan">5</span>
</div>
</div>
<div style="display:flex;gap:var(--space-sm);align-items:center;flex-wrap:wrap">
<div class="lt-bar-loader"><span></span><span></span><span></span><span></span></div>
<div class="lt-spinner"></div>
<div class="lt-spinner lt-spinner--cyan lt-spinner--sm"></div>
<div class="lt-pulse-dot"></div>
</div>
</div>
</div>
<!-- TAB BAR -->
<div class="lt-section-header">Tab Bar</div>
<div class="lt-section-body">
<div class="lt-tab-bar" role="tablist">
<button class="lt-tab active" role="tab" data-tab-target="tab-overview" aria-selected="true">Overview</button>
<button class="lt-tab" role="tab" data-tab-target="tab-logs" aria-selected="false">Logs</button>
<button class="lt-tab" role="tab" data-tab-target="tab-metrics" aria-selected="false">Metrics</button>
<button class="lt-tab" role="tab" data-tab-target="tab-config" aria-selected="false">Config</button>
</div>
<div class="lt-tab-panels" style="padding:var(--space-md) 0">
<div id="tab-overview" class="lt-tab-panel active">System overview: all nodes nominal. 14 active sessions.</div>
<div id="tab-logs" class="lt-tab-panel">
<pre style="font-family:var(--font-mono);font-size:0.75rem;color:var(--text-secondary);margin:0">[03:41:22] nginx: 200 GET /api/nodes (12ms)
[03:41:23] cron: heartbeat OK
[03:41:24] alert: disk 87% on node-03</pre>
</div>
<div id="tab-metrics" class="lt-tab-panel">CPU avg: 34% — Mem avg: 61% — Net TX: 4.2 Mbps</div>
<div id="tab-config" class="lt-tab-panel">Config last updated: 2026-03-20 by admin@lotusguild.io</div>
</div>
</div>
<!-- TOOLTIP & DIVIDER DEMO -->
<div class="lt-section-header">Tooltips / Divider</div>
<div class="lt-section-body" style="display:flex;flex-direction:column;gap:var(--space-md)">
<div style="display:flex;gap:var(--space-lg);flex-wrap:wrap;padding-top:var(--space-md)">
<span data-tooltip="Primary accent — use for actions" class="lt-tag lt-tag--orange">ORANGE</span>
<span data-tooltip="Info / border color" data-tooltip-pos="bottom" class="lt-tag lt-tag--cyan">CYAN (bottom)</span>
<span data-tooltip="Success states" class="lt-tag lt-tag--green">GREEN</span>
<span data-tooltip="Critical / error" class="lt-tag lt-tag--red">RED</span>
</div>
<div class="lt-divider"><span class="lt-divider-label">SECTION BREAK</span></div>
<div class="lt-divider lt-divider--orange"><span class="lt-divider-label">CRITICAL ZONE</span></div>
</div>
<!-- DROPZONE -->
<div class="lt-section-header">File Dropzone</div>
<div class="lt-section-body">
<div class="lt-dropzone" data-dropzone data-accept=".json,.csv,.log" data-max-size="10485760">
<input type="file" accept=".json,.csv,.log" multiple>
<div class="lt-dropzone-icon"></div>
<div class="lt-dropzone-text">Drop files here or <strong>click to browse</strong></div>
<div class="lt-dropzone-hint">Accepts .json, .csv, .log — max 10 MB each</div>
</div>
<div class="lt-file-list" id="demo-file-list"></div>
</div>
<!-- COMMAND PALETTE TRIGGER -->
<div class="lt-section-header">Command Palette</div>
<div class="lt-section-body">
<p style="font-family:var(--font-mono);font-size:0.8rem;color:var(--text-secondary);margin:0 0 var(--space-sm)">
Press <kbd style="background:var(--bg-tertiary);border:1px solid var(--border-dim);padding:2px 6px;font-family:var(--font-mono)">Ctrl+K</kbd> or click the button below to open the command palette.
</p>
<button class="lt-btn lt-btn-ghost" onclick="lt.cmdPalette.open()">⌘ Open Command Palette</button>
</div>
</div><!-- /.lt-frame-inner (v1.2 additions) -->
</div><!-- /.lt-frame (v1.2 additions) -->
<!-- ═══════════════════════════════════════════════════════════
v1.1 COMPONENT SHOWCASE
═══════════════════════════════════════════════════════════ -->
<!-- Theme Toggle -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// THEME TOGGLE</span>
</div>
<div class="lt-section-body">
<p style="font-size:0.78rem;color:var(--text-secondary);margin-bottom:1rem">Dark/light mode with OS preference detection and localStorage persistence.</p>
<div class="lt-flex lt-gap-sm lt-align-center lt-wrap">
<button class="lt-btn lt-btn-primary" onclick="lt.theme.toggle()">Toggle Theme</button>
<button class="lt-btn" onclick="lt.theme.set('dark')">Force Dark</button>
<button class="lt-btn" onclick="lt.theme.set('light')">Force Light</button>
<code style="font-size:0.72rem;color:var(--accent-cyan)">lt.theme.toggle() | .set('light') | .get()</code>
</div>
</div>
</div>
<!-- Skeleton Loaders -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// SKELETON LOADERS</span>
</div>
<div class="lt-section-body">
<div class="lt-grid lt-grid-3" style="gap:1rem;align-items:start">
<div class="lt-skeleton-card">
<div class="lt-skeleton-card-header">
<span class="lt-skeleton lt-skeleton-avatar"></span>
<div style="flex:1"><span class="lt-skeleton lt-skeleton-title"></span><span class="lt-skeleton lt-skeleton-line-sm"></span></div>
</div>
<span class="lt-skeleton lt-skeleton-text"></span>
<span class="lt-skeleton lt-skeleton-line-lg"></span>
<span class="lt-skeleton lt-skeleton-text" style="width:70%"></span>
<div class="lt-flex lt-gap-sm" style="margin-top:0.25rem">
<span class="lt-skeleton lt-skeleton-btn"></span>
<span class="lt-skeleton lt-skeleton-badge"></span>
</div>
</div>
<div style="grid-column:span 2">
<div class="lt-skeleton-row"><span class="lt-skeleton" style="height:1rem"></span><span class="lt-skeleton lt-skeleton-text"></span><span class="lt-skeleton" style="height:0.8rem;width:80%"></span><span class="lt-skeleton lt-skeleton-badge"></span><span class="lt-skeleton" style="height:0.8rem;width:60%"></span><span class="lt-skeleton lt-skeleton-btn"></span></div>
<div class="lt-skeleton-row" style="opacity:0.65"><span class="lt-skeleton" style="height:1rem"></span><span class="lt-skeleton lt-skeleton-text"></span><span class="lt-skeleton" style="height:0.8rem;width:65%"></span><span class="lt-skeleton lt-skeleton-badge"></span><span class="lt-skeleton" style="height:0.8rem;width:50%"></span><span class="lt-skeleton lt-skeleton-btn"></span></div>
<div class="lt-skeleton-row" style="opacity:0.35"><span class="lt-skeleton" style="height:1rem"></span><span class="lt-skeleton lt-skeleton-text"></span><span class="lt-skeleton" style="height:0.8rem;width:55%"></span><span class="lt-skeleton lt-skeleton-badge"></span><span class="lt-skeleton" style="height:0.8rem;width:40%"></span><span class="lt-skeleton lt-skeleton-btn"></span></div>
</div>
</div>
</div>
</div>
<!-- Empty States -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// EMPTY STATES</span>
</div>
<div class="lt-section-body">
<div class="lt-grid lt-grid-3" style="gap:1rem">
<div class="lt-card"><div class="lt-empty-state"><div class="lt-empty-state-icon">📭</div><div class="lt-empty-state-title">No Tickets Found</div><div class="lt-empty-state-body">No tickets match your current filters.</div><button class="lt-btn lt-btn-sm lt-btn-primary">Clear Filters</button></div></div>
<div class="lt-card"><div class="lt-empty-state"><div class="lt-empty-state-icon">🔌</div><div class="lt-empty-state-title">No Workers Online</div><div class="lt-empty-state-body">All workers are offline or unreachable.</div><button class="lt-btn lt-btn-sm" onclick="lt.toast.warning('Checking workers…')">Retry</button></div></div>
<div class="lt-card"><div class="lt-empty-state lt-empty-state--sm"><div class="lt-empty-state-icon">🗂</div><div class="lt-empty-state-title">No Results</div><div class="lt-empty-state-body">Try a different search term.</div></div></div>
</div>
</div>
</div>
<!-- Avatars & Notification Badges -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// AVATARS &amp; NOTIFICATION BADGES</span>
</div>
<div class="lt-section-body">
<div class="lt-flex lt-gap-lg lt-wrap lt-align-center" style="margin-bottom:1rem">
<span class="lt-avatar lt-avatar--xs lt-avatar--orange">AB</span>
<span class="lt-avatar lt-avatar--sm lt-avatar--green">CD</span>
<span class="lt-avatar lt-avatar--purple">EF</span>
<span class="lt-avatar lt-avatar--lg lt-avatar--red">GH</span>
<div class="lt-avatar-wrap"><span class="lt-avatar lt-avatar--orange">JD</span><span class="lt-avatar-status lt-avatar-status--online"></span></div>
<div class="lt-avatar-wrap"><span class="lt-avatar">SK</span><span class="lt-avatar-status lt-avatar-status--away"></span></div>
<div class="lt-avatar-wrap"><span class="lt-avatar lt-avatar--red">MR</span><span class="lt-avatar-status lt-avatar-status--busy"></span></div>
<div class="lt-avatar-group">
<span class="lt-avatar lt-avatar--sm lt-avatar--orange">AA</span>
<span class="lt-avatar lt-avatar--sm lt-avatar--green">BB</span>
<span class="lt-avatar lt-avatar--sm lt-avatar--purple">CC</span>
<span class="lt-avatar lt-avatar--sm" style="background:var(--bg-tertiary);color:var(--text-muted);font-size:0.6rem">+4</span>
</div>
</div>
<div class="lt-flex lt-gap-md lt-align-center lt-wrap">
<span class="lt-notif-wrap" id="demo-notif-btn"><button class="lt-btn lt-btn-sm">🔔 Alerts</button></span>
<button class="lt-btn lt-btn-sm" onclick="lt.notif.inc('#demo-notif-btn')">+1 Badge</button>
<button class="lt-btn lt-btn-sm" onclick="lt.notif.clear('#demo-notif-btn')">Clear</button>
<button class="lt-btn lt-btn-sm" onclick="lt.notif.inc('#lt-notif-bell')">+1 Header Bell</button>
</div>
</div>
</div>
<!-- Timeline -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// TIMELINE / ACTIVITY FEED</span>
</div>
<div class="lt-section-body">
<div class="lt-grid lt-grid-2" style="gap:1.5rem">
<div class="lt-timeline">
<div class="lt-timeline-item lt-timeline-item--red">
<div class="lt-timeline-meta"><span class="lt-timeline-actor">alertmanager</span> fired<span class="lt-timeline-time lt-num">14:22:05</span></div>
<div class="lt-timeline-body">CRITICAL: Storage array link-down on <code>compute-storage-01</code>. Ticket auto-created.</div>
</div>
<div class="lt-timeline-item lt-timeline-item--orange">
<div class="lt-timeline-meta"><span class="lt-timeline-actor">jdoe</span> assigned<span class="lt-timeline-time lt-num">14:24:11</span></div>
<div class="lt-timeline-body">Escalated to P1 Critical. Paged on-call team.</div>
</div>
<div class="lt-timeline-item">
<div class="lt-timeline-meta"><span class="lt-timeline-actor">jdoe</span> commented<span class="lt-timeline-time lt-num">14:31:44</span></div>
<div class="lt-timeline-body">Confirmed NIC failure. Ordered replacement hardware. ETA 2h.</div>
</div>
<div class="lt-timeline-item lt-timeline-item--green">
<div class="lt-timeline-meta"><span class="lt-timeline-actor">jdoe</span> resolved<span class="lt-timeline-time lt-num">16:55:00</span></div>
<div class="lt-timeline-body">Hardware replaced. Link restored. Monitoring for 30 min.</div>
</div>
</div>
<div style="display:flex;flex-direction:column;gap:0.75rem">
<div class="lt-stat-card"><div class="lt-stat-label">Resolution Time</div><div class="lt-stat-value lt-text-green lt-num">2h 33m</div></div>
<div class="lt-stat-card"><div class="lt-stat-label">Events</div><div class="lt-stat-value lt-num">4</div></div>
<div class="lt-stat-card"><div class="lt-stat-label">SLA Status</div><div class="lt-stat-value lt-text-orange">Within SLA</div></div>
</div>
</div>
</div>
</div>
<!-- Right Drawer -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// RIGHT-SIDE DRAWER</span>
</div>
<div class="lt-section-body">
<p style="font-size:0.78rem;color:var(--text-secondary);margin-bottom:1rem">Detail/inspect panel from the right. Focus trap, ESC close, overlay backdrop, return-focus.</p>
<div class="lt-flex lt-gap-sm lt-wrap">
<button class="lt-btn lt-btn-primary" data-drawer-open="lt-detail-drawer">Open Detail Panel</button>
<code style="font-size:0.72rem;color:var(--accent-cyan)">lt.rightDrawer.open('id') | .close() | .toggle()</code>
</div>
</div>
</div>
<!-- Context Menu -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// CONTEXT MENU</span>
</div>
<div class="lt-section-body">
<p style="font-size:0.78rem;color:var(--text-secondary);margin-bottom:1rem">Right-click any element with <code>data-context-menu</code> or trigger programmatically.</p>
<div class="lt-flex lt-gap-sm lt-wrap lt-align-center">
<div data-context-menu="demo-ctx" class="lt-card" style="padding:0.75rem 1.25rem;cursor:context-menu;border-style:dashed;display:inline-block">
Right-click this card
</div>
<button class="lt-btn lt-btn-sm" id="demo-ctx-btn">Show Context Menu</button>
</div>
</div>
</div>
<!-- Combobox + Typeahead -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// COMBOBOX &amp; TYPEAHEAD</span>
</div>
<div class="lt-section-body">
<div class="lt-grid lt-grid-2" style="gap:1.5rem">
<div>
<label class="lt-label">Assign Workers (multi-select)</label>
<div class="lt-combobox" id="demo-combobox">
<div class="lt-combobox-input-wrap">
<input type="text" class="lt-combobox-input" id="demo-combobox-input" placeholder="Search workers…" autocomplete="off">
</div>
<div class="lt-combobox-dropdown"></div>
</div>
</div>
<div>
<label class="lt-label">Search Tickets (typeahead)</label>
<div class="lt-typeahead" id="demo-typeahead">
<input type="text" class="lt-input lt-w-full" id="demo-typeahead-input" placeholder="Type to search…" autocomplete="off">
<div class="lt-typeahead-dropdown"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Sticky Table -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// STICKY TABLE HEADERS</span>
</div>
<div class="lt-section-body">
<div class="lt-table-sticky-wrap">
<table class="lt-table lt-table-responsive">
<thead><tr><th>ID</th><th>Priority</th><th>Title</th><th>Status</th><th>Assignee</th></tr></thead>
<tbody>
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#001</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p1">P1</span></td><td data-label="Title">Link-down on compute-storage-01</td><td data-label="Status"><span class="lt-badge lt-badge-open">Open</span></td><td data-label="Assignee">jdoe</td></tr>
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#002</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p2">P2</span></td><td data-label="Title">Switch port flapping USW-Pro-24</td><td data-label="Status"><span class="lt-badge lt-badge-in-progress">In Progress</span></td><td data-label="Assignee">smith</td></tr>
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#003</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p3">P3</span></td><td data-label="Title">Scheduled SFP+ replacement</td><td data-label="Status"><span class="lt-badge lt-badge-pending">Pending</span></td><td data-label="Assignee">ops-bot</td></tr>
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#004</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p4">P4</span></td><td data-label="Title">SSL cert renewal wiki</td><td data-label="Status"><span class="lt-badge lt-badge-open">Open</span></td><td data-label="Assignee">admin</td></tr>
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#005</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p1">P1</span></td><td data-label="Title">RAID controller firmware</td><td data-label="Status"><span class="lt-badge lt-badge-open">Open</span></td><td data-label="Assignee">jdoe</td></tr>
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#006</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p2">P2</span></td><td data-label="Title">Backup job failure nas-01</td><td data-label="Status"><span class="lt-badge lt-badge-closed">Closed</span></td><td data-label="Assignee">backup-bot</td></tr>
<tr><td data-label="ID"><a href="#" class="lt-text-cyan">#007</a></td><td data-label="Priority"><span class="lt-badge lt-badge-p3">P3</span></td><td data-label="Title">Prometheus alert rule tuning</td><td data-label="Status"><span class="lt-badge lt-badge-in-progress">In Progress</span></td><td data-label="Assignee">ops-team</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Chart Containers -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// CHART CONTAINERS</span>
</div>
<div class="lt-section-body">
<div class="lt-grid lt-grid-2" style="gap:1rem">
<div class="lt-chart-wrap">
<div class="lt-chart-header">
<span class="lt-chart-title">Ticket Volume (7d)</span>
<div class="lt-chart-legend">
<span class="lt-chart-legend-item"><span class="lt-chart-legend-dot" style="background:var(--accent-cyan)"></span>Open</span>
<span class="lt-chart-legend-item"><span class="lt-chart-legend-dot" style="background:var(--accent-green)"></span>Closed</span>
</div>
</div>
<div class="lt-chart-body" style="min-height:120px;display:flex;align-items:center;justify-content:center;color:var(--text-muted);font-size:0.72rem;letter-spacing:0.1em">[ Plug in Chart.js / D3 here ]</div>
<div class="lt-chart-axis"><span>Mon</span><span>Tue</span><span>Wed</span><span>Thu</span><span>Fri</span><span>Sat</span><span>Sun</span></div>
</div>
<div class="lt-chart-wrap is-loading">
<div class="lt-chart-header"><span class="lt-chart-title">Worker Uptime</span></div>
<div class="lt-chart-body"></div>
</div>
</div>
</div>
</div>
<!-- Split Pane -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// SPLIT PANE</span>
</div>
<div class="lt-section-body">
<div class="lt-split" id="demo-split" style="height:200px;border:1px solid var(--border-dim)">
<div class="lt-split-pane" style="padding:1rem;overflow:auto">
<div class="lt-section-label" style="margin-bottom:0.5rem">Panel A</div>
<p style="font-size:0.78rem;color:var(--text-secondary)">Drag the divider to resize. Stacks vertically on mobile.</p>
</div>
<div class="lt-split-divider" title="Drag to resize"></div>
<div class="lt-split-pane" style="padding:1rem;overflow:auto">
<div class="lt-section-label" style="margin-bottom:0.5rem">Panel B</div>
<p style="font-size:0.78rem;color:var(--text-secondary)">Both panels maintain independent scrolling.</p>
</div>
</div>
<p style="font-size:0.72rem;color:var(--text-muted);margin-top:0.5rem"><code>lt.splitPane.init(el, &#123; initial: 0.4, minA: 120 &#125;)</code></p>
</div>
</div>
<!-- WebSocket & Offline -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// WEBSOCKET &amp; OFFLINE DETECTION</span>
</div>
<div class="lt-section-body">
<div class="lt-flex lt-gap-md lt-wrap lt-align-center" style="margin-bottom:1rem">
<div class="lt-ws-status" data-state="connected"><span class="lt-dot"></span><span>Connected</span></div>
<div class="lt-ws-status" data-state="connecting"><span class="lt-dot"></span><span>Connecting…</span></div>
<div class="lt-ws-status" data-state="disconnected"><span class="lt-dot"></span><span>Disconnected</span></div>
</div>
<div class="lt-flex lt-gap-sm lt-wrap">
<button class="lt-btn lt-btn-sm" onclick="lt.offline.isOnline() ? lt.toast.success('Online ✓') : lt.toast.error('Offline ✗')">Check Online Status</button>
<button class="lt-btn lt-btn-sm" onclick="lt.toast.info('lt.ws.connect(url, { reconnect:true, onMessage: fn })')">WS API Hint</button>
</div>
<p style="font-size:0.72rem;color:var(--text-muted);margin-top:0.75rem">Offline banner + body class auto-applied on <code>navigator.onLine</code> change. WS manager has exponential backoff, event emitter, status indicator binding.</p>
</div>
</div>
</main><!-- /.lt-main -->
<!-- ===========================================================
TOAST DEMO BUTTONS (remove in production)
=========================================================== -->
<div style="position:fixed;bottom:1rem;left:1rem;display:flex;flex-direction:column;gap:0.5rem;z-index:900">
<button class="lt-btn lt-btn-sm" onclick="lt.toast.success('Ticket saved successfully')">✓ Toast</button>
<button class="lt-btn lt-btn-sm lt-btn-danger" onclick="lt.toast.error('Network error — retry in 5s', 5000)">✗ Error</button>
<button class="lt-btn lt-btn-sm" onclick="lt.toast.warning('Rate limit 80% used')">! Warn</button>
<button class="lt-btn lt-btn-sm lt-btn-ghost" onclick="lt.toast.info('Auto-refresh triggered')">i Info</button>
</div>
<!-- ===========================================================
MODAL EXAMPLE: Export
=========================================================== -->
<div id="export-modal" class="lt-modal-overlay" aria-hidden="true">
<div class="lt-modal">
<div class="lt-modal-header">
<span class="lt-modal-title">Export Tickets</span>
<button class="lt-modal-close" data-modal-close aria-label="Close"></button>
</div>
<div class="lt-modal-body">
<div class="lt-form-group">
<label class="lt-label" for="export-fmt">Format</label>
<select id="export-fmt" class="lt-select">
<option>CSV</option>
<option>JSON</option>
</select>
</div>
<div class="lt-form-group">
<label class="lt-label">
<input type="checkbox" class="lt-checkbox" checked> Selected tickets only
</label>
</div>
<div class="lt-msg lt-msg-info">Exports include all visible columns.</div>
</div>
<div class="lt-modal-footer">
<button class="lt-btn lt-btn-ghost" data-modal-close>Cancel</button>
<button class="lt-btn lt-btn-primary" onclick="lt.toast.success('Export started'); lt.modal.close('export-modal')">Export</button>
</div>
</div>
</div>
<!-- ===========================================================
MODAL EXAMPLE: Keyboard shortcuts help
=========================================================== -->
<div id="lt-keys-help" class="lt-modal-overlay" aria-hidden="true">
<div class="lt-modal">
<div class="lt-modal-header">
<span class="lt-modal-title">Keyboard Shortcuts</span>
<button class="lt-modal-close" data-modal-close aria-label="Close"></button>
</div>
<div class="lt-modal-body">
<table class="lt-data-table" style="width:100%">
<thead>
<tr><th>Shortcut</th><th>Action</th></tr>
</thead>
<tbody>
<tr><td>Ctrl / ⌘ + K</td><td>Focus search box</td></tr>
<tr><td>Ctrl / ⌘ + E</td><td>Toggle edit mode (ticket page)</td></tr>
<tr><td>Ctrl / ⌘ + S</td><td>Save changes (ticket page)</td></tr>
<tr><td>j / ↓</td><td>Select next row</td></tr>
<tr><td>k / ↑</td><td>Select previous row</td></tr>
<tr><td>Enter</td><td>Open selected ticket</td></tr>
<tr><td>n</td><td>New ticket</td></tr>
<tr><td>?</td><td>Show this help</td></tr>
<tr><td>ESC</td><td>Close modal / cancel</td></tr>
</tbody>
</table>
</div>
<div class="lt-modal-footer">
<button class="lt-btn" data-modal-close>Close</button>
</div>
</div>
</div>
<!-- ===========================================================
COMMAND PALETTE OVERLAY
=========================================================== -->
<div id="lt-cmd-overlay" class="lt-cmd-overlay" role="dialog" aria-modal="true" aria-label="Command palette">
<div class="lt-cmd-palette" id="lt-cmd-palette">
<div class="lt-cmd-input-wrap">
<span class="lt-cmd-prompt">&gt;</span>
<input id="lt-cmd-input" class="lt-cmd-input" type="text" placeholder="Search commands…" autocomplete="off" spellcheck="false">
</div>
<div class="lt-cmd-results" id="lt-cmd-results">
<div class="lt-cmd-empty">Start typing to search…</div>
</div>
<div class="lt-cmd-footer">
<span><kbd></kbd><kbd></kbd> Navigate</span>
<span><kbd>Enter</kbd> Select</span>
<span><kbd>Esc</kbd> Close</span>
</div>
</div>
</div>
<!-- ===========================================================
SCRIPTS
=========================================================== -->
<!--
PHP apps inject CSRF token + config here (with CSP nonce):
<script nonce="<?php echo $nonce; ?>">
window.CSRF_TOKEN = '<?php echo CsrfMiddleware::getToken(); ?>';
window.APP_TIMEZONE = '<?php echo $config['TIMEZONE']; ?>';
</script>
Node/Express (EJS):
<script nonce="<%= nonce %>">
window.CSRF_TOKEN = '<%= csrfToken %>';
</script>
Flask/Jinja2:
<script nonce="{{ nonce }}">
window.CSRF_TOKEN = '{{ csrf_token() }}';
</script>
-->
<!-- LotusGuild Terminal Design System JS v1.2 -->
<script src="/web_template/base.js"></script>
<!-- Init v1.2 modules -->
<script>
document.addEventListener('DOMContentLoaded', () => {
// Accordion
lt.accordion.init();
// Tooltips (JS-positioned — optional, CSS tooltips work without this)
lt.tooltip.init();
// Clipboard copy buttons ([data-copy])
lt.clipboard.init();
// Alert dismiss buttons
lt.alerts.init();
// Command palette demo commands
lt.cmdPalette.init([
{
group: 'Navigation',
items: [
{ icon: '⌂', label: 'Dashboard', kbd: 'G D', action: () => lt.toast.info('→ Dashboard') },
{ icon: '⊞', label: 'All Tickets', kbd: 'G T', action: () => lt.toast.info('→ Tickets') },
{ icon: '★', label: 'Saved Filters', action: () => lt.toast.info('→ Filters') },
]
},
{
group: 'Actions',
items: [
{ icon: '+', label: 'New Ticket', kbd: 'N', action: () => lt.toast.success('New ticket form') },
{ icon: '⤓', label: 'Export CSV', action: () => lt.modal.open('export-modal') },
{ icon: '⟳', label: 'Refresh Data', kbd: 'R', action: () => lt.toast.info('Refreshing...') },
]
},
{
group: 'Help',
items: [
{ icon: '?', label: 'Keyboard Shortcuts', kbd: '?', action: () => lt.modal.open('lt-keys-help') },
]
},
]);
// Range slider live value display
document.querySelectorAll('input[type="range"].lt-range').forEach(r => {
const wrap = r.closest('.lt-range-wrap');
const label = wrap && wrap.querySelector('.lt-range-value');
if (label) {
label.textContent = r.value;
r.addEventListener('input', () => { label.textContent = r.value; });
}
});
// Animate the demo progress bars on load
document.querySelectorAll('.lt-progress-bar[data-width]').forEach(bar => {
requestAnimationFrame(() => { bar.style.width = bar.dataset.width; });
});
// Theme toggle button
document.getElementById('lt-theme-btn').addEventListener('click', () => lt.theme.toggle());
// Context menu: register demo menu items
lt.contextMenu.register('demo-ctx', [
{ icon: '📋', label: 'Copy ID', kbd: 'C', action: () => lt.toast.info('ID copied') },
{ icon: '👁', label: 'View Details', action: () => lt.rightDrawer.open('lt-detail-drawer') },
{ icon: '✏️', label: 'Edit Ticket', kbd: 'E', action: () => lt.toast.info('Edit…') },
{ divider: true },
{ icon: '🗑', label: 'Delete', danger: true, action: () => lt.toast.error('Deleted') },
]);
// Programmatic context menu button
const demoCtxBtn = document.getElementById('demo-ctx-btn');
if (demoCtxBtn) demoCtxBtn.addEventListener('click', e => {
const r = demoCtxBtn.getBoundingClientRect();
lt.contextMenu.show(r.left, r.bottom + 4, [
{ icon: '📋', label: 'Copy ID', action: () => lt.toast.info('Copied') },
{ icon: '✏️', label: 'Edit', action: () => lt.toast.info('Edit') },
{ divider: true },
{ icon: '🗑', label: 'Delete', danger: true, action: () => lt.toast.error('Deleted') },
]);
});
// Combobox demo
lt.combobox.init(
document.getElementById('demo-combobox-input'),
[
{ value: 'worker-01', label: 'worker-01', icon: '🖥' },
{ value: 'worker-02', label: 'worker-02', icon: '🖥' },
{ value: 'worker-03', label: 'worker-03', icon: '🖥' },
{ value: 'gpu-01', label: 'gpu-01', icon: '⚡' },
{ value: 'gpu-02', label: 'gpu-02', icon: '⚡' },
{ value: 'storage-01',label: 'storage-01',icon: '💾' },
],
{ onChange: vals => console.log('[combobox]', vals) }
);
// Typeahead demo
lt.typeahead.init(
document.getElementById('demo-typeahead-input'),
[
{ value: '001', label: 'Link-down on compute-storage-01', icon: '🔴', meta: 'P1' },
{ value: '002', label: 'Switch port flapping USW-Pro-24', icon: '🟠', meta: 'P2' },
{ value: '003', label: 'Scheduled SFP+ replacement large1',icon: '🔵', meta: 'P3' },
{ value: '004', label: 'SSL cert renewal wiki.lotusguild.org', icon: '🟢', meta: 'P4' },
{ value: '005', label: 'RAID controller firmware update', icon: '🔴', meta: 'P1' },
],
{ onSelect: item => lt.toast.info(`Selected: ${item.label}`) }
);
// Split pane demo
lt.splitPane.init(document.getElementById('demo-split'), { initial: 0.4, minA: 100, minB: 100 });
// Demo notification badge initial count
lt.notif.set('#lt-notif-bell', 3);
// Tab bar switching
document.querySelectorAll('.lt-tab-bar').forEach(bar => {
bar.addEventListener('click', e => {
const tab = e.target.closest('.lt-tab');
if (!tab) return;
const targetId = tab.dataset.tabTarget;
if (!targetId) return;
const panels = bar.nextElementSibling;
bar.querySelectorAll('.lt-tab').forEach(t => { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); });
tab.classList.add('active');
tab.setAttribute('aria-selected', 'true');
if (panels) {
panels.querySelectorAll('.lt-tab-panel').forEach(p => p.classList.remove('active'));
const target = panels.querySelector('#' + targetId);
if (target) target.classList.add('active');
}
});
});
});
</script>
</body>
</html>