diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-09-01 14:48:45 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-09-01 14:48:45 +0300 |
| commit | 8a6d697308162874a236c84aac0b0f8c7ac01f83 (patch) | |
| tree | ed7dc7e8c6f50d66bde721fae78576aede929f66 /website/src/components | |
| parent | b54a78aa510b87d1e3b56c619b72eaeb59e4a2a1 (diff) | |
refactor: separate website presentation from runtime
Diffstat (limited to 'website/src/components')
| -rw-r--r-- | website/src/components/AuthorCard.astro | 30 | ||||
| -rw-r--r-- | website/src/components/Banner.astro | 75 | ||||
| -rw-r--r-- | website/src/components/ConfigReference.astro | 204 | ||||
| -rw-r--r-- | website/src/components/DocsVersionSelect.astro | 84 | ||||
| -rw-r--r-- | website/src/components/Head.astro | 67 | ||||
| -rw-r--r-- | website/src/components/Header.astro | 99 | ||||
| -rw-r--r-- | website/src/components/MarketingLayout.astro | 274 | ||||
| -rw-r--r-- | website/src/components/Search.astro | 52 | ||||
| -rw-r--r-- | website/src/components/Sidebar.astro | 53 | ||||
| -rw-r--r-- | website/src/components/SiteLayout.astro | 223 | ||||
| -rw-r--r-- | website/src/components/SiteTitle.astro | 95 |
11 files changed, 0 insertions, 1256 deletions
diff --git a/website/src/components/AuthorCard.astro b/website/src/components/AuthorCard.astro deleted file mode 100644 index ba3a1014..00000000 --- a/website/src/components/AuthorCard.astro +++ /dev/null @@ -1,30 +0,0 @@ ---- -const name = 'Can Celik'; -const role = 'Building Herdr'; -const photo = '/assets/author-can.jpeg'; -const x = 'https://x.com/cancelik'; -const email = 'can@herdr.dev'; ---- - -<aside class="author-card" aria-label="About the author"> - <img class="author-photo" src={photo} width="72" height="72" alt={name} loading="lazy" decoding="async" /> - <div class="author-body"> - <p class="author-name">{name}</p> - <p class="author-role">{role}</p> - <div class="author-links"> - <a href={x} target="_blank" rel="me noopener" aria-label={`${name} on X`}> - <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" fill="currentColor"> - <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" /> - </svg> - <span>X</span> - </a> - <a href={`mailto:${email}`} aria-label={`Email ${name}`}> - <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> - <rect x="2.5" y="4.5" width="19" height="15" rx="2" /> - <path d="m3 6 9 7 9-7" /> - </svg> - <span>Email</span> - </a> - </div> - </div> -</aside> diff --git a/website/src/components/Banner.astro b/website/src/components/Banner.astro deleted file mode 100644 index cc6567a6..00000000 --- a/website/src/components/Banner.astro +++ /dev/null @@ -1,75 +0,0 @@ ---- -import versions from '../data/docs-versions.json'; -import { docsRoute, docsTargetHref } from '../docs-path'; - -const { entry, locale, isFallback } = Astro.locals.starlightRoute; -const { banner } = entry.data; - -const issuesUrl = 'https://github.com/herdrdev/herdr/issues/new?template=translation.yml'; -const translationNotices = { - ja: `このページの翻訳は LLM によって生成されています。誤りに気づいた場合は <a href="${issuesUrl}">GitHub で issue を開いて</a>お知らせください。`, - 'zh-cn': `本页面的翻译由 LLM 生成。如果你发现翻译有误,请<a href="${issuesUrl}">在 GitHub 上提交 issue</a> 告诉我们。`, -}; -const translationNotice = locale && !isFallback ? translationNotices[locale] : undefined; -const docs = docsRoute(Astro.url.pathname); -const archivedVersion = - docs.target !== 'stable' && docs.target !== 'preview' && docs.target !== versions.current - ? docs.target - : undefined; -const latestHref = docsTargetHref(docs, 'stable', versions.scopes); -const archivedNotices = { - root: `This page documents Herdr ${archivedVersion}. <a href="${latestHref}">Switch to the latest release.</a>`, - ja: `このページは Herdr ${archivedVersion} のドキュメントです。<a href="${latestHref}">最新版に切り替えてください。</a>`, - 'zh-cn': `本页面是 Herdr ${archivedVersion} 的文档。<a href="${latestHref}">切换到最新版本。</a>`, -}; -const archivedNotice = archivedVersion ? archivedNotices[locale ?? 'root'] : undefined; ---- - -{banner && <div class="sl-banner" data-pagefind-ignore set:html={banner.content} />} -{archivedNotice && ( - <div class="version-notice" data-pagefind-ignore set:html={archivedNotice} /> -)} -{translationNotice && ( - <div class="translation-notice" data-pagefind-ignore set:html={translationNotice} /> -)} - -<style> - @layer starlight.core { - .sl-banner { - --__sl-banner-text: var(--sl-color-banner-text, var(--sl-color-text-invert)); - padding: var(--sl-nav-pad-y) var(--sl-nav-pad-x); - background-color: var(--sl-color-banner-bg, var(--sl-color-bg-accent)); - color: var(--__sl-banner-text); - line-height: var(--sl-line-height-headings); - text-align: center; - text-wrap: balance; - box-shadow: var(--sl-shadow-sm); - } - .sl-banner :global(a) { - color: var(--__sl-banner-text); - } - - .translation-notice, - .version-notice { - padding: 0.5rem var(--sl-nav-pad-x); - background-color: var(--sl-color-bg-nav); - border-bottom: 1px solid var(--sl-color-hairline); - color: var(--sl-color-gray-2); - font-size: var(--sl-text-xs); - line-height: var(--sl-line-height-headings); - text-align: center; - text-wrap: balance; - } - .version-notice { - background-color: var(--sl-color-orange-low); - border-bottom-color: var(--sl-color-orange); - color: var(--sl-color-orange-high); - } - .translation-notice :global(a) { - color: var(--sl-color-text-accent); - } - .version-notice :global(a) { - color: var(--sl-color-orange-high); - } - } -</style> diff --git a/website/src/components/ConfigReference.astro b/website/src/components/ConfigReference.astro deleted file mode 100644 index 33894993..00000000 --- a/website/src/components/ConfigReference.astro +++ /dev/null @@ -1,204 +0,0 @@ ---- -import stableReference from '../data/config-reference.json'; -import previewReference from '../data/config-reference-preview.json'; -import versionReferences from '../data/config-reference-versions.json'; -import { docsVersion } from '../docs-path'; - -interface Props { - placeholder?: string; - noMatches?: string; - keyCountLabel?: string; -} - -const { - placeholder = 'filter keys, e.g. sidebar, prefix, sound…', - noMatches = 'No keys match this filter.', - keyCountLabel = 'keys', -} = Astro.props; - -const version = docsVersion(Astro.url.pathname); -const archivedReferences = versionReferences as Record<string, typeof stableReference>; -const reference = version === 'preview' - ? previewReference - : version === 'stable' - ? stableReference - : archivedReferences[version]; -if (!reference) { - throw new Error(`documentation version ${version} has no archived configuration reference`); -} -const totalKeys = reference.sections.reduce((sum, section) => sum + section.keys.length, 0); ---- - -<div class="config-reference" data-count-label={keyCountLabel}> - <div class="config-reference-controls"> - <input - type="search" - class="config-reference-filter" - placeholder={placeholder} - autocomplete="off" - spellcheck="false" - aria-label={placeholder} - /> - <p class="config-reference-meta" aria-live="polite">{totalKeys} {keyCountLabel}</p> - </div> - - {reference.sections.map((section) => ( - <section class="config-reference-section"> - <h2 class="config-reference-title" id={`ref-${section.id}`}>{section.title}</h2> - <ul class="config-reference-list"> - {section.keys.map((entry) => ( - <li - class="config-reference-row" - data-search={`${entry.key} ${entry.description} ${(entry.values ?? []).join(' ')}`.toLowerCase()} - > - <div class="config-reference-head"> - <code class="config-reference-key">{entry.key}</code> - <span class="config-reference-type">{entry.type}</span> - {entry.default && ( - <span class="config-reference-default"> - default <code>{entry.default}</code> - </span> - )} - </div> - <p class="config-reference-desc"> - {entry.description} - {entry.values && ( - <span class="config-reference-values"> - {entry.values.map((value) => ( - <code>{value}</code> - ))} - </span> - )} - </p> - </li> - ))} - </ul> - </section> - ))} - - <p class="config-reference-empty" hidden>{noMatches}</p> -</div> - -<style> - .config-reference-controls { - display: flex; - align-items: baseline; - gap: 1rem; - margin-top: 1rem; - } - - .config-reference-filter { - flex: 1; - font: inherit; - font-family: var(--sl-font-mono); - font-size: var(--sl-text-sm); - color: var(--sl-color-white); - background: var(--sl-color-bg-inline-code); - border: 1px solid var(--sl-color-hairline-light); - border-radius: 0.4rem; - padding: 0.5rem 0.75rem; - } - - .config-reference-filter:focus { - outline: 2px solid var(--sl-color-accent); - outline-offset: -1px; - } - - .config-reference-meta { - margin: 0; - font-size: var(--sl-text-xs); - color: var(--sl-color-gray-3); - white-space: nowrap; - } - - .config-reference-title { - margin-top: 2rem; - } - - .config-reference-list { - list-style: none; - padding: 0; - margin: 0.75rem 0 0; - } - - .config-reference-row { - padding: 0.6rem 0; - border-bottom: 1px solid var(--sl-color-hairline); - } - - .config-reference-row[hidden] { - display: none; - } - - .config-reference-head { - display: flex; - flex-wrap: wrap; - align-items: baseline; - gap: 0.6rem; - } - - .config-reference-key { - font-weight: 600; - } - - .config-reference-type { - font-size: var(--sl-text-xs); - color: var(--sl-color-gray-3); - border: 1px solid var(--sl-color-hairline-light); - border-radius: 0.3rem; - padding: 0 0.35rem; - } - - .config-reference-default { - font-size: var(--sl-text-xs); - color: var(--sl-color-gray-3); - } - - .config-reference-desc { - margin: 0.25rem 0 0; - font-size: var(--sl-text-sm); - color: var(--sl-color-gray-2); - } - - .config-reference-values { - display: inline-flex; - flex-wrap: wrap; - gap: 0.3rem; - margin-inline-start: 0.4rem; - } - - .config-reference-empty { - margin-top: 1.5rem; - color: var(--sl-color-gray-3); - } -</style> - -<script> - for (const root of document.querySelectorAll('.config-reference')) { - const filter = root.querySelector('.config-reference-filter'); - const meta = root.querySelector('.config-reference-meta'); - const empty = root.querySelector('.config-reference-empty'); - const sections = [...root.querySelectorAll('.config-reference-section')]; - const rows = [...root.querySelectorAll('.config-reference-row')]; - const countLabel = root.dataset.countLabel; - - filter?.addEventListener('input', () => { - const terms = filter.value.trim().toLowerCase().split(/\s+/).filter(Boolean); - let visible = 0; - - for (const row of rows) { - const haystack = row.dataset.search ?? ''; - const match = terms.every((term) => haystack.includes(term)); - row.hidden = !match; - if (match) visible += 1; - } - - for (const section of sections) { - section.hidden = !section.querySelector('.config-reference-row:not([hidden])'); - } - - if (meta) meta.textContent = `${visible} ${countLabel}`; - if (empty) empty.hidden = visible > 0; - }); - } -</script> diff --git a/website/src/components/DocsVersionSelect.astro b/website/src/components/DocsVersionSelect.astro deleted file mode 100644 index 5ccd856c..00000000 --- a/website/src/components/DocsVersionSelect.astro +++ /dev/null @@ -1,84 +0,0 @@ ---- -import versions from '../data/docs-versions.json'; -import { docsRoute, docsTargetHref } from '../docs-path'; - -const route = docsRoute(Astro.url.pathname); -const locale = route.locale ?? 'root'; -const labels = { - root: { accessible: 'Documentation version', latest: 'Latest', preview: 'Preview' }, - ja: { accessible: 'ドキュメントのバージョン', latest: '最新', preview: 'プレビュー' }, - 'zh-cn': { accessible: '文档版本', latest: '最新', preview: '预览版' }, -}[locale]; -const scopes = versions.scopes; ---- - -{route.isDocs && ( - <label class="docs-version-select"> - <span class="sr-only">{labels.accessible}</span> - <select autocomplete="off" aria-label={labels.accessible}> - <option - value={docsTargetHref(route, 'stable', scopes)} - selected={route.target === 'stable'} - > - {labels.latest}{' '}{versions.current} - </option> - <option - value={docsTargetHref(route, 'preview', scopes)} - selected={route.target === 'preview'} - > - {labels.preview} - </option> - {versions.versions - .filter((entry) => entry.version !== versions.current || route.target === versions.current) - .map((entry) => ( - <option - value={docsTargetHref(route, entry.version, scopes)} - selected={route.target === entry.version} - > - {entry.version} - </option> - ))} - </select> - </label> -)} - -<script> - for (const select of document.querySelectorAll<HTMLSelectElement>('.docs-version-select select')) { - select.addEventListener('change', () => { - globalThis.location.href = select.value; - }); - } -</script> - -<style> - .docs-version-select { - display: flex; - min-width: 0; - } - - select { - width: 9.5rem; - min-width: 0; - border: 1px solid var(--sl-color-hairline); - border-radius: 4px; - padding: 0.4rem 1.6rem 0.4rem 0.55rem; - background: var(--sl-color-bg-nav); - color: var(--sl-color-gray-2); - font: inherit; - font-size: var(--sl-text-xs); - font-weight: 700; - text-overflow: ellipsis; - } - - select:hover, - select:focus { - border-color: var(--sl-color-accent); - color: var(--sl-color-accent-high); - } - - @media (max-width: 38rem) { - select { - width: 7.5rem; - } - } -</style> diff --git a/website/src/components/Head.astro b/website/src/components/Head.astro deleted file mode 100644 index dac83b81..00000000 --- a/website/src/components/Head.astro +++ /dev/null @@ -1,67 +0,0 @@ ---- -import { docsVersion } from '../docs-path'; - -const { head } = Astro.locals.starlightRoute; -const version = docsVersion(Astro.url.pathname); ---- - -{head.map(({ tag: Tag, attrs, content }) => <Tag {...attrs} set:html={content} />)} -<meta data-pagefind-filter="version[content]" content={version} /> -{version !== 'stable' && <meta name="robots" content="noindex, nofollow" />} - -<script> - const labels = (() => { - const lang = document.documentElement.lang.toLowerCase(); - if (lang.startsWith('ja')) return { copy: 'リンクをコピー', copied: 'コピーしました' }; - if (lang.startsWith('zh')) return { copy: '复制链接', copied: '已复制' }; - return { copy: 'Copy link', copied: 'Copied' }; - })(); - - function labelHeadingLinks(root: ParentNode = document) { - for (const link of root.querySelectorAll<HTMLAnchorElement>('.sl-anchor-link')) { - link.title = labels.copy; - const section = link.querySelector('.sr-only')?.textContent; - if (section) link.setAttribute('aria-label', `${labels.copy}: ${section}`); - } - } - - labelHeadingLinks(); - document.addEventListener('astro:page-load', () => labelHeadingLinks()); - document.addEventListener('click', async (event) => { - if ( - event instanceof MouseEvent && - (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) - ) { - return; - } - - const target = event.target; - if (!(target instanceof Element)) return; - const link = target.closest('.sl-anchor-link'); - if (!(link instanceof HTMLAnchorElement) || !navigator.clipboard?.writeText) return; - - try { - await navigator.clipboard.writeText(link.href); - } catch { - return; - } - - link.querySelector('.heading-link-copy-feedback')?.remove(); - const feedback = document.createElement('span'); - feedback.className = 'heading-link-copy-feedback'; - feedback.setAttribute('aria-hidden', 'true'); - feedback.textContent = labels.copied; - link.append(feedback); - window.setTimeout(() => feedback.remove(), 1400); - - let status = document.querySelector<HTMLElement>('[data-heading-link-copy-status]'); - if (!status) { - status = document.createElement('span'); - status.className = 'sr-only'; - status.setAttribute('role', 'status'); - status.dataset.headingLinkCopyStatus = ''; - document.body.append(status); - } - status.textContent = labels.copied; - }); -</script> diff --git a/website/src/components/Header.astro b/website/src/components/Header.astro deleted file mode 100644 index cfa3d759..00000000 --- a/website/src/components/Header.astro +++ /dev/null @@ -1,99 +0,0 @@ ---- -import config from 'virtual:starlight/user-config'; - -import LanguageSelect from 'virtual:starlight/components/LanguageSelect'; -import Search from 'virtual:starlight/components/Search'; -import SiteTitle from 'virtual:starlight/components/SiteTitle'; -import SocialIcons from 'virtual:starlight/components/SocialIcons'; -import ThemeSelect from 'virtual:starlight/components/ThemeSelect'; -import DocsVersionSelect from './DocsVersionSelect.astro'; -import { docsRoute } from '../docs-path'; - -const shouldRenderSearch = - config.pagefind || config.components.Search !== '@astrojs/starlight/components/Search.astro'; -const isDocs = docsRoute(Astro.url.pathname).isDocs; ---- - -<div class="header"> - <div class="title-wrapper sl-flex"> - <SiteTitle /> - </div> - <div class="sl-flex print:hidden search-wrapper"> - {isDocs && <DocsVersionSelect />} - {shouldRenderSearch && <Search />} - </div> - <div class="sl-hidden md:sl-flex print:hidden right-group"> - <div class="sl-flex social-icons"> - <SocialIcons /> - </div> - <ThemeSelect /> - <LanguageSelect /> - </div> -</div> - -<style> - @layer starlight.core { - .header { - display: flex; - gap: var(--sl-nav-gap); - justify-content: space-between; - align-items: center; - height: 100%; - } - - .title-wrapper { - overflow: clip; - padding: 0.25rem; - margin: -0.25rem; - min-width: 0; - } - - .search-wrapper { - align-items: center; - gap: 0.75rem; - min-width: 0; - } - - .right-group, - .social-icons { - gap: 1rem; - align-items: center; - } - - .social-icons::after { - content: ""; - height: 2rem; - border-inline-end: 1px solid var(--sl-color-gray-5); - } - - @media (min-width: 50rem) { - :global(:root[data-has-sidebar]) { - --__sidebar-pad: calc(2 * var(--sl-nav-pad-x)); - } - - :global(:root:not([data-has-toc])) { - --__toc-width: 0rem; - } - - .header { - --__sidebar-width: max(0rem, var(--sl-content-inline-start, 0rem) - var(--sl-nav-pad-x)); - --__main-column-fr: calc( - ( - 100% + var(--__sidebar-pad, 0rem) - var(--__toc-width, var(--sl-sidebar-width)) - - (2 * var(--__toc-width, var(--sl-nav-pad-x))) - var(--sl-content-inline-start, 0rem) - - var(--sl-content-width) - ) / 2 - ); - display: grid; - grid-template-columns: - minmax( - calc(var(--__sidebar-width) + max(0rem, var(--__main-column-fr) - var(--sl-nav-gap))), - auto - ) - 1fr - auto; - align-content: center; - } - } - } -</style> diff --git a/website/src/components/MarketingLayout.astro b/website/src/components/MarketingLayout.astro deleted file mode 100644 index 6db7e00d..00000000 --- a/website/src/components/MarketingLayout.astro +++ /dev/null @@ -1,274 +0,0 @@ ---- -interface Props { - title: string; - description: string; - canonicalPath: string; - ogType?: 'website' | 'article'; - ogImage?: string; - ogImageAlt?: string; -} - -const { - title, - description, - canonicalPath, - ogType = 'website', - ogImage = '/assets/og-card-v9.png', - ogImageAlt = 'Herdr — run them anywhere, leave them running.', -} = Astro.props; -const canonicalUrl = new URL(canonicalPath, 'https://herdr.dev').toString(); -const ogImageUrl = new URL(ogImage, 'https://herdr.dev').toString(); ---- - -<!doctype html> -<html lang="en"> - <head> - <meta charset="utf-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <title>{title}</title> - <meta name="description" content={description} /> - <meta name="theme-color" content="#f0eee9" /> - <link rel="canonical" href={canonicalUrl} /> - - <meta property="og:type" content={ogType} /> - <meta property="og:site_name" content="Herdr" /> - <meta property="og:title" content={title} /> - <meta property="og:description" content={description} /> - <meta property="og:url" content={canonicalUrl} /> - <meta property="og:image" content={ogImageUrl} /> - <meta property="og:image:width" content="1200" /> - <meta property="og:image:height" content="630" /> - <meta property="og:image:alt" content={ogImageAlt} /> - - <meta name="twitter:card" content="summary_large_image" /> - <meta name="twitter:title" content={title} /> - <meta name="twitter:description" content={description} /> - <meta name="twitter:image" content={ogImageUrl} /> - <meta name="twitter:image:alt" content={ogImageAlt} /> - - <link rel="icon" href="/favicon.ico" sizes="48x48" /> - <link rel="icon" type="image/png" sizes="192x192" href="/assets/favicon.png" /> - <link rel="apple-touch-icon" href="/assets/logo.png" /> - <link rel="preload" href="/assets/jbm.woff2" as="font" type="font/woff2" crossorigin /> - <script> - (function () { - var saved = localStorage.getItem("herdr-palette"); - var system = window.matchMedia("(prefers-color-scheme: dark)").matches - ? "tokyo-night" - : "tokyo-night-day"; - document.documentElement.setAttribute("data-palette", saved || system); - })(); - </script> - <link rel="stylesheet" href="/css/style.css?v=41" /> - </head> - <body> - <header class="site-header"> - <nav class="nav" aria-label="Primary navigation"> - <a class="brand" href="/" aria-label="Herdr home"> - <span class="brand-logo" aria-hidden="true"></span> - <span>herdr</span> - </a> - <details class="nav-menu"> - <summary class="nav-menu-button" aria-label="Open navigation"> - <span></span> - <span></span> - <span></span> - </summary> - <div class="nav-menu-panel"> - <a href="/">Home</a> - <a href="/compare/">Compare</a> - <a href="/plugins/">Plugins</a> - <a href="/blog/">Blog</a> - <a href="/docs/quick-start/">Quick start</a> - <a href="/docs/">Docs</a> - <a href="/docs/socket-api/">API</a> - <a href="/docs/install/">Install</a> - <a href="https://github.com/herdrdev/herdr">GitHub</a> - </div> - </details> - <div class="nav-links"> - <a href="/">Home</a> - <a href="/compare/">Compare</a> - <a href="/plugins/">Plugins</a> - <a href="/blog/">Blog</a> - <a href="/docs/quick-start/">Quick start</a> - <a href="/docs/">Docs</a> - <a href="/docs/socket-api/">API</a> - </div> - <div class="nav-tools"> - <a class="nav-icon-link" href="https://github.com/herdrdev/herdr" aria-label="Herdr on GitHub"> - <svg viewBox="0 0 16 16" aria-hidden="true"> - <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82A7.64 7.64 0 0 1 8 3.87c.68 0 1.36.09 2 .26 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z" /> - </svg> - </a> - <a class="nav-icon-link" href="https://x.com/herdrdev" aria-label="Herdr on X"> - <svg viewBox="0 0 24 24" aria-hidden="true"> - <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231Zm-1.161 17.52h1.833L7.084 4.126H5.117Z" /> - </svg> - </a> - <a class="nav-cta" href="/docs/install/">Install</a> - </div> - </nav> - </header> - - <slot /> - - <div class="theme-hint" id="theme-hint" title="Press / or t to switch theme"> - <span class="theme-hint-dot"></span> - <span class="theme-hint-label" id="theme-hint-label">light</span> - <span class="theme-hint-key">[/]</span> - </div> - - <div class="theme-overlay" id="theme-overlay" hidden> - <div class="theme-overlay-panel" role="dialog" aria-label="Theme switcher" aria-modal="true"> - <div class="theme-overlay-title">settings</div> - <div class="theme-overlay-tabs"><span class="active">theme</span></div> - <div class="theme-overlay-list" id="theme-overlay-list" role="listbox" aria-label="Themes"> - <div class="theme-sep"><span>dark</span></div> - <button type="button" role="option" data-value="catppuccin">catppuccin</button> - <button type="button" role="option" data-value="terminal">terminal</button> - <button type="button" role="option" data-value="tokyo-night">tokyo night</button> - <button type="button" role="option" data-value="dracula">dracula</button> - <button type="button" role="option" data-value="nord">nord</button> - <button type="button" role="option" data-value="gruvbox">gruvbox</button> - <button type="button" role="option" data-value="one-dark">one dark</button> - <button type="button" role="option" data-value="solarized">solarized</button> - <button type="button" role="option" data-value="kanagawa">kanagawa</button> - <button type="button" role="option" data-value="rose-pine">rose pine</button> - <button type="button" role="option" data-value="vesper">vesper</button> - <div class="theme-sep"><span>light</span></div> - <button type="button" role="option" data-value="catppuccin-latte">catppuccin latte</button> - <button type="button" role="option" data-value="tokyo-night-day">tokyo day</button> - <button type="button" role="option" data-value="gruvbox-light">gruvbox light</button> - <button type="button" role="option" data-value="one-light">one light</button> - <button type="button" role="option" data-value="solarized-light">solarized light</button> - <button type="button" role="option" data-value="kanagawa-lotus">kanagawa lotus</button> - <button type="button" role="option" data-value="rose-pine-dawn">rose pine dawn</button> - </div> - <div class="theme-overlay-footer"> - <span class="theme-overlay-hints"><span class="key">↑↓</span> select</span> - <span class="theme-overlay-actions"><span class="theme-apply-btn">↵ apply</span><button class="theme-close-btn" type="button" id="theme-close-btn">esc close</button></span> - </div> - </div> - </div> - - <script> - (function () { - var overlay = document.getElementById("theme-overlay"); - var list = document.getElementById("theme-overlay-list"); - var hintLabel = document.getElementById("theme-hint-label"); - if (!overlay || !list) return; - var options = Array.prototype.slice.call(list.querySelectorAll("[role='option']")); - var media = window.matchMedia("(prefers-color-scheme: dark)"); - var saved = localStorage.getItem("herdr-palette") || ""; - var committed = saved || defaultPalette(); - - function defaultPalette() { - return media.matches ? "tokyo-night" : "tokyo-night-day"; - } - - function setPalette(value) { - document.documentElement.setAttribute("data-palette", value || defaultPalette()); - } - - function updateUI(value) { - var activeValue = value || defaultPalette(); - options.forEach(function (opt) { - opt.setAttribute("aria-selected", String(opt.dataset.value === activeValue)); - }); - var active = options.find(function (opt) { return opt.dataset.value === activeValue; }); - if (hintLabel && active) hintLabel.textContent = active.textContent.trim(); - } - - function openOverlay() { - overlay.hidden = false; - document.body.style.overflow = "hidden"; - committed = localStorage.getItem("herdr-palette") || defaultPalette(); - updateUI(committed); - var active = list.querySelector("[aria-selected='true']") || options[0]; - if (active) active.focus(); - } - - function closeOverlay() { - overlay.hidden = true; - document.body.style.overflow = ""; - } - - function toggleOverlay() { - if (overlay.hidden) openOverlay(); - else closeOverlay(); - } - - function preview(index) { - if (index < 0 || index >= options.length) return; - setPalette(options[index].dataset.value); - updateUI(options[index].dataset.value); - options[index].focus(); - } - - function commit(value) { - setPalette(value); - localStorage.setItem("herdr-palette", value); - updateUI(value); - closeOverlay(); - } - - function revert() { - setPalette(committed); - updateUI(committed); - closeOverlay(); - } - - media.addEventListener("change", function () { - if (!localStorage.getItem("herdr-palette")) { - setPalette(""); - updateUI(""); - } - }); - - options.forEach(function (opt, idx) { - opt.addEventListener("click", function () { commit(opt.dataset.value); }); - opt.addEventListener("mouseenter", function () { preview(idx); }); - }); - - document.addEventListener("keydown", function (e) { - if ((e.key === "t" || e.key === "/") && !e.ctrlKey && !e.metaKey && !e.altKey) { - var tag = document.activeElement && document.activeElement.tagName; - if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return; - e.preventDefault(); - toggleOverlay(); - return; - } - if (overlay.hidden) return; - var idx = options.indexOf(document.activeElement); - if (e.key === "ArrowDown") { - e.preventDefault(); - preview(idx >= 0 ? Math.min(options.length - 1, idx + 1) : 0); - } else if (e.key === "ArrowUp") { - e.preventDefault(); - preview(idx >= 0 ? Math.max(0, idx - 1) : options.length - 1); - } else if (e.key === "Enter" && idx >= 0) { - e.preventDefault(); - commit(options[idx].dataset.value); - } else if (e.key === "Escape") { - e.preventDefault(); - revert(); - } - }); - - var themeHint = document.getElementById("theme-hint"); - if (localStorage.getItem("herdr-theme-hint-seen") === "1") { - themeHint.classList.add("is-seen"); - } - themeHint.addEventListener("mouseenter", function () { - localStorage.setItem("herdr-theme-hint-seen", "1"); - themeHint.classList.add("is-seen"); - }, { once: true }); - themeHint.addEventListener("click", toggleOverlay); - document.getElementById("theme-close-btn").addEventListener("click", revert); - setPalette(saved); - updateUI(saved); - })(); - </script> - </body> -</html> diff --git a/website/src/components/Search.astro b/website/src/components/Search.astro deleted file mode 100644 index cceeed34..00000000 --- a/website/src/components/Search.astro +++ /dev/null @@ -1,52 +0,0 @@ ---- -import StarlightSearch from '@astrojs/starlight/components/Search.astro'; -import { docsVersion } from '../docs-path'; - -const version = docsVersion(Astro.url.pathname); ---- - -<version-search data-version={version}> - <StarlightSearch /> -</version-search> - -<script> - class VersionSearch extends HTMLElement { - observer?: MutationObserver; - - connectedCallback() { - const version = this.dataset.version; - if (!version) return; - - const selectVersion = () => { - const checkbox = this.querySelector<HTMLInputElement>( - `.pagefind-ui__filter-checkbox[name="version"][value="${version}"]`, - ); - if (!checkbox) return false; - if (!checkbox.checked) checkbox.click(); - this.observer?.disconnect(); - return true; - }; - - if (selectVersion()) return; - // Starlight does not expose its PagefindUI instance, so wait for Pagefind's filter control. - this.observer = new MutationObserver(selectVersion); - this.observer.observe(this, { childList: true, subtree: true }); - } - - disconnectedCallback() { - this.observer?.disconnect(); - } - } - - customElements.define('version-search', VersionSearch); -</script> - -<style> - version-search { - display: contents; - } - - version-search :global(.pagefind-ui__filter-panel) { - display: none; - } -</style> diff --git a/website/src/components/Sidebar.astro b/website/src/components/Sidebar.astro deleted file mode 100644 index 08d31f79..00000000 --- a/website/src/components/Sidebar.astro +++ /dev/null @@ -1,53 +0,0 @@ ---- -import MobileMenuFooter from 'virtual:starlight/components/MobileMenuFooter'; -import SidebarPersister from '@astrojs/starlight/components/SidebarPersister.astro'; -import SidebarSublist from '@astrojs/starlight/components/SidebarSublist.astro'; -import versions from '../data/docs-versions.json'; -import { docsRoute, docsTargetHref } from '../docs-path'; - -const { sidebar } = Astro.locals.starlightRoute; -const pathname = Astro.url.pathname; -const route = docsRoute(pathname); -const scopedSidebar = route.target === 'stable' ? sidebar : rewriteSidebar(sidebar); - -function rewriteSidebar(entries) { - return entries.flatMap((entry) => { - if (entry.type === 'link') { - const linkRoute = docsRoute(entry.href); - if (!linkRoute.isDocs) return [entry]; - if (linkRoute.target !== 'stable') { - return linkRoute.target === route.target - ? [{ ...entry, isCurrent: samePath(entry.href, pathname) }] - : []; - } - - const localeKey = route.locale ?? 'root'; - const availablePages = versions.scopes[route.target]?.locales[localeKey] ?? []; - if (!availablePages.includes(linkRoute.page)) return []; - - const href = docsTargetHref({ ...linkRoute, locale: route.locale }, route.target, versions.scopes); - return [{ ...entry, href, isCurrent: samePath(href, pathname) }]; - } - - const rewrittenEntries = rewriteSidebar(entry.entries); - if (rewrittenEntries.length === 0) return []; - return [{ ...entry, entries: rewrittenEntries }]; - }); -} - -function samePath(left, right) { - return stripTrailingSlash(left) === stripTrailingSlash(right); -} - -function stripTrailingSlash(path) { - return path.length > 1 ? path.replace(/\/$/, '') : path; -} ---- - -<SidebarPersister> - <SidebarSublist sublist={scopedSidebar} /> -</SidebarPersister> - -<div class="md:sl-hidden"> - <MobileMenuFooter /> -</div> diff --git a/website/src/components/SiteLayout.astro b/website/src/components/SiteLayout.astro deleted file mode 100644 index f056458c..00000000 --- a/website/src/components/SiteLayout.astro +++ /dev/null @@ -1,223 +0,0 @@ ---- -/* The chassis every non-docs page shares: one dark ground, the framed grid, - the same nav and footer as the homepage. Pages supply their own <main>. - Docs keep Starlight's shell and are themed through src/styles/starlight.css. */ -interface Props { - title: string; - description: string; - canonicalPath: string; - ogType?: 'website' | 'article'; - ogImage?: string; - ogImageAlt?: string; -} - -const { - title, - description, - canonicalPath, - ogType = 'website', - ogImage = '/assets/og-card-v9.png', - ogImageAlt = 'Herdr — run them anywhere, leave them running.', -} = Astro.props; - -import { fmt, releaseVersion, siteStats } from '../site-data'; - -const canonicalUrl = new URL(canonicalPath, 'https://herdr.dev').toString(); -const ogImageUrl = new URL(ogImage, 'https://herdr.dev').toString(); -const year = new Date().getFullYear(); - -/* Same build-time source the homepage uses, so the nav count and the footer - version agree across every page without being written down twice. */ -const { starCount } = await siteStats(); -const version = await releaseVersion(); -const facts = version ? `v${version} · Apache 2.0` : 'Apache 2.0'; ---- - -<!doctype html> -<html lang="en" data-palette="catppuccin" data-mode="ink"> - <head> - <meta charset="utf-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <title>{title}</title> - <meta name="description" content={description} /> - <meta name="theme-color" content="#17171a" /> - <link rel="canonical" href={canonicalUrl} /> - - <meta property="og:type" content={ogType} /> - <meta property="og:site_name" content="Herdr" /> - <meta property="og:title" content={title} /> - <meta property="og:description" content={description} /> - <meta property="og:url" content={canonicalUrl} /> - <meta property="og:image" content={ogImageUrl} /> - <meta property="og:image:width" content="1200" /> - <meta property="og:image:height" content="630" /> - <meta property="og:image:alt" content={ogImageAlt} /> - - <meta name="twitter:card" content="summary_large_image" /> - <meta name="twitter:title" content={title} /> - <meta name="twitter:description" content={description} /> - <meta name="twitter:image" content={ogImageUrl} /> - <meta name="twitter:image:alt" content={ogImageAlt} /> - - <link rel="icon" href="/favicon.ico" sizes="48x48" /> - <link rel="icon" type="image/png" sizes="192x192" href="/assets/favicon.png" /> - <link rel="apple-touch-icon" href="/assets/logo.png" /> - <link rel="preload" href="/assets/jbm.woff2?v=2" as="font" type="font/woff2" crossorigin /> - <link rel="preconnect" href="https://fonts.googleapis.com" /> - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> - <link - href="https://fonts.googleapis.com/css2?family=Archivo:wght@600;800;900&family=Inter:wght@400;500;600&display=swap" - rel="stylesheet" - /> - <script is:inline> - /* Ground selection, shared with the docs — see index.html for the - reasoning. Starlight's `starlight-theme` key is the source of truth; - `auto` is treated as dark so the default never follows the OS. */ - window.herdrGround = { - read: function () { - try { - return localStorage.getItem('starlight-theme') === 'light' ? 'paper' : 'ink'; - } catch (e) { - return 'ink'; - } - }, - apply: function (mode) { - var root = document.documentElement; - root.setAttribute('data-mode', mode); - root.setAttribute('data-palette', mode === 'paper' ? 'solarized-light' : 'catppuccin'); - return mode; - }, - save: function (mode) { - try { - localStorage.setItem('starlight-theme', mode === 'paper' ? 'light' : 'dark'); - } catch (e) {} - }, - }; - window.herdrGround.apply(window.herdrGround.read()); - </script> - <link rel="stylesheet" href="/css/style.css?v=59" /> - <link rel="stylesheet" href="/css/site.css?v=64" /> - </head> - <body class="site"> - <div class="hd-frame"> - <nav class="hd-nav"> - <a class="lock" href="/"><span class="ram"></span><b>herdr</b></a> - <div class="sp"></div> - <a class="lnk" href="/docs/">Docs</a> - <a class="lnk" href="/plugins/">Plugins</a> - <a class="lnk" href="/blog/">Blog</a> - <a class="lnk" href="/compare/">Compare</a> - <a class="ico wide" href="https://github.com/herdrdev/herdr" aria-label="Herdr on GitHub — star count"> - <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82A7.64 7.64 0 0 1 8 3.87c.68 0 1.36.09 2 .26 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z" /></svg> - <b data-stars>{fmt(starCount)}</b> - </a> - <a class="ico" href="https://x.com/herdrdev" aria-label="Herdr on X"> - <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231Zm-1.161 17.52h1.833L7.084 4.126H5.117Z" /></svg> - </a> - <div class="mode"> - <button type="button" data-ground="ink" aria-pressed="true">ink</button> - <button type="button" data-ground="paper" aria-pressed="false">paper</button> - </div> - <a class="btn" href="/docs/install/">Install</a> - <button type="button" class="menu" id="nav-menu" aria-expanded="false" aria-controls="nav-sheet" aria-label="Menu"> - <span></span><span></span><span></span> - </button> - </nav> - - <div class="sheet" id="nav-sheet"> - <a href="/docs/">Docs</a> - <a href="/plugins/">Plugins</a> - <a href="/blog/">Blog</a> - <a href="/compare/">Compare</a> - <a href="https://x.com/herdrdev">@herdrdev</a> - <div class="sheet-ground"> - <span>Ground</span> - <div class="mode"> - <button type="button" data-ground="ink" aria-pressed="true">ink</button> - <button type="button" data-ground="paper" aria-pressed="false">paper</button> - </div> - </div> - </div> - - <slot /> - - <footer class="foot"> - <span class="ram"></span> - <a href="/docs/">docs</a> - <a href="/plugins/">plugins</a> - <a href="/blog/">blog</a> - <a href="/compare/">compare</a> - <span>built in the terminal · © {year} Herdr, Inc.</span> - <span class="fact">{facts}</span> - <span class="out"> - <a class="ico" href="https://github.com/herdrdev/herdr" aria-label="Herdr on GitHub"> - <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82A7.64 7.64 0 0 1 8 3.87c.68 0 1.36.09 2 .26 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z" /></svg> - </a> - <a class="ico" href="https://x.com/herdrdev" aria-label="Herdr on X"> - <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231Zm-1.161 17.52h1.833L7.084 4.126H5.117Z" /></svg> - </a> - <a href="mailto:hey@herdr.dev">hey@herdr.dev</a> - </span> - </footer> - </div> - - <script is:inline> - (function () { - var buttons = document.querySelectorAll('[data-ground]'); - function paint(mode) { - window.herdrGround.apply(mode); - buttons.forEach(function (b) { - b.setAttribute('aria-pressed', String(b.dataset.ground === mode)); - }); - } - buttons.forEach(function (b) { - b.addEventListener('click', function () { - window.herdrGround.save(b.dataset.ground); - paint(b.dataset.ground); - }); - }); - paint(window.herdrGround.read()); - })(); - - /* nav sheet — the links, the account and the ground toggle that don't - fit the bar on a phone */ - (function () { - var btn = document.getElementById('nav-menu'); - var sheet = document.getElementById('nav-sheet'); - if (!btn || !sheet) return; - function set(open) { - sheet.classList.toggle('open', open); - btn.setAttribute('aria-expanded', String(open)); - } - btn.addEventListener('click', function (event) { - event.stopPropagation(); - set(!sheet.classList.contains('open')); - }); - /* the ground buttons live in the sheet, so a tap inside must not close - it — only taps on the page behind do */ - document.addEventListener('click', function (event) { - if (!sheet.contains(event.target)) set(false); - }); - document.addEventListener('keydown', function (event) { - if (event.key === 'Escape') set(false); - }); - })(); - - /* The nav count is baked at build, which can be a deploy behind. Refresh - it from the same snapshot so the number is current on every page. No - count-up here — this is chrome, not the argument the homepage makes. */ - fetch('https://assets.herdr.dev/stats/stats.json', { headers: { accept: 'application/json' } }) - .then(function (r) { - return r.ok ? r.json() : null; - }) - .then(function (d) { - if (!d || !d.stars || typeof d.stars.total !== 'number') return; - var text = d.stars.total.toLocaleString('en-US'); - document.querySelectorAll('[data-stars]').forEach(function (el) { - el.textContent = text; - }); - }) - .catch(function () {}); - </script> - </body> -</html> diff --git a/website/src/components/SiteTitle.astro b/website/src/components/SiteTitle.astro deleted file mode 100644 index 3e5af0a3..00000000 --- a/website/src/components/SiteTitle.astro +++ /dev/null @@ -1,95 +0,0 @@ ---- -import { docsRoute } from '../docs-path'; - -const { siteTitle, siteTitleHref, locale } = Astro.locals.starlightRoute; -const route = docsRoute(Astro.url.pathname); -const localePrefix = locale ? `/${locale}` : ''; -const targetSegment = route.target === 'stable' ? '' : `/${route.target}`; -const docsHref = `${localePrefix}/docs${targetSegment}/`; ---- - -<div class="title-row sl-flex"> - <a href={siteTitleHref} class="site-title sl-flex"> - <span class="docs-brand-logo" aria-hidden="true"></span> - <span translate="no">{siteTitle}</span> - </a> - <nav class="title-nav" aria-label="Primary"> - <a href={docsHref}>Docs</a> - <a href="/plugins/">Plugins</a> - <a href="/blog/">Blog</a> - </nav> -</div> - -<style> - @layer starlight.core { - .title-row { - align-items: center; - gap: 1rem; - min-width: 0; - } - - .site-title { - align-items: center; - gap: 0.55rem; - font-family: var(--sl-font-mono); - font-size: var(--sl-text-h4); - font-weight: 700; - letter-spacing: -0.04em; - color: var(--sl-color-white); - text-decoration: none; - white-space: nowrap; - min-width: 0; - transition: color 120ms ease; - } - - .docs-brand-logo { - position: relative; - display: inline-block; - width: 32px; - height: 32px; - flex: 0 0 auto; - border: 1px solid var(--sl-color-hairline); - border-radius: 4px; - background: var(--sl-color-bg-nav); - } - - .docs-brand-logo::before { - content: ""; - position: absolute; - inset: 2px; - background: var(--sl-color-white); - -webkit-mask: url("/assets/agent-icons/herdr-mask.svg") center / contain no-repeat; - mask: url("/assets/agent-icons/herdr-mask.svg") center / contain no-repeat; - } - - span:not(.docs-brand-logo) { - overflow: hidden; - } - - .title-nav { - display: flex; - align-items: baseline; - gap: 0.75rem; - font-size: var(--sl-text-sm); - font-weight: 600; - line-height: 1; - } - - .title-nav a { - color: var(--sl-color-white); - text-decoration: none; - transition: color 120ms ease; - } - - .site-title:hover, - .title-nav a:hover { - color: var(--sl-color-accent-high); - } - - @media (max-width: 35rem) { - .title-nav { - display: none; - } - } - } -</style> |
