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
6 changes: 6 additions & 0 deletions .github/workflows/website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ jobs:
- name: Check links
run: python3 scripts/check_links.py dist

- name: Install browser
run: npx playwright install --with-deps chromium

- name: Check branding and responsive navigation
run: npm test

- name: Upload site
if: github.event_name != 'pull_request'
uses: actions/upload-pages-artifact@v3
Expand Down
2 changes: 2 additions & 0 deletions website/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
node_modules/
dist/
.astro/
test-results/
playwright-report/
1 change: 1 addition & 0 deletions website/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The SQLBuild website: the homepage at `/` and the documentation at `/docs/`, bui
- `src/content/docs/docs/`: the documentation pages (MDX), first ported from the Mintlify docs and now edited here. `src/navigation.mjs` holds the sidebar and redirects.
- `src/components/`: the page layout, header and page title, replacing Starlight's defaults. `src/components/mintlify/` has drop-in versions of the Mintlify components the pages use (`Card`, `CardGroup`, `Frame`, `Note`, `Warning`).
- `public/fonts/`: self-hosted fonts, each with its licence.
- `public/brand.css`: shared SQLStack attribution styling for the homepage and docs. The SQLStack symbols in `public/assets/sqlstack*.png` are unchanged copies from `sqlstack-io/sqlstack` at revision `9b37b9e128d36930e1fe0c5880606525100b228b` (`assets/icons/sqlstack.png` and `sqlstack-dark.png`).

Search uses Pagefind, and `llms.txt`, `llms-small.txt` and `llms-full.txt` are generated at build time.

Expand Down
2 changes: 2 additions & 0 deletions website/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default defineConfig({
},
favicon: '/favicon.png',
head: [
{ tag: 'link', attrs: { rel: 'stylesheet', href: '/brand.css' } },
{ tag: 'meta', attrs: { property: 'og:image', content: 'https://sqlbuild.com/assets/og-sqlbuild-docs.png' } },
{ tag: 'meta', attrs: { property: 'og:image:width', content: '1200' } },
{ tag: 'meta', attrs: { property: 'og:image:height', content: '630' } },
Expand All @@ -35,6 +36,7 @@ export default defineConfig({
SiteTitle: './src/components/SiteTitle.astro',
Pagination: './src/components/Pagination.astro',
ThemeSelect: './src/components/ThemeSelect.astro',
Footer: './src/components/Footer.astro',
},
customCss: ['./src/styles/fonts.css', './src/styles/sqlbuild.css'],
expressiveCode: {
Expand Down
48 changes: 48 additions & 0 deletions website/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview"
"preview": "astro preview",
"test": "playwright test"
},
"dependencies": {
"@astrojs/starlight": "^0.42.4",
"@lucide/astro": "^1.48.0",
"astro": "^7.3.5",
"sharp": "^0.35.4",
"starlight-llms-txt": "^0.12.0"
},
"devDependencies": {
"@playwright/test": "^1.64.1"
}
}
14 changes: 14 additions & 0 deletions website/playwright.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineConfig } from '@playwright/test';

export default defineConfig({
testDir: './tests',
use: {
baseURL: process.env.SITE_URL || 'http://127.0.0.1:4174',
deviceScaleFactor: 2,
launchOptions: process.env.CHROME_PATH ? { executablePath: process.env.CHROME_PATH } : {},
},
webServer: process.env.SITE_URL ? undefined : {
command: 'npm run preview -- --host 127.0.0.1 --port 4174 --ignore-lock',
url: 'http://127.0.0.1:4174',
},
});
Binary file added website/public/assets/sqlstack-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added website/public/assets/sqlstack.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 67 additions & 0 deletions website/public/brand.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/* Shared attribution for the static homepage and Starlight documentation. */
.brand-group {
display: grid;
flex-shrink: 0;
}

.brand-byline {
display: flex;
align-items: center;
min-height: 24px;
/* The wordmark starts at x=447 in the unchanged 1626px-wide lockup. */
margin-inline-start: 27.5%;
color: var(--sqb-muted, var(--muted-foreground));
font-size: 11px;
line-height: 14px;
text-decoration: none;
white-space: nowrap;
}

.sqlstack-family {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid var(--sqb-line, var(--inner-line));
font-size: 13px;
}

.sqlstack-parent {
display: inline-flex;
align-items: center;
gap: 10px;
}

.sqlstack-parent img {
width: 28px;
height: auto;
}

.sqlstack-for-dark,
:root[data-theme='dark'] .sqlstack-for-light {
display: none;
}

:root[data-theme='dark'] .sqlstack-for-dark {
display: block;
}

.sqlstack-products {
display: flex;
flex-wrap: wrap;
gap: 8px 20px;
}

.sqlstack-family a {
color: var(--sqb-muted, var(--muted-foreground));
text-decoration: none;
}

.brand-byline:hover,
.sqlstack-family a:hover {
color: var(--sqb-fg, var(--foreground));
text-decoration: underline;
}
25 changes: 21 additions & 4 deletions website/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<link rel="icon" type="image/png" href="assets/icon.png" />
<link rel="preload" href="/fonts/inter-latin-wght-normal.woff2" as="font" type="font/woff2" crossorigin />
<link rel="stylesheet" href="styles.css" />
<link rel="stylesheet" href="brand.css" />
<script>
(function () {
var stored = localStorage.getItem('sqlbuild-theme');
Expand All @@ -35,10 +36,13 @@
<body>
<header class="site-header">
<div class="wrap">
<a class="brand" href="/" aria-label="SQLBuild home">
<img class="logo-for-light" src="assets/logo-light.png" alt="SQLBuild" width="116" height="30" />
<img class="logo-for-dark" src="assets/logo-dark.png" alt="SQLBuild" width="116" height="30" />
</a>
<div class="brand-group">
<a class="brand" href="/" aria-label="SQLBuild home">
<img class="logo-for-light" src="assets/logo-light.png" alt="SQLBuild" width="116" height="30" />
<img class="logo-for-dark" src="assets/logo-dark.png" alt="SQLBuild" width="116" height="30" />
</a>
<a class="brand-byline" href="https://sqlstack.io">by SQLStack</a>
</div>
<nav class="nav" aria-label="Main">
<a class="hide-sm" href="#how">How it works</a>
<a href="/docs/">Docs</a>
Expand Down Expand Up @@ -492,6 +496,19 @@ <h2>Change your warehouse like you change code</h2>
<a href="https://pypi.org/project/sqlbuild/">PyPI</a>
</span>
</div>
<div class="wrap sqlstack-family">
<a class="sqlstack-parent" href="https://sqlstack.io">
<img class="sqlstack-for-light" src="/assets/sqlstack.png" width="28" height="23" alt="" />
<img class="sqlstack-for-dark" src="/assets/sqlstack-dark.png" width="28" height="23" alt="" />
<span>Part of SQLStack</span>
</a>
<nav class="sqlstack-products" aria-label="SQLStack products">
<a href="/">SQLBuild</a>
<a href="https://sqlclean.io">SQLClean</a>
<a href="https://docs.streambuild.dev">SQLTide</a>
<a href="https://docs.fensu.dev">Fensu</a>
</nav>
</div>
</footer>

<script>
Expand Down
3 changes: 2 additions & 1 deletion website/public/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
html {
-webkit-text-size-adjust: 100%;
scroll-behavior: smooth;
scroll-padding-top: 88px;
}

body {
Expand Down Expand Up @@ -139,7 +140,7 @@ pre {
display: flex;
align-items: center;
justify-content: space-between;
height: 60px;
height: 80px;
}

.brand {
Expand Down
18 changes: 18 additions & 0 deletions website/src/components/Footer.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
import DefaultFooter from '@astrojs/starlight/components/Footer.astro';
---

<DefaultFooter />
<div class="sqlstack-family">
<a class="sqlstack-parent" href="https://sqlstack.io">
<img class="sqlstack-for-light" src="/assets/sqlstack.png" width="28" height="23" alt="" />
<img class="sqlstack-for-dark" src="/assets/sqlstack-dark.png" width="28" height="23" alt="" />
<span>Part of SQLStack</span>
</a>
<nav class="sqlstack-products" aria-label="SQLStack products">
<a href="/">SQLBuild</a>
<a href="https://sqlclean.io">SQLClean</a>
<a href="https://docs.streambuild.dev">SQLTide</a>
<a href="https://docs.fensu.dev">Fensu</a>
</nav>
</div>
11 changes: 7 additions & 4 deletions website/src/components/SiteTitle.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import logoLight from '../assets/logo-light.png';
---

<div class="site-title">
<a class="logo" href="/" aria-label="SQLBuild home">
<img class="logo-for-light" src={logoLight.src} alt="SQLBuild" width={116} height={30} />
<img class="logo-for-dark" src={logoDark.src} alt="SQLBuild" width={116} height={30} />
</a>
<div class="brand-group">
<a class="logo" href="/" aria-label="SQLBuild home">
<img class="logo-for-light" src={logoLight.src} alt="SQLBuild" width={116} height={30} />
<img class="logo-for-dark" src={logoDark.src} alt="SQLBuild" width={116} height={30} />
</a>
<a class="brand-byline" href="https://sqlstack.io">by SQLStack</a>
</div>
<a class="docs" href="/docs/">Docs</a>
</div>

Expand Down
2 changes: 1 addition & 1 deletion website/src/styles/sqlbuild.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
:root {
--sl-font: 'Inter', system-ui, sans-serif;
--sl-font-mono: 'IBM Plex Mono', 'SQLBuild Box Drawing', ui-monospace, monospace;
--sl-nav-height: 60px;
--sl-nav-height: 80px;
--sl-content-width: 50rem;
--sl-sidebar-width: 18rem;
--sqb-toc-width: 16rem;
Expand Down
76 changes: 76 additions & 0 deletions website/tests/branding.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { test, expect } from '@playwright/test';

for (const path of ['/', '/docs/quickstart/']) {
for (const width of [320, 390, 800, 1440]) {
for (const theme of ['light', 'dark']) {
test(`${path} ${width}px ${theme}: branding and header offsets`, async ({ page }) => {
await page.setViewportSize({ width, height: 900 });
await page.emulateMedia({ colorScheme: theme, reducedMotion: 'reduce' });
await page.goto(path);
await page.evaluate(() => document.fonts.ready);
await expect(page.locator('html')).toHaveAttribute('data-theme', theme);
await expect(page.getByRole('link', { name: 'by SQLStack', exact: true })).toHaveAttribute('href', 'https://sqlstack.io');
await expect(page.getByRole('link', { name: 'Part of SQLStack', exact: true })).toHaveAttribute('href', 'https://sqlstack.io');
const products = page.getByRole('navigation', { name: 'SQLStack products' });
await expect(products.getByRole('link')).toHaveText(['SQLBuild', 'SQLClean', 'SQLTide', 'Fensu']);
for (const [name, href] of Object.entries({ SQLBuild: '/', SQLClean: 'https://sqlclean.io', SQLTide: 'https://docs.streambuild.dev', Fensu: 'https://docs.fensu.dev' })) {
await expect(products.getByRole('link', { name, exact: true })).toHaveAttribute('href', href);
}
const metrics = await page.evaluate(() => {
const box = (selector) => document.querySelector(selector).getBoundingClientRect();
const header = box('.site-header');
const brand = box('.brand-group');
const byline = box('.brand-byline');
const controls = box('.nav, .search');
return {
top: brand.top - header.top,
bottom: header.bottom - brand.bottom,
centerDifference: Math.abs((brand.top + brand.bottom - controls.top - controls.bottom) / 2),
textOffset: byline.left - brand.left,
overflow: document.documentElement.scrollWidth - innerWidth,
visibleSymbols: [...document.querySelectorAll('.sqlstack-parent img')].filter((img) => getComputedStyle(img).display !== 'none').map((img) => new URL(img.src).pathname),
};
});
// The entire 54px group needs breathing room, not just the logo.
expect(metrics.top).toBeGreaterThanOrEqual(12);
expect(metrics.bottom).toBeGreaterThanOrEqual(12);
expect(metrics.centerDifference).toBeLessThanOrEqual(1);
expect(metrics.textOffset).toBeCloseTo(31.85, 0);
expect(metrics.overflow).toBeLessThanOrEqual(0);
expect(metrics.visibleSymbols).toEqual([`/assets/sqlstack${theme === 'dark' ? '-dark' : ''}.png`]);
for (const image of await page.locator('.brand-group img, .sqlstack-parent img').all()) {
expect(await image.evaluate((img) => img.complete && img.naturalWidth > 0)).toBe(true);
}
if (path === '/') {
const howLink = page.getByRole('link', { name: 'How it works', exact: true });
if (await howLink.isVisible()) {
// Keep anchor geometry deterministic without sampling smooth-scroll frames.
await page.evaluate(() => { document.documentElement.style.scrollBehavior = 'auto'; });
await howLink.click();
} else {
await page.goto('/#how');
}
await expect(page.locator('#how h2')).toBeInViewport();
const headerBottom = await page.locator('.site-header').evaluate((el) => el.getBoundingClientRect().bottom);
expect((await page.locator('#how h2').boundingBox()).y).toBeGreaterThanOrEqual(headerBottom);
} else {
await expect(page.getByRole('navigation', { name: 'Pagination' })).toBeVisible();
const anchor = page.locator('.sl-markdown-content h2[id]').first();
const id = await anchor.getAttribute('id');
await page.goto(`${path}#${id}`);
await expect(anchor).toBeInViewport();
const headerBottom = await page.locator('.site-header').evaluate((el) => el.getBoundingClientRect().bottom);
expect((await anchor.boundingBox()).y).toBeGreaterThanOrEqual(headerBottom);
if (width < 800) {
await page.getByRole('button', { name: 'Menu', exact: true }).click();
const sidebar = page.locator('#starlight__sidebar');
await expect(sidebar).toBeVisible();
expect((await sidebar.boundingBox()).y).toBeGreaterThanOrEqual(headerBottom - 1);
await page.keyboard.press('Escape');
await expect(sidebar).not.toBeVisible();
}
}
});
}
}
}
Loading