@@ -719,7 +719,6 @@ const OnboardingV2Page = (): ReactElement => {
719719 const shouldRun =
720720 feedVisible &&
721721 ! feedReadyState &&
722- ! panelVisible &&
723722 ! showSignupChooser &&
724723 ! showSignupPrompt &&
725724 ! showGithubImportFlow &&
@@ -823,6 +822,7 @@ const OnboardingV2Page = (): ReactElement => {
823822
824823 const article = articles [ Math . floor ( Math . random ( ) * articles . length ) ] ;
825824 article . setAttribute ( 'data-eng-active' , 'true' ) ;
825+ article . classList . add ( 'relative' ) ; // Ensure article can contain absolute elements
826826
827827 const isUpvote = Math . random ( ) < 0.7 ;
828828 const suffix = isUpvote ? '-upvote-btn' : '-comment-btn' ;
@@ -835,27 +835,10 @@ const OnboardingV2Page = (): ReactElement => {
835835 return ;
836836 }
837837
838- const wrapperEl = wrapper as HTMLElement ;
839- // Add CSS class instead of direct style mutation — the class provides
840- // position:relative via the stylesheet without forcing a style recalculation.
841- wrapperEl . classList . add ( 'onb-eng-pos-relative' ) ;
842-
843- const counter = ensureCounter (
844- wrapper ,
845- isUpvote
846- ? 4 + Math . floor ( Math . random ( ) * 50 )
847- : 1 + Math . floor ( Math . random ( ) * 10 ) ,
848- ) ;
849-
850- const activeClass = isUpvote
851- ? 'onb-eng-active-upvote'
852- : 'onb-eng-active-comment' ;
853838 const color = isUpvote
854839 ? 'var(--theme-actions-upvote-default)'
855840 : 'var(--theme-actions-comment-default)' ;
856841
857- wrapperEl . classList . add ( activeClass ) ;
858-
859842 const numIncrements = 1 + Math . floor ( Math . random ( ) * 3 ) ; // 1 to 3 increments
860843 const increments : number [ ] = [ ] ;
861844 for ( let i = 0 ; i < numIncrements ; i += 1 ) {
@@ -875,35 +858,35 @@ const OnboardingV2Page = (): ReactElement => {
875858
876859 increments . forEach ( ( inc ) => {
877860 addTimeout ( ( ) => {
878- // Restart the pulse animation without reading layout (offsetWidth
879- // forces a synchronous reflow). Setting animationName to 'none' is a
880- // write-only style operation, then the next rAF restores it so the
881- // animation re-runs from the start.
882- btn . classList . remove ( 'onb-eng-pulse' ) ;
883- ( btn as HTMLElement ) . style . animationName = 'none' ;
884- requestAnimationFrame ( ( ) => {
885- ( btn as HTMLElement ) . style . animationName = '' ;
886- btn . classList . add ( 'onb-eng-pulse' ) ;
887- } ) ;
888-
889- const currentVal = parseCount ( counter . textContent || '' ) || 0 ;
890- counter . textContent = formatCount ( currentVal + inc ) ;
891-
892- const floaterAnchor =
893- counter . parentElement instanceof HTMLElement
894- ? counter . parentElement
895- : wrapperEl ;
896- floaterAnchor . classList . add ( 'onb-eng-pos-relative' ) ;
861+ const btnRect = btn . getBoundingClientRect ( ) ;
862+ const articleRect = article . getBoundingClientRect ( ) ;
863+
864+ // Center of the button relative to the article
865+ const startX = btnRect . left - articleRect . left + btnRect . width / 2 ;
866+ const startY = btnRect . top - articleRect . top ;
897867
898868 const floater = document . createElement ( 'span' ) ;
899869 floater . className = 'onb-eng-floater' ;
900870 floater . style . color = color ;
901- floater . style . left = `${ counter . offsetLeft } px` ;
902- floater . style . top = `${ counter . offsetTop } px` ;
871+
872+ // Random offset so they don't stack on top of each other (PolyMarket style)
873+ const offsetX = ( Math . random ( ) - 0.5 ) * 32 ; // -16px to +16px
874+ const offsetY = ( Math . random ( ) - 0.5 ) * 16 ; // -8px to +8px
875+
876+ floater . style . left = `${ startX + offsetX } px` ;
877+ floater . style . top = `${ startY + offsetY } px` ;
903878 floater . textContent = `+${ inc } ` ;
904- floaterAnchor . appendChild ( floater ) ;
879+
880+ article . appendChild ( floater ) ;
905881 activeFloaters . add ( floater ) ;
906882
883+ // Update the actual counter text
884+ const counter = ensureCounter ( wrapper , isUpvote ? 14 : 3 ) ;
885+ if ( counter ) {
886+ const currentVal = parseCount ( counter . textContent || '' ) || 0 ;
887+ counter . textContent = formatCount ( currentVal + inc ) ;
888+ }
889+
907890 addTimeout ( ( ) => {
908891 floater . remove ( ) ;
909892 activeFloaters . delete ( floater ) ;
@@ -914,7 +897,6 @@ const OnboardingV2Page = (): ReactElement => {
914897 } ) ;
915898
916899 addTimeout ( ( ) => {
917- wrapperEl . classList . remove ( activeClass ) ;
918900 article . removeAttribute ( 'data-eng-active' ) ;
919901 } , delayAcc + 600 ) ;
920902
@@ -2836,70 +2818,34 @@ const OnboardingV2Page = (): ReactElement => {
28362818 opacity: 1;
28372819 }
28382820
2839- .onb-eng-active-upvote,
2840- .onb-eng-active-upvote svg,
2841- .onb-eng-active-upvote span {
2842- color: var(--theme-actions-upvote-default) !important;
2843- }
2844- .onb-eng-active-comment,
2845- .onb-eng-active-comment svg,
2846- .onb-eng-active-comment span {
2847- color: var(--theme-actions-comment-default) !important;
2848- }
2849-
2850- .onb-eng-pulse {
2851- animation: onb-eng-pulse-anim 0.3s
2852- cubic-bezier(0.175, 0.885, 0.32, 1.275);
2853- }
2854- @keyframes onb-eng-pulse-anim {
2855- 0% {
2856- transform: scale(1);
2857- }
2858- 50% {
2859- transform: scale(1.25);
2860- }
2861- 100% {
2862- transform: scale(1);
2863- }
2864- }
2865-
2866- .onb-eng-pos-relative {
2867- position: relative;
2868- }
2869- .onb-eng-floater-anchor {
2870- position: relative;
2871- }
28722821 .onb-eng-floater {
28732822 position: absolute;
2874- font-size: 0.875rem ;
2823+ font-size: 0.75rem ;
28752824 font-weight: 800;
28762825 font-variant-numeric: tabular-nums;
28772826 white-space: nowrap;
28782827 pointer-events: none;
2879- z-index: 2;
2880- animation: onb-eng-float-anim 1.4s ease-out forwards;
2828+ z-index: 10;
2829+ animation: onb-eng-float-anim 1.6s cubic-bezier(0.2, 0.8, 0.2, 1)
2830+ forwards;
28812831 text-shadow: 0 1px 4px
28822832 color-mix(in srgb, currentColor 25%, transparent);
28832833 }
28842834 @keyframes onb-eng-float-anim {
28852835 0% {
2886- transform: translateY(0 ) scale(0.6);
2836+ transform: translateY(10px ) scale(0.6);
28872837 opacity: 0;
28882838 }
2889- 10 % {
2890- transform: translateY(-8px ) scale(1.1);
2839+ 15 % {
2840+ transform: translateY(0px ) scale(1.1);
28912841 opacity: 1;
28922842 }
2893- 25 % {
2894- transform: translateY(-12px ) scale(1);
2843+ 30 % {
2844+ transform: translateY(-5px ) scale(1);
28952845 opacity: 1;
28962846 }
2897- 70% {
2898- transform: translateY(-24px) scale(1);
2899- opacity: 0.8;
2900- }
29012847 100% {
2902- transform: translateY(-32px ) scale(0.95 );
2848+ transform: translateY(-30px ) scale(0.9 );
29032849 opacity: 0;
29042850 }
29052851 }
0 commit comments