logo-soup 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,19 +22,64 @@ npx logo-soup ./logos
22
22
  logo-soup ./public/logos -o logo-metrics.json
23
23
  ```
24
24
 
25
- Output JSON keys are filenames only (e.g. `"logo.svg"`), so consumers can prepend their own base path.
25
+ Output keys are filenames only (e.g. `"logo.svg"`), so you can prepend your own base path.
26
26
 
27
27
  ```
28
28
  logo-soup <dir> [options]
29
29
 
30
30
  Options:
31
31
  --output, -o Output JSON file path (default: "logo-metrics.json")
32
- --base-size Base size for normalization in px (default: 48)
32
+ --base-size Base size for normalization in px (default: 64)
33
33
  --scale-factor Aspect ratio normalization 0-1 (default: 0.5)
34
34
  --density-factor Density compensation 0-1 (default: 0.5)
35
35
  --extensions, -e Comma-separated file extensions (default: "svg,png")
36
36
  ```
37
37
 
38
+ ## Usage
39
+
40
+ Running the CLI produces a JSON file mapping each filename to its normalized dimensions:
41
+
42
+ ```json
43
+ {
44
+ "acme-wordmark.svg": { "width": 143, "height": 28, "offsetX": 0, "offsetY": 0.5 },
45
+ "globex-icon.svg": { "width": 64, "height": 64, "offsetX": 0, "offsetY": 0 },
46
+ "initech-monogram.svg": { "width": 52, "height": 79, "offsetX": -0.4, "offsetY": 0 }
47
+ }
48
+ ```
49
+
50
+ `width` and `height` are pixel dimensions normalized so every logo feels the same visual size – use them directly as `width`/`height` attributes. `offsetX`/`offsetY` are optional sub-pixel corrections (see [Visual Center Offsets](#visual-center-offsets)).
51
+
52
+ ### Logo Strip
53
+
54
+ The most common use case: a "trusted by" row or partner logo strip. Apply `width` and `height` directly – the normalization ensures all logos feel balanced side by side.
55
+
56
+ ```html
57
+ <div class="flex flex-wrap items-center justify-center gap-8">
58
+ <img src="/logos/acme-wordmark.svg" width="143" height="28" alt="Acme">
59
+ <img src="/logos/globex-icon.svg" width="64" height="64" alt="Globex">
60
+ <img src="/logos/initech-monogram.svg" width="52" height="79" alt="Initech">
61
+ </div>
62
+ ```
63
+
64
+ > [!TIP]
65
+ > The default `baseSize` is 64px, so a square logo renders at 64×64px. For a different base, pass `--base-size <n>` to the CLI (or `{ baseSize }` to `normalize()`), or scale uniformly with CSS – the proportions stay correct either way.
66
+
67
+ ### Visual Center Offsets
68
+
69
+ Some logos have visual weight that doesn't match their geometric center – a play button or an arrow, for instance. `offsetX`/`offsetY` correct for this by nudging the logo toward its optical center.
70
+
71
+ These offsets are typically small (< 2px) and `width`/`height` alone handle most of the balancing. For pixel-perfect alignment, apply them as CSS transforms:
72
+
73
+ ```html
74
+ <img src="/logos/acme-wordmark.svg" width="143" height="28" style="transform: translate(0px, 0.5px)">
75
+ ```
76
+
77
+ For horizontal strips where only vertical alignment matters, you can apply just the Y offset:
78
+
79
+ ```html
80
+ <img src="/logos/acme-wordmark.svg" width="143" height="28" style="transform: translateY(0.5px)">
81
+ ```
82
+
38
83
  ## Programmatic API
39
84
 
40
85
  ```ts
@@ -43,7 +88,7 @@ import { analyze, analyzeDirectory, normalize } from 'logo-soup'
43
88
  // Single file
44
89
  const metrics = await analyze('./logo.svg')
45
90
  if (metrics) {
46
- const dimensions = normalize(metrics, { baseSize: 48 })
91
+ const dimensions = normalize(metrics)
47
92
  console.log(dimensions) // { width, height, offsetX, offsetY }
48
93
  }
49
94
 
@@ -104,7 +149,7 @@ Converts raw metrics into display dimensions using aspect ratio normalization wi
104
149
  function normalize(metrics: Metrics, options?: NormalizeOptions): NormalizedDimensions
105
150
 
106
151
  interface NormalizeOptions {
107
- /** Base size in pixels (default: 48) */
152
+ /** Base size in pixels (default: 64) */
108
153
  baseSize?: number
109
154
  /** Aspect ratio normalization factor, 0–1 (default: 0.5) */
110
155
  scaleFactor?: number
package/dist/cli.mjs CHANGED
@@ -1,16 +1,29 @@
1
- import { c as DENSITY_FACTOR, d as SCALE_FACTOR, i as BASE_SIZE, o as DEFAULT_EXTENSIONS, r as analyzeDirectory, t as normalize } from "./normalize-DbCIuSqg.mjs";
1
+ import { c as DENSITY_FACTOR, d as SCALE_FACTOR, i as BASE_SIZE, o as DEFAULT_EXTENSIONS, r as analyzeDirectory, t as normalize } from "./normalize-B5apzI38.mjs";
2
2
  import * as fsp from "node:fs/promises";
3
3
  import * as path from "node:path";
4
4
  import process from "node:process";
5
+ import * as ansis from "ansis";
5
6
  import { defineCommand, runMain } from "citty";
6
- import { consola } from "consola";
7
- import { colors } from "consola/utils";
8
7
 
9
8
  //#region package.json
10
9
  var name = "logo-soup";
11
- var version = "0.2.0";
10
+ var version = "0.2.2";
12
11
  var description = "Normalize logo dimensions for visual balance";
13
12
 
13
+ //#endregion
14
+ //#region src/log.ts
15
+ const log = {
16
+ success(message) {
17
+ console.log(`${ansis.green("✔")} ${message}`);
18
+ },
19
+ error(message) {
20
+ console.error(`${ansis.red("✖")} ${message}`);
21
+ },
22
+ info(message) {
23
+ console.log(`${ansis.cyan("●")} ${message}`);
24
+ }
25
+ };
26
+
14
27
  //#endregion
15
28
  //#region src/cli.ts
16
29
  const command = defineCommand({
@@ -53,11 +66,11 @@ const command = defineCommand({
53
66
  const dirPath = path.resolve(args.dir);
54
67
  try {
55
68
  if (!(await fsp.stat(dirPath)).isDirectory()) {
56
- consola.error(`Not a directory: ${dirPath}`);
69
+ log.error(`Not a directory: ${dirPath}`);
57
70
  process.exit(1);
58
71
  }
59
72
  } catch {
60
- consola.error(`Directory not found: ${dirPath}`);
73
+ log.error(`Directory not found: ${dirPath}`);
61
74
  process.exit(1);
62
75
  }
63
76
  const baseSize = parseNumericArg(args["base-size"], "base-size", BASE_SIZE);
@@ -75,30 +88,29 @@ const command = defineCommand({
75
88
  results[file] = dimensions;
76
89
  entries.push([file, dimensions]);
77
90
  }
78
- console.log();
79
- console.log(`${colors.cyan("●")} ${colors.bold(name)} ${colors.dim(`v${version}`)}`);
91
+ log.info(`${ansis.bold(name)} ${ansis.dim(`v${version}`)}`);
80
92
  console.log();
81
93
  const maxEntryLength = Math.max(...entries.map(([entry]) => entry.length));
82
94
  const total = entries.length;
83
95
  for (const [i, [file, dimensions]] of entries.entries()) {
84
96
  const branch = i === total - 1 ? "└─" : "├─";
85
- const dimStr = `${dimensions.width}${colors.dim("×")}${dimensions.height}`;
97
+ const dimStr = `${dimensions.width}${ansis.dim("×")}${dimensions.height}`;
86
98
  const padding = " ".repeat(maxEntryLength - file.length + 2);
87
- console.log(` ${colors.dim(branch)} ${colors.cyan(file)}${padding}${dimStr}`);
99
+ console.log(` ${ansis.dim(branch)} ${ansis.cyan(file)}${padding}${dimStr}`);
88
100
  }
89
101
  const outputPath = path.resolve(args.output);
90
102
  await fsp.mkdir(path.dirname(outputPath), { recursive: true });
91
- await fsp.writeFile(outputPath, `${JSON.stringify(results, null, 2)}\n`);
103
+ await fsp.writeFile(outputPath, `${JSON.stringify(results, void 0, 2)}\n`);
92
104
  const relativeOutput = path.relative(process.cwd(), outputPath);
93
105
  console.log();
94
- consola.success(`Wrote ${colors.bold(String(total))} entries to ${colors.cyan(relativeOutput)}`);
106
+ log.success(`Wrote ${ansis.bold(String(total))} entries to ${ansis.cyan(relativeOutput)}`);
95
107
  }
96
108
  });
97
109
  function parseNumericArg(value, name, fallback) {
98
110
  if (value == null) return fallback;
99
111
  const parsedNumber = Number(value);
100
112
  if (Number.isNaN(parsedNumber)) {
101
- consola.error(`Invalid value for --${name}: "${value}" (expected a number)`);
113
+ log.error(`Invalid value for --${name}: "${value}" (expected a number)`);
102
114
  process.exit(1);
103
115
  }
104
116
  return parsedNumber;
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { a as CONTRAST_THRESHOLD, c as DENSITY_FACTOR, d as SCALE_FACTOR, i as BASE_SIZE, l as REFERENCE_DENSITY, n as analyze, o as DEFAULT_EXTENSIONS, r as analyzeDirectory, s as DENSITY_DAMPENING, t as normalize, u as SAMPLE_MAX_SIZE } from "./normalize-DbCIuSqg.mjs";
1
+ import { a as CONTRAST_THRESHOLD, c as DENSITY_FACTOR, d as SCALE_FACTOR, i as BASE_SIZE, l as REFERENCE_DENSITY, n as analyze, o as DEFAULT_EXTENSIONS, r as analyzeDirectory, s as DENSITY_DAMPENING, t as normalize, u as SAMPLE_MAX_SIZE } from "./normalize-B5apzI38.mjs";
2
2
 
3
3
  export { BASE_SIZE, CONTRAST_THRESHOLD, DEFAULT_EXTENSIONS, DENSITY_DAMPENING, DENSITY_FACTOR, REFERENCE_DENSITY, SAMPLE_MAX_SIZE, SCALE_FACTOR, analyze, analyzeDirectory, normalize };
@@ -8,7 +8,7 @@ const SAMPLE_MAX_SIZE = 200;
8
8
  /** Minimum contrast threshold to consider a pixel as content. */
9
9
  const CONTRAST_THRESHOLD = 10;
10
10
  /** Base size in pixels for the normalized output. */
11
- const BASE_SIZE = 48;
11
+ const BASE_SIZE = 64;
12
12
  /** Aspect ratio normalization factor (0–1). */
13
13
  const SCALE_FACTOR = .5;
14
14
  /** Density compensation factor (0–1). */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "logo-soup",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "packageManager": "pnpm@10.30.0",
6
6
  "description": "Normalize logo dimensions for visual balance",
7
7
  "author": "Johann Schopplich <hello@johannschopplich.com>",
@@ -40,6 +40,7 @@
40
40
  "dist"
41
41
  ],
42
42
  "scripts": {
43
+ "dev": "node src/cli.ts",
43
44
  "build": "tsdown",
44
45
  "lint": "eslint .",
45
46
  "lint:fix": "eslint . --fix",
@@ -48,8 +49,8 @@
48
49
  "release": "bumpp"
49
50
  },
50
51
  "dependencies": {
52
+ "ansis": "^4.2.0",
51
53
  "citty": "^0.2.1",
52
- "consola": "^3.4.2",
53
54
  "sharp": "^0.34.5"
54
55
  },
55
56
  "devDependencies": {