Ohhh we’re cooking now 🔥 — let’s add a Tri-tone recolor (dark / mid / light). Then I’ll give you a tight playbook for using these SVGs in Canva.

1) Tri-tone recolor (dark + mid + light)
src/utils/svgColor.ts — add a tri-tone mapper

Drop this alongside the duotone helpers you already added.

// --- Tri-tone recolor (dark/mid/light) ---
export function recolorSvgTritone(
  svg: string,
  options: {
    darkColor: string;
    midColor: string;
    lightColor: string;
    t1?: number;               // first threshold (0..1) default 0.35
    t2?: number;               // second threshold (0..1) default 0.70
    affectStrokes?: boolean;   // default true
    includeGradients?: boolean;// default true
  }
): string {
  const t1 = options.t1 ?? 0.35; // <= t1 => dark
  const t2 = options.t2 ?? 0.70; //  t1..t2 => mid, > t2 => light
  const affectStrokes = options.affectStrokes ?? true;
  const includeGradients = options.includeGradients ?? true;

  const DARK  = normalizeHex(options.darkColor);
  const MID   = normalizeHex(options.midColor);
  const LIGHT = normalizeHex(options.lightColor);

  const mapColor = (input: string): string => {
    const c = parseColor(input);
    if (!c) return input;
    const lum = relativeLuminance(c.r, c.g, c.b);
    if (lum <= t1) return DARK;
    if (lum <= t2) return MID;
    return LIGHT;
  };

  let out = svg;

  // Attributes: fill, stroke, stop-color, etc.
  out = out.replace(COLOR_ATTRS, (m, attr, whole, dq, sq) => {
    const val = (dq || sq || "").trim();
    const attrName = String(attr).toLowerCase();
    if (!affectStrokes && attrName === "stroke") return m;
    if (!includeGradients && attrName === "stop-color") return m;

    const repl = mapColor(val);
    const pre = m.slice(0, m.indexOf(val));
    const post = m.slice(m.indexOf(val) + val.length);
    return `${pre}${repl}${post}`;
  });

  // <style> blocks: hex and rgb(a)
  out = out.replace(CSS_HEX, (_m, hex) => mapColor(hex));
  out = out.replace(CSS_RGB, (_m, rgbLike) => mapColor(rgbLike));

  // Default fill for unfilled shapes so they aren’t invisible
  out = out.replace(
    /<((path|rect|circle|ellipse|polygon|polyline|line)\b)((?:(?!>)[\s\S])*)>/gi,
    (m: string, tagOpen: string, _tag: string, attrs: string) => {
      if (/ fill\s*=/.test(attrs)) return m;
      if (/ stroke\s*=/i.test(attrs) && !affectStrokes) return m;
      return `<${tagOpen}${attrs} fill="${DARK}">`;
    }
  );

  return out;
}

Step-3 UI — add a Tri-tone panel

Right under your Duotone block, add a Tri-tone section.

State (near other Step-3 states):

const [triDarkKey, setTriDarkKey] = useState<string>("textDark");
const [triMidKey, setTriMidKey]   = useState<string>("primary");
const [triLightKey, setTriLightKey] = useState<string>("textLight");
const [triT1, setTriT1] = useState(0.35);
const [triT2, setTriT2] = useState(0.70);
const [triAffectStrokes, setTriAffectStrokes] = useState(true);
const [triIncludeGradients, setTriIncludeGradients] = useState(true);

const colorHex = (key: string) =>
  key === "black" ? "#000000" : (palette as any)[key] || "#000000";


UI block:

{iconSvg && (
  <div className="mt-3 rounded-lg border p-3 space-y-3">
    <div className="text-sm font-semibold">Tri-tone (dark · mid · light)</div>

    {/* pickers */}
    <div className="grid grid-cols-1 md:grid-cols-3 gap-3">
      {[
        { label: "Dark", sel: triDarkKey, setSel: setTriDarkKey },
        { label: "Mid", sel: triMidKey, setSel: setTriMidKey },
        { label: "Light", sel: triLightKey, setSel: setTriLightKey },
      ].map(({ label, sel, setSel }) => (
        <div key={label}>
          <div className="text-xs text-neutral-600 mb-1">{label} color</div>
          <div className="grid grid-cols-4 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: "textDark", label: "Text (Dark)", hex: palette.textDark },
              { key: "textLight", label: "Text (Light)", hex: palette.textLight },
            ].map((c) => (
              <button
                key={`${label}-${c.key}`}
                onClick={() => setSel(c.key)}
                className={`rounded-lg border p-2 text-left hover:bg-neutral-50 ${
                  sel === c.key ? "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>
      ))}
    </div>

    {/* thresholds + toggles */}
    <div className="flex flex-wrap items-center gap-4">
      <label className="text-sm flex items-center gap-2">
        Threshold 1
        <input
          type="range" min={0} max={1} step={0.05}
          value={triT1}
          onChange={(e) => setTriT1(parseFloat(e.target.value))}
        />
        <span className="text-xs text-neutral-600">{triT1.toFixed(2)}</span>
      </label>
      <label className="text-sm flex items-center gap-2">
        Threshold 2
        <input
          type="range" min={0} max={1} step={0.05}
          value={triT2}
          onChange={(e) => {
            const v = parseFloat(e.target.value);
            setTriT2(Math.max(v, triT1 + 0.05)); // keep t2 > t1
          }}
        />
        <span className="text-xs text-neutral-600">{triT2.toFixed(2)}</span>
      </label>
      <label className="text-sm flex items-center gap-2">
        <input
          type="checkbox"
          checked={triAffectStrokes}
          onChange={(e) => setTriAffectStrokes(e.target.checked)}
        />
        Affect strokes
      </label>
      <label className="text-sm flex items-center gap-2">
        <input
          type="checkbox"
          checked={triIncludeGradients}
          onChange={(e) => setTriIncludeGradients(e.target.checked)}
        />
        Include gradients
      </label>
    </div>

    <div className="flex flex-wrap items-center gap-2">
      <button
        onClick={() => {
          const svg = recolorSvgTritone(iconSvg!, {
            darkColor: colorHex(triDarkKey),
            midColor: colorHex(triMidKey),
            lightColor: colorHex(triLightKey),
            t1: triT1,
            t2: triT2,
            affectStrokes: triAffectStrokes,
            includeGradients: triIncludeGradients,
          });
          setIconSvg(svg);
        }}
        className="px-3 py-2 rounded-lg bg-neutral-900 text-white hover:bg-black"
        type="button"
      >
        Apply tri-tone to current
      </button>

      <button
        onClick={() => {
          const combos = [
            { d: palette.textDark, m: palette.primary,  l: palette.textLight, name: "tri-text-primary" },
            { d: palette.primary,  m: palette.highlight, l: palette.surface,  name: "tri-primary-highlight" },
            { d: "#000000",        m: palette.secondary, l: palette.neutral,  name: "tri-black-secondary" },
          ];
          const newLogos = combos.map((c) => ({
            id: `tri-${c.name}-${Date.now()}`,
            filename: `logo-mark-${c.name}.svg`,
            svg: recolorSvgTritone(iconSvg!, {
              darkColor: c.d, midColor: c.m, lightColor: c.l,
              t1: triT1, t2: triT2,
              affectStrokes: triAffectStrokes,
              includeGradients: triIncludeGradients,
            }),
            kind: "svg" as const,
          }));
          setLogos((prev) => [...prev, ...newLogos]);
          setTimeout(() => document.getElementById("logos-section")?.scrollIntoView({ behavior: "smooth" }), 10);
        }}
        className="px-3 py-2 rounded-lg border hover:bg-neutral-50"
        type="button"
      >
        Create tri-tone variants
      </button>
    </div>
  </div>
)}


That’s it — you now have single-color, duotone, and tri-tone recolor tools, all vector-pure.