Files
web_template/base.html
T
jared db67f0c92b v1.2 → v1.3: responsive overhaul, mobile nav, accessibility & bug fixes
- 8-breakpoint responsive system (xs→4k) replacing 3-breakpoint stub
- Off-canvas mobile nav drawer with swipe gestures and focus trap
- iOS-safe scroll lock for modals (position:fixed pattern + ref count)
- Modal focus trap with Tab cycling and return-focus to trigger
- Z-index stack overhaul: modal > nav drawer, toast above scanlines
- Fixed --border-dim CSS variable undefined across all v1.2 components
- Fixed badge absolute positioning clipping in Component Reference
- Fixed accordion CSS class mismatch (open → is-open)
- Fixed command palette selector/class mismatches
- Select dark mode: color-scheme:dark + option background
- 4K scaling: rem-based overrides for all hardcoded px elements
- Safe area insets for iPhone notch/home bar (viewport-fit:cover)
- Touch targets: 44px min on all interactive elements (pointer:coarse)
- Disabled glitch/pulse animations on coarse/hover:none devices
- Table responsive card mode with data-label attributes
- viewport.is() validation, debounce caching, 350ms orientation debounce
- initMobileNav() guard prevents duplicate listener registration
- cmd palette: input.select() on open, consistent scroll lock

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 18:47:26 -04:00

1137 lines
53 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">
<!-- Current user -->
<span class="lt-header-user">operator</span>
<!-- Admin badge (only show for admins) -->
<span class="lt-badge-admin">admin</span>
</div>
</header>
<!-- ===========================================================
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) -->
</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; });
});
// 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>