Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
289 linesCodeBlameRaw
1---
2// A page's title, the section it is in above it, what it is about beneath
3// it, and a way to take the page
4// to an AI assistant: copied as Markdown, read as Markdown, or opened in
5// Claude or ChatGPT.
6import { ArrowUpRight, ChevronDown, Copy, FileText, MessageSquare } from '@lucide/astro';
7
8import { NAV } from '../route-data';
9
10const { entry, id } = Astro.locals.starlightRoute;
11const { title, description } = entry.data;
12const nav = NAV.get(Astro.locals.starlightRoute)?.section;
13// No eyebrow that only repeats the title, as on Chat's page in Chat.
14const section = nav && nav.toLowerCase() !== title.toLowerCase() ? nav : undefined;
15const markdown = `/${id === '' || id === 'index' ? 'index' : id}.md`;
16const absolute = new URL(markdown, Astro.site).href;
17const ask = encodeURIComponent(`Read ${absolute} so I can ask questions about it.`);
18---
19
20<div class="g1t-page-title">
21 {section && <p class="g1t-eyebrow">{section}</p>}
22 <div class="g1t-page-title-row">
23 <h1 id="_top">{title}</h1>
24 <div class="g1t-copy not-content" data-pagefind-ignore>
25 <button type="button" class="g1t-copy-main" data-markdown={markdown}>
26 <Copy />
27 <span>Copy page</span>
28 </button>
29 <details class="g1t-copy-more">
30 <summary aria-label="More ways to use this page">
31 <ChevronDown />
32 </summary>
33 <div class="g1t-copy-menu">
34 <a href={markdown} target="_blank" rel="noopener">
35 <FileText class="g1t-copy-icon" />
36 <span class="g1t-copy-text"><strong>View as Markdown</strong><small>This page as plain text</small></span>
37 </a>
38 <a href={`https://claude.ai/new?q=${ask}`} target="_blank" rel="noopener">
39 <MessageSquare class="g1t-copy-icon" />
40 <span class="g1t-copy-text"><strong>Open in Claude</strong><small>Ask questions about this page</small></span>
41 <ArrowUpRight class="g1t-copy-out" />
42 </a>
43 <a href={`https://chatgpt.com/?hints=search&q=${ask}`} target="_blank" rel="noopener">
44 <MessageSquare class="g1t-copy-icon" />
45 <span class="g1t-copy-text"><strong>Open in ChatGPT</strong><small>Ask questions about this page</small></span>
46 <ArrowUpRight class="g1t-copy-out" />
47 </a>
48 </div>
49 </details>
50 </div>
51 </div>
52 {description && <p class="g1t-description">{description}</p>}
53</div>
54
55<script>
56 document.querySelectorAll<HTMLButtonElement>('.g1t-copy-main').forEach((button) => {
57 button.addEventListener('click', async () => {
58 const label = button.querySelector('span')!;
59 try {
60 const text = await (await fetch(button.dataset.markdown!)).text();
61 await navigator.clipboard.writeText(text);
62 label.textContent = 'Copied';
63 } catch {
64 label.textContent = 'Could not copy';
65 }
66 setTimeout(() => (label.textContent = 'Copy page'), 1600);
67 });
68 });
69 // A menu closes when you click elsewhere.
70 document.addEventListener('click', (event) => {
71 document.querySelectorAll<HTMLDetailsElement>('.g1t-copy-more[open]').forEach((menu) => {
72 if (!menu.contains(event.target as Node)) menu.open = false;
73 });
74 });
75</script>
76
77<style>
78 .g1t-page-title {
79 margin-top: 0.75rem;
80 }
81 .g1t-page-title-row {
82 display: flex;
83 align-items: flex-start;
84 justify-content: space-between;
85 gap: 1.5rem;
86 }
87 /* The section, in the accent: where in the docs this page is. In its own
88 case, never capitals, since the product's name stays lowercase. */
89 .g1t-eyebrow {
90 margin: 0 0 0.6rem;
91 font-family: var(--g1t-font-mono);
92 font-size: 0.8125rem;
93 font-weight: 500;
94 color: var(--g1t-accent);
95 }
96 h1 {
97 margin: 0;
98 font-family: var(--g1t-font-display);
99 font-optical-sizing: auto;
100 font-size: 2.75rem;
101 font-weight: 600;
102 letter-spacing: -0.03em;
103 line-height: 1.08;
104 color: var(--g1t-fg);
105 }
106 .g1t-description {
107 margin-top: 0.9rem;
108 max-width: 42rem;
109 font-size: 1.1875rem;
110 line-height: 1.55;
111 color: var(--g1t-muted);
112 }
113 /* A button group: one bordered box, both parts the same height, and a
114 divider that runs the box's full height. */
115 .g1t-copy {
116 position: relative;
117 display: inline-flex;
118 align-items: stretch;
119 flex-shrink: 0;
120 height: 2rem;
121 margin-top: 0.35rem;
122 border: 1px solid var(--g1t-line);
123 border-radius: 0.5rem;
124 }
125 .g1t-copy :global(svg) {
126 display: block;
127 flex-shrink: 0;
128 width: 0.875rem;
129 height: 0.875rem;
130 }
131 .g1t-copy-more {
132 display: flex;
133 margin: 0;
134 }
135 .g1t-copy-main,
136 .g1t-copy-more summary {
137 display: inline-flex;
138 align-items: center;
139 justify-content: center;
140 gap: 0.375rem;
141 height: 100%;
142 margin: 0;
143 border: 0;
144 background: transparent;
145 padding: 0 0.625rem;
146 font: inherit;
147 font-size: 0.8125rem;
148 font-weight: 500;
149 line-height: 1;
150 color: var(--g1t-muted);
151 cursor: pointer;
152 transition: color 0.15s, background 0.15s;
153 }
154 .g1t-copy-main {
155 border-radius: calc(0.5rem - 1px) 0 0 calc(0.5rem - 1px);
156 }
157 .g1t-copy-more summary {
158 list-style: none;
159 border-left: 1px solid var(--g1t-line);
160 border-radius: 0 calc(0.5rem - 1px) calc(0.5rem - 1px) 0;
161 padding-inline: 0.5rem;
162 }
163 .g1t-copy-main:focus-visible,
164 .g1t-copy-more summary:focus-visible {
165 outline: 2px solid var(--g1t-accent, currentColor);
166 outline-offset: -2px;
167 }
168 .g1t-copy-more summary::-webkit-details-marker {
169 display: none;
170 }
171 .g1t-copy-main:hover,
172 .g1t-copy-more summary:hover {
173 background: var(--g1t-surface);
174 color: var(--g1t-fg);
175 }
176 .g1t-copy-menu {
177 position: absolute;
178 top: calc(100% + 0.4rem);
179 right: 0;
180 z-index: 10;
181 width: 16.5rem;
182 border-radius: 0.75rem;
183 padding: 0.35rem;
184 background: var(--g1t-raised);
185 box-shadow:
186 inset 0 0 0 1px var(--g1t-line-strong),
187 var(--g1t-docs-menu-shadow);
188 }
189 /* Its own spacing: the page's prose rules stop at not-content, and
190 nothing inside keeps a margin of its own. */
191 .g1t-copy-menu * {
192 margin: 0;
193 }
194 .g1t-copy-menu a {
195 display: flex;
196 align-items: flex-start;
197 gap: 0.6rem;
198 border-radius: 0.5rem;
199 padding: 0.5rem 0.6rem;
200 text-decoration: none;
201 transition: background 0.15s;
202 }
203 .g1t-copy-menu a + a {
204 margin-top: 0.1rem;
205 }
206 .g1t-copy-menu a:hover {
207 background: var(--g1t-surface);
208 }
209 .g1t-copy-menu :global(.g1t-copy-icon) {
210 flex-shrink: 0;
211 margin-top: 0.15rem;
212 color: var(--g1t-faint);
213 }
214 .g1t-copy-menu :global(.g1t-copy-out) {
215 flex-shrink: 0;
216 margin-left: auto;
217 margin-top: 0.15rem;
218 width: 0.8rem;
219 height: 0.8rem;
220 color: var(--g1t-faint);
221 opacity: 0;
222 transition: opacity 0.15s;
223 }
224 .g1t-copy-menu a:hover :global(.g1t-copy-out) {
225 opacity: 1;
226 }
227 .g1t-copy-text {
228 display: flex;
229 min-width: 0;
230 flex-direction: column;
231 gap: 0.1rem;
232 line-height: 1.3;
233 }
234 .g1t-copy-menu strong {
235 font-size: 0.8125rem;
236 font-weight: 500;
237 color: var(--g1t-fg);
238 }
239 .g1t-copy-menu small {
240 font-size: 0.75rem;
241 color: var(--g1t-faint);
242 }
243 /* Phones: the group keeps its place beside the title, as two square
244 buttons a finger can hit; the copy button shows only its icon, and
245 its label stays for screen readers. */
246 @media (max-width: 40rem) {
247 .g1t-page-title-row {
248 gap: 0.75rem;
249 }
250 h1 {
251 min-width: 0;
252 font-size: 2rem;
253 overflow-wrap: anywhere;
254 }
255 /* The lead a step above the body, not a headline of its own. */
256 .g1t-description {
257 margin-top: 0.75rem;
258 font-size: 1.0625rem;
259 line-height: 1.5;
260 }
261 .g1t-copy {
262 height: 2.5rem;
263 margin-top: 0;
264 }
265 .g1t-copy-main span {
266 position: absolute;
267 width: 1px;
268 height: 1px;
269 overflow: hidden;
270 clip-path: inset(50%);
271 white-space: nowrap;
272 }
273 .g1t-copy-main,
274 .g1t-copy-more summary {
275 width: 2.5rem;
276 padding: 0;
277 }
278 .g1t-copy :global(svg) {
279 width: 1rem;
280 height: 1rem;
281 }
282 .g1t-copy-menu {
283 width: min(16.5rem, calc(100vw - 2rem));
284 }
285 .g1t-copy-menu a {
286 padding-block: 0.65rem;
287 }
288 }
289</style>