flagon-io/g1t

public

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/apps/docs/src/components/PageTitle.astro

180 lines4,761 bytesCodeBlame
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.
5const { entry, id } = Astro.locals.starlightRoute;
6const { title, description } = entry.data;
7const markdown = `/${id === '' || id === 'index' ? 'index' : id}.md`;
8const absolute = new URL(markdown, Astro.site).href;
9const ask = encodeURIComponent(`Read ${absolute} so I can ask questions about it.`);
10---
11
12<div class="g1t-page-title">
13 <div class="g1t-page-title-row">
14 <h1 id="_top">{title}</h1>
15 <div class="g1t-copy">
16 <button type="button" class="g1t-copy-main" data-markdown={markdown}>
17 <svg viewBox="0 0 16 16" aria-hidden="true">
18 <rect x="5" y="5" width="9" height="9" rx="1.5"></rect>
19 <path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5"></path>
20 </svg>
21 <span>Copy page</span>
22 </button>
23 <details class="g1t-copy-more">
24 <summary aria-label="More ways to use this page">
25 <svg viewBox="0 0 16 16" aria-hidden="true"><path d="m4 6 4 4 4-4"></path></svg>
26 </summary>
27 <div class="g1t-copy-menu">
28 <a href={markdown} target="_blank" rel="noopener">
29 <strong>View as Markdown</strong>
30 <span>This page as plain text</span>
31 </a>
32 <a href={`https://claude.ai/new?q=${ask}`} target="_blank" rel="noopener">
33 <strong>Open in Claude</strong>
34 <span>Ask questions about this page</span>
35 </a>
36 <a href={`https://chatgpt.com/?hints=search&q=${ask}`} target="_blank" rel="noopener">
37 <strong>Open in ChatGPT</strong>
38 <span>Ask questions about this page</span>
39 </a>
40 </div>
41 </details>
42 </div>
43 </div>
44 {description && <p class="g1t-description">{description}</p>}
45</div>
46
47<script>
48 document.querySelectorAll<HTMLButtonElement>('.g1t-copy-main').forEach((button) => {
49 button.addEventListener('click', async () => {
50 const label = button.querySelector('span')!;
51 try {
52 const text = await (await fetch(button.dataset.markdown!)).text();
53 await navigator.clipboard.writeText(text);
54 label.textContent = 'Copied';
55 } catch {
56 label.textContent = 'Could not copy';
57 }
58 setTimeout(() => (label.textContent = 'Copy page'), 1600);
59 });
60 });
61 // A menu closes when you click elsewhere.
62 document.addEventListener('click', (event) => {
63 document.querySelectorAll<HTMLDetailsElement>('.g1t-copy-more[open]').forEach((menu) => {
64 if (!menu.contains(event.target as Node)) menu.open = false;
65 });
66 });
67</script>
68
69<style>
70 .g1t-page-title {
71 margin-top: 0.75rem;
72 }
73 .g1t-page-title-row {
74 display: flex;
75 align-items: flex-start;
76 justify-content: space-between;
77 gap: 1.5rem;
78 }
79 h1 {
80 margin: 0;
81 font-size: 2rem;
82 font-weight: 600;
83 letter-spacing: -0.025em;
84 line-height: 1.2;
85 color: var(--g1t-fg);
86 }
87 .g1t-description {
88 margin-top: 0.6rem;
89 max-width: 42rem;
90 font-size: 1.125rem;
91 line-height: 1.55;
92 color: var(--g1t-muted);
93 }
94 .g1t-copy {
95 position: relative;
96 display: flex;
97 flex-shrink: 0;
98 margin-top: 0.35rem;
99 border-radius: 0.5rem;
100 box-shadow: inset 0 0 0 1px var(--g1t-line);
101 }
102 .g1t-copy svg {
103 width: 0.9rem;
104 height: 0.9rem;
105 fill: none;
106 stroke: currentColor;
107 stroke-width: 1.3;
108 stroke-linecap: round;
109 stroke-linejoin: round;
110 }
111 .g1t-copy-main,
112 .g1t-copy-more summary {
113 display: flex;
114 align-items: center;
115 gap: 0.4rem;
116 border: 0;
117 background: transparent;
118 padding: 0.3rem 0.6rem;
119 font: inherit;
120 font-size: 0.8125rem;
121 color: var(--g1t-muted);
122 cursor: pointer;
123 transition: color 0.15s, background 0.15s;
124 }
125 .g1t-copy-main {
126 border-radius: 0.5rem 0 0 0.5rem;
127 }
128 .g1t-copy-more summary {
129 list-style: none;
130 border-left: 1px solid var(--g1t-line);
131 border-radius: 0 0.5rem 0.5rem 0;
132 padding-inline: 0.45rem;
133 }
134 .g1t-copy-more summary::-webkit-details-marker {
135 display: none;
136 }
137 .g1t-copy-main:hover,
138 .g1t-copy-more summary:hover {
139 background: var(--g1t-surface);
140 color: var(--g1t-fg);
141 }
142 .g1t-copy-menu {
143 position: absolute;
144 top: calc(100% + 0.4rem);
145 right: 0;
146 z-index: 10;
147 width: 15rem;
148 border-radius: 0.75rem;
149 padding: 0.35rem;
150 background: var(--g1t-raised);
151 box-shadow:
152 inset 0 0 0 1px var(--g1t-line-strong),
153 0 12px 32px rgb(0 0 0 / 0.45);
154 }
155 .g1t-copy-menu a {
156 display: block;
157 border-radius: 0.5rem;
158 padding: 0.45rem 0.6rem;
159 text-decoration: none;
160 }
161 .g1t-copy-menu a:hover {
162 background: var(--g1t-surface);
163 }
164 .g1t-copy-menu strong {
165 display: block;
166 font-size: 0.8125rem;
167 font-weight: 500;
168 color: var(--g1t-fg);
169 }
170 .g1t-copy-menu span {
171 display: block;
172 font-size: 0.75rem;
173 color: var(--g1t-faint);
174 }
175 @media (max-width: 40rem) {
176 .g1t-copy {
177 display: none;
178 }
179 }
180</style>