// parts.jsx — reusable components & visuals for ALIAD site
// Exports to window: Logo, LogoMark, Icon, InstaTile, DotsField, PhotoSlot, Silhouette
// ─── ALIAD logo mark (the circular dots) ──────────────────────────────────
function LogoMark({ size = 56, blue = '#1F5673', gold = '#E5B73B' }) {
const dots = [];
const outer = [[200,150],[205,195],[185,235],[150,268],[110,285],[70,285],[40,270],[22,240]];
outer.forEach(([x,y],i) => dots.push({x,y,r:22-i*1.2,c:blue}));
const mid = [[205,110],[185,80],[155,60],[120,50],[85,55],[55,75],[38,105]];
mid.forEach(([x,y],i) => dots.push({x,y,r:14-i*0.6,c:i<3?gold:blue}));
const inner = [[165,95],[140,78],[115,75],[92,88],[78,110],[72,138],[80,165],[98,188],[122,200],[148,200],[170,188],[183,168],[188,145],[182,125]];
inner.forEach(([x,y],i) => dots.push({x,y,r:Math.max(2.5,9-i*0.45),c:gold}));
return (
);
}
function Logo({ size = 40, color = '#C25A30', subColor = '#C25A30', tagline = true }) {
return (
Association ALIAD
{tagline && (
Au service de tous les âges
)}
);
}
// ─── Service & UI icons ──────────────────────────────────────────────────
function Icon({ name, size = 44, color = 'currentColor' }) {
const s = { width:size, height:size, fill:'none', stroke:color, strokeWidth:1.6, strokeLinecap:'round', strokeLinejoin:'round' };
if (name === 'home') return ();
if (name === 'family') return ();
if (name === 'meal') return ();
if (name === 'mediation') return ();
if (name === 'wellbeing') return ();
if (name === 'youth') return ();
if (name === 'arrow') return ();
if (name === 'phone') return ();
if (name === 'mail') return ();
if (name === 'pin') return ();
if (name === 'clock') return ();
if (name === 'play') return ();
if (name === 'insta') return ();
if (name === 'fb') return ();
return null;
}
// ─── Decorative dot pattern ──────────────────────────────────────────────
function DotsField({ color = '#1F5673', opacity = 0.18, w = 260, h = 260 }) {
const dots = [];
const cols = 8, rows = 8;
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
const x = 14 + c*(w-28)/(cols-1);
const y = 14 + r*(h-28)/(rows-1);
const d = Math.hypot(c - cols+1, r - rows+1) / Math.hypot(cols-1, rows-1);
const rad = 1 + (1-d) * 5;
dots.push();
}
return ;
}
// ─── Silhouette illustrations (Afro-Caribbean figures, placeholders) ─────
// Soft, respectful, abstract silhouettes — warm brown skin tone, natural
// hair / head wraps, suggested smile. Stand in until real photos are dropped.
const SKIN_LIGHT = '#A87B5C';
const SKIN_MID = '#7E5238';
const SKIN_DEEP = '#5C3621';
const WRAP_A = '#C25A30'; // terracotta
const WRAP_B = '#E5B73B'; // gold
function Silhouette({ kind = 'elder-woman', bg = '#F8F1E1' }) {
// viewBox 400x400 (square). Components are positioned to look composed.
const figures = {
'elder-woman': (
{/* shoulders */}
{/* dress accent */}
{/* neck */}
{/* face */}
{/* head wrap (madras) */}
{/* knot */}
{/* eyes — gentle closed-smile arcs */}
{/* smile */}
{/* earrings */}
),
'elder-man': (
{/* shoulders */}
{/* shirt collar */}
{/* neck */}
{/* face */}
{/* hair — short white/grey beard suggestion */}
{/* grey hair specks */}
{/* short beard */}
{/* eyes */}
{/* smile */}
),
'grandma-child': (
{/* grandma — left */}
{/* child — right */}
{/* twin afro puffs */}
{/* face */}
),
'family': (
{/* back layer — father */}
{/* mother — right */}
{/* hair — natural afro */}
{/* child — front center */}
),
'caregiver': (
{/* elder — seated, left */}
{/* white hair */}
{/* caregiver — standing behind, right */}
{/* hair pulled back */}
{/* caring hand on shoulder — abstract */}
),
'ateliers': (
{/* table / surface */}
{/* three small figures around a table */}
{[
{x:90, hair:'#1A0F08', skin:SKIN_DEEP, top:WRAP_A, hairType:'afro'},
{x:200, hair:WRAP_A, skin:SKIN_LIGHT, top:'#234862', hairType:'wrap'},
{x:310, hair:'#1A0F08', skin:SKIN_MID, top:WRAP_B, hairType:'short'},
].map((f, i) => (
{f.hairType === 'afro' && }
{f.hairType === 'wrap' && }
{f.hairType === 'short' && }
))}
),
};
return (
);
}
// Convert a Silhouette to a data URL (for use as )
function silhouetteDataUrl(kind, bg) {
const tmpDiv = document.createElement('div');
ReactDOM.createRoot(tmpDiv).render();
// We can't easily serialize React-rendered SVG synchronously; instead
// render the SVG markup directly with a string builder. Use a manual
// approach.
return null; // not used — we render as the visible background instead
}
// ─── PhotoSlot — drop-zone with silhouette placeholder ──────────────────
// Renders the Silhouette underneath; layers an on top with a
// transparent placeholder so when the user drops a real photo it covers
// the silhouette cleanly.
function PhotoSlot({ slotId, kind = 'elder-woman', ratio = '4/3', label, tone = 'gold', style, radius = 18 }) {
const tones = {
gold: '#F8E9B8',
blue: '#CFE0EA',
terracotta: '#F0CFBE',
paper: '#F8F1E1',
};
const bg = tones[tone] || tones.gold;
return (
{/* silhouette fallback */}
{/* image-slot overlay — fills entire tile */}
{slotId && (
)}
{label && (
{label}
)}
);
}
// ─── Instagram-style tile ────────────────────────────────────────────────
function InstaTile({ slotId, kind, caption, date, postType = 'photo', tag }) {
return (
{tag || '@aliadassociation'}
{postType === 'video' && (
Reel
)}
);
}
Object.assign(window, { Logo, LogoMark, Icon, InstaTile, DotsField, PhotoSlot, Silhouette });