diff --git a/CUSTOMIZATION.md b/CUSTOMIZATION.md deleted file mode 100644 index f1ff402..0000000 --- a/CUSTOMIZATION.md +++ /dev/null @@ -1,361 +0,0 @@ -# Theme Customization Cookbook - -Quick recipes for common customization tasks in your Xibo CMS theme. - -## 1. Change Primary Brand Color - -**File:** `css/override.css` - -Find the `:root` selector and update: - -```css -:root { - --color-primary: #006bb3; /* Change from blue #2563eb to custom blue */ - --color-primary-dark: #004c80; /* Darker shade for hover/active states */ - --color-primary-light: #1a9ad1; /* Lighter shade for backgrounds */ - --color-primary-lighter: #d4e6f1; /* Very light for highlights */ -} -``` - -This single change affects: -- Header bar background -- Sidebar primary colors -- Buttons, links, and focus states -- Widget title bars - -## 2. Use a Custom Font - -**File:** `css/override.css` - -Replace the `--font-family-base` variable: - -```css -:root { - /* Option A: Google Font (add to in Twig override) */ - --font-family-base: "Inter", "Segoe UI", sans-serif; - - /* Option B: Local font file */ - @font-face { - font-family: "MyCustomFont"; - src: url('../fonts/my-font.woff2') format('woff2'); - } - --font-family-base: "MyCustomFont", sans-serif; -} -``` - -To use Google Fonts, add this line to a Twig template (e.g., in a view override): -```html - - -``` - -## 3. Implement a Company Logo in Header - -**Files:** `css/override.css` (styling), `img/` (asset) - -1. Replace logo files in `custom/otssignange/img/`: - - `xibologo.png` (header logo, ~40x40px) - - `192x192.png` (app icon for app manifest) - - `512x512.png` (splash/bookmarklet icon) - -2. If you need to style the logo more prominently, add to `override.css`: - -```css -.navbar-brand { - display: flex; - align-items: center; - gap: var(--space-2); - padding-left: var(--space-4); -} - -.navbar-brand img { - height: 40px; - width: auto; -} -``` - -## 4. Darken the Sidebar - -**File:** `css/override.css` - -Update sidebar color tokens: - -```css -:root { - --color-surface: #2d3748; /* Darker gray instead of light */ - --color-text-primary: #ffffff; /* White text on dark background */ -} - -#sidebar-wrapper { - background-color: #1a202c; /* Even darker for contrast */ -} - -ul.sidebar .sidebar-list a { - color: #cbd5e1; /* Light gray text */ -} - -ul.sidebar .sidebar-list a:hover { - background-color: #2d3748; /* Slightly lighter on hover */ - color: #ffffff; -} -``` - -## 5. Increase Widget Spacing & Padding - -**File:** `css/override.css` - -Modify spacing tokens to make everything roomier: - -```css -:root { - /* Scale up all spacing */ - --space-4: 1.25rem; /* was 1rem (16px → 20px) */ - --space-6: 2rem; /* was 1.5rem (24px → 32px) */ - --space-8: 2.75rem; /* was 2rem (32px → 44px) */ -} - -.widget { - padding: var(--space-8); /* Uses new token value */ -} -``` - -## 6. Remove Shadows (Flat Design) - -**File:** `css/override.css` - -Set all shadows to none: - -```css -:root { - --shadow-xs: none; - --shadow-sm: none; - --shadow-base: none; - --shadow-md: none; - --shadow-lg: none; - --shadow-xl: none; -} -``` - -## 7. Customize Button Styles - -**File:** `css/override.css` - -Make buttons larger with more rounded corners: - -```css -button, -.btn { - padding: var(--space-4) var(--space-6); /* Increase from var(--space-3) var(--space-4) */ - border-radius: var(--radius-xl); /* Make more rounded */ - font-weight: var(--font-weight-semibold); /* Make text bolder */ - text-transform: uppercase; /* OPTIONAL: Uppercase labels */ - letter-spacing: 0.05em; /* OPTIONAL: Wider letter spacing */ -} -``` - -## 8. Force Light Mode (Disable Dark Mode) - -**File:** `css/override.css` - -Remove the dark mode media query or override it: - -```css -/* Delete or comment out this section: */ -/* -@media (prefers-color-scheme: dark) { - :root { ... } -} -*/ - -/* OR force light mode explicitly: */ -:root { - color-scheme: light; /* Tells browser to not apply dark UI elements */ -} - -/* Force light colors even if system prefers dark */ -@media (prefers-color-scheme: dark) { - :root { - /* Keep all light mode values, don't override */ - } -} -``` - -## 9. Add a Custom Alert Style - -**File:** `css/override.css` - -Append a new alert variant (e.g., for secondary notifications): - -```css -.alert-secondary { - background-color: #e2e8f0; - border-color: #cbd5e1; - color: #334155; -} - -.alert-secondary a { - color: #2563eb; - font-weight: var(--font-weight-semibold); -} -``` - -Use in Xibo: Apply `.alert alert-secondary` class to a notification element. - -## 10. Improve Form Focus States - -**File:** `css/override.css` - -Make focused form inputs more prominent: - -```css -input[type="text"]:focus, -input[type="email"]:focus, -input[type="password"]:focus, -textarea:focus, -select:focus { - outline: none; - border-color: var(--color-primary); - box-shadow: 0 0 0 4px var(--color-primary-lighter), /* Outer glow */ - 0 0 0 1px var(--color-primary); /* Inner border */ - background-color: #fafbff; /* Subtle highlight */ -} -``` - -## 11. Create a Compact Theme Variant - -**File:** `css/override.css` - -Add a utility class for a denser layout: - -```css -.theme-compact { - --space-4: 0.75rem; - --space-6: 1rem; - --font-size-base: 0.875rem; /* Slightly smaller text */ -} - -/* Apply to body or any container */ -body.theme-compact { - /* All tokens inherit new values */ -} -``` - -Then toggle with a Twig override or JS: -```javascript -document.body.classList.toggle('theme-compact'); -``` - -## 12. Modify Widget Title Bar Colors - -**File:** `css/override.css` - -Make widget titles more distinctive: - -```css -.widget .widget-title { - background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark)); - color: var(--color-text-inverse); - text-transform: uppercase; - letter-spacing: 0.05em; - font-size: var(--font-size-sm); - padding: var(--space-6); - border-bottom: 2px solid var(--color-primary-dark); -} -``` - -## 13. Style Table Headers Distinctly - -**File:** `css/override.css` - -Make tables look more modern: - -```css -thead { - background: linear-gradient(to bottom, var(--color-primary-lighter), var(--color-gray-100)); -} - -th { - color: var(--color-primary); - font-weight: var(--font-weight-bold); - text-transform: uppercase; - letter-spacing: 0.05em; - font-size: var(--font-size-sm); - padding: var(--space-6) var(--space-4); - border-bottom: 2px solid var(--color-primary); -} - -tbody tr:nth-child(odd) { - background-color: var(--color-gray-50); -} - -tbody tr:hover { - background-color: var(--color-primary-lighter); -} -``` - -## 14. Enable Full Dark Mode in Browser - -Some users may have `prefers-color-scheme: dark` set. To test locally: - -**Chrome DevTools:** -1. Open DevTools (F12) -2. Ctrl+Shift+P (or Cmd+Shift+P on Mac) -3. Type "rendering" -4. Select "Show Rendering" -5. Scroll to "Prefers color scheme" and select "dark" -6. Refresh page - -**Firefox:** -1. about:config -2. Search for `ui.systemUsesDarkTheme` -3. Set to `1` for dark mode - -## 15. Add Custom Utility Classes - -**File:** `css/override.css` - -Extend the theme with custom utilities at the end: - -```css -/* Custom utilities */ -.text-primary { - color: var(--color-primary); -} - -.bg-primary { - background-color: var(--color-primary); - color: var(--color-text-inverse); -} - -.border-primary { - border-color: var(--color-primary); -} - -.opacity-50 { - opacity: 0.5; -} - -.cursor-pointer { - cursor: pointer; -} - -.no-wrap { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.flex-center { - display: flex; - align-items: center; - justify-content: center; -} -``` - ---- - -**Pro Tips:** -- Always test changes in a staging Xibo instance before deploying to production -- Use browser DevTools to inspect elements and live-edit CSS before making permanent changes -- Keep a backup of your original CSS before making large modifications -- Document any custom changes you make in comments within the CSS file for future reference diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md deleted file mode 100644 index 3780e3f..0000000 --- a/DEPLOYMENT.md +++ /dev/null @@ -1,175 +0,0 @@ -# Deployment Checklist & Quick Start - -## Pre-Deployment Checklist - -- [ ] Review the modern theme changes in [README.md](README.md) -- [ ] Test CSS syntax validation (no errors reported) -- [ ] Backup existing `custom/otssignange/css/override.css` -- [ ] Verify asset paths (logos, preview images) -- [ ] Check browser compatibility requirements -- [ ] Test on your development Xibo instance first -- [ ] Verify dark mode toggle if using system preference -- [ ] Test responsive layout on mobile devices - -## Installation Steps - -### Option A: Direct File Copy - -```bash -# Navigate to your Xibo installation root -cd /path/to/xibo - -# Backup original theme files -cp web/theme/custom/otssignange/css/override.css web/theme/custom/otssignange/css/override.css.backup -cp web/theme/custom/otssignange/css/html-preview.css web/theme/custom/otssignange/css/html-preview.css.backup - -# Copy new theme files -cp /Users/matt/dev/theme/custom/otssignange/css/override.css web/theme/custom/otssignange/css/ -cp /Users/matt/dev/theme/custom/otssignange/css/html-preview.css web/theme/custom/otssignange/css/ -cp /Users/matt/dev/theme/custom/otssignange/css/client.css web/theme/custom/otssignange/css/ - -# Verify files copied -ls -la web/theme/custom/otssignange/css/ -``` - -### Option B: Using Git (if version-controlled) - -```bash -cd /path/to/xibo -git checkout web/theme/custom/otssignange/css/ -# Or manually merge your changes with the new files -``` - -## Post-Deployment Validation - -1. **Clear Xibo Cache** (if applicable): - ```bash - # Xibo may cache CSS—clear if using PHP APC or similar - rm -rf web/uploads/temp/* - ``` - -2. **Verify in Browser**: - - Open Xibo CMS admin interface - - Inspect elements for CSS color changes - - Check Network tab for CSS file loads (should see override.css) - - Verify no CSS errors in browser console - -3. **Test Key Features**: - - [ ] Login page displays correctly - - [ ] Header bar shows primary color - - [ ] Sidebar navigation is styled properly - - [ ] Dashboard widgets render as cards with shadows - - [ ] Links have correct color and hover state - - [ ] Forms have proper focus states (blue outline) - - [ ] Mobile layout: open DevTools (F12) and resize to <640px - - [ ] Sidebar collapses into hamburger menu on mobile - - [ ] Dark mode: open DevTools → Rendering → Prefers color scheme: dark - -4. **Check Asset Loading**: - - Verify `xibologo.png` displays in header - - Check preview splash screen background loads - - Confirm favicon appears in browser tab - -## Rollback Plan - -If issues occur: - -```bash -cd /path/to/xibo - -# Restore backup -cp web/theme/custom/otssignange/css/override.css.backup web/theme/custom/otssignange/css/override.css -cp web/theme/custom/otssignange/css/html-preview.css.backup web/theme/custom/otssignange/css/html-preview.css - -# Optional: Remove new client.css if causing issues -rm web/theme/custom/otssignange/css/client.css - -# Clear cache -rm -rf web/uploads/temp/* - -# Refresh browser (Ctrl+Shift+R for hard refresh) -``` - -## Browser Support - -### Fully Supported (CSS Variables) -- Chrome/Edge 49+ -- Firefox 31+ -- Safari 9.1+ -- Opera 36+ -- Mobile browsers (iOS Safari 9.3+, Chrome Mobile 49+) - -### Partial Support (Fallbacks Recommended) -- IE 11 and below: Not supported - -To add IE11 fallbacks, modify `override.css` (advanced): -```css -/* Fallback for older browsers */ -.widget { - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); /* IE fallback */ - box-shadow: var(--shadow-sm); -} -``` - -## Performance Notes - -- **CSS File Size**: `override.css` is ~35KB (gzipped ~8KB) -- **Variables**: 70+ CSS variables—negligible performance impact -- **Dark Mode**: Uses `prefers-color-scheme` media query (no JavaScript required) -- **Responsive**: Mobile-first approach—efficient layout recalculation - -## Next Steps - -1. **Optional Enhancements**: - - Add SVG icon sprite to `img/` for consistent iconography - - Create Twig view overrides for deeper layout customization - - Implement user-controlled dark mode toggle - -2. **Documentation**: - - See [CUSTOMIZATION.md](CUSTOMIZATION.md) for 15+ customization recipes - - See [README.md](README.md) for full feature documentation - -3. **Testing in CI/CD**: - - Add CSS linter (stylelint) to your build pipeline - - Validate HTML/CSS in staging before production push - -## Support & Troubleshooting - -### Issue: CSS Not Loading -**Solution**: -- Hard refresh browser: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (Mac) -- Check browser console for 404 errors on CSS files -- Verify file permissions: `chmod 644 override.css` - -### Issue: Colors Look Wrong -**Solution**: -- Check if system dark mode is enabled (see Post-Deployment Validation) -- Verify `--color-primary` value in `:root` matches intended brand color -- Test in different browsers - -### Issue: Sidebar Doesn't Collapse on Mobile -**Solution**: -- Verify viewport meta tag is in Xibo's ``: `` -- Check browser DevTools for responsive mode enabled -- Ensure no custom CSS is overriding the media query - -### Issue: Fonts Not Loading -**Solution**: -- Verify system fonts are available (`-apple-system`, `Segoe UI`, etc.) -- If using Google Fonts, check internet connectivity -- Add font-family fallback: `-fallback-font, sans-serif` - ---- - -## Quick Links - -- [Theme README](README.md) — Feature overview and tokens reference -- [Customization Cookbook](CUSTOMIZATION.md) — 15+ customization recipes -- [Xibo Developer Docs](https://account.xibosignage.com/docs/developer/) -- [Config Reference](custom/otssignange/config.php) - ---- - -**Version**: 1.0.0 (Modern) -**Last Updated**: February 2026 -**Status**: Ready for Production diff --git a/DESIGN_SYSTEM.md b/DESIGN_SYSTEM.md deleted file mode 100644 index d4e78a3..0000000 --- a/DESIGN_SYSTEM.md +++ /dev/null @@ -1,391 +0,0 @@ -# Design System Reference Card - -A quick visual and technical reference for the OTS Signage Modern Theme. - -## 🎨 Color Palette - -### Brand Colors -``` ---color-primary #2563eb ████████████ Blue ---color-primary-dark #1d4ed8 ██████████ Darker Blue ---color-primary-light #3b82f6 ██████████████ Lighter Blue ---color-secondary #7c3aed ████████████ Purple -``` - -### Status Colors -``` ---color-success #10b981 ██████████ Green ---color-warning #f59e0b ██████████ Amber ---color-danger #ef4444 ██████████ Red ---color-info #0ea5e9 ██████████ Cyan -``` - -### Gray Scale (Neutral) -``` -Level Color Hex Usage -50 Very Light #f9fafb Backgrounds, light surfaces -100 Light #f3f4f6 Hover states, borders -200 Light Gray #e5e7eb Borders, dividers -300 Gray #d1d5db Form inputs, disabled -400 Gray #9ca3af Placeholder text -500 Medium Gray #6b7280 Secondary text -600 Dark Gray #4b5563 Body text, labels -700 Darker Gray #374151 Headings -800 Very Dark #1f2937 Primary text -900 Darkest #111827 High contrast text -``` - -### Semantic Colors -``` ---color-background #ffffff (dark: #0f172a) ---color-surface #f9fafb (dark: #1e293b) ---color-surface-elevated #ffffff (dark: #334155) ---color-text-primary #1f2937 (dark: #f1f5f9) ---color-text-secondary #6b7280 (dark: #cbd5e1) ---color-text-tertiary #9ca3af (dark: #94a3b8) ---color-border #e5e7eb (dark: #475569) -``` - ---- - -## 📝 Typography Scale - -### Font Family -``` ---font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif ---font-family-mono: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Menlo, Courier, monospace -``` - -### Font Sizes -``` -Size Pixels Usage ---font-size-xs 12px Small labels, badges, captions ---font-size-sm 14px Form hints, table cells, small text ---font-size-base 16px Body text, default size ---font-size-lg 18px Subheadings, callouts ---font-size-xl 20px Section headings ---font-size-2xl 24px Page headings ---font-size-3xl 30px Large headings ---font-size-4xl 36px Main titles -``` - -### Font Weights -``` -Weight Value Usage ---font-weight-normal 400 Body text, regular content ---font-weight-medium 500 Form labels, emphasis ---font-weight-semibold 600 Headings, strong emphasis ---font-weight-bold 700 Major headings, highlights -``` - -### Line Heights -``` -Height Value Usage ---line-height-tight 1.25 Tight headings ---line-height-snug 1.375 Subheadings ---line-height-normal 1.5 Default, body text ---line-height-relaxed 1.625 Large text blocks ---line-height-loose 2 Extra spacing -``` - ---- - -## 📏 Spacing Scale (8px base unit) - -``` -Token Pixels CSS Rem ---space-1 4px 0.25rem Tightest spacing ---space-2 8px 0.5rem Small padding ---space-3 12px 0.75rem Medium-small ---space-4 16px 1rem Standard padding ---space-5 20px 1.25rem Medium spacing ---space-6 24px 1.5rem Default margins ---space-7 28px 1.75rem Large spacing ---space-8 32px 2rem Section spacing ---space-10 40px 2.5rem Large spacing ---space-12 48px 3rem Very large ---space-16 64px 4rem Extra large ---space-20 80px 5rem Massive spacing -``` - -**Usage Example:** -```css -.widget { - padding: var(--space-6); /* 24px all sides */ - margin-bottom: var(--space-8); /* 32px below */ - gap: var(--space-4); /* 16px between items */ -} -``` - ---- - -## 🎭 Shadows (Elevation System) - -``` -Level Shadow Usage -none none No elevation -xs 0 1px 2px 0 rgba(0, 0, 0, 0.05) Subtle depth -sm 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 ... Small cards -base 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px ... Default cards -md 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px ... Medium elevation -lg 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px ... Large hover -xl 0 25px 50px -12px rgba(0, 0, 0, 0.25) Maximum depth -``` - ---- - -## 🔲 Border Radius - -``` -Token Pixels Usage ---radius-none 0px Sharp corners ---radius-sm 4px Minimal rounding ---radius-base 6px Default input fields ---radius-md 8px Standard components ---radius-lg 12px Cards, modals ---radius-xl 16px Large containers ---radius-2xl 24px Extra rounded ---radius-full 9999px Fully rounded (pills, circles) -``` - ---- - -## ⚡ Transitions - -``` -Token Duration Easing Usage ---transition-fast 150ms ease-in-out Hover, quick interactions ---transition-base 200ms ease-in-out Default, UI changes ---transition-slow 300ms ease-in-out Page transitions, major changes -``` - -**Usage:** -```css -a { - transition: color var(--transition-fast); -} - -.widget { - transition: box-shadow var(--transition-base), transform var(--transition-base); -} -``` - ---- - -## 📱 Responsive Breakpoints - -``` -Name Width Use Case -sm 640px Mobile phones (landscape) -md 768px Tablets -lg 1024px Desktop screens -xl 1280px Large desktops -2xl 1536px Ultra-wide displays -``` - -**Usage Pattern (Mobile-First):** -```css -/* Mobile first (default) */ -.widget { - grid-template-columns: 1fr; -} - -/* Tablets and up */ -@media (min-width: 768px) { - .widget { - grid-template-columns: repeat(2, 1fr); - } -} - -/* Desktops and up */ -@media (min-width: 1024px) { - .widget { - grid-template-columns: repeat(3, 1fr); - } -} -``` - ---- - -## 🎯 Component Reference - -### Buttons -```css -.btn { - padding: var(--space-3) var(--space-4); /* 12px × 16px */ - border-radius: var(--radius-md); /* 8px */ - font-weight: var(--font-weight-medium); /* 500 */ - font-size: var(--font-size-base); /* 16px */ -} - -.btn-primary { - background: var(--color-primary); /* #2563eb */ - color: var(--color-text-inverse); /* White */ -} - -.btn-primary:hover { - background: var(--color-primary-dark); /* #1d4ed8 */ -} -``` - -### Form Inputs -```css -input, textarea, select { - background: var(--color-background); /* #ffffff */ - border: 1px solid var(--color-border); /* #e5e7eb */ - border-radius: var(--radius-md); /* 8px */ - padding: var(--space-3) var(--space-4); /* 12px × 16px */ - font-size: var(--font-size-base); /* 16px */ -} - -input:focus { - border-color: var(--color-primary); /* #2563eb */ - box-shadow: 0 0 0 3px var(--color-primary-lighter); -} -``` - -### Cards/Widgets -```css -.widget { - background: var(--color-surface-elevated); /* #ffffff */ - border: 1px solid var(--color-border); /* #e5e7eb */ - border-radius: var(--radius-lg); /* 12px */ - box-shadow: var(--shadow-sm); /* Subtle depth */ - padding: var(--space-6); /* 24px */ -} - -.widget:hover { - box-shadow: var(--shadow-base); /* More elevation */ - transform: translateY(-1px); /* Slight lift */ -} -``` - -### Alerts -```css -.alert { - border-radius: var(--radius-md); /* 8px */ - padding: var(--space-4) var(--space-6); /* 16px × 24px */ - border-left: 4px solid var(--color-success); -} -``` - ---- - -## 🌙 Dark Mode - -All colors automatically switch when `prefers-color-scheme: dark` is detected: - -```css -@media (prefers-color-scheme: dark) { - :root { - --color-background: #0f172a; /* Deep navy */ - --color-surface: #1e293b; /* Slate */ - --color-text-primary: #f1f5f9; /* Near white */ - /* ... other dark mode overrides ... */ - } -} -``` - -**Test in Browser DevTools:** -1. F12 (Open DevTools) -2. Ctrl+Shift+P (Search in Chrome) -3. Type "rendering" -4. Toggle "Prefers color scheme" to dark -5. Page updates instantly - ---- - -## ♿ Accessibility - -### Color Contrast -``` -Element Ratio WCAG Level -Primary text on white 7:1 AAA -Secondary text on white 4.5:1 AA -Links on white 5:1 AA -Buttons 4.5:1 AA -``` - -### Focus Indicators -```css -*:focus-visible { - outline: 2px solid var(--color-primary); /* #2563eb */ - outline-offset: 2px; -} -``` - -### Keyboard Navigation -- All interactive elements are tab-accessible -- Logical tab order maintained -- No keyboard traps -- Focus always visible - ---- - -## 🔧 Using CSS Variables - -### Override in Your Code -```css -/* In your custom CSS, you can override tokens: */ -:root { - --color-primary: #006bb3; /* Your brand blue */ - --font-size-base: 18px; /* Larger default text */ -} - -/* All components using tokens will update automatically */ -``` - -### Reference in Components -```css -.my-component { - background: var(--color-surface); - color: var(--color-text-primary); - padding: var(--space-4); - border-radius: var(--radius-md); - box-shadow: var(--shadow-base); -} -``` - -### With Fallbacks (IE11 compat, optional) -```css -.my-component { - background: #f9fafb; /* Fallback */ - background: var(--color-surface); /* Modern */ - - color: #1f2937; /* Fallback */ - color: var(--color-text-primary); /* Modern */ -} -``` - ---- - -## 📊 Quick Lookup Table - -| Need | Variable | Value | -|------|----------|-------| -| Brand color | `--color-primary` | #2563eb | -| Button padding | `--space-3` + `--space-4` | 12px × 16px | -| Card shadow | `--shadow-base` | 4px 6px -1px rgba... | -| Body text | `--font-size-base` | 16px | -| Heading | `--font-size-2xl` | 24px | -| Default spacing | `--space-6` | 24px | -| Card radius | `--radius-lg` | 12px | -| Focus outline | `--color-primary` | #2563eb | -| Dark background | --color-surface (dark mode) | #1e293b | - ---- - -## 🎓 Pro Tips - -1. **Always use variables** — Don't hardcode colors or sizes -2. **Spacing is 8px-based** — Use `--space-*` for consistency -3. **Test dark mode** — Use DevTools (see above) -4. **Mobile-first queries** — Styles default to mobile, expand on larger screens -5. **Focus states matter** — Never remove `outline` without adding alternative -6. **Semantic colors** — Use `--color-surface` instead of hardcoding #ffffff - ---- - -**Last Updated**: February 2026 -**Theme Version**: 1.0.0 (Modern) -**Xibo Compatibility**: 3.x+ diff --git a/IMPLEMENTATION_COMPLETE.md b/IMPLEMENTATION_COMPLETE.md deleted file mode 100644 index 301b0ef..0000000 --- a/IMPLEMENTATION_COMPLETE.md +++ /dev/null @@ -1,307 +0,0 @@ -# Implementation Summary: OTS Signs Xibo Theme Redesign - -## ✅ Complete Implementation - -Your custom Xibo CMS theme has been fully redesigned and modernized to match the screenshots you provided. All major views and components have been replaced with a contemporary dark-themed UI. - ---- - -## 📋 What Was Changed - -### **View Files (5 files updated)** - -| File | Changes | -|------|---------| -| `authed.twig` | Modern shell layout with fixed sidebar (250px) + main area. SVG icons in topbar, responsive hamburger menu, user avatar dropdown | -| `authed-sidebar.twig` | Reorganized navigation with section dividers, SVG icons for all menu items, user profile card at bottom | -| `dashboard.twig` | 3-column KPI card grid (Displays, Schedules, Users), status panels, quick action grid with 3 action cards | -| `displays.twig` | Two-column layout: left folder tree with 6 folder items, right content with search bar, stat boxes, modern data table | -| `media.twig` | Two-column layout: left folder tree, right media grid with 4 sample images, storage stats, media type badges | - -### **CSS Styling (override.css - ~1,050 lines)** - -**Dark theme colors:** -- Background: `#0f172a` (dark navy) -- Surface: `#1e293b` (slate) -- Elevated: `#334155` (darker slate) -- Primary: `#3b82f6` (bright blue) -- Text: `#f1f5f9` (off-white) - -**Component system:** -- ✅ Sidebar (fixed, collapsible on mobile) -- ✅ Topbar (search, notifications, user menu) -- ✅ KPI cards (3-column grid, hover effects) -- ✅ Badges (success, danger, info, secondary) -- ✅ Panels (full-width and half-width) -- ✅ Tables (striped, hover states) -- ✅ Media grid (3-column, responsive) -- ✅ Buttons (primary, outline, small, ghost) -- ✅ Forms (search input styling) -- ✅ Responsive layout (768px breakpoint) - -### **JavaScript (theme.js - ~120 lines)** - -- Sidebar toggle on mobile -- Dropdown menu interactions -- Search focus states -- Folder item selection feedback -- Mobile viewport detection -- Click-outside-to-close menus - ---- - -## 🎯 Key Visual Changes - -### **Before → After** - -| Element | Before | After | -|---------|--------|-------| -| **Background** | Light white | Dark navy (#0f172a) | -| **Sidebar** | Icon emoji (📊🖥📁) | SVG icons + proper hierarchy | -| **Dashboard KPI** | Simple text cards | Large numbered cards with gradients | -| **Tables** | Basic Bootstrap | Modern dark tables with hover states | -| **Buttons** | Basic styling | Modern gradient primary, outline variants | -| **Media Items** | Text list | Image thumbnail grid with badges | -| **Navigation** | Flat list | Organized sections with dividers | - ---- - -## 📁 Files Modified - -``` -custom/otssignange/ -├── config.php ..................... (unchanged - already correct) -├── css/ -│ ├── override.css ............... ✅ REPLACED (1,050 lines dark theme) -│ ├── client.css ................. (unchanged) -│ └── html-preview.css ........... (unchanged) -├── js/ -│ └── theme.js ................... ✅ UPDATED (interactivity) -└── views/ - ├── authed.twig ................ ✅ REPLACED (shell + topbar) - ├── authed-sidebar.twig ........ ✅ REPLACED (nav sidebar) - ├── dashboard.twig ............. ✅ REPLACED (KPI cards) - ├── displays.twig .............. ✅ REPLACED (two-column) - ├── media.twig ................. ✅ REPLACED (media grid) - ├── index.html ................. (unchanged) - └── layouts/ ................... (inherited from Xibo) -``` - ---- - -## 🚀 Next Steps - -### 1. **Deploy to Xibo CMS** - -If you have Xibo installed locally: - -```bash -# Copy theme to Xibo installation -cp -r /path/to/otssignstheme/custom/otssignange /path/to/xibo-cms/web/theme/custom/ - -# Clear cache in Xibo admin UI: -# Settings → Maintenance → Purge Cache -``` - -### 2. **Enable Theme in Xibo** - -1. Log in to Xibo CMS admin -2. Go to **Settings → Preferences → Themes** -3. Select **"OTS Signs"** from dropdown -4. Click **Save** -5. Refresh browser - -### 3. **Test Pages** - -After enabling, verify these pages render correctly: - -- [ ] **Dashboard** - KPI cards should display (3 columns) -- [ ] **Displays** - Folder tree on left, table on right -- [ ] **Media Library** - Folder tree, image grid with thumbnails -- [ ] **Sidebar** - Toggle on mobile (<768px) -- [ ] **Topbar** - Search, notifications, user menu -- [ ] **Responsive** - Test on mobile/tablet view - -### 4. **Customize (Optional)** - -Edit `/custom/otssignange/css/override.css`: - -**Change primary color (line ~19):** -```css ---color-primary: #3b82f6; /* Change to #8b5cf6 for purple, etc. */ -``` - -**Change sidebar width (line ~58):** -```css -width: 250px; /* Change to 280px, 300px, etc. */ -``` - -**Add company logo (views/authed-sidebar.twig, line ~7):** -Replace `🎯` with: -```twig -{{ app_name }} -``` - ---- - -## 🎨 Design Highlights - -### **Color Palette** -``` -Primary Blue: #3b82f6 (accents, buttons, hover states) -Success Green: #10b981 (online status badges) -Danger Red: #ef4444 (offline status, alerts) -Warning Orange: #f59e0b (warnings) -Info Cyan: #0ea5e9 (information) -Background: #0f172a (main background) -Surface: #1e293b (cards, panels) -Text Primary: #f1f5f9 (headings, main text) -Text Secondary: #cbd5e1 (descriptions, labels) -``` - -### **Typography** -- Font: System fonts (-apple-system, BlinkMacSystemFont, Segoe UI, Roboto) -- Sizes: 12px (xs) → 36px (4xl) -- Weights: 400 (normal) → 700 (bold) -- Line heights: 1.25 (tight) → 2 (loose) - -### **Spacing** -- 8px base unit -- Padding: 8px, 12px, 16px, 20px, 24px, 32px -- Gaps: 12px, 16px, 20px, 24px, 32px -- Margins: Based on spacing scale - -### **Rounded Corners** -- Buttons/inputs: 6px -- Cards: 8px -- Badges: 4px -- Full: 9999px (circles) - -### **Shadows** -- Hover cards: `0 4px 12px rgba(0, 0, 0, 0.15)` -- Dropdowns: `0 20px 25px -5px rgba(0, 0, 0, 0.1)` -- Large modals: `0 25px 50px -12px rgba(0, 0, 0, 0.25)` - ---- - -## 🔧 Technical Details - -### **CSS Architecture** -- **Design tokens:** 50+ CSS variables -- **Component system:** Sidebar, topbar, KPI, panel, badge, button, table, media -- **Responsive:** Mobile-first, 768px breakpoint -- **Accessibility:** Proper focus states, contrast ratios (WCAG AA) - -### **JavaScript Features** -- ES6 IIFE module pattern -- Event delegation -- localStorage for state -- Mobile viewport detection -- No external dependencies - -### **Browser Support** -- ✅ Chrome/Edge (latest 2 versions) -- ✅ Firefox (latest 2 versions) -- ✅ Safari (latest 2 versions) -- ❌ IE11 (CSS Grid not supported) - ---- - -## 📊 Size & Performance - -- **CSS:** ~8 KB (override.css) -- **JavaScript:** ~3 KB (theme.js) -- **Total impact:** ~11 KB additional -- **Load time:** <500ms on typical connection -- **Lighthouse:** 85+ score (with optimized images) - ---- - -## 🐛 Troubleshooting - -### Sidebar toggle not working -→ Check browser console (F12) for JavaScript errors -→ Ensure `theme.js` is loading from: `/theme/custom/otssignange/js/theme.js` - -### Dark theme not applying -→ Clear Xibo cache: Settings → Maintenance → Purge Cache -→ Clear browser cache: Ctrl+Shift+Delete (Windows) / Cmd+Shift+Delete (Mac) - -### Images in media grid not showing -→ Check image URLs are accessible -→ Verify image permissions (644) -→ Test with different image formats (JPEG, PNG, GIF) - -### Search bar styling broken -→ Verify CSS file loaded: check Network tab (F12) -→ Check CSS file size (~8 KB) -→ Look for parse errors in Console (F12) - ---- - -## 📚 Documentation - -Complete documentation available in: -- **[THEME_IMPLEMENTATION.md](./THEME_IMPLEMENTATION.md)** - Full feature guide, customization, troubleshooting -- **[config.php](./custom/otssignange/config.php)** - Theme registration -- **Individual view files** - Twig comments explaining structure - ---- - -## ✨ What Makes This Theme Great - -✅ **Pixel-perfect match** to your screenshots -✅ **Fully responsive** from mobile to 4K -✅ **Modern dark theme** with professional color palette -✅ **SVG icons** for crisp appearance at any size -✅ **Smooth animations** and transitions -✅ **Keyboard accessible** navigation -✅ **Mobile-optimized** sidebar and menus -✅ **Zero external dependencies** (pure CSS/JS) -✅ **Well-commented code** for easy maintenance -✅ **Design token system** for quick customization - ---- - -## 🎓 Learning Resources - -**If you want to modify the theme further:** - -1. **CSS Variables:** Start with `:root` block in override.css (lines 1-25) -2. **Component classes:** Follow `.ots-` naming in CSS -3. **Twig syntax:** Check `views/*.twig` files for template structure -4. **SVG icons:** Edit SVG directly in Twig files or replace with icon font -5. **JavaScript:** Modify `js/theme.js` for new interactions - ---- - -## 🎁 Deliverables Checklist - -- ✅ Dashboard page redesigned (KPI cards, panels, quick actions) -- ✅ Displays page redesigned (folder tree, table, search) -- ✅ Media Library page redesigned (media grid, thumbnails) -- ✅ Sidebar navigation modernized (SVG icons, sections) -- ✅ Topbar created (search, notifications, user menu) -- ✅ Dark theme applied (colors, contrast, shadows) -- ✅ Responsive design (mobile sidebar, flexible layouts) -- ✅ Interactive components (toggles, dropdowns, focus states) -- ✅ Documentation (README, comments, customization guide) -- ✅ Zero breaking changes (Xibo integration intact) - ---- - -## 📞 Support - -If you encounter issues: - -1. **Check console errors:** F12 → Console tab -2. **Review Network tab:** F12 → Network tab (all resources loading?) -3. **Test in incognito:** Browser incognito mode (clear caching) -4. **Verify file paths:** All CSS/JS paths relative to baseUrl -5. **Contact Xibo community:** https://community.xibo.org.uk/ - ---- - -**Theme implementation complete!** 🎉 - -Your OTS Signs theme is ready for deployment. All views match your screenshots with a modern dark interface, responsive design, and interactive components. diff --git a/INDEX.md b/INDEX.md deleted file mode 100644 index bed3938..0000000 --- a/INDEX.md +++ /dev/null @@ -1,267 +0,0 @@ -# OTS Signage Modern Theme - Complete Package - -Welcome! Your Xibo CMS theme has been fully modernized. This file is your starting point. - -## 📚 Documentation Index - -Start here based on what you need: - -### 🚀 First Time? Start Here -**→ [SUMMARY.md](SUMMARY.md)** — 5 min read -- High-level overview of what was implemented -- Before/after comparison -- Quick start (3 steps to deploy) -- What's next suggestions - -### 🎨 Want to Customize? -**→ [CUSTOMIZATION.md](CUSTOMIZATION.md)** — Browse as needed -- 15 ready-made customization recipes -- Change colors, fonts, spacing, etc. -- Code examples for each task -- Advanced tips and tricks - -### 📦 Ready to Deploy? -**→ [DEPLOYMENT.md](DEPLOYMENT.md)** — Follow step-by-step -- Pre-deployment checklist -- Installation instructions (3 methods) -- Post-deployment validation -- Troubleshooting guide -- Rollback procedure - -### 📖 Full Documentation -**→ [README.md](README.md)** — Complete reference -- Feature documentation -- Design tokens reference -- File structure -- Accessibility checklist -- Browser compatibility - ---- - -## 📁 Your Theme Structure - -``` -theme/ (Root) -├── SUMMARY.md ← START HERE (high-level overview) -├── README.md ← Full feature documentation -├── CUSTOMIZATION.md ← Customization recipes (15+) -├── DEPLOYMENT.md ← Deployment & troubleshooting -├── INDEX.md ← This file -└── custom/otssignange/ - ├── config.php (Theme configuration—unchanged) - ├── css/ - │ ├── override.css ✨ MODERNIZED (800 lines) - │ ├── html-preview.css ✨ UPDATED (gradient background) - │ └── client.css ✨ NEW (widget styling) - ├── img/ (Logo assets) - ├── layouts/ (Layout templates) - └── views/ (Twig overrides—empty, ready for custom) -``` - ---- - -## 🎯 What Was Delivered - -### ✅ Design System -- **70+ CSS Variables** — Colors, typography, spacing, shadows, transitions -- **Dark Mode** — Automatic via system preference -- **Responsive Layout** — Mobile-first, 5 breakpoints -- **Accessibility** — WCAG AA color contrast, focus states, keyboard nav - -### ✅ Modern Components -- Header/navbar with brand color -- Sidebar navigation with collapse on mobile -- Card-based widgets with shadows and hover effects -- Form controls with focus rings -- Button variants (primary, secondary, success, danger) -- Alerts/notifications (4 status types) -- Tables with hover states - -### ✅ Documentation -- **4 comprehensive guides** (README, Customization, Deployment, Summary) -- **15+ customization recipes** with code examples -- **Complete API reference** for CSS variables -- **Deployment checklist** with validation steps -- **Troubleshooting guide** for common issues - ---- - -## 🚀 Quick Start (3 Steps) - -### Step 1: Review Changes -```bash -# Look at what changed -ls -la custom/otssignange/css/ -``` -Output: `override.css` (800 lines), `html-preview.css` (updated), `client.css` (new) - -### Step 2: Backup & Deploy -```bash -# Backup original -cp custom/otssignange/css/override.css custom/otssignange/css/override.css.backup - -# Copy to your Xibo installation -cp custom/otssignange/css/* /path/to/xibo/web/theme/custom/otssignange/css/ -``` - -### Step 3: Test -1. Hard refresh browser: Ctrl+Shift+R (or Cmd+Shift+R on Mac) -2. Log into Xibo CMS -3. Verify header, sidebar, and widgets show new styling -4. Test on mobile: Resize to <640px or use device - -✅ Done! Your CMS looks modern now. - ---- - -## 🎨 Popular Customizations - -Want to make it your own? Here are 3 easiest changes: - -### 1. Change Brand Color (5 min) -Edit `custom/otssignange/css/override.css`: -```css -:root { - --color-primary: #006bb3; /* Your color here */ - --color-primary-dark: #004c80; /* Darker version */ - --color-primary-light: #1a9ad1; /* Lighter version */ -} -``` - -### 2. Update Logo (2 min) -Replace these files in `custom/otssignange/img/`: -- `xibologo.png` (header logo) -- `192x192.png` (app icon) -- `512x512.png` (splash icon) - -### 3. Change Font (5 min) -Edit `custom/otssignange/css/override.css`: -```css -:root { - --font-family-base: "Your Font", sans-serif; -} -``` - -**→ See [CUSTOMIZATION.md](CUSTOMIZATION.md) for 12+ more recipes!** - ---- - -## 📊 What's Different - -| Feature | Before | After | -|---------|--------|-------| -| CSS Architecture | Empty hooks | Full token system | -| Colors | Hardcoded | 70+ CSS variables | -| Dark Mode | ❌ None | ✅ Full system support | -| Responsive | Basic | ✅ Mobile-first | -| Components | Minimal | ✅ Complete design system | -| Accessibility | Basic | ✅ WCAG AA compliant | -| Documentation | Minimal | ✅ 4 complete guides | - ---- - -## 🔧 Technical Details - -### Files Modified/Created -- **override.css** — Rewritten (50 → 800 lines) -- **html-preview.css** — Updated with gradient -- **client.css** — New file for widget styling - -### CSS Features Used -- CSS Variables (Custom Properties) -- Media Queries (responsive, dark mode) -- Flexbox & Grid layouts -- Focus-visible for accessibility -- prefers-color-scheme for dark mode - -### Browser Support -✅ All modern browsers (Chrome, Firefox, Safari, Edge, Mobile) -⚠️ IE 11 and below: Not supported (CSS variables required) - ---- - -## 📋 Next Steps - -### Today: Deploy & Test -1. Read [SUMMARY.md](SUMMARY.md) (5 min overview) -2. Follow [DEPLOYMENT.md](DEPLOYMENT.md) (step-by-step) -3. Test on mobile and desktop - -### This Week: Customize -1. Browse [CUSTOMIZATION.md](CUSTOMIZATION.md) recipes -2. Try 1–2 customizations -3. Share with your team - -### This Month: Enhance -1. Add SVG icons to `custom/otssignange/img/` -2. Create Twig view overrides for advanced layout changes -3. Implement user-controlled dark mode toggle - ---- - -## 💬 FAQ - -**Q: Will this work with my Xibo version?** -A: Yes, tested on Xibo 3.x+. CSS-first approach means fewer compatibility issues. - -**Q: Do I need to modify any PHP code?** -A: No. All changes are CSS-based. `config.php` is unchanged. - -**Q: Can I still upgrade Xibo?** -A: Yes! CSS overrides are upgrade-safe. Just re-deploy the theme files after upgrades. - -**Q: How do I test dark mode?** -A: Open DevTools (F12) → Rendering → Prefers color scheme: dark - -**Q: What if something breaks?** -A: See [DEPLOYMENT.md](DEPLOYMENT.md) Rollback section. Takes 2 minutes. - ---- - -## 📞 Support Resources - -- **Customization Help**: [CUSTOMIZATION.md](CUSTOMIZATION.md) — 15+ recipes -- **Deployment Help**: [DEPLOYMENT.md](DEPLOYMENT.md) — Troubleshooting guide -- **Feature Reference**: [README.md](README.md) — Complete documentation -- **Xibo Official Docs**: [account.xibosignage.com/docs/](https://account.xibosignage.com/docs/) - ---- - -## ✅ Quality Checklist - -- [x] CSS syntax validated (no errors) -- [x] Design tokens comprehensive (70+ variables) -- [x] Dark mode fully working -- [x] Responsive on all devices -- [x] WCAG AA accessible -- [x] Documentation complete -- [x] Customization examples provided -- [x] Deployment steps clear -- [x] Rollback procedure documented -- [x] Production ready - ---- - -## 📝 Version & License - -- **Theme**: OTS Signage (Modern) -- **Version**: 1.0.0 -- **Status**: ✅ Production Ready -- **License**: AGPLv3 (per Xibo requirements) -- **Compatibility**: Xibo CMS 3.x+ - ---- - -## 🎉 You're All Set! - -Your Xibo CMS now has a modern, professional theme with a complete design system. - -**Next action**: Read [SUMMARY.md](SUMMARY.md) (5 min) → Deploy (10 min) → Celebrate! 🚀 - ---- - -**Questions?** Check the guides above. Most answers are in [CUSTOMIZATION.md](CUSTOMIZATION.md) or [DEPLOYMENT.md](DEPLOYMENT.md). - -**Ready to go live?** See [DEPLOYMENT.md](DEPLOYMENT.md) for step-by-step instructions. - -**Want to customize?** See [CUSTOMIZATION.md](CUSTOMIZATION.md) for 15+ ready-made recipes. diff --git a/QUICK_REFERENCE.md b/QUICK_REFERENCE.md deleted file mode 100644 index 6e1e997..0000000 --- a/QUICK_REFERENCE.md +++ /dev/null @@ -1,262 +0,0 @@ -# OTS Signs Xibo Theme - Quick Reference Card - -## 🚀 Quick Start (2 minutes) - -### Step 1: Deploy Theme -```bash -# Copy to your Xibo installation (if you have it) -cp -r ~/dev/otssignstheme/custom/otssignange /path/to/xibo-cms/web/theme/custom/ -``` - -### Step 2: Enable in Xibo -1. Login to Xibo CMS -2. Settings → Preferences → Themes -3. Select "OTS Signs" -4. Click Save -5. Refresh page - -### Step 3: Verify -- Dashboard shows 3 KPI cards (Displays, Schedules, Users) -- Sidebar has organized menu sections -- Dark theme applied (navy background) -- Mobile sidebar toggle works on narrow screens - ---- - -## 📂 File Map - -| Path | Size | Purpose | -|------|------|---------| -| `css/override.css` | 20 KB | Dark theme + all components | -| `js/theme.js` | 4.6 KB | Sidebar toggle, dropdowns | -| `views/authed.twig` | 3.4 KB | Main layout shell | -| `views/authed-sidebar.twig` | 5.9 KB | Left navigation sidebar | -| `views/dashboard.twig` | 4.9 KB | Dashboard page | -| `views/displays.twig` | 5.1 KB | Displays management | -| `views/media.twig` | 5.2 KB | Media library | - ---- - -## 🎨 Color Reference - -``` ---color-primary: #3b82f6 Blue (buttons, accents) ---color-background: #0f172a Navy (main background) ---color-surface: #1e293b Slate (cards) ---color-surface-elevated: #334155 Darker slate (headers) ---color-text-primary: #f1f5f9 Off-white (text) ---color-text-secondary: #cbd5e1 Gray (labels) ---color-success: #10b981 Green (online status) ---color-danger: #ef4444 Red (offline status) -``` - ---- - -## 🔧 Quick Customization - -### Change Primary Color -**File:** `css/override.css` (line ~19) -```css ---color-primary: #3b82f6; /* Change me! */ ---color-primary-dark: #1d4ed8; ---color-primary-light: #60a5fa; -``` - -**Suggested colors:** -- Purple: `#8b5cf6` / `#7c3aed` / `#6d28d9` -- Red: `#ef4444` / `#dc2626` / `#b91c1c` -- Green: `#10b981` / `#059669` / `#047857` -- Orange: `#f97316` / `#ea580c` / `#c2410c` - -### Change Sidebar Width -**File:** `css/override.css` (line ~58) -```css -.ots-sidebar { - width: 250px; /* Change to 280px, 300px, etc. */ -} -.ots-main { - margin-left: 250px; /* Must match sidebar width */ -} -``` - -### Add Company Logo -**File:** `views/authed-sidebar.twig` (line ~7) -```twig - -🎯 - - -{{ app_name }} -``` - ---- - -## ✅ Component Classes - -### Layout -- `.ots-shell` - Main wrapper -- `.ots-sidebar` - Left navigation -- `.ots-topbar` - Top header -- `.ots-content` - Main content area -- `.ots-footer` - Footer - -### Dashboard -- `.kpi-section` - KPI cards container -- `.kpi-card` - Single KPI card -- `.dashboard-panels` - Panel grid -- `.panel` - Card component -- `.action-cards` - Quick actions grid - -### Displays/Media -- `.two-column-layout` - Layout wrapper -- `.left-panel` - Sidebar panel -- `.content-panel` - Main content -- `.folder-tree` - Folder list -- `.media-grid` - Image grid - -### Common -- `.btn`, `.btn-primary`, `.btn-outline`, `.btn-sm` -- `.badge`, `.badge-success`, `.badge-danger` -- `.table`, `.table-striped` -- `.text-muted`, `.text-xs` - ---- - -## 🐛 Common Issues - -| Issue | Solution | -|-------|----------| -| Dark theme not showing | Clear cache: Settings → Maintenance → Purge Cache | -| Sidebar toggle not working | Check browser console (F12) for errors | -| Images not showing in media grid | Verify image URLs are accessible, check permissions | -| Mobile sidebar stuck off-screen | Test in new browser tab, clear localStorage | -| CSS not loading | Check file exists at `web/theme/custom/otssignange/css/override.css` | - ---- - -## 📱 Responsive Breakpoints - -```css -Mobile: max-width: 640px -Tablet: 641px - 768px -Desktop: 769px+ - -Key behavior: -- Sidebar: hidden/drawer on mobile, fixed on desktop -- Topbar: flex-column on mobile, flex-row on desktop -- Grids: 1 column on mobile, 2+ columns on desktop -``` - ---- - -## 🎯 CSS Variables Quick Edit - -**File:** `css/override.css` top section - -Change any of these to customize the entire theme: - -```css ---color-primary: #3b82f6; /* Primary brand color */ ---color-background: #0f172a; /* Main background */ ---color-surface: #1e293b; /* Card background */ ---color-border: #475569; /* Divider lines */ ---color-text-primary: #f1f5f9; /* Main text */ ---color-text-secondary: #cbd5e1; /* Secondary text */ ---color-text-tertiary: #94a3b8; /* Muted text */ -``` - ---- - -## 🔗 Important Paths - -Relative to Xibo root: - -``` -/web/theme/custom/otssignange/ -├── config.php -├── css/override.css ← Main styling -├── js/theme.js ← Interactions -└── views/ - ├── authed.twig ← Main shell - ├── authed-sidebar.twig ← Sidebar nav - ├── dashboard.twig - ├── displays.twig - └── media.twig -``` - ---- - -## 📊 File Sizes - -- `override.css`: 20 KB -- `theme.js`: 4.6 KB -- `authed.twig`: 3.4 KB -- `authed-sidebar.twig`: 5.9 KB -- `dashboard.twig`: 4.9 KB -- `displays.twig`: 5.1 KB -- `media.twig`: 5.2 KB - -**Total theme size:** ~49 KB - ---- - -## 🎓 Code Examples - -### Use in Twig -```twig - -
-
-

Displays

-
-
-
{{ count }}
-
-
- - -Add Display - - -Online -``` - -### CSS Variables in Custom Styles -```css -.my-component { - background: var(--color-surface); - color: var(--color-text-primary); - border: 1px solid var(--color-border); - border-radius: var(--radius-md); - padding: var(--space-4); - transition: all var(--transition-base); -} -``` - ---- - -## 🚨 Before Modifying - -1. **Backup original:** `cp override.css override.css.backup` -2. **Test locally:** Use browser DevTools to test changes -3. **Clear cache:** After any CSS/JS change, purge Xibo cache -4. **Check mobile:** Test responsive changes at 375px width - ---- - -## 📞 Need Help? - -Check these files in order: - -1. `IMPLEMENTATION_COMPLETE.md` - What was changed -2. `THEME_IMPLEMENTATION.md` - Full documentation -3. `views/authed.twig` - Template structure -4. `css/override.css` - Component styling -5. `js/theme.js` - Interactivity - ---- - -**Last Updated:** February 4, 2026 -**Theme Version:** 1.0.0 -**Xibo Compatibility:** v4.0+ diff --git a/README.md b/README.md deleted file mode 100644 index 5897d58..0000000 --- a/README.md +++ /dev/null @@ -1,266 +0,0 @@ -# OTS Signage - Modern Xibo CMS Theme - -A modernized theme for Xibo CMS that brings contemporary UI/UX patterns, improved accessibility, and a comprehensive design token system. - -## What's New - -### 🎨 Design Tokens System -- **Color palette**: 10 semantic colors + 9-step gray scale with light/dark mode support -- **Typography**: System font stack, 8-step type scale, 5 font weights -- **Spacing**: 8px-based scale (0–80px) for consistent margins and padding -- **Elevation**: Shadow system with 6 levels for depth perception -- **Radius**: Border radius scale from sharp to full-rounded -- **Transitions**: Predefined animation durations for consistency - -### 🌙 Dark Mode Support -- Automatic dark mode via `prefers-color-scheme` media query -- Token overrides for dark theme built-in -- No additional JavaScript required for system preference - -### 📱 Responsive Layout -- Mobile-first responsive grid system -- Sidebar collapses into hamburger on screens ≤768px -- Flexible widget containers with auto-fit grid -- Optimized breakpoints (sm: 640px, md: 768px, lg: 1024px, xl: 1280px) - -### 🎯 Modern Components -- **Cards**: Widgets styled as elevated cards with shadows and hover effects -- **Typography**: Improved hierarchy with modern font sizing -- **Forms**: Refined input styling with focus ring system -- **Buttons**: Consistent button styles with proper color variants -- **Alerts**: Color-coded alerts (success, danger, warning, info) -- **Tables**: Clean table styling with hover states - -### ♿ Accessibility Improvements -- WCAG AA compliant color contrasts (verified on primary palette) -- Focus-visible outlines for keyboard navigation -- Semantic HTML structure preserved -- Proper heading hierarchy support -- Form labels and ARIA attributes maintained - -### 📊 Widget Styling -- Card-based widget design with consistent shadows -- Hover lift effect for interactivity feedback -- Proper spacing and padding via tokens -- Title bars with semantic color distinction -- Dashboard grid with responsive columns - -## File Structure - -``` -custom/otssignange/ -├── config.php # Theme configuration (unchanged) -├── css/ -│ ├── override.css # Main theme styles + design tokens (UPDATED) -│ ├── html-preview.css # Preview splash screen (UPDATED) -│ └── client.css # Widget embedding styles (NEW) -├── img/ -│ ├── favicon.ico -│ ├── 192x192.png -│ ├── 512x512.png -│ └── xibologo.png -├── layouts/ -│ └── default-layout.zip -└── views/ - └── index.html # Empty - ready for Twig overrides if needed -``` - -## CSS Variables Reference - -### Colors -```css -/* Brand Colors */ ---color-primary: #2563eb ---color-primary-dark: #1d4ed8 ---color-primary-light: #3b82f6 ---color-secondary: #7c3aed - -/* Semantic Colors */ ---color-success: #10b981 ---color-warning: #f59e0b ---color-danger: #ef4444 ---color-info: #0ea5e9 - -/* Grays (50–900) */ ---color-gray-50, 100, 200, 300, 400, 500, 600, 700, 800, 900 - -/* Semantic Semantic */ ---color-background: #ffffff (dark: #0f172a) ---color-surface: #f9fafb (dark: #1e293b) ---color-text-primary: #1f2937 (dark: #f1f5f9) ---color-text-secondary: #6b7280 (dark: #cbd5e1) ---color-border: #e5e7eb (dark: #475569) -``` - -### Typography -```css ---font-family-base: System font stack ---font-size-xs to 4xl: 0.75rem → 2.25rem ---font-weight-normal/medium/semibold/bold: 400–700 ---line-height-tight/snug/normal/relaxed/loose: 1.25–2 -``` - -### Spacing (8px base) -```css ---space-1 through --space-20: 0.25rem → 5rem -``` - -### Elevation & Borders -```css ---shadow-none/xs/sm/base/md/lg/xl ---radius-none/sm/base/md/lg/xl/2xl/full ---transition-fast/base/slow: 150ms–300ms -``` - -## Customization Guide - -### Change Brand Color -Edit `override.css` root selector: -```css -:root { - --color-primary: #your-color; - --color-primary-dark: #darker-shade; - --color-primary-light: #lighter-shade; -} -``` - -### Update Typography -Modify font family and scale: -```css -:root { - --font-family-base: "Your Font", sans-serif; - --font-size-base: 1.125rem; /* Increase base from 16px to 18px */ -} -``` - -### Adjust Spacing -Change the base spacing multiplier (affects all --space-* variables): -```css -/* If you prefer tighter spacing, reduce proportionally */ ---space-4: 0.75rem; /* was 1rem */ ---space-6: 1.125rem; /* was 1.5rem */ -``` - -### Implement Dark Mode Toggle (Optional) -If you want a user-controlled toggle instead of system preference: - -1. Add a button to toggle theme in a Twig view override -2. Store preference in localStorage -3. Add to `override.css`: -```css -[data-theme="dark"] { - --color-background: #0f172a; - --color-surface: #1e293b; - /* etc. */ -} -``` - -## Files Modified - -### `css/override.css` -**Changes:** -- Replaced empty CSS hooks with comprehensive design token system -- Added global styles using tokens -- Implemented responsive header/sidebar with mobile hamburger -- Added widget card styling with elevation effects -- Included form, button, table, and alert component styles -- Added responsive grid utilities and spacing classes - -**Size:** ~800 lines (was ~50 lines) - -### `css/html-preview.css` -**Changes:** -- Updated splash screen with gradient background matching brand color -- Added preview widget styling for consistency -- Improved visual fidelity with shadows and spacing - -### `css/client.css` (NEW) -**Purpose:** -- Styles HTML/embedded widgets on displays -- Uses mirrored design tokens for consistency -- Includes typography, form, button, and alert styles -- Supports dark mode with prefers-color-scheme - -## Deployment Notes - -### Before Deploying to Production - -1. **Test in your Xibo CMS instance:** - - Log in and verify admin UI appearance - - Check sidebar navigation, widgets, and forms - - Test on mobile (resize browser or use device) - - Verify color contrast with a tool like WAVE or aXe - -2. **Verify Asset Paths:** - - Ensure `preview/img/xibologo.png` is accessible - - Check that logo files in `img/` are served correctly - - Validate CSS paths resolve in your installation - -3. **Browser Compatibility:** - - CSS variables supported in all modern browsers (Chrome 49+, Firefox 31+, Safari 9.1+, Edge 15+) - - For legacy browser support, add CSS fallbacks (not included) - -4. **Backup:** - - Keep a backup of original `override.css` before deployment - -### Installation - -1. Copy the updated theme files to `/web/theme/custom/otssignange/` -2. Clear Xibo CMS cache if applicable -3. Reload the CMS in your browser -4. No database changes or CMS restart required - -### Rollback - -If issues occur, revert to backup: -```bash -cp override.css.backup override.css -# Refresh browser cache -``` - -## Responsive Breakpoints - -| Breakpoint | Width | Use Case | -|-----------|-------|----------| -| **Mobile** | < 640px | Single column layout | -| **Tablet** | 640–768px | Sidebar collapse | -| **Desktop** | 768–1024px | 2–3 column grids | -| **Large** | 1024–1280px | Multi-column dashboards | -| **Ultra** | ≥ 1280px | Full-width content | - -## Accessibility Checklist - -- [x] WCAG AA color contrast (4.5:1 text, 3:1 components) -- [x] Focus visible outlines (2px solid primary color) -- [x] Keyboard navigation preserved -- [x] Semantic HTML maintained -- [x] Form labels and ARIA attributes respected -- [x] No color-only information conveyed -- [x] Sufficient touch target sizes (≥44px) - -## Future Enhancements - -Suggested follow-ups: -- Create Twig view overrides for `authed.twig` and `authed-sidebar.twig` for DOM-level layout improvements -- Add SVG icon sprite for consistent iconography across CMS -- Implement animated transitions for sidebar collapse -- Add data visualization component styles (charts, graphs) -- Create documentation portal in Xibo for custom branding - -## Support & Questions - -For documentation on Xibo CMS theming: -- [Xibo Developer Docs](https://account.xibosignage.com/docs/developer/) -- Theme config reference: `config.php` -- Available Twig overrides: Check `/web/theme/default/views/` in your Xibo installation - -## License - -This theme extends Xibo CMS and is subject to AGPLv3 license per Xibo requirements. -Xibo is © 2006–2021 Xibo Signage Ltd. - ---- - -**Theme Version:** 1.0.0 (Modern) -**Last Updated:** February 2026 -**Xibo CMS Compatibility:** 3.x and above diff --git a/SUMMARY.md b/SUMMARY.md deleted file mode 100644 index cf2d981..0000000 --- a/SUMMARY.md +++ /dev/null @@ -1,260 +0,0 @@ -# Implementation Summary - -## What Was Done - -Your Xibo CMS OTS Signage theme has been fully modernized with a comprehensive design system. Here's what's been delivered: - -### 📁 Files Created/Updated - -#### 1. **`css/override.css`** (Main Theme File) -- **Status**: ✅ Complete rewrite -- **Lines**: ~800 (was ~50) -- **Contains**: - - 70+ CSS variables defining colors, typography, spacing, shadows, transitions - - Dark mode support via `prefers-color-scheme` - - Global typography styles with proper hierarchy - - Modern header/navbar styling - - Responsive sidebar with mobile hamburger menu - - Card-based widget styling with elevation effects - - Form controls with focus rings - - Button component variants (primary, secondary, success, danger) - - Alert/notification styling - - Table styling with hover effects - - Responsive grid utilities - - Accessibility features (focus-visible, color contrast) - -#### 2. **`css/html-preview.css`** (Preview Styling) -- **Status**: ✅ Updated -- **Changes**: - - Gradient background matching brand color - - Preview widget component styling - - Consistent with main theme tokens - -#### 3. **`css/client.css`** (Widget Styling) -- **Status**: ✅ New file created -- **Purpose**: Styles HTML/embedded widgets on displays -- **Includes**: Typography, forms, buttons, tables, alerts, dark mode support - -#### 4. **`README.md`** (Full Documentation) -- **Status**: ✅ Comprehensive guide created -- **Contents**: - - Feature overview (design tokens, dark mode, responsive, accessibility) - - File structure reference - - CSS variable reference guide - - Customization basics - - Deployment notes - - Responsive breakpoints - - Accessibility compliance checklist - -#### 5. **`CUSTOMIZATION.md`** (Recipes & Cookbook) -- **Status**: ✅ 15 customization recipes provided -- **Examples**: - - Change brand colors - - Update typography - - Adjust spacing - - Implement dark mode toggle - - Create custom alert styles - - And 10 more... - -#### 6. **`DEPLOYMENT.md`** (Quick Start & Checklist) -- **Status**: ✅ Ready for production -- **Includes**: - - Pre-deployment checklist - - Step-by-step installation (3 methods) - - Post-deployment validation - - Rollback procedures - - Browser support matrix - - Troubleshooting guide - ---- - -## 🎯 Key Features Implemented - -### Design System -| Element | Details | -|---------|---------| -| **Color Palette** | 10 semantic colors + 9-step gray scale | -| **Typography** | System font stack, 8-step type scale, 5 weights | -| **Spacing** | 8px-based scale (13 units from 4px to 80px) | -| **Shadows** | 6-level elevation system for depth | -| **Radius** | 8 border radius options (sharp to full-round) | -| **Transitions** | 3 predefined animation speeds | - -### Responsive Design -- Mobile-first approach -- Sidebar collapses into hamburger menu on screens ≤768px -- Flexible grid layouts with auto-fit -- 5 breakpoints (sm, md, lg, xl, 2xl) - -### Dark Mode -- Automatic via system preference (`prefers-color-scheme: dark`) -- No JavaScript required -- All colors adjusted for readability in dark mode - -### Accessibility -- WCAG AA color contrast compliance -- Focus-visible outlines for keyboard navigation -- Semantic HTML preserved -- Form labels and ARIA attributes maintained -- Proper heading hierarchy support - -### Components -- Header/navbar with brand color -- Sidebar navigation with active states -- Cards with elevation and hover effects -- Forms with focus rings -- Buttons (4 color variants) -- Alerts (4 status types) -- Tables with hover states -- Modal & dialog support ready - ---- - -## 📊 Comparison: Before vs. After - -| Aspect | Before | After | -|--------|--------|-------| -| **CSS Architecture** | Empty hooks | Comprehensive token system | -| **Color System** | No tokens | 70+ CSS variables | -| **Dark Mode** | Not supported | Full system preference support | -| **Responsive** | Basic | Mobile-first with breakpoints | -| **Components** | Minimal | Complete design system | -| **Accessibility** | Baseline | WCAG AA compliant | -| **Documentation** | Minimal | 4 comprehensive guides | -| **Customization** | Limited | 15+ recipes provided | - ---- - -## 🚀 Quick Start - -### 1. Review the Changes -```bash -# Check modified files -ls -la custom/otssignange/css/ -# Output: -# override.css (800 lines, modernized) -# html-preview.css (updated with gradient) -# client.css (new, for widgets) -``` - -### 2. Backup Current Theme -```bash -cp custom/otssignange/css/override.css custom/otssignange/css/override.css.backup -``` - -### 3. Deploy to Xibo -Copy the three CSS files to your Xibo installation: -```bash -cp custom/otssignange/css/* /path/to/xibo/web/theme/custom/otssignange/css/ -``` - -### 4. Clear Cache & Test -- Hard refresh browser: Ctrl+Shift+R -- Log into Xibo CMS -- Verify header, sidebar, and widgets display with new styling -- Test on mobile: Resize browser to <640px or use device - -### 5. Customize (Optional) -See [CUSTOMIZATION.md](CUSTOMIZATION.md) for 15 ready-made recipes to adjust colors, fonts, spacing, etc. - ---- - -## 📋 What's Next? - -### Immediate (Try It) -1. Deploy to your Xibo test instance -2. Verify appearance across devices -3. Test color contrast with WAVE or aXe tools -4. Customize brand colors if needed (see CUSTOMIZATION.md) - -### Short Term (Enhancement) -- Add SVG icon sprite to `img/` for better iconography -- Create Twig view overrides for header/sidebar layout customization -- Implement user-controlled dark mode toggle - -### Medium Term (Polish) -- Add data visualization component styles (charts, graphs) -- Create mini documentation portal within Xibo for custom branding -- Add animations/transitions for sidebar collapse, form interactions - -### Long Term (Expansion) -- Style custom Xibo modules/extensions -- Create light/dark theme variants as separate CSS files -- Build theme generator tool for rapid customization - ---- - -## 🔄 Rollback (If Needed) - -If any issues occur after deployment: - -```bash -# Restore backup -cp custom/otssignange/css/override.css.backup custom/otssignange/css/override.css - -# Remove new file if problematic -rm custom/otssignange/css/client.css - -# Clear cache and refresh -# (Clear Xibo cache or hard-refresh browser) -``` - ---- - -## 📚 Documentation Files - -| File | Purpose | -|------|---------| -| **README.md** | Full feature documentation, tokens reference, accessibility checklist | -| **CUSTOMIZATION.md** | 15+ customization recipes (change colors, fonts, spacing, etc.) | -| **DEPLOYMENT.md** | Installation steps, validation checklist, troubleshooting | -| **SUMMARY.md** | This file—high-level overview | - ---- - -## ✅ Quality Assurance - -- [x] CSS syntax validated (no errors) -- [x] Design tokens comprehensive (70+ variables) -- [x] Dark mode fully implemented -- [x] Responsive breakpoints correct -- [x] Color contrast WCAG AA compliant -- [x] Accessibility features included -- [x] Documentation complete -- [x] Customization recipes provided -- [x] Deployment guide created -- [x] Rollback procedure documented - ---- - -## 🎓 Learning Resources - -- **CSS Variables**: [MDN - CSS Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/--*) -- **Dark Mode**: [MDN - prefers-color-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) -- **Responsive Design**: [MDN - Responsive Design](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Responsive_Design) -- **Xibo Theming**: [Xibo Developer Docs](https://account.xibosignage.com/docs/developer/) - ---- - -## 📝 Version Info - -- **Theme Name**: OTS Signage (Modern) -- **Version**: 1.0.0 -- **Status**: Production Ready -- **Last Updated**: February 2026 -- **Xibo Compatibility**: 3.x and above -- **Browser Support**: All modern browsers (Chrome, Firefox, Safari, Edge, Mobile) - ---- - -## 💡 Support - -For issues or questions: -1. Check [DEPLOYMENT.md](DEPLOYMENT.md) troubleshooting section -2. Review [CUSTOMIZATION.md](CUSTOMIZATION.md) for common tasks -3. Consult [README.md](README.md) for feature details -4. Refer to [Xibo Developer Docs](https://account.xibosignage.com/docs/developer/) - ---- - -**Status**: ✅ Implementation Complete & Ready for Deployment diff --git a/THEME_IMPLEMENTATION.md b/THEME_IMPLEMENTATION.md deleted file mode 100644 index 7b3ce10..0000000 --- a/THEME_IMPLEMENTATION.md +++ /dev/null @@ -1,396 +0,0 @@ -# OTS Signs Xibo CMS Theme - Implementation Guide - -## Overview - -This is a modern, dark-themed Xibo CMS theme with a professional UI redesign. The theme replaces all major views (dashboard, displays, media library) with contemporary components, a responsive sidebar navigation, and a modern topbar layout. - -**Theme Name:** OTS Signs -**Theme Directory:** `custom/otssignange/` -**Target Xibo Version:** Latest stable (v4.0+) -**License:** AGPL-3.0 (Xibo) - ---- - -## Implementation Summary - -### ✅ Completed Changes - -#### 1. **Views Updated** -- **authed.twig** - Modern shell with fixed sidebar + main layout, SVG icons, responsive topbar -- **authed-sidebar.twig** - Enhanced sidebar with organized nav sections, user profile card, SVG icons -- **dashboard.twig** - KPI cards, status panels, quick action grid -- **displays.twig** - Two-column layout with folder tree, modern table, stat boxes -- **media.twig** - Media grid with image previews, storage stats, folder structure - -#### 2. **CSS Styling (override.css)** -- **Dark theme colors:** Navy backgrounds (#0f172a), slate surfaces (#1e293b), blue accents (#3b82f6) -- **Component system:** Sidebar, topbar, KPI cards, badges, buttons, tables, media grid -- **Responsive design:** Mobile breakpoints at 768px, flexible grid layouts -- **Transitions & effects:** Smooth hover states, focus states, elevation shadows -- **CSS variables:** Comprehensive design token system for easy customization - -#### 3. **JavaScript Functionality (theme.js)** -- Sidebar toggle with mobile responsiveness -- Dropdown menu interactions (user menu) -- Search form focus states -- Page-specific interactions (folder selection, media item interaction) -- Mobile viewport detection and adaptive behavior - ---- - -## File Structure - -``` -custom/otssignange/ -├── config.php # Theme registration & configuration -├── css/ -│ ├── client.css # HTML widget styling (mirrored design tokens) -│ ├── override.css # Main dark theme & component styles (1000+ lines) -│ └── html-preview.css # Preview mode styles -├── js/ -│ └── theme.js # Interactive components & sidebar toggle -├── img/ # Placeholder for logo/icons -├── views/ -│ ├── authed.twig # Main shell (sidebar + topbar + main area) -│ ├── authed-sidebar.twig # Left navigation sidebar -│ ├── dashboard.twig # Dashboard page with KPI cards -│ ├── displays.twig # Displays management page -│ ├── media.twig # Media library page -│ ├── index.html # Fallback page -│ └── layouts/ # Layout templates (inherited from Xibo core) -└── README.md # This file - -``` - ---- - -## Key Features - -### 🎨 **Dark Theme** -- **Colors:** Dark navy backgrounds, slate panels, bright blue primary accent -- **Contrast:** WCAG AA compliant (high contrast text) -- **Consistent:** Applied across all pages and components - -### 📱 **Responsive Layout** -- **Sidebar:** Fixed on desktop, slide-in drawer on mobile (<768px) -- **Topbar:** Responsive search bar, user menu, notification button -- **Content:** Flexible grids that stack on smaller screens -- **Tables:** Horizontal scroll on mobile, proper alignment - -### 🎯 **Modern Components** -- **KPI Cards:** Display key metrics (displays online, schedules, users) -- **Panels:** Two-column layouts for displays & media sections -- **Tables:** Striped rows, hover states, action menus -- **Media Grid:** Thumbnail preview cards with metadata -- **Badges:** Status indicators (Online/Offline, Success/Danger/Info) -- **Buttons:** Primary (blue), outline, small, and ghost variants - -### ⚡ **Interactivity** -- Sidebar toggle on mobile -- Dropdown menus (user profile menu) -- Folder/item selection with visual feedback -- Search input focus states -- Smooth transitions (150-300ms) - ---- - -## Installation & Deployment - -### Option 1: Local Development (Xibo CMS Installed) - -1. **Navigate to theme directory:** - ```bash - cd /path/to/xibo-cms/web/theme/custom/ - ``` - -2. **Copy the theme folder:** - ```bash - cp -r /path/to/otssignstheme/custom/otssignange ./ - ``` - -3. **Enable in Xibo Admin:** - - Go to **Settings → Preferences → Themes** - - Select "OTS Signs" from the dropdown - - Click **Save** - -4. **Clear caches:** - - Go to **Settings → Maintenance → Purge Cache** - - Refresh the page - -### Option 2: Package for Distribution - -Create a ZIP file for sharing: - -```bash -cd /path/to/otssignstheme/custom/ -zip -r ots-signs-theme.zip otssignange/ -``` - -**Distribution contents:** -- `otssignange/` - Full theme directory -- `INSTALLATION.txt` - Setup instructions -- `LICENSE` - AGPL-3.0 license - ---- - -## Customization - -### Change Primary Color - -Edit `css/override.css`, line ~10: - -```css ---color-primary: #3b82f6; /* Change from blue to your color */ ---color-primary-dark: #1d4ed8; ---color-primary-light: #60a5fa; -``` - -**Hex color suggestions:** -- Purple: `#8b5cf6` -- Red: `#ef4444` -- Green: `#10b981` -- Orange: `#f97316` - -### Adjust Sidebar Width - -Edit `css/override.css`, line ~58: - -```css -.ots-sidebar { - width: 250px; /* Change to 280px, 300px, etc. */ -} - -.ots-main { - margin-left: 250px; /* Must match sidebar width */ -} -``` - -### Customize Fonts - -Edit `css/override.css`: - -```css -/* Base font family */ ---font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; - -/* Monospace (for code) */ ---font-family-mono: 'Monaco', monospace; -``` - -### Add Company Logo - -1. Place logo at `img/logo.png` (recommended: 40x40px, PNG/SVG) -2. Edit `views/authed-sidebar.twig`, line ~7: - ```twig - 🎯 - {{ app_name }} - ``` - ---- - -## Browser Compatibility - -- **Chrome/Edge:** ✅ Full support (latest 2 versions) -- **Firefox:** ✅ Full support (latest 2 versions) -- **Safari:** ✅ Full support (latest 2 versions) -- **IE11:** ❌ Not supported (CSS Grid, CSS Variables not available) - -**CSS Features Used:** -- CSS Grid (layout) -- CSS Flexbox (alignment) -- CSS Variables (theming) -- CSS Transitions (animations) -- SVG inline (icons) - ---- - -## Common Issues & Troubleshooting - -### Issue: Sidebar not toggling on mobile -**Solution:** Ensure `theme.js` is loaded. Check browser console for errors: -```bash -Press F12 → Console tab → Look for red errors -``` - -### Issue: Dark theme not applying -**Solution:** Clear Xibo cache and browser cache: -1. **Xibo:** Settings → Maintenance → Purge Cache -2. **Browser:** Ctrl+Shift+Delete (Windows) or Cmd+Shift+Delete (Mac) - -### Issue: Images in media grid not showing -**Solution:** Verify image URLs are accessible. Check: -1. File permissions (644 for files, 755 for directories) -2. Image format is supported (JPEG, PNG, GIF, WebP) - -### Issue: Search bar styling broken -**Solution:** Ensure `override.css` is fully loaded. Check: -1. CSS file size: should be ~8-10 KB -2. No CSS parse errors in DevTools (F12 → Console) - ---- - -## Development & Debugging - -### Enable Debug Mode - -Edit `config.php`: - -```php -// Add at top of file -define('DEBUG', true); -``` - -### View Generated HTML - -In browser, right-click → **Inspect Element** to see: -- DOM structure -- Applied CSS classes -- CSS rules (with file/line) -- SVG icons rendering - -### Test Responsive Design - -In browser DevTools (F12): -1. Click **Toggle Device Toolbar** (or Ctrl+Shift+M) -2. Select mobile device (iPhone 12, Pixel 5, etc.) -3. Test sidebar toggle, search, menus - ---- - -## Performance Metrics - -- **CSS file size:** ~8 KB (minified) -- **JS file size:** ~3 KB (minified) -- **Load time (typical):** <500ms additional -- **Lighthouse score:** 85+ (with proper images) - ---- - -## Xibo CMS Integration Notes - -### Theme Hooks (Twig Blocks) - -The theme extends `base.twig` and overrides: - -- `{% block head %}` - Link to custom CSS -- `{% block htmlTag %}` - Dark mode attribute -- `{% block body %}` - Custom shell structure -- `{% block header %}` - Topbar -- `{% block content %}` - Main content area -- `{% block footer %}` - Footer -- `{% block scripts %}` - Include theme.js - -### Available Twig Variables - -In views, you can access: - -```twig -{{ baseUrl }} # Base URL of Xibo CMS -{{ app_name }} # Application name (from config.php) -{{ user.username }} # Current user's login -{{ currentDate }} # Current date/time -{{ pageTitle }} # Page title (from view) -{{ pageSubtitle }} # Optional page subtitle -``` - -### CSS Class Conventions - -Custom classes follow BEM naming: - -``` -.ots- # Root component -.ots-__item # Child element -.ots---active # Modifier -``` - ---- - -## Future Enhancements - -### Planned Features -- [ ] Light mode toggle (currently dark only) -- [ ] Custom color picker in admin -- [ ] Theme variants (compact, expanded sidebar) -- [ ] Export/import settings -- [ ] RTL (Right-to-Left) support - -### Community Contribution Ideas -- Additional color schemes -- Accessibility improvements (AAA contrast) -- More page overrides (Settings, Users, Schedules) -- Keyboard navigation enhancements -- Dashboard widget system - ---- - -## Support & License - -**License:** AGPL-3.0 (inherited from Xibo CMS) - -**Legal Notice:** -This theme is provided as-is for use with Xibo Digital Signage CMS. It maintains compatibility with Xibo's AGPL license. Any modifications must be shared with the community under the same license. - -**Support Channels:** -- Xibo Community Forum: https://community.xibo.org.uk/ -- GitHub Issues: https://github.com/xibosignage/xibo-cms/issues - ---- - -## Credits - -**Theme Created For:** OTS Signs -**Based On:** Xibo CMS v4.0+ default theme -**Design System:** Modern dark theme with blue accent -**Created:** February 2026 - ---- - -## Changelog - -### v1.0.0 (Initial Release) -- Complete redesign of dashboard, displays, media views -- Dark theme with blue accent color -- Responsive sidebar & topbar -- Modern component system -- SVG icon integration -- Keyboard & mobile accessibility -- ~1000 lines of new CSS -- Interactive JavaScript components - ---- - -## Quick Reference - -### CSS Variables -```css ---color-primary: #3b82f6 /* Main brand color */ ---color-background: #0f172a /* Page background */ ---color-surface: #1e293b /* Card/panel background */ ---color-text-primary: #f1f5f9 /* Main text */ ---color-border: #475569 /* Dividers */ -``` - -### Breakpoints -```css -Mobile: max-width: 640px -Tablet: 641px - 768px -Desktop: 769px+ -``` - -### Key Classes -``` -.ots-shell /* Main layout wrapper */ -.ots-sidebar /* Left navigation */ -.ots-topbar /* Top header bar */ -.ots-content /* Main content area */ -.kpi-section /* Dashboard KPI grid */ -.panel /* Card component */ -.btn /* Button */ -.badge /* Status badge */ -.table /* Data table */ -.media-grid /* Image grid */ -``` - ---- - -**For questions or issues, refer to the Xibo Community Forum or review the theme files directly.** diff --git a/custom/otssignange/css/override-dark.css b/custom/otssignange/css/override-dark.css index bd61508..2cc43b8 100644 --- a/custom/otssignange/css/override-dark.css +++ b/custom/otssignange/css/override-dark.css @@ -1,1098 +1,581 @@ -/* ============================================================================ - XIBO CMS MODERN THEME - OTS Signs - Dark Mode Override - Component Styling - ============================================================================ */ +/* ============================================================================= + OTS SIGNAGE DARK THEME (REBUILT) + February 4, 2026 + ============================================================================= */ -/* Force dark mode */ :root { - --color-primary: #3b82f6; - --color-primary-dark: #1d4ed8; - --color-primary-light: #60a5fa; - --color-primary-lighter: #dbeafe; - --color-secondary: #7c3aed; - --color-success: #10b981; - --color-warning: #f59e0b; - --color-danger: #ef4444; - --color-info: #0ea5e9; - - --color-background: #0f172a; - --color-surface: #1e293b; - --color-surface-elevated: #334155; - --color-border: #475569; - --color-border-light: #1e293b; - --color-text-primary: #f1f5f9; - --color-text-secondary: #cbd5e1; - --color-text-tertiary: #94a3b8; - --color-text-inverse: #0f172a; - color-scheme: dark; + + --ots-bg: #0b111a; + --ots-surface: #141c2b; + --ots-surface-2: #1b2436; + --ots-surface-3: #222c3f; + --ots-border: #2c3a54; + --ots-border-soft: #243047; + --ots-text: #e6eefb; + --ots-text-muted: #a9b6cc; + --ots-text-faint: #7f8aa3; + --ots-primary: #4f8cff; + --ots-primary-2: #2f6bff; + --ots-success: #2ad4a4; + --ots-warning: #f4b860; + --ots-danger: #ff6b6b; + --ots-info: #5ec0ff; + + --ots-shadow-lg: 0 14px 30px rgba(0, 0, 0, 0.35); + --ots-shadow-md: 0 8px 18px rgba(0, 0, 0, 0.25); + --ots-shadow-sm: 0 3px 8px rgba(0, 0, 0, 0.2); + + --ots-radius-sm: 6px; + --ots-radius-md: 10px; + --ots-radius-lg: 14px; + + --ots-transition: 160ms ease; } +/* ============================================================================= + GLOBAL + ============================================================================= */ + html, body { - background-color: var(--color-background); - color: var(--color-text-primary); + background: var(--ots-bg); + color: var(--ots-text); } -/* ============================================================================ - SHELL LAYOUT - SIDEBAR + MAIN - ============================================================================ */ - -.ots-shell { - display: flex; - min-height: 100vh; +body { + font-family: "Inter", "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; } -.ots-sidebar { - position: fixed; - left: 0; - top: 0; - width: 250px; - height: 100vh; - background-color: var(--color-surface); - border-right: 1px solid var(--color-border); - display: flex; - flex-direction: column; - overflow-y: auto; - z-index: 1000; -} - -.ots-main { - flex: 1; - margin-left: 250px; - display: flex; - flex-direction: column; -} - -.ots-content { - flex: 1; - padding: 32px; - overflow-y: auto; -} - -.ots-footer { - border-top: 1px solid var(--color-border); - padding: 16px 32px; - background-color: var(--color-surface-elevated); - text-align: center; -} - -/* Responsive sidebar */ -@media (max-width: 768px) { - .ots-sidebar { - transform: translateX(-100%); - transition: transform var(--transition-base); - width: 280px; - } - - .ots-sidebar.active { - transform: translateX(0); - box-shadow: 2px 0 8px rgba(0, 0, 0, 0.3); - } - - .ots-main { - margin-left: 0; - } -} - -/* ============================================================================ - SIDEBAR STYLES - ============================================================================ */ - -.sidebar-header { - padding: 24px 16px; - border-bottom: 1px solid var(--color-border); - display: flex; - align-items: center; - gap: 12px; -} - -.brand-link { - display: flex; - align-items: center; - gap: 12px; - color: var(--color-text-primary); +a, +.nav-link, +.sidebar a { + color: var(--ots-text); text-decoration: none; + transition: color var(--ots-transition), background var(--ots-transition); +} + +a:hover, +.nav-link:hover, +.sidebar a:hover { + color: var(--ots-primary); +} + +h1, +h2, +h3, +h4, +h5, +h6 { + color: var(--ots-text); font-weight: 600; - font-size: 18px; } -.brand-icon { - font-size: 28px; +small, +.text-muted { + color: var(--ots-text-muted) !important; } -.sidebar-content { - flex: 1; - padding: 12px 0; - overflow-y: auto; +hr { + border-color: var(--ots-border); } -.sidebar-nav { - list-style: none; - margin: 0; +/* ============================================================================= + LAYOUT WRAPPERS + ============================================================================= */ + +#page-wrapper, +#content-wrapper, +.page-content { + background: var(--ots-bg); +} + +.page-content { + padding-top: 24px; +} + +/* ============================================================================= + NAVBAR / TOPBAR + ============================================================================= */ + +.navbar, +.navbar-default { + background: var(--ots-surface-2); + border: 1px solid var(--ots-border); + box-shadow: var(--ots-shadow-sm); +} + +.navbar-brand, +.navbar-brand .xibo-logo { + color: var(--ots-text); +} + +.navbar-nav > li > a, +.navbar-nav > .active > a, +.navbar-nav > .open > a { + color: var(--ots-text) !important; + background: transparent !important; +} + +.navbar-nav > li > a:hover { + color: var(--ots-primary) !important; +} + +.navbar-toggler, +.navbar-toggler-side { + color: var(--ots-text); + border: 1px solid var(--ots-border); +} + +/* Topbar nav refinements (dark) */ +.ots-topbar.navbar-nav { + background: transparent; + border: 0; padding: 0; + margin: 0; + gap: 6px; + height: auto; + align-items: center; } -.sidebar-nav li { - display: block; -} - -.nav-section-divider { - padding: 12px 16px 8px; - margin-top: 8px; - border-top: 1px solid var(--color-border); -} - -.nav-label { - display: block; - font-size: 12px; +.ots-topbar .nav-link { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + border-radius: 10px; + color: var(--ots-text); font-weight: 600; - text-transform: uppercase; - color: var(--color-text-tertiary); - letter-spacing: 0.05em; + transition: background var(--ots-transition), color var(--ots-transition); } -.nav-item { +.ots-topbar .nav-link:hover, +.ots-topbar .nav-item.open .nav-link, +.ots-topbar .nav-item.active .nav-link { + background: rgba(79, 140, 255, 0.18); + color: var(--ots-primary); +} + +.ots-topbar .dropdown-menu { + border-radius: 12px; + padding: 8px; + box-shadow: var(--ots-shadow-md); +} + +.ots-topbar .dropdown-item, +.ots-topbar .dropdown-menu a { display: flex; align-items: center; - gap: 12px; - padding: 12px 16px; - color: var(--color-text-secondary); - text-decoration: none; - transition: all var(--transition-fast); - position: relative; + gap: 8px; + border-radius: 8px; + padding: 8px 10px; } -.nav-item:hover { - color: var(--color-text-primary); - background-color: rgba(59, 130, 246, 0.1); +.ots-topbar-icon { + width: 16px; + text-align: center; + opacity: 0.85; + font-size: 13px; } -.nav-item.active { - color: var(--color-primary); - background-color: rgba(59, 130, 246, 0.15); - border-left: 3px solid var(--color-primary); - padding-left: 13px; +/* ============================================================================= + SIDEBAR + ============================================================================= */ + +#sidebar-wrapper { + background: var(--ots-surface); + border-right: 1px solid var(--ots-border); } -.nav-icon { - width: 20px; - height: 20px; - flex-shrink: 0; +/* OTS sidebar override marker */ +.ots-sidebar-wrapper { + box-shadow: inset 0 0 0 1px rgba(79, 140, 255, 0.2); } -.nav-text { +.ots-sidebar .sidebar-list a, +.ots-sidebar .sidebar-main a { + display: flex; + align-items: center; + gap: 10px; +} + +.ots-nav-icon { + width: 18px; + text-align: center; + opacity: 0.85; font-size: 14px; - font-weight: 500; } -.sidebar-footer { - border-top: 1px solid var(--color-border); - padding: 16px; - background-color: rgba(59, 130, 246, 0.05); -} - -.sidebar-user { - display: flex; - align-items: center; - gap: 12px; -} - -.user-avatar-lg { - width: 40px; - height: 40px; - border-radius: 50%; - background: linear-gradient(135deg, #3b82f6, #7c3aed); - color: white; - display: flex; - align-items: center; - justify-content: center; - font-weight: 600; - font-size: 16px; - flex-shrink: 0; -} - -.user-details { +.ots-nav-text { + flex: 1; min-width: 0; } -.user-name { - font-size: 14px; - font-weight: 600; - color: var(--color-text-primary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.user-role { - color: var(--color-text-tertiary); - white-space: nowrap; -} - -/* ============================================================================ - TOPBAR STYLES - ============================================================================ */ - -.ots-topbar { - background-color: var(--color-surface-elevated); - border-bottom: 1px solid var(--color-border); - padding: 12px 32px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 24px; - height: 70px; -} - -.topbar-left { - display: flex; - align-items: center; - gap: 16px; - flex: 1; -} - -.topbar-toggle { - display: none; - width: 40px; - height: 40px; - padding: 0; - border: none; - background-color: transparent; - color: var(--color-text-primary); - cursor: pointer; - border-radius: 6px; - transition: background-color var(--transition-fast); -} - -.topbar-toggle:hover { - background-color: var(--color-surface); -} - -.topbar-toggle .icon { - width: 24px; - height: 24px; -} - -.topbar-title h1 { - margin: 0; - font-size: 24px; - font-weight: 600; - color: var(--color-text-primary); -} - -.topbar-title p { - margin: 0; - font-size: 14px; - color: var(--color-text-secondary); -} - -.topbar-right { - display: flex; - align-items: center; - gap: 24px; -} - -.topbar-search { - position: relative; - width: 280px; - display: flex; - align-items: center; - background-color: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: 6px; - padding: 8px 12px; -} - -.topbar-search .search-icon { - color: var(--color-text-tertiary); - flex-shrink: 0; - margin-right: 8px; -} - -.topbar-search .search-input { - flex: 1; - background: none; - border: none; - color: var(--color-text-primary); - font-size: 14px; - outline: none; -} - -.topbar-search .search-input::placeholder { - color: var(--color-text-tertiary); -} - -.topbar-actions { - display: flex; - align-items: center; - gap: 12px; -} - -.topbar-btn { - width: 40px; - height: 40px; - padding: 0; - border: none; - background-color: transparent; - color: var(--color-text-secondary); - cursor: pointer; - border-radius: 6px; - display: flex; - align-items: center; - justify-content: center; - transition: all var(--transition-fast); -} - -.topbar-btn:hover { - background-color: var(--color-surface); - color: var(--color-text-primary); -} - -.topbar-btn .icon { - width: 20px; - height: 20px; -} - -.user-btn { - padding: 2px; -} - -.avatar { - width: 36px; - height: 36px; - border-radius: 50%; - background: linear-gradient(135deg, #3b82f6, #7c3aed); - color: white; - display: flex; - align-items: center; - justify-content: center; - font-weight: 600; - font-size: 14px; -} - -.avatar-sm { - width: 32px; - height: 32px; - font-size: 13px; -} - -.dropdown { - position: relative; -} - -.dropdown-menu { - position: absolute; - top: 100%; - right: 0; - margin-top: 8px; - background-color: var(--color-surface-elevated); - border: 1px solid var(--color-border); - border-radius: 6px; - box-shadow: var(--shadow-lg); - list-style: none; - padding: 8px 0; - min-width: 160px; - display: none; - z-index: 1001; -} - -.dropdown.active .dropdown-menu, -.dropdown:focus-within .dropdown-menu { - display: block; -} - -.dropdown-menu li { - display: block; -} - -.dropdown-menu li a { - display: block; - padding: 10px 16px; - color: var(--color-text-primary); - text-decoration: none; - font-size: 14px; - transition: background-color var(--transition-fast); -} - -.dropdown-menu li a:hover { - background-color: var(--color-surface); -} - -/* ============================================================================ - DASHBOARD PAGE - ============================================================================ */ - -.dashboard-page { - display: flex; - flex-direction: column; - gap: 32px; -} - -.page-header { - margin-bottom: 16px; -} - -.page-header h1 { - margin: 0 0 8px; - font-size: 32px; - font-weight: 700; -} - -.page-header .text-muted { - margin: 0; - font-size: 16px; - color: var(--color-text-secondary); -} - -/* KPI Section */ -.kpi-section { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 20px; -} - -.kpi-card { - background-color: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: 8px; - padding: 20px; - transition: all var(--transition-base); -} - -.kpi-card:hover { - border-color: var(--color-primary); - box-shadow: 0 0 0 1px var(--color-primary); -} - -.kpi-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 16px; -} - -.kpi-label { - margin: 0; - font-size: 14px; - font-weight: 600; - color: var(--color-text-secondary); - text-transform: uppercase; - letter-spacing: 0.05em; -} - -.kpi-icon-box { - width: 40px; - height: 40px; - border-radius: 6px; - background: linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(124, 58, 237, 0.2)); - display: flex; - align-items: center; - justify-content: center; - color: var(--color-primary); -} - -.kpi-body { - margin-bottom: 12px; -} - -.kpi-number { - font-size: 32px; - font-weight: 700; - color: var(--color-text-primary); - line-height: 1; - margin-bottom: 4px; -} - -.kpi-meta { - font-size: 13px; - color: var(--color-text-secondary); -} - -.kpi-footer { - display: flex; - align-items: center; - gap: 8px; - padding-top: 12px; - border-top: 1px solid var(--color-border); -} - -/* Badges */ -.badge { - display: inline-block; - padding: 4px 10px; - border-radius: 4px; - font-size: 12px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.05em; -} - -.badge-success { - background-color: rgba(16, 185, 129, 0.2); - color: #a7f3d0; -} - -.badge-danger { - background-color: rgba(239, 68, 68, 0.2); - color: #fca5a5; -} - -.badge-info { - background-color: rgba(14, 165, 233, 0.2); - color: #7dd3fc; -} - -.badge-secondary { - background-color: rgba(124, 58, 237, 0.2); - color: #d8b4fe; -} - -/* Dashboard Panels */ -.dashboard-panels { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 24px; -} - -.panel { - background-color: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: 8px; - overflow: hidden; -} - -.panel-full { - grid-column: 1 / -1; -} - -.panel-header { - padding: 20px; - border-bottom: 1px solid var(--color-border); - display: flex; - justify-content: space-between; - align-items: center; -} - -.panel-header h3 { - margin: 0; - font-size: 16px; - font-weight: 600; - color: var(--color-text-primary); -} - -.link-secondary { - color: var(--color-primary); - text-decoration: none; - font-size: 13px; - font-weight: 500; - transition: color var(--transition-fast); -} - -.link-secondary:hover { - color: var(--color-primary-light); -} - -.panel-body { - padding: 20px; -} - -.empty-state-compact { - text-align: center; - padding: 20px 0; -} - -.empty-state-compact p { - margin: 0 0 16px; - color: var(--color-text-secondary); -} - -/* Quick Actions */ -.quick-actions-grid { - margin-top: 32px; -} - -.section-title { - margin: 0 0 16px; - font-size: 18px; - font-weight: 600; - color: var(--color-text-primary); -} - -.action-cards { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 16px; -} - -.action-card { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 12px; - padding: 24px; - background-color: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: 8px; - text-decoration: none; - color: var(--color-text-primary); - transition: all var(--transition-base); - cursor: pointer; -} - -.action-card:hover { - border-color: var(--color-primary); - background-color: rgba(59, 130, 246, 0.05); - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); -} - -.action-icon { - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - color: var(--color-primary); -} - -.action-label { - font-size: 14px; - font-weight: 600; - text-align: center; -} - -/* ============================================================================ - TWO-COLUMN LAYOUT (Displays, Media) - ============================================================================ */ - -.two-column-layout { - display: grid; - grid-template-columns: 280px 1fr; - gap: 24px; - height: calc(100vh - 130px); -} - -.left-panel { - background-color: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: 8px; - display: flex; - flex-direction: column; - overflow-y: auto; -} - -.content-panel { - display: flex; - flex-direction: column; - gap: 16px; -} - -.panel-header { - padding: 16px; - border-bottom: 1px solid var(--color-border); - display: flex; - justify-content: space-between; - align-items: center; -} - -.panel-header h3 { - margin: 0; - font-size: 14px; - font-weight: 600; - color: var(--color-text-primary); - text-transform: uppercase; - letter-spacing: 0.05em; -} - -.btn-icon-sm { - width: 32px; - height: 32px; - padding: 0; - border: none; - background-color: transparent; - color: var(--color-text-secondary); - cursor: pointer; - border-radius: 4px; - display: flex; - align-items: center; - justify-content: center; - transition: all var(--transition-fast); -} - -.btn-icon-sm:hover { - background-color: var(--color-surface-elevated); - color: var(--color-text-primary); -} - -.folder-tree { - padding: 8px 0; -} - -.folder-item { - display: flex; - align-items: center; - gap: 12px; - padding: 10px 16px; - color: var(--color-text-secondary); - cursor: pointer; - transition: all var(--transition-fast); -} - -.folder-item:hover { - background-color: var(--color-surface-elevated); - color: var(--color-text-primary); -} - -.folder-item.active { - background-color: rgba(59, 130, 246, 0.15); - color: var(--color-primary); -} - -.folder-icon { - width: 18px; - height: 18px; - flex-shrink: 0; -} - -.folder-name { - font-size: 14px; - font-weight: 500; -} - -.content-toolbar { - display: flex; - align-items: center; - gap: 16px; - padding: 16px; - background-color: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: 8px; -} - -.search-wrapper { - flex: 1; - position: relative; - display: flex; - align-items: center; - background-color: var(--color-surface-elevated); - border: 1px solid var(--color-border); - border-radius: 6px; - padding: 8px 12px; -} - -.search-icon { - color: var(--color-text-tertiary); - margin-right: 8px; - flex-shrink: 0; -} - -.search-field { - flex: 1; - background: none; - border: none; - color: var(--color-text-primary); - font-size: 14px; - outline: none; - padding: 0; -} - -.search-field::placeholder { - color: var(--color-text-tertiary); -} - -.toolbar-actions { - display: flex; - align-items: center; - gap: 12px; -} - -.stat-row { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); - gap: 12px; -} - -.stat-box { - background-color: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: 6px; - padding: 12px; - text-align: center; -} - -.stat-label { - font-size: 12px; - color: var(--color-text-secondary); - text-transform: uppercase; - letter-spacing: 0.05em; - margin-bottom: 8px; -} - -.stat-value { - font-size: 24px; - font-weight: 700; - color: var(--color-text-primary); -} - -.text-success { - color: #10b981; -} - -.text-danger { - color: #ef4444; -} - -/* ============================================================================ - TABLES - ============================================================================ */ - -.table-wrapper { - background-color: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: 8px; - overflow-x: auto; -} - -.table { - width: 100%; - border-collapse: collapse; - margin: 0; - font-size: 14px; -} - -.table thead { - background-color: var(--color-surface-elevated); - border-bottom: 1px solid var(--color-border); -} - -.table thead th { - padding: 12px 16px; - text-align: left; - font-weight: 600; - color: var(--color-text-secondary); - text-transform: uppercase; - letter-spacing: 0.05em; - font-size: 12px; -} - -.table tbody tr { - border-bottom: 1px solid var(--color-border); - transition: background-color var(--transition-fast); -} - -.table tbody tr:hover { - background-color: var(--color-surface-elevated); -} - -.table tbody td { - padding: 12px 16px; - color: var(--color-text-primary); -} - -.table-striped tbody tr:nth-child(even) { - background-color: rgba(59, 130, 246, 0.02); -} - -/* ============================================================================ - MEDIA LIBRARY - ============================================================================ */ - -.media-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 16px; - padding: 16px; - background-color: var(--color-surface); - border: 1px solid var(--color-border); - border-radius: 8px; -} - -.media-item { - background-color: var(--color-surface-elevated); - border: 1px solid var(--color-border); - border-radius: 6px; - overflow: hidden; - cursor: pointer; - transition: all var(--transition-base); -} - -.media-item:hover { - border-color: var(--color-primary); - box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2); - transform: translateY(-4px); -} - -.media-thumbnail { - position: relative; - width: 100%; - padding-bottom: 75%; - overflow: hidden; - background-color: var(--color-border); -} - -.media-thumbnail img { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; -} - -.media-type-badge { - position: absolute; - top: 8px; - right: 8px; - background-color: rgba(0, 0, 0, 0.6); - color: white; - padding: 4px 8px; - border-radius: 4px; +/* Xibo sidebar refinements (dark) */ +#sidebar-wrapper .sidebar-title a { + color: var(--ots-text-faint); font-size: 11px; - font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + padding: 12px 16px 6px; +} + +#sidebar-wrapper .sidebar-list a, +#sidebar-wrapper .sidebar-main a { + display: block; + margin: 2px 8px; + padding: 10px 12px; + border-radius: var(--ots-radius-sm); + transition: background var(--ots-transition), color var(--ots-transition); +} + +#sidebar-wrapper .sidebar-list a:hover, +#sidebar-wrapper .sidebar-main a:hover { + background: rgba(79, 140, 255, 0.12); + color: var(--ots-primary); +} + +#sidebar-wrapper .sidebar { + padding: 14px 0; +} + +#sidebar-wrapper .sidebar-main a, +#sidebar-wrapper .sidebar-list a { + display: block; + padding: 10px 18px; + color: var(--ots-text); + border-left: 3px solid transparent; +} + +#sidebar-wrapper .sidebar-main a:hover, +#sidebar-wrapper .sidebar-list a:hover { + background: rgba(79, 140, 255, 0.12); + color: var(--ots-primary); +} + +#sidebar-wrapper .sidebar-title a { + padding: 12px 18px 6px; + color: var(--ots-text-faint); + font-size: 11px; + letter-spacing: 0.08em; text-transform: uppercase; } -.media-info { - padding: 12px; +/* ============================================================================= + WIDGETS / CARDS + ============================================================================= */ + +.widget, +.card, +.panel, +.modal-content { + background: var(--ots-surface-2); + border: 1px solid var(--ots-border); + border-radius: var(--ots-radius-md); + box-shadow: var(--ots-shadow-sm); } -.media-name { - margin: 0 0 4px; - font-size: 14px; - font-weight: 600; - color: var(--color-text-primary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; +.widget-title, +.panel-heading, +.card-header, +.modal-header { + background: var(--ots-surface-3); + border-bottom: 1px solid var(--ots-border); + color: var(--ots-text); } -.media-size { - margin: 0; - color: var(--color-text-tertiary); +.widget-body, +.panel-body, +.card-body, +.modal-body { + color: var(--ots-text); } -/* ============================================================================ +/* ============================================================================= BUTTONS - ============================================================================ */ + ============================================================================= */ -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 8px; - padding: 10px 16px; - border: none; - border-radius: 6px; - font-size: 14px; - font-weight: 600; - text-decoration: none; - cursor: pointer; - transition: all var(--transition-fast); - white-space: nowrap; +.btn, +button, +input[type="button"], +input[type="submit"] { + border-radius: var(--ots-radius-sm); + border: 1px solid transparent; + transition: background var(--ots-transition), border var(--ots-transition), color var(--ots-transition); +} + +.btn-default, +.btn-secondary { + background: var(--ots-surface-3); + color: var(--ots-text); + border-color: var(--ots-border); +} + +.btn-default:hover, +.btn-secondary:hover { + background: var(--ots-surface-2); + border-color: var(--ots-primary); + color: var(--ots-primary); } .btn-primary { - background-color: var(--color-primary); - color: white; + background: var(--ots-primary); + border-color: var(--ots-primary-2); + color: #0b1020; } .btn-primary:hover { - background-color: var(--color-primary-dark); + background: var(--ots-primary-2); + border-color: var(--ots-primary-2); + color: #0b1020; } -.btn-outline { - background-color: transparent; - border: 1px solid var(--color-border); - color: var(--color-text-primary); +.btn-success { + background: var(--ots-success); + border-color: var(--ots-success); + color: #041410; } -.btn-outline:hover { - background-color: var(--color-surface); - border-color: var(--color-primary); - color: var(--color-primary); +.btn-warning { + background: var(--ots-warning); + border-color: var(--ots-warning); + color: #1c1200; } -.btn-sm { - padding: 6px 12px; - font-size: 13px; +.btn-danger { + background: var(--ots-danger); + border-color: var(--ots-danger); + color: #220707; } -.btn-ghost { - background-color: transparent; - color: var(--color-text-secondary); - padding: 0; +.btn-info { + background: var(--ots-info); + border-color: var(--ots-info); + color: #07101a; } -.btn-ghost:hover { - color: var(--color-text-primary); +/* ============================================================================= + FORMS + ============================================================================= */ + +.form-control, +input[type="text"], +input[type="search"], +input[type="email"], +input[type="password"], +select, +textarea { + background: var(--ots-surface); + color: var(--ots-text); + border: 1px solid var(--ots-border); + border-radius: var(--ots-radius-sm); + box-shadow: none; } -/* ============================================================================ - UTILITY CLASSES - ============================================================================ */ - -.text-muted { - color: var(--color-text-secondary); +.form-control:focus, +input[type="text"]:focus, +input[type="search"]:focus, +input[type="email"]:focus, +input[type="password"]:focus, +select:focus, +textarea:focus { + border-color: var(--ots-primary); + box-shadow: 0 0 0 2px rgba(79, 140, 255, 0.2); + outline: none; } -.text-tertiary { - color: var(--color-text-tertiary); +.input-group-addon { + background: var(--ots-surface-3); + border: 1px solid var(--ots-border); + color: var(--ots-text-muted); } -.text-xs { +/* ============================================================================= + TABLES / DATATABLES + ============================================================================= */ + +.table, +.table > thead > tr > th, +.table > tbody > tr > td { + color: var(--ots-text); + border-color: var(--ots-border); +} + +.table-striped > tbody > tr:nth-of-type(odd) { + background: rgba(79, 140, 255, 0.04); +} + +.table-hover > tbody > tr:hover { + background: rgba(79, 140, 255, 0.08); +} + +.dataTables_wrapper .dataTables_length, +.dataTables_wrapper .dataTables_filter, +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_paginate { + color: var(--ots-text-muted) !important; +} + +.dataTables_wrapper .dataTables_filter input, +.dataTables_wrapper .dataTables_length select { + background: var(--ots-surface) !important; + color: var(--ots-text) !important; + border: 1px solid var(--ots-border) !important; +} + +.dataTables_wrapper table { + color: var(--ots-text); +} + +/* ============================================================================= + DROPDOWNS / MENUS + ============================================================================= */ + +.dropdown-menu { + background: var(--ots-surface-2); + border: 1px solid var(--ots-border); + box-shadow: var(--ots-shadow-md); +} + +.dropdown-item, +.dropdown-menu > li > a { + color: var(--ots-text); +} + +.dropdown-item:hover, +.dropdown-menu > li > a:hover { + background: rgba(79, 140, 255, 0.12); + color: var(--ots-primary); +} + +/* ============================================================================= + ALERTS / BADGES + ============================================================================= */ + +.alert { + border-radius: var(--ots-radius-sm); + border: 1px solid var(--ots-border); +} + +.alert-success { + background: rgba(42, 212, 164, 0.15); + color: var(--ots-success); +} + +.alert-warning { + background: rgba(244, 184, 96, 0.15); + color: var(--ots-warning); +} + +.alert-danger { + background: rgba(255, 107, 107, 0.15); + color: var(--ots-danger); +} + +.alert-info { + background: rgba(94, 192, 255, 0.15); + color: var(--ots-info); +} + +.badge, +.label { + background: var(--ots-surface-3); + color: var(--ots-text); + border: 1px solid var(--ots-border); +} + +/* ============================================================================= + TABS / PAGINATION + ============================================================================= */ + +.nav-tabs { + border-bottom: 1px solid var(--ots-border); +} + +.nav-tabs > li > a { + color: var(--ots-text-muted); + border: 1px solid transparent; +} + +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:focus, +.nav-tabs > li.active > a:hover { + background: var(--ots-surface-2); + color: var(--ots-text); + border: 1px solid var(--ots-border); + border-bottom-color: transparent; +} + +.pagination > li > a, +.pagination > li > span { + background: var(--ots-surface-2); + border: 1px solid var(--ots-border); + color: var(--ots-text); +} + +.pagination > li > a:hover, +.pagination > li > span:hover { + background: var(--ots-surface-3); + color: var(--ots-primary); +} + +.pagination > .active > a, +.pagination > .active > span { + background: var(--ots-primary); + border-color: var(--ots-primary-2); + color: #0b1020; +} + +/* ============================================================================= + MODALS + ============================================================================= */ + +.modal-content { + border-radius: var(--ots-radius-lg); +} + +.modal-footer { + border-top: 1px solid var(--ots-border); +} + +/* ============================================================================= + HELP PANE / MISC + ============================================================================= */ + +#help-pane { + background: var(--ots-surface-2); + border: 1px solid var(--ots-border); +} + +#help-pane .help-pane-btn { + background: var(--ots-primary); + color: #0b1020; +} + +/* ============================================================================= + OTS DASHBOARD MESSAGE + ============================================================================= */ + +.ots-dashboard-message { + margin: 16px 0 24px; + padding: 12px 16px; + border-radius: var(--ots-radius-md); + background: rgba(79, 140, 255, 0.16); + border: 1px solid rgba(79, 140, 255, 0.45); + color: var(--ots-text); +} + +.ots-dashboard-message__title { + font-weight: 700; + letter-spacing: 0.02em; + text-transform: uppercase; font-size: 12px; + margin-bottom: 4px; } -.text-sm { - font-size: 14px; -} - -/* ============================================================================ - RESPONSIVE - ============================================================================ */ - -@media (max-width: 768px) { - .ots-topbar { - flex-direction: column; - align-items: flex-start; - gap: 12px; - height: auto; - padding: 12px 16px; - } - - .topbar-right { - width: 100%; - flex-direction: column; - } - - .topbar-search { - width: 100%; - } - - .two-column-layout { - grid-template-columns: 1fr; - height: auto; - } - - .left-panel { - max-height: 300px; - } - - .dashboard-panels { - grid-template-columns: 1fr; - } - - .kpi-section { - grid-template-columns: 1fr; - } - - .action-cards { - grid-template-columns: 1fr; - } - - .media-grid { - grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); - } +.ots-dashboard-message__body { + font-size: 13px; + color: var(--ots-text-muted); } diff --git a/custom/otssignange/css/override.css b/custom/otssignange/css/override.css index 8c56481..a933705 100644 --- a/custom/otssignange/css/override.css +++ b/custom/otssignange/css/override.css @@ -20,10 +20,11 @@ --color-surface-elevated: #334155; --color-border: #475569; --color-border-light: #1e293b; - --color-text-primary: #f1f5f9; - --color-text-secondary: #cbd5e1; - --color-text-tertiary: #94a3b8; - --color-text-inverse: #0f172a; + --color-text-primary: #ffffff; + --color-text-secondary: #f1f5f9; + --color-text-tertiary: #e2e8f0; + --color-text-inverse: #ffffff; + --color-on-primary: #ffffff; color-scheme: dark; } @@ -47,10 +48,11 @@ body { position: fixed; left: 0; top: 0; - width: 250px; + width: 260px; height: 100vh; - background-color: var(--color-surface); - border-right: 1px solid var(--color-border); + background-color: #08132a; + border-right: 1px solid rgba(255, 255, 255, 0.06); + padding: 0; display: flex; flex-direction: column; overflow-y: auto; @@ -59,9 +61,9 @@ body { .ots-main { flex: 1; - margin-left: 250px; display: flex; flex-direction: column; + margin-left: 260px; } .ots-content { @@ -75,6 +77,7 @@ body { padding: 16px 32px; background-color: var(--color-surface-elevated); text-align: center; + color: var(--color-text-primary); } /* Responsive sidebar */ @@ -84,7 +87,6 @@ body { transition: transform var(--transition-base); width: 280px; } - .ots-sidebar.active { transform: translateX(0); box-shadow: 2px 0 8px rgba(0, 0, 0, 0.3); @@ -93,6 +95,10 @@ body { .ots-main { margin-left: 0; } + + .ots-content { + color: var(--color-text-primary); + } } /* ============================================================================ @@ -100,10 +106,11 @@ body { ============================================================================ */ .sidebar-header { - padding: 24px 16px; + padding: 20px 16px; border-bottom: 1px solid var(--color-border); display: flex; align-items: center; + justify-content: space-between; gap: 12px; } @@ -112,13 +119,20 @@ body { align-items: center; gap: 12px; color: var(--color-text-primary); + font-weight: 700; + font-size: 16px; text-decoration: none; - font-weight: 600; - font-size: 18px; + flex: 1; } .brand-icon { - font-size: 28px; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + flex-shrink: 0; } .sidebar-content { @@ -130,13 +144,85 @@ body { .sidebar-nav { list-style: none; margin: 0; - padding: 0; + padding: 12px 0 120px; } .sidebar-nav li { display: block; } +/* Compatibility: Xibo sidebar markup uses `sidebar-list`, `sidebar-main`, `sidebar-title`. + Map those into the modern `.nav-item/.nav-text/.nav-icon` style system so styles apply. +*/ +.ots-sidebar li.sidebar-list > a, +.ots-sidebar li.sidebar-main > a, +.ots-sidebar li.sidebar-title > a { + display: grid; + grid-template-columns: 20px 1fr; + align-items: center; + column-gap: 12px; + padding: 8px 12px; + color: #c8d5ee; + text-decoration: none; + transition: all var(--transition-fast); + position: relative; + font-size: 14px; + font-weight: 500; + border-left: 2px solid transparent; + margin: 3px 10px; + border-radius: 12px; + min-height: 40px; + line-height: 1.25; +} + +.ots-sidebar li.sidebar-list > a:hover { + color: #ffffff; + background-color: rgba(255, 255, 255, 0.08); +} + +.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; + border-left-color: transparent; + font-weight: 600; + box-shadow: 0 8px 18px rgba(15, 23, 42, 0.25); +} + +.ots-sidebar .ots-nav-icon { + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + font-size: 16px; + color: currentColor; + justify-self: center; +} + +.ots-sidebar .ots-nav-text { + font-size: 14px; + font-weight: 500; + flex: 1; + line-height: 1.2; +} + +.ots-sidebar li.sidebar-title > a { + display: block; + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + color: #8ea4c7; + letter-spacing: 0.12em; + padding: 12px 14px 4px; + margin: 8px 0 0; + min-height: auto; + line-height: 1; +} + .nav-section-divider { padding: 12px 16px 8px; margin-top: 8px; @@ -145,45 +231,68 @@ body { .nav-label { display: block; - font-size: 12px; - font-weight: 600; + font-size: 11px; + font-weight: 700; text-transform: uppercase; color: var(--color-text-tertiary); - letter-spacing: 0.05em; + letter-spacing: 0.08em; + padding: 12px 16px 8px; + margin-top: 8px; } .nav-item { display: flex; align-items: center; - gap: 12px; + gap: 14px; padding: 12px 16px; - color: var(--color-text-secondary); + color: var(--color-text-primary); text-decoration: none; transition: all var(--transition-fast); position: relative; + font-size: 14px; + font-weight: 500; + border-left: 2px solid transparent; + margin: 0 4px; + border-radius: 6px; } .nav-item:hover { color: var(--color-text-primary); background-color: rgba(59, 130, 246, 0.1); + border-left-color: transparent; } .nav-item.active { color: var(--color-primary); - background-color: rgba(59, 130, 246, 0.15); - border-left: 3px solid var(--color-primary); - padding-left: 13px; + background-color: rgba(59, 130, 246, 0.12); + border-left-color: var(--color-primary); + font-weight: 600; +} + +.nav-item.active::after { + content: ''; + position: absolute; + right: 8px; + width: 8px; + height: 8px; + background-color: var(--color-primary); + border-radius: 50%; } .nav-icon { - width: 20px; - height: 20px; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; flex-shrink: 0; + font-size: 18px; } .nav-text { font-size: 14px; font-weight: 500; + flex: 1; } .sidebar-footer { @@ -226,30 +335,151 @@ body { } .user-role { - color: var(--color-text-tertiary); + color: var(--color-text-secondary); white-space: nowrap; } /* ============================================================================ - TOPBAR STYLES + TOPBAR STYLES - DEEP ANALYSIS & MODERN OVERHAUL + ============================================================================ + Design decisions: + - 64px total height (44px content + 10px top/bottom padding) = consistent baseline + - All interactive elements are 40px (buttons, search height) + - Icon sizing: 20px container, 16px icon (scale-friendly) + - Consistent 16px gap spacing throughout + - Remove conflicting .navbar-nav rules + - Proper z-index layering for dropdowns ============================================================================ */ .ots-topbar { background-color: var(--color-surface-elevated); - border-bottom: 1px solid var(--color-border); - padding: 12px 32px; + border-bottom: 2px solid var(--color-border); + padding: 10px 32px; display: flex; align-items: center; justify-content: space-between; - gap: 24px; - height: 70px; + gap: 16px; + height: 64px; + z-index: 1000; } +/* Topbar nav container - override .navbar-nav defaults */ +.ots-topbar.navbar-nav { + background: transparent !important; + border: 0 !important; + padding: 0 !important; + margin: 0 !important; + gap: 4px; + height: 100%; + align-items: center; +} + +.ots-topbar .nav-item { + display: flex; + align-items: center; + height: 100%; + margin: 0 !important; + padding: 0 !important; +} + +.ots-topbar .nav-link { + display: inline-flex; + align-items: center; + height: 100%; + gap: 6px; + padding: 0 12px; + border-radius: 6px; + color: var(--color-text-secondary); + font-weight: 500; + font-size: 14px; + white-space: nowrap; + transition: all var(--transition-fast); + position: relative; +} + +.ots-topbar .nav-link:hover { + background-color: rgba(59, 130, 246, 0.08); + color: var(--color-primary); +} + +.ots-topbar .nav-item.open .nav-link, +.ots-topbar .nav-item.active .nav-link { + background-color: rgba(59, 130, 246, 0.12); + color: var(--color-primary); + font-weight: 600; +} + +.ots-topbar .dropdown-toggle::after { + content: ''; + display: inline-block; + margin-left: 6px; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid currentColor; + opacity: 0.6; + transition: transform var(--transition-fast); +} + +.ots-topbar .nav-item.open .dropdown-toggle::after { + transform: rotate(180deg); +} + +.ots-topbar .dropdown-menu { + border-radius: 8px; + padding: 6px 0; + margin-top: 4px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); + border: 1px solid var(--color-border); + background-color: var(--color-surface); + min-width: 180px; + z-index: 1100; +} + +.ots-topbar .dropdown-item, +.ots-topbar .dropdown-menu a { + display: flex; + align-items: center; + gap: 8px; + border-radius: 4px; + padding: 8px 14px; + color: var(--color-text-secondary); + font-size: 14px; + font-weight: 500; + margin: 2px 6px; + transition: all var(--transition-fast); +} + +.ots-topbar .dropdown-item:hover, +.ots-topbar .dropdown-menu a:hover { + background-color: rgba(59, 130, 246, 0.1); + color: var(--color-primary); +} + +/* Icon sizing for topbar and nav */ +.ots-topbar-icon { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + opacity: 0.9; + flex-shrink: 0; +} + +.ots-topbar .dropdown-menu .ots-topbar-icon { + font-size: 13px; +} + +/* Left section: toggle, title, nav */ .topbar-left { display: flex; align-items: center; gap: 16px; flex: 1; + min-width: 0; } .topbar-toggle { @@ -262,52 +492,95 @@ body { color: var(--color-text-primary); cursor: pointer; border-radius: 6px; - transition: background-color var(--transition-fast); + transition: all var(--transition-fast); + flex-shrink: 0; } .topbar-toggle:hover { - background-color: var(--color-surface); + background-color: rgba(59, 130, 246, 0.1); +} + +.topbar-toggle:active { + background-color: rgba(59, 130, 246, 0.15); } .topbar-toggle .icon { - width: 24px; - height: 24px; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; +} + +/* Title section with proper vertical centering */ +.topbar-title { + display: flex; + flex-direction: column; + justify-content: center; + gap: 0; + min-width: 0; } .topbar-title h1 { margin: 0; - font-size: 24px; - font-weight: 600; + padding: 0; + font-size: 18px; + font-weight: 700; color: var(--color-text-primary); + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .topbar-title p { margin: 0; - font-size: 14px; - color: var(--color-text-secondary); + padding: 0; + font-size: 12px; + color: var(--color-text-tertiary); + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } +/* Right section: search, actions */ .topbar-right { display: flex; align-items: center; - gap: 24px; + gap: 12px; + flex-shrink: 0; } +/* Search box - 40px height to match button grid */ .topbar-search { position: relative; + height: 40px; width: 280px; display: flex; align-items: center; background-color: var(--color-surface); border: 1px solid var(--color-border); border-radius: 6px; - padding: 8px 12px; + padding: 0 12px; + gap: 8px; + transition: all var(--transition-fast); +} + +.topbar-search:focus-within { + border-color: var(--color-primary); + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1); } .topbar-search .search-icon { color: var(--color-text-tertiary); flex-shrink: 0; - margin-right: 8px; + font-size: 16px; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; } .topbar-search .search-input { @@ -317,21 +590,28 @@ body { color: var(--color-text-primary); font-size: 14px; outline: none; + padding: 0; + height: 100%; + min-width: 120px; } .topbar-search .search-input::placeholder { color: var(--color-text-tertiary); } +/* Action buttons container */ .topbar-actions { display: flex; align-items: center; - gap: 12px; + gap: 4px; } +/* Consistent 40px button sizing */ .topbar-btn { width: 40px; height: 40px; + min-width: 40px; + min-height: 40px; padding: 0; border: none; background-color: transparent; @@ -342,16 +622,25 @@ body { align-items: center; justify-content: center; transition: all var(--transition-fast); + position: relative; } .topbar-btn:hover { - background-color: var(--color-surface); - color: var(--color-text-primary); + background-color: rgba(59, 130, 246, 0.08); + color: var(--color-primary); +} + +.topbar-btn:active { + background-color: rgba(59, 130, 246, 0.12); } .topbar-btn .icon { width: 20px; height: 20px; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; } .user-btn { @@ -388,42 +677,187 @@ body { right: 0; margin-top: 8px; background-color: var(--color-surface-elevated); - border: 1px solid var(--color-border); - border-radius: 6px; - box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 8px; + box-shadow: 0 20px 45px rgba(0, 0, 0, 0.3); list-style: none; padding: 8px 0; - min-width: 160px; + min-width: 180px; display: none; z-index: 1001; + overflow: hidden; } .dropdown.active .dropdown-menu, .dropdown:focus-within .dropdown-menu { display: block; + animation: slideDown 150ms ease-out; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-8px); + } + to { + opacity: 1; + transform: translateY(0); + } } .dropdown-menu li { 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; +} + .dropdown-menu li a { - display: block; + display: flex; + align-items: center; + gap: 12px; padding: 10px 16px; color: var(--color-text-primary); text-decoration: none; font-size: 14px; - transition: background-color var(--transition-fast); + transition: all var(--transition-fast); + font-weight: 500; } .dropdown-menu li a:hover { - background-color: var(--color-surface); + background-color: rgba(59, 130, 246, 0.1); + color: var(--color-primary); +} + +.dropdown-menu li a:active { + background-color: rgba(59, 130, 246, 0.15); } /* ============================================================================ DASHBOARD PAGE ============================================================================ */ +.dashboard-card { + background: linear-gradient(180deg, rgba(30, 41, 59, 0.92), rgba(15, 23, 42, 0.92)); + border: 1px solid rgba(148, 163, 184, 0.18); + border-radius: 16px; + box-shadow: 0 18px 40px rgba(8, 15, 30, 0.35); + backdrop-filter: blur(6px); + transition: transform var(--transition-base), box-shadow var(--transition-base), border-color var(--transition-base); + overflow: hidden; +} + +.dashboard-card:hover { + transform: translateY(-2px); + box-shadow: 0 22px 50px rgba(8, 15, 30, 0.45); + border-color: rgba(59, 130, 246, 0.45); +} + +.dashboard-card .panel-header, +.dashboard-card .widget-title { + background: linear-gradient(90deg, rgba(59, 130, 246, 0.16), rgba(99, 102, 241, 0.12)); + border-bottom: 1px solid rgba(148, 163, 184, 0.2); + padding: 18px 20px; + border-radius: 16px 16px 0 0; +} + +.dashboard-card .panel-header h3, +.dashboard-card .widget-title h3, +.dashboard-card .panel-header h4, +.dashboard-card .widget-title h4 { + color: var(--color-text-primary); + font-weight: 700; +} + +.dashboard-card .panel-body, +.dashboard-card .widget-body { + padding: 20px; + min-width: 0; + width: 100%; +} + +/* Ensure XiboData expands in grid */ +.XiboData { + width: 100%; + min-width: 0; + flex: 1; +} + +.kpi-card--modern { + position: relative; + overflow: hidden; +} + +.kpi-card.dashboard-card { + border-radius: 18px; + padding: 22px; +} + +.kpi-card--modern::after { + content: ''; + position: absolute; + inset: 0; + background: radial-gradient(circle at top right, rgba(59, 130, 246, 0.18), transparent 55%); + pointer-events: none; +} + +.kpi-card--modern .kpi-number { + font-size: 36px; + letter-spacing: -0.02em; +} + +.kpi-card--modern .kpi-icon-box { + background: linear-gradient(135deg, rgba(59, 130, 246, 0.28), rgba(99, 102, 241, 0.2)); + color: #e2e8f0; +} + +.action-card--modern { + border-radius: 16px; + background: linear-gradient(180deg, rgba(30, 41, 59, 0.9), rgba(15, 23, 42, 0.88)); + border: 1px solid rgba(148, 163, 184, 0.2); + box-shadow: 0 14px 30px rgba(8, 15, 30, 0.35); +} + +.action-card--modern:hover { + transform: translateY(-4px); + border-color: rgba(59, 130, 246, 0.45); + box-shadow: 0 20px 36px rgba(8, 15, 30, 0.45); +} + +/* OTS dashboard message (theme-dashboard-message.twig) */ +.ots-dashboard-message { + margin: 16px 0 24px; + padding: 12px 16px; + border-radius: 10px; + background: rgba(59, 130, 246, 0.12); + border: 1px solid rgba(59, 130, 246, 0.35); + color: var(--color-text-primary); +} + +.ots-dashboard-message__title { + font-weight: 700; + letter-spacing: 0.02em; + text-transform: uppercase; + font-size: 12px; + margin-bottom: 4px; +} + +.ots-dashboard-message__body { + font-size: 13px; + color: var(--color-text-secondary); +} + .dashboard-page { display: flex; flex-direction: column; @@ -556,11 +990,188 @@ body { gap: 24px; } +/* Dashboard chart cards (bandwidth/library) */ +.dashboard-chart-card { + background: linear-gradient(180deg, rgba(59, 130, 246, 0.06), rgba(59, 130, 246, 0.02)); + border: 1px solid rgba(59, 130, 246, 0.18); + box-shadow: 0 12px 26px rgba(8, 15, 30, 0.35); + display: flex; + flex-direction: column; + min-height: 360px; + align-items: stretch; +} + +.dashboard-chart-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; + width: 100%; + box-sizing: border-box; + float: none; + flex-wrap: wrap; + position: static !important; + inset: auto !important; +} + +.dashboard-chart-card .widget-title, +.dashboard-chart-card .widget-body { + width: 100%; + float: none !important; + clear: both; + display: block !important; + position: static !important; +} + +.dashboard-chart-card .widget-title { + flex: 0 0 auto; +} + +.dashboard-chart-card .widget-body { + position: relative !important; +} + +.dashboard-chart-title { + display: flex; + align-items: center; + gap: 12px; +} + +.dashboard-chart-icon { + width: 40px; + height: 40px; + border-radius: 12px; + background: rgba(59, 130, 246, 0.18); + color: var(--color-primary-light); + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 18px; + flex-shrink: 0; +} + +.dashboard-chart-heading { + margin: 0; + font-size: 16px; + font-weight: 700; +} + +.dashboard-chart-subtitle { + margin: 4px 0 0; + font-size: 12px; + color: var(--color-text-secondary); +} + +.dashboard-chart-link { + font-size: 12px; + font-weight: 600; + color: var(--color-primary); + text-decoration: none; + padding: 6px 10px; + border-radius: 999px; + border: 1px solid rgba(59, 130, 246, 0.24); + background: rgba(59, 130, 246, 0.12); + transition: all var(--transition-fast); + white-space: nowrap; +} + +.dashboard-chart-link:hover { + color: var(--color-text-primary); + border-color: rgba(59, 130, 246, 0.5); + background: rgba(59, 130, 246, 0.22); +} + +.dashboard-chart-body { + padding: 0 20px 20px; + flex: 1; + display: flex; + flex-direction: column; + width: 100%; + box-sizing: border-box; + float: none; + 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; + height: clamp(220px, 32vh, 280px); + width: 100%; + position: relative; + overflow: hidden; + flex: 1; +} + +.dashboard-chart-canvas canvas { + display: block; + width: 100% !important; + height: 100% !important; + max-width: 100% !important; + max-height: 100% !important; + margin: 0 !important; +} + +.dashboard-chart-card .panel-body canvas, +.dashboard-chart-card .widget-body canvas { + min-height: 0 !important; +} + .panel { background-color: var(--color-surface); border: 1px solid var(--color-border); border-radius: 8px; overflow: hidden; + display: flex; + flex-direction: column; +} + +/* Force Xibo panels/cards to dark theme (use higher specificity to override core styles) */ +body .panel, +body .panel.panel-default, +.panel.panel-white, +.panel.card, +.panel.box, +.widget { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +body .panel .panel-body, +body .panel .panel-footer, +body .panel .panel-heading, +.panel .panel-header { + background-color: transparent !important; + color: var(--color-text-primary) !important; + border-color: var(--color-border) !important; +} + +body .panel .panel-heading, +.panel .panel-header { + background-color: var(--color-surface-elevated) !important; + border-bottom: 1px solid var(--color-border) !important; +} + +/* Tables inside panels should inherit dark background and readable text */ +.panel table, +.panel table thead, +.panel table tbody, +.panel table tr, +.panel table td, +.panel table th, +.panel .dataTables_wrapper { + background-color: transparent !important; + color: var(--color-text-primary) !important; +} + +/* Card-specific fallbacks */ +.card, +.card .card-body { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; } .panel-full { @@ -596,6 +1207,10 @@ body { .panel-body { padding: 20px; + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; } .empty-state-compact { @@ -668,6 +1283,311 @@ body { TWO-COLUMN LAYOUT (Displays, Media) ============================================================================ */ +.ots-displays-page .page-header { + margin-bottom: 20px; +} + +.ots-displays-card { + padding: 0; + display: flex; + flex-direction: column; + min-width: 0; +} + +.ots-displays-body { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; +} + +.ots-displays-body .XiboGrid { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; +} + +.ots-displays-title { + font-size: 18px; + font-weight: 700; + letter-spacing: 0.02em; +} + +.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); + margin-bottom: 0; + border-radius: 12px; + overflow: hidden; +} + +/* Filter header with collapse button */ +.ots-filter-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 16px; + border-bottom: 1px solid rgba(148, 163, 184, 0.2); + background: rgba(15, 23, 42, 0.3); +} + +.ots-filter-title { + font-weight: 600; + color: var(--color-text-primary); + font-size: 14px; + margin: 0; +} + +.ots-filter-toggle { + width: 32px; + height: 32px; + padding: 0; + border: none; + background: transparent; + color: var(--color-text-secondary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + border-radius: 6px; + transition: all var(--transition-fast); +} + +.ots-filter-toggle:hover { + background: rgba(59, 130, 246, 0.1); + color: var(--color-primary); +} + +.ots-filter-content { + padding: 16px; + max-height: 600px; + overflow: hidden; + transition: max-height 300ms ease-out, padding 300ms ease-out; +} + +.ots-filter-content.collapsed { + max-height: 0; + padding: 0 16px; + overflow: hidden; +} + +.ots-filter-card .nav-tabs { + border-bottom: 1px solid rgba(148, 163, 184, 0.2); + gap: 8px; + margin-bottom: 14px; +} + +.ots-filter-card .nav-tabs .nav-link { + color: var(--color-text-secondary); + border: 0; + border-radius: 8px; + padding: 10px 14px; + background: transparent; + font-size: 13px; + font-weight: 500; + transition: all var(--transition-fast); +} + +.ots-filter-card .nav-tabs .nav-link.active, +.ots-filter-card .nav-tabs .nav-link:hover { + color: var(--color-text-primary); + background: rgba(59, 130, 246, 0.12); +} + +.ots-filter-card .form-inline .form-group, +.ots-filter-card .form-inline .input-group { + margin-right: 16px; + margin-bottom: 12px; +} + +.ots-filter-card .form-control, +.ots-filter-card select, +.ots-filter-card .select2-selection, +.ots-filter-card .input-group-addon { + background: var(--color-surface) !important; + border: 1px solid var(--color-border) !important; + color: var(--color-text-primary) !important; + border-radius: 6px !important; + padding: 8px 12px !important; + font-size: 13px !important; + transition: all var(--transition-fast) !important; + height: 36px !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; + background: var(--color-surface) !important; +} + +.ots-filter-card label { + color: var(--color-text-secondary); + font-size: 12px; + font-weight: 500; + margin-bottom: 4px; + display: block; +} + +.ots-grid-with-folders { + display: grid; + grid-template-columns: 260px 1fr; + gap: 32px; + align-items: start; + width: 100%; + transition: grid-template-columns 200ms ease-out; +} + +.ots-grid-with-folders.ots-folder-collapsed { + grid-template-columns: 1fr; + gap: 0; +} + +.ots-grid-with-folders.ots-folder-collapsed .ots-folder-tree { + display: none; + width: 0; + overflow: hidden; +} + +.ots-folder-tree { + min-height: 320px; + padding: 16px; + margin-right: -8px; + max-width: 100%; + box-sizing: content-box; +} + +.ots-folder-tree .form-check { + margin-top: 12px; +} + +.ots-grid-controller { + margin-top: 8px; +} + +.ots-grid-controller .btn { + border-radius: 10px; + box-shadow: 0 6px 16px rgba(8, 15, 30, 0.25); +} + +.ots-table-card { + padding: 12px 16px 16px; + flex: 1; + min-width: 0; + overflow: hidden; +} + +#datatable-container { + width: 100%; + overflow-x: auto; +} + +.ots-table-card .table thead th { + background: rgba(15, 23, 42, 0.9); + color: var(--color-text-primary); + border-bottom: 1px solid rgba(148, 163, 184, 0.2); +} + +.ots-table-card .table thead th, +.ots-table-card .table tbody td, +.ots-table-card .table tbody th, +.ots-table-card .table tbody tr { + color: #e2e8f0; +} + +.ots-table-card .table tbody tr { + background: rgba(15, 23, 42, 0.7); +} + +.ots-table-card .table tbody tr:nth-child(even) { + background: rgba(30, 41, 59, 0.75); +} + +.ots-table-card .table tbody tr:hover { + background: rgba(59, 130, 246, 0.14); + color: #ffffff; +} + +.ots-table-card .table tbody tr.selected, +.ots-table-card .table tbody tr.dt-row-selected, +.ots-table-card .table tbody tr.selected td, +.ots-table-card .table tbody tr.dt-row-selected td { + background: rgba(16, 185, 129, 0.25) !important; + color: #ffffff !important; +} + +.ots-table-card .dataTables_wrapper, +.ots-table-card .dataTables_wrapper * { + color: #e2e8f0 !important; +} + +.ots-table-card .dataTables_wrapper .dataTables_filter input, +.ots-table-card .dataTables_wrapper .dataTables_length select { + background: rgba(15, 23, 42, 0.8) !important; + color: #e2e8f0 !important; + border: 1px solid rgba(148, 163, 184, 0.25) !important; +} + +/* Extra specificity for Xibo Displays DataTable */ +.ots-displays-page #datatable-container .XiboData .table, +.ots-displays-page #datatable-container .XiboData table.dataTable { + color: #e2e8f0 !important; +} + +.ots-displays-page #datatable-container .XiboData .table thead th, +.ots-displays-page #datatable-container .XiboData table.dataTable thead th { + color: #cbd5e1 !important; + background-color: var(--color-surface) !important; + font-weight: 600 !important; + opacity: 1 !important; +} + +.ots-displays-page #datatable-container .XiboData .table tbody td, +.ots-displays-page #datatable-container .XiboData table.dataTable tbody td { + color: #e2e8f0 !important; + opacity: 1 !important; +} + +.ots-displays-page #datatable-container .XiboData table.dataTable tbody tr { + background-color: rgba(15, 23, 42, 0.75) !important; +} + +/* Status icons in Displays table */ +.ots-displays-page #datatable-container .fa-check, +.ots-displays-page #datatable-container .fa-check-circle { + color: var(--color-success) !important; +} + +.ots-displays-page #datatable-container .fa-times, +.ots-displays-page #datatable-container .fa-times-circle, +.ots-displays-page #datatable-container .fa-x { + color: var(--color-danger) !important; +} + +.ots-displays-page #datatable-container .XiboData table.dataTable tbody tr:nth-child(even) { + background-color: rgba(30, 41, 59, 0.8) !important; +} + +.ots-table-card .table tbody tr:hover { + background: rgba(59, 130, 246, 0.08); +} + +.ots-map-card { + margin-top: 16px; + min-height: 360px; + background: rgba(15, 23, 42, 0.6); + border: 1px solid rgba(148, 163, 184, 0.2); + border-radius: 16px; +} + +@media (max-width: 1024px) { + .ots-grid-with-folders { + grid-template-columns: 1fr; + } +} + .two-column-layout { display: grid; grid-template-columns: 280px 1fr; @@ -827,8 +1747,8 @@ body { } .table thead { - background-color: var(--color-surface-elevated); - border-bottom: 1px solid var(--color-border); + background-color: var(--color-surface); + border-bottom: 2px solid var(--color-border); } .table thead th { @@ -838,7 +1758,30 @@ body { color: var(--color-text-secondary); text-transform: uppercase; letter-spacing: 0.05em; - font-size: 12px; + font-size: 11px; + background-color: var(--color-surface); +} + +/* Status icons and checkmarks color */ +.table tbody td .fa-check, +.table tbody td .fa-check-circle { + color: var(--color-success); +} + +.table tbody td .fa-times, +.table tbody td .fa-times-circle, +.table tbody td .fa-x { + color: var(--color-danger); +} + +.table tbody td .fa-exclamation, +.table tbody td .fa-exclamation-circle, +.table tbody td .fa-question-circle { + color: var(--color-warning); +} + +.table tbody td .fa-info-circle { + color: var(--color-info); } .table tbody tr { @@ -1023,7 +1966,7 @@ body { } .form-control::placeholder { - color: var(--color-text-tertiary); + color: var(--color-text-secondary); } .form-control:focus { @@ -1088,9 +2031,638 @@ body { } } +/* ============================================================================ + CANVAS / CHART ELEMENTS + ============================================================================ */ + +/* Ensure canvas elements and chart containers render properly */ +canvas { + max-width: 100%; + height: auto; + display: block; +} + +.chart-container, +.chart-wrapper { + position: relative; + width: 100%; + height: 100%; +} +/* OTS sidebar override marker */ +.ots-sidebar-wrapper { + box-shadow: inset 0 0 0 1px rgba(59, 130, 246, 0.15); +} + +.ots-sidebar .sidebar-list a, +.ots-sidebar .sidebar-main a { + display: flex; + align-items: center; + gap: 10px; +} + +.ots-nav-icon { + width: 18px; + text-align: center; + opacity: 0.85; + font-size: 14px; +} + +.ots-nav-text { + flex: 1; + min-width: 0; +} + +/* Xibo sidebar refinements (light) */ +#sidebar-wrapper .sidebar-title a { + color: var(--color-text-tertiary); + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + padding: 12px 16px 6px; +} + +#sidebar-wrapper .sidebar-list a, +#sidebar-wrapper .sidebar-main a { + display: block; + margin: 2px 8px; + padding: 10px 12px; + border-radius: 8px; + transition: background-color var(--transition-fast), color var(--transition-fast); +} + +#sidebar-wrapper .sidebar-list a:hover, +#sidebar-wrapper .sidebar-main a:hover { + background: rgba(59, 130, 246, 0.1); + color: var(--color-primary); +} + +/* Ensure chart parent containers have proper sizing */ +.panel-body canvas, +.panel-body .chart-container { + flex: 1; + min-height: 300px; +} + +.panel-body > .chart-container, +.panel-body > .chart-wrapper { + width: 100% !important; + height: 100% !important; +} + +.panel-body > .chart-container > canvas, +.panel-body > .chart-wrapper > canvas { + width: 100% !important; + height: 100% !important; +} + +.dashboard-chart-card [class*="chart"] { + height: auto; +} + +/* Hidden dashboard chart canvas bridge */ +.chart-sandbox { + position: absolute; + left: -10000px; + top: -10000px; + width: 1px; + height: 1px; + overflow: hidden; + pointer-events: none; +} + +.chart-sandbox canvas { + width: 1px !important; + height: 1px !important; +} + /* Transition variable fallback */ :root { --transition-fast: 150ms ease-in-out; --transition-base: 200ms ease-in-out; --transition-slow: 300ms ease-in-out; } + +/* ================================================================ + Modern table styles (theme override) + Applied: makes XIBO tables look cleaner and responsive + ================================================================ */ + +.modern-table-card { + background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01)); + border-radius: 10px; + box-shadow: 0 6px 18px rgba(2,6,23,0.45); + padding: 18px; + margin: 16px 0; + border: 1px solid rgba(255,255,255,0.02); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial; +} + +.modern-table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + font-size: 13px; + color: var(--color-text-primary); +} + +.modern-table thead th { + background: linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0.03)); + color: var(--color-text-primary); + font-weight: 700; + text-transform: uppercase; + letter-spacing: .06em; + font-size: 11px; + padding: 10px 12px; + border-bottom: 1px solid rgba(255,255,255,0.1); + position: sticky; + top: 0; + z-index: 2; +} + +.modern-table tbody tr { + background: transparent; +} +.modern-table tbody tr:nth-child(odd) { + background: rgba(255,255,255,0.01); +} +.modern-table tbody tr:hover { + background: rgba(59,130,246,0.06); +} + +.modern-table td { + padding: 12px; + vertical-align: middle; + border-bottom: 1px solid rgba(255,255,255,0.02); +} + +.table-controls { + display:flex; + gap:8px; + align-items:center; + justify-content:flex-end; + margin-bottom:12px; +} +.table-controls .btn { + background: var(--color-primary); + color: var(--color-on-primary); + border-radius: 999px; + padding: 6px 10px; + font-size: 13px; + border: none; + cursor: pointer; +} +.table-controls .btn.ghost { + background:transparent; + color:var(--color-primary); + border:1px solid rgba(59,130,246,0.12); +} + +.table-meta { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + padding-top: 10px; + font-size: 13px; + color: var(--color-text-secondary); +} + +@media (max-width: 880px) { + .modern-table thead th:nth-child(n+6), + .modern-table tbody td:nth-child(n+6) { + display: none; + } + .modern-table tbody td.details-toggle { + display: table-cell; + } + .modern-table-card { + padding: 12px; + } +} + +/* Improve legibility: force table and datatables text to full contrast */ +.modern-table, +.modern-table tbody, +.modern-table tbody tr, +.modern-table td, +.dataTables_wrapper, +.dataTables_wrapper table, +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_length, +.dataTables_wrapper .dataTables_filter { + color: var(--Color-fallback, var(--color-text-primary)) !important; + opacity: 1 !important; +} + +/* Stronger selected row for readability */ +.modern-table tbody tr.selected, +.modern-table tbody tr.dt-row-selected, +.dataTables_wrapper table tbody tr.selected, +.dataTable tbody tr.selected { + background: rgba(16,185,129,0.14) !important; /* stronger mint */ + color: var(--color-text-primary) !important; +} + +/* Inputs and selects used in filters/search should be readable */ +.dataTables_wrapper .dataTables_filter input, +.dataTables_wrapper .dataTables_length select, +.topbar-search .search-input, +.table-controls input, +input[type="search"], +input[type="text"], +select, +textarea { + background: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +/* Buttons - ensure contrast */ +.btn, +.button, +.dataTables_wrapper .dataTables_paginate .paginate_button { + color: var(--color-text-primary) !important; +} + +/* If panels or overlays dim content, ensure table contents remain fully visible */ +.panel, +.panel-body, +.table-wrapper, +.dataTables_wrapper .dataTables_scrollBody { + opacity: 1 !important; + background: transparent !important; +} + + +/* DataTables color adjustments to ensure legibility in dark theme */ +.dataTables_wrapper, +.dataTables_wrapper * { + color: var(--color-text-primary) !important; +} +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_paginate { + color: var(--color-text-primary) !important; +} +.dataTables_wrapper .dataTables_paginate .paginate_button { + background: transparent; + color: var(--color-text-primary) !important; + border: 1px solid transparent; + border-radius: 6px; + padding: 4px 8px; +} +.dataTables_wrapper .dataTables_paginate .paginate_button.current, +.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { + background: var(--color-primary) !important; + color: var(--color-on-primary) !important; + border-color: rgba(255,255,255,0.06); +} + +/* Global legibility overrides */ +body, +.ots-content, +.content, +.container, +.card, +.panel, +.panel-body, +.table, +.dataTables_wrapper, +.dropdown-menu, +.dropdown-right { + color: var(--color-text-primary) !important; +} + +.text-muted, +.text-secondary, +.text-tertiary { + color: var(--color-text-secondary) !important; + opacity: 1 !important; +} + +.ots-sidebar a, +.ots-sidebar .nav-item, +.ots-sidebar .nav-label, +.ots-sidebar .nav-text { + color: var(--color-text-primary) !important; +} + +a, +.nav-link { + color: var(--color-text-primary); +} + +a:hover, +.nav-link:hover { + color: var(--color-primary); +} + +/* ============================================================================ + COMPREHENSIVE DARK THEME ENFORCEMENT + Ensure all elements have readable contrast and dark backgrounds + ============================================================================ */ + +/* Force ALL text to be readable - master override */ +* { + color-scheme: dark; +} + +/* Ensure headings are always visible */ +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + color: var(--color-text-primary) !important; +} + +/* All labels must be readable */ +label, +.label, +.form-label, +legend { + color: var(--color-text-primary) !important; +} + +/* Dropdowns, modals, and popovers */ +.dropdown-menu, +.dropdown-toggle, +.popover, +.modal, +.modal-content, +.modal-header, +.modal-body, +.modal-footer { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +.modal-header { + background-color: var(--color-surface-elevated) !important; + border-bottom: 1px solid var(--color-border) !important; +} + +.dropdown-menu a, +.dropdown-item { + color: var(--color-text-primary) !important; +} + +.dropdown-menu a:hover, +.dropdown-item:hover { + background-color: rgba(59, 130, 246, 0.12) !important; + color: var(--color-primary) !important; +} + +/* Ensure form elements are dark and readable */ +.form-group, +.form-check, +.input-group { + color: var(--color-text-primary) !important; +} + +input, +textarea, +select, +.form-control, +.form-select { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +input::placeholder, +textarea::placeholder { + color: var(--color-text-secondary) !important; +} + +input:focus, +textarea:focus, +select:focus { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border-color: var(--color-primary) !important; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important; +} + +/* Alerts and messages */ +.alert, +.alert-info, +.alert-warning, +.alert-success, +.alert-danger, +.alert-light { + background-color: var(--color-surface) !important; + border: 1px solid var(--color-border) !important; + color: var(--color-text-primary) !important; +} + +.alert-light { + background-color: var(--color-surface-elevated) !important; +} + +/* Tooltips and help text */ +.tooltip, +.tooltip-inner, +.help-block, +.form-text, +small { + color: var(--color-text-secondary) !important; + background-color: transparent !important; +} + +/* Breadcrumbs */ +.breadcrumb { + background-color: transparent !important; + color: var(--color-text-primary) !important; +} + +.breadcrumb a { + color: var(--color-primary) !important; +} + +.breadcrumb-item.active { + color: var(--color-text-secondary) !important; +} + +/* Pagination */ +.pagination, +.pager { + background-color: transparent !important; +} + +.pagination a, +.pagination button, +.pager a { + color: var(--color-text-primary) !important; + background-color: transparent !important; + border: 1px solid var(--color-border) !important; +} + +.pagination a:hover, +.pagination button:hover, +.pager a:hover { + background-color: var(--color-surface) !important; + color: var(--color-primary) !important; + border-color: var(--color-primary) !important; +} + +.pagination .active a, +.pagination .active button, +.pager .active a { + background-color: var(--color-primary) !important; + border-color: var(--color-primary) !important; + color: white !important; +} + +/* Disabled state */ +[disabled], +:disabled, +.disabled { + opacity: 0.6 !important; + color: var(--color-text-secondary) !important; +} + +/* Badge customization */ +.badge, +.badge-default { + background-color: var(--color-surface-elevated) !important; + color: var(--color-text-primary) !important; +} + +/* List groups */ +.list-group, +.list-group-item { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +.list-group-item:hover { + background-color: var(--color-surface-elevated) !important; +} + +.list-group-item.active { + background-color: var(--color-primary) !important; + border-color: var(--color-primary) !important; + color: white !important; +} + +/* Tabs */ +.nav-tabs, +.nav-pills { + background-color: transparent !important; +} + +.nav-tabs .nav-link, +.nav-pills .nav-link { + color: var(--color-text-secondary) !important; + background-color: transparent !important; +} + +.nav-tabs .nav-link:hover, +.nav-pills .nav-link:hover { + color: var(--color-text-primary) !important; + background-color: rgba(59, 130, 246, 0.1) !important; +} + +.nav-tabs .nav-link.active, +.nav-pills .nav-link.active { + color: white !important; + background-color: var(--color-primary) !important; + border-color: var(--color-primary) !important; +} + +/* Cards */ +.card { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +.card-header { + background-color: var(--color-surface-elevated) !important; + border-bottom: 1px solid var(--color-border) !important; + color: var(--color-text-primary) !important; +} + +.card-footer { + background-color: var(--color-surface-elevated) !important; + border-top: 1px solid var(--color-border) !important; + color: var(--color-text-secondary) !important; +} + +/* Progress bars */ +.progress { + background-color: var(--color-surface-elevated) !important; +} + +.progress-bar { + background-color: var(--color-primary) !important; +} + +/* Ensure code blocks are readable */ +code, +.code, +pre { + background-color: var(--color-surface-elevated) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +/* DataTables specific overrides */ +.dataTables_wrapper { + color: var(--color-text-primary) !important; +} + +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_paginate { + background-color: transparent !important; + color: var(--color-text-primary) !important; +} + +.dataTables_wrapper table tbody tr { + color: var(--color-text-primary) !important; +} + +.dataTables_wrapper table tbody tr:hover { + background-color: rgba(59, 130, 246, 0.08) !important; +} + +/* Spinner/loading indicators */ +.spinner, +.spinner-border, +.spinner-grow { + border-color: rgba(255, 255, 255, 0.25) !important; +} + +.spinner-border.text-primary, +.spinner-grow.text-primary { + color: var(--color-primary) !important; + border-color: var(--color-primary) !important; +} + +/* Links should always be visible and contrasting */ +a { + color: var(--color-primary) !important; + text-decoration: none; +} + +a:hover { + color: var(--color-primary-light) !important; + text-decoration: underline; +} + +a.text-muted { + color: var(--color-text-secondary) !important; +} + +a.text-muted:hover { + color: var(--color-text-primary) !important; +} + +/* Ensure absolutely nothing is invisible */ +.hidden, +[hidden] { + display: none !important; +} + +.invisible { + visibility: hidden !important; +} diff --git a/custom/otssignange/js/theme.js b/custom/otssignange/js/theme.js index 3cb2b5b..c9a5e3f 100644 --- a/custom/otssignange/js/theme.js +++ b/custom/otssignange/js/theme.js @@ -131,6 +131,123 @@ }); } + /** + * Prevent Chart.js errors when chart elements are missing + */ + function initChartSafeguard() { + if (!window.Chart) return; + + if (typeof window.Chart.acquireContext === 'function') { + window.Chart.acquireContext = function(item) { + if (!item) return null; + + const candidate = item.length ? item[0] : item; + if (candidate && typeof candidate.getContext === 'function') { + return candidate.getContext('2d'); + } + + return null; + }; + return; + } + + if (window.Chart.prototype && typeof window.Chart.prototype.acquireContext === 'function') { + window.Chart.prototype.acquireContext = function(item) { + if (!item) return null; + + const candidate = item.length ? item[0] : item; + if (candidate && typeof candidate.getContext === 'function') { + return candidate.getContext('2d'); + } + + return null; + }; + } + } + + /** + * Enhance tables: wrap in card, add per-table search box, client-side filtering + * Non-destructive: skips tables already enhanced + */ + function enhanceTables() { + const selector = '.ots-content table, .content table, .container table, .card table, table'; + const tables = Array.from(document.querySelectorAll(selector)); + let counter = 0; + + tables.forEach(table => { + // only enhance tables that have a thead and tbody + if (!table || table.classList.contains('modern-table')) return; + if (!table.querySelector('thead') || !table.querySelector('tbody')) return; + + counter += 1; + table.classList.add('modern-table'); + + // Build wrapper structure + const wrapper = document.createElement('div'); + wrapper.className = 'modern-table-card'; + + const controls = document.createElement('div'); + controls.className = 'table-controls'; + + const input = document.createElement('input'); + input.type = 'search'; + input.placeholder = 'Search…'; + input.className = 'table-search-input'; + input.setAttribute('aria-label', 'Table search'); + input.style.minWidth = '180px'; + + controls.appendChild(input); + + const tableWrapper = document.createElement('div'); + tableWrapper.className = 'table-wrapper'; + tableWrapper.style.overflow = 'auto'; + + // Insert wrapper into DOM in place of the table + const parent = table.parentNode; + parent.replaceChild(wrapper, table); + wrapper.appendChild(controls); + wrapper.appendChild(tableWrapper); + tableWrapper.appendChild(table); + + // Simple, light-weight search filtering for this table only + input.addEventListener('input', function (e) { + const term = (e.target.value || '').toLowerCase(); + table.querySelectorAll('tbody tr').forEach(tr => { + const text = tr.textContent.toLowerCase(); + tr.style.display = term === '' || text.includes(term) ? '' : 'none'; + }); + }); + }); + } + + /** + * Initialize DataTables for enhanced behavior when available. + * Falls back gracefully if DataTables or jQuery are not present. + */ + function initDataTables() { + if (!window.jQuery) return; + const $ = window.jQuery; + if (!$.fn || !$.fn.dataTable) return; + + $('.modern-table, table').each(function () { + try { + if (!$.fn.dataTable.isDataTable(this)) { + $(this).DataTable({ + responsive: true, + lengthChange: false, + pageLength: 10, + autoWidth: false, + dom: '<"table-controls"f>rt<"table-meta"ip>', + language: { search: '' } + }); + } + } catch (err) { + // If initialization fails, ignore and allow fallback enhancer + console.warn('DataTables init failed for table', this, err); + } + }); + } + /** * Initialize all features when DOM is ready */ @@ -139,35 +256,13 @@ initDropdowns(); initSearch(); initPageInteractions(); + initDataTables(); + enhanceTables(); makeResponsive(); + initChartSafeguard(); } // Wait for DOM to be ready - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', init); - } else { - init(); - } -})(); - - themeBtn.addEventListener('click', function() { - const currentTheme = html.getAttribute('data-theme') || 'light'; - const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; - - html.setAttribute('data-theme', newTheme); - localStorage.setItem(STORAGE_KEYS.themeMode, newTheme); - themeBtn.setAttribute('aria-pressed', newTheme === 'dark'); - }); - } - - /** - * Initialize page on DOM ready - */ - function init() { - initSidebarToggle(); - initThemeToggle(); - } - if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { diff --git a/custom/otssignange/views/authed-sidebar.twig b/custom/otssignange/views/authed-sidebar.twig index 4c92876..b351ddf 100644 --- a/custom/otssignange/views/authed-sidebar.twig +++ b/custom/otssignange/views/authed-sidebar.twig @@ -1,116 +1,356 @@ -{# - OTS Signage Modern Theme - Sidebar Override - Modern left navigation sidebar with collapsible state and icons -#} - + diff --git a/custom/otssignange/views/authed-theme-topbar.twig b/custom/otssignange/views/authed-theme-topbar.twig new file mode 100644 index 0000000..7b5486e --- /dev/null +++ b/custom/otssignange/views/authed-theme-topbar.twig @@ -0,0 +1,8 @@ +{# + OTS Signage Theme override + Optional include rendered in authed.twig (top right navbar) + Minimal, low-risk addition for verification +#} + diff --git a/custom/otssignange/views/authed-topbar.twig b/custom/otssignange/views/authed-topbar.twig new file mode 100644 index 0000000..3c8d7d4 --- /dev/null +++ b/custom/otssignange/views/authed-topbar.twig @@ -0,0 +1,472 @@ +{# +/** + * Copyright (C) 2023 Xibo Signage Ltd + * + * Xibo - Digital Signage - https://xibosignage.com + * + * This file is part of Xibo. + * + * Xibo is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Xibo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Xibo. If not, see . + */ +#} + diff --git a/custom/otssignange/views/authed-user-menu.twig b/custom/otssignange/views/authed-user-menu.twig new file mode 100644 index 0000000..7fc811d --- /dev/null +++ b/custom/otssignange/views/authed-user-menu.twig @@ -0,0 +1,33 @@ +{# + OTS Signage Theme override + Based on Xibo CMS default authed-user-menu.twig (master branch) + Minimal change: add ots-user-menu class for easy verification +#} + diff --git a/custom/otssignange/views/authed.twig b/custom/otssignange/views/authed.twig deleted file mode 100644 index 869180f..0000000 --- a/custom/otssignange/views/authed.twig +++ /dev/null @@ -1,83 +0,0 @@ -{# - OTS Signage Modern Theme - Authenticated Shell Override - Replaces the header and shell structure with a modern topbar + sidebar layout -#} -{% extends "base.twig" %} - -{% block head %} - {{ parent() }} - - -{% endblock %} - -{% block htmlTag %} - -{% endblock %} - -{% block body %} - -
- {% include "authed-sidebar.twig" %} - -
- {% block header %} -
-
- -
-

{{ pageTitle|default('Dashboard') }}

- {% if pageSubtitle is defined %}

{{ pageSubtitle }}

{% endif %} -
-
- -
- -
- - -
-
-
- {% endblock %} - -
- {% block content %} - - {% endblock %} -
- - {% block footer %} -
-

© {{ currentDate|date('Y') }} {{ app_name }}. Powered by Xibo.

-
- {% endblock %} -
-
- - {% block scripts %} - {{ parent() }} - - {% endblock %} - -{% endblock %} diff --git a/custom/otssignange/views/dashboard-status-page.twig b/custom/otssignange/views/dashboard-status-page.twig new file mode 100644 index 0000000..4f07d7d --- /dev/null +++ b/custom/otssignange/views/dashboard-status-page.twig @@ -0,0 +1,1005 @@ +{# +/** + * Copyright (C) 2021 Xibo Signage Ltd + * + * Xibo - Digital Signage - http://www.xibo.org.uk + * + * This file is part of Xibo. + * + * Xibo is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Xibo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Xibo. If not, see . + */ +#} +{% extends "authed.twig" %} +{% import "inline.twig" as inline %} + +{% block pageContent %} + {% include "theme-dashboard-message.twig" ignore missing %} + +
+ + +
+
+
+

{% trans "Displays" %}

+
+
+
+
0
+
+
+ +
+ +
+
+

{% trans "Library" %}

+
+
+
+
{{ librarySize }}
+
{% trans "Library Size" %}
+
+ +
+ +
+
+

{% trans "Users" %}

+
+
+
+
{{ countUsers }}
+
{% if countUsers == 1 %}{% trans "User" %}{% else %}{% trans "Users" %}{% endif %}
+
+ +
+ +
+
+

{% trans "Now Showing" %}

+
+
+
+ {% if embeddedWidget != "" %} + {{ embeddedWidget|raw }} + {% else %} +
{{ nowShowing }}
+
{% trans "Now Showing" %}
+ {% endif %} +
+ +
+
+ +
+
+
+
+ +
+
+ {% if xmdsLimit != "" %} + {% trans %}Bandwidth Usage{% endtrans %} + {% else %} + {% trans %}Bandwidth Usage{% endtrans %} + {% endif %} +
+
+ {% if xmdsLimit != "" %} + {% trans %}Limit {{ xmdsLimit }}{% endtrans %} + {% else %} + {% trans %}Reporting in {{ bandwidthSuffix }}{% endtrans %} + {% endif %} +
+
+
+ {% if currentUser.featureEnabled("displays.reporting") %} + {% trans "More Statistics" %} + {% endif %} +
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+ {% trans "Library Usage" %} +
+
+ {% if libraryLimitSet != "" %} + {% trans %}Limit {{ libraryLimit }}{% endtrans %} + {% else %} + {% trans "Storage distribution" %} + {% endif %} +
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+

{% trans "Display Activity" %}

+
+
+
+ + + + + + + + + +
{% trans "Display" %}{% trans "Logged In" %}{% trans "Authorised" %}
+
+
+
+ +
+
+

{% trans "Latest News" %}

+
+
+ {% if latestNews|length > 0 %} + {% for news in latestNews %} +
+

{{ news.title }}

+

{{ news.description|raw }} {% if news.link %}{% trans "Full Article" %}.{% endif %}

+
+ {% endfor %} + {% else %} +
+

{% trans "No news available." %}

+
+ {% endif %} +
+
+
+ +
+
+
+

{% trans "Display Status" %}

+
+
+
{% trans "Click on the chart for a breakdown" %}
+
+ +
+
+
+ +
+
+

{% trans "Display Content Status" %}

+
+
+
{% trans "Click on the chart for a breakdown" %}
+
+ +
+
+
+
+ +
+
+
+

{% trans "Display Groups Status" %}

+
+
+
{% trans "Click on the chart to view Display information" %}
+
+ +
+
+
+ +
+
+

{% trans "Display Groups Content Status" %}

+
+
+
{% trans "Click on the chart to view Display information" %}
+
+ +
+
+
+
+ +
+
+

{% trans "Displays" %}

+ {% if currentUser.featureEnabled("displays.view") %} + {% trans "Displays Page" %} + {% endif %} +
+
+
+
+
+
+
+
+ {{ inline.hidden("displayGroupId") }} + {{ inline.hidden("mediaInventoryStatus") }} + {{ inline.hidden("loggedIn") }} +
+
+
+
+
+
+ + + + + + + + + + + + + + {% if currentUser.featureEnabled("tag.tagging") %}{% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% trans "ID" %}{% trans "Display" %}{% trans "Status" %}{% trans "Authorised?" %}{% trans "Current Layout" %}{% trans "Storage Available" %}{% trans "Storage Total" %}{% trans "Storage Free %" %}{% trans "Description" %}{% trans "Orientation" %}{% trans "Resolution" %}{% trans "Tags" %}{% trans "Default Layout" %}{% trans "Interleave Default" %}{% trans "Email Alert" %}{% trans "Logged In" %}{% trans "Last Accessed" %}{% trans "Display Profile" %}{% trans "Version" %}{% trans "Device Name" %}{% trans "IP Address" %}{% trans "Mac Address" %}{% trans "Timezone" %}{% trans "Latitude" %}{% trans "Longitude" %}{% trans "Screen shot?" %}{% trans "Thumbnail" %}{% trans "CMS Transfer?" %}{% trans "Bandwidth Limit" %}{% trans "Last Command" %}{% trans "XMR Registered" %}{% trans "Commercial Licence" %}{% trans "Remote" %}{% trans "Created Date" %}{% trans "Modified Date" %}{% trans "Faults?" %}
+
+
+
+
+ +
+

{% trans "Quick Actions" %}

+
+ {% if currentUser.featureEnabled("schedule.view") %} + +
+
{% trans "Create Schedule" %}
+
+ {% endif %} + + {% if currentUser.featureEnabled("displays.view") %} + +
+
{% trans "Manage Displays" %}
+
+ {% endif %} + + {% if currentUser.featureEnabled("users.view") and (currentUser.isGroupAdmin() or currentUser.isSuperAdmin()) %} + +
+
{% trans "Add User" %}
+
+ {% endif %} +
+
+
+{% endblock %} + +{% block javaScript %} + +{% endblock %} diff --git a/custom/otssignange/views/dashboard.twig b/custom/otssignange/views/dashboard.twig deleted file mode 100644 index ade3bab..0000000 --- a/custom/otssignange/views/dashboard.twig +++ /dev/null @@ -1,131 +0,0 @@ -{# - OTS Signage Modern Theme - Dashboard Page Override - Modern dashboard with KPI cards, status panels, and quick actions -#} -{% extends "authed.twig" %} - -{% block pageTitle %}Dashboard{% endblock %} - -{% block content %} -
- {# KPI Cards Row #} -
-
-
-

Displays

- - - - - -
-
-
1
-
100% Displays Online
-
- -
- -
-
-

Schedules

- - - - - -
-
-
0
-
Scheduled events
-
- -
- -
-
-

Users

- - - - - -
-
-
2
-
OTS Signs users
-
- -
-
- - {# Main Panels Row #} -
- - - -
- - {# Quick Actions Section #} -
-

Quick Actions

- -
-
-{% endblock %} diff --git a/custom/otssignange/views/datatable-contrast.twig b/custom/otssignange/views/datatable-contrast.twig new file mode 100644 index 0000000..df2b042 --- /dev/null +++ b/custom/otssignange/views/datatable-contrast.twig @@ -0,0 +1,66 @@ +/* High-specificity DataTables contrast overrides + Ensures table body text is readable against dark theme backgrounds. + Light text on dark backgrounds. +*/ + +#datatable-container table.dataTable tbody td, +#datatable-container .dataTables_wrapper table.dataTable tbody td, +.ots-table-card table.dataTable tbody td, +.ots-table-card table.dataTable tbody td * { + color: #f5f5f5 !important; + opacity: 1 !important; +} + +#datatable-container table.dataTable thead th, +.ots-table-card table.dataTable thead th, +#datatable-container table.dataTable thead th * { + color: #ffffff !important; + opacity: 1 !important; + background-color: rgba(0,0,0,0.3) !important; +} + +#datatable-container table.dataTable tbody tr.table-success td, +#datatable-container table.dataTable tbody tr.success td, +#datatable-container table.dataTable tbody tr.selected td, +#datatable-container table.dataTable tbody tr.highlight td { + background-color: rgba(255,255,255,0.08) !important; + color: #ffffff !important; +} + +#datatable-container table.dataTable tbody td .btn, +#datatable-container table.dataTable tbody td .badge, +#datatable-container table.dataTable tbody td .dropdown-toggle { + color: #ffffff !important; +} + +#datatable-container table.dataTable tbody tr { + background-color: rgba(0,0,0,0.1) !important; +} + +#datatable-container table.dataTable tbody tr:hover { + background-color: rgba(255,255,255,0.05) !important; +} + +.dataTables_wrapper .dataTables_filter input, +.dataTables_wrapper .dataTables_length select, +.dataTables_wrapper .dataTables_paginate .paginate_button { + color: #f5f5f5 !important; + background: rgba(0,0,0,0.2) !important; + border-color: rgba(255,255,255,0.1) !important; +} + +#datatable-container table.dataTable tbody td img, +#datatable-container table.dataTable tbody td svg { + filter: none !important; +} + +#datatable-container table.dataTable thead th.sorting:after, +#datatable-container table.dataTable thead th.sorting_asc:after, +#datatable-container table.dataTable thead th.sorting_desc:after { + color: rgba(255,255,255,0.7) !important; +} + +.ots-table-card table.dataTable tbody tr td, +.ots-table-card table.dataTable tbody tr td * { + -webkit-text-fill-color: #f5f5f5 !important; +} diff --git a/custom/otssignange/views/display-page.twig b/custom/otssignange/views/display-page.twig new file mode 100644 index 0000000..0ceb6ad --- /dev/null +++ b/custom/otssignange/views/display-page.twig @@ -0,0 +1,384 @@ +{# +/** + * Copyright (C) 2023 Xibo Signage Ltd + * + * Xibo - Digital Signage - http://www.xibo.org.uk + * + * This file is part of Xibo. + * + * Xibo is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Xibo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Xibo. If not, see . + */ +#} +{% extends "authed.twig" %} +{% import "inline.twig" as inline %} + +{% block title %}{{ "Displays"|trans }} | {% endblock %} + +{% block actionMenu %} +
+ {% if currentUser.featureEnabled("displays.add") %} + + {% endif %} + +
+{% endblock %} + +{% block headContent %} + {# Add page source code bundle ( CSS ) #} + +{% endblock %} + +{% block pageContent %} +
+ + +
+
{% trans "Displays" %}
+
+
+
+
+

{% trans "Filter Displays" %}

+ +
+
+
+ +
+
+
+ {% set title %}{% trans "ID" %}{% endset %} + {{ inline.number("displayId", title) }} + + {% set title %}{% trans "Name" %}{% endset %} + {{ inline.inputNameGrid('display', title) }} + + {% set title %}{% trans "Status" %}{% endset %} + {% set check %}{% trans "Up to date" %}{% endset %} + {% set cross %}{% trans "Downloading" %}{% endset %} + {% set cloud %}{% trans "Out of date" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: "1", option: check}, + { optionid: "2", option: cross}, + { optionid: "3", option: cloud} + ] %} + {{ inline.dropdown("mediaInventoryStatus", "single", title, "", options, "optionid", "option") }} + + {% set title %}{% trans "Logged In?" %}{% endset %} + {% set yesOption %}{% trans "Yes" %}{% endset %} + {% set noOption %}{% trans "No" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: "1", option: yesOption}, + { optionid: "0", option: noOption} + ] %} + {{ inline.dropdown("loggedIn", "single", title, "", options, "optionid", "option") }} + + {% set title %}{% trans "Authorised?" %}{% endset %} + {% set yesOption %}{% trans "Yes" %}{% endset %} + {% set noOption %}{% trans "No" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: "1", option: yesOption }, + { optionid: "0", option: noOption}, + ] %} + {{ inline.dropdown("authorised", "single", title, "", options, "optionid", "option") }} + + {% set title %}{% trans "XMR Registered?" %}{% endset %} + {% set yesOption %}{% trans "Yes" %}{% endset %} + {% set noOption %}{% trans "No" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: 1, option: yesOption}, + { optionid: 0, option: noOption}, + ] %} + {{ inline.dropdown("xmrRegistered", "single", title, "", options, "optionid", "option") }} + + {% if currentUser.featureEnabled("tag.tagging") %} + {% set title %}{% trans "Tags" %}{% endset %} + {% set exactTagTitle %}{% trans "Exact match?" %}{% endset %} + {% set logicalOperatorTitle %}{% trans "When filtering by multiple Tags, which logical operator should be used?" %}{% endset %} + {% set helpText %}{% trans "A comma separated list of tags to filter by. Enter a tag|tag value to filter tags with values. Enter --no-tag to filter all items without tags. Enter - before a tag or tag value to exclude from results." %}{% endset %} + {{ inline.inputWithTags("tags", title, null, helpText, null, null, null, "exactTags", exactTagTitle, logicalOperatorTitle) }} + {% endif %} + + {% if currentUser.featureEnabled("displaygroup.view") %} + {% set title %}{% trans "Display Group" %}{% endset %} + {% set attributes = [ + { name: "data-width", value: "200px" }, + { name: "data-allow-clear", value: "true" }, + { name: "data-placeholder--id", value: null }, + { name: "data-placeholder--value", value: "" }, + { name: "data-search-url", value: url_for("displayGroup.search") }, + { name: "data-filter-options", value: '{"isDisplaySpecific":0}' }, + { name: "data-search-term", value: "displayGroup" }, + { name: "data-id-property", value: "displayGroupId" }, + { name: "data-text-property", value: "displayGroup" }, + { name: "data-initial-key", value: "displayGroupId" }, + ] %} + {{ inline.dropdown("displayGroupId", "single", title, "", null, "displayGroupId", "displayGroup", helpText, "pagedSelect", "", "", "", attributes) }} + {% endif %} + + {% if currentUser.featureEnabled("displayprofile.view") %} + {% set title %}{% trans "Display Profile" %}{% endset %} + {{ inline.dropdown("displayProfileId", "single", title, "", [{displayProfileId:null, name:""}]|merge(displayProfiles), "displayProfileId", "name") }} + {% endif %} + + {{ inline.hidden("folderId") }} +
+ +
+ {% set title %}{% trans "Last Accessed" %}{% endset %} + {{ inline.date("lastAccessed", title) }} + + {% set title %}{% trans "Player Type" %}{% endset %} + {% set android %}{% trans "Android" %}{% endset %} + {% set chromeos %}{% trans "ChromeOS" %}{% endset %} + {% set windows %}{% trans "Windows" %}{% endset %} + {% set webos %}{% trans "webOS" %}{% endset %} + {% set sssp %}{% trans "Tizen" %}{% endset %} + {% set linux %}{% trans "Linux" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: "android", option: android}, + { optionid: "chromeos", option: chromeos}, + { optionid: "windows", option: windows}, + { optionid: "lg", option: webos}, + { optionid: "sssp", option: sssp}, + { optionid: "linux", option: linux}, + ] %} + {{ inline.dropdown("clientType", "single", title, "", options, "optionid", "option") }} + + {% set title %}{% trans "Player Code" %}{% endset %} + {{ inline.input("clientCode", title) }} + + {% set title %}{% trans "Custom ID" %}{% endset %} + {{ inline.input("customId", title) }} + + {% set title %}{% trans "Mac Address" %}{% endset %} + {{ inline.input("macAddress", title) }} + + {% set title %}{% trans "IP Address" %}{% endset %} + {{ inline.input("clientAddress", title) }} + + {% set title %}{% trans "Orientation" %}{% endset %} + {% set landscape %}{% trans "Landscape" %}{% endset %} + {% set portrait %}{% trans "Portrait" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: "landscape", option: landscape}, + { optionid: "portrait", option: portrait} + ] %} + {{ inline.dropdown("orientation", "single", title, "", options, "optionid", "option") }} + + {% set title %}{% trans "Commercial Licence" %}{% endset %} + {% set licensed %}{% trans "Licensed fully" %}{% endset %} + {% set trial %}{% trans "Trial" %}{% endset %} + {% set notLinceced %}{% trans "Not licenced" %}{% endset %} + {% set notApplicable %}{% trans "Not applicable" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: "1", option: licensed}, + { optionid: "2", option: trial}, + { optionid: "0", option: notLinceced}, + { optionid: "3", option: notApplicable} + ] %} + {{ inline.dropdown("commercialLicence", "single", title, "", options, "optionid", "option") }} + + {% set title %}{% trans "Player supported?" %}{% endset %} + {% set yesOption %}{% trans "Yes" %}{% endset %} + {% set noOption %}{% trans "No" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: 1, option: yesOption}, + { optionid: 0, option: noOption}, + ] %} + {{ inline.dropdown("isPlayerSupported", "single", title, "", options, "optionid", "option") }} +
+
+
+
+
+
+
+
+ +
+ + +
+
+

{% trans 'No Folders matching the search term' %}

+
+
+
+
+ + +
+
+ +
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + {% if currentUser.featureEnabled("tag.tagging") %}{% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% trans "ID" %}{% trans "Display" %}{% trans "Display Type" %}{% trans "Address" %}{% trans "Status" %}{% trans "Authorised?" %}{% trans "Current Layout" %}{% trans "Storage Available" %}{% trans "Storage Total" %}{% trans "Storage Free %" %}{% trans "Description" %}{% trans "Orientation" %}{% trans "Resolution" %}{% trans "Tags" %}{% trans "Default Layout" %}{% trans "Interleave Default" %}{% trans "Email Alert" %}{% trans "Logged In" %}{% trans "Last Accessed" %}{% trans "Display Profile" %}{% trans "Version" %}{% trans "Supported?" %}{% trans "Device Name" %}{% trans "IP Address" %}{% trans "Mac Address" %}{% trans "Timezone" %}{% trans "Languages" %}{% trans "Latitude" %}{% trans "Longitude" %}{% trans "Screen shot?" %}{% trans "Thumbnail" %}{% trans "CMS Transfer?" %}{% trans "Bandwidth Limit" %}{% trans "Last Command" %}{% trans "XMR Registered" %}{% trans "Commercial Licence" %}{% trans "Remote" %}{% trans "Sharing" %}{% trans "Screen Size" %}{% trans "Is Mobile?" %}{% trans "Outdoor?" %}{% trans "Reference 1" %}{% trans "Reference 2" %}{% trans "Reference 3" %}{% trans "Reference 4" %}{% trans "Reference 5" %}{% trans "Custom ID" %}{% trans "Cost Per Play" %}{% trans "Impressions Per Play" %}{% trans "Created Date" %}{% trans "Modified Date" %}{% trans "Faults?" %}{% trans "OS Version" %}{% trans "OS SDK" %}{% trans "Manufacturer" %}{% trans "Brand" %}{% trans "Model" %}
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+
+{% endblock %} + +{% block javaScript %} + {# Initialise JS variables and translations #} + + + {# Add page source code bundle ( JS ) #} + + +{% endblock %} diff --git a/custom/otssignange/views/displays.twig b/custom/otssignange/views/displays.twig deleted file mode 100644 index f6419bf..0000000 --- a/custom/otssignange/views/displays.twig +++ /dev/null @@ -1,124 +0,0 @@ -{# - OTS Signage Modern Theme - Displays Page Override - Two-column layout with folder panel on left, modern display table -#} -{% extends "authed.twig" %} - -{% block pageTitle %}Displays{% endblock %} - -{% block content %} -
- - -
- - -
-
- - - - -
-
- - Add Display -
-
- -
-
-
Total
-
1
-
-
-
Online
-
1
-
-
-
Offline
-
0
-
-
- -
- - - - - - - - - - - - - - - - - - - - - -
DisplayStatusFolderGroupLast Check-inActions
Test1OnlineTEMPLATE_DemoHolderTest Screensjust now - -
-
-
-
-{% endblock %} \ No newline at end of file diff --git a/custom/otssignange/views/media.twig b/custom/otssignange/views/media.twig deleted file mode 100644 index 8fb832e..0000000 --- a/custom/otssignange/views/media.twig +++ /dev/null @@ -1,129 +0,0 @@ -{# - OTS Signage Modern Theme - Media Library Page Override - Two-column layout with folder panel on left, media grid on right -#} -{% extends "authed.twig" %} - -{% block pageTitle %}Media Library{% endblock %} - -{% block content %} -
- - -
- - -
-
- - - - -
-
- - Upload Media -
-
- -
-
-
Total Files
-
4
-
-
-
Storage Used
-
12.3 MB
-
-
-
Storage Limit
-
5 GB
-
-
- -
-
-
- Galaxy space - Image -
-
-

2000x1158

-

3.3 MB • 1920x1112

-
-
- -
-
- Cat portrait - Image -
-
-

Images.jpg

-

5.2 KB • 194x260

-
-
- -
-
- OTS Logo - Image -
-
-

OTS Logo

-

2.9 KB • 360x350

-
-
- -
-
- Sunrise Hotel - Image -
-
-

suncrest hotel l...

-

4.1 KB • 5824x3401

-
-
-
-
-
-{% endblock %} \ No newline at end of file diff --git a/custom/otssignange/views/override-styles.twig b/custom/otssignange/views/override-styles.twig new file mode 100644 index 0000000..13aa98c --- /dev/null +++ b/custom/otssignange/views/override-styles.twig @@ -0,0 +1,3117 @@ +/* ============================================================================ + XIBO CMS MODERN THEME - OTS Signs + Dark Mode Override - Component Styling + ============================================================================ */ + +/* Force dark mode */ +:root { + --color-primary: #3b82f6; + --color-primary-dark: #1d4ed8; + --color-primary-light: #60a5fa; + --color-primary-lighter: #dbeafe; + --color-secondary: #7c3aed; + --color-success: #10b981; + --color-warning: #f59e0b; + --color-danger: #ef4444; + --color-info: #0ea5e9; + + --color-background: #0f172a; + --color-surface: #1e293b; + --color-surface-elevated: #334155; + --color-border: #475569; + --color-border-light: #1e293b; + --color-text-primary: #ffffff; + --color-text-secondary: #f1f5f9; + --color-text-tertiary: #e2e8f0; + --color-text-inverse: #ffffff; + --color-on-primary: #ffffff; + + color-scheme: dark; +} + +html, +body { + background-color: var(--color-background); + color: var(--color-text-primary); +} + +/* ============================================================================ + SHELL LAYOUT - SIDEBAR + MAIN + ============================================================================ */ + +.ots-shell { + display: flex; + min-height: 100vh; +} + +.ots-sidebar { + position: fixed; + left: 0; + top: 0; + width: 260px; + height: 100vh; + background-color: #08132a; + border-right: 1px solid rgba(255, 255, 255, 0.06); + padding: 0; + display: flex; + flex-direction: column; + overflow-y: auto; + z-index: 1000; +} + +.ots-main { + flex: 1; + display: flex; + flex-direction: column; + margin-left: 260px; +} + +.ots-content { + flex: 1; + padding: 32px; + overflow-y: auto; +} + +.ots-footer { + border-top: 1px solid var(--color-border); + padding: 16px 32px; + background-color: var(--color-surface-elevated); + text-align: center; + color: var(--color-text-primary); +} + +/* Responsive sidebar */ +@media (max-width: 768px) { + .ots-sidebar { + transform: translateX(-100%); + transition: transform var(--transition-base); + width: 280px; + } + .ots-sidebar.active { + transform: translateX(0); + box-shadow: 2px 0 8px rgba(0, 0, 0, 0.3); + } + + .ots-main { + margin-left: 0; + } + + .ots-content { + color: var(--color-text-primary); + } +} + +/* ============================================================================ + SIDEBAR STYLES + ============================================================================ */ + +.sidebar-header { + padding: 20px 16px; + border-bottom: 1px solid var(--color-border); + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.brand-link { + display: flex; + align-items: center; + gap: 12px; + color: var(--color-text-primary); + font-weight: 700; + font-size: 16px; + text-decoration: none; + flex: 1; +} + +.brand-icon { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + flex-shrink: 0; +} + +.sidebar-content { + flex: 1; + padding: 12px 0; + overflow-y: auto; +} + +.sidebar-nav { + list-style: none; + margin: 0; + padding: 12px 0 120px; +} + +.sidebar-nav li { + display: block; +} + +/* Compatibility: Xibo sidebar markup uses `sidebar-list`, `sidebar-main`, `sidebar-title`. + Map those into the modern `.nav-item/.nav-text/.nav-icon` style system so styles apply. +*/ +.ots-sidebar li.sidebar-list > a, +.ots-sidebar li.sidebar-main > a, +.ots-sidebar li.sidebar-title > a { + display: grid; + grid-template-columns: 20px 1fr; + align-items: center; + column-gap: 12px; + padding: 8px 12px; + color: #c8d5ee; + text-decoration: none; + transition: all var(--transition-fast); + position: relative; + font-size: 14px; + font-weight: 500; + border-left: 2px solid transparent; + margin: 3px 10px; + border-radius: 12px; + min-height: 40px; + line-height: 1.25; +} + +.ots-sidebar li.sidebar-list > a:hover { + color: #ffffff; + background-color: rgba(255, 255, 255, 0.08); +} + +.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; + border-left-color: transparent; + font-weight: 600; + box-shadow: 0 8px 18px rgba(15, 23, 42, 0.25); +} + +.ots-sidebar .ots-nav-icon { + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + font-size: 16px; + color: currentColor; + justify-self: center; +} + +.ots-sidebar .ots-nav-text { + font-size: 14px; + font-weight: 500; + flex: 1; + line-height: 1.2; +} + +.ots-sidebar li.sidebar-title > a { + display: block; + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + color: #8ea4c7; + letter-spacing: 0.12em; + padding: 12px 14px 4px; + margin: 8px 0 0; + min-height: auto; + line-height: 1; +} + +.nav-section-divider { + padding: 12px 16px 8px; + margin-top: 8px; + border-top: 1px solid var(--color-border); +} + +.nav-label { + display: block; + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + color: var(--color-text-tertiary); + letter-spacing: 0.08em; + padding: 12px 16px 8px; + margin-top: 8px; +} + +.nav-item { + display: flex; + align-items: center; + gap: 14px; + padding: 12px 16px; + color: var(--color-text-primary); + text-decoration: none; + transition: all var(--transition-fast); + position: relative; + font-size: 14px; + font-weight: 500; + border-left: 2px solid transparent; + margin: 0 4px; + border-radius: 6px; +} + +.nav-item:hover { + color: var(--color-text-primary); + background-color: rgba(59, 130, 246, 0.1); + border-left-color: transparent; +} + +.nav-item.active { + color: var(--color-primary); + background-color: rgba(59, 130, 246, 0.12); + border-left-color: var(--color-primary); + font-weight: 600; +} + +.nav-item.active::after { + content: ''; + position: absolute; + right: 8px; + width: 8px; + height: 8px; + background-color: var(--color-primary); + border-radius: 50%; +} + +.nav-icon { + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + font-size: 18px; +} + +.nav-text { + font-size: 14px; + font-weight: 500; + flex: 1; +} + +.sidebar-footer { + border-top: 1px solid var(--color-border); + padding: 16px; + background-color: rgba(59, 130, 246, 0.05); +} + +.sidebar-user { + display: flex; + align-items: center; + gap: 12px; +} + +.user-avatar-lg { + width: 40px; + height: 40px; + border-radius: 50%; + background: linear-gradient(135deg, #3b82f6, #7c3aed); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 16px; + flex-shrink: 0; +} + +.user-details { + min-width: 0; +} + +.user-name { + font-size: 14px; + font-weight: 600; + color: var(--color-text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.user-role { + color: var(--color-text-secondary); + white-space: nowrap; +} + +/* ============================================================================ + TOPBAR STYLES - DEEP ANALYSIS & MODERN OVERHAUL + ============================================================================ + Design decisions: + - 64px total height (44px content + 10px top/bottom padding) = consistent baseline + - All interactive elements are 40px (buttons, search height) + - Icon sizing: 20px container, 16px icon (scale-friendly) + - Consistent 16px gap spacing throughout + - Remove conflicting .navbar-nav rules + - Proper z-index layering for dropdowns + ============================================================================ */ + +.ots-topbar { + background-color: var(--color-surface-elevated); + border-bottom: 2px solid var(--color-border); + padding: 10px 32px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + height: 64px; + z-index: 1000; +} + +/* Topbar nav container - override .navbar-nav defaults */ +.ots-topbar.navbar-nav { + background: transparent !important; + border: 0 !important; + padding: 0 !important; + margin: 0 !important; + gap: 4px; + height: 100%; + align-items: center; +} + +.ots-topbar .nav-item { + display: flex; + align-items: center; + height: 100%; + margin: 0 !important; + padding: 0 !important; +} + +.ots-topbar .nav-link { + display: inline-flex; + align-items: center; + height: 100%; + gap: 6px; + padding: 0 12px; + border-radius: 6px; + color: var(--color-text-secondary); + font-weight: 500; + font-size: 14px; + white-space: nowrap; + transition: all var(--transition-fast); + position: relative; +} + +.ots-topbar .nav-link:hover { + background-color: rgba(59, 130, 246, 0.08); + color: var(--color-primary); +} + +.ots-topbar .nav-item.open .nav-link, +.ots-topbar .nav-item.active .nav-link { + background-color: rgba(59, 130, 246, 0.12); + color: var(--color-primary); + font-weight: 600; +} + +.ots-topbar .dropdown-toggle::after { + content: ''; + display: inline-block; + margin-left: 6px; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid currentColor; + opacity: 0.6; + transition: transform var(--transition-fast); +} + +.ots-topbar .nav-item.open .dropdown-toggle::after { + transform: rotate(180deg); +} + +.ots-topbar .dropdown-menu { + border-radius: 8px; + padding: 6px 0; + margin-top: 4px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); + border: 1px solid var(--color-border); + background-color: var(--color-surface); + min-width: 180px; + z-index: 1100; +} + +.ots-topbar .dropdown-item, +.ots-topbar .dropdown-menu a { + display: flex; + align-items: center; + gap: 8px; + border-radius: 4px; + padding: 8px 14px; + color: var(--color-text-secondary); + font-size: 14px; + font-weight: 500; + margin: 2px 6px; + transition: all var(--transition-fast); +} + +.ots-topbar .dropdown-item:hover, +.ots-topbar .dropdown-menu a:hover { + background-color: rgba(59, 130, 246, 0.1); + color: var(--color-primary); +} + +/* Icon sizing for topbar and nav */ +.ots-topbar-icon { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + opacity: 0.9; + flex-shrink: 0; +} + +.ots-topbar .dropdown-menu .ots-topbar-icon { + font-size: 13px; +} + +/* Left section: toggle, title, nav */ +.topbar-left { + display: flex; + align-items: center; + gap: 16px; + flex: 1; + min-width: 0; +} + +.topbar-toggle { + display: none; + width: 40px; + height: 40px; + padding: 0; + border: none; + background-color: transparent; + color: var(--color-text-primary); + cursor: pointer; + border-radius: 6px; + transition: all var(--transition-fast); + flex-shrink: 0; +} + +.topbar-toggle:hover { + background-color: rgba(59, 130, 246, 0.1); +} + +.topbar-toggle:active { + background-color: rgba(59, 130, 246, 0.15); +} + +.topbar-toggle .icon { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; +} + +/* Title section with proper vertical centering */ +.topbar-title { + display: flex; + flex-direction: column; + justify-content: center; + gap: 0; + min-width: 0; +} + +.topbar-title h1 { + margin: 0; + padding: 0; + font-size: 18px; + font-weight: 700; + color: var(--color-text-primary); + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.topbar-title p { + margin: 0; + padding: 0; + font-size: 12px; + color: var(--color-text-tertiary); + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* Right section: search, actions */ +.topbar-right { + display: flex; + align-items: center; + gap: 12px; + flex-shrink: 0; +} + +/* Search box - 40px height to match button grid */ +.topbar-search { + position: relative; + height: 40px; + width: 280px; + display: flex; + align-items: center; + background-color: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: 6px; + padding: 0 12px; + gap: 8px; + transition: all var(--transition-fast); +} + +.topbar-search:focus-within { + border-color: var(--color-primary); + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1); +} + +.topbar-search .search-icon { + color: var(--color-text-tertiary); + flex-shrink: 0; + font-size: 16px; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; +} + +.topbar-search .search-input { + flex: 1; + background: none; + border: none; + color: var(--color-text-primary); + font-size: 14px; + outline: none; + padding: 0; + height: 100%; + min-width: 120px; +} + +.topbar-search .search-input::placeholder { + color: var(--color-text-tertiary); +} + +/* Action buttons container */ +.topbar-actions { + display: flex; + align-items: center; + gap: 4px; +} + +/* Consistent 40px button sizing */ +.topbar-btn { + width: 40px; + height: 40px; + min-width: 40px; + min-height: 40px; + padding: 0; + border: none; + background-color: transparent; + color: var(--color-text-secondary); + cursor: pointer; + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + transition: all var(--transition-fast); + position: relative; +} + +.topbar-btn:hover { + background-color: rgba(59, 130, 246, 0.08); + color: var(--color-primary); +} + +.topbar-btn:active { + background-color: rgba(59, 130, 246, 0.12); +} + +.topbar-btn .icon { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; +} + +.user-btn { + padding: 2px; +} + +.avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: linear-gradient(135deg, #3b82f6, #7c3aed); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 14px; +} + +.avatar-sm { + width: 32px; + height: 32px; + font-size: 13px; +} + +.dropdown { + position: relative; +} + +.dropdown-menu, +.dropdown-right { + position: absolute; + top: 100%; + right: 0; + margin-top: 8px; + background-color: var(--color-surface-elevated); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 8px; + box-shadow: 0 20px 45px rgba(0, 0, 0, 0.3); + list-style: none; + padding: 8px 0; + min-width: 180px; + display: none; + z-index: 1001; + overflow: hidden; +} + +.dropdown.active .dropdown-menu, +.dropdown:focus-within .dropdown-menu { + display: block; + animation: slideDown 150ms ease-out; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.dropdown-menu li { + 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; +} + +.dropdown-menu li a { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 16px; + color: var(--color-text-primary); + text-decoration: none; + font-size: 14px; + transition: all var(--transition-fast); + font-weight: 500; +} + +.dropdown-menu li a:hover { + background-color: rgba(59, 130, 246, 0.1); + color: var(--color-primary); +} + +.dropdown-menu li a:active { + background-color: rgba(59, 130, 246, 0.15); +} + +/* ============================================================================ + DASHBOARD PAGE + ============================================================================ */ + +/* OTS dashboard message (theme-dashboard-message.twig) */ +.ots-dashboard-message { + margin: 16px 0 24px; + padding: 12px 16px; + border-radius: 10px; + background: rgba(59, 130, 246, 0.12); + border: 1px solid rgba(59, 130, 246, 0.35); + color: var(--color-text-primary); +} + +.ots-dashboard-message__title { + font-weight: 700; + letter-spacing: 0.02em; + text-transform: uppercase; + font-size: 12px; + margin-bottom: 4px; +} + +.ots-dashboard-message__body { + font-size: 13px; + color: var(--color-text-secondary); +} + +.dashboard-page { + display: flex; + flex-direction: column; + gap: 32px; +} + +.page-header { + margin-bottom: 16px; +} + +.page-header h1 { + margin: 0 0 8px; + font-size: 32px; + font-weight: 700; +} + +.page-header .text-muted { + margin: 0; + font-size: 16px; + color: var(--color-text-secondary); +} + +/* KPI Section */ +.kpi-section { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 20px; +} + +.kpi-card { + background-color: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: 8px; + padding: 20px; + transition: all var(--transition-base); +} + +.kpi-card:hover { + border-color: var(--color-primary); + box-shadow: 0 0 0 1px var(--color-primary); +} + +.kpi-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 16px; +} + +.kpi-label { + margin: 0; + font-size: 14px; + font-weight: 600; + color: var(--color-text-secondary); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.kpi-icon-box { + width: 40px; + height: 40px; + border-radius: 6px; + background: linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(124, 58, 237, 0.2)); + display: flex; + align-items: center; + justify-content: center; + color: var(--color-primary); +} + +.kpi-body { + margin-bottom: 12px; +} + +.kpi-number { + font-size: 32px; + font-weight: 700; + color: var(--color-text-primary); + line-height: 1; + margin-bottom: 4px; +} + +.kpi-meta { + font-size: 13px; + color: var(--color-text-secondary); +} + +.kpi-footer { + display: flex; + align-items: center; + gap: 8px; + padding-top: 12px; + border-top: 1px solid var(--color-border); +} + +/* Badges */ +.badge { + display: inline-block; + padding: 4px 10px; + border-radius: 4px; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.badge-success { + background-color: rgba(16, 185, 129, 0.2); + color: #a7f3d0; +} + +.badge-danger { + background-color: rgba(239, 68, 68, 0.2); + color: #fca5a5; +} + +.badge-info { + background-color: rgba(14, 165, 233, 0.2); + color: #7dd3fc; +} + +.badge-secondary { + background-color: rgba(124, 58, 237, 0.2); + color: #d8b4fe; +} + +/* Dashboard Panels */ +.dashboard-panels { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 24px; +} + +/* Dashboard chart cards (bandwidth/library) */ +.dashboard-chart-card { + background: linear-gradient(180deg, rgba(59, 130, 246, 0.06), rgba(59, 130, 246, 0.02)); + border: 1px solid rgba(59, 130, 246, 0.18); + box-shadow: 0 12px 26px rgba(8, 15, 30, 0.35); +} + +.dashboard-chart-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; +} + +.dashboard-chart-title { + display: flex; + align-items: center; + gap: 12px; +} + +.dashboard-chart-icon { + width: 40px; + height: 40px; + border-radius: 12px; + background: rgba(59, 130, 246, 0.18); + color: var(--color-primary-light); + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 18px; + flex-shrink: 0; +} + +.dashboard-chart-heading { + margin: 0; + font-size: 16px; + font-weight: 700; +} + +.dashboard-chart-subtitle { + margin: 4px 0 0; + font-size: 12px; + color: var(--color-text-secondary); +} + +.dashboard-chart-link { + font-size: 12px; + font-weight: 600; + color: var(--color-primary); + text-decoration: none; + padding: 6px 10px; + border-radius: 999px; + border: 1px solid rgba(59, 130, 246, 0.24); + background: rgba(59, 130, 246, 0.12); + transition: all var(--transition-fast); + white-space: nowrap; +} + +.dashboard-chart-link:hover { + color: var(--color-text-primary); + border-color: rgba(59, 130, 246, 0.5); + background: rgba(59, 130, 246, 0.22); +} + +.dashboard-chart-body { + padding: 0 20px 20px; +} + +.dashboard-chart-canvas { + background: rgba(15, 23, 42, 0.45); + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: 12px; + padding: 12px; + height: 250px; + position: relative; + overflow: hidden; +} + +.dashboard-chart-canvas canvas { + display: block; + width: 100% !important; + height: 100% !important; + max-width: 100% !important; + max-height: 100% !important; + margin: 0 !important; +} + +.dashboard-chart-card .panel-body canvas, +.dashboard-chart-card .widget-body canvas { + min-height: 0 !important; +} + +.panel { + background-color: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: 8px; + overflow: hidden; + display: flex; + flex-direction: column; +} + +/* Force Xibo panels/cards to dark theme (use higher specificity to override core styles) */ +body .panel, +body .panel.panel-default, +.panel.panel-white, +.panel.card, +.panel.box, +.widget { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +body .panel .panel-body, +body .panel .panel-footer, +body .panel .panel-heading, +.panel .panel-header { + background-color: transparent !important; + color: var(--color-text-primary) !important; + border-color: var(--color-border) !important; +} + +body .panel .panel-heading, +.panel .panel-header { + background-color: var(--color-surface-elevated) !important; + border-bottom: 1px solid var(--color-border) !important; +} + +/* Tables inside panels should inherit dark background and readable text */ +.panel table, +.panel table thead, +.panel table tbody, +.panel table tr, +.panel table td, +.panel table th, +.panel .dataTables_wrapper { + background-color: transparent !important; + color: var(--color-text-primary) !important; +} + +/* Card-specific fallbacks */ +.card, +.card .card-body { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +.panel-full { + grid-column: 1 / -1; +} + +.panel-header { + padding: 20px; + border-bottom: 1px solid var(--color-border); + display: flex; + justify-content: space-between; + align-items: center; +} + +.panel-header h3 { + margin: 0; + font-size: 16px; + font-weight: 600; + color: var(--color-text-primary); +} + +.link-secondary { + color: var(--color-primary); + text-decoration: none; + font-size: 13px; + font-weight: 500; + transition: color var(--transition-fast); +} + +.link-secondary:hover { + color: var(--color-primary-light); +} + +.panel-body { + padding: 20px; + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; +} + +.empty-state-compact { + text-align: center; + padding: 20px 0; +} + +.empty-state-compact p { + margin: 0 0 16px; + color: var(--color-text-secondary); +} + +/* Quick Actions */ +.quick-actions-grid { + margin-top: 32px; +} + +.section-title { + margin: 0 0 16px; + font-size: 18px; + font-weight: 600; + color: var(--color-text-primary); +} + +.action-cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; +} + +.action-card { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + padding: 24px; + background-color: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: 8px; + text-decoration: none; + color: var(--color-text-primary); + transition: all var(--transition-base); + cursor: pointer; +} + +.action-card:hover { + border-color: var(--color-primary); + background-color: rgba(59, 130, 246, 0.05); + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +.action-icon { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + color: var(--color-primary); +} + +.action-label { + font-size: 14px; + font-weight: 600; + text-align: center; +} + +/* ============================================================================ + TWO-COLUMN LAYOUT (Displays, Media) + ============================================================================ */ + +.ots-displays-page .page-header { + margin-bottom: 20px; +} + +.ots-displays-card { + padding: 0; + display: flex; + flex-direction: column; + min-width: 0; +} + +.ots-displays-body { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; +} + +.ots-displays-body .XiboGrid { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; +} + +.ots-displays-title { + font-size: 18px; + font-weight: 700; + letter-spacing: 0.02em; +} + +.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); + margin-bottom: 0; + border-radius: 12px; + overflow: hidden; +} + +/* Filter header with collapse button */ +.ots-filter-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 16px; + border-bottom: 1px solid rgba(148, 163, 184, 0.2); + background: rgba(15, 23, 42, 0.3); +} + +.ots-filter-title { + font-weight: 600; + color: var(--color-text-primary); + font-size: 14px; + margin: 0; +} + +.ots-filter-toggle { + width: 32px; + height: 32px; + padding: 0; + border: none; + background: transparent; + color: var(--color-text-secondary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + border-radius: 6px; + transition: all var(--transition-fast); +} + +.ots-filter-toggle:hover { + background: rgba(59, 130, 246, 0.1); + color: var(--color-primary); +} + +.ots-filter-content { + padding: 16px; + max-height: 600px; + overflow: hidden; + transition: max-height 300ms ease-out, padding 300ms ease-out; +} + +.ots-filter-content.collapsed { + max-height: 0; + padding: 0 16px; + overflow: hidden; +} + +.ots-filter-card .nav-tabs { + border-bottom: 1px solid rgba(148, 163, 184, 0.2); + gap: 8px; + margin-bottom: 14px; +} + +.ots-filter-card .nav-tabs .nav-link { + color: var(--color-text-secondary); + border: 0; + border-radius: 8px; + padding: 10px 14px; + background: transparent; + font-size: 13px; + font-weight: 500; + transition: all var(--transition-fast); +} + +.ots-filter-card .nav-tabs .nav-link.active, +.ots-filter-card .nav-tabs .nav-link:hover { + color: var(--color-text-primary); + background: rgba(59, 130, 246, 0.12); +} + +.ots-filter-card .form-inline .form-group, +.ots-filter-card .form-inline .input-group { + margin-right: 16px; + margin-bottom: 12px; +} + +.ots-filter-card .form-control, +.ots-filter-card select, +.ots-filter-card .select2-selection, +.ots-filter-card .input-group-addon { + background: var(--color-surface) !important; + border: 1px solid var(--color-border) !important; + color: var(--color-text-primary) !important; + border-radius: 6px !important; + padding: 8px 12px !important; + font-size: 13px !important; + transition: all var(--transition-fast) !important; + height: 36px !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; + background: var(--color-surface) !important; +} + +.ots-filter-card label { + color: var(--color-text-secondary); + font-size: 12px; + font-weight: 500; + margin-bottom: 4px; + display: block; +} + +.ots-grid-with-folders { + display: grid; + grid-template-columns: 260px 1fr; + gap: 32px; + align-items: start; + width: 100%; + transition: grid-template-columns 200ms ease-out; +} + +.ots-grid-with-folders.ots-folder-collapsed { + grid-template-columns: 1fr; + gap: 0; +} + +.ots-grid-with-folders.ots-folder-collapsed .ots-folder-tree { + display: none; + width: 0; + overflow: hidden; +} + +.ots-folder-tree { + min-height: 320px; + padding: 16px; + margin-right: -8px; + max-width: 100%; + box-sizing: content-box; +} + +.ots-folder-tree .form-check { + margin-top: 12px; +} + +.ots-grid-controller { + margin-top: 8px; +} + +.ots-grid-controller .btn { + border-radius: 10px; + box-shadow: 0 6px 16px rgba(8, 15, 30, 0.25); +} + +.ots-table-card { + padding: 12px 16px 16px; + flex: 1; + min-width: 0; + overflow: hidden; +} + +#datatable-container { + width: 100%; + overflow-x: auto; +} + +.ots-table-card .table thead th { + background: rgba(15, 23, 42, 0.9); + color: var(--color-text-primary); + border-bottom: 1px solid rgba(148, 163, 184, 0.2); +} + +.ots-table-card .table thead th, +.ots-table-card .table tbody td, +.ots-table-card .table tbody th, +.ots-table-card .table tbody tr { + color: #e2e8f0; +} + +.ots-table-card .table tbody tr { + background: rgba(15, 23, 42, 0.7); +} + +.ots-table-card .table tbody tr:nth-child(even) { + background: rgba(30, 41, 59, 0.75); +} + +.ots-table-card .table tbody tr:hover { + background: rgba(59, 130, 246, 0.14); + color: #ffffff; +} + +.ots-table-card .table tbody tr.selected, +.ots-table-card .table tbody tr.dt-row-selected, +.ots-table-card .table tbody tr.selected td, +.ots-table-card .table tbody tr.dt-row-selected td { + background: rgba(16, 185, 129, 0.25) !important; + color: #ffffff !important; +} + +.ots-table-card .dataTables_wrapper, +.ots-table-card .dataTables_wrapper * { + color: #e2e8f0 !important; +} + +.ots-table-card .dataTables_wrapper .dataTables_filter input, +.ots-table-card .dataTables_wrapper .dataTables_length select { + background: rgba(15, 23, 42, 0.8) !important; + color: #e2e8f0 !important; + border: 1px solid rgba(148, 163, 184, 0.25) !important; +} + +/* Extra specificity for Xibo Displays DataTable */ +.ots-displays-page #datatable-container .XiboData .table, +.ots-displays-page #datatable-container .XiboData table.dataTable { + color: #e2e8f0 !important; +} + +.ots-displays-page #datatable-container .XiboData .table thead th, +.ots-displays-page #datatable-container .XiboData table.dataTable thead th { + color: #cbd5e1 !important; + background-color: var(--color-surface) !important; + font-weight: 600 !important; + opacity: 1 !important; +} + +.ots-displays-page #datatable-container .XiboData .table tbody td, +.ots-displays-page #datatable-container .XiboData table.dataTable tbody td { + color: #e2e8f0 !important; + opacity: 1 !important; +} + +.ots-displays-page #datatable-container .XiboData table.dataTable tbody tr { + background-color: rgba(15, 23, 42, 0.75) !important; +} + +/* Status icons in Displays table */ +.ots-displays-page #datatable-container .fa-check, +.ots-displays-page #datatable-container .fa-check-circle { + color: var(--color-success) !important; +} + +.ots-displays-page #datatable-container .fa-times, +.ots-displays-page #datatable-container .fa-times-circle, +.ots-displays-page #datatable-container .fa-x { + color: var(--color-danger) !important; +} + +.ots-displays-page #datatable-container .XiboData table.dataTable tbody tr:nth-child(even) { + background-color: rgba(30, 41, 59, 0.8) !important; +} + +.ots-table-card .table tbody tr:hover { + background: rgba(59, 130, 246, 0.08); +} + +.ots-map-card { + margin-top: 16px; + min-height: 360px; + background: rgba(15, 23, 42, 0.6); + border: 1px solid rgba(148, 163, 184, 0.2); + border-radius: 16px; +} + +@media (max-width: 1024px) { + .ots-grid-with-folders { + grid-template-columns: 1fr; + } +} + +.two-column-layout { + display: grid; + grid-template-columns: 280px 1fr; + gap: 24px; + height: calc(100vh - 130px); +} + +.left-panel { + background-color: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: 8px; + display: flex; + flex-direction: column; + overflow-y: auto; +} + +.content-panel { + display: flex; + flex-direction: column; + gap: 16px; +} + +.folder-tree { + padding: 8px 0; +} + +.folder-item { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 16px; + color: var(--color-text-secondary); + cursor: pointer; + transition: all var(--transition-fast); +} + +.folder-item:hover { + background-color: var(--color-surface-elevated); + color: var(--color-text-primary); +} + +.folder-item.active { + background-color: rgba(59, 130, 246, 0.15); + color: var(--color-primary); +} + +.folder-icon { + width: 18px; + height: 18px; + flex-shrink: 0; +} + +.folder-name { + font-size: 14px; + font-weight: 500; +} + +.content-toolbar { + display: flex; + align-items: center; + gap: 16px; + padding: 16px; + background-color: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: 8px; +} + +.search-wrapper { + flex: 1; + position: relative; + display: flex; + align-items: center; + background-color: var(--color-surface-elevated); + border: 1px solid var(--color-border); + border-radius: 6px; + padding: 8px 12px; +} + +.search-icon { + color: var(--color-text-tertiary); + margin-right: 8px; + flex-shrink: 0; +} + +.search-field { + flex: 1; + background: none; + border: none; + color: var(--color-text-primary); + font-size: 14px; + outline: none; + padding: 0; +} + +.search-field::placeholder { + color: var(--color-text-tertiary); +} + +.toolbar-actions { + display: flex; + align-items: center; + gap: 12px; +} + +.stat-row { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + gap: 12px; +} + +.stat-box { + background-color: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: 6px; + padding: 12px; + text-align: center; +} + +.stat-label { + font-size: 12px; + color: var(--color-text-secondary); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 8px; +} + +.stat-value { + font-size: 24px; + font-weight: 700; + color: var(--color-text-primary); +} + +.text-success { + color: #10b981; +} + +.text-danger { + color: #ef4444; +} + +/* ============================================================================ + TABLES + ============================================================================ */ + +.table-wrapper { + background-color: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: 8px; + overflow-x: auto; +} + +.table { + width: 100%; + border-collapse: collapse; + margin: 0; + font-size: 14px; +} + +.table thead { + background-color: var(--color-surface); + border-bottom: 2px solid var(--color-border); +} + +.table thead th { + padding: 12px 16px; + text-align: left; + font-weight: 600; + color: var(--color-text-secondary); + text-transform: uppercase; + letter-spacing: 0.05em; + font-size: 11px; + background-color: var(--color-surface); +} + +/* Status icons and checkmarks color */ +.table tbody td .fa-check, +.table tbody td .fa-check-circle { + color: var(--color-success); +} + +.table tbody td .fa-times, +.table tbody td .fa-times-circle, +.table tbody td .fa-x { + color: var(--color-danger); +} + +.table tbody td .fa-exclamation, +.table tbody td .fa-exclamation-circle, +.table tbody td .fa-question-circle { + color: var(--color-warning); +} + +.table tbody td .fa-info-circle { + color: var(--color-info); +} + +.table tbody tr { + border-bottom: 1px solid var(--color-border); + transition: background-color var(--transition-fast); +} + +.table tbody tr:hover { + background-color: var(--color-surface-elevated); +} + +.table tbody td { + padding: 12px 16px; + color: var(--color-text-primary); +} + +.table-striped tbody tr:nth-child(even) { + background-color: rgba(59, 130, 246, 0.02); +} + +/* ============================================================================ + MEDIA LIBRARY + ============================================================================ */ + +.media-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 16px; + padding: 16px; + background-color: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: 8px; +} + +.media-item { + background-color: var(--color-surface-elevated); + border: 1px solid var(--color-border); + border-radius: 6px; + overflow: hidden; + cursor: pointer; + transition: all var(--transition-base); +} + +.media-item:hover { + border-color: var(--color-primary); + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2); + transform: translateY(-4px); +} + +.media-thumbnail { + position: relative; + width: 100%; + padding-bottom: 75%; + overflow: hidden; + background-color: var(--color-border); +} + +.media-thumbnail img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; +} + +.media-type-badge { + position: absolute; + top: 8px; + right: 8px; + background-color: rgba(0, 0, 0, 0.6); + color: white; + padding: 4px 8px; + border-radius: 4px; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; +} + +.media-info { + padding: 12px; +} + +.media-name { + margin: 0 0 4px; + font-size: 14px; + font-weight: 600; + color: var(--color-text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.media-size { + margin: 0; + color: var(--color-text-tertiary); +} + +/* ============================================================================ + BUTTONS + ============================================================================ */ + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 10px 16px; + border: none; + border-radius: 6px; + font-size: 14px; + font-weight: 600; + text-decoration: none; + cursor: pointer; + transition: all var(--transition-fast); + white-space: nowrap; +} + +.btn-primary { + background-color: var(--color-primary); + color: white; +} + +.btn-primary:hover { + background-color: var(--color-primary-dark); +} + +.btn-outline { + background-color: transparent; + border: 1px solid var(--color-border); + color: var(--color-text-primary); +} + +.btn-outline:hover { + background-color: var(--color-surface); + border-color: var(--color-primary); + color: var(--color-primary); +} + +.btn-sm { + padding: 6px 12px; + font-size: 13px; +} + +.btn-ghost { + background-color: transparent; + color: var(--color-text-secondary); + padding: 0; +} + +.btn-ghost:hover { + color: var(--color-text-primary); +} + +/* ============================================================================ + UTILITY CLASSES + ============================================================================ */ + +.text-muted { + color: var(--color-text-secondary); +} + +.text-tertiary { + color: var(--color-text-tertiary); +} + +.text-xs { + font-size: 12px; +} + +.text-sm { + font-size: 14px; +} + +.form-control { + background-color: var(--color-surface); + border: 1px solid var(--color-border); + color: var(--color-text-primary); + padding: 8px 12px; + border-radius: 6px; + font-size: 14px; +} + +.form-control::placeholder { + color: var(--color-text-secondary); +} + +.form-control:focus { + outline: none; + border-color: var(--color-primary); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + +/* ============================================================================ + RESPONSIVE + ============================================================================ */ + +@media (max-width: 768px) { + .ots-topbar { + flex-direction: column; + align-items: flex-start; + gap: 12px; + height: auto; + padding: 12px 16px; + } + + .topbar-right { + width: 100%; + flex-direction: column; + } + + .topbar-search { + width: 100%; + } + + .two-column-layout { + grid-template-columns: 1fr; + height: auto; + } + + .left-panel { + max-height: 300px; + } + + .dashboard-panels { + grid-template-columns: 1fr; + } + + .kpi-section { + grid-template-columns: 1fr; + } + + .action-cards { + grid-template-columns: 1fr; + } + + .media-grid { + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + } + + .ots-content { + padding: 16px; + } + + .ots-footer { + padding: 12px 16px; + } +} + +/* ============================================================================ + CANVAS / CHART ELEMENTS + ============================================================================ */ + +/* Ensure canvas elements and chart containers render properly */ +canvas { + max-width: 100%; + height: auto; + display: block; +} + +.chart-container, +.chart-wrapper, +[class*="chart"] { + position: relative; + width: 100%; + height: 100%; +} +/* OTS sidebar override marker */ +.ots-sidebar-wrapper { + box-shadow: inset 0 0 0 1px rgba(59, 130, 246, 0.15); +} + +.ots-sidebar .sidebar-list a, +.ots-sidebar .sidebar-main a { + display: flex; + align-items: center; + gap: 10px; +} + +.ots-nav-icon { + width: 18px; + text-align: center; + opacity: 0.85; + font-size: 14px; +} + +.ots-nav-text { + flex: 1; + min-width: 0; +} + +/* Xibo sidebar refinements (light) */ +#sidebar-wrapper .sidebar-title a { + color: var(--color-text-tertiary); + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + padding: 12px 16px 6px; +} + +#sidebar-wrapper .sidebar-list a, +#sidebar-wrapper .sidebar-main a { + display: block; + margin: 2px 8px; + padding: 10px 12px; + border-radius: 8px; + transition: background-color var(--transition-fast), color var(--transition-fast); +} + +#sidebar-wrapper .sidebar-list a:hover, +#sidebar-wrapper .sidebar-main a:hover { + background: rgba(59, 130, 246, 0.1); + color: var(--color-primary); +} + +/* Ensure chart parent containers have proper sizing */ +.panel-body canvas, +.panel-body .chart-container { + flex: 1; + min-height: 300px; +} + +.panel-body > div[class*="chart"], +.panel-body > div > canvas { + width: 100% !important; + height: 100% !important; +} + +/* Hidden dashboard chart canvas bridge */ +.chart-sandbox { + position: absolute; + left: -10000px; + top: -10000px; + width: 1px; + height: 1px; + overflow: hidden; + pointer-events: none; +} + +.chart-sandbox canvas { + width: 1px !important; + height: 1px !important; +} + +/* Transition variable fallback */ +:root { + --transition-fast: 150ms ease-in-out; + --transition-base: 200ms ease-in-out; + --transition-slow: 300ms ease-in-out; +} + +/* ================================================================ + Modern table styles (theme override) + Applied: makes XIBO tables look cleaner and responsive + ================================================================ */ + +.modern-table-card { + background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01)); + border-radius: 10px; + box-shadow: 0 6px 18px rgba(2,6,23,0.45); + padding: 18px; + margin: 16px 0; + border: 1px solid rgba(255,255,255,0.02); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial; +} + +.modern-table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + font-size: 13px; + color: var(--color-text-primary); +} + +.modern-table thead th { + background: linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0.03)); + color: var(--color-text-primary); + font-weight: 700; + text-transform: uppercase; + letter-spacing: .06em; + font-size: 11px; + padding: 10px 12px; + border-bottom: 1px solid rgba(255,255,255,0.1); + position: sticky; + top: 0; + z-index: 2; +} + +.modern-table tbody tr { + background: transparent; +} +.modern-table tbody tr:nth-child(odd) { + background: rgba(255,255,255,0.01); +} +.modern-table tbody tr:hover { + background: rgba(59,130,246,0.06); +} + +.modern-table td { + padding: 12px; + vertical-align: middle; + border-bottom: 1px solid rgba(255,255,255,0.02); +} + +.table-controls { + display:flex; + gap:8px; + align-items:center; + justify-content:flex-end; + margin-bottom:12px; +} +.table-controls .btn { + background: var(--color-primary); + color: var(--color-on-primary); + border-radius: 999px; + padding: 6px 10px; + font-size: 13px; + border: none; + cursor: pointer; +} +.table-controls .btn.ghost { + background:transparent; + color:var(--color-primary); + border:1px solid rgba(59,130,246,0.12); +} + +.table-meta { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + padding-top: 10px; + font-size: 13px; + color: var(--color-text-secondary); +} + +@media (max-width: 880px) { + .modern-table thead th:nth-child(n+6), + .modern-table tbody td:nth-child(n+6) { + display: none; + } + .modern-table tbody td.details-toggle { + display: table-cell; + } + .modern-table-card { + padding: 12px; + } +} + +/* Improve legibility: force table and datatables text to full contrast */ +.modern-table, +.modern-table tbody, +.modern-table tbody tr, +.modern-table td, +.dataTables_wrapper, +.dataTables_wrapper table, +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_length, +.dataTables_wrapper .dataTables_filter { + color: var(--Color-fallback, var(--color-text-primary)) !important; + opacity: 1 !important; +} + +/* Stronger selected row for readability */ +.modern-table tbody tr.selected, +.modern-table tbody tr.dt-row-selected, +.dataTables_wrapper table tbody tr.selected, +.dataTable tbody tr.selected { + background: rgba(16,185,129,0.14) !important; /* stronger mint */ + color: var(--color-text-primary) !important; +} + +/* Inputs and selects used in filters/search should be readable */ +.dataTables_wrapper .dataTables_filter input, +.dataTables_wrapper .dataTables_length select, +.topbar-search .search-input, +.table-controls input, +input[type="search"], +input[type="text"], +select, +textarea { + background: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +/* Buttons - ensure contrast */ +.btn, +.button, +.dataTables_wrapper .dataTables_paginate .paginate_button { + color: var(--color-text-primary) !important; +} + +/* If panels or overlays dim content, ensure table contents remain fully visible */ +.panel, +.panel-body, +.table-wrapper, +.dataTables_wrapper .dataTables_scrollBody { + opacity: 1 !important; + background: transparent !important; +} + + +/* DataTables color adjustments to ensure legibility in dark theme */ +.dataTables_wrapper, +.dataTables_wrapper * { + color: var(--color-text-primary) !important; +} +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_paginate { + color: var(--color-text-primary) !important; +} +.dataTables_wrapper .dataTables_paginate .paginate_button { + background: transparent; + color: var(--color-text-primary) !important; + border: 1px solid transparent; + border-radius: 6px; + padding: 4px 8px; +} +.dataTables_wrapper .dataTables_paginate .paginate_button.current, +.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { + background: var(--color-primary) !important; + color: var(--color-on-primary) !important; + border-color: rgba(255,255,255,0.06); +} + +/* Global legibility overrides */ +body, +.ots-content, +.content, +.container, +.card, +.panel, +.panel-body, +.table, +.dataTables_wrapper, +.dropdown-menu, +.dropdown-right { + color: var(--color-text-primary) !important; +} + +.text-muted, +.text-secondary, +.text-tertiary { + color: var(--color-text-secondary) !important; + opacity: 1 !important; +} + +.ots-sidebar a, +.ots-sidebar .nav-item, +.ots-sidebar .nav-label, +.ots-sidebar .nav-text { + color: var(--color-text-primary) !important; +} + +a, +.nav-link { + color: var(--color-text-primary); +} + +a:hover, +.nav-link:hover { + color: var(--color-primary); +} + +/* ============================================================================ + COMPREHENSIVE DARK THEME ENFORCEMENT + Ensure all elements have readable contrast and dark backgrounds + ============================================================================ */ + +/* Force ALL text to be readable - master override */ +* { + color-scheme: dark; +} + +/* Ensure headings are always visible */ +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + color: var(--color-text-primary) !important; +} + +/* All labels must be readable */ +label, +.label, +.form-label, +legend { + color: var(--color-text-primary) !important; +} + +/* Dropdowns, modals, and popovers */ +.dropdown-menu, +.dropdown-toggle, +.popover, +.modal, +.modal-content, +.modal-header, +.modal-body, +.modal-footer { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +.modal-header { + background-color: var(--color-surface-elevated) !important; + border-bottom: 1px solid var(--color-border) !important; +} + +.dropdown-menu a, +.dropdown-item { + color: var(--color-text-primary) !important; +} + +.dropdown-menu a:hover, +.dropdown-item:hover { + background-color: rgba(59, 130, 246, 0.12) !important; + color: var(--color-primary) !important; +} + +/* Ensure form elements are dark and readable */ +.form-group, +.form-check, +.input-group { + color: var(--color-text-primary) !important; +} + +input, +textarea, +select, +.form-control, +.form-select { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +input::placeholder, +textarea::placeholder { + color: var(--color-text-secondary) !important; +} + +input:focus, +textarea:focus, +select:focus { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border-color: var(--color-primary) !important; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important; +} + +/* Alerts and messages */ +.alert, +.alert-info, +.alert-warning, +.alert-success, +.alert-danger, +.alert-light { + background-color: var(--color-surface) !important; + border: 1px solid var(--color-border) !important; + color: var(--color-text-primary) !important; +} + +.alert-light { + background-color: var(--color-surface-elevated) !important; +} + +/* Tooltips and help text */ +.tooltip, +.tooltip-inner, +.help-block, +.form-text, +small { + color: var(--color-text-secondary) !important; + background-color: transparent !important; +} + +/* Breadcrumbs */ +.breadcrumb { + background-color: transparent !important; + color: var(--color-text-primary) !important; +} + +.breadcrumb a { + color: var(--color-primary) !important; +} + +.breadcrumb-item.active { + color: var(--color-text-secondary) !important; +} + +/* Pagination */ +.pagination, +.pager { + background-color: transparent !important; +} + +.pagination a, +.pagination button, +.pager a { + color: var(--color-text-primary) !important; + background-color: transparent !important; + border: 1px solid var(--color-border) !important; +} + +.pagination a:hover, +.pagination button:hover, +.pager a:hover { + background-color: var(--color-surface) !important; + color: var(--color-primary) !important; + border-color: var(--color-primary) !important; +} + +.pagination .active a, +.pagination .active button, +.pager .active a { + background-color: var(--color-primary) !important; + border-color: var(--color-primary) !important; + color: white !important; +} + +/* Disabled state */ +[disabled], +:disabled, +.disabled { + opacity: 0.6 !important; + color: var(--color-text-secondary) !important; +} + +/* Badge customization */ +.badge, +.badge-default { + background-color: var(--color-surface-elevated) !important; + color: var(--color-text-primary) !important; +} + +/* List groups */ +.list-group, +.list-group-item { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +.list-group-item:hover { + background-color: var(--color-surface-elevated) !important; +} + +.list-group-item.active { + background-color: var(--color-primary) !important; + border-color: var(--color-primary) !important; + color: white !important; +} + +/* Tabs */ +.nav-tabs, +.nav-pills { + background-color: transparent !important; +} + +.nav-tabs .nav-link, +.nav-pills .nav-link { + color: var(--color-text-secondary) !important; + background-color: transparent !important; +} + +.nav-tabs .nav-link:hover, +.nav-pills .nav-link:hover { + color: var(--color-text-primary) !important; + background-color: rgba(59, 130, 246, 0.1) !important; +} + +.nav-tabs .nav-link.active, +.nav-pills .nav-link.active { + color: white !important; + background-color: var(--color-primary) !important; + border-color: var(--color-primary) !important; +} + +/* Cards */ +.card { + background-color: var(--color-surface) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +.card-header { + background-color: var(--color-surface-elevated) !important; + border-bottom: 1px solid var(--color-border) !important; + color: var(--color-text-primary) !important; +} + +.card-footer { + background-color: var(--color-surface-elevated) !important; + border-top: 1px solid var(--color-border) !important; + color: var(--color-text-secondary) !important; +} + +/* Progress bars */ +.progress { + background-color: var(--color-surface-elevated) !important; +} + +.progress-bar { + background-color: var(--color-primary) !important; +} + +/* Ensure code blocks are readable */ +code, +.code, +pre { + background-color: var(--color-surface-elevated) !important; + color: var(--color-text-primary) !important; + border: 1px solid var(--color-border) !important; +} + +/* DataTables specific overrides */ +.dataTables_wrapper { + color: var(--color-text-primary) !important; +} + +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_paginate { + background-color: transparent !important; + color: var(--color-text-primary) !important; +} + +.dataTables_wrapper table tbody tr { + color: var(--color-text-primary) !important; +} + +.dataTables_wrapper table tbody tr:hover { + background-color: rgba(59, 130, 246, 0.08) !important; +} + +/* Spinner/loading indicators */ +.spinner, +.spinner-border, +.spinner-grow { + border-color: rgba(255, 255, 255, 0.25) !important; +} + +.spinner-border.text-primary, +.spinner-grow.text-primary { + color: var(--color-primary) !important; + border-color: var(--color-primary) !important; +} + +/* Links should always be visible and contrasting */ +a { + color: var(--color-primary) !important; + text-decoration: none; +} + +a:hover { + color: var(--color-primary-light) !important; + text-decoration: underline; +} + +a.text-muted { + color: var(--color-text-secondary) !important; +} + +a.text-muted:hover { + color: var(--color-text-primary) !important; +} + +/* Ensure absolutely nothing is invisible */ +.hidden, +[hidden] { + display: none !important; +} + +.invisible { + visibility: hidden !important; +} +/* ============================================================================= + OTS SIGNAGE DARK THEME (REBUILT) + February 4, 2026 + ============================================================================= */ + +:root { + color-scheme: dark; + + --ots-bg: #0b111a; + --ots-surface: #141c2b; + --ots-surface-2: #1b2436; + --ots-surface-3: #222c3f; + --ots-border: #2c3a54; + --ots-border-soft: #243047; + --ots-text: #e6eefb; + --ots-text-muted: #a9b6cc; + --ots-text-faint: #7f8aa3; + --ots-primary: #4f8cff; + --ots-primary-2: #2f6bff; + --ots-success: #2ad4a4; + --ots-warning: #f4b860; + --ots-danger: #ff6b6b; + --ots-info: #5ec0ff; + + --ots-shadow-lg: 0 14px 30px rgba(0, 0, 0, 0.35); + --ots-shadow-md: 0 8px 18px rgba(0, 0, 0, 0.25); + --ots-shadow-sm: 0 3px 8px rgba(0, 0, 0, 0.2); + + --ots-radius-sm: 6px; + --ots-radius-md: 10px; + --ots-radius-lg: 14px; + + --ots-transition: 160ms ease; +} + +/* ============================================================================= + GLOBAL + ============================================================================= */ + +html, +body { + background: var(--ots-bg); + color: var(--ots-text); +} + +body { + font-family: "Inter", "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; +} + +a, +.nav-link, +.sidebar a { + color: var(--ots-text); + text-decoration: none; + transition: color var(--ots-transition), background var(--ots-transition); +} + +a:hover, +.nav-link:hover, +.sidebar a:hover { + color: var(--ots-primary); +} + +h1, +h2, +h3, +h4, +h5, +h6 { + color: var(--ots-text); + font-weight: 600; +} + +small, +.text-muted { + color: var(--ots-text-muted) !important; +} + +hr { + border-color: var(--ots-border); +} + +/* ============================================================================= + LAYOUT WRAPPERS + ============================================================================= */ + +#page-wrapper, +#content-wrapper, +.page-content { + background: var(--ots-bg); +} + +.page-content { + padding-top: 24px; +} + +/* ============================================================================= + NAVBAR / TOPBAR + ============================================================================= */ + +.navbar, +.navbar-default { + background: var(--ots-surface-2); + border: 1px solid var(--ots-border); + box-shadow: var(--ots-shadow-sm); +} + +.navbar-brand, +.navbar-brand .xibo-logo { + color: var(--ots-text); +} + +.navbar-nav > li > a, +.navbar-nav > .active > a, +.navbar-nav > .open > a { + color: var(--ots-text) !important; + background: transparent !important; +} + +.navbar-nav > li > a:hover { + color: var(--ots-primary) !important; +} + +.navbar-toggler, +.navbar-toggler-side { + color: var(--ots-text); + border: 1px solid var(--ots-border); +} + +/* Topbar nav refinements (dark) */ +.ots-topbar.navbar-nav { + background: transparent; + border: 0; + padding: 0; + margin: 0; + gap: 6px; + height: auto; + align-items: center; +} + +.ots-topbar .nav-link { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + border-radius: 10px; + color: var(--ots-text); + font-weight: 600; + transition: background var(--ots-transition), color var(--ots-transition); +} + +.ots-topbar .nav-link:hover, +.ots-topbar .nav-item.open .nav-link, +.ots-topbar .nav-item.active .nav-link { + background: rgba(79, 140, 255, 0.18); + color: var(--ots-primary); +} + +.ots-topbar .dropdown-menu { + border-radius: 12px; + padding: 8px; + box-shadow: var(--ots-shadow-md); +} + +.ots-topbar .dropdown-item, +.ots-topbar .dropdown-menu a { + display: flex; + align-items: center; + gap: 8px; + border-radius: 8px; + padding: 8px 10px; +} + +.ots-topbar-icon { + width: 16px; + text-align: center; + opacity: 0.85; + font-size: 13px; +} + +/* ============================================================================= + SIDEBAR + ============================================================================= */ + +#sidebar-wrapper { + background: var(--ots-surface); + border-right: 1px solid var(--ots-border); +} + +/* OTS sidebar override marker */ +.ots-sidebar-wrapper { + box-shadow: inset 0 0 0 1px rgba(79, 140, 255, 0.2); +} + +.ots-sidebar .sidebar-list a, +.ots-sidebar .sidebar-main a { + display: flex; + align-items: center; + gap: 10px; +} + +.ots-nav-icon { + width: 18px; + text-align: center; + opacity: 0.85; + font-size: 14px; +} + +.ots-nav-text { + flex: 1; + min-width: 0; +} + +/* Xibo sidebar refinements (dark) */ +#sidebar-wrapper .sidebar-title a { + color: var(--ots-text-faint); + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + padding: 12px 16px 6px; +} + +#sidebar-wrapper .sidebar-list a, +#sidebar-wrapper .sidebar-main a { + display: block; + margin: 2px 8px; + padding: 10px 12px; + border-radius: var(--ots-radius-sm); + transition: background var(--ots-transition), color var(--ots-transition); +} + +#sidebar-wrapper .sidebar-list a:hover, +#sidebar-wrapper .sidebar-main a:hover { + background: rgba(79, 140, 255, 0.12); + color: var(--ots-primary); +} + +#sidebar-wrapper .sidebar { + padding: 14px 0; +} + +#sidebar-wrapper .sidebar-main a, +#sidebar-wrapper .sidebar-list a { + display: block; + padding: 10px 18px; + color: var(--ots-text); + border-left: 3px solid transparent; +} + +#sidebar-wrapper .sidebar-main a:hover, +#sidebar-wrapper .sidebar-list a:hover { + background: rgba(79, 140, 255, 0.12); + color: var(--ots-primary); +} + +#sidebar-wrapper .sidebar-title a { + padding: 12px 18px 6px; + color: var(--ots-text-faint); + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +/* ============================================================================= + WIDGETS / CARDS + ============================================================================= */ + +.widget, +.card, +.panel, +.modal-content { + background: var(--ots-surface-2); + border: 1px solid var(--ots-border); + border-radius: var(--ots-radius-md); + box-shadow: var(--ots-shadow-sm); +} + +.widget-title, +.panel-heading, +.card-header, +.modal-header { + background: var(--ots-surface-3); + border-bottom: 1px solid var(--ots-border); + color: var(--ots-text); +} + +.widget-body, +.panel-body, +.card-body, +.modal-body { + color: var(--ots-text); +} + +/* ============================================================================= + BUTTONS + ============================================================================= */ + +.btn, +button, +input[type="button"], +input[type="submit"] { + border-radius: var(--ots-radius-sm); + border: 1px solid transparent; + transition: background var(--ots-transition), border var(--ots-transition), color var(--ots-transition); +} + +.btn-default, +.btn-secondary { + background: var(--ots-surface-3); + color: var(--ots-text); + border-color: var(--ots-border); +} + +.btn-default:hover, +.btn-secondary:hover { + background: var(--ots-surface-2); + border-color: var(--ots-primary); + color: var(--ots-primary); +} + +.btn-primary { + background: var(--ots-primary); + border-color: var(--ots-primary-2); + color: #0b1020; +} + +.btn-primary:hover { + background: var(--ots-primary-2); + border-color: var(--ots-primary-2); + color: #0b1020; +} + +.btn-success { + background: var(--ots-success); + border-color: var(--ots-success); + color: #041410; +} + +.btn-warning { + background: var(--ots-warning); + border-color: var(--ots-warning); + color: #1c1200; +} + +.btn-danger { + background: var(--ots-danger); + border-color: var(--ots-danger); + color: #220707; +} + +.btn-info { + background: var(--ots-info); + border-color: var(--ots-info); + color: #07101a; +} + +/* ============================================================================= + FORMS + ============================================================================= */ + +.form-control, +input[type="text"], +input[type="search"], +input[type="email"], +input[type="password"], +select, +textarea { + background: var(--ots-surface); + color: var(--ots-text); + border: 1px solid var(--ots-border); + border-radius: var(--ots-radius-sm); + box-shadow: none; +} + +.form-control:focus, +input[type="text"]:focus, +input[type="search"]:focus, +input[type="email"]:focus, +input[type="password"]:focus, +select:focus, +textarea:focus { + border-color: var(--ots-primary); + box-shadow: 0 0 0 2px rgba(79, 140, 255, 0.2); + outline: none; +} + +.input-group-addon { + background: var(--ots-surface-3); + border: 1px solid var(--ots-border); + color: var(--ots-text-muted); +} + +/* ============================================================================= + TABLES / DATATABLES + ============================================================================= */ + +.table, +.table > thead > tr > th, +.table > tbody > tr > td { + color: var(--ots-text); + border-color: var(--ots-border); +} + +.table-striped > tbody > tr:nth-of-type(odd) { + background: rgba(79, 140, 255, 0.04); +} + +.table-hover > tbody > tr:hover { + background: rgba(79, 140, 255, 0.08); +} + +.dataTables_wrapper .dataTables_length, +.dataTables_wrapper .dataTables_filter, +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_paginate { + color: var(--ots-text-muted) !important; +} + +.dataTables_wrapper .dataTables_filter input, +.dataTables_wrapper .dataTables_length select { + background: var(--ots-surface) !important; + color: var(--ots-text) !important; + border: 1px solid var(--ots-border) !important; +} + +.dataTables_wrapper table { + color: var(--ots-text); +} + +/* ============================================================================= + DROPDOWNS / MENUS + ============================================================================= */ + +.dropdown-menu { + background: var(--ots-surface-2); + border: 1px solid var(--ots-border); + box-shadow: var(--ots-shadow-md); +} + +.dropdown-item, +.dropdown-menu > li > a { + color: var(--ots-text); +} + +.dropdown-item:hover, +.dropdown-menu > li > a:hover { + background: rgba(79, 140, 255, 0.12); + color: var(--ots-primary); +} + +/* ============================================================================= + ALERTS / BADGES + ============================================================================= */ + +.alert { + border-radius: var(--ots-radius-sm); + border: 1px solid var(--ots-border); +} + +.alert-success { + background: rgba(42, 212, 164, 0.15); + color: var(--ots-success); +} + +.alert-warning { + background: rgba(244, 184, 96, 0.15); + color: var(--ots-warning); +} + +.alert-danger { + background: rgba(255, 107, 107, 0.15); + color: var(--ots-danger); +} + +.alert-info { + background: rgba(94, 192, 255, 0.15); + color: var(--ots-info); +} + +.badge, +.label { + background: var(--ots-surface-3); + color: var(--ots-text); + border: 1px solid var(--ots-border); +} + +/* ============================================================================= + TABS / PAGINATION + ============================================================================= */ + +.nav-tabs { + border-bottom: 1px solid var(--ots-border); +} + +.nav-tabs > li > a { + color: var(--ots-text-muted); + border: 1px solid transparent; +} + +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:focus, +.nav-tabs > li.active > a:hover { + background: var(--ots-surface-2); + color: var(--ots-text); + border: 1px solid var(--ots-border); + border-bottom-color: transparent; +} + +.pagination > li > a, +.pagination > li > span { + background: var(--ots-surface-2); + border: 1px solid var(--ots-border); + color: var(--ots-text); +} + +.pagination > li > a:hover, +.pagination > li > span:hover { + background: var(--ots-surface-3); + color: var(--ots-primary); +} + +.pagination > .active > a, +.pagination > .active > span { + background: var(--ots-primary); + border-color: var(--ots-primary-2); + color: #0b1020; +} + +/* ============================================================================= + MODALS + ============================================================================= */ + +.modal-content { + border-radius: var(--ots-radius-lg); +} + +.modal-footer { + border-top: 1px solid var(--ots-border); +} + +/* ============================================================================= + HELP PANE / MISC + ============================================================================= */ + +#help-pane { + background: var(--ots-surface-2); + border: 1px solid var(--ots-border); +} + +#help-pane .help-pane-btn { + background: var(--ots-primary); + color: #0b1020; +} + +/* ============================================================================= + OTS DASHBOARD MESSAGE + ============================================================================= */ + +.ots-dashboard-message { + margin: 16px 0 24px; + padding: 12px 16px; + border-radius: var(--ots-radius-md); + background: rgba(79, 140, 255, 0.16); + border: 1px solid rgba(79, 140, 255, 0.45); + color: var(--ots-text); +} + +.ots-dashboard-message__title { + font-weight: 700; + letter-spacing: 0.02em; + text-transform: uppercase; + font-size: 12px; + margin-bottom: 4px; +} + +.ots-dashboard-message__body { + font-size: 13px; + color: var(--ots-text-muted); +} diff --git a/custom/otssignange/views/theme-dashboard-message.twig b/custom/otssignange/views/theme-dashboard-message.twig new file mode 100644 index 0000000..79a5877 --- /dev/null +++ b/custom/otssignange/views/theme-dashboard-message.twig @@ -0,0 +1,8 @@ +{# + OTS Signage Theme override + Optional dashboard message block included with ignore missing +#} +
+
OTS Theme Active
+
This is a low-risk override for troubleshooting. Remove or restyle at any time.
+
diff --git a/custom/otssignange/views/theme-javascript.twig b/custom/otssignange/views/theme-javascript.twig new file mode 100644 index 0000000..f0e98fe --- /dev/null +++ b/custom/otssignange/views/theme-javascript.twig @@ -0,0 +1,22 @@ +{# + OTS Signage Theme - JavaScript and CSS injection + This file is auto-included by Xibo's base.twig at the end of the document + + NOTE: CSS and JS are INLINED to bypass web server MIME type issues with /custom/ paths + This ensures all styles and scripts load regardless of web server routing configuration +#} + + + + + + + + + diff --git a/custom/otssignange/views/theme-scripts.twig b/custom/otssignange/views/theme-scripts.twig new file mode 100644 index 0000000..1a0f74a --- /dev/null +++ b/custom/otssignange/views/theme-scripts.twig @@ -0,0 +1,368 @@ +/** + * OTS Signage Modern Theme - Client-Side Utilities + * Sidebar toggle, dropdown menus, and UI interactions + */ + +(function() { + 'use strict'; + + const STORAGE_KEYS = { + sidebarCollapsed: 'otsTheme:sidebarCollapsed' + }; + + /** + * Initialize sidebar toggle functionality + */ + function initSidebarToggle() { + const toggleBtn = document.querySelector('[data-action="toggle-sidebar"]'); + const sidebar = document.querySelector('.ots-sidebar'); + + if (!toggleBtn || !sidebar) return; + + toggleBtn.addEventListener('click', function(e) { + e.preventDefault(); + sidebar.classList.toggle('active'); + }); + + // Close sidebar when clicking outside on mobile + document.addEventListener('click', function(e) { + if (window.innerWidth <= 768) { + const isClickInsideSidebar = sidebar.contains(e.target); + const isClickOnToggle = toggleBtn.contains(e.target); + + if (!isClickInsideSidebar && !isClickOnToggle && sidebar.classList.contains('active')) { + sidebar.classList.remove('active'); + } + } + }); + } + + /** + * Initialize dropdown menus + */ + function initDropdowns() { + const dropdowns = document.querySelectorAll('.dropdown'); + + dropdowns.forEach(dropdown => { + const button = dropdown.querySelector('.dropdown-menu'); + + if (!button) return; + + const menu = dropdown.querySelector('.dropdown-menu'); + + // Toggle menu on button click + dropdown.addEventListener('click', function(e) { + if (e.target.closest('.user-btn') || e.target.closest('[aria-label="User menu"]')) { + e.preventDefault(); + dropdown.classList.toggle('active'); + } + }); + + // Close menu when clicking outside + document.addEventListener('click', function(e) { + if (!dropdown.contains(e.target)) { + dropdown.classList.remove('active'); + } + }); + }); + } + + /** + * Initialize search functionality + */ + function initSearch() { + const searchForm = document.querySelector('.topbar-search'); + if (!searchForm) return; + + const input = searchForm.querySelector('.search-input'); + if (input) { + input.addEventListener('focus', function() { + searchForm.style.borderColor = 'var(--color-primary)'; + }); + input.addEventListener('blur', function() { + searchForm.style.borderColor = 'var(--color-border)'; + }); + } + } + + /** + * Initialize page specific interactions + */ + function initPageInteractions() { + // Displays page - folder selection + const folderItems = document.querySelectorAll('.folder-item'); + folderItems.forEach(item => { + item.addEventListener('click', function() { + folderItems.forEach(f => f.classList.remove('active')); + this.classList.add('active'); + }); + }); + + // Filter collapse toggle + const filterCollapseBtn = document.querySelector('#ots-filter-collapse-btn'); + const filterContent = document.querySelector('#ots-filter-content'); + + if (filterCollapseBtn && filterContent) { + let isCollapsed = false; + + filterCollapseBtn.addEventListener('click', function() { + isCollapsed = !isCollapsed; + filterContent.classList.toggle('collapsed', isCollapsed); + + // Rotate icon + const icon = filterCollapseBtn.querySelector('i'); + icon.classList.toggle('fa-chevron-up'); + icon.classList.toggle('fa-chevron-down'); + + // Save preference to localStorage + localStorage.setItem('ots-filter-collapsed', isCollapsed); + }); + + // Restore saved preference + const savedState = localStorage.getItem('ots-filter-collapsed'); + if (savedState === 'true') { + isCollapsed = true; + filterContent.classList.add('collapsed'); + const icon = filterCollapseBtn.querySelector('i'); + icon.classList.remove('fa-chevron-up'); + icon.classList.add('fa-chevron-down'); + } + } + + // Displays page - folder tree toggle layout + const folderToggleBtn = document.querySelector('#folder-tree-select-folder-button'); + const folderContainer = document.querySelector('.grid-with-folders-container'); + const folderTree = document.querySelector('#grid-folder-filter'); + + if (folderToggleBtn && folderContainer && folderTree) { + let debounceTimeout; + + const syncFolderLayout = () => { + // Check actual visibility using computed styles + const computedStyle = window.getComputedStyle(folderTree); + const isHidden = computedStyle.display === 'none' || + computedStyle.visibility === 'hidden' || + folderTree.offsetHeight === 0; + + console.log('Folder collapse sync:', { + isHidden, + display: computedStyle.display, + visibility: computedStyle.visibility, + offsetHeight: folderTree.offsetHeight + }); + + folderContainer.classList.toggle('ots-folder-collapsed', !!isHidden); + + // Log the result + console.log('Container classes:', folderContainer.className); + console.log('Grid template columns:', window.getComputedStyle(folderContainer).gridTemplateColumns); + + // Force reflow + folderContainer.offsetHeight; + }; + + const debouncedSync = () => { + clearTimeout(debounceTimeout); + debounceTimeout = setTimeout(syncFolderLayout, 50); + }; + + // Watch for style/class changes on folderTree (let Xibo's code run first) + const treeObserver = new MutationObserver(() => { + console.log('Folder tree mutation detected, debouncing sync...'); + debouncedSync(); + }); + treeObserver.observe(folderTree, { + attributes: true, + attributeFilter: ['style', 'class'] + }); + + // Initial sync + syncFolderLayout(); + + // Monitor the folder tree's parent for display changes + const parentObserver = new MutationObserver(debouncedSync); + const treeParent = folderTree.parentElement; + if (treeParent) { + parentObserver.observe(treeParent, { + childList: false, + attributes: true, + subtree: false + }); + } + } + + // Media page - item selection + const mediaItems = document.querySelectorAll('.media-item'); + mediaItems.forEach(item => { + item.addEventListener('click', function() { + this.style.opacity = '0.7'; + setTimeout(() => this.style.opacity = '1', 200); + }); + }); + } + + /** + * Make sidebar responsive + */ + function makeResponsive() { + const sidebar = document.querySelector('.ots-sidebar'); + const main = document.querySelector('.ots-main'); + + if (!sidebar) return; + + // Add toggle button for mobile + if (window.innerWidth <= 768) { + sidebar.classList.add('mobile'); + } + + window.addEventListener('resize', function() { + if (window.innerWidth > 768) { + sidebar.classList.remove('mobile', 'active'); + } else { + sidebar.classList.add('mobile'); + } + }); + } + + /** + * Prevent Chart.js errors when chart elements are missing + */ + function initChartSafeguard() { + if (!window.Chart) return; + + if (typeof window.Chart.acquireContext === 'function') { + window.Chart.acquireContext = function(item) { + if (!item) return null; + + const candidate = item.length ? item[0] : item; + if (candidate && typeof candidate.getContext === 'function') { + return candidate.getContext('2d'); + } + + return null; + }; + return; + } + + if (window.Chart.prototype && typeof window.Chart.prototype.acquireContext === 'function') { + window.Chart.prototype.acquireContext = function(item) { + if (!item) return null; + + const candidate = item.length ? item[0] : item; + if (candidate && typeof candidate.getContext === 'function') { + return candidate.getContext('2d'); + } + + return null; + }; + } + } + + /** + * Enhance tables: wrap in card, add per-table search box, client-side filtering + * Non-destructive: skips tables already enhanced + */ + function enhanceTables() { + const selector = '.ots-content table, .content table, .container table, .card table, table'; + const tables = Array.from(document.querySelectorAll(selector)); + let counter = 0; + + tables.forEach(table => { + // only enhance tables that have a thead and tbody + if (!table || table.classList.contains('modern-table')) return; + if (!table.querySelector('thead') || !table.querySelector('tbody')) return; + + counter += 1; + table.classList.add('modern-table'); + + // Build wrapper structure + const wrapper = document.createElement('div'); + wrapper.className = 'modern-table-card'; + + const controls = document.createElement('div'); + controls.className = 'table-controls'; + + const input = document.createElement('input'); + input.type = 'search'; + input.placeholder = 'Search…'; + input.className = 'table-search-input'; + input.setAttribute('aria-label', 'Table search'); + input.style.minWidth = '180px'; + + controls.appendChild(input); + + const tableWrapper = document.createElement('div'); + tableWrapper.className = 'table-wrapper'; + tableWrapper.style.overflow = 'auto'; + + // Insert wrapper into DOM in place of the table + const parent = table.parentNode; + parent.replaceChild(wrapper, table); + wrapper.appendChild(controls); + wrapper.appendChild(tableWrapper); + tableWrapper.appendChild(table); + + // Simple, light-weight search filtering for this table only + input.addEventListener('input', function (e) { + const term = (e.target.value || '').toLowerCase(); + table.querySelectorAll('tbody tr').forEach(tr => { + const text = tr.textContent.toLowerCase(); + tr.style.display = term === '' || text.includes(term) ? '' : 'none'; + }); + }); + }); + } + + /** + * Initialize DataTables for enhanced behavior when available. + * Falls back gracefully if DataTables or jQuery are not present. + */ + function initDataTables() { + if (!window.jQuery) return; + const $ = window.jQuery; + if (!$.fn || !$.fn.dataTable) return; + + // Skip Xibo-managed grids to avoid double initialization + if (document.querySelector('.XiboGrid')) return; + + $('.modern-table, table').each(function () { + try { + if (this.closest('.XiboGrid')) return; + if (!$.fn.dataTable.isDataTable(this)) { + $(this).DataTable({ + responsive: true, + lengthChange: false, + pageLength: 10, + autoWidth: false, + dom: '<"table-controls"f>rt<"table-meta"ip>', + language: { search: '' } + }); + } + } catch (err) { + // If initialization fails, ignore and allow fallback enhancer + console.warn('DataTables init failed for table', this, err); + } + }); + } + + /** + * Initialize all features when DOM is ready + */ + function init() { + initSidebarToggle(); + initDropdowns(); + initSearch(); + initPageInteractions(); + initDataTables(); + enhanceTables(); + makeResponsive(); + initChartSafeguard(); + } + + // Wait for DOM to be ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/display-page.twig b/display-page.twig new file mode 100644 index 0000000..88010fd --- /dev/null +++ b/display-page.twig @@ -0,0 +1,369 @@ +{# +/** + * Copyright (C) 2023 Xibo Signage Ltd + * + * Xibo - Digital Signage - http://www.xibo.org.uk + * + * This file is part of Xibo. + * + * Xibo is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * Xibo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Xibo. If not, see . + */ +#} +{% extends "authed.twig" %} +{% import "inline.twig" as inline %} + +{% block title %}{{ "Displays"|trans }} | {% endblock %} + +{% block actionMenu %} +
+ {% if currentUser.featureEnabled("displays.add") %} + + {% endif %} + +
+{% endblock %} + +{% block headContent %} + {# Add page source code bundle ( CSS ) #} + +{% endblock %} + +{% block pageContent %} +
+
{% trans "Displays" %}
+
+
+
+
+ +
+
+
+ {% set title %}{% trans "ID" %}{% endset %} + {{ inline.number("displayId", title) }} + + {% set title %}{% trans "Name" %}{% endset %} + {{ inline.inputNameGrid('display', title) }} + + {% set title %}{% trans "Status" %}{% endset %} + {% set check %}{% trans "Up to date" %}{% endset %} + {% set cross %}{% trans "Downloading" %}{% endset %} + {% set cloud %}{% trans "Out of date" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: "1", option: check}, + { optionid: "2", option: cross}, + { optionid: "3", option: cloud} + ] %} + {{ inline.dropdown("mediaInventoryStatus", "single", title, "", options, "optionid", "option") }} + + {% set title %}{% trans "Logged In?" %}{% endset %} + {% set yesOption %}{% trans "Yes" %}{% endset %} + {% set noOption %}{% trans "No" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: "1", option: yesOption}, + { optionid: "0", option: noOption} + ] %} + {{ inline.dropdown("loggedIn", "single", title, "", options, "optionid", "option") }} + + {% set title %}{% trans "Authorised?" %}{% endset %} + {% set yesOption %}{% trans "Yes" %}{% endset %} + {% set noOption %}{% trans "No" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: "1", option: yesOption }, + { optionid: "0", option: noOption}, + ] %} + {{ inline.dropdown("authorised", "single", title, "", options, "optionid", "option") }} + + {% set title %}{% trans "XMR Registered?" %}{% endset %} + {% set yesOption %}{% trans "Yes" %}{% endset %} + {% set noOption %}{% trans "No" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: 1, option: yesOption}, + { optionid: 0, option: noOption}, + ] %} + {{ inline.dropdown("xmrRegistered", "single", title, "", options, "optionid", "option") }} + + {% if currentUser.featureEnabled("tag.tagging") %} + {% set title %}{% trans "Tags" %}{% endset %} + {% set exactTagTitle %}{% trans "Exact match?" %}{% endset %} + {% set logicalOperatorTitle %}{% trans "When filtering by multiple Tags, which logical operator should be used?" %}{% endset %} + {% set helpText %}{% trans "A comma separated list of tags to filter by. Enter a tag|tag value to filter tags with values. Enter --no-tag to filter all items without tags. Enter - before a tag or tag value to exclude from results." %}{% endset %} + {{ inline.inputWithTags("tags", title, null, helpText, null, null, null, "exactTags", exactTagTitle, logicalOperatorTitle) }} + {% endif %} + + {% if currentUser.featureEnabled("displaygroup.view") %} + {% set title %}{% trans "Display Group" %}{% endset %} + {% set attributes = [ + { name: "data-width", value: "200px" }, + { name: "data-allow-clear", value: "true" }, + { name: "data-placeholder--id", value: null }, + { name: "data-placeholder--value", value: "" }, + { name: "data-search-url", value: url_for("displayGroup.search") }, + { name: "data-filter-options", value: '{"isDisplaySpecific":0}' }, + { name: "data-search-term", value: "displayGroup" }, + { name: "data-id-property", value: "displayGroupId" }, + { name: "data-text-property", value: "displayGroup" }, + { name: "data-initial-key", value: "displayGroupId" }, + ] %} + {{ inline.dropdown("displayGroupId", "single", title, "", null, "displayGroupId", "displayGroup", helpText, "pagedSelect", "", "", "", attributes) }} + {% endif %} + + {% if currentUser.featureEnabled("displayprofile.view") %} + {% set title %}{% trans "Display Profile" %}{% endset %} + {{ inline.dropdown("displayProfileId", "single", title, "", [{displayProfileId:null, name:""}]|merge(displayProfiles), "displayProfileId", "name") }} + {% endif %} + + {{ inline.hidden("folderId") }} +
+ +
+ {% set title %}{% trans "Last Accessed" %}{% endset %} + {{ inline.date("lastAccessed", title) }} + + {% set title %}{% trans "Player Type" %}{% endset %} + {% set android %}{% trans "Android" %}{% endset %} + {% set chromeos %}{% trans "ChromeOS" %}{% endset %} + {% set windows %}{% trans "Windows" %}{% endset %} + {% set webos %}{% trans "webOS" %}{% endset %} + {% set sssp %}{% trans "Tizen" %}{% endset %} + {% set linux %}{% trans "Linux" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: "android", option: android}, + { optionid: "chromeos", option: chromeos}, + { optionid: "windows", option: windows}, + { optionid: "lg", option: webos}, + { optionid: "sssp", option: sssp}, + { optionid: "linux", option: linux}, + ] %} + {{ inline.dropdown("clientType", "single", title, "", options, "optionid", "option") }} + + {% set title %}{% trans "Player Code" %}{% endset %} + {{ inline.input("clientCode", title) }} + + {% set title %}{% trans "Custom ID" %}{% endset %} + {{ inline.input("customId", title) }} + + {% set title %}{% trans "Mac Address" %}{% endset %} + {{ inline.input("macAddress", title) }} + + {% set title %}{% trans "IP Address" %}{% endset %} + {{ inline.input("clientAddress", title) }} + + {% set title %}{% trans "Orientation" %}{% endset %} + {% set landscape %}{% trans "Landscape" %}{% endset %} + {% set portrait %}{% trans "Portrait" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: "landscape", option: landscape}, + { optionid: "portrait", option: portrait} + ] %} + {{ inline.dropdown("orientation", "single", title, "", options, "optionid", "option") }} + + {% set title %}{% trans "Commercial Licence" %}{% endset %} + {% set licensed %}{% trans "Licensed fully" %}{% endset %} + {% set trial %}{% trans "Trial" %}{% endset %} + {% set notLinceced %}{% trans "Not licenced" %}{% endset %} + {% set notApplicable %}{% trans "Not applicable" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: "1", option: licensed}, + { optionid: "2", option: trial}, + { optionid: "0", option: notLinceced}, + { optionid: "3", option: notApplicable} + ] %} + {{ inline.dropdown("commercialLicence", "single", title, "", options, "optionid", "option") }} + + {% set title %}{% trans "Player supported?" %}{% endset %} + {% set yesOption %}{% trans "Yes" %}{% endset %} + {% set noOption %}{% trans "No" %}{% endset %} + {% set options = [ + { optionid: "", option: "" }, + { optionid: 1, option: yesOption}, + { optionid: 0, option: noOption}, + ] %} + {{ inline.dropdown("isPlayerSupported", "single", title, "", options, "optionid", "option") }} +
+
+
+
+
+
+
+ +
+ + +
+
+

{% trans 'No Folders matching the search term' %}

+
+
+
+
+ + +
+
+ +
+
+ +
+ +
+
+ + + + + + + + + + + + + + + + + {% if currentUser.featureEnabled("tag.tagging") %}{% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% trans "ID" %}{% trans "Display" %}{% trans "Display Type" %}{% trans "Address" %}{% trans "Status" %}{% trans "Authorised?" %}{% trans "Current Layout" %}{% trans "Storage Available" %}{% trans "Storage Total" %}{% trans "Storage Free %" %}{% trans "Description" %}{% trans "Orientation" %}{% trans "Resolution" %}{% trans "Tags" %}{% trans "Default Layout" %}{% trans "Interleave Default" %}{% trans "Email Alert" %}{% trans "Logged In" %}{% trans "Last Accessed" %}{% trans "Display Profile" %}{% trans "Version" %}{% trans "Supported?" %}{% trans "Device Name" %}{% trans "IP Address" %}{% trans "Mac Address" %}{% trans "Timezone" %}{% trans "Languages" %}{% trans "Latitude" %}{% trans "Longitude" %}{% trans "Screen shot?" %}{% trans "Thumbnail" %}{% trans "CMS Transfer?" %}{% trans "Bandwidth Limit" %}{% trans "Last Command" %}{% trans "XMR Registered" %}{% trans "Commercial Licence" %}{% trans "Remote" %}{% trans "Sharing" %}{% trans "Screen Size" %}{% trans "Is Mobile?" %}{% trans "Outdoor?" %}{% trans "Reference 1" %}{% trans "Reference 2" %}{% trans "Reference 3" %}{% trans "Reference 4" %}{% trans "Reference 5" %}{% trans "Custom ID" %}{% trans "Cost Per Play" %}{% trans "Impressions Per Play" %}{% trans "Created Date" %}{% trans "Modified Date" %}{% trans "Faults?" %}{% trans "OS Version" %}{% trans "OS SDK" %}{% trans "Manufacturer" %}{% trans "Brand" %}{% trans "Model" %}
+ + +
+
+ +
+
+
+
+
+
+
+
+
+
+{% endblock %} + +{% block javaScript %} + {# Initialise JS variables and translations #} + + + {# Add page source code bundle ( JS ) #} + + +{% endblock %}