no nuking the page this time. We’ll keep everything (template background, existing UI), just swap the rotate UX from hover-handles → sliders. Here’s a surgical patch you can apply to your existing AdminUploadLogoTemplate.tsx (the big original file you sent). Zero layout loss.

1) Keep your background & preview intact (no changes needed)

Your <img src={previewImageUrl} … /> blocks are good — don’t touch them.

2) Disable the hover/handle UI (do NOT delete code; just gate it off)

Find each place like this (you have one for brand-name, tagline, est-year in both PNG and SVG branches):

{/* Rotation Handle */}
{(hoveredTextId === 'brand-name' || isRotating === 'brand-name') && (
  <div
    className="absolute cursor-pointer"
    ...
    onMouseDown={(e) => handleRotationHandleMouseDown(e, 'brand-name')}
  >
    ↻
  </div>
)}


Change the condition to hard-disable:

{/* Rotation Handle (disabled; using sliders now) */}
{false && (hoveredTextId === 'brand-name' || isRotating === 'brand-name') && (
  <div
    className="absolute cursor-pointer"
    ...
    onMouseDown={(e) => handleRotationHandleMouseDown(e, 'brand-name')}
  >
    ↻
  </div>
)}


Repeat for tagline and est-year (both PNG and SVG overlay sections).
This keeps the old code around (safe) but makes it inert.

You can also kill the hover listener if you want (optional), but not required:

If you see an effect that adds mousemove + elementFromPoint(...).closest('[data-text-id]') just leave it; it won’t render handles anyway.

3) Rotation stays in state (you already have it)

You already have:

const [textRotations, setTextRotations] = useState<{ [key: string]: number }>({
  'brand-name': 0, 'tagline': 0, 'est-year': 0
});


✅ Keep this as-is.

Ensure your positioned wrappers apply the rotation:

style={{
  left: ..., top: ...,
  transform: `rotate(${textRotations['brand-name']}deg)`,
  transformOrigin: 'center center',
  pointerEvents: 'auto'
}}


(From your file, you already do this for each item. Leave it.)

4) Add sliders under your existing Text Controls (right panel)

Find the “Text Controls” section where you render:

<TextControlWithCheckbox
  label="Company/Brand Name"
  value={textControls.brandName}
  checked={textControlsVisibility.showBrandName}
  onValueChange={(value) => setTextControls(prev => ({ ...prev, brandName: value }))}
  onCheckedChange={(checked) => setTextControlsVisibility(prev => ({ ...prev, showBrandName: checked }))}
  testId="brand-name"
/>


👉 Immediately after each TextControlWithCheckbox, insert a slider block:

Brand Name slider
{/* Rotation slider: Brand Name */}
{textControlsVisibility.showBrandName && (
  <div className="mt-2">
    <div className="flex items-center justify-between text-sm text-gray-600 mb-1">
      <span>Rotation (Brand Name)</span>
      <span className="font-mono">{Math.round(textRotations['brand-name'] || 0)}°</span>
    </div>
    <input
      type="range"
      min={-180}
      max={180}
      step={1}
      value={textRotations['brand-name'] || 0}
      onChange={(e) =>
        setTextRotations((prev) => ({ ...prev, ['brand-name']: Number(e.target.value) }))
      }
      className="w-full"
    />
    <div className="flex justify-between text-xs text-gray-400">
      <span>-180°</span><span>0°</span><span>+180°</span>
    </div>
  </div>
)}

Tagline slider

Place after the “Default Tagline” control:

{/* Rotation slider: Tagline */}
{textControlsVisibility.showTagline && (
  <div className="mt-2">
    <div className="flex items-center justify-between text-sm text-gray-600 mb-1">
      <span>Rotation (Tagline)</span>
      <span className="font-mono">{Math.round(textRotations['tagline'] || 0)}°</span>
    </div>
    <input
      type="range"
      min={-180}
      max={180}
      step={1}
      value={textRotations['tagline'] || 0}
      onChange={(e) =>
        setTextRotations((prev) => ({ ...prev, ['tagline']: Number(e.target.value) }))
      }
      className="w-full"
    />
    <div className="flex justify-between text-xs text-gray-400">
      <span>-180°</span><span>0°</span><span>+180°</span>
    </div>
  </div>
)}

Est. Year slider

Place after the Est. Year control:

{/* Rotation slider: Est. Year */}
{textControlsVisibility.showEstYear && (
  <div className="mt-2">
    <div className="flex items-center justify-between text-sm text-gray-600 mb-1">
      <span>Rotation (Est. Year)</span>
      <span className="font-mono">{Math.round(textRotations['est-year'] || 0)}°</span>
    </div>
    <input
      type="range"
      min={-180}
      max={180}
      step={1}
      value={textRotations['est-year'] || 0}
      onChange={(e) =>
        setTextRotations((prev) => ({ ...prev, ['est-year']: Number(e.target.value) }))
      }
      className="w-full"
    />
    <div className="flex justify-between text-xs text-gray-400">
      <span>-180°</span><span>0°</span><span>+180°</span>
    </div>
  </div>
)}


That’s it. Those three inserts give you stable, live rotation without touching any of your preview/backdrop logic.

5) Keep drag-to-move working (no change)

Your onMouseDown={e => handleTextMouseDown(e, 'brand-name')} logic remains. The slider only changes rotation; moving still uses your drag code.

Quick sanity checklist

✅ Background/template shows (we didn’t touch previewImageUrl renders).

✅ Text still drags (we didn’t touch drag handlers).

✅ Rotation updates in real-time via sliders (we write textRotations[id]).

✅ No hover/watchers/handles needed (we gated them off with false &&).