Commit 79079d7a authored by Mahmoud Aglan's avatar Mahmoud Aglan

css update

parent a8bfc7e5
...@@ -14,7 +14,8 @@ MenuRegistry::register('branches_settings', [ ...@@ -14,7 +14,8 @@ MenuRegistry::register('branches_settings', [
'children' => [ 'children' => [
['label_ar' => 'الفروع', 'label_en' => 'Branches', 'route' => '/branches', 'permission' => 'settings.view', 'order' => 1], ['label_ar' => 'الفروع', 'label_en' => 'Branches', 'route' => '/branches', 'permission' => 'settings.view', 'order' => 1],
['label_ar' => 'إعدادات النظام', 'label_en' => 'Settings', 'route' => '/settings', 'permission' => 'settings.view', 'order' => 2], ['label_ar' => 'إعدادات النظام', 'label_en' => 'Settings', 'route' => '/settings', 'permission' => 'settings.view', 'order' => 2],
['label_ar' => 'العلامة التجارية', 'label_en' => 'Branding', 'route' => '/settings/branding', 'permission' => 'settings.edit', 'order' => 3], ['label_ar' => 'المظهر والتخصيص', 'label_en' => 'Appearance', 'route' => '/settings/appearance', 'permission' => 'settings.edit', 'order' => 3],
['label_ar' => 'العلامة التجارية', 'label_en' => 'Branding', 'route' => '/settings/branding', 'permission' => 'settings.edit', 'order' => 4],
['label_ar' => 'سجل المراجعة', 'label_en' => 'Audit Log', 'route' => '/audit', 'permission' => 'report.view_audit', 'order' => 4], ['label_ar' => 'سجل المراجعة', 'label_en' => 'Audit Log', 'route' => '/audit', 'permission' => 'report.view_audit', 'order' => 4],
['label_ar' => 'التنبيهات', 'label_en' => 'Alerts', 'route' => '/alerts', 'permission' => 'alert.view', 'order' => 5], ['label_ar' => 'التنبيهات', 'label_en' => 'Alerts', 'route' => '/alerts', 'permission' => 'alert.view', 'order' => 5],
], ],
......
<?php
declare(strict_types=1);
namespace App\Modules\Settings\Controllers;
use App\Core\Controller;
use App\Core\Request;
use App\Core\Response;
use App\Modules\Settings\Models\SystemConfig;
class AppearanceController extends Controller
{
private const DEFAULTS = [
'appearance.brand_color' => '#0D7377',
'appearance.accent_color' => '#6366f1',
'appearance.sidebar_style' => 'dark',
'appearance.sidebar_width' => 'normal',
'appearance.card_radius' => 'rounded',
'appearance.font_size' => 'normal',
'appearance.animation' => 'full',
'appearance.header_style' => 'clean',
'appearance.table_style' => 'striped',
'appearance.density' => 'comfortable',
];
private const COLOR_PRESETS = [
['label' => 'تيل كلاسيك', 'value' => '#0D7377'],
['label' => 'أزرق ملكي', 'value' => '#2563EB'],
['label' => 'بنفسجي أنيق', 'value' => '#7C3AED'],
['label' => 'أخضر زمردي', 'value' => '#059669'],
['label' => 'وردي عصري', 'value' => '#DB2777'],
['label' => 'برتقالي دافئ', 'value' => '#EA580C'],
['label' => 'رمادي حديث', 'value' => '#475569'],
['label' => 'ذهبي فاخر', 'value' => '#B45309'],
];
public function index(Request $request): Response
{
$this->authorize('settings.edit');
$settings = [];
foreach (self::DEFAULTS as $key => $default) {
$row = SystemConfig::get($key);
$settings[str_replace('appearance.', '', $key)] = $row ? $row['config_value'] : $default;
}
return $this->view('Settings.Views.appearance', [
'settings' => $settings,
'colorPresets' => self::COLOR_PRESETS,
]);
}
public function update(Request $request): Response
{
$this->authorize('settings.edit');
$fields = [
'brand_color' => $_POST['brand_color'] ?? '',
'accent_color' => $_POST['accent_color'] ?? '',
'sidebar_style' => $_POST['sidebar_style'] ?? 'dark',
'sidebar_width' => $_POST['sidebar_width'] ?? 'normal',
'card_radius' => $_POST['card_radius'] ?? 'rounded',
'font_size' => $_POST['font_size'] ?? 'normal',
'animation' => $_POST['animation'] ?? 'full',
'header_style' => $_POST['header_style'] ?? 'clean',
'table_style' => $_POST['table_style'] ?? 'striped',
'density' => $_POST['density'] ?? 'comfortable',
];
foreach ($fields as $name => $value) {
$key = 'appearance.' . $name;
if ($name === 'brand_color' || $name === 'accent_color') {
if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $value)) {
$value = self::DEFAULTS[$key];
}
}
SystemConfig::set($key, $value);
}
return $this->redirect('/settings/appearance')->withSuccess('تم حفظ إعدادات المظهر بنجاح');
}
public static function getThemeVars(): array
{
$vars = [];
foreach (self::DEFAULTS as $key => $default) {
$row = SystemConfig::get($key);
$name = str_replace('appearance.', '', $key);
$vars[$name] = $row ? $row['config_value'] : $default;
}
return $vars;
}
public static function getCssOverrides(): string
{
$v = self::getThemeVars();
$css = ':root{';
if ($v['brand_color'] !== '#0D7377') {
$hex = $v['brand_color'];
$r = hexdec(substr($hex, 1, 2));
$g = hexdec(substr($hex, 3, 2));
$b = hexdec(substr($hex, 5, 2));
$css .= "--brand-primary:{$hex};";
$css .= "--brand-primary-rgb:{$r},{$g},{$b};";
$css .= "--brand-primary-light:{$hex}cc;";
$css .= "--brand-primary-dark:{$hex};";
$css .= "--border-focus:{$hex};";
}
if ($v['accent_color'] !== '#6366f1') {
$hex = $v['accent_color'];
$r = hexdec(substr($hex, 1, 2));
$g = hexdec(substr($hex, 3, 2));
$b = hexdec(substr($hex, 5, 2));
$css .= "--brand-accent:{$hex};";
$css .= "--brand-accent-rgb:{$r},{$g},{$b};";
}
$radiusMap = ['sharp' => '4px', 'rounded' => '10px', 'pill' => '18px'];
if (isset($radiusMap[$v['card_radius']])) {
$r = $radiusMap[$v['card_radius']];
$css .= "--radius-md:{$r};";
}
$fontMap = ['compact' => '13px', 'normal' => '14px', 'large' => '15px'];
if (isset($fontMap[$v['font_size']])) {
$css .= "--font-size-base:{$fontMap[$v['font_size']]};";
}
$widthMap = ['narrow' => '240px', 'normal' => '270px', 'wide' => '300px'];
if (isset($widthMap[$v['sidebar_width']])) {
$css .= "--sidebar-width:{$widthMap[$v['sidebar_width']]};";
}
$css .= '}';
if ($v['animation'] === 'none') {
$css .= '*,*::before,*::after{animation-duration:0s!important;transition-duration:0s!important;}';
} elseif ($v['animation'] === 'subtle') {
$css .= ':root{--duration-fast:100ms;--duration-normal:150ms;--duration-slow:200ms;}';
}
if ($v['sidebar_style'] === 'light') {
$css .= ':root{--sidebar-bg:#ffffff;--sidebar-item-hover:rgba(0,0,0,0.04);--sidebar-item-active:rgba(13,115,119,0.08);--sidebar-border:rgba(0,0,0,0.06);}';
$css .= '.sidebar{border-left:1px solid var(--border-light);}.sidebar .sidebar-brand-name,.sidebar .sidebar-item-label,.sidebar .sidebar-section-title{color:#1A1A2E!important;}.sidebar .sidebar-item i{color:#64748B!important;}.sidebar .sidebar-item.active i{color:var(--brand-primary)!important;}';
} elseif ($v['sidebar_style'] === 'gradient') {
$css .= ':root{--sidebar-bg:linear-gradient(180deg,#0f0f1a 0%,#1a1a3e 100%);}';
$css .= '.sidebar{background:var(--sidebar-bg);}';
}
if ($v['density'] === 'compact') {
$css .= '.page-content{padding:16px;}.card{padding:14px;}.data-table td,.data-table th{padding:8px 12px;}.form-input,.form-select,.form-textarea{padding:7px 12px;}';
} elseif ($v['density'] === 'spacious') {
$css .= '.page-content{padding:32px;}.card{padding:24px;}.data-table td,.data-table th{padding:14px 18px;}';
}
if ($v['table_style'] === 'bordered') {
$css .= '.data-table td,.data-table th{border:1px solid var(--border-light);}';
} elseif ($v['table_style'] === 'minimal') {
$css .= '.data-table thead{background:transparent;}.data-table th{border-bottom:2px solid var(--border-light);background:transparent;}.data-table td{border:none;}';
}
return $css;
}
}
...@@ -6,6 +6,10 @@ return [ ...@@ -6,6 +6,10 @@ return [
['GET', '/settings/group/{group}', 'Settings\Controllers\SettingsController@editGroup', ['auth'], 'settings.edit'], ['GET', '/settings/group/{group}', 'Settings\Controllers\SettingsController@editGroup', ['auth'], 'settings.edit'],
['POST', '/settings/group/{group}', 'Settings\Controllers\SettingsController@updateGroup', ['auth', 'csrf'], 'settings.edit'], ['POST', '/settings/group/{group}', 'Settings\Controllers\SettingsController@updateGroup', ['auth', 'csrf'], 'settings.edit'],
// Appearance
['GET', '/settings/appearance', 'Settings\Controllers\AppearanceController@index', ['auth'], 'settings.edit'],
['POST', '/settings/appearance', 'Settings\Controllers\AppearanceController@update', ['auth', 'csrf'], 'settings.edit'],
// Branding // Branding
['GET', '/settings/branding', 'Settings\Controllers\BrandingController@index', ['auth'], 'settings.edit'], ['GET', '/settings/branding', 'Settings\Controllers\BrandingController@index', ['auth'], 'settings.edit'],
['POST', '/settings/branding/logo', 'Settings\Controllers\BrandingController@updateLogo', ['auth', 'csrf'], 'settings.edit'], ['POST', '/settings/branding/logo', 'Settings\Controllers\BrandingController@updateLogo', ['auth', 'csrf'], 'settings.edit'],
......
This diff is collapsed.
...@@ -634,6 +634,13 @@ final class TutorialRegistry ...@@ -634,6 +634,13 @@ final class TutorialRegistry
['title' => 'تخصيص المحتوى', 'body' => 'حدد العناصر الظاهرة: الشعار، اسم النادي، بيانات العضو، تفاصيل الدفعة، كود QR.'], ['title' => 'تخصيص المحتوى', 'body' => 'حدد العناصر الظاهرة: الشعار، اسم النادي، بيانات العضو، تفاصيل الدفعة، كود QR.'],
['title' => 'معاينة وحفظ', 'body' => 'اضغط <span class="field">معاينة</span> لرؤية النتيجة. ثم <span class="field">حفظ</span>.<span class="success">التصميم يُطبق على جميع الإيصالات الجديدة فوراً.</span>'], ['title' => 'معاينة وحفظ', 'body' => 'اضغط <span class="field">معاينة</span> لرؤية النتيجة. ثم <span class="field">حفظ</span>.<span class="success">التصميم يُطبق على جميع الإيصالات الجديدة فوراً.</span>'],
], ],
'settings.appearance' => [
['title' => 'فتح إعدادات المظهر', 'body' => 'من القائمة الجانبية: <span class="field">الإعدادات</span> > <span class="field">المظهر والتخصيص</span>. تفتح صفحة تحتوي على جميع خيارات تخصيص شكل النظام.'],
['title' => 'تغيير الألوان', 'body' => 'اختر <span class="field">اللون الأساسي</span> من القائمة الجاهزة (8 ألوان) أو أدخل لون مخصص بصيغة HEX. يمكنك أيضاً تغيير <span class="field">لون التمييز</span> المستخدم في الأزرار الثانوية.<span class="info">اللون الأساسي يُطبق على القائمة الجانبية والأزرار والعناصر التفاعلية.</span>'],
['title' => 'نمط القائمة الجانبية', 'body' => 'اختر أحد الأنماط:<ul><li><strong>داكن</strong> — الافتراضي، خلفية سوداء أنيقة</li><li><strong>فاتح</strong> — خلفية بيضاء بحدود خفيفة</li><li><strong>متدرج</strong> — تدرج لوني احترافي</li></ul>يمكنك أيضاً تغيير عرض القائمة (ضيق، عادي، عريض).'],
['title' => 'الخطوط والحواف والكثافة', 'body' => 'تحكم في:<ul><li><span class="field">حجم الخط</span>: مضغوط (13px)، عادي (14px)، كبير (15px)</li><li><span class="field">استدارة البطاقات</span>: حاد، مستدير، كبسولة</li><li><span class="field">الحركات</span>: كاملة، خفيفة، بدون</li><li><span class="field">كثافة العرض</span>: مضغوط، مريح، واسع</li></ul>'],
['title' => 'المعاينة والحفظ', 'body' => 'لوحة المعاينة الحية أسفل الصفحة تعرض تأثير التغييرات فوراً. بعد الرضا عن الشكل اضغط <span class="field">حفظ التغييرات</span>.<span class="success">التغييرات تُطبق فوراً على جميع المستخدمين. يمكنك العودة للإعدادات الافتراضية في أي وقت بزر «استعادة الافتراضي».</span>'],
],
// ── FINES ── // ── FINES ──
'fines.impose-fine' => [ 'fines.impose-fine' => [
['title' => 'فرض غرامة', 'body' => 'من ملف العضو > <span class="field">مخالفات</span> > <span class="field">غرامة جديدة</span>.'], ['title' => 'فرض غرامة', 'body' => 'من ملف العضو > <span class="field">مخالفات</span> > <span class="field">غرامة جديدة</span>.'],
...@@ -1770,6 +1777,14 @@ final class TutorialRegistry ...@@ -1770,6 +1777,14 @@ final class TutorialRegistry
'category' => 'customization', 'category' => 'customization',
'order' => 3, 'order' => 3,
], ],
'appearance' => [
'title' => 'المظهر والتخصيص',
'subtitle' => 'تغيير ألوان النظام ونمط القائمة والخطوط والحركات',
'icon' => 'paintbrush',
'color' => '#8B5CF6',
'category' => 'customization',
'order' => 4,
],
]; ];
} }
......
...@@ -32,6 +32,7 @@ $currentPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH); ...@@ -32,6 +32,7 @@ $currentPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
<!-- Main Stylesheet --> <!-- Main Stylesheet -->
<link rel="stylesheet" href="<?= url('assets/css/main.css') ?>?v=<?= @filemtime(dirname(__DIR__, 3) . '/public/assets/css/main.css') ?: time() ?>"> <link rel="stylesheet" href="<?= url('assets/css/main.css') ?>?v=<?= @filemtime(dirname(__DIR__, 3) . '/public/assets/css/main.css') ?: time() ?>">
<style><?= \App\Modules\Settings\Controllers\AppearanceController::getCssOverrides() ?></style>
<?= $__template->yield('styles', '') ?> <?= $__template->yield('styles', '') ?>
</head> </head>
<body> <body>
......
...@@ -2137,3 +2137,188 @@ code { ...@@ -2137,3 +2137,188 @@ code {
.p-lg { padding: 20px; } .p-lg { padding: 20px; }
.w-full { width: 100%; } .w-full { width: 100%; }
.hidden { display: none; } .hidden { display: none; }
/* ══════════════════════════════════════════════════
PREMIUM ANIMATIONS & MICRO-INTERACTIONS
══════════════════════════════════════════════════ */
/* Page content entrance */
.page-content {
animation: pageEnter var(--duration-slow) var(--ease-out) both;
}
@keyframes pageEnter {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: none; }
}
/* Card hover lift — only for interactive/grid cards */
.stats-card,
.card-interactive,
.appear-card {
transition: transform var(--duration-normal) var(--ease-out),
box-shadow var(--duration-normal) var(--ease-out),
border-color var(--duration-fast) ease;
}
/* Staggered card entrance */
.card:nth-child(1) { animation-delay: 0ms; }
.card:nth-child(2) { animation-delay: 50ms; }
.card:nth-child(3) { animation-delay: 100ms; }
.card:nth-child(4) { animation-delay: 150ms; }
.card:nth-child(5) { animation-delay: 200ms; }
.card:nth-child(6) { animation-delay: 250ms; }
/* Table row entrance */
.data-table tbody tr {
animation: rowSlideIn var(--duration-normal) var(--ease-out) both;
}
.data-table tbody tr:nth-child(1) { animation-delay: 0ms; }
.data-table tbody tr:nth-child(2) { animation-delay: 30ms; }
.data-table tbody tr:nth-child(3) { animation-delay: 60ms; }
.data-table tbody tr:nth-child(4) { animation-delay: 90ms; }
.data-table tbody tr:nth-child(5) { animation-delay: 120ms; }
.data-table tbody tr:nth-child(6) { animation-delay: 150ms; }
.data-table tbody tr:nth-child(7) { animation-delay: 180ms; }
.data-table tbody tr:nth-child(8) { animation-delay: 210ms; }
.data-table tbody tr:nth-child(9) { animation-delay: 240ms; }
.data-table tbody tr:nth-child(10) { animation-delay: 270ms; }
@keyframes rowSlideIn {
from { opacity: 0; transform: translateX(8px); }
to { opacity: 1; transform: none; }
}
/* Table row hover enhancement */
.data-table tbody tr {
transition: background var(--duration-fast) ease;
}
/* Button ghost/secondary hover lift */
.btn-secondary:hover,
.btn-ghost:hover {
transform: translateY(-1px);
}
/* Form group label transition on focus */
.form-group:focus-within .form-label {
transform: translateX(-2px);
}
/* Badge pulse for warning status indicators */
.badge-warning {
animation: badgePulse 2s ease-in-out infinite;
}
/* Modal backdrop enhancement */
.modal-overlay {
animation: overlayFadeIn var(--duration-normal) ease both;
}
/* Sidebar item hover slide (RTL: shift content slightly left) */
.sidebar-item {
transition: background var(--duration-fast) ease,
padding-left var(--duration-fast) var(--ease-out);
}
.sidebar-item:hover {
padding-left: 4px;
}
/* Stat card counter animation */
.stat-value {
transition: color var(--duration-normal) ease;
}
/* Smooth scrollbar */
::-webkit-scrollbar {
width: 7px;
height: 7px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.15);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.25);
}
/* Focus ring for accessibility */
:focus-visible {
outline: 2px solid var(--brand-primary);
outline-offset: 2px;
border-radius: var(--radius-sm);
}
/* Enhanced card header with subtle gradient */
.card-header {
position: relative;
}
.card-header::after {
content: '';
position: absolute;
bottom: 0;
right: 0;
left: 0;
height: 1px;
background: linear-gradient(90deg, var(--border-light), rgba(var(--brand-primary-rgb), 0.2), var(--border-light));
}
/* Badge border enhancement */
.badge-primary { border: 1px solid rgba(var(--brand-primary-rgb), 0.15); }
.badge-success { border: 1px solid var(--success-border); }
.badge-danger { border: 1px solid var(--danger-border); }
.badge-warning { border: 1px solid var(--warning-border); }
.badge-info { border: 1px solid var(--info-border); }
/* Form section title accent */
.form-section-title::before {
content: '';
width: 4px;
height: 18px;
background: var(--brand-primary);
border-radius: 2px;
flex-shrink: 0;
}
/* Pagination active glow */
.page-link.active {
box-shadow: 0 2px 8px rgba(var(--brand-primary-rgb), 0.35);
}
/* Stats card number font feature */
.stats-card-value {
font-feature-settings: 'tnum';
}
/* Tabs active indicator glow */
.tab-link.active {
text-shadow: 0 0 8px rgba(var(--brand-primary-rgb), 0.15);
}
/* Tooltip hover */
[data-tooltip] {
position: relative;
}
[data-tooltip]::after {
content: attr(data-tooltip);
position: absolute;
bottom: calc(100% + 6px);
right: 50%;
transform: translateX(50%) translateY(4px);
background: #1A1A2E;
color: #fff;
padding: 4px 10px;
border-radius: 6px;
font-size: 11px;
font-weight: 600;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity var(--duration-fast) ease, transform var(--duration-fast) ease;
z-index: var(--z-toast);
}
[data-tooltip]:hover::after {
opacity: 1;
transform: translateX(50%) translateY(0);
}
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