diff --git a/bun.lockb b/bun.lockb index d61967c..9628a29 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 77abd63..c924057 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,8 @@ "playwright": "^1.57.0", "prettier": "^3.7.4", "prettier-plugin-tailwindcss": "^0.7.2", - "trpc-panel": "^1.3.4" + "rollup-plugin-visualizer": "^6.0.5", + "trpc-panel": "^1.3.4", + "vite-bundle-visualizer": "^1.2.1" } } diff --git a/perf-results-2026-01-04.json b/perf-results-2026-01-04.json deleted file mode 100644 index 4a445fe..0000000 --- a/perf-results-2026-01-04.json +++ /dev/null @@ -1,1645 +0,0 @@ -{ - "timestamp": "2026-01-04T16:59:54.705Z", - "baseUrl": "http://localhost:3000", - "runsPerPage": 3, - "results": [ - { - "page": "Home", - "url": "http://localhost:3000/", - "runs": [ - { - "fcp": 160, - "lcp": 156, - "cls": 0.04666640980253497, - "fid": 0, - "ttfb": 9.399999998509884, - "domContentLoaded": 166.79999999701977, - "loadComplete": 562.6999999955297, - "totalRequests": 105, - "totalBytes": 8451841, - "jsBytes": 4421934, - "cssBytes": 0, - "imageBytes": 810035, - "fontBytes": 0, - "jsRequests": 91, - "cssRequests": 0, - "imageRequests": 7, - "jsExecutionTime": 0, - "taskDuration": 56, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 152, - "lcp": 184, - "cls": 0.04665493133921685, - "fid": 0, - "ttfb": 8, - "domContentLoaded": 163.89999999850988, - "loadComplete": 580.5, - "totalRequests": 105, - "totalBytes": 8451841, - "jsBytes": 4421934, - "cssBytes": 0, - "imageBytes": 810035, - "fontBytes": 0, - "jsRequests": 91, - "cssRequests": 0, - "imageRequests": 7, - "jsExecutionTime": 0, - "taskDuration": 104, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 136, - "lcp": 164, - "cls": 0.04663327383504485, - "fid": 0, - "ttfb": 6.399999998509884, - "domContentLoaded": 141.89999999850988, - "loadComplete": 602.1999999955297, - "totalRequests": 105, - "totalBytes": 8451841, - "jsBytes": 4421934, - "cssBytes": 0, - "imageBytes": 810035, - "fontBytes": 0, - "jsRequests": 91, - "cssRequests": 0, - "imageRequests": 7, - "jsExecutionTime": 0, - "taskDuration": 0, - "layoutDuration": 0, - "paintDuration": 0 - } - ], - "average": { - "fcp": 149.33333333333334, - "lcp": 168, - "cls": 0.046651538325598894, - "fid": 0, - "ttfb": 7.933333332339923, - "domContentLoaded": 157.5333333313465, - "loadComplete": 581.7999999970198, - "totalRequests": 105, - "totalBytes": 8451841, - "jsBytes": 4421934, - "cssBytes": 0, - "imageBytes": 810035, - "fontBytes": 0, - "jsRequests": 91, - "cssRequests": 0, - "imageRequests": 7, - "jsExecutionTime": 0, - "taskDuration": 53.333333333333336, - "layoutDuration": 0, - "paintDuration": 0 - }, - "median": { - "fcp": 136, - "lcp": 164, - "cls": 0.04663327383504485, - "fid": 0, - "ttfb": 6.399999998509884, - "domContentLoaded": 141.89999999850988, - "loadComplete": 602.1999999955297, - "totalRequests": 105, - "totalBytes": 8451841, - "jsBytes": 4421934, - "cssBytes": 0, - "imageBytes": 810035, - "fontBytes": 0, - "jsRequests": 91, - "cssRequests": 0, - "imageRequests": 7, - "jsExecutionTime": 0, - "taskDuration": 0, - "layoutDuration": 0, - "paintDuration": 0 - }, - "p95": { - "fcp": 152, - "lcp": 184, - "cls": 0.04665493133921685, - "fid": 0, - "ttfb": 8, - "domContentLoaded": 163.89999999850988, - "loadComplete": 580.5, - "totalRequests": 105, - "totalBytes": 8451841, - "jsBytes": 4421934, - "cssBytes": 0, - "imageBytes": 810035, - "fontBytes": 0, - "jsRequests": 91, - "cssRequests": 0, - "imageRequests": 7, - "jsExecutionTime": 0, - "taskDuration": 104, - "layoutDuration": 0, - "paintDuration": 0 - }, - "min": { - "fcp": 136, - "lcp": 156, - "cls": 0.04663327383504485, - "fid": 0, - "ttfb": 6.399999998509884, - "domContentLoaded": 141.89999999850988, - "loadComplete": 562.6999999955297, - "totalRequests": 105, - "totalBytes": 8451841, - "jsBytes": 4421934, - "cssBytes": 0, - "imageBytes": 810035, - "fontBytes": 0, - "jsRequests": 91, - "cssRequests": 0, - "imageRequests": 7, - "jsExecutionTime": 0, - "taskDuration": 0, - "layoutDuration": 0, - "paintDuration": 0 - }, - "max": { - "fcp": 160, - "lcp": 184, - "cls": 0.04666640980253497, - "fid": 0, - "ttfb": 9.399999998509884, - "domContentLoaded": 166.79999999701977, - "loadComplete": 602.1999999955297, - "totalRequests": 105, - "totalBytes": 8451841, - "jsBytes": 4421934, - "cssBytes": 0, - "imageBytes": 810035, - "fontBytes": 0, - "jsRequests": 91, - "cssRequests": 0, - "imageRequests": 7, - "jsExecutionTime": 0, - "taskDuration": 104, - "layoutDuration": 0, - "paintDuration": 0 - } - }, - { - "page": "About", - "url": "http://localhost:3000/about", - "runs": [ - { - "fcp": 428, - "lcp": 428, - "cls": 0.00008728592484085647, - "fid": 0, - "ttfb": 22.200000002980232, - "domContentLoaded": 132.89999999850988, - "loadComplete": 851.6999999955297, - "totalRequests": 103, - "totalBytes": 4514246, - "jsBytes": 4423537, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 95, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 390, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 336, - "lcp": 336, - "cls": 0.00014995421107413841, - "fid": 0, - "ttfb": 19.799999997019768, - "domContentLoaded": 274.1000000014901, - "loadComplete": 979.7000000029802, - "totalRequests": 103, - "totalBytes": 4514246, - "jsBytes": 4423537, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 95, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 295, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 392, - "lcp": 392, - "cls": 0.00007936278292181069, - "fid": 0, - "ttfb": 24.099999994039536, - "domContentLoaded": 327.8999999985099, - "loadComplete": 644.2999999970198, - "totalRequests": 103, - "totalBytes": 4514246, - "jsBytes": 4423537, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 95, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 346, - "layoutDuration": 0, - "paintDuration": 0 - } - ], - "average": { - "fcp": 385.3333333333333, - "lcp": 385.3333333333333, - "cls": 0.00010553430627893521, - "fid": 0, - "ttfb": 22.033333331346512, - "domContentLoaded": 244.96666666616997, - "loadComplete": 825.2333333318433, - "totalRequests": 103, - "totalBytes": 4514246, - "jsBytes": 4423537, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 95, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 343.6666666666667, - "layoutDuration": 0, - "paintDuration": 0 - }, - "median": { - "fcp": 392, - "lcp": 392, - "cls": 0.00007936278292181069, - "fid": 0, - "ttfb": 24.099999994039536, - "domContentLoaded": 327.8999999985099, - "loadComplete": 644.2999999970198, - "totalRequests": 103, - "totalBytes": 4514246, - "jsBytes": 4423537, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 95, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 346, - "layoutDuration": 0, - "paintDuration": 0 - }, - "p95": { - "fcp": 428, - "lcp": 428, - "cls": 0.00008728592484085647, - "fid": 0, - "ttfb": 22.200000002980232, - "domContentLoaded": 132.89999999850988, - "loadComplete": 851.6999999955297, - "totalRequests": 103, - "totalBytes": 4514246, - "jsBytes": 4423537, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 95, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 390, - "layoutDuration": 0, - "paintDuration": 0 - }, - "min": { - "fcp": 336, - "lcp": 336, - "cls": 0.00007936278292181069, - "fid": 0, - "ttfb": 19.799999997019768, - "domContentLoaded": 132.89999999850988, - "loadComplete": 644.2999999970198, - "totalRequests": 103, - "totalBytes": 4514246, - "jsBytes": 4423537, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 95, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 295, - "layoutDuration": 0, - "paintDuration": 0 - }, - "max": { - "fcp": 428, - "lcp": 428, - "cls": 0.00014995421107413841, - "fid": 0, - "ttfb": 24.099999994039536, - "domContentLoaded": 327.8999999985099, - "loadComplete": 979.7000000029802, - "totalRequests": 103, - "totalBytes": 4514246, - "jsBytes": 4423537, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 95, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 390, - "layoutDuration": 0, - "paintDuration": 0 - } - }, - { - "page": "Blog Index", - "url": "http://localhost:3000/blog", - "runs": [ - { - "fcp": 656, - "lcp": 1940, - "cls": 0.00011959311402874229, - "fid": 0, - "ttfb": 78.20000000298023, - "domContentLoaded": 413.19999999552965, - "loadComplete": 1915.7999999970198, - "totalRequests": 122, - "totalBytes": 4632398, - "jsBytes": 4541689, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 103, - "cssRequests": 0, - "imageRequests": 16, - "jsExecutionTime": 0, - "taskDuration": 1297, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 552, - "lcp": 1216, - "cls": 0.00008675864890769677, - "fid": 0, - "ttfb": 32.70000000298023, - "domContentLoaded": 490.8999999985099, - "loadComplete": 1442.1000000014901, - "totalRequests": 122, - "totalBytes": 4632398, - "jsBytes": 4541689, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 103, - "cssRequests": 0, - "imageRequests": 16, - "jsExecutionTime": 0, - "taskDuration": 980, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 544, - "lcp": 1732, - "cls": 0.00007793539541739004, - "fid": 0, - "ttfb": 37.20000000298023, - "domContentLoaded": 158.70000000298023, - "loadComplete": 1629.8000000044703, - "totalRequests": 122, - "totalBytes": 4632398, - "jsBytes": 4541689, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 103, - "cssRequests": 0, - "imageRequests": 16, - "jsExecutionTime": 0, - "taskDuration": 1246, - "layoutDuration": 0, - "paintDuration": 0 - } - ], - "average": { - "fcp": 584, - "lcp": 1629.3333333333333, - "cls": 0.00009476238611794304, - "fid": 0, - "ttfb": 49.3666666696469, - "domContentLoaded": 354.26666666567326, - "loadComplete": 1662.56666666766, - "totalRequests": 122, - "totalBytes": 4632398, - "jsBytes": 4541689, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 103, - "cssRequests": 0, - "imageRequests": 16, - "jsExecutionTime": 0, - "taskDuration": 1174.3333333333333, - "layoutDuration": 0, - "paintDuration": 0 - }, - "median": { - "fcp": 544, - "lcp": 1732, - "cls": 0.00007793539541739004, - "fid": 0, - "ttfb": 37.20000000298023, - "domContentLoaded": 158.70000000298023, - "loadComplete": 1629.8000000044703, - "totalRequests": 122, - "totalBytes": 4632398, - "jsBytes": 4541689, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 103, - "cssRequests": 0, - "imageRequests": 16, - "jsExecutionTime": 0, - "taskDuration": 1246, - "layoutDuration": 0, - "paintDuration": 0 - }, - "p95": { - "fcp": 656, - "lcp": 1940, - "cls": 0.00011959311402874229, - "fid": 0, - "ttfb": 78.20000000298023, - "domContentLoaded": 413.19999999552965, - "loadComplete": 1915.7999999970198, - "totalRequests": 122, - "totalBytes": 4632398, - "jsBytes": 4541689, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 103, - "cssRequests": 0, - "imageRequests": 16, - "jsExecutionTime": 0, - "taskDuration": 1297, - "layoutDuration": 0, - "paintDuration": 0 - }, - "min": { - "fcp": 544, - "lcp": 1216, - "cls": 0.00007793539541739004, - "fid": 0, - "ttfb": 32.70000000298023, - "domContentLoaded": 158.70000000298023, - "loadComplete": 1442.1000000014901, - "totalRequests": 122, - "totalBytes": 4632398, - "jsBytes": 4541689, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 103, - "cssRequests": 0, - "imageRequests": 16, - "jsExecutionTime": 0, - "taskDuration": 980, - "layoutDuration": 0, - "paintDuration": 0 - }, - "max": { - "fcp": 656, - "lcp": 1940, - "cls": 0.00011959311402874229, - "fid": 0, - "ttfb": 78.20000000298023, - "domContentLoaded": 490.8999999985099, - "loadComplete": 1915.7999999970198, - "totalRequests": 122, - "totalBytes": 4632398, - "jsBytes": 4541689, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 103, - "cssRequests": 0, - "imageRequests": 16, - "jsExecutionTime": 0, - "taskDuration": 1297, - "layoutDuration": 0, - "paintDuration": 0 - } - }, - { - "page": "Blog Post (basic)", - "url": "http://localhost:3000/blog/I_made_a_macOS_app_in_a_day", - "runs": [ - { - "fcp": 748, - "lcp": 876, - "cls": 0.00007054605994204926, - "fid": 0, - "ttfb": 251.20000000298023, - "domContentLoaded": 583, - "loadComplete": 927.4000000059605, - "totalRequests": 138, - "totalBytes": 5055597, - "jsBytes": 4964550, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 129, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 354, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 792, - "lcp": 924, - "cls": 0.000056656700102880665, - "fid": 0, - "ttfb": 287.20000000298023, - "domContentLoaded": 594.1000000014901, - "loadComplete": 1019.1000000014901, - "totalRequests": 138, - "totalBytes": 5055597, - "jsBytes": 4964550, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 129, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 353, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 1004, - "lcp": 1132, - "cls": 0.00016702569561240116, - "fid": 0, - "ttfb": 416.40000000596046, - "domContentLoaded": 550.5, - "loadComplete": 1677.8999999985099, - "totalRequests": 138, - "totalBytes": 5055597, - "jsBytes": 4964550, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 129, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 332, - "layoutDuration": 0, - "paintDuration": 0 - } - ], - "average": { - "fcp": 848, - "lcp": 977.3333333333334, - "cls": 0.00009807615188577703, - "fid": 0, - "ttfb": 318.2666666706403, - "domContentLoaded": 575.8666666671634, - "loadComplete": 1208.1333333353202, - "totalRequests": 138, - "totalBytes": 5055597, - "jsBytes": 4964550, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 129, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 346.3333333333333, - "layoutDuration": 0, - "paintDuration": 0 - }, - "median": { - "fcp": 792, - "lcp": 924, - "cls": 0.000056656700102880665, - "fid": 0, - "ttfb": 287.20000000298023, - "domContentLoaded": 594.1000000014901, - "loadComplete": 1019.1000000014901, - "totalRequests": 138, - "totalBytes": 5055597, - "jsBytes": 4964550, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 129, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 353, - "layoutDuration": 0, - "paintDuration": 0 - }, - "p95": { - "fcp": 1004, - "lcp": 1132, - "cls": 0.00016702569561240116, - "fid": 0, - "ttfb": 416.40000000596046, - "domContentLoaded": 550.5, - "loadComplete": 1677.8999999985099, - "totalRequests": 138, - "totalBytes": 5055597, - "jsBytes": 4964550, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 129, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 332, - "layoutDuration": 0, - "paintDuration": 0 - }, - "min": { - "fcp": 748, - "lcp": 876, - "cls": 0.000056656700102880665, - "fid": 0, - "ttfb": 251.20000000298023, - "domContentLoaded": 550.5, - "loadComplete": 927.4000000059605, - "totalRequests": 138, - "totalBytes": 5055597, - "jsBytes": 4964550, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 129, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 332, - "layoutDuration": 0, - "paintDuration": 0 - }, - "max": { - "fcp": 1004, - "lcp": 1132, - "cls": 0.00016702569561240116, - "fid": 0, - "ttfb": 416.40000000596046, - "domContentLoaded": 594.1000000014901, - "loadComplete": 1677.8999999985099, - "totalRequests": 138, - "totalBytes": 5055597, - "jsBytes": 4964550, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 129, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 354, - "layoutDuration": 0, - "paintDuration": 0 - } - }, - { - "page": "Blog Post with multiple images", - "url": "http://localhost:3000/blog/Shapes_With_Abigail!", - "runs": [ - { - "fcp": 2088, - "lcp": 4300, - "cls": 0.00019789763242619515, - "fid": 0, - "ttfb": 1480, - "domContentLoaded": 1821.3000000044703, - "loadComplete": 4132.20000000298, - "totalRequests": 165, - "totalBytes": 5831575, - "jsBytes": 5740528, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 152, - "cssRequests": 0, - "imageRequests": 9, - "jsExecutionTime": 0, - "taskDuration": 443, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 812, - "lcp": 920, - "cls": 0.00015847958851253054, - "fid": 0, - "ttfb": 286.5, - "domContentLoaded": 633.3999999985099, - "loadComplete": 1862.1999999955297, - "totalRequests": 165, - "totalBytes": 5831575, - "jsBytes": 5740528, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 152, - "cssRequests": 0, - "imageRequests": 9, - "jsExecutionTime": 0, - "taskDuration": 367, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 808, - "lcp": 936, - "cls": 0.00011160022437327193, - "fid": 0, - "ttfb": 292.80000000447035, - "domContentLoaded": 578.1999999955297, - "loadComplete": 1314.3999999985099, - "totalRequests": 165, - "totalBytes": 5831575, - "jsBytes": 5740528, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 152, - "cssRequests": 0, - "imageRequests": 9, - "jsExecutionTime": 0, - "taskDuration": 257, - "layoutDuration": 0, - "paintDuration": 0 - } - ], - "average": { - "fcp": 1236, - "lcp": 2052, - "cls": 0.00015599248177066588, - "fid": 0, - "ttfb": 686.4333333348235, - "domContentLoaded": 1010.96666666617, - "loadComplete": 2436.2666666656733, - "totalRequests": 165, - "totalBytes": 5831575, - "jsBytes": 5740528, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 152, - "cssRequests": 0, - "imageRequests": 9, - "jsExecutionTime": 0, - "taskDuration": 355.6666666666667, - "layoutDuration": 0, - "paintDuration": 0 - }, - "median": { - "fcp": 808, - "lcp": 936, - "cls": 0.00011160022437327193, - "fid": 0, - "ttfb": 292.80000000447035, - "domContentLoaded": 578.1999999955297, - "loadComplete": 1314.3999999985099, - "totalRequests": 165, - "totalBytes": 5831575, - "jsBytes": 5740528, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 152, - "cssRequests": 0, - "imageRequests": 9, - "jsExecutionTime": 0, - "taskDuration": 257, - "layoutDuration": 0, - "paintDuration": 0 - }, - "p95": { - "fcp": 2088, - "lcp": 4300, - "cls": 0.00019789763242619515, - "fid": 0, - "ttfb": 1480, - "domContentLoaded": 1821.3000000044703, - "loadComplete": 4132.20000000298, - "totalRequests": 165, - "totalBytes": 5831575, - "jsBytes": 5740528, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 152, - "cssRequests": 0, - "imageRequests": 9, - "jsExecutionTime": 0, - "taskDuration": 443, - "layoutDuration": 0, - "paintDuration": 0 - }, - "min": { - "fcp": 808, - "lcp": 920, - "cls": 0.00011160022437327193, - "fid": 0, - "ttfb": 286.5, - "domContentLoaded": 578.1999999955297, - "loadComplete": 1314.3999999985099, - "totalRequests": 165, - "totalBytes": 5831575, - "jsBytes": 5740528, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 152, - "cssRequests": 0, - "imageRequests": 9, - "jsExecutionTime": 0, - "taskDuration": 257, - "layoutDuration": 0, - "paintDuration": 0 - }, - "max": { - "fcp": 2088, - "lcp": 4300, - "cls": 0.00019789763242619515, - "fid": 0, - "ttfb": 1480, - "domContentLoaded": 1821.3000000044703, - "loadComplete": 4132.20000000298, - "totalRequests": 165, - "totalBytes": 5831575, - "jsBytes": 5740528, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 152, - "cssRequests": 0, - "imageRequests": 9, - "jsExecutionTime": 0, - "taskDuration": 443, - "layoutDuration": 0, - "paintDuration": 0 - } - }, - { - "page": "Blog Post with large banner", - "url": "http://localhost:3000/blog/My_MacOS_rice.", - "runs": [ - { - "fcp": 820, - "lcp": 1576, - "cls": 0.0000651823758097833, - "fid": 0, - "ttfb": 276.59999999403954, - "domContentLoaded": 646.2000000029802, - "loadComplete": 952.8000000044703, - "totalRequests": 164, - "totalBytes": 5831575, - "jsBytes": 5740528, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 152, - "cssRequests": 0, - "imageRequests": 8, - "jsExecutionTime": 0, - "taskDuration": 409, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 880, - "lcp": 2160, - "cls": 0.000051238950878504373, - "fid": 0, - "ttfb": 274.09999999403954, - "domContentLoaded": 693.7000000029802, - "loadComplete": 1390.8000000044703, - "totalRequests": 164, - "totalBytes": 5831575, - "jsBytes": 5740528, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 152, - "cssRequests": 0, - "imageRequests": 8, - "jsExecutionTime": 0, - "taskDuration": 1301, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 876, - "lcp": 1852, - "cls": 0.000055821799447016466, - "fid": 0, - "ttfb": 225.70000000298023, - "domContentLoaded": 327, - "loadComplete": 1227.6999999955297, - "totalRequests": 164, - "totalBytes": 5831575, - "jsBytes": 5740528, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 152, - "cssRequests": 0, - "imageRequests": 8, - "jsExecutionTime": 0, - "taskDuration": 1070, - "layoutDuration": 0, - "paintDuration": 0 - } - ], - "average": { - "fcp": 858.6666666666666, - "lcp": 1862.6666666666667, - "cls": 0.00005741437537843471, - "fid": 0, - "ttfb": 258.79999999701977, - "domContentLoaded": 555.6333333353201, - "loadComplete": 1190.4333333348234, - "totalRequests": 164, - "totalBytes": 5831575, - "jsBytes": 5740528, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 152, - "cssRequests": 0, - "imageRequests": 8, - "jsExecutionTime": 0, - "taskDuration": 926.6666666666666, - "layoutDuration": 0, - "paintDuration": 0 - }, - "median": { - "fcp": 876, - "lcp": 1852, - "cls": 0.000055821799447016466, - "fid": 0, - "ttfb": 225.70000000298023, - "domContentLoaded": 327, - "loadComplete": 1227.6999999955297, - "totalRequests": 164, - "totalBytes": 5831575, - "jsBytes": 5740528, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 152, - "cssRequests": 0, - "imageRequests": 8, - "jsExecutionTime": 0, - "taskDuration": 1070, - "layoutDuration": 0, - "paintDuration": 0 - }, - "p95": { - "fcp": 880, - "lcp": 2160, - "cls": 0.000051238950878504373, - "fid": 0, - "ttfb": 274.09999999403954, - "domContentLoaded": 693.7000000029802, - "loadComplete": 1390.8000000044703, - "totalRequests": 164, - "totalBytes": 5831575, - "jsBytes": 5740528, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 152, - "cssRequests": 0, - "imageRequests": 8, - "jsExecutionTime": 0, - "taskDuration": 1301, - "layoutDuration": 0, - "paintDuration": 0 - }, - "min": { - "fcp": 820, - "lcp": 1576, - "cls": 0.000051238950878504373, - "fid": 0, - "ttfb": 225.70000000298023, - "domContentLoaded": 327, - "loadComplete": 952.8000000044703, - "totalRequests": 164, - "totalBytes": 5831575, - "jsBytes": 5740528, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 152, - "cssRequests": 0, - "imageRequests": 8, - "jsExecutionTime": 0, - "taskDuration": 409, - "layoutDuration": 0, - "paintDuration": 0 - }, - "max": { - "fcp": 880, - "lcp": 2160, - "cls": 0.0000651823758097833, - "fid": 0, - "ttfb": 276.59999999403954, - "domContentLoaded": 693.7000000029802, - "loadComplete": 1390.8000000044703, - "totalRequests": 164, - "totalBytes": 5831575, - "jsBytes": 5740528, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 152, - "cssRequests": 0, - "imageRequests": 8, - "jsExecutionTime": 0, - "taskDuration": 1301, - "layoutDuration": 0, - "paintDuration": 0 - } - }, - { - "page": "Blog Post with Mermaid", - "url": "http://localhost:3000/blog/A_Journey_in_Self_Hosting", - "runs": [ - { - "fcp": 2768, - "lcp": 2768, - "cls": 0.00024232751351815682, - "fid": 0, - "ttfb": 2173.5999999940395, - "domContentLoaded": 2543, - "loadComplete": 4855.5, - "totalRequests": 186, - "totalBytes": 12817938, - "jsBytes": 12726891, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 177, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 1005, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 3548, - "lcp": 3548, - "cls": 0.00016448296440972222, - "fid": 0, - "ttfb": 3000.7000000029802, - "domContentLoaded": 3408.4000000059605, - "loadComplete": 4668, - "totalRequests": 186, - "totalBytes": 12817938, - "jsBytes": 12726891, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 177, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 1076, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 920, - "lcp": 920, - "cls": 0.00005739648450058674, - "fid": 0, - "ttfb": 328.20000000298023, - "domContentLoaded": 719, - "loadComplete": 1139.2000000029802, - "totalRequests": 186, - "totalBytes": 12817938, - "jsBytes": 12726891, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 177, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 1066, - "layoutDuration": 0, - "paintDuration": 0 - } - ], - "average": { - "fcp": 2412, - "lcp": 2412, - "cls": 0.00015473565414282194, - "fid": 0, - "ttfb": 1834.1666666666667, - "domContentLoaded": 2223.4666666686535, - "loadComplete": 3554.2333333343267, - "totalRequests": 186, - "totalBytes": 12817938, - "jsBytes": 12726891, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 177, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 1049, - "layoutDuration": 0, - "paintDuration": 0 - }, - "median": { - "fcp": 2768, - "lcp": 2768, - "cls": 0.00024232751351815682, - "fid": 0, - "ttfb": 2173.5999999940395, - "domContentLoaded": 2543, - "loadComplete": 4855.5, - "totalRequests": 186, - "totalBytes": 12817938, - "jsBytes": 12726891, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 177, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 1005, - "layoutDuration": 0, - "paintDuration": 0 - }, - "p95": { - "fcp": 3548, - "lcp": 3548, - "cls": 0.00016448296440972222, - "fid": 0, - "ttfb": 3000.7000000029802, - "domContentLoaded": 3408.4000000059605, - "loadComplete": 4668, - "totalRequests": 186, - "totalBytes": 12817938, - "jsBytes": 12726891, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 177, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 1076, - "layoutDuration": 0, - "paintDuration": 0 - }, - "min": { - "fcp": 920, - "lcp": 920, - "cls": 0.00005739648450058674, - "fid": 0, - "ttfb": 328.20000000298023, - "domContentLoaded": 719, - "loadComplete": 1139.2000000029802, - "totalRequests": 186, - "totalBytes": 12817938, - "jsBytes": 12726891, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 177, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 1005, - "layoutDuration": 0, - "paintDuration": 0 - }, - "max": { - "fcp": 3548, - "lcp": 3548, - "cls": 0.00024232751351815682, - "fid": 0, - "ttfb": 3000.7000000029802, - "domContentLoaded": 3408.4000000059605, - "loadComplete": 4855.5, - "totalRequests": 186, - "totalBytes": 12817938, - "jsBytes": 12726891, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 177, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 1076, - "layoutDuration": 0, - "paintDuration": 0 - } - }, - { - "page": "Resume", - "url": "http://localhost:3000/resume", - "runs": [ - { - "fcp": 380, - "lcp": 380, - "cls": 0.00014887656694577065, - "fid": 0, - "ttfb": 23.399999998509884, - "domContentLoaded": 328.79999999701977, - "loadComplete": 965.7999999970198, - "totalRequests": 99, - "totalBytes": 4494958, - "jsBytes": 4404249, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 91, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 319, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 332, - "lcp": 332, - "cls": 0.0002888629112714603, - "fid": 0, - "ttfb": 19.899999998509884, - "domContentLoaded": 304.29999999701977, - "loadComplete": 606.6999999955297, - "totalRequests": 99, - "totalBytes": 4494958, - "jsBytes": 4404249, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 91, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 335, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 92, - "lcp": 92, - "cls": 0.00035258505864398473, - "fid": 0, - "ttfb": 8.199999995529652, - "domContentLoaded": 105.40000000596046, - "loadComplete": 1785.4000000059605, - "totalRequests": 99, - "totalBytes": 4494958, - "jsBytes": 4404249, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 91, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 53, - "layoutDuration": 0, - "paintDuration": 0 - } - ], - "average": { - "fcp": 268, - "lcp": 268, - "cls": 0.00026344151228707187, - "fid": 0, - "ttfb": 17.16666666418314, - "domContentLoaded": 246.16666666666666, - "loadComplete": 1119.2999999995034, - "totalRequests": 99, - "totalBytes": 4494958, - "jsBytes": 4404249, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 91, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 235.66666666666666, - "layoutDuration": 0, - "paintDuration": 0 - }, - "median": { - "fcp": 332, - "lcp": 332, - "cls": 0.0002888629112714603, - "fid": 0, - "ttfb": 19.899999998509884, - "domContentLoaded": 304.29999999701977, - "loadComplete": 606.6999999955297, - "totalRequests": 99, - "totalBytes": 4494958, - "jsBytes": 4404249, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 91, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 335, - "layoutDuration": 0, - "paintDuration": 0 - }, - "p95": { - "fcp": 380, - "lcp": 380, - "cls": 0.00014887656694577065, - "fid": 0, - "ttfb": 23.399999998509884, - "domContentLoaded": 328.79999999701977, - "loadComplete": 965.7999999970198, - "totalRequests": 99, - "totalBytes": 4494958, - "jsBytes": 4404249, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 91, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 319, - "layoutDuration": 0, - "paintDuration": 0 - }, - "min": { - "fcp": 92, - "lcp": 92, - "cls": 0.00014887656694577065, - "fid": 0, - "ttfb": 8.199999995529652, - "domContentLoaded": 105.40000000596046, - "loadComplete": 606.6999999955297, - "totalRequests": 99, - "totalBytes": 4494958, - "jsBytes": 4404249, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 91, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 53, - "layoutDuration": 0, - "paintDuration": 0 - }, - "max": { - "fcp": 380, - "lcp": 380, - "cls": 0.00035258505864398473, - "fid": 0, - "ttfb": 23.399999998509884, - "domContentLoaded": 328.79999999701977, - "loadComplete": 1785.4000000059605, - "totalRequests": 99, - "totalBytes": 4494958, - "jsBytes": 4404249, - "cssBytes": 0, - "imageBytes": 0, - "fontBytes": 0, - "jsRequests": 91, - "cssRequests": 0, - "imageRequests": 5, - "jsExecutionTime": 0, - "taskDuration": 335, - "layoutDuration": 0, - "paintDuration": 0 - } - }, - { - "page": "Contact", - "url": "http://localhost:3000/contact", - "runs": [ - { - "fcp": 624, - "lcp": 624, - "cls": 0.00034250432081183314, - "fid": 0, - "ttfb": 25.899999998509884, - "domContentLoaded": 362.09999999403954, - "loadComplete": 4668.79999999702, - "totalRequests": 104, - "totalBytes": 5229913, - "jsBytes": 4478159, - "cssBytes": 0, - "imageBytes": 660146, - "fontBytes": 0, - "jsRequests": 95, - "cssRequests": 0, - "imageRequests": 6, - "jsExecutionTime": 0, - "taskDuration": 387, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 824, - "lcp": 824, - "cls": 0.00020064100414637185, - "fid": 0, - "ttfb": 44.600000001490116, - "domContentLoaded": 482.20000000298023, - "loadComplete": 1250.5, - "totalRequests": 104, - "totalBytes": 5229913, - "jsBytes": 4478159, - "cssBytes": 0, - "imageBytes": 660146, - "fontBytes": 0, - "jsRequests": 95, - "cssRequests": 0, - "imageRequests": 6, - "jsExecutionTime": 0, - "taskDuration": 389, - "layoutDuration": 0, - "paintDuration": 0 - }, - { - "fcp": 540, - "lcp": 540, - "cls": 0.00018063759509428048, - "fid": 0, - "ttfb": 28.19999999552965, - "domContentLoaded": 330.09999999403954, - "loadComplete": 1055.5999999940395, - "totalRequests": 104, - "totalBytes": 5229913, - "jsBytes": 4478159, - "cssBytes": 0, - "imageBytes": 660146, - "fontBytes": 0, - "jsRequests": 95, - "cssRequests": 0, - "imageRequests": 6, - "jsExecutionTime": 0, - "taskDuration": 349, - "layoutDuration": 0, - "paintDuration": 0 - } - ], - "average": { - "fcp": 662.6666666666666, - "lcp": 662.6666666666666, - "cls": 0.0002412609733508285, - "fid": 0, - "ttfb": 32.899999998509884, - "domContentLoaded": 391.46666666368645, - "loadComplete": 2324.9666666636863, - "totalRequests": 104, - "totalBytes": 5229913, - "jsBytes": 4478159, - "cssBytes": 0, - "imageBytes": 660146, - "fontBytes": 0, - "jsRequests": 95, - "cssRequests": 0, - "imageRequests": 6, - "jsExecutionTime": 0, - "taskDuration": 375, - "layoutDuration": 0, - "paintDuration": 0 - }, - "median": { - "fcp": 624, - "lcp": 624, - "cls": 0.00034250432081183314, - "fid": 0, - "ttfb": 25.899999998509884, - "domContentLoaded": 362.09999999403954, - "loadComplete": 4668.79999999702, - "totalRequests": 104, - "totalBytes": 5229913, - "jsBytes": 4478159, - "cssBytes": 0, - "imageBytes": 660146, - "fontBytes": 0, - "jsRequests": 95, - "cssRequests": 0, - "imageRequests": 6, - "jsExecutionTime": 0, - "taskDuration": 387, - "layoutDuration": 0, - "paintDuration": 0 - }, - "p95": { - "fcp": 824, - "lcp": 824, - "cls": 0.00020064100414637185, - "fid": 0, - "ttfb": 44.600000001490116, - "domContentLoaded": 482.20000000298023, - "loadComplete": 1250.5, - "totalRequests": 104, - "totalBytes": 5229913, - "jsBytes": 4478159, - "cssBytes": 0, - "imageBytes": 660146, - "fontBytes": 0, - "jsRequests": 95, - "cssRequests": 0, - "imageRequests": 6, - "jsExecutionTime": 0, - "taskDuration": 389, - "layoutDuration": 0, - "paintDuration": 0 - }, - "min": { - "fcp": 540, - "lcp": 540, - "cls": 0.00018063759509428048, - "fid": 0, - "ttfb": 25.899999998509884, - "domContentLoaded": 330.09999999403954, - "loadComplete": 1055.5999999940395, - "totalRequests": 104, - "totalBytes": 5229913, - "jsBytes": 4478159, - "cssBytes": 0, - "imageBytes": 660146, - "fontBytes": 0, - "jsRequests": 95, - "cssRequests": 0, - "imageRequests": 6, - "jsExecutionTime": 0, - "taskDuration": 349, - "layoutDuration": 0, - "paintDuration": 0 - }, - "max": { - "fcp": 824, - "lcp": 824, - "cls": 0.00034250432081183314, - "fid": 0, - "ttfb": 44.600000001490116, - "domContentLoaded": 482.20000000298023, - "loadComplete": 4668.79999999702, - "totalRequests": 104, - "totalBytes": 5229913, - "jsBytes": 4478159, - "cssBytes": 0, - "imageBytes": 660146, - "fontBytes": 0, - "jsRequests": 95, - "cssRequests": 0, - "imageRequests": 6, - "jsExecutionTime": 0, - "taskDuration": 389, - "layoutDuration": 0, - "paintDuration": 0 - } - } - ] -} \ No newline at end of file diff --git a/src/components/Bars.tsx b/src/components/Bars.tsx index 5e1ac2a..2de7161 100644 --- a/src/components/Bars.tsx +++ b/src/components/Bars.tsx @@ -265,47 +265,60 @@ export function LeftBar() { const glitchChars = "!@#$%^&*()_+-=[]{}|;':\",./<>?~`"; const originalText = "What's this?"; let glitchInterval: NodeJS.Timeout; + let animationFrame: number; setTimeout(() => { setGetLostVisible(true); let currentIndex = 0; - const typeInterval = setInterval(() => { - if (currentIndex <= originalText.length) { - let displayText = originalText.substring(0, currentIndex); - if (currentIndex < originalText.length) { - const remaining = originalText.length - currentIndex; - for (let i = 0; i < remaining; i++) { - displayText += - glitchChars[Math.floor(Math.random() * glitchChars.length)]; - } - } - setGetLostText(displayText); - currentIndex++; - } else { - clearInterval(typeInterval); - setGetLostText(originalText); + let lastUpdate = 0; + const updateInterval = 80; // ms between updates - glitchInterval = setInterval(() => { - if (Math.random() > 0.9) { - let glitched = ""; - for (let i = 0; i < originalText.length; i++) { - if (Math.random() > 0.7) { - glitched += - glitchChars[Math.floor(Math.random() * glitchChars.length)]; - } else { - glitched += originalText[i]; - } + const revealAnimation = (timestamp: number) => { + if (timestamp - lastUpdate >= updateInterval) { + if (currentIndex <= originalText.length) { + let displayText = originalText.substring(0, currentIndex); + if (currentIndex < originalText.length) { + const remaining = originalText.length - currentIndex; + for (let i = 0; i < remaining; i++) { + displayText += + glitchChars[Math.floor(Math.random() * glitchChars.length)]; } - setGetLostText(glitched); - - setTimeout(() => { - setGetLostText(originalText); - }, 100); } - }, 150); + setGetLostText(displayText); + currentIndex++; + lastUpdate = timestamp; + } else { + setGetLostText(originalText); + + // Occasional glitch effect after reveal + glitchInterval = setInterval(() => { + if (Math.random() > 0.92) { + let glitched = ""; + for (let i = 0; i < originalText.length; i++) { + if (Math.random() > 0.75) { + glitched += + glitchChars[ + Math.floor(Math.random() * glitchChars.length) + ]; + } else { + glitched += originalText[i]; + } + } + setGetLostText(glitched); + + setTimeout(() => { + setGetLostText(originalText); + }, 80); + } + }, 200); + return; + } } - }, 140); + animationFrame = requestAnimationFrame(revealAnimation); + }; + + animationFrame = requestAnimationFrame(revealAnimation); }, 500); if (ref) { @@ -345,11 +358,13 @@ export function LeftBar() { onCleanup(() => { ref?.removeEventListener("keydown", handleKeyDown); clearInterval(glitchInterval); + if (animationFrame) cancelAnimationFrame(animationFrame); window.removeEventListener("resize", handleResize); }); } else { onCleanup(() => { clearInterval(glitchInterval); + if (animationFrame) cancelAnimationFrame(animationFrame); window.removeEventListener("resize", handleResize); }); } @@ -586,7 +601,8 @@ export function LeftBar() { navigate(randomUrl); handleLinkClick(); }} - class="text-left font-mono" + class="text-left font-mono transition-opacity duration-75" + style={{ "will-change": "contents" }} > {getLostText()} diff --git a/src/components/blog/Card.tsx b/src/components/blog/Card.tsx index 71e1a70..4665eaf 100644 --- a/src/components/blog/Card.tsx +++ b/src/components/blog/Card.tsx @@ -1,12 +1,14 @@ -import { Show } from "solid-js"; +import { Show, lazy } from "solid-js"; import CardLinks from "./CardLinks"; -import DeletePostButton from "./DeletePostButton"; import { Fire } from "~/components/icons/Fire"; -import { Post } from "~/db/types"; +import { PostCardData } from "~/db/types"; + +const DeletePostButton = lazy(() => import("./DeletePostButton")); export interface CardProps { - post: Post; + post: PostCardData; privilegeLevel: "anonymous" | "admin" | "user"; + index?: number; } export default function Card(props: CardProps) { @@ -27,6 +29,12 @@ export default function Card(props: CardProps) { {props.post.title.replaceAll("_",
diff --git a/src/components/blog/PostSorting.tsx b/src/components/blog/PostSorting.tsx index ff744d5..68f07b4 100644 --- a/src/components/blog/PostSorting.tsx +++ b/src/components/blog/PostSorting.tsx @@ -1,6 +1,6 @@ import { For, Show, createMemo } from "solid-js"; import Card from "./Card"; -import { Post } from "~/db/types"; +import { PostCardData } from "~/db/types"; export interface Tag { value: string; @@ -8,7 +8,7 @@ export interface Tag { } export interface PostSortingProps { - posts: Post[]; + posts: PostCardData[]; tags: Tag[]; privilegeLevel: "anonymous" | "admin" | "user"; filters?: string; @@ -133,9 +133,13 @@ export default function PostSorting(props: PostSortingProps) { } > - {(post) => ( + {(post, index) => (
- +
)}
diff --git a/src/db/create.ts b/src/db/create.ts index 9d56c74..29dba25 100644 --- a/src/db/create.ts +++ b/src/db/create.ts @@ -63,6 +63,9 @@ export const model: { [key: string]: string } = { last_edited_date TEXT ); CREATE INDEX IF NOT EXISTS idx_posts_category ON Post (category); + CREATE INDEX IF NOT EXISTS idx_posts_published ON Post (published); + CREATE INDEX IF NOT EXISTS idx_posts_date ON Post (date); + CREATE INDEX IF NOT EXISTS idx_posts_published_date ON Post (published, date); `, PostLike: ` CREATE TABLE PostLike @@ -72,6 +75,7 @@ export const model: { [key: string]: string } = { post_id INTEGER NOT NULL ); CREATE UNIQUE INDEX IF NOT EXISTS idx_likes_user_post ON PostLike (user_id, post_id); + CREATE INDEX IF NOT EXISTS idx_likes_post_id ON PostLike (post_id); `, Comment: ` CREATE TABLE Comment diff --git a/src/db/types.ts b/src/db/types.ts index 5e909cf..b76ff76 100644 --- a/src/db/types.ts +++ b/src/db/types.ts @@ -105,6 +105,21 @@ export interface PostWithCommentsAndLikes { total_comments: number; last_edited_date?: string | null; } + +export interface PostCardData { + id: number; + category: "blog" | "project"; + title: string; + subtitle: string; + banner_photo: string; + date?: string | null; + published: number; + author_id: string; + reads: number; + attachments: string; + total_likes: number; + total_comments: number; +} export interface PostWithTags { id: number; category: "blog" | "project"; // this is no longer used diff --git a/src/routes/blog/index.tsx b/src/routes/blog/index.tsx index d054556..c04eedd 100644 --- a/src/routes/blog/index.tsx +++ b/src/routes/blog/index.tsx @@ -1,4 +1,4 @@ -import { Show } from "solid-js"; +import { Show, createSignal, onMount, lazy } from "solid-js"; import { useSearchParams, A, query } from "@solidjs/router"; import { Title } from "@solidjs/meta"; import { createAsync } from "@solidjs/router"; @@ -6,11 +6,15 @@ import { getRequestEvent } from "solid-js/web"; import PostSortingSelect from "~/components/blog/PostSortingSelect"; import TagSelector from "~/components/blog/TagSelector"; import PostSorting from "~/components/blog/PostSorting"; -import PublishStatusToggle from "~/components/blog/PublishStatusToggle"; import { TerminalSplash } from "~/components/TerminalSplash"; import { CACHE_CONFIG } from "~/config"; -const getPosts = query(async () => { +const PublishStatusToggle = lazy(() => import("~/components/blog/PublishStatusToggle")); + +const POSTS_PER_PAGE = 12; + +// Separate query for all tags (needed for TagSelector) +const getAllTags = query(async () => { "use server"; const { ConnectionFactory, getPrivilegeLevel } = await import("~/server/utils"); @@ -19,17 +23,61 @@ const getPosts = query(async () => { const privilegeLevel = await getPrivilegeLevel(event.nativeEvent); return withCache( - `posts-${privilegeLevel}`, + `all-tags-${privilegeLevel}`, CACHE_CONFIG.BLOG_POSTS_LIST_CACHE_TTL_MS, async () => { const conn = ConnectionFactory(); + const tagsQuery = ` + SELECT t.value, t.post_id + FROM Tag t + JOIN Post p ON t.post_id = p.id + ${privilegeLevel !== "admin" ? "WHERE p.published = TRUE" : ""} + ORDER BY t.value ASC + `; + + const tagsResult = await conn.execute(tagsQuery); + const tags = tagsResult.rows; + + const tagMap: Record = {}; + tags.forEach((tag: any) => { + const key = `${tag.value}`; + tagMap[key] = (tagMap[key] || 0) + 1; + }); + + return { tagMap, privilegeLevel }; + } + ); +}, "all-tags"); + +const getPosts = query(async (page: number = 1) => { + "use server"; + const { ConnectionFactory, getPrivilegeLevel } = + await import("~/server/utils"); + const { withCache } = await import("~/server/cache"); + const event = getRequestEvent()!; + const privilegeLevel = await getPrivilegeLevel(event.nativeEvent); + + return withCache( + `posts-${privilegeLevel}-page-${page}`, + CACHE_CONFIG.BLOG_POSTS_LIST_CACHE_TTL_MS, + async () => { + const conn = ConnectionFactory(); + const offset = (page - 1) * POSTS_PER_PAGE; + + // Get total count first + let countQuery = `SELECT COUNT(*) as total FROM Post p`; + if (privilegeLevel !== "admin") { + countQuery += ` WHERE p.published = TRUE`; + } + const countResult = await conn.execute(countQuery); + const totalPosts = (countResult.rows[0] as any).total; + let postsQuery = ` SELECT p.id, p.title, p.subtitle, - p.body, p.banner_photo, p.date, p.published, @@ -48,96 +96,185 @@ const getPosts = query(async () => { postsQuery += ` WHERE p.published = TRUE`; } - postsQuery += ` GROUP BY p.id, p.title, p.subtitle, p.body, p.banner_photo, p.date, p.published, p.category, p.author_id, p.reads, p.attachments`; - postsQuery += ` ORDER BY p.date ASC;`; + postsQuery += ` GROUP BY p.id, p.title, p.subtitle, p.banner_photo, p.date, p.published, p.category, p.author_id, p.reads, p.attachments`; + postsQuery += ` ORDER BY p.date ASC`; + postsQuery += ` LIMIT ${POSTS_PER_PAGE} OFFSET ${offset};`; const postsResult = await conn.execute(postsQuery); const posts = postsResult.rows; - const tagsQuery = ` + // Only fetch tags for the posts we're returning + const postIds = posts.map((p: any) => p.id).join(","); + const tagsQuery = postIds + ? ` SELECT t.value, t.post_id FROM Tag t - JOIN Post p ON t.post_id = p.id - ${privilegeLevel !== "admin" ? "WHERE p.published = TRUE" : ""} + WHERE t.post_id IN (${postIds}) ORDER BY t.value ASC - `; + ` + : `SELECT t.value, t.post_id FROM Tag t WHERE 1=0`; const tagsResult = await conn.execute(tagsQuery); const tags = tagsResult.rows; - const tagMap: Record = {}; - tags.forEach((tag: any) => { - const key = `${tag.value}`; - tagMap[key] = (tagMap[key] || 0) + 1; - }); - - return { posts, tags, tagMap, privilegeLevel }; + return { + posts, + tags, + hasMore: offset + posts.length < totalPosts, + totalPosts + }; } ); }, "posts"); export default function BlogIndex() { const [searchParams] = useSearchParams(); + const [currentPage, setCurrentPage] = createSignal(1); + const [allPosts, setAllPosts] = createSignal([]); + const [allTags, setAllTags] = createSignal([]); + const [hasMore, setHasMore] = createSignal(true); + const [isLoading, setIsLoading] = createSignal(false); + let sentinelRef: HTMLDivElement | undefined; - const sort = () => searchParams.sort || "newest"; - const filters = () => - "filter" in searchParams ? searchParams.filter : undefined; - const include = () => - "include" in searchParams ? searchParams.include : undefined; - const status = () => - "status" in searchParams ? searchParams.status : undefined; + const sort = () => { + const sortParam = searchParams.sort; + return Array.isArray(sortParam) ? sortParam[0] : sortParam || "newest"; + }; + const filters = () => { + const filterParam = searchParams.filter; + return filterParam + ? Array.isArray(filterParam) + ? filterParam[0] + : filterParam + : undefined; + }; + const include = () => { + const includeParam = searchParams.include; + return includeParam + ? Array.isArray(includeParam) + ? includeParam[0] + : includeParam + : undefined; + }; + const status = () => { + const statusParam = searchParams.status; + return statusParam + ? Array.isArray(statusParam) + ? statusParam[0] + : statusParam + : undefined; + }; - const data = createAsync(() => getPosts(), { deferStream: true }); + // Load initial page and tag data + const initialData = createAsync(() => getPosts(1), { deferStream: true }); + const tagsData = createAsync(() => getAllTags(), { deferStream: true }); + + // Initialize with first page data + const initializeData = () => { + const firstPage = initialData(); + if (firstPage) { + setAllPosts(firstPage.posts); + setAllTags(firstPage.tags); + setHasMore(firstPage.hasMore); + } + }; + + // Load more posts + const loadMorePosts = async () => { + if (isLoading() || !hasMore()) return; + + setIsLoading(true); + const nextPage = currentPage() + 1; + + try { + const newData = await getPosts(nextPage); + setAllPosts((prev) => [...prev, ...newData.posts]); + setAllTags((prev) => [...prev, ...newData.tags]); + setHasMore(newData.hasMore); + setCurrentPage(nextPage); + } catch (error) { + console.error("Error loading more posts:", error); + } finally { + setIsLoading(false); + } + }; + + // Set up IntersectionObserver for infinite scroll + onMount(() => { + initializeData(); + + const observer = new IntersectionObserver( + (entries) => { + if (entries[0]?.isIntersecting) { + loadMorePosts(); + } + }, + { rootMargin: "200px" } + ); + + if (sentinelRef) { + observer.observe(sentinelRef); + } + + return () => observer.disconnect(); + }); return ( <> Blog | Michael Freno
- }> - {(loadedData) => ( - <> -
- + }> +
+ - 0}> - - + 0} + > + + - - - + + + - - - + + + +
- 0} - fallback={
No posts yet!
} - > -
- + 0} + fallback={
No posts yet!
} + > +
+ + + {/* Sentinel element for infinite scroll */} + +
+ +
Loading more posts...
+
- - )} +
+
diff --git a/src/routes/index.tsx b/src/routes/index.tsx index bf695b0..443799b 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -65,6 +65,8 @@ export default function Home() {
@@ -137,6 +147,8 @@ export default function Home() { Life and Lineage Shops