MediaWiki:Gadget-wix-interactive.js: Difference between revisions

Content deleted Content added
Created page with "/* ================================================================ WIX-INTERACTIVE.JS — Wiki Interactive Experience: Complex Widgets ================================================================ Depends on: ext.gadget.wix-core (window.wix must exist) Dispatches by data-wix-module value. Each value maps to an initializer function that builds the widget DOM and wires logic. Supported modules: - "pool-simulator" Risk pooling comparison (Borde..."
 
No edit summary
Line 8:
 
Supported modules:
- "pool-simulator" Risk pooling comparison (Bordeaux vs Normandy)
- "insurer-engines" Two-engine profit simulator (underwriting + investment)
================================================================ */
 
Line 17 ⟶ 18:
 
var dispatchers = {
'pool-simulator': initPoolSimulator,
'insurer-engines': initInsurerEngines
};
 
Line 433 ⟶ 435:
drawChart( canvasPool, poolData, {}, colorPool );
} );
}
 
 
 
/* ================================================================
INSURER ENGINES
Two-engine profit simulator: underwriting + investment income.
================================================================ */
 
function initInsurerEngines( container ) {
 
/* ── Constants ────────────────────────────────────────────── */
 
var PREMIUMS = 5000000;
var EXPENSES = 1200000;
 
/* ── Colors (match WIX tokens) ───────────────────────────── */
 
var styles = getComputedStyle( container );
var colorPos = styles.getPropertyValue( '--wix-correct' ).trim() || '#1e8449';
var colorNeg = styles.getPropertyValue( '--wix-wrong' ).trim() || '#922b21';
var colorInv = styles.getPropertyValue( '--wix-accent' ).trim() || '#1a5276';
 
/* ── Formatting ───────────────────────────────────────────── */
 
function fmt( v ) {
var sign = v < 0 ? '-' : '';
var abs = Math.abs( v );
if ( abs >= 1000000 ) {
return sign + '\u20AC' + ( abs / 1000000 ).toFixed( 2 ) + 'M';
}
return sign + '\u20AC' + wix.formatNumber( Math.round( abs ) );
}
 
/* ── Build UI ─────────────────────────────────────────────── */
 
wix.empty( container );
 
// Fixed info row
container.appendChild( wix.el( 'div', { className: 'wix-eng-fixed' }, [
wix.el( 'div', {}, [ 'Policies: ', wix.el( 'span', { textContent: '10,000' } ) ] ),
wix.el( 'div', {}, [ 'Avg premium: ', wix.el( 'span', { textContent: '\u20AC500' } ) ] ),
wix.el( 'div', {}, [ 'Total premiums: ', wix.el( 'span', { textContent: '\u20AC5,000,000' } ) ] ),
wix.el( 'div', {}, [ 'Expenses: ', wix.el( 'span', { textContent: '\u20AC1,200,000' } ) ] )
] ) );
 
// Scenario strip
container.appendChild( wix.el( 'div', { className: 'wix-eng-section-label', textContent: 'Claims scenario' } ) );
 
var scenarios = [
{ label: 'Mild year', sub: '\u20AC2.8M', claims: 2800000 },
{ label: 'Expected', sub: '\u20AC3.2M', claims: 3200000 },
{ label: 'Harsh winter', sub: '\u20AC3.6M', claims: 3600000 }
];
 
var scenarioBtns = [];
var strip = wix.el( 'div', { className: 'wix-eng-scenario-strip' } );
 
scenarios.forEach( function ( s, idx ) {
var cls = 'wix-eng-scenario-btn';
if ( idx === 1 ) {
cls += ' wix-eng-scenario-btn--active';
}
var btn = wix.el( 'button', {
className: cls,
'data-claims': String( s.claims )
}, [
s.label,
wix.el( 'br' ),
s.sub
] );
scenarioBtns.push( btn );
strip.appendChild( btn );
} );
 
container.appendChild( strip );
 
// Sliders
var claimsSlider = wix.el( 'input', {
type: 'range', min: '2400000', max: '4200000', step: '50000', value: '3200000'
} );
var claimsVal = wix.el( 'div', { className: 'wix-eng-slider-val', textContent: '\u20AC3.20M' } );
 
container.appendChild( wix.el( 'div', { className: 'wix-eng-slider-row' }, [
wix.el( 'div', { className: 'wix-eng-slider-label', textContent: 'Actual claims' } ),
claimsSlider,
claimsVal
] ) );
 
var investSlider = wix.el( 'input', {
type: 'range', min: '0', max: '8', step: '0.1', value: '3'
} );
var investVal = wix.el( 'div', { className: 'wix-eng-slider-val', textContent: '3.0%' } );
 
container.appendChild( wix.el( 'div', { className: 'wix-eng-slider-row' }, [
wix.el( 'div', { className: 'wix-eng-slider-label', textContent: 'Annual return on float' } ),
investSlider,
investVal
] ) );
 
// Divider
container.appendChild( wix.el( 'div', { className: 'wix-eng-divider' } ) );
 
// Engine 1: Underwriting
var uwValEl = wix.el( 'div', { className: 'wix-eng-val', textContent: '\u20AC600,000' } );
var uwDetail = wix.el( 'div', { className: 'wix-eng-detail', textContent: '\u20AC5,000,000 premiums \u2212 \u20AC1,200,000 expenses \u2212 \u20AC3,200,000 claims' } );
var uwBar = wix.el( 'div', { className: 'wix-eng-bar' } );
 
container.appendChild( wix.el( 'div', { className: 'wix-eng-card' }, [
wix.el( 'div', { className: 'wix-eng-head' }, [
wix.el( 'div', { className: 'wix-eng-title', textContent: 'Engine 1: underwriting' } ),
uwValEl
] ),
uwDetail,
wix.el( 'div', { className: 'wix-eng-bar-track' }, [ uwBar ] )
] ) );
 
// Combined ratio
var crValEl = wix.el( 'div', { className: 'wix-eng-cr-val', textContent: '88.0%' } );
var crSub = wix.el( 'div', { className: 'wix-eng-cr-sub', textContent: 'Below 100% \u2014 underwriting profit' } );
 
container.appendChild( wix.el( 'div', { className: 'wix-eng-cr' }, [
wix.el( 'div', { className: 'wix-eng-cr-label', textContent: 'Combined ratio' } ),
crValEl,
crSub
] ) );
 
// Engine 2: Investment
var invValEl = wix.el( 'div', { className: 'wix-eng-val', textContent: '\u20AC75,000' } );
var invDetail = wix.el( 'div', { className: 'wix-eng-detail' } );
var invBar = wix.el( 'div', { className: 'wix-eng-bar' } );
 
container.appendChild( wix.el( 'div', { className: 'wix-eng-card' }, [
wix.el( 'div', { className: 'wix-eng-head' }, [
wix.el( 'div', { className: 'wix-eng-title', textContent: 'Engine 2: investment' } ),
invValEl
] ),
invDetail,
wix.el( 'div', { className: 'wix-eng-bar-track' }, [ invBar ] )
] ) );
 
// Divider
container.appendChild( wix.el( 'div', { className: 'wix-eng-divider' } ) );
 
// Total profit
var totalValEl = wix.el( 'div', { className: 'wix-eng-total-val', textContent: '\u20AC675,000' } );
var totalSub = wix.el( 'div', { className: 'wix-eng-total-sub', textContent: 'Underwriting + investment, before tax' } );
 
container.appendChild( wix.el( 'div', { className: 'wix-eng-total' }, [
wix.el( 'div', { className: 'wix-eng-total-head' }, [
wix.el( 'div', { className: 'wix-eng-total-label', textContent: 'Total pre-tax profit' } ),
totalValEl
] ),
totalSub
] ) );
 
// Callout
var callout = wix.el( 'div', { className: 'wix-eng-callout wix-eng-callout--profit', textContent: 'Both engines are contributing to profit.' } );
container.appendChild( callout );
 
 
/* ── Update Logic ─────────────────────────────────────────── */
 
function update() {
var claims = parseInt( claimsSlider.value, 10 );
var rate = parseFloat( investSlider.value ) / 100;
var uw = PREMIUMS - EXPENSES - claims;
var avgFloat = Math.round( PREMIUMS * 0.5 );
var inv = Math.round( avgFloat * rate );
var total = uw + inv;
var cr = ( claims + EXPENSES ) / PREMIUMS * 100;
 
// Slider displays
claimsVal.textContent = '\u20AC' + ( claims / 1000000 ).toFixed( 2 ) + 'M';
investVal.textContent = ( rate * 100 ).toFixed( 1 ) + '%';
 
// Engine 1: underwriting
var uwColor = uw >= 0 ? colorPos : colorNeg;
uwValEl.textContent = fmt( uw );
uwValEl.style.color = uwColor;
uwDetail.textContent = '\u20AC5,000,000 premiums \u2212 \u20AC1,200,000 expenses \u2212 \u20AC' + wix.formatNumber( claims ) + ' claims';
uwBar.style.width = Math.min( 100, Math.abs( uw ) / 1000000 * 100 ) + '%';
uwBar.style.background = uwColor;
 
// Combined ratio
crValEl.textContent = cr.toFixed( 1 ) + '%';
crValEl.style.color = cr > 100 ? colorNeg : colorPos;
if ( cr > 100 ) {
crSub.textContent = 'Above 100% \u2014 underwriting loss';
} else if ( cr === 100 ) {
crSub.textContent = 'Exactly 100% \u2014 break-even';
} else {
crSub.textContent = 'Below 100% \u2014 underwriting profit';
}
 
// Engine 2: investment
invValEl.textContent = fmt( inv );
invValEl.style.color = colorInv;
invDetail.textContent = 'The insurer collects \u20AC5M upfront and pays claims gradually. On average, \u20AC' +
wix.formatNumber( avgFloat ) + ' sits invested during the year, earning ' + ( rate * 100 ).toFixed( 1 ) + '%.';
invBar.style.width = Math.min( 100, inv / 200000 * 100 ) + '%';
invBar.style.background = colorInv;
 
// Total
totalValEl.textContent = fmt( total );
totalValEl.style.color = total >= 0 ? colorPos : colorNeg;
 
// Callout
if ( uw < 0 && total >= 0 ) {
callout.className = 'wix-eng-callout wix-eng-callout--profit';
callout.textContent = 'The combined ratio exceeds 100%, so underwriting alone is losing money. But investment income more than covers the gap \u2014 the insurer is still profitable overall.';
} else if ( uw < 0 && total < 0 ) {
callout.className = 'wix-eng-callout wix-eng-callout--loss';
callout.textContent = 'Investment income cannot offset the underwriting loss. The insurer is losing money overall.';
} else {
callout.className = 'wix-eng-callout wix-eng-callout--profit';
callout.textContent = 'Both engines are contributing to profit.';
}
 
// Sync scenario buttons
scenarioBtns.forEach( function ( btn ) {
if ( parseInt( btn.getAttribute( 'data-claims' ), 10 ) === claims ) {
btn.classList.add( 'wix-eng-scenario-btn--active' );
} else {
btn.classList.remove( 'wix-eng-scenario-btn--active' );
}
} );
}
 
 
/* ── Event Wiring ─────────────────────────────────────────── */
 
claimsSlider.addEventListener( 'input', update );
investSlider.addEventListener( 'input', update );
 
wix.on( strip, 'click', '.wix-eng-scenario-btn', function ( btn ) {
claimsSlider.value = btn.getAttribute( 'data-claims' );
update();
} );
 
// Initial render
update();
}