diff --git a/assets/animations/CustomAgents.lottie b/assets/animations/CustomAgents.lottie
new file mode 100644
index 000000000000..6f6b40b8c463
Binary files /dev/null and b/assets/animations/CustomAgents.lottie differ
diff --git a/assets/animations/ExpenseAssistant.lottie b/assets/animations/ExpenseAssistant.lottie
new file mode 100644
index 000000000000..e2c0d9729eae
Binary files /dev/null and b/assets/animations/ExpenseAssistant.lottie differ
diff --git a/assets/animations/SpendAnalysis.lottie b/assets/animations/SpendAnalysis.lottie
new file mode 100644
index 000000000000..f1cc7ab61cb5
Binary files /dev/null and b/assets/animations/SpendAnalysis.lottie differ
diff --git a/config/eslint/eslint.seatbelt.tsv b/config/eslint/eslint.seatbelt.tsv
index fb1b04d4d4c0..80cf8829a007 100644
--- a/config/eslint/eslint.seatbelt.tsv
+++ b/config/eslint/eslint.seatbelt.tsv
@@ -697,6 +697,7 @@
 "../../src/libs/Navigation/PlatformStackNavigation/ScreenLayout.tsx"	"@typescript-eslint/no-unsafe-type-assertion"	1
 "../../src/libs/Navigation/PlatformStackNavigation/createPlatformStackNavigatorComponent/ScreenFreezeWrapper/index.native.tsx"	"@typescript-eslint/no-unsafe-type-assertion"	2
 "../../src/libs/Navigation/PlatformStackNavigation/navigationOptions/animation/withAnimation.ts"	"@typescript-eslint/no-unsafe-type-assertion"	6
+"../../src/libs/Navigation/guards/AIFeaturesPromoGuard.ts"	"@typescript-eslint/no-unsafe-type-assertion"	1
 "../../src/libs/Navigation/guards/MigratedUserWelcomeModalGuard.ts"	"@typescript-eslint/no-unsafe-type-assertion"	1
 "../../src/libs/Navigation/guards/OnboardingGuard.ts"	"@typescript-eslint/no-unsafe-type-assertion"	2
 "../../src/libs/Navigation/helpers/createNormalizedConfigs.ts"	"@typescript-eslint/no-deprecated/escape"	1
diff --git a/jest/setupAfterEnv.ts b/jest/setupAfterEnv.ts
index 6756a8c6ac8c..905758dfc8b4 100644
--- a/jest/setupAfterEnv.ts
+++ b/jest/setupAfterEnv.ts
@@ -8,6 +8,11 @@ import Onyx from 'react-native-onyx';
 
 jest.useRealTimers();
 
+jest.mock('@hooks/useAIFeaturesPromoModal', () => ({
+    __esModule: true,
+    default: jest.fn(),
+}));
+
 // Patch Keyboard.addListener to return a subscription object with .remove() so that
 // @react-navigation/bottom-tabs useIsKeyboardShown hook doesn't crash on cleanup.
 if (Keyboard && typeof Keyboard.addListener === 'function') {
diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index 701893c986d6..149b6fdb9bfc 100644
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -7266,6 +7266,7 @@ const CONST = {
         SCREENS.SAML_SIGN_IN,
         SCREENS.VALIDATE_LOGIN,
         SCREENS.MIGRATED_USER_WELCOME_MODAL.DYNAMIC_ROOT,
+        SCREENS.AI_FEATURES_PROMO_MODAL.DYNAMIC_ROOT,
         SCREENS.MONEY_REQUEST.STEP_SCAN,
         SCREENS.DOMAIN.MEMBERS_MOVE_TO_GROUP,
         ...Object.values(SCREENS.MULTIFACTOR_AUTHENTICATION),
@@ -7800,6 +7801,14 @@ const CONST = {
 
     MIGRATED_USER_WELCOME_MODAL: 'migratedUserWelcomeModal',
 
+    AI_FEATURES_PROMO_MODAL: 'aiFeaturesPromoModal',
+
+    AI_FEATURES_PROMO_LEARN_MORE_URLS: {
+        SPEND_ANALYSIS: 'https://help.expensify.com/articles/new-expensify/concierge-ai/How-Concierge-Analyzes-Spend',
+        EXPENSE_ASSISTANT: 'https://help.expensify.com/articles/new-expensify/concierge-ai/Expense-Assistant',
+        BUILD_AGENTS: 'https://help.expensify.com/articles/new-expensify/ai-agents/Create-Agent-Rules',
+    },
+
     BASE_LIST_ITEM_TEST_ID: 'base-list-item-',
     SELECTION_BUTTON_TEST_ID: 'selection-button-',
     PRODUCT_TRAINING_TOOLTIP_NAMES: {
@@ -8812,6 +8821,14 @@ const CONST = {
                 ROW: 'DomainMembers-Row',
             },
         },
+        FEATURE_TRAINING: {
+            CLOSE_BUTTON: 'FeatureTraining-CloseButton',
+            BACK_BUTTON: 'FeatureTraining-BackButton',
+        },
+        AI_FEATURES_PROMO_MODAL: {
+            CONFIRM_BUTTON: 'AIFeaturesPromoModal-ConfirmButton',
+            HELP_BUTTON: 'AIFeaturesPromoModal-HelpButton',
+        },
     },
 
     DOMAIN: {
diff --git a/src/NAVIGATORS.ts b/src/NAVIGATORS.ts
index 5b7369b7dddf..a042f4e6659e 100644
--- a/src/NAVIGATORS.ts
+++ b/src/NAVIGATORS.ts
@@ -8,6 +8,7 @@ export default {
     ONBOARDING_MODAL_NAVIGATOR: 'OnboardingModalNavigator',
     FEATURE_TRAINING_MODAL_NAVIGATOR: 'FeatureTrainingModalNavigator',
     MIGRATED_USER_MODAL_NAVIGATOR: 'MigratedUserModalNavigator',
+    AI_FEATURES_PROMO_MODAL_NAVIGATOR: 'AIFeaturesPromoModalNavigator',
     TEST_DRIVE_DEMO_NAVIGATOR: 'TestDriveDemoNavigator',
     REPORTS_SPLIT_NAVIGATOR: 'ReportsSplitNavigator',
     SETTINGS_SPLIT_NAVIGATOR: 'SettingsSplitNavigator',
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 8d9908767d6c..815445e71315 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -144,6 +144,10 @@ const DYNAMIC_ROUTES = {
         path: 'migrated-user-welcome',
         entryScreens: [SCREENS.HOME, SCREENS.INBOX, SCREENS.REPORT, SCREENS.SEARCH.ROOT, SCREENS.WORKSPACES_LIST, SCREENS.WORKSPACE.PROFILE, SCREENS.SETTINGS.ROOT],
     },
+    AI_FEATURES_PROMO: {
+        path: 'ai-features-promo',
+        entryScreens: ['*'],
+    },
     EXPENSE_LIMIT_TYPE_SELECTOR: {
         path: 'expense-limit-type',
         entryScreens: [SCREENS.WORKSPACE.DYNAMIC_CATEGORY_FLAG_AMOUNTS_OVER],
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 68cb4353f4f0..877bab0bc953 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -967,6 +967,10 @@ const SCREENS = {
         DYNAMIC_ROOT: 'Dynamic_MigratedUserWelcomeModal_Root',
     },
 
+    AI_FEATURES_PROMO_MODAL: {
+        DYNAMIC_ROOT: 'Dynamic_AIFeaturesPromoModal_Root',
+    },
+
     TEST_DRIVE_DEMO: {
         ROOT: 'TestDrive_Demo_Root',
     },
diff --git a/src/components/AIFeaturesPromoModal.tsx b/src/components/AIFeaturesPromoModal.tsx
new file mode 100644
index 000000000000..a3b9dca157d8
--- /dev/null
+++ b/src/components/AIFeaturesPromoModal.tsx
@@ -0,0 +1,107 @@
+import useLocalize from '@hooks/useLocalize';
+import usePermissions from '@hooks/usePermissions';
+import useThemeStyles from '@hooks/useThemeStyles';
+
+import {dismissProductTraining} from '@libs/actions/Welcome';
+import Navigation from '@libs/Navigation/Navigation';
+
+import variables from '@styles/variables';
+
+import CONST from '@src/CONST';
+
+import React from 'react';
+import {View} from 'react-native';
+
+import type {FeatureTrainingContentDataProps} from './FeatureTrainingContent';
+
+import Badge from './Badge';
+import CenteredModalLayout from './CenteredModalLayout';
+import {FeatureTrainingCarousel} from './FeatureTrainingContent';
+import LottieAnimations from './LottieAnimations';
+import Text from './Text';
+
+function AIFeaturesPromoModal() {
+    const {translate} = useLocalize();
+    const styles = useThemeStyles();
+    const {isBetaEnabled} = usePermissions();
+    const canUseCustomAgent = isBetaEnabled(CONST.BETAS.CUSTOM_AGENT);
+
+    const customAgentPromoTitle = (
+        <View style={[styles.dFlex, styles.flexRow]}>
+            <Text style={[styles.textHeadlineH1, styles.mb2]}>{translate('aiFeaturesPromoModal.customAgents.title')}</Text>
+            <Badge
+                isStrong
+                isCondensed
+                text={translate('common.beta')}
+                badgeStyles={styles.mb2}
+            />
+        </View>
+    );
+
+    const pages: FeatureTrainingContentDataProps[] = [
+        {
+            animation: LottieAnimations.SpendAnalysis,
+            title: translate('aiFeaturesPromoModal.spendAnalysis.title'),
+            subtitle: translate('aiFeaturesPromoModal.subtitle'),
+            description: translate('aiFeaturesPromoModal.spendAnalysis.description'),
+            confirmText: translate('common.next'),
+        },
+        {
+            animation: LottieAnimations.ExpenseAssistant,
+            title: translate('aiFeaturesPromoModal.expenseAssistant.title'),
+            subtitle: translate('aiFeaturesPromoModal.subtitle'),
+            description: translate('aiFeaturesPromoModal.expenseAssistant.description'),
+            confirmText: canUseCustomAgent ? translate('common.next') : translate('aiFeaturesPromoModal.confirmText'),
+        },
+        ...(canUseCustomAgent
+            ? [
+                  {
+                      animation: LottieAnimations.CustomAgents,
+                      title: customAgentPromoTitle,
+                      subtitle: translate('aiFeaturesPromoModal.subtitle'),
+                      description: translate('aiFeaturesPromoModal.customAgents.description'),
+                      confirmText: translate('aiFeaturesPromoModal.confirmText'),
+                  },
+              ]
+            : []),
+    ];
+
+    const dismissNVP = (isDismissedUsingCloseButton: boolean) => {
+        dismissProductTraining(CONST.AI_FEATURES_PROMO_MODAL, isDismissedUsingCloseButton);
+    };
+
+    const confirmAndCloseModal = () => {
+        Navigation.goBack();
+        dismissNVP(false);
+    };
+
+    const closeModal = () => {
+        Navigation.goBack();
+        dismissNVP(true);
+    };
+
+    return (
+        <CenteredModalLayout
+            onBackdropPress={closeModal}
+            width={variables.aiFeaturesPromoModalWidth}
+            contentStyle={styles.pt0}
+        >
+            <FeatureTrainingCarousel
+                pages={pages}
+                onConfirm={confirmAndCloseModal}
+                onClose={closeModal}
+                width={variables.aiFeaturesPromoModalWidth}
+                shouldRenderHTMLDescription
+                shouldUseScrollView
+                illustrationOuterContainerStyle={styles.p0}
+                illustrationAspectRatio={LottieAnimations.SpendAnalysis.w / LottieAnimations.SpendAnalysis.h}
+                contentInnerContainerStyles={styles.mb4}
+                titleStyles={styles.mb2}
+                confirmSentryLabel={CONST.SENTRY_LABEL.AI_FEATURES_PROMO_MODAL.CONFIRM_BUTTON}
+                helpSentryLabel={CONST.SENTRY_LABEL.AI_FEATURES_PROMO_MODAL.HELP_BUTTON}
+            />
+        </CenteredModalLayout>
+    );
+}
+
+export default AIFeaturesPromoModal;
diff --git a/src/components/FeatureTrainingContent.tsx b/src/components/FeatureTrainingContent.tsx
deleted file mode 100644
index 02969874c268..000000000000
--- a/src/components/FeatureTrainingContent.tsx
+++ /dev/null
@@ -1,342 +0,0 @@
-import useKeyboardState from '@hooks/useKeyboardState';
-import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
-import useLocalize from '@hooks/useLocalize';
-import useNetwork from '@hooks/useNetwork';
-import useResponsiveLayout from '@hooks/useResponsiveLayout';
-import useSafeAreaInsets from '@hooks/useSafeAreaInsets';
-import useStyleUtils from '@hooks/useStyleUtils';
-import useThemeStyles from '@hooks/useThemeStyles';
-import useWindowDimensions from '@hooks/useWindowDimensions';
-
-import Accessibility from '@libs/Accessibility';
-import isInLandscapeModeUtil from '@libs/isInLandscapeMode';
-import {getIsOffline} from '@libs/NetworkState';
-
-import variables from '@styles/variables';
-
-import CONST from '@src/CONST';
-import type IconAsset from '@src/types/utils/IconAsset';
-
-import type {ImageContentFit} from 'expo-image';
-import type {SourceLoadEventPayload} from 'expo-video';
-// eslint-disable-next-line no-restricted-imports -- type-only import from react-native
-import type {LayoutChangeEvent, ScrollView as RNScrollView, StyleProp, TextStyle, ViewStyle} from 'react-native';
-import type {MergeExclusive} from 'type-fest';
-
-import React, {useEffect, useRef, useState} from 'react';
-import {View} from 'react-native';
-import {GestureHandlerRootView} from 'react-native-gesture-handler';
-
-import type ImageSVGProps from './ImageSVG/types';
-import type DotLottieAnimation from './LottieAnimations/types';
-
-import Button from './Button';
-import CheckboxWithLabel from './CheckboxWithLabel';
-import FormAlertWithSubmitButton from './FormAlertWithSubmitButton';
-import ImageSVG from './ImageSVG';
-import Lottie from './Lottie';
-import LottieAnimations from './LottieAnimations';
-import ScrollView from './ScrollView';
-import Text from './Text';
-import VideoPlayer from './VideoPlayer';
-
-const VIDEO_ASPECT_RATIO = 1280 / 960;
-
-const CONTENT_PADDING = variables.spacing2;
-
-type VideoStatus = 'video' | 'animation';
-
-type BaseFeatureTrainingContentProps = {
-    /** The aspect ratio to preserve for the icon, video or animation */
-    illustrationAspectRatio?: number;
-
-    /** Style for the inner container of the animation */
-    illustrationInnerContainerStyle?: StyleProp<ViewStyle>;
-
-    /** Style for the outer container of the animation */
-    illustrationOuterContainerStyle?: StyleProp<ViewStyle>;
-
-    /** Title for the modal */
-    title?: string | React.ReactNode;
-
-    /** Describe what is showing */
-    description?: string;
-
-    /** Style for the title */
-    titleStyles?: StyleProp<TextStyle>;
-
-    /** Whether to show `Don't show me this again` option */
-    shouldShowDismissModalOption?: boolean;
-
-    /** Text to show on primary button */
-    confirmText: string;
-
-    /** A callback to call when user confirms */
-    onConfirm?: (willShowAgain: boolean) => void;
-
-    /** A callback to call when content wants to close */
-    onClose?: () => void;
-
-    /** Called whenever the "don't show again" checkbox value changes */
-    onWillShowAgainChange?: (willShowAgain: boolean) => void;
-
-    /** Text to show on secondary button */
-    helpText?: string;
-
-    /** Link to navigate to when user wants to learn more */
-    onHelp?: () => void;
-
-    /** Styles for the content container */
-    contentInnerContainerStyles?: StyleProp<ViewStyle>;
-
-    /** Styles for the content outer container */
-    contentOuterContainerStyles?: StyleProp<ViewStyle>;
-
-    /** Children to show below title and description and above buttons */
-    children?: React.ReactNode;
-
-    /** Content width for wide layouts */
-    width?: number;
-
-    /** Whether closing should happen on confirm */
-    shouldCloseOnConfirm?: boolean;
-
-    /** Whether the content is scrollable */
-    shouldUseScrollView?: boolean;
-};
-
-type FeatureTrainingContentVideoProps = {
-    /** Animation to show when video is unavailable */
-    animation?: DotLottieAnimation;
-
-    /** Additional styles for the animation */
-    animationStyle?: StyleProp<ViewStyle>;
-
-    /** URL for the video */
-    videoURL?: string;
-};
-
-type FeatureTrainingContentSVGProps = {
-    /** Expensicon for the page */
-    image: IconAsset;
-
-    /** Determines how the image should be resized to fit its container */
-    contentFitImage?: ImageContentFit;
-
-    /** The width of the image */
-    imageWidth?: ImageSVGProps['width'];
-
-    /** The height of the image */
-    imageHeight?: ImageSVGProps['height'];
-};
-
-type FeatureTrainingContentProps = BaseFeatureTrainingContentProps & MergeExclusive<FeatureTrainingContentVideoProps, FeatureTrainingContentSVGProps>;
-
-const LANDSCAPE_ILLUSTRATION_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO = 0.7;
-
-/**
- * Once the device has been online, lock to 'video' permanently.
- * While it has never been online, show 'animation' as a fallback.
- */
-function useVideoStatus(): VideoStatus {
-    const [isLockedToVideo, setIsLockedToVideo] = useState(() => !getIsOffline());
-    const {isOffline} = useNetwork({
-        onReconnect: () => setIsLockedToVideo(true),
-    });
-
-    return isLockedToVideo || !isOffline ? 'video' : 'animation';
-}
-
-function FeatureTrainingContent({
-    animation,
-    animationStyle,
-    illustrationInnerContainerStyle,
-    illustrationOuterContainerStyle,
-    videoURL,
-    illustrationAspectRatio: illustrationAspectRatioProp,
-    image,
-    contentFitImage,
-    width = variables.featureTrainingModalWidth,
-    title = '',
-    description = '',
-    titleStyles,
-    shouldShowDismissModalOption = false,
-    confirmText = '',
-    onConfirm,
-    onClose,
-    onWillShowAgainChange,
-    helpText = '',
-    onHelp,
-    children,
-    contentInnerContainerStyles,
-    contentOuterContainerStyles,
-    imageWidth,
-    imageHeight,
-    shouldCloseOnConfirm = true,
-    shouldUseScrollView: shouldUseScrollViewProp = false,
-}: FeatureTrainingContentProps) {
-    const styles = useThemeStyles();
-    const StyleUtils = useStyleUtils();
-    const {translate} = useLocalize();
-    const isReduceMotionEnabled = Accessibility.useReducedMotion();
-    const illustrations = useMemoizedLazyIllustrations(['Hands']);
-    const {onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout();
-    const {windowHeight, windowWidth} = useWindowDimensions();
-    const [willShowAgain, setWillShowAgain] = useState(true);
-    const [illustrationAspectRatio, setIllustrationAspectRatio] = useState(illustrationAspectRatioProp ?? VIDEO_ASPECT_RATIO);
-    const {shouldUseNarrowLayout} = useResponsiveLayout();
-    const videoStatus = useVideoStatus();
-    const scrollViewRef = useRef<RNScrollView>(null);
-    const [containerHeight, setContainerHeight] = useState(0);
-    const [contentHeight, setContentHeight] = useState(0);
-    const insets = useSafeAreaInsets();
-    const {isKeyboardActive} = useKeyboardState();
-    const isInLandscapeMode = isInLandscapeModeUtil(windowWidth, windowHeight);
-
-    const shouldUseScrollView = shouldUseScrollViewProp || isInLandscapeMode;
-
-    const setAspectRatio = (event: SourceLoadEventPayload) => {
-        const track = event.availableVideoTracks.at(0);
-
-        if (!track) {
-            return;
-        }
-
-        setIllustrationAspectRatio(track.size.width / track.size.height);
-    };
-
-    const renderIllustration = () => {
-        const aspectRatio = illustrationAspectRatio || VIDEO_ASPECT_RATIO;
-
-        return (
-            <View style={[isInLandscapeMode ? styles.h100 : styles.w100, illustrationInnerContainerStyle, (!!videoURL || !!image) && {aspectRatio}]}>
-                {!!image && (
-                    <ImageSVG
-                        src={image}
-                        contentFit={contentFitImage}
-                        width={imageWidth}
-                        height={imageHeight}
-                        testID={CONST.IMAGE_SVG_TEST_ID}
-                    />
-                )}
-                {!!videoURL && videoStatus === 'video' && (
-                    <GestureHandlerRootView>
-                        <VideoPlayer
-                            url={videoURL}
-                            videoPlayerStyle={[styles.onboardingVideoPlayer, {aspectRatio}]}
-                            onSourceLoaded={setAspectRatio}
-                            controlsStatus={CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE}
-                            shouldUseControlsBottomMargin={false}
-                            shouldPlay
-                            isLooping
-                        />
-                    </GestureHandlerRootView>
-                )}
-                {((!videoURL && !image) || (!!videoURL && videoStatus === 'animation')) && (
-                    <View style={[styles.flex1, styles.alignItemsCenter, styles.justifyContentCenter, !!videoURL && {aspectRatio}, animationStyle]}>
-                        {isReduceMotionEnabled && (animation ?? LottieAnimations.Hands) === LottieAnimations.Hands ? (
-                            <ImageSVG
-                                src={illustrations.Hands}
-                                style={styles.h100}
-                            />
-                        ) : (
-                            <Lottie
-                                source={animation ?? LottieAnimations.Hands}
-                                style={styles.h100}
-                                webStyle={shouldUseNarrowLayout ? styles.h100 : undefined}
-                                autoPlay
-                                loop
-                            />
-                        )}
-                    </View>
-                )}
-            </View>
-        );
-    };
-
-    const toggleWillShowAgain = () => {
-        onWillShowAgainChange?.(!willShowAgain);
-        setWillShowAgain((prev) => !prev);
-    };
-
-    const handleConfirm = () => {
-        onConfirm?.(willShowAgain);
-        if (shouldCloseOnConfirm) {
-            onClose?.();
-        }
-    };
-
-    useEffect(() => {
-        if (contentHeight <= containerHeight || onboardingIsMediumOrLargerScreenWidth || !shouldUseScrollView) {
-            return;
-        }
-        scrollViewRef.current?.scrollToEnd({animated: false});
-    }, [contentHeight, containerHeight, onboardingIsMediumOrLargerScreenWidth, shouldUseScrollView]);
-
-    const Wrapper = shouldUseScrollView ? ScrollView : View;
-
-    const wrapperStyles = shouldUseScrollView ? StyleUtils.getScrollableFeatureTrainingModalStyles(insets, isKeyboardActive) : {};
-
-    return (
-        <Wrapper
-            scrollsToTop={false}
-            style={[
-                onboardingIsMediumOrLargerScreenWidth && StyleUtils.getWidthStyle(width),
-                wrapperStyles.style,
-                isInLandscapeMode ? {maxHeight: windowHeight * CONST.MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO_LANDSCAPE_MODE} : styles.mh100,
-            ]}
-            contentContainerStyle={wrapperStyles.containerStyle}
-            keyboardShouldPersistTaps={shouldUseScrollView ? 'handled' : undefined}
-            ref={shouldUseScrollView ? scrollViewRef : undefined}
-            onLayout={shouldUseScrollView ? (e: LayoutChangeEvent) => setContainerHeight(e.nativeEvent.layout.height) : undefined}
-            onContentSizeChange={shouldUseScrollView ? (_w: number, h: number) => setContentHeight(h) : undefined}
-            // eslint-disable-next-line react/forbid-component-props -- fsClass is required for FullStory session masking
-            fsClass={CONST.FULLSTORY.CLASS.UNMASK}
-        >
-            <View
-                style={[
-                    onboardingIsMediumOrLargerScreenWidth ? {padding: CONTENT_PADDING} : {paddingHorizontal: CONTENT_PADDING},
-                    illustrationOuterContainerStyle,
-                    isInLandscapeMode ? [{maxHeight: windowHeight * LANDSCAPE_ILLUSTRATION_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO}, styles.alignSelfCenter] : undefined,
-                ]}
-            >
-                {renderIllustration()}
-            </View>
-            <View style={[styles.mt5, styles.mh5, contentOuterContainerStyles]}>
-                {!!title && !!description && (
-                    <View style={[onboardingIsMediumOrLargerScreenWidth ? [styles.gap1, styles.mb8] : styles.mb10, contentInnerContainerStyles]}>
-                        {typeof title === 'string' ? <Text style={[styles.textHeadlineH1, titleStyles]}>{title}</Text> : title}
-                        <Text style={styles.textSupporting}>{description}</Text>
-                        {children}
-                    </View>
-                )}
-                {shouldShowDismissModalOption && (
-                    <CheckboxWithLabel
-                        label={translate('featureTraining.doNotShowAgain')}
-                        accessibilityLabel={translate('featureTraining.doNotShowAgain')}
-                        style={styles.mb5}
-                        isChecked={!willShowAgain}
-                        onInputChange={toggleWillShowAgain}
-                    />
-                )}
-                {!!helpText && (
-                    <Button
-                        large
-                        style={styles.mb3}
-                        onPress={() => onHelp?.()}
-                        text={helpText}
-                    />
-                )}
-                <FormAlertWithSubmitButton
-                    onSubmit={handleConfirm}
-                    buttonText={confirmText}
-                    enabledWhenOffline
-                />
-            </View>
-        </Wrapper>
-    );
-}
-
-export default FeatureTrainingContent;
-
-export type {FeatureTrainingContentProps};
diff --git a/src/components/FeatureTrainingContent/FeatureTrainingCarousel.tsx b/src/components/FeatureTrainingContent/FeatureTrainingCarousel.tsx
new file mode 100644
index 000000000000..567e951dfa61
--- /dev/null
+++ b/src/components/FeatureTrainingContent/FeatureTrainingCarousel.tsx
@@ -0,0 +1,298 @@
+import Icon from '@components/Icon';
+import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
+import ScrollView from '@components/ScrollView';
+import Tooltip from '@components/Tooltip';
+
+import useKeyboardState from '@hooks/useKeyboardState';
+import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
+import useLocalize from '@hooks/useLocalize';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useSafeAreaInsets from '@hooks/useSafeAreaInsets';
+import useStyleUtils from '@hooks/useStyleUtils';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+
+import isInLandscapeModeUtil from '@libs/isInLandscapeMode';
+
+import variables from '@styles/variables';
+
+import CONST from '@src/CONST';
+
+// eslint-disable-next-line no-restricted-imports -- Type import needed for ref typing; no wrapper available
+import type {LayoutChangeEvent, FlatList as RNFlatList, ScrollView as RNScrollView, ViewabilityConfig, ViewStyle, ViewToken} from 'react-native';
+
+import React, {useEffect, useRef, useState} from 'react';
+import {FlatList, Platform, View} from 'react-native';
+
+import type {FeatureTrainingCarouselProps, FeatureTrainingContentDataProps} from './types';
+
+import FeatureTrainingContentBody from './FeatureTrainingContentBody';
+import FeatureTrainingContentBodyText from './FeatureTrainingContentBodyText';
+import FeatureTrainingContentIllustration from './FeatureTrainingContentIllustration';
+
+const CONTENT_PADDING = variables.spacing2;
+
+// A page is considered "viewable" — and `currentPage` updates — only once it occupies at least
+// 95% of the viewport. The viewability event fires for both user swipes and programmatic
+// scrollToIndex once the scroll has practically settled on a new page.
+const CAROUSEL_VIEWABILITY_CONFIG: ViewabilityConfig = {itemVisiblePercentThreshold: 95};
+const CAROUSEL_DOT_SIZE = 6;
+const PAGINATION_DOTS_BOTTOM_OFFSET = 5;
+
+const WEB_CAROUSEL_PAGE_SNAP_STYLE: ViewStyle = Platform.OS === 'web' ? ({scrollSnapAlign: 'start', scrollSnapStop: 'always'} as ViewStyle) : {};
+
+function FeatureTrainingCarousel({
+    pages,
+    width = variables.featureTrainingModalWidth,
+    titleStyles,
+    illustrationAspectRatio,
+    illustrationInnerContainerStyle,
+    illustrationOuterContainerStyle,
+    shouldRenderHTMLDescription = false,
+    shouldUseScrollView: shouldUseScrollViewProp = false,
+    helpText = '',
+    onHelp = () => {},
+    helpSentryLabel,
+    confirmSentryLabel,
+    contentInnerContainerStyles,
+    contentOuterContainerStyles,
+    onConfirm,
+    onClose,
+    onPageChange,
+}: FeatureTrainingCarouselProps) {
+    const styles = useThemeStyles();
+    const StyleUtils = useStyleUtils();
+    const theme = useTheme();
+    const {translate} = useLocalize();
+    const expensifyIcons = useMemoizedLazyExpensifyIcons(['Close']);
+    const {onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout();
+    const {windowHeight, windowWidth} = useWindowDimensions();
+    const insets = useSafeAreaInsets();
+    const {isKeyboardActive} = useKeyboardState();
+    const isInLandscapeMode = isInLandscapeModeUtil(windowWidth, windowHeight);
+
+    const [currentPage, setCurrentPage] = useState(0);
+    const [carouselViewportWidth, setCarouselViewportWidth] = useState(0);
+    const horizontalListRef = useRef<RNFlatList<FeatureTrainingContentDataProps>>(null);
+    const lastReportedPage = useRef(0);
+
+    const scrollViewRef = useRef<RNScrollView>(null);
+    const [containerHeight, setContainerHeight] = useState(0);
+    const [contentHeight, setContentHeight] = useState(0);
+    const [contentMinHeight, setContentMinHeight] = useState<number | undefined>(undefined);
+    const measuredHeightsRef = useRef<Record<number, number>>({});
+    const handleProbeLayout = (index: number) => (event: LayoutChangeEvent) => {
+        const measured = event.nativeEvent.layout.height;
+        if (measuredHeightsRef.current[index] === measured) {
+            return;
+        }
+        measuredHeightsRef.current[index] = measured;
+        if (Object.keys(measuredHeightsRef.current).length < pages.length) {
+            return;
+        }
+        setContentMinHeight(Math.max(...Object.values(measuredHeightsRef.current)));
+    };
+
+    const shouldUseScrollView = shouldUseScrollViewProp || isInLandscapeMode;
+
+    // FlatList's `onViewableItemsChanged` must keep a stable identity (it errors otherwise).
+    // The handler reads the latest `onPageChange` via a ref so the callback identity never changes.
+    const onPageChangeRef = useRef(onPageChange);
+    useEffect(() => {
+        onPageChangeRef.current = onPageChange;
+    }, [onPageChange]);
+
+    const onViewableItemsChanged = ({viewableItems}: {viewableItems: ViewToken[]}) => {
+        const entry = viewableItems.at(0);
+        if (entry?.index == null || entry.index === lastReportedPage.current) {
+            return;
+        }
+        lastReportedPage.current = entry.index;
+        setCurrentPage(entry.index);
+        onPageChangeRef.current?.(entry.index);
+    };
+
+    const advanceCarousel = () => {
+        horizontalListRef.current?.scrollToIndex({index: Math.min(currentPage + 1, pages.length - 1), animated: true});
+    };
+
+    const goBack = () => {
+        if (currentPage <= 0) {
+            return;
+        }
+        horizontalListRef.current?.scrollToIndex({index: Math.max(currentPage - 1, 0), animated: true});
+    };
+
+    const confirmPage = () => {
+        if (currentPage < pages.length - 1) {
+            advanceCarousel();
+            return;
+        }
+        onConfirm?.(false);
+    };
+
+    useEffect(() => {
+        if (contentHeight <= containerHeight || onboardingIsMediumOrLargerScreenWidth || !shouldUseScrollView) {
+            return;
+        }
+        scrollViewRef.current?.scrollToEnd({animated: false});
+    }, [contentHeight, containerHeight, onboardingIsMediumOrLargerScreenWidth, shouldUseScrollView]);
+
+    const Wrapper = shouldUseScrollView ? ScrollView : View;
+
+    const wrapperStyles = shouldUseScrollView ? StyleUtils.getScrollableFeatureTrainingModalStyles(insets, isKeyboardActive) : {};
+
+    const carouselPaginationDots = pages.map((_page, index) => (
+        <View
+            // The array is static for the modal's lifetime, so the index is a stable key.
+            // eslint-disable-next-line react/no-array-index-key
+            key={`carousel-dot-${index}`}
+            style={StyleUtils.getFeatureTrainingCarouselDotStyle(CAROUSEL_DOT_SIZE, theme.buttonSuccessText, index === currentPage)}
+        />
+    ));
+
+    const currentPageData = pages.at(currentPage);
+
+    return (
+        <Wrapper
+            scrollsToTop={false}
+            style={[
+                onboardingIsMediumOrLargerScreenWidth && StyleUtils.getWidthStyle(width),
+                wrapperStyles.style,
+                isInLandscapeMode ? {maxHeight: windowHeight * CONST.MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO_LANDSCAPE_MODE} : styles.mh100,
+            ]}
+            contentContainerStyle={isInLandscapeMode ? wrapperStyles.containerStyle : undefined}
+            keyboardShouldPersistTaps={shouldUseScrollView ? 'handled' : undefined}
+            ref={shouldUseScrollView ? scrollViewRef : undefined}
+            onLayout={(e: LayoutChangeEvent) => {
+                const newWidth = e.nativeEvent.layout.width;
+                if (newWidth === carouselViewportWidth || newWidth <= 0) {
+                    return;
+                }
+                setCarouselViewportWidth(newWidth);
+                if (!shouldUseScrollView) {
+                    return;
+                }
+                setContainerHeight(e.nativeEvent.layout.height);
+            }}
+            onContentSizeChange={shouldUseScrollView ? (_w: number, h: number) => setContentHeight(h) : undefined}
+            // eslint-disable-next-line react/forbid-component-props -- fsClass is required for FullStory session masking
+            fsClass={CONST.FULLSTORY.CLASS.UNMASK}
+        >
+            {carouselViewportWidth > 0 &&
+                contentMinHeight === undefined && (
+                    // Probe layer is used to measure the tallest page to lock the modal height
+                    // when moving between pages with different content lengths.
+                    <View
+                        pointerEvents="none"
+                        accessibilityElementsHidden
+                        importantForAccessibility="no-hide-descendants"
+                        style={[styles.pAbsolute, styles.l0, styles.t0, {width: carouselViewportWidth, opacity: 0}]}
+                    >
+                        {pages.map((page, index) => (
+                            <View
+                                // The pages array is static for the modal's lifetime, so the index is a stable key.
+                                // eslint-disable-next-line react/no-array-index-key
+                                key={`FeatureTrainingModalCarousel-probe-${index}`}
+                                style={styles.mh5}
+                            >
+                                <FeatureTrainingContentBodyText
+                                    title={page.title}
+                                    subtitle={page.subtitle}
+                                    description={page.description}
+                                    titleStyles={titleStyles}
+                                    contentInnerContainerStyles={contentInnerContainerStyles}
+                                    shouldRenderHTMLDescription={shouldRenderHTMLDescription}
+                                    onLayout={handleProbeLayout(index)}
+                                />
+                            </View>
+                        ))}
+                    </View>
+                )}
+            {carouselViewportWidth > 0 && (
+                <>
+                    <View>
+                        <FlatList
+                            ref={horizontalListRef}
+                            data={pages}
+                            keyExtractor={(_page, index) => `FeatureTrainingContentIllustration-${index}`}
+                            horizontal
+                            pagingEnabled
+                            disableIntervalMomentum
+                            snapToInterval={carouselViewportWidth}
+                            decelerationRate="fast"
+                            bounces={false}
+                            showsHorizontalScrollIndicator={false}
+                            keyboardShouldPersistTaps="handled"
+                            viewabilityConfig={CAROUSEL_VIEWABILITY_CONFIG}
+                            onViewableItemsChanged={onViewableItemsChanged}
+                            getItemLayout={(_data, index) => ({length: carouselViewportWidth, offset: index * carouselViewportWidth, index})}
+                            renderItem={({item: page, index}) => (
+                                <View style={[{width: carouselViewportWidth}, WEB_CAROUSEL_PAGE_SNAP_STYLE]}>
+                                    <FeatureTrainingContentIllustration
+                                        illustrationAspectRatio={illustrationAspectRatio}
+                                        illustrationInnerContainerStyle={illustrationInnerContainerStyle}
+                                        illustrationOuterContainerStyle={illustrationOuterContainerStyle}
+                                        isCarousel
+                                        isFocused={index === currentPage}
+                                        {...page}
+                                    />
+                                </View>
+                            )}
+                        />
+                        <View
+                            pointerEvents="none"
+                            style={[
+                                styles.pAbsolute,
+                                styles.flexRow,
+                                styles.justifyContentCenter,
+                                styles.w100,
+                                styles.l0,
+                                styles.r0,
+                                StyleUtils.getFeatureTrainingCarouselDotsContainerStyle(PAGINATION_DOTS_BOTTOM_OFFSET + CONTENT_PADDING),
+                            ]}
+                        >
+                            {carouselPaginationDots}
+                        </View>
+                    </View>
+                    <FeatureTrainingContentBody
+                        title={currentPageData?.title}
+                        subtitle={currentPageData?.subtitle}
+                        description={currentPageData?.description}
+                        confirmText={currentPageData?.confirmText ?? ''}
+                        helpText={helpText}
+                        onHelp={onHelp}
+                        helpSentryLabel={helpSentryLabel}
+                        confirmSentryLabel={confirmSentryLabel}
+                        onConfirm={confirmPage}
+                        shouldShowBackButton={currentPage > 0}
+                        onBack={goBack}
+                        titleStyles={titleStyles}
+                        contentInnerContainerStyles={[contentInnerContainerStyles, contentMinHeight !== undefined && {minHeight: contentMinHeight}]}
+                        contentOuterContainerStyles={contentOuterContainerStyles}
+                        shouldRenderHTMLDescription={shouldRenderHTMLDescription}
+                    />
+                    <View style={StyleUtils.getFeatureTrainingCarouselCloseButtonContainerStyle(CONTENT_PADDING)}>
+                        <Tooltip text={translate('common.close')}>
+                            <PressableWithFeedback
+                                onPress={onClose}
+                                role={CONST.ROLE.BUTTON}
+                                accessibilityLabel={translate('common.close')}
+                                sentryLabel={CONST.SENTRY_LABEL.FEATURE_TRAINING.CLOSE_BUTTON}
+                                style={[styles.p2, styles.opacitySemiTransparent]}
+                            >
+                                <Icon
+                                    src={expensifyIcons.Close}
+                                    fill={theme.buttonSuccessText}
+                                />
+                            </PressableWithFeedback>
+                        </Tooltip>
+                    </View>
+                </>
+            )}
+        </Wrapper>
+    );
+}
+
+export default FeatureTrainingCarousel;
diff --git a/src/components/FeatureTrainingContent/FeatureTrainingContent.tsx b/src/components/FeatureTrainingContent/FeatureTrainingContent.tsx
new file mode 100644
index 000000000000..792e4e070372
--- /dev/null
+++ b/src/components/FeatureTrainingContent/FeatureTrainingContent.tsx
@@ -0,0 +1,136 @@
+import ScrollView from '@components/ScrollView';
+
+import useKeyboardState from '@hooks/useKeyboardState';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useSafeAreaInsets from '@hooks/useSafeAreaInsets';
+import useStyleUtils from '@hooks/useStyleUtils';
+import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+
+import isInLandscapeModeUtil from '@libs/isInLandscapeMode';
+
+import variables from '@styles/variables';
+
+import CONST from '@src/CONST';
+
+// eslint-disable-next-line no-restricted-imports -- type-only import from react-native
+import type {LayoutChangeEvent, ScrollView as RNScrollView} from 'react-native';
+
+import React, {useEffect, useRef, useState} from 'react';
+import {View} from 'react-native';
+
+import type {FeatureTrainingContentProps} from './types';
+
+import FeatureTrainingContentBody from './FeatureTrainingContentBody';
+import FeatureTrainingContentIllustration from './FeatureTrainingContentIllustration';
+
+function FeatureTrainingContent({
+    illustrationInnerContainerStyle,
+    illustrationOuterContainerStyle,
+    illustrationAspectRatio: illustrationAspectRatioProp,
+    width = variables.featureTrainingModalWidth,
+    title = '',
+    subtitle = '',
+    description = '',
+    titleStyles,
+    shouldShowDismissModalOption = false,
+    confirmText = '',
+    onConfirm,
+    onClose,
+    onWillShowAgainChange,
+    helpText = '',
+    onHelp,
+    children,
+    contentInnerContainerStyles,
+    contentOuterContainerStyles,
+    shouldRenderHTMLDescription = false,
+    shouldCloseOnConfirm = true,
+    shouldUseScrollView: shouldUseScrollViewProp = false,
+    helpSentryLabel,
+    confirmSentryLabel,
+    ...props
+}: FeatureTrainingContentProps) {
+    const styles = useThemeStyles();
+    const StyleUtils = useStyleUtils();
+    const {onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout();
+    const {windowHeight, windowWidth} = useWindowDimensions();
+    const [willShowAgain, setWillShowAgain] = useState(true);
+    const scrollViewRef = useRef<RNScrollView>(null);
+    const [containerHeight, setContainerHeight] = useState(0);
+    const [contentHeight, setContentHeight] = useState(0);
+    const insets = useSafeAreaInsets();
+    const {isKeyboardActive} = useKeyboardState();
+    const isInLandscapeMode = isInLandscapeModeUtil(windowWidth, windowHeight);
+
+    const shouldUseScrollView = shouldUseScrollViewProp || isInLandscapeMode;
+
+    const toggleWillShowAgain = () => {
+        onWillShowAgainChange?.(!willShowAgain);
+        setWillShowAgain((prev) => !prev);
+    };
+
+    const handleConfirm = () => {
+        onConfirm?.(willShowAgain);
+        if (shouldCloseOnConfirm) {
+            onClose?.();
+        }
+    };
+
+    useEffect(() => {
+        if (contentHeight <= containerHeight || onboardingIsMediumOrLargerScreenWidth || !shouldUseScrollView) {
+            return;
+        }
+        scrollViewRef.current?.scrollToEnd({animated: false});
+    }, [contentHeight, containerHeight, onboardingIsMediumOrLargerScreenWidth, shouldUseScrollView]);
+
+    const Wrapper = shouldUseScrollView ? ScrollView : View;
+
+    const wrapperStyles = shouldUseScrollView ? StyleUtils.getScrollableFeatureTrainingModalStyles(insets, isKeyboardActive) : {};
+
+    return (
+        <Wrapper
+            scrollsToTop={false}
+            style={[
+                onboardingIsMediumOrLargerScreenWidth && StyleUtils.getWidthStyle(width),
+                wrapperStyles.style,
+                isInLandscapeMode ? {maxHeight: windowHeight * CONST.MODAL_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO_LANDSCAPE_MODE} : styles.mh100,
+            ]}
+            contentContainerStyle={wrapperStyles.containerStyle}
+            keyboardShouldPersistTaps={shouldUseScrollView ? 'handled' : undefined}
+            ref={shouldUseScrollView ? scrollViewRef : undefined}
+            onLayout={shouldUseScrollView ? (e: LayoutChangeEvent) => setContainerHeight(e.nativeEvent.layout.height) : undefined}
+            onContentSizeChange={shouldUseScrollView ? (_w: number, h: number) => setContentHeight(h) : undefined}
+            // eslint-disable-next-line react/forbid-component-props -- fsClass is required for FullStory session masking
+            fsClass={CONST.FULLSTORY.CLASS.UNMASK}
+        >
+            <FeatureTrainingContentIllustration
+                illustrationAspectRatio={illustrationAspectRatioProp}
+                illustrationInnerContainerStyle={illustrationInnerContainerStyle}
+                illustrationOuterContainerStyle={illustrationOuterContainerStyle}
+                {...props}
+            />
+            <FeatureTrainingContentBody
+                title={title}
+                subtitle={subtitle}
+                description={description}
+                confirmText={confirmText}
+                helpText={helpText}
+                onHelp={onHelp}
+                helpSentryLabel={helpSentryLabel}
+                confirmSentryLabel={confirmSentryLabel}
+                shouldShowDismissModalOption={shouldShowDismissModalOption}
+                willShowAgain={willShowAgain}
+                toggleWillShowAgain={toggleWillShowAgain}
+                onConfirm={handleConfirm}
+                titleStyles={titleStyles}
+                contentInnerContainerStyles={contentInnerContainerStyles}
+                contentOuterContainerStyles={contentOuterContainerStyles}
+                shouldRenderHTMLDescription={shouldRenderHTMLDescription}
+            >
+                {children}
+            </FeatureTrainingContentBody>
+        </Wrapper>
+    );
+}
+
+export default FeatureTrainingContent;
diff --git a/src/components/FeatureTrainingContent/FeatureTrainingContentBody.tsx b/src/components/FeatureTrainingContent/FeatureTrainingContentBody.tsx
new file mode 100644
index 000000000000..467bf1a52a30
--- /dev/null
+++ b/src/components/FeatureTrainingContent/FeatureTrainingContentBody.tsx
@@ -0,0 +1,123 @@
+import Button from '@components/ButtonComposed';
+import CheckboxWithLabel from '@components/CheckboxWithLabel';
+import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton';
+
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+
+import CONST from '@src/CONST';
+
+import React from 'react';
+import {View} from 'react-native';
+
+import type {FeatureTrainingContentBodyProps as BaseFeatureTrainingContentBodyProps, BaseFeatureTrainingContentProps} from './types';
+
+import FeatureTrainingContentBodyText from './FeatureTrainingContentBodyText';
+
+type FeatureTrainingModalContentProps = Pick<
+    BaseFeatureTrainingContentProps,
+    | 'helpText'
+    | 'onConfirm'
+    | 'onHelp'
+    | 'helpSentryLabel'
+    | 'confirmSentryLabel'
+    | 'shouldShowDismissModalOption'
+    | 'titleStyles'
+    | 'contentInnerContainerStyles'
+    | 'contentOuterContainerStyles'
+    | 'shouldRenderHTMLDescription'
+    | 'children'
+> &
+    BaseFeatureTrainingContentBodyProps & {
+        /** Whether the modal should be shown again (drives the dismiss checkbox state) */
+        willShowAgain?: boolean;
+
+        /** Callback when the "Don't show me this again" option is toggled */
+        toggleWillShowAgain?: () => void;
+
+        /** Whether to render a Back button (carousel mode, non-first pages) */
+        shouldShowBackButton?: boolean;
+
+        /** Callback when the Back button is pressed */
+        onBack?: () => void;
+    };
+
+function FeatureTrainingContentBody({
+    title = '',
+    subtitle = '',
+    description = '',
+    confirmText,
+    onConfirm,
+    helpText = '',
+    onHelp,
+    helpSentryLabel,
+    confirmSentryLabel,
+    shouldShowDismissModalOption = false,
+    willShowAgain = false,
+    toggleWillShowAgain,
+    shouldShowBackButton = false,
+    onBack,
+    titleStyles,
+    contentInnerContainerStyles,
+    contentOuterContainerStyles,
+    shouldRenderHTMLDescription = false,
+    children,
+}: FeatureTrainingModalContentProps) {
+    const styles = useThemeStyles();
+    const {translate} = useLocalize();
+
+    return (
+        <View style={[styles.mt5, styles.mh5, contentOuterContainerStyles]}>
+            <FeatureTrainingContentBodyText
+                title={title}
+                subtitle={subtitle}
+                description={description}
+                titleStyles={titleStyles}
+                contentInnerContainerStyles={contentInnerContainerStyles}
+                shouldRenderHTMLDescription={shouldRenderHTMLDescription}
+            >
+                {children}
+            </FeatureTrainingContentBodyText>
+            {shouldShowDismissModalOption && (
+                <CheckboxWithLabel
+                    label={translate('featureTraining.doNotShowAgain')}
+                    accessibilityLabel={translate('featureTraining.doNotShowAgain')}
+                    style={[styles.mb5]}
+                    isChecked={!willShowAgain}
+                    onInputChange={toggleWillShowAgain}
+                />
+            )}
+            {!!helpText && (
+                <Button
+                    size={CONST.BUTTON_SIZE.LARGE}
+                    style={[styles.mb3]}
+                    onPress={onHelp}
+                    sentryLabel={helpSentryLabel}
+                >
+                    <Button.Text>{helpText}</Button.Text>
+                </Button>
+            )}
+            <View style={styles.featureTrainingModalNavButtons}>
+                {shouldShowBackButton && (
+                    <Button
+                        size={CONST.BUTTON_SIZE.LARGE}
+                        onPress={onBack}
+                        sentryLabel={CONST.SENTRY_LABEL.FEATURE_TRAINING.BACK_BUTTON}
+                        style={styles.flex1}
+                    >
+                        <Button.Text>{translate('common.back')}</Button.Text>
+                    </Button>
+                )}
+                <FormAlertWithSubmitButton
+                    onSubmit={() => onConfirm?.(willShowAgain)}
+                    buttonText={confirmText}
+                    enabledWhenOffline
+                    sentryLabel={confirmSentryLabel}
+                    containerStyles={styles.flex1}
+                />
+            </View>
+        </View>
+    );
+}
+
+export default FeatureTrainingContentBody;
diff --git a/src/components/FeatureTrainingContent/FeatureTrainingContentBodyText.tsx b/src/components/FeatureTrainingContent/FeatureTrainingContentBodyText.tsx
new file mode 100644
index 000000000000..757fd4f0db7f
--- /dev/null
+++ b/src/components/FeatureTrainingContent/FeatureTrainingContentBodyText.tsx
@@ -0,0 +1,74 @@
+import RenderHTML from '@components/RenderHTML';
+import Text from '@components/Text';
+
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useThemeStyles from '@hooks/useThemeStyles';
+
+import type {LayoutChangeEvent, StyleProp, TextStyle, ViewStyle} from 'react-native';
+
+import React from 'react';
+import {View} from 'react-native';
+
+type FeatureTrainingModalTextContentProps = {
+    /** Title for the modal */
+    title?: string | React.ReactNode;
+
+    /** Subtitle for the modal */
+    subtitle?: string;
+
+    /** Describe what is showing */
+    description?: string;
+
+    /** Style for the title */
+    titleStyles?: StyleProp<TextStyle>;
+
+    /** Styles applied to the inner text container */
+    contentInnerContainerStyles?: StyleProp<ViewStyle>;
+
+    /** Whether description is HTML markup */
+    shouldRenderHTMLDescription?: boolean;
+
+    /** Children rendered below the description (single-page mode only) */
+    children?: React.ReactNode;
+
+    /** onLayout hook — used by the carousel probe to measure the tallest page */
+    onLayout?: (event: LayoutChangeEvent) => void;
+};
+
+function FeatureTrainingContentBodyText({
+    title = '',
+    subtitle = '',
+    description = '',
+    titleStyles,
+    contentInnerContainerStyles,
+    shouldRenderHTMLDescription = false,
+    children,
+    onLayout,
+}: FeatureTrainingModalTextContentProps) {
+    const styles = useThemeStyles();
+    const {onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout();
+
+    if (!title || !description) {
+        return null;
+    }
+
+    return (
+        <View
+            style={[onboardingIsMediumOrLargerScreenWidth ? [styles.gap1, styles.mb8] : [shouldRenderHTMLDescription ? styles.mb5 : styles.mb10], contentInnerContainerStyles]}
+            onLayout={onLayout}
+        >
+            {!!subtitle && <Text style={[styles.textLabel, styles.textBold, styles.textSuccess]}>{subtitle}</Text>}
+            {typeof title === 'string' ? <Text style={[styles.textHeadlineH1, titleStyles]}>{title}</Text> : title}
+            {shouldRenderHTMLDescription ? (
+                <View style={[styles.flexRow, styles.w100, styles.mb2, styles.renderHTML]}>
+                    <RenderHTML html={description} />
+                </View>
+            ) : (
+                <Text style={styles.textSupporting}>{description}</Text>
+            )}
+            {children}
+        </View>
+    );
+}
+
+export default FeatureTrainingContentBodyText;
diff --git a/src/components/FeatureTrainingContent/FeatureTrainingContentIllustration.tsx b/src/components/FeatureTrainingContent/FeatureTrainingContentIllustration.tsx
new file mode 100644
index 000000000000..0716dca3987f
--- /dev/null
+++ b/src/components/FeatureTrainingContent/FeatureTrainingContentIllustration.tsx
@@ -0,0 +1,170 @@
+import ImageSVG from '@components/ImageSVG';
+import Lottie from '@components/Lottie';
+import LottieAnimations from '@components/LottieAnimations';
+import VideoPlayer from '@components/VideoPlayer';
+
+import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
+import useNetwork from '@hooks/useNetwork';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+
+import Accessibility from '@libs/Accessibility';
+import {isMobile} from '@libs/Browser';
+import isInLandscapeModeUtil from '@libs/isInLandscapeMode';
+import {getIsOffline} from '@libs/NetworkState';
+
+import variables from '@styles/variables';
+
+import CONST from '@src/CONST';
+
+import type {SourceLoadEventPayload} from 'expo-video';
+import type LottieView from 'lottie-react-native';
+
+import React, {useEffect, useRef, useState} from 'react';
+import {View} from 'react-native';
+import {GestureHandlerRootView} from 'react-native-gesture-handler';
+
+import type {FeatureTrainingContentIllustrationProps as BaseFeatureTrainingContentIllustrationProps, BaseFeatureTrainingContentProps} from './types';
+
+// Aspect ratio and height of the video.
+// Useful before video loads to reserve space.
+const VIDEO_ASPECT_RATIO = 1280 / 960;
+
+const CONTENT_PADDING = variables.spacing2;
+
+const LANDSCAPE_ILLUSTRATION_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO = 0.7;
+
+type VideoStatus = 'video' | 'animation';
+
+type FeatureTrainingContentIllustrationProps = Pick<BaseFeatureTrainingContentProps, 'illustrationAspectRatio' | 'illustrationInnerContainerStyle' | 'illustrationOuterContainerStyle'> &
+    BaseFeatureTrainingContentIllustrationProps & {
+        /** Whether this illustration belongs to the currently-visible carousel page */
+        isFocused?: boolean;
+
+        /** Whether this illustration is part of a carousel */
+        isCarousel?: boolean;
+    };
+
+/**
+ * Once the device has been online, lock to 'video' permanently.
+ * While it has never been online, show 'animation' as a fallback.
+ */
+function useVideoStatus(): VideoStatus {
+    const [isLockedToVideo, setIsLockedToVideo] = useState(() => !getIsOffline());
+    const {isOffline} = useNetwork({
+        onReconnect: () => setIsLockedToVideo(true),
+    });
+
+    return isLockedToVideo || !isOffline ? 'video' : 'animation';
+}
+
+function FeatureTrainingContentIllustration({
+    animation,
+    animationStyle,
+    videoURL,
+    image,
+    contentFitImage,
+    imageWidth,
+    imageHeight,
+    illustrationAspectRatio: illustrationAspectRatioProp,
+    illustrationInnerContainerStyle,
+    illustrationOuterContainerStyle,
+    isFocused = true,
+    isCarousel = false,
+}: FeatureTrainingContentIllustrationProps) {
+    const styles = useThemeStyles();
+    const isReduceMotionEnabled = Accessibility.useReducedMotion();
+    const illustrations = useMemoizedLazyIllustrations(['Hands']);
+    const {onboardingIsMediumOrLargerScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout();
+    const videoStatus = useVideoStatus();
+    const {windowHeight, windowWidth} = useWindowDimensions();
+    const [illustrationAspectRatio, setIllustrationAspectRatio] = useState(illustrationAspectRatioProp ?? VIDEO_ASPECT_RATIO);
+    const isInLandscapeMode = isInLandscapeModeUtil(windowWidth, windowHeight);
+
+    const animationRef = useRef<LottieView | null>(null);
+    useEffect(() => {
+        if (isMobile() || !isCarousel || !animationRef.current || isReduceMotionEnabled) {
+            return;
+        }
+        if (isFocused) {
+            animationRef.current.play();
+        } else {
+            animationRef.current.pause();
+        }
+    }, [isFocused, isCarousel, isReduceMotionEnabled]);
+
+    const setAspectRatio = (event: SourceLoadEventPayload) => {
+        const track = event.availableVideoTracks.at(0);
+        if (!track) {
+            return;
+        }
+        setIllustrationAspectRatio(track.size.width / track.size.height);
+    };
+
+    const aspectRatio = illustrationAspectRatio || VIDEO_ASPECT_RATIO;
+
+    return (
+        <View
+            style={[
+                onboardingIsMediumOrLargerScreenWidth ? {padding: CONTENT_PADDING} : {paddingHorizontal: CONTENT_PADDING},
+                illustrationOuterContainerStyle,
+                isInLandscapeMode ? [{maxHeight: windowHeight * LANDSCAPE_ILLUSTRATION_MAX_HEIGHT_TO_WINDOW_HEIGHT_RATIO}, styles.alignSelfCenter] : undefined,
+            ]}
+        >
+            <View
+                style={[
+                    isInLandscapeMode ? styles.h100 : styles.w100,
+                    // Prevent layout jumps by reserving height for the video until it loads.
+                    // When videoStatus === 'animation' it preserves the same aspect ratio.
+                    illustrationInnerContainerStyle,
+                    (!!videoURL || !!image || (!!animation && !!illustrationAspectRatioProp)) && {aspectRatio},
+                ]}
+            >
+                {!!image && (
+                    <ImageSVG
+                        src={image}
+                        contentFit={contentFitImage}
+                        width={imageWidth}
+                        height={imageHeight}
+                        testID={CONST.IMAGE_SVG_TEST_ID}
+                    />
+                )}
+                {!!videoURL && videoStatus === 'video' && (
+                    <GestureHandlerRootView>
+                        <VideoPlayer
+                            url={videoURL}
+                            videoPlayerStyle={[styles.onboardingVideoPlayer, {aspectRatio}]}
+                            onSourceLoaded={setAspectRatio}
+                            controlsStatus={CONST.VIDEO_PLAYER.CONTROLS_STATUS.HIDE}
+                            shouldUseControlsBottomMargin={false}
+                            shouldPlay
+                            isLooping
+                        />
+                    </GestureHandlerRootView>
+                )}
+                {((!videoURL && !image) || (!!videoURL && videoStatus === 'animation')) && (
+                    <View style={[styles.flex1, styles.alignItemsCenter, styles.justifyContentCenter, !!videoURL && {aspectRatio}, animationStyle]}>
+                        {isReduceMotionEnabled && (animation ?? LottieAnimations.Hands) === LottieAnimations.Hands ? (
+                            <ImageSVG
+                                src={illustrations.Hands}
+                                style={styles.h100}
+                            />
+                        ) : (
+                            <Lottie
+                                ref={animationRef}
+                                source={animation ?? LottieAnimations.Hands}
+                                style={styles.h100}
+                                webStyle={shouldUseNarrowLayout ? styles.h100 : undefined}
+                                autoPlay={isMobile() || isFocused}
+                                loop
+                            />
+                        )}
+                    </View>
+                )}
+            </View>
+        </View>
+    );
+}
+
+export default FeatureTrainingContentIllustration;
diff --git a/src/components/FeatureTrainingContent/index.tsx b/src/components/FeatureTrainingContent/index.tsx
new file mode 100644
index 000000000000..ce07f8f6eb2c
--- /dev/null
+++ b/src/components/FeatureTrainingContent/index.tsx
@@ -0,0 +1,8 @@
+import type {FeatureTrainingContentDataProps, FeatureTrainingContentProps} from './types';
+
+import FeatureTrainingCarousel from './FeatureTrainingCarousel';
+import FeatureTrainingContent from './FeatureTrainingContent';
+
+export default FeatureTrainingContent;
+export {FeatureTrainingCarousel};
+export type {FeatureTrainingContentProps, FeatureTrainingContentDataProps};
diff --git a/src/components/FeatureTrainingContent/types.ts b/src/components/FeatureTrainingContent/types.ts
new file mode 100644
index 000000000000..4ec58b1fb41b
--- /dev/null
+++ b/src/components/FeatureTrainingContent/types.ts
@@ -0,0 +1,148 @@
+import type ImageSVGProps from '@components/ImageSVG/types';
+import type DotLottieAnimation from '@components/LottieAnimations/types';
+
+import type IconAsset from '@src/types/utils/IconAsset';
+
+import type {ImageContentFit} from 'expo-image';
+import type {ReactNode} from 'react';
+import type {StyleProp, TextStyle, ViewStyle} from 'react-native';
+import type {MergeExclusive} from 'type-fest';
+
+type BaseFeatureTrainingContentProps = {
+    /** The aspect ratio to preserve for the icon, video or animation */
+    illustrationAspectRatio?: number;
+
+    /** Style for the inner container of the animation */
+    illustrationInnerContainerStyle?: StyleProp<ViewStyle>;
+
+    /** Style for the outer container of the animation */
+    illustrationOuterContainerStyle?: StyleProp<ViewStyle>;
+
+    /** Style for the title */
+    titleStyles?: StyleProp<TextStyle>;
+
+    /** Whether to show `Don't show me this again` option */
+    shouldShowDismissModalOption?: boolean;
+
+    /** A callback to call when user confirms the tutorial */
+    onConfirm?: (willShowAgain: boolean) => void;
+
+    /** A callback to call when modal closes */
+    onClose?: () => void;
+
+    /** Called whenever the "don't show again" checkbox value changes */
+    onWillShowAgainChange?: (willShowAgain: boolean) => void;
+
+    /** Text to show on secondary button */
+    helpText?: string;
+
+    /** Link to navigate to when user wants to learn more */
+    onHelp?: () => void;
+
+    /** Styles for the content container */
+    contentInnerContainerStyles?: StyleProp<ViewStyle>;
+
+    /** Styles for the content outer container */
+    contentOuterContainerStyles?: StyleProp<ViewStyle>;
+
+    /** Styles for the modal inner container */
+    modalInnerContainerStyle?: ViewStyle;
+
+    /** Children to show below title and description and above buttons (single-page mode only) */
+    children?: ReactNode;
+
+    /** Modal width */
+    width?: number;
+
+    /** Whether to disable the modal */
+    isModalDisabled?: boolean;
+
+    /** Whether the modal description is written in HTML */
+    shouldRenderHTMLDescription?: boolean;
+
+    /** Whether the modal will be closed on confirm */
+    shouldCloseOnConfirm?: boolean;
+
+    /** Whether the modal should avoid the keyboard */
+    avoidKeyboard?: boolean;
+
+    /** Whether the modal content is scrollable */
+    shouldUseScrollView?: boolean;
+
+    /** Whether to navigate back when closing the modal */
+    shouldGoBack?: boolean;
+
+    /** Whether to call onHelp when modal is hidden completely */
+    shouldCallOnHelpWhenModalHidden?: boolean;
+
+    /** Sentry label for the help/skip button */
+    helpSentryLabel?: string;
+
+    /** Sentry label for the confirm/submit button */
+    confirmSentryLabel?: string;
+};
+
+type FeatureTrainingContentBodyProps = {
+    /** Title for the modal */
+    title?: string | React.ReactNode;
+
+    /** Subtitle for the modal */
+    subtitle?: string;
+
+    /** Describe what is showing */
+    description?: string;
+
+    /** Text to show on primary button */
+    confirmText: string;
+};
+
+type FeatureTrainingContentIllustrationVideoProps = {
+    /** Animation to show when video is unavailable. Useful when app is offline */
+    animation?: DotLottieAnimation;
+
+    /** Additional styles for the animation */
+    animationStyle?: StyleProp<ViewStyle>;
+
+    /** URL for the video */
+    videoURL?: string;
+};
+
+type FeatureTrainingContentIllustrationSVGProps = {
+    /** Expensicon for the page */
+    image: IconAsset;
+
+    /** Determines how the image should be resized to fit its container */
+    contentFitImage?: ImageContentFit;
+
+    /** The width of the image */
+    imageWidth?: ImageSVGProps['width'];
+
+    /** The height of the image */
+    imageHeight?: ImageSVGProps['height'];
+};
+
+// Illustration requires either an icon or a video/animation, but not both.
+type FeatureTrainingContentIllustrationProps = MergeExclusive<FeatureTrainingContentIllustrationVideoProps, FeatureTrainingContentIllustrationSVGProps>;
+
+type FeatureTrainingContentDataProps = FeatureTrainingContentIllustrationProps & FeatureTrainingContentBodyProps;
+
+// Single-page mode
+type FeatureTrainingContentProps = BaseFeatureTrainingContentProps & FeatureTrainingContentDataProps;
+
+// Carousel mode
+type FeatureTrainingCarouselProps = BaseFeatureTrainingContentProps & {
+    /** Renders a horizontal paging carousel */
+    pages: FeatureTrainingContentDataProps[];
+
+    /** Called when the user swipes to a different page. */
+    onPageChange?: (index: number) => void;
+};
+
+export type {
+    BaseFeatureTrainingContentProps,
+    FeatureTrainingContentBodyProps,
+    FeatureTrainingContentIllustrationProps,
+    FeatureTrainingContentDataProps,
+    FeatureTrainingContentProps,
+    FeatureTrainingCarouselProps,
+};
diff --git a/src/components/Lottie/index.tsx b/src/components/Lottie/index.tsx
index fa9f8891754d..0ec61429e3eb 100644
--- a/src/components/Lottie/index.tsx
+++ b/src/components/Lottie/index.tsx
@@ -13,6 +13,7 @@ import CONST from '@src/CONST';
 import {useSplashScreenState} from '@src/SplashScreenStateContext';
 
 import type {AnimationObject, LottieViewProps} from 'lottie-react-native';
+import type {ForwardedRef} from 'react';
 
 import {NavigationContainerRefContext, NavigationContext} from '@react-navigation/native';
 import LottieView from 'lottie-react-native';
@@ -20,11 +21,12 @@ import React, {useContext, useEffect, useRef, useState} from 'react';
 import {View} from 'react-native';
 
 type Props = {
+    ref?: ForwardedRef<LottieView | null>;
     source: DotLottieAnimation;
     shouldLoadAfterInteractions?: boolean;
 } & Omit<LottieViewProps, 'source'>;
 
-function Lottie({source, webStyle, shouldLoadAfterInteractions, ...props}: Props) {
+function Lottie({ref, source, webStyle, shouldLoadAfterInteractions, ...props}: Props) {
     const animationRef = useRef<LottieView | null>(null);
     const appState = useAppState();
     const {splashScreenState} = useSplashScreenState();
@@ -126,8 +128,15 @@ function Lottie({source, webStyle, shouldLoadAfterInteractions, ...props}: Props
             key={`${hasNavigatedAway}`}
             ref={(newRef) => {
                 animationRef.current = newRef;
+                if (typeof ref === 'function') {
+                    ref(newRef);
+                } else if (ref && 'current' in ref) {
+                    // Forwarding the LottieView ref onto the caller-provided RefObject requires mutating `.current`.
+                    // eslint-disable-next-line no-param-reassign
+                    ref.current = newRef;
+                }
             }}
-            autoPlay={!isReduceMotionEnabled}
+            autoPlay={props.autoPlay && !isReduceMotionEnabled}
             style={[aspectRatioStyle, props.style]}
             webStyle={{...aspectRatioStyle, ...webStyle}}
             onAnimationFailure={() => setIsError(true)}
diff --git a/src/components/LottieAnimations/index.tsx b/src/components/LottieAnimations/index.tsx
index 0e4a5984ca4a..31d613dd8750 100644
--- a/src/components/LottieAnimations/index.tsx
+++ b/src/components/LottieAnimations/index.tsx
@@ -96,6 +96,22 @@ const DotLottieAnimations = {
         w: 204,
         h: 204,
     },
+    SpendAnalysis: {
+        file: require<LottieViewProps['source']>('@assets/animations/SpendAnalysis.lottie'),
+        w: 440,
+        h: 240,
+        backgroundColor: colors.pink700,
+    },
+    ExpenseAssistant: {
+        file: require<LottieViewProps['source']>('@assets/animations/ExpenseAssistant.lottie'),
+        w: 440,
+        h: 240,
+    },
+    CustomAgents: {
+        file: require<LottieViewProps['source']>('@assets/animations/CustomAgents.lottie'),
+        w: 440,
+        h: 240,
+    },
 } satisfies Record<string, DotLottieAnimation>;
 
 export default DotLottieAnimations;
diff --git a/src/components/Section/index.tsx b/src/components/Section/index.tsx
index 50f14668e735..8f8e538e5dba 100644
--- a/src/components/Section/index.tsx
+++ b/src/components/Section/index.tsx
@@ -172,6 +172,7 @@ function Section({
                                 style={styles.h100}
                                 webStyle={styles.h100}
                                 loop
+                                autoPlay
                                 shouldLoadAfterInteractions={shouldUseNarrowLayout}
                             />
                         ) : (
diff --git a/src/hooks/useAIFeaturesPromoModal.ts b/src/hooks/useAIFeaturesPromoModal.ts
new file mode 100644
index 000000000000..6e4feff0e3c3
--- /dev/null
+++ b/src/hooks/useAIFeaturesPromoModal.ts
@@ -0,0 +1,115 @@
+import Log from '@libs/Log';
+import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
+import Navigation from '@libs/Navigation/Navigation';
+import navigationRef from '@libs/Navigation/navigationRef';
+import TransitionTracker from '@libs/Navigation/TransitionTracker';
+import isProductTrainingElementDismissed from '@libs/TooltipUtils';
+
+import CONST from '@src/CONST';
+import NAVIGATORS from '@src/NAVIGATORS';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES, {DYNAMIC_ROUTES} from '@src/ROUTES';
+import SCREENS from '@src/SCREENS';
+import type {Session} from '@src/types/onyx';
+import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
+
+import type {OnyxEntry} from 'react-native-onyx';
+
+import {isActingAsDelegateSelector} from '@selectors/Account';
+import {hasCompletedGuidedSetupFlowSelector, tryNewDotOnyxSelector} from '@selectors/Onboarding';
+import {useEffect, useRef} from 'react';
+
+import useOnyx from './useOnyx';
+
+let hasRedirectedToAIFeaturesPromoModal = false;
+let observedActiveMigrationModalThisSession = false;
+let observedActiveOnboardingThisSession = false;
+
+/**
+ * Hook that navigates to the AI features promo modal if:
+ * - The user is not acting as a delegate; and
+ * - The user has not dismissed the AI features promo modal; and
+ * - The user has seen neither the migrated user welcome modal nor the onboarding modal in this session
+ */
+function useAIFeaturesPromoModal(session: OnyxEntry<Session>) {
+    const [isLoadingApp = true, isLoadingAppMetadata] = useOnyx(ONYXKEYS.IS_LOADING_APP);
+    const [isActingAsDelegate, accountMetadata] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isActingAsDelegateSelector});
+    const [dismissedProductTraining, dismissedProductTrainingMetadata] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING);
+    const [tryNewDot, tryNewDotMetadata] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT, {selector: tryNewDotOnyxSelector});
+    const [onboarding, onboardingMetadata] = useOnyx(ONYXKEYS.NVP_ONBOARDING);
+
+    const isWaitingForProtectedRoutes = useRef(false);
+
+    const hasBeenAddedToNudgeMigration = tryNewDot?.hasBeenAddedToNudgeMigration ?? false;
+    const isMigrationModalDismissed = isProductTrainingElementDismissed(CONST.MIGRATED_USER_WELCOME_MODAL, dismissedProductTraining);
+    const isAIPromoModalDismissed = isProductTrainingElementDismissed(CONST.AI_FEATURES_PROMO_MODAL, dismissedProductTraining);
+    const isMigrationModalPending = hasBeenAddedToNudgeMigration && !isMigrationModalDismissed;
+    const hasCompletedOnboarding = hasCompletedGuidedSetupFlowSelector(onboarding);
+
+    useEffect(() => {
+        if (!isAIPromoModalDismissed) {
+            return;
+        }
+        hasRedirectedToAIFeaturesPromoModal = false;
+    }, [isAIPromoModalDismissed]);
+
+    useEffect(() => {
+        if (!isMigrationModalPending) {
+            return;
+        }
+        observedActiveMigrationModalThisSession = true;
+    }, [isMigrationModalPending]);
+
+    useEffect(() => {
+        if (hasCompletedOnboarding !== false) {
+            return;
+        }
+        observedActiveOnboardingThisSession = true;
+    }, [hasCompletedOnboarding]);
+
+    const isAllOnyxLoaded = !isLoadingOnyxValue(isLoadingAppMetadata, accountMetadata, dismissedProductTrainingMetadata, tryNewDotMetadata, onboardingMetadata);
+
+    const isEligible =
+        isAllOnyxLoaded &&
+        !!session?.authToken &&
+        !isLoadingApp &&
+        !isActingAsDelegate &&
+        !hasRedirectedToAIFeaturesPromoModal &&
+        !isAIPromoModalDismissed &&
+        !isMigrationModalPending &&
+        !observedActiveMigrationModalThisSession &&
+        !observedActiveOnboardingThisSession;
+
+    useEffect(() => {
+        if (!isEligible || isWaitingForProtectedRoutes.current) {
+            return;
+        }
+        isWaitingForProtectedRoutes.current = true;
+        // Defer until any in-flight navigation transition (splash → home, etc.) has fully settled
+        const handle = TransitionTracker.runAfterTransitions({
+            callback: () => {
+                Navigation.waitForProtectedRoutes().then(() => {
+                    isWaitingForProtectedRoutes.current = false;
+                    if (hasRedirectedToAIFeaturesPromoModal || observedActiveMigrationModalThisSession || observedActiveOnboardingThisSession || isAIPromoModalDismissed) {
+                        return;
+                    }
+                    const lastRoute = navigationRef.getRootState?.()?.routes.at(-1)?.name;
+                    if (lastRoute === NAVIGATORS.SHARE_MODAL_NAVIGATOR || lastRoute === SCREENS.NOT_FOUND) {
+                        return;
+                    }
+                    Log.info('[useAIFeaturesPromoModal] Navigating to AI features promo modal');
+                    hasRedirectedToAIFeaturesPromoModal = true;
+                    Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.AI_FEATURES_PROMO.path, Navigation.getActiveRoute() || ROUTES.HOME));
+                });
+            },
+            waitForUpcomingTransition: true,
+        });
+
+        return () => {
+            handle.cancel();
+            isWaitingForProtectedRoutes.current = false;
+        };
+    }, [isEligible, isAIPromoModalDismissed]);
+}
+
+export default useAIFeaturesPromoModal;
diff --git a/src/languages/de.ts b/src/languages/de.ts
index 7c8d98df242f..dd5c9b11f3b5 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -10350,5 +10350,21 @@ Hier ist ein *Testbeleg*, um dir zu zeigen, wie es funktioniert:`,
         negativeButton: 'Nicht wirklich',
     },
     monthPickerPage: {month: 'Monat', selectMonth: 'Bitte wählen Sie einen Monat aus'},
+    aiFeaturesPromoModal: {
+        subtitle: 'Neu bei Concierge AI',
+        confirmText: 'Los geht’s!',
+        spendAnalysis: {
+            title: 'Interaktive Ausgabenanalyse',
+            description: `<muted-text>Concierge zeigt monatliche Ausgabenanalysen an und ermöglicht es Ihnen, die Details hinter jeder Zahl genauer zu betrachten. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.SPEND_ANALYSIS}">Mehr erfahren</a>.</muted-text>`,
+        },
+        expenseAssistant: {
+            title: 'Lernen Sie Ihre neue Spesenassistenz kennen',
+            description: `<muted-text>Chatten Sie mit Concierge, um Ausgaben direkt in der App oder per E-Mail oder SMS zu erstellen und zu aktualisieren. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.EXPENSE_ASSISTANT}">Mehr erfahren</a>.</muted-text>`,
+        },
+        customAgents: {
+            title: 'Erstellen Sie Ihre eigenen Agenten',
+            description: `<muted-text>Erstellen Sie benutzerdefinierte Agenten, die Ausgaben anhand Ihrer Regeln prüfen, genehmigen und weiterleiten. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.BUILD_AGENTS}">Mehr erfahren</a>.</muted-text>`,
+        },
+    },
 };
 export default translations;
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 8d725b42f641..48c74d7c846e 100644
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -10166,6 +10166,22 @@ const translations = {
             chat: 'Chat on any expense to resolve questions quickly',
         },
     },
+    aiFeaturesPromoModal: {
+        subtitle: 'New to Concierge AI',
+        confirmText: "Let's go!",
+        spendAnalysis: {
+            title: 'Interactive spend analysis',
+            description: `<muted-text>Concierge surfaces monthly spend insights and lets you drill into the details behind every number. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.SPEND_ANALYSIS}">Learn more</a>.</muted-text>`,
+        },
+        expenseAssistant: {
+            title: 'Meet your new expense assistant',
+            description: `<muted-text>Chat with Concierge to create and update expenses, right in the app or by email or text. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.EXPENSE_ASSISTANT}">Learn more</a>.</muted-text>`,
+        },
+        customAgents: {
+            title: 'Build your own agents',
+            description: `<muted-text>Create custom agents to review, approve, and route expenses based on rules you set. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.BUILD_AGENTS}">Learn more</a>.</muted-text>`,
+        },
+    },
     productTrainingTooltip: {
         // TODO: CONCIERGE_LHN_GBR tooltip will be replaced by a tooltip in the #admins room
         // https://github.com/Expensify/App/issues/57045#issuecomment-2701455668
diff --git a/src/languages/es.ts b/src/languages/es.ts
index e876881150da..3f447a3d85cb 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -10584,5 +10584,21 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`,
             lockScreenTrackingText: 'Siguiendo...',
         },
     },
+    aiFeaturesPromoModal: {
+        subtitle: 'Nuevo en Concierge AI',
+        confirmText: '¡Vamos!',
+        spendAnalysis: {
+            title: 'Análisis interactivo del gasto',
+            description: `<muted-text>Concierge muestra información mensual sobre gastos y te permite profundizar en los detalles detrás de cada cifra. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.SPEND_ANALYSIS}">Más información</a>.</muted-text>`,
+        },
+        expenseAssistant: {
+            title: 'Conoce a tu nuevo asistente de gastos',
+            description: `<muted-text>Chatea con Concierge para crear y actualizar gastos, directamente en la aplicación o por correo electrónico o mensaje de texto. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.EXPENSE_ASSISTANT}">Más información</a>.</muted-text>`,
+        },
+        customAgents: {
+            title: 'Crea tus propios agentes',
+            description: `<muted-text>Crea agentes personalizados para revisar, aprobar y asignar gastos según las reglas que configures. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.BUILD_AGENTS}">Más información</a>.</muted-text>`,
+        },
+    },
 };
 export default translations;
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 6fb1bf109c2a..c3fad8209053 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -10388,5 +10388,21 @@ Voici un *reçu test* pour vous montrer comment ça fonctionne :`,
         negativeButton: 'Pas vraiment',
     },
     monthPickerPage: {month: 'Mois', selectMonth: 'Veuillez sélectionner un mois'},
+    aiFeaturesPromoModal: {
+        subtitle: 'Nouveau dans Concierge IA',
+        confirmText: 'Allons-y !',
+        spendAnalysis: {
+            title: 'Analyse interactive des dépenses',
+            description: `<muted-text>Concierge met en avant des analyses mensuelles des dépenses et vous permet d’examiner en détail chaque chiffre. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.SPEND_ANALYSIS}">En savoir plus</a>.</muted-text>`,
+        },
+        expenseAssistant: {
+            title: 'Découvrez votre nouvel assistant de dépenses',
+            description: `<muted-text>Discutez avec Concierge pour créer et mettre à jour des dépenses, directement dans l’application ou par e-mail ou SMS. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.EXPENSE_ASSISTANT}">En savoir plus</a>.</muted-text>`,
+        },
+        customAgents: {
+            title: 'Créez vos propres agents',
+            description: `<muted-text>Créez des agents personnalisés pour examiner, approuver et acheminer les dépenses selon les règles que vous définissez. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.BUILD_AGENTS}">En savoir plus</a>.</muted-text>`,
+        },
+    },
 };
 export default translations;
diff --git a/src/languages/it.ts b/src/languages/it.ts
index fa7323cf253a..3f6a04678900 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -10340,5 +10340,21 @@ Ecco una *ricevuta di prova* per mostrarti come funziona:`,
         negativeButton: 'Non proprio',
     },
     monthPickerPage: {month: 'Mese', selectMonth: 'Seleziona un mese'},
+    aiFeaturesPromoModal: {
+        subtitle: 'Nuovo in Concierge AI',
+        confirmText: 'Andiamo!',
+        spendAnalysis: {
+            title: 'Analisi interattiva delle spese',
+            description: `<muted-text>Concierge mette in evidenza approfondimenti sulla spesa mensile e ti permette di approfondire i dettagli dietro ogni numero. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.SPEND_ANALYSIS}">Scopri di più</a>.</muted-text>`,
+        },
+        expenseAssistant: {
+            title: 'Incontra il tuo nuovo assistente per le spese',
+            description: `<muted-text>Chatta con Concierge per creare e aggiornare le spese direttamente nell’app o via email o SMS. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.EXPENSE_ASSISTANT}">Scopri di più</a>.</muted-text>`,
+        },
+        customAgents: {
+            title: 'Crea i tuoi agenti',
+            description: `<muted-text>Crea agenti personalizzati per verificare, approvare e instradare le spese in base alle regole che imposti. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.BUILD_AGENTS}">Scopri di più</a>.</muted-text>`,
+        },
+    },
 };
 export default translations;
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 5ff43744a4b8..757c25a375d5 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -10190,5 +10190,21 @@ ${reportName}`,
         negativeButton: 'そうでもありません',
     },
     monthPickerPage: {month: '月', selectMonth: '月を選択してください'},
+    aiFeaturesPromoModal: {
+        subtitle: 'はじめての Concierge AI',
+        confirmText: '始めましょう！',
+        spendAnalysis: {
+            title: 'インタラクティブな支出分析',
+            description: `<muted-text>Concierge は毎月の支出インサイトを提示し、すべての数値の内訳を詳しく確認できるようにします。<a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.SPEND_ANALYSIS}">詳しく見る</a>。</muted-text>`,
+        },
+        expenseAssistant: {
+            title: '新しい経費アシスタントをご紹介します',
+            description: `<muted-text>アプリ内やメール、テキストメッセージでConciergeとチャットして、経費を作成・更新できます。<a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.EXPENSE_ASSISTANT}">詳しく見る</a>。</muted-text>`,
+        },
+        customAgents: {
+            title: '独自のエージェントを作成する',
+            description: `<muted-text>設定したルールに基づいて経費を確認、承認、振り分けるカスタムエージェントを作成できます。<a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.BUILD_AGENTS}">さらに詳しく</a>。</muted-text>`,
+        },
+    },
 };
 export default translations;
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 01433ff2cea5..4dcf8b5b0402 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -10305,5 +10305,21 @@ Hier is een *proefbon* om je te laten zien hoe het werkt:`,
         negativeButton: 'Niet echt',
     },
     monthPickerPage: {month: 'Maand', selectMonth: 'Selecteer een maand'},
+    aiFeaturesPromoModal: {
+        subtitle: 'Nieuw bij Concierge AI',
+        confirmText: 'Laten we gaan!',
+        spendAnalysis: {
+            title: 'Interactieve uitgavenanalyse',
+            description: `<muted-text>Concierge toont maandelijkse uitgaveninzichten en laat je inzoomen op de details achter elk getal. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.SPEND_ANALYSIS}">Meer informatie</a>.</muted-text>`,
+        },
+        expenseAssistant: {
+            title: 'Maak kennis met je nieuwe declaratie-assistent',
+            description: `<muted-text>Chat met Concierge om uitgaven aan te maken en bij te werken, rechtstreeks in de app of via e-mail of sms. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.EXPENSE_ASSISTANT}">Meer informatie</a>.</muted-text>`,
+        },
+        customAgents: {
+            title: 'Bouw je eigen agents',
+            description: `<muted-text>Maak aangepaste agents om uitgaven te beoordelen, goed te keuren en door te sturen op basis van regels die jij instelt. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.BUILD_AGENTS}">Meer informatie</a>.</muted-text>`,
+        },
+    },
 };
 export default translations;
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 8c533103f70d..81ae503aabdb 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -10274,5 +10274,21 @@ Oto *paragon testowy*, żeby pokazać Ci, jak to działa:`,
         negativeButton: 'Niekoniecznie',
     },
     monthPickerPage: {month: 'Miesiąc', selectMonth: 'Wybierz miesiąc'},
+    aiFeaturesPromoModal: {
+        subtitle: 'Nowość w Concierge AI',
+        confirmText: 'Jedziemy!',
+        spendAnalysis: {
+            title: 'Interaktywna analiza wydatków',
+            description: `<muted-text>Concierge przedstawia miesięczne informacje o wydatkach i pozwala zagłębić się w szczegóły stojące za każdą liczbą. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.SPEND_ANALYSIS}">Dowiedz się więcej</a>.</muted-text>`,
+        },
+        expenseAssistant: {
+            title: 'Poznaj swojego nowego asystenta wydatków',
+            description: `<muted-text>Rozmawiaj z Concierge, aby tworzyć i aktualizować wydatki bezpośrednio w aplikacji, e-mailem lub SMS-em. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.EXPENSE_ASSISTANT}">Dowiedz się więcej</a>.</muted-text>`,
+        },
+        customAgents: {
+            title: 'Zbuduj własne agentów',
+            description: `<muted-text>Twórz niestandardowych agentów do przeglądania, zatwierdzania i kierowania wydatków na podstawie ustalonych przez siebie zasad. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.BUILD_AGENTS}">Dowiedz się więcej</a>.</muted-text>`,
+        },
+    },
 };
 export default translations;
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index c9a7f3a374c9..f1a75962e152 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -10291,5 +10291,21 @@ Aqui está um *comprovante de teste* para mostrar como funciona:`,
         negativeButton: 'Na verdade, não',
     },
     monthPickerPage: {month: 'Mês', selectMonth: 'Selecione um mês por favor'},
+    aiFeaturesPromoModal: {
+        subtitle: 'Novo no Concierge AI',
+        confirmText: 'Vamos lá!',
+        spendAnalysis: {
+            title: 'Análise interativa de gastos',
+            description: `<muted-text>O Concierge apresenta insights mensais de gastos e permite que você aprofunde nos detalhes por trás de cada número. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.SPEND_ANALYSIS}">Saiba mais</a>.</muted-text>`,
+        },
+        expenseAssistant: {
+            title: 'Conheça seu novo assistente de despesas',
+            description: `<muted-text>Converse com o Concierge para criar e atualizar despesas, direto no app ou por e-mail ou SMS. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.EXPENSE_ASSISTANT}">Saiba mais</a>.</muted-text>`,
+        },
+        customAgents: {
+            title: 'Crie seus próprios agentes',
+            description: `<muted-text>Crie agentes personalizados para revisar, aprovar e direcionar despesas com base nas regras que você definir. <a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.BUILD_AGENTS}">Saiba mais</a>.</muted-text>`,
+        },
+    },
 };
 export default translations;
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index c6e52c2a09d8..5e46367a209c 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -9981,5 +9981,21 @@ ${reportName}`,
     },
     proactiveAppReview: {title: '喜欢全新的 Expensify 吗？', description: '请告诉我们，这样我们就能帮助您让报销体验变得更好。', positiveButton: '太棒了！', negativeButton: '不太是'},
     monthPickerPage: {month: '月份', selectMonth: '请选择月份'},
+    aiFeaturesPromoModal: {
+        subtitle: 'Concierge AI 新手指南',
+        confirmText: '出发吧！',
+        spendAnalysis: {
+            title: '交互式支出分析',
+            description: `<muted-text>Concierge 会提供每月支出洞察，并让你深入查看每个数字背后的详细信息。<a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.SPEND_ANALYSIS}">了解详情</a>。</muted-text>`,
+        },
+        expenseAssistant: {
+            title: '认识你的全新报销助手',
+            description: `<muted-text>在应用内或通过电子邮件、短信与 Concierge 聊天来创建和更新报销。<a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.EXPENSE_ASSISTANT}">了解更多</a>。</muted-text>`,
+        },
+        customAgents: {
+            title: '构建你自己的代理',
+            description: `<muted-text>创建自定义代理，根据你设置的规则审核、批准和分配报销。<a href="${CONST.AI_FEATURES_PROMO_LEARN_MORE_URLS.BUILD_AGENTS}">了解更多</a>。</muted-text>`,
+        },
+    },
 };
 export default translations;
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
index 9910d6b6679e..82ac934225f2 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
@@ -62,6 +62,7 @@ import DelegatorConnectGuard from './DelegatorConnectGate';
 import hideKeyboardOnSwipe from './hideKeyboardOnSwipe';
 import KeyboardShortcutsHandler from './KeyboardShortcutsHandler';
 import {ShareModalStackNavigator} from './ModalStackNavigators';
+import AIFeaturesPromoModalNavigator from './Navigators/AIFeaturesPromoModalNavigator';
 import FeatureTrainingModalNavigator from './Navigators/FeatureTrainingModalNavigator';
 import MigratedUserWelcomeModalNavigator from './Navigators/MigratedUserWelcomeModalNavigator';
 import MultifactorAuthenticationModalNavigator from './Navigators/MultifactorAuthenticationModalNavigator';
@@ -309,6 +310,11 @@ function AuthScreens() {
                             options={rootNavigatorScreenOptions.centeredModalNavigator}
                             component={MigratedUserWelcomeModalNavigator}
                         />
+                        <RootStack.Screen
+                            name={NAVIGATORS.AI_FEATURES_PROMO_MODAL_NAVIGATOR}
+                            options={rootNavigatorScreenOptions.centeredModalNavigator}
+                            component={AIFeaturesPromoModalNavigator}
+                        />
                         <RootStack.Screen
                             name={NAVIGATORS.TEST_DRIVE_DEMO_NAVIGATOR}
                             options={rootNavigatorScreenOptions.basicModalNavigator}
diff --git a/src/libs/Navigation/AppNavigator/AuthScreensInitHandler.tsx b/src/libs/Navigation/AppNavigator/AuthScreensInitHandler.tsx
index 336656f31ed2..72b8c70dcd78 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreensInitHandler.tsx
+++ b/src/libs/Navigation/AppNavigator/AuthScreensInitHandler.tsx
@@ -1,6 +1,7 @@
 import {useInitialURLActions, useInitialURLState} from '@components/InitialURLContextProvider';
 
 import useActivePolicy from '@hooks/useActivePolicy';
+import useAIFeaturesPromoModal from '@hooks/useAIFeaturesPromoModal';
 import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
 import useHasActiveAdminPolicies from '@hooks/useHasActiveAdminPolicies';
 import useLastWorkspaceNumber from '@hooks/useLastWorkspaceNumber';
@@ -88,6 +89,7 @@ function AuthScreensInitHandler() {
     reportAttributesRef.current = reportAttributes;
 
     useReconcileHighContrastIntent();
+    useAIFeaturesPromoModal(session);
 
     const topmostReportID = useRootNavigationState(Navigation.getTopmostReportId);
     const topmostOneTransactionThreadReportID = useOneTransactionThreadReportID(topmostReportID);
diff --git a/src/libs/Navigation/AppNavigator/Navigators/AIFeaturesPromoModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/AIFeaturesPromoModalNavigator.tsx
new file mode 100644
index 000000000000..07793b4004f2
--- /dev/null
+++ b/src/libs/Navigation/AppNavigator/Navigators/AIFeaturesPromoModalNavigator.tsx
@@ -0,0 +1,32 @@
+import AIFeaturesPromoModal from '@components/AIFeaturesPromoModal';
+import NoDropZone from '@components/DragAndDrop/NoDropZone';
+
+import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator';
+import type {AIFeaturesPromoModalNavigatorParamList} from '@libs/Navigation/types';
+
+import SCREENS from '@src/SCREENS';
+
+import React from 'react';
+
+const Stack = createPlatformStackNavigator<AIFeaturesPromoModalNavigatorParamList>();
+
+function AIFeaturesPromoModalNavigator() {
+    return (
+        <NoDropZone>
+            <Stack.Navigator
+                screenOptions={{
+                    headerShown: false,
+                    web: {cardStyle: {backgroundColor: 'transparent'}},
+                    native: {contentStyle: {backgroundColor: 'transparent'}},
+                }}
+            >
+                <Stack.Screen
+                    name={SCREENS.AI_FEATURES_PROMO_MODAL.DYNAMIC_ROOT}
+                    component={AIFeaturesPromoModal}
+                />
+            </Stack.Navigator>
+        </NoDropZone>
+    );
+}
+
+export default AIFeaturesPromoModalNavigator;
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 95513d769391..061c14fdb2af 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -53,6 +53,11 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
                 [SCREENS.MIGRATED_USER_WELCOME_MODAL.DYNAMIC_ROOT]: DYNAMIC_ROUTES.MIGRATED_USER_WELCOME.path,
             },
         },
+        [NAVIGATORS.AI_FEATURES_PROMO_MODAL_NAVIGATOR]: {
+            screens: {
+                [SCREENS.AI_FEATURES_PROMO_MODAL.DYNAMIC_ROOT]: DYNAMIC_ROUTES.AI_FEATURES_PROMO.path,
+            },
+        },
 
         [NAVIGATORS.TEST_DRIVE_DEMO_NAVIGATOR]: {
             screens: {
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 12ff43f7d6ca..b165988c7344 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -3011,6 +3011,10 @@ type MigratedUserModalNavigatorParamList = {
     [SCREENS.MIGRATED_USER_WELCOME_MODAL.DYNAMIC_ROOT]: undefined;
 };
 
+type AIFeaturesPromoModalNavigatorParamList = {
+    [SCREENS.AI_FEATURES_PROMO_MODAL.DYNAMIC_ROOT]: undefined;
+};
+
 type TestDriveDemoNavigatorParamList = {
     [SCREENS.TEST_DRIVE_DEMO.ROOT]: undefined;
 };
@@ -3181,6 +3185,7 @@ type AuthScreensParamList = SharedScreensParamList &
         [NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR]: NavigatorScreenParams<OnboardingModalNavigatorParamList>;
         [NAVIGATORS.FEATURE_TRAINING_MODAL_NAVIGATOR]: NavigatorScreenParams<FeatureTrainingNavigatorParamList>;
         [NAVIGATORS.MIGRATED_USER_MODAL_NAVIGATOR]: NavigatorScreenParams<MigratedUserModalNavigatorParamList>;
+        [NAVIGATORS.AI_FEATURES_PROMO_MODAL_NAVIGATOR]: NavigatorScreenParams<AIFeaturesPromoModalNavigatorParamList>;
         [NAVIGATORS.TEST_DRIVE_DEMO_NAVIGATOR]: NavigatorScreenParams<TestDriveDemoNavigatorParamList>;
         [SCREENS.CONNECTION_COMPLETE]: undefined;
         [NAVIGATORS.SHARE_MODAL_NAVIGATOR]: NavigatorScreenParams<ShareNavigatorParamList>;
@@ -3451,6 +3456,7 @@ export type {
     WorkspaceSplitNavigatorParamList,
     WorkspaceNavigatorParamList,
     MigratedUserModalNavigatorParamList,
+    AIFeaturesPromoModalNavigatorParamList,
     WorkspaceConfirmationNavigatorParamList,
     WorkspaceDuplicateNavigatorParamList,
     PolicyCopySettingsNavigatorParamList,
diff --git a/src/styles/index.ts b/src/styles/index.ts
index 5656fdd573d5..29e6ddb272f9 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -6000,6 +6000,12 @@ const staticStyles = (theme: ThemeColors) =>
             border: 'none',
         },
 
+        featureTrainingModalNavButtons: {
+            flexDirection: 'row',
+            justifyContent: 'space-between',
+            gap: variables.spacing2,
+        },
+
         twoColumnLayoutCol: {
             flexGrow: 1,
             flexShrink: 1,
diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts
index ac28ad102438..368e7e856d25 100644
--- a/src/styles/utils/index.ts
+++ b/src/styles/utils/index.ts
@@ -2123,6 +2123,26 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({
             styleObj[key] = null;
             return styleObj;
         }, {} as Nullable<K>) as K,
+    getFeatureTrainingCarouselDotStyle: (size: number, color: string, isActive: boolean): ViewStyle => ({
+        width: size,
+        height: size,
+        borderRadius: size / 2,
+        marginHorizontal: size,
+        backgroundColor: color,
+        opacity: isActive ? 1 : 0.3,
+    }),
+
+    getFeatureTrainingCarouselCloseButtonContainerStyle: (padding: number): ViewStyle => ({
+        position: 'absolute',
+        top: padding,
+        right: padding,
+        zIndex: 1,
+    }),
+
+    getFeatureTrainingCarouselDotsContainerStyle: (bottomOffset: number): ViewStyle => ({
+        bottom: bottomOffset,
+    }),
+
     getScrollableFeatureTrainingModalStyles: (
         insets: EdgeInsets,
         isKeyboardOpen = false,
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index 42366a90b72f..28d9bfa92ef1 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -285,6 +285,7 @@ export default {
     photoUploadPopoverWidth: 335,
     featureTrainingModalWidth: 500,
     onboardingModalWidth: 640,
+    aiFeaturesPromoModalWidth: 440,
     holdEducationModalWidth: 400,
     changePolicyEducationModalWidth: 400,
     wideConfirmModalWidth: 400,
diff --git a/src/types/onyx/DismissedProductTraining.ts b/src/types/onyx/DismissedProductTraining.ts
index 958cb35a1650..904da3d460a8 100644
--- a/src/types/onyx/DismissedProductTraining.ts
+++ b/src/types/onyx/DismissedProductTraining.ts
@@ -31,6 +31,11 @@ type DismissedProductTraining = {
      */
     [CONST.MIGRATED_USER_WELCOME_MODAL]: DismissedProductTrainingElement;
 
+    /**
+     * When user dismisses the AI features promo modal, we store the timestamp here.
+     */
+    [CONST.AI_FEATURES_PROMO_MODAL]: DismissedProductTrainingElement;
+
     // TODO: CONCIERGE_LHN_GBR tooltip will be replaced by a tooltip in the #admins room
     // https://github.com/Expensify/App/issues/57045#issuecomment-2701455668
     /**
