librelad 875a60f90f LibrePortal v0.1.0 — initial release
A free, open, self-hosted app platform (GNU AGPLv3): one-click app deploys,
Traefik reverse proxy with automatic SSL, rootless Docker support, gluetun
VPN routing, and a web dashboard to manage it all.

Free & open forever to self-host; optional paid hosted services fund it.
See PROMISE.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-21 20:37:54 +01:00

254 lines
7.5 KiB
CSS

/*
Shared "aurora" background — cool sparkly-blue swirling water.
Applied to both the login overlay and the loading screen so they share
one visual identity.
Usage: a container element gets `.aurora-bg`. Two pseudo-element layers
paint:
::before — slowly rotating conic-gradient swirl
::after — drifting radial-gradient "blobs"
Plus a separate `.aurora-stars` overlay child for sparkle twinkles
(kept as a real element so its animation can stack on top of pseudo
layers without z-index gymnastics).
*/
.aurora-bg {
background:
radial-gradient(ellipse at 20% 30%, var(--gradient-mid) 0%, transparent 55%),
radial-gradient(ellipse at 80% 70%, var(--gradient-to) 0%, transparent 55%),
linear-gradient(135deg, var(--gradient-from) 0%, var(--gradient-from) 40%, var(--gradient-mid) 100%);
overflow: hidden;
isolation: isolate;
}
/* Pair with `.aurora-bg` only if the host element isn't already
positioned. Login overlay and loading screen are both `position: fixed`
so the pseudo-elements anchor correctly without us overriding their
positioning. */
/* Slow conic swirl — the "current" of the water */
.aurora-bg::before {
content: '';
position: absolute;
inset: -25%;
background: conic-gradient(
from 0deg at 50% 50%,
rgba(var(--accent-rgb), 0.0) 0deg,
rgba(var(--accent-rgb), 0.18) 60deg,
rgba(var(--accent-rgb), 0.22) 130deg,
rgba(var(--accent-rgb), 0.20) 200deg,
rgba(var(--accent-rgb), 0.18) 280deg,
rgba(var(--accent-rgb), 0.0) 360deg
);
filter: blur(60px);
animation: auroraSpin 38s linear infinite;
z-index: -2;
pointer-events: none;
}
/* Drifting glow blobs — the "sparkly" volumes */
.aurora-bg::after {
content: '';
position: absolute;
inset: -10%;
background:
radial-gradient(circle at 18% 22%, rgba(var(--accent-rgb), 0.45) 0%, transparent 35%),
radial-gradient(circle at 78% 18%, rgba(var(--accent-rgb), 0.40) 0%, transparent 32%),
radial-gradient(circle at 30% 78%, rgba(var(--accent-rgb), 0.38) 0%, transparent 38%),
radial-gradient(circle at 82% 80%, rgba(var(--accent-rgb), 0.45) 0%, transparent 36%),
radial-gradient(circle at 50% 50%, rgba(var(--accent-rgb), 0.18) 0%, transparent 50%);
filter: blur(40px);
animation: auroraDrift 22s ease-in-out infinite alternate;
z-index: -1;
pointer-events: none;
}
@keyframes auroraSpin {
to { transform: rotate(360deg); }
}
@keyframes auroraDrift {
0% { transform: translate(0, 0) scale(1); opacity: 0.85; }
50% { transform: translate(-3%, 4%) scale(1.08); opacity: 1; }
100% { transform: translate(2%, -3%) scale(0.95); opacity: 0.9; }
}
/*
Sparkles. We render a tiny tiling of radial-gradient dots and
modulate opacity so they twinkle. Two staggered layers feel less
uniform than one.
*/
.aurora-stars {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 0;
}
.aurora-stars::before,
.aurora-stars::after {
content: '';
position: absolute;
inset: 0;
background-repeat: repeat;
}
.aurora-stars::before {
background-image:
radial-gradient(1.5px 1.5px at 12px 18px, rgba(var(--text-rgb),0.9), transparent 60%),
radial-gradient(1px 1px at 47px 92px, rgba(var(--accent-rgb),0.85), transparent 60%),
radial-gradient(1.2px 1.2px at 110px 40px, rgba(var(--text-rgb),0.75), transparent 60%),
radial-gradient(1px 1px at 165px 130px, rgba(var(--accent-rgb),0.7), transparent 60%);
background-size: 200px 200px;
animation: auroraTwinkleA 4.5s ease-in-out infinite;
}
.aurora-stars::after {
background-image:
radial-gradient(1px 1px at 30px 60px, rgba(var(--accent-rgb),0.8), transparent 60%),
radial-gradient(1.4px 1.4px at 88px 22px, rgba(var(--text-rgb),0.7), transparent 60%),
radial-gradient(1px 1px at 140px 100px, rgba(var(--accent-rgb),0.85), transparent 60%),
radial-gradient(1.2px 1.2px at 195px 70px, rgba(var(--text-rgb),0.6), transparent 60%);
background-size: 240px 240px;
background-position: 80px 50px;
animation: auroraTwinkleB 6.5s ease-in-out infinite;
}
@keyframes auroraTwinkleA {
0%, 100% { opacity: 0.55; }
50% { opacity: 1; }
}
@keyframes auroraTwinkleB {
0%, 100% { opacity: 1; }
50% { opacity: 0.45; }
}
/* Children of an aurora surface should sit above the FX layers */
.aurora-bg > :not(.aurora-stars) {
position: relative;
z-index: 2;
}
/* Shared header used by both the loading screen and the login overlay
so the two surfaces have identical branding. */
.aurora-header {
text-align: center;
margin-bottom: 2.5rem;
}
.aurora-logo {
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
}
.aurora-logo img {
width: 48px;
height: 48px;
/* Fade in once the SVG actually loads — avoids the brief broken-image
flash on first paint. The .loaded class is added by the inline onload
handler on the img tag in each surface that uses this header. */
opacity: 0;
transition: opacity 0.45s ease;
}
.aurora-logo img.loaded {
opacity: 1;
}
.aurora-logo h1 {
font-size: 3rem;
font-weight: 700;
margin: 0;
background: linear-gradient(45deg, var(--accent), var(--accent-hover));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
letter-spacing: -0.01em;
}
.aurora-subtitle {
margin: 0.6rem 0 0 0;
font-size: 1.05rem;
font-weight: 300;
color: var(--text-secondary);
font-style: italic;
letter-spacing: 0.01em;
}
/* Shrink the header on phones — 3rem title + 48px icon swallow the
viewport on small screens. */
@media (max-width: 480px) {
.aurora-header {
margin-bottom: 1.5rem;
}
.aurora-logo {
gap: 10px;
}
.aurora-logo img {
width: 36px;
height: 36px;
}
.aurora-logo h1 {
font-size: 2.1rem;
}
.aurora-subtitle {
font-size: 0.9rem;
margin-top: 0.4rem;
padding: 0 12px;
}
}
/* Respect reduced-motion — drop the animations but keep the gradient */
@media (prefers-reduced-motion: reduce) {
.aurora-bg::before,
.aurora-bg::after,
.aurora-stars::before,
.aurora-stars::after {
animation: none;
}
}
/*
Static modifier — pair with `.aurora-bg` to keep the gradient identity
but drop the per-frame work that was making login / loading / setup
sluggish on lower-power systems. We kill the animations only — the
blur filter is kept because, without the rotation, the blur layer
rasterises exactly once at first paint and then just composites each
frame for free. Removing the blur entirely created visible banding
and a dark seam where the conic gradient wraps from 360° back to 0°.
*/
.aurora-bg.aurora-static::before,
.aurora-bg.aurora-static::after,
.aurora-bg.aurora-static .aurora-stars::before,
.aurora-bg.aurora-static .aurora-stars::after {
animation: none !important;
}
/* dark-blue and light themes get a flat, solid loading/login surface
— the cyan swirl + glow blobs + stars belong to the Nebula identity. */
html[data-theme="dark-blue"] .aurora-bg::before,
html[data-theme="dark-blue"] .aurora-bg::after,
html[data-theme="dark-blue"] .aurora-stars::before,
html[data-theme="dark-blue"] .aurora-stars::after,
html[data-theme="light"] .aurora-bg::before,
html[data-theme="light"] .aurora-bg::after,
html[data-theme="light"] .aurora-stars::before,
html[data-theme="light"] .aurora-stars::after {
display: none;
}
/* Same suppression on the body-level layers Nebula uses. */
html[data-theme="dark-blue"]::before,
html[data-theme="dark-blue"]::after,
html[data-theme="light"]::before,
html[data-theme="light"]::after {
display: none;
}