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