Files
Learn_System/frontend/css/ls.css
T
Maxim Dolgolyov dfaa346051 feat(ls): expand design-system tokens + utility classes
Adds to ls.css:
- Spacing scale (--space-1..16, 4px base)
- Radius ladder (--r-xs/sm/md/xl in addition to existing lg/pill)
- Semantic color aliases (--success/warning/danger/info -> existing brand)
- Typography scale (--text-xs..3xl) + font-weight scale (--fw-*)
- Motion tokens (--ease-out, --ease-spring, --duration-*)
- Breakpoint documentation vars
- Utility classes (.hidden, .p-*, .m-*, .gap-*, .flex, .grid, .text-*, .rounded-*)
- Shared .ls-skeleton with shimmer animation

All existing tokens preserved unchanged -- additions only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 09:40:15 +03:00

1176 lines
39 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ═══════════════════════════════════════════════════════
LearnSpace — Shared Design System /css/ls.css
═══════════════════════════════════════════════════════ */
/* ── Reset ── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* ── Design tokens ── */
:root {
--bg: #EEF2FF;
--surface: rgba(255,255,255,0.82);
--border: rgba(15,23,42,0.10);
--border-h: rgba(15,23,42,0.20);
--text: #0F172A;
--text-2: #3D4F6B;
--text-3: #56687A; /* WCAG AA: ~5.1:1 on white, ~4.6:1 on --bg */
--violet: #9B5DE5;
--cyan: #06D6E0;
--green: #06D664;
--pink: #F15BB5;
--amber: #FFB347;
--grad-1: linear-gradient(135deg, #06D6E0, #9B5DE5);
/* Spacing scale (4px base) */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
--space-10: 40px;
--space-12: 48px;
--space-16: 64px;
/* Radius ladder */
--r-xs: 4px;
--r-sm: 8px;
--r-md: 12px;
--r-lg: 20px;
--r-xl: 24px;
--r-pill: 999px;
/* Semantic color aliases */
--success: var(--green);
--warning: var(--amber);
--danger: var(--pink);
--info: var(--cyan);
/* Typography scale */
--text-xs: 0.72rem;
--text-sm: 0.82rem;
--text-base: 0.92rem;
--text-md: 1.02rem;
--text-lg: 1.18rem;
--text-xl: 1.5rem;
--text-2xl: 2rem;
--text-3xl: 2.6rem;
/* Font-weight scale */
--fw-regular: 400;
--fw-medium: 500;
--fw-semibold: 600;
--fw-bold: 700;
--fw-extrabold: 800;
/* Motion */
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--duration-fast: 0.12s;
--duration-base: 0.22s;
--duration-slow: 0.40s;
/* Layout breakpoints (documentation only — CSS @media doesn't support var()) */
--bp-mobile: 640px;
--bp-tablet: 1024px;
--bp-desktop: 1280px;
--blur: blur(20px);
/* two-layer shadow: crisp + ambient */
--shadow: 0 2px 8px rgba(15,23,42,0.08), 0 8px 40px rgba(15,23,42,0.10);
--shadow-h: 0 4px 16px rgba(15,23,42,0.12), 0 16px 56px rgba(15,23,42,0.13);
--tr: 0.22s ease;
}
/* ── Body + dot-grid ── */
body {
font-family: 'Manrope', sans-serif;
background: var(--bg);
background-image: radial-gradient(circle, rgba(15,23,42,0.055) 1px, transparent 1px);
background-size: 22px 22px;
min-height: 100vh;
color: var(--text);
}
/* ── Custom scrollbar ── */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(155,93,229,0.35); border-radius: 99px; }
::-webkit-scrollbar-thumb:hover { background: rgba(155,93,229,0.60); }
/* ── Focus ring ── */
:focus-visible { outline: 2px solid var(--violet); outline-offset: 3px; }
/* ── Icon-only button (WCAG 2.5.5: 44×44 tap area) ── */
.icon-btn {
display: inline-flex; align-items: center; justify-content: center;
min-width: 44px; min-height: 44px;
border: none; border-radius: 10px;
background: transparent;
cursor: pointer;
transition: background var(--tr), color var(--tr);
color: var(--text-2);
flex-shrink: 0;
}
.icon-btn:hover { background: rgba(155,93,229,0.08); color: var(--violet); }
.icon-btn svg { width: 20px; height: 20px; }
/* ── Navbar ── */
.nav {
position: sticky; top: 0; z-index: 100;
padding: 10px 24px;
display: flex; align-items: center; justify-content: space-between;
background: rgba(238,242,255,0.90);
backdrop-filter: var(--blur);
border-bottom: 1px solid var(--border);
}
.nav-logo {
font-family: 'Unbounded', sans-serif;
font-size: 1rem; font-weight: 800;
color: var(--text); text-decoration: none;
letter-spacing: -0.01em;
}
.nav-logo span {
background: var(--grad-1);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
}
.nav-right { display: flex; align-items: center; gap: 10px; }
/* nav user avatar chip */
.nav-user-chip {
display: flex; align-items: center; gap: 8px;
padding: 4px 12px 4px 4px;
background: rgba(155,93,229,0.07);
border: 1.5px solid rgba(155,93,229,0.18);
border-radius: var(--r-pill);
}
.nav-avatar {
width: 28px; height: 28px;
border-radius: 50%;
background: var(--grad-1);
display: flex; align-items: center; justify-content: center;
font-family: 'Unbounded', sans-serif;
font-size: 0.62rem; font-weight: 800; color: #fff;
flex-shrink: 0;
}
.nav-user-name {
font-size: 0.78rem; font-weight: 600; color: var(--text-2);
max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* legacy .nav-user (plain text) */
.nav-user { font-size: 0.85rem; color: var(--text-2); }
.btn-nav {
padding: 6px 16px;
border: 1.5px solid var(--border-h);
border-radius: var(--r-pill);
background: transparent;
font-family: 'Manrope', sans-serif; font-size: 0.8rem; font-weight: 600;
color: var(--text-3);
cursor: pointer; text-decoration: none; display: inline-block;
transition: all var(--tr);
}
.btn-nav:hover { border-color: var(--violet); color: var(--violet); }
.nav-active {
background: rgba(155,93,229,0.08) !important;
border-color: var(--violet) !important;
color: var(--violet) !important;
cursor: default; pointer-events: none;
}
/* ── Shimmer btn-primary ── */
.btn-primary {
position: relative; overflow: hidden;
padding: 10px 26px;
min-height: 44px; /* WCAG 2.5.5 touch target */
border: none; border-radius: var(--r-pill);
background: var(--grad-1);
color: #fff;
font-family: 'Manrope', sans-serif; font-size: 0.88rem; font-weight: 700;
cursor: pointer;
transition: transform var(--tr), box-shadow var(--tr);
box-shadow: 0 2px 14px rgba(155,93,229,0.30);
}
.btn-primary::after {
content: '';
position: absolute; top: 0; left: -120%;
width: 80%; height: 100%;
background: linear-gradient(100deg, transparent, rgba(255,255,255,0.28), transparent);
transform: skewX(-15deg);
transition: left 0.55s ease;
}
.btn-primary:hover { transform: translateY(-1px); box-shadow: 0 6px 22px rgba(155,93,229,0.40); }
.btn-primary:hover::after { left: 160%; }
.btn-primary:active { transform: translateY(0); }
/* ── Ghost & danger buttons ── */
.btn-ghost {
padding: 8px 18px;
min-height: 44px; /* WCAG 2.5.5 touch target */
border: 1.5px solid var(--border-h); border-radius: var(--r-pill);
background: transparent;
font-family: 'Manrope', sans-serif; font-size: 0.82rem; font-weight: 600;
color: var(--text-2); cursor: pointer;
transition: all var(--tr);
}
.btn-ghost:hover { border-color: var(--violet); color: var(--violet); }
.btn-danger {
padding: 6px 14px;
border: 1.5px solid rgba(241,91,181,0.35); border-radius: var(--r-pill);
background: rgba(241,91,181,0.06);
font-family: 'Manrope', sans-serif; font-size: 0.76rem; font-weight: 600;
color: #c0306a; cursor: pointer;
transition: all var(--tr);
}
.btn-danger:hover { border-color: var(--pink); background: rgba(241,91,181,0.12); color: #a0204a; }
/* ── Form inputs ── */
.form-input {
width: 100%;
padding: 10px 14px;
border: 1.5px solid var(--border-h); border-radius: 12px;
background: rgba(255,255,255,0.70);
font-family: 'Manrope', sans-serif; font-size: 0.88rem; color: var(--text);
transition: border-color var(--tr), box-shadow var(--tr);
}
.form-input:focus {
outline: none;
border-color: var(--violet);
box-shadow: 0 0 0 3px rgba(155,93,229,0.15);
}
/* ── Badges ── */
.badge {
display: inline-flex; align-items: center;
padding: 2px 9px;
border-radius: var(--r-pill);
font-size: 0.72rem; font-weight: 700;
}
.badge-violet { background: rgba(155,93,229,0.12); color: var(--violet); }
.badge-cyan { background: rgba(6,214,224,0.12); color: #05aab3; }
.badge-green { background: rgba(6,214,100,0.12); color: #059950; }
.badge-pink { background: rgba(241,91,181,0.12); color: #c0306a; }
.badge-amber { background: rgba(255,179,71,0.15); color: #b06a00; }
/* ── Modal ── */
.modal-overlay {
position: fixed; inset: 0; z-index: 1000;
background: rgba(15,23,42,0.48);
backdrop-filter: blur(6px);
display: flex; align-items: center; justify-content: center;
padding: 20px;
animation: fadeIn 0.18s ease;
}
.modal {
background: #fff;
border-radius: 24px;
padding: 32px 28px;
max-width: 520px; width: 100%;
box-shadow: var(--shadow-h);
animation: fadeUp 0.22s ease;
}
.modal-title {
font-family: 'Unbounded', sans-serif;
font-size: 1.05rem; font-weight: 800;
margin-bottom: 20px;
}
.modal-footer { display: flex; gap: 10px; justify-content: flex-end; margin-top: 24px; }
/* ── Spinner ── */
.spinner {
width: 32px; height: 32px;
border: 3px solid var(--border);
border-top-color: var(--violet);
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin: 30px auto; display: block;
}
/* ── Empty state ── */
.empty {
text-align: center; padding: 40px 20px;
color: var(--text-3); font-size: 0.88rem;
}
.empty-icon { font-size: 2.4rem; margin-bottom: 12px; }
/* ── Error text ── */
.error { color: var(--pink); font-size: 0.85rem; padding: 12px 0; }
/* ── Keyframes ── */
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes fadeUp { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: translateY(0); } }
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes shimmer {
0% { left: -120%; }
100% { left: 160%; }
}
/* ══════════════════════════════════════════
SIDEBAR LAYOUT
══════════════════════════════════════════ */
.app-layout {
display: flex;
min-height: 100vh;
}
.app-layout > .sidebar {
width: 230px;
flex-shrink: 0;
position: sticky;
top: 0;
height: 100vh;
overflow: hidden; /* sidebar itself does NOT scroll */
display: flex;
flex-direction: column;
background: rgba(238,242,255,0.94);
backdrop-filter: blur(28px);
border-right: 1.5px solid var(--border);
padding: 0 10px 0;
z-index: 50;
}
.sb-brand {
display: flex;
align-items: center;
padding: 20px 12px 14px;
margin-bottom: 4px;
}
.sb-logo {
font-family: 'Unbounded', sans-serif;
font-size: 1.05rem;
font-weight: 800;
color: var(--text);
text-decoration: none;
}
.sb-logo span {
background: var(--grad-1);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.sb-nav {
display: flex;
flex-direction: column;
gap: 2px;
flex: 1;
min-height: 0; /* allows flex child to shrink below content size */
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: none;
}
.sb-nav::-webkit-scrollbar { display: none; }
.sb-link, a.sb-link, button.sb-link {
display: flex;
align-items: center;
gap: 10px;
padding: 9px 12px;
min-height: 44px; /* WCAG 2.5.5 touch target */
border-radius: 12px;
text-decoration: none;
font-size: 0.875rem;
font-weight: 600;
color: var(--text-3);
transition: all var(--tr);
cursor: pointer;
border: none;
background: transparent;
width: 100%;
text-align: left;
font-family: 'Manrope', sans-serif;
position: relative;
white-space: nowrap;
}
.sb-link:hover {
background: rgba(155,93,229,0.07);
color: var(--text);
}
.sb-link.active {
background: rgba(155,93,229,0.10);
color: var(--violet);
font-weight: 700;
}
.sb-link.active::before {
content: '';
position: absolute;
left: 0; top: 5px; bottom: 5px;
width: 3px;
border-radius: 0 3px 3px 0;
background: var(--grad-1);
}
.sb-icon {
width: 18px;
height: 18px;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
color: inherit;
}
.sb-icon svg {
width: 18px;
height: 18px;
stroke-width: 1.85;
}
/* tab-icon: inline svg в табах и кнопках */
.tab-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
flex-shrink: 0;
}
.tab-icon svg {
width: 16px;
height: 16px;
stroke-width: 2;
}
/* stat-icon: иконки в карточках KPI */
.stat-icon {
display: inline-flex;
align-items: center;
justify-content: center;
}
.stat-icon svg {
width: 22px;
height: 22px;
stroke-width: 1.75;
}
.sb-divider {
height: 1px;
background: var(--border);
margin: 8px 2px;
}
.sb-badge {
min-width: 18px;
height: 18px;
border-radius: 99px;
background: var(--pink);
color: #fff;
font-size: 0.6rem;
font-weight: 700;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0 4px;
margin-left: auto;
}
.sb-foot {
padding: 10px 0 16px;
border-top: 1px solid var(--border);
margin-top: 6px;
flex-shrink: 0; /* never shrink — always visible at bottom */
}
.sb-user-row {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 10px;
border-radius: 12px;
transition: background var(--tr);
}
.sb-user-row:hover { background: rgba(155,93,229,0.05); }
.sb-avatar {
width: 32px; height: 32px;
border-radius: 10px;
background: var(--grad-1);
display: flex; align-items: center; justify-content: center;
font-family: 'Unbounded', sans-serif;
font-size: 0.6rem; font-weight: 800; color: #fff;
flex-shrink: 0;
}
.sb-user-info { flex: 1; min-width: 0; }
.sb-user-name {
font-size: 0.78rem; font-weight: 700; color: var(--text);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
margin-bottom: 1px;
}
.sb-logout {
font-size: 0.68rem; color: var(--text-3);
background: none; border: none; cursor: pointer; padding: 0;
font-family: 'Manrope', sans-serif; font-weight: 600;
transition: color var(--tr);
}
.sb-logout:hover { color: var(--pink); }
.sb-content {
flex: 1;
min-width: 0;
overflow-x: hidden;
}
/* notif wrap — visually separate from nav items */
#notif-wrap {
border-top: 1.5px solid var(--border);
margin-top: 6px;
padding-top: 6px;
}
/* notif drop anchored relative to sidebar */
.notif-drop {
position: fixed;
left: 246px;
width: 320px;
max-height: 420px;
overflow-y: auto;
background: #fff;
border: 1.5px solid var(--border-h);
border-radius: 16px;
box-shadow: 0 12px 48px rgba(15,23,42,0.14);
z-index: 1000;
display: none;
font-size: 0.82rem;
color: var(--text-2);
}
.notif-drop.open { display: block; }
.notif-drop-header { display: flex; align-items: center; justify-content: space-between; padding: 13px 16px 9px; border-bottom: 1px solid var(--border); }
.notif-drop-title { font-family: 'Unbounded', sans-serif; font-size: 0.77rem; font-weight: 800; }
.notif-read-all { background: none; border: none; font-size: 0.72rem; color: var(--violet); cursor: pointer; font-family: 'Manrope', sans-serif; font-weight: 600; padding: 0; }
.notif-read-all:hover { text-decoration: underline; }
.notif-item { display: flex; gap: 9px; padding: 10px 14px; border-bottom: 1px solid var(--border); cursor: pointer; text-decoration: none; color: inherit; transition: background var(--tr); }
.notif-item:last-child { border-bottom: none; }
.notif-item:hover { background: rgba(155,93,229,0.04); }
.notif-item.unread { background: rgba(155,93,229,0.06); }
.notif-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--violet); flex-shrink: 0; margin-top: 5px; }
.notif-dot.read { background: transparent; border: 1.5px solid var(--border-h); }
.notif-msg { font-size: 0.79rem; line-height: 1.4; flex: 1; }
.notif-time { font-size: 0.68rem; color: var(--text-3); margin-top: 2px; }
.notif-empty { padding: 26px 16px; text-align: center; color: var(--text-3); font-size: 0.83rem; }
/* ══════════════════════════════════════════
PAGE TRANSITIONS (View Transitions API)
══════════════════════════════════════════ */
@view-transition { navigation: auto; }
/* Shared element: sidebar stays fixed, only main content transitions */
.sb-content { view-transition-name: main-content; }
.sidebar { view-transition-name: sidebar; }
/* Sidebar: no animation — stays in place */
::view-transition-old(sidebar),
::view-transition-new(sidebar) { animation: none; }
/* Main content: slide + fade */
::view-transition-old(main-content) {
animation: 180ms cubic-bezier(.4,0,1,1) both vt-slide-out;
}
::view-transition-new(main-content) {
animation: 260ms cubic-bezier(0,.5,.3,1) both vt-slide-in;
}
/* Fallback for pages without sidebar (login, test-run, test-result) */
::view-transition-old(root) {
animation: 160ms cubic-bezier(.4,0,1,1) both vt-fade-out;
}
::view-transition-new(root) {
animation: 240ms cubic-bezier(0,.5,.3,1) both vt-fade-in;
}
@keyframes vt-slide-out {
from { opacity: 1; transform: translateX(0) scale(1); }
to { opacity: 0; transform: translateX(-16px) scale(.98); }
}
@keyframes vt-slide-in {
from { opacity: 0; transform: translateX(20px) scale(.98); }
to { opacity: 1; transform: translateX(0) scale(1); }
}
@keyframes vt-fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes vt-fade-in {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* ══════════════════════════════════════════
LOGIN PAGE SPLIT LAYOUT
══════════════════════════════════════════ */
.login-page {
display: block !important;
padding: 0 !important;
}
.login-layout {
display: flex;
min-height: 100vh;
}
.login-left {
flex: 1;
background: linear-gradient(145deg, #0f0c29 0%, #1a1547 40%, #24243e 100%);
position: relative;
overflow: hidden;
display: flex;
align-items: center;
padding: 60px 56px;
}
.login-left::before {
content: '';
position: absolute; inset: 0;
background-image: radial-gradient(circle, rgba(255,255,255,0.055) 1px, transparent 1px);
background-size: 22px 22px;
pointer-events: none;
}
.ll-blob1, .ll-blob2 { position: absolute; border-radius: 50%; pointer-events: none; }
.ll-blob1 { width: 520px; height: 520px; background: radial-gradient(circle, rgba(155,93,229,0.32), transparent 70%); top: -160px; right: -100px; }
.ll-blob2 { width: 380px; height: 380px; background: radial-gradient(circle, rgba(6,214,224,0.22), transparent 70%); bottom: -100px; left: 15%; }
.ll-inner { position: relative; z-index: 1; max-width: 440px; }
.ll-logo {
font-family: 'Unbounded', sans-serif;
font-size: 1.35rem; font-weight: 800;
color: #fff;
margin-bottom: 52px;
}
.ll-logo span {
background: linear-gradient(135deg, #06D6E0, #9B5DE5);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
}
.ll-headline {
font-family: 'Unbounded', sans-serif;
font-size: 1.75rem; font-weight: 800; line-height: 1.3;
color: #fff;
margin-bottom: 14px;
text-shadow: 0 2px 20px rgba(0,0,0,0.25);
}
.ll-tagline {
font-size: 0.88rem; color: rgba(255,255,255,0.58); font-weight: 500;
margin-bottom: 40px; line-height: 1.65;
}
.ll-features { display: flex; flex-direction: column; gap: 10px; }
.ll-feat {
display: flex; align-items: center; gap: 12px;
font-size: 0.85rem; color: rgba(255,255,255,0.82); font-weight: 600;
padding: 10px 16px;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.09);
border-radius: 12px;
}
.ll-feat-icon { font-size: 1rem; flex-shrink: 0; }
.login-right {
width: 450px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 44px 40px;
background: #f4f6ff;
}
.login-right .auth-wrap {
width: 100%;
max-width: 100%;
}
@media (max-width: 840px) {
.login-layout { flex-direction: column; }
.login-left { padding: 40px 28px; min-height: 300px; }
.login-right { width: 100%; padding: 36px 24px; }
.ll-headline { font-size: 1.35rem; }
}
/* ══════════════════════════════════════════
A1: STAGGERED ENTRANCE ANIMATION
══════════════════════════════════════════ */
@keyframes cardIn {
from { opacity: 0; transform: translateY(18px) scale(0.97); }
to { opacity: 1; transform: none; }
}
.stagger-item {
animation: cardIn 0.42s cubic-bezier(0.22, 0.61, 0.36, 1) both;
animation-delay: calc(var(--i, 0) * 50ms);
}
/* ══════════════════════════════════════════
D1: SKELETON 2.0 — VIOLET SHIMMER
══════════════════════════════════════════ */
@keyframes ls-shimmer {
from { background-position: -600px 0; }
to { background-position: 600px 0; }
}
.ls-sk {
background: linear-gradient(
90deg,
rgba(155,93,229,0.06) 20%,
rgba(155,93,229,0.16) 40%,
rgba(255,255,255,0.7) 50%,
rgba(155,93,229,0.16) 60%,
rgba(155,93,229,0.06) 80%
) !important;
background-size: 1200px 100% !important;
animation: ls-shimmer 1.8s infinite ease-in-out !important;
}
/* ══════════════════════════════════════════
C2: RICH EMPTY STATES
══════════════════════════════════════════ */
.rich-empty {
display: flex; flex-direction: column; align-items: center;
gap: 10px; padding: 52px 24px; text-align: center;
background: rgba(255,255,255,0.7);
border: 1.5px dashed rgba(155,93,229,0.22);
border-radius: 20px;
backdrop-filter: blur(12px);
animation: cardIn 0.4s ease both;
}
.rich-empty-svg { opacity: 0.7; }
.rich-empty-title {
font-family: 'Unbounded', sans-serif;
font-size: 0.95rem; font-weight: 800; color: var(--text);
margin-top: 4px;
}
.rich-empty-sub {
font-size: 0.83rem; color: var(--text-3);
max-width: 290px; line-height: 1.62; margin-bottom: 2px;
}
.rich-empty-btn {
padding: 10px 26px;
background: var(--grad-1); color: #fff;
border: none; border-radius: 999px;
font-family: 'Manrope', sans-serif;
font-size: 0.84rem; font-weight: 700;
cursor: pointer; transition: all var(--tr);
}
.rich-empty-btn:hover { opacity: 0.88; transform: translateY(-2px); box-shadow: 0 8px 24px rgba(155,93,229,0.3); }
/* ══════════════════════════════════════════
C1: COLLAPSIBLE SIDEBAR
══════════════════════════════════════════ */
/* Smooth transition for sidebar width */
.app-layout > .sidebar { transition: width 0.28s cubic-bezier(0.4,0,0.2,1); }
/* Label text inside nav links — fades/collapses */
.sb-lbl {
overflow: hidden;
max-width: 160px;
transition: max-width 0.25s ease, opacity 0.18s ease, margin 0.18s ease;
white-space: nowrap;
flex-shrink: 1;
min-width: 0;
}
/* Toggle button (chevron) */
.sb-toggle {
width: 26px; height: 26px; border-radius: 50%;
background: rgba(255,255,255,0.85);
border: 1.5px solid var(--border-h);
display: inline-flex; align-items: center; justify-content: center;
cursor: pointer; flex-shrink: 0;
box-shadow: 0 1px 6px rgba(15,23,42,0.10);
transition: background var(--tr), border-color var(--tr), transform 0.28s ease;
padding: 0; margin-left: auto;
}
.sb-toggle:hover { background: var(--violet); color: #fff; border-color: var(--violet); }
.sb-toggle svg { width: 13px; height: 13px; stroke-width: 2.5; }
/* ── Collapsed state ── */
.app-layout.sb-collapsed > .sidebar { width: 62px; padding: 0 6px 16px; }
.app-layout.sb-collapsed .sb-brand { padding: 20px 4px 14px; justify-content: center; }
.app-layout.sb-collapsed .sb-logo { display: none; }
.app-layout.sb-collapsed .sb-lbl { max-width: 0; opacity: 0; }
.app-layout.sb-collapsed .sb-link { justify-content: center; padding: 10px 0; gap: 0; }
.app-layout.sb-collapsed .sb-badge { display: none !important; }
.app-layout.sb-collapsed .sb-user-info { display: none; }
.app-layout.sb-collapsed .sb-user-row { justify-content: center; padding: 8px 0; }
.app-layout.sb-collapsed .sb-toggle { margin: 0; transform: rotate(180deg); }
/* ══════════════════════════════════════════
GLOBAL SEARCH MODAL
══════════════════════════════════════════ */
.gs-overlay {
position: fixed; inset: 0; z-index: 9500;
background: rgba(15,23,42,0.45); backdrop-filter: blur(12px);
display: flex; align-items: flex-start; justify-content: center;
padding: 10vh 20px 20px; opacity: 0; pointer-events: none;
transition: opacity .18s ease;
}
.gs-overlay.open { opacity: 1; pointer-events: auto; }
.gs-box {
width: 100%; max-width: 560px; background: #fff;
border-radius: 20px; box-shadow: 0 40px 100px rgba(15,23,42,0.28);
overflow: hidden; transform: scale(.96) translateY(-8px);
transition: transform .18s ease;
}
.gs-overlay.open .gs-box { transform: scale(1) translateY(0); }
.gs-input-wrap {
display: flex; align-items: center; gap: 10px;
padding: 16px 20px; border-bottom: 1px solid rgba(15,23,42,0.08);
}
.gs-input-wrap svg { flex-shrink: 0; color: var(--text-3); }
.gs-input {
flex: 1; border: none; outline: none; font-family: 'Manrope', sans-serif;
font-size: 0.95rem; font-weight: 500; color: #0F172A; background: transparent;
}
.gs-input::placeholder { color: #B0BEC5; }
.gs-kbd {
font-size: 0.65rem; font-weight: 700; color: var(--text-3); background: rgba(15,23,42,0.06);
padding: 3px 7px; border-radius: 5px; line-height: 1;
}
.gs-results {
max-height: 380px; overflow-y: auto; padding: 8px;
}
.gs-empty {
text-align: center; padding: 40px 20px; color: var(--text-3); font-size: 0.85rem;
}
.gs-group-label {
font-size: 0.65rem; font-weight: 700; text-transform: uppercase;
letter-spacing: 0.06em; color: var(--text-3); padding: 10px 12px 4px;
}
.gs-item {
display: flex; align-items: center; gap: 12px;
padding: 10px 12px; border-radius: 12px; cursor: pointer;
transition: background .12s;
}
.gs-item:hover, .gs-item.active { background: rgba(155,93,229,0.06); }
.gs-item-icon {
width: 34px; height: 34px; border-radius: 9px;
display: flex; align-items: center; justify-content: center;
font-size: 0.85rem; flex-shrink: 0;
}
.gs-icon-lesson { background: rgba(155,93,229,0.1); color: #9B5DE5; }
.gs-icon-course { background: rgba(6,214,160,0.1); color: #06D6A0; }
.gs-icon-file { background: rgba(6,214,224,0.1); color: #06D6E0; }
.gs-icon-question { background: rgba(245,158,11,0.1); color: #F59E0B; }
.gs-item-body { flex: 1; min-width: 0; }
.gs-item-title {
font-size: 0.85rem; font-weight: 600; color: #0F172A;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gs-item-sub {
font-size: 0.7rem; color: var(--text-3); margin-top: 1px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gs-item-arrow { color: #ccc; flex-shrink: 0; }
/* ══════════════════════════════════════════
MOBILE TOPBAR
══════════════════════════════════════════ */
.mob-bar { display: none; }
/* ══════════════════════════════════════════
RESPONSIVE — TABLET & MOBILE (≤ 768px)
══════════════════════════════════════════ */
@media (max-width: 768px) {
/* ── Mobile top bar ── */
.mob-bar {
display: flex;
align-items: center;
justify-content: space-between;
position: fixed;
top: 0; left: 0; right: 0;
height: 56px;
padding: 0 14px;
background: rgba(238,242,255,0.95);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
z-index: 150;
gap: 10px;
}
.mob-bar-logo {
font-family: 'Unbounded', sans-serif;
font-size: 0.9rem; font-weight: 800;
color: var(--text); text-decoration: none;
flex: 1;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.mob-bar-logo span {
background: var(--grad-1);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
}
.mob-bar-actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.mob-icon-btn {
width: 36px; height: 36px;
border-radius: 10px;
background: transparent;
border: 1.5px solid var(--border-h);
display: flex; align-items: center; justify-content: center;
cursor: pointer;
transition: all var(--tr);
position: relative;
flex-shrink: 0;
}
.mob-icon-btn:hover { background: rgba(155,93,229,0.08); border-color: var(--violet); }
.mob-icon-btn svg { width: 17px; height: 17px; stroke: var(--text-2); stroke-width: 2; pointer-events: none; }
/* ── Sidebar becomes fixed drawer ── */
.app-layout { flex-direction: column; }
.app-layout > .sidebar {
position: fixed !important;
top: 0; left: 0;
height: 100vh !important;
width: 272px !important;
transform: translateX(-100%);
z-index: 200;
transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.28s ease !important;
box-shadow: none !important;
padding-top: 0 !important;
}
.app-layout.sb-open > .sidebar {
transform: translateX(0) !important;
box-shadow: 8px 0 40px rgba(15,23,42,0.22) !important;
}
/* Backdrop overlay when drawer is open */
.sb-backdrop {
display: none;
position: fixed; inset: 0;
background: rgba(15,23,42,0.40);
z-index: 190;
backdrop-filter: blur(2px);
animation: fadeIn 0.2s ease;
}
.sb-backdrop.open { display: block; }
/* Hide desktop collapse toggle on mobile */
.sb-toggle { display: none !important; }
/* Content: full width, offset for topbar */
.sb-content { width: 100%; padding-top: 56px; }
/* Restore labels inside drawer on mobile (ignore collapsed state) */
.app-layout > .sidebar .sb-lbl { max-width: 160px !important; opacity: 1 !important; }
.app-layout > .sidebar .sb-logo { display: block !important; }
.app-layout > .sidebar .sb-link { justify-content: flex-start !important; padding: 9px 12px !important; gap: 10px !important; }
.app-layout > .sidebar .sb-user-info { display: block !important; }
.app-layout > .sidebar .sb-user-row { justify-content: flex-start !important; padding: 8px 10px !important; }
.app-layout > .sidebar .sb-badge { display: inline-flex !important; }
.app-layout > .sidebar .sb-brand { padding: 20px 12px 14px !important; justify-content: flex-start !important; }
/* ── Notification dropdown ── */
.notif-drop {
position: fixed !important;
left: auto !important;
right: 14px !important;
top: 60px !important;
width: min(360px, calc(100vw - 28px)) !important;
}
/* ── Modal: bottom sheet on mobile ── */
.modal-overlay {
align-items: flex-end;
padding: 0;
}
.modal {
max-width: 100% !important;
width: 100%;
border-radius: 22px 22px 0 0;
padding: 24px 20px calc(32px + env(safe-area-inset-bottom, 0px));
max-height: 90vh;
overflow-y: auto;
}
/* ── Safe area for fixed bottom elements ── */
.mob-bar {
padding-bottom: env(safe-area-inset-bottom, 0px);
}
/* ── Login page: handled by login.html inline styles ── */
}
/* ══════════════════════════════════════════
INLINE SVG ICONS (.ic)
══════════════════════════════════════════ */
/* Replaces emoji / Unicode symbols throughout the UI */
.ic {
display: inline-block;
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: none;
stroke: currentColor;
stroke-width: 2.5;
stroke-linecap: round;
stroke-linejoin: round;
flex-shrink: 0;
overflow: visible;
}
/* Larger display icons (page headers, empty-state hints) */
.page-header-icon .ic,
.hint-icon .ic,
.complete-icon .ic {
width: 1.1em; height: 1.1em;
stroke-width: 1.5;
}
/* ══════════════════════════════════════════
FEATURE FLAGS
══════════════════════════════════════════ */
/* Student without a class: hide leaderboard */
body.no-class #lb-section { display: none !important; }
body.no-gamification .gam-bar,
body.no-gamification .lb-widget,
body.no-gamification .achievements-section,
body.no-gamification #tab-btn-achievements,
body.no-gamification #tab-btn-shop,
body.no-gamification #tab-achievements,
body.no-gamification #tab-shop { display: none !important; }
/* ══════════════════════════════════════════
RESPONSIVE — SMALL PHONES (≤ 480px)
══════════════════════════════════════════ */
@media (max-width: 480px) {
.mob-bar { padding: 0 12px; }
.modal { padding: 20px 16px 28px; }
.btn-primary { padding: 10px 20px; }
.filter-tabs { gap: 4px; }
.filter-tab { padding: 6px 12px; font-size: 0.78rem; }
}
/* ══════════════════════════════════════════
SKELETON SHIMMER
══════════════════════════════════════════ */
@keyframes ls-shimmer-util {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.ls-skeleton {
background: linear-gradient(90deg,
rgba(15,23,42,0.04) 0%,
rgba(15,23,42,0.10) 50%,
rgba(15,23,42,0.04) 100%
);
background-size: 200% 100%;
animation: ls-shimmer-util 1.6s infinite;
border-radius: var(--r-sm);
}
.ls-skeleton-line { height: 12px; margin: var(--space-2) 0; }
.ls-skeleton-card { aspect-ratio: 1; }
.ls-skeleton-row { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3); }
/* ── Utility Classes ── */
/* Visibility */
.hidden { display: none !important; }
.invisible { visibility: hidden; }
/* Padding */
.p-0 { padding: 0; }
.p-1 { padding: var(--space-1); }
.p-2 { padding: var(--space-2); }
.p-3 { padding: var(--space-3); }
.p-4 { padding: var(--space-4); }
.p-5 { padding: var(--space-5); }
.p-6 { padding: var(--space-6); }
.px-1 { padding-left: var(--space-1); padding-right: var(--space-1); }
.px-2 { padding-left: var(--space-2); padding-right: var(--space-2); }
.px-3 { padding-left: var(--space-3); padding-right: var(--space-3); }
.px-4 { padding-left: var(--space-4); padding-right: var(--space-4); }
.py-1 { padding-top: var(--space-1); padding-bottom: var(--space-1); }
.py-2 { padding-top: var(--space-2); padding-bottom: var(--space-2); }
.py-3 { padding-top: var(--space-3); padding-bottom: var(--space-3); }
.py-4 { padding-top: var(--space-4); padding-bottom: var(--space-4); }
/* Margin */
.m-0 { margin: 0; }
.m-1 { margin: var(--space-1); }
.m-2 { margin: var(--space-2); }
.m-3 { margin: var(--space-3); }
.m-4 { margin: var(--space-4); }
.m-5 { margin: var(--space-5); }
.m-6 { margin: var(--space-6); }
.mt-1 { margin-top: var(--space-1); }
.mt-2 { margin-top: var(--space-2); }
.mt-3 { margin-top: var(--space-3); }
.mt-4 { margin-top: var(--space-4); }
.mt-5 { margin-top: var(--space-5); }
.mt-6 { margin-top: var(--space-6); }
.mb-1 { margin-bottom: var(--space-1); }
.mb-2 { margin-bottom: var(--space-2); }
.mb-3 { margin-bottom: var(--space-3); }
.mb-4 { margin-bottom: var(--space-4); }
.mb-5 { margin-bottom: var(--space-5); }
.mb-6 { margin-bottom: var(--space-6); }
.ml-1 { margin-left: var(--space-1); }
.ml-2 { margin-left: var(--space-2); }
.ml-3 { margin-left: var(--space-3); }
.ml-4 { margin-left: var(--space-4); }
.ml-5 { margin-left: var(--space-5); }
.ml-6 { margin-left: var(--space-6); }
.mr-1 { margin-right: var(--space-1); }
.mr-2 { margin-right: var(--space-2); }
.mr-3 { margin-right: var(--space-3); }
.mr-4 { margin-right: var(--space-4); }
.mr-5 { margin-right: var(--space-5); }
.mr-6 { margin-right: var(--space-6); }
.mx-1 { margin-left: var(--space-1); margin-right: var(--space-1); }
.mx-2 { margin-left: var(--space-2); margin-right: var(--space-2); }
.mx-3 { margin-left: var(--space-3); margin-right: var(--space-3); }
.mx-4 { margin-left: var(--space-4); margin-right: var(--space-4); }
.mx-5 { margin-left: var(--space-5); margin-right: var(--space-5); }
.mx-6 { margin-left: var(--space-6); margin-right: var(--space-6); }
.mx-auto { margin-left: auto; margin-right: auto; }
.my-1 { margin-top: var(--space-1); margin-bottom: var(--space-1); }
.my-2 { margin-top: var(--space-2); margin-bottom: var(--space-2); }
.my-3 { margin-top: var(--space-3); margin-bottom: var(--space-3); }
.my-4 { margin-top: var(--space-4); margin-bottom: var(--space-4); }
.my-5 { margin-top: var(--space-5); margin-bottom: var(--space-5); }
.my-6 { margin-top: var(--space-6); margin-bottom: var(--space-6); }
/* Gap */
.gap-1 { gap: var(--space-1); }
.gap-2 { gap: var(--space-2); }
.gap-3 { gap: var(--space-3); }
.gap-4 { gap: var(--space-4); }
.gap-6 { gap: var(--space-6); }
/* Flex / Grid */
.flex { display: flex; }
.inline-flex { display: inline-flex; }
.grid { display: grid; }
.items-center { align-items: center; }
.items-start { align-items: flex-start; }
.justify-center { justify-content: center; }
.justify-between { justify-content: space-between; }
.flex-col { flex-direction: column; }
.flex-1 { flex: 1; }
.flex-wrap { flex-wrap: wrap; }
/* Text */
.text-xs { font-size: var(--text-xs); }
.text-sm { font-size: var(--text-sm); }
.text-base { font-size: var(--text-base); }
.text-lg { font-size: var(--text-lg); }
.text-xl { font-size: var(--text-xl); }
.font-bold { font-weight: var(--fw-bold); }
.font-semibold{ font-weight: var(--fw-semibold); }
.text-muted { color: var(--text-3); }
.text-secondary { color: var(--text-2); }
.text-violet { color: var(--violet); }
.text-success { color: var(--success); }
.text-danger { color: var(--danger); }
.text-center { text-align: center; }
.text-right { text-align: right; }
/* Radius */
.rounded-sm { border-radius: var(--r-sm); }
.rounded-md { border-radius: var(--r-md); }
.rounded-lg { border-radius: var(--r-lg); }
.rounded-full { border-radius: var(--r-pill); }