Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions src/features/auth/components/FloatingShape.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { motion } from 'framer-motion'

/**
* FloatingShape — the shared decorative background orb for every auth shell
* (dashboard login + register + MFA + the standalone auth pages).
*
* ONE-SHOT fade/scale-in only (no `repeat: Infinity`) with a soft radial fill
* instead of an animated `backdrop-filter: blur`. The previous orb ran an
* INFINITE framer-motion loop AND re-blurred its backdrop every frame, which —
* layered over the (now removed) animated shell gradient — was a continuous
* main-thread compositing cost (worst with hardware-acceleration off) that
* contributed to the auth-page jank. After the entrance transition this orb is
* fully static, so it adds zero per-frame work while staying as brand
* decoration. See loginBackground.ts for the matching static-gradient fix.
*
* Callers gate the orbs behind `!usePrefersReducedMotion()` so users who ask
* for reduced motion get no decorative chrome at all.
*/
const FloatingShape = ({ delay, size, left, top }: {
delay: number
size: number
left: string
top: string
}) => (
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 0.28, scale: 1 }}
transition={{
duration: 1.2,
delay,
ease: 'easeOut',
}}
style={{
position: 'absolute',
left,
top,
width: size,
height: size,
borderRadius: '50%',
background:
'radial-gradient(circle, rgba(255,255,255,0.22) 0%, rgba(255,255,255,0.06) 60%, rgba(255,255,255,0) 100%)',
}}
/>
)

export default FloatingShape
35 changes: 1 addition & 34 deletions src/features/auth/components/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { hasPasswordLayer1, selectPuzzleConfig, type LoginConfig } from '@domain
import StepProgress from '../../../verify-app/StepProgress'
import { usePrefersReducedMotion } from '@hooks/usePrefersReducedMotion'
import { loginShellBackgroundSx, glassCardBackdropFilter } from './loginBackground'
import FloatingShape from './FloatingShape'

/**
* Login form validation schema
Expand Down Expand Up @@ -108,40 +109,6 @@ const logoVariants: Variants = {
},
}

// Decorative background orb. ONE-SHOT fade/scale-in only (no `repeat: Infinity`)
// and a soft radial fill instead of an animated `backdrop-filter: blur` — the
// continuous orb animation + per-frame backdrop re-blur was a main-thread compositing
// cost (worst with hardware-acceleration off) that contributed to the login jank.
// After the entrance it is fully static, so it adds zero per-frame work — the orbs
// stay as brand decoration without the cost. See loginBackground.ts for the matching
// static-gradient fix.
const FloatingShape = ({ delay, size, left, top }: {
delay: number
size: number
left: string
top: string
}) => (
<motion.div
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 0.28, scale: 1 }}
transition={{
duration: 1.2,
delay,
ease: 'easeOut',
}}
style={{
position: 'absolute',
left,
top,
width: size,
height: size,
borderRadius: '50%',
background:
'radial-gradient(circle, rgba(255,255,255,0.22) 0%, rgba(255,255,255,0.06) 60%, rgba(255,255,255,0) 100%)',
}}
/>
)

/**
* Login Page Component
* Beautiful animated login with glassmorphism design
Expand Down
62 changes: 15 additions & 47 deletions src/features/auth/components/RegisterPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ import { getService } from '@core/di/container'
import { TYPES } from '@core/di/types'
import type { IHttpClient } from '@domain/interfaces/IHttpClient'
import { isLikelyValidEmail } from '@domain/validators/emailValidator'
import { usePrefersReducedMotion } from '@hooks/usePrefersReducedMotion'
import { loginShellBackgroundSx } from './loginBackground'
import FloatingShape from './FloatingShape'

/**
* Register form validation schema
Expand Down Expand Up @@ -117,46 +120,14 @@ const logoVariants: Variants = {
},
}

// Floating shapes for background
const FloatingShape = ({ delay, size, left, top }: {
delay: number
size: number
left: string
top: string
}) => (
<motion.div
initial={{ opacity: 0, scale: 0 }}
animate={{
opacity: [0.1, 0.3, 0.1],
scale: [1, 1.2, 1],
y: [0, -20, 0],
}}
transition={{
duration: 6,
delay,
repeat: Infinity,
ease: 'easeInOut',
}}
style={{
position: 'absolute',
left,
top,
width: size,
height: size,
borderRadius: '50%',
background: 'rgba(255, 255, 255, 0.1)',
backdropFilter: 'blur(10px)',
}}
/>
)

/**
* Register Page Component
* Beautiful animated registration with glassmorphism design
*/
export default function RegisterPage() {
const { t } = useTranslation()
const navigate = useNavigate()
const prefersReducedMotion = usePrefersReducedMotion()
const [showPassword, setShowPassword] = useState(false)
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
const [loading, setLoading] = useState(false)
Expand Down Expand Up @@ -226,23 +197,20 @@ export default function RegisterPage() {
justifyContent: 'center',
position: 'relative',
overflow: 'hidden',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f64f59 100%)',
backgroundSize: '400% 400%',
animation: 'gradientShift 15s ease infinite',
'@keyframes gradientShift': {
'0%': { backgroundPosition: '0% 50%' },
'50%': { backgroundPosition: '100% 50%' },
'100%': { backgroundPosition: '0% 50%' },
},
py: 4,
...loginShellBackgroundSx(),
}}
>
{/* Animated background shapes */}
<FloatingShape delay={0} size={300} left="10%" top="20%" />
<FloatingShape delay={1} size={200} left="70%" top="10%" />
<FloatingShape delay={2} size={150} left="80%" top="60%" />
<FloatingShape delay={0.5} size={100} left="5%" top="70%" />
<FloatingShape delay={1.5} size={250} left="50%" top="80%" />
{/* Decorative background shapes — skipped for reduced-motion users. */}
{!prefersReducedMotion && (
<>
<FloatingShape delay={0} size={300} left="10%" top="20%" />
<FloatingShape delay={1} size={200} left="70%" top="10%" />
<FloatingShape delay={2} size={150} left="80%" top="60%" />
<FloatingShape delay={0.5} size={100} left="5%" top="70%" />
<FloatingShape delay={1.5} size={250} left="50%" top="80%" />
</>
)}

<motion.div
initial="hidden"
Expand Down
14 changes: 3 additions & 11 deletions src/features/auth/components/SecondaryAuthFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { EnrollmentStatus, type EnrollmentJSON } from '@domain/models/Enrollment
import { FivucsasAuth } from '@/verify-app/sdk/FivucsasAuth'
import type { AuthSessionResponse } from '@core/repositories/AuthSessionRepository'
import { config as envConfig } from '@config/env'
import { loginShellBackgroundSx } from './loginBackground'

const easeOut: [number, number, number, number] = [0.25, 0.46, 0.45, 0.94]

Expand Down Expand Up @@ -255,8 +256,7 @@ export default function SecondaryAuthFlow({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background:
'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f64f59 100%)',
...loginShellBackgroundSx(),
}}
>
<Card
Expand Down Expand Up @@ -296,16 +296,8 @@ export default function SecondaryAuthFlow({
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
background:
'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f64f59 100%)',
backgroundSize: '400% 400%',
animation: 'gradientShift 15s ease infinite',
'@keyframes gradientShift': {
'0%': { backgroundPosition: '0% 50%' },
'50%': { backgroundPosition: '100% 50%' },
'100%': { backgroundPosition: '0% 50%' },
},
p: 2,
...loginShellBackgroundSx(),
}}
>
<motion.div
Expand Down
51 changes: 15 additions & 36 deletions src/pages/AcceptInvitePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ import { motion, Variants } from 'framer-motion'
import { getService } from '@core/di/container'
import { TYPES } from '@core/di/types'
import type { IGuestRepository } from '@domain/interfaces/IGuestRepository'
import { usePrefersReducedMotion } from '@hooks/usePrefersReducedMotion'
import { loginShellBackgroundSx } from '@features/auth/components/loginBackground'
import FloatingShape from '@features/auth/components/FloatingShape'

const acceptInviteSchema = z
.object({
Expand Down Expand Up @@ -65,32 +68,10 @@ const logoVariants: Variants = {
visible: { scale: 1, opacity: 1, rotateY: 0, transition: { duration: 0.8, ease: easeOut } },
}

const FloatingShape = ({ delay, size, left, top }: {
delay: number
size: number
left: string
top: string
}) => (
<motion.div
initial={{ opacity: 0, scale: 0 }}
animate={{ opacity: [0.1, 0.3, 0.1], scale: [1, 1.2, 1], y: [0, -20, 0] }}
transition={{ duration: 6, delay, repeat: Infinity, ease: 'easeInOut' }}
style={{
position: 'absolute',
left,
top,
width: size,
height: size,
borderRadius: '50%',
background: 'rgba(255, 255, 255, 0.1)',
backdropFilter: 'blur(10px)',
}}
/>
)

export default function AcceptInvitePage() {
const navigate = useNavigate()
const { t } = useTranslation()
const prefersReducedMotion = usePrefersReducedMotion()
const [searchParams] = useSearchParams()
const token = searchParams.get('token') || ''
const [loading, setLoading] = useState(false)
Expand Down Expand Up @@ -203,21 +184,19 @@ export default function AcceptInvitePage() {
justifyContent: 'center',
position: 'relative',
overflow: 'hidden',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f64f59 100%)',
backgroundSize: '400% 400%',
animation: 'gradientShift 15s ease infinite',
'@keyframes gradientShift': {
'0%': { backgroundPosition: '0% 50%' },
'50%': { backgroundPosition: '100% 50%' },
'100%': { backgroundPosition: '0% 50%' },
},
...loginShellBackgroundSx(),
}}
>
<FloatingShape delay={0} size={300} left="10%" top="20%" />
<FloatingShape delay={1} size={200} left="70%" top="10%" />
<FloatingShape delay={2} size={150} left="80%" top="60%" />
<FloatingShape delay={0.5} size={100} left="5%" top="70%" />
<FloatingShape delay={1.5} size={250} left="50%" top="80%" />
{/* Decorative background shapes — skipped for reduced-motion users. */}
{!prefersReducedMotion && (
<>
<FloatingShape delay={0} size={300} left="10%" top="20%" />
<FloatingShape delay={1} size={200} left="70%" top="10%" />
<FloatingShape delay={2} size={150} left="80%" top="60%" />
<FloatingShape delay={0.5} size={100} left="5%" top="70%" />
<FloatingShape delay={1.5} size={250} left="50%" top="80%" />
</>
)}

<motion.div
initial="hidden"
Expand Down
60 changes: 15 additions & 45 deletions src/pages/ForgotPasswordPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import { motion, Variants } from 'framer-motion'
import { getService } from '@core/di/container'
import { TYPES } from '@core/di/types'
import type { IHttpClient } from '@domain/interfaces/IHttpClient'
import { usePrefersReducedMotion } from '@hooks/usePrefersReducedMotion'
import { loginShellBackgroundSx } from '@features/auth/components/loginBackground'
import FloatingShape from '@features/auth/components/FloatingShape'

const forgotPasswordSchema = z.object({
email: z.string().min(1, 'Email is required').email('Invalid email address'),
Expand Down Expand Up @@ -71,41 +74,10 @@ const logoVariants: Variants = {
},
}

const FloatingShape = ({ delay, size, left, top }: {
delay: number
size: number
left: string
top: string
}) => (
<motion.div
initial={{ opacity: 0, scale: 0 }}
animate={{
opacity: [0.1, 0.3, 0.1],
scale: [1, 1.2, 1],
y: [0, -20, 0],
}}
transition={{
duration: 6,
delay,
repeat: Infinity,
ease: 'easeInOut',
}}
style={{
position: 'absolute',
left,
top,
width: size,
height: size,
borderRadius: '50%',
background: 'rgba(255, 255, 255, 0.1)',
backdropFilter: 'blur(10px)',
}}
/>
)

export default function ForgotPasswordPage() {
const navigate = useNavigate()
const { t } = useTranslation()
const prefersReducedMotion = usePrefersReducedMotion()
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)

Expand Down Expand Up @@ -148,21 +120,19 @@ export default function ForgotPasswordPage() {
justifyContent: 'center',
position: 'relative',
overflow: 'hidden',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f64f59 100%)',
backgroundSize: '400% 400%',
animation: 'gradientShift 15s ease infinite',
'@keyframes gradientShift': {
'0%': { backgroundPosition: '0% 50%' },
'50%': { backgroundPosition: '100% 50%' },
'100%': { backgroundPosition: '0% 50%' },
},
...loginShellBackgroundSx(),
}}
>
<FloatingShape delay={0} size={300} left="10%" top="20%" />
<FloatingShape delay={1} size={200} left="70%" top="10%" />
<FloatingShape delay={2} size={150} left="80%" top="60%" />
<FloatingShape delay={0.5} size={100} left="5%" top="70%" />
<FloatingShape delay={1.5} size={250} left="50%" top="80%" />
{/* Decorative background shapes — skipped for reduced-motion users. */}
{!prefersReducedMotion && (
<>
<FloatingShape delay={0} size={300} left="10%" top="20%" />
<FloatingShape delay={1} size={200} left="70%" top="10%" />
<FloatingShape delay={2} size={150} left="80%" top="60%" />
<FloatingShape delay={0.5} size={100} left="5%" top="70%" />
<FloatingShape delay={1.5} size={250} left="50%" top="80%" />
</>
)}

<motion.div
initial="hidden"
Expand Down
Loading
Loading