From a02cf42295af49965c838f1c2cef8ac602b82ab5 Mon Sep 17 00:00:00 2001 From: Daniel Dias Pereira <86617987+DanielDPereira@users.noreply.github.com> Date: Thu, 22 Oct 2026 22:00:41 -0300 Subject: [PATCH 1/9] feat(frontend): add animated graph login background Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- frontend/src/index.css | 97 ++++++++++++++++++++++++++++ frontend/src/views/auth/AuthView.tsx | 59 +++++++++++++++++ 2 files changed, 156 insertions(+) diff --git a/frontend/src/index.css b/frontend/src/index.css index 42c03f1..41234b7 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -3,18 +3,93 @@ @import "./styles/layout.css"; .auth-shell { + position: relative; + isolation: isolate; min-height: 100vh; display: grid; place-items: center; padding: 24px; + overflow: hidden; + background: + radial-gradient(circle at 50% 44%, color-mix(in srgb, var(--brand-primary) 8%, transparent), transparent 38%), + var(--surface-base); +} + +.auth-graph { + position: absolute; + z-index: -1; + inset: 0; + pointer-events: none; + opacity: .9; +} + +.auth-graph::before, +.auth-graph::after { + position: absolute; + content: ""; + width: 34vw; + aspect-ratio: 1; + border-radius: 50%; + background: var(--accent-glow); + filter: blur(80px); + opacity: .22; +} + +.auth-graph::before { + top: -18%; + left: -12%; +} + +.auth-graph::after { + right: -12%; + bottom: -22%; +} + +.auth-graph svg { + display: block; + width: 100%; + height: 100%; +} + +.auth-graph__edges line { + stroke: var(--brand-primary); + stroke-width: 1; + stroke-dasharray: 6 14; + opacity: .26; + animation: auth-graph-edge 7s ease-in-out infinite alternate; + animation-delay: var(--edge-delay); +} + +.auth-graph__nodes circle { + fill: var(--accent-secondary); + opacity: .58; + filter: drop-shadow(0 0 5px var(--brand-primary)); + transform-box: fill-box; + transform-origin: center; + animation: auth-graph-node 4.5s ease-in-out infinite; + animation-delay: var(--node-delay); +} + +@keyframes auth-graph-edge { + from { stroke-dashoffset: 0; opacity: .16; } + to { stroke-dashoffset: -42; opacity: .52; } +} + +@keyframes auth-graph-node { + 0%, 100% { transform: scale(.8); opacity: .36; } + 50% { transform: scale(1.35); opacity: .9; } } .auth-card { + position: relative; + z-index: 1; width: 100%; max-width: 440px; padding: 32px; display: grid; gap: 18px; + box-shadow: 0 24px 80px rgba(0, 0, 0, .34), 0 0 0 1px color-mix(in srgb, var(--brand-primary) 12%, transparent); + backdrop-filter: blur(18px); } .auth-card h1 { @@ -45,6 +120,28 @@ color: var(--status-danger); } +@media (max-width: 600px) { + .auth-shell { + padding: 16px; + } + + .auth-card { + padding: 24px; + } + + .auth-graph { + inset: -8% -28%; + opacity: .7; + } +} + +@media (prefers-reduced-motion: reduce) { + .auth-graph__edges line, + .auth-graph__nodes circle { + animation: none; + } +} + button:focus-visible, input:focus-visible, select:focus-visible, diff --git a/frontend/src/views/auth/AuthView.tsx b/frontend/src/views/auth/AuthView.tsx index 39b2c2c..96c34bd 100644 --- a/frontend/src/views/auth/AuthView.tsx +++ b/frontend/src/views/auth/AuthView.tsx @@ -4,6 +4,64 @@ import { ApiError } from "../../api/api_auth"; import { useAuth } from "../../viewmodels/useAuthViewModel"; import { loginDestination, navigate, safeDestination } from "../../models/navigation"; +const graphNodes = [ + [82, 118], [178, 68], [286, 138], [402, 74], [526, 154], [654, 82], + [780, 132], [918, 64], [1080, 142], [1128, 286], [978, 332], [842, 268], + [704, 370], [566, 292], [418, 382], [274, 300], [126, 382], [42, 524], + [190, 594], [344, 536], [488, 650], [644, 548], [792, 660], [946, 560], + [1102, 672], [1034, 458], [884, 478], [742, 466], [604, 432], [364, 462], +] as const; + +const graphEdges = [ + [0, 1], [0, 2], [1, 2], [1, 3], [2, 3], [2, 5], [3, 4], [3, 6], + [4, 5], [4, 7], [5, 6], [5, 8], [6, 7], [6, 10], [7, 8], [8, 9], + [8, 10], [9, 10], [10, 11], [10, 12], [11, 12], [11, 13], [12, 13], + [12, 14], [13, 14], [13, 15], [14, 15], [14, 19], [15, 16], [15, 17], + [16, 17], [16, 18], [17, 18], [18, 19], [18, 20], [19, 20], [19, 28], + [20, 21], [20, 28], [21, 22], [21, 27], [22, 23], [22, 24], [23, 24], + [23, 25], [24, 25], [24, 26], [25, 26], [26, 27], [26, 29], [27, 28], + [28, 29], [14, 28], [10, 27], +] as const; + +function LoginGraphBackground() { + return ( +
+ ); +} + export function AuthGate({ children }: { children: ReactNode }) { const { session, notice, restore, logout } = useAuth(); const [location, setLocation] = useState(() => window.location.href); @@ -52,6 +110,7 @@ export function AuthScreen({ initialMode }: { initialMode: "login" | "register" return (