/* Icons (lucide-style, currentColor) + Sunburst + small helpers — shared via window */
const I = ({ d, children, sw = 2 }) => (
);
const ICONS = {
target: ,
chart: ,
monitor: ,
users: ,
doctor: ,
clinic: ,
building: ,
rocket: ,
doc: ,
key: ,
compass: ,
tools: ,
calendar: ,
clock: ,
video: ,
gift: ,
check: ,
shield: ,
spark: ,
arrow: ,
download: ,
mail: ,
phone: ,
pin: ,
bell: ,
layers: ,
zap: ,
};
function Icon({ name, className }) {
return {ICONS[name] || ICONS.spark};
}
function Sunburst({ size = 22, color = '' }) {
// Pick the official single-color mark by intended tone (CSS color can't recolor an
SVG).
const c = String(color).toLowerCase();
const src = c.includes('amber') ? 'brand-assets/mark-sun-amber.svg'
: (c.includes('white') || c.includes('#fff')) ? 'brand-assets/mark-sun-white.svg'
: 'brand-assets/mark-sun-navy.svg';
return
;
}
// ---- validation helpers ----
const isEmail = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(v || '').trim());
const isPhoneVN = (v) => /^(0|\+84)(\s?\d){8,10}$/.test(String(v || '').replace(/[.\-()]/g, '').trim());
function validateField(f, value) {
const empty = value == null || value === '' || (Array.isArray(value) && value.length === 0) ||
(f.type === 'matrix' && !Object.keys(value || {}).length);
if (f.required && empty) return 'Vui lòng hoàn tất mục này.';
if (empty) return '';
if (f.type === 'email' && !isEmail(value)) return 'Email chưa hợp lệ.';
if (f.type === 'tel' && !isPhoneVN(value)) return 'Số điện thoại chưa hợp lệ (VD: 0901 234 567).';
return '';
}
// ---- Submit form to webinar backend (CSV + Google Sheet append) ----
// Backend strips honeypot/password/type/captcha server-side, but rejecting locally
// when the honeypot is filled avoids a network round-trip for obvious bots.
async function submitForm(formName, payload) {
if (String(payload?.__honeypot || '').length) throw new Error('spam');
const res = await fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
form: formName,
submittedAt: new Date().toISOString(),
data: payload,
}),
});
if (!res.ok) {
const body = await res.text().catch(() => '');
throw new Error('submit failed: ' + res.status + ' ' + body);
}
return res.json();
}
Object.assign(window, { Icon, Sunburst, validateField, isEmail, isPhoneVN, submitForm });