authorgravatar for gianmarcosimone89@gmail.comGianmarco <gianmarcosimone89@gmail.com> 2026-09-22 18:46:41+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-22 11:46:41-05:00
logda6a85b1365993d0ff2a79698cd82498de247d8a
tree60926c78fc591a79abe48b2b0288995d9a96ecac
parentaff9318bf46beaf05cc7155b428d3f0b8711efd2
signature Signed by PGP key B5690EEEBB952194

fix(web): respect panel motion in composer transitions (#11064)


5 files changed, 93 insertions(+), 36 deletions(-)

apps/web/src/components/ChatView.tsx+26-16
...@@ -406,7 +406,6 @@ import {...@@ -406,7 +406,6 @@ import {
406import { deriveLatestContextWindowSnapshot, formatContextWindowTokens } from "../lib/contextWindow";406import { deriveLatestContextWindowSnapshot, formatContextWindowTokens } from "../lib/contextWindow";
407import {407import {
408 DRAFT_HERO_TRANSITION_ANIMATION_ID,408 DRAFT_HERO_TRANSITION_ANIMATION_ID,
409 DRAFT_HERO_TRANSITION_DURATION_MS,
410 DRAFT_HERO_TRANSITION_EASING,409 DRAFT_HERO_TRANSITION_EASING,
411 MOBILE_COMPOSER_VIEW_TRANSITION_NAME,410 MOBILE_COMPOSER_VIEW_TRANSITION_NAME,
412 MOBILE_DRAFT_HEADLINE_VIEW_TRANSITION_NAME,411 MOBILE_DRAFT_HEADLINE_VIEW_TRANSITION_NAME,
...@@ -539,7 +538,11 @@ const EMPTY_PROVIDERS: ServerProvider[] = [];...@@ -539,7 +538,11 @@ const EMPTY_PROVIDERS: ServerProvider[] = [];
539const EMPTY_USAGE_LIMIT_SOURCES: UsageLimitSourceSnapshots = [];538const EMPTY_USAGE_LIMIT_SOURCES: UsageLimitSourceSnapshots = [];
540const EMPTY_PROVIDER_SKILLS: ServerProvider["skills"] = [];539const EMPTY_PROVIDER_SKILLS: ServerProvider["skills"] = [];
541const EMPTY_PENDING_USER_INPUT_ANSWERS: Record<string, PendingUserInputDraftAnswer> = {};540const EMPTY_PENDING_USER_INPUT_ANSWERS: Record<string, PendingUserInputDraftAnswer> = {};
542function useDraftHeroLayoutTransition(isDraftHeroState: boolean) {541function useDraftHeroLayoutTransition(
542 isDraftHeroState: boolean,
543 animationsActive: boolean,
544 animationDurationMs: number,
545) {
543 const transitionGroupRef = useRef<HTMLDivElement | null>(null);546 const transitionGroupRef = useRef<HTMLDivElement | null>(null);
544 const composerAnchorRef = useRef<HTMLDivElement | null>(null);547 const composerAnchorRef = useRef<HTMLDivElement | null>(null);
545 const previousStateRef = useRef(isDraftHeroState);548 const previousStateRef = useRef(isDraftHeroState);
...@@ -559,9 +562,6 @@ function useDraftHeroLayoutTransition(isDraftHeroState: boolean) {...@@ -559,9 +562,6 @@ function useDraftHeroLayoutTransition(isDraftHeroState: boolean) {
559 const transitionGroup = transitionGroupRef.current;562 const transitionGroup = transitionGroupRef.current;
560 const nextComposerRect = composerAnchorRef.current?.getBoundingClientRect() ?? null;563 const nextComposerRect = composerAnchorRef.current?.getBoundingClientRect() ?? null;
561 const stateChanged = previousStateRef.current !== isDraftHeroState;564 const stateChanged = previousStateRef.current !== isDraftHeroState;
562 const prefersReducedMotion =
563 typeof window !== "undefined" &&
564 window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
565 const mobileComposerTransitionActive =565 const mobileComposerTransitionActive =
566 typeof document !== "undefined" &&566 typeof document !== "undefined" &&
567 document.documentElement.dataset.mobileComposerRouteTransition === "true";567 document.documentElement.dataset.mobileComposerRouteTransition === "true";
...@@ -572,7 +572,7 @@ function useDraftHeroLayoutTransition(isDraftHeroState: boolean) {...@@ -572,7 +572,7 @@ function useDraftHeroLayoutTransition(isDraftHeroState: boolean) {
572 const previousComposerRect = previousComposerRectRef.current;572 const previousComposerRect = previousComposerRectRef.current;
573 if (573 if (
574 stateChanged &&574 stateChanged &&
575 !prefersReducedMotion &&575 animationsActive &&
576 !mobileComposerTransitionActive &&576 !mobileComposerTransitionActive &&
577 transitionGroup &&577 transitionGroup &&
578 previousComposerRect &&578 previousComposerRect &&
...@@ -588,7 +588,7 @@ function useDraftHeroLayoutTransition(isDraftHeroState: boolean) {...@@ -588,7 +588,7 @@ function useDraftHeroLayoutTransition(isDraftHeroState: boolean) {
588 { transform: "translate3d(0, 0, 0)" },588 { transform: "translate3d(0, 0, 0)" },
589 ],589 ],
590 {590 {
591 duration: DRAFT_HERO_TRANSITION_DURATION_MS,591 duration: animationDurationMs,
592 easing: DRAFT_HERO_TRANSITION_EASING,592 easing: DRAFT_HERO_TRANSITION_EASING,
593 },593 },
594 );594 );
...@@ -607,7 +607,7 @@ function useDraftHeroLayoutTransition(isDraftHeroState: boolean) {...@@ -607,7 +607,7 @@ function useDraftHeroLayoutTransition(isDraftHeroState: boolean) {
607607
608 previousStateRef.current = isDraftHeroState;608 previousStateRef.current = isDraftHeroState;
609 previousComposerRectRef.current = nextComposerRect;609 previousComposerRectRef.current = nextComposerRect;
610 }, [isDraftHeroState]);610 }, [animationDurationMs, animationsActive, isDraftHeroState]);
611611
612 return [attachTransitionGroupRef, attachComposerAnchorRef, captureComposerRect] as const;612 return [attachTransitionGroupRef, attachComposerAnchorRef, captureComposerRect] as const;
613}613}
...@@ -3634,7 +3634,11 @@ export default function ChatView(props: ChatViewProps) {...@@ -3634,7 +3634,11 @@ export default function ChatView(props: ChatViewProps) {
3634 attachDraftHeroTransitionGroupRef,3634 attachDraftHeroTransitionGroupRef,
3635 attachDraftHeroComposerAnchorRef,3635 attachDraftHeroComposerAnchorRef,
3636 captureDraftHeroComposerRect,3636 captureDraftHeroComposerRect,
3637 ] = useDraftHeroLayoutTransition(isDraftHeroState);3637 ] = useDraftHeroLayoutTransition(
3638 isDraftHeroState,
3639 panelAnimationsActive,
3640 panelAnimationDurationMs,
3641 );
36383642
3639 const gitCwd = activeProject3643 const gitCwd = activeProject
3640 ? projectScriptCwd({3644 ? projectScriptCwd({
...@@ -7877,13 +7881,19 @@ export default function ChatView(props: ChatViewProps) {...@@ -7877,13 +7881,19 @@ export default function ChatView(props: ChatViewProps) {
7877 const dockStarted = new Promise<void>((resolve) => {7881 const dockStarted = new Promise<void>((resolve) => {
7878 resolveDockStarted = resolve;7882 resolveDockStarted = resolve;
7879 });7883 });
7880 const dockTransition = runMobileComposerTransition(() => {7884 const dockTransition = runMobileComposerTransition(
7881 flushSync(() => {7885 () => {
7882 captureDraftHeroComposerRect();7886 flushSync(() => {
7883 setDockedDraftHeroThreadKey(activeThreadKey);7887 captureDraftHeroComposerRect();
7884 });7888 setDockedDraftHeroThreadKey(activeThreadKey);
7885 resolveDockStarted?.();7889 });
7886 });7890 resolveDockStarted?.();
7891 },
7892 {
7893 active: panelAnimationsActive,
7894 durationMs: panelAnimationDurationMs,
7895 },
7896 );
7887 void dockTransition.catch(() => resolveDockStarted?.());7897 void dockTransition.catch(() => resolveDockStarted?.());
7888 await dockStarted;7898 await dockStarted;
7889 }7899 }
apps/web/src/components/chat/ChatComposer.tsx+14-8
...@@ -395,7 +395,6 @@ const EMPTY_PULL_REQUEST_LIST_TARGETS: ReadonlyArray<EnvironmentQueryTarget<Pull...@@ -395,7 +395,6 @@ const EMPTY_PULL_REQUEST_LIST_TARGETS: ReadonlyArray<EnvironmentQueryTarget<Pull
395395
396const COMPOSER_SCROLL_COLLAPSE_THRESHOLD_PX = 24;396const COMPOSER_SCROLL_COLLAPSE_THRESHOLD_PX = 24;
397const COMPOSER_SCROLL_GESTURE_RESET_MS = 120;397const COMPOSER_SCROLL_GESTURE_RESET_MS = 120;
398const COMPOSER_RESTING_TRANSITION_DURATION_MS = 280;
399const COMPOSER_RESTING_TRANSITION_CLEANUP_BUFFER_MS = 50;398const COMPOSER_RESTING_TRANSITION_CLEANUP_BUFFER_MS = 50;
400const COMPOSER_RESTING_TRANSITION_EASING = "cubic-bezier(0.32, 0.72, 0, 1)";399const COMPOSER_RESTING_TRANSITION_EASING = "cubic-bezier(0.32, 0.72, 0, 1)";
401const COMPOSER_RESTING_CONTROLS_ARRIVAL_DRIFT_PX = 4;400const COMPOSER_RESTING_CONTROLS_ARRIVAL_DRIFT_PX = 4;
...@@ -405,6 +404,8 @@ function useComposerRestingTransition(...@@ -405,6 +404,8 @@ function useComposerRestingTransition(
405 isResting: boolean,404 isResting: boolean,
406 restingControlsRef: React.RefObject<HTMLDivElement | null>,405 restingControlsRef: React.RefObject<HTMLDivElement | null>,
407 onOverlayHeightChange: (height: number) => void,406 onOverlayHeightChange: (height: number) => void,
407 animationsActive: boolean,
408 animationDurationMs: number,
408) {409) {
409 const elementRef = useRef<HTMLDivElement>(null);410 const elementRef = useRef<HTMLDivElement>(null);
410 const isCollapsedRef = useRef(isCollapsed);411 const isCollapsedRef = useRef(isCollapsed);
...@@ -523,7 +524,6 @@ function useComposerRestingTransition(...@@ -523,7 +524,6 @@ function useComposerRestingTransition(
523 const previousHeight = interruptedHeight ?? previousHeightRef.current;524 const previousHeight = interruptedHeight ?? previousHeightRef.current;
524 const targetChanged =525 const targetChanged =
525 interruptedTargetHeight === null || Math.abs(interruptedTargetHeight - nextHeight) >= 0.5;526 interruptedTargetHeight === null || Math.abs(interruptedTargetHeight - nextHeight) >= 0.5;
526 const prefersReducedMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
527 const shouldAnimate = shouldAnimateComposerRestingTransition({527 const shouldAnimate = shouldAnimateComposerRestingTransition({
528 hasCompletedInitialLayout: hasCompletedInitialLayoutRef.current,528 hasCompletedInitialLayout: hasCompletedInitialLayoutRef.current,
529 stateChanged,529 stateChanged,
...@@ -532,18 +532,16 @@ function useComposerRestingTransition(...@@ -532,18 +532,16 @@ function useComposerRestingTransition(
532532
533 if (533 if (
534 shouldAnimate &&534 shouldAnimate &&
535 !prefersReducedMotion &&535 animationsActive &&
536 previousHeight !== null &&536 previousHeight !== null &&
537 Math.abs(previousHeight - nextHeight) >= 0.5537 Math.abs(previousHeight - nextHeight) >= 0.5
538 ) {538 ) {
539 const remainingDuration =539 const remainingDuration =
540 typeof interruptedDuration === "number" && interruptedCurrentTime !== null540 typeof interruptedDuration === "number" && interruptedCurrentTime !== null
541 ? Math.max(1, interruptedDuration - interruptedCurrentTime)541 ? Math.max(1, interruptedDuration - interruptedCurrentTime)
542 : COMPOSER_RESTING_TRANSITION_DURATION_MS;542 : animationDurationMs;
543 const duration =543 const duration =
544 interruptedHeight !== null && !targetChanged544 interruptedHeight !== null && !targetChanged ? remainingDuration : animationDurationMs;
545 ? remainingDuration
546 : COMPOSER_RESTING_TRANSITION_DURATION_MS;
547 element.style.overflow = "clip";545 element.style.overflow = "clip";
548 surface.style.height = "100%";546 surface.style.height = "100%";
549547
...@@ -743,7 +741,13 @@ function useComposerRestingTransition(...@@ -743,7 +741,13 @@ function useComposerRestingTransition(
743 actionFromBottom: nextActionTop === null ? null : nextRect.bottom - nextActionTop,741 actionFromBottom: nextActionTop === null ? null : nextRect.bottom - nextActionTop,
744 };742 };
745 },743 },
746 [clearTransitionStyles, onOverlayHeightChange, restingControlsRef],744 [
745 animationDurationMs,
746 animationsActive,
747 clearTransitionStyles,
748 onOverlayHeightChange,
749 restingControlsRef,
750 ],
747 );751 );
748752
749 useLayoutEffect(() => {753 useLayoutEffect(() => {
...@@ -4826,6 +4830,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)...@@ -4826,6 +4830,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
4826 isComposerResting,4830 isComposerResting,
4827 restingComposerControlsRef,4831 restingComposerControlsRef,
4828 onComposerOverlayHeightChange,4832 onComposerOverlayHeightChange,
4833 panelAnimationsActive,
4834 panelAnimationDurationMs,
4829 );4835 );
4830 const canTrackComposerScrollGesture =4836 const canTrackComposerScrollGesture =
4831 routeKind === "server" && activeThreadId !== null && !isMobileViewport;4837 routeKind === "server" && activeThreadId !== null && !isMobileViewport;
apps/web/src/components/chat/draftHeroTransition.test.ts+32-5
...@@ -56,8 +56,9 @@ describe("runMobileComposerTransition", () => {...@@ -56,8 +56,9 @@ describe("runMobileComposerTransition", () => {
56 finishTransition = resolve;56 finishTransition = resolve;
57 });57 });
58 const dataset: Record<string, string> = {};58 const dataset: Record<string, string> = {};
59 const style = { setProperty: vi.fn(), removeProperty: vi.fn() };
59 vi.stubGlobal("document", {60 vi.stubGlobal("document", {
60 documentElement: { dataset },61 documentElement: { dataset, style },
61 getAnimations: () => [],62 getAnimations: () => [],
62 startViewTransition: (update: () => void | Promise<void>) => {63 startViewTransition: (update: () => void | Promise<void>) => {
63 void update();64 void update();
...@@ -68,7 +69,10 @@ describe("runMobileComposerTransition", () => {...@@ -68,7 +69,10 @@ describe("runMobileComposerTransition", () => {
68 matchMedia: (query: string) => ({ matches: query === "(max-width: 639px)" }),69 matchMedia: (query: string) => ({ matches: query === "(max-width: 639px)" }),
69 });70 });
7071
71 const transition = runMobileComposerTransition(() => undefined);72 const transition = runMobileComposerTransition(() => undefined, {
73 active: true,
74 durationMs: 240,
75 });
72 await Promise.resolve();76 await Promise.resolve();
7377
74 let handoffComplete = false;78 let handoffComplete = false;
...@@ -85,11 +89,12 @@ describe("runMobileComposerTransition", () => {...@@ -85,11 +89,12 @@ describe("runMobileComposerTransition", () => {
8589
86 it("uses a scoped view transition on mobile", async () => {90 it("uses a scoped view transition on mobile", async () => {
87 const dataset: Record<string, string> = {};91 const dataset: Record<string, string> = {};
92 const style = { setProperty: vi.fn(), removeProperty: vi.fn() };
88 const startViewTransition = vi.fn((update: () => void | Promise<void>) => ({93 const startViewTransition = vi.fn((update: () => void | Promise<void>) => ({
89 finished: Promise.resolve(update()).then(() => undefined),94 finished: Promise.resolve(update()).then(() => undefined),
90 }));95 }));
91 vi.stubGlobal("document", {96 vi.stubGlobal("document", {
92 documentElement: { dataset },97 documentElement: { dataset, style },
93 startViewTransition,98 startViewTransition,
94 });99 });
95 vi.stubGlobal("window", {100 vi.stubGlobal("window", {
...@@ -97,13 +102,35 @@ describe("runMobileComposerTransition", () => {...@@ -97,13 +102,35 @@ describe("runMobileComposerTransition", () => {
97 });102 });
98 const update = vi.fn();103 const update = vi.fn();
99104
100 await runMobileComposerTransition(update);105 await runMobileComposerTransition(update, { active: true, durationMs: 360 });
101106
102 expect(startViewTransition).toHaveBeenCalledOnce();107 expect(startViewTransition).toHaveBeenCalledOnce();
103 expect(update).toHaveBeenCalledOnce();108 expect(update).toHaveBeenCalledOnce();
109 expect(style.setProperty).toHaveBeenCalledWith(
110 "--mobile-composer-transition-duration",
111 "360ms",
112 );
113 expect(style.removeProperty).toHaveBeenCalledWith("--mobile-composer-transition-duration");
104 expect(dataset).not.toHaveProperty("mobileComposerRouteTransition");114 expect(dataset).not.toHaveProperty("mobileComposerRouteTransition");
105 });115 });
106116
117 it("updates without a view transition when panel animations are inactive", async () => {
118 const startViewTransition = vi.fn();
119 vi.stubGlobal("document", {
120 documentElement: { dataset: {} },
121 startViewTransition,
122 });
123 vi.stubGlobal("window", {
124 matchMedia: () => ({ matches: true }),
125 });
126 const update = vi.fn();
127
128 await runMobileComposerTransition(update, { active: false, durationMs: 360 });
129
130 expect(startViewTransition).not.toHaveBeenCalled();
131 expect(update).toHaveBeenCalledOnce();
132 });
133
107 it("updates without a view transition when reduced motion is preferred", async () => {134 it("updates without a view transition when reduced motion is preferred", async () => {
108 const startViewTransition = vi.fn();135 const startViewTransition = vi.fn();
109 vi.stubGlobal("document", {136 vi.stubGlobal("document", {
...@@ -115,7 +142,7 @@ describe("runMobileComposerTransition", () => {...@@ -115,7 +142,7 @@ describe("runMobileComposerTransition", () => {
115 });142 });
116 const update = vi.fn();143 const update = vi.fn();
117144
118 await runMobileComposerTransition(update);145 await runMobileComposerTransition(update, { active: true, durationMs: 360 });
119146
120 expect(startViewTransition).not.toHaveBeenCalled();147 expect(startViewTransition).not.toHaveBeenCalled();
121 expect(update).toHaveBeenCalledOnce();148 expect(update).toHaveBeenCalledOnce();
apps/web/src/components/chat/draftHeroTransition.ts+15-2
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1export const DRAFT_HERO_TRANSITION_ANIMATION_ID = "t3-draft-hero-transition";1export const DRAFT_HERO_TRANSITION_ANIMATION_ID = "t3-draft-hero-transition";
2export const DRAFT_HERO_TRANSITION_DURATION_MS = 180;
3export const DRAFT_HERO_TRANSITION_EASING = "cubic-bezier(0.4, 0, 0.2, 1)";2export const DRAFT_HERO_TRANSITION_EASING = "cubic-bezier(0.4, 0, 0.2, 1)";
4export const MOBILE_COMPOSER_VIEW_TRANSITION_NAME = "t3-mobile-composer";3export const MOBILE_COMPOSER_VIEW_TRANSITION_NAME = "t3-mobile-composer";
5export const MOBILE_DRAFT_HEADLINE_VIEW_TRANSITION_NAME = "t3-mobile-draft-headline";4export const MOBILE_DRAFT_HEADLINE_VIEW_TRANSITION_NAME = "t3-mobile-draft-headline";
5const MOBILE_COMPOSER_TRANSITION_DURATION_PROPERTY = "--mobile-composer-transition-duration";
66
7type ComposerViewTransition = {7type ComposerViewTransition = {
8 readonly finished: Promise<void>;8 readonly finished: Promise<void>;
...@@ -39,6 +39,7 @@ export async function waitForDraftHeroTransition(): Promise<void> {...@@ -39,6 +39,7 @@ export async function waitForDraftHeroTransition(): Promise<void> {
3939
40export async function runMobileComposerTransition(40export async function runMobileComposerTransition(
41 update: () => void | Promise<void>,41 update: () => void | Promise<void>,
42 options: { active: boolean; durationMs: number },
42): Promise<void> {43): Promise<void> {
43 if (typeof document === "undefined" || typeof window === "undefined") {44 if (typeof document === "undefined" || typeof window === "undefined") {
44 await update();45 await update();
...@@ -49,7 +50,12 @@ export async function runMobileComposerTransition(...@@ -49,7 +50,12 @@ export async function runMobileComposerTransition(
49 const mobileViewport = window.matchMedia?.("(max-width: 639px)").matches ?? false;50 const mobileViewport = window.matchMedia?.("(max-width: 639px)").matches ?? false;
50 const prefersReducedMotion =51 const prefersReducedMotion =
51 window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;52 window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
52 if (!mobileViewport || prefersReducedMotion || !transitionDocument.startViewTransition) {53 if (
54 !options.active ||
55 !mobileViewport ||
56 prefersReducedMotion ||
57 !transitionDocument.startViewTransition
58 ) {
53 await update();59 await update();
54 return;60 return;
55 }61 }
...@@ -61,6 +67,10 @@ export async function runMobileComposerTransition(...@@ -61,6 +67,10 @@ export async function runMobileComposerTransition(
61 await update();67 await update();
62 };68 };
63 let transitionFinished: Promise<void> | null = null;69 let transitionFinished: Promise<void> | null = null;
70 transitionDocument.documentElement.style.setProperty(
71 MOBILE_COMPOSER_TRANSITION_DURATION_PROPERTY,
72 `${String(options.durationMs)}ms`,
73 );
64 transitionDocument.documentElement.dataset.mobileComposerRouteTransition = "true";74 transitionDocument.documentElement.dataset.mobileComposerRouteTransition = "true";
65 try {75 try {
66 const transition = transitionDocument.startViewTransition(runUpdate);76 const transition = transitionDocument.startViewTransition(runUpdate);
...@@ -78,5 +88,8 @@ export async function runMobileComposerTransition(...@@ -78,5 +88,8 @@ export async function runMobileComposerTransition(
78 activeMobileComposerTransition = null;88 activeMobileComposerTransition = null;
79 }89 }
80 delete transitionDocument.documentElement.dataset.mobileComposerRouteTransition;90 delete transitionDocument.documentElement.dataset.mobileComposerRouteTransition;
91 transitionDocument.documentElement.style.removeProperty(
92 MOBILE_COMPOSER_TRANSITION_DURATION_PROPERTY,
93 );
81 }94 }
82}95}
apps/web/src/index.css+6-5
...@@ -13,7 +13,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-new(root) {...@@ -13,7 +13,7 @@ html[data-mobile-composer-route-transition="true"]::view-transition-new(root) {
13}13}
1414
15html[data-mobile-composer-route-transition="true"]::view-transition-group(t3-mobile-composer) {15html[data-mobile-composer-route-transition="true"]::view-transition-group(t3-mobile-composer) {
16 animation-duration: 180ms;16 animation-duration: var(--mobile-composer-transition-duration);
17 animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);17 animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
18}18}
1919
...@@ -22,12 +22,12 @@ html[data-mobile-composer-route-transition="true"]::view-transition-image-pair(t...@@ -22,12 +22,12 @@ html[data-mobile-composer-route-transition="true"]::view-transition-image-pair(t
22}22}
2323
24html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobile-composer) {24html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobile-composer) {
25 animation: t3-mobile-composer-old 180ms linear both;25 animation: t3-mobile-composer-old var(--mobile-composer-transition-duration) linear both;
26 mix-blend-mode: normal;26 mix-blend-mode: normal;
27}27}
2828
29html[data-mobile-composer-route-transition="true"]::view-transition-new(t3-mobile-composer) {29html[data-mobile-composer-route-transition="true"]::view-transition-new(t3-mobile-composer) {
30 animation: t3-mobile-composer-new 180ms linear both;30 animation: t3-mobile-composer-new var(--mobile-composer-transition-duration) linear both;
31 mix-blend-mode: normal;31 mix-blend-mode: normal;
32}32}
3333
...@@ -57,11 +57,12 @@ html[data-mobile-composer-route-transition="true"]::view-transition-new(t3-mobil...@@ -57,11 +57,12 @@ html[data-mobile-composer-route-transition="true"]::view-transition-new(t3-mobil
57}57}
5858
59html[data-mobile-composer-route-transition="true"]::view-transition-group(t3-mobile-draft-headline) {59html[data-mobile-composer-route-transition="true"]::view-transition-group(t3-mobile-draft-headline) {
60 animation-duration: 130ms;60 animation-duration: var(--mobile-composer-transition-duration);
61}61}
6262
63html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobile-draft-headline) {63html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobile-draft-headline) {
64 animation: t3-mobile-draft-headline-exit 130ms cubic-bezier(0.4, 0, 1, 1) both;64 animation: t3-mobile-draft-headline-exit var(--mobile-composer-transition-duration)
65 cubic-bezier(0.4, 0, 1, 1) both;
65 mix-blend-mode: normal;66 mix-blend-mode: normal;
66}67}
6768