feat: add customizable Nitro client loading overlay with Filament settings

Add full Client Login Effect section to Theme & Buttons page with:
- Enable toggle, 30+ animation effects, customizable colors/logo/text
- 6 loading bar styles (sliding, dots, pulse, double, spinner, skeleton)
- Optimized to single DB query via WebsiteSetting::whereIn
- Overlay covers Nitro v3 internal loading (5s min, 15s fallback)
This commit is contained in:
root
2026-05-22 21:09:33 +02:00
parent c53d1bca45
commit 76bce1d092
5 changed files with 875 additions and 31 deletions
@@ -369,11 +369,289 @@
pointer-events: none;
}
@keyframes djPopupPulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(1.02); }
}
</style>
@keyframes djPopupPulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(1.02); }
}
{{-- Client Loading Overlay --}}
@if($loginData)
@php $ic = $loginData['icon_color']; $bg = $loginData['background']; @endphp
.client-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10001;
display: flex;
align-items: center;
justify-content: center;
background: {{ $bg }};
transition: opacity 0.6s ease, transform 0.6s ease;
}
.client-overlay.hidden {
opacity: 0;
transform: scale(1.1);
pointer-events: none;
}
.client-overlay-content {
text-align: center;
}
.client-overlay-icon {
width: {{ $loginData['icon_size'] }}px;
height: {{ $loginData['icon_size'] }}px;
margin: 0 auto 24px;
background: {{ $ic }};
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
color: {{ $loginData['text_color'] }};
box-shadow: 0 0 40px {{ $ic }}44;
}
.client-overlay-letter {
font-size: 52px;
font-weight: 800;
line-height: 1;
}
.client-overlay-img {
width: 80px;
height: auto;
object-fit: contain;
}
.client-overlay-text {
font-size: 28px;
font-weight: 700;
color: {{ $loginData['text_color'] }};
margin-bottom: 16px;
letter-spacing: 2px;
}
.client-overlay-subtitle {
font-size: 14px;
color: {{ $loginData['text_color'] }}99;
margin-bottom: 24px;
}
.client-overlay-loader {
margin: 24px auto 0;
position: relative;
}
@keyframes clientBar {
0% { left: -40%; }
100% { left: 100%; }
}
@keyframes clientDots {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
@keyframes clientPulseLoad {
0%, 100% { transform: scale(1); opacity: 0.5; }
50% { transform: scale(1.3); opacity: 1; }
}
@keyframes clientSpinLoad {
to { transform: rotate(360deg); }
}
@keyframes clientSkeleton {
0% { background-position: -200px 0; }
100% { background-position: calc(200px + 100%) 0; }
}
.client-bar-bar {
width: 200px;
height: 4px;
background: rgba(255,255,255,0.1);
border-radius: 4px;
overflow: hidden;
position: relative;
}
.client-bar-bar::after {
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 40%;
background: {{ $loginData['bar_color'] }};
border-radius: 4px;
animation: clientBar 1.2s ease-in-out infinite;
}
.client-bar-dots {
display: flex;
gap: 8px;
justify-content: center;
align-items: center;
height: 20px;
}
.client-bar-dots span {
width: 12px;
height: 12px;
background: {{ $loginData['bar_color'] }};
border-radius: 50%;
display: inline-block;
animation: clientDots 1.4s ease-in-out infinite both;
}
.client-bar-dots span:nth-child(1) { animation-delay: -0.32s; }
.client-bar-dots span:nth-child(2) { animation-delay: -0.16s; }
.client-bar-dots span:nth-child(3) { animation-delay: 0s; }
.client-bar-pulse {
width: 24px;
height: 24px;
margin: 0 auto;
background: {{ $loginData['bar_color'] }};
border-radius: 50%;
animation: clientPulseLoad 1.5s ease-in-out infinite;
}
.client-bar-double {
width: 200px;
height: 4px;
position: relative;
margin: 0 auto;
}
.client-bar-double::before,
.client-bar-double::after {
content: '';
position: absolute;
height: 4px;
border-radius: 4px;
background: {{ $loginData['bar_color'] }}66;
}
.client-bar-double::before {
width: 100%;
top: 0;
animation: clientBar 2s ease-in-out infinite;
}
.client-bar-double::after {
width: 60%;
top: 10px;
animation: clientBar 2s ease-in-out infinite reverse;
}
.client-bar-spinner {
width: 30px;
height: 30px;
margin: 0 auto;
border: 3px solid rgba(255,255,255,0.1);
border-top-color: {{ $loginData['bar_color'] }};
border-radius: 50%;
animation: clientSpinLoad 0.8s linear infinite;
}
.client-bar-skeleton {
width: 200px;
height: 14px;
margin: 0 auto;
background: rgba(255,255,255,0.06);
border-radius: 8px;
position: relative;
overflow: hidden;
}
.client-bar-skeleton::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, {{ $loginData['bar_color'] }}33, transparent);
background-size: 200px 100%;
animation: clientSkeleton 1.5s ease-in-out infinite;
}
{{-- Animation Effects --}}
.client-effect-sparkle .client-overlay-icon { animation: clientSparkle 1.5s ease-in-out infinite; }
@keyframes clientSparkle {
0%, 100% { transform: scale(1) rotate(0deg); box-shadow: 0 0 20px {{ $ic }}44; }
50% { transform: scale(1.15) rotate(10deg); box-shadow: 0 0 60px {{ $ic }}88; }
}
.client-effect-glow .client-overlay-icon { animation: clientGlow 2s ease-in-out infinite; }
@keyframes clientGlow {
0%, 100% { box-shadow: 0 0 20px {{ $ic }}44; }
50% { box-shadow: 0 0 80px {{ $ic }}cc, 0 0 120px {{ $ic }}66; }
}
.client-effect-float .client-overlay-icon { animation: clientFloat 3s ease-in-out infinite; }
@keyframes clientFloat {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-20px); }
}
.client-effect-bounce .client-overlay-icon { animation: clientBounce 1s ease-in-out infinite; }
@keyframes clientBounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-30px); }
}
.client-effect-pulse .client-overlay-icon { animation: clientPulse 1.5s ease-in-out infinite; }
@keyframes clientPulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
.client-effect-shine .client-overlay-icon { overflow: hidden; position: relative; }
.client-effect-shine .client-overlay-icon::after {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(45deg, transparent 40%, rgba(255,255,255,0.3) 50%, transparent 60%);
animation: clientShine 2s ease-in-out infinite;
}
@keyframes clientShine {
0% { transform: translateX(-100%) rotate(45deg); }
100% { transform: translateX(100%) rotate(45deg); }
}
.client-effect-heartbeat .client-overlay-icon { animation: clientHeartbeat 1.5s ease-in-out infinite; }
@keyframes clientHeartbeat {
0%, 100% { transform: scale(1); }
15% { transform: scale(1.2); }
30% { transform: scale(1); }
45% { transform: scale(1.15); }
60% { transform: scale(1); }
}
.client-effect-rotate .client-overlay-icon { animation: clientRotate 2s linear infinite; }
@keyframes clientRotate {
to { transform: rotate(360deg); }
}
.client-effect-color-cycle .client-overlay-icon { animation: clientColorCycle 3s linear infinite; }
@keyframes clientColorCycle {
0% { background: {{ $ic }}; }
25% { background: #ef4444; }
50% { background: #22c55e; }
75% { background: #3b82f6; }
100% { background: {{ $ic }}; }
}
.client-effect-neon-pulse .client-overlay-icon { animation: clientNeonPulse 1.5s ease-in-out infinite; }
@keyframes clientNeonPulse {
0%, 100% { box-shadow: 0 0 20px {{ $ic }}, 0 0 40px {{ $ic }}44; }
50% { box-shadow: 0 0 60px {{ $ic }}, 0 0 100px {{ $ic }}88, 0 0 140px {{ $ic }}44; }
}
.client-effect-rainbow .client-overlay-icon { animation: clientRainbow 4s linear infinite; }
@keyframes clientRainbow {
0% { filter: hue-rotate(0deg); }
100% { filter: hue-rotate(360deg); }
}
.client-effect-fire .client-overlay-icon { animation: clientFire 0.5s ease-in-out infinite alternate; }
@keyframes clientFire {
0% { transform: scale(1); box-shadow: 0 0 30px #ff4500, 0 0 60px #ff8c00; background: #ff4500; }
100% { transform: scale(1.05); box-shadow: 0 0 50px #ff8c00, 0 0 80px #ff4500; background: #ff6347; }
}
.client-effect-glitch .client-overlay-content { animation: clientGlitchText 0.3s ease-in-out infinite alternate; }
@keyframes clientGlitchText {
0% { transform: translate(0); }
25% { transform: translate(-3px, 2px); }
50% { transform: translate(3px, -2px); }
75% { transform: translate(-2px, -1px); }
100% { transform: translate(2px, 1px); }
}
.client-effect-pop .client-overlay-icon { animation: clientPop 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite; }
@keyframes clientPop {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.3); }
}
@endif
</style>
</head>
<body class="overflow-hidden" id="nitro-client">
@@ -479,14 +757,67 @@
{{-- Nitro Client --}}
@php
$nitroUrl = sprintf('%s/index.html?sso=%s', setting('nitro_path'), $sso);
$toolbarParams = [];
// Toolbar with fallback to button colors (don't URL encode - # is valid in URLs)
$toolbarParams[] = 'toolbar_primary=' . (setting('toolbar_primary_color') ?: setting('button_primary_color', '#eeb425'));
$toolbarParams[] = 'toolbar_hover=' . (setting('toolbar_hover_color') ?: setting('button_hover_color', '#cf9d15'));
$toolbarParams[] = 'toolbar_border=' . (setting('toolbar_border_color') ?: setting('button_border_color', '#cf9d15'));
$toolbarParams[] = 'toolbar_text=' . (setting('toolbar_text_color') ?: setting('button_text_color', '#1a1a2e'));
if(count($toolbarParams)) $nitroUrl .= '&' . implode('&', $toolbarParams);
$toolbarParams = [];
// Toolbar with fallback to button colors (don't URL encode - # is valid in URLs)
$toolbarParams[] = 'toolbar_primary=' . (setting('toolbar_primary_color') ?: setting('button_primary_color', '#eeb425'));
$toolbarParams[] = 'toolbar_hover=' . (setting('toolbar_hover_color') ?: setting('button_hover_color', '#cf9d15'));
$toolbarParams[] = 'toolbar_border=' . (setting('toolbar_border_color') ?: setting('button_border_color', '#cf9d15'));
$toolbarParams[] = 'toolbar_text=' . (setting('toolbar_text_color') ?: setting('button_text_color', '#1a1a2e'));
if(count($toolbarParams)) $nitroUrl .= '&' . implode('&', $toolbarParams);
@endphp
{{-- Client Loading Overlay --}}
@if($loginData)
@php $cmsLogo = setting('cms_logo'); $anim = $loginData['animation']; @endphp
<div id="clientOverlay" class="client-overlay client-effect-{{ $anim }}">
<div class="client-overlay-content">
<div class="client-overlay-icon">
@if($loginData['show_logo'] && $cmsLogo)
<img src="{{ $cmsLogo }}" alt="{{ setting('hotel_name') }}" class="client-overlay-img">
@else
<span class="client-overlay-letter">{{ substr(setting('hotel_name'), 0, 1) }}</span>
@endif
</div>
@if($loginData['show_name'])
<div class="client-overlay-text">{{ setting('hotel_name') }}</div>
@endif
@if($loginData['custom_text'])
<div class="client-overlay-subtitle">{{ $loginData['custom_text'] }}</div>
@endif
<div class="client-overlay-loader">
@if($loginData['bar_style'] === 'dots')
<div class="client-bar-dots"><span></span><span></span><span></span></div>
@else
<div class="client-bar-{{ $loginData['bar_style'] }}"></div>
@endif
</div>
</div>
</div>
<script>
(function() {
var overlay = document.getElementById('clientOverlay');
var iframe = document.getElementById('nitro');
var startTime = Date.now();
var hidden = false;
function hideOverlay() {
if (!hidden) {
hidden = true;
overlay.classList.add('hidden');
setTimeout(function() { overlay.remove(); }, 700);
}
}
if (iframe) {
iframe.addEventListener('load', function() {
var elapsed = Date.now() - startTime;
var minWait = Math.max(0, 5000 - elapsed);
setTimeout(hideOverlay, minWait);
});
}
setTimeout(hideOverlay, 15000);
})();
</script>
@endif
<iframe id="nitro" src="{{ $nitroUrl }}"
class="absolute top-0 left-0 m-0 h-full w-full overflow-hidden border-none p-0"></iframe>
@@ -674,12 +1005,13 @@
setInterval(updateRadioInfo, 10000);
// Init
window.addEventListener('DOMContentLoaded', () => {
loadRadio();
updateRadioInfo();
});
</script>
// Init
window.addEventListener('DOMContentLoaded', () => {
loadRadio();
updateRadioInfo();
});
</script>
<script src="{{ asset('assets/js/atom.js') }}"></script>
</body>