diff --git a/README.md b/README.md index 5946185..9a3f020 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ light/dark preference in the browser. Without JavaScript, the page remains usabl ## Checks Browser tests cover theme selection and persistence, keyboard navigation, responsive layouts, -product statuses, sharing metadata and image dimensions. They require Node.js and Python 3: +product statuses, sharing metadata, image dimensions and favicon transparency. They require Node.js and Python 3: ```sh npm ci @@ -35,13 +35,15 @@ publishing or deployment step. ## Sharing assets -`favicon.ico` contains 16px, 32px and 48px icons. PNG favicons and a 180px Apple touch icon use the -original SQLStack mark on white backing for legibility in light and dark browser chrome. +`favicon.ico` contains transparent 16px, 32px and 48px icons. The PNG favicons preserve the same +original SQLStack mark without its former white backing. The existing theme script selects a white +variant for dark browser chrome using the system preference, independently of the page's theme +override. The 180px Apple home-screen icon retains its opaque background. `assets/og-sqlstack-dark.png` is an opaque 1200 × 630 sharing card using the original SQLStack wordmark and the site's Inter typography on a near-black background. Its branding and text fit inside the central 630px square so small thumbnail crops retain the complete message. The previous -`assets/og-sqlstack.png` remains available for cached metadata. These are raster derivatives, not -replacement logos or SVG originals. +`assets/og-sqlstack.png` remains available for cached metadata. These use the original raster artwork, +not replacement logos or SVG originals. Canonical and Open Graph/Twitter URLs target `https://sqlstack.io/`. Local previews can verify the metadata and assets, but public social-crawler validation requires a separately authorized deployment. diff --git a/assets/favicon-16.png b/assets/favicon-16.png index c63a40a..cca1cc9 100644 Binary files a/assets/favicon-16.png and b/assets/favicon-16.png differ diff --git a/assets/favicon-32.png b/assets/favicon-32.png index c8fcab8..fc01076 100644 Binary files a/assets/favicon-32.png and b/assets/favicon-32.png differ diff --git a/assets/favicon-dark-32.png b/assets/favicon-dark-32.png new file mode 100644 index 0000000..ea82819 Binary files /dev/null and b/assets/favicon-dark-32.png differ diff --git a/favicon.ico b/favicon.ico index 4db1904..371fee3 100644 Binary files a/favicon.ico and b/favicon.ico differ diff --git a/index.html b/index.html index 49c0852..34cea79 100644 --- a/index.html +++ b/index.html @@ -22,9 +22,7 @@ - - - + @@ -58,7 +56,6 @@
Compile-time checks, tested behaviour and enforceable architecture.
diff --git a/styles.css b/styles.css index c7ace19..ecfe6f2 100644 --- a/styles.css +++ b/styles.css @@ -25,9 +25,6 @@ --focus: #1768ba; --button-border: #bdc9da; --button-hover: #f3f6fa; - --primary: #0866db; - --primary-ink: #ffffff; - --primary-hover: #0755b7; --gold-ink: #765600; --gold-bg: #fff3cb; --pink-ink: #a52138; @@ -50,9 +47,6 @@ --focus: #75b5ff; --button-border: #626a76; --button-hover: #171a1f; - --primary: #3788e8; - --primary-ink: #0d0f12; - --primary-hover: #66a6f0; --gold-ink: #ffe07a; --gold-bg: #352b12; --pink-ink: #ffacba; @@ -99,8 +93,6 @@ h1 span { display: block; } .hero-actions { display: flex; justify-content: center; flex-wrap: wrap; gap: 12px; margin-top: 20px; } .button { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 14px 20px; min-height: 48px; border: 1px solid var(--button-border); border-radius: 2px; color: var(--ink); font-size: 14px; line-height: 20px; text-decoration: none; } .button:hover { background: var(--button-hover); text-decoration: none; } -.button.primary { color: var(--primary-ink); background: var(--primary); border-color: var(--primary); } -.button.primary:hover { background: var(--primary-hover); border-color: var(--primary-hover); } .tool-row { display: grid; grid-template-columns: 304px minmax(0, 1fr); gap: 24px; border-top: 1px solid var(--line); padding: 18px 0 16px; } .category { padding-top: 8px; } .category .eyebrow { margin: 0 0 8px; color: var(--muted); font-size: 14px; line-height: 20px; text-transform: uppercase; } diff --git a/tests/website.spec.mjs b/tests/website.spec.mjs index 26b7b91..97816e4 100644 --- a/tests/website.spec.mjs +++ b/tests/website.spec.mjs @@ -8,9 +8,11 @@ for (const system of ['light', 'dark']) { const toggle = page.getByRole('button', { name: 'Dark mode', exact: true }); await expect(page.locator('html')).toHaveCSS('color-scheme', system); await expect(toggle).toHaveAttribute('aria-pressed', String(system === 'dark')); + await expect(page.locator('[data-favicon]')).toHaveAttribute('href', `assets/favicon${system === 'dark' ? '-dark' : ''}-32.png?v=2`); await page.emulateMedia({ colorScheme: other }); await expect(page.locator('html')).toHaveCSS('color-scheme', other); + await expect(page.locator('[data-favicon]')).toHaveAttribute('href', `assets/favicon${other === 'dark' ? '-dark' : ''}-32.png?v=2`); await page.emulateMedia({ colorScheme: system }); await expect(page.locator('html')).toHaveCSS('color-scheme', system); @@ -24,6 +26,7 @@ for (const system of ['light', 'dark']) { await page.emulateMedia({ colorScheme: system }); await expect(page.locator('html')).toHaveCSS('color-scheme', other); await expect(page.locator('meta[name="theme-color"]')).toHaveAttribute('content', other === 'dark' ? '#0d0f12' : '#ffffff'); + await expect(page.locator('[data-favicon]')).toHaveAttribute('href', `assets/favicon${system === 'dark' ? '-dark' : ''}-32.png?v=2`); }); } @@ -59,7 +62,11 @@ test('approved products, sharing assets and responsive navigation work in both t expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); await expect(page.getByRole('button', { name: 'Dark mode', exact: true })).toBeInViewport(); } - await page.getByRole('link', { name: 'Find a tool', exact: false }).click(); + await expect(page.locator('.hero').getByRole('link')).toHaveCount(1); + await expect(page.locator('.hero').getByRole('link')).toHaveAccessibleName('View on GitHub'); + await expect(page.locator('.hero').getByRole('link')).toHaveAttribute('href', 'https://github.com/sqlstack-io/sqlstack'); + await page.getByRole('link', { name: 'Skip to tools', exact: true }).focus(); + await page.keyboard.press('Enter'); await expect(page).toHaveURL(/#tools$/); await page.keyboard.press('Tab'); await expect(page.getByRole('link', { name: 'SQLBuild website', exact: true })).toBeFocused(); @@ -75,6 +82,7 @@ test('approved products, sharing assets and responsive navigation work in both t ['/assets/og-sqlstack-dark.png', 1200, 630], ['/assets/favicon-16.png', 16, 16], ['/assets/favicon-32.png', 32, 32], + ['/assets/favicon-dark-32.png', 32, 32], ['/assets/apple-touch-icon.png', 180, 180], ]) { const response = await request.get(path); @@ -90,4 +98,25 @@ test('approved products, sharing assets and responsive navigation work in both t expect(ico.readUInt16LE(2)).toBe(1); expect(ico.readUInt16LE(4)).toBe(3); expect([ico[6], ico[22], ico[38]]).toEqual([16, 32, 48]); + + for (const path of ['/assets/favicon-16.png', '/assets/favicon-32.png', '/assets/favicon-dark-32.png', '/favicon.ico']) { + const pixels = await page.evaluate(async (src) => { + const image = new Image(); + image.src = src; + await image.decode(); + const canvas = document.createElement('canvas'); + canvas.width = image.naturalWidth; + canvas.height = image.naturalHeight; + const context = canvas.getContext('2d'); + context.drawImage(image, 0, 0); + const data = context.getImageData(0, 0, canvas.width, canvas.height).data; + const corners = [0, canvas.width - 1, canvas.width * (canvas.height - 1), canvas.width * canvas.height - 1]; + return { + corners: corners.map((pixel) => data[pixel * 4 + 3]), + opaquePixels: Array.from(data).filter((value, index) => index % 4 === 3 && value === 255).length, + }; + }, path); + expect(pixels.corners, path).toEqual([0, 0, 0, 0]); + expect(pixels.opaquePixels, path).toBeGreaterThan(0); + } }); diff --git a/theme.js b/theme.js index 4d4ae7b..1aba173 100644 --- a/theme.js +++ b/theme.js @@ -13,6 +13,8 @@ function apply(theme) { root.dataset.theme = theme; document.querySelector('meta[name="theme-color"]').content = theme === 'dark' ? '#0d0f12' : '#ffffff'; + // Match browser chrome, even when the page theme is explicitly overridden. + document.querySelector('[data-favicon]').href = system.matches ? 'assets/favicon-dark-32.png?v=2' : 'assets/favicon-32.png?v=2'; const toggle = document.querySelector('[data-theme-toggle]'); if (toggle) { toggle.setAttribute('aria-pressed', String(theme === 'dark')); @@ -23,7 +25,7 @@ apply(preference || (system.matches ? 'dark' : 'light')); system.addEventListener('change', () => { - if (!preference) apply(system.matches ? 'dark' : 'light'); + apply(preference || (system.matches ? 'dark' : 'light')); }); document.addEventListener('DOMContentLoaded', () => {