Sync TDS v1.2 additions: scanlines, cursor, radar, display-field, VT323

- Sync base.css + base.js from web_template (adds lt-scanlines,
  lt-cursor, lt-radar, lt-display-field, --font-crt/VT323 token)
- Add VT323 to Google Fonts link in layout_header.php
- Add lt-scanlines to <body> — CRT scanline overlay, light-mode suppressed
- Replace custom .editable-metadata:disabled CSS override in ticket.css
  with the canonical .lt-display-field class from base.css
- Switch Priority/Category/Type/Visibility selects and visibility-group
  checkboxes in TicketView.php from disabled attribute to lt-display-field
- Update toggleEditMode() in ticket.js to add/remove lt-display-field
  instead of toggling the disabled attribute

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-01 16:55:12 -04:00
parent 55c6fc81db
commit cfdc9e0f37
6 changed files with 118 additions and 157 deletions
+104 -133
View File
@@ -79,15 +79,6 @@
--accent-purple: #BF5FFF;
--accent-purple-dim: rgba(191,95,255,0.10);
/* ── App semantic aliases (used in ticket.css, dashboard.css) ── */
--lt-danger: var(--accent-red);
--lt-amber: var(--accent-amber);
--lt-cyan: var(--accent-cyan);
--lt-success: var(--accent-green);
--lt-text-primary: var(--accent-green);
--lt-border: var(--border-color);
--lt-surface: var(--bg-card);
/* Legacy aliases — keeps all existing component HTML working */
--terminal-green: var(--accent-green);
--terminal-green-dim: var(--accent-green-dim);
@@ -155,6 +146,7 @@
/* --- Typography --- */
--font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Courier New', monospace;
--font-display: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;
--font-crt: 'VT323', 'Courier New', monospace;
/* --- Spacing --- */
--space-xs: 0.25rem;
@@ -225,8 +217,6 @@ body {
min-height: 100vh;
overflow-x: hidden;
position: relative;
display: flex;
flex-direction: column;
}
a {
@@ -360,18 +350,6 @@ hr {
.lt-main {
padding-top: calc(var(--header-height) + var(--space-lg));
flex: 1;
/* When body is a flex column, margin:0 auto from .lt-container would prevent
stretch. Force full width so max-width+auto-margin centering still works. */
width: 100%;
min-width: 0; /* prevent flex overflow on very small viewports */
}
/* When both lt-main and lt-container are on the same element, the lt-container
shorthand `padding` overrides the lt-main `padding-top` in responsive breakpoints
(same cascade specificity, later rule wins). The combined selector has higher
specificity (0,2,0 vs 0,1,0) and always wins regardless of source order. */
.lt-main.lt-container {
padding-top: calc(var(--header-height) + var(--space-lg));
}
.lt-layout {
@@ -392,17 +370,6 @@ hr {
.lt-flex-col { display: flex; flex-direction: column; }
.lt-flex-wrap { flex-wrap: wrap; }
/* Flex gap modifiers (used with lt-flex) */
.lt-flex-gap-xs { gap: var(--space-xs); }
.lt-flex-gap-sm { gap: var(--space-sm); }
.lt-flex-gap-md { gap: var(--space-md); }
.lt-flex-gap-lg { gap: var(--space-lg); }
/* Flex align modifiers */
.lt-flex-align-start { align-items: flex-start; }
.lt-flex-align-center { align-items: center; }
.lt-flex-align-end { align-items: flex-end; }
.lt-gap-sm { gap: var(--space-sm); }
.lt-gap-md { gap: var(--space-md); }
.lt-gap-lg { gap: var(--space-lg); }
@@ -941,6 +908,19 @@ hr {
border-color: var(--border-dim);
}
/* Display-only fields — readable, non-editable, not "broken" */
.lt-display-field,
.lt-input.lt-display-field,
.lt-select.lt-display-field,
.lt-textarea.lt-display-field {
opacity: 1;
color: var(--text-secondary);
cursor: default;
pointer-events: none;
background: transparent;
border-color: var(--border-dim);
}
.lt-input::placeholder,
.lt-textarea::placeholder { color: var(--text-dim); }
@@ -975,26 +955,11 @@ select option:checked {
clip-path: none;
}
/* Compact size variants for inline filter bars and tight layouts */
.lt-select-sm {
font-size: 0.7rem;
padding: 0.25rem 1.6rem 0.25rem 0.5rem;
width: auto;
}
.lt-input-sm {
font-size: 0.7rem;
padding: 0.25rem 0.5rem;
width: auto;
}
.lt-form-hint {
font-size: 0.63rem;
color: var(--text-muted);
letter-spacing: 0.04em;
}
.lt-form-hint--warn { color: var(--accent-amber); }
.lt-font-mono { font-family: var(--font-mono); }
/* Search */
.lt-search { position: relative; }
@@ -1239,7 +1204,6 @@ select option:checked {
.lt-badge-green { color: var(--accent-green); }
.lt-badge-amber { color: var(--accent-amber); }
.lt-badge-red { color: var(--accent-red); }
.lt-badge-sm { font-size: 0.52rem; padding: 0.05rem 0.3rem; letter-spacing: 0.08em; }
/* Status + priority badge variants (dark-mode base) */
.lt-badge-open { color: var(--accent-green); background: rgba(0,255,136,0.08); border-color: rgba(0,255,136,0.35); text-shadow: var(--glow-green); }
@@ -1610,14 +1574,12 @@ select option:checked {
}
.lt-msg::before { flex-shrink: 0; font-weight: 700; }
.lt-msg-error,
.lt-msg-danger { color: var(--accent-red); background: var(--accent-red-dim); border-left-color: var(--accent-red); }
.lt-msg-error { color: var(--accent-red); background: var(--accent-red-dim); border-left-color: var(--accent-red); }
.lt-msg-success { color: var(--accent-green); background: var(--accent-green-dim); border-left-color: var(--accent-green); }
.lt-msg-warning { color: var(--accent-amber); background: var(--accent-amber-dim); border-left-color: var(--accent-amber); }
.lt-msg-info { color: var(--accent-cyan); background: var(--accent-cyan-dim); border-left-color: var(--accent-cyan); }
.lt-msg-error::before,
.lt-msg-danger::before { content: '✗'; }
.lt-msg-error::before { content: '✗'; }
.lt-msg-success::before { content: '✓'; }
.lt-msg-warning::before { content: '!'; }
.lt-msg-info::before { content: 'i'; }
@@ -2083,7 +2045,6 @@ select option:checked {
.lt-main { padding-top: calc(50px + var(--space-md)); }
.lt-container { padding: var(--space-md); }
.lt-main.lt-container { padding-top: calc(var(--header-height) + var(--space-md)); }
.lt-header { padding: 0 var(--space-md); }
.lt-brand-subtitle { display: none; }
@@ -2184,7 +2145,6 @@ select option:checked {
.lt-main { padding-top: calc(46px + var(--space-sm)); }
.lt-container { padding: var(--space-sm); }
.lt-main.lt-container { padding-top: calc(var(--header-height) + var(--space-sm)); }
.lt-stats-grid { grid-template-columns: 1fr 1fr; gap: var(--space-xs); }
.lt-stat-card { padding: var(--space-xs) var(--space-sm); }
.lt-stat-value { font-size: 1.4rem; }
@@ -2281,7 +2241,6 @@ select option:checked {
.lt-stats-grid { grid-template-columns: repeat(6, 1fr); }
.lt-grid-4 { grid-template-columns: repeat(4, 1fr); }
.lt-container { padding: var(--space-xl) var(--space-2xl); }
.lt-main.lt-container { padding-top: calc(var(--header-height) + var(--space-xl)); }
}
@@ -2349,12 +2308,6 @@ select option:checked {
.lt-text-red { color: var(--accent-red); text-shadow: var(--glow-red); }
.lt-text-muted { color: var(--text-muted); }
.lt-text-dim { color: var(--text-dim); }
/* Semantic aliases */
.lt-text-danger { color: var(--accent-red); text-shadow: var(--glow-red); }
.lt-text-warning { color: var(--accent-amber); text-shadow: var(--glow-amber); }
.lt-text-success { color: var(--accent-green); text-shadow: var(--glow-green); }
.lt-text-info { color: var(--accent-cyan); text-shadow: var(--glow-cyan); }
.lt-text-primary { color: var(--accent-green); text-shadow: var(--glow-green); }
.lt-text-xs { font-size: 0.63rem; }
.lt-text-sm { font-size: 0.78rem; }
@@ -2393,9 +2346,6 @@ select option:checked {
border: 0;
}
/* Global visibility utility — used by JS and all views */
.is-hidden { display: none !important; }
/* Cursor blink */
.lt-cursor::after {
content: '█';
@@ -3229,11 +3179,6 @@ input[type="range"].lt-range::-moz-range-thumb {
.lt-kv-val--green { color: var(--accent-green); }
.lt-kv-val--red { color: var(--accent-red); }
/* v1.2 aliases: lt-kv-row wraps label+value as a transparent grid wrapper */
.lt-kv-row { display: contents; }
.lt-kv-label { padding: var(--space-xs) var(--space-md) var(--space-xs) 0; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.7rem; white-space: nowrap; border-right: 1px solid var(--border-dim); }
.lt-kv-value { padding: var(--space-xs) 0 var(--space-xs) var(--space-md); color: var(--text-primary); }
/* ----------------------------------------------------------------
43. HERO / BANNER SECTION
@@ -4497,10 +4442,9 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
margin-bottom: 0.2rem;
flex-wrap: wrap;
}
.lt-timeline-actor { color: var(--accent-cyan); }
.lt-timeline-action { color: var(--text-secondary); font-style: italic; }
.lt-timeline-time { margin-left: auto; white-space: nowrap; }
.lt-timeline-body { font-size: 0.78rem; color: var(--text-secondary); line-height: 1.5; }
.lt-timeline-actor { color: var(--accent-cyan); }
.lt-timeline-time { margin-left: auto; white-space: nowrap; }
.lt-timeline-body { font-size: 0.78rem; color: var(--text-secondary); line-height: 1.5; }
.lt-timeline-body code { font-size: 0.72rem; color: var(--accent-green); }
@@ -4522,20 +4466,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
flex-shrink: 0;
user-select: none;
}
/* Photo overlay: img sits on top of initials text; hidden via onerror if no photo */
.lt-avatar { position: relative; }
.lt-avatar-initials { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; }
.lt-avatar-img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
border-radius: inherit;
z-index: 1;
}
/* Legacy: bare img inside lt-avatar (no .lt-avatar-img class) */
.lt-avatar > img:not(.lt-avatar-img) { width: 100%; height: 100%; object-fit: cover; display: block; }
.lt-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Sizes */
.lt-avatar--xs { width: 1.5rem; height: 1.5rem; font-size: 0.55rem; }
.lt-avatar--sm { width: 2rem; height: 2rem; font-size: 0.65rem; }
@@ -5560,7 +5491,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
align-items: center;
flex-wrap: wrap;
gap: var(--space-sm);
padding: var(--space-sm) var(--space-lg);
padding: var(--space-md) var(--space-lg);
border-top: 1px solid var(--border-dim);
margin-top: auto;
background: var(--bg-secondary);
@@ -5568,49 +5499,89 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
font-size: 0.7rem;
font-family: var(--font-mono);
}
/* Keyboard hint bar */
.lt-footer-hints {
display: flex;
gap: 0.75rem;
align-items: center;
flex-wrap: wrap;
}
.lt-footer-hint {
display: inline-flex;
align-items: center;
gap: 0.25rem;
color: var(--text-muted);
font-size: 0.68rem;
letter-spacing: 0.03em;
text-decoration: none;
background: none;
border: none;
padding: 0;
font-family: inherit;
cursor: default;
transition: color 0.12s;
}
a.lt-footer-hint,
button.lt-footer-hint {
cursor: pointer;
}
a.lt-footer-hint:hover,
button.lt-footer-hint:hover {
color: var(--accent-green);
text-shadow: var(--glow-green);
}
.lt-footer-key {
color: var(--accent-green);
opacity: 0.75;
font-weight: 700;
}
.lt-footer-sep {
opacity: 0.25;
user-select: none;
}
@media (max-width: 479px) {
.lt-footer { flex-direction: column; align-items: flex-start; gap: 0.25rem; }
.lt-footer-hints { gap: 0.5rem; }
}
/* ================================================================
BLINKING CURSOR
<h1 class="lt-cursor">SYSTEM STATUS</h1>
<span class="lt-cursor lt-cursor--cyan">SCANNING</span>
================================================================ */
.lt-cursor::after {
content: '▊';
animation: lt-blink 1s step-end infinite;
color: var(--accent-green);
margin-left: 2px;
}
.lt-cursor--cyan::after { color: var(--accent-cyan); }
.lt-cursor--orange::after { color: var(--accent-orange); }
.lt-cursor--red::after { color: var(--accent-red); }
@keyframes lt-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
/* ================================================================
CRT SCANLINE OVERLAY
Add lt-scanlines to <body> or any container to enable.
Automatically suppressed in light theme.
================================================================ */
.lt-scanlines::after {
content: '';
position: fixed;
inset: 0;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 0, 0, 0.04) 2px,
rgba(0, 0, 0, 0.04) 4px
);
pointer-events: none;
z-index: 9998;
}
html[data-theme="light"] .lt-scanlines::after { display: none; }
/* ================================================================
RADAR SWEEP LOADING INDICATOR
<div class="lt-radar"></div>
Drop-in replacement for lt-spinner where a radar aesthetic fits.
================================================================ */
.lt-radar {
display: inline-block;
width: 48px; height: 48px;
border-radius: 50%;
border: 1px solid var(--accent-cyan);
position: relative;
overflow: hidden;
flex-shrink: 0;
}
.lt-radar::before {
content: '';
position: absolute;
inset: 0;
background: conic-gradient(
from 0deg,
transparent 70%,
rgba(0, 212, 255, 0.35) 100%
);
animation: lt-radar-sweep 2s linear infinite;
transform-origin: center;
}
.lt-radar::after {
content: '';
position: absolute;
inset: 50% 0 0 50%;
width: 1px; height: 50%;
background: var(--accent-cyan);
transform-origin: top left;
animation: lt-radar-sweep 2s linear infinite;
opacity: 0.6;
}
.lt-radar--sm { width: 28px; height: 28px; }
.lt-radar--lg { width: 72px; height: 72px; }
.lt-radar--green { border-color: var(--accent-green); }
.lt-radar--green::before { background: conic-gradient(from 0deg, transparent 70%, rgba(0,255,136,0.35) 100%); }
.lt-radar--green::after { background: var(--accent-green); }
@keyframes lt-radar-sweep { to { transform: rotate(360deg); } }