diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml
index b10b2fb3fb..0dbd53aed5 100644
--- a/.github/workflows/website.yml
+++ b/.github/workflows/website.yml
@@ -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
diff --git a/website/.gitignore b/website/.gitignore
index ddce69b680..4faa99dd86 100644
--- a/website/.gitignore
+++ b/website/.gitignore
@@ -1,3 +1,5 @@
node_modules/
dist/
.astro/
+test-results/
+playwright-report/
diff --git a/website/README.md b/website/README.md
index b920a0a57f..7800276220 100644
--- a/website/README.md
+++ b/website/README.md
@@ -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.
diff --git a/website/astro.config.mjs b/website/astro.config.mjs
index 3b47d41c91..594c8e1665 100644
--- a/website/astro.config.mjs
+++ b/website/astro.config.mjs
@@ -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' } },
@@ -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: {
diff --git a/website/package-lock.json b/website/package-lock.json
index f69819c3ee..e99ca21c9e 100644
--- a/website/package-lock.json
+++ b/website/package-lock.json
@@ -12,6 +12,9 @@
"sharp": "^0.35.4",
"starlight-llms-txt": "^0.12.0"
},
+ "devDependencies": {
+ "@playwright/test": "^1.64.1"
+ },
"engines": {
"node": ">=22.12.0"
}
@@ -1733,6 +1736,22 @@
"win32"
]
},
+ "node_modules/@playwright/test": {
+ "version": "1.64.1",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.64.1.tgz",
+ "integrity": "sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.64.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/@rolldown/binding-android-arm-eabi": {
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.11.tgz",
@@ -4960,6 +4979,35 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/playwright": {
+ "version": "1.64.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.1.tgz",
+ "integrity": "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.64.1"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.64.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.1.tgz",
+ "integrity": "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/postcss": {
"version": "8.5.28",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
diff --git a/website/package.json b/website/package.json
index 0576825801..3df87e0ac7 100644
--- a/website/package.json
+++ b/website/package.json
@@ -8,7 +8,8 @@
"scripts": {
"dev": "astro dev",
"build": "astro build",
- "preview": "astro preview"
+ "preview": "astro preview",
+ "test": "playwright test"
},
"dependencies": {
"@astrojs/starlight": "^0.42.4",
@@ -16,5 +17,8 @@
"astro": "^7.3.5",
"sharp": "^0.35.4",
"starlight-llms-txt": "^0.12.0"
+ },
+ "devDependencies": {
+ "@playwright/test": "^1.64.1"
}
}
diff --git a/website/playwright.config.mjs b/website/playwright.config.mjs
new file mode 100644
index 0000000000..8aafb91d47
--- /dev/null
+++ b/website/playwright.config.mjs
@@ -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',
+ },
+});
diff --git a/website/public/assets/sqlstack-dark.png b/website/public/assets/sqlstack-dark.png
new file mode 100644
index 0000000000..d29890b7dd
Binary files /dev/null and b/website/public/assets/sqlstack-dark.png differ
diff --git a/website/public/assets/sqlstack.png b/website/public/assets/sqlstack.png
new file mode 100644
index 0000000000..ac560230ea
Binary files /dev/null and b/website/public/assets/sqlstack.png differ
diff --git a/website/public/brand.css b/website/public/brand.css
new file mode 100644
index 0000000000..1e3042bd0b
--- /dev/null
+++ b/website/public/brand.css
@@ -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;
+}
diff --git a/website/public/index.html b/website/public/index.html
index 57a33059b1..1c33ebbcf0 100644
--- a/website/public/index.html
+++ b/website/public/index.html
@@ -24,6 +24,7 @@
+