diff --git a/.github/assets/hero-dark.png b/.github/assets/hero-dark.png
new file mode 100644
index 00000000..871b356c
Binary files /dev/null and b/.github/assets/hero-dark.png differ
diff --git a/.github/assets/hero-light.png b/.github/assets/hero-light.png
new file mode 100644
index 00000000..e4c4bb2b
Binary files /dev/null and b/.github/assets/hero-light.png differ
diff --git a/README.md b/README.md
index 2bb1efdf..1c2ff7fb 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,18 @@
+
+
+ Docs · + DESIGN.md · + shadcn registry · + npm +
+ # Locus Design -Design that keeps humans in the loop. A design system for agent products: React components +A design system for agent products: React components on Radix, product blocks, Loop (agent UI with the human in the loop), brand assets, and one theme with light and dark modes. Icons are Phosphor (`@phosphor-icons/react`), bold weight. diff --git a/apps/design/package.json b/apps/design/package.json index 35681bad..4b9fd74d 100644 --- a/apps/design/package.json +++ b/apps/design/package.json @@ -9,7 +9,8 @@ "typecheck": "tsc -p tsconfig.json --noEmit", "test": "vitest run", "deploy": "pnpm registry && cf deploy", - "registry": "node ../../scripts/registry/build.mjs" + "registry": "node ../../scripts/registry/build.mjs", + "readme:hero": "node scripts/readme-hero.ts" }, "dependencies": { "@json-render/core": "catalog:", diff --git a/apps/design/plugins/raster.ts b/apps/design/plugins/raster.ts index f3808161..492a2add 100644 --- a/apps/design/plugins/raster.ts +++ b/apps/design/plugins/raster.ts @@ -36,9 +36,13 @@ export function svgToPng(svg: string, width: number) { return new Resvg(svg, { fitTo: { mode: "width", value: width } }).render().asPng(); } -export async function elementToPng(element: ReactNode, size: { width: number; height: number }) { +export async function elementToPng( + element: ReactNode, + size: { width: number; height: number }, + outputWidth = size.width +) { const svg = await satori(element, { ...size, fonts: loadFonts() }); - return svgToPng(svg, size.width); + return svgToPng(svg, outputWidth); } const icoHeaderBytes = 6; diff --git a/apps/design/scripts/readme-hero.ts b/apps/design/scripts/readme-hero.ts new file mode 100644 index 00000000..23a9c2ac --- /dev/null +++ b/apps/design/scripts/readme-hero.ts @@ -0,0 +1,24 @@ +import { writeFileSync } from "node:fs"; +import { createElement } from "react"; +import { elementToPng } from "../plugins/raster.ts"; +import { withStandaloneServer } from "../plugins/standalone-server.ts"; + +const heroModule = "/src/seo/build/readme-hero.tsx"; +const outputDir = new URL("../../../.github/assets/", import.meta.url); +const retinaScale = 2; + +type HeroModule = typeof import("../src/seo/build/readme-hero.tsx"); + +await withStandaloneServer(async (server) => { + const { ReadmeHero, readmeHeroSize } = (await server.ssrLoadModule(heroModule)) as HeroModule; + for (const theme of ["dark", "light"] as const) { + const png = await elementToPng( + createElement(ReadmeHero, { theme }), + readmeHeroSize, + readmeHeroSize.width * retinaScale + ); + const file = new URL(`hero-${theme}.png`, outputDir); + writeFileSync(file, png); + console.log(`readme hero: ${file.pathname}`); + } +}); diff --git a/apps/design/src/seo/build/readme-hero.tsx b/apps/design/src/seo/build/readme-hero.tsx new file mode 100644 index 00000000..92b41e92 --- /dev/null +++ b/apps/design/src/seo/build/readme-hero.tsx @@ -0,0 +1,195 @@ +import { darkColors, lightColors, type Palette } from "@locusgraph/tokens"; +import type * as React from "react"; + +export type HeroTheme = "dark" | "light"; + +export const readmeHeroSize = { width: 1280, height: 560 } as const; + +const fonts = { + heading: "Archivo", + body: "IBM Plex Sans", + mono: "IBM Plex Mono", +}; + +const palettes: Record