# Starlight plugin

`heliaStarlight()` is how a Starlight site adopts the theme. It does two
things: it splices the package stylesheets into `customCss` in the order the
cascade depends on, and it installs the shell components that carry no site
vocabulary, which is the theme menu, the mobile menu toggle, and the footer.

```js
import starlight from '@astrojs/starlight';
import { heliaStarlight } from '@ambiqai/helia-ui/starlight';

starlight({
  customCss: ['./src/styles/tailwind.css'],
  plugins: [
    heliaStarlight({
      footer: {
        links: [{ label: 'GitHub', href: 'https://github.com/AmbiqAI' }],
        tagline: 'One line under the logo.',
        logo: 'ambiq',
      },
    }),
  ],
});
```

## Options

| Option                      | Type                                 | Default                               | Effect                                                                                                                                                                                                                                                                                                                             |
| --------------------------- | ------------------------------------ | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `styles`                    | `boolean`                            | `true`                                | Splices the four package stylesheets into `customCss`. `false` leaves `customCss` untouched, and the site loads them itself.                                                                                                                                                                                                       |
| `accent`                    | product id                           | undefined                             | The product identity color, as `data-helia-accent` on the document element. See [Product accent](https://ambiqai.github.io/helia-ui/starlight-plugin/#product-accent).                                                                                                                                                                                                                 |
| `shell.themeSelect`         | `boolean`                            | `true`                                | Installs the `ThemeSelect` override: the theme menu with its three choices.                                                                                                                                                                                                                                                        |
| `shell.mobileMenuToggle`    | `boolean`                            | `true`                                | Installs the `MobileMenuToggle` override.                                                                                                                                                                                                                                                                                          |
| `shell.footer`              | `boolean`                            | `true`                                | Installs the `Footer` override, which renders the brand row and the pagination.                                                                                                                                                                                                                                                    |
| `footer.links`              | `{ label, href }[]`                  | `[]`                                  | Navigation shown on the right of the footer's brand row.                                                                                                                                                                                                                                                                           |
| `footer.tagline`            | `string`                             | `Part of the Ambiq HELIA AI platform` | One line printed under the logo.                                                                                                                                                                                                                                                                                                   |
| `footer.logo`               | `'ambiq' \| false`                   | `'ambiq'`                             | `false` drops the brand lockup and renders the tagline on its own.                                                                                                                                                                                                                                                                 |
| `header`                    | `object`                             | unset                                 | Installs the `Header` override: the site name as text at the top-left, the links, search, and the theme menu. Unset, Starlight's own header stands.                                                                                                                                                                                |
| `header.title`              | `string`                             | site title                            | The name at the top-left. It links to the site root.                                                                                                                                                                                                                                                                               |
| `header.titleRegularPrefix` | `string`                             | unset                                 | Initial title text to render at regular weight; the remaining title stays bold. The `helia` prefix is automatic for names such as `heliaCORE`.                                                                                                                                                                                     |
| `header.links`              | `{ label, href, match? }[]`          | `[]`                                  | The sections beside the name, or the `sections` list when there is one and this is unset. A link is current when the path starts with its `href`, longest prefix first; `match` is the prefix to use instead.                                                                                                                      |
| `header.hub`                | `{ label?, href }`                   | unset                                 | A link back to the HELIA Dev Hub at the end of the bar, in the menu below `42rem`. `label` defaults to `HELIA HUB`; custom destination labels are supported.                                                                                                                                                                       |
| `header.search`             | `boolean`                            | `true`                                | Starlight's search trigger.                                                                                                                                                                                                                                                                                                        |
| `header.themeToggle`        | `boolean`                            | `true`                                | The package theme menu. Off anyway when `shell.themeSelect` is `false`.                                                                                                                                                                                                                                                            |
| `sections`                  | `{ label, href, match?, sidebar }[]` | `[]`                                  | The site's sections, as one definition the top bar and the left sidebar both read. See [Sections](https://ambiqai.github.io/helia-ui/starlight-plugin/#sections).                                                                                                                                                                                                                      |
| `sections[].sidebar`        | `entries \| false`                   | required                              | The section's pages, in Starlight's own sidebar vocabulary. `false` is a section with no pane and no column on its routes. See [Sections](https://ambiqai.github.io/helia-ui/starlight-plugin/#sections).                                                                                                                                                                              |
| `sidebar`                   | `'docs' \| 'always'`                 | `'docs'`                              | `'always'` keeps the left sidebar on every page at desktop widths, a `template: splash` landing page included. A section that declared `sidebar: false` is not one of them.                                                                                                                                                        |
| `shell.head`                | `boolean`                            | `true`                                | Installs the `Head` override: the brand font preload and the per-page discoverability tags.                                                                                                                                                                                                                                        |
| `shell.pageTitle`           | `boolean`                            | `true`                                | Installs the `PageTitle` override, which reads `helia.pageTitle`. See [A page that opens with a hero](https://ambiqai.github.io/helia-ui/starlight-plugin/#a-page-that-opens-with-a-hero).                                                                                                                                                                                             |
| `code`                      | `boolean`                            | `true`                                | Themes Expressive Code onto the package surface. `false` leaves Starlight's code frame alone.                                                                                                                                                                                                                                      |
| `markdownCallouts`          | `boolean`                            | `true`                                | Renders Starlight's `:::note`, `:::tip`, `:::caution` and `:::danger` asides as the package `Callout`, in `.md` and `.mdx` alike. A directive label becomes the title. `false` leaves Starlight's asides alone. Registered on whichever markdown processor the site configured. See [Callouts](https://ambiqai.github.io/helia-ui/callouts/#markdown-form). |
| `discoverability`           | `object \| false`                    | `{}`                                  | Search-engine and agent metadata. `false` switches the lot off; see [Discoverability](https://ambiqai.github.io/helia-ui/starlight-plugin/discoverability/).                                                                                                                                                                                                |
| `discoverability.ogImage`   | `boolean`                            | `true`                                | Per-page Open Graph card at `<route>/og.svg`, with the `og:image` and `twitter:image` tags that point at it.                                                                                                                                                                                                                       |
| `discoverability.jsonLd`    | `boolean`                            | `true`                                | `WebSite` and `Organization` on the home page, `TechArticle` elsewhere, and a `BreadcrumbList` from the sidebar path.                                                                                                                                                                                                              |
| `discoverability.markdown`  | `boolean`                            | `true`                                | `<route>/index.md` renditions and the `alternate` link that points at them.                                                                                                                                                                                                                                                        |
| `discoverability.llms`      | `boolean`                            | `true`                                | `llms.txt`, `llms-full.txt`, and `content-index.json` at the site root.                                                                                                                                                                                                                                                            |

## The product top bar

A product site puts its own name at the top-left, as text, with its sections
beside it. That is the `header` option, and it installs a `Header` override in
place of Starlight's:

```js
heliaStarlight({
  header: {
    title: 'heliaCORE',
    links: [
      { label: 'Getting started', href: '/heliaCORE/getting-started/' },
      { label: 'Guides', href: '/heliaCORE/guides/' },
      { label: 'API', href: '/heliaCORE/api/' },
    ],
  },
});
```

The `helia` prefix in a title such as `heliaCORE` uses regular weight while
the product suffix stays bold. Other product names can opt into the same
treatment with `header.titleRegularPrefix`. For example, set `title` to
`'physioKIT'` and `titleRegularPrefix` to `'physio'` to render `physio` at
regular weight and `KIT` bold. The prefix must match the start of the title
and leave a nonempty suffix.

An `href` is a path on the site, written the way a footer link is: with the
site's `base` on the front, since the plugin does not prefix it for you. A link
is current when the page's path starts with its `href`, so a section stays lit
on the pages under it; `match` names a different prefix for a section whose
pages do not live under its link. A link to the site root is current on the
root alone, and where two links both match, the longer prefix is the one that
lights, so only ever one of them does.

`header.hub` puts a link back to the HELIA Dev Hub at the end of the bar, so a
product site reads as one of a family rather than as a site on its own:

```js
heliaStarlight({
  header: {
    title: 'heliaCORE',
    hub: { href: 'https://ambiqai.github.io/helia-developer-hub/' },
  },
});
```

The outlined link uses the navigation foreground in either theme. `label`
replaces the wording, which defaults to `HELIA HUB`. The link stays in
the bar when the section links become a dropdown below `62rem`. Below `42rem`,
the section dropdown includes a HELIA AI DEV Hub destination and the
duplicate header link is hidden. A header with no section links keeps the Hub
link visible. Documentation sidebars also carry the Hub link on narrow screens.

Search and the theme menu sit at the end of the bar, and either can be dropped
with `header.search: false` or `header.themeToggle: false`. What Starlight
draws there by default and this bar does not is the social icon row: a site
that wants those links keeps them by naming them in `links`.

Below `62rem`, the section links move into a dropdown in the shared header.
Documentation pages also have a separate button for the current section's
sidebar pages. Landing sections declaring `sidebar: false` keep the dropdown
and omit that button. The sidebar button uses Starlight's menu state so its
overlay and scroll lock stay synchronized. This site uses the shared header,
so the bar above this page demonstrates that behavior.

## Sections

A product site is read one part at a time. `sections` is the option that says
so: the top bar lists the site's sections, and a page inside one gets a left
sidebar of that section's pages under the section's name. Both come from the
one list, so the bar and the pane cannot drift apart.

```js
heliaStarlight({
  header: { title: 'heliaCORE' },
  sections: [
    { label: 'Home', href: '/heliaCORE/', sidebar: false },
    {
      label: 'Getting started',
      href: '/heliaCORE/getting-started/',
      sidebar: [
        { label: 'Choose your path', slug: 'getting-started' },
        { label: 'CMake', slug: 'getting-started/cmake' },
        { label: 'Zephyr', slug: 'getting-started/zephyr' },
      ],
    },
    {
      label: 'Architecture',
      href: '/heliaCORE/architecture/',
      sidebar: [{ autogenerate: { directory: 'architecture' } }],
    },
  ],
});
```

A section is a `label`, an `href` written with the site's `base` on the front
the way a bar link is, an optional `match` for a section whose pages do not
live under its `href`, and a `sidebar` of Starlight's own entries: links,
slugs, groups, `autogenerate` directories, badges, or `false` for a section
with no pages of its own. They are Starlight's
entries, not a copy of them, because the plugin hands them back to Starlight
as sidebar config; nothing about them is resolved here.

- **The bar.** `header.links` is derived from the sections when the site does
  not write its own. A site whose bar carries something that is not a section,
  a link out to GitHub for instance, writes `header.links` and keeps both.
- **The pane.** The section's entries become the whole sidebar, with the
  section's name above them as a heading rather than as an entry. The group the
  entries were declared under is not drawn: it is the heading.
- **The landing page.** Give the Home section the site's base as its `href`. A
  base prefix would otherwise match every page on the site, so a section at the
  base is current on the landing page alone. A landing page has the bar above
  it and the page itself to navigate from, so give Home `sidebar: false` and it
  is read at the full width of the frame.
- **A section with no pane.** `sidebar: false` in place of the entries is a
  section with no pages of its own: no pane on its routes and no column
  reserved for one, whatever `sidebar: 'always'` says, since that option is
  about a template rather than about a section. The section still lists in the
  bar and in the narrow-width menu, as a link rather than an expandable item.
- **Narrow screens.** With a configured header, the section dropdown replaces
  the bar's links below `62rem`. Documentation pages retain a menu button for
  the current section's sidebar. Sections with `sidebar: false` have no menu
  button or sidebar. Without a configured header, section switching remains
  available through Starlight's mobile sidebar.
- **Which section a page is in.** The longest matching prefix wins, so a
  section nested under another one takes its own pages. A page under no section
  keeps the sidebar the site declared in `sidebar`, or the whole navigation if
  it declared none.

[The sections demo](https://ambiqai.github.io/helia-ui/starlight-plugin/sections/) is three sections
wired up this way. It is a fixture rather than this site's own navigation: this
site's bar links out to GitHub and leaves several groups off the bar, and
sections only work as navigation when every part of the site is one.

### Generated reference pages

Generated pages sit at most one level under their group. The trap is the shape
a generator's own fragment usually arrives in, a group wrapping an
`autogenerate`:

```js
/* Two levels: Reference > Generated C API > Convolution > conv page. */
{
  label: 'Reference',
  href: '/heliaCORE/reference/',
  sidebar: [
    { label: 'Generated C API', items: [{ autogenerate: { directory: 'reference/api' } }] },
  ],
}
```

Inside a section there is no need for the wrapper, because the section itself
is the heading the entries sit under. Declare one entry per group instead and
the groups are the top level of the pane, with module pages one level under
them:

```js
/* The generator's directory names, one group each. */
const GROUPS = [
  ['Activation', 'activation'],
  ['Basic math', 'basicmath'],
  /* ...one row per group the generator emits. */
];

{
  label: 'Reference',
  href: '/heliaCORE/reference/',
  sidebar: [
    { label: 'C API index', slug: 'reference' },
    ...GROUPS.map(([label, directory]) => ({
      label,
      collapsed: true,
      items: [{ autogenerate: { directory: `reference/api/${directory}` } }],
    })),
    { label: 'API generation notes', slug: 'reference/generation' },
  ],
}
```

A generator that emits a sidebar fragment of its own splices in the same way:
the fragment has to be the list of groups, not a single group containing them.
If it hands you the wrapped shape, spread its `items` rather than the entry
itself.

## Keeping the sidebar on a landing page

Starlight gives a `template: splash` page no sidebar, which is right for a hub
landing page and wrong for a product site: a reader who arrives on a product's
landing page should be able to see the rest of the product from it. Set
`sidebar: 'always'` and every page keeps the left sidebar at desktop widths,
the landing page included. Narrow screens are untouched: the sidebar stays
off-canvas there, as it is on a documentation page.

```js
heliaStarlight({ sidebar: 'always' });
```

A site with `sections` says the same thing per section instead: a section that
declared `sidebar: false` has no pane on its routes, and this option does not
put one back there.

The plugin does this with a route middleware rather than a component override.
Starlight decides a page has no sidebar from its template, and one flag on the
route carries that decision to the page shell, to the frame, and to the layout
widths; the middleware sets the flag back, so the sidebar is Starlight's own
and the widths follow it. This site sets the option, and
[Landing page example](https://ambiqai.github.io/helia-ui/starlight-plugin/landing-example/) is what it
looks like.

## A page that opens with a hero

Starlight draws the page title above the content of every page whose
frontmatter carries no `hero`, whatever the template. A page whose content
opens with the `Hero` part therefore shows its name twice, with a band above
the card, and ships two `h1`s. `helia: { pageTitle: false }` drops Starlight's
heading and leaves the hero's as the page's only one.

The field is not Starlight's, and a key its schema does not declare is stripped
before a component can read it. Starlight has no hook for a plugin to extend
that schema either, so the site names the fragment once in `content.config.ts`:

```ts
import { defineCollection } from 'astro:content';
import { docsLoader } from '@astrojs/starlight/loaders';
import { docsSchema } from '@astrojs/starlight/schema';
import { heliaFrontmatterSchema } from '@ambiqai/helia-ui/starlight';

export const collections = {
  docs: defineCollection({
    loader: docsLoader(),
    schema: docsSchema({ extend: heliaFrontmatterSchema }),
  }),
};
```

The page then asks for it:

```mdx
---
title: heliaCORE
helia:
  pageTitle: false
---

<Hero variant="contrast" eyebrow="Kernel library">
  <Fragment>
    Kernel <em>acceleration</em> for Ambiq AI.
  </Fragment>
</Hero>
```

The heading goes; a bare `_top` anchor stays in its place, because that is what
the skip link and the overview entry in the table of contents point at, and the
panel that held the title collapses so the hero is the first thing under the
header. [Hero page example](https://ambiqai.github.io/helia-ui/starlight-plugin/hero-page/) is the page
this site builds from that frontmatter.

Suppressing the title removes the only `h1` Starlight gave the page, so the
hero has to keep its own: `Hero` renders an `h1` by default, and
`titleAs="h2"` belongs on a section hero further down the page rather than on
the one at the top. Leave the frontmatter field off and nothing changes.

## Product accent

`--helia-product-accent` is the one color a site gets to call its own: the
accent word in a `Hero`, an accent `Badge`, the outline on a primary action.
Without an accent it resolves to slate, which is a site with no identity color
rather than a site with a neutral one.

`accent` names a product the package already holds a color for. The plugin puts
that id on the document element as `data-helia-accent`, and `semantic.css`
derives the accent from it, so the palette stays in the package and the site
writes no CSS.

```js
heliaStarlight({ accent: 'helia-core' });
```

The ids are `helia-aot`, `helia-core`, `helia-dsp`, `helia-edge`, `helia-ml`,
`helia-profiler`, `helia-rt`, `kit-compression`, `kit-heart`, `kit-physio`,
`kit-sleep`, `kit-sound`, and `kit-vision`. Anything else fails the build rather
than falling back quietly.

The attribute is set from the inline script the `Head` override emits, which
is the only scope a component rendered inside `&lt;head&gt;` can reach. It
needs `shell.head` left installed, and with scripting off the accent stays
slate. A page that carries several products puts `data-helia-accent` on each
subtree itself; the derivation is the same.

### How much of it to spend

Setting the accent is not the same as using it. The budget below is what a
product site spends on a full landing page, and it is the same budget on every
page of that site: enough that the color means "this one", little enough that
it still can.

| Moment               | What carries it                                  | How often                    |
| -------------------- | ------------------------------------------------ | ---------------------------- |
| The hub link         | The link out to the HELIA Dev Hub in the top bar | Once, in the chrome          |
| The eyebrow dot      | `Hero`'s eyebrow on a contrast ground            | Once, in the hero            |
| The accent word      | One or two words of the `Hero` headline          | Once, in the hero            |
| The primary action   | `Button variant="primary"`, its outline only     | Once, in the hero            |
| The page's own layer | `Stack` with `accent` on one layer               | Once, where a diagram sits   |
| The leading bar      | `Bars` with `accent` on one row                  | Once per comparison          |
| The unit glyph       | `BigNumber accent`, the × or the %               | Once per row of figures      |
| The section link     | The link beside a section heading                | Once per section             |
| Link hover           | Any link in prose, under the pointer             | Not counted: it is transient |

The ink is scarcer: one inked field above the fold, which is usually
`Hero variant="contrast"`, and at most one `Card` or `LinkCard` with
`tone="ink"` per set below it. Two inked tiles in one mosaic read as a dark row
with a hole in it.

Off the budget entirely: card edges and brims, a colored fill behind body copy,
persistent colored marks, and a second accent color on the same page. A page
that really does carry two products scopes each subtree with
`data-helia-accent` rather than introducing a color the plugin does not know
about.

Every part named above draws from `--helia-product-accent`, so all of it
follows from the one `accent` option and the site still writes no CSS. The
longer argument, and the mockup the budget was drawn from, are in the hub's
design system document.

## Stylesheet order is the contract

The four sheets the plugin injects are `tokens.css`, `semantic.css`,
`recipes.css`, and `starlight.css`, in that order. They go after the leading
Tailwind roots in your `customCss` and before everything else:

```
./src/styles/tailwind.css       your Tailwind entry, which imports
                                @ambiqai/helia-ui/starlight-tailwind.css
@ambiqai/helia-ui/tokens.css    injected
@ambiqai/helia-ui/semantic.css  injected
@ambiqai/helia-ui/recipes.css   injected
@ambiqai/helia-ui/starlight.css injected
./src/styles/site.css           yours
```

Two properties depend on that position. The package sheets are unlayered, so
they outrank every cascade layer the Tailwind entry declares, which is what
keeps the tokens authoritative over the Starlight Tailwind compat package. And
the site's own sheet lands last, so a page-specific override needs no extra
specificity to win.

The plugin finds the insertion point by treating any leading `customCss` entry
whose filename matches `tailwind*.css` as a Tailwind root. If your entry is
named something else, put it first and name it accordingly, or set
`styles: false` and order the sheets yourself.

## Overrides you already set win

A Starlight plugin's `updateConfig` shallow-merges over the site's config, so
an array or a map written by a plugin would replace the site's rather than add
to it. Both values this plugin touches are read back from the config and merged
by hand instead. `customCss` is spliced, not replaced, and a shell override is
installed only under a key the site left alone.

Name `Footer` in Starlight's `components` map and the plugin will not touch
it, with no need to set `shell.footer: false`. The same holds for
`ThemeSelect` and `MobileMenuToggle`. Use `shell.*: false` when you want
Starlight's stock component back rather than one of your own.

## How options reach the shell

Starlight hands nothing to a component override, so the resolved options reach
the Footer through the module graph. The plugin adds a small Astro integration
that serves them from the virtual module `virtual:helia-ui/starlight-config`,
which is how Starlight itself does this and keeps the values build-time
constants rather than runtime lookups. Nothing outside the package should
import that module: it is an implementation detail of the shell components, and
its shape is not part of the export map.

## Discoverability

The plugin also emits the search-engine and agent metadata every HELIA site
needs, so no site authors its own.
[Discoverability](https://ambiqai.github.io/helia-ui/starlight-plugin/discoverability/) states the
contract: what lands in the head, which artifacts land at the site root, and how
a page opts out of the description rule.
