Skip to content

Commit

Merge the docs' new look: tabs for each section with their own short sidebar, a larger title under its section, a lavender pill for the current page, links in the text's colour on a lavender underline, On this page on a rail, and a header whose right side is g1t.sh, Source, Sign up and one theme button.

syntaqxcommitted Parents84d1991b6c92bfBrowse 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 }