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
34 changes: 21 additions & 13 deletions apps/web/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
--surface-raised: #fbfbf8;
--ink: #121413;
--muted: #636964;
--faint: #8d938e;
--faint: #6f756f;
--line: #dfe2dd;
--line-strong: #c7cbc6;
--primary: #4d5ee8;
Expand All @@ -17,6 +17,13 @@
--danger: #c34444;
--code-bg: #111413;
--code-ink: #e7ece8;
--contrast-bg: #121413;
--contrast-ink: #f4f7f5;
--contrast-muted: #aeb5b0;
--contrast-line: #414743;
--primary-button-bg: #121413;
--primary-button-ink: #ffffff;
--primary-button-hover: #272b28;
--logo-cut: #ffffff;
--shadow-lg: 0 28px 80px rgba(26, 29, 27, 0.13), 0 2px 7px rgba(26, 29, 27, 0.08);
--shell: 1220px;
Expand All @@ -32,7 +39,7 @@ svg { display: block; }

.shell { width: min(var(--shell), calc(100% - 48px)); margin-inline: auto; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
.skip-link { position: fixed; left: 16px; top: 8px; z-index: 100; transform: translateY(-150%); border-radius: 8px; background: var(--ink); color: white; padding: 10px 14px; font-weight: 700; }
.skip-link { position: fixed; left: 16px; top: 8px; z-index: 100; transform: translateY(-150%); border-radius: 8px; background: var(--primary-button-bg); color: var(--primary-button-ink); padding: 10px 14px; font-weight: 700; }
.skip-link:focus { transform: translateY(0); }
:focus-visible { outline: 3px solid color-mix(in srgb, var(--primary) 70%, transparent); outline-offset: 3px; }

Expand All @@ -59,8 +66,8 @@ svg { display: block; }
.button { display: inline-flex; min-height: 44px; align-items: center; justify-content: center; gap: 10px; border: 1px solid transparent; border-radius: 9px; padding: 0 17px; font-size: 13px; font-weight: 680; transition: transform .18s ease, background .18s ease, border-color .18s ease; }
.button:hover { transform: translateY(-1px); }
.button svg, .text-link svg { width: 16px; }
.button.primary { background: var(--ink); color: white; box-shadow: 0 7px 18px rgba(20, 23, 21, .16); }
.button.primary:hover { background: #272b28; }
.button.primary { background: var(--primary-button-bg); color: var(--primary-button-ink); box-shadow: 0 7px 18px rgba(20, 23, 21, .16); }
.button.primary:hover { background: var(--primary-button-hover); }
.button.secondary { border-color: var(--line-strong); background: var(--surface); }
.button.secondary:hover { border-color: #aab0aa; }
.hero-proof { margin: 42px 0 0; border-top: 1px solid var(--line); }
Expand Down Expand Up @@ -104,7 +111,7 @@ svg { display: block; }
.diff-row > span:first-child { color: #8f9791; }
.diff-row del { overflow: hidden; color: #f18b8b; text-overflow: ellipsis; white-space: nowrap; }
.diff-row ins { overflow: hidden; color: #73e6b1; text-decoration: none; text-overflow: ellipsis; white-space: nowrap; }
.diff-arrow { color: #5e665f; }
.diff-arrow { color: #929a94; }
.warning-row { display: flex; gap: 9px; align-items: flex-start; margin-top: 13px; border: 1px solid #524328; border-radius: 7px; background: #292416; color: #e4bf73; padding: 10px; }
.warning-row > span { display: grid; width: 14px; height: 14px; flex: none; place-items: center; border: 1px solid #82662e; border-radius: 50%; font-size: 8px; }
.warning-row p { margin: 0; font-size: 8px; line-height: 1.5; }
Expand Down Expand Up @@ -161,8 +168,8 @@ svg { display: block; }
.feature-grid article > svg { width: 23px; color: var(--primary); }
.feature-grid h3 { margin: 70px 0 10px; font-size: 16px; }
.feature-grid p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.65; }
.feature-grid .feature-large { grid-column: span 2; background: var(--ink); color: white; }
.feature-large p { max-width: 520px; color: #aeb5b0; }
.feature-grid .feature-large { grid-column: span 2; border-color: var(--contrast-line); background: var(--contrast-bg); color: var(--contrast-ink); }
.feature-large p { max-width: 520px; color: var(--contrast-muted); }
.feature-large .mini-code { margin-top: 24px; border-color: #373c39; background: #1d211f; color: #aeb5b0; }
.mini-code span { color: #b9a7ff; }.mini-code strong { color: #f08e8e; font-weight: 500; }

Expand Down Expand Up @@ -192,13 +199,15 @@ tbody th { font-weight: 570; }
.faq-list details[open] summary::after { content: "−"; }
.faq-list p { max-width: 620px; margin: -5px 0 24px; color: var(--muted); font-size: 13px; line-height: 1.7; }

.final-cta { overflow: hidden; background: var(--ink); color: white; }
.final-cta { overflow: hidden; background: var(--contrast-bg); color: var(--contrast-ink); }
.final-cta-inner { display: flex; min-height: 350px; align-items: center; justify-content: space-between; gap: 60px; }
.final-cta .kicker { color: #8290ff; }
.final-cta h2 { margin: 12px 0 14px; font-size: clamp(42px, 5vw, 68px); letter-spacing: -.055em; }
.final-cta p:not(.kicker) { margin: 0; color: #a7aea9; }
.button.inverted { background: white; color: var(--ink); }
.button.ghost-dark { border-color: #414743; color: white; }
.final-cta p:not(.kicker) { margin: 0; color: var(--contrast-muted); }
.button.inverted { background: #f4f7f5; color: #111412; }
.button.inverted:hover { background: #e1e7e3; }
.button.ghost-dark { border-color: var(--contrast-line); color: var(--contrast-ink); }
.button.ghost-dark:hover { border-color: #6c756f; background: rgba(255, 255, 255, .04); }
.final-cta .hero-actions { flex: none; margin: 0; }

.site-footer { border-top: 1px solid var(--line); background: var(--surface); }
Expand Down Expand Up @@ -384,10 +393,9 @@ tbody th { font-weight: 570; }

@media (prefers-color-scheme: dark) {
:root {
--bg: #0b0d0c; --surface: #111412; --surface-raised: #151816; --ink: #edf1ee; --muted: #9aa19b; --faint: #707771; --line: #282d29; --line-strong: #3b413c; --primary: #8490ff; --primary-dark: #a3acff; --primary-soft: #1c2144; --success-soft: #143429; --warning-soft: #342815; --logo-cut: #0b0d0c;
--bg: #0b0d0c; --surface: #111412; --surface-raised: #151816; --ink: #edf1ee; --muted: #9aa19b; --faint: #7b837d; --line: #282d29; --line-strong: #3b413c; --primary: #8490ff; --primary-dark: #a3acff; --primary-soft: #1c2144; --success-soft: #143429; --warning-soft: #342815; --contrast-bg: #171b18; --contrast-ink: #f4f7f5; --contrast-muted: #b4bcb6; --contrast-line: #3b433e; --primary-button-bg: #eff3f0; --primary-button-ink: #111412; --primary-button-hover: #dfe6e1; --logo-cut: #0b0d0c;
}
.install-pill { box-shadow: none; color: #c7cdc8; }
.button.primary { background: #eff3f0; color: #111412; }
.button.secondary { background: var(--surface); }
.provider-list .github { background: #edf1ee; }
.workflow-section { background-image: linear-gradient(#202420 1px, transparent 1px), linear-gradient(90deg, #202420 1px, transparent 1px); }
Expand Down
39 changes: 39 additions & 0 deletions apps/web/tests/site.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ import test from "node:test";

const read = (path) => readFile(new URL(`../${path}`, import.meta.url), "utf8");

const cssVariables = (block) => Object.fromEntries(
[...block.matchAll(/--([\w-]+):\s*([^;]+);/g)].map(([, name, value]) => [name, value.trim()]),
);

const contrastRatio = (foreground, background) => {
const luminance = (hex) => {
const channels = hex.match(/[a-f\d]{2}/gi).map((channel) => {
const value = Number.parseInt(channel, 16) / 255;
return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
});
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
};
const first = luminance(foreground);
const second = luminance(background);
return (Math.max(first, second) + 0.05) / (Math.min(first, second) + 0.05);
};

test("homepage states the category and safe-write lifecycle", async () => {
const page = await read("app/page.tsx");
assert.match(page, /One work SDK for/);
Expand Down Expand Up @@ -72,6 +89,28 @@ test("interactive controls expose accessible state", async () => {
assert.match(workbench, /aria-pressed/);
});

test("contrast surfaces remain readable in light and dark color schemes", async () => {
const css = await read("app/globals.css");
const lightBlock = css.match(/^:root\s*\{([\s\S]*?)\}/m)?.[1];
const darkBlock = css.match(/@media \(prefers-color-scheme: dark\)[\s\S]*?:root\s*\{([\s\S]*?)\}/)?.[1];
assert.ok(lightBlock);
assert.ok(darkBlock);

const light = cssVariables(lightBlock);
const dark = { ...light, ...cssVariables(darkBlock) };
for (const palette of [light, dark]) {
assert.ok(contrastRatio(palette["contrast-ink"], palette["contrast-bg"]) >= 4.5);
assert.ok(contrastRatio(palette["contrast-muted"], palette["contrast-bg"]) >= 4.5);
assert.ok(contrastRatio(palette["primary-button-ink"], palette["primary-button-bg"]) >= 4.5);
assert.ok(contrastRatio(palette.faint, palette.surface) >= 4.5);
}

assert.match(css, /\.feature-grid \.feature-large \{[^}]*background: var\(--contrast-bg\)/);
assert.match(css, /\.final-cta \{[^}]*background: var\(--contrast-bg\)/);
assert.match(css, /\.diff-arrow \{ color: #929a94; \}/);
assert.doesNotMatch(css, /\.final-cta \{[^}]*background: var\(--ink\)/);
});

test("company marks come from locally cached SVGL assets", async () => {
const logo = await read("components/brand-logo.tsx");
const sources = await read("public/brands/SOURCES.md");
Expand Down
Loading