Skip to main content

Installation

npm install @sanity-labs/logo-soup
No framework dependencies required.

Quick Start

The core engine works with any JavaScript environment:
import {
  createLogoSoup,
  getVisualCenterTransform,
} from "@sanity-labs/logo-soup";

const engine = createLogoSoup();

engine.subscribe(() => {
  const { status, normalizedLogos } = engine.getSnapshot();
  if (status !== "ready") return;

  const container = document.getElementById("logos")!;
  container.innerHTML = normalizedLogos
    .map((logo) => {
      const transform = getVisualCenterTransform(logo, "visual-center-y");
      return `<img
        src="${logo.src}"
        alt="${logo.alt}"
        width="${logo.normalizedWidth}"
        height="${logo.normalizedHeight}"
        style="transform: ${transform ?? "none"}"
      />`;
    })
    .join("");
});

engine.process({
  logos: ["/logos/acme.svg", "/logos/globex.svg", "/logos/initech.svg"],
});

Core Engine API

createLogoSoup

Returns: LogoSoupEngine
type LogoSoupEngine = {
  process(options: ProcessOptions): void;
  subscribe(listener: () => void): () => void;
  getSnapshot(): LogoSoupState;
  destroy(): void;
};

process(options)

Triggers logo loading, measurement, and normalization.
engine.process({
  logos: ["/logo1.svg", { src: "/logo2.svg", alt: "Logo 2" }],
  baseSize: 48,
  scaleFactor: 0.5,
  densityAware: true,
  densityFactor: 0.5,
  cropToContent: false,
  contrastThreshold: 10,
  backgroundColor: "#ffffff",
});
See API Reference for complete options.

subscribe(listener)

Subscribes to state changes. Returns an unsubscribe function.
const unsubscribe = engine.subscribe(() => {
  console.log("State changed:", engine.getSnapshot());
});

// Later:
unsubscribe();

getSnapshot()

Returns the current immutable state:
type LogoSoupState = {
  status: "idle" | "loading" | "ready" | "error";
  normalizedLogos: NormalizedLogo[];
  error: Error | null;
};
const { status, normalizedLogos, error } = engine.getSnapshot();

if (status === "ready") {
  console.log("Loaded", normalizedLogos.length, "logos");
}

destroy()

Cleans up resources, cancels in-flight work, and revokes blob URLs:
engine.destroy();

State Management

The engine uses an immutable state model. The snapshot reference only changes when actual values change:
let prevSnapshot = engine.getSnapshot();

engine.subscribe(() => {
  const nextSnapshot = engine.getSnapshot();
  
  if (prevSnapshot !== nextSnapshot) {
    console.log("State actually changed");
    prevSnapshot = nextSnapshot;
  }
});

Examples

import { createLogoSoup, getVisualCenterTransform } from "@sanity-labs/logo-soup";

const engine = createLogoSoup();
const container = document.getElementById("logos");

engine.subscribe(() => {
  const { status, normalizedLogos } = engine.getSnapshot();
  
  if (status === "loading") {
    container.innerHTML = "<p>Loading...</p>";
  } else if (status === "ready") {
    container.innerHTML = normalizedLogos
      .map((logo) => {
        const transform = getVisualCenterTransform(logo, "visual-center-y");
        return `
          <img
            src="${logo.src}"
            alt="${logo.alt}"
            width="${logo.normalizedWidth}"
            height="${logo.normalizedHeight}"
            style="transform: ${transform ?? "none"}; margin: 0 1rem;"
          />
        `;
      })
      .join("");
  }
});

engine.process({
  logos: [
    { src: "/logos/acme.svg", alt: "Acme Corp" },
    { src: "/logos/globex.svg", alt: "Globex" },
    { src: "/logos/initech.svg", alt: "Initech" },
  ],
  baseSize: 48,
});

TypeScript

All core exports are fully typed:
import type {
  LogoSoupEngine,
  LogoSoupState,
  ProcessOptions,
  LogoSource,
  NormalizedLogo,
  AlignmentMode,
  BackgroundColor,
  BoundingBox,
  VisualCenter,
  MeasurementResult,
} from "@sanity-labs/logo-soup";

Utilities

getVisualCenterTransform

Computes the CSS transform for visual alignment:
import { getVisualCenterTransform } from "@sanity-labs/logo-soup";

const transform = getVisualCenterTransform(logo, "visual-center-y");
// "translateY(-2.5px)" or null
Alignment modes:
  • "bounds" — Geometric center (no transform)
  • "visual-center" — Visual center on both axes
  • "visual-center-x" — Visual center horizontally
  • "visual-center-y" — Visual center vertically (default)

See Also

Build docs developers (and LLMs) love