Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
P
phphr
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
phphr
Commits
223727b5
Commit
223727b5
authored
Apr 08, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 5 files via Son of Anton
parent
7b9f7368
Pipeline
#24
canceled with stage
Changes
5
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
1232 additions
and
316 deletions
+1232
-316
TemplateEngine.php
engine/Template/TemplateEngine.php
+63
-83
app.css
public/assets/css/app.css
+974
-170
admin.php
templates/dashboard/admin.php
+72
-25
super_admin.php
templates/dashboard/super_admin.php
+119
-34
auth.php
templates/layouts/auth.php
+4
-4
No files found.
engine/Template/TemplateEngine.php
View file @
223727b5
...
...
@@ -3,113 +3,93 @@ declare(strict_types=1);
namespace
Engine\Template
;
/**
* Simple PHP template engine with layout support.
*
* Usage:
* $engine->render('dashboard/super_admin', ['user' => $user]);
*
* Templates can specify layout at the top:
* (no explicit call needed — all templates use app layout by default for logged-in users)
*/
final
class
TemplateEngine
{
private
string
$templateDir
;
private
string
$cacheDir
;
private
array
$globals
=
[];
private
array
$sections
=
[];
private
array
$sectionStack
=
[];
private
?
string
$layout
=
null
;
private
string
$childContent
=
''
;
private
string
$basePath
;
private
string
$cachePath
;
private
string
$defaultLayout
=
'layouts/app'
;
public
function
__construct
(
string
$
templateDir
,
string
$cacheDir
)
public
function
__construct
(
string
$
basePath
=
''
,
string
$cachePath
=
''
)
{
$this
->
templateDir
=
rtrim
(
$templateDir
,
'/'
);
$this
->
cacheDir
=
rtrim
(
$cacheDir
,
'/'
);
if
(
!
is_dir
(
$this
->
cacheDir
))
{
mkdir
(
$this
->
cacheDir
,
0755
,
true
);
}
$this
->
basePath
=
$basePath
?:
(
defined
(
'ROOT_PATH'
)
?
ROOT_PATH
.
'/templates'
:
__DIR__
.
'/../../templates'
);
$this
->
cachePath
=
$cachePath
?:
(
defined
(
'ROOT_PATH'
)
?
ROOT_PATH
.
'/storage/cache/templates'
:
'/tmp'
);
}
public
function
addGlobal
(
string
$key
,
mixed
$value
)
:
void
/**
* Render a template with data and wrap in layout.
*/
public
function
render
(
string
$template
,
array
$data
=
[],
?
string
$layout
=
null
)
:
string
{
$this
->
globals
[
$key
]
=
$value
;
}
$templateFile
=
$this
->
basePath
.
'/'
.
$template
.
'.php'
;
public
function
render
(
string
$template
,
array
$data
=
[])
:
string
{
$this
->
sections
=
[];
$this
->
layout
=
null
;
if
(
!
file_exists
(
$templateFile
))
{
throw
new
\RuntimeException
(
"Template not found:
{
$template
}
(looked in
{
$templateFile
}
)"
);
}
// Render the child template first
$content
=
$this
->
renderFile
(
$templateFile
,
$data
);
// Determine layout
$layoutName
=
$layout
;
if
(
$layoutName
===
null
)
{
// Auto-detect: use 'auth' layout for auth pages, 'app' for everything else
if
(
str_starts_with
(
$template
,
'auth/'
)
||
str_starts_with
(
$template
,
'errors/'
))
{
$layoutName
=
'layouts/auth'
;
}
else
{
$layoutName
=
$this
->
defaultLayout
;
}
}
$content
=
$this
->
renderFile
(
$template
,
$data
);
if
(
$layoutName
===
'none'
||
$layoutName
===
false
||
$layoutName
===
''
)
{
return
$content
;
}
if
(
$this
->
layout
)
{
$layoutTemplate
=
$this
->
layout
;
$this
->
childContent
=
$content
;
$this
->
layout
=
null
;
$content
=
$this
->
renderFile
(
$layoutTemplate
,
$data
);
$layoutFile
=
$this
->
basePath
.
'/'
.
$layoutName
.
'.php'
;
if
(
!
file_exists
(
$layoutFile
))
{
// No layout file? Just return the content directly.
return
$content
;
}
return
$content
;
// Render layout with content injected
$layoutData
=
array_merge
(
$data
,
[
'content'
=>
$content
]);
return
$this
->
renderFile
(
$layoutFile
,
$layoutData
);
}
private
function
renderFile
(
string
$template
,
array
$data
)
:
string
/**
* Render a partial (no layout wrapping).
*/
public
function
partial
(
string
$template
,
array
$data
=
[])
:
string
{
$file
=
$this
->
templateDir
.
'/'
.
str_replace
(
'.'
,
'/'
,
$template
)
.
'.php'
;
$file
=
$this
->
basePath
.
'/'
.
$template
.
'.php'
;
if
(
!
file_exists
(
$file
))
{
throw
new
\RuntimeException
(
"Template not found:
{
$file
}
"
)
;
return
"<!-- partial not found:
{
$template
}
-->"
;
}
return
$this
->
renderFile
(
$file
,
$data
);
}
extract
(
array_merge
(
$this
->
globals
,
$data
));
$__engine
=
$this
;
/**
* Render a PHP file with extracted data and return output as string.
*/
private
function
renderFile
(
string
$file
,
array
$data
)
:
string
{
extract
(
$data
,
EXTR_SKIP
);
ob_start
();
try
{
includ
e
$file
;
requir
e
$file
;
}
catch
(
\Throwable
$e
)
{
ob_end_clean
();
throw
$e
;
throw
new
\RuntimeException
(
"Template render error in
{
$file
}
: "
.
$e
->
getMessage
(),
0
,
$e
)
;
}
return
ob_get_clean
();
}
public
function
extend
(
string
$layout
)
:
void
{
$this
->
layout
=
$layout
;
}
public
function
section
(
string
$name
)
:
void
{
$this
->
sectionStack
[]
=
$name
;
ob_start
();
}
public
function
endSection
()
:
void
{
$name
=
array_pop
(
$this
->
sectionStack
);
$this
->
sections
[
$name
]
=
ob_get_clean
();
}
public
function
yield
(
string
$name
,
string
$default
=
''
)
:
string
{
return
$this
->
sections
[
$name
]
??
$default
;
}
public
function
content
()
:
string
{
return
$this
->
childContent
;
}
public
function
partial
(
string
$template
,
array
$data
=
[])
:
string
{
return
$this
->
renderFile
(
$template
,
$data
);
}
public
function
e
(
mixed
$value
)
:
string
{
return
htmlspecialchars
((
string
)
$value
,
ENT_QUOTES
,
'UTF-8'
);
}
public
function
csrf
()
:
string
{
$token
=
$_SESSION
[
'csrf_token'
]
??
(
$_COOKIE
[
'csrf_token'
]
??
''
);
return
'<input type="hidden" name="_csrf_token" value="'
.
$this
->
e
(
$token
)
.
'">'
;
}
public
function
csrfMeta
()
:
string
{
$token
=
$_SESSION
[
'csrf_token'
]
??
(
$_COOKIE
[
'csrf_token'
]
??
''
);
return
'<meta name="csrf-token" content="'
.
$this
->
e
(
$token
)
.
'">'
;
}
}
\ No newline at end of file
public/assets/css/app.css
View file @
223727b5
/* ======================================================
AL-ARCADE HR PLATFORM v3.0 — "THE GRIND"
Core Stylesheet — Phase 1
====================================================== */
/* ============================================================================
* AL-ARCADE HR PLATFORM v3.0 — "THE GRIND"
* Complete Application Stylesheet
* ============================================================================ */
/* ─── RESET & BASE ─── */
*,
*
::before
,
*
::after
{
box-sizing
:
border-box
;
margin
:
0
;
padding
:
0
;
}
:root
{
--primary
:
#6366F1
;
--primary-dark
:
#4F46E5
;
--success
:
#22C55E
;
--warning
:
#EAB308
;
--danger
:
#EF4444
;
--gold
:
#F59E0B
;
--bg
:
#F8FAFC
;
--bg-card
:
#FFFFFF
;
--text
:
#1E293B
;
--text-muted
:
#64748B
;
--border
:
#E2E8F0
;
--shadow
:
0
1px
3px
rgba
(
0
,
0
,
0
,
0.1
);
/* Colors */
--bg-primary
:
#0f1117
;
--bg-secondary
:
#1a1d27
;
--bg-tertiary
:
#242836
;
--bg-card
:
#1e2230
;
--bg-hover
:
#2a2f3f
;
--bg-input
:
#1a1d27
;
--text-primary
:
#e8eaed
;
--text-secondary
:
#9aa0a6
;
--text-muted
:
#5f6368
;
--text-inverse
:
#0f1117
;
--border-color
:
#2d3348
;
--border-light
:
#3d4460
;
--accent-primary
:
#6366f1
;
--accent-primary-hover
:
#818cf8
;
--accent-primary-bg
:
rgba
(
99
,
102
,
241
,
0.12
);
--success
:
#22c55e
;
--success-bg
:
rgba
(
34
,
197
,
94
,
0.12
);
--warning
:
#f59e0b
;
--warning-bg
:
rgba
(
245
,
158
,
11
,
0.12
);
--danger
:
#ef4444
;
--danger-bg
:
rgba
(
239
,
68
,
68
,
0.12
);
--info
:
#3b82f6
;
--info-bg
:
rgba
(
59
,
130
,
246
,
0.12
);
--bounty
:
#f59e0b
;
--bounty-bg
:
rgba
(
245
,
158
,
11
,
0.12
);
/* Spacing */
--sidebar-width
:
240px
;
--nav-height
:
60px
;
--radius
:
8px
;
--font
:
'Segoe UI'
,
system-ui
,
-apple-system
,
sans-serif
;
}
[
data-theme
=
"dark"
]
{
--bg
:
#0F172A
;
--bg-card
:
#1E293B
;
--text
:
#E2E8F0
;
--text-muted
:
#94A3B8
;
--border
:
#334155
;
--shadow
:
0
1px
3px
rgba
(
0
,
0
,
0
,
0.3
);
}
*
{
margin
:
0
;
padding
:
0
;
box-sizing
:
border-box
;
}
body
{
font-family
:
var
(
--font
);
background
:
var
(
--bg
);
color
:
var
(
--text
);
line-height
:
1.6
;
}
/* Navigation */
.top-nav
{
display
:
flex
;
align-items
:
center
;
gap
:
16px
;
padding
:
8px
24px
;
background
:
var
(
--bg-card
);
border-bottom
:
1px
solid
var
(
--border
);
box-shadow
:
var
(
--shadow
);
position
:
sticky
;
top
:
0
;
z-index
:
100
;
}
.nav-brand
a
{
text-decoration
:
none
;
font-size
:
1.2em
;
font-weight
:
700
;
color
:
var
(
--primary
);
}
.nav-search
{
flex
:
1
;
max-width
:
400px
;
}
.nav-search
input
{
width
:
100%
;
padding
:
8px
12px
;
border
:
1px
solid
var
(
--border
);
border-radius
:
var
(
--radius
);
background
:
var
(
--bg
);
color
:
var
(
--text
);
font-size
:
0.9em
;
}
.nav-actions
{
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
margin-left
:
auto
;
}
.nav-btn
{
background
:
none
;
border
:
none
;
cursor
:
pointer
;
font-size
:
1.1em
;
padding
:
6px
;
border-radius
:
var
(
--radius
);
color
:
var
(
--text
);
position
:
relative
;
}
.nav-btn
:hover
{
background
:
var
(
--bg
);
}
.nav-user
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
end
;
}
.nav-user
span
:first-child
{
font-weight
:
600
;
font-size
:
0.9em
;
}
.role-badge
{
font-size
:
0.7em
;
color
:
var
(
--text-muted
);
text-transform
:
uppercase
;
letter-spacing
:
0.5px
;
}
.badge
{
position
:
absolute
;
top
:
-2px
;
right
:
-2px
;
background
:
var
(
--danger
);
color
:
white
;
border-radius
:
50%
;
width
:
18px
;
height
:
18px
;
font-size
:
0.65em
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
/* HUD */
.hud
{
padding
:
12px
24px
;
background
:
var
(
--bg-card
);
border-bottom
:
2px
solid
var
(
--border
);
}
.hud-primary
{
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
flex-wrap
:
wrap
;
}
.hud-month
{
font-weight
:
700
;
}
.hud-bar-container
{
flex
:
1
;
min-width
:
200px
;
height
:
24px
;
background
:
var
(
--border
);
border-radius
:
12px
;
overflow
:
hidden
;
}
.hud-bar
{
height
:
100%
;
border-radius
:
12px
;
transition
:
width
0.5s
ease
,
background
0.5s
ease
;
}
.hud-exceptional
.hud-bar
,
[
data-color
=
"hud-exceptional"
]
.hud-bar
{
background
:
linear-gradient
(
90deg
,
var
(
--gold
),
#FBBF24
);
}
.hud-healthy
.hud-bar
,
[
data-color
=
"hud-healthy"
]
.hud-bar
{
background
:
var
(
--success
);
}
.hud-warning
.hud-bar
,
[
data-color
=
"hud-warning"
]
.hud-bar
{
background
:
var
(
--warning
);
}
.hud-critical
.hud-bar
,
[
data-color
=
"hud-critical"
]
.hud-bar
{
background
:
var
(
--danger
);
}
.hud-amount
{
font-weight
:
700
;
font-size
:
1.1em
;
white-space
:
nowrap
;
}
.hud-secondary
{
display
:
flex
;
gap
:
16px
;
margin-top
:
4px
;
font-size
:
0.85em
;
color
:
var
(
--text-muted
);
}
.hud-deductions
{
color
:
var
(
--danger
);
}
.hud-bounties
{
color
:
var
(
--success
);
}
/* Main Content */
.main-content
{
padding
:
24px
;
max-width
:
1400px
;
margin
:
0
auto
;
}
.container
{
max-width
:
1200px
;
margin
:
0
auto
;
}
/* Cards */
.card
{
background
:
var
(
--bg-card
);
border-radius
:
var
(
--radius
);
border
:
1px
solid
var
(
--border
);
padding
:
20px
;
box-shadow
:
var
(
--shadow
);
}
.card
h3
{
margin-bottom
:
12px
;
font-size
:
1em
;
color
:
var
(
--text-muted
);
}
.card-wide
{
grid-column
:
span
2
;
}
/* Dashboard Grid */
.dashboard-grid
{
display
:
grid
;
grid-template-columns
:
repeat
(
auto-fill
,
minmax
(
350px
,
1
fr
));
gap
:
20px
;
margin-top
:
20px
;
}
.stat-card
{
text-align
:
center
;
}
.stat-number
{
font-size
:
2.5em
;
font-weight
:
800
;
color
:
var
(
--primary
);
}
.stat-sub
{
font-size
:
0.85em
;
color
:
var
(
--text-muted
);
}
/* Forms */
.form-group
{
margin-bottom
:
16px
;
}
.form-group
label
{
display
:
block
;
font-weight
:
600
;
margin-bottom
:
4px
;
font-size
:
0.9em
;
}
.form-group
input
,
.form-group
select
,
.form-group
textarea
{
width
:
100%
;
padding
:
10px
12px
;
border
:
1px
solid
var
(
--border
);
border-radius
:
var
(
--radius
);
background
:
var
(
--bg
);
color
:
var
(
--text
);
font-size
:
0.95em
;
}
.form-group
textarea
{
min-height
:
100px
;
resize
:
vertical
;
}
/* Buttons */
.btn
{
display
:
inline-block
;
padding
:
10px
20px
;
border-radius
:
var
(
--radius
);
font-weight
:
600
;
text-decoration
:
none
;
cursor
:
pointer
;
border
:
none
;
font-size
:
0.9em
;
transition
:
all
0.2s
;
}
.btn-primary
{
background
:
var
(
--primary
);
color
:
white
;
}
.btn-primary
:hover
{
background
:
var
(
--primary-dark
);
}
.btn-secondary
{
background
:
var
(
--border
);
color
:
var
(
--text
);
}
.btn-danger
{
background
:
var
(
--danger
);
color
:
white
;
}
.btn-success
{
background
:
var
(
--success
);
color
:
white
;
}
.btn-sm
{
padding
:
6px
12px
;
font-size
:
0.8em
;
}
.btn-lg
{
padding
:
14px
28px
;
font-size
:
1.1em
;
}
.btn-block
{
display
:
block
;
width
:
100%
;
text-align
:
center
;
}
.btn-link
{
background
:
none
;
color
:
var
(
--primary
);
padding
:
0
;
}
/* Alerts */
.alert
{
padding
:
12px
16px
;
border-radius
:
var
(
--radius
);
margin-bottom
:
16px
;
font-size
:
0.9em
;
}
.alert-error
{
background
:
#FEF2F2
;
color
:
#991B1B
;
border
:
1px
solid
#FECACA
;
}
.alert-success
{
background
:
#F0FDF4
;
color
:
#166534
;
border
:
1px
solid
#BBF7D0
;
}
.alert-warning
{
background
:
#FFFBEB
;
color
:
#92400E
;
border
:
1px
solid
#FDE68A
;
}
/* Auth Page */
.auth-page
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
min-height
:
100vh
;
}
.auth-container
{
width
:
100%
;
max-width
:
420px
;
padding
:
20px
;
}
.auth-logo
{
text-align
:
center
;
font-size
:
2em
;
font-weight
:
800
;
margin-bottom
:
30px
;
color
:
var
(
--primary
);
}
.auth-form
{
background
:
var
(
--bg-card
);
padding
:
30px
;
border-radius
:
var
(
--radius
);
box-shadow
:
var
(
--shadow
);
border
:
1px
solid
var
(
--border
);
}
.auth-form
h2
{
text-align
:
center
;
margin-bottom
:
24px
;
}
/* Blocking Notification */
.blocking-page
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
min-height
:
100vh
;
background
:
rgba
(
0
,
0
,
0
,
0.85
);
}
.blocking-overlay
{
width
:
100%
;
max-width
:
600px
;
padding
:
20px
;
}
.blocking-card
{
background
:
var
(
--bg-card
);
padding
:
40px
;
border-radius
:
var
(
--radius
);
text-align
:
center
;
}
.blocking-icon
{
font-size
:
3em
;
margin-bottom
:
16px
;
}
.blocking-content
{
text-align
:
left
;
margin
:
20px
0
;
padding
:
16px
;
background
:
var
(
--bg
);
border-radius
:
var
(
--radius
);
}
.blocking-note
{
font-size
:
0.85em
;
color
:
var
(
--text-muted
);
margin
:
16px
0
;
font-style
:
italic
;
}
/* Notifications */
.notification-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
8px
;
}
.notification-item
{
padding
:
16px
;
background
:
var
(
--bg-card
);
border
:
1px
solid
var
(
--border
);
border-radius
:
var
(
--radius
);
}
.notification-item.unread
{
border-left
:
3px
solid
var
(
--primary
);
background
:
#F0F4FF
;
}
.notification-item.tier-blocking
{
border-left-color
:
var
(
--danger
);
}
.notif-header
{
display
:
flex
;
justify-content
:
space-between
;
margin-bottom
:
4px
;
}
.notif-time
{
font-size
:
0.8em
;
color
:
var
(
--text-muted
);
}
/* Tasks */
.task-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
8px
;
}
.task-item
{
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
padding
:
8px
;
border
:
1px
solid
var
(
--border
);
border-radius
:
var
(
--radius
);
font-size
:
0.9em
;
}
.task-key
{
font-weight
:
700
;
color
:
var
(
--primary
);
font-size
:
0.8em
;
white-space
:
nowrap
;
}
.task-title
{
flex
:
1
;
}
.task-status
{
padding
:
2px
8px
;
border-radius
:
12px
;
font-size
:
0.75em
;
font-weight
:
600
;
}
.overdue
{
color
:
var
(
--danger
);
font-weight
:
600
;
}
/* Badges */
.badge-doing
{
background
:
#DBEAFE
;
color
:
#1E40AF
;
}
.badge-todo
{
background
:
#FEF9C3
;
color
:
#854D0E
;
}
.badge-in_review
{
background
:
#E0E7FF
;
color
:
#4338CA
;
}
.badge-frozen
{
background
:
#E0F2FE
;
color
:
#075985
;
}
.badge-done
{
background
:
#DCFCE7
;
color
:
#166534
;
}
.badge-backlog
{
background
:
var
(
--border
);
color
:
var
(
--text-muted
);
}
.badge-success
{
background
:
#DCFCE7
;
color
:
#166534
;
}
.badge-warning
{
background
:
#FEF9C3
;
color
:
#854D0E
;
}
.badge-danger
{
background
:
#FEF2F2
;
color
:
#991B1B
;
}
/* Page Header */
.page-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
margin-bottom
:
20px
;
}
/* Team Status */
.team-member-row
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
padding
:
8px
0
;
border-bottom
:
1px
solid
var
(
--border
);
}
/* Toast */
#toast-container
{
position
:
fixed
;
bottom
:
20px
;
right
:
20px
;
z-index
:
9999
;
display
:
flex
;
flex-direction
:
column
;
gap
:
8px
;
}
.toast
{
padding
:
12px
20px
;
border-radius
:
var
(
--radius
);
color
:
white
;
font-weight
:
600
;
animation
:
slideIn
0.3s
ease
;
box-shadow
:
0
4px
12px
rgba
(
0
,
0
,
0
,
0.2
);
}
.toast-success
{
background
:
var
(
--success
);
}
.toast-error
{
background
:
var
(
--danger
);
}
.toast-warning
{
background
:
var
(
--warning
);
color
:
#000
;
}
.toast-info
{
background
:
var
(
--primary
);
}
.toast-gold
{
background
:
linear-gradient
(
135deg
,
#F59E0B
,
#D97706
);
}
@keyframes
slideIn
{
from
{
transform
:
translateX
(
100%
);
opacity
:
0
;
}
to
{
transform
:
translateX
(
0
);
opacity
:
1
;
}
}
/* Error Pages */
.error-page
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
min-height
:
100vh
;
}
.error-container
{
text-align
:
center
;
}
.error-container
h1
{
font-size
:
6em
;
font-weight
:
800
;
color
:
var
(
--primary
);
}
/* Responsive */
--radius-lg
:
12px
;
--radius-sm
:
6px
;
/* Shadows */
--shadow-sm
:
0
1px
2px
rgba
(
0
,
0
,
0
,
0.3
);
--shadow-md
:
0
4px
12px
rgba
(
0
,
0
,
0
,
0.3
);
--shadow-lg
:
0
8px
24px
rgba
(
0
,
0
,
0
,
0.4
);
/* Transitions */
--transition
:
all
0.2s
ease
;
/* Typography */
--font-sans
:
'Inter'
,
-apple-system
,
BlinkMacSystemFont
,
'Segoe UI'
,
Roboto
,
sans-serif
;
--font-mono
:
'JetBrains Mono'
,
'Fira Code'
,
'Cascadia Code'
,
monospace
;
}
/* Light theme overrides */
[
data-theme
=
"light"
]
{
--bg-primary
:
#f8f9fa
;
--bg-secondary
:
#ffffff
;
--bg-tertiary
:
#f1f3f5
;
--bg-card
:
#ffffff
;
--bg-hover
:
#e9ecef
;
--bg-input
:
#f1f3f5
;
--text-primary
:
#1a1a2e
;
--text-secondary
:
#495057
;
--text-muted
:
#868e96
;
--text-inverse
:
#ffffff
;
--border-color
:
#dee2e6
;
--border-light
:
#e9ecef
;
--shadow-sm
:
0
1px
2px
rgba
(
0
,
0
,
0
,
0.06
);
--shadow-md
:
0
4px
12px
rgba
(
0
,
0
,
0
,
0.08
);
--shadow-lg
:
0
8px
24px
rgba
(
0
,
0
,
0
,
0.12
);
}
html
{
font-size
:
14px
;
-webkit-font-smoothing
:
antialiased
;
-moz-osx-font-smoothing
:
grayscale
;
}
body
{
font-family
:
var
(
--font-sans
);
background
:
var
(
--bg-primary
);
color
:
var
(
--text-primary
);
line-height
:
1.6
;
min-height
:
100vh
;
overflow-x
:
hidden
;
}
a
{
color
:
var
(
--accent-primary
);
text-decoration
:
none
;
transition
:
var
(
--transition
);
}
a
:hover
{
color
:
var
(
--accent-primary-hover
);
}
img
{
max-width
:
100%
;
height
:
auto
;
}
/* ─── TYPOGRAPHY ─── */
h1
,
h2
,
h3
,
h4
,
h5
,
h6
{
font-weight
:
600
;
line-height
:
1.3
;
color
:
var
(
--text-primary
);
}
h1
{
font-size
:
1.75rem
;
}
h2
{
font-size
:
1.4rem
;
}
h3
{
font-size
:
1.15rem
;
}
h4
{
font-size
:
1rem
;
}
/* ─── TOP NAVIGATION ─── */
.top-nav
{
position
:
fixed
;
top
:
0
;
left
:
0
;
right
:
0
;
height
:
var
(
--nav-height
);
background
:
var
(
--bg-secondary
);
border-bottom
:
1px
solid
var
(
--border-color
);
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
padding
:
0
20px
;
z-index
:
1000
;
box-shadow
:
var
(
--shadow-sm
);
}
.nav-left
{
display
:
flex
;
align-items
:
center
;
gap
:
20px
;
}
.nav-brand
{
font-size
:
1.2rem
;
font-weight
:
700
;
color
:
var
(
--text-primary
)
!important
;
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
white-space
:
nowrap
;
}
.nav-brand
:hover
{
color
:
var
(
--accent-primary
)
!important
;
}
.search-bar
input
{
background
:
var
(
--bg-input
);
border
:
1px
solid
var
(
--border-color
);
border-radius
:
var
(
--radius
);
padding
:
8px
14px
;
color
:
var
(
--text-primary
);
font-size
:
0.9rem
;
width
:
280px
;
transition
:
var
(
--transition
);
}
.search-bar
input
:focus
{
outline
:
none
;
border-color
:
var
(
--accent-primary
);
box-shadow
:
0
0
0
3px
var
(
--accent-primary-bg
);
width
:
360px
;
}
.search-bar
input
::placeholder
{
color
:
var
(
--text-muted
);
}
.nav-right
{
display
:
flex
;
align-items
:
center
;
gap
:
16px
;
}
.nav-icon
{
position
:
relative
;
font-size
:
1.3rem
;
color
:
var
(
--text-secondary
)
!important
;
padding
:
6px
;
border-radius
:
var
(
--radius-sm
);
transition
:
var
(
--transition
);
}
.nav-icon
:hover
{
background
:
var
(
--bg-hover
);
color
:
var
(
--text-primary
)
!important
;
}
.notification-badge
{
position
:
absolute
;
top
:
-2px
;
right
:
-4px
;
background
:
var
(
--danger
);
color
:
white
;
font-size
:
10px
;
font-weight
:
700
;
padding
:
1px
5px
;
border-radius
:
10px
;
min-width
:
18px
;
text-align
:
center
;
line-height
:
1.4
;
}
.nav-user
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
flex-end
;
line-height
:
1.2
;
}
.nav-user-name
{
font-weight
:
600
;
font-size
:
0.85rem
;
color
:
var
(
--text-primary
);
}
.nav-user-role
{
font-size
:
0.7rem
;
color
:
var
(
--text-muted
);
letter-spacing
:
0.5px
;
}
.nav-avatar
{
width
:
36px
;
height
:
36px
;
border-radius
:
50%
;
overflow
:
hidden
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
background
:
var
(
--bg-tertiary
);
font-size
:
1.2rem
;
border
:
2px
solid
var
(
--border-color
);
}
.nav-avatar
img
{
width
:
100%
;
height
:
100%
;
object-fit
:
cover
;
}
/* ─── SIDEBAR ─── */
.sidebar
{
position
:
fixed
;
top
:
var
(
--nav-height
);
left
:
0
;
bottom
:
0
;
width
:
var
(
--sidebar-width
);
background
:
var
(
--bg-secondary
);
border-right
:
1px
solid
var
(
--border-color
);
overflow-y
:
auto
;
padding
:
12px
0
;
z-index
:
999
;
}
.sidebar
ul
{
list-style
:
none
;
}
.sidebar
li
a
,
.sidebar-link-btn
{
display
:
flex
;
align-items
:
center
;
gap
:
10px
;
padding
:
10px
20px
;
color
:
var
(
--text-secondary
);
font-size
:
0.9rem
;
font-weight
:
500
;
transition
:
var
(
--transition
);
border
:
none
;
background
:
none
;
cursor
:
pointer
;
width
:
100%
;
text-align
:
left
;
font-family
:
inherit
;
}
.sidebar
li
a
:hover
,
.sidebar-link-btn
:hover
{
background
:
var
(
--bg-hover
);
color
:
var
(
--text-primary
);
}
.sidebar
li
a
.active
{
background
:
var
(
--accent-primary-bg
);
color
:
var
(
--accent-primary
);
font-weight
:
600
;
border-right
:
3px
solid
var
(
--accent-primary
);
}
.sidebar-divider
{
margin
:
8px
16px
;
border-top
:
1px
solid
var
(
--border-color
);
}
/* Scrollbar for sidebar */
.sidebar
::-webkit-scrollbar
{
width
:
4px
;
}
.sidebar
::-webkit-scrollbar-track
{
background
:
transparent
;
}
.sidebar
::-webkit-scrollbar-thumb
{
background
:
var
(
--border-color
);
border-radius
:
4px
;
}
/* ─── MAIN CONTENT ─── */
.main-content
{
margin-left
:
var
(
--sidebar-width
);
margin-top
:
var
(
--nav-height
);
padding
:
24px
;
min-height
:
calc
(
100vh
-
var
(
--nav-height
));
}
/* ─── CARDS / PANELS ─── */
.card
,
.panel
,
.stat-card
,
.health-card
{
background
:
var
(
--bg-card
);
border
:
1px
solid
var
(
--border-color
);
border-radius
:
var
(
--radius-lg
);
padding
:
20px
;
box-shadow
:
var
(
--shadow-sm
);
transition
:
var
(
--transition
);
}
.card
:hover
,
.panel
:hover
{
box-shadow
:
var
(
--shadow-md
);
}
.card-header
,
.panel-header
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
margin-bottom
:
16px
;
padding-bottom
:
12px
;
border-bottom
:
1px
solid
var
(
--border-color
);
}
.card-title
{
font-size
:
1.05rem
;
font-weight
:
600
;
color
:
var
(
--text-primary
);
}
/* ─── GRID LAYOUTS ─── */
.grid
{
display
:
grid
;
gap
:
20px
;
}
.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
);
}
@media
(
max-width
:
1200px
)
{
.grid-4
{
grid-template-columns
:
repeat
(
2
,
1
fr
);
}
.grid-3
{
grid-template-columns
:
repeat
(
2
,
1
fr
);
}
}
@media
(
max-width
:
768px
)
{
.top-nav
{
flex-wrap
:
wrap
;
gap
:
8px
;
padding
:
8px
12px
;
}
.nav-search
{
order
:
3
;
max-width
:
100%
;
flex-basis
:
100%
;
}
.dashboard-grid
{
grid-template-columns
:
1
fr
;
}
.card-wide
{
grid-column
:
span
1
;
}
.hud
{
padding
:
8px
12px
;
}
.main-content
{
padding
:
12px
;
}
}
\ No newline at end of file
.grid-4
,
.grid-3
,
.grid-2
{
grid-template-columns
:
1
fr
;
}
.sidebar
{
display
:
none
;
}
.main-content
{
margin-left
:
0
;
}
.search-bar
input
{
width
:
160px
;
}
.search-bar
input
:focus
{
width
:
200px
;
}
}
/* ─── STAT CARDS (Dashboard) ─── */
.stat-card
{
display
:
flex
;
flex-direction
:
column
;
gap
:
8px
;
position
:
relative
;
overflow
:
hidden
;
}
.stat-card
::before
{
content
:
''
;
position
:
absolute
;
top
:
0
;
left
:
0
;
right
:
0
;
height
:
3px
;
}
.stat-card.stat-primary
::before
{
background
:
var
(
--accent-primary
);
}
.stat-card.stat-success
::before
{
background
:
var
(
--success
);
}
.stat-card.stat-warning
::before
{
background
:
var
(
--warning
);
}
.stat-card.stat-danger
::before
{
background
:
var
(
--danger
);
}
.stat-card.stat-info
::before
{
background
:
var
(
--info
);
}
.stat-card.stat-bounty
::before
{
background
:
var
(
--bounty
);
}
.stat-label
{
font-size
:
0.8rem
;
color
:
var
(
--text-muted
);
text-transform
:
uppercase
;
letter-spacing
:
0.5px
;
font-weight
:
600
;
}
.stat-value
{
font-size
:
1.8rem
;
font-weight
:
700
;
color
:
var
(
--text-primary
);
line-height
:
1
;
}
.stat-sub
{
font-size
:
0.8rem
;
color
:
var
(
--text-secondary
);
}
/* ─── BUTTONS ─── */
.btn
{
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
gap
:
8px
;
padding
:
9px
18px
;
font-size
:
0.875rem
;
font-weight
:
600
;
font-family
:
inherit
;
border-radius
:
var
(
--radius
);
border
:
1px
solid
transparent
;
cursor
:
pointer
;
transition
:
var
(
--transition
);
white-space
:
nowrap
;
text-decoration
:
none
;
line-height
:
1.4
;
}
.btn
:disabled
{
opacity
:
0.5
;
cursor
:
not-allowed
;
}
.btn-primary
{
background
:
var
(
--accent-primary
);
color
:
white
;
border-color
:
var
(
--accent-primary
);
}
.btn-primary
:hover:not
(
:disabled
)
{
background
:
var
(
--accent-primary-hover
);
transform
:
translateY
(
-1px
);
box-shadow
:
var
(
--shadow-md
);
}
.btn-success
{
background
:
var
(
--success
);
color
:
white
;
}
.btn-success
:hover:not
(
:disabled
)
{
filter
:
brightness
(
1.1
);
}
.btn-danger
{
background
:
var
(
--danger
);
color
:
white
;
}
.btn-danger
:hover:not
(
:disabled
)
{
filter
:
brightness
(
1.1
);
}
.btn-warning
{
background
:
var
(
--warning
);
color
:
var
(
--text-inverse
);
}
.btn-ghost
{
background
:
transparent
;
color
:
var
(
--text-secondary
);
border-color
:
var
(
--border-color
);
}
.btn-ghost
:hover:not
(
:disabled
)
{
background
:
var
(
--bg-hover
);
color
:
var
(
--text-primary
);
}
.btn-sm
{
padding
:
5px
12px
;
font-size
:
0.8rem
;
}
.btn-lg
{
padding
:
12px
24px
;
font-size
:
1rem
;
}
.btn-block
{
width
:
100%
;
}
/* ─── FORMS ─── */
.form-group
{
margin-bottom
:
16px
;
}
.form-label
{
display
:
block
;
font-size
:
0.85rem
;
font-weight
:
600
;
color
:
var
(
--text-secondary
);
margin-bottom
:
6px
;
}
.form-control
{
width
:
100%
;
padding
:
10px
14px
;
font-size
:
0.9rem
;
font-family
:
inherit
;
background
:
var
(
--bg-input
);
border
:
1px
solid
var
(
--border-color
);
border-radius
:
var
(
--radius
);
color
:
var
(
--text-primary
);
transition
:
var
(
--transition
);
}
.form-control
:focus
{
outline
:
none
;
border-color
:
var
(
--accent-primary
);
box-shadow
:
0
0
0
3px
var
(
--accent-primary-bg
);
}
.form-control
::placeholder
{
color
:
var
(
--text-muted
);
}
select
.form-control
{
appearance
:
none
;
background-image
:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%239aa0a6' d='M6 8L1 3h10z'/%3E%3C/svg%3E")
;
background-repeat
:
no-repeat
;
background-position
:
right
12px
center
;
padding-right
:
36px
;
}
textarea
.form-control
{
min-height
:
100px
;
resize
:
vertical
;
}
.form-hint
{
font-size
:
0.75rem
;
color
:
var
(
--text-muted
);
margin-top
:
4px
;
}
.form-error
{
font-size
:
0.75rem
;
color
:
var
(
--danger
);
margin-top
:
4px
;
}
/* ─── TABLES ─── */
.data-table
{
width
:
100%
;
border-collapse
:
collapse
;
font-size
:
0.875rem
;
}
.data-table
th
{
background
:
var
(
--bg-tertiary
);
font-weight
:
600
;
color
:
var
(
--text-secondary
);
text-transform
:
uppercase
;
font-size
:
0.75rem
;
letter-spacing
:
0.5px
;
padding
:
10px
14px
;
text-align
:
left
;
border-bottom
:
2px
solid
var
(
--border-color
);
white-space
:
nowrap
;
}
.data-table
td
{
padding
:
10px
14px
;
border-bottom
:
1px
solid
var
(
--border-color
);
color
:
var
(
--text-primary
);
vertical-align
:
middle
;
}
.data-table
tbody
tr
:hover
{
background
:
var
(
--bg-hover
);
}
.data-table
.right
{
text-align
:
right
;
}
.data-table
.center
{
text-align
:
center
;
}
.data-table
.mono
{
font-family
:
var
(
--font-mono
);
font-size
:
0.8rem
;
}
/* ─── BADGES / TAGS ─── */
.badge
{
display
:
inline-flex
;
align-items
:
center
;
padding
:
3px
10px
;
font-size
:
0.72rem
;
font-weight
:
600
;
border-radius
:
20px
;
white-space
:
nowrap
;
letter-spacing
:
0.3px
;
}
.badge-success
{
background
:
var
(
--success-bg
);
color
:
var
(
--success
);
}
.badge-warning
{
background
:
var
(
--warning-bg
);
color
:
var
(
--warning
);
}
.badge-danger
{
background
:
var
(
--danger-bg
);
color
:
var
(
--danger
);
}
.badge-info
{
background
:
var
(
--info-bg
);
color
:
var
(
--info
);
}
.badge-primary
{
background
:
var
(
--accent-primary-bg
);
color
:
var
(
--accent-primary
);
}
.badge-muted
{
background
:
var
(
--bg-tertiary
);
color
:
var
(
--text-muted
);
}
/* ─── HUD (Heads-Up Display for Contractors) ─── */
.hud-container
{
background
:
var
(
--bg-card
);
border
:
1px
solid
var
(
--border-color
);
border-radius
:
var
(
--radius-lg
);
padding
:
20px
;
margin-bottom
:
24px
;
position
:
relative
;
overflow
:
hidden
;
}
.hud-top
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
margin-bottom
:
12px
;
}
.hud-salary-label
{
font-size
:
0.8rem
;
color
:
var
(
--text-muted
);
}
.hud-salary-value
{
font-size
:
2rem
;
font-weight
:
800
;
font-family
:
var
(
--font-mono
);
}
.hud-bar-container
{
height
:
8px
;
background
:
var
(
--bg-tertiary
);
border-radius
:
4px
;
overflow
:
hidden
;
margin
:
12px
0
;
}
.hud-bar
{
height
:
100%
;
border-radius
:
4px
;
transition
:
width
0.6s
ease
,
background
0.3s
ease
;
}
.hud-bar.hud-exceptional
{
background
:
linear-gradient
(
90deg
,
var
(
--success
),
gold
);
}
.hud-bar.hud-healthy
{
background
:
var
(
--success
);
}
.hud-bar.hud-warning
{
background
:
var
(
--warning
);
}
.hud-bar.hud-critical
{
background
:
var
(
--danger
);
}
.hud-details
{
display
:
flex
;
gap
:
20px
;
flex-wrap
:
wrap
;
margin-top
:
12px
;
}
.hud-detail
{
display
:
flex
;
flex-direction
:
column
;
gap
:
2px
;
}
.hud-detail-label
{
font-size
:
0.72rem
;
color
:
var
(
--text-muted
);
text-transform
:
uppercase
;
}
.hud-detail-value
{
font-size
:
0.95rem
;
font-weight
:
600
;
font-family
:
var
(
--font-mono
);
}
.hud-detail-value.positive
{
color
:
var
(
--success
);
}
.hud-detail-value.negative
{
color
:
var
(
--danger
);
}
/* ─── DASHBOARD SPECIFIC ─── */
.dashboard-header
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
margin-bottom
:
24px
;
}
.dashboard-header
h1
{
display
:
flex
;
align-items
:
center
;
gap
:
10px
;
}
.dashboard-date
{
font-size
:
0.9rem
;
color
:
var
(
--text-secondary
);
}
.welcome-banner
{
background
:
linear-gradient
(
135deg
,
var
(
--accent-primary
),
#4f46e5
);
border-radius
:
var
(
--radius-lg
);
padding
:
24px
28px
;
color
:
white
;
margin-bottom
:
24px
;
position
:
relative
;
overflow
:
hidden
;
}
.welcome-banner
::after
{
content
:
'🎮'
;
position
:
absolute
;
right
:
30px
;
top
:
50%
;
transform
:
translateY
(
-50%
);
font-size
:
3rem
;
opacity
:
0.3
;
}
.welcome-banner
h2
{
color
:
white
;
font-size
:
1.4rem
;
margin-bottom
:
4px
;
}
.welcome-banner
p
{
opacity
:
0.85
;
font-size
:
0.9rem
;
}
.quick-actions
{
display
:
flex
;
gap
:
10px
;
margin-top
:
12px
;
}
/* ─── TASK LIST ─── */
.task-list
{
list-style
:
none
;
}
.task-item
{
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
padding
:
10px
0
;
border-bottom
:
1px
solid
var
(
--border-color
);
}
.task-item
:last-child
{
border-bottom
:
none
;
}
.task-key
{
font-family
:
var
(
--font-mono
);
font-size
:
0.78rem
;
font-weight
:
600
;
color
:
var
(
--accent-primary
);
background
:
var
(
--accent-primary-bg
);
padding
:
2px
8px
;
border-radius
:
var
(
--radius-sm
);
white-space
:
nowrap
;
}
.task-title
{
flex
:
1
;
font-size
:
0.9rem
;
color
:
var
(
--text-primary
);
}
.task-meta
{
font-size
:
0.78rem
;
color
:
var
(
--text-muted
);
white-space
:
nowrap
;
}
/* ─── PRIORITY DOTS ─── */
.priority-dot
{
width
:
8px
;
height
:
8px
;
border-radius
:
50%
;
display
:
inline-block
;
flex-shrink
:
0
;
}
.priority-critical
{
background
:
var
(
--danger
);
}
.priority-high
{
background
:
#f97316
;
}
.priority-medium
{
background
:
var
(
--warning
);
}
.priority-low
{
background
:
var
(
--info
);
}
.priority-none
{
background
:
var
(
--text-muted
);
}
/* ─── REPORT STATUS ─── */
.report-status
{
padding
:
2px
10px
;
border-radius
:
12px
;
font-size
:
0.75rem
;
font-weight
:
600
;
display
:
inline-flex
;
align-items
:
center
;
gap
:
4px
;
}
.report-submitted
{
background
:
var
(
--info-bg
);
color
:
var
(
--info
);
}
.report-approved
{
background
:
var
(
--success-bg
);
color
:
var
(
--success
);
}
.report-late
{
background
:
var
(
--warning-bg
);
color
:
var
(
--warning
);
}
.report-unreported
{
background
:
var
(
--danger-bg
);
color
:
var
(
--danger
);
}
.report-flagged
{
background
:
var
(
--danger-bg
);
color
:
var
(
--danger
);
}
/* ─── HEALTH GRID (System Health) ─── */
.health-grid
{
display
:
grid
;
grid-template-columns
:
repeat
(
auto-fill
,
minmax
(
350px
,
1
fr
));
gap
:
20px
;
}
.health-card.full-width
{
grid-column
:
1
/
-1
;
}
.health-card
h3
{
margin-bottom
:
12px
;
font-size
:
1rem
;
}
.stat-row
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
padding
:
6px
0
;
border-bottom
:
1px
solid
var
(
--border-color
);
font-size
:
0.85rem
;
}
.stat-row
:last-child
{
border-bottom
:
none
;
}
.stat-row
span
{
color
:
var
(
--text-secondary
);
}
.stat-row
strong
{
color
:
var
(
--text-primary
);
}
.big-number
{
font-size
:
2.5rem
;
font-weight
:
800
;
color
:
var
(
--accent-primary
);
margin-bottom
:
12px
;
}
/* ─── SESSION / MEMBER LIST ─── */
.session-list
{
list-style
:
none
;
font-size
:
0.8rem
;
color
:
var
(
--text-secondary
);
}
.session-list
li
{
padding
:
4px
0
;
border-bottom
:
1px
solid
var
(
--border-color
);
}
/* ─── EMPTY STATE ─── */
.empty-state
{
text-align
:
center
;
padding
:
40px
20px
;
color
:
var
(
--text-muted
);
}
.empty-state-icon
{
font-size
:
3rem
;
margin-bottom
:
12px
;
}
.empty-state-text
{
font-size
:
1rem
;
margin-bottom
:
8px
;
}
/* ─── PAGINATION ─── */
.pagination
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
gap
:
4px
;
margin-top
:
20px
;
}
.pagination
a
,
.pagination
span
{
padding
:
6px
12px
;
border-radius
:
var
(
--radius-sm
);
font-size
:
0.85rem
;
font-weight
:
500
;
}
.pagination
a
{
color
:
var
(
--text-secondary
);
border
:
1px
solid
var
(
--border-color
);
}
.pagination
a
:hover
{
background
:
var
(
--bg-hover
);
}
.pagination
.active
{
background
:
var
(
--accent-primary
);
color
:
white
;
border
:
1px
solid
var
(
--accent-primary
);
}
/* ─── LOADING ─── */
.loading-spinner
{
display
:
inline-block
;
width
:
20px
;
height
:
20px
;
border
:
2px
solid
var
(
--border-color
);
border-top-color
:
var
(
--accent-primary
);
border-radius
:
50%
;
animation
:
spin
0.6s
linear
infinite
;
}
@keyframes
spin
{
to
{
transform
:
rotate
(
360deg
);
}
}
/* ─── ERROR CELLS ─── */
.error-cell
{
max-width
:
300px
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
font-size
:
0.78rem
;
color
:
var
(
--danger
);
}
/* ─── MODAL ─── */
.modal-overlay
{
position
:
fixed
;
top
:
0
;
left
:
0
;
right
:
0
;
bottom
:
0
;
background
:
rgba
(
0
,
0
,
0
,
0.6
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
z-index
:
2000
;
backdrop-filter
:
blur
(
4px
);
}
.modal
{
background
:
var
(
--bg-card
);
border
:
1px
solid
var
(
--border-color
);
border-radius
:
var
(
--radius-lg
);
padding
:
24px
;
min-width
:
400px
;
max-width
:
600px
;
max-height
:
80vh
;
overflow-y
:
auto
;
box-shadow
:
var
(
--shadow-lg
);
}
.modal-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
margin-bottom
:
16px
;
}
.modal-close
{
background
:
none
;
border
:
none
;
font-size
:
1.5rem
;
cursor
:
pointer
;
color
:
var
(
--text-muted
);
padding
:
4px
8px
;
border-radius
:
var
(
--radius-sm
);
}
.modal-close
:hover
{
background
:
var
(
--bg-hover
);
}
/* ─── AUTH PAGES ─── */
.auth-container
{
min-height
:
100vh
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
background
:
var
(
--bg-primary
);
}
.auth-box
{
background
:
var
(
--bg-card
);
border
:
1px
solid
var
(
--border-color
);
border-radius
:
var
(
--radius-lg
);
padding
:
40px
;
width
:
100%
;
max-width
:
420px
;
box-shadow
:
var
(
--shadow-lg
);
}
.auth-title
{
text-align
:
center
;
margin-bottom
:
24px
;
font-size
:
1.5rem
;
}
.auth-error
{
background
:
var
(
--danger-bg
);
color
:
var
(
--danger
);
padding
:
10px
14px
;
border-radius
:
var
(
--radius
);
margin-bottom
:
16px
;
font-size
:
0.85rem
;
}
/* ─── ERROR PAGES ─── */
.error-page
{
min-height
:
100vh
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
.error-container
{
text-align
:
center
;
}
.error-container
h1
{
font-size
:
6rem
;
color
:
var
(
--accent-primary
);
line-height
:
1
;
margin-bottom
:
12px
;
}
/* ─── TOAST CONTAINER ─── */
#toast-container
{
position
:
fixed
;
top
:
80px
;
right
:
20px
;
z-index
:
99999
;
display
:
flex
;
flex-direction
:
column
;
gap
:
8px
;
max-width
:
400px
;
}
/* ─── SCROLLBARS ─── */
::-webkit-scrollbar
{
width
:
6px
;
height
:
6px
;
}
::-webkit-scrollbar-track
{
background
:
transparent
;
}
::-webkit-scrollbar-thumb
{
background
:
var
(
--border-color
);
border-radius
:
3px
;
}
::-webkit-scrollbar-thumb:hover
{
background
:
var
(
--border-light
);
}
/* ─── UTILITY CLASSES ─── */
.text-success
{
color
:
var
(
--success
)
!important
;
}
.text-danger
{
color
:
var
(
--danger
)
!important
;
}
.text-warning
{
color
:
var
(
--warning
)
!important
;
}
.text-muted
{
color
:
var
(
--text-muted
)
!important
;
}
.text-primary
{
color
:
var
(
--accent-primary
)
!important
;
}
.text-right
{
text-align
:
right
;
}
.text-center
{
text-align
:
center
;
}
.font-mono
{
font-family
:
var
(
--font-mono
);
}
.font-bold
{
font-weight
:
700
;
}
.mt-0
{
margin-top
:
0
;
}
.mt-1
{
margin-top
:
8px
;
}
.mt-2
{
margin-top
:
16px
;
}
.mt-3
{
margin-top
:
24px
;
}
.mb-0
{
margin-bottom
:
0
;
}
.mb-1
{
margin-bottom
:
8px
;
}
.mb-2
{
margin-bottom
:
16px
;
}
.mb-3
{
margin-bottom
:
24px
;
}
.gap-1
{
gap
:
8px
;
}
.gap-2
{
gap
:
16px
;
}
.flex
{
display
:
flex
;
}
.flex-wrap
{
flex-wrap
:
wrap
;
}
.items-center
{
align-items
:
center
;
}
.justify-between
{
justify-content
:
space-between
;
}
.hidden
{
display
:
none
!important
;
}
\ No newline at end of file
templates/dashboard/admin.php
View file @
223727b5
<?php
$__engine
->
extend
(
'layouts/app'
);
?>
<?php
$__engine
->
section
(
'title'
);
?>
Admin Dashboard
<?php
$__engine
->
endSection
();
?>
<?php
/** @var array $user */
/** @var array $notifications */
/** @var int $unread_count */
/** @var int $active_contractors */
/** @var int $onboarding_count */
/** @var array $month_deductions */
/** @var array $month_bounties */
/** @var int $active_pips */
<div
class=
"dashboard"
>
<h1>
Admin Dashboard
</h1>
<div
class=
"dashboard-grid"
>
<div
class=
"card stat-card"
>
<h3>
👥 Active Contractors
</h3>
<div
class=
"stat-number"
>
<?=
$active_contractors
??
0
?>
</div>
</div>
<div
class=
"card stat-card"
>
<h3>
📋 Onboarding
</h3>
<div
class=
"stat-number"
>
<?=
$onboarding_count
??
0
?>
</div>
</div>
<div
class=
"card stat-card"
>
<h3>
⚠️ Deductions This Month
</h3>
<div
class=
"stat-number"
>
<?=
$month_deductions
[
'cnt'
]
??
0
?>
</div>
<div
class=
"stat-sub"
>
EGP
<?=
number_format
((
float
)(
$month_deductions
[
'total'
]
??
0
),
0
)
?>
</div>
</div>
<div
class=
"card stat-card"
>
<h3>
💰 Bounties This Month
</h3>
<div
class=
"stat-number"
>
<?=
$month_bounties
[
'cnt'
]
??
0
?>
</div>
<div
class=
"stat-sub"
>
EGP
<?=
number_format
((
float
)(
$month_bounties
[
'total'
]
??
0
),
0
)
?>
</div>
$month
=
date
(
'F Y'
);
?>
<div
class=
"dashboard-header"
>
<h1>
📊 Dashboard
</h1>
<span
class=
"dashboard-date"
>
<?=
date
(
'l, F j, Y'
)
?>
</span>
</div>
<div
class=
"welcome-banner"
>
<h2>
Welcome back,
<?=
htmlspecialchars
(
$user
[
'full_name_en'
]
??
'Admin'
)
?>
</h2>
<p>
Administration overview for
<?=
$month
?>
.
</p>
</div>
<div
class=
"grid grid-4 mb-3"
>
<div
class=
"stat-card stat-primary"
>
<span
class=
"stat-label"
>
Active Contractors
</span>
<span
class=
"stat-value"
>
<?=
$active_contractors
??
0
?>
</span>
<span
class=
"stat-sub"
>
<?=
$onboarding_count
??
0
?>
onboarding
</span>
</div>
<div
class=
"stat-card stat-danger"
>
<span
class=
"stat-label"
>
Deductions (
<?=
date
(
'M'
)
?>
)
</span>
<span
class=
"stat-value font-mono"
>
<?=
number_format
(
$month_deductions
[
'total'
]
??
0
,
0
)
?>
</span>
<span
class=
"stat-sub"
>
<?=
$month_deductions
[
'cnt'
]
??
0
?>
issued
</span>
</div>
<div
class=
"stat-card stat-success"
>
<span
class=
"stat-label"
>
Bounties (
<?=
date
(
'M'
)
?>
)
</span>
<span
class=
"stat-value font-mono"
>
<?=
number_format
(
$month_bounties
[
'total'
]
??
0
,
0
)
?>
</span>
<span
class=
"stat-sub"
>
<?=
$month_bounties
[
'cnt'
]
??
0
?>
paid
</span>
</div>
<div
class=
"stat-card stat-warning"
>
<span
class=
"stat-label"
>
Active PIPs
</span>
<span
class=
"stat-value"
>
<?=
$active_pips
??
0
?>
</span>
</div>
</div>
<div
class=
"grid grid-2"
>
<div
class=
"card"
>
<div
class=
"card-header"
>
<span
class=
"card-title"
>
🔔 Notifications
</span>
<a
href=
"/notifications"
class=
"btn btn-sm btn-ghost"
>
View All
</a>
</div>
<div
class=
"card stat-card"
>
<h3>
📊 Active PIPs
</h3>
<div
class=
"stat-number"
>
<?=
$active_pips
??
0
?>
</div>
<?php
if
(
!
empty
(
$notifications
))
:
?>
<ul
class=
"task-list"
>
<?php
foreach
(
$notifications
as
$n
)
:
?>
<li
class=
"task-item"
>
<span
class=
"badge
<?=
$n
[
'tier'
]
===
'blocking'
?
'badge-danger'
:
'badge-info'
?>
"
>
<?=
strtoupper
(
$n
[
'tier'
])
?>
</span>
<span
class=
"task-title"
>
<?=
htmlspecialchars
(
$n
[
'title'
]
??
''
)
?>
</span>
<span
class=
"task-meta"
>
<?=
date
(
'M j'
,
strtotime
(
$n
[
'created_at'
]
??
'now'
))
?>
</span>
</li>
<?php
endforeach
;
?>
</ul>
<?php
else
:
?>
<div
class=
"empty-state"
><div
class=
"empty-state-icon"
>
🔕
</div><div>
No recent notifications
</div></div>
<?php
endif
;
?>
</div>
<div
class=
"card"
>
<div
class=
"card-header"
><span
class=
"card-title"
>
⚡ Quick Access
</span></div>
<div
style=
"display:grid;grid-template-columns:1fr 1fr;gap:10px;"
>
<a
href=
"/boards"
class=
"btn btn-ghost btn-block"
>
📋 Boards
</a>
<a
href=
"/users"
class=
"btn btn-ghost btn-block"
>
👥 Directory
</a>
<a
href=
"/reports/review"
class=
"btn btn-ghost btn-block"
>
📝 Review Reports
</a>
<a
href=
"/deductions"
class=
"btn btn-ghost btn-block"
>
⚠️ Deductions
</a>
<a
href=
"/payroll"
class=
"btn btn-ghost btn-block"
>
💰 Payroll
</a>
<a
href=
"/invites"
class=
"btn btn-ghost btn-block"
>
📨 Invites
</a>
</div>
</div>
</div>
\ No newline at end of file
templates/dashboard/super_admin.php
View file @
223727b5
<?php
$__engine
->
extend
(
'layouts/app'
);
?>
<?php
$__engine
->
section
(
'title'
);
?>
Super Admin Dashboard
<?php
$__engine
->
endSection
();
?>
<div
class=
"dashboard"
>
<h1>
Super Admin Dashboard
</h1>
<div
class=
"dashboard-grid"
>
<div
class=
"card stat-card"
>
<h3>
👥 Active Contractors
</h3>
<div
class=
"stat-number"
>
<?=
$active_contractors
??
0
?>
</div>
</div>
<div
class=
"card stat-card"
>
<h3>
💵 Total Payroll
</h3>
<div
class=
"stat-number"
>
EGP
<?=
number_format
(
$total_payroll
??
0
,
0
)
?>
</div>
</div>
<div
class=
"card stat-card"
>
<h3>
⚠️ Deductions
</h3>
<div
class=
"stat-number"
>
<?=
$month_deductions
[
'cnt'
]
??
0
?>
</div>
<div
class=
"stat-sub"
>
EGP
<?=
number_format
((
float
)(
$month_deductions
[
'total'
]
??
0
),
0
)
?>
</div>
</div>
<div
class=
"card stat-card"
>
<h3>
💰 Bounties
</h3>
<div
class=
"stat-number"
>
<?=
$month_bounties
[
'cnt'
]
??
0
?>
</div>
<div
class=
"stat-sub"
>
EGP
<?=
number_format
((
float
)(
$month_bounties
[
'total'
]
??
0
),
0
)
?>
</div>
</div>
<div
class=
"card stat-card"
>
<h3>
📋 Onboarding
</h3>
<div
class=
"stat-number"
>
<?=
$onboarding_count
??
0
?>
</div>
<?php
/** @var array $user */
/** @var array $notifications */
/** @var int $unread_count */
/** @var int $active_contractors */
/** @var int $onboarding_count */
/** @var array $month_deductions */
/** @var array $month_bounties */
/** @var array $payroll_status */
/** @var int $active_pips */
/** @var float $total_payroll */
/** @var int $expiring_contracts */
$month
=
date
(
'F Y'
);
$greeting
=
match
(
true
)
{
date
(
'H'
)
<
12
=>
'Good morning'
,
date
(
'H'
)
<
18
=>
'Good afternoon'
,
default
=>
'Good evening'
,
};
?>
<div
class=
"dashboard-header"
>
<h1>
📊 Dashboard
</h1>
<span
class=
"dashboard-date"
>
<?=
date
(
'l, F j, Y'
)
?>
</span>
</div>
<div
class=
"welcome-banner"
>
<h2>
<?=
$greeting
?>
,
<?=
htmlspecialchars
(
$user
[
'full_name_en'
]
??
'Admin'
)
?>
!
</h2>
<p>
Here's your operational overview for
<?=
$month
?>
.
</p>
<div
class=
"quick-actions"
>
<a
href=
"/invites"
class=
"btn btn-sm"
style=
"background:rgba(255,255,255,0.2);color:white;border:1px solid rgba(255,255,255,0.3);"
>
📨 Create Invite
</a>
<a
href=
"/payroll?month=
<?=
date
(
'Y-m'
)
?>
"
class=
"btn btn-sm"
style=
"background:rgba(255,255,255,0.2);color:white;border:1px solid rgba(255,255,255,0.3);"
>
💰 View Payroll
</a>
<a
href=
"/reports/review?date=
<?=
date
(
'Y-m-d'
)
?>
"
class=
"btn btn-sm"
style=
"background:rgba(255,255,255,0.2);color:white;border:1px solid rgba(255,255,255,0.3);"
>
📝 Review Reports
</a>
</div>
</div>
<!-- Key Metrics -->
<div
class=
"grid grid-4 mb-3"
>
<div
class=
"stat-card stat-primary"
>
<span
class=
"stat-label"
>
Active Contractors
</span>
<span
class=
"stat-value"
>
<?=
$active_contractors
??
0
?>
</span>
<span
class=
"stat-sub"
>
<?=
$onboarding_count
??
0
?>
onboarding
</span>
</div>
<div
class=
"stat-card stat-bounty"
>
<span
class=
"stat-label"
>
Total Payroll (
<?=
date
(
'M'
)
?>
)
</span>
<span
class=
"stat-value font-mono"
>
<?=
number_format
(
$total_payroll
??
0
,
0
)
?>
</span>
<span
class=
"stat-sub"
>
EGP this month
</span>
</div>
<div
class=
"stat-card stat-danger"
>
<span
class=
"stat-label"
>
Deductions (
<?=
date
(
'M'
)
?>
)
</span>
<span
class=
"stat-value font-mono"
>
<?=
number_format
(
$month_deductions
[
'total'
]
??
0
,
0
)
?>
</span>
<span
class=
"stat-sub"
>
<?=
$month_deductions
[
'cnt'
]
??
0
?>
deductions issued
</span>
</div>
<div
class=
"stat-card stat-success"
>
<span
class=
"stat-label"
>
Bounties (
<?=
date
(
'M'
)
?>
)
</span>
<span
class=
"stat-value font-mono"
>
<?=
number_format
(
$month_bounties
[
'total'
]
??
0
,
0
)
?>
</span>
<span
class=
"stat-sub"
>
<?=
$month_bounties
[
'cnt'
]
??
0
?>
bounties paid
</span>
</div>
</div>
<div
class=
"grid grid-3 mb-3"
>
<div
class=
"stat-card stat-warning"
>
<span
class=
"stat-label"
>
Active PIPs
</span>
<span
class=
"stat-value"
>
<?=
$active_pips
??
0
?>
</span>
<span
class=
"stat-sub"
>
Under performance improvement
</span>
</div>
<div
class=
"stat-card stat-info"
>
<span
class=
"stat-label"
>
Expiring Contracts
</span>
<span
class=
"stat-value"
>
<?=
$expiring_contracts
??
0
?>
</span>
<span
class=
"stat-sub"
>
Within 90 days
</span>
</div>
<div
class=
"stat-card stat-primary"
>
<span
class=
"stat-label"
>
Payroll Status
</span>
<span
class=
"stat-value"
style=
"font-size:1.2rem"
>
<?=
ucfirst
(
str_replace
(
'_'
,
' '
,
$payroll_status
[
'status'
]
??
'N/A'
))
?>
</span>
<span
class=
"stat-sub"
>
<?=
$payroll_status
[
'cnt'
]
??
0
?>
records
</span>
</div>
</div>
<!-- Recent Notifications -->
<div
class=
"grid grid-2"
>
<div
class=
"card"
>
<div
class=
"card-header"
>
<span
class=
"card-title"
>
🔔 Recent Notifications
</span>
<a
href=
"/notifications"
class=
"btn btn-sm btn-ghost"
>
View All
</a>
</div>
<div
class=
"card stat-card"
>
<h3>
📊 Active PIPs
</h3>
<div
class=
"stat-number"
>
<?=
$active_pips
??
0
?>
</div>
<?php
if
(
!
empty
(
$notifications
))
:
?>
<ul
class=
"task-list"
>
<?php
foreach
(
$notifications
as
$n
)
:
?>
<li
class=
"task-item"
>
<span
class=
"badge
<?=
$n
[
'tier'
]
===
'blocking'
?
'badge-danger'
:
(
$n
[
'tier'
]
===
'important'
?
'badge-warning'
:
'badge-muted'
)
?>
"
>
<?=
strtoupper
(
$n
[
'tier'
])
?>
</span>
<span
class=
"task-title"
>
<?=
htmlspecialchars
(
$n
[
'title'
]
??
''
)
?>
</span>
<span
class=
"task-meta"
>
<?=
date
(
'M j, g:ia'
,
strtotime
(
$n
[
'created_at'
]
??
'now'
))
?>
</span>
</li>
<?php
endforeach
;
?>
</ul>
<?php
else
:
?>
<div
class=
"empty-state"
>
<div
class=
"empty-state-icon"
>
🔕
</div>
<div
class=
"empty-state-text"
>
No recent notifications
</div>
</div>
<?php
endif
;
?>
</div>
<div
class=
"card"
>
<div
class=
"card-header"
>
<span
class=
"card-title"
>
⚡ Quick Access
</span>
</div>
<div
class=
"card stat-card"
>
<h3>
📄 Expiring Contracts
</h3>
<div
class=
"stat-number"
>
<?=
$expiring_contracts
??
0
?>
</div>
<div
class=
"stat-sub"
>
within 90 days
</div>
<div
style=
"display:grid;grid-template-columns:1fr 1fr;gap:10px;"
>
<a
href=
"/boards"
class=
"btn btn-ghost btn-block"
>
📋 Boards
</a>
<a
href=
"/users"
class=
"btn btn-ghost btn-block"
>
👥 Directory
</a>
<a
href=
"/deductions"
class=
"btn btn-ghost btn-block"
>
⚠️ Deductions
</a>
<a
href=
"/evaluations"
class=
"btn btn-ghost btn-block"
>
📊 Evaluations
</a>
<a
href=
"/analytics"
class=
"btn btn-ghost btn-block"
>
📈 Analytics
</a>
<a
href=
"/control-panel"
class=
"btn btn-ghost btn-block"
>
⚙️ Control Panel
</a>
<a
href=
"/settings"
class=
"btn btn-ghost btn-block"
>
🔧 Settings
</a>
<a
href=
"/system-health"
class=
"btn btn-ghost btn-block"
>
🏥 System Health
</a>
</div>
</div>
</div>
\ No newline at end of file
templates/layouts/auth.php
View file @
223727b5
<?php
/** @var string $content */
?>
<!DOCTYPE html>
<html
lang=
"en"
>
<html
lang=
"en"
data-theme=
"dark"
>
<head>
<meta
charset=
"UTF-8"
>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
>
<title>
<?=
$__engine
->
yield
(
'title'
,
'AL-ARCADE HR Platform - Login'
)
?>
</title>
<title>
The Grind — AL-ARCADE HR
</title>
<link
rel=
"stylesheet"
href=
"/assets/css/app.css"
>
</head>
<body
class=
"auth-page"
>
<div
class=
"auth-container"
>
<div
class=
"auth-logo"
>
🎮 The Grind
</div>
<?=
$__engine
->
content
()
?>
<?=
$content
??
''
?>
</div>
</body>
</html>
\ 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