added background to panel itself
This commit is contained in:
parent
87173ae43f
commit
f7b3af48de
8 changed files with 122 additions and 58 deletions
46
cmd/telesrv-admin/web/src/components/AppBackground.tsx
Normal file
46
cmd/telesrv-admin/web/src/components/AppBackground.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { useEffect, useRef } from "react";
|
||||
import { BgIcons } from "./BgIcons";
|
||||
|
||||
// The product's background: three blurred orbs that drift with the pointer,
|
||||
// and the field of slowly moving icons over them. Ported from the marketing
|
||||
// site so the console reads as the same product.
|
||||
//
|
||||
// One component for both places it appears -- the sign-in screen and the
|
||||
// workspace behind every page -- because two copies of a parallax handler is
|
||||
// how they end up subtly different.
|
||||
export function AppBackground({ className = "" }: { className?: string }) {
|
||||
const orbsRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// The orbs drift with the pointer, the same 30px parallax the site uses.
|
||||
// Written straight to the node instead of through state: this fires on every
|
||||
// mouse move, and re-rendering the tree for a background offset would be a
|
||||
// lot of work to move three blurred circles.
|
||||
useEffect(() => {
|
||||
// Someone who asked the system for less motion gets none of this. The CSS
|
||||
// drift is already disabled for them, and a JS-driven transform would walk
|
||||
// straight past that preference.
|
||||
if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
|
||||
return;
|
||||
}
|
||||
function onMove(event: MouseEvent) {
|
||||
const node = orbsRef.current;
|
||||
if (!node) return;
|
||||
const x = (event.clientX / window.innerWidth - 0.5) * 30;
|
||||
const y = (event.clientY / window.innerHeight - 0.5) * 30;
|
||||
node.style.transform = `translate(${x}px, ${y}px)`;
|
||||
}
|
||||
window.addEventListener("mousemove", onMove);
|
||||
return () => window.removeEventListener("mousemove", onMove);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={`app-background ${className}`.trim()} aria-hidden="true">
|
||||
<div className="bg-orbs" ref={orbsRef}>
|
||||
<div className="bg-orb bg-orb--1" />
|
||||
<div className="bg-orb bg-orb--2" />
|
||||
<div className="bg-orb bg-orb--3" />
|
||||
</div>
|
||||
<BgIcons />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ import { permissionBotVerificationReview, permissionServerManage, permissionAdmi
|
|||
import { type Navigate, type RouteState, routeTitle } from "../routing";
|
||||
import { ThemeSwitch } from "../theme";
|
||||
import { AddServerLinkModal } from "./AddServerLinkModal";
|
||||
import { AppBackground } from "./AppBackground";
|
||||
import { AppLink } from "./AppLink";
|
||||
|
||||
// Compresses a sorted (or unsorted) list of layer numbers into run-length
|
||||
|
|
@ -338,6 +339,9 @@ export function Shell({
|
|||
</header>
|
||||
<main className="content">{children}</main>
|
||||
</div>
|
||||
{/* Behind everything, fixed to the viewport. The sidebar and topbar paint
|
||||
over it, so it shows through the working area only. */}
|
||||
<AppBackground className="app-background--workspace" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { FormEvent } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { api, errorMessage } from "../api";
|
||||
import { BgIcons } from "../components/BgIcons";
|
||||
import { AppBackground } from "../components/AppBackground";
|
||||
import { Alert } from "../components/ui";
|
||||
import { ThemeSwitch } from "../theme";
|
||||
import type { AdminSession, PublicBranding } from "../types";
|
||||
|
|
@ -19,34 +19,11 @@ export function LoginPage({ onLogin }: { onLogin: (session: AdminSession) => voi
|
|||
// failure so the panel simply keeps its own branding.
|
||||
const [branding, setBranding] = useState<PublicBranding | null>(null);
|
||||
const [iconFailed, setIconFailed] = useState(false);
|
||||
const orbsRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
api.publicBranding().then(setBranding).catch(() => undefined);
|
||||
}, []);
|
||||
|
||||
// The orbs drift with the pointer, the same 30px parallax the marketing site
|
||||
// uses. Written straight to the node instead of through state: this fires on
|
||||
// every mouse move, and re-rendering the whole form for a background offset
|
||||
// would be a lot of work to move three blurred circles.
|
||||
useEffect(() => {
|
||||
// Someone who asked the system for less motion gets none of this: the CSS
|
||||
// drift is already disabled for them, and a JS-driven transform would walk
|
||||
// straight past that preference.
|
||||
if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
|
||||
return;
|
||||
}
|
||||
function onMove(event: MouseEvent) {
|
||||
const node = orbsRef.current;
|
||||
if (!node) return;
|
||||
const x = (event.clientX / window.innerWidth - 0.5) * 30;
|
||||
const y = (event.clientY / window.innerHeight - 0.5) * 30;
|
||||
node.style.transform = `translate(${x}px, ${y}px)`;
|
||||
}
|
||||
window.addEventListener("mousemove", onMove);
|
||||
return () => window.removeEventListener("mousemove", onMove);
|
||||
}, []);
|
||||
|
||||
const serverName = branding?.name?.trim() || "OwpenGram";
|
||||
const iconSrc = branding?.has_icon && !iconFailed ? api.publicIconURL() : "/logo.png";
|
||||
|
||||
|
|
@ -68,12 +45,7 @@ export function LoginPage({ onLogin }: { onLogin: (session: AdminSession) => voi
|
|||
|
||||
return (
|
||||
<main className="login-page">
|
||||
<div className="bg-orbs" aria-hidden="true" ref={orbsRef}>
|
||||
<div className="bg-orb bg-orb--1" />
|
||||
<div className="bg-orb bg-orb--2" />
|
||||
<div className="bg-orb bg-orb--3" />
|
||||
</div>
|
||||
<BgIcons />
|
||||
<AppBackground />
|
||||
<section className="login-panel">
|
||||
<div className="login-head">
|
||||
<div className="brand brand-elevated">
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-page .bg-orbs {
|
||||
.bg-orbs {
|
||||
position: absolute;
|
||||
inset: -60px;
|
||||
z-index: 0;
|
||||
|
|
@ -283,14 +283,14 @@
|
|||
will-change: transform;
|
||||
}
|
||||
|
||||
.login-page .bg-orb {
|
||||
.bg-orb {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(100px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.login-page .bg-orb--1 {
|
||||
.bg-orb--1 {
|
||||
top: -15%;
|
||||
left: -10%;
|
||||
width: 700px;
|
||||
|
|
@ -299,7 +299,7 @@
|
|||
animation: loginOrbFloat1 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.login-page .bg-orb--2 {
|
||||
.bg-orb--2 {
|
||||
top: 25%;
|
||||
right: -15%;
|
||||
width: 600px;
|
||||
|
|
@ -308,7 +308,7 @@
|
|||
animation: loginOrbFloat2 24s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.login-page .bg-orb--3 {
|
||||
.bg-orb--3 {
|
||||
bottom: -15%;
|
||||
left: 30%;
|
||||
width: 500px;
|
||||
|
|
@ -334,15 +334,15 @@
|
|||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.login-page .bg-orb { filter: blur(60px); }
|
||||
.login-page .bg-orb--1 { width: 350px; height: 350px; }
|
||||
.login-page .bg-orb--2 { width: 300px; height: 300px; }
|
||||
.login-page .bg-orb--3 { width: 250px; height: 250px; }
|
||||
.bg-orb { filter: blur(60px); }
|
||||
.bg-orb--1 { width: 350px; height: 350px; }
|
||||
.bg-orb--2 { width: 300px; height: 300px; }
|
||||
.bg-orb--3 { width: 250px; height: 250px; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.login-page .bg-orb { animation: none; }
|
||||
.login-page .bg-orbs { transition: none; }
|
||||
.bg-orb { animation: none; }
|
||||
.bg-orbs { transition: none; }
|
||||
}
|
||||
|
||||
.login-page .login-panel {
|
||||
|
|
@ -491,9 +491,8 @@
|
|||
|
||||
/* The drifting icon field behind the sign-in card. Sits above the blurred orbs
|
||||
and below the panel, so the two background layers read as one scene.
|
||||
Positioned absolutely inside .login-page rather than fixed like the site's
|
||||
version, because here it belongs to one screen rather than the whole app. */
|
||||
.login-page .bg-icons {
|
||||
Fills whichever .app-background contains it. */
|
||||
.bg-icons {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
|
|
@ -502,9 +501,52 @@
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.login-page .bg-icon {
|
||||
.bg-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
|
||||
/* The background wrapper. On the sign-in screen it fills that screen; behind
|
||||
the workspace it is pinned to the viewport so it does not scroll away under
|
||||
a long page, and so the icons -- which bounce off window bounds -- stay
|
||||
inside the area they are drawn in. */
|
||||
.app-background {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.app-background--workspace {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
/* Quieter behind real content than behind a lone sign-in card: the panel is
|
||||
dense with tables and numbers, and the background has to stay background. */
|
||||
.app-background--workspace .bg-icons {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.app-background--workspace .bg-orb {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* The chrome paints over the background: the sidebar hides the part of it that
|
||||
would otherwise show through the navigation, which is also why the wrapper
|
||||
can span the whole viewport instead of hard-coding the sidebar's width.
|
||||
Only z-index is set here -- the sidebar is already sticky, and restating
|
||||
position would be a chance to change it by accident. The topbar needs
|
||||
nothing: it carries z-index 20 of its own, and a rule here would out-specify
|
||||
its own "position: sticky" and quietly stop it sticking. */
|
||||
.shell > .sidebar {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.workspace > .content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue