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({
1717 quality: 90,
1818});
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];
3020---
3121
3222<Layout>
......@@ -121,80 +111,29 @@ const mobileEndorsementRows = [
121111 </div>
122112 </div>
123113
124 <div class="endorsement-carousel endorsement-carousel--desktop" aria-label="Developer endorsements">
125 {
126 desktopEndorsementRows.map((row, rowIndex) => (
127 <div class:list={["endorsement-row", rowIndex === 1 && "is-reverse"]}>
128 <div class="endorsement-track">
129 {[0, 1].map((groupIndex) => (
130 <div class="endorsement-track-group" aria-hidden={groupIndex === 1 ? "true" : undefined}>
131 {row.map((tweet) => (
132 <a
133 class="endorsement-card"
134 href={tweet.link}
135 target="_blank"
136 rel="noopener noreferrer"
137 aria-label={`Read ${tweet.handle}'s post on X`}
138 tabindex={groupIndex === 1 ? "-1" : undefined}
139 >
140 <div class="endorsement-author">
141 <img
142 src={`/pfps/${tweet.handle}.webp`}
143 alt=""
144 width="28"
145 height="28"
146 loading="lazy"
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 }
114 <div class="endorsement-carousel" role="region" aria-label="Developer endorsements" tabindex="0">
115 {tweets.map((tweet) => (
116 <a
117 class="endorsement-card"
118 href={tweet.link}
119 target="_blank"
120 rel="noopener noreferrer"
121 aria-label={`Read ${tweet.handle}'s post on X`}
122 >
123 <div class="endorsement-author">
124 <img
125 src={`/pfps/${tweet.handle}.webp`}
126 alt=""
127 width="28"
128 height="28"
129 loading="lazy"
130 decoding="async"
131 />
132 <div class="endorsement-handle">@{tweet.handle}</div>
133 </div>
134 <p>{tweet.excerpt ?? tweet.content}</p>
135 </a>
136 ))}
198137 </div>
199138 </section>
200139
......@@ -480,28 +419,6 @@ const mobileEndorsementRows = [
480419 }
481420
482421 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 }
505422</script>
506423
507424<style>
......@@ -673,18 +590,12 @@ const mobileEndorsementRows = [
673590 :global([data-platform="linux"]) .dl-icon--linux { display: block; }
674591 :global(html:not([data-platform])) .dl-icon--apple { display: block; }
675592
676 /* Floating harness marks. Six points on an ellipse around the hero copy:
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. */
593 /* Harness marks stay in their final positions after the entry animation. */
681594 .hero-float {
682595 position: absolute;
683596 inset: 0;
684597 pointer-events: none;
685598 z-index: 1;
686 --px: 0px;
687 --py: 0px;
688599 }
689600
690601 .hero-float-mark {
......@@ -694,8 +605,6 @@ const mobileEndorsementRows = [
694605 width: var(--s);
695606 height: var(--s);
696607 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);
699608 }
700609
701610 .hero-float-card {
......@@ -709,9 +618,7 @@ const mobileEndorsementRows = [
709618 box-shadow:
710619 0 20px 48px -16px rgba(0, 0, 0, 0.65),
711620 0 2px 0 0 rgba(255, 255, 255, 0.04) inset;
712 animation:
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;
621 animation: mark-in 1s cubic-bezier(0.2, 0.7, 0.2, 1) var(--d) both;
715622 }
716623
717624 .hero-float-card img {
......@@ -730,21 +637,16 @@ const mobileEndorsementRows = [
730637 from { opacity: 0; transform: translate(var(--fx), var(--fy)) rotate(calc(var(--rot) + 24deg)) scale(0.6); }
731638 to { opacity: 1; transform: translate(0, 0) rotate(var(--rot)) scale(1); }
732639 }
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; }
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; }
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); }
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; }
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; }
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; }
641 .hf-opencode { --x: max(-300px, -19vw); --y: 20px; --s: 64px; --rot: 6deg; --d: 620ms; --fx: -40px; --fy: -80px; }
642 .hf-antigravity { --x: min(300px, 19vw); --y: 20px; --s: 64px; --rot: -5deg; --d: 580ms; --fx: 40px; --fy: -80px; }
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); }
644 .hf-codex { --x: min(560px, 38vw); --y: 128px; --s: 96px; --rot: 7deg; --d: 460ms; --fx: 160px; --fy: -40px; }
645 .hf-grok { --x: max(-440px, -30vw); --y: 440px; --s: 80px; --rot: 4deg; --d: 540ms; --fx: -120px; --fy: 80px; }
646 .hf-cursor { --x: min(440px, 30vw); --y: 440px; --s: 80px; --rot: -5deg; --d: 700ms; --fx: 120px; --fy: 80px; }
744647
745648 @media (prefers-reduced-motion: reduce) {
746649 .hero-float-card { animation: none; transform: rotate(var(--rot)); }
747 .hero-float-mark { transition: none; }
748650 }
749651
750652 @media (max-width: 1180px) {
......@@ -767,12 +669,12 @@ const mobileEndorsementRows = [
767669 }
768670
769671 /* Four above the headline, two beside the CTA. Keeps the copy clear. */
770 .hf-claude { --x: -37vw; --y: 44px; --s: 64px; --rot: -10deg; }
771 .hf-opencode { --x: -12vw; --y: 52px; --s: 56px; --rot: -4deg; }
772 .hf-antigravity { --x: 12vw; --y: 52px; --s: 56px; --rot: 5deg; }
773 .hf-codex { --x: 37vw; --y: 44px; --s: 64px; --rot: 8deg; }
672 .hf-claude { --x: -37vw; --y: 44px; --s: 64px; --rot: -10deg; }
673 .hf-opencode { --x: -12vw; --y: 52px; --s: 56px; --rot: -4deg; }
674 .hf-antigravity { --x: 12vw; --y: 52px; --s: 56px; --rot: 5deg; }
675 .hf-codex { --x: 37vw; --y: 44px; --s: 64px; --rot: 8deg; }
774676 .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; }
776678 }
777679
778680 @media (max-width: 340px) {
......@@ -896,67 +798,19 @@ const mobileEndorsementRows = [
896798 }
897799
898800 .endorsement-carousel {
899 position: relative;
900 display: flex;
901 flex-direction: column;
902 gap: 12px;
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;
801 display: grid;
802 grid-auto-flow: column;
803 grid-template-rows: repeat(2, 132px);
804 grid-auto-columns: clamp(264px, 22vw, 308px);
943805 gap: 12px;
944 padding-right: 12px;
945 }
946
947 .endorsement-row.is-reverse .endorsement-track {
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;
806 overflow-x: auto;
807 overscroll-behavior-x: contain;
808 padding: 4px max(32px, calc((100vw - 1240px) / 2 + 32px));
809 scroll-padding-inline: max(32px, calc((100vw - 1240px) / 2 + 32px));
955810 }
956811
957812 .endorsement-card {
958813 position: relative;
959 width: clamp(264px, 22vw, 308px);
960814 height: 132px;
961815 padding: 14px 16px;
962816 display: flex;
......@@ -1134,7 +988,6 @@ const mobileEndorsementRows = [
1134988 width: 7px; height: 14px;
1135989 background: var(--fg);
1136990 margin-left: 4px;
1137 animation: blink 1s steps(2) infinite;
1138991 }
1139992
1140993 .open-pitch {
......@@ -1252,19 +1105,6 @@ const mobileEndorsementRows = [
12521105 margin-bottom: 24px;
12531106 }
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
12681108 /* ── Responsive ────────────────────────────────────────── */
12691109 @media (max-width: 960px) {
12701110 .hero-preview {
......@@ -1291,25 +1131,13 @@ const mobileEndorsementRows = [
12911131 .endorsements-head {
12921132 margin-bottom: 40px;
12931133 }
1294 .endorsement-carousel--desktop {
1295 display: none;
1296 }
1297 .endorsement-carousel--mobile {
1298 display: flex;
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;
1134 .endorsement-carousel {
1135 grid-template-rows: repeat(3, 132px);
1136 grid-auto-columns: 268px;
1137 padding-inline: 20px;
1138 scroll-padding-inline: 20px;
13091139 }
13101140 .endorsement-card {
1311 width: 268px;
1312 height: 132px;
13131141 padding: 14px;
13141142 }
13151143 .endorsement-card p {