/* JewelLink EDU — shared primitives for the Parent App UI kit Exposes: Icon, Avatar, Pill, color tokens. */ function jlPascal(name){ return name.split(/[-_ ]/).map(s => s.charAt(0).toUpperCase()+s.slice(1)).join(''); } /* Lucide icon → inline SVG, built imperatively so React never fights it. */ function Icon({ name, size = 20, color = 'currentColor', stroke = 2, fill = 'none', style }) { const ref = React.useRef(null); React.useEffect(() => { const node = window.lucide && window.lucide.icons && window.lucide.icons[jlPascal(name)]; const host = ref.current; if (!host || !node) return; const ns = 'http://www.w3.org/2000/svg'; const svg = document.createElementNS(ns, 'svg'); svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('width', size); svg.setAttribute('height', size); svg.setAttribute('fill', fill); svg.setAttribute('stroke', color); svg.setAttribute('stroke-width', stroke); svg.setAttribute('stroke-linecap', 'round'); svg.setAttribute('stroke-linejoin', 'round'); node.forEach(([tag, attrs]) => { const child = document.createElementNS(ns, tag); for (const k in attrs) child.setAttribute(k, attrs[k]); svg.appendChild(child); }); host.innerHTML = ''; host.appendChild(svg); }, [name, size, color, stroke, fill]); return ; } /* Initials avatar — avoids shipping real faces, fully reusable. */ const JL_AV_COLORS = { blue:'var(--blue-500)', navy:'var(--navy-600)', gold:'var(--gold-500)', green:'var(--success-500)', red:'var(--danger-500)', cyan:'var(--cyan-500)', }; function Avatar({ initials, color = 'blue', size = 42, ring = false }) { const bg = JL_AV_COLORS[color] || color; return (
{initials}
); } function Pill({ children, bg='var(--gray-200)', fg='var(--gray-600)', style }) { return ( {children} ); } /* Lightweight toast — gives feedback for prototype actions that aren't wired to a full flow. */ function jlToast(msg, opts) { opts = opts || {}; let wrap = document.getElementById('jl-toast-wrap'); if (!wrap) { wrap = document.createElement('div'); wrap.id = 'jl-toast-wrap'; wrap.style.cssText = 'position:fixed;right:28px;bottom:28px;z-index:9999;display:flex;flex-direction:column;gap:8px;align-items:flex-end;pointer-events:none;'; document.body.appendChild(wrap); } const t = document.createElement('div'); const icon = opts.icon || 'check'; t.style.cssText = 'display:flex;align-items:center;gap:9px;background:#131C30;color:#fff;font-family:var(--font-sans);font-size:13.5px;font-weight:600;padding:12px 17px;border-radius:12px;box-shadow:0 10px 30px rgba(19,28,48,.35);opacity:0;transform:translateY(8px);transition:.18s ease;max-width:340px;'; const ic = (window.lucide && window.lucide.icons && window.lucide.icons[jlPascal(icon)]) || null; let svg = ''; if (ic) { const inner = ic.map(([tag, a]) => '<' + tag + ' ' + Object.entries(a).map(([k,v])=>k+'="'+v+'"').join(' ') + ' />').join(''); svg = '' + inner + ''; } t.innerHTML = svg + '' + msg + ''; wrap.appendChild(t); requestAnimationFrame(() => { t.style.opacity = '1'; t.style.transform = 'translateY(0)'; }); setTimeout(() => { t.style.opacity = '0'; t.style.transform = 'translateY(8px)'; setTimeout(() => t.remove(), 220); }, opts.ms || 2400); } Object.assign(window, { Icon, Avatar, Pill, jlPascal, jlToast });