/* Shared UI primitives — Logo, charts, helpers. Exposed on window. */ (function () { const { useState, useEffect, useRef } = React; const Icon = window.Icon; /* YEIDA logo mark */ function Logo({ size = 38, light = false, showText = true, sub = true }) { const fg = light ? '#fff' : 'var(--navy-800)'; return React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 11 } }, React.createElement('img', { src: 'assets/yeida-logo.png', alt: 'YEIDA', style: { width: size * 1.08, height: size * 1.08, flex: 'none', objectFit: 'contain', display: 'block', filter: light ? 'drop-shadow(0 2px 6px rgba(0,0,0,.35))' : 'drop-shadow(0 2px 5px rgba(16,49,99,.25))', } }), showText && React.createElement('div', { style: { lineHeight: 1.05 } }, React.createElement('div', { style: { fontFamily: 'var(--font-display)', fontWeight: 800, fontSize: size * 0.46, color: fg, letterSpacing: '-.01em' } }, 'YEIDA'), sub && React.createElement('div', { style: { fontSize: size * 0.235, fontWeight: 600, color: light ? 'rgba(234,241,251,.7)' : 'var(--muted)', letterSpacing: '.02em', marginTop: 1 } }, 'Yamuna Expressway Authority') ) ); } /* Avatar */ function Avatar({ initials = 'RM', size = 38, style }) { return React.createElement('div', { className: 'avatar', style: { width: size, height: size, fontSize: size * 0.4, ...style } }, initials); } /* Line chart with gradient fill + animated draw */ function LineChart({ data, w = 520, h = 150, color = 'var(--blue-500)', fill = 'rgba(38,112,230,.12)', pad = 8 }) { const max = Math.max(...data) * 1.12, min = Math.min(...data) * 0.85; const range = max - min || 1; const stepX = (w - pad * 2) / (data.length - 1); const pts = data.map((d, i) => [pad + i * stepX, pad + (h - pad * 2) * (1 - (d - min) / range)]); const line = pts.map((p, i) => (i === 0 ? `M${p[0]},${p[1]}` : `L${p[0]},${p[1]}`)).join(' '); const area = `${line} L${pts[pts.length - 1][0]},${h - pad} L${pts[0][0]},${h - pad} Z`; const ref = useRef(null); useEffect(() => { if (ref.current) { const len = ref.current.getTotalLength(); ref.current.style.strokeDasharray = len; ref.current.style.strokeDashoffset = len; ref.current.getBoundingClientRect(); ref.current.style.transition = 'stroke-dashoffset 1.1s cubic-bezier(.3,.7,.3,1)'; ref.current.style.strokeDashoffset = 0; } }, []); return React.createElement('svg', { viewBox: `0 0 ${w} ${h}`, width: '100%', height: h, preserveAspectRatio: 'none', style: { display: 'block' } }, React.createElement('path', { d: area, fill }), React.createElement('path', { ref, d: line, fill: 'none', stroke: color, strokeWidth: 2.5, strokeLinecap: 'round', strokeLinejoin: 'round' }), pts.map((p, i) => React.createElement('circle', { key: i, cx: p[0], cy: p[1], r: i === pts.length - 1 ? 4 : 0, fill: '#fff', stroke: color, strokeWidth: 2.5 })) ); } /* Mini bar chart */ function BarChart({ data, w = 240, h = 70, color = 'var(--emerald-500)', gap = 5 }) { const max = Math.max(...data) || 1; const bw = (w - gap * (data.length - 1)) / data.length; return React.createElement('svg', { viewBox: `0 0 ${w} ${h}`, width: '100%', height: h, style: { display: 'block' } }, data.map((d, i) => { const bh = Math.max(3, (d / max) * h); return React.createElement('rect', { key: i, x: i * (bw + gap), y: h - bh, width: bw, height: bh, rx: 3, fill: i === data.length - 1 ? color : 'var(--blue-100)', style: { animation: `growBar .6s ${i * 0.05}s both` } }); }) ); } /* Donut progress */ function Donut({ value, size = 116, stroke = 12, color = 'var(--emerald-500)', track = 'var(--bg-2)', label, sub }) { const r = (size - stroke) / 2, c = 2 * Math.PI * r; const target = c - (value / 100) * c; const [off, setOff] = useState(typeof document !== 'undefined' && document.hidden ? target : c); useEffect(() => { const t = setTimeout(() => setOff(target), 100); return () => clearTimeout(t); }, [target]); return React.createElement('div', { style: { position: 'relative', width: size, height: size } }, React.createElement('svg', { width: size, height: size, style: { transform: 'rotate(-90deg)' } }, React.createElement('circle', { cx: size / 2, cy: size / 2, r, fill: 'none', stroke: track, strokeWidth: stroke }), React.createElement('circle', { cx: size / 2, cy: size / 2, r, fill: 'none', stroke: color, strokeWidth: stroke, strokeLinecap: 'round', strokeDasharray: c, strokeDashoffset: off, style: { transition: 'stroke-dashoffset 1s cubic-bezier(.3,.7,.3,1)' } }) ), React.createElement('div', { style: { position: 'absolute', inset: 0, display: 'grid', placeItems: 'center', textAlign: 'center' } }, React.createElement('div', null, React.createElement('div', { style: { fontFamily: 'var(--font-display)', fontWeight: 800, fontSize: size * 0.26, color: 'var(--ink)' } }, label != null ? label : value + '%'), sub && React.createElement('div', { style: { fontSize: 11.5, color: 'var(--muted)', fontWeight: 600, marginTop: 1 } }, sub) ) ) ); } /* Section header with kicker */ function PageHead({ title, subtitle, children }) { return React.createElement('div', { className: 'row spread', style: { marginBottom: 24, gap: 16, flexWrap: 'wrap' } }, React.createElement('div', null, React.createElement('h1', { style: { fontSize: 27, fontWeight: 800 } }, title), subtitle && React.createElement('p', { className: 'muted', style: { fontSize: 14.5, marginTop: 5 } }, subtitle) ), children && React.createElement('div', { className: 'row gap-3' }, children) ); } /* Toast host */ function useToast() { const [toasts, setToasts] = useState([]); const push = (msg, kind = 'success') => { const id = Math.random(); setToasts(t => [...t, { id, msg, kind }]); setTimeout(() => setToasts(t => t.filter(x => x.id !== id)), 3200); }; const node = React.createElement('div', { style: { position: 'fixed', bottom: 24, right: 24, display: 'flex', flexDirection: 'column', gap: 10, zIndex: 9000 } }, toasts.map(t => React.createElement('div', { key: t.id, className: 'anim-pop', style: { display: 'flex', alignItems: 'center', gap: 11, padding: '13px 17px', borderRadius: 12, background: 'var(--navy-800)', color: '#fff', boxShadow: 'var(--sh-lg)', fontSize: 14, fontWeight: 500, maxWidth: 360, } }, React.createElement('span', { style: { display: 'grid', placeItems: 'center', width: 22, height: 22, borderRadius: 99, background: t.kind === 'success' ? 'var(--emerald-500)' : 'var(--blue-500)', flex: 'none' } }, React.createElement(Icon, { name: t.kind === 'success' ? 'check' : 'info', size: 14 })), t.msg )) ); return [node, push]; } /* Authority status badge — maps a backend status code to a labelled, coloured chip */ function StatusBadge({ code, size, withIcon = true }) { const D = window.YEIDA; const s = (D && D.STATUS_CODES && D.STATUS_CODES[code]) || { label: code, tone: 'gray', icon: 'info' }; return React.createElement('span', { className: 'badge badge-' + s.tone, style: size === 'lg' ? { fontSize: 13, padding: '6px 12px' } : undefined }, withIcon && React.createElement(Icon, { name: s.icon, size: 13 }), s.label); } Object.assign(window, { Logo, Avatar, LineChart, BarChart, Donut, PageHead, useToast, StatusBadge }); })(); /* keyframes for bars */ const __s = document.createElement('style'); __s.textContent = '@keyframes growBar{from{transform:scaleY(0);transform-origin:bottom}to{transform:scaleY(1);transform-origin:bottom}}'; document.head.appendChild(__s);