/* ============================================================ Icons (functional, line-based) ============================================================ */ const Icon = ({ name, size = 32, stroke = 2, color = "currentColor" }) => { const props = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round" }; switch (name) { case "arrow-right": return ; case "arrow-left": return ; case "arrow-up-right": return ; case "check": return ; case "x": return ; case "alert": return ; case "info": return ; case "lock": return ; case "user": return ; case "card": return ; case "iris": return ; case "cash": return ; case "mix": return ; case "qr": return ; case "scan": return ; case "mail": return ; case "printer": return ; case "clock": return ; case "graduation": return ; case "school": return ; case "help": return ; case "language": return ; case "refresh": return ; case "wifi-off": return ; case "tools": return ; case "banknote-x": return ; case "campus": return ; case "shield-check": return ; case "menu": return ; case "eye-off": return ; case "delete": return ; default: return ; } }; window.Icon = Icon; /* ============================================================ Logo (MC GR Portrait) ============================================================ */ const MCLogo = ({ height = 96, white = false }) => ( Μητροπολιτικό Κολλέγιο ); window.MCLogo = MCLogo; /* ============================================================ Header — three variants: brand, compact, minimal ============================================================ */ const Header = ({ variant = "brand", showSteps = false, step = 0, totalSteps = 5, onCancel, hideLang = false, leftAction, rightAction }) => { const t = useT(); const lang = window.AppState.lang; const setLang = window.AppState.setLang; const cls = variant === "compact" ? "screen-header compact" : variant === "minimal" ? "screen-header minimal" : "screen-header"; return (
{leftAction || }
{rightAction} {!hideLang && (
)} {onCancel && ( )}
{showSteps && (
{Array.from({ length: totalSteps }).map((_, i) => (
))}
)}
); }; window.Header = Header; /* ============================================================ Big touch button — mirrors attached home screen vibe ============================================================ */ const BigButton = ({ children, onClick, variant = "primary", icon, disabled }) => ( ); window.BigButton = BigButton; /* ============================================================ Option tile (for two- or three-up choices) ============================================================ */ const OptionTile = ({ icon, title, sub, selected, onClick, badge }) => ( ); window.OptionTile = OptionTile; /* ============================================================ Status panel (success / error / warning / info) ============================================================ */ const StatusPanel = ({ kind = "success", icon, title, sub, children }) => (

{title}

{sub &&

{sub}

} {children}
); window.StatusPanel = StatusPanel; /* ============================================================ Numeric keypad (large kiosk-friendly) ============================================================ */ const Keypad = ({ onKey, onDelete, onClear, onSubmit, submitLabel = "OK", canSubmit = true }) => { const t = useT(); return (
{[1,2,3,4,5,6,7,8,9].map(n => ( ))}
); }; window.Keypad = Keypad; /* ============================================================ QR placeholder — generated SVG pattern (deterministic) ============================================================ */ const QRPlaceholder = ({ seed = "MC" }) => { const size = 25; const cells = React.useMemo(() => { let h = 0; for (let i = 0; i < seed.length; i++) h = (h * 31 + seed.charCodeAt(i)) | 0; const grid = []; for (let y = 0; y < size; y++) { const row = []; for (let x = 0; x < size; x++) { // fixed-position blocks for QR markers const isFinder = (x < 7 && y < 7) || (x > size - 8 && y < 7) || (x < 7 && y > size - 8); if (isFinder) { const fx = x < 7 ? x : x - (size - 7); const fy = y < 7 ? y : y - (size - 7); const onEdge = fx === 0 || fx === 6 || fy === 0 || fy === 6; const onCenter = fx >= 2 && fx <= 4 && fy >= 2 && fy <= 4; row.push(onEdge || onCenter); } else { h = (h * 1103515245 + 12345) | 0; row.push(((h >>> 16) & 1) === 1); } } grid.push(row); } return grid; }, [seed]); const cellSize = 100 / size; return ( {cells.flatMap((row, y) => row.map((on, x) => on && ( )))} ); }; window.QRPlaceholder = QRPlaceholder; /* ============================================================ Pulsing scan target (kiosk-style "place code here" indicator) ============================================================ */ const ScanTarget = ({ icon = "qr" }) => (
); window.ScanTarget = ScanTarget; /* ============================================================ Footer/secure label ============================================================ */ const SecureFooter = () => { const t = useT(); return (
{t.secured} · {t.classter}
); }; window.SecureFooter = SecureFooter; /* ============================================================ Money formatting ============================================================ */ window.fmtEUR = (n, lang = 'el') => { const formatted = new Intl.NumberFormat(lang === 'el' ? 'el-GR' : 'en-GB', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2 }).format(n); return formatted; }; /* ============================================================ Modal ============================================================ */ const Modal = ({ children, onClose }) => (
e.stopPropagation()}>{children}
); window.Modal = Modal;