Files
web_template/base.html
T

1981 lines
105 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<!--
2026-03-26 18:22:53 -04:00
LOTUSGUILD TERMINAL DESIGN SYSTEM v1.2 — 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">
2026-03-26 18:22:53 -04:00
<meta name="robots" content="noindex, nofollow">
<!-- =========================================================
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>
2026-03-26 18:22:53 -04:00
<a class="lt-skip-link" href="#main-content">Skip to main content</a>
<!-- ===========================================================
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)
=========================================================== -->
2026-03-26 18:22:53 -04:00
<div id="lt-nav-drawer" class="lt-nav-drawer" aria-hidden="true" role="dialog" aria-modal="true" 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 + dropdown -->
<div class="lt-notif-dropdown-wrap" id="lt-notif-bell">
<button class="lt-btn lt-btn-sm lt-notif-bell-btn" id="lt-notif-bell-btn" aria-label="Open notifications" aria-expanded="false" aria-haspopup="true" style="padding:0 0.6rem;">🔔</button>
<div class="lt-notif-panel" id="lt-notif-panel" role="region" aria-label="Notifications" aria-hidden="true">
<div class="lt-notif-panel-header">
<span>Notifications</span>
<button class="lt-notif-panel-clear" id="lt-notif-clear-all">Mark all read</button>
</div>
<div class="lt-notif-panel-list">
<div class="lt-notif-item lt-notif-item--unread" role="button" tabindex="0">
<span class="lt-notif-dot"></span>
<div class="lt-notif-item-body">
<div class="lt-notif-item-title">P1 alert: storage link-down</div>
<div class="lt-notif-item-time">5 min ago</div>
</div>
</div>
<div class="lt-notif-item lt-notif-item--unread" role="button" tabindex="0">
<span class="lt-notif-dot"></span>
<div class="lt-notif-item-body">
<div class="lt-notif-item-title">Worker node-03 reconnected</div>
<div class="lt-notif-item-time">12 min ago</div>
</div>
</div>
<div class="lt-notif-item lt-notif-item--unread" role="button" tabindex="0">
<span class="lt-notif-dot"></span>
<div class="lt-notif-item-body">
<div class="lt-notif-item-title">Export CSV complete — 42 rows</div>
<div class="lt-notif-item-time">1 hr ago</div>
</div>
</div>
<div class="lt-notif-item" role="button" tabindex="0">
<span class="lt-notif-dot lt-notif-dot--read"></span>
<div class="lt-notif-item-body">
<div class="lt-notif-item-title">Scheduled maintenance completed</div>
<div class="lt-notif-item-time">3 hr ago</div>
</div>
</div>
</div>
<div class="lt-notif-panel-footer">
<button class="lt-btn lt-btn-sm lt-btn-ghost" style="width:100%;font-size:0.72rem">View all notifications</button>
</div>
</div>
</div>
<!-- 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 -->
2026-03-26 18:22:53 -04:00
<div id="lt-detail-drawer" class="lt-drawer-right" aria-hidden="true" role="dialog" aria-modal="true" 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">
<!-- Read-only meta row -->
<div class="lt-kv-grid" style="margin-bottom:1rem">
<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">Created</span><span class="lt-kv-value lt-text-muted">2026-03-10 09:14 UTC</span></div>
</div>
<!-- Editable fields -->
<div class="lt-form-group">
<label class="lt-label" for="td-title">Title</label>
<input id="td-title" class="lt-input" type="text" value="Storage array link-down on compute-storage-01">
</div>
<div class="lt-drawer-form-grid" style="display:grid;grid-template-columns:1fr 1fr;gap:0.75rem">
<div class="lt-form-group">
<label class="lt-label" for="td-status">Status</label>
<select id="td-status" class="lt-select">
<option selected>Open</option>
<option>In Progress</option>
<option>Pending</option>
<option>Closed</option>
</select>
</div>
<div class="lt-form-group">
<label class="lt-label" for="td-priority">Priority</label>
<select id="td-priority" class="lt-select">
<option selected>P1 Critical</option>
<option>P2 High</option>
<option>P3 Medium</option>
<option>P4 Low</option>
</select>
</div>
</div>
<div class="lt-form-group">
<label class="lt-label" for="td-assignee">Assignee</label>
<select id="td-assignee" class="lt-select">
<option>Unassigned</option>
<option selected>jdoe</option>
<option>operator</option>
<option>admin</option>
</select>
</div>
<div class="lt-form-group">
<label class="lt-label" for="td-desc">Description</label>
<textarea id="td-desc" class="lt-input lt-textarea" rows="3" style="resize:vertical">Storage array link-down on compute-storage-01. Affects prod write path. Investigate RAID controller firmware.</textarea>
</div>
<!-- Add a comment -->
<div class="lt-divider-label" style="margin:1rem 0 0.75rem">Add Comment</div>
<div class="lt-form-group" style="margin-bottom:0.5rem">
<textarea id="td-comment" class="lt-input lt-textarea" rows="2" placeholder="Leave a comment…" style="resize:vertical"></textarea>
</div>
2026-03-26 18:22:53 -04:00
<button type="button" class="lt-btn lt-btn-sm" onclick="
const c=document.getElementById('td-comment');
if(c.value.trim()){lt.toast.success('Comment posted');c.value='';}
else lt.toast.warning('Comment is empty');
">Post Comment</button>
<!-- Activity timeline -->
<div class="lt-divider-label" style="margin:1rem 0 0.75rem">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">
2026-03-26 18:22:53 -04:00
<button type="button" class="lt-btn lt-btn-sm" onclick="lt.rightDrawer.close('lt-detail-drawer')">Cancel</button>
<button type="button" class="lt-btn lt-btn-primary lt-btn-sm" onclick="lt.toast.success('Ticket #123456789 updated')">Save Changes</button>
</div>
</div>
<div id="lt-detail-overlay" class="lt-drawer-right-overlay"></div>
<!-- ===========================================================
MAIN CONTENT AREA
=========================================================== -->
2026-03-26 18:22:53 -04:00
<main class="lt-main lt-container" id="main-content">
<!-- 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
========================================================== -->
2026-03-26 18:22:53 -04:00
<div class="lt-tabs" role="tablist" aria-label="Main views">
<button class="lt-tab active" role="tab" data-tab="tab-table" aria-selected="true" aria-controls="tab-table" id="tab-btn-table">Table View</button>
<button class="lt-tab" role="tab" data-tab="tab-kanban" aria-selected="false" aria-controls="tab-kanban" id="tab-btn-kanban">Kanban</button>
<button class="lt-tab" role="tab" data-tab="tab-workers" aria-selected="false" aria-controls="tab-workers" id="tab-btn-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">
2026-03-26 18:22:53 -04:00
<fieldset class="lt-filter-group">
<legend class="lt-filter-label">Status</legend>
<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>
2026-03-26 18:22:53 -04:00
</fieldset>
2026-03-26 18:22:53 -04:00
<fieldset class="lt-filter-group">
<legend class="lt-filter-label">Priority</legend>
<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>
2026-03-26 18:22:53 -04:00
</fieldset>
<div class="lt-filter-group">
<label class="lt-filter-label" for="filter-assigned-to">Assigned To</label>
<div class="lt-form-group">
<select class="lt-select lt-btn-sm" id="filter-assigned-to">
<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" id="ticket-search"
2026-03-26 18:22:53 -04:00
placeholder="Search tickets..." aria-label="Search" autocomplete="off">
</div>
<!-- Advanced filter dropdown -->
<div class="lt-dropdown-wrap" id="adv-filter-wrap">
<button class="lt-btn lt-btn-sm lt-dropdown-trigger" id="adv-filter-btn" aria-expanded="false" aria-haspopup="true">Advanced ▾</button>
<div class="lt-dropdown-panel" id="adv-filter-panel" aria-hidden="true">
<div style="padding:0.75rem;display:grid;gap:0.5rem;width:clamp(200px,60vw,260px)">
<div class="lt-form-group" style="margin:0">
<label class="lt-label" style="font-size:0.75rem">Status</label>
<select class="lt-select" style="font-size:0.8rem">
<option value="">All</option>
<option>Open</option>
<option>In Progress</option>
<option>Pending</option>
<option>Closed</option>
</select>
</div>
<div class="lt-form-group" style="margin:0">
<label class="lt-label" style="font-size:0.75rem">Priority</label>
<select class="lt-select" style="font-size:0.8rem">
<option value="">All</option>
<option>P1 Critical</option>
<option>P2 High</option>
<option>P3 Medium</option>
<option>P4 Low</option>
</select>
</div>
<div class="lt-form-group" style="margin:0">
<label class="lt-label" style="font-size:0.75rem">Assignee</label>
<select class="lt-select" style="font-size:0.8rem">
<option value="">Anyone</option>
<option>Unassigned</option>
<option>jdoe</option>
<option>operator</option>
<option>admin</option>
</select>
</div>
<div style="display:flex;gap:0.5rem;margin-top:0.25rem">
2026-03-26 18:22:53 -04:00
<button type="button" class="lt-btn lt-btn-sm lt-btn-primary" style="flex:1" onclick="lt.toast.info('Filters applied');document.getElementById('adv-filter-panel').setAttribute('aria-hidden','true');document.getElementById('adv-filter-btn').setAttribute('aria-expanded','false')">Apply</button>
<button type="button" class="lt-btn lt-btn-sm lt-btn-ghost" onclick="lt.toast.info('Filters cleared')">Reset</button>
</div>
</div>
</div>
</div>
</div>
<div class="lt-toolbar-right">
<span class="lt-text-muted lt-text-xs lt-hide-xs" id="ticket-result-count">42 results</span>
<!-- Bulk actions dropdown -->
<div class="lt-dropdown-wrap" id="bulk-action-wrap">
<button class="lt-btn lt-btn-sm lt-btn-ghost lt-dropdown-trigger" id="bulk-action-btn" aria-expanded="false" aria-haspopup="true">Bulk Actions ▾</button>
<div class="lt-dropdown-panel lt-dropdown-panel--right" id="bulk-action-panel" aria-hidden="true">
2026-03-26 18:22:53 -04:00
<button type="button" class="lt-dropdown-item" onclick="lt.toast.success('Closed selected tickets');this.closest('.lt-dropdown-panel').setAttribute('aria-hidden','true')">✓ Close Selected</button>
<button type="button" class="lt-dropdown-item" onclick="lt.toast.info('Reassign dialog…');this.closest('.lt-dropdown-panel').setAttribute('aria-hidden','true')">↩ Reassign…</button>
<button type="button" class="lt-dropdown-item" onclick="lt.toast.info('Exporting selected…');this.closest('.lt-dropdown-panel').setAttribute('aria-hidden','true')">⤓ Export Selected</button>
<div class="lt-dropdown-divider"></div>
2026-03-26 18:22:53 -04:00
<button type="button" class="lt-dropdown-item lt-dropdown-item--danger" onclick="lt.toast.error('Deleted selected');this.closest('.lt-dropdown-panel').setAttribute('aria-hidden','true')">🗑 Delete Selected</button>
</div>
</div>
</div>
</div>
<!-- ==================================================
TAB PANEL: TABLE VIEW
================================================== -->
2026-03-26 18:22:53 -04:00
<div id="tab-table" class="lt-tab-panel active" role="tabpanel" aria-labelledby="tab-btn-table">
<!-- 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" aria-label="Ticket queue">
<caption class="lt-sr-only">Ticket queue — sorted by priority</caption>
<thead>
<tr>
2026-03-26 18:22:53 -04:00
<th scope="col"><input type="checkbox" class="lt-checkbox" aria-label="Select all"></th>
<th scope="col" data-sort-key="id" aria-sort="none">ID</th>
<th scope="col" data-sort-key="priority" aria-sort="none">Priority</th>
<th scope="col" data-sort-key="title" aria-sort="none">Title</th>
<th scope="col" data-sort-key="status" aria-sort="none">Status</th>
<th scope="col" data-sort-key="assignee" aria-sort="none">Assignee</th>
<th scope="col" data-sort-key="created" aria-sort="none">Created</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<!-- P1 Critical row -->
<tr class="lt-row-p1 lt-row-critical">
2026-03-26 18:22:53 -04:00
<td data-label="Select"><input type="checkbox" class="lt-checkbox" aria-label="Select ticket #123456789"></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">
2026-03-26 18:22:53 -04:00
<td data-label="Select"><input type="checkbox" class="lt-checkbox" aria-label="Select ticket #987654321"></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">
2026-03-26 18:22:53 -04:00
<td data-label="Select"><input type="checkbox" class="lt-checkbox" aria-label="Select ticket #111222333"></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">
2026-03-26 18:22:53 -04:00
<td data-label="Select"><input type="checkbox" class="lt-checkbox" aria-label="Select ticket #444555666"></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
================================================== -->
2026-03-26 18:22:53 -04:00
<div id="tab-kanban" class="lt-tab-panel" role="tabpanel" aria-labelledby="tab-btn-kanban">
<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" id="kanban-col-open" style="min-height:60px">
<div class="lt-card lt-mb-md lt-row-p1" role="article" tabindex="0" aria-label="P1 — Storage array link-down, 5m ago, Unassigned">
<div class="lt-flex lt-flex-between lt-mb-md">
<span class="lt-p1" aria-hidden="true">P1</span>
<span class="lt-dot lt-dot-up" aria-hidden="true"></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" role="article" tabindex="0" aria-label="P3 — Update node_exporter on micro1, 1d ago, operator">
<div class="lt-flex lt-flex-between lt-mb-md">
<span class="lt-p3" aria-hidden="true">P3</span>
<span class="lt-dot lt-dot-up" aria-hidden="true"></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" id="kanban-col-pending" style="min-height:60px">
<div class="lt-card lt-row-p2" role="article" tabindex="0" aria-label="P2 — Scheduled maintenance window, 2d ago, admin">
<div class="lt-flex lt-flex-between lt-mb-md">
<span class="lt-p2" aria-hidden="true">P2</span>
<span class="lt-dot lt-dot-warn" aria-hidden="true"></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" id="kanban-col-inprogress" style="min-height:60px">
<div class="lt-card lt-row-p2 lt-item-running" role="article" tabindex="0" aria-label="P2 — Switch port flapping on USW-Pro, 2h ago, operator">
<div class="lt-flex lt-flex-between lt-mb-md">
<span class="lt-p2" aria-hidden="true">P2</span>
<span class="lt-dot lt-dot-warn" aria-hidden="true"></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" id="kanban-col-closed" style="min-height:60px">
<div class="lt-card lt-row-p4" style="opacity:0.6" role="article" tabindex="0" aria-label="P4 — Update SSL cert on wiki, 3d ago, operator">
<div class="lt-flex lt-flex-between lt-mb-md">
<span class="lt-p4" aria-hidden="true">P4</span>
<span class="lt-dot lt-dot-idle" aria-hidden="true"></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
================================================== -->
2026-03-26 18:22:53 -04:00
<div id="tab-workers" class="lt-tab-panel" role="tabpanel" aria-labelledby="tab-btn-workers">
<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">
2026-03-26 18:22:53 -04:00
<tbody>
<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>
</tbody>
</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"
2026-03-26 18:22:53 -04:00
placeholder="Ctrl+K to focus" autocomplete="off">
</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>
2026-03-26 18:22:53 -04:00
<div class="lt-progress" role="progressbar" aria-valuenow="72" aria-valuemin="0" aria-valuemax="100" aria-label="CPU Load"><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>
2026-03-26 18:22:53 -04:00
<div class="lt-progress lt-progress--cyan" role="progressbar" aria-valuenow="45" aria-valuemin="0" aria-valuemax="100" aria-label="Memory"><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>
2026-03-26 18:22:53 -04:00
<div class="lt-progress lt-progress--red lt-progress--lg" role="progressbar" aria-valuenow="89" aria-valuemin="0" aria-valuemax="100" aria-label="Disk I/O"><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>
2026-03-26 18:22:53 -04:00
<div class="lt-progress lt-progress--green lt-progress--striped" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" aria-label="Uptime"><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" id="demo-pagination" aria-label="pagination"></nav>
</div>
<!-- ACCORDION -->
<div class="lt-section-header">Accordion</div>
<div class="lt-section-body">
<div>
<div class="lt-accordion">
2026-03-26 18:22:53 -04:00
<button class="lt-accordion-header" aria-expanded="false" aria-controls="acc-body-1" 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>
2026-03-26 18:22:53 -04:00
<div class="lt-accordion-body" id="acc-body-1"><div class="lt-accordion-content">Node running at 72% CPU. 12 active processes. Last restart: 3d ago.</div></div>
</div>
<div class="lt-accordion">
2026-03-26 18:22:53 -04:00
<button class="lt-accordion-header" aria-expanded="false" aria-controls="acc-body-2" 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>
2026-03-26 18:22:53 -04:00
<div class="lt-accordion-body" id="acc-body-2"><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">
2026-03-26 18:22:53 -04:00
<button class="lt-accordion-header" aria-expanded="false" aria-controls="acc-body-3" 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>
2026-03-26 18:22:53 -04:00
<div class="lt-accordion-body" id="acc-body-3"><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">
2026-03-26 18:22:53 -04:00
<button class="lt-tab active" role="tab" id="tab2-btn-overview" data-tab-target="tab-overview" aria-selected="true" aria-controls="tab-overview">Overview</button>
<button class="lt-tab" role="tab" id="tab2-btn-logs" data-tab-target="tab-logs" aria-selected="false" aria-controls="tab-logs">Logs</button>
<button class="lt-tab" role="tab" id="tab2-btn-metrics" data-tab-target="tab-metrics" aria-selected="false" aria-controls="tab-metrics">Metrics</button>
<button class="lt-tab" role="tab" id="tab2-btn-config" data-tab-target="tab-config" aria-selected="false" aria-controls="tab-config">Config</button>
</div>
<div class="lt-tab-panels" style="padding:var(--space-md) 0">
2026-03-26 18:22:53 -04:00
<div id="tab-overview" class="lt-tab-panel active" role="tabpanel" aria-labelledby="tab2-btn-overview">System overview: all nodes nominal. 14 active sessions.</div>
<div id="tab-logs" class="lt-tab-panel" role="tabpanel" aria-labelledby="tab2-btn-logs">
<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>
2026-03-26 18:22:53 -04:00
<div id="tab-metrics" class="lt-tab-panel" role="tabpanel" aria-labelledby="tab2-btn-metrics">CPU avg: 34% — Mem avg: 61% — Net TX: 4.2 Mbps</div>
<div id="tab-config" class="lt-tab-panel" role="tabpanel" aria-labelledby="tab2-btn-config">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>
2026-03-26 18:22:53 -04:00
<button type="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">
2026-03-26 18:22:53 -04:00
<button type="button" class="lt-btn lt-btn-primary" onclick="lt.theme.toggle()">Toggle Theme</button>
<button type="button" class="lt-btn" onclick="lt.theme.set('dark')">Force Dark</button>
<button type="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>
2026-03-26 18:22:53 -04:00
<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 type="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>
2026-03-26 18:22:53 -04:00
<button type="button" class="lt-btn lt-btn-sm" onclick="lt.notif.inc('#demo-notif-btn')">+1 Badge</button>
<button type="button" class="lt-btn lt-btn-sm" onclick="lt.notif.clear('#demo-notif-btn')">Clear</button>
<button type="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;grid-template-columns:repeat(auto-fit,minmax(240px,1fr))">
<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:clamp(160px,30vh,240px);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">
2026-03-26 18:22:53 -04:00
<button type="button" class="lt-btn lt-btn-sm" onclick="lt.offline.isOnline() ? lt.toast.success('Online ✓') : lt.toast.error('Offline ✗')">Check Online Status</button>
<button type="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>
<!-- Wizard / Multi-Step Form -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// WIZARD / MULTI-STEP FORM</span>
</div>
<div class="lt-section-body">
<div id="demo-wizard">
<!-- Step indicators -->
<div class="lt-wizard-steps">
2026-03-26 18:22:53 -04:00
<div class="lt-wizard-step is-active" data-wizard-indicator aria-current="step">
<div class="lt-wizard-num">1</div>
<div class="lt-wizard-label">Details</div>
</div>
<div class="lt-wizard-step" data-wizard-indicator>
<div class="lt-wizard-num">2</div>
<div class="lt-wizard-label">Assign</div>
</div>
<div class="lt-wizard-step" data-wizard-indicator>
<div class="lt-wizard-num">3</div>
<div class="lt-wizard-label">Review</div>
</div>
</div>
<!-- Counter -->
<p class="lt-wizard-counter">Step <strong data-wizard-current>1</strong> of <strong data-wizard-total>3</strong></p>
<!-- Steps -->
<div data-wizard-step="1" class="is-active">
<div class="lt-grid lt-grid-2" style="gap:1rem;margin-top:1rem">
<div class="lt-form-group"><label class="lt-label">Ticket Title</label><input type="text" name="title" class="lt-input lt-w-full" placeholder="Brief description…"></div>
<div class="lt-form-group"><label class="lt-label">Priority</label><select name="priority" class="lt-select lt-w-full"><option value="p1">P1 Critical</option><option value="p2">P2 High</option><option value="p3" selected>P3 Medium</option><option value="p4">P4 Low</option></select></div>
</div>
</div>
<div data-wizard-step="2" aria-hidden="true">
<div class="lt-form-group" style="margin-top:1rem"><label class="lt-label">Assign To</label><input type="text" name="assignee" class="lt-input lt-w-full" placeholder="Username…"></div>
<div class="lt-form-group"><label class="lt-label">Due Date</label><input type="date" name="due" class="lt-input"></div>
</div>
<div data-wizard-step="3" aria-hidden="true">
<div class="lt-empty-state lt-empty-state--sm"><div class="lt-empty-state-icon"></div><div class="lt-empty-state-title">Review &amp; Submit</div><div class="lt-empty-state-body">Check the details above and click Submit when ready.</div></div>
</div>
<!-- Nav -->
<div class="lt-wizard-nav">
<button class="lt-btn lt-btn-sm" data-wizard-prev disabled>← Back</button>
<button class="lt-btn lt-btn-primary lt-btn-sm" data-wizard-next>Next →</button>
<button class="lt-btn lt-btn-primary lt-btn-sm" data-wizard-done style="display:none">Submit ✓</button>
</div>
</div>
</div>
</div>
<!-- Sortable List -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// DRAG-TO-REORDER (SORTABLE)</span>
</div>
<div class="lt-section-body">
<div class="lt-grid lt-grid-2" style="gap:1.5rem;grid-template-columns:repeat(auto-fit,minmax(220px,1fr))">
<div>
<p style="font-size:0.75rem;color:var(--text-muted);margin-bottom:0.75rem">Drag items to reorder. Uses native HTML5 drag API with pointer-events fallback.</p>
<ul id="demo-sortable" style="list-style:none;padding:0;display:flex;flex-direction:column;gap:4px">
<li data-id="p1" style="padding:0.5rem 0.75rem;background:var(--bg-card);border:1px solid var(--border-dim);display:flex;align-items:center;gap:0.5rem"><span style="color:var(--text-muted);cursor:grab"></span> P1 — Storage link-down</li>
<li data-id="p2" style="padding:0.5rem 0.75rem;background:var(--bg-card);border:1px solid var(--border-dim);display:flex;align-items:center;gap:0.5rem"><span style="color:var(--text-muted);cursor:grab"></span> P2 — Switch port flapping</li>
<li data-id="p3" style="padding:0.5rem 0.75rem;background:var(--bg-card);border:1px solid var(--border-dim);display:flex;align-items:center;gap:0.5rem"><span style="color:var(--text-muted);cursor:grab"></span> P3 — SFP+ replacement</li>
<li data-id="p4" style="padding:0.5rem 0.75rem;background:var(--bg-card);border:1px solid var(--border-dim);display:flex;align-items:center;gap:0.5rem"><span style="color:var(--text-muted);cursor:grab"></span> P4 — SSL cert renewal</li>
</ul>
<p id="demo-sort-order" style="font-size:0.68rem;color:var(--text-muted);margin-top:0.5rem">Order: p1, p2, p3, p4</p>
</div>
<div>
<p style="font-size:0.72rem;color:var(--text-muted);margin-bottom:0.75rem">API:</p>
<pre class="lt-code-block" style="font-size:0.7rem">const s = lt.sortable.init(listEl, {
onSort: (items) => console.log(s.getOrder())
});</pre>
</div>
</div>
</div>
</div>
<!-- Countdown / Timer -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// COUNTDOWN &amp; TIMER</span>
</div>
<div class="lt-section-body">
<div class="lt-grid lt-grid-3" style="gap:1rem;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))">
<div class="lt-stat-card">
<div class="lt-stat-label">SLA Countdown</div>
<div class="lt-countdown lt-num" id="demo-countdown">--:--:--</div>
<div style="font-size:0.68rem;color:var(--text-muted);margin-top:0.25rem">Goes urgent (red) at &lt;5 min</div>
</div>
<div class="lt-stat-card">
<div class="lt-stat-label">Stopwatch</div>
<div class="lt-countdown lt-num" id="demo-stopwatch">00:00:00</div>
<div class="lt-flex lt-gap-xs" style="margin-top:0.5rem">
<button class="lt-btn lt-btn-sm" id="sw-pause">Pause</button>
<button class="lt-btn lt-btn-sm" id="sw-reset">Reset</button>
</div>
</div>
<div class="lt-stat-card">
<div class="lt-stat-label">API</div>
<pre class="lt-code-block" style="font-size:0.62rem;margin:0">lt.timer.countdown(el, date, {
urgent: 300,
onExpire: () => {}
});
lt.timer.stopwatch(el);</pre>
</div>
</div>
</div>
</div>
<!-- Image Lightbox -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// IMAGE LIGHTBOX</span>
</div>
<div class="lt-section-body">
<p style="font-size:0.78rem;color:var(--text-secondary);margin-bottom:1rem">Click any image to open full-screen viewer. Keyboard: ←/→ navigate, Esc closes.</p>
<div class="lt-flex lt-gap-md lt-wrap">
<img class="lt-lightbox-demo" src="https://picsum.photos/seed/lt1/320/180" alt="Server rack overview" style="width:160px;height:90px;object-fit:cover;border:1px solid var(--border-dim)" loading="lazy">
<img class="lt-lightbox-demo" src="https://picsum.photos/seed/lt2/320/180" alt="Network switch closeup" style="width:160px;height:90px;object-fit:cover;border:1px solid var(--border-dim)" loading="lazy">
<img class="lt-lightbox-demo" src="https://picsum.photos/seed/lt3/320/180" alt="Datacenter floor view" style="width:160px;height:90px;object-fit:cover;border:1px solid var(--border-dim)" loading="lazy">
</div>
<p style="font-size:0.72rem;color:var(--text-muted);margin-top:0.5rem"><code>lt.lightbox.init('.lt-lightbox-demo', { caption: 'alt', loop: true })</code></p>
</div>
</div>
<!-- Sidebar Submenus -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// SIDEBAR SUBMENUS</span>
</div>
<div class="lt-section-body">
<div style="width:220px;background:var(--bg-secondary);border:1px solid var(--border-dim);padding:0.5rem">
<a href="#" class="lt-sidebar-link active" style="display:flex;align-items:center;gap:0.5rem;padding:0.4rem 0.75rem;font-size:0.78rem;color:var(--accent-orange);text-decoration:none">⊞ Dashboard</a>
<a href="#" class="lt-sidebar-link" style="display:flex;align-items:center;gap:0.5rem;padding:0.4rem 0.75rem;font-size:0.78rem;color:var(--text-secondary);text-decoration:none">🎫 Tickets</a>
<div class="lt-sidebar-group is-open">
<div class="lt-sidebar-group-label">Admin <span class="chevron"></span></div>
<div class="lt-sidebar-submenu">
<a href="#" class="lt-sidebar-sub-link active" aria-current="page">⚙ Templates</a>
<a href="#" class="lt-sidebar-sub-link">🔀 Workflow</a>
<a href="#" class="lt-sidebar-sub-link">📋 Audit Log</a>
<a href="#" class="lt-sidebar-sub-link">🔑 API Keys</a>
</div>
</div>
<div class="lt-sidebar-group">
<div class="lt-sidebar-group-label">Reports <span class="chevron"></span></div>
<div class="lt-sidebar-submenu">
<a href="#" class="lt-sidebar-sub-link">📊 SLA Report</a>
<a href="#" class="lt-sidebar-sub-link">📈 Volume Trends</a>
</div>
</div>
</div>
</div>
</div>
<!-- Markdown Renderer -->
<div class="lt-section">
<div class="lt-section-header">
<span class="lt-section-title">// MARKDOWN RENDERER</span>
</div>
<div class="lt-section-body">
<div class="lt-grid lt-grid-2" style="gap:1.5rem">
<div>
<div class="lt-section-label" style="margin-bottom:0.5rem">Source</div>
<pre class="lt-code-block" style="font-size:0.7rem"># Incident Report
**Severity**: P1 Critical
## Summary
Storage array link-down on `compute-storage-01`.
- Affected: 3 production services
- Duration: 2h 33m
- Root cause: NIC hardware failure
> Resolved. Monitoring continues.
[View Ticket](#001)</pre>
</div>
<div>
<div class="lt-section-label" style="margin-bottom:0.5rem">Rendered</div>
<div id="demo-markdown" class="lt-markdown" style="background:var(--bg-card);border:1px solid var(--border-dim);padding:1rem" data-markdown="# Incident Report&#10;**Severity**: P1 Critical&#10;&#10;## Summary&#10;Storage array link-down on &#96;compute-storage-01&#96;.&#10;&#10;- Affected: 3 production services&#10;- Duration: 2h 33m&#10;- Root cause: NIC hardware failure&#10;&#10;&gt; Resolved. Monitoring continues.&#10;&#10;[View Ticket](#001)"></div>
</div>
</div>
<p style="font-size:0.72rem;color:var(--text-muted);margin-top:0.75rem">Built-in micro-renderer (no deps). Drops in <code>window.marked</code> or <code>window.markdownit</code> automatically if present.</p>
</div>
</div>
</main><!-- /.lt-main -->
2026-03-26 18:22:53 -04:00
<!-- ================================================================
FOOTER LANDMARK
================================================================ -->
<footer class="lt-footer" role="contentinfo" aria-label="Application footer">
<span class="lt-text-muted lt-font-mono lt-text-xs">
LotusGuild Terminal Design System v1.2 &mdash; Internal Use Only
</span>
<span class="lt-text-muted lt-font-mono lt-text-xs">
&copy; <span id="footer-year"></span> LotusGuild. All rights reserved.
</span>
</footer>
<!-- ===========================================================
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">
2026-03-26 18:22:53 -04:00
<button type="button" class="lt-btn lt-btn-sm" onclick="lt.toast.success('Ticket saved successfully')">✓ Toast</button>
<button type="button" class="lt-btn lt-btn-sm lt-btn-danger" onclick="lt.toast.error('Network error — retry in 5s', 5000)">✗ Error</button>
<button type="button" class="lt-btn lt-btn-sm" onclick="lt.toast.warning('Rate limit 80% used')">! Warn</button>
<button type="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">
2026-03-26 18:22:53 -04:00
<div class="lt-modal" role="dialog" aria-modal="true" aria-labelledby="export-modal-title">
<div class="lt-modal-header">
2026-03-26 18:22:53 -04:00
<span class="lt-modal-title" id="export-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">
2026-03-26 18:22:53 -04:00
<button type="button" class="lt-btn lt-btn-ghost" data-modal-close>Cancel</button>
<button type="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">
2026-03-26 18:22:53 -04:00
<div class="lt-modal" role="dialog" aria-modal="true" aria-labelledby="keys-help-title">
<div class="lt-modal-header">
2026-03-26 18:22:53 -04:00
<span class="lt-modal-title" id="keys-help-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', () => {
// One call initializes accordion, tooltip, alerts, clipboard, sidebar, submenus + boot.
// Pass { boot: false } to skip the terminal boot animation.
lt.init({ bootName: 'MY APP' });
// 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);
// Wizard demo
lt.wizard.init(document.getElementById('demo-wizard'), {
onComplete: data => lt.toast.success('Ticket submitted: ' + (data.title || 'untitled')),
validate: (step, data) => {
if (step === 1 && !data.title?.trim()) { lt.toast.error('Title is required'); return false; }
return true;
},
});
// Sortable demo
const sortableList = lt.sortable.init(document.getElementById('demo-sortable'), {
onSort: (items) => {
document.getElementById('demo-sort-order').textContent = 'Order: ' + items.map(el => el.dataset.id).join(', ');
},
});
// Kanban drag-and-drop — all four columns share group "kanban"
['kanban-col-open','kanban-col-pending','kanban-col-inprogress','kanban-col-closed'].forEach(id => {
const col = document.getElementById(id);
if (col) lt.sortable.init(col, { group: 'kanban', onSort: () => {} });
});
// Pagination demo (50 items, 10 per page)
lt.pagination.init('#demo-pagination', {
total: 50, perPage: 10, page: 2,
onChange: p => lt.toast.info('Page ' + p),
});
// Countdown demo — SLA expires 2 hours from now
const slaTarget = new Date(Date.now() + 2 * 60 * 60 * 1000);
lt.timer.countdown(document.getElementById('demo-countdown'), slaTarget, {
urgent: 300,
urgentClass: 'lt-text-red lt-countdown-urgent',
onExpire: () => lt.toast.error('SLA BREACHED'),
});
// Stopwatch demo
const sw = lt.timer.stopwatch(document.getElementById('demo-stopwatch'));
let swRunning = true;
document.getElementById('sw-pause').addEventListener('click', function() {
if (swRunning) { sw.pause(); this.textContent = 'Resume'; } else { sw.resume(); this.textContent = 'Pause'; }
swRunning = !swRunning;
});
document.getElementById('sw-reset').addEventListener('click', () => { sw.reset(); swRunning = true; document.getElementById('sw-pause').textContent = 'Pause'; });
// Lightbox demo
lt.lightbox.init('.lt-lightbox-demo');
// Markdown demo
lt.markdown.init('#demo-markdown');
// Sidebar submenus (re-init for demo sidebar)
lt.sidebarSubmenus.init(document.querySelector('.lt-section-body'));
// ----- Notification bell dropdown -----
(function() {
const btn = document.getElementById('lt-notif-bell-btn');
const panel = document.getElementById('lt-notif-panel');
if (!btn || !panel) return;
function open() {
panel.removeAttribute('aria-hidden');
btn.setAttribute('aria-expanded', 'true');
// Mark all as read visually
}
function close() {
panel.setAttribute('aria-hidden', 'true');
btn.setAttribute('aria-expanded', 'false');
}
btn.addEventListener('click', e => {
e.stopPropagation();
panel.hasAttribute('aria-hidden') ? open() : close();
});
// "Mark all read" button
const clearBtn = document.getElementById('lt-notif-clear-all');
if (clearBtn) clearBtn.addEventListener('click', () => {
panel.querySelectorAll('.lt-notif-item--unread').forEach(el => el.classList.remove('lt-notif-item--unread'));
panel.querySelectorAll('.lt-notif-dot').forEach(el => { el.classList.remove('lt-notif-dot'); el.classList.add('lt-notif-dot', 'lt-notif-dot--read'); });
lt.notif.clear('#lt-notif-bell');
lt.toast.info('All notifications marked as read');
});
// Individual item click + keyboard activation
panel.querySelectorAll('.lt-notif-item').forEach(item => {
const activate = () => {
item.classList.remove('lt-notif-item--unread');
const dot = item.querySelector('.lt-notif-dot');
if (dot) { dot.classList.add('lt-notif-dot--read'); }
close();
};
item.addEventListener('click', activate);
item.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); activate(); } });
});
// Close on outside click
document.addEventListener('click', e => {
if (!document.getElementById('lt-notif-bell').contains(e.target)) close();
});
// Esc close
document.addEventListener('keydown', e => { if (e.key === 'Escape') close(); });
}());
// ----- Generic dropdown toggle (Advanced filter + Bulk Actions) -----
document.querySelectorAll('.lt-dropdown-trigger').forEach(btn => {
const wrap = btn.closest('.lt-dropdown-wrap');
const panel = wrap && wrap.querySelector('.lt-dropdown-panel');
if (!panel) return;
btn.addEventListener('click', e => {
e.stopPropagation();
const isOpen = !panel.hasAttribute('aria-hidden');
// Close all other dropdowns first
document.querySelectorAll('.lt-dropdown-panel:not([aria-hidden])').forEach(p => {
p.setAttribute('aria-hidden', 'true');
const t = p.closest('.lt-dropdown-wrap').querySelector('.lt-dropdown-trigger');
if (t) t.setAttribute('aria-expanded', 'false');
});
if (!isOpen) {
panel.removeAttribute('aria-hidden');
btn.setAttribute('aria-expanded', 'true');
}
});
});
// Close dropdowns on outside click / Esc
document.addEventListener('click', () => {
document.querySelectorAll('.lt-dropdown-panel:not([aria-hidden])').forEach(p => {
p.setAttribute('aria-hidden', 'true');
const t = p.closest('.lt-dropdown-wrap').querySelector('.lt-dropdown-trigger');
if (t) t.setAttribute('aria-expanded', 'false');
});
});
2026-03-26 18:22:53 -04:00
// Footer year
const footerYear = document.getElementById('footer-year');
if (footerYear) footerYear.textContent = new Date().getFullYear();
// 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>