From da8fe7f6f7135a26c4d1fa8be6711f7554e23769 Mon Sep 17 00:00:00 2001 From: Thomas des Francs Date: Thu, 16 Apr 2026 08:42:25 +0200 Subject: [PATCH] Homepage 3 cards finished (#19732) & various fixes --- .../images/home/stepper/download-worker.webp | Bin 0 -> 18940 bytes .../familiar-interface-gradient.webp | Bin 0 -> 6570 bytes .../fast-path-background-noise.webp | Bin 0 -> 50644 bytes .../fast-path-gradient.webp | Bin 0 -> 6810 bytes .../live-data-gradient.webp | Bin 0 -> 3168 bytes .../images/shared/companies/logos/airbnb.png | Bin 27169 -> 2692 bytes .../shared/companies/logos/anthropic.png | Bin 0 -> 1476 bytes .../images/shared/companies/logos/cursor.png | Bin 0 -> 1837 bytes .../images/shared/companies/logos/figma.png | Bin 10066 -> 2638 bytes .../images/shared/companies/logos/github.png | Bin 3551 -> 2746 bytes .../images/shared/companies/logos/linear.svg | 7 + .../images/shared/companies/logos/notion.png | Bin 4827 -> 1874 bytes .../images/shared/companies/logos/twenty.svg | 13 + .../(home)/_constants/three-cards-feature.ts | 6 + .../halftone/_components/HalftoneCanvas.tsx | 24 +- .../src/app/pricing/_constants/salesforce.ts | 4 +- .../Helped/HelpedHalftoneModel.tsx | 151 +- .../src/illustrations/Helped/Money.tsx | 64 +- .../src/illustrations/Helped/Spaceship.tsx | 52 +- .../src/illustrations/Helped/Target.tsx | 52 +- .../src/lib/shared-asset-paths.ts | 4 + .../Visual/StepperBackgroundHalftone.tsx | 36 +- .../HomeStepper/components/Visual/Visual.tsx | 5 +- .../src/sections/Signoff/components/Root.tsx | 2 +- .../FamiliarInterfaceGradientBackdrop.tsx | 921 ++++++++++++ .../FeatureCard/FamiliarInterfaceVisual.tsx | 286 ++-- .../FeatureCard/FastPathGradientBackdrop.tsx | 924 ++++++++++++ .../components/FeatureCard/FastPathVisual.tsx | 1242 ++++++++++++----- .../components/FeatureCard/FeatureCard.tsx | 54 +- .../FeatureCard/LiveDataGradientBackdrop.tsx | 923 ++++++++++++ .../FeatureCard/LiveDataHeroTable.tsx | 164 ++- .../components/FeatureCard/LiveDataVisual.tsx | 753 ++++++---- .../components/FeatureCards/FeatureCards.tsx | 10 + .../ThreeCardsScrollLayoutEffect.tsx | 9 +- .../ThreeCards/types/ThreeCardsFeatureCard.ts | 2 + .../utils/three-cards-scroll-layout.ts | 39 +- 36 files changed, 4910 insertions(+), 837 deletions(-) create mode 100644 packages/twenty-website-new/public/images/home/stepper/download-worker.webp create mode 100644 packages/twenty-website-new/public/images/home/three-cards-feature/familiar-interface-gradient.webp create mode 100644 packages/twenty-website-new/public/images/home/three-cards-feature/fast-path-background-noise.webp create mode 100644 packages/twenty-website-new/public/images/home/three-cards-feature/fast-path-gradient.webp create mode 100644 packages/twenty-website-new/public/images/home/three-cards-feature/live-data-gradient.webp create mode 100644 packages/twenty-website-new/public/images/shared/companies/logos/anthropic.png create mode 100644 packages/twenty-website-new/public/images/shared/companies/logos/cursor.png create mode 100644 packages/twenty-website-new/public/images/shared/companies/logos/linear.svg create mode 100644 packages/twenty-website-new/public/images/shared/companies/logos/twenty.svg create mode 100644 packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/FamiliarInterfaceGradientBackdrop.tsx create mode 100644 packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/FastPathGradientBackdrop.tsx create mode 100644 packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/LiveDataGradientBackdrop.tsx diff --git a/packages/twenty-website-new/public/images/home/stepper/download-worker.webp b/packages/twenty-website-new/public/images/home/stepper/download-worker.webp new file mode 100644 index 0000000000000000000000000000000000000000..1a72b984b2b1e690bba8329321691f6d7e84b0a1 GIT binary patch literal 18940 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 bIMrSUN#^o(!6s~N1)IeIx)}gox`Ge@tP|Yu literal 0 HcmV?d00001 diff --git a/packages/twenty-website-new/public/images/home/three-cards-feature/familiar-interface-gradient.webp b/packages/twenty-website-new/public/images/home/three-cards-feature/familiar-interface-gradient.webp new file mode 100644 index 0000000000000000000000000000000000000000..dd618465887e236abe867c18b4f883558a2f216f GIT binary patch literal 6570 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%0D@4>v7Ynj*E`sDJ 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 Ir#AQi0HaSj!TqojP&gn63;+P|umGI_DgXl) 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 GhJXMu^7v!` literal 0 HcmV?d00001 diff --git a/packages/twenty-website-new/public/images/shared/companies/logos/airbnb.png b/packages/twenty-website-new/public/images/shared/companies/logos/airbnb.png index 45d956fae80839fae6148549ea489f1f7e5d888e..85c649bf22797bc6152510ef6a6ecc50e5f628e6 100644 GIT binary patch literal 2692 zcmYM0cQ_kbAIB4k5UX*us2Qyq9cJx|P$O2%nne_?acjj$U1~ID&C>KzY8I_cjoP6$ z5u+MKt=hdR_KcVJz0ZBm^L)?ed%n;2cm6!ji7`THGc#~80001H9i)cIIVCPcPjkML z(H^9805K-o>VPsL&+7RB>ujm>$iM(_^W4(|z(6hl?M3BWDnRaky%tae0Qzr!eovGu z0Q^q}bMfPd0rf&!VE_O|whI9P=^32o z**hH#H8V@--zHNJMz-7=6~C)qVW_p2ut)^;hL zs)vjd8uy^^So=ljI#2PBLus^Db0!=T8CqMcYJv%c7OP6`rDJE#i19ww4sze&Wr5xC zjoF1EDBPB{WyqmU7U_3O`t^Q%I;!yHaH%I1; zJT00dj>eTWpYyR(hxv4%mVPq>(x(y(fTaiD*hbuh!CXS)9_DSa&Qmo>$sseHstU23 z04|aUyYROKnKuBQGip_lue|=wL{o*i2t3*^FW<_nU+BRjV!`yK$C1iqG+)#!KDof{d>D54oYLxKqPyQ!= zMip@lJ=;T?rGf(TIbK0 z#~BM(aJM#6V)HAIHdMCXDP-;~=;$plOertZJNu!45xY~C=Cn-o(KRJ(+9-YLm44`> z8Pv@zYo4UJ{aW`B;1?CCFZel@7LoYD6P8tr*pOtYbT0$|eJmX7TVt5?;9YQmkz5H1 zRhzQ$9On9CtrrfN6GCO;*K6Cv34IY#X+74r>`#_y!9p)eTHa|5lt{T|7xJz2-t2I) zWNj*1D3sV}bYx0?Y>f(cC+EVpWb65v($2EJZ|#p_JkDaw?J$X@Ia8)2cU^~E|wzjK_Q2<3P2n)hv@D9f8$PS0CLpA=5cG7&aX z&8rLPWK5IHu!HRQUF}4O!uvClH2v@TVrPf z!;xyO)z$k=AKIPrmA&YzJx0;`R+|$P>;j*LgiWQTRI;-B+%-}o8(H78AKGIvh;ow& zmMSil?IO!~R{ufWt%fU0bMYvZ2JpAVy`)>Z6|zq;5YSp`HakJ&( z$Z~aL-`gGy*7nlOnzIyApw_9oXFsrKdm)MDV4vs!{Caj2z2W)U%g$fUXkJ1Xb^cOx zOV$6awHb(HHou9Ts~c4Bs{bmBDO64u7oF9(%a2#u2hHqkD2&W31q$q(Wr30`97~%^ zey@KA!7Xx6V!`IE72M)&5iTc!)@OY0Qk9K4Ug0%vbe_a0b`=E$h)-gYHUXcHrW7UW zdP-)N;^yQmCMB<Y(Stcp*)q92~R4Z|9?QvQg-l-LKF65omn znP-GFv0xd6%~$qVj@#D&WsWY0_n)E4b_b7hNEuY;-_xS`p7#GLUznHRyo3EiGH zjj;E6E>2kd3ySZ=<>F0|+$NG*@x05viQ(df1jZFQ(o*eu_?zO!V1Xw8bQ>vcXM|!D z1|BikGu-5J+v(Qyw^+%k8cg&e4Y*$56|YL!o5ZbLK&kc8r-qd+bQ~_wWrCL=otT{b zjzVWJ+;Yj)RVwJn`03ei%yso)pt_3{-mP#u7Ed>_VQtq zwd8ws+8jkP&Xkqh`&e}`w_m$a+al7iq|G6lg{R8!l;w$=muY!ETi4bn8J`{crw8(D zD>y^IxMiXKWr=Dxt1Y^HxVn^h8FXL9VK_5Ftd@&NiD%7K(aRV%S3X2r)`5jI`FY1} zW(u`!Y^8ihg&~+S;XGY}7o|Y?piVpnvmB#o?5(c)>BCCdC%C6l!#wY=g%G($;zw?| zNMf`3m_DR*)o*gO+@2kI@9o}U0K#K{mVQi4gR`RI#oXNxmo!U1j$2~ki;cF>op5E} ze$m4<(~s6s0HK$|Ywu93fl?MqlklZ>i+E1engDD6S{wpr0Ij=cKY@IIl=`XxaktXGBtho0k;(50a% zPF{y9ZVWWw^jR(lL*xBN!&nSUg6Mh~)i5zye4L>@<@okG2uu2UvPM^rd`3|zW|{{F o3sSqwXK0RYwzoR36muS3vSu@K`F?Wa;?D@s(L`yKsiVXH1NNKn)c^nh literal 27169 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 C)AFAH diff --git a/packages/twenty-website-new/public/images/shared/companies/logos/anthropic.png b/packages/twenty-website-new/public/images/shared/companies/logos/anthropic.png new file mode 100644 index 0000000000000000000000000000000000000000..63b112ea508591b01266a8cf186083a12b479ec4 GIT binary patch literal 1476 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 bV&wKO&hOX|%4;@400000NkvXXu0mjfW14OB literal 0 HcmV?d00001 diff --git a/packages/twenty-website-new/public/images/shared/companies/logos/figma.png b/packages/twenty-website-new/public/images/shared/companies/logos/figma.png index da239c8bcb7242617e047fa9768daa82bf7dd99c..b8123ce8d0a516d346f858354a9a6e32943af032 100644 GIT binary patch literal 2638 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 literal 10066 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 literal 3551 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 diff --git a/packages/twenty-website-new/public/images/shared/companies/logos/linear.svg b/packages/twenty-website-new/public/images/shared/companies/logos/linear.svg new file mode 100644 index 0000000000..8147bdefa2 --- /dev/null +++ b/packages/twenty-website-new/public/images/shared/companies/logos/linear.svg @@ -0,0 +1,7 @@ + + + + diff --git a/packages/twenty-website-new/public/images/shared/companies/logos/notion.png b/packages/twenty-website-new/public/images/shared/companies/logos/notion.png index 33b1bafeb1466a04dbd8ade133414b90399a7b7e..3053fd47945864acfd4106ea6ab67eee4372170f 100644 GIT binary patch literal 1874 zcmYL~c{tnI8pnSTBGfiR?Mv;1Q9;o$p*5Byh*mA39a^Gjwbt5~plDoGJ5jYf9dyJt z_C2*rheVW?)OKrMTU*gsZrnT1-19u&^L@|zeBOWF=e*|-ZeYxLxy88w0N_O-O>9`M zc`hh~wL7pKC9(_@U}J6!R8b{YS%(mJEb6|M6#!$!P=FmI4se_!EE$3%{)x>%DggMu zc{Tt@^a9xbW-u&2U#Tpe*Zj}HMWDZFR<4NcA6l~r{Qvm8jH`95^4xrpjsXB5tavUE z%0}TU06+pzCf98}IW~#mo-`NHfx_V_v3{J!nYSqMHcIZMisSZxjT6>_bVZTij~f0- z0=}E5Dtkt-Ili~eN#bgFcBd{{7`>pyqn-0&H*Bqfw%kTL_Gi-BqT%kReXXRmGoC|ou;x11c5azgxU;|RaCCfB7Z?)_*di%- z7jhJ($!FGG6@OSo#mV@tx6U8V=RYS<2Q0ffvj`Bq4P4(T?c_wIpML&1`3+qMDk8$iEN1 z%#K#O7)^FYws}w1nvw+ z&61Cjmo|)8+1r|Kdnv|afkx+ltaf4Y@>kM;xH=D-^lv&iVId)X7Mo0&J%A#@_KrRk!BpFHv6O{eprr!EVRZy@>YQB08i zm*b;dgs9?Z*Mr(5p-i?0Z%~$sy1*RZM4Xp@1i4W$yPc6%56Y%Y{OM)~O~QJbXmU?{ zHiPvSsiU2m`vkRT#mzRA>?3*-c$3WNslx_62!sBE)+g&Y*6o2wa^Pv6Y_2r~rr@c9 zbO{c4EYptBoBWrg1qKcARf-#ZspqpWPI3sNT$i~)upmbq`6FBjJJu9Da_Q1AtCx1N zBP%D&1E)hJ1;={NG>;^|%p{y>7ro9G5&q5FvC*68;oWYg0udk!-26^|UuIp>(&9LO ziD{72R9kD4mzTG|Xn2@}F0(HDFs`hFN-z#h`w3ffZEbzs{c|q?fisFa#(vxUw7e=~ z6xE*l)#O8kGNv&%t&zO=voG5k28r_O{upF7?a ze^GMNKfqkqXQsmWQwlGMZyX>AVIGdXx!L%~K5Hx%-^PFoIyJ?;;J^z@AR#W`eVV-a z!iUP+TUBpoz2JEHI@dmKRW-F;73ho)qaO9zn#K=0rz0%|F1H13ru)tJhmN&HG8@On z$3wrz>WsPq@8V#gp`o?m2V2s9qg`>F93hG++-NWO+sd)EaZ*Wh^Tc$CCp&CAgbQ69 z@)?U3ko!0`HaPrJ?@D`(TlJk5xu-u~SKTzg$KEQnOndaHF*Hzdv^`)srR;K6LNzG= z9y+%^>LiS45d1h-Ju9agG}lByKZPX&eCB3SN=M?F_lHDh->MhfWx`%7bzMaDT5g!+ zE<6N$>?JW;y~M^YG5#|K!>J{3HN6xw6)_kieDPvS+a4lyaQ!cBitdo(fnwZz%J%OC z3)#fin;FX1VM}+KeY=Qow*)u)8SR-v(7;@Whx+sEY++?1BbR}^KK~*4epzi1lm|7R z4p*2&HXhxy6;<5#TN#mUi9YL`Y73?@)^E?(L!|~i($i`v&fV)sYo0jAy8D#p5_&o! zKl?8ri8@|3{2l}PGhBWY8Z5YwASabEvR|?B$E~udUq`<^64u<8s>$BvWVi0$+1c3& zETm7I<_8WlbMDfFw|G~PwPEeWl)dfYIC62Ztj5_J^Fc(wCZl03Fhv?sKD|tR_1^7T z`a5nDm158pF-Ve?-snJIsg;#28q!^Q37!z7!)6PmN2=0K^kQGdUl0kBQ51Z3wI8tR zF?V*Be-nOZ#0Qr;qgRb^v5U-T>&(u>oZ_*yIKnT{r@t@+W}^TQ887Je@v!u(|9p1_ zp{OyZaJ+QHX9Qhp^>T^z;IL+$?afp3K4|y7%p$xYR6z>4Nqxc4h0HCg4wYb+IltJd yc!6zxNT*)q0v`R`$GLn$!_PewP400agQEets^>9HP3mRme-p|SV^U>&H}*fhwpoM# literal 4827 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

@%wmBL_e}H`Im=< zn$;64BP%Pb=T;|}^p~gBuj*`=rvA~}$y#gWRVs^s7QHH?)J9CV43nNbKex<@*a5wL z7)cRUWlnw;vF^K7Z)+rhg5S6-w0JqDIy0O$yUa>r<*oINcBaT6a&|~h_~CXtmTrw2 z$==OGQhvtDvwDpz#XC64idWwoot+e6MRxSgu_{?=j(gA6PD6G_k;H7LWZag|=?Qec zKtP#F@Jckdn(QT8VxNONJqLMwv-8(V!(uinV$MgX8J;8u7ChESa#sE@0&>x(w*qdq z-&D08v{cl>XNWYKkiAe98MeQtsvdL;-cHE;vi;pqCHHxSA}%smYka{IO#qtzKs8f6 zH|rnA&`#Fbs;}y;2Vk|H!i$M?uZ<#3zVp{2^_OD2Uv7bfSEa@7MBOgubR0s%f;Ee` z)Nv4aW$hKCf7?-)nb#Wc8ouIz^N}e7x8(J1+Z;6p4!^|QPhq^cxY>C(78s-FS*)b0XQ-23DB(;q6)OYU1`X@Z*NjDYR2Xrpgd;H7&3xiU$HtmMVaQDlpp zlM)W!W9^PtJ^b?=eey z!U)JR4i;L61^bzV^}G5967Ol2=?S^ep{TgEMELbgsw6KaJ@3!CtSAvnd}9WP_fn(Y z>=nD>Bn1K_DMWw=Z`3U=^JgO75H*nglJmbsp8u-`R1yp+3$O|%*}2}qR;z&OV=cw% I|13iP2NPukCIA2c diff --git a/packages/twenty-website-new/public/images/shared/companies/logos/twenty.svg b/packages/twenty-website-new/public/images/shared/companies/logos/twenty.svg new file mode 100644 index 0000000000..e911214799 --- /dev/null +++ b/packages/twenty-website-new/public/images/shared/companies/logos/twenty.svg @@ -0,0 +1,13 @@ + + + + diff --git a/packages/twenty-website-new/src/app/(home)/_constants/three-cards-feature.ts b/packages/twenty-website-new/src/app/(home)/_constants/three-cards-feature.ts index 9457471db7..ab33265c20 100644 --- a/packages/twenty-website-new/src/app/(home)/_constants/three-cards-feature.ts +++ b/packages/twenty-website-new/src/app/(home)/_constants/three-cards-feature.ts @@ -18,6 +18,8 @@ export const THREE_CARDS_FEATURE_DATA: ThreeCardsFeatureCardsDataType = { body: { text: "Twenty makes it simple. It's clean, intuitive, and built to feel like Notion.", }, + backgroundImageSrc: + '/images/home/three-cards-feature/familiar-interface-gradient.webp', icon: 'users-group', illustration: 'familiar-interface', }, @@ -26,6 +28,8 @@ export const THREE_CARDS_FEATURE_DATA: ThreeCardsFeatureCardsDataType = { body: { text: 'Everything updates in real time, with AI chat always ready to help you move faster.', }, + backgroundImageSrc: + '/images/home/three-cards-feature/live-data-gradient.webp', icon: 'live-data', illustration: 'live-data', }, @@ -34,6 +38,8 @@ export const THREE_CARDS_FEATURE_DATA: ThreeCardsFeatureCardsDataType = { body: { text: 'Smart patterns, shortcuts, and layouts make everyday tasks faster and easier to execute.', }, + backgroundImageSrc: + '/images/home/three-cards-feature/fast-path-gradient.webp', icon: 'fast-path', illustration: 'fast-path', }, diff --git a/packages/twenty-website-new/src/app/halftone/_components/HalftoneCanvas.tsx b/packages/twenty-website-new/src/app/halftone/_components/HalftoneCanvas.tsx index 93d9406fcc..1745f8d94b 100644 --- a/packages/twenty-website-new/src/app/halftone/_components/HalftoneCanvas.tsx +++ b/packages/twenty-website-new/src/app/halftone/_components/HalftoneCanvas.tsx @@ -265,8 +265,9 @@ const IMAGE_HOVER_FADE_IN = 18; const IMAGE_HOVER_FADE_OUT = 7; const MAX_PREVIEW_PIXEL_RATIO = 2; -const CanvasMount = styled.div<{ $background: string }>` - background: ${(props) => props.$background}; +const CanvasMount = styled.div<{ $background: string; $transparent: boolean }>` + background: ${(props) => + props.$transparent ? 'transparent' : props.$background}; display: block; height: 100%; min-width: 0; @@ -1618,10 +1619,10 @@ export function HalftoneCanvas({ -interaction.pointerVelocityY * logicalHeight, ); halftoneMaterial.uniforms.dragOffset.value.set(0, 0); - halftoneMaterial.uniforms.hoverHalftoneActive.value = - activeSettings.animation.hoverHalftoneEnabled - ? interaction.hoverStrength - : 0; + halftoneMaterial.uniforms.hoverHalftoneActive.value = activeSettings + .animation.hoverHalftoneEnabled + ? interaction.hoverStrength + : 0; halftoneMaterial.uniforms.hoverHalftonePowerShift.value = activeSettings.animation.hoverHalftoneEnabled ? activeSettings.animation.hoverHalftonePowerShift @@ -1632,11 +1633,11 @@ export function HalftoneCanvas({ activeSettings.animation.hoverHalftoneEnabled ? activeSettings.animation.hoverHalftoneWidthShift : 0; - halftoneMaterial.uniforms.hoverLightStrength.value = - activeSettings.animation.hoverLightEnabled - ? activeSettings.animation.hoverLightIntensity * - interaction.hoverStrength - : 0; + halftoneMaterial.uniforms.hoverLightStrength.value = activeSettings + .animation.hoverLightEnabled + ? activeSettings.animation.hoverLightIntensity * + interaction.hoverStrength + : 0; halftoneMaterial.uniforms.hoverLightRadius.value = activeSettings.animation.hoverLightRadius; halftoneMaterial.uniforms.hoverFlowStrength.value = 0; @@ -2031,6 +2032,7 @@ export function HalftoneCanvas({ return ( diff --git a/packages/twenty-website-new/src/app/pricing/_constants/salesforce.ts b/packages/twenty-website-new/src/app/pricing/_constants/salesforce.ts index ab78e336d5..35c567845c 100644 --- a/packages/twenty-website-new/src/app/pricing/_constants/salesforce.ts +++ b/packages/twenty-website-new/src/app/pricing/_constants/salesforce.ts @@ -225,12 +225,12 @@ export const SALESFORCE_DATA: SalesforceDataType = { productIconAlt: 'Retro help document icon', productIconSrc: '/images/pricing/salesforce/help-icon.webp', priceSuffix: ' / seat / month - billed yearly', - productTitle: 'Salesforce Pro', + productTitle: 'Salesfarce Pro', secondaryCtaNote: 'More options available!', secondaryCtaHref: 'https://www.salesforce.com/en-us/wp-content/uploads/sites/4/documents/pricing/all-add-ons.pdf', secondaryCtaLabel: 'Check more add-ons', totalPriceLabel: 'total per month with fixed cost', - windowTitle: 'Salesforce Pro pricing', + windowTitle: 'Salesfarce Add-on Center', }, }; diff --git a/packages/twenty-website-new/src/illustrations/Helped/HelpedHalftoneModel.tsx b/packages/twenty-website-new/src/illustrations/Helped/HelpedHalftoneModel.tsx index f1fa1586f0..16377c03bb 100644 --- a/packages/twenty-website-new/src/illustrations/Helped/HelpedHalftoneModel.tsx +++ b/packages/twenty-website-new/src/illustrations/Helped/HelpedHalftoneModel.tsx @@ -1,5 +1,17 @@ 'use client'; +import { HalftoneCanvas } from '@/app/halftone/_components/HalftoneCanvas'; +import { + DEFAULT_GLASS_ANIMATION_SETTINGS, + DEFAULT_GLASS_BACKGROUND_SETTINGS, + DEFAULT_GLASS_MATERIAL_SETTINGS, + DEFAULT_SHAPE_HALFTONE_SETTINGS, + DEFAULT_SOLID_ANIMATION_SETTINGS, + DEFAULT_SOLID_BACKGROUND_SETTINGS, + DEFAULT_SOLID_MATERIAL_SETTINGS, + type HalftoneMaterialSurface, + type HalftoneStudioSettings, +} from '@/app/halftone/_lib/state'; import { styled } from '@linaria/react'; import { useEffect, useRef, useState } from 'react'; import * as THREE from 'three'; @@ -17,6 +29,7 @@ const MIN_FOOTPRINT_SCALE = 0.001; type HalftoneRotateAxis = 'x' | 'y' | 'z' | 'xy' | '-x' | '-y' | '-z' | '-xy'; type HalftoneRotatePreset = 'axis' | 'lissajous' | 'orbit' | 'tumble'; +type HelpedHalftoneRenderer = 'legacy' | 'studio'; type ViewRect = { x: number; @@ -47,13 +60,20 @@ export type HelpedHalftoneSettings = { material: { roughness: number; metalness: number; + surface?: HalftoneMaterialSurface; + color?: string; + thickness?: number; + refraction?: number; + environmentPower?: number; }; halftone: { enabled: boolean; scale: number; power: number; width: number; + imageContrast?: number; dashColor: string; + hoverDashColor?: string; }; animation: { autoRotateEnabled: boolean; @@ -91,6 +111,10 @@ export type HelpedHalftoneSettings = { springDamping: number; springReturnEnabled: boolean; springStrength: number; + hoverHalftoneEnabled?: boolean; + hoverHalftonePowerShift?: number; + hoverHalftoneRadius?: number; + hoverHalftoneWidthShift?: number; hoverLightIntensity: number; hoverLightRadius: number; dragFlowDecay: number; @@ -533,10 +557,7 @@ function getFootprintScaleFromRects( return 1; } - return Math.max( - Math.sqrt(currentArea / referenceArea), - MIN_FOOTPRINT_SCALE, - ); + return Math.max(Math.sqrt(currentArea / referenceArea), MIN_FOOTPRINT_SCALE); } function projectBox3ToViewport({ @@ -552,11 +573,7 @@ function projectBox3ToViewport({ viewportHeight: number; viewportWidth: number; }) { - if ( - localBounds.isEmpty() || - viewportWidth <= 0 || - viewportHeight <= 0 - ) { + if (localBounds.isEmpty() || viewportWidth <= 0 || viewportHeight <= 0) { return null; } @@ -728,6 +745,57 @@ const StyledVisualMount = styled.div` width: 100%; `; +const NOOP = () => {}; + +function getStudioMaterialSurface( + settings: HelpedHalftoneSettings, +): HalftoneMaterialSurface { + return settings.material.surface === 'glass' ? 'glass' : 'solid'; +} + +function createStudioSettings( + settings: HelpedHalftoneSettings, +): HalftoneStudioSettings { + const surface = getStudioMaterialSurface(settings); + const defaultMaterial = + surface === 'glass' + ? DEFAULT_GLASS_MATERIAL_SETTINGS + : DEFAULT_SOLID_MATERIAL_SETTINGS; + const defaultAnimation = + surface === 'glass' + ? DEFAULT_GLASS_ANIMATION_SETTINGS + : DEFAULT_SOLID_ANIMATION_SETTINGS; + const defaultBackground = + surface === 'glass' + ? DEFAULT_GLASS_BACKGROUND_SETTINGS + : DEFAULT_SOLID_BACKGROUND_SETTINGS; + + return { + sourceMode: 'shape', + shapeKey: 'helped', + lighting: { ...settings.lighting }, + material: { + ...defaultMaterial, + ...settings.material, + surface, + }, + halftone: { + ...DEFAULT_SHAPE_HALFTONE_SETTINGS, + ...settings.halftone, + hoverDashColor: + settings.halftone.hoverDashColor ?? settings.halftone.dashColor, + imageContrast: + settings.halftone.imageContrast ?? + DEFAULT_SHAPE_HALFTONE_SETTINGS.imageContrast, + }, + background: defaultBackground, + animation: { + ...defaultAnimation, + ...settings.animation, + }, + }; +} + type HelpedHalftoneCanvasProps = { geometry: THREE.BufferGeometry; initialPose: HelpedHalftonePose; @@ -770,7 +838,9 @@ function HelpedHalftoneCanvas({ renderer.setSize(getVirtualWidth(), getVirtualHeight(), false); const canvas = renderer.domElement; - canvas.style.cursor = settings.animation.followDragEnabled ? 'grab' : 'default'; + canvas.style.cursor = settings.animation.followDragEnabled + ? 'grab' + : 'default'; canvas.style.display = 'block'; canvas.style.height = '100%'; canvas.style.touchAction = 'none'; @@ -855,7 +925,9 @@ function HelpedHalftoneCanvas({ dashColor: { value: new THREE.Color(settings.halftone.dashColor) }, time: { value: 0 }, waveAmount: { - value: settings.animation.waveEnabled ? settings.animation.waveAmount : 0, + value: settings.animation.waveEnabled + ? settings.animation.waveAmount + : 0, }, waveSpeed: { value: settings.animation.waveSpeed }, footprintScale: { value: 1.0 }, @@ -1073,8 +1145,7 @@ function HelpedHalftoneCanvas({ const floatPhase = elapsedTime * settings.animation.floatSpeed; const driftAmount = (settings.animation.driftAmount * Math.PI) / 180; - meshOffsetY += - Math.sin(floatPhase) * settings.animation.floatAmplitude; + meshOffsetY += Math.sin(floatPhase) * settings.animation.floatAmplitude; baseRotationX += Math.sin(floatPhase * 0.72) * driftAmount * 0.45; baseRotationZ += Math.cos(floatPhase * 0.93) * driftAmount * 0.3; } @@ -1089,13 +1160,15 @@ function HelpedHalftoneCanvas({ if (rotateEnabled) { interaction.rotateElapsed += delta; const rotateProgress = settings.animation.rotatePingPong - ? Math.sin(interaction.rotateElapsed * settings.animation.rotateSpeed) * - Math.PI + ? Math.sin( + interaction.rotateElapsed * settings.animation.rotateSpeed, + ) * Math.PI : interaction.rotateElapsed * settings.animation.rotateSpeed; if (settings.animation.rotatePreset === 'axis') { - const axisDirection = - settings.animation.rotateAxis.startsWith('-') ? -1 : 1; + const axisDirection = settings.animation.rotateAxis.startsWith('-') + ? -1 + : 1; const axisProgress = rotateProgress * axisDirection; if ( @@ -1256,8 +1329,7 @@ function HelpedHalftoneCanvas({ const orbitPitch = centeredY * cameraRange * 0.7; const horizontalRadius = Math.cos(orbitPitch) * baseCameraDistance; const targetCameraX = Math.sin(orbitYaw) * horizontalRadius; - const targetCameraY = - Math.sin(orbitPitch) * baseCameraDistance * 0.85; + const targetCameraY = Math.sin(orbitPitch) * baseCameraDistance * 0.85; const targetCameraZ = Math.cos(orbitYaw) * horizontalRadius; camera.position.x += (targetCameraX - camera.position.x) * cameraEase; @@ -1266,8 +1338,7 @@ function HelpedHalftoneCanvas({ } else { camera.position.x += (0 - camera.position.x) * 0.12; camera.position.y += (0 - camera.position.y) * 0.12; - camera.position.z += - (baseCameraDistance - camera.position.z) * 0.12; + camera.position.z += (baseCameraDistance - camera.position.z) * 0.12; } lookAtTarget.set(0, meshOffsetY * 0.2, 0); @@ -1329,11 +1400,37 @@ function HelpedHalftoneCanvas({ return ; } +function StudioHelpedHalftoneCanvas({ + geometry, + initialPose, + previewDistance, + settings, +}: HelpedHalftoneCanvasProps) { + useEffect(() => { + return () => { + geometry.dispose(); + }; + }, [geometry]); + + return ( + + ); +} + type HelpedHalftoneModelProps = { initialPose: HelpedHalftonePose; label: string; modelUrl: string; previewDistance: number; + renderer?: HelpedHalftoneRenderer; settings: HelpedHalftoneSettings; }; @@ -1342,6 +1439,7 @@ export function HelpedHalftoneModel({ label, modelUrl, previewDistance, + renderer = 'legacy', settings, }: HelpedHalftoneModelProps) { const [geometry, setGeometry] = useState(null); @@ -1371,6 +1469,17 @@ export function HelpedHalftoneModel({ return ; } + if (renderer === 'studio') { + return ( + + ); + } + return ( ); diff --git a/packages/twenty-website-new/src/illustrations/Helped/Spaceship.tsx b/packages/twenty-website-new/src/illustrations/Helped/Spaceship.tsx index 90345fc30a..41392793cd 100644 --- a/packages/twenty-website-new/src/illustrations/Helped/Spaceship.tsx +++ b/packages/twenty-website-new/src/illustrations/Helped/Spaceship.tsx @@ -11,26 +11,33 @@ const SPACESHIP_PREVIEW_DISTANCE = 3.5; const SPACESHIP_SETTINGS: HelpedHalftoneSettings = { lighting: { - intensity: 1, + intensity: 3, fillIntensity: 0, ambientIntensity: 0, - angleDegrees: 103, - height: -3.6, + angleDegrees: 47, + height: 1.4, }, material: { - roughness: 0.32, - metalness: 0.98, + surface: 'glass', + color: '#7d7d7d', + roughness: 0.26, + metalness: 0.15, + thickness: 15.58, + refraction: 3, + environmentPower: 5, }, halftone: { enabled: true, - scale: 14.97, - power: -0.25, - width: 1.4, + scale: 16.64, + power: -1.24, + width: 0.5, + imageContrast: 1, dashColor: '#89FC9A', + hoverDashColor: '#89FC9A', }, animation: { autoRotateEnabled: false, - breatheEnabled: false, + breatheEnabled: true, cameraParallaxEnabled: false, followHoverEnabled: false, followDragEnabled: true, @@ -39,31 +46,35 @@ const SPACESHIP_SETTINGS: HelpedHalftoneSettings = { dragFlowEnabled: false, lightSweepEnabled: false, rotateEnabled: false, - autoSpeed: 0.1, - autoWobble: 0, - breatheAmount: 0.04, - breatheSpeed: 0.8, + autoSpeed: 0.15, + autoWobble: 0.3, + breatheAmount: 0.015, + breatheSpeed: 0.25, cameraParallaxAmount: 0.3, cameraParallaxEase: 0.08, driftAmount: 8, - hoverRange: 24, - hoverEase: 0.16, + hoverRange: 25, + hoverEase: 0.08, hoverReturn: true, dragSens: 0.008, dragFriction: 0.08, dragMomentum: true, - rotateAxis: '-xy', + rotateAxis: 'y', rotatePreset: 'axis', rotateSpeed: 0.1, rotatePingPong: false, floatAmplitude: 0.16, floatSpeed: 0.8, - lightSweepHeightRange: 0.5, + lightSweepHeightRange: 0, lightSweepRange: 28, - lightSweepSpeed: 0.2, - springDamping: 0.4, + lightSweepSpeed: 0.25, + springDamping: 0.5, springReturnEnabled: true, - springStrength: 0.06, + springStrength: 0.1, + hoverHalftoneEnabled: false, + hoverHalftonePowerShift: 0.42, + hoverHalftoneRadius: 0.2, + hoverHalftoneWidthShift: -0.18, hoverLightIntensity: 0.8, hoverLightRadius: 0.2, dragFlowDecay: 0.08, @@ -96,6 +107,7 @@ export function Spaceship() { label="spaceship.glb" modelUrl={GLB_URL} previewDistance={SPACESHIP_PREVIEW_DISTANCE} + renderer="studio" settings={SPACESHIP_SETTINGS} /> ); diff --git a/packages/twenty-website-new/src/illustrations/Helped/Target.tsx b/packages/twenty-website-new/src/illustrations/Helped/Target.tsx index 9c815f0779..46a8b5663e 100644 --- a/packages/twenty-website-new/src/illustrations/Helped/Target.tsx +++ b/packages/twenty-website-new/src/illustrations/Helped/Target.tsx @@ -11,26 +11,33 @@ const TARGET_PREVIEW_DISTANCE = 4; const TARGET_SETTINGS: HelpedHalftoneSettings = { lighting: { - intensity: 1, + intensity: 3, fillIntensity: 0, ambientIntensity: 0, - angleDegrees: 103, - height: -3.6, + angleDegrees: 47, + height: 1.4, }, material: { - roughness: 0.32, - metalness: 0.98, + surface: 'glass', + color: '#7d7d7d', + roughness: 0.26, + metalness: 0.15, + thickness: 15.58, + refraction: 3, + environmentPower: 5, }, halftone: { enabled: true, - scale: 14.97, - power: -0.25, - width: 1.4, + scale: 25.64, + power: -1.24, + width: 0.5, + imageContrast: 1, dashColor: '#ED87FC', + hoverDashColor: '#ED87FC', }, animation: { autoRotateEnabled: false, - breatheEnabled: false, + breatheEnabled: true, cameraParallaxEnabled: false, followHoverEnabled: false, followDragEnabled: true, @@ -39,31 +46,35 @@ const TARGET_SETTINGS: HelpedHalftoneSettings = { dragFlowEnabled: false, lightSweepEnabled: false, rotateEnabled: false, - autoSpeed: 0.1, - autoWobble: 0, - breatheAmount: 0.04, - breatheSpeed: 0.8, + autoSpeed: 0.15, + autoWobble: 0.3, + breatheAmount: 0.015, + breatheSpeed: 0.25, cameraParallaxAmount: 0.3, cameraParallaxEase: 0.08, driftAmount: 8, - hoverRange: 24, - hoverEase: 0.16, + hoverRange: 25, + hoverEase: 0.08, hoverReturn: true, dragSens: 0.008, dragFriction: 0.08, dragMomentum: true, - rotateAxis: '-xy', + rotateAxis: 'y', rotatePreset: 'axis', rotateSpeed: 0.1, rotatePingPong: false, floatAmplitude: 0.16, floatSpeed: 0.8, - lightSweepHeightRange: 0.5, + lightSweepHeightRange: 0, lightSweepRange: 28, - lightSweepSpeed: 0.2, - springDamping: 0.4, + lightSweepSpeed: 0.25, + springDamping: 0.5, springReturnEnabled: true, - springStrength: 0.06, + springStrength: 0.1, + hoverHalftoneEnabled: false, + hoverHalftonePowerShift: 0.42, + hoverHalftoneRadius: 0.2, + hoverHalftoneWidthShift: -0.18, hoverLightIntensity: 0.8, hoverLightRadius: 0.2, dragFlowDecay: 0.08, @@ -96,6 +107,7 @@ export function Target() { label="target.glb" modelUrl={GLB_URL} previewDistance={TARGET_PREVIEW_DISTANCE} + renderer="studio" settings={TARGET_SETTINGS} /> ); diff --git a/packages/twenty-website-new/src/lib/shared-asset-paths.ts b/packages/twenty-website-new/src/lib/shared-asset-paths.ts index 7953acf09e..9ac6c0489d 100644 --- a/packages/twenty-website-new/src/lib/shared-asset-paths.ts +++ b/packages/twenty-website-new/src/lib/shared-asset-paths.ts @@ -1,10 +1,14 @@ export const SHARED_COMPANY_LOGO_URLS = { airbnb: '/images/shared/companies/logos/airbnb.png', + anthropic: '/images/shared/companies/logos/anthropic.png', + cursor: '/images/shared/companies/logos/cursor.png', figma: '/images/shared/companies/logos/figma.png', github: '/images/shared/companies/logos/github.png', + linear: '/images/shared/companies/logos/linear.svg', notion: '/images/shared/companies/logos/notion.png', sequoia: '/images/shared/companies/logos/sequoia.png', slack: '/images/shared/companies/logos/slack.png', + twenty: '/images/shared/companies/logos/twenty.svg', } as const; export const SHARED_PEOPLE_AVATAR_URLS = { diff --git a/packages/twenty-website-new/src/sections/HomeStepper/components/Visual/StepperBackgroundHalftone.tsx b/packages/twenty-website-new/src/sections/HomeStepper/components/Visual/StepperBackgroundHalftone.tsx index dcc3111483..d7e7bfeebf 100644 --- a/packages/twenty-website-new/src/sections/HomeStepper/components/Visual/StepperBackgroundHalftone.tsx +++ b/packages/twenty-website-new/src/sections/HomeStepper/components/Visual/StepperBackgroundHalftone.tsx @@ -15,25 +15,24 @@ const HOVER_FADE_OUT = 7; const HALFTONE_SETTINGS = { animation: { - hoverHalftoneEnabled: true, - hoverHalftonePowerShift: 0.62, - hoverHalftoneRadius: 0.6, + hoverHalftoneEnabled: false, + hoverHalftonePowerShift: 0.42, + hoverHalftoneRadius: 0.45, hoverHalftoneWidthShift: -0.18, - hoverLightEnabled: false, - hoverLightIntensity: 0.12, - hoverLightRadius: 0.8, + hoverLightEnabled: true, + hoverLightIntensity: 0.35, + hoverLightRadius: 0.42, waveAmount: 2, waveEnabled: false, waveSpeed: 1, }, halftone: { - dashColor: '#dddddd', - hoverDashColor: '#FFF', - imageContrast: 1.12, - minimumTone: 0.26, - power: 0.18, - scale: 12, - width: 0.72, + dashColor: '#868686', + hoverDashColor: '#F5F5F5', + imageContrast: 1, + power: 0.5, + scale: 8, + width: 0.3, }, }; @@ -63,7 +62,6 @@ const imagePassthroughFragmentShader = /* glsl */ ` vec2 uv = vUv; - // Cover: match the underlying NextImage background crop. if (imageAspect > viewAspect) { float scale = viewAspect / imageAspect; uv.x = (uv.x - 0.5) * scale + 0.5; @@ -95,7 +93,6 @@ const halftoneFragmentShader = /* glsl */ ` uniform float s_4; uniform vec3 dashColor; uniform vec3 hoverDashColor; - uniform float minimumTone; uniform float time; uniform float waveAmount; uniform float waveSpeed; @@ -209,17 +206,15 @@ const halftoneFragmentShader = /* glsl */ ` ); float lightLift = hoverLightStrength * hoverLightMask * mix(0.78, 1.18, motionBias) * 0.22; - float tonalAverage = ( + float bandRadius = clamp( ( sceneSample.r + sceneSample.g + sceneSample.b + localPower * length(vec2(0.5)) ) * - (1.0 / 3.0) - ) + lightLift; - float bandRadius = clamp( - max(tonalAverage, minimumTone), + (1.0 / 3.0) + + lightLift, 0.0, 1.0 ) * 1.86 * 0.5; @@ -391,7 +386,6 @@ async function mountHalftoneCanvas({ logicalResolution: { value: new THREE.Vector2(getVirtualWidth(), getVirtualHeight()), }, - minimumTone: { value: HALFTONE_SETTINGS.halftone.minimumTone }, s_3: { value: HALFTONE_SETTINGS.halftone.power }, s_4: { value: HALFTONE_SETTINGS.halftone.width }, tScene: { value: sceneTarget.texture }, diff --git a/packages/twenty-website-new/src/sections/HomeStepper/components/Visual/Visual.tsx b/packages/twenty-website-new/src/sections/HomeStepper/components/Visual/Visual.tsx index 1f532c9585..c424b29952 100644 --- a/packages/twenty-website-new/src/sections/HomeStepper/components/Visual/Visual.tsx +++ b/packages/twenty-website-new/src/sections/HomeStepper/components/Visual/Visual.tsx @@ -6,7 +6,8 @@ import { StepperVisualFrame } from '../StepperVisualFrame/StepperVisualFrame'; import { StepperBackgroundHalftone } from './StepperBackgroundHalftone'; import { StepperLottie } from './StepperLottie'; -const HOME_STEPPER_BACKGROUND = '/images/home/stepper/gears.jpg'; +const HOME_STEPPER_BACKGROUND = + '/images/home/stepper/download-worker.webp'; const HOME_STEPPER_SHAPE = '/images/home/stepper/background-shape.webp'; type VisualProps = { @@ -16,7 +17,7 @@ type VisualProps = { export function Visual({ scrollProgress }: VisualProps) { return ( diff --git a/packages/twenty-website-new/src/sections/Signoff/components/Root.tsx b/packages/twenty-website-new/src/sections/Signoff/components/Root.tsx index a8beb355f8..c5f4e5c19f 100644 --- a/packages/twenty-website-new/src/sections/Signoff/components/Root.tsx +++ b/packages/twenty-website-new/src/sections/Signoff/components/Root.tsx @@ -86,7 +86,7 @@ export function Root(props: RootProps) { ) : null} {page === Pages.Partner ? ( - + ) : null} {children} diff --git a/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/FamiliarInterfaceGradientBackdrop.tsx b/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/FamiliarInterfaceGradientBackdrop.tsx new file mode 100644 index 0000000000..ab0f9eb41e --- /dev/null +++ b/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/FamiliarInterfaceGradientBackdrop.tsx @@ -0,0 +1,921 @@ +'use client'; + +import { styled } from '@linaria/react'; +import { type RefObject, useEffect, useRef } from 'react'; +import * as THREE from 'three'; + +const DEFAULT_IMAGE_URL = + '/images/home/three-cards-feature/familiar-interface-gradient.webp'; +const VIRTUAL_RENDER_HEIGHT = 800; +const REFERENCE_PREVIEW_DISTANCE = 4; +const PREVIEW_DISTANCE = REFERENCE_PREVIEW_DISTANCE; +const MIN_FOOTPRINT_SCALE = 0.001; +const HALFTONE_SCALE = 18; +const HALFTONE_POWER = -0.3; +const HALFTONE_WIDTH = 0.3; +const IMAGE_CONTRAST = 0.7; +const DASH_COLOR = '#777'; +const HOVER_DASH_COLOR = '#777'; +const HOVER_LIGHT_INTENSITY = 0.85; +const HOVER_LIGHT_RADIUS = 0.6; +const HOVER_HALFTONE_RADIUS = 0.45; +const IMAGE_POINTER_FOLLOW = 0.38; +const IMAGE_POINTER_VELOCITY_DAMPING = 0.82; +const IMAGE_HOVER_FADE_IN = 18; +const IMAGE_HOVER_FADE_OUT = 7; + +export type HalftoneBackdropConfig = { + activeHoverX?: number; + activeHoverY?: number; + dashColor?: string; + halftonePower?: number; + halftoneScale?: number; + halftoneWidth?: number; + hoverDashColor?: string; + hoverHalftoneRadius?: number; + hoverLightIntensity?: number; + hoverLightRadius?: number; + imageContrast?: number; + previewDistance?: number; +}; + +type ResolvedHalftoneBackdropConfig = Required; + +const DEFAULT_HALFTONE_BACKDROP_CONFIG: ResolvedHalftoneBackdropConfig = { + activeHoverX: 0.16, + activeHoverY: 0.46, + dashColor: DASH_COLOR, + halftonePower: HALFTONE_POWER, + halftoneScale: HALFTONE_SCALE, + halftoneWidth: HALFTONE_WIDTH, + hoverDashColor: HOVER_DASH_COLOR, + hoverHalftoneRadius: HOVER_HALFTONE_RADIUS, + hoverLightIntensity: HOVER_LIGHT_INTENSITY, + hoverLightRadius: HOVER_LIGHT_RADIUS, + imageContrast: IMAGE_CONTRAST, + previewDistance: PREVIEW_DISTANCE, +}; + +const passThroughVertexShader = ` + varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = vec4(position, 1.0); + } +`; + +const blurFragmentShader = ` + precision highp float; + + uniform sampler2D tInput; + uniform vec2 dir; + uniform vec2 res; + + varying vec2 vUv; + + void main() { + vec4 sum = vec4(0.0); + vec2 px = dir / res; + + float w[5]; + w[0] = 0.227027; + w[1] = 0.1945946; + w[2] = 0.1216216; + w[3] = 0.054054; + w[4] = 0.016216; + + sum += texture2D(tInput, vUv) * w[0]; + + for (int i = 1; i < 5; i++) { + float fi = float(i) * 3.0; + sum += texture2D(tInput, vUv + px * fi) * w[i]; + sum += texture2D(tInput, vUv - px * fi) * w[i]; + } + + gl_FragColor = sum; + } +`; + +const halftoneFragmentShader = ` + precision highp float; + + uniform sampler2D tScene; + uniform vec2 effectResolution; + uniform vec2 logicalResolution; + uniform float tile; + uniform float s_3; + uniform float s_4; + uniform vec3 dashColor; + uniform vec3 hoverDashColor; + uniform float footprintScale; + uniform vec2 interactionUv; + uniform vec2 interactionVelocity; + uniform float hoverLightStrength; + uniform float hoverLightRadius; + + varying vec2 vUv; + + float distSegment(in vec2 p, in vec2 a, in vec2 b) { + vec2 pa = p - a; + vec2 ba = b - a; + float denom = max(dot(ba, ba), 0.000001); + float h = clamp(dot(pa, ba) / denom, 0.0, 1.0); + return length(pa - ba * h); + } + + float lineSimpleEt(in vec2 p, in float r, in float thickness) { + vec2 a = vec2(0.5) + vec2(-r, 0.0); + vec2 b = vec2(0.5) + vec2(r, 0.0); + float distToSegment = distSegment(p, a, b); + float halfThickness = thickness * r; + return distToSegment - halfThickness; + } + + void main() { + vec2 fragCoord = + (gl_FragCoord.xy / max(effectResolution, vec2(1.0))) * logicalResolution; + float halftoneSize = max(tile * max(footprintScale, 0.001), 1.0); + vec2 pointerPx = interactionUv * logicalResolution; + vec2 fragDelta = fragCoord - pointerPx; + float fragDist = length(fragDelta); + vec2 radialDir = fragDist > 0.001 ? fragDelta / fragDist : vec2(0.0, 1.0); + float velocityMagnitude = length(interactionVelocity); + vec2 motionDir = velocityMagnitude > 0.001 + ? interactionVelocity / velocityMagnitude + : vec2(0.0, 0.0); + float motionBias = velocityMagnitude > 0.001 + ? dot(-radialDir, motionDir) * 0.5 + 0.5 + : 0.5; + + float hoverLightMask = 0.0; + if (hoverLightStrength > 0.0) { + float lightRadiusPx = hoverLightRadius * logicalResolution.y; + hoverLightMask = smoothstep(lightRadiusPx, 0.0, fragDist); + } + + vec2 effectCoord = fragCoord; + + vec2 cellIndex = floor(effectCoord / halftoneSize); + vec2 sampleUv = clamp( + (cellIndex + 0.5) * halftoneSize / logicalResolution, + vec2(0.0), + vec2(1.0) + ); + vec2 cellUv = fract(effectCoord / halftoneSize); + + vec4 sceneSample = texture2D(tScene, sampleUv); + float mask = smoothstep(0.02, 0.08, sceneSample.a); + float lightLift = + hoverLightStrength * hoverLightMask * mix(0.78, 1.18, motionBias) * 0.22; + float bandRadius = clamp( + ( + ( + sceneSample.r + + sceneSample.g + + sceneSample.b + + s_3 * length(vec2(0.5)) + ) * + (1.0 / 3.0) + ) + lightLift, + 0.0, + 1.0 + ) * 1.86 * 0.5; + + float alpha = 0.0; + if (bandRadius > 0.0001) { + float signedDistance = lineSimpleEt(cellUv, bandRadius, s_4); + float edge = 0.02; + alpha = (1.0 - smoothstep(0.0, edge, signedDistance)) * mask; + } + + float hoverStrength = clamp(hoverLightMask * hoverLightStrength, 0.0, 1.0); + vec3 activeDashColor = mix(dashColor, hoverDashColor, hoverStrength); + vec3 color = activeDashColor * alpha; + gl_FragColor = vec4(color, alpha); + + #include + #include + } +`; + +const imagePassthroughFragmentShader = ` + precision highp float; + + uniform sampler2D tImage; + uniform vec2 imageSize; + uniform vec2 viewportSize; + uniform float zoom; + uniform float contrast; + + varying vec2 vUv; + + void main() { + float imageAspect = imageSize.x / imageSize.y; + float viewAspect = viewportSize.x / viewportSize.y; + + vec2 uv = vUv; + + if (imageAspect > viewAspect) { + float scale = viewAspect / imageAspect; + uv.x = (uv.x - 0.5) * scale + 0.5; + } else { + float scale = imageAspect / viewAspect; + uv.y = (uv.y - 0.5) * scale + 0.5; + } + + uv = (uv - 0.5) / zoom + 0.5; + uv.y = 1.0 - uv.y; + + float inBounds = step(0.0, uv.x) * step(uv.x, 1.0) + * step(0.0, uv.y) * step(uv.y, 1.0); + + vec4 color = texture2D(tImage, clamp(uv, 0.0, 1.0)); + vec3 contrastColor = clamp((color.rgb - 0.5) * contrast + 0.5, 0.0, 1.0); + + gl_FragColor = vec4(contrastColor, inBounds); + } +`; + +type Rect = { + x: number; + y: number; + width: number; + height: number; +}; + +type InteractionState = { + hoverStrength: number; + mouseX: number; + mouseY: number; + pointerInside: boolean; + pointerVelocityX: number; + pointerVelocityY: number; + smoothedMouseX: number; + smoothedMouseY: number; +}; + +function clampRectToViewport( + rect: Rect, + viewportWidth: number, + viewportHeight: number, +) { + const minX = Math.max(rect.x, 0); + const minY = Math.max(rect.y, 0); + const maxX = Math.min(rect.x + rect.width, viewportWidth); + const maxY = Math.min(rect.y + rect.height, viewportHeight); + + if (maxX <= minX || maxY <= minY) { + return null; + } + + return { + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY, + }; +} + +function getRectArea(rect: Rect | null) { + if (!rect) { + return 0; + } + + return Math.max(rect.width, 0) * Math.max(rect.height, 0); +} + +function getImagePreviewZoom(previewDistance: number) { + return REFERENCE_PREVIEW_DISTANCE / Math.max(previewDistance, 0.001); +} + +function getContainedImageRect({ + imageHeight, + imageWidth, + viewportHeight, + viewportWidth, + zoom, +}: { + imageHeight: number; + imageWidth: number; + viewportHeight: number; + viewportWidth: number; + zoom: number; +}) { + if ( + imageWidth <= 0 || + imageHeight <= 0 || + viewportWidth <= 0 || + viewportHeight <= 0 + ) { + return null; + } + + const imageAspect = imageWidth / imageHeight; + const viewAspect = viewportWidth / viewportHeight; + + let fittedWidth = viewportWidth; + let fittedHeight = viewportHeight; + + if (imageAspect > viewAspect) { + fittedHeight = viewportWidth / imageAspect; + } else { + fittedWidth = viewportHeight * imageAspect; + } + + const scaledWidth = fittedWidth * zoom; + const scaledHeight = fittedHeight * zoom; + + return clampRectToViewport( + { + x: (viewportWidth - scaledWidth) * 0.5, + y: (viewportHeight - scaledHeight) * 0.5, + width: scaledWidth, + height: scaledHeight, + }, + viewportWidth, + viewportHeight, + ); +} + +function getFootprintScaleFromRects( + currentRect: Rect | null, + referenceRect: Rect | null, +) { + const currentArea = getRectArea(currentRect); + const referenceArea = getRectArea(referenceRect); + + if (currentArea <= 0 || referenceArea <= 0) { + return 1; + } + + return Math.max(Math.sqrt(currentArea / referenceArea), MIN_FOOTPRINT_SCALE); +} + +function getImageFootprintScale({ + imageHeight, + imageWidth, + previewDistance, + viewportHeight, + viewportWidth, +}: { + imageHeight: number; + imageWidth: number; + previewDistance: number; + viewportHeight: number; + viewportWidth: number; +}) { + const currentRect = getContainedImageRect({ + imageHeight, + imageWidth, + viewportHeight, + viewportWidth, + zoom: getImagePreviewZoom(previewDistance), + }); + const referenceRect = getContainedImageRect({ + imageHeight, + imageWidth, + viewportHeight, + viewportWidth, + zoom: 1, + }); + + return getFootprintScaleFromRects(currentRect, referenceRect); +} + +function createRenderTarget(width: number, height: number) { + return new THREE.WebGLRenderTarget(width, height, { + minFilter: THREE.LinearFilter, + magFilter: THREE.LinearFilter, + format: THREE.RGBAFormat, + }); +} + +function createInteractionState(): InteractionState { + return { + hoverStrength: 0, + mouseX: 0.5, + mouseY: 0.5, + pointerInside: false, + pointerVelocityX: 0, + pointerVelocityY: 0, + smoothedMouseX: 0.5, + smoothedMouseY: 0.5, + }; +} + +function loadImage(imageUrl: string) { + return new Promise((resolve, reject) => { + const image = new Image(); + image.onload = () => resolve(image); + image.onerror = () => reject(new Error(`Failed to load image: ${imageUrl}`)); + image.src = imageUrl; + }); +} + +async function mountFamiliarInterfaceGradient({ + container, + config, + imageUrl, + isExternallyActive, + pointerTarget, +}: { + container: HTMLDivElement; + config: ResolvedHalftoneBackdropConfig; + imageUrl: string; + isExternallyActive: () => boolean; + pointerTarget?: HTMLElement | null; +}) { + const HOVER_STRENGTH_STOP_EPSILON = 0.001; + const POINTER_POSITION_STOP_EPSILON = 0.001; + const POINTER_VELOCITY_STOP_EPSILON = 0.0001; + const image = await loadImage(imageUrl); + const getWidth = () => Math.max(container.clientWidth, 1); + const getHeight = () => Math.max(container.clientHeight, 1); + const getVirtualHeight = () => Math.max(VIRTUAL_RENDER_HEIGHT, getHeight()); + const getVirtualWidth = () => + Math.max( + Math.round(getVirtualHeight() * (getWidth() / Math.max(getHeight(), 1))), + 1, + ); + + const renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true }); + renderer.outputColorSpace = THREE.SRGBColorSpace; + renderer.setPixelRatio(1); + renderer.setClearColor(0x000000, 0); + renderer.setSize(getVirtualWidth(), getVirtualHeight(), false); + + const canvas = renderer.domElement; + canvas.style.cursor = 'default'; + canvas.style.display = 'block'; + canvas.style.height = '100%'; + canvas.style.touchAction = 'none'; + canvas.style.width = '100%'; + container.appendChild(canvas); + const trackingElement = pointerTarget ?? canvas; + + const imageTexture = new THREE.Texture(image); + imageTexture.colorSpace = THREE.SRGBColorSpace; + imageTexture.needsUpdate = true; + + const sceneTarget = createRenderTarget(getVirtualWidth(), getVirtualHeight()); + const blurTargetA = createRenderTarget(getVirtualWidth(), getVirtualHeight()); + const blurTargetB = createRenderTarget(getVirtualWidth(), getVirtualHeight()); + const fullScreenGeometry = new THREE.PlaneGeometry(2, 2); + const orthographicCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); + + const imageMaterial = new THREE.ShaderMaterial({ + uniforms: { + contrast: { value: config.imageContrast }, + imageSize: { value: new THREE.Vector2(image.width, image.height) }, + tImage: { value: imageTexture }, + viewportSize: { + value: new THREE.Vector2(getVirtualWidth(), getVirtualHeight()), + }, + zoom: { value: getImagePreviewZoom(config.previewDistance) }, + }, + fragmentShader: imagePassthroughFragmentShader, + vertexShader: passThroughVertexShader, + }); + + const blurHorizontalMaterial = new THREE.ShaderMaterial({ + uniforms: { + dir: { value: new THREE.Vector2(1, 0) }, + res: { value: new THREE.Vector2(getVirtualWidth(), getVirtualHeight()) }, + tInput: { value: null }, + }, + fragmentShader: blurFragmentShader, + vertexShader: passThroughVertexShader, + }); + + const blurVerticalMaterial = new THREE.ShaderMaterial({ + uniforms: { + dir: { value: new THREE.Vector2(0, 1) }, + res: { value: new THREE.Vector2(getVirtualWidth(), getVirtualHeight()) }, + tInput: { value: null }, + }, + fragmentShader: blurFragmentShader, + vertexShader: passThroughVertexShader, + }); + + const halftoneMaterial = new THREE.ShaderMaterial({ + fragmentShader: halftoneFragmentShader, + transparent: true, + uniforms: { + dashColor: { value: new THREE.Color(config.dashColor) }, + effectResolution: { + value: new THREE.Vector2(getVirtualWidth(), getVirtualHeight()), + }, + footprintScale: { value: 1 }, + hoverDashColor: { value: new THREE.Color(config.hoverDashColor) }, + hoverLightRadius: { value: config.hoverLightRadius }, + hoverLightStrength: { value: 0 }, + interactionUv: { value: new THREE.Vector2(0.5, 0.5) }, + interactionVelocity: { value: new THREE.Vector2(0, 0) }, + logicalResolution: { + value: new THREE.Vector2(getVirtualWidth(), getVirtualHeight()), + }, + s_3: { value: config.halftonePower }, + s_4: { value: config.halftoneWidth }, + tScene: { value: sceneTarget.texture }, + tile: { value: config.halftoneScale }, + }, + vertexShader: passThroughVertexShader, + }); + + const fullScreenMesh = new THREE.Mesh(fullScreenGeometry, imageMaterial); + const blurHorizontalMesh = new THREE.Mesh( + fullScreenGeometry, + blurHorizontalMaterial, + ); + const blurVerticalMesh = new THREE.Mesh( + fullScreenGeometry, + blurVerticalMaterial, + ); + const postProcessMesh = new THREE.Mesh(fullScreenGeometry, halftoneMaterial); + + const imageScene = new THREE.Scene(); + imageScene.add(fullScreenMesh); + + const blurHorizontalScene = new THREE.Scene(); + blurHorizontalScene.add(blurHorizontalMesh); + + const blurVerticalScene = new THREE.Scene(); + blurVerticalScene.add(blurVerticalMesh); + + const postScene = new THREE.Scene(); + postScene.add(postProcessMesh); + + const updateViewportUniforms = ( + logicalWidth: number, + logicalHeight: number, + effectWidth: number, + effectHeight: number, + ) => { + blurHorizontalMaterial.uniforms.res.value.set(effectWidth, effectHeight); + blurVerticalMaterial.uniforms.res.value.set(effectWidth, effectHeight); + halftoneMaterial.uniforms.effectResolution.value.set( + effectWidth, + effectHeight, + ); + halftoneMaterial.uniforms.logicalResolution.value.set( + logicalWidth, + logicalHeight, + ); + imageMaterial.uniforms.viewportSize.value.set(logicalWidth, logicalHeight); + }; + + const getHalftoneScale = () => + getImageFootprintScale({ + imageHeight: image.height, + imageWidth: image.width, + previewDistance: config.previewDistance, + viewportHeight: getVirtualHeight(), + viewportWidth: getVirtualWidth(), + }); + + const interaction = createInteractionState(); + let animationFrameId: number | null = null; + let lastFrameTime: number | null = null; + + const renderBackdrop = (deltaSeconds: number) => { + const externalActive = isExternallyActive(); + const isHoverActive = externalActive || interaction.pointerInside; + const targetHoverStrength = isHoverActive ? 1 : 0; + const hoverEasing = + 1 - + Math.exp( + -deltaSeconds * + (isHoverActive ? IMAGE_HOVER_FADE_IN : IMAGE_HOVER_FADE_OUT), + ); + + interaction.hoverStrength += + (targetHoverStrength - interaction.hoverStrength) * hoverEasing; + + if ( + Math.abs(targetHoverStrength - interaction.hoverStrength) <= + HOVER_STRENGTH_STOP_EPSILON + ) { + interaction.hoverStrength = targetHoverStrength; + } + + const targetMouseX = + externalActive && !interaction.pointerInside + ? config.activeHoverX + : interaction.mouseX; + const targetMouseY = + externalActive && !interaction.pointerInside + ? config.activeHoverY + : interaction.mouseY; + + interaction.smoothedMouseX += + (targetMouseX - interaction.smoothedMouseX) * IMAGE_POINTER_FOLLOW; + interaction.smoothedMouseY += + (targetMouseY - interaction.smoothedMouseY) * IMAGE_POINTER_FOLLOW; + + if ( + Math.abs(targetMouseX - interaction.smoothedMouseX) <= + POINTER_POSITION_STOP_EPSILON + ) { + interaction.smoothedMouseX = targetMouseX; + } + + if ( + Math.abs(targetMouseY - interaction.smoothedMouseY) <= + POINTER_POSITION_STOP_EPSILON + ) { + interaction.smoothedMouseY = targetMouseY; + } + + interaction.pointerVelocityX *= IMAGE_POINTER_VELOCITY_DAMPING; + interaction.pointerVelocityY *= IMAGE_POINTER_VELOCITY_DAMPING; + + if ( + Math.abs(interaction.pointerVelocityX) <= POINTER_VELOCITY_STOP_EPSILON + ) { + interaction.pointerVelocityX = 0; + } + + if ( + Math.abs(interaction.pointerVelocityY) <= POINTER_VELOCITY_STOP_EPSILON + ) { + interaction.pointerVelocityY = 0; + } + + halftoneMaterial.uniforms.interactionUv.value.set( + interaction.smoothedMouseX, + 1 - interaction.smoothedMouseY, + ); + halftoneMaterial.uniforms.interactionVelocity.value.set( + interaction.pointerVelocityX * getVirtualWidth(), + -interaction.pointerVelocityY * getVirtualHeight(), + ); + halftoneMaterial.uniforms.hoverLightStrength.value = + config.hoverLightIntensity * interaction.hoverStrength; + halftoneMaterial.uniforms.hoverLightRadius.value = + config.hoverHalftoneRadius + config.hoverLightRadius * 0.5; + halftoneMaterial.uniforms.footprintScale.value = getHalftoneScale(); + imageMaterial.uniforms.zoom.value = getImagePreviewZoom( + config.previewDistance, + ); + + renderer.setRenderTarget(sceneTarget); + renderer.render(imageScene, orthographicCamera); + + blurHorizontalMaterial.uniforms.tInput.value = sceneTarget.texture; + renderer.setRenderTarget(blurTargetA); + renderer.render(blurHorizontalScene, orthographicCamera); + + blurVerticalMaterial.uniforms.tInput.value = blurTargetA.texture; + renderer.setRenderTarget(blurTargetB); + renderer.render(blurVerticalScene, orthographicCamera); + + blurHorizontalMaterial.uniforms.tInput.value = blurTargetB.texture; + renderer.setRenderTarget(blurTargetA); + renderer.render(blurHorizontalScene, orthographicCamera); + + blurVerticalMaterial.uniforms.tInput.value = blurTargetA.texture; + renderer.setRenderTarget(blurTargetB); + renderer.render(blurVerticalScene, orthographicCamera); + + renderer.setRenderTarget(null); + renderer.clear(); + renderer.render(postScene, orthographicCamera); + + return ( + isHoverActive || + interaction.hoverStrength !== targetHoverStrength || + interaction.smoothedMouseX !== targetMouseX || + interaction.smoothedMouseY !== targetMouseY || + interaction.pointerVelocityX !== 0 || + interaction.pointerVelocityY !== 0 + ); + }; + + const runFrame = (timestamp: number) => { + animationFrameId = null; + + const deltaSeconds = + lastFrameTime === null + ? 1 / 60 + : Math.min((timestamp - lastFrameTime) / 1000, 0.1); + + lastFrameTime = timestamp; + + if (renderBackdrop(deltaSeconds)) { + animationFrameId = window.requestAnimationFrame(runFrame); + return; + } + + lastFrameTime = null; + }; + + const ensureAnimationLoop = () => { + if (animationFrameId !== null) { + return; + } + + animationFrameId = window.requestAnimationFrame(runFrame); + }; + + const syncSize = () => { + const virtualWidth = getVirtualWidth(); + const virtualHeight = getVirtualHeight(); + + renderer.setSize(virtualWidth, virtualHeight, false); + sceneTarget.setSize(virtualWidth, virtualHeight); + blurTargetA.setSize(virtualWidth, virtualHeight); + blurTargetB.setSize(virtualWidth, virtualHeight); + updateViewportUniforms( + virtualWidth, + virtualHeight, + virtualWidth, + virtualHeight, + ); + + if (animationFrameId === null) { + renderBackdrop(0); + } + }; + + const resizeObserver = new ResizeObserver(syncSize); + resizeObserver.observe(container); + syncSize(); + + const updatePointerPosition = ( + event: PointerEvent, + options?: { resetVelocity?: boolean }, + ) => { + const rect = trackingElement.getBoundingClientRect(); + const width = Math.max(rect.width, 1); + const height = Math.max(rect.height, 1); + const nextMouseX = THREE.MathUtils.clamp( + (event.clientX - rect.left) / width, + 0, + 1, + ); + const nextMouseY = THREE.MathUtils.clamp( + (event.clientY - rect.top) / height, + 0, + 1, + ); + const deltaX = nextMouseX - interaction.mouseX; + const deltaY = nextMouseY - interaction.mouseY; + + interaction.mouseX = nextMouseX; + interaction.mouseY = nextMouseY; + interaction.pointerInside = + event.clientX >= rect.left && + event.clientX <= rect.right && + event.clientY >= rect.top && + event.clientY <= rect.bottom; + + if (options?.resetVelocity) { + interaction.pointerVelocityX = 0; + interaction.pointerVelocityY = 0; + interaction.smoothedMouseX = nextMouseX; + interaction.smoothedMouseY = nextMouseY; + return; + } + + interaction.pointerVelocityX = deltaX; + interaction.pointerVelocityY = deltaY; + }; + + const handlePointerMove = (event: PointerEvent) => { + const shouldReset = !interaction.pointerInside; + updatePointerPosition( + event, + shouldReset ? { resetVelocity: true } : undefined, + ); + ensureAnimationLoop(); + }; + + const handlePointerLeave = () => { + interaction.pointerInside = false; + interaction.pointerVelocityX = 0; + interaction.pointerVelocityY = 0; + ensureAnimationLoop(); + }; + + trackingElement.addEventListener('pointermove', handlePointerMove); + trackingElement.addEventListener('pointerleave', handlePointerLeave); + + if (isExternallyActive()) { + ensureAnimationLoop(); + } + + return { + dispose: () => { + if (animationFrameId !== null) { + window.cancelAnimationFrame(animationFrameId); + } + + resizeObserver.disconnect(); + trackingElement.removeEventListener('pointermove', handlePointerMove); + trackingElement.removeEventListener('pointerleave', handlePointerLeave); + blurHorizontalMaterial.dispose(); + blurVerticalMaterial.dispose(); + halftoneMaterial.dispose(); + imageMaterial.dispose(); + imageTexture.dispose(); + fullScreenGeometry.dispose(); + sceneTarget.dispose(); + blurTargetA.dispose(); + blurTargetB.dispose(); + renderer.dispose(); + + if (canvas.parentNode === container) { + container.removeChild(canvas); + } + }, + wake: ensureAnimationLoop, + }; +} + +const VisualMount = styled.div` + background: transparent; + display: block; + height: 100%; + min-width: 0; + width: 100%; +`; + +type FamiliarInterfaceGradientBackdropProps = { + active?: boolean; + config?: HalftoneBackdropConfig; + imageUrl?: string; + pointerTargetRef?: RefObject; +}; + +export function FamiliarInterfaceGradientBackdrop({ + active = false, + config, + imageUrl = DEFAULT_IMAGE_URL, + pointerTargetRef, +}: FamiliarInterfaceGradientBackdropProps) { + const mountRef = useRef(null); + const activeRef = useRef(active); + const wakeRef = useRef(null); + const resolvedConfig = { + ...DEFAULT_HALFTONE_BACKDROP_CONFIG, + ...config, + }; + + activeRef.current = active; + + useEffect(() => { + const container = mountRef.current; + + if (!container) { + return; + } + + let dispose: VoidFunction | undefined; + let cancelled = false; + + void mountFamiliarInterfaceGradient({ + config: resolvedConfig, + container, + imageUrl, + isExternallyActive: () => activeRef.current, + pointerTarget: pointerTargetRef?.current, + }) + .then((mountedGradient) => { + if (cancelled) { + mountedGradient.dispose(); + return; + } + + wakeRef.current = mountedGradient.wake; + dispose = mountedGradient.dispose; + }) + .catch((error) => { + console.error(error); + }); + + return () => { + cancelled = true; + wakeRef.current = null; + dispose?.(); + }; + }, [ + imageUrl, + resolvedConfig.dashColor, + resolvedConfig.halftonePower, + resolvedConfig.halftoneScale, + resolvedConfig.halftoneWidth, + resolvedConfig.hoverDashColor, + resolvedConfig.hoverHalftoneRadius, + resolvedConfig.hoverLightIntensity, + resolvedConfig.hoverLightRadius, + resolvedConfig.imageContrast, + resolvedConfig.previewDistance, + ]); + + useEffect(() => { + wakeRef.current?.(); + }, [active]); + + return ; +} diff --git a/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/FamiliarInterfaceVisual.tsx b/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/FamiliarInterfaceVisual.tsx index 47f7a718f9..cd4c3e8827 100644 --- a/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/FamiliarInterfaceVisual.tsx +++ b/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/FamiliarInterfaceVisual.tsx @@ -9,10 +9,13 @@ import { theme } from '@/theme'; import { styled } from '@linaria/react'; import { type PointerEvent as ReactPointerEvent, + type RefObject, + useEffect, useLayoutEffect, useRef, useState, } from 'react'; +import { FamiliarInterfaceGradientBackdrop } from './FamiliarInterfaceGradientBackdrop'; import { IconBuildingSkyscraper, IconCalendarEvent, @@ -40,6 +43,8 @@ const FIGMA_FIELD_STACK_GAP = 1.839; const FIGMA_CARD_RADIUS = 3.677; const FIGMA_CARD_SHADOW = '0px 0px 3.677px rgba(0, 0, 0, 0.08), 0px 1.839px 3.677px rgba(0, 0, 0, 0.04)'; +const FIGMA_COLUMN_GAP = 8; +const FIGMA_COLUMN_SIDE_PADDING = 5.516; const FIGMA_HEADER_PADDING_X = 5.516; const FIGMA_HEADER_PADDING_TOP = 7.354; const FIGMA_HEADER_PADDING_BOTTOM = 3.677; @@ -66,15 +71,20 @@ type CardPlacement = | { laneIndex: LaneIndex; type: 'lane' }; type DropTarget = { cardIndex: number; laneIndex: LaneIndex }; type LaneCards = [CardId[], CardId[]]; -type FamiliarInterfaceVisualProps = { active?: boolean }; +type FamiliarInterfaceVisualProps = { + active?: boolean; + backgroundImageRotationDeg?: number; + backgroundImageSrc?: string; + pointerTargetRef?: RefObject; +}; const COLORS = { backdrop: '#1b1b1b', - backdropStripe: 'rgba(255, 255, 255, 0.28)', border: '#ebebeb', borderLight: '#f1f1f1', borderStrong: '#d6d6d6', boardSurface: '#ffffff', + cardSurface: '#fcfcfc', activeCardBorder: '#b5ccff', activeCardSurface: '#e8f1ff', imageAreaSurface: '#f5f5f3', @@ -288,7 +298,7 @@ const SceneViewport = styled.div` height: ${SCENE_HEIGHT}px; left: 50%; position: absolute; - top: 9px; + top: 0; transform: translateX(-50%) scale(${SCENE_SCALE}); transform-origin: top center; width: ${SCENE_WIDTH}px; @@ -309,35 +319,20 @@ const SceneFrame = styled.div` overflow: hidden; position: relative; width: 411px; +`; - &::before { - background: - radial-gradient( - circle at 1px 1px, - rgba(255, 255, 255, 0.09) 1px, - transparent 0 - ) - right top / 12px 12px repeat, - linear-gradient(rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.02)); - content: ''; - inset: 0; - opacity: 0.42; - pointer-events: none; - position: absolute; - } - - &::after { - background: repeating-linear-gradient( - 180deg, - transparent 0 11px, - ${COLORS.backdropStripe} 11px 15px, - transparent 15px 27px - ); - content: ''; - inset: 0; - pointer-events: none; - position: absolute; - } +const SceneBackdrop = styled.div<{ + $backgroundImageRotationDeg?: number; +}>` + background-color: ${COLORS.backdrop}; + inset: 0; + overflow: hidden; + pointer-events: none; + position: absolute; + transform: rotate( + ${({ $backgroundImageRotationDeg = 0 }) => $backgroundImageRotationDeg}deg + ); + transform-origin: center center; `; const BoardGroup = styled.div<{ $active: boolean }>` @@ -434,10 +429,13 @@ const BoardTitleCount = styled.span` `; const ColumnsHeaderGrid = styled.div` + background: ${COLORS.boardSurface}; display: grid; grid-template-columns: repeat(2, 186.355px); + justify-content: center; min-height: 29.354px; - padding-left: 7.354px; + position: relative; + z-index: 1; `; const LaneHeader = styled.div` @@ -446,7 +444,7 @@ const LaneHeader = styled.div` display: flex; gap: 4px; min-height: 29.354px; - padding: 7.354px 7.354px 0; + padding: 7.354px ${FIGMA_COLUMN_SIDE_PADDING}px 0; &:last-child { border-right: none; @@ -480,18 +478,18 @@ const LaneCount = styled.span` const ColumnsGrid = styled.div` display: grid; grid-template-columns: repeat(2, 186.355px); + justify-content: center; flex: 1 1 auto; min-height: 0; - padding-left: 7.354px; `; const LaneBody = styled.div` border-right: 1px solid ${COLORS.borderLight}; display: flex; flex-direction: column; - gap: 7.354px; + gap: ${FIGMA_COLUMN_GAP}px; min-height: 0; - padding: 7.354px 7.354px 8px; + padding: 7.354px ${FIGMA_COLUMN_SIDE_PADDING}px 8px; &:last-child { border-right: none; @@ -511,7 +509,7 @@ const AddCardRow = styled.div` const OpportunityCard = styled.div<{ $variant?: 'active' | 'board' }>` background: ${({ $variant }) => - $variant === 'active' ? COLORS.activeCardSurface : COLORS.boardSurface}; + $variant === 'active' ? COLORS.activeCardSurface : COLORS.cardSurface}; border: ${({ $variant }) => ($variant === 'active' ? '0.919px' : '1px')} solid ${({ $variant }) => $variant === 'active' ? COLORS.activeCardBorder : COLORS.border}; @@ -519,7 +517,26 @@ const OpportunityCard = styled.div<{ $variant?: 'active' | 'board' }>` box-shadow: ${FIGMA_CARD_SHADOW}; display: flex; flex-direction: column; + overflow: hidden; + position: relative; + transition: background-color 120ms ease; width: ${FIGMA_CARD_WIDTH}px; + + &::after { + background: rgba(0, 0, 0, 0.04); + content: ''; + inset: 0; + opacity: 0; + pointer-events: none; + position: absolute; + transition: opacity 120ms ease; + } + + @media (hover: hover) { + &:hover::after { + opacity: ${({ $variant }) => ($variant === 'board' ? 1 : 0)}; + } + } `; const CardHeader = styled.div` @@ -1163,7 +1180,11 @@ function OpportunityPreviewCard({ export function FamiliarInterfaceVisual({ active = false, + backgroundImageRotationDeg, + backgroundImageSrc, + pointerTargetRef, }: FamiliarInterfaceVisualProps) { + const rootRef = useRef(null); const sceneFrameRef = useRef(null); const interactionLayerRef = useRef(null); const laneBodyRefs = useRef<(HTMLDivElement | null)[]>([]); @@ -1187,8 +1208,11 @@ export function FamiliarInterfaceVisual({ const [draggedCardId, setDraggedCardId] = useState(null); const [hasDraggedCard, setHasDraggedCard] = useState(false); const [isDragging, setIsDragging] = useState(false); + const activePointerIdRef = useRef(null); const dragStateRef = useRef<{ cardId: CardId; + lastClientX: number; + lastClientY: number; maxX: number; maxY: number; minX: number; @@ -1272,6 +1296,51 @@ export function FamiliarInterfaceVisual({ pendingCardAnimationRectsRef.current = nextCardRects; }; + const releaseActivePointerCapture = () => { + const activePointerId = activePointerIdRef.current; + const interactionLayer = interactionLayerRef.current; + + if ( + activePointerId !== null && + interactionLayer?.hasPointerCapture(activePointerId) + ) { + interactionLayer.releasePointerCapture(activePointerId); + } + + activePointerIdRef.current = null; + }; + + const clearDragState = () => { + dragStateRef.current = null; + setActiveCardId(null); + setDraggedCardId(null); + setIsDragging(false); + }; + + const updateDragOffsetFromPointer = (clientX: number, clientY: number) => { + const currentDragState = dragStateRef.current; + + if (currentDragState === null) { + return; + } + + currentDragState.lastClientX = clientX; + currentDragState.lastClientY = clientY; + + const nextX = clamp( + currentDragState.originX + clientX - currentDragState.pointerX, + currentDragState.minX, + currentDragState.maxX, + ); + const nextY = clamp( + currentDragState.originY + clientY - currentDragState.pointerY, + currentDragState.minY, + currentDragState.maxY, + ); + + setDragOffset({ x: nextX, y: nextY }); + }; + const getDropTarget = ( clientX: number, clientY: number, @@ -1332,6 +1401,10 @@ export function FamiliarInterfaceVisual({ return; } + if (dragStateRef.current !== null) { + return; + } + setHasDraggedCard(true); setActiveCardId(cardId); setDraggedCardId(cardId); @@ -1347,6 +1420,8 @@ export function FamiliarInterfaceVisual({ dragStateRef.current = { cardId, + lastClientX: event.clientX, + lastClientY: event.clientY, maxX: interactionLayerRect.width - cardRect.width, maxY: interactionLayerRect.height - cardRect.height, minX: 0, @@ -1359,37 +1434,11 @@ export function FamiliarInterfaceVisual({ }; setDragOffset({ x: originX, y: originY }); setIsDragging(true); - event.currentTarget.setPointerCapture(event.pointerId); + activePointerIdRef.current = event.pointerId; + interactionLayerRef.current?.setPointerCapture(event.pointerId); }; - const handlePointerMove = (event: ReactPointerEvent) => { - if (dragStateRef.current === null) { - return; - } - - event.preventDefault(); - - const nextX = clamp( - dragStateRef.current.originX + - event.clientX - - dragStateRef.current.pointerX, - dragStateRef.current.minX, - dragStateRef.current.maxX, - ); - const nextY = clamp( - dragStateRef.current.originY + - event.clientY - - dragStateRef.current.pointerY, - dragStateRef.current.minY, - dragStateRef.current.maxY, - ); - - setDragOffset({ x: nextX, y: nextY }); - }; - - const handlePointerUp = (event: ReactPointerEvent) => { - event.preventDefault(); - + const finishDragAtPosition = (clientX: number, clientY: number) => { const currentDragState = dragStateRef.current; if (currentDragState === null) { @@ -1398,22 +1447,18 @@ export function FamiliarInterfaceVisual({ const releasedOffset = { x: clamp( - currentDragState.originX + event.clientX - currentDragState.pointerX, + currentDragState.originX + clientX - currentDragState.pointerX, currentDragState.minX, currentDragState.maxX, ), y: clamp( - currentDragState.originY + event.clientY - currentDragState.pointerY, + currentDragState.originY + clientY - currentDragState.pointerY, currentDragState.minY, currentDragState.maxY, ), }; - const dropTarget = getDropTarget( - event.clientX, - event.clientY, - currentDragState.cardId, - ); + const dropTarget = getDropTarget(clientX, clientY, currentDragState.cardId); if (dropTarget !== null) { captureCardAnimationRects(currentDragState.cardId); @@ -1442,14 +1487,72 @@ export function FamiliarInterfaceVisual({ setFloatingPosition(releasedOffset); } - dragStateRef.current = null; - setDraggedCardId(null); - setIsDragging(false); - if (event.currentTarget.hasPointerCapture(event.pointerId)) { - event.currentTarget.releasePointerCapture(event.pointerId); - } + clearDragState(); }; + const handleCapturedPointerMove = ( + event: ReactPointerEvent, + ) => { + if (event.pointerId !== activePointerIdRef.current) { + return; + } + + event.preventDefault(); + updateDragOffsetFromPointer(event.clientX, event.clientY); + }; + + const handleCapturedPointerUp = ( + event: ReactPointerEvent, + ) => { + if (event.pointerId !== activePointerIdRef.current) { + return; + } + + event.preventDefault(); + finishDragAtPosition(event.clientX, event.clientY); + releaseActivePointerCapture(); + }; + + const handleCapturedPointerCancel = ( + event: ReactPointerEvent, + ) => { + if (event.pointerId !== activePointerIdRef.current) { + return; + } + + event.preventDefault(); + + const currentDragState = dragStateRef.current; + + if (currentDragState !== null) { + finishDragAtPosition( + currentDragState.lastClientX, + currentDragState.lastClientY, + ); + } + + releaseActivePointerCapture(); + }; + + const handleLostPointerCapture = () => { + const currentDragState = dragStateRef.current; + + if (currentDragState !== null) { + finishDragAtPosition( + currentDragState.lastClientX, + currentDragState.lastClientY, + ); + } + + activePointerIdRef.current = null; + }; + + useEffect(() => { + return () => { + releaseActivePointerCapture(); + }; + }, []); + const showHandCursor = !hasDraggedCard && !isDragging && activeCardId === null; @@ -1468,9 +1571,6 @@ export function FamiliarInterfaceVisual({ onPointerDown={(event) => { handlePointerDown(event, cardId, { laneIndex, type: 'lane' }); }} - onPointerMove={handlePointerMove} - onPointerUp={handlePointerUp} - onPointerCancel={handlePointerUp} style={isDraggedCard ? { visibility: 'hidden' } : undefined} >

- {section.label} - {section.items.map((item) => ( - - - {renderMenuIcon(item.icon)} - - {item.label} - {item.highlighted ? ( - - - - ) : null} - - ))} -
- ))} - - - - - - - + "Companies" object + + + + + Import data + + + + + + Create company + + + Navigate + + + + + Go to People + + G + then + P + + + + + + + Go to Opportunities + + G + then + O + + + + Settings + + + + + Go to settings + + G + then + S + + + + + + + Switch to dark mode + + + + + + ); } diff --git a/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/FeatureCard.tsx b/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/FeatureCard.tsx index ccfb2dc462..4562c70350 100644 --- a/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/FeatureCard.tsx +++ b/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/FeatureCard.tsx @@ -7,7 +7,7 @@ import { theme } from '@/theme'; import { styled } from '@linaria/react'; import { IconUsersGroup } from '@tabler/icons-react'; import Image from 'next/image'; -import { type ReactNode, useState } from 'react'; +import { type ReactNode, useRef, useState } from 'react'; import { FastPathVisual } from './FastPathVisual'; import { FamiliarInterfaceVisual } from './FamiliarInterfaceVisual'; import { LiveDataVisual } from './LiveDataVisual'; @@ -16,7 +16,7 @@ const FeatureCardContainer = styled.div` display: grid; grid-template-columns: 1fr; grid-template-rows: auto auto; - background-color: ${theme.colors.primary.background[100]}; + background-color: ${theme.colors.secondary.background[5]}; border: 1px solid ${theme.colors.primary.border[20]}; border-radius: ${theme.radius(2)}; overflow: hidden; @@ -26,18 +26,26 @@ const FeatureCardContainer = styled.div` const CardImage = styled.div` width: 100%; - height: 508px; + height: 524px; + box-sizing: border-box; + padding: 8px 8px 0; +`; + +const CardImageFrame = styled.div` background-color: ${theme.colors.primary.border[10]}; - position: relative; + border-radius: 2px; + height: 100%; + isolation: isolate; overflow: hidden; + position: relative; + width: 100%; `; const CardContent = styled.div` - background-color: ${theme.colors.secondary.background[5]}; display: grid; grid-template-columns: 1fr; - row-gap: ${theme.spacing(3)}; - padding: ${theme.spacing(3.5)} ${theme.spacing(4)} ${theme.spacing(4)}; + row-gap: 8px; + padding: ${theme.spacing(3)} ${theme.spacing(4)} ${theme.spacing(4)}; `; const CardTitleRow = styled.div` @@ -111,18 +119,40 @@ function renderFeatureCardIcon(icon: ThreeCardsFeatureCardType['icon']) { export function FeatureCard({ featureCard }: FeatureCardProps) { const [isHovered, setIsHovered] = useState(false); + const imageFrameRef = useRef(null); let visual: ReactNode; if ('illustration' in featureCard) { switch (featureCard.illustration) { case 'familiar-interface': - visual = ; + visual = ( + + ); break; case 'fast-path': - visual = ; + visual = ( + + ); break; case 'live-data': - visual = ; + visual = ( + + ); break; } } else { @@ -145,7 +175,9 @@ export function FeatureCard({ featureCard }: FeatureCardProps) { setIsHovered(false); }} > - {visual} + + {visual} + {renderFeatureCardIcon(featureCard.icon)} diff --git a/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/LiveDataGradientBackdrop.tsx b/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/LiveDataGradientBackdrop.tsx new file mode 100644 index 0000000000..a609bf1ea9 --- /dev/null +++ b/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/LiveDataGradientBackdrop.tsx @@ -0,0 +1,923 @@ +'use client'; + +import { styled } from '@linaria/react'; +import { type RefObject, useEffect, useRef } from 'react'; +import * as THREE from 'three'; + +const DEFAULT_IMAGE_URL = + '/images/home/three-cards-feature/live-data-gradient.webp'; +const VIRTUAL_RENDER_HEIGHT = 1200; +const REFERENCE_PREVIEW_DISTANCE = 4; +const PREVIEW_DISTANCE = REFERENCE_PREVIEW_DISTANCE; +const MIN_FOOTPRINT_SCALE = 0.001; +const HALFTONE_SCALE = 28; +const HALFTONE_POWER = -0.2; +const HALFTONE_WIDTH = 0.4; +const IMAGE_CONTRAST = 1; +const DASH_COLOR = '#777'; +const HOVER_DASH_COLOR = '#777'; +const HOVER_LIGHT_INTENSITY = 0.85; +const HOVER_LIGHT_RADIUS = 0.6; +const HOVER_HALFTONE_RADIUS = 0.45; +const IMAGE_POINTER_FOLLOW = 0.38; +const IMAGE_POINTER_VELOCITY_DAMPING = 0.82; +const IMAGE_HOVER_FADE_IN = 18; +const IMAGE_HOVER_FADE_OUT = 7; + +export type HalftoneBackdropConfig = { + activeHoverX?: number; + activeHoverY?: number; + dashColor?: string; + halftonePower?: number; + halftoneScale?: number; + halftoneWidth?: number; + hoverDashColor?: string; + hoverHalftoneRadius?: number; + hoverLightIntensity?: number; + hoverLightRadius?: number; + imageContrast?: number; + previewDistance?: number; +}; + +type ResolvedHalftoneBackdropConfig = Required; + +const DEFAULT_HALFTONE_BACKDROP_CONFIG: ResolvedHalftoneBackdropConfig = { + activeHoverX: 0.16, + activeHoverY: 0.46, + dashColor: DASH_COLOR, + halftonePower: HALFTONE_POWER, + halftoneScale: HALFTONE_SCALE, + halftoneWidth: HALFTONE_WIDTH, + hoverDashColor: HOVER_DASH_COLOR, + hoverHalftoneRadius: HOVER_HALFTONE_RADIUS, + hoverLightIntensity: HOVER_LIGHT_INTENSITY, + hoverLightRadius: HOVER_LIGHT_RADIUS, + imageContrast: IMAGE_CONTRAST, + previewDistance: PREVIEW_DISTANCE, +}; + +const passThroughVertexShader = ` + varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = vec4(position, 1.0); + } +`; + +const blurFragmentShader = ` + precision highp float; + + uniform sampler2D tInput; + uniform vec2 dir; + uniform vec2 res; + + varying vec2 vUv; + + void main() { + vec4 sum = vec4(0.0); + vec2 px = dir / res; + + float w[5]; + w[0] = 0.227027; + w[1] = 0.1945946; + w[2] = 0.1216216; + w[3] = 0.054054; + w[4] = 0.016216; + + sum += texture2D(tInput, vUv) * w[0]; + + for (int i = 1; i < 5; i++) { + float fi = float(i) * 3.0; + sum += texture2D(tInput, vUv + px * fi) * w[i]; + sum += texture2D(tInput, vUv - px * fi) * w[i]; + } + + gl_FragColor = sum; + } +`; + +const halftoneFragmentShader = ` + precision highp float; + + uniform sampler2D tScene; + uniform vec2 effectResolution; + uniform vec2 logicalResolution; + uniform float tile; + uniform float s_3; + uniform float s_4; + uniform vec3 dashColor; + uniform vec3 hoverDashColor; + uniform float footprintScale; + uniform vec2 interactionUv; + uniform vec2 interactionVelocity; + uniform float hoverLightStrength; + uniform float hoverLightRadius; + + varying vec2 vUv; + + float distSegment(in vec2 p, in vec2 a, in vec2 b) { + vec2 pa = p - a; + vec2 ba = b - a; + float denom = max(dot(ba, ba), 0.000001); + float h = clamp(dot(pa, ba) / denom, 0.0, 1.0); + return length(pa - ba * h); + } + + float lineSimpleEt(in vec2 p, in float r, in float thickness) { + vec2 a = vec2(0.5) + vec2(-r, 0.0); + vec2 b = vec2(0.5) + vec2(r, 0.0); + float distToSegment = distSegment(p, a, b); + float halfThickness = thickness * r; + return distToSegment - halfThickness; + } + + void main() { + vec2 fragCoord = + (gl_FragCoord.xy / max(effectResolution, vec2(1.0))) * logicalResolution; + float halftoneSize = max(tile * max(footprintScale, 0.001), 1.0); + vec2 pointerPx = interactionUv * logicalResolution; + vec2 fragDelta = fragCoord - pointerPx; + float fragDist = length(fragDelta); + vec2 radialDir = fragDist > 0.001 ? fragDelta / fragDist : vec2(0.0, 1.0); + float velocityMagnitude = length(interactionVelocity); + vec2 motionDir = velocityMagnitude > 0.001 + ? interactionVelocity / velocityMagnitude + : vec2(0.0, 0.0); + float motionBias = velocityMagnitude > 0.001 + ? dot(-radialDir, motionDir) * 0.5 + 0.5 + : 0.5; + + float hoverLightMask = 0.0; + if (hoverLightStrength > 0.0) { + float lightRadiusPx = hoverLightRadius * logicalResolution.y; + hoverLightMask = smoothstep(lightRadiusPx, 0.0, fragDist); + } + + vec2 effectCoord = fragCoord; + + vec2 cellIndex = floor(effectCoord / halftoneSize); + vec2 sampleUv = clamp( + (cellIndex + 0.5) * halftoneSize / logicalResolution, + vec2(0.0), + vec2(1.0) + ); + vec2 cellUv = fract(effectCoord / halftoneSize); + + vec4 sceneSample = texture2D(tScene, sampleUv); + float mask = smoothstep(0.02, 0.08, sceneSample.a); + float lightLift = + hoverLightStrength * hoverLightMask * mix(0.78, 1.18, motionBias) * 0.22; + float bandRadius = clamp( + ( + ( + sceneSample.r + + sceneSample.g + + sceneSample.b + + s_3 * length(vec2(0.5)) + ) * + (1.0 / 3.0) + ) + lightLift, + 0.0, + 1.0 + ) * 1.86 * 0.5; + + float alpha = 0.0; + if (bandRadius > 0.0001) { + float signedDistance = lineSimpleEt(cellUv, bandRadius, s_4); + float edge = 0.02; + alpha = (1.0 - smoothstep(0.0, edge, signedDistance)) * mask; + } + + float hoverStrength = clamp(hoverLightMask * hoverLightStrength, 0.0, 1.0); + vec3 activeDashColor = mix(dashColor, hoverDashColor, hoverStrength); + vec3 color = activeDashColor * alpha; + gl_FragColor = vec4(color, alpha); + + #include + #include + } +`; + +const imagePassthroughFragmentShader = ` + precision highp float; + + uniform sampler2D tImage; + uniform vec2 imageSize; + uniform vec2 viewportSize; + uniform float zoom; + uniform float contrast; + + varying vec2 vUv; + + void main() { + float imageAspect = imageSize.x / imageSize.y; + float viewAspect = viewportSize.x / viewportSize.y; + + vec2 uv = vUv; + + if (imageAspect > viewAspect) { + float scale = viewAspect / imageAspect; + uv.x = (uv.x - 0.5) * scale + 0.5; + } else { + float scale = imageAspect / viewAspect; + uv.y = (uv.y - 0.5) * scale + 0.5; + } + + uv = (uv - 0.5) / zoom + 0.5; + + float inBounds = step(0.0, uv.x) * step(uv.x, 1.0) + * step(0.0, uv.y) * step(uv.y, 1.0); + + vec4 color = texture2D(tImage, clamp(uv, 0.0, 1.0)); + vec3 contrastColor = clamp((color.rgb - 0.5) * contrast + 0.5, 0.0, 1.0); + + gl_FragColor = vec4(contrastColor, inBounds); + } +`; + +type Rect = { + x: number; + y: number; + width: number; + height: number; +}; + +type InteractionState = { + hoverStrength: number; + mouseX: number; + mouseY: number; + pointerInside: boolean; + pointerVelocityX: number; + pointerVelocityY: number; + smoothedMouseX: number; + smoothedMouseY: number; +}; + +function clampRectToViewport( + rect: Rect, + viewportWidth: number, + viewportHeight: number, +) { + const minX = Math.max(rect.x, 0); + const minY = Math.max(rect.y, 0); + const maxX = Math.min(rect.x + rect.width, viewportWidth); + const maxY = Math.min(rect.y + rect.height, viewportHeight); + + if (maxX <= minX || maxY <= minY) { + return null; + } + + return { + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY, + }; +} + +function getRectArea(rect: Rect | null) { + if (!rect) { + return 0; + } + + return Math.max(rect.width, 0) * Math.max(rect.height, 0); +} + +function getImagePreviewZoom(previewDistance: number) { + return REFERENCE_PREVIEW_DISTANCE / Math.max(previewDistance, 0.001); +} + +function getContainedImageRect({ + imageHeight, + imageWidth, + viewportHeight, + viewportWidth, + zoom, +}: { + imageHeight: number; + imageWidth: number; + viewportHeight: number; + viewportWidth: number; + zoom: number; +}) { + if ( + imageWidth <= 0 || + imageHeight <= 0 || + viewportWidth <= 0 || + viewportHeight <= 0 + ) { + return null; + } + + const imageAspect = imageWidth / imageHeight; + const viewAspect = viewportWidth / viewportHeight; + + let fittedWidth = viewportWidth; + let fittedHeight = viewportHeight; + + if (imageAspect > viewAspect) { + fittedHeight = viewportWidth / imageAspect; + } else { + fittedWidth = viewportHeight * imageAspect; + } + + const scaledWidth = fittedWidth * zoom; + const scaledHeight = fittedHeight * zoom; + + return clampRectToViewport( + { + x: (viewportWidth - scaledWidth) * 0.5, + y: (viewportHeight - scaledHeight) * 0.5, + width: scaledWidth, + height: scaledHeight, + }, + viewportWidth, + viewportHeight, + ); +} + +function getFootprintScaleFromRects( + currentRect: Rect | null, + referenceRect: Rect | null, +) { + const currentArea = getRectArea(currentRect); + const referenceArea = getRectArea(referenceRect); + + if (currentArea <= 0 || referenceArea <= 0) { + return 1; + } + + return Math.max( + Math.sqrt(currentArea / referenceArea), + MIN_FOOTPRINT_SCALE, + ); +} + +function getImageFootprintScale({ + imageHeight, + imageWidth, + previewDistance, + viewportHeight, + viewportWidth, +}: { + imageHeight: number; + imageWidth: number; + previewDistance: number; + viewportHeight: number; + viewportWidth: number; +}) { + const currentRect = getContainedImageRect({ + imageHeight, + imageWidth, + viewportHeight, + viewportWidth, + zoom: getImagePreviewZoom(previewDistance), + }); + const referenceRect = getContainedImageRect({ + imageHeight, + imageWidth, + viewportHeight, + viewportWidth, + zoom: 1, + }); + + return getFootprintScaleFromRects(currentRect, referenceRect); +} + +function createRenderTarget(width: number, height: number) { + return new THREE.WebGLRenderTarget(width, height, { + minFilter: THREE.LinearFilter, + magFilter: THREE.LinearFilter, + format: THREE.RGBAFormat, + }); +} + +function createInteractionState(): InteractionState { + return { + hoverStrength: 0, + mouseX: 0.5, + mouseY: 0.5, + pointerInside: false, + pointerVelocityX: 0, + pointerVelocityY: 0, + smoothedMouseX: 0.5, + smoothedMouseY: 0.5, + }; +} + +function loadImage(imageUrl: string) { + return new Promise((resolve, reject) => { + const image = new Image(); + image.onload = () => resolve(image); + image.onerror = () => reject(new Error(`Failed to load image: ${imageUrl}`)); + image.src = imageUrl; + }); +} + +async function mountLiveDataGradient({ + container, + config, + imageUrl, + isExternallyActive, + pointerTarget, +}: { + container: HTMLDivElement; + config: ResolvedHalftoneBackdropConfig; + imageUrl: string; + isExternallyActive: () => boolean; + pointerTarget?: HTMLElement | null; +}) { + const HOVER_STRENGTH_STOP_EPSILON = 0.001; + const POINTER_POSITION_STOP_EPSILON = 0.001; + const POINTER_VELOCITY_STOP_EPSILON = 0.0001; + const image = await loadImage(imageUrl); + const getWidth = () => Math.max(container.clientWidth, 1); + const getHeight = () => Math.max(container.clientHeight, 1); + const getVirtualHeight = () => Math.max(VIRTUAL_RENDER_HEIGHT, getHeight()); + const getVirtualWidth = () => + Math.max( + Math.round(getVirtualHeight() * (getWidth() / Math.max(getHeight(), 1))), + 1, + ); + + const renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true }); + renderer.outputColorSpace = THREE.SRGBColorSpace; + renderer.setPixelRatio(1); + renderer.setClearColor(0x000000, 0); + renderer.setSize(getVirtualWidth(), getVirtualHeight(), false); + + const canvas = renderer.domElement; + canvas.style.cursor = 'default'; + canvas.style.display = 'block'; + canvas.style.height = '100%'; + canvas.style.touchAction = 'none'; + canvas.style.width = '100%'; + container.appendChild(canvas); + const trackingElement = pointerTarget ?? canvas; + + const imageTexture = new THREE.Texture(image); + imageTexture.colorSpace = THREE.SRGBColorSpace; + imageTexture.needsUpdate = true; + + const sceneTarget = createRenderTarget(getVirtualWidth(), getVirtualHeight()); + const blurTargetA = createRenderTarget(getVirtualWidth(), getVirtualHeight()); + const blurTargetB = createRenderTarget(getVirtualWidth(), getVirtualHeight()); + const fullScreenGeometry = new THREE.PlaneGeometry(2, 2); + const orthographicCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); + + const imageMaterial = new THREE.ShaderMaterial({ + uniforms: { + contrast: { value: config.imageContrast }, + imageSize: { value: new THREE.Vector2(image.width, image.height) }, + tImage: { value: imageTexture }, + viewportSize: { + value: new THREE.Vector2(getVirtualWidth(), getVirtualHeight()), + }, + zoom: { value: getImagePreviewZoom(config.previewDistance) }, + }, + fragmentShader: imagePassthroughFragmentShader, + vertexShader: passThroughVertexShader, + }); + + const blurHorizontalMaterial = new THREE.ShaderMaterial({ + uniforms: { + dir: { value: new THREE.Vector2(1, 0) }, + res: { value: new THREE.Vector2(getVirtualWidth(), getVirtualHeight()) }, + tInput: { value: null }, + }, + fragmentShader: blurFragmentShader, + vertexShader: passThroughVertexShader, + }); + + const blurVerticalMaterial = new THREE.ShaderMaterial({ + uniforms: { + dir: { value: new THREE.Vector2(0, 1) }, + res: { value: new THREE.Vector2(getVirtualWidth(), getVirtualHeight()) }, + tInput: { value: null }, + }, + fragmentShader: blurFragmentShader, + vertexShader: passThroughVertexShader, + }); + + const halftoneMaterial = new THREE.ShaderMaterial({ + fragmentShader: halftoneFragmentShader, + transparent: true, + uniforms: { + dashColor: { value: new THREE.Color(config.dashColor) }, + effectResolution: { + value: new THREE.Vector2(getVirtualWidth(), getVirtualHeight()), + }, + footprintScale: { value: 1 }, + hoverDashColor: { value: new THREE.Color(config.hoverDashColor) }, + hoverLightRadius: { value: config.hoverLightRadius }, + hoverLightStrength: { value: 0 }, + interactionUv: { value: new THREE.Vector2(0.5, 0.5) }, + interactionVelocity: { value: new THREE.Vector2(0, 0) }, + logicalResolution: { + value: new THREE.Vector2(getVirtualWidth(), getVirtualHeight()), + }, + s_3: { value: config.halftonePower }, + s_4: { value: config.halftoneWidth }, + tScene: { value: sceneTarget.texture }, + tile: { value: config.halftoneScale }, + }, + vertexShader: passThroughVertexShader, + }); + + const fullScreenMesh = new THREE.Mesh(fullScreenGeometry, imageMaterial); + const blurHorizontalMesh = new THREE.Mesh( + fullScreenGeometry, + blurHorizontalMaterial, + ); + const blurVerticalMesh = new THREE.Mesh( + fullScreenGeometry, + blurVerticalMaterial, + ); + const postProcessMesh = new THREE.Mesh(fullScreenGeometry, halftoneMaterial); + + const imageScene = new THREE.Scene(); + imageScene.add(fullScreenMesh); + + const blurHorizontalScene = new THREE.Scene(); + blurHorizontalScene.add(blurHorizontalMesh); + + const blurVerticalScene = new THREE.Scene(); + blurVerticalScene.add(blurVerticalMesh); + + const postScene = new THREE.Scene(); + postScene.add(postProcessMesh); + + const updateViewportUniforms = ( + logicalWidth: number, + logicalHeight: number, + effectWidth: number, + effectHeight: number, + ) => { + blurHorizontalMaterial.uniforms.res.value.set(effectWidth, effectHeight); + blurVerticalMaterial.uniforms.res.value.set(effectWidth, effectHeight); + halftoneMaterial.uniforms.effectResolution.value.set( + effectWidth, + effectHeight, + ); + halftoneMaterial.uniforms.logicalResolution.value.set( + logicalWidth, + logicalHeight, + ); + imageMaterial.uniforms.viewportSize.value.set(logicalWidth, logicalHeight); + }; + + const getHalftoneScale = () => + getImageFootprintScale({ + imageHeight: image.height, + imageWidth: image.width, + previewDistance: config.previewDistance, + viewportHeight: getVirtualHeight(), + viewportWidth: getVirtualWidth(), + }); + + const interaction = createInteractionState(); + let animationFrameId: number | null = null; + let lastFrameTime: number | null = null; + + const renderBackdrop = (deltaSeconds: number) => { + const externalActive = isExternallyActive(); + const isHoverActive = externalActive || interaction.pointerInside; + const targetHoverStrength = isHoverActive ? 1 : 0; + const hoverEasing = + 1 - + Math.exp( + -deltaSeconds * + (isHoverActive ? IMAGE_HOVER_FADE_IN : IMAGE_HOVER_FADE_OUT), + ); + + interaction.hoverStrength += + (targetHoverStrength - interaction.hoverStrength) * hoverEasing; + + if ( + Math.abs(targetHoverStrength - interaction.hoverStrength) <= + HOVER_STRENGTH_STOP_EPSILON + ) { + interaction.hoverStrength = targetHoverStrength; + } + + const targetMouseX = + externalActive && !interaction.pointerInside + ? config.activeHoverX + : interaction.mouseX; + const targetMouseY = + externalActive && !interaction.pointerInside + ? config.activeHoverY + : interaction.mouseY; + + interaction.smoothedMouseX += + (targetMouseX - interaction.smoothedMouseX) * IMAGE_POINTER_FOLLOW; + interaction.smoothedMouseY += + (targetMouseY - interaction.smoothedMouseY) * IMAGE_POINTER_FOLLOW; + + if ( + Math.abs(targetMouseX - interaction.smoothedMouseX) <= + POINTER_POSITION_STOP_EPSILON + ) { + interaction.smoothedMouseX = targetMouseX; + } + + if ( + Math.abs(targetMouseY - interaction.smoothedMouseY) <= + POINTER_POSITION_STOP_EPSILON + ) { + interaction.smoothedMouseY = targetMouseY; + } + + interaction.pointerVelocityX *= IMAGE_POINTER_VELOCITY_DAMPING; + interaction.pointerVelocityY *= IMAGE_POINTER_VELOCITY_DAMPING; + + if ( + Math.abs(interaction.pointerVelocityX) <= POINTER_VELOCITY_STOP_EPSILON + ) { + interaction.pointerVelocityX = 0; + } + + if ( + Math.abs(interaction.pointerVelocityY) <= POINTER_VELOCITY_STOP_EPSILON + ) { + interaction.pointerVelocityY = 0; + } + + halftoneMaterial.uniforms.interactionUv.value.set( + interaction.smoothedMouseX, + 1 - interaction.smoothedMouseY, + ); + halftoneMaterial.uniforms.interactionVelocity.value.set( + interaction.pointerVelocityX * getVirtualWidth(), + -interaction.pointerVelocityY * getVirtualHeight(), + ); + halftoneMaterial.uniforms.hoverLightStrength.value = + config.hoverLightIntensity * interaction.hoverStrength; + halftoneMaterial.uniforms.hoverLightRadius.value = + config.hoverHalftoneRadius + config.hoverLightRadius * 0.5; + halftoneMaterial.uniforms.footprintScale.value = getHalftoneScale(); + imageMaterial.uniforms.zoom.value = getImagePreviewZoom( + config.previewDistance, + ); + + renderer.setRenderTarget(sceneTarget); + renderer.render(imageScene, orthographicCamera); + + blurHorizontalMaterial.uniforms.tInput.value = sceneTarget.texture; + renderer.setRenderTarget(blurTargetA); + renderer.render(blurHorizontalScene, orthographicCamera); + + blurVerticalMaterial.uniforms.tInput.value = blurTargetA.texture; + renderer.setRenderTarget(blurTargetB); + renderer.render(blurVerticalScene, orthographicCamera); + + blurHorizontalMaterial.uniforms.tInput.value = blurTargetB.texture; + renderer.setRenderTarget(blurTargetA); + renderer.render(blurHorizontalScene, orthographicCamera); + + blurVerticalMaterial.uniforms.tInput.value = blurTargetA.texture; + renderer.setRenderTarget(blurTargetB); + renderer.render(blurVerticalScene, orthographicCamera); + + renderer.setRenderTarget(null); + renderer.clear(); + renderer.render(postScene, orthographicCamera); + + return ( + isHoverActive || + interaction.hoverStrength !== targetHoverStrength || + interaction.smoothedMouseX !== targetMouseX || + interaction.smoothedMouseY !== targetMouseY || + interaction.pointerVelocityX !== 0 || + interaction.pointerVelocityY !== 0 + ); + }; + + const runFrame = (timestamp: number) => { + animationFrameId = null; + + const deltaSeconds = + lastFrameTime === null + ? 1 / 60 + : Math.min((timestamp - lastFrameTime) / 1000, 0.1); + + lastFrameTime = timestamp; + + if (renderBackdrop(deltaSeconds)) { + animationFrameId = window.requestAnimationFrame(runFrame); + return; + } + + lastFrameTime = null; + }; + + const ensureAnimationLoop = () => { + if (animationFrameId !== null) { + return; + } + + animationFrameId = window.requestAnimationFrame(runFrame); + }; + + const syncSize = () => { + const virtualWidth = getVirtualWidth(); + const virtualHeight = getVirtualHeight(); + + renderer.setSize(virtualWidth, virtualHeight, false); + sceneTarget.setSize(virtualWidth, virtualHeight); + blurTargetA.setSize(virtualWidth, virtualHeight); + blurTargetB.setSize(virtualWidth, virtualHeight); + updateViewportUniforms( + virtualWidth, + virtualHeight, + virtualWidth, + virtualHeight, + ); + + if (animationFrameId === null) { + renderBackdrop(0); + } + }; + + const resizeObserver = new ResizeObserver(syncSize); + resizeObserver.observe(container); + syncSize(); + + const updatePointerPosition = ( + event: PointerEvent, + options?: { resetVelocity?: boolean }, + ) => { + const rect = trackingElement.getBoundingClientRect(); + const width = Math.max(rect.width, 1); + const height = Math.max(rect.height, 1); + const nextMouseX = THREE.MathUtils.clamp( + (event.clientX - rect.left) / width, + 0, + 1, + ); + const nextMouseY = THREE.MathUtils.clamp( + (event.clientY - rect.top) / height, + 0, + 1, + ); + const deltaX = nextMouseX - interaction.mouseX; + const deltaY = nextMouseY - interaction.mouseY; + + interaction.mouseX = nextMouseX; + interaction.mouseY = nextMouseY; + interaction.pointerInside = + event.clientX >= rect.left && + event.clientX <= rect.right && + event.clientY >= rect.top && + event.clientY <= rect.bottom; + + if (options?.resetVelocity) { + interaction.pointerVelocityX = 0; + interaction.pointerVelocityY = 0; + interaction.smoothedMouseX = nextMouseX; + interaction.smoothedMouseY = nextMouseY; + return; + } + + interaction.pointerVelocityX = deltaX; + interaction.pointerVelocityY = deltaY; + }; + + const handlePointerMove = (event: PointerEvent) => { + const shouldReset = !interaction.pointerInside; + updatePointerPosition( + event, + shouldReset ? { resetVelocity: true } : undefined, + ); + ensureAnimationLoop(); + }; + + const handlePointerLeave = () => { + interaction.pointerInside = false; + interaction.pointerVelocityX = 0; + interaction.pointerVelocityY = 0; + ensureAnimationLoop(); + }; + + trackingElement.addEventListener('pointermove', handlePointerMove); + trackingElement.addEventListener('pointerleave', handlePointerLeave); + + if (isExternallyActive()) { + ensureAnimationLoop(); + } + + return { + dispose: () => { + if (animationFrameId !== null) { + window.cancelAnimationFrame(animationFrameId); + } + + resizeObserver.disconnect(); + trackingElement.removeEventListener('pointermove', handlePointerMove); + trackingElement.removeEventListener('pointerleave', handlePointerLeave); + blurHorizontalMaterial.dispose(); + blurVerticalMaterial.dispose(); + halftoneMaterial.dispose(); + imageMaterial.dispose(); + imageTexture.dispose(); + fullScreenGeometry.dispose(); + sceneTarget.dispose(); + blurTargetA.dispose(); + blurTargetB.dispose(); + renderer.dispose(); + + if (canvas.parentNode === container) { + container.removeChild(canvas); + } + }, + wake: ensureAnimationLoop, + }; +} + +const VisualMount = styled.div` + background: transparent; + display: block; + height: 100%; + min-width: 0; + width: 100%; +`; + +type LiveDataGradientBackdropProps = { + active?: boolean; + config?: HalftoneBackdropConfig; + imageUrl?: string; + pointerTargetRef?: RefObject; +}; + +export function LiveDataGradientBackdrop({ + active = false, + config, + imageUrl = DEFAULT_IMAGE_URL, + pointerTargetRef, +}: LiveDataGradientBackdropProps) { + const mountRef = useRef(null); + const activeRef = useRef(active); + const wakeRef = useRef(null); + const resolvedConfig = { + ...DEFAULT_HALFTONE_BACKDROP_CONFIG, + ...config, + }; + + activeRef.current = active; + + useEffect(() => { + const container = mountRef.current; + + if (!container) { + return; + } + + let dispose: VoidFunction | undefined; + let cancelled = false; + + void mountLiveDataGradient({ + config: resolvedConfig, + container, + imageUrl, + isExternallyActive: () => activeRef.current, + pointerTarget: pointerTargetRef?.current, + }) + .then((mountedGradient) => { + if (cancelled) { + mountedGradient.dispose(); + return; + } + + wakeRef.current = mountedGradient.wake; + dispose = mountedGradient.dispose; + }) + .catch((error) => { + console.error(error); + }); + + return () => { + cancelled = true; + wakeRef.current = null; + dispose?.(); + }; + }, [ + imageUrl, + resolvedConfig.dashColor, + resolvedConfig.halftonePower, + resolvedConfig.halftoneScale, + resolvedConfig.halftoneWidth, + resolvedConfig.hoverDashColor, + resolvedConfig.hoverHalftoneRadius, + resolvedConfig.hoverLightIntensity, + resolvedConfig.hoverLightRadius, + resolvedConfig.imageContrast, + resolvedConfig.previewDistance, + ]); + + useEffect(() => { + wakeRef.current?.(); + }, [active]); + + return ; +} diff --git a/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/LiveDataHeroTable.tsx b/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/LiveDataHeroTable.tsx index 196b6a3167..2ac6511df1 100644 --- a/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/LiveDataHeroTable.tsx +++ b/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/LiveDataHeroTable.tsx @@ -15,15 +15,19 @@ import { useRef, useState, type PointerEvent as ReactPointerEvent, - type ReactNode, } from 'react'; -import { Chip, ChipVariant } from '@/sections/Hero/components/HomeVisual/homeVisualChip'; +import { + Chip, + ChipVariant, +} from '@/sections/Hero/components/HomeVisual/homeVisualChip'; import { VISUAL_TOKENS } from '@/sections/Hero/components/HomeVisual/homeVisualTokens'; const APP_FONT = VISUAL_TOKENS.font.family; const TABLE_CELL_HORIZONTAL_PADDING = 8; const TABLER_STROKE = 1.6; const DEFAULT_TABLE_WIDTH = 520; +const ROW_ENTER_DURATION_MS = 760; +const ROW_ENTER_STAGGER_MS = 160; const COLORS = { accentBorder: VISUAL_TOKENS.border.color.blue, @@ -47,11 +51,25 @@ const COLORS = { const TABLE_COLUMNS = [ { id: 'company', isFirstColumn: true, label: 'Companies', width: 180 }, - { id: 'type', isFirstColumn: false, label: 'Type', width: 160 }, + { id: 'type', isFirstColumn: false, label: 'Type', width: 132 }, { id: 'url', isFirstColumn: false, label: 'Domain', width: 150 }, ] as const; -const TABLE_ROWS = [ +type TableRow = { + company: string; + domain: string; + isNew?: boolean; + logoSrc: string; + status: string; +}; + +const BASE_TABLE_ROWS: ReadonlyArray = [ + { + company: 'Anthropic', + domain: 'anthropic.com', + logoSrc: SHARED_COMPANY_LOGO_URLS.anthropic, + status: 'Customer', + }, { company: 'Slack', domain: 'slack.com', @@ -70,7 +88,31 @@ const TABLE_ROWS = [ logoSrc: SHARED_COMPANY_LOGO_URLS.sequoia, status: 'Customer', }, -] as const; + { + company: 'Cursor', + domain: 'cursor.com', + logoSrc: SHARED_COMPANY_LOGO_URLS.cursor, + status: 'Customer', + }, +]; + +const EXPANDED_TABLE_ROWS: ReadonlyArray = [ + ...BASE_TABLE_ROWS, + { + company: 'Twenty', + domain: 'twenty.com', + isNew: true, + logoSrc: SHARED_COMPANY_LOGO_URLS.twenty, + status: 'Customer', + }, + { + company: 'Linear', + domain: 'linear.app', + isNew: true, + logoSrc: SHARED_COMPANY_LOGO_URLS.linear, + status: 'Customer', + }, +]; const TableShell = styled.div` display: flex; @@ -81,19 +123,6 @@ const TableShell = styled.div` width: 100%; `; -const GripRail = styled.div` - background: ${COLORS.background}; - display: grid; - flex: 0 0 12px; - grid-auto-rows: 32px; - width: 12px; -`; - -const GripCell = styled.div` - background: ${COLORS.background}; - border-bottom: 1px solid ${COLORS.borderLight}; -`; - const TableViewport = styled.div<{ $dragging: boolean }>` cursor: ${({ $dragging }) => ($dragging ? 'grabbing' : 'grab')}; flex: 1 1 auto; @@ -126,6 +155,38 @@ const DataRow = styled.div` display: flex; `; +const RowMotion = styled.div<{ $delayMs?: number; $entering?: boolean }>` + align-items: center; + animation: ${({ $entering }) => + $entering + ? `live-data-row-enter ${ROW_ENTER_DURATION_MS}ms cubic-bezier(0.16, 1, 0.3, 1)` + : 'none'}; + animation-delay: ${({ $delayMs = 0 }) => `${$delayMs}ms`}; + animation-fill-mode: both; + display: flex; + height: 100%; + min-width: 0; + width: 100%; + will-change: opacity, transform; + + @keyframes live-data-row-enter { + 0% { + opacity: 0; + transform: translate3d(0, 28px, 0); + } + + 58% { + opacity: 1; + transform: translate3d(0, 6px, 0); + } + + 100% { + opacity: 1; + transform: translate3d(0, 0, 0); + } + } +`; + const FooterRow = styled.div` display: flex; `; @@ -312,7 +373,11 @@ const StatusChip = styled.div<{ $edited?: boolean; $hoveredByAlice?: boolean }>` white-space: nowrap; `; -function HeaderIcon({ columnId }: { columnId: (typeof TABLE_COLUMNS)[number]['id'] }) { +function HeaderIcon({ + columnId, +}: { + columnId: (typeof TABLE_COLUMNS)[number]['id']; +}) { if (columnId === 'url') { return ( @@ -384,7 +443,12 @@ function CompanyCell({ label={label} leftComponent={ - + } variant={ChipVariant.Highlighted} @@ -405,12 +469,14 @@ type LiveDataHeroTableProps = { editedStatusLabel: string; isFirstTagEdited: boolean; isFirstTagHoveredByAlice: boolean; + showExtendedRows: boolean; }; export function LiveDataHeroTable({ editedStatusLabel, isFirstTagEdited, isFirstTagHoveredByAlice, + showExtendedRows, }: LiveDataHeroTableProps) { const viewportRef = useRef(null); const dragRef = useRef({ @@ -421,6 +487,7 @@ export function LiveDataHeroTable({ }); const [dragging, setDragging] = useState(false); const [hoveredRowIndex, setHoveredRowIndex] = useState(null); + const visibleRows = showExtendedRows ? EXPANDED_TABLE_ROWS : BASE_TABLE_ROWS; const columnWidth = TABLE_COLUMNS.reduce( (sum, column) => sum + column.width, @@ -514,14 +581,6 @@ export function LiveDataHeroTable({ return ( - - - {TABLE_ROWS.map((row, index) => { + {visibleRows.map((row, index) => { const hovered = hoveredRowIndex === index; + const enterDelayMs = row.isNew + ? ROW_ENTER_STAGGER_MS * (index - BASE_TABLE_ROWS.length + 1) + : 0; return ( - + + + - - {index === 0 && isFirstTagEdited - ? editedStatusLabel - : row.status} - + + + {index === 0 && isFirstTagEdited + ? editedStatusLabel + : row.status} + + - + + + - + + + ); })} diff --git a/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/LiveDataVisual.tsx b/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/LiveDataVisual.tsx index d087a933b7..23d9127b28 100644 --- a/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/LiveDataVisual.tsx +++ b/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCard/LiveDataVisual.tsx @@ -3,13 +3,15 @@ import { theme } from '@/theme'; import { styled } from '@linaria/react'; import { - IconCheckbox, IconChevronDown, + IconHeartHandshake, IconList, IconPlus, + IconUser, IconX, } from '@tabler/icons-react'; -import { useEffect, useState } from 'react'; +import { type RefObject, useEffect, useRef, useState } from 'react'; +import { LiveDataGradientBackdrop } from './LiveDataGradientBackdrop'; import { LiveDataHeroTable } from './LiveDataHeroTable'; const APP_FONT = `'Inter', ${theme.font.family.sans}`; @@ -18,18 +20,28 @@ const SCENE_WIDTH = 411; const SCENE_SCALE = 1; const SCENE_SCALE_MD = 0.86; const SCENE_SCALE_SM = 0.74; +const TABLE_PANEL_HOVER_SCALE = 1.012; const TABLER_STROKE = 1.65; const FILTER_ICON_STROKE = 1.33; +const FILTER_ROW_GAP = 4; +const BOB_READY_CURSOR_ROTATION = 135; +const MARKER_CURSOR_HEIGHT = 32; +const MARKER_CURSOR_WIDTH = 29; +const BOB_MARKER_LEFT = 196; +const BOB_MARKER_TOP = 372; +const BOB_ARROW_LEFT_OFFSET = 20; +const DEFAULT_ADD_FILTER_LEFTS = { + docked: 152, + parked: 276, +}; const COLORS = { backdrop: '#1b1b1b', - backdropDot: 'rgba(255, 255, 255, 0.14)', - backdropStripe: 'rgba(255, 255, 255, 0.20)', black: '#1f1f1f', + bobCursor: '#EDB7FF', blue: '#1961ed', blueBorder: '#edf2fe', blueSurface: '#f5f9fd', - cardSurface: '#f5f5f3', green: '#18794e', greenSurface: '#ddf3e4', muted: '#999999', @@ -42,10 +54,19 @@ const COLORS = { yellow: '#fff6a5', } as const; +type AliceReturnPhase = 'return-to-start'; +type BobLiveDataPhase = + | 'bob-ready' + | 'move-to-filter' + | 'remove-filter' + | 'return-bob'; + type LiveDataPhase = | 'idle' | 'move-to-tag' | 'rename-tag' + | AliceReturnPhase + | BobLiveDataPhase | 'settle'; const LIVE_DATA_SEQUENCE: Array<{ @@ -54,25 +75,48 @@ const LIVE_DATA_SEQUENCE: Array<{ }> = [ { delay: 0, phase: 'move-to-tag' }, { delay: 760, phase: 'rename-tag' }, - { delay: 1860, phase: 'settle' }, + { delay: 1700, phase: 'return-to-start' }, + { delay: 2380, phase: 'bob-ready' }, + { delay: 2620, phase: 'move-to-filter' }, + { delay: 3260, phase: 'remove-filter' }, + { delay: 3900, phase: 'return-bob' }, + { delay: 4680, phase: 'settle' }, ]; -const LIVE_DATA_LOOP_DURATION = 3200; const EDITED_TAG_LABEL = 'Priority'; const TYPING_STEP_MS = 90; +const ALICE_IDLE_MARKER = { left: 238, rotation: 0, top: 90 }; +const BOB_READY_MARKER = { + bottom: SCENE_HEIGHT - (BOB_MARKER_TOP + MARKER_CURSOR_HEIGHT / 2), + right: + SCENE_WIDTH - + (BOB_MARKER_LEFT + BOB_ARROW_LEFT_OFFSET + MARKER_CURSOR_WIDTH / 2), + rotation: BOB_READY_CURSOR_ROTATION, +}; +const BOB_FILTER_CURSOR_ROTATION = 132; +const BOB_FILTER_MARKER = { + bottom: 301, + right: 50, + rotation: BOB_FILTER_CURSOR_ROTATION, +}; -const LIVE_DATA_CURSOR_POSITIONS: Record< +const ALICE_MARKER_POSITIONS: Record< LiveDataPhase, { left: number; rotation: number; top: number } > = { - idle: { left: 238, rotation: 0, top: 90 }, - 'move-to-tag': { left: 286, rotation: -38, top: 272 }, - 'rename-tag': { left: 279, rotation: -18, top: 273 }, - settle: { left: 293, rotation: -18, top: 277 }, + idle: ALICE_IDLE_MARKER, + 'move-to-tag': { left: 286, rotation: -38, top: 246 }, + 'rename-tag': { left: 279, rotation: -18, top: 247 }, + 'return-to-start': ALICE_IDLE_MARKER, + 'bob-ready': ALICE_IDLE_MARKER, + 'move-to-filter': ALICE_IDLE_MARKER, + 'remove-filter': ALICE_IDLE_MARKER, + 'return-bob': ALICE_IDLE_MARKER, + settle: ALICE_IDLE_MARKER, }; const VisualRoot = styled.div` - background: ${COLORS.cardSurface}; + background: ${COLORS.backdrop}; height: 100%; overflow: hidden; position: relative; @@ -80,13 +124,13 @@ const VisualRoot = styled.div` `; const SceneViewport = styled.div` - height: ${SCENE_HEIGHT}px; + bottom: 0; left: 50%; position: absolute; - top: 16px; + top: 0; transform: translateX(-50%) scale(${SCENE_SCALE}); transform-origin: top center; - width: ${SCENE_WIDTH}px; + width: 101%; @media (max-width: ${theme.breakpoints.md - 1}px) { transform: translateX(-50%) scale(${SCENE_SCALE_MD}); @@ -98,60 +142,53 @@ const SceneViewport = styled.div` `; const SceneFrame = styled.div` - background: ${COLORS.backdrop}; border-radius: 2px; height: 100%; overflow: hidden; position: relative; width: 100%; - - &::before { - background: radial-gradient( - circle at 1px 1px, - ${COLORS.backdropDot} 1px, - transparent 1.2px - ), - repeating-linear-gradient( - 90deg, - transparent 0 18px, - ${COLORS.backdropStripe} 18px 25px, - transparent 25px 52px - ); - background-size: - 12px 12px, - 100% 32px; - content: ''; - inset: 0; - opacity: 0.8; - position: absolute; - } `; -const AccentRail = styled.div` - background: repeating-linear-gradient( - 180deg, - ${COLORS.backdropStripe} 0 4px, - transparent 4px 11px - ); - bottom: 0; - left: 0; - opacity: 0.9; +const SceneBackdrop = styled.div<{ + $backgroundImageRotationDeg?: number; +}>` + background-color: ${COLORS.backdrop}; + inset: 0; + pointer-events: none; position: absolute; - top: 67%; - width: 58px; `; -const TablePanel = styled.div` +const SceneContent = styled.div` + inset: 16px 0 0; + position: absolute; + width: 100%; +`; + +const TablePanel = styled.div<{ $active?: boolean }>` + backface-visibility: hidden; background: ${COLORS.white}; - border-top-left-radius: ${theme.radius(1)}; - box-shadow: 0 0 0 1px rgba(241, 241, 241, 0.9); + border-top-left-radius: 8px; + box-shadow: ${({ $active }) => + $active + ? '0 0 0 1px rgba(241, 241, 241, 0.9), 0 14px 28px rgba(15, 23, 42, 0.08)' + : '0 0 0 1px rgba(241, 241, 241, 0.9)'}; + contain: paint; display: grid; grid-template-rows: auto auto minmax(0, 1fr); - height: 341px; - left: 58px; + height: 376px; + bottom: -4px; overflow: hidden; position: absolute; - top: 151px; + right: 0px; + transform: ${({ $active }) => + `translate3d(0, 0, 0) scale(${ + $active ? TABLE_PANEL_HOVER_SCALE : 1 + })`}; + transform-origin: bottom right; + transition: + box-shadow 260ms cubic-bezier(0.22, 1, 0.36, 1), + transform 260ms cubic-bezier(0.22, 1, 0.36, 1); + will-change: transform, box-shadow; width: 355px; `; @@ -232,12 +269,34 @@ const FilterRow = styled.div` align-items: center; border-bottom: 1px solid #f1f1f1; display: flex; - gap: 4px; + gap: ${FILTER_ROW_GAP}px; height: 48px; padding: 0 12px; + position: relative; `; -const FilterChip = styled.div<{ $pressed?: boolean }>` +const FilterChipMotion = styled.div<{ $removing?: boolean; $visible: boolean }>` + display: inline-flex; + max-width: ${({ $visible }) => ($visible ? '172px' : '0')}; + opacity: ${({ $removing, $visible }) => ($visible || $removing ? 1 : 0)}; + overflow: ${({ $removing }) => ($removing ? 'visible' : 'hidden')}; + pointer-events: ${({ $removing, $visible }) => + $visible && !$removing ? 'auto' : 'none'}; + transform: ${({ $removing, $visible }) => + $visible || $removing + ? 'translateX(0) scale(1)' + : 'translateX(-8px) scale(0.92)'}; + transform-origin: right center; + transition: + max-width 280ms cubic-bezier(0.22, 1, 0.36, 1) + ${({ $removing }) => ($removing ? '180ms' : '0ms')}, + opacity 180ms ease ${({ $removing }) => ($removing ? '180ms' : '0ms')}, + transform 280ms cubic-bezier(0.22, 1, 0.36, 1) + ${({ $removing }) => ($removing ? '180ms' : '0ms')}; + white-space: nowrap; +`; + +const FilterChip = styled.div<{ $pressed?: boolean; $removing?: boolean }>` align-items: center; background: ${COLORS.blueSurface}; border: 1px solid ${COLORS.blueBorder}; @@ -252,11 +311,36 @@ const FilterChip = styled.div<{ $pressed?: boolean }>` height: 24px; line-height: 1.4; padding: 2px 2px 2px 4px; + opacity: 1; transform: ${({ $pressed }) => ($pressed ? 'scale(0.985)' : 'scale(1)')}; transition: box-shadow 180ms ease, transform 180ms ease; white-space: nowrap; + + animation: ${({ $removing }) => + $removing + ? 'employees-filter-pop-away 320ms cubic-bezier(0.18, 1, 0.32, 1) forwards' + : 'none'}; + transform-origin: right center; + will-change: opacity, transform; + + @keyframes employees-filter-pop-away { + 0% { + opacity: 1; + transform: scale(0.985) translate3d(0, 0, 0); + } + + 36% { + opacity: 1; + transform: scale(1.08) translate3d(2px, -1px, 0); + } + + 100% { + opacity: 0; + transform: scale(0.64) translate3d(18px, -6px, 0); + } + } `; const FilterChipLabel = styled.span` @@ -286,9 +370,10 @@ const FilterValue = styled.span` font-weight: ${theme.font.weight.regular}; `; -const FilterCloseButton = styled.button` +const FilterCloseButton = styled.button<{ $pressed?: boolean }>` align-items: center; - background: transparent; + background: ${({ $pressed }) => + $pressed ? 'rgba(25, 97, 237, 0.08)' : 'transparent'}; border: 0; border-radius: 2px; color: ${COLORS.blue}; @@ -298,7 +383,18 @@ const FilterCloseButton = styled.button` height: 20px; justify-content: center; padding: 0; + transition: background-color 140ms ease; width: 20px; + + &:hover { + background: ${({ $pressed }) => + $pressed ? 'rgba(25, 97, 237, 0.08)' : 'rgba(25, 97, 237, 0.06)'}; + } + + &:focus-visible { + background: rgba(25, 97, 237, 0.06); + outline: none; + } `; const AddFilter = styled.button` @@ -325,6 +421,19 @@ const AddFilter = styled.button` } `; +const FloatingAddFilter = styled(AddFilter)<{ + $animated: boolean; + $left: number; +}>` + left: ${({ $left }) => `${$left}px`}; + margin-left: 0; + position: absolute; + top: 50%; + transform: translateY(-50%); + transition: ${({ $animated }) => + $animated ? 'left 340ms cubic-bezier(0.22, 1, 0.36, 1)' : 'none'}; +`; + const TableBodyArea = styled.div` min-height: 0; min-width: 0; @@ -354,25 +463,50 @@ const CursorGlyph = styled.svg<{ width: ${({ $width }) => `${$width}px`}; `; -const AnimatedCursorLayer = styled.div<{ +const AnimatedMarkerGroup = styled.div<{ $left: number; - $pressed?: boolean; $top: number; }>` - animation: ${({ $pressed }) => - $pressed ? 'cursor-pop 240ms cubic-bezier(0.22, 1, 0.36, 1)' : 'none'}; left: ${({ $left }) => `${$left}px`}; - opacity: 1; pointer-events: none; position: absolute; top: ${({ $top }) => `${$top}px`}; - transform: translate(-50%, -50%) scale(1); transition: left 620ms cubic-bezier(0.22, 1, 0.36, 1), - opacity 180ms ease, - top 620ms cubic-bezier(0.22, 1, 0.36, 1), - transform 140ms ease; + top 620ms cubic-bezier(0.22, 1, 0.36, 1); z-index: 9; +`; + +const AnchoredMarkerGroup = styled.div<{ + $bottom: number; + $right: number; +}>` + bottom: ${({ $bottom }) => `${$bottom}px`}; + pointer-events: none; + position: absolute; + right: ${({ $right }) => `${$right}px`}; + transition: + bottom 620ms cubic-bezier(0.22, 1, 0.36, 1), + right 620ms cubic-bezier(0.22, 1, 0.36, 1); + z-index: 9; +`; + +const MarkerCursorSlot = styled.div<{ + $pressed?: boolean; + $visible?: boolean; +}>` + animation: ${({ $pressed, $visible }) => + $pressed && $visible + ? 'cursor-pop 240ms cubic-bezier(0.22, 1, 0.36, 1)' + : 'none'}; + left: 0; + opacity: ${({ $visible = true }) => ($visible ? 1 : 0)}; + position: absolute; + top: 0; + transform: translate(-50%, -50%) scale(1); + transition: + opacity 180ms ease, + transform 140ms ease; @keyframes cursor-pop { 0% { @@ -391,20 +525,25 @@ const AnimatedCursorLayer = styled.div<{ transform: translate(-50%, -50%) scale(1); } } + + @media (prefers-reduced-motion: reduce) { + animation: none; + } `; -const AnimatedNameTagLayer = styled.div<{ - $left: number; - $top: number; -}>` - left: ${({ $left }) => `${$left}px`}; - pointer-events: none; +const MarkerLabelTop = styled.div` + left: 0; position: absolute; - top: ${({ $top }) => `${$top}px`}; - transform: translate(-50%, calc(-100% - 18px)); - transition: - left 620ms cubic-bezier(0.22, 1, 0.36, 1), - top 620ms cubic-bezier(0.22, 1, 0.36, 1); + top: 0; + transform: translate(-50%, calc(-100% - 24px)); + z-index: 10; +`; + +const MarkerLabelBottom = styled.div` + left: 0; + position: absolute; + top: 0; + transform: translate(-50%, 26px); z-index: 10; `; @@ -423,7 +562,7 @@ const TomMarker = styled.div` left: 31px; pointer-events: auto; position: absolute; - top: 306px; + top: 271px; height: 112px; width: 92px; z-index: 8; @@ -474,11 +613,13 @@ const TomCursorGlyph = styled.svg` width: 41px; `; -const BobMarker = styled.div` - pointer-events: auto; - left: 214px; +const BobMarker = styled.div<{ $hidden?: boolean }>` + left: ${BOB_MARKER_LEFT}px; + opacity: ${({ $hidden }) => ($hidden ? 0 : 1)}; + pointer-events: ${({ $hidden }) => ($hidden ? 'none' : 'auto')}; position: absolute; - top: 386px; + top: ${BOB_MARKER_TOP}px; + transition: opacity 180ms ease; height: 112px; width: 96px; z-index: 8; @@ -492,19 +633,21 @@ const BobMarkerMotion = styled.div<{ $hovered: boolean }>` `; const BobArrow = styled.div` - margin-left: 20px; + height: ${MARKER_CURSOR_HEIGHT}px; + margin-left: ${BOB_ARROW_LEFT_OFFSET}px; position: relative; + width: ${MARKER_CURSOR_WIDTH}px; z-index: 2; `; -const BobLabel = styled.div` +const BobLabel = styled.div<{ $withTopMargin?: boolean }>` align-items: center; - background: ${COLORS.purple}; + background: ${COLORS.bobCursor}; border-radius: 4px; display: inline-flex; height: 38px; justify-content: center; - margin-top: 10px; + margin-top: ${({ $withTopMargin }) => ($withTopMargin ? '10px' : '0')}; min-width: 60px; padding: 0 12px; position: relative; @@ -556,13 +699,27 @@ function TomCursor() { type LiveDataVisualProps = { active?: boolean; + backgroundImageRotationDeg?: number; + backgroundImageSrc?: string; + pointerTargetRef?: RefObject; }; -export function LiveDataVisual({ active = false }: LiveDataVisualProps) { +export function LiveDataVisual({ + active = false, + backgroundImageSrc, + pointerTargetRef, +}: LiveDataVisualProps) { + const rootRef = useRef(null); + const typeFilterRef = useRef(null); + const employeesFilterRef = useRef(null); const [isBobHovered, setIsBobHovered] = useState(false); const [isTomHovered, setIsTomHovered] = useState(false); const [phase, setPhase] = useState('idle'); const [typedTagLabel, setTypedTagLabel] = useState(''); + const [addFilterLefts, setAddFilterLefts] = useState<{ + docked: number; + parked: number; + } | null>(null); useEffect(() => { if (!active) { @@ -570,36 +727,18 @@ export function LiveDataVisual({ active = false }: LiveDataVisualProps) { return; } - let cancelled = false; const timeoutIds: number[] = []; + setPhase('move-to-tag'); - const runSequence = () => { - setPhase('move-to-tag'); + for (const step of LIVE_DATA_SEQUENCE.slice(1)) { + const timeoutId = window.setTimeout(() => { + setPhase(step.phase); + }, step.delay); - for (const step of LIVE_DATA_SEQUENCE.slice(1)) { - const timeoutId = window.setTimeout(() => { - if (!cancelled) { - setPhase(step.phase); - } - }, step.delay); - - timeoutIds.push(timeoutId); - } - - const loopTimeoutId = window.setTimeout(() => { - if (!cancelled) { - runSequence(); - } - }, LIVE_DATA_LOOP_DURATION); - - timeoutIds.push(loopTimeoutId); - }; - - runSequence(); + timeoutIds.push(timeoutId); + } return () => { - cancelled = true; - for (const timeoutId of timeoutIds) { window.clearTimeout(timeoutId); } @@ -627,7 +766,14 @@ export function LiveDataVisual({ active = false }: LiveDataVisualProps) { }; } - if (phase === 'settle') { + if ( + phase === 'return-to-start' || + phase === 'bob-ready' || + phase === 'move-to-filter' || + phase === 'remove-filter' || + phase === 'return-bob' || + phase === 'settle' + ) { setTypedTagLabel(EDITED_TAG_LABEL); return; } @@ -635,160 +781,279 @@ export function LiveDataVisual({ active = false }: LiveDataVisualProps) { setTypedTagLabel(''); }, [phase]); - const animatedCursor = LIVE_DATA_CURSOR_POSITIONS[phase]; - const isFirstTagRenamed = phase === 'rename-tag' || phase === 'settle'; - const isFirstTagEdited = phase === 'rename-tag' || phase === 'settle'; - const isFirstTagHoveredByAlice = phase !== 'idle'; + useEffect(() => { + const measureAddFilterLefts = () => { + const typeFilter = typeFilterRef.current; + const employeesFilter = employeesFilterRef.current; + + if ( + !typeFilter || + !employeesFilter || + employeesFilter.offsetWidth === 0 + ) { + return; + } + + const nextLefts = { + docked: typeFilter.offsetLeft + typeFilter.offsetWidth + FILTER_ROW_GAP, + parked: + employeesFilter.offsetLeft + + employeesFilter.offsetWidth + + FILTER_ROW_GAP, + }; + + setAddFilterLefts((current) => + current?.docked === nextLefts.docked && + current?.parked === nextLefts.parked + ? current + : nextLefts, + ); + }; + + const frameId = window.requestAnimationFrame(measureAddFilterLefts); + window.addEventListener('resize', measureAddFilterLefts); + void document.fonts?.ready.then(measureAddFilterLefts); + + return () => { + window.cancelAnimationFrame(frameId); + window.removeEventListener('resize', measureAddFilterLefts); + }; + }, []); + + const aliceMarker = ALICE_MARKER_POSITIONS[phase]; + const bobCursor = active + ? phase === 'move-to-filter' || phase === 'remove-filter' + ? BOB_FILTER_MARKER + : BOB_READY_MARKER + : null; + const isAliceCursorVisible = + phase === 'idle' || + phase === 'move-to-tag' || + phase === 'rename-tag' || + phase === 'return-to-start' || + phase === 'bob-ready' || + phase === 'move-to-filter' || + phase === 'remove-filter' || + phase === 'return-bob' || + phase === 'settle'; + const isBobCursorVisible = active; + const isEmployeesFilterRemoving = phase === 'remove-filter'; + const isEmployeesFilterVisible = + phase !== 'remove-filter' && phase !== 'return-bob' && phase !== 'settle'; + const hasEmployeesFilterBeenRemoved = + phase === 'remove-filter' || phase === 'return-bob' || phase === 'settle'; + const isFirstTagRenamed = + phase === 'rename-tag' || + phase === 'return-to-start' || + phase === 'bob-ready' || + phase === 'move-to-filter' || + phase === 'remove-filter' || + phase === 'return-bob' || + phase === 'settle'; + const isFirstTagEdited = isFirstTagRenamed; + const isFirstTagHoveredByAlice = + phase === 'move-to-tag' || phase === 'rename-tag'; + const isAddFilterDocked = phase === 'return-bob' || phase === 'settle'; + const addFilterLeft = isAddFilterDocked + ? (addFilterLefts?.docked ?? DEFAULT_ADD_FILTER_LEFTS.docked) + : (addFilterLefts?.parked ?? DEFAULT_ADD_FILTER_LEFTS.parked); + const viewCount = hasEmployeesFilterBeenRemoved ? 11 : 9; return ( - + - - - - - Alice - - - - - + - + + + + + + Alice + + + + + + + {bobCursor ? ( + + + + + + + Bob + + + + ) : null} - - - - - - + + + + + + + All + + {viewCount} + + + - - All - - 9 - - - - - - + + + - - - - - + + + + + + Type + + is Customer + + - - Type - - is Customer - - - - + + - - + + + + + + + Employees + + {'>500'} + + + + + + + - - Employees - - {'>500'} - - - - + Add filter + + - - - - - Add filter - - + + + + - - - - + setIsTomHovered(true)} + onPointerLeave={() => setIsTomHovered(false)} + > + + + + + + Tom + + + - setIsTomHovered(true)} - onPointerLeave={() => setIsTomHovered(false)} - > - - - - - - Tom - - - - - setIsBobHovered(true)} - onPointerLeave={() => setIsBobHovered(false)} - > - - - - - - Bob - - - + setIsBobHovered(true)} + onPointerLeave={() => setIsBobHovered(false)} + > + + + + + + Bob + + + + diff --git a/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCards/FeatureCards.tsx b/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCards/FeatureCards.tsx index aa466d64ed..63c284f879 100644 --- a/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCards/FeatureCards.tsx +++ b/packages/twenty-website-new/src/sections/ThreeCards/components/FeatureCards/FeatureCards.tsx @@ -2,6 +2,7 @@ import { ThreeCardsScrollLayoutEffect } from '@/sections/ThreeCards/effect-components/ThreeCardsScrollLayoutEffect'; import { ThreeCardsFeatureCardType } from '@/sections/ThreeCards/types'; +import { type ThreeCardsScrollLayoutOptions } from '@/sections/ThreeCards/utils/three-cards-scroll-layout'; import { theme } from '@/theme'; import { styled } from '@linaria/react'; import { useRef } from 'react'; @@ -23,6 +24,14 @@ const CardSlot = styled.div` will-change: transform, opacity; `; +const FEATURE_CARDS_SCROLL_LAYOUT_OPTIONS: ThreeCardsScrollLayoutOptions = { + stagger: 0.12, + endEdgeRatio: 0.3, + initialTranslateY: 120, + initialScale: 0.94, + opacityRamp: 0.25, +}; + type FeatureCardsProps = { featureCards: ThreeCardsFeatureCardType[] }; export function FeatureCards({ featureCards }: FeatureCardsProps) { @@ -35,6 +44,7 @@ export function FeatureCards({ featureCards }: FeatureCardsProps) { cardCount={featureCards.length} cardRefs={cardRefs} gridRef={gridRef} + layoutOptions={FEATURE_CARDS_SCROLL_LAYOUT_OPTIONS} /> {featureCards.map((featureCard, index) => ( { const refs: ThreeCardsScrollLayoutRefs = { cardRefs, gridRef }; @@ -23,7 +26,7 @@ export function ThreeCardsScrollLayoutEffect({ const flushLayout = () => { rafId = null; - applyThreeCardsScrollLayout(refs, cardCount); + applyThreeCardsScrollLayout(refs, cardCount, layoutOptions); }; const scheduleLayout = () => { @@ -33,7 +36,7 @@ export function ThreeCardsScrollLayoutEffect({ rafId = window.requestAnimationFrame(flushLayout); }; - applyThreeCardsScrollLayout(refs, cardCount); + applyThreeCardsScrollLayout(refs, cardCount, layoutOptions); window.addEventListener('scroll', scheduleLayout, { passive: true }); window.addEventListener('resize', scheduleLayout); return () => { @@ -43,7 +46,7 @@ export function ThreeCardsScrollLayoutEffect({ window.cancelAnimationFrame(rafId); } }; - }, [cardCount, cardRefs, gridRef]); + }, [cardCount, cardRefs, gridRef, layoutOptions]); return null; } diff --git a/packages/twenty-website-new/src/sections/ThreeCards/types/ThreeCardsFeatureCard.ts b/packages/twenty-website-new/src/sections/ThreeCards/types/ThreeCardsFeatureCard.ts index 829fb68e2f..35787b01e3 100644 --- a/packages/twenty-website-new/src/sections/ThreeCards/types/ThreeCardsFeatureCard.ts +++ b/packages/twenty-website-new/src/sections/ThreeCards/types/ThreeCardsFeatureCard.ts @@ -5,6 +5,8 @@ import type { ImageType } from '@/design-system/components/Image/types/Image'; type ThreeCardsFeatureCardBaseType = { heading: HeadingType; body: BodyType; + backgroundImageSrc?: string; + backgroundImageRotationDeg?: number; icon: 'fast-path' | 'live-data' | 'users-group'; }; diff --git a/packages/twenty-website-new/src/sections/ThreeCards/utils/three-cards-scroll-layout.ts b/packages/twenty-website-new/src/sections/ThreeCards/utils/three-cards-scroll-layout.ts index 52e51540be..719ae82af9 100644 --- a/packages/twenty-website-new/src/sections/ThreeCards/utils/three-cards-scroll-layout.ts +++ b/packages/twenty-website-new/src/sections/ThreeCards/utils/three-cards-scroll-layout.ts @@ -15,12 +15,35 @@ export type ThreeCardsScrollLayoutRefs = { gridRef: RefObject; }; -const STAGGER = 0.25; +export type ThreeCardsScrollLayoutOptions = { + endEdgeRatio?: number; + initialScale?: number; + initialTranslateY?: number; + opacityRamp?: number; + stagger?: number; +}; + +const DEFAULT_SCROLL_LAYOUT_OPTIONS: Required = { + endEdgeRatio: 0.2, + initialScale: 0.88, + initialTranslateY: 200, + opacityRamp: 0.4, + stagger: 0.25, +}; export function applyThreeCardsScrollLayout( refs: ThreeCardsScrollLayoutRefs, cardCount: number, + options: ThreeCardsScrollLayoutOptions = {}, ): void { + const { + endEdgeRatio, + initialScale, + initialTranslateY, + opacityRamp, + stagger, + } = { ...DEFAULT_SCROLL_LAYOUT_OPTIONS, ...options }; + const grid = refs.gridRef.current; if (!grid) { return; @@ -46,7 +69,7 @@ export function applyThreeCardsScrollLayout( const viewportHeight = window.innerHeight; const startEdge = viewportHeight; - const endEdge = viewportHeight * 0.2; + const endEdge = viewportHeight * endEdgeRatio; const progress = clamp01((startEdge - rect.top) / (startEdge - endEdge)); for (let index = 0; index < cardCount; index++) { @@ -55,13 +78,15 @@ export function applyThreeCardsScrollLayout( continue; } - const delay = index * STAGGER; - const localProgress = clamp01((progress - delay) / (1 - delay)); + const delay = index * stagger; + const localProgress = clamp01( + (progress - delay) / Math.max(1 - delay, Number.EPSILON), + ); const eased = easeOutQuint(localProgress); - const translateY = (1 - eased) * 200; - const scale = 0.88 + eased * 0.12; - const opacity = clamp01(localProgress / 0.4); + const translateY = (1 - eased) * initialTranslateY; + const scale = initialScale + eased * (1 - initialScale); + const opacity = clamp01(localProgress / opacityRamp); node.style.opacity = String(opacity); node.style.transform = `translateY(${translateY}px) scale(${scale})`;