/* ═══════════════════════════════════════════════════════ 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; overflow: hidden; } .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; } /* Gamification kill-switch. When admin turns off the feature, body.no-gamification is set by api.js/hideDisabledFeatures and EVERY XP / coin / streak / shop / achievement / frame element must vanish — across the whole app, not just the dashboard. The rules below cover: • dashboard widgets (.gam-bar, .lb-widget) • profile tabs (achievements, shop, frames) • textbook XP badges and progress cards (.hero-xp-badge, .po-xp, .xp-bar, .xp-card) • a catch-all [data-gamified] hook that wraps any future block — authors of new pages should wrap XP UI in a
instead of inventing new classes. */ 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, body.no-gamification #frames-section, body.no-gamification .hero-xp-badge, body.no-gamification .po-xp, body.no-gamification .xp-card, body.no-gamification .xp-bar, body.no-gamification .xp-pill, body.no-gamification .xp-badge, body.no-gamification [data-gamified] { 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); } /* ══════════════════════════════════════════ ANIMATED BACKGROUNDS (Phase 6) Painted by a single fixed div appended to by api.js applyCosmetics:
. The container is always position:fixed inset:0 z-index:-1, never intercepts pointer events, and sits BEHIND every page chrome. Each slug has a static fallback first (no animation), then opts into motion outside the prefers-reduced-motion overlay below. Picker preview swatches reuse `.bg-preview.` with the same visuals at smaller scale. ══════════════════════════════════════════ */ #ls-bg-fx { position: fixed; inset: 0; z-index: -2; pointer-events: none; overflow: hidden; will-change: opacity, transform, background-position; transition: opacity .4s ease; } /* The veil: a translucent white sheet that sits between the animated bg layer and page content. Without it, vibrant backgrounds bleed straight through every UI element that uses an rgba() fill — chips, cards, panel backdrops. With it, the animation reads as a subtle accent rather than a takeover. */ #ls-bg-veil { position: fixed; inset: 0; z-index: -1; pointer-events: none; background: rgba(245, 247, 251, 0.78); transition: background .4s ease; } /* On the 'dark' preset we want a darker veil so light content still pops. body[data-bg-tone="dark"] is set by api.js for dark slugs. */ body[data-bg-tone="dark"] #ls-bg-veil { background: rgba(15, 23, 42, 0.55); } .bg-preview { position: relative; width: 100%; aspect-ratio: 16 / 10; border-radius: 10px; overflow: hidden; background: #f5f7fb; } /* ── 1. none (no overlay) ─────────────────────────────────────── */ .bg-none, .bg-preview.bg-none { background: transparent; } /* ── 2. gradient-soft (free, static) ──────────────────────────── */ .bg-gradient-soft, .bg-preview.bg-gradient-soft { background: linear-gradient(135deg, #e0e7ff 0%, #fce7f3 100%); } /* ── 3. dots (free, static) ───────────────────────────────────── */ .bg-dots, .bg-preview.bg-dots { background-color: #fafbff; background-image: radial-gradient(rgba(99,102,241,0.18) 1px, transparent 1px); background-size: 22px 22px; } /* ── 4. dark (free, static) ───────────────────────────────────── */ .bg-dark, .bg-preview.bg-dark { background: radial-gradient(ellipse at top, #1e1b4b 0%, #0f172a 100%); } /* ── 5. gradient-flow (paid, animated) ────────────────────────── */ @keyframes ls-bg-flow { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } .bg-gradient-flow, .bg-preview.bg-gradient-flow { background: linear-gradient(120deg, #9B5DE5, #06D6E0, #FFD166, #9B5DE5); background-size: 300% 300%; animation: ls-bg-flow 24s ease-in-out infinite; } /* ── 6. grid (paid, animated) ─────────────────────────────────── */ @keyframes ls-bg-grid-scan { 0% { background-position: 0 0, 0 0; } 100% { background-position: 0 60px, 60px 0; } } .bg-grid, .bg-preview.bg-grid { background-color: #0f172a; background-image: linear-gradient(rgba(6,214,224,0.18) 1px, transparent 1px), linear-gradient(90deg, rgba(6,214,224,0.18) 1px, transparent 1px); background-size: 60px 60px; animation: ls-bg-grid-scan 18s linear infinite; } /* ── 7. bubbles (paid, animated) ──────────────────────────────── */ @keyframes ls-bg-bubble-rise { 0% { transform: translateY(0) scale(0.9); opacity: 0.55; } 50% { transform: translateY(-50vh) scale(1.05); opacity: 0.85; } 100% { transform: translateY(-100vh) scale(0.9); opacity: 0; } } .bg-bubbles, .bg-preview.bg-bubbles { background: linear-gradient(180deg, #cbeaff 0%, #e9f5ff 100%); position: relative; } .bg-bubbles::before, .bg-bubbles::after, .bg-preview.bg-bubbles::before, .bg-preview.bg-bubbles::after { content: ''; position: absolute; inset: 0; background-image: radial-gradient(circle at 12% 80%, rgba(255,255,255,0.7) 6px, transparent 7px), radial-gradient(circle at 38% 90%, rgba(255,255,255,0.5) 9px, transparent 10px), radial-gradient(circle at 65% 95%, rgba(255,255,255,0.6) 5px, transparent 6px), radial-gradient(circle at 85% 75%, rgba(255,255,255,0.55) 11px, transparent 12px); animation: ls-bg-bubble-rise 14s linear infinite; } .bg-bubbles::after, .bg-preview.bg-bubbles::after { background-image: radial-gradient(circle at 22% 70%, rgba(255,255,255,0.5) 8px, transparent 9px), radial-gradient(circle at 50% 88%, rgba(255,255,255,0.6) 4px, transparent 5px), radial-gradient(circle at 78% 60%, rgba(255,255,255,0.6) 7px, transparent 8px); animation-duration: 19s; animation-delay: -6s; } /* ── 8. stars (paid, animated) ────────────────────────────────── */ @keyframes ls-bg-stars-twinkle { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } } .bg-stars, .bg-preview.bg-stars { background-color: #0a0e27; background-image: radial-gradient(2px 2px at 20% 30%, #ffffff, transparent), radial-gradient(1px 1px at 40% 70%, #ffffff, transparent), radial-gradient(2px 2px at 60% 20%, #ffffff, transparent), radial-gradient(1px 1px at 80% 50%, #ffffff, transparent), radial-gradient(2px 2px at 90% 85%, #ffffff, transparent), radial-gradient(1px 1px at 10% 90%, #ffffff, transparent), radial-gradient(2px 2px at 50% 10%, #ffffff, transparent), radial-gradient(1px 1px at 30% 50%, #ffffff, transparent); background-size: 200px 200px; animation: ls-bg-stars-twinkle 6s ease-in-out infinite; } /* ── 9. aurora (premium, animated) ────────────────────────────── */ @keyframes ls-bg-aurora-spin { 0% { transform: rotate(0deg) scale(1.2); } 50% { transform: rotate(180deg) scale(1.4); } 100% { transform: rotate(360deg) scale(1.2); } } .bg-aurora, .bg-preview.bg-aurora { background: #050b1a; position: relative; overflow: hidden; } .bg-aurora::before, .bg-preview.bg-aurora::before { content: ''; position: absolute; inset: -40%; background: conic-gradient( from 0deg at 50% 50%, transparent 0%, rgba(6,214,224,0.45) 12%, rgba(155,93,229,0.55) 24%, transparent 36%, rgba(6,214,160,0.35) 60%, transparent 78%, rgba(255,107,53,0.30) 92%, transparent 100% ); filter: blur(46px); animation: ls-bg-aurora-spin 40s linear infinite; } /* ── 10. nebula (premium, animated) ───────────────────────────── */ @keyframes ls-bg-nebula-pan { 0% { background-position: 0% 0%, 100% 100%; } 50% { background-position: 50% 50%, 50% 50%; } 100% { background-position: 0% 0%, 100% 100%; } } .bg-nebula, .bg-preview.bg-nebula { background: radial-gradient(ellipse at 30% 30%, rgba(155,93,229,0.6), transparent 55%), radial-gradient(ellipse at 70% 70%, rgba(6,214,224,0.5), transparent 55%), radial-gradient(circle at 50% 50%, rgba(255,107,53,0.25), transparent 60%), #050214; background-size: 150% 150%, 150% 150%, 100% 100%, 100% 100%; animation: ls-bg-nebula-pan 30s ease-in-out infinite; } /* ── Reduced-motion: kill all bg animations, keep static palette ─ */ @media (prefers-reduced-motion: reduce) { #ls-bg-fx, #ls-bg-fx::before, #ls-bg-fx::after, .bg-preview, .bg-preview::before, .bg-preview::after { animation: none !important; } }