Skip to content
275 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1/**
2 * The site's footer: a few links worth having, who makes g1t, and a row of
3 * the trust pages with the live status dot, the account, and the legal
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas4 * line. Marketing and signed-out pages show it; the app's account menu
5 * has its own rows (components/shell.tsx).
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look6 */
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas7import { useEffect, useState } from "react";
8import { Link } from "react-router";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look9
10import type { User } from "@g1t/contracts";
11
Analytics asks first in the EU, EEA, UK and Switzerland, and loads only when the page is idle12import { useAsksFirst } from "./analytics-consent";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look13import { Mark } from "./logo";
Analytics asks first in the EU, EEA, UK and Switzerland, and loads only when the page is idle14import { choose } from "../lib/analytics-consent";
g1t on X: @g1t_sh in the site footer, the docs header and the structured data15import { COMPANY, MAKER_PRODUCTS, SOCIAL, copyright, listed } from "../lib/legal";
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas16import { type OverallState, STATUS_JSON_URL, STATUS_URL, STATUS_WORDS, type StatusReport, dotClass } from "../lib/status";
17
18/** How long one fetched report is reused across the pages of one visit. */
19const STATUS_REUSE_MS = 60_000;
20let lastStatus: { report: StatusReport; at: number } | null = null;
21let pendingStatus: Promise<StatusReport | null> | null = null;
22
23function fetchStatus(): Promise<StatusReport | null> {
24 if (lastStatus && Date.now() - lastStatus.at < STATUS_REUSE_MS) return Promise.resolve(lastStatus.report);
25 pendingStatus ??= fetch(STATUS_JSON_URL, { headers: { accept: "application/json" } })
26 .then((response) => (response.ok ? (response.json() as Promise<StatusReport>) : null))
27 .then((report) => {
28 if (report?.overall) lastStatus = { report, at: Date.now() };
29 return report?.overall ? report : null;
30 })
31 // The status page being unreachable only means no dot.
32 .catch(() => null)
33 .finally(() => {
34 pendingStatus = null;
35 });
36 return pendingStatus;
37}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look38
39/**
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas40 * The live status, from status.g1t.sh once the page is up (or `when` turns
41 * true), so no page waits on it. Kept a minute across pages. Null until it
42 * arrives, or if it cannot.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look43 */
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas44export function useSiteStatus(when = true): StatusReport | null {
45 const [report, setReport] = useState<StatusReport | null>(() => lastStatus?.report ?? null);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look46 useEffect(() => {
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas47 if (!when) return;
48 let live = true;
49 fetchStatus().then((fresh) => {
50 if (live && fresh) setReport(fresh);
51 });
52 return () => {
53 live = false;
54 };
55 }, [when]);
56 return report;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look57}
58
59/** The coloured dot, with words for screen readers. */
60export function StatusDot({ state, className = "" }: { state: OverallState | null; className?: string }) {
61 return (
62 <span className={`relative inline-flex size-2 shrink-0 ${className}`}>
63 {state && state !== "up" && state !== "unknown" && (
64 <span aria-hidden className={`absolute inset-0 animate-ping rounded-full opacity-60 ${dotClass(state)}`} />
65 )}
66 <span aria-hidden className={`relative inline-flex size-2 rounded-full ${dotClass(state)}`} />
67 <span className="sr-only">{state ? STATUS_WORDS[state] : "Checking status"}</span>
68 </span>
69 );
70}
71
72/** Where the footer's grid links go. Plain links: some are files or other hosts. */
73const FOOTER_LINKS: { title: string; links: [string, string][] }[] = [
74 {
75 title: "Product",
76 links: [
77 ["Explore", "/explore"],
78 ["Pricing", "/pricing"],
Chat and workspace agents: channels, DMs and named agents you talk to79 ["Chat", "https://docs.g1t.sh/guides/chat/"],
80 ["Agents", "https://docs.g1t.sh/guides/agents/"],
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent81 ["Working with g1t", "https://docs.g1t.sh/guides/working-with-g1t/"],
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look82 ["Bring your own agent", "https://docs.g1t.sh/guides/bring-your-own-agent/"],
83 ["Integrations", "https://docs.g1t.sh/guides/integrations/"],
84 ],
85 },
86 {
87 title: "Developers",
88 links: [
89 ["Documentation", "https://docs.g1t.sh/"],
90 ["API reference", "https://docs.g1t.sh/reference/api/"],
91 ["MCP tools", "https://docs.g1t.sh/reference/mcp/"],
92 ["llms.txt", "/llms.txt"],
93 ],
94 },
95 {
96 title: "Company",
97 links: [
98 ["Flagon, Inc.", COMPANY.url],
g1t on X: @g1t_sh in the site footer, the docs header and the structured data99 ...SOCIAL.map((account): [string, string] => [account.label, account.url]),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look100 ["Support", "/support"],
101 ["Security", "/security"],
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas102 ["Status", STATUS_URL],
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look103 ],
104 },
105 {
106 title: "Open source",
107 links: [
108 ["Source on g1t", "/flagon-io/g1t"],
109 ["MIT license", "/flagon-io/g1t/blob/main/LICENSE"],
110 ["Run g1t yourself", "https://docs.g1t.sh/guides/self-hosting/"],
111 ],
112 },
113];
114
115/** The row's own links, after Status. */
116const ROW_LINKS: [string, string][] = [
117 ["Support", "/support"],
118 ["Policies", "/policies"],
119 ["Privacy", "/policies/privacy"],
120 ["Security", "/security"],
121];
122
123/**
124 * Flagon's mark, single-ink, as in Flagon's brand files
125 * (flagon-mark-mono.svg): a flagon, its brew a tone of the text colour.
126 */
127function FlagonMark({ className = "" }: { className?: string }) {
128 return (
129 <svg viewBox="11.3 12.3 41 41" fill="none" aria-hidden="true" className={className}>
130 <defs>
131 <clipPath id="g1t-footer-flagon-body">
132 <path d="M19.5 23 L34.5 23 L36.5 46 Q36.7 48 34.3 48 L19.7 48 Q17.3 48 17.5 46 Z" />
133 </clipPath>
134 </defs>
135 <g clipPath="url(#g1t-footer-flagon-body)">
136 <path
137 d="M13 27 q3.75 -0.7 7.5 0 t7.5 0 t7.5 0 t7.5 0 t7.5 0 L51 52 L13 52 Z"
138 fill="currentColor"
139 fillOpacity="0.22"
140 />
141 </g>
142 <path
143 d="M37 27 L45.5 27 L48.5 30 L48.5 36.5 L45.5 39.5 L38 39.5"
144 stroke="currentColor"
145 strokeWidth="3.1"
146 strokeLinecap="round"
147 strokeLinejoin="round"
148 />
149 <path
150 d="M17.5 21 L36.5 21 L38.6 47 Q39 50 36 50 L18 50 Q15 50 15.4 47 Z"
151 stroke="currentColor"
152 strokeWidth="3.1"
153 strokeLinejoin="round"
154 />
155 <path d="M17 21 Q16.3 16.3 20 15.6 L33 15.6 Q37.2 16.2 37 21 Z" stroke="currentColor" strokeWidth="3" strokeLinejoin="round" />
156 <path d="M36.7 17.1 L39.4 16.1" stroke="currentColor" strokeWidth="2.4" strokeLinejoin="round" />
157 <circle cx="40.3" cy="15.8" r="1.4" fill="currentColor" />
158 </svg>
The site says who it is for, how to switch, and what it costs a team; search engines get a map159 );
160}
161
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look162/** "g1t is designed, built, and backed by Flagon, Inc., …" */
163export function MakerLine() {
164 const products = MAKER_PRODUCTS.map((product) => product.name);
165 return (
166 <p className="text-sm leading-6 text-muted text-balance">
167 <Mark tight className="mr-1.5 inline-block h-[0.85em] w-auto align-baseline" />
168 <span className="font-semibold text-fg">g1t</span> is designed, built, and backed by{" "}
169 <a href={COMPANY.url} className="whitespace-nowrap font-semibold text-fg hover:underline hover:underline-offset-4">
170 <FlagonMark className="mr-1 inline-block size-[0.95em] -translate-y-px align-middle" />
171 {COMPANY.name}
172 </a>
Name Chase Pierce as Flagon's founder; drop the empty testimonial and founder stubs173 {products.length > 0 ? `, the people behind ${listed(products)}.` : `, ${COMPANY.about} founded by ${COMPANY.founder}.`}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look174 </p>
175 );
176}
177
178const ROW_LINK = "rounded-sm transition-colors hover:text-fg";
179
180/**
181 * Status, the trust pages, the account and the legal line, separated by
182 * dots that wrap away cleanly on a phone.
183 */
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas184export function LegalRow({ user }: { user: User | null | undefined }) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look185 const status = useSiteStatus();
Analytics asks first in the EU, EEA, UK and Switzerland, and loads only when the page is idle186 const asksFirst = useAsksFirst();
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look187 const links = [
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas188 <a key="status" href={STATUS_URL} className={`inline-flex items-center gap-1.5 ${ROW_LINK}`}>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look189 <StatusDot state={status?.overall.state ?? null} />
190 Status
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas191 </a>,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look192 ...ROW_LINKS.map(([label, to]) => (
193 <Link key={to} to={to} className={ROW_LINK}>
194 {label}
195 </Link>
196 )),
Analytics asks first in the EU, EEA, UK and Switzerland, and loads only when the page is idle197 // Where the visitor was asked about the analytics cookie: ask again.
198 ...(asksFirst
199 ? [
200 <button key="consent" type="button" onClick={() => choose(null)} className={ROW_LINK}>
201 Cookie choices
202 </button>,
203 ]
204 : []),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look205 ];
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas206 const account = [
207 user ? (
208 <Link key="me" to={`/u/${user.username}`} className={`font-medium text-fg/90 ${ROW_LINK}`}>
209 {user.username}
210 </Link>
211 ) : (
212 <Link key="login" to="/login" className={ROW_LINK}>
213 Sign in
214 </Link>
215 ),
216 ];
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look217 const legal = [
218 ...account,
219 <span key="copyright" className="text-faint">
220 {copyright()}
221 </span>,
222 ];
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas223 const gap = "gap-x-3";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look224 const dot = (
225 <span aria-hidden className="text-line-strong">
226 ·
227 </span>
228 );
229 // Two runs, the links and the legal line, so that on a narrow screen the
230 // second starts its own line rather than a line starting with a dot.
231 const run = (items: React.ReactNode[]) =>
232 items.map((item, index) => (
233 <li key={index} className={`flex items-center ${gap}`}>
234 {index > 0 && dot}
235 {item}
236 </li>
237 ));
238 return (
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas239 <div className={`flex flex-wrap items-center justify-center gap-y-1.5 ${gap} text-[0.8125rem] text-muted`}>
240 <ul className={`flex flex-wrap items-center justify-center gap-y-1.5 ${gap}`}>{run(links)}</ul>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look241 {/* Between the two runs only while they share a line. */}
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas242 <span className="hidden md:inline">{dot}</span>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look243 <ul className={`flex flex-wrap items-center justify-center gap-y-1.5 ${gap}`}>{run(legal)}</ul>
244 </div>
245 );
246}
247
248export function SiteFooter({ user }: { user: User | null | undefined }) {
249 return (
250 <footer className="mt-24 border-t border-line">
251 <nav aria-label="Footer" className="mx-auto grid max-w-6xl grid-cols-2 gap-x-8 gap-y-8 px-4 pt-12 pb-10 sm:grid-cols-4">
252 {FOOTER_LINKS.map((group) => (
253 <div key={group.title}>
254 <p className="text-sm font-medium">{group.title}</p>
255 <ul className="mt-3 space-y-2 text-sm text-muted">
256 {group.links.map(([label, to]) => (
257 <li key={to}>
258 <a href={to} className="transition-colors hover:text-fg">
259 {label}
260 </a>
261 </li>
262 ))}
263 </ul>
264 </div>
265 ))}
266 </nav>
267 <div className="border-t border-line">
268 <div className="mx-auto max-w-3xl space-y-4 px-4 py-10 text-center">
269 <MakerLine />
270 <LegalRow user={user} />
271 </div>
272 </div>
273 </footer>
274 );
275}