@@ -16,6 +16,7 @@ const modalOk = $("#modalOk");
1616const valentine = $ ( "#valentine" ) ;
1717const heartsRoot = $ ( "#hearts" ) ;
1818let llCleanup = null ;
19+ let heartDone = false ;
1920
2021function sleep ( ms ) {
2122 return new Promise ( ( r ) => setTimeout ( r , ms ) ) ;
@@ -196,13 +197,64 @@ function drawHeartAnimated(canvas, onDone) {
196197 }
197198 ctx . stroke ( ) ;
198199 ctx . restore ( ) ;
200+ heartDone = true ;
199201 onDone ?. ( ) ;
200202 }
201203 }
202204
203205 requestAnimationFrame ( frame ) ;
204206}
205207
208+ function drawHeartFull ( canvas ) {
209+ if ( ! canvas ) return ;
210+ const ctx = setupCanvasHiDPI ( canvas ) ;
211+ const w = canvas . clientWidth || 920 ;
212+ const h = canvas . clientHeight || 520 ;
213+
214+ const pts = computeImplicitHeartPoints ( 1200 ) ;
215+ const padding = 34 ;
216+ let minX = Infinity ,
217+ maxX = - Infinity ,
218+ minY = Infinity ,
219+ maxY = - Infinity ;
220+ for ( const p of pts ) {
221+ if ( p . x < minX ) minX = p . x ;
222+ if ( p . x > maxX ) maxX = p . x ;
223+ if ( p . y < minY ) minY = p . y ;
224+ if ( p . y > maxY ) maxY = p . y ;
225+ }
226+ const spanX = Math . max ( 1e-6 , maxX - minX ) ;
227+ const spanY = Math . max ( 1e-6 , maxY - minY ) ;
228+ const scale = Math . min ( ( w - padding * 2 ) / spanX , ( h - padding * 2 ) / spanY ) ;
229+ const ox = w / 2 - ( ( minX + maxX ) / 2 ) * scale ;
230+ const oy = h / 2 + ( ( minY + maxY ) / 2 ) * scale ;
231+
232+ const toCanvas = ( p ) => ( { cx : ox + p . x * scale , cy : oy - p . y * scale } ) ;
233+
234+ const gradient = ctx . createLinearGradient ( 0 , 0 , w , h ) ;
235+ gradient . addColorStop ( 0 , "#ff4d8d" ) ;
236+ gradient . addColorStop ( 0.6 , "#7c5cff" ) ;
237+ gradient . addColorStop ( 1 , "#ff7ab0" ) ;
238+
239+ drawAxes ( ctx , w , h ) ;
240+ ctx . save ( ) ;
241+ ctx . lineWidth = 4 ;
242+ ctx . lineCap = "round" ;
243+ ctx . lineJoin = "round" ;
244+ ctx . strokeStyle = gradient ;
245+ ctx . shadowColor = "rgba(124,92,255,0.30)" ;
246+ ctx . shadowBlur = 10 ;
247+ ctx . beginPath ( ) ;
248+ const pStart = toCanvas ( pts [ 0 ] ) ;
249+ ctx . moveTo ( pStart . cx , pStart . cy ) ;
250+ for ( let i = 1 ; i < pts . length ; i ++ ) {
251+ const pi = toCanvas ( pts [ i ] ) ;
252+ ctx . lineTo ( pi . cx , pi . cy ) ;
253+ }
254+ ctx . stroke ( ) ;
255+ ctx . restore ( ) ;
256+ }
257+
206258function initLLRunAway ( ) {
207259 // Make LL move across the website (viewport), not just inside the paper.
208260 // We'll keep the original LL button as a placeholder in the layout,
@@ -413,3 +465,13 @@ window.addEventListener("load", () => {
413465 runSequence ( ) ;
414466} ) ;
415467
468+ // Keep the graph crisp when the viewport changes (mobile rotation, etc.).
469+ let resizeRaf = 0 ;
470+ window . addEventListener ( "resize" , ( ) => {
471+ if ( ! heartDone ) return ;
472+ if ( document . body . classList . contains ( "is-valentine" ) ) return ;
473+ if ( ! graph ?. isConnected ) return ;
474+ cancelAnimationFrame ( resizeRaf ) ;
475+ resizeRaf = requestAnimationFrame ( ( ) => drawHeartFull ( graph ) ) ;
476+ } ) ;
477+
0 commit comments