From 122d098be49ddc80bd4f2e5c82138a1a78cc8d6f Mon Sep 17 00:00:00 2001 From: Matt Batchelder Date: Thu, 5 Feb 2026 09:04:06 -0500 Subject: [PATCH] Refactor filter panels and enhance sidebar functionality - Updated filter panel toggle icons from chevron-up to chevron-down across multiple pages for consistency. - Added 'collapsed' class to filter content divs to manage visibility state. - Enhanced library page button for tidying up media items, replacing the trash icon with a custom SVG broom icon. - Improved CSS styles for sidebar and page header to ensure visibility and proper layout when the sidebar is collapsed. - Introduced JavaScript functionality to manage sidebar width and state, including theme toggle for light/dark mode. - Created a new notification drawer template that adapts based on the compact view state. --- custom/otssignange/css/override-dark.css | 27 +- custom/otssignange/css/override.css | 1444 ++++++++++++++++- custom/otssignange/js/theme.js | 122 +- .../views/authed-notification-drawer.twig | 26 + custom/otssignange/views/authed-sidebar.twig | 5 +- .../views/authed-theme-topbar.twig | 4 +- .../otssignange/views/authed-user-menu.twig | 17 + custom/otssignange/views/authed.twig | 65 +- custom/otssignange/views/campaign-page.twig | 4 +- custom/otssignange/views/command-page.twig | 4 +- .../otssignange/views/datatable-contrast.twig | 98 +- custom/otssignange/views/daypart-page.twig | 4 +- custom/otssignange/views/display-page.twig | 44 +- .../views/displayprofile-page.twig | 4 +- custom/otssignange/views/layout-page.twig | 4 +- custom/otssignange/views/library-page.twig | 17 +- custom/otssignange/views/menuboard-page.twig | 4 +- custom/otssignange/views/override-styles.twig | 399 ++++- custom/otssignange/views/resolution-page.twig | 4 +- custom/otssignange/views/schedule-page.twig | 4 +- custom/otssignange/views/syncgroup-page.twig | 4 +- custom/otssignange/views/template-page.twig | 4 +- custom/otssignange/views/theme-scripts.twig | 329 +++- 23 files changed, 2447 insertions(+), 190 deletions(-) create mode 100644 custom/otssignange/views/authed-notification-drawer.twig diff --git a/custom/otssignange/css/override-dark.css b/custom/otssignange/css/override-dark.css index 29b5414..b1fd1db 100644 --- a/custom/otssignange/css/override-dark.css +++ b/custom/otssignange/css/override-dark.css @@ -270,6 +270,26 @@ hr { padding: 0; } +/* Dark-mode repair overrides: ensure sidebar doesn't draw a vertical divider */ +#sidebar-wrapper, +.ots-sidebar { + border-right: none !important; + box-shadow: none !important; +} + +/* Hide pseudo-elements that might draw a divider */ +.ots-sidebar::before, +.ots-sidebar::after, +#sidebar-wrapper::before, +#sidebar-wrapper::after, +#page-wrapper::before, +#page-wrapper::after { + content: none !important; + display: none !important; + background: transparent !important; + box-shadow: none !important; +} + /* Sidebar Main Item */ .ots-sidebar li.sidebar-main > a { display: flex; @@ -740,7 +760,9 @@ textarea:focus { .modal-content { border-radius: var(--ots-radius-lg); - background-color: var(--ots-surface-2) !important; + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; } .modal, @@ -748,6 +770,7 @@ textarea:focus { .modal-body, .modal-footer { background-color: transparent !important; + color: var(--color-text-primary) !important; } .modal-backdrop, @@ -759,7 +782,7 @@ textarea:focus { } .modal-footer { - border-top: 1px solid var(--ots-border); + border-top: 1px solid var(--color-border) !important; } /* ============================================================================= diff --git a/custom/otssignange/css/override.css b/custom/otssignange/css/override.css index 8ae1b59..4ad369a 100644 --- a/custom/otssignange/css/override.css +++ b/custom/otssignange/css/override.css @@ -25,6 +25,33 @@ --color-text-tertiary: #e2e8f0; --color-text-inverse: #ffffff; --color-on-primary: #ffffff; + + --ots-sidebar-bg: #08132a; + --ots-sidebar-header-bg: #08132a; + --ots-sidebar-border: rgba(255, 255, 255, 0.08); + --ots-sidebar-link: #f9fbff; + --ots-sidebar-link-hover-bg: rgba(255, 255, 255, 0.08); + --ots-sidebar-link-hover-text: #ffffff; + --ots-sidebar-active-bg: rgba(255, 255, 255, 0.06); + --ots-sidebar-active-text: #ffffff; + --ots-sidebar-active-shadow: 0 8px 18px rgba(15, 23, 42, 0.25); + --ots-sidebar-muted-text: #8ea4c7; + --ots-sidebar-group-bg: rgba(255, 255, 255, 0.03); + --ots-sidebar-group-hover-bg: rgba(255, 255, 255, 0.1); + --ots-sidebar-submenu-bg: rgba(15, 23, 42, 0.6); + --ots-sidebar-submenu-border: rgba(255, 255, 255, 0.9); + --ots-sidebar-submenu-hover-bg: rgba(255, 255, 255, 0.1); + --ots-sidebar-button-bg: rgba(255, 255, 255, 0.08); + --ots-sidebar-button-bg-hover: rgba(255, 255, 255, 0.16); + --ots-sidebar-button-text: #d7e2f8; + --ots-sidebar-collapsed-item-bg: rgba(255, 255, 255, 0.08); + --ots-sidebar-collapsed-item-hover-bg: rgba(255, 255, 255, 0.16); + --ots-sidebar-width: 256px; + --ots-sidebar-collapsed-width: 64px; + --ots-sidebar-header-height: 64px; + --ots-sidebar-item-radius: 10px; + --ots-sidebar-item-height: 44px; + --ots-sidebar-item-padding-x: 12px; color-scheme: dark; } @@ -35,6 +62,219 @@ body { color: var(--color-text-primary); } +/* Light/dark mode toggle */ +#ots-theme-toggle { + display: flex !important; + align-items: center; + gap: 8px; + cursor: pointer; +} + +#ots-theme-toggle:hover { + background-color: rgba(59, 130, 246, 0.1) !important; +} + +/* Light mode styles (applied when .ots-light-mode is on body) */ +body.ots-light-mode { + --color-primary: #2563eb; + --color-primary-dark: #1d4ed8; + --color-primary-light: #60a5fa; + --color-primary-lighter: #eff6ff; + --color-secondary: #7c3aed; + --color-success: #059669; + --color-warning: #d97706; + --color-danger: #dc2626; + --color-info: #0284c7; + + --color-background: #f8fafc; + --color-surface: #ffffff; + --color-surface-elevated: #f1f5f9; + --color-border: #e2e8f0; + --color-border-light: #f1f5f9; + --color-text-primary: #0f172a; + --color-text-secondary: #334155; + --color-text-tertiary: #475569; + --color-text-inverse: #0f172a; + --color-on-primary: #ffffff; + + --ots-sidebar-bg: #f1f5f9; + --ots-sidebar-header-bg: #f1f5f9; + --ots-sidebar-border: #e2e8f0; + --ots-sidebar-link: #334155; + --ots-sidebar-link-hover-bg: rgba(59, 130, 246, 0.08); + --ots-sidebar-link-hover-text: #0f172a; + --ots-sidebar-active-bg: #ffffff; + --ots-sidebar-active-text: #0f172a; + --ots-sidebar-active-shadow: 0 8px 18px rgba(15, 23, 42, 0.12); + --ots-sidebar-muted-text: #64748b; + --ots-sidebar-group-bg: rgba(0, 0, 0, 0.03); + --ots-sidebar-group-hover-bg: rgba(0, 0, 0, 0.06); + --ots-sidebar-submenu-bg: rgba(0, 0, 0, 0.04); + --ots-sidebar-submenu-border: #cbd5e1; + --ots-sidebar-submenu-hover-bg: rgba(0, 0, 0, 0.08); + --ots-sidebar-button-bg: rgba(0, 0, 0, 0.04); + --ots-sidebar-button-bg-hover: rgba(0, 0, 0, 0.08); + --ots-sidebar-button-text: #334155; + --ots-sidebar-collapsed-item-bg: rgba(0, 0, 0, 0.04); + --ots-sidebar-collapsed-item-hover-bg: rgba(0, 0, 0, 0.08); + --ots-sidebar-width: 256px; + --ots-sidebar-collapsed-width: 64px; + --ots-sidebar-header-height: 64px; + --ots-sidebar-item-radius: 10px; + --ots-sidebar-item-height: 44px; + --ots-sidebar-item-padding-x: 12px; + + background-color: var(--color-background); + color: var(--color-text-primary); +} + +body.ots-light-mode .ots-sidebar { + background-color: #f1f5f9 !important; + border-right-color: #e2e8f0 !important; +} + +body.ots-light-mode .ots-sidebar.collapsed .sidebar-group-toggle, +body.ots-light-mode .ots-sidebar.collapsed .sidebar-list > a { + background: rgba(59, 130, 246, 0.06) !important; + color: #334155 !important; +} + +body.ots-light-mode .ots-sidebar.collapsed .sidebar-group-toggle:hover, +body.ots-light-mode .ots-sidebar.collapsed .sidebar-list > a:hover { + background: rgba(59, 130, 246, 0.12) !important; + color: #0f172a !important; +} + +body.ots-light-mode .ots-sidebar.collapsed .ots-nav-icon { + color: #334155 !important; +} + +body.ots-light-mode .ots-sidebar.collapsed .ots-nav-icon::before, +body.ots-light-mode .ots-sidebar.collapsed .ots-nav-icon.fa::before, +body.ots-light-mode .ots-sidebar.collapsed .fa::before { + color: #334155 !important; +} + +body.ots-light-mode .ots-sidebar li.sidebar-list > a, +body.ots-light-mode .ots-sidebar li.sidebar-main > a { + color: #334155; +} + +body.ots-light-mode .ots-sidebar li.sidebar-list > a:hover { + color: #0f172a; + background-color: rgba(59, 130, 246, 0.08); +} + +body.ots-light-mode .sidebar-group-toggle { + color: #334155; + background: rgba(0, 0, 0, 0.03); +} + +body.ots-light-mode .sidebar-group-toggle:hover { + background: rgba(0, 0, 0, 0.06); + color: #0f172a; +} + +body.ots-light-mode .sidebar-group.is-open > .sidebar-group-toggle, +body.ots-light-mode .sidebar-group-toggle[aria-expanded="true"] { + background: #ffffff; + color: #0b1221 !important; +} + +body.ots-light-mode .sidebar-submenu .sidebar-list > a { + background: rgba(0, 0, 0, 0.04); + color: #334155; + border-left-color: #cbd5e1; +} + +body.ots-light-mode .sidebar-header { + border-bottom-color: #e2e8f0; + background: #f1f5f9; +} + +body.ots-light-mode .ots-topbar { + background-color: #ffffff; + border-bottom: 1px solid #e2e8f0; +} + +body.ots-light-mode .ots-topbar .nav-link { + color: #334155; +} + +body.ots-light-mode .ots-topbar .nav-link:hover { + background-color: rgba(59, 130, 246, 0.06); + color: #2563eb; +} + +body.ots-light-mode .dashboard-card { + background: linear-gradient(180deg, rgba(255, 255, 255, 0.95), rgba(241, 245, 249, 0.95)); + border-color: #e2e8f0; +} + +body.ots-light-mode .dropdown-menu, +body.ots-light-mode .dropdown-right { + background-color: #ffffff; + border-color: #e2e8f0; + color: #0f172a; +} + +body.ots-light-mode .dropdown-item { + color: #334155; +} + +body.ots-light-mode .dropdown-item:hover, +body.ots-light-mode .dropdown-menu a:hover { + background-color: rgba(59, 130, 246, 0.1); + color: #2563eb; +} + +body.ots-light-mode .ots-content { + background-color: var(--color-background); +} + +/* ============================================================================ + THEME ENFORCE - Ensure page backgrounds follow the light/dark CSS variables + This block uses high-specificity rules and !important to override other + theme rules that may hardcode colors so the toggle always reflects + the `--color-*` variables set by `body.ots-light-mode` or :root. + ============================================================================ */ + +html, body, #page-wrapper, .ots-main, .ots-content, .page-content { + background-color: var(--color-background) !important; + color: var(--color-text-primary) !important; +} + +/* Topbar, footer and panels should use surface variables (elevated where appropriate) */ +.ots-topbar, +.ots-footer, +.dashboard-card, +.dropdown-menu, +.dropdown-right, +.modal-content { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; +} + +/* Surface-elevated elements (cards, panels) */ +.card, +.card-body, +.panel, +.panel-body, +.ots-panel, +.modal-body { + background-color: var(--color-surface-elevated) !important; + color: var(--color-text-primary) !important; +} + +/* Ensure sidebar and its components still receive their own overrides */ +.ots-sidebar, +.ots-sidebar .sidebar-header, +.ots-sidebar .sidebar-content { + background-color: var(--ots-sidebar-bg) !important; + color: var(--ots-sidebar-link) !important; +} + + /* ============================================================================ SHELL LAYOUT - SIDEBAR + MAIN ============================================================================ */ @@ -48,10 +288,10 @@ body { position: fixed; left: 0; top: 0; - width: auto !important; + width: var(--ots-sidebar-width) !important; height: 100vh; - background-color: #08132a; - border-right: 1px solid rgba(255, 255, 255, 0.06); + background-color: var(--ots-sidebar-bg); + border-right: 1px solid var(--ots-sidebar-border); padding: 0; display: flex; flex-direction: column; @@ -63,22 +303,85 @@ body { flex: 1; display: flex; flex-direction: column; - margin-left: calc(var(--ots-sidebar-width, 240px)) !important; + margin-left: var(--ots-sidebar-width) !important; } .ots-main, #page-wrapper { position: relative; - margin-left: calc(var(--ots-sidebar-width, 240px) - 1px) !important; + margin-left: var(--ots-sidebar-width) !important; transition: margin-left 150ms ease-out; } .ots-content { flex: 1; - padding: 32px; + padding: 32px 32px 32px 0; overflow-y: auto; } +/* Remove left padding between sidebar and page content */ +.page-content { + padding-left: 0 !important; +} + +/* Remove bootstrap gutters for main content so it sits flush against sidebar */ +.ots-main .page-content .row { + margin-left: 0 !important; + margin-right: 0 !important; +} + +.ots-main .page-content [class*="col-"] { + padding-left: 0 !important; + padding-right: 0 !important; +} + +.ots-main #content-wrapper, +.ots-main .page-content, +.ots-main .ots-content { + padding-left: 0 !important; +} + +/* Strongly enforce no left gap between sidebar and content for all layouts */ +#content-wrapper, +#content-wrapper .page-content, +#content-wrapper .page-content .row, +#content-wrapper .page-content [class*="col-"] { + margin-left: 0 !important; + padding-left: 0 !important; +} + +/* Ensure page header and meta area align flush */ +.page .meta, +.page-header, +.header-side { + margin-left: 0 !important; + padding-left: 0 !important; +} + +/* When the sidebar is expanded (not collapsed) make the page header clear and hide the header logo */ +.ots-sidebar:not(.collapsed) ~ .ots-main .page .meta, +.ots-sidebar:not(.collapsed) ~ .ots-main .page-header, +.ots-sidebar:not(.collapsed) ~ .ots-main .header-side, +.ots-sidebar:not(.collapsed) + .ots-main .page .meta, +.ots-sidebar:not(.collapsed) + .ots-main .page-header, +body.ots-sidebar-open .page .meta, +body.ots-sidebar-open .page-header, +body.ots-sidebar-open .header-side { + background: transparent !important; + box-shadow: none !important; +} + +.ots-sidebar:not(.collapsed) ~ .ots-main .page .xibo-logo, +.ots-sidebar:not(.collapsed) ~ .ots-main .xibo-logo, +.ots-sidebar:not(.collapsed) + .ots-main .page .xibo-logo, +.ots-sidebar:not(.collapsed) + .ots-main .xibo-logo, +.ots-sidebar:not(.collapsed) ~ .ots-main .xibo-logo-container, +.ots-sidebar:not(.collapsed) + .ots-main .xibo-logo-container, +body.ots-sidebar-open .xibo-logo, +body.ots-sidebar-open .xibo-logo-container { + display: none !important; +} + .ots-footer { border-top: 1px solid var(--color-border); padding: 16px 32px; @@ -113,13 +416,14 @@ body { ============================================================================ */ .sidebar-header { - padding: 18px 16px; + padding: 12px 16px; border-bottom: 1px solid var(--color-border); display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-shrink: 0; + height: var(--ots-sidebar-header-height); } .brand-link { @@ -134,8 +438,8 @@ body { } .brand-icon { - width: 32px; - height: 32px; + width: 40px; + height: 40px; display: flex; align-items: center; justify-content: center; @@ -145,7 +449,7 @@ body { .sidebar-content { flex: 1; - padding: 12px 0; + padding: 8px 0; overflow-y: auto; min-height: 0; } @@ -153,7 +457,7 @@ body { .sidebar-nav { list-style: none; margin: 0; - padding: 12px 0 140px; + padding: 100px 0 140px; } .sidebar-nav li { @@ -170,8 +474,8 @@ body { display: flex; align-items: center; gap: 12px; - padding: 8px 16px 8px 12px; - color: #f9fbff; + padding: 8px var(--ots-sidebar-item-padding-x); + color: var(--ots-sidebar-link); text-decoration: none; transition: all var(--transition-fast); position: relative; @@ -179,8 +483,8 @@ body { font-weight: 500; border-left: 2px solid transparent; margin: 3px 0; - border-radius: 12px; - min-height: 48px; + border-radius: var(--ots-sidebar-item-radius); + min-height: var(--ots-sidebar-item-height); line-height: 1.25; width: 100%; box-sizing: border-box; @@ -190,42 +494,85 @@ body { /* Force readable text in sidebar links */ .ots-sidebar a, .ots-sidebar .ots-nav-text { - color: #f9fbff; + color: var(--ots-sidebar-link); } .ots-sidebar li.sidebar-list > a:hover { - color: #ffffff; - background-color: rgba(255, 255, 255, 0.08); + color: var(--ots-sidebar-link-hover-text); + background-color: var(--ots-sidebar-link-hover-bg); } .ots-sidebar li.sidebar-list.active > a, .ots-sidebar li.sidebar-list > a.active, .ots-sidebar li.sidebar-main.active > a, .ots-sidebar li.sidebar-main > a.active { - color: #0b1221; - background-color: #ffffff; + color: var(--ots-sidebar-active-text); + background-color: var(--ots-sidebar-active-bg); border-left-color: transparent; font-weight: 600; - box-shadow: 0 8px 18px rgba(15, 23, 42, 0.25); + box-shadow: var(--ots-sidebar-active-shadow); } .ots-sidebar li.sidebar-list.active > a .ots-nav-text, .ots-sidebar li.sidebar-list > a.active .ots-nav-text, .ots-sidebar li.sidebar-main.active > a .ots-nav-text, .ots-sidebar li.sidebar-main > a.active .ots-nav-text { - color: #0b1221; + color: var(--ots-sidebar-active-text); } +/* Stronger active state rules to ensure the currently active page is visibly + highlighted regardless of other theme CSS. Covers both `li.active > a` and + `a.active` patterns as well as collapsed sidebar appearance. */ +.ots-sidebar li.sidebar-list.active > a, +.ots-sidebar li.sidebar-list > a.active, +#sidebar-wrapper .sidebar-list a.active, +.ots-sidebar li.sidebar-main.active > a, +.ots-sidebar li.sidebar-main > a.active, +.ots-sidebar .sidebar-list > a[aria-current="page"] { + background-color: var(--ots-sidebar-active-bg) !important; + color: var(--ots-sidebar-active-text) !important; + border-left-color: transparent !important; + font-weight: 600 !important; + box-shadow: var(--ots-sidebar-active-shadow) !important; +} + +/* Ensure the icon in the active link also uses active text color */ +.ots-sidebar li.sidebar-list.active > a .ots-nav-icon, +.ots-sidebar li.sidebar-list > a.active .ots-nav-icon, +#sidebar-wrapper .sidebar-list a.active .ots-nav-icon { + color: var(--ots-sidebar-active-text) !important; +} + +/* Collapsed sidebar: show a subtle circular highlight behind the icon */ +.ots-sidebar.collapsed .sidebar-list > a.active, +.ots-sidebar.collapsed .sidebar-group-toggle.active { + background: transparent !important; +} +.ots-sidebar.collapsed .sidebar-list > a.active .ots-nav-icon, +.ots-sidebar.collapsed .sidebar-group-toggle.active .ots-nav-icon { + background-color: var(--ots-sidebar-collapsed-item-bg) !important; + width: 40px !important; + height: 40px !important; + border-radius: 10px !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + color: var(--ots-sidebar-active-text) !important; +} + + .ots-sidebar .ots-nav-icon { width: 28px; height: 28px; - display: inline-flex; + display: flex !important; align-items: center; justify-content: center; flex-shrink: 0; font-size: 16px; color: currentColor; - margin-left: 0; + margin: 0; + text-align: center; + line-height: 1; } .ots-sidebar .ots-nav-text { @@ -245,7 +592,7 @@ body { font-size: 10px; font-weight: 700; text-transform: uppercase; - color: #8ea4c7; + color: var(--ots-sidebar-muted-text); letter-spacing: 0.12em; padding: 12px 14px 4px; margin: 8px 0 0; @@ -254,8 +601,8 @@ body { } .brand-logo { - width: 32px; - height: 32px; + width: 40px; + height: 40px; object-fit: contain; } @@ -267,19 +614,18 @@ body { } .sidebar-header { - padding: 18px 16px; - border-bottom: 1px solid rgba(255, 255, 255, 0.08); + padding: 12px 16px; + border-bottom: 1px solid var(--ots-sidebar-border); display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-shrink: 0; - height: 72px; + height: var(--ots-sidebar-header-height); box-sizing: border-box; - position: sticky; - top: 0; - background: #08132a; - z-index: 1210; + position: relative; + background: var(--ots-sidebar-header-bg); + z-index: 1; } .sidebar-collapse-btn { @@ -287,8 +633,8 @@ body { height: 32px; border-radius: 8px; border: 0; - background: rgba(255, 255, 255, 0.08); - color: #d7e2f8; + background: var(--ots-sidebar-button-bg); + color: var(--ots-sidebar-button-text); display: inline-flex; align-items: center; justify-content: center; @@ -297,12 +643,40 @@ body { } .sidebar-collapse-btn:hover { - background: rgba(255, 255, 255, 0.16); - color: #ffffff; + background: var(--ots-sidebar-button-bg-hover); + color: var(--ots-sidebar-link-hover-text); +} + +/* Expand button - visible only when collapsed */ +.sidebar-expand-btn { + width: 32px; + height: 32px; + border-radius: 8px; + border: 0; + background: var(--ots-sidebar-button-bg); + color: var(--ots-sidebar-button-text); + display: none; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all var(--transition-fast); +} + +.sidebar-expand-btn:hover { + background: var(--ots-sidebar-button-bg-hover); + color: var(--ots-sidebar-link-hover-text); +} + +.ots-sidebar.collapsed .sidebar-expand-btn { + display: inline-flex !important; +} + +.ots-sidebar.collapsed .sidebar-collapse-btn-visible { + display: none !important; } .ots-sidebar.collapsed { - width: 88px; + width: var(--ots-sidebar-collapsed-width) !important; } .ots-sidebar.collapsed .brand-text, @@ -310,14 +684,57 @@ body { .ots-sidebar.collapsed .sidebar-list .ots-nav-text, .ots-sidebar.collapsed .sidebar-submenu, .ots-sidebar.collapsed .sidebar-group-caret, -.ots-sidebar.collapsed .user-details { +.ots-sidebar.collapsed .user-details, +.ots-sidebar.collapsed .sidebar-header .sidebar-collapse-btn, +.ots-sidebar.collapsed .brand-link .brand-text { display: none !important; } +.ots-sidebar.collapsed .sidebar-header { + height: auto; + padding: 8px; + justify-content: center; + flex-direction: column; + gap: 8px; +} + +.ots-sidebar.collapsed .brand-icon { + flex-shrink: 0; +} + .ots-sidebar.collapsed .sidebar-group-toggle, .ots-sidebar.collapsed .sidebar-list > a { - justify-content: center; - padding: 10px; + display: flex !important; + align-items: center !important; + justify-content: center !important; + padding: 0 !important; + width: 100% !important; + height: 56px !important; + background: var(--ots-sidebar-collapsed-item-bg) !important; + color: var(--ots-sidebar-link) !important; + border-radius: 12px !important; + margin: 8px 6px !important; + box-sizing: border-box !important; + overflow: visible !important; + flex-direction: row !important; + gap: 0 !important; +} + +/* When collapsed, hide all nav text labels so only icons are visible */ +.ots-sidebar.collapsed .ots-nav-text, +.ots-sidebar.collapsed .sidebar-group-toggle .ots-nav-text, +.ots-sidebar.collapsed .sidebar-list .ots-nav-text, +.ots-sidebar.collapsed .brand-text, +.ots-sidebar.collapsed .user-details { + display: none !important; + visibility: hidden !important; + width: 0 !important; + overflow: hidden !important; +} + +.ots-sidebar.collapsed .sidebar-group-toggle:hover, +.ots-sidebar.collapsed .sidebar-list > a:hover { + background: var(--ots-sidebar-collapsed-item-hover-bg) !important; } /* Hide caret when collapsed */ @@ -327,34 +744,177 @@ body { /* Center icons when collapsed */ .ots-sidebar.collapsed .ots-nav-icon { - justify-self: center !important; - margin-left: 0 !important; + display: flex !important; + width: 20px !important; + height: 20px !important; + align-items: center !important; + justify-content: center !important; + flex-shrink: 0 !important; + font-size: 20px !important; + margin: 0 !important; + padding: 0 !important; + color: var(--ots-sidebar-link) !important; + visibility: visible !important; + line-height: 1 !important; +} + +/* Stronger collapsed-state icon overrides */ +.ots-sidebar.collapsed .ots-nav-icon, +.ots-sidebar.collapsed .ots-nav-icon i, +.ots-sidebar.collapsed .ots-nav-icon svg, +.ots-sidebar.collapsed .btn-icon { + color: var(--ots-sidebar-link) !important; + fill: currentColor !important; + stroke: currentColor !important; + opacity: 1 !important; +} + +/* Force-fill any SVG child shapes (paths, g) to currentColor so embedded fills are overridden */ +.ots-sidebar.collapsed .ots-nav-icon svg *, +.ots-sidebar.collapsed .ots-nav-icon svg path, +.ots-sidebar.collapsed .ots-nav-icon svg g { + fill: currentColor !important; + stroke: currentColor !important; + opacity: 1 !important; +} + +/* Remove potential dimming filters */ +.ots-sidebar.collapsed .ots-nav-icon svg, +.ots-sidebar.collapsed .ots-nav-icon i { + filter: none !important; +} + +/* Very strong override: force collapsed icons to a bright foreground in dark mode, + and to a dark foreground in light mode. This is intentionally specific to + override icon fills coming from embedded SVGs, fonts, or background images. */ +:root .ots-sidebar.collapsed .sidebar-list > a .ots-nav-icon, +:root .ots-sidebar.collapsed .sidebar-group-toggle .ots-nav-icon, +:root .ots-sidebar.collapsed .ots-nav-icon, +:root .ots-sidebar.collapsed .ots-nav-icon * { + color: #ffffff !important; + fill: #ffffff !important; + stroke: #ffffff !important; + opacity: 1 !important; + filter: none !important; +} + +/* Light mode: force dark icons */ +body.ots-light-mode .ots-sidebar.collapsed .sidebar-list > a .ots-nav-icon, +body.ots-light-mode .ots-sidebar.collapsed .sidebar-group-toggle .ots-nav-icon, +body.ots-light-mode .ots-sidebar.collapsed .ots-nav-icon, +body.ots-light-mode .ots-sidebar.collapsed .ots-nav-icon * { + color: #0b1221 !important; + fill: #0b1221 !important; + stroke: #0b1221 !important; + opacity: 1 !important; + filter: none !important; +} + +/* Target Font Awesome pseudo-element glyphs (fa, fa-*) which render via ::before */ +:root .ots-sidebar.collapsed .ots-nav-icon::before, +:root .ots-sidebar.collapsed .ots-nav-icon.fa::before, +:root .ots-sidebar.collapsed .fa::before, +:root .ots-sidebar.collapsed .ots-nav-icon .fa::before { + color: #ffffff !important; + opacity: 1 !important; + text-shadow: none !important; +} + +body.ots-light-mode .ots-sidebar.collapsed .ots-nav-icon::before, +body.ots-light-mode .ots-sidebar.collapsed .ots-nav-icon.fa::before, +body.ots-light-mode .ots-sidebar.collapsed .fa::before, +body.ots-light-mode .ots-sidebar.collapsed .ots-nav-icon .fa::before { + color: #0b1221 !important; + opacity: 1 !important; + text-shadow: none !important; +} + +/* Center Font Awesome pseudo-elements inside icon containers */ +.ots-sidebar .ots-nav-icon::before, +.ots-sidebar .ots-nav-icon.fa::before, +.ots-sidebar .fa::before { + display: inline-block !important; + line-height: 1 !important; +} + +/* Ensure icon glyphs (font icons and svgs) follow the sidebar link color + and adapt to light/dark mode. Use currentColor for SVG fill/stroke. */ +.ots-sidebar .ots-nav-icon, +.ots-sidebar.collapsed .ots-nav-icon, +.ots-sidebar .ots-nav-icon i, +.ots-sidebar .ots-nav-icon svg { + color: var(--ots-sidebar-link) !important; +} + +.ots-sidebar .ots-nav-icon svg { + fill: currentColor !important; + stroke: currentColor !important; +} + +/* Explicitly handle light-mode variant (redundant but higher specificity) */ +body.ots-light-mode .ots-sidebar .ots-nav-icon, +body.ots-light-mode .ots-sidebar.collapsed .ots-nav-icon { + color: var(--ots-sidebar-link) !important; +} + +/* Show expand button when collapsed, hide collapse button */ +.ots-sidebar.collapsed .sidebar-expand-btn { + display: inline-flex !important; +} + +.ots-sidebar.collapsed .sidebar-collapse-btn-visible { + display: none !important; } .ots-sidebar-collapsed #page-wrapper, .ots-sidebar-collapsed .ots-main { - margin-left: 88px !important; + margin-left: var(--ots-sidebar-collapsed-width) !important; } .ots-sidebar-nav { padding: 12px 0 140px; - padding-top: 8px; + padding-top: 6px; +} + +/* When collapsed, add extra top padding so menu items sit below the header (logo + buttons) */ +.ots-sidebar.collapsed .ots-sidebar-nav, +.ots-sidebar.collapsed .ots-sidebar-nav, +.ots-sidebar-collapsed .ots-sidebar-nav { + padding-top: 72px !important; +} + +/* Also ensure expanded sidebar has sufficient top padding below header/logo */ +.ots-sidebar:not(.collapsed) .ots-sidebar-nav, +.ots-sidebar-collapsed:not(.collapsed) .ots-sidebar-nav { + padding-top: 72px !important; } .sidebar-group { margin-top: 6px; } +/* Center nav icons inside their item containers */ +.ots-sidebar .sidebar-list > a, +.ots-sidebar .sidebar-group-toggle { + align-items: center !important; +} +.ots-sidebar .ots-nav-icon { + margin: 0 auto !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; +} + .sidebar-group-toggle { display: flex; align-items: center; gap: 12px; - padding: 10px 16px 10px 12px; + padding: 8px var(--ots-sidebar-item-padding-x); margin: 6px 0; - border-radius: 12px; - color: #f9fbff; + border-radius: var(--ots-sidebar-item-radius); + color: var(--ots-sidebar-link); text-decoration: none; - background: rgba(255, 255, 255, 0.03); + background: var(--ots-sidebar-group-bg); transition: all var(--transition-fast); position: relative; /* for absolutely positioned caret */ width: 100%; @@ -366,9 +926,9 @@ body { .ots-sidebar .sidebar-group.is-open > .sidebar-group-toggle, .ots-sidebar .sidebar-group-toggle[aria-expanded="true"], .ots-sidebar .sidebar-group-toggle.active { - background: #ffffff; - color: #0b1221 !important; - box-shadow: 0 10px 22px rgba(15, 23, 42, 0.2); + background: var(--ots-sidebar-active-bg); + color: var(--ots-sidebar-active-text) !important; + box-shadow: var(--ots-sidebar-active-shadow); } .ots-sidebar .sidebar-group.is-open > .sidebar-group-toggle .ots-nav-text, @@ -380,12 +940,12 @@ body { .ots-sidebar .sidebar-group-toggle.active .ots-nav-text, .ots-sidebar .sidebar-group-toggle.active .ots-nav-icon, .ots-sidebar .sidebar-group-toggle.active .sidebar-group-caret { - color: #0b1221 !important; + color: var(--ots-sidebar-active-text) !important; } .sidebar-group-toggle:hover { - background: rgba(255, 255, 255, 0.1); - color: #ffffff; + background: var(--ots-sidebar-group-hover-bg); + color: var(--ots-sidebar-link-hover-text); } .sidebar-group-caret { @@ -394,19 +954,90 @@ body { top: 50%; transform: translateY(-50%); font-size: 12px; - opacity: 0.85; + opacity: 0.95; cursor: pointer; pointer-events: auto; + display: inline-flex; /* ensure visible whether open or closed */ + align-items: center; + justify-content: center; + color: var(--ots-sidebar-link); /* default visible color */ } .sidebar-submenu { list-style: none; margin: 4px 0 8px; padding: 0 0 0 12px; - border-left: 1px solid rgba(255, 255, 255, 0.08); + border-left: 1px solid var(--ots-sidebar-border); display: none; } +/* Show submenu on hover for accessibility and collapsed sidebar UX */ +.ots-sidebar .sidebar-group:hover > .sidebar-submenu { + display: block; +} + +/* When the sidebar is collapsed, float submenus to the right as panels */ +.ots-sidebar.collapsed .sidebar-submenu { + position: absolute !important; + left: calc(var(--ots-sidebar-collapsed-width) + 8px) !important; + top: 0 !important; + margin: 0 !important; + padding: 8px !important; + background: var(--color-surface) !important; + border: 1px solid var(--color-border) !important; + box-shadow: 0 8px 24px rgba(0,0,0,0.45) !important; + min-width: 220px !important; + z-index: 1300 !important; + border-radius: 8px !important; + display: none; + list-style: none; +} + +.ots-sidebar.collapsed .sidebar-group:hover > .sidebar-submenu { + display: block !important; +} + +/* Style submenu items when displayed in the hover panel */ +.ots-sidebar.collapsed .sidebar-submenu .sidebar-list > a { + display: flex !important; + align-items: center !important; + gap: 12px !important; + padding: 10px 12px !important; + margin: 4px 0 !important; + width: 100% !important; + height: auto !important; + background: transparent !important; + border-radius: 6px !important; + color: var(--color-text-primary) !important; + text-decoration: none !important; + transition: all var(--transition-fast) !important; + box-sizing: border-box !important; + border: none !important; +} + +.ots-sidebar.collapsed .sidebar-submenu .sidebar-list > a:hover { + background: rgba(59, 130, 246, 0.12) !important; + color: var(--color-primary) !important; +} + +/* Show text and icon in submenu items when hovering */ +.ots-sidebar.collapsed .sidebar-submenu .ots-nav-text, +.ots-sidebar.collapsed .sidebar-submenu .ots-nav-icon { + display: inline-flex !important; + visibility: visible !important; + width: auto !important; + overflow: visible !important; +} + +.ots-sidebar.collapsed .sidebar-submenu .ots-nav-icon { + width: 20px !important; + height: 20px !important; + font-size: 14px !important; + flex-shrink: 0 !important; + margin: 0 !important; + color: var(--color-text-primary) !important; +} + .sidebar-group.is-open .sidebar-group-caret { transform: rotate(180deg); } @@ -417,15 +1048,21 @@ body { .sidebar-submenu .sidebar-list > a { margin: 4px 0; - background: rgba(15, 23, 42, 0.6); + background: var(--ots-sidebar-submenu-bg); width: 100%; box-sizing: border-box; padding-right: 48px; /* keep caret space for nested items */ /* Slight indent for submenu items */ padding-left: 28px !important; - border-left: 4px solid rgba(255, 255, 255, 0.9); - border-radius: 12px; - color: #f9fbff; + border-left: 4px solid var(--ots-sidebar-submenu-border); + border-radius: var(--ots-sidebar-item-radius); + color: var(--ots-sidebar-link); + font-weight: 500; +} + +.sidebar-submenu .sidebar-list > a:hover { + background: var(--ots-sidebar-submenu-hover-bg); + color: var(--ots-sidebar-link-hover-text); } /* Ensure parent toggles and top-level links span full sidebar width */ @@ -437,14 +1074,16 @@ body { } .sidebar-submenu .sidebar-list > a:hover { - background: rgba(255, 255, 255, 0.1); + background: var(--ots-sidebar-submenu-hover-bg); } .sidebar-submenu .sidebar-list.active > a, .sidebar-submenu .sidebar-list > a.active { - color: #0b1221; - background-color: #ffffff; - box-shadow: 0 8px 18px rgba(15, 23, 42, 0.25); + color: var(--ots-sidebar-active-text); + background-color: var(--ots-sidebar-active-bg); + box-shadow: var(--ots-sidebar-active-shadow); + border-left-color: var(--color-primary); + font-weight: 600; } /* sidebar-footer removed; account bar intentionally omitted */ @@ -587,6 +1226,13 @@ body { gap: 16px; height: 64px; z-index: 1000; + position: sticky; + top: 0; +} + +body.ots-sidebar-open .ots-topbar { + background-color: transparent !important; + box-shadow: none !important; } /* Topbar nav container - override .navbar-nav defaults */ @@ -631,6 +1277,19 @@ body { color: var(--color-primary); } +/* Ensure dropdowns, popovers and modals appear above the sticky topbar */ +.dropdown, +.dropdown-menu, +.popover, +.modal { + z-index: 1201 !important; +} + +/* Modal backdrop should sit behind the modal but above other content */ +.modal-backdrop { + z-index: 1200 !important; +} + .ots-topbar .nav-item.open .nav-link, .ots-topbar .nav-item.active .nav-link { background-color: rgba(59, 130, 246, 0.12); @@ -1031,19 +1690,7 @@ body { display: block; } -/* OTS theme badge in topbar (authed-theme-topbar.twig) */ -.ots-theme-badge .nav-link { - display: inline-flex; - align-items: center; - padding: 6px 10px; - border-radius: 999px; - background: rgba(59, 130, 246, 0.12); - color: var(--color-text-primary); - font-size: 12px; - font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; -} +/* OTS topbar badge removed */ .dropdown-menu li a { display: flex; @@ -1462,17 +2109,21 @@ body { min-height: 0; } + .dashboard-chart-canvas { - background: rgba(15, 23, 42, 0.45); - border: 1px solid rgba(255, 255, 255, 0.05); - border-radius: 12px; - padding: 12px; + /* Make chart containers visually match their parent dashboard card by + removing their independent background and chrome so the card's + background (gradient or surface) shows through. */ + background: transparent !important; + border: none !important; + border-radius: inherit !important; + padding: 0 !important; height: 200px; width: 100%; position: relative; overflow: hidden; flex: 1; - box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2); + box-shadow: none !important; } .dashboard-chart-canvas canvas { @@ -1482,6 +2133,7 @@ body { max-width: 100% !important; max-height: 100% !important; margin: 0 !important; + background: transparent !important; } .dashboard-chart-card .panel-body canvas, @@ -1783,14 +2435,102 @@ body .panel .panel-heading, } .ots-filter-card { - background: linear-gradient(180deg, rgba(30, 41, 59, 0.95), rgba(15, 23, 42, 0.92)); - border: 1px solid rgba(148, 163, 184, 0.22); - box-shadow: 0 18px 34px rgba(8, 15, 30, 0.32); + background-color: var(--color-surface-elevated) !important; + background-image: none !important; + border: 1px solid var(--color-border) !important; + box-shadow: var(--ots-shadow-md, 0 8px 18px rgba(0,0,0,0.12)); margin-bottom: 0; border-radius: 12px; overflow: hidden; } +/* Strongly remove any remaining gradients / shadows inside filters */ +.ots-filter-card *, +.ots-filter-card *::before, +.ots-filter-card *::after { + background-image: none !important; + background: transparent !important; + box-shadow: none !important; +} + +.ots-filter-card .form-control, +.ots-filter-card select, +.ots-filter-card .select2-selection, +.ots-filter-card .input-group-addon, +.ots-filter-card button, +.ots-filter-card .btn { + background-color: var(--color-surface) !important; + background-image: none !important; + box-shadow: none !important; + color: var(--color-text-primary) !important; +} + +/* Extra strong fallbacks targeting various control classes used in filter markup */ +.ots-filter-card input.form-control, +.ots-filter-card input[type="text"], +.ots-filter-card input[type="search"], +.ots-filter-card textarea, +.ots-filter-card select, +.ots-filter-card select.custom-select, +.ots-filter-card .custom-select, +.ots-filter-card .input-group-text, +.ots-filter-card .input-group-addon, +.ots-filter-card .input-group-append, +.ots-filter-card .input-group { + background-color: var(--color-surface, var(--ots-surface, var(--color-background))) !important; + background-image: none !important; + background: var(--color-surface, var(--ots-surface, var(--color-background))) !important; + box-shadow: none !important; + border: 1px solid var(--color-border, var(--ots-border)) !important; +} + +.ots-filter-card select option { + background: var(--color-surface, var(--ots-surface)) !important; + color: var(--color-text-primary, var(--ots-text)) !important; +} + +/* Specific override for the append/addon area (checkbox + operator) */ +.ots-filter-card .input-group-append, +.ots-filter-card .input-group-append .input-group-text, +.ots-filter-card .input-group-append .input-group-addon, +.ots-filter-card .input-group-append .custom-select, +.ots-filter-card .input-group-append .select, +.ots-filter-card .input-group-append .btn, +.ots-filter-card .input-group-append .dropdown-toggle { + background-color: var(--color-surface) !important; + background-image: none !important; + background: var(--color-surface) !important; + box-shadow: none !important; + border: 1px solid var(--color-border) !important; + color: var(--color-text-primary) !important; +} + +/* Force checkbox wrapper to be transparent so only checkbox is visible */ +.ots-filter-card .input-group-text input[type="checkbox"], +.ots-filter-card .input-group-text input[type="checkbox"] + label { + background: transparent !important; + box-shadow: none !important; +} + +/* Aggressive override: force all direct children of input-group to use solid surface */ +.ots-filter-card .input-group > *, +.ots-filter-card .input-group > *::before, +.ots-filter-card .input-group > *::after { + background: var(--color-surface, var(--ots-surface, var(--color-background))) !important; + background-image: none !important; + box-shadow: none !important; + border-radius: 6px !important; + border: 1px solid var(--color-border, var(--ots-border)) !important; + color: var(--color-text-primary, var(--ots-text)) !important; +} + +/* If any decorative pseudo elements are used to create the rounded pills, hide them */ +.ots-filter-card .input-group > *::before, +.ots-filter-card .input-group > *::after { + content: none !important; + display: none !important; +} + /* Filter header with collapse button */ .ots-filter-header { display: flex; @@ -1846,7 +2586,7 @@ body .panel .panel-heading, } .ots-filter-card .nav-tabs { - border-bottom: 1px solid rgba(148, 163, 184, 0.2); + border-bottom: 1px solid var(--color-border); gap: 8px; margin-bottom: 14px; } @@ -1879,6 +2619,7 @@ body .panel .panel-heading, .ots-filter-card .select2-selection, .ots-filter-card .input-group-addon { background: var(--color-surface) !important; + background-image: none !important; border: 1px solid var(--color-border) !important; color: var(--color-text-primary) !important; border-radius: 6px !important; @@ -1886,13 +2627,86 @@ body .panel .panel-heading, font-size: 13px !important; transition: all var(--transition-fast) !important; height: 36px !important; + box-shadow: none !important; } .ots-filter-card .form-control:focus, .ots-filter-card select:focus { border-color: var(--color-primary) !important; - box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1) !important; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.08) !important; background: var(--color-surface) !important; + background-image: none !important; +} + +/* Fix: force checkboxes to respect light/dark theme (remove hard dark square) */ +.ots-filter-card input[type="checkbox"], +.ots-filter-card .form-check-input, +.ots-filter-card .custom-control-input { + -webkit-appearance: none !important; + appearance: none !important; + width: 18px !important; + height: 18px !important; + display: inline-block !important; + vertical-align: middle !important; + border-radius: 4px !important; + background-color: var(--color-surface) !important; + background-image: none !important; + border: 1px solid var(--color-border) !important; + box-shadow: none !important; +} + +.ots-filter-card input[type="checkbox"]:checked, +.ots-filter-card .form-check-input:checked, +.ots-filter-card .custom-control-input:checked { + background-color: var(--color-primary) !important; + border-color: var(--color-primary) !important; + background-image: none !important; + box-shadow: none !important; +} + +/* Support bootstrap/custom-control markup where labels render the box via ::before */ +.ots-filter-card .custom-control-input + .custom-control-label::before, +.ots-filter-card .form-check-input + label::before, +.modal .custom-control-input + .custom-control-label::before, +.modal .form-check-input + label::before { + background-color: var(--color-surface) !important; + background-image: none !important; + border: 1px solid var(--color-border) !important; + box-shadow: none !important; +} + +.ots-filter-card .custom-control-input:checked + .custom-control-label::before, +.ots-filter-card .form-check-input:checked + label::before, +.modal .custom-control-input:checked + .custom-control-label::before, +.modal .form-check-input:checked + label::before { + background-color: var(--color-primary) !important; + border-color: var(--color-primary) !important; + background-image: none !important; + box-shadow: none !important; +} + +/* Also apply broadly inside modals/popovers in case the checkbox is outside the filter card */ +.modal input[type="checkbox"], +.modal .form-check-input, +.popover input[type="checkbox"], +.popover .form-check-input { + -webkit-appearance: none !important; + appearance: none !important; + width: 18px !important; + height: 18px !important; + background-color: var(--color-surface) !important; + background-image: none !important; + border: 1px solid var(--color-border) !important; + box-shadow: none !important; +} + +.modal input[type="checkbox"]:checked, +.modal .form-check-input:checked, +.popover input[type="checkbox"]:checked, +.popover .form-check-input:checked { + background-color: var(--color-primary) !important; + border-color: var(--color-primary) !important; + background-image: none !important; } .ots-filter-card label { @@ -2060,6 +2874,64 @@ body .panel .panel-heading, } } +/* Light-mode table color overrides to ensure contrast and legibility */ +body.ots-light-mode .ots-table-card .table thead th { + background: var(--color-surface-elevated) !important; + color: var(--color-text-primary) !important; + border-bottom: 1px solid var(--color-border) !important; + opacity: 1 !important; +} + +body.ots-light-mode .ots-table-card .table thead th, +body.ots-light-mode .ots-table-card .table tbody td, +body.ots-light-mode .ots-table-card .table tbody th, +body.ots-light-mode .ots-table-card .table tbody tr { + color: var(--color-text-primary) !important; +} + +body.ots-light-mode .ots-table-card .table tbody tr { + background: var(--color-surface) !important; +} + +body.ots-light-mode .ots-table-card .table tbody tr:nth-child(even) { + background: var(--color-surface-elevated) !important; +} + +body.ots-light-mode .ots-table-card .table tbody tr:hover { + background: rgba(37, 99, 235, 0.06) !important; /* subtle blue hover */ + color: var(--color-text-primary) !important; +} + +body.ots-light-mode .ots-table-card .table tbody tr.selected, +body.ots-light-mode .ots-table-card .table tbody tr.dt-row-selected, +body.ots-light-mode .ots-table-card .table tbody tr.selected td, +body.ots-light-mode .ots-table-card .table tbody tr.dt-row-selected td { + background: rgba(16, 185, 129, 0.12) !important; + color: var(--color-text-primary) !important; +} + +body.ots-light-mode .ots-table-card .dataTables_wrapper, +body.ots-light-mode .ots-table-card .dataTables_wrapper * { + color: var(--color-text-primary) !important; +} + +body.ots-light-mode .ots-table-card .dataTables_wrapper .dataTables_filter input, +body.ots-light-mode .ots-table-card .dataTables_wrapper .dataTables_length select { + background: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +/* Ensure Displays page specific table rows are light in light-mode */ +body.ots-light-mode .ots-displays-page #datatable-container .XiboData table.dataTable tbody tr { + background-color: transparent !important; +} + +body.ots-light-mode .ots-displays-page #datatable-container .XiboData table.dataTable tbody tr:nth-child(even) { + background-color: var(--color-surface-elevated) !important; +} + + .two-column-layout { display: grid; grid-template-columns: 280px 1fr; @@ -2547,7 +3419,7 @@ body .panel .panel-heading, } .ots-content { - padding: 16px; + padding: 16px 16px 16px 0; } .ots-footer { @@ -2996,6 +3868,57 @@ legend { color: var(--color-primary) !important; } +/* Ensure all pop-up/dialog surfaces follow theme variables (light/dark toggle) */ +.modal, +.modal-content, +.modal-header, +.modal-body, +.modal-footer, +.popover, +.popover .popover-body, +.tooltip, +.tooltip .tooltip-inner, +.dropdown-menu, +.select2-dropdown, +.select2-container--default .select2-dropdown { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; + box-shadow: var(--shadow-base, 0 6px 18px rgba(0,0,0,0.08)) !important; +} + +/* Elevated headers inside modals should use elevated surface */ +.modal-header { + background-color: var(--color-surface-elevated) !important; + border-bottom: 1px solid var(--color-border) !important; +} + +/* Tooltips */ +.tooltip .tooltip-inner { + background-color: var(--color-text-primary) !important; + color: var(--color-background) !important; + border-radius: 6px !important; +} + +/* Select2 dropdowns and choices */ +.select2-container--default .select2-results__option[aria-selected=true], +.select2-container--default .select2-results__option--highlighted { + background-color: rgba(var(--color-primary-rgb, 37,99,235), 0.12) !important; +} + +/* Modal backdrop should be lighter in light mode */ +.modal-backdrop, +.modal-backdrop.show, +.modal-backdrop.in { + background-color: rgba(0,0,0,0.3) !important; +} + +body.ots-light-mode .modal-backdrop, +body.ots-light-mode .modal-backdrop.show, +body.ots-light-mode .modal-backdrop.in { + background-color: rgba(0,0,0,0.06) !important; +} + /* Ensure form elements are dark and readable */ .form-group, .form-check, @@ -3239,6 +4162,35 @@ a.text-muted { color: var(--color-text-secondary) !important; } +/* Tidy (library) button - broom icon and amber colouring */ +.btn-tidy { + background-color: var(--color-warning) !important; + border-color: rgba(0,0,0,0.08) !important; + color: var(--color-on-primary) !important; +} +.btn-tidy .fa, +.btn-tidy .fas, +.btn-tidy .far { + color: var(--color-on-primary) !important; +} + +/* Slightly darker hover for contrast (fallback where color-mix isn't available) */ +.btn-tidy:hover { + filter: brightness(0.95) !important; + color: var(--color-on-primary) !important; +} + +/* Ensure inline SVG broom inherits the tidy button colour and sizes nicely */ +.btn-tidy svg.icon-broom, +.btn-tidy svg.icon-broom-pantry { + width: 18px !important; + height: 18px !important; + display: inline-block !important; + vertical-align: middle !important; + fill: currentColor !important; +} + + a.text-muted:hover { color: var(--color-text-primary) !important; } @@ -3278,4 +4230,316 @@ a.text-muted:hover { font-weight: 600; margin: 0; white-space: nowrap; +} + +/* Strong final light-mode table overrides (high specificity & !important) + Placed at file end to ensure highest precedence over earlier dark rules. */ +body.ots-light-mode .table, +body.ots-light-mode .dataTable, +body.ots-light-mode .dataTables_wrapper, +body.ots-light-mode .ots-table-card .table, +body.ots-light-mode .ots-table-card .dataTables_wrapper { + color: var(--color-text-primary) !important; + background-color: transparent !important; + opacity: 1 !important; +} + +body.ots-light-mode .table thead th, +body.ots-light-mode .dataTable thead th, +body.ots-light-mode .ots-table-card .table thead th { + color: var(--color-text-secondary) !important; + background-color: var(--color-surface) !important; + opacity: 1 !important; +} + +body.ots-light-mode .table tbody td, +body.ots-light-mode .dataTable tbody td, +body.ots-light-mode .ots-table-card .table tbody td { + color: var(--color-text-primary) !important; + opacity: 1 !important; +} + +body.ots-light-mode .table tbody tr, +body.ots-light-mode .dataTable tbody tr, +body.ots-light-mode .ots-table-card .table tbody tr { + background: transparent !important; +} + +body.ots-light-mode .table tbody tr:nth-child(even), +body.ots-light-mode .dataTable tbody tr:nth-child(even), +body.ots-light-mode .ots-table-card .table tbody tr:nth-child(even) { + background: var(--color-surface-elevated) !important; +} + +body.ots-light-mode .table tbody tr:hover, +body.ots-light-mode .dataTable tbody tr:hover, +body.ots-light-mode .ots-table-card .table tbody tr:hover { + background: rgba(37, 99, 235, 0.06) !important; +} + +/* Ensure disabled/low-contrast states do not dim important table text */ +body.ots-light-mode .table [disabled], +body.ots-light-mode .table .disabled, +body.ots-light-mode .table :disabled { + opacity: 1 !important; + color: var(--color-text-secondary) !important; +} + +/* Target Xibo-specific table selectors that used light colors with !important */ +body.ots-light-mode .ots-displays-page #datatable-container .XiboData .table thead th, +body.ots-light-mode .ots-displays-page #datatable-container .XiboData table.dataTable thead th { + color: var(--color-text-secondary) !important; + background-color: var(--color-surface) !important; +} + +body.ots-light-mode .ots-displays-page #datatable-container .XiboData .table tbody td, +body.ots-light-mode .ots-displays-page #datatable-container .XiboData table.dataTable tbody td { + color: var(--color-text-primary) !important; + opacity: 1 !important; +} + +/* Also ensure any `.text-muted` or tertiary text inside tables becomes readable in light-mode */ +body.ots-light-mode .table thead th .text-muted, +body.ots-light-mode .table tbody td .text-muted, +body.ots-light-mode .ots-table-card .table thead th .text-muted { + color: var(--color-text-tertiary) !important; + opacity: 1 !important; +} + +/* Override the hardcoded #e2e8f0 colors from lines 2659 & 2673 in light-mode + by matching exact selectors with body.ots-light-mode prefix */ +body.ots-light-mode .ots-displays-page #datatable-container .XiboData .table, +body.ots-light-mode .ots-displays-page #datatable-container .XiboData table.dataTable { + color: var(--color-text-primary) !important; +} + +body.ots-light-mode .ots-displays-page #datatable-container .XiboData .table tbody td, +body.ots-light-mode .ots-displays-page #datatable-container .XiboData table.dataTable tbody td { + color: var(--color-text-primary) !important; + opacity: 1 !important; +} + +/* Ensure all table cells and text are dark in light-mode */ +body.ots-light-mode .ots-displays-page #datatable-container .XiboData .table tr, +body.ots-light-mode .ots-displays-page #datatable-container .XiboData table.dataTable tr { + color: var(--color-text-primary) !important; +} + + + +/* ============================================================================ + Remove hover animations inside the page wrapper + This prevents elements inside `#page-wrapper` from animating on hover + while keeping other site transitions intact. + ============================================================================ */ + +#page-wrapper *:hover { + transition: none !important; + transform: none !important; + animation: none !important; + box-shadow: none !important; +} + +#page-wrapper, +#page-wrapper * { + will-change: auto !important; +} + +/* Re-enable hover/transform/animation behavior inside dashboard areas only */ +#page-wrapper .dashboard-page *:hover, +#page-wrapper .dashboard-card *:hover, +#page-wrapper .dashboard-panels *:hover, +#page-wrapper .dashboard-chart-card *:hover { + transition: initial !important; + transform: initial !important; + animation: initial !important; + box-shadow: initial !important; +} + +#page-wrapper .dashboard-page, +#page-wrapper .dashboard-card, +#page-wrapper .dashboard-panels, +#page-wrapper .dashboard-chart-card { + will-change: auto !important; +} + +/* Ensure the very bottom of the page follows the theme variables (light/dark) + by forcing html/body and shell containers to use the variable-driven + background. This prevents residual dark bars under the footer. */ +html, +body, +.ots-shell, +#page-wrapper { + min-height: 100% !important; + background-color: var(--color-background) !important; +} + +/* Repair: remove any visible divider/seam between sidebar and main content + Some themes add a border or pseudo-element that creates a dark vertical bar. + Force those to be transparent/none so the content sits flush. */ +.ots-sidebar, +#sidebar-wrapper { + border-right: none !important; + box-shadow: none !important; +} + +/* Hide common pseudo-elements used as dividers or overlays */ +.ots-sidebar::after, +.ots-sidebar::before, +#page-wrapper::before, +#page-wrapper::after, +.ots-main::before, +.ots-main::after { + content: none !important; + display: none !important; + background: transparent !important; + box-shadow: none !important; +} + +.ots-footer { + background-color: var(--color-surface-elevated) !important; + color: var(--color-text-primary) !important; +} + +/* Reduce horizontal gap between sidebar and page content. + Keep content flush with the sidebar by matching the sidebar width. */ +.ots-main { + margin-left: var(--ots-sidebar-width) !important; +} + +.ots-main, +#page-wrapper, +#content-wrapper, +.ots-main #content-wrapper { + margin-left: var(--ots-sidebar-width) !important; +} + +/* When sidebar is collapsed, align content to the collapsed width */ +.ots-sidebar.collapsed ~ .ots-main, +.ots-sidebar.collapsed + .ots-main, +.ots-sidebar-collapsed .ots-main { + margin-left: var(--ots-sidebar-collapsed-width) !important; +} + + +/* Emergency: force root/background pseudo-elements to follow theme variables + This clears any leftover background-images or pseudo-element fills that + can create a dark bar at the bottom. */ +html, html:before, html:after, +body, body:before, body:after, +.ots-shell, #page-wrapper, #page-wrapper:before, #page-wrapper:after { + background-image: none !important; + background-color: var(--color-background) !important; + color: var(--color-text-primary) !important; + min-height: 100vh !important; +} + +/* Remove any content on pseudo-elements that might draw a dark strip */ +html:before, html:after, body:before, body:after, #page-wrapper:before, #page-wrapper:after { + content: none !important; +} + + +/* ============================================================================ + COMPREHENSIVE LIGHT-MODE TABLE OVERRIDES (Final Pass) + Force all table colors to be light-background + dark-text in light mode + ============================================================================ */ +body.ots-light-mode .table, +body.ots-light-mode .table thead, +body.ots-light-mode .table tbody, +body.ots-light-mode .table thead th, +body.ots-light-mode .table tbody td, +body.ots-light-mode .table tbody tr, +body.ots-light-mode .dataTables_wrapper, +body.ots-light-mode .dataTables_wrapper table, +body.ots-light-mode .dataTables_wrapper thead, +body.ots-light-mode .dataTables_wrapper thead th, +body.ots-light-mode .dataTables_wrapper tbody, +body.ots-light-mode .dataTables_wrapper tbody td, +body.ots-light-mode .dataTables_wrapper tbody tr, +body.ots-light-mode .modern-table, +body.ots-light-mode .modern-table thead, +body.ots-light-mode .modern-table thead th, +body.ots-light-mode .modern-table tbody, +body.ots-light-mode .modern-table tbody tr, +body.ots-light-mode .modern-table tbody td { + color: var(--color-text-primary) !important; + background-color: transparent !important; + opacity: 1 !important; +} + +body.ots-light-mode .table thead th, +body.ots-light-mode .dataTables_wrapper thead th, +body.ots-light-mode .modern-table thead th { + background-color: var(--color-surface-elevated) !important; + color: var(--color-text-secondary) !important; +} + +body.ots-light-mode .table tbody tr:nth-child(even), +body.ots-light-mode .dataTables_wrapper tbody tr:nth-child(even), +body.ots-light-mode .modern-table tbody tr:nth-child(even) { + background-color: var(--color-surface-elevated) !important; +} + +body.ots-light-mode .table tbody tr:hover, +body.ots-light-mode .dataTables_wrapper tbody tr:hover, +body.ots-light-mode .modern-table tbody tr:hover { + background-color: rgba(37, 99, 235, 0.06) !important; +} + +body.ots-light-mode .dataTables_wrapper .dataTables_info, +body.ots-light-mode .dataTables_wrapper .dataTables_length, +body.ots-light-mode .dataTables_wrapper .dataTables_filter, +body.ots-light-mode .dataTables_wrapper .dataTables_paginate { + color: var(--color-text-primary) !important; +} + +body.ots-light-mode .dataTables_wrapper .dataTables_filter input, +body.ots-light-mode .dataTables_wrapper .dataTables_length select { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border-color: var(--color-border) !important; +} + +body.ots-light-mode .dataTables_wrapper .dataTables_paginate .paginate_button { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border-color: var(--color-border) !important; +} + +body.ots-light-mode .dataTables_wrapper .dataTables_paginate .paginate_button.current { + background-color: var(--color-primary) !important; + color: white !important; + border-color: var(--color-primary) !important; +} +/* Aggressive unconditional overrides to ensure table text is readable + These apply regardless of light/dark mode */ +.table td, +.table th, +.dataTable td, +.dataTable th, +.dataTables_wrapper td, +.dataTables_wrapper th, +.modern-table td, +.modern-table th { + color: #0f172a !important; +} + +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_filter, +.dataTables_wrapper .dataTables_length { + color: #0f172a !important; +} + +.table thead, +.dataTable thead, +.modern-table thead { + background-color: #f1f5f9 !important; +} + +.dataTables_wrapper .dataTables_filter input, +.dataTables_wrapper .dataTables_length select { + color: #0f172a !important; + background-color: #ffffff !important; + border-color: #e2e8f0 !important; } \ No newline at end of file diff --git a/custom/otssignange/js/theme.js b/custom/otssignange/js/theme.js index da1ae98..207cf06 100644 --- a/custom/otssignange/js/theme.js +++ b/custom/otssignange/js/theme.js @@ -17,7 +17,8 @@ const toggleBtn = document.querySelector('[data-action="toggle-sidebar"]'); const sidebar = document.querySelector('.ots-sidebar'); const closeBtn = document.querySelector('.ots-sidebar-close'); - const collapseBtn = document.querySelector('.sidebar-collapse-btn'); + const collapseBtn = document.querySelector('.sidebar-collapse-btn-visible'); + const expandBtn = document.querySelector('.sidebar-expand-btn'); const body = document.body; if (!sidebar) return; @@ -42,6 +43,7 @@ if (isCollapsed) { sidebar.classList.add('collapsed'); body.classList.add('ots-sidebar-collapsed'); + updateSidebarStateClass(); } collapseBtn.addEventListener('click', function(e) { @@ -52,6 +54,22 @@ localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, nowCollapsed ? 'true' : 'false'); // Update measured sidebar width when collapsed state changes updateSidebarWidth(); + // Recalculate nav offset so items remain below header after collapse + updateSidebarNavOffset(); + updateSidebarStateClass(); + }); + } + + if (expandBtn) { + expandBtn.addEventListener('click', function(e) { + e.preventDefault(); + sidebar.classList.remove('collapsed'); + body.classList.remove('ots-sidebar-collapsed'); + localStorage.setItem(STORAGE_KEYS.sidebarCollapsed, 'false'); + updateSidebarWidth(); + // Recalculate nav offset after expanding + updateSidebarNavOffset(); + updateSidebarStateClass(); }); } @@ -79,18 +97,82 @@ if (!sidebar) return; // If collapsed, use the known collapsed width; otherwise use measured width const collapsed = sidebar.classList.contains('collapsed'); - const base = collapsed ? 88 : sidebar.offsetWidth || sidebar.getBoundingClientRect().width || 240; - const padding = 5; // user requested ~5px padding - const value = Math.max(88, Math.round(base + padding)); + const base = collapsed ? 64 : sidebar.offsetWidth || sidebar.getBoundingClientRect().width || 256; + const padding = 0; + const value = Math.max(64, Math.round(base + padding)); document.documentElement.style.setProperty('--ots-sidebar-width', `${value}px`); } + /** + * Measure the sidebar header bottom and set the top padding of the nav list + * so nav items always begin below the header (logo + buttons). + */ + function updateSidebarNavOffset() { + const sidebar = document.querySelector('.ots-sidebar'); + if (!sidebar) return; + const header = sidebar.querySelector('.sidebar-header'); + const nav = sidebar.querySelector('.sidebar-nav, .ots-sidebar-nav'); + if (!nav) return; + // Calculate header bottom relative to the sidebar top (so it works with scrolling) + const sidebarRect = sidebar.getBoundingClientRect(); + const headerRect = header ? header.getBoundingClientRect() : null; + let offset = 0; + if (headerRect) { + offset = Math.max(0, Math.ceil(headerRect.bottom - sidebarRect.top)); + } else if (header) { + offset = header.offsetHeight || 0; + } + // Add a small gap so nav doesn't touch the header edge + const gap = 8; + const paddingTop = offset > 0 ? offset + gap : ''; + // apply as inline style to ensure it overrides static CSS rules + if (paddingTop) { + // Use setProperty with priority so it overrides stylesheet !important rules + try { + nav.style.setProperty('padding-top', `${paddingTop}px`, 'important'); + } catch (err) { + // fallback + nav.style.paddingTop = `${paddingTop}px`; + } + } else { + // remove inline override + try { + nav.style.removeProperty('padding-top'); + } catch (err) { + nav.style.paddingTop = ''; + } + } + } + + // simple debounce helper + function debounce(fn, wait) { + let t; + return function () { + clearTimeout(t); + t = setTimeout(() => fn.apply(this, arguments), wait); + }; + } + + function updateSidebarStateClass() { + const sidebar = document.querySelector('.ots-sidebar'); + if (!sidebar) return; + const body = document.body; + const isCollapsed = sidebar.classList.contains('collapsed'); + if (!isCollapsed) { + body.classList.add('ots-sidebar-open'); + } else { + body.classList.remove('ots-sidebar-open'); + } + } + /** * Initialize sidebar section collapse/expand functionality */ function initSidebarSectionToggles() { const groupToggles = document.querySelectorAll('.sidebar-group-toggle'); + syncSidebarActiveStates(); + groupToggles.forEach(toggle => { const group = toggle.closest('.sidebar-group'); const submenu = group ? group.querySelector('.sidebar-submenu') : null; @@ -112,6 +194,7 @@ group.classList.toggle('is-open', !isOpen); toggle.setAttribute('aria-expanded', (!isOpen).toString()); submenu.style.display = isOpen ? 'none' : 'block'; + syncSidebarActiveStates(); }); if (caret) { @@ -141,9 +224,32 @@ group.classList.toggle('is-open', !isOpen); target.setAttribute('aria-expanded', (!isOpen).toString()); submenu.style.display = isOpen ? 'none' : 'block'; + syncSidebarActiveStates(); }, true); } + function syncSidebarActiveStates() { + const groups = document.querySelectorAll('.sidebar-group'); + groups.forEach(group => { + const toggle = group.querySelector('.sidebar-group-toggle'); + if (!toggle) return; + + const hasActiveChild = Boolean( + group.querySelector('.sidebar-list.active') || + group.querySelector('.sidebar-list > a.active') + ); + + toggle.classList.toggle('active', hasActiveChild); + + if (hasActiveChild) { + group.classList.add('is-open'); + const submenu = group.querySelector('.sidebar-submenu'); + if (submenu) submenu.style.display = 'block'; + toggle.setAttribute('aria-expanded', 'true'); + } + }); + } + /** * Initialize dropdown menus */ @@ -462,7 +568,13 @@ initChartSafeguard(); // Set initial sidebar width variable and keep it updated updateSidebarWidth(); - window.addEventListener('resize', updateSidebarWidth); + // Set initial nav offset and keep it updated on resize + updateSidebarNavOffset(); + const debouncedUpdateNavOffset = debounce(function() { + updateSidebarNavOffset(); + updateSidebarWidth(); + }, 120); + window.addEventListener('resize', debouncedUpdateNavOffset); } // Wait for DOM to be ready diff --git a/custom/otssignange/views/authed-notification-drawer.twig b/custom/otssignange/views/authed-notification-drawer.twig new file mode 100644 index 0000000..25a7a94 --- /dev/null +++ b/custom/otssignange/views/authed-notification-drawer.twig @@ -0,0 +1,26 @@ +{# + Compact-aware notification drawer override +#} +{% if compact is defined and compact %} + +{% else %} + +{% endif %} diff --git a/custom/otssignange/views/authed-sidebar.twig b/custom/otssignange/views/authed-sidebar.twig index 3bbabe3..d249840 100644 --- a/custom/otssignange/views/authed-sidebar.twig +++ b/custom/otssignange/views/authed-sidebar.twig @@ -11,7 +11,10 @@ OTS Signs - + diff --git a/custom/otssignange/views/authed-theme-topbar.twig b/custom/otssignange/views/authed-theme-topbar.twig index 7b5486e..2590b11 100644 --- a/custom/otssignange/views/authed-theme-topbar.twig +++ b/custom/otssignange/views/authed-theme-topbar.twig @@ -3,6 +3,4 @@ Optional include rendered in authed.twig (top right navbar) Minimal, low-risk addition for verification #} - +{# OTS topbar badge removed #} diff --git a/custom/otssignange/views/authed-user-menu.twig b/custom/otssignange/views/authed-user-menu.twig index 7fc811d..6f48266 100644 --- a/custom/otssignange/views/authed-user-menu.twig +++ b/custom/otssignange/views/authed-user-menu.twig @@ -3,11 +3,19 @@ Based on Xibo CMS default authed-user-menu.twig (master branch) Minimal change: add ots-user-menu class for easy verification #} +{% if compact is defined and compact %} + +{% endif %} diff --git a/custom/otssignange/views/authed.twig b/custom/otssignange/views/authed.twig index 9fd9bf5..43671f0 100644 --- a/custom/otssignange/views/authed.twig +++ b/custom/otssignange/views/authed.twig @@ -22,6 +22,50 @@ #} {% extends "base.twig" %} +{% block headContent %} + + +{% endblock %} + {% block content %} {% set horizontalNav = currentUser.getOptionValue("navigationMenuPosition", theme.getSetting("NAVIGATION_MENU_POSITION", "vertical")) == "horizontal" %} @@ -36,7 +80,10 @@