1---
2import { getImage, Image } from "astro:assets";
3import appIcon from "../assets/icon.webp";
4import socialCard from "../assets/social-card.webp";
5import dmSansLatinUrl from "../assets/fonts/dm-sans-latin.woff2?url";
6import "../styles/fonts.css";
7import {
8 ANDROID_PLAY_STORE_URL,
9 GITHUB_REPOSITORY_URL,
10 IOS_APP_STORE_URL,
11 MARKETING_STATS,
12} from "../lib/site";
13
14interface Props {
15 title?: string;
16 description?: string;
17 pageClass?: string;
18}
19
20const {
21 title = "T3 Code",
22 description = "T3 Code. The open-source control plane for coding agents.",
23 pageClass,
24} = Astro.props;
25
26// Social preview card, shared by og:image and twitter:image. Link unfurlers
27// (Instagram, iMessage, X, Slack) want a 1200x630 jpg or png at an absolute URL.
28// The source is a 2x export so the downscale stays sharp.
29const socialImage = await getImage({
30 src: socialCard,
31 width: 1200,
32 height: 630,
33 format: "jpg",
34 quality: 90,
35});
36const socialImageUrl = new URL(socialImage.src, Astro.site);
37const socialImageAlt =
38 "T3 Code: the open-source control plane for coding agents, with Claude Code, Codex, Cursor, OpenCode, Grok and Antigravity.";
39const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
40---
41
42<html lang="en">
43 <head>
44 <meta charset="UTF-8" />
45 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
46 <meta name="description" content={description} />
47 <link rel="canonical" href={canonicalUrl} />
48 <meta property="og:type" content="website" />
49 <meta property="og:site_name" content="T3 Code" />
50 <meta property="og:url" content={canonicalUrl} />
51 <meta property="og:title" content={title} />
52 <meta property="og:description" content={description} />
53 <meta property="og:image" content={socialImageUrl} />
54 <meta property="og:image:width" content="1200" />
55 <meta property="og:image:height" content="630" />
56 <meta property="og:image:alt" content={socialImageAlt} />
57 <meta name="twitter:card" content="summary_large_image" />
58 <meta name="twitter:site" content="@t3dotgg" />
59 <meta name="twitter:title" content={title} />
60 <meta name="twitter:description" content={description} />
61 <meta name="twitter:image" content={socialImageUrl} />
62 <meta name="twitter:image:alt" content={socialImageAlt} />
63 <link
64 rel="preload"
65 href={dmSansLatinUrl}
66 as="font"
67 type="font/woff2"
68 crossorigin="anonymous"
69 />
70 <link rel="icon" href="/favicon.ico" sizes="48x48" />
71 <link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32" />
72 <link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16" />
73 <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
74 <title>{title}</title>
75 </head>
76 <body>
77 <div class:list={["page", pageClass]}>
78 <nav class="nav">
79 <div class="nav-inner">
80 <a href="/" class="nav-brand" aria-label="T3 Code home">
81 <svg
82 class="nav-brand-mark"
83 viewBox="15.5309 37 94.3941 56.96"
84 fill="currentColor"
85 aria-hidden="true"
86 >
87 <path d="M33.4509 93V47.56H15.5309V37H64.3309V47.56H46.4109V93H33.4509ZM86.7253 93.96C82.832 93.96 78.9653 93.4533 75.1253 92.44C71.2853 91.3733 68.032 89.88 65.3653 87.96L70.4053 78.04C72.5386 79.5867 75.0186 80.8133 77.8453 81.72C80.672 82.6267 83.5253 83.08 86.4053 83.08C89.6586 83.08 92.2186 82.44 94.0853 81.16C95.952 79.88 96.8853 78.12 96.8853 75.88C96.8853 73.7467 96.0586 72.0667 94.4053 70.84C92.752 69.6133 90.0853 69 86.4053 69H80.4853V60.44L96.0853 42.76L97.5253 47.4H68.1653V37H107.365V45.4L91.8453 63.08L85.2853 59.32H89.0453C95.9253 59.32 101.125 60.8667 104.645 63.96C108.165 67.0533 109.925 71.0267 109.925 75.88C109.925 79.0267 109.099 81.9867 107.445 84.76C105.792 87.48 103.259 89.6933 99.8453 91.4C96.432 93.1067 92.0586 93.96 86.7253 93.96Z" />
88 </svg>
89 <span class="nav-brand-name">Code</span>
90 </a>
91 <div class="nav-right">
92 <a class="nav-link" href="/download">Download</a>
93 <a
94 class="nav-stars"
95 href={GITHUB_REPOSITORY_URL}
96 target="_blank"
97 rel="noopener noreferrer"
98 aria-label={`Star T3 Code on GitHub — ${MARKETING_STATS.githubStars} stars`}
99 >
100 <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
101 <path d="M12 2.3l2.9 5.88 6.49.94-4.7 4.58 1.11 6.47L12 17.12l-5.8 3.05 1.11-6.47-4.7-4.58 6.49-.94L12 2.3z"/>
102 </svg>
103 <span><strong>{MARKETING_STATS.githubStars}</strong> GitHub stars</span>
104 </a>
105 </div>
106 </div>
107 </nav>
108
109 <main class="main">
110 <slot />
111 </main>
112
113 <footer class="footer">
114 <div class="footer-inner">
115 <div class="footer-brand">
116 <Image
117 src={appIcon}
118 alt=""
119 width={20}
120 height={20}
121 densities={[1, 2, 3]}
122 format="webp"
123 quality={90}
124 loading="lazy"
125 decoding="async"
126 />
127 <span>&copy; {new Date().getFullYear()} T3 Tools Inc · MIT licensed</span>
128 </div>
129 <div class="footer-links">
130 <a href={GITHUB_REPOSITORY_URL} target="_blank" rel="noopener noreferrer">GitHub</a>
131 <a href="https://discord.gg/jn4EGJjrvv" target="_blank" rel="noopener noreferrer">Discord</a>
132 <a href="/download">Download</a>
133 <a href={IOS_APP_STORE_URL} target="_blank" rel="noopener noreferrer">iOS app</a>
134 <a href={ANDROID_PLAY_STORE_URL} target="_blank" rel="noopener noreferrer">Android app</a>
135 <a href="/terms-of-service">Terms</a>
136 <a href="/privacy-policy">Privacy</a>
137 <a href="/security-policy">Security</a>
138 </div>
139 </div>
140 </footer>
141 </div>
142
143 <script>
144 const nav = document.querySelector(".nav");
145 const onScroll = () => {
146 if (!nav) return;
147 if (window.scrollY > 12) nav.classList.add("is-scrolled");
148 else nav.classList.remove("is-scrolled");
149 };
150 window.addEventListener("scroll", onScroll, { passive: true });
151 onScroll();
152 </script>
153 </body>
154</html>
155
156<style is:global>
157 :root {
158 --bg: #09090b;
159 --bg-elev: #0c0c0e;
160 --bg-card: #111113;
161 --fg: #fafafa;
162 --fg-muted: #a1a1aa;
163 --fg-dim: #71717a;
164 --fg-faint: #52525b;
165 --border: rgba(255, 255, 255, 0.08);
166 --border-strong: rgba(255, 255, 255, 0.14);
167 --accent-h: 250;
168 --accent: oklch(0.68 0.17 var(--accent-h));
169 --accent-dim: oklch(0.68 0.17 var(--accent-h) / 0.15);
170 --ok: oklch(0.72 0.16 150);
171 --warn: oklch(0.76 0.15 75);
172 --font-sans: "DM Sans", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
173 --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
174 --radius: 12px;
175 --radius-sm: 8px;
176 --radius-lg: 16px;
177 }
178
179 * {
180 box-sizing: border-box;
181 margin: 0;
182 padding: 0;
183 }
184
185 html {
186 color-scheme: dark;
187 scroll-behavior: smooth;
188 }
189
190 body {
191 background: var(--bg);
192 color: var(--fg);
193 font-family: var(--font-sans);
194 font-feature-settings: "ss01", "ss02";
195 -webkit-font-smoothing: antialiased;
196 -moz-osx-font-smoothing: grayscale;
197 min-height: 100vh;
198 overflow-x: hidden;
199 line-height: 1.5;
200 }
201
202 body::after {
203 content: "";
204 position: fixed;
205 inset: 0;
206 pointer-events: none;
207 opacity: 0.035;
208 background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
209 background-repeat: repeat;
210 background-size: 256px 256px;
211 z-index: 9999;
212 }
213
214 a {
215 color: inherit;
216 text-decoration: none;
217 }
218
219 button {
220 font-family: inherit;
221 cursor: pointer;
222 border: 0;
223 background: none;
224 color: inherit;
225 }
226
227 img,
228 svg {
229 display: block;
230 max-width: 100%;
231 }
232
233 .eyebrow {
234 font-family: var(--font-mono);
235 font-size: 11px;
236 letter-spacing: 0.14em;
237 text-transform: uppercase;
238 color: var(--fg-dim);
239 font-weight: 500;
240 }
241
242 .display {
243 font-weight: 500;
244 letter-spacing: -0.035em;
245 line-height: 1.05;
246 }
247
248 .container {
249 width: 100%;
250 max-width: 1240px;
251 margin: 0 auto;
252 padding: 0 32px;
253 }
254
255 .btn {
256 display: inline-flex;
257 align-items: center;
258 justify-content: center;
259 gap: 8px;
260 font-weight: 500;
261 font-size: 14px;
262 padding: 10px 18px;
263 border-radius: 8px;
264 transition: transform 0.18s ease, background 0.18s ease,
265 border-color 0.18s ease, color 0.18s ease;
266 white-space: nowrap;
267 }
268
269 .btn-primary {
270 background: var(--fg);
271 color: #09090b;
272 font-weight: 600;
273 }
274
275 .btn-primary:hover {
276 transform: translateY(-1px);
277 background: #fff;
278 }
279
280 .btn-primary:active {
281 transform: translateY(0);
282 }
283
284 .btn-ghost {
285 color: var(--fg-muted);
286 border: 1px solid var(--border);
287 background: transparent;
288 }
289
290 .btn-ghost:hover {
291 color: var(--fg);
292 border-color: var(--border-strong);
293 background: rgba(255, 255, 255, 0.02);
294 }
295
296 .btn-lg {
297 padding: 14px 22px;
298 font-size: 15px;
299 border-radius: 10px;
300 }
301
302 .tile {
303 position: relative;
304 border: 1px solid var(--border);
305 border-radius: var(--radius);
306 background: linear-gradient(180deg, rgba(255, 255, 255, 0.015), rgba(255, 255, 255, 0));
307 overflow: hidden;
308 }
309
310 :focus-visible {
311 outline: 2px solid var(--accent);
312 outline-offset: 2px;
313 border-radius: 4px;
314 }
315
316 @keyframes fade-in {
317 from {
318 opacity: 0;
319 transform: translateY(8px);
320 }
321 to {
322 opacity: 1;
323 transform: translateY(0);
324 }
325 }
326
327 /* Page-load sequence: [data-rise] plays once with delay --d. */
328 @keyframes rise {
329 from { opacity: 0; transform: translateY(16px); }
330 to { opacity: 1; transform: none; }
331 }
332 [data-rise] {
333 animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) both;
334 animation-delay: var(--d, 0ms);
335 }
336 @media (prefers-reduced-motion: reduce) {
337 [data-rise] { animation: none; }
338 }
339</style>
340
341<style>
342 .page {
343 min-height: 100vh;
344 display: flex;
345 flex-direction: column;
346 overflow-x: clip;
347 }
348
349 .nav {
350 position: sticky;
351 top: 0;
352 z-index: 50;
353 backdrop-filter: blur(18px);
354 -webkit-backdrop-filter: blur(18px);
355 background: color-mix(in srgb, var(--bg) 75%, transparent);
356 border-bottom: 1px solid transparent;
357 transition: border-color 0.3s ease;
358 }
359
360 .nav.is-scrolled {
361 border-bottom-color: var(--border);
362 }
363
364 .nav-inner {
365 max-width: 1240px;
366 margin: 0 auto;
367 padding: 12px 32px;
368 display: flex;
369 align-items: center;
370 justify-content: space-between;
371 gap: 24px;
372 }
373
374 .nav-brand {
375 display: inline-flex;
376 align-items: baseline;
377 gap: 4px;
378 color: var(--fg);
379 transition: color 0.2s ease;
380 }
381
382 .nav-brand:hover {
383 color: #fff;
384 }
385
386 .nav-brand-mark {
387 height: 1cap;
388 width: auto;
389 flex: none;
390 }
391
392 .nav-brand-name {
393 font-size: 16px;
394 font-weight: 500;
395 letter-spacing: -0.025em;
396 line-height: 1;
397 color: var(--fg-muted);
398 }
399
400 .nav-right {
401 display: inline-flex;
402 align-items: center;
403 gap: 8px;
404 }
405
406 .nav-link {
407 padding: 0 10px;
408 color: var(--fg-muted);
409 font-size: 13px;
410 letter-spacing: -0.01em;
411 white-space: nowrap;
412 transition: color 0.18s ease;
413 }
414
415 .nav-link:hover {
416 color: var(--fg);
417 }
418
419 .nav-stars {
420 display: inline-flex;
421 align-items: center;
422 gap: 7px;
423 height: 36px;
424 padding: 0 14px;
425 border: 1px solid var(--border);
426 border-radius: 999px;
427 background: rgba(255, 255, 255, 0.02);
428 color: var(--fg-muted);
429 font-size: 13px;
430 letter-spacing: -0.01em;
431 white-space: nowrap;
432 transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease;
433 }
434
435 .nav-stars:hover {
436 color: var(--fg);
437 border-color: var(--border-strong);
438 background: rgba(255, 255, 255, 0.04);
439 }
440
441 .nav-stars strong {
442 color: var(--fg);
443 font-weight: 600;
444 }
445
446 .nav-stars svg {
447 color: var(--warn);
448 flex-shrink: 0;
449 }
450
451 .main {
452 flex: 1;
453 display: flex;
454 flex-direction: column;
455 }
456
457 .footer {
458 margin-top: auto;
459 border-top: 1px solid var(--border);
460 padding: 40px 0 32px;
461 }
462
463 .footer-inner {
464 max-width: 1240px;
465 margin: 0 auto;
466 padding: 0 32px;
467 display: flex;
468 flex-wrap: wrap;
469 gap: 24px;
470 align-items: center;
471 justify-content: space-between;
472 }
473
474 .footer-brand {
475 display: flex;
476 align-items: center;
477 gap: 10px;
478 font-size: 13px;
479 color: var(--fg-dim);
480 }
481
482 .footer-brand img {
483 width: 20px;
484 height: 20px;
485 border-radius: 4px;
486 }
487
488 .footer-links {
489 display: flex;
490 flex-wrap: wrap;
491 justify-content: flex-end;
492 gap: 20px;
493 }
494
495 .footer-links a {
496 font-size: 13px;
497 color: var(--fg-dim);
498 transition: color 0.2s ease;
499 }
500
501 .footer-links a:hover {
502 color: var(--fg);
503 }
504
505 @media (max-width: 820px) {
506 .nav-inner,
507 .footer-inner {
508 padding-left: 20px;
509 padding-right: 20px;
510 }
511
512 .legal-document-page .nav {
513 display: none;
514 }
515
516 .footer {
517 padding-bottom: max(32px, env(safe-area-inset-bottom));
518 }
519
520 .footer-inner {
521 align-items: flex-start;
522 flex-direction: column;
523 gap: 28px;
524 }
525
526 .footer-links {
527 display: grid;
528 grid-template-columns: repeat(3, minmax(0, 1fr));
529 width: 100%;
530 gap: 14px 20px;
531 justify-content: initial;
532 }
533 }
534
535 @media (max-width: 420px) {
536 .nav-inner {
537 gap: 12px;
538 }
539
540 .nav-stars {
541 height: 34px;
542 gap: 6px;
543 padding: 0 12px;
544 font-size: 12px;
545 }
546 }
547</style>