From 41615dfab9c9511bcd02d5fc45d14cd3b4ea7be2 Mon Sep 17 00:00:00 2001 From: Tanish Manku Date: Thu, 25 Jun 2026 13:29:47 -0700 Subject: [PATCH] feat(webapp): stagger the login form entrance as its own beat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The login form faded in at the same time as the shell/header, so its entrance read as one undifferentiated fade rather than a deliberate beat. Two changes fix that: 1. Reveal: stop hardcoding `delay` in the stagger-child transition. framer-motion lets a child's explicit `transition.delay` override the parent's `staggerChildren`/`delayChildren`, so leaving it in (even as 0) collapsed every RevealGroup cascade — all children animated in unison. Now the explicit `delay` is only applied to standalone reveals; stagger children inherit the parent orchestration. This makes RevealGroup actually cascade (affects every RevealGroup in the webapp, as intended). 2. Login: split the single form Reveal into per-element children so email, password and the submit button cascade in sequence, with the secondary options (divider, Google, footer) continuing the same cascade. The email field leads on a near-zero delay so the primary input lands immediately — typing is never gated by the animation (inputs stay focusable and accept keystrokes mid-cascade). Verified with Playwright: a smooth ~75ms staircase across the six elements (email → password → button → divider → Google → footer), and typing the instant the field exists retains the full value with zero focus loss. --- .../src/components/motion/reveal.tsx | 20 +- .../artcraft-webapp/src/pages/login/login.tsx | 175 ++++++++++-------- 2 files changed, 108 insertions(+), 87 deletions(-) diff --git a/frontend/apps/artcraft-webapp/src/components/motion/reveal.tsx b/frontend/apps/artcraft-webapp/src/components/motion/reveal.tsx index 9a4a118b1d..e7760a7151 100644 --- a/frontend/apps/artcraft-webapp/src/components/motion/reveal.tsx +++ b/frontend/apps/artcraft-webapp/src/components/motion/reveal.tsx @@ -102,20 +102,28 @@ export function Reveal({ const reduceMotion = useReducedMotion(); const MotionTag = motionTag(as); + // When `inView` is left undefined the element behaves as a stagger child: + // it inherits the parent RevealGroup's orchestration and must NOT drive its + // own initial/animate props (that would override the cascade). + const standalone = inView !== undefined; + + // Only a standalone reveal sets its own `delay`. For a stagger child the + // delay MUST be omitted: framer-motion lets a child's explicit transition + // `delay` override the parent's `staggerChildren`/`delayChildren`, so leaving + // it in (even as 0) collapses the cascade and every child animates at once. const variants: Variants = { initial: reduceMotion ? { opacity: 0 } : { opacity: 0, y }, animate: { opacity: 1, y: 0, - transition: { duration: DUR_SLOW, ease: EASE_OUT, delay }, + transition: { + duration: DUR_SLOW, + ease: EASE_OUT, + ...(standalone ? { delay } : {}), + }, }, }; - // When `inView` is left undefined the element behaves as a stagger child: - // it inherits the parent RevealGroup's orchestration and must NOT drive its - // own initial/animate props (that would override the cascade). - const standalone = inView !== undefined; - return ( { /> - -
{ @@ -87,93 +85,108 @@ const Login = () => { )} -
- - setEmail(e.target.value)} - placeholder="you@example.com or username" - inputClassName="w-full bg-black/40 border border-white/10 focus:border-primary/50 rounded-xl px-4 py-3 text-white placeholder-white/20 outline-none transition-colors" - /> -
-
-
-
-
setPassword(e.target.value)} - placeholder="Min. 8 characters" - inputClassName="w-full bg-black/40 border border-white/10 focus:border-primary/50 rounded-xl px-4 py-3 text-white placeholder-white/20 outline-none transition-colors pr-12" + type="text" + value={email} + onChange={(e) => setEmail(e.target.value)} + placeholder="you@example.com or username" + inputClassName="w-full bg-black/40 border border-white/10 focus:border-primary/50 rounded-xl px-4 py-3 text-white placeholder-white/20 outline-none transition-colors" /> - +
+ + + + -
- - -
- -
+ {isLoading ? ( + + ) : ( + "Log in" + )} + + + -
- -
-
-
- - or - - + {/* Secondary sign-in options pick up the same cascade just after the + fields (delay continues from the form group's three children above: + 0.04 + 3 × 0.08). */} + + +
+
+
+ + or + + - - - + + + - - - Don't have an account?{" "} - - Sign up - - - + + + Don't have an account?{" "} + + Sign up + + + );