From 1000d49c1e611fee82b7a16339b56460ebe21d92 Mon Sep 17 00:00:00 2001 From: EgorZuev <вÐyegorzuev@gmail.com> Date: Mon, 16 Feb 2026 16:12:56 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D1=81=D0=B2=D0=B5=D1=82=D0=BB=D0=B0=D1=8F?= =?UTF-8?q?=20=D1=82=D0=B5=D0=BC=D0=B0=20=D1=81=20=D0=BA=D0=BD=D0=BE=D0=BF?= =?UTF-8?q?=D0=BA=D0=BE=D0=B9=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BA=D0=BB=D1=8E?= =?UTF-8?q?=D1=87=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/admin/admin.css | 90 +++++++++++++++++++++++++- frontend/admin/index.html | 1 + frontend/index.html | 1 + frontend/student/index.html | 74 +++++++++++++++++++-- frontend/style.css | 126 +++++++++++++++++++++++++++++++++--- frontend/teacher/index.html | 74 +++++++++++++++++++-- frontend/theme-toggle.js | 65 +++++++++++++++++++ 7 files changed, 409 insertions(+), 22 deletions(-) create mode 100644 frontend/theme-toggle.js diff --git a/frontend/admin/admin.css b/frontend/admin/admin.css index 7b6498b..76d3749 100644 --- a/frontend/admin/admin.css +++ b/frontend/admin/admin.css @@ -29,6 +29,46 @@ --transition: 0.2s ease; } +/* ===== Light Theme ===== */ +[data-theme="light"] { + --bg-primary: #e8eaef; + --bg-sidebar: rgba(255, 255, 255, 0.88); + --bg-card: rgba(255, 255, 255, 0.95); + --bg-card-border: rgba(0, 0, 0, 0.22); + --bg-input: rgba(0, 0, 0, 0.08); + --bg-input-focus: rgba(0, 0, 0, 0.12); + --bg-hover: rgba(0, 0, 0, 0.08); + --text-primary: #0f172a; + --text-secondary: #374151; + --text-placeholder: #6b7280; + --accent: #6366f1; + --accent-hover: #4f46e5; + --accent-glow: rgba(99, 102, 241, 0.25); + --error: #dc2626; + --success: #16a34a; + --warning: #d97706; +} + +[data-theme="light"] .form-group select option, +[data-theme="light"] .filter-row select option { + background: #fff; + color: #1a1a2e; +} + +[data-theme="light"] .nav-item.active { + background: rgba(99, 102, 241, 0.18); +} + +[data-theme="light"] .form-group input, +[data-theme="light"] .form-group select, +[data-theme="light"] .filter-row select { + border-color: rgba(0, 0, 0, 0.15); +} + +[data-theme="light"] tbody td { + border-bottom-color: rgba(0, 0, 0, 0.08); +} + html { font-size: 16px; -webkit-font-smoothing: antialiased; @@ -40,6 +80,7 @@ body { color: var(--text-primary); min-height: 100vh; display: flex; + transition: background 0.4s ease, color 0.4s ease; } /* ===== Sidebar ===== */ @@ -55,6 +96,7 @@ body { top: 0; bottom: 0; z-index: 10; + transition: background 0.4s ease, border-color 0.4s ease; } .sidebar-header { @@ -135,12 +177,18 @@ body { .topbar { padding: 1.5rem 2rem; border-bottom: 1px solid var(--bg-card-border); + transition: border-color 0.4s ease; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; } .topbar h1 { font-size: 1.3rem; font-weight: 700; letter-spacing: -0.02em; + flex: 1; } .content { @@ -156,16 +204,16 @@ body { border: 1px solid var(--bg-card-border); border-radius: var(--radius-md); padding: 1.5rem; + transition: background 0.4s ease, border-color 0.4s ease; } .card h2 { - font-size: 1rem; + font-size: 0.8rem; font-weight: 600; margin-bottom: 1rem; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.04em; - font-size: 0.8rem; } /* ===== Create Form ===== */ @@ -462,6 +510,44 @@ tbody tr:hover { backdrop-filter: blur(2px); } +/* ===== Theme Toggle Button ===== */ +.theme-toggle { + width: 38px; + height: 38px; + border-radius: 50%; + background: var(--bg-input); + border: 1px solid var(--bg-card-border); + color: var(--text-primary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease; + flex-shrink: 0; +} + +.theme-toggle svg { + width: 18px; + height: 18px; + transition: transform 0.4s ease; +} + +.theme-toggle:hover { + transform: scale(1.1); + box-shadow: 0 4px 16px var(--accent-glow); +} + +.theme-toggle:active { + transform: scale(0.95); +} + +.theme-toggle--fixed { + position: fixed; + top: 1.25rem; + right: 1.25rem; + z-index: 100; +} + /* ===== Responsive ===== */ @media (max-width: 768px) { .sidebar { diff --git a/frontend/admin/index.html b/frontend/admin/index.html index 19fdeae..1fa9ac6 100644 --- a/frontend/admin/index.html +++ b/frontend/admin/index.html @@ -227,6 +227,7 @@ + diff --git a/frontend/index.html b/frontend/index.html index 025cc5e..65a4acf 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -81,6 +81,7 @@ + diff --git a/frontend/student/index.html b/frontend/student/index.html index 34f0a27..2a04092 100644 --- a/frontend/student/index.html +++ b/frontend/student/index.html @@ -13,14 +13,37 @@ box-sizing: border-box; } + :root { + --bg-primary: #0f0f1a; + --text-primary: #f0f0f5; + --text-secondary: #9ca3af; + --accent-hover: #818cf8; + --bg-card: rgba(255, 255, 255, 0.05); + --bg-card-border: rgba(255, 255, 255, 0.08); + --bg-input: rgba(255, 255, 255, 0.06); + --accent-glow: rgba(99, 102, 241, 0.35); + } + + [data-theme="light"] { + --bg-primary: #e8eaef; + --text-primary: #0f172a; + --text-secondary: #374151; + --accent-hover: #4f46e5; + --bg-card: rgba(255, 255, 255, 0.95); + --bg-card-border: rgba(0, 0, 0, 0.22); + --bg-input: rgba(0, 0, 0, 0.08); + --accent-glow: rgba(99, 102, 241, 0.25); + } + body { font-family: 'Inter', sans-serif; - background: #0f0f1a; - color: #f0f0f5; + background: var(--bg-primary); + color: var(--text-primary); min-height: 100vh; display: flex; align-items: center; justify-content: center; + transition: background 0.4s ease, color 0.4s ease; } .placeholder { @@ -33,18 +56,57 @@ } .placeholder p { - color: #9ca3af; + color: var(--text-secondary); margin-bottom: 1.5rem; } .placeholder a { - color: #818cf8; + color: var(--accent-hover); text-decoration: none; } .placeholder a:hover { text-decoration: underline; } + + /* Theme Toggle */ + .theme-toggle { + width: 42px; + height: 42px; + border-radius: 50%; + background: var(--bg-card); + border: 1px solid var(--bg-card-border); + color: var(--text-primary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + transition: background 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease; + z-index: 100; + } + + .theme-toggle svg { + width: 20px; + height: 20px; + transition: transform 0.4s ease; + } + + .theme-toggle:hover { + transform: scale(1.1); + box-shadow: 0 4px 16px var(--accent-glow); + } + + .theme-toggle:active { + transform: scale(0.95); + } + + .theme-toggle--fixed { + position: fixed; + top: 1.25rem; + right: 1.25rem; + } @@ -52,8 +114,10 @@

Панель студента

Раздел в разработке

- Выйти + Выйти
+ + \ No newline at end of file diff --git a/frontend/style.css b/frontend/style.css index a1aecf7..a995c66 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -27,6 +27,33 @@ --transition: 0.25s cubic-bezier(0.4, 0, 0.2, 1); } +/* ===== Light Theme ===== */ +[data-theme="light"] { + --bg-primary: #e8eaef; + --bg-card: rgba(255, 255, 255, 0.95); + --bg-card-border: rgba(0, 0, 0, 0.22); + --bg-input: rgba(0, 0, 0, 0.08); + --bg-input-focus: rgba(0, 0, 0, 0.12); + --text-primary: #0f172a; + --text-secondary: #374151; + --text-placeholder: #6b7280; + --accent: #6366f1; + --accent-hover: #4f46e5; + --accent-glow: rgba(99, 102, 241, 0.25); + --error: #dc2626; + --success: #16a34a; +} + +[data-theme="light"] .shape { + opacity: 0.25; +} + +[data-theme="light"] .login-card { + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.08), + inset 0 1px 0 rgba(255, 255, 255, 0.6); +} + html { font-size: 16px; -webkit-font-smoothing: antialiased; @@ -42,6 +69,7 @@ body { align-items: center; justify-content: center; overflow: hidden; + transition: background 0.4s ease, color 0.4s ease; } /* ===== Animated Background ===== */ @@ -58,6 +86,7 @@ body { filter: blur(80px); opacity: 0.5; animation: float 20s ease-in-out infinite; + transition: opacity 0.4s ease; } .shape-1 { @@ -89,10 +118,23 @@ body { } @keyframes float { - 0%, 100% { transform: translate(0, 0) scale(1); } - 25% { transform: translate(30px, -40px) scale(1.05); } - 50% { transform: translate(-20px, 20px) scale(0.95); } - 75% { transform: translate(15px, 35px) scale(1.03); } + + 0%, + 100% { + transform: translate(0, 0) scale(1); + } + + 25% { + transform: translate(30px, -40px) scale(1.05); + } + + 50% { + transform: translate(-20px, 20px) scale(0.95); + } + + 75% { + transform: translate(15px, 35px) scale(1.03); + } } /* ===== Login Container ===== */ @@ -110,6 +152,7 @@ body { opacity: 0; transform: translateY(24px); } + to { opacity: 1; transform: translateY(0); @@ -127,6 +170,7 @@ body { box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.05); + transition: background 0.4s ease, border-color 0.4s ease, box-shadow 0.4s ease; } /* ===== Header ===== */ @@ -142,8 +186,15 @@ body { } @keyframes pulse-glow { - 0%, 100% { filter: drop-shadow(0 0 6px var(--accent-glow)); } - 50% { filter: drop-shadow(0 0 18px var(--accent-glow)); } + + 0%, + 100% { + filter: drop-shadow(0 0 6px var(--accent-glow)); + } + + 50% { + filter: drop-shadow(0 0 18px var(--accent-glow)); + } } .login-header h1 { @@ -214,8 +265,8 @@ body { box-shadow: 0 0 0 3px var(--accent-glow); } -.input-wrapper input:focus ~ .input-icon, -.input-wrapper input:focus + .input-icon { +.input-wrapper input:focus~.input-icon, +.input-wrapper input:focus+.input-icon { color: var(--accent); } @@ -320,7 +371,9 @@ body { } @keyframes spin { - to { transform: rotate(360deg); } + to { + transform: rotate(360deg); + } } /* ===== Input Error State ===== */ @@ -329,6 +382,47 @@ body { box-shadow: 0 0 0 3px rgba(248, 113, 113, 0.15); } +/* ===== Theme Toggle Button ===== */ +.theme-toggle { + width: 42px; + height: 42px; + border: none; + border-radius: 50%; + background: var(--bg-card); + border: 1px solid var(--bg-card-border); + color: var(--text-primary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + transition: background 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease; + z-index: 100; + flex-shrink: 0; +} + +.theme-toggle svg { + width: 20px; + height: 20px; + transition: transform 0.4s ease; +} + +.theme-toggle:hover { + transform: scale(1.1); + box-shadow: 0 4px 16px var(--accent-glow); +} + +.theme-toggle:active { + transform: scale(0.95); +} + +.theme-toggle--fixed { + position: fixed; + top: 1.25rem; + right: 1.25rem; +} + /* ===== Responsive ===== */ @media (max-width: 480px) { .login-card { @@ -338,4 +432,16 @@ body { .login-header h1 { font-size: 1.3rem; } -} + + .theme-toggle--fixed { + top: 0.75rem; + right: 0.75rem; + width: 36px; + height: 36px; + } + + .theme-toggle--fixed svg { + width: 18px; + height: 18px; + } +} \ No newline at end of file diff --git a/frontend/teacher/index.html b/frontend/teacher/index.html index 718bfae..f3d04af 100644 --- a/frontend/teacher/index.html +++ b/frontend/teacher/index.html @@ -13,14 +13,37 @@ box-sizing: border-box; } + :root { + --bg-primary: #0f0f1a; + --text-primary: #f0f0f5; + --text-secondary: #9ca3af; + --accent-hover: #818cf8; + --bg-card: rgba(255, 255, 255, 0.05); + --bg-card-border: rgba(255, 255, 255, 0.08); + --bg-input: rgba(255, 255, 255, 0.06); + --accent-glow: rgba(99, 102, 241, 0.35); + } + + [data-theme="light"] { + --bg-primary: #e8eaef; + --text-primary: #0f172a; + --text-secondary: #374151; + --accent-hover: #4f46e5; + --bg-card: rgba(255, 255, 255, 0.95); + --bg-card-border: rgba(0, 0, 0, 0.22); + --bg-input: rgba(0, 0, 0, 0.08); + --accent-glow: rgba(99, 102, 241, 0.25); + } + body { font-family: 'Inter', sans-serif; - background: #0f0f1a; - color: #f0f0f5; + background: var(--bg-primary); + color: var(--text-primary); min-height: 100vh; display: flex; align-items: center; justify-content: center; + transition: background 0.4s ease, color 0.4s ease; } .placeholder { @@ -33,18 +56,57 @@ } .placeholder p { - color: #9ca3af; + color: var(--text-secondary); margin-bottom: 1.5rem; } .placeholder a { - color: #818cf8; + color: var(--accent-hover); text-decoration: none; } .placeholder a:hover { text-decoration: underline; } + + /* Theme Toggle */ + .theme-toggle { + width: 42px; + height: 42px; + border-radius: 50%; + background: var(--bg-card); + border: 1px solid var(--bg-card-border); + color: var(--text-primary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + transition: background 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease; + z-index: 100; + } + + .theme-toggle svg { + width: 20px; + height: 20px; + transition: transform 0.4s ease; + } + + .theme-toggle:hover { + transform: scale(1.1); + box-shadow: 0 4px 16px var(--accent-glow); + } + + .theme-toggle:active { + transform: scale(0.95); + } + + .theme-toggle--fixed { + position: fixed; + top: 1.25rem; + right: 1.25rem; + } @@ -52,8 +114,10 @@

Панель преподавателя

Раздел в разработке

- Выйти + Выйти
+ + \ No newline at end of file diff --git a/frontend/theme-toggle.js b/frontend/theme-toggle.js new file mode 100644 index 0000000..2931635 --- /dev/null +++ b/frontend/theme-toggle.js @@ -0,0 +1,65 @@ +/** + * Theme Toggle — shared across all pages. + * Reads/writes 'theme' key in localStorage. + * Injects an animated moon/sun button into the page. + */ +(() => { + 'use strict'; + + const STORAGE_KEY = 'theme'; + + /* ---------- SVG icons ---------- */ + const moonSVG = ``; + const sunSVG = ``; + + /* ---------- Apply saved theme BEFORE paint ---------- */ + const saved = localStorage.getItem(STORAGE_KEY); + if (saved) { + document.documentElement.setAttribute('data-theme', saved); + } + + /* ---------- Create button when DOM ready ---------- */ + function init() { + const isDark = () => document.documentElement.getAttribute('data-theme') !== 'light'; + + const btn = document.createElement('button'); + btn.className = 'theme-toggle'; + btn.setAttribute('aria-label', 'Переключить тему'); + btn.innerHTML = isDark() ? moonSVG : sunSVG; + + btn.addEventListener('click', () => { + const goLight = isDark(); + document.documentElement.setAttribute('data-theme', goLight ? 'light' : 'dark'); + localStorage.setItem(STORAGE_KEY, goLight ? 'light' : 'dark'); + + /* Swap icon immediately and trigger a gentle rotate via CSS */ + btn.innerHTML = goLight ? sunSVG : moonSVG; + const svg = btn.querySelector('svg'); + svg.style.transition = 'none'; + svg.style.transform = 'rotate(-90deg) scale(0.5)'; + svg.style.opacity = '0'; + requestAnimationFrame(() => { + svg.style.transition = 'transform 0.4s ease, opacity 0.3s ease'; + svg.style.transform = 'rotate(0deg) scale(1)'; + svg.style.opacity = '1'; + }); + }); + + /* Where to place the button */ + const topbar = document.querySelector('.topbar'); + if (topbar) { + /* Admin-style pages: insert into topbar (CSS handles flex layout) */ + topbar.appendChild(btn); + } else { + /* Login / placeholder pages: fixed position */ + btn.classList.add('theme-toggle--fixed'); + document.body.appendChild(btn); + } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})();