diff --git a/docs/api-reference/types.mdx b/docs/api-reference/types.mdx deleted file mode 100644 index fb932f7..0000000 --- a/docs/api-reference/types.mdx +++ /dev/null @@ -1,362 +0,0 @@ ---- -title: Types -description: All TypeScript types exported by @sanity-labs/logo-soup. ---- - -All types are exported from the root `@sanity-labs/logo-soup` package. Framework-specific types are exported from their respective subpaths. - -```ts -import type { - AlignmentMode, - BackgroundColor, - BoundingBox, - LogoSoupEngine, - LogoSoupState, - LogoSource, - MeasurementResult, - NormalizedLogo, - ProcessOptions, - VisualCenter, -} from "@sanity-labs/logo-soup"; -``` - -## Core Types - -### `LogoSource` - -Represents a logo input. Can be a plain URL string or an object with `src` and optional `alt`. - -```ts -type LogoSource = { - src: string; - alt?: string; -}; -``` - -When a plain string is passed in the `logos` array, it's internally normalized to `{ src: string, alt: "" }`. - -### `NormalizedLogo` - -The result of processing a single logo. Contains the original image info, detected content bounds, and computed display dimensions. - -```ts -type NormalizedLogo = { - src: string; - alt: string; - originalWidth: number; - originalHeight: number; - contentBox?: BoundingBox; - normalizedWidth: number; - normalizedHeight: number; - aspectRatio: number; - pixelDensity?: number; - visualCenter?: VisualCenter; - croppedSrc?: string; -}; -``` - -| Field | Description | -| --- | --- | -| `src` | Original image URL | -| `alt` | Alt text (empty string if not provided) | -| `originalWidth` | Natural width of the source image in pixels | -| `originalHeight` | Natural height of the source image in pixels | -| `contentBox` | Tight bounding rectangle around detected content pixels. Absent if content detection wasn't performed. | -| `normalizedWidth` | Computed display width after normalization | -| `normalizedHeight` | Computed display height after normalization | -| `aspectRatio` | Content aspect ratio (`contentWidth / contentHeight`) | -| `pixelDensity` | Visual weight measurement (0โ€“1). Present when `densityAware` is enabled. | -| `visualCenter` | Visual weight center with offset from geometric center. Used by `getVisualCenterTransform`. | -| `croppedSrc` | Blob URL of the cropped image. Present when `cropToContent` is enabled and the logo has a `contentBox`. | - -### `BoundingBox` - -A rectangle within an image, in source image pixels. - -```ts -type BoundingBox = { - x: number; - y: number; - width: number; - height: number; -}; -``` - -### `VisualCenter` - -The visual weight center of a logo, computed during content detection. - -```ts -type VisualCenter = { - x: number; - y: number; - offsetX: number; - offsetY: number; -}; -``` - -| Field | Description | -| --- | --- | -| `x` | Absolute X position of the visual center in source image pixels | -| `y` | Absolute Y position of the visual center in source image pixels | -| `offsetX` | Horizontal displacement from the geometric center of the content box (positive = right of center) | -| `offsetY` | Vertical displacement from the geometric center of the content box (positive = below center) | - -The `offsetX` and `offsetY` values are what `getVisualCenterTransform` uses to compute the CSS `translate()` correction. - -### `AlignmentMode` - -Controls which axes `getVisualCenterTransform` compensates for. - -```ts -type AlignmentMode = - | "bounds" - | "visual-center" - | "visual-center-x" - | "visual-center-y"; -``` - -| Mode | Description | -| --- | --- | -| `"bounds"` | No visual center compensation. Align by geometric bounding box center. | -| `"visual-center"` | Compensate on both X and Y axes. | -| `"visual-center-x"` | Compensate horizontally only. | -| `"visual-center-y"` | Compensate vertically only. Default for most use cases. | - -### `BackgroundColor` - -The background color for contrast detection and irradiation compensation. - -```ts -type BackgroundColor = CSSColor | [number, number, number]; -``` - -Where `CSSColor` accepts hex strings (`"#1a1a1a"`), `rgb()`/`rgba()` functions, `hsl()`/`hsla()` functions, or any CSS color string. The `[number, number, number]` tuple form accepts raw RGB values (0โ€“255). - -### `MeasurementResult` - -Raw measurement data produced by the content detection engine. This is an internal type that you typically don't interact with directly, but it's exported for advanced use cases. - -```ts -type MeasurementResult = { - width: number; - height: number; - contentBox?: BoundingBox; - pixelDensity?: number; - visualCenter?: VisualCenter; - backgroundLuminance?: number; -}; -``` - -| Field | Description | -| --- | --- | -| `width` | Natural width of the source image | -| `height` | Natural height of the source image | -| `contentBox` | Detected content bounds | -| `pixelDensity` | Visual weight (0โ€“1), present when `densityAware` is enabled | -| `visualCenter` | Visual weight center | -| `backgroundLuminance` | Background brightness (0โ€“1), present for opaque images. Used for irradiation compensation. | - -## Engine Types - -### `LogoSoupEngine` - -The interface returned by `createLogoSoup()`. - -```ts -type LogoSoupEngine = { - process(options: ProcessOptions): void; - subscribe(listener: () => void): () => void; - getSnapshot(): LogoSoupState; - destroy(): void; -}; -``` - -See [createLogoSoup](/api-reference/create-logo-soup) for detailed method documentation. - -### `LogoSoupState` - -Immutable state snapshot returned by `engine.getSnapshot()`. - -```ts -type LogoSoupState = { - status: "idle" | "loading" | "ready" | "error"; - normalizedLogos: NormalizedLogo[]; - error: Error | null; -}; -``` - -### `ProcessOptions` - -Options passed to `engine.process()`. All fields except `logos` are optional and have sensible defaults. - -```ts -type ProcessOptions = { - logos: (string | LogoSource)[]; - baseSize?: number; - scaleFactor?: number; - contrastThreshold?: number; - densityAware?: boolean; - densityFactor?: number; - cropToContent?: boolean; - backgroundColor?: BackgroundColor; -}; -``` - -See [Options Reference](/options) for detailed descriptions and default values. - -## React Types - -Exported from `@sanity-labs/logo-soup/react`: - -```ts -import type { - ImageRenderProps, - LogoSoupProps, - RenderImageFn, - UseLogoSoupOptions, - UseLogoSoupResult, -} from "@sanity-labs/logo-soup/react"; -``` - -### `UseLogoSoupOptions` - -Options accepted by the React `useLogoSoup` hook. Same fields as `ProcessOptions`. - -```ts -type UseLogoSoupOptions = { - logos: (string | LogoSource)[]; - baseSize?: number; - scaleFactor?: number; - contrastThreshold?: number; - densityAware?: boolean; - densityFactor?: number; - cropToContent?: boolean; - backgroundColor?: BackgroundColor; -}; -``` - -### `UseLogoSoupResult` - -Return value of the React `useLogoSoup` hook. - -```ts -type UseLogoSoupResult = { - isLoading: boolean; - isReady: boolean; - normalizedLogos: NormalizedLogo[]; - error: Error | null; -}; -``` - -### `LogoSoupProps` - -Props accepted by the React `` component. Extends `UseLogoSoupOptions` with layout and rendering options. - -```ts -type LogoSoupProps = { - logos: (string | LogoSource)[]; - baseSize?: number; - scaleFactor?: number; - contrastThreshold?: number; - densityAware?: boolean; - densityFactor?: number; - cropToContent?: boolean; - backgroundColor?: BackgroundColor; - alignBy?: AlignmentMode; - gap?: number | string; - renderImage?: RenderImageFn; - className?: string; - style?: CSSProperties; - onNormalized?: (logos: NormalizedLogo[]) => void; -}; -``` - -### `ImageRenderProps` - -Props passed to the `renderImage` callback. Extends `ImgHTMLAttributes` with required fields. - -```ts -type ImageRenderProps = ImgHTMLAttributes & { - src: string; - alt: string; - width: number; - height: number; - style?: CSSProperties; -}; -``` - -### `RenderImageFn` - -The type of the `renderImage` prop. - -```ts -type RenderImageFn = (props: ImageRenderProps) => ReactNode; -``` - -## Vue Types - -Exported from `@sanity-labs/logo-soup/vue`: - -```ts -import type { - UseLogoSoupOptions, - UseLogoSoupReturn, -} from "@sanity-labs/logo-soup/vue"; -``` - -### `UseLogoSoupOptions` (Vue) - -Options accepted by the Vue `useLogoSoup` composable. Each field accepts a plain value, a `Ref`, or a getter function (`MaybeRefOrGetter`). - -```ts -type UseLogoSoupOptions = { - logos: MaybeRefOrGetter<(string | LogoSource)[]>; - baseSize?: MaybeRefOrGetter; - scaleFactor?: MaybeRefOrGetter; - contrastThreshold?: MaybeRefOrGetter; - densityAware?: MaybeRefOrGetter; - densityFactor?: MaybeRefOrGetter; - cropToContent?: MaybeRefOrGetter; - backgroundColor?: MaybeRefOrGetter; -}; -``` - -### `UseLogoSoupReturn` - -Return value of the Vue `useLogoSoup` composable. - -```ts -type UseLogoSoupReturn = { - state: ShallowRef; - isLoading: ComputedRef; - isReady: ComputedRef; - normalizedLogos: ComputedRef; - error: ComputedRef; -}; -``` - -## Solid Types - -Exported from `@sanity-labs/logo-soup/solid`: - -```ts -import type { UseLogoSoupResult } from "@sanity-labs/logo-soup/solid"; -``` - -### `UseLogoSoupResult` (Solid) - -Return value of the Solid `useLogoSoup` primitive. Properties are reactive getters. - -```ts -type UseLogoSoupResult = { - readonly isLoading: boolean; - readonly isReady: boolean; - readonly normalizedLogos: NormalizedLogo[]; - readonly error: Error | null; -}; -``` - - - The Solid `useLogoSoup` accepts a getter function `() => ProcessOptions` as its argument, not a plain options object. This is the standard Solid pattern for reactive props. - diff --git a/docs/docs.json b/docs/docs.json new file mode 100644 index 0000000..15cee81 --- /dev/null +++ b/docs/docs.json @@ -0,0 +1,72 @@ +{ + "$schema": "https://mintlify.com/docs.json", + "theme": "mint", + "name": "Logo Soup", + "logo": { + "light": "/logo/light.svg", + "dark": "/logo/dark.svg", + "href": "https://github.com/sanity-labs/logo-soup" + }, + "favicon": "/favicon.svg", + "colors": { + "primary": "#f36458", + "light": "#ff8a7a", + "dark": "#d44a3e" + }, + "navbar": { + "links": [ + { + "label": "GitHub", + "href": "https://github.com/sanity-labs/logo-soup" + } + ], + "primary": { + "type": "button", + "label": "npm", + "href": "https://www.npmjs.com/package/@sanity-labs/logo-soup" + } + }, + "navigation": { + "dropdowns": [ + { + "dropdown": "Documentation", + "icon": "book", + "pages": [ + { + "group": "Get Started", + "pages": ["introduction", "quickstart"] + }, + { + "group": "Frameworks", + "pages": [ + "frameworks/react", + "frameworks/vue", + "frameworks/svelte", + "frameworks/solid", + "frameworks/angular", + "frameworks/jquery", + "frameworks/vanilla", + "frameworks/custom" + ] + }, + { + "group": "Guides", + "pages": ["options", "how-it-works"] + }, + { + "group": "Reference", + "pages": [ + "api-reference/create-logo-soup", + "api-reference/get-visual-center-transform" + ] + } + ] + } + ] + }, + "footer": { + "socials": { + "github": "https://github.com/sanity-labs/logo-soup" + } + } +} diff --git a/docs/favicon.svg b/docs/favicon.svg new file mode 100644 index 0000000..ca5f80a --- /dev/null +++ b/docs/favicon.svg @@ -0,0 +1,3 @@ + + ๐Ÿœ + diff --git a/docs/images/after.png b/docs/images/after.png new file mode 100644 index 0000000..92c5418 Binary files /dev/null and b/docs/images/after.png differ diff --git a/docs/images/before.png b/docs/images/before.png new file mode 100644 index 0000000..3c9d231 Binary files /dev/null and b/docs/images/before.png differ diff --git a/docs/introduction.mdx b/docs/introduction.mdx index 4c503b9..a37b5b3 100644 --- a/docs/introduction.mdx +++ b/docs/introduction.mdx @@ -3,19 +3,14 @@ title: Introduction description: A tiny framework-agnostic library that makes logos look good together. --- -# ๐Ÿœ Logo Soup - -Real-world logos are messy. Some have padding, some don't. Some are dense and blocky, others are thin and airy. Put them in a row and they look chaotic. +import { LogoSoupDemo } from "/snippets/logo-soup-demo.jsx"; +import { FrameworkGrid } from "/snippets/framework-grid.jsx"; - - Logos without normalization โ€” different sizes, weights, and aspect ratios create visual chaos - +# ๐Ÿœ Logo Soup -Logo Soup fixes this automatically. +Real-world logos are messy. Some have padding, some don't. Some are dense and blocky, others are thin and airy. Put them in a row and they look chaotic. Logo Soup fixes this automatically. - - After normalization โ€” the same logos appear balanced and harmonious - + ## What it does @@ -26,30 +21,28 @@ Logo Soup analyzes each logo image on a `` and normalizes them so they a 3. **Density Compensation** โ€” Measures visual weight so dense/bold logos don't overpower light/thin ones 4. **Irradiation Compensation** โ€” Adjusts for the optical illusion where light content on dark backgrounds appears larger + + + The full story behind the problem and the math behind the solution. + + + Interactive Storybook with real logos and tunable parameters. + + + ## Framework support Logo Soup is a single npm package with subpath exports for every major framework: - - - Component + hook - - - Composable - - - Runes-compatible store - - - Reactive primitive - - - Injectable service - - - Core engine directly - - + ## Architecture @@ -64,8 +57,4 @@ The library is built around a framework-agnostic core engine (`createLogoSoup`) @sanity-labs/logo-soup/angular โ†’ LogoSoupService (Injectable) ``` -Tree-shaking ensures a React consumer never pulls in Vue/Svelte/Solid/Angular code, and vice versa. - - - Read the full deep-dive on the problem and the math behind the solution: [The Logo Soup Problem (and how to solve it)](https://www.sanity.io/blog/the-logo-soup-problem) - +Tree-shaking ensures a React consumer never pulls in Vue/Svelte/Solid/Angular code, and vice versa. Need a framework we don't support? [Build your own adapter](/frameworks/custom) in 20โ€“40 lines. diff --git a/docs/logo/dark.svg b/docs/logo/dark.svg new file mode 100644 index 0000000..6a5c8e7 --- /dev/null +++ b/docs/logo/dark.svg @@ -0,0 +1,3 @@ + + ๐Ÿœ Logo Soup + diff --git a/docs/logo/light.svg b/docs/logo/light.svg new file mode 100644 index 0000000..c732b01 --- /dev/null +++ b/docs/logo/light.svg @@ -0,0 +1,3 @@ + + ๐Ÿœ Logo Soup + diff --git a/docs/mint.json b/docs/mint.json deleted file mode 100644 index 6a69538..0000000 --- a/docs/mint.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "$schema": "https://mintlify.com/schema.json", - "name": "Logo Soup", - "logo": { - "dark": "/logo/dark.svg", - "light": "/logo/light.svg" - }, - "favicon": "/favicon.svg", - "colors": { - "primary": "#f36458", - "light": "#ff8a7a", - "dark": "#d44a3e", - "anchors": { - "from": "#f36458", - "to": "#ff8a7a" - } - }, - "topbarLinks": [ - { - "name": "GitHub", - "url": "https://github.com/sanity-labs/logo-soup" - } - ], - "topbarCtaButton": { - "name": "npm", - "url": "https://www.npmjs.com/package/@sanity-labs/logo-soup" - }, - "tabs": [ - { - "name": "API Reference", - "url": "api-reference" - } - ], - "anchors": [ - { - "name": "GitHub", - "icon": "github", - "url": "https://github.com/sanity-labs/logo-soup" - }, - { - "name": "npm", - "icon": "npm", - "url": "https://www.npmjs.com/package/@sanity-labs/logo-soup" - } - ], - "navigation": [ - { - "group": "Get Started", - "pages": ["introduction", "quickstart"] - }, - { - "group": "Frameworks", - "pages": [ - "frameworks/react", - "frameworks/vue", - "frameworks/svelte", - "frameworks/solid", - "frameworks/angular", - "frameworks/jquery", - "frameworks/vanilla", - "frameworks/custom" - ] - }, - { - "group": "Guides", - "pages": ["options", "how-it-works"] - }, - { - "group": "API Reference", - "pages": [ - "api-reference/create-logo-soup", - "api-reference/get-visual-center-transform", - "api-reference/types" - ] - } - ], - "footerSocials": { - "github": "https://github.com/sanity-labs/logo-soup" - } -} diff --git a/docs/snippets/framework-grid.jsx b/docs/snippets/framework-grid.jsx new file mode 100644 index 0000000..c7aa621 --- /dev/null +++ b/docs/snippets/framework-grid.jsx @@ -0,0 +1,88 @@ +export const FrameworkGrid = () => { + const svgl = + "https://raw.githubusercontent.com/pheralb/svgl/main/static/library"; + + const frameworks = [ + { + name: "React", + logo: `${svgl}/react_dark.svg`, + href: "/frameworks/react", + desc: "Component + hook", + }, + { + name: "Vue", + logo: `${svgl}/vue.svg`, + href: "/frameworks/vue", + desc: "Composable", + }, + { + name: "Svelte", + logo: `${svgl}/svelte.svg`, + href: "/frameworks/svelte", + desc: "Runes-compatible store", + }, + { + name: "Solid", + logo: `${svgl}/solidjs.svg`, + href: "/frameworks/solid", + desc: "Reactive primitive", + }, + { + name: "Angular", + logo: `${svgl}/angular.svg`, + href: "/frameworks/angular", + desc: "Injectable service", + }, + { + name: "jQuery", + logo: `${svgl}/jquery.svg`, + href: "/frameworks/jquery", + desc: "$.fn plugin", + }, + { + name: "Vanilla JS", + logo: `${svgl}/javascript.svg`, + href: "/frameworks/vanilla", + desc: "Core engine directly", + }, + { + name: "Your framework", + logo: null, + href: "/frameworks/custom", + desc: "Build your own adapter", + }, + ]; + + return ( + + ); +}; diff --git a/docs/snippets/logo-soup-demo.jsx b/docs/snippets/logo-soup-demo.jsx new file mode 100644 index 0000000..2619c20 --- /dev/null +++ b/docs/snippets/logo-soup-demo.jsx @@ -0,0 +1,118 @@ +export const LogoSoupDemo = () => { + // [name, naturalW, naturalH, normalizedW, normalizedH, translateY] + // Values from the real library: createLogoSoup() with baseSize=48, scaleFactor=0.5, densityAware=true + const data = [ + ["coda", 247, 82, 77, 26, -2.5], + ["reforge", 420, 100, 112, 23, 0.5], + ["kahoot", 294, 100, 82, 28, -1.0], + ["cursor", 300, 71, 103, 24, -0.3], + ["wetransfer", 371, 55, 116, 17, -0.9], + ["redis", 170, 54, 78, 25, -2.1], + ["expedia", 300, 67, 108, 22, -0.1], + ["browser-comp", 274, 190, 78, 54, -1.4], + ["hinge", 332, 126, 79, 30, 1.0], + ["too-good-to-go", 250, 200, 64, 52, -1.4], + ["unity", 305, 112, 80, 29, 0], + ["keystone", 400, 80, 113, 23, 1.9], + ["retool", 321, 63, 107, 21, -0.4], + ["loveholidays", 357, 58, 130, 21, -1.5], + ["rad-power-bikes", 427, 33, 164, 13, 0.6], + ["stereolabs", 372, 50, 131, 18, -1.5], + ["pinecone", 434, 90, 118, 24, -3.0], + ["clerk", 317, 92, 89, 26, -1.1], + ["samsung", 325, 50, 110, 17, 0], + ["customer.io", 400, 54, 129, 18, -0.2], + ]; + + const base = + "https://raw.githubusercontent.com/sanity-labs/logo-soup/main/static/logos"; + const uniformH = 28; + const displayScale = 0.8; + + const [on, setOn] = useState(true); + const [count, setCount] = useState(10); + + return ( +
+
+
+ {data.slice(0, count).map(([name, nw, nh, dw, dh, ty]) => ( + {name} + ))} +
+ +
+
+ + + Logo Soup {on ? "on" : "off"} + +
+ + +
+
+

+ Real values from createLogoSoup() + {" ยท "} + + Open playground โ†’ + +

+
+ ); +}; diff --git a/package.json b/package.json index 7e95429..c6a8d9d 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,9 @@ "test": "NODE_ENV=development bun test", "bench": "bun tests/bench/run.ts", "storybook": "storybook dev -p 6006", - "storybook:build": "storybook build" + "storybook:build": "storybook build", + "docs": "cd docs && mint dev", + "docs:build": "cd docs && mint build" }, "repository": { "type": "git",