Fix bracket buttons rendering below text + UI/security improvements

CSS fixes:
- Fix [ ] brackets appearing below button text by replacing display:inline-flex
  with display:inline-block + white-space:nowrap on .btn — removes cross-browser
  flex pseudo-element inconsistency as root cause
- Remove conflicting .btn::before ripple block (position:absolute was overriding
  bracket content positioning)
- Remove overflow:hidden from .btn which was clipping bracket content
- Fix body::after duplicate rule causing GPU layer blink (second position:fixed
  rule re-created compositor layer, overriding display:none suppression)
- Replace all transition:all with scoped property transitions in dashboard.css,
  ticket.css, base.css (prevents full CSS property evaluation on every hover)
- Convert pulse-warning/pulse-critical keyframes from box-shadow to opacity
  animation (GPU-composited, eliminates CPU repaints at 60fps)
- Fix mobile *::before/*::after blanket content:none rule — now targets only
  decorative frame glyphs, preserving button brackets and status indicators
- Remove --terminal-green-dim override that broke .lt-btn hover backgrounds

JS fixes:
- Fix all lt.lt.toast.* double-prefix instances in dashboard.js
- Add null guard before .appendChild() on bulkAssignUser select
- Replace all remaining emoji with terminal bracket notation (dashboard.js,
  ticket.js, markdown.js)
- Migrate all toast.*() shim calls to lt.toast.* across all JS files

View fixes:
- Remove hardcoded [ ] brackets from .btn buttons (CSS now adds them)
- Replace all emoji with terminal bracket notation in all views and admin views
- Add missing CSP nonces to AuditLogView.php and UserActivityView.php script tags
- Bump CSS version strings to ?v=20260319b for cache busting

Security fixes:
- update_ticket.php: add authorization check (non-admins can only edit their own
  or assigned tickets)
- add_comment.php: validate and cast ticket_id to integer with 400 response
- clone_ticket.php: fix unconditional session_start(), add ticket ID validation,
  add internal ticket access check
- bulk_operation.php: add HTTP 401/403 status codes on auth failures
- upload_attachment.php: fix missing $conn arg in AttachmentModel constructor
- assign_ticket.php: add ticket existence check and permission verification

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-19 22:20:43 -04:00
parent dd8833ee2f
commit 27075a62ee
23 changed files with 376 additions and 328 deletions

View File

@@ -7,7 +7,8 @@
/* Terminal Colors */
--terminal-green: #00ff41;
--terminal-green-dim: #00cc33;
/* Note: --terminal-green-dim is NOT overridden here — base.css defines it as
rgba(0,255,65,0.15) for button hover backgrounds. Use --text-secondary for dim text. */
--terminal-amber: #ffb000;
--terminal-cyan: #00ffff;
--terminal-red: #ff4444;
@@ -56,8 +57,8 @@
--spacing-md: 1.5rem;
--spacing-lg: 2rem;
/* Transitions */
--transition-default: all 0.3s ease;
/* Transitions — scoped to GPU-safe properties only (no box-shadow, no filter) */
--transition-default: border-color 0.2s ease, background-color 0.2s ease, color 0.2s ease;
/* Z-Index Scale - Centralized stacking context management */
--z-base: 1;
@@ -116,7 +117,9 @@ body::before {
display: none;
}
/* Suppress body::after binary text watermark (also position:fixed → GPU layer) */
/* Suppress body::after binary text watermark (also position:fixed → GPU layer).
Do NOT add a second body::after rule below — the last rule wins in the cascade,
overriding this display:none and re-creating the fixed GPU compositing layer. */
body::after {
display: none;
}
@@ -139,30 +142,6 @@ body::after {
100% { transform: translate(0); }
}
/* Subtle data stream effect in corner */
body::after {
content: '10101010';
position: fixed;
bottom: 10px;
right: 10px;
font-family: var(--font-mono);
font-size: 0.6rem;
color: var(--terminal-green);
opacity: 0.1;
pointer-events: none;
letter-spacing: 2px;
animation: data-stream 3s linear infinite;
}
@keyframes data-stream {
0% { content: '10101010'; opacity: 0.1; }
25% { content: '01010101'; opacity: 0.15; }
50% { content: '11001100'; opacity: 0.1; }
75% { content: '00110011'; opacity: 0.15; }
100% { content: '10101010'; opacity: 0.1; }
}
/* ===== ENHANCED TERMINAL ANIMATIONS ===== */
/* Typing cursor effect for focused inputs */
@@ -182,12 +161,8 @@ textarea:focus,
select:focus {
outline: 2px solid var(--terminal-amber);
outline-offset: 2px;
animation: focus-pulse 2s ease-in-out infinite;
}
@keyframes focus-pulse {
0%, 100% { box-shadow: var(--glow-amber), inset 0 0 10px rgba(0, 0, 0, 0.5); }
50% { box-shadow: var(--glow-amber-intense), inset 0 0 10px rgba(0, 0, 0, 0.5); }
/* Static glow on focus — no animation to avoid CPU repaints on every frame */
box-shadow: var(--glow-amber);
}
/* Focus visible for keyboard navigation */
@@ -313,29 +288,6 @@ tbody tr {
.btn {
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
position: relative;
overflow: hidden;
}
.btn::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
transform: translate(-50%, -50%);
transition: width 0.4s, height 0.4s;
}
.btn:active::before {
width: 200%;
height: 200%;
}
.btn:active {
transform: scale(0.98);
}
/* Terminal cursor blink for active/selected elements */
@@ -1584,6 +1536,7 @@ h1 {
}
/* ===== BUTTON STYLES - TERMINAL EDITION ===== */
/* Base: apply terminal font/reset to all buttons */
.btn,
.btn-base,
button {
@@ -1597,25 +1550,25 @@ button {
text-transform: uppercase;
font-weight: bold;
position: relative;
display: inline-block;
white-space: nowrap;
will-change: transform;
transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease, transform 0.15s ease;
}
/* Bracket notation only for explicitly-styled .btn class buttons */
.btn::before,
.btn-base::before,
button::before {
.btn-base::before {
content: '[ ';
}
.btn::after,
.btn-base::after,
button::after {
.btn-base::after {
content: ' ]';
}
.btn:hover,
.btn-base:hover,
button:hover {
.btn-base:hover {
background: rgba(0, 255, 65, 0.15);
color: var(--terminal-amber);
border-color: var(--terminal-amber);
@@ -1623,8 +1576,7 @@ button:hover {
}
.btn:active,
.btn-base:active,
button:active {
.btn-base:active {
transform: translateY(0);
}
@@ -2083,7 +2035,7 @@ select {
border: 2px solid var(--terminal-green);
border-radius: 0;
padding: 8px 12px;
transition: all 0.3s ease;
transition: border-color 0.2s ease, background-color 0.2s ease;
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5);
}
@@ -2288,7 +2240,7 @@ input[type="checkbox"]:checked {
text-align: left;
font-size: 0.9rem;
text-shadow: var(--glow-amber);
transition: all 0.2s ease;
transition: background-color 0.2s ease;
}
.banner-toggle:hover {
@@ -2532,7 +2484,7 @@ input[type="checkbox"]:checked {
cursor: pointer;
font-size: 0.85rem;
font-family: var(--font-mono);
transition: all 0.2s ease;
transition: color 0.2s ease, padding-left 0.2s ease;
}
.filter-group label:hover {
@@ -2548,7 +2500,7 @@ input[type="checkbox"]:checked {
width: 16px;
height: 16px;
border: 2px solid var(--terminal-green);
transition: all 0.2s ease;
transition: opacity 0.15s ease;
}
.filter-group input[type="checkbox"]:checked {
@@ -2568,7 +2520,7 @@ input[type="checkbox"]:checked {
border: 2px solid var(--terminal-green);
font-family: var(--font-mono);
cursor: pointer;
transition: all 0.2s ease;
transition: background-color 0.2s ease, color 0.2s ease;
}
.dashboard-sidebar .btn:hover {
@@ -2802,7 +2754,7 @@ input[type="checkbox"]:checked {
border-radius: 0;
display: inline-block;
border: 1px solid transparent;
transition: background 0.2s ease, border-color 0.2s ease, text-shadow 0.2s ease;
transition: background-color 0.2s ease, border-color 0.2s ease;
text-shadow: var(--glow-green);
}
@@ -3250,9 +3202,25 @@ body.dark-mode select option {
font-size: 0.4rem !important;
}
/* Remove all pseudo-element decorations except essential ones */
*::before,
*::after {
/* Remove purely decorative frame corner glyphs on tiny screens */
.ascii-frame-outer .bottom-left-corner,
.ascii-frame-outer .bottom-right-corner {
display: none;
}
.lt-frame::before, .lt-frame::after,
.lt-frame-inner::before, .lt-frame-inner::after,
.lt-card::before, .lt-card::after,
.lt-stat-card::before, .lt-stat-card::after,
.lt-divider::before, .lt-divider::after,
.lt-section-header::before, .lt-section-header::after,
.lt-subsection-header::before, .lt-subsection-header::after,
.lt-sidebar-header::before,
.lt-modal::before, .lt-modal::after,
.lt-modal-title::before,
.lt-timeline::before,
h1::before, h3::before, h3::after,
.lt-page-title::before,
.lt-brand-title::before {
content: none !important;
}
@@ -3278,11 +3246,6 @@ body.dark-mode select option {
padding: 0.25rem;
}
/* Re-enable essential pseudo-elements */
.search-form::before {
content: '$ ' !important;
}
/* Simplify modals */
.settings-modal,
.modal-content {
@@ -3350,7 +3313,7 @@ body.dark-mode select option {
z-index: var(--z-toast);
opacity: 0;
transform: translateX(400px);
transition: all 0.3s ease;
transition: opacity 0.3s ease, transform 0.3s ease;
max-width: 400px;
word-wrap: break-word;
}
@@ -3403,7 +3366,7 @@ body.dark-mode select option {
padding: 0.25rem 0.5rem;
cursor: pointer;
font-family: var(--font-mono);
transition: all 0.3s ease;
transition: background-color 0.2s ease, color 0.2s ease;
border-radius: 0;
margin-left: 1rem;
}
@@ -3579,7 +3542,7 @@ body.dark-mode select option {
padding: 0.25rem 0.75rem;
cursor: pointer;
font-family: var(--font-mono);
transition: all 0.3s ease;
transition: background-color 0.2s ease, color 0.2s ease;
}
.close-settings:hover {
@@ -3863,7 +3826,7 @@ body.dark-mode select option {
padding: 0.75rem 1.5rem;
border: 2px solid;
cursor: pointer;
transition: all 0.3s ease;
transition: background-color 0.2s ease, color 0.2s ease;
background: transparent;
}
@@ -4035,7 +3998,7 @@ code.inline-code {
color: var(--terminal-cyan);
text-decoration: none;
border-bottom: 1px dotted var(--terminal-cyan);
transition: all 0.3s ease;
transition: color 0.2s ease, border-bottom-color 0.2s ease;
}
[data-markdown] a:hover {
@@ -4126,7 +4089,7 @@ code.inline-code {
padding: 0.25rem 0.75rem;
cursor: pointer;
font-family: var(--font-mono);
transition: all 0.3s ease;
transition: background-color 0.2s ease, color 0.2s ease;
}
.close-advanced-search:hover {
@@ -4211,7 +4174,7 @@ code.inline-code {
font-family: var(--font-mono);
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
transition: background-color 0.2s ease, color 0.2s ease;
font-weight: bold;
}
@@ -4229,7 +4192,7 @@ code.inline-code {
font-family: var(--font-mono);
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
transition: background-color 0.2s ease, color 0.2s ease;
}
.btn-search-secondary:hover {
@@ -4246,7 +4209,7 @@ code.inline-code {
font-family: var(--font-mono);
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
transition: background-color 0.2s ease, color 0.2s ease;
}
.btn-search-reset:hover {
@@ -4377,7 +4340,7 @@ tr:hover .quick-actions {
.stat-label {
font-size: 0.8rem;
color: var(--terminal-green-dim, #008822);
color: var(--text-secondary);
margin-top: 0.25rem;
text-transform: uppercase;
letter-spacing: 0.05em;
@@ -4456,7 +4419,7 @@ tr:hover .quick-actions {
color: var(--terminal-green);
text-decoration: none;
font-family: var(--font-mono);
transition: all 0.2s ease;
transition: background-color 0.15s ease, color 0.15s ease;
}
.export-dropdown-content a:hover {
@@ -4498,7 +4461,7 @@ tr:hover .quick-actions {
text-decoration: none;
font-family: var(--font-mono);
font-size: 0.85rem;
transition: all 0.2s ease;
transition: background-color 0.15s ease, color 0.15s ease;
border-bottom: 1px solid var(--bg-tertiary);
}
@@ -4545,7 +4508,7 @@ table td:nth-child(4) {
padding: 0.1rem 0.3rem;
border-radius: 0;
background: rgba(0, 255, 255, 0.1);
transition: all 0.2s ease;
transition: color 0.15s ease, background-color 0.15s ease;
}
.ticket-link-ref:hover {
@@ -4859,7 +4822,7 @@ table td:nth-child(4) {
font-size: 1.2rem;
min-width: 44px;
min-height: 44px;
transition: all 0.2s ease;
transition: background-color 0.15s ease, color 0.15s ease;
}
.view-btn::before,
@@ -4957,7 +4920,7 @@ table td:nth-child(4) {
border: 1px solid var(--terminal-green);
padding: 0.75rem;
cursor: pointer;
transition: all 0.2s ease;
transition: border-color 0.15s ease, transform 0.15s ease;
font-family: var(--font-mono);
}