fix(ui): paneles Zona de marca tipo Overview/Prohibido
Cards con eyebrow, tipografía sm legible, más aire y agrupación por bloques en Identidad, Voz, Visual, Ads, Canales y Compliance. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -79,27 +79,93 @@ function Field({
|
|||||||
hint,
|
hint,
|
||||||
children,
|
children,
|
||||||
className = '',
|
className = '',
|
||||||
|
wide = false,
|
||||||
}: {
|
}: {
|
||||||
label: string;
|
label: string;
|
||||||
hint?: string;
|
hint?: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
/** Ocupa las 2 columnas del grid del PanelCard */
|
||||||
|
wide?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={`block ${className}`}>
|
<div className={`block min-w-0 ${wide ? 'sm:col-span-2' : ''} ${className}`}>
|
||||||
<label className="block text-xs font-bold text-gray-700 mb-1">{label}</label>
|
<label className="block text-[13px] font-semibold text-gray-900 mb-1.5 leading-snug">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
{hint ? (
|
{hint ? (
|
||||||
<p className="text-[11px] text-gray-500 leading-snug mb-1.5">{hint}</p>
|
<p className="text-xs text-gray-500 leading-relaxed mb-2">{hint}</p>
|
||||||
) : null}
|
) : null}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Bloque tipo Overview / Prohibido: aire, eyebrow, título y grid legible */
|
||||||
|
function PanelCard({
|
||||||
|
eyebrow,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
children,
|
||||||
|
accent = 'neutral',
|
||||||
|
cols = 2,
|
||||||
|
}: {
|
||||||
|
eyebrow?: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
accent?: 'neutral' | 'brand' | 'amber' | 'sky' | 'rose';
|
||||||
|
cols?: 1 | 2;
|
||||||
|
}) {
|
||||||
|
const tone =
|
||||||
|
accent === 'brand'
|
||||||
|
? 'border-brand-200/80 bg-gradient-to-br from-brand-50/70 via-white to-white'
|
||||||
|
: accent === 'amber'
|
||||||
|
? 'border-amber-200/80 bg-gradient-to-br from-amber-50/60 via-white to-white'
|
||||||
|
: accent === 'sky'
|
||||||
|
? 'border-sky-200/80 bg-gradient-to-br from-sky-50/60 via-white to-white'
|
||||||
|
: accent === 'rose'
|
||||||
|
? 'border-rose-200/80 bg-gradient-to-br from-rose-50/50 via-white to-white'
|
||||||
|
: 'border-gray-200 bg-gradient-to-br from-gray-50/80 via-white to-white';
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
className={`rounded-2xl border ${tone} px-5 py-5 sm:px-6 sm:py-6 shadow-xs`}
|
||||||
|
>
|
||||||
|
<div className="mb-4 pb-3 border-b border-black/[0.04]">
|
||||||
|
{eyebrow ? (
|
||||||
|
<p className="text-[10px] font-bold uppercase tracking-[0.16em] text-gray-500">
|
||||||
|
{eyebrow}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
<h4
|
||||||
|
className={`text-[15px] font-bold text-gray-900 tracking-tight ${
|
||||||
|
eyebrow ? 'mt-1' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</h4>
|
||||||
|
{description ? (
|
||||||
|
<p className="text-xs text-gray-500 mt-1.5 leading-relaxed max-w-2xl">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
cols === 1 ? 'space-y-4' : 'grid gap-4 sm:grid-cols-2 sm:gap-x-5 sm:gap-y-5'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const inputCls =
|
const inputCls =
|
||||||
'w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-xs text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-brand-200';
|
'w-full rounded-xl border border-gray-200 bg-white px-3.5 py-2.5 text-sm text-gray-900 placeholder:text-gray-400 shadow-xs focus:outline-none focus:border-brand-400 focus:ring-2 focus:ring-brand-100 transition-colors';
|
||||||
const areaCls = `${inputCls} min-h-[88px] resize-y`;
|
const areaCls = `${inputCls} min-h-[104px] resize-y leading-relaxed`;
|
||||||
const selectCls = inputCls;
|
const selectCls = inputCls;
|
||||||
|
const sectionStack = 'space-y-5';
|
||||||
|
|
||||||
function KitThumb({ assetFileId, alt }: { assetFileId: string; alt: string }) {
|
function KitThumb({ assetFileId, alt }: { assetFileId: string; alt: string }) {
|
||||||
const [url, setUrl] = useState<string | null>(null);
|
const [url, setUrl] = useState<string | null>(null);
|
||||||
@@ -555,7 +621,7 @@ export default function BrandZone({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className={sectionStack}>
|
||||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between rounded-2xl border border-brand-200 bg-brand-50/50 p-4">
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between rounded-2xl border border-brand-200 bg-brand-50/50 p-4">
|
||||||
<div className="flex items-start gap-3 min-w-0">
|
<div className="flex items-start gap-3 min-w-0">
|
||||||
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-brand-600 text-white shadow-xs">
|
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-brand-600 text-white shadow-xs">
|
||||||
@@ -662,11 +728,18 @@ export default function BrandZone({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="lg:col-span-9 rounded-2xl border border-gray-200 bg-white p-6 shadow-xs space-y-6">
|
<div className="lg:col-span-9 rounded-2xl border border-gray-200 bg-white p-5 sm:p-8 shadow-xs space-y-6 min-h-[520px]">
|
||||||
{section !== 'overview' ? (
|
{section !== 'overview' ? (
|
||||||
<div>
|
<div className="pb-1">
|
||||||
<h3 className="text-base font-bold text-gray-900">{secMeta?.label}</h3>
|
<p className="text-[10px] font-bold uppercase tracking-[0.16em] text-brand-600">
|
||||||
<p className="text-xs text-gray-500 mt-0.5">{secMeta?.subtitle}</p>
|
Zona de marca
|
||||||
|
</p>
|
||||||
|
<h3 className="text-lg font-bold text-gray-900 tracking-tight mt-1">
|
||||||
|
{secMeta?.label}
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-gray-500 mt-1.5 leading-relaxed max-w-2xl">
|
||||||
|
{secMeta?.subtitle}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
@@ -683,147 +756,179 @@ export default function BrandZone({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{section === 'identidad' && (
|
{section === 'identidad' && (
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className={sectionStack}>
|
||||||
<Field label="Nombre de marca">
|
<PanelCard
|
||||||
<input
|
eyebrow="Esencia"
|
||||||
className={inputCls}
|
title="Quién es la marca"
|
||||||
value={book.brand_name}
|
description="Nombre, tagline y pitch: lo primero que debe leerse con claridad."
|
||||||
onChange={(e) => patch({ brand_name: e.target.value })}
|
accent="brand"
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
<Field label="Tagline">
|
|
||||||
<input
|
|
||||||
className={inputCls}
|
|
||||||
value={book.tagline}
|
|
||||||
onChange={(e) => patch({ tagline: e.target.value })}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
<Field
|
|
||||||
label="Elevator pitch (30s)"
|
|
||||||
hint="Una frase que un desconocido entiende"
|
|
||||||
className="sm:col-span-2"
|
|
||||||
>
|
>
|
||||||
<textarea
|
<Field label="Nombre de marca">
|
||||||
className={areaCls}
|
<input
|
||||||
value={book.elevator_pitch}
|
className={inputCls}
|
||||||
onChange={(e) => patch({ elevator_pitch: e.target.value })}
|
value={book.brand_name}
|
||||||
/>
|
onChange={(e) => patch({ brand_name: e.target.value })}
|
||||||
</Field>
|
/>
|
||||||
<Field label="Promesa de marca">
|
</Field>
|
||||||
<textarea
|
<Field label="Tagline">
|
||||||
className={areaCls}
|
<input
|
||||||
value={book.brand_promise}
|
className={inputCls}
|
||||||
onChange={(e) => patch({ brand_promise: e.target.value })}
|
value={book.tagline}
|
||||||
/>
|
onChange={(e) => patch({ tagline: e.target.value })}
|
||||||
</Field>
|
/>
|
||||||
<Field label="Posicionamiento">
|
</Field>
|
||||||
<textarea
|
<Field
|
||||||
className={areaCls}
|
label="Elevator pitch (30s)"
|
||||||
value={book.positioning}
|
hint="Una frase que un desconocido entiende"
|
||||||
onChange={(e) => patch({ positioning: e.target.value })}
|
wide
|
||||||
/>
|
>
|
||||||
</Field>
|
<textarea
|
||||||
<Field label="Arquetipo" hint="Jung / marca (Sabio, Héroe…)">
|
className={areaCls}
|
||||||
<input
|
value={book.elevator_pitch}
|
||||||
className={inputCls}
|
onChange={(e) => patch({ elevator_pitch: e.target.value })}
|
||||||
value={book.archetype}
|
/>
|
||||||
onChange={(e) => patch({ archetype: e.target.value })}
|
</Field>
|
||||||
/>
|
<Field label="Promesa de marca">
|
||||||
</Field>
|
<textarea
|
||||||
<Field label="Vertical / sector">
|
className={areaCls}
|
||||||
<input
|
value={book.brand_promise}
|
||||||
className={inputCls}
|
onChange={(e) => patch({ brand_promise: e.target.value })}
|
||||||
value={book.vertical}
|
/>
|
||||||
onChange={(e) => patch({ vertical: e.target.value })}
|
</Field>
|
||||||
/>
|
<Field label="Posicionamiento">
|
||||||
</Field>
|
<textarea
|
||||||
<Field label="Misión">
|
className={areaCls}
|
||||||
<textarea
|
value={book.positioning}
|
||||||
className={areaCls}
|
onChange={(e) => patch({ positioning: e.target.value })}
|
||||||
value={book.mission}
|
/>
|
||||||
onChange={(e) => patch({ mission: e.target.value })}
|
</Field>
|
||||||
/>
|
</PanelCard>
|
||||||
</Field>
|
|
||||||
<Field label="Visión">
|
<PanelCard
|
||||||
<textarea
|
eyebrow="Marco"
|
||||||
className={areaCls}
|
title="Misión, visión y valores"
|
||||||
value={book.vision}
|
description="Marco estratégico que sostiene el tono y las campañas."
|
||||||
onChange={(e) => patch({ vision: e.target.value })}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
<Field label="Valores (uno por línea)">
|
|
||||||
<textarea
|
|
||||||
className={areaCls}
|
|
||||||
value={listToLines(book.values)}
|
|
||||||
onChange={(e) =>
|
|
||||||
patch({ values: linesToList(e.target.value) })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
<Field label="Historia / origen">
|
|
||||||
<textarea
|
|
||||||
className={areaCls}
|
|
||||||
value={book.origin_story}
|
|
||||||
onChange={(e) => patch({ origin_story: e.target.value })}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
<Field label="Sitio Web">
|
|
||||||
<input
|
|
||||||
className={inputCls}
|
|
||||||
value={book.website || ''}
|
|
||||||
placeholder="ej. blueparrotschool.com"
|
|
||||||
onChange={(e) => patch({ website: e.target.value })}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
<Field
|
|
||||||
label="Oferta y programas B2B (empresas)"
|
|
||||||
hint="Programas in-company, formación equipos, acreditaciones oficiales"
|
|
||||||
className="sm:col-span-2"
|
|
||||||
>
|
>
|
||||||
<textarea
|
<Field label="Arquetipo" hint="Jung / marca (Sabio, Héroe…)">
|
||||||
className={areaCls}
|
<input
|
||||||
value={book.b2b_offering || ''}
|
className={inputCls}
|
||||||
onChange={(e) => patch({ b2b_offering: e.target.value })}
|
value={book.archetype}
|
||||||
/>
|
onChange={(e) => patch({ archetype: e.target.value })}
|
||||||
</Field>
|
/>
|
||||||
<Field
|
</Field>
|
||||||
label="Oferta y cursos B2C (particulares)"
|
<Field label="Vertical / sector">
|
||||||
hint="Cursos adultos, intensivos, certificaciones Cambridge/Aptis, 1to1"
|
<input
|
||||||
className="sm:col-span-2"
|
className={inputCls}
|
||||||
|
value={book.vertical}
|
||||||
|
onChange={(e) => patch({ vertical: e.target.value })}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="Misión">
|
||||||
|
<textarea
|
||||||
|
className={areaCls}
|
||||||
|
value={book.mission}
|
||||||
|
onChange={(e) => patch({ mission: e.target.value })}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="Visión">
|
||||||
|
<textarea
|
||||||
|
className={areaCls}
|
||||||
|
value={book.vision}
|
||||||
|
onChange={(e) => patch({ vision: e.target.value })}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="Valores (uno por línea)">
|
||||||
|
<textarea
|
||||||
|
className={areaCls}
|
||||||
|
value={listToLines(book.values)}
|
||||||
|
onChange={(e) =>
|
||||||
|
patch({ values: linesToList(e.target.value) })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="Historia / origen">
|
||||||
|
<textarea
|
||||||
|
className={areaCls}
|
||||||
|
value={book.origin_story}
|
||||||
|
onChange={(e) => patch({ origin_story: e.target.value })}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="Sitio web" wide>
|
||||||
|
<input
|
||||||
|
className={inputCls}
|
||||||
|
value={book.website || ''}
|
||||||
|
placeholder="ej. blueparrotschool.com"
|
||||||
|
onChange={(e) => patch({ website: e.target.value })}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</PanelCard>
|
||||||
|
|
||||||
|
<PanelCard
|
||||||
|
eyebrow="Negocio"
|
||||||
|
title="Oferta B2B / B2C y foco comercial"
|
||||||
|
description="Qué vende cada línea y qué priorizamos ahora."
|
||||||
|
accent="sky"
|
||||||
>
|
>
|
||||||
<textarea
|
<Field
|
||||||
className={areaCls}
|
label="Oferta y programas B2B (empresas)"
|
||||||
value={book.b2c_offering || ''}
|
hint="In-company, equipos, acreditaciones"
|
||||||
onChange={(e) => patch({ b2c_offering: e.target.value })}
|
wide
|
||||||
/>
|
>
|
||||||
</Field>
|
<textarea
|
||||||
<Field
|
className={areaCls}
|
||||||
label="Diferenciación clave (USPs frente a competencia)"
|
value={book.b2b_offering || ''}
|
||||||
hint="Profesor fijo, sesiones completas, acreditación directa FUNDAE..."
|
onChange={(e) => patch({ b2b_offering: e.target.value })}
|
||||||
className="sm:col-span-2"
|
/>
|
||||||
>
|
</Field>
|
||||||
<textarea
|
<Field
|
||||||
className={areaCls}
|
label="Oferta y cursos B2C (particulares)"
|
||||||
value={book.usp_differentiators || ''}
|
hint="Adultos, intensivos, certificaciones"
|
||||||
onChange={(e) => patch({ usp_differentiators: e.target.value })}
|
wide
|
||||||
/>
|
>
|
||||||
</Field>
|
<textarea
|
||||||
<Field
|
className={areaCls}
|
||||||
label="Prioridades actuales y foco comercial"
|
value={book.b2c_offering || ''}
|
||||||
hint="Foco de captación prioritario para este periodo"
|
onChange={(e) => patch({ b2c_offering: e.target.value })}
|
||||||
className="sm:col-span-2"
|
/>
|
||||||
>
|
</Field>
|
||||||
<textarea
|
<Field
|
||||||
className={areaCls}
|
label="Diferenciación clave (USPs)"
|
||||||
value={book.current_priorities || ''}
|
hint="Por qué ganar frente a alternativas"
|
||||||
onChange={(e) => patch({ current_priorities: e.target.value })}
|
wide
|
||||||
/>
|
>
|
||||||
</Field>
|
<textarea
|
||||||
|
className={areaCls}
|
||||||
|
value={book.usp_differentiators || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
patch({ usp_differentiators: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field
|
||||||
|
label="Prioridades actuales y foco comercial"
|
||||||
|
hint="Captación prioritaria este periodo"
|
||||||
|
wide
|
||||||
|
>
|
||||||
|
<textarea
|
||||||
|
className={areaCls}
|
||||||
|
value={book.current_priorities || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
patch({ current_priorities: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</PanelCard>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{section === 'voz' && (
|
{section === 'voz' && (
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className={sectionStack}>
|
||||||
|
<PanelCard
|
||||||
|
eyebrow="Voz"
|
||||||
|
title="Cómo suena la marca"
|
||||||
|
description="Tono, do/don’t y ejemplos reales"
|
||||||
|
accent="brand"
|
||||||
|
>
|
||||||
<Field label="Tono" className="sm:col-span-2">
|
<Field label="Tono" className="sm:col-span-2">
|
||||||
<textarea
|
<textarea
|
||||||
className={areaCls}
|
className={areaCls}
|
||||||
@@ -954,28 +1059,107 @@ export default function BrandZone({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
</PanelCard>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{section === 'visual' && (
|
{section === 'visual' && (
|
||||||
<div className="space-y-6">
|
<div className={sectionStack}>
|
||||||
<div>
|
<PanelCard
|
||||||
<div className="flex items-center justify-between mb-2">
|
eyebrow="Upload"
|
||||||
<p className="text-sm font-semibold text-gray-900 inline-flex items-center gap-1.5">
|
title="Añadir al kit"
|
||||||
<Palette className="w-4 h-4 text-rose-500" />
|
description="Logos, tipografías, patrones e imágenes oficiales."
|
||||||
Colores
|
accent="neutral"
|
||||||
|
cols={1}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col md:flex-row md:items-end gap-3">
|
||||||
|
<Field label="Tipo" className="md:w-40">
|
||||||
|
<select
|
||||||
|
className={selectCls}
|
||||||
|
value={kitKind}
|
||||||
|
onChange={(e) => {
|
||||||
|
const k = e.target.value as BrandAssetKind;
|
||||||
|
setKitKind(k);
|
||||||
|
setKitRole(
|
||||||
|
k === 'logo'
|
||||||
|
? 'primary'
|
||||||
|
: k === 'font'
|
||||||
|
? 'body'
|
||||||
|
: k === 'image'
|
||||||
|
? 'moodboard'
|
||||||
|
: 'asset'
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.entries(ASSET_KIND_LABELS).map(([k, v]) => (
|
||||||
|
<option key={k} value={k}>
|
||||||
|
{v}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</Field>
|
||||||
|
<Field label="Rol" className="md:w-44" hint="ej. primary, dark">
|
||||||
|
<input
|
||||||
|
className={inputCls}
|
||||||
|
value={kitRole}
|
||||||
|
onChange={(e) => setKitRole(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<label className="inline-flex items-center justify-center gap-1.5 rounded-xl bg-gray-900 px-4 py-2.5 text-xs font-bold text-white cursor-pointer hover:bg-gray-800">
|
||||||
|
{kitUploading ? (
|
||||||
|
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Plus className="w-3.5 h-3.5" />
|
||||||
|
)}
|
||||||
|
Subir archivo
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
className="hidden"
|
||||||
|
accept={
|
||||||
|
kitKind === 'logo' ||
|
||||||
|
kitKind === 'image' ||
|
||||||
|
kitKind === 'icon'
|
||||||
|
? 'image/*,.svg,.pdf'
|
||||||
|
: kitKind === 'font'
|
||||||
|
? '.ttf,.otf,.woff,.woff2,.zip'
|
||||||
|
: kitKind === 'video'
|
||||||
|
? 'video/*'
|
||||||
|
: '*/*'
|
||||||
|
}
|
||||||
|
disabled={kitUploading}
|
||||||
|
onChange={(e) => {
|
||||||
|
const f = e.target.files?.[0];
|
||||||
|
if (f) void uploadKit(f);
|
||||||
|
e.target.value = '';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</PanelCard>
|
||||||
|
|
||||||
|
<PanelCard
|
||||||
|
eyebrow="Paleta"
|
||||||
|
title="Colores de marca"
|
||||||
|
description="Tokens HEX con rol y uso. Misma claridad que el overview."
|
||||||
|
accent="brand"
|
||||||
|
cols={1}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<p className="text-sm font-semibold text-gray-800 inline-flex items-center gap-1.5">
|
||||||
|
<Palette className="w-4 h-4 text-brand-600" />
|
||||||
|
Tokens de color
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={addColor}
|
onClick={addColor}
|
||||||
className="text-xs font-semibold text-brand-700 inline-flex items-center gap-1"
|
className="inline-flex items-center gap-1 rounded-lg bg-brand-50 px-2.5 py-1.5 text-xs font-semibold text-brand-800 hover:bg-brand-100"
|
||||||
>
|
>
|
||||||
<Plus className="w-3.5 h-3.5" /> Añadir color
|
<Plus className="w-3.5 h-3.5" /> Añadir color
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2.5">
|
||||||
{book.colors.length === 0 ? (
|
{book.colors.length === 0 ? (
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-sm text-gray-500 italic">
|
||||||
Añade tokens de color con picker o pega un #hex.
|
Añade tokens de color con picker o pega un #hex.
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -1061,9 +1245,14 @@ export default function BrandZone({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</PanelCard>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<PanelCard
|
||||||
|
eyebrow="Tipografía"
|
||||||
|
title="Fuentes del kit"
|
||||||
|
description="Headline y body con la misma jerarquía visual que el overview."
|
||||||
|
cols={1}
|
||||||
|
>
|
||||||
<GoogleFontsGallery
|
<GoogleFontsGallery
|
||||||
selectedFamilies={book.fonts_kit.map((f) => f.family)}
|
selectedFamilies={book.fonts_kit.map((f) => f.family)}
|
||||||
onPick={(font, pickRole) => {
|
onPick={(font, pickRole) => {
|
||||||
@@ -1093,15 +1282,15 @@ export default function BrandZone({
|
|||||||
setOkMsg(`${font.family} añadida al kit (${pickRole})`);
|
setOkMsg(`${font.family} añadida al kit (${pickRole})`);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-sm font-semibold text-gray-900 inline-flex items-center gap-1.5">
|
<p className="text-sm font-semibold text-gray-800 inline-flex items-center gap-1.5">
|
||||||
<Type className="w-4 h-4 text-rose-500" />
|
<Type className="w-4 h-4 text-brand-600" />
|
||||||
Tipografías del kit
|
Tipografías del kit
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={addFont}
|
onClick={addFont}
|
||||||
className="text-xs font-semibold text-brand-700 inline-flex items-center gap-1"
|
className="inline-flex items-center gap-1 rounded-lg bg-brand-50 px-2.5 py-1.5 text-xs font-semibold text-brand-800 hover:bg-brand-100"
|
||||||
>
|
>
|
||||||
<Plus className="w-3.5 h-3.5" /> Manual
|
<Plus className="w-3.5 h-3.5" /> Manual
|
||||||
</button>
|
</button>
|
||||||
@@ -1199,13 +1388,17 @@ export default function BrandZone({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<p className="text-[11px] text-gray-500 mt-2">
|
<p className="text-xs text-gray-500">
|
||||||
Para archivos .ttf/.otf/.woff usa la barra «Añadir al kit» con
|
Para archivos .ttf/.otf/.woff usa la barra «Añadir al kit» con
|
||||||
tipo Tipografía.
|
tipo Tipografía.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</PanelCard>
|
||||||
|
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<PanelCard
|
||||||
|
eyebrow="Estilo"
|
||||||
|
title="Logo, foto y reglas de composición"
|
||||||
|
description="Cómo se ve la marca en creatividades y layouts."
|
||||||
|
>
|
||||||
<Field label="Uso de logo">
|
<Field label="Uso de logo">
|
||||||
<textarea
|
<textarea
|
||||||
className={areaCls}
|
className={areaCls}
|
||||||
@@ -1284,7 +1477,7 @@ export default function BrandZone({
|
|||||||
onChange={(e) => patch({ typography_body: e.target.value })}
|
onChange={(e) => patch({ typography_body: e.target.value })}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="Reglas de texto en imagen" className="sm:col-span-2">
|
<Field label="Reglas de texto en imagen" wide>
|
||||||
<textarea
|
<textarea
|
||||||
className={areaCls}
|
className={areaCls}
|
||||||
value={book.image_text_rules || ''}
|
value={book.image_text_rules || ''}
|
||||||
@@ -1292,12 +1485,14 @@ export default function BrandZone({
|
|||||||
onChange={(e) => patch({ image_text_rules: e.target.value })}
|
onChange={(e) => patch({ image_text_rules: e.target.value })}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
</div>
|
</PanelCard>
|
||||||
|
|
||||||
<div>
|
<PanelCard
|
||||||
<p className="text-sm font-semibold text-gray-900 mb-2">
|
eyebrow="Kit"
|
||||||
Archivos del kit ({book.kit_assets.length})
|
title={`Archivos del kit (${book.kit_assets.length})`}
|
||||||
</p>
|
description="Logos, tipografías e imágenes subidos al brand kit."
|
||||||
|
cols={1}
|
||||||
|
>
|
||||||
{book.kit_assets.length === 0 ? (
|
{book.kit_assets.length === 0 ? (
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
Todavía no hay logos ni archivos. Usa «Añadir al kit»
|
Todavía no hay logos ni archivos. Usa «Añadir al kit»
|
||||||
@@ -1344,13 +1539,19 @@ export default function BrandZone({
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</div>
|
</PanelCard>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{section === 'audiencia' && (
|
{section === 'audiencia' && (
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className={sectionStack}>
|
||||||
<Field label="ICP" className="sm:col-span-2">
|
<PanelCard
|
||||||
|
eyebrow="ICP"
|
||||||
|
title="A quién hablamos"
|
||||||
|
description="Perfil ideal, geografía e idiomas — misma claridad que Overview."
|
||||||
|
accent="sky"
|
||||||
|
>
|
||||||
|
<Field label="ICP" wide>
|
||||||
<textarea
|
<textarea
|
||||||
className={areaCls}
|
className={areaCls}
|
||||||
value={book.icp}
|
value={book.icp}
|
||||||
@@ -1501,7 +1702,7 @@ export default function BrandZone({
|
|||||||
{(!book.b2b_personas || book.b2b_personas.length === 0) ? (
|
{(!book.b2b_personas || book.b2b_personas.length === 0) ? (
|
||||||
<p className="text-xs text-gray-400 italic">No hay buyer personas B2B definidas.</p>
|
<p className="text-xs text-gray-400 italic">No hay buyer personas B2B definidas.</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className="grid gap-4 sm:grid-cols-2 sm:gap-x-5 sm:gap-y-5">
|
||||||
{book.b2b_personas.map((p, idx) => (
|
{book.b2b_personas.map((p, idx) => (
|
||||||
<div key={idx} className="rounded-xl border border-gray-200 bg-gray-50/50 p-3 space-y-2">
|
<div key={idx} className="rounded-xl border border-gray-200 bg-gray-50/50 p-3 space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@@ -1622,7 +1823,7 @@ export default function BrandZone({
|
|||||||
{(!book.b2c_personas || book.b2c_personas.length === 0) ? (
|
{(!book.b2c_personas || book.b2c_personas.length === 0) ? (
|
||||||
<p className="text-xs text-gray-400 italic">No hay buyer personas B2C definidas.</p>
|
<p className="text-xs text-gray-400 italic">No hay buyer personas B2C definidas.</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className="grid gap-4 sm:grid-cols-2 sm:gap-x-5 sm:gap-y-5">
|
||||||
{book.b2c_personas.map((p, idx) => (
|
{book.b2c_personas.map((p, idx) => (
|
||||||
<div key={idx} className="rounded-xl border border-gray-200 bg-gray-50/50 p-3 space-y-2">
|
<div key={idx} className="rounded-xl border border-gray-200 bg-gray-50/50 p-3 space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@@ -1710,11 +1911,18 @@ export default function BrandZone({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</PanelCard>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{section === 'oferta' && (
|
{section === 'oferta' && (
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className={sectionStack}>
|
||||||
|
<PanelCard
|
||||||
|
eyebrow="Oferta"
|
||||||
|
title="Propuesta de valor"
|
||||||
|
description="Qué vendes, prueba y alternativas"
|
||||||
|
accent="sky"
|
||||||
|
>
|
||||||
<Field label="Propuesta de valor" className="sm:col-span-2">
|
<Field label="Propuesta de valor" className="sm:col-span-2">
|
||||||
<textarea
|
<textarea
|
||||||
className={areaCls}
|
className={areaCls}
|
||||||
@@ -1797,11 +2005,18 @@ export default function BrandZone({
|
|||||||
onChange={(e) => patch({ price_and_offer_framing: e.target.value })}
|
onChange={(e) => patch({ price_and_offer_framing: e.target.value })}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
</PanelCard>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{section === 'pilares' && (
|
{section === 'pilares' && (
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className={sectionStack}>
|
||||||
|
<PanelCard
|
||||||
|
eyebrow="Contenido"
|
||||||
|
title="Pilares y series"
|
||||||
|
description="Temas recurrentes y matriz editorial"
|
||||||
|
accent="neutral"
|
||||||
|
>
|
||||||
<Field label="Pilares RRSS">
|
<Field label="Pilares RRSS">
|
||||||
<textarea
|
<textarea
|
||||||
className={areaCls}
|
className={areaCls}
|
||||||
@@ -1855,12 +2070,18 @@ export default function BrandZone({
|
|||||||
placeholder="Pilar × formato × KPI…"
|
placeholder="Pilar × formato × KPI…"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
</PanelCard>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{section === 'canales' && (
|
{section === 'canales' && (
|
||||||
<div className="space-y-4">
|
<div className={sectionStack}>
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<PanelCard
|
||||||
|
eyebrow="Presencia"
|
||||||
|
title="Rol por canal"
|
||||||
|
description="Para qué usamos cada red y qué no debe mezclarse."
|
||||||
|
accent="sky"
|
||||||
|
>
|
||||||
{(
|
{(
|
||||||
[
|
[
|
||||||
['instagram', 'Instagram'],
|
['instagram', 'Instagram'],
|
||||||
@@ -1885,8 +2106,12 @@ export default function BrandZone({
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
))}
|
))}
|
||||||
</div>
|
</PanelCard>
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<PanelCard
|
||||||
|
eyebrow="Handles"
|
||||||
|
title="URLs y cadencia"
|
||||||
|
description="Enlaces oficiales y ritmo de publicación."
|
||||||
|
>
|
||||||
{(
|
{(
|
||||||
[
|
[
|
||||||
['instagram', 'Handle IG'],
|
['instagram', 'Handle IG'],
|
||||||
@@ -1929,12 +2154,18 @@ export default function BrandZone({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
</div>
|
</PanelCard>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{section === 'ads' && (
|
{section === 'ads' && (
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className={sectionStack}>
|
||||||
|
<PanelCard
|
||||||
|
eyebrow="Paid"
|
||||||
|
title="Anuncios que convierten"
|
||||||
|
description="USPs, hooks, CTAs y ángulos observados"
|
||||||
|
accent="amber"
|
||||||
|
>
|
||||||
<Field label="USPs">
|
<Field label="USPs">
|
||||||
<textarea
|
<textarea
|
||||||
className={areaCls}
|
className={areaCls}
|
||||||
@@ -2124,6 +2355,7 @@ export default function BrandZone({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</PanelCard>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -2381,7 +2613,7 @@ export default function BrandZone({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className="grid gap-4 sm:grid-cols-2 sm:gap-x-5 sm:gap-y-5">
|
||||||
<Field label="Temas LinkedIn">
|
<Field label="Temas LinkedIn">
|
||||||
<textarea
|
<textarea
|
||||||
className={areaCls}
|
className={areaCls}
|
||||||
@@ -2742,7 +2974,13 @@ export default function BrandZone({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{section === 'compliance' && (
|
{section === 'compliance' && (
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className={sectionStack}>
|
||||||
|
<PanelCard
|
||||||
|
eyebrow="Líneas rojas"
|
||||||
|
title="Prohibido y compliance"
|
||||||
|
description="Claims, competencia, emojis y testimonios"
|
||||||
|
accent="rose"
|
||||||
|
>
|
||||||
<Field label="Claims prohibidos">
|
<Field label="Claims prohibidos">
|
||||||
<textarea
|
<textarea
|
||||||
className={areaCls}
|
className={areaCls}
|
||||||
@@ -2820,6 +3058,7 @@ export default function BrandZone({
|
|||||||
onChange={(e) => patch({ testimonials_policy: e.target.value })}
|
onChange={(e) => patch({ testimonials_policy: e.target.value })}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
</PanelCard>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user