Skip to content

Commit a6cece6

Browse files
authored
ui: use Starlight primitives and refresh Echo theme (#462)
* ui: use Starlight primitives and refresh Echo theme * ui: keep feature titles as headings and show go mod init - Add FeatureCard, Starlight's Card markup with an <h3> title. Card renders its title as a <p>, which dropped the "Why Echo" titles from the heading outline. Pin the title's weight and letter-spacing so the global heading rule leaves the cards looking as before. - The install step now creates a module before `go get`, matching the quickstart. * ui: runnable get-started steps, card hover, and no terminal flash - Get started: step 2 shows the complete hello-world server instead of the route excerpt, so `go run server.go` in step 3 works. On wide screens it takes the wide column beside Install and Run. Drop the now-unused route region. - Ecosystem cards: restore a hover state; the unlayered theme colors were overriding Starlight's LinkCard hover. - Hero terminal: hide the static command and output until the typing script starts, so they no longer flash before being cleared. No-JS and reduced-motion visitors still see the static state. - Search: take the locale from Starlight's route data instead of a second hardcoded locale list, and fail the build if a locale has no labels. - Ask AI: the modal logo now uses the same Starlight star as the header pill; update the comments that still said sparkle. - Remove margin resets that .not-content already makes dead. * ui: keep search launchpad out of page HTML and fix sponsor button - Search: build the empty-state launchpad on the client from a shared module again, so its markup ships once in the cached bundle instead of a <template> in every page (about 1.4 KB gzip per page). Icon paths are Starlight's own. Modified clicks on a quick link no longer close the palette. - Record the smaller page sizes in the performance baseline; the CORS pages are back inside the old master tolerance. - Sponsor button: drop LinkButton's markdown margins and 1.5rem icon so it is centred and the heart matches the text. - Hero terminal: gate the no-flash rule on a flag set by an inline script during parsing instead of @media (scripting), which older browsers ignore. - Get started: include the server through a file= fence so the source checks see it, instead of a raw import. - Footer: give the link columns a visually hidden h2 so they no longer sit under the sponsors heading. - Sponsor avatars: allow the GitHub avatar hosts so Astro optimizes them. - Merge the duplicated .hh-btn rule; unlayered CSS already beats Starlight's layered margins, so !important is not needed. * ui: make get-started steps build on a clean machine - Run step: `go mod tidy` before `go run`. The server imports echo/v5/middleware, which needs golang.org/x/time; `go get` of the root package does not record it, so the build failed with a missing go.sum entry. - Sponsor avatars: back to plain <img> with the ?size= 2x sources. Astro <Image> resized them to 1x (blurry on high-DPI screens) and made every build depend on downloading them from GitHub. - Search: quick links always close the palette, as Starlight does for its own result links; drop the observer's 10s cutoff, since Pagefind can mount late in a background tab. - Hero terminal: if the typing script never runs, reveal the static state after 2s instead of leaving the terminal blank. Read the command from the markup so it is written once. - FeatureCard owns its final styles instead of copying Card's and having terminal.css override them. * ui: light-mode text hierarchy, hero button icons, search observer bound - Light theme: darken gray-3 to #42566a so secondary text again sits between gray-2 and caption gray-4 (7.3:1 vs 5.2:1 on the page background); the two were nearly the same color. - Hero buttons: size LinkButton's 1.5rem icons to the button text. - Search: bound the observer to 10s of visible time, so it still stops when Pagefind never mounts but a background tab gets the launchpad once shown. - Launchpad labels share the English key set as a type; drop a fallback the build already rules out and an ignore attribute Pagefind never sees.
1 parent 9d234d5 commit a6cece6

11 files changed

Lines changed: 336 additions & 273 deletions

File tree

‎site/astro.config.mjs‎

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export default defineConfig({
6464
// Modal header title (defaults to "Echo Docs AI" from the project name).
6565
'data-modal-title': 'Ask AI',
6666
'data-project-color': '#00afd1',
67-
// Modal header logo — the same sparkle as the "Ask AI" header pill
67+
// Modal header logo — the same star as the "Ask AI" header pill
6868
// (not the Echo cube). The floating launcher that also used this is hidden.
6969
'data-project-logo': '/ask-ai.svg',
7070
// Sync the widget's light/dark with the site (we set data-theme on <html>).
@@ -88,13 +88,6 @@ export default defineConfig({
8888
href: 'https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&family=Fragment+Mono&display=swap',
8989
},
9090
},
91-
{
92-
tag: 'link',
93-
attrs: {
94-
rel: 'stylesheet',
95-
href: 'https://unpkg.com/@phosphor-icons/web@2.1.1/src/regular/style.css',
96-
},
97-
},
9891
// Default social card. Starlight already emits og:title/description/url
9992
// and twitter:card=summary_large_image, but no image — add a site-wide
10093
// default so shares aren't imageless. Absolute URLs are required by

‎site/performance-baseline.json‎

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,61 @@
11
{
22
"/": {
33
"html": {
4-
"raw": 27523,
5-
"gzip": 8601
4+
"raw": 51106,
5+
"gzip": 11580
66
},
77
"firstLoadAssets": {
8-
"raw": 94854,
9-
"gzip": 19546
8+
"raw": 121132,
9+
"gzip": 27418
1010
},
1111
"externalDomains": [
1212
"fonts.googleapis.com",
1313
"github.com",
14-
"unpkg.com",
1514
"widget.kapa.ai",
1615
"www.googletagmanager.com"
1716
]
1817
},
1918
"/middleware/cors/": {
2019
"html": {
21-
"raw": 60522,
22-
"gzip": 11390
20+
"raw": 64337,
21+
"gzip": 12671
2322
},
2423
"firstLoadAssets": {
25-
"raw": 118355,
26-
"gzip": 26052
24+
"raw": 123856,
25+
"gzip": 28794
2726
},
2827
"externalDomains": [
2928
"fonts.googleapis.com",
30-
"unpkg.com",
3129
"widget.kapa.ai",
3230
"www.googletagmanager.com"
3331
]
3432
},
3533
"/next/": {
3634
"html": {
37-
"raw": 27645,
38-
"gzip": 8615
35+
"raw": 50213,
36+
"gzip": 11409
3937
},
4038
"firstLoadAssets": {
41-
"raw": 94869,
42-
"gzip": 19549
39+
"raw": 121152,
40+
"gzip": 27425
4341
},
4442
"externalDomains": [
4543
"fonts.googleapis.com",
4644
"github.com",
47-
"unpkg.com",
4845
"www.googletagmanager.com"
4946
]
5047
},
5148
"/next/middleware/cors/": {
5249
"html": {
53-
"raw": 60976,
54-
"gzip": 11405
50+
"raw": 64150,
51+
"gzip": 12326
5552
},
5653
"firstLoadAssets": {
57-
"raw": 118370,
58-
"gzip": 26055
54+
"raw": 123876,
55+
"gzip": 28801
5956
},
6057
"externalDomains": [
6158
"fonts.googleapis.com",
62-
"unpkg.com",
6359
"www.googletagmanager.com"
6460
]
6561
}

‎site/public/ask-ai.svg‎

Lines changed: 1 addition & 1 deletion
Loading

‎site/src/components/ConfigReference.astro‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import prometheusManifest from '../generated/prometheus.json';
55
import telemetryManifest from '../generated/open-telemetry.json';
66
import externalSources from '../../external-sources.json';
77
import { formatRevision } from '../data/source.mjs';
8+
import { Badge } from '@astrojs/starlight/components';
89
910
interface Props {
1011
name: string;
@@ -43,7 +44,7 @@ const sourceUrl = (file: string, line: number) => `${repository}/blob/${manifest
4344
<thead><tr><th scope="col">{labels.field}</th><th scope="col">{labels.type}</th><th scope="col">{labels.description}</th><th scope="col">{labels.source}</th></tr></thead>
4445
<tbody>
4546
{config.fields.map((field) => <tr>
46-
<th scope="row"><code>{field.name}</code>{field.deprecated && <span class="deprecated">{labels.deprecated}</span>}</th>
47+
<th scope="row"><code>{field.name}</code>{field.deprecated && <Badge text={labels.deprecated} variant="caution" size="small" class="deprecated" />}</th>
4748
<td><code>{field.type}</code></td>
4849
<td class="field-description">{field.doc || '—'}</td>
4950
<td><a href={sourceUrl(config.file, field.line)} aria-label={`${field.name} ${labels.source}`}>L{field.line}</a></td>
@@ -76,5 +77,5 @@ const sourceUrl = (file: string, line: number) => `${repository}/blob/${manifest
7677
tbody th { font-weight: 500; }
7778
code { overflow-wrap: anywhere; white-space: pre-wrap; }
7879
.field-description { min-width: 16rem; white-space: pre-line; overflow-wrap: anywhere; }
79-
.deprecated { display: inline-block; margin-inline-start: .5rem; font-size: .75em; color: var(--sl-color-text-accent); }
80+
.deprecated { margin-inline-start: .5rem; }
8081
</style>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
// Starlight's <Card> markup with an <h3> title: Card renders its title as a <p>,
3+
// which drops homepage feature titles out of the heading outline.
4+
import { Icon } from '@astrojs/starlight/components';
5+
import type { StarlightIcon } from '@astrojs/starlight/types';
6+
7+
interface Props {
8+
icon: StarlightIcon;
9+
title: string;
10+
}
11+
12+
const { icon, title } = Astro.props;
13+
---
14+
15+
<article class="card sl-flex">
16+
<h3 class="title sl-flex">
17+
<Icon name={icon} class="icon" size="1.333em" />
18+
<span>{title}</span>
19+
</h3>
20+
<div class="body"><slot /></div>
21+
</article>
22+
23+
<style>
24+
/* .card .title outranks the theme's global .sl-markdown-content heading rule. */
25+
.card {
26+
flex-direction: column;
27+
gap: clamp(0.5rem, calc(0.125rem + 1vw), 1rem);
28+
border: 1px solid var(--sl-color-hairline);
29+
border-radius: 13px;
30+
background: var(--sl-color-bg-sidebar);
31+
padding: 20px;
32+
}
33+
.card .title {
34+
align-items: center;
35+
gap: 0.7rem;
36+
font-family: var(--sl-font);
37+
font-size: 1.05rem;
38+
font-weight: 600;
39+
letter-spacing: normal;
40+
line-height: var(--sl-line-height-headings);
41+
color: var(--sl-color-white);
42+
}
43+
.icon {
44+
flex-shrink: 0;
45+
color: var(--echo-cyan-text);
46+
}
47+
.body {
48+
margin: 0;
49+
font-size: 0.9rem;
50+
line-height: 1.62;
51+
color: var(--sl-color-gray-3);
52+
}
53+
</style>

‎site/src/components/Footer.astro‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
import Default from '@astrojs/starlight/components/Footer.astro';
3+
import { Icon } from '@astrojs/starlight/components';
34
45
const year = new Date().getFullYear();
56
---
@@ -8,10 +9,10 @@ const year = new Date().getFullYear();
89
<span>© {year} LabStack LLC. Released under the MIT License.</span>
910
<div class="echo-social">
1011
<a href="https://x.com/labstack" target="_blank" rel="noopener noreferrer" aria-label="LabStack on X">
11-
<i class="ph ph-x-logo"></i>
12+
<Icon name="x.com" />
1213
</a>
1314
<a href="https://github.com/labstack/echo" target="_blank" rel="noopener noreferrer" aria-label="Echo on GitHub">
14-
<i class="ph ph-github-logo"></i>
15+
<Icon name="github" />
1516
</a>
1617
</div>
1718
</footer>

‎site/src/components/HomeHero.astro‎

Lines changed: 16 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,66 +4,40 @@
44
// static final-state fallback for reduced motion.
55
import { starsLabel } from '../data/github.ts';
66
import stable from '../generated/echo-source.json';
7-
import { Code } from 'astro:components';
7+
import { Code, Icon, LinkButton } from '@astrojs/starlight/components';
88
import helloWorldSource from '../../../cookbook/hello-world/server.go?raw';
99
import { sourceSnippet } from '../../plugins/remark-source-code.mjs';
1010
const next = process.env.DOCS_CHANNEL === 'next';
1111
const helloWorldCode = sourceSnippet(helloWorldSource, 'hero', 'cookbook/hello-world/server.go');
12-
// The code window is aria-hidden, so Shiki's <pre> must not be a tab stop.
13-
const notFocusable = { pre(node: { properties: Record<string, unknown> }) { delete node.properties.tabindex; } };
1412
---
1513

16-
<section class="hh">
14+
<section class="hh not-content">
1715
<div class="hh-left">
18-
<span class="hh-eyebrow"><i class="ph ph-sparkle"></i> {next ? 'Echo next — preview documentation' : `Echo ${stable.release} — release documentation`}</span>
16+
<span class="hh-eyebrow"><Icon name="star" /> {next ? 'Echo next — preview documentation' : `Echo ${stable.release} — release documentation`}</span>
1917
<h1>Build fast Go APIs.<br /><span class="g">Without the bloat.</span></h1>
2018
<p class="hh-sub">
2119
A high-performance, minimalist Go web framework — a zero-allocation
2220
router, batteries-included middleware, and an expressive API. Ship
2321
production services in minutes.
2422
</p>
2523
<div class="hh-cta">
26-
<a class="hh-btn pri" href="/guide/quickstart/">Get Started <i class="ph ph-arrow-right"></i></a>
27-
<a class="hh-btn sec" href="https://github.com/labstack/echo" target="_blank" rel="noopener noreferrer"><i class="ph ph-star"></i> {starsLabel} on GitHub</a>
24+
<LinkButton class="hh-btn pri" href="/guide/quickstart/" icon="right-arrow">Get Started</LinkButton>
25+
<LinkButton class="hh-btn sec" variant="secondary" icon="github" iconPlacement="start" href="https://github.com/labstack/echo" target="_blank" rel="noopener noreferrer">{starsLabel} on GitHub</LinkButton>
2826
</div>
29-
<button class="hh-install" type="button" data-copy="go get github.com/labstack/echo/v5" aria-label="Copy install command">
30-
<span class="p">$</span> go get github.com/labstack/echo/v5 <i class="ph ph-copy cp"></i>
31-
</button>
27+
<div class="hh-install"><Code code="go get github.com/labstack/echo/v5" lang="bash" frame="none" wrap /></div>
3228
</div>
3329

34-
<div class="hh-win" aria-hidden="true">
35-
<div class="bar">
36-
<i style="background:#ff5f57"></i><i style="background:#febc2e"></i><i style="background:#28c840"></i>
37-
<span class="fn">server.go · excerpt</span>
38-
</div>
39-
<Code code={helloWorldCode} lang="go" style="background: transparent" transformers={[notFocusable]} />
40-
<div class="hh-term" data-term>
41-
<div><span class="pr">~/echo $</span> <span data-cmd></span><span class="hh-cur" data-cur></span></div>
42-
<div data-out></div>
30+
<div class="hh-win">
31+
<Code code={helloWorldCode} lang="go" title="server.go · excerpt" frame="code" wrap />
32+
<div class="hh-term" data-term aria-hidden="true">
33+
<script is:inline>document.currentScript.parentElement.setAttribute('data-js', '');</script>
34+
<div><span class="pr">~/echo $</span> <span data-cmd>curl localhost:1323</span><span class="hh-cur" data-cur style="display:none"></span></div>
35+
<div data-out><div class="dim">HTTP/1.1 200 OK</div><div class="response">Hello, World!</div></div>
4336
</div>
4437
</div>
4538
</section>
4639

4740
<script>
48-
// --- install copy button ---
49-
document.querySelectorAll<HTMLButtonElement>('.hh-install').forEach((btn) => {
50-
btn.addEventListener('click', async () => {
51-
const text = btn.dataset.copy || '';
52-
try {
53-
await navigator.clipboard.writeText(text);
54-
const icon = btn.querySelector('.cp');
55-
if (icon) {
56-
icon.classList.remove('ph-copy');
57-
icon.classList.add('ph-check');
58-
setTimeout(() => {
59-
icon.classList.remove('ph-check');
60-
icon.classList.add('ph-copy');
61-
}, 1400);
62-
}
63-
} catch {}
64-
});
65-
});
66-
6741
// --- terminal typing animation ---
6842
const term = document.querySelector('[data-term]');
6943
if (term) {
@@ -72,17 +46,14 @@ const notFocusable = { pre(node: { properties: Record<string, unknown> }) { dele
7246
const curEl = term.querySelector('[data-cur]') as HTMLElement;
7347
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
7448

75-
const response =
76-
'<span class="dim">HTTP/1.1 200 OK</span>\n' +
77-
'<span class="json">Hello, World!</span>';
78-
const cmd = 'curl localhost:1323';
49+
const cmd = cmdEl.textContent ?? '';
7950

8051
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
8152

8253
async function run() {
8354
while (true) {
8455
cmdEl.textContent = '';
85-
outEl.innerHTML = '';
56+
outEl.hidden = true;
8657
curEl.style.display = 'inline-block';
8758
await sleep(900);
8859
for (const ch of cmd) {
@@ -91,16 +62,16 @@ const notFocusable = { pre(node: { properties: Record<string, unknown> }) { dele
9162
}
9263
await sleep(450);
9364
curEl.style.display = 'none';
94-
outEl.innerHTML = '<pre style="margin:6px 0 0;padding:0;background:none;font:inherit">' + response + '</pre>';
65+
outEl.hidden = false;
9566
await sleep(3600);
9667
}
9768
}
9869

9970
if (reduce) {
10071
cmdEl.textContent = cmd;
10172
curEl.style.display = 'none';
102-
outEl.innerHTML = '<pre style="margin:6px 0 0;padding:0;background:none;font:inherit">' + response + '</pre>';
10373
} else {
74+
term.setAttribute('data-typing', '');
10475
run();
10576
}
10677
}

0 commit comments

Comments
 (0)