@@ -143,6 +143,80 @@ test.describe('Home page · structure & SEO', () => {
143143 await expect ( page . locator ( '#hero-heading' ) ) . toBeVisible ( ) ;
144144 } ) ;
145145
146+ test ( 'hero terminal keeps its anchor when the typewriter clears text @desktop' , async ( {
147+ page,
148+ } ) => {
149+ await page . goto ( '/' ) ;
150+ await prepareForResponsiveAudit ( page ) ;
151+ await page . locator ( '.crest__status' ) . waitFor ( { state : 'visible' } ) ;
152+ await page . waitForFunction ( ( ) =>
153+ document . querySelector < HTMLElement > ( '.crest__status' ) ?. style . minBlockSize . endsWith ( 'px' )
154+ ) ;
155+
156+ const samples = await page . evaluate ( async ( ) => {
157+ ( window as Window & { __finishTerminal ?: ( ) => void } ) . __finishTerminal ?.( ) ;
158+ const crest = document . querySelector < HTMLElement > ( '.hero__crest' ) ;
159+ const status = document . querySelector < HTMLElement > ( '.crest__status' ) ;
160+ const code = document . querySelector < HTMLElement > ( '.terminal__body code' ) ;
161+ if ( ! crest || ! status || ! code ) return null ;
162+
163+ const spans = Array . from ( code . querySelectorAll < HTMLElement > ( 'span' ) ) ;
164+ const originalText = spans . map ( ( span ) => span . textContent ?? '' ) ;
165+ const measure = ( ) => {
166+ const crestRect = crest . getBoundingClientRect ( ) ;
167+ const statusRect = status . getBoundingClientRect ( ) ;
168+ return {
169+ crestTop : crestRect . top ,
170+ statusTop : statusRect . top ,
171+ statusHeight : statusRect . height ,
172+ } ;
173+ } ;
174+
175+ const frames : Array < ReturnType < typeof measure > & { textLength : number } > = [ ] ;
176+ frames . push ( { ...measure ( ) , textLength : code . textContent ?. length ?? 0 } ) ;
177+
178+ spans . forEach ( ( span ) => {
179+ span . textContent = '' ;
180+ span . classList . remove ( 'caret' ) ;
181+ } ) ;
182+ spans [ 0 ] ?. classList . add ( 'caret' ) ;
183+ await new Promise ( ( resolve ) => window . requestAnimationFrame ( resolve ) ) ;
184+ frames . push ( { ...measure ( ) , textLength : code . textContent ?. length ?? 0 } ) ;
185+
186+ if ( spans [ 0 ] ) spans [ 0 ] . textContent = originalText [ 0 ] ?. slice ( 0 , 8 ) ?? '' ;
187+ await new Promise ( ( resolve ) => window . requestAnimationFrame ( resolve ) ) ;
188+ frames . push ( { ...measure ( ) , textLength : code . textContent ?. length ?? 0 } ) ;
189+
190+ spans . forEach ( ( span , i ) => {
191+ span . textContent = originalText [ i ] ?? '' ;
192+ } ) ;
193+ await new Promise ( ( resolve ) => window . requestAnimationFrame ( resolve ) ) ;
194+ frames . push ( { ...measure ( ) , textLength : code . textContent ?. length ?? 0 } ) ;
195+
196+ return frames ;
197+ } ) ;
198+
199+ expect ( samples ) . not . toBeNull ( ) ;
200+ const frames = samples ?? [ ] ;
201+ const heightDelta =
202+ Math . max ( ...frames . map ( ( frame ) => frame . statusHeight ) ) -
203+ Math . min ( ...frames . map ( ( frame ) => frame . statusHeight ) ) ;
204+ const crestTopDelta =
205+ Math . max ( ...frames . map ( ( frame ) => frame . crestTop ) ) -
206+ Math . min ( ...frames . map ( ( frame ) => frame . crestTop ) ) ;
207+ const statusTopDelta =
208+ Math . max ( ...frames . map ( ( frame ) => frame . statusTop ) ) -
209+ Math . min ( ...frames . map ( ( frame ) => frame . statusTop ) ) ;
210+ const typedDelta =
211+ Math . max ( ...frames . map ( ( frame ) => frame . textLength ) ) -
212+ Math . min ( ...frames . map ( ( frame ) => frame . textLength ) ) ;
213+
214+ expect ( heightDelta ) . toBeLessThanOrEqual ( 1 ) ;
215+ expect ( crestTopDelta ) . toBeLessThanOrEqual ( 1 ) ;
216+ expect ( statusTopDelta ) . toBeLessThanOrEqual ( 1 ) ;
217+ expect ( typedDelta ) . toBeGreaterThan ( 10 ) ;
218+ } ) ;
219+
146220 test ( 'tab-away title is human-readable and restores the SEO title' , async ( { page } ) => {
147221 await page . addInitScript ( ( ) => {
148222 Object . defineProperty ( navigator , 'webdriver' , { configurable : true , get : ( ) => false } ) ;
0 commit comments