let’s add a Recolor control so you (or your users) can instantly remap the generated SVG mark’s colors to your brand palette (Primary, Secondary, Accent, Highlight, Neutral, Surface, Text Light, Text Dark — plus Black). You’ll be able to:

Pick a target color (from your 8-color kit + black).

Choose whether to affect fills, strokes, and gradients.

Apply to the current preview or one-click create variants (Primary/Secondary/Accent/Highlight) and drop them into the Logos section.

Below are drop-ins.

1) Utility: robust SVG color remapper

Create src/utils/svgColor.ts:

// src/utils/svgColor.ts
// Lightweight color tools + SVG recolor helpers

const HEX_RE = /#([0-9a-f]{3,8})\b/gi;

export function normalizeHex(hex: string): string {
  if (!hex) return hex;
  hex = hex.trim().toLowerCase();
  if (!hex.startsWith("#")) return hex;
  if (hex.length === 4) {
    // #abc -> #aabbcc
    const r = hex[1], g = hex[2], b = hex[3];
    return `#${r}${r}${g}${g}${b}${b}`;
  }
  if (hex.length === 5) {
    // #abcf -> #aabbccff (rare, keep alpha)
    const r = hex[1], g = hex[2], b = hex[3], a = hex[4];
    return `#${r}${r}${g}${g}${b}${b}${a}${a}`;
  }
  return hex;
}

export function extractSvgColors(svg: string): string[] {
  const set = new Set<string>();
  // fill, stroke, stop-color, flood-color, lighting-color inside attributes and <style>
  const attrRe = /(fill|stroke|stop-color|flood-color|lighting-color)\s*=\s*("([^"]+)"|'([^']+)')/gi;
  let m: RegExpExecArray | null;
  while ((m = attrRe.exec(svg))) {
    const val = (m[3] || m[4] || "").trim();
    if (val.startsWith("#")) set.add(normalizeHex(val));
  }
  // also catch hex inside <style>
  let s;
  while ((s = HEX_RE.exec(svg))) set.add(normalizeHex(s[0]));
  return [...set];
}

/**
 * Recolor all non-transparent fills (and optionally strokes + gradient stops) to a single target color.
 * This is opinionated, but great for logo marks where we want palette-constrained output.
 */
export function recolorSvgToSingle(
  svg: string,
  options: {
    color: string;              // hex like "#2563eb"
    affectStrokes?: boolean;    // default true
    includeGradients?: boolean; // default true: updates stop-color etc
  }
): string {
  const target = normalizeHex(options.color);
  const affectStrokes = options.affectStrokes ?? true;
  const includeGradients = options.includeGradients ?? true;

  let out = svg;

  // Replace fill hexes
  out = out.replace(/(\sfill\s*=\s*["'])(#[0-9a-fA-F]{3,8})(["'])/g, (_m, pre, _col, post) => `${pre}${target}${post}`);

  // Some elements rely on inherited fill via CSS or default "black".
  // Add a default fill on shapes that lack it (but not gradients/defs)
  out = out.replace(
    /<((path|rect|circle|ellipse|polygon|polyline|line)\b)((?:(?!>)[\s\S])*)>/gi,
    (m: string, tagOpen: string, _tagName: string, attrs: string) => {
      if (/ fill\s*=/.test(attrs)) return m; // has explicit fill
      // If there's a stroke-only element, leave it; otherwise give it a fill
      if (/ stroke\s*=/i.test(attrs) && !affectStrokes) return m;
      return `<${tagOpen}${attrs} fill="${target}">`;
    }
  );

  if (affectStrokes) {
    out = out.replace(/(\sstroke\s*=\s*["'])(#[0-9a-fA-F]{3,8})(["'])/g, (_m, pre, _col, post) => `${pre}${target}${post}`);
  }

  if (includeGradients) {
    // stop-color replacements inside <linearGradient>/<radialGradient>
    out = out.replace(/(\sstop-color\s*=\s*["'])(#[0-9a-fA-F]{3,8})(["'])/g, (_m, pre, _col, post) => `${pre}${target}${post}`);
    // Fallback fill in <style> blocks (simple cases)
    out = out.replace(/(fill\s*:\s*)(#[0-9a-fA-F]{3,8})(\s*;)/g, (_m, pre, _col, post) => `${pre}${target}${post}`);
    if (affectStrokes) {
      out = out.replace(/(stroke\s*:\s*)(#[0-9a-fA-F]{3,8})(\s*;)/g, (_m, pre, _col, post) => `${pre}${target}${post}`);
    }
  }

  return out;
}


This is intentionally palette-first: it forces the mark to a single branded color (great for consistent logo marks). It also handles gradients by recoloring their stops to the target — simple, clean brand variants.

2) Step-3 UI: “Recolor mark” panel

In your Step 3 (image generator card), add a Recolor panel that appears when an SVG exists.

At top of file:

import { recolorSvgToSingle } from "../../utils/svgColor";


Add state near your other Step-3 states:

const [recolorTarget, setRecolorTarget] = useState<string>("primary"); // 'black' or palette key
const [recolorAffectStrokes, setRecolorAffectStrokes] = useState(true);
const [recolorIncludeGradients, setRecolorIncludeGradients] = useState(true);

const resolveRecolorHex = () => {
  if (recolorTarget === "black") return "#000000";
  // @ts-ignore palette keys are typed
  return palette[recolorTarget] || "#000000";
};


Then, below your SVG preview, insert:

{iconSvg && (
  <div className="mt-3 rounded-lg border p-3 space-y-3">
    <div className="text-sm font-semibold">Recolor mark</div>
    <div className="grid grid-cols-3 sm:grid-cols-5 gap-2">
      {[
        { key: "black", label: "Black", hex: "#000000" },
        { key: "primary", label: "Primary", hex: palette.primary },
        { key: "secondary", label: "Secondary", hex: palette.secondary },
        { key: "accent", label: "Accent", hex: palette.accent },
        { key: "highlight", label: "Highlight", hex: palette.highlight },
        { key: "neutral", label: "Neutral", hex: palette.neutral },
        { key: "surface", label: "Surface", hex: palette.surface },
        { key: "textLight", label: "Text (Light)", hex: palette.textLight },
        { key: "textDark", label: "Text (Dark)", hex: palette.textDark },
      ].map((c) => {
        const active = recolorTarget === c.key;
        return (
          <button
            key={c.key}
            onClick={() => setRecolorTarget(c.key)}
            className={`rounded-lg border p-2 text-left hover:bg-neutral-50 ${
              active ? "border-neutral-900" : "border-neutral-200"
            }`}
            type="button"
            title={c.label}
          >
            <div className="h-6 w-full rounded" style={{ background: c.hex }} />
            <div className="text-[11px] mt-1 text-neutral-600">{c.label}</div>
          </button>
        );
      })}
    </div>

    <div className="flex items-center gap-4">
      <label className="text-sm flex items-center gap-2">
        <input
          type="checkbox"
          checked={recolorAffectStrokes}
          onChange={(e) => setRecolorAffectStrokes(e.target.checked)}
        />
        Affect strokes
      </label>
      <label className="text-sm flex items-center gap-2">
        <input
          type="checkbox"
          checked={recolorIncludeGradients}
          onChange={(e) => setRecolorIncludeGradients(e.target.checked)}
        />
        Include gradients
      </label>
    </div>

    <div className="flex flex-wrap items-center gap-2">
      <button
        onClick={() => {
          const hex = resolveRecolorHex();
          const recolored = recolorSvgToSingle(iconSvg, {
            color: hex,
            affectStrokes: recolorAffectStrokes,
            includeGradients: recolorIncludeGradients,
          });
          setIconSvg(recolored); // update preview in place
        }}
        className="px-3 py-2 rounded-lg bg-neutral-900 text-white hover:bg-black"
        type="button"
      >
        Apply to current
      </button>

      <button
        onClick={() => {
          const primary = recolorSvgToSingle(iconSvg!, { color: palette.primary, affectStrokes: recolorAffectStrokes, includeGradients: recolorIncludeGradients });
          const secondary = recolorSvgToSingle(iconSvg!, { color: palette.secondary, affectStrokes: recolorAffectStrokes, includeGradients: recolorIncludeGradients });
          const accent = recolorSvgToSingle(iconSvg!, { color: palette.accent, affectStrokes: recolorAffectStrokes, includeGradients: recolorIncludeGradients });
          const highlight = recolorSvgToSingle(iconSvg!, { color: palette.highlight, affectStrokes: recolorAffectStrokes, includeGradients: recolorIncludeGradients });

          setLogos((prev) => ([
            ...prev,
            { id: `v-${Date.now()}-p`, filename: "logo-mark-primary.svg", svg: primary, kind: "svg" as const },
            { id: `v-${Date.now()}-s`, filename: "logo-mark-secondary.svg", svg: secondary, kind: "svg" as const },
            { id: `v-${Date.now()}-a`, filename: "logo-mark-accent.svg", svg: accent, kind: "svg" as const },
            { id: `v-${Date.now()}-h`, filename: "logo-mark-highlight.svg", svg: highlight, kind: "svg" as const },
          ]));
          setTimeout(() => {
            document.getElementById("logos-section")?.scrollIntoView({ behavior: "smooth" });
          }, 10);
        }}
        className="px-3 py-2 rounded-lg border hover:bg-neutral-50"
        type="button"
      >
        Create brand variants
      </button>
    </div>
  </div>
)}

3) Combine step (vector all the way)

Since you’ve moved to SVG marks, I recommend using the vector compose I gave you earlier (composeMarkAndWordmarkSVG) so the final export remains fully scalable SVG (icon + wordmark text). Your text color choice you already added will just drop in as the textColor param.

Notes & tips

Palette discipline: The SVG generation route already constrains the model to your palette colors; the recolor lets you enforce or tweak after the fact.

Gradients: The remapper updates stop-color too — so your gradient marks stay intact but adopt the new palette tone uniformly.

Safety: You’re already optimizing/sanitizing SVG server-side with SVGO + a simple allowlist. Keep that in place before saving/serving user-generated SVGs.