[POLISH] Docker setup fix + UI polish + project completion

- Fix Docker: add php.ini, correct env var names (DB_NAME/DB_USER/DB_PASS),
  add 002_email_log.sql to initdb, add Authorization header passthrough,
  add uploads volume, install opcache, create .dockerignore
- UI polish: page fade-in transitions, skeleton loader CSS, staggered card
  animations, mobile sidebar backdrop overlay, keyboard focus-visible styles,
  button loading state, tooltip system, alert banners, tab component,
  custom scrollbar, print styles, clickable table rows
- Add setButtonLoading() and _toggleSidebar() helpers to common.js
- Update CLAUDE.md to reflect 100% project completion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-17 19:48:11 +01:00
co-authored by Claude Opus 4.6
parent bcc5a2b003
commit 68f8cab0bf
8 changed files with 476 additions and 29 deletions
+328
View File
@@ -1639,3 +1639,331 @@ tbody tr:last-child td {
padding: 16px;
}
}
/* ── Page Load Transition ────────────────────────────────────────── */
.main-content {
animation: fadeInUp 0.35s ease both;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* ── Skeleton Loaders ────────────────────────────────────────────── */
.skeleton {
background: linear-gradient(90deg, var(--gray-200) 25%, var(--gray-100) 50%, var(--gray-200) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s ease-in-out infinite;
border-radius: var(--border-radius-sm);
}
.skeleton-text {
height: 14px;
margin-bottom: 8px;
width: 80%;
}
.skeleton-text-sm {
height: 10px;
margin-bottom: 6px;
width: 60%;
}
.skeleton-title {
height: 20px;
margin-bottom: 12px;
width: 50%;
}
.skeleton-circle {
border-radius: 50%;
}
.skeleton-card {
padding: 24px;
background: var(--card-bg);
border-radius: var(--border-radius-lg);
box-shadow: var(--card-shadow);
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* ── Mobile Sidebar Backdrop ─────────────────────────────────────── */
.sidebar-backdrop {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(15, 23, 42, 0.5);
backdrop-filter: blur(2px);
z-index: 99;
opacity: 0;
transition: opacity var(--transition);
}
.sidebar-backdrop.visible {
opacity: 1;
}
@media (max-width: 768px) {
.sidebar-backdrop.visible {
display: block;
}
}
/* ── Focus Visible (Keyboard Accessibility) ──────────────────────── */
:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
.btn:focus-visible {
box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.3);
}
.form-input:focus-visible,
.form-select:focus-visible,
.form-textarea:focus-visible {
outline: none;
}
.sidebar-nav a:focus-visible {
outline-offset: -2px;
outline-color: var(--primary-light);
}
/* ── Button Loading State ────────────────────────────────────────── */
.btn.loading {
position: relative;
color: transparent !important;
pointer-events: none;
}
.btn.loading::after {
content: '';
position: absolute;
width: 18px;
height: 18px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top-color: #fff;
border-radius: 50%;
animation: spin 0.6s linear infinite;
}
.btn-secondary.loading::after,
.btn-ghost.loading::after {
border-color: rgba(0, 0, 0, 0.15);
border-top-color: var(--gray-600);
}
/* ── Staggered Card Entrance ─────────────────────────────────────── */
.stats-grid > * {
animation: fadeInUp 0.4s ease both;
}
.stats-grid > *:nth-child(1) { animation-delay: 0.05s; }
.stats-grid > *:nth-child(2) { animation-delay: 0.1s; }
.stats-grid > *:nth-child(3) { animation-delay: 0.15s; }
.stats-grid > *:nth-child(4) { animation-delay: 0.2s; }
/* ── Custom Scrollbar (Sidebar) ──────────────────────────────────── */
.sidebar::-webkit-scrollbar {
width: 4px;
}
.sidebar::-webkit-scrollbar-track {
background: transparent;
}
.sidebar::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
.sidebar::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.2);
}
/* ── Tooltip ─────────────────────────────────────────────────────── */
[data-tooltip] {
position: relative;
}
[data-tooltip]::after {
content: attr(data-tooltip);
position: absolute;
bottom: calc(100% + 6px);
left: 50%;
transform: translateX(-50%) scale(0.95);
background: var(--gray-800);
color: #fff;
padding: 5px 10px;
border-radius: var(--border-radius-sm);
font-size: 0.7rem;
font-weight: 500;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity var(--transition-fast), transform var(--transition-fast);
z-index: 50;
}
[data-tooltip]:hover::after {
opacity: 1;
transform: translateX(-50%) scale(1);
}
/* ── Clickable Table Rows ────────────────────────────────────────── */
tbody tr.clickable {
cursor: pointer;
}
tbody tr.clickable:hover {
background: var(--primary-bg);
}
tbody tr.clickable:active {
background: rgba(26, 115, 232, 0.12);
}
/* ── Alert Banners ───────────────────────────────────────────────── */
.alert {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 14px 18px;
border-radius: var(--border-radius);
font-size: 0.875rem;
margin-bottom: 16px;
border-left: 4px solid;
}
.alert-info {
background: var(--info-bg);
border-left-color: var(--info);
color: var(--gray-700);
}
.alert-warning {
background: var(--warning-bg);
border-left-color: var(--warning);
color: var(--gray-700);
}
.alert-danger {
background: var(--danger-bg);
border-left-color: var(--danger);
color: var(--gray-700);
}
.alert-success {
background: var(--secondary-bg);
border-left-color: var(--secondary);
color: var(--gray-700);
}
.alert svg {
width: 20px;
height: 20px;
flex-shrink: 0;
margin-top: 1px;
}
/* ── Tabs ────────────────────────────────────────────────────────── */
.tabs {
display: flex;
border-bottom: 2px solid var(--gray-200);
margin-bottom: 24px;
overflow-x: auto;
}
.tab {
padding: 10px 20px;
font-size: 0.875rem;
font-weight: 600;
color: var(--gray-500);
border-bottom: 2px solid transparent;
margin-bottom: -2px;
cursor: pointer;
white-space: nowrap;
transition: all var(--transition-fast);
background: none;
border-top: none;
border-left: none;
border-right: none;
font-family: inherit;
}
.tab:hover {
color: var(--gray-700);
}
.tab.active {
color: var(--primary);
border-bottom-color: var(--primary);
}
/* ── Print Styles ────────────────────────────────────────────────── */
@media print {
.sidebar,
.sidebar-toggle,
.sidebar-backdrop,
.content-header-actions,
.notification-container,
.modal-overlay,
.btn {
display: none !important;
}
.main-content {
margin-left: 0 !important;
animation: none !important;
}
.content-header {
position: static;
border-bottom: 2px solid #000;
padding: 16px 0;
}
.content-body {
padding: 16px 0;
}
.card {
box-shadow: none;
border: 1px solid #ddd;
break-inside: avoid;
}
.stats-grid > * {
animation: none !important;
}
body {
background: #fff;
color: #000;
font-size: 12pt;
}
a {
color: #000;
text-decoration: underline;
}
.badge {
border: 1px solid currentColor;
}
}