| ... | ... | @@ -26,34 +26,31 @@ const mobileEndorsementRows = [ |
| 26 | 26 | <div class="hero-grid" aria-hidden="true"></div> |
| 27 | 27 | |
| 28 | 28 | <div class="hero-float" aria-hidden="true"> |
| 29 | | <span class="hero-float-mark hf-claude" title="Claude Code"> |
| 30 | | <img src="/harnesses/claude-ai-icon.svg" alt="" /> |
| 31 | | </span> |
| 32 | | <span class="hero-float-mark hf-codex" title="Codex"> |
| 33 | | <img src="/harnesses/openai_dark.svg" alt="" /> |
| 34 | | </span> |
| 35 | | <span class="hero-float-mark hf-opencode" title="OpenCode"> |
| 36 | | <img src="/harnesses/opencode-dark.svg" alt="" /> |
| 37 | | </span> |
| 38 | | <span class="hero-float-mark hf-cursor" title="Cursor"> |
| 39 | | <img src="/harnesses/cursor_light.svg" alt="" /> |
| 40 | | </span> |
| 41 | | <span class="hero-float-mark hf-grok" title="Grok"> |
| 42 | | <img src="/harnesses/grok-dark.svg" alt="" /> |
| 43 | | </span> |
| 29 | {[ |
| 30 | { key: "opencode", icon: "/harnesses/opencode-dark.svg", cls: "hf-opencode" }, |
| 31 | { key: "antigravity", icon: "/harnesses/antigravity.png", cls: "hf-antigravity" }, |
| 32 | { key: "claude", icon: "/harnesses/claude-ai-icon.svg", cls: "hf-claude" }, |
| 33 | { key: "codex", icon: "/harnesses/openai_dark.svg", cls: "hf-codex" }, |
| 34 | { key: "grok", icon: "/harnesses/grok-dark.svg", cls: "hf-grok" }, |
| 35 | { key: "cursor", icon: "/harnesses/cursor_light.svg", cls: "hf-cursor" }, |
| 36 | ].map((m) => ( |
| 37 | <span class:list={["hero-float-mark", m.cls]}> |
| 38 | <span class="hero-float-card"><img src={m.icon} alt="" /></span> |
| 39 | </span> |
| 40 | ))} |
| 44 | 41 | </div> |
| 45 | 42 | |
| 46 | 43 | <div class="container hero-inner"> |
| 47 | 44 | <h1 class="hero-title display"> |
| 48 | | The open-source<br />control plane<br />for coding agents. |
| 45 | <span data-rise>The open-source</span><br /><span data-rise style="--d:90ms">control plane</span><br /><span data-rise style="--d:180ms">for coding agents.</span> |
| 49 | 46 | </h1> |
| 50 | 47 | |
| 51 | | <p class="hero-sub"> |
| 52 | | Orchestrate Claude Code, Codex, OpenCode, Cursor, and Grok from one surface. |
| 48 | <p class="hero-sub" data-rise style="--d:300ms"> |
| 49 | Orchestrate Claude Code, Codex, Antigravity, OpenCode, Cursor, and Grok from one surface. |
| 53 | 50 | Bring your own subscription. Fork the whole thing. |
| 54 | 51 | </p> |
| 55 | 52 | |
| 56 | | <div class="hero-actions"> |
| 53 | <div class="hero-actions" data-rise style="--d:400ms"> |
| 57 | 54 | <a |
| 58 | 55 | id="download-btn" |
| 59 | 56 | href="https://github.com/pingdotgg/t3code/releases" |
| ... | ... | @@ -79,17 +76,20 @@ const mobileEndorsementRows = [ |
| 79 | 76 | </p> |
| 80 | 77 | </div> |
| 81 | 78 | |
| 82 | | <div class="hero-preview"> |
| 83 | | <figure class="hero-screenshot-frame"> |
| 84 | | <img |
| 85 | | src="/updated-screenshot.webp" |
| 86 | | alt="T3 Code app showing multiple coding agent threads in one workspace" |
| 87 | | width="2508" |
| 88 | | height="1682" |
| 89 | | loading="eager" |
| 90 | | decoding="async" |
| 91 | | /> |
| 92 | | </figure> |
| 79 | <!-- The rise wrapper is separate so its animated transform does not override the preview tilt. --> |
| 80 | <div data-rise style="--d:520ms"> |
| 81 | <div class="hero-preview"> |
| 82 | <figure class="hero-screenshot-frame"> |
| 83 | <img |
| 84 | src="/app-desktop.webp" |
| 85 | alt="T3 Code with a settled thread beside its pull request diff" |
| 86 | width="2880" |
| 87 | height="1800" |
| 88 | loading="eager" |
| 89 | decoding="async" |
| 90 | /> |
| 91 | </figure> |
| 92 | </div> |
| 93 | 93 | </div> |
| 94 | 94 | </div> |
| 95 | 95 | </section> |
| ... | ... | @@ -186,9 +186,9 @@ const mobileEndorsementRows = [ |
| 186 | 186 | <div class="section-head"> |
| 187 | 187 | <h2>Bring your own sub</h2> |
| 188 | 188 | <p> |
| 189 | | T3 Code doesn't resell tokens. Plug in Claude Code, Codex, OpenCode, |
| 190 | | Cursor, or Grok with the credentials you already have — we orchestrate |
| 191 | | them, you keep your plan. |
| 189 | T3 Code doesn't resell tokens. Plug in Claude Code, Codex, Antigravity, |
| 190 | OpenCode, Cursor, or Grok with the credentials you already have. We |
| 191 | orchestrate them, you keep your plan. |
| 192 | 192 | </p> |
| 193 | 193 | </div> |
| 194 | 194 | |
| ... | ... | @@ -228,6 +228,13 @@ const mobileEndorsementRows = [ |
| 228 | 228 | <div class="harness-tag">grok login</div> |
| 229 | 229 | </div> |
| 230 | 230 | </div> |
| 231 | <div class="harness"> |
| 232 | <div class="harness-mark"><img src="/harnesses/antigravity.png" alt="" /></div> |
| 233 | <div class="harness-meta"> |
| 234 | <div class="harness-name">Antigravity</div> |
| 235 | <div class="harness-tag">Google sign-in</div> |
| 236 | </div> |
| 237 | </div> |
| 231 | 238 | </div> |
| 232 | 239 | |
| 233 | 240 | <div class="harness-footer"> |
| ... | ... | @@ -455,6 +462,28 @@ const mobileEndorsementRows = [ |
| 455 | 462 | } |
| 456 | 463 | |
| 457 | 464 | init(); |
| 465 | |
| 466 | // Marks lean a few pixels toward the pointer, each by its own depth. |
| 467 | const hero = document.querySelector<HTMLElement>(".hero"); |
| 468 | const field = document.querySelector<HTMLElement>(".hero-float"); |
| 469 | const fine = window.matchMedia("(pointer: fine) and (prefers-reduced-motion: no-preference)").matches; |
| 470 | if (hero && field && fine) { |
| 471 | let raf = 0; |
| 472 | let nx = 0; |
| 473 | let ny = 0; |
| 474 | const apply = () => { |
| 475 | raf = 0; |
| 476 | field.style.setProperty("--px", `${(nx * 18).toFixed(1)}px`); |
| 477 | field.style.setProperty("--py", `${(ny * 14).toFixed(1)}px`); |
| 478 | }; |
| 479 | hero.addEventListener("pointermove", (e) => { |
| 480 | const r = hero.getBoundingClientRect(); |
| 481 | nx = ((e.clientX - r.left) / r.width - 0.5) * 2; |
| 482 | ny = ((e.clientY - r.top) / r.height - 0.5) * 2; |
| 483 | if (!raf) raf = requestAnimationFrame(apply); |
| 484 | }); |
| 485 | hero.addEventListener("pointerleave", () => { nx = 0; ny = 0; if (!raf) raf = requestAnimationFrame(apply); }); |
| 486 | } |
| 458 | 487 | </script> |
| 459 | 488 | |
| 460 | 489 | <style> |
| ... | ... | @@ -541,6 +570,8 @@ const mobileEndorsementRows = [ |
| 541 | 570 | max-width: 20ch; |
| 542 | 571 | text-wrap: balance; |
| 543 | 572 | } |
| 573 | /* Inline boxes ignore transforms, so each rising line needs its own block. */ |
| 574 | .hero-title span { display: inline-block; } |
| 544 | 575 | |
| 545 | 576 | .hero-sub { |
| 546 | 577 | color: var(--fg-muted); |
| ... | ... | @@ -624,52 +655,83 @@ const mobileEndorsementRows = [ |
| 624 | 655 | :global([data-platform="linux"]) .dl-icon--linux { display: block; } |
| 625 | 656 | :global(html:not([data-platform])) .dl-icon--apple { display: block; } |
| 626 | 657 | |
| 627 | | /* Floating harness marks */ |
| 658 | /* Floating harness marks. Six points on an ellipse around the hero copy: |
| 659 | two small at the top, two high and wide, two low and close. Symmetric, |
| 660 | and none of them touch the screenshot. Each mark flies in from its own |
| 661 | side on load, then drifts on its own period, and the whole set leans a |
| 662 | few pixels toward the pointer. */ |
| 628 | 663 | .hero-float { |
| 629 | 664 | position: absolute; |
| 630 | 665 | inset: 0; |
| 631 | 666 | pointer-events: none; |
| 632 | 667 | z-index: 1; |
| 668 | --px: 0px; |
| 669 | --py: 0px; |
| 633 | 670 | } |
| 634 | 671 | |
| 635 | 672 | .hero-float-mark { |
| 636 | 673 | position: absolute; |
| 637 | | width: 96px; |
| 638 | | height: 96px; |
| 639 | | border-radius: 24px; |
| 674 | left: calc(50% + var(--x)); |
| 675 | top: var(--y); |
| 676 | width: var(--s); |
| 677 | height: var(--s); |
| 678 | margin-left: calc(var(--s) / -2); |
| 679 | translate: calc(var(--px) * var(--depth)) calc(var(--py) * var(--depth)); |
| 680 | transition: translate 0.7s cubic-bezier(0.2, 0.7, 0.2, 1); |
| 681 | } |
| 682 | |
| 683 | .hero-float-card { |
| 640 | 684 | display: grid; |
| 641 | 685 | place-items: center; |
| 642 | | background: rgba(20, 20, 24, 0.9); |
| 686 | width: 100%; |
| 687 | height: 100%; |
| 688 | border-radius: 25%; |
| 689 | background: radial-gradient(circle at 30% 25%, var(--tint, rgba(255, 255, 255, 0.08)), rgba(20, 20, 24, 0.92) 65%); |
| 643 | 690 | border: 1px solid var(--border); |
| 644 | | backdrop-filter: blur(10px); |
| 645 | | -webkit-backdrop-filter: blur(10px); |
| 646 | 691 | box-shadow: |
| 647 | 692 | 0 20px 48px -16px rgba(0, 0, 0, 0.65), |
| 648 | 693 | 0 2px 0 0 rgba(255, 255, 255, 0.04) inset; |
| 649 | | animation: floatDrift 9s ease-in-out infinite; |
| 694 | animation: |
| 695 | mark-in 1s cubic-bezier(0.2, 0.7, 0.2, 1) var(--d) both, |
| 696 | mark-drift var(--dur) ease-in-out calc(var(--d) + 1s) infinite; |
| 650 | 697 | } |
| 651 | 698 | |
| 652 | | .hero-float-mark img { |
| 653 | | width: 54px; |
| 654 | | height: 54px; |
| 699 | .hero-float-card img { |
| 700 | width: 56%; |
| 701 | height: 56%; |
| 655 | 702 | display: block; |
| 656 | 703 | object-fit: contain; |
| 657 | 704 | } |
| 658 | 705 | |
| 659 | | .hero-float-mark.hf-claude { background: radial-gradient(circle at 30% 25%, rgba(217, 119, 87, 0.22), rgba(20, 20, 24, 0.92) 65%); top: 8%; left: 7%; animation-delay: 0s; transform: rotate(-8deg); } |
| 660 | | .hero-float-mark.hf-codex { background: radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.08), rgba(20, 20, 24, 0.92) 65%); top: 6%; right: 7%; animation-delay: -2.5s; transform: rotate(6deg); } |
| 661 | | .hero-float-mark.hf-opencode { background: radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.06), rgba(20, 20, 24, 0.92) 65%); top: 30%; left: 5%; animation-delay: -5s; transform: rotate(4deg); } |
| 662 | | .hero-float-mark.hf-cursor { background: radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.08), rgba(20, 20, 24, 0.92) 65%); top: 30%; right: 5%; animation-delay: -7s; transform: rotate(-5deg); } |
| 663 | | .hero-float-mark.hf-grok { background: radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.08), rgba(20, 20, 24, 0.92) 65%); top: 52%; left: 6%; animation-delay: -1.2s; transform: rotate(3deg); } |
| 706 | /* The Antigravity icon ships with its own rounded dark tile, so it fills the |
| 707 | card edge to edge instead of sitting inside it. */ |
| 708 | .hf-antigravity .hero-float-card { background: #0d0d10; border-color: rgba(255, 255, 255, 0.1); } |
| 709 | .hf-antigravity .hero-float-card img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; } |
| 710 | |
| 711 | @keyframes mark-in { |
| 712 | from { opacity: 0; transform: translate(var(--fx), var(--fy)) rotate(calc(var(--rot) + 24deg)) scale(0.6); } |
| 713 | to { opacity: 1; transform: translate(0, 0) rotate(var(--rot)) scale(1); } |
| 714 | } |
| 715 | @keyframes mark-drift { |
| 716 | 0%, 100% { transform: translate(0, 0) rotate(var(--rot)); } |
| 717 | 50% { transform: translate(var(--dx), var(--dy)) rotate(calc(var(--rot) + var(--wob))); } |
| 718 | } |
| 719 | |
| 720 | .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; } |
| 721 | .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; } |
| 722 | .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); } |
| 723 | .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; } |
| 724 | .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; } |
| 725 | .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; } |
| 726 | |
| 727 | @media (prefers-reduced-motion: reduce) { |
| 728 | .hero-float-card { animation: none; transform: rotate(var(--rot)); } |
| 729 | .hero-float-mark { transition: none; } |
| 730 | } |
| 664 | 731 | |
| 665 | 732 | @media (max-width: 1180px) { |
| 666 | | .hero-float-mark { width: 76px; height: 76px; border-radius: 20px; } |
| 667 | | .hero-float-mark img { width: 42px; height: 42px; } |
| 668 | | .hero-float-mark.hf-claude { top: 4%; left: 3%; } |
| 669 | | .hero-float-mark.hf-codex { top: 2%; right: 3%; } |
| 670 | | .hero-float-mark.hf-opencode { top: 26%; left: 2%; } |
| 671 | | .hero-float-mark.hf-cursor { top: 24%; right: 2%; } |
| 672 | | .hero-float-mark.hf-grok { top: 48%; left: 2%; transform: rotate(3deg); } |
| 733 | .hf-claude, .hf-codex { --s: 80px; } |
| 734 | .hf-grok, .hf-cursor { --s: 68px; --y: 420px; } |
| 673 | 735 | } |
| 674 | 736 | |
| 675 | 737 | @media (max-width: 820px) { |
| ... | ... | @@ -686,90 +748,18 @@ const mobileEndorsementRows = [ |
| 686 | 748 | margin-bottom: 96px; |
| 687 | 749 | } |
| 688 | 750 | |
| 689 | | .hero-float { |
| 690 | | position: absolute; |
| 691 | | inset: 0; |
| 692 | | display: block; |
| 693 | | } |
| 694 | | |
| 695 | | .hero-float-mark { |
| 696 | | position: absolute; |
| 697 | | width: 78px; |
| 698 | | height: 78px; |
| 699 | | border-radius: 20px; |
| 700 | | } |
| 701 | | |
| 702 | | .hero-float-mark img { |
| 703 | | width: 44px; |
| 704 | | height: 44px; |
| 705 | | } |
| 706 | | |
| 707 | | /* Three above the headline, two beside the CTA — keeps the copy clear. */ |
| 708 | | .hero-float-mark.hf-claude { |
| 709 | | top: 44px; |
| 710 | | left: 10px; |
| 711 | | transform: rotate(-10deg); |
| 712 | | } |
| 713 | | |
| 714 | | .hero-float-mark.hf-grok { |
| 715 | | top: 44px; |
| 716 | | left: calc(50% - 39px); |
| 717 | | right: auto; |
| 718 | | transform: rotate(-4deg); |
| 719 | | } |
| 720 | | |
| 721 | | .hero-float-mark.hf-opencode { |
| 722 | | top: 474px; |
| 723 | | left: 4px; |
| 724 | | right: auto; |
| 725 | | transform: rotate(-6deg); |
| 726 | | } |
| 727 | | |
| 728 | | .hero-float-mark.hf-codex { |
| 729 | | top: 44px; |
| 730 | | right: 10px; |
| 731 | | transform: rotate(8deg); |
| 732 | | } |
| 733 | | |
| 734 | | .hero-float-mark.hf-cursor { |
| 735 | | top: 474px; |
| 736 | | right: 4px; |
| 737 | | left: auto; |
| 738 | | transform: rotate(7deg); |
| 739 | | } |
| 751 | /* Four above the headline, two beside the CTA. Keeps the copy clear. */ |
| 752 | .hf-claude { --x: -37vw; --y: 44px; --s: 64px; --rot: -10deg; } |
| 753 | .hf-opencode { --x: -12vw; --y: 52px; --s: 56px; --rot: -4deg; } |
| 754 | .hf-antigravity { --x: 12vw; --y: 52px; --s: 56px; --rot: 5deg; } |
| 755 | .hf-codex { --x: 37vw; --y: 44px; --s: 64px; --rot: 8deg; } |
| 756 | .hf-grok { --x: -36vw; --y: 474px; --s: 64px; --rot: -6deg; } |
| 757 | .hf-cursor { --x: 36vw; --y: 474px; --s: 64px; --rot: 7deg; } |
| 740 | 758 | } |
| 741 | 759 | |
| 742 | 760 | @media (max-width: 340px) { |
| 743 | | .hero-float-mark.hf-grok { |
| 744 | | top: 57px; |
| 745 | | left: calc(50% - 26px); |
| 746 | | width: 52px; |
| 747 | | height: 52px; |
| 748 | | border-radius: 14px; |
| 749 | | } |
| 750 | | |
| 751 | | .hero-float-mark.hf-opencode, |
| 752 | | .hero-float-mark.hf-cursor { |
| 753 | | top: 580px; |
| 754 | | width: 52px; |
| 755 | | height: 52px; |
| 756 | | border-radius: 14px; |
| 757 | | } |
| 758 | | |
| 759 | | .hero-float-mark.hf-opencode { |
| 760 | | left: 0; |
| 761 | | } |
| 762 | | |
| 763 | | .hero-float-mark.hf-cursor { |
| 764 | | right: 0; |
| 765 | | } |
| 766 | | |
| 767 | | .hero-float-mark.hf-grok img, |
| 768 | | .hero-float-mark.hf-opencode img, |
| 769 | | .hero-float-mark.hf-cursor img { |
| 770 | | width: 30px; |
| 771 | | height: 30px; |
| 772 | | } |
| 761 | .hf-opencode, .hf-antigravity, .hf-grok, .hf-cursor { --s: 48px; } |
| 762 | .hf-grok, .hf-cursor { --y: 580px; } |
| 773 | 763 | } |
| 774 | 764 | |
| 775 | 765 | .hero-preview { |
| ... | ... | @@ -786,7 +776,7 @@ const mobileEndorsementRows = [ |
| 786 | 776 | .hero-screenshot-frame { |
| 787 | 777 | position: relative; |
| 788 | 778 | overflow: hidden; |
| 789 | | aspect-ratio: 2508 / 1682; |
| 779 | aspect-ratio: 2880 / 1800; |
| 790 | 780 | border-radius: 14px; |
| 791 | 781 | background: #0a0a0c; |
| 792 | 782 | box-shadow: 0 28px 64px -28px rgba(0, 0, 0, 0.75); |
| ... | ... | @@ -811,7 +801,7 @@ const mobileEndorsementRows = [ |
| 811 | 801 | |
| 812 | 802 | .harness-grid { |
| 813 | 803 | display: grid; |
| 814 | | grid-template-columns: repeat(5, 1fr); |
| 804 | grid-template-columns: repeat(6, 1fr); |
| 815 | 805 | gap: 0; |
| 816 | 806 | border: 1px solid var(--border); |
| 817 | 807 | border-radius: var(--radius); |
| ... | ... | @@ -833,7 +823,7 @@ const mobileEndorsementRows = [ |
| 833 | 823 | flex-shrink: 0; width: 28px; height: 28px; |
| 834 | 824 | display: grid; place-items: center; |
| 835 | 825 | } |
| 836 | | .harness-mark img { width: 22px; height: 22px; object-fit: contain; } |
| 826 | .harness-mark img { width: 22px; height: 22px; object-fit: contain; border-radius: 5px; } |
| 837 | 827 | |
| 838 | 828 | .harness-meta { flex: 1; min-width: 0; } |
| 839 | 829 | .harness-name { |
| ... | ... | @@ -1244,6 +1234,10 @@ const mobileEndorsementRows = [ |
| 1244 | 1234 | margin-bottom: 24px; |
| 1245 | 1235 | } |
| 1246 | 1236 | |
| 1237 | @keyframes blink { |
| 1238 | 50% { opacity: 0; } |
| 1239 | } |
| 1240 | |
| 1247 | 1241 | @keyframes endorsementMarquee { |
| 1248 | 1242 | from { |
| 1249 | 1243 | transform: translateX(0); |
| ... | ... | @@ -1266,10 +1260,7 @@ const mobileEndorsementRows = [ |
| 1266 | 1260 | .harness-grid { grid-template-columns: 1fr 1fr; } |
| 1267 | 1261 | .harness { border-right: 0; border-bottom: 1px solid var(--border); } |
| 1268 | 1262 | .harness:nth-child(odd) { border-right: 1px solid var(--border); } |
| 1269 | | .harness:nth-child(5):last-child { |
| 1270 | | grid-column: 1 / -1; |
| 1271 | | border-right: 0; |
| 1272 | | } |
| 1263 | .harness:nth-child(even) { border-right: 0; } |
| 1273 | 1264 | .git-inner { grid-template-columns: 1fr; gap: 40px; } |
| 1274 | 1265 | .open-grid { grid-template-columns: 1fr; } |
| 1275 | 1266 | } |