| 1 | import { existsSync, readdirSync, readFileSync } from "node:fs"; |
| 2 | import { describe, expect, it } from "vitest"; |
| 3 | import { siteCss } from "./site-css"; |
| 4 | |
| 5 | // The whale-road (codewhale-design/DIRECTION.md): one character drawn from |
| 6 | // the v2 kit, one horizon, the sea below it, and a header that stays usable |
| 7 | // in every locale. |
| 8 | const CSS = siteCss(); |
| 9 | const web = (path: string) => readFileSync(new URL(`../${path}`, import.meta.url), "utf8"); |
| 10 | const POSES = [ |
| 11 | "rest", "listen", "think", "busy", "read", "search", "write", "run", "browse", |
| 12 | "talk", "pod", "needs", "done", "hmm", "sleep", "computer", "connect", |
| 13 | ]; |
| 14 | |
| 15 | describe("the whale", () => { |
| 16 | it("ships all seventeen v2 poses in the logo gradient", () => { |
| 17 | const dir = new URL("../public/whale/", import.meta.url); |
| 18 | expect(readdirSync(dir).filter((f) => f.endsWith(".svg")).sort()).toEqual(POSES.map((p) => `${p}.svg`).sort()); |
| 19 | for (const pose of POSES) { |
| 20 | const svg = readFileSync(new URL(`${pose}.svg`, dir), "utf8"); |
| 21 | expect(svg, pose).toContain('stop-color="#1E8FD8"'); |
| 22 | expect(svg, pose).toContain('stop-color="#0B48BB"'); |
| 23 | // A still pose: never animated. |
| 24 | expect(svg, pose).not.toMatch(/<animate|@keyframes/); |
| 25 | } |
| 26 | expect(web("components/whale-pose.tsx")).toContain(POSES.map((p) => `"${p}"`).join(",\n ")); |
| 27 | }); |
| 28 | |
| 29 | it("uses the brand mark for the site icons, not a tile", () => { |
| 30 | const icon = web("app/icon.svg"); |
| 31 | expect(icon).toContain('stop-color="#1E8FD8"'); |
| 32 | expect(icon).not.toMatch(/<rect/); |
| 33 | expect(web("app/manifest.ts")).not.toContain("#142352"); |
| 34 | }); |
| 35 | |
| 36 | it("keeps the founder's 404 poster and the restored seals and water, and no retired illustrations", () => { |
| 37 | // The Codwhale poster is the 404 joke, restored on purpose (site merge plan R4). |
| 38 | expect(existsSync(new URL("../public/codwhale-404.webp", import.meta.url))).toBe(true); |
| 39 | expect(web("components/route-state.tsx")).toContain('src="/codwhale-404.webp"'); |
| 40 | // Seals stay. The blurred strata bands were retired on 2026-10-04 for |
| 41 | // paper above, sea below: one horizon and the tide ombré, no wave art. |
| 42 | expect(existsSync(new URL("../components/seal.tsx", import.meta.url))).toBe(true); |
| 43 | expect(existsSync(new URL("../components/strata.tsx", import.meta.url))).toBe(false); |
| 44 | expect(existsSync(new URL("../components/install-binary.tsx", import.meta.url))).toBe(false); |
| 45 | }); |
| 46 | }); |
| 47 | |
| 48 | describe("the horizon", () => { |
| 49 | it("ends every page on one horizon line, then the sea", () => { |
| 50 | const footer = web("components/footer.tsx"); |
| 51 | expect(footer).toContain('className="horizon"'); |
| 52 | expect(footer).not.toContain("Strata"); |
| 53 | expect(CSS).toMatch(/\.site-footer-sea \{[^}]*background: var\(--sea\)/); |
| 54 | // A page already ending in the sea (home) runs on without a second line. |
| 55 | expect(CSS).toMatch(/main:has\(\.sea-continues:last-child\) \+ \.site-footer \.site-footer-sea > \.horizon \{ display: none; \}/); |
| 56 | const home = web("app/[locale]/page.tsx"); |
| 57 | expect(home).toContain("sea-continues"); |
| 58 | // Paper above, sea below: the hero is paper and the terminal sits in the tide. |
| 59 | expect(home).toContain('className="home-terminal stage"'); |
| 60 | expect(CSS).toMatch(/\.home-terminal \{[^}]*background: var\(--tide\)/); |
| 61 | }); |
| 62 | |
| 63 | it("paints the stage with the dark set in both appearances", () => { |
| 64 | expect(CSS).toMatch(/\.stage,\s*\.site-footer\s*\{[^}]*--ring: var\(--gpui-dark-primary\);[^}]*color-scheme: dark;/); |
| 65 | }); |
| 66 | |
| 67 | it("keeps the horizon visible in forced colours", () => { |
| 68 | expect(CSS).toMatch(/@media \(forced-colors: active\) \{\s*\.horizon \{ background: CanvasText; \}/); |
| 69 | }); |
| 70 | }); |
| 71 | |
| 72 | describe("the header", () => { |
| 73 | it("keeps localized navigation controls inside compact viewports", () => { |
| 74 | expect(CSS).toMatch(/@media \(max-width: 520px\) \{\s*\.site-nav-inner\s*\{\s*gap:\s*0\.5rem;\s*\}\s*\.site-nav-actions\s*\{\s*min-width:\s*0;/); |
| 75 | expect(CSS).toMatch(/\.site-nav-actions\s*\{[\s\S]*?flex-shrink:\s*0/); |
| 76 | expect(CSS).toMatch(/\.site-nav-actions\s*>\s*\*\s*\{\s*flex-shrink:\s*0/); |
| 77 | expect(CSS).toMatch(/@media \(max-width: 900px\)[\s\S]*?\.site-github-link\s*\{\s*display:\s*none/); |
| 78 | // The locale <select> sits inside the fixed icon box instead of sizing |
| 79 | // the row, and the wordmark keeps a clickable floor. |
| 80 | expect(CSS).not.toMatch(/\.site-nav-actions select/); |
| 81 | expect(CSS).toMatch(/\.nav-locale select\s*\{[^}]*position:\s*absolute;[^}]*inset:\s*0;/); |
| 82 | expect(CSS).toMatch(/\.paper-wordmark\s*\{[^}]*flex:\s*0 1 auto;[^}]*min-width:\s*8\.75rem;/); |
| 83 | }); |
| 84 | |
| 85 | it("re-inks the wordmark through the mark ink, never a filter", () => { |
| 86 | expect(CSS).toMatch(/\.paper-wordmark-logo,\s*\.wordmark\s*\{[^}]*background: var\(--mark-ink\);[^}]*mask: url\("\/brand\/wordmark\.svg"\)/); |
| 87 | expect(CSS).not.toMatch(/paper-wordmark-logo\s*\{\s*filter:/); |
| 88 | }); |
| 89 | }); |
| 90 |