Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
Clubphp
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Clubphp
Commits
56b6d45d
Commit
56b6d45d
authored
Apr 07, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 3 files via Son of Anton
parent
c74a9e19
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
743 additions
and
365 deletions
+743
-365
App.php
app/Core/App.php
+214
-69
main.php
app/Shared/Layout/main.php
+70
-36
main.css
public/assets/css/main.css
+459
-260
No files found.
app/Core/App.php
View file @
56b6d45d
...
...
@@ -7,16 +7,20 @@ final class App
{
private
static
?
App
$instance
=
null
;
private
Config
$config
;
private
Database
$database
;
private
Session
$session
;
private
Router
$router
;
private
?
Database
$db
=
null
;
private
?
Session
$session
=
null
;
private
?
Router
$router
=
null
;
private
array
$config
=
[]
;
private
?
object
$currentEmployee
=
null
;
private
?
array
$currentBranch
=
null
;
private
array
$bindings
=
[];
private
string
$basePath
;
private
bool
$booted
=
false
;
private
function
__construct
()
{}
private
function
__construct
()
{
$this
->
basePath
=
dirname
(
__DIR__
,
2
);
}
public
static
function
getInstance
()
:
self
{
...
...
@@ -32,84 +36,200 @@ final class App
return
$this
;
}
// Set timezone
date_default_timezone_set
(
'Africa/Cairo'
);
$this
->
config
=
new
Config
();
$this
->
config
->
loadAll
();
// Load .env
$this
->
loadEnv
();
$this
->
database
=
new
Database
(
$this
->
config
->
get
(
'database.host'
,
'127.0.0.1'
),
(
int
)
$this
->
config
->
get
(
'database.port'
,
'3306'
),
$this
->
config
->
get
(
'database.name'
,
'the_club_erp'
),
$this
->
config
->
get
(
'database.user'
,
'root'
),
$this
->
config
->
get
(
'database.pass'
,
''
),
$this
->
config
->
get
(
'database.charset'
,
'utf8mb4'
)
);
// Load config files
$this
->
loadConfig
();
$this
->
session
=
new
Session
(
(
int
)
$this
->
config
->
get
(
'app.session_lifetime'
,
30
)
);
$this
->
session
->
start
();
// Initialize error handler
ExceptionHandler
::
register
();
$this
->
config
->
loadFromDatabase
(
$this
->
database
);
// Initialize database
$this
->
initDatabase
();
$this
->
router
=
new
Router
();
// Initialize session
$this
->
initSession
();
// Load DB config overrides
$this
->
loadDbConfigOverrides
();
$this
->
discoverModuleBootstraps
();
$this
->
discoverModuleRoutes
();
// Auto-discover and load module bootstraps
$this
->
loadModuleBootstraps
();
// Auto-discover and load module routes
$this
->
router
=
new
Router
();
$this
->
loadModuleRoutes
();
$this
->
booted
=
true
;
return
$this
;
}
private
function
discoverModuleBootstraps
()
:
void
private
function
loadEnv
()
:
void
{
$pattern
=
$this
->
basePath
()
.
'/app/Modules/*/bootstrap.php'
;
$files
=
glob
(
$pattern
);
if
(
$files
===
false
)
{
$envFile
=
$this
->
basePath
.
'/.env'
;
if
(
!
file_exists
(
$envFile
))
{
return
;
}
sort
(
$files
);
foreach
(
$files
as
$file
)
{
require_once
$file
;
$lines
=
file
(
$envFile
,
FILE_IGNORE_NEW_LINES
|
FILE_SKIP_EMPTY_LINES
);
foreach
(
$lines
as
$line
)
{
$line
=
trim
(
$line
);
if
(
$line
===
''
||
$line
[
0
]
===
'#'
)
{
continue
;
}
if
(
str_contains
(
$line
,
'='
))
{
[
$key
,
$value
]
=
explode
(
'='
,
$line
,
2
);
$key
=
trim
(
$key
);
$value
=
trim
(
$value
);
if
(
!
isset
(
$_ENV
[
$key
]))
{
$_ENV
[
$key
]
=
$value
;
putenv
(
"
{
$key
}
=
{
$value
}
"
);
}
}
}
}
private
function
discoverModuleRoutes
()
:
void
private
function
loadConfig
()
:
void
{
$pattern
=
$this
->
basePath
()
.
'/app/Modules/*/Routes.php'
;
$files
=
glob
(
$pattern
);
$configDir
=
$this
->
basePath
.
'/config'
;
if
(
!
is_dir
(
$configDir
))
{
return
;
}
$files
=
glob
(
$configDir
.
'/*.php'
);
if
(
$files
===
false
)
{
return
;
}
sort
(
$files
);
foreach
(
$files
as
$file
)
{
$routes
=
require
$file
;
if
(
is_array
(
$routes
))
{
foreach
(
$routes
as
$route
)
{
$this
->
router
->
addRoute
(
$route
[
0
],
$route
[
1
],
$route
[
2
],
$route
[
3
]
??
[],
$route
[
4
]
??
null
);
$key
=
basename
(
$file
,
'.php'
);
$this
->
config
[
$key
]
=
require
$file
;
}
}
private
function
initDatabase
()
:
void
{
$this
->
db
=
new
Database
(
(
string
)
(
$this
->
config
[
'database'
][
'host'
]
??
env
(
'DB_HOST'
,
'127.0.0.1'
)),
(
int
)
(
$this
->
config
[
'database'
][
'port'
]
??
env
(
'DB_PORT'
,
3306
)),
(
string
)
(
$this
->
config
[
'database'
][
'name'
]
??
env
(
'DB_NAME'
,
'the_club_erp'
)),
(
string
)
(
$this
->
config
[
'database'
][
'user'
]
??
env
(
'DB_USER'
,
'root'
)),
(
string
)
(
$this
->
config
[
'database'
][
'pass'
]
??
env
(
'DB_PASS'
,
''
)),
(
string
)
(
$this
->
config
[
'database'
][
'charset'
]
??
'utf8mb4'
)
);
}
private
function
initSession
()
:
void
{
$this
->
session
=
new
Session
();
}
private
function
loadDbConfigOverrides
()
:
void
{
try
{
if
(
!
$this
->
db
->
tableExists
(
'system_config'
))
{
return
;
}
$rows
=
$this
->
db
->
select
(
"SELECT config_key, config_value, config_type FROM system_config"
);
foreach
(
$rows
as
$row
)
{
$value
=
$row
[
'config_value'
];
switch
(
$row
[
'config_type'
])
{
case
'integer'
:
$value
=
(
int
)
$value
;
break
;
case
'float'
:
$value
=
(
float
)
$value
;
break
;
case
'boolean'
:
$value
=
in_array
(
strtolower
((
string
)
$value
),
[
'1'
,
'true'
,
'yes'
]);
break
;
case
'json'
:
$value
=
json_decode
(
$value
,
true
)
??
$value
;
break
;
}
// Store as dot notation override
$this
->
config
[
'db_overrides'
][
$row
[
'config_key'
]]
=
$value
;
}
}
catch
(
\Throwable
$e
)
{
// DB might not be ready yet — ignore
}
}
public
function
config
(
string
$key
=
null
,
$default
=
null
)
/**
* Auto-discover and require all Modules/*/
bootstrap
.
php
files
.
*
This
is
where
modules
register
their
menu
items
,
permissions
,
event
listeners
,
etc
.
*/
private
function
loadModuleBootstraps
()
:
void
{
if
(
$key
===
null
)
{
return
$this
->
config
;
$modulesDir
=
$this
->
basePath
.
'/app/Modules'
;
if
(
!
is_dir
(
$modulesDir
))
{
return
;
}
$bootstrapFiles
=
glob
(
$modulesDir
.
'/*/bootstrap.php'
);
if
(
$bootstrapFiles
===
false
)
{
return
;
}
sort
(
$bootstrapFiles
);
// Alphabetical order for deterministic loading
foreach
(
$bootstrapFiles
as
$file
)
{
try
{
require_once
$file
;
}
catch
(
\Throwable
$e
)
{
Logger
::
warning
(
'Failed to load bootstrap: '
.
$file
.
' — '
.
$e
->
getMessage
());
}
}
}
/**
* Auto-discover and merge all Modules/*/
Routes
.
php
files
.
*/
private
function
loadModuleRoutes
()
:
void
{
$modulesDir
=
$this
->
basePath
.
'/app/Modules'
;
if
(
!
is_dir
(
$modulesDir
))
{
return
;
}
$routeFiles
=
glob
(
$modulesDir
.
'/*/Routes.php'
);
if
(
$routeFiles
===
false
)
{
return
;
}
sort
(
$routeFiles
);
foreach
(
$routeFiles
as
$file
)
{
try
{
$routes
=
require
$file
;
if
(
is_array
(
$routes
))
{
foreach
(
$routes
as
$route
)
{
if
(
is_array
(
$route
)
&&
count
(
$route
)
>=
3
)
{
$this
->
router
->
addRoute
(
$route
[
0
],
// method
$route
[
1
],
// path
$route
[
2
],
// controller@action
$route
[
3
]
??
[],
// middleware
$route
[
4
]
??
null
// permission
);
}
}
}
}
catch
(
\Throwable
$e
)
{
Logger
::
warning
(
'Failed to load routes: '
.
$file
.
' — '
.
$e
->
getMessage
());
}
}
return
$this
->
config
->
get
(
$key
,
$default
);
}
// ── Accessors ──
public
function
db
()
:
Database
{
return
$this
->
d
atabase
;
return
$this
->
d
b
;
}
public
function
session
()
:
Session
...
...
@@ -122,53 +242,78 @@ final class App
return
$this
->
router
;
}
public
function
setCurrentEmployee
(
?
object
$employee
)
:
void
public
function
config
(
string
$key
=
null
,
$default
=
null
)
{
$this
->
currentEmployee
=
$employee
;
if
(
$key
===
null
)
{
return
$this
->
config
;
}
// Check DB overrides first
if
(
isset
(
$this
->
config
[
'db_overrides'
][
$key
]))
{
return
$this
->
config
[
'db_overrides'
][
$key
];
}
// Dot notation: 'app.name' → $this->config['app']['name']
$parts
=
explode
(
'.'
,
$key
);
$value
=
$this
->
config
;
foreach
(
$parts
as
$part
)
{
if
(
is_array
(
$value
)
&&
array_key_exists
(
$part
,
$value
))
{
$value
=
$value
[
$part
];
}
else
{
return
$default
;
}
}
return
$value
;
}
public
function
currentEmployee
()
:
?
object
public
function
basePath
()
:
string
{
return
$this
->
currentEmployee
;
return
$this
->
basePath
;
}
public
function
setCurrentBranch
(
?
array
$branch
)
:
void
public
function
publicPath
()
:
string
{
$this
->
currentBranch
=
$branch
;
return
$this
->
basePath
.
'/public'
;
}
public
function
currentBranch
()
:
?
array
public
function
storagePath
()
:
string
{
return
$this
->
currentBranch
;
return
$this
->
basePath
.
'/storage'
;
}
public
function
bind
(
string
$key
,
$value
)
:
void
// ── Employee Context ──
public
function
setCurrentEmployee
(
object
$employee
)
:
void
{
$this
->
bindings
[
$key
]
=
$valu
e
;
$this
->
currentEmployee
=
$employe
e
;
}
public
function
resolve
(
string
$key
,
$default
=
null
)
public
function
currentEmployee
()
:
?
object
{
return
$this
->
bindings
[
$key
]
??
$default
;
return
$this
->
currentEmployee
;
}
public
function
basePath
()
:
string
public
function
setCurrentBranch
(
?
array
$branch
)
:
void
{
return
Autoloader
::
basePath
()
;
$this
->
currentBranch
=
$branch
;
}
public
function
publicPath
()
:
string
public
function
currentBranch
()
:
?
array
{
return
$this
->
basePath
()
.
'/public'
;
return
$this
->
currentBranch
;
}
public
function
storagePath
()
:
string
// ── Simple Service Container ──
public
function
bind
(
string
$key
,
$value
)
:
void
{
return
$this
->
basePath
()
.
'/storage'
;
$this
->
bindings
[
$key
]
=
$value
;
}
public
function
isDebug
()
:
bool
public
function
resolve
(
string
$key
,
$default
=
null
)
{
return
(
bool
)
$this
->
config
->
get
(
'app.debug'
,
false
)
;
return
$this
->
bindings
[
$key
]
??
$default
;
}
}
\ No newline at end of file
app/Shared/Layout/main.php
View file @
56b6d45d
<?php
/**
* Main application layout — RTL Arabic-first.
* All authenticated pages use this layout.
*/
use
App\Core\App
;
use
App\Core\CSRF
;
$app
=
App
::
getInstance
();
$employee
=
$app
->
currentEmployee
();
$employeeName
=
$employee
?
(
$employee
->
full_name_ar
??
'مستخدم'
)
:
'زائر'
;
$currentPath
=
parse_url
(
$_SERVER
[
'REQUEST_URI'
]
??
'/'
,
PHP_URL_PATH
);
?>
<!DOCTYPE html>
<html
lang=
"ar"
dir=
"rtl"
>
<head>
<meta
charset=
"UTF-8"
>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
>
<meta
name=
"csrf-token"
content=
"
<?=
\App\Core\CSRF
::
token
(
)
?>
"
>
<title>
<?=
$__template
->
yield
(
'title'
,
'
نادي النادي شيراتون'
)
?>
</title>
<meta
name=
"csrf-token"
content=
"
<?=
e
(
CSRF
::
token
()
)
?>
"
>
<title>
<?=
$__template
->
yield
(
'title'
,
'
لوحة التحكم'
)
?>
— نادي النادي شيراتون
</title>
<link
rel=
"stylesheet"
href=
"
<?=
url
(
'assets/css/main.css'
)
?>
"
>
<?=
$__template
->
yield
(
'styles'
,
''
)
?>
</head>
<body>
<aside
class=
"sidebar"
id=
"sidebar"
>
<?php
$__template
->
include
(
'Shared.Components.sidebar'
,
[
'app'
=>
$app
]);
?>
</aside>
<div
class=
"main-wrapper"
>
<header
class=
"topbar"
>
<?php
$__template
->
include
(
'Shared.Components.header'
,
[
'app'
=>
$app
]);
?>
</header>
<div
class=
"content-area"
>
<div
class=
"breadcrumb-area"
>
<?=
$__template
->
yield
(
'breadcrumbs'
,
''
)
?>
</div>
<div
class=
"page-header-row"
>
<h1
class=
"page-title"
>
<?=
$__template
->
yield
(
'title'
,
''
)
?>
</h1>
<div
class=
"page-actions"
>
<?=
$__template
->
yield
(
'page_actions'
,
''
)
?>
</div>
</div>
<!-- Sidebar -->
<aside
class=
"sidebar"
id=
"sidebar"
>
<?php
$__template
->
include
(
'Shared.Components.sidebar'
);
?>
</aside>
<?php
$__template
->
include
(
'Shared.Components.alerts'
,
[
'app'
=>
$app
]);
?>
<!-- Main Wrapper -->
<div
class=
"main-wrapper"
id=
"main-wrapper"
>
<main
class=
"main-content"
>
<?=
$__template
->
yield
(
'content'
,
''
)
?>
</main>
<!-- Top Header -->
<header
class=
"top-header"
>
<div
class=
"header-right"
>
<button
class=
"sidebar-toggle-btn"
onclick=
"toggleSidebar()"
>
☰
</button>
<div
class=
"header-title"
>
<h1>
<?=
$__template
->
yield
(
'title'
,
'لوحة التحكم'
)
?>
</h1>
</div>
</div>
<div
class=
"header-left"
>
<?=
$__template
->
yield
(
'page_actions'
,
''
)
?>
<div
class=
"header-user"
>
<span
class=
"header-user-name"
>
<?=
e
(
$employeeName
)
?>
</span>
<a
href=
"/logout"
class=
"header-logout"
title=
"تسجيل الخروج"
>
🚪
</a>
</div>
</div>
</header>
<!-- Alerts -->
<?php
$__template
->
include
(
'Shared.Components.alerts'
);
?>
<footer
class=
"footer"
>
<span>
نادي النادي شيراتون
©
<?=
date
(
'Y'
)
?>
</span>
<span>
الإصدار
<?=
e
(
$app
->
config
(
'app.version'
,
'1.0.0'
))
?>
</span>
<span>
<?=
arabic_date
(
today
())
?>
</span>
</footer>
</div>
<!-- Page Content -->
<main
class=
"page-content"
>
<?=
$__template
->
yield
(
'content'
,
''
)
?>
</main>
<div
id=
"modal-container"
></div>
<div
id=
"toast-container"
class=
"toast-container"
></div>
<!-- Footer -->
<footer
class=
"page-footer"
>
<span>
نادي النادي شيراتون
©
<?=
date
(
'Y'
)
?>
</span>
<span>
الإصدار 1.0.0
</span>
<span>
<?=
arabic_date
(
date
(
'Y-m-d'
))
?>
</span>
</footer>
</div>
<script
src=
"
<?=
url
(
'assets/js/app.js'
)
?>
"
></script>
<?=
$__template
->
yield
(
'scripts'
,
''
)
?>
<?=
$__template
->
stack
(
'scripts'
)
?>
<script
src=
"
<?=
url
(
'assets/js/app.js'
)
?>
"
></script>
<script>
function
toggleSidebar
()
{
var
sb
=
document
.
getElementById
(
'sidebar'
);
var
mw
=
document
.
getElementById
(
'main-wrapper'
);
sb
.
classList
.
toggle
(
'collapsed'
);
mw
.
classList
.
toggle
(
'sidebar-collapsed'
);
}
function
toggleSubmenu
(
el
)
{
var
parent
=
el
.
parentElement
;
var
submenu
=
parent
.
querySelector
(
'.sidebar-submenu'
);
if
(
submenu
)
{
var
isOpen
=
submenu
.
style
.
display
===
'block'
;
submenu
.
style
.
display
=
isOpen
?
'none'
:
'block'
;
parent
.
classList
.
toggle
(
'open'
,
!
isOpen
);
}
}
</script>
<?=
$__template
->
yield
(
'scripts'
,
''
)
?>
</body>
</html>
\ No newline at end of file
public/assets/css/main.css
View file @
56b6d45d
/* ════════════════════════════════════════════════════════════
THE CLUB ERP — Complete RTL-First Design System
════════════════════════════════════════════════════════════ */
/* ── CSS Custom Properties ── */
:root
{
--primary
:
#0D7377
;
--primary-light
:
#14A3A8
;
--primary-dark
:
#095355
;
--bg
:
#FFFFFF
;
--surface
:
#F5F7FA
;
--text
:
#1A1A2E
;
--text-secondary
:
#6B7280
;
--border
:
#E5E7EB
;
--success
:
#059669
;
--warning
:
#D97706
;
--error
:
#DC2626
;
--info
:
#0284C7
;
--shadow
:
0
1px
3px
rgba
(
0
,
0
,
0
,
0.1
);
--shadow-md
:
0
4px
6px
rgba
(
0
,
0
,
0
,
0.1
);
--radius
:
8px
;
--radius-sm
:
4px
;
--sidebar-w
:
260px
;
--topbar-h
:
56px
;
--font
:
'Cairo'
,
'Segoe UI'
,
Tahoma
,
sans-serif
;
--font-mono
:
'Courier New'
,
monospace
;
}
/* ── Reset & Base ── */
*,
*
::before
,
*
::after
{
box-sizing
:
border-box
;
margin
:
0
;
padding
:
0
;
}
html
{
font-size
:
14px
;
}
body
{
font-family
:
var
(
--font
);
color
:
var
(
--text
);
background
:
var
(
--surface
);
direction
:
rtl
;
line-height
:
1.6
;
min-height
:
100vh
;
}
a
{
color
:
var
(
--primary
);
text-decoration
:
none
;
}
a
:hover
{
color
:
var
(
--primary-dark
);
}
img
{
max-width
:
100%
;
}
h1
{
font-size
:
1.75rem
;
font-weight
:
700
;
}
h2
{
font-size
:
1.5rem
;
font-weight
:
700
;
}
h3
{
font-size
:
1.25rem
;
font-weight
:
600
;
}
h4
{
font-size
:
1.1rem
;
font-weight
:
600
;
}
h5
{
font-size
:
1rem
;
font-weight
:
600
;
}
h6
{
font-size
:
.875rem
;
font-weight
:
600
;
}
/* ═══════════════════════════════════════════
ADD THESE TO THE END OF YOUR main.css
IF SIDEBAR STYLES ARE MISSING
═══════════════════════════════════════════ */
/* ── Sidebar ── */
.sidebar
{
position
:
fixed
;
top
:
0
;
right
:
0
;
width
:
var
(
--sidebar-w
);
height
:
100vh
;
background
:
var
(
--primary-dark
);
color
:
#fff
;
overflow-y
:
auto
;
z-index
:
100
;
transition
:
transform
.3s
ease
;
}
.sidebar-header
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
padding
:
16px
;
border-bottom
:
1px
solid
rgba
(
255
,
255
,
255
,
.1
);
}
.sidebar-brand
{
font-size
:
1.2rem
;
font-weight
:
700
;
color
:
#fff
;
}
.sidebar-toggle
{
background
:
none
;
border
:
none
;
color
:
#fff
;
font-size
:
1.2rem
;
cursor
:
pointer
;
display
:
none
;
}
.sidebar-nav
{
padding
:
8px
0
;
}
.sidebar-menu
{
list-style
:
none
;
}
.sidebar-item
{
border-bottom
:
1px
solid
rgba
(
255
,
255
,
255
,
.05
);
}
.sidebar-link
{
display
:
flex
;
align-items
:
center
;
padding
:
10px
16px
;
color
:
rgba
(
255
,
255
,
255
,
.8
);
transition
:
all
.2s
;
gap
:
10px
;
}
.sidebar-link
:hover
,
.sidebar-link.active
{
background
:
rgba
(
255
,
255
,
255
,
.1
);
color
:
#fff
;
}
.sidebar-icon
{
width
:
24px
;
text-align
:
center
;
font-size
:
1rem
;
}
.sidebar-text
{
flex
:
1
;
}
.sidebar-arrow
{
font-size
:
.7rem
;
transition
:
transform
.2s
;
}
.sidebar-item.open
.sidebar-arrow
{
transform
:
rotate
(
-90deg
);
}
.sidebar-submenu
{
list-style
:
none
;
background
:
rgba
(
0
,
0
,
0
,
.15
);
}
.sidebar-sublink
{
display
:
block
;
padding
:
8px
40px
;
color
:
rgba
(
255
,
255
,
255
,
.7
);
font-size
:
.9rem
;
transition
:
all
.2s
;
}
.sidebar-sublink
:hover
,
.sidebar-sublink.active
{
color
:
#fff
;
background
:
rgba
(
255
,
255
,
255
,
.08
);
}
.sidebar
{
position
:
fixed
;
top
:
0
;
right
:
0
;
width
:
260px
;
height
:
100vh
;
background
:
#1A1A2E
;
color
:
#E5E7EB
;
overflow-y
:
auto
;
overflow-x
:
hidden
;
z-index
:
1000
;
transition
:
width
0.3s
ease
,
transform
0.3s
ease
;
display
:
flex
;
flex-direction
:
column
;
}
.sidebar.collapsed
{
width
:
0
;
transform
:
translateX
(
260px
);
}
.sidebar-header
{
padding
:
20px
15px
;
border-bottom
:
1px
solid
rgba
(
255
,
255
,
255
,
0.1
);
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
flex-shrink
:
0
;
}
.sidebar-brand
{
font-size
:
18px
;
font-weight
:
700
;
color
:
#14b8a6
;
letter-spacing
:
1px
;
}
.sidebar-toggle
{
background
:
none
;
border
:
none
;
color
:
#9CA3AF
;
font-size
:
18px
;
cursor
:
pointer
;
padding
:
5px
;
}
.sidebar-toggle
:hover
{
color
:
#fff
;
}
.sidebar-nav
{
flex
:
1
;
overflow-y
:
auto
;
padding
:
10px
0
;
}
.sidebar-menu
{
list-style
:
none
;
padding
:
0
;
margin
:
0
;
}
.sidebar-item
{
margin
:
2px
0
;
}
.sidebar-link
{
display
:
flex
;
align-items
:
center
;
gap
:
10px
;
padding
:
10px
20px
;
color
:
#D1D5DB
;
text-decoration
:
none
;
font-size
:
14px
;
font-weight
:
500
;
transition
:
all
0.2s
ease
;
border-radius
:
0
;
position
:
relative
;
}
.sidebar-link
:hover
{
background
:
rgba
(
255
,
255
,
255
,
0.08
);
color
:
#fff
;
}
.sidebar-link.active
{
background
:
rgba
(
13
,
115
,
119
,
0.3
);
color
:
#14b8a6
;
border-left
:
3px
solid
#14b8a6
;
}
.sidebar-icon
{
font-size
:
16px
;
width
:
24px
;
text-align
:
center
;
flex-shrink
:
0
;
}
.sidebar-text
{
flex
:
1
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
.sidebar-arrow
{
font-size
:
10px
;
transition
:
transform
0.2s
ease
;
color
:
#6B7280
;
}
.sidebar-item.open
>
.sidebar-link
.sidebar-arrow
{
transform
:
rotate
(
-90deg
);
}
.sidebar-submenu
{
list-style
:
none
;
padding
:
0
;
margin
:
0
;
background
:
rgba
(
0
,
0
,
0
,
0.15
);
}
.sidebar-sublink
{
display
:
block
;
padding
:
8px
20px
8px
45px
;
color
:
#9CA3AF
;
text-decoration
:
none
;
font-size
:
13px
;
transition
:
all
0.2s
ease
;
}
.sidebar-sublink
:hover
{
color
:
#fff
;
background
:
rgba
(
255
,
255
,
255
,
0.05
);
}
.sidebar-sublink.active
{
color
:
#14b8a6
;
font-weight
:
600
;
}
/* ── Main Wrapper ── */
.main-wrapper
{
margin-right
:
var
(
--sidebar-w
);
min-height
:
100vh
;
display
:
flex
;
flex-direction
:
column
;
}
/* ── Topbar ── */
.topbar
{
position
:
sticky
;
top
:
0
;
height
:
var
(
--topbar-h
);
background
:
var
(
--bg
);
border-bottom
:
1px
solid
var
(
--border
);
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
padding
:
0
20px
;
z-index
:
50
;
box-shadow
:
var
(
--shadow
);
}
.topbar-right
,
.topbar-left
{
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
}
.topbar-search-input
{
border
:
1px
solid
var
(
--border
);
border-radius
:
var
(
--radius-sm
);
padding
:
6px
12px
;
width
:
280px
;
font-family
:
var
(
--font
);
}
.topbar-btn
{
background
:
none
;
border
:
none
;
cursor
:
pointer
;
font-size
:
1rem
;
padding
:
4px
8px
;
border-radius
:
var
(
--radius-sm
);
color
:
var
(
--text-secondary
);
}
.topbar-btn
:hover
{
background
:
var
(
--surface
);
color
:
var
(
--text
);
}
.topbar-date
{
color
:
var
(
--text-secondary
);
font-size
:
.85rem
;
}
.topbar-branch
{
background
:
var
(
--primary
);
color
:
#fff
;
padding
:
2px
10px
;
border-radius
:
12px
;
font-size
:
.8rem
;
}
.topbar-username
{
font-weight
:
600
;
color
:
var
(
--text
);
}
.topbar-logout
{
color
:
var
(
--error
)
!important
;
font-weight
:
600
;
}
.notif-badge
{
background
:
var
(
--error
);
color
:
#fff
;
border-radius
:
50%
;
padding
:
0
5px
;
font-size
:
.7rem
;
position
:
relative
;
top
:
-8px
;
}
.sidebar-mobile-toggle
{
display
:
none
;
}
/* ── Content Area ── */
.content-area
{
flex
:
1
;
padding
:
20px
;
}
.page-header-row
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
margin-bottom
:
20px
;
}
.page-title
{
color
:
var
(
--text
);
margin
:
0
;
}
.page-actions
{
display
:
flex
;
gap
:
8px
;
}
.main-wrapper
{
margin-right
:
260px
;
min-height
:
100vh
;
display
:
flex
;
flex-direction
:
column
;
transition
:
margin-right
0.3s
ease
;
background
:
#F3F4F6
;
}
.main-wrapper.sidebar-collapsed
{
margin-right
:
0
;
}
/* ── Top Header ── */
.top-header
{
background
:
#fff
;
border-bottom
:
1px
solid
#E5E7EB
;
padding
:
12px
25px
;
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
position
:
sticky
;
top
:
0
;
z-index
:
100
;
box-shadow
:
0
1px
3px
rgba
(
0
,
0
,
0
,
0.05
);
}
.header-right
{
display
:
flex
;
align-items
:
center
;
gap
:
15px
;
}
.header-left
{
display
:
flex
;
align-items
:
center
;
gap
:
15px
;
}
.header-title
h1
{
margin
:
0
;
font-size
:
18px
;
font-weight
:
700
;
color
:
#1A1A2E
;
}
.header-user
{
display
:
flex
;
align-items
:
center
;
gap
:
10px
;
font-size
:
14px
;
color
:
#4B5563
;
}
.header-user-name
{
font-weight
:
600
;
}
.header-logout
{
text-decoration
:
none
;
font-size
:
18px
;
padding
:
4px
;
}
.sidebar-toggle-btn
{
background
:
none
;
border
:
1px
solid
#E5E7EB
;
border-radius
:
6px
;
padding
:
6px
10px
;
cursor
:
pointer
;
font-size
:
16px
;
color
:
#4B5563
;
display
:
none
;
}
/* ── Page Content ── */
.page-content
{
flex
:
1
;
padding
:
25px
;
}
/* ── Footer ── */
.footer
{
padding
:
12px
20px
;
background
:
var
(
--bg
);
border-top
:
1px
solid
var
(
--border
);
display
:
flex
;
justify-content
:
space-between
;
font-size
:
.8rem
;
color
:
var
(
--text-secondary
);
}
.page-footer
{
padding
:
15px
25px
;
background
:
#fff
;
border-top
:
1px
solid
#E5E7EB
;
display
:
flex
;
justify-content
:
space-between
;
font-size
:
12px
;
color
:
#9CA3AF
;
}
/* ── Cards ── */
.card
{
background
:
var
(
--bg
);
border-radius
:
var
(
--radius
);
box-shadow
:
var
(
--shadow
);
margin-bottom
:
20px
;
overflow
:
hidden
;
}
.card-header
{
padding
:
14px
20px
;
border-bottom
:
1px
solid
var
(
--border
);
font-weight
:
600
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
}
.card-body
{
padding
:
20px
;
}
.card-footer
{
padding
:
14px
20px
;
border-top
:
1px
solid
var
(
--border
);
display
:
flex
;
justify-content
:
flex-end
;
gap
:
8px
;
}
.card
{
background
:
#fff
;
border-radius
:
8px
;
border
:
1px
solid
#E5E7EB
;
box-shadow
:
0
1px
3px
rgba
(
0
,
0
,
0
,
0.04
);
margin-bottom
:
0
;
overflow
:
hidden
;
}
/* ── Tables ── */
.table-responsive
{
overflow-x
:
auto
;
}
.data-table
{
width
:
100%
;
border-collapse
:
collapse
;
}
.data-table
th
{
background
:
var
(
--primary-dark
);
color
:
#fff
;
padding
:
10px
14px
;
text-align
:
right
;
font-weight
:
600
;
white-space
:
nowrap
;
}
.data-table
td
{
padding
:
10px
14px
;
border-bottom
:
1px
solid
var
(
--border
);
}
.data-table
tbody
tr
:nth-child
(
even
)
{
background
:
#F9FAFB
;
}
.data-table
tbody
tr
:hover
{
background
:
#E6F4F4
;
}
.actions-col
{
width
:
120px
;
text-align
:
center
;
}
.action-buttons
{
display
:
flex
;
gap
:
4px
;
justify-content
:
center
;
}
.table-responsive
{
overflow-x
:
auto
;
}
/* ── Forms ── */
.form-group
{
margin-bottom
:
16px
;
}
.form-label
{
display
:
block
;
margin-bottom
:
4px
;
font-weight
:
600
;
color
:
var
(
--text
);
font-size
:
.9rem
;
}
.required-mark
{
color
:
var
(
--error
);
}
.form-input
,
.form-select
,
.form-textarea
{
width
:
100%
;
padding
:
8px
12px
;
border
:
1px
solid
var
(
--border
);
border-radius
:
var
(
--radius-sm
);
font-family
:
var
(
--font
);
font-size
:
.95rem
;
color
:
var
(
--text
);
background
:
var
(
--bg
);
transition
:
border-color
.2s
;
}
.form-input
:focus
,
.form-select
:focus
,
.form-textarea
:focus
{
outline
:
none
;
border-color
:
var
(
--primary
);
box-shadow
:
0
0
0
3px
rgba
(
13
,
115
,
119
,
.15
);
}
.form-textarea
{
min-height
:
100px
;
resize
:
vertical
;
}
.form-error
{
color
:
var
(
--error
);
font-size
:
.8rem
;
margin-top
:
2px
;
}
.form-help
{
color
:
var
(
--text-secondary
);
font-size
:
.8rem
;
margin-top
:
2px
;
}
.has-error
.form-input
,
.has-error
.form-select
{
border-color
:
var
(
--error
);
}
.form-row
{
display
:
grid
;
grid-template-columns
:
repeat
(
auto-fit
,
minmax
(
250px
,
1
fr
));
gap
:
16px
;
}
.checkbox-label
,
.radio-label
{
display
:
flex
;
align-items
:
center
;
gap
:
6px
;
cursor
:
pointer
;
}
.radio-group
{
display
:
flex
;
gap
:
16px
;
flex-wrap
:
wrap
;
}
.data-table
{
width
:
100%
;
border-collapse
:
collapse
;
font-size
:
14px
;
}
.data-table
thead
{
background
:
#0D7377
;
color
:
#fff
;
}
.data-table
th
{
padding
:
12px
15px
;
text-align
:
right
;
font-weight
:
600
;
white-space
:
nowrap
;
}
.data-table
td
{
padding
:
10px
15px
;
border-bottom
:
1px
solid
#F3F4F6
;
text-align
:
right
;
}
.data-table
tbody
tr
:hover
{
background
:
#F9FAFB
;
}
/* ── Buttons ── */
.btn
{
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
gap
:
6px
;
padding
:
8px
18px
;
border
:
1px
solid
transparent
;
border-radius
:
var
(
--radius-sm
);
font-family
:
var
(
--font
);
font-size
:
.9rem
;
font-weight
:
600
;
cursor
:
pointer
;
transition
:
all
.2s
;
text-decoration
:
none
;
}
.btn-primary
{
background
:
var
(
--primary
);
color
:
#fff
;
}
.btn-primary
:hover
{
background
:
var
(
--primary-dark
);
color
:
#fff
;
}
.btn-secondary
{
background
:
var
(
--surface
);
color
:
var
(
--text
);
border-color
:
var
(
--border
);
}
.btn-secondary
:hover
{
background
:
var
(
--border
);
}
.btn-danger
{
background
:
var
(
--error
);
color
:
#fff
;
}
.btn-danger
:hover
{
background
:
#B91C1C
;
color
:
#fff
;
}
.btn-success
{
background
:
var
(
--success
);
color
:
#fff
;
}
.btn-success
:hover
{
background
:
#047857
;
color
:
#fff
;
}
.btn-warning
{
background
:
var
(
--warning
);
color
:
#fff
;
}
.btn-outline
{
background
:
transparent
;
color
:
var
(
--primary
);
border-color
:
var
(
--primary
);
}
.btn-outline
:hover
{
background
:
var
(
--primary
);
color
:
#fff
;
}
.btn-sm
{
padding
:
4px
10px
;
font-size
:
.8rem
;
}
.btn-lg
{
padding
:
12px
28px
;
font-size
:
1rem
;
}
.btn
:disabled
{
opacity
:
.5
;
cursor
:
not-allowed
;
}
.btn
{
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
gap
:
6px
;
padding
:
8px
16px
;
border-radius
:
6px
;
font-size
:
14px
;
font-weight
:
600
;
text-decoration
:
none
;
cursor
:
pointer
;
border
:
1px
solid
transparent
;
transition
:
all
0.2s
ease
;
white-space
:
nowrap
;
font-family
:
inherit
;
}
.btn-primary
{
background
:
#0D7377
;
color
:
#fff
;
border-color
:
#0D7377
;
}
.btn-primary
:hover
{
background
:
#0a5c5f
;
}
.btn-outline
{
background
:
transparent
;
color
:
#0D7377
;
border-color
:
#D1D5DB
;
}
.btn-outline
:hover
{
background
:
#F3F4F6
;
border-color
:
#0D7377
;
}
.btn-sm
{
padding
:
4px
10px
;
font-size
:
12px
;
}
/* ── Forms ── */
.form-group
{
margin-bottom
:
0
;
}
.form-label
{
display
:
block
;
margin-bottom
:
5px
;
font-size
:
13px
;
font-weight
:
600
;
color
:
#374151
;
}
.form-input
,
.form-select
,
.form-textarea
{
width
:
100%
;
padding
:
8px
12px
;
border
:
1px
solid
#D1D5DB
;
border-radius
:
6px
;
font-size
:
14px
;
font-family
:
inherit
;
background
:
#fff
;
color
:
#1A1A2E
;
transition
:
border-color
0.2s
;
box-sizing
:
border-box
;
}
.form-input
:focus
,
.form-select
:focus
,
.form-textarea
:focus
{
outline
:
none
;
border-color
:
#0D7377
;
box-shadow
:
0
0
0
3px
rgba
(
13
,
115
,
119
,
0.1
);
}
.form-textarea
{
resize
:
vertical
;
min-height
:
80px
;
}
/* ── Alerts ── */
.alerts-wrapper
{
margin-bottom
:
16px
;
}
.alert
{
padding
:
12px
16px
;
border-radius
:
var
(
--radius-sm
);
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
margin-bottom
:
8px
;
animation
:
slideIn
.3s
ease
;
}
.alert-success
{
background
:
#ECFDF5
;
color
:
#065F46
;
border
:
1px
solid
#A7F3D0
;
}
.alert-error
{
background
:
#FEF2F2
;
color
:
#991B1B
;
border
:
1px
solid
#FECACA
;
}
.alert-warning
{
background
:
#FFFBEB
;
color
:
#92400E
;
border
:
1px
solid
#FDE68A
;
}
.alert-info
{
background
:
#EFF6FF
;
color
:
#1E40AF
;
border
:
1px
solid
#BFDBFE
;
}
.alert-close
{
background
:
none
;
border
:
none
;
cursor
:
pointer
;
font-size
:
1rem
;
opacity
:
.6
;
}
.alert-close
:hover
{
opacity
:
1
;
}
/* ── Badges ── */
.badge
{
display
:
inline-block
;
padding
:
2px
8px
;
border-radius
:
12px
;
font-size
:
.75rem
;
font-weight
:
600
;
}
.badge-primary
{
background
:
#E0F2F1
;
color
:
var
(
--primary-dark
);
}
.badge-success
{
background
:
#ECFDF5
;
color
:
var
(
--success
);
}
.badge-danger
{
background
:
#FEF2F2
;
color
:
var
(
--error
);
}
.badge-warning
{
background
:
#FFFBEB
;
color
:
var
(
--warning
);
}
.badge-info
{
background
:
#EFF6FF
;
color
:
var
(
--info
);
}
/* ── Modals ── */
.modal-overlay
{
position
:
fixed
;
inset
:
0
;
background
:
rgba
(
0
,
0
,
0
,
.5
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
z-index
:
200
;
animation
:
fadeIn
.2s
;
}
.modal
{
background
:
var
(
--bg
);
border-radius
:
var
(
--radius
);
width
:
90%
;
max-height
:
90vh
;
overflow
:
hidden
;
display
:
flex
;
flex-direction
:
column
;
}
.modal-small
{
max-width
:
400px
;
}
.modal-medium
{
max-width
:
600px
;
}
.modal-large
{
max-width
:
900px
;
}
.modal-fullscreen
{
max-width
:
95vw
;
max-height
:
95vh
;
}
.modal-header
{
padding
:
14px
20px
;
background
:
var
(
--primary-dark
);
color
:
#fff
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
}
.modal-title
{
font-weight
:
600
;
}
.modal-close
{
background
:
none
;
border
:
none
;
color
:
#fff
;
font-size
:
1.2rem
;
cursor
:
pointer
;
}
.modal-body
{
padding
:
20px
;
overflow-y
:
auto
;
flex
:
1
;
}
.modal-footer
{
padding
:
14px
20px
;
border-top
:
1px
solid
var
(
--border
);
display
:
flex
;
justify-content
:
flex-end
;
gap
:
8px
;
}
/* ── Toast Notifications ── */
.toast-container
{
position
:
fixed
;
bottom
:
20px
;
left
:
20px
;
z-index
:
300
;
display
:
flex
;
flex-direction
:
column-reverse
;
gap
:
8px
;
}
.toast
{
padding
:
12px
18px
;
border-radius
:
var
(
--radius-sm
);
color
:
#fff
;
font-weight
:
500
;
min-width
:
280px
;
box-shadow
:
var
(
--shadow-md
);
animation
:
slideUp
.3s
ease
;
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
}
.toast-success
{
background
:
var
(
--success
);
}
.toast-error
{
background
:
var
(
--error
);
}
.toast-warning
{
background
:
var
(
--warning
);
}
.toast-info
{
background
:
var
(
--info
);
}
.toast-close
{
background
:
none
;
border
:
none
;
color
:
#fff
;
cursor
:
pointer
;
margin-right
:
10px
;
}
/* ── Breadcrumbs ── */
.breadcrumbs
{
display
:
flex
;
align-items
:
center
;
gap
:
4px
;
margin-bottom
:
10px
;
font-size
:
.85rem
;
flex-wrap
:
wrap
;
}
.breadcrumb-item
{
color
:
var
(
--text-secondary
);
}
.breadcrumb-item.current
{
color
:
var
(
--text
);
font-weight
:
600
;
}
.breadcrumb-separator
{
color
:
var
(
--border
);
}
/* ── Pagination ── */
.pagination-wrapper
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
margin-top
:
16px
;
flex-wrap
:
wrap
;
gap
:
12px
;
}
.pagination-info
{
color
:
var
(
--text-secondary
);
font-size
:
.85rem
;
}
.pagination
{
display
:
flex
;
list-style
:
none
;
gap
:
4px
;
}
.page-link
{
display
:
inline-block
;
padding
:
6px
12px
;
border
:
1px
solid
var
(
--border
);
border-radius
:
var
(
--radius-sm
);
color
:
var
(
--text
);
font-size
:
.85rem
;
}
.page-link
:hover
{
background
:
var
(
--surface
);
color
:
var
(
--text
);
}
.page-link.active
{
background
:
var
(
--primary
);
color
:
#fff
;
border-color
:
var
(
--primary
);
}
.page-ellipsis
{
padding
:
6px
8px
;
color
:
var
(
--text-secondary
);
}
/* ── Stats Cards ── */
.stats-card
{
background
:
var
(
--bg
);
border-radius
:
var
(
--radius
);
box-shadow
:
var
(
--shadow
);
padding
:
16px
;
display
:
flex
;
align-items
:
center
;
gap
:
16px
;
position
:
relative
;
overflow
:
hidden
;
}
.stats-card
::after
{
content
:
''
;
position
:
absolute
;
top
:
0
;
right
:
0
;
width
:
4px
;
height
:
100%
;
}
.stats-card-primary
::after
{
background
:
var
(
--primary
);
}
.stats-card-success
::after
{
background
:
var
(
--success
);
}
.stats-card-danger
::after
{
background
:
var
(
--error
);
}
.stats-card-warning
::after
{
background
:
var
(
--warning
);
}
.stats-card-icon
{
font-size
:
2rem
;
}
.stats-card-title
{
color
:
var
(
--text-secondary
);
font-size
:
.85rem
;
}
.stats-card-value
{
font-size
:
1.5rem
;
font-weight
:
700
;
color
:
var
(
--text
);
}
.stats-card-change
{
font-size
:
.8rem
;
color
:
var
(
--success
);
}
.stats-card-link
{
position
:
absolute
;
bottom
:
8px
;
left
:
16px
;
font-size
:
.8rem
;
}
/* ── Empty State ── */
.empty-state
{
text-align
:
center
;
padding
:
40px
;
}
.empty-state-icon
{
font-size
:
3rem
;
margin-bottom
:
12px
;
}
.empty-state-message
{
color
:
var
(
--text-secondary
);
font-size
:
1rem
;
margin-bottom
:
16px
;
}
/* ── Tabs ── */
.tabs
{
display
:
flex
;
border-bottom
:
2px
solid
var
(
--border
);
margin-bottom
:
16px
;
gap
:
0
;
overflow-x
:
auto
;
}
.tab-link
{
padding
:
10px
20px
;
color
:
var
(
--text-secondary
);
font-weight
:
600
;
border-bottom
:
2px
solid
transparent
;
margin-bottom
:
-2px
;
cursor
:
pointer
;
white-space
:
nowrap
;
transition
:
all
.2s
;
background
:
none
;
border-top
:
none
;
border-left
:
none
;
border-right
:
none
;
font-family
:
var
(
--font
);
}
.tab-link
:hover
{
color
:
var
(
--primary
);
}
.tab-link.active
{
color
:
var
(
--primary
);
border-bottom-color
:
var
(
--primary
);
}
.tab-content
{
display
:
none
;
}
.tab-content.active
{
display
:
block
;
}
/* ── Status Dots ── */
.status-dot
{
width
:
8px
;
height
:
8px
;
border-radius
:
50%
;
display
:
inline-block
;
margin-left
:
6px
;
}
.status-dot-success
{
background
:
var
(
--success
);
}
.status-dot-warning
{
background
:
var
(
--warning
);
}
.status-dot-danger
{
background
:
var
(
--error
);
}
.status-dot-info
{
background
:
var
(
--info
);
}
/* ── Loading Spinner ── */
.spinner
{
display
:
inline-block
;
width
:
24px
;
height
:
24px
;
border
:
3px
solid
var
(
--border
);
border-top-color
:
var
(
--primary
);
border-radius
:
50%
;
animation
:
spin
.7s
linear
infinite
;
}
@keyframes
spin
{
to
{
transform
:
rotate
(
360deg
);
}
}
/* ── Utilities ── */
.d-none
{
display
:
none
!important
;
}
.d-block
{
display
:
block
!important
;
}
.d-flex
{
display
:
flex
!important
;
}
.d-grid
{
display
:
grid
!important
;
}
.text-right
{
text-align
:
right
!important
;
}
.text-left
{
text-align
:
left
!important
;
}
.text-center
{
text-align
:
center
!important
;
}
.text-bold
{
font-weight
:
700
!important
;
}
.text-muted
{
color
:
var
(
--text-secondary
)
!important
;
}
.text-success
{
color
:
var
(
--success
)
!important
;
}
.text-danger
{
color
:
var
(
--error
)
!important
;
}
.text-warning
{
color
:
var
(
--warning
)
!important
;
}
.text-primary
{
color
:
var
(
--primary
)
!important
;
}
.m-0
{
margin
:
0
!important
;
}
.m-1
{
margin
:
4px
!important
;
}
.m-2
{
margin
:
8px
!important
;
}
.m-3
{
margin
:
16px
!important
;
}
.m-4
{
margin
:
24px
!important
;
}
.m-5
{
margin
:
32px
!important
;
}
.mb-0
{
margin-bottom
:
0
!important
;
}
.mb-1
{
margin-bottom
:
4px
!important
;
}
.mb-2
{
margin-bottom
:
8px
!important
;
}
.mb-3
{
margin-bottom
:
16px
!important
;
}
.mb-4
{
margin-bottom
:
24px
!important
;
}
.mt-0
{
margin-top
:
0
!important
;
}
.mt-2
{
margin-top
:
8px
!important
;
}
.mt-3
{
margin-top
:
16px
!important
;
}
.p-0
{
padding
:
0
!important
;
}
.p-1
{
padding
:
4px
!important
;
}
.p-2
{
padding
:
8px
!important
;
}
.p-3
{
padding
:
16px
!important
;
}
.p-4
{
padding
:
24px
!important
;
}
.p-5
{
padding
:
32px
!important
;
}
.gap-1
{
gap
:
4px
;
}
.gap-2
{
gap
:
8px
;
}
.gap-3
{
gap
:
16px
;
}
.gap-4
{
gap
:
24px
;
}
.flex-wrap
{
flex-wrap
:
wrap
;
}
.items-center
{
align-items
:
center
;
}
.justify-between
{
justify-content
:
space-between
;
}
.justify-end
{
justify-content
:
flex-end
;
}
.grid-2
{
grid-template-columns
:
repeat
(
2
,
1
fr
);
}
.grid-3
{
grid-template-columns
:
repeat
(
3
,
1
fr
);
}
.grid-4
{
grid-template-columns
:
repeat
(
4
,
1
fr
);
}
.w-full
{
width
:
100%
;
}
/* ── Animations ── */
@keyframes
fadeIn
{
from
{
opacity
:
0
;
}
to
{
opacity
:
1
;
}
}
@keyframes
slideIn
{
from
{
transform
:
translateY
(
-10px
);
opacity
:
0
;
}
to
{
transform
:
translateY
(
0
);
opacity
:
1
;
}
}
@keyframes
slideUp
{
from
{
transform
:
translateY
(
20px
);
opacity
:
0
;
}
to
{
transform
:
translateY
(
0
);
opacity
:
1
;
}
}
.alert
{
padding
:
12px
20px
;
border-radius
:
8px
;
margin
:
0
25px
15px
;
font-size
:
14px
;
font-weight
:
500
;
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
}
.alert-success
{
background
:
#F0FDF4
;
color
:
#059669
;
border
:
1px
solid
#BBF7D0
;
}
.alert-error
{
background
:
#FEF2F2
;
color
:
#DC2626
;
border
:
1px
solid
#FECACA
;
}
.alert-warning
{
background
:
#FFF7ED
;
color
:
#D97706
;
border
:
1px
solid
#FED7AA
;
}
.alert-info
{
background
:
#EFF6FF
;
color
:
#0284C7
;
border
:
1px
solid
#BFDBFE
;
}
.alert-close
{
background
:
none
;
border
:
none
;
cursor
:
pointer
;
font-size
:
18px
;
color
:
inherit
;
opacity
:
0.6
;
padding
:
0
5px
;
}
.alert-close
:hover
{
opacity
:
1
;
}
/* ── Responsive ── */
@media
(
max-width
:
1024px
)
{
.sidebar
{
transform
:
translateX
(
260px
);
width
:
260px
;
}
.sidebar.show
{
transform
:
translateX
(
0
);
}
.main-wrapper
{
margin-right
:
0
;
}
.sidebar-toggle-btn
{
display
:
block
;
}
}
/* ── Print ── */
@media
print
{
.sidebar
,
.topbar
,
.footer
,
.page-actions
,
.btn
,
.alerts-wrapper
,
.pagination-wrapper
,
.toast-container
{
display
:
none
!important
;
}
.main-wrapper
{
margin-right
:
0
!important
;
}
.content-area
{
padding
:
0
!important
;
}
body
{
background
:
#fff
;
}
.sidebar
,
.top-header
,
.page-footer
,
.btn
,
.sidebar-toggle-btn
{
display
:
none
!important
;
}
.main-wrapper
{
margin-right
:
0
!important
;
}
.page-content
{
padding
:
0
!important
;
}
}
.d-print-none
{
}
.d-print-block
{
display
:
none
;
}
@media
print
{
.d-print-none
{
display
:
none
!important
;
}
.d-print-block
{
display
:
block
!important
;
}
}
/* ── Responsive ── */
@media
(
max-width
:
1279px
)
{
.sidebar
{
transform
:
translateX
(
100%
);
}
.sidebar.open
{
transform
:
translateX
(
0
);
}
.main-wrapper
{
margin-right
:
0
;
}
.sidebar-toggle
,
.sidebar-mobile-toggle
{
display
:
block
;
}
}
@media
(
max-width
:
767px
)
{
.topbar-search-input
{
width
:
150px
;
}
.form-row
{
grid-template-columns
:
1
fr
;
}
.grid-2
,
.grid-3
,
.grid-4
{
grid-template-columns
:
1
fr
;
}
.footer
{
flex-direction
:
column
;
gap
:
4px
;
text-align
:
center
;
}
/* ── Body Reset ── */
*
{
box-sizing
:
border-box
;
}
body
{
margin
:
0
;
padding
:
0
;
font-family
:
'Cairo'
,
'Segoe UI'
,
Tahoma
,
Arial
,
sans-serif
;
font-size
:
14px
;
color
:
#1A1A2E
;
background
:
#F3F4F6
;
direction
:
rtl
;
line-height
:
1.6
;
}
a
{
color
:
#0D7377
;
text-decoration
:
none
;
}
a
:hover
{
text-decoration
:
underline
;
}
code
{
background
:
#F3F4F6
;
padding
:
2px
6px
;
border-radius
:
4px
;
font-size
:
12px
;
font-family
:
'Courier New'
,
monospace
;
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment