diff --git a/public/img/identity-topography.webp b/public/img/identity-topography.webp new file mode 100644 index 0000000..86e1426 Binary files /dev/null and b/public/img/identity-topography.webp differ diff --git a/src/login/InteractiveBackground.css b/src/login/InteractiveBackground.css new file mode 100644 index 0000000..0df7849 --- /dev/null +++ b/src/login/InteractiveBackground.css @@ -0,0 +1 @@ +.corelink-interactive-background{--pointer-x:50%;--pointer-y:50%;position:fixed;inset:0;z-index:0;overflow:hidden;pointer-events:none;background:#041014}.corelink-interactive-background__image{position:absolute;inset:-3%;background-position:center;background-repeat:no-repeat;background-size:cover;transform:scale(1.035);will-change:transform}.corelink-interactive-background__light{position:absolute;inset:0;background:radial-gradient(circle 320px at var(--pointer-x) var(--pointer-y),rgba(72,220,210,.11),rgba(72,220,210,.035) 36%,transparent 68%);mix-blend-mode:screen}.corelink-interactive-background__shade{position:absolute;inset:0;background:linear-gradient(90deg,rgba(2,11,15,.52),rgba(2,11,15,.24) 45%,rgba(2,11,15,.62)),linear-gradient(180deg,rgba(0,0,0,.08),rgba(0,7,10,.46))}body{background:#041014}body:before{display:none!important}#root{position:relative;isolation:isolate}.corelink-brand-bar,#kc-content,.corelink-auth-footer{z-index:2}#kc-content{position:relative}#kc-content-wrapper{background:linear-gradient(180deg,rgba(8,22,28,.78),rgba(4,15,21,.84));border-color:rgba(120,220,215,.16);box-shadow:0 32px 90px rgba(0,0,0,.42);backdrop-filter:blur(22px) saturate(115%);-webkit-backdrop-filter:blur(22px) saturate(115%)}input[type=text],input[type=email],input[type=password],input[type=tel],input[type=number],select{background:rgba(5,18,24,.82);border-color:rgba(130,190,200,.2)}@media(hover:none),(pointer:coarse){.corelink-interactive-background__image{transform:scale(1.02)!important;will-change:auto}.corelink-interactive-background__light{display:none}}@media(prefers-reduced-motion:reduce){.corelink-interactive-background__image{transform:scale(1.02)!important;will-change:auto}.corelink-interactive-background__light{display:none}} diff --git a/src/login/InteractiveBackground.tsx b/src/login/InteractiveBackground.tsx new file mode 100644 index 0000000..02d5bd1 --- /dev/null +++ b/src/login/InteractiveBackground.tsx @@ -0,0 +1,80 @@ +import { useEffect, useRef } from "react"; + +interface InteractiveBackgroundProps { + imageUrl: string; +} + +export function InteractiveBackground({ imageUrl }: InteractiveBackgroundProps) { + const rootRef = useRef(null); + const imageRef = useRef(null); + + useEffect(() => { + const root = rootRef.current; + const image = imageRef.current; + + if (!root || !image) { + return; + } + + const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; + const finePointer = window.matchMedia("(hover: hover) and (pointer: fine)").matches; + + if (reducedMotion || !finePointer) { + return; + } + + let targetX = 0; + let targetY = 0; + let currentX = 0; + let currentY = 0; + let frame = 0; + + const move = (event: PointerEvent) => { + const x = event.clientX / window.innerWidth; + const y = event.clientY / window.innerHeight; + + targetX = (x - 0.5) * -14; + targetY = (y - 0.5) * -10; + + root.style.setProperty("--pointer-x", `${x * 100}%`); + root.style.setProperty("--pointer-y", `${y * 100}%`); + }; + + const leave = () => { + targetX = 0; + targetY = 0; + root.style.setProperty("--pointer-x", "50%"); + root.style.setProperty("--pointer-y", "50%"); + }; + + const animate = () => { + currentX += (targetX - currentX) * 0.055; + currentY += (targetY - currentY) * 0.055; + + image.style.transform = `translate3d(${currentX}px, ${currentY}px, 0) scale(1.035)`; + frame = window.requestAnimationFrame(animate); + }; + + window.addEventListener("pointermove", move, { passive: true }); + window.addEventListener("pointerleave", leave); + frame = window.requestAnimationFrame(animate); + + return () => { + window.removeEventListener("pointermove", move); + window.removeEventListener("pointerleave", leave); + window.cancelAnimationFrame(frame); + }; + }, []); + + return ( +