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:
4Ventures
2026-10-15 03:54:48 +02:00
parent de546e00e0
commit 49d3529525

View File

@@ -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,7 +756,13 @@ export default function BrandZone({
)} )}
{section === 'identidad' && ( {section === 'identidad' && (
<div className="grid gap-3 sm:grid-cols-2"> <div className={sectionStack}>
<PanelCard
eyebrow="Esencia"
title="Quién es la marca"
description="Nombre, tagline y pitch: lo primero que debe leerse con claridad."
accent="brand"
>
<Field label="Nombre de marca"> <Field label="Nombre de marca">
<input <input
className={inputCls} className={inputCls}
@@ -701,7 +780,7 @@ export default function BrandZone({
<Field <Field
label="Elevator pitch (30s)" label="Elevator pitch (30s)"
hint="Una frase que un desconocido entiende" hint="Una frase que un desconocido entiende"
className="sm:col-span-2" wide
> >
<textarea <textarea
className={areaCls} className={areaCls}
@@ -723,6 +802,13 @@ export default function BrandZone({
onChange={(e) => patch({ positioning: e.target.value })} onChange={(e) => patch({ positioning: e.target.value })}
/> />
</Field> </Field>
</PanelCard>
<PanelCard
eyebrow="Marco"
title="Misión, visión y valores"
description="Marco estratégico que sostiene el tono y las campañas."
>
<Field label="Arquetipo" hint="Jung / marca (Sabio, Héroe…)"> <Field label="Arquetipo" hint="Jung / marca (Sabio, Héroe…)">
<input <input
className={inputCls} className={inputCls}
@@ -767,7 +853,7 @@ export default function BrandZone({
onChange={(e) => patch({ origin_story: e.target.value })} onChange={(e) => patch({ origin_story: e.target.value })}
/> />
</Field> </Field>
<Field label="Sitio Web"> <Field label="Sitio web" wide>
<input <input
className={inputCls} className={inputCls}
value={book.website || ''} value={book.website || ''}
@@ -775,10 +861,18 @@ export default function BrandZone({
onChange={(e) => patch({ website: e.target.value })} onChange={(e) => patch({ website: e.target.value })}
/> />
</Field> </Field>
</PanelCard>
<PanelCard
eyebrow="Negocio"
title="Oferta B2B / B2C y foco comercial"
description="Qué vende cada línea y qué priorizamos ahora."
accent="sky"
>
<Field <Field
label="Oferta y programas B2B (empresas)" label="Oferta y programas B2B (empresas)"
hint="Programas in-company, formación equipos, acreditaciones oficiales" hint="In-company, equipos, acreditaciones"
className="sm:col-span-2" wide
> >
<textarea <textarea
className={areaCls} className={areaCls}
@@ -788,8 +882,8 @@ export default function BrandZone({
</Field> </Field>
<Field <Field
label="Oferta y cursos B2C (particulares)" label="Oferta y cursos B2C (particulares)"
hint="Cursos adultos, intensivos, certificaciones Cambridge/Aptis, 1to1" hint="Adultos, intensivos, certificaciones"
className="sm:col-span-2" wide
> >
<textarea <textarea
className={areaCls} className={areaCls}
@@ -798,32 +892,43 @@ export default function BrandZone({
/> />
</Field> </Field>
<Field <Field
label="Diferenciación clave (USPs frente a competencia)" label="Diferenciación clave (USPs)"
hint="Profesor fijo, sesiones completas, acreditación directa FUNDAE..." hint="Por qué ganar frente a alternativas"
className="sm:col-span-2" wide
> >
<textarea <textarea
className={areaCls} className={areaCls}
value={book.usp_differentiators || ''} value={book.usp_differentiators || ''}
onChange={(e) => patch({ usp_differentiators: e.target.value })} onChange={(e) =>
patch({ usp_differentiators: e.target.value })
}
/> />
</Field> </Field>
<Field <Field
label="Prioridades actuales y foco comercial" label="Prioridades actuales y foco comercial"
hint="Foco de captación prioritario para este periodo" hint="Captación prioritaria este periodo"
className="sm:col-span-2" wide
> >
<textarea <textarea
className={areaCls} className={areaCls}
value={book.current_priorities || ''} value={book.current_priorities || ''}
onChange={(e) => patch({ current_priorities: e.target.value })} onChange={(e) =>
patch({ current_priorities: e.target.value })
}
/> />
</Field> </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>
)} )}