From 06f6040d51b68c40e9293fb77c4003338e7d42bd Mon Sep 17 00:00:00 2001 From: Jay Goss Date: Tue, 7 Apr 2026 12:07:30 -0500 Subject: [PATCH 1/6] feat(onboarding): Add animated Sentry logo to SCM onboarding steps Add a progress-driven Sentry brand mark to the top of each SCM onboarding step header. Uses stroke-dashoffset drawing adapted from the app loading screen (static/index.ejs), with a single accent color and fill level controlled by step progression (1/3, 2/3, 3/3). Refs VDY-66 --- static/app/components/animatedSentryLogo.tsx | 111 ++++++++++++++++++ .../onboarding/components/scmStepHeader.tsx | 2 + 2 files changed, 113 insertions(+) create mode 100644 static/app/components/animatedSentryLogo.tsx diff --git a/static/app/components/animatedSentryLogo.tsx b/static/app/components/animatedSentryLogo.tsx new file mode 100644 index 00000000000000..019a749fdd387b --- /dev/null +++ b/static/app/components/animatedSentryLogo.tsx @@ -0,0 +1,111 @@ +import {useEffect, useId, useState} from 'react'; +import {useTheme} from '@emotion/react'; + +interface AnimatedSentryLogoProps { + /** + * Draw progress from 0 (no stroke) to 1 (fully drawn). + * Uses the stroke-dashoffset technique from the app loading screen. + */ + progress: number; + className?: string; + size?: number; +} + +const DASH_TOTAL = 1150; + +// Filled glyph (same path as index.ejs base + mask) +const SENTRY_GLYPH = + 'M223.687 85.56a28.02 28.02 0 0 0-24-13.559 28.02 28.02 0 0 0-24 13.559l-39.48 67.62a193.26 193.26 0 0 1 106.5 159.96h-27.72a166.07 166.07 0 0 0-92.76-136.32L85.687 240a95.52 95.52 0 0 1 55.38 73.02h-63.66a4.55 4.55 0 0 1-3.659-2.325 4.56 4.56 0 0 1-.06-4.335l17.64-30a64.4 64.4 0 0 0-20.16-11.4l-17.46 30a27.24 27.24 0 0 0 2.023 30.435 27.2 27.2 0 0 0 8.116 7.005 27.96 27.96 0 0 0 13.56 3.6h87.18a116.41 116.41 0 0 0-48-103.86l13.86-24a143.22 143.22 0 0 1 61.8 127.86h73.86a215.28 215.28 0 0 0-98.46-190.8l28.02-48a4.62 4.62 0 0 1 6.3-1.62c3.18 1.74 121.74 208.62 123.96 211.02a4.562 4.562 0 0 1-4.08 6.78h-28.56q.54 11.46 0 22.86h28.68a27.54 27.54 0 0 0 27.72-27.66 26.94 26.94 0 0 0-3.72-13.68z'; + +// Stroke paths that trace the logo outline (one group from index.ejs) +const STROKE_PATHS = [ + 'm82.8 253.5-12.7 22-12.5 21.2-.2.3v.1l-.2.2v.3a24 24 0 00.3 22.4l.3.6A24 24 0 0076.4 332h87l-3.2-21.8A115 115 0 00112 233l17.6-30.4a147 147 0 0166.8 112l1.3 17.5h65.6l-1.3-20.2a212 212 0 00-99.3-165.8l29.6-50.8a9 9 0 013.3-3.1 9 9 0 0112.1 3.1l122.2 209.2h.1a8 8 0 011 4v.2a8.5 8.5 0 01-8.6 8.5h-38.9', + 'M92.5 268.7 88 276.6 71.4 305v.1a8 8 0 000 7.2l.2.4a8 8 0 006 3.4h67.3l-.6-3.4A99 99 0 0097.2 242.5l-6.8-4 33.4-57.8 6.9 4a163 163 0 0181.6 128.8l.2 2.7h33.7L246 313A196 196 0 00147.8 155.7l-7-4L178.6 87v-.1a25 25 0 0133-9.1l.8.3a25 25 0 019 8.9h.1v.2L343.7 296.4a24 24 0 013.3 12v.4a24.5 24.5 0 01-24.6 24.4H291.5', + 'M85.7 264.5l-8.7 15-12.5 21.3-.1.2-.1.3A15.6 15.6 0 0076.9 324h77.2l-1.8-12.6a107 107 0 00-51-75.8l25.5-44.1A155 155 0 01204.3 314l.8 10.1h49.7l-.8-11.7A204 204 0 00151.8 148.9l33.6-57.7a17 17 0 0129.2 0L336.8 300.4a16 16 0 012.2 8.2 16.5 16.5 0 01-16.6 16.6H291.5', +]; + +/** + * Sentry brand mark with stroke-draw progress, adapted from the app + * loading animation in static/index.ejs. Single color, progress-driven + * instead of the looping multicolor original. + */ +export function AnimatedSentryLogo({ + progress, + size = 72, + className, +}: AnimatedSentryLogoProps) { + const theme = useTheme(); + const uid = useId(); + const gooId = `goo-${uid}`; + const maskId = `mask-${uid}`; + + // Animate from 0 on mount so each step shows a draw-in + const [fill, setFill] = useState(0); + useEffect(() => { + const raf = requestAnimationFrame(() => setFill(progress)); + return () => cancelAnimationFrame(raf); + }, [progress]); + + const dashOffset = DASH_TOTAL * (1 - fill); + + return ( + + + + + + + + + + + + + + {/* Muted base shape */} + + + {/* Stroke-draw overlay, masked to the logo shape */} + + {STROKE_PATHS.map((d, i) => ( + + ))} + + + ); +} diff --git a/static/app/views/onboarding/components/scmStepHeader.tsx b/static/app/views/onboarding/components/scmStepHeader.tsx index 57414268f7932e..3668eb4e122be6 100644 --- a/static/app/views/onboarding/components/scmStepHeader.tsx +++ b/static/app/views/onboarding/components/scmStepHeader.tsx @@ -2,6 +2,7 @@ import {Tag} from '@sentry/scraps/badge'; import {Flex, Stack} from '@sentry/scraps/layout'; import {Heading, Text} from '@sentry/scraps/text'; +import {AnimatedSentryLogo} from 'sentry/components/animatedSentryLogo'; import {t} from 'sentry/locale'; interface ScmStepHeaderProps { @@ -21,6 +22,7 @@ export function ScmStepHeader({ }: ScmStepHeaderProps) { return ( + {t('Step %s of %s', stepNumber, totalSteps)} From b459218af1923c95007c2c39eecf41f365186b43 Mon Sep 17 00:00:00 2001 From: Jay Goss Date: Tue, 7 Apr 2026 12:37:03 -0500 Subject: [PATCH 2/6] ref(onboarding): Simplify logo SVG and lift above step transitions Replace the complex 400x400 multi-path SVG with a single-path glyph (64x59 viewBox) and use pathLength normalization for cleaner stroke-dashoffset control. Move the logo from ScmStepHeader into the onboarding container above AnimatePresence so a single instance persists across SCM step transitions, enabling smooth forward/back animation. Refs VDY-66 --- static/app/components/animatedSentryLogo.tsx | 88 +++++-------------- .../onboarding/components/scmStepHeader.tsx | 2 - static/app/views/onboarding/onboarding.tsx | 30 ++++++- 3 files changed, 47 insertions(+), 73 deletions(-) diff --git a/static/app/components/animatedSentryLogo.tsx b/static/app/components/animatedSentryLogo.tsx index 019a749fdd387b..7568c27a51c8f5 100644 --- a/static/app/components/animatedSentryLogo.tsx +++ b/static/app/components/animatedSentryLogo.tsx @@ -1,33 +1,23 @@ -import {useEffect, useId, useState} from 'react'; +import {useId} from 'react'; import {useTheme} from '@emotion/react'; interface AnimatedSentryLogoProps { /** * Draw progress from 0 (no stroke) to 1 (fully drawn). - * Uses the stroke-dashoffset technique from the app loading screen. + * Uses stroke-dashoffset on the Sentry glyph outline. */ progress: number; className?: string; size?: number; } -const DASH_TOTAL = 1150; - -// Filled glyph (same path as index.ejs base + mask) +// Single-path Sentry glyph (64×59 viewBox) const SENTRY_GLYPH = - 'M223.687 85.56a28.02 28.02 0 0 0-24-13.559 28.02 28.02 0 0 0-24 13.559l-39.48 67.62a193.26 193.26 0 0 1 106.5 159.96h-27.72a166.07 166.07 0 0 0-92.76-136.32L85.687 240a95.52 95.52 0 0 1 55.38 73.02h-63.66a4.55 4.55 0 0 1-3.659-2.325 4.56 4.56 0 0 1-.06-4.335l17.64-30a64.4 64.4 0 0 0-20.16-11.4l-17.46 30a27.24 27.24 0 0 0 2.023 30.435 27.2 27.2 0 0 0 8.116 7.005 27.96 27.96 0 0 0 13.56 3.6h87.18a116.41 116.41 0 0 0-48-103.86l13.86-24a143.22 143.22 0 0 1 61.8 127.86h73.86a215.28 215.28 0 0 0-98.46-190.8l28.02-48a4.62 4.62 0 0 1 6.3-1.62c3.18 1.74 121.74 208.62 123.96 211.02a4.562 4.562 0 0 1-4.08 6.78h-28.56q.54 11.46 0 22.86h28.68a27.54 27.54 0 0 0 27.72-27.66 26.94 26.94 0 0 0-3.72-13.68z'; - -// Stroke paths that trace the logo outline (one group from index.ejs) -const STROKE_PATHS = [ - 'm82.8 253.5-12.7 22-12.5 21.2-.2.3v.1l-.2.2v.3a24 24 0 00.3 22.4l.3.6A24 24 0 0076.4 332h87l-3.2-21.8A115 115 0 00112 233l17.6-30.4a147 147 0 0166.8 112l1.3 17.5h65.6l-1.3-20.2a212 212 0 00-99.3-165.8l29.6-50.8a9 9 0 013.3-3.1 9 9 0 0112.1 3.1l122.2 209.2h.1a8 8 0 011 4v.2a8.5 8.5 0 01-8.6 8.5h-38.9', - 'M92.5 268.7 88 276.6 71.4 305v.1a8 8 0 000 7.2l.2.4a8 8 0 006 3.4h67.3l-.6-3.4A99 99 0 0097.2 242.5l-6.8-4 33.4-57.8 6.9 4a163 163 0 0181.6 128.8l.2 2.7h33.7L246 313A196 196 0 00147.8 155.7l-7-4L178.6 87v-.1a25 25 0 0133-9.1l.8.3a25 25 0 019 8.9h.1v.2L343.7 296.4a24 24 0 013.3 12v.4a24.5 24.5 0 01-24.6 24.4H291.5', - 'M85.7 264.5l-8.7 15-12.5 21.3-.1.2-.1.3A15.6 15.6 0 0076.9 324h77.2l-1.8-12.6a107 107 0 00-51-75.8l25.5-44.1A155 155 0 01204.3 314l.8 10.1h49.7l-.8-11.7A204 204 0 00151.8 148.9l33.6-57.7a17 17 0 0129.2 0L336.8 300.4a16 16 0 012.2 8.2 16.5 16.5 0 01-16.6 16.6H291.5', -]; + 'M31.9948 0C33.0411 0 34.0698 0.28309 34.9701 0.816074C35.8698 1.34902 36.6108 2.11326 37.1138 3.03002L63.1813 49.4916C63.7108 50.4167 63.9896 51.4634 63.9896 52.5294C63.9896 53.3288 63.8344 54.1179 63.5327 54.8527L63.2243 55.6492C62.7224 56.5678 61.9805 57.3325 61.0806 57.8671C60.1803 58.4016 59.1523 58.6861 58.1053 58.687H51.9867V53.611H58.1053C58.275 53.6124 58.4421 53.5664 58.5894 53.4821C58.7365 53.398 58.8599 53.2774 58.9448 53.1307C59.0363 52.9806 59.0851 52.8068 59.0853 52.6309C59.0853 52.4551 59.0361 52.2814 58.9448 52.1311L32.8733 5.63052C32.7904 5.48053 32.6696 5.35444 32.5219 5.26739C32.3743 5.18042 32.2052 5.13463 32.0338 5.13463C31.8633 5.1349 31.6967 5.18091 31.5497 5.26739C31.402 5.35444 31.2773 5.48053 31.1943 5.63052L25.1968 16.3059C31.3083 20.4099 36.3755 25.887 39.9915 32.2994C44.0222 39.5769 46.1174 47.7675 46.0711 56.0865V58.4879H30.3158V55.9694C30.3478 50.489 28.9706 45.0915 26.3174 40.2961C24.0788 36.3061 20.9951 32.8521 17.282 30.1791L14.3223 35.4582C17.1667 37.6052 19.5265 40.331 21.2414 43.455C23.3822 47.3214 24.5081 51.667 24.5174 56.0865V58.609H5.92727C4.88044 58.6081 3.85214 58.3233 2.95192 57.789C2.05148 57.2543 1.31035 56.4862 0.808265 55.5672C0.278995 54.6422 0 53.5952 0 52.5294C1.59391e-05 51.4636 0.27896 50.4166 0.808265 49.4916L4.56845 42.7717C6.13984 43.3348 7.58937 44.1931 8.84406 45.2941L5.08777 52.0101C4.99593 52.1606 4.94721 52.3335 4.94721 52.5099C4.94727 52.6861 4.996 52.8592 5.08777 53.0097C5.17546 53.1534 5.29701 53.2735 5.4431 53.3572C5.58995 53.4411 5.75816 53.4875 5.92727 53.4899H19.4413C19.0534 50.4366 17.9811 47.5091 16.3059 44.927C14.6307 42.345 12.3957 40.1727 9.76556 38.5741L7.64533 37.2973L15.521 23.303L17.6413 24.5408C23.0555 27.7797 27.524 32.3846 30.5969 37.8947C33.2819 42.6981 34.8667 48.0386 35.2357 53.529H41.1512C40.7642 46.9702 38.8906 40.5854 35.673 34.8569C32.1062 28.4358 26.9071 23.071 20.601 19.3046L18.4417 18.0239L26.8758 3.03002C27.3791 2.11272 28.123 1.34906 29.0233 0.816074C29.9234 0.283345 30.9489 0.000109961 31.9948 0Z'; /** - * Sentry brand mark with stroke-draw progress, adapted from the app - * loading animation in static/index.ejs. Single color, progress-driven - * instead of the looping multicolor original. + * Sentry brand mark with stroke-draw progress. The outline draws + * itself via stroke-dashoffset, driven by a `progress` prop (0-1). */ export function AnimatedSentryLogo({ progress, @@ -35,24 +25,13 @@ export function AnimatedSentryLogo({ className, }: AnimatedSentryLogoProps) { const theme = useTheme(); - const uid = useId(); - const gooId = `goo-${uid}`; - const maskId = `mask-${uid}`; - - // Animate from 0 on mount so each step shows a draw-in - const [fill, setFill] = useState(0); - useEffect(() => { - const raf = requestAnimationFrame(() => setFill(progress)); - return () => cancelAnimationFrame(raf); - }, [progress]); - - const dashOffset = DASH_TOTAL * (1 - fill); + const clipId = useId(); return ( - - - - - + + + - - - - {/* Muted base shape */} - {/* Stroke-draw overlay, masked to the logo shape */} - - {STROKE_PATHS.map((d, i) => ( - - ))} - + strokeDasharray={1} + strokeDashoffset={1 - progress} + style={{transition: 'stroke-dashoffset 600ms ease-out'}} + /> ); } diff --git a/static/app/views/onboarding/components/scmStepHeader.tsx b/static/app/views/onboarding/components/scmStepHeader.tsx index 3668eb4e122be6..57414268f7932e 100644 --- a/static/app/views/onboarding/components/scmStepHeader.tsx +++ b/static/app/views/onboarding/components/scmStepHeader.tsx @@ -2,7 +2,6 @@ import {Tag} from '@sentry/scraps/badge'; import {Flex, Stack} from '@sentry/scraps/layout'; import {Heading, Text} from '@sentry/scraps/text'; -import {AnimatedSentryLogo} from 'sentry/components/animatedSentryLogo'; import {t} from 'sentry/locale'; interface ScmStepHeaderProps { @@ -22,7 +21,6 @@ export function ScmStepHeader({ }: ScmStepHeaderProps) { return ( - {t('Step %s of %s', stepNumber, totalSteps)} diff --git a/static/app/views/onboarding/onboarding.tsx b/static/app/views/onboarding/onboarding.tsx index 6bb306970484ae..62ecec80b957e0 100644 --- a/static/app/views/onboarding/onboarding.tsx +++ b/static/app/views/onboarding/onboarding.tsx @@ -3,9 +3,10 @@ import styled from '@emotion/styled'; import {AnimatePresence, motion} from 'framer-motion'; import {Button} from '@sentry/scraps/button'; -import {Stack} from '@sentry/scraps/layout'; +import {Container, Stack} from '@sentry/scraps/layout'; import {Link} from '@sentry/scraps/link'; +import {AnimatedSentryLogo} from 'sentry/components/animatedSentryLogo'; import Hook from 'sentry/components/hook'; import {LogoSentry} from 'sentry/components/logoSentry'; import { @@ -101,6 +102,16 @@ const scmOnboardingSteps: StepDescriptor[] = [ }, ]; +/** + * The SCM steps that display the animated logo progress indicator. + * Order determines the progress level (first = 0, last = 1). + */ +const SCM_LOGO_STEPS = [ + OnboardingStepId.SCM_CONNECT, + OnboardingStepId.SCM_PLATFORM_FEATURES, + OnboardingStepId.SCM_PROJECT_DETAILS, +]; + function WelcomeVariable(props: StepProps) { const hasNewWelcomeUI = useHasNewWelcomeUI(); @@ -117,7 +128,9 @@ interface ContainerVariableProps { function ContainerVariable(props: PropsWithChildren) { const newWelcomeUIStep = props.hasNewWelcomeUI && props.id === OnboardingStepId.WELCOME; - const Component = newWelcomeUIStep ? ContainerNewWelcomeUI : Container; + const Component = newWelcomeUIStep + ? OnboardingContainerNewWelcomeUI + : OnboardingContainer; return ( @@ -302,6 +315,10 @@ export function OnboardingWithoutContext() { ); }; + const scmLogoIndex = stepObj ? SCM_LOGO_STEPS.indexOf(stepObj.id) : -1; + const scmLogoProgress = + scmLogoIndex >= 0 ? scmLogoIndex / (SCM_LOGO_STEPS.length - 1) : null; + // Redirect to the first step if we end up in an invalid state const isInvalidDocsStep = stepId === 'setup-docs' && !projectSlug; if (!stepObj || stepIndex === -1 || isInvalidDocsStep) { @@ -372,6 +389,11 @@ export function OnboardingWithoutContext() { )} + {scmLogoProgress !== null && ( + + + + )} {stepObj.Component && ( @@ -402,7 +424,7 @@ function Onboarding() { ); } -const ContainerNewWelcomeUI = styled('div')<{hasFooter: boolean}>` +const OnboardingContainerNewWelcomeUI = styled('div')<{hasFooter: boolean}>` flex-grow: 1; display: flex; flex-direction: column; @@ -421,7 +443,7 @@ const ContainerNewWelcomeUI = styled('div')<{hasFooter: boolean}>` } `; -const Container = styled('div')<{hasFooter: boolean}>` +const OnboardingContainer = styled('div')<{hasFooter: boolean}>` flex-grow: 1; display: flex; flex-direction: column; From 7ff4f21abc06f158ffa31ac20ac8b8ab0eacf08a Mon Sep 17 00:00:00 2001 From: Jay Goss Date: Tue, 7 Apr 2026 12:54:17 -0500 Subject: [PATCH 3/6] ref(onboarding): Restore index.ejs mask + filter for solid fill Switch back to the index.ejs approach with mask, goo filter, and interior stroke paths in the 400x400 viewBox. The goo filter merges overlapping strokes into a solid fill, which clipPath alone could not achieve. Refs VDY-66 --- static/app/components/animatedSentryLogo.tsx | 81 ++++++++++++++------ 1 file changed, 59 insertions(+), 22 deletions(-) diff --git a/static/app/components/animatedSentryLogo.tsx b/static/app/components/animatedSentryLogo.tsx index 7568c27a51c8f5..bb40895a8bf63f 100644 --- a/static/app/components/animatedSentryLogo.tsx +++ b/static/app/components/animatedSentryLogo.tsx @@ -1,23 +1,32 @@ -import {useId} from 'react'; import {useTheme} from '@emotion/react'; interface AnimatedSentryLogoProps { /** * Draw progress from 0 (no stroke) to 1 (fully drawn). - * Uses stroke-dashoffset on the Sentry glyph outline. + * Uses the stroke-dashoffset technique from the app loading screen. */ progress: number; className?: string; size?: number; } -// Single-path Sentry glyph (64×59 viewBox) +const DASH_TOTAL = 1150; + +// Filled glyph (same path as index.ejs base + mask) const SENTRY_GLYPH = - 'M31.9948 0C33.0411 0 34.0698 0.28309 34.9701 0.816074C35.8698 1.34902 36.6108 2.11326 37.1138 3.03002L63.1813 49.4916C63.7108 50.4167 63.9896 51.4634 63.9896 52.5294C63.9896 53.3288 63.8344 54.1179 63.5327 54.8527L63.2243 55.6492C62.7224 56.5678 61.9805 57.3325 61.0806 57.8671C60.1803 58.4016 59.1523 58.6861 58.1053 58.687H51.9867V53.611H58.1053C58.275 53.6124 58.4421 53.5664 58.5894 53.4821C58.7365 53.398 58.8599 53.2774 58.9448 53.1307C59.0363 52.9806 59.0851 52.8068 59.0853 52.6309C59.0853 52.4551 59.0361 52.2814 58.9448 52.1311L32.8733 5.63052C32.7904 5.48053 32.6696 5.35444 32.5219 5.26739C32.3743 5.18042 32.2052 5.13463 32.0338 5.13463C31.8633 5.1349 31.6967 5.18091 31.5497 5.26739C31.402 5.35444 31.2773 5.48053 31.1943 5.63052L25.1968 16.3059C31.3083 20.4099 36.3755 25.887 39.9915 32.2994C44.0222 39.5769 46.1174 47.7675 46.0711 56.0865V58.4879H30.3158V55.9694C30.3478 50.489 28.9706 45.0915 26.3174 40.2961C24.0788 36.3061 20.9951 32.8521 17.282 30.1791L14.3223 35.4582C17.1667 37.6052 19.5265 40.331 21.2414 43.455C23.3822 47.3214 24.5081 51.667 24.5174 56.0865V58.609H5.92727C4.88044 58.6081 3.85214 58.3233 2.95192 57.789C2.05148 57.2543 1.31035 56.4862 0.808265 55.5672C0.278995 54.6422 0 53.5952 0 52.5294C1.59391e-05 51.4636 0.27896 50.4166 0.808265 49.4916L4.56845 42.7717C6.13984 43.3348 7.58937 44.1931 8.84406 45.2941L5.08777 52.0101C4.99593 52.1606 4.94721 52.3335 4.94721 52.5099C4.94727 52.6861 4.996 52.8592 5.08777 53.0097C5.17546 53.1534 5.29701 53.2735 5.4431 53.3572C5.58995 53.4411 5.75816 53.4875 5.92727 53.4899H19.4413C19.0534 50.4366 17.9811 47.5091 16.3059 44.927C14.6307 42.345 12.3957 40.1727 9.76556 38.5741L7.64533 37.2973L15.521 23.303L17.6413 24.5408C23.0555 27.7797 27.524 32.3846 30.5969 37.8947C33.2819 42.6981 34.8667 48.0386 35.2357 53.529H41.1512C40.7642 46.9702 38.8906 40.5854 35.673 34.8569C32.1062 28.4358 26.9071 23.071 20.601 19.3046L18.4417 18.0239L26.8758 3.03002C27.3791 2.11272 28.123 1.34906 29.0233 0.816074C29.9234 0.283345 30.9489 0.000109961 31.9948 0Z'; + 'M223.687 85.56a28.02 28.02 0 0 0-24-13.559 28.02 28.02 0 0 0-24 13.559l-39.48 67.62a193.26 193.26 0 0 1 106.5 159.96h-27.72a166.07 166.07 0 0 0-92.76-136.32L85.687 240a95.52 95.52 0 0 1 55.38 73.02h-63.66a4.55 4.55 0 0 1-3.659-2.325 4.56 4.56 0 0 1-.06-4.335l17.64-30a64.4 64.4 0 0 0-20.16-11.4l-17.46 30a27.24 27.24 0 0 0 2.023 30.435 27.2 27.2 0 0 0 8.116 7.005 27.96 27.96 0 0 0 13.56 3.6h87.18a116.41 116.41 0 0 0-48-103.86l13.86-24a143.22 143.22 0 0 1 61.8 127.86h73.86a215.28 215.28 0 0 0-98.46-190.8l28.02-48a4.62 4.62 0 0 1 6.3-1.62c3.18 1.74 121.74 208.62 123.96 211.02a4.562 4.562 0 0 1-4.08 6.78h-28.56q.54 11.46 0 22.86h28.68a27.54 27.54 0 0 0 27.72-27.66 26.94 26.94 0 0 0-3.72-13.68z'; + +// Stroke paths that trace the logo outline (one group from index.ejs) +const STROKE_PATHS = [ + 'm82.8 253.5-12.7 22-12.5 21.2-.2.3v.1l-.2.2v.3a24 24 0 00.3 22.4l.3.6A24 24 0 0076.4 332h87l-3.2-21.8A115 115 0 00112 233l17.6-30.4a147 147 0 0166.8 112l1.3 17.5h65.6l-1.3-20.2a212 212 0 00-99.3-165.8l29.6-50.8a9 9 0 013.3-3.1 9 9 0 0112.1 3.1l122.2 209.2h.1a8 8 0 011 4v.2a8.5 8.5 0 01-8.6 8.5h-38.9', + 'M92.5 268.7 88 276.6 71.4 305v.1a8 8 0 000 7.2l.2.4a8 8 0 006 3.4h67.3l-.6-3.4A99 99 0 0097.2 242.5l-6.8-4 33.4-57.8 6.9 4a163 163 0 0181.6 128.8l.2 2.7h33.7L246 313A196 196 0 00147.8 155.7l-7-4L178.6 87v-.1a25 25 0 0133-9.1l.8.3a25 25 0 019 8.9h.1v.2L343.7 296.4a24 24 0 013.3 12v.4a24.5 24.5 0 01-24.6 24.4H291.5', + 'M85.7 264.5l-8.7 15-12.5 21.3-.1.2-.1.3A15.6 15.6 0 0076.9 324h77.2l-1.8-12.6a107 107 0 00-51-75.8l25.5-44.1A155 155 0 01204.3 314l.8 10.1h49.7l-.8-11.7A204 204 0 00151.8 148.9l33.6-57.7a17 17 0 0129.2 0L336.8 300.4a16 16 0 012.2 8.2 16.5 16.5 0 01-16.6 16.6H291.5', +]; /** - * Sentry brand mark with stroke-draw progress. The outline draws - * itself via stroke-dashoffset, driven by a `progress` prop (0-1). + * Sentry brand mark with stroke-draw progress, adapted from the app + * loading animation in static/index.ejs. Single color, progress-driven + * instead of the looping multicolor original. */ export function AnimatedSentryLogo({ progress, @@ -25,13 +34,16 @@ export function AnimatedSentryLogo({ className, }: AnimatedSentryLogoProps) { const theme = useTheme(); - const clipId = useId(); + const gooId = 'goo-layer'; + const maskId = 'mask-layer'; + + const dashOffset = DASH_TOTAL * (1 - progress); return ( - - - + + + + + + + + + {/* Muted base shape */} - + - {/* Stroke-draw overlay, clipped to the glyph shape */} - + > + {STROKE_PATHS.map((d, i) => ( + + ))} + ); } From c017b7ab008e5431a9fc6ab1b292bbb216fc996f Mon Sep 17 00:00:00 2001 From: Jay Goss Date: Tue, 7 Apr 2026 14:37:05 -0500 Subject: [PATCH 4/6] ref(onboarding): Clean up animated logo mask fill and default props Use content.accent for the mask fill to match the stroke color, drop the unnecessary React.CSSProperties cast, and remove the redundant default size prop. --- static/app/components/animatedSentryLogo.tsx | 4 ++-- static/app/views/onboarding/onboarding.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/static/app/components/animatedSentryLogo.tsx b/static/app/components/animatedSentryLogo.tsx index bb40895a8bf63f..a8bb384d5108c8 100644 --- a/static/app/components/animatedSentryLogo.tsx +++ b/static/app/components/animatedSentryLogo.tsx @@ -70,9 +70,9 @@ export function AnimatedSentryLogo({ x="50" y="72" maskUnits="userSpaceOnUse" - style={{maskType: 'alpha'} as React.CSSProperties} + style={{maskType: 'alpha'}} > - + {/* Muted base shape */} diff --git a/static/app/views/onboarding/onboarding.tsx b/static/app/views/onboarding/onboarding.tsx index 62ecec80b957e0..124b53bfa6398c 100644 --- a/static/app/views/onboarding/onboarding.tsx +++ b/static/app/views/onboarding/onboarding.tsx @@ -391,7 +391,7 @@ export function OnboardingWithoutContext() { )} {scmLogoProgress !== null && ( - + )} From 2a67c32da6e7ce8d1246c55c3e35dc94370369d6 Mon Sep 17 00:00:00 2001 From: Jay Goss Date: Tue, 7 Apr 2026 14:55:20 -0500 Subject: [PATCH 5/6] fix(onboarding): Guard SCM logo progress against single-step division by zero Add a length check before dividing by SCM_LOGO_STEPS.length - 1 to prevent Infinity if the array is ever reduced to a single element. --- static/app/views/onboarding/onboarding.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/static/app/views/onboarding/onboarding.tsx b/static/app/views/onboarding/onboarding.tsx index 124b53bfa6398c..73f322b5d6aab7 100644 --- a/static/app/views/onboarding/onboarding.tsx +++ b/static/app/views/onboarding/onboarding.tsx @@ -317,7 +317,9 @@ export function OnboardingWithoutContext() { const scmLogoIndex = stepObj ? SCM_LOGO_STEPS.indexOf(stepObj.id) : -1; const scmLogoProgress = - scmLogoIndex >= 0 ? scmLogoIndex / (SCM_LOGO_STEPS.length - 1) : null; + scmLogoIndex >= 0 && SCM_LOGO_STEPS.length > 1 + ? scmLogoIndex / (SCM_LOGO_STEPS.length - 1) + : null; // Redirect to the first step if we end up in an invalid state const isInvalidDocsStep = stepId === 'setup-docs' && !projectSlug; From ee5ce712c28caf594e98d4801b9d22d98c11ab8a Mon Sep 17 00:00:00 2001 From: Jay Goss Date: Tue, 7 Apr 2026 15:20:49 -0500 Subject: [PATCH 6/6] fix(onboarding): Use unique SVG IDs and correct feColorMatrix attribute Use useId() for SVG filter/mask IDs to prevent collisions if multiple AnimatedSentryLogo instances render on the same page. Also fix feColorMatrix attribute from `mode` to `type` (the correct SVG attr). --- static/app/components/animatedSentryLogo.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/static/app/components/animatedSentryLogo.tsx b/static/app/components/animatedSentryLogo.tsx index a8bb384d5108c8..afc089e5b5f11f 100644 --- a/static/app/components/animatedSentryLogo.tsx +++ b/static/app/components/animatedSentryLogo.tsx @@ -1,3 +1,4 @@ +import {useId} from 'react'; import {useTheme} from '@emotion/react'; interface AnimatedSentryLogoProps { @@ -34,8 +35,9 @@ export function AnimatedSentryLogo({ className, }: AnimatedSentryLogoProps) { const theme = useTheme(); - const gooId = 'goo-layer'; - const maskId = 'mask-layer'; + const id = useId(); + const gooId = `goo-layer-${id}`; + const maskId = `mask-layer-${id}`; const dashOffset = DASH_TOTAL * (1 - progress); @@ -55,7 +57,7 @@ export function AnimatedSentryLogo({