@@ -46,6 +46,10 @@ import { keyframes } from "../../../../../@react-spectrum/s2/style/style-macro"
4646import { getBaseUrl } from "@react-spectrum/s2-docs/src/pageUtils" ;
4747import SearchMenuWrapperServer from "@react-spectrum/s2-docs/src/SearchMenuWrapperServer" ;
4848import type { Page } from "@parcel/rsc" ;
49+ // @ts -ignore
50+ import { fontSizeToken } from "../../../../../@react-spectrum/s2/style/tokens" with { type : 'macro' } ;
51+ // @ts -ignore
52+ import { letters } from "../../../src/textWidth" ;
4953
5054const container = style ( {
5155 backgroundColor : 'layer-2/80' ,
@@ -65,78 +69,90 @@ const container = style({
6569const swapWrapper = style ( {
6670 display : 'inline-block' ,
6771 position : 'relative' ,
68- height : '[1em ]' ,
72+ height : '[1.2em ]' ,
6973 overflow : 'hidden' ,
7074 verticalAlign : 'baseline' ,
7175 whiteSpace : 'nowrap' ,
72- lineHeight : '[1em]' ,
73- marginEnd : 12
76+ lineHeight : '[1.2]'
7477} ) ;
7578
7679// Track that scrolls vertically through all the items.
77- const slideTrack = keyframes ( `
80+ // Use 3D to ensure crisp text rendering.
81+ // With 10x hold time vs transition time:
82+ // Total units: (6 holds × 10) + (6 transitions × 1) = 60 + 6 = 66 units
83+ // Each transition = 100/66 = 1.515%
84+ // Each hold = 10 × 1.515% = 15.152%
85+ const slideTrack : string = keyframes ( `
7886 0% {
79- transform: translateY(0% );
87+ transform: translate3d(0, 0, 0 );
8088 }
81- 15% {
82- transform: translateY(0% );
89+ 15.15 % {
90+ transform: translate3d(0, 0, 0 );
8391 }
84- 16% {
85- transform: translateY(-16.666% );
92+ 16.67 % {
93+ transform: translate3d(0, -1.2em, 0 );
8694 }
87- 31% {
88- transform: translateY(-16.666% );
95+ 31.82 % {
96+ transform: translate3d(0, -1.2em, 0 );
8997 }
90- 32 % {
91- transform: translateY(-33.333% );
98+ 33.33 % {
99+ transform: translate3d(0, -2.4em, 0 );
92100 }
93- 47 % {
94- transform: translateY(-33.333% );
101+ 48.48 % {
102+ transform: translate3d(0, -2.4em, 0 );
95103 }
96- 48 % {
97- transform: translateY(-50% );
104+ 50 % {
105+ transform: translate3d(0, -3.6em, 0 );
98106 }
99- 63 % {
100- transform: translateY(-50% );
107+ 65.15 % {
108+ transform: translate3d(0, -3.6em, 0 );
101109 }
102- 64 % {
103- transform: translateY(-66.666% );
110+ 66.67 % {
111+ transform: translate3d(0, -4.8em, 0 );
104112 }
105- 79 % {
106- transform: translateY(-66.666% );
113+ 81.82 % {
114+ transform: translate3d(0, -4.8em, 0 );
107115 }
108- 80 % {
109- transform: translateY(-83.333% );
116+ 83.33 % {
117+ transform: translate3d(0, -6em, 0 );
110118 }
111- 95 % {
112- transform: translateY(-83.333% );
119+ 98.48 % {
120+ transform: translate3d(0, -6em, 0 );
113121 }
114122 100% {
115- transform: translateY(0% );
123+ transform: translate3d(0, -7.2em, 0 );
116124 }
117125` ) ;
118126
119127const swapTrack = style ( {
120- animation : slideTrack ,
121- animationDuration : 15000 ,
122- animationTimingFunction : 'linear ' ,
123- animationIterationCount : 'infinite' ,
124- display : 'flex' ,
128+ position : 'relative' ,
129+ display : {
130+ default : 'flex ' ,
131+ '@media (prefers-reduced-motion: reduce)' : 'none'
132+ } ,
125133 flexDirection : 'column' ,
126- whiteSpace : 'nowrap'
134+ whiteSpace : 'nowrap' ,
135+ height : '[1.2em]' ,
136+ overflow : 'hidden' ,
137+ fontSize : '[1em]' ,
138+ willChange : 'transform'
127139} ) ;
128140
129- // for measuring longest word
130141const swapSizer = style ( {
131- opacity : 0 ,
142+ display : {
143+ default : 'none' ,
144+ '@media (prefers-reduced-motion: reduce)' : 'block'
145+ } ,
132146 whiteSpace : 'nowrap'
133147} ) ;
134148
135-
136149const swapRow = style ( {
137- display : 'block' ,
138- height : '[1em]' ,
139- lineHeight : '[1em]'
150+ animation : slideTrack ,
151+ animationDuration : 10000 ,
152+ animationTimingFunction : 'linear' ,
153+ animationIterationCount : 'infinite' ,
154+ lineHeight : '[1.2]' ,
155+ height : '[1.2em]'
140156} ) ;
141157
142158export function Home ( { currentPage} : { currentPage : Page } ) {
@@ -195,22 +211,22 @@ export function Home({currentPage}: {currentPage: Page}) {
195211 </ div >
196212 </ nav >
197213 < header aria-labelledby = { headingId } className = { style ( { marginX : 'auto' , paddingX : { default : 16 , sm : 40 } , paddingY : 96 , maxWidth : 1024 } ) } >
198- < h1 id = { headingId } className = { style ( { font : 'heading-3xl' , marginY : 0 , color : 'white' } ) } >
199- < span className = { swapWrapper } > Build apps </ span >
214+ < HomeH1 id = { headingId } >
215+ < span className = { swapWrapper } > Build apps with </ span >
200216 < span className = { swapWrapper } >
201- { /* @ts -ignore */ }
202- < span className = { swapTrack } >
203- < span className = { swapRow } > with polish </ span >
204- < span className = { swapRow } > with speed </ span >
205- < span className = { swapRow } > with ease </ span >
206- < span className = { swapRow } > with accessibility </ span >
207- < span className = { swapRow } > with consistency </ span >
208- < span className = { swapRow } > with React Spectrum </ span >
209- </ span >
210- < span className = { swapSizer } aria-hidden > with React Spectrum</ span >
217+ < div className = { swapTrack } >
218+ < span className = { swapRow } > polish </ span >
219+ < span className = { swapRow } > speed </ span >
220+ < span className = { swapRow } > ease </ span >
221+ < span className = { swapRow } > accessibility </ span >
222+ < span className = { swapRow } > consistency </ span >
223+ < span className = { swapRow } > React Spectrum </ span >
224+ < span className = { swapRow } aria-hidden > polish </ span >
225+ </ div >
226+ < span className = { swapSizer } aria-hidden > React Spectrum</ span >
211227 </ span >
212- </ h1 >
213- < p className = { style ( { font : 'body-3xl' , marginY : 0 , color : 'white' , textWrap : 'balance' } ) } > React Spectrum empowers you to build high quality, accessible, cohesive apps with Adobe's signature design.</ p >
228+ </ HomeH1 >
229+ < p className = { style ( { font : { default : 'body-2xl' , md : 'body- 3xl'} , marginY : 0 , color : 'white' , textWrap : 'balance' } ) } > React Spectrum empowers you to build high quality, accessible, cohesive apps with Adobe's signature design.</ p >
214230 < div className = { style ( { display : 'flex' , gap : 16 , flexDirection : { default : 'column' , sm : 'row' } , marginTop : 32 , marginBottom : 96 } ) } >
215231 < LinkButton size = "XL" staticColor = "white" href = "getting-started" > Get started</ LinkButton >
216232 < SearchMenuWrapperServer currentPage = { currentPage } >
@@ -497,6 +513,46 @@ const buttonStyle = style({
497513 ) ;
498514}
499515
516+ function getTitleTextWidth ( text : string ) {
517+ let width = 0 ;
518+ for ( let c of text ) {
519+ let w = letters [ c ] ;
520+ if ( w != null ) {
521+ width += w ;
522+ }
523+ }
524+
525+ return width ;
526+ }
527+
528+ function HomeH1 ( props ) {
529+ let { children, ...otherProps } = props ;
530+ return (
531+ < h1
532+ { ...otherProps }
533+ style = { { '--width-per-em' : getTitleTextWidth ( 'React Spectrum' ) } as any }
534+ className = { style ( {
535+ font : 'heading-3xl' ,
536+ // This variable is used to calculate the line height.
537+ // Normally it is set by the fontSize, but the custom clamp prevents this.
538+ '--fs' : {
539+ type : 'opacity' ,
540+ value : 'pow(1.125, 10)' // heading-2xl
541+ } ,
542+ '--headingFontSize' : {
543+ type : 'width' ,
544+ value : `[round(pow(1.125, ${ fontSizeToken ( 'heading-size-xxxl' ) } ) * var(--s2-font-size-base, 14) / 16 * 1rem, 1px)]`
545+ } ,
546+ // On mobile, adjust heading to fit in the viewport, and clamp between a min and max font size.
547+ fontSize : `clamp(${ 35 / 16 } rem, (100vw - 40px) / var(--width-per-em), var(--headingFontSize))` ,
548+ marginY : 0 ,
549+ color : 'white'
550+ } ) } >
551+ { children }
552+ </ h1 >
553+ )
554+ }
555+
500556function Section ( { title, description, children} : any ) {
501557 let headingId = useId ( ) ;
502558 return (
0 commit comments