authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-09-04 08:01:59-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 08:01:59-07:00
log4ee2a9d046a0986451c703c9221bb32ba53540bb
treee30b3906fcc8a708be8c8df7e60e08f1421411ef
parent1587f248dd81ed45e214d476451ebf16dbfadb1a
signature Signed by PGP key B5690EEEBB952194

perf(marketing): stop continuous homepage motion (#9697)


1 files changed, 49 insertions(+), 221 deletions(-)

apps/marketing/src/pages/index.astro+49-221
...@@ -17,16 +17,6 @@ const screenshot = await getImage({...@@ -17,16 +17,6 @@ const screenshot = await getImage({
17 quality: 90,17 quality: 90,
18});18});
1919
20const desktopEndorsementRows = [
21 tweets.filter((_, index) => index % 2 === 0),
22 tweets.filter((_, index) => index % 2 === 1),
23];
24
25const mobileEndorsementRows = [
26 tweets.filter((_, index) => index % 3 === 0),
27 tweets.filter((_, index) => index % 3 === 1),
28 tweets.filter((_, index) => index % 3 === 2),
29];
30---20---
3121
32<Layout>22<Layout>
...@@ -121,80 +111,29 @@ const mobileEndorsementRows = [...@@ -121,80 +111,29 @@ const mobileEndorsementRows = [
121 </div>111 </div>
122 </div>112 </div>
123113
124 <div class="endorsement-carousel endorsement-carousel--desktop" aria-label="Developer endorsements">114 <div class="endorsement-carousel" role="region" aria-label="Developer endorsements" tabindex="0">
125 {115 {tweets.map((tweet) => (
126 desktopEndorsementRows.map((row, rowIndex) => (116 <a
127 <div class:list={["endorsement-row", rowIndex === 1 && "is-reverse"]}>117 class="endorsement-card"
128 <div class="endorsement-track">118 href={tweet.link}
129 {[0, 1].map((groupIndex) => (119 target="_blank"
130 <div class="endorsement-track-group" aria-hidden={groupIndex === 1 ? "true" : undefined}>120 rel="noopener noreferrer"
131 {row.map((tweet) => (121 aria-label={`Read ${tweet.handle}'s post on X`}
132 <a122 >
133 class="endorsement-card"123 <div class="endorsement-author">
134 href={tweet.link}124 <img
135 target="_blank"125 src={`/pfps/${tweet.handle}.webp`}
136 rel="noopener noreferrer"126 alt=""
137 aria-label={`Read ${tweet.handle}'s post on X`}127 width="28"
138 tabindex={groupIndex === 1 ? "-1" : undefined}128 height="28"
139 >129 loading="lazy"
140 <div class="endorsement-author">130 decoding="async"
141 <img131 />
142 src={`/pfps/${tweet.handle}.webp`}132 <div class="endorsement-handle">@{tweet.handle}</div>
143 alt=""133 </div>
144 width="28"134 <p>{tweet.excerpt ?? tweet.content}</p>
145 height="28"135 </a>
146 loading="lazy"136 ))}
147 decoding="async"
148 />
149 <div class="endorsement-handle">@{tweet.handle}</div>
150 </div>
151 <p>{tweet.excerpt ?? tweet.content}</p>
152 </a>
153 ))}
154 </div>
155 ))}
156 </div>
157 </div>
158 ))
159 }
160 </div>
161
162 <div class="endorsement-carousel endorsement-carousel--mobile" aria-label="Developer endorsements">
163 {
164 mobileEndorsementRows.map((row, rowIndex) => (
165 <div class:list={["endorsement-row", rowIndex === 1 && "is-reverse"]}>
166 <div class="endorsement-track">
167 {[0, 1].map((groupIndex) => (
168 <div class="endorsement-track-group" aria-hidden={groupIndex === 1 ? "true" : undefined}>
169 {row.map((tweet) => (
170 <a
171 class="endorsement-card"
172 href={tweet.link}
173 target="_blank"
174 rel="noopener noreferrer"
175 aria-label={`Read ${tweet.handle}'s post on X`}
176 tabindex={groupIndex === 1 ? "-1" : undefined}
177 >
178 <div class="endorsement-author">
179 <img
180 src={`/pfps/${tweet.handle}.webp`}
181 alt=""
182 width="28"
183 height="28"
184 loading="lazy"
185 decoding="async"
186 />
187 <div class="endorsement-handle">@{tweet.handle}</div>
188 </div>
189 <p>{tweet.excerpt ?? tweet.content}</p>
190 </a>
191 ))}
192 </div>
193 ))}
194 </div>
195 </div>
196 ))
197 }
198 </div>137 </div>
199 </section>138 </section>
200139
...@@ -480,28 +419,6 @@ const mobileEndorsementRows = [...@@ -480,28 +419,6 @@ const mobileEndorsementRows = [
480 }419 }
481420
482 init();421 init();
483
484 // Marks lean a few pixels toward the pointer, each by its own depth.
485 const hero = document.querySelector<HTMLElement>(".hero");
486 const field = document.querySelector<HTMLElement>(".hero-float");
487 const fine = window.matchMedia("(pointer: fine) and (prefers-reduced-motion: no-preference)").matches;
488 if (hero && field && fine) {
489 let raf = 0;
490 let nx = 0;
491 let ny = 0;
492 const apply = () => {
493 raf = 0;
494 field.style.setProperty("--px", `${(nx * 18).toFixed(1)}px`);
495 field.style.setProperty("--py", `${(ny * 14).toFixed(1)}px`);
496 };
497 hero.addEventListener("pointermove", (e) => {
498 const r = hero.getBoundingClientRect();
499 nx = ((e.clientX - r.left) / r.width - 0.5) * 2;
500 ny = ((e.clientY - r.top) / r.height - 0.5) * 2;
501 if (!raf) raf = requestAnimationFrame(apply);
502 });
503 hero.addEventListener("pointerleave", () => { nx = 0; ny = 0; if (!raf) raf = requestAnimationFrame(apply); });
504 }
505</script>422</script>
506423
507<style>424<style>
...@@ -673,18 +590,12 @@ const mobileEndorsementRows = [...@@ -673,18 +590,12 @@ const mobileEndorsementRows = [
673 :global([data-platform="linux"]) .dl-icon--linux { display: block; }590 :global([data-platform="linux"]) .dl-icon--linux { display: block; }
674 :global(html:not([data-platform])) .dl-icon--apple { display: block; }591 :global(html:not([data-platform])) .dl-icon--apple { display: block; }
675592
676 /* Floating harness marks. Six points on an ellipse around the hero copy:593 /* Harness marks stay in their final positions after the entry animation. */
677 two small at the top, two high and wide, two low and close. Symmetric,
678 and none of them touch the screenshot. Each mark flies in from its own
679 side on load, then drifts on its own period, and the whole set leans a
680 few pixels toward the pointer. */
681 .hero-float {594 .hero-float {
682 position: absolute;595 position: absolute;
683 inset: 0;596 inset: 0;
684 pointer-events: none;597 pointer-events: none;
685 z-index: 1;598 z-index: 1;
686 --px: 0px;
687 --py: 0px;
688 }599 }
689600
690 .hero-float-mark {601 .hero-float-mark {
...@@ -694,8 +605,6 @@ const mobileEndorsementRows = [...@@ -694,8 +605,6 @@ const mobileEndorsementRows = [
694 width: var(--s);605 width: var(--s);
695 height: var(--s);606 height: var(--s);
696 margin-left: calc(var(--s) / -2);607 margin-left: calc(var(--s) / -2);
697 translate: calc(var(--px) * var(--depth)) calc(var(--py) * var(--depth));
698 transition: translate 0.7s cubic-bezier(0.2, 0.7, 0.2, 1);
699 }608 }
700609
701 .hero-float-card {610 .hero-float-card {
...@@ -709,9 +618,7 @@ const mobileEndorsementRows = [...@@ -709,9 +618,7 @@ const mobileEndorsementRows = [
709 box-shadow:618 box-shadow:
710 0 20px 48px -16px rgba(0, 0, 0, 0.65),619 0 20px 48px -16px rgba(0, 0, 0, 0.65),
711 0 2px 0 0 rgba(255, 255, 255, 0.04) inset;620 0 2px 0 0 rgba(255, 255, 255, 0.04) inset;
712 animation:621 animation: mark-in 1s cubic-bezier(0.2, 0.7, 0.2, 1) var(--d) both;
713 mark-in 1s cubic-bezier(0.2, 0.7, 0.2, 1) var(--d) both,
714 mark-drift var(--dur) ease-in-out calc(var(--d) + 1s) infinite;
715 }622 }
716623
717 .hero-float-card img {624 .hero-float-card img {
...@@ -730,21 +637,16 @@ const mobileEndorsementRows = [...@@ -730,21 +637,16 @@ const mobileEndorsementRows = [
730 from { opacity: 0; transform: translate(var(--fx), var(--fy)) rotate(calc(var(--rot) + 24deg)) scale(0.6); }637 from { opacity: 0; transform: translate(var(--fx), var(--fy)) rotate(calc(var(--rot) + 24deg)) scale(0.6); }
731 to { opacity: 1; transform: translate(0, 0) rotate(var(--rot)) scale(1); }638 to { opacity: 1; transform: translate(0, 0) rotate(var(--rot)) scale(1); }
732 }639 }
733 @keyframes mark-drift {
734 0%, 100% { transform: translate(0, 0) rotate(var(--rot)); }
735 50% { transform: translate(var(--dx), var(--dy)) rotate(calc(var(--rot) + var(--wob))); }
736 }
737640
738 .hf-opencode { --x: max(-300px, -19vw); --y: 20px; --s: 64px; --rot: 6deg; --depth: 0.6; --d: 620ms; --dur: 8.5s; --dx: 2px; --dy: -8px; --wob: 3deg; --fx: -40px; --fy: -80px; }641 .hf-opencode { --x: max(-300px, -19vw); --y: 20px; --s: 64px; --rot: 6deg; --d: 620ms; --fx: -40px; --fy: -80px; }
739 .hf-antigravity { --x: min(300px, 19vw); --y: 20px; --s: 64px; --rot: -5deg; --depth: 0.6; --d: 580ms; --dur: 9.8s; --dx: -2px; --dy: -9px; --wob: -3deg; --fx: 40px; --fy: -80px; }642 .hf-antigravity { --x: min(300px, 19vw); --y: 20px; --s: 64px; --rot: -5deg; --d: 580ms; --fx: 40px; --fy: -80px; }
740 .hf-claude { --x: max(-560px, -38vw); --y: 128px; --s: 96px; --rot: -8deg; --depth: 1.2; --d: 380ms; --dur: 10.5s; --dx: 4px; --dy: -12px; --wob: -2deg; --fx: -160px; --fy: -40px; --tint: rgba(217, 119, 87, 0.22); }643 .hf-claude { --x: max(-560px, -38vw); --y: 128px; --s: 96px; --rot: -8deg; --d: 380ms; --fx: -160px; --fy: -40px; --tint: rgba(217, 119, 87, 0.22); }
741 .hf-codex { --x: min(560px, 38vw); --y: 128px; --s: 96px; --rot: 7deg; --depth: 1.2; --d: 460ms; --dur: 9.2s; --dx: -4px; --dy: -10px; --wob: 2deg; --fx: 160px; --fy: -40px; }644 .hf-codex { --x: min(560px, 38vw); --y: 128px; --s: 96px; --rot: 7deg; --d: 460ms; --fx: 160px; --fy: -40px; }
742 .hf-grok { --x: max(-440px, -30vw); --y: 440px; --s: 80px; --rot: 4deg; --depth: 0.9; --d: 540ms; --dur: 11.5s; --dx: 3px; --dy: -9px; --wob: -3deg; --fx: -120px; --fy: 80px; }645 .hf-grok { --x: max(-440px, -30vw); --y: 440px; --s: 80px; --rot: 4deg; --d: 540ms; --fx: -120px; --fy: 80px; }
743 .hf-cursor { --x: min(440px, 30vw); --y: 440px; --s: 80px; --rot: -5deg; --depth: 0.9; --d: 700ms; --dur: 7.8s; --dx: -3px; --dy: -11px; --wob: 3deg; --fx: 120px; --fy: 80px; }646 .hf-cursor { --x: min(440px, 30vw); --y: 440px; --s: 80px; --rot: -5deg; --d: 700ms; --fx: 120px; --fy: 80px; }
744647
745 @media (prefers-reduced-motion: reduce) {648 @media (prefers-reduced-motion: reduce) {
746 .hero-float-card { animation: none; transform: rotate(var(--rot)); }649 .hero-float-card { animation: none; transform: rotate(var(--rot)); }
747 .hero-float-mark { transition: none; }
748 }650 }
749651
750 @media (max-width: 1180px) {652 @media (max-width: 1180px) {
...@@ -767,12 +669,12 @@ const mobileEndorsementRows = [...@@ -767,12 +669,12 @@ const mobileEndorsementRows = [
767 }669 }
768670
769 /* Four above the headline, two beside the CTA. Keeps the copy clear. */671 /* Four above the headline, two beside the CTA. Keeps the copy clear. */
770 .hf-claude { --x: -37vw; --y: 44px; --s: 64px; --rot: -10deg; }672 .hf-claude { --x: -37vw; --y: 44px; --s: 64px; --rot: -10deg; }
771 .hf-opencode { --x: -12vw; --y: 52px; --s: 56px; --rot: -4deg; }673 .hf-opencode { --x: -12vw; --y: 52px; --s: 56px; --rot: -4deg; }
772 .hf-antigravity { --x: 12vw; --y: 52px; --s: 56px; --rot: 5deg; }674 .hf-antigravity { --x: 12vw; --y: 52px; --s: 56px; --rot: 5deg; }
773 .hf-codex { --x: 37vw; --y: 44px; --s: 64px; --rot: 8deg; }675 .hf-codex { --x: 37vw; --y: 44px; --s: 64px; --rot: 8deg; }
774 .hf-grok { --x: -36vw; --y: 474px; --s: 64px; --rot: -6deg; }676 .hf-grok { --x: -36vw; --y: 474px; --s: 64px; --rot: -6deg; }
775 .hf-cursor { --x: 36vw; --y: 474px; --s: 64px; --rot: 7deg; }677 .hf-cursor { --x: 36vw; --y: 474px; --s: 64px; --rot: 7deg; }
776 }678 }
777679
778 @media (max-width: 340px) {680 @media (max-width: 340px) {
...@@ -896,67 +798,19 @@ const mobileEndorsementRows = [...@@ -896,67 +798,19 @@ const mobileEndorsementRows = [
896 }798 }
897799
898 .endorsement-carousel {800 .endorsement-carousel {
899 position: relative;801 display: grid;
900 display: flex;802 grid-auto-flow: column;
901 flex-direction: column;803 grid-template-rows: repeat(2, 132px);
902 gap: 12px;804 grid-auto-columns: clamp(264px, 22vw, 308px);
903 }
904
905 .endorsement-carousel--mobile {
906 display: none;
907 }
908
909 .endorsement-carousel::before,
910 .endorsement-carousel::after {
911 content: "";
912 position: absolute;
913 top: 0;
914 bottom: 0;
915 z-index: 2;
916 width: max(48px, calc((100vw - 1240px) / 2 + 56px));
917 pointer-events: none;
918 }
919
920 .endorsement-carousel::before {
921 left: 0;
922 background: linear-gradient(90deg, var(--bg), transparent);
923 }
924
925 .endorsement-carousel::after {
926 right: 0;
927 background: linear-gradient(270deg, var(--bg), transparent);
928 }
929
930 .endorsement-row {
931 overflow: hidden;
932 }
933
934 .endorsement-track {
935 width: max-content;
936 display: flex;
937 animation: endorsementMarquee 76s linear infinite;
938 will-change: transform;
939 }
940
941 .endorsement-track-group {
942 display: flex;
943 gap: 12px;805 gap: 12px;
944 padding-right: 12px;806 overflow-x: auto;
945 }807 overscroll-behavior-x: contain;
946808 padding: 4px max(32px, calc((100vw - 1240px) / 2 + 32px));
947 .endorsement-row.is-reverse .endorsement-track {809 scroll-padding-inline: max(32px, calc((100vw - 1240px) / 2 + 32px));
948 animation-direction: reverse;
949 animation-duration: 84s;
950 }
951
952 .endorsement-carousel:hover .endorsement-track,
953 .endorsement-carousel:focus-within .endorsement-track {
954 animation-play-state: paused;
955 }810 }
956811
957 .endorsement-card {812 .endorsement-card {
958 position: relative;813 position: relative;
959 width: clamp(264px, 22vw, 308px);
960 height: 132px;814 height: 132px;
961 padding: 14px 16px;815 padding: 14px 16px;
962 display: flex;816 display: flex;
...@@ -1134,7 +988,6 @@ const mobileEndorsementRows = [...@@ -1134,7 +988,6 @@ const mobileEndorsementRows = [
1134 width: 7px; height: 14px;988 width: 7px; height: 14px;
1135 background: var(--fg);989 background: var(--fg);
1136 margin-left: 4px;990 margin-left: 4px;
1137 animation: blink 1s steps(2) infinite;
1138 }991 }
1139992
1140 .open-pitch {993 .open-pitch {
...@@ -1252,19 +1105,6 @@ const mobileEndorsementRows = [...@@ -1252,19 +1105,6 @@ const mobileEndorsementRows = [
1252 margin-bottom: 24px;1105 margin-bottom: 24px;
1253 }1106 }
12541107
1255 @keyframes blink {
1256 50% { opacity: 0; }
1257 }
1258
1259 @keyframes endorsementMarquee {
1260 from {
1261 transform: translateX(0);
1262 }
1263 to {
1264 transform: translateX(-50%);
1265 }
1266 }
1267
1268 /* ── Responsive ────────────────────────────────────────── */1108 /* ── Responsive ────────────────────────────────────────── */
1269 @media (max-width: 960px) {1109 @media (max-width: 960px) {
1270 .hero-preview {1110 .hero-preview {
...@@ -1291,25 +1131,13 @@ const mobileEndorsementRows = [...@@ -1291,25 +1131,13 @@ const mobileEndorsementRows = [
1291 .endorsements-head {1131 .endorsements-head {
1292 margin-bottom: 40px;1132 margin-bottom: 40px;
1293 }1133 }
1294 .endorsement-carousel--desktop {1134 .endorsement-carousel {
1295 display: none;1135 grid-template-rows: repeat(3, 132px);
1296 }1136 grid-auto-columns: 268px;
1297 .endorsement-carousel--mobile {1137 padding-inline: 20px;
1298 display: flex;1138 scroll-padding-inline: 20px;
1299 }
1300 .endorsement-carousel::before,
1301 .endorsement-carousel::after {
1302 width: 28px;
1303 }
1304 .endorsement-track {
1305 animation-duration: 68s;
1306 }
1307 .endorsement-row.is-reverse .endorsement-track {
1308 animation-duration: 76s;
1309 }1139 }
1310 .endorsement-card {1140 .endorsement-card {
1311 width: 268px;
1312 height: 132px;
1313 padding: 14px;1141 padding: 14px;
1314 }1142 }
1315 .endorsement-card p {1143 .endorsement-card p {