Commit 02d81493 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix: sidebar to right side (RTL) + add 20 UI customization features

- Moved sidebar from left to right for proper RTL layout
- Added full customization panel at /branding with 20 live UI preferences:
  sidebar width, font size, card style, animation speed, accent color,
  table density, toast position, content width, sidebar style, border radius,
  time format, number format, reduce motion, high contrast, color blind modes,
  focus mode, sounds, auto save, page transitions, compact header
- All preferences persist in localStorage and apply instantly
- Added customization.css + customization.js engine
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent b0303129
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
<link rel="stylesheet" href="/public/css/animations.css"> <link rel="stylesheet" href="/public/css/animations.css">
<link rel="stylesheet" href="/public/css/utilities.css"> <link rel="stylesheet" href="/public/css/utilities.css">
<link rel="stylesheet" href="/public/css/enhancements.css"> <link rel="stylesheet" href="/public/css/enhancements.css">
<link rel="stylesheet" href="/public/css/customization.css">
<?php if (isset($moduleCSS)): ?> <?php if (isset($moduleCSS)): ?>
<link rel="stylesheet" href="/modules/<?= $moduleCSS ?>/assets/<?= basename($moduleCSS) ?>.css"> <link rel="stylesheet" href="/modules/<?= $moduleCSS ?>/assets/<?= basename($moduleCSS) ?>.css">
<?php endif; ?> <?php endif; ?>
...@@ -34,6 +35,7 @@ ...@@ -34,6 +35,7 @@
<?php require LAYOUTS_PATH . '/partials/modal.php'; ?> <?php require LAYOUTS_PATH . '/partials/modal.php'; ?>
<?php require LAYOUTS_PATH . '/partials/confirm-dialog.php'; ?> <?php require LAYOUTS_PATH . '/partials/confirm-dialog.php'; ?>
<script src="/public/js/customization.js"></script>
<script src="/public/js/app.js"></script> <script src="/public/js/app.js"></script>
<script src="/public/js/sidebar.js"></script> <script src="/public/js/sidebar.js"></script>
<script src="/public/js/enhancements.js"></script> <script src="/public/js/enhancements.js"></script>
......
<div class="content-header"> <div class="content-header">
<h1>الهوية والعلامة التجارية</h1> <h1>الهوية والتخصيص</h1>
</div> </div>
<div class="tabs mb-5"> <div class="tabs mb-5">
<button class="tab active" onclick="switchBrandTab(this, 'colorsTab')">الألوان</button> <button class="tab active" onclick="switchBrandTab(this, 'customizeTab')">تخصيص الواجهة</button>
<button class="tab" onclick="switchBrandTab(this, 'colorsTab')">ألوان المنصة</button>
<button class="tab" onclick="switchBrandTab(this, 'assetsTab')">الأصول المرئية</button> <button class="tab" onclick="switchBrandTab(this, 'assetsTab')">الأصول المرئية</button>
<button class="tab" onclick="switchBrandTab(this, 'previewTab')">معاينة حية</button> <button class="tab" onclick="switchBrandTab(this, 'previewTab')">معاينة حية</button>
</div> </div>
<!-- Customization Tab -->
<div id="customizeTab" class="tab-content">
<div class="customize-grid">
<!-- 1. Sidebar Width -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon blue"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/></svg></div>
<div><div class="customize-card-title">عرض القائمة الجانبية</div><div class="customize-card-desc">حجم الشريط الجانبي</div></div>
</div>
<div class="customize-options" data-pref="sidebarWidth">
<button class="customize-option" data-value="compact">مدمج</button>
<button class="customize-option" data-value="normal">عادي</button>
<button class="customize-option" data-value="wide">عريض</button>
</div>
</div>
<!-- 2. Font Size -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon purple"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="4 7 4 4 20 4 20 7"/><line x1="9" y1="20" x2="15" y2="20"/><line x1="12" y1="4" x2="12" y2="20"/></svg></div>
<div><div class="customize-card-title">حجم الخط</div><div class="customize-card-desc">حجم النصوص في الواجهة</div></div>
</div>
<div class="customize-options" data-pref="fontSize">
<button class="customize-option" data-value="small">صغير</button>
<button class="customize-option" data-value="medium">متوسط</button>
<button class="customize-option" data-value="large">كبير</button>
</div>
</div>
<!-- 3. Card Style -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon gold"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg></div>
<div><div class="customize-card-title">نمط البطاقات</div><div class="customize-card-desc">شكل بطاقات المحتوى</div></div>
</div>
<div class="customize-options" data-pref="cardStyle">
<button class="customize-option" data-value="flat">مسطح</button>
<button class="customize-option" data-value="elevated">مرتفع</button>
<button class="customize-option" data-value="glass">زجاجي</button>
</div>
</div>
<!-- 4. Animation Speed -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon green"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg></div>
<div><div class="customize-card-title">سرعة الحركات</div><div class="customize-card-desc">الانتقالات والرسوم المتحركة</div></div>
</div>
<div class="customize-options" data-pref="animationSpeed">
<button class="customize-option" data-value="off">إيقاف</button>
<button class="customize-option" data-value="fast">سريع</button>
<button class="customize-option" data-value="normal">عادي</button>
</div>
</div>
<!-- 5. Accent Color -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon blue"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="13.5" cy="6.5" r="2.5"/><circle cx="17.5" cy="10.5" r="2.5"/><circle cx="8.5" cy="7.5" r="2.5"/><circle cx="6.5" cy="12.5" r="2.5"/><path d="M12 22c5.5 0 10-4.5 10-10S17.5 2 12 2 2 6.5 2 12s4.5 10 10 10z"/></svg></div>
<div><div class="customize-card-title">اللون الرئيسي</div><div class="customize-card-desc">لون التمييز العام</div></div>
</div>
<input type="color" class="customize-color-input" id="accentColorPicker" value="#2082F0" onchange="UIPrefs.set('accentColor', this.value)">
</div>
<!-- 6. Table Density -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon orange"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg></div>
<div><div class="customize-card-title">كثافة الجداول</div><div class="customize-card-desc">المسافات بين صفوف الجدول</div></div>
</div>
<div class="customize-options" data-pref="tableDensity">
<button class="customize-option" data-value="compact">مدمج</button>
<button class="customize-option" data-value="comfortable">مريح</button>
<button class="customize-option" data-value="spacious">واسع</button>
</div>
</div>
<!-- 7. Toast Position -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon cyan"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg></div>
<div><div class="customize-card-title">موقع الإشعارات</div><div class="customize-card-desc">مكان ظهور رسائل التنبيه</div></div>
</div>
<div class="customize-options" data-pref="toastPosition">
<button class="customize-option" data-value="top-left">أعلى يسار</button>
<button class="customize-option" data-value="top-right">أعلى يمين</button>
<button class="customize-option" data-value="bottom-left">أسفل يسار</button>
<button class="customize-option" data-value="bottom-right">أسفل يمين</button>
</div>
</div>
<!-- 8. Content Width -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon purple"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg></div>
<div><div class="customize-card-title">عرض المحتوى</div><div class="customize-card-desc">المساحة المتاحة للمحتوى</div></div>
</div>
<div class="customize-options" data-pref="contentWidth">
<button class="customize-option" data-value="narrow">ضيق</button>
<button class="customize-option" data-value="standard">قياسي</button>
<button class="customize-option" data-value="full">كامل</button>
</div>
</div>
<!-- 9. Sidebar Style -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon gold"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/></svg></div>
<div><div class="customize-card-title">نمط القائمة</div><div class="customize-card-desc">خلفية الشريط الجانبي</div></div>
</div>
<div class="customize-options" data-pref="sidebarStyle">
<button class="customize-option" data-value="solid">صلب</button>
<button class="customize-option" data-value="transparent">شفاف</button>
<button class="customize-option" data-value="gradient">متدرج</button>
</div>
</div>
<!-- 10. Border Radius -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon green"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg></div>
<div><div class="customize-card-title">الحواف</div><div class="customize-card-desc">شكل زوايا العناصر</div></div>
</div>
<div class="customize-options" data-pref="borderRadius">
<button class="customize-option" data-value="sharp">حاد</button>
<button class="customize-option" data-value="rounded">دائري</button>
<button class="customize-option" data-value="pill">كبسولة</button>
</div>
</div>
<!-- 11. Time Format -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon blue"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></div>
<div><div class="customize-card-title">صيغة الوقت</div><div class="customize-card-desc">طريقة عرض الأوقات</div></div>
</div>
<div class="customize-options" data-pref="timeFormat">
<button class="customize-option" data-value="24h">24 ساعة</button>
<button class="customize-option" data-value="12h">12 ساعة</button>
</div>
</div>
<!-- 12. Number Format -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon orange"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/></svg></div>
<div><div class="customize-card-title">صيغة الأرقام</div><div class="customize-card-desc">عربية أم غربية</div></div>
</div>
<div class="customize-options" data-pref="numberFormat">
<button class="customize-option" data-value="western">1234</button>
<button class="customize-option" data-value="arabic">١٢٣٤</button>
</div>
</div>
<!-- 13. Reduce Motion -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon purple"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14"/><path d="M12 5v14"/></svg></div>
<div><div class="customize-card-title">تقليل الحركة</div><div class="customize-card-desc">إيقاف جميع الرسوم المتحركة</div></div>
</div>
<div class="customize-toggle">
<span class="text-sm text-secondary">إيقاف الحركات</span>
<label class="toggle"><input type="checkbox" data-pref-toggle="reduceMotion" onchange="UIPrefs.set('reduceMotion', this.checked)"><span class="toggle-slider"></span></label>
</div>
</div>
<!-- 14. High Contrast -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon gold"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 2v20"/><path d="M12 2a10 10 0 0 1 0 20"/></svg></div>
<div><div class="customize-card-title">تباين عالي</div><div class="customize-card-desc">زيادة وضوح النصوص والحدود</div></div>
</div>
<div class="customize-toggle">
<span class="text-sm text-secondary">تفعيل التباين العالي</span>
<label class="toggle"><input type="checkbox" data-pref-toggle="highContrast" onchange="UIPrefs.set('highContrast', this.checked)"><span class="toggle-slider"></span></label>
</div>
</div>
<!-- 15. Color Blind Mode -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon cyan"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></div>
<div><div class="customize-card-title">وضع عمى الألوان</div><div class="customize-card-desc">ألوان بديلة لضعف رؤية الألوان</div></div>
</div>
<div class="customize-options" data-pref="colorBlindMode">
<button class="customize-option" data-value="none">بدون</button>
<button class="customize-option" data-value="protanopia">بروتانوبيا</button>
<button class="customize-option" data-value="deuteranopia">ديوتيرانوبيا</button>
<button class="customize-option" data-value="tritanopia">تريتانوبيا</button>
</div>
</div>
<!-- 16. Focus Mode -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon green"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/></svg></div>
<div><div class="customize-card-title">وضع التركيز</div><div class="customize-card-desc">إخفاء العناصر غير الضرورية</div></div>
</div>
<div class="customize-toggle">
<span class="text-sm text-secondary">تفعيل وضع التركيز</span>
<label class="toggle"><input type="checkbox" data-pref-toggle="focusMode" onchange="UIPrefs.set('focusMode', this.checked)"><span class="toggle-slider"></span></label>
</div>
</div>
<!-- 17. Sound -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon orange"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg></div>
<div><div class="customize-card-title">الأصوات</div><div class="customize-card-desc">أصوات عند الإشعارات</div></div>
</div>
<div class="customize-toggle">
<span class="text-sm text-secondary">تفعيل الأصوات</span>
<label class="toggle"><input type="checkbox" data-pref-toggle="soundEnabled" onchange="UIPrefs.set('soundEnabled', this.checked)"><span class="toggle-slider"></span></label>
</div>
</div>
<!-- 18. Auto Save -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon blue"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg></div>
<div><div class="customize-card-title">حفظ تلقائي</div><div class="customize-card-desc">حفظ التعديلات تلقائياً</div></div>
</div>
<div class="customize-toggle">
<span class="text-sm text-secondary">حفظ تلقائي للنماذج</span>
<label class="toggle"><input type="checkbox" data-pref-toggle="autoSave" onchange="UIPrefs.set('autoSave', this.checked)"><span class="toggle-slider"></span></label>
</div>
</div>
<!-- 19. Page Transitions -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon purple"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg></div>
<div><div class="customize-card-title">انتقالات الصفحات</div><div class="customize-card-desc">تأثيرات عند التنقل</div></div>
</div>
<div class="customize-toggle">
<span class="text-sm text-secondary">تفعيل الانتقالات</span>
<label class="toggle"><input type="checkbox" data-pref-toggle="pageTransitions" onchange="UIPrefs.set('pageTransitions', this.checked)"><span class="toggle-slider"></span></label>
</div>
</div>
<!-- 20. Compact Header -->
<div class="customize-card">
<div class="customize-card-header">
<div class="customize-card-icon gold"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/></svg></div>
<div><div class="customize-card-title">شريط علوي مدمج</div><div class="customize-card-desc">تقليل ارتفاع الشريط العلوي</div></div>
</div>
<div class="customize-toggle">
<span class="text-sm text-secondary">الوضع المدمج</span>
<label class="toggle"><input type="checkbox" data-pref-toggle="compactHeader" onchange="UIPrefs.set('compactHeader', this.checked)"><span class="toggle-slider"></span></label>
</div>
</div>
</div>
<div class="mt-5">
<div class="customize-reset" onclick="UIPrefs.reset(); location.reload();">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>
إعادة ضبط كل التخصيصات
</div>
</div>
</div>
<!-- Colors Tab --> <!-- Colors Tab -->
<div id="colorsTab" class="tab-content"> <div id="colorsTab" class="tab-content hidden">
<?php if (empty($colors)): ?> <?php if (empty($colors)): ?>
<div class="card"><div class="empty-state"><h3 class="empty-state-title">لا توجد ألوان محفوظة</h3><p class="empty-state-text">أضف ألوان العلامة التجارية من جدول platform_theme</p></div></div> <div class="card"><div class="empty-state"><h3 class="empty-state-title">لا توجد ألوان محفوظة</h3><p class="empty-state-text">أضف ألوان العلامة التجارية من جدول platform_theme</p></div></div>
<?php else: ?> <?php else: ?>
...@@ -98,3 +360,33 @@ ...@@ -98,3 +360,33 @@
</div> </div>
</div> </div>
</div> </div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const prefs = UIPrefs.get();
// Set active states for option buttons
document.querySelectorAll('.customize-options').forEach(group => {
const key = group.dataset.pref;
const currentVal = prefs[key];
group.querySelectorAll('.customize-option').forEach(btn => {
if (btn.dataset.value === currentVal) btn.classList.add('active');
btn.addEventListener('click', () => {
group.querySelectorAll('.customize-option').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
UIPrefs.set(key, btn.dataset.value);
});
});
});
// Set toggle states
document.querySelectorAll('[data-pref-toggle]').forEach(toggle => {
const key = toggle.dataset.prefToggle;
toggle.checked = prefs[key];
});
// Set color picker
const colorPicker = document.getElementById('accentColorPicker');
if (colorPicker) colorPicker.value = prefs.accentColor;
});
</script>
/* Customization Engine Styles */
/* Card Styles */
[data-card-style="flat"] .card,
[data-card-style="flat"] .stat-card {
box-shadow: none;
border: 1px solid var(--border);
}
[data-card-style="glass"] .card,
[data-card-style="glass"] .stat-card {
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.08);
}
/* Toast Position */
[data-toast-position="top-left"] .toast-container { top: var(--space-5); left: var(--space-5); right: auto; bottom: auto; }
[data-toast-position="top-right"] .toast-container { top: var(--space-5); right: var(--space-5); left: auto; bottom: auto; }
[data-toast-position="bottom-left"] .toast-container { bottom: var(--space-5); left: var(--space-5); right: auto; top: auto; }
[data-toast-position="bottom-right"] .toast-container { bottom: var(--space-5); right: var(--space-5); left: auto; top: auto; }
/* Sidebar Style */
[data-sidebar-style="transparent"] .sidebar {
background: rgba(17, 17, 35, 0.7);
backdrop-filter: blur(16px);
}
[data-sidebar-style="gradient"] .sidebar {
background: linear-gradient(180deg, var(--bg-secondary) 0%, rgba(32, 130, 240, 0.05) 100%);
}
/* High Contrast */
.high-contrast {
--text-primary: #ffffff;
--text-secondary: #e0e0e0;
--text-muted: #b0b0b0;
--border: rgba(255, 255, 255, 0.25);
--bg-hover: rgba(255, 255, 255, 0.12);
}
.high-contrast .card,
.high-contrast .stat-card {
border: 1px solid rgba(255, 255, 255, 0.2);
}
.high-contrast .nav-item.active {
background: rgba(32, 130, 240, 0.2);
}
/* Color Blind Modes */
[data-color-blind="protanopia"] {
--success: #3b82f6;
--danger: #f59e0b;
--warning: #8b5cf6;
}
[data-color-blind="deuteranopia"] {
--success: #06b6d4;
--danger: #f97316;
--warning: #a855f7;
}
[data-color-blind="tritanopia"] {
--success: #ec4899;
--danger: #14b8a6;
--warning: #f43f5e;
}
/* Focus Mode */
.focus-mode .sidebar .nav-section:not(:has(.nav-item.active)) .nav-section-items {
opacity: 0.4;
transition: opacity var(--duration-fast);
}
.focus-mode .sidebar .nav-section:not(:has(.nav-item.active)):hover .nav-section-items {
opacity: 1;
}
.focus-mode .topbar {
opacity: 0.7;
transition: opacity var(--duration-fast);
}
.focus-mode .topbar:hover {
opacity: 1;
}
/* Page transitions */
.no-page-transitions * {
animation-duration: 0ms !important;
transition-duration: 0ms !important;
}
/* Compact header */
.compact-header .topbar {
height: 48px;
min-height: 48px;
}
.compact-header .content-header {
margin-bottom: var(--space-4);
}
.compact-header .content-header h1 {
font-size: var(--font-size-lg);
}
/* Table density via custom property */
.data-table td,
.data-table th {
padding: var(--table-cell-padding, 10px 14px);
}
/* Customization Panel */
.customize-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--space-4);
}
.customize-card {
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: var(--space-5);
transition: border-color var(--duration-fast);
}
.customize-card:hover {
border-color: var(--border-hover);
}
.customize-card-header {
display: flex;
align-items: center;
gap: var(--space-3);
margin-bottom: var(--space-4);
}
.customize-card-icon {
width: 36px;
height: 36px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.customize-card-icon.blue { background: rgba(32, 130, 240, 0.15); color: var(--brand-blue); }
.customize-card-icon.purple { background: rgba(139, 92, 246, 0.15); color: var(--brand-purple); }
.customize-card-icon.gold { background: rgba(228, 172, 56, 0.15); color: var(--brand-gold); }
.customize-card-icon.green { background: rgba(16, 185, 129, 0.15); color: var(--success); }
.customize-card-icon.orange { background: rgba(232, 77, 30, 0.15); color: var(--brand-orange); }
.customize-card-icon.cyan { background: rgba(6, 182, 212, 0.15); color: var(--brand-cyan); }
.customize-card-title {
font-weight: var(--font-weight-semibold);
font-size: var(--font-size-sm);
}
.customize-card-desc {
font-size: var(--font-size-xs);
color: var(--text-muted);
}
.customize-options {
display: flex;
gap: var(--space-2);
flex-wrap: wrap;
}
.customize-option {
padding: var(--space-2) var(--space-3);
border: 1px solid var(--border);
border-radius: var(--radius-md);
font-size: var(--font-size-xs);
cursor: pointer;
transition: all var(--duration-fast);
background: transparent;
color: var(--text-secondary);
}
.customize-option:hover {
border-color: var(--brand-blue);
color: var(--text-primary);
}
.customize-option.active {
background: rgba(32, 130, 240, 0.15);
border-color: var(--brand-blue);
color: var(--brand-blue);
font-weight: var(--font-weight-medium);
}
.customize-toggle {
display: flex;
align-items: center;
justify-content: space-between;
}
.customize-color-input {
width: 100%;
height: 36px;
border: 1px solid var(--border);
border-radius: var(--radius-md);
cursor: pointer;
padding: 2px;
}
.customize-reset {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
padding: var(--space-3) var(--space-5);
border: 1px dashed var(--border);
border-radius: var(--radius-lg);
color: var(--text-muted);
cursor: pointer;
transition: all var(--duration-fast);
font-size: var(--font-size-sm);
}
.customize-reset:hover {
border-color: var(--danger);
color: var(--danger);
}
.app-layout { .app-layout {
display: grid; display: grid;
grid-template-columns: var(--sidebar-width) 1fr; grid-template-columns: 1fr var(--sidebar-width);
grid-template-rows: var(--topbar-height) 1fr; grid-template-rows: var(--topbar-height) 1fr;
grid-template-areas: grid-template-areas:
"sidebar topbar" "topbar sidebar"
"sidebar content"; "content sidebar";
min-height: 100vh; min-height: 100vh;
} }
.sidebar { .sidebar {
grid-area: sidebar; grid-area: sidebar;
background: var(--bg-secondary); background: var(--bg-secondary);
border-inline-start: 1px solid var(--border); border-inline-end: 1px solid var(--border);
position: fixed; position: fixed;
inset-inline-end: 0; inset-inline-start: 0;
top: 0; top: 0;
bottom: 0; bottom: 0;
width: var(--sidebar-width); width: var(--sidebar-width);
...@@ -108,7 +108,7 @@ ...@@ -108,7 +108,7 @@
.nav-item.active::before { .nav-item.active::before {
content: ''; content: '';
position: absolute; position: absolute;
inset-inline-end: 0; inset-inline-start: 0;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
width: 3px; width: 3px;
...@@ -148,7 +148,7 @@ ...@@ -148,7 +148,7 @@
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 90; z-index: 90;
margin-inline-end: var(--sidebar-width); margin-inline-start: var(--sidebar-width);
} }
.topbar-right { .topbar-right {
...@@ -196,7 +196,7 @@ ...@@ -196,7 +196,7 @@
.content { .content {
grid-area: content; grid-area: content;
padding: var(--space-6); padding: var(--space-6);
margin-inline-end: var(--sidebar-width); margin-inline-start: var(--sidebar-width);
min-height: calc(100vh - var(--topbar-height)); min-height: calc(100vh - var(--topbar-height));
max-width: var(--content-max-width); max-width: var(--content-max-width);
} }
...@@ -234,7 +234,7 @@ ...@@ -234,7 +234,7 @@
@media (max-width: 1200px) { @media (max-width: 1200px) {
.app-layout { .app-layout {
grid-template-columns: var(--sidebar-collapsed) 1fr; grid-template-columns: 1fr var(--sidebar-collapsed);
} }
.sidebar { .sidebar {
...@@ -258,7 +258,7 @@ ...@@ -258,7 +258,7 @@
} }
.topbar, .content { .topbar, .content {
margin-inline-end: var(--sidebar-collapsed); margin-inline-start: var(--sidebar-collapsed);
} }
} }
...@@ -271,7 +271,7 @@ ...@@ -271,7 +271,7 @@
} }
.sidebar { .sidebar {
transform: translateX(100%); transform: translateX(-100%);
width: var(--sidebar-width); width: var(--sidebar-width);
} }
...@@ -296,7 +296,7 @@ ...@@ -296,7 +296,7 @@
} }
.topbar, .content { .topbar, .content {
margin-inline-end: 0; margin-inline-start: 0;
} }
.mobile-toggle { .mobile-toggle {
......
/**
* El3ab UI Customization Engine
* All preferences stored in localStorage, applied via CSS custom property overrides
*/
(function() {
'use strict';
const STORAGE_KEY = 'el3ab-ui-prefs';
const DEFAULTS = {
sidebarWidth: 'normal', // compact | normal | wide
fontSize: 'medium', // small | medium | large
cardStyle: 'elevated', // flat | elevated | glass
animationSpeed: 'normal', // off | fast | normal
accentColor: '#2082F0', // hex
tableDensity: 'comfortable', // compact | comfortable | spacious
toastPosition: 'top-left', // top-left | top-right | bottom-left | bottom-right
contentWidth: 'standard', // narrow | standard | full
sidebarStyle: 'solid', // solid | transparent | gradient
borderRadius: 'rounded', // sharp | rounded | pill
timeFormat: '24h', // 12h | 24h
numberFormat: 'western', // western | arabic
reduceMotion: false,
highContrast: false,
colorBlindMode: 'none', // none | protanopia | deuteranopia | tritanopia
focusMode: false,
soundEnabled: false,
autoSave: true,
pageTransitions: true,
compactHeader: false,
};
function getPrefs() {
try {
const saved = localStorage.getItem(STORAGE_KEY);
return saved ? { ...DEFAULTS, ...JSON.parse(saved) } : { ...DEFAULTS };
} catch { return { ...DEFAULTS }; }
}
function savePrefs(prefs) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs));
}
function applyPrefs(prefs) {
const root = document.documentElement;
// 1. Sidebar width
const sidebarWidths = { compact: '200px', normal: '260px', wide: '300px' };
root.style.setProperty('--sidebar-width', sidebarWidths[prefs.sidebarWidth] || '260px');
// 2. Font size
const fontScales = { small: '13px', medium: '14px', large: '16px' };
root.style.setProperty('--font-size-base', fontScales[prefs.fontSize] || '14px');
document.body.style.fontSize = fontScales[prefs.fontSize] || '14px';
// 3. Card style
document.body.setAttribute('data-card-style', prefs.cardStyle);
// 4. Animation speed
const durations = { off: '0ms', fast: '100ms', normal: '200ms' };
root.style.setProperty('--duration-fast', durations[prefs.animationSpeed] || '150ms');
root.style.setProperty('--duration-normal', prefs.animationSpeed === 'off' ? '0ms' : '250ms');
// 5. Accent color
root.style.setProperty('--brand-blue', prefs.accentColor);
// 6. Table density
const densities = { compact: '4px 8px', comfortable: '10px 14px', spacious: '14px 18px' };
root.style.setProperty('--table-cell-padding', densities[prefs.tableDensity] || '10px 14px');
// 7. Toast position
document.body.setAttribute('data-toast-position', prefs.toastPosition);
// 8. Content width
const widths = { narrow: '900px', standard: '1200px', full: '100%' };
root.style.setProperty('--content-max-width', widths[prefs.contentWidth] || '1200px');
// 9. Sidebar style
document.body.setAttribute('data-sidebar-style', prefs.sidebarStyle);
// 10. Border radius
const radii = { sharp: '4px', rounded: '8px', pill: '16px' };
root.style.setProperty('--radius-md', radii[prefs.borderRadius] || '8px');
root.style.setProperty('--radius-lg', prefs.borderRadius === 'sharp' ? '6px' : prefs.borderRadius === 'pill' ? '20px' : '12px');
// 11-12. Time/Number format stored for use by formatters
// 13. Reduce motion
if (prefs.reduceMotion) {
root.style.setProperty('--duration-fast', '0ms');
root.style.setProperty('--duration-normal', '0ms');
root.style.setProperty('--duration-slow', '0ms');
}
// 14. High contrast
document.body.classList.toggle('high-contrast', prefs.highContrast);
// 15. Color blind mode
document.body.setAttribute('data-color-blind', prefs.colorBlindMode);
// 16. Focus mode
document.body.classList.toggle('focus-mode', prefs.focusMode);
// 17. Page transitions
document.body.classList.toggle('no-page-transitions', !prefs.pageTransitions);
// 18. Compact header
document.body.classList.toggle('compact-header', prefs.compactHeader);
}
// Expose globally
window.UIPrefs = {
get: getPrefs,
set: function(key, value) {
const prefs = getPrefs();
prefs[key] = value;
savePrefs(prefs);
applyPrefs(prefs);
},
reset: function() {
localStorage.removeItem(STORAGE_KEY);
applyPrefs(DEFAULTS);
},
apply: function() {
applyPrefs(getPrefs());
},
defaults: DEFAULTS,
};
// Apply on load
document.addEventListener('DOMContentLoaded', () => applyPrefs(getPrefs()));
// Also apply immediately for above-fold
applyPrefs(getPrefs());
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment