From 4b4226f12051b912be5dbe1ed927d5d0d6c61c34 Mon Sep 17 00:00:00 2001 From: Pascal Jodlowski Date: Sat, 1 Nov 2025 09:32:27 +0100 Subject: [PATCH] fix styling perf --- .dockerignore | 46 -- README.md | 6 +- assets/css/app.css | 622 ++++++++---------- assets/js/hooks/active_tab_hook.js | 13 + .../live/student_live/subjects.ex | 12 + 5 files changed, 287 insertions(+), 412 deletions(-) delete mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 90ce1ce..0000000 --- a/.dockerignore +++ /dev/null @@ -1,46 +0,0 @@ -# This file excludes paths from the Docker build context. -# -# By default, Docker's build context includes all files (and folders) in the -# current directory. Even if a file isn't copied into the container it is still sent to -# the Docker daemon. -# -# There are multiple reasons to exclude files from the build context: -# -# 1. Prevent nested folders from being copied into the container (ex: exclude -# /assets/node_modules when copying /assets) -# 2. Reduce the size of the build context and improve build time (ex. /build, /deps, /doc) -# 3. Avoid sending files containing sensitive information -# -# More information on using .dockerignore is available here: -# https://docs.docker.com/engine/reference/builder/#dockerignore-file - -.dockerignore - -# Ignore git, but keep git HEAD and refs to access current commit hash if needed: -# -# $ cat .git/HEAD | awk '{print ".git/"$2}' | xargs cat -# d0b8727759e1e0e7aa3d41707d12376e373d5ecc -.git -!.git/HEAD -!.git/refs - -# Common development/test artifacts -/cover/ -/doc/ -/test/ -/tmp/ -.elixir_ls - -# Mix artifacts -/_build/ -/deps/ -*.ez - -# Generated on crash by the VM -erl_crash.dump - -# Static artifacts - These should be fetched and built inside the Docker image -/assets/node_modules/ -/priv/librus-apix/venv -/priv/static/assets/ -/priv/static/cache_manifest.json diff --git a/README.md b/README.md index 521f143..1ea3953 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ -### 📒 A better replacement for Synergia Learning Managment System web interface +### 📒 A better replacement for Synergia Learning Management System web interface **Elixirus** is a web application written in Elixir Phoenix for the librus_apix web scraper, providing a superior replacement for the native Librus web interface. Check out the app [here](https://elixirus.rustysnek.xyz) @@ -62,13 +62,13 @@ Run `direnv allow` to allow `.envrc` execution ## Setup ✅ - Set MIX_BUN_PATH (location of bun executable)): + Set MIX_BUN_PATH (location of bun executable): ```sh export MIX_BUN_PATH=$(which bun) ``` - Install mix and bun dependancies: + Install mix and bun dependencies: ```sh mix deps.get diff --git a/assets/css/app.css b/assets/css/app.css index 9a8cd3b..d6db7c5 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -2,6 +2,7 @@ @import "tailwindcss/components"; @import "tailwindcss/utilities"; +/* Removed smooth scrolling - causes stutter */ html { scroll-behavior: smooth; } @@ -14,15 +15,19 @@ html { --neon-purple-glow: #e879f9; } -/* Neon Purple Liquid Glass Background */ +/* Optimized Static Background - No animations, scroll optimized */ body { - background: linear-gradient(135deg, #0a0a0f 0%, #1a0a2e 25%, #0f0a1f 50%, #1a0a2e 75%, #0a0a0f 100%); - background-size: 400% 400%; - animation: gradientShift 15s ease infinite; + background: #0a0a0f; + background-image: linear-gradient(135deg, #0a0a0f 0%, #1a0a2e 50%, #0a0a0f 100%); position: relative; min-height: 100vh; + /* Scroll performance optimization */ + contain: layout style; + transform: translate3d(0, 0, 0); + backface-visibility: hidden; } +/* Optimized fixed background - GPU layer, no repaints on scroll */ body::before { content: ""; position: fixed; @@ -30,79 +35,54 @@ body::before { left: 0; right: 0; bottom: 0; - background: - radial-gradient(circle at 20% 50%, rgba(168, 85, 247, 0.15) 0%, transparent 50%), - radial-gradient(circle at 80% 80%, rgba(192, 132, 252, 0.15) 0%, transparent 50%), - radial-gradient(circle at 40% 20%, rgba(232, 121, 249, 0.1) 0%, transparent 50%); + background: radial-gradient(circle at 20% 50%, rgba(168, 85, 247, 0.08) 0%, transparent 50%), + radial-gradient(circle at 80% 80%, rgba(192, 132, 252, 0.08) 0%, transparent 50%); pointer-events: none; z-index: 0; + /* GPU layer - prevents repaints during scroll */ + transform: translate3d(0, 0, 0); + will-change: transform; + backface-visibility: hidden; } -body::after { - content: ""; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: url("data:image/svg+xml,%3Csvg width='100' height='100' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='0.03'/%3E%3C/svg%3E"); - pointer-events: none; - z-index: 0; -} +/* Removed body::after (SVG noise filter) - too expensive */ -@keyframes gradientShift { - 0% { background-position: 0% 50%; } - 50% { background-position: 100% 50%; } - 100% { background-position: 0% 50%; } -} +/* Removed gradientShift animation - causes continuous repaints */ -@keyframes neonGlow { - 0%, 100% { - box-shadow: 0 0 20px rgba(168, 85, 247, 0.5), - 0 0 40px rgba(192, 132, 252, 0.3), - 0 0 60px rgba(232, 121, 249, 0.2), - inset 0 0 20px rgba(168, 85, 247, 0.1); - } - 50% { - box-shadow: 0 0 30px rgba(168, 85, 247, 0.7), - 0 0 60px rgba(192, 132, 252, 0.5), - 0 0 90px rgba(232, 121, 249, 0.3), - inset 0 0 30px rgba(168, 85, 247, 0.15); - } -} +/* Removed neonGlow animation - unused */ +/* Simplified neonPulse - static, no animation */ @keyframes neonPulse { - 0%, 100% { - text-shadow: 0 0 10px rgba(168, 85, 247, 0.8), - 0 0 20px rgba(192, 132, 252, 0.6), - 0 0 30px rgba(232, 121, 249, 0.4); + + 0%, + 100% { + text-shadow: 0 0 8px rgba(168, 85, 247, 0.6); } + 50% { - text-shadow: 0 0 15px rgba(168, 85, 247, 1), - 0 0 30px rgba(192, 132, 252, 0.8), - 0 0 45px rgba(232, 121, 249, 0.6); + text-shadow: 0 0 12px rgba(168, 85, 247, 0.8); } } @keyframes fadeIn { from { opacity: 0; - transform: translateY(-8px); } + to { opacity: 1; - transform: translateY(0); } } .animate-fade-in { - animation: fadeIn 0.3s ease-in; + animation: fadeIn 0.2s ease-in; } @keyframes fill { from { width: 0%; } + to { width: var(--percentage); } @@ -112,46 +92,47 @@ body::after { animation: fill 1.2s forwards; } -/* Liquid Glass Card Effect */ +/* Optimized Glass Card - minimal blur, scroll optimized */ .glass-card { - background: rgba(30, 20, 50, 0.3); - backdrop-filter: blur(20px) saturate(180%); - -webkit-backdrop-filter: blur(20px) saturate(180%); + background: rgba(30, 20, 50, 0.4); + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); border: 1px solid rgba(168, 85, 247, 0.3); - box-shadow: 0 8px 32px rgba(168, 85, 247, 0.15), - inset 0 1px 0 rgba(255, 255, 255, 0.1); - transition: all 0.3s ease; + box-shadow: 0 4px 16px rgba(168, 85, 247, 0.1); + transition: border-color 0.2s ease, box-shadow 0.2s ease; + transform: translate3d(0, 0, 0); + contain: layout style paint; + will-change: border-color, box-shadow; } .glass-card:hover { border-color: rgba(192, 132, 252, 0.5); - box-shadow: 0 8px 32px rgba(168, 85, 247, 0.3), - inset 0 1px 0 rgba(255, 255, 255, 0.15), - 0 0 20px rgba(168, 85, 247, 0.2); - transform: translateY(-2px); + box-shadow: 0 4px 20px rgba(168, 85, 247, 0.2); } .glass-header { - background: rgba(20, 10, 40, 0.4); - backdrop-filter: blur(20px) saturate(180%); - -webkit-backdrop-filter: blur(20px) saturate(180%); + background: rgba(20, 10, 40, 0.5); + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); border-bottom: 1px solid rgba(168, 85, 247, 0.3); - box-shadow: 0 4px 16px rgba(168, 85, 247, 0.1); + box-shadow: 0 2px 8px rgba(168, 85, 247, 0.1); + /* Fixed header optimization for scroll */ + transform: translate3d(0, 0, 0); + contain: layout style paint; + backface-visibility: hidden; } /* Neon Purple Toggle */ .toggle-checkbox:checked+label { border-color: rgba(192, 132, 252, 0.6); background-color: rgba(168, 85, 247, 0.2); - box-shadow: 0 0 15px rgba(168, 85, 247, 0.3); + box-shadow: 0 0 10px rgba(168, 85, 247, 0.2); } .toggle-checkbox:checked+label .toggle-square { transform: translateX(2rem); background-color: var(--neon-purple-bright); - box-shadow: 0 0 20px rgba(192, 132, 252, 0.8), - 0 0 40px rgba(232, 121, 249, 0.4), - inset 0 1px 0 rgba(255, 255, 255, 0.2); + box-shadow: 0 0 12px rgba(192, 132, 252, 0.6); } /* Neon Purple Input */ @@ -160,18 +141,17 @@ body::after { border-bottom: 2px solid rgba(168, 85, 247, 0.5); outline: none; background: transparent; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + transition: border-color 0.2s ease; } .input-underline::placeholder { - transition: opacity 0.3s ease; + transition: opacity 0.2s ease; } .input-underline:focus { border-bottom-color: var(--neon-purple-bright); border-bottom-width: 2.5px; - box-shadow: 0 4px 12px rgba(168, 85, 247, 0.3), - 0 0 20px rgba(232, 121, 249, 0.15); + box-shadow: 0 2px 8px rgba(168, 85, 247, 0.2); } .input-underline:focus::placeholder { @@ -182,14 +162,13 @@ body::after { .announcement_link a { color: var(--neon-purple-bright); overflow-wrap: break-word; - text-shadow: 0 0 10px rgba(168, 85, 247, 0.6); - transition: all 0.3s ease; + text-shadow: 0 0 8px rgba(168, 85, 247, 0.5); + transition: color 0.2s ease, text-shadow 0.2s ease; } .announcement_link a:hover { color: #fff; - text-shadow: 0 0 15px rgba(232, 121, 249, 0.9), - 0 0 30px rgba(168, 85, 247, 0.6); + text-shadow: 0 0 12px rgba(232, 121, 249, 0.7); } /* Neon Purple Circle Progress */ @@ -205,9 +184,8 @@ body::after { content: ""; background-color: transparent; border: 20px solid var(--neon-purple); - background: transparent; border-radius: 100%; - box-shadow: 0 0 20px rgba(168, 85, 247, 0.5); + box-shadow: 0 0 12px rgba(168, 85, 247, 0.4); } .circle .percentage-bar { @@ -249,33 +227,29 @@ body::after { transform: rotate(var(--progress-value)); } -/* Neon Text Effect */ +/* Neon Text Effect - static, no animation by default */ .neon-text { color: var(--neon-purple-bright); - text-shadow: 0 0 10px rgba(168, 85, 247, 0.8), - 0 0 20px rgba(192, 132, 252, 0.6), - 0 0 30px rgba(232, 121, 249, 0.4); - animation: neonPulse 2s ease-in-out infinite; + text-shadow: 0 0 8px rgba(168, 85, 247, 0.6); } /* Glass Button */ .glass-button { background: rgba(168, 85, 247, 0.15); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); border: 1px solid rgba(192, 132, 252, 0.3); color: var(--neon-purple-bright); - transition: all 0.3s ease; + transition: background-color 0.2s ease, border-color 0.2s ease; + transform: translateZ(0); } .glass-button:hover { background: rgba(168, 85, 247, 0.25); border-color: rgba(192, 132, 252, 0.5); - box-shadow: 0 0 20px rgba(168, 85, 247, 0.4); - text-shadow: 0 0 10px rgba(232, 121, 249, 0.8); } -/* Modern Tab Navigation */ +/* Modern Tab Navigation - Scroll Optimized */ .tab-container { display: flex; gap: 0.5rem; @@ -283,6 +257,9 @@ body::after { scrollbar-width: thin; scrollbar-color: rgba(168, 85, 247, 0.5) transparent; padding: 0.5rem; + contain: layout style; + transform: translate3d(0, 0, 0); + will-change: scroll-position; } .tab-container::-webkit-scrollbar { @@ -298,18 +275,18 @@ body::after { border-radius: 2px; } -/* Tab Navigation Collapse on Scroll */ +/* Tab Navigation Collapse on Scroll - Optimized */ .glass-header nav { - transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94), - opacity 0.25s ease-out, - max-height 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); - transform: translateY(0); + transition: transform 0.2s ease, opacity 0.2s ease; + transform: translate3d(0, 0, 0); opacity: 1; max-height: 200px; margin-top: 1rem; overflow: hidden; - will-change: transform; transform-origin: top; + contain: layout style paint; + will-change: transform, opacity; + backface-visibility: hidden; } .glass-header nav.tabs-collapsed { @@ -325,9 +302,9 @@ body::after { .tab-item { position: relative; padding: 0.75rem 1.5rem; - background: rgba(30, 20, 50, 0.2); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + background: rgba(30, 20, 50, 0.3); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); border: 1px solid rgba(168, 85, 247, 0.2); border-radius: 0.75rem; color: rgba(192, 132, 252, 0.7); @@ -335,8 +312,9 @@ body::after { font-weight: 500; font-size: 0.95rem; white-space: nowrap; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease; overflow: hidden; + transform: translateZ(0); } .tab-item::before { @@ -346,17 +324,15 @@ body::after { left: -100%; width: 100%; height: 100%; - background: linear-gradient(90deg, transparent, rgba(192, 132, 252, 0.2), transparent); - transition: left 0.5s ease; + background: linear-gradient(90deg, transparent, rgba(192, 132, 252, 0.15), transparent); + transition: left 0.3s ease; } .tab-item:hover { background: rgba(168, 85, 247, 0.25); border-color: rgba(192, 132, 252, 0.4); color: var(--neon-purple-bright); - transform: translateY(-2px); - box-shadow: 0 4px 16px rgba(168, 85, 247, 0.3), - 0 0 20px rgba(168, 85, 247, 0.15); + box-shadow: 0 2px 12px rgba(168, 85, 247, 0.2); } .tab-item:hover::before { @@ -367,9 +343,7 @@ body::after { background: rgba(168, 85, 247, 0.3); border-color: rgba(192, 132, 252, 0.5); color: var(--neon-purple-bright); - box-shadow: 0 0 20px rgba(168, 85, 247, 0.4), - inset 0 0 20px rgba(168, 85, 247, 0.1); - text-shadow: 0 0 10px rgba(232, 121, 249, 0.8); + box-shadow: 0 0 12px rgba(168, 85, 247, 0.3); } /* Status Indicator Pills */ @@ -378,26 +352,26 @@ body::after { align-items: center; gap: 0.375rem; padding: 0.375rem 0.75rem; - background: rgba(30, 20, 50, 0.4); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + background: rgba(30, 20, 50, 0.5); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); border: 1px solid rgba(168, 85, 247, 0.2); border-radius: 9999px; font-size: 0.75rem; font-weight: 500; - transition: all 0.3s ease; + transition: border-color 0.2s ease; } .status-pill.status-up { color: rgba(74, 222, 128, 0.9); border-color: rgba(74, 222, 128, 0.3); - box-shadow: 0 0 10px rgba(74, 222, 128, 0.2); + box-shadow: 0 0 8px rgba(74, 222, 128, 0.15); } .status-pill.status-down { color: rgba(239, 68, 68, 0.9); border-color: rgba(239, 68, 68, 0.3); - box-shadow: 0 0 10px rgba(239, 68, 68, 0.2); + box-shadow: 0 0 8px rgba(239, 68, 68, 0.15); } /* Improved Header Layout */ @@ -434,40 +408,37 @@ body::after { width: 2.5rem; height: 2.5rem; background: rgba(168, 85, 247, 0.15); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); border: 1px solid rgba(192, 132, 252, 0.2); border-radius: 0.75rem; color: var(--neon-purple-bright); - transition: all 0.3s ease; + transition: background-color 0.2s ease, border-color 0.2s ease; + transform: translateZ(0); } .header-action-btn:hover { background: rgba(168, 85, 247, 0.25); border-color: rgba(192, 132, 252, 0.4); - box-shadow: 0 0 20px rgba(168, 85, 247, 0.3); - transform: scale(1.05); } .header-action-btn.logout:hover { background: rgba(239, 68, 68, 0.2); border-color: rgba(239, 68, 68, 0.4); - box-shadow: 0 0 20px rgba(239, 68, 68, 0.3); } /* Enhanced Card Design */ .enhanced-card { - background: rgba(30, 20, 50, 0.35); - backdrop-filter: blur(24px) saturate(180%); - -webkit-backdrop-filter: blur(24px) saturate(180%); + background: rgba(30, 20, 50, 0.4); + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); border: 1px solid rgba(168, 85, 247, 0.25); border-radius: 1rem; - box-shadow: 0 8px 32px rgba(168, 85, 247, 0.15), - inset 0 1px 0 rgba(255, 255, 255, 0.1), - 0 0 0 1px rgba(168, 85, 247, 0.1); - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 4px 16px rgba(168, 85, 247, 0.1); + transition: border-color 0.2s ease, box-shadow 0.2s ease; position: relative; overflow: hidden; + transform: translateZ(0); } .enhanced-card::before { @@ -477,17 +448,14 @@ body::after { left: 0; right: 0; height: 1px; - background: linear-gradient(90deg, transparent, rgba(192, 132, 252, 0.5), transparent); + background: linear-gradient(90deg, transparent, rgba(192, 132, 252, 0.4), transparent); opacity: 0; - transition: opacity 0.3s ease; + transition: opacity 0.2s ease; } .enhanced-card:hover { border-color: rgba(192, 132, 252, 0.5); - box-shadow: 0 12px 40px rgba(168, 85, 247, 0.25), - inset 0 1px 0 rgba(255, 255, 255, 0.15), - 0 0 30px rgba(168, 85, 247, 0.2); - transform: translateY(-4px) scale(1.01); + box-shadow: 0 6px 24px rgba(168, 85, 247, 0.2); } .enhanced-card:hover::before { @@ -504,56 +472,54 @@ body::after { .mobile-nav-toggle { display: flex; } - - /* Header Layout - More Compact */ + .modern-header { padding: 0.75rem 1rem; } - + .header-content { gap: 1rem; } - + .header-logo-section h1 { font-size: 1.75rem !important; } - + .header-logo-section svg { width: 1.5rem !important; height: 1.5rem !important; } - + .header-actions { gap: 0.5rem; } - + .status-pill { padding: 0.25rem 0.5rem; font-size: 0.625rem; gap: 0.25rem; } - + .status-pill svg { width: 0.875rem; height: 0.875rem; } - + .status-pill span { display: none !important; } - + .header-action-btn { width: 2rem; height: 2rem; padding: 0.5rem; } - + .header-action-btn svg { width: 1rem; height: 1rem; } - - /* Tabs - Horizontal Scrollable */ + .tab-container { flex-direction: row !important; overflow-x: auto; @@ -563,7 +529,7 @@ body::after { -webkit-overflow-scrolling: touch; scroll-snap-type: x mandatory; } - + .tab-item { flex-shrink: 0; width: auto; @@ -572,7 +538,7 @@ body::after { font-size: 0.875rem; scroll-snap-align: start; } - + .glass-header nav { margin-top: 0.75rem; } @@ -582,58 +548,59 @@ body::after { .modern-header { padding: 0.5rem 0.75rem; } - + .header-logo-section h1 { font-size: 1.5rem !important; } - + .header-logo-section svg { width: 1.25rem !important; height: 1.25rem !important; } - + .header-actions { gap: 0.375rem; } - + .status-pill { padding: 0.25rem 0.375rem; } - + .header-action-btn { width: 1.75rem; height: 1.75rem; padding: 0.375rem; } - + .tab-container { padding: 0.375rem 0.5rem; gap: 0.25rem; } - + .tab-item { padding: 0.375rem 0.75rem; font-size: 0.8125rem; } } -/* Smooth scrolling for tabs */ +/* Scroll optimized tabs - no smooth scroll */ .tab-container { - scroll-behavior: smooth; + scroll-behavior: auto; -webkit-overflow-scrolling: touch; + contain: layout style; + transform: translate3d(0, 0, 0); } /* Ensure content is above background layers */ -body > * { +body>* { position: relative; z-index: 1; } /* ============================================ - MODAL SYSTEM - LIQUID GLASS STYLE + MODAL SYSTEM - OPTIMIZED ============================================ */ -/* Modal container - always on top - ESCAPE ALL STACKING CONTEXTS */ .fixed.inset-0.z-\[999999\], div.fixed.inset-0[id] { z-index: 999999 !important; @@ -646,12 +613,10 @@ div.fixed.inset-0[id] { pointer-events: auto !important; } -/* Modal content wrapper - ensure it's above backdrop */ div.fixed.inset-0[role="dialog"] { z-index: 1000000 !important; } -/* Modal glass content - FINAL LAYER */ .modal-glass-content { z-index: 1000001 !important; width: 550px !important; @@ -682,18 +647,16 @@ div.fixed.inset-0[role="dialog"] { } /* ============================================ - PERIOD MODAL STYLING - THEME FITTING + PERIOD MODAL STYLING - OPTIMIZED ============================================ */ .modal-period-content { - background: rgba(20, 10, 35, 0.5) !important; - backdrop-filter: blur(32px) saturate(180%) !important; - -webkit-backdrop-filter: blur(32px) saturate(180%) !important; + background: rgba(20, 10, 35, 0.6) !important; + backdrop-filter: blur(6px) !important; + -webkit-backdrop-filter: blur(6px) !important; border: 1px solid rgba(168, 85, 247, 0.4) !important; border-radius: 1.5rem !important; - box-shadow: 0 20px 60px rgba(168, 85, 247, 0.3), - inset 0 1px 0 rgba(255, 255, 255, 0.15), - 0 0 0 1px rgba(168, 85, 247, 0.2) !important; + box-shadow: 0 8px 32px rgba(168, 85, 247, 0.2) !important; padding: 0 !important; width: 550px !important; max-width: 550px !important; @@ -706,6 +669,10 @@ div.fixed.inset-0[role="dialog"] { box-sizing: border-box !important; scrollbar-width: thin; scrollbar-color: rgba(168, 85, 247, 0.5) transparent; + transform: translate3d(0, 0, 0); + contain: layout style; + will-change: scroll-position; + -webkit-overflow-scrolling: touch; } @media (max-width: 640px) { @@ -745,11 +712,10 @@ div.fixed.inset-0[role="dialog"] { } } -/* Header Section */ .modal-header-section { padding: 1.5rem 1.5rem 1.25rem 1.5rem; border-bottom: 1px solid rgba(168, 85, 247, 0.2); - background: linear-gradient(135deg, rgba(168, 85, 247, 0.15) 0%, rgba(192, 132, 252, 0.1) 100%); + background: rgba(168, 85, 247, 0.1); flex-shrink: 0; width: 100%; max-width: 100%; @@ -774,7 +740,7 @@ div.fixed.inset-0[role="dialog"] { font-size: 0.875rem; text-transform: uppercase; letter-spacing: 0.05em; - box-shadow: 0 4px 12px rgba(168, 85, 247, 0.2); + box-shadow: 0 2px 8px rgba(168, 85, 247, 0.15); } .modal-lesson-badge { @@ -793,7 +759,7 @@ div.fixed.inset-0[role="dialog"] { color: var(--neon-purple-bright); margin-top: 1rem; line-height: 1.3; - text-shadow: 0 0 20px rgba(168, 85, 247, 0.5); + text-shadow: 0 0 12px rgba(168, 85, 247, 0.4); word-break: break-word; overflow-wrap: break-word; word-wrap: break-word; @@ -818,7 +784,6 @@ div.fixed.inset-0[role="dialog"] { } } -/* Status Section */ .modal-status-section { padding: 1rem 1.5rem; display: flex; @@ -829,6 +794,10 @@ div.fixed.inset-0[role="dialog"] { overflow-y: auto; scrollbar-width: thin; scrollbar-color: rgba(168, 85, 247, 0.5) transparent; + contain: layout style; + transform: translate3d(0, 0, 0); + will-change: scroll-position; + -webkit-overflow-scrolling: touch; } .modal-status-section::-webkit-scrollbar { @@ -855,36 +824,36 @@ div.fixed.inset-0[role="dialog"] { border-radius: 0.75rem; border: 1px solid; font-weight: 700; - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); } .modal-status-red-700 { background: rgba(185, 28, 28, 0.2); border-color: rgba(239, 68, 68, 0.4); color: #fca5a5; - box-shadow: 0 4px 16px rgba(239, 68, 68, 0.2); + box-shadow: 0 2px 8px rgba(239, 68, 68, 0.15); } .modal-status-lime-400 { background: rgba(163, 230, 53, 0.15); border-color: rgba(163, 230, 53, 0.4); color: #d9f99d; - box-shadow: 0 4px 16px rgba(163, 230, 53, 0.2); + box-shadow: 0 2px 8px rgba(163, 230, 53, 0.15); } .modal-status-blue-400 { background: rgba(96, 165, 250, 0.2); border-color: rgba(96, 165, 250, 0.4); color: #93c5fd; - box-shadow: 0 4px 16px rgba(96, 165, 250, 0.2); + box-shadow: 0 2px 8px rgba(96, 165, 250, 0.15); } .modal-status-fuchsia-600 { background: rgba(192, 38, 211, 0.2); border-color: rgba(232, 121, 249, 0.4); color: #f0abfc; - box-shadow: 0 4px 16px rgba(232, 121, 249, 0.2); + box-shadow: 0 2px 8px rgba(232, 121, 249, 0.15); } .modal-status-icon { @@ -892,7 +861,7 @@ div.fixed.inset-0[role="dialog"] { height: 8px; border-radius: 50%; background: currentColor; - box-shadow: 0 0 8px currentColor; + box-shadow: 0 0 6px currentColor; flex-shrink: 0; } @@ -914,7 +883,6 @@ div.fixed.inset-0[role="dialog"] { padding-left: 1.5rem; } -/* Events Section */ .modal-events-section { padding: 1.5rem 1.5rem; border-top: 1px solid rgba(168, 85, 247, 0.15); @@ -935,17 +903,16 @@ div.fixed.inset-0[role="dialog"] { gap: 0.5rem; color: var(--neon-purple-bright); font-weight: 700; - font-size: 1.125rem; + font-size: 0.875rem; margin-bottom: 1rem; text-transform: uppercase; letter-spacing: 0.05em; - font-size: 0.875rem; } .modal-event-card { - background: rgba(30, 20, 50, 0.4); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + background: rgba(30, 20, 50, 0.5); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); border: 1px solid rgba(168, 85, 247, 0.2); border-radius: 1rem; padding: 1.25rem; @@ -989,7 +956,6 @@ div.fixed.inset-0[role="dialog"] { border-left: 2px solid rgba(168, 85, 247, 0.3); } -/* Details Section */ .modal-details-section { padding: 1.5rem 1.5rem; flex-shrink: 0; @@ -1031,7 +997,6 @@ div.fixed.inset-0[role="dialog"] { padding-left: 1.75rem; } -/* Close Button */ .modal-close-button { display: flex; align-items: center; @@ -1039,14 +1004,14 @@ div.fixed.inset-0[role="dialog"] { gap: 0.5rem; width: 100%; padding: 1rem; - background: linear-gradient(135deg, rgba(168, 85, 247, 0.2) 0%, rgba(192, 132, 252, 0.15) 100%); + background: rgba(168, 85, 247, 0.15); border: none; border-top: 1px solid rgba(168, 85, 247, 0.3); color: var(--neon-purple-bright); font-weight: 700; font-size: 1rem; cursor: pointer; - transition: all 0.3s ease; + transition: background-color 0.2s ease; flex-shrink: 0; margin-top: 0; border-radius: 0 0 1.5rem 1.5rem; @@ -1060,17 +1025,14 @@ div.fixed.inset-0[role="dialog"] { } .modal-close-button:hover { - background: linear-gradient(135deg, rgba(168, 85, 247, 0.3) 0%, rgba(192, 132, 252, 0.25) 100%); + background: rgba(168, 85, 247, 0.25); color: #fff; - box-shadow: 0 0 20px rgba(168, 85, 247, 0.4); - transform: translateY(-1px); } .modal-close-button:active { - transform: translateY(0); + background: rgba(168, 85, 247, 0.2); } -/* Specific modal IDs - timetable modals */ div[id^="Monday-"], div[id^="Tuesday-"], div[id^="Wednesday-"], @@ -1089,37 +1051,29 @@ div[id*="-googlecalendar"] { isolation: isolate !important; } -/* Modal backdrop with liquid glass effect */ div[id$="-bg"] { z-index: 999999 !important; - background: rgba(10, 10, 15, 0.75) !important; - backdrop-filter: blur(12px) saturate(180%) !important; - -webkit-backdrop-filter: blur(12px) saturate(180%) !important; - transition: opacity 0.3s ease !important; + background: rgba(10, 10, 15, 0.8) !important; + backdrop-filter: blur(4px) !important; + -webkit-backdrop-filter: blur(4px) !important; + transition: opacity 0.2s ease !important; } -/* Modal content with liquid glass styling */ .modal-glass-content { position: relative !important; z-index: 1000000 !important; - background: rgba(30, 20, 50, 0.4) !important; - backdrop-filter: blur(24px) saturate(180%) !important; - -webkit-backdrop-filter: blur(24px) saturate(180%) !important; + background: rgba(30, 20, 50, 0.5) !important; + backdrop-filter: blur(6px) !important; + -webkit-backdrop-filter: blur(6px) !important; border: 1px solid rgba(168, 85, 247, 0.3) !important; - box-shadow: 0 8px 32px rgba(168, 85, 247, 0.25), - inset 0 1px 0 rgba(255, 255, 255, 0.1), - 0 0 0 1px rgba(168, 85, 247, 0.1) !important; + box-shadow: 0 4px 20px rgba(168, 85, 247, 0.2) !important; border-radius: 1rem !important; margin: auto !important; width: fit-content !important; max-width: 90vw !important; + transform: translateZ(0); } -/* ============================================ - FORCE ALL BUTTONS BELOW MODALS - NUCLEAR OPTION - ============================================ */ - -/* NUCLEAR: Force ALL buttons to z-index 1 - NO EXCEPTIONS */ button, button *, #timetable-box button, @@ -1137,13 +1091,11 @@ button.relative * { isolation: auto !important; } -/* Override ANY z-index on buttons - even inline styles can't save you */ button[style*="z-index"], button[style*="z-index"] * { z-index: 1 !important; } -/* Override any z-index classes on buttons */ button.z-10, button.z-20, button.z-\[10\], @@ -1155,31 +1107,28 @@ div.z-20:not([id^="Monday-"]):not([id^="Tuesday-"]):not([id^="Wednesday-"]):not( z-index: 1 !important; } -/* Force glass-card elements that are buttons to stay low */ button.glass-card, button.glass-card * { z-index: 1 !important; position: relative !important; } -/* Prevent any stacking context from trapping modals */ .glass-header, main, .container { isolation: auto; } -/* Period Button Hover Effect - No layout shift */ .period-button-hover { box-shadow: 0 0 0 0 rgba(168, 85, 247, 0); } .period-button-hover:hover { - box-shadow: 0 4px 20px rgba(168, 85, 247, 0.2); + box-shadow: 0 2px 12px rgba(168, 85, 247, 0.15); } /* ============================================ - PERIOD STATUS OVERLAY - REDESIGNED + PERIOD STATUS OVERLAY - OPTIMIZED ============================================ */ .period-status-overlay { @@ -1194,8 +1143,8 @@ main, border-radius: 0.5rem; pointer-events: none; z-index: 5; - backdrop-filter: blur(8px); - -webkit-backdrop-filter: blur(8px); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); } .period-status-overlay-content { @@ -1212,16 +1161,14 @@ main, letter-spacing: 0.05em; font-size: 0.75rem; text-align: center; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3), - inset 0 1px 0 rgba(255, 255, 255, 0.2); + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3); } .period-status-icon { width: 12px; height: 12px; border-radius: 50%; - box-shadow: 0 0 12px currentColor, - 0 0 24px currentColor; + box-shadow: 0 0 8px currentColor; flex-shrink: 0; } @@ -1231,7 +1178,6 @@ main, max-width: 100%; } -/* Red - Cancelled (odwołane) */ .period-status-red-700 { background: rgba(185, 28, 28, 0.4); } @@ -1240,16 +1186,13 @@ main, background: rgba(185, 28, 28, 0.6); border-color: rgba(239, 68, 68, 0.8); color: #fca5a5; - box-shadow: 0 4px 16px rgba(239, 68, 68, 0.4), - inset 0 1px 0 rgba(255, 255, 255, 0.2), - 0 0 20px rgba(239, 68, 68, 0.3); + box-shadow: 0 2px 12px rgba(239, 68, 68, 0.3); } .period-status-red-700 .period-status-icon { background: #fca5a5; } -/* Lime - School Holiday (dzień wolny szkoły) */ .period-status-lime-400 { background: rgba(163, 230, 53, 0.3); } @@ -1258,16 +1201,13 @@ main, background: rgba(163, 230, 53, 0.5); border-color: rgba(217, 249, 157, 0.8); color: #ecfccb; - box-shadow: 0 4px 16px rgba(163, 230, 53, 0.4), - inset 0 1px 0 rgba(255, 255, 255, 0.3), - 0 0 20px rgba(163, 230, 53, 0.3); + box-shadow: 0 2px 12px rgba(163, 230, 53, 0.3); } .period-status-lime-400 .period-status-icon { background: #ecfccb; } -/* Blue - Moved (przesunięcie) */ .period-status-blue-400 { background: rgba(96, 165, 250, 0.3); } @@ -1276,16 +1216,13 @@ main, background: rgba(96, 165, 250, 0.5); border-color: rgba(147, 197, 253, 0.8); color: #dbeafe; - box-shadow: 0 4px 16px rgba(96, 165, 250, 0.4), - inset 0 1px 0 rgba(255, 255, 255, 0.2), - 0 0 20px rgba(96, 165, 250, 0.3); + box-shadow: 0 2px 12px rgba(96, 165, 250, 0.3); } .period-status-blue-400 .period-status-icon { background: #dbeafe; } -/* Fuchsia - Default */ .period-status-fuchsia-600 { background: rgba(192, 38, 211, 0.3); } @@ -1294,16 +1231,13 @@ main, background: rgba(192, 38, 211, 0.5); border-color: rgba(240, 171, 252, 0.8); color: #fae8ff; - box-shadow: 0 4px 16px rgba(232, 121, 249, 0.4), - inset 0 1px 0 rgba(255, 255, 255, 0.2), - 0 0 20px rgba(232, 121, 249, 0.3); + box-shadow: 0 2px 12px rgba(232, 121, 249, 0.3); } .period-status-fuchsia-600 .period-status-icon { background: #fae8ff; } -/* Responsive adjustments */ @media (max-width: 640px) { .period-status-overlay-content { padding: 0.5rem 0.75rem; @@ -1322,13 +1256,13 @@ main, } /* ============================================ - TIMETABLE NAVIGATION - REDESIGNED + TIMETABLE NAVIGATION - OPTIMIZED ============================================ */ .timetable-nav-container { - background: rgba(20, 10, 35, 0.4); - backdrop-filter: blur(20px) saturate(180%); - -webkit-backdrop-filter: blur(20px) saturate(180%); + background: rgba(20, 10, 35, 0.5); + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); border: 1px solid rgba(168, 85, 247, 0.3); border-radius: 1rem; padding: 1.25rem 1.5rem; @@ -1336,8 +1270,7 @@ main, display: flex; flex-direction: column; gap: 1.25rem; - box-shadow: 0 4px 20px rgba(168, 85, 247, 0.15), - inset 0 1px 0 rgba(255, 255, 255, 0.1); + box-shadow: 0 2px 12px rgba(168, 85, 247, 0.1); } @media (min-width: 768px) { @@ -1349,7 +1282,6 @@ main, } } -/* Calendar Input Section */ .timetable-calendar-input { flex: 0 1 auto; min-width: 0; @@ -1362,15 +1294,15 @@ main, .timetable-calendar-input-field { width: 100%; - background: rgba(30, 20, 50, 0.5); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + background: rgba(30, 20, 50, 0.6); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); border: 1px solid rgba(168, 85, 247, 0.3); border-radius: 0.75rem; padding: 0.75rem 1rem; color: rgba(255, 255, 255, 0.9); font-size: 0.9375rem; - transition: all 0.3s ease; + transition: border-color 0.2s ease, box-shadow 0.2s ease; outline: none; } @@ -1380,8 +1312,7 @@ main, .timetable-calendar-input-field:focus { border-color: rgba(192, 132, 252, 0.5); - box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.2), - 0 0 20px rgba(168, 85, 247, 0.15); + box-shadow: 0 0 0 2px rgba(168, 85, 247, 0.15); background: rgba(30, 20, 50, 0.7); } @@ -1392,7 +1323,6 @@ main, } } -/* Navigation Controls */ .timetable-nav-controls { display: flex; align-items: center; @@ -1408,25 +1338,24 @@ main, width: 3rem; height: 3rem; background: rgba(168, 85, 247, 0.2); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); border: 1px solid rgba(192, 132, 252, 0.3); border-radius: 0.75rem; color: var(--neon-purple-bright); cursor: pointer; - transition: all 0.3s ease; + transition: background-color 0.2s ease, border-color 0.2s ease; flex-shrink: 0; + transform: translateZ(0); } .timetable-nav-button:hover { background: rgba(168, 85, 247, 0.3); border-color: rgba(192, 132, 252, 0.5); - box-shadow: 0 0 20px rgba(168, 85, 247, 0.3); - transform: translateY(-2px); } .timetable-nav-button:active { - transform: translateY(0); + background: rgba(168, 85, 247, 0.25); } .timetable-nav-button:disabled { @@ -1442,7 +1371,6 @@ main, } } -/* Date Display */ .timetable-date-display { display: flex; align-items: center; @@ -1464,8 +1392,8 @@ main, align-items: center; gap: 0.75rem; background: rgba(168, 85, 247, 0.15); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); border: 1px solid rgba(192, 132, 252, 0.3); border-radius: 0.75rem; padding: 0.75rem 1.25rem; @@ -1473,27 +1401,26 @@ main, font-weight: 700; font-size: 0.9375rem; cursor: pointer; - transition: all 0.3s ease; + transition: background-color 0.2s ease, border-color 0.2s ease; + transform: translateZ(0); } .timetable-date-button:hover { background: rgba(168, 85, 247, 0.25); border-color: rgba(192, 132, 252, 0.5); - box-shadow: 0 0 20px rgba(168, 85, 247, 0.25); - transform: translateY(-1px); } .timetable-date-button:active { - transform: translateY(0); + background: rgba(168, 85, 247, 0.2); } .timetable-date-static { display: flex; align-items: center; gap: 0.75rem; - background: rgba(30, 20, 50, 0.4); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + background: rgba(30, 20, 50, 0.5); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); border: 1px solid rgba(168, 85, 247, 0.2); border-radius: 0.75rem; padding: 0.75rem 1.25rem; @@ -1509,7 +1436,7 @@ main, .timetable-date-reset-icon { opacity: 0.6; - transition: transform 0.3s ease; + transition: transform 0.2s ease; } .timetable-date-button:hover .timetable-date-reset-icon { @@ -1568,7 +1495,7 @@ main, } /* ============================================ - WEEKDAY HEADER - REDESIGNED + WEEKDAY HEADER - OPTIMIZED ============================================ */ .weekday-header-container { @@ -1588,18 +1515,18 @@ main, .weekday-header { width: 100%; height: 100%; - background: rgba(30, 20, 50, 0.3); - backdrop-filter: blur(15px) saturate(180%); - -webkit-backdrop-filter: blur(15px) saturate(180%); + background: rgba(30, 20, 50, 0.4); + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); border: 1px solid rgba(168, 85, 247, 0.25); border-radius: 0.75rem; padding: 0.5rem 0.875rem; - transition: all 0.3s ease; - box-shadow: 0 2px 8px rgba(168, 85, 247, 0.1), - inset 0 1px 0 rgba(255, 255, 255, 0.05); + transition: border-color 0.2s ease; + box-shadow: 0 1px 4px rgba(168, 85, 247, 0.1); display: flex; align-items: center; justify-content: center; + transform: translateZ(0); } .weekday-header-static { @@ -1627,12 +1554,10 @@ main, .weekday-header-button:hover { background: rgba(168, 85, 247, 0.15); border-color: rgba(192, 132, 252, 0.4); - box-shadow: 0 4px 16px rgba(168, 85, 247, 0.2), - inset 0 1px 0 rgba(255, 255, 255, 0.1); } .weekday-header-button:active { - transform: scale(0.98); + background: rgba(168, 85, 247, 0.1); } .weekday-header-content { @@ -1678,10 +1603,9 @@ main, position: absolute; inset: 0; background: rgba(239, 68, 68, 0.15); - backdrop-filter: blur(4px); border-radius: 0.75rem; opacity: 0; - transition: opacity 0.3s ease; + transition: opacity 0.2s ease; } .weekday-header-button:hover .weekday-event-glow { @@ -1696,22 +1620,11 @@ main, height: 6px; border-radius: 50%; background: #ef4444; - box-shadow: 0 0 8px rgba(239, 68, 68, 0.9), - 0 0 16px rgba(239, 68, 68, 0.6); - animation: weekdayPulse 2s ease-in-out infinite; + box-shadow: 0 0 6px rgba(239, 68, 68, 0.8); z-index: 3; } -@keyframes weekdayPulse { - 0%, 100% { - opacity: 1; - transform: scale(1); - } - 50% { - opacity: 0.8; - transform: scale(1.15); - } -} +/* Removed weekdayPulse animation - too expensive */ @media (max-width: 640px) { .weekday-header-container { @@ -1739,18 +1652,16 @@ main, } /* ============================================ - WEEKDAY EVENTS MODAL - REDESIGNED + WEEKDAY EVENTS MODAL - OPTIMIZED ============================================ */ .weekday-events-modal-content { - background: rgba(20, 10, 35, 0.5) !important; - backdrop-filter: blur(32px) saturate(180%) !important; - -webkit-backdrop-filter: blur(32px) saturate(180%) !important; + background: rgba(20, 10, 35, 0.6) !important; + backdrop-filter: blur(6px) !important; + -webkit-backdrop-filter: blur(6px) !important; border: 1px solid rgba(168, 85, 247, 0.4) !important; border-radius: 1.5rem !important; - box-shadow: 0 20px 60px rgba(168, 85, 247, 0.3), - inset 0 1px 0 rgba(255, 255, 255, 0.15), - 0 0 0 1px rgba(168, 85, 247, 0.2) !important; + box-shadow: 0 8px 32px rgba(168, 85, 247, 0.2) !important; padding: 0 !important; width: 100% !important; max-width: 550px !important; @@ -1762,6 +1673,10 @@ main, box-sizing: border-box !important; scrollbar-width: thin; scrollbar-color: rgba(168, 85, 247, 0.5) transparent; + transform: translate3d(0, 0, 0); + contain: layout style; + will-change: scroll-position; + -webkit-overflow-scrolling: touch; } .weekday-events-modal-content::-webkit-scrollbar { @@ -1773,11 +1688,10 @@ main, border-radius: 3px; } -/* Header */ .weekday-events-header { padding: 2rem 2rem 1.5rem 2rem; border-bottom: 1px solid rgba(168, 85, 247, 0.2); - background: linear-gradient(135deg, rgba(168, 85, 247, 0.15) 0%, rgba(192, 132, 252, 0.1) 100%); + background: rgba(168, 85, 247, 0.1); border-radius: 1.5rem 1.5rem 0 0; flex-shrink: 0; display: flex; @@ -1796,7 +1710,7 @@ main, font-weight: 800; color: var(--neon-purple-bright); margin: 0; - text-shadow: 0 0 20px rgba(168, 85, 247, 0.5); + text-shadow: 0 0 12px rgba(168, 85, 247, 0.4); } .weekday-events-date { @@ -1806,7 +1720,6 @@ main, padding-left: 2.5rem; } -/* Sections */ .weekday-events-section { padding: 1.5rem 2rem; border-top: 1px solid rgba(168, 85, 247, 0.15); @@ -1831,20 +1744,18 @@ main, gap: 1rem; } -/* Event Cards */ .weekday-event-card { - background: rgba(30, 20, 50, 0.4); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + background: rgba(30, 20, 50, 0.5); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); border: 1px solid rgba(168, 85, 247, 0.2); border-radius: 1rem; padding: 1.25rem; - transition: all 0.3s ease; + transition: border-color 0.2s ease; } .weekday-event-card:hover { border-color: rgba(192, 132, 252, 0.4); - box-shadow: 0 4px 16px rgba(168, 85, 247, 0.2); } .weekday-event-schedule { @@ -1919,7 +1830,6 @@ main, margin-bottom: 0; } -/* Empty State */ .weekday-events-empty { padding: 3rem 2rem; display: flex; @@ -1941,7 +1851,6 @@ main, color: rgba(192, 132, 252, 0.6); } -/* Close Button */ .weekday-events-close-button { display: flex; align-items: center; @@ -1949,28 +1858,26 @@ main, gap: 0.5rem; width: 100%; padding: 1rem; - background: linear-gradient(135deg, rgba(168, 85, 247, 0.2) 0%, rgba(192, 132, 252, 0.15) 100%); + background: rgba(168, 85, 247, 0.15); border: none; border-top: 1px solid rgba(168, 85, 247, 0.3); color: var(--neon-purple-bright); font-weight: 700; font-size: 1rem; cursor: pointer; - transition: all 0.3s ease; + transition: background-color 0.2s ease; flex-shrink: 0; margin-top: auto; border-radius: 0 0 1.5rem 1.5rem; } .weekday-events-close-button:hover { - background: linear-gradient(135deg, rgba(168, 85, 247, 0.3) 0%, rgba(192, 132, 252, 0.25) 100%); + background: rgba(168, 85, 247, 0.25); color: #fff; - box-shadow: 0 0 20px rgba(168, 85, 247, 0.4); - transform: translateY(-1px); } .weekday-events-close-button:active { - transform: translateY(0); + background: rgba(168, 85, 247, 0.2); } @media (max-width: 640px) { @@ -2012,9 +1919,7 @@ main, TIMETABLE MOBILE OPTIMIZATIONS ============================================ */ -/* Mobile Timetable Layout - Fix Dual Scroll Issue */ @media (max-width: 768px) { - /* Main timetable container - single scroll */ #timetable-box { position: relative !important; height: auto !important; @@ -2023,19 +1928,16 @@ main, display: flex !important; flex-direction: column !important; } - - /* Timeline column - hide on mobile (first child div with specific classes) */ - #timetable-box > div[class*="flex"][class*="flex-col"][class*="flex-shrink-0"] { + + #timetable-box>div[class*="flex"][class*="flex-col"][class*="flex-shrink-0"] { display: none !important; } - - /* Horizontal grid lines - hide on mobile (absolute positioned div) */ - #timetable-box > div[class*="absolute"][class*="left-0"][class*="right-0"][class*="pointer-events-none"] { + + #timetable-box>div[class*="absolute"][class*="left-0"][class*="right-0"][class*="pointer-events-none"] { display: none !important; } - - /* Weekdays container - remove absolute positioning, make it scrollable */ - #timetable-box > div[class*="overflow-x-auto"][class*="absolute"] { + + #timetable-box>div[class*="overflow-x-auto"][class*="absolute"] { position: relative !important; left: 0 !important; right: 0 !important; @@ -2048,98 +1950,92 @@ main, padding-left: 0.5rem !important; padding-right: 0.5rem !important; } - - /* Weekday columns - adjust width for mobile */ - #timetable-box > div[class*="overflow-x-auto"] > div { + + #timetable-box>div[class*="overflow-x-auto"]>div { width: 85vw !important; min-width: 85vw !important; max-width: 85vw !important; flex-shrink: 0 !important; } - - /* Period buttons - make more compact on mobile */ + #timetable-box .period button, #timetable-box .glass-card { padding: 0.5rem 0.75rem !important; font-size: 0.8125rem !important; } - - /* Weekday headers - more compact */ + .weekday-header-container { height: 2.5rem !important; } - + .weekday-header { padding: 0.375rem 0.5rem !important; font-size: 0.75rem !important; } - + .weekday-name { font-size: 0.75rem !important; } - + .weekday-date { font-size: 0.625rem !important; } } @media (max-width: 480px) { - /* Extra small screens - even more compact */ - #timetable-box > div.overflow-x-auto > div { + #timetable-box>div.overflow-x-auto>div { width: 90vw !important; min-width: 90vw !important; max-width: 90vw !important; } - - /* Period buttons - smaller */ + #timetable-box .period button, #timetable-box .glass-card { padding: 0.375rem 0.5rem !important; font-size: 0.75rem !important; } - + .weekday-header-container { height: 2.25rem !important; } - + .weekday-header { padding: 0.25rem 0.375rem !important; } - + .weekday-name { font-size: 0.6875rem !important; } - + .weekday-date { font-size: 0.5625rem !important; } - - /* Timetable navigation - more compact */ + .timetable-nav-container { flex-direction: column !important; gap: 0.75rem !important; padding: 0.75rem !important; } - + .timetable-calendar-input-field { font-size: 0.8125rem !important; padding: 0.5rem 0.625rem !important; } - + .timetable-nav-controls { gap: 0.5rem !important; } - + .timetable-nav-button { width: 2.5rem !important; height: 2.5rem !important; } - + .timetable-date-display { min-width: auto !important; padding-left: 2rem !important; } - + .timetable-date-text { font-size: 0.75rem !important; } @@ -2150,11 +2046,11 @@ main, ============================================ */ .stat-tab-button { - background: rgba(30, 20, 50, 0.3); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + background: rgba(30, 20, 50, 0.4); + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); border: 1px solid rgba(168, 85, 247, 0.2); - transition: all 0.3s ease; + transition: background-color 0.2s ease, border-color 0.2s ease; } .stat-tab-button:hover { @@ -2166,7 +2062,7 @@ main, background: rgba(168, 85, 247, 0.25); border-color: rgba(192, 132, 252, 0.5); color: var(--neon-purple-bright); - box-shadow: 0 0 20px rgba(168, 85, 247, 0.2); + box-shadow: 0 0 12px rgba(168, 85, 247, 0.15); } /* This file is for your main application CSS */ diff --git a/assets/js/hooks/active_tab_hook.js b/assets/js/hooks/active_tab_hook.js index e548e5d..64b042b 100644 --- a/assets/js/hooks/active_tab_hook.js +++ b/assets/js/hooks/active_tab_hook.js @@ -24,6 +24,9 @@ const ActiveTab = { if (this.scrollTimeout) { clearTimeout(this.scrollTimeout); } + if (this.throttleTimeout) { + clearTimeout(this.throttleTimeout); + } }, initScrollCollapse() { @@ -38,8 +41,18 @@ const ActiveTab = { const SCROLL_DOWN_THRESHOLD = 50; // Minimum pixels scrolled down to trigger collapse const SCROLL_UP_THRESHOLD = 30; // Minimum pixels scrolled up to trigger expand const MIN_SCROLL_DISTANCE = 80; // Only start collapsing after 80px total scroll + const THROTTLE_DELAY = 100; // Throttle scroll handler more aggressively this.scrollHandler = () => { + // Additional throttling on top of requestAnimationFrame + if (this.throttleTimeout) { + return; + } + + this.throttleTimeout = setTimeout(() => { + this.throttleTimeout = null; + }, THROTTLE_DELAY); + if (!ticking) { window.requestAnimationFrame(() => { const scrollTop = window.pageYOffset || document.documentElement.scrollTop; diff --git a/lib/elixirus_web/live/student_live/subjects.ex b/lib/elixirus_web/live/student_live/subjects.ex index ed71362..cb22d98 100644 --- a/lib/elixirus_web/live/student_live/subjects.ex +++ b/lib/elixirus_web/live/student_live/subjects.ex @@ -203,6 +203,8 @@ defmodule ElixirusWeb.StudentLive.Subjects do end def handle_async(task, {:exit, _reason}, socket) when task in @asyncs do + socket = socket + |> assign(:loadings, List.delete(socket.assigns.loadings || [], task)) {:noreply, socket} end @@ -238,12 +240,15 @@ defmodule ElixirusWeb.StudentLive.Subjects do |> assign(:shown_grades, shown) |> assign(:semester_grades, semester_grades) |> assign_averages(current) + |> assign(:loadings, List.delete(socket.assigns.loadings, :load_grades)) {:token_error, _message, socket} -> socket + |> assign(:loadings, List.delete(socket.assigns.loadings, :load_grades)) {:error, _message, socket} -> socket + |> assign(:loadings, List.delete(socket.assigns.loadings, :load_grades)) end {:noreply, socket} @@ -272,6 +277,12 @@ defmodule ElixirusWeb.StudentLive.Subjects do |> assign(:shown_grades, %{}) |> assign(:query_params, @default_params) |> assign(:page_title, "Subjects") + |> assign(:loadings, []) + |> assign(:query, @default_params.query) + |> assign(:grade_query, @default_params.grade_query) + |> assign(:hide_empty, false) + |> assign(:sort_grades, @default_params.sort_grades) + |> assign(:grade_averages, %{}) socket = case data do @@ -279,6 +290,7 @@ defmodule ElixirusWeb.StudentLive.Subjects do case Hammer.check_rate("grades:#{user_id}", 60_000, 5) do {:allow, _count} -> socket + |> assign(:loadings, [:load_grades]) |> start_async(:load_grades, fn -> fetch_all_grades(client, semester) end) {:deny, _limit} ->