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