green mode slop 1

This commit is contained in:
legop3
2026-08-08 23:59:47 -04:00
parent c8742dbbd6
commit 70f71b2d1e
30 changed files with 547 additions and 98 deletions
+17 -3
View File
@@ -42,6 +42,7 @@ export default function CardFrame({
children,
}) {
const showHeader = !hideHeader && (title || meta != null || actions);
const greenMode = useSessionSelector((state) => Boolean(state.session?.greenMode));
const ownRoverColor = useSessionSelector((state) => {
const roverId = String(state.session?.assignment?.roverId || '').trim();
if (!roverId) return null;
@@ -52,11 +53,20 @@ export default function CardFrame({
const accentRgb = hexToRgb(ownRoverColor);
// swap these to toggle rover card border colors stuff
const cardStyle = accentRgb ? { borderColor: rgba(accentRgb, 0.3) } : undefined;
// Green mode is global server chrome, so it wins over the assigned rover's
// personal accent while active. Keeping this override in CardFrame makes all
// present and future cards participate without sprinkling mode checks around.
const cardStyle = greenMode
? { borderColor: '#00ff00' }
: accentRgb
? { borderColor: rgba(accentRgb, 0.3) }
: undefined;
// const cardStyle = undefined;
const headerStyle = accentRgb
const headerStyle = greenMode
? { borderColor: '#00ff00' }
: accentRgb
? {
backgroundImage: `linear-gradient(90deg, rgba(23,23,23,0.96) 0%, rgba(38,38,38,0.94) 0%, ${rgba(accentRgb, 0.1)} 100%)`,
// backgroundImage: `linear-gradient(90deg, ${rgba(accentRgb, 0.1)} 100%)`,
@@ -85,7 +95,11 @@ export default function CardFrame({
style={headerStyle}
>
<div className="flex min-w-0 items-center gap-0.5">
{title ? <p className="m-0 text-[0.78rem] font-semibold leading-none text-neutral-50">{title}</p> : null}
{title ? (
<p className={cx('m-0 text-[0.78rem] font-semibold leading-none', greenMode ? 'text-lime-400' : 'text-neutral-50')}>
{title}
</p>
) : null}
{meta != null ? <span className="text-[0.68rem] font-medium leading-none text-neutral-200">{meta}</span> : null}
</div>
{actions ? <div className="flex flex-wrap items-center justify-end gap-0.5">{actions}</div> : null}
+2 -2
View File
@@ -23,7 +23,7 @@ import { usePtzCameraSnapshots } from '../../hooks/usePtzCameraSnapshot.js';
import { useSharedClock } from '../../hooks/useSharedClock.js';
import { isFeatureEnabled } from '../../lib/features.js';
import { useSettingsNamespace } from '../../settings/index.js';
import { DEFAULT_PAGE_THEME_KEY, getPageThemeClass } from '../../themes/index.js';
import { DEFAULT_PAGE_THEME_KEY, usePageThemeClass } from '../../themes/index.js';
import { triggerTouchHaptic } from '../../lib/touchHaptics.js';
const PTZ_DEFAULT_COLOR = '#38bdf8';
@@ -713,7 +713,7 @@ export function PtzControllerPage({ layout = 'desktop' }) {
// PTZ is a separate route but shares the browser's page settings. Applying the catalog class to
// its body surface exposes the theme only through layout padding and card gaps; camera pixels,
// controls, and card interiors retain their purpose-built dark backgrounds.
const pageBackgroundClass = getPageThemeClass(pageSettings?.backgroundTheme);
const pageBackgroundClass = usePageThemeClass(pageSettings?.backgroundTheme);
useEffect(() => {
// Route-exit cleanup runs after the last render, so retain the latest
+2 -2
View File
@@ -7,7 +7,7 @@ import SocketConnectionPill from '../components/SocketConnectionPill/index.jsx';
import { useSessionSelector } from '../context/SessionContext.jsx';
import useUserIdentitySync from '../hooks/useUserIdentitySync.js';
import { useSettingsNamespace } from '../settings/index.js';
import { DEFAULT_PAGE_THEME_KEY, getPageThemeClass } from '../themes/index.js';
import { DEFAULT_PAGE_THEME_KEY, usePageThemeClass } from '../themes/index.js';
import IdentityDatabasePanel from './IdentityDatabasePanel.jsx';
function isLockdownAdminRole(role) {
@@ -23,7 +23,7 @@ export default function DatabaseAdminApp() {
});
// Database cards use the same narrow seams as the driver page, so honoring the shared browser
// preference here keeps the existing route-level background behavior while making it dynamic.
const pageBackgroundClass = getPageThemeClass(pageSettings?.backgroundTheme);
const pageBackgroundClass = usePageThemeClass(pageSettings?.backgroundTheme);
const isLockdownAdmin = isLockdownAdminRole(role);
const isLoggedInAdmin = role === 'admin' || isLockdownAdmin;
+2 -2
View File
@@ -20,8 +20,8 @@ import SocketConnectionPill from '../components/SocketConnectionPill/index.jsx';
import DuplicateIdentityOverlay from '../components/DuplicateIdentityOverlay/index.jsx';
import {
DEFAULT_PAGE_THEME_KEY,
getPageThemeClass,
themeGapClass,
usePageThemeClass,
} from '../themes/index.js';
import useLayoutMode from '../hooks/useLayoutMode.js';
import { DriverLayoutProvider } from '../layouts/driver/DriverLayoutContext.jsx';
@@ -35,7 +35,7 @@ function DriverPageRoot() {
});
// Resolve the cookie value through the shared catalog before painting the page. This prevents
// an obsolete or hand-edited key from stripping the background class from every exposed seam.
const pageBackgroundClass = getPageThemeClass(pageSettings?.backgroundTheme);
const pageBackgroundClass = usePageThemeClass(pageSettings?.backgroundTheme);
return (
<div className={`${pageBackgroundClass} text-slate-100`}>
+3 -2
View File
@@ -12,7 +12,7 @@ import useUserIdentitySync from '../hooks/useUserIdentitySync.js';
import useFleetReport from '../hooks/useFleetReport.js';
import { isFeatureEnabled } from '../lib/features.js';
import { useSettingsNamespace } from '../settings/index.js';
import { DEFAULT_PAGE_THEME_KEY, getPageThemeClass, themeGapClass } from '../themes/index.js';
import { DEFAULT_PAGE_THEME_KEY, themeGapClass, usePageThemeClass } from '../themes/index.js';
const RANGE_OPTIONS = [
{ label: '24 hours', ms: 24 * 60 * 60 * 1000 },
@@ -261,8 +261,9 @@ export default function FleetReportsApp() {
useUserIdentitySync({ identitySurface: 'passive' });
const enabled = useSessionSelector((state) => isFeatureEnabled(state, 'fleetReports'));
const { value: pageSettings } = useSettingsNamespace('page', { backgroundTheme: DEFAULT_PAGE_THEME_KEY });
const pageBackgroundClass = usePageThemeClass(pageSettings?.backgroundTheme);
return (
<div className={`${getPageThemeClass(pageSettings?.backgroundTheme)} min-h-screen text-slate-100`}>
<div className={`${pageBackgroundClass} min-h-screen text-slate-100`}>
<SocketConnectionPill />
<main className={`mx-auto flex min-h-screen w-full max-w-[120rem] flex-col ${themeGapClass} p-1`}>
{enabled ? <FullReportContent /> : (
+1
View File
@@ -10,3 +10,4 @@ export {
normalizePageThemeKey,
} from './catalog.js';
export { themeGapClass, themeStackClass } from './layout.js';
export { default as usePageThemeClass } from './usePageThemeClass.js';
+7
View File
@@ -24,3 +24,10 @@
background-color: #000000;
background-image: none;
}
.page-theme-green-mode {
/* The deliberately blunt solid lime canvas is a temporary server mode, not
a selectable personal theme, so it belongs outside the persisted catalog. */
background-color: #00ff00;
background-image: none;
}
+17
View File
@@ -0,0 +1,17 @@
// Active Page Theme Hook
// Purpose: Resolves personal theme settings together with temporary server-owned theme overrides.
// Scope: Keeps page shells unaware of individual modes while preserving the pure catalog helpers.
import { useSessionSelector } from '../context/SessionContext.jsx';
import { getPageThemeClass } from './catalog.js';
export default function usePageThemeClass(backgroundTheme) {
const greenMode = useSessionSelector((state) => Boolean(state.session?.greenMode));
/*
Green mode is a temporary server-wide theme, so it wins at resolution time
without modifying the user's persisted theme selection. Every page consumes
this hook and therefore receives future server theme overrides consistently.
*/
if (greenMode) return 'page-theme page-theme-green-mode';
return getPageThemeClass(backgroundTheme);
}