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

Commit

The docs read as sections, each with its own short sidebar. The tabs under the header are Get started, Chat and agents, Code, Projects, Workspaces, Self-hosting and API reference, each holding some of the sidebar's groups (tabs.ts), where Documentation held all of them in one long list; the API's groups are told apart by their pages, since a tag can share a guide group's name. A page's title is larger, with its section above it in lavender; sidebar groups are headed in the page's ink rather than small capitals, and the current page is a lavender pill. Links are the text's own colour on a lavender underline, On this page runs on a rail that marks the heading being read, and link cards turn toward the accent under the pointer. The header's right side is g1t.sh, Source and a Sign up button, then one theme button that moves between Auto, Light and Dark, in place of the full theme select; the phone menu keeps the select, and the two stay in step.

syntaqxcommitted Parentacf6e66Browse files
7 files+289−1170/7 viewed
+24−17
77 // and when it does not. Here the row is three columns, the outer two always
88 // the same width, so the search sits at the header's centre on every page.
99 import Search from '@astrojs/starlight/components/Search.astro';
10−// Auto, Light or Dark, kept in this browser; Auto follows the system.
11−// On tablets and phones it is at the foot of the menu instead.
12−import ThemeSelect from '@astrojs/starlight/components/ThemeSelect.astro';
1310
14−import { TABS, tabFor } from '../tabs';
11+import { NAV } from '../route-data';
1512 import SiteTitle from './SiteTitle.astro';
1613 import SocialIcons from './SocialIcons.astro';
14+// Auto, Light or Dark, kept in this browser; Auto follows the system.
15+// On tablets and phones it is at the foot of the menu instead.
16+import ThemeToggle from './ThemeToggle.astro';
1717
18−const current = tabFor(Astro.locals.starlightRoute.id);
18+const tabs = NAV.get(Astro.locals.starlightRoute)?.tabs ?? [];
1919 ---
2020
2121 <div class="g1t-header">
2424 <div class="g1t-header-search print:hidden"><Search /></div>
2525 <div class="g1t-header-links print:hidden">
2626 <SocialIcons />
27− <div class="g1t-header-theme"><ThemeSelect /></div>
27+ <div class="g1t-header-theme"><ThemeToggle /></div>
2828 </div>
2929 </div>
3030 <nav class="g1t-tabs" aria-label="Sections">
3131 {
32− TABS.map((tab) => (
33− <a href={tab.href} aria-current={tab === current ? 'page' : undefined}>
32+ tabs.map((tab) => (
33+ <a href={tab.href} aria-current={tab.current ? 'page' : undefined}>
3434 {tab.label}
3535 </a>
3636 ))
3838 </nav>
3939 </div>
4040
41+<script>
42+ // On a phone the tabs scroll sideways: start with the current one in view.
43+ const current = document.querySelector<HTMLElement>('.g1t-tabs [aria-current]');
44+ const tabs = current?.parentElement;
45+ if (current && tabs && current.offsetLeft + current.offsetWidth > tabs.clientWidth) {
46+ tabs.scrollLeft = current.offsetLeft - (tabs.clientWidth - current.offsetWidth) / 2;
47+ }
48+</script>
49+
4150 <style>
4251 .g1t-header {
4352 display: flex;
7887 display: flex;
7988 align-items: center;
8089 justify-content: flex-end;
81− gap: 0.75rem;
90+ gap: 0.5rem;
8291 min-width: 0;
8392 }
8493 .g1t-header-theme {
8594 display: flex;
8695 align-items: center;
87− padding-inline-start: 0.75rem;
88− border-inline-start: 1px solid var(--g1t-line);
89− height: 1.25rem;
9096 }
91− /* Desktop: the links and the theme select together are wider. On a
92− narrow desktop the select shows only its icon (styles/g1t.css), so the
93− search keeps its room. */
97+ /* Desktop: the links and the theme button together are wider. */
9498 @media (min-width: 50rem) {
9599 .g1t-header-top {
96100 --g1t-header-side: 17.5rem;
127131 }
128132 }
129133 .g1t-tabs {
134+ /* The tabs' own offsets are measured from here. */
135+ position: relative;
130136 display: flex;
131137 gap: 1.5rem;
132138 height: 2.75rem;
142148 display: flex;
143149 flex-shrink: 0;
144150 align-items: center;
145− border-bottom: 1.5px solid transparent;
151+ border-bottom: 2px solid transparent;
146152 font-size: 0.875rem;
147153 font-weight: 500;
148154 white-space: nowrap;
154160 color: var(--g1t-fg);
155161 }
156162 .g1t-tabs a[aria-current='page'] {
157− border-color: var(--g1t-fg);
163+ border-color: var(--g1t-accent);
164+ font-weight: 600;
158165 color: var(--g1t-fg);
159166 }
160167 </style>
+21−7
11 ---
2−// A page's title, what it is about beneath it, and a way to take the page
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
34 // to an AI assistant: copied as Markdown, read as Markdown, or opened in
45 // Claude or ChatGPT.
56 import { ArrowUpRight, ChevronDown, Copy, FileText, MessageSquare } from '@lucide/astro';
67
8+import { NAV } from '../route-data';
9+
710 const { entry, id } = Astro.locals.starlightRoute;
811 const { title, description } = entry.data;
12+const section = NAV.get(Astro.locals.starlightRoute)?.section;
913 const markdown = `/${id === '' || id === 'index' ? 'index' : id}.md`;
1014 const absolute = new URL(markdown, Astro.site).href;
1115 const ask = encodeURIComponent(`Read ${absolute} so I can ask questions about it.`);
1216 ---
1317
1418 <div class="g1t-page-title">
19+ {section && <p class="g1t-eyebrow">{section}</p>}
1520 <div class="g1t-page-title-row">
1621 <h1 id="_top">{title}</h1>
1722 <div class="g1t-copy not-content" data-pagefind-ignore>
7782 justify-content: space-between;
7883 gap: 1.5rem;
7984 }
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+ }
8094 h1 {
8195 margin: 0;
8296 font-family: var(--g1t-font-display);
8397 font-optical-sizing: auto;
84− font-size: 2rem;
98+ font-size: 2.75rem;
8599 font-weight: 600;
86− letter-spacing: -0.015em;
87− line-height: 1.2;
100+ letter-spacing: -0.03em;
101+ line-height: 1.08;
88102 color: var(--g1t-fg);
89103 }
90104 .g1t-description {
91− margin-top: 0.6rem;
105+ margin-top: 0.9rem;
92106 max-width: 42rem;
93− font-size: 1.125rem;
107+ font-size: 1.1875rem;
94108 line-height: 1.55;
95109 color: var(--g1t-muted);
96110 }
233247 }
234248 h1 {
235249 min-width: 0;
236− font-size: 1.75rem;
250+ font-size: 2rem;
237251 overflow-wrap: anywhere;
238252 }
239253 .g1t-copy {
+29−28
11 ---
2−// The header's right side: where people go from the docs, then the source.
2+// The header's right side: g1t.sh, its source, and signing up.
33 // g1t's social accounts live in g1t.sh's footer, not here.
4−import { GitBranch } from '@lucide/astro';
4+import { ChevronRight } from '@lucide/astro';
55 ---
66
77 <nav class="g1t-nav" aria-label="g1t">
88 <a href="https://g1t.sh/">g1t.sh</a>
9− <a href="https://g1t.sh/register" class="g1t-nav-cta">Sign up</a>
10− <a href="https://g1t.sh/flagon-io/g1t" aria-label="Source on g1t" title="Source on g1t" class="g1t-nav-icon">
11− <GitBranch />
12− </a>
9+ <a href="https://g1t.sh/flagon-io/g1t">Source</a>
10+ <a href="https://g1t.sh/register" class="g1t-nav-cta">Sign up<ChevronRight /></a>
1311 </nav>
1412
1513 <style>
2422 display: inline-flex;
2523 align-items: center;
2624 height: 2rem;
27− border-radius: 0.4rem;
25+ border-radius: 0.5rem;
2826 padding: 0 0.6rem;
29− font-size: 0.85rem;
27+ font-size: 0.875rem;
28+ font-weight: 500;
3029 white-space: nowrap;
3130 color: var(--g1t-muted);
3231 text-decoration: none;
3635 background: var(--g1t-raised);
3736 color: var(--g1t-fg);
3837 }
38+ /* The one call to action: a raised button, its chevron nudging on hover. */
3939 nav.g1t-nav .g1t-nav-cta {
40− margin-left: 0.25rem;
41− background: var(--g1t-fg);
42− color: var(--g1t-bg);
43− font-weight: 500;
40+ gap: 0.15rem;
41+ margin-left: 0.35rem;
42+ padding-inline: 0.75rem 0.5rem;
43+ background: var(--g1t-raised);
44+ box-shadow: inset 0 0 0 1px var(--g1t-line-strong);
45+ color: var(--g1t-fg);
46+ font-weight: 600;
4447 }
4548 nav.g1t-nav .g1t-nav-cta:hover {
46− background: var(--g1t-docs-fg-hover);
47− color: var(--g1t-bg);
49+ background: color-mix(in srgb, var(--g1t-accent) 12%, var(--g1t-raised));
50+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--g1t-accent) 50%, var(--g1t-line-strong));
51+ color: var(--g1t-fg);
4852 }
49− nav.g1t-nav .g1t-nav-icon {
50− justify-content: center;
51− width: 2rem;
52− padding: 0;
53+ .g1t-nav-cta :global(svg) {
54+ width: 0.875rem;
55+ height: 0.875rem;
56+ color: var(--g1t-muted);
57+ transition: transform 0.15s, color 0.15s;
5358 }
54− .g1t-nav-icon :global(svg) {
55− width: 1rem;
56− height: 1rem;
59+ .g1t-nav-cta:hover :global(svg) {
60+ transform: translateX(2px);
61+ color: var(--g1t-accent);
5762 }
58− /* Tablets and phones: in the header g1t.sh's link gives way (the menu
59− keeps it), and the icons grow to a size a finger can hit. */
63+ /* Tablets and phones: in the header only Sign up stays (the menu keeps
64+ the rest). */
6065 @media (max-width: 49.99rem) {
61− :global(.g1t-header) nav.g1t-nav a:not(.g1t-nav-icon):not(.g1t-nav-cta) {
66+ :global(.g1t-header) nav.g1t-nav a:not(.g1t-nav-cta) {
6267 display: none;
63− }
64− nav.g1t-nav .g1t-nav-icon {
65− width: 2.5rem;
66− height: 2.5rem;
6768 }
6869 :global(.sidebar-content) nav.g1t-nav a {
6970 height: 2.5rem;
+105−0
1+---
2+// The header's theme button: one icon, for Auto (the system's), Light or
3+// Dark, and a click moves to the next. The same choice, kept in the same
4+// place, as Starlight's theme select at the foot of the phone menu, so
5+// the two stay in step.
6+import { Monitor, Moon, Sun } from '@lucide/astro';
7+---
8+
9+<button type="button" class="g1t-theme" data-g1t-theme aria-label="Theme: Auto">
10+ <Monitor class="g1t-theme-auto" />
11+ <Sun class="g1t-theme-light" />
12+ <Moon class="g1t-theme-dark" />
13+</button>
14+
15+<script is:inline>
16+ // Before the first paint, so the button never shows the wrong icon.
17+ (() => {
18+ let stored = null;
19+ try {
20+ stored = localStorage.getItem('starlight-theme');
21+ } catch {}
22+ const choice = stored === 'light' || stored === 'dark' ? stored : 'auto';
23+ for (const button of document.querySelectorAll('[data-g1t-theme]')) button.dataset.choice = choice;
24+ })();
25+</script>
26+
27+<script>
28+ type Theme = 'auto' | 'light' | 'dark';
29+ const NEXT: Record<Theme, Theme> = { auto: 'light', light: 'dark', dark: 'auto' };
30+ const NAMES: Record<Theme, string> = { auto: 'Auto', light: 'Light', dark: 'Dark' };
31+ const KEY = 'starlight-theme';
32+
33+ const load = (): Theme => {
34+ try {
35+ const stored = localStorage.getItem(KEY);
36+ return stored === 'light' || stored === 'dark' ? stored : 'auto';
37+ } catch {
38+ return 'auto';
39+ }
40+ };
41+
42+ function show(theme: Theme) {
43+ document.querySelectorAll<HTMLButtonElement>('[data-g1t-theme]').forEach((button) => {
44+ button.dataset.choice = theme;
45+ button.setAttribute('aria-label', `Theme: ${NAMES[theme]}`);
46+ });
47+ }
48+
49+ function choose(theme: Theme) {
50+ const system = matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
51+ document.documentElement.dataset.theme = theme === 'auto' ? system : theme;
52+ try {
53+ localStorage.setItem(KEY, theme === 'auto' ? '' : theme);
54+ } catch {}
55+ // Starlight's own select, in the phone menu, shows the same choice.
56+ (window as { StarlightThemeProvider?: { updatePickers(theme: Theme): void } }).StarlightThemeProvider?.updatePickers(
57+ theme,
58+ );
59+ show(theme);
60+ }
61+
62+ show(load());
63+ document.querySelectorAll<HTMLButtonElement>('[data-g1t-theme]').forEach((button) => {
64+ button.addEventListener('click', () => choose(NEXT[load()]));
65+ });
66+ // A choice made in the phone menu's select.
67+ document.addEventListener('change', (event) => {
68+ if ((event.target as Element).closest('starlight-theme-select')) show(load());
69+ });
70+</script>
71+
72+<style>
73+ .g1t-theme {
74+ display: inline-flex;
75+ align-items: center;
76+ justify-content: center;
77+ width: 2rem;
78+ height: 2rem;
79+ border: 0;
80+ border-radius: 0.5rem;
81+ padding: 0;
82+ background: transparent;
83+ color: var(--g1t-muted);
84+ cursor: pointer;
85+ transition: color 0.15s, background 0.15s;
86+ }
87+ .g1t-theme:hover {
88+ background: var(--g1t-raised);
89+ color: var(--g1t-fg);
90+ }
91+ .g1t-theme:focus-visible {
92+ outline: 2px solid var(--g1t-accent);
93+ outline-offset: 1px;
94+ }
95+ .g1t-theme :global(svg) {
96+ display: none;
97+ width: 1rem;
98+ height: 1rem;
99+ }
100+ .g1t-theme[data-choice='auto'] :global(.g1t-theme-auto),
101+ .g1t-theme[data-choice='light'] :global(.g1t-theme-light),
102+ .g1t-theme[data-choice='dark'] :global(.g1t-theme-dark) {
103+ display: block;
104+ }
105+</style>
+39−10
11 import { defineRouteMiddleware } from '@astrojs/starlight/route-data';
22 import { ogVersion } from '@g1t/contracts/og';
33
4−import { tabFor } from './tabs';
4+import { TABS, type Tab, tabForGroup } from './tabs';
55
66 type Entry = { type: string; label?: string; href?: string; isCurrent?: boolean; entries?: Entry[] };
77
3434 return undefined;
3535 }
3636
37+/** What the header and the page's title show: the tabs, and where the page sits. */
38+export type Nav = {
39+ tabs: { label: string; href: string; current: boolean }[];
40+ /** The sidebar group holding the page, or its tab's name, shown above its title. */
41+ section?: string;
42+};
43+
44+/** Each route's Nav, set here for Header.astro and PageTitle.astro. */
45+export const NAV = new WeakMap<object, Nav>();
46+
3747 /**
38− * Each page's sidebar holds only its tab's groups: those whose pages
39− * belong to the same tab. Groups are told apart by their pages, not their
40− * labels, since a guide group and an API group can share a name.
48+ * The tab a top-level sidebar group belongs to. The API's groups are told
49+ * apart by their pages, not their labels, since an API tag can share a
50+ * guide group's name.
51+ */
52+function tabOf(group: Entry): Tab {
53+ const slug = firstSlug(group.entries ?? []) ?? '';
54+ return slug.startsWith('reference/') ? TABS[TABS.length - 1] : tabForGroup(group.label ?? '');
55+}
56+
57+/** Whether the current page is in these entries, at any depth. */
58+function holdsCurrent(entries: Entry[]): boolean {
59+ return entries.some((entry) => entry.isCurrent || holdsCurrent(entry.entries ?? []));
60+}
61+
62+/**
63+ * Each page's sidebar holds only its tab's groups.
4164 *
4265 * Each page also gets its own social card: its title, its section and its
4366 * description, drawn by the og service.
4467 */
4568 export const onRequest = defineRouteMiddleware((context) => {
4669 const route = context.locals.starlightRoute;
47− const section = sectionOf(route.sidebar as Entry[]);
48− const tab = tabFor(route.id);
49− route.sidebar = route.sidebar.filter((entry) => {
50− if (entry.type !== 'group') return false;
51− const slug = firstSlug(entry.entries as Entry[]);
52− return slug !== undefined && tabFor(slug) === tab;
70+ const groups = (route.sidebar as Entry[]).filter((entry) => entry.type === 'group');
71+ const section = sectionOf(groups);
72+ // A page in no group, such as the home page, is in the first tab.
73+ const holder = groups.find((group) => holdsCurrent(group.entries ?? []));
74+ const tab = holder ? tabOf(holder) : TABS[0];
75+ // A tab opens on the first page of its first group; the first tab, on the home page.
76+ const tabs = TABS.map((each) => {
77+ const group = each === TABS[0] ? undefined : groups.find((entry) => tabOf(entry) === each);
78+ const slug = group ? firstSlug(group.entries ?? []) : undefined;
79+ return { label: each.label, href: slug === undefined ? '/' : `/${slug}/`, current: each === tab };
5380 });
81+ NAV.set(route, { tabs, section: section ?? (route.id === '' ? undefined : tab.label) });
82+ route.sidebar = route.sidebar.filter((entry) => entry.type === 'group' && tabOf(entry as Entry) === tab);
5483
5584 const { title, description } = route.entry.data;
5685 const card = new URL('/docs', OG);
+54−40
4545 --sl-menu-button-size: 2.5rem;
4646 }
4747
48−/* Starlight's theme select (Auto, Light, Dark): in the header's links, and
49− at the foot of the phone menu. Drawn like the links beside it. */
48+/* Starlight's theme select (Auto, Light, Dark), at the foot of the phone
49+ menu; the header has its own button (components/ThemeToggle.astro). */
5050 starlight-theme-select label {
5151 color: var(--g1t-muted);
5252 font-size: 0.85rem;
6060 starlight-theme-select option {
6161 background: var(--g1t-surface);
6262 color: var(--g1t-fg);
63−}
64−/* A narrow desktop: in the header, only the select's icon and caret; the
65− choice is still its accessible name and in the list it opens. */
66−@media (min-width: 50rem) and (max-width: 63.99rem) {
67− .g1t-header-theme starlight-theme-select select {
68− width: 3.625rem;
69− color: transparent;
70− }
7163 }
7264
7365 /* --- Header --------------------------------------------------------------- */
161153 /* --- Sidebar -------------------------------------------------------------- */
162154
163155 .sidebar-content {
164− padding-top: 1.25rem;
156+ padding-top: 1.5rem;
165157 }
166158 /* Groups always show their pages: no caret, no folding. */
167159 .sidebar-content details > summary {
172164 display: none;
173165 }
174166 .sidebar-content .top-level > li + li {
175− margin-top: 1rem;
167+ margin-top: 1.75rem;
176168 }
177−/* Group labels: small and quiet, like section names, not links. */
169+/* Group labels: headings over their pages, in the page's own ink. */
178170 .sidebar-content details > summary .group-label .large,
179171 .sidebar-content .top-level > li > details > summary {
180− font-size: 0.75rem;
172+ font-size: 0.875rem;
181173 font-weight: 600;
182− letter-spacing: 0.06em;
183− text-transform: uppercase;
184− color: var(--g1t-faint);
174+ color: var(--g1t-fg);
185175 }
176+.sidebar-content .top-level > li > details > summary {
177+ padding-inline: 0.75rem;
178+ margin-bottom: 0.35rem;
179+}
186180 .sidebar-content ul ul,
187181 .sidebar-content ul ul li {
188182 border-inline-start: 0;
190184 padding-inline-start: 0;
191185 }
192186 .sidebar-content a {
193− border-radius: 0.375rem;
194− padding: 0.32rem 0.6rem;
187+ border-radius: 0.5rem;
188+ padding: 0.4rem 0.75rem;
195189 font-size: 0.875rem;
196190 color: var(--g1t-muted);
191+ transition: background 0.15s, color 0.15s;
197192 }
198193 .sidebar-content a:hover {
199194 background: var(--g1t-surface);
200195 color: var(--g1t-fg);
201196 }
202−/* The current page: brighter text and a lavender mark, not a filled bar.
203− Square on the mark's side, so the mark is a straight line. */
197+/* The current page: a lavender pill. */
204198 .sidebar-content a[aria-current='page'],
205199 .sidebar-content a[aria-current='page']:hover {
206− border-radius: 0 0.375rem 0.375rem 0;
207− background: var(--g1t-surface);
208− color: var(--g1t-fg);
209− font-weight: 500;
210− box-shadow: inset 2px 0 0 var(--g1t-accent);
200+ background: var(--g1t-accent-low);
201+ color: var(--g1t-docs-accent-high);
202+ font-weight: 600;
211203 }
212204
213205 /* --- Content -------------------------------------------------------------- */
214206
215207 .content-panel {
216− padding-top: 2rem;
208+ padding-top: 2.75rem;
217209 }
218210 /* The title and the page read as one, without a rule between them. */
219211 .content-panel + .content-panel {
234226 }
235227 /* Sections open with space, not rules. */
236228 .sl-markdown-content .sl-heading-wrapper.level-h2 {
237− margin-top: 3rem;
229+ margin-top: 3.5rem;
238230 }
239231 .sl-markdown-content h2 {
240− font-size: 1.5rem;
232+ font-size: 1.75rem;
241233 font-weight: 600;
242− letter-spacing: -0.01em;
234+ letter-spacing: -0.02em;
235+ line-height: 1.2;
243236 color: var(--g1t-fg);
244237 }
245238 .sl-markdown-content h3 {
253246 .sl-markdown-content li {
254247 color: var(--g1t-fg-soft);
255248 }
249+/* Links: the text's own ink, a little heavier, on a lavender underline. */
256250 .sl-markdown-content a:not(.card, .sl-link-button, .sl-link-card) {
257− color: var(--g1t-docs-link);
258− text-decoration-color: color-mix(in srgb, var(--g1t-accent) 45%, transparent);
259− text-underline-offset: 0.2em;
251+ color: var(--g1t-fg);
252+ font-weight: 500;
253+ text-decoration-color: color-mix(in srgb, var(--g1t-accent) 55%, transparent);
254+ text-decoration-thickness: 1.5px;
255+ text-underline-offset: 0.25em;
256+ transition: text-decoration-color 0.15s, color 0.15s;
260257 }
261258 .sl-markdown-content a:not(.card, .sl-link-button, .sl-link-card):hover {
259+ color: var(--g1t-docs-link);
262260 text-decoration-color: var(--g1t-accent);
263261 }
264262 .sl-markdown-content :not(pre) > code {
347345
348346 /* --- On this page --------------------------------------------------------- */
349347
348+.right-sidebar-panel {
349+ padding-top: 2.75rem;
350+}
350351 .right-sidebar-panel h2 {
351− font-size: 0.75rem;
352+ font-size: 0.875rem;
352353 font-weight: 600;
353− letter-spacing: 0.06em;
354− text-transform: uppercase;
355− color: var(--g1t-faint);
354+ color: var(--g1t-fg);
355+}
356+/* The headings on a rail; the one being read is marked on it. */
357+.right-sidebar-panel starlight-toc > nav > ul {
358+ border-inline-start: 1px solid var(--g1t-line);
356359 }
357360 .right-sidebar-panel a {
361+ margin-inline-start: -1px;
362+ border-inline-start: 1px solid transparent;
363+ border-radius: 0;
364+ padding-inline-start: calc(0.75rem + var(--depth) * 0.75rem);
358365 font-size: 0.8125rem;
359366 color: var(--g1t-muted);
367+ transition: color 0.15s, border-color 0.15s;
360368 }
361369 .right-sidebar-panel a:hover {
362370 color: var(--g1t-fg);
363371 }
364372 .right-sidebar-panel a[aria-current='true'] {
373+ border-color: var(--g1t-accent);
365374 color: var(--g1t-docs-link);
375+ font-weight: 500;
366376 }
367377
368378 /* --- Cards and buttons ---------------------------------------------------- */
372382 border-radius: 1rem;
373383 background: var(--g1t-surface);
374384 border: 1px solid var(--g1t-line);
375− transition: border-color 0.15s;
385+ transition: border-color 0.15s, background 0.15s, transform 0.15s;
376386 }
387+/* A link card lifts toward the accent under the pointer. */
377388 .sl-link-card:hover {
378− border-color: var(--g1t-line-strong);
379− background: var(--g1t-surface);
389+ border-color: color-mix(in srgb, var(--g1t-accent) 45%, var(--g1t-line));
390+ background: color-mix(in srgb, var(--g1t-accent) 4%, var(--g1t-surface));
391+}
392+.sl-link-card:hover .icon {
393+ color: var(--g1t-accent);
380394 }
381395 .sl-link-button {
382396 border-radius: 0.5rem;
+17−15
11 /**
22 * The tabs under the header. Each shows its own part of the sidebar: the
3− * groups whose pages fit its `match`. A page belongs to the tab whose
4− * `match` it fits.
3+ * groups named in its `groups`, in the sidebar's order. A page belongs to
4+ * the tab holding its group; the home page, in no group, to the first.
5+ * The API reference tab takes every group not named here, since the
6+ * operations' groups are named after the API's tags.
57 */
6−export type Tab = { label: string; href: string; match: (slug: string) => boolean };
8+export type Tab = { label: string; groups: string[] };
79
810 export const TABS: Tab[] = [
9− {
10− label: 'Documentation',
11− href: '/',
12− match: (slug) => !slug.startsWith('reference/'),
13− },
14− {
15− label: 'API reference',
16− href: '/reference/api/',
17− match: (slug) => slug.startsWith('reference/'),
18− },
11+ { label: 'Get started', groups: ['Get started', 'About'] },
12+ { label: 'Chat and agents', groups: ['Chat and agents', 'Agents on code'] },
13+ { label: 'Code', groups: ['Landing changes', 'Security'] },
14+ { label: 'Projects', groups: ['Projects'] },
15+ { label: 'Workspaces', groups: ['Workspaces', 'Connect your tools'] },
16+ { label: 'Self-hosting', groups: ['Run g1t yourself'] },
17+ { label: 'API reference', groups: [] },
1918 ];
2019
21−export function tabFor(slug: string): Tab {
22− return TABS.find((tab) => tab.match(slug)) ?? TABS[0];
20+const API = TABS[TABS.length - 1];
21+
22+/** The tab that holds the sidebar group with this label. */
23+export function tabForGroup(label: string): Tab {
24+ return TABS.find((tab) => tab.groups.includes(label)) ?? API;
2325 }