You can slot this right under your “Logo text color” section.

React component: per-letter color editor (with SVG export)
"use client";
import React, { useEffect, useMemo, useRef, useState } from "react";

type Color = string;

type Props = {
  text: string;                  // e.g., "Pawtopia"
  fontFamily: string;            // e.g., "Poppins"
  fontSize?: number;             // px
  palette?: Color[];             // color swatches
  lineHeight?: number;           // px
  letterSpacing?: number;        // em
};

const DEFAULT_PALETTE = ["#000000","#4274b9","#bd4b43","#3ab7bd","#9b7cf5","#666","#111111"];

export default function PerLetterColorText({
  text,
  fontFamily,
  fontSize = 72,
  palette = DEFAULT_PALETTE,
  lineHeight = 1.1,
  letterSpacing = 0,
}: Props) {
  const letters = useMemo(() => Array.from(text), [text]);
  // color state per index (defaults to first palette swatch)
  const [colors, setColors] = useState<Color[]>(() => letters.map(() => palette[0]));
  const [active, setActive] = useState<Color>(palette[1] ?? "#4274b9");
  const [dragging, setDragging] = useState(false);

  // reset colors if text changes length
  useEffect(() => {
    setColors(prev => {
      if (prev.length === letters.length) return prev;
      const base = prev[0] ?? palette[0];
      return letters.map((_, i) => prev[i] ?? base);
    });
  }, [letters.length]); // eslint-disable-line

  // paint helpers
  const applyColor = (idx: number, col: Color) =>
    setColors(prev => prev.map((c, i) => (i === idx ? col : c)));

  const onLetterDown = (i: number) => {
    setDragging(true);
    applyColor(i, active);
  };
  const onLetterEnter = (i: number) => {
    if (dragging) applyColor(i, active);
  };
  const stopDragging = () => setDragging(false);
  useEffect(() => {
    window.addEventListener("mouseup", stopDragging);
    return () => window.removeEventListener("mouseup", stopDragging);
  }, []);

  // SVG export (build <tspan> per letter so colors persist)
  const downloadSvg = () => {
    const padding = 16;
    const fontPx = fontSize;
    const svg = [
      `<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewBox="0 0 ${Math.max(
        400,
        Math.ceil((fontPx * 0.6) * letters.length) + padding * 2
      )} ${fontPx + padding * 2}">`,
      `<style>@font-face{font-family:'${cssEscape(fontFamily)}';src:local('${cssEscape(
        fontFamily
      )}');}</style>`,
      `<g transform="translate(${padding}, ${padding + fontPx * 0.8})">`,
      `<text font-family="${escapeAttr(fontFamily)}" font-size="${fontPx}" letter-spacing="${letterSpacing}em">`,
      ...letters.map((ch, i) => `<tspan fill="${escapeAttr(colors[i])}">${escapeText(ch)}</tspan>`),
      `</text>`,
      `</g>`,
      `</svg>`,
    ].join("");

    const blob = new Blob([svg], { type: "image/svg+xml;charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = fileNameFrom(text || "logo") + ".svg";
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div className="space-y-3">
      {/* palette */}
      <div className="flex flex-wrap gap-8 items-center">
        <div className="flex gap-8">
          {palette.map((c) => (
            <button
              key={c}
              onClick={() => setActive(c)}
              title={c}
              className="h-8 w-12 rounded-md border"
              style={{ background: c, boxShadow: active === c ? "0 0 0 3px rgba(0,0,0,.2) inset" : undefined }}
            />
          ))}
        </div>
        <div className="text-sm opacity-70">Paint color</div>
        <button
          onClick={() => setColors(letters.map(() => active))}
          className="rounded-md border px-3 py-1 text-sm"
        >
          Fill all with active
        </button>
        <button
          onClick={downloadSvg}
          className="rounded-md border px-3 py-1 text-sm"
        >
          Download SVG
        </button>
      </div>

      {/* editable preview (HTML spans), keeps your Google Font */}
      <div
        className="rounded-xl border p-4"
        style={{
          lineHeight,
          fontFamily: `'${fontFamily}', system-ui, sans-serif`,
          fontSize,
          userSelect: "none",
          cursor: "pointer",
          background:
            "linear-gradient(45deg, #eee 25%, transparent 25%), linear-gradient(-45deg, #eee 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #eee 75%), linear-gradient(-45deg, transparent 75%, #eee 75%)",
          backgroundSize: "20px 20px",
          backgroundPosition: "0 0, 0 10px, 10px -10px, -10px 0px", // checkerboard
        }}
      >
        {letters.map((ch, i) => (
          <span
            key={i}
            onMouseDown={() => onLetterDown(i)}
            onMouseEnter={() => onLetterEnter(i)}
            style={{ color: colors[i], letterSpacing: `${letterSpacing}em` }}
          >
            {ch}
          </span>
        ))}
      </div>
      <p className="text-xs opacity-70">
        Tip: click or click-drag to “paint” letters with the active color. Uses your selected Google Font for a true preview.
      </p>
    </div>
  );
}

/* --- helpers --- */
function cssEscape(s: string) {
  return s.replace(/['"]/g, "");
}
function escapeAttr(s: string) {
  return s.replace(/"/g, "&quot;");
}
function escapeText(s: string) {
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function fileNameFrom(s: string) {
  return (s || "logo").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
}


How it works

Preview is HTML <span> per letter so users can click/drag paint with your current Google Font loaded.

Download SVG rebuilds the same thing as an SVG <text> with <tspan> per letter so colors are preserved in the exported file.

You keep your color swatches; just pass them as palette.

Use it

<PerLetterColorText
  text="Pawtopia"
  fontFamily="Poppins"          // must also be loaded via Google Fonts link
  palette={["#000000","#4274b9","#bd4b43","#3ab7bd","#9b7cf5","#222","#777"]}
/>
