MediaWiki:Gadget-resign-calculator.js: Difference between revisions
Appearance
Drop the article, matching the FA format |
Stack calculator rows on narrow screens |
||
| Line 105: | Line 105: | ||
var form = document.createElement( 'div' ); | var form = document.createElement( 'div' ); | ||
form.className = 'tc-calc'; | form.className = 'tc-calc'; | ||
/** | |||
* One labelled row. Controls live in their own flex wrapper so that on a | |||
* narrow screen the label can stack above them while they stay in a row. | |||
*/ | |||
function row( labelText, controls, extraClass ) { | |||
var r = document.createElement( 'div' ); | |||
r.className = 'tc-calc-row' + ( extraClass ? ' ' + extraClass : '' ); | |||
var l = document.createElement( 'label' ); | |||
l.textContent = labelText; | |||
var c = document.createElement( 'div' ); | |||
c.className = 'tc-calc-controls'; | |||
controls.forEach( function ( el ) { c.appendChild( el ); } ); | |||
r.appendChild( l ); | |||
r.appendChild( c ); | |||
form.appendChild( r ); | |||
return c; | |||
} | |||
// --- Fantrax scores --- | // --- Fantrax scores --- | ||
for ( var i = 0; i < 3; i++ ) { | for ( var i = 0; i < 3; i++ ) { | ||
var input = document.createElement( 'input' ); | var input = document.createElement( 'input' ); | ||
| Line 118: | Line 131: | ||
input.min = '0'; | input.min = '0'; | ||
input.placeholder = [ 'season 1', 'season 2', 'season 3' ][ i ]; | input.placeholder = [ 'season 1', 'season 2', 'season 3' ][ i ]; | ||
scoreInputs.push( input ); | scoreInputs.push( input ); | ||
} | } | ||
row( 'Fantrax scores', scoreInputs, 'tc-scores' ); | |||
// --- term --- | // --- term --- | ||
var term = document.createElement( 'select' ); | var term = document.createElement( 'select' ); | ||
[ 3, 4, 5 ].forEach( function ( y ) { | [ 3, 4, 5 ].forEach( function ( y ) { | ||
| Line 136: | Line 143: | ||
term.appendChild( o ); | term.appendChild( o ); | ||
} ); | } ); | ||
row( 'Contract length', [ term ] ); | |||
// --- | // --- first season, to generate the contract line --- | ||
var season = document.createElement( 'input' ); | var season = document.createElement( 'input' ); | ||
season.type = 'number'; | season.type = 'number'; | ||
| Line 150: | Line 151: | ||
season.placeholder = String( UPCOMING_SEASON ); | season.placeholder = String( UPCOMING_SEASON ); | ||
season.value = String( UPCOMING_SEASON ); | season.value = String( UPCOMING_SEASON ); | ||
row( 'First season', [ season ] ); | |||
// --- team and player, for the post block --- | // --- team and player, for the post block --- | ||
var team = document.createElement( 'input' ); | var team = document.createElement( 'input' ); | ||
team.type = 'text'; | team.type = 'text'; | ||
team.placeholder = 'Celtics'; | team.placeholder = 'Celtics'; | ||
team. | team.className = 'tc-calc-wide'; | ||
row( 'Your team (optional)', [ team ] ); | |||
var name = document.createElement( 'input' ); | var name = document.createElement( 'input' ); | ||
name.type = 'text'; | name.type = 'text'; | ||
name.placeholder = 'Larry Bird'; | name.placeholder = 'Larry Bird'; | ||
name. | name.className = 'tc-calc-wide'; | ||
row( 'Player (optional)', [ name ] ); | |||
var out = document.createElement( 'div' ); | var out = document.createElement( 'div' ); | ||
Revision as of 14:30, 25 September 2026
/**
* 1TC RealGM — re-sign salary calculator.
* Implements the formula on [[NBA:Re-Signing]]. If the two ever disagree,
* the wiki page is the rule and this script is the bug.
*/
( function () {
'use strict';
// The league numbers a season by the year it ends in, so the season being
// signed for is always the next one up from the current calendar year.
var NEW_POST_URL = 'https://2teams1cup.com/forum/index.php?act=post&do=new_post&f=70';
var UPCOMING_SEASON = new Date().getFullYear() + 1,
MAX = 11500000,
DISCOUNT = 1500000,
CUTOFF = 85;
function money( n ) {
return '$' + n.toLocaleString( 'en-US' );
}
/** The seven steps, in the order the rules state them. */
function calculate( scores ) {
var avg = scores.reduce( function ( a, b ) { return a + b; }, 0 ) / scores.length,
raw = ( avg / 100 ) * MAX,
rounded = Math.round( raw / 100000 ) * 100000,
discounted = avg < CUTOFF ? rounded - DISCOUNT : rounded,
capped = Math.min( discounted, MAX );
return {
average: avg,
raw: raw,
rounded: rounded,
discountApplied: avg < CUTOFF,
salary: capped,
cappedAtMax: discounted > MAX,
nonPositive: capped <= 0
};
}
/** A monospace post block with a copy button. */
function postBlock( text ) {
var wrap = document.createElement( 'div' );
wrap.className = 'tc-calc-postwrap';
var pre = document.createElement( 'div' );
pre.className = 'tc-calc-post';
pre.textContent = text;
wrap.appendChild( pre );
var btn = document.createElement( 'button' );
btn.className = 'tc-calc-copy';
btn.type = 'button';
btn.textContent = 'Copy';
btn.addEventListener( 'click', function () {
function done( ok ) {
btn.textContent = ok ? 'Copied' : 'Press \u2318C / Ctrl+C';
setTimeout( function () { btn.textContent = 'Copy'; }, 2000 );
}
if ( navigator.clipboard && navigator.clipboard.writeText ) {
navigator.clipboard.writeText( text ).then( function () { done( true ); },
function () { selectFallback( pre ); done( false ); } );
} else {
selectFallback( pre );
done( false );
}
} );
wrap.appendChild( btn );
var actions = document.createElement( 'div' );
actions.className = 'tc-calc-actions';
var link = document.createElement( 'a' );
link.href = NEW_POST_URL;
link.target = '_blank';
link.rel = 'noopener';
link.textContent = 'Copy and start a new topic in NBA: Re-Signing';
// IPB has no parameter for seeding the post body, so the next best thing
// is to put it on the clipboard on the way out.
link.addEventListener( 'click', function () {
if ( navigator.clipboard && navigator.clipboard.writeText ) {
navigator.clipboard.writeText( text );
}
} );
actions.appendChild( link );
var outer = document.createElement( 'div' );
outer.appendChild( wrap );
outer.appendChild( actions );
return outer;
}
/** Clipboard API unavailable or refused: select the text so the reader can copy it. */
function selectFallback( el ) {
var range = document.createRange();
range.selectNodeContents( el );
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange( range );
}
function build( container ) {
var scoreInputs = [];
var form = document.createElement( 'div' );
form.className = 'tc-calc';
/**
* One labelled row. Controls live in their own flex wrapper so that on a
* narrow screen the label can stack above them while they stay in a row.
*/
function row( labelText, controls, extraClass ) {
var r = document.createElement( 'div' );
r.className = 'tc-calc-row' + ( extraClass ? ' ' + extraClass : '' );
var l = document.createElement( 'label' );
l.textContent = labelText;
var c = document.createElement( 'div' );
c.className = 'tc-calc-controls';
controls.forEach( function ( el ) { c.appendChild( el ); } );
r.appendChild( l );
r.appendChild( c );
form.appendChild( r );
return c;
}
// --- Fantrax scores ---
for ( var i = 0; i < 3; i++ ) {
var input = document.createElement( 'input' );
input.type = 'number';
input.step = '0.01';
input.min = '0';
input.placeholder = [ 'season 1', 'season 2', 'season 3' ][ i ];
scoreInputs.push( input );
}
row( 'Fantrax scores', scoreInputs, 'tc-scores' );
// --- term ---
var term = document.createElement( 'select' );
[ 3, 4, 5 ].forEach( function ( y ) {
var o = document.createElement( 'option' );
o.value = String( y );
o.textContent = y + ' years';
term.appendChild( o );
} );
row( 'Contract length', [ term ] );
// --- first season, to generate the contract line ---
var season = document.createElement( 'input' );
season.type = 'number';
season.step = '1';
season.placeholder = String( UPCOMING_SEASON );
season.value = String( UPCOMING_SEASON );
row( 'First season', [ season ] );
// --- team and player, for the post block ---
var team = document.createElement( 'input' );
team.type = 'text';
team.placeholder = 'Celtics';
team.className = 'tc-calc-wide';
row( 'Your team (optional)', [ team ] );
var name = document.createElement( 'input' );
name.type = 'text';
name.placeholder = 'Larry Bird';
name.className = 'tc-calc-wide';
row( 'Player (optional)', [ name ] );
var out = document.createElement( 'div' );
out.className = 'tc-calc-out';
form.appendChild( out );
function render() {
var scores = scoreInputs
.map( function ( el ) { return el.value.trim() === '' ? null : Number( el.value ); } )
.filter( function ( v ) { return v !== null && !isNaN( v ); } );
out.textContent = '';
if ( !scores.length ) {
var empty = document.createElement( 'p' );
empty.className = 'tc-calc-note';
empty.textContent = 'Enter at least one Fantrax score.';
out.appendChild( empty );
return;
}
var r = calculate( scores );
var big = document.createElement( 'div' );
big.className = 'tc-calc-big';
big.textContent = money( r.salary );
out.appendChild( big );
var steps = document.createElement( 'ol' );
steps.className = 'tc-calc-steps';
[
'Average of ' + scores.length + ' score' + ( scores.length === 1 ? '' : 's' ) +
': ' + r.average.toFixed( 2 ),
'× ' + money( MAX ) + ' = ' + money( Math.round( r.raw ) ),
'Rounded to nearest $100,000 = ' + money( r.rounded ),
r.discountApplied ?
'Average is below ' + CUTOFF + ', so − ' + money( DISCOUNT ) + ' = ' + money( r.rounded - DISCOUNT ) :
'Average is ' + CUTOFF + ' or above, so no discount'
].forEach( function ( t ) {
var li = document.createElement( 'li' );
li.textContent = t;
steps.appendChild( li );
} );
out.appendChild( steps );
if ( r.cappedAtMax ) {
var cap = document.createElement( 'div' );
cap.className = 'tc-calc-warn';
cap.textContent = 'Capped at the ' + money( MAX ) + ' maximum re-sign contract.';
out.appendChild( cap );
}
if ( r.nonPositive ) {
var warn = document.createElement( 'div' );
warn.className = 'tc-calc-warn';
warn.textContent = 'The formula produces ' + money( r.salary ) + ', which is not a usable ' +
'salary. The rules do not say what happens below this point — ask TAB before posting.';
out.appendChild( warn );
return;
}
// --- ready-to-paste post ---
var years = Number( term.value ),
first = parseInt( season.value, 10 ),
who = name.value.trim();
if ( isNaN( first ) ) {
first = UPCOMING_SEASON;
}
var endYear = first + years - 1;
// "Celtics offer Larry Bird 5 years" — team and player drop out if blank.
var tm = team.value.trim(),
term_ = years + ( years === 1 ? ' year' : ' years' ),
prefix = tm && who ? tm + ' offer ' + who :
( who || ( tm ? tm + ' offer' : '' ) );
var lines = [ prefix ? prefix + ' ' + term_ : term_ ];
lines.push( '(' + scores.join( ', ' ) + ' = ' +
( Number.isInteger( r.average ) ? r.average : r.average.toFixed( 2 ) ) + ')' );
lines.push( money( r.salary ) + ' (' + endYear + ')' );
out.appendChild( postBlock( lines.join( '\n' ) ) );
}
[].concat( scoreInputs, [ term, season, team, name ] ).forEach( function ( el ) {
el.addEventListener( 'input', render );
el.addEventListener( 'change', render );
} );
container.appendChild( form );
render();
}
mw.hook( 'wikipage.content' ).add( function () {
var el = document.getElementById( 'tc-resign-calculator' );
if ( el && !el.dataset.tcBuilt ) {
el.dataset.tcBuilt = '1';
build( el );
}
} );
}() );