feat(ui): apply brand identity colors, typography, and contextual toolbar

This commit is contained in:
2026-06-02 15:27:01 -05:00
parent f998e454fe
commit b7656cf8eb
6 changed files with 129 additions and 3 deletions
+4 -1
View File
@@ -3,7 +3,10 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Remix — Editor de Branding Automatizado</title>
<title>Bradly — Especialista en Contenido de Marca</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<meta name="description" content="Plataforma SaaS para automatizar el branding visual de tu empresa. Crea videos e imágenes de marca con un sistema de diseño estricto usando Remotion." />
<meta property="og:title" content="Remix — Editor de Branding Automatizado" />
<meta property="og:description" content="Automatiza tu branding visual con Design MD y Remotion." />
+2 -2
View File
@@ -85,9 +85,9 @@ export const StudioProperties: React.FC<StudioPropertiesProps> = ({
const isImageMode = outputFormat === 'image';
return (
<aside className="w-72 bg-neutral-900 border-l border-neutral-800/60 flex flex-col z-10 shrink-0 h-full" onClick={(e) => e.stopPropagation()}>
<aside className="w-72 bg-brand-blue/90 backdrop-blur-xl border-l border-brand-teal/20 flex flex-col z-10 shrink-0 h-full shadow-2xl shadow-brand-blue/50" onClick={(e) => e.stopPropagation()}>
{/* Properties section */}
<div className={isImageMode ? 'flex-1 min-h-0 flex flex-col border-b border-neutral-800' : 'flex-1 min-h-0 flex flex-col'}>
<div className={isImageMode ? 'flex-1 min-h-0 flex flex-col border-b border-brand-teal/20' : 'flex-1 min-h-0 flex flex-col'}>
{activeTool === 'transitions' ? (
<TransitionsPanel designMD={designMD} />
) : (selectedElementIds && selectedElementIds.size >= 2) ? (
@@ -241,6 +241,15 @@ export const ImageLayersPanel: React.FC<ImageLayersPanelProps> = ({
{getLabel(el)}
</p>
<div className="flex items-center gap-2 mt-0.5">
{el.isBrandElement ? (
<span className="text-[8px] text-brand-orange bg-brand-orange/10 px-1 rounded uppercase tracking-wider font-semibold">
🏷 Marca
</span>
) : (
<span className="text-[8px] text-brand-teal border border-brand-teal/30 border-dashed px-1 rounded uppercase tracking-wider font-semibold">
Editable
</span>
)}
<span className="text-[8px] text-neutral-600 uppercase tracking-wider font-semibold">
{TYPE_LABELS[el.type] || el.type}
</span>
+11
View File
@@ -19,6 +19,7 @@ import { TimelineMarkerList, TimelineMarker } from '../timeline/TimelineMarkerLi
import { ResponsivePreviewToggle } from '../ui/ResponsivePreviewToggle';
import { AutoSaveIndicator } from '../ui/AutoSaveIndicator';
import { CanvasGridOverlay } from '../ui/CanvasGridOverlay';
import { FloatingContextToolbar } from '../ui/FloatingContextToolbar';
import { useEditor } from '../../context/EditorContext';
import { useKeyboardShortcuts } from '../../hooks/useKeyboardShortcuts';
@@ -337,6 +338,16 @@ export const StudioEditor: React.FC<{ onAssetSaved?: (url: string) => void }> =
</div>
{/* Canvas Grid + Safe Zone Overlay */}
<CanvasGridOverlay showGrid={showGrid} showSafeZone={showSafeZone} width={1080} height={1080} />
{/* Contextual Floating Toolbar */}
<FloatingContextToolbar
element={timelineElements.find(el => el.id === selectedElementId) || null}
onDuplicate={handleDuplicate}
onDelete={handleDelete}
onLock={handleLock}
setTimelineElements={setTimelineElements}
/>
{/* Auto-save indicator */}
<AutoSaveIndicator lastSaved={lastSaved} />
</div>
@@ -0,0 +1,94 @@
import React from 'react';
import { motion, AnimatePresence } from 'motion/react';
import { Wand2, Copy, Trash2, Lock, Unlock, AlignCenter, AlignVerticalSpaceAround } from 'lucide-react';
import { TimelineElement } from '../../types';
import { removeImageBackground } from '../../utils/backgroundRemoval';
interface FloatingContextToolbarProps {
element: TimelineElement | null;
onDuplicate: (id: string) => void;
onDelete: (id: string) => void;
onLock: (id: string) => void;
setTimelineElements: React.Dispatch<React.SetStateAction<TimelineElement[]>>;
}
export const FloatingContextToolbar: React.FC<FloatingContextToolbarProps> = ({
element,
onDuplicate,
onDelete,
onLock,
setTimelineElements,
}) => {
const [isRemovingBg, setIsRemovingBg] = React.useState(false);
if (!element) return null;
const handleRemoveBg = async () => {
if (!element || element.type !== 'image') return;
setIsRemovingBg(true);
try {
const newUrl = await removeImageBackground(element.content);
setTimelineElements(prev => prev.map(el =>
el.id === element.id ? { ...el, content: newUrl } : el
));
} catch (error) {
alert('Error al extraer el sujeto. Inténtalo de nuevo.');
} finally {
setIsRemovingBg(false);
}
};
return (
<AnimatePresence>
<motion.div
initial={{ opacity: 0, y: 10, scale: 0.95 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 10, scale: 0.95 }}
className="absolute bottom-4 left-1/2 -translate-x-1/2 z-50 flex items-center gap-1 p-1 bg-brand-blue/80 backdrop-blur-xl border border-brand-teal/20 rounded-xl shadow-2xl"
>
{element.type === 'image' && (
<button
onClick={handleRemoveBg}
disabled={isRemovingBg}
title="Extracción IA (Smart Mask)"
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all ${
isRemovingBg
? 'text-neutral-400 bg-neutral-800 cursor-wait'
: 'text-white bg-brand-teal hover:bg-brand-teal/80 shadow-lg shadow-brand-teal/20'
}`}
>
<Wand2 className={`w-3.5 h-3.5 ${isRemovingBg ? 'animate-pulse' : ''}`} />
{isRemovingBg ? 'Extrayendo...' : 'Extracción IA'}
</button>
)}
<div className="w-px h-4 bg-brand-gray/20 mx-1" />
<button
onClick={() => onDuplicate(element.id)}
title="Duplicar elemento"
className="p-1.5 text-brand-gray hover:text-white hover:bg-white/10 rounded-lg transition-colors"
>
<Copy className="w-4 h-4" />
</button>
<button
onClick={() => onLock(element.id)}
title={element.isLocked ? "Desbloquear" : "Bloquear"}
className="p-1.5 text-brand-gray hover:text-white hover:bg-white/10 rounded-lg transition-colors"
>
{element.isLocked ? <Lock className="w-4 h-4 text-brand-orange" /> : <Unlock className="w-4 h-4" />}
</button>
<button
onClick={() => onDelete(element.id)}
title="Eliminar elemento"
className="p-1.5 text-brand-gray hover:text-red-400 hover:bg-red-400/10 rounded-lg transition-colors"
>
<Trash2 className="w-4 h-4" />
</button>
</motion.div>
</AnimatePresence>
);
};
+9
View File
@@ -1,5 +1,14 @@
@import "tailwindcss";
@theme {
--color-brand-blue: #0D2C54;
--color-brand-teal: #00A69C;
--color-brand-orange: #F9A826;
--color-brand-gray: #E0E0E0;
--font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
}
/* Brand Content Editor input utility */
.input-sm {
width: 100%;