diff --git a/apps/site/src/components/magicui/border-beam.tsx b/apps/site/src/components/magicui/border-beam.tsx index 7586d0d3..2d35e379 100644 --- a/apps/site/src/components/magicui/border-beam.tsx +++ b/apps/site/src/components/magicui/border-beam.tsx @@ -15,8 +15,8 @@ export const BorderBeam = ({ size = 200, duration = 15, borderWidth = 1.5, - colorFrom = "#70c9b7", // edgeever brand - colorTo = "#3fb2a1", // edgeever brand + colorFrom = "var(--edgeever-decor-beam-from)", + colorTo = "var(--edgeever-decor-beam-to)", delay = 0, }: BorderBeamProps) => { return ( diff --git a/apps/site/src/components/magicui/magic-card.tsx b/apps/site/src/components/magicui/magic-card.tsx index 42fe2f25..c24bf6c6 100644 --- a/apps/site/src/components/magicui/magic-card.tsx +++ b/apps/site/src/components/magicui/magic-card.tsx @@ -12,7 +12,7 @@ export function MagicCard({ children, className, gradientSize = 200, - gradientColor = "#3fb2a1", // edgeever brand + gradientColor = "var(--edgeever-decor-beam-to)", gradientOpacity = 0.8, }: MagicCardProps) { const mouseX = useMotionValue(-gradientSize); diff --git a/apps/site/src/styles/brand-gamut.test.mjs b/apps/site/src/styles/brand-gamut.test.mjs new file mode 100644 index 00000000..a216c09c --- /dev/null +++ b/apps/site/src/styles/brand-gamut.test.mjs @@ -0,0 +1,19 @@ +import { describe, expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; + +const brand = readFileSync(new URL("./brand.css", import.meta.url), "utf8"); +const globalCss = readFileSync(new URL("./global.css", import.meta.url), "utf8"); + +describe("site decorative wide gamut", () => { + test("keeps the celadon brand in sRGB and limits oklch to decorative tokens", () => { + expect(brand).toContain("--edgeever-celadon-600: #2b968b;"); + expect(brand).toContain("--edgeever-brand: var(--edgeever-celadon-600);"); + expect(brand).toContain("--edgeever-decor-gradient: linear-gradient(135deg, var(--edgeever-decor-from) 0%, var(--edgeever-decor-to) 100%);"); + expect(brand).toContain("@media (color-gamut: p3)"); + expect(brand).toContain("--edgeever-decor-to: oklch(0.611 0.129 185);"); + expect(brand).not.toContain("--edgeever-brand: oklch("); + expect(globalCss).toContain("background-image: var(--edgeever-decor-gradient);"); + expect(globalCss).toContain("box-shadow: 0 20px 40px -15px var(--edgeever-decor-shadow);"); + expect(globalCss).toContain("box-shadow: 0 0 50px -12px var(--edgeever-decor-glow);"); + }); +}); diff --git a/apps/site/src/styles/brand.css b/apps/site/src/styles/brand.css index 6389b4a9..6cd13b8b 100644 --- a/apps/site/src/styles/brand.css +++ b/apps/site/src/styles/brand.css @@ -24,4 +24,26 @@ --edgeever-brand-logo-mark: var(--edgeever-celadon-700); --edgeever-brand-logo-accent: var(--edgeever-celadon-400); --edgeever-brand-surface: #f8fafc; + + /* Decorative color only. Buttons, links, and the logo keep the hex brand tokens. */ + --edgeever-decor-from: var(--edgeever-celadon-800); + --edgeever-decor-to: var(--edgeever-celadon-600); + --edgeever-decor-gradient: linear-gradient(135deg, var(--edgeever-decor-from) 0%, var(--edgeever-decor-to) 100%); + --edgeever-decor-beam-from: var(--edgeever-celadon-400); + --edgeever-decor-beam-to: var(--edgeever-celadon-500); + --edgeever-decor-glow: rgb(var(--edgeever-brand-rgb) / 0.25); + --edgeever-decor-shadow: rgb(var(--edgeever-brand-rgb) / 0.15); +} + +@media (color-gamut: p3) { + :root { + /* Same lightness and hue as the celadon hex stops, with chroma past sRGB. */ + --edgeever-decor-from: oklch(0.453 0.096 188.1); + --edgeever-decor-to: oklch(0.611 0.129 185); + --edgeever-decor-gradient: linear-gradient(in oklch 135deg, var(--edgeever-decor-from) 0%, var(--edgeever-decor-to) 100%); + --edgeever-decor-beam-from: oklch(0.775 0.169 179.2); + --edgeever-decor-beam-to: oklch(0.695 0.149 181.5); + --edgeever-decor-glow: oklch(0.611 0.129 185 / 0.25); + --edgeever-decor-shadow: oklch(0.611 0.129 185 / 0.15); + } } diff --git a/apps/site/src/styles/global.css b/apps/site/src/styles/global.css index 0588190c..43eefee7 100644 --- a/apps/site/src/styles/global.css +++ b/apps/site/src/styles/global.css @@ -18,7 +18,7 @@ } @utility glow-brand { - box-shadow: 0 0 50px -12px rgb(var(--edgeever-brand-rgb) / 0.25); + box-shadow: 0 0 50px -12px var(--edgeever-decor-glow); } @utility glow-card { @@ -32,7 +32,7 @@ transition: all 0.4s cubic-bezier(0.25, 1, 0.5, 1); &:hover { transform: translateY(-6px); - box-shadow: 0 20px 40px -15px rgb(var(--edgeever-brand-rgb) / 0.15); + box-shadow: 0 20px 40px -15px var(--edgeever-decor-shadow); border-color: var(--color-brand-border); } } @@ -48,7 +48,7 @@ background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; - background-image: linear-gradient(135deg, var(--color-brand-text) 0%, var(--color-brand) 100%); + background-image: var(--edgeever-decor-gradient); } @utility animate-slow-spin { diff --git a/apps/web/src/lib/tailwind-theme.test.mjs b/apps/web/src/lib/tailwind-theme.test.mjs index c476eda5..419c1e4c 100644 --- a/apps/web/src/lib/tailwind-theme.test.mjs +++ b/apps/web/src/lib/tailwind-theme.test.mjs @@ -13,7 +13,12 @@ describe("tailwind theme tokens", () => { expect(css).toContain("--color-slate-950: rgb(var(--slate-950-rgb) / 1)"); expect(css).toContain("--color-emerald-500: rgb(var(--brand-green-500-rgb) / 1)"); expect(css).toContain("--color-amber-50: rgb(var(--amber-50-rgb) / 1)"); + expect(css).toContain("--color-amber-300: rgb(var(--amber-300-rgb) / 1)"); + expect(css).toContain("--color-amber-400: rgb(var(--amber-400-rgb) / 1)"); + expect(css).toContain("--color-amber-500: rgb(var(--amber-500-rgb) / 1)"); expect(css).toContain("--color-rose-50: rgb(var(--rose-50-rgb) / 1)"); + expect(css).toContain("--color-rose-400: rgb(var(--rose-400-rgb) / 1)"); + expect(css).toContain("--color-red-100: rgb(var(--rose-100-rgb) / 1)"); expect(css).toContain("--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05)"); expect(css).toContain("--blur-sm: 4px"); expect(css).toContain(".focus-visible\\:outline-none:focus-visible"); diff --git a/apps/web/src/lib/ui-palette.test.mjs b/apps/web/src/lib/ui-palette.test.mjs index f90d5ef1..77b0c4f1 100644 --- a/apps/web/src/lib/ui-palette.test.mjs +++ b/apps/web/src/lib/ui-palette.test.mjs @@ -26,7 +26,11 @@ describe("application color system", () => { expect(globals).toContain("--slate-500-rgb: 115 115 115;"); expect(globals).toContain("--slate-950-rgb: 10 10 10;"); expect(globals).toContain("--amber-50-rgb: 255 251 235;"); + expect(globals).toContain("--amber-300-rgb: 252 211 77;"); + expect(globals).toContain("--amber-400-rgb: 251 191 36;"); + expect(globals).toContain("--amber-500-rgb: 245 158 11;"); expect(globals).toContain("--rose-50-rgb: 255 241 242;"); + expect(globals).toContain("--rose-400-rgb: 251 113 133;"); expect(globals).not.toContain("--slate-500-rgb: 100 116 139;"); expect(contrastRatio("#222222", "#ffffff")).toBeGreaterThanOrEqual(4.5); expect(contrastRatio("#737373", "#ffffff")).toBeGreaterThanOrEqual(4.5); @@ -37,6 +41,7 @@ describe("application color system", () => { expect(globals).toContain("--workspace-sidebar: #121612;"); expect(globals).toContain("--workspace-memo-list: #151a17;"); expect(globals).toContain("--workspace-editor: #191e1b;"); + expect(globals).toContain("--amber-300-rgb: 180 83 9;"); expect(globals).not.toContain("scrollbar-color: rgba(100, 116, 139, 0.18)"); expect(contrastRatio("#cad4ce", "#191e1b")).toBeGreaterThanOrEqual(4.5); expect(contrastRatio("#9aa9a0", "#191e1b")).toBeGreaterThanOrEqual(4.5); diff --git a/apps/web/src/styles/globals.css b/apps/web/src/styles/globals.css index 7b6d102d..823b773c 100644 --- a/apps/web/src/styles/globals.css +++ b/apps/web/src/styles/globals.css @@ -67,6 +67,9 @@ --color-amber-50: rgb(var(--amber-50-rgb) / 1); --color-amber-100: rgb(var(--amber-100-rgb) / 1); --color-amber-200: rgb(var(--amber-200-rgb) / 1); + --color-amber-300: rgb(var(--amber-300-rgb) / 1); + --color-amber-400: rgb(var(--amber-400-rgb) / 1); + --color-amber-500: rgb(var(--amber-500-rgb) / 1); --color-amber-600: rgb(var(--amber-600-rgb) / 1); --color-amber-700: rgb(var(--amber-700-rgb) / 1); --color-amber-800: rgb(var(--amber-800-rgb) / 1); @@ -75,12 +78,14 @@ --color-rose-50: rgb(var(--rose-50-rgb) / 1); --color-rose-100: rgb(var(--rose-100-rgb) / 1); --color-rose-200: rgb(var(--rose-200-rgb) / 1); + --color-rose-400: rgb(var(--rose-400-rgb) / 1); --color-rose-500: rgb(var(--rose-500-rgb) / 1); --color-rose-600: rgb(var(--rose-600-rgb) / 1); --color-rose-700: rgb(var(--rose-700-rgb) / 1); --color-rose-800: rgb(var(--rose-800-rgb) / 1); --color-rose-950: rgb(var(--rose-950-rgb) / 1); --color-red-50: rgb(var(--rose-50-rgb) / 1); + --color-red-100: rgb(var(--rose-100-rgb) / 1); --color-red-600: rgb(var(--rose-600-rgb) / 1); --color-red-700: rgb(var(--rose-700-rgb) / 1); --color-blue-600: rgb(var(--blue-600-rgb) / 1); @@ -308,6 +313,9 @@ html.dark[data-edgeever-environment="demo"] body::after { --amber-50-rgb: 255 251 235; --amber-100-rgb: 254 243 199; --amber-200-rgb: 253 230 138; + --amber-300-rgb: 252 211 77; + --amber-400-rgb: 251 191 36; + --amber-500-rgb: 245 158 11; --amber-600-rgb: 217 119 6; --amber-700-rgb: 180 83 9; --amber-800-rgb: 146 64 14; @@ -316,6 +324,7 @@ html.dark[data-edgeever-environment="demo"] body::after { --rose-50-rgb: 255 241 242; --rose-100-rgb: 255 228 230; --rose-200-rgb: 254 205 211; + --rose-400-rgb: 251 113 133; --rose-500-rgb: 244 63 94; --rose-600-rgb: 225 29 72; --rose-700-rgb: 190 18 60; @@ -418,6 +427,7 @@ html.dark[data-edgeever-environment="demo"] body::after { --amber-50-rgb: 69 26 3; --amber-100-rgb: 80 30 5; --amber-200-rgb: 146 64 14; + --amber-300-rgb: 180 83 9; --amber-600-rgb: 253 230 138; --amber-700-rgb: 253 230 138; --amber-800-rgb: 252 211 77;