Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions site/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default defineConfig({
components: {
Footer: './src/components/Footer.astro',
PageTitle: './src/components/PageTitle.astro',
Search: './src/components/Search.astro',
},
head: [
// Google Analytics (carried over from the Docusaurus site, anonymized IP).
Expand Down
4 changes: 2 additions & 2 deletions site/src/components/HomeHero.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
// Living Terminal hero: code window + a terminal pane that types `curl`
// and streams Echo's JSON response. Animation is client-side, with a
// static final-state fallback for reduced-motion / no-JS.
import { starsLabel } from '../data/github.ts';
import { starsLabel, echoVersion } from '../data/github.ts';
---

<section class="hh">
<div class="hh-left">
<span class="hh-eyebrow"><i class="ph ph-sparkle"></i> Echo v5.2 — now released</span>
<span class="hh-eyebrow"><i class="ph ph-sparkle"></i> Echo {echoVersion} — now released</span>
<h1>Build fast Go APIs.<br /><span class="g">Without the bloat.</span></h1>
<p class="hh-sub">
A high-performance, minimalist Go web framework — a zero-allocation
Expand Down
52 changes: 52 additions & 0 deletions site/src/components/Search.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
import Default from '@astrojs/starlight/components/Search.astro';
---
<Default />

<script>
// Command-palette empty state: inject quick links + a shortcut footer into the
// Pagefind modal once it mounts. terminal.css shows it only while the query is
// empty (the results drawer carries .pagefind-ui__hidden then).
const PANEL = `
<div class="echo-search-empty">
<div class="ess-group">
<div class="ess-label">Start here</div>
<a class="ess-link" href="/guide/quickstart/"><i aria-hidden="true" class="ph ph-rocket-launch"></i> Quickstart <span class="ess-sec">Guide</span></a>
<a class="ess-link" href="/guide/routing/"><i aria-hidden="true" class="ph ph-signpost"></i> Routing <span class="ess-sec">Guide</span></a>
<a class="ess-link" href="/guide/binding/"><i aria-hidden="true" class="ph ph-brackets-curly"></i> Binding <span class="ess-sec">Guide</span></a>
</div>
<div class="ess-group">
<div class="ess-label">Popular</div>
<a class="ess-link" href="/middleware/jwt/"><i aria-hidden="true" class="ph ph-lock-key"></i> JWT <span class="ess-sec">Middleware</span></a>
<a class="ess-link" href="/middleware/cors/"><i aria-hidden="true" class="ph ph-globe"></i> CORS <span class="ess-sec">Middleware</span></a>
<a class="ess-link" href="/cookbook/hello-world/"><i aria-hidden="true" class="ph ph-cube"></i> Hello World <span class="ess-sec">Cookbook</span></a>
</div>
<div class="ess-foot">
<span><kbd>↵</kbd>open</span><span><kbd>↑</kbd><kbd>↓</kbd>navigate</span><span><kbd>esc</kbd>close</span>
</div>
</div>`;

function inject() {
const ui = document.querySelector('.pagefind-ui');
if (!ui || ui.querySelector('.echo-search-empty')) return !!ui;
const form = ui.querySelector('.pagefind-ui__form');
if (!form) return false;
form.insertAdjacentHTML('afterend', PANEL);
return true;
}

// Pagefind mounts lazily when the dialog first opens — watch for it, then stop.
if (!inject()) {
const obs = new MutationObserver(() => { if (inject()) obs.disconnect(); });
obs.observe(document.body, { childList: true, subtree: true });
// Bound the observer: if Pagefind never mounts (disabled in dev, or the
// Starlight markup changed on upgrade), stop watching so it can't run for
// the page's lifetime. Losing the launchpad is harmless; search still works.
setTimeout(() => {
obs.disconnect();
if (import.meta.env.DEV && !document.querySelector('.echo-search-empty')) {
console.warn('[search] empty-state panel not injected — .pagefind-ui selectors may have changed');
}
}, 10000);
}
</script>
29 changes: 28 additions & 1 deletion site/src/data/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ async function fetchStars(): Promise<number> {
const token = process.env.GITHUB_TOKEN;
if (token) headers.Authorization = `Bearer ${token}`;

const res = await fetch(`https://api.github.com/repos/${REPO}`, { headers });
const res = await fetch(`https://api.github.com/repos/${REPO}`, { headers, signal: AbortSignal.timeout(5000) });
if (!res.ok) throw new Error(`GitHub API responded ${res.status}`);
const data = await res.json();
return typeof data.stargazers_count === 'number' ? data.stargazers_count : FALLBACK_STARS;
Expand All @@ -29,3 +29,30 @@ export const stars = await fetchStars();

/** e.g. 32412 -> "32.4k" */
export const starsLabel = stars >= 1000 ? `${(stars / 1000).toFixed(1)}k` : String(stars);

const FALLBACK_VERSION = 'v5';

// Latest released echo/v5, via the Go module proxy — canonical and v5-only
// (avoids GitHub "latest release" occasionally pointing at a v4 patch).
async function fetchLatestVersion(): Promise<string> {
try {
const res = await fetch('https://proxy.golang.org/github.com/labstack/echo/v5/@latest', {
signal: AbortSignal.timeout(5000),
});
if (!res.ok) throw new Error(`Go proxy responded ${res.status}`);
const data = await res.json();
const v = typeof data.Version === 'string' ? data.Version.trim() : '';
// Require a real "vX..." tag — an empty/garbage value would silently render a blank badge.
if (!/^v\d/.test(v)) {
console.warn(`[github] unexpected version ${JSON.stringify(data.Version)}; using fallback ${FALLBACK_VERSION}`);
return FALLBACK_VERSION;
}
return v;
} catch (e) {
console.warn(`[github] version fetch failed; using fallback ${FALLBACK_VERSION}:`, e);
return FALLBACK_VERSION;
}
}

/** e.g. "v5.2.1" */
export const echoVersion = await fetchLatestVersion();
28 changes: 28 additions & 0 deletions site/src/styles/terminal.css
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,34 @@
/* search box (chrome) */
site-search button { border-radius: 8px !important; }

/* Empty search modal: a command-palette launchpad (quick links + shortcut
footer) injected by Search.astro, shown only before a query exists. Pagefind
marks the results drawer .pagefind-ui__hidden when empty. */
.echo-search-empty { display: none; }
.pagefind-ui:has(.pagefind-ui__drawer.pagefind-ui__hidden) .echo-search-empty { display: block; }
.echo-search-empty { margin-top: 16px; }
.ess-group + .ess-group { margin-top: 14px; }
.ess-label {
font-family: var(--sl-font-mono); font-size: 0.66rem; letter-spacing: 0.12em;
text-transform: uppercase; color: var(--sl-color-gray-4); padding: 0 4px 6px;
}
.ess-link {
display: flex; align-items: center; gap: 11px; padding: 9px 12px; border-radius: 9px;
text-decoration: none; color: var(--sl-color-white); font-size: 0.9rem; line-height: 1;
}
.ess-link i { font-size: 1.05rem; color: var(--echo-cyan-text); width: 1.2em; text-align: center; }
.ess-link .ess-sec { margin-left: auto; font-family: var(--sl-font-mono); font-size: 0.7rem; color: var(--sl-color-gray-4); }
.ess-link:hover, .ess-link:focus-visible { background: var(--echo-soft); outline: none; }
.ess-foot {
display: flex; gap: 18px; justify-content: center; flex-wrap: wrap;
margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--sl-color-hairline);
font-family: var(--sl-font-mono); font-size: 0.72rem; color: var(--sl-color-gray-4);
}
.ess-foot kbd {
font-family: var(--sl-font-mono); font-size: 0.72rem; background: var(--sl-color-bg-inline-code);
border: 1px solid var(--sl-color-hairline); border-radius: 5px; padding: 1px 6px; margin-right: 5px;
}

/* code blocks — let Expressive Code round the whole frame as one unit
(title bar: rounded top, code body: rounded bottom). Don't force the inner
<pre> corners or it detaches from the title bar into a second box. */
Expand Down
Loading