// ── Remittance comparison panel ───────────────────────────────────────────── // Slide-out panel from the right. User selects: // FROM currency + amount → TO country // Shows ranked provider cards: // Wise · Remitly · Western Union · MyBeacon · MoneyGram // Data is modeled from typical real-world markups (no free live API exists). // Markups are rate cuts below mid-market; fees are flat charges. (function(){ var E = React.createElement; // ── Provider model ────────────────────────────────────────────────────── // markup: % the provider shaves off the mid-market rate // feeFlat: flat fee in the SEND currency // feeWaiverAbove: fee drops to 0 for send amounts above this (in source ccy units) // delivery / blurb / tag self-explanatory // domain: used for logo — fetched from Google favicon service // ── Corridor availability (modeled from real provider coverage patterns) ── // Most remittance providers serve asymmetric corridors: // Remitly is a "send from developed country → receive in EM" service. // You can send CAD→INR, USD→PHP, etc. You CAN'T send INR→CAD through Remitly. // Western Union / MoneyGram are closest to bi-directional but still gated // by where each country's regulatory license allows. // Wise is bi-directional between most major developed + EM currencies. var CORRIDORS = { wise: { // Bi-directional between these major currencies bidi: new Set(['USD','EUR','GBP','CAD','AUD','NZD','CHF','SEK','NOK','DKK','JPY','SGD','HKD','HUF','PLN','CZK','RON','BGN','TRY','ZAR','AED','ILS','MXN','BRL','INR','IDR','MYR','PHP','THB','VND','KRW','CNY']), }, remitly: { // Can SEND from these countries sendFrom: new Set(['USD','CAD','GBP','EUR','AUD','SGD','JPY','KRW','HKD','NZD','CHF','SEK','NOK','DKK','AED']), // Can RECEIVE into these countries receiveTo: new Set(['INR','PHP','MXN','CNY','VND','COP','BRL','NGN','KES','PKR','BDT','LKR','NPR','IDR','THB','GHS','JMD','HTG','DOP','GTQ','HNL','PEN','MAD','EGP','ZAR','TRY','PLN','RON','HUF','UAH','ARS','CLP','UYU','BOB','PYG','SVC','NIO','CRC','PAB','TTD','BBD','XCD','GYD','SRD','BSD','BMD','KHR','LAK','MMK','MNT','KZT','UZS','KGS','TJS','GEL','AMD','AZN']), }, 'western-union': { // WU serves most corridors in both directions; we model "sends from anywhere with a license" + "receives nearly everywhere" sendFrom: new Set(['USD','CAD','GBP','EUR','AUD','NZD','CHF','SEK','NOK','DKK','JPY','SGD','HKD','KRW','AED','SAR','QAR','KWD','BHD','OMR','ZAR','MYR','THB','PLN','CZK','HUF','RON','BGN','TRY','ILS','MXN','BRL','ARS','CLP','COP','PEN','UYU','HRK','RUB']), receiveTo: new Set(['USD','CAD','GBP','EUR','AUD','NZD','CHF','SEK','NOK','DKK','JPY','SGD','HKD','KRW','CNY','INR','IDR','MYR','PHP','THB','VND','PKR','BDT','LKR','NPR','AED','SAR','QAR','KWD','BHD','OMR','EGP','MAD','TND','DZD','NGN','GHS','KES','UGX','TZS','RWF','ETB','ZAR','BWP','MWK','ZMW','MZN','MGA','UGX','PLN','CZK','HUF','RON','BGN','HRK','TRY','ILS','MXN','BRL','ARS','CLP','COP','PEN','UYU','BOB','PYG','DOP','HTG','JMD','TTD','BBD','GTQ','HNL','SVC','CRC','NIO','PAB','RUB','UAH','GEL','AMD','AZN','KZT','UZS','KGS','MMK','KHR','LAK','MNT']), }, moneygram: { // Similar to WU but slightly narrower sendFrom: new Set(['USD','CAD','GBP','EUR','AUD','NZD','CHF','SEK','NOK','DKK','JPY','SGD','HKD','KRW','AED','SAR','QAR','ZAR','MYR','THB','PLN','CZK','HUF','RON','TRY','ILS','MXN','BRL','ARS','CLP','COP']), receiveTo: new Set(['USD','CAD','GBP','EUR','AUD','CHF','SEK','NOK','DKK','JPY','SGD','HKD','CNY','INR','IDR','MYR','PHP','THB','VND','PKR','BDT','LKR','NPR','AED','SAR','EGP','MAD','NGN','GHS','KES','ETB','ZAR','PLN','CZK','HUF','RON','TRY','ILS','MXN','BRL','ARS','CLP','COP','PEN','DOP','HTG','JMD','GTQ','HNL','SVC','CRC','NIO','KZT','KHR','MMK']), }, mybeacon: { sendFrom: new Set(['USD','EUR','GBP','CAD','AUD','CHF','SEK','NOK','DKK','JPY','SGD','HKD','NZD']), receiveTo: new Set(['USD','EUR','GBP','CAD','AUD','CHF','SEK','NOK','DKK','JPY','SGD','HKD','NZD','INR']), }, }; function providerSupports(providerId, from, to) { if (from === to) return false; var c = CORRIDORS[providerId]; if (!c) return true; // unknown → assume supported if (c.bidi) return c.bidi.has(from) && c.bidi.has(to); if (c.sendFrom && c.receiveTo) return c.sendFrom.has(from) && c.receiveTo.has(to); return true; } var PROVIDERS = [ { id:'wise', name:'Wise', domain:'wise.com', markup: 0.45, feeFlat: 4.50, feePct: 0.41, feeWaiverAbove: null, delivery: '1–2 hours', blurb: 'Transparent mid-market rate. Lowest total cost for most corridors.', tag: 'LOW FEE', }, { id:'remitly', name:'Remitly', domain:'remitly.com', markup: 1.35, feeFlat: 2.99, feePct: 0, feeWaiverAbove: 100, delivery: 'Minutes – 3 days', blurb: 'Express to bank account. One-way corridor only.', tag: 'FAST · ONE-WAY', }, { id:'western-union', name:'Western Union', domain:'westernunion.com', markup: 3.6, feeFlat: 7.00, feePct: 0, feeWaiverAbove: 100, delivery: 'Minutes (cash pickup)', blurb: 'Cash pickup in 500k+ locations. Fee-free above $100.', tag: '500K+ AGENTS', }, { id:'mybeacon', name:'MyBeacon', domain:'mybeacon.com', markup: 1.1, feeFlat: 2.50, feePct: 0, feeWaiverAbove: 100, delivery: '1 business day', blurb: 'Competitive rate, low flat fee. Bank transfer only.', tag: 'BANK ONLY', }, { id:'moneygram', name:'MoneyGram', domain:'moneygram.com', markup: 2.8, feeFlat: 5.99, feePct: 0, feeWaiverAbove: 100, delivery: 'Minutes – 1 day', blurb: 'Cash or bank. Fee-free above $100.', tag: 'CASH + BANK', }, ]; // Expose for other panels (Calculator uses it for corridor badge) window.RemittanceProviders = PROVIDERS; window.providerSupports = providerSupports; var CUSTOM_LOGOS = { 'mybeacon.com': '/assets/mybeacon.jpg', }; function logoUrl(domain) { if (CUSTOM_LOGOS[domain]) return CUSTOM_LOGOS[domain]; return 'https://www.google.com/s2/favicons?domain=' + domain + '&sz=64'; } var REM_STYLES = { scrim: { position:'fixed', inset:0, background:'rgba(4,8,16,0.5)', zIndex:60, opacity:0, pointerEvents:'none', transition:'opacity 220ms ease', }, scrimOn: { opacity:1, pointerEvents:'auto' }, panel: { position:'fixed', top:0, right:0, bottom:0, width:'min(460px, 100vw)', background:'var(--bg)', borderLeft:'1px solid var(--line-soft)', zIndex:61, transform:'translateX(100%)', transition:'transform 260ms cubic-bezier(0.22, 0.61, 0.36, 1)', display:'flex', flexDirection:'column', boxShadow:'-8px 0 40px rgba(0,0,0,0.5)', }, panelOpen: { transform:'translateX(0)' }, header: { display:'flex', alignItems:'center', justifyContent:'space-between', padding:'14px 16px', borderBottom:'1px solid var(--line-soft)', flexShrink:0, }, title: { fontFamily:"'JetBrains Mono', monospace", fontSize:10, letterSpacing:'0.2em', color:'var(--fg-dim)', }, closeBtn: { width:28, height:28, display:'grid', placeItems:'center', background:'transparent', border:'1px solid var(--line-soft)', color:'var(--fg-dim)', cursor:'pointer', }, // Top: FROM → TO pills + big amount inputs: { padding:'16px 16px 18px', borderBottom:'1px solid var(--line-soft)', display:'flex', flexDirection:'column', gap:14, flexShrink:0, }, pillRow: { display:'flex', alignItems:'center', gap:10, justifyContent:'center', }, pill: { display:'flex', alignItems:'center', gap:7, padding:'7px 11px', background:'var(--bg-elev)', border:'1px solid var(--line-soft)', color:'var(--fg)', cursor:'pointer', fontFamily:"'JetBrains Mono', monospace", fontSize:12, letterSpacing:'0.04em', flex:'0 0 auto', }, arrowWrap: { color:'var(--fg-faint)', display:'flex', alignItems:'center', justifyContent:'center', width:32, height:32, background:'transparent', border:'1px solid var(--line-soft)', cursor:'pointer', transition:'color 140ms, border-color 140ms, transform 180ms', }, amountShell: { display:'flex', alignItems:'center', justifyContent:'center', paddingTop:4, }, amountBigInput: { background:'transparent', border:'none', outline:'none', color:'var(--fg)', fontFamily:"'Cormorant Garamond', 'Instrument Serif', Georgia, serif", fontSize:56, fontWeight:400, lineHeight:1.05, textAlign:'center', width:'100%', letterSpacing:'-0.01em', }, amountHint: { textAlign:'center', fontFamily:"'JetBrains Mono', monospace", fontSize:9, letterSpacing:'0.22em', color:'var(--fg-faint)', marginTop:2, }, flag: { width:20, height:13, objectFit:'cover', flexShrink:0, background:'var(--bg-soft)', }, // Mid-market strip midStrip: { padding:'10px 16px', display:'flex', justifyContent:'space-between', background:'var(--bg-soft)', borderBottom:'1px solid var(--line-soft)', flexShrink:0, }, midLabel: { fontFamily:"'JetBrains Mono', monospace", fontSize:9, letterSpacing:'0.22em', color:'var(--fg-faint)', }, midValue: { fontFamily:"'JetBrains Mono', monospace", fontSize:11, color:'var(--fg-dim)', letterSpacing:'0.04em', }, // Provider cards list body: { flex:1, overflowY:'auto', padding:'14px 14px 20px' }, card: { position:'relative', marginBottom:12, padding:'14px 14px', background:'var(--bg-elev)', border:'1px solid var(--line-soft)', display:'flex', flexDirection:'column', gap:10, }, cardBest: { borderColor:'var(--accent)', background:'linear-gradient(180deg, rgba(232,162,63,0.06), transparent 60%)', }, cardHead: { display:'flex', alignItems:'center', justifyContent:'space-between', gap:10, }, cardName: { fontFamily:"'Cormorant Garamond', Georgia, serif", fontSize:20, color:'var(--fg)', fontWeight:500, }, cardLogo: { width:28, height:28, objectFit:'contain', flexShrink:0, background:'var(--bg-soft)', padding:3, border:'1px solid var(--line-soft)', }, cardTag: { fontFamily:"'JetBrains Mono', monospace", fontSize:8, letterSpacing:'0.22em', color:'var(--fg-faint)', border:'1px solid var(--line-soft)', padding:'2px 7px', }, bestBadge: { fontFamily:"'JetBrains Mono', monospace", fontSize:8, letterSpacing:'0.22em', color:'#1a1206', fontWeight:600, background:'var(--accent)', padding:'3px 8px', }, cardMetrics: { display:'grid', gridTemplateColumns:'1fr 1fr', gap:12, }, metric: {}, metricLabel: { fontFamily:"'JetBrains Mono', monospace", fontSize:9, letterSpacing:'0.18em', color:'var(--fg-faint)', marginBottom:3, }, metricValue: { fontFamily:"'Cormorant Garamond', Georgia, serif", fontSize:22, color:'var(--fg)', lineHeight:1.1, }, metricSub: { fontFamily:"'JetBrains Mono', monospace", fontSize:10, color:'var(--fg-dim)', marginTop:2, letterSpacing:'0.04em', }, bar: { height:4, background:'var(--bg-soft)', position:'relative', overflow:'hidden', }, barFill: { position:'absolute', inset:'0 auto 0 0', transition:'width 400ms ease', }, blurb: { fontFamily:"'Inter', sans-serif", fontSize:11, color:'var(--fg-dim)', lineHeight:1.5, }, savings: { fontFamily:"'JetBrains Mono', monospace", fontSize:10, color:'var(--up)', letterSpacing:'0.1em', }, costMore: { color:'var(--down)', }, footer: { padding:'10px 16px', borderTop:'1px solid var(--line-soft)', fontFamily:"'JetBrains Mono', monospace", fontSize:9, letterSpacing:'0.12em', color:'var(--fg-faint)', textAlign:'center', flexShrink:0, lineHeight:1.6, }, // Picker reused from calculator pickerScrim: { position:'fixed', inset:0, zIndex:80, background:'rgba(4,8,16,0.35)' }, pickerPanel: { position:'absolute', background:'var(--bg-elev)', border:'1px solid var(--line)', boxShadow:'0 12px 32px rgba(0,0,0,0.6)', width:280, maxHeight:360, overflow:'hidden', display:'flex', flexDirection:'column', }, pickerSearch: { padding:'10px 12px', border:'none', borderBottom:'1px solid var(--line-soft)', background:'transparent', color:'var(--fg)', outline:'none', fontFamily:"'JetBrains Mono', monospace", fontSize:11, }, pickerList: { overflowY:'auto', flex:1 }, pickerRow: { display:'flex', alignItems:'center', gap:10, padding:'8px 12px', cursor:'pointer', borderBottom:'1px solid var(--line-soft)', }, pickerRowHover: { background:'var(--bg-soft)' }, pickerCode: { fontFamily:"'JetBrains Mono', monospace", fontSize:11, letterSpacing:'0.04em', color:'var(--fg)', }, pickerName: { fontFamily:"'Inter', sans-serif", fontSize:11, color:'var(--fg-dim)', flex:1, minWidth:0, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', }, }; function fmt(n, ccy) { if (!isFinite(n)) return '—'; var noDec = { JPY:1, KRW:1, VND:1, IDR:1, CLP:1, HUF:1, ISK:1 }; var frac = noDec[ccy] ? 0 : 2; return n.toLocaleString('en-US', { minimumFractionDigits: frac, maximumFractionDigits: frac }); } function currencyMeta(code) { var arr = window.CURRENCIES || []; for (var i=0;i window.innerWidth - 8) left = window.innerWidth - 288; if (left < 8) left = 8; if (top + 360 > window.innerHeight - 8) top = Math.max(8, r.top - 364); style.top = top + 'px'; style.left = left + 'px'; } var node = E('div', { style: REM_STYLES.pickerScrim, onClick: onClose, onMouseDown: function(e){ e.stopPropagation(); } }, E('div', { style: style, onClick: function(e){ e.stopPropagation(); } }, E('input', { style: REM_STYLES.pickerSearch, placeholder: 'Search currency or country…', value: q, autoFocus: true, onChange: function(e){ setQ(e.target.value); setHoverIdx(0); }, onKeyDown: function(e){ if (e.key === 'Escape') onClose(); else if (e.key === 'Enter' && filtered[hoverIdx]) onPick(filtered[hoverIdx].code); else if (e.key === 'ArrowDown') { e.preventDefault(); setHoverIdx(function(i){ return Math.min(filtered.length-1, i+1); }); } else if (e.key === 'ArrowUp') { e.preventDefault(); setHoverIdx(function(i){ return Math.max(0, i-1); }); } }, }), E('div', { style: REM_STYLES.pickerList }, filtered.map(function(c, i){ return E('div', { key: c.code, style: Object.assign({}, REM_STYLES.pickerRow, i===hoverIdx ? REM_STYLES.pickerRowHover : null), onMouseEnter: function(){ setHoverIdx(i); }, onClick: function(){ onPick(c.code); }, }, c.flag ? E('img', { src: flagUrl(c.flag), style: REM_STYLES.flag, alt:'' }) : null, E('span', { style: REM_STYLES.pickerCode }, c.code), E('span', { style: REM_STYLES.pickerName }, c.name || '') ); }) ) ) ); return ReactDOM.createPortal(node, document.body); } // ── Main panel ─────────────────────────────────────────────────────────── function Remittance({ open, onClose, initialBase, initialFromCcy, initialToCcy, initialAmount }) { var [fromCcy, setFromCcy] = React.useState(initialFromCcy || initialBase || 'USD'); var [toCcy, setToCcy] = React.useState(initialToCcy || 'INR'); var [amount, setAmount] = React.useState(initialAmount || 1000); var [draft, setDraft] = React.useState(String(initialAmount || 1000)); var [pickerFor, setPickerFor] = React.useState(null); var [rateTick, setRateTick] = React.useState(0); // When opened with new override props, sync state React.useEffect(function(){ if (!open) return; if (initialFromCcy) setFromCcy(initialFromCcy); if (initialToCcy) setToCcy(initialToCcy); if (initialAmount && isFinite(initialAmount) && initialAmount > 0) { var rounded = Math.round(initialAmount); setAmount(rounded); setDraft(String(rounded)); } }, [open, initialFromCcy, initialToCcy, initialAmount]); React.useEffect(function(){ // Only re-sync draft when panel opens (don't clobber user typing) if (open) setDraft(String(amount)); }, [open]); React.useEffect(function(){ if (!open || !window.RatesService) return; return window.RatesService.subscribe(function(){ setRateTick(function(t){ return t+1; }); }); }, [open]); React.useEffect(function(){ if (!open) return; function onKey(e){ if (e.key === 'Escape' && !pickerFor) onClose(); } window.addEventListener('keydown', onKey); return function(){ window.removeEventListener('keydown', onKey); }; }, [open, pickerFor, onClose]); // Mid-market rate (from → to) var midRate = React.useMemo(function(){ if (!window.RatesService) return null; return window.RatesService.convert(fromCcy, toCcy); }, [fromCcy, toCcy, rateTick]); // Compute per-provider quote + corridor availability var quotes = React.useMemo(function(){ if (midRate == null || !isFinite(amount) || amount <= 0) return []; return PROVIDERS.map(function(p){ var supports = providerSupports(p.id, fromCcy, toCcy); var supportsReverse = providerSupports(p.id, toCcy, fromCcy); var providerRate = midRate * (1 - p.markup/100); var flatFee = (p.feeWaiverAbove != null && amount > p.feeWaiverAbove) ? 0 : p.feeFlat; var pctFee = amount * (p.feePct||0)/100; var feeEff = flatFee + pctFee; var sendAfterFee = Math.max(0, amount - feeEff); var received = sendAfterFee * providerRate; return { p: p, providerRate: providerRate, received: received, feeEffective: feeEff, feeWaived: (p.feeWaiverAbove != null && amount > p.feeWaiverAbove), supports: supports, oneWayOnly: supports && !supportsReverse, }; }).sort(function(a,b){ // unsupported sink to the bottom if (a.supports !== b.supports) return a.supports ? -1 : 1; return b.received - a.received; }); }, [midRate, amount, fromCcy, toCcy]); var supportedQuotes = quotes.filter(function(q){ return q.supports; }); var best = supportedQuotes[0]; var worst = supportedQuotes[supportedQuotes.length-1]; var maxReceived = best ? best.received : 0; var minReceived = worst ? worst.received : 0; var receivedSpan = Math.max(1e-9, maxReceived - minReceived); var fromMeta = currencyMeta(fromCcy); var toMeta = currencyMeta(toCcy); return E(React.Fragment, null, E('div', { style: Object.assign({}, REM_STYLES.scrim, open ? REM_STYLES.scrimOn : null), onClick: onClose, }), E('div', { style: Object.assign({}, REM_STYLES.panel, open ? REM_STYLES.panelOpen : null) }, // Header E('div', { style: REM_STYLES.header }, E('div', { style: REM_STYLES.title }, 'SEND MONEY — COMPARE'), E('button', { style: REM_STYLES.closeBtn, onClick: onClose, 'aria-label':'Close' }, E('svg', { width:13, height:13, viewBox:'0 0 24 24', fill:'none', stroke:'currentColor', strokeWidth:1.8 }, E('path', { d:'M18 6L6 18M6 6l12 12' }) ) ) ), // Inputs E('div', { style: REM_STYLES.inputs }, // FROM → TO pill row E('div', { style: REM_STYLES.pillRow }, E('button', { style: REM_STYLES.pill, onClick: function(e){ var r = e.currentTarget.getBoundingClientRect(); setPickerFor({ target:'from', rect: { top:r.top, bottom:r.bottom, left:r.left, right:r.right } }); }, }, fromMeta.flag ? E('img', { src: flagUrl(fromMeta.flag), style: REM_STYLES.flag, alt:'' }) : null, E('span', null, fromCcy), E('svg', { width:9, height:9, viewBox:'0 0 24 24', fill:'none', stroke:'currentColor', strokeWidth:2, style:{ opacity:0.5 } }, E('path', { d:'M6 9l6 6 6-6' }) ) ), E('button', { style: REM_STYLES.arrowWrap, title: 'Swap direction', onClick: function(){ setFromCcy(toCcy); setToCcy(fromCcy); }, onMouseEnter: function(e){ e.currentTarget.style.color = 'var(--accent)'; e.currentTarget.style.borderColor = 'var(--accent)'; }, onMouseLeave: function(e){ e.currentTarget.style.color = 'var(--fg-faint)'; e.currentTarget.style.borderColor = 'var(--line-soft)'; }, }, E('svg', { width:14, height:14, viewBox:'0 0 24 24', fill:'none', stroke:'currentColor', strokeWidth:1.8 }, E('path', { d:'M7 7h11l-3-3M17 17H6l3 3' }) ) ), E('button', { style: REM_STYLES.pill, onClick: function(e){ var r = e.currentTarget.getBoundingClientRect(); setPickerFor({ target:'to', rect: { top:r.top, bottom:r.bottom, left:r.left, right:r.right } }); }, }, toMeta.flag ? E('img', { src: flagUrl(toMeta.flag), style: REM_STYLES.flag, alt:'' }) : null, E('span', null, toCcy), E('svg', { width:9, height:9, viewBox:'0 0 24 24', fill:'none', stroke:'currentColor', strokeWidth:2, style:{ opacity:0.5 } }, E('path', { d:'M6 9l6 6 6-6' }) ) ) ), // Big amount input E('div', null, E('div', { style: REM_STYLES.amountShell }, E('input', { type:'text', inputMode:'decimal', value: draft, onFocus: function(e){ e.target.select(); }, onChange: function(e){ var v = e.target.value.replace(/[^0-9.]/g,''); setDraft(v); var n = parseFloat(v); if (isFinite(n) && n > 0) setAmount(n); }, style: REM_STYLES.amountBigInput, 'aria-label':'Send amount', }) ), E('div', { style: REM_STYLES.amountHint }, 'AMOUNT IN ' + fromCcy) ) ), // Mid-market strip E('div', { style: REM_STYLES.midStrip }, E('div', { style: REM_STYLES.midLabel }, 'MID-MARKET RATE'), E('div', { style: REM_STYLES.midValue }, midRate != null ? '1 ' + fromCcy + ' = ' + fmt(midRate, toCcy) + ' ' + toCcy : '—' ) ), // Ranked provider cards E('div', { style: REM_STYLES.body }, quotes.length === 0 ? E('div', { style:{ textAlign:'center', padding:'40px 20px', color:'var(--fg-faint)', fontFamily:"'JetBrains Mono', monospace", fontSize:11, letterSpacing:'0.15em' } }, 'ENTER AMOUNT TO COMPARE') : (function(){ var nodes = []; // Warn if NO provider supports this corridor if (supportedQuotes.length === 0 && fromCcy !== toCcy) { nodes.push(E('div', { key: 'no-support', style: { padding:'16px 14px', marginBottom:12, border:'1px solid var(--down)', background:'color-mix(in oklch, var(--down) 12%, var(--bg-elev))', fontFamily:"'Inter', sans-serif", fontSize:12, lineHeight:1.5, color:'var(--fg)', }, }, E('div', { style:{ fontFamily:"'JetBrains Mono', monospace", fontSize:10, letterSpacing:'0.2em', color:'var(--down)', marginBottom:6 } }, '● NO DIRECT CORRIDOR'), 'None of these providers offer direct ' + fromCcy + ' → ' + toCcy + '. ' + 'Try routing through USD or EUR, or check a local bank.' )); } quotes.forEach(function(q, i){ var unsup = !q.supports; var supportedIdx = supportedQuotes.indexOf(q); var isBest = supportedIdx === 0; var barPct = unsup ? 0 : ((q.received - minReceived) / receivedSpan * 100); var deltaVsWorst = q.received - minReceived; nodes.push(E('div', { key: q.p.id, style: Object.assign({}, REM_STYLES.card, isBest ? REM_STYLES.cardBest : null, unsup ? { opacity: 0.55, borderStyle: 'dashed', background: 'transparent', } : null), }, E('div', { style: REM_STYLES.cardHead }, E('div', { style:{ display:'flex', alignItems:'center', gap:10 } }, E('img', { src: logoUrl(q.p.domain), alt:'', style: Object.assign({}, REM_STYLES.cardLogo, unsup ? { filter:'grayscale(1)' } : null), onError: function(e){ e.target.style.display='none'; } }), E('div', { style: REM_STYLES.cardName }, q.p.name) ), unsup ? E('div', { style: Object.assign({}, REM_STYLES.cardTag, { borderColor:'var(--down)', color:'var(--down)' }) }, 'NOT AVAILABLE') : isBest ? E('div', { style: REM_STYLES.bestBadge }, '● BEST VALUE') : E('div', { style: REM_STYLES.cardTag }, q.p.tag) ), // One-way badge (shown for Remitly-style corridors where reverse doesn't work) q.oneWayOnly ? E('div', { style: { marginBottom: 10, padding: '6px 10px', background: 'color-mix(in oklch, var(--accent) 10%, transparent)', border: '1px solid color-mix(in oklch, var(--accent) 40%, transparent)', fontFamily:"'JetBrains Mono', monospace", fontSize: 9, letterSpacing:'0.16em', color:'var(--accent)', }, }, '→ ONE-WAY ONLY · ' + toCcy + ' → ' + fromCcy + ' NOT SUPPORTED') : null, unsup ? E('div', { style: { padding:'12px 0', fontFamily:"'Inter', sans-serif", fontSize:12, color:'var(--fg-dim)', lineHeight:1.5, }, }, 'This provider does not operate the ' + fromCcy + ' → ' + toCcy + ' corridor.') : [ // Main metrics: received + total cost E('div', { key:'m', style: REM_STYLES.cardMetrics }, E('div', { style: REM_STYLES.metric }, E('div', { style: REM_STYLES.metricLabel }, 'RECIPIENT GETS'), E('div', { style: REM_STYLES.metricValue }, fmt(q.received, toCcy), ' ', E('span', { style:{ color:'var(--fg-dim)', fontSize:14 } }, toCcy) ), E('div', { style: REM_STYLES.metricSub }, '@ ' + fmt(q.providerRate, toCcy) + ' (' + q.p.markup.toFixed(2) + '% markup)' ) ), E('div', { style: REM_STYLES.metric }, E('div', { style: REM_STYLES.metricLabel }, 'FEE'), E('div', { style: REM_STYLES.metricValue }, q.feeEffective === 0 ? E('span', { style:{ color:'var(--up)' } }, 'FREE') : [fmt(q.feeEffective, fromCcy), ' ', E('span', { key:'c', style:{ color:'var(--fg-dim)', fontSize:14 } }, fromCcy)] ), E('div', { style: REM_STYLES.metricSub }, q.p.delivery) ) ), E('div', { key:'b', style: REM_STYLES.bar }, E('div', { style: Object.assign({}, REM_STYLES.barFill, { width: Math.max(4, barPct) + '%', background: isBest ? 'var(--accent)' : 'var(--fg-faint)', opacity: isBest ? 1 : 0.55, }) }) ), isBest && deltaVsWorst > 0 ? E('div', { key:'s', style: REM_STYLES.savings }, '▲ SAVE ' + fmt(deltaVsWorst, toCcy) + ' ' + toCcy + ' VS CHEAPEST OPTION') : (!isBest && supportedIdx >= 0 ? E('div', { key:'s', style: Object.assign({}, REM_STYLES.savings, REM_STYLES.costMore) }, '▼ ' + fmt(maxReceived - q.received, toCcy) + ' ' + toCcy + ' LESS THAN BEST') : null), E('div', { key:'bl', style: REM_STYLES.blurb }, q.p.blurb) ] )); }); return nodes; })() ), // Footer disclaimer E('div', { style: REM_STYLES.footer }, 'ESTIMATES BASED ON TYPICAL MARKUPS · ACTUAL QUOTES VARY BY CORRIDOR · VERIFY ON PROVIDER') ), // Picker overlay pickerFor ? E(CurrencyPicker, { anchor: pickerFor, onPick: function(code){ if (pickerFor.target === 'from') setFromCcy(code); else setToCcy(code); setPickerFor(null); }, onClose: function(){ setPickerFor(null); }, }) : null ); } window.Remittance = Remittance; })();