Merge branch 'worktree-ui-tiers'

This commit is contained in:
James Smith
2026-05-19 22:36:52 +01:00
12 changed files with 338 additions and 143 deletions
+32
View File
@@ -434,3 +434,35 @@ a:hover {
--text-secondary: #d1d5db;
}
}
/* ============================================
LEAN TIER — strip body background and animations
============================================ */
[data-ui-tier="lean"] body {
background-image: none;
background-attachment: unset;
background-size: unset;
}
[data-ui-tier="lean"] *,
[data-ui-tier="lean"] *::before,
[data-ui-tier="lean"] *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0ms !important;
scroll-behavior: auto !important;
}
/* ============================================
ENHANCED TIER — tighter grid background
============================================ */
[data-ui-tier="enhanced"] body {
background-image:
radial-gradient(1200px 620px at 8% -12%, var(--ambient-top-left), transparent 62%),
radial-gradient(980px 560px at 92% -16%, var(--ambient-top-right), transparent 64%),
radial-gradient(900px 520px at 50% 126%, var(--ambient-bottom), transparent 68%),
linear-gradient(var(--grid-line) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
background-size: auto, auto, auto, 20px 20px, 20px 20px;
background-attachment: fixed;
}
+40 -4
View File
@@ -248,10 +248,6 @@
}
}
[data-animations="off"] .panel-indicator.active {
animation: none;
}
.panel-content {
padding: var(--space-3);
}
@@ -1181,3 +1177,43 @@ textarea:focus {
z-index: 1;
border-radius: inherit;
}
/* ============================================
LEAN TIER — flat cards, no shadows/glows
============================================ */
[data-ui-tier="lean"] .card,
[data-ui-tier="lean"] .panel,
[data-ui-tier="lean"] .stat-card,
[data-ui-tier="lean"] .data-card {
box-shadow: none;
border-radius: 2px;
}
[data-ui-tier="lean"] .panel-indicator.active {
animation: none;
box-shadow: none;
}
/* ============================================
ENHANCED TIER — amber left-border card accents
============================================ */
[data-ui-tier="enhanced"] .stat-card,
[data-ui-tier="enhanced"] .data-card {
border-left: 2px solid var(--accent-cyan);
background: rgba(200, 150, 40, 0.03);
}
[data-ui-tier="enhanced"] .stat-value,
[data-ui-tier="enhanced"] .data-value {
font-family: var(--font-mono);
color: var(--accent-cyan);
}
[data-ui-tier="enhanced"] .stat-label,
[data-ui-tier="enhanced"] .data-label {
font-family: var(--font-mono);
letter-spacing: 2px;
text-transform: uppercase;
font-size: var(--text-xs);
color: rgba(200, 150, 40, 0.5);
}
+88 -13
View File
@@ -1041,19 +1041,6 @@
transform: rotate(90deg);
}
/* Effects/animations toggle icon states */
.nav-tool-btn .icon-effects-off {
display: none;
}
[data-animations="off"] .nav-tool-btn .icon-effects-on {
display: none;
}
[data-animations="off"] .nav-tool-btn .icon-effects-off {
display: flex;
}
/* Dashboard Button in Nav */
a.nav-dashboard-btn,
a.nav-dashboard-btn:link,
@@ -1172,3 +1159,91 @@ a.nav-dashboard-btn:hover {
background: rgba(220, 230, 244, 0.9);
box-shadow: var(--shadow-sm);
}
/* ============================================
LEAN TIER — flat nav and header
============================================ */
[data-ui-tier="lean"] .mode-nav {
background: #181818;
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
[data-ui-tier="lean"] .mode-nav::after,
[data-ui-tier="lean"] .app-header::after,
[data-ui-tier="lean"] .mobile-nav::after,
[data-ui-tier="lean"] .dashboard-header::after {
display: none;
}
[data-ui-tier="lean"] .app-header {
background: #181818;
box-shadow: none;
}
[data-ui-tier="lean"] .mode-nav-dropdown-menu {
backdrop-filter: none;
-webkit-backdrop-filter: none;
background: #202020;
}
/* ============================================
ENHANCED TIER — amber console nav framing
============================================ */
[data-ui-tier="enhanced"] .mode-nav {
background: linear-gradient(180deg, rgba(14, 11, 3, 0.95), rgba(8, 6, 0, 0.92));
}
[data-ui-tier="enhanced"] .mode-nav::after,
[data-ui-tier="enhanced"] .app-header::after,
[data-ui-tier="enhanced"] .dashboard-header::after {
background: linear-gradient(90deg, transparent, var(--accent-cyan), transparent);
}
[data-ui-tier="enhanced"] .mode-nav-btn.active {
background: rgba(200, 150, 40, 0.08);
color: var(--accent-cyan);
border-left: 2px solid var(--accent-cyan);
box-shadow: -2px 0 8px rgba(200, 150, 40, 0.15);
padding-left: 12px;
}
[data-ui-tier="enhanced"] .nav-clock .utc-time {
color: var(--accent-cyan);
text-shadow: 0 0 8px rgba(200, 150, 40, 0.3);
font-weight: 700;
}
[data-ui-tier="enhanced"] .mode-nav-btn .nav-label,
[data-ui-tier="enhanced"] .mode-nav-label {
font-family: var(--font-mono);
letter-spacing: 2px;
}
/* Tier toggle button — label visibility */
.nav-tier-btn .tier-label-lean { display: none; }
.nav-tier-btn .tier-label-enhanced { display: none; }
[data-ui-tier="lean"] .nav-tier-btn .tier-label-lean { display: inline; }
[data-ui-tier="enhanced"] .nav-tier-btn .tier-label-enhanced { display: inline; }
/* Tier toggle — icon visibility */
.nav-tier-btn .icon-tier-lean { display: flex; }
.nav-tier-btn .icon-tier-enhanced { display: none; }
[data-ui-tier="lean"] .nav-tier-btn .icon-tier-lean { display: flex; }
[data-ui-tier="lean"] .nav-tier-btn .icon-tier-enhanced { display: none; }
[data-ui-tier="enhanced"] .nav-tier-btn .icon-tier-lean { display: none; }
[data-ui-tier="enhanced"] .nav-tier-btn .icon-tier-enhanced { display: flex; }
/* Enhanced tier toggle button styling */
[data-ui-tier="enhanced"] .nav-tier-btn {
background: rgba(200, 150, 40, 0.10);
border-color: rgba(200, 150, 40, 0.4);
color: #c89628;
box-shadow: 0 0 8px rgba(200, 150, 40, 0.08);
text-shadow: 0 0 6px rgba(200, 150, 40, 0.3);
}
[data-ui-tier="enhanced"] .nav-tier-btn:hover {
background: rgba(200, 150, 40, 0.16);
border-color: rgba(200, 150, 40, 0.6);
}
+73
View File
@@ -265,3 +265,76 @@
--transition-slow: 0ms;
}
}
/* ============================================
LEAN TIER — flat dark, no GPU effects
============================================ */
[data-ui-tier="lean"] {
--bg-primary: #111111;
--bg-secondary: #181818;
--bg-tertiary: #1f1f1f;
--bg-card: #1a1a1a;
--bg-elevated: #202020;
--bg-overlay: rgba(17, 17, 17, 0.92);
--surface-glass: #181818;
--surface-panel-gradient: #181818;
--ambient-top-left: transparent;
--ambient-top-right: transparent;
--ambient-bottom: transparent;
--border-color: #2a2a2a;
--border-light: #333333;
--border-glow: transparent;
--shadow-sm: 0 1px 1px rgba(0, 0, 0, 0.3);
--shadow-md: 0 2px 4px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.3);
--shadow-glow: none;
--transition-fast: 0ms;
--transition-base: 0ms;
--transition-slow: 0ms;
--scanline: none;
--grid-line: transparent;
--noise-image: none;
}
/* ============================================
ENHANCED TIER — amber military console
============================================ */
[data-ui-tier="enhanced"] {
--bg-primary: #080600;
--bg-secondary: #0c0a04;
--bg-tertiary: #100d06;
--bg-card: #0e0b05;
--bg-elevated: #141008;
--bg-overlay: rgba(8, 6, 0, 0.82);
--surface-glass: rgba(14, 11, 5, 0.82);
--surface-panel-gradient: linear-gradient(160deg, rgba(20, 16, 8, 0.94) 0%, rgba(12, 10, 4, 0.96) 100%);
--accent-cyan: #c89628;
--accent-cyan-dim: rgba(200, 150, 40, 0.14);
--accent-cyan-hover: #e0aa30;
--accent-cyan-glow: rgba(200, 150, 40, 0.10);
--accent-green: #c89628;
--accent-green-hover: #e0aa30;
--accent-green-dim: rgba(200, 150, 40, 0.14);
/* red is intentionally unchanged — critical alerts only */
--ambient-top-left: rgba(200, 150, 40, 0.08);
--ambient-top-right: rgba(200, 150, 40, 0.05);
--ambient-bottom: rgba(200, 150, 40, 0.04);
--grid-line: rgba(200, 150, 40, 0.07);
--border-color: rgba(200, 150, 40, 0.2);
--border-light: rgba(200, 150, 40, 0.3);
--border-glow: rgba(200, 150, 40, 0.25);
--border-focus: #c89628;
--status-online: #c89628;
--status-info: #c89628;
}
-55
View File
@@ -7299,57 +7299,6 @@ body::before {
transform: scale(0.98);
}
/* Animation toggle icon states in nav bar */
.nav-tool-btn .icon-effects-on,
.nav-tool-btn .icon-effects-off {
position: absolute;
transition: opacity 0.2s, transform 0.2s;
font-size: 14px;
}
.nav-tool-btn .icon-effects-on {
opacity: 1;
transform: rotate(0deg);
}
.nav-tool-btn .icon-effects-off {
opacity: 0;
transform: rotate(-90deg);
}
[data-animations="off"] .nav-tool-btn .icon-effects-on {
opacity: 0;
transform: rotate(90deg);
}
[data-animations="off"] .nav-tool-btn .icon-effects-off {
opacity: 1;
transform: rotate(0deg);
}
/* Disable cosmetic animations when toggled off */
[data-animations="off"] .globe-svg,
[data-animations="off"] .rotating-meridians,
[data-animations="off"] .meridian-1,
[data-animations="off"] .meridian-2,
[data-animations="off"] .meridian-3,
[data-animations="off"] .welcome-scanline,
[data-animations="off"] .landing-scanline,
[data-animations="off"] .scanline,
[data-animations="off"] .signal-wave,
[data-animations="off"] .signal-wave-1,
[data-animations="off"] .signal-wave-2,
[data-animations="off"] .signal-wave-3,
[data-animations="off"] .logo-dot,
[data-animations="off"] .welcome-logo {
animation: none !important;
}
[data-animations="off"] body::before,
[data-animations="off"] .visuals-container::after {
display: none;
}
/* ============================================
VISUAL REFRESH OVERRIDES
============================================ */
@@ -7687,10 +7636,6 @@ body[data-mode="tscm"] {
box-shadow: 0 10px 24px rgba(18, 40, 66, 0.08);
}
[data-animations="off"] .mode-content.active {
animation: none !important;
}
@media (max-width: 1023px) {
.run-state-strip {
margin-left: 8px;
+63 -1
View File
@@ -10,6 +10,7 @@ const FirstRunSetup = (function() {
let notifyStatusEl = null;
let modeStatusEl = null;
let modeSelectEl = null;
let uiTierStatusEl = null;
let dependencyReady = null;
@@ -168,10 +169,69 @@ const FirstRunSetup = (function() {
}
);
const tierStep = createStep(
'Display Mode',
'Choose how the interface looks. You can change this later with the toggle in the nav bar.',
(statusEl, actionsEl) => {
uiTierStatusEl = statusEl;
const btnWrap = document.createElement('div');
btnWrap.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:4px;';
function makeTierBtn(tier, title, desc, previewFn) {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'setup-btn';
btn.style.cssText = 'display:flex;flex-direction:column;align-items:flex-start;height:auto;padding:10px;text-align:left;gap:4px;';
const preview = document.createElement('div');
preview.style.cssText = 'width:100%;height:48px;border-radius:3px;margin-bottom:4px;overflow:hidden;';
previewFn(preview);
const titleEl = document.createElement('strong');
titleEl.style.cssText = 'font-size:12px;';
titleEl.textContent = title;
const descEl = document.createElement('span');
descEl.style.cssText = 'font-size:10px;color:var(--text-dim);white-space:normal;line-height:1.3;';
descEl.textContent = desc;
btn.appendChild(preview);
btn.appendChild(titleEl);
btn.appendChild(descEl);
btn.addEventListener('click', () => {
document.documentElement.setAttribute('data-ui-tier', tier);
localStorage.setItem('intercept-ui-tier', tier);
refreshStatuses();
if (typeof showAppToast === 'function') {
showAppToast('Display Mode Set', `Switched to ${title} mode.`, 'info');
}
});
return btn;
}
const leanBtn = makeTierBtn('lean', 'Lean', 'No effects — for Raspberry Pi or older hardware.', (el) => {
el.style.cssText += 'background:#111;border:1px solid #222;display:flex;flex-direction:column;justify-content:center;padding:6px;gap:3px;';
el.innerHTML = '<div style="display:flex;justify-content:space-between;font-size:7px;color:#aaa;font-family:monospace;"><span>INTERCEPT</span><span style="color:#4a4;">● LIVE</span></div><div style="font-size:7px;color:#888;font-family:monospace;margin-top:2px;">ADS-B ........... 247<br>TSCM ............ 3 ⚠</div>';
});
const enhancedBtn = makeTierBtn('enhanced', 'Enhanced', 'Amber military console — for desktop or laptop.', (el) => {
el.style.cssText += 'background:#080600;border:1px solid rgba(200,150,40,0.3);display:flex;flex-direction:column;justify-content:center;padding:6px;gap:3px;';
el.innerHTML = '<div style="display:flex;justify-content:space-between;font-size:7px;color:#c89628;font-family:monospace;letter-spacing:2px;"><span>INTERCEPT</span><span style="opacity:0.6;">14:27Z</span></div><div style="border-left:2px solid #c89628;padding-left:4px;margin-top:4px;font-size:8px;color:#c89628;font-family:monospace;font-weight:700;">247 ADS-B</div>';
});
btnWrap.appendChild(leanBtn);
btnWrap.appendChild(enhancedBtn);
actionsEl.appendChild(btnWrap);
}
);
content.appendChild(depsStep);
content.appendChild(locationStep);
content.appendChild(notifyStep);
content.appendChild(modeStep);
content.appendChild(tierStep);
const footer = document.createElement('div');
footer.className = 'setup-footer';
@@ -276,6 +336,8 @@ const FirstRunSetup = (function() {
setStatus(locationStatusEl, hasLocation, hasLocation ? 'Configured' : 'Not set');
setStatus(notifyStatusEl, notifications.ready, notifications.label);
setStatus(modeStatusEl, hasDefaultMode, hasDefaultMode ? localStorage.getItem(DEFAULT_MODE_KEY) : 'Not set');
const hasTier = Boolean(localStorage.getItem('intercept-ui-tier'));
setStatus(uiTierStatusEl, hasTier, hasTier ? localStorage.getItem('intercept-ui-tier') : 'Not set');
if (dependencyReady === null) {
checkDependencies();
@@ -283,7 +345,7 @@ const FirstRunSetup = (function() {
}
setStatus(depsStatusEl, dependencyReady, dependencyReady ? 'Ready' : 'Missing tools');
const doneCount = Number(dependencyReady) + Number(hasLocation) + Number(notifications.ready) + Number(hasDefaultMode);
const doneCount = Number(dependencyReady) + Number(hasLocation) + Number(notifications.ready) + Number(hasDefaultMode) + Number(hasTier);
const completeBtn = document.getElementById('setupCompleteBtn');
if (completeBtn) {
completeBtn.textContent = doneCount >= 3 ? 'Mark Setup Complete' : 'Complete Anyway';
+7 -12
View File
@@ -705,10 +705,9 @@ const Settings = {
themeSelect.value = localStorage.getItem('intercept-theme') || 'dark';
}
// Animations toggle
const animationsEnabled = document.getElementById('animationsEnabled');
if (animationsEnabled) {
animationsEnabled.checked = localStorage.getItem('intercept-animations') !== 'off';
const uiTierSelect = document.getElementById('uiTierSelect');
if (uiTierSelect) {
uiTierSelect.value = localStorage.getItem('intercept-ui-tier') || 'enhanced';
}
},
@@ -1486,15 +1485,11 @@ function setThemePreference(value) {
}
/**
* Set animations preference from the Display settings tab
* Set UI tier preference from the Display settings tab
*/
function setAnimationsEnabled(enabled) {
if (enabled) {
document.documentElement.removeAttribute('data-animations');
} else {
document.documentElement.setAttribute('data-animations', 'off');
}
localStorage.setItem('intercept-animations', enabled ? 'on' : 'off');
function setUiTierPreference(tier) {
document.documentElement.setAttribute('data-ui-tier', tier);
localStorage.setItem('intercept-ui-tier', tier);
}
if (!window._settingsEscapeHandlerBound) {
+1 -23
View File
@@ -11990,34 +11990,12 @@
}).catch(err => console.warn('Failed to save theme to server:', err));
}
// Animation toggle functions
function toggleAnimations() {
const html = document.documentElement;
const currentState = html.getAttribute('data-animations');
const newState = currentState === 'off' ? 'on' : 'off';
if (newState === 'on') {
html.removeAttribute('data-animations');
} else {
html.setAttribute('data-animations', newState);
}
// Save to localStorage for persistence
localStorage.setItem('intercept-animations', newState);
}
// Load saved theme and animations on page load
// Load saved theme on page load
(function () {
// First apply localStorage theme for instant load (no flash)
const localTheme = localStorage.getItem('intercept-theme') || 'dark';
document.documentElement.setAttribute('data-theme', localTheme);
// Apply animations preference
const localAnimations = localStorage.getItem('intercept-animations');
if (localAnimations === 'off') {
document.documentElement.setAttribute('data-animations', 'off');
}
// Then fetch from server to sync (in case changed on another device)
fetch('/settings/theme')
.then(r => r.json())
-7
View File
@@ -74,13 +74,6 @@
100% { top: 100%; }
}
/* Animations toggle */
[data-animations="off"] .scanline,
[data-animations="off"] .radar-bg,
[data-animations="off"] .grid-bg {
display: none;
}
/* Dashboard main content */
.dashboard-content {
flex: 1;
+3 -5
View File
@@ -2,12 +2,10 @@
<html lang="en">
<head>
<script>
// Apply animations preference immediately to prevent flash
// Apply UI tier immediately to prevent flash
(function() {
var animations = localStorage.getItem('intercept-animations');
if (animations === 'off') {
document.documentElement.setAttribute('data-animations', 'off');
}
var tier = localStorage.getItem('intercept-ui-tier') || 'enhanced';
document.documentElement.setAttribute('data-ui-tier', tier);
})();
</script>
<meta charset="UTF-8" />
+25 -17
View File
@@ -190,9 +190,11 @@
</a>
<div class="nav-divider"></div>
<div class="nav-tools">
<button type="button" class="nav-tool-btn" onclick="toggleAnimations()" title="Toggle Animations" aria-label="Toggle animations">
<span class="icon-effects-on icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg></span>
<span class="icon-effects-off icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/><line x1="2" y1="2" x2="22" y2="22"/></svg></span>
<button type="button" class="nav-tool-btn nav-tier-btn" onclick="toggleUiTier()" title="Switch display mode" aria-label="Switch display mode" style="width:auto;padding:0 8px;gap:4px;font-family:var(--font-mono);font-size:9px;letter-spacing:1px;">
<span class="icon-tier-lean icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="21" x2="9" y2="9"/></svg></span>
<span class="icon-tier-enhanced icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg></span>
<span class="tier-label-lean">LEAN</span>
<span class="tier-label-enhanced">ENHANCED</span>
</button>
<button type="button" class="nav-tool-btn" onclick="toggleTheme()" title="Toggle Light/Dark Theme" aria-label="Toggle theme">
<span class="icon-moon icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg></span>
@@ -456,14 +458,13 @@
});
}
if (typeof toggleAnimations === 'undefined') {
window.toggleAnimations = function() {
if (typeof toggleUiTier === 'undefined') {
window.toggleUiTier = function() {
const html = document.documentElement;
const current = html.getAttribute('data-animations') || 'on';
const next = current === 'on' ? 'off' : 'on';
html.setAttribute('data-animations', next);
localStorage.setItem('intercept-animations', next);
localStorage.removeItem('animations');
const current = html.getAttribute('data-ui-tier') || 'enhanced';
const next = current === 'lean' ? 'enhanced' : 'lean';
html.setAttribute('data-ui-tier', next);
localStorage.setItem('intercept-ui-tier', next);
};
}
@@ -528,14 +529,21 @@
document.documentElement.setAttribute('data-theme', savedTheme);
}
const legacyAnimations = localStorage.getItem('animations');
const savedAnimations = localStorage.getItem('intercept-animations') || legacyAnimations;
if (legacyAnimations && !localStorage.getItem('intercept-animations')) {
localStorage.setItem('intercept-animations', legacyAnimations);
localStorage.removeItem('animations');
// Migrate old animations pref: animations=off → tier=lean
const oldAnimations = localStorage.getItem('intercept-animations') || localStorage.getItem('animations');
if (oldAnimations && !localStorage.getItem('intercept-ui-tier')) {
if (oldAnimations === 'off') {
localStorage.setItem('intercept-ui-tier', 'lean');
}
}
if (savedAnimations) {
document.documentElement.setAttribute('data-animations', savedAnimations);
localStorage.removeItem('intercept-animations');
localStorage.removeItem('animations');
// Apply tier (default to enhanced if none saved)
const savedTier = localStorage.getItem('intercept-ui-tier') || 'enhanced';
document.documentElement.setAttribute('data-ui-tier', savedTier);
if (!localStorage.getItem('intercept-ui-tier')) {
localStorage.setItem('intercept-ui-tier', 'enhanced');
}
// UTC Clock update (if not already defined by parent page)
+6 -6
View File
@@ -218,13 +218,13 @@
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Animations</span>
<span class="settings-label-desc">Enable visual effects and animations</span>
<span class="settings-label-text">Display Mode</span>
<span class="settings-label-desc">Lean — no effects (Pi / older hardware) · Enhanced — amber military console</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="animationsEnabled" checked onchange="setAnimationsEnabled(this.checked)">
<span class="toggle-slider"></span>
</label>
<select id="uiTierSelect" class="settings-select" onchange="setUiTierPreference(this.value)">
<option value="enhanced">Enhanced</option>
<option value="lean">Lean</option>
</select>
</div>
</div>